Enable join collection for control flow constructs

This commit is contained in:
Martin Haug 2022-02-28 14:36:02 +01:00
parent 9fde38a6f8
commit 4f09233bda
6 changed files with 163 additions and 59 deletions

63
src/eval/control.rs Normal file
View File

@ -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<TypError> for Control {
fn from(error: TypError) -> Self {
Self::Err(error)
}
}
impl From<Control> 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<Value>,
result_span: Span,
) -> EvalResult<Value> {
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,
}
}

View File

@ -139,7 +139,7 @@ impl Closure {
// Evaluate the body. // Evaluate the body.
let value = match self.body.eval(ctx, &mut scp) { let value = match self.body.eval(ctx, &mut scp) {
Err(Control::Return(value, _)) => value.unwrap_or_default(), Err(Control::Return(value, _, _)) => value,
other => other?, other => other?,
}; };

View File

@ -11,6 +11,7 @@ mod styles;
mod capture; mod capture;
mod class; mod class;
mod collapse; mod collapse;
mod control;
mod func; mod func;
mod layout; mod layout;
mod module; mod module;
@ -23,6 +24,7 @@ pub use array::*;
pub use capture::*; pub use capture::*;
pub use class::*; pub use class::*;
pub use collapse::*; pub use collapse::*;
pub use control::*;
pub use dict::*; pub use dict::*;
pub use func::*; pub use func::*;
pub use layout::*; pub use layout::*;
@ -35,7 +37,7 @@ pub use value::*;
use unicode_segmentation::UnicodeSegmentation; 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::geom::{Angle, Fractional, Length, Relative};
use crate::library; use crate::library;
use crate::syntax::ast::*; use crate::syntax::ast::*;
@ -55,40 +57,6 @@ pub trait Eval {
/// The result type for evaluating a syntactic construct. /// The result type for evaluating a syntactic construct.
pub type EvalResult<T> = Result<T, Control>; pub type EvalResult<T> = Result<T, Control>;
/// 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<Value>, Span),
/// Stop the execution because an error occurred.
Err(TypError),
}
impl From<TypError> for Control {
fn from(error: TypError) -> Self {
Self::Err(error)
}
}
impl From<Control> 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 { impl Eval for Markup {
type Output = Template; type Output = Template;
@ -337,12 +305,10 @@ impl Eval for BlockExpr {
let mut output = Value::None; let mut output = Value::None;
for expr in self.exprs() { for expr in self.exprs() {
let value = expr.eval(ctx, scp)?; output = join_result(output, expr.eval(ctx, scp), expr.span())?;
output = ops::join(output, value).at(expr.span())?;
} }
scp.exit(); scp.exit();
Ok(output) Ok(output)
} }
} }
@ -637,12 +603,14 @@ impl Eval for WhileExpr {
let condition = self.condition(); let condition = self.condition();
while condition.eval(ctx, scp)?.cast::<bool>().at(condition.span())? { while condition.eval(ctx, scp)?.cast::<bool>().at(condition.span())? {
let body = self.body(); let body = self.body();
let value = match body.eval(ctx, scp) { match join_result(output, body.eval(ctx, scp), body.span()) {
Err(Control::Break(_)) => break, Err(Control::Break(value, _)) => {
Err(Control::Continue(_)) => continue, output = value;
other => other?, break;
}; }
output = ops::join(output, value).at(body.span())?; Err(Control::Continue(value, _)) => output = value,
other => output = other?,
}
} }
Ok(output) Ok(output)
@ -663,13 +631,14 @@ impl Eval for ForExpr {
$(scp.top.def_mut(&$binding, $value);)* $(scp.top.def_mut(&$binding, $value);)*
let body = self.body(); let body = self.body();
let value = match body.eval(ctx, scp) { match join_result(output, body.eval(ctx, scp), body.span()) {
Err(Control::Break(_)) => break, Err(Control::Break(value, _)) => {
Err(Control::Continue(_)) => continue, output = value;
other => other?, break;
}; }
Err(Control::Continue(value, _)) => output = value,
output = ops::join(output, value).at(body.span())?; other => output = other?,
}
} }
scp.exit(); scp.exit();
@ -783,7 +752,7 @@ impl Eval for BreakExpr {
type Output = Value; type Output = Value;
fn eval(&self, _: &mut Context, _: &mut Scopes) -> EvalResult<Self::Output> { fn eval(&self, _: &mut Context, _: &mut Scopes) -> EvalResult<Self::Output> {
Err(Control::Break(self.span())) Err(Control::Break(Value::default(), self.span()))
} }
} }
@ -791,7 +760,7 @@ impl Eval for ContinueExpr {
type Output = Value; type Output = Value;
fn eval(&self, _: &mut Context, _: &mut Scopes) -> EvalResult<Self::Output> { fn eval(&self, _: &mut Context, _: &mut Scopes) -> EvalResult<Self::Output> {
Err(Control::Continue(self.span())) Err(Control::Continue(Value::default(), self.span()))
} }
} }
@ -799,8 +768,11 @@ impl Eval for ReturnExpr {
type Output = Value; type Output = Value;
fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult<Self::Output> { fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult<Self::Output> {
let value = self.body().map(|body| body.eval(ctx, scp)).transpose()?;
let explicit = value.is_some();
Err(Control::Return( Err(Control::Return(
self.body().map(|body| body.eval(ctx, scp)).transpose()?, value.unwrap_or_default(),
explicit,
self.span(), self.span(),
)) ))
} }

BIN
tests/ref/code/return.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -4,8 +4,8 @@
--- ---
// Test break. // Test break.
#let error = false
#let var = 0 #let var = 0
#let error = false
#for i in range(10) { #for i in range(10) {
var += i var += i
@ -15,18 +15,32 @@
} }
} }
#test(error, false)
#test(var, 21) #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. // Test continue.
#let x = 0
#let i = 0 #let i = 0
#let x = 0
#while x < 8 { #while x < 8 {
i += 1 i += 1
if mod(i, 3) == 0 { if mod(i, 3) == 0 {
continue continue
} }
@ -36,6 +50,20 @@
// If continue did not work, this would equal 10. // If continue did not work, this would equal 10.
#test(x, 12) #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. // Test break outside of loop.
@ -43,6 +71,7 @@
// Error: 3-8 cannot break outside of loop // Error: 3-8 cannot break outside of loop
break break
} }
#f() #f()
--- ---

View File

@ -2,12 +2,52 @@
// Ref: false // Ref: false
--- ---
// Test return with value.
#let f(x) = { #let f(x) = {
return x + 1 return x + 1
} }
#test(f(1), 2) #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. // Test return outside of function.