Skip to content
6 changes: 6 additions & 0 deletions .github/workflows/CompatHelper.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,16 @@ on:
schedule:
- cron: 0 0 * * *
workflow_dispatch:
permissions:
contents: write
pull-requests: write
jobs:
CompatHelper:
runs-on: ubuntu-latest
steps:
- uses: julia-actions/setup-julia@v1
with:
version: '1'
- name: Pkg.add("CompatHelper")
run: julia -e 'using Pkg; Pkg.add("CompatHelper")'
- name: CompatHelper.main()
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/TagBot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ on:
types:
- created
workflow_dispatch:
permissions:
contents: write
issues: write
pull-requests: write
jobs:
TagBot:
if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot'
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,20 @@ jobs:
fail-fast: false
matrix:
version:
- '1.0'
- '1.10'
- '1'
- 'nightly'
os:
- ubuntu-latest
arch:
- x64
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- uses: julia-actions/setup-julia@v1
with:
version: ${{ matrix.version }}
arch: ${{ matrix.arch }}
- uses: actions/cache@v1
- uses: actions/cache@v4
env:
cache-name: cache-artifacts
with:
Expand All @@ -36,6 +36,6 @@ jobs:
- uses: julia-actions/julia-buildpkg@v1
- uses: julia-actions/julia-runtest@v1
- uses: julia-actions/julia-processcoverage@v1
- uses: codecov/codecov-action@v1
- uses: codecov/codecov-action@v3
with:
file: lcov.info
6 changes: 4 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
name = "OpenQASM"
uuid = "a8821629-a4c0-4df7-9e00-12969ff383a7"
version = "2.2.0"
authors = ["Roger-luo <rogerluo.rl18@gmail.com> and contributors"]
version = "2.1.4"

[deps]
Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
MLStyle = "d8e11817-5142-5d16-987a-aa16d5891078"
RBNF = "83ef0002-5b9e-11e9-219b-65bac3c6d69c"

[compat]
Aqua = "0.8.14"
MLStyle = "0.4"
RBNF = "0.2"
julia = "1"
julia = "1.10"

