├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── Cargo.toml ├── Justfile ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── benches └── bincode.rs ├── bin └── snapshot.rs ├── examples ├── linter.rs ├── parser.rs └── project │ ├── Awaitable.ara │ ├── Sequence.ara │ └── format.ara ├── src ├── lexer │ ├── byte_string.rs │ ├── internal │ │ ├── identifier.rs │ │ ├── mod.rs │ │ ├── number.rs │ │ ├── string.rs │ │ └── variable.rs │ ├── issue.rs │ ├── iterator.rs │ ├── macros.rs │ ├── mod.rs │ ├── result.rs │ ├── state │ │ ├── mod.rs │ │ └── source_bytes.rs │ └── token.rs ├── lib.rs ├── parser │ ├── internal │ │ ├── definition │ │ │ ├── attribute.rs │ │ │ ├── class.rs │ │ │ ├── constant.rs │ │ │ ├── enum.rs │ │ │ ├── function.rs │ │ │ ├── interface.rs │ │ │ ├── mod.rs │ │ │ ├── modifier.rs │ │ │ ├── namespace.rs │ │ │ ├── parameter.rs │ │ │ ├── property.rs │ │ │ ├── template.rs │ │ │ ├── type.rs │ │ │ └── use.rs │ │ ├── expression │ │ │ ├── argument.rs │ │ │ ├── array.rs │ │ │ ├── class.rs │ │ │ ├── control_flow.rs │ │ │ ├── function.rs │ │ │ ├── generic.rs │ │ │ ├── infix.rs │ │ │ ├── mod.rs │ │ │ ├── postfix.rs │ │ │ └── precedence.rs │ │ ├── identifier.rs │ │ ├── mod.rs │ │ ├── statement │ │ │ ├── block.rs │ │ │ ├── control_flow.rs │ │ │ ├── loop.rs │ │ │ ├── mod.rs │ │ │ └── try.rs │ │ ├── utils.rs │ │ └── variable.rs │ ├── issue.rs │ ├── macros.rs │ ├── mod.rs │ ├── result.rs │ └── state │ │ └── mod.rs ├── traverser │ ├── mod.rs │ └── visitor.rs └── tree │ ├── comment.rs │ ├── definition │ ├── attribute.rs │ ├── class.rs │ ├── constant.rs │ ├── enum.rs │ ├── function.rs │ ├── interface.rs │ ├── mod.rs │ ├── modifier.rs │ ├── namespace.rs │ ├── property.rs │ ├── template.rs │ ├── type.rs │ └── use.rs │ ├── expression │ ├── argument.rs │ ├── array.rs │ ├── class.rs │ ├── construct.rs │ ├── control_flow.rs │ ├── function.rs │ ├── generic.rs │ ├── literal.rs │ ├── magic_constant.rs │ ├── mod.rs │ └── operator.rs │ ├── identifier.rs │ ├── mod.rs │ ├── statement │ ├── block.rs │ ├── control_flow.rs │ ├── expression.rs │ ├── loop.rs │ ├── mod.rs │ ├── return.rs │ └── try.rs │ ├── token.rs │ ├── utils.rs │ └── variable.rs └── tests ├── bincode_test.rs ├── samples ├── 0001 │ ├── code.ara │ └── tree.txt ├── 0002 │ ├── code.ara │ └── tree.txt ├── 0003 │ ├── code.ara │ └── error.txt ├── 0004 │ ├── code.ara │ └── tree.txt ├── 0005 │ ├── code.ara │ └── tree.txt ├── 0006 │ ├── code.ara │ └── tree.txt ├── 0007 │ ├── code.ara │ └── tree.txt ├── 0008 │ ├── code.ara │ └── tree.txt ├── 0009 │ ├── code.ara │ └── tree.txt ├── 0010 │ ├── code.ara │ └── tree.txt ├── 0011 │ ├── code.ara │ └── tree.txt ├── 0012 │ ├── code.ara │ └── tree.txt ├── 0013 │ ├── code.ara │ └── tree.txt ├── 0014 │ ├── code.ara │ └── tree.txt ├── 0015 │ ├── code.ara │ └── tree.txt ├── 0016 │ ├── code.ara │ └── tree.txt ├── 0017 │ ├── code.ara │ └── tree.txt ├── 0018 │ ├── code.ara │ └── tree.txt ├── 0019 │ ├── code.ara │ └── tree.txt ├── 0020 │ ├── code.ara │ └── tree.txt ├── 0021 │ ├── code.ara │ └── tree.txt ├── 0022 │ ├── code.ara │ └── tree.txt ├── 0023 │ ├── code.ara │ └── error.txt ├── 0024 │ ├── code.ara │ └── tree.txt ├── 0025 │ ├── code.ara │ └── tree.txt ├── 0026 │ ├── code.ara │ └── tree.txt ├── 0027 │ ├── code.ara │ └── tree.txt ├── 0028 │ ├── code.ara │ └── tree.txt ├── 0029 │ ├── code.ara │ └── error.txt ├── 0030 │ ├── code.ara │ └── tree.txt ├── 0031 │ ├── code.ara │ └── tree.txt ├── 0032 │ ├── code.ara │ └── tree.txt ├── 0033 │ ├── code.ara │ └── tree.txt ├── 0034 │ ├── code.ara │ └── tree.txt ├── 0035 │ ├── code.ara │ └── tree.txt ├── 0036 │ ├── code.ara │ └── tree.txt ├── 0037 │ ├── code.ara │ └── tree.txt ├── 0038 │ ├── code.ara │ └── tree.txt ├── 0039 │ ├── code.ara │ └── tree.txt ├── 0040 │ ├── code.ara │ └── tree.txt ├── 0041 │ ├── code.ara │ └── tree.txt ├── 0042 │ ├── code.ara │ └── tree.txt ├── 0043 │ ├── code.ara │ └── error.txt ├── 0044 │ ├── code.ara │ └── tree.txt ├── 0045 │ ├── code.ara │ └── tree.txt ├── 0046 │ ├── code.ara │ └── tree.txt ├── 0047 │ ├── code.ara │ └── tree.txt ├── 0048 │ ├── code.ara │ └── tree.txt ├── 0049 │ ├── code.ara │ └── tree.txt ├── 0050 │ ├── code.ara │ └── tree.txt ├── 0051 │ ├── code.ara │ └── tree.txt ├── 0052 │ ├── code.ara │ └── tree.txt ├── 0053 │ ├── code.ara │ └── tree.txt ├── 0054 │ ├── code.ara │ └── tree.txt ├── 0055 │ ├── code.ara │ └── tree.txt ├── 0056 │ ├── code.ara │ └── tree.txt ├── 0057 │ ├── code.ara │ └── tree.txt ├── 0058 │ ├── code.ara │ └── tree.txt ├── 0059 │ ├── code.ara │ └── tree.txt ├── 0060 │ ├── code.ara │ └── tree.txt ├── 0061 │ ├── code.ara │ └── tree.txt ├── 0062 │ ├── code.ara │ └── tree.txt ├── 0063 │ ├── code.ara │ └── tree.txt ├── 0064 │ ├── code.ara │ └── tree.txt ├── 0065 │ ├── code.ara │ └── error.txt ├── 0066 │ ├── code.ara │ └── tree.txt ├── 0067 │ ├── code.ara │ ├── error.txt │ └── parse-error.txt ├── 0068 │ ├── code.ara │ ├── error.txt │ └── parse-error.txt ├── 0069 │ ├── code.ara │ └── tree.txt ├── 0070 │ ├── code.ara │ └── tree.txt ├── 0071 │ ├── code.ara │ └── tree.txt ├── 0072 │ ├── code.ara │ └── tree.txt ├── 0073 │ ├── code.ara │ └── tree.txt ├── 0074 │ ├── code.ara │ └── tree.txt ├── 0075 │ ├── code.ara │ └── tree.txt ├── 0076 │ ├── code.ara │ └── tree.txt ├── 0077 │ ├── code.ara │ ├── error.txt │ └── parse-error.txt ├── 0078 │ ├── code.ara │ └── tree.txt ├── 0079 │ ├── code.ara │ └── tree.txt ├── 0080 │ ├── code.ara │ └── tree.txt ├── 0081 │ ├── code.ara │ └── tree.txt ├── 0082 │ ├── code.ara │ └── tree.txt ├── 0083 │ ├── code.ara │ └── error.txt ├── 0084 │ ├── code.ara │ └── tree.txt ├── 0085 │ ├── code.ara │ └── tree.txt ├── 0086 │ ├── code.ara │ └── error.txt ├── 0087 │ ├── code.ara │ └── tree.txt ├── 0088 │ ├── code.ara │ └── tree.txt ├── 0089 │ ├── code.ara │ └── tree.txt ├── 0090 │ ├── code.ara │ └── tree.txt ├── 0091 │ ├── code.ara │ └── tree.txt ├── 0092 │ ├── code.ara │ └── tree.txt ├── 0093 │ ├── code.ara │ └── tree.txt ├── 0094 │ ├── code.ara │ └── tree.txt ├── 0095 │ ├── code.ara │ └── tree.txt ├── 0096 │ ├── code.ara │ └── tree.txt ├── 0097 │ ├── code.ara │ └── tree.txt ├── 0098 │ ├── code.ara │ └── tree.txt ├── 0099 │ ├── code.ara │ └── tree.txt ├── 0100 │ ├── code.ara │ └── tree.txt ├── 0101 │ ├── code.ara │ └── tree.txt ├── 0102 │ ├── code.ara │ └── tree.txt ├── 0103 │ ├── code.ara │ └── tree.txt ├── 0104 │ ├── code.ara │ └── tree.txt ├── 0105 │ ├── code.ara │ └── tree.txt ├── 0106 │ ├── code.ara │ └── tree.txt ├── 0107 │ ├── code.ara │ └── tree.txt ├── 0108 │ ├── code.ara │ └── tree.txt ├── 0109 │ ├── code.ara │ └── tree.txt ├── 0110 │ ├── code.ara │ └── tree.txt ├── 0111 │ ├── code.ara │ └── tree.txt ├── 0112 │ ├── code.ara │ └── tree.txt ├── 0113 │ ├── code.ara │ └── tree.txt ├── 0114 │ ├── code.ara │ └── tree.txt └── 0115 │ ├── code.ara │ └── tree.txt └── test.rs /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | open_collective: php-standard-library 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | 8 | jobs: 9 | tests: 10 | name: ci 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | rust: 16 | - 'stable' 17 | - 'nightly' 18 | os: 19 | - 'ubuntu-latest' 20 | 21 | steps: 22 | - name: checkout 23 | uses: actions/checkout@v3 24 | 25 | - name: install rust 26 | uses: actions-rs/toolchain@v1 27 | with: 28 | toolchain: ${{ matrix.rust }} 29 | override: true 30 | components: rustfmt, clippy 31 | 32 | - name: install php 33 | uses: shivammathur/setup-php@v2 34 | with: 35 | php-version: 8.1 36 | tools: composer:v2 37 | env: 38 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 39 | 40 | - name: cache 41 | id: cache 42 | uses: actions/cache@v3 43 | with: 44 | path: | 45 | ~/.cargo/bin/ 46 | ~/.cargo/registry/index/ 47 | ~/.cargo/registry/cache/ 48 | ~/.cargo/git/db/ 49 | target/ 50 | key: rust-${{ inputs.rust }}-os-${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 51 | 52 | - name: check 53 | uses: actions-rs/cargo@v1 54 | with: 55 | command: check 56 | 57 | - name: fmt 58 | if: matrix.rust == 'stable' 59 | uses: actions-rs/cargo@v1 60 | with: 61 | command: fmt 62 | args: --all --check 63 | 64 | - name: clippy 65 | if: matrix.rust == 'stable' 66 | uses: actions-rs/cargo@v1 67 | with: 68 | command: clippy 69 | 70 | - name: test 71 | uses: actions-rs/cargo@v1 72 | with: 73 | command: test 74 | args: -r --all 75 | 76 | - name: bench 77 | uses: actions-rs/cargo@v1 78 | with: 79 | command: bench 80 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at azjezz@protonmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [https://www.contributor-covenant.org/version/1/4/code-of-conduct.html](https://www.contributor-covenant.org/version/1/4/code-of-conduct.html) 72 | 73 | [homepage]: [https://www.contributor-covenant.org](https://www.contributor-covenant.org) 74 | 75 | For answers to common questions about this code of conduct, see 76 | [https://www.contributor-covenant.org/faq](https://www.contributor-covenant.org/faq) 77 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ara_parser" 3 | description = "A fault-tolerant, recursive-descent parser for Ara Programming Language 🌲" 4 | repository = "https://github.com/ara-lang/ara" 5 | version = "0.6.6" 6 | edition = "2021" 7 | license = "MIT OR Apache-2.0" 8 | authors = ["Saif Eddin Gmati "] 9 | 10 | [lib] 11 | doctest = false 12 | 13 | [[bin]] 14 | name = "ara-internal-snapshot" 15 | path = "bin/snapshot.rs" 16 | 17 | [dependencies] 18 | ara_source = { version = "0.2.0" } 19 | ara_reporting = { version = "0.6.1" } 20 | schemars = { version = "0.8.11" } 21 | serde = { version = "1.0.149", features = ["derive"] } 22 | serde_json = { version = "1.0.89" } 23 | bincode = { version = "2.0.0-rc.2" } 24 | 25 | [dev-dependencies] 26 | criterion = "0.4" 27 | pretty_assertions = { version = "1.3.0" } 28 | 29 | [[bench]] 30 | name = "bincode" 31 | harness = false 32 | 33 | [profile.release] 34 | opt-level = 3 35 | debug = false 36 | strip = 'symbols' 37 | debug-assertions = false 38 | overflow-checks = false 39 | lto = 'fat' 40 | panic = 'abort' 41 | incremental = true 42 | codegen-units = 1 43 | rpath = true 44 | -------------------------------------------------------------------------------- /Justfile: -------------------------------------------------------------------------------- 1 | default: 2 | @just --list 3 | 4 | # build the library 5 | build: 6 | cargo build 7 | 8 | # regenerate test snapshots 9 | snapshot: 10 | cargo run --bin ara-internal-snapshot 11 | 12 | # detect linting problems. 13 | lint: 14 | cargo fmt --all -- --check 15 | cargo clippy 16 | 17 | # fix linting problems. 18 | fix: 19 | cargo fmt 20 | cargo clippy --fix --allow-dirty --allow-staged 21 | cargo fix --allow-dirty --allow-staged 22 | 23 | # run all integration tests, except third-party. 24 | test filter='--all': 25 | cargo test -r {{filter}} 26 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022-2023 Saif Eddin Gmati 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `Ara` Parser 2 | 3 | [![Actions Status](https://github.com/ara-lang/parser/workflows/ci/badge.svg)](https://github.com/ara-lang/parser/actions) 4 | [![Crates.io](https://img.shields.io/crates/v/ara_parser.svg)](https://crates.io/crates/ara_parser) 5 | [![Docs](https://docs.rs/ara_parser/badge.svg)](https://docs.rs/ara_parser/latest/ara_parser/) 6 | 7 | A fault-tolerant, recursive-descent parser for `Ara` Programming Language 🌲 8 | 9 | > **Note:** This project is a hard-fork of [`php-rust-tools/parser`](https://github.com/php-rust-tools/parser) 10 | > 11 | > Special thanks to the original [authors](https://github.com/php-rust-tools/parser/graphs/contributors) for their work. 12 | 13 | --- 14 | 15 | ## Usage 16 | 17 | Add `ara_parser` to your `Cargo.toml`, and you're good to go! 18 | 19 | ```toml 20 | [dependencies] 21 | ara_parser = "0.6.6" 22 | ``` 23 | 24 | ## Example 25 | 26 | ```rust 27 | use ara_parser::parser; 28 | use ara_reporting::builder::CharSet; 29 | use ara_reporting::builder::ColorChoice; 30 | use ara_reporting::builder::ReportBuilder; 31 | use ara_reporting::error::Error; 32 | use ara_source::loader::load_directories; 33 | 34 | fn main() -> Result<(), Error> { 35 | let source_map = load_directories("/path/to/project", vec!["src/"]).unwrap(); 36 | 37 | match parser::parse_map(&source_map) { 38 | Ok(tree_map) => tree_map.trees.iter().for_each(|tree| { 39 | println!("{:#?}", tree.definitions); 40 | }), 41 | Err(report) => { 42 | ReportBuilder::new(&source_map) 43 | .with_charset(CharSet::Unicode) 44 | .with_colors(ColorChoice::Always) 45 | .print(report.as_ref())?; 46 | } 47 | } 48 | 49 | Ok(()) 50 | } 51 | ``` 52 | 53 | ## Documentation 54 | 55 | See the [documentation](https://ara-lang.io) for more information. 56 | 57 | ## License 58 | 59 | Licensed under either of 60 | 61 | * Apache License, Version 2.0 62 | ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 63 | * MIT license 64 | ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 65 | 66 | at your option. 67 | 68 | ## Contribution 69 | 70 | Unless you explicitly state otherwise, any contribution intentionally submitted 71 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 72 | dual licensed as above, without any additional terms or conditions. 73 | 74 | ## Credits 75 | 76 | * [Saif Eddin Gmati](https://github.com/azjezz) 77 | * [All contributors](https://github.com/ara-lang/parser/graphs/contributors) 78 | -------------------------------------------------------------------------------- /bin/snapshot.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::fs::read_dir; 3 | use std::io; 4 | use std::path::PathBuf; 5 | 6 | use ara_parser::parser; 7 | use ara_reporting::builder::CharSet; 8 | use ara_reporting::builder::ColorChoice; 9 | use ara_reporting::builder::ReportBuilder; 10 | use ara_source::loader::FileSourceLoader; 11 | use ara_source::loader::SourceLoader; 12 | 13 | fn main() -> io::Result<()> { 14 | let manifest = env::var("CARGO_MANIFEST_DIR").unwrap(); 15 | let root = format!("{manifest}/tests/samples/"); 16 | 17 | let mut entries = read_dir(&root)? 18 | .flatten() 19 | .map(|entry| entry.path()) 20 | .filter(|entry| entry.is_dir()) 21 | .collect::>(); 22 | 23 | entries.sort(); 24 | 25 | let loader = FileSourceLoader::new(&root); 26 | for entry in entries { 27 | let code_filename = entry.join("code.ara"); 28 | let tree_filename = entry.join("tree.txt"); 29 | let error_filename = entry.join("error.txt"); 30 | 31 | if !code_filename.exists() { 32 | continue; 33 | } 34 | 35 | if tree_filename.exists() { 36 | std::fs::remove_file(&tree_filename)?; 37 | } 38 | 39 | if error_filename.exists() { 40 | std::fs::remove_file(&error_filename)?; 41 | } 42 | 43 | let source_map = loader.load(&code_filename).unwrap(); 44 | match parser::parse(&source_map.sources[0]) { 45 | Ok(tree) => { 46 | std::fs::write(tree_filename, format!("{:#?}", tree.definitions))?; 47 | println!( 48 | "✅ generated `tree.txt` for `{}`", 49 | source_map.sources[0].name() 50 | ); 51 | } 52 | Err(report) => { 53 | let builder = ReportBuilder::new(&source_map) 54 | .with_charset(CharSet::Ascii) 55 | .with_colors(ColorChoice::Never); 56 | 57 | let error = builder 58 | .as_string(report.as_ref()) 59 | .expect("failed to build the report"); 60 | 61 | std::fs::write(error_filename, error)?; 62 | 63 | println!( 64 | "✅ generated `error.txt` for `{}`", 65 | source_map.sources[0].name() 66 | ); 67 | } 68 | } 69 | } 70 | 71 | Ok(()) 72 | } 73 | -------------------------------------------------------------------------------- /examples/linter.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | use ara_parser::parser; 4 | use ara_parser::traverser::visitor::NodeVisitor; 5 | use ara_parser::traverser::TreeTraverser; 6 | use ara_parser::tree::definition::function::FunctionLikeParameterDefinition; 7 | use ara_parser::tree::downcast; 8 | use ara_parser::tree::Node; 9 | use ara_reporting::annotation::Annotation; 10 | use ara_reporting::builder::CharSet; 11 | use ara_reporting::builder::ColorChoice; 12 | use ara_reporting::builder::ReportBuilder; 13 | use ara_reporting::error::Error; 14 | use ara_reporting::issue::Issue; 15 | use ara_reporting::Report; 16 | use ara_source::loader::FileSourceLoader; 17 | use ara_source::loader::SourceLoader; 18 | 19 | struct NoVariadicParameterRuleVisitor; 20 | 21 | impl NodeVisitor for NoVariadicParameterRuleVisitor { 22 | fn visit( 23 | &mut self, 24 | source: &str, 25 | node: &dyn Node, 26 | _parent: Option<&dyn Node>, 27 | ) -> Result<(), Issue> { 28 | if let Some(parameter) = downcast::(node) { 29 | if let Some(position) = parameter.ellipsis { 30 | let issue = Issue::warning("some-code", "variadic parameters are forbidden") 31 | .with_source(source, position, position + 3) 32 | .with_annotation(Annotation::secondary( 33 | source, 34 | parameter.initial_position(), 35 | parameter.final_position(), 36 | )); 37 | 38 | return Err(issue); 39 | } 40 | } 41 | 42 | Ok(()) 43 | } 44 | } 45 | 46 | fn main() -> Result<(), Error> { 47 | let cargo_manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); 48 | 49 | let loader = FileSourceLoader::new(&cargo_manifest_dir); 50 | 51 | let source_map = loader.load(&"examples/project/format.ara").unwrap(); 52 | 53 | match parser::parse_map(&source_map) { 54 | Ok(tree_map) => { 55 | let mut traverser = 56 | TreeTraverser::new(vec![Box::new(NoVariadicParameterRuleVisitor {})]); 57 | 58 | match traverser.traverse(&tree_map) { 59 | Ok(_) => {} 60 | Err(issues) => { 61 | let report = Report { 62 | issues, 63 | footer: None, 64 | }; 65 | ReportBuilder::new(&source_map) 66 | .with_charset(CharSet::Unicode) 67 | .with_colors(ColorChoice::Always) 68 | .print(&report) 69 | .unwrap(); 70 | } 71 | } 72 | } 73 | Err(report) => { 74 | ReportBuilder::new(&source_map) 75 | .with_charset(CharSet::Unicode) 76 | .with_colors(ColorChoice::Always) 77 | .print(report.as_ref())?; 78 | } 79 | } 80 | 81 | Ok(()) 82 | } 83 | -------------------------------------------------------------------------------- /examples/parser.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | use ara_parser::parser; 4 | use ara_reporting::builder::CharSet; 5 | use ara_reporting::builder::ColorChoice; 6 | use ara_reporting::builder::ReportBuilder; 7 | use ara_reporting::error::Error; 8 | use ara_source::loader::load_directories; 9 | 10 | fn main() -> Result<(), Error> { 11 | let cargo_manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); 12 | 13 | let source_map = load_directories(cargo_manifest_dir, vec!["examples/project/"]).unwrap(); 14 | 15 | match parser::parse_map(&source_map) { 16 | Ok(tree_map) => tree_map.trees.iter().for_each(|tree| { 17 | println!("{:#?}", tree.definitions); 18 | }), 19 | Err(report) => { 20 | ReportBuilder::new(&source_map) 21 | .with_charset(CharSet::Unicode) 22 | .with_colors(ColorChoice::Always) 23 | .print(report.as_ref())?; 24 | } 25 | } 26 | 27 | Ok(()) 28 | } 29 | -------------------------------------------------------------------------------- /examples/project/Sequence.ara: -------------------------------------------------------------------------------- 1 | 16 | { 17 | private bool $ingoing = false; 18 | 19 | private vec $pending = vec[]; 20 | private vec $waits = vec[]; 21 | 22 | public function __construct( 23 | private readonly Closure<(I), O> $operation, 24 | ) { 25 | } 26 | 27 | /** 28 | * Run the operation using the given `$input`, after all previous operations have completed. 29 | */ 30 | public function waitFor(I $input): O 31 | { 32 | if $this->ingoing { 33 | $suspension = EventLoop::getSuspension::(); 34 | $this->pending[] = $suspension; 35 | 36 | $suspension->suspend(); 37 | } 38 | 39 | $this->ingoing = true; 40 | 41 | try { 42 | ($this->operation)($input) 43 | } finally { 44 | $suspension = $this->pending[0] ?? null; 45 | if $suspension !== null { 46 | $this->pending = array_slice($this->pending, 1); 47 | $suspension->resume(); 48 | } else { 49 | foreach $this->waits as $suspension { 50 | $suspension->resume(); 51 | } 52 | 53 | $this->waits = vec[]; 54 | $this->ingoing = false; 55 | } 56 | } 57 | } 58 | 59 | /** 60 | * Cancel all pending operations. 61 | * 62 | * Any pending operation will fail with the given exception. 63 | * 64 | * Future operations will continue execution as usual. 65 | */ 66 | public function cancel(Exception $exception): void 67 | { 68 | $suspensions = $this->pending; 69 | $this->pending = vec[]; 70 | 71 | foreach $suspensions as $suspension { 72 | $suspension->throw($exception); 73 | } 74 | } 75 | 76 | /** 77 | * Get the number of operations pending execution. 78 | */ 79 | public function getPendingOperations(): int 80 | { 81 | count($this->pending) 82 | } 83 | 84 | /** 85 | * Check if there's any operations pending execution. 86 | * 87 | * If this method returns `true`, it means future calls to `waitFor` will wait. 88 | */ 89 | public function hasPendingOperations(): bool 90 | { 91 | $this->pending !== vec[] 92 | } 93 | 94 | /** 95 | * Check if the sequence has any ingoing operations. 96 | * 97 | * If this method returns `true`, it means future calls to `waitFor` will wait. 98 | * If this method returns `false`, it means future calls to `waitFor` will execute immediately. 99 | */ 100 | public function hasIngoingOperations(): bool 101 | { 102 | $this->ingoing 103 | } 104 | 105 | /** 106 | * Wait for all pending operations to finish execution. 107 | */ 108 | public function waitForPending(): void 109 | { 110 | if !$this->ingoing { 111 | return; 112 | } 113 | 114 | $suspension = EventLoop::getSuspension::(); 115 | $this->waits[] = $suspension; 116 | 117 | $suspension->suspend(); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /examples/project/format.ara: -------------------------------------------------------------------------------- 1 | namespace Psl\Str; 2 | 3 | type scalar = bool | float | int | string; 4 | 5 | function format(string $format, scalar ...$args): string { 6 | return \sprintf($format, ...$args); 7 | } 8 | -------------------------------------------------------------------------------- /src/lexer/internal/variable.rs: -------------------------------------------------------------------------------- 1 | use crate::lexer::byte_string::ByteString; 2 | use crate::lexer::internal::identifier; 3 | use crate::lexer::state::State; 4 | use crate::lexer::token::TokenKind; 5 | 6 | pub fn tokenize(state: &mut State) -> (TokenKind, ByteString) { 7 | let mut var = state.bytes.read_and_skip(1).to_vec(); 8 | 9 | var.extend(identifier::consume(state)); 10 | 11 | (TokenKind::Variable, var.into()) 12 | } 13 | -------------------------------------------------------------------------------- /src/lexer/issue.rs: -------------------------------------------------------------------------------- 1 | #![macro_use] 2 | 3 | use ara_reporting::issue::Issue; 4 | 5 | use crate::lexer::state::State; 6 | 7 | #[derive(Debug, Copy, Clone)] 8 | #[repr(u8)] 9 | pub enum LexerIssueCode { 10 | /// An unreachable code was encountered. 11 | UnreachableCode = 0, 12 | 13 | /// An unclosed string literal was encountered. 14 | /// 15 | /// Example: 16 | /// 17 | /// ```ara 18 | /// function foo(): void { 19 | /// $a = "Hello, World! 20 | /// } 21 | /// ``` 22 | UnclosedStringLiteral = 1, 23 | 24 | /// An invalid unicode escape sequence was encountered. 25 | /// 26 | /// Example: 27 | /// 28 | /// ```ara 29 | /// function foo(): void { 30 | /// $a = "\u{1234567890}"; 31 | /// } 32 | /// ``` 33 | InvalidUnicodeEscapeSequence = 2, 34 | 35 | /// An invalid octal escape sequence was encountered. 36 | /// 37 | /// Example: 38 | /// 39 | /// ```ara 40 | /// function foo(): void { 41 | /// $a = "\1234567890"; 42 | /// } 43 | /// ``` 44 | InvalidOctalEscapeSequence = 3, 45 | 46 | /// An unrecognizable token was encountered. 47 | UnrecognizableToken = 4, 48 | } 49 | 50 | pub(crate) fn unreachable_code>(state: &State, message: M) -> Issue { 51 | let position = state.bytes.position(); 52 | 53 | Issue::bug(LexerIssueCode::UnreachableCode, message).with_source( 54 | state.source.name(), 55 | position, 56 | position + 1, 57 | ) 58 | } 59 | 60 | pub(crate) fn unclosed_string_literal(state: &State, from: usize) -> Issue { 61 | Issue::error( 62 | LexerIssueCode::UnclosedStringLiteral, 63 | "Unclosed string literal", 64 | ) 65 | .with_source(state.source.name(), from, state.bytes.position()) 66 | } 67 | 68 | pub(crate) fn invalid_unicode_escape_sequence(state: &State, from: usize) -> Issue { 69 | Issue::error( 70 | LexerIssueCode::InvalidUnicodeEscapeSequence, 71 | "Invalid unicode escape sequence", 72 | ) 73 | .with_source(state.source.name(), from, state.bytes.position()) 74 | } 75 | 76 | pub(crate) fn invalid_octal_escape_sequence(state: &State, from: usize) -> Issue { 77 | Issue::error( 78 | LexerIssueCode::InvalidOctalEscapeSequence, 79 | "Invalid octal escape sequence", 80 | ) 81 | .with_source(state.source.name(), from, state.bytes.position()) 82 | } 83 | 84 | pub(crate) fn unrecognizable_token(state: &State) -> Issue { 85 | let position = state.bytes.position(); 86 | 87 | Issue::error(LexerIssueCode::UnrecognizableToken, "Unrecognizable token").with_source( 88 | state.source.name(), 89 | position, 90 | position + 1, 91 | ) 92 | } 93 | 94 | impl ::std::fmt::Display for LexerIssueCode { 95 | fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { 96 | write!(f, "L{:04}", *self as u8) 97 | } 98 | } 99 | 100 | impl From for String { 101 | fn from(code: LexerIssueCode) -> String { 102 | format!("{code}") 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/lexer/macros.rs: -------------------------------------------------------------------------------- 1 | // Reusable pattern for the first byte of an identifier. 2 | #[macro_export] 3 | macro_rules! ident_start { 4 | () => { 5 | b'a'..=b'z' | b'A'..=b'Z' | b'_' | b'\x80'..=b'\xff' 6 | }; 7 | } 8 | 9 | // Reusable pattern for identifier after the first byte. 10 | #[macro_export] 11 | macro_rules! ident { 12 | () => { 13 | b'0'..=b'9' | b'a'..=b'z' | b'A'..=b'Z' | b'_' | b'\x80'..=b'\xff' 14 | }; 15 | } 16 | 17 | #[macro_export] 18 | macro_rules! lexer_bail { 19 | ($state:expr, $issue:ident($($args:expr),+$(,)?)$(,)?) => { 20 | { 21 | let issue = $crate::lexer::issue::$issue($state, $($args,)+); 22 | 23 | return Err(Box::new(issue)); 24 | } 25 | }; 26 | ($state:expr, $issue:ident) => { 27 | { 28 | let issue = $crate::lexer::issue::$issue($state); 29 | 30 | return Err(Box::new(issue)); 31 | } 32 | }; 33 | } 34 | -------------------------------------------------------------------------------- /src/lexer/mod.rs: -------------------------------------------------------------------------------- 1 | use ara_reporting::issue::Issue; 2 | use ara_source::source::Source; 3 | 4 | use crate::lexer::byte_string::ByteString; 5 | use crate::lexer::state::State; 6 | use crate::lexer::token::Token; 7 | use crate::lexer::token::TokenKind; 8 | 9 | pub mod byte_string; 10 | pub mod issue; 11 | pub mod iterator; 12 | pub mod token; 13 | 14 | pub(in crate::lexer) mod internal; 15 | pub(in crate::lexer) mod macros; 16 | pub(in crate::lexer) mod result; 17 | pub(in crate::lexer) mod state; 18 | 19 | pub fn lex(source: &Source) -> Result, Box> { 20 | let mut state = State::new(source); 21 | let mut tokens = Vec::new(); 22 | 23 | while !state.bytes.eof() { 24 | while let Some(true) = state.bytes.current().map(|u: &u8| u.is_ascii_whitespace()) { 25 | state.bytes.next(); 26 | } 27 | 28 | // If we have consumed whitespace and then reached the end of the file, we should break. 29 | if state.bytes.eof() { 30 | break; 31 | } 32 | 33 | tokens.push(internal::tokenize(&mut state)?); 34 | } 35 | 36 | tokens.push(Token { 37 | kind: TokenKind::Eof, 38 | position: state.bytes.position(), 39 | value: ByteString::default(), 40 | }); 41 | 42 | Ok(tokens) 43 | } 44 | -------------------------------------------------------------------------------- /src/lexer/result.rs: -------------------------------------------------------------------------------- 1 | use ara_reporting::issue::Issue; 2 | 3 | pub type SyntaxResult = Result>; 4 | -------------------------------------------------------------------------------- /src/lexer/state/mod.rs: -------------------------------------------------------------------------------- 1 | use ara_source::source::Source; 2 | 3 | use crate::lexer::state::source_bytes::SourceBytes; 4 | 5 | pub mod source_bytes; 6 | 7 | #[derive(Debug)] 8 | pub struct State<'a> { 9 | pub source: &'a Source, 10 | pub bytes: SourceBytes<'a>, 11 | } 12 | 13 | impl<'a> State<'a> { 14 | pub fn new(source: &'a Source) -> Self { 15 | Self { 16 | source, 17 | bytes: SourceBytes::new(source.content.as_bytes()), 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/lexer/state/source_bytes.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | pub struct SourceBytes<'a> { 3 | input: &'a [u8], 4 | length: usize, 5 | position: usize, 6 | } 7 | 8 | impl<'a> SourceBytes<'a> { 9 | pub fn new(input: &'a [u8]) -> Self { 10 | let input = input; 11 | let length = input.len(); 12 | 13 | Self { 14 | input, 15 | length, 16 | position: 0, 17 | } 18 | } 19 | 20 | pub const fn position(&self) -> usize { 21 | self.position 22 | } 23 | 24 | pub const fn eof(&self) -> bool { 25 | self.position >= self.length 26 | } 27 | 28 | pub fn next(&mut self) { 29 | self.position += 1; 30 | } 31 | 32 | pub fn skip(&mut self, count: usize) { 33 | for _ in 0..count { 34 | self.next(); 35 | } 36 | } 37 | 38 | pub fn read_and_skip(&mut self, count: usize) -> &'a [u8] { 39 | let (from, until) = self.to_bound(count); 40 | 41 | self.skip(count); 42 | 43 | &self.input[from..until] 44 | } 45 | 46 | pub fn current(&self) -> Option<&'a u8> { 47 | if self.position >= self.length { 48 | None 49 | } else { 50 | Some(&self.input[self.position]) 51 | } 52 | } 53 | 54 | pub fn read(&self, n: usize) -> &'a [u8] { 55 | let (from, until) = self.to_bound(n); 56 | 57 | &self.input[from..until] 58 | } 59 | 60 | pub fn at_case_insensitive(&self, search: &[u8], len: usize) -> bool { 61 | let (from, until) = self.to_bound(len); 62 | 63 | let slice = &self.input[from..until]; 64 | 65 | slice.eq_ignore_ascii_case(search) 66 | } 67 | 68 | pub fn peek(&self, i: usize, n: usize) -> &'a [u8] { 69 | let from = self.position + i; 70 | if from >= self.length { 71 | return &self.input[self.length..self.length]; 72 | } 73 | 74 | let mut until = from + n; 75 | if until >= self.length { 76 | until = self.length; 77 | } 78 | 79 | &self.input[from..until] 80 | } 81 | 82 | const fn to_bound(&self, n: usize) -> (usize, usize) { 83 | if self.position >= self.length { 84 | return (self.length, self.length); 85 | } 86 | 87 | let mut until = self.position + n; 88 | 89 | if until >= self.length { 90 | until = self.length; 91 | } 92 | 93 | (self.position, until) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod lexer; 2 | pub mod parser; 3 | pub mod traverser; 4 | pub mod tree; 5 | -------------------------------------------------------------------------------- /src/parser/internal/definition/attribute.rs: -------------------------------------------------------------------------------- 1 | use crate::lexer::token::TokenKind; 2 | use crate::parser::internal::expression::argument; 3 | use crate::parser::internal::identifier; 4 | use crate::parser::internal::utils; 5 | use crate::parser::result::ParseResult; 6 | use crate::parser::state::State; 7 | use crate::tree::definition::attribute::AttributeDefinition; 8 | use crate::tree::definition::attribute::AttributeGroupDefinition; 9 | 10 | pub fn gather(state: &mut State) -> ParseResult { 11 | if state.iterator.current().kind != TokenKind::Attribute { 12 | return Ok(false); 13 | } 14 | 15 | let attributes = AttributeGroupDefinition { 16 | hash_left_bracket: utils::skip(state, TokenKind::Attribute)?, 17 | members: utils::comma_separated( 18 | state, 19 | &|state| { 20 | Ok(AttributeDefinition { 21 | name: identifier::fully_qualified_type_identifier_including_self(state)?, 22 | arguments: if state.iterator.current().kind == TokenKind::LeftParen { 23 | Some(argument::argument_list_expression(state)?) 24 | } else { 25 | None 26 | }, 27 | }) 28 | }, 29 | TokenKind::RightBracket, 30 | )?, 31 | right_bracket: utils::skip(state, TokenKind::RightBracket)?, 32 | }; 33 | 34 | state.attribute(attributes); 35 | 36 | // recursive, looking for multiple attribute brackets after each other. 37 | gather(state).map(|_| true) 38 | } 39 | -------------------------------------------------------------------------------- /src/parser/internal/definition/constant.rs: -------------------------------------------------------------------------------- 1 | use crate::lexer::token::TokenKind; 2 | use crate::parser::internal::definition::r#type; 3 | use crate::parser::internal::expression; 4 | use crate::parser::internal::identifier; 5 | use crate::parser::internal::utils; 6 | use crate::parser::result::ParseResult; 7 | use crate::parser::state::State; 8 | use crate::tree::definition::constant::ClassishConstantDefinition; 9 | use crate::tree::definition::constant::ConstantDefinition; 10 | use crate::tree::definition::modifier::ModifierGroupDefinition; 11 | 12 | pub fn constant_definition(state: &mut State) -> ParseResult { 13 | Ok(ConstantDefinition { 14 | comments: state.iterator.comments(), 15 | r#const: utils::skip_keyword(state, TokenKind::Const)?, 16 | type_definition: r#type::type_definition(state)?, 17 | name: identifier::constant_identifier(state)?, 18 | equals: utils::skip(state, TokenKind::Equals)?, 19 | value: expression::create(state)?, 20 | semicolon: utils::skip_semicolon(state)?, 21 | }) 22 | } 23 | 24 | pub fn classish_constant_definition( 25 | state: &mut State, 26 | modifiers: ModifierGroupDefinition, 27 | ) -> ParseResult { 28 | Ok(ClassishConstantDefinition { 29 | comments: state.iterator.comments(), 30 | attributes: state.get_attributes(), 31 | modifiers, 32 | r#const: utils::skip_keyword(state, TokenKind::Const)?, 33 | type_definition: r#type::type_definition(state)?, 34 | name: identifier::constant_identifier(state)?, 35 | equals: utils::skip(state, TokenKind::Equals)?, 36 | value: expression::create(state)?, 37 | semicolon: utils::skip_semicolon(state)?, 38 | }) 39 | } 40 | -------------------------------------------------------------------------------- /src/parser/internal/definition/interface.rs: -------------------------------------------------------------------------------- 1 | use crate::lexer::token::TokenKind; 2 | use crate::parser::internal::definition::attribute; 3 | use crate::parser::internal::definition::constant; 4 | use crate::parser::internal::definition::function::method_definition; 5 | use crate::parser::internal::definition::modifier; 6 | use crate::parser::internal::definition::template; 7 | use crate::parser::internal::identifier; 8 | use crate::parser::internal::utils; 9 | use crate::parser::result::ParseResult; 10 | use crate::parser::state::State; 11 | use crate::tree::definition::interface::InterfaceDefinition; 12 | use crate::tree::definition::interface::InterfaceDefinitionBody; 13 | use crate::tree::definition::interface::InterfaceDefinitionExtends; 14 | use crate::tree::definition::interface::InterfaceDefinitionMember; 15 | 16 | pub fn interface_definition(state: &mut State) -> ParseResult { 17 | let comments = state.iterator.comments(); 18 | let interface = utils::skip_keyword(state, TokenKind::Interface)?; 19 | let name = identifier::classname_identifier(state)?; 20 | let templates = if state.iterator.current().kind == TokenKind::LessThan { 21 | Some(template::template_group_definition(state)?) 22 | } else { 23 | None 24 | }; 25 | 26 | let current = state.iterator.current(); 27 | let extends = if current.kind == TokenKind::Extends { 28 | let extends = utils::skip_keyword(state, TokenKind::Extends)?; 29 | let parents = utils::at_least_one_comma_separated( 30 | state, 31 | &identifier::fully_qualified_templated_identifier, 32 | TokenKind::LeftBrace, 33 | )?; 34 | 35 | Some(InterfaceDefinitionExtends { extends, parents }) 36 | } else { 37 | None 38 | }; 39 | 40 | let attributes = state.get_attributes(); 41 | 42 | let body = InterfaceDefinitionBody { 43 | left_brace: utils::skip_left_brace(state)?, 44 | members: { 45 | let mut members = Vec::new(); 46 | while state.iterator.current().kind != TokenKind::RightBrace { 47 | members.push(interface_definition_member(state)?); 48 | } 49 | 50 | members 51 | }, 52 | right_brace: utils::skip_right_brace(state)?, 53 | }; 54 | 55 | Ok(InterfaceDefinition { 56 | comments, 57 | attributes, 58 | interface, 59 | name, 60 | templates, 61 | extends, 62 | body, 63 | }) 64 | } 65 | 66 | fn interface_definition_member(state: &mut State) -> ParseResult { 67 | attribute::gather(state)?; 68 | 69 | let modifiers = modifier::collect(state)?; 70 | 71 | if state.iterator.current().kind == TokenKind::Const { 72 | constant::classish_constant_definition(state, modifiers) 73 | .map(InterfaceDefinitionMember::Constant) 74 | } else { 75 | method_definition(state, modifiers).map(InterfaceDefinitionMember::Method) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/parser/internal/definition/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::lexer::token::TokenKind; 2 | use crate::parser::result::ParseResult; 3 | use crate::parser::state::State; 4 | use crate::tree::definition::Definition; 5 | use crate::tree::definition::DefinitionTree; 6 | 7 | pub mod attribute; 8 | pub mod class; 9 | pub mod constant; 10 | pub mod r#enum; 11 | pub mod function; 12 | pub mod interface; 13 | pub mod modifier; 14 | pub mod namespace; 15 | pub mod parameter; 16 | pub mod property; 17 | pub mod template; 18 | pub mod r#type; 19 | pub mod r#use; 20 | 21 | pub fn tree(state: &mut State) -> ParseResult { 22 | let mut definitions = Vec::new(); 23 | 24 | while !state.iterator.is_eof() { 25 | definitions.push(definition(state)?); 26 | } 27 | 28 | Ok(DefinitionTree { 29 | definitions, 30 | eof: state.iterator.current().position, 31 | }) 32 | } 33 | 34 | pub fn definition(state: &mut State) -> ParseResult { 35 | let current = state.iterator.current(); 36 | if matches!(current.kind, TokenKind::OpenTag(_)) { 37 | state.iterator.next(); 38 | 39 | crate::parser_report!(state, php_opening_tag_not_supported(current)); 40 | 41 | return definition(state); 42 | } 43 | 44 | if current.kind == TokenKind::Namespace { 45 | return Ok(Definition::Namespace(Box::new( 46 | namespace::namespace_definition(state)?, 47 | ))); 48 | } 49 | 50 | if current.kind == TokenKind::Use { 51 | return Ok(Definition::Use(Box::new(r#use::use_definition(state)?))); 52 | } 53 | 54 | if current.kind == TokenKind::Const { 55 | return Ok(Definition::Constant(Box::new( 56 | constant::constant_definition(state)?, 57 | ))); 58 | } 59 | 60 | if current.kind == TokenKind::Type { 61 | return Ok(Definition::TypeAlias(Box::new( 62 | r#type::type_alias_definition(state)?, 63 | ))); 64 | } 65 | 66 | let has_attributes = attribute::gather(state)?; 67 | let current = state.iterator.current(); 68 | 69 | if current.kind == TokenKind::Enum { 70 | return Ok(Definition::Enum(Box::new(r#enum::enum_definition(state)?))); 71 | } 72 | 73 | if current.kind == TokenKind::Interface { 74 | return Ok(Definition::Interface(Box::new( 75 | interface::interface_definition(state)?, 76 | ))); 77 | } 78 | 79 | if matches!(current.kind, TokenKind::Async | TokenKind::Function) { 80 | return Ok(Definition::Function(Box::new( 81 | function::function_definition(state)?, 82 | ))); 83 | } 84 | 85 | if matches!( 86 | current.kind, 87 | TokenKind::Readonly | TokenKind::Final | TokenKind::Abstract | TokenKind::Class 88 | ) { 89 | return Ok(Definition::Class(Box::new(class::class_definition(state)?))); 90 | } 91 | 92 | if has_attributes { 93 | crate::parser_report!(state, missing_item_definition_after_attributes); 94 | } 95 | 96 | crate::parser_bail!(state, unexpected_token(vec!["a definition"], current)); 97 | } 98 | -------------------------------------------------------------------------------- /src/parser/internal/definition/modifier.rs: -------------------------------------------------------------------------------- 1 | use crate::lexer::token::TokenKind; 2 | use crate::parser::internal::utils; 3 | use crate::parser::result::ParseResult; 4 | use crate::parser::state::State; 5 | use crate::tree::definition::modifier::{ModifierDefinition, ModifierGroupDefinition}; 6 | 7 | pub fn collect(state: &mut State) -> ParseResult { 8 | let mut modifiers: Vec = vec![]; 9 | 10 | let collectable_tokens = vec![ 11 | TokenKind::Private, 12 | TokenKind::Protected, 13 | TokenKind::Public, 14 | TokenKind::Final, 15 | TokenKind::Abstract, 16 | TokenKind::Static, 17 | TokenKind::Readonly, 18 | TokenKind::Async, 19 | ]; 20 | 21 | let mut current = state.iterator.current().clone(); 22 | let mut current_kind = current.kind; 23 | let mut current_position = current.position; 24 | 25 | while collectable_tokens.contains(¤t_kind) { 26 | modifiers.push(match current_kind { 27 | TokenKind::Private => { 28 | ModifierDefinition::Private(utils::skip_keyword(state, TokenKind::Private)?) 29 | } 30 | TokenKind::Protected => { 31 | ModifierDefinition::Protected(utils::skip_keyword(state, TokenKind::Protected)?) 32 | } 33 | TokenKind::Public => { 34 | ModifierDefinition::Public(utils::skip_keyword(state, TokenKind::Public)?) 35 | } 36 | TokenKind::Final => { 37 | ModifierDefinition::Final(utils::skip_keyword(state, TokenKind::Final)?) 38 | } 39 | TokenKind::Abstract => { 40 | ModifierDefinition::Abstract(utils::skip_keyword(state, TokenKind::Abstract)?) 41 | } 42 | TokenKind::Static => { 43 | ModifierDefinition::Static(utils::skip_keyword(state, TokenKind::Static)?) 44 | } 45 | TokenKind::Readonly => { 46 | ModifierDefinition::Readonly(utils::skip_keyword(state, TokenKind::Readonly)?) 47 | } 48 | TokenKind::Async => { 49 | ModifierDefinition::Async(utils::skip_keyword(state, TokenKind::Async)?) 50 | } 51 | _ => unreachable!(), 52 | }); 53 | 54 | current = state.iterator.current().clone(); 55 | current_kind = current.kind; 56 | current_position = current.position; 57 | } 58 | 59 | Ok(ModifierGroupDefinition { 60 | position: current_position, 61 | modifiers, 62 | }) 63 | } 64 | -------------------------------------------------------------------------------- /src/parser/internal/definition/namespace.rs: -------------------------------------------------------------------------------- 1 | use crate::lexer::token::TokenKind; 2 | use crate::parser::internal::definition; 3 | use crate::parser::internal::identifier; 4 | use crate::parser::internal::utils; 5 | use crate::parser::result::ParseResult; 6 | use crate::parser::state::State; 7 | use crate::tree::definition::namespace::NamespaceDefinition; 8 | 9 | pub fn namespace_definition(state: &mut State) -> ParseResult { 10 | let namespace = utils::skip_keyword(state, TokenKind::Namespace)?; 11 | let name = identifier::namespace_identifier(state)?; 12 | let semicolon = utils::skip_semicolon(state)?; 13 | 14 | state.namespace(name.clone()); 15 | 16 | let mut definitions = Vec::new(); 17 | while state.iterator.current().kind != TokenKind::Namespace && !state.iterator.is_eof() { 18 | definitions.push(definition::definition(state)?); 19 | } 20 | 21 | Ok(NamespaceDefinition { 22 | namespace, 23 | name, 24 | semicolon, 25 | definitions, 26 | }) 27 | } 28 | -------------------------------------------------------------------------------- /src/parser/internal/definition/property.rs: -------------------------------------------------------------------------------- 1 | use crate::lexer::token::TokenKind; 2 | use crate::parser::internal::definition::r#type; 3 | use crate::parser::internal::expression; 4 | use crate::parser::internal::utils; 5 | use crate::parser::internal::variable; 6 | use crate::parser::result::ParseResult; 7 | use crate::parser::state::State; 8 | use crate::tree::definition::modifier::ModifierGroupDefinition; 9 | use crate::tree::definition::property::PropertyDefinition; 10 | use crate::tree::definition::property::PropertyEntryDefinition; 11 | 12 | pub fn property_definition( 13 | state: &mut State, 14 | modifiers: ModifierGroupDefinition, 15 | ) -> ParseResult { 16 | let type_definition = r#type::type_definition(state)?; 17 | let variable = variable::parse(state)?; 18 | 19 | let attributes = state.get_attributes(); 20 | let current = state.iterator.current(); 21 | 22 | let entry = if current.kind == TokenKind::Equals { 23 | PropertyEntryDefinition::Initialized { 24 | variable, 25 | equals: utils::skip(state, TokenKind::Equals)?, 26 | value: expression::create(state)?, 27 | } 28 | } else { 29 | PropertyEntryDefinition::Uninitialized { variable } 30 | }; 31 | 32 | Ok(PropertyDefinition { 33 | type_definition, 34 | modifiers, 35 | attributes, 36 | entry, 37 | semicolon: utils::skip(state, TokenKind::SemiColon)?, 38 | }) 39 | } 40 | -------------------------------------------------------------------------------- /src/parser/internal/definition/use.rs: -------------------------------------------------------------------------------- 1 | use crate::lexer::token::TokenKind; 2 | use crate::parser::internal::identifier; 3 | use crate::parser::internal::utils; 4 | use crate::parser::result::ParseResult; 5 | use crate::parser::state::State; 6 | use crate::tree::definition::r#use::UseDefinition; 7 | use crate::tree::definition::r#use::UseDefinitionSymbolAlias; 8 | 9 | pub fn use_definition(state: &mut State) -> ParseResult { 10 | let r#use = utils::skip_keyword(state, TokenKind::Use)?; 11 | 12 | let current = state.iterator.current(); 13 | 14 | match current.kind { 15 | TokenKind::Function => Ok(UseDefinition::Function { 16 | r#use, 17 | function: utils::skip_keyword(state, TokenKind::Function)?, 18 | name: identifier::fully_qualified_type_identifier(state)?, 19 | alias: if state.iterator.current().kind == TokenKind::As { 20 | Some(UseDefinitionSymbolAlias { 21 | r#as: utils::skip_keyword(state, TokenKind::As)?, 22 | alias: identifier::classname_identifier(state)?, 23 | }) 24 | } else { 25 | None 26 | }, 27 | semicolon: utils::skip_semicolon(state)?, 28 | }), 29 | TokenKind::Const => Ok(UseDefinition::Constant { 30 | r#use, 31 | r#const: utils::skip_keyword(state, TokenKind::Const)?, 32 | name: identifier::fully_qualified_type_identifier(state)?, 33 | alias: if state.iterator.current().kind == TokenKind::As { 34 | Some(UseDefinitionSymbolAlias { 35 | r#as: utils::skip_keyword(state, TokenKind::As)?, 36 | alias: identifier::classname_identifier(state)?, 37 | }) 38 | } else { 39 | None 40 | }, 41 | semicolon: utils::skip_semicolon(state)?, 42 | }), 43 | _ => Ok(UseDefinition::Default { 44 | r#use, 45 | name: identifier::fully_qualified_type_identifier(state)?, 46 | alias: if state.iterator.current().kind == TokenKind::As { 47 | Some(UseDefinitionSymbolAlias { 48 | r#as: utils::skip_keyword(state, TokenKind::As)?, 49 | alias: identifier::classname_identifier(state)?, 50 | }) 51 | } else { 52 | None 53 | }, 54 | semicolon: utils::skip_semicolon(state)?, 55 | }), 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/parser/internal/expression/argument.rs: -------------------------------------------------------------------------------- 1 | use crate::lexer::token::TokenKind; 2 | use crate::parser::internal::expression; 3 | use crate::parser::internal::identifier; 4 | use crate::parser::internal::utils; 5 | use crate::parser::result::ParseResult; 6 | use crate::parser::state::State; 7 | use crate::tree::expression::argument::ArgumentExpression; 8 | use crate::tree::expression::argument::ArgumentListExpression; 9 | 10 | pub fn argument_list_expression(state: &mut State) -> ParseResult { 11 | Ok(ArgumentListExpression { 12 | comments: state.iterator.comments(), 13 | left_parenthesis: utils::skip_left_parenthesis(state)?, 14 | arguments: utils::comma_separated(state, &argument_expression, TokenKind::RightParen)?, 15 | right_parenthesis: utils::skip_right_parenthesis(state)?, 16 | }) 17 | } 18 | 19 | fn argument_expression(state: &mut State) -> ParseResult { 20 | let current = state.iterator.current(); 21 | let comments = state.iterator.comments(); 22 | 23 | if identifier::is_identifier_maybe_reserved(¤t.kind) 24 | && state.iterator.lookahead(1).kind == TokenKind::Colon 25 | { 26 | let name = identifier::identifier_maybe_reserved(state)?; 27 | let colon = utils::skip(state, TokenKind::Colon)?; 28 | let value = expression::create(state)?; 29 | 30 | return Ok(ArgumentExpression::Named { 31 | comments, 32 | name, 33 | colon, 34 | value, 35 | }); 36 | } 37 | 38 | if current.kind == TokenKind::Ellipsis { 39 | let ellipsis = current.position; 40 | state.iterator.next(); 41 | let value = expression::create(state)?; 42 | 43 | Ok(ArgumentExpression::Spread { 44 | comments, 45 | ellipsis, 46 | value, 47 | }) 48 | } else { 49 | let value = expression::create(state)?; 50 | let current = state.iterator.current(); 51 | 52 | if current.kind == TokenKind::Ellipsis { 53 | let ellipsis = current.position; 54 | state.iterator.next(); 55 | 56 | Ok(ArgumentExpression::ReverseSpread { 57 | comments, 58 | value, 59 | ellipsis, 60 | }) 61 | } else { 62 | Ok(ArgumentExpression::Value { comments, value }) 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/parser/internal/expression/array.rs: -------------------------------------------------------------------------------- 1 | use crate::lexer::token::TokenKind; 2 | use crate::parser::internal::expression; 3 | use crate::parser::internal::utils; 4 | use crate::parser::result::ParseResult; 5 | use crate::parser::state::State; 6 | use crate::tree::expression::array::DictElementExpression; 7 | use crate::tree::expression::array::DictExpression; 8 | use crate::tree::expression::array::VecElementExpression; 9 | use crate::tree::expression::array::VecExpression; 10 | 11 | pub fn vec_expression(state: &mut State) -> ParseResult { 12 | Ok(VecExpression { 13 | comments: state.iterator.comments(), 14 | vec: utils::skip_keyword(state, TokenKind::Vec)?, 15 | left_bracket: utils::skip(state, TokenKind::LeftBracket)?, 16 | elements: utils::comma_separated( 17 | state, 18 | &|state| { 19 | Ok(VecElementExpression { 20 | value: expression::create(state)?, 21 | }) 22 | }, 23 | TokenKind::RightBracket, 24 | )?, 25 | right_bracket: utils::skip(state, TokenKind::RightBracket)?, 26 | }) 27 | } 28 | 29 | pub fn dict_expression(state: &mut State) -> ParseResult { 30 | Ok(DictExpression { 31 | comments: state.iterator.comments(), 32 | dict: utils::skip_keyword(state, TokenKind::Dict)?, 33 | left_bracket: utils::skip(state, TokenKind::LeftBracket)?, 34 | elements: utils::comma_separated( 35 | state, 36 | &|state| { 37 | Ok(DictElementExpression { 38 | key: expression::create(state)?, 39 | double_arrow: utils::skip(state, TokenKind::DoubleArrow)?, 40 | value: expression::create(state)?, 41 | }) 42 | }, 43 | TokenKind::RightBracket, 44 | )?, 45 | right_bracket: utils::skip(state, TokenKind::RightBracket)?, 46 | }) 47 | } 48 | -------------------------------------------------------------------------------- /src/parser/internal/expression/class.rs: -------------------------------------------------------------------------------- 1 | use crate::lexer::token::TokenKind; 2 | use crate::parser::internal::definition::attribute; 3 | use crate::parser::internal::definition::class; 4 | use crate::parser::internal::expression::argument; 5 | use crate::parser::internal::utils; 6 | use crate::parser::result::ParseResult; 7 | use crate::parser::state::State; 8 | use crate::tree::expression::class::AnonymousClassExpression; 9 | use crate::tree::expression::operator::ClassOperationExpression; 10 | 11 | pub fn anonymous_initialization_class_operation_expression( 12 | state: &mut State, 13 | ) -> ParseResult { 14 | Ok(ClassOperationExpression::AnonymousInitialization { 15 | comments: state.iterator.comments(), 16 | new: utils::skip_keyword(state, TokenKind::New)?, 17 | class: anonymous_class_expression(state)?, 18 | }) 19 | } 20 | 21 | pub fn anonymous_class_expression(state: &mut State) -> ParseResult { 22 | attribute::gather(state)?; 23 | 24 | let attributes = state.get_attributes(); 25 | let class = utils::skip_keyword(state, TokenKind::Class)?; 26 | let comments = state.iterator.comments(); 27 | let arguments = argument::argument_list_expression(state)?; 28 | 29 | Ok(AnonymousClassExpression { 30 | comments, 31 | attributes, 32 | class, 33 | arguments, 34 | extends: class::class_definition_extends(state)?, 35 | implements: class::class_definition_implements(state)?, 36 | body: class::class_definition_body(state)?, 37 | }) 38 | } 39 | -------------------------------------------------------------------------------- /src/parser/internal/expression/control_flow.rs: -------------------------------------------------------------------------------- 1 | use crate::lexer::token::TokenKind; 2 | use crate::parser::internal::expression; 3 | use crate::parser::internal::utils; 4 | use crate::parser::result::ParseResult; 5 | use crate::parser::state::State; 6 | use crate::tree::expression::control_flow::MatchArmConditionExpression; 7 | use crate::tree::expression::control_flow::MatchArmExpression; 8 | use crate::tree::expression::control_flow::MatchBodyExpression; 9 | use crate::tree::expression::control_flow::MatchExpression; 10 | use crate::tree::utils::CommaSeparated; 11 | 12 | pub fn match_expression(state: &mut State) -> ParseResult { 13 | let r#match = utils::skip_keyword(state, TokenKind::Match)?; 14 | 15 | let expression = if state.iterator.current().kind == TokenKind::LeftBrace { 16 | None 17 | } else { 18 | Some(Box::new(expression::create(state)?)) 19 | }; 20 | 21 | Ok(MatchExpression { 22 | comments: state.iterator.comments(), 23 | r#match, 24 | expression, 25 | body: MatchBodyExpression { 26 | left_brace: utils::skip_left_brace(state)?, 27 | arms: { 28 | let mut items = Vec::new(); 29 | let mut commas = Vec::new(); 30 | 31 | while state.iterator.current().kind != TokenKind::RightBrace { 32 | let current = state.iterator.current(); 33 | let condition = if current.kind == TokenKind::Default { 34 | MatchArmConditionExpression::Default(utils::skip_keyword( 35 | state, 36 | TokenKind::Default, 37 | )?) 38 | } else { 39 | MatchArmConditionExpression::Expressions(utils::comma_separated( 40 | state, 41 | &expression::create, 42 | TokenKind::DoubleArrow, 43 | )?) 44 | }; 45 | 46 | let arm = MatchArmExpression { 47 | condition, 48 | arrow: utils::skip_double_arrow(state)?, 49 | expression: expression::create(state)?, 50 | }; 51 | 52 | items.push(arm); 53 | 54 | let current = state.iterator.current(); 55 | if current.kind == TokenKind::Comma { 56 | state.iterator.next(); 57 | commas.push(current.position); 58 | } else { 59 | break; 60 | } 61 | } 62 | 63 | CommaSeparated { 64 | inner: items, 65 | commas, 66 | } 67 | }, 68 | right_brace: utils::skip_right_brace(state)?, 69 | }, 70 | }) 71 | } 72 | -------------------------------------------------------------------------------- /src/parser/internal/expression/function.rs: -------------------------------------------------------------------------------- 1 | use crate::lexer::token::TokenKind; 2 | use crate::parser::internal::definition::modifier; 3 | use crate::parser::internal::definition::parameter; 4 | use crate::parser::internal::definition::r#type; 5 | use crate::parser::internal::expression; 6 | use crate::parser::internal::statement::block; 7 | use crate::parser::internal::utils; 8 | use crate::parser::internal::variable; 9 | use crate::parser::result::ParseResult; 10 | use crate::parser::state::State; 11 | use crate::tree::definition::function::FunctionLikeReturnTypeDefinition; 12 | use crate::tree::expression::function::AnonymousFunctionExpression; 13 | use crate::tree::expression::function::AnonymousFunctionUseClauseExpression; 14 | use crate::tree::expression::function::AnonymousFunctionUseClauseVariableExpression; 15 | use crate::tree::expression::function::ArrowFunctionExpression; 16 | 17 | pub fn anonymous_function_expression( 18 | state: &mut State, 19 | ) -> ParseResult { 20 | let comments = state.iterator.comments(); 21 | let attributes = state.get_attributes(); 22 | let modifiers = modifier::collect(state)?; 23 | 24 | let function = utils::skip_keyword(state, TokenKind::Function)?; 25 | let parameters = parameter::function_like_parameter_list_definition(state)?; 26 | 27 | let current = state.iterator.current(); 28 | let uses = if current.kind == TokenKind::Use { 29 | Some(AnonymousFunctionUseClauseExpression { 30 | comments: state.iterator.comments(), 31 | r#use: utils::skip_keyword(state, TokenKind::Use)?, 32 | left_parenthesis: utils::skip_left_parenthesis(state)?, 33 | variables: utils::comma_separated::( 34 | state, 35 | &|state| { 36 | let use_comments = state.iterator.comments(); 37 | let var = variable::parse(state)?; 38 | 39 | Ok(AnonymousFunctionUseClauseVariableExpression { 40 | comments: use_comments, 41 | variable: var, 42 | }) 43 | }, 44 | TokenKind::RightParen, 45 | )?, 46 | right_parenthesis: utils::skip_right_parenthesis(state)?, 47 | }) 48 | } else { 49 | None 50 | }; 51 | 52 | Ok(AnonymousFunctionExpression { 53 | comments, 54 | attributes, 55 | modifiers, 56 | function, 57 | parameters, 58 | use_clause: uses, 59 | return_type: FunctionLikeReturnTypeDefinition { 60 | colon: utils::skip_colon(state)?, 61 | type_definition: r#type::type_definition(state)?, 62 | }, 63 | body: block::block_statement(state)?, 64 | }) 65 | } 66 | 67 | pub fn arrow_function_expression(state: &mut State) -> ParseResult { 68 | let comments = state.iterator.comments(); 69 | let attributes = state.get_attributes(); 70 | let modifiers = modifier::collect(state)?; 71 | 72 | Ok(ArrowFunctionExpression { 73 | comments, 74 | attributes, 75 | modifiers, 76 | r#fn: utils::skip_keyword(state, TokenKind::Fn)?, 77 | parameters: parameter::function_like_parameter_list_definition(state)?, 78 | return_type: FunctionLikeReturnTypeDefinition { 79 | colon: utils::skip_colon(state)?, 80 | type_definition: r#type::type_definition(state)?, 81 | }, 82 | double_arrow: utils::skip(state, TokenKind::DoubleArrow)?, 83 | body: Box::new(expression::create(state)?), 84 | }) 85 | } 86 | -------------------------------------------------------------------------------- /src/parser/internal/expression/generic.rs: -------------------------------------------------------------------------------- 1 | use crate::lexer::token::TokenKind; 2 | use crate::parser::internal::definition::r#type; 3 | use crate::parser::internal::utils; 4 | use crate::parser::result::ParseResult; 5 | use crate::parser::state::State; 6 | use crate::tree::expression::generic::GenericGroupExpression; 7 | use crate::tree::utils::CommaSeparated; 8 | 9 | pub fn generic_group(state: &mut State) -> ParseResult> { 10 | if state.iterator.current().kind != TokenKind::Generic { 11 | return Ok(None); 12 | } 13 | 14 | Ok(Some(GenericGroupExpression { 15 | double_colon_less_than: utils::skip(state, TokenKind::Generic)?, 16 | types: { 17 | let mut inner = vec![]; 18 | let mut commas = vec![]; 19 | 20 | let mut current = state.iterator.current(); 21 | while current.kind != TokenKind::GreaterThan && current.kind != TokenKind::RightShift { 22 | inner.push(r#type::type_definition(state)?); 23 | 24 | current = state.iterator.current(); 25 | if current.kind != TokenKind::Comma { 26 | break; 27 | } 28 | 29 | commas.push(current.position); 30 | 31 | state.iterator.next(); 32 | current = state.iterator.current(); 33 | } 34 | 35 | CommaSeparated { inner, commas } 36 | }, 37 | greater_than: { 38 | let current = state.iterator.current(); 39 | 40 | if let Some(token) = state.ignored_shift_at { 41 | utils::skip(state, TokenKind::RightShift)?; 42 | state.ignored_shift_at = None; 43 | token.position + 1 44 | } else if current.kind == TokenKind::RightShift { 45 | state.ignored_shift_at = Some(current); 46 | 47 | current.position 48 | } else { 49 | utils::skip(state, TokenKind::GreaterThan)? 50 | } 51 | }, 52 | })) 53 | } 54 | -------------------------------------------------------------------------------- /src/parser/internal/expression/precedence.rs: -------------------------------------------------------------------------------- 1 | use crate::lexer::token::TokenKind; 2 | use crate::parser::result::ParseResult; 3 | use crate::parser::state::State; 4 | 5 | pub enum Associativity { 6 | Non, 7 | Left, 8 | Right, 9 | } 10 | 11 | #[allow(dead_code)] 12 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] 13 | pub enum Precedence { 14 | Lowest, 15 | Range, 16 | Yield, 17 | YieldFrom, 18 | IncDec, 19 | Assignment, 20 | Ternary, 21 | NullCoalesce, 22 | Or, 23 | And, 24 | BitwiseOr, 25 | BitwiseXor, 26 | BitwiseAnd, 27 | Equality, 28 | LtGt, 29 | Concat, 30 | BitShift, 31 | AddSub, 32 | MulDivMod, 33 | Bang, 34 | TypeCheck, 35 | ArrayContains, 36 | Prefix, 37 | Pow, 38 | Clone, 39 | CallDim, 40 | ObjectAccess, 41 | New, 42 | } 43 | 44 | impl Precedence { 45 | pub fn infix(state: &mut State, kind: &TokenKind) -> ParseResult { 46 | use TokenKind::*; 47 | 48 | match kind { 49 | Pow => Ok(Self::Pow), 50 | Instanceof | Is | As | Into => Ok(Self::TypeCheck), 51 | In => Ok(Self::ArrayContains), 52 | Asterisk | Slash | Percent => Ok(Self::MulDivMod), 53 | Plus | Minus => Ok(Self::AddSub), 54 | LeftShift | RightShift => Ok(Self::BitShift), 55 | Dot => Ok(Self::Concat), 56 | LessThan | LessThanEquals | GreaterThan | GreaterThanEquals => Ok(Self::LtGt), 57 | DoubleEquals | BangEquals | TripleEquals | BangDoubleEquals | Spaceship => { 58 | Ok(Self::Equality) 59 | } 60 | Ampersand => Ok(Self::BitwiseAnd), 61 | Caret => Ok(Self::BitwiseXor), 62 | Pipe => Ok(Self::BitwiseOr), 63 | BooleanAnd => Ok(Self::And), 64 | BooleanOr => Ok(Self::Or), 65 | DoubleQuestion => Ok(Self::NullCoalesce), 66 | Question | QuestionColon => Ok(Self::Ternary), 67 | Equals | PlusEquals | MinusEquals | AsteriskEquals | PowEquals | SlashEquals 68 | | DotEquals | AndEquals | DoubleQuestionEquals | PercentEquals | AmpersandEquals 69 | | PipeEquals | CaretEquals | LeftShiftEquals | RightShiftEquals => Ok(Self::Assignment), 70 | Yield => Ok(Self::Yield), 71 | DoubleDot => Ok(Self::Range), 72 | _ => crate::parser_bail!( 73 | state, 74 | unreachable_code(format!("unexpected precedence for operator {kind:?}")) 75 | ), 76 | } 77 | } 78 | 79 | pub fn postfix(state: &mut State, kind: &TokenKind) -> ParseResult { 80 | use TokenKind::*; 81 | 82 | match kind { 83 | DoubleQuestion => Ok(Self::NullCoalesce), 84 | Increment | Decrement => Ok(Self::IncDec), 85 | LeftParen | Generic | LeftBracket => Ok(Self::CallDim), 86 | Arrow | QuestionArrow | DoubleColon => Ok(Self::ObjectAccess), 87 | _ => crate::parser_bail!( 88 | state, 89 | unreachable_code(format!("unexpected precedence for operator {kind:?}")) 90 | ), 91 | } 92 | } 93 | 94 | pub fn associativity(&self) -> Option { 95 | Some(match &self { 96 | Self::TypeCheck 97 | | Self::ArrayContains 98 | | Self::MulDivMod 99 | | Self::AddSub 100 | | Self::BitShift 101 | | Self::Concat 102 | | Self::BitwiseAnd 103 | | Self::BitwiseOr 104 | | Self::BitwiseXor 105 | | Self::And 106 | | Self::Or => Associativity::Left, 107 | Self::Pow | Self::NullCoalesce | Self::Assignment => Associativity::Right, 108 | Self::Ternary | Self::Equality | Self::LtGt => Associativity::Non, 109 | _ => return None, 110 | }) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/parser/internal/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod definition; 2 | pub(crate) mod expression; 3 | pub(crate) mod identifier; 4 | pub(crate) mod statement; 5 | pub(crate) mod utils; 6 | pub(crate) mod variable; 7 | -------------------------------------------------------------------------------- /src/parser/internal/statement/block.rs: -------------------------------------------------------------------------------- 1 | use crate::lexer::token::OpenTagKind; 2 | use crate::lexer::token::TokenKind; 3 | use crate::parser::internal::statement; 4 | use crate::parser::internal::utils; 5 | use crate::parser::result::ParseResult; 6 | use crate::parser::state::State; 7 | use crate::tree::statement::block::BlockStatement; 8 | use crate::tree::statement::Statement; 9 | 10 | pub fn block_statement(state: &mut State) -> ParseResult { 11 | Ok(BlockStatement { 12 | comments: state.iterator.comments(), 13 | left_brace: utils::skip_left_brace(state)?, 14 | statements: multiple_statements_until(state, &TokenKind::RightBrace)?, 15 | right_brace: utils::skip_right_brace(state)?, 16 | }) 17 | } 18 | 19 | pub fn multiple_statements_until( 20 | state: &mut State, 21 | until: &TokenKind, 22 | ) -> ParseResult> { 23 | let mut statements = Vec::new(); 24 | 25 | let mut current = state.iterator.current(); 26 | while ¤t.kind != until { 27 | if let TokenKind::OpenTag(OpenTagKind::Full) = current.kind { 28 | state.iterator.next(); 29 | 30 | current = state.iterator.current(); 31 | continue; 32 | } 33 | 34 | statements.push(statement::statement(state)?); 35 | current = state.iterator.current(); 36 | } 37 | 38 | Ok(statements) 39 | } 40 | -------------------------------------------------------------------------------- /src/parser/internal/statement/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::lexer::token::TokenKind; 2 | use crate::parser::internal::expression; 3 | use crate::parser::internal::utils; 4 | use crate::parser::result::ParseResult; 5 | use crate::parser::state::State; 6 | use crate::tree::statement::expression::ExpressionStatement; 7 | use crate::tree::statement::r#return::ReturnStatement; 8 | use crate::tree::statement::Statement; 9 | 10 | pub mod block; 11 | pub mod control_flow; 12 | pub mod r#loop; 13 | pub mod r#try; 14 | 15 | fn statement(state: &mut State) -> ParseResult { 16 | let current = state.iterator.current(); 17 | 18 | if matches!(current.kind, TokenKind::OpenTag(_)) { 19 | state.iterator.next(); 20 | 21 | crate::parser_report!(state, php_opening_tag_not_supported(current)); 22 | 23 | return statement(state); 24 | } 25 | 26 | if matches!(current.kind, TokenKind::CloseTag) { 27 | state.iterator.next(); 28 | 29 | crate::parser_report!(state, php_closing_tag_not_supported(current)); 30 | 31 | return statement(state); 32 | } 33 | 34 | let statement = match ¤t.kind { 35 | TokenKind::Do => Statement::DoWhile(Box::new(r#loop::do_while_statement(state)?)), 36 | TokenKind::While => Statement::While(Box::new(r#loop::while_statement(state)?)), 37 | TokenKind::For => Statement::For(Box::new(r#loop::for_statement(state)?)), 38 | TokenKind::Foreach => Statement::Foreach(Box::new(r#loop::foreach_statement(state)?)), 39 | TokenKind::Continue => Statement::Continue(Box::new(r#loop::continue_statement(state)?)), 40 | TokenKind::Break => Statement::Break(Box::new(r#loop::break_statement(state)?)), 41 | TokenKind::If => Statement::If(Box::new(control_flow::if_statement(state)?)), 42 | TokenKind::Try => Statement::Try(Box::new(r#try::try_statement(state)?)), 43 | TokenKind::LeftBrace => Statement::Block(Box::new(block::block_statement(state)?)), 44 | TokenKind::Using 45 | if matches!( 46 | state.iterator.lookahead(1).kind, 47 | TokenKind::Variable | TokenKind::LeftBrace | TokenKind::If 48 | ) => 49 | { 50 | Statement::Using(Box::new(control_flow::using_statement(state)?)) 51 | } 52 | TokenKind::Return => Statement::Return(Box::new(ReturnStatement::Explicit { 53 | comments: state.iterator.comments(), 54 | r#return: utils::skip_keyword(state, TokenKind::Return)?, 55 | expression: if matches!(state.iterator.current().kind, TokenKind::SemiColon) { 56 | None 57 | } else { 58 | expression::create(state).map(Some)? 59 | }, 60 | semicolon: utils::skip_semicolon(state)?, 61 | })), 62 | TokenKind::SemiColon => Statement::Empty(utils::skip_semicolon(state)?), 63 | _ => { 64 | let comments = state.iterator.comments(); 65 | let expression = expression::create(state)?; 66 | 67 | if state.iterator.current().kind == TokenKind::SemiColon { 68 | Statement::Expression(Box::new(ExpressionStatement { 69 | comments, 70 | expression, 71 | semicolon: utils::skip_semicolon(state)?, 72 | })) 73 | } else { 74 | Statement::Return(Box::new(ReturnStatement::Implicit { 75 | comments, 76 | expression, 77 | })) 78 | } 79 | } 80 | }; 81 | 82 | Ok(statement) 83 | } 84 | -------------------------------------------------------------------------------- /src/parser/internal/statement/try.rs: -------------------------------------------------------------------------------- 1 | use crate::lexer::token::TokenKind; 2 | use crate::parser::internal::identifier; 3 | use crate::parser::internal::statement::block; 4 | use crate::parser::internal::utils; 5 | use crate::parser::internal::variable; 6 | use crate::parser::result::ParseResult; 7 | use crate::parser::state::State; 8 | use crate::tree::statement::r#try::TryCatchBlockStatement; 9 | use crate::tree::statement::r#try::TryCatchTypeStatement; 10 | use crate::tree::statement::r#try::TryFinallyBlockStatement; 11 | use crate::tree::statement::r#try::TryStatement; 12 | 13 | pub fn try_statement(state: &mut State) -> ParseResult { 14 | Ok(TryStatement { 15 | comments: state.iterator.comments(), 16 | r#try: utils::skip_keyword(state, TokenKind::Try)?, 17 | block: block::block_statement(state)?, 18 | catches: { 19 | let mut catches = Vec::new(); 20 | loop { 21 | let current = state.iterator.current(); 22 | if current.kind != TokenKind::Catch { 23 | break; 24 | } 25 | 26 | catches.push(TryCatchBlockStatement { 27 | comments: state.iterator.comments(), 28 | catch: utils::skip_keyword(state, TokenKind::Catch)?, 29 | left_parenthesis: utils::skip_left_parenthesis(state)?, 30 | types: try_statement_catch_type(state)?, 31 | variable: if state.iterator.current().kind == TokenKind::RightParen { 32 | None 33 | } else { 34 | Some(variable::parse(state)?) 35 | }, 36 | right_parenthesis: utils::skip_right_parenthesis(state)?, 37 | block: block::block_statement(state)?, 38 | }) 39 | } 40 | 41 | catches 42 | }, 43 | finally: if state.iterator.current().kind == TokenKind::Finally { 44 | Some(TryFinallyBlockStatement { 45 | comments: state.iterator.comments(), 46 | finally: utils::skip_keyword(state, TokenKind::Finally)?, 47 | block: block::block_statement(state)?, 48 | }) 49 | } else { 50 | None 51 | }, 52 | }) 53 | } 54 | 55 | #[inline(always)] 56 | fn try_statement_catch_type(state: &mut State) -> ParseResult { 57 | let id = identifier::fully_qualified_type_identifier(state)?; 58 | 59 | if state.iterator.current().kind == TokenKind::Pipe { 60 | state.iterator.next(); 61 | 62 | let mut types = vec![id]; 63 | loop { 64 | types.push(identifier::fully_qualified_type_identifier(state)?); 65 | 66 | if state.iterator.current().kind != TokenKind::Pipe { 67 | break; 68 | } 69 | 70 | state.iterator.next(); 71 | } 72 | 73 | return Ok(TryCatchTypeStatement::Union(types)); 74 | } 75 | 76 | Ok(TryCatchTypeStatement::Identifier(id)) 77 | } 78 | -------------------------------------------------------------------------------- /src/parser/internal/variable.rs: -------------------------------------------------------------------------------- 1 | use crate::lexer::token::TokenKind; 2 | use crate::parser::result::ParseResult; 3 | use crate::parser::state::State; 4 | use crate::tree::variable::Variable; 5 | 6 | pub fn parse(state: &mut State) -> ParseResult { 7 | let current = state.iterator.current(); 8 | if let TokenKind::Variable = ¤t.kind { 9 | let position = current.position; 10 | let name = current.value.clone(); 11 | state.iterator.next(); 12 | 13 | return Ok(Variable { position, name }); 14 | } 15 | 16 | crate::parser_bail!(state, unexpected_token(vec!["a variable"], current)); 17 | } 18 | -------------------------------------------------------------------------------- /src/parser/macros.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | macro_rules! parser_report { 3 | ($state:expr, $issue:ident($($args:expr),+$(,)?)$(,)?) => { 4 | { 5 | let issue = $crate::parser::issue::$issue($state, $($args,)+); 6 | 7 | $state.record(issue); 8 | } 9 | }; 10 | ($state:expr, $issue:ident) => { 11 | { 12 | let issue = $crate::parser::issue::$issue($state); 13 | 14 | $state.record(issue); 15 | } 16 | }; 17 | } 18 | 19 | #[macro_export] 20 | macro_rules! parser_bail { 21 | ($state:expr, $issue:ident($($args:expr),+$(,)?)$(,)?) => { 22 | { 23 | let issue = $crate::parser::issue::$issue($state, $($args,)+); 24 | 25 | return Err(Box::new($state.report(issue))); 26 | } 27 | }; 28 | ($state:expr, $issue:ident) => { 29 | { 30 | let issue = $crate::parser::issue::$issue($state); 31 | 32 | return Err(Box::new($state.report(issue))); 33 | } 34 | }; 35 | } 36 | -------------------------------------------------------------------------------- /src/parser/mod.rs: -------------------------------------------------------------------------------- 1 | use ara_reporting::Report; 2 | use ara_reporting::ReportFooter; 3 | use ara_source::source::Source; 4 | use ara_source::SourceMap; 5 | 6 | use crate::lexer; 7 | use crate::lexer::iterator::TokenIterator; 8 | use crate::lexer::token::Token; 9 | use crate::parser::internal::definition; 10 | use crate::parser::state::State; 11 | use crate::tree::Tree; 12 | use crate::tree::TreeMap; 13 | 14 | pub mod issue; 15 | 16 | pub(in crate::parser) mod internal; 17 | pub(in crate::parser) mod macros; 18 | pub(in crate::parser) mod result; 19 | pub(in crate::parser) mod state; 20 | 21 | pub fn parse_map(map: &SourceMap) -> Result> { 22 | let mut trees = vec![]; 23 | let mut reports = vec![]; 24 | 25 | for source in &map.sources { 26 | match parse(source) { 27 | Ok(tree) => trees.push(tree), 28 | Err(report) => reports.push(report), 29 | } 30 | } 31 | 32 | if !reports.is_empty() { 33 | let mut issues = vec![]; 34 | for mut report in reports { 35 | issues.append(&mut report.issues); 36 | } 37 | 38 | Err(Box::new(Report { 39 | issues, 40 | footer: Some(ReportFooter::new( 41 | "failed to parse source map due to the above issue(s)", 42 | )), 43 | })) 44 | } else { 45 | Ok(TreeMap::new(trees)) 46 | } 47 | } 48 | 49 | pub fn parse(source: &Source) -> Result> { 50 | let tokens = match lexer::lex(source) { 51 | Ok(tokens) => tokens, 52 | Err(issue) => { 53 | return Err(Box::new(Report { 54 | issues: vec![*issue], 55 | footer: Some(ReportFooter::new(format!( 56 | "failed to parse \"{}\" due to the above issue(s)", 57 | source.name(), 58 | ))), 59 | })) 60 | } 61 | }; 62 | 63 | construct(source, &tokens) 64 | } 65 | 66 | pub fn construct(source: &Source, tokens: &[Token]) -> Result> { 67 | let mut iterator = TokenIterator::new(tokens); 68 | let mut state = State::new(source, &mut iterator); 69 | 70 | let definitions = definition::tree(&mut state)?; 71 | 72 | state.finish(Tree::new(source.name(), definitions)) 73 | } 74 | -------------------------------------------------------------------------------- /src/parser/result.rs: -------------------------------------------------------------------------------- 1 | use ara_reporting::Report; 2 | 3 | pub type ParseResult = Result>; 4 | -------------------------------------------------------------------------------- /src/parser/state/mod.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | 3 | use ara_reporting::issue::Issue; 4 | use ara_reporting::{Report, ReportFooter}; 5 | use ara_source::source::Source; 6 | 7 | use crate::lexer::iterator::TokenIterator; 8 | use crate::lexer::token::Token; 9 | use crate::parser::result::ParseResult; 10 | use crate::tree::definition::attribute::AttributeGroupDefinition; 11 | use crate::tree::identifier::Identifier; 12 | 13 | #[derive(Debug)] 14 | pub struct State<'a> { 15 | pub source: &'a Source, 16 | pub iterator: &'a mut TokenIterator<'a>, 17 | pub namespace: Option, 18 | pub attributes: Vec, 19 | pub issues: Vec, 20 | pub ignored_shift_at: Option<&'a Token>, 21 | } 22 | 23 | impl<'a> State<'a> { 24 | pub fn new(source: &'a Source, iterator: &'a mut TokenIterator<'a>) -> Self { 25 | Self { 26 | source, 27 | iterator, 28 | namespace: None, 29 | attributes: vec![], 30 | issues: vec![], 31 | ignored_shift_at: None, 32 | } 33 | } 34 | 35 | pub fn attribute(&mut self, attr: AttributeGroupDefinition) { 36 | self.attributes.push(attr); 37 | } 38 | 39 | pub fn get_attributes(&mut self) -> Vec { 40 | let mut attributes = vec![]; 41 | 42 | std::mem::swap(&mut self.attributes, &mut attributes); 43 | 44 | attributes 45 | } 46 | 47 | pub fn record(&mut self, issue: Issue) { 48 | self.issues.push(issue); 49 | } 50 | 51 | pub fn report(&mut self, issue: Issue) -> Report { 52 | if let Some(token) = self.ignored_shift_at { 53 | crate::parser_report!(self, unexpected_token(vec![">".to_string()], token)); 54 | } 55 | 56 | let mut issues = vec![]; 57 | 58 | std::mem::swap(&mut self.issues, &mut issues); 59 | 60 | Report { 61 | issues: issues.into_iter().chain(std::iter::once(issue)).collect(), 62 | footer: Some(ReportFooter::new(format!( 63 | "failed to parse \"{}\" due to the above issue(s)", 64 | self.source.name(), 65 | ))), 66 | } 67 | } 68 | 69 | pub fn finish(&mut self, item: T) -> ParseResult { 70 | if let Some(token) = self.ignored_shift_at { 71 | crate::parser_report!(self, unexpected_token(vec![">".to_string()], token)); 72 | } 73 | 74 | if self.issues.is_empty() { 75 | Ok(item) 76 | } else { 77 | Err(Box::new(Report { 78 | issues: self.issues.drain(..).collect(), 79 | footer: Some(ReportFooter::new(format!( 80 | "failed to parse \"{}\" due to the above issue(s)", 81 | self.source.name(), 82 | ))), 83 | })) 84 | } 85 | } 86 | 87 | pub fn namespace(&mut self, namespace: Identifier) { 88 | self.namespace = Some(namespace); 89 | } 90 | 91 | pub fn named(&self, name: &T) -> String { 92 | if let Some(namespace) = &self.namespace { 93 | format!("{namespace}\\{name}") 94 | } else { 95 | name.to_string() 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/traverser/mod.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | 3 | use crate::traverser::visitor::NodeVisitor; 4 | use crate::tree::Tree; 5 | use crate::tree::TreeMap; 6 | 7 | pub mod visitor; 8 | 9 | pub struct TreeTraverser { 10 | visitors: Vec>>, 11 | } 12 | 13 | impl TreeTraverser { 14 | pub fn new(visitors: Vec>>) -> Self { 15 | Self { visitors } 16 | } 17 | 18 | pub fn traverse(&mut self, map: &TreeMap) -> Result<(), Vec> { 19 | let mut errors = Vec::new(); 20 | 21 | for tree in &map.trees { 22 | if let Err(mut error) = self.traverse_tree(tree) { 23 | errors.append(&mut error); 24 | } 25 | } 26 | 27 | if errors.is_empty() { 28 | Ok(()) 29 | } else { 30 | Err(errors) 31 | } 32 | } 33 | 34 | pub fn traverse_tree(&mut self, tree: &Tree) -> Result<(), Vec> { 35 | let mut errors = Vec::new(); 36 | 37 | for visitor in &mut self.visitors { 38 | if let Err(error) = visitor.visit_node(&tree.source, &tree.definitions, None) { 39 | errors.push(error); 40 | } 41 | } 42 | 43 | if errors.is_empty() { 44 | Ok(()) 45 | } else { 46 | Err(errors) 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/traverser/visitor.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | 3 | use crate::tree::Node; 4 | 5 | pub trait NodeVisitor { 6 | fn visit_node( 7 | &mut self, 8 | source: &str, 9 | node: &dyn Node, 10 | parent: Option<&dyn Node>, 11 | ) -> Result<(), E> { 12 | self.visit(source, node, parent)?; 13 | 14 | for child in node.children() { 15 | self.visit_node(source, child, Some(node))?; 16 | } 17 | 18 | Ok(()) 19 | } 20 | 21 | fn visit(&mut self, source: &str, node: &dyn Node, parent: Option<&dyn Node>) -> Result<(), E>; 22 | } 23 | -------------------------------------------------------------------------------- /src/tree/comment.rs: -------------------------------------------------------------------------------- 1 | use bincode::Decode; 2 | use bincode::Encode; 3 | use schemars::JsonSchema; 4 | use serde::Deserialize; 5 | use serde::Serialize; 6 | 7 | use crate::lexer::byte_string::ByteString; 8 | 9 | #[derive(Debug, PartialEq, Eq, Clone, Hash, Deserialize, Serialize, Encode, Decode, JsonSchema)] 10 | #[serde(rename_all = "snake_case", tag = "type")] 11 | pub enum CommentFormat { 12 | SingleLine, 13 | MultiLine, 14 | HashMark, 15 | Document, 16 | } 17 | 18 | #[derive(Debug, PartialEq, Eq, Clone, Hash, Deserialize, Serialize, Encode, Decode, JsonSchema)] 19 | #[serde(rename_all = "snake_case")] 20 | pub struct Comment { 21 | pub position: usize, 22 | pub format: CommentFormat, 23 | pub content: ByteString, 24 | } 25 | 26 | #[derive(Debug, PartialEq, Eq, Clone, Hash, Deserialize, Serialize, Encode, Decode, JsonSchema)] 27 | #[serde(rename_all = "snake_case")] 28 | pub struct CommentGroup { 29 | pub comments: Vec, 30 | } 31 | -------------------------------------------------------------------------------- /src/tree/definition/namespace.rs: -------------------------------------------------------------------------------- 1 | use bincode::Decode; 2 | use bincode::Encode; 3 | use schemars::JsonSchema; 4 | use serde::Deserialize; 5 | use serde::Serialize; 6 | 7 | use crate::tree::definition::Definition; 8 | use crate::tree::identifier::Identifier; 9 | use crate::tree::token::Keyword; 10 | use crate::tree::Node; 11 | 12 | #[derive(Debug, PartialEq, Eq, Clone, Hash, Deserialize, Serialize, Encode, Decode, JsonSchema)] 13 | #[serde(rename_all = "snake_case")] 14 | pub struct NamespaceDefinition { 15 | pub namespace: Keyword, 16 | pub name: Identifier, 17 | pub semicolon: usize, 18 | pub definitions: Vec, 19 | } 20 | 21 | impl Node for NamespaceDefinition { 22 | fn initial_position(&self) -> usize { 23 | self.namespace.initial_position() 24 | } 25 | 26 | fn final_position(&self) -> usize { 27 | self.semicolon + 1 28 | } 29 | 30 | fn children(&self) -> Vec<&dyn Node> { 31 | let mut children: Vec<&dyn Node> = vec![&self.namespace, &self.name]; 32 | 33 | for definition in &self.definitions { 34 | children.push(definition); 35 | } 36 | 37 | children 38 | } 39 | 40 | fn get_description(&self) -> String { 41 | "namespace definition".to_string() 42 | } 43 | } 44 | 45 | impl std::fmt::Display for NamespaceDefinition { 46 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 47 | write!(f, "{} {};", self.namespace, self.name)?; 48 | Ok(()) 49 | } 50 | } 51 | 52 | #[cfg(test)] 53 | mod tests { 54 | use super::*; 55 | use crate::lexer::byte_string::ByteString; 56 | 57 | #[test] 58 | fn test_namespace_definition_display() { 59 | let namespace_definition = NamespaceDefinition { 60 | namespace: Keyword { 61 | value: ByteString::from("namespace"), 62 | position: 0, 63 | }, 64 | name: Identifier { 65 | position: 0, 66 | value: ByteString::from("Foo\\Bar"), 67 | }, 68 | semicolon: 14, 69 | definitions: vec![], 70 | }; 71 | 72 | assert_eq!(namespace_definition.to_string(), "namespace Foo\\Bar;"); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/tree/expression/construct.rs: -------------------------------------------------------------------------------- 1 | use bincode::Decode; 2 | use bincode::Encode; 3 | use schemars::JsonSchema; 4 | use serde::Deserialize; 5 | use serde::Serialize; 6 | 7 | use crate::tree::comment::CommentGroup; 8 | use crate::tree::expression::Expression; 9 | use crate::tree::token::Keyword; 10 | use crate::tree::Node; 11 | 12 | #[derive(Debug, PartialEq, Eq, Clone, Hash, Deserialize, Serialize, Encode, Decode, JsonSchema)] 13 | #[serde(rename_all = "snake_case", tag = "type", content = "value")] 14 | pub enum ExitConstructExpression { 15 | Exit { 16 | comments: CommentGroup, 17 | exit: Keyword, 18 | }, 19 | // `exit(42)` 20 | ExitWith { 21 | comments: CommentGroup, 22 | exit: Keyword, 23 | left_parenthesis: usize, 24 | value: Option>, 25 | right_parenthesis: usize, 26 | }, 27 | } 28 | 29 | impl Node for ExitConstructExpression { 30 | fn comments(&self) -> Option<&CommentGroup> { 31 | match &self { 32 | Self::Exit { comments, .. } => Some(comments), 33 | Self::ExitWith { comments, .. } => Some(comments), 34 | } 35 | } 36 | 37 | fn initial_position(&self) -> usize { 38 | match &self { 39 | Self::Exit { exit, .. } | Self::ExitWith { exit, .. } => exit.initial_position(), 40 | } 41 | } 42 | 43 | fn final_position(&self) -> usize { 44 | match &self { 45 | Self::Exit { exit, .. } => exit.final_position(), 46 | Self::ExitWith { 47 | right_parenthesis, .. 48 | } => right_parenthesis + 1, 49 | } 50 | } 51 | 52 | fn children(&self) -> Vec<&dyn Node> { 53 | match &self { 54 | Self::Exit { exit, .. } => vec![exit], 55 | Self::ExitWith { exit, value, .. } => { 56 | if let Some(value) = value { 57 | vec![exit, value.as_ref()] 58 | } else { 59 | vec![exit] 60 | } 61 | } 62 | } 63 | } 64 | 65 | fn get_description(&self) -> String { 66 | "exit construct expression".to_string() 67 | } 68 | } 69 | 70 | impl std::fmt::Display for ExitConstructExpression { 71 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 72 | match &self { 73 | Self::Exit { .. } => write!(f, "exit;"), 74 | Self::ExitWith { value, .. } => { 75 | if let Some(value) = value { 76 | write!(f, "exit({});", value) 77 | } else { 78 | write!(f, "exit;") 79 | } 80 | } 81 | } 82 | } 83 | } 84 | 85 | #[cfg(test)] 86 | mod tests { 87 | use super::*; 88 | use crate::lexer::byte_string::ByteString; 89 | use crate::tree::expression::literal::Literal::Integer; 90 | use crate::tree::expression::literal::LiteralInteger; 91 | 92 | #[test] 93 | fn test_exit_display() { 94 | let exit = ExitConstructExpression::Exit { 95 | comments: CommentGroup { comments: vec![] }, 96 | exit: Keyword::new(ByteString::from("exit"), 0), 97 | }; 98 | 99 | assert_eq!(exit.to_string(), "exit;"); 100 | 101 | let value = Expression::Literal(Integer(LiteralInteger { 102 | comments: CommentGroup { comments: vec![] }, 103 | position: 0, 104 | value: ByteString::from("1"), 105 | })); 106 | 107 | let exit_with = ExitConstructExpression::ExitWith { 108 | comments: CommentGroup { comments: vec![] }, 109 | exit: Keyword::new(ByteString::from("exit"), 0), 110 | left_parenthesis: 0, 111 | value: Some(Box::new(value)), 112 | right_parenthesis: 0, 113 | }; 114 | 115 | assert_eq!(exit_with.to_string(), "exit(1);"); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/tree/expression/generic.rs: -------------------------------------------------------------------------------- 1 | use bincode::Decode; 2 | use bincode::Encode; 3 | use schemars::JsonSchema; 4 | use serde::Deserialize; 5 | use serde::Serialize; 6 | 7 | use crate::tree::definition::r#type::TypeDefinition; 8 | use crate::tree::utils::CommaSeparated; 9 | use crate::tree::Node; 10 | 11 | #[derive(Debug, PartialEq, Eq, Clone, Hash, Deserialize, Serialize, Encode, Decode, JsonSchema)] 12 | #[serde(rename_all = "snake_case")] 13 | pub struct GenericGroupExpression { 14 | pub double_colon_less_than: usize, 15 | pub types: CommaSeparated, 16 | pub greater_than: usize, 17 | } 18 | 19 | impl Node for GenericGroupExpression { 20 | fn initial_position(&self) -> usize { 21 | self.double_colon_less_than 22 | } 23 | 24 | fn final_position(&self) -> usize { 25 | self.greater_than + 1 26 | } 27 | 28 | fn children(&self) -> Vec<&dyn Node> { 29 | self.types.inner.iter().map(|t| t as &dyn Node).collect() 30 | } 31 | 32 | fn get_description(&self) -> String { 33 | "generic group expression".to_string() 34 | } 35 | } 36 | 37 | impl std::fmt::Display for GenericGroupExpression { 38 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 39 | write!(f, "::<{}>", self.types) 40 | } 41 | } 42 | 43 | #[cfg(test)] 44 | mod tests { 45 | use super::*; 46 | use crate::lexer::byte_string::ByteString; 47 | use crate::tree::identifier::Identifier; 48 | use crate::tree::identifier::TemplatedIdentifier; 49 | 50 | #[test] 51 | fn test_generic_group_expression_display() { 52 | let generic_group_expression = GenericGroupExpression { 53 | double_colon_less_than: 0, 54 | types: CommaSeparated { 55 | inner: vec![ 56 | TypeDefinition::Identifier(TemplatedIdentifier { 57 | name: Identifier { 58 | position: 3, 59 | value: ByteString::from("T"), 60 | }, 61 | templates: None, 62 | }), 63 | TypeDefinition::Identifier(TemplatedIdentifier { 64 | name: Identifier { 65 | position: 3, 66 | value: ByteString::from("P"), 67 | }, 68 | templates: None, 69 | }), 70 | ], 71 | commas: vec![], 72 | }, 73 | greater_than: 0, 74 | }; 75 | 76 | assert_eq!( 77 | format!("{}", generic_group_expression), 78 | "::".to_string() 79 | ); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/tree/expression/magic_constant.rs: -------------------------------------------------------------------------------- 1 | use bincode::Decode; 2 | use bincode::Encode; 3 | use schemars::JsonSchema; 4 | use serde::Deserialize; 5 | use serde::Serialize; 6 | 7 | use crate::lexer::byte_string::ByteString; 8 | use crate::tree::Node; 9 | 10 | #[derive(Debug, PartialEq, Eq, Clone, Hash, Deserialize, Serialize, Encode, Decode, JsonSchema)] 11 | #[serde(rename_all = "snake_case", tag = "type", content = "value")] 12 | pub enum MagicConstant { 13 | Directory { position: usize, value: ByteString }, 14 | File { position: usize, value: ByteString }, 15 | Line { position: usize, value: ByteString }, 16 | Class { position: usize, value: ByteString }, 17 | Function { position: usize, value: ByteString }, 18 | Method { position: usize, value: ByteString }, 19 | Namespace { position: usize, value: ByteString }, 20 | } 21 | 22 | impl Node for MagicConstant { 23 | fn initial_position(&self) -> usize { 24 | match &self { 25 | Self::Directory { position, .. } => *position, 26 | Self::File { position, .. } => *position, 27 | Self::Line { position, .. } => *position, 28 | Self::Class { position, .. } => *position, 29 | Self::Function { position, .. } => *position, 30 | Self::Method { position, .. } => *position, 31 | Self::Namespace { position, .. } => *position, 32 | } 33 | } 34 | 35 | fn final_position(&self) -> usize { 36 | match &self { 37 | Self::Directory { position, value } => position + value.len(), 38 | Self::File { position, value } => position + value.len(), 39 | Self::Line { position, value } => position + value.len(), 40 | Self::Class { position, value } => position + value.len(), 41 | Self::Function { position, value } => position + value.len(), 42 | Self::Method { position, value } => position + value.len(), 43 | Self::Namespace { position, value } => position + value.len(), 44 | } 45 | } 46 | 47 | fn children(&self) -> Vec<&dyn Node> { 48 | vec![] 49 | } 50 | 51 | fn get_description(&self) -> String { 52 | match &self { 53 | Self::Directory { .. } => "directory magic constant expression".to_string(), 54 | Self::File { .. } => "file magic constant expression".to_string(), 55 | Self::Line { .. } => "line magic constant expression".to_string(), 56 | Self::Class { .. } => "class magic constant expression".to_string(), 57 | Self::Function { .. } => "function magic constant expression".to_string(), 58 | Self::Method { .. } => "method magic constant expression".to_string(), 59 | Self::Namespace { .. } => "namespace magic constant expression".to_string(), 60 | } 61 | } 62 | } 63 | 64 | impl std::fmt::Display for MagicConstant { 65 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 66 | match &self { 67 | Self::Directory { value, .. } 68 | | Self::File { value, .. } 69 | | Self::Line { value, .. } 70 | | Self::Class { value, .. } 71 | | Self::Function { value, .. } 72 | | Self::Method { value, .. } 73 | | Self::Namespace { value, .. } => write!(f, "{}", value), 74 | } 75 | } 76 | } 77 | 78 | #[cfg(test)] 79 | mod tests { 80 | use super::*; 81 | use crate::lexer::byte_string::ByteString; 82 | 83 | #[test] 84 | fn test_magic_constant_display() { 85 | let magic_constant = MagicConstant::Directory { 86 | position: 0, 87 | value: ByteString::from("__DIR__"), 88 | }; 89 | assert_eq!(magic_constant.to_string(), "__DIR__"); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/tree/mod.rs: -------------------------------------------------------------------------------- 1 | use bincode::Decode; 2 | use bincode::Encode; 3 | use std::any::Any; 4 | use std::any::TypeId; 5 | 6 | use crate::tree::comment::CommentGroup; 7 | use crate::tree::definition::DefinitionTree; 8 | 9 | pub mod comment; 10 | pub mod definition; 11 | pub mod expression; 12 | pub mod identifier; 13 | pub mod statement; 14 | pub mod token; 15 | pub mod utils; 16 | pub mod variable; 17 | 18 | #[derive(Debug)] 19 | pub struct TreeMap { 20 | pub trees: Vec, 21 | } 22 | 23 | impl TreeMap { 24 | pub fn new(trees: Vec) -> Self { 25 | Self { trees } 26 | } 27 | } 28 | 29 | #[derive(Debug, Hash, Encode, Decode)] 30 | pub struct Tree { 31 | pub source: String, 32 | pub definitions: DefinitionTree, 33 | } 34 | 35 | impl Tree { 36 | pub fn new>(source: S, definitions: DefinitionTree) -> Self { 37 | Self { 38 | source: source.into(), 39 | definitions, 40 | } 41 | } 42 | } 43 | 44 | pub trait Node: Any { 45 | /// The comments associated with the node. 46 | fn comments(&self) -> Option<&CommentGroup> { 47 | None 48 | } 49 | 50 | /// The position of the first token in the node. 51 | fn initial_position(&self) -> usize; 52 | 53 | /// The position of the last token in the node, including the last token itself. 54 | /// 55 | /// This is not necessarily the same as the last token in the node's children. 56 | fn final_position(&self) -> usize; 57 | 58 | /// The children of the node. 59 | /// 60 | /// This is used for traversing the tree. 61 | fn children(&self) -> Vec<&dyn Node>; 62 | 63 | /// The description of the node. 64 | fn get_description(&self) -> String; 65 | } 66 | 67 | pub fn downcast(node: &dyn Node) -> Option<&T> { 68 | // Get `TypeId` of the type this function is instantiated with. 69 | let t = TypeId::of::(); 70 | 71 | // Get `TypeId` of the node we want to downcast. 72 | let concrete = node.type_id(); 73 | 74 | // Compare both `TypeId`s on equality. 75 | if t == concrete { 76 | // Get the concrete type pointer from the trait object. 77 | let concrete = node as *const dyn Node as *const T; 78 | 79 | // Convert it to a reference and return it. 80 | // 81 | // SAFETY: This is safe because we know for sure that the pointer 82 | // is valid and references are only handed out for the lifetime 83 | // of the function. 84 | let concrete = unsafe { &*concrete }; 85 | 86 | Some(concrete) 87 | } else { 88 | None 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/tree/statement/block.rs: -------------------------------------------------------------------------------- 1 | use bincode::Decode; 2 | use bincode::Encode; 3 | use schemars::JsonSchema; 4 | use serde::Deserialize; 5 | use serde::Serialize; 6 | 7 | use crate::tree::comment::CommentGroup; 8 | use crate::tree::statement::Statement; 9 | use crate::tree::Node; 10 | 11 | #[derive(Debug, PartialEq, Eq, Clone, Hash, Deserialize, Serialize, Encode, Decode, JsonSchema)] 12 | #[serde(rename_all = "snake_case")] 13 | pub struct BlockStatement { 14 | pub comments: CommentGroup, 15 | pub left_brace: usize, 16 | pub statements: Vec, 17 | pub right_brace: usize, 18 | } 19 | 20 | impl Node for BlockStatement { 21 | fn comments(&self) -> Option<&CommentGroup> { 22 | Some(&self.comments) 23 | } 24 | 25 | fn initial_position(&self) -> usize { 26 | self.left_brace 27 | } 28 | 29 | fn final_position(&self) -> usize { 30 | self.right_brace + 1 31 | } 32 | 33 | fn children(&self) -> Vec<&dyn Node> { 34 | self.statements 35 | .iter() 36 | .map(|statement| statement as &dyn Node) 37 | .collect() 38 | } 39 | 40 | fn get_description(&self) -> String { 41 | "block statement".to_string() 42 | } 43 | } 44 | 45 | impl std::fmt::Display for BlockStatement { 46 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 47 | write!(f, "{{ /* ... */ }}") 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/tree/statement/expression.rs: -------------------------------------------------------------------------------- 1 | use bincode::Decode; 2 | use bincode::Encode; 3 | use schemars::JsonSchema; 4 | use serde::Deserialize; 5 | use serde::Serialize; 6 | 7 | use crate::tree::comment::CommentGroup; 8 | use crate::tree::expression::Expression; 9 | use crate::tree::Node; 10 | 11 | #[derive(Debug, PartialEq, Eq, Clone, Hash, Deserialize, Serialize, Encode, Decode, JsonSchema)] 12 | #[serde(rename_all = "snake_case")] 13 | pub struct ExpressionStatement { 14 | pub comments: CommentGroup, 15 | pub expression: Expression, 16 | pub semicolon: usize, 17 | } 18 | 19 | impl Node for ExpressionStatement { 20 | fn comments(&self) -> Option<&CommentGroup> { 21 | Some(&self.comments) 22 | } 23 | 24 | fn initial_position(&self) -> usize { 25 | self.expression.initial_position() 26 | } 27 | 28 | fn final_position(&self) -> usize { 29 | self.semicolon + 1 30 | } 31 | 32 | fn children(&self) -> Vec<&dyn Node> { 33 | vec![&self.expression] 34 | } 35 | 36 | fn get_description(&self) -> String { 37 | "expression statement".to_string() 38 | } 39 | } 40 | 41 | impl std::fmt::Display for ExpressionStatement { 42 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 43 | write!(f, "{};", self.expression) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/tree/token.rs: -------------------------------------------------------------------------------- 1 | use bincode::Decode; 2 | use bincode::Encode; 3 | use schemars::JsonSchema; 4 | use serde::Deserialize; 5 | use serde::Serialize; 6 | 7 | use std::fmt::Display; 8 | 9 | use crate::lexer::byte_string::ByteString; 10 | use crate::tree::comment::CommentGroup; 11 | use crate::tree::Node; 12 | 13 | #[derive(Debug, PartialEq, Eq, Clone, Hash, Deserialize, Serialize, Encode, Decode, JsonSchema)] 14 | #[serde(rename_all = "snake_case")] 15 | pub struct Keyword { 16 | pub value: ByteString, 17 | pub position: usize, 18 | } 19 | 20 | impl Keyword { 21 | pub fn new(value: ByteString, position: usize) -> Self { 22 | Self { value, position } 23 | } 24 | } 25 | 26 | impl Node for Keyword { 27 | fn comments(&self) -> Option<&CommentGroup> { 28 | None 29 | } 30 | 31 | fn initial_position(&self) -> usize { 32 | self.position 33 | } 34 | 35 | fn final_position(&self) -> usize { 36 | self.position + self.value.len() 37 | } 38 | 39 | fn children(&self) -> Vec<&dyn Node> { 40 | vec![] 41 | } 42 | 43 | fn get_description(&self) -> String { 44 | "keyword".to_string() 45 | } 46 | } 47 | 48 | impl Display for Keyword { 49 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 50 | write!(f, "{}", self.value) 51 | } 52 | } 53 | 54 | #[cfg(test)] 55 | mod tests { 56 | use super::*; 57 | 58 | #[test] 59 | fn test_keyword_display() { 60 | let keyword = Keyword::new(ByteString::from("iterable"), 0); 61 | 62 | assert_eq!(keyword.to_string(), "iterable"); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/tree/utils.rs: -------------------------------------------------------------------------------- 1 | use bincode::Decode; 2 | use bincode::Encode; 3 | use schemars::JsonSchema; 4 | use serde::Deserialize; 5 | use serde::Serialize; 6 | 7 | use crate::tree::Node; 8 | 9 | #[derive(Debug, PartialEq, Eq, Clone, Hash, Deserialize, Serialize, Encode, Decode, JsonSchema)] 10 | #[serde(rename_all = "snake_case")] 11 | pub struct CommaSeparated { 12 | pub inner: Vec, 13 | pub commas: Vec, // `,` 14 | } 15 | 16 | impl std::fmt::Display for CommaSeparated { 17 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 18 | write!( 19 | f, 20 | "{}", 21 | self.inner 22 | .iter() 23 | .map(|node| node.to_string()) 24 | .collect::>() 25 | .join(", ") 26 | ) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/tree/variable.rs: -------------------------------------------------------------------------------- 1 | use bincode::Decode; 2 | use bincode::Encode; 3 | use schemars::JsonSchema; 4 | use serde::Deserialize; 5 | use serde::Serialize; 6 | 7 | use std::fmt::Display; 8 | 9 | use crate::lexer::byte_string::ByteString; 10 | use crate::tree::Node; 11 | 12 | #[derive(Debug, PartialEq, Eq, Clone, Hash, Deserialize, Serialize, Encode, Decode, JsonSchema)] 13 | #[serde(rename_all = "snake_case")] 14 | pub struct Variable { 15 | pub position: usize, 16 | pub name: ByteString, 17 | } 18 | 19 | impl Node for Variable { 20 | fn initial_position(&self) -> usize { 21 | self.position 22 | } 23 | 24 | fn final_position(&self) -> usize { 25 | self.position + self.name.len() 26 | } 27 | 28 | fn children(&self) -> Vec<&dyn Node> { 29 | vec![] 30 | } 31 | 32 | fn get_description(&self) -> String { 33 | "variable".to_string() 34 | } 35 | } 36 | 37 | impl Display for Variable { 38 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 39 | write!(f, "${}", self.name) 40 | } 41 | } 42 | 43 | #[cfg(test)] 44 | mod tests { 45 | use super::*; 46 | 47 | #[test] 48 | fn test_variable_display() { 49 | let variable = Variable { 50 | position: 0, 51 | name: ByteString::from("foo"), 52 | }; 53 | 54 | assert_eq!(variable.to_string(), "$foo"); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /tests/bincode_test.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | use ara_source::source::Source; 4 | use ara_source::source::SourceKind; 5 | 6 | use ara_parser::parser; 7 | use ara_parser::tree::Tree; 8 | 9 | #[test] 10 | fn test_bincode() -> io::Result<()> { 11 | let code = r#" 12 | function println(string $format, string|int|float ...$args): void { 13 | printf($format, ...$args); 14 | printf("\n"); 15 | } 16 | 17 | function main(): void { 18 | $a = 1; 19 | $b = 2; 20 | $c = $a + $b; 21 | 22 | println("a = %d", $a); 23 | println("b = %d", $b); 24 | println("c = a + b = %d", $c); 25 | } 26 | "#; 27 | 28 | let source = Source::inline(SourceKind::Script, code); 29 | let tree = parser::parse(&source).unwrap(); 30 | 31 | let config = bincode::config::standard(); 32 | let encoded_tree = bincode::encode_to_vec(&tree, config).unwrap(); 33 | let decoded_tree = bincode::decode_from_slice::(&encoded_tree[..], config) 34 | .unwrap() 35 | .0; 36 | 37 | dbg!(encoded_tree); 38 | dbg!(decoded_tree); 39 | 40 | Ok(()) 41 | } 42 | -------------------------------------------------------------------------------- /tests/samples/0001/code.ara: -------------------------------------------------------------------------------- 1 | function foo(): void { 2 | a; 3 | 4 | $a; 5 | 6 | $a::b; 7 | 8 | $a->b; 9 | 10 | $a::b(); 11 | $a::b($a); 12 | $a::b(...); 13 | $a::b(...$a); 14 | 15 | $a->b(); 16 | $a->b($a); 17 | $a->b(...); 18 | $a->b(...$a); 19 | 20 | $a?->b(); 21 | $a?->b($a); 22 | $a?->b(...$a); 23 | 24 | $a::b::(); 25 | $a::b::($a); 26 | $a::b::(...); 27 | $a::b::(...$a); 28 | 29 | $a->b::(); 30 | $a->b::($a); 31 | $a->b::(...); 32 | $a->b::(...$a); 33 | 34 | $a?->b::(); 35 | $a?->b::($a); 36 | $a?->b::(...$a); 37 | 38 | a(); 39 | a($a); 40 | a(...); 41 | a(...$a); 42 | 43 | a::(); 44 | a::($a); 45 | a::(...); 46 | a::(...$a); 47 | 48 | new Foo(); 49 | new Foo($a); 50 | 51 | new Foo::($a); 52 | new Foo::(...$a); 53 | 54 | new Foo::($a); 55 | new Foo::(...$a); 56 | 57 | $c = $a | $b; 58 | $c = $a & $b; 59 | $c = $a ^ $b; 60 | $c = $a << $b; 61 | $c = $a >> $b; 62 | 63 | $c = $a ** $b; 64 | $c = $a + $b; 65 | $c = $a - $b; 66 | $c = $a * $b; 67 | $c = $a / $b; 68 | $c = $a % $b; 69 | $c = $a . $b; 70 | 71 | $c = $a ?? $b; 72 | 73 | $c = $a && $b; 74 | $c = $a || $b; 75 | 76 | $c = $a == $b; 77 | $c = $a === $b; 78 | $c = $a != $b; 79 | $c = $a !== $b; 80 | $c = $a <= $b; 81 | $c = $a >= $b; 82 | $c = $a < $b; 83 | $c = $a > $b; 84 | 85 | $c = $a <=> $b; 86 | 87 | $a ??= $b; 88 | $a += $b; 89 | $a -= $b; 90 | $a *= $b; 91 | $a /= $b; 92 | $a %= $b; 93 | $a .= $b; 94 | $a &= $b; 95 | $a |= $b; 96 | $a ^= $b; 97 | $a <<= $b; 98 | $a >>= $b; 99 | $a **= $b; 100 | 101 | $a++; 102 | $a--; 103 | ++$a; 104 | --$a; 105 | 106 | $a = !$b; 107 | $a = ~$b; 108 | $a = +$b; 109 | $a = -$b; 110 | 111 | $a = vec[]; 112 | $a = vec[$a, $b]; 113 | $a = vec[$a, $b,]; 114 | 115 | $a = dict[]; 116 | $a = dict[$a => $b, $a => $b]; 117 | $a = dict[$a => $b, $a => $b,]; 118 | 119 | $a = $a[$b]; 120 | 121 | $a = $a[$b] ?? $c; 122 | 123 | $a[$b] = $c; 124 | $a[$b] ??= $c; 125 | } 126 | -------------------------------------------------------------------------------- /tests/samples/0002/code.ara: -------------------------------------------------------------------------------- 1 | 2 | function foo(): void { 3 | $a?->b($c); 4 | } 5 | -------------------------------------------------------------------------------- /tests/samples/0003/code.ara: -------------------------------------------------------------------------------- 1 | 2 | function foo(): void { 3 | $foo = give_me_foo(); 4 | 5 | $a = [ 6 | 'single' => $foo instanceof Foo, 7 | 'multiple' => $foo instanceof Bar && $foo instanceof Baz 8 | ]; 9 | } 10 | -------------------------------------------------------------------------------- /tests/samples/0003/error.txt: -------------------------------------------------------------------------------- 1 | error[P0011]: unexpected token `[`, expected an expression 2 | --> 0003/code.ara:5:10 3 | | 4 | 5 | $a = [ 5 | | ^ 6 | 7 | error: failed to parse "0003/code.ara" due to the above issue(s) 8 | = summary: 1 error(s) 9 | 10 | -------------------------------------------------------------------------------- /tests/samples/0004/code.ara: -------------------------------------------------------------------------------- 1 | function q(): void { 2 | $a = 2 ** 2; 3 | 4 | $b = 1 ? 2 : 3; 5 | 6 | $c = 1 ? 2 ? 3 : 4 : 5; 7 | 8 | $d = 1 ?: 2 ?: 3; 9 | 10 | $e = 1 ?? 2; 11 | 12 | $f = 1 ?? 2 ?? 3; 13 | } 14 | -------------------------------------------------------------------------------- /tests/samples/0005/code.ara: -------------------------------------------------------------------------------- 1 | function h(): void { 2 | $foo['bar']; 3 | $foo['bar']['baz']; 4 | $foo['bar'] = 'baz'; 5 | } 6 | -------------------------------------------------------------------------------- /tests/samples/0006/code.ara: -------------------------------------------------------------------------------- 1 | function q(): void { 2 | $a = new Foo(); 3 | $b = +1; 4 | $c = ~2; 5 | $d = --$b; 6 | $e = ++$d; 7 | } 8 | -------------------------------------------------------------------------------- /tests/samples/0007/code.ara: -------------------------------------------------------------------------------- 1 | function main(): void { 2 | try { 3 | 4 | } catch (Exception $e) { 5 | 6 | } catch (CustomException $e) { 7 | 8 | } 9 | 10 | try { 11 | 12 | } catch (Exception $e) { 13 | 14 | } finally { 15 | 16 | } 17 | 18 | try { 19 | 20 | } finally {} 21 | 22 | try { 23 | 24 | } catch (Exception) { 25 | 26 | } 27 | 28 | try { 29 | 30 | } catch (Exception $e) { 31 | 32 | } 33 | } -------------------------------------------------------------------------------- /tests/samples/0008/code.ara: -------------------------------------------------------------------------------- 1 | function main(): void { 2 | break; 3 | } 4 | -------------------------------------------------------------------------------- /tests/samples/0008/tree.txt: -------------------------------------------------------------------------------- 1 | DefinitionTree { 2 | definitions: [ 3 | Function( 4 | FunctionDefinition { 5 | attributes: [], 6 | comments: CommentGroup { 7 | comments: [], 8 | }, 9 | modifiers: ModifierGroupDefinition { 10 | position: 0, 11 | modifiers: [], 12 | }, 13 | function: Keyword { 14 | value: "function", 15 | position: 0, 16 | }, 17 | name: Identifier { 18 | position: 9, 19 | value: "main", 20 | }, 21 | templates: None, 22 | parameters: FunctionLikeParameterListDefinition { 23 | comments: CommentGroup { 24 | comments: [], 25 | }, 26 | left_parenthesis: 13, 27 | parameters: CommaSeparated { 28 | inner: [], 29 | commas: [], 30 | }, 31 | right_parenthesis: 14, 32 | }, 33 | return_type: FunctionLikeReturnTypeDefinition { 34 | colon: 15, 35 | type_definition: Void( 36 | Keyword { 37 | value: "void", 38 | position: 17, 39 | }, 40 | ), 41 | }, 42 | body: BlockStatement { 43 | comments: CommentGroup { 44 | comments: [], 45 | }, 46 | left_brace: 22, 47 | statements: [ 48 | Break( 49 | BreakStatement { 50 | comments: CommentGroup { 51 | comments: [], 52 | }, 53 | break: Keyword { 54 | value: "break", 55 | position: 28, 56 | }, 57 | level: None, 58 | semicolon: 33, 59 | }, 60 | ), 61 | ], 62 | right_brace: 35, 63 | }, 64 | }, 65 | ), 66 | ], 67 | eof: 37, 68 | } -------------------------------------------------------------------------------- /tests/samples/0009/code.ara: -------------------------------------------------------------------------------- 1 | function main(): void { 2 | break 2; 3 | } -------------------------------------------------------------------------------- /tests/samples/0009/tree.txt: -------------------------------------------------------------------------------- 1 | DefinitionTree { 2 | definitions: [ 3 | Function( 4 | FunctionDefinition { 5 | attributes: [], 6 | comments: CommentGroup { 7 | comments: [], 8 | }, 9 | modifiers: ModifierGroupDefinition { 10 | position: 0, 11 | modifiers: [], 12 | }, 13 | function: Keyword { 14 | value: "function", 15 | position: 0, 16 | }, 17 | name: Identifier { 18 | position: 9, 19 | value: "main", 20 | }, 21 | templates: None, 22 | parameters: FunctionLikeParameterListDefinition { 23 | comments: CommentGroup { 24 | comments: [], 25 | }, 26 | left_parenthesis: 13, 27 | parameters: CommaSeparated { 28 | inner: [], 29 | commas: [], 30 | }, 31 | right_parenthesis: 14, 32 | }, 33 | return_type: FunctionLikeReturnTypeDefinition { 34 | colon: 15, 35 | type_definition: Void( 36 | Keyword { 37 | value: "void", 38 | position: 17, 39 | }, 40 | ), 41 | }, 42 | body: BlockStatement { 43 | comments: CommentGroup { 44 | comments: [], 45 | }, 46 | left_brace: 22, 47 | statements: [ 48 | Break( 49 | BreakStatement { 50 | comments: CommentGroup { 51 | comments: [], 52 | }, 53 | break: Keyword { 54 | value: "break", 55 | position: 28, 56 | }, 57 | level: Some( 58 | LiteralInteger { 59 | comments: CommentGroup { 60 | comments: [], 61 | }, 62 | value: "2", 63 | position: 34, 64 | }, 65 | ), 66 | semicolon: 35, 67 | }, 68 | ), 69 | ], 70 | right_brace: 37, 71 | }, 72 | }, 73 | ), 74 | ], 75 | eof: 38, 76 | } -------------------------------------------------------------------------------- /tests/samples/0010/code.ara: -------------------------------------------------------------------------------- 1 | function main(): void { 2 | continue; 3 | } 4 | -------------------------------------------------------------------------------- /tests/samples/0010/tree.txt: -------------------------------------------------------------------------------- 1 | DefinitionTree { 2 | definitions: [ 3 | Function( 4 | FunctionDefinition { 5 | attributes: [], 6 | comments: CommentGroup { 7 | comments: [], 8 | }, 9 | modifiers: ModifierGroupDefinition { 10 | position: 0, 11 | modifiers: [], 12 | }, 13 | function: Keyword { 14 | value: "function", 15 | position: 0, 16 | }, 17 | name: Identifier { 18 | position: 9, 19 | value: "main", 20 | }, 21 | templates: None, 22 | parameters: FunctionLikeParameterListDefinition { 23 | comments: CommentGroup { 24 | comments: [], 25 | }, 26 | left_parenthesis: 13, 27 | parameters: CommaSeparated { 28 | inner: [], 29 | commas: [], 30 | }, 31 | right_parenthesis: 14, 32 | }, 33 | return_type: FunctionLikeReturnTypeDefinition { 34 | colon: 15, 35 | type_definition: Void( 36 | Keyword { 37 | value: "void", 38 | position: 17, 39 | }, 40 | ), 41 | }, 42 | body: BlockStatement { 43 | comments: CommentGroup { 44 | comments: [], 45 | }, 46 | left_brace: 22, 47 | statements: [ 48 | Continue( 49 | ContinueStatement { 50 | comments: CommentGroup { 51 | comments: [], 52 | }, 53 | continue: Keyword { 54 | value: "continue", 55 | position: 28, 56 | }, 57 | level: None, 58 | semicolon: 36, 59 | }, 60 | ), 61 | ], 62 | right_brace: 38, 63 | }, 64 | }, 65 | ), 66 | ], 67 | eof: 40, 68 | } -------------------------------------------------------------------------------- /tests/samples/0011/code.ara: -------------------------------------------------------------------------------- 1 | function main(): void { 2 | continue 2; 3 | } 4 | -------------------------------------------------------------------------------- /tests/samples/0011/tree.txt: -------------------------------------------------------------------------------- 1 | DefinitionTree { 2 | definitions: [ 3 | Function( 4 | FunctionDefinition { 5 | attributes: [], 6 | comments: CommentGroup { 7 | comments: [], 8 | }, 9 | modifiers: ModifierGroupDefinition { 10 | position: 0, 11 | modifiers: [], 12 | }, 13 | function: Keyword { 14 | value: "function", 15 | position: 0, 16 | }, 17 | name: Identifier { 18 | position: 9, 19 | value: "main", 20 | }, 21 | templates: None, 22 | parameters: FunctionLikeParameterListDefinition { 23 | comments: CommentGroup { 24 | comments: [], 25 | }, 26 | left_parenthesis: 13, 27 | parameters: CommaSeparated { 28 | inner: [], 29 | commas: [], 30 | }, 31 | right_parenthesis: 14, 32 | }, 33 | return_type: FunctionLikeReturnTypeDefinition { 34 | colon: 15, 35 | type_definition: Void( 36 | Keyword { 37 | value: "void", 38 | position: 17, 39 | }, 40 | ), 41 | }, 42 | body: BlockStatement { 43 | comments: CommentGroup { 44 | comments: [], 45 | }, 46 | left_brace: 22, 47 | statements: [ 48 | Continue( 49 | ContinueStatement { 50 | comments: CommentGroup { 51 | comments: [], 52 | }, 53 | continue: Keyword { 54 | value: "continue", 55 | position: 28, 56 | }, 57 | level: Some( 58 | LiteralInteger { 59 | comments: CommentGroup { 60 | comments: [], 61 | }, 62 | value: "2", 63 | position: 37, 64 | }, 65 | ), 66 | semicolon: 38, 67 | }, 68 | ), 69 | ], 70 | right_brace: 40, 71 | }, 72 | }, 73 | ), 74 | ], 75 | eof: 42, 76 | } -------------------------------------------------------------------------------- /tests/samples/0012/code.ara: -------------------------------------------------------------------------------- 1 | function main(): void { 2 | $foo->bar; 3 | $foo->bar->baz; 4 | } 5 | -------------------------------------------------------------------------------- /tests/samples/0013/code.ara: -------------------------------------------------------------------------------- 1 | function main(): void { 2 | $foo->bar(); 3 | $foo->bar()->baz(); 4 | $foo->bar()(); 5 | } 6 | -------------------------------------------------------------------------------- /tests/samples/0014/code.ara: -------------------------------------------------------------------------------- 1 | function main(): void { 2 | 'foo' . 'bar' . 'baz'; 3 | } 4 | -------------------------------------------------------------------------------- /tests/samples/0015/code.ara: -------------------------------------------------------------------------------- 1 | function fib(int $n): int { 2 | if ($n < 2) { 3 | return $n; 4 | } 5 | 6 | return fib($n - 1) + fib($n - 2); 7 | } 8 | -------------------------------------------------------------------------------- /tests/samples/0016/code.ara: -------------------------------------------------------------------------------- 1 | function main(): void { 2 | if($foo) { return $foo; } else { return $foo; } 3 | } 4 | -------------------------------------------------------------------------------- /tests/samples/0017/code.ara: -------------------------------------------------------------------------------- 1 | function main(): void { 2 | if($foo) { 3 | return $foo; 4 | } elseif($foo) { 5 | return $foo; 6 | } else { 7 | return $foo; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/samples/0018/code.ara: -------------------------------------------------------------------------------- 1 | class Foo {} -------------------------------------------------------------------------------- /tests/samples/0018/tree.txt: -------------------------------------------------------------------------------- 1 | DefinitionTree { 2 | definitions: [ 3 | Class( 4 | ClassDefinition { 5 | comments: CommentGroup { 6 | comments: [], 7 | }, 8 | attributes: [], 9 | modifiers: ModifierGroupDefinition { 10 | position: 0, 11 | modifiers: [], 12 | }, 13 | class: Keyword { 14 | value: "class", 15 | position: 0, 16 | }, 17 | name: Identifier { 18 | position: 6, 19 | value: "Foo", 20 | }, 21 | templates: None, 22 | extends: None, 23 | implements: None, 24 | body: ClassDefinitionBody { 25 | left_brace: 10, 26 | members: [], 27 | right_brace: 11, 28 | }, 29 | }, 30 | ), 31 | ], 32 | eof: 12, 33 | } -------------------------------------------------------------------------------- /tests/samples/0019/code.ara: -------------------------------------------------------------------------------- 1 | class Foo extends Bar {} -------------------------------------------------------------------------------- /tests/samples/0019/tree.txt: -------------------------------------------------------------------------------- 1 | DefinitionTree { 2 | definitions: [ 3 | Class( 4 | ClassDefinition { 5 | comments: CommentGroup { 6 | comments: [], 7 | }, 8 | attributes: [], 9 | modifiers: ModifierGroupDefinition { 10 | position: 0, 11 | modifiers: [], 12 | }, 13 | class: Keyword { 14 | value: "class", 15 | position: 0, 16 | }, 17 | name: Identifier { 18 | position: 6, 19 | value: "Foo", 20 | }, 21 | templates: None, 22 | extends: Some( 23 | ClassDefinitionExtends { 24 | extends: Keyword { 25 | value: "extends", 26 | position: 10, 27 | }, 28 | parent: TemplatedIdentifier { 29 | name: Identifier { 30 | position: 18, 31 | value: "Bar", 32 | }, 33 | templates: None, 34 | }, 35 | }, 36 | ), 37 | implements: None, 38 | body: ClassDefinitionBody { 39 | left_brace: 22, 40 | members: [], 41 | right_brace: 23, 42 | }, 43 | }, 44 | ), 45 | ], 46 | eof: 24, 47 | } -------------------------------------------------------------------------------- /tests/samples/0020/code.ara: -------------------------------------------------------------------------------- 1 | class Foo implements Bar, Baz {} -------------------------------------------------------------------------------- /tests/samples/0020/tree.txt: -------------------------------------------------------------------------------- 1 | DefinitionTree { 2 | definitions: [ 3 | Class( 4 | ClassDefinition { 5 | comments: CommentGroup { 6 | comments: [], 7 | }, 8 | attributes: [], 9 | modifiers: ModifierGroupDefinition { 10 | position: 0, 11 | modifiers: [], 12 | }, 13 | class: Keyword { 14 | value: "class", 15 | position: 0, 16 | }, 17 | name: Identifier { 18 | position: 6, 19 | value: "Foo", 20 | }, 21 | templates: None, 22 | extends: None, 23 | implements: Some( 24 | ClassDefinitionImplements { 25 | implements: Keyword { 26 | value: "implements", 27 | position: 10, 28 | }, 29 | interfaces: CommaSeparated { 30 | inner: [ 31 | TemplatedIdentifier { 32 | name: Identifier { 33 | position: 21, 34 | value: "Bar", 35 | }, 36 | templates: None, 37 | }, 38 | TemplatedIdentifier { 39 | name: Identifier { 40 | position: 26, 41 | value: "Baz", 42 | }, 43 | templates: None, 44 | }, 45 | ], 46 | commas: [ 47 | 24, 48 | ], 49 | }, 50 | }, 51 | ), 52 | body: ClassDefinitionBody { 53 | left_brace: 30, 54 | members: [], 55 | right_brace: 31, 56 | }, 57 | }, 58 | ), 59 | ], 60 | eof: 32, 61 | } -------------------------------------------------------------------------------- /tests/samples/0021/code.ara: -------------------------------------------------------------------------------- 1 | function foo(): string {} -------------------------------------------------------------------------------- /tests/samples/0021/tree.txt: -------------------------------------------------------------------------------- 1 | DefinitionTree { 2 | definitions: [ 3 | Function( 4 | FunctionDefinition { 5 | attributes: [], 6 | comments: CommentGroup { 7 | comments: [], 8 | }, 9 | modifiers: ModifierGroupDefinition { 10 | position: 0, 11 | modifiers: [], 12 | }, 13 | function: Keyword { 14 | value: "function", 15 | position: 0, 16 | }, 17 | name: Identifier { 18 | position: 9, 19 | value: "foo", 20 | }, 21 | templates: None, 22 | parameters: FunctionLikeParameterListDefinition { 23 | comments: CommentGroup { 24 | comments: [], 25 | }, 26 | left_parenthesis: 12, 27 | parameters: CommaSeparated { 28 | inner: [], 29 | commas: [], 30 | }, 31 | right_parenthesis: 13, 32 | }, 33 | return_type: FunctionLikeReturnTypeDefinition { 34 | colon: 14, 35 | type_definition: String( 36 | Keyword { 37 | value: "string", 38 | position: 16, 39 | }, 40 | ), 41 | }, 42 | body: BlockStatement { 43 | comments: CommentGroup { 44 | comments: [], 45 | }, 46 | left_brace: 23, 47 | statements: [], 48 | right_brace: 24, 49 | }, 50 | }, 51 | ), 52 | ], 53 | eof: 25, 54 | } -------------------------------------------------------------------------------- /tests/samples/0022/code.ara: -------------------------------------------------------------------------------- 1 | function foo(): void {} -------------------------------------------------------------------------------- /tests/samples/0022/tree.txt: -------------------------------------------------------------------------------- 1 | DefinitionTree { 2 | definitions: [ 3 | Function( 4 | FunctionDefinition { 5 | attributes: [], 6 | comments: CommentGroup { 7 | comments: [], 8 | }, 9 | modifiers: ModifierGroupDefinition { 10 | position: 0, 11 | modifiers: [], 12 | }, 13 | function: Keyword { 14 | value: "function", 15 | position: 0, 16 | }, 17 | name: Identifier { 18 | position: 9, 19 | value: "foo", 20 | }, 21 | templates: None, 22 | parameters: FunctionLikeParameterListDefinition { 23 | comments: CommentGroup { 24 | comments: [], 25 | }, 26 | left_parenthesis: 12, 27 | parameters: CommaSeparated { 28 | inner: [], 29 | commas: [], 30 | }, 31 | right_parenthesis: 13, 32 | }, 33 | return_type: FunctionLikeReturnTypeDefinition { 34 | colon: 14, 35 | type_definition: Void( 36 | Keyword { 37 | value: "void", 38 | position: 16, 39 | }, 40 | ), 41 | }, 42 | body: BlockStatement { 43 | comments: CommentGroup { 44 | comments: [], 45 | }, 46 | left_brace: 21, 47 | statements: [], 48 | right_brace: 22, 49 | }, 50 | }, 51 | ), 52 | ], 53 | eof: 23, 54 | } -------------------------------------------------------------------------------- /tests/samples/0023/code.ara: -------------------------------------------------------------------------------- 1 | 2 | function foo(): object { 3 | return new class implements Foo, Bar {}; 4 | } 5 | -------------------------------------------------------------------------------- /tests/samples/0023/error.txt: -------------------------------------------------------------------------------- 1 | error[P0011]: unexpected token `implements`, expected `(` 2 | --> 0023/code.ara:3:22 3 | | 4 | 3 | return new class implements Foo, Bar {}; 5 | | ^^^^^^^^^^ 6 | 7 | error: failed to parse "0023/code.ara" due to the above issue(s) 8 | = summary: 1 error(s) 9 | 10 | -------------------------------------------------------------------------------- /tests/samples/0024/code.ara: -------------------------------------------------------------------------------- 1 | 2 | function foo(): void { 3 | { 4 | 5 | } 6 | { 7 | { 8 | { 9 | { 10 | { 11 | 12 | } 13 | } 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/samples/0025/code.ara: -------------------------------------------------------------------------------- 1 | class MyClass { 2 | protected string $a; 3 | // my comment 4 | } 5 | -------------------------------------------------------------------------------- /tests/samples/0025/tree.txt: -------------------------------------------------------------------------------- 1 | DefinitionTree { 2 | definitions: [ 3 | Class( 4 | ClassDefinition { 5 | comments: CommentGroup { 6 | comments: [], 7 | }, 8 | attributes: [], 9 | modifiers: ModifierGroupDefinition { 10 | position: 0, 11 | modifiers: [], 12 | }, 13 | class: Keyword { 14 | value: "class", 15 | position: 0, 16 | }, 17 | name: Identifier { 18 | position: 6, 19 | value: "MyClass", 20 | }, 21 | templates: None, 22 | extends: None, 23 | implements: None, 24 | body: ClassDefinitionBody { 25 | left_brace: 14, 26 | members: [ 27 | Property( 28 | PropertyDefinition { 29 | attributes: [], 30 | modifiers: ModifierGroupDefinition { 31 | position: 30, 32 | modifiers: [ 33 | Protected( 34 | Keyword { 35 | value: "protected", 36 | position: 20, 37 | }, 38 | ), 39 | ], 40 | }, 41 | type_definition: String( 42 | Keyword { 43 | value: "string", 44 | position: 30, 45 | }, 46 | ), 47 | entry: Uninitialized { 48 | variable: Variable { 49 | position: 37, 50 | name: "$a", 51 | }, 52 | }, 53 | semicolon: 39, 54 | }, 55 | ), 56 | ], 57 | right_brace: 59, 58 | }, 59 | }, 60 | ), 61 | ], 62 | eof: 61, 63 | } -------------------------------------------------------------------------------- /tests/samples/0026/code.ara: -------------------------------------------------------------------------------- 1 | 2 | 3 | function foo(): void { 4 | do { } while ($a); 5 | } 6 | -------------------------------------------------------------------------------- /tests/samples/0027/code.ara: -------------------------------------------------------------------------------- 1 | function foo(): void { 2 | $foo = new Foo(); 3 | $foo->bar(); 4 | $foo?->bar(); 5 | $bar = $foo?->bar; 6 | $baz = $foo->bar?->baz; 7 | $a = $foo->a(...); 8 | $b = $foo::b(...); 9 | $c = $foo::c(); 10 | $d = $foo->d; 11 | $e = $foo::$d; 12 | $f = $foo?->b?->c(); 13 | } 14 | -------------------------------------------------------------------------------- /tests/samples/0028/code.ara: -------------------------------------------------------------------------------- 1 | const u8 FOO = 1; 2 | const u8 BAR = FOO; 3 | -------------------------------------------------------------------------------- /tests/samples/0028/tree.txt: -------------------------------------------------------------------------------- 1 | DefinitionTree { 2 | definitions: [ 3 | Constant( 4 | ConstantDefinition { 5 | comments: CommentGroup { 6 | comments: [], 7 | }, 8 | const: Keyword { 9 | value: "const", 10 | position: 0, 11 | }, 12 | type_definition: UnsignedInteger( 13 | U8( 14 | Keyword { 15 | value: "u8", 16 | position: 6, 17 | }, 18 | ), 19 | ), 20 | name: Identifier { 21 | position: 9, 22 | value: "FOO", 23 | }, 24 | equals: 13, 25 | value: Literal( 26 | Integer( 27 | LiteralInteger { 28 | comments: CommentGroup { 29 | comments: [], 30 | }, 31 | value: "1", 32 | position: 15, 33 | }, 34 | ), 35 | ), 36 | semicolon: 16, 37 | }, 38 | ), 39 | Constant( 40 | ConstantDefinition { 41 | comments: CommentGroup { 42 | comments: [], 43 | }, 44 | const: Keyword { 45 | value: "const", 46 | position: 19, 47 | }, 48 | type_definition: UnsignedInteger( 49 | U8( 50 | Keyword { 51 | value: "u8", 52 | position: 25, 53 | }, 54 | ), 55 | ), 56 | name: Identifier { 57 | position: 28, 58 | value: "BAR", 59 | }, 60 | equals: 32, 61 | value: Identifier( 62 | Identifier { 63 | position: 34, 64 | value: "FOO", 65 | }, 66 | ), 67 | semicolon: 37, 68 | }, 69 | ), 70 | ], 71 | eof: 39, 72 | } -------------------------------------------------------------------------------- /tests/samples/0029/code.ara: -------------------------------------------------------------------------------- 1 | const int FOO = 1, BAR = 2; -------------------------------------------------------------------------------- /tests/samples/0029/error.txt: -------------------------------------------------------------------------------- 1 | error[P0011]: unexpected token `,`, expected `;` 2 | --> 0029/code.ara:1:18 3 | | 4 | 1 | const int FOO = 1, BAR = 2; 5 | | ^ 6 | 7 | error: failed to parse "0029/code.ara" due to the above issue(s) 8 | = summary: 1 error(s) 9 | 10 | -------------------------------------------------------------------------------- /tests/samples/0030/code.ara: -------------------------------------------------------------------------------- 1 | readonly class Foo {} -------------------------------------------------------------------------------- /tests/samples/0030/tree.txt: -------------------------------------------------------------------------------- 1 | DefinitionTree { 2 | definitions: [ 3 | Class( 4 | ClassDefinition { 5 | comments: CommentGroup { 6 | comments: [], 7 | }, 8 | attributes: [], 9 | modifiers: ModifierGroupDefinition { 10 | position: 9, 11 | modifiers: [ 12 | Readonly( 13 | Keyword { 14 | value: "readonly", 15 | position: 0, 16 | }, 17 | ), 18 | ], 19 | }, 20 | class: Keyword { 21 | value: "class", 22 | position: 9, 23 | }, 24 | name: Identifier { 25 | position: 15, 26 | value: "Foo", 27 | }, 28 | templates: None, 29 | extends: None, 30 | implements: None, 31 | body: ClassDefinitionBody { 32 | left_brace: 19, 33 | members: [], 34 | right_brace: 20, 35 | }, 36 | }, 37 | ), 38 | ], 39 | eof: 21, 40 | } -------------------------------------------------------------------------------- /tests/samples/0031/code.ara: -------------------------------------------------------------------------------- 1 | function a(): true { 2 | return true; 3 | } 4 | -------------------------------------------------------------------------------- /tests/samples/0031/tree.txt: -------------------------------------------------------------------------------- 1 | DefinitionTree { 2 | definitions: [ 3 | Function( 4 | FunctionDefinition { 5 | attributes: [], 6 | comments: CommentGroup { 7 | comments: [], 8 | }, 9 | modifiers: ModifierGroupDefinition { 10 | position: 0, 11 | modifiers: [], 12 | }, 13 | function: Keyword { 14 | value: "function", 15 | position: 0, 16 | }, 17 | name: Identifier { 18 | position: 9, 19 | value: "a", 20 | }, 21 | templates: None, 22 | parameters: FunctionLikeParameterListDefinition { 23 | comments: CommentGroup { 24 | comments: [], 25 | }, 26 | left_parenthesis: 10, 27 | parameters: CommaSeparated { 28 | inner: [], 29 | commas: [], 30 | }, 31 | right_parenthesis: 11, 32 | }, 33 | return_type: FunctionLikeReturnTypeDefinition { 34 | colon: 12, 35 | type_definition: Literal( 36 | True( 37 | LiteralTrue { 38 | comments: CommentGroup { 39 | comments: [], 40 | }, 41 | true: Keyword { 42 | value: "true", 43 | position: 14, 44 | }, 45 | }, 46 | ), 47 | ), 48 | }, 49 | body: BlockStatement { 50 | comments: CommentGroup { 51 | comments: [], 52 | }, 53 | left_brace: 19, 54 | statements: [ 55 | Return( 56 | Explicit { 57 | comments: CommentGroup { 58 | comments: [], 59 | }, 60 | return: Keyword { 61 | value: "return", 62 | position: 25, 63 | }, 64 | expression: Some( 65 | Literal( 66 | True( 67 | LiteralTrue { 68 | comments: CommentGroup { 69 | comments: [], 70 | }, 71 | true: Keyword { 72 | value: "true", 73 | position: 32, 74 | }, 75 | }, 76 | ), 77 | ), 78 | ), 79 | semicolon: 36, 80 | }, 81 | ), 82 | ], 83 | right_brace: 38, 84 | }, 85 | }, 86 | ), 87 | ], 88 | eof: 40, 89 | } -------------------------------------------------------------------------------- /tests/samples/0032/code.ara: -------------------------------------------------------------------------------- 1 | function a(): false { 2 | return false; 3 | } 4 | -------------------------------------------------------------------------------- /tests/samples/0032/tree.txt: -------------------------------------------------------------------------------- 1 | DefinitionTree { 2 | definitions: [ 3 | Function( 4 | FunctionDefinition { 5 | attributes: [], 6 | comments: CommentGroup { 7 | comments: [], 8 | }, 9 | modifiers: ModifierGroupDefinition { 10 | position: 0, 11 | modifiers: [], 12 | }, 13 | function: Keyword { 14 | value: "function", 15 | position: 0, 16 | }, 17 | name: Identifier { 18 | position: 9, 19 | value: "a", 20 | }, 21 | templates: None, 22 | parameters: FunctionLikeParameterListDefinition { 23 | comments: CommentGroup { 24 | comments: [], 25 | }, 26 | left_parenthesis: 10, 27 | parameters: CommaSeparated { 28 | inner: [], 29 | commas: [], 30 | }, 31 | right_parenthesis: 11, 32 | }, 33 | return_type: FunctionLikeReturnTypeDefinition { 34 | colon: 12, 35 | type_definition: Literal( 36 | False( 37 | LiteralFalse { 38 | comments: CommentGroup { 39 | comments: [], 40 | }, 41 | false: Keyword { 42 | value: "false", 43 | position: 14, 44 | }, 45 | }, 46 | ), 47 | ), 48 | }, 49 | body: BlockStatement { 50 | comments: CommentGroup { 51 | comments: [], 52 | }, 53 | left_brace: 20, 54 | statements: [ 55 | Return( 56 | Explicit { 57 | comments: CommentGroup { 58 | comments: [], 59 | }, 60 | return: Keyword { 61 | value: "return", 62 | position: 26, 63 | }, 64 | expression: Some( 65 | Literal( 66 | False( 67 | LiteralFalse { 68 | comments: CommentGroup { 69 | comments: [], 70 | }, 71 | false: Keyword { 72 | value: "false", 73 | position: 33, 74 | }, 75 | }, 76 | ), 77 | ), 78 | ), 79 | semicolon: 38, 80 | }, 81 | ), 82 | ], 83 | right_brace: 40, 84 | }, 85 | }, 86 | ), 87 | ], 88 | eof: 42, 89 | } -------------------------------------------------------------------------------- /tests/samples/0033/code.ara: -------------------------------------------------------------------------------- 1 | enum Foo: int { 2 | case Bar = 2; 3 | case Baz = 4; 4 | } 5 | 6 | -------------------------------------------------------------------------------- /tests/samples/0033/tree.txt: -------------------------------------------------------------------------------- 1 | DefinitionTree { 2 | definitions: [ 3 | Enum( 4 | Backed( 5 | BackedEnumDefinition { 6 | comments: CommentGroup { 7 | comments: [], 8 | }, 9 | attributes: [], 10 | enum: Keyword { 11 | value: "enum", 12 | position: 0, 13 | }, 14 | name: Identifier { 15 | position: 5, 16 | value: "Foo", 17 | }, 18 | backed_type: Int( 19 | 8, 20 | Identifier { 21 | position: 10, 22 | value: "int", 23 | }, 24 | ), 25 | implements: None, 26 | body: BackedEnumBodyDefinition { 27 | left_brace: 14, 28 | members: [ 29 | Case( 30 | BackedEnumCaseDefinition { 31 | attributes: [], 32 | case: Keyword { 33 | value: "case", 34 | position: 20, 35 | }, 36 | name: Identifier { 37 | position: 25, 38 | value: "Bar", 39 | }, 40 | equals: 29, 41 | value: Literal( 42 | Integer( 43 | LiteralInteger { 44 | comments: CommentGroup { 45 | comments: [], 46 | }, 47 | value: "2", 48 | position: 31, 49 | }, 50 | ), 51 | ), 52 | semicolon: 32, 53 | }, 54 | ), 55 | Case( 56 | BackedEnumCaseDefinition { 57 | attributes: [], 58 | case: Keyword { 59 | value: "case", 60 | position: 38, 61 | }, 62 | name: Identifier { 63 | position: 43, 64 | value: "Baz", 65 | }, 66 | equals: 47, 67 | value: Literal( 68 | Integer( 69 | LiteralInteger { 70 | comments: CommentGroup { 71 | comments: [], 72 | }, 73 | value: "4", 74 | position: 49, 75 | }, 76 | ), 77 | ), 78 | semicolon: 50, 79 | }, 80 | ), 81 | ], 82 | right_brace: 52, 83 | }, 84 | }, 85 | ), 86 | ), 87 | ], 88 | eof: 55, 89 | } -------------------------------------------------------------------------------- /tests/samples/0034/code.ara: -------------------------------------------------------------------------------- 1 | enum Foo { 2 | case Bar; 3 | case Baz; 4 | } 5 | 6 | -------------------------------------------------------------------------------- /tests/samples/0034/tree.txt: -------------------------------------------------------------------------------- 1 | DefinitionTree { 2 | definitions: [ 3 | Enum( 4 | Unit( 5 | UnitEnumDefinition { 6 | comments: CommentGroup { 7 | comments: [], 8 | }, 9 | attributes: [], 10 | enum: Keyword { 11 | value: "enum", 12 | position: 0, 13 | }, 14 | name: Identifier { 15 | position: 5, 16 | value: "Foo", 17 | }, 18 | implements: None, 19 | body: UnitEnumBodyDefinition { 20 | left_brace: 9, 21 | members: [ 22 | Case( 23 | UnitEnumCaseDefinition { 24 | attributes: [], 25 | case: Keyword { 26 | value: "case", 27 | position: 15, 28 | }, 29 | name: Identifier { 30 | position: 20, 31 | value: "Bar", 32 | }, 33 | semicolon: 23, 34 | }, 35 | ), 36 | Case( 37 | UnitEnumCaseDefinition { 38 | attributes: [], 39 | case: Keyword { 40 | value: "case", 41 | position: 29, 42 | }, 43 | name: Identifier { 44 | position: 34, 45 | value: "Baz", 46 | }, 47 | semicolon: 37, 48 | }, 49 | ), 50 | ], 51 | right_brace: 39, 52 | }, 53 | }, 54 | ), 55 | ), 56 | ], 57 | eof: 42, 58 | } -------------------------------------------------------------------------------- /tests/samples/0035/code.ara: -------------------------------------------------------------------------------- 1 | enum Foo: string { 2 | case Bar = '3'; 3 | case Baz = 'g'; 4 | } 5 | -------------------------------------------------------------------------------- /tests/samples/0036/code.ara: -------------------------------------------------------------------------------- 1 | class foo { 2 | public function __construct( 3 | readonly public string $s = 'h', 4 | ) {} 5 | } 6 | -------------------------------------------------------------------------------- /tests/samples/0037/code.ara: -------------------------------------------------------------------------------- 1 | namespace A\B\C\D\E; 2 | 3 | function foo(string $s): self { 4 | exit(0); 5 | } 6 | -------------------------------------------------------------------------------- /tests/samples/0038/code.ara: -------------------------------------------------------------------------------- 1 | namespace A\B\C\D\E; 2 | 3 | function foo(string $s): static { 4 | exit(0); 5 | } 6 | -------------------------------------------------------------------------------- /tests/samples/0039/code.ara: -------------------------------------------------------------------------------- 1 | namespace A\B\C\D\E; 2 | 3 | function foo(string $s): parent { 4 | exit(0); 5 | } 6 | -------------------------------------------------------------------------------- /tests/samples/0040/code.ara: -------------------------------------------------------------------------------- 1 | namespace Foo\Bar; 2 | 3 | final class Baz { 4 | public function __construct( 5 | public readonly string $name = 'foo', 6 | ) {} 7 | } 8 | -------------------------------------------------------------------------------- /tests/samples/0041/code.ara: -------------------------------------------------------------------------------- 1 | namespace Foo\Bar; 2 | 3 | final class Baz { 4 | public readonly string $foo; 5 | } 6 | -------------------------------------------------------------------------------- /tests/samples/0042/code.ara: -------------------------------------------------------------------------------- 1 | function foo(string $a, string $b, string $c, string ...$d): void { 2 | foo($a, $b, $c, ...$d); 3 | } 4 | -------------------------------------------------------------------------------- /tests/samples/0043/code.ara: -------------------------------------------------------------------------------- 1 | interface A extends B, C { 2 | #[R] 3 | const u16 F = 344; 4 | 5 | #[R] 6 | public const u16 O = 344; 7 | 8 | #[R] 9 | #[P] 10 | final public const u16 R = 344, P = 214; 11 | } 12 | -------------------------------------------------------------------------------- /tests/samples/0043/error.txt: -------------------------------------------------------------------------------- 1 | error[P0011]: unexpected token `,`, expected `;` 2 | --> 0043/code.ara:10:35 3 | | 4 | 10 | final public const u16 R = 344, P = 214; 5 | | ^ 6 | 7 | error: failed to parse "0043/code.ara" due to the above issue(s) 8 | = summary: 1 error(s) 9 | 10 | -------------------------------------------------------------------------------- /tests/samples/0044/code.ara: -------------------------------------------------------------------------------- 1 | class A implements B, C {} 2 | -------------------------------------------------------------------------------- /tests/samples/0044/tree.txt: -------------------------------------------------------------------------------- 1 | DefinitionTree { 2 | definitions: [ 3 | Class( 4 | ClassDefinition { 5 | comments: CommentGroup { 6 | comments: [], 7 | }, 8 | attributes: [], 9 | modifiers: ModifierGroupDefinition { 10 | position: 0, 11 | modifiers: [], 12 | }, 13 | class: Keyword { 14 | value: "class", 15 | position: 0, 16 | }, 17 | name: Identifier { 18 | position: 6, 19 | value: "A", 20 | }, 21 | templates: None, 22 | extends: None, 23 | implements: Some( 24 | ClassDefinitionImplements { 25 | implements: Keyword { 26 | value: "implements", 27 | position: 8, 28 | }, 29 | interfaces: CommaSeparated { 30 | inner: [ 31 | TemplatedIdentifier { 32 | name: Identifier { 33 | position: 19, 34 | value: "B", 35 | }, 36 | templates: None, 37 | }, 38 | TemplatedIdentifier { 39 | name: Identifier { 40 | position: 22, 41 | value: "C", 42 | }, 43 | templates: None, 44 | }, 45 | ], 46 | commas: [ 47 | 20, 48 | ], 49 | }, 50 | }, 51 | ), 52 | body: ClassDefinitionBody { 53 | left_brace: 24, 54 | members: [], 55 | right_brace: 25, 56 | }, 57 | }, 58 | ), 59 | ], 60 | eof: 27, 61 | } -------------------------------------------------------------------------------- /tests/samples/0045/code.ara: -------------------------------------------------------------------------------- 1 | enum A implements B, C {} 2 | -------------------------------------------------------------------------------- /tests/samples/0045/tree.txt: -------------------------------------------------------------------------------- 1 | DefinitionTree { 2 | definitions: [ 3 | Enum( 4 | Unit( 5 | UnitEnumDefinition { 6 | comments: CommentGroup { 7 | comments: [], 8 | }, 9 | attributes: [], 10 | enum: Keyword { 11 | value: "enum", 12 | position: 0, 13 | }, 14 | name: Identifier { 15 | position: 5, 16 | value: "A", 17 | }, 18 | implements: Some( 19 | EnumImplementsDefinition { 20 | implements: Keyword { 21 | value: "implements", 22 | position: 7, 23 | }, 24 | interfaces: CommaSeparated { 25 | inner: [ 26 | TemplatedIdentifier { 27 | name: Identifier { 28 | position: 18, 29 | value: "B", 30 | }, 31 | templates: None, 32 | }, 33 | TemplatedIdentifier { 34 | name: Identifier { 35 | position: 21, 36 | value: "C", 37 | }, 38 | templates: None, 39 | }, 40 | ], 41 | commas: [ 42 | 19, 43 | ], 44 | }, 45 | }, 46 | ), 47 | body: UnitEnumBodyDefinition { 48 | left_brace: 23, 49 | members: [], 50 | right_brace: 24, 51 | }, 52 | }, 53 | ), 54 | ), 55 | ], 56 | eof: 26, 57 | } -------------------------------------------------------------------------------- /tests/samples/0046/code.ara: -------------------------------------------------------------------------------- 1 | interface foo { 2 | public function bar(): parent; 3 | } 4 | -------------------------------------------------------------------------------- /tests/samples/0046/tree.txt: -------------------------------------------------------------------------------- 1 | DefinitionTree { 2 | definitions: [ 3 | Interface( 4 | InterfaceDefinition { 5 | comments: CommentGroup { 6 | comments: [], 7 | }, 8 | attributes: [], 9 | interface: Keyword { 10 | value: "interface", 11 | position: 0, 12 | }, 13 | name: Identifier { 14 | position: 10, 15 | value: "foo", 16 | }, 17 | templates: None, 18 | extends: None, 19 | body: InterfaceDefinitionBody { 20 | left_brace: 14, 21 | members: [ 22 | Method( 23 | MethodDefinition { 24 | comments: CommentGroup { 25 | comments: [], 26 | }, 27 | attributes: [], 28 | modifiers: ModifierGroupDefinition { 29 | position: 27, 30 | modifiers: [ 31 | Public( 32 | Keyword { 33 | value: "public", 34 | position: 20, 35 | }, 36 | ), 37 | ], 38 | }, 39 | function: Keyword { 40 | value: "function", 41 | position: 27, 42 | }, 43 | name: Identifier { 44 | position: 36, 45 | value: "bar", 46 | }, 47 | templates: None, 48 | parameters: MethodParameterListDefinition { 49 | comments: CommentGroup { 50 | comments: [], 51 | }, 52 | left_parenthesis: 39, 53 | parameters: CommaSeparated { 54 | inner: [], 55 | commas: [], 56 | }, 57 | right_parenthesis: 40, 58 | }, 59 | return_type: Some( 60 | FunctionLikeReturnTypeDefinition { 61 | colon: 41, 62 | type_definition: Identifier( 63 | TemplatedIdentifier { 64 | name: Identifier { 65 | position: 43, 66 | value: "parent", 67 | }, 68 | templates: None, 69 | }, 70 | ), 71 | }, 72 | ), 73 | constraints: None, 74 | body: Abstract( 75 | 49, 76 | ), 77 | }, 78 | ), 79 | ], 80 | right_brace: 51, 81 | }, 82 | }, 83 | ), 84 | ], 85 | eof: 53, 86 | } -------------------------------------------------------------------------------- /tests/samples/0047/code.ara: -------------------------------------------------------------------------------- 1 | interface s {} 2 | 3 | interface foo extends s { 4 | public function bar(): parent; 5 | } 6 | -------------------------------------------------------------------------------- /tests/samples/0048/code.ara: -------------------------------------------------------------------------------- 1 | class foo { 2 | public function bar(): parent { 3 | exit(1); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /tests/samples/0049/code.ara: -------------------------------------------------------------------------------- 1 | class s {} 2 | 3 | class foo extends s { 4 | public function bar(): parent { 5 | return new s(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/samples/0050/code.ara: -------------------------------------------------------------------------------- 1 | enum foo { 2 | public function bar(): parent { 3 | exit(1); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /tests/samples/0051/code.ara: -------------------------------------------------------------------------------- 1 | interface A {} 2 | interface B {} 3 | interface C {} 4 | interface D {} 5 | 6 | function foo(A|(B&C&D) $a): A&(B|C|D) { 7 | exit(0); 8 | } 9 | -------------------------------------------------------------------------------- /tests/samples/0052/code.ara: -------------------------------------------------------------------------------- 1 | function foo(): void { 2 | for ($i = 0, $i = 1;;) { 3 | 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /tests/samples/0053/code.ara: -------------------------------------------------------------------------------- 1 | function foo(): void { 2 | for ($i = 0, $i = 1; $j < 2, $j--;) { 3 | 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /tests/samples/0054/code.ara: -------------------------------------------------------------------------------- 1 | 2 | function foo(): void { 3 | if ($foo) { 4 | 5 | } else if ($bar) { 6 | 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tests/samples/0055/code.ara: -------------------------------------------------------------------------------- 1 | class Foo { 2 | const u8 foo = 1; 3 | 4 | public static function foo(): void { 5 | $a = static::foo; 6 | 7 | static::foo(); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/samples/0056/code.ara: -------------------------------------------------------------------------------- 1 | function true(): void {} 2 | function false(): void {} 3 | function null(): void {} 4 | function readonly(): void {} 5 | function self(): void {} 6 | function parent(): void {} 7 | function enum(): void {} 8 | function from(): void {} 9 | 10 | function main(): void { 11 | true(); 12 | false(); 13 | null(); 14 | readonly(); 15 | self(); 16 | parent(); 17 | enum(); 18 | from(); 19 | } 20 | -------------------------------------------------------------------------------- /tests/samples/0057/code.ara: -------------------------------------------------------------------------------- 1 | #[A, B] 2 | #[C, D] 3 | interface A extends B, C { 4 | #[R] 5 | const int F = 344; 6 | 7 | #[R] 8 | public const int O = 344; 9 | 10 | #[R] 11 | #[P] 12 | final public const int R = 344; 13 | 14 | #[R] 15 | #[P] 16 | final const int M = 34; 17 | 18 | #[M] 19 | public function bar(): void; 20 | 21 | #[Q] 22 | #[S] 23 | public static function baz(): void; 24 | } 25 | -------------------------------------------------------------------------------- /tests/samples/0058/code.ara: -------------------------------------------------------------------------------- 1 | #[foo(self::class), bar(new self(), new parent(), new static())] 2 | class a { 3 | 4 | } 5 | -------------------------------------------------------------------------------- /tests/samples/0059/code.ara: -------------------------------------------------------------------------------- 1 | const null a = \null; -------------------------------------------------------------------------------- /tests/samples/0059/tree.txt: -------------------------------------------------------------------------------- 1 | DefinitionTree { 2 | definitions: [ 3 | Constant( 4 | ConstantDefinition { 5 | comments: CommentGroup { 6 | comments: [], 7 | }, 8 | const: Keyword { 9 | value: "const", 10 | position: 0, 11 | }, 12 | type_definition: Literal( 13 | Null( 14 | LiteralNull { 15 | comments: CommentGroup { 16 | comments: [], 17 | }, 18 | null: Keyword { 19 | value: "null", 20 | position: 6, 21 | }, 22 | }, 23 | ), 24 | ), 25 | name: Identifier { 26 | position: 11, 27 | value: "a", 28 | }, 29 | equals: 13, 30 | value: Identifier( 31 | Identifier { 32 | position: 15, 33 | value: "\null", 34 | }, 35 | ), 36 | semicolon: 20, 37 | }, 38 | ), 39 | ], 40 | eof: 21, 41 | } -------------------------------------------------------------------------------- /tests/samples/0060/code.ara: -------------------------------------------------------------------------------- 1 | enum a: int { 2 | case static = 2413; 3 | case self = 2432; 4 | case return = 2433; 5 | case parent = 2543; 6 | case enum = 24133; 7 | case from = 25443; 8 | } 9 | 10 | class f { 11 | const u8 static = 1; 12 | const u16 self = 2; 13 | const u32 return = 3; 14 | const i8 parent = 4; 15 | const i16 enum = 5; 16 | const i32 from = 6; 17 | } 18 | 19 | const u64 from = 243; 20 | const i64 enum = 243; 21 | -------------------------------------------------------------------------------- /tests/samples/0061/code.ara: -------------------------------------------------------------------------------- 1 | function m(): void { 2 | $e = (fn (): \Generator => yield)(); 3 | } 4 | -------------------------------------------------------------------------------- /tests/samples/0062/code.ara: -------------------------------------------------------------------------------- 1 | interface enum extends enum, from {} 2 | 3 | class enum extends enum implements enum, from {} 4 | class from extends from implements Foo\enum, \Bar\Baz\from {} 5 | -------------------------------------------------------------------------------- /tests/samples/0063/code.ara: -------------------------------------------------------------------------------- 1 | function q(): void { 2 | foo(...); 3 | 4 | $foo->bar(...); 5 | 6 | Foo::bar(...); 7 | 8 | foo(1, 2); 9 | 10 | $foo->bar(1, 2); 11 | 12 | Foo::bar(1, 2); 13 | } 14 | -------------------------------------------------------------------------------- /tests/samples/0064/code.ara: -------------------------------------------------------------------------------- 1 | #[static(), self(), parent(), foo()] 2 | class a {} 3 | -------------------------------------------------------------------------------- /tests/samples/0065/code.ara: -------------------------------------------------------------------------------- 1 | function bar(): void { 2 | $a = [1, 2, 3]; 3 | } 4 | -------------------------------------------------------------------------------- /tests/samples/0065/error.txt: -------------------------------------------------------------------------------- 1 | error[P0011]: unexpected token `[`, expected an expression 2 | --> 0065/code.ara:2:10 3 | | 4 | 2 | $a = [1, 2, 3]; 5 | | ^ 6 | 7 | error: failed to parse "0065/code.ara" due to the above issue(s) 8 | = summary: 1 error(s) 9 | 10 | -------------------------------------------------------------------------------- /tests/samples/0066/code.ara: -------------------------------------------------------------------------------- 1 | function bar(): void { 2 | $a = vec[]; 3 | $a = vec[1, 2, 3]; 4 | } 5 | -------------------------------------------------------------------------------- /tests/samples/0067/code.ara: -------------------------------------------------------------------------------- 1 | function bar(): void { 2 | $a = []; 3 | $a = [1 => 2, 2 => 3, 3 => 4]; 4 | } 5 | -------------------------------------------------------------------------------- /tests/samples/0067/error.txt: -------------------------------------------------------------------------------- 1 | error[P0011]: unexpected token `[`, expected an expression 2 | --> 0067/code.ara:2:10 3 | | 4 | 2 | $a = []; 5 | | ^ 6 | 7 | error: failed to parse "0067/code.ara" due to the above issue(s) 8 | = summary: 1 error(s) 9 | 10 | -------------------------------------------------------------------------------- /tests/samples/0067/parse-error.txt: -------------------------------------------------------------------------------- 1 | error[P0041]: unexpected token `[`, expected an expression 2 | --> 0067/code.ara:2:10 3 | | 4 | 2 | $a = []; 5 | | ^ unexpected token `[`, expected an expression 6 | 7 | -------------------------------------------------------------------------------- /tests/samples/0068/code.ara: -------------------------------------------------------------------------------- 1 | function bar(): void { 2 | $a = []; 3 | $a = dict[1 => 2, 2 => 3, 3 => 4]; 4 | } 5 | -------------------------------------------------------------------------------- /tests/samples/0068/error.txt: -------------------------------------------------------------------------------- 1 | error[P0011]: unexpected token `[`, expected an expression 2 | --> 0068/code.ara:2:10 3 | | 4 | 2 | $a = []; 5 | | ^ 6 | 7 | error: failed to parse "0068/code.ara" due to the above issue(s) 8 | = summary: 1 error(s) 9 | 10 | -------------------------------------------------------------------------------- /tests/samples/0068/parse-error.txt: -------------------------------------------------------------------------------- 1 | error[P0041]: unexpected token `[`, expected an expression 2 | --> 0068/code.ara:2:10 3 | | 4 | 2 | $a = []; 5 | | ^ unexpected token `[`, expected an expression 6 | 7 | -------------------------------------------------------------------------------- /tests/samples/0069/code.ara: -------------------------------------------------------------------------------- 1 | namespace Dict; 2 | 3 | use Psl\Dict as Dict; 4 | 5 | function bar(): void { 6 | $a = dict[1 => 2, 2 => 3, 3 => 4]; 7 | 8 | Dict\map($a, fn(int $x): int => $x + 1); 9 | } 10 | 11 | namespace Vec; 12 | 13 | use Psl\Vec as Vec; 14 | 15 | function bar(): void { 16 | $a = vec[1, 2, 3]; 17 | 18 | Vec\map($a, fn(int $x): int => $x + 1); 19 | } 20 | 21 | namespace True; 22 | namespace False; 23 | namespace Null; 24 | namespace Int; 25 | namespace Float; 26 | namespace String; 27 | namespace List; 28 | namespace Array; 29 | namespace Object; 30 | namespace Resource; 31 | namespace Mixed; 32 | namespace Num; 33 | namespace Scalar; 34 | namespace Callable; 35 | namespace Void; 36 | namespace Fn; 37 | namespace Iterable; 38 | namespace Never; 39 | 40 | namespace True\Foo\Bar; 41 | namespace False\Foo\Bar; 42 | namespace Null\Foo\Bar; 43 | namespace Int\Foo\Bar; 44 | namespace Float\Foo\Bar; 45 | namespace String\Foo\Bar; 46 | namespace List\Foo\Bar; 47 | namespace Array\Foo\Bar; 48 | namespace Object\Foo\Bar; 49 | namespace Resource\Foo\Bar; 50 | namespace Mixed\Foo\Bar; 51 | namespace Num\Foo\Bar; 52 | namespace Scalar\Foo\Bar; 53 | namespace Callable\Foo\Bar; 54 | namespace Void\Foo\Bar; 55 | namespace Iterable\Foo\Bar; 56 | namespace Never\Foo\Bar; 57 | 58 | namespace Foo\Bar\True; 59 | namespace Foo\Bar\False; 60 | namespace Foo\Bar\Null; 61 | namespace Foo\Bar\Int; 62 | namespace Foo\Bar\Float; 63 | namespace Foo\Bar\String; 64 | namespace Foo\Bar\List; 65 | namespace Foo\Bar\Array; 66 | namespace Foo\Bar\Object; 67 | namespace Foo\Bar\Resource; 68 | namespace Foo\Bar\Mixed; 69 | namespace Foo\Bar\Num; 70 | namespace Foo\Bar\Scalar; 71 | namespace Foo\Bar\Callable; 72 | namespace Foo\Bar\Void; 73 | namespace Foo\Bar\Iterable; 74 | namespace Foo\Bar\Never; 75 | -------------------------------------------------------------------------------- /tests/samples/0070/code.ara: -------------------------------------------------------------------------------- 1 | function bar(): void { 2 | if $a { } 3 | if ($a) {} 4 | 5 | if $a { } elseif $b { } else if $c { } else {} 6 | if ($a) { } elseif ($b) { } else if ($c) {} else {} 7 | 8 | if $a { } else {} 9 | if ($a) {} else {} 10 | 11 | while $a { } 12 | while ($a) {} 13 | 14 | do {} while $a; 15 | do {} while ($a); 16 | 17 | foreach $a as $b { } 18 | foreach ($a) as $b {} 19 | foreach ($a as $b) {} 20 | foreach $a as $b => $c { } 21 | foreach ($a) as $b => $c {} 22 | foreach ($a as $b => $c) {} 23 | foreach ($a as $b => $c) {} else {} 24 | 25 | for $a = 0; $a < 10; $a++ { } 26 | for ($a = 0; $a < 10; $a++) {} 27 | for ($a = 0); $a < 10; $a++ {} 28 | for ($a = 0), $i = 10; $a < 10; $a++, ($i--) {} 29 | 30 | for ;; { } 31 | for (;;) {} 32 | 33 | $a = match $a { 34 | 1 => 2, 35 | 3 => 4, 36 | default => 5, 37 | }; 38 | 39 | $a = match ($a) { 40 | 1 => 2, 41 | 3 => 4, 42 | default => 5, 43 | }; 44 | } 45 | -------------------------------------------------------------------------------- /tests/samples/0071/code.ara: -------------------------------------------------------------------------------- 1 | interface ObjectCollection<+T as object> extends IteratorAggregate {} 2 | 3 | function foo(): vec { 4 | return vec[1, 2, 3]; 5 | } 6 | 7 | function bar(): dict { 8 | return dict['a' => 1, 'b' => 2, 'c' => 3]; 9 | } 10 | 11 | function baz(bool $a): iterable { 12 | if $a { 13 | return vec[1, 2, 3]; 14 | } else { 15 | return dict[1 => 1, 2 => 2, 3 => 3]; 16 | } 17 | } 18 | 19 | final class Box { 20 | public function __construct(public T $value) {} 21 | } 22 | 23 | function box(): Box> { 24 | return new Box(vec[1, 2, 3]); 25 | } 26 | 27 | function unbox(Box> $box): vec { 28 | return $box->value; 29 | } 30 | 31 | function foo(bool $a, bool $b): vec|dict|Box> { 32 | if $a { 33 | return vec[1, 2, 3]; 34 | } else if $b { 35 | return dict['a' => 1, 'b' => 2, 'c' => 3]; 36 | } else { 37 | return new Box(vec[1, 2, 3]); 38 | } 39 | } 40 | 41 | function into_vec(iterable<_, Tv> $iterable): vec { 42 | $vec = vec[]; 43 | foreach $iterable as $value { 44 | $vec[] = $value; 45 | } 46 | 47 | return $vec; 48 | } 49 | 50 | function into_dict(iterable $iterable): dict { 51 | $dict = dict[]; 52 | foreach $iterable as $key => $value { 53 | $dict[$key] = $value; 54 | } 55 | 56 | return $dict; 57 | } 58 | 59 | function baz(): void { 60 | $dict = dict[1 => 1, 2 => 2, 3 => 3]; 61 | $vec = vec[1, 2, 3]; 62 | 63 | $dict2 = into_dict::($vec); 64 | $vec2 = into_vec::($dict); 65 | 66 | $a = vec[$dict1, $dict2]; 67 | $b = into_dict::>($a); 68 | 69 | $c = new Box::>>($b); 70 | } 71 | -------------------------------------------------------------------------------- /tests/samples/0072/code.ara: -------------------------------------------------------------------------------- 1 | function a( 2 | 3 | Closure<(T), U>|dict|vec|(Foo&Bar>) $x, 4 | 5 | ): dict {} 6 | -------------------------------------------------------------------------------- /tests/samples/0073/code.ara: -------------------------------------------------------------------------------- 1 | type Predicate = Closure<(T), bool>; 2 | 3 | function filter(vec $vec, Predicate $predicate): vec { 4 | $result = vec[]; 5 | foreach $vec as $item { 6 | if $predicate($item) { 7 | $result[] = $item; 8 | } 9 | } 10 | 11 | return $result; 12 | } 13 | 14 | type Mapper = Closure<(K, V), U>|SomeOtherType; 15 | 16 | function map(dict $dict, Mapper $mapper): dict { 17 | $result = dict[]; 18 | if $mapper is SomeOtherType<_, _, _> { 19 | foreach $dict as $key => $value { 20 | $result[$key] = $mapper->doMap($key, $value); 21 | } 22 | } else { 23 | foreach $dict as $key => $value { 24 | $result[$key] = $mapper($key, $value); 25 | } 26 | } 27 | 28 | return $result; 29 | } 30 | -------------------------------------------------------------------------------- /tests/samples/0074/code.ara: -------------------------------------------------------------------------------- 1 | 2 | type scalar = string | int | float | bool; 3 | 4 | function scalar_to_string(scalar $value): string { 5 | if $value is string { 6 | $value 7 | } else if $value is int || $value is float { 8 | if $value is int { 9 | Str\format("%d", $value) 10 | } else { 11 | Str\format("%.2f", $value) 12 | } 13 | } 14 | 15 | $value ? "true" : "false" 16 | } 17 | 18 | function is_scalar(mixed $value): bool { 19 | $value is scalar 20 | } 21 | 22 | function as_scalar(mixed $value): scalar { 23 | $value as scalar 24 | } 25 | 26 | -------------------------------------------------------------------------------- /tests/samples/0075/code.ara: -------------------------------------------------------------------------------- 1 | type num = int|float; 2 | 3 | final readonly class Collection { 4 | public function __construct( 5 | private vec $items = vec[], 6 | ) {} 7 | 8 | public function filter(Closure<(T), bool> $func): Collection { 9 | $result = vec[]; 10 | foreach $this->items as $item { 11 | if $func($item) { 12 | $result[] = $item; 13 | } 14 | } 15 | 16 | return new Collection::($result); 17 | } 18 | 19 | public function map(Closure<(T), Tout> $func): Collection { 20 | $result = vec[]; 21 | foreach $this->items as $item { 22 | $result[] = $func($item); 23 | } 24 | 25 | return new Collection::($result); 26 | } 27 | 28 | public function sum(): int where T is num { 29 | $result = 0; 30 | foreach $this->items as $item { 31 | $result += $item; 32 | } 33 | 34 | $result 35 | } 36 | } 37 | 38 | function example(): int { 39 | $collection = new Collection::(vec[ 40 | 1, 2, 3.0, 4.0, 5, 6, 7.0, 9.0, 10 41 | ]); 42 | 43 | $collection 44 | ->filter(fn(num $n): bool => $n < 8) 45 | ->map::( 46 | fn(num $n): float => $n is float ? $n : $n + 0.0 47 | ) 48 | ->sum() 49 | } 50 | -------------------------------------------------------------------------------- /tests/samples/0076/code.ara: -------------------------------------------------------------------------------- 1 | namespace where; 2 | 3 | const u8 where = 1; 4 | interface where {} 5 | class where { 6 | public const u8 where = 1; 7 | 8 | public function where(): void {} 9 | } 10 | 11 | function where(): where { 12 | $where = where; 13 | $where = new where(); 14 | $where = where::where; 15 | $where = where::where(); 16 | $where = where::where::where; 17 | $where = where::where::where(); 18 | $where = $where->where(); 19 | 20 | $where 21 | } 22 | 23 | function where(): int { 24 | where 25 | } 26 | 27 | namespace type; 28 | 29 | const u8 type = 1; 30 | interface type {} 31 | class type { 32 | public const u8 type = 1; 33 | 34 | public function type(): void {} 35 | } 36 | 37 | function type(): type { 38 | $type = type; 39 | $type = new type(); 40 | $type = type::type; 41 | $type = type::type(); 42 | $type = type::type::type; 43 | $type = type::type::type(); 44 | $type = $type->type(); 45 | 46 | $type 47 | } 48 | 49 | function type(): int { 50 | type 51 | } 52 | -------------------------------------------------------------------------------- /tests/samples/0077/code.ara: -------------------------------------------------------------------------------- 1 | 0077/code.ara:1:1 3 | | 4 | 1 | 0077/code.ara:1:1 3 | | 4 | 1 | = Closure<(T), U>; 4 | 5 | type reducer = Closure<(T, U), U>; 6 | 7 | type filter = Closure<(T), bool>; 8 | 9 | function vec_filter( 10 | vec $vec, 11 | filter $filter 12 | ): vec { 13 | $result = vec[]; 14 | foreach $vec as $item { 15 | if $filter($item) { 16 | $result[] = $item; 17 | } 18 | } 19 | 20 | return $result; 21 | } 22 | 23 | function vec_map( 24 | vec $vec, 25 | mapper $mapper 26 | ): (vec, vec) { 27 | $result = vec[]; 28 | foreach $vec as $item { 29 | $result[] = $mapper($item); 30 | } 31 | 32 | ($vec, $result) 33 | } 34 | 35 | function dict_filter( 36 | dict $dict, 37 | filter $filter 38 | ): dict { 39 | $result = dict[]; 40 | foreach $dict as $key => $item { 41 | if $filter($item) { 42 | $result[$key] = $item; 43 | } 44 | } 45 | 46 | return $result; 47 | } 48 | 49 | function dict_map( 50 | dict $dict, 51 | mapper $mapper 52 | ): (dict, dict) { 53 | $result = dict[]; 54 | foreach $dict as $key => $item { 55 | $result[$key] = $mapper($item); 56 | } 57 | 58 | ($dict, $result) 59 | } 60 | 61 | function iterable_map_to_dict( 62 | iterable $iterable, 63 | mapper $mapper 64 | ): (iterable, dict,) { 65 | $result = dict[]; 66 | foreach $iterable as $key => $item { 67 | $result[$key] = $mapper($item); 68 | } 69 | 70 | ($iterable, $result) 71 | } 72 | 73 | function iterable_filter_to_dict( 74 | iterable $iterable, 75 | filter $filter 76 | ): dict { 77 | $result = dict[]; 78 | foreach $iterable as $key => $item { 79 | if $filter($item) { 80 | $result[$key] = $item; 81 | } 82 | } 83 | 84 | return $result; 85 | } 86 | -------------------------------------------------------------------------------- /tests/samples/0079/code.ara: -------------------------------------------------------------------------------- 1 | 2 | function foo(): object { 3 | return new class() implements Foo, Bar {}; 4 | } 5 | -------------------------------------------------------------------------------- /tests/samples/0080/code.ara: -------------------------------------------------------------------------------- 1 | 2 | type a = null; 3 | type b = false; 4 | type c = true; 5 | type d = nonnull; 6 | type e = a|b|c|d; 7 | type f = Closure<(a|b), c|d>; 8 | type g = vec; 9 | type h = Foo|Bar; 10 | type i = Closure<(a|b), g>|Closure<(a|b), h>|Closure<(a|b), a|b|c>|(Foo&Bar); 11 | type j = Closure<(a|b), i>; 12 | type k = dict>; 13 | type l = "bar"; 14 | type m = 1; 15 | type n = 1.5; 16 | type o = 1 | 1.5 | 2 | "foo" | 2.5 | "bar"; 17 | -------------------------------------------------------------------------------- /tests/samples/0081/code.ara: -------------------------------------------------------------------------------- 1 | function foo(): void { 2 | $a = async bar(); 3 | 4 | $b = await $a; 5 | 6 | $c = async baz(); 7 | $d = async qux(); 8 | 9 | ($e, $f) = concurrently { 10 | await $c, 11 | await $d, 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /tests/samples/0082/code.ara: -------------------------------------------------------------------------------- 1 | type num = int|float; 2 | 3 | final readonly class Collection { 4 | public function __construct( 5 | private vec $items = vec[], 6 | ) {} 7 | 8 | public function filter(Closure<(T), bool> $func): Collection { 9 | $result = vec[]; 10 | foreach $this->items as $item { 11 | if $func($item) { 12 | $result[] = $item; 13 | } 14 | } 15 | 16 | return new Collection::($result); 17 | } 18 | 19 | public function map(Closure<(T), Tout> $func): Collection { 20 | $result = vec[]; 21 | foreach $this->items as $item { 22 | $result[] = $func($item); 23 | } 24 | 25 | return new Collection::($result); 26 | } 27 | 28 | public function sum(): int where T is num { 29 | $result = 0; 30 | foreach $this->items as $item { 31 | $result += $item; 32 | } 33 | 34 | $result 35 | } 36 | } 37 | 38 | function example(): int { 39 | $collection = new Collection::(vec[ 40 | 1, 2, 3.0, 4.0, 5, 6, 7.0, 9.0, 10 41 | ]); 42 | 43 | $collection 44 | ->filter(fn(num $n): bool => $n < 8) 45 | ->map::( 46 | static fn(num $n): float => $n is float ? $n : $n + 0.0 47 | ) 48 | ->sum() 49 | } -------------------------------------------------------------------------------- /tests/samples/0083/code.ara: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/samples/0083/error.txt: -------------------------------------------------------------------------------- 1 | error[P0001]: PHP opening tag ` 0083/code.ara:1:1 3 | | 4 | 1 | 0083/code.ara:4:10 9 | | 10 | 3 | enum Foo: int { 11 | | --- 12 | 4 | case Bar; 13 | | ^^^^ 14 | 15 | error[P0003]: case `Bar::Baz` of unit enum `Bar` cannot have a value 16 | --> 0083/code.ara:10:10 17 | | 18 | 9 | enum Bar { 19 | | --- 20 | 10 | case Baz = 1; 21 | | ^^^^^^^^ 22 | 23 | error[P0011]: unexpected token `?>`, expected a definition 24 | --> 0083/code.ara:17:1 25 | | 26 | 17 | ?> 27 | | ^^ 28 | 29 | error: failed to parse "0083/code.ara" due to the above issue(s) 30 | = summary: 4 error(s) 31 | 32 | -------------------------------------------------------------------------------- /tests/samples/0084/code.ara: -------------------------------------------------------------------------------- 1 | use Async; 2 | use Await; 3 | use Concurrently; 4 | 5 | use function async; 6 | use function await; 7 | use function concurrently; 8 | 9 | class Async {} 10 | class Await {} 11 | class Concurrently {} 12 | 13 | // async, await, and concurrently operations. 14 | function async(): void { 15 | $a = async bar(); 16 | $b = await $a; 17 | ($c, $d) = concurrently { 18 | await baz(), 19 | await qux(), 20 | }; 21 | } 22 | 23 | // async, await, and concurrently function calls. 24 | function await(): void { 25 | $a = async(bar()); 26 | $b = await($a); 27 | ($c, $d) = concurrently( 28 | await(baz()), 29 | await(qux()), 30 | ); 31 | } 32 | 33 | // async, await, and concurrently function calls with generic type parameters. 34 | function concurrently(): void { 35 | $a = async::(bar()); 36 | $b = await::($a); 37 | ($c, $d) = concurrently::( 38 | await::(baz()), 39 | await::(qux()), 40 | ); 41 | } 42 | 43 | // async, await, and concurrently static method calls. 44 | function _(): void { 45 | Async::async(); 46 | Await::await(Async::async()); 47 | Concurrently::concurrently( 48 | Await::await(), 49 | Await::await(), 50 | ); 51 | } 52 | 53 | // async, await, and concurrently method calls. 54 | function _(): void { 55 | $async->async(); 56 | $await->await($async->async()); 57 | $concurrently->concurrently( 58 | $await->await(), 59 | $await->await(), 60 | ); 61 | } 62 | -------------------------------------------------------------------------------- /tests/samples/0086/code.ara: -------------------------------------------------------------------------------- 1 | const Foo FOO = new Foo(); 2 | 3 | const Bar BAR = function(): Bar { 4 | return new Bar(); 5 | }; 6 | 7 | const Baz BAZ = fn(): Baz => new Baz(); 8 | 9 | #[Foo(new Bar($bar), new Baz($baz), new Qux(function(): void {}))] 10 | function foo( 11 | Bar $a = new Bar($bar), 12 | Baz $b = new Baz($baz), 13 | Qux $c = new Qux(function(): void {}) 14 | ): void {} 15 | 16 | class Bar { 17 | const Foo FOO = new Foo(); 18 | 19 | const Bar BAR = function(): Bar { 20 | return new Bar(); 21 | }; 22 | 23 | const Baz BAZ = fn(): Baz => new Baz(); 24 | 25 | private mixed $a FOO = new Foo(); 26 | 27 | private mixed $b BAR = function(): Bar { 28 | return new Bar(); 29 | }; 30 | 31 | private mixed $c BAZ = fn(): Baz => new Baz(); 32 | 33 | public function __construct( 34 | Bar $a = new Bar($bar), 35 | Baz $b = new Baz($baz), 36 | Qux $c = new Qux(function(): void {}) 37 | ) {} 38 | 39 | public function foo( 40 | Bar $a = new Bar($bar), 41 | Baz $b = new Baz($baz), 42 | Qux $c = new Qux(function(): void {}) 43 | ): void {} 44 | } 45 | -------------------------------------------------------------------------------- /tests/samples/0086/error.txt: -------------------------------------------------------------------------------- 1 | error[P0011]: unexpected identifier, expected `;` 2 | --> 0086/code.ara:25:22 3 | | 4 | 25 | private mixed $a FOO = new Foo(); 5 | | ^^^ 6 | 7 | error: failed to parse "0086/code.ara" due to the above issue(s) 8 | = summary: 1 error(s) 9 | 10 | -------------------------------------------------------------------------------- /tests/samples/0087/code.ara: -------------------------------------------------------------------------------- 1 | interface IFoo { 2 | public function __construct(string $foo); 3 | 4 | public function doSomething(): void; 5 | } 6 | 7 | final class FooImpl implements IFoo { 8 | public function __construct(string $foo) { 9 | // ... 10 | } 11 | 12 | public function doSomething(): void { 13 | // ... 14 | } 15 | } 16 | 17 | function instantiate(class $class, string $foo): T { 18 | return new $class($foo); 19 | } 20 | 21 | function test(): void { 22 | $foo = instantiate::(FooImpl::class, 'bar'); 23 | 24 | $foo->doSomething(); 25 | } 26 | -------------------------------------------------------------------------------- /tests/samples/0088/code.ara: -------------------------------------------------------------------------------- 1 | function test(): void { 2 | $a = vec[1, 2, 3]; 3 | 4 | unset $a[0]; 5 | unset ($a[0]); 6 | unset ($a[0], $a[0]); 7 | unset ($a[0], $a[0], $a[0]); 8 | 9 | isset $a[0]; 10 | isset ($a[0]); 11 | isset ($a[0], $a[0]); 12 | isset ($a[0], $a[0], $a[0]); 13 | 14 | $a = dict['a' => 1, 'b' => 2, 'c' => 3]; 15 | 16 | unset $a['a']; 17 | unset ($a['a']); 18 | unset ($a['a'], $a['a']); 19 | unset ($a['a'], $a['a'], $a['a']); 20 | 21 | isset $a['a']; 22 | isset ($a['a']); 23 | isset ($a['a'], $a['a']); 24 | isset ($a['a'], $a['a'], $a['a']); 25 | } 26 | -------------------------------------------------------------------------------- /tests/samples/0089/code.ara: -------------------------------------------------------------------------------- 1 | namespace in; 2 | 3 | const u8 in = 1; 4 | interface in {} 5 | class in { 6 | public const u8 in = 1; 7 | 8 | public function in(): void {} 9 | } 10 | 11 | function in(): in { 12 | $in = in; 13 | $in = new in(); 14 | $in = in::in; 15 | $in = in::in(); 16 | $in = in::in::in; 17 | $in = in::in::in(); 18 | $in = $in->in(); 19 | 20 | $in 21 | } 22 | 23 | function in(): in { 24 | in 25 | } 26 | -------------------------------------------------------------------------------- /tests/samples/0090/code.ara: -------------------------------------------------------------------------------- 1 | function foo(): void { 2 | if $a in $b { 3 | 4 | } 5 | 6 | if $a in ($b, $c) { 7 | 8 | } 9 | 10 | if $a in vec[$b, $b] { 11 | 12 | } 13 | 14 | if $a in dict['a' => $b, 'b' => $c] { 15 | 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/samples/0091/code.ara: -------------------------------------------------------------------------------- 1 | function foo(): vec { 2 | $a = $b as string; 3 | $a = $b into string; 4 | $a = $b as int; 5 | $a = $b into int; 6 | $a = $b as float; 7 | $a = $b into float; 8 | $a = $b as bool; 9 | $a = $b into bool; 10 | $a = $b as vec; 11 | $a = $b into vec; 12 | $a = $b as vec|dict|(string, int); 13 | $a = $b into vec|dict|(string, int); 14 | } 15 | -------------------------------------------------------------------------------- /tests/samples/0092/code.ara: -------------------------------------------------------------------------------- 1 | type test = (A|B|C, A|(B&C), (B&C)|A); 2 | type test = (A, A|(B), (B)|A); 3 | type test = (A, A, (B)|A); 4 | type test = (A, A, A); 5 | type test = (A&B&C, A&(B|C), (B|C)&A); 6 | type test = (A, A&(B), (B)&A); 7 | type test = (A, A, (B)&A); 8 | type test = (A, A, A); 9 | type test = (A|B|C, A|(B&C), (B&C)|A); 10 | type test = (A, A|(B), (B)|A); 11 | type test = (A, A, (B)|A); 12 | type test = (A, A, A); 13 | type test = (A&B&C, A&(B|C), (B|C)&A); 14 | type test = (A, A&(B), (B)&A); 15 | type test = (A, A, (B)&A); 16 | type test = (A, A, A); 17 | -------------------------------------------------------------------------------- /tests/samples/0093/code.ara: -------------------------------------------------------------------------------- 1 | namespace using; 2 | 3 | const u8 using = 1; 4 | interface using {} 5 | class using { 6 | public const u8 using = 1; 7 | 8 | public function using(): void {} 9 | } 10 | 11 | function using(): using { 12 | $using = using; 13 | $using = new using(); 14 | $using = using::using; 15 | $using = using::using(); 16 | $using = using::using::using; 17 | $using = using::using::using(); 18 | $using = $using->using(); 19 | 20 | $using 21 | } 22 | 23 | function using(): using { 24 | using 25 | } 26 | -------------------------------------------------------------------------------- /tests/samples/0094/code.ara: -------------------------------------------------------------------------------- 1 | function example(): void { 2 | using $file = File\open_read_only("example.txt") { 3 | // $file is present here 4 | } 5 | 6 | // $file is destroyed here 7 | 8 | using $user = Users\by_id($user_id), $articles = Articles\by_user($user_id) { 9 | // $user and $articles are present here 10 | } 11 | 12 | // $user and $articles are destroyed here 13 | 14 | using $user = Users\by_id($id) if $user is nonnull { 15 | // $user is present here 16 | } 17 | 18 | // $user is destroyed here 19 | } 20 | -------------------------------------------------------------------------------- /tests/samples/0095/code.ara: -------------------------------------------------------------------------------- 1 | function example(): void { 2 | $a = 1..; 3 | $b = 1..10; 4 | $c = 1..=10; 5 | $d = ..10; 6 | $e = ..=10; 7 | $f = ..; 8 | } 9 | -------------------------------------------------------------------------------- /tests/samples/0096/code.ara: -------------------------------------------------------------------------------- 1 | function example(): void { 2 | if $a in 1.. {} 3 | if $a in 1..10 {} 4 | if $a in 1..=10 {} 5 | if $a in ..10 {} 6 | if $a in ..=10 {} 7 | if $a in .. {} 8 | } 9 | -------------------------------------------------------------------------------- /tests/samples/0097/code.ara: -------------------------------------------------------------------------------- 1 | function example(): void { 2 | foo(1..); 3 | foo(1..10); 4 | foo(1..=10); 5 | foo(..10); 6 | foo(..=10); 7 | foo(..); 8 | } 9 | -------------------------------------------------------------------------------- /tests/samples/0098/code.ara: -------------------------------------------------------------------------------- 1 | function example(): void { 2 | if $x { 1.. } 3 | if $x { 1..10 } 4 | if $x { 1..=10 } 5 | if $x { ..10 } 6 | if $x { ..=10 } 7 | if $x { .. } 8 | } 9 | -------------------------------------------------------------------------------- /tests/samples/0099/code.ara: -------------------------------------------------------------------------------- 1 | function example(): void { 2 | $x = vec[1..]; 3 | $x = vec[1..10]; 4 | $x = vec[1..=10]; 5 | $x = vec[..10]; 6 | $x = vec[..=10]; 7 | $x = vec[..]; 8 | } 9 | -------------------------------------------------------------------------------- /tests/samples/0100/code.ara: -------------------------------------------------------------------------------- 1 | function example(): void { 2 | $x = vec[1.., $y]; 3 | $x = vec[1..10, $y]; 4 | $x = vec[1..=10, $y]; 5 | $x = vec[..10, $y]; 6 | $x = vec[..=10, $y]; 7 | $x = vec[.., $y]; 8 | } 9 | -------------------------------------------------------------------------------- /tests/samples/0101/code.ara: -------------------------------------------------------------------------------- 1 | function example(): void { 2 | foreach 1.. as $y { 3 | 4 | } 5 | 6 | foreach 1..10 as $y { 7 | 8 | } 9 | 10 | foreach 1..=10 as $y { 11 | 12 | } 13 | 14 | // this is a compile error, because the range does not have a start 15 | foreach ..10 as $y { 16 | 17 | } 18 | 19 | // this is a compile error, because the range does not have a start 20 | foreach ..=10 as $y { 21 | 22 | } 23 | 24 | // this is a compile error, because the range does not have a start 25 | foreach .. as $y { 26 | 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/samples/0102/code.ara: -------------------------------------------------------------------------------- 1 | function example(): void { 2 | using $foo = 1.. if $foo->contains($y) { 3 | 4 | } 5 | 6 | using $foo = 1..10 if $foo->contains($y) { 7 | 8 | } 9 | 10 | using $foo = 1..=10 if $foo->contains($y) { 11 | 12 | } 13 | 14 | // this is a compile error, because the range does not have a start 15 | using $foo = ..10 if $foo->contains($y) { 16 | 17 | } 18 | 19 | // this is a compile error, because the range does not have a start 20 | using $foo = ..=10 if $foo->contains($y) { 21 | 22 | } 23 | 24 | // this is a compile error, because the range does not have a start 25 | using $foo = .. if $foo->contains($y) { 26 | 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/samples/0103/code.ara: -------------------------------------------------------------------------------- 1 | function example(): void { 2 | foo( 3 | $a, 4 | ...$b, 5 | $c..., 6 | d: $d, 7 | ); 8 | } 9 | -------------------------------------------------------------------------------- /tests/samples/0104/code.ara: -------------------------------------------------------------------------------- 1 | 2 | #[Baz(...BAZ)] 3 | function foo( 4 | Bar $bar = new Bar(...BAZ), 5 | ): void {} 6 | 7 | #[Bar(...BAZ)] 8 | function qux( 9 | Bar $bar = new Bar(BAZ...), 10 | ): void {} 11 | -------------------------------------------------------------------------------- /tests/samples/0105/code.ara: -------------------------------------------------------------------------------- 1 | function foo(string $foo): Bar { 2 | ; 3 | } 4 | -------------------------------------------------------------------------------- /tests/samples/0105/tree.txt: -------------------------------------------------------------------------------- 1 | DefinitionTree { 2 | definitions: [ 3 | Function( 4 | FunctionDefinition { 5 | attributes: [], 6 | comments: CommentGroup { 7 | comments: [], 8 | }, 9 | modifiers: ModifierGroupDefinition { 10 | position: 0, 11 | modifiers: [], 12 | }, 13 | function: Keyword { 14 | value: "function", 15 | position: 0, 16 | }, 17 | name: Identifier { 18 | position: 9, 19 | value: "foo", 20 | }, 21 | templates: None, 22 | parameters: FunctionLikeParameterListDefinition { 23 | comments: CommentGroup { 24 | comments: [], 25 | }, 26 | left_parenthesis: 12, 27 | parameters: CommaSeparated { 28 | inner: [ 29 | FunctionLikeParameterDefinition { 30 | comments: CommentGroup { 31 | comments: [], 32 | }, 33 | attributes: [], 34 | type_definition: String( 35 | Keyword { 36 | value: "string", 37 | position: 13, 38 | }, 39 | ), 40 | ellipsis: None, 41 | variable: Variable { 42 | position: 20, 43 | name: "$foo", 44 | }, 45 | default: None, 46 | }, 47 | ], 48 | commas: [], 49 | }, 50 | right_parenthesis: 24, 51 | }, 52 | return_type: FunctionLikeReturnTypeDefinition { 53 | colon: 25, 54 | type_definition: Identifier( 55 | TemplatedIdentifier { 56 | name: Identifier { 57 | position: 27, 58 | value: "Bar", 59 | }, 60 | templates: None, 61 | }, 62 | ), 63 | }, 64 | body: BlockStatement { 65 | comments: CommentGroup { 66 | comments: [], 67 | }, 68 | left_brace: 31, 69 | statements: [ 70 | Empty( 71 | 35, 72 | ), 73 | ], 74 | right_brace: 37, 75 | }, 76 | }, 77 | ), 78 | ], 79 | eof: 39, 80 | } -------------------------------------------------------------------------------- /tests/samples/0106/code.ara: -------------------------------------------------------------------------------- 1 | function bar(): void { 2 | $c = match { 3 | $a == $b => 'a is equal to b', 4 | $a > $b => 'a is greater than b', 5 | $a < $b => 'a is less than b', 6 | default => 'a and b are not comparable', 7 | default => 'a and b are not comparable', // The analyzer should warn about this 8 | }; 9 | } 10 | -------------------------------------------------------------------------------- /tests/samples/0107/code.ara: -------------------------------------------------------------------------------- 1 | const Foo ONE = new Foo(); 2 | 3 | class Foo { 4 | const \Closure FOO = static fn(): int => 1; 5 | 6 | public static abstract Abstract readonly private static protected string $baz = 'baz'; 7 | 8 | public int $bar = fn(): Foo => new Foo(); 9 | 10 | public int $foo = $this->bar; 11 | } 12 | -------------------------------------------------------------------------------- /tests/samples/0108/code.ara: -------------------------------------------------------------------------------- 1 | type SignedInt = int | i128 | i64 | i32 | i16 | i8; 2 | type UnsignedInt = uint | u32 | u16 | u8; 3 | type FloatingPoint = float | f64 | f32; 4 | 5 | function foo(): int { return 9223372036854775807; } 6 | function foo(): i128 { return 170141183460469231731687303715884105727; } 7 | function foo(): i64 { return 9223372036854775807; } 8 | function foo(): i32 { return 2147483647; } 9 | function foo(): i16 { return 32767; } 10 | function foo(): i8 { return 127; } 11 | 12 | function bar(): uint { return 18446744073709551615; } 13 | function bar(): u32 { return 4294967295; } 14 | function bar(): u16 { return 65535; } 15 | function bar(): u8 { return 255; } 16 | 17 | function baz(): float { return 3.4028235e+38; } 18 | function baz(): f64 { return 1.7976931348623157e+308; } 19 | function baz(): f32 { return 3.4028235e+38; } 20 | -------------------------------------------------------------------------------- /tests/samples/0109/code.ara: -------------------------------------------------------------------------------- 1 | function pipe(): void { 2 | $f1 = $a1 |> $b1; 3 | 4 | $f2 = $a2 |> $b2 |> $c2; 5 | 6 | $f3 = $a3 |> $b3 |> $c3 |> $d3; 7 | 8 | $f4 = f1(...) |> f2(...) |> f3(...); 9 | } 10 | -------------------------------------------------------------------------------- /tests/samples/0110/code.ara: -------------------------------------------------------------------------------- 1 | async function baz(): string { 2 | return foo(); 3 | } 4 | 5 | final class Foo { 6 | async public function bar(): string { 7 | return bar(); 8 | } 9 | 10 | async public static function baz(): string { 11 | return baz(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tests/samples/0111/code.ara: -------------------------------------------------------------------------------- 1 | function foo(): void { 2 | $a = $(123); 3 | $b = $::($a); 4 | return $($b); 5 | } 6 | -------------------------------------------------------------------------------- /tests/samples/0112/code.ara: -------------------------------------------------------------------------------- 1 | function foo(): void { 2 | using { 3 | 4 | } 5 | 6 | using if $a { 7 | 8 | } 9 | 10 | using $a = foo() if $a { 11 | 12 | } 13 | 14 | using $a = foo() { 15 | 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/samples/0113/code.ara: -------------------------------------------------------------------------------- 1 | function foo(): void { 2 | if { 3 | 4 | } 5 | 6 | if $a, { 7 | 8 | } 9 | 10 | if $a, $b, $c, { 11 | 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tests/samples/0114/code.ara: -------------------------------------------------------------------------------- 1 | function foo(): void { 2 | while { 3 | 4 | } 5 | 6 | while $a, { 7 | 8 | } 9 | 10 | while $a, $b, $c, { 11 | 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tests/samples/0115/code.ara: -------------------------------------------------------------------------------- 1 | function foo(): void { 2 | do { 3 | 4 | } while; 5 | 6 | do { 7 | 8 | } while $a, ; 9 | 10 | do { 11 | 12 | } while $a, $b, $c, ; 13 | } 14 | -------------------------------------------------------------------------------- /tests/test.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::fs::read_dir; 3 | use std::io; 4 | use std::path::PathBuf; 5 | 6 | use pretty_assertions::assert_str_eq; 7 | 8 | use ara_parser::parser; 9 | use ara_reporting::builder::CharSet; 10 | use ara_reporting::builder::ColorChoice; 11 | use ara_reporting::builder::ReportBuilder; 12 | use ara_source::loader::FileSourceLoader; 13 | use ara_source::loader::SourceLoader; 14 | 15 | #[test] 16 | fn test_fixtures() -> io::Result<()> { 17 | let manifest = env::var("CARGO_MANIFEST_DIR").unwrap(); 18 | let root = format!("{manifest}/tests/samples/"); 19 | 20 | let mut entries = read_dir(&root)? 21 | .flatten() 22 | .map(|entry| entry.path()) 23 | .filter(|entry| entry.is_dir()) 24 | .collect::>(); 25 | 26 | entries.sort(); 27 | 28 | let loader = FileSourceLoader::new(&root); 29 | for entry in entries { 30 | let code_filename = entry.join("code.ara"); 31 | let tree_filename = entry.join("tree.txt"); 32 | let error_filename = entry.join("error.txt"); 33 | 34 | if !code_filename.exists() { 35 | continue; 36 | } 37 | 38 | let source_map = loader.load(&code_filename).unwrap(); 39 | match parser::parse(&source_map.sources[0]) { 40 | Ok(tree) => { 41 | let expected_tree = std::fs::read_to_string(&tree_filename)?; 42 | 43 | assert_str_eq!( 44 | expected_tree, 45 | format!("{:#?}", tree.definitions), 46 | "tree mismatch for sample `{}`", 47 | source_map.sources[0].name() 48 | ); 49 | 50 | assert!( 51 | !error_filename.exists(), 52 | "found `error.txt` for `{}` but was expected.", 53 | source_map.sources[0].name() 54 | ); 55 | } 56 | Err(report) => { 57 | let builder = ReportBuilder::new(&source_map) 58 | .with_charset(CharSet::Ascii) 59 | .with_colors(ColorChoice::Never); 60 | 61 | let error = builder.as_string(report.as_ref()).unwrap(); 62 | 63 | let expected_error = std::fs::read_to_string(&error_filename)?; 64 | 65 | assert_str_eq!( 66 | expected_error, 67 | format!("{error}"), 68 | "error mismatch for sample `{}`", 69 | source_map.sources[0].name() 70 | ); 71 | 72 | assert!( 73 | !tree_filename.exists(), 74 | "found `tree.txt` for `{}` but was expected.", 75 | source_map.sources[0].name() 76 | ); 77 | } 78 | } 79 | } 80 | 81 | Ok(()) 82 | } 83 | --------------------------------------------------------------------------------