diff --git a/crates/oq3_parser/src/grammar/expressions.rs b/crates/oq3_parser/src/grammar/expressions.rs index 9dd8537..3075ee7 100644 --- a/crates/oq3_parser/src/grammar/expressions.rs +++ b/crates/oq3_parser/src/grammar/expressions.rs @@ -422,17 +422,39 @@ fn type_name(p: &mut Parser<'_>) { p.bump(p.current()); } +/// Parse a type specification appearing in +/// the signature of a subroutine definition (`def`) statement. +pub(crate) fn param_type_spec(p: &mut Parser<'_>) -> bool { + if p.at(T![array]) || p.at(T![mutable]) || p.at(T![readonly]) { + let want_array_ref_type = true; + return array_type_spec(p, want_array_ref_type); + } + non_array_type_spec(p) +} + +/// Parse a type specification in contexts other than the +/// the signature of a subroutine definition (`def`) statement. pub(crate) fn type_spec(p: &mut Parser<'_>) -> bool { if p.at(T![array]) { - return array_type_spec(p); + let want_array_ref_type = false; + return array_type_spec(p, want_array_ref_type); } non_array_type_spec(p) } // Parse an array type spec -pub(crate) fn array_type_spec(p: &mut Parser<'_>) -> bool { - assert!(p.at(T![array])); +pub(crate) fn array_type_spec(p: &mut Parser<'_>, want_array_ref_type: bool) -> bool { let m = p.start(); + if want_array_ref_type { + if p.at(T![array]) { + p.error("Expecting modifier `mutable` or `immutable`"); + } else { + p.eat(T![mutable]); + p.eat(T![readonly]); + } + } else { + assert!(p.at(T![array])); + } p.bump_any(); p.expect(T!['[']); if !matches!( @@ -445,6 +467,9 @@ pub(crate) fn array_type_spec(p: &mut Parser<'_>) -> bool { p.expect(COMMA); // Parse the dimensions. if p.at(T![dim]) { + if !want_array_ref_type { + p.error("Unexpected dim expression outside of subroutine declaration"); + } let m = p.start(); p.bump_any(); if p.eat(T![=]) { diff --git a/crates/oq3_parser/src/grammar/params.rs b/crates/oq3_parser/src/grammar/params.rs index 976603b..d6c9115 100644 --- a/crates/oq3_parser/src/grammar/params.rs +++ b/crates/oq3_parser/src/grammar/params.rs @@ -27,6 +27,7 @@ pub(super) fn arg_list_gate_call_qubits(p: &mut Parser<'_>) { _param_list_openqasm(p, DefFlavor::GateCallQubits); } +// Used only in subroutine (`def`) definitions. pub(super) fn param_list_def_params(p: &mut Parser<'_>) { // Subroutine definition parameter list: (t0 p0, t1 p1, ...) // - parens: yes @@ -149,9 +150,11 @@ fn _param_list_openqasm(p: &mut Parser<'_>, flavor: DefFlavor) { let m = p.start(); let inner_array_literal = p.at(T!['{']); + // Allowed starts for an item: either a type or a first-token of a param/expression, // or first token of array literal. - if !(p.current().is_type() || p.at_ts(PARAM_FIRST) || inner_array_literal) { + if matches!(flavor, DefParams) && (p.at(T![mutable]) || p.at(T![readonly])) { + } else if !(p.current().is_type() || p.at_ts(PARAM_FIRST) || inner_array_literal) { p.error("expected value parameter"); m.abandon(p); break; @@ -165,9 +168,10 @@ fn _param_list_openqasm(p: &mut Parser<'_>, flavor: DefFlavor) { true } GateCallQubits => arg_gate_call_qubit(p, m), - // These two have different requirements but share this entry point. - DefParams | DefCalParams => param_typed(p, m), TypeListFlavor => scalar_type(p, m), + // TODO: possibly fix after clarifying what the spec says + DefCalParams => param_typed(p, m), + DefParams => param_typed(p, m), // Untyped parameters/qubits. GateParams | GateQubits => param_untyped(p, m), DefCalQubits => param_untyped_or_hardware_qubit(p, m), @@ -287,13 +291,24 @@ fn param_untyped_or_hardware_qubit(p: &mut Parser<'_>, m: Marker) -> bool { } } +/// Parse one parameter in the list of parameters in the signature +/// of a subroutine defintion (that is, a `def` statement) fn param_typed(p: &mut Parser<'_>, m: Marker) -> bool { - expressions::type_spec(p); + expressions::param_type_spec(p); expressions::var_name(p); m.complete(p, TYPED_PARAM); true } +// TODO: Get clarification on the spec vis a vis defcal and def, +// then revisit this. +// fn scalar_typed(p: &mut Parser<'_>, m: Marker) -> bool { +// expressions::type_spec(p); +// expressions::var_name(p); +// m.complete(p, TYPED_PARAM); +// true +// } + fn scalar_type(p: &mut Parser<'_>, m: Marker) -> bool { expressions::type_spec(p); m.complete(p, SCALAR_TYPE); diff --git a/crates/oq3_parser/src/syntax_kind/syntax_kind_enum.rs b/crates/oq3_parser/src/syntax_kind/syntax_kind_enum.rs index e939d7e..6696685 100644 --- a/crates/oq3_parser/src/syntax_kind/syntax_kind_enum.rs +++ b/crates/oq3_parser/src/syntax_kind/syntax_kind_enum.rs @@ -184,6 +184,7 @@ pub enum SyntaxKind { LITERAL, NAME, PARAM, + PARAM_TYPE, PARAM_LIST, PREFIX_EXPR, QUBIT_LIST, @@ -197,6 +198,7 @@ pub enum SyntaxKind { ALIAS_DECLARATION_STATEMENT, ARRAY_LITERAL, ARRAY_TYPE, + ARRAY_REF_TYPE, ASSIGNMENT_STMT, CLASSICAL_DECLARATION_STATEMENT, DESIGNATOR, diff --git a/crates/oq3_semantics/src/syntax_to_semantics.rs b/crates/oq3_semantics/src/syntax_to_semantics.rs index 3f946d5..e4d0370 100644 --- a/crates/oq3_semantics/src/syntax_to_semantics.rs +++ b/crates/oq3_semantics/src/syntax_to_semantics.rs @@ -1126,6 +1126,19 @@ fn block_or_stmt_to_asg_type(val: oq3_syntax::BlockOrStmt, context: &mut Context } // Convert AST scalar type to a `types::Type` +fn param_type_to_type( + param_type: &synast::ParamType, + // scalar_type: &synast::ScalarType, + isconst: bool, + context: &mut Context, +) -> Type { + let scalar_type = match param_type { + synast::ParamType::ScalarType(scalar_type) => scalar_type, + synast::ParamType::ArrayRefType(_) => return Type::ToDo, + }; + scalar_type_to_type(scalar_type, isconst, context) +} + fn scalar_type_to_type( scalar_type: &synast::ScalarType, isconst: bool, @@ -1140,6 +1153,7 @@ fn scalar_type_to_type( // Eg, we write `int[32]`, but we don't write `complex[32]`, but rather `complex[float[32]]`. // However `Type::Complex` has exactly the same form as other scalar types. In this case // `width` is understood to be the width of each of real and imaginary components. + let designator = if let Some(float_type) = scalar_type.scalar_type() { // complex float_type.designator() @@ -1457,7 +1471,7 @@ fn bind_typed_parameter_list( param_list .typed_params() .map(|param| { - let typ = scalar_type_to_type(¶m.scalar_type().unwrap(), false, context); + let typ = param_type_to_type(¶m.param_type().unwrap(), false, context); let namestr = param.name().unwrap().string(); context.new_binding(namestr.as_ref(), &typ, ¶m) }) diff --git a/crates/oq3_syntax/build.rs b/crates/oq3_syntax/build.rs index d40f6d8..7642382 100644 --- a/crates/oq3_syntax/build.rs +++ b/crates/oq3_syntax/build.rs @@ -439,6 +439,7 @@ mod sourcegen { "LITERAL", "NAME", "PARAM", + "PARAM_TYPE", "PARAM_LIST", "PREFIX_EXPR", "QUBIT_LIST", @@ -453,6 +454,7 @@ mod sourcegen { "ALIAS_DECLARATION_STATEMENT", "ARRAY_LITERAL", "ARRAY_TYPE", + "ARRAY_REF_TYPE", "ASSIGNMENT_STMT", "CLASSICAL_DECLARATION_STATEMENT", "DESIGNATOR", diff --git a/crates/oq3_syntax/openqasm3.ungram b/crates/oq3_syntax/openqasm3.ungram index a8ba04a..99ceded 100644 --- a/crates/oq3_syntax/openqasm3.ungram +++ b/crates/oq3_syntax/openqasm3.ungram @@ -189,11 +189,16 @@ Gate = ParamList = '(' (Param (',' Param)* ','?)? ')' -TypedParamList = - '(' (TypedParam (',' TypedParam)* ','?)? ')' +ArrayRefType = ('readonly' | 'mutable') 'array' '[' ScalarType ',' (ExpressionList | DimExpr) ']' + +ParamType = + (ScalarType | ArrayRefType) TypedParam = - ScalarType Name + ParamType Name + +TypedParamList = + '(' (TypedParam (',' TypedParam)* ','?)? ')' // For 'extern' TypeList = diff --git a/crates/oq3_syntax/src/ast/generated/nodes.rs b/crates/oq3_syntax/src/ast/generated/nodes.rs index 0856d32..63727c0 100644 --- a/crates/oq3_syntax/src/ast/generated/nodes.rs +++ b/crates/oq3_syntax/src/ast/generated/nodes.rs @@ -675,14 +675,37 @@ pub struct Param { impl ast::HasName for Param {} impl Param {} #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct TypedParam { +pub struct ArrayRefType { pub(crate) syntax: SyntaxNode, } -impl ast::HasName for TypedParam {} -impl TypedParam { +impl ArrayRefType { + pub fn readonly_token(&self) -> Option { + support::token(&self.syntax, T![readonly]) + } + pub fn mutable_token(&self) -> Option { + support::token(&self.syntax, T![mutable]) + } + pub fn array_token(&self) -> Option { + support::token(&self.syntax, T![array]) + } + pub fn l_brack_token(&self) -> Option { + support::token(&self.syntax, T!['[']) + } pub fn scalar_type(&self) -> Option { support::child(&self.syntax) } + pub fn comma_token(&self) -> Option { + support::token(&self.syntax, T![,]) + } + pub fn expression_list(&self) -> Option { + support::child(&self.syntax) + } + pub fn dim_expr(&self) -> Option { + support::child(&self.syntax) + } + pub fn r_brack_token(&self) -> Option { + support::token(&self.syntax, T![']']) + } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct ScalarType { @@ -730,6 +753,16 @@ impl ScalarType { } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct TypedParam { + pub(crate) syntax: SyntaxNode, +} +impl ast::HasName for TypedParam {} +impl TypedParam { + pub fn param_type(&self) -> Option { + support::child(&self.syntax) + } +} +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct ArrayExpr { pub(crate) syntax: SyntaxNode, } @@ -1191,6 +1224,11 @@ pub enum GateOperand { HardwareQubit(HardwareQubit), } #[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum ParamType { + ScalarType(ScalarType), + ArrayRefType(ArrayRefType), +} +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Modifier { InvModifier(InvModifier), PowModifier(PowModifier), @@ -1872,9 +1910,9 @@ impl AstNode for Param { &self.syntax } } -impl AstNode for TypedParam { +impl AstNode for ArrayRefType { fn can_cast(kind: SyntaxKind) -> bool { - kind == TYPED_PARAM + kind == ARRAY_REF_TYPE } fn cast(syntax: SyntaxNode) -> Option { if Self::can_cast(syntax.kind()) { @@ -1902,6 +1940,21 @@ impl AstNode for ScalarType { &self.syntax } } +impl AstNode for TypedParam { + fn can_cast(kind: SyntaxKind) -> bool { + kind == TYPED_PARAM + } + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(Self { syntax }) + } else { + None + } + } + fn syntax(&self) -> &SyntaxNode { + &self.syntax + } +} impl AstNode for ArrayExpr { fn can_cast(kind: SyntaxKind) -> bool { kind == ARRAY_EXPR @@ -2840,6 +2893,35 @@ impl AstNode for GateOperand { } } } +impl From for ParamType { + fn from(node: ScalarType) -> ParamType { + ParamType::ScalarType(node) + } +} +impl From for ParamType { + fn from(node: ArrayRefType) -> ParamType { + ParamType::ArrayRefType(node) + } +} +impl AstNode for ParamType { + fn can_cast(kind: SyntaxKind) -> bool { + matches!(kind, SCALAR_TYPE | ARRAY_REF_TYPE) + } + fn cast(syntax: SyntaxNode) -> Option { + let res = match syntax.kind() { + SCALAR_TYPE => ParamType::ScalarType(ScalarType { syntax }), + ARRAY_REF_TYPE => ParamType::ArrayRefType(ArrayRefType { syntax }), + _ => return None, + }; + Some(res) + } + fn syntax(&self) -> &SyntaxNode { + match self { + ParamType::ScalarType(it) => &it.syntax, + ParamType::ArrayRefType(it) => &it.syntax, + } + } +} impl From for Modifier { fn from(node: InvModifier) -> Modifier { Modifier::InvModifier(node) @@ -2986,6 +3068,11 @@ impl std::fmt::Display for GateOperand { std::fmt::Display::fmt(self.syntax(), f) } } +impl std::fmt::Display for ParamType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} impl std::fmt::Display for Modifier { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) @@ -3216,7 +3303,7 @@ impl std::fmt::Display for Param { std::fmt::Display::fmt(self.syntax(), f) } } -impl std::fmt::Display for TypedParam { +impl std::fmt::Display for ArrayRefType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } @@ -3226,6 +3313,11 @@ impl std::fmt::Display for ScalarType { std::fmt::Display::fmt(self.syntax(), f) } } +impl std::fmt::Display for TypedParam { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} impl std::fmt::Display for ArrayExpr { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) diff --git a/crates/pipeline-tests/tests/snapshots/runner__tests__snippets__reference__subroutine__array.qasm-parse.snap b/crates/pipeline-tests/tests/snapshots/runner__tests__snippets__reference__subroutine__array.qasm-parse.snap index a11eb78..7241822 100644 --- a/crates/pipeline-tests/tests/snapshots/runner__tests__snippets__reference__subroutine__array.qasm-parse.snap +++ b/crates/pipeline-tests/tests/snapshots/runner__tests__snippets__reference__subroutine__array.qasm-parse.snap @@ -3,11 +3,11 @@ source: crates/pipeline-tests/tests/runner.rs expression: parse_snap --- id: tests/snippets/reference/subroutine/array.qasm -expect-parse: Todo +expect-parse: Ok --- parser --- -ok: false +ok: true panicked: false -errors: 40 +errors: 0 --- ast --- SOURCE_FILE@0..374: def test_array_1(mutable array[uint[16], 4, 2] a) {} @@ -16,53 +16,47 @@ def test_array_3(mutable array[uint[16], #dim=2] a) {} def test_array_4(readonly array[uint[16], #dim=2*n] a) {} def test_array_5(readonly array[int[8], #dim=1] a, mutable array[complex[float[64]], #dim=3] b, readonly array[complex[float[64]], 2, 2] c) -> int[8] {} -DEF@1..18: def test_array_1( +DEF@1..53: def test_array_1(mutable array[uint[16], 4, 2] a) {} NAME@5..17: test_array_1 -TYPED_PARAM_LIST@17..18: ( -ERROR@18..25: mutable -ARRAY_TYPE@26..47: array[uint[16], 4, 2] +TYPED_PARAM_LIST@17..50: (mutable array[uint[16], 4, 2] a) +TYPED_PARAM@18..49: mutable array[uint[16], 4, 2] a +ARRAY_TYPE@18..47: mutable array[uint[16], 4, 2] SCALAR_TYPE@32..40: uint[16] DESIGNATOR@36..40: [16] LITERAL@37..39: 16 LITERAL@42..43: 4 LITERAL@45..46: 2 NAME@48..49: a -ERROR@49..50: ) -EXPR_STMT@51..53: {} BLOCK_EXPR@51..53: {} -DEF@54..71: def test_array_2( +DEF@54..107: def test_array_2(readonly array[uint[16], 4, 2] a) {} NAME@58..70: test_array_2 -TYPED_PARAM_LIST@70..71: ( -ERROR@71..79: readonly -ARRAY_TYPE@80..101: array[uint[16], 4, 2] +TYPED_PARAM_LIST@70..104: (readonly array[uint[16], 4, 2] a) +TYPED_PARAM@71..103: readonly array[uint[16], 4, 2] a +ARRAY_TYPE@71..101: readonly array[uint[16], 4, 2] SCALAR_TYPE@86..94: uint[16] DESIGNATOR@90..94: [16] LITERAL@91..93: 16 LITERAL@96..97: 4 LITERAL@99..100: 2 NAME@102..103: a -ERROR@103..104: ) -EXPR_STMT@105..107: {} BLOCK_EXPR@105..107: {} -DEF@108..125: def test_array_3( +DEF@108..162: def test_array_3(mutable array[uint[16], #dim=2] a) {} NAME@112..124: test_array_3 -TYPED_PARAM_LIST@124..125: ( -ERROR@125..132: mutable -ARRAY_TYPE@133..156: array[uint[16], #dim=2] +TYPED_PARAM_LIST@124..159: (mutable array[uint[16], #dim=2] a) +TYPED_PARAM@125..158: mutable array[uint[16], #dim=2] a +ARRAY_TYPE@125..156: mutable array[uint[16], #dim=2] SCALAR_TYPE@139..147: uint[16] DESIGNATOR@143..147: [16] LITERAL@144..146: 16 DIM_EXPR@149..156: #dim=2] LITERAL@154..155: 2 NAME@157..158: a -ERROR@158..159: ) -EXPR_STMT@160..162: {} BLOCK_EXPR@160..162: {} -DEF@163..180: def test_array_4( +DEF@163..220: def test_array_4(readonly array[uint[16], #dim=2*n] a) {} NAME@167..179: test_array_4 -TYPED_PARAM_LIST@179..180: ( -ERROR@180..188: readonly -ARRAY_TYPE@189..214: array[uint[16], #dim=2*n] +TYPED_PARAM_LIST@179..217: (readonly array[uint[16], #dim=2*n] a) +TYPED_PARAM@180..216: readonly array[uint[16], #dim=2*n] a +ARRAY_TYPE@180..214: readonly array[uint[16], #dim=2*n] SCALAR_TYPE@195..203: uint[16] DESIGNATOR@199..203: [16] LITERAL@200..202: 16 @@ -71,23 +65,20 @@ BIN_EXPR@210..213: 2*n LITERAL@210..211: 2 IDENTIFIER@212..213: n NAME@215..216: a -ERROR@216..217: ) -EXPR_STMT@218..220: {} BLOCK_EXPR@218..220: {} -DEF@221..238: def test_array_5( +DEF@221..373: def test_array_5(readonly array[int[8], #dim=1] a, mutable array[complex[float[64]], #dim=3] b, readonly array[complex[float[64]], 2, 2] c) -> int[8] {} NAME@225..237: test_array_5 -TYPED_PARAM_LIST@237..238: ( -ERROR@238..246: readonly -ARRAY_TYPE@247..268: array[int[8], #dim=1] +TYPED_PARAM_LIST@237..360: (readonly array[int[8], #dim=1] a, mutable array[complex[float[64]], #dim=3] b, readonly array[complex[float[64]], 2, 2] c) +TYPED_PARAM@238..270: readonly array[int[8], #dim=1] a +ARRAY_TYPE@238..268: readonly array[int[8], #dim=1] SCALAR_TYPE@253..259: int[8] DESIGNATOR@256..259: [8] LITERAL@257..258: 8 DIM_EXPR@261..268: #dim=1] LITERAL@266..267: 1 NAME@269..270: a -ERROR@270..271: , -ERROR@272..279: mutable -ARRAY_TYPE@280..313: array[complex[float[64]], #dim=3] +TYPED_PARAM@272..315: mutable array[complex[float[64]], #dim=3] b +ARRAY_TYPE@272..313: mutable array[complex[float[64]], #dim=3] SCALAR_TYPE@286..304: complex[float[64]] SCALAR_TYPE@294..303: float[64] DESIGNATOR@299..303: [64] @@ -95,9 +86,8 @@ LITERAL@300..302: 64 DIM_EXPR@306..313: #dim=3] LITERAL@311..312: 3 NAME@314..315: b -ERROR@315..316: , -ERROR@317..325: readonly -ARRAY_TYPE@326..357: array[complex[float[64]], 2, 2] +TYPED_PARAM@317..359: readonly array[complex[float[64]], 2, 2] c +ARRAY_TYPE@317..357: readonly array[complex[float[64]], 2, 2] SCALAR_TYPE@332..350: complex[float[64]] SCALAR_TYPE@340..349: float[64] DESIGNATOR@345..349: [64] @@ -105,13 +95,8 @@ LITERAL@346..348: 64 LITERAL@352..353: 2 LITERAL@355..356: 2 NAME@358..359: c -ERROR@359..360: ) -EXPR_STMT@361..363: -> -PREFIX_EXPR@361..363: -> -ERROR@362..363: > +RETURN_SIGNATURE@361..370: -> int[8] SCALAR_TYPE@364..370: int[8] DESIGNATOR@367..370: [8] LITERAL@368..369: 8 -NAME@371..371: -EXPR_STMT@371..373: {} BLOCK_EXPR@371..373: {} diff --git a/crates/pipeline-tests/tests/snippets/reference/subroutine/array.qasm b/crates/pipeline-tests/tests/snippets/reference/subroutine/array.qasm index b84e1c5..bbd568a 100644 --- a/crates/pipeline-tests/tests/snippets/reference/subroutine/array.qasm +++ b/crates/pipeline-tests/tests/snippets/reference/subroutine/array.qasm @@ -1,5 +1,5 @@ // lex: ok -// parse: todo +// parse: ok // sema: skip def test_array_1(mutable array[uint[16], 4, 2] a) {}