From 55cf69a5914be4f6b084a89e9313dd976747423b Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Fri, 11 Oct 2024 14:29:03 -0400 Subject: [PATCH 1/2] Files generated by PkgTemplates PkgTemplates version: 0.7.52 --- .github/dependabot.yml | 7 + .github/workflows/CI.yml | 78 ++ .github/workflows/CompatHelper.yml | 16 + .github/workflows/Documentation.yml | 26 + .github/workflows/TagBot.yml | 31 + .gitignore | 2 + LICENSE | 21 + Project.toml | 29 + README.md | 3 + docs/Project.toml | 3 + docs/make.jl | 23 + docs/src/index.md | 94 ++ src/Quasar.jl | 1768 +++++++++++++++++++++++++++ src/builtin_functions.jl | 25 + test/builtin_gates.jl | 79 ++ test/runtests.jl | 1453 ++++++++++++++++++++++ test/stdgates.inc | 82 ++ 17 files changed, 3740 insertions(+) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/CI.yml create mode 100644 .github/workflows/CompatHelper.yml create mode 100644 .github/workflows/Documentation.yml create mode 100644 .github/workflows/TagBot.yml create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 Project.toml create mode 100644 README.md create mode 100644 docs/Project.toml create mode 100644 docs/make.jl create mode 100644 docs/src/index.md create mode 100644 src/Quasar.jl create mode 100644 src/builtin_functions.jl create mode 100644 test/builtin_gates.jl create mode 100644 test/runtests.jl create mode 100644 test/stdgates.inc diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..700707c --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" # Location of package manifests + schedule: + interval: "weekly" diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml new file mode 100644 index 0000000..b0cf78c --- /dev/null +++ b/.github/workflows/CI.yml @@ -0,0 +1,78 @@ +name: CI +on: + push: + branches: + - main + tags: ['*'] + pull_request: + workflow_dispatch: +concurrency: + # Skip intermediate builds: always. + # Cancel intermediate builds: only if it is a pull request build. + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }} +jobs: + test: + name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }} + runs-on: ${{ matrix.os }} + timeout-minutes: 10 + permissions: # needed to allow julia-actions/cache to proactively delete old caches that it has created + actions: write + contents: read + strategy: + fail-fast: false + matrix: + version: + - '1' + - 'lts' + - 'pre' + os: + - ubuntu-latest + arch: + - x64 + steps: + - uses: actions/checkout@v4 + - uses: julia-actions/setup-julia@v2 + with: + version: ${{ matrix.version }} + arch: ${{ matrix.arch }} + - uses: julia-actions/cache@v2 + - uses: julia-actions/julia-buildpkg@v1 + - uses: julia-actions/julia-runtest@v1 + - uses: julia-actions/julia-processcoverage@v1 + - uses: codecov/codecov-action@v3 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: lcov.info + docs: + name: Documentation + runs-on: ubuntu-latest + permissions: + actions: write # needed to allow julia-actions/cache to proactively delete old caches that it has created + contents: write + statuses: write + steps: + - uses: actions/checkout@v4 + - uses: julia-actions/setup-julia@v2 + with: + version: '1' + - uses: julia-actions/cache@v2 + - uses: julia-actions/julia-buildpkg@v1 # forces PkgServer refresh + - name: Configure doc environment + shell: julia --project=docs --color=yes {0} + run: | + using Pkg + Pkg.Registry.update() + Pkg.develop(PackageSpec(path=pwd())) + Pkg.instantiate() + - uses: julia-actions/julia-buildpkg@v1 + - uses: julia-actions/julia-docdeploy@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Run doctests + shell: julia --project=docs --color=yes {0} + run: | + using Documenter: DocMeta, doctest + using Quasar + DocMeta.setdocmeta!(Quasar, :DocTestSetup, :(using Quasar); recursive=true) + doctest(Quasar) diff --git a/.github/workflows/CompatHelper.yml b/.github/workflows/CompatHelper.yml new file mode 100644 index 0000000..cba9134 --- /dev/null +++ b/.github/workflows/CompatHelper.yml @@ -0,0 +1,16 @@ +name: CompatHelper +on: + schedule: + - cron: 0 0 * * * + workflow_dispatch: +jobs: + CompatHelper: + runs-on: ubuntu-latest + steps: + - name: Pkg.add("CompatHelper") + run: julia -e 'using Pkg; Pkg.add("CompatHelper")' + - name: CompatHelper.main() + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + COMPATHELPER_PRIV: ${{ secrets.DOCUMENTER_KEY }} + run: julia -e 'using CompatHelper; CompatHelper.main()' diff --git a/.github/workflows/Documentation.yml b/.github/workflows/Documentation.yml new file mode 100644 index 0000000..c13a234 --- /dev/null +++ b/.github/workflows/Documentation.yml @@ -0,0 +1,26 @@ +name: Documentation + +on: + push: + branches: + - main # update to match your development branch (master, main, dev, trunk, ...) + tags: '*' + +jobs: + build: + permissions: + contents: write + statuses: write + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: julia-actions/setup-julia@v2 + with: + version: '1' + - name: Install dependencies + run: julia --project=docs/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()' + - name: Build and deploy + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: julia --project=docs/ docs/make.jl + diff --git a/.github/workflows/TagBot.yml b/.github/workflows/TagBot.yml new file mode 100644 index 0000000..0cd3114 --- /dev/null +++ b/.github/workflows/TagBot.yml @@ -0,0 +1,31 @@ +name: TagBot +on: + issue_comment: + types: + - created + workflow_dispatch: + inputs: + lookback: + default: "3" +permissions: + actions: read + checks: read + contents: write + deployments: read + issues: read + discussions: read + packages: read + pages: read + pull-requests: read + repository-projects: read + security-events: read + statuses: read +jobs: + TagBot: + if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot' + runs-on: ubuntu-latest + steps: + - uses: JuliaRegistries/TagBot@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + ssh: ${{ secrets.DOCUMENTER_KEY }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9ad3775 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*Manifest.toml +lcov.info diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ad8fae4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Katharine Hyatt and contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Project.toml b/Project.toml new file mode 100644 index 0000000..d5e4934 --- /dev/null +++ b/Project.toml @@ -0,0 +1,29 @@ +name = "Quasar" +uuid = "86e06105-936e-439e-acf0-7687560b0bd9" +authors = ["Katharine Hyatt and contributors"] +version = "1.0.0-DEV" + +[deps] +AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" +Automa = "67c07d97-cdcb-5c2c-af73-a7f9c32a568b" +DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" +Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" +PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a" + +[compat] +AbstractTrees = "0.4.5" +Automa = "1.0.4" +DataStructures = "0.18.20" +Dates = "1" +PrecompileTools = "1.2.1" +julia = "1.6" + +[extras] +Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" +Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[targets] +test = ["Test", "Dates", "LinearAlgebra", "Statistics"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..2db17cc --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# Quasar + +[![Build Status](https://github.com/kshyatt-aws/Quasar.jl/actions/workflows/CI.yml/badge.svg?branch=main)](https://github.com/kshyatt-aws/Quasar.jl/actions/workflows/CI.yml?query=branch%3Amain) diff --git a/docs/Project.toml b/docs/Project.toml new file mode 100644 index 0000000..255e517 --- /dev/null +++ b/docs/Project.toml @@ -0,0 +1,3 @@ +[deps] +Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +Quasar = "86e06105-936e-439e-acf0-7687560b0bd9" diff --git a/docs/make.jl b/docs/make.jl new file mode 100644 index 0000000..5b8bf31 --- /dev/null +++ b/docs/make.jl @@ -0,0 +1,23 @@ +using Quasar +using Documenter + +DocMeta.setdocmeta!(Quasar, :DocTestSetup, :(using Quasar; recursive=true)) + +makedocs(; + modules=[Quasar], + sitename="Quasar.jl", + repo="github.com/kshyatt-aws/Quasar.jl", + format=Documenter.HTML(; + canonical="github.com/kshyatt-aws/Quasar.jl", + edit_link="main", + assets=String[], + ), + pages=[ + "Home" => "index.md", + ], +) + +deploydocs(; + repo="github.com/kshyatt-aws/Quasar.jl", + devbranch="main", +) diff --git a/docs/src/index.md b/docs/src/index.md new file mode 100644 index 0000000..f3fc00d --- /dev/null +++ b/docs/src/index.md @@ -0,0 +1,94 @@ +```@meta +DocTestSetup = quote using Quasar end +CurrentModule = Quasar +``` + +# Quasar + +`Quasar.jl`, the Qu(antum) as(sembly) (lex/pars)er, is a package for lexing and parsing the [OpenQASM quantum assembly IR](https://openqasm.com/). + +`Quasar` works in a two-step fashion: first, it uses a tokenizer to generate an [abstract syntax tree (AST)](https://en.wikipedia.org/wiki/Abstract_syntax_tree) representing the +quantum program, which is a nested structure of `QasmExpression`s. +Then, it walks (visits) the AST to evaluate all loops, conditionals, gate calls, and variable declarations. + +`Quasar` has support for: + +- Basic builtin OpenQASM 3 gates `gphase` and `U` +- Custom gate definitions +- `for` and `while` loops +- `if`, `else`, and `switch` conditional statements +- Builtin OpenQASM3 functions +- Function definitions and calls +- Casting classical types +- Timing statements `barrier` and `delay` +- Pragmas (see below) + +What is *not* yet supported: +- `angle` types +- Annotations +- OpenPulse + +If you need any of these features, feel free to open an issue requesting support for them! + +## Quick Start + +For more extensive examples, see the tests in `test/`. Here, we can parse and visit a simple OpenQASM 3.0 program to generate a Bell circuit: + +```jldoctest +julia> qasm = """OPENQASM 3.0;\ngate h a { U(π/2, 0, π) a; gphase(-π/4);};\ngate x a { U(π, 0, π) a; gphase(-π/2);};\ngate cx a, b { ctrl @ x a, b; };\ndef bell(qubit q0, qubit q1) {\n h q0;\n cx q0, q1;\n}\nqubit[2] q;\nbell(q[0], q[1]);\nbit[2] b = "00";\nb[0] = measure q[0];\nb[1] = measure q[1];\n"""; + +julia> parsed = Quasar.parse_qasm(qasm); + +julia> visitor = Quasar.QasmProgramVisitor(); + +julia> visitor(parsed); +``` + +You can supply [inputs](https://openqasm.com/language/directives.html#input-output) to your program +by creating the `QasmProgramVisitor` with a `Dict{String, Any}` containing the names of the input variables +as the keys and their values as the `Dict`'s values. This allows you to re-use the same AST multiple times +for different inputs. + +Once the visitor has walked the AST, its `instructions` field contains `CircuitInstruction` `NamedTuple`s you can +use to construct your own circuit instruction types. These tuples have fields: + +- `type::String` - the name of the instruction, e.g. `"measure"` or `"cx"` +- `arguments` - any arguments the instruction accepts, such as an angle for an `rx` gate or a `Period` for a `duration` +- `targets::Vector{Int}` - the qubits targeted by this instruction. This should include any control qubits. +- `controls::Vector{Pair{Int,Int}}` - any controls (including `negctrl`) applied to the instruction. The first item in the pair is the qubit, the second is the value controlled upon, so that `2=>0` represents a `negctrl` on qubit 2. +- `exponent::Float64` - the `pow` modifier applied to the instruction, if any + +You can use a package such as [`StructTypes.jl`](https://github.com/JuliaData/StructTypes.jl) to build your circuits and programs from these named tuples. + +In the above example, the `h`, `x`, and `cx` were defined in terms of the built-in gates `U` and `gphase`. In many cases, you may have an implementation of these +gates that is simpler or more efficient. In that case, you can supply your own gate definition file using the [`include` instruction](https://openqasm.com/language/comments.html#included-files), or provide a *function* to generate a dictionary of builtin gates to `Quasar.jl`: + +```julia +using Quasar + +function my_builtin_generator() + ... +end + +Quasar.builtin_gates[] = my_builtin_generator +``` + +A generator function is used here in order to allow visitors to overwrite builtin functions in certain scopes without corrupting the reference definition. + +## Working with `pragma`s + +[OpenQASM `pragma`s](https://openqasm.com/language/directives.html#pragmas) allow specific platforms to provide custom instructions. +`Quasar.jl` provides a bare `parse_pragma` function you can override and extend to support parsing your pragma constructs. This will +teach `Quasar.jl` how to incorporate your `pragma` instructions into the AST. You will also need to write a `visit_pragma` function +for the AST walking step. You may want to use `pragma`s to support custom circuit operations (like noise channels) or results. + +Your `parse_pragma` should take four arguments: +- `tokens` -- some vector of tokens generated by the tokenizer which represents the `pragma` line, with the leading `#pragma` removed +- `stack` -- the current stack of `QasmExpressions`, used for error reporting +- `start` -- the location in the QASM source string of the start of the `#pragma` line, used for error reporting +- `qasm` -- the QASM source string, used for error reporting + +Your `visit_pragma` should take two arguments: +- `visitor` -- any subtype of `AbstractVisitor`, e.g. a `QasmProgramVisitor`, `QasmForLoopVisitor`, etc. +- `expr` -- the `QasmExpression` containing your full `pragma` invocation, generated by the above `parse_pragma` function + diff --git a/src/Quasar.jl b/src/Quasar.jl new file mode 100644 index 0000000..c5e23ae --- /dev/null +++ b/src/Quasar.jl @@ -0,0 +1,1768 @@ +module Quasar + +using Automa, AbstractTrees, DataStructures, Dates, PrecompileTools +using DataStructures: Stack + +export parse_qasm, QasmProgramVisitor + +struct QasmParseError <: Exception + message::String + parse_stack::Stack + position::Int + qasm::String +end +function Base.showerror(io::IO, err::QasmParseError) + print(io, "QasmParseError: ") + print(io, err.message * "\n") + max_codeunits = min(length(err.qasm), err.position+100) + print(io, "Qasm location: ", err.qasm[err.position:max_codeunits]) +end + +include("builtin_functions.jl") +const unicode = re"α|β|γ|δ|ϵ|ε|ϕ|φ|ζ|η|θ|ϑ|ι|κ|λ|μ|ν|ξ|ρ|σ|ς|υ|χ|ψ|ω" +const first_letter = re"[A-Za-z_]" | unicode +const general_letter = first_letter | re"[0-9]" + +const prefloat = re"[-+]?([0-9]+\.[0-9]*|[0-9]*\.[0-9]+)" +const integer = re"[-+]?[0-9]+" +const float = prefloat | ((prefloat | re"[-+]?[0-9]+") * re"[eE][-+]?[0-9]+") + +const qasm_tokens = [ + :identifier => first_letter * rep(general_letter), + :irrational => re"π|pi|τ|tau|ℯ|ℇ|euler", + :comma => re",", + :colon => re":", + :semicolon => re";", + :question => re"\?", + :equal => re"=", + :lparen => re"\(", + :rparen => re"\)", + :lbracket => re"\[", + :rbracket => re"]", + :lbrace => re"{", + :rbrace => re"}", + :annot => re"@[*]", + :at => re"@", + :version => re"OPENQASM", + :input => re"input", + :output => re"output", + :pragma => re"#pragma", + :qubit => re"qubit", + :hw_qubit => re"$[0-9]+", + :gate_def => re"gate", + :function_def => re"def", + :if_block => re"if", + :else_block => re"else", + :switch_block => re"switch", + :while_block => re"while", + :in_token => re"in", + :for_block => re"for", + :return_token => re"return", + :control_mod => re"ctrl", + :negctrl_mod => re"negctrl", + :inverse_mod => re"inv", + :power_mod => re"pow", + :measure => re"measure", + :arrow_token => re"->", + :reset_token => re"reset", + :delay_token => re"delay", + :barrier_token => re"barrier", + :void => re"void", + :const_token => re"const", + :assignment => re"=|-=|\+=|\*=|/=|^=|&=|\|=|<<=|>>=", + :operator => re"-|\+|\++|\*|\*\*|/|%|&|&&|\||\|\||^|!|!=|~|>|<|<<|>>|>=|<=|=>|==", + :boolean => re"true|false", + :bitstring => re"\"([01] _?)* [01]\"", + :all_token => re"all", + :break_token => re"break", + :mutable => re"mutable", + :readonly => re"readonly", + :builtin_gate => re"gphase|U", + :alias => re"let", + :box => re"box", + :end_token => re"end", + :dim_token => re"#dim[ ]?=[ ]?[0-7]", + :im_token => re"im", + :case => re"case", + :default => re"default", + :keyword => re"creg|qreg", + :oct => re"0o[0-7]+", + :bin => re"(0b|0B)[0-1]+", + :hex => re"0x[0-9A-Fa-f]+", + :dot => re"\.", + :integer_token => integer, + :float_token => float, + :include_token => re"include", + :continue_token => re"continue", + :octal_integer => re"0o([0-7]_?)* [0-7]", + :hex_integer => re"(0x|0X) ([0-9a-fA-F] _?)* [0-9a-fA-F]", + :hardware_qubit => re"$ [0-9]+", + :line_comment => re"//", + :block_comment => re"/\* .*? \*/", + :char => '\'' * (re"[ -&(-~]" | ('\\' * re"[ -~]")) * '\'', + :string_token => '"' * rep(re"[ !#-~]" | re"\\\\\"") * '"' | '\'' * rep(re"[ -&(-~]" | ('\\' * re"[ -~]")) * '\'', + :newline => re"\r?\n", + :spaces => re"[\t ]+", + :classical_type => re"bool|uint|int|float|angle|complex|array|bit|stretch|duration", + :durationof_token => re"durationof", # this MUST be lower than classical_type to preempt duration + :duration_literal => (float | integer) * re"dt|ns|us|ms|s|\xce\xbc\x73", # transcode'd μs + :forbidden_keyword => re"cal|defcal|extern", +] + +const dt_type = Ref{DataType}() +const builtin_gates = Ref{Function}() + +function __init__() + dt_type[] = Nanosecond + builtin_gates[] = basic_builtin_gates +end +function parse_pragma end +function visit_pragma end + +@eval @enum Token error $(first.(qasm_tokens)...) +make_tokenizer((error, + [Token(i) => j for (i,j) in enumerate(last.(qasm_tokens))] +)) |> eval + +struct QasmExpression + head::Symbol + args::Vector{Any} + QasmExpression(head::Symbol, args::Vector) = new(head, args) +end +QasmExpression(head) = QasmExpression(head, []) +QasmExpression(head, @nospecialize(args...)) = QasmExpression(head, collect(args)) +QasmExpression(head, arg) = QasmExpression(head, [arg]) + +Base.show(io::IO, qasm_expr::QasmExpression) = print_tree(io, qasm_expr, maxdepth=10) +Base.iterate(qasm_expr::QasmExpression) = (qasm_expr, nothing) +Base.iterate(qasm_expr::QasmExpression, ::Nothing) = nothing +Base.length(qasm_expr::QasmExpression) = 1 +Base.push!(qasm_expr::QasmExpression, arg) = push!(qasm_expr.args, arg) +Base.append!(qasm_expr::QasmExpression, arg::QasmExpression) = push!(qasm_expr.args, arg) +Base.append!(qasm_expr::QasmExpression, args::Vector) = append!(qasm_expr.args, args) +Base.pop!(qasm_expr::QasmExpression) = pop!(qasm_expr.args) +Base.copy(qasm_expr::QasmExpression) = QasmExpression(qasm_expr.head, deepcopy(qasm_expr.args)) + +head(qasm_expr::QasmExpression) = qasm_expr.head + +AbstractTrees.children(qasm_expr::QasmExpression) = qasm_expr.args +AbstractTrees.printnode(io::IO, qasm_expr::QasmExpression) = print(io, "QasmExpression :$(qasm_expr.head)") + +function Base.:(==)(qasm_a::QasmExpression, qasm_b::QasmExpression) + a_children = children(qasm_a) + b_children = children(qasm_b) + length(a_children) != length(b_children) && return false + return a_children == b_children +end + +parse_hw_qubit(token, qasm) = QasmExpression(:hw_qubit, qasm[token[1]:token[1]+token[2]-1]) +parse_identifier(token, qasm) = QasmExpression(:identifier, String(codeunits(qasm)[token[1]:token[1]+token[2]-1])) +function extract_scope(tokens, stack, start, qasm) + # a "scope" begins with an { and ends with an } + # but we may have nested scope! + opener = popfirst!(tokens) + opener[end] == lbrace || throw(QasmParseError("scope does not open with {", stack, start, qasm)) + # need to match openers to closers to exit the scope + openers_met = 1 + closers_met = 0 + scope_tokens = Tuple{Int64, Int32, Token}[] + while closers_met < openers_met && !isempty(tokens) + next_token = popfirst!(tokens) + next_token[end] == lbrace && (openers_met += 1) + next_token[end] == rbrace && (closers_met += 1) + push!(scope_tokens, next_token) + end + pop!(scope_tokens) # closing } + return scope_tokens +end + +function parse_scope(tokens, stack, start, qasm) + scope_tokens = extract_scope(tokens, stack, start, qasm) + return parse_qasm(scope_tokens, qasm, QasmExpression(:scope)) +end + +function parse_block_body(expr, tokens, stack, start, qasm) + is_scope = tokens[1][end] == lbrace + if is_scope + body = parse_scope(tokens, stack, start, qasm) + body_exprs = convert(Vector{QasmExpression}, collect(Iterators.reverse(body)))::Vector{QasmExpression} + foreach(body_expr->push!(body_exprs[1], body_expr), body_exprs[2:end]) + push!(expr, body_exprs[1]) + else # one line + eol = findfirst(triplet->triplet[end] == semicolon, tokens) + body_tokens = splice!(tokens, 1:eol) + body = parse_expression(body_tokens, stack, start, qasm) + push!(expr, body) + end +end + +function parse_switch_block(tokens, stack, start, qasm) + expr = QasmExpression(:switch) + cond_tokens = extract_parensed(tokens, stack, start, qasm) + push!(cond_tokens, (-1, Int32(-1), semicolon)) + push!(expr, parse_expression(cond_tokens, stack, start, qasm)) + interior_tokens = extract_scope(tokens, stack, start, qasm) + met_default = false + while !isempty(interior_tokens) + next_token = popfirst!(interior_tokens) + if next_token[end] == case + !met_default || throw(QasmParseError("case statement cannot occur after default in switch block.", stack, start, qasm)) + case_expr = QasmExpression(:case) + brace_loc = findfirst(triplet->triplet[end] == lbrace, interior_tokens) + isnothing(brace_loc) && throw(QasmParseError("case statement missing opening {", stack, start, qasm)) + val_tokens = splice!(interior_tokens, 1:brace_loc-1) + push!(val_tokens, (-1, Int32(-1), semicolon)) + push!(case_expr, parse_list_expression(val_tokens, stack, start, qasm)) + parse_block_body(case_expr, interior_tokens, stack, start, qasm) + push!(expr, case_expr) + elseif next_token[end] == default + !met_default || throw(QasmParseError("only one default statement allowed in switch block.", stack, start, qasm)) + default_expr = QasmExpression(:default) + parse_block_body(default_expr, interior_tokens, stack, start, qasm) + push!(expr, default_expr) + met_default = true + elseif next_token[end] == newline + continue + else + throw(QasmParseError("invalid switch-case statement.", stack, start, qasm)) + end + end + return expr +end + +function parse_if_block(tokens, stack, start, qasm) + condition_tokens = extract_parensed(tokens, stack, start, qasm) + push!(condition_tokens, (-1, Int32(-1), semicolon)) + condition_value = parse_expression(condition_tokens, stack, start, qasm) + if_expr = QasmExpression(:if, condition_value) + # handle condition + parse_block_body(if_expr, tokens, stack, start, qasm) + has_else = tokens[1][end] == else_block + if has_else + popfirst!(tokens) # else + else_expr = QasmExpression(:else) + parse_block_body(else_expr, tokens, stack, start, qasm) + push!(if_expr, else_expr) + end + return if_expr +end +function parse_while_loop(tokens, stack, start, qasm) + condition_tokens = extract_parensed(tokens, stack, start, qasm) + push!(condition_tokens, (-1, Int32(-1), semicolon)) + condition_value = parse_expression(condition_tokens, stack, start, qasm) + while_expr = QasmExpression(:while, condition_value) + # handle condition + parse_block_body(while_expr, tokens, stack, start, qasm) + return while_expr +end +function parse_for_loop(tokens, loop_var_type, loop_var_name, loop_values, stack, start, qasm) + for_expr = QasmExpression(:for, loop_var_type, loop_var_name, loop_values) + parse_block_body(for_expr, tokens, stack, start, qasm) + return for_expr +end + +function parse_arguments_list(tokens, stack, start, qasm) + arguments = QasmExpression(:arguments) + first(tokens)[end] != lparen && return arguments + interior = extract_parensed(tokens, stack, start, qasm) + push!(interior, (-1, Int32(-1), semicolon)) + push!(arguments, parse_list_expression(interior, stack, start, qasm)) + return arguments +end + +function parse_function_def(tokens, stack, start, qasm) + function_name = popfirst!(tokens) + function_name[end] == identifier || throw(QasmParseError("function definition must have a valid identifier as a name", stack, start, qasm)) + function_name_id = parse_identifier(function_name, qasm) + arguments = parse_arguments_list(tokens, stack, start, qasm) + has_return_type = tokens[1][end] == arrow_token + if has_return_type + arrow = popfirst!(tokens) + tokens[1][end] == classical_type || throw(QasmParseError("function return type must be a classical type", stack, start, qasm)) + return_type = parse_classical_type(tokens, stack, start, qasm) + else + return_type = QasmExpression(:void) + end + expr = QasmExpression(:function_definition, function_name_id, arguments, return_type) + parse_block_body(expr, tokens, stack, start, qasm) + return expr +end +function parse_gate_def(tokens, stack, start, qasm) + gate_name = popfirst!(tokens) + gate_name[end] == identifier || throw(QasmParseError("gate definition must have a valid identifier as a name", stack, start, qasm)) + gate_name_id = parse_identifier(gate_name, qasm) + + gate_args = parse_arguments_list(tokens, stack, start, qasm) + qubit_tokens = splice!(tokens, 1:findfirst(triplet->triplet[end]==lbrace, tokens)-1) + push!(qubit_tokens, (-1, Int32(-1), semicolon)) + target_expr = QasmExpression(:qubit_targets, parse_list_expression(qubit_tokens, stack, start, qasm)) + expr = QasmExpression(:gate_definition, gate_name_id, gate_args, target_expr) + parse_block_body(expr, tokens, stack, start, qasm) + return expr +end + +struct SizedBitVector <: AbstractArray{Bool, 1} + size::QasmExpression + SizedBitVector(size::QasmExpression) = new(size) + SizedBitVector(sbv::SizedBitVector) = new(sbv.size) +end +Base.length(s::SizedBitVector) = s.size +Base.size(s::SizedBitVector) = (s.size,) +Base.show(io::IO, s::SizedBitVector) = print(io, "SizedBitVector{$(s.size.args[end])}") +struct SizedInt <: Integer + size::QasmExpression + SizedInt(size::QasmExpression) = new(size) + SizedInt(sint::SizedInt) = new(sint.size) +end +Base.show(io::IO, s::SizedInt) = print(io, "SizedInt{$(s.size.args[end])}") +struct SizedUInt <: Unsigned + size::QasmExpression + SizedUInt(size::QasmExpression) = new(size) + SizedUInt(suint::SizedUInt) = new(suint.size) +end +Base.show(io::IO, s::SizedUInt) = print(io, "SizedUInt{$(s.size.args[end])}") +struct SizedFloat <: AbstractFloat + size::QasmExpression + SizedFloat(size::QasmExpression) = new(size) + SizedFloat(sfloat::SizedFloat) = new(sfloat.size) +end +Base.show(io::IO, s::SizedFloat) = print(io, "SizedFloat{$(s.size.args[end])}") +struct SizedAngle <: AbstractFloat + size::QasmExpression + SizedAngle(size::QasmExpression) = new(size) + SizedAngle(sangle::SizedAngle) = new(sangle.size) +end +Base.show(io::IO, s::SizedAngle) = print(io, "SizedAngle{$(s.size.args[end])}") +struct SizedComplex <: Number + size::QasmExpression + SizedComplex(size::QasmExpression) = new(size) + SizedComplex(scomplex::SizedComplex) = new(scomplex.size) +end +Base.show(io::IO, s::SizedComplex) = print(io, "SizedComplex{$(s.size.args[end])}") + +struct SizedArray{T,N} <: AbstractArray{T, N} + type::T + size::NTuple{N, Int} +end +function SizedArray(eltype::QasmExpression, size::QasmExpression) + arr_size = if head(size) == :n_dims + ntuple(i->0, size.args[1].args[1]) + else + ntuple(i->size.args[i], length(size.args)) + end + return SizedArray(eltype.args[1], arr_size) +end +Base.show(io::IO, s::SizedArray{T, N}) where {T, N} = print(io, "SizedArray{$(sprint(show, s.type)), $N}") +Base.size(a::SizedArray{T, N}, dim::Int=0) where {T, N} = a.size[dim+1] + +const SizedNumber = Union{SizedComplex, SizedAngle, SizedFloat, SizedInt, SizedUInt} + +function parse_classical_type(tokens, stack, start, qasm) + is_sized = length(tokens) > 1 && tokens[2][end] == lbracket + type_name = popfirst!(tokens) + type_name[end] == classical_type || throw(QasmParseError("classical variable must have a classical type", stack, start, qasm)) + var_type = qasm[type_name[1]:type_name[1]+type_name[2]-1] + if var_type == "complex" + complex_tokens = extract_braced_block(tokens, stack, start, qasm) + eltype = parse_classical_type(complex_tokens, stack, start, qasm) + size = eltype.args[1].size + elseif var_type == "array" + array_tokens = extract_braced_block(tokens, stack, start, qasm) + eltype = parse_classical_type(array_tokens, stack, start, qasm) + first(array_tokens)[end] == comma && popfirst!(array_tokens) + size = parse_expression(array_tokens, stack, start, qasm) + return QasmExpression(:classical_type, SizedArray(eltype, size)) + elseif var_type == "duration" + @warn "duration expression encountered -- currently `duration` is a no-op" + # TODO: add proper parsing of duration expressions, including + # support for units and algebraic durations like 2*a. + return QasmExpression(:classical_type, :duration) + elseif var_type == "stretch" + @warn "stretch expression encountered -- currently `stretch` is a no-op" + # TODO: add proper parsing of stretch expressions + return QasmExpression(:classical_type, :stretch) + else + !any(triplet->triplet[end] == semicolon, tokens) && push!(tokens, (-1, Int32(-1), semicolon)) + size = is_sized ? parse_expression(tokens, stack, start, qasm) : QasmExpression(:integer_literal, -1) + + end + if var_type == "bit" + return QasmExpression(:classical_type, SizedBitVector(size)) + elseif var_type == "int" + return QasmExpression(:classical_type, SizedInt(size)) + elseif var_type == "uint" + return QasmExpression(:classical_type, SizedUInt(size)) + elseif var_type == "float" + return QasmExpression(:classical_type, SizedFloat(size)) + elseif var_type == "complex" + return QasmExpression(:classical_type, SizedComplex(size)) + elseif var_type == "bool" + return QasmExpression(:classical_type, Bool) + end + throw(QasmParseError("could not parse classical type", stack, start, qasm)) +end + +function parse_classical_var(tokens, stack, start, qasm) + # detect if we have a declared size + is_declaration = false + if tokens[end][end] == semicolon + is_declaration = true + pop!(tokens) + end + name = pop!(tokens) + type = parse_classical_type(tokens, stack, start, qasm) + name[end] == identifier || throw(QasmParseError("classical variable must have a valid name", stack, start, qasm)) + return type, parse_identifier(name, qasm) +end + +const binary_assignment_ops = Dict{String, Symbol}( + "="=>Symbol("="), + "-="=>Symbol("-"), + "+="=>Symbol("+"), + "*="=>Symbol("*"), + "/="=>Symbol("/"), + "^="=>Symbol("^"), + "&="=>Symbol("&"), + "|="=>Symbol("|"), + "<<="=>Symbol("<<"), + ">>="=>Symbol(">>"), + ) +function parse_assignment_op(op_token, qasm) + op_string = parse_identifier(op_token, qasm) + return binary_assignment_ops[op_string.args[1]::String] +end + +parse_string_literal(token, qasm) = QasmExpression(:string_literal, String(qasm[token[1]:token[1]+token[2]-1])) +parse_integer_literal(token, qasm) = QasmExpression(:integer_literal, tryparse(Int, qasm[token[1]:token[1]+token[2]-1])) +parse_hex_literal(token, qasm) = QasmExpression(:integer_literal, tryparse(UInt, qasm[token[1]:token[1]+token[2]-1])) +parse_oct_literal(token, qasm) = QasmExpression(:integer_literal, tryparse(Int, qasm[token[1]:token[1]+token[2]-1])) +parse_bin_literal(token, qasm) = QasmExpression(:integer_literal, tryparse(Int, qasm[token[1]:token[1]+token[2]-1])) +parse_float_literal(token, qasm) = QasmExpression(:float_literal, tryparse(Float64, qasm[token[1]:token[1]+token[2]-1])) +parse_boolean_literal(token, qasm) = QasmExpression(:boolean_literal, tryparse(Bool, qasm[token[1]:token[1]+token[2]-1])) +function parse_duration_literal(token, qasm) + str = String(codeunits(qasm)[token[1]:token[1]+token[2]-1]) + duration = if endswith(str, "ns") + Nanosecond(tryparse(Int, chop(str, tail=2))) + elseif endswith(str, "ms") + Millisecond(tryparse(Int, chop(str, tail=2))) + elseif endswith(str, "us") || endswith(str, "μs") + Microsecond(tryparse(Int, chop(str, tail=2))) + elseif endswith(str, "s") + Second(tryparse(Int, chop(str, tail=1))) + elseif endswith(str, "dt") + dt_type[](tryparse(Int, chop(str, tail=2))) + end + QasmExpression(:duration_literal, duration) +end +function parse_irrational_literal(token, qasm) + raw_string = String(codeunits(qasm)[token[1]:token[1]+token[2]-1]) + raw_string == "pi" && return QasmExpression(:irrational_literal, π) + raw_string == "euler" && return QasmExpression(:irrational_literal, ℯ) + raw_string == "tau" && return QasmExpression(:irrational_literal, 2*π) + raw_string == "π" && return QasmExpression(:irrational_literal, π) + raw_string == "τ" && return QasmExpression(:irrational_literal, 2*π) + raw_string ∈ ("ℯ", "ℇ") && return QasmExpression(:irrational_literal, ℯ) +end +function parse_set_expression(tokens::Vector{Tuple{Int64, Int32, Token}}, stack, start, qasm) + interior = extract_scope(tokens, stack, start, qasm) + set_elements = QasmExpression(:array_literal) + push!(interior, (-1, Int32(-1), semicolon)) + while !isempty(interior) + push!(set_elements, parse_expression(interior, stack, start, qasm)) + next_token = first(interior) + next_token[end] == comma && popfirst!(interior) + next_token[end] == semicolon && break + end + return set_elements +end + +function extract_braced_block(tokens::Vector{Tuple{Int64, Int32, Token}}, stack, start, qasm) + bracket_opening = findfirst(triplet->triplet[end] == lbracket, tokens) + bracket_closing = findfirst(triplet->triplet[end] == rbracket, tokens) + isnothing(bracket_opening) && throw(QasmParseError("missing opening [ ", stack, start, qasm)) + opener = popat!(tokens, bracket_opening) + openers_met = 1 + closers_met = 0 + braced_tokens = Tuple{Int64, Int32, Token}[] + while closers_met < openers_met && !isempty(tokens) + next_token = popfirst!(tokens) + next_token[end] == lbracket && (openers_met += 1) + next_token[end] == rbracket && (closers_met += 1) + push!(braced_tokens, next_token) + end + pop!(braced_tokens) # closing ] + push!(braced_tokens, (-1, Int32(-1), semicolon)) + return braced_tokens +end + +function extract_parensed(tokens, stack, start, qasm) + opener = popfirst!(tokens) + opener[end] == lparen || throw(QasmParseError("parentethical expression does not open with (", stack, start, qasm)) + openers_met = 1 + closers_met = 0 + interior_tokens = Tuple{Int64, Int32, Token}[] + while closers_met < openers_met && !isempty(tokens) + next_token = popfirst!(tokens) + next_token[end] == lparen && (openers_met += 1) + next_token[end] == rparen && (closers_met += 1) + push!(interior_tokens, next_token) + end + pop!(interior_tokens) # closing paren + return interior_tokens +end + +function parse_bracketed_expression(tokens::Vector{Tuple{Int64, Int32, Token}}, stack, start, qasm) + interior_tokens = extract_braced_block(tokens, stack, start, qasm) + push!(interior_tokens, (-1, Int32(-1), semicolon)) + return parse_expression(interior_tokens, stack, start, qasm) +end + +function parse_paren_expression(tokens::Vector{Tuple{Int64, Int32, Token}}, stack, start, qasm) + interior_tokens = extract_parensed(tokens, stack, start, qasm) + push!(interior_tokens, (-1, Int32(-1), semicolon)) + return parse_expression(interior_tokens, stack, start, qasm) +end + +function parse_list_expression(tokens::Vector{Tuple{Int64, Int32, Token}}, stack, start, qasm) + expr_list = QasmExpression[] + while !isempty(tokens) && first(tokens)[end] != semicolon + tokens[1][end] == comma && popfirst!(tokens) + next_expr = parse_expression(tokens, stack, start, qasm) + push!(expr_list, next_expr) + end + if length(expr_list) == 1 + return only(expr_list) + else + return QasmExpression(:array_literal, expr_list) + end +end + +function parse_literal(tokens::Vector{Tuple{Int64, Int32, Token}}, stack, start, qasm) + tokens[1][end] == duration_literal && return parse_duration_literal(popfirst!(tokens), qasm) + tokens[1][end] == string_token && return parse_string_literal(popfirst!(tokens), qasm) + tokens[1][end] == hex && return parse_hex_literal(popfirst!(tokens), qasm) + tokens[1][end] == oct && return parse_oct_literal(popfirst!(tokens), qasm) + tokens[1][end] == bin && return parse_bin_literal(popfirst!(tokens), qasm) + tokens[1][end] == irrational && return parse_irrational_literal(popfirst!(tokens), qasm) + tokens[1][end] == boolean && return parse_boolean_literal(popfirst!(tokens), qasm) + tokens[1][end] == integer_token && length(tokens) == 1 && return parse_integer_literal(popfirst!(tokens), qasm) + tokens[1][end] == float_token && length(tokens) == 1 && return parse_float_literal(popfirst!(tokens), qasm) + + is_float = tokens[1][end] == float_token + is_complex = false + is_operator = tokens[2][end] == operator + is_plusminus = is_operator && parse_identifier(tokens[2], qasm).args[1] ∈ ("+","-") + is_terminal = (tokens[2][end] == semicolon || tokens[2][end] == comma || (is_operator && !is_plusminus)) + tokens[1][end] == integer_token && is_terminal && return parse_integer_literal(popfirst!(tokens), qasm) + tokens[1][end] == float_token && is_terminal && return parse_float_literal(popfirst!(tokens), qasm) + splice_end = 1 + if tokens[2][end] == im_token + is_complex = true + splice_end = 2 + elseif is_plusminus && tokens[3][end] ∈ (integer_token, float_token) && tokens[4][end] == im_token + is_complex = true + is_float |= tokens[3][end] == float_token + splice_end = 4 + elseif tokens[2][end] ∈ (integer_token, float_token) && tokens[3][end] == im_token # may have absorbed +/- sign + is_complex = true + is_float |= tokens[2][end] == float_token + splice_end = 3 + end + literal_tokens = splice!(tokens, 1:splice_end) + raw_literal_string = qasm[literal_tokens[1][1]:literal_tokens[end][1]+literal_tokens[end][2]-1] + raw_literal = if is_float + parse(ComplexF64, raw_literal_string) + elseif is_complex + parse(Complex{Int}, raw_literal_string) + else + parse(Int, raw_literal_string) + end + if is_complex # complex float + return QasmExpression(:complex_literal, raw_literal) + elseif is_float + return QasmExpression(:float_literal, raw_literal) + else + return QasmExpression(:integer_literal, raw_literal) + end + throw(QasmParseError("unable to parse literal", stack, start, qasm)) +end + +function parse_qubit_declaration(tokens::Vector{Tuple{Int64, Int32, Token}}, stack, start, qasm) + next_token = tokens[1] + if next_token[end] == lbracket + size_tokens = extract_braced_block(tokens, stack, start, qasm) + size = parse_expression(push!(size_tokens, (-1,-1,semicolon)), stack, start, qasm) + else + size = QasmExpression(:integer_literal, 1) + end + qubit_name = parse_identifier(popfirst!(tokens), qasm) + size.args[1] == -1 && (size.args[1] = 1) + return QasmExpression(:qubit_declaration, qubit_name, size) +end + +function parse_gate_mods(tokens::Vector{Tuple{Int64, Int32, Token}}, stack, start, qasm) + mod_type = popfirst!(tokens) + expr = if mod_type[end] == control_mod + QasmExpression(:control_mod) + elseif mod_type[end] == negctrl_mod + QasmExpression(:negctrl_mod) + elseif mod_type[end] == inverse_mod + QasmExpression(:inverse_mod) + elseif mod_type[end] == power_mod + QasmExpression(:power_mod) + else + throw(QasmParseError("cannot parse token of type $(mod_type[end]) as a gate modifier", stack, start, qasm)) + end + next_token = first(tokens) + if next_token[end] == lparen + arg = parse_paren_expression(tokens, stack, start, qasm) + push!(expr, arg) + next_token = first(tokens) + end + if next_token[end] == at + popfirst!(tokens) + next_token = first(tokens) + if next_token[end] == identifier || next_token[end] == builtin_gate + push!(expr, parse_expression(tokens, stack, start, qasm)) + return expr + else + next_mod_expr = parse_gate_mods(tokens, stack, start, qasm) + push!(expr, next_mod_expr) + return expr + end + end +end + +function parse_expression(tokens::Vector{Tuple{Int64, Int32, Token}}, stack, start, qasm) + start_token = popfirst!(tokens) + next_token = first(tokens) + token_name = QasmExpression(:empty) + if start_token[end] != classical_type && next_token[end] == lbracket + name = parse_identifier(start_token, qasm) + indices = parse_expression(tokens, stack, start, qasm) + expr_indices = length(indices) == 1 ? only(indices) : indices + token_name = QasmExpression(:indexed_identifier, name, expr_indices) + elseif start_token[end] == identifier || start_token[end] == builtin_gate + token_name = parse_identifier(start_token, qasm) + elseif start_token[end] == hw_qubit + token_name = parse_hw_qubit(start_token, qasm) + elseif start_token[end] == qubit + token_name = parse_qubit_declaration(tokens, stack, start, qasm) + elseif start_token[end] == operator + token_name = parse_identifier(start_token, qasm) + elseif start_token[end] == break_token + token_name = QasmExpression(:break) + elseif start_token[end] == continue_token + token_name = QasmExpression(:continue) + elseif start_token[end] == lparen # could be some kind of expression + token_name = parse_paren_expression(pushfirst!(tokens, start_token), stack, start, qasm) + elseif start_token[end] == lbrace # set expression + token_name = parse_set_expression(pushfirst!(tokens, start_token), stack, start, qasm) + elseif start_token[end] == lbracket + token_name = parse_bracketed_expression(pushfirst!(tokens, start_token), stack, start, qasm) + elseif start_token[end] == classical_type + token_name = parse_classical_type(pushfirst!(tokens, start_token), stack, start, qasm) + elseif start_token[end] ∈ (string_token, integer_token, float_token, hex, oct, bin, irrational, dot, boolean, duration_literal) + token_name = parse_literal(pushfirst!(tokens, start_token), stack, start, qasm) + elseif start_token[end] ∈ (mutable, readonly, const_token) + token_name = parse_identifier(start_token, qasm) + elseif start_token[end] == dim_token + raw_dim = qasm[start_token[1]:start_token[1]+start_token[2]-1] + dim = replace(replace(raw_dim, " "=>""), "#dim="=>"") + token_name = QasmExpression(:n_dims, QasmExpression(:integer_literal, parse(Int, dim))) + end + head(token_name) == :empty && throw(QasmParseError("unable to parse line with start token $(start_token[end])", stack, start, qasm)) + next_token = first(tokens) + if next_token[end] == semicolon || next_token[end] == comma || start_token[end] ∈ (lbracket, lbrace) + expr = token_name + elseif start_token[end] == integer_token && next_token[end] == irrational # this is banned! 2π is not supported, 2*π is. + integer_lit = parse_integer_literal(start_token, qasm).args[1] + irrational_lit = parse_irrational_literal(next_token, qasm).args[1] + throw(QasmParseError("expressions of form $(integer_lit)$(irrational_lit) are not supported -- you must separate the terms with a * operator.", stack, start, qasm)) + elseif start_token[end] == operator + unary_op_symbol::Symbol = Symbol(token_name.args[1]::String) + unary_op_symbol ∈ (:~, :!, :-) || throw(QasmParseError("invalid unary operator $unary_op_symbol.", stack, start, qasm)) + next_expr = parse_expression(tokens, stack, start, qasm) + # apply unary op to next_expr + if head(next_expr) ∈ (:identifier, :indexed_identifier, :integer_literal, :float_literal, :string_literal, :irrational_literal, :boolean_literal, :complex_literal, :function_call, :cast, :duration_literal) + expr = QasmExpression(:unary_op, unary_op_symbol, next_expr) + elseif head(next_expr) == :binary_op + # replace first argument + left_hand_side = next_expr.args[2]::QasmExpression + new_left_hand_side = QasmExpression(:unary_op, unary_op_symbol, left_hand_side) + next_expr.args[2] = new_left_hand_side + expr = next_expr + end + elseif next_token[end] == colon + start = token_name + popfirst!(tokens) + second_colon = findfirst(triplet->triplet[end] == colon, tokens) + if !isnothing(second_colon) + step_tokens = push!(splice!(tokens, 1:second_colon-1), (-1, Int32(-1), semicolon)) + popfirst!(tokens) # colon + step = parse_expression(step_tokens, stack, start, qasm)::QasmExpression + else + step = QasmExpression(:integer_literal, 1) + end + if isempty(tokens) || first(tokens)[end] == semicolon #missing stop + stop = QasmExpression(:integer_literal, -1) + else + stop = parse_expression(tokens, stack, start, qasm)::QasmExpression + end + expr = QasmExpression(:range, QasmExpression[start, step, stop]) + elseif next_token[end] == classical_type && start_token[end] ∈ (mutable, readonly, const_token) + type = parse_classical_type(tokens, stack, start, qasm) + is_mutable = (start_token[end] == mutable) + header = is_mutable ? :classical_declaration : :const_declaration + expr = QasmExpression(header, type) + push!(expr, parse_expression(tokens, stack, start, qasm)) + elseif start_token[end] == classical_type && (next_token[end] ∈ (lbracket, identifier)) + expr = QasmExpression(:classical_declaration, token_name) + push!(expr, parse_expression(tokens, stack, start, qasm)) + elseif start_token[end] == classical_type && next_token[end] == lparen + expr = QasmExpression(:cast, token_name) + interior_tokens = extract_parensed(tokens, stack, start, qasm) + push!(interior_tokens, (-1, Int32(-1), semicolon)) + interior = parse_expression(interior_tokens, stack, start, qasm) + push!(expr, interior) + elseif next_token[end] == assignment + op_token = popfirst!(tokens) + next_token = first(tokens) + if next_token[end] ∈ (lparen, lbracket, lbrace, string_token, integer_token, float_token, hex, oct, bin) + right_hand_side = parse_expression(tokens, stack, start, qasm) + elseif next_token[end] == measure + popfirst!(tokens) + right_hand_side = QasmExpression(:measure, parse_expression(tokens, stack, start, qasm)) + elseif next_token[end] == operator + unary_op_token = parse_identifier(popfirst!(tokens), qasm) + next_token = first(tokens) + unary_right_hand_side = next_token[end] == lparen ? parse_paren_expression(tokens, stack, start, qasm) : parse_expression(tokens, stack, start, qasm) + right_hand_side = QasmExpression(:unary_op, Symbol(unary_op_token.args[1]::String), unary_right_hand_side) + else + right_hand_side = parse_expression(tokens, stack, start, qasm)::QasmExpression + end + op_expr = QasmExpression(:binary_op, parse_assignment_op(op_token, qasm), token_name, right_hand_side) + expr = QasmExpression(:classical_assignment, op_expr) + elseif next_token[end] == operator + op_token = parse_identifier(popfirst!(tokens), qasm) + right_hand_side = parse_expression(tokens, stack, start, qasm)::QasmExpression + expr = QasmExpression(:binary_op, Symbol(op_token.args[1]), token_name, right_hand_side) + else # some kind of function or gate call + # either a gate call or function call + arguments = parse_arguments_list(tokens, stack, start, qasm) + next_token = first(tokens) + is_gphase::Bool = (token_name isa QasmExpression && head(token_name) == :identifier && token_name.args[1]::String == "gphase")::Bool + # this is a gate call with qubit targets + is_gate_call = next_token[end] == identifier || next_token[end] == hw_qubit || is_gphase + # this is a function call - unless it is gphase! + if (next_token[end] == semicolon && !is_gphase) + popfirst!(tokens) + expr = QasmExpression(:function_call, token_name, arguments) + elseif next_token[end] == operator # actually a binary op! + op_token = parse_identifier(popfirst!(tokens), qasm) + left_hand_side = QasmExpression(:function_call, token_name, arguments) + right_hand_side = parse_expression(tokens, stack, start, qasm) + expr = QasmExpression(:binary_op, Symbol(op_token.args[1]), left_hand_side, right_hand_side) + else # it's a gate call or gphase + target_expr = QasmExpression(:qubit_targets, parse_list_expression(tokens, stack, start, qasm)) + expr = QasmExpression(:gate_call, token_name, arguments) + push!(expr, target_expr) + end + end + return expr +end + +function parse_qasm(clean_tokens::Vector{Tuple{Int64, Int32, Token}}, qasm::String, root=QasmExpression(:program)) + stack = Stack{QasmExpression}() + push!(stack, root) + while !isempty(clean_tokens) + start, len, token = popfirst!(clean_tokens) + if token == newline + continue + elseif token == version + closing = findfirst(triplet->triplet[end] == semicolon, clean_tokens) + isnothing(closing) && throw(QasmParseError("missing final semicolon for OPENQASM", stack, start, qasm)) + closing == 1 && throw(QasmParseError("missing version number", stack, start, qasm)) + version_val = parse_literal([popfirst!(clean_tokens)], stack, start, qasm) + isinteger(version_val.args[1]) || throw(QasmParseError("version number must be an integer", stack, version_start, qasm)) + expr = QasmExpression(:version, QasmExpression(:float_literal, version_val.args[1])) + push!(stack, expr) + elseif token == pragma + closing = findfirst(triplet->triplet[end] == newline, clean_tokens) + isnothing(closing) && throw(QasmParseError("missing final newline for #pragma", stack, start, qasm)) + pragma_tokens = splice!(clean_tokens, 1:closing-1) + push!(stack, parse_pragma(pragma_tokens, stack, start, qasm)) + elseif token == include_token + closing = findfirst(triplet->triplet[end] == semicolon, clean_tokens) + isnothing(closing) && throw(QasmParseError("missing final semicolon for include", stack, start, qasm)) + file_name = popfirst!(clean_tokens) + popfirst!(clean_tokens) #semicolon + file_name[end] == string_token || throw(QasmParseError("included filename must be passed as a string", stack, start, qasm)) + file_name_str = replace(qasm[file_name[1]:file_name[1]+file_name[2]-1], "\""=>"", "'"=>"") + file_contents = read(file_name_str, String) + file_expr = parse_qasm(file_contents) + file_exprs = file_expr.args + foreach(ex->push!(stack, ex), file_exprs) + elseif token == const_token + closing = findfirst(triplet->triplet[end] == semicolon, clean_tokens) + isnothing(closing) && throw(QasmParseError("missing final semicolon for const declaration", stack, start, qasm)) + raw_expr = parse_expression(splice!(clean_tokens, 1:closing), stack, start, qasm) + expr = QasmExpression(:const_declaration, raw_expr.args) + push!(stack, expr) + elseif token == classical_type + closing = findfirst(triplet->triplet[end] == semicolon, clean_tokens) + isnothing(closing) && throw(QasmParseError("missing final semicolon for classical declaration", stack, start, qasm)) + line_tokens = pushfirst!(splice!(clean_tokens, 1:closing), (start, len, token)) + expr = parse_expression(line_tokens, stack, start, qasm) + push!(stack, expr) + elseif token == input + closing = findfirst(triplet->triplet[end] == semicolon, clean_tokens) + isnothing(closing) && throw(QasmParseError("missing final semicolon for input", stack, start, qasm)) + input_var = parse_classical_var(splice!(clean_tokens, 1:closing), stack, start, qasm) + expr = QasmExpression(:input, input_var...) + push!(stack, expr) + popfirst!(clean_tokens) #semicolon + elseif token == output + closing = findfirst(triplet->triplet[end] == semicolon, clean_tokens) + isnothing(closing) && throw(QasmParseError("missing final semicolon for output", stack, start, qasm)) + output_var = parse_classical_var(splice!(clean_tokens, 1:closing), stack, start, qasm) + expr = QasmExpression(:output, output_var...) + push!(stack, expr) + popfirst!(clean_tokens) #semicolon + elseif token == qubit + closing = findfirst(triplet->triplet[end] == semicolon, clean_tokens) + isnothing(closing) && throw(QasmParseError("missing final semicolon for qubit", stack, start, qasm)) + qubit_tokens = splice!(clean_tokens, 1:closing-1) + popfirst!(clean_tokens) # semicolon + expr = parse_qubit_declaration(qubit_tokens, stack, start, qasm) + push!(stack, expr) + elseif token == gate_def + expr = parse_gate_def(clean_tokens, stack, start, qasm) + push!(stack, expr) + elseif token == function_def + expr = parse_function_def(clean_tokens, stack, start, qasm) + push!(stack, expr) + elseif token == for_block + loop_in = findfirst(triplet->triplet[end] == in_token, clean_tokens) + isnothing(loop_in) && throw(QasmParseError("for loop variable must have in declaration", stack, start, qasm)) + loop_var = parse_classical_var(splice!(clean_tokens, 1:loop_in-1), stack, start, qasm) + popfirst!(clean_tokens) # in + loop_vals = parse_expression(clean_tokens, stack, start, qasm) + expr = parse_for_loop(clean_tokens, loop_var[1], loop_var[2], loop_vals, stack, start, qasm) + push!(stack, expr) + elseif token == while_block + expr = parse_while_loop(clean_tokens, stack, start, qasm) + push!(stack, expr) + elseif token == if_block + expr = parse_if_block(clean_tokens, stack, start, qasm) + push!(stack, expr) + elseif token == break_token + push!(stack, QasmExpression(:break)) + elseif token == continue_token + push!(stack, QasmExpression(:continue)) + elseif token == switch_block + expr = parse_switch_block(clean_tokens, stack, start, qasm) + push!(stack, expr) + elseif token == line_comment + eol = findfirst(triplet->triplet[end] == newline, clean_tokens) + splice!(clean_tokens, 1:eol) + elseif token == measure + eol = findfirst(triplet->triplet[end] == semicolon, clean_tokens) + measure_tokens = splice!(clean_tokens, 1:eol) + arrow_location = findfirst(triplet->triplet[end] == arrow_token, measure_tokens) + if !isnothing(arrow_location) # assignment + targets_tokens = splice!(measure_tokens, 1:arrow_location - 1) + popfirst!(measure_tokens) # arrow + targets = parse_expression(push!(targets_tokens, (-1, Int32(-1), semicolon)), stack, start, qasm) + left_hand_side = parse_expression(measure_tokens, stack, start, qasm) + right_hand_side = QasmExpression(:measure, targets) + op_expression = QasmExpression(:binary_op, Symbol("="), left_hand_side, right_hand_side) + push!(stack, QasmExpression(:classical_assignment, op_expression)) + else + targets = parse_expression(measure_tokens, stack, start, qasm) + push!(stack, QasmExpression(:measure, targets)) + end + elseif token ∈ (negctrl_mod, control_mod, inverse_mod, power_mod) + gate_mod_tokens = pushfirst!(clean_tokens, (start, len, token)) + expr = parse_gate_mods(gate_mod_tokens, stack, start, qasm) + push!(stack, expr) + elseif token == return_token + eol = findfirst(triplet->triplet[end] == semicolon, clean_tokens) + return_line_tokens = splice!(clean_tokens, 1:eol) + line_body = parse_qasm(return_line_tokens, qasm, QasmExpression(:return)) + line_exprs = collect(Iterators.reverse(line_body))[2:end] + push!(stack, QasmExpression(:return, line_exprs)) + elseif token == box + @warn "box expression encountered -- currently `box` is a no-op" + box_expr = QasmExpression(:box) + parse_block_body(box_expr, clean_tokens, stack, start, qasm) + push!(stack, box_expr) + elseif token == reset_token + @warn "reset expression encountered -- currently `reset` is a no-op" + eol = findfirst(triplet->triplet[end] == semicolon, clean_tokens) + reset_tokens = splice!(clean_tokens, 1:eol) + targets = parse_list_expression(reset_tokens, stack, start, qasm) + push!(stack, QasmExpression(:reset, targets)) + elseif token == barrier_token + @warn "barrier expression encountered -- currently `barrier` is a no-op" + eol = findfirst(triplet->triplet[end] == semicolon, clean_tokens) + barrier_tokens = splice!(clean_tokens, 1:eol) + targets = parse_list_expression(barrier_tokens, stack, start, qasm) + push!(stack, QasmExpression(:barrier, targets)) + elseif token == delay_token + @warn "delay expression encountered -- currently `delay` is a no-op" + eol = findfirst(triplet->triplet[end] == semicolon, clean_tokens) + delay_tokens = splice!(clean_tokens, 1:eol) + delay_expr = QasmExpression(:delay) + # format is delay[duration]; or delay[duration] targets; + delay_duration = extract_braced_block(delay_tokens, stack, start, qasm) + push!(delay_expr, QasmExpression(:duration, parse_expression(delay_duration, stack, start, qasm))) + target_expr = QasmExpression(:targets) + if first(delay_tokens)[end] != semicolon # targets present + targets = parse_list_expression(delay_tokens, stack, start, qasm) + push!(target_expr, targets) + end + push!(delay_expr, target_expr) + push!(stack, delay_expr) + elseif token == end_token + push!(stack, QasmExpression(:end)) + elseif token == identifier || token == builtin_gate + clean_tokens = pushfirst!(clean_tokens, (start, len, token)) + expr = parse_expression(clean_tokens, stack, start, qasm) + push!(stack, expr) + elseif token == forbidden_keyword + token_id = name(parse_identifier((start, len, token), qasm)) + throw(QasmParseError("keyword $token_id not supported.", stack, start, qasm)) + end + end + return stack +end +function parse_qasm(qasm::String, root=QasmExpression(:program))::QasmExpression + raw_tokens = tokenize(Token, qasm) + clean_tokens = filter(triplet->triplet[3] ∉ (spaces, block_comment), collect(raw_tokens)) + # add a final newline in case one is missing + clean_tokens[end][end] == newline || push!(clean_tokens, (-1, Int32(-1), newline)) + stack = parse_qasm(clean_tokens, qasm, root) + stack_exprs = convert(Vector{QasmExpression}, collect(Iterators.reverse(stack)))::Vector{QasmExpression} + foreach(ex->push!(stack_exprs[1], ex), stack_exprs[2:end]) + return stack_exprs[1] +end + +mutable struct ClassicalVariable + name::String + type + val + is_const::Bool +end + +struct Qubit + name::String + size::Int +end + +const InstructionArgument = Union{Symbol, Dates.Period, Real, Matrix{ComplexF64}} +const CircuitInstruction = @NamedTuple begin type::String; arguments::Vector{InstructionArgument}; targets::Vector{Int}; controls::Vector{Pair{Int, Int}}; exponent::Float64 end +const CircuitResult = @NamedTuple begin type::Symbol; operator::Vector{Union{String, Matrix{ComplexF64}}}; targets::Vector{Int}; states::Vector{String}; end + +abstract type AbstractGateDefinition end + +struct GateDefinition <: AbstractGateDefinition + name::String + arguments::Vector{String} + qubit_targets::Vector{String} # keep this as string to support splatting + body::QasmExpression +end + +struct BuiltinGateDefinition <: AbstractGateDefinition + name::String + arguments::Vector{String} + qubit_targets::Vector{String} # keep this as string to support splatting + body::CircuitInstruction +end + +struct FunctionDefinition + name::String + arguments::QasmExpression + body::Vector{QasmExpression} + return_type +end +FunctionDefinition(name::String, arguments::QasmExpression, body::QasmExpression, return_type) = FunctionDefinition(name, arguments, [body], return_type) + +struct QasmVisitorError <: Exception + message::String + alternate_type::String +end +QasmVisitorError(message::String) = QasmVisitorError(message, "") +function Base.showerror(io::IO, err::QasmVisitorError) + print(io, "QasmVisitorError: ") + print(io, err.message) +end + +abstract type AbstractVisitor end + +basic_builtin_gates() = Dict{String, BuiltinGateDefinition}( + "U"=>BuiltinGateDefinition("U", ["θ", "ϕ", "λ"], ["a"], (type="u", arguments=InstructionArgument[:θ, :ϕ, :λ], targets=[0], controls=Pair{Int,Int}[], exponent=1.0)), + ) + +mutable struct QasmProgramVisitor <: AbstractVisitor + inputs::Dict{String, Any} + classical_defs::Dict{String, ClassicalVariable} + function_defs::Dict{String, FunctionDefinition} + gate_defs::Dict{String, AbstractGateDefinition} + qubit_defs::Dict{String, Qubit} + qubit_mapping::Dict{String, Vector{Int}} + qubit_count::Int + instructions::Vector{CircuitInstruction} + results::Vector{CircuitResult} + function QasmProgramVisitor(inputs::Dict{String, <:Any} = Dict{String, Any}()) + new(inputs, + Dict{String, ClassicalVariable}(), + Dict{String, FunctionDefinition}(), + builtin_gates[](), + Dict{String, Qubit}(), + Dict{String, Vector{Int}}(), + 0, + CircuitInstruction[], + CircuitResult[], + ) + end +end + +mutable struct QasmGateDefVisitor <: AbstractVisitor + parent::AbstractVisitor + classical_defs::Dict{String, ClassicalVariable} + qubit_defs::Dict{String, Qubit} + qubit_mapping::Dict{String, Vector{Int}} + qubit_count::Int + instructions::Vector{CircuitInstruction} + function QasmGateDefVisitor(parent::AbstractVisitor, declared_arguments::Vector{String}, provided_arguments::QasmExpression, gate_qubits::Vector{String}) + qubit_defs = Dict(q=>Qubit(q, 1) for q in gate_qubits) + qubit_mapping = Dict(gate_qubits[ix+1]=>[ix] for ix in 0:length(gate_qubits)-1) + for ix in 0:length(gate_qubits)-1 + qubit_mapping[gate_qubits[ix+1] * "[0]"] = [ix] + end + v = new(parent, + deepcopy(classical_defs(parent)), + qubit_defs, + qubit_mapping, + length(gate_qubits), + CircuitInstruction[], + ) + for (arg_name, arg_value) in zip(declared_arguments, parent(provided_arguments)) + classical_defs(v)[arg_name] = ClassicalVariable(arg_name, Real, arg_value, true) + end + return v + end +end + +mutable struct QasmForLoopVisitor <: AbstractVisitor + parent::AbstractVisitor + classical_defs::Dict{String, ClassicalVariable} + QasmForLoopVisitor(parent::AbstractVisitor) = new(parent, classical_defs(parent)) +end + +mutable struct QasmWhileLoopVisitor <: AbstractVisitor + parent::AbstractVisitor + QasmWhileLoopVisitor(parent::AbstractVisitor) = new(parent) +end + +mutable struct QasmFunctionVisitor <: AbstractVisitor + parent::AbstractVisitor + classical_defs::Dict{String, ClassicalVariable} + qubit_defs::Dict{String, Qubit} + qubit_mapping::Dict{String, Vector{Int}} + qubit_count::Int + instructions::Vector{CircuitInstruction} + function QasmFunctionVisitor(parent::AbstractVisitor, declared_arguments::Vector{QasmExpression}, provided_arguments::Vector{QasmExpression}) + v = new(parent, + classical_defs(parent), + deepcopy(parent.qubit_defs), + deepcopy(parent.qubit_mapping), + qubit_count(parent), + CircuitInstruction[], + ) + arg_map = Dict(zip(declared_arguments, provided_arguments)) + for arg in declared_arguments + if head(arg) ∈ (:const_declaration, :classical_declaration) + new_val = parent(arg_map[arg]) + if head(arg.args[2]) != :classical_assignment + arg_id = pop!(arg) + push!(arg, QasmExpression(:classical_assignment, QasmExpression(:binary_op, Symbol("="), arg_id, new_val))) + else + arg.args[2].args[1].args[end] = new_val + end + end + v(arg) + end + return v + end +end +function QasmFunctionVisitor(parent::AbstractVisitor, declared_arguments::Vector{QasmExpression}, provided_arguments::QasmExpression) + head(provided_arguments) == :array_literal && return QasmFunctionVisitor(parent, declared_arguments, convert(Vector{QasmExpression}, provided_arguments.args)) + QasmFunctionVisitor(parent, declared_arguments, [provided_arguments]) +end +function QasmFunctionVisitor(parent::AbstractVisitor, declared_arguments::QasmExpression, provided_arguments) + head(declared_arguments) == :array_literal && return QasmFunctionVisitor(parent, convert(Vector{QasmExpression}, declared_arguments.args), provided_arguments) + QasmFunctionVisitor(parent, [declared_arguments], provided_arguments) +end +Base.parent(v::AbstractVisitor) = v.parent + +hasgate(v::AbstractVisitor, gate_name::String) = hasgate(parent(v), gate_name) +hasgate(v::QasmProgramVisitor, gate_name::String) = haskey(v.gate_defs, gate_name) +gate_defs(v::AbstractVisitor) = gate_defs(parent(v)) +gate_defs(v::QasmProgramVisitor) = v.gate_defs + +function_defs(v::QasmProgramVisitor) = v.function_defs +function_defs(v::AbstractVisitor) = function_defs(parent(v)) + +hasfunction(v::AbstractVisitor, function_name::String) = haskey(function_defs(v), function_name) + +qubit_defs(v::AbstractVisitor) = qubit_defs(parent(v)) +qubit_defs(v::QasmFunctionVisitor) = v.qubit_defs +qubit_defs(v::QasmProgramVisitor) = v.qubit_defs + +qubit_mapping(v::AbstractVisitor) = qubit_mapping(parent(v)) +qubit_mapping(v::QasmProgramVisitor) = v.qubit_mapping +qubit_mapping(v::QasmFunctionVisitor) = v.qubit_mapping +qubit_mapping(v::QasmGateDefVisitor) = v.qubit_mapping + +qubit_count(v::AbstractVisitor) = qubit_count(parent(v)) +qubit_count(v::QasmProgramVisitor) = v.qubit_count +qubit_count(v::QasmFunctionVisitor) = v.qubit_count +qubit_count(v::QasmGateDefVisitor) = v.qubit_count + +classical_defs(v::AbstractVisitor) = classical_defs(parent(v)) +classical_defs(v::QasmProgramVisitor) = v.classical_defs +classical_defs(v::QasmGateDefVisitor) = v.classical_defs +classical_defs(v::QasmFunctionVisitor) = v.classical_defs + +instructions(v::AbstractVisitor) = instructions(parent(v)) +instructions(v::QasmProgramVisitor) = v.instructions +instructions(v::QasmGateDefVisitor) = v.instructions +instructions(v::QasmFunctionVisitor) = v.instructions + +results(v::AbstractVisitor) = results(parent(v)) +results(v::QasmProgramVisitor) = v.results + +Base.push!(v::AbstractVisitor, ixs::Vector{CircuitInstruction}) = append!(instructions(v), ixs) +Base.push!(v::AbstractVisitor, ix::CircuitInstruction) = push!(instructions(v), ix) + +Base.push!(v::AbstractVisitor, rts::Vector{CircuitResult}) = append!(results(v), rts) +Base.push!(v::AbstractVisitor, rt::CircuitResult) = push!(results(v), rt) + +function evaluate_unary_op(op::Symbol, arg) + op == :! && return !arg + op == :~ && return .!arg + op == :- && return -arg +end +function evaluate_unary_op(op::Symbol, arg::BitVector) + op == :! && return !any(arg) + op == :~ && return .!arg +end + +# semgrep rules can't handle this macro properly yet +# nosemgrep +function evaluate_binary_op(op::Symbol, @nospecialize(lhs), @nospecialize(rhs)) + op == :< && return lhs < rhs + op == :> && return lhs > rhs + op == :<= && return lhs <= rhs + op == :>= && return lhs >= rhs + op == Symbol("=") && return rhs + op == Symbol("!=") && return lhs != rhs + op == Symbol("==") && return lhs == rhs + op == :+ && return lhs + rhs + op == :- && return lhs - rhs + op == :* && return lhs * rhs + op == :/ && return lhs / rhs + op == :% && return lhs % rhs + op == Symbol("<<") && return lhs << rhs + op == Symbol(">>") && return lhs >> rhs + op == Symbol("**") && return lhs ^ rhs + op == Symbol("&&") && return lhs && rhs + op == Symbol("||") && return lhs || rhs + op == :| && return lhs .| rhs + op == :& && return lhs .& rhs + op == :^ && return lhs .⊻ rhs +end + +function name(expr::QasmExpression)::String + head(expr) == :identifier && return expr.args[1]::String + head(expr) == :indexed_identifier && return name(expr.args[1]::QasmExpression) + head(expr) == :qubit_declaration && return name(expr.args[1]::QasmExpression) + head(expr) == :classical_declaration && return name(expr.args[2]::QasmExpression) + head(expr) == :input && return name(expr.args[2]::QasmExpression) + head(expr) == :function_call && return name(expr.args[1]::QasmExpression) + head(expr) == :gate_call && return name(expr.args[1]::QasmExpression) + head(expr) == :gate_definition && return name(expr.args[1]::QasmExpression) + head(expr) == :classical_assignment && return name(expr.args[1].args[2]::QasmExpression) + head(expr) == :hw_qubit && return replace(expr.args[1], "\$"=>"") + throw(QasmVisitorError("name not defined for expressions of type $(head(expr))")) +end + +function evaluate_modifiers(v::V, expr::QasmExpression) where {V<:AbstractVisitor} + if head(expr) == :power_mod + pow_expr = QasmExpression(:pow, v(expr.args[1]::QasmExpression)) + return (pow_expr, expr.args[2]) + elseif head(expr) == :inverse_mod + return (QasmExpression(:inv), expr.args[1]::QasmExpression) + elseif head(expr) ∈ (:control_mod, :negctrl_mod) + has_argument = length(expr.args) > 1 + if has_argument + arg_val::Int = v(first(expr.args)::QasmExpression)::Int + isinteger(arg_val) || throw(QasmVisitorError("cannot apply non-integer ($arg_val) number of controls or negcontrols.")) + true_inner = expr.args[2]::QasmExpression + inner = QasmExpression(head(expr), true_inner) + while arg_val > 2 + inner = QasmExpression(head(expr), inner) + arg_val -= 1 + end + else + inner = expr.args[1]::QasmExpression + end + new_head = head(expr) == :control_mod ? :ctrl : :negctrl + return (QasmExpression(new_head), inner) + end +end + +# nosemgrep +function _evaluate_qubits(::Val{:identifier}, v, qubit_expr::QasmExpression)::Vector{Int} + qubit_name = name(qubit_expr) + mapping = qubit_mapping(v)::Dict{String, Vector{Int}} + haskey(mapping, qubit_name) || throw(QasmVisitorError("Missing input variable '$qubit_name'.", "NameError")) + return mapping[qubit_name] +end + +# nosemgrep +function _evaluate_qubits(::Val{:indexed_identifier}, v, qubit_expr::QasmExpression)::Vector{Int} + qubit_name = name(qubit_expr) + mapping = qubit_mapping(v)::Dict{String, Vector{Int}} + haskey(mapping, qubit_name) || throw(QasmVisitorError("Missing input variable '$qubit_name'.", "NameError")) + qubit_ix = v(qubit_expr.args[2]::QasmExpression) + qubits = Iterators.flatmap(qubit_ix) do rq + haskey(mapping, qubit_name * "[$rq]") || throw(QasmVisitorError("Invalid qubit index '$rq' in '$qubit_name'.", "IndexError")) + return mapping[qubit_name * "[$rq]"] + end + return collect(qubits) +end +_evaluate_qubits(::Val{:array_literal}, v, qubit_expr::QasmExpression)::Vector{Int} = collect(Iterators.flatmap(expr->_evaluate_qubits(Val(head(expr)), v, expr), qubit_expr.args)) +_evaluate_qubits(::Val{:hw_qubit}, v, qubit_expr::QasmExpression)::Vector{Int} = Int[v(qubit_expr)::Int] +_evaluate_qubits(val, v, qubit_expr) = throw(QasmVisitorError("unable to evaluate qubits for expression $qubit_expr.")) + +function evaluate_qubits(v::AbstractVisitor, qubit_targets::Vector)::Vector{Int} + final_qubits = map(qubit_expr->_evaluate_qubits(Val(head(qubit_expr)), v, qubit_expr), qubit_targets) + return vcat(final_qubits...) +end +evaluate_qubits(v::AbstractVisitor, qubit_targets::QasmExpression) = evaluate_qubits(v::AbstractVisitor, [qubit_targets]) + +function remap(ix, target_mapper::Dict{Int, Int}) + mapped_targets = map(t->getindex(target_mapper, t), ix.targets) + mapped_controls = map(c->getindex(target_mapper, c[1])=>c[2], ix.controls) + return (type=ix.type, arguments=ix.arguments, targets=mapped_targets, controls=mapped_controls, exponent=ix.exponent) +end +function bind_arguments!(ix::CircuitInstruction, argument_values::Dict{Symbol, <:Real}) + new_arguments = InstructionArgument[get(argument_values, arg, arg) for arg in ix.arguments] + return (type=ix.type, arguments=new_arguments, targets=ix.targets, controls=ix.controls, exponent=ix.exponent) +end + +function process_gate_arguments(v::AbstractVisitor, gate_name::String, defined_arguments::Vector{String}, called_arguments::QasmExpression, gate_body::Vector{CircuitInstruction}) + def_has_arguments = !isempty(defined_arguments) + call_has_arguments = !isempty(v(called_arguments)) + if def_has_arguments ⊻ call_has_arguments + def_has_arguments && throw(QasmVisitorError("gate $gate_name requires arguments but none were provided.")) + call_has_arguments && throw(QasmVisitorError("gate $gate_name does not accept arguments but arguments were provided.")) + end + if def_has_arguments + evaled_args = v(called_arguments) + argument_values = Dict{Symbol, Real}(Symbol(arg_name)=>argument for (arg_name, argument) in zip(defined_arguments, evaled_args)) + return map(ix->bind_arguments!(ix, argument_values), gate_body) + else + return deepcopy(gate_body) + end +end + +function handle_gate_modifiers(ixs, mods::Vector{QasmExpression}, control_qubits::Vector{Int}, is_gphase::Bool) + for mod in Iterators.reverse(mods) + control_qubit = head(mod) ∈ (:negctrl, :ctrl) ? pop!(control_qubits) : -1 + for (ii, ix) in enumerate(ixs) + if head(mod) == :pow + ixs[ii] = (type=ix.type, arguments=ix.arguments, targets=ix.targets, controls=ix.controls, exponent=ix.exponent*mod.args[1]) + elseif head(mod) == :inv + ixs[ii] = (type=ix.type, arguments=ix.arguments, targets=ix.targets, controls=ix.controls, exponent=-ix.exponent) + # need to handle "extra" target + elseif head(mod) ∈ (:negctrl, :ctrl) + bit = head(mod) == :ctrl ? 1 : 0 + if is_gphase + ixs[ii] = (type=ix.type, arguments=ix.arguments, targets=ix.targets, controls=pushfirst!(ix.controls, control_qubit=>bit), exponent=ix.exponent) + else + ixs[ii] = (type=ix.type, arguments=ix.arguments, targets=pushfirst!(ix.targets, control_qubit), controls=pushfirst!(ix.controls, control_qubit=>bit), exponent=ix.exponent) + end + end + end + head(mod) == :inv && reverse!(ixs) + end + return ixs +end + +function splat_gate_targets(gate_targets::Vector{Vector{Int}}) + target_lengths::Vector{Int} = Int[length(t) for t in gate_targets] + longest = maximum(target_lengths) + must_splat::Bool = any(len->len!=1 || len != longest, target_lengths) + !must_splat && return longest, gate_targets + for target_ix in 1:length(gate_targets) + if target_lengths[target_ix] == 1 + append!(gate_targets[target_ix], fill(only(gate_targets[target_ix]), longest-1)) + end + end + return longest, gate_targets +end + +function visit_gphase_call(v::AbstractVisitor, program_expr::QasmExpression) + has_modifiers = length(program_expr.args) == 4 + n_called_with::Int = qubit_count(v) + gate_targets::Vector{Int} = collect(0:n_called_with-1) + provided_arg::QasmExpression = only(program_expr.args[2].args) + evaled_arg = v(provided_arg) + applied_arguments = CircuitInstruction[(type="gphase", arguments=[evaled_arg], targets=gate_targets, controls=Pair{Int,Int}[], exponent=1.0)] + mods::Vector{QasmExpression} = has_modifiers ? program_expr.args[4].args : QasmExpression[] + applied_arguments = handle_gate_modifiers(applied_arguments, mods, deepcopy(gate_targets), true) + target_mapper = Dict{Int, Int}(g_ix=>gate_targets[g_ix+1][1] for g_ix in 0:n_called_with-1) + push!(v, map(ix->remap(ix, target_mapper), applied_arguments)) + return +end + +function visit_gate_call(v::AbstractVisitor, program_expr::QasmExpression) + gate_name = name(program_expr)::String + raw_call_targets = program_expr.args[3]::QasmExpression + call_targets::Vector{QasmExpression} = convert(Vector{QasmExpression}, head(raw_call_targets.args[1]) == :array_literal ? raw_call_targets.args[1].args : raw_call_targets.args)::Vector{QasmExpression} + provided_args = isempty(program_expr.args[2].args) ? QasmExpression(:empty) : only(program_expr.args[2].args)::QasmExpression + has_modifiers = length(program_expr.args) == 4 + hasgate(v, gate_name) || throw(QasmVisitorError("gate $gate_name not defined!")) + gate_def = gate_defs(v)[gate_name] + gate_def_v = QasmGateDefVisitor(v, gate_def.arguments, provided_args, gate_def.qubit_targets) + gate_def_v(deepcopy(gate_def.body)) + gate_ixs = instructions(gate_def_v) + gate_targets = Vector{Int}[evaluate_qubits(v, call_target)::Vector{Int} for call_target in call_targets] + n_called_with = length(gate_targets) + n_defined_with = length(gate_def.qubit_targets) + # cases like `ccnot qs`; + if n_called_with < n_defined_with && length(gate_targets[1]) == n_defined_with + n_called_with = length(gate_targets[1]) + gate_targets = Vector{Int}[[gt] for gt in gate_targets[1]] + end + applied_arguments = process_gate_arguments(v, gate_name, gate_def.arguments, provided_args, gate_ixs) + control_qubits::Vector{Int} = collect(0:(n_called_with-n_defined_with)-1) + mods::Vector{QasmExpression} = has_modifiers ? convert(Vector{QasmExpression}, program_expr.args[4].args) : QasmExpression[] + if !isempty(control_qubits) + modifier_remap = Dict{Int, Int}(old_qubit=>(old_qubit + length(control_qubits)) for old_qubit in 0:length(gate_def.qubit_targets)) + for ii in 1:length(applied_arguments) + applied_arguments[ii] = remap(applied_arguments[ii], modifier_remap) + end + end + applied_arguments = handle_gate_modifiers(applied_arguments, mods, control_qubits, false) + longest, gate_targets = splat_gate_targets(gate_targets) + for splatted_ix in 1:longest + target_mapper = Dict{Int, Int}(g_ix=>gate_targets[g_ix+1][splatted_ix] for g_ix in 0:n_called_with-1) + push!(v, map(ix->remap(ix, target_mapper), applied_arguments)) + end + return +end + +(v::AbstractVisitor)(i::Number) = i +(v::AbstractVisitor)(i::String) = i +(v::AbstractVisitor)(i::BitVector) = i +(v::AbstractVisitor)(i::NTuple{N,<:Number}) where {N} = i +(v::AbstractVisitor)(i::Vector{<:Number}) = i +(v::AbstractVisitor)(program_exprs::Vector) = map(v, program_exprs) +(v::QasmGateDefVisitor)(ix::CircuitInstruction) = push!(v, ix) +function (v::AbstractVisitor)(program_expr::QasmExpression) + var_name::String = "" + if head(program_expr) == :program + for expr in program_expr.args + head(expr) == :end && return + v(expr) + end + elseif head(program_expr) == :scope + for expr in program_expr.args + head(expr) == :end && return + head(expr) == :continue && return :continue + head(expr) == :break && return :break + v(expr) + end + elseif head(program_expr) == :version + return v + elseif head(program_expr) == :reset + targets = program_expr.args[1]::QasmExpression + target_qubits = evaluate_qubits(v, targets) + ixs = [(type="reset", arguments=InstructionArgument[], targets=[t], controls=Pair{Int, Int}[], exponent=1.0) for t in target_qubits] + push!(v, ixs) + return v + elseif head(program_expr) == :barrier + targets = program_expr.args[1]::QasmExpression + target_qubits = evaluate_qubits(v, targets) + ixs = [(type="barrier", arguments=InstructionArgument[], targets=[t], controls=Pair{Int, Int}[], exponent=1.0) for t in target_qubits] + push!(v, ixs) + return v + elseif head(program_expr) == :delay + duration_expr = program_expr.args[1].args[1]::QasmExpression + targets = program_expr.args[2].args[1]::QasmExpression + target_qubits = evaluate_qubits(v, targets) + duration = v(duration_expr) + ixs = [(type="delay", arguments=InstructionArgument[duration], targets=[t], controls=Pair{Int, Int}[], exponent=1.0) for t in target_qubits] + push!(v, ixs) + return v + elseif head(program_expr) == :stretch + return v + elseif head(program_expr) == :duration + return v + elseif head(program_expr) == :input + var_name = name(program_expr) + var_type = program_expr.args[1].args[1] + haskey(v.inputs, var_name) || throw(QasmVisitorError("Missing input variable '$var_name'.", "NameError")) + var = ClassicalVariable(var_name, var_type, v.inputs[var_name], true) + v.classical_defs[var_name] = var + return v + elseif head(program_expr) ∈ (:continue, :break) + v isa Union{QasmForLoopVisitor, QasmWhileLoopVisitor} && return head(program_expr) + throw(QasmVisitorError(string(head(program_expr)) * " statement encountered outside a loop.")) + elseif head(program_expr) == :for + for_v = QasmForLoopVisitor(v) + for_loop = convert(Vector{QasmExpression}, program_expr.args) + loop_variable_type = for_loop[1].args[1] + loop_variable_name = for_loop[2].args[1]::String + loop_variable_values = for_v(for_loop[3]) + loop_body = for_loop[4]::QasmExpression + for loop_value in loop_variable_values + loop_variable = ClassicalVariable(loop_variable_name, loop_variable_type, loop_value, false) + for_v.classical_defs[loop_variable_name] = loop_variable + if head(loop_body) == :scope + for expr in convert(Vector{QasmExpression}, loop_body.args) + rt = for_v(expr) + rt == :continue && break + if rt == :break + delete!(classical_defs(v), loop_variable_name) + return v + end + end + else + for_v(loop_body) + end + end + delete!(classical_defs(v), loop_variable_name) + elseif head(program_expr) == :switch + case_val = v(program_expr.args[1]) + all_cases = convert(Vector{QasmExpression}, program_expr.args[2:end]) + default = findfirst(expr->head(expr) == :default, all_cases) + case_found = false + for case in all_cases + if head(case) == :case && case_val ∈ v(case.args[1]) + case_found = true + foreach(v, convert(Vector{QasmExpression}, case.args[2:end])) + break + end + end + if !case_found + isnothing(default) && throw(QasmVisitorError("no case matched and no default defined.")) + foreach(v, convert(Vector{QasmExpression}, all_cases[default].args)) + end + elseif head(program_expr) == :identifier + id_name = name(program_expr) + haskey(classical_defs(v), id_name) && return classical_defs(v)[id_name].val + haskey(qubit_mapping(v), id_name) && return evaluate_qubits(v, program_expr) + throw(QasmVisitorError("no identifier $id_name defined.")) + elseif head(program_expr) == :indexed_identifier + identifier_name = name(program_expr) + if haskey(classical_defs(v), identifier_name) + var = classical_defs(v)[identifier_name] + ix = v(program_expr.args[2]::QasmExpression) + if ix isa StepRange && ix.step > 0 && ix.stop < ix.start # -1 in place of end + new_stop = var.type isa SizedNumber || var.type isa SizedBitVector ? v(var.type.size) : length(var.val) + ix = StepRange(ix.start, ix.step, new_stop-1) + end + flat_ix = mapreduce(ix_ -> ix_ .+ 1, vcat, ix) + if var.type isa SizedInt || var.type isa SizedUInt + n_bits::Int = v(var.type.size)::Int + int_val = convert(Int, var.val)::Int + values = Int[(int_val >> (n_bits - index)) & 1 for index in flat_ix] + return length(flat_ix) == 1 ? values[1] : values + else + return length(flat_ix) == 1 ? var.val[only(flat_ix)] : var.val[flat_ix] + end + elseif haskey(qubit_mapping(v), identifier_name) + return evaluate_qubits(v, program_expr) + else + throw(QasmVisitorError("no identifier $identifier_name defined.")) + end + elseif head(program_expr) == :if + condition_value = v(program_expr.args[1]) > 0 + has_else = findfirst(expr->head(expr) == :else, convert(Vector{QasmExpression}, program_expr.args)) + last_expr = !isnothing(has_else) ? length(program_expr.args) - 1 : length(program_expr.args) + if condition_value + for expr in program_expr.args[2:last_expr] + rt = v(expr) + rt == :continue && return :continue + rt == :break && return :break + end + elseif !isnothing(has_else) + for expr in program_expr.args[has_else].args + rt = v(expr) + rt == :continue && return :continue + rt == :break && return :break + end + end + elseif head(program_expr) == :while + while_v = QasmWhileLoopVisitor(v) + condition_value = v(program_expr.args[1]) > 0 + loop_body = program_expr.args[2] + while condition_value + if head(loop_body) == :scope + for expr in loop_body.args + rt = while_v(expr) + rt == :continue && break + rt == :break && return v + end + else + while_v(loop_body) + end + condition_value = while_v(program_expr.args[1]) + end + elseif head(program_expr) == :classical_assignment + op = program_expr.args[1].args[1]::Symbol + left_hand_side = program_expr.args[1].args[2]::QasmExpression + right_hand_side = program_expr.args[1].args[3] + var_name = name(left_hand_side)::String + right_val = v(right_hand_side) + left_val = v(left_hand_side) + classical_defs(v)[var_name].is_const && throw(QasmVisitorError("cannot reassign value of const variable!")) + if head(left_hand_side) == :identifier + var = classical_defs(v)[var_name] + var_type = var.type + if var_type isa SizedBitVector && right_val isa AbstractString # bitstring literal + cleaned_val::String = replace(right_val, "\""=>"") + bit_right = BitVector(tryparse(Int, "$b") for b in cleaned_val) + new_val = evaluate_binary_op(op, left_val, bit_right) + else + new_val = evaluate_binary_op(op, left_val, right_val) + end + var.val = new_val + elseif head(left_hand_side) == :indexed_identifier + inds = v(left_hand_side.args[2]) + var = classical_defs(v)[var_name] + var_type = var.type + if inds isa StepRange && inds.step > 0 && inds.stop < inds.start # -1 in place of end + new_stop = var.type isa SizedNumber ? v(var.type.size) - 1 : length(var.val) - 1 + inds = StepRange(inds.start, inds.step, new_stop) + end + inds = inds .+ 1 + if var_type isa SizedBitVector && right_val isa AbstractString # bitstring literal + cleaned_val = replace(right_val, "\""=>"") + bit_right = BitVector(tryparse(Int, "$b") for b in cleaned_val) + new_val = evaluate_binary_op(op, left_val, bit_right) + else + new_val = evaluate_binary_op(op, left_val, right_val) + end + if length(inds) > 1 + var.val[inds] .= new_val + else + var.val[inds] = new_val + end + end + elseif head(program_expr) == :classical_declaration + var_type = program_expr.args[1].args[1] + init = if var_type isa SizedNumber + undef + elseif var_type isa SizedArray + fill(undef, v(var_type.size)) + elseif var_type isa SizedBitVector + falses(max(0, v(var_type.size))) + end + # no initial value + if head(program_expr.args[2]) == :identifier + var_name = name(program_expr.args[2]) + v.classical_defs[var_name] = ClassicalVariable(var_name, var_type, init, false) + elseif head(program_expr.args[2]) == :classical_assignment + op, left_hand_side, right_hand_side = program_expr.args[2].args[1].args + var_name = name(left_hand_side) + v.classical_defs[var_name] = ClassicalVariable(var_name, var_type, init, false) + v(program_expr.args[2]) + end + elseif head(program_expr) == :const_declaration + head(program_expr.args[2]) == :classical_assignment || throw(QasmVisitorError("const declaration must assign an initial value.")) + var_type = program_expr.args[1].args[1] + init = if var_type isa SizedNumber + undef + elseif var_type isa SizedArray + fill(undef, v(var_type.size)) + elseif var_type isa SizedBitVector + falses(max(0, v(var_type.size))) + end + op, left_hand_side, right_hand_side = program_expr.args[2].args[1].args + var_name = name(left_hand_side) + v.classical_defs[var_name] = ClassicalVariable(var_name, var_type, init, false) + v(program_expr.args[2]) + v.classical_defs[var_name] = ClassicalVariable(var_name, var_type, v.classical_defs[var_name].val, true) + elseif head(program_expr) == :qubit_declaration + qubit_name::String = name(program_expr) + qubit_size::Int = v(program_expr.args[2])::Int + qubit_defs(v)[qubit_name] = Qubit(qubit_name, qubit_size) + qubit_mapping(v)[qubit_name] = collect(qubit_count(v) : qubit_count(v) + qubit_size - 1) + for qubit_i in 0:qubit_size-1 + qubit_mapping(v)["$qubit_name[$qubit_i]"] = [qubit_count(v) + qubit_i] + end + v.qubit_count += qubit_size + elseif head(program_expr) ∈ (:power_mod, :inverse_mod, :control_mod, :negctrl_mod) + mods = QasmExpression(:modifiers) + mod_expr, inner = evaluate_modifiers(v, program_expr) + push!(mods, mod_expr) + while head(inner) != :gate_call # done + mod_expr, inner = evaluate_modifiers(v, inner) + push!(mods, mod_expr) + end + push!(inner, mods) + v(inner) + elseif head(program_expr) == :gate_call + gate_name = name(program_expr) + is_gphase = gate_name == "gphase" + if is_gphase + visit_gphase_call(v, program_expr) + else + visit_gate_call(v, program_expr) + end + elseif head(program_expr) == :box + foreach(v, program_expr.args) + elseif head(program_expr) == :gate_definition + gate_def = program_expr.args + gate_name = name(program_expr) + gate_arguments = gate_def[2]::QasmExpression + gate_def_targets = gate_def[3]::QasmExpression + gate_body = gate_def[4]::QasmExpression + single_argument = !isempty(gate_arguments.args) && head(gate_arguments.args[1]) == :array_literal + argument_exprs = single_argument ? gate_arguments.args[1].args::Vector{Any} : gate_arguments.args::Vector{Any} + argument_names = String[arg.args[1] for arg::QasmExpression in argument_exprs] + single_target = head(gate_def_targets.args[1]) == :array_literal + qubit_targets = single_target ? map(name, gate_def_targets.args[1].args)::Vector{String} : map(name, gate_def_targets.args)::Vector{String} + v.gate_defs[gate_name] = GateDefinition(gate_name, argument_names, qubit_targets, gate_body) + elseif head(program_expr) == :function_call + function_name = name(program_expr) + if haskey(builtin_functions, function_name) + concrete_arguments = v(convert(Vector{QasmExpression}, program_expr.args[2].args)) + if function_name != "sizeof" + return_val = builtin_functions[function_name](Iterators.flatten(concrete_arguments)...) + else + return_val = builtin_functions[function_name](concrete_arguments...) + end + return return_val[1] + else + hasfunction(v, function_name) || throw(QasmVisitorError("function $function_name not defined!")) + function_def = function_defs(v)[function_name] + function_body = function_def.body::Vector{QasmExpression} + declared_args = only(function_def.arguments.args)::QasmExpression + provided_args = only(program_expr.args[2].args)::QasmExpression + function_v = QasmFunctionVisitor(v, declared_args, provided_args) + return_val = nothing + body_exprs::Vector{QasmExpression} = head(function_body[1]) == :scope ? function_body[1].args : function_body + for f_expr in body_exprs + if head(f_expr) == :return + return_val = function_v(f_expr.args[1]) + else + function_v(f_expr) + end + end + # remap qubits and classical variables + function_args = if head(declared_args) == :array_literal + convert(Vector{QasmExpression}, declared_args.args)::Vector{QasmExpression} + else + declared_args + end + called_args = if head(provided_args) == :array_literal + convert(Vector{QasmExpression}, provided_args.args)::Vector{QasmExpression} + else + provided_args + end + arguments_map = Dict{QasmExpression, QasmExpression}(zip(function_args, called_args)) + reverse_arguments_map = Dict{QasmExpression, QasmExpression}(zip(called_args, function_args)) + reverse_qubits_map = Dict{Int, Int}() + for variable in keys(reverse_arguments_map) + if head(variable) ∈ (:identifier, :indexed_identifier) + variable_name = name(variable) + if haskey(classical_defs(v), variable_name) && classical_defs(v)[variable_name].type isa SizedArray + if head(reverse_arguments_map[variable]) != :const_declaration + inner_variable_name = name(reverse_arguments_map[variable]) + new_val = classical_defs(function_v)[inner_variable_name].val + back_assignment = QasmExpression(:classical_assignment, QasmExpression(:binary_op, Symbol("="), variable, new_val)) + v(back_assignment) + end + elseif haskey(qubit_defs(v), variable_name) + outer_context_map = only(evaluate_qubits(v, variable)) + inner_context_map = only(evaluate_qubits(function_v, reverse_arguments_map[variable].args[1])) + reverse_qubits_map[inner_context_map] = outer_context_map + end + end + end + mapper = isempty(reverse_qubits_map) ? identity : ix->remap(ix, reverse_qubits_map) + push!(v, map(mapper, function_v.instructions)) + return return_val + end + elseif head(program_expr) == :function_definition + function_def = program_expr.args + function_name = function_def[1].args[1]::String + function_arguments = function_def[2] + function_return_type = function_def[3]::QasmExpression + function_body = function_def[4]::QasmExpression + full_function_def = FunctionDefinition(function_name, function_arguments, function_body, function_return_type) + v.function_defs[function_name] = full_function_def + elseif head(program_expr) == :pragma + visit_pragma(v, program_expr) + elseif head(program_expr) ∈ (:integer_literal, :float_literal, :string_literal, :complex_literal, :irrational_literal, :boolean_literal, :duration_literal) + return program_expr.args[1] + elseif head(program_expr) == :array_literal + return map(v, program_expr.args) + elseif head(program_expr) == :range + start::Int, step::Int, stop::Int = v(program_expr.args) + return StepRange(start, step, stop) + elseif head(program_expr) == :empty + return () + elseif head(program_expr) == :measure + qubits_to_measure = evaluate_qubits(v, program_expr.args[1]) + push!(v, CircuitInstruction[(type="measure", arguments=InstructionArgument[], targets=[q], controls=Pair{Int,Int}[], exponent=1.0) for q in qubits_to_measure]) + return false + elseif head(program_expr) == :hw_qubit + return tryparse(Int, name(program_expr)) + elseif head(program_expr) == :output + throw(QasmVisitorError("Output not supported.")) + elseif head(program_expr) == :binary_op + op = program_expr.args[1]::Symbol + lhs = v(program_expr.args[2]) + rhs = v(program_expr.args[3]) + val = evaluate_binary_op(op, lhs, rhs) + return val + elseif head(program_expr) == :unary_op + op = program_expr.args[1]::Symbol + arg = v(program_expr.args[2]) + return evaluate_unary_op(op, arg) + elseif head(program_expr) == :cast + casting_to = program_expr.args[1].args[1] + value = v(program_expr.args[2]) + if casting_to == Bool && !(value isa Period) + return value isa BitVector ? any(value) : value > 0 + elseif casting_to isa SizedBitVector && !(value isa Period || value isa AbstractFloat) + new_size = v(casting_to.size) + return value isa BitVector ? value[1:new_size] : BitVector(reverse(digits(value, base=2, pad=new_size))) + elseif casting_to isa SizedNumber && !(value isa Period) + num_value = value isa BitVector ? sum(reverse(value)[k]*2^(k-1) for k=1:length(value)) : value + if casting_to isa SizedInt + return Int(num_value) + elseif casting_to isa SizedUInt + return UInt(num_value) + elseif casting_to isa SizedFloat + return Float64(num_value) + end + else + throw(QasmVisitorError("unable to evaluate cast expression $program_expr")) + end + else + throw(QasmVisitorError("cannot visit expression $program_expr.")) + end + return v +end + +end diff --git a/src/builtin_functions.jl b/src/builtin_functions.jl new file mode 100644 index 0000000..bc40476 --- /dev/null +++ b/src/builtin_functions.jl @@ -0,0 +1,25 @@ +popcount() = 0 +popcount(s::String) = count(c->c=='1', s) +popcount(c::Char...) = count(c_->c_=='1', c) +popcount(bv::BitVector) = count(bv) +popcount(b::Bool...) = count(b) +popcount(i::Integer) = count_ones(i) +popcount(u::Unsigned) = count_ones(u) + +const builtin_functions = Dict{String, Function}( + "sizeof" => size, + "arccos" => acos, + "arcsin" => asin, + "arctan" => atan, + "ceiling" => ceil, + "floor" => floor, + "cos" => cos, + "sin" => sin, + "tan" => tan, + "exp" => exp, + "log" => log, + "mod" => mod, + "pow" => ^, + "sqrt" => sqrt, + "popcount" => popcount, +) diff --git a/test/builtin_gates.jl b/test/builtin_gates.jl new file mode 100644 index 0000000..332f4e2 --- /dev/null +++ b/test/builtin_gates.jl @@ -0,0 +1,79 @@ +# OpenQASM 3 Braket Standard Gates +complex_builtin_gates() = Dict{String, Quasar.BuiltinGateDefinition}( + # identity gate + "i"=>Quasar.BuiltinGateDefinition("i", String[], ["a"], (type="i", arguments=InstructionArgument[], targets=[0], controls=Pair{Int,Int}[], exponent=1.0)), + # phase gate + "phaseshift"=>Quasar.BuiltinGateDefinition("phaseshift", ["λ"], ["a"], (type="phaseshift", arguments=InstructionArgument[:λ], targets=[0], controls=Pair{Int,Int}[], exponent=1.0)), + # pauli X gate + "x"=>Quasar.BuiltinGateDefinition("x", String[], ["a"], (type="x", arguments=InstructionArgument[], targets=[0], controls=Pair{Int,Int}[], exponent=1.0)), + # pauli Y gate + "y"=>Quasar.BuiltinGateDefinition("y", String[], ["a"], (type="y", arguments=InstructionArgument[], targets=[0], controls=Pair{Int,Int}[], exponent=1.0)), + # pauli Z gate + "z"=>Quasar.BuiltinGateDefinition("z", String[], ["a"], (type="z", arguments=InstructionArgument[], targets=[0], controls=Pair{Int,Int}[], exponent=1.0)), + # Hadamard gate + "h"=>Quasar.BuiltinGateDefinition("h", String[], ["a"], (type="h", arguments=InstructionArgument[], targets=[0], controls=Pair{Int,Int}[], exponent=1.0)), + # S gate + "s"=>Quasar.BuiltinGateDefinition("s", String[], ["a"], (type="s", arguments=InstructionArgument[], targets=[0], controls=Pair{Int,Int}[], exponent=1.0)), + # Si gate + "si"=>Quasar.BuiltinGateDefinition("si", String[], ["a"], (type="si", arguments=InstructionArgument[], targets=[0], controls=Pair{Int,Int}[], exponent=1.0)), + # T gate + "t"=>Quasar.BuiltinGateDefinition("t", String[], ["a"], (type="t", arguments=InstructionArgument[], targets=[0], controls=Pair{Int,Int}[], exponent=1.0)), + # Ti gate + "ti"=>Quasar.BuiltinGateDefinition("ti", String[], ["a"], (type="ti", arguments=InstructionArgument[], targets=[0], controls=Pair{Int,Int}[], exponent=1.0)), + # V gate + "v"=>Quasar.BuiltinGateDefinition("v", String[], ["a"], (type="v", arguments=InstructionArgument[], targets=[0], controls=Pair{Int,Int}[], exponent=1.0)), + # Vi gate + "vi"=>Quasar.BuiltinGateDefinition("vi", String[], ["a"], (type="vi", arguments=InstructionArgument[], targets=[0], controls=Pair{Int,Int}[], exponent=1.0)), + # RotX gate + "rx"=>Quasar.BuiltinGateDefinition("rx", ["θ"], ["a"], (type="rx", arguments=InstructionArgument[:θ], targets=[0], controls=Pair{Int,Int}[], exponent=1.0)), + # RotY gate + "ry"=>Quasar.BuiltinGateDefinition("ry", ["θ"], ["a"], (type="ry", arguments=InstructionArgument[:θ], targets=[0], controls=Pair{Int,Int}[], exponent=1.0)), + # RotZ gate + "rz"=>Quasar.BuiltinGateDefinition("rz", ["θ"], ["a"], (type="rz", arguments=InstructionArgument[:θ], targets=[0], controls=Pair{Int,Int}[], exponent=1.0)), + # CNot gate + "cnot"=>Quasar.BuiltinGateDefinition("cnot", String[], ["a", "b"], (type="cnot", arguments=InstructionArgument[], targets=[0, 1], controls=Pair{Int,Int}[], exponent=1.0)), + # CY gate + "cy"=>Quasar.BuiltinGateDefinition("cy", String[], ["a", "b"], (type="cy", arguments=InstructionArgument[], targets=[0, 1], controls=Pair{Int,Int}[], exponent=1.0)), + # CZ gate + "cz"=>Quasar.BuiltinGateDefinition("cz", String[], ["a", "b"], (type="cz", arguments=InstructionArgument[], targets=[0, 1], controls=Pair{Int,Int}[], exponent=1.0)), + # CV gate + "cv"=>Quasar.BuiltinGateDefinition("cv", String[], ["a", "b"], (type="cv", arguments=InstructionArgument[], targets=[0, 1], controls=Pair{Int,Int}[], exponent=1.0)), + # controlled-phase + "cphaseshift"=>Quasar.BuiltinGateDefinition("cphaseshift", ["λ"], ["a", "b"], (type="cphaseshift", arguments=InstructionArgument[:λ], targets=[0, 1], controls=Pair{Int,Int}[], exponent=1.0)), + # controlled-phase-00 + "cphaseshift00"=>Quasar.BuiltinGateDefinition("cphaseshift00", ["λ"], ["a", "b"], (type="cphaseshift00", arguments=InstructionArgument[:λ], targets=[0, 1], controls=Pair{Int,Int}[], exponent=1.0)), + # controlled-phase-01 + "cphaseshift01"=>Quasar.BuiltinGateDefinition("cphaseshift01", ["λ"], ["a", "b"], (type="cphaseshift01", arguments=InstructionArgument[:λ], targets=[0, 1], controls=Pair{Int,Int}[], exponent=1.0)), + # controlled-phase-10 + "cphaseshift10"=>Quasar.BuiltinGateDefinition("cphaseshift10", ["λ"], ["a", "b"], (type="cphaseshift10", arguments=InstructionArgument[:λ], targets=[0, 1], controls=Pair{Int,Int}[], exponent=1.0)), + # Swap gate + "swap"=>Quasar.BuiltinGateDefinition("swap", String[], ["a", "b"], (type="swap", arguments=InstructionArgument[], targets=[0, 1], controls=Pair{Int,Int}[], exponent=1.0)), + # ISwap gate + "iswap"=>Quasar.BuiltinGateDefinition("iswap", String[], ["a", "b"], (type="iswap", arguments=InstructionArgument[], targets=[0, 1], controls=Pair{Int,Int}[], exponent=1.0)), + # ISwap gate + "pswap"=>Quasar.BuiltinGateDefinition("pswap", ["θ"], ["a", "b"], (type="pswap", arguments=InstructionArgument[:θ], targets=[0, 1], controls=Pair{Int,Int}[], exponent=1.0)), + # controlled-swap gate + "cswap"=>Quasar.BuiltinGateDefinition("cswap", String[], ["a", "b", "c"], (type="cswap", arguments=InstructionArgument[], targets=[0, 1, 2], controls=Pair{Int,Int}[], exponent=1.0)), + # ccnot/Toffoli gate + "ccnot"=>Quasar.BuiltinGateDefinition("ccnot", String[], ["a", "b", "c"], (type="ccnot", arguments=InstructionArgument[], targets=[0, 1, 2], controls=Pair{Int,Int}[], exponent=1.0)), + # XX gate + "xx"=>Quasar.BuiltinGateDefinition("xx", ["θ"], ["a", "b"], (type="xx", arguments=InstructionArgument[:θ], targets=[0, 1], controls=Pair{Int,Int}[], exponent=1.0)), + # XY gate + "xy"=>Quasar.BuiltinGateDefinition("xy", ["θ"], ["a", "b"], (type="xy", arguments=InstructionArgument[:θ], targets=[0, 1], controls=Pair{Int,Int}[], exponent=1.0)), + # YY gate + "yy"=>Quasar.BuiltinGateDefinition("yy", ["θ"], ["a", "b"], (type="yy", arguments=InstructionArgument[:θ], targets=[0, 1], controls=Pair{Int,Int}[], exponent=1.0)), + # ZZ gate + "zz"=>Quasar.BuiltinGateDefinition("zz", ["θ"], ["a", "b"], (type="zz", arguments=InstructionArgument[:θ], targets=[0, 1], controls=Pair{Int,Int}[], exponent=1.0)), + # ECR gate + "ecr"=>Quasar.BuiltinGateDefinition("ecr", String[], ["a", "b"], (type="ecr", arguments=InstructionArgument[], targets=[0, 1], controls=Pair{Int,Int}[], exponent=1.0)), + # MS gate + "ms"=>Quasar.BuiltinGateDefinition("ms", ["ϕ", "θ", "λ"], ["a", "b"], (type="ms", arguments=InstructionArgument[:ϕ, :θ, :λ], targets=[0, 1], controls=Pair{Int,Int}[], exponent=1.0)), + # GPi gate + "gpi"=>Quasar.BuiltinGateDefinition("gpi", ["θ"], ["a"], (type="gpi", arguments=InstructionArgument[:θ], targets=[0], controls=Pair{Int,Int}[], exponent=1.0)), + # GPi2 gate + "gpi2"=>Quasar.BuiltinGateDefinition("gpi2", ["θ"], ["a"], (type="gpi2", arguments=InstructionArgument[:θ], targets=[0], controls=Pair{Int,Int}[], exponent=1.0)), + # PRx gate + "prx"=>Quasar.BuiltinGateDefinition("prx", ["θ", "ϕ"], ["a"], (type="prx", arguments=InstructionArgument[:θ, :ϕ], targets=[0], controls=Pair{Int,Int}[], exponent=1.0)), + # 3-angle U gate + "U"=>Quasar.BuiltinGateDefinition("U", ["θ", "ϕ", "λ"], ["a"], (type="u", arguments=InstructionArgument[:θ, :ϕ, :λ], targets=[0], controls=Pair{Int,Int}[], exponent=1.0)), +) diff --git a/test/runtests.jl b/test/runtests.jl new file mode 100644 index 0000000..82970d5 --- /dev/null +++ b/test/runtests.jl @@ -0,0 +1,1453 @@ +using Quasar +using Quasar: InstructionArgument +using Test +using Dates, Statistics, LinearAlgebra + +include("builtin_gates.jl") +Quasar.builtin_gates[] = complex_builtin_gates + +@testset "Quasar.jl" begin + @testset "QasmExpression" begin + @testset "Printing" begin + expr = Quasar.QasmExpression(:head, Quasar.QasmExpression(:integer_literal, 2)) + @test sprint(show, expr) == "QasmExpression :head\n└─ QasmExpression :integer_literal\n └─ 2\n" + bad_expression = Quasar.QasmExpression(:invalid_expression, Quasar.QasmExpression(:error)) + err = Quasar.QasmVisitorError("unable to evaluate expression $bad_expression") + @test sprint(showerror, err) == "QasmVisitorError: unable to evaluate expression $bad_expression" + end + @testset "Expression basics" begin + expr = Quasar.QasmExpression(:head, Quasar.QasmExpression(:integer_literal, 2)) + @test length(expr) == 1 + push!(expr, Quasar.QasmExpression(:integer_literal, 3)) + @test length(expr.args) == 2 + append!(expr, Quasar.QasmExpression(:integer_literal, 4)) + @test length(expr.args) == 3 + append!(expr, [Quasar.QasmExpression(:integer_literal, 5), Quasar.QasmExpression(:integer_literal, 6)]) + @test length(expr.args) == 5 + end + @testset "Equality" begin + expr_a = Quasar.QasmExpression(:head, Quasar.QasmExpression(:integer_literal, 2)) + expr_b = Quasar.QasmExpression(:head, Quasar.QasmExpression(:float_literal, 2.2)) + @test expr_a != expr_b + expr_a = Quasar.QasmExpression(:head, Quasar.QasmExpression(:integer_literal, 2)) + @test expr_a == copy(expr_a) + end + @testset "Name undefined" begin + expr = Quasar.QasmExpression(:undef, Quasar.QasmExpression(:integer_literal, 2)) + @test_throws Quasar.QasmVisitorError("name not defined for expressions of type undef") Quasar.name(expr) + end + end + @testset "Visiting/evaluating an invalid expression" begin + visitor = Quasar.QasmProgramVisitor() + bad_expression = Quasar.QasmExpression(:invalid_expression, Quasar.QasmExpression(:error)) + @test_throws Quasar.QasmVisitorError("cannot visit expression $bad_expression.") visitor(bad_expression) + bad_expression = Quasar.QasmExpression(:invalid_expression, Quasar.QasmExpression(:error)) + @test_throws Quasar.QasmVisitorError("unable to evaluate qubits for expression $bad_expression.") Quasar.evaluate_qubits(visitor, bad_expression) + cast_expression = Quasar.QasmExpression(:cast, Quasar.QasmExpression(:type_expr, Int32), Quasar.QasmExpression(:float_literal, 2.0)) + @test_throws Quasar.QasmVisitorError("unable to evaluate cast expression $cast_expression") visitor(cast_expression) + end + @testset "Visitors and parents" begin + qasm = """ + def flip(qubit q) { + x q; + } + qubit[2] qs; + flip(qs[0]); + """ + parsed = parse_qasm(qasm) + visitor = Quasar.QasmProgramVisitor() + visitor(parsed) + for_visitor = Quasar.QasmForLoopVisitor(visitor) + @test Quasar.function_defs(for_visitor) == Quasar.function_defs(visitor) + @test Quasar.qubit_defs(for_visitor) == Quasar.qubit_defs(visitor) + @test Quasar.qubit_count(for_visitor) == Quasar.qubit_count(visitor) + push!(for_visitor, (type=:amplitude, operator=Union{String,Matrix{ComplexF64}}[], targets=Int[], states=["00"])) + @test visitor.results[1] == (type=:amplitude, operator=Union{String,Matrix{ComplexF64}}[], targets=Int[], states=["00"]) + push!(for_visitor, [(type=:state_vector, operator=Union{String,Matrix{ComplexF64}}[], targets=Int[], states=String[]), (type=:probability, operator=Union{String,Matrix{ComplexF64}}[], targets=Int[], states=String[])]) + @test visitor.results[2] == (type=:state_vector, operator=Union{String,Matrix{ComplexF64}}[], targets=Int[], states=String[]) + @test visitor.results[3] == (type=:probability, operator=Union{String,Matrix{ComplexF64}}[], targets=Int[], states=String[]) + push!(for_visitor, [(type="x", arguments=Union{Symbol,Dates.Period,Real,Matrix{ComplexF64}}[], targets=[0], controls=Pair{Int,Int}[], exponent=1.0), (type="y", arguments=Union{Symbol,Dates.Period,Real,Matrix{ComplexF64}}[], targets=[1], controls=Pair{Int,Int}[], exponent=1.0)]) + @test visitor.instructions == [(type="x", arguments=Union{Symbol,Dates.Period,Real,Matrix{ComplexF64}}[], targets=[0], controls=Pair{Int,Int}[], exponent=1.0), (type="x", arguments=Union{Symbol,Dates.Period,Real,Matrix{ComplexF64}}[], targets=[0], controls=Pair{Int,Int}[], exponent=1.0), (type="y", arguments=Union{Symbol,Dates.Period,Real,Matrix{ComplexF64}}[], targets=[1], controls=Pair{Int,Int}[], exponent=1.0)] + gate_visitor = Quasar.QasmGateDefVisitor(visitor, String[], Quasar.QasmExpression(:empty), String[]) + push!(gate_visitor, [(type="x", arguments=InstructionArgument[], targets=[0], controls=Pair{Int,Int}[], exponent=1.0), (type="y", arguments=InstructionArgument[], targets=[1], controls=Pair{Int,Int}[], exponent=1.0)]) + @test gate_visitor.instructions == [(type="x", arguments=InstructionArgument[], targets=[0], controls=Pair{Int,Int}[], exponent=1.0), (type="y", arguments=InstructionArgument[], targets=[1], controls=Pair{Int,Int}[], exponent=1.0)] + function_visitor = Quasar.QasmFunctionVisitor(visitor, Quasar.QasmExpression[], Quasar.QasmExpression[]) + push!(function_visitor, [(type="x", arguments=InstructionArgument[], targets=[0], controls=Pair{Int,Int}[], exponent=1.0), (type="y", arguments=InstructionArgument[], targets=[1], controls=Pair{Int,Int}[], exponent=1.0)]) + @test function_visitor.instructions == [(type="x", arguments=InstructionArgument[], targets=[0], controls=Pair{Int,Int}[], exponent=1.0), (type="y", arguments=InstructionArgument[], targets=[1], controls=Pair{Int,Int}[], exponent=1.0)] + end + @testset "Sized types" begin + for (t_string, T) in (("SizedBitVector", Quasar.SizedBitVector), + ("SizedInt", Quasar.SizedInt), + ("SizedUInt", Quasar.SizedUInt), + ("SizedFloat", Quasar.SizedFloat), + ("SizedAngle", Quasar.SizedAngle), + ("SizedComplex", Quasar.SizedComplex)) + object = T(Quasar.QasmExpression(:integer_literal, 4)) + @test sprint(show, object) == t_string * "{4}" + array_object = Quasar.SizedArray(object, (10,)) + @test sprint(show, array_object) == "SizedArray{" * t_string * "{4}, 1}" + @test_throws BoundsError size(array_object, 1) + @test size(array_object, 0) == 10 + new_object = T(object) + @test sprint(show, new_object) == t_string * "{4}" + end + bitvector = Quasar.SizedBitVector(Quasar.QasmExpression(:integer_literal, 10)) + @test length(bitvector) == Quasar.QasmExpression(:integer_literal, 10) + @test size(bitvector) == (Quasar.QasmExpression(:integer_literal, 10),) + end + @testset "Adder" begin + sv_adder_qasm = """ + OPENQASM 3; + + input uint[4] a_in; + input uint[4] b_in; + + gate majority a, b, c { + cnot c, b; + cnot c, a; + ccnot a, b, c; + } + + gate unmaj a, b, c { + ccnot a, b, c; + cnot c, a; + cnot a, b; + } + + qubit cin; + qubit[4] a; + qubit[4] b; + qubit cout; + + // set input states + for int[8] i in [0: 3] { + if(bool(a_in[i])) x a[i]; + if(bool(b_in[i])) x b[i]; + } + + // add a to b, storing result in b + majority cin, b[3], a[3]; + for int[8] i in [3: -1: 1] { majority a[i], b[i - 1], a[i - 1]; } + cnot a[0], cout; + for int[8] i in [1: 3] { unmaj a[i], b[i - 1], a[i - 1]; } + unmaj cin, b[3], a[3]; + """ + parsed = parse_qasm(sv_adder_qasm) + visitor = QasmProgramVisitor(Dict("a_in"=>3, "b_in"=>7)) + visitor(parsed) + @test visitor.qubit_count == 10 + correct_instructions = [ + (type="x", arguments=InstructionArgument[], targets=[6], controls=Pair{Int,Int}[], exponent=1.0) + (type="x", arguments=InstructionArgument[], targets=[3], controls=Pair{Int,Int}[], exponent=1.0) + (type="x", arguments=InstructionArgument[], targets=[7], controls=Pair{Int,Int}[], exponent=1.0) + (type="x", arguments=InstructionArgument[], targets=[4], controls=Pair{Int,Int}[], exponent=1.0) + (type="x", arguments=InstructionArgument[], targets=[8], controls=Pair{Int,Int}[], exponent=1.0) + (type="cnot", arguments=InstructionArgument[], targets=[4, 8], controls=Pair{Int,Int}[], exponent=1.0) + (type="cnot", arguments=InstructionArgument[], targets=[4, 0], controls=Pair{Int,Int}[], exponent=1.0) + (type="ccnot", arguments=InstructionArgument[], targets=[0, 8, 4], controls=Pair{Int,Int}[], exponent=1.0) + (type="cnot", arguments=InstructionArgument[], targets=[3, 7], controls=Pair{Int,Int}[], exponent=1.0) + (type="cnot", arguments=InstructionArgument[], targets=[3, 4], controls=Pair{Int,Int}[], exponent=1.0) + (type="ccnot", arguments=InstructionArgument[], targets=[4, 7, 3], controls=Pair{Int,Int}[], exponent=1.0) + (type="cnot", arguments=InstructionArgument[], targets=[2, 6], controls=Pair{Int,Int}[], exponent=1.0) + (type="cnot", arguments=InstructionArgument[], targets=[2, 3], controls=Pair{Int,Int}[], exponent=1.0) + (type="ccnot", arguments=InstructionArgument[], targets=[3, 6, 2], controls=Pair{Int,Int}[], exponent=1.0) + (type="cnot", arguments=InstructionArgument[], targets=[1, 5], controls=Pair{Int,Int}[], exponent=1.0) + (type="cnot", arguments=InstructionArgument[], targets=[1, 2], controls=Pair{Int,Int}[], exponent=1.0) + (type="ccnot", arguments=InstructionArgument[], targets=[2, 5, 1], controls=Pair{Int,Int}[], exponent=1.0) + (type="cnot", arguments=InstructionArgument[], targets=[1, 9], controls=Pair{Int,Int}[], exponent=1.0) + (type="ccnot", arguments=InstructionArgument[], targets=[2, 5, 1], controls=Pair{Int,Int}[], exponent=1.0) + (type="cnot", arguments=InstructionArgument[], targets=[1, 2], controls=Pair{Int,Int}[], exponent=1.0) + (type="cnot", arguments=InstructionArgument[], targets=[2, 5], controls=Pair{Int,Int}[], exponent=1.0) + (type="ccnot", arguments=InstructionArgument[], targets=[3, 6, 2], controls=Pair{Int,Int}[], exponent=1.0) + (type="cnot", arguments=InstructionArgument[], targets=[2, 3], controls=Pair{Int,Int}[], exponent=1.0) + (type="cnot", arguments=InstructionArgument[], targets=[3, 6], controls=Pair{Int,Int}[], exponent=1.0) + (type="ccnot", arguments=InstructionArgument[], targets=[4, 7, 3], controls=Pair{Int,Int}[], exponent=1.0) + (type="cnot", arguments=InstructionArgument[], targets=[3, 4], controls=Pair{Int,Int}[], exponent=1.0) + (type="cnot", arguments=InstructionArgument[], targets=[4, 7], controls=Pair{Int,Int}[], exponent=1.0) + (type="ccnot", arguments=InstructionArgument[], targets=[0, 8, 4], controls=Pair{Int,Int}[], exponent=1.0) + (type="cnot", arguments=InstructionArgument[], targets=[4, 0], controls=Pair{Int,Int}[], exponent=1.0) + (type="cnot", arguments=InstructionArgument[], targets=[0, 8], controls=Pair{Int,Int}[], exponent=1.0) + ] + for (ix, c_ix) in zip(visitor.instructions, correct_instructions) + @test ix == c_ix + end + end + @testset "Randomized Benchmarking" begin + qasm = """ + qubit[2] q; + bit[2] c; + + h q[0]; + cz q[0], q[1]; + s q[0]; + cz q[0], q[1]; + s q[0]; + z q[0]; + h q[0]; + measure q -> c; + """ + parsed = parse_qasm(qasm) + visitor = QasmProgramVisitor() + visitor(parsed) + @test visitor.instructions == [(type="h", arguments=InstructionArgument[], targets=[0], controls=Pair{Int,Int}[], exponent=1.0), + (type="cz", arguments=InstructionArgument[], targets=[0, 1], controls=Pair{Int,Int}[], exponent=1.0), + (type="s", arguments=InstructionArgument[], targets=[0], controls=Pair{Int,Int}[], exponent=1.0), + (type="cz", arguments=InstructionArgument[], targets=[0, 1], controls=Pair{Int,Int}[], exponent=1.0), + (type="s", arguments=InstructionArgument[], targets=[0], controls=Pair{Int,Int}[], exponent=1.0), + (type="z", arguments=InstructionArgument[], targets=[0], controls=Pair{Int,Int}[], exponent=1.0), + (type="h", arguments=InstructionArgument[], targets=[0], controls=Pair{Int,Int}[], exponent=1.0), + (type="measure", arguments=InstructionArgument[], targets=[0], controls=Pair{Int,Int}[], exponent=1.0), + (type="measure", arguments=InstructionArgument[], targets=[1], controls=Pair{Int,Int}[], exponent=1.0)] + end + @testset "Bare measure" begin + qasm = """ + qubit[2] q; + measure q; + """ + parsed = parse_qasm(qasm) + visitor = QasmProgramVisitor() + visitor(parsed) + @test visitor.instructions == [(type="measure", arguments=InstructionArgument[], targets=[0], controls=Pair{Int,Int}[], exponent=1.0), + (type="measure", arguments=InstructionArgument[], targets=[1], controls=Pair{Int,Int}[], exponent=1.0)] + end + @testset "Measure with density matrix" begin + qasm = """ + qubit[3] qs; + qubit q; + + x qs[{0, 2}]; + h q; + cphaseshift(1) qs, q; + phaseshift(-2) q; + measure qs; + measure q; + """ + parsed = parse_qasm(qasm) + visitor = QasmProgramVisitor() + visitor(parsed) + @test visitor.instructions == [ + (type="x", arguments=InstructionArgument[], targets=[0], controls=Pair{Int,Int}[], exponent=1.0), + (type="x", arguments=InstructionArgument[], targets=[2], controls=Pair{Int,Int}[], exponent=1.0), + (type="h", arguments=InstructionArgument[], targets=[3], controls=Pair{Int,Int}[], exponent=1.0), + (type="cphaseshift", arguments=InstructionArgument[1.0], targets=[0, 3], controls=Pair{Int,Int}[], exponent=1.0), + (type="cphaseshift", arguments=InstructionArgument[1.0], targets=[1, 3], controls=Pair{Int,Int}[], exponent=1.0), + (type="cphaseshift", arguments=InstructionArgument[1.0], targets=[2, 3], controls=Pair{Int,Int}[], exponent=1.0), + (type="phaseshift", arguments=InstructionArgument[-2.0], targets=[3], controls=Pair{Int,Int}[], exponent=1.0), + (type="measure", arguments=InstructionArgument[], targets=[0], controls=Pair{Int,Int}[], exponent=1.0), + (type="measure", arguments=InstructionArgument[], targets=[1], controls=Pair{Int,Int}[], exponent=1.0), + (type="measure", arguments=InstructionArgument[], targets=[2], controls=Pair{Int,Int}[], exponent=1.0), + (type="measure", arguments=InstructionArgument[], targets=[3], controls=Pair{Int,Int}[], exponent=1.0), + ] + end + @testset "Pragma" begin + qasm = """ + qubit[4] q; + #pragma fake_pragma + """ + @test_throws MethodError parse_qasm(qasm) + end + @testset "Box" begin + qasm = """ + OPENQASM 3.0; + bit[2] b; + qubit[2] q; + box{ + cnot q[0], q[1]; + cnot q[0], q[1]; + rx(1.57) q[0]; + } + b[0] = measure q[0]; + b[1] = measure q[1]; + """ + parsed = parse_qasm(qasm) + visitor = QasmProgramVisitor() + visitor(parsed) + @test visitor.instructions == [ + (type="cnot", arguments=InstructionArgument[], targets=[0, 1], controls=Pair{Int,Int}[], exponent=1.0), + (type="cnot", arguments=InstructionArgument[], targets=[0, 1], controls=Pair{Int,Int}[], exponent=1.0), + (type="rx", arguments=InstructionArgument[1.57], targets=[0], controls=Pair{Int,Int}[], exponent=1.0), + (type="measure", arguments=InstructionArgument[], targets=[0], controls=Pair{Int,Int}[], exponent=1.0), + (type="measure", arguments=InstructionArgument[], targets=[1], controls=Pair{Int,Int}[], exponent=1.0), + ] + end + @testset "GPhase" begin + qasm = """ + qubit[2] qs; + int[8] two = 2; + gphase(π); + inv @ gphase(π / 2); + negctrl @ ctrl @ gphase(2 * π) qs[0], qs[1]; + """ + parsed = parse_qasm(qasm) + visitor = QasmProgramVisitor() + visitor(parsed) + @test visitor.instructions == [ + (type="gphase", arguments=InstructionArgument[π], targets=[0, 1], controls=Pair{Int,Int}[], exponent=1.0), + (type="gphase", arguments=InstructionArgument[π/2], targets=[0, 1], controls=Pair{Int,Int}[], exponent=-1.0), + (type="gphase", arguments=InstructionArgument[2*π], targets=[0, 1], controls=[0=>0, 1=>1], exponent=1.0), + ] + end + @testset "Casting" begin + @testset "Casting to $to_type from $from_type" for (to_type, to_value) in (("bool", true),), (from_type, from_value) in (("int[32]", "32",), + ("uint[16]", "1",), + ("float", "2.5",), + ("bool", "true",), + ("bit", "\"1\"",), + ) + qasm = """ + $from_type a = $from_value; + $to_type b = $to_type(a); + """ + parsed = parse_qasm(qasm) + visitor = QasmProgramVisitor() + visitor(parsed) + @test visitor.classical_defs["b"].val == to_value + end + @testset "Casting to $to_type from $from_type" for (to_type, to_value) in (("uint[32]", 32), ("int[16]", 32), ("float", 32.0)), (from_type, from_value) in (("int[32]", "32",), + ("uint[16]", "32",), + ("float", "32.0",), + ("bit[6]", "\"100000\"",), + ) + qasm = """ + $from_type a = $from_value; + $to_type b = $to_type(a); + """ + parsed = parse_qasm(qasm) + visitor = QasmProgramVisitor() + visitor(parsed) + @test visitor.classical_defs["b"].val == to_value + end + @testset "Casting to $to_type from $from_type" for (to_type, to_value) in (("bit[6]", BitVector([1,0,0,0,0,0])),), (from_type, from_value) in (("int[32]", "32",), + ("uint[16]", "32",), + ("bit[6]", "\"100000\"",), + ) + qasm = """ + $from_type a = $from_value; + $to_type b = $to_type(a); + """ + parsed = parse_qasm(qasm) + visitor = QasmProgramVisitor() + visitor(parsed) + @test visitor.classical_defs["b"].val == to_value + end + end + @testset "Numbers $qasm_str" for (qasm_str, var_name, output_val) in (("float[32] a = 1.24e-3;", "a", 1.24e-3), + ("complex[float] b = 1-0.23im;", "b", 1-0.23im), + ("const bit c = \"0\";", "c", falses(1)), + ("bool d = false;", "d", false), + ("complex[float] e = -0.23+2im;", "e", -0.23+2im), + ("uint f = 0x123456789abcdef;", "f", 0x123456789abcdef), + ("int g = 0o010;", "g", 0o010), + ("float[64] h = 2*π;", "h", 2π), + ("float[64] i = τ/2;", "i", Float64(π)), + ("complex[float] j = 0.23im;", "j", 0.23im), + ("complex[float] k = -0.23im;", "k", -0.23im), + ("complex[float] l = 2im;", "l", 2*im), + ("complex[float] m = -0.23 + -2.0im;", "m", -0.23-2.0*im), + ) + + + qasm = """ + $qasm_str + """ + parsed = parse_qasm(qasm) + visitor = QasmProgramVisitor() + visitor(parsed) + @test visitor.classical_defs[var_name].val == output_val + end + @testset "Qubit identifiers" begin + qasm = """ + qubit[2] q; + """ + parsed = parse_qasm(qasm) + visitor = QasmProgramVisitor() + visitor(parsed) + @test visitor(Quasar.QasmExpression(:identifier, "q")) == [0, 1] + @test visitor(Quasar.QasmExpression(:indexed_identifier, Quasar.QasmExpression(:identifier, "q"), Quasar.QasmExpression(:integer_literal, 1))) == [1] + @test_throws Quasar.QasmVisitorError("no identifier p defined.") visitor(Quasar.QasmExpression(:identifier, "p")) + @test_throws Quasar.QasmVisitorError("no identifier p defined.") visitor(Quasar.QasmExpression(:indexed_identifier, Quasar.QasmExpression(:identifier, "p"), Quasar.QasmExpression(:integer_literal, 1))) + end + @testset "Forbidden keywords" begin + qasm = """ + extern b; + """ + @test_throws Quasar.QasmParseError parse_qasm(qasm) + try + parse_qasm(qasm) + catch e + msg = sprint(showerror, e) + @test startswith(msg, "QasmParseError: keyword extern not supported.") + end + end + @testset "Integers next to irrationals" begin + qasm = """ + float[64] h = 2π; + """ + @test_throws Quasar.QasmParseError parse_qasm(qasm) + try + parse_qasm(qasm) + catch e + msg = sprint(showerror, e) + @test startswith(msg, "QasmParseError: expressions of form 2π are not supported -- you must separate the terms with a * operator.") + end + end + @testset "Gate def with argument manipulation" begin + qasm = """ + qubit[2] __qubits__; + gate u3(θ, ϕ, λ) q { + gphase(-(ϕ+λ)/2); + U(θ, ϕ, λ) q; + } + u3(0.1, 0.2, 0.3) __qubits__[0]; + """ + parsed = parse_qasm(qasm) + visitor = Quasar.QasmProgramVisitor() + visitor(parsed) + canonical_ixs = [(type="gphase", arguments=InstructionArgument[-(0.2 + 0.3)/2], targets=[0], controls=Pair{Int,Int}[], exponent=1.0), + (type="u", arguments=InstructionArgument[0.1, 0.2, 0.3], targets=[0], controls=Pair{Int,Int}[], exponent=1.0)] + @test visitor.instructions == canonical_ixs + end + @testset "Bad classical type" begin + qasm = """ + angle[64] h = 2π; + """ + @test_throws Quasar.QasmParseError parse_qasm(qasm) + end + @testset "Physical qubits" begin + qasm = """ + h \$0; + cnot \$0, \$1; + """ + parsed = parse_qasm(qasm) + visitor = QasmProgramVisitor() + visitor(parsed) + @test visitor.instructions == [(type="h", arguments=InstructionArgument[], targets=[0], controls=Pair{Int,Int}[], exponent=1.0), + (type="cnot", arguments=InstructionArgument[], targets=[0, 1], controls=Pair{Int,Int}[], exponent=1.0)] + end + @testset "For loop and subroutines" begin + qasm = """ + OPENQASM 3.0; + def bell(qubit q0, qubit q1) { + h q0; + cnot q0, q1; + } + def n_bells(int[32] n, qubit q0, qubit q1) { + for int i in [0:n - 1] { + h q0; + cnot q0, q1; + } + } + qubit[4] __qubits__; + bell(__qubits__[0], __qubits__[1]); + n_bells(5, __qubits__[2], __qubits__[3]); + bit[4] __bit_0__ = "0000"; + __bit_0__[0] = measure __qubits__[0]; + __bit_0__[1] = measure __qubits__[1]; + __bit_0__[2] = measure __qubits__[2]; + __bit_0__[3] = measure __qubits__[3]; + """ + parsed = parse_qasm(qasm) + visitor = QasmProgramVisitor(Dict("theta"=>0.2)) + visitor(parsed) + expected_ixs = [(type="h", arguments=InstructionArgument[], targets=[0], controls=Pair{Int,Int}[], exponent=1.0), + (type="cnot", arguments=InstructionArgument[], targets=[0, 1], controls=Pair{Int,Int}[], exponent=1.0), + (type="h", arguments=InstructionArgument[], targets=[2], controls=Pair{Int,Int}[], exponent=1.0), + (type="cnot", arguments=InstructionArgument[], targets=[2, 3], controls=Pair{Int,Int}[], exponent=1.0), + (type="h", arguments=InstructionArgument[], targets=[2], controls=Pair{Int,Int}[], exponent=1.0), + (type="cnot", arguments=InstructionArgument[], targets=[2, 3], controls=Pair{Int,Int}[], exponent=1.0), + (type="h", arguments=InstructionArgument[], targets=[2], controls=Pair{Int,Int}[], exponent=1.0), + (type="cnot", arguments=InstructionArgument[], targets=[2, 3], controls=Pair{Int,Int}[], exponent=1.0), + (type="h", arguments=InstructionArgument[], targets=[2], controls=Pair{Int,Int}[], exponent=1.0), + (type="cnot", arguments=InstructionArgument[], targets=[2, 3], controls=Pair{Int,Int}[], exponent=1.0), + (type="h", arguments=InstructionArgument[], targets=[2], controls=Pair{Int,Int}[], exponent=1.0), + (type="cnot", arguments=InstructionArgument[], targets=[2, 3], controls=Pair{Int,Int}[], exponent=1.0) + ] + for (v_ix, e_ix) in zip(visitor.instructions, expected_ixs) + @test v_ix == e_ix + end + end + @testset "For Loop" begin + qasm_with_scope = """ + int[8] x = 0; + int[8] y = -100; + int[8] ten = 10; + + for uint[8] i in [0:2:ten - 3] { + x += i; + } + + for int[8] i in {2, 4, 6} { + y += i; + } + """ + qasm_no_scope = """ + int[8] x = 0; + int[8] y = -100; + int[8] ten = 10; + + for uint[8] i in [0:2:ten - 3] x += i; + for int[8] i in {2, 4, 6} y += i; + """ + @testset "$label" for (label, qasm) in (("With scoping {}", qasm_with_scope), ("Without scoping {}", qasm_no_scope)) + parsed = parse_qasm(qasm) + visitor = QasmProgramVisitor() + visitor(parsed) + @test visitor.classical_defs["x"].val == sum((0, 2, 4, 6)) + @test visitor.classical_defs["y"].val == sum((-100, 2, 4, 6)) + end + end + @testset "While Loop" begin + qasm_with_scope = """ + int[8] x = 0; + int[8] i = 0; + + while (i < 7) { + x += i; + i += 1; + } + """ + qasm_no_scope = """ + int[8] x = 0; + + while (x < 7) x += 1; + """ + @testset "$label" for (label, qasm, val) in (("With scoping {}", qasm_with_scope, sum(0:6)), ("Without scoping {}", qasm_no_scope, 7)) + parsed = parse_qasm(qasm) + visitor = QasmProgramVisitor() + visitor(parsed) + @test visitor.classical_defs["x"].val == val + end + end + @testset "Break and continue" begin + @testset for str in ("continue;", "{ continue; }") + qasm = """ + int[8] x = 0; + for int[8] i in [0: 3] { + if (mod(i, 2) == 0) $str + x += i; + } + """ + parsed = parse_qasm(qasm) + visitor = QasmProgramVisitor() + visitor(parsed) + @test visitor.classical_defs["x"].val == 4 + qasm = """ + int[8] x = 0; + int[8] i = 0; + + while (i < 7) { + i += 1; + if (i == 5) $str + x += i; + } + """ + parsed = parse_qasm(qasm) + visitor = QasmProgramVisitor() + visitor(parsed) + @test visitor.classical_defs["x"].val == sum(0:7) - 5 + end + @testset for str in ("break;", "{ break; }") + qasm = """ + int[8] x = 0; + for int[8] i in [0: 3] { + x += i; + if (mod(i, 2) == 1) $str + } + """ + parsed = parse_qasm(qasm) + visitor = QasmProgramVisitor() + visitor(parsed) + @test visitor.classical_defs["x"].val == 1 + qasm = """ + int[8] x = 0; + int[8] i = 0; + + while (i < 7) { + x += i; + i += 1; + if (i == 5) $str + } + """ + parsed = parse_qasm(qasm) + visitor = QasmProgramVisitor() + visitor(parsed) + @test visitor.classical_defs["x"].val == sum(0:4) + end + @testset for str in ("continue", "break") + qasm = """ + int[8] x = 0; + int[8] i = 0; + $str; + """ + parsed = parse_qasm(qasm) + visitor = QasmProgramVisitor() + @test_throws Quasar.QasmVisitorError("$str statement encountered outside a loop.") visitor(parsed) + end + end + @testset "Builtin functions $fn" for (fn, type, arg, result) in (("arccos", "float[64]", 1, acos(1)), + ("arcsin", "float[64]", 1, asin(1)), + ("arctan", "float[64]", 1, atan(1)), + ("ceiling", "int[64]", "π", 4), + ("floor", "int[64]", "π", 3), + ("mod", "int[64]", "4, 3", 1), + ("mod", "float[64]", "5.2, 2.5", mod(5.2, 2.5)), + ("cos", "float[64]", 1, cos(1)), + ("sin", "float[64]", 1, sin(1)), + ("tan", "float[64]", 1, tan(1)), + ("sqrt", "float[64]", 2, sqrt(2)), + ("exp", "float[64]", 2, exp(2)), + ("log", "float[64]", "ℇ", log(ℯ)), + ("popcount", "int[64]", "true", 1), + ("popcount", "int[64]", "\"1001110\"", 4), + ("popcount", "int[64]", "78", 4), + ("popcount", "int[64]", "0x78", 4), + ) + qasm = """ + const $type $(fn)_result = $fn($arg); + """ + parsed = parse_qasm(qasm) + visitor = QasmProgramVisitor() + visitor(parsed) + @test visitor.classical_defs["$(fn)_result"].val == result + end + @testset "Builtin functions" begin + qasm = """ + bit[2] bitvec = "11"; + const int[64] popcount_bitvec_result = popcount(bitvec); + """ + parsed = parse_qasm(qasm) + visitor = QasmProgramVisitor() + visitor(parsed) + @test visitor.classical_defs["popcount_bitvec_result"].val == 2 + qasm = """ + const int[64] popcount_empty_result = popcount(); + """ + parsed = parse_qasm(qasm) + visitor = QasmProgramVisitor() + visitor(parsed) + @test visitor.classical_defs["popcount_empty_result"].val == 0 + @testset "Symbolic" begin + qasm = """ + input float x; + input float y; + rx(x) \$0; + rx(arccos(x)) \$0; + rx(arcsin(x)) \$0; + rx(arctan(x)) \$0; + rx(ceiling(x)) \$0; + rx(cos(x)) \$0; + rx(exp(x)) \$0; + rx(floor(x)) \$0; + rx(log(x)) \$0; + rx(mod(x, y)) \$0; + rx(sin(x)) \$0; + rx(sqrt(x)) \$0; + rx(tan(x)) \$0; + """ + x = 1.0 + y = 2.0 + parsed = parse_qasm(qasm) + visitor = Quasar.QasmProgramVisitor(Dict("x"=>x, "y"=>y)) + visitor(parsed) + ixs = [ + (type="rx", arguments=InstructionArgument[x], targets=[0], controls=Pair{Int,Int}[], exponent=1.0), + (type="rx", arguments=InstructionArgument[acos(x)], targets=[0], controls=Pair{Int,Int}[], exponent=1.0), + (type="rx", arguments=InstructionArgument[asin(x)], targets=[0], controls=Pair{Int,Int}[], exponent=1.0), + (type="rx", arguments=InstructionArgument[atan(x)], targets=[0], controls=Pair{Int,Int}[], exponent=1.0), + (type="rx", arguments=InstructionArgument[ceil(x)], targets=[0], controls=Pair{Int,Int}[], exponent=1.0), + (type="rx", arguments=InstructionArgument[cos(x)], targets=[0], controls=Pair{Int,Int}[], exponent=1.0), + (type="rx", arguments=InstructionArgument[exp(x)], targets=[0], controls=Pair{Int,Int}[], exponent=1.0), + (type="rx", arguments=InstructionArgument[floor(x)], targets=[0], controls=Pair{Int,Int}[], exponent=1.0), + (type="rx", arguments=InstructionArgument[log(x)], targets=[0], controls=Pair{Int,Int}[], exponent=1.0), + (type="rx", arguments=InstructionArgument[mod(x, y)], targets=[0], controls=Pair{Int,Int}[], exponent=1.0), + (type="rx", arguments=InstructionArgument[sin(x)], targets=[0], controls=Pair{Int,Int}[], exponent=1.0), + (type="rx", arguments=InstructionArgument[sqrt(x)], targets=[0], controls=Pair{Int,Int}[], exponent=1.0), + (type="rx", arguments=InstructionArgument[tan(x)], targets=[0], controls=Pair{Int,Int}[], exponent=1.0), + ] + @test visitor.instructions == ixs + end + @testset "popcount!" begin + @test Quasar.popcount() == 0 + @test Quasar.popcount(vcat(trues(3), falses(2))) == 3 + @test Quasar.popcount("01010101") == 4 + end + end + @testset "Delay $duration" for (duration, ix) in (("200us", Microsecond(200)), + ("100μs", Microsecond(100)), + ("50ns", Nanosecond(50)), + ("20dt", Nanosecond(20)), + ("10ms", Millisecond(10)), + ("1s", Second(1)), + ) + + + qasm = """ + qubit[4] q; + x q[0]; + delay[$duration] q[0], q[1]; + """ + @test_warn "delay expression encountered -- currently `delay` is a no-op" parse_qasm(qasm) + parsed = parse_qasm(qasm) + visitor = Quasar.QasmProgramVisitor() + visitor(parsed) + @test visitor.instructions == [(type="x", arguments=InstructionArgument[], targets=[0], controls=Pair{Int,Int}[], exponent=1.0), + (type="delay", arguments=InstructionArgument[ix], targets=[0], controls=Pair{Int,Int}[], exponent=1.0), + (type="delay", arguments=InstructionArgument[ix], targets=[1], controls=Pair{Int,Int}[], exponent=1.0), + ] + end + @testset "Barrier" begin + qasm = """ + qubit[4] q; + x q[0]; + barrier q[0]; + """ + @test_warn "barrier expression encountered -- currently `barrier` is a no-op" parse_qasm(qasm) + parsed = parse_qasm(qasm) + visitor = Quasar.QasmProgramVisitor() + visitor(parsed) + @test visitor.instructions == [(type="x", arguments=InstructionArgument[], targets=[0], controls=Pair{Int,Int}[], exponent=1.0), + (type="barrier", arguments=InstructionArgument[], targets=[0], controls=Pair{Int,Int}[], exponent=1.0), + ] + end + @testset "Stretch" begin + qasm = """ + stretch a; + """ + @test_warn "stretch expression encountered -- currently `stretch` is a no-op" parse_qasm(qasm) + visitor = Quasar.QasmProgramVisitor() + @test visitor(Quasar.QasmExpression(:stretch)) === visitor + end + @testset "Duration" begin + qasm = """ + duration a = 10μs; + """ + @test_warn "duration expression encountered -- currently `duration` is a no-op" parse_qasm(qasm) + visitor = Quasar.QasmProgramVisitor() + @test visitor(Quasar.QasmExpression(:duration)) === visitor + end + @testset "Reset" begin + qasm = """ + qubit[4] q; + x q[0]; + reset q[0]; + """ + @test_warn "reset expression encountered -- currently `reset` is a no-op" parse_qasm(qasm) + parsed = parse_qasm(qasm) + visitor = Quasar.QasmProgramVisitor() + visitor(parsed) + @test visitor.instructions == [(type="x", arguments=InstructionArgument[], targets=[0], controls=Pair{Int,Int}[], exponent=1.0), + (type="reset", arguments=InstructionArgument[], targets=[0], controls=Pair{Int,Int}[], exponent=1.0), + ] + @testset "Reset after Measure" begin + good_qasm = """ + qubit[4] q; + measure q[0]; + reset q[0]; + x q[0]; + """ + parsed = parse_qasm(good_qasm) + visitor = Quasar.QasmProgramVisitor() + visitor(parsed) + @test visitor.instructions == [ + (type="measure", arguments=InstructionArgument[], targets=[0], controls=Pair{Int,Int}[], exponent=1.0), + (type="reset", arguments=InstructionArgument[], targets=[0], controls=Pair{Int,Int}[], exponent=1.0), + (type="x", arguments=InstructionArgument[], targets=[0], controls=Pair{Int,Int}[], exponent=1.0), + ] + end + end + @testset "Gate call missing/extra args" begin + qasm = """ + qubit[4] q; + rx q[0]; + """ + parsed = parse_qasm(qasm) + visitor = QasmProgramVisitor() + @test_throws Quasar.QasmVisitorError("gate rx requires arguments but none were provided.") visitor(parsed) + qasm = """ + qubit[4] q; + x(0.1) q[0]; + """ + parsed = parse_qasm(qasm) + visitor = QasmProgramVisitor() + @test_throws Quasar.QasmVisitorError("gate x does not accept arguments but arguments were provided.") visitor(parsed) + end + @testset "Assignment operators" begin + @testset "Op: $op" for (op, initial, arg, final) in (("+", 0, 1, 1), ("*", 1, 2, 2), ("/", 2, 2, 1), ("-", 1, 5, -4)) + qasm = """ + int[16] x; + x = $initial; + x $op= $arg; + """ + parsed = parse_qasm(qasm) + visitor = QasmProgramVisitor() + visitor(parsed) + @test visitor.classical_defs["x"].val == final + end + @testset "Op: $op" for (op, initial, arg, final) in (("|", "0000", "11", BitVector([0,0,1,1])), + ("&", "0010", "11", BitVector([0,0,1,0])), + ("^", "0001", "10", BitVector([0,0,1,1])), + ) + qasm = """ + bit[4] xs = \"$initial\"; + xs[2:] $op= \"$arg\"; + """ + parsed = parse_qasm(qasm) + visitor = QasmProgramVisitor() + visitor(parsed) + @test visitor.classical_defs["xs"].val == final + end + end + @testset "Binary bit operators $op" for (op, result) in (("&", BitVector([0,1,0,0])), + ("|", BitVector([1,1,0,1])), + ("^", BitVector([1,0,0,1])), + (">", false), + ("<", true), + (">=", false), + ("<=", true), + ("==", false), + ("!=", true), + ) + + qasm = """ + bit[4] x = "0101"; + bit[4] y = "1100"; + + bit[4] result = x $op y; + """ + parsed = parse_qasm(qasm) + visitor = QasmProgramVisitor() + visitor(parsed) + @test visitor.classical_defs["x"].val == BitVector([0,1,0,1]) + @test visitor.classical_defs["y"].val == BitVector([1,1,0,0]) + @test visitor.classical_defs["result"].val == result + end + @testset "Binary bit operators $op" for (op, arg1, arg2, result) in (("<<", "x", 2, BitVector([0,1,0,0])), + (">>", "y", 2, BitVector([0,0,1,1])), + ) + qasm = """ + bit[4] x = "0101"; + bit[4] y = "1100"; + + bit[4] result = $arg1 $op $arg2; + """ + parsed = parse_qasm(qasm) + visitor = QasmProgramVisitor() + visitor(parsed) + @test visitor.classical_defs["result"].val == result + end + @testset "Unary bit operators $op" for (op, typ, arg, result) in (("~", "bit[4]", "x", BitVector([1,0,1,0])), + ("!", "bit", "x", false), + ("!", "bit", "x << 4", true) + ) + + qasm = """ + bit not; + bit not_zero; + + bit[4] x = "0101"; + $typ result = $(op)$(arg); + """ + parsed = parse_qasm(qasm) + visitor = QasmProgramVisitor() + visitor(parsed) + @test visitor.classical_defs["x"].val == BitVector([0,1,0,1]) + @test visitor.classical_defs["result"].val == result + end + @testset "End statement" begin + qasm = """ + z \$0; + x \$1; + h \$2; + y \$3; + end; + y \$2; + h \$3; + """ + parsed = parse_qasm(qasm) + visitor = QasmProgramVisitor() + visitor(parsed) + @test visitor.instructions == [(type="z", arguments=InstructionArgument[], targets=[0], controls=Pair{Int,Int}[], exponent=1.0), + (type="x", arguments=InstructionArgument[], targets=[1], controls=Pair{Int,Int}[], exponent=1.0), + (type="h", arguments=InstructionArgument[], targets=[2], controls=Pair{Int,Int}[], exponent=1.0), + (type="y", arguments=InstructionArgument[], targets=[3], controls=Pair{Int,Int}[], exponent=1.0), + ] + end + @testset "Switch/case" begin + qasm = """ + input int[8] x; + switch (x + 1) { + case 0b00 {} + default { z \$0; } + } + """ + parsed = parse_qasm(qasm) + visitor = QasmProgramVisitor(Dict("x"=>-1)) + visitor(parsed) + @test isempty(visitor.instructions) + parsed = parse_qasm(qasm) + visitor = QasmProgramVisitor(Dict("x"=>0)) + visitor(parsed) + @test only(visitor.instructions) == (type="z", arguments=InstructionArgument[], targets=[0], controls=Pair{Int,Int}[], exponent=1.0) + qasm = """ + input int[8] x; + switch (x) { case 0 {} case 1, 2 { z \$0; } } + """ + parsed = parse_qasm(qasm) + visitor = QasmProgramVisitor(Dict("x"=>0)) + visitor(parsed) + @test isempty(visitor.instructions) + parsed = parse_qasm(qasm) + visitor = QasmProgramVisitor(Dict("x"=>1)) + visitor(parsed) + @test only(visitor.instructions) == (type="z", arguments=InstructionArgument[], targets=[0], controls=Pair{Int,Int}[], exponent=1.0) + parsed = parse_qasm(qasm) + visitor = QasmProgramVisitor(Dict("x"=>1)) + visitor(parsed) + @test only(visitor.instructions) == (type="z", arguments=InstructionArgument[], targets=[0], controls=Pair{Int,Int}[], exponent=1.0) + qasm = """ + input int[8] x; + switch (x) { case 0 {} default { z \$0; } default { x \$0; } } + """ + @test_throws Quasar.QasmParseError parse_qasm(qasm) + qasm = """ + input int[8] x; + switch (x) { default { z \$0; } case 0 {} } + """ + @test_throws Quasar.QasmParseError parse_qasm(qasm) + qasm = """ + input int[8] x; + switch (x) { case 0 { z \$0; } true {} } + """ + @test_throws Quasar.QasmParseError parse_qasm(qasm) + end + @testset "If/Else" begin + qasm = """ + int[8] two = 2; + bit[3] m = "000"; + + if (two + 1) { + m[0] = 1; + } else { + m[1] = 1; + } + + if (!bool(two - 2)) { + m[2] = 1; + } + """ + parsed = parse_qasm(qasm) + visitor = QasmProgramVisitor() + visitor(parsed) + bit_vec = BitVector([1,0,1]) + @test visitor.classical_defs["m"].val == bit_vec + qasm = """ + int[8] two = -2; + bit[3] m = "000"; + + if (two + 1) { + m[0] = 1; + } else { + m[1] = 1; + } + + if (!bool(two - 2)) { + m[2] = 1; + } + """ + parsed = parse_qasm(qasm) + visitor = QasmProgramVisitor() + visitor(parsed) + bit_vec = BitVector([0,1,1]) + @test visitor.classical_defs["m"].val == bit_vec + qasm = """ + input bool which_gate; + qubit q; + + if (which_gate) { + x q; + } else { + y q; + } + """ + parsed = parse_qasm(qasm) + for (flag, which_gate) in ((true, "x"), (false, "y")) + visitor = QasmProgramVisitor(Dict("which_gate"=>flag)) + visitor(parsed) + @test only(visitor.instructions) == (type=which_gate, arguments=InstructionArgument[], targets=[0], controls=Pair{Int,Int}[], exponent=1.0) + end + end + @testset "Global gate control" begin + qasm = """ + qubit q1; + qubit q2; + + h q1; + h q2; + ctrl @ s q1, q2; + """ + parsed = parse_qasm(qasm) + visitor = QasmProgramVisitor() + visitor(parsed) + @test visitor.instructions == [ + (type="h", arguments=InstructionArgument[], targets=[0], controls=Pair{Int,Int}[], exponent=1.0), + (type="h", arguments=InstructionArgument[], targets=[1], controls=Pair{Int,Int}[], exponent=1.0), + (type="s", arguments=InstructionArgument[], targets=[0, 1], controls=[0=>1], exponent=1.0), + ] + end + @testset "Simple Pow" for gate in ("z", "x") + qasm = """ + qubit q1; + qubit q2; + h q1; + h q2; + + pow(1/2) @ $gate q1; + """ + parsed = parse_qasm(qasm) + visitor = QasmProgramVisitor() + visitor(parsed) + @test visitor.instructions == [ + (type="h", arguments=InstructionArgument[], targets=[0], controls=Pair{Int,Int}[], exponent=1.0), + (type="h", arguments=InstructionArgument[], targets=[1], controls=Pair{Int,Int}[], exponent=1.0), + (type=gate, arguments=InstructionArgument[], targets=[0], controls=Pair{Int,Int}[], exponent=0.5), + ] + end + @testset "Complex Pow" begin + qasm = """ + int[8] two = 2; + gate x a { U(π, 0, π) a; } + gate cx c, a { + pow(1) @ ctrl @ x c, a; + } + gate cxx_1 c, a { + pow(two) @ cx c, a; + } + gate cxx_2 c, a { + pow(1/2) @ pow(4) @ cx c, a; + } + gate cxxx c, a { + pow(1) @ pow(two) @ cx c, a; + } + + qubit q1; + qubit q2; + qubit q3; + qubit q4; + qubit q5; + + pow(1/2) @ x q1; // half flip + pow(1/2) @ x q1; // half flip + cx q1, q2; // flip + cxx_1 q1, q3; // don't flip + cxx_2 q1, q4; // don't flip + cx q1, q5; // flip + x q3; // flip + x q4; // flip + + s q1; // sqrt z + s q1; // again + inv @ z q1; // inv z + """ + parsed = parse_qasm(qasm) + visitor = QasmProgramVisitor() + visitor(parsed) + expected_ixs = [(type="u", arguments=InstructionArgument[π, 0, π], targets=[0], controls=Pair{Int,Int}[], exponent=0.5), + (type="u", arguments=InstructionArgument[π, 0, π], targets=[0], controls=Pair{Int,Int}[], exponent=0.5), + (type="u", arguments=InstructionArgument[π, 0, π], targets=[0, 1], controls=[0=>1], exponent=1.0), + (type="u", arguments=InstructionArgument[π, 0, π], targets=[0, 2], controls=[0=>1], exponent=2.0), + (type="u", arguments=InstructionArgument[π, 0, π], targets=[0, 3], controls=[0=>1], exponent=2.0), + (type="u", arguments=InstructionArgument[π, 0, π], targets=[0, 4], controls=[0=>1], exponent=1.0), + (type="u", arguments=InstructionArgument[π, 0, π], targets=[2], controls=Pair{Int,Int}[], exponent=1.0), + (type="u", arguments=InstructionArgument[π, 0, π], targets=[3], controls=Pair{Int,Int}[], exponent=1.0), + (type="s", arguments=InstructionArgument[], targets=[0], controls=Pair{Int,Int}[], exponent=1.0), + (type="s", arguments=InstructionArgument[], targets=[0], controls=Pair{Int,Int}[], exponent=1.0), + (type="z", arguments=InstructionArgument[], targets=[0], controls=Pair{Int,Int}[], exponent=-1.0), + ] + for (ix, e_ix) in zip(visitor.instructions, expected_ixs) + @test ix == e_ix + end + end + @testset "Gate control" begin + qasm = """ + int[8] two = 2; + gate x a { U(π, 0, π) a; } + gate cx c, a { + ctrl @ x c, a; + } + gate ccx_1 c1, c2, a { + ctrl @ ctrl @ x c1, c2, a; + } + gate ccx_2 c1, c2, a { + ctrl(two) @ x c1, c2, a; + } + gate ccx_3 c1, c2, a { + ctrl @ cx c1, c2, a; + } + + qubit q1; + qubit q2; + qubit q3; + qubit q4; + qubit q5; + + // doesn't flip q2 + cx q1, q2; + // flip q1 + x q1; + // flip q2 + cx q1, q2; + // doesn't flip q3, q4, q5 + ccx_1 q1, q4, q3; + ccx_2 q1, q3, q4; + ccx_3 q1, q3, q5; + // flip q3, q4, q5; + ccx_1 q1, q2, q3; + ccx_2 q1, q2, q4; + ccx_2 q1, q2, q5; + """ + parsed = parse_qasm(qasm) + visitor = QasmProgramVisitor() + visitor(parsed) + expected_ixs = [ + (type="u", arguments=InstructionArgument[π, 0, π], targets=[0, 1], controls=[0=>1], exponent=1.0), + (type="u", arguments=InstructionArgument[π, 0, π], targets=[0], controls=Pair{Int,Int}[], exponent=1.0), + (type="u", arguments=InstructionArgument[π, 0, π], targets=[0, 1], controls=[0=>1], exponent=1.0), + (type="u", arguments=InstructionArgument[π, 0, π], targets=[0, 3, 2], controls=[0=>1, 3=>1], exponent=1.0), + (type="u", arguments=InstructionArgument[π, 0, π], targets=[0, 2, 3], controls=[0=>1, 2=>1], exponent=1.0), + (type="u", arguments=InstructionArgument[π, 0, π], targets=[0, 2, 4], controls=[0=>1, 2=>1], exponent=1.0), + (type="u", arguments=InstructionArgument[π, 0, π], targets=[0, 1, 2], controls=[0=>1, 1=>1], exponent=1.0), + (type="u", arguments=InstructionArgument[π, 0, π], targets=[0, 1, 3], controls=[0=>1, 1=>1], exponent=1.0), + (type="u", arguments=InstructionArgument[π, 0, π], targets=[0, 1, 4], controls=[0=>1, 1=>1], exponent=1.0), + ] + @test length(visitor.instructions) == length(expected_ixs) + @testset for ix in 1:length(expected_ixs) + @test visitor.instructions[ix] == expected_ixs[ix] + end + qasm = """ + gate cccx c1, c2, c3, a { + ctrl(3) @ x c1, c2, c3, a; + } + gate ncccx c1, c2, c3, a { + negctrl(3) @ x c1, c2, c3, a; + } + + qubit q1; + qubit q2; + qubit q3; + qubit q4; + qubit q5; + + h q1; + h q2; + h q3; + h q4; + h q5; + cccx q1, q2, q5, q3; + ncccx q4, q2, q5, q3; + """ + parsed = parse_qasm(qasm) + visitor = QasmProgramVisitor() + visitor(parsed) + @test visitor.instructions == [(type="h", arguments=InstructionArgument[], targets=[0], controls=Pair{Int,Int}[], exponent=1.0), + (type="h", arguments=InstructionArgument[], targets=[1], controls=Pair{Int,Int}[], exponent=1.0), + (type="h", arguments=InstructionArgument[], targets=[2], controls=Pair{Int,Int}[], exponent=1.0), + (type="h", arguments=InstructionArgument[], targets=[3], controls=Pair{Int,Int}[], exponent=1.0), + (type="h", arguments=InstructionArgument[], targets=[4], controls=Pair{Int,Int}[], exponent=1.0), + (type="x", arguments=InstructionArgument[], targets=[0, 1, 4, 2], controls=[0=>1, 1=>1, 4=>1], exponent=1.0), + (type="x", arguments=InstructionArgument[], targets=[3, 1, 4, 2], controls=[3=>0, 1=>0, 4=>0], exponent=1.0), + ] + end + @testset "Gate inverses" begin + qasm = """ + gate both a { + x a; + y a; + } + gate both_inv a { + inv @ both a; + } + qubit q; + + both q; + both_inv q; + inv @ both_inv q; + """ + parsed = parse_qasm(qasm) + visitor = QasmProgramVisitor() + visitor(parsed) + @test visitor.instructions == [ + (type="x", arguments=InstructionArgument[], targets=[0], controls=Pair{Int,Int}[], exponent=1.0), + (type="y", arguments=InstructionArgument[], targets=[0], controls=Pair{Int,Int}[], exponent=1.0), + (type="y", arguments=InstructionArgument[], targets=[0], controls=Pair{Int,Int}[], exponent=-1.0), + (type="x", arguments=InstructionArgument[], targets=[0], controls=Pair{Int,Int}[], exponent=-1.0), + (type="x", arguments=InstructionArgument[], targets=[0], controls=Pair{Int,Int}[], exponent=1.0), + (type="y", arguments=InstructionArgument[], targets=[0], controls=Pair{Int,Int}[], exponent=1.0), + ] + end + @testset "Output" begin + qasm = """ + output int[8] out_int; + """ + parsed = parse_qasm(qasm) + visitor = Quasar.QasmProgramVisitor() + @test_throws Quasar.QasmVisitorError("Output not supported.") visitor(parsed) + end + @testset "Input" begin + qasm = """ + input int[8] in_int; + input bit[8] in_bit; + int[8] doubled; + + doubled = in_int * 2; + """ + in_bit = 0b10110010 + parsed = parse_qasm(qasm) + @testset for in_int in (0, 1, -2, 5) + inputs = Dict("in_int"=>in_int, "in_bit"=>in_bit) + visitor = QasmProgramVisitor(inputs) + visitor(parsed) + @test visitor.classical_defs["doubled"].val == in_int * 2 + @test visitor.classical_defs["in_bit"].val == in_bit + end + end + @testset "Gate on qubit registers" begin + qasm = """ + qubit[3] qs; + qubit q; + + x qs[{0, 2}]; + h q; + cphaseshift(1) qs, q; + phaseshift(-2) q; + ccnot qs; + """ + parsed = parse_qasm(qasm) + visitor = QasmProgramVisitor() + visitor(parsed) + @test visitor.instructions[1] == (type="x", arguments=InstructionArgument[], targets=[0], controls=Pair{Int,Int}[], exponent=1.0) + @test visitor.instructions[2] == (type="x", arguments=InstructionArgument[], targets=[2], controls=Pair{Int,Int}[], exponent=1.0) + @test visitor.instructions[3] == (type="h", arguments=InstructionArgument[], targets=[3], controls=Pair{Int,Int}[], exponent=1.0) + @test visitor.instructions[4] == (type="cphaseshift", arguments=InstructionArgument[1], targets=[0, 3], controls=Pair{Int,Int}[], exponent=1.0) + @test visitor.instructions[5] == (type="cphaseshift", arguments=InstructionArgument[1], targets=[1, 3], controls=Pair{Int,Int}[], exponent=1.0) + @test visitor.instructions[6] == (type="cphaseshift", arguments=InstructionArgument[1], targets=[2, 3], controls=Pair{Int,Int}[], exponent=1.0) + @test visitor.instructions[7] == (type="phaseshift", arguments=InstructionArgument[-2], targets=[3], controls=Pair{Int,Int}[], exponent=1.0) + @test visitor.instructions[8] == (type="ccnot", arguments=InstructionArgument[], targets=[0, 1, 2], controls=Pair{Int,Int}[], exponent=1.0) + end + @testset "Subroutine" begin + qasm = """ + const int[8] n = 4; + def parity(bit[n] cin) -> bit { + bit c = false; + for int[8] i in [0: n - 1] { + c ^= cin[i]; + } + return c; + } + + bit[n] c = "1011"; + bit p = parity(c); + """ + parsed = parse_qasm(qasm) + visitor = QasmProgramVisitor() + visitor(parsed) + @test visitor.classical_defs["p"].val == true + end + @testset "Void subroutine" begin + qasm = """ + def flip(qubit q) { + x q; + } + qubit[2] qs; + flip(qs[0]); + """ + parsed = parse_qasm(qasm) + visitor = QasmProgramVisitor() + visitor(parsed) + @test only(visitor.instructions) == (type="x", arguments=InstructionArgument[], targets=[0], controls=Pair{Int,Int}[], exponent=1.0) + end + @testset "Array ref subroutine" begin + qasm = """ + int[16] total_1; + int[16] total_2; + + def sum(readonly array[int[8], #dim = 1] arr) -> int[16] { + int[16] size = sizeof(arr); + int[16] x = 0; + for int[8] i in [0:size - 1] { + x += arr[i]; + } + return x; + } + + array[int[8], 5] array_1 = {1, 2, 3, 4, 5}; + array[int[8], 10] array_2 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + + total_1 = sum(array_1); + total_2 = sum(array_2); + """ + parsed = parse_qasm(qasm) + visitor = QasmProgramVisitor() + visitor(parsed) + @test visitor.classical_defs["total_1"].val == 15 + @test visitor.classical_defs["total_2"].val == 55 + @test visitor.classical_defs["array_1"].val == [1, 2, 3, 4, 5] + @test visitor.classical_defs["array_2"].val == collect(1:10) + end + @testset "Const array argument" begin + qasm_string = """ + qubit q; + + def sum(const array[int[8], #dim = 1] arr) -> int { + int size = sizeof(arr); + int x = 0; + for int i in [0:size - 1] { + x += arr[i]; + } + return x; + } + + array[int, 10] arr = {9, 3, 6, 2, 2, 4, 3, 1, 12, 7}; + int s = sum(arr); + rx(s*π/4) q; + """ + parsed = parse_qasm(qasm_string) + visitor = QasmProgramVisitor() + visitor(parsed) + @test visitor.classical_defs["s"].val == sum([ 9, 3, 6, 2, 2, 4, 3, 1, 12, 7 ]) + end + @testset "Array ref subroutine with mutation" begin + qasm = """ + def mutate_array(mutable array[int[8], #dim = 1] arr) { + int[16] size = sizeof(arr); + for int[8] i in [0:size - 1] { + arr[i] = 0; + } + } + + array[int[8], 5] array_1 = {1, 2, 3, 4, 5}; + array[int[8], 10] array_2 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + array[int[8], 10] array_3 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + + mutate_array(array_1); + mutate_array(array_2); + mutate_array(array_3[4:2:-1]); + """ + parsed = parse_qasm(qasm) + visitor = QasmProgramVisitor() + visitor(parsed) + @test visitor.classical_defs["array_1"].val == zeros(Int, 5) + @test visitor.classical_defs["array_2"].val == zeros(Int, 10) + @test visitor.classical_defs["array_3"].val == [1, 2, 3, 4, 0, 6, 0, 8, 0, 10] + end + # TODO + #=@testset "Rotation parameter expressions" begin + @testset "Operation: $operation" for (operation, state_vector) in + [ + ["rx(π) q[0];", [0, -im]], + ["rx(pi) q[0];", [0, -im]], + ["rx(ℇ) q[0];", [0.21007866, -0.97768449im]], + ["rx(euler) q[0];", [0.21007866, -0.97768449im]], + ["rx(τ) q[0];", [-1, 0]], + ["rx(tau) q[0];", [-1, 0]], + ["rx(pi + pi) q[0];", [-1, 0]], + ["rx(pi - pi) q[0];", [1, 0]], + ["rx(-pi + pi) q[0];", [1, 0]], + ["rx(pi * 2) q[0];", [-1, 0]], + ["rx(pi / 2) q[0];", [0.70710678, -0.70710678im]], + ["rx(-pi / 2) q[0];", [0.70710678, 0.70710678im]], + ["rx(-pi) q[0];", [0, im]], + ["rx(pi + 2 * pi) q[0];", [0, im]], + ["rx(pi + pi / 2) q[0];", [-0.70710678, -0.70710678im]], + ["rx((pi / 4) + (pi / 2) / 2) q[0];", [0.70710678, -0.70710678im]], + ["rx(0) q[0];", [1, 0]], + ["rx(0 + 0) q[0];", [1, 0]], + ["rx((1.1 + 2.04) / 2) q[0];", [0.70738827, -0.70682518im]], + ["rx((6 - 2.86) * 0.5) q[0];", [0.70738827, -0.70682518im]], + ["rx(pi ** 2) q[0];", [0.22058404, 0.97536797im]], + ] + qasm = """ + OPENQASM 3.0; + bit[1] b; + qubit[1] q; + $operation + """ + end + end=# + @testset "Qubits with variable as size" begin + qasm_string = """ + OPENQASM 3.0; + + def ghz(int[32] n) { + h q[0]; + for int i in [0:n - 1] { + cnot q[i], q[i + 1]; + } + } + + int[32] n = 5; + bit[n + 1] c; + qubit[n + 1] q; + + ghz(n); + + c = measure q; + """ + parsed = parse_qasm(qasm_string) + visitor = Quasar.QasmProgramVisitor() + visitor(parsed) + @test Quasar.qubit_count(visitor) == 6 + end + @testset "String inputs" begin + qasm_string = """ + const int[8] n = 4; + input bit[n] x; + + qubit q; + + def parity(bit[n] cin) -> bit { + bit c = false; + for int[8] i in [0: n - 1] { + c ^= cin[i]; + } + return c; + } + + if(parity(x)) { + x q; + } else { + i q; + } + """ + parsed = parse_qasm(qasm_string) + visitor = Quasar.QasmProgramVisitor(Dict("x"=>"1011")) + visitor(parsed) + @test only(visitor.instructions) == (type="x", arguments=InstructionArgument[], targets=[0], controls=Pair{Int,Int}[], exponent=1.0) + visitor = Quasar.QasmProgramVisitor(Dict("x"=>"0011")) + visitor(parsed) + @test only(visitor.instructions) == (type="i", arguments=InstructionArgument[], targets=[0], controls=Pair{Int,Int}[], exponent=1.0) + end + @testset "Include" begin + mktempdir() do dir_path + inc_path = joinpath(dir_path, "new_gate.inc") + write(inc_path, """\ngate u3(θ, ϕ, λ) q { gphase(-(ϕ+λ)/2); U(θ, ϕ, λ) q; }\n""") + qasm = """ + OPENQASM 3; + include "$inc_path"; + """ + parsed = parse_qasm(qasm) + visitor = QasmProgramVisitor() + visitor(parsed) + @test haskey(visitor.gate_defs, "u3") + end + @testset "stdgates.inc" begin + qasm = """ + OPENQASM 3.0; + include "stdgates.inc"; + """ + parsed = parse_qasm(qasm) + visitor = QasmProgramVisitor() + visitor(parsed) + @test haskey(visitor.gate_defs, "tdg") + end + end +end diff --git a/test/stdgates.inc b/test/stdgates.inc new file mode 100644 index 0000000..2599b22 --- /dev/null +++ b/test/stdgates.inc @@ -0,0 +1,82 @@ +// OpenQASM 3.0 standard gate library +// +// Vendored from the OpenQASM 3.0 project at commit 4ca1d793833b24a1 of +// https://github.com/openqasm/openqasm. +// +// Used under the terms of its Apache-2.0 license. +// Copyright 2017-2024 OpenQASM Contributors. + + +// phase gate +gate p(λ) a { ctrl @ gphase(λ) a; } + +// Pauli gate: bit-flip or NOT gate +gate x a { U(π, 0, π) a; gphase(-π/2);} +// Pauli gate: bit and phase flip +gate y a { U(π, π/2, π/2) a; gphase(-π/2);} + // Pauli gate: phase flip +gate z a { p(π) a; } + +// Clifford gate: Hadamard +gate h a { U(π/2, 0, π) a; gphase(-π/4);} +// Clifford gate: sqrt(Z) or S gate +gate s a { pow(0.5) @ z a; } +// Clifford gate: inverse of sqrt(Z) +gate sdg a { inv @ pow(0.5) @ z a; } + +// sqrt(S) or T gate +gate t a { pow(0.5) @ s a; } +// inverse of sqrt(S) +gate tdg a { inv @ pow(0.5) @ s a; } + +// sqrt(NOT) gate +gate sx a { pow(0.5) @ x a; } + +// Rotation around X-axis +gate rx(θ) a { U(θ, -π/2, π/2) a; gphase(-θ/2);} +// rotation around Y-axis +gate ry(θ) a { U(θ, 0, 0) a; gphase(-θ/2);} +// rotation around Z axis +gate rz(λ) a { gphase(-λ/2); U(0, 0, λ) a; } + +// controlled-NOT +gate cx a, b { ctrl @ x a, b; } +// controlled-Y +gate cy a, b { ctrl @ y a, b; } +// controlled-Z +gate cz a, b { ctrl @ z a, b; } +// controlled-phase +gate cp(λ) a, b { ctrl @ p(λ) a, b; } +// controlled-rx +gate crx(θ) a, b { ctrl @ rx(θ) a, b; } +// controlled-ry +gate cry(θ) a, b { ctrl @ ry(θ) a, b; } +// controlled-rz +gate crz(θ) a, b { ctrl @ rz(θ) a, b; } +// controlled-H +gate ch a, b { ctrl @ h a, b; } + +// swap +gate swap a, b { cx a, b; cx b, a; cx a, b; } + +// Toffoli +gate ccx a, b, c { ctrl @ ctrl @ x a, b, c; } +// controlled-swap +gate cswap a, b, c { ctrl @ swap a, b, c; } + +// four parameter controlled-U gate with relative phase γ +gate cu(θ, φ, λ, γ) a, b { p(γ-θ/2) a; ctrl @ U(θ, φ, λ) a, b; } + +// Gates for OpenQASM 2 backwards compatibility +// CNOT +gate CX a, b { ctrl @ U(π, 0, π) a, b; } +// phase gate +gate phase(λ) q { U(0, 0, λ) q; } +// controlled-phase +gate cphase(λ) a, b { ctrl @ phase(λ) a, b; } +// identity or idle gate +gate id a { U(0, 0, 0) a; } +// IBM Quantum experience gates +gate u1(λ) q { U(0, 0, λ) q; } +gate u2(φ, λ) q { gphase(-(φ+λ+π/2)/2); U(π/2, φ, λ) q; } +gate u3(θ, φ, λ) q { gphase(-(φ+λ+θ)/2); U(θ, φ, λ) q; } From 3032e8fc63cf92959ee693923fe7259fdf631a93 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Oct 2024 18:44:08 +0000 Subject: [PATCH 2/2] Bump actions/checkout from 2 to 4 Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v2...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/Documentation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/Documentation.yml b/.github/workflows/Documentation.yml index c13a234..a54ef85 100644 --- a/.github/workflows/Documentation.yml +++ b/.github/workflows/Documentation.yml @@ -13,7 +13,7 @@ jobs: statuses: write runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - uses: julia-actions/setup-julia@v2 with: version: '1'