mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
Enable join collection for control flow constructs
This commit is contained in:
parent
9fde38a6f8
commit
4f09233bda
63
src/eval/control.rs
Normal file
63
src/eval/control.rs
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
@ -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?,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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
BIN
tests/ref/code/return.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.9 KiB |
@ -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()
|
||||||
|
|
||||||
---
|
---
|
||||||
|
@ -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.
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user