From 32e84de0e6c3baeb87bd86824e77dce5a1b39444 Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Thu, 18 Jan 2024 19:53:30 -0500 Subject: [PATCH] Implement parsing `measure` from text through ASG (#42) * Partially implement parsing measure from text through ASG Statements like `c = measure q` are analyzed correctly. But `c[0] = measure q` is not analyzed correctly. The code that builds the assignment statement assumes the LHS is an identifier. The ASG should be able to handle valid lvalues. But we cannot yet construct statements with them. * Fix some minor errors found in code review * Factor code into from_gate_operand This is done twice and is several lines, so it is a function now. * Fix test in from_string_test This is unrelated to measurement. * Parse argument to `measure` as gate operand `GateOperand` is a qubit argument to gate call. And arguments to `measure` have the same spec. Identifier, hardware identifier, or indexed identifier. So we reuse the code from gate call. The code in oq3_parser is a bit mysterious. I added some comments to make it easier to locate next time, I hope. * Add test for measuring hardware qubit --- .../src/grammar/expressions/atom.rs | 6 +- crates/oq3_parser/src/grammar/items.rs | 9 +- crates/oq3_parser/src/grammar/params.rs | 3 +- crates/oq3_semantics/src/asg.rs | 29 ++++- .../oq3_semantics/src/syntax_to_semantics.rs | 46 +++++--- crates/oq3_semantics/src/types.rs | 11 +- .../oq3_semantics/tests/from_string_tests.rs | 109 +++++++++++++----- 7 files changed, 154 insertions(+), 59 deletions(-) diff --git a/crates/oq3_parser/src/grammar/expressions/atom.rs b/crates/oq3_parser/src/grammar/expressions/atom.rs index cf2b604..2052631 100644 --- a/crates/oq3_parser/src/grammar/expressions/atom.rs +++ b/crates/oq3_parser/src/grammar/expressions/atom.rs @@ -103,12 +103,12 @@ fn measure_expression(p: &mut Parser<'_>) -> CompletedMarker { p.bump(T![measure]); match p.current() { IDENT | HARDWAREIDENT => { - items::ident_or_index_expr(p); + let m1 = p.start(); + // Parses elements that can be cast to GateOperand + params::arg_gate_call_qubit(p, m1); } _ => { p.error("expecting qubit(s) to measure"); - // m.abandon(p); - // return; } } m.complete(p, MEASURE_EXPRESSION) diff --git a/crates/oq3_parser/src/grammar/items.rs b/crates/oq3_parser/src/grammar/items.rs index 718b0bc..42cbfa6 100644 --- a/crates/oq3_parser/src/grammar/items.rs +++ b/crates/oq3_parser/src/grammar/items.rs @@ -233,11 +233,13 @@ pub(crate) fn ident_or_index_expr(p: &mut Parser<'_>) { p.bump(IDENT); match p.current() { T!['['] => { - let newm = m.complete(p, IDENT); + let newm = m.complete(p, IDENTIFIER); expressions::index_expr(p, newm); } _ => { - m.complete(p, IDENT); + // FIXME: m.complete(p, IDENT) is valid, but it should not be + // it is a source of bugs! + m.complete(p, IDENTIFIER); } } } @@ -270,7 +272,8 @@ pub(crate) fn measure_(p: &mut Parser<'_>, m: Marker) { p.bump(T![measure]); match p.current() { IDENT | HARDWAREIDENT => { - ident_or_index_expr(p); + let m1 = p.start(); + params::arg_gate_call_qubit(p, m1); } _ => { p.error("expecting qubit(s) to measure"); diff --git a/crates/oq3_parser/src/grammar/params.rs b/crates/oq3_parser/src/grammar/params.rs index 1a11d33..7c995c0 100644 --- a/crates/oq3_parser/src/grammar/params.rs +++ b/crates/oq3_parser/src/grammar/params.rs @@ -234,7 +234,8 @@ fn param_typed(p: &mut Parser<'_>, m: Marker) -> bool { success } -fn arg_gate_call_qubit(p: &mut Parser<'_>, m: Marker) -> bool { +// These can be cast to GateOperand +pub(crate) fn arg_gate_call_qubit(p: &mut Parser<'_>, m: Marker) -> bool { if p.at(HARDWAREIDENT) { p.bump(HARDWAREIDENT); m.complete(p, HARDWARE_QUBIT); diff --git a/crates/oq3_semantics/src/asg.rs b/crates/oq3_semantics/src/asg.rs index 9363154..d69ea14 100644 --- a/crates/oq3_semantics/src/asg.rs +++ b/crates/oq3_semantics/src/asg.rs @@ -129,9 +129,9 @@ pub enum Expr { // But we need to handle these in a consistent way. Is there any situation where the "type" of Range is meaningful or useful? // For example, in syntax_to_semantics, have a routine that handles out-of-tree expressions. Range(Range), - Call, // stub function (def) call - Set, // stub - Measure, // stub + Call, // stub function (def) call + Set, // stub + MeasureExpression(MeasureExpression), } /// Typed expression implemented by tagging an `Expr` with a `Type`. @@ -562,6 +562,29 @@ impl GateDeclaration { } } +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct MeasureExpression { + operand: Box, +} + +impl MeasureExpression { + pub fn new(operand: TExpr) -> MeasureExpression { + MeasureExpression { + operand: Box::new(operand), + } + } + + pub fn operand(&self) -> &TExpr { + &self.operand + } + + // FIXME: type may not be correct here. + // This assumes a single qubit is measured. + pub fn to_texpr(self) -> TExpr { + TExpr::new(Expr::MeasureExpression(self), Type::Bit(IsConst::False)) + } +} + #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct GateCall { name: SymbolIdResult, diff --git a/crates/oq3_semantics/src/syntax_to_semantics.rs b/crates/oq3_semantics/src/syntax_to_semantics.rs index ca91531..2043f4a 100644 --- a/crates/oq3_semantics/src/syntax_to_semantics.rs +++ b/crates/oq3_semantics/src/syntax_to_semantics.rs @@ -228,7 +228,13 @@ fn from_expr(expr: synast::Expr, context: &mut Context) -> Option { Some(indexed_identifier.to_texpr()) } - synast::Expr::MeasureExpression(_measure_expr) => None, + synast::Expr::MeasureExpression(ref measure_expr) => { + let gate_operand = measure_expr.gate_operand().unwrap(); // FIXME: check this + let gate_operand_asg = from_gate_operand(gate_operand, context); + Some(asg::MeasureExpression::new(gate_operand_asg).to_texpr()) + } + + // Everything else is not yet implemented _ => { println!("Expression not supported {:?}", expr); None @@ -236,6 +242,22 @@ fn from_expr(expr: synast::Expr, context: &mut Context) -> Option { } } +fn from_gate_operand(gate_operand: synast::GateOperand, context: &mut Context) -> asg::TExpr { + match gate_operand { + synast::GateOperand::HardwareQubit(ref hwq) => { + asg::GateOperand::HardwareQubit(ast_hardware_qubit(hwq)).to_texpr(Type::HardwareQubit) + } + synast::GateOperand::Identifier(identifier) => { + let (astidentifier, typ) = ast_identifier(&identifier, context); + asg::GateOperand::Identifier(astidentifier).to_texpr(typ) + } + synast::GateOperand::IndexedIdentifier(indexed_identifier) => { + let (indexed_identifier, typ) = ast_indexed_identifier(&indexed_identifier, context); + asg::GateOperand::IndexedIdentifier(indexed_identifier).to_texpr(typ) + } + } +} + fn from_index_operator( index_op: synast::IndexOperator, context: &mut Context, @@ -437,21 +459,7 @@ fn from_item(item: synast::Item, context: &mut Context) -> Option { .qubit_list() .unwrap() .gate_operands() - .map(|qubit| match qubit { - synast::GateOperand::HardwareQubit(ref hwq) => { - asg::GateOperand::HardwareQubit(ast_hardware_qubit(hwq)) - .to_texpr(Type::HardwareQubit) - } - synast::GateOperand::Identifier(identifier) => { - let (astidentifier, typ) = ast_identifier(&identifier, context); - asg::GateOperand::Identifier(astidentifier).to_texpr(typ) - } - synast::GateOperand::IndexedIdentifier(indexed_identifier) => { - let (indexed_identifier, typ) = - ast_indexed_identifier(&indexed_identifier, context); - asg::GateOperand::IndexedIdentifier(indexed_identifier).to_texpr(typ) - } - }) + .map(|qubit| from_gate_operand(qubit, context)) .collect(); let param_list = gate_call @@ -571,7 +579,7 @@ fn from_assignment_stmt( assignment_stmt: &synast::AssignmentStmt, context: &mut Context, ) -> Option { - let nameb = assignment_stmt.name(); + let nameb = assignment_stmt.name(); // LHS of assignment let name = nameb.as_ref().unwrap(); let name_str = name.string(); let expr = from_expr(assignment_stmt.expr().unwrap(), context); // rhs of `=` operator @@ -579,11 +587,11 @@ fn from_assignment_stmt( let (symbol_id, typ) = context.lookup_symbol(name_str.as_str(), name).as_tuple(); let is_mutating_const = symbol_id.is_ok() && typ.is_const(); let lvalue = asg::LValue::Identifier(symbol_id); - let ret_stmt = Some(asg::Assignment::new(lvalue, expr.unwrap()).to_stmt()); + let stmt_asg = Some(asg::Assignment::new(lvalue, expr.unwrap()).to_stmt()); if is_mutating_const { context.insert_error(MutateConstError, assignment_stmt); } - ret_stmt + stmt_asg } // diff --git a/crates/oq3_semantics/src/types.rs b/crates/oq3_semantics/src/types.rs index d996fa8..e926270 100644 --- a/crates/oq3_semantics/src/types.rs +++ b/crates/oq3_semantics/src/types.rs @@ -155,7 +155,16 @@ impl Type { pub fn is_const(&self) -> bool { use Type::*; match self { - Bit(c) | Int(_, c) => matches!(*c, IsConst::True), + Bit(c) + | Int(_, c) + | UInt(_, c) + | Float(_, c) + | Angle(_, c) + | Complex(_, c) + | Bool(c) + | Duration(c) + | Stretch(c) + | BitArray(_, c) => matches!(*c, IsConst::True), _ => true, } } diff --git a/crates/oq3_semantics/tests/from_string_tests.rs b/crates/oq3_semantics/tests/from_string_tests.rs index 9bc3a03..f07e8b7 100644 --- a/crates/oq3_semantics/tests/from_string_tests.rs +++ b/crates/oq3_semantics/tests/from_string_tests.rs @@ -5,7 +5,7 @@ use oq3_semantics::asg; use oq3_semantics::semantic_error::SemanticErrorList; use oq3_semantics::symbols::{SymbolTable, SymbolType}; use oq3_semantics::syntax_to_semantics::parse_source_string; -use oq3_semantics::types::{IsConst, Type}; +use oq3_semantics::types::{ArrayDims, IsConst, Type}; fn parse_string(code: &str) -> (asg::Program, SemanticErrorList, SymbolTable) { parse_source_string(code, None).take_context().as_tuple() @@ -166,31 +166,82 @@ bit[4] b = "1001"; assert_eq!(program.len(), 1); } -// #[test] -// fn test_include() { -// let code = r##" -// include "somefile.qasm"; -// "##; -// let (program, errors, symbol_table) = parse_string(code); -// assert_eq!(errors.len(), 0); -// assert_eq!(program.len(), 1); -// } - -// #[test] -// fn test_from_string_qubit_register_decl() { -// let code = r##" -// qubit[3] q; -// "##; -// let (program, errors, symbol_table) = parse_string(code); -// assert!(errors.is_empty()); -// assert_eq!(program.len(), 1); -// let stmt = program.first(); -// assert!(matches!(stmt, Some(asg::Stmt::DeclareQuantum(_)))); -// let qdecl = match stmt { -// Some(asg::Stmt::DeclareQuantum(qdecl)) => qdecl, -// _ => unreachable!(), -// }; -// let varname_id = qdecl.name().clone().unwrap(); -// assert_eq!(varname_id, symbol_table.lookup("q").unwrap().symbol_id()); -// assert_eq!(&Type::Qubit(Some(3)), (symbol_table[varname_id]).symbol_type()); -// } +#[test] +fn test_from_string_qubit_register_decl() { + let code = r#" +qubit[3] q; +"#; + let (program, errors, symbol_table) = parse_string(code); + assert!(errors.is_empty()); + assert_eq!(program.len(), 1); + let stmt = program.first(); + assert!(matches!(stmt, Some(asg::Stmt::DeclareQuantum(_)))); + let qdecl = match stmt { + Some(asg::Stmt::DeclareQuantum(qdecl)) => qdecl, + _ => unreachable!(), + }; + let varname_id = qdecl.name().clone().unwrap(); + assert_eq!(varname_id, symbol_table.lookup("q").unwrap().symbol_id()); + assert_eq!( + &Type::QubitArray(ArrayDims::D1(3)), + symbol_table[&varname_id].symbol_type() + ); +} + +#[test] +fn test_from_string_measure() { + let code = r#" +qubit q; +measure q; +"#; + let (program, errors, _symbol_table) = parse_string(code); + assert!(errors.is_empty()); + assert_eq!(program.len(), 2); +} + +// Issue #42 +#[test] +fn test_from_string_measure_assign() { + let code = r#" +qubit q; +bit c; +c = measure q; +"#; + let (program, errors, _symbol_table) = parse_string(code); + assert!(errors.is_empty()); + assert_eq!(program.len(), 3); +} + +#[test] +fn test_from_string_measure_indexed() { + let code = r#" +qubit[2] q; +measure q[1]; +"#; + let (program, errors, _symbol_table) = parse_string(code); + assert!(errors.is_empty()); + assert_eq!(program.len(), 2); +} + +#[test] +fn test_from_string_measure_hardware() { + let code = r#" +measure $0; +measure $42; +"#; + let (program, errors, _symbol_table) = parse_string(code); + assert!(errors.is_empty()); + assert_eq!(program.len(), 2); +} + +#[test] +fn test_from_string_gate_call_indexed() { + let code = r#" +gate h q {} +qubit[2] q; +h q[1]; +"#; + let (program, errors, _symbol_table) = parse_string(code); + assert!(errors.is_empty()); + assert_eq!(program.len(), 3); +}