While loops 🔁
@ -118,6 +118,7 @@ impl Eval for Expr {
|
|||||||
Self::Binary(v) => v.eval(ctx),
|
Self::Binary(v) => v.eval(ctx),
|
||||||
Self::Let(v) => v.eval(ctx),
|
Self::Let(v) => v.eval(ctx),
|
||||||
Self::If(v) => v.eval(ctx),
|
Self::If(v) => v.eval(ctx),
|
||||||
|
Self::While(v) => v.eval(ctx),
|
||||||
Self::For(v) => v.eval(ctx),
|
Self::For(v) => v.eval(ctx),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -403,26 +404,58 @@ impl Eval for ExprIf {
|
|||||||
|
|
||||||
fn eval(&self, ctx: &mut EvalContext) -> Self::Output {
|
fn eval(&self, ctx: &mut EvalContext) -> Self::Output {
|
||||||
let condition = self.condition.eval(ctx);
|
let condition = self.condition.eval(ctx);
|
||||||
|
if let Value::Bool(condition) = condition {
|
||||||
if let Value::Bool(boolean) = condition {
|
if condition {
|
||||||
return if boolean {
|
|
||||||
self.if_body.eval(ctx)
|
self.if_body.eval(ctx)
|
||||||
} else if let Some(expr) = &self.else_body {
|
} else if let Some(expr) = &self.else_body {
|
||||||
expr.eval(ctx)
|
expr.eval(ctx)
|
||||||
} else {
|
} else {
|
||||||
Value::None
|
Value::None
|
||||||
};
|
}
|
||||||
} else if condition != Value::Error {
|
} else {
|
||||||
|
if condition != Value::Error {
|
||||||
ctx.diag(error!(
|
ctx.diag(error!(
|
||||||
self.condition.span(),
|
self.condition.span(),
|
||||||
"expected boolean, found {}",
|
"expected boolean, found {}",
|
||||||
condition.type_name(),
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Eval for ExprFor {
|
impl Eval for ExprFor {
|
||||||
type Output = Value;
|
type Output = Value;
|
||||||
@ -438,7 +471,8 @@ impl Eval for ExprFor {
|
|||||||
$(ctx.scopes.def_mut($binding.as_str(), $value);)*
|
$(ctx.scopes.def_mut($binding.as_str(), $value);)*
|
||||||
|
|
||||||
match self.body.eval(ctx) {
|
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 => {
|
Value::Error => {
|
||||||
ctx.scopes.pop();
|
ctx.scopes.pop();
|
||||||
return Value::Error;
|
return Value::Error;
|
||||||
|
@ -586,7 +586,11 @@ primitive! { Color: "color", Value::Color }
|
|||||||
primitive! { String: "string", Value::Str }
|
primitive! { String: "string", Value::Str }
|
||||||
primitive! { ValueArray: "array", Value::Array }
|
primitive! { ValueArray: "array", Value::Array }
|
||||||
primitive! { ValueDict: "dictionary", Value::Dict }
|
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! { ValueFunc: "function", Value::Func }
|
||||||
primitive! { ValueArgs: "arguments", Value::Args }
|
primitive! { ValueArgs: "arguments", Value::Args }
|
||||||
|
|
||||||
|
@ -71,7 +71,7 @@ fn node(p: &mut Parser, at_start: &mut bool) -> Option<Node> {
|
|||||||
Token::UnicodeEscape(t) => Node::Text(unicode_escape(p, t)),
|
Token::UnicodeEscape(t) => Node::Text(unicode_escape(p, t)),
|
||||||
|
|
||||||
// Hashtag + keyword / identifier.
|
// Hashtag + keyword / identifier.
|
||||||
Token::Ident(_) | Token::Let | Token::If | Token::For => {
|
Token::Ident(_) | Token::Let | Token::If | Token::While | Token::For => {
|
||||||
*at_start = false;
|
*at_start = false;
|
||||||
let stmt = token == Token::Let;
|
let stmt = token == Token::Let;
|
||||||
let group = if stmt { Group::Stmt } else { Group::Expr };
|
let group = if stmt { Group::Stmt } else { Group::Expr };
|
||||||
@ -191,6 +191,7 @@ fn primary(p: &mut Parser) -> Option<Expr> {
|
|||||||
// Keywords.
|
// Keywords.
|
||||||
Some(Token::Let) => expr_let(p),
|
Some(Token::Let) => expr_let(p),
|
||||||
Some(Token::If) => expr_if(p),
|
Some(Token::If) => expr_if(p),
|
||||||
|
Some(Token::While) => expr_while(p),
|
||||||
Some(Token::For) => expr_for(p),
|
Some(Token::For) => expr_for(p),
|
||||||
|
|
||||||
// Structures.
|
// Structures.
|
||||||
@ -382,6 +383,25 @@ fn expr_if(p: &mut Parser) -> Option<Expr> {
|
|||||||
expr_if
|
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.
|
/// Parse a for expression.
|
||||||
fn expr_for(p: &mut Parser) -> Option<Expr> {
|
fn expr_for(p: &mut Parser) -> Option<Expr> {
|
||||||
let start = p.start();
|
let start = p.start();
|
||||||
|
@ -221,6 +221,7 @@ impl Pretty for Expr {
|
|||||||
Self::Call(v) => v.pretty(p),
|
Self::Call(v) => v.pretty(p),
|
||||||
Self::Let(v) => v.pretty(p),
|
Self::Let(v) => v.pretty(p),
|
||||||
Self::If(v) => v.pretty(p),
|
Self::If(v) => v.pretty(p),
|
||||||
|
Self::While(v) => v.pretty(p),
|
||||||
Self::For(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 {
|
impl Pretty for ExprFor {
|
||||||
fn pretty(&self, p: &mut Printer) {
|
fn pretty(&self, p: &mut Printer) {
|
||||||
p.push_str("for ");
|
p.push_str("for ");
|
||||||
@ -718,9 +728,10 @@ mod tests {
|
|||||||
|
|
||||||
// Keywords.
|
// Keywords.
|
||||||
roundtrip("#let x = 1 + 2");
|
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 x in y {z}");
|
||||||
roundtrip("#for k, x in y {z}");
|
roundtrip("#for k, x in y {z}");
|
||||||
test_parse("#if x [y] #else [z]", "#if x [y] else [z]");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -19,19 +19,21 @@ pub enum Expr {
|
|||||||
Template(ExprTemplate),
|
Template(ExprTemplate),
|
||||||
/// A grouped expression: `(1 + 2)`.
|
/// A grouped expression: `(1 + 2)`.
|
||||||
Group(ExprGroup),
|
Group(ExprGroup),
|
||||||
/// A block expression: `{ #let x = 1; x + 2 }`.
|
/// A block expression: `{ let x = 1; x + 2 }`.
|
||||||
Block(ExprBlock),
|
Block(ExprBlock),
|
||||||
/// A unary operation: `-x`.
|
/// A unary operation: `-x`.
|
||||||
Unary(ExprUnary),
|
Unary(ExprUnary),
|
||||||
/// A binary operation: `a + b`.
|
/// A binary operation: `a + b`.
|
||||||
Binary(ExprBinary),
|
Binary(ExprBinary),
|
||||||
/// An invocation of a function: `foo(...)`, `#[foo ...]`.
|
/// An invocation of a function: `foo(...)`.
|
||||||
Call(ExprCall),
|
Call(ExprCall),
|
||||||
/// A let expression: `#let x = 1`.
|
/// A let expression: `let x = 1`.
|
||||||
Let(ExprLet),
|
Let(ExprLet),
|
||||||
/// An if expression: `#if x { y } #else { z }`.
|
/// An if expression: `if x { y } else { z }`.
|
||||||
If(ExprIf),
|
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),
|
For(ExprFor),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,6 +53,7 @@ impl Expr {
|
|||||||
Self::Call(v) => v.span,
|
Self::Call(v) => v.span,
|
||||||
Self::Let(v) => v.span,
|
Self::Let(v) => v.span,
|
||||||
Self::If(v) => v.span,
|
Self::If(v) => v.span,
|
||||||
|
Self::While(v) => v.span,
|
||||||
Self::For(v) => v.span,
|
Self::For(v) => v.span,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -62,6 +65,7 @@ impl Expr {
|
|||||||
| Expr::Call(_)
|
| Expr::Call(_)
|
||||||
| Expr::Let(_)
|
| Expr::Let(_)
|
||||||
| Expr::If(_)
|
| Expr::If(_)
|
||||||
|
| Expr::While(_)
|
||||||
| Expr::For(_)
|
| Expr::For(_)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -154,7 +158,7 @@ pub struct ExprGroup {
|
|||||||
pub expr: Box<Expr>,
|
pub expr: Box<Expr>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A block expression: `{ #let x = 1; x + 2 }`.
|
/// A block expression: `{ let x = 1; x + 2 }`.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct ExprBlock {
|
pub struct ExprBlock {
|
||||||
/// The source code location.
|
/// The source code location.
|
||||||
@ -365,7 +369,7 @@ pub enum Associativity {
|
|||||||
Right,
|
Right,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An invocation of a function: `foo(...)`, `#[foo ...]`.
|
/// An invocation of a function: `foo(...)`.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct ExprCall {
|
pub struct ExprCall {
|
||||||
/// The source code location.
|
/// 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)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct ExprLet {
|
pub struct ExprLet {
|
||||||
/// The source code location.
|
/// The source code location.
|
||||||
@ -418,7 +422,7 @@ pub struct ExprLet {
|
|||||||
pub init: Option<Box<Expr>>,
|
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)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct ExprIf {
|
pub struct ExprIf {
|
||||||
/// The source code location.
|
/// The source code location.
|
||||||
@ -431,7 +435,18 @@ pub struct ExprIf {
|
|||||||
pub else_body: Option<Box<Expr>>,
|
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)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct ExprFor {
|
pub struct ExprFor {
|
||||||
/// The source code location.
|
/// The source code location.
|
||||||
@ -447,9 +462,9 @@ pub struct ExprFor {
|
|||||||
/// A pattern in a for loop.
|
/// A pattern in a for loop.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum ForPattern {
|
pub enum ForPattern {
|
||||||
/// A value pattern: `#for v #in array`.
|
/// A value pattern: `for v in array`.
|
||||||
Value(Ident),
|
Value(Ident),
|
||||||
/// A key-value pattern: `#for k, v #in dict`.
|
/// A key-value pattern: `for k, v in dict`.
|
||||||
KeyValue(Ident, Ident),
|
KeyValue(Ident, Ident),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,6 +61,7 @@ visit! {
|
|||||||
Expr::Call(e) => v.visit_call(e),
|
Expr::Call(e) => v.visit_call(e),
|
||||||
Expr::Let(e) => v.visit_let(e),
|
Expr::Let(e) => v.visit_let(e),
|
||||||
Expr::If(e) => v.visit_if(e),
|
Expr::If(e) => v.visit_if(e),
|
||||||
|
Expr::While(e) => v.visit_while(e),
|
||||||
Expr::For(e) => v.visit_for(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) {
|
fn visit_for(v, node: &ExprFor) {
|
||||||
v.visit_expr(&node.iter);
|
v.visit_expr(&node.iter);
|
||||||
v.visit_expr(&node.body);
|
v.visit_expr(&node.body);
|
||||||
|
Before Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.6 KiB |
BIN
tests/ref/control/invalid.png
Normal file
After Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 364 B |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
BIN
tests/ref/control/while.png
Normal file
After Width: | Height: | Size: 838 B |
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 5.0 KiB |
@ -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 {}
|
|
@ -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])
|
|
||||||
} + "]"
|
|
||||||
}
|
|
@ -1,11 +1,10 @@
|
|||||||
// Test for loops.
|
// Test for loops.
|
||||||
// Ref: false
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
// Empty array.
|
||||||
|
#for x in () [Nope]
|
||||||
|
|
||||||
// Array.
|
// Array.
|
||||||
|
|
||||||
#for x in () {}
|
|
||||||
|
|
||||||
#let sum = 0
|
#let sum = 0
|
||||||
#for x in (1, 2, 3, 4, 5) {
|
#for x in (1, 2, 3, 4, 5) {
|
||||||
sum += x
|
sum += x
|
||||||
@ -13,14 +12,12 @@
|
|||||||
|
|
||||||
#test(sum, 15)
|
#test(sum, 15)
|
||||||
|
|
||||||
---
|
// Dictionary is not traversed in insertion order.
|
||||||
// Dictionary.
|
// Should output `age: 1, name: Typst,`.
|
||||||
// Ref: true
|
#for k, v in (name: "Typst", age: 2) [
|
||||||
(\ #for k, v in (name: "Typst", age: 2) [
|
{k}: {v}, \
|
||||||
#h(0.5cm) {k}: {v}, \
|
]
|
||||||
])
|
|
||||||
|
|
||||||
---
|
|
||||||
// String.
|
// String.
|
||||||
{
|
{
|
||||||
let out = ""
|
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.
|
// Uniterable expression.
|
||||||
// Error: 11-15 cannot loop over boolean
|
// Error: 11-15 cannot loop over boolean
|
||||||
#for v in true {}
|
#for v in true {}
|
||||||
@ -44,9 +68,7 @@
|
|||||||
// Error: 11-18 cannot add integer and string
|
// Error: 11-18 cannot add integer and string
|
||||||
#for v in 1 + "2" {}
|
#for v in 1 + "2" {}
|
||||||
|
|
||||||
// Error: 14-17 cannot apply '-' to string
|
// A single error stops iteration.
|
||||||
#let error = -""
|
#test(error, for v in (1, 2, 3) {
|
||||||
#let result = for v in (1, 2, 3) {
|
|
||||||
if v < 2 [Ok] else {error}
|
if v < 2 [Ok] else {error}
|
||||||
}
|
})
|
||||||
#test(result, error)
|
|
||||||
|
@ -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
|
|
@ -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)
|
|
||||||
}
|
|
@ -3,35 +3,61 @@
|
|||||||
---
|
---
|
||||||
// Test condition evaluation.
|
// Test condition evaluation.
|
||||||
#if 1 < 2 [
|
#if 1 < 2 [
|
||||||
Ok.
|
One.
|
||||||
]
|
]
|
||||||
|
|
||||||
#if true == false [
|
#if true == false [
|
||||||
Bad, but we {dont-care}!
|
{Bad}, but we {dont-care}!
|
||||||
]
|
]
|
||||||
|
|
||||||
---
|
---
|
||||||
// Brace in condition.
|
// Braced condition.
|
||||||
#if {true} [
|
#if {true} [
|
||||||
Ok.
|
One.
|
||||||
|
]
|
||||||
|
|
||||||
|
// Template in condition.
|
||||||
|
#if [] != none [
|
||||||
|
Two.
|
||||||
]
|
]
|
||||||
|
|
||||||
// Multi-line condition with parens.
|
// Multi-line condition with parens.
|
||||||
#if (
|
#if (
|
||||||
1 + 1
|
1 + 1
|
||||||
== 1
|
== 1
|
||||||
) {
|
) [
|
||||||
nope
|
Nope.
|
||||||
} #else {
|
] #else {
|
||||||
"Ok."
|
"Three."
|
||||||
}
|
}
|
||||||
|
|
||||||
// Multiline.
|
// Multiline.
|
||||||
#if false [
|
#if false [
|
||||||
Bad.
|
Bad.
|
||||||
] #else {
|
] #else {
|
||||||
let pt = "."
|
let point = "."
|
||||||
"Ok" + pt
|
"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)
|
||||||
}
|
}
|
||||||
|
|
||||||
---
|
---
|
||||||
|
100
tests/typ/control/invalid.typ
Normal 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 {}
|
@ -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
|
|
@ -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))
|
|
@ -1,7 +1,8 @@
|
|||||||
// Test let bindings.
|
// Test let bindings.
|
||||||
// Ref: false
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
// Ref: false
|
||||||
|
|
||||||
// Automatically initialized with none.
|
// Automatically initialized with none.
|
||||||
#let x
|
#let x
|
||||||
#test(x, none)
|
#test(x, none)
|
||||||
@ -9,3 +10,32 @@
|
|||||||
// Manually initialized with one.
|
// Manually initialized with one.
|
||||||
#let x = 1
|
#let x = 1
|
||||||
#test(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))
|
||||||
|
46
tests/typ/control/while.typ
Normal 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)
|
@ -19,9 +19,17 @@ A #if true{"B"} C \
|
|||||||
A#if false [] #else [B]C \
|
A#if false [] #else [B]C \
|
||||||
A#if true [B] #else [] 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.
|
// 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 \
|
||||||
A #for _ in (none,) [B]C \
|
A #for _ in (none,) {"B"}C \
|
||||||
|
@ -92,9 +92,9 @@ function getWebviewContent(pngSrc, refSrc, stdout, stderr) {
|
|||||||
<style>
|
<style>
|
||||||
body, html {
|
body, html {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
text-align: center;
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
img {
|
img {
|
||||||
width: 80%;
|
width: 80%;
|
||||||
@ -102,7 +102,10 @@ function getWebviewContent(pngSrc, refSrc, stdout, stderr) {
|
|||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
pre {
|
pre {
|
||||||
|
display: inline-block;
|
||||||
font-family: var(--vscode-editor-font-family);
|
font-family: var(--vscode-editor-font-family);
|
||||||
|
text-align: left;
|
||||||
|
width: 80%;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|