diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 2023004f1..a8c6c688f 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -390,6 +390,9 @@ impl Eval for Expr { Self::For(v) => v.eval(ctx), Self::Import(v) => v.eval(ctx), Self::Include(v) => v.eval(ctx), + Self::Break(v) => v.eval(ctx), + Self::Continue(v) => v.eval(ctx), + Self::Return(v) => v.eval(ctx), } } } @@ -905,6 +908,30 @@ impl Eval for IncludeExpr { } } +impl Eval for BreakExpr { + type Output = Value; + + fn eval(&self, _: &mut EvalContext) -> TypResult { + Err("break is not yet implemented").at(self.span()) + } +} + +impl Eval for ContinueExpr { + type Output = Value; + + fn eval(&self, _: &mut EvalContext) -> TypResult { + Err("continue is not yet implemented").at(self.span()) + } +} + +impl Eval for ReturnExpr { + type Output = Value; + + fn eval(&self, _: &mut EvalContext) -> TypResult { + Err("return is not yet implemented").at(self.span()) + } +} + /// Try to mutably access the value an expression points to. /// /// This only works if the expression is a valid lvalue. diff --git a/src/parse/incremental.rs b/src/parse/incremental.rs index 2edb84ba5..9dd5bec19 100644 --- a/src/parse/incremental.rs +++ b/src/parse/incremental.rs @@ -548,7 +548,10 @@ impl NodeKind { | Self::ShowExpr | Self::WrapExpr | Self::ImportExpr - | Self::IncludeExpr => SuccessionRule::AtomicPrimary, + | Self::IncludeExpr + | Self::BreakExpr + | Self::ContinueExpr + | Self::ReturnExpr => SuccessionRule::AtomicPrimary, // This element always has to remain in the same column so better // reparse the whole parent. diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 67e35a5aa..a9839ed67 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -405,6 +405,9 @@ fn primary(p: &mut Parser, atomic: bool) -> ParseResult { Some(NodeKind::For) => for_expr(p), Some(NodeKind::Import) => import_expr(p), Some(NodeKind::Include) => include_expr(p), + Some(NodeKind::Break) => break_expr(p), + Some(NodeKind::Continue) => continue_expr(p), + Some(NodeKind::Return) => return_expr(p), Some(NodeKind::Error(_, _)) => { p.eat(); @@ -833,6 +836,33 @@ fn include_expr(p: &mut Parser) -> ParseResult { }) } +/// Parse a break expression. +fn break_expr(p: &mut Parser) -> ParseResult { + p.perform(NodeKind::BreakExpr, |p| { + p.eat_assert(&NodeKind::Break); + Ok(()) + }) +} + +/// Parse a continue expression. +fn continue_expr(p: &mut Parser) -> ParseResult { + p.perform(NodeKind::ContinueExpr, |p| { + p.eat_assert(&NodeKind::Continue); + Ok(()) + }) +} + +/// Parse a return expression. +fn return_expr(p: &mut Parser) -> ParseResult { + p.perform(NodeKind::ReturnExpr, |p| { + p.eat_assert(&NodeKind::Return); + if !p.eof() { + expr(p)?; + } + Ok(()) + }) +} + /// Parse an identifier. fn ident(p: &mut Parser) -> ParseResult { match p.peek() { diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs index 6732aa40e..13c639f98 100644 --- a/src/syntax/ast.rs +++ b/src/syntax/ast.rs @@ -231,6 +231,12 @@ pub enum Expr { Import(ImportExpr), /// An include expression: `include "chapter1.typ"`. Include(IncludeExpr), + /// A break expression: `break`. + Break(BreakExpr), + /// A continue expression: `continue`. + Continue(ContinueExpr), + /// A return expression: `return`. + Return(ReturnExpr), } impl TypedNode for Expr { @@ -256,6 +262,9 @@ impl TypedNode for Expr { NodeKind::ForExpr => node.cast().map(Self::For), NodeKind::ImportExpr => node.cast().map(Self::Import), NodeKind::IncludeExpr => node.cast().map(Self::Include), + NodeKind::BreakExpr => node.cast().map(Self::Break), + NodeKind::ContinueExpr => node.cast().map(Self::Continue), + NodeKind::ReturnExpr => node.cast().map(Self::Return), _ => node.cast().map(Self::Lit), } } @@ -283,6 +292,9 @@ impl TypedNode for Expr { Self::For(v) => v.as_red(), Self::Import(v) => v.as_red(), Self::Include(v) => v.as_red(), + Self::Break(v) => v.as_red(), + Self::Continue(v) => v.as_red(), + Self::Return(v) => v.as_red(), } } } @@ -1041,6 +1053,28 @@ impl IncludeExpr { } } +node! { + /// A break expression: `break`. + BreakExpr +} + +node! { + /// A continue expression: `continue`. + ContinueExpr +} + +node! { + /// A return expression: `return x + 1`. + ReturnExpr +} + +impl ReturnExpr { + /// The expression to return. + pub fn body(&self) -> Option { + self.0.cast_last_child() + } +} + node! { /// An identifier. Ident: NodeKind::Ident(_) diff --git a/src/syntax/highlight.rs b/src/syntax/highlight.rs index c399b487b..315e8f17e 100644 --- a/src/syntax/highlight.rs +++ b/src/syntax/highlight.rs @@ -223,6 +223,9 @@ impl Category { NodeKind::ImportExpr => None, NodeKind::ImportItems => None, NodeKind::IncludeExpr => None, + NodeKind::BreakExpr => None, + NodeKind::ContinueExpr => None, + NodeKind::ReturnExpr => None, } } diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs index 74a642cac..9b606e0e4 100644 --- a/src/syntax/mod.rs +++ b/src/syntax/mod.rs @@ -689,6 +689,12 @@ pub enum NodeKind { ImportItems, /// An include expression: `include "chapter1.typ"`. IncludeExpr, + /// A break expression: `break`. + BreakExpr, + /// A continue expression: `continue`. + ContinueExpr, + /// A return expression: `return x + 1`. + ReturnExpr, /// A line comment, two slashes followed by inner contents, terminated with /// a newline: `//\n`. LineComment, @@ -755,7 +761,7 @@ impl NodeKind { } } - /// Whether this token appears in Markup. + /// Which mode this token can appear in, in both if `None`. pub fn mode(&self) -> Option { match self { Self::Markup(_) @@ -780,6 +786,9 @@ impl NodeKind { | Self::Block | Self::Ident(_) | Self::LetExpr + | Self::SetExpr + | Self::ShowExpr + | Self::WrapExpr | Self::IfExpr | Self::WhileExpr | Self::ForExpr @@ -897,6 +906,9 @@ impl NodeKind { Self::ImportExpr => "`import` expression", Self::ImportItems => "import items", Self::IncludeExpr => "`include` expression", + Self::BreakExpr => "`break` expression", + Self::ContinueExpr => "`continue` expression", + Self::ReturnExpr => "`return` expression", Self::LineComment => "line comment", Self::BlockComment => "block comment", Self::Error(_, _) => "parse error", diff --git a/src/syntax/pretty.rs b/src/syntax/pretty.rs index f8285f0c0..e8110262f 100644 --- a/src/syntax/pretty.rs +++ b/src/syntax/pretty.rs @@ -234,6 +234,9 @@ impl Pretty for Expr { Self::For(v) => v.pretty(p), Self::Import(v) => v.pretty(p), Self::Include(v) => v.pretty(p), + Self::Break(v) => v.pretty(p), + Self::Continue(v) => v.pretty(p), + Self::Return(v) => v.pretty(p), } } } @@ -547,6 +550,28 @@ impl Pretty for IncludeExpr { } } +impl Pretty for BreakExpr { + fn pretty(&self, p: &mut Printer) { + p.push_str("break"); + } +} + +impl Pretty for ContinueExpr { + fn pretty(&self, p: &mut Printer) { + p.push_str("continue"); + } +} + +impl Pretty for ReturnExpr { + fn pretty(&self, p: &mut Printer) { + p.push_str("return"); + if let Some(body) = self.body() { + p.push(' '); + body.pretty(p); + } + } +} + impl Pretty for Ident { fn pretty(&self, p: &mut Printer) { p.push_str(self); @@ -681,5 +706,9 @@ mod tests { roundtrip("#for k, x in y {z}"); roundtrip("#import * from \"file.typ\""); roundtrip("#include \"chapter1.typ\""); + roundtrip("{break}"); + roundtrip("{continue}"); + roundtrip("{return}"); + roundtrip("{return x + 1}"); } } diff --git a/tests/typ/code/break-continue.typ b/tests/typ/code/break-continue.typ new file mode 100644 index 000000000..2b38cf77c --- /dev/null +++ b/tests/typ/code/break-continue.typ @@ -0,0 +1,14 @@ +// Test break and continue in loops. +// Ref: false + +--- +#for i in range(10) { + if i > 5 { + // Error: 5-10 break is not yet implemented + break + } +} + +--- +// Error: 1-10 unexpected keyword `continue` +#continue diff --git a/tests/typ/code/return.typ b/tests/typ/code/return.typ new file mode 100644 index 000000000..bd30c46f4 --- /dev/null +++ b/tests/typ/code/return.typ @@ -0,0 +1,10 @@ +// Test return out of functions. +// Ref: false + +--- +#let f(x) = { + // Error: 3-15 return is not yet implemented + return x + 1 +} + +#f(1)