Skip to content

Commit

Permalink
Add loop support
Browse files Browse the repository at this point in the history
This change adds support for the three kinds of Q# loops: for-loop, while-loop, and repeat-until-fixup-loop. It updates the scoping for the repeat style in parser to ensure variables are scoped as expected, ie: the whole repeat-until-fixup construct is considered a single scope despite the presence of blocks.
  • Loading branch information
swernli committed Mar 27, 2023
1 parent bfdccf1 commit 6c9d7e3
Show file tree
Hide file tree
Showing 3 changed files with 272 additions and 4 deletions.
89 changes: 85 additions & 4 deletions compiler/qsc_eval/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,18 @@ pub enum Error {
#[error("reassigning immutable variable")]
Mutability(#[label("variable declared as immutable")] Span),

#[error("iterable ranges cannot be open-ended")]
OpenEnded(#[label("open-ended range used as iterator")] Span),

#[error("index out of range: {0}")]
OutOfRange(i64, #[label("out of range")] Span),

#[error("negative integers cannot be used here: {0}")]
Negative(i64, #[label("invalid negative integer")] Span),

#[error("type {0} is not iterable")]
NotIterable(&'static str, #[label("not iterable")] Span),

#[error("range with step size of zero")]
RangeStepZero(#[label("invalid range")] Span),

Expand Down Expand Up @@ -283,6 +289,7 @@ impl<'a> Evaluator<'a> {
self.eval_expr_impl(msg)?.try_into().with_span(msg.span)?,
expr.span,
))),
ExprKind::For(pat, expr, block) => self.eval_for_loop(pat, expr, block),
ExprKind::If(cond, then, els) => {
if self.eval_expr_impl(cond)?.try_into().with_span(cond.span)? {
self.eval_block(then)
Expand Down Expand Up @@ -314,6 +321,7 @@ impl<'a> Evaluator<'a> {
ExprKind::Paren(expr) => self.eval_expr_impl(expr),
ExprKind::Path(path) => ControlFlow::Continue(self.resolve_binding(path.id)),
ExprKind::Range(start, step, end) => self.eval_range(start, step, end),
ExprKind::Repeat(repeat, cond, fixup) => self.eval_repeat_loop(repeat, cond, fixup),
ExprKind::Return(expr) => {
ControlFlow::Break(Reason::Return(self.eval_expr_impl(expr)?))
}
Expand All @@ -324,17 +332,20 @@ impl<'a> Evaluator<'a> {
}
ControlFlow::Continue(Value::Tuple(val_tup))
}
ExprKind::While(cond, block) => {
while self.eval_expr_impl(cond)?.try_into().with_span(cond.span)? {
let _ = self.eval_block(block)?;
}
ControlFlow::Continue(Value::UNIT)
}
ExprKind::UnOp(op, rhs) => self.eval_unop(expr, *op, rhs),
ExprKind::AssignUpdate(..)
| ExprKind::Conjugate(..)
| ExprKind::Err
| ExprKind::Field(..)
| ExprKind::For(..)
| ExprKind::Hole
| ExprKind::Lambda(..)
| ExprKind::Repeat(..)
| ExprKind::TernOp(..)
| ExprKind::While(..) => {
| ExprKind::TernOp(..) => {
ControlFlow::Break(Reason::Error(Error::Unimplemented(expr.span)))
}
}
Expand Down Expand Up @@ -401,6 +412,76 @@ impl<'a> Evaluator<'a> {
}
}

fn eval_for_loop(
&mut self,
pat: &Pat,
expr: &Expr,
block: &Block,
) -> ControlFlow<Reason, Value> {
let iterable = self.eval_expr_impl(expr)?;
let iterable = match iterable {
Value::Array(arr) => arr.into_iter(),
Value::Range(start, step, end) => Range::new(
start.map_or_else(
|| ControlFlow::Break(Reason::Error(Error::OpenEnded(expr.span))),
ControlFlow::Continue,
)?,
step.unwrap_or(1),
end.map_or_else(
|| ControlFlow::Break(Reason::Error(Error::OpenEnded(expr.span))),
ControlFlow::Continue,
)?,
)
.into_iter()
.map(Value::Int)
.collect::<Vec<_>>()
.into_iter(),
_ => ControlFlow::Break(Reason::Error(Error::NotIterable(
iterable.type_name(),
expr.span,
)))?,
};

for value in iterable {
self.enter_scope();
self.bind_value(pat, value, expr.span, Mutability::Immutable);
let _ = self.eval_block(block)?;
self.leave_scope(false);
}

ControlFlow::Continue(Value::UNIT)
}

fn eval_repeat_loop(
&mut self,
repeat: &Block,
cond: &Expr,
fixup: &Option<Block>,
) -> ControlFlow<Reason, Value> {
self.enter_scope();

repeat.stmts.iter().for_each(|stmt| {
self.eval_stmt_impl(stmt);
});
while !self.eval_expr_impl(cond)?.try_into().with_span(cond.span)? {
if let Some(block) = fixup.as_ref() {
block.stmts.iter().for_each(|stmt| {
self.eval_stmt_impl(stmt);
});
}

self.leave_scope(true);
self.enter_scope();

repeat.stmts.iter().for_each(|stmt| {
self.eval_stmt_impl(stmt);
});
}

self.leave_scope(true);
ControlFlow::Continue(Value::UNIT)
}

fn eval_qubit_init(&mut self, qubit_init: &QubitInit) -> ControlFlow<Reason, Value> {
match &qubit_init.kind {
QubitInitKind::Array(count) => {
Expand Down
172 changes: 172 additions & 0 deletions compiler/qsc_eval/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1463,6 +1463,84 @@ fn fail_shortcut_expr() {
);
}

#[test]
fn for_loop_range_expr() {
check_expr(
"",
indoc! {"{
mutable x = 0;
for i in 0..10 {
set x = x + i;
}
x
}"},
&expect!["55"],
);
}

#[test]
fn for_loop_array_expr() {
check_expr(
"",
indoc! {"{
mutable x = 0;
for i in [5, size = 5] {
set x = x + i;
}
x
}"},
&expect!["25"],
);
}

#[test]
fn for_loop_iterator_immutable_expr() {
check_expr(
"",
"for i in 0..10 { set i = 0; }",
&expect![[r#"
Mutability(
Span {
lo: 21,
hi: 22,
},
)
"#]],
);
}

#[test]
fn for_loop_not_iterable_expr() {
check_expr(
"",
"for i in (1, true, One) {}",
&expect![[r#"
NotIterable(
"Tuple",
Span {
lo: 9,
hi: 23,
},
)
"#]],
);
}

#[test]
fn for_loop_ignore_iterator_expr() {
check_expr(
"",
indoc! {"{
mutable x = 0;
for _ in [5, size = 5] {
set x = x + 1;
}
x
}"},
&expect!["5"],
);
}

#[test]
fn array_index_expr() {
check_expr("", "[1, 2, 3][1]", &expect!["2"]);
Expand Down Expand Up @@ -1736,6 +1814,58 @@ fn range_start_step_end_expr() {
check_expr("", "1..2..3", &expect!["1..2..3"]);
}

#[test]
fn repeat_until_expr() {
check_expr(
"",
indoc! {"{
mutable x = 0;
repeat {
set x = x + 1;
}
until x >= 10;
x
}"},
&expect!["10"],
);
}

#[test]
fn repeat_until_fixup_expr() {
check_expr(
"",
indoc! {"{
mutable x = 0;
repeat {}
until x >= 10
fixup {
set x = x + 1;
}
x
}"},
&expect!["10"],
);
}

#[test]
fn repeat_until_fixup_scoping_expr() {
check_expr(
"",
indoc! {"{
mutable x = 0;
repeat {
let increment = 2;
}
until x >= 10 * increment
fixup {
set x = x + increment;
}
x
}"},
&expect!["20"],
);
}

#[test]
fn return_expr() {
check_expr("", "return 4", &expect!["4"]);
Expand Down Expand Up @@ -1782,6 +1912,48 @@ fn unop_bitwise_not_bool_expr() {
);
}

#[test]
fn while_expr() {
check_expr(
"",
indoc! {"{
mutable x = 0;
while x < 10 {
set x = x + 1;
}
x
}"},
&expect!["10"],
);
}

#[test]
fn while_false_shortcut_expr() {
check_expr(
"",
r#"while false { fail "Shouldn't fail" }"#,
&expect!["()"],
);
}

#[test]
fn while_invalid_type_expr() {
check_expr(
"",
r#"while Zero { fail "Shouldn't fail" }"#,
&expect![[r#"
Type(
"Bool",
"Result",
Span {
lo: 6,
hi: 10,
},
)
"#]],
);
}

#[test]
fn unop_bitwise_not_int_expr() {
check_expr("", "~~~(13)", &expect!["-14"]);
Expand Down
15 changes: 15 additions & 0 deletions compiler/qsc_frontend/src/resolve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,21 @@ impl<'a> Visitor<'a> for Resolver<'a> {
self.visit_expr(iter);
self.with_pat(pat, |resolver| resolver.visit_block(block));
}
ExprKind::Repeat(repeat, cond, fixup) => {
self.with_scope(&mut HashMap::new(), |resolver| {
repeat
.stmts
.iter()
.for_each(|stmt| resolver.visit_stmt(stmt));
resolver.visit_expr(cond);
if let Some(block) = fixup.as_ref() {
block
.stmts
.iter()
.for_each(|stmt| resolver.visit_stmt(stmt));
}
});
}
ExprKind::Lambda(_, input, output) => {
self.with_pat(input, |resolver| resolver.visit_expr(output));
}
Expand Down

0 comments on commit 6c9d7e3

Please sign in to comment.