Skip to content

Commit

Permalink
Implement parsing measure from text through ASG (#42)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
jlapeyre authored Jan 19, 2024
1 parent 6daea46 commit 32e84de
Show file tree
Hide file tree
Showing 7 changed files with 154 additions and 59 deletions.
6 changes: 3 additions & 3 deletions crates/oq3_parser/src/grammar/expressions/atom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
9 changes: 6 additions & 3 deletions crates/oq3_parser/src/grammar/items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
Expand Down Expand Up @@ -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");
Expand Down
3 changes: 2 additions & 1 deletion crates/oq3_parser/src/grammar/params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
29 changes: 26 additions & 3 deletions crates/oq3_semantics/src/asg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down Expand Up @@ -562,6 +562,29 @@ impl GateDeclaration {
}
}

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct MeasureExpression {
operand: Box<TExpr>,
}

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,
Expand Down
46 changes: 27 additions & 19 deletions crates/oq3_semantics/src/syntax_to_semantics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,14 +228,36 @@ fn from_expr(expr: synast::Expr, context: &mut Context) -> Option<asg::TExpr> {
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
}
}
}

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,
Expand Down Expand Up @@ -437,21 +459,7 @@ fn from_item(item: synast::Item, context: &mut Context) -> Option<asg::Stmt> {
.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
Expand Down Expand Up @@ -571,19 +579,19 @@ fn from_assignment_stmt(
assignment_stmt: &synast::AssignmentStmt,
context: &mut Context,
) -> Option<asg::Stmt> {
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

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
}

//
Expand Down
11 changes: 10 additions & 1 deletion crates/oq3_semantics/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}
Expand Down
109 changes: 80 additions & 29 deletions crates/oq3_semantics/tests/from_string_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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);
}

0 comments on commit 32e84de

Please sign in to comment.