summaryrefslogtreecommitdiff
path: root/src/eval
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2021-01-03 00:12:09 +0100
committerLaurenz <laurmaedje@gmail.com>2021-01-03 00:12:09 +0100
commitaae67bd572ad86f4c57e364daa51a9dc883b8913 (patch)
tree0aba021e0748ebad2197ea390385ec5f93ccbc6e /src/eval
parent1c40dc42e7bc7b799b77f06d25414aca59a044ba (diff)
Move and rename many things 🚛
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.rs298
-rw-r--r--src/eval/mod.rs383
-rw-r--r--src/eval/scope.rs4
-rw-r--r--src/eval/state.rs58
-rw-r--r--src/eval/value.rs22
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)
}
}