diff options
| author | Laurenz <laurmaedje@gmail.com> | 2021-02-24 21:29:32 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2021-02-24 21:29:32 +0100 |
| commit | f084165eabbb8ad1b8e8969078fce89070ab4d96 (patch) | |
| tree | 6003822cc646ecf0ba6a3070c87ab283503a4c3b /src | |
| parent | dae3dad5407e49715736a2a3d8735e65027e6c11 (diff) | |
While loops 🔁
Diffstat (limited to 'src')
| -rw-r--r-- | src/eval/mod.rs | 58 | ||||
| -rw-r--r-- | src/eval/value.rs | 6 | ||||
| -rw-r--r-- | src/parse/mod.rs | 22 | ||||
| -rw-r--r-- | src/pretty.rs | 13 | ||||
| -rw-r--r-- | src/syntax/expr.rs | 39 | ||||
| -rw-r--r-- | src/syntax/visit.rs | 6 |
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); |