[extras]
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
Expand Down
129 changes: 125 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
[![CI](https://github.com/QuantumBFS/OpenQASM.jl/actions/workflows/ci.yml/badge.svg)](https://github.com/QuantumBFS/OpenQASM.jl/actions/workflows/ci.yml)
[![Coverage](https://codecov.io/gh/QuantumBFS/OpenQASM.jl/branch/master/graph/badge.svg)](https://codecov.io/gh/QuantumBFS/OpenQASM.jl)

Tools for parsing OpenQASM.
Tools for parsing OpenQASM 2.0 and 3.0.

## Installation

Expand All @@ -24,16 +24,137 @@ pkg> add OpenQASM

## Usage

This package provides a simple function `OpenQASM.parse` to parse a QASM string to
its AST according to its BNF specification described in [OpenQASM 2.0](https://github.com/Qiskit/openqasm/tree/OpenQASM2.x).
This package provides a simple function `OpenQASM.parse` to parse QASM programs (both 2.0 and 3.0) to their AST representation.

### OpenQASM 2.0

Parse QASM 2.0 programs according to the [OpenQASM 2.0 specification](https://github.com/Qiskit/openqasm/tree/OpenQASM2.x):

```julia
using OpenQASM

qasm_2_0 = """
OPENQASM 2.0;
include "qelib1.inc";
qreg q[2];
creg c[2];
h q[0];
cx q[0], q[1];
measure q -> c;
"""

ast = parse(qasm_2_0) # Auto-detects version 2.0
```

![demo](demo.png)

### OpenQASM 3.0

Parse QASM 3.0 programs with support for classical types, control flow, gate modifiers, and input/output parameters:

```julia
using OpenQASM

qasm_3_0 = """
OPENQASM 3.0;
include "stdgates.inc";

input float[64] theta;
qubit[2] q;
bit[2] c;

reset q[0];
reset q[1];

ry(theta) q[0];
ctrl @ x q[0], q[1];

measure q -> c;

if (c[0] == 1) {
x q[0];
}

output bit[2] c;
"""

ast = parse(qasm_3_0) # Auto-detects version 3.0
```

### Supported OpenQASM 3.0 Features

This package currently supports the following OpenQASM 3.0 features:

- **Classical Types**: `int`, `uint`, `float`, `bit`, `angle` with optional bit widths
```julia
int[32] x = 5;
float[64] y = 3.14;
const angle[20] theta = pi/4;
```

- **Control Flow**: `if-else`, `while`, `for` loops, `break`, `continue`
```julia
if (c == 1) { x q; } else { h q; }
while (c == 0) { x q; }
for int i in [0:10] { ... }
for int i in {1, 5, 10} { ... }
```

- **Gate Modifiers**: `inv`, `ctrl`, `negctrl`, `pow`
```julia
inv @ h q[0];
ctrl @ x q[0], q[1];
pow(2) @ s q[0];
```

- **Input/Output Parameters**: Parameterized circuits
```julia
input float[64] theta;
output bit[2] c;
```

- **Qubit Declarations**: New syntax alongside legacy `qreg`/`creg`
```julia
qubit[2] q; // New QASM 3.0 syntax
qreg q[2]; // Legacy syntax (still supported)
```

### API Reference

- `parse(src::String; version=:auto)` - Parse QASM program with auto version detection
- `parse_v2(src::String)` - Parse QASM 2.0 program explicitly
- `parse_v3(src::String)` - Parse QASM 3.0 program explicitly
- `detect_version(src::String)` - Detect OpenQASM version from source code
- `parse_gate(src::String)` - Parse a QASM 2.0 gate definition

### Breaking Changes from OpenQASM 2.0 to 3.0

Important differences to be aware of when migrating from QASM 2.0 to 3.0:

1. **Qubit Initialization**: In QASM 3.0, qubits are **NOT** automatically initialized to |0⟩. You must explicitly use `reset`:
```julia
// QASM 2.0: qreg q[2]; automatically initializes to |00⟩
// QASM 3.0: qubit[2] q; does NOT initialize
qubit[2] q;
reset q; // Required to ensure |0⟩ state
```

2. **New Syntax**: Prefer `qubit[n]` over `qreg`, and `bit[n]` over `creg` in QASM 3.0 (though legacy syntax is still supported)

3. **Enhanced Expressions**: QASM 3.0 supports logical operators (`&&`, `||`) and comparison operators (`==`, `!=`, `<`, `>`, `<=`, `>=`)

## Roadmap

- [x] support for QASM 2.0
- [ ] support for QASM 3.0
- [x] support for QASM 3.0 (basic features)
- [x] Classical types (int, uint, float, bit, angle)
- [x] Control flow (if-else, while, for loops)
- [x] Gate modifiers (inv, ctrl, pow)
- [x] Input/output parameters
- [ ] Arrays (multi-dimensional)
- [ ] Subroutines (def keyword)
- [ ] Pulse-level calibration (defcal)
- [ ] Timing control (delay, box, stretch)

## Cite Us

Expand Down
84 changes: 75 additions & 9 deletions src/OpenQASM.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,94 @@ module OpenQASM

using RBNF

include("types.jl")
include("parse.jl")
include("tools.jl")
# Include modules in dependency order
include("types.jl") # Base AST types for QASM 2.0
include("types_v3.jl") # AST types for QASM 3.0
include("token_wrappers.jl") # Token wrapper types to avoid type piracy
include("qasm_common.jl") # Shared utilities
include("parse.jl") # QASM 2.0 parser
include("parse_v3.jl") # QASM 3.0 parser
include("tools.jl") # Utilities

using .Types: print_qasm
using .Tools: cmp_ast

export parse, parse_v2, parse_v3, parse_gate, detect_version

"""
detect_version(src::String)

Detect the OpenQASM version from the source code.
Returns `v"2.0.0"` by default if no version is found.
"""
parse(qasm::String)
function detect_version(src::String)
m = match(r"OPENQASM\s+([\d.]+)", src)
m === nothing && return v"2.0.0" # Default to 2.0
return VersionNumber(m.captures[1])
end

Parse a piece of QASM program at top-level to AST.
"""
function parse(src::String)
parse(src::String; version=:auto)

Parse a piece of QASM program to AST with automatic version detection.

# Arguments
- `src::String`: The QASM source code
- `version`: Version to use for parsing. Can be:
- `:auto` (default): Auto-detect from source
- `2` or `v"2.0"`: Force QASM 2.0 parser
- `3` or `v"3.0"`: Force QASM 3.0 parser

# Examples
```julia
# Auto-detect version
parse("OPENQASM 2.0; qreg q[2];") # Uses QASM 2.0 parser
parse("OPENQASM 3.0; qubit[2] q;") # Uses QASM 3.0 parser

# Force specific version
parse(src, version=2)
parse(src, version=3)
```
"""
function parse(src::String; version=:auto)
if version == :auto
detected = detect_version(src)
return detected >= v"3.0" ? parse_v3(src) : parse_v2(src)
elseif version == 2 || version == v"2.0"
return parse_v2(src)
elseif version == 3 || version == v"3.0"
return parse_v3(src)
else
throw(ArgumentError("Unsupported QASM version: $version"))
end
end

"""
parse_v2(src::String)

Parse a QASM 2.0 program to AST.
"""
function parse_v2(src::String)
ast, ctx = RBNF.runparser(Parse.mainprogram, RBNF.runlexer(Parse.QASMLang, src))
ctx.tokens.current > ctx.tokens.length || throw(Meta.ParseError("invalid syntax in QASM program"))
ctx.tokens.current > ctx.tokens.length || throw(Meta.ParseError("invalid syntax in QASM 2.0 program"))
return ast
end

"""
parse_v3(src::String)

Parse a QASM 3.0 program to AST.
"""
function parse_v3(src::String)
ast, ctx = RBNF.runparser(ParseV3.mainprogram, RBNF.runlexer(ParseV3.QASM3Lang, src))
ctx.tokens.current > ctx.tokens.length || throw(Meta.ParseError("invalid syntax in QASM 3.0 program"))
return ast
end

"""
parse(qasm::String)
parse_gate(src::String)

Parse a piece of QASM gate program.
Parse a piece of QASM 2.0 gate program.
"""
function parse_gate(src::String)
ast, ctx = RBNF.runparser(Parse.gate, RBNF.runlexer(Parse.QASMLang, src))
Expand Down
Loading
Loading