While loops 🔁

This commit is contained in:
Laurenz 2021-02-24 21:29:32 +01:00
parent dae3dad540
commit f084165eab
29 changed files with 381 additions and 205 deletions

View File

@ -118,6 +118,7 @@ impl Eval for Expr {
Self::Binary(v) => v.eval(ctx),
Self::Let(v) => v.eval(ctx),
Self::If(v) => v.eval(ctx),
Self::While(v) => v.eval(ctx),
Self::For(v) => v.eval(ctx),
}
}
@ -403,24 +404,56 @@ impl Eval for ExprIf {
fn eval(&self, ctx: &mut EvalContext) -> Self::Output {
let condition = self.condition.eval(ctx);
if let Value::Bool(boolean) = condition {
return if boolean {
if let Value::Bool(condition) = condition {
if condition {
self.if_body.eval(ctx)
} else if let Some(expr) = &self.else_body {
expr.eval(ctx)
} else {
Value::None
};
} else if condition != Value::Error {
ctx.diag(error!(
self.condition.span(),
"expected boolean, found {}",
condition.type_name(),
));
}
} else {
if condition != Value::Error {
ctx.diag(error!(
self.condition.span(),
"expected boolean, found {}",
condition.type_name(),
));
}
Value::Error
}
}
}
Value::Error
impl Eval for ExprWhile {
type Output = Value;
fn eval(&self, ctx: &mut EvalContext) -> Self::Output {
let mut output = vec![];
loop {
let condition = self.condition.eval(ctx);
if let Value::Bool(condition) = condition {
if condition {
match self.body.eval(ctx) {
Value::Template(v) => output.extend(v),
Value::Str(v) => output.push(TemplateNode::Str(v)),
Value::Error => return Value::Error,
_ => {}
}
} else {
return Value::Template(output);
}
} else {
if condition != Value::Error {
ctx.diag(error!(
self.condition.span(),
"expected boolean, found {}",
condition.type_name(),
));
}
return Value::Error;
}
}
}
}
@ -438,7 +471,8 @@ impl Eval for ExprFor {
$(ctx.scopes.def_mut($binding.as_str(), $value);)*
match self.body.eval(ctx) {
Value::Template(new) => output.extend(new),
Value::Template(v) => output.extend(v),
Value::Str(v) => output.push(TemplateNode::Str(v)),
Value::Error => {
ctx.scopes.pop();
return Value::Error;

View File

@ -586,7 +586,11 @@ primitive! { Color: "color", Value::Color }
primitive! { String: "string", Value::Str }
primitive! { ValueArray: "array", Value::Array }
primitive! { ValueDict: "dictionary", Value::Dict }
primitive! { ValueTemplate: "template", Value::Template }
primitive! {
ValueTemplate: "template",
Value::Template,
Value::Str(v) => vec![TemplateNode::Str(v)],
}
primitive! { ValueFunc: "function", Value::Func }
primitive! { ValueArgs: "arguments", Value::Args }

View File

@ -71,7 +71,7 @@ fn node(p: &mut Parser, at_start: &mut bool) -> Option<Node> {
Token::UnicodeEscape(t) => Node::Text(unicode_escape(p, t)),
// Hashtag + keyword / identifier.
Token::Ident(_) | Token::Let | Token::If | Token::For => {
Token::Ident(_) | Token::Let | Token::If | Token::While | Token::For => {
*at_start = false;
let stmt = token == Token::Let;
let group = if stmt { Group::Stmt } else { Group::Expr };
@ -191,6 +191,7 @@ fn primary(p: &mut Parser) -> Option<Expr> {
// Keywords.
Some(Token::Let) => expr_let(p),
Some(Token::If) => expr_if(p),
Some(Token::While) => expr_while(p),
Some(Token::For) => expr_for(p),
// Structures.
@ -382,6 +383,25 @@ fn expr_if(p: &mut Parser) -> Option<Expr> {
expr_if
}
/// Parse a while expresion.
fn expr_while(p: &mut Parser) -> Option<Expr> {
let start = p.start();
p.assert(Token::While);
let mut expr_while = None;
if let Some(condition) = expr(p) {
if let Some(body) = body(p) {
expr_while = Some(Expr::While(ExprWhile {
span: p.span(start),
condition: Box::new(condition),
body: Box::new(body),
}));
}
}
expr_while
}
/// Parse a for expression.
fn expr_for(p: &mut Parser) -> Option<Expr> {
let start = p.start();

View File

@ -221,6 +221,7 @@ impl Pretty for Expr {
Self::Call(v) => v.pretty(p),
Self::Let(v) => v.pretty(p),
Self::If(v) => v.pretty(p),
Self::While(v) => v.pretty(p),
Self::For(v) => v.pretty(p),
}
}
@ -413,6 +414,15 @@ impl Pretty for ExprIf {
}
}
impl Pretty for ExprWhile {
fn pretty(&self, p: &mut Printer) {
p.push_str("while ");
self.condition.pretty(p);
p.push(' ');
self.body.pretty(p);
}
}
impl Pretty for ExprFor {
fn pretty(&self, p: &mut Printer) {
p.push_str("for ");
@ -718,9 +728,10 @@ mod tests {
// Keywords.
roundtrip("#let x = 1 + 2");
test_parse("#if x [y] #else [z]", "#if x [y] else [z]");
roundtrip("#while x {y}");
roundtrip("#for x in y {z}");
roundtrip("#for k, x in y {z}");
test_parse("#if x [y] #else [z]", "#if x [y] else [z]");
}
#[test]

View File

@ -19,19 +19,21 @@ pub enum Expr {
Template(ExprTemplate),
/// A grouped expression: `(1 + 2)`.
Group(ExprGroup),
/// A block expression: `{ #let x = 1; x + 2 }`.
/// A block expression: `{ let x = 1; x + 2 }`.
Block(ExprBlock),
/// A unary operation: `-x`.
Unary(ExprUnary),
/// A binary operation: `a + b`.
Binary(ExprBinary),
/// An invocation of a function: `foo(...)`, `#[foo ...]`.
/// An invocation of a function: `foo(...)`.
Call(ExprCall),
/// A let expression: `#let x = 1`.
/// A let expression: `let x = 1`.
Let(ExprLet),
/// An if expression: `#if x { y } #else { z }`.
/// An if expression: `if x { y } else { z }`.
If(ExprIf),
/// A for expression: `#for x #in y { z }`.
/// A while expression: `while x { y }`.
While(ExprWhile),
/// A for expression: `for x in y { z }`.
For(ExprFor),
}
@ -51,6 +53,7 @@ impl Expr {
Self::Call(v) => v.span,
Self::Let(v) => v.span,
Self::If(v) => v.span,
Self::While(v) => v.span,
Self::For(v) => v.span,
}
}
@ -62,6 +65,7 @@ impl Expr {
| Expr::Call(_)
| Expr::Let(_)
| Expr::If(_)
| Expr::While(_)
| Expr::For(_)
)
}
@ -154,7 +158,7 @@ pub struct ExprGroup {
pub expr: Box<Expr>,
}
/// A block expression: `{ #let x = 1; x + 2 }`.
/// A block expression: `{ let x = 1; x + 2 }`.
#[derive(Debug, Clone, PartialEq)]
pub struct ExprBlock {
/// The source code location.
@ -365,7 +369,7 @@ pub enum Associativity {
Right,
}
/// An invocation of a function: `foo(...)`, `#[foo ...]`.
/// An invocation of a function: `foo(...)`.
#[derive(Debug, Clone, PartialEq)]
pub struct ExprCall {
/// The source code location.
@ -407,7 +411,7 @@ impl ExprArg {
}
}
/// A let expression: `#let x = 1`.
/// A let expression: `let x = 1`.
#[derive(Debug, Clone, PartialEq)]
pub struct ExprLet {
/// The source code location.
@ -418,7 +422,7 @@ pub struct ExprLet {
pub init: Option<Box<Expr>>,
}
/// An if expression: `#if x { y } #else { z }`.
/// An if expression: `if x { y } else { z }`.
#[derive(Debug, Clone, PartialEq)]
pub struct ExprIf {
/// The source code location.
@ -431,7 +435,18 @@ pub struct ExprIf {
pub else_body: Option<Box<Expr>>,
}
/// A for expression: `#for x #in y { z }`.
/// A while expression: `while x { y }`.
#[derive(Debug, Clone, PartialEq)]
pub struct ExprWhile {
/// The source code location.
pub span: Span,
/// The condition which selects whether to evaluate the body.
pub condition: Box<Expr>,
/// The expression to evaluate while the condition is true.
pub body: Box<Expr>,
}
/// A for expression: `for x in y { z }`.
#[derive(Debug, Clone, PartialEq)]
pub struct ExprFor {
/// The source code location.
@ -447,9 +462,9 @@ pub struct ExprFor {
/// A pattern in a for loop.
#[derive(Debug, Clone, PartialEq)]
pub enum ForPattern {
/// A value pattern: `#for v #in array`.
/// A value pattern: `for v in array`.
Value(Ident),
/// A key-value pattern: `#for k, v #in dict`.
/// A key-value pattern: `for k, v in dict`.
KeyValue(Ident, Ident),
}

View File

@ -61,6 +61,7 @@ visit! {
Expr::Call(e) => v.visit_call(e),
Expr::Let(e) => v.visit_let(e),
Expr::If(e) => v.visit_if(e),
Expr::While(e) => v.visit_while(e),
Expr::For(e) => v.visit_for(e),
}
}
@ -132,6 +133,11 @@ visit! {
}
}
fn visit_while(v, node: &ExprWhile) {
v.visit_expr(&node.condition);
v.visit_expr(&node.body);
}
fn visit_for(v, node: &ExprFor) {
v.visit_expr(&node.iter);
v.visit_expr(&node.body);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 364 B

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
tests/ref/control/while.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 838 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@ -1,32 +0,0 @@
// Test invalid for loop syntax.
---
// Error: 5-5 expected identifier
#for
// Error: 7-7 expected keyword `in`
#for v
// Error: 10-10 expected expression
#for v in
// Error: 15-15 expected body
#for v in iter
---
// Should output `v in iter`.
// Error: 5 expected identifier
#for
v in iter {}
// Should output `A thing`.
// Error: 7-10 expected identifier, found string
A#for "v" thing.
// Should output `in iter`.
// Error: 6-9 expected identifier, found string
#for "v" in iter {}
// Should output `+ b in iter`.
// Error: 7 expected keyword `in`
#for a + b in iter {}

View File

@ -1,20 +0,0 @@
// Test return value of for loops.
---
// Template body yields template.
// Should output `234`.
#for v in (1, 2, 3, 4) [#if v >= 2 [{v}]]
---
// Block body yields template.
// Should output `[1st, 2nd, 3rd, 4th, 5th, 6th]`.
{
"[" + for v in (1, 2, 3, 4, 5, 6) {
(if v > 1 [, ]
+ [{v}]
+ if v == 1 [st]
+ if v == 2 [nd]
+ if v == 3 [rd]
+ if v >= 4 [th])
} + "]"
}

View File

@ -1,11 +1,10 @@
// Test for loops.
// Ref: false
---
// Empty array.
#for x in () [Nope]
// Array.
#for x in () {}
#let sum = 0
#for x in (1, 2, 3, 4, 5) {
sum += x
@ -13,14 +12,12 @@
#test(sum, 15)
---
// Dictionary.
// Ref: true
(\ #for k, v in (name: "Typst", age: 2) [
#h(0.5cm) {k}: {v}, \
])
// Dictionary is not traversed in insertion order.
// Should output `age: 1, name: Typst,`.
#for k, v in (name: "Typst", age: 2) [
{k}: {v}, \
]
---
// String.
{
let out = ""
@ -36,6 +33,33 @@
}
---
// Block body.
// Should output `[1st, 2nd, 3rd, 4th, 5th, 6th]`.
{
"[" + for v in (1, 2, 3, 4, 5, 6) {
(if v > 1 [, ]
+ [{v}]
+ if v == 1 [st]
+ if v == 2 [nd]
+ if v == 3 [rd]
+ if v >= 4 [th])
} + "]"
}
// Template body.
// Should output `234`.
#for v in (1, 2, 3, 4, 5, 6, 7) [#if v >= 2 and v <= 5 { repr(v) }]
---
// Value of for loops.
// Ref: false
#test(type(for v in () {}), "template")
#test(type(for v in () []), "template")
---
// Error: 14-19 unknown variable
#let error = error
// Uniterable expression.
// Error: 11-15 cannot loop over boolean
#for v in true {}
@ -44,9 +68,7 @@
// Error: 11-18 cannot add integer and string
#for v in 1 + "2" {}
// Error: 14-17 cannot apply '-' to string
#let error = -""
#let result = for v in (1, 2, 3) {
// A single error stops iteration.
#test(error, for v in (1, 2, 3) {
if v < 2 [Ok] else {error}
}
#test(result, error)
})

View File

@ -1,28 +0,0 @@
// Test invalid if syntax.
---
// Error: 4 expected expression
#if
// Error: 4 expected expression
{if}
// Error: 6 expected body
#if x
// Error: 1-6 unexpected keyword `else`
#else {}
---
// Should output `x`.
// Error: 4 expected expression
#if
x {}
// Should output `something`.
// Error: 6 expected body
#if x something
// Should output `A thing.`
// Error: 20 expected body
A#if false {} #else thing

View File

@ -1,21 +0,0 @@
// Test return value of if expressions.
// Ref: false
---
{
let x = 1
let y = 2
let z
// Returns if branch.
z = if x < y { "ok" }
test(z, "ok")
// Returns else branch.
z = if x > y { "bad" } else { "ok" }
test(z, "ok")
// Missing else evaluates to none.
z = if x > y { "bad" }
test(z, none)
}

View File

@ -3,35 +3,61 @@
---
// Test condition evaluation.
#if 1 < 2 [
Ok.
One.
]
#if true == false [
Bad, but we {dont-care}!
{Bad}, but we {dont-care}!
]
---
// Brace in condition.
// Braced condition.
#if {true} [
Ok.
One.
]
// Template in condition.
#if [] != none [
Two.
]
// Multi-line condition with parens.
#if (
1 + 1
== 1
) {
nope
} #else {
"Ok."
) [
Nope.
] #else {
"Three."
}
// Multiline.
#if false [
Bad.
] #else {
let pt = "."
"Ok" + pt
let point = "."
"Four" + point
}
---
// Value of if expressions.
// Ref: false
{
let x = 1
let y = 2
let z
// Returns if branch.
z = if x < y { "ok" }
test(z, "ok")
// Returns else branch.
z = if x > y { "bad" } else { "ok" }
test(z, "ok")
// Missing else evaluates to none.
z = if x > y { "bad" }
test(z, none)
}
---

