diff --git a/src/eval/control.rs b/src/eval/control.rs new file mode 100644 index 000000000..b310bfb84 --- /dev/null +++ b/src/eval/control.rs @@ -0,0 +1,63 @@ +use super::{ops, EvalResult, Value}; +use crate::diag::{At, Error, TypError}; +use crate::syntax::Span; + +/// A control flow event that occurred during evaluation. +#[derive(Clone, Debug, PartialEq)] +pub enum Control { + /// Stop iteration in a loop. + Break(Value, Span), + /// Skip the remainder of the current iteration in a loop. + Continue(Value, Span), + /// Stop execution of a function early, returning a value. The bool + /// indicates whether this was an explicit return with value. + Return(Value, bool, Span), + /// Stop the execution because an error occurred. + Err(TypError), +} + +impl From for Control { + fn from(error: TypError) -> Self { + Self::Err(error) + } +} + +impl From for TypError { + fn from(control: Control) -> Self { + match control { + Control::Break(_, span) => Error::boxed(span, "cannot break outside of loop"), + Control::Continue(_, span) => { + Error::boxed(span, "cannot continue outside of loop") + } + Control::Return(_, _, span) => { + Error::boxed(span, "cannot return outside of function") + } + Control::Err(e) => e, + } + } +} + +/// Join a value with an evaluated result. +pub(super) fn join_result( + prev: Value, + result: EvalResult, + result_span: Span, +) -> EvalResult { + match result { + Ok(value) => Ok(ops::join(prev, value).at(result_span)?), + Err(Control::Break(value, span)) => Err(Control::Break( + ops::join(prev, value).at(result_span)?, + span, + )), + Err(Control::Continue(value, span)) => Err(Control::Continue( + ops::join(prev, value).at(result_span)?, + span, + )), + Err(Control::Return(value, false, span)) => Err(Control::Return( + ops::join(prev, value).at(result_span)?, + false, + span, + )), + other => other, + } +} diff --git a/src/eval/func.rs b/src/eval/func.rs index a7f2a209c..451dcbbb7 100644 --- a/src/eval/func.rs +++ b/src/eval/func.rs @@ -139,7 +139,7 @@ impl Closure { // Evaluate the body. let value = match self.body.eval(ctx, &mut scp) { - Err(Control::Return(value, _)) => value.unwrap_or_default(), + Err(Control::Return(value, _, _)) => value, other => other?, }; diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 3686d665e..380e9e9d1 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -11,6 +11,7 @@ mod styles; mod capture; mod class; mod collapse; +mod control; mod func; mod layout; mod module; @@ -23,6 +24,7 @@ pub use array::*; pub use capture::*; pub use class::*; pub use collapse::*; +pub use control::*; pub use dict::*; pub use func::*; pub use layout::*; @@ -35,7 +37,7 @@ pub use value::*; use unicode_segmentation::UnicodeSegmentation; -use crate::diag::{At, Error, StrResult, Trace, Tracepoint, TypError, TypResult}; +use crate::diag::{At, Error, StrResult, Trace, Tracepoint, TypResult}; use crate::geom::{Angle, Fractional, Length, Relative}; use crate::library; use crate::syntax::ast::*; @@ -55,40 +57,6 @@ pub trait Eval { /// The result type for evaluating a syntactic construct. pub type EvalResult = Result; -/// A control flow event that occurred during evaluation. -#[derive(Clone, Debug, PartialEq)] -pub enum Control { - /// Stop iteration in a loop. - Break(Span), - /// Skip the remainder of the current iteration in a loop. - Continue(Span), - /// Stop execution of a function early, optionally returning a value. - Return(Option, Span), - /// Stop the execution because an error occurred. - Err(TypError), -} - -impl From for Control { - fn from(error: TypError) -> Self { - Self::Err(error) - } -} - -impl From for TypError { - fn from(control: Control) -> Self { - match control { - Control::Break(span) => Error::boxed(span, "cannot break outside of loop"), - Control::Continue(span) => { - Error::boxed(span, "cannot continue outside of loop") - } - Control::Return(_, span) => { - Error::boxed(span, "cannot return outside of function") - } - Control::Err(e) => e, - } - } -} - impl Eval for Markup { type Output = Template; @@ -337,12 +305,10 @@ impl Eval for BlockExpr { let mut output = Value::None; for expr in self.exprs() { - let value = expr.eval(ctx, scp)?; - output = ops::join(output, value).at(expr.span())?; + output = join_result(output, expr.eval(ctx, scp), expr.span())?; } scp.exit(); - Ok(output) } } @@ -637,12 +603,14 @@ impl Eval for WhileExpr { let condition = self.condition(); while condition.eval(ctx, scp)?.cast::().at(condition.span())? { let body = self.body(); - let value = match body.eval(ctx, scp) { - Err(Control::Break(_)) => break, - Err(Control::Continue(_)) => continue, - other => other?, - }; - output = ops::join(output, value).at(body.span())?; + match join_result(output, body.eval(ctx, scp), body.span()) { + Err(Control::Break(value, _)) => { + output = value; + break; + } + Err(Control::Continue(value, _)) => output = value, + other => output = other?, + } } Ok(output) @@ -663,13 +631,14 @@ impl Eval for ForExpr { $(scp.top.def_mut(&$binding, $value);)* let body = self.body(); - let value = match body.eval(ctx, scp) { - Err(Control::Break(_)) => break, - Err(Control::Continue(_)) => continue, - other => other?, - }; - - output = ops::join(output, value).at(body.span())?; + match join_result(output, body.eval(ctx, scp), body.span()) { + Err(Control::Break(value, _)) => { + output = value; + break; + } + Err(Control::Continue(value, _)) => output = value, + other => output = other?, + } } scp.exit(); @@ -783,7 +752,7 @@ impl Eval for BreakExpr { type Output = Value; fn eval(&self, _: &mut Context, _: &mut Scopes) -> EvalResult { - Err(Control::Break(self.span())) + Err(Control::Break(Value::default(), self.span())) } } @@ -791,7 +760,7 @@ impl Eval for ContinueExpr { type Output = Value; fn eval(&self, _: &mut Context, _: &mut Scopes) -> EvalResult { - Err(Control::Continue(self.span())) + Err(Control::Continue(Value::default(), self.span())) } } @@ -799,8 +768,11 @@ impl Eval for ReturnExpr { type Output = Value; fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult { + let value = self.body().map(|body| body.eval(ctx, scp)).transpose()?; + let explicit = value.is_some(); Err(Control::Return( - self.body().map(|body| body.eval(ctx, scp)).transpose()?, + value.unwrap_or_default(), + explicit, self.span(), )) } diff --git a/tests/ref/code/return.png b/tests/ref/code/return.png new file mode 100644 index 000000000..504108872 Binary files /dev/null and b/tests/ref/code/return.png differ diff --git a/tests/typ/code/break-continue.typ b/tests/typ/code/break-continue.typ index e54651f19..60dac44d3 100644 --- a/tests/typ/code/break-continue.typ +++ b/tests/typ/code/break-continue.typ @@ -4,8 +4,8 @@ --- // Test break. -#let error = false #let var = 0 +#let error = false #for i in range(10) { var += i @@ -15,18 +15,32 @@ } } -#test(error, false) #test(var, 21) +#test(error, false) + +--- +// Test joining with break. + +#let i = 0 +#let x = while true { + i += 1 + str(i) + if i >= 5 { + "." + break + } +} + +#test(x, "12345.") --- // Test continue. -#let x = 0 #let i = 0 +#let x = 0 #while x < 8 { i += 1 - if mod(i, 3) == 0 { continue } @@ -36,6 +50,20 @@ // If continue did not work, this would equal 10. #test(x, 12) +--- +// Test joining with continue. + +#let x = for i in range(5) { + "a" + if mod(i, 3) == 0 { + "_" + continue + } + str(i) +} + +#test(x, "a_a1a2a_a4") + --- // Test break outside of loop. @@ -43,6 +71,7 @@ // Error: 3-8 cannot break outside of loop break } + #f() --- diff --git a/tests/typ/code/return.typ b/tests/typ/code/return.typ index 46ff190c0..9ee3aed75 100644 --- a/tests/typ/code/return.typ +++ b/tests/typ/code/return.typ @@ -2,12 +2,52 @@ // Ref: false --- +// Test return with value. #let f(x) = { return x + 1 } #test(f(1), 2) +--- +// Test return with joining. + +#let f(x) = { + "a" + if x == 0 { + return "b" + } else if x == 1 { + "c" + } else { + "d" + return + "e" + } +} + +#test(f(0), "b") +#test(f(1), "ac") +#test(f(2), "ad") + +--- +// Test return with joining and template. +// Ref: true + +#let f(text, caption: none) = { + text + if caption == none { + [\.] + return + } + [, ] + emph(caption) + [\.] +} + +#f(caption: [with caption])[My figure] + +#f[My other figure] + --- // Test return outside of function.