├── .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 | [](https://opensource.org/licenses/Apache-2.0)
3 | [](https://rust-lang.github.io/rfcs/2495-min-rust-version.html)
4 | [](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 |
--------------------------------------------------------------------------------