├── .github └── workflows │ ├── main.yml │ └── release.yml ├── .gitignore ├── .rustfmt.toml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Cargo.toml ├── LICENSE.txt ├── NOTICE.txt ├── README.md ├── clippy.toml ├── codegen_scripts ├── README.md ├── cleangenerated.sh ├── cpgenerated.sh ├── cpnodes.sh ├── mk_nodes.sh ├── mk_syntax_kinds.sh ├── mk_syntax_tokens.sh └── mkgenerated.sh ├── crates ├── oq3_lexer │ ├── Cargo.toml │ └── src │ │ ├── cursor.rs │ │ ├── lib.rs │ │ ├── tests.rs │ │ ├── unescape.rs │ │ └── unescape │ │ └── tests.rs ├── oq3_parser │ ├── Cargo.toml │ └── src │ │ ├── event.rs │ │ ├── grammar.rs │ │ ├── grammar │ │ ├── expressions.rs │ │ ├── expressions │ │ │ └── atom.rs │ │ ├── items.rs │ │ └── params.rs │ │ ├── input.rs │ │ ├── lexed_str.rs │ │ ├── lib.rs │ │ ├── output.rs │ │ ├── parser.rs │ │ ├── shortcuts.rs │ │ ├── syntax_kind.rs │ │ ├── syntax_kind │ │ └── syntax_kind_enum.rs │ │ └── token_set.rs ├── oq3_semantics │ ├── .gitignore │ ├── Cargo.toml │ ├── examples │ │ └── semdemo.rs │ ├── src │ │ ├── asg.rs │ │ ├── context.rs │ │ ├── display.rs │ │ ├── initialize_ast.rs │ │ ├── lib.rs │ │ ├── semantic_error.rs │ │ ├── symbols.rs │ │ ├── syntax_to_semantics.rs │ │ ├── types.rs │ │ ├── utils.rs │ │ └── validate.rs │ └── tests │ │ ├── ast_tests.rs │ │ ├── from_string_tests.rs │ │ ├── spec.rs │ │ ├── symbol_tests.rs │ │ └── types_test.rs ├── oq3_source_file │ ├── Cargo.toml │ └── src │ │ ├── api.rs │ │ ├── lib.rs │ │ └── source_file.rs └── oq3_syntax │ ├── Cargo.toml │ ├── examples │ ├── demoparse.rs │ └── itemparse.rs │ ├── openqasm3.ungram │ └── src │ ├── ast.rs │ ├── ast │ ├── edit.rs │ ├── expr_ext.rs │ ├── generated.rs │ ├── generated │ │ ├── nodes.rs │ │ └── tokens.rs │ ├── make.rs │ ├── node_ext.rs │ ├── operators.rs │ ├── prec.rs │ ├── token_ext.rs │ ├── traits.rs │ └── type_ext.rs │ ├── lib.rs │ ├── parsing.rs │ ├── ptr.rs │ ├── sourcegen.rs │ ├── syntax_error.rs │ ├── syntax_node.rs │ ├── ted.rs │ ├── tests.rs │ ├── tests │ ├── ast_src.rs │ └── sourcegen_ast.rs │ ├── token_text.rs │ ├── validation.rs │ └── validation │ └── block.rs ├── dummy_triagebot.toml ├── local_CI.sh ├── run_tests.sh └── run_tests_less.sh /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Rust 3 | 4 | on: 5 | push: 6 | branches: [ "main" ] 7 | pull_request: 8 | branches: [ "main" ] 9 | 10 | env: 11 | CARGO_TERM_COLOR: always 12 | 13 | jobs: 14 | build: 15 | runs-on: ${{ matrix.os }} 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | os: [ubuntu-latest, macos-13, windows-latest] 20 | steps: 21 | - uses: actions/checkout@v3 22 | - name: Rust Format 23 | run: cargo fmt --all -- --check 24 | - name: Build 25 | run: cargo build --release --verbose 26 | - name: Clippy 27 | run: cargo clippy --all-targets -- -D warnings -D clippy::dbg_macro 28 | - name: Run tests 29 | run: cargo test --verbose -- --skip sourcegen_ast --skip sourcegen_ast_nodes 30 | 31 | msrv: 32 | runs-on: ${{ matrix.os }} 33 | strategy: 34 | fail-fast: false 35 | matrix: 36 | os: [ubuntu-latest, macos-13, windows-latest] 37 | steps: 38 | - uses: actions/checkout@v3 39 | - name: Install Rust toolchain 40 | uses: dtolnay/rust-toolchain@1.70 41 | - name: Build 42 | run: cargo build --release --verbose 43 | - name: Run tests 44 | run: cargo test --verbose --lib --tests -- --skip sourcegen_ast --skip sourcegen_ast_nodes 45 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Release openqasm3_parser 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | jobs: 9 | publish_crates: 10 | name: Publish openqasm3_parser crates 11 | runs-on: ubuntu-latest 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | crate: [oq3_lexer, oq3_parser, oq3_syntax, oq3_source_file, oq3_semantics] 16 | max-parallel: 1 17 | 18 | steps: 19 | - uses: actions/checkout@v4 20 | - uses: dtolnay/rust-toolchain@stable 21 | - name: Run cargo publish 22 | run: | 23 | cd crates/${{ matrix.crate }} 24 | cargo publish 25 | env: 26 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} 27 | - run: date 28 | - run: sleep 60 29 | - run: date 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | /Cargo.lock 3 | crates/oq3_parser/src/syntax_kind/_syntax_kind_enum.rs 4 | crates/oq3_syntax/src/ast/generated/_new_nodes.rs 5 | crates/oq3_syntax/src/ast/generated/_new_tokens.rs 6 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | # See https://rust-lang.github.io/rustfmt/?version=v1.6.0&search= 2 | 3 | # Copyright contributors to the openqasm-parser project 4 | 5 | # Use this when it becomes stable 6 | # reorder_impl_items = true 7 | 8 | # When use_small_heuristics is set to Max, then each granular width setting is set to the same value as max_width. 9 | # use_small_heuristics = "Max" 10 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | All members of this project agree to adhere to the Qiskit Code of Conduct as found 3 | at [https://github.com/Qiskit/qiskit/blob/master/CODE_OF_CONDUCT.md](https://github.com/Qiskit/qiskit/blob/master/CODE_OF_CONDUCT.md) 4 | 5 | ---- 6 | 7 | License: [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/), 8 | Copyright Contributors to Qiskit. 9 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Pull Requests 2 | 3 | ### Debugging / Developing 4 | 5 | Set the environment variable `QASM3_PATH` to the directory containing the QASM 3 example you are working on. 6 | Alternatively, in the examples below, you can use the fully qualified path to `example.qasm`. 7 | 8 | Use `semdemo` to see how the parser/analyzer handles your example in `example.qasm`. 9 | You will usually be concerned with two data structures, which we call the AST and the ASG. 10 | In order to test construction of the ASG, the AST must be constructed without errors. 11 | 12 | #### The AST 13 | 14 | Run 15 | ```shell 16 | cargo run --color always --example semdemo -- parse example.qasm 17 | ``` 18 | If parsing was successful, you should see something like 19 | ``` 20 | Found 3 stmts 21 | Found 0 parse errors: 22 | [] 23 | ``` 24 | followed by a representation of the AST. 25 | 26 | ### The ASG 27 | If running `semdemo` with the option `parse` as above prints no syntax errors, then 28 | you can proceed to the ASG. Run 29 | ```shell 30 | cargo run --color always --example semdemo -- semantic example.qasm 31 | ``` 32 | If there were in fact syntax errors in producing the AST, then this command will 33 | pretty-print those errors, but will not attempt to construct the ASG. 34 | Otherwise the ASG will be constructed, and any *semantic* errors found will be 35 | printed. 36 | 37 | ### Testing and Continuous Integration (CI) 38 | 39 | > [!IMPORTANT] 40 | > Don't run tests with `cargo test`. Rather use [./run_tests.sh](./run_tests.sh). 41 | > Or run the command contained therein: 42 | > 43 | > `cargo test --lib --tests -- --skip sourcegen_ast --skip sourcegen_ast_nodes` 44 | 45 | All pull requests must pass a CI check before being merged. You can check if CI will pass locally with 46 | ```shell 47 | cargo fmt --all -- --check && cargo build --verbose && cargo clippy -- -D warnings && cargo test --verbose -- --skip sourcegen_ast --skip sourcegen_ast_nodes 48 | ``` 49 | A script for checking CI locally is [./local_CI.sh](./local_CI.sh) 50 | 51 | ### Clippy 52 | 53 | One of the CI items is `cargo clippy`. 54 | To handle a lot of errors at the command line you can use (for unix-like OS) `cargo clippy --color always &| less -R`. 55 | 56 | ### Modifying the ungrammar 57 | 58 | An ["ungrammar"](https://docs.rs/ungrammar/latest/ungrammar/) (and [here](https://github.com/rust-analyzer/ungrammar)) describes a concrete syntax tree. 59 | 60 | An ungrammar for OpenQASM 3 is 61 | in [./crates/oq3_syntax/openqasm3.ungram](./crates/oq3_syntax/openqasm3.ungram). 62 | For most work, it need not be edited. 63 | If the file is modified, 64 | three source files must be regenerated: 65 | * [./crates/oq3_parser/src/syntax_kind/syntax_kind_enum.rs](./crates/oq3_parser/src/syntax_kind/syntax_kind_enum.rs) 66 | * [./crates/oq3_syntax/src/ast/generated/nodes.rs](./crates/oq3_syntax/src/ast/generated/nodes.rs) 67 | * [./crates/oq3_syntax/src/ast/generated/tokens.rs](./crates/oq3_syntax/src/ast/generated/tokens.rs) 68 | 69 | For further information, see [./codegen_scripts/README.md](./codegen_scripts/README.md) 70 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["crates/*"] 3 | resolver = "2" 4 | 5 | [workspace.package] 6 | version = "0.7.0" 7 | rust-version = "1.70" 8 | edition = "2021" 9 | license = "Apache-2.0" 10 | authors = ["OpenQASM3 parser team"] 11 | readme = "README.md" 12 | keywords = ["QASM", "openqasm3", "parser"] 13 | categories = ["parser-implementations"] 14 | repository = "https://github.com/Qiskit/openqasm3_parser" 15 | 16 | [workspace.dependencies] 17 | # local crates 18 | oq3_lexer = { path = "crates/oq3_lexer", version = "0.7.0" } 19 | oq3_parser = { path = "crates/oq3_parser", version = "0.7.0" } 20 | oq3_syntax = { path = "crates/oq3_syntax", version = "0.7.0" } 21 | oq3_semantics = { path = "crates/oq3_semantics", version = "0.7.0" } 22 | oq3_source_file = { path = "crates/oq3_source_file", version = "0.7.0" } 23 | -------------------------------------------------------------------------------- /NOTICE.txt: -------------------------------------------------------------------------------- 1 | The code in crates `oq3_lexer`, `oq3_parser`, and `oq3_syntax` are derived 2 | from crates in the rust-analyzer project, commit (d398a). 3 | The code in these crates is licensed in this OpenQASM 3 parser project under 4 | the following MIT license: 5 | 6 | Permission is hereby granted, free of charge, to any 7 | person obtaining a copy of this software and associated 8 | documentation files (the "Software"), to deal in the 9 | Software without restriction, including without 10 | limitation the rights to use, copy, modify, merge, 11 | publish, distribute, sublicense, and/or sell copies of 12 | the Software, and to permit persons to whom the Software 13 | is furnished to do so, subject to the following 14 | conditions: 15 | 16 | The above copyright notice and this permission notice 17 | shall be included in all copies or substantial portions 18 | of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 21 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 22 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 23 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 24 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 25 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 26 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 27 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 28 | DEALINGS IN THE SOFTWARE. 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenQASM 3 Parser 2 | [![License](https://img.shields.io/github/license/Qiskit/rustworkx.svg?style=popout-square)](https://opensource.org/licenses/Apache-2.0) 3 | [![Minimum rustc 1.70](https://img.shields.io/badge/rustc-1.70+-blue.svg)](https://rust-lang.github.io/rfcs/2495-min-rust-version.html) 4 | [![oq3_semantics crate](https://img.shields.io/crates/v/oq3_semantics.svg)](https://crates.io/crates/oq3_semantics) 5 | 6 | 7 | This project provides a compiler front end for OpenQASM 3 language (OQ3). 8 | 9 | In this document, this parser is referred to as `openqasm3_parser`. 10 | 11 | ### Why is this important? 12 | 13 | There is a lot of interest in using OpenQASM 3 for a number of purposes. For example, for controlling 14 | hardware and as an exchange medium for quantum circuits. This project aims to provide a performant, 15 | robust front end to compilers and importers. There are no other open source (or public) projects of this 16 | kind. 17 | 18 | Differences with the [OpenQASM reference parser](https://github.com/openqasm/openqasm) include 19 | 20 | * The parser in `openqasm3_parser` is much more performant. 21 | A crude test with large source files showed parse time reduced by a factor of 80. 22 | * `openqasm3_parser` performs semantic analysis. In particular diagnostics and error reporting 23 | are much better. 24 | 25 | ### Contributing 26 | 27 | If you are interested in contributing, browsing the issues is a good place to start. 28 | Some of the issues are [tagged with the label "Looking for assignee"](https://github.com/Qiskit/openqasm3_parser/issues?q=label%3A%22Looking+for+assignee%22+is%3Aopen+sort%3Aupdated-desc). 29 | Issues with the tag include a bit more context than some other issues in order to guide a newcomer. Also, the core developers are not 30 | planning on working on these issues in the immediate future. 31 | However, this does not mean you can't tackle an issue without this tag if it is a better fit. 32 | 33 | There are a few more tips in [CONTRIBUTING.md](./CONTRIBUTING.md), as well as some later in this README. 34 | 35 | ### Crates (roughly one installable library per crate) 36 | 37 | The first three crates are based on tools for `rust` and `rust-analyzer`. 38 | 39 | * [oq3_lexer](./crates/oq3_lexer) -- A lightly modified version of the `rustc` (the rust compiler) lexer. 40 | * [oq3_parser](./crates/oq3_parser) -- Ingests output of `oq3_lexer` and outputs a concrete syntax tree. 41 | * [oq3_syntax](./crates/oq3_syntax) -- Ingests output of `oq3_parser` and outputs an abstract syntax tree (AST). 42 | The rust-analyzer [documentation](#design) sometimes refers to this AST by something like "typed AST". 43 | This can be confusing. It does not mean that semantic 44 | analysis has been performed and OQ3 types have been assigned to all expressions. It means that the rust type system is 45 | used to encode syntactic elements, in contrast to some lower representations in the same crate. 46 | * [oq3_semantics](./crates/oq3_semantics) -- Performs [semantic analysis](https://en.wikipedia.org/wiki/Compiler#Front_end) 47 | and outputs an [abstract semantic graph (ASG)](https://en.wikipedia.org/wiki/Abstract_semantic_graph) 48 | There are other names for this structure. But "ASG" is convenient. 49 | * [oq3_source_file](./crates/oq3_source_file) -- A higher-level interface to the syntactic AST. This sits beetween the syntactic AST and 50 | semantic ASG. This crate manages the main source file and incuded source files. 51 | 52 |
53 | What is a rust "crate" 54 | 55 | We talk about rust ["crates"](https://doc.rust-lang.org/book/ch07-01-packages-and-crates.html). 56 | A rust library crate is more or less the source for a rust library that is developed, built, and installed with the rust package manager [cargo](https://doc.rust-lang.org/cargo/). 57 | This single repository contains more than one separately installable crates. In the future, this repository may also be used to generate other artifacts. 58 | 59 |
60 | 61 | 62 | ### Warning ! 63 | 64 | Do not run `cargo test`. Rather use `./run_tests.sh` or commands found therein. This is because codegen is implemented via 65 | the test system (you read correctly). If possible, we plan to change this to a more conventional approach. 66 | 67 | ### Using this front end 68 | 69 | A reminder: A front end is not of much use unless you have a back end. Examples showing the entry points and how to use them, 70 | can be found in [./crates/oq3_semantics/examples/semdemo.rs](./crates/oq3_semantics/examples/semdemo.rs). 71 | 72 | ```shell 73 | shell> export QASM3_PATH=./crates/semantics/examples/qasm/ 74 | shell> cargo run --example semdemo -- semantic scratch1.qasm 75 | ``` 76 | 77 | Replace `scratch1.qasm` with some file found in [./crates/oq3_semantics/examples/qasm/](./crates/oq3_semantics/examples/qasm/). 78 | 79 | #### Search path 80 | 81 | The environment variable `QASM_PATH` is a colon separated list of paths. Note that the name follows the venerable unix tradition of 82 | ending in `PATH` rather than `PATHS`. The code that retrives the paths uses routines `std::path` which may actually handle 83 | path specifications on other platforms. 84 | 85 | ### Design 86 | 87 | Code from rust-analyzer has been borrowed and modified for the lower levels of parsing. 88 | The [developer documents for rust-analyzer](https://github.com/rust-lang/rust-analyzer/tree/master/docs/dev) are very relevant as the 89 | structure has not been changed in adapting to OQ3. 90 | 91 | * [Syntax](https://github.com/rust-lang/rust-analyzer/blob/master/docs/dev/syntax.md) 92 | * [Red/Green trees](https://ericlippert.com/2012/06/08/red-green-trees/) taken from the C# parser. 93 | * Pratt parsing 94 | * [Simple but Powerful Pratt Parsing](https://matklad.github.io/2020/04/13/simple-but-powerful-pratt-parsing.html) 95 | * [From Pratt to Dijkstra](https://matklad.github.io/2020/04/15/from-pratt-to-dijkstra.html) 96 | * [Resilient parsing](https://matklad.github.io/2023/05/21/resilient-ll-parsing-tutorial.html) 97 | 98 | ## Notes 99 | 100 | Some of this code is modified from code found in [rust-analyzer](https://github.com/rust-lang/rust-analyzer). 101 | It was taken at [this commit](https://github.com/rust-lang/rust-analyzer/pull/15380): 102 | 103 | commit d398ad3326780598bbf1480014f4c59fbf6461a7 104 | Merge: 2f2cf21da 6990d0f26 105 | Author: bors 106 | Date: Wed Aug 2 14:28:41 2023 +0000 107 | 108 | Auto merge of #15380 - HKalbasi:mir, r=HKalbasi 109 | 110 | Fix unsized struct problems in mir eval 111 | 112 | 113 | 119 | -------------------------------------------------------------------------------- /clippy.toml: -------------------------------------------------------------------------------- 1 | # This does not work for some reason. 2 | # filter-map-identitiy = false 3 | 4 | msrv = "1.70.0" 5 | -------------------------------------------------------------------------------- /codegen_scripts/README.md: -------------------------------------------------------------------------------- 1 | ## Code generation for the crate oq3_syntax 2 | 3 | ### Do I need to run code generation? 4 | 5 | If you don't modify the "ungrammar" [./crates/oq3_syntax/openqasm3.ungram](./crates/oq3_syntax/openqasm3.ungram) 6 | then you don't need to generate any code to build or develop openqasm_parser, and you can stop reading here. 7 | 8 | If you *do* modify [./crates/oq3_syntax/openqasm3.ungram](./crates/oq3_syntax/openqasm3.ungram), then read on. 9 | 10 | NOTE: These (rather simple) scripts controlling codegen have been developed under fedora linux. They may need 11 | to be modified for your OS. The codegen itself is done in rust and should run correctly on most 12 | platforms, or should be easily made to do so. 13 | 14 | The source files in [../crates/oq3_syntax/src/ast/generated](../crates/oq3_syntax/src/ast/generated) are generated via scripts 15 | that are run during development. 16 | They do not need to be run when building the crates if the input files to the codegen have not been modified. 17 | 18 | On the other hand, if the input files to codegen have been modified, then codegen scripts 19 | must be run by calling [./mkgenerated](./mkgenerated). See comments in [./mkgenerated](./mkgenerated). 20 | 21 | Run ./mkgenerated from this directory to write generated rust source to temporary files. 22 | Trying to run ./mkgenerated from the root of this repo will fail to generate the required code. 23 | Run [./cpnodes.sh](./cpnodes.sh) and [./cpgenerated.sh](./cpgenerated.sh) to overwrite the revision-controlled source 24 | files with the temporary files. 25 | 26 | > [!IMPORTANT] 27 | > [./mkgenerated](./mkgenerated) runs [./mk_nodes.sh](./mk_nodes.sh) twice. The first time errors are reported such as: `test tests::sourcegen_ast::write_syntax_kinds_enum ... FAILED`. These errors can be ignored. The second time [./mk_nodes.sh](./mk_nodes.sh) runs no errors should be reported. 28 | -------------------------------------------------------------------------------- /codegen_scripts/cleangenerated.sh: -------------------------------------------------------------------------------- 1 | #!/bin/env sh 2 | 3 | # Copyright contributors to the openqasm-parser project 4 | # SPDX-License-Identifier: Apache-2.0 5 | 6 | # Remove backups of generated code. 7 | # Also remove the temporary file that generated code is written to. 8 | # The generated files that are actually in use, nodes.rs and generated.rs 9 | # are not touched by this script. 10 | 11 | cd .. 12 | rm crates/oq3_parser/src/syntax_kind/_syntax_kind_enum.rs 13 | rm crates/oq3_parser/src/syntax_kind/syntax_kind_enum.rs.~* 14 | 15 | rm crates/oq3_syntax/src/ast/generated/_new_nodes.rs 16 | rm crates/oq3_syntax/src/ast/generated/nodes.rs.~* 17 | 18 | # This one is not backed up or copied because it rarely 19 | # changes. 20 | rm crates/oq3_syntax/src/ast/generated/_new_tokens.rs 21 | -------------------------------------------------------------------------------- /codegen_scripts/cpgenerated.sh: -------------------------------------------------------------------------------- 1 | #!/bin/env sh 2 | 3 | # Copyright contributors to the openqasm-parser project 4 | # SPDX-License-Identifier: Apache-2.0 5 | 6 | # For generated.rs 7 | # Copy the generated code from the temporary files to which it is written 8 | # to it's final location where it will be compiled into the library. 9 | 10 | cd .. && cp -a --backup=t crates/oq3_parser/src/syntax_kind/_syntax_kind_enum.rs crates/oq3_parser/src/syntax_kind/syntax_kind_enum.rs 11 | 12 | -------------------------------------------------------------------------------- /codegen_scripts/cpnodes.sh: -------------------------------------------------------------------------------- 1 | #!/bin/env sh 2 | 3 | # Copyright contributors to the openqasm-parser project 4 | # SPDX-License-Identifier: Apache-2.0 5 | 6 | # For nodes.rs 7 | # Copy the generated code from the temporary files to which it is written 8 | # to its final location where it will be compiled into the library. 9 | 10 | cd .. && cp -a --backup=t crates/oq3_syntax/src/ast/generated/_new_nodes.rs crates/oq3_syntax/src/ast/generated/nodes.rs && \ 11 | cp -a --backup=t crates/oq3_syntax/src/ast/generated/_new_tokens.rs crates/oq3_syntax/src/ast/generated/tokens.rs 12 | -------------------------------------------------------------------------------- /codegen_scripts/mk_nodes.sh: -------------------------------------------------------------------------------- 1 | #!/bin/env sh 2 | 3 | # Copyright contributors to the openqasm-parser project 4 | # SPDX-License-Identifier: Apache-2.0 5 | 6 | # See mkgenerated.sh 7 | 8 | echo 9 | echo =============== Running sourcegen_ast_nodes once =============== 10 | echo 11 | 12 | cd .. && cargo test sourcegen_ast_nodes 13 | 14 | # echo 15 | # echo =============== Running sourcegen_ast_nodes twice =============== 16 | # echo 17 | 18 | # cargo test sourcegen_ast_nodes 19 | 20 | # rustfmt crates/oq3_syntax/src/ast/generated/_new_nodes.rs 21 | # rustfmt crates/parser/src/syntax_kind/_syntax_kind_enum.rs 22 | # rustfmt crates/oq3_syntax/src/ast/generated/_new_tokens.rs 23 | -------------------------------------------------------------------------------- /codegen_scripts/mk_syntax_kinds.sh: -------------------------------------------------------------------------------- 1 | #!/bin/env sh 2 | 3 | # Copyright contributors to the openqasm-parser project 4 | # SPDX-License-Identifier: Apache-2.0 5 | 6 | # See mkgenerated.sh 7 | 8 | echo 9 | echo ======================= Running write_syntax_kinds_enum once =============== 10 | echo 11 | 12 | cd .. && cargo test write_syntax_kinds_enum 13 | 14 | #echo ======================= Running write_syntax_kinds_enum() twice =============== 15 | # echo >>>>>>>>>>>>>>> Running write_syntax_kinds_enum() twice 16 | # echo 17 | 18 | # cargo test write_syntax_kinds_enum 19 | 20 | # rustfmt crates/oq3_syntax/src/ast/generated/_new_nodes.rs 21 | # rustfmt crates/parser/src/syntax_kind/_syntax_kind_enum.rs 22 | # rustfmt crates/oq3_syntax/src/ast/generated/_new_tokens.rs 23 | 24 | -------------------------------------------------------------------------------- /codegen_scripts/mk_syntax_tokens.sh: -------------------------------------------------------------------------------- 1 | #!/bin/env sh 2 | 3 | # Copyright contributors to the openqasm-parser project 4 | # SPDX-License-Identifier: Apache-2.0 5 | 6 | # See mkgenerated.sh 7 | 8 | echo 9 | echo =============== Running sourcegen_ast_tokens once =============== 10 | echo 11 | 12 | cd .. && cargo test sourcegen_ast_tokens 13 | 14 | # rustfmt crates/oq3_syntax/src/ast/generated/_new_nodes.rs 15 | # rustfmt crates/parser/src/syntax_kind/_syntax_kind_enum.rs 16 | # rustfmt crates/oq3_syntax/src/ast/generated/_new_tokens.rs 17 | 18 | -------------------------------------------------------------------------------- /codegen_scripts/mkgenerated.sh: -------------------------------------------------------------------------------- 1 | #!/bin/env sh 2 | 3 | # Copyright contributors to the openqasm-parser project 4 | # SPDX-License-Identifier: Apache-2.0 5 | 6 | # This is not how r-a does generation. 7 | # But I find a boot strapping problem. 8 | # So we write generated code to a temporary file (GJL Aug 2023) 9 | 10 | # 1 Update tokens here: crates/oq3_syntax/src/tests/ast_src.rs 11 | # 2 Update tokens in `fn lower` here crates/oq3_syntax/src/tests/sourcegen_ast.rs 12 | # 3 Update grammar here: crates/oq3_syntax/openqasm3.ungram 13 | # This should be enough for codegen. 14 | 15 | # But you still need to add the things to the parser grammar 16 | # 4 You might need to update crates/oq3_parser/src/grammar/expressions/atom.rs 17 | # Or crates/oq3_parser/src/grammar/items.rs (eg. for `gate`) 18 | # Or other grammar files 19 | 20 | # Generated files are not given their final names in order not to clobber exisiting generated code 21 | # Here are the temporary filenames and the final filenames 22 | # You have to copy them manually. 23 | # crates/oq3_syntax/src/ast/generated/_new_tokens.rs --> tokens.rs 24 | # crates/oq3_syntax/src/ast/generated/_new_nodes.rs --> nodes.rs 25 | # crates/oq3_parser/src/syntax_kind/_generated.rs --> generated.rs 26 | 27 | # Update: Running this script now seems robust. Originally all codegen was done 28 | # by a single test. I split it into two tests and run each of them twice. 29 | 30 | # Running the tests always fails at least twice when generating the code. 31 | # If there is really an error, it would fail indefinitely. 32 | # Running three times works. 33 | 34 | ./mk_syntax_kinds.sh 35 | 36 | ./mk_syntax_tokens.sh 37 | 38 | ./mk_nodes.sh 39 | ./mk_nodes.sh 40 | 41 | # Warning: Do not format till all files are generated. 42 | # Don't know why, but code gen will fail otherwise. 43 | 44 | cd .. 45 | rustfmt crates/oq3_parser/src/syntax_kind/_syntax_kind_enum.rs 46 | rustfmt crates/oq3_syntax/src/ast/generated/_new_nodes.rs 47 | rustfmt crates/oq3_syntax/src/ast/generated/_new_tokens.rs 48 | -------------------------------------------------------------------------------- /crates/oq3_lexer/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "oq3_lexer" 3 | description = "Lexer for OpenQASM 3 parser/analyzer" 4 | version.workspace = true 5 | edition.workspace = true 6 | rust-version.workspace = true 7 | license.workspace = true 8 | authors.workspace = true 9 | readme.workspace = true 10 | keywords.workspace = true 11 | categories.workspace = true 12 | repository.workspace = true 13 | 14 | [dependencies] 15 | unicode-xid = "0.2.0" 16 | 17 | [dependencies.unicode-properties] 18 | version = "0.1.0" 19 | default-features = false 20 | features = ["emoji"] 21 | 22 | [dev-dependencies] 23 | expect-test = "1.4.0" 24 | -------------------------------------------------------------------------------- /crates/oq3_lexer/src/cursor.rs: -------------------------------------------------------------------------------- 1 | // Copyright contributors to the openqasm-parser project 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | use std::str::Chars; 5 | 6 | /// Peekable iterator over a char sequence. 7 | /// 8 | /// Next characters can be peeked via `first` method, 9 | /// and position can be shifted forward via `bump` method. 10 | pub struct Cursor<'a> { 11 | len_remaining: usize, 12 | /// Iterator over chars. Slightly faster than a &str. 13 | chars: Chars<'a>, 14 | #[cfg(debug_assertions)] 15 | prev: char, 16 | } 17 | 18 | pub(crate) const EOF_CHAR: char = '\0'; 19 | 20 | impl<'a> Cursor<'a> { 21 | pub fn new(input: &'a str) -> Cursor<'a> { 22 | Cursor { 23 | len_remaining: input.len(), 24 | chars: input.chars(), 25 | #[cfg(debug_assertions)] 26 | prev: EOF_CHAR, 27 | } 28 | } 29 | 30 | pub fn as_str(&self) -> &'a str { 31 | self.chars.as_str() 32 | } 33 | 34 | /// Returns the last eaten symbol (or `'\0'` in release builds). 35 | /// (For debug assertions only.) 36 | pub(crate) fn prev(&self) -> char { 37 | #[cfg(debug_assertions)] 38 | { 39 | self.prev 40 | } 41 | 42 | #[cfg(not(debug_assertions))] 43 | { 44 | EOF_CHAR 45 | } 46 | } 47 | 48 | /// Peeks the next symbol from the input stream without consuming it. 49 | /// If requested position doesn't exist, `EOF_CHAR` is returned. 50 | /// However, getting `EOF_CHAR` doesn't always mean actual end of file, 51 | /// it should be checked with `is_eof` method. 52 | pub(crate) fn first(&self) -> char { 53 | // `.next()` optimizes better than `.nth(0)` 54 | self.chars.clone().next().unwrap_or(EOF_CHAR) 55 | } 56 | 57 | /// Peeks the second symbol from the input stream without consuming it. 58 | pub(crate) fn second(&self) -> char { 59 | // `.next()` optimizes better than `.nth(1)` 60 | let mut iter = self.chars.clone(); 61 | iter.next(); 62 | iter.next().unwrap_or(EOF_CHAR) 63 | } 64 | 65 | /// Checks if there is nothing more to consume. 66 | pub(crate) fn is_eof(&self) -> bool { 67 | self.chars.as_str().is_empty() 68 | } 69 | 70 | /// Returns amount of already consumed symbols. 71 | pub(crate) fn pos_within_token(&self) -> u32 { 72 | (self.len_remaining - self.chars.as_str().len()) as u32 73 | } 74 | 75 | /// Resets the number of bytes consumed to 0. 76 | pub(crate) fn reset_pos_within_token(&mut self) { 77 | self.len_remaining = self.chars.as_str().len(); 78 | } 79 | 80 | /// Moves to the next character. 81 | pub(crate) fn bump(&mut self) -> Option { 82 | let c = self.chars.next()?; 83 | 84 | #[cfg(debug_assertions)] 85 | { 86 | self.prev = c; 87 | } 88 | 89 | Some(c) 90 | } 91 | 92 | /// Eats symbols while predicate returns true or until the end of file is reached. 93 | pub(crate) fn eat_while(&mut self, mut predicate: impl FnMut(char) -> bool) { 94 | // It was tried making optimized version of this for eg. line comments, but 95 | // LLVM can inline all of this and compile it down to fast iteration over bytes. 96 | while predicate(self.first()) && !self.is_eof() { 97 | self.bump(); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /crates/oq3_lexer/src/tests.rs: -------------------------------------------------------------------------------- 1 | // Copyright contributors to the openqasm-parser project 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | use super::*; 5 | 6 | use expect_test::{expect, Expect}; 7 | use std::fmt::Write; 8 | 9 | fn check_lexing(src: &str, expect: Expect) { 10 | let actual: String = tokenize(src).fold(String::new(), |mut output, token| { 11 | let _ = writeln!(output, "{token:?}"); 12 | output 13 | }); 14 | expect.assert_eq(&actual) 15 | } 16 | 17 | #[test] 18 | fn smoke_test() { 19 | check_lexing( 20 | "/* my source file */ fn main() { println!(\"zebra\"); }\n", 21 | expect![[r#" 22 | Token { kind: BlockComment { terminated: true }, len: 20 } 23 | Token { kind: Whitespace, len: 1 } 24 | Token { kind: Ident, len: 2 } 25 | Token { kind: Whitespace, len: 1 } 26 | Token { kind: Ident, len: 4 } 27 | Token { kind: OpenParen, len: 1 } 28 | Token { kind: CloseParen, len: 1 } 29 | Token { kind: Whitespace, len: 1 } 30 | Token { kind: OpenBrace, len: 1 } 31 | Token { kind: Whitespace, len: 1 } 32 | Token { kind: Ident, len: 7 } 33 | Token { kind: Bang, len: 1 } 34 | Token { kind: OpenParen, len: 1 } 35 | Token { kind: Literal { kind: Str { terminated: true }, suffix_start: 7 }, len: 7 } 36 | Token { kind: CloseParen, len: 1 } 37 | Token { kind: Semi, len: 1 } 38 | Token { kind: Whitespace, len: 1 } 39 | Token { kind: CloseBrace, len: 1 } 40 | Token { kind: Whitespace, len: 1 } 41 | "#]], 42 | ) 43 | } 44 | 45 | #[test] 46 | fn comment_flavors() { 47 | check_lexing( 48 | r" 49 | // line 50 | //// line as well 51 | /// outer doc line 52 | //! inner doc line 53 | /* block */ 54 | /**/ 55 | /*** also block */ 56 | /** outer doc block */ 57 | /*! inner doc block */ 58 | ", 59 | expect![[r#" 60 | Token { kind: Whitespace, len: 1 } 61 | Token { kind: LineComment, len: 7 } 62 | Token { kind: Whitespace, len: 1 } 63 | Token { kind: LineComment, len: 17 } 64 | Token { kind: Whitespace, len: 1 } 65 | Token { kind: LineComment, len: 18 } 66 | Token { kind: Whitespace, len: 1 } 67 | Token { kind: LineComment, len: 18 } 68 | Token { kind: Whitespace, len: 1 } 69 | Token { kind: BlockComment { terminated: true }, len: 11 } 70 | Token { kind: Whitespace, len: 1 } 71 | Token { kind: BlockComment { terminated: true }, len: 4 } 72 | Token { kind: Whitespace, len: 1 } 73 | Token { kind: BlockComment { terminated: true }, len: 18 } 74 | Token { kind: Whitespace, len: 1 } 75 | Token { kind: BlockComment { terminated: true }, len: 22 } 76 | Token { kind: Whitespace, len: 1 } 77 | Token { kind: BlockComment { terminated: true }, len: 22 } 78 | Token { kind: Whitespace, len: 1 } 79 | "#]], 80 | ) 81 | } 82 | 83 | #[test] 84 | fn nested_block_comments() { 85 | check_lexing( 86 | r#"/* /* */ */"a""#, 87 | expect![[r#" 88 | Token { kind: BlockComment { terminated: true }, len: 11 } 89 | Token { kind: Literal { kind: Str { terminated: true }, suffix_start: 3 }, len: 3 } 90 | "#]], 91 | ) 92 | } 93 | 94 | // Why does this fail? GJL 95 | // #[test] 96 | // fn characters() { 97 | // check_lexing( 98 | // "'a' ' ' '\\n'", 99 | // expect![[r#" 100 | // Token { kind: Literal { kind: Char { terminated: true }, suffix_start: 3 }, len: 3 } 101 | // Token { kind: Whitespace, len: 1 } 102 | // Token { kind: Literal { kind: Char { terminated: true }, suffix_start: 3 }, len: 3 } 103 | // Token { kind: Whitespace, len: 1 } 104 | // Token { kind: Literal { kind: Char { terminated: true }, suffix_start: 4 }, len: 4 } 105 | // "#]], 106 | // ); 107 | // } 108 | 109 | #[test] 110 | fn literal_suffixes() { 111 | check_lexing( 112 | r#" 113 | "a" 114 | 1234 115 | 0b101 116 | 0xABC 117 | 1.0 118 | 1.0e10 119 | 2us 120 | 3ns 121 | 4ms 122 | 6dt 123 | 5s 124 | 6dta 125 | "#, 126 | expect![[r#" 127 | Token { kind: Whitespace, len: 1 } 128 | Token { kind: Literal { kind: Str { terminated: true }, suffix_start: 3 }, len: 3 } 129 | Token { kind: Whitespace, len: 1 } 130 | Token { kind: Literal { kind: Int { base: Decimal, empty_int: false }, suffix_start: 4 }, len: 4 } 131 | Token { kind: Whitespace, len: 1 } 132 | Token { kind: Literal { kind: Int { base: Binary, empty_int: false }, suffix_start: 5 }, len: 5 } 133 | Token { kind: Whitespace, len: 1 } 134 | Token { kind: Literal { kind: Int { base: Hexadecimal, empty_int: false }, suffix_start: 5 }, len: 5 } 135 | Token { kind: Whitespace, len: 1 } 136 | Token { kind: Literal { kind: Float { base: Decimal, empty_exponent: false }, suffix_start: 3 }, len: 3 } 137 | Token { kind: Whitespace, len: 1 } 138 | Token { kind: Literal { kind: Float { base: Decimal, empty_exponent: false }, suffix_start: 6 }, len: 6 } 139 | Token { kind: Whitespace, len: 1 } 140 | Token { kind: Literal { kind: Int { base: Decimal, empty_int: false }, suffix_start: 1 }, len: 1 } 141 | Token { kind: Ident, len: 2 } 142 | Token { kind: Whitespace, len: 1 } 143 | Token { kind: Literal { kind: Int { base: Decimal, empty_int: false }, suffix_start: 1 }, len: 1 } 144 | Token { kind: Ident, len: 2 } 145 | Token { kind: Whitespace, len: 1 } 146 | Token { kind: Literal { kind: Int { base: Decimal, empty_int: false }, suffix_start: 1 }, len: 1 } 147 | Token { kind: Ident, len: 2 } 148 | Token { kind: Whitespace, len: 1 } 149 | Token { kind: Literal { kind: Int { base: Decimal, empty_int: false }, suffix_start: 1 }, len: 1 } 150 | Token { kind: Ident, len: 2 } 151 | Token { kind: Whitespace, len: 1 } 152 | Token { kind: Literal { kind: Int { base: Decimal, empty_int: false }, suffix_start: 1 }, len: 1 } 153 | Token { kind: Ident, len: 1 } 154 | Token { kind: Whitespace, len: 1 } 155 | Token { kind: Literal { kind: Int { base: Decimal, empty_int: false }, suffix_start: 1 }, len: 1 } 156 | Token { kind: Ident, len: 3 } 157 | Token { kind: Whitespace, len: 1 } 158 | "#]], 159 | ) 160 | } 161 | -------------------------------------------------------------------------------- /crates/oq3_parser/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "oq3_parser" 3 | description = "Parser for OpenQASM 3 parser/analyzer" 4 | version.workspace = true 5 | edition.workspace = true 6 | rust-version.workspace = true 7 | license.workspace = true 8 | authors.workspace = true 9 | readme.workspace = true 10 | keywords.workspace = true 11 | categories.workspace = true 12 | repository.workspace = true 13 | 14 | [lib] 15 | doctest = false 16 | 17 | [dependencies] 18 | oq3_lexer.workspace = true 19 | drop_bomb = "0.1.5" 20 | limit = { version = "0.0.188", package = "ra_ap_limit" } 21 | 22 | [dev-dependencies] 23 | expect-test = "1.4.0" 24 | -------------------------------------------------------------------------------- /crates/oq3_parser/src/event.rs: -------------------------------------------------------------------------------- 1 | // Copyright contributors to the openqasm-parser project 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //! This module provides a way to construct a `File`. 5 | //! It is intended to be completely decoupled from the 6 | //! parser, so as to allow to evolve the tree representation 7 | //! and the parser algorithm independently. 8 | //! 9 | //! The `TreeSink` trait is the bridge between the parser and the 10 | //! tree builder: the parser produces a stream of events like 11 | //! `start node`, `finish node`, and `FileBuilder` converts 12 | //! this stream to a real tree. 13 | use std::mem; 14 | 15 | use crate::{ 16 | output::Output, 17 | SyntaxKind::{self, *}, 18 | }; 19 | 20 | /// `Parser` produces a flat list of `Event`s. 21 | /// They are converted to a tree-structure in 22 | /// a separate pass, via `TreeBuilder`. 23 | #[derive(Debug)] 24 | pub(crate) enum Event { 25 | /// This event signifies the start of the node. 26 | /// It should be either abandoned (in which case the 27 | /// `kind` is `TOMBSTONE`, and the event is ignored), 28 | /// or completed via a `Finish` event. 29 | /// 30 | /// All tokens between a `Start` and a `Finish` would 31 | /// become the children of the respective node. 32 | /// 33 | /// For left-recursive syntactic constructs, the parser produces 34 | /// a child node before it sees a parent. `forward_parent` 35 | /// saves the position of current event's parent. 36 | /// 37 | /// Consider this path 38 | /// 39 | /// foo::bar 40 | /// 41 | /// The events for it would look like this: 42 | /// 43 | /// ```text 44 | /// START(PATH) IDENT('foo') FINISH START(PATH) T![::] IDENT('bar') FINISH 45 | /// | /\ 46 | /// | | 47 | /// +------forward-parent------+ 48 | /// ``` 49 | /// 50 | /// And the tree would look like this 51 | /// 52 | /// ```text 53 | /// +--PATH---------+ 54 | /// | | | 55 | /// | | | 56 | /// | '::' 'bar' 57 | /// | 58 | /// PATH 59 | /// | 60 | /// 'foo' 61 | /// ``` 62 | /// 63 | /// See also `CompletedMarker::precede`. 64 | Start { 65 | kind: SyntaxKind, 66 | forward_parent: Option, 67 | }, 68 | 69 | /// Complete the previous `Start` event 70 | Finish, 71 | 72 | /// Produce a single leaf-element. 73 | /// `n_raw_tokens` is used to glue complex contextual tokens. 74 | /// For example, lexer tokenizes `>>` as `>`, `>`, and 75 | /// `n_raw_tokens = 2` is used to produced a single `>>`. 76 | Token { kind: SyntaxKind, n_raw_tokens: u8 }, 77 | /// When we parse `foo.0.0` or `foo. 0. 0` the lexer will hand us a float literal 78 | /// instead of an integer literal followed by a dot as the lexer has no contextual knowledge. 79 | /// This event instructs whatever consumes the events to split the float literal into 80 | /// the corresponding parts. 81 | // FloatSplitHack { 82 | // ends_in_dot: bool, 83 | // }, 84 | Error { msg: String }, 85 | } 86 | 87 | impl Event { 88 | pub(crate) fn tombstone() -> Self { 89 | Event::Start { 90 | kind: TOMBSTONE, 91 | forward_parent: None, 92 | } 93 | } 94 | } 95 | 96 | /// Generate the syntax tree with the control of events. 97 | pub(super) fn process(mut events: Vec) -> Output { 98 | let mut res = Output::default(); 99 | let mut forward_parents = Vec::new(); 100 | 101 | for i in 0..events.len() { 102 | match mem::replace(&mut events[i], Event::tombstone()) { 103 | Event::Start { 104 | kind, 105 | forward_parent, 106 | } => { 107 | // For events[A, B, C], B is A's forward_parent, C is B's forward_parent, 108 | // in the normal control flow, the parent-child relation: `A -> B -> C`, 109 | // while with the magic forward_parent, it writes: `C <- B <- A`. 110 | 111 | // append `A` into parents. 112 | forward_parents.push(kind); 113 | let mut idx = i; 114 | let mut fp = forward_parent; 115 | while let Some(fwd) = fp { 116 | idx += fwd as usize; 117 | // append `A`'s forward_parent `B` 118 | fp = match mem::replace(&mut events[idx], Event::tombstone()) { 119 | Event::Start { 120 | kind, 121 | forward_parent, 122 | } => { 123 | forward_parents.push(kind); 124 | forward_parent 125 | } 126 | _ => unreachable!(), 127 | }; 128 | // append `B`'s forward_parent `C` in the next stage. 129 | } 130 | 131 | for kind in forward_parents.drain(..).rev() { 132 | if kind != TOMBSTONE { 133 | res.enter_node(kind); 134 | } 135 | } 136 | } 137 | Event::Finish => res.leave_node(), 138 | Event::Token { kind, n_raw_tokens } => { 139 | res.token(kind, n_raw_tokens); 140 | } 141 | // Event::FloatSplitHack { ends_in_dot } => { 142 | // res.float_split_hack(ends_in_dot); 143 | // let ev = mem::replace(&mut events[i + 1], Event::tombstone()); 144 | // assert!(matches!(ev, Event::Finish), "{ev:?}"); 145 | // } 146 | Event::Error { msg } => res.error(msg), 147 | } 148 | } 149 | 150 | res 151 | } 152 | -------------------------------------------------------------------------------- /crates/oq3_parser/src/grammar.rs: -------------------------------------------------------------------------------- 1 | // Copyright contributors to the openqasm-parser project 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //! This is the actual "grammar" of the OpenQASM 3 language. 5 | //! 6 | //! Each function in this module and its children corresponds 7 | //! to a production of the formal grammar. Submodules roughly 8 | //! correspond to different *areas* of the grammar. By convention, 9 | //! each submodule starts with `use super::*` import and exports 10 | //! "public" productions via `pub(super)`. 11 | //! 12 | //! Because OQ3 is simpler than Rust, most submodules mentioned above 13 | //! have been removed. 14 | //! 15 | //! See docs for [`Parser`](super::parser::Parser) to learn about API, 16 | //! available to the grammar, and see docs for [`Event`](super::event::Event) 17 | //! to learn how this actually manages to produce parse trees. 18 | //! 19 | //! Code in this module also contains inline tests, which start with 20 | //! `// test name-of-the-test` comment and look like this: 21 | //! 22 | //! Most of the inline tests have been removed and not yet replaced with 23 | //! OQ3 tests. 24 | //! 25 | //! ``` 26 | //! // test function_with_zero_parameters 27 | //! // fn foo() {} 28 | //! ``` 29 | //! 30 | //! Note: `xtask` is not (yet?) updated for OQ3. 31 | //! After adding a new inline-test, run `cargo test -p xtask` to 32 | //! extract it as a standalone text-fixture into 33 | //! `crates/syntax/test_data/parser/`, and run `cargo test` once to 34 | //! create the "gold" value. 35 | //! 36 | //! Coding convention: rules like `where_clause` always produce either a 37 | //! node or an error, rules like `opt_where_clause` may produce nothing. 38 | //! Non-opt rules typically start with `assert!(p.at(FIRST_TOKEN))`, the 39 | //! caller is responsible for branching on the first token. 40 | 41 | mod expressions; 42 | mod items; 43 | mod params; 44 | 45 | use crate::{ 46 | parser::{CompletedMarker, Marker, Parser}, 47 | SyntaxKind::{self, *}, 48 | TokenSet, T, 49 | }; 50 | 51 | pub(crate) mod entry { 52 | use super::*; 53 | 54 | pub(crate) mod top { 55 | use super::*; 56 | 57 | pub(crate) fn source_file(p: &mut Parser<'_>) { 58 | let m = p.start(); 59 | items::source_file_contents(p, false); 60 | m.complete(p, SOURCE_FILE); 61 | } 62 | 63 | pub(crate) fn expr(p: &mut Parser<'_>) { 64 | let m = p.start(); 65 | expressions::expr(p); 66 | if p.at(EOF) { 67 | m.abandon(p); 68 | return; 69 | } 70 | while !p.at(EOF) { 71 | p.bump_any(); 72 | } 73 | m.complete(p, ERROR); 74 | } 75 | } 76 | } 77 | 78 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 79 | enum BlockLike { 80 | Block, 81 | NotBlock, 82 | } 83 | 84 | // FIXME: flow control expressions removed because flow control are not expressions in OQ3 85 | // This might break something 86 | impl BlockLike { 87 | fn is_block(self) -> bool { 88 | self == BlockLike::Block 89 | } 90 | 91 | fn is_blocklike(kind: SyntaxKind) -> bool { 92 | matches!(kind, BLOCK_EXPR) 93 | // matches!(kind, BLOCK_EXPR | IF_EXPR | WHILE_EXPR | FOR_EXPR) 94 | } 95 | } 96 | 97 | // FIXME: was only used in opt_ret_type. But no longer even there 98 | // fn scalar_type(p: &mut Parser<'_>) { 99 | // assert!(p.current().is_scalar_type()); 100 | // let r = p.start(); 101 | // p.bump_any(); 102 | // r.complete(p, SCALAR_TYPE); 103 | // } 104 | 105 | /// Parse the optional return signature of a `defcal` or `def` definition. 106 | /// Return `true` if the return type was found, else `false. 107 | fn opt_ret_type(p: &mut Parser<'_>) -> bool { 108 | if p.at(T![->]) { 109 | let m = p.start(); 110 | p.bump(T![->]); 111 | if !p.current().is_scalar_type() { 112 | p.error("Expected scalar return type after ->"); 113 | } 114 | // Allow any type, with possible error. 115 | if p.current().is_type() { 116 | expressions::type_spec(p); 117 | } else { 118 | m.abandon(p); 119 | return false; 120 | } 121 | m.complete(p, RETURN_SIGNATURE); 122 | true 123 | } else { 124 | false 125 | } 126 | } 127 | 128 | /// Parse an identifer signifying a name. Attempt recovery 129 | /// on failure. 130 | fn name_r(p: &mut Parser<'_>, recovery: TokenSet) { 131 | // FIXME: testing. dont know if this belongs 132 | if p.at(HARDWAREIDENT) { 133 | let m = p.start(); 134 | p.bump(HARDWAREIDENT); 135 | m.complete(p, HARDWARE_QUBIT); 136 | return; 137 | } 138 | 139 | if p.at(IDENT) { 140 | let m = p.start(); 141 | p.bump(IDENT); 142 | m.complete(p, NAME); 143 | } else { 144 | p.err_recover("expected a name", recovery); 145 | } 146 | } 147 | 148 | /// Parse an identifer signifying a name. Do not attempt 149 | /// error recovery. 150 | fn name(p: &mut Parser<'_>) { 151 | name_r(p, TokenSet::EMPTY); 152 | } 153 | 154 | /// The `parser` passed this is required to at least consume one token if it returns `true`. 155 | /// If the `parser` returns false, parsing will stop. 156 | fn delimited( 157 | p: &mut Parser<'_>, 158 | bra: SyntaxKind, 159 | ket: SyntaxKind, 160 | consume_braket: bool, 161 | delim: SyntaxKind, 162 | first_set: TokenSet, 163 | mut parser: impl FnMut(&mut Parser<'_>) -> bool, 164 | ) { 165 | if consume_braket { 166 | p.bump(bra); 167 | } 168 | while !p.at(ket) && !p.at(EOF) { 169 | if !parser(p) { 170 | break; 171 | } 172 | if !p.at(delim) { 173 | if p.at_ts(first_set) { 174 | p.error(format!("expected {:?}", delim)); 175 | } else { 176 | break; 177 | } 178 | } else { 179 | p.bump(delim); 180 | } 181 | } 182 | if consume_braket { 183 | p.expect(ket); 184 | } 185 | } 186 | 187 | impl SyntaxKind { 188 | /// Return `true` if the next token begins a classical type, including array, specification. 189 | pub fn is_classical_type(&self) -> bool { 190 | self.is_scalar_type() || matches!(self, ARRAY_KW) 191 | } 192 | 193 | /// Return `true` if the next token begins quantum type. 194 | pub fn is_quantum_type(&self) -> bool { 195 | matches!(self, T![qubit] | HARDWARE_QUBIT) 196 | } 197 | 198 | /// Return `true` if the next token begins a type. 199 | pub fn is_type(&self) -> bool { 200 | self.is_classical_type() || self.is_quantum_type() 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /crates/oq3_parser/src/grammar/params.rs: -------------------------------------------------------------------------------- 1 | // Copyright contributors to the openqasm-parser project 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | use super::*; 5 | 6 | pub(super) fn param_list_gate_params(p: &mut Parser<'_>) { 7 | _param_list_openqasm(p, DefFlavor::GateParams, None); 8 | } 9 | pub(super) fn param_list_gate_qubits(p: &mut Parser<'_>) { 10 | _param_list_openqasm(p, DefFlavor::GateQubits, None); 11 | } 12 | 13 | pub(super) fn arg_list_gate_call_qubits(p: &mut Parser<'_>) { 14 | _param_list_openqasm(p, DefFlavor::GateCallQubits, None); 15 | } 16 | 17 | pub(super) fn param_list_def_params(p: &mut Parser<'_>) { 18 | _param_list_openqasm(p, DefFlavor::DefParams, None); 19 | } 20 | pub(super) fn param_list_defcal_params(p: &mut Parser<'_>) { 21 | _param_list_openqasm(p, DefFlavor::DefCalParams, None); 22 | } 23 | pub(super) fn param_list_defcal_qubits(p: &mut Parser<'_>) { 24 | _param_list_openqasm(p, DefFlavor::DefCalQubits, None); 25 | } 26 | 27 | pub(super) fn expression_list(p: &mut Parser<'_>) { 28 | _param_list_openqasm(p, DefFlavor::ExpressionList, None); 29 | } 30 | 31 | // Here and elsewhere "Gate" means gate def, and "GateCall" means gate call. 32 | #[derive(Debug, Clone, Copy)] 33 | enum DefFlavor { 34 | // Same syntax for: gate def params, function call params, gate call params. 35 | // But for various reasons, we separate this into GateParams and CallOrGateCallParams 36 | // One reason: we can disallow here empty parens in gate def. 37 | GateParams, // parens, no type 38 | // For the moment, following is handled in expressions::arg_list instead. 39 | GateQubits, // no parens, no type, '{' terminates 40 | GateCallQubits, // no parens, no type, ';' terminates 41 | DefParams, // parens, type 42 | DefCalParams, // parens, opt type 43 | DefCalQubits, // no parens, no type, '{' or '->' terminates 44 | ExpressionList, 45 | } 46 | 47 | // Parse a list of parameters. 48 | // FIXME: m: Option is unused, always called with None. Can remove. 49 | fn _param_list_openqasm(p: &mut Parser<'_>, flavor: DefFlavor, m: Option) { 50 | use DefFlavor::*; 51 | let list_marker = p.start(); 52 | let want_parens = matches!(flavor, GateParams | DefParams | DefCalParams); 53 | match flavor { 54 | GateParams | DefParams | DefCalParams => p.bump(T!['(']), 55 | GateQubits | GateCallQubits | DefCalQubits | ExpressionList => (), 56 | } 57 | // FIXME: find implementation that does not require [T![')'], T![')']] 58 | // I tried using TokenSet, which should give exactly the same result. 59 | // But it does not. May be because -> is a compound token. 60 | let list_end_tokens = match flavor { 61 | // GateParams | DefParams | DefCalParams => {TokenSet::new(&[T![')']])}, 62 | // GateQubits => {TokenSet::new(&[T!['{']])}, 63 | // DefCalQubits => {TokenSet::new(&[T!['{'], T![->]])}, 64 | ExpressionList => [T![']'], T![']']], 65 | GateParams | DefParams | DefCalParams => [T![')'], T![')']], 66 | // When no parens are present `{` terminates the list of parameters. 67 | GateQubits => [T!['{'], T!['{']], 68 | GateCallQubits => [SEMICOLON, SEMICOLON], 69 | DefCalQubits => [T!['{'], T![->]], 70 | }; 71 | let mut param_marker = m; 72 | // let mut param_marker = None; 73 | let mut num_params: usize = 0; 74 | // Would be nice if we could used the following line instead of hacked roll your own two lines down. 75 | // while !p.at(EOF) && !p.at_ts(list_end_tokens) { 76 | while !p.at(EOF) && !list_end_tokens.iter().any(|x| p.at(*x)) { 77 | let m = param_marker.take().unwrap_or_else(|| p.start()); 78 | if !(p.current().is_type() || p.at_ts(PARAM_FIRST)) { 79 | p.error("expected value parameter"); 80 | m.abandon(p); 81 | break; 82 | } 83 | let found_param = match flavor { 84 | ExpressionList => { 85 | m.abandon(p); 86 | expressions::expr_or_range_expr(p); 87 | true 88 | } 89 | GateCallQubits => arg_gate_call_qubit(p, m), 90 | // FIXME: this can't work. These two variants have different reqs on params 91 | DefParams | DefCalParams => param_typed(p, m), 92 | // The following is pretty ugly. Probably inefficient as well 93 | GateParams | GateQubits | DefCalQubits => param_untyped(p, m), 94 | }; 95 | if !found_param { 96 | break; 97 | } 98 | num_params += 1; 99 | // FIXME: This is only needed to support `->` as terminating tokens. 100 | // Not for `{`. But I don't know why. prbly because `->` is compound. 101 | // FIXME: use at_ts() 102 | if list_end_tokens.iter().any(|x| p.at(*x)) { 103 | // if p.at_ts(list_end_tokens) { 104 | break; 105 | } 106 | // Params must be separated by commas. 107 | if !p.at(T![,]) { 108 | if p.at_ts(PARAM_FIRST) { 109 | p.error("Expected `,`"); 110 | } else { 111 | break; 112 | } 113 | } else { 114 | // We found the expected comma, so consume it. 115 | p.bump(T![,]); 116 | } 117 | } 118 | match flavor { 119 | GateParams | ExpressionList if num_params < 1 => { 120 | p.error("expected one or more parameters"); 121 | } 122 | GateParams | ExpressionList => {} 123 | GateQubits | GateCallQubits | DefParams | DefCalParams | DefCalQubits => {} 124 | }; 125 | if let Some(m) = param_marker { 126 | m.abandon(p); 127 | } 128 | // Error if we don't find closing paren. 129 | if want_parens { 130 | p.expect(T![')']); 131 | } 132 | let kind = match flavor { 133 | GateQubits => PARAM_LIST, 134 | DefCalQubits => QUBIT_LIST, 135 | GateCallQubits => QUBIT_LIST, 136 | ExpressionList => EXPRESSION_LIST, 137 | DefParams | DefCalParams => TYPED_PARAM_LIST, 138 | GateParams => PARAM_LIST, 139 | }; 140 | list_marker.complete(p, kind); 141 | } 142 | 143 | const PATTERN_FIRST: TokenSet = expressions::LITERAL_FIRST 144 | .union(expressions::atom::PATH_FIRST) 145 | .union(TokenSet::new(&[ 146 | T![box], 147 | T![const], 148 | T!['('], 149 | T!['['], 150 | T![&], 151 | T![_], 152 | T![-], 153 | T![.], 154 | ])); 155 | 156 | const TYPE_FIRST: TokenSet = expressions::atom::PATH_FIRST.union(TokenSet::new(&[ 157 | T!['('], 158 | T!['['], 159 | T![<], 160 | T![!], 161 | T![*], 162 | T![&], 163 | T![_], 164 | T![extern], 165 | ])); 166 | 167 | const PARAM_FIRST: TokenSet = PATTERN_FIRST.union(TYPE_FIRST); 168 | 169 | // TODO: Look again at the r-a code to copy the idioms there. 170 | // We have removed all of the code that can serve as an example. 171 | // In OQ 3, parameters in gate defs don't have type annotations. 172 | fn param_untyped(p: &mut Parser<'_>, m: Marker) -> bool { 173 | if !p.at(IDENT) { 174 | p.error("Expected parameter name"); 175 | m.abandon(p); 176 | return false; 177 | } 178 | p.bump(IDENT); 179 | m.complete(p, PARAM); 180 | true 181 | } 182 | 183 | fn param_typed(p: &mut Parser<'_>, m: Marker) -> bool { 184 | expressions::type_spec(p); 185 | expressions::var_name(p); 186 | m.complete(p, TYPED_PARAM); 187 | true 188 | } 189 | 190 | // These can be cast to GateOperand 191 | pub(crate) fn arg_gate_call_qubit(p: &mut Parser<'_>, m: Marker) -> bool { 192 | if p.at(HARDWAREIDENT) { 193 | p.bump(HARDWAREIDENT); 194 | m.complete(p, HARDWARE_QUBIT); 195 | return true; 196 | } 197 | 198 | if !p.at(IDENT) { 199 | p.error("Expected name in qubit argument"); 200 | m.abandon(p); 201 | return false; 202 | } 203 | // let mcomp = expressions::atom::identifier(p); 204 | p.bump(IDENT); 205 | let mcomp = m.complete(p, IDENTIFIER); 206 | if p.at(T!['[']) { 207 | // expressions::index_expr(p, mcomp); 208 | expressions::indexed_identifier(p, mcomp); 209 | return true; 210 | } 211 | true 212 | } 213 | -------------------------------------------------------------------------------- /crates/oq3_parser/src/input.rs: -------------------------------------------------------------------------------- 1 | // Copyright contributors to the openqasm-parser project 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //! See [`Input`]. 5 | 6 | use crate::SyntaxKind; 7 | 8 | #[allow(non_camel_case_types)] 9 | type bits = u64; 10 | 11 | // FIXME GJL `LexerToken` does not appear anywhere in the r-a project. 12 | /// Input for the parser -- a sequence of tokens. 13 | /// 14 | /// As of now, parser doesn't have access to the *text* of the tokens, and makes 15 | /// decisions based solely on their classification. Unlike `LexerToken`, the 16 | /// `Tokens` doesn't include whitespace and comments. Main input to the parser. 17 | /// 18 | /// Struct of arrays internally, but this shouldn't really matter. 19 | #[derive(Default)] 20 | pub struct Input { 21 | kind: Vec, 22 | joint: Vec, 23 | contextual_kind: Vec, 24 | } 25 | 26 | /// `pub` impl used by callers to create `Tokens`. 27 | impl Input { 28 | #[inline] 29 | pub fn push(&mut self, kind: SyntaxKind) { 30 | self.push_impl(kind, SyntaxKind::EOF) 31 | } 32 | #[inline] 33 | pub fn push_ident(&mut self, contextual_kind: SyntaxKind) { 34 | self.push_impl(SyntaxKind::IDENT, contextual_kind) 35 | } 36 | /// Sets jointness for the last token we've pushed. 37 | /// 38 | /// This is a separate API rather than an argument to the `push` to make it 39 | /// convenient both for textual and mbe tokens. With text, you know whether 40 | /// the *previous* token was joint, with mbe, you know whether the *current* 41 | /// one is joint. This API allows for styles of usage: 42 | /// 43 | /// ``` 44 | /// // In text: 45 | /// tokens.was_joint(prev_joint); 46 | /// tokens.push(curr); 47 | /// 48 | /// // In MBE: 49 | /// token.push(curr); 50 | /// tokens.push(curr_joint) 51 | /// ``` 52 | #[inline] 53 | pub fn was_joint(&mut self) { 54 | let n = self.len() - 1; 55 | let (idx, b_idx) = self.bit_index(n); 56 | self.joint[idx] |= 1 << b_idx; 57 | } 58 | #[inline] 59 | fn push_impl(&mut self, kind: SyntaxKind, contextual_kind: SyntaxKind) { 60 | let idx = self.len(); 61 | if idx % (bits::BITS as usize) == 0 { 62 | self.joint.push(0); 63 | } 64 | self.kind.push(kind); 65 | self.contextual_kind.push(contextual_kind); 66 | } 67 | } 68 | 69 | /// pub(crate) impl used by the parser to consume `Tokens`. 70 | impl Input { 71 | pub(crate) fn kind(&self, idx: usize) -> SyntaxKind { 72 | self.kind.get(idx).copied().unwrap_or(SyntaxKind::EOF) 73 | } 74 | // pub(crate) fn contextual_kind(&self, idx: usize) -> SyntaxKind { 75 | // self.contextual_kind.get(idx).copied().unwrap_or(SyntaxKind::EOF) 76 | // } 77 | pub(crate) fn is_joint(&self, n: usize) -> bool { 78 | let (idx, b_idx) = self.bit_index(n); 79 | self.joint[idx] & 1 << b_idx != 0 80 | } 81 | } 82 | 83 | impl Input { 84 | fn bit_index(&self, n: usize) -> (usize, usize) { 85 | let idx = n / (bits::BITS as usize); 86 | let b_idx = n % (bits::BITS as usize); 87 | (idx, b_idx) 88 | } 89 | // Number of tokens 90 | fn len(&self) -> usize { 91 | self.kind.len() 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /crates/oq3_parser/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright contributors to the openqasm-parser project 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //! Parser for OpenQASM 3 5 | 6 | mod lexed_str; 7 | mod token_set; 8 | 9 | // Temp make pub for debugging 10 | mod event; 11 | mod grammar; 12 | mod input; 13 | mod output; 14 | mod parser; 15 | mod shortcuts; 16 | pub mod syntax_kind; 17 | 18 | // FIXME 19 | // #[cfg(test)] 20 | // mod tests; 21 | 22 | pub(crate) use token_set::TokenSet; 23 | 24 | pub use crate::{ 25 | input::Input, 26 | lexed_str::LexedStr, 27 | output::{Output, Step}, 28 | shortcuts::StrStep, 29 | syntax_kind::SyntaxKind, 30 | }; 31 | 32 | /// GJL FIXME comments 33 | /// Parse the whole of the input as a given syntactic construct. 34 | /// [`TopEntryPoint::parse`] makes a guarantee that 35 | /// * all input is consumed 36 | /// * the result is a valid tree (there's one root node) 37 | #[derive(Debug)] 38 | pub enum TopEntryPoint { 39 | SourceFile, 40 | // Type, 41 | Expr, 42 | } 43 | 44 | impl TopEntryPoint { 45 | pub fn parse(&self, input: &Input) -> Output { 46 | let entry_point: fn(&'_ mut parser::Parser<'_>) = match self { 47 | TopEntryPoint::SourceFile => grammar::entry::top::source_file, 48 | // TopEntryPoint::Type => grammar::entry::top::type_, 49 | TopEntryPoint::Expr => grammar::entry::top::expr, 50 | }; 51 | let mut p = parser::Parser::new(input); 52 | entry_point(&mut p); 53 | let events = p.finish(); 54 | let res = event::process(events); 55 | 56 | if cfg!(debug_assertions) { 57 | let mut depth = 0; 58 | let mut first = true; 59 | for step in res.iter() { 60 | assert!(depth > 0 || first); 61 | first = false; 62 | match step { 63 | Step::Enter { .. } => depth += 1, 64 | Step::Exit => depth -= 1, 65 | Step::FloatSplit { 66 | ends_in_dot: has_pseudo_dot, 67 | } => depth -= 1 + !has_pseudo_dot as usize, 68 | Step::Token { .. } | Step::Error { .. } => (), 69 | } 70 | } 71 | assert!(!first, "no tree at all"); 72 | assert_eq!(depth, 0, "unbalanced tree"); 73 | } 74 | 75 | res 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /crates/oq3_parser/src/output.rs: -------------------------------------------------------------------------------- 1 | // Copyright contributors to the openqasm-parser project 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //! See [`Output`] 5 | 6 | use crate::SyntaxKind; 7 | 8 | /// Output of the parser -- a DFS traversal of a concrete syntax tree. 9 | /// 10 | /// Use the [`Output::iter`] method to iterate over traversal steps and consume 11 | /// a syntax tree. 12 | /// 13 | /// In a sense, this is just a sequence of [`SyntaxKind`]-colored parenthesis 14 | /// interspersed into the original [`crate::Input`]. The output is fundamentally 15 | /// coordinated with the input and `n_input_tokens` refers to the number of 16 | /// times [`crate::Input::push`] was called. 17 | #[derive(Default)] 18 | pub struct Output { 19 | /// 32-bit encoding of events. If LSB is zero, then that's an index into the 20 | /// error vector. Otherwise, it's one of the thee other variants, with data encoded as 21 | /// 22 | /// |16 bit kind|8 bit n_input_tokens|4 bit tag|4 bit leftover| 23 | /// 24 | event: Vec, 25 | error: Vec, 26 | } 27 | 28 | #[derive(Debug)] 29 | pub enum Step<'a> { 30 | Token { 31 | kind: SyntaxKind, 32 | n_input_tokens: u8, 33 | }, 34 | FloatSplit { 35 | ends_in_dot: bool, 36 | }, 37 | Enter { 38 | kind: SyntaxKind, 39 | }, 40 | Exit, 41 | Error { 42 | msg: &'a str, 43 | }, 44 | } 45 | 46 | impl Output { 47 | const EVENT_MASK: u32 = 0b1; 48 | const TAG_MASK: u32 = 0x0000_00F0; 49 | const N_INPUT_TOKEN_MASK: u32 = 0x0000_FF00; 50 | const KIND_MASK: u32 = 0xFFFF_0000; 51 | 52 | const ERROR_SHIFT: u32 = Self::EVENT_MASK.trailing_ones(); 53 | const TAG_SHIFT: u32 = Self::TAG_MASK.trailing_zeros(); 54 | const N_INPUT_TOKEN_SHIFT: u32 = Self::N_INPUT_TOKEN_MASK.trailing_zeros(); 55 | const KIND_SHIFT: u32 = Self::KIND_MASK.trailing_zeros(); 56 | 57 | const TOKEN_EVENT: u8 = 0; 58 | const ENTER_EVENT: u8 = 1; 59 | const EXIT_EVENT: u8 = 2; 60 | const SPLIT_EVENT: u8 = 3; 61 | 62 | pub fn iter(&self) -> impl Iterator> { 63 | self.event.iter().map(|&event| { 64 | if event & Self::EVENT_MASK == 0 { 65 | return Step::Error { 66 | msg: self.error[(event as usize) >> Self::ERROR_SHIFT].as_str(), 67 | }; 68 | } 69 | let tag = ((event & Self::TAG_MASK) >> Self::TAG_SHIFT) as u8; 70 | match tag { 71 | Self::TOKEN_EVENT => { 72 | let kind: SyntaxKind = 73 | (((event & Self::KIND_MASK) >> Self::KIND_SHIFT) as u16).into(); 74 | let n_input_tokens = 75 | ((event & Self::N_INPUT_TOKEN_MASK) >> Self::N_INPUT_TOKEN_SHIFT) as u8; 76 | Step::Token { 77 | kind, 78 | n_input_tokens, 79 | } 80 | } 81 | Self::ENTER_EVENT => { 82 | let kind: SyntaxKind = 83 | (((event & Self::KIND_MASK) >> Self::KIND_SHIFT) as u16).into(); 84 | Step::Enter { kind } 85 | } 86 | Self::EXIT_EVENT => Step::Exit, 87 | Self::SPLIT_EVENT => Step::FloatSplit { 88 | ends_in_dot: event & Self::N_INPUT_TOKEN_MASK != 0, 89 | }, 90 | _ => unreachable!(), 91 | } 92 | }) 93 | } 94 | 95 | pub(crate) fn token(&mut self, kind: SyntaxKind, n_tokens: u8) { 96 | let e = ((kind as u16 as u32) << Self::KIND_SHIFT) 97 | | ((n_tokens as u32) << Self::N_INPUT_TOKEN_SHIFT) 98 | | Self::EVENT_MASK; 99 | self.event.push(e) 100 | } 101 | 102 | // pub(crate) fn float_split_hack(&mut self, ends_in_dot: bool) { 103 | // let e = (Self::SPLIT_EVENT as u32) << Self::TAG_SHIFT 104 | // | ((ends_in_dot as u32) << Self::N_INPUT_TOKEN_SHIFT) 105 | // | Self::EVENT_MASK; 106 | // self.event.push(e); 107 | // } 108 | 109 | pub(crate) fn enter_node(&mut self, kind: SyntaxKind) { 110 | let e = ((kind as u16 as u32) << Self::KIND_SHIFT) 111 | | ((Self::ENTER_EVENT as u32) << Self::TAG_SHIFT) 112 | | Self::EVENT_MASK; 113 | self.event.push(e) 114 | } 115 | 116 | pub(crate) fn leave_node(&mut self) { 117 | let e = (Self::EXIT_EVENT as u32) << Self::TAG_SHIFT | Self::EVENT_MASK; 118 | self.event.push(e) 119 | } 120 | 121 | pub(crate) fn error(&mut self, error: String) { 122 | let idx = self.error.len(); 123 | self.error.push(error); 124 | let e = (idx as u32) << Self::ERROR_SHIFT; 125 | self.event.push(e); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /crates/oq3_parser/src/syntax_kind.rs: -------------------------------------------------------------------------------- 1 | // Copyright contributors to the openqasm-parser project 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //! Defines [`SyntaxKind`] -- a fieldless enum of all possible syntactic 5 | //! constructs of the OpenQASM 3 language. 6 | 7 | pub mod syntax_kind_enum; 8 | 9 | #[allow(unreachable_pub)] 10 | pub use self::syntax_kind_enum::{SyntaxKind, T}; 11 | 12 | impl From for SyntaxKind { 13 | #[inline] 14 | fn from(d: u16) -> SyntaxKind { 15 | assert!(d <= (SyntaxKind::__LAST as u16)); 16 | unsafe { std::mem::transmute::(d) } 17 | } 18 | } 19 | 20 | impl From for u16 { 21 | #[inline] 22 | fn from(k: SyntaxKind) -> u16 { 23 | k as u16 24 | } 25 | } 26 | 27 | impl SyntaxKind { 28 | #[inline] 29 | pub fn is_trivia(self) -> bool { 30 | matches!(self, SyntaxKind::WHITESPACE | SyntaxKind::COMMENT) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /crates/oq3_parser/src/token_set.rs: -------------------------------------------------------------------------------- 1 | // Copyright contributors to the openqasm-parser project 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //! A bit-set of `SyntaxKind`s. 5 | 6 | use crate::SyntaxKind; 7 | 8 | /// A bit-set of `SyntaxKind`s 9 | #[derive(Clone, Copy)] 10 | pub(crate) struct TokenSet(u128); 11 | 12 | impl TokenSet { 13 | pub(crate) const EMPTY: TokenSet = TokenSet(0); 14 | 15 | pub(crate) const fn new(kinds: &[SyntaxKind]) -> TokenSet { 16 | let mut res = 0u128; 17 | let mut i = 0; 18 | while i < kinds.len() { 19 | res |= mask(kinds[i]); 20 | i += 1; 21 | } 22 | TokenSet(res) 23 | } 24 | 25 | pub(crate) const fn union(self, other: TokenSet) -> TokenSet { 26 | TokenSet(self.0 | other.0) 27 | } 28 | 29 | pub(crate) const fn contains(&self, kind: SyntaxKind) -> bool { 30 | self.0 & mask(kind) != 0 31 | } 32 | } 33 | 34 | const fn mask(kind: SyntaxKind) -> u128 { 35 | 1u128 << (kind as usize) 36 | } 37 | 38 | #[test] 39 | fn token_set_works_for_tokens() { 40 | use crate::SyntaxKind::*; 41 | let ts = TokenSet::new(&[EOF, COMMENT]); 42 | assert!(ts.contains(EOF)); 43 | assert!(ts.contains(COMMENT)); 44 | assert!(!ts.contains(PLUS)); 45 | } 46 | -------------------------------------------------------------------------------- /crates/oq3_semantics/.gitignore: -------------------------------------------------------------------------------- 1 | examples/qasm/ 2 | -------------------------------------------------------------------------------- /crates/oq3_semantics/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "oq3_semantics" 3 | description = "AST with semantic information for OpenQASM 3 parser/analyzer" 4 | version.workspace = true 5 | edition.workspace = true 6 | rust-version.workspace = true 7 | license.workspace = true 8 | authors.workspace = true 9 | readme.workspace = true 10 | keywords.workspace = true 11 | categories.workspace = true 12 | repository.workspace = true 13 | 14 | [lib] 15 | doctest = false 16 | 17 | [dependencies] 18 | oq3_source_file.workspace = true 19 | oq3_syntax.workspace = true 20 | hashbrown = { version = "0.12.3" } 21 | rowan = {version = "<=0.15.15"} 22 | boolenum = "0.1" 23 | 24 | [dev-dependencies] 25 | clap = { version = "~4.4", features = ["derive"] } 26 | oq3_lexer.workspace = true 27 | oq3_parser.workspace = true 28 | -------------------------------------------------------------------------------- /crates/oq3_semantics/examples/semdemo.rs: -------------------------------------------------------------------------------- 1 | // Copyright contributors to the openqasm-parser project 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | use clap::{Parser, Subcommand}; 5 | use std::fs; 6 | use std::path::PathBuf; 7 | 8 | use oq3_lexer::{tokenize, Token}; 9 | use oq3_parser::SyntaxKind; 10 | use oq3_semantics::syntax_to_semantics; 11 | use oq3_source_file::SourceTrait; 12 | use oq3_syntax::{parse_text, GreenNode, SourceFile}; 13 | use rowan::NodeOrToken; // TODO: this can be accessed from a higher level 14 | 15 | #[derive(Parser)] 16 | #[command(name = "demotest")] 17 | #[command(about = "Demo of parser that parses and prints tokens or trees to stdout.")] 18 | #[command(long_about = " 19 | Demo of parser that parses and prints tokens or trees to stdout. 20 | 21 | Commands are `lex`, `parse-green`, `parse`, `semantic`, `semantic-string`, `semantic-pretty`. 22 | `lex` prints a stream of tokens. `parse-green` prints the green tree. `parse` prints the (syntactic) red tree. 23 | `semantic` prints the semantic abstract semantic graph (ASG). This structure includes types and resolved symbols. 24 | ")] 25 | struct Cli { 26 | #[command(subcommand)] 27 | /// This is the Cli command doc 28 | command: Option, 29 | } 30 | 31 | // `value_name` expects bare word, not flag. 32 | #[derive(Subcommand)] 33 | enum Commands { 34 | /// Parse file to ASG 35 | Semantic { 36 | #[arg(value_name = "FILENAME")] 37 | /// file name to read 38 | file_name: PathBuf, 39 | }, 40 | 41 | /// Same as `semantic`, but test the parse_from_string interface 42 | SemanticString { 43 | #[arg(value_name = "FILENAME")] 44 | /// file name to read 45 | file_name: PathBuf, 46 | }, 47 | 48 | /// Same as `semantic`, but pretty-print the ASG 49 | SemanticPretty { 50 | #[arg(value_name = "FILENAME")] 51 | /// file name to read 52 | file_name: PathBuf, 53 | }, 54 | 55 | /// Parse file to `SyntaxNode` 56 | Parse { 57 | #[arg(value_name = "FILENAME")] 58 | /// file name to read 59 | file_name: PathBuf, 60 | }, 61 | 62 | /// Parse file to `GreenNode` 63 | ParseGreen { 64 | #[arg(value_name = "FILENAME")] 65 | file_name: PathBuf, 66 | }, 67 | 68 | /// Lex file to `Token`s 69 | Lex { 70 | #[arg(value_name = "FILENAME")] 71 | file_name: PathBuf, 72 | }, 73 | } 74 | 75 | fn main() { 76 | let cli = Cli::parse(); 77 | 78 | // You can check for the existence of subcommands, and if found use their 79 | // matches just as you would the top level cmd 80 | match &cli.command { 81 | Some(Commands::SemanticString { file_name }) => { 82 | let source = read_example_source(file_name); 83 | let file_name = Some("giraffe"); 84 | let result = 85 | syntax_to_semantics::parse_source_string(source, file_name, None::<&[PathBuf]>); 86 | if result.any_errors() { 87 | result.print_errors(); 88 | } 89 | result.program().print_asg_debug(); 90 | } 91 | 92 | #[allow(clippy::dbg_macro)] 93 | Some(Commands::Semantic { file_name }) => { 94 | let result = syntax_to_semantics::parse_source_file(file_name, None::<&[PathBuf]>); 95 | if result.any_errors() { 96 | println!("Found errors:"); 97 | result.print_errors(); 98 | } else { 99 | println!("No errors found."); 100 | } 101 | println!("{} statements in program:", result.program().len()); 102 | result.program().print_asg_debug(); 103 | dbg!(oq3_semantics::validate::count_symbol_errors( 104 | result.program(), 105 | result.symbol_table() 106 | )); 107 | // result.take_context().symbol_table().dump(); 108 | } 109 | 110 | Some(Commands::SemanticPretty { file_name }) => { 111 | let result = syntax_to_semantics::parse_source_file(file_name, None::<&[PathBuf]>); 112 | if result.any_errors() { 113 | println!("Found errors:"); 114 | result.print_errors(); 115 | } else { 116 | println!("No errors found."); 117 | } 118 | result.print_errors(); 119 | result.program().print_asg_debug_pretty(); 120 | } 121 | 122 | Some(Commands::Parse { file_name }) => { 123 | let parsed_source = oq3_source_file::parse_source_file(file_name, None::<&[PathBuf]>); 124 | let ast = parsed_source.syntax_ast(); 125 | let num_stmts = if ast.have_parse() { 126 | ast.tree().statements().count() 127 | } else { 128 | 0 129 | }; 130 | println!("Found {num_stmts} stmts"); 131 | let syntax_errors = ast.errors(); 132 | println!( 133 | "Found {} parse errors:\n{:?}\n", 134 | syntax_errors.len(), 135 | syntax_errors 136 | ); 137 | if ast.have_parse() { 138 | print_tree(ast.tree()); 139 | } 140 | } 141 | 142 | Some(Commands::ParseGreen { file_name }) => { 143 | let (green_node, syntax_errors) = parse_text(&read_example_source(file_name)); 144 | println!("{:?}", green_node); 145 | println!("{:?}", green_node.kind()); 146 | print_node_or_token(green_node, 0); 147 | println!( 148 | "\nFound {} parse errors:\n{:?}", 149 | syntax_errors.len(), 150 | syntax_errors 151 | ); 152 | } 153 | 154 | Some(Commands::Lex { file_name }) => { 155 | let tokens: Vec = tokenize(&read_example_source(file_name)).collect(); 156 | for tok in tokens { 157 | println!("{:?}", tok); 158 | } 159 | } 160 | 161 | None => { 162 | // TODO should print usage here. 163 | println!("Commands are semantic, parse, parse-green, and lex") 164 | } 165 | } 166 | } 167 | 168 | fn read_example_source(file_path: &PathBuf) -> String { 169 | fs::read_to_string(file_path.clone()) 170 | .unwrap_or_else(|_| panic!("Unable to read file {:?}", file_path)) 171 | } 172 | 173 | fn print_tree(file: SourceFile) { 174 | use oq3_syntax::ast::AstNode; 175 | for item in file.syntax().descendants() { 176 | println!("{:?}: {:}", item, item); 177 | } 178 | } 179 | 180 | fn print_node_or_token(item: GreenNode, depth: usize) { 181 | let spcs = " ".repeat(depth); 182 | for child in item.children() { 183 | // println!("{}{}: {} : {:?}", spcs, i, child, child); 184 | match child { 185 | NodeOrToken::Node(node) => { 186 | print_node_or_token(node.to_owned(), depth + 1); 187 | } 188 | NodeOrToken::Token(token) => { 189 | let sk = SyntaxKind::from(token.kind().0); 190 | println!("{} {:?} {:?}", spcs, sk, token.text()); 191 | } 192 | }; 193 | } 194 | println!("{}<", spcs); 195 | } 196 | -------------------------------------------------------------------------------- /crates/oq3_semantics/src/context.rs: -------------------------------------------------------------------------------- 1 | // Copyright contributors to the openqasm-parser project 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | use crate::asg; 5 | use crate::semantic_error::SemanticErrorKind::*; 6 | use crate::semantic_error::{SemanticErrorKind, SemanticErrorList}; 7 | use crate::symbols::{SymbolIdResult, SymbolRecordResult, SymbolTable}; 8 | use crate::types::Type; 9 | use oq3_syntax::ast::AstNode; 10 | use std::path::PathBuf; 11 | 12 | #[derive(Clone, Debug)] 13 | pub struct Context { 14 | pub program: asg::Program, 15 | pub semantic_errors: SemanticErrorList, 16 | pub symbol_table: SymbolTable, 17 | pub annotations: Vec, 18 | } 19 | 20 | impl Context { 21 | pub(crate) fn new(file_path: PathBuf) -> Context { 22 | Context { 23 | program: asg::Program::new(), 24 | semantic_errors: SemanticErrorList::new(file_path), 25 | symbol_table: SymbolTable::new(), 26 | annotations: Vec::::new(), 27 | } 28 | } 29 | 30 | pub fn push_included(&mut self, errors: SemanticErrorList) { 31 | self.semantic_errors.push_included(errors); 32 | } 33 | 34 | pub fn errors(&self) -> &SemanticErrorList { 35 | &self.semantic_errors 36 | } 37 | 38 | pub fn program(&self) -> &asg::Program { 39 | &self.program 40 | } 41 | 42 | pub fn symbol_table(&self) -> &SymbolTable { 43 | &self.symbol_table 44 | } 45 | 46 | // `SymbolTable::standard_library_gates()` returns a vector of 47 | // all names that were already bound. We record a redeclaration error 48 | // for each of these. The caller of the present method should pass 49 | // the node corresponding to `include "stdgates.inc"`. This is the 50 | // best we can do since no real file has been included. 51 | /// Define gates in the standard library. 52 | pub fn standard_library_gates(&mut self, node: &T) 53 | where 54 | T: AstNode, 55 | { 56 | self.symbol_table 57 | .standard_library_gates() 58 | .into_iter() 59 | .map(|name| { 60 | self.semantic_errors 61 | .insert(RedeclarationError(name.to_string()), node); 62 | }) 63 | .for_each(drop); 64 | } 65 | 66 | pub fn as_tuple(self) -> (asg::Program, SemanticErrorList, SymbolTable) { 67 | (self.program, self.semantic_errors, self.symbol_table) 68 | } 69 | 70 | pub fn push_annotation(&mut self, annotation: asg::Annotation) { 71 | self.annotations.push(annotation); 72 | } 73 | 74 | pub fn clear_annotations(&mut self) { 75 | self.annotations.clear(); 76 | } 77 | 78 | pub fn annotations_is_empty(&self) -> bool { 79 | self.annotations.is_empty() 80 | } 81 | 82 | pub fn take_annotations(&mut self) -> Vec { 83 | let outvec = self.annotations.clone(); 84 | self.annotations.clear(); 85 | outvec 86 | } 87 | 88 | // fn as_tuple_mut(&mut self) -> (&mut asg::Program, &mut SemanticErrorList, &mut SymbolTable) { 89 | // (&mut self.program, &mut self.semantic_errors, &mut self.symbol_table) 90 | // } 91 | 92 | pub fn insert_error(&mut self, error_kind: SemanticErrorKind, node: &T) 93 | where 94 | T: AstNode, 95 | { 96 | self.semantic_errors.insert(error_kind, node); 97 | } 98 | 99 | /// Lookup the symbol, returing a SymbolRecordResult. Possibly log a `UndefVarError`. 100 | pub(crate) fn lookup_symbol(&mut self, name: &str, node: &T) -> SymbolRecordResult 101 | where 102 | T: AstNode, 103 | { 104 | let symbol_record = self.symbol_table.lookup(name); 105 | if symbol_record.is_err() { 106 | self.semantic_errors.insert(UndefVarError, node); 107 | } 108 | symbol_record 109 | } 110 | 111 | /// Lookup the gate symbol, returing a SymbolRecordResult. Possibly log a `UndefGateError`. 112 | pub(crate) fn lookup_gate_symbol(&mut self, name: &str, node: &T) -> SymbolRecordResult 113 | where 114 | T: AstNode, 115 | { 116 | let symbol_record = self.symbol_table.lookup(name); 117 | if symbol_record.is_err() { 118 | self.semantic_errors.insert(UndefGateError, node); 119 | } 120 | symbol_record 121 | } 122 | 123 | /// Bind `name` to new Symbol, returning SymbolIdResult. Possibly log a `RedeclarationError`. 124 | pub(crate) fn new_binding(&mut self, name: &str, typ: &Type, node: &T) -> SymbolIdResult 125 | where 126 | T: AstNode, 127 | { 128 | // let symbol_id_result = self.symbol_table.new_binding(name, typ, node.syntax()); 129 | let symbol_id_result = self.symbol_table.new_binding(name, typ); 130 | if symbol_id_result.is_err() { 131 | self.semantic_errors 132 | .insert(RedeclarationError(name.to_string()), node); 133 | } 134 | symbol_id_result 135 | } 136 | } 137 | 138 | #[macro_export] 139 | macro_rules! with_scope { 140 | ($val:expr) => { 2 }; 141 | ($ctxt:ident, $scope:path, $($code:stmt);+ $(;)?) => { 142 | $ctxt.symbol_table.enter_scope($scope); 143 | $($code)+ 144 | $ctxt.symbol_table.exit_scope(); 145 | }; 146 | 147 | ($ctxt:ident, $scope:path, $code:block) => { 148 | $ctxt.symbol_table.enter_scope($scope); 149 | $code; 150 | $ctxt.symbol_table.exit_scope(); 151 | }; 152 | } 153 | -------------------------------------------------------------------------------- /crates/oq3_semantics/src/display.rs: -------------------------------------------------------------------------------- 1 | // Copyright contributors to the openqasm-parser project 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Unused at the moment. 5 | // This intent here is to print something like source code. 6 | // But this involves a ton of boiler plate. 7 | 8 | use std::fmt; 9 | use crate::asg::*; 10 | 11 | impl fmt::Display for Include { 12 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 13 | write!(f, "include \"{}\"", self.file_path()) 14 | } 15 | } 16 | 17 | impl fmt::Display for Stmt { 18 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 19 | match self { 20 | Stmt::Include(include) => { 21 | write!(f, "{}", include); 22 | } 23 | _ => () 24 | } 25 | write!(f, ";") 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /crates/oq3_semantics/src/initialize_ast.rs: -------------------------------------------------------------------------------- 1 | // Copyright contributors to the openqasm-parser project 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | use crate::asg; 5 | use crate::symbols; 6 | -------------------------------------------------------------------------------- /crates/oq3_semantics/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright contributors to the openqasm-parser project 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //! Abstract Semantic Graph (ASG) 5 | //! This crate implements an abstract semantic graph (ASG) for the OpenQASM 3 language. 6 | //! Currently the semantic information encoded in this ASG includes: 7 | //! All identifiers are resolved to (scoped) symbols. 8 | //! All expressions are annotated with a type. 9 | //! 10 | //! This kind of structure is often refered to as something like an AST decorated with semantic 11 | //! information, even though it's not really a tree, but rather a directed acyclic graph. We use the 12 | //! acronym ASG here not to be pedantic but rather to have an easy and succinct way to distinguish 13 | //! the output of syntactic analysis from output of semantic analysis. 14 | 15 | // This code is littered with FIXME. This doesn't always mean something needs to be FIXED. 16 | // All FIXME's should be turned into external Issues (ie on GH) or just removed if obsolete. 17 | 18 | // Organization of API 19 | // We opt for adding one level of hierarchy to the API by using file structure. 20 | // This is useful for example because one might want to manipulate the ASG (in asg.rs), 21 | // but not need to construct the ASG from the output of the parser (in syntax_to_semantics.rs) 22 | // Whether types and symbols should be separate is a bit less clear. 23 | // An alternative is to make these modules private and introduce new modules here in lib.rs 24 | // that only `use` things from the file-level modules. 25 | 26 | pub mod asg; 27 | pub mod context; 28 | pub mod semantic_error; 29 | pub mod symbols; 30 | pub mod syntax_to_semantics; 31 | pub mod types; 32 | pub mod validate; 33 | 34 | pub use rowan::{TextRange, TextSize}; 35 | 36 | // mod display; 37 | 38 | mod utils; 39 | -------------------------------------------------------------------------------- /crates/oq3_semantics/src/semantic_error.rs: -------------------------------------------------------------------------------- 1 | // Copyright contributors to the openqasm-parser project 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // This is copied and modified from rust-analyzer syntax_error.rs 5 | use oq3_source_file; 6 | use oq3_source_file::ErrorTrait; 7 | use oq3_syntax::AstNode; 8 | use oq3_syntax::SyntaxNode; 9 | use std::fmt; 10 | use std::path::{Path, PathBuf}; 11 | 12 | // re-exported in lib.rs from rowan 13 | use crate::TextRange; 14 | 15 | #[derive(Clone, Debug)] 16 | pub enum SemanticErrorKind { 17 | UndefVarError, 18 | UndefGateError, 19 | RedeclarationError(String), 20 | ConstIntegerError, // need a better way to organize this kind of type error 21 | IncompatibleTypesError, 22 | IncompatibleDimensionError, 23 | TooManyIndexes, 24 | CastError, 25 | MutateConstError, 26 | NotInGlobalScopeError, 27 | IncludeNotInGlobalScopeError, 28 | ReturnInGlobalScopeError, 29 | NumGateParamsError, 30 | NumGateQubitsError, 31 | } 32 | 33 | #[derive(Clone, Debug)] 34 | pub struct SemanticError { 35 | error_kind: SemanticErrorKind, 36 | node: SyntaxNode, // Includes span and api functions to retrieve text, etc. 37 | } 38 | 39 | #[derive(Clone, Debug)] 40 | pub struct SemanticErrorList { 41 | source_file_path: PathBuf, 42 | list: Vec, 43 | include_errors: Vec, // Errors when doing `include "code.qasm"` 44 | } 45 | 46 | impl std::ops::Deref for SemanticErrorList { 47 | type Target = Vec; 48 | 49 | fn deref(&self) -> &Self::Target { 50 | &self.list 51 | } 52 | } 53 | 54 | impl ErrorTrait for SemanticError { 55 | fn message(&self) -> String { 56 | self.message() 57 | } 58 | 59 | fn range(&self) -> TextRange { 60 | self.range() 61 | } 62 | } 63 | 64 | impl SemanticErrorList { 65 | pub fn new(source_file_path: PathBuf) -> SemanticErrorList { 66 | SemanticErrorList { 67 | source_file_path, 68 | list: Vec::::new(), 69 | include_errors: Vec::::new(), 70 | } 71 | } 72 | 73 | pub fn push_included(&mut self, new_errors: SemanticErrorList) { 74 | self.include_errors.push(new_errors); 75 | } 76 | 77 | pub fn include_errors(&self) -> &Vec { 78 | &self.include_errors 79 | } 80 | 81 | pub fn source_file_path(&self) -> &PathBuf { 82 | &self.source_file_path 83 | } 84 | 85 | fn insert_error(&mut self, error: SemanticError) { 86 | self.list.push(error); 87 | } 88 | 89 | fn insert_syntax_node(&mut self, error_kind: SemanticErrorKind, node: SyntaxNode) { 90 | self.insert_error(SemanticError::new(error_kind, node)); 91 | } 92 | 93 | pub fn insert(&mut self, error_kind: SemanticErrorKind, node: &T) 94 | where 95 | T: AstNode, 96 | { 97 | self.insert_syntax_node(error_kind, node.syntax().clone()); 98 | } 99 | 100 | fn print_included_errors(&self) { 101 | for errors in &self.include_errors { 102 | errors.print_errors(); 103 | } 104 | } 105 | 106 | pub fn print_errors(&self) { 107 | oq3_source_file::print_compiler_errors(self, &self.source_file_path); 108 | self.print_included_errors(); 109 | } 110 | 111 | /// Print errors for the case that the top-level code is not associated 112 | /// with a file. For example it came from a literal string. 113 | pub fn print_errors_no_file(&self, fake_file_path: &Path, source: &str) { 114 | // print errors from top level. 115 | oq3_source_file::inner_print_compiler_errors(self, fake_file_path, source); 116 | // print (with recursion) errors from included files. 117 | self.print_included_errors(); 118 | } 119 | 120 | pub fn any_semantic_errors(&self) -> bool { 121 | if !&self.list.is_empty() { 122 | return true; 123 | } 124 | self.include_errors() 125 | .iter() 126 | .any(|inclusion| inclusion.any_semantic_errors()) 127 | } 128 | } 129 | 130 | impl SemanticError { 131 | pub fn new(error_kind: SemanticErrorKind, node: SyntaxNode) -> Self { 132 | Self { error_kind, node } 133 | } 134 | 135 | pub fn range(&self) -> TextRange { 136 | self.node.text_range() 137 | } 138 | 139 | pub fn kind(&self) -> &SemanticErrorKind { 140 | &self.error_kind 141 | } 142 | 143 | pub fn message(&self) -> String { 144 | format!("{:?}", self.error_kind) 145 | } 146 | } 147 | 148 | impl fmt::Display for SemanticError { 149 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 150 | write!( 151 | f, 152 | "{:?}: {}, {:?}", 153 | self.error_kind, 154 | self.node.text(), 155 | self.node.text_range() 156 | ) 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /crates/oq3_semantics/src/utils.rs: -------------------------------------------------------------------------------- 1 | // Copyright contributors to the openqasm-parser project 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | /// Return the name of the type of a value. 5 | pub fn type_name_of(_: T) -> &'static str { 6 | std::any::type_name::() 7 | } 8 | 9 | /// Like dbg!, but print to stdout 10 | #[macro_export] 11 | macro_rules! dbg_stdout { 12 | // NOTE: We cannot use `concat!` to make a static string as a format argument 13 | // of `println!` because `file!` could contain a `{` or 14 | // `$val` expression could be a block (`{ .. }`), in which case the `println!` 15 | // will be malformed. 16 | () => { 17 | ::std::println!("[{}:{}]", ::std::file!(), ::std::line!()) 18 | }; 19 | ($val:expr $(,)?) => { 20 | // Use of `match` here is intentional because it affects the lifetimes 21 | // of temporaries - https://stackoverflow.com/a/48732525/1063961 22 | match $val { 23 | tmp => { 24 | ::std::println!("[{}:{}] {} = {:#?}", 25 | ::std::file!(), ::std::line!(), ::std::stringify!($val), &tmp); 26 | tmp 27 | } 28 | } 29 | }; 30 | ($($val:expr),+ $(,)?) => { 31 | ($(::std::dbg_stdout!($val)),+,) 32 | }; 33 | } 34 | -------------------------------------------------------------------------------- /crates/oq3_semantics/src/validate.rs: -------------------------------------------------------------------------------- 1 | // Copyright contributors to the openqasm-parser project 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | use crate::asg::{Expr, Program, Stmt, TExpr}; 5 | use crate::symbols::{SymbolIdResult, SymbolTable}; 6 | 7 | // Thus far, everything below is meant to apply any function 8 | // FnMut(&SymbolIdResult) to all possible places in the ASG. 9 | 10 | // FIXME: use more traits to generalize this code to 11 | // walk for more things than just SymbolIdResult. 12 | // I tried to do this, but was stopped by lack of sealed traits. 13 | // Tried all the tricks I could find online. No luck. 14 | 15 | // This struct is used to apply `func` to all `SymbolIdResult` in the ASG. 16 | // We intend that T : FnMut(&SymbolIdResult), but the bound is not needed here. 17 | // We want the `FnMut` in the trait bound so that the compiler 18 | // knows the function at compile time and can optimize. 19 | #[allow(unused)] 20 | struct SymContext<'a, T> { 21 | // : FnMut(&SymbolIdResult)> { 22 | func: T, 23 | symtab: &'a SymbolTable, 24 | } 25 | 26 | trait WalkSymbols { 27 | fn walk_symbols(&self, context: &mut SymContext); 28 | } 29 | 30 | // Trick to emulate an alias for a trait bound 31 | // This is like: 32 | // type SymTrait = FnMut(&SymbolIdResult); 33 | trait SymTrait: FnMut(&SymbolIdResult) {} 34 | impl SymTrait for T where T: FnMut(&SymbolIdResult) {} 35 | 36 | impl WalkSymbols for Program { 37 | fn walk_symbols(&self, context: &mut SymContext) { 38 | self.stmts().walk_symbols(context); 39 | } 40 | } 41 | 42 | impl WalkSymbols for &[V] 43 | where 44 | V: WalkSymbols, 45 | { 46 | fn walk_symbols(&self, context: &mut SymContext) { 47 | self.iter().for_each(|s| s.walk_symbols(context)); 48 | } 49 | } 50 | 51 | impl WalkSymbols for Option 52 | where 53 | V: WalkSymbols, 54 | { 55 | fn walk_symbols(&self, context: &mut SymContext) { 56 | if self.is_some() { 57 | self.as_ref().unwrap().walk_symbols(context); 58 | } 59 | } 60 | } 61 | 62 | impl WalkSymbols for &Box 63 | where 64 | V: WalkSymbols, 65 | { 66 | fn walk_symbols(&self, context: &mut SymContext) { 67 | self.as_ref().walk_symbols(context); 68 | } 69 | } 70 | 71 | impl WalkSymbols for &TExpr { 72 | fn walk_symbols(&self, context: &mut SymContext) { 73 | self.expression().walk_symbols(context); 74 | } 75 | } 76 | 77 | // The impl's for Stmt and Expr are the only two that actually apply 78 | // the function 79 | impl WalkSymbols for Stmt { 80 | fn walk_symbols(&self, context: &mut SymContext) { 81 | match self { 82 | Stmt::DeclareClassical(decl) => { 83 | (context.func)(decl.name()); 84 | decl.initializer().walk_symbols(context); 85 | } 86 | Stmt::DeclareQuantum(decl) => { 87 | (context.func)(decl.name()); 88 | } 89 | Stmt::Assignment(assign) => { 90 | assign.rvalue().walk_symbols(context); 91 | } 92 | _ => (), 93 | } 94 | } 95 | } 96 | 97 | impl WalkSymbols for Expr { 98 | fn walk_symbols(&self, context: &mut SymContext) { 99 | if let Expr::Identifier(sym) = self { 100 | (context.func)(sym) 101 | } 102 | } 103 | } 104 | 105 | pub fn count_symbol_errors(program: &Program, symtab: &SymbolTable) -> usize { 106 | let mut okcount = 0; 107 | let mut context = SymContext { 108 | func: |x: &SymbolIdResult| { 109 | if x.is_err() { 110 | okcount += 1 111 | } 112 | }, 113 | symtab, 114 | }; 115 | program.walk_symbols(&mut context); 116 | okcount 117 | } 118 | -------------------------------------------------------------------------------- /crates/oq3_semantics/tests/ast_tests.rs: -------------------------------------------------------------------------------- 1 | // Copyright contributors to the openqasm-parser project 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | use oq3_semantics::asg; 5 | use oq3_semantics::symbols; 6 | use oq3_semantics::types; 7 | 8 | const NUM_BUILTIN_CONSTS: usize = 7; 9 | 10 | // 11 | // TExpr 12 | // 13 | 14 | #[test] 15 | fn test_texpr_bool_literal() { 16 | use asg::BoolLiteral; 17 | use types::{IsConst, Type}; 18 | 19 | let bool_value = true; 20 | let literal = BoolLiteral::new(bool_value); 21 | let texpr = literal.clone().to_texpr(); 22 | assert_eq!(texpr.expression(), &literal.to_expr()); 23 | assert_eq!(texpr.get_type(), &Type::Bool(IsConst::True)); 24 | } 25 | 26 | #[test] 27 | fn test_texpr_int_literal() { 28 | use asg::IntLiteral; 29 | use types::{IsConst, Type}; 30 | 31 | let literal = IntLiteral::new(1_u32, true); 32 | let texpr = literal.clone().to_texpr(); 33 | assert_eq!(texpr.expression(), &literal.to_expr()); 34 | assert_eq!(texpr.get_type(), &Type::Int(Some(128), IsConst::True)); 35 | } 36 | 37 | // 38 | // Structs representing expressions in ASG 39 | // 40 | 41 | // 42 | // Literal 43 | // 44 | 45 | #[test] 46 | fn test_int_literal() { 47 | use asg::IntLiteral; 48 | 49 | let int_value = 42; 50 | let literal = IntLiteral::new(int_value as u32, true); 51 | assert_eq!(*literal.value(), int_value as u128); 52 | } 53 | 54 | #[test] 55 | fn test_bitstring_literal() { 56 | use asg::BitStringLiteral; 57 | // use types::{Type, IsConst}; 58 | 59 | let bit_string_value = "10110"; 60 | let literal = BitStringLiteral::new(bit_string_value.to_string()); 61 | assert_eq!(literal.value(), bit_string_value); 62 | let texpr = literal.clone().to_texpr(); 63 | assert_eq!(texpr.expression(), &literal.clone().to_expr()); 64 | // assert_eq!(texpr.get_type(), &Type::Bit(Some(5), IsConst::True)); 65 | assert_eq!(texpr.expression(), &literal.to_expr()); 66 | } 67 | 68 | // 69 | // Cast 70 | // 71 | 72 | #[test] 73 | fn test_cast() { 74 | use asg::{Cast, IntLiteral}; 75 | use types::{IsConst, Type}; 76 | 77 | let typ = Type::Int(Some(32), IsConst::True); 78 | let literal = IntLiteral::new(1_u64, true).to_texpr(); 79 | let cast = Cast::new(literal.clone(), typ.clone()); 80 | assert_eq!(cast.get_type(), &typ); 81 | assert_eq!(cast.operand(), &literal); 82 | } 83 | 84 | #[test] 85 | fn test_declaration() { 86 | // use asg::{ClassicalDeclaration}; 87 | use symbols::SymbolTable; 88 | use types::{IsConst, Type}; 89 | 90 | let mut table = SymbolTable::new(); 91 | let x = table.new_binding("x", &Type::Bool(IsConst::False)); 92 | assert!(x.is_ok()); 93 | assert_eq!(table.len_current_scope(), 1 + NUM_BUILTIN_CONSTS); 94 | let result = table.lookup("x"); 95 | assert!(result.is_ok()); 96 | assert_eq!(result.unwrap().symbol_id(), x.unwrap()); 97 | } 98 | 99 | #[test] 100 | fn test_ident() { 101 | // use asg::{Identifier}; 102 | // use symbols::{SymbolTable}; 103 | // let table = SymbolTable::new(); 104 | } 105 | 106 | #[test] 107 | fn test_binary_expr_add() { 108 | // use asg::{BinaryExpr, Identifier}; 109 | // use asg::{BinaryOp::*}; 110 | // let expr = BinaryExpr::new(Add, 111 | } 112 | 113 | #[test] 114 | fn test_annotation() { 115 | let a = asg::Annotation::new("ann details".to_string()); 116 | assert_eq!(a.annotation_text(), "ann details"); 117 | } 118 | -------------------------------------------------------------------------------- /crates/oq3_semantics/tests/spec.rs: -------------------------------------------------------------------------------- 1 | use oq3_semantics::asg; 2 | use oq3_semantics::semantic_error::SemanticErrorList; 3 | use oq3_semantics::symbols::SymbolTable; 4 | use oq3_semantics::syntax_to_semantics::parse_source_string; 5 | 6 | // This file tests code in the written OpenQASM 3 spec. 7 | // 8 | // The commit from which the example code was taken is written above each test. 9 | // 10 | // Tests that cause the parser to panic are commented out. 11 | // 12 | // Tests of unimplemented features are marked "Not supported". Note that if possible 13 | // these tests are designed to pass. The 14 | 15 | fn parse_string(code: &str) -> (asg::Program, SemanticErrorList, SymbolTable) { 16 | parse_source_string(code, None, None::<&[&std::path::Path]>) 17 | .take_context() 18 | .as_tuple() 19 | } 20 | 21 | // 22 | // comments.rst 23 | // 24 | 25 | // 17dedf 26 | #[test] 27 | fn test_spec_comments_1() { 28 | let code = r#" 29 | // A comment line 30 | 31 | /* 32 | A comment block 33 | */ 34 | "#; 35 | let (program, errors, _symbol_table) = parse_string(code); 36 | assert!(errors.is_empty()); 37 | assert!(program.is_empty()); 38 | } 39 | 40 | // 17dedf 41 | // NOTE! Two problems here. One: In this version of the spec, 42 | // the file is stdgates.qasm rather than the correct stdgates.inc 43 | // Two: This parser panics rather than gracefully recording an error 44 | // if an included file is not found. 45 | // There is a PR open to fix the spec https://github.com/openqasm/openqasm/pull/523 46 | // In the meantime, I took the liberty of changing the code in the spec and testing that 47 | // instead. 48 | // Note that neither of the non-comment lines is translated to a statement, so the 49 | // program is still empty. 50 | #[test] 51 | fn test_spec_comments_2() { 52 | let code = r#" 53 | // First non-comment is a version string 54 | OPENQASM 3.0; 55 | 56 | include "stdgates.inc"; 57 | 58 | // Rest of QASM program 59 | "#; 60 | let (program, errors, _symbol_table) = parse_string(code); 61 | assert!(errors.is_empty()); 62 | assert!(program.is_empty()); 63 | } 64 | 65 | // 66 | // types.rst 67 | // 68 | 69 | // 17dedf 70 | #[test] 71 | fn test_spec_types_1() { 72 | let code = r#" 73 | qubit q0; 74 | qubit q1; 75 | qubit q2; 76 | 77 | // and to declare a set of classical variables 78 | 79 | int[32] a; 80 | float[32] b = 5.5; 81 | bit[3] c; 82 | bool my_bool = false; 83 | "#; 84 | let (program, errors, _symbol_table) = parse_string(code); 85 | assert!(errors.is_empty()); 86 | assert_eq!(program.len(), 7); 87 | } 88 | 89 | // 17dedf 90 | // Not supported 91 | // Issue #211 92 | // #[test] 93 | // fn test_spec_types_2() { 94 | // let code = r#" 95 | // // Valid statements 96 | 97 | // include "stdgates.inc"; 98 | 99 | // qubit[5] q1; 100 | // const uint SIZE = 4; 101 | // uint runtime_u = 2; 102 | // qubit[SIZE] q2; // Declare a 4-qubit register. 103 | 104 | // x q1[0]; 105 | // z q2[SIZE - 2]; // The index operand is of type `const uint`. 106 | 107 | // // Validity is implementation-defined. 108 | 109 | // x q1[runtime_u]; 110 | // // Indexing with a value with a non-`const` type (`uint`, in this case) is 111 | // // not guaranteed to be supported. 112 | // "#; 113 | // let (_program, errors, _symbol_table) = parse_string(code); 114 | // assert_eq!(errors.len(), 0); 115 | // } 116 | 117 | // 17dedf 118 | #[test] 119 | fn test_spec_types_3() { 120 | let code = r#" 121 | // Declare a qubit 122 | qubit gamma; 123 | // Declare a qubit with a Unicode name 124 | qubit γ; 125 | // Declare a qubit register with 20 qubits 126 | qubit[20] qubit_array; 127 | "#; 128 | let (program, errors, _symbol_table) = parse_string(code); 129 | assert!(errors.is_empty()); 130 | assert_eq!(program.len(), 3); 131 | } 132 | 133 | // 17dedf 134 | #[test] 135 | fn test_spec_types_4() { 136 | let code = r#" 137 | // Not in spec code block 138 | include "stdgates.inc"; 139 | 140 | // CNOT gate between physical qubits 0 and 1 141 | CX $0, $1; 142 | "#; 143 | let (program, errors, _symbol_table) = parse_string(code); 144 | assert!(errors.is_empty()); 145 | assert_eq!(program.len(), 1); 146 | } 147 | 148 | // 17dedf 149 | // Bug 150 | // Incorrect syntax error recorded. 151 | // Not supported in the semantic analysis. 152 | // Issue #210 153 | #[test] 154 | fn test_spec_types_5() { 155 | let code = r#" 156 | defcal h $0 { } 157 | "#; 158 | let (program, errors, _symbol_table) = parse_string(code); 159 | assert!(errors.is_empty()); 160 | assert_eq!(program.len(), 0); 161 | } 162 | 163 | // 17dedf 164 | #[test] 165 | fn test_spec_types_6() { 166 | let code = r#" 167 | // Declare a register of 20 bits 168 | bit[20] bit_array; 169 | // Declare and assign a register of bits with decimal value of 15 170 | bit[8] name = "00001111"; 171 | "#; 172 | let (program, errors, _symbol_table) = parse_string(code); 173 | assert!(errors.is_empty()); 174 | assert_eq!(program.len(), 2); 175 | } 176 | 177 | // 17dedf 178 | #[test] 179 | fn test_spec_types_7() { 180 | let code = r#" 181 | // Declare a 32-bit unsigned integer 182 | uint[32] my_uint = 10; 183 | // Declare a 16 bit signed integer 184 | int[16] my_int; 185 | my_int = int[16](my_uint); 186 | // Declare a machine-sized integer 187 | int my_machine_int; 188 | "#; 189 | let (program, errors, _symbol_table) = parse_string(code); 190 | assert!(errors.is_empty()); 191 | assert_eq!(program.len(), 4); 192 | } 193 | 194 | // 17dedf 195 | #[test] 196 | fn test_spec_types_8() { 197 | let code = r#" 198 | // Declare a single-precision 32-bit float 199 | float[32] my_float = π; 200 | // Declare a machine-precision float. 201 | float my_machine_float = 2.3; 202 | "#; 203 | let (program, errors, _symbol_table) = parse_string(code); 204 | assert!(errors.is_empty()); 205 | assert_eq!(program.len(), 2); 206 | } 207 | -------------------------------------------------------------------------------- /crates/oq3_semantics/tests/symbol_tests.rs: -------------------------------------------------------------------------------- 1 | // Copyright contributors to the openqasm-parser project 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | use oq3_semantics::symbols; 5 | use oq3_semantics::types; 6 | 7 | const NUM_BUILTIN_CONSTS: usize = 7; 8 | 9 | // 10 | // Test API of symbols and symbol tables 11 | // 12 | 13 | #[test] 14 | fn test_symbol_table_create() { 15 | use symbols::SymbolTable; 16 | 17 | let table = SymbolTable::new(); 18 | assert_eq!(table.len_current_scope(), NUM_BUILTIN_CONSTS); 19 | let result = table.lookup("x"); 20 | assert!(result.is_err()); 21 | } 22 | 23 | #[test] 24 | fn test_symbol_table_bind() { 25 | use symbols::SymbolTable; 26 | use types::{IsConst, Type}; 27 | 28 | let mut table = SymbolTable::new(); 29 | let symbol_name = "x"; 30 | let x = table.new_binding(symbol_name, &Type::Bool(IsConst::False)); 31 | assert!(x.is_ok()); 32 | assert_eq!(table.len_current_scope(), 1 + NUM_BUILTIN_CONSTS); 33 | let result = table.lookup(symbol_name); 34 | assert!(result.is_ok()); 35 | assert_eq!(result.unwrap().symbol_id(), x.unwrap()); 36 | } 37 | -------------------------------------------------------------------------------- /crates/oq3_semantics/tests/types_test.rs: -------------------------------------------------------------------------------- 1 | // Copyright contributors to the openqasm-parser project 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | use oq3_semantics::types; 5 | 6 | // 7 | // Types 8 | // 9 | 10 | #[test] 11 | fn test_int_type_const() { 12 | use types::{IsConst, Type}; 13 | 14 | let typ = Type::Int(Some(32), IsConst::True); 15 | assert_eq!(typ.width(), Some(32)); 16 | assert!(typ.is_scalar()); 17 | assert!(typ.is_const()); 18 | assert!(!typ.is_quantum()); 19 | } 20 | 21 | #[test] 22 | fn test_int_type_not_const() { 23 | use types::{IsConst, Type}; 24 | 25 | let typ = Type::Int(Some(32), IsConst::False); 26 | assert_eq!(typ.width(), Some(32)); 27 | assert!(typ.is_scalar()); 28 | assert!(!typ.is_const()); 29 | assert!(!typ.is_quantum()); 30 | } 31 | 32 | #[test] 33 | fn test_int_type_no_width() { 34 | use types::{IsConst, Type}; 35 | 36 | let typ = Type::Int(None, IsConst::False); // No width 37 | assert!(typ.width().is_none()); 38 | assert!(typ.is_scalar()); 39 | assert!(!typ.is_const()); 40 | assert!(!typ.is_quantum()); 41 | } 42 | 43 | #[test] 44 | fn test_qubit_type_single_qubit() { 45 | use types::Type; 46 | 47 | let typ = Type::Qubit; 48 | assert!(typ.width().is_none()); 49 | assert!(!typ.is_scalar()); 50 | assert!(typ.is_const()); 51 | assert!(typ.is_quantum()); 52 | } 53 | 54 | // #[test] 55 | // fn test_qubit_type_qubit_register() { 56 | // use types::{Type}; 57 | 58 | // let width = 100; 59 | // let typ = Type::Qubit(Some(width)); 60 | // assert_eq!(typ.width(), Some(width)); 61 | // assert_eq!(typ.is_scalar(), false); 62 | // assert_eq!(typ.is_const(), false); 63 | // assert_eq!(typ.is_quantum(), true); 64 | // } 65 | -------------------------------------------------------------------------------- /crates/oq3_source_file/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "oq3_source_file" 3 | description = "Manage parsing source files, included files, and reporting diagnotics OpenQASM 3 parser/analyzer" 4 | version.workspace = true 5 | edition.workspace = true 6 | rust-version.workspace = true 7 | license.workspace = true 8 | authors.workspace = true 9 | readme.workspace = true 10 | keywords.workspace = true 11 | categories.workspace = true 12 | repository.workspace = true 13 | 14 | [lib] 15 | doctest = false 16 | 17 | [dependencies] 18 | ariadne = { version = "0.3.0", features = ["auto-color"] } 19 | oq3_syntax.workspace = true 20 | -------------------------------------------------------------------------------- /crates/oq3_source_file/src/api.rs: -------------------------------------------------------------------------------- 1 | // Copyright contributors to the openqasm-parser project 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // It might be nice to put these functions in lib.rs. 5 | // But they are also used in this crate, so we put them here. 6 | 7 | use ariadne::Config; 8 | use ariadne::{ColorGenerator, Label, Report, ReportKind, Source}; 9 | use std::ops::Range; 10 | use std::path::{Path, PathBuf}; 11 | 12 | // Syntactic AST 13 | 14 | use crate::source_file::{ 15 | expand_path, parse_source_and_includes, range_to_span, read_source_file, ErrorTrait, 16 | SourceFile, SourceString, 17 | }; 18 | 19 | /// Read source from `file_path` and parse to the syntactic AST. 20 | /// Parse and store included files recursively. 21 | pub fn parse_source_file(file_path: T, search_path_list: Option<&[P]>) -> SourceFile 22 | where 23 | T: AsRef, 24 | P: AsRef, 25 | { 26 | let full_path = expand_path(file_path, search_path_list); 27 | let (syntax_ast, included) = 28 | parse_source_and_includes(read_source_file(&full_path).as_str(), search_path_list); 29 | SourceFile::new(full_path, syntax_ast, included) 30 | } 31 | 32 | /// Read source from `file_path` and parse to the syntactic AST. 33 | /// Parse and store included files recursively. 34 | pub fn parse_source_string( 35 | source: T, 36 | fake_file_path: Option<&str>, 37 | search_path_list: Option<&[P]>, 38 | ) -> SourceString 39 | where 40 | T: AsRef, 41 | P: AsRef, 42 | { 43 | let source = source.as_ref(); 44 | let (syntax_ast, included) = parse_source_and_includes(source, search_path_list); 45 | let fake_file_path = PathBuf::from(fake_file_path.unwrap_or("no file")); 46 | SourceString::new(source, fake_file_path, syntax_ast, included) 47 | } 48 | 49 | /// Print compiler errors. Diagnostics include text take from `source`. 50 | /// The file `info_file_path` is only used for printing error messages. In particular, 51 | /// it does not need to correspond to an existing file. In case `info_file_path` is indeed 52 | /// the source file, then `source` is read before calling this function. 53 | pub fn inner_print_compiler_errors( 54 | errors: &[T], 55 | info_file_path: &Path, 56 | source: &str, 57 | ) { 58 | let file_path_str = info_file_path.as_os_str().to_str().unwrap(); 59 | for err in errors.iter() { 60 | let err_string = err.message(); 61 | let err_span = range_to_span(&err.range()); 62 | report_error(&err_string, &err_span, file_path_str, source); 63 | println!(); 64 | } 65 | } 66 | 67 | pub fn print_compiler_errors(errors: &[T], file_path: &Path) { 68 | // ariadne seems to want path only as &str, not PathBuf. 69 | let source = &read_source_file(file_path); 70 | inner_print_compiler_errors(errors, file_path, source); 71 | } 72 | 73 | pub fn report_error(message: &str, span: &Range, file_path: &str, source: &str) { 74 | let mut colors = ColorGenerator::new(); 75 | // Generate & choose some colours for each of our elements 76 | let a = colors.next(); 77 | // `offset` is a zero-indexed character offset from beginning of file. 78 | // Here `offset` is character index of start of error. 79 | let offset = span.start; 80 | Report::build(ReportKind::Error, file_path, offset) 81 | // .with_code(3) 82 | .with_message(message) 83 | .with_config(Config::default().with_compact(true)) 84 | .with_label( 85 | Label::new((file_path, span.clone())) 86 | .with_message("Near this point") 87 | .with_color(a), 88 | ) 89 | .finish() 90 | .print((file_path, Source::from(source))) 91 | .unwrap(); 92 | } 93 | -------------------------------------------------------------------------------- /crates/oq3_source_file/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright contributors to the openqasm-parser project 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //! Management of source files for OpenQASM 3 parsing and semantic analysis. The main `struct` here 5 | //! is `SourceFile` which contains the path to a source file and the AST produced by the parser. It 6 | //! also contains a `Vec` representing source files that are included via 7 | //! `include`. Nested includes are handled naturally this way. 8 | //! 9 | //! `report_error` formats error messages using the external crate `ariadne`. 10 | 11 | // mod error_report; 12 | mod api; 13 | mod source_file; 14 | 15 | pub use source_file::{ErrorTrait, SourceFile, SourceString, SourceTrait}; 16 | 17 | pub use api::{ 18 | inner_print_compiler_errors, parse_source_file, parse_source_string, print_compiler_errors, 19 | report_error, 20 | }; 21 | -------------------------------------------------------------------------------- /crates/oq3_source_file/src/source_file.rs: -------------------------------------------------------------------------------- 1 | // Copyright contributors to the openqasm-parser project 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | use crate::api::{inner_print_compiler_errors, parse_source_file, print_compiler_errors}; 5 | use oq3_syntax::ast as synast; // Syntactic AST 6 | use oq3_syntax::ParseOrErrors; 7 | use oq3_syntax::TextRange; 8 | use std::env; 9 | use std::fs; 10 | use std::path::{Path, PathBuf}; 11 | 12 | // I think SourceFile actually just works with the source as a string. 13 | // Knowledge of any file is not used by synast::SourceFile; 14 | pub(crate) type ParsedSource = ParseOrErrors; 15 | 16 | pub(crate) fn parse_source_and_includes>( 17 | source: &str, 18 | search_path_list: Option<&[P]>, 19 | ) -> (ParsedSource, Vec) { 20 | let parsed_source = synast::SourceFile::parse_check_lex(source); 21 | let included = if parsed_source.have_parse() { 22 | parse_included_files(&parsed_source, search_path_list) 23 | } else { 24 | Vec::::new() 25 | }; 26 | (parsed_source, included) 27 | } 28 | 29 | pub trait ErrorTrait { 30 | fn message(&self) -> String; 31 | fn range(&self) -> TextRange; 32 | } 33 | 34 | pub(crate) fn range_to_span(range: &TextRange) -> std::ops::Range { 35 | let r1: usize = range.start().into(); 36 | let r2: usize = range.end().into(); 37 | // maybe rowan and ariadne differ on def of span 38 | // In any case, for ariadne, r2 > r1 is a requirement. Often not satisfied by the r-a crates 39 | // r1..(r2+1) <--- However, this sometimes is past EOF. 40 | r1..r2 41 | } 42 | 43 | impl ErrorTrait for oq3_syntax::SyntaxError { 44 | fn message(&self) -> String { 45 | self.message().to_string() 46 | } 47 | 48 | fn range(&self) -> TextRange { 49 | self.range() 50 | } 51 | } 52 | 53 | pub trait SourceTrait { 54 | /// Return `true` if the source file or any included files produced a parse error. 55 | fn any_parse_errors(&self) -> bool { 56 | if !&self.syntax_ast().errors().is_empty() { 57 | return true; 58 | } 59 | self.included() 60 | .iter() 61 | .any(|inclusion| inclusion.any_parse_errors()) 62 | } 63 | 64 | fn included(&self) -> &Vec; 65 | fn syntax_ast(&self) -> &ParsedSource; 66 | fn print_syntax_errors(&self); 67 | fn file_path(&self) -> PathBuf; 68 | } 69 | 70 | impl SourceTrait for SourceFile { 71 | fn syntax_ast(&self) -> &ParsedSource { 72 | &self.syntax_ast 73 | } 74 | 75 | fn included(&self) -> &Vec { 76 | self.included.as_ref() 77 | } 78 | 79 | fn file_path(&self) -> PathBuf { 80 | self.file_path().clone() 81 | } 82 | 83 | fn print_syntax_errors(&self) { 84 | print_compiler_errors(self.syntax_ast().errors(), &self.file_path); 85 | for source_file in self.included().iter() { 86 | source_file.print_syntax_errors() 87 | } 88 | } 89 | } 90 | 91 | impl SourceFile { 92 | pub fn new>( 93 | file_path: F, 94 | syntax_ast: ParsedSource, 95 | included: Vec, 96 | ) -> SourceFile { 97 | let file_path = match fs::canonicalize(file_path.as_ref()) { 98 | Ok(file_path) => file_path, 99 | Err(e) => panic!("Unable to find {:?}\n{:?}", file_path.as_ref(), e), 100 | }; 101 | SourceFile { 102 | file_path, 103 | syntax_ast, 104 | included, 105 | } 106 | } 107 | 108 | pub fn file_path(&self) -> &PathBuf { 109 | &self.file_path 110 | } 111 | } 112 | 113 | pub fn search_paths() -> Option> { 114 | env::var_os("QASM3_PATH").map(|paths| env::split_paths(&paths).collect()) 115 | } 116 | 117 | /// Expand path with search paths. Return input if expansion fails. 118 | pub(crate) fn expand_path, P: AsRef>( 119 | file_path: T, 120 | search_path_list: Option<&[P]>, 121 | ) -> PathBuf { 122 | let file_path = PathBuf::from(file_path.as_ref()); 123 | if file_path.is_absolute() { 124 | return file_path; 125 | } 126 | let try_path = |dir: &Path| { 127 | let full_path = dir.join(&file_path); 128 | full_path.is_file().then_some(full_path) 129 | }; 130 | 131 | if let Some(paths) = search_path_list { 132 | for path in paths { 133 | if let Some(full_path) = try_path(path.as_ref()) { 134 | return full_path; 135 | } 136 | } 137 | } else if let Some(paths) = search_paths() { 138 | for path in paths { 139 | if let Some(full_path) = try_path(path.as_ref()) { 140 | return full_path; 141 | } 142 | } 143 | } 144 | file_path 145 | } 146 | 147 | /// Read QASM3 source file, respecting env variable `QASM3_PATH` if set. 148 | pub(crate) fn read_source_file(file_path: &Path) -> String { 149 | match fs::read_to_string(file_path) { 150 | Ok(source) => source, 151 | Err(err) => panic!( 152 | "Unable to read OpenQASM source file '{}': {}", 153 | file_path.display(), 154 | err 155 | ), 156 | } 157 | } 158 | 159 | // FIXME: prevent a file from including itself. Then there are two-file cycles, etc. 160 | /// Recursively parse any files `include`d in the program `syntax_ast`. 161 | pub(crate) fn parse_included_files>( 162 | syntax_ast: &ParsedSource, 163 | search_path_list: Option<&[P]>, 164 | ) -> Vec { 165 | syntax_ast 166 | .tree() 167 | .statements() 168 | .filter_map(|parse_stmt| match parse_stmt { 169 | synast::Stmt::Include(include) => { 170 | let file: synast::FilePath = include.file().unwrap(); 171 | let file_path = file.to_string().unwrap(); 172 | // stdgates.inc will be handled "as if" it really existed. 173 | if file_path == "stdgates.inc" { 174 | None 175 | } else { 176 | Some(parse_source_file(file_path, search_path_list)) 177 | } 178 | } 179 | _ => None, 180 | }) 181 | .collect::>() 182 | } 183 | 184 | /// Structure for managing parsing QASM from a string with no associated 185 | /// source file. `fake_file_path` contains something like "no file" and is 186 | /// present in order to make it easier to avoid duplicating code supporing 187 | /// QASM read from source files. 188 | #[derive(Clone, Debug)] 189 | pub struct SourceString { 190 | pub(crate) fake_file_path: PathBuf, // Option, // Typical name is "no file". 191 | pub(crate) source: String, 192 | pub(crate) syntax_ast: ParsedSource, 193 | pub(crate) included: Vec, 194 | } 195 | 196 | #[derive(Clone, Debug)] 197 | pub struct SourceFile { 198 | file_path: PathBuf, 199 | syntax_ast: ParsedSource, 200 | included: Vec, 201 | } 202 | 203 | impl SourceTrait for SourceString { 204 | fn syntax_ast(&self) -> &ParsedSource { 205 | &self.syntax_ast 206 | } 207 | 208 | fn included(&self) -> &Vec { 209 | self.included.as_ref() 210 | } 211 | 212 | fn file_path(&self) -> PathBuf { 213 | self.fake_file_path().clone() 214 | } 215 | 216 | fn print_syntax_errors(&self) { 217 | // Print errors from top level source. 218 | inner_print_compiler_errors( 219 | self.syntax_ast().errors(), 220 | self.fake_file_path(), 221 | self.source(), 222 | ); 223 | // Print from included source files (recursively). 224 | for source_file in self.included().iter() { 225 | source_file.print_syntax_errors() 226 | } 227 | } 228 | } 229 | 230 | impl SourceString { 231 | pub fn new, P: AsRef>( 232 | source: T, 233 | fake_file_path: P, 234 | syntax_ast: ParsedSource, 235 | included: Vec, 236 | ) -> SourceString { 237 | SourceString { 238 | source: source.as_ref().to_owned(), 239 | fake_file_path: fake_file_path.as_ref().to_owned(), 240 | syntax_ast, 241 | included, 242 | } 243 | } 244 | 245 | pub fn fake_file_path(&self) -> &PathBuf { 246 | &self.fake_file_path 247 | } 248 | 249 | pub fn source(&self) -> &str { 250 | &self.source 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /crates/oq3_syntax/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "oq3_syntax" 3 | description = "Comment and whitespace preserving parser for the OpenQASM 3 parser/analyzer" 4 | version.workspace = true 5 | edition.workspace = true 6 | rust-version.workspace = true 7 | license.workspace = true 8 | authors.workspace = true 9 | readme.workspace = true 10 | keywords.workspace = true 11 | categories.workspace = true 12 | repository.workspace = true 13 | 14 | [lib] 15 | doctest = false 16 | 17 | [dependencies] 18 | # external crates 19 | cov-mark = "2.0.0-pre.1" 20 | either = "1.7.0" 21 | indexmap = "2.0.0" 22 | itertools = "0.10.5" 23 | once_cell = "1.17.0" 24 | rowan = {version = "<=0.15.15"} 25 | rustc-hash = "1.1.0" 26 | smol_str = "0.2.0" 27 | triomphe = { version = "<= 0.1.11", default-features = false, features = ["std"] } 28 | xshell = "0.2.2" 29 | rustversion = "1.0" 30 | # local crates 31 | oq3_lexer.workspace = true 32 | oq3_parser.workspace = true 33 | 34 | [dev-dependencies] 35 | rayon = "1.6.1" 36 | expect-test = "1.4.0" 37 | proc-macro2 = "1.0.47" 38 | quote = "1.0.20" 39 | ungrammar = "1.16.1" 40 | clap = { version = "~4.4", features = ["derive"] } 41 | 42 | -------------------------------------------------------------------------------- /crates/oq3_syntax/examples/demoparse.rs: -------------------------------------------------------------------------------- 1 | // Copyright contributors to the openqasm-parser project 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | use clap::{Parser, Subcommand}; 5 | use std::fs; 6 | use std::path::PathBuf; 7 | 8 | use oq3_lexer::{tokenize, Token}; 9 | use oq3_parser::SyntaxKind; 10 | use oq3_syntax::{ast, parse_text, GreenNode, SourceFile}; 11 | use rowan::NodeOrToken; // TODO: this can be accessed from a higher level 12 | 13 | #[derive(Parser)] 14 | #[command(name = "demoparse")] 15 | #[command(about = "Demo of parser that parses and prints tokens or trees to stdout.")] 16 | #[command(long_about = " 17 | Demo of parser that parses and prints tokens or trees to stdout. 18 | 19 | Commands are `lex`, `parse`, and `parse-green`. 20 | `lex` prints a stream of tokens. `parse` prints the red tree. `parse-green` prints the green tree. 21 | ")] 22 | struct Cli { 23 | #[command(subcommand)] 24 | /// This is the Cli command doc 25 | command: Option, 26 | } 27 | 28 | // `value_name` expects bare word, not flag. 29 | #[derive(Subcommand)] 30 | enum Commands { 31 | /// Parse file to `SyntaxNode` 32 | Parse { 33 | #[arg(value_name = "FILENAME")] 34 | /// file name to read 35 | filename: String, 36 | }, 37 | 38 | /// Parse file to `GreenNode` 39 | ParseGreen { 40 | #[arg(value_name = "FILENAME")] 41 | filename: String, 42 | }, 43 | 44 | /// Lex file to `Token`s 45 | Lex { 46 | #[arg(value_name = "FILENAME")] 47 | filename: String, 48 | }, 49 | } 50 | 51 | fn main() { 52 | let cli = Cli::parse(); 53 | 54 | // You can check for the existence of subcommands, and if found use their 55 | // matches just as you would the top level cmd 56 | match &cli.command { 57 | Some(Commands::Parse { filename }) => { 58 | let parsed_source = SourceFile::parse(&read_example_source(filename)); 59 | let parse_tree: SourceFile = parsed_source.tree(); 60 | println!( 61 | "Found {} statements", 62 | parse_tree.statements().collect::>().len() 63 | ); 64 | let syntax_errors = parsed_source.errors(); 65 | println!( 66 | "Found {} parse errors:\n{:?}\n", 67 | syntax_errors.len(), 68 | syntax_errors 69 | ); 70 | print_tree(parse_tree); 71 | } 72 | 73 | Some(Commands::ParseGreen { filename }) => { 74 | let (green_node, syntax_errors) = parse_text(&read_example_source(filename)); 75 | println!("{:?}", green_node); 76 | println!("{:?}", green_node.kind()); 77 | print_node_or_token(green_node, 0); 78 | println!( 79 | "\nFound {} parse errors:\n{:?}", 80 | syntax_errors.len(), 81 | syntax_errors 82 | ); 83 | } 84 | 85 | Some(Commands::Lex { filename }) => { 86 | let tokens: Vec = tokenize(&read_example_source(filename)).collect(); 87 | for tok in tokens { 88 | println!("{:?}", tok); 89 | } 90 | } 91 | None => { 92 | // FIXME should print usage here. 93 | println!("Commands are parse, parse-green, and lex") 94 | } 95 | } 96 | } 97 | 98 | /// Construct the fqpn of an example from a filename. 99 | fn example_path(example: &str) -> PathBuf { 100 | return ["crates", "oq3_syntax", "examples", "oq3_source", example] 101 | .iter() 102 | .collect(); 103 | } 104 | 105 | fn read_example_source(file_name: &str) -> String { 106 | let file_path = example_path(file_name); 107 | fs::read_to_string(file_path.clone()) 108 | .unwrap_or_else(|_| panic!("Unable to read file {:?}", file_path)) 109 | } 110 | 111 | fn print_tree(file: SourceFile) { 112 | use ast::AstNode; 113 | for item in file.syntax().descendants() { 114 | println!("{:?}", item); 115 | } 116 | } 117 | 118 | fn print_node_or_token(item: GreenNode, depth: usize) { 119 | let spcs = " ".repeat(depth); 120 | for child in item.children() { 121 | // println!("{}{}: {} : {:?}", spcs, i, child, child); 122 | match child { 123 | NodeOrToken::Node(node) => { 124 | print_node_or_token(node.to_owned(), depth + 1); 125 | } 126 | NodeOrToken::Token(token) => { 127 | let sk = SyntaxKind::from(token.kind().0); 128 | // let sk = token.kind().0; 129 | println!("{} {:?} {:?}", spcs, sk, token.text()); 130 | } 131 | }; 132 | } 133 | println!("{}<", spcs); 134 | } 135 | -------------------------------------------------------------------------------- /crates/oq3_syntax/examples/itemparse.rs: -------------------------------------------------------------------------------- 1 | // Copyright contributors to the openqasm-parser project 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | use ast::HasName; 5 | use oq3_syntax::ast; 6 | use oq3_syntax::SourceFile; 7 | 8 | #[allow(dead_code)] 9 | fn parse_some_code() { 10 | let code = r#" 11 | int q; 12 | 13 | OPENQASM 3.1; 14 | 15 | include "stdgates.inc"; 16 | 17 | defcalgrammar "openpulse"; 18 | 19 | gate mygate q1, q2 { 20 | x q1; 21 | } 22 | 23 | defcal xmeasure(int a, int b) q, p -> bit { 24 | 1 + 1; 25 | } 26 | 27 | def myfunc(int a, int b) -> int { 28 | x = 3; 29 | } 30 | 31 | cal { 32 | x + y; 33 | } 34 | 35 | "#; 36 | parse_print_stmts(code); 37 | } 38 | 39 | fn try_int_def() { 40 | let code = r##" 41 | int[64] x = 142; 42 | "##; 43 | parse_print_stmts(code); 44 | } 45 | 46 | //use parser::syntax_kind::SyntaxKind; 47 | fn main() { 48 | // parts_testing(); 49 | try_int_def(); 50 | // parse_some_code(); 51 | } 52 | 53 | fn print_stmt(stmt: ast::Stmt) { 54 | match stmt { 55 | ast::Stmt::Gate(gate) => print_gate(gate), 56 | ast::Stmt::Def(def) => print_def(def), 57 | ast::Stmt::DefCal(defcal) => print_defcal(defcal), 58 | ast::Stmt::DefCalGrammar(defcg) => print_defcalgrammar(defcg), 59 | ast::Stmt::Cal(cal) => print_cal(cal), 60 | ast::Stmt::VersionString(version_string) => print_version_string(version_string), 61 | ast::Stmt::Include(include) => print_include(include), 62 | ast::Stmt::ClassicalDeclarationStatement(type_decl) => { 63 | print_type_declaration_statement(type_decl) 64 | } 65 | _ => { 66 | println!("unhandled stmt: {:?}", stmt) 67 | } 68 | } 69 | } 70 | 71 | fn parse_print_stmts(code: &str) { 72 | use oq3_syntax::AstNode; 73 | let parse = SourceFile::parse(code); 74 | let file: SourceFile = parse.tree(); 75 | println!( 76 | "Found {} stmts", 77 | file.statements().collect::>().len() 78 | ); 79 | for stmt in file.statements() { 80 | print!( 81 | "desc {}: ", 82 | stmt.syntax().descendants().collect::>().len() 83 | ); 84 | print_stmt(stmt.clone()); 85 | println!(); 86 | for d in stmt.syntax().descendants().collect::>() { 87 | println!(" {}", d); 88 | } 89 | println!(); 90 | } 91 | } 92 | 93 | // 94 | // Printing of each stmt 95 | // 96 | 97 | #[allow(dead_code)] 98 | fn parts_testing() { 99 | let code = r##" 100 | gate mygate q { 101 | h x; 102 | } 103 | "##; 104 | // let parse = SourceFile::parse(&code); 105 | // let file: SourceFile = parse.tree(); 106 | let file: SourceFile = SourceFile::parse(code).tree(); 107 | let mut gatestmt = None; 108 | for stmt in file.statements() { 109 | if let ast::Stmt::Gate(gate) = stmt { 110 | gatestmt = Some(gate); 111 | break; 112 | }; 113 | } 114 | println!("{}", test_gate_def(gatestmt.unwrap(), ("mygate", "q"))); 115 | } 116 | 117 | #[allow(dead_code)] 118 | fn test_gate_def(gate: ast::Gate, (name, qubit_list): (&str, &str)) -> bool { 119 | format!("{}", gate.name().unwrap()) == name 120 | && format!("{}", gate.qubit_params().unwrap()) == qubit_list 121 | } 122 | 123 | #[allow(dead_code)] 124 | fn print_type_declaration_statement(type_decl: ast::ClassicalDeclarationStatement) { 125 | println!("Type declaration"); 126 | print!(" scalar_type "); 127 | let scalar_type = type_decl.scalar_type().unwrap(); 128 | println!(" name {:?}", scalar_type); 129 | println!(" syntax {:?}", scalar_type.kind()); 130 | println!(" token {}", scalar_type.token()); 131 | println!(" token {:?}", scalar_type.token()); 132 | print!(" initial value: "); 133 | if type_decl.expr().is_some() { 134 | print!("{}", type_decl.expr().unwrap()); 135 | } else { 136 | print!(" none"); 137 | } 138 | } 139 | 140 | fn print_gate(gate: ast::Gate) { 141 | println!("Gate\ngate name: '{}'", gate.name().unwrap()); 142 | if gate.angle_params().is_some() { 143 | println!("parameters: '{}'", gate.angle_params().unwrap()); 144 | } 145 | println!("qubits: '{}'", gate.qubit_params().unwrap()); 146 | print!("body: '{}'", gate.body().unwrap()); 147 | } 148 | 149 | fn print_defcal(defcal: ast::DefCal) { 150 | println!("DefCal\ndefcal name: '{}'", defcal.name().unwrap()); 151 | if defcal.param_list().is_some() { 152 | println!("parameters: '{}'", defcal.param_list().unwrap()); 153 | } 154 | println!("qubits: '{}'", defcal.qubit_list().unwrap()); 155 | if defcal.return_signature().is_some() { 156 | println!("return type: '{}'", defcal.return_signature().unwrap()); 157 | } 158 | print!("body: '{}'", defcal.body().unwrap()); 159 | } 160 | 161 | fn print_def(def: ast::Def) { 162 | println!("Def\ndef name: '{}'", def.name().unwrap()); 163 | if def.typed_param_list().is_some() { 164 | println!("parameters: '{}'", def.typed_param_list().unwrap()); 165 | } 166 | if def.return_signature().is_some() { 167 | println!("return type: '{}'", def.return_signature().unwrap()); 168 | } 169 | print!("body: '{}'", def.body().unwrap()); 170 | } 171 | 172 | fn print_defcalgrammar(defcg: ast::DefCalGrammar) { 173 | println!( 174 | "DefCalgrammar\ndefcalgrammar token: '{}'", 175 | defcg.defcalgrammar_token().unwrap() 176 | ); 177 | if defcg.file().is_some() { 178 | print!("file: '{}'", defcg.file().unwrap()); 179 | } else { 180 | print!("file: NONE"); 181 | } 182 | } 183 | 184 | fn print_cal(cal: ast::Cal) { 185 | println!("Cal\ncal token: '{}'", cal.cal_token().unwrap()); 186 | if cal.body().is_some() { 187 | print!("body: '{}'", cal.body().unwrap()); 188 | } else { 189 | print!("body: NONE"); 190 | } 191 | } 192 | 193 | fn print_version_string(version_string: ast::VersionString) { 194 | println!( 195 | "VersionString\n openqasm_token: '{}'", 196 | version_string.OPENQASM_token().unwrap() 197 | ); 198 | if version_string.version().is_some() { 199 | print!("version: '{}'", version_string.version().unwrap()); 200 | } else { 201 | print!("version: NONE"); 202 | } 203 | } 204 | 205 | fn print_include(include: ast::Include) { 206 | println!( 207 | "Include\ninclude token: '{}'", 208 | include.include_token().unwrap() 209 | ); 210 | if include.file().is_some() { 211 | print!("file: '{}'", include.file().unwrap()); 212 | } else { 213 | print!("file: NONE"); 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /crates/oq3_syntax/src/ast.rs: -------------------------------------------------------------------------------- 1 | // Copyright contributors to the openqasm-parser project 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //! Abstract Syntax Tree, layered on top of untyped `SyntaxNode`s 5 | 6 | pub mod edit; 7 | mod expr_ext; 8 | mod generated; 9 | pub mod make; 10 | mod node_ext; 11 | mod operators; 12 | pub mod prec; 13 | mod token_ext; 14 | mod traits; 15 | mod type_ext; 16 | 17 | use std::marker::PhantomData; 18 | 19 | use either::Either; 20 | 21 | use crate::{ 22 | syntax_node::{SyntaxNode, SyntaxNodeChildren, SyntaxToken}, 23 | SyntaxKind, 24 | }; 25 | 26 | pub use self::{ 27 | expr_ext::{ArrayExprKind, ElseBranch, LiteralKind, TimeUnit}, 28 | generated::{nodes::*, tokens::*}, 29 | node_ext::HasTextName, 30 | operators::{ArithOp, BinaryOp, CmpOp, LogicOp, Ordering, RangeOp, UnaryOp}, 31 | token_ext::{CommentKind, CommentShape, IsString, QuoteOffsets, Radix}, 32 | traits::{HasArgList, HasLoopBody, HasName}, 33 | type_ext::ScalarTypeKind, // need a better name, one that does not clash 34 | }; 35 | 36 | // NOTE! "typed ast" here does not mean annotated with types in the target language. 37 | // It means that rather than encoding syntactic elements with enum variants, they 38 | // are encoded via types, i.e. `struct`s/ 39 | /// The main trait to go from untyped `SyntaxNode` to a typed ast. The 40 | /// conversion itself has zero runtime cost: ast and syntax nodes have exactly 41 | /// the same representation: a pointer to the tree root and a pointer to the 42 | /// node itself. 43 | pub trait AstNode { 44 | fn can_cast(kind: SyntaxKind) -> bool 45 | where 46 | Self: Sized; 47 | 48 | fn cast(syntax: SyntaxNode) -> Option 49 | where 50 | Self: Sized; 51 | 52 | fn syntax(&self) -> &SyntaxNode; 53 | 54 | fn clone_for_update(&self) -> Self 55 | where 56 | Self: Sized, 57 | { 58 | Self::cast(self.syntax().clone_for_update()).unwrap() 59 | } 60 | fn clone_subtree(&self) -> Self 61 | where 62 | Self: Sized, 63 | { 64 | Self::cast(self.syntax().clone_subtree()).unwrap() 65 | } 66 | } 67 | 68 | /// Like `AstNode`, but wraps tokens rather than interior nodes. 69 | pub trait AstToken { 70 | fn can_cast(token: SyntaxKind) -> bool 71 | where 72 | Self: Sized; 73 | 74 | fn cast(syntax: SyntaxToken) -> Option 75 | where 76 | Self: Sized; 77 | 78 | fn syntax(&self) -> &SyntaxToken; 79 | 80 | fn text(&self) -> &str { 81 | self.syntax().text() 82 | } 83 | } 84 | 85 | /// An iterator over `SyntaxNode` children of a particular AST type. 86 | #[derive(Debug, Clone)] 87 | pub struct AstChildren { 88 | inner: SyntaxNodeChildren, 89 | ph: PhantomData, 90 | } 91 | 92 | impl AstChildren { 93 | fn new(parent: &SyntaxNode) -> Self { 94 | AstChildren { 95 | inner: parent.children(), 96 | ph: PhantomData, 97 | } 98 | } 99 | } 100 | 101 | impl Iterator for AstChildren { 102 | type Item = N; 103 | fn next(&mut self) -> Option { 104 | self.inner.find_map(N::cast) 105 | } 106 | } 107 | 108 | impl AstNode for Either 109 | where 110 | L: AstNode, 111 | R: AstNode, 112 | { 113 | fn can_cast(kind: SyntaxKind) -> bool 114 | where 115 | Self: Sized, 116 | { 117 | L::can_cast(kind) || R::can_cast(kind) 118 | } 119 | 120 | fn cast(syntax: SyntaxNode) -> Option 121 | where 122 | Self: Sized, 123 | { 124 | if L::can_cast(syntax.kind()) { 125 | L::cast(syntax).map(Either::Left) 126 | } else { 127 | R::cast(syntax).map(Either::Right) 128 | } 129 | } 130 | 131 | fn syntax(&self) -> &SyntaxNode { 132 | self.as_ref().either(L::syntax, R::syntax) 133 | } 134 | } 135 | 136 | mod support { 137 | use super::{AstChildren, AstNode, SyntaxKind, SyntaxNode, SyntaxToken}; 138 | 139 | pub(super) fn child(parent: &SyntaxNode) -> Option { 140 | parent.children().find_map(N::cast) 141 | } 142 | 143 | pub(super) fn children(parent: &SyntaxNode) -> AstChildren { 144 | AstChildren::new(parent) 145 | } 146 | 147 | pub(super) fn token(parent: &SyntaxNode, kind: SyntaxKind) -> Option { 148 | parent 149 | .children_with_tokens() 150 | .filter_map(|it| it.into_token()) 151 | .find(|it| it.kind() == kind) 152 | } 153 | } 154 | 155 | #[test] 156 | fn assert_ast_is_object_safe() { 157 | fn _f(_: &dyn AstNode, _: &dyn HasName) {} 158 | } 159 | -------------------------------------------------------------------------------- /crates/oq3_syntax/src/ast/edit.rs: -------------------------------------------------------------------------------- 1 | // Copyright contributors to the openqasm-parser project 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //! This module contains functions for editing syntax trees. As the trees are 5 | //! immutable, all function here return a fresh copy of the tree, instead of 6 | //! doing an in-place modification. 7 | use std::{fmt, iter, ops}; 8 | 9 | use crate::{ 10 | ast::{self, make, AstNode}, 11 | ted, AstToken, NodeOrToken, SyntaxElement, SyntaxNode, SyntaxToken, 12 | }; 13 | 14 | #[derive(Debug, Clone, Copy)] 15 | pub struct IndentLevel(pub u8); 16 | 17 | impl From for IndentLevel { 18 | fn from(level: u8) -> IndentLevel { 19 | IndentLevel(level) 20 | } 21 | } 22 | 23 | impl fmt::Display for IndentLevel { 24 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 25 | let spaces = " "; 26 | let buf; 27 | let len = self.0 as usize * 4; 28 | let indent = if len <= spaces.len() { 29 | &spaces[..len] 30 | } else { 31 | buf = " ".repeat(len); 32 | &buf 33 | }; 34 | fmt::Display::fmt(indent, f) 35 | } 36 | } 37 | 38 | impl ops::Add for IndentLevel { 39 | type Output = IndentLevel; 40 | fn add(self, rhs: u8) -> IndentLevel { 41 | IndentLevel(self.0 + rhs) 42 | } 43 | } 44 | 45 | impl IndentLevel { 46 | pub fn single() -> IndentLevel { 47 | IndentLevel(0) 48 | } 49 | pub fn is_zero(&self) -> bool { 50 | self.0 == 0 51 | } 52 | pub fn from_element(element: &SyntaxElement) -> IndentLevel { 53 | match element { 54 | rowan::NodeOrToken::Node(it) => IndentLevel::from_node(it), 55 | rowan::NodeOrToken::Token(it) => IndentLevel::from_token(it), 56 | } 57 | } 58 | 59 | pub fn from_node(node: &SyntaxNode) -> IndentLevel { 60 | match node.first_token() { 61 | Some(it) => Self::from_token(&it), 62 | None => IndentLevel(0), 63 | } 64 | } 65 | 66 | pub fn from_token(token: &SyntaxToken) -> IndentLevel { 67 | for ws in prev_tokens(token.clone()).filter_map(ast::Whitespace::cast) { 68 | let text = ws.syntax().text(); 69 | if let Some(pos) = text.rfind('\n') { 70 | let level = text[pos + 1..].chars().count() / 4; 71 | return IndentLevel(level as u8); 72 | } 73 | } 74 | IndentLevel(0) 75 | } 76 | 77 | /// XXX: this intentionally doesn't change the indent of the very first token. 78 | /// Ie, in something like 79 | /// ``` 80 | /// fn foo() { 81 | /// 92 82 | /// } 83 | /// ``` 84 | /// if you indent the block, the `{` token would stay put. 85 | pub(super) fn increase_indent(self, node: &SyntaxNode) { 86 | let tokens = node.preorder_with_tokens().filter_map(|event| match event { 87 | rowan::WalkEvent::Leave(NodeOrToken::Token(it)) => Some(it), 88 | _ => None, 89 | }); 90 | for token in tokens { 91 | if let Some(ws) = ast::Whitespace::cast(token) { 92 | if ws.text().contains('\n') { 93 | let new_ws = make::tokens::whitespace(&format!("{}{self}", ws.syntax())); 94 | ted::replace(ws.syntax(), &new_ws); 95 | } 96 | } 97 | } 98 | } 99 | 100 | pub(super) fn decrease_indent(self, node: &SyntaxNode) { 101 | let tokens = node.preorder_with_tokens().filter_map(|event| match event { 102 | rowan::WalkEvent::Leave(NodeOrToken::Token(it)) => Some(it), 103 | _ => None, 104 | }); 105 | for token in tokens { 106 | if let Some(ws) = ast::Whitespace::cast(token) { 107 | if ws.text().contains('\n') { 108 | let new_ws = make::tokens::whitespace( 109 | &ws.syntax().text().replace(&format!("\n{self}"), "\n"), 110 | ); 111 | ted::replace(ws.syntax(), &new_ws); 112 | } 113 | } 114 | } 115 | } 116 | } 117 | 118 | fn prev_tokens(token: SyntaxToken) -> impl Iterator { 119 | iter::successors(Some(token), |token| token.prev_token()) 120 | } 121 | 122 | /// Soft-deprecated in favor of mutable tree editing API `edit_in_place::Ident`. 123 | pub trait AstNodeEdit: AstNode + Clone + Sized { 124 | fn indent_level(&self) -> IndentLevel { 125 | IndentLevel::from_node(self.syntax()) 126 | } 127 | #[must_use] 128 | fn indent(&self, level: IndentLevel) -> Self { 129 | fn indent_inner(node: &SyntaxNode, level: IndentLevel) -> SyntaxNode { 130 | let res = node.clone_subtree().clone_for_update(); 131 | level.increase_indent(&res); 132 | res.clone_subtree() 133 | } 134 | 135 | Self::cast(indent_inner(self.syntax(), level)).unwrap() 136 | } 137 | #[must_use] 138 | fn dedent(&self, level: IndentLevel) -> Self { 139 | fn dedent_inner(node: &SyntaxNode, level: IndentLevel) -> SyntaxNode { 140 | let res = node.clone_subtree().clone_for_update(); 141 | level.decrease_indent(&res); 142 | res.clone_subtree() 143 | } 144 | 145 | Self::cast(dedent_inner(self.syntax(), level)).unwrap() 146 | } 147 | #[must_use] 148 | fn reset_indent(&self) -> Self { 149 | let level = IndentLevel::from_node(self.syntax()); 150 | self.dedent(level) 151 | } 152 | } 153 | 154 | impl AstNodeEdit for N {} 155 | -------------------------------------------------------------------------------- /crates/oq3_syntax/src/ast/generated.rs: -------------------------------------------------------------------------------- 1 | // Copyright contributors to the openqasm-parser project 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //! This file is actually hand-written, but the submodules are indeed generated. 5 | #[rustfmt::skip] 6 | pub(crate) mod nodes; 7 | #[rustfmt::skip] 8 | pub(crate) mod tokens; 9 | -------------------------------------------------------------------------------- /crates/oq3_syntax/src/ast/generated/tokens.rs: -------------------------------------------------------------------------------- 1 | //! Generated by `sourcegen_ast`, do not edit by hand. 2 | 3 | use crate::{ 4 | ast::AstToken, 5 | SyntaxKind::{self, *}, 6 | SyntaxToken, 7 | }; 8 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 9 | pub struct Whitespace { 10 | pub(crate) syntax: SyntaxToken, 11 | } 12 | impl std::fmt::Display for Whitespace { 13 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 14 | std::fmt::Display::fmt(&self.syntax, f) 15 | } 16 | } 17 | impl AstToken for Whitespace { 18 | fn can_cast(kind: SyntaxKind) -> bool { 19 | kind == WHITESPACE 20 | } 21 | fn cast(syntax: SyntaxToken) -> Option { 22 | if Self::can_cast(syntax.kind()) { 23 | Some(Self { syntax }) 24 | } else { 25 | None 26 | } 27 | } 28 | fn syntax(&self) -> &SyntaxToken { 29 | &self.syntax 30 | } 31 | } 32 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 33 | pub struct Comment { 34 | pub(crate) syntax: SyntaxToken, 35 | } 36 | impl std::fmt::Display for Comment { 37 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 38 | std::fmt::Display::fmt(&self.syntax, f) 39 | } 40 | } 41 | impl AstToken for Comment { 42 | fn can_cast(kind: SyntaxKind) -> bool { 43 | kind == COMMENT 44 | } 45 | fn cast(syntax: SyntaxToken) -> Option { 46 | if Self::can_cast(syntax.kind()) { 47 | Some(Self { syntax }) 48 | } else { 49 | None 50 | } 51 | } 52 | fn syntax(&self) -> &SyntaxToken { 53 | &self.syntax 54 | } 55 | } 56 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 57 | pub struct String { 58 | pub(crate) syntax: SyntaxToken, 59 | } 60 | impl std::fmt::Display for String { 61 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 62 | std::fmt::Display::fmt(&self.syntax, f) 63 | } 64 | } 65 | impl AstToken for String { 66 | fn can_cast(kind: SyntaxKind) -> bool { 67 | kind == STRING 68 | } 69 | fn cast(syntax: SyntaxToken) -> Option { 70 | if Self::can_cast(syntax.kind()) { 71 | Some(Self { syntax }) 72 | } else { 73 | None 74 | } 75 | } 76 | fn syntax(&self) -> &SyntaxToken { 77 | &self.syntax 78 | } 79 | } 80 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 81 | pub struct IntNumber { 82 | pub(crate) syntax: SyntaxToken, 83 | } 84 | impl std::fmt::Display for IntNumber { 85 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 86 | std::fmt::Display::fmt(&self.syntax, f) 87 | } 88 | } 89 | impl AstToken for IntNumber { 90 | fn can_cast(kind: SyntaxKind) -> bool { 91 | kind == INT_NUMBER 92 | } 93 | fn cast(syntax: SyntaxToken) -> Option { 94 | if Self::can_cast(syntax.kind()) { 95 | Some(Self { syntax }) 96 | } else { 97 | None 98 | } 99 | } 100 | fn syntax(&self) -> &SyntaxToken { 101 | &self.syntax 102 | } 103 | } 104 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 105 | pub struct FloatNumber { 106 | pub(crate) syntax: SyntaxToken, 107 | } 108 | impl std::fmt::Display for FloatNumber { 109 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 110 | std::fmt::Display::fmt(&self.syntax, f) 111 | } 112 | } 113 | impl AstToken for FloatNumber { 114 | fn can_cast(kind: SyntaxKind) -> bool { 115 | kind == FLOAT_NUMBER 116 | } 117 | fn cast(syntax: SyntaxToken) -> Option { 118 | if Self::can_cast(syntax.kind()) { 119 | Some(Self { syntax }) 120 | } else { 121 | None 122 | } 123 | } 124 | fn syntax(&self) -> &SyntaxToken { 125 | &self.syntax 126 | } 127 | } 128 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 129 | pub struct Char { 130 | pub(crate) syntax: SyntaxToken, 131 | } 132 | impl std::fmt::Display for Char { 133 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 134 | std::fmt::Display::fmt(&self.syntax, f) 135 | } 136 | } 137 | impl AstToken for Char { 138 | fn can_cast(kind: SyntaxKind) -> bool { 139 | kind == CHAR 140 | } 141 | fn cast(syntax: SyntaxToken) -> Option { 142 | if Self::can_cast(syntax.kind()) { 143 | Some(Self { syntax }) 144 | } else { 145 | None 146 | } 147 | } 148 | fn syntax(&self) -> &SyntaxToken { 149 | &self.syntax 150 | } 151 | } 152 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 153 | pub struct Byte { 154 | pub(crate) syntax: SyntaxToken, 155 | } 156 | impl std::fmt::Display for Byte { 157 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 158 | std::fmt::Display::fmt(&self.syntax, f) 159 | } 160 | } 161 | impl AstToken for Byte { 162 | fn can_cast(kind: SyntaxKind) -> bool { 163 | kind == BYTE 164 | } 165 | fn cast(syntax: SyntaxToken) -> Option { 166 | if Self::can_cast(syntax.kind()) { 167 | Some(Self { syntax }) 168 | } else { 169 | None 170 | } 171 | } 172 | fn syntax(&self) -> &SyntaxToken { 173 | &self.syntax 174 | } 175 | } 176 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 177 | pub struct Ident { 178 | pub(crate) syntax: SyntaxToken, 179 | } 180 | impl std::fmt::Display for Ident { 181 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 182 | std::fmt::Display::fmt(&self.syntax, f) 183 | } 184 | } 185 | impl AstToken for Ident { 186 | fn can_cast(kind: SyntaxKind) -> bool { 187 | kind == IDENT 188 | } 189 | fn cast(syntax: SyntaxToken) -> Option { 190 | if Self::can_cast(syntax.kind()) { 191 | Some(Self { syntax }) 192 | } else { 193 | None 194 | } 195 | } 196 | fn syntax(&self) -> &SyntaxToken { 197 | &self.syntax 198 | } 199 | } 200 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 201 | pub struct BitString { 202 | pub(crate) syntax: SyntaxToken, 203 | } 204 | impl std::fmt::Display for BitString { 205 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 206 | std::fmt::Display::fmt(&self.syntax, f) 207 | } 208 | } 209 | impl AstToken for BitString { 210 | fn can_cast(kind: SyntaxKind) -> bool { 211 | kind == BIT_STRING 212 | } 213 | fn cast(syntax: SyntaxToken) -> Option { 214 | if Self::can_cast(syntax.kind()) { 215 | Some(Self { syntax }) 216 | } else { 217 | None 218 | } 219 | } 220 | fn syntax(&self) -> &SyntaxToken { 221 | &self.syntax 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /crates/oq3_syntax/src/ast/make.rs: -------------------------------------------------------------------------------- 1 | // Copyright contributors to the openqasm-parser project 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //! This module contains free-standing functions for creating AST fragments out 5 | //! of smaller pieces. 6 | //! 7 | //! Note that all functions here intended to be stupid constructors, which just 8 | //! assemble a finish node from immediate children. If you want to do something 9 | //! smarter than that, it belongs to the `ext` submodule. 10 | //! 11 | //! GJL: I think these are meant to be used at a higher level, ie HIR. Not used within 12 | //! the oq3_lexer and oq3_parser crates. 13 | //! 14 | //! Keep in mind that `from_text` functions should be kept private. The public 15 | //! API should require to assemble every node piecewise. The trick of 16 | //! `parse(format!())` we use internally is an implementation detail -- long 17 | //! term, it will be replaced with direct tree manipulation. 18 | // use itertools::Itertools; 19 | //use stdx::{format_to}; 20 | 21 | use crate::{ast, AstNode, SourceFile, SyntaxKind, SyntaxToken}; // utils::is_raw_identifier 22 | 23 | /// While the parent module defines basic atomic "constructors", the `ext` 24 | /// module defines shortcuts for common things. 25 | /// 26 | /// It's named `ext` rather than `shortcuts` just to keep it short. 27 | pub mod ext {} 28 | 29 | pub fn expr_loop(block: ast::BlockExpr) -> ast::Expr { 30 | expr_from_text(&format!("loop {block}")) 31 | } 32 | 33 | pub fn expr_prefix(op: SyntaxKind, expr: ast::Expr) -> ast::Expr { 34 | let token = token(op); 35 | expr_from_text(&format!("{token}{expr}")) 36 | } 37 | pub fn expr_call(f: ast::Expr, arg_list: ast::ArgList) -> ast::Expr { 38 | expr_from_text(&format!("{f}{arg_list}")) 39 | } 40 | pub fn expr_paren(expr: ast::Expr) -> ast::Expr { 41 | expr_from_text(&format!("({expr})")) 42 | } 43 | pub fn expr_assignment(lhs: ast::Expr, rhs: ast::Expr) -> ast::Expr { 44 | expr_from_text(&format!("{lhs} = {rhs}")) 45 | } 46 | fn expr_from_text(text: &str) -> ast::Expr { 47 | ast_from_text(&format!("const C: () = {text};")) 48 | } 49 | pub fn expr_stmt(expr: ast::Expr) -> ast::ExprStmt { 50 | let semi = if expr.is_block_like() { "" } else { ";" }; 51 | ast_from_text(&format!("fn f() {{ {expr}{semi} (); }}")) 52 | } 53 | 54 | #[track_caller] 55 | fn ast_from_text(text: &str) -> N { 56 | let parse = SourceFile::parse(text); 57 | let node = match parse.tree().syntax().descendants().find_map(N::cast) { 58 | Some(it) => it, 59 | None => { 60 | let node = std::any::type_name::(); 61 | panic!("Failed to make ast node `{node}` from text {text}") 62 | } 63 | }; 64 | let node = node.clone_subtree(); 65 | assert_eq!(node.syntax().text_range().start(), 0.into()); 66 | node 67 | } 68 | 69 | pub fn token(kind: SyntaxKind) -> SyntaxToken { 70 | tokens::SOURCE_FILE 71 | .tree() 72 | .syntax() 73 | .clone_for_update() 74 | .descendants_with_tokens() 75 | .filter_map(|it| it.into_token()) 76 | .find(|it| it.kind() == kind) 77 | .unwrap_or_else(|| panic!("unhandled token: {kind:?}")) 78 | } 79 | 80 | pub mod tokens { 81 | use once_cell::sync::Lazy; 82 | 83 | use crate::{ast, AstNode, Parse, SourceFile, SyntaxKind::*, SyntaxToken}; 84 | 85 | pub(super) static SOURCE_FILE: Lazy> = Lazy::new(|| { 86 | SourceFile::parse( 87 | "const C: <()>::Item = (1 != 1, 2 == 2, 3 < 3, 4 <= 4, 5 > 5, 6 >= 6, !true, *p, &p , &mut p)\n;\n\n", 88 | ) 89 | }); 90 | 91 | pub fn semicolon() -> SyntaxToken { 92 | SOURCE_FILE 93 | .tree() 94 | .syntax() 95 | .clone_for_update() 96 | .descendants_with_tokens() 97 | .filter_map(|it| it.into_token()) 98 | .find(|it| it.kind() == SEMICOLON) 99 | .unwrap() 100 | } 101 | 102 | pub fn single_space() -> SyntaxToken { 103 | SOURCE_FILE 104 | .tree() 105 | .syntax() 106 | .clone_for_update() 107 | .descendants_with_tokens() 108 | .filter_map(|it| it.into_token()) 109 | .find(|it| it.kind() == WHITESPACE && it.text() == " ") 110 | .unwrap() 111 | } 112 | 113 | pub fn whitespace(text: &str) -> SyntaxToken { 114 | assert!(text.trim().is_empty()); 115 | let sf = SourceFile::parse(text).ok().unwrap(); 116 | sf.syntax() 117 | .clone_for_update() 118 | .first_child_or_token() 119 | .unwrap() 120 | .into_token() 121 | .unwrap() 122 | } 123 | 124 | pub fn doc_comment(text: &str) -> SyntaxToken { 125 | assert!(!text.trim().is_empty()); 126 | let sf = SourceFile::parse(text).ok().unwrap(); 127 | sf.syntax() 128 | .first_child_or_token() 129 | .unwrap() 130 | .into_token() 131 | .unwrap() 132 | } 133 | 134 | pub fn literal(text: &str) -> SyntaxToken { 135 | assert_eq!(text.trim(), text); 136 | let lit: ast::Literal = super::ast_from_text(&format!("fn f() {{ let _ = {text}; }}")); 137 | lit.syntax() 138 | .first_child_or_token() 139 | .unwrap() 140 | .into_token() 141 | .unwrap() 142 | } 143 | 144 | pub fn single_newline() -> SyntaxToken { 145 | let res = SOURCE_FILE 146 | .tree() 147 | .syntax() 148 | .clone_for_update() 149 | .descendants_with_tokens() 150 | .filter_map(|it| it.into_token()) 151 | .find(|it| it.kind() == WHITESPACE && it.text() == "\n") 152 | .unwrap(); 153 | res.detach(); 154 | res 155 | } 156 | 157 | pub fn blank_line() -> SyntaxToken { 158 | SOURCE_FILE 159 | .tree() 160 | .syntax() 161 | .clone_for_update() 162 | .descendants_with_tokens() 163 | .filter_map(|it| it.into_token()) 164 | .find(|it| it.kind() == WHITESPACE && it.text() == "\n\n") 165 | .unwrap() 166 | } 167 | 168 | pub struct WsBuilder(SourceFile); 169 | 170 | impl WsBuilder { 171 | pub fn new(text: &str) -> WsBuilder { 172 | WsBuilder(SourceFile::parse(text).ok().unwrap()) 173 | } 174 | pub fn ws(&self) -> SyntaxToken { 175 | self.0 176 | .syntax() 177 | .first_child_or_token() 178 | .unwrap() 179 | .into_token() 180 | .unwrap() 181 | } 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /crates/oq3_syntax/src/ast/node_ext.rs: -------------------------------------------------------------------------------- 1 | // Copyright contributors to the openqasm-parser project 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //! Various extension methods to ast Nodes, which are hard to code-generate. 5 | //! Extensions for various expressions live in a sibling `expr_extensions` module. 6 | //! 7 | //! These methods should only do simple, shallow tasks related to the syntax of the node itself. 8 | 9 | use rowan::{GreenNodeData, GreenTokenData}; 10 | use std::borrow::Cow; 11 | 12 | use crate::{ 13 | ast::{self, support, AstNode, SyntaxNode}, 14 | NodeOrToken, TokenText, 15 | }; 16 | 17 | // This function, `text` is borrowed from r-a (may still be commented out below.) 18 | // There is another method, also called `text` implemented for `SyntaxNode`. 19 | // The present function differs in that it returns the text of the first token 20 | // rather than the text of all tokens in the subtree. 21 | // 22 | // This trait needs a better name. 23 | // Several AstNodes have a single token that is a name. 24 | // This includes, but is not limited to, `Name`. 25 | pub trait HasTextName: AstNode { 26 | fn text(&self) -> TokenText<'_> { 27 | text_of_first_token(self.syntax()) 28 | } 29 | 30 | // FIXME: is this ok? The name of the function does not seem idiomatic 31 | // Nor is returning an owned string. but in practice, at the moment, we always, 32 | // convert text immediately to a `String`. 33 | fn string(&self) -> String { 34 | self.text().to_string() 35 | } 36 | } 37 | 38 | impl HasTextName for ast::Param {} 39 | impl HasTextName for ast::Name {} 40 | impl HasTextName for ast::HardwareQubit {} 41 | impl HasTextName for ast::Identifier {} 42 | 43 | // This was in the r-a code 44 | // // You didn't really want the name-name, did you? 45 | // impl ast::Name { 46 | // pub fn text(&self) -> TokenText<'_> { 47 | // text_of_first_token(self.syntax()) 48 | // } 49 | // } 50 | 51 | fn text_of_first_token(node: &SyntaxNode) -> TokenText<'_> { 52 | fn first_token(green_ref: &GreenNodeData) -> &GreenTokenData { 53 | green_ref 54 | .children() 55 | .next() 56 | .and_then(NodeOrToken::into_token) 57 | .unwrap() 58 | } 59 | 60 | match node.green() { 61 | Cow::Borrowed(green_ref) => TokenText::borrowed(first_token(green_ref).text()), 62 | Cow::Owned(green) => TokenText::owned(first_token(&green).to_owned()), 63 | } 64 | } 65 | 66 | // TODO: Implementing something like this would be useful. 67 | // Determining which kind of for iterable we have is done in semantic 68 | // analysis by querying via methods on ast::ForIterable 69 | // We could construct an enum here and expose it to consumers. 70 | // #[derive(Clone, Debug, PartialEq, Eq, Hash)] 71 | // pub enum ForIterable { 72 | // SetExpression(ast::SetExpression), 73 | // RangeExpression(ast::RangeExpr), 74 | // Expr(ast::Expr), 75 | // } 76 | 77 | // This was carried over from rust. It seems we do not need this any 78 | // longer for disambiguation. 79 | // impl ast::ForStmt { 80 | // // pub fn iterable(&self) -> Option { 81 | // pub fn iterable(&self) -> Option { 82 | // // If the iterable is a BlockExpr, check if the body is missing. 83 | // // If it is, assume the iterable is the expression that is missing instead. 84 | // // let token = self.token(); 85 | // // if let Some(t) = ast::SetExpression::cast(token.clone()) { 86 | // // return ForIterable::SetExpression(t); 87 | // // } 88 | // // if let Some(t) = ast::RangeExpr::cast(token.clone()) { 89 | // // return ForIterable::RangeExpression(t); 90 | // // } 91 | // // None 92 | // let mut exprs = support::children(self.syntax()); 93 | // let first = exprs.next(); 94 | // match first { 95 | // Some(ast::Expr::BlockExpr(_)) => exprs.next().and(first), 96 | // first => first, 97 | // } 98 | // } 99 | // } 100 | 101 | impl ast::AssignmentStmt { 102 | pub fn identifier(&self) -> Option { 103 | support::child(&self.syntax) 104 | } 105 | } 106 | 107 | impl ast::HasLoopBody for ast::ForStmt { 108 | fn loop_body(&self) -> Option { 109 | let mut exprs = support::children(self.syntax()); 110 | let first = exprs.next(); 111 | let second = exprs.next(); 112 | second.or(first) 113 | } 114 | } 115 | 116 | impl ast::WhileStmt { 117 | pub fn condition(&self) -> Option { 118 | // If the condition is a BlockExpr, check if the body is missing. 119 | // If it is assume the condition is the expression that is missing instead. 120 | let mut exprs = support::children(self.syntax()); 121 | let first = exprs.next(); 122 | match first { 123 | Some(ast::Expr::BlockExpr(_)) => exprs.next().and(first), 124 | first => first, 125 | } 126 | } 127 | 128 | // FIXME: need to support both single statement and block. 129 | // Or, can we / should we collapse the distinction already at this level? 130 | pub fn body(&self) -> Option { 131 | let mut exprs = support::children(self.syntax()); 132 | exprs.next() 133 | } 134 | } 135 | 136 | // FIXME: This is wrong. Use `body` above for now. But must sort this out. 137 | impl ast::HasLoopBody for ast::WhileStmt { 138 | fn loop_body(&self) -> Option { 139 | let mut exprs = support::children(self.syntax()); 140 | let first = exprs.next(); 141 | let second = exprs.next(); 142 | second.or(first) 143 | } 144 | } 145 | 146 | impl ast::PragmaStatement { 147 | fn text(&self) -> TokenText<'_> { 148 | text_of_first_token(self.syntax()) 149 | } 150 | 151 | // return the pragma line omitting the word "pragma" 152 | pub fn pragma_text(&self) -> String { 153 | let text = self.text(); 154 | if text.starts_with('#') { 155 | text[7..].to_string() 156 | } else { 157 | text[6..].to_string() 158 | } 159 | } 160 | } 161 | 162 | impl ast::AnnotationStatement { 163 | fn text(&self) -> TokenText<'_> { 164 | text_of_first_token(self.syntax()) 165 | } 166 | 167 | pub fn annotation_text(&self) -> String { 168 | self.text().to_string() 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /crates/oq3_syntax/src/ast/operators.rs: -------------------------------------------------------------------------------- 1 | // Copyright contributors to the openqasm-parser project 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Note the following comment was written for r-a 5 | // It is tempting to reuse these structures, but that creates one 6 | // more entanglement between crates. 7 | //! Defines a bunch of data-less enums for unary and binary operators. 8 | //! 9 | //! Types here don't know about AST, this allows re-using them for both AST and 10 | //! HIR. 11 | use std::fmt; 12 | 13 | #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] 14 | pub enum RangeOp { 15 | /// `..` 16 | Exclusive, 17 | /// `..=` 18 | Inclusive, 19 | } 20 | 21 | #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] 22 | pub enum UnaryOp { 23 | /// `~` 24 | LogicNot, 25 | /// `!` 26 | Not, 27 | /// `-` 28 | Neg, 29 | } 30 | 31 | #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] 32 | pub enum BinaryOp { 33 | LogicOp(LogicOp), 34 | ArithOp(ArithOp), 35 | CmpOp(CmpOp), 36 | ConcatenationOp, 37 | // FIXME: Don't we want to remove this? 38 | Assignment { op: Option }, 39 | } 40 | 41 | #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] 42 | pub enum LogicOp { 43 | And, 44 | Or, 45 | } 46 | 47 | // FIXME: This is the way r-a did it. IDK, might be simpler 48 | // to just have four individal variants to replace `Ord`. 49 | #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] 50 | pub enum CmpOp { 51 | Eq { negated: bool }, 52 | Ord { ordering: Ordering, strict: bool }, 53 | } 54 | 55 | #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] 56 | pub enum Ordering { 57 | Less, 58 | Greater, 59 | } 60 | 61 | #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] 62 | pub enum ArithOp { 63 | Add, 64 | Mul, 65 | Sub, 66 | Div, 67 | Rem, 68 | Shl, 69 | Shr, 70 | BitXor, 71 | BitOr, 72 | BitAnd, 73 | } 74 | 75 | impl fmt::Display for LogicOp { 76 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 77 | let res = match self { 78 | LogicOp::And => "&&", 79 | LogicOp::Or => "||", 80 | }; 81 | f.write_str(res) 82 | } 83 | } 84 | 85 | impl fmt::Display for ArithOp { 86 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 87 | let res = match self { 88 | ArithOp::Add => "+", 89 | ArithOp::Mul => "*", 90 | ArithOp::Sub => "-", 91 | ArithOp::Div => "/", 92 | ArithOp::Rem => "%", 93 | ArithOp::Shl => "<<", 94 | ArithOp::Shr => ">>", 95 | ArithOp::BitXor => "^", 96 | ArithOp::BitOr => "|", 97 | ArithOp::BitAnd => "&", 98 | }; 99 | f.write_str(res) 100 | } 101 | } 102 | 103 | impl fmt::Display for CmpOp { 104 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 105 | let res = match self { 106 | CmpOp::Eq { negated: false } => "==", 107 | CmpOp::Eq { negated: true } => "!=", 108 | CmpOp::Ord { 109 | ordering: Ordering::Less, 110 | strict: false, 111 | } => "<=", 112 | CmpOp::Ord { 113 | ordering: Ordering::Less, 114 | strict: true, 115 | } => "<", 116 | CmpOp::Ord { 117 | ordering: Ordering::Greater, 118 | strict: false, 119 | } => ">=", 120 | CmpOp::Ord { 121 | ordering: Ordering::Greater, 122 | strict: true, 123 | } => ">", 124 | }; 125 | f.write_str(res) 126 | } 127 | } 128 | 129 | impl fmt::Display for BinaryOp { 130 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 131 | match self { 132 | BinaryOp::LogicOp(op) => fmt::Display::fmt(op, f), 133 | BinaryOp::ArithOp(op) => fmt::Display::fmt(op, f), 134 | BinaryOp::CmpOp(op) => fmt::Display::fmt(op, f), 135 | BinaryOp::ConcatenationOp => fmt::Display::fmt("++", f), 136 | BinaryOp::Assignment { op } => { 137 | if let Some(op) = op { 138 | fmt::Display::fmt(op, f)?; 139 | } 140 | f.write_str("=")?; 141 | Ok(()) 142 | } 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /crates/oq3_syntax/src/ast/traits.rs: -------------------------------------------------------------------------------- 1 | // Copyright contributors to the openqasm-parser project 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //! Various traits that are implemented by ast nodes. 5 | //! 6 | //! The implementations are usually trivial, and live in generated.rs 7 | //! 8 | //! NOTE!!! This is not support for the rust traits! 9 | //! This cannot be removed for OQ3. 10 | 11 | use either::Either; 12 | 13 | use crate::ast::{self, support, AstNode}; 14 | 15 | pub trait HasName: AstNode { 16 | fn name(&self) -> Option { 17 | support::child(self.syntax()) 18 | } 19 | } 20 | 21 | pub trait HasLoopBody: AstNode { 22 | fn loop_body(&self) -> Option { 23 | support::child(self.syntax()) 24 | } 25 | } 26 | 27 | pub trait HasArgList: AstNode { 28 | fn arg_list(&self) -> Option { 29 | support::child(self.syntax()) 30 | } 31 | } 32 | 33 | impl HasName for Either {} 34 | -------------------------------------------------------------------------------- /crates/oq3_syntax/src/ast/type_ext.rs: -------------------------------------------------------------------------------- 1 | // Copyright contributors to the openqasm-parser project 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | use crate::{ast, SyntaxToken, T}; 5 | 6 | // `ScalarTypeKind` includes Qubit because thus far, we only 7 | // use it for def can defcall, which allows mixing scalar and qubits 8 | // in a list of parameters. 9 | #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] 10 | pub enum ScalarTypeKind { 11 | Angle, 12 | Bit, 13 | Bool, 14 | Complex, 15 | Duration, 16 | Float, 17 | Int, 18 | None, // Use this to record errors 19 | Stretch, 20 | UInt, 21 | Qubit, 22 | } 23 | 24 | impl ast::ScalarType { 25 | pub fn kind(&self) -> ScalarTypeKind { 26 | use ScalarTypeKind::*; 27 | match self.token().kind() { 28 | T![angle] => Angle, 29 | T![bit] => Bit, 30 | T![bool] => Bool, 31 | T![complex] => Complex, 32 | T![duration] => Duration, 33 | T![float] => Float, 34 | T![int] => Int, 35 | T![stretch] => Stretch, 36 | T![uint] => UInt, 37 | T![qubit] => Qubit, 38 | _ => None, 39 | } 40 | } 41 | 42 | pub fn token(&self) -> SyntaxToken { 43 | self.syntax 44 | .children_with_tokens() 45 | .find(|e| !e.kind().is_trivia()) 46 | .and_then(|e| e.into_token()) 47 | .unwrap() 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /crates/oq3_syntax/src/parsing.rs: -------------------------------------------------------------------------------- 1 | // Copyright contributors to the openqasm-parser project 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //! Lexing, bridging to parser (which does the actual parsing) and 5 | //! incremental reparsing. 6 | 7 | use rowan::TextRange; 8 | 9 | use crate::{syntax_node::GreenNode, SyntaxError, SyntaxTreeBuilder}; 10 | 11 | pub fn parse_text(text: &str) -> (GreenNode, Vec) { 12 | let lexed = oq3_parser::LexedStr::new(text); 13 | let parser_input = lexed.to_input(); 14 | let parser_output = oq3_parser::TopEntryPoint::SourceFile.parse(&parser_input); 15 | let (node, errors, _eof) = build_tree(lexed, parser_output); 16 | (node, errors) 17 | } 18 | 19 | pub fn parse_text_check_lex(text: &str) -> (Option, Vec) { 20 | let lexed = oq3_parser::LexedStr::new(text); 21 | if lexed.errors_len() > 0 { 22 | return (None, just_errors(lexed)); 23 | } 24 | let parser_input = lexed.to_input(); 25 | let parser_output = oq3_parser::TopEntryPoint::SourceFile.parse(&parser_input); 26 | let (node, errors, _eof) = build_tree(lexed, parser_output); 27 | (Some(node), errors) 28 | } 29 | 30 | fn just_errors(lexed: oq3_parser::LexedStr<'_>) -> Vec { 31 | let mut errors = Vec::::new(); 32 | for (i, err) in lexed.errors() { 33 | let text_range = lexed.text_range(i); 34 | let text_range = TextRange::new( 35 | text_range.start.try_into().unwrap(), 36 | text_range.end.try_into().unwrap(), 37 | ); 38 | errors.push(SyntaxError::new(err, text_range)) 39 | } 40 | errors 41 | } 42 | 43 | pub(crate) fn build_tree( 44 | lexed: oq3_parser::LexedStr<'_>, 45 | parser_output: oq3_parser::Output, 46 | ) -> (GreenNode, Vec, bool) { 47 | let mut builder = SyntaxTreeBuilder::default(); 48 | 49 | let is_eof = lexed.intersperse_trivia(&parser_output, &mut |step| match step { 50 | oq3_parser::StrStep::Token { kind, text } => builder.token(kind, text), 51 | oq3_parser::StrStep::Enter { kind } => builder.start_node(kind), 52 | oq3_parser::StrStep::Exit => builder.finish_node(), 53 | oq3_parser::StrStep::Error { msg, pos } => { 54 | builder.error(msg.to_string(), pos.try_into().unwrap()) 55 | } 56 | }); 57 | 58 | let (node, mut errors) = builder.finish_raw(); 59 | for (i, err) in lexed.errors() { 60 | let text_range = lexed.text_range(i); 61 | let text_range = TextRange::new( 62 | text_range.start.try_into().unwrap(), 63 | text_range.end.try_into().unwrap(), 64 | ); 65 | errors.push(SyntaxError::new(err, text_range)) 66 | } 67 | 68 | (node, errors, is_eof) 69 | } 70 | -------------------------------------------------------------------------------- /crates/oq3_syntax/src/ptr.rs: -------------------------------------------------------------------------------- 1 | // Copyright contributors to the openqasm-parser project 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //! In rust-analyzer, syntax trees are transient objects. 5 | //! 6 | //! That means that we create trees when we need them, and tear them down to 7 | //! save memory. In this architecture, hanging on to a particular syntax node 8 | //! for a long time is ill-advisable, as that keeps the whole tree resident. 9 | //! 10 | //! Instead, we provide a [`SyntaxNodePtr`] type, which stores information about 11 | //! *location* of a particular syntax node in a tree. Its a small type which can 12 | //! be cheaply stored, and which can be resolved to a real [`SyntaxNode`] when 13 | //! necessary. 14 | 15 | use std::{ 16 | hash::{Hash, Hasher}, 17 | marker::PhantomData, 18 | }; 19 | 20 | use rowan::TextRange; 21 | 22 | use crate::{syntax_node::OpenQASM3Language, AstNode, SyntaxNode}; 23 | 24 | /// A "pointer" to a [`SyntaxNode`], via location in the source code. 25 | pub type SyntaxNodePtr = rowan::ast::SyntaxNodePtr; 26 | 27 | /// Like `SyntaxNodePtr`, but remembers the type of node. 28 | #[derive(Debug)] 29 | pub struct AstPtr { 30 | raw: SyntaxNodePtr, 31 | _ty: PhantomData N>, 32 | } 33 | 34 | impl Clone for AstPtr { 35 | #[rustversion::before(1.74)] 36 | fn clone(&self) -> AstPtr { 37 | AstPtr { 38 | raw: self.raw, 39 | _ty: PhantomData, 40 | } 41 | } 42 | #[rustversion::since(1.74)] 43 | fn clone(&self) -> AstPtr { 44 | AstPtr { 45 | raw: self.raw, 46 | _ty: PhantomData, 47 | } 48 | } 49 | } 50 | 51 | impl Eq for AstPtr {} 52 | 53 | impl PartialEq for AstPtr { 54 | fn eq(&self, other: &AstPtr) -> bool { 55 | self.raw == other.raw 56 | } 57 | } 58 | 59 | impl Hash for AstPtr { 60 | fn hash(&self, state: &mut H) { 61 | self.raw.hash(state); 62 | } 63 | } 64 | 65 | impl AstPtr { 66 | pub fn new(node: &N) -> AstPtr { 67 | AstPtr { 68 | raw: SyntaxNodePtr::new(node.syntax()), 69 | _ty: PhantomData, 70 | } 71 | } 72 | 73 | pub fn to_node(&self, root: &SyntaxNode) -> N { 74 | let syntax_node = self.raw.to_node(root); 75 | N::cast(syntax_node).unwrap() 76 | } 77 | 78 | #[rustversion::since(1.74)] 79 | pub fn syntax_node_ptr(&self) -> SyntaxNodePtr { 80 | self.raw 81 | } 82 | 83 | #[rustversion::before(1.74)] 84 | pub fn syntax_node_ptr(&self) -> SyntaxNodePtr { 85 | self.raw 86 | } 87 | 88 | pub fn text_range(&self) -> TextRange { 89 | self.raw.text_range() 90 | } 91 | 92 | pub fn cast(self) -> Option> { 93 | if !U::can_cast(self.raw.kind()) { 94 | return None; 95 | } 96 | Some(AstPtr { 97 | raw: self.raw, 98 | _ty: PhantomData, 99 | }) 100 | } 101 | 102 | pub fn upcast(self) -> AstPtr 103 | where 104 | N: Into, 105 | { 106 | AstPtr { 107 | raw: self.raw, 108 | _ty: PhantomData, 109 | } 110 | } 111 | 112 | /// Like `SyntaxNodePtr::cast` but the trait bounds work out. 113 | pub fn try_from_raw(raw: SyntaxNodePtr) -> Option> { 114 | N::can_cast(raw.kind()).then_some(AstPtr { 115 | raw, 116 | _ty: PhantomData, 117 | }) 118 | } 119 | } 120 | 121 | impl From> for SyntaxNodePtr { 122 | fn from(ptr: AstPtr) -> SyntaxNodePtr { 123 | ptr.raw 124 | } 125 | } 126 | 127 | // #[test] 128 | // fn test_local_syntax_ptr() { 129 | // use crate::{ast, AstNode, SourceFile}; 130 | 131 | // let file = SourceFile::parse("struct Foo { f: u32, }").ok().unwrap(); 132 | // let field = file.syntax().descendants().find_map(ast::RecordField::cast).unwrap(); 133 | // let ptr = SyntaxNodePtr::new(field.syntax()); 134 | // let field_syntax = ptr.to_node(file.syntax()); 135 | // assert_eq!(field.syntax(), &field_syntax); 136 | // } 137 | -------------------------------------------------------------------------------- /crates/oq3_syntax/src/sourcegen.rs: -------------------------------------------------------------------------------- 1 | // Copyright contributors to the openqasm-parser project 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //! This was copied from an external crate. It would be better to use the 5 | //! external crate. But it has a bug-- a hard coded path that requires the 6 | //! source to be at this path. 7 | //! 8 | //! OQ3 uses the same sourcegen routines that rust-analyer does. This reduces 9 | //! boiler plate somewhat. 10 | //! 11 | //! Things like feature documentation or assist tests are implemented by 12 | //! processing rust-analyzer's own source code and generating the appropriate 13 | //! output. See `sourcegen_` tests in various crates. 14 | //! 15 | //! This crate contains utilities to make this kind of source-gen easy. 16 | 17 | use std::{ 18 | fmt, fs, mem, 19 | path::{Path, PathBuf}, 20 | }; 21 | 22 | use xshell::{cmd, Shell}; 23 | 24 | // I think list_rust_files and list_files are only used to support 25 | // extracting tests from comments. 26 | 27 | #[allow(unused)] 28 | pub fn list_rust_files(dir: &Path) -> Vec { 29 | let mut res = list_files(dir); 30 | res.retain(|it| { 31 | it.file_name() 32 | .unwrap_or_default() 33 | .to_str() 34 | .unwrap_or_default() 35 | .ends_with(".rs") 36 | }); 37 | res 38 | } 39 | 40 | #[allow(unused)] 41 | pub fn list_files(dir: &Path) -> Vec { 42 | let mut res = Vec::new(); 43 | let mut work = vec![dir.to_path_buf()]; 44 | while let Some(dir) = work.pop() { 45 | for entry in dir.read_dir().unwrap() { 46 | let entry = entry.unwrap(); 47 | let file_type = entry.file_type().unwrap(); 48 | let path = entry.path(); 49 | let is_hidden = path 50 | .file_name() 51 | .unwrap_or_default() 52 | .to_str() 53 | .unwrap_or_default() 54 | .starts_with('.'); 55 | if !is_hidden { 56 | if file_type.is_dir() { 57 | work.push(path); 58 | } else if file_type.is_file() { 59 | res.push(path); 60 | } 61 | } 62 | } 63 | } 64 | res 65 | } 66 | 67 | #[derive(Clone)] 68 | pub struct CommentBlock { 69 | pub id: String, 70 | pub line: usize, 71 | pub contents: Vec, 72 | is_doc: bool, 73 | } 74 | 75 | impl CommentBlock { 76 | #[allow(unused)] 77 | pub fn extract(tag: &str, text: &str) -> Vec { 78 | assert!(tag.starts_with(char::is_uppercase)); 79 | 80 | let tag = format!("{tag}:"); 81 | let mut blocks = CommentBlock::extract_untagged(text); 82 | blocks.retain_mut(|block| { 83 | let first = block.contents.remove(0); 84 | let Some(id) = first.strip_prefix(&tag) else { 85 | return false; 86 | }; 87 | 88 | if block.is_doc { 89 | panic!("Use plain (non-doc) comments with tags like {tag}:\n {first}"); 90 | } 91 | 92 | block.id = id.trim().to_string(); 93 | true 94 | }); 95 | blocks 96 | } 97 | 98 | #[allow(unused)] 99 | pub fn extract_untagged(text: &str) -> Vec { 100 | let mut res = Vec::new(); 101 | 102 | let lines = text.lines().map(str::trim_start); 103 | 104 | let dummy_block = CommentBlock { 105 | id: String::new(), 106 | line: 0, 107 | contents: Vec::new(), 108 | is_doc: false, 109 | }; 110 | let mut block = dummy_block.clone(); 111 | for (line_num, line) in lines.enumerate() { 112 | match line.strip_prefix("//") { 113 | Some(mut contents) => { 114 | if let Some('/' | '!') = contents.chars().next() { 115 | contents = &contents[1..]; 116 | block.is_doc = true; 117 | } 118 | if let Some(' ') = contents.chars().next() { 119 | contents = &contents[1..]; 120 | } 121 | block.contents.push(contents.to_string()); 122 | } 123 | None => { 124 | if !block.contents.is_empty() { 125 | let block = mem::replace(&mut block, dummy_block.clone()); 126 | res.push(block); 127 | } 128 | block.line = line_num + 2; 129 | } 130 | } 131 | } 132 | if !block.contents.is_empty() { 133 | res.push(block); 134 | } 135 | res 136 | } 137 | } 138 | 139 | #[derive(Debug)] 140 | pub struct Location { 141 | pub file: PathBuf, 142 | pub line: usize, 143 | } 144 | 145 | // FIXME: URL for r-a is hardcoded here. 146 | // My guess is: 147 | // This is meant to display the location in the code of a test failure. 148 | // The tests are extracted and written and run elsewhere, so the location in 149 | // source is recorded so that the dev can find them. 150 | impl fmt::Display for Location { 151 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 152 | let path = self 153 | .file 154 | .strip_prefix(project_root()) 155 | .unwrap() 156 | .display() 157 | .to_string(); 158 | let path = path.replace('\\', "/"); 159 | let name = self.file.file_name().unwrap(); 160 | write!( 161 | f, 162 | "https://github.com/rust-lang/rust-analyzer/blob/master/{}#L{}[{}]", 163 | path, 164 | self.line, 165 | name.to_str().unwrap() 166 | ) 167 | } 168 | } 169 | 170 | #[allow(unused)] 171 | fn ensure_rustfmt(sh: &Shell) { 172 | let version = cmd!(sh, "rustup run stable rustfmt --version") 173 | .read() 174 | .unwrap_or_default(); 175 | if !version.contains("stable") { 176 | panic!( 177 | "Failed to run rustfmt from toolchain 'stable'. \ 178 | Please run `rustup component add rustfmt --toolchain stable` to install it.", 179 | ); 180 | } 181 | } 182 | 183 | #[allow(unused)] 184 | pub fn reformat(text: String) -> String { 185 | let sh = Shell::new().unwrap(); 186 | ensure_rustfmt(&sh); 187 | let rustfmt_toml = project_root().join("rustfmt.toml"); 188 | let mut stdout = cmd!( 189 | sh, 190 | "rustup run stable rustfmt --config-path {rustfmt_toml} --config fn_single_line=true" 191 | ) 192 | .stdin(text) 193 | .read() 194 | .unwrap(); 195 | if !stdout.ends_with('\n') { 196 | stdout.push('\n'); 197 | } 198 | stdout 199 | } 200 | 201 | #[allow(unused)] 202 | pub fn add_preamble(generator: &'static str, mut text: String) -> String { 203 | let preamble = format!("//! Generated by `{generator}`, do not edit by hand.\n\n"); 204 | text.insert_str(0, &preamble); 205 | text 206 | } 207 | 208 | /// Checks that the `file` has the specified `contents`. If that is not the 209 | /// case, updates the file and then fails the test. 210 | #[allow(unused)] 211 | pub fn ensure_file_contents(file: &Path, contents: &str) { 212 | if let Ok(old_contents) = fs::read_to_string(file) { 213 | if normalize_newlines(&old_contents) == normalize_newlines(contents) { 214 | // File is already up to date. 215 | return; 216 | } 217 | } 218 | 219 | let display_path = file.strip_prefix(project_root()).unwrap_or(file); 220 | eprintln!( 221 | "\n\x1b[31;1merror\x1b[0m: {} was not up-to-date, updating\n", 222 | display_path.display() 223 | ); 224 | if std::env::var("CI").is_ok() { 225 | eprintln!(" NOTE: run `cargo test` locally and commit the updated files\n"); 226 | } 227 | if let Some(parent) = file.parent() { 228 | let _ = fs::create_dir_all(parent); 229 | } 230 | fs::write(file, contents).unwrap(); 231 | panic!("some file was not up to date and has been updated, simply re-run the tests"); 232 | } 233 | 234 | #[allow(unused)] 235 | fn normalize_newlines(s: &str) -> String { 236 | s.replace("\r\n", "\n") 237 | } 238 | 239 | pub fn project_root() -> PathBuf { 240 | let dir = env!("CARGO_MANIFEST_DIR"); 241 | let res = PathBuf::from(dir) 242 | .parent() 243 | .unwrap() 244 | .parent() 245 | .unwrap() 246 | .to_owned(); 247 | assert!(res.join("dummy_triagebot.toml").exists()); // in rust-analyzer just "triagebot.toml". 248 | res 249 | } 250 | -------------------------------------------------------------------------------- /crates/oq3_syntax/src/syntax_error.rs: -------------------------------------------------------------------------------- 1 | // Copyright contributors to the openqasm-parser project 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //! See docs for `SyntaxError`. 5 | 6 | use std::fmt; 7 | 8 | use crate::{TextRange, TextSize}; 9 | 10 | /// Represents the result of unsuccessful tokenization, parsing 11 | /// or tree validation. 12 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 13 | pub struct SyntaxError(String, TextRange); 14 | 15 | // FIXME: The fixme comment below is retained from the original r-a code. 16 | // FIXME: there was an unused SyntaxErrorKind previously (before this enum was removed) 17 | // It was introduced in this PR: https://github.com/rust-lang/rust-analyzer/pull/846/files#diff-827da9b03b8f9faa1bade5cdd44d5dafR95 18 | // but it was not removed by a mistake. 19 | // 20 | // So, we need to find a place where to stick validation for attributes in match clauses. 21 | // Code before refactor: 22 | // InvalidMatchInnerAttr => { 23 | // write!(f, "Inner attributes are only allowed directly after the opening brace of the match expression") 24 | // } 25 | 26 | impl SyntaxError { 27 | pub fn new(message: impl Into, range: TextRange) -> Self { 28 | Self(message.into(), range) 29 | } 30 | 31 | // Note that this is meant to convert from `TextSize` to `TextRange`, whatever these mean. 32 | pub fn new_at_offset(message: impl Into, offset: TextSize) -> Self { 33 | Self(message.into(), TextRange::empty(offset)) 34 | } 35 | 36 | pub fn range(&self) -> TextRange { 37 | self.1 38 | } 39 | 40 | pub fn message(&self) -> &str { 41 | self.0.as_ref() 42 | } 43 | 44 | pub fn with_range(mut self, range: TextRange) -> Self { 45 | self.1 = range; 46 | self 47 | } 48 | } 49 | 50 | impl fmt::Display for SyntaxError { 51 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 52 | self.0.fmt(f) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /crates/oq3_syntax/src/syntax_node.rs: -------------------------------------------------------------------------------- 1 | // Copyright contributors to the openqasm-parser project 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //! This module defines Concrete Syntax Tree (CST), used by rust-analyzer. 5 | //! 6 | //! The CST includes comments and whitespace, provides a single node type, 7 | //! `SyntaxNode`, and a basic traversal API (parent, children, siblings). 8 | //! 9 | //! The *real* implementation is in the (language-agnostic) `rowan` crate, this 10 | //! module just wraps its API. 11 | 12 | use rowan::{GreenNodeBuilder, Language}; 13 | 14 | use crate::{Parse, SyntaxError, SyntaxKind, TextSize}; 15 | 16 | // FIXME: GJL Using this in demo program, so make it public. 17 | // actually rustc should warn if this is not used. 18 | pub use rowan::GreenNode; 19 | // pub(crate) use rowan::{GreenNode}; 20 | 21 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 22 | pub enum OpenQASM3Language {} 23 | impl Language for OpenQASM3Language { 24 | type Kind = SyntaxKind; 25 | 26 | fn kind_from_raw(raw: rowan::SyntaxKind) -> SyntaxKind { 27 | SyntaxKind::from(raw.0) 28 | } 29 | 30 | fn kind_to_raw(kind: SyntaxKind) -> rowan::SyntaxKind { 31 | rowan::SyntaxKind(kind.into()) 32 | } 33 | } 34 | 35 | pub type SyntaxNode = rowan::SyntaxNode; 36 | pub type SyntaxToken = rowan::SyntaxToken; 37 | pub type SyntaxElement = rowan::SyntaxElement; 38 | pub type SyntaxNodeChildren = rowan::SyntaxNodeChildren; 39 | pub type SyntaxElementChildren = rowan::SyntaxElementChildren; 40 | pub type PreorderWithTokens = rowan::api::PreorderWithTokens; 41 | 42 | #[derive(Default)] 43 | pub struct SyntaxTreeBuilder { 44 | errors: Vec, 45 | inner: GreenNodeBuilder<'static>, 46 | } 47 | 48 | impl SyntaxTreeBuilder { 49 | pub(crate) fn finish_raw(self) -> (GreenNode, Vec) { 50 | let green = self.inner.finish(); 51 | (green, self.errors) 52 | } 53 | 54 | pub fn finish(self) -> Parse { 55 | let (green, errors) = self.finish_raw(); 56 | // Disable block validation, see https://github.com/rust-lang/rust-analyzer/pull/10357 57 | #[allow(clippy::overly_complex_bool_expr)] 58 | // if cfg!(debug_assertions) && false { 59 | // let node = SyntaxNode::new_root(green.clone()); 60 | // crate::validation::validate_block_structure(&node); FIXME GJL 61 | // } 62 | Parse::new(green, errors) 63 | } 64 | 65 | pub fn token(&mut self, kind: SyntaxKind, text: &str) { 66 | let kind = OpenQASM3Language::kind_to_raw(kind); 67 | self.inner.token(kind, text); 68 | } 69 | 70 | pub fn start_node(&mut self, kind: SyntaxKind) { 71 | let kind = OpenQASM3Language::kind_to_raw(kind); 72 | self.inner.start_node(kind); 73 | } 74 | 75 | pub fn finish_node(&mut self) { 76 | self.inner.finish_node(); 77 | } 78 | 79 | pub fn error(&mut self, error: String, text_pos: TextSize) { 80 | self.errors 81 | .push(SyntaxError::new_at_offset(error, text_pos)); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /crates/oq3_syntax/src/ted.rs: -------------------------------------------------------------------------------- 1 | // Copyright contributors to the openqasm-parser project 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //! Primitive tree editor, ed for trees. 5 | //! 6 | //! The `_raw`-suffixed functions insert elements as is, unsuffixed versions fix 7 | //! up elements around the edges. 8 | use std::{mem, ops::RangeInclusive}; 9 | 10 | use oq3_parser::T; 11 | 12 | use crate::{ 13 | ast::{self, edit::IndentLevel, make, AstNode}, 14 | SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, 15 | }; 16 | 17 | /// Utility trait to allow calling `ted` functions with references or owned 18 | /// nodes. Do not use outside of this module. 19 | pub trait Element { 20 | fn syntax_element(self) -> SyntaxElement; 21 | } 22 | 23 | impl Element for &'_ E { 24 | fn syntax_element(self) -> SyntaxElement { 25 | self.clone().syntax_element() 26 | } 27 | } 28 | impl Element for SyntaxElement { 29 | fn syntax_element(self) -> SyntaxElement { 30 | self 31 | } 32 | } 33 | impl Element for SyntaxNode { 34 | fn syntax_element(self) -> SyntaxElement { 35 | self.into() 36 | } 37 | } 38 | impl Element for SyntaxToken { 39 | fn syntax_element(self) -> SyntaxElement { 40 | self.into() 41 | } 42 | } 43 | 44 | #[derive(Debug)] 45 | pub struct Position { 46 | repr: PositionRepr, 47 | } 48 | 49 | #[derive(Debug)] 50 | enum PositionRepr { 51 | FirstChild(SyntaxNode), 52 | After(SyntaxElement), 53 | } 54 | 55 | impl Position { 56 | pub fn after(elem: impl Element) -> Position { 57 | let repr = PositionRepr::After(elem.syntax_element()); 58 | Position { repr } 59 | } 60 | pub fn before(elem: impl Element) -> Position { 61 | let elem = elem.syntax_element(); 62 | let repr = match elem.prev_sibling_or_token() { 63 | Some(it) => PositionRepr::After(it), 64 | None => PositionRepr::FirstChild(elem.parent().unwrap()), 65 | }; 66 | Position { repr } 67 | } 68 | pub fn first_child_of(node: &(impl Into + Clone)) -> Position { 69 | let repr = PositionRepr::FirstChild(node.clone().into()); 70 | Position { repr } 71 | } 72 | pub fn last_child_of(node: &(impl Into + Clone)) -> Position { 73 | let node = node.clone().into(); 74 | let repr = match node.last_child_or_token() { 75 | Some(it) => PositionRepr::After(it), 76 | None => PositionRepr::FirstChild(node), 77 | }; 78 | Position { repr } 79 | } 80 | } 81 | 82 | pub fn insert(position: Position, elem: impl Element) { 83 | insert_all(position, vec![elem.syntax_element()]); 84 | } 85 | pub fn insert_raw(position: Position, elem: impl Element) { 86 | insert_all_raw(position, vec![elem.syntax_element()]); 87 | } 88 | pub fn insert_all(position: Position, mut elements: Vec) { 89 | if let Some(first) = elements.first() { 90 | if let Some(ws) = ws_before(&position, first) { 91 | elements.insert(0, ws.into()); 92 | } 93 | } 94 | if let Some(last) = elements.last() { 95 | if let Some(ws) = ws_after(&position, last) { 96 | elements.push(ws.into()); 97 | } 98 | } 99 | insert_all_raw(position, elements); 100 | } 101 | pub fn insert_all_raw(position: Position, elements: Vec) { 102 | let (parent, index) = match position.repr { 103 | PositionRepr::FirstChild(parent) => (parent, 0), 104 | PositionRepr::After(child) => (child.parent().unwrap(), child.index() + 1), 105 | }; 106 | parent.splice_children(index..index, elements); 107 | } 108 | 109 | pub fn remove(elem: impl Element) { 110 | elem.syntax_element().detach(); 111 | } 112 | pub fn remove_all(range: RangeInclusive) { 113 | replace_all(range, Vec::new()); 114 | } 115 | pub fn remove_all_iter(range: impl IntoIterator) { 116 | let mut it = range.into_iter(); 117 | if let Some(mut first) = it.next() { 118 | match it.last() { 119 | Some(mut last) => { 120 | if first.index() > last.index() { 121 | mem::swap(&mut first, &mut last); 122 | } 123 | remove_all(first..=last); 124 | } 125 | None => remove(first), 126 | } 127 | } 128 | } 129 | 130 | pub fn replace(old: impl Element, new: impl Element) { 131 | replace_with_many(old, vec![new.syntax_element()]); 132 | } 133 | pub fn replace_with_many(old: impl Element, new: Vec) { 134 | let old = old.syntax_element(); 135 | replace_all(old.clone()..=old, new); 136 | } 137 | pub fn replace_all(range: RangeInclusive, new: Vec) { 138 | let start = range.start().index(); 139 | let end = range.end().index(); 140 | let parent = range.start().parent().unwrap(); 141 | parent.splice_children(start..end + 1, new); 142 | } 143 | 144 | pub fn append_child(node: &(impl Into + Clone), child: impl Element) { 145 | let position = Position::last_child_of(node); 146 | insert(position, child); 147 | } 148 | pub fn append_child_raw(node: &(impl Into + Clone), child: impl Element) { 149 | let position = Position::last_child_of(node); 150 | insert_raw(position, child); 151 | } 152 | 153 | fn ws_before(position: &Position, new: &SyntaxElement) -> Option { 154 | let prev = match &position.repr { 155 | PositionRepr::FirstChild(_) => return None, 156 | PositionRepr::After(it) => it, 157 | }; 158 | 159 | if prev.kind() == T!['{'] && ast::Stmt::can_cast(new.kind()) { 160 | if let Some(stmt_list) = prev.parent().and_then(ast::BlockExpr::cast) { 161 | let mut indent = IndentLevel::from_element(&stmt_list.syntax().clone().into()); 162 | indent.0 += 1; 163 | return Some(make::tokens::whitespace(&format!("\n{indent}"))); 164 | } 165 | } 166 | 167 | ws_between(prev, new) 168 | } 169 | fn ws_after(position: &Position, new: &SyntaxElement) -> Option { 170 | let next = match &position.repr { 171 | PositionRepr::FirstChild(parent) => parent.first_child_or_token()?, 172 | PositionRepr::After(sibling) => sibling.next_sibling_or_token()?, 173 | }; 174 | ws_between(new, &next) 175 | } 176 | fn ws_between(left: &SyntaxElement, right: &SyntaxElement) -> Option { 177 | if left.kind() == SyntaxKind::WHITESPACE || right.kind() == SyntaxKind::WHITESPACE { 178 | return None; 179 | } 180 | if right.kind() == T![;] || right.kind() == T![,] { 181 | return None; 182 | } 183 | if left.kind() == T![<] || right.kind() == T![>] { 184 | return None; 185 | } 186 | Some(make::tokens::single_space()) 187 | } 188 | -------------------------------------------------------------------------------- /crates/oq3_syntax/src/tests.rs: -------------------------------------------------------------------------------- 1 | // Copyright contributors to the openqasm-parser project 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | mod ast_src; 5 | mod sourcegen_ast; 6 | 7 | use crate::ast; 8 | use crate::ast::HasTextName; // for methods: text(), string() 9 | //use oq3_syntax::ast; 10 | // use std::{ 11 | // fs, 12 | // path::{Path, PathBuf}, 13 | // }; 14 | // use ast::HasName; 15 | //use expect_test::expect_file; 16 | // use rayon::prelude::*; 17 | // Adds complication from rust-analyzer 18 | // use test_utils::{bench, bench_fixture, project_root}; 19 | //use crate::{ast, AstNode, SourceFile, SyntaxError}; 20 | use crate::SourceFile; 21 | 22 | // fn collect_stmts(code: &str) -> (usize, Vec){ 23 | // let parse = SourceFile::parse(code); 24 | // let file : SourceFile = parse.tree(); 25 | // let stmts = file.statements().collect::>(); 26 | // return (parse.errors.len(), stmts) 27 | // } 28 | 29 | #[test] 30 | fn parse_measure_1_test() { 31 | let code = r##" 32 | measure q; 33 | "##; 34 | let parse = SourceFile::parse(code); 35 | assert!(parse.ok().is_ok()); 36 | } 37 | 38 | #[test] 39 | fn parse_measure_err1_test() { 40 | let code = r##" 41 | measure; 42 | "##; 43 | let parse = SourceFile::parse(code); 44 | assert_eq!(parse.errors.len(), 1); 45 | } 46 | 47 | #[test] 48 | fn parse_barrier_1_test() { 49 | let code = r##" 50 | barrier q; 51 | "##; 52 | let parse = SourceFile::parse(code); 53 | assert!(parse.ok().is_ok()); 54 | } 55 | 56 | #[test] 57 | fn parse_barrier_2_test() { 58 | let code = r##" 59 | barrier; 60 | "##; 61 | let parse = SourceFile::parse(code); 62 | assert_eq!(parse.errors.len(), 0); 63 | } 64 | 65 | #[test] 66 | fn parse_gate_def_test() { 67 | let code = r##" 68 | gate h q { 69 | U(π/2, 0, π, q); 70 | gphase(-π/4); 71 | } 72 | "##; 73 | let parse = SourceFile::parse(code); 74 | assert!(parse.ok().is_ok()); 75 | } 76 | 77 | #[test] 78 | fn parse_gate_call_test() { 79 | let code = r##" 80 | h q; 81 | "##; 82 | let parse = SourceFile::parse(code); 83 | assert!(parse.ok().is_ok()); 84 | } 85 | 86 | #[test] 87 | fn parse_gate_call_err1_test() { 88 | let code = r##" 89 | h q; () 90 | "##; 91 | let parse = SourceFile::parse(code); 92 | assert_eq!(parse.errors.len(), 1); 93 | } 94 | 95 | #[test] 96 | fn parse_let_test() { 97 | let code = r##" 98 | def myfunc() { 99 | let x = q; 100 | } 101 | "##; 102 | let parse = SourceFile::parse(code); 103 | assert!(parse.ok().is_ok()); 104 | } 105 | 106 | #[test] 107 | fn parse_qasm_test() { 108 | let code = r##" 109 | gate chpase(x) a, b { 110 | CX a, b; 111 | } 112 | "##; 113 | 114 | let parse = SourceFile::parse(code); 115 | assert!(parse.ok().is_ok()); 116 | } 117 | 118 | #[test] 119 | fn parse_qasm_err1_test() { 120 | let code = r##" 121 | gate chpase() a, b { 122 | CX a, b; 123 | } 124 | "##; 125 | 126 | let parse = SourceFile::parse(code); 127 | assert_eq!(parse.errors.len(), 1); 128 | } 129 | 130 | #[test] 131 | fn parse_qasm_err2_test() { 132 | let code = r##" 133 | gate chpase(x) a b { 134 | CX a, b; 135 | } 136 | "##; 137 | let parse = SourceFile::parse(code); 138 | assert_eq!(parse.errors.len(), 1); 139 | } 140 | 141 | #[test] 142 | fn parse_qasm_err3_test() { 143 | let code = r##" 144 | gate chpase() a b { 145 | CX a, b; 146 | } 147 | "##; 148 | 149 | let parse = SourceFile::parse(code); 150 | assert_eq!(parse.errors.len(), 2); 151 | } 152 | 153 | #[test] 154 | fn parse_qasm_defcal_2_test() { 155 | let code = r##" 156 | defcal xmeasure(int a, int b) q, p { 157 | 1 + 1; 158 | } 159 | "##; 160 | let parse = SourceFile::parse(code); 161 | assert_eq!(parse.errors.len(), 0); 162 | } 163 | 164 | #[test] 165 | fn parse_qasm_defcal_err2_test() { 166 | let code = r##" 167 | defcal xmeasure(int a, b) q, p -> bit { 168 | 1 + 1; 169 | } 170 | "##; 171 | let parse = SourceFile::parse(code); 172 | assert_eq!(parse.errors.len(), 1); 173 | } 174 | 175 | #[test] 176 | fn parse_qasm_defcal_err1_test() { 177 | let code = r##" 178 | defcal xmeasure(int a, int b) q, p -> { 179 | 1 + 1; 180 | } 181 | "##; 182 | let parse = SourceFile::parse(code); 183 | assert_eq!(parse.errors.len(), 1); 184 | } 185 | 186 | #[test] 187 | fn parse_qasm_defcal_test() { 188 | let code = r##" 189 | defcal xmeasure(int a, int b) q, p -> bit { 190 | 1 + 1; 191 | } 192 | "##; 193 | let parse = SourceFile::parse(code); 194 | assert_eq!(parse.errors.len(), 0); 195 | } 196 | 197 | #[test] 198 | fn parse_qasm_def_test() { 199 | let code = r##" 200 | def xmeasure(int q, int q2) -> bit { 201 | h q; 202 | return measure q; 203 | } 204 | "##; 205 | let parse = SourceFile::parse(code); 206 | assert_eq!(parse.errors.len(), 0); 207 | } 208 | 209 | #[test] 210 | fn parse_qasm_def2_test() { 211 | let code = r##" 212 | def xmeasure(q) -> bit { 213 | h q; 214 | return measure q; 215 | } 216 | "##; 217 | let parse = SourceFile::parse(code); 218 | assert_eq!(parse.errors.len(), 1); 219 | } 220 | 221 | #[test] 222 | fn with_details_test() { 223 | use crate::ast; 224 | use ast::HasName; 225 | 226 | let code = r##" 227 | defcal xmeasure(int a, int b) q, p -> bit { 228 | 1 + 1; 229 | } 230 | "##; 231 | 232 | // Get tree and list of errors 233 | let parse = SourceFile::parse(code); 234 | assert_eq!(parse.errors.len(), 0); 235 | 236 | // Get just the tree, as a SyntaxNode 237 | let file: SourceFile = parse.tree(); 238 | 239 | let mut defcal = None; 240 | for stmt in file.statements() { 241 | if let ast::Stmt::DefCal(f) = stmt { 242 | defcal = Some(f) 243 | } 244 | } 245 | let defcal: ast::DefCal = defcal.unwrap(); 246 | let name: Option = defcal.name(); 247 | let name = name.unwrap(); 248 | assert_eq!(name.text(), "xmeasure"); 249 | } 250 | 251 | #[test] 252 | fn variable_declaration() { 253 | let code = r##" 254 | int x; 255 | "##; 256 | let parse = SourceFile::parse(code); 257 | assert!(parse.errors().is_empty()); 258 | let mut stmts = parse.tree().statements(); 259 | let decl = match stmts.next() { 260 | Some(ast::Stmt::ClassicalDeclarationStatement(s)) => s, 261 | _ => unreachable!(), 262 | }; 263 | let scalar_type = decl.scalar_type().unwrap(); 264 | assert_eq!(scalar_type.kind(), ast::ScalarTypeKind::Int); 265 | assert!(scalar_type.designator().is_none()); 266 | } 267 | 268 | #[test] 269 | fn parse_cast_expr_test_1() { 270 | let code = r##" 271 | int(x); 272 | "##; 273 | let parse = SourceFile::parse(code); 274 | assert_eq!(parse.errors.len(), 0); 275 | } 276 | 277 | #[test] 278 | fn parse_cast_expr_test_2() { 279 | let code = r##" 280 | uint(x); 281 | "##; 282 | let parse = SourceFile::parse(code); 283 | assert_eq!(parse.errors.len(), 0); 284 | } 285 | 286 | #[test] 287 | fn parse_cast_expr_test_3() { 288 | let code = r##" 289 | float(x); 290 | "##; 291 | let parse = SourceFile::parse(code); 292 | assert_eq!(parse.errors.len(), 0); 293 | } 294 | 295 | #[test] 296 | fn parse_cast_expr_test_4() { 297 | let code = r##" 298 | int[32](x); 299 | "##; 300 | let parse = SourceFile::parse(code); 301 | assert_eq!(parse.errors.len(), 0); 302 | } 303 | 304 | #[test] 305 | fn parse_cast_expr_test_5() { 306 | let code = r##" 307 | z + int(x); 308 | "##; 309 | let parse = SourceFile::parse(code); 310 | assert_eq!(parse.errors.len(), 0); 311 | } 312 | 313 | #[test] 314 | fn parse_cast_expr_test_6() { 315 | let code = r##" 316 | int(x) + z; 317 | "##; 318 | let parse = SourceFile::parse(code); 319 | assert_eq!(parse.errors.len(), 0); 320 | } 321 | 322 | // Issue #208 and associated PR 323 | #[test] 324 | fn parse_cast_expr_test_7() { 325 | let code = r##" 326 | z + int[32](x); 327 | "##; 328 | let parse = SourceFile::parse(code); 329 | assert_eq!(parse.errors.len(), 0); 330 | } 331 | -------------------------------------------------------------------------------- /crates/oq3_syntax/src/tests/ast_src.rs: -------------------------------------------------------------------------------- 1 | // Copyright contributors to the openqasm-parser project 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //! Defines input for code generation process. 5 | 6 | pub(crate) struct KindsSrc<'a> { 7 | pub(crate) punct: &'a [(&'a str, &'a str)], 8 | pub(crate) keywords: &'a [&'a str], 9 | pub(crate) literals: &'a [&'a str], 10 | pub(crate) scalar_types: &'a [&'a str], 11 | pub(crate) tokens: &'a [&'a str], 12 | pub(crate) nodes: &'a [&'a str], 13 | } 14 | 15 | pub(crate) const KINDS_SRC: KindsSrc<'_> = KindsSrc { 16 | punct: &[ 17 | ("!", "BANG"), 18 | ("!=", "NEQ"), 19 | ("#", "POUND"), 20 | ("$", "DOLLAR"), 21 | ("%", "PERCENT"), 22 | ("%=", "PERCENTEQ"), 23 | ("&", "AMP"), 24 | ("&&", "AMP2"), 25 | ("&=", "AMPEQ"), 26 | ("(", "L_PAREN"), 27 | (")", "R_PAREN"), 28 | ("*", "STAR"), 29 | ("*=", "STAREQ"), 30 | ("+", "PLUS"), 31 | ("++", "DOUBLE_PLUS"), 32 | ("+=", "PLUSEQ"), 33 | (",", "COMMA"), 34 | ("-", "MINUS"), 35 | ("-=", "MINUSEQ"), 36 | ("->", "THIN_ARROW"), 37 | (".", "DOT"), 38 | ("..", "DOT2"), 39 | ("...", "DOT3"), 40 | ("..=", "DOT2EQ"), 41 | ("/", "SLASH"), 42 | ("/=", "SLASHEQ"), 43 | (":", "COLON"), 44 | ("::", "COLON2"), 45 | (";", "SEMICOLON"), 46 | ("<", "L_ANGLE"), 47 | ("<<", "SHL"), 48 | ("<<=", "SHLEQ"), 49 | ("<=", "LTEQ"), 50 | ("=", "EQ"), 51 | ("==", "EQ2"), 52 | ("=>", "FAT_ARROW"), 53 | (">", "R_ANGLE"), 54 | (">=", "GTEQ"), 55 | (">>", "SHR"), 56 | (">>=", "SHREQ"), 57 | ("?", "QUESTION"), 58 | ("@", "AT"), 59 | ("[", "L_BRACK"), 60 | ("]", "R_BRACK"), 61 | ("^", "CARET"), 62 | ("^=", "CARETEQ"), 63 | ("_", "UNDERSCORE"), 64 | ("{", "L_CURLY"), 65 | ("|", "PIPE"), 66 | ("|=", "PIPEEQ"), 67 | ("||", "PIPE2"), 68 | ("}", "R_CURLY"), 69 | ("~", "TILDE"), 70 | ], 71 | keywords: &[ 72 | "OPENQASM", 73 | "barrier", 74 | "box", 75 | "cal", 76 | "const", 77 | "def", 78 | "defcal", 79 | "defcalgrammar", 80 | "delay", 81 | "extern", 82 | "gate", 83 | "gphase", // This is a slight hack because a `gphase` call has unique syntax. 84 | "include", 85 | "let", 86 | "measure", 87 | "pragma", 88 | "reset", 89 | // Flow control 90 | "break", 91 | "case", 92 | "continue", 93 | "default", 94 | "else", 95 | "end", 96 | "for", 97 | "if", 98 | "in", 99 | "return", 100 | "switch", 101 | "while", 102 | // Types 103 | "array", 104 | "creg", 105 | "input", 106 | "mutable", 107 | "output", 108 | "qreg", 109 | "qubit", 110 | "readonly", 111 | "void", 112 | // Gate modifiers 113 | "ctrl", 114 | "inv", 115 | "negctrl", 116 | "pow", 117 | // I suppose these are literals 118 | "false", 119 | "true", 120 | ], 121 | // GJL: try introducing scalar_types to help parse var declarations. May not be useful 122 | // sourcegen_ast.rs can convert these to upper snake case. 123 | scalar_types: &[ 124 | "angle", "bit", "bool", "complex", "duration", "float", "int", "stretch", "uint", 125 | ], 126 | // These are already upper snake case. 127 | literals: &[ 128 | "BIT_STRING", 129 | "BYTE", 130 | "CHAR", 131 | "FLOAT_NUMBER", 132 | "INT_NUMBER", 133 | "STRING", 134 | ], 135 | tokens: &[ 136 | "ANNOTATION", 137 | "COMMENT", 138 | "ERROR", 139 | "HARDWAREIDENT", 140 | "IDENT", 141 | "PRAGMA", 142 | "WHITESPACE", 143 | ], 144 | nodes: &[ 145 | "ANNOTATION_STATEMENT", 146 | "BARRIER", 147 | "CAL", 148 | "CONST", 149 | "DEF", 150 | "DEF_CAL", 151 | "DEF_CAL_GRAMMAR", 152 | "DELAY_STMT", 153 | "GATE", 154 | "MEASURE", 155 | "PRAGMA_STATEMENT", 156 | "RESET", 157 | "SOURCE_FILE", 158 | "TIMING_LITERAL", 159 | // atoms 160 | "ARRAY_EXPR", 161 | "BLOCK_EXPR", 162 | "BOX_EXPR", 163 | "BREAK_STMT", 164 | "CASE_EXPR", 165 | "CONTINUE_STMT", 166 | "END_STMT", 167 | "END_STMT", 168 | "FOR_ITERABLE", 169 | "FOR_STMT", 170 | "LET_STMT", 171 | "PAREN_EXPR", 172 | "RETURN_EXPR", 173 | "STMT_LIST", 174 | "SWITCH_CASE_STMT", 175 | "TUPLE_EXPR", 176 | "WHILE_STMT", 177 | // postfix 178 | "CALL_EXPR", 179 | "CAST_EXPRESSION", 180 | "GATE_CALL_EXPR", 181 | "G_PHASE_CALL_EXPR", 182 | "INDEX_EXPR", 183 | "MODIFIED_GATE_CALL_EXPR", 184 | // unary 185 | "ARG_LIST", 186 | "BIN_EXPR", 187 | "DECLARATION", 188 | "EXPR_STMT", 189 | "FILE_PATH", 190 | "INCLUDE", 191 | "LITERAL", 192 | "NAME", 193 | "PARAM", 194 | "PARAM_LIST", 195 | "PREFIX_EXPR", 196 | "QUBIT_LIST", 197 | "RANGE_EXPR", 198 | "TYPE", 199 | "TYPED_PARAM", 200 | "TYPED_PARAM_LIST", 201 | "VERSION", 202 | "VERSION_STRING", 203 | // From ANTLR grammar 204 | "ALIAS_DECLARATION_STATEMENT", 205 | "ARRAY_LITERAL", 206 | "ARRAY_TYPE", 207 | "ASSIGNMENT_STMT", 208 | "CLASSICAL_DECLARATION_STATEMENT", 209 | "DESIGNATOR", 210 | "EXPRESSION_LIST", 211 | "GATE_OPERAND", 212 | "HARDWARE_QUBIT", 213 | "IDENTIFIER", 214 | "INDEXED_IDENTIFIER", 215 | "INDEX_KIND", 216 | "INDEX_OPERATOR", 217 | "I_O_DECLARATION_STATEMENT", 218 | "MEASURE_EXPRESSION", 219 | "OLD_STYLE_DECLARATION_STATEMENT", 220 | "QUANTUM_DECLARATION_STATEMENT", 221 | "QUBIT_TYPE", 222 | "RETURN_SIGNATURE", 223 | "SCALAR_TYPE", 224 | "SET_EXPRESSION", 225 | // Gate modifiers 226 | "CTRL_MODIFIER", 227 | "INV_MODIFIER", 228 | "MODIFIER", 229 | "NEG_CTRL_MODIFIER", 230 | "POW_MODIFIER", 231 | ], 232 | }; 233 | 234 | #[derive(Default, Debug)] 235 | pub(crate) struct AstSrc { 236 | pub(crate) tokens: Vec, 237 | pub(crate) nodes: Vec, 238 | pub(crate) enums: Vec, 239 | } 240 | 241 | #[derive(Debug)] 242 | pub(crate) struct AstNodeSrc { 243 | pub(crate) doc: Vec, 244 | pub(crate) name: String, 245 | pub(crate) traits: Vec, 246 | pub(crate) fields: Vec, 247 | } 248 | 249 | #[derive(Debug, Eq, PartialEq)] 250 | pub(crate) enum Field { 251 | Token(String), 252 | Node { 253 | name: String, 254 | ty: String, 255 | cardinality: Cardinality, 256 | }, 257 | } 258 | 259 | #[derive(Debug, Eq, PartialEq)] 260 | pub(crate) enum Cardinality { 261 | Optional, 262 | Many, 263 | } 264 | 265 | #[derive(Debug)] 266 | pub(crate) struct AstEnumSrc { 267 | pub(crate) doc: Vec, 268 | pub(crate) name: String, 269 | pub(crate) traits: Vec, 270 | pub(crate) variants: Vec, 271 | } 272 | -------------------------------------------------------------------------------- /crates/oq3_syntax/src/token_text.rs: -------------------------------------------------------------------------------- 1 | // Copyright contributors to the openqasm-parser project 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //! Yet another version of owned string, backed by a syntax tree token. 5 | 6 | use std::{cmp::Ordering, fmt, ops}; 7 | 8 | use rowan::GreenToken; 9 | use smol_str::SmolStr; 10 | 11 | pub struct TokenText<'a>(pub(crate) Repr<'a>); 12 | 13 | pub(crate) enum Repr<'a> { 14 | Borrowed(&'a str), 15 | Owned(GreenToken), 16 | } 17 | 18 | impl<'a> TokenText<'a> { 19 | pub(crate) fn borrowed(text: &'a str) -> Self { 20 | TokenText(Repr::Borrowed(text)) 21 | } 22 | 23 | pub(crate) fn owned(green: GreenToken) -> Self { 24 | TokenText(Repr::Owned(green)) 25 | } 26 | 27 | pub fn as_str(&self) -> &str { 28 | match &self.0 { 29 | &Repr::Borrowed(it) => it, 30 | Repr::Owned(green) => green.text(), 31 | } 32 | } 33 | } 34 | 35 | impl ops::Deref for TokenText<'_> { 36 | type Target = str; 37 | 38 | fn deref(&self) -> &str { 39 | self.as_str() 40 | } 41 | } 42 | impl AsRef for TokenText<'_> { 43 | fn as_ref(&self) -> &str { 44 | self.as_str() 45 | } 46 | } 47 | 48 | impl From> for String { 49 | fn from(token_text: TokenText<'_>) -> Self { 50 | token_text.as_str().into() 51 | } 52 | } 53 | 54 | impl From> for SmolStr { 55 | fn from(token_text: TokenText<'_>) -> Self { 56 | SmolStr::new(token_text.as_str()) 57 | } 58 | } 59 | 60 | impl PartialEq<&'_ str> for TokenText<'_> { 61 | fn eq(&self, other: &&str) -> bool { 62 | self.as_str() == *other 63 | } 64 | } 65 | impl PartialEq> for &'_ str { 66 | fn eq(&self, other: &TokenText<'_>) -> bool { 67 | other == self 68 | } 69 | } 70 | impl PartialEq for TokenText<'_> { 71 | fn eq(&self, other: &String) -> bool { 72 | self.as_str() == other.as_str() 73 | } 74 | } 75 | impl PartialEq> for String { 76 | fn eq(&self, other: &TokenText<'_>) -> bool { 77 | other == self 78 | } 79 | } 80 | impl PartialEq for TokenText<'_> { 81 | fn eq(&self, other: &TokenText<'_>) -> bool { 82 | self.as_str() == other.as_str() 83 | } 84 | } 85 | impl Eq for TokenText<'_> {} 86 | impl Ord for TokenText<'_> { 87 | fn cmp(&self, other: &Self) -> Ordering { 88 | self.as_str().cmp(other.as_str()) 89 | } 90 | } 91 | impl PartialOrd for TokenText<'_> { 92 | fn partial_cmp(&self, other: &Self) -> Option { 93 | Some(self.cmp(other)) 94 | } 95 | } 96 | impl fmt::Display for TokenText<'_> { 97 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 98 | fmt::Display::fmt(self.as_str(), f) 99 | } 100 | } 101 | impl fmt::Debug for TokenText<'_> { 102 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 103 | fmt::Debug::fmt(self.as_str(), f) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /crates/oq3_syntax/src/validation/block.rs: -------------------------------------------------------------------------------- 1 | // Copyright contributors to the openqasm-parser project 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //! Logic for validating block expressions i.e. `ast::BlockExpr`. 5 | 6 | // use crate::{ 7 | // ast::{self, AstNode, HasAttrs}, 8 | // SyntaxError, 9 | // SyntaxKind::*, 10 | // }; 11 | 12 | // pub(crate) fn validate_block_expr(block: ast::BlockExpr, errors: &mut Vec) { 13 | // if let Some(parent) = block.syntax().parent() { 14 | // match parent.kind() { 15 | // FN | EXPR_STMT | STMT_LIST => return, 16 | // _ => {} 17 | // } 18 | // } 19 | // if let Some(stmt_list) = block.stmt_list() { 20 | // errors.extend(stmt_list.attrs().filter(|attr| attr.kind().is_inner()).map(|attr| { 21 | // SyntaxError::new( 22 | // "A block in this position cannot accept inner attributes", 23 | // attr.syntax().text_range(), 24 | // ) 25 | // })); 26 | // } 27 | // } 28 | -------------------------------------------------------------------------------- /dummy_triagebot.toml: -------------------------------------------------------------------------------- 1 | # Presence of this file is checked in sourcegen::project_root in ra_ap_sourcegen 2 | # If this file is not found, codegen is aborted. 3 | -------------------------------------------------------------------------------- /local_CI.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # Run locally the same checks that are done remotely in CI 4 | # Works on linux, and maybe Mac OS. 5 | 6 | cargo fmt --all -- --check || exit 1 7 | cargo build --release --verbose || exit 1 8 | cargo test --verbose --lib --tests -- --skip sourcegen_ast --skip sourcegen_ast_nodes || exit 1 9 | cargo clippy --all-targets -- -D warnings -D clippy::dbg_macro || exit 1 10 | 11 | -------------------------------------------------------------------------------- /run_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/env sh 2 | 3 | # Copyright contributors to the openqasm-parser project 4 | # SPDX-License-Identifier: Apache-2.0 5 | 6 | # Run the tests, but skip the tests that do codegen. They currently 7 | # are breaking the source. Or may break it. 8 | # r-a uses some system to control this that we have not, and may never, set up. 9 | 10 | cargo test --lib --tests -- --skip sourcegen_ast --skip sourcegen_ast_nodes 11 | -------------------------------------------------------------------------------- /run_tests_less.sh: -------------------------------------------------------------------------------- 1 | #!/bin/env sh 2 | 3 | # Copyright contributors to the openqasm-parser project 4 | # SPDX-License-Identifier: Apache-2.0 5 | 6 | # Same as run_tests.sh, but pipe stderr to less 7 | 8 | # Run the tests, but skip the tests that do codegen. They currently 9 | # are breaking the source. Or may break it. 10 | # r-a uses some system to control this that we have not, and may never, set up. 11 | 12 | cargo test --lib --tests --color always -- --skip sourcegen_ast --skip sourcegen_ast_nodes 2>&1 | less -R 13 | --------------------------------------------------------------------------------