diff --git a/src/eval/array.rs b/src/eval/array.rs index 9abab8cf2..86347106b 100644 --- a/src/eval/array.rs +++ b/src/eval/array.rs @@ -3,7 +3,7 @@ use std::fmt::{self, Debug, Formatter, Write}; use std::ops::{Add, AddAssign}; use std::sync::Arc; -use super::{Args, Func, Value}; +use super::{ops, Args, Func, Value}; use crate::diag::{At, StrResult, TypResult}; use crate::syntax::Spanned; use crate::util::ArcExt; @@ -171,18 +171,14 @@ impl Array { let mut result = Value::None; for (i, value) in self.iter().cloned().enumerate() { if i > 0 { - if i + 1 == len { - if let Some(last) = last.take() { - result = result.join(last)?; - } else { - result = result.join(sep.clone())?; - } + if i + 1 == len && last.is_some() { + result = ops::join(result, last.take().unwrap())?; } else { - result = result.join(sep.clone())?; + result = ops::join(result, sep.clone())?; } } - result = result.join(value)?; + result = ops::join(result, value)?; } Ok(result) diff --git a/src/eval/control.rs b/src/eval/control.rs deleted file mode 100644 index 166676d49..000000000 --- a/src/eval/control.rs +++ /dev/null @@ -1,65 +0,0 @@ -use super::{ops, EvalResult, Value}; -use crate::diag::{At, 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!(span, "cannot break outside of loop") - } - Control::Continue(_, span) => { - error!(span, "cannot continue outside of loop") - } - Control::Return(_, _, span) => { - error!(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 73f2cac92..4c5761ab6 100644 --- a/src/eval/func.rs +++ b/src/eval/func.rs @@ -2,7 +2,7 @@ use std::fmt::{self, Debug, Formatter}; use std::hash::{Hash, Hasher}; use std::sync::Arc; -use super::{Args, Control, Eval, Scope, Scopes, Value}; +use super::{Args, Eval, Flow, Scope, Scopes, Value}; use crate::diag::{StrResult, TypResult}; use crate::model::{Content, NodeId, StyleMap}; use crate::syntax::ast::Expr; @@ -210,11 +210,19 @@ impl Closure { scp.top.def_mut(sink, args.take()); } + // Backup the old control flow state. + let prev_flow = ctx.flow.take(); + // Evaluate the body. - let value = match self.body.eval(ctx, &mut scp) { - Err(Control::Return(value, _, _)) => value, - other => other?, - }; + let mut value = self.body.eval(ctx, &mut scp)?; + + // Handle control flow. + match std::mem::replace(&mut ctx.flow, prev_flow) { + Some(Flow::Return(_, Some(explicit))) => value = explicit, + Some(Flow::Return(_, None)) => {} + Some(flow) => return Err(flow.forbidden())?, + None => {} + } Ok(value) } diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 677fa4aa1..0021b93ca 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -9,10 +9,8 @@ mod value; mod args; mod capture; -mod control; mod func; pub mod methods; -mod module; pub mod ops; mod raw; mod scope; @@ -22,10 +20,8 @@ pub use self::str::*; pub use args::*; pub use array::*; pub use capture::*; -pub use control::*; pub use dict::*; pub use func::*; -pub use module::*; pub use raw::*; pub use scope::*; pub use value::*; @@ -35,10 +31,11 @@ use std::collections::BTreeMap; use parking_lot::{MappedRwLockWriteGuard, RwLockWriteGuard}; use unicode_segmentation::UnicodeSegmentation; -use crate::diag::{At, StrResult, Trace, Tracepoint, TypResult}; +use crate::diag::{At, StrResult, Trace, Tracepoint, TypError, TypResult}; use crate::geom::{Angle, Em, Fraction, Length, Ratio}; use crate::library; use crate::model::{Content, Pattern, Recipe, StyleEntry, StyleMap}; +use crate::source::{SourceId, SourceStore}; use crate::syntax::ast::*; use crate::syntax::{Span, Spanned}; use crate::util::EcoString; @@ -50,16 +47,60 @@ pub trait Eval { type Output; /// Evaluate the expression to the output value. - fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult; + fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> TypResult; } -/// The result type for evaluating a syntactic construct. -pub type EvalResult = Result; +/// An evaluated module, ready for importing or layouting. +#[derive(Debug, Clone)] +pub struct Module { + /// The top-level definitions that were bound in this module. + pub scope: Scope, + /// The module's layoutable contents. + pub content: Content, + /// The source file revisions this module depends on. + pub deps: Vec<(SourceId, usize)>, +} + +impl Module { + /// Whether the module is still valid for the given sources. + pub fn valid(&self, sources: &SourceStore) -> bool { + self.deps.iter().all(|&(id, rev)| rev == sources.get(id).rev()) + } +} + +/// A control flow event that occurred during evaluation. +#[derive(Debug, Clone, PartialEq)] +pub enum Flow { + /// 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 an explicit + /// value. + Return(Span, Option), +} + +impl Flow { + /// Return an error stating that this control flow is forbidden. + pub fn forbidden(&self) -> TypError { + match *self { + Self::Break(span) => { + error!(span, "cannot break outside of loop") + } + Self::Continue(span) => { + error!(span, "cannot continue outside of loop") + } + Self::Return(span, _) => { + error!(span, "cannot return outside of function") + } + } + } +} impl Eval for Markup { type Output = Content; - fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult { + fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> TypResult { eval_markup(ctx, scp, &mut self.nodes()) } } @@ -69,17 +110,25 @@ fn eval_markup( ctx: &mut Context, scp: &mut Scopes, nodes: &mut impl Iterator, -) -> EvalResult { +) -> TypResult { let mut seq = Vec::with_capacity(nodes.size_hint().1.unwrap_or_default()); while let Some(node) = nodes.next() { seq.push(match node { MarkupNode::Expr(Expr::Set(set)) => { let styles = set.eval(ctx, scp)?; + if ctx.flow.is_some() { + break; + } + eval_markup(ctx, scp, nodes)?.styled_with_map(styles) } MarkupNode::Expr(Expr::Show(show)) => { let recipe = show.eval(ctx, scp)?; + if ctx.flow.is_some() { + break; + } + eval_markup(ctx, scp, nodes)? .styled_with_entry(StyleEntry::Recipe(recipe).into()) } @@ -88,8 +137,13 @@ fn eval_markup( scp.top.def_mut(wrap.binding().take(), tail); wrap.body().eval(ctx, scp)?.display() } + _ => node.eval(ctx, scp)?, }); + + if ctx.flow.is_some() { + break; + } } Ok(Content::sequence(seq)) @@ -98,7 +152,7 @@ fn eval_markup( impl Eval for MarkupNode { type Output = Content; - fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult { + fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> TypResult { Ok(match self { Self::Space => Content::Space, Self::Parbreak => Content::Parbreak, @@ -120,7 +174,7 @@ impl Eval for MarkupNode { impl Eval for StrongNode { type Output = Content; - fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult { + fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> TypResult { Ok(Content::show(library::text::StrongNode( self.body().eval(ctx, scp)?, ))) @@ -130,7 +184,7 @@ impl Eval for StrongNode { impl Eval for EmphNode { type Output = Content; - fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult { + fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> TypResult { Ok(Content::show(library::text::EmphNode( self.body().eval(ctx, scp)?, ))) @@ -140,7 +194,7 @@ impl Eval for EmphNode { impl Eval for RawNode { type Output = Content; - fn eval(&self, _: &mut Context, _: &mut Scopes) -> EvalResult { + fn eval(&self, _: &mut Context, _: &mut Scopes) -> TypResult { let content = Content::show(library::text::RawNode { text: self.text.clone(), block: self.block, @@ -155,7 +209,7 @@ impl Eval for RawNode { impl Eval for MathNode { type Output = Content; - fn eval(&self, _: &mut Context, _: &mut Scopes) -> EvalResult { + fn eval(&self, _: &mut Context, _: &mut Scopes) -> TypResult { Ok(Content::show(library::math::MathNode { formula: self.formula.clone(), display: self.display, @@ -166,7 +220,7 @@ impl Eval for MathNode { impl Eval for HeadingNode { type Output = Content; - fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult { + fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> TypResult { Ok(Content::show(library::structure::HeadingNode { body: self.body().eval(ctx, scp)?, level: self.level(), @@ -177,7 +231,7 @@ impl Eval for HeadingNode { impl Eval for ListNode { type Output = Content; - fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult { + fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> TypResult { Ok(Content::Item(library::structure::ListItem { kind: library::structure::UNORDERED, number: None, @@ -189,7 +243,7 @@ impl Eval for ListNode { impl Eval for EnumNode { type Output = Content; - fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult { + fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> TypResult { Ok(Content::Item(library::structure::ListItem { kind: library::structure::ORDERED, number: self.number(), @@ -201,7 +255,14 @@ impl Eval for EnumNode { impl Eval for Expr { type Output = Value; - fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult { + fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> TypResult { + let forbidden = |name| { + error!( + self.span(), + "{} is only allowed directly in code and content blocks", name + ) + }; + match self { Self::Lit(v) => v.eval(ctx, scp), Self::Ident(v) => v.eval(ctx, scp), @@ -217,11 +278,9 @@ impl Eval for Expr { Self::Unary(v) => v.eval(ctx, scp), Self::Binary(v) => v.eval(ctx, scp), Self::Let(v) => v.eval(ctx, scp), - Self::Set(_) | Self::Show(_) | Self::Wrap(_) => { - Err("set, show and wrap are only allowed directly in markup") - .at(self.span()) - .map_err(Into::into) - } + Self::Set(_) => Err(forbidden("set")), + Self::Show(_) => Err(forbidden("show")), + Self::Wrap(_) => Err(forbidden("wrap")), Self::If(v) => v.eval(ctx, scp), Self::While(v) => v.eval(ctx, scp), Self::For(v) => v.eval(ctx, scp), @@ -237,7 +296,7 @@ impl Eval for Expr { impl Eval for Lit { type Output = Value; - fn eval(&self, _: &mut Context, _: &mut Scopes) -> EvalResult { + fn eval(&self, _: &mut Context, _: &mut Scopes) -> TypResult { Ok(match self.kind() { LitKind::None => Value::None, LitKind::Auto => Value::Auto, @@ -259,7 +318,7 @@ impl Eval for Lit { impl Eval for Ident { type Output = Value; - fn eval(&self, _: &mut Context, scp: &mut Scopes) -> EvalResult { + fn eval(&self, _: &mut Context, scp: &mut Scopes) -> TypResult { match scp.get(self) { Some(slot) => Ok(slot.read().clone()), None => bail!(self.span(), "unknown variable"), @@ -270,23 +329,75 @@ impl Eval for Ident { impl Eval for CodeBlock { type Output = Value; - fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult { + fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> TypResult { scp.enter(); - - let mut output = Value::None; - for expr in self.exprs() { - output = join_result(output, expr.eval(ctx, scp), expr.span())?; - } - + let output = eval_code(ctx, scp, &mut self.exprs())?; scp.exit(); Ok(output) } } +/// Evaluate a stream of expressions. +fn eval_code( + ctx: &mut Context, + scp: &mut Scopes, + exprs: &mut impl Iterator, +) -> TypResult { + let mut output = Value::None; + + while let Some(expr) = exprs.next() { + let span = expr.span(); + let value = match expr { + Expr::Set(set) => { + let styles = set.eval(ctx, scp)?; + if ctx.flow.is_some() { + break; + } + + let tail = to_content(eval_code(ctx, scp, exprs)?).at(span)?; + Value::Content(tail.styled_with_map(styles)) + } + Expr::Show(show) => { + let recipe = show.eval(ctx, scp)?; + let entry = StyleEntry::Recipe(recipe).into(); + if ctx.flow.is_some() { + break; + } + + let tail = to_content(eval_code(ctx, scp, exprs)?).at(span)?; + Value::Content(tail.styled_with_entry(entry)) + } + Expr::Wrap(wrap) => { + let tail = to_content(eval_code(ctx, scp, exprs)?).at(span)?; + scp.top.def_mut(wrap.binding().take(), Value::Content(tail)); + wrap.body().eval(ctx, scp)? + } + + _ => expr.eval(ctx, scp)?, + }; + + output = ops::join(output, value).at(span)?; + + if ctx.flow.is_some() { + break; + } + } + + Ok(output) +} + +/// Extract content from a value. +fn to_content(value: Value) -> StrResult { + let ty = value.type_name(); + value + .cast() + .map_err(|_| format!("expected remaining block to yield content, found {ty}")) +} + impl Eval for ContentBlock { type Output = Content; - fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult { + fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> TypResult { scp.enter(); let content = self.body().eval(ctx, scp)?; scp.exit(); @@ -297,7 +408,7 @@ impl Eval for ContentBlock { impl Eval for GroupExpr { type Output = Value; - fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult { + fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> TypResult { self.expr().eval(ctx, scp) } } @@ -305,7 +416,7 @@ impl Eval for GroupExpr { impl Eval for ArrayExpr { type Output = Array; - fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult { + fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> TypResult { let items = self.items(); let mut vec = Vec::with_capacity(items.size_hint().0); @@ -327,7 +438,7 @@ impl Eval for ArrayExpr { impl Eval for DictExpr { type Output = Dict; - fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult { + fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> TypResult { let mut map = BTreeMap::new(); for item in self.items() { @@ -357,7 +468,7 @@ impl Eval for DictExpr { impl Eval for UnaryExpr { type Output = Value; - fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult { + fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> TypResult { let value = self.expr().eval(ctx, scp)?; let result = match self.op() { UnOp::Pos => ops::pos(value), @@ -371,7 +482,7 @@ impl Eval for UnaryExpr { impl Eval for BinaryExpr { type Output = Value; - fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult { + fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> TypResult { match self.op() { BinOp::Add => self.apply(ctx, scp, ops::add), BinOp::Sub => self.apply(ctx, scp, ops::sub), @@ -403,7 +514,7 @@ impl BinaryExpr { ctx: &mut Context, scp: &mut Scopes, op: fn(Value, Value) -> StrResult, - ) -> EvalResult { + ) -> TypResult { let lhs = self.lhs().eval(ctx, scp)?; // Short-circuit boolean operations. @@ -423,7 +534,7 @@ impl BinaryExpr { ctx: &mut Context, scp: &mut Scopes, op: fn(Value, Value) -> StrResult, - ) -> EvalResult { + ) -> TypResult { let rhs = self.rhs().eval(ctx, scp)?; let lhs = self.lhs(); let mut location = lhs.access(ctx, scp)?; @@ -436,7 +547,7 @@ impl BinaryExpr { impl Eval for FieldAccess { type Output = Value; - fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult { + fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> TypResult { let object = self.object().eval(ctx, scp)?; let span = self.field().span(); let field = self.field().take(); @@ -462,7 +573,7 @@ impl Eval for FieldAccess { impl Eval for FuncCall { type Output = Value; - fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult { + fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> TypResult { let callee = self.callee().eval(ctx, scp)?; let args = self.args().eval(ctx, scp)?; @@ -486,7 +597,7 @@ impl Eval for FuncCall { impl Eval for MethodCall { type Output = Value; - fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult { + fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> TypResult { let span = self.span(); let method = self.method(); let point = || Tracepoint::Call(Some(method.to_string())); @@ -507,7 +618,7 @@ impl Eval for MethodCall { impl Eval for CallArgs { type Output = Args; - fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult { + fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> TypResult { let mut items = Vec::new(); for arg in self.items() { @@ -559,7 +670,7 @@ impl Eval for CallArgs { impl Eval for ClosureExpr { type Output = Value; - fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult { + fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> TypResult { // The closure's name is defined by its let binding if there's one. let name = self.name().map(Ident::take); @@ -606,7 +717,7 @@ impl Eval for ClosureExpr { impl Eval for LetExpr { type Output = Value; - fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult { + fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> TypResult { let value = match self.init() { Some(expr) => expr.eval(ctx, scp)?, None => Value::None, @@ -619,7 +730,7 @@ impl Eval for LetExpr { impl Eval for SetExpr { type Output = StyleMap; - fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult { + fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> TypResult { let target = self.target(); let target = target.eval(ctx, scp)?.cast::().at(target.span())?; let args = self.args().eval(ctx, scp)?; @@ -630,7 +741,7 @@ impl Eval for SetExpr { impl Eval for ShowExpr { type Output = Recipe; - fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult { + fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> TypResult { // Evaluate the target function. let pattern = self.pattern(); let pattern = pattern.eval(ctx, scp)?.cast::().at(pattern.span())?; @@ -666,7 +777,7 @@ impl Eval for ShowExpr { impl Eval for IfExpr { type Output = Value; - fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult { + fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> TypResult { let condition = self.condition(); if condition.eval(ctx, scp)?.cast::().at(condition.span())? { self.if_body().eval(ctx, scp) @@ -681,19 +792,23 @@ impl Eval for IfExpr { impl Eval for WhileExpr { type Output = Value; - fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult { + fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> TypResult { let mut output = Value::None; let condition = self.condition(); while condition.eval(ctx, scp)?.cast::().at(condition.span())? { let body = self.body(); - match join_result(output, body.eval(ctx, scp), body.span()) { - Err(Control::Break(value, _)) => { - output = value; + let value = body.eval(ctx, scp)?; + output = ops::join(output, value).at(body.span())?; + + match ctx.flow { + Some(Flow::Break(_)) => { + ctx.flow = None; break; } - Err(Control::Continue(value, _)) => output = value, - other => output = other?, + Some(Flow::Continue(_)) => ctx.flow = None, + Some(Flow::Return(..)) => break, + None => {} } } @@ -704,7 +819,7 @@ impl Eval for WhileExpr { impl Eval for ForExpr { type Output = Value; - fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult { + fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> TypResult { macro_rules! iter { (for ($($binding:ident => $value:ident),*) in $iter:expr) => {{ let mut output = Value::None; @@ -715,13 +830,17 @@ impl Eval for ForExpr { $(scp.top.def_mut(&$binding, $value);)* let body = self.body(); - match join_result(output, body.eval(ctx, scp), body.span()) { - Err(Control::Break(value, _)) => { - output = value; + let value = body.eval(ctx, scp)?; + output = ops::join(output, value).at(body.span())?; + + match ctx.flow { + Some(Flow::Break(_)) => { + ctx.flow = None; break; } - Err(Control::Continue(value, _)) => output = value, - other => output = other?, + Some(Flow::Continue(_)) => ctx.flow = None, + Some(Flow::Return(..)) => break, + None => {} } } @@ -773,7 +892,7 @@ impl Eval for ForExpr { impl Eval for ImportExpr { type Output = Value; - fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult { + fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> TypResult { let span = self.path().span(); let path = self.path().eval(ctx, scp)?.cast::().at(span)?; let module = import(ctx, &path, span)?; @@ -802,7 +921,7 @@ impl Eval for ImportExpr { impl Eval for IncludeExpr { type Output = Content; - fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult { + fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> TypResult { let span = self.path().span(); let path = self.path().eval(ctx, scp)?.cast::().at(span)?; let module = import(ctx, &path, span)?; @@ -833,30 +952,34 @@ fn import(ctx: &mut Context, path: &str, span: Span) -> TypResult { impl Eval for BreakExpr { type Output = Value; - fn eval(&self, _: &mut Context, _: &mut Scopes) -> EvalResult { - Err(Control::Break(Value::default(), self.span())) + fn eval(&self, ctx: &mut Context, _: &mut Scopes) -> TypResult { + if ctx.flow.is_none() { + ctx.flow = Some(Flow::Break(self.span())); + } + Ok(Value::None) } } impl Eval for ContinueExpr { type Output = Value; - fn eval(&self, _: &mut Context, _: &mut Scopes) -> EvalResult { - Err(Control::Continue(Value::default(), self.span())) + fn eval(&self, ctx: &mut Context, _: &mut Scopes) -> TypResult { + if ctx.flow.is_none() { + ctx.flow = Some(Flow::Continue(self.span())); + } + Ok(Value::None) } } impl Eval for ReturnExpr { type Output = Value; - fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult { + fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> TypResult { let value = self.body().map(|body| body.eval(ctx, scp)).transpose()?; - let explicit = value.is_some(); - Err(Control::Return( - value.unwrap_or_default(), - explicit, - self.span(), - )) + if ctx.flow.is_none() { + ctx.flow = Some(Flow::Return(self.span(), value)); + } + Ok(Value::None) } } @@ -867,7 +990,7 @@ pub trait Access { &self, ctx: &mut Context, scp: &'a mut Scopes, - ) -> EvalResult>; + ) -> TypResult>; } impl Access for Expr { @@ -875,7 +998,7 @@ impl Access for Expr { &self, ctx: &mut Context, scp: &'a mut Scopes, - ) -> EvalResult> { + ) -> TypResult> { match self { Expr::Ident(ident) => ident.access(ctx, scp), Expr::FuncCall(call) => call.access(ctx, scp), @@ -889,7 +1012,7 @@ impl Access for Ident { &self, _: &mut Context, scp: &'a mut Scopes, - ) -> EvalResult> { + ) -> TypResult> { match scp.get(self) { Some(slot) => match slot.try_write() { Some(guard) => Ok(RwLockWriteGuard::map(guard, |v| v)), @@ -905,7 +1028,7 @@ impl Access for FuncCall { &self, ctx: &mut Context, scp: &'a mut Scopes, - ) -> EvalResult> { + ) -> TypResult> { let args = self.args().eval(ctx, scp)?; let guard = self.callee().access(ctx, scp)?; try_map(guard, |value| { @@ -928,9 +1051,9 @@ impl Access for FuncCall { type Location<'a> = MappedRwLockWriteGuard<'a, Value>; /// Map a reader-writer lock with a function. -fn try_map(location: Location, f: F) -> EvalResult +fn try_map(location: Location, f: F) -> TypResult where - F: FnOnce(&mut Value) -> EvalResult<&mut Value>, + F: FnOnce(&mut Value) -> TypResult<&mut Value>, { let mut error = None; MappedRwLockWriteGuard::try_map(location, |value| match f(value) { diff --git a/src/eval/module.rs b/src/eval/module.rs deleted file mode 100644 index d2b4c520f..000000000 --- a/src/eval/module.rs +++ /dev/null @@ -1,21 +0,0 @@ -use super::Scope; -use crate::model::Content; -use crate::source::{SourceId, SourceStore}; - -/// An evaluated module, ready for importing or layouting. -#[derive(Debug, Clone)] -pub struct Module { - /// The top-level definitions that were bound in this module. - pub scope: Scope, - /// The module's layoutable contents. - pub content: Content, - /// The source file revisions this module depends on. - pub deps: Vec<(SourceId, usize)>, -} - -impl Module { - /// Whether the module is still valid for the given sources. - pub fn valid(&self, sources: &SourceStore) -> bool { - self.deps.iter().all(|&(id, rev)| rev == sources.get(id).rev()) - } -} diff --git a/src/eval/value.rs b/src/eval/value.rs index dd1839269..8096721e8 100644 --- a/src/eval/value.rs +++ b/src/eval/value.rs @@ -102,11 +102,6 @@ impl Value { T::cast(self) } - /// Join the value with another value. - pub fn join(self, rhs: Self) -> StrResult { - ops::join(self, rhs) - } - /// Return the debug representation of the value. pub fn repr(&self) -> EcoString { format_eco!("{:?}", self) diff --git a/src/lib.rs b/src/lib.rs index 0edaeb99c..efddc239b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -58,7 +58,7 @@ use std::path::PathBuf; use std::sync::Arc; use crate::diag::TypResult; -use crate::eval::{Eval, Module, Scope, Scopes}; +use crate::eval::{Eval, Flow, Module, Scope, Scopes}; use crate::font::FontStore; use crate::frame::Frame; use crate::image::ImageStore; @@ -88,6 +88,8 @@ pub struct Context { route: Vec, /// The dependencies of the current evaluation process. deps: Vec<(SourceId, usize)>, + /// A control flow event that is currently happening. + flow: Option, } impl Context { @@ -145,10 +147,16 @@ impl Context { let content = ast.eval(self, &mut scp); self.route.pop().unwrap(); let deps = std::mem::replace(&mut self.deps, prev); + let flow = self.flow.take(); // Assemble the module. let module = Module { scope: scp.top, content: content?, deps }; + // Handle unhandled flow. + if let Some(flow) = flow { + return Err(flow.forbidden()); + } + // Save the evaluated module. self.modules.insert(id, module.clone()); @@ -213,6 +221,7 @@ impl ContextBuilder { cache: HashMap::new(), route: vec![], deps: vec![], + flow: None, } } } diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 86d18cbac..4ef1c96f2 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -246,7 +246,10 @@ fn markup_node(p: &mut Parser, at_start: &mut bool) { | NodeKind::While | NodeKind::For | NodeKind::Import - | NodeKind::Include => markup_expr(p), + | NodeKind::Include + | NodeKind::Break + | NodeKind::Continue + | NodeKind::Return => markup_expr(p), // Code and content block. NodeKind::LeftBrace => code_block(p), @@ -965,7 +968,7 @@ fn continue_expr(p: &mut Parser) -> ParseResult { fn return_expr(p: &mut Parser) -> ParseResult { p.perform(NodeKind::ReturnExpr, |p| { p.assert(NodeKind::Return); - if !p.eof() { + if !p.at(NodeKind::Comma) && !p.eof() { expr(p)?; } Ok(()) diff --git a/tests/ref/code/break-continue.png b/tests/ref/code/break-continue.png new file mode 100644 index 000000000..848f4fa60 Binary files /dev/null and b/tests/ref/code/break-continue.png differ diff --git a/tests/ref/style/set.png b/tests/ref/style/set.png index 024f7c3e9..c63ddb7c9 100644 Binary files a/tests/ref/style/set.png and b/tests/ref/style/set.png differ diff --git a/tests/ref/style/show-node.png b/tests/ref/style/show-node.png index 3a48e3dbc..3cb3539c5 100644 Binary files a/tests/ref/style/show-node.png and b/tests/ref/style/show-node.png differ diff --git a/tests/typ/code/break-continue.typ b/tests/typ/code/break-continue.typ index 60dac44d3..02c221a47 100644 --- a/tests/typ/code/break-continue.typ +++ b/tests/typ/code/break-continue.typ @@ -66,13 +66,28 @@ --- // Test break outside of loop. - #let f() = { // Error: 3-8 cannot break outside of loop break } -#f() +#for i in range(1) { + f() +} + +--- +// Test break in function call. +#let identity(x) = x +#let out = for i in range(5) { + "A" + identity({ + "B" + break + }) + "C" +} + +#test(out, "AB") --- // Test continue outside of loop. @@ -81,5 +96,41 @@ #let x = { continue } --- -// Error: 1-10 unexpected keyword `continue` +// Error: 1-10 cannot continue outside of loop #continue + +--- +// Ref: true +// Should output `Hello World 🌎`. +#for _ in range(10) { + [Hello ] + [World { + [🌎] + break + }] +} + +--- +// Ref: true +// Should output `Some` in red, `Some` in blue and `Last` in green. +// Everything should be in smallcaps. +#for color in (red, blue, green, yellow) [ + #set text("Roboto") + #wrap body in text(fill: color, body) + #smallcaps(if color != green [ + Some + ] else [ + Last + #break + ]) +] + +--- +// Ref: true +// Test break in set rule. +// Should output `Hi` in blue. +#for i in range(10) { + [Hello] + set text(blue, ..break) + [Not happening] +} diff --git a/tests/typ/code/return.typ b/tests/typ/code/return.typ index d30164447..8db99a813 100644 --- a/tests/typ/code/return.typ +++ b/tests/typ/code/return.typ @@ -35,10 +35,7 @@ #let f(text, caption: none) = { text - if caption == none { - [\.] - return - } + if caption == none [\.#return] [, ] emph(caption) [\.] @@ -55,3 +52,33 @@ // Error: 3-9 cannot return outside of function return } + +--- +// Test that the expression is evaluated to the end. +#let y = 1 +#let identity(x, ..rest) = x +#let f(x) = { + identity( + ..return, + x + 1, + y = 2, + ) + "nope" +} + +#test(f(1), 2) +#test(y, 2) + +--- +// Test value return from content. +#let x = 3 +#let f() = [ + Hello 😀 + { x = 1 } + #return "nope" + { x = 2 } + World +] + +#test(f(), "nope") +#test(x, 1) diff --git a/tests/typ/style/set.typ b/tests/typ/style/set.typ index 249666126..2c12d3e97 100644 --- a/tests/typ/style/set.typ +++ b/tests/typ/style/set.typ @@ -27,5 +27,15 @@ Hello *{x}* #text(fill: forest, x) --- -// Error: 2-10 set, show and wrap are only allowed directly in markup -{set f(x)} +// Test that scoping works as expected. +{ + if true { + set text(blue) + [Blue ] + } + [Not blue] +} + +--- +// Error: 11-25 set is only allowed directly in code and content blocks +{ let x = set text(blue) } diff --git a/tests/typ/style/show-node.typ b/tests/typ/style/show-node.typ index d0586c9d4..678ff1513 100644 --- a/tests/typ/style/show-node.typ +++ b/tests/typ/style/show-node.typ @@ -48,6 +48,33 @@ Some more text. = Task 2 Another text. +--- +// Test set and show in code blocks. +#show node: heading as { + set text(red) + show "ding" as [🛎] + node.body +} + += Heading + +--- +// Test that scoping works as expected. +{ + let world = [ World ] + show c: "W" as strong(c) + world + { + set text(blue) + wrap it in { + show "o" as "Ø" + it + } + world + } + world +} + --- // Error: 18-22 expected content, found integer #show heading as 1234 @@ -67,5 +94,5 @@ Another text. #show red as [] --- -// Error: 2-16 set, show and wrap are only allowed directly in markup -{show list as a} +// Error: 7-27 show is only allowed directly in code and content blocks +{ 1 + show heading as none } diff --git a/tests/typ/style/wrap.typ b/tests/typ/style/wrap.typ index 2a9074cb0..57f21f996 100644 --- a/tests/typ/style/wrap.typ +++ b/tests/typ/style/wrap.typ @@ -25,5 +25,12 @@ A [_B #wrap c in [*#c*]; C_] D Forest --- -// Error: 6-17 set, show and wrap are only allowed directly in markup -{1 + wrap x in y} +{ + // Error: 3-24 expected remaining block to yield content, found integer + wrap body in 2 * body + 2 +} + +--- +// Error: 4-18 wrap is only allowed directly in code and content blocks +{ (wrap body in 2) * body }