View File

@ -0,0 +1,100 @@
// Test invalid control syntax.
---
// Error: 5 expected identifier
#let
// Error: 5 expected identifier
{let}
// Error: 6-9 expected identifier, found string
#let "v"
// Should output `1`.
// Error: 7 expected semicolon or line break
#let v 1
// Error: 9 expected expression
#let v =
// Should output `= 1`.
// Error: 6-9 expected identifier, found string
#let "v" = 1
---
// Error: 4 expected expression
#if
// Error: 4 expected expression
{if}
// Error: 6 expected body
#if x
// Error: 1-6 unexpected keyword `else`
#else {}
// Should output `x`.
// Error: 4 expected expression
#if
x {}
// Should output `something`.
// Error: 6 expected body
#if x something
// Should output `A thing.`
// Error: 20 expected body
A#if false {} #else thing
---
// Error: 7 expected expression
#while
// Error: 7 expected expression
{while}
// Error: 9 expected body
#while x
// Should output `x`.
// Error: 7 expected expression
#while
x {}
// Should output `something`.
// Error: 9 expected body
#while x something
---
// Error: 5 expected identifier
#for
// Error: 5 expected identifier
{for}
// Error: 7 expected keyword `in`
#for v
// Error: 10 expected expression
#for v in
// Error: 15 expected body
#for v in iter
// Should output `v in iter`.
// Error: 5 expected identifier
#for
v in iter {}
// Should output `A thing`.
// Error: 7-10 expected identifier, found string
A#for "v" thing
// Should output `in iter`.
// Error: 6-9 expected identifier, found string
#for "v" in iter {}
// Should output `+ b in iter`.
// Error: 7 expected keyword `in`
#for a + b in iter {}

