diff options
Diffstat (limited to 'src/eval')
| -rw-r--r-- | src/eval/call.rs (renamed from src/eval/args.rs) | 52 | ||||
| -rw-r--r-- | src/eval/context.rs | 298 | ||||
| -rw-r--r-- | src/eval/mod.rs | 383 | ||||
| -rw-r--r-- | src/eval/scope.rs | 4 | ||||
| -rw-r--r-- | src/eval/state.rs | 58 | ||||
| -rw-r--r-- | src/eval/value.rs | 22 |
6 files changed, 417 insertions, 400 deletions
diff --git a/src/eval/args.rs b/src/eval/call.rs index ea248ec4..186e7630 100644 --- a/src/eval/args.rs +++ b/src/eval/call.rs @@ -1,14 +1,37 @@ use super::*; +use crate::diag::Deco; -/// Evaluated arguments to a function. -#[derive(Debug)] -pub struct Args { - span: Span, - pos: SpanVec<Value>, - named: Vec<(Spanned<String>, Spanned<Value>)>, +impl Eval for Spanned<&ExprCall> { + type Output = Value; + + fn eval(self, ctx: &mut EvalContext) -> Self::Output { + let name = &self.v.name.v; + let span = self.v.name.span; + + if let Some(value) = ctx.state.scope.get(name) { + if let Value::Func(func) = value { + let func = func.clone(); + ctx.deco(Deco::Resolved.with_span(span)); + + let mut args = self.v.args.as_ref().eval(ctx); + let returned = func(ctx, &mut args); + args.finish(ctx); + + return returned; + } else { + let ty = value.type_name(); + ctx.diag(error!(span, "a value of type {} is not callable", ty)); + } + } else if !name.is_empty() { + ctx.diag(error!(span, "unknown function")); + } + + ctx.deco(Deco::Unresolved.with_span(span)); + Value::Error + } } -impl Eval for Spanned<&Arguments> { +impl Eval for Spanned<&ExprArgs> { type Output = Args; fn eval(self, ctx: &mut EvalContext) -> Self::Output { @@ -33,6 +56,14 @@ impl Eval for Spanned<&Arguments> { } } +/// Evaluated arguments to a function. +#[derive(Debug)] +pub struct Args { + span: Span, + pos: SpanVec<Value>, + named: Vec<(Spanned<String>, Spanned<Value>)>, +} + impl Args { /// Find the first convertible positional argument. pub fn find<T>(&mut self, ctx: &mut EvalContext) -> Option<T> @@ -66,9 +97,8 @@ impl Args { self.pos.iter_mut().filter_map(move |slot| try_cast(ctx, slot)) } - /// Convert the value for the given named argument. - /// - /// Generates an error if the conversion fails. + /// Convert the value for the given named argument, producing an error if + /// the conversion fails. pub fn get<'a, T>(&mut self, ctx: &mut EvalContext, name: &str) -> Option<T> where T: Cast<Spanned<Value>>, @@ -78,7 +108,7 @@ impl Args { cast(ctx, value) } - /// Generate "unexpected argument" errors for all remaining arguments. + /// Produce "unexpected argument" errors for all remaining arguments. pub fn finish(self, ctx: &mut EvalContext) { let a = self.pos.iter().map(|v| v.as_ref()); let b = self.named.iter().map(|(k, v)| (&v.v).with_span(k.span.join(v.span))); diff --git a/src/eval/context.rs b/src/eval/context.rs new file mode 100644 index 00000000..ece33146 --- /dev/null +++ b/src/eval/context.rs @@ -0,0 +1,298 @@ +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::env::SharedEnv; +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 { + /// The environment from which resources are gathered. + pub env: SharedEnv, + /// 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 EvalContext { + /// Create a new evaluation context with a base state. + pub fn new(env: SharedEnv, state: State) -> Self { + Self { + env, + 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, + 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: Node::any(NodePad { + padding: group.padding, + child: Node::any(NodeStack { + dirs: group.dirs, + align: group.align, + expansion: Gen::uniform(Expansion::Fill), + children, + }), + }), + }) + } + 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, + 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 62bd444c..20d32e84 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -1,36 +1,32 @@ -//! Evaluation of syntax trees. +//! Evaluation of syntax trees into layout trees. #[macro_use] mod value; -mod args; +mod call; +mod context; mod scope; mod state; -pub use args::*; +pub use call::*; +pub use context::*; pub use scope::*; pub use state::*; pub use value::*; -use std::any::Any; use std::rc::Rc; -use fontdock::FontStyle; - use crate::color::Color; -use crate::diag::Diag; -use crate::diag::{Deco, Feedback, Pass}; +use crate::diag::Pass; use crate::env::SharedEnv; -use crate::geom::{BoxAlign, Dir, Flow, Gen, Length, Linear, Relative, Sides, Size}; -use crate::layout::{ - Document, Expansion, LayoutNode, Pad, Pages, Par, Spacing, Stack, Text, -}; +use crate::geom::{Gen, Length, Relative}; +use crate::layout::{self, Expansion, NodeSpacing, NodeStack}; use crate::syntax::*; -/// Evaluate a syntax tree into a document. +/// Evaluate a syntax tree into a layout tree. /// /// The given `state` is the base state that may be updated over the course of /// evaluation. -pub fn eval(tree: &SynTree, env: SharedEnv, state: State) -> Pass<Document> { +pub fn eval(tree: &Tree, env: SharedEnv, state: State) -> Pass<layout::Tree> { let mut ctx = EvalContext::new(env, state); ctx.start_page_group(Softness::Hard); tree.eval(&mut ctx); @@ -38,285 +34,6 @@ pub fn eval(tree: &SynTree, env: SharedEnv, state: State) -> Pass<Document> { ctx.finish() } -/// The context for evaluation. -#[derive(Debug)] -pub struct EvalContext { - /// The environment from which resources are gathered. - pub env: SharedEnv, - /// The active evaluation state. - pub state: State, - /// The accumulated feedback. - feedback: Feedback, - /// The finished page runs. - runs: Vec<Pages>, - /// 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<LayoutNode>)>, - /// The nodes in the current innermost group - /// (whose metadata is in `groups.last()`). - inner: Vec<LayoutNode>, -} - -impl EvalContext { - /// Create a new evaluation context with a base state. - pub fn new(env: SharedEnv, state: State) -> Self { - Self { - env, - state, - groups: vec![], - inner: vec![], - runs: vec![], - feedback: Feedback::new(), - } - } - - /// Finish evaluation and return the created document. - pub fn finish(self) -> Pass<Document> { - assert!(self.groups.is_empty(), "unfinished group"); - Pass::new(Document { 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<LayoutNode>) { - let node = node.into(); - - if let LayoutNode::Spacing(this) = node { - if this.softness == Softness::Soft && self.inner.is_empty() { - return; - } - - if let Some(&LayoutNode::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, - padding: self.state.page.margins(), - flow: self.state.flow, - 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( - &mut self, - keep_empty: impl FnOnce(Softness) -> bool, - ) -> Softness { - self.end_par_group(); - let (group, children) = self.end_group::<PageGroup>(); - if !children.is_empty() || keep_empty(group.softness) { - self.runs.push(Pages { - size: group.size, - child: LayoutNode::dynamic(Pad { - padding: group.padding, - child: LayoutNode::dynamic(Stack { - flow: group.flow, - align: group.align, - expansion: Gen::uniform(Expansion::Fill), - children, - }), - }), - }) - } - 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<LayoutNode> { - 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 { - flow: self.state.flow, - 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() { - // FIXME: This is a hack and should be superseded by something - // better. - let cross_expansion = Expansion::fill_if(self.groups.len() <= 1); - self.push(Par { - flow: group.flow, - align: group.align, - cross_expansion, - 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<LayoutNode>) { - if let Some(&LayoutNode::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)) - } - - /// Updates the flow directions if the resulting main and cross directions - /// apply to different axes. Generates an appropriate error, otherwise. - pub fn set_flow(&mut self, new: Gen<Option<Spanned<Dir>>>) { - let flow = Gen::new( - new.main.map(|s| s.v).unwrap_or(self.state.flow.main), - new.cross.map(|s| s.v).unwrap_or(self.state.flow.cross), - ); - - if flow.main.axis() != flow.cross.axis() { - self.state.flow = flow; - } 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(Spacing { - 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) -> Text { - 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, - } - } - - Text { - text, - align: self.state.align, - dir: self.state.flow.cross, - font_size: self.state.font.font_size(), - families: Rc::clone(&self.state.font.families), - variant, - } - } -} - -/// A group for page runs. -struct PageGroup { - size: Size, - padding: Sides<Linear>, - flow: Flow, - align: BoxAlign, - softness: Softness, -} - -/// A group for generic content. -struct ContentGroup; - -/// A group for paragraphs. -struct ParGroup { - flow: Flow, - align: BoxAlign, - line_spacing: Length, -} - -/// Defines how an item interact 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, -} - /// Evaluate an item. /// /// _Note_: Evaluation is not necessarily pure, it may change the active state. @@ -339,7 +56,7 @@ where } } -impl Eval for &[Spanned<SynNode>] { +impl Eval for &[Spanned<Node>] { type Output = (); fn eval(self, ctx: &mut EvalContext) -> Self::Output { @@ -349,33 +66,33 @@ impl Eval for &[Spanned<SynNode>] { } } -impl Eval for Spanned<&SynNode> { +impl Eval for Spanned<&Node> { type Output = (); fn eval(self, ctx: &mut EvalContext) -> Self::Output { match self.v { - SynNode::Text(text) => { + Node::Text(text) => { let node = ctx.make_text_node(text.clone()); ctx.push(node); } - SynNode::Space => { + Node::Space => { let em = ctx.state.font.font_size(); - ctx.push(Spacing { + ctx.push(NodeSpacing { amount: ctx.state.par.word_spacing.resolve(em), softness: Softness::Soft, }); } - SynNode::Linebreak => ctx.apply_linebreak(), - SynNode::Parbreak => ctx.apply_parbreak(), + Node::Linebreak => ctx.apply_linebreak(), + Node::Parbreak => ctx.apply_parbreak(), - SynNode::Strong => ctx.state.font.strong ^= true, - SynNode::Emph => ctx.state.font.emph ^= true, + Node::Strong => ctx.state.font.strong ^= true, + Node::Emph => ctx.state.font.emph ^= true, - SynNode::Heading(heading) => heading.with_span(self.span).eval(ctx), - SynNode::Raw(raw) => raw.with_span(self.span).eval(ctx), + Node::Heading(heading) => heading.with_span(self.span).eval(ctx), + Node::Raw(raw) => raw.with_span(self.span).eval(ctx), - SynNode::Expr(expr) => { + Node::Expr(expr) => { let value = expr.with_span(self.span).eval(ctx); value.eval(ctx) } @@ -413,15 +130,15 @@ impl Eval for Spanned<&NodeRaw> { let mut children = vec![]; for line in &self.v.lines { - children.push(LayoutNode::Text(ctx.make_text_node(line.clone()))); - children.push(LayoutNode::Spacing(Spacing { + children.push(layout::Node::Text(ctx.make_text_node(line.clone()))); + children.push(layout::Node::Spacing(NodeSpacing { amount: line_spacing, softness: Softness::Hard, })); } - ctx.push(Stack { - flow: ctx.state.flow, + ctx.push(NodeStack { + dirs: ctx.state.dirs, align: ctx.state.align, expansion: Gen::uniform(Expansion::Fit), children, @@ -436,10 +153,13 @@ impl Eval for Spanned<&Expr> { fn eval(self, ctx: &mut EvalContext) -> Self::Output { match self.v { - Expr::Lit(lit) => lit.with_span(self.span).eval(ctx), - Expr::Call(call) => call.with_span(self.span).eval(ctx), - Expr::Unary(unary) => unary.with_span(self.span).eval(ctx), - Expr::Binary(binary) => binary.with_span(self.span).eval(ctx), + Expr::Lit(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::Array(v) => Value::Array(v.with_span(self.span).eval(ctx)), + Expr::Dict(v) => Value::Dict(v.with_span(self.span).eval(ctx)), + Expr::Content(v) => Value::Content(v.clone()), } } } @@ -463,14 +183,11 @@ impl Eval for Spanned<&Lit> { Lit::Percent(v) => Value::Relative(Relative::new(v / 100.0)), Lit::Color(v) => Value::Color(Color::Rgba(v)), Lit::Str(ref v) => Value::Str(v.clone()), - Lit::Array(ref v) => Value::Array(v.with_span(self.span).eval(ctx)), - Lit::Dict(ref v) => Value::Dict(v.with_span(self.span).eval(ctx)), - Lit::Content(ref v) => Value::Content(v.clone()), } } } -impl Eval for Spanned<&Array> { +impl Eval for Spanned<&ExprArray> { type Output = ValueArray; fn eval(self, ctx: &mut EvalContext) -> Self::Output { @@ -478,7 +195,7 @@ impl Eval for Spanned<&Array> { } } -impl Eval for Spanned<&Dict> { +impl Eval for Spanned<&ExprDict> { type Output = ValueDict; fn eval(self, ctx: &mut EvalContext) -> Self::Output { @@ -489,36 +206,6 @@ impl Eval for Spanned<&Dict> { } } -impl Eval for Spanned<&ExprCall> { - type Output = Value; - - fn eval(self, ctx: &mut EvalContext) -> Self::Output { - let name = &self.v.name.v; - let span = self.v.name.span; - - if let Some(value) = ctx.state.scope.get(name) { - if let Value::Func(func) = value { - let func = func.clone(); - ctx.feedback.decos.push(Deco::Resolved.with_span(span)); - - let mut args = self.v.args.as_ref().eval(ctx); - let returned = func(ctx, &mut args); - args.finish(ctx); - - return returned; - } else { - let ty = value.type_name(); - ctx.diag(error!(span, "a value of type {} is not callable", ty)); - } - } else if !name.is_empty() { - ctx.diag(error!(span, "unknown function")); - } - - ctx.feedback.decos.push(Deco::Unresolved.with_span(span)); - Value::Error - } -} - impl Eval for Spanned<&ExprUnary> { type Output = Value; diff --git a/src/eval/scope.rs b/src/eval/scope.rs index c9ce1423..dd7cc1da 100644 --- a/src/eval/scope.rs +++ b/src/eval/scope.rs @@ -1,11 +1,9 @@ -//! Mapping from identifiers to functions. - use std::collections::HashMap; use std::fmt::{self, Debug, Formatter}; use super::Value; -/// A map from identifiers to functions. +/// A map from identifiers to values. #[derive(Default, Clone, PartialEq)] pub struct Scope { values: HashMap<String, Value>, diff --git a/src/eval/state.rs b/src/eval/state.rs index 3f3ac8e4..9cdafaf2 100644 --- a/src/eval/state.rs +++ b/src/eval/state.rs @@ -1,46 +1,46 @@ -//! Evaluation state. - use std::rc::Rc; use fontdock::{fallback, FallbackTree, FontStretch, FontStyle, FontVariant, FontWeight}; use super::Scope; -use crate::geom::{Align, BoxAlign, Dir, Flow, Length, Linear, Relative, Sides, Size}; +use crate::geom::{ + Align, ChildAlign, Dir, LayoutDirs, Length, Linear, Relative, Sides, Size, +}; use crate::paper::{Paper, PaperClass, PAPER_A4}; -/// The active evaluation state. +/// The evaluation state. #[derive(Debug, Clone, PartialEq)] pub struct State { - /// The scope that contains function definitions. + /// The scope that contains variable definitions. pub scope: Scope, - /// The page state. - pub page: PageState, - /// The paragraph state. - pub par: ParState, - /// The font state. - pub font: FontState, - /// The active layouting directions. - pub flow: Flow, - /// The active box alignments. - pub align: BoxAlign, + /// The current page state. + pub page: StatePage, + /// The current paragraph state. + pub par: StatePar, + /// The current font state. + pub font: StateFont, + /// The current directions. + pub dirs: LayoutDirs, + /// The current alignments. + pub align: ChildAlign, } impl Default for State { fn default() -> Self { Self { scope: crate::library::_std(), - page: PageState::default(), - par: ParState::default(), - font: FontState::default(), - flow: Flow::new(Dir::TTB, Dir::LTR), - align: BoxAlign::new(Align::Start, Align::Start), + page: StatePage::default(), + par: StatePar::default(), + font: StateFont::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 PageState { +pub struct StatePage { /// The class of this page. pub class: PaperClass, /// The width and height of the page. @@ -50,7 +50,7 @@ pub struct PageState { pub margins: Sides<Option<Linear>>, } -impl PageState { +impl StatePage { /// The default page style for the given paper. pub fn new(paper: Paper) -> Self { Self { @@ -72,7 +72,7 @@ impl PageState { } } -impl Default for PageState { +impl Default for StatePage { fn default() -> Self { Self::new(PAPER_A4) } @@ -80,7 +80,7 @@ impl Default for PageState { /// Defines paragraph properties. #[derive(Debug, Copy, Clone, PartialEq)] -pub struct ParState { +pub struct StatePar { /// The spacing between words (dependent on scaled font size). pub word_spacing: Linear, /// The spacing between lines (dependent on scaled font size). @@ -89,7 +89,7 @@ pub struct ParState { pub par_spacing: Linear, } -impl Default for ParState { +impl Default for StatePar { fn default() -> Self { Self { word_spacing: Relative::new(0.25).into(), @@ -101,7 +101,7 @@ impl Default for ParState { /// Defines font properties. #[derive(Debug, Clone, PartialEq)] -pub struct FontState { +pub struct StateFont { /// A tree of font family names and generic class names. pub families: Rc<FallbackTree>, /// The selected font variant. @@ -118,14 +118,14 @@ pub struct FontState { pub emph: bool, } -impl FontState { +impl StateFont { /// The absolute font size. pub fn font_size(&self) -> Length { self.scale.resolve(self.size) } } -impl Default for FontState { +impl Default for StateFont { fn default() -> Self { Self { families: Rc::new(default_font_families()), @@ -150,8 +150,6 @@ fn default_font_families() -> FallbackTree { "serif" => ["source serif pro", "noto serif"], "sans-serif" => ["source sans pro", "noto sans"], "monospace" => ["source code pro", "noto sans mono"], - "emoji" => ["segoe ui emoji", "noto emoji"], - "math" => ["latin modern math", "serif"], }, base: [ "source sans pro", diff --git a/src/eval/value.rs b/src/eval/value.rs index d1dcdcfa..a91ff137 100644 --- a/src/eval/value.rs +++ b/src/eval/value.rs @@ -1,5 +1,3 @@ -//! Computational values. - use std::any::Any; use std::collections::HashMap; use std::fmt::{self, Debug, Formatter}; @@ -9,7 +7,7 @@ use std::rc::Rc; use super::{Args, Eval, EvalContext}; use crate::color::Color; use crate::geom::{Length, Linear, Relative}; -use crate::syntax::{Spanned, SynTree, WithSpan}; +use crate::syntax::{Spanned, Tree, WithSpan}; /// A computational value. #[derive(Clone, PartialEq)] @@ -47,6 +45,14 @@ pub enum Value { } impl Value { + /// Create a new dynamic value. + pub fn any<T>(any: T) -> Self + where + T: Type + Debug + Clone + PartialEq + 'static, + { + Self::Any(ValueAny::new(any)) + } + /// Try to cast the value into a specific type. pub fn cast<T>(self) -> CastResult<T, Self> where @@ -130,7 +136,7 @@ pub type ValueArray = Vec<Value>; pub type ValueDict = HashMap<String, Value>; /// A content value: `{*Hi* there}`. -pub type ValueContent = SynTree; +pub type ValueContent = Tree; /// A wrapper around a reference-counted executable function. #[derive(Clone)] @@ -197,7 +203,7 @@ impl ValueAny { self.0.as_any().downcast_ref() } - /// The name of the stored object's type. + /// The name of the stored value's type. pub fn type_name(&self) -> &'static str { self.0.dyn_type_name() } @@ -289,7 +295,7 @@ pub enum CastResult<T, V> { } impl<T, V> CastResult<T, V> { - /// Access the conversion resulting, discarding a possibly existing warning. + /// Access the conversion result, discarding a possibly existing warning. pub fn ok(self) -> Option<T> { match self { CastResult::Ok(t) | CastResult::Warn(t, _) => Some(t), @@ -399,7 +405,7 @@ impl From<ValueAny> for Value { } } -/// Make a type usable with [`ValueAny`]. +/// Make a type usable as a [`Value`]. /// /// Given a type `T`, this implements the following traits: /// - [`Type`] for `T`, @@ -419,7 +425,7 @@ macro_rules! impl_type { impl From<$type> for $crate::eval::Value { fn from(any: $type) -> Self { - $crate::eval::Value::Any($crate::eval::ValueAny::new(any)) + $crate::eval::Value::any(any) } } |
