summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2021-02-24 21:29:32 +0100
committerLaurenz <laurmaedje@gmail.com>2021-02-24 21:29:32 +0100
commitf084165eabbb8ad1b8e8969078fce89070ab4d96 (patch)
tree6003822cc646ecf0ba6a3070c87ab283503a4c3b /src
parentdae3dad5407e49715736a2a3d8735e65027e6c11 (diff)
While loops 🔁
Diffstat (limited to 'src')
-rw-r--r--src/eval/mod.rs58
-rw-r--r--src/eval/value.rs6
-rw-r--r--src/parse/mod.rs22
-rw-r--r--src/pretty.rs13
-rw-r--r--src/syntax/expr.rs39
-rw-r--r--src/syntax/visit.rs6
6 files changed, 117 insertions, 27 deletions
diff --git a/src/eval/mod.rs b/src/eval/mod.rs
index 596ceb50..3cf97860 100644
--- a/src/eval/mod.rs
+++ b/src/eval/mod.rs
@@ -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;
diff --git a/src/eval/value.rs b/src/eval/value.rs
index 2a91cf8a..d910155a 100644
--- a/src/eval/value.rs
+++ b/src/eval/value.rs
@@ -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 }
diff --git a/src/parse/mod.rs b/src/parse/mod.rs
index 8d6958cf..d4cf90c7 100644
--- a/src/parse/mod.rs
+++ b/src/parse/mod.rs
@@ -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();
diff --git a/src/pretty.rs b/src/pretty.rs
index de910b99..0899824a 100644
--- a/src/pretty.rs
+++ b/src/pretty.rs
@@ -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]
diff --git a/src/syntax/expr.rs b/src/syntax/expr.rs
index 5b37bb56..638d9dd3 100644
--- a/src/syntax/expr.rs
+++ b/src/syntax/expr.rs
@@ -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),
}
diff --git a/src/syntax/visit.rs b/src/syntax/visit.rs
index 2d3c683f..1bf260c7 100644
--- a/src/syntax/visit.rs
+++ b/src/syntax/visit.rs
@@ -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);