View File

@ -1,20 +0,0 @@
// Test invalid let binding syntax.
---
// Error: 5 expected identifier
#let
// Error: 6-9 expected identifier, found string
#let "v"
// Should output `1`.
// Error: 7 expected semicolon or line break
#let v 1
// Error: 9 expected expression
#let v =
---
// Should output `= 1`.
// Error: 6-9 expected identifier, found string
#let "v" = 1

View File

@ -1,28 +0,0 @@
// Test termination of let statements.
---
// Terminated by line break.
#let v1 = 1
One
// Terminated by semicolon.
#let v2 = 2; Two
// Terminated by semicolon and line break.
#let v3 = 3;
Three
// Terminated because expression ends.
// Error: 12 expected semicolon or line break
#let v4 = 4 Four
// Terminated by semicolon even though we are in a paren group.
// Error: 2:19 expected expression
// Error: 1:19 expected closing paren
#let v5 = (1, 2 + ; Five
#test(v1, 1)
#test(v2, 2)
#test(v3, 3)
#test(v4, 4)
#test(v5, (1, 2))

View File

@ -1,7 +1,8 @@
// Test let bindings.
// Ref: false
---
// Ref: false
// Automatically initialized with none.
#let x
#test(x, none)
@ -9,3 +10,32 @@
// Manually initialized with one.
#let x = 1
#test(x, 1)
---
// Termination.
// Terminated by line break.
#let v1 = 1
One
// Terminated by semicolon.
#let v2 = 2; Two
// Terminated by semicolon and line break.
#let v3 = 3;
Three
// Terminated because expression ends.
// Error: 12 expected semicolon or line break
#let v4 = 4 Four
// Terminated by semicolon even though we are in a paren group.
// Error: 2:19 expected expression
// Error: 1:19 expected closing paren
#let v5 = (1, 2 + ; Five
#test(v1, 1)
#test(v2, 2)
#test(v3, 3)
#test(v4, 4)
#test(v5, (1, 2))

View File

@ -0,0 +1,46 @@
// Test while expressions.
---
// Should output `2 4 6 8 10`.
#let i = 0
#while i < 10 [
{ i += 2 }
#i
]
// Should output `Hi`.
#let iter = true
#while iter {
iter = false
"Hi."
}
#while false {
dont-care
}
---
// Value of while loops.
// Ref: false
#test(type(while false {}), "template")
#test(type(while false []), "template")
---
// Error: 14-19 unknown variable
#let error = error
// Condition must be boolean.
// Error: 8-14 expected boolean, found template
#while [nope] [nope]
// Make sure that we don't complain twice.
// Error: 8-15 unknown variable
#while nothing {}
// A single error stops iteration.
#let i = 0
#test(error, while i < 10 {
i += 1
if i < 5 [nope] else { error }
})
#test(i, 5)

View File

@ -19,9 +19,17 @@ A #if true{"B"} C \
A#if false [] #else [B]C \
A#if true [B] #else [] C \
---
// Spacing around while loop.
#let c = true; A#while c [{c = false}B]C \
#let c = true; A#while c [{c = false}B] C \
#let c = true; A #while c { c = false; "B" }C \
#let c = true; A #while c { c = false; "B" } C \
---
// Spacing around for loop.
A#for _ in (none,) [B]C \
A#for _ in (none,) [B] C \
A #for _ in (none,) [B]C \
A #for _ in (none,) {"B"}C \

View File

@ -92,9 +92,9 @@ function getWebviewContent(pngSrc, refSrc, stdout, stderr) {
<style>
body, html {
width: 100%;
text-align: center;
margin: 0;
padding: 0;
text-align: center;
}
img {
width: 80%;
@ -102,7 +102,10 @@ function getWebviewContent(pngSrc, refSrc, stdout, stderr) {
object-fit: contain;
}
pre {
display: inline-block;
font-family: var(--vscode-editor-font-family);
text-align: left;
width: 80%;
}
</style>
</head>