Skip to content

Add missing statements to QASM3 parser #2200

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 31 commits into from
Mar 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
7311b7f
gate calls
orpuente-MS Feb 24, 2025
982592f
update expect tests
orpuente-MS Feb 25, 2025
23e07df
gate-call qubit args should be comma separated
orpuente-MS Feb 25, 2025
ccc0342
add barrier, box, calgrammar, cal, defcal, delay, measure arrow, and …
orpuente-MS Feb 25, 2025
a1cbc8f
update completions tests
orpuente-MS Feb 25, 2025
0454d04
add unit tests
orpuente-MS Feb 25, 2025
bc5c8e8
fix formatting
orpuente-MS Feb 25, 2025
fae7bff
update completions tests
orpuente-MS Feb 25, 2025
94c0b1f
update unit tests
orpuente-MS Feb 25, 2025
1baa92d
add support for timing literals
orpuente-MS Feb 26, 2025
f47b47e
improve TODO messages
orpuente-MS Feb 26, 2025
a11c1c9
address comments in PR review
orpuente-MS Feb 26, 2025
36d5f1b
add gphase
orpuente-MS Feb 27, 2025
d26cea7
add unit test for box with designator
orpuente-MS Feb 27, 2025
2384643
require exactly one angle argument in `gphase`
orpuente-MS Feb 27, 2025
8672881
fix parse_include and parse_cal_grammar
orpuente-MS Feb 27, 2025
25ad75b
PR review fixes
orpuente-MS Feb 27, 2025
552e77e
fixes during PR review
orpuente-MS Feb 28, 2025
92acb17
fixes during PR review
orpuente-MS Feb 28, 2025
99c22c7
add unit tests for gate_call
orpuente-MS Feb 28, 2025
a56bcbf
change unit tests names
orpuente-MS Feb 28, 2025
b701ff6
format file
orpuente-MS Feb 28, 2025
20c85e3
disambiguate between cast expr_stmts and classical decls
orpuente-MS Feb 28, 2025
fafa524
update cast unit tests
orpuente-MS Feb 28, 2025
a541210
make expr unit tests also run for expr_stmts by adding a semicolon at…
orpuente-MS Feb 28, 2025
9f395b3
fix formatting
orpuente-MS Feb 28, 2025
a47fe4a
fix lexer bug
orpuente-MS Mar 1, 2025
111823b
format code
orpuente-MS Mar 1, 2025
a4dab64
fix lexer bug and remove `second` from cooked lexer
orpuente-MS Mar 1, 2025
d4d3a3a
update test
orpuente-MS Mar 1, 2025
1c0b82d
remove extra scope
orpuente-MS Mar 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
238 changes: 98 additions & 140 deletions compiler/qsc_qasm3/src/ast.rs

Large diffs are not rendered by default.

