diff --git a/src/eval/mod.rs b/src/eval/mod.rs index eaa0b08fe..27c66095a 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -182,6 +182,7 @@ impl Eval for Spanned<&Expr> { Expr::Binary(v) => v.with_span(self.span).eval(ctx), Expr::Let(v) => v.with_span(self.span).eval(ctx), Expr::If(v) => v.with_span(self.span).eval(ctx), + Expr::For(v) => v.with_span(self.span).eval(ctx), } } } @@ -372,3 +373,38 @@ impl Eval for Spanned<&ExprIf> { Value::Error } } + +impl Eval for Spanned<&ExprFor> { + type Output = Value; + + fn eval(self, ctx: &mut EvalContext) -> Self::Output { + let iter = self.v.iter.eval(ctx); + if let Value::Array(array) = iter { + let mut output = match self.v.body.v { + Expr::Template(_) => Value::Template(vec![]), + _ => Value::None, + }; + + for value in array { + ctx.scopes.define(self.v.pat.v.as_str(), value); + let value = self.v.body.eval(ctx); + + if let Value::Template(prev) = &mut output { + if let Value::Template(new) = value { + prev.extend(new); + } + } + } + + return output; + } else if iter != Value::Error { + ctx.diag(error!( + self.v.iter.span, + "expected array, found {}", + iter.type_name(), + )); + } + + Value::Error + } +} diff --git a/src/parse/mod.rs b/src/parse/mod.rs index f731cd17e..ccf333b92 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -319,6 +319,7 @@ fn primary(p: &mut Parser) -> Option { // Keywords. Some(Token::Let) => return expr_let(p), Some(Token::If) => return expr_if(p), + Some(Token::For) => return expr_for(p), // No value. _ => { @@ -417,7 +418,27 @@ fn expr_if(p: &mut Parser) -> Option { expr_if } +/// Parse a for expression. +fn expr_for(p: &mut Parser) -> Option { + p.assert(Token::For); + let mut expr_for = None; + if let Some(pat) = p.span_if(ident) { + if p.expect(Token::In) { + if let Some(iter) = p.span_if(expr) { + if let Some(body) = p.span_if(body) { + expr_for = Some(Expr::For(ExprFor { + pat, + iter: Box::new(iter), + body: Box::new(body), + })); + } + } + } + } + + expr_for +} /// Parse an identifier. fn ident(p: &mut Parser) -> Option { diff --git a/src/syntax/expr.rs b/src/syntax/expr.rs index afeac9884..5a57cf9ac 100644 --- a/src/syntax/expr.rs +++ b/src/syntax/expr.rs @@ -48,6 +48,8 @@ pub enum Expr { Let(ExprLet), /// An if expression: `if x { y } else { z }`. If(ExprIf), + /// A for expression: `for x in y { z }`. + For(ExprFor), } impl Pretty for Expr { @@ -81,6 +83,7 @@ impl Pretty for Expr { Self::Call(v) => v.pretty(p), Self::Let(v) => v.pretty(p), Self::If(v) => v.pretty(p), + Self::For(v) => v.pretty(p), } } } @@ -519,6 +522,27 @@ impl Pretty for ExprIf { } } +/// A for expression: `for x in y { z }`. +#[derive(Debug, Clone, PartialEq)] +pub struct ExprFor { + /// The pattern to assign to. + pub pat: Spanned, + /// The expression to iterate over. + pub iter: SpanBox, + /// The expression to evaluate for each iteration. + pub body: SpanBox, +} + +impl Pretty for ExprFor { + fn pretty(&self, p: &mut Printer) { + p.push_str("#for "); + p.push_str(&self.pat.v); + p.push_str(" #in "); + self.iter.v.pretty(p); + p.push_str(" "); + self.body.v.pretty(p); + } +} #[cfg(test)] mod tests { use super::super::tests::test_pretty; @@ -560,6 +584,7 @@ mod tests { // Control flow. test_pretty("#let x = 1+2", "#let x = 1 + 2"); test_pretty("#if x [y] #else [z]", "#if x [y] #else [z]"); + test_pretty("#for x #in y {z}", "#for x #in y {z}"); } #[test]