summaryrefslogtreecommitdiff
path: root/src/eval
diff options
context:
space:
mode:
Diffstat (limited to 'src/eval')
-rw-r--r--src/eval/call.rs22
-rw-r--r--src/eval/capture.rs8
-rw-r--r--src/eval/context.rs304
-rw-r--r--src/eval/mod.rs364
-rw-r--r--src/eval/scope.rs19
-rw-r--r--src/eval/state.rs163
-rw-r--r--src/eval/value.rs133
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)");
}
}