122 changes: 73 additions & 49 deletions compiler/qsc_qasm3/src/lex/cooked.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,6 @@ pub enum TokenKind {
NegCtrl,
Dim,
DurationOf,
Delay,
Reset,
Measure,

Literal(Literal),
Expand All @@ -125,6 +123,8 @@ pub enum TokenKind {
PlusPlus,
/// `->`
Arrow,
/// `@`
At,

// Operators,
ClosedBinOp(ClosedBinOp),
Expand Down Expand Up @@ -157,8 +157,6 @@ impl Display for TokenKind {
TokenKind::NegCtrl => write!(f, "negctrl"),
TokenKind::Dim => write!(f, "dim"),
TokenKind::DurationOf => write!(f, "durationof"),
TokenKind::Delay => write!(f, "delay"),
TokenKind::Reset => write!(f, "reset"),
TokenKind::Measure => write!(f, "measure"),
TokenKind::Literal(literal) => write!(f, "literal `{literal}`"),
TokenKind::Open(Delim::Brace) => write!(f, "`{{`"),
Expand All @@ -173,6 +171,7 @@ impl Display for TokenKind {
TokenKind::Comma => write!(f, "`,`"),
TokenKind::PlusPlus => write!(f, "`++`"),
TokenKind::Arrow => write!(f, "`->`"),
TokenKind::At => write!(f, "`@`"),
TokenKind::ClosedBinOp(op) => write!(f, "`{op}`"),
TokenKind::BinOpEq(op) => write!(f, "`{op}=`"),
TokenKind::ComparisonOp(op) => write!(f, "`{op}`"),
Expand Down Expand Up @@ -404,6 +403,12 @@ pub(crate) struct Lexer<'a> {

// This uses a `Peekable` iterator over the raw lexer, which allows for one token lookahead.
tokens: Peekable<raw::Lexer<'a>>,

/// This flag is used to detect annotations at the
/// beginning of a file. Normally annotations are
/// detected because there is a Newline followed by an `@`,
/// but there is no newline at the beginning of a file.
beginning_of_file: bool,
}

impl<'a> Lexer<'a> {
Expand All @@ -415,6 +420,7 @@ impl<'a> Lexer<'a> {
.try_into()
.expect("input length should fit into u32"),
tokens: raw::Lexer::new(input).peekable(),
beginning_of_file: true,
}
}

Expand Down Expand Up @@ -455,14 +461,6 @@ impl<'a> Lexer<'a> {
self.tokens.peek().map(|i| i.kind)
}

/// Returns the second token ahead of the cursor without consuming it. This is slower
/// than [`first`] and should be avoided when possible.
fn second(&self) -> Option<raw::TokenKind> {
let mut tokens = self.tokens.clone();
tokens.next();
tokens.next().map(|i| i.kind)
}

/// Consumes the characters while they satisfy `f`. Returns the last character eaten, if any.
fn eat_while(&mut self, mut f: impl FnMut(raw::TokenKind) -> bool) -> Option<raw::TokenKind> {
let mut last_eaten: Option<raw::Token> = None;
Expand Down Expand Up @@ -503,8 +501,27 @@ impl<'a> Lexer<'a> {
hi: token.offset,
}))
}
raw::TokenKind::Comment(_) | raw::TokenKind::Newline | raw::TokenKind::Whitespace => {
Ok(None)
raw::TokenKind::Comment(_) | raw::TokenKind::Whitespace => Ok(None),
raw::TokenKind::Newline => {
// AnnotationKeyword: '@' Identifier ('.' Identifier)* -> pushMode(EAT_TO_LINE_END);
self.next_if_eq(raw::TokenKind::Whitespace);
match self.tokens.peek() {
Some(token) if token.kind == raw::TokenKind::Single(Single::At) => {
let token = self.tokens.next().expect("self.tokens.peek() was Some(_)");
let complete = TokenKind::Annotation;
self.expect(raw::TokenKind::Ident, complete);
self.eat_to_end_of_line();
let kind = Some(complete);
return Ok(kind.map(|kind| {
let span = Span {
lo: token.offset,
hi: self.offset(),
};
Token { kind, span }
}));
}
_ => Ok(None),
}
}
raw::TokenKind::Ident => {
let ident = &self.input[(token.offset as usize)..(self.offset() as usize)];
Expand All @@ -529,31 +546,31 @@ impl<'a> Lexer<'a> {
raw::TokenKind::Number(number) => {
// after reading a decimal number or a float there could be a whitespace
// followed by a fragment, which will change the type of the literal.
match (self.first(), self.second()) {
(Some(raw::TokenKind::LiteralFragment(fragment)), _)
| (
Some(raw::TokenKind::Whitespace),
Some(raw::TokenKind::LiteralFragment(fragment)),
) => {
use self::Literal::{Imaginary, Timing};
use TokenKind::Literal;

// if first() was a whitespace, we need to consume an extra token
if self.first() == Some(raw::TokenKind::Whitespace) {
self.next();
}
self.next();

Ok(Some(match fragment {
raw::LiteralFragmentKind::Imag => Literal(Imaginary),
raw::LiteralFragmentKind::Dt => Literal(Timing(TimingLiteralKind::Dt)),
raw::LiteralFragmentKind::Ns => Literal(Timing(TimingLiteralKind::Ns)),
raw::LiteralFragmentKind::Us => Literal(Timing(TimingLiteralKind::Us)),
raw::LiteralFragmentKind::Ms => Literal(Timing(TimingLiteralKind::Ms)),
raw::LiteralFragmentKind::S => Literal(Timing(TimingLiteralKind::S)),
}))
}
_ => Ok(Some(number.into())),
let numeric_part_hi = self.offset();
self.next_if_eq(raw::TokenKind::Whitespace);

if let Some(raw::TokenKind::LiteralFragment(fragment)) = self.first() {
use self::Literal::{Imaginary, Timing};
use TokenKind::Literal;

// Consume the fragment.
self.next();

Ok(Some(match fragment {
raw::LiteralFragmentKind::Imag => Literal(Imaginary),
raw::LiteralFragmentKind::Dt => Literal(Timing(TimingLiteralKind::Dt)),
raw::LiteralFragmentKind::Ns => Literal(Timing(TimingLiteralKind::Ns)),
raw::LiteralFragmentKind::Us => Literal(Timing(TimingLiteralKind::Us)),
raw::LiteralFragmentKind::Ms => Literal(Timing(TimingLiteralKind::Ms)),
raw::LiteralFragmentKind::S => Literal(Timing(TimingLiteralKind::S)),
}))
} else {
let kind: TokenKind = number.into();
let span = Span {
lo: token.offset,
hi: numeric_part_hi,
};
return Ok(Some(Token { kind, span }));
}
}
raw::TokenKind::Single(Single::Sharp) => {
Expand Down Expand Up @@ -620,11 +637,14 @@ impl<'a> Lexer<'a> {
}
}
Single::At => {
// AnnotationKeyword: '@' Identifier ('.' Identifier)* -> pushMode(EAT_TO_LINE_END);
let complete = TokenKind::Annotation;
self.expect(raw::TokenKind::Ident, complete);
self.eat_to_end_of_line();
Ok(complete)
if self.beginning_of_file {
let complete = TokenKind::Annotation;
self.expect(raw::TokenKind::Ident, complete);
self.eat_to_end_of_line();
Ok(complete)
} else {
Ok(TokenKind::At)
}
}
Single::Bang => {
if self.next_if_eq_single(Single::Eq) {
Expand Down Expand Up @@ -717,8 +737,6 @@ impl<'a> Lexer<'a> {
"negctrl" => TokenKind::NegCtrl,
"dim" => TokenKind::Dim,
"durationof" => TokenKind::DurationOf,
"delay" => TokenKind::Delay,
"reset" => TokenKind::Reset,
"measure" => TokenKind::Measure,
ident => {
if let Ok(keyword) = ident.parse::<Keyword>() {
Expand All @@ -739,9 +757,15 @@ impl Iterator for Lexer<'_> {
fn next(&mut self) -> Option<Self::Item> {
while let Some(token) = self.tokens.next() {
match self.cook(&token) {
Ok(None) => {}
Ok(Some(token)) => return Some(Ok(token)),
Err(err) => return Some(Err(err)),
Ok(None) => self.beginning_of_file = false,
Ok(Some(token)) => {
self.beginning_of_file = false;
return Some(Ok(token));
}
Err(err) => {
self.beginning_of_file = false;
return Some(Err(err));
}
}
}

Expand Down
34 changes: 32 additions & 2 deletions compiler/qsc_qasm3/src/lex/cooked/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,10 @@ fn op_string(kind: TokenKind) -> Option<String> {
TokenKind::NegCtrl => Some("negctrl".to_string()),
TokenKind::Dim => Some("dim".to_string()),
TokenKind::DurationOf => Some("durationof".to_string()),
TokenKind::Delay => Some("delay".to_string()),
TokenKind::Reset => Some("reset".to_string()),
TokenKind::Measure => Some("measure".to_string()),
TokenKind::Semicolon => Some(";".to_string()),
TokenKind::Arrow => Some("->".to_string()),
TokenKind::At => Some("@".to_string()),
TokenKind::ClosedBinOp(op) => Some(op.to_string()),
TokenKind::BinOpEq(super::ClosedBinOp::AmpAmp | super::ClosedBinOp::BarBar)
| TokenKind::Literal(_)
Expand Down Expand Up @@ -364,6 +363,37 @@ fn imag_with_whitespace() {
);
}

#[test]
fn imag_with_whitespace_semicolon() {
check(
"123 im;",
&expect![[r#"
[
Ok(
Token {
kind: Literal(
Imaginary,
),
span: Span {
lo: 0,
hi: 6,
},
},
),
Ok(
Token {
kind: Semicolon,
span: Span {
lo: 6,
hi: 7,
},
},
),
]
"#]],
);
}

#[test]
fn negative_imag() {
check(
Expand Down
35 changes: 35 additions & 0 deletions compiler/qsc_qasm3/src/lex/raw/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1274,3 +1274,38 @@ fn hardware_qubit_with_underscore_in_the_middle() {
"#]],
);
}

#[test]
fn decimal_space_imag_semicolon() {
check(
"10 im;",
&expect![[r#"
[
Token {
kind: Number(
Int(
Decimal,
),
),
offset: 0,
},
Token {
kind: Whitespace,
offset: 2,
},
Token {
kind: LiteralFragment(
Imag,
),
offset: 4,
},
Token {
kind: Single(
Semi,
),
offset: 6,
},
]
"#]],
);
}
4 changes: 2 additions & 2 deletions compiler/qsc_qasm3/src/parser/completion/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ fn begin_document() {
"|OPENQASM 3;",
&expect![[r#"
WordKinds(
Annotation | Break | Continue | CReg | Def | End | Extern | False | For | Gate | If | Include | Input | Let | OpenQASM | Output | Pragma | QReg | Qubit | True | Return | Switch | While,
Annotation | Barrier | Box | Break | Cal | Const | Continue | CReg | Def | DefCal | DefCalGrammar | Delay | End | Extern | False | For | Gate | If | Include | Input | Let | OpenQASM | Output | Pragma | QReg | Qubit | Reset | True | Return | Switch | While,
)
"#]],
);
Expand All @@ -48,7 +48,7 @@ fn end_of_version() {
"OPENQASM 3;|",
&expect![[r#"
WordKinds(
Annotation | Break | Continue | CReg | Def | End | Extern | False | For | Gate | If | Include | Input | Let | Output | Pragma | QReg | Qubit | True | Return | Switch | While,
Annotation | Barrier | Box | Break | Cal | Const | Continue | CReg | Def | DefCal | DefCalGrammar | Delay | End | Extern | False | For | Gate | If | Include | Input | Let | Output | Pragma | QReg | Qubit | Reset | True | Return | Switch | While,
)
"#]],
);
Expand Down
16 changes: 16 additions & 0 deletions compiler/qsc_qasm3/src/parser/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ pub enum ErrorKind {
#[error("expected {0}, found {1}")]
#[diagnostic(code("Qasm3.Parse.Rule"))]
Rule(&'static str, TokenKind, #[label] Span),
#[error("invalid classical statement in box")]
#[diagnostic(code("Qasm3.Parse.ClassicalStmtInBox"))]
ClassicalStmtInBox(#[label] Span),
#[error("expected {0}, found {1}")]
#[diagnostic(code("Qasm3.Parse.Convert"))]
Convert(&'static str, &'static str, #[label] Span),
Expand All @@ -119,9 +122,18 @@ pub enum ErrorKind {
#[error("missing switch statement case labels")]
#[diagnostic(code("Qasm3.Parse.MissingSwitchCaseLabels"))]
MissingSwitchCaseLabels(#[label] Span),
#[error("missing switch statement case labels")]
#[diagnostic(code("Qasm3.Parse.MissingGateCallOperands"))]
MissingGateCallOperands(#[label] Span),
#[error("expected an item or closing brace, found {0}")]
#[diagnostic(code("Qasm3.Parse.ExpectedItem"))]
ExpectedItem(TokenKind, #[label] Span),
#[error("gphase gate requires exactly one angle")]
#[diagnostic(code("Qasm3.Parse.GPhaseInvalidArguments"))]
GPhaseInvalidArguments(#[label] Span),
#[error("invalid gate call designator")]
#[diagnostic(code("Qasm3.Parse.InvalidGateCallDesignator"))]
InvalidGateCallDesignator(#[label] Span),
}

impl ErrorKind {
Expand All @@ -132,14 +144,18 @@ impl ErrorKind {
Self::Escape(ch, span) => Self::Escape(ch, span + offset),
Self::Token(expected, actual, span) => Self::Token(expected, actual, span + offset),
Self::Rule(name, token, span) => Self::Rule(name, token, span + offset),
Self::ClassicalStmtInBox(span) => Self::ClassicalStmtInBox(span + offset),
Self::Convert(expected, actual, span) => Self::Convert(expected, actual, span + offset),
Self::MissingSemi(span) => Self::MissingSemi(span + offset),
Self::MissingParens(span) => Self::MissingParens(span + offset),
Self::FloatingAnnotation(span) => Self::FloatingAnnotation(span + offset),
Self::MissingSeqEntry(span) => Self::MissingSeqEntry(span + offset),
Self::MissingSwitchCases(span) => Self::MissingSwitchCases(span + offset),
Self::MissingSwitchCaseLabels(span) => Self::MissingSwitchCaseLabels(span + offset),
Self::MissingGateCallOperands(span) => Self::MissingGateCallOperands(span + offset),
Self::ExpectedItem(token, span) => Self::ExpectedItem(token, span + offset),
Self::GPhaseInvalidArguments(span) => Self::GPhaseInvalidArguments(span + offset),
Self::InvalidGateCallDesignator(span) => Self::InvalidGateCallDesignator(span + offset),
}
}
}
Loading