Skip to content

Commit f1a7b8f

Browse files
orpuente-MSidavis
andauthored
Add support for array concatenation in OpenQASM (#2676)
This PR adds support for [array concatenation in OpenQASM](https://openqasm.com/versions/3.0/language/types.html#array-concatenation-and-slicing). ### Notes Array concatenation does not appear in the Grammar and is underspecified in the Spec. This implementation is conservative in two senses while we wait for Spec clarification: 1. We don't allow concatenating arrays of different base types, even if they differ only in width. 2. We don't allow mixing together arrays, static array references, and dynamic array references. --------- Co-authored-by: Ian Davis <[email protected]>
1 parent 751d930 commit f1a7b8f

File tree

23 files changed

+2642
-44
lines changed

23 files changed

+2642
-44
lines changed

source/compiler/qsc_qasm/src/compiler.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -620,6 +620,34 @@ impl QasmCompiler {
620620
Some(decl)
621621
}
622622

623+
fn compile_concat_expr(&mut self, expr: &semast::ConcatExpr) -> qsast::Expr {
624+
let exprs = expr
625+
.operands
626+
.iter()
627+
.map(|expr| self.compile_expr(expr))
628+
.collect::<Vec<_>>();
629+
630+
assert!(
631+
exprs.len() >= 2,
632+
"the parser guarantees that a concat expression has at least two operands"
633+
);
634+
635+
let mut expr_iter = exprs.into_iter();
636+
let mut expr = expr_iter
637+
.next()
638+
.expect("concat exprs must have at least one expression");
639+
640+
for rhs in expr_iter {
641+
let span = Span {
642+
lo: expr.span.lo,
643+
hi: rhs.span.hi,
644+
};
645+
expr = build_binary_expr(false, qsast::BinOp::Add, expr, rhs, span);
646+
}
647+
648+
expr
649+
}
650+
623651
fn compile_assign_stmt(&mut self, stmt: &semast::AssignStmt) -> Option<qsast::Stmt> {
624652
let lhs = self.compile_expr(&stmt.lhs);
625653
let rhs = self.compile_expr(&stmt.rhs);
@@ -1506,6 +1534,7 @@ impl QasmCompiler {
15061534
semast::ExprKind::Paren(pexpr) => self.compile_paren_expr(pexpr, expr.span),
15071535
semast::ExprKind::Measure(mexpr) => self.compile_measure_expr(mexpr, &expr.ty),
15081536
semast::ExprKind::SizeofCall(sizeof_call) => self.compile_sizeof_call_expr(sizeof_call),
1537+
semast::ExprKind::Concat(concat) => self.compile_concat_expr(concat),
15091538
semast::ExprKind::DurationofCall(duration_call) => {
15101539
self.compile_durationof_call_expr(duration_call)
15111540
}

source/compiler/qsc_qasm/src/parser/ast.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,23 @@ impl Display for MeasureExpr {
241241
}
242242
}
243243

244+
/// This expression is not part of the expression tree
245+
/// and is only used as rhs of alias, classical declaration,
246+
/// and assignment statements.
247+
/// Grammar: `expression (DOUBLE_PLUS expression)*`.
248+
#[derive(Clone, Debug)]
249+
pub struct ConcatExpr {
250+
pub span: Span,
251+
pub operands: List<Expr>,
252+
}
253+
254+
impl Display for ConcatExpr {
255+
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
256+
writeln_header(f, "ConcatExpr", self.span)?;
257+
write_list_field(f, "operands", &self.operands)
258+
}
259+
}
260+
244261
/// A binary operator.
245262
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
246263
pub enum BinOp {
@@ -1400,15 +1417,19 @@ impl Display for ClassicalDeclarationStmt {
14001417
}
14011418
}
14021419

1420+
/// A special kind of Expr that allows measurement and concatenation expressions.
1421+
/// It is used as the rhs of alias, classical declaration, and assign statements.
14031422
#[derive(Clone, Debug)]
14041423
pub enum ValueExpr {
1424+
Concat(ConcatExpr),
14051425
Expr(Expr),
14061426
Measurement(MeasureExpr),
14071427
}
14081428

14091429
impl Display for ValueExpr {
14101430
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
14111431
match self {
1432+
Self::Concat(expr) => write!(f, "{expr}"),
14121433
Self::Expr(expr) => write!(f, "{expr}"),
14131434
Self::Measurement(measure) => write!(f, "{measure}"),
14141435
}

source/compiler/qsc_qasm/src/parser/expr.rs

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ use crate::{
1717
ClosedBinOp, Delim, Radix, Token, TokenKind,
1818
cooked::{ComparisonOp, Literal, TimingLiteralKind},
1919
},
20-
parser::{ast::DurationofCall, stmt::parse_block},
20+
parser::{
21+
ast::{ConcatExpr, DurationofCall, List},
22+
stmt::parse_block,
23+
},
2124
};
2225

2326
use crate::parser::Result;
@@ -26,7 +29,7 @@ use super::{
2629
ast::{
2730
BinOp, BinaryOpExpr, Cast, Expr, ExprKind, FunctionCall, GateOperand, GateOperandKind,
2831
HardwareQubit, Ident, IdentOrIndexedIdent, Index, IndexExpr, IndexList, IndexListItem,
29-
IndexedIdent, List, Lit, LiteralKind, MeasureExpr, Range, Set, TimeUnit, TypeDef, UnaryOp,
32+
IndexedIdent, Lit, LiteralKind, MeasureExpr, Range, Set, TimeUnit, TypeDef, UnaryOp,
3033
UnaryOpExpr, ValueExpr, Version, list_from_iter,
3134
},
3235
completion::word_kinds::WordKinds,
@@ -700,18 +703,42 @@ fn lit_array_element(s: &mut ParserContext) -> Result<Expr> {
700703

701704
/// These are expressions allowed in classical declarations.
702705
/// Grammar: `arrayLiteral | expression | measureExpression`.
706+
///
707+
/// The Grammar and this comment don't match, since there is a bug in
708+
/// the Grammar. Here is a link to the issue:
709+
/// <https://github.com/openqasm/openqasm/issues/620>
703710
pub(super) fn declaration_expr(s: &mut ParserContext) -> Result<ValueExpr> {
711+
let lo = s.peek().span.lo;
712+
713+
// 1. Try to parse a measurement expression.
704714
if let Some(measurement) = opt(s, measure_expr)? {
705715
return Ok(ValueExpr::Measurement(measurement));
706716
}
707717

708-
let expr = if let Some(expr) = opt(s, expr)? {
709-
expr
710-
} else {
711-
lit_array(s)?
712-
};
718+
// Try to parse an expression or a concatenation.
719+
if let Some(e) = opt(s, expr)? {
720+
let mut exprs = Vec::new();
721+
exprs.push(e);
713722

714-
Ok(ValueExpr::Expr(expr))
723+
// Try to parse many expressions separated by the `++` operator.
724+
while opt(s, |s| token(s, TokenKind::PlusPlus))?.is_some() {
725+
exprs.push(expr(s)?);
726+
}
727+
728+
// If we parsed a single expression, this is just an `Expr`.
729+
if exprs.len() == 1 {
730+
return Ok(ValueExpr::Expr(
731+
exprs.into_iter().next().expect("there is one element"),
732+
));
733+
}
734+
735+
// If we parsed more than one expression, this is a concatenation.
736+
let span = s.span(lo);
737+
let operands = list_from_iter(exprs);
738+
return Ok(ValueExpr::Concat(ConcatExpr { span, operands }));
739+
}
740+
741+
Ok(ValueExpr::Expr(lit_array(s)?))
715742
}
716743

717744
/// These are expressions allowed in constant classical declarations.
@@ -728,16 +755,6 @@ pub(super) fn const_declaration_expr(s: &mut ParserContext) -> Result<ValueExpr>
728755
Ok(ValueExpr::Expr(expr))
729756
}
730757

731-
/// These are expressions allowed in `Assign`, `AssignOp`, and return stmts.
732-
/// Grammar: `expression | measureExpression`.
733-
pub(super) fn expr_or_measurement(s: &mut ParserContext) -> Result<ValueExpr> {
734-
if let Some(measurement) = opt(s, measure_expr)? {
735-
return Ok(ValueExpr::Measurement(measurement));
736-
}
737-
738-
Ok(ValueExpr::Expr(expr(s)?))
739-
}
740-
741758
pub(crate) fn expr_list(s: &mut ParserContext) -> Result<Vec<Expr>> {
742759
seq(s, expr).map(|pair| pair.0)
743760
}

source/compiler/qsc_qasm/src/parser/expr/tests.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the MIT License.
33

44
use super::expr;
5+
use crate::parser::stmt::parse as stmt;
56
use crate::{
67
parser::ast::StmtKind,
78
parser::{scan::ParserContext, stmt, tests::check},
@@ -1211,3 +1212,31 @@ fn duration_of() {
12111212
kind: HardwareQubit [21-23]: 0"#]],
12121213
);
12131214
}
1215+
1216+
#[test]
1217+
fn array_concatenation_is_not_part_of_the_expr_tree() {
1218+
let source = "a ++ b;";
1219+
1220+
check(
1221+
stmt,
1222+
source,
1223+
&expect![[r#"
1224+
Stmt [0-1]:
1225+
annotations: <empty>
1226+
kind: ExprStmt [0-1]:
1227+
expr: Expr [0-1]: Ident [0-1] "a"
1228+
1229+
[
1230+
Error(
1231+
Token(
1232+
Semicolon,
1233+
PlusPlus,
1234+
Span {
1235+
lo: 2,
1236+
hi: 4,
1237+
},
1238+
),
1239+
),
1240+
]"#]],
1241+
);
1242+
}

source/compiler/qsc_qasm/src/parser/mut_visit.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
use qsc_data_structures::span::Span;
55

6-
use crate::parser::ast::{DefParameter, DefParameterType, DurationofCall, QubitType};
6+
use crate::parser::ast::{ConcatExpr, DefParameter, DefParameterType, DurationofCall, QubitType};
77

88
use super::ast::{
99
AccessControl, AliasDeclStmt, Annotation, ArrayBaseTypeKind, ArrayReferenceType, ArrayType,
@@ -203,6 +203,10 @@ pub trait MutVisitor: Sized {
203203
walk_measure_expr(self, expr);
204204
}
205205

206+
fn visit_concat_expr(&mut self, expr: &mut ConcatExpr) {
207+
walk_concat_expr(self, expr);
208+
}
209+
206210
fn visit_ident_or_indexed_ident(&mut self, ident: &mut IdentOrIndexedIdent) {
207211
walk_ident_or_indexed_ident(self, ident);
208212
}
@@ -659,6 +663,7 @@ pub fn walk_index_expr(vis: &mut impl MutVisitor, expr: &mut IndexExpr) {
659663

660664
pub fn walk_value_expr(vis: &mut impl MutVisitor, expr: &mut ValueExpr) {
661665
match &mut *expr {
666+
ValueExpr::Concat(expr) => vis.visit_concat_expr(expr),
662667
ValueExpr::Expr(expr) => vis.visit_expr(expr),
663668
ValueExpr::Measurement(measure_expr) => vis.visit_measure_expr(measure_expr),
664669
}
@@ -670,6 +675,13 @@ pub fn walk_measure_expr(vis: &mut impl MutVisitor, expr: &mut MeasureExpr) {
670675
vis.visit_gate_operand(&mut expr.operand);
671676
}
672677

678+
pub fn walk_concat_expr(vis: &mut impl MutVisitor, expr: &mut ConcatExpr) {
679+
vis.visit_span(&mut expr.span);
680+
expr.operands
681+
.iter_mut()
682+
.for_each(|expr| vis.visit_expr(expr));
683+
}
684+
673685
pub fn walk_ident_or_indexed_ident(vis: &mut impl MutVisitor, ident: &mut IdentOrIndexedIdent) {
674686
match ident {
675687
IdentOrIndexedIdent::Ident(ident) => vis.visit_ident(ident),

source/compiler/qsc_qasm/src/parser/stmt.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ fn disambiguate_ident(
221221
let lo = ident_or_indexed_ident.span().lo;
222222
if s.peek().kind == TokenKind::Eq {
223223
s.advance();
224-
let expr = expr::expr_or_measurement(s)?;
224+
let expr = expr::declaration_expr(s)?;
225225
recovering_semi(s);
226226
Ok(StmtKind::Assign(AssignStmt {
227227
span: s.span(lo),
@@ -231,7 +231,7 @@ fn disambiguate_ident(
231231
} else if let TokenKind::BinOpEq(op) = s.peek().kind {
232232
s.advance();
233233
let op = expr::closed_bin_op(op);
234-
let expr = expr::expr_or_measurement(s)?;
234+
let expr = expr::declaration_expr(s)?;
235235
recovering_semi(s);
236236
Ok(StmtKind::AssignOp(AssignOpStmt {
237237
span: s.span(lo),
@@ -805,7 +805,7 @@ fn gate_params(s: &mut ParserContext<'_>) -> Result<Vec<SeqItem<Ident>>> {
805805
fn parse_return(s: &mut ParserContext) -> Result<StmtKind> {
806806
let lo = s.peek().span.lo;
807807
token(s, TokenKind::Keyword(crate::keyword::Keyword::Return))?;
808-
let expr = opt(s, expr::expr_or_measurement)?.map(Box::new);
808+
let expr = opt(s, expr::declaration_expr)?.map(Box::new);
809809
recovering_semi(s);
810810
Ok(StmtKind::Return(ReturnStmt {
811811
span: s.span(lo),

source/compiler/qsc_qasm/src/semantic/ast.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -855,6 +855,7 @@ pub enum ExprKind {
855855
Measure(MeasureExpr),
856856
SizeofCall(SizeofCallExpr),
857857
DurationofCall(DurationofCallExpr),
858+
Concat(ConcatExpr),
858859
}
859860

860861
impl Display for ExprKind {
@@ -874,6 +875,7 @@ impl Display for ExprKind {
874875
ExprKind::Measure(expr) => write!(f, "{expr}"),
875876
ExprKind::SizeofCall(call) => write!(f, "{call}"),
876877
ExprKind::DurationofCall(call) => write!(f, "{call}"),
878+
ExprKind::Concat(expr) => write!(f, "{expr}"),
877879
}
878880
}
879881
}
@@ -1205,6 +1207,19 @@ impl Display for Cast {
12051207
}
12061208
}
12071209

1210+
#[derive(Clone, Debug)]
1211+
pub struct ConcatExpr {
1212+
pub span: Span,
1213+
pub operands: List<Expr>,
1214+
}
1215+
1216+
impl Display for ConcatExpr {
1217+
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1218+
writeln_header(f, "ConcatExpr", self.span)?;
1219+
write_list_field(f, "operands", &self.operands)
1220+
}
1221+
}
1222+
12081223
#[derive(Clone, Debug)]
12091224
pub struct IndexedExpr {
12101225
pub span: Span,

source/compiler/qsc_qasm/src/semantic/const_eval.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,11 @@ impl Expr {
127127
// in [`Lowerer::lower_sizeof_call_expr`].
128128
None
129129
}
130+
ExprKind::Concat(_) => {
131+
// Arrays are non-const, so we don't need to implement array
132+
// concatenation in the const-evaluator.
133+
None
134+
}
130135
ExprKind::DurationofCall(expr) => Some(LiteralKind::Duration(expr.duration)),
131136
ExprKind::Err => None,
132137
}

source/compiler/qsc_qasm/src/semantic/error.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,9 @@ pub enum SemanticErrorKind {
169169
#[error("inconsistent types in alias expression: {0}")]
170170
#[diagnostic(code("Qasm.Lowerer.InconsistentTypesInAlias"))]
171171
InconsistentTypesInAlias(String, #[label] Span),
172+
#[error("inconsistent types in array concatenation expression: {0}")]
173+
#[diagnostic(code("Qasm.Lowerer.InconsistentTypesInArrayConcatenation"))]
174+
InconsistentTypesInArrayConcatenation(String, #[label] Span),
172175
#[error("indexed must be a single expression")]
173176
#[diagnostic(code("Qasm.Lowerer.IndexMustBeSingleExpr"))]
174177
IndexMustBeSingleExpr(#[label] Span),
@@ -181,6 +184,9 @@ pub enum SemanticErrorKind {
181184
#[error("assigning {0} values to {1} must be in a range that can be converted to {1}")]
182185
#[diagnostic(code("Qasm.Lowerer.InvalidCastValueRange"))]
183186
InvalidCastValueRange(String, String, #[label] Span),
187+
#[error("concatenation expressions are not allowed in {0}")]
188+
#[diagnostic(code("Qasm.Lowerer.InvalidConcatenationPosition"))]
189+
InvalidConcatenationPosition(String, #[label] Span),
184190
#[error("gate operands other than qubits or qubit arrays are not supported")]
185191
#[diagnostic(code("Qasm.Lowerer.InvalidGateOperand"))]
186192
InvalidGateOperand(#[label] Span),
@@ -203,6 +209,10 @@ pub enum SemanticErrorKind {
203209
#[diagnostic(code("Qasm.Lowerer.InvalidTypeInAlias"))]
204210
#[diagnostic(help("aliases can only be applied to quantum bits and registers"))]
205211
InvalidTypeInAlias(String, #[label] Span),
212+
#[error("invalid type in array concatenation expression: {0}")]
213+
#[diagnostic(code("Qasm.Lowerer.InvalidTypeInArrayConcatenation"))]
214+
#[diagnostic(help("array concatenation can only be applied to arrays"))]
215+
InvalidTypeInArrayConcatenation(String, #[label] Span),
206216
#[error("measure statements must have a name")]
207217
#[diagnostic(code("Qasm.Lowerer.MeasureExpressionsMustHaveName"))]
208218
MeasureExpressionsMustHaveName(#[label] Span),

0 commit comments

Comments
 (0)