diff options
| author | Laurenz <laurmaedje@gmail.com> | 2021-02-09 19:46:57 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2021-02-09 19:46:57 +0100 |
| commit | 06ca740d01b428f12f6bd327257cd05dce737b03 (patch) | |
| tree | 995bf8ff3a606aedecf296c9e805e11e9cd0ae8e /src/eval | |
| parent | e35bbfffcb1f84b2fb0679759152ca0a5eabfad4 (diff) | |
Split evaluation and execution 🔪
Diffstat (limited to 'src/eval')
| -rw-r--r-- | src/eval/call.rs | 22 | ||||
| -rw-r--r-- | src/eval/capture.rs | 8 | ||||
| -rw-r--r-- | src/eval/context.rs | 304 | ||||
| -rw-r--r-- | src/eval/mod.rs | 364 | ||||
| -rw-r--r-- | src/eval/scope.rs | 19 | ||||
| -rw-r--r-- | src/eval/state.rs | 163 | ||||
| -rw-r--r-- | src/eval/value.rs | 133 |
7 files changed, 288 insertions, 725 deletions
diff --git a/src/eval/call.rs b/src/eval/call.rs index 7b45c09a..1a80e15a 100644 --- a/src/eval/call.rs +++ b/src/eval/call.rs @@ -1,21 +1,21 @@ use super::*; -impl Eval for Spanned<&ExprCall> { +impl Eval for ExprCall { type Output = Value; - fn eval(self, ctx: &mut EvalContext) -> Self::Output { - let callee = self.v.callee.eval(ctx); + fn eval(&self, ctx: &mut EvalContext) -> Self::Output { + let callee = self.callee.eval(ctx); if let Value::Func(func) = callee { let func = func.clone(); - let mut args = self.v.args.as_ref().eval(ctx); + let mut args = self.args.eval(ctx); let returned = func(ctx, &mut args); args.finish(ctx); return returned; } else if callee != Value::Error { ctx.diag(error!( - self.v.callee.span, + self.callee.span(), "expected function, found {}", callee.type_name(), )); @@ -25,22 +25,22 @@ impl Eval for Spanned<&ExprCall> { } } -impl Eval for Spanned<&ExprArgs> { +impl Eval for ExprArgs { type Output = Args; - fn eval(self, ctx: &mut EvalContext) -> Self::Output { + fn eval(&self, ctx: &mut EvalContext) -> Self::Output { let mut pos = vec![]; let mut named = vec![]; - for arg in self.v { + for arg in &self.items { match arg { Argument::Pos(expr) => { - pos.push(expr.as_ref().eval(ctx).with_span(expr.span)); + pos.push(expr.eval(ctx).with_span(expr.span())); } Argument::Named(Named { name, expr }) => { named.push(( - name.as_ref().map(|id| id.0.clone()), - expr.as_ref().eval(ctx).with_span(expr.span), + name.string.clone().with_span(name.span), + expr.eval(ctx).with_span(expr.span()), )); } } diff --git a/src/eval/capture.rs b/src/eval/capture.rs index 9ef55fb2..bbabc503 100644 --- a/src/eval/capture.rs +++ b/src/eval/capture.rs @@ -50,11 +50,11 @@ impl<'ast> Visit<'ast> for CapturesVisitor<'_> { fn visit_block(&mut self, item: &'ast ExprBlock) { // Blocks create a scope except if directly in a template. - if item.scopes { + if item.scoping { self.internal.push(); } visit_block(self, item); - if item.scopes { + if item.scoping { self.internal.pop(); } } @@ -67,12 +67,12 @@ impl<'ast> Visit<'ast> for CapturesVisitor<'_> { } fn visit_let(&mut self, item: &'ast ExprLet) { - self.define(&item.pat.v); + self.define(&item.binding); visit_let(self, item); } fn visit_for(&mut self, item: &'ast ExprFor) { - match &item.pat.v { + match &item.pattern { ForPattern::Value(value) => self.define(value), ForPattern::KeyValue(key, value) => { self.define(key); diff --git a/src/eval/context.rs b/src/eval/context.rs deleted file mode 100644 index fd7e264f..00000000 --- a/src/eval/context.rs +++ /dev/null @@ -1,304 +0,0 @@ -use std::any::Any; -use std::rc::Rc; - -use fontdock::FontStyle; - -use super::*; -use crate::diag::Diag; -use crate::diag::{Deco, Feedback, Pass}; -use crate::geom::{ChildAlign, Dir, Gen, LayoutDirs, Length, Linear, Sides, Size}; -use crate::layout::{ - Expansion, Node, NodePad, NodePages, NodePar, NodeSpacing, NodeStack, NodeText, Tree, -}; - -/// The context for evaluation. -#[derive(Debug)] -pub struct EvalContext<'a> { - /// The environment from which resources are gathered. - pub env: &'a mut Env, - /// The active scopes. - pub scopes: Scopes<'a>, - /// The active evaluation state. - pub state: State, - /// The accumulated feedback. - feedback: Feedback, - /// The finished page runs. - runs: Vec<NodePages>, - /// The stack of logical groups (paragraphs and such). - /// - /// Each entry contains metadata about the group and nodes that are at the - /// same level as the group, which will return to `inner` once the group is - /// finished. - groups: Vec<(Box<dyn Any>, Vec<Node>)>, - /// The nodes in the current innermost group - /// (whose metadata is in `groups.last()`). - inner: Vec<Node>, -} - -impl<'a> EvalContext<'a> { - /// Create a new evaluation context with a base state and scope. - pub fn new(env: &'a mut Env, scope: &'a Scope, state: State) -> Self { - Self { - env, - scopes: Scopes::new(Some(scope)), - state, - groups: vec![], - inner: vec![], - runs: vec![], - feedback: Feedback::new(), - } - } - - /// Finish evaluation and return the created document. - pub fn finish(self) -> Pass<Tree> { - assert!(self.groups.is_empty(), "unfinished group"); - Pass::new(Tree { runs: self.runs }, self.feedback) - } - - /// Add a diagnostic to the feedback. - pub fn diag(&mut self, diag: Spanned<Diag>) { - self.feedback.diags.push(diag); - } - - /// Add a decoration to the feedback. - pub fn deco(&mut self, deco: Spanned<Deco>) { - self.feedback.decos.push(deco); - } - - /// Push a layout node to the active group. - /// - /// Spacing nodes will be handled according to their [`Softness`]. - pub fn push(&mut self, node: impl Into<Node>) { - let node = node.into(); - - if let Node::Spacing(this) = node { - if this.softness == Softness::Soft && self.inner.is_empty() { - return; - } - - if let Some(&Node::Spacing(other)) = self.inner.last() { - if this.softness > other.softness { - self.inner.pop(); - } else if this.softness == Softness::Soft { - return; - } - } - } - - self.inner.push(node); - } - - /// Start a page group based on the active page state. - /// - /// The `softness` is a hint on whether empty pages should be kept in the - /// output. - /// - /// This also starts an inner paragraph. - pub fn start_page_group(&mut self, softness: Softness) { - self.start_group(PageGroup { - size: self.state.page.size, - expand: self.state.page.expand, - padding: self.state.page.margins(), - dirs: self.state.dirs, - align: self.state.align, - softness, - }); - self.start_par_group(); - } - - /// End a page group, returning its [`Softness`]. - /// - /// Whether the page is kept when it's empty is decided by `keep_empty` - /// based on its softness. If kept, the page is pushed to the finished page - /// runs. - /// - /// This also ends an inner paragraph. - pub fn end_page_group<F>(&mut self, keep_empty: F) -> Softness - where - F: FnOnce(Softness) -> bool, - { - self.end_par_group(); - let (group, children) = self.end_group::<PageGroup>(); - if !children.is_empty() || keep_empty(group.softness) { - self.runs.push(NodePages { - size: group.size, - child: NodePad { - padding: group.padding, - child: NodeStack { - dirs: group.dirs, - align: group.align, - expand: group.expand, - children, - } - .into(), - } - .into(), - }) - } - group.softness - } - - /// Start a content group. - /// - /// This also starts an inner paragraph. - pub fn start_content_group(&mut self) { - self.start_group(ContentGroup); - self.start_par_group(); - } - - /// End a content group and return the resulting nodes. - /// - /// This also ends an inner paragraph. - pub fn end_content_group(&mut self) -> Vec<Node> { - self.end_par_group(); - self.end_group::<ContentGroup>().1 - } - - /// Start a paragraph group based on the active text state. - pub fn start_par_group(&mut self) { - let em = self.state.font.font_size(); - self.start_group(ParGroup { - dirs: self.state.dirs, - align: self.state.align, - line_spacing: self.state.par.line_spacing.resolve(em), - }); - } - - /// End a paragraph group and push it to its parent group if it's not empty. - pub fn end_par_group(&mut self) { - let (group, children) = self.end_group::<ParGroup>(); - if !children.is_empty() { - self.push(NodePar { - dirs: group.dirs, - align: group.align, - // FIXME: This is a hack and should be superseded by something - // better. - cross_expansion: if self.groups.len() <= 1 { - Expansion::Fill - } else { - Expansion::Fit - }, - line_spacing: group.line_spacing, - children, - }); - } - } - - /// Start a layouting group. - /// - /// All further calls to [`push`](Self::push) will collect nodes for this group. - /// The given metadata will be returned alongside the collected nodes - /// in a matching call to [`end_group`](Self::end_group). - fn start_group<T: 'static>(&mut self, meta: T) { - self.groups.push((Box::new(meta), std::mem::take(&mut self.inner))); - } - - /// End a layouting group started with [`start_group`](Self::start_group). - /// - /// This returns the stored metadata and the collected nodes. - #[track_caller] - fn end_group<T: 'static>(&mut self) -> (T, Vec<Node>) { - if let Some(&Node::Spacing(spacing)) = self.inner.last() { - if spacing.softness == Softness::Soft { - self.inner.pop(); - } - } - - let (any, outer) = self.groups.pop().expect("no pushed group"); - let group = *any.downcast::<T>().expect("bad group type"); - (group, std::mem::replace(&mut self.inner, outer)) - } - - /// Set the directions if they would apply to different axes, producing an - /// appropriate error otherwise. - pub fn set_dirs(&mut self, new: Gen<Option<Spanned<Dir>>>) { - let dirs = Gen::new( - new.main.map(|s| s.v).unwrap_or(self.state.dirs.main), - new.cross.map(|s| s.v).unwrap_or(self.state.dirs.cross), - ); - - if dirs.main.axis() != dirs.cross.axis() { - self.state.dirs = dirs; - } else { - for dir in new.main.iter().chain(new.cross.iter()) { - self.diag(error!(dir.span, "aligned axis")); - } - } - } - - /// Apply a forced line break. - pub fn apply_linebreak(&mut self) { - self.end_par_group(); - self.start_par_group(); - } - - /// Apply a forced paragraph break. - pub fn apply_parbreak(&mut self) { - self.end_par_group(); - let em = self.state.font.font_size(); - self.push(NodeSpacing { - amount: self.state.par.par_spacing.resolve(em), - softness: Softness::Soft, - }); - self.start_par_group(); - } - - /// Construct a text node from the given string based on the active text - /// state. - pub fn make_text_node(&self, text: String) -> NodeText { - let mut variant = self.state.font.variant; - - if self.state.font.strong { - variant.weight = variant.weight.thicken(300); - } - - if self.state.font.emph { - variant.style = match variant.style { - FontStyle::Normal => FontStyle::Italic, - FontStyle::Italic => FontStyle::Normal, - FontStyle::Oblique => FontStyle::Normal, - } - } - - NodeText { - text, - align: self.state.align, - dir: self.state.dirs.cross, - font_size: self.state.font.font_size(), - families: Rc::clone(&self.state.font.families), - variant, - } - } -} - -/// Defines how an item interacts with surrounding items. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] -pub enum Softness { - /// A soft item can be skipped in some circumstances. - Soft, - /// A hard item is always retained. - Hard, -} - -/// A group for a page run. -#[derive(Debug)] -struct PageGroup { - size: Size, - expand: Spec<Expansion>, - padding: Sides<Linear>, - dirs: LayoutDirs, - align: ChildAlign, - softness: Softness, -} - -/// A group for generic content. -#[derive(Debug)] -struct ContentGroup; - -/// A group for a paragraph. -#[derive(Debug)] -struct ParGroup { - dirs: LayoutDirs, - align: ChildAlign, - line_spacing: Length, -} diff --git a/src/eval/mod.rs b/src/eval/mod.rs index e49f7779..2390a84f 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -1,247 +1,199 @@ -//! Evaluation of syntax trees into layout trees. +//! Evaluation of syntax trees. #[macro_use] mod value; mod call; mod capture; -mod context; mod ops; mod scope; -mod state; pub use call::*; pub use capture::*; -pub use context::*; pub use scope::*; -pub use state::*; pub use value::*; +use std::collections::HashMap; use std::rc::Rc; +use super::*; use crate::color::Color; -use crate::diag::Pass; -use crate::env::Env; -use crate::geom::{Angle, Length, Relative, Spec}; -use crate::layout::{self, Expansion, NodeSpacing, NodeStack}; +use crate::diag::{Diag, Feedback}; +use crate::geom::{Angle, Length, Relative}; use crate::syntax::visit::Visit; use crate::syntax::*; -/// Evaluate a syntax tree into a layout tree. +/// Evaluate all expressions in a syntax tree. /// -/// The `state` is the base state that may be updated over the course of -/// evaluation. The `scope` similarly consists of the base definitions that are -/// present from the beginning (typically, the standard library). -pub fn eval( - tree: &Tree, - env: &mut Env, - scope: &Scope, - state: State, -) -> Pass<layout::Tree> { - let mut ctx = EvalContext::new(env, scope, state); - ctx.start_page_group(Softness::Hard); - tree.eval(&mut ctx); - ctx.end_page_group(|s| s == Softness::Hard); - ctx.finish() +/// The `scope` consists of the base definitions that are present from the +/// beginning (typically, the standard library). +pub fn eval(env: &mut Env, tree: &Tree, scope: &Scope) -> Pass<ExprMap> { + let mut ctx = EvalContext::new(env, scope); + let map = tree.eval(&mut ctx); + Pass::new(map, ctx.feedback) } -/// Evaluate an item. +/// A map from expression to values to evaluated to. /// -/// _Note_: Evaluation is not necessarily pure, it may change the active state. -pub trait Eval { - /// The output of evaluating the item. - type Output; - - /// Evaluate the item to the output value. - fn eval(self, ctx: &mut EvalContext) -> Self::Output; +/// The raw pointers point into the expressions contained in `tree`. Since +/// the lifetime is erased, `tree` could go out of scope while the hash map +/// still lives. While this could lead to lookup panics, it is not unsafe +/// since the pointers are never dereferenced. +pub type ExprMap = HashMap<*const Expr, Value>; + +/// The context for evaluation. +#[derive(Debug)] +pub struct EvalContext<'a> { + /// The environment from which resources are gathered. + pub env: &'a mut Env, + /// The active scopes. + pub scopes: Scopes<'a>, + /// The accumulated feedback. + feedback: Feedback, } -impl<'a, T> Eval for &'a Spanned<T> -where - Spanned<&'a T>: Eval, -{ - type Output = <Spanned<&'a T> as Eval>::Output; - - fn eval(self, ctx: &mut EvalContext) -> Self::Output { - self.as_ref().eval(ctx) - } -} - -impl Eval for &[Spanned<Node>] { - type Output = (); - - fn eval(self, ctx: &mut EvalContext) -> Self::Output { - for node in self { - node.eval(ctx); +impl<'a> EvalContext<'a> { + /// Create a new execution context with a base scope. + pub fn new(env: &'a mut Env, scope: &'a Scope) -> Self { + Self { + env, + scopes: Scopes::with_base(scope), + feedback: Feedback::new(), } } -} -impl Eval for Spanned<&Node> { - type Output = (); - - fn eval(self, ctx: &mut EvalContext) -> Self::Output { - match self.v { - Node::Text(text) => { - let node = ctx.make_text_node(text.clone()); - ctx.push(node); - } - Node::Space => { - let em = ctx.state.font.font_size(); - ctx.push(NodeSpacing { - amount: ctx.state.par.word_spacing.resolve(em), - softness: Softness::Soft, - }); - } - Node::Linebreak => ctx.apply_linebreak(), - Node::Parbreak => ctx.apply_parbreak(), - Node::Strong => ctx.state.font.strong ^= true, - Node::Emph => ctx.state.font.emph ^= true, - Node::Heading(heading) => heading.with_span(self.span).eval(ctx), - Node::Raw(raw) => raw.with_span(self.span).eval(ctx), - Node::Expr(expr) => { - let value = expr.with_span(self.span).eval(ctx); - value.eval(ctx) - } - } + /// Add a diagnostic to the feedback. + pub fn diag(&mut self, diag: Spanned<Diag>) { + self.feedback.diags.push(diag); } } -impl Eval for Spanned<&NodeHeading> { - type Output = (); - - fn eval(self, ctx: &mut EvalContext) -> Self::Output { - let prev = ctx.state.clone(); - let upscale = 1.5 - 0.1 * self.v.level.v as f64; - ctx.state.font.scale *= upscale; - ctx.state.font.strong = true; - - self.v.contents.eval(ctx); - ctx.apply_parbreak(); +/// Evaluate an expression. +pub trait Eval { + /// The output of evaluating the expression. + type Output; - ctx.state = prev; - } + /// Evaluate the expression to the output value. + fn eval(&self, ctx: &mut EvalContext) -> Self::Output; } -impl Eval for Spanned<&NodeRaw> { - type Output = (); - - fn eval(self, ctx: &mut EvalContext) -> Self::Output { - let prev = Rc::clone(&ctx.state.font.families); - let families = ctx.state.font.families_mut(); - families.list.insert(0, "monospace".to_string()); - families.flatten(); - - let em = ctx.state.font.font_size(); - let line_spacing = ctx.state.par.line_spacing.resolve(em); - - let mut children = vec![]; - for line in &self.v.lines { - children.push(layout::Node::Text(ctx.make_text_node(line.clone()))); - children.push(layout::Node::Spacing(NodeSpacing { - amount: line_spacing, - softness: Softness::Hard, - })); - } +impl Eval for Tree { + type Output = ExprMap; - if self.v.block { - ctx.apply_parbreak(); + fn eval(&self, ctx: &mut EvalContext) -> Self::Output { + struct ExprVisitor<'a, 'b> { + map: ExprMap, + ctx: &'a mut EvalContext<'b>, } - ctx.push(NodeStack { - dirs: ctx.state.dirs, - align: ctx.state.align, - expand: Spec::uniform(Expansion::Fit), - children, - }); - - if self.v.block { - ctx.apply_parbreak(); + impl<'ast> Visit<'ast> for ExprVisitor<'_, '_> { + fn visit_expr(&mut self, item: &'ast Expr) { + self.map.insert(item as *const _, item.eval(self.ctx)); + } } - ctx.state.font.families = prev; + let mut visitor = ExprVisitor { map: HashMap::new(), ctx }; + visitor.visit_tree(self); + visitor.map } } -impl Eval for Spanned<&Expr> { +impl Eval for Expr { type Output = Value; - fn eval(self, ctx: &mut EvalContext) -> Self::Output { - match self.v { - Expr::None => Value::None, - Expr::Ident(v) => match ctx.scopes.get(v) { + fn eval(&self, ctx: &mut EvalContext) -> Self::Output { + match self { + Self::Lit(lit) => lit.eval(ctx), + Self::Ident(v) => match ctx.scopes.get(&v) { Some(slot) => slot.borrow().clone(), None => { - ctx.diag(error!(self.span, "unknown variable")); + ctx.diag(error!(v.span, "unknown variable")); Value::Error } }, - &Expr::Bool(v) => Value::Bool(v), - &Expr::Int(v) => Value::Int(v), - &Expr::Float(v) => Value::Float(v), - &Expr::Length(v, unit) => Value::Length(Length::with_unit(v, unit)), - &Expr::Angle(v, unit) => Value::Angle(Angle::with_unit(v, unit)), - &Expr::Percent(v) => Value::Relative(Relative::new(v / 100.0)), - &Expr::Color(v) => Value::Color(Color::Rgba(v)), - Expr::Str(v) => Value::Str(v.clone()), - Expr::Array(v) => Value::Array(v.with_span(self.span).eval(ctx)), - Expr::Dict(v) => Value::Dict(v.with_span(self.span).eval(ctx)), - Expr::Template(v) => v.with_span(self.span).eval(ctx), - Expr::Group(v) => v.eval(ctx), - Expr::Block(v) => v.with_span(self.span).eval(ctx), - Expr::Call(v) => v.with_span(self.span).eval(ctx), - Expr::Unary(v) => v.with_span(self.span).eval(ctx), - Expr::Binary(v) => v.with_span(self.span).eval(ctx), - Expr::Let(v) => v.with_span(self.span).eval(ctx), - Expr::If(v) => v.with_span(self.span).eval(ctx), - Expr::For(v) => v.with_span(self.span).eval(ctx), + Self::Array(v) => Value::Array(v.eval(ctx)), + Self::Dict(v) => Value::Dict(v.eval(ctx)), + Self::Template(v) => Value::Template(vec![v.eval(ctx)]), + Self::Group(v) => v.eval(ctx), + Self::Block(v) => v.eval(ctx), + Self::Call(v) => v.eval(ctx), + Self::Unary(v) => v.eval(ctx), + Self::Binary(v) => v.eval(ctx), + Self::Let(v) => v.eval(ctx), + Self::If(v) => v.eval(ctx), + Self::For(v) => v.eval(ctx), + } + } +} + +impl Eval for Lit { + type Output = Value; + + fn eval(&self, _: &mut EvalContext) -> Self::Output { + match self.kind { + LitKind::None => Value::None, + LitKind::Bool(v) => Value::Bool(v), + LitKind::Int(v) => Value::Int(v), + LitKind::Float(v) => Value::Float(v), + LitKind::Length(v, unit) => Value::Length(Length::with_unit(v, unit)), + LitKind::Angle(v, unit) => Value::Angle(Angle::with_unit(v, unit)), + LitKind::Percent(v) => Value::Relative(Relative::new(v / 100.0)), + LitKind::Color(v) => Value::Color(Color::Rgba(v)), + LitKind::Str(ref v) => Value::Str(v.clone()), } } } -impl Eval for Spanned<&ExprArray> { +impl Eval for ExprArray { type Output = ValueArray; - fn eval(self, ctx: &mut EvalContext) -> Self::Output { - self.v.iter().map(|expr| expr.eval(ctx)).collect() + fn eval(&self, ctx: &mut EvalContext) -> Self::Output { + self.items.iter().map(|expr| expr.eval(ctx)).collect() } } -impl Eval for Spanned<&ExprDict> { +impl Eval for ExprDict { type Output = ValueDict; - fn eval(self, ctx: &mut EvalContext) -> Self::Output { - self.v + fn eval(&self, ctx: &mut EvalContext) -> Self::Output { + self.items .iter() - .map(|Named { name, expr }| (name.v.0.clone(), expr.eval(ctx))) + .map(|Named { name, expr }| (name.string.clone(), expr.eval(ctx))) .collect() } } -impl Eval for Spanned<&ExprTemplate> { +impl Eval for ExprTemplate { + type Output = TemplateNode; + + fn eval(&self, ctx: &mut EvalContext) -> Self::Output { + let tree = Rc::clone(&self.tree); + let map = self.tree.eval(ctx); + TemplateNode::Tree { tree, map } + } +} + +impl Eval for ExprGroup { type Output = Value; - fn eval(self, ctx: &mut EvalContext) -> Self::Output { - let mut template = self.v.clone(); - let mut visitor = CapturesVisitor::new(&ctx.scopes); - visitor.visit_template(&mut template); - Value::Template(template) + fn eval(&self, ctx: &mut EvalContext) -> Self::Output { + self.expr.eval(ctx) } } -impl Eval for Spanned<&ExprBlock> { +impl Eval for ExprBlock { type Output = Value; - fn eval(self, ctx: &mut EvalContext) -> Self::Output { - if self.v.scopes { + fn eval(&self, ctx: &mut EvalContext) -> Self::Output { + if self.scoping { ctx.scopes.push(); } let mut output = Value::None; - for expr in &self.v.exprs { + for expr in &self.exprs { output = expr.eval(ctx); } - if self.v.scopes { + if self.scoping { ctx.scopes.pop(); } @@ -249,17 +201,17 @@ impl Eval for Spanned<&ExprBlock> { } } -impl Eval for Spanned<&ExprUnary> { +impl Eval for ExprUnary { type Output = Value; - fn eval(self, ctx: &mut EvalContext) -> Self::Output { - let value = self.v.expr.eval(ctx); + fn eval(&self, ctx: &mut EvalContext) -> Self::Output { + let value = self.expr.eval(ctx); if value == Value::Error { return Value::Error; } let ty = value.type_name(); - let out = match self.v.op.v { + let out = match self.op { UnOp::Pos => ops::pos(value), UnOp::Neg => ops::neg(value), UnOp::Not => ops::not(value), @@ -269,7 +221,7 @@ impl Eval for Spanned<&ExprUnary> { ctx.diag(error!( self.span, "cannot apply '{}' to {}", - self.v.op.v.as_str(), + self.op.as_str(), ty, )); } @@ -278,11 +230,11 @@ impl Eval for Spanned<&ExprUnary> { } } -impl Eval for Spanned<&ExprBinary> { +impl Eval for ExprBinary { type Output = Value; - fn eval(self, ctx: &mut EvalContext) -> Self::Output { - match self.v.op.v { + fn eval(&self, ctx: &mut EvalContext) -> Self::Output { + match self.op { BinOp::Add => self.apply(ctx, ops::add), BinOp::Sub => self.apply(ctx, ops::sub), BinOp::Mul => self.apply(ctx, ops::mul), @@ -304,22 +256,22 @@ impl Eval for Spanned<&ExprBinary> { } } -impl Spanned<&ExprBinary> { +impl ExprBinary { /// Apply a basic binary operation. - fn apply<F>(self, ctx: &mut EvalContext, op: F) -> Value + fn apply<F>(&self, ctx: &mut EvalContext, op: F) -> Value where F: FnOnce(Value, Value) -> Value, { - let lhs = self.v.lhs.eval(ctx); + let lhs = self.lhs.eval(ctx); // Short-circuit boolean operations. - match (self.v.op.v, &lhs) { + match (self.op, &lhs) { (BinOp::And, Value::Bool(false)) => return lhs, (BinOp::Or, Value::Bool(true)) => return lhs, _ => {} } - let rhs = self.v.rhs.eval(ctx); + let rhs = self.rhs.eval(ctx); if lhs == Value::Error || rhs == Value::Error { return Value::Error; @@ -336,23 +288,23 @@ impl Spanned<&ExprBinary> { } /// Apply an assignment operation. - fn assign<F>(self, ctx: &mut EvalContext, op: F) -> Value + fn assign<F>(&self, ctx: &mut EvalContext, op: F) -> Value where F: FnOnce(Value, Value) -> Value, { - let rhs = self.v.rhs.eval(ctx); - let span = self.v.lhs.span; + let rhs = self.rhs.eval(ctx); - let slot = if let Expr::Ident(id) = &self.v.lhs.v { + let lhs_span = self.lhs.span(); + let slot = if let Expr::Ident(id) = self.lhs.as_ref() { match ctx.scopes.get(id) { Some(slot) => slot, None => { - ctx.diag(error!(span, "unknown variable")); + ctx.diag(error!(lhs_span, "unknown variable")); return Value::Error; } } } else { - ctx.diag(error!(span, "cannot assign to this expression")); + ctx.diag(error!(lhs_span, "cannot assign to this expression")); return Value::Error; }; @@ -371,7 +323,7 @@ impl Spanned<&ExprBinary> { }; if constant { - ctx.diag(error!(span, "cannot assign to a constant")); + ctx.diag(error!(lhs_span, "cannot assign to a constant")); } if let Some((l, r)) = err { @@ -382,47 +334,45 @@ impl Spanned<&ExprBinary> { } fn error(&self, ctx: &mut EvalContext, l: &str, r: &str) { - let op = self.v.op.v.as_str(); - let message = match self.v.op.v { + ctx.diag(error!(self.span, "{}", match self.op { BinOp::Add => format!("cannot add {} and {}", l, r), BinOp::Sub => format!("cannot subtract {1} from {0}", l, r), BinOp::Mul => format!("cannot multiply {} with {}", l, r), BinOp::Div => format!("cannot divide {} by {}", l, r), - _ => format!("cannot apply '{}' to {} and {}", op, l, r), - }; - ctx.diag(error!(self.span, "{}", message)); + _ => format!("cannot apply '{}' to {} and {}", self.op.as_str(), l, r), + })); } } -impl Eval for Spanned<&ExprLet> { +impl Eval for ExprLet { type Output = Value; - fn eval(self, ctx: &mut EvalContext) -> Self::Output { - let value = match &self.v.init { + fn eval(&self, ctx: &mut EvalContext) -> Self::Output { + let value = match &self.init { Some(expr) => expr.eval(ctx), None => Value::None, }; - ctx.scopes.def_mut(self.v.pat.v.as_str(), value); + ctx.scopes.def_mut(self.binding.as_str(), value); Value::None } } -impl Eval for Spanned<&ExprIf> { +impl Eval for ExprIf { type Output = Value; - fn eval(self, ctx: &mut EvalContext) -> Self::Output { - let condition = self.v.condition.eval(ctx); + fn eval(&self, ctx: &mut EvalContext) -> Self::Output { + let condition = self.condition.eval(ctx); if let Value::Bool(boolean) = condition { return if boolean { - self.v.if_body.eval(ctx) - } else if let Some(expr) = &self.v.else_body { + self.if_body.eval(ctx) + } else if let Some(expr) = &self.else_body { expr.eval(ctx) } else { Value::None }; } else if condition != Value::Error { ctx.diag(error!( - self.v.condition.span, + self.condition.span(), "expected boolean, found {}", condition.type_name(), )); @@ -432,10 +382,10 @@ impl Eval for Spanned<&ExprIf> { } } -impl Eval for Spanned<&ExprFor> { +impl Eval for ExprFor { type Output = Value; - fn eval(self, ctx: &mut EvalContext) -> Self::Output { + fn eval(&self, ctx: &mut EvalContext) -> Self::Output { macro_rules! iterate { (for ($($binding:ident => $value:ident),*) in $iter:expr) => {{ let mut output = vec![]; @@ -444,7 +394,7 @@ impl Eval for Spanned<&ExprFor> { for ($($value),*) in $iter { $(ctx.scopes.def_mut($binding.as_str(), $value);)* - if let Value::Template(new) = self.v.body.eval(ctx) { + if let Value::Template(new) = self.body.eval(ctx) { output.extend(new); } } @@ -455,8 +405,8 @@ impl Eval for Spanned<&ExprFor> { ctx.scopes.push(); - let iter = self.v.iter.eval(ctx); - let value = match (self.v.pat.v.clone(), iter) { + let iter = self.iter.eval(ctx); + let value = match (self.pattern.clone(), iter) { (ForPattern::Value(v), Value::Str(string)) => { iterate!(for (v => value) in string.chars().map(|c| Value::Str(c.into()))) } @@ -472,14 +422,14 @@ impl Eval for Spanned<&ExprFor> { (ForPattern::KeyValue(_, _), Value::Str(_)) | (ForPattern::KeyValue(_, _), Value::Array(_)) => { - ctx.diag(error!(self.v.pat.span, "mismatched pattern")); + ctx.diag(error!(self.pattern.span(), "mismatched pattern")); Value::Error } (_, Value::Error) => Value::Error, (_, iter) => { ctx.diag(error!( - self.v.iter.span, + self.iter.span(), "cannot loop over {}", iter.type_name(), )); diff --git a/src/eval/scope.rs b/src/eval/scope.rs index 8f2bd1d5..0991564f 100644 --- a/src/eval/scope.rs +++ b/src/eval/scope.rs @@ -21,9 +21,22 @@ pub struct Scopes<'a> { } impl<'a> Scopes<'a> { - /// Create a new hierarchy of scopes. - pub fn new(base: Option<&'a Scope>) -> Self { - Self { top: Scope::new(), scopes: vec![], base } + /// Create a new, empty hierarchy of scopes. + pub fn new() -> Self { + Self { + top: Scope::new(), + scopes: vec![], + base: None, + } + } + + /// Create a new hierarchy of scopes with a base scope. + pub fn with_base(base: &'a Scope) -> Self { + Self { + top: Scope::new(), + scopes: vec![], + base: Some(base), + } } /// Push a new scope. diff --git a/src/eval/state.rs b/src/eval/state.rs deleted file mode 100644 index 21fb7fb6..00000000 --- a/src/eval/state.rs +++ /dev/null @@ -1,163 +0,0 @@ -use std::rc::Rc; - -use fontdock::{fallback, FallbackTree, FontStretch, FontStyle, FontVariant, FontWeight}; - -use crate::geom::{ - Align, ChildAlign, Dir, LayoutDirs, Length, Linear, Relative, Sides, Size, Spec, -}; -use crate::layout::Expansion; -use crate::paper::{Paper, PaperClass, PAPER_A4}; - -/// The evaluation state. -#[derive(Debug, Clone, PartialEq)] -pub struct State { - /// The current page settings. - pub page: PageSettings, - /// The current paragraph settings. - pub par: ParSettings, - /// The current font settings. - pub font: FontSettings, - /// The current layouting directions. - pub dirs: LayoutDirs, - /// The current alignments of an item in its parent. - pub align: ChildAlign, -} - -impl Default for State { - fn default() -> Self { - Self { - page: PageSettings::default(), - par: ParSettings::default(), - font: FontSettings::default(), - dirs: LayoutDirs::new(Dir::TTB, Dir::LTR), - align: ChildAlign::new(Align::Start, Align::Start), - } - } -} - -/// Defines page properties. -#[derive(Debug, Copy, Clone, PartialEq)] -pub struct PageSettings { - /// The class of this page. - pub class: PaperClass, - /// The width and height of the page. - pub size: Size, - /// Whether the expand the pages to the `size` or to fit the content. - pub expand: Spec<Expansion>, - /// The amount of white space in the order [left, top, right, bottom]. If a - /// side is set to `None`, the default for the paper class is used. - pub margins: Sides<Option<Linear>>, -} - -impl PageSettings { - /// The default page style for the given paper. - pub fn new(paper: Paper) -> Self { - Self { - class: paper.class, - size: paper.size(), - expand: Spec::uniform(Expansion::Fill), - margins: Sides::uniform(None), - } - } - - /// The margins. - pub fn margins(&self) -> Sides<Linear> { - let default = self.class.default_margins(); - Sides { - left: self.margins.left.unwrap_or(default.left), - top: self.margins.top.unwrap_or(default.top), - right: self.margins.right.unwrap_or(default.right), - bottom: self.margins.bottom.unwrap_or(default.bottom), - } - } -} - -impl Default for PageSettings { - fn default() -> Self { - Self::new(PAPER_A4) - } -} - -/// Defines paragraph properties. -#[derive(Debug, Copy, Clone, PartialEq)] -pub struct ParSettings { - /// The spacing between words (dependent on scaled font size). - pub word_spacing: Linear, - /// The spacing between lines (dependent on scaled font size). - pub line_spacing: Linear, - /// The spacing between paragraphs (dependent on scaled font size). - pub par_spacing: Linear, -} - -impl Default for ParSettings { - fn default() -> Self { - Self { - word_spacing: Relative::new(0.25).into(), - line_spacing: Relative::new(0.2).into(), - par_spacing: Relative::new(0.5).into(), - } - } -} - -/// Defines font properties. -#[derive(Debug, Clone, PartialEq)] -pub struct FontSettings { - /// A tree of font family names and generic class names. - pub families: Rc<FallbackTree>, - /// The selected font variant. - pub variant: FontVariant, - /// The font size. - pub size: Length, - /// The linear to apply on the base font size. - pub scale: Linear, - /// Whether the strong toggle is active or inactive. This determines - /// whether the next `*` adds or removes font weight. - pub strong: bool, - /// Whether the emphasis toggle is active or inactive. This determines - /// whether the next `_` makes italic or non-italic. - pub emph: bool, -} - -impl FontSettings { - /// Access the `families` mutably. - pub fn families_mut(&mut self) -> &mut FallbackTree { - Rc::make_mut(&mut self.families) - } - - /// The absolute font size. - pub fn font_size(&self) -> Length { - self.scale.resolve(self.size) - } -} - -impl Default for FontSettings { - fn default() -> Self { - Self { - /// The default tree of font fallbacks. - families: Rc::new(fallback! { - list: ["sans-serif"], - classes: { - "serif" => ["source serif pro", "noto serif"], - "sans-serif" => ["source sans pro", "noto sans"], - "monospace" => ["source code pro", "noto sans mono"], - }, - base: [ - "source sans pro", - "noto sans", - "segoe ui emoji", - "noto emoji", - "latin modern math", - ], - }), - variant: FontVariant { - style: FontStyle::Normal, - weight: FontWeight::REGULAR, - stretch: FontStretch::Normal, - }, - size: Length::pt(11.0), - scale: Linear::ONE, - strong: false, - emph: false, - } - } -} diff --git a/src/eval/value.rs b/src/eval/value.rs index 119a2f1b..dd1221e5 100644 --- a/src/eval/value.rs +++ b/src/eval/value.rs @@ -4,11 +4,12 @@ use std::fmt::{self, Debug, Display, Formatter}; use std::ops::Deref; use std::rc::Rc; -use super::{Args, Eval, EvalContext}; +use super::*; use crate::color::Color; +use crate::exec::ExecContext; use crate::geom::{Angle, Length, Linear, Relative}; -use crate::pretty::{pretty, Pretty, Printer}; -use crate::syntax::{pretty_template, Spanned, Tree, WithSpan}; +use crate::pretty::{Pretty, Printer}; +use crate::syntax::Tree; /// A computational value. #[derive(Debug, Clone, PartialEq)] @@ -48,12 +49,12 @@ pub enum Value { } impl Value { - /// Try to cast the value into a specific type. - pub fn cast<T>(self) -> CastResult<T, Self> + /// Create a new template value consisting of a single dynamic node. + pub fn template<F>(f: F) -> Self where - T: Cast<Value>, + F: Fn(&mut ExecContext) + 'static, { - T::cast(self) + Self::Template(vec![TemplateNode::Any(TemplateAny::new(f))]) } /// The name of the stored value's type. @@ -77,26 +78,13 @@ impl Value { Self::Error => "error", } } -} - -impl Eval for &Value { - type Output = (); - /// Evaluate everything contained in this value. - fn eval(self, ctx: &mut EvalContext) -> Self::Output { - ctx.push(ctx.make_text_node(match self { - Value::None => return, - Value::Str(s) => s.clone(), - Value::Template(tree) => { - // We do not want to allow the template access to the current - // scopes. - let prev = std::mem::take(&mut ctx.scopes); - tree.eval(ctx); - ctx.scopes = prev; - return; - } - other => pretty(other), - })); + /// Try to cast the value into a specific type. + pub fn cast<T>(self) -> CastResult<T, Self> + where + T: Cast<Value>, + { + T::cast(self) } } @@ -121,7 +109,7 @@ impl Pretty for Value { Value::Str(v) => v.pretty(p), Value::Array(v) => v.pretty(p), Value::Dict(v) => v.pretty(p), - Value::Template(v) => pretty_template(v, p), + Value::Template(v) => v.pretty(p), Value::Func(v) => v.pretty(p), Value::Any(v) => v.pretty(p), Value::Error => p.push_str("(error)"), @@ -163,7 +151,88 @@ impl Pretty for ValueDict { } /// A template value: `[*Hi* there]`. -pub type ValueTemplate = Tree; +pub type ValueTemplate = Vec<TemplateNode>; + +impl Pretty for ValueTemplate { + fn pretty(&self, p: &mut Printer) { + p.push('['); + for part in self { + part.pretty(p); + } + p.push(']'); + } +} + +/// One chunk of a template. +/// +/// Evaluating a template expression creates only a single chunk. Adding two +/// such templates yields a two-chunk template. +#[derive(Debug, Clone, PartialEq)] +pub enum TemplateNode { + /// A template that consists of a syntax tree plus already evaluated + /// expression. + Tree { + /// The tree of this template part. + tree: Rc<Tree>, + /// The evaluated expressions. + map: ExprMap, + }, + /// A template that can implement custom behaviour. + Any(TemplateAny), +} + +impl Pretty for TemplateNode { + fn pretty(&self, p: &mut Printer) { + match self { + // TODO: Pretty-print the values. + Self::Tree { tree, .. } => tree.pretty(p), + Self::Any(any) => any.pretty(p), + } + } +} + +/// A reference-counted dynamic template node (can implement custom behaviour). +#[derive(Clone)] +pub struct TemplateAny { + f: Rc<dyn Fn(&mut ExecContext)>, +} + +impl TemplateAny { + /// Create a new dynamic template value from a rust function or closure. + pub fn new<F>(f: F) -> Self + where + F: Fn(&mut ExecContext) + 'static, + { + Self { f: Rc::new(f) } + } +} + +impl PartialEq for TemplateAny { + fn eq(&self, _: &Self) -> bool { + // TODO: Figure out what we want here. + false + } +} + +impl Deref for TemplateAny { + type Target = dyn Fn(&mut ExecContext); + + fn deref(&self) -> &Self::Target { + self.f.as_ref() + } +} + +impl Pretty for TemplateAny { + fn pretty(&self, p: &mut Printer) { + p.push_str("<any>"); + } +} + +impl Debug for TemplateAny { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.debug_struct("TemplateAny").finish() + } +} /// A wrapper around a reference-counted executable function. #[derive(Clone)] @@ -184,6 +253,7 @@ impl ValueFunc { impl PartialEq for ValueFunc { fn eq(&self, _: &Self) -> bool { + // TODO: Figure out what we want here. false } } @@ -497,9 +567,7 @@ macro_rules! impl_type { mod tests { use super::*; use crate::color::RgbaColor; - use crate::parse::parse; use crate::pretty::pretty; - use crate::syntax::Node; #[track_caller] fn test_pretty(value: impl Into<Value>, exp: &str) { @@ -517,7 +585,6 @@ mod tests { test_pretty(Relative::new(0.3) + Length::cm(2.0), "30.0% + 2.0cm"); test_pretty(Color::Rgba(RgbaColor::new(1, 1, 1, 0xff)), "#010101"); test_pretty("hello", r#""hello""#); - test_pretty(vec![Spanned::zero(Node::Strong)], "[*]"); test_pretty(ValueFunc::new("nil", |_, _| Value::None), "nil"); test_pretty(ValueAny::new(1), "1"); test_pretty(Value::Error, "(error)"); @@ -533,8 +600,8 @@ mod tests { // Dictionary. let mut dict = BTreeMap::new(); dict.insert("one".into(), Value::Int(1)); - dict.insert("two".into(), Value::Template(parse("#[f]").output)); + dict.insert("two".into(), Value::Bool(false)); test_pretty(BTreeMap::new(), "(:)"); - test_pretty(dict, "(one: 1, two: #[f])"); + test_pretty(dict, "(one: 1, two: false)"); } } |
