summaryrefslogtreecommitdiff
path: root/src/eval
diff options
context:
space:
mode:
Diffstat (limited to 'src/eval')
-rw-r--r--src/eval/class.rs101
-rw-r--r--src/eval/mod.rs28
-rw-r--r--src/eval/node.rs10
-rw-r--r--src/eval/scope.rs29
-rw-r--r--src/eval/value.rs7
5 files changed, 162 insertions, 13 deletions
diff --git a/src/eval/class.rs b/src/eval/class.rs
new file mode 100644
index 00000000..45674933
--- /dev/null
+++ b/src/eval/class.rs
@@ -0,0 +1,101 @@
+use std::fmt::{self, Debug, Formatter, Write};
+use std::marker::PhantomData;
+use std::rc::Rc;
+
+use super::{Args, EvalContext, Node, Styles};
+use crate::diag::TypResult;
+use crate::util::EcoString;
+
+/// A class of nodes.
+#[derive(Clone)]
+pub struct Class(Rc<Inner<dyn Bounds>>);
+
+/// The unsized structure behind the [`Rc`].
+struct Inner<T: ?Sized> {
+ name: EcoString,
+ dispatch: T,
+}
+
+impl Class {
+ /// Create a new class.
+ pub fn new<T>(name: EcoString) -> Self
+ where
+ T: Construct + Set + 'static,
+ {
+ Self(Rc::new(Inner {
+ name,
+ dispatch: Dispatch::<T>(PhantomData),
+ }))
+ }
+
+ /// The name of the class.
+ pub fn name(&self) -> &EcoString {
+ &self.0.name
+ }
+
+ /// Construct an instance of the class.
+ pub fn construct(&self, ctx: &mut EvalContext, args: &mut Args) -> TypResult<Node> {
+ self.0.dispatch.construct(ctx, args)
+ }
+
+ /// Execute the class's set rule.
+ pub fn set(&self, styles: &mut Styles, args: &mut Args) -> TypResult<()> {
+ self.0.dispatch.set(styles, args)
+ }
+}
+
+impl Debug for Class {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ f.write_str("<class ")?;
+ f.write_str(&self.0.name)?;
+ f.write_char('>')
+ }
+}
+
+impl PartialEq for Class {
+ fn eq(&self, other: &Self) -> bool {
+ // We cast to thin pointers for comparison.
+ std::ptr::eq(
+ Rc::as_ptr(&self.0) as *const (),
+ Rc::as_ptr(&other.0) as *const (),
+ )
+ }
+}
+
+/// Construct an instance of a class.
+pub trait Construct {
+ /// Construct an instance of this class from the arguments.
+ ///
+ /// This is passed only the arguments that remain after execution of the
+ /// class's set rule.
+ fn construct(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Node>;
+}
+
+/// Set style properties of a class.
+pub trait Set {
+ /// Parse the arguments and insert style properties of this class into the
+ /// given style map.
+ fn set(styles: &mut Styles, args: &mut Args) -> TypResult<()>;
+}
+
+/// Zero-sized struct whose vtable contains the constructor and set rule of a
+/// class.
+struct Dispatch<T>(PhantomData<T>);
+
+trait Bounds {
+ fn construct(&self, ctx: &mut EvalContext, args: &mut Args) -> TypResult<Node>;
+ fn set(&self, styles: &mut Styles, args: &mut Args) -> TypResult<()>;
+}
+
+impl<T> Bounds for Dispatch<T>
+where
+ T: Construct + Set,
+{
+ fn construct(&self, ctx: &mut EvalContext, args: &mut Args) -> TypResult<Node> {
+ T::construct(ctx, args)
+ }
+
+ fn set(&self, styles: &mut Styles, args: &mut Args) -> TypResult<()> {
+ T::set(styles, args)
+ }
+}
diff --git a/src/eval/mod.rs b/src/eval/mod.rs
index 6dcff900..ae330134 100644
--- a/src/eval/mod.rs
+++ b/src/eval/mod.rs
@@ -9,6 +9,7 @@ mod value;
#[macro_use]
mod styles;
mod capture;
+mod class;
mod function;
mod node;
mod ops;
@@ -16,6 +17,7 @@ mod scope;
pub use array::*;
pub use capture::*;
+pub use class::*;
pub use dict::*;
pub use function::*;
pub use node::*;
@@ -54,7 +56,7 @@ pub fn eval(ctx: &mut Context, source: SourceId, markup: &Markup) -> TypResult<M
pub struct Module {
/// The top-level definitions that were bound in this module.
pub scope: Scope,
- /// The node defined by this module.
+ /// The module's layoutable contents.
pub node: Node,
}
@@ -288,6 +290,7 @@ impl Eval for Expr {
Self::Unary(v) => v.eval(ctx),
Self::Binary(v) => v.eval(ctx),
Self::Let(v) => v.eval(ctx),
+ Self::Set(v) => v.eval(ctx),
Self::If(v) => v.eval(ctx),
Self::While(v) => v.eval(ctx),
Self::For(v) => v.eval(ctx),
@@ -474,9 +477,17 @@ impl Eval for CallExpr {
Ok(value)
}
+ Value::Class(class) => {
+ let mut styles = Styles::new();
+ class.set(&mut styles, &mut args)?;
+ let node = class.construct(ctx, &mut args)?;
+ args.finish()?;
+ Ok(Value::Node(node.styled(styles)))
+ }
+
v => bail!(
self.callee().span(),
- "expected function or collection, found {}",
+ "expected callable or collection, found {}",
v.type_name(),
),
}
@@ -643,6 +654,19 @@ impl Eval for LetExpr {
}
}
+impl Eval for SetExpr {
+ type Output = Value;
+
+ fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
+ let class = self.class();
+ let class = class.eval(ctx)?.cast::<Class>().at(class.span())?;
+ let mut args = self.args().eval(ctx)?;
+ class.set(&mut ctx.styles, &mut args)?;
+ args.finish()?;
+ Ok(Value::None)
+ }
+}
+
impl Eval for IfExpr {
type Output = Value;
diff --git a/src/eval/node.rs b/src/eval/node.rs
index 5653beff..a04fe84b 100644
--- a/src/eval/node.rs
+++ b/src/eval/node.rs
@@ -36,6 +36,8 @@ pub enum Node {
Inline(PackedNode),
/// A block node.
Block(PackedNode),
+ /// A page node.
+ Page(PackedNode),
/// A sequence of nodes (which may themselves contain sequences).
Sequence(Vec<(Self, Styles)>),
}
@@ -214,6 +216,14 @@ impl Packer {
Node::Block(block) => {
self.push_block(block.styled(styles));
}
+ Node::Page(flow) => {
+ if self.top {
+ self.pagebreak();
+ self.pages.push(PageNode { child: flow, styles });
+ } else {
+ self.push_block(flow.styled(styles));
+ }
+ }
Node::Sequence(list) => {
// For a list of nodes, we apply the list's styles to each node
// individually.
diff --git a/src/eval/scope.rs b/src/eval/scope.rs
index ffe2d63e..5178c819 100644
--- a/src/eval/scope.rs
+++ b/src/eval/scope.rs
@@ -4,7 +4,7 @@ use std::fmt::{self, Debug, Formatter};
use std::iter;
use std::rc::Rc;
-use super::{Args, EvalContext, Function, Value};
+use super::{Args, Class, Construct, EvalContext, Function, Set, Value};
use crate::diag::TypResult;
use crate::util::EcoString;
@@ -88,15 +88,6 @@ impl Scope {
self.values.insert(var.into(), Rc::new(cell));
}
- /// Define a constant function.
- pub fn def_func<F>(&mut self, name: impl Into<EcoString>, f: F)
- where
- F: Fn(&mut EvalContext, &mut Args) -> TypResult<Value> + 'static,
- {
- let name = name.into();
- self.def_const(name.clone(), Function::new(Some(name), f));
- }
-
/// Define a mutable variable with a value.
pub fn def_mut(&mut self, var: impl Into<EcoString>, value: impl Into<Value>) {
self.values.insert(var.into(), Rc::new(RefCell::new(value.into())));
@@ -107,6 +98,24 @@ impl Scope {
self.values.insert(var.into(), slot);
}
+ /// Define a constant function.
+ pub fn def_func<F>(&mut self, name: &str, f: F)
+ where
+ F: Fn(&mut EvalContext, &mut Args) -> TypResult<Value> + 'static,
+ {
+ let name = EcoString::from(name);
+ self.def_const(name.clone(), Function::new(Some(name), f));
+ }
+
+ /// Define a constant class.
+ pub fn def_class<T>(&mut self, name: &str)
+ where
+ T: Construct + Set + 'static,
+ {
+ let name = EcoString::from(name);
+ self.def_const(name.clone(), Class::new::<T>(name));
+ }
+
/// Look up the value of a variable.
pub fn get(&self, var: &str) -> Option<&Slot> {
self.values.get(var)
diff --git a/src/eval/value.rs b/src/eval/value.rs
index 2cf82a26..0995ab75 100644
--- a/src/eval/value.rs
+++ b/src/eval/value.rs
@@ -4,7 +4,7 @@ use std::fmt::{self, Debug, Formatter};
use std::hash::Hash;
use std::rc::Rc;
-use super::{ops, Array, Dict, Function, Node};
+use super::{ops, Array, Class, Dict, Function, Node};
use crate::diag::StrResult;
use crate::geom::{Angle, Color, Fractional, Length, Linear, Relative, RgbaColor};
use crate::layout::Layout;
@@ -46,6 +46,8 @@ pub enum Value {
Node(Node),
/// An executable function.
Func(Function),
+ /// A class of nodes.
+ Class(Class),
/// A dynamic value.
Dyn(Dynamic),
}
@@ -86,6 +88,7 @@ impl Value {
Self::Dict(_) => Dict::TYPE_NAME,
Self::Node(_) => Node::TYPE_NAME,
Self::Func(_) => Function::TYPE_NAME,
+ Self::Class(_) => Class::TYPE_NAME,
Self::Dyn(v) => v.type_name(),
}
}
@@ -148,6 +151,7 @@ impl Debug for Value {
Self::Dict(v) => Debug::fmt(v, f),
Self::Node(_) => f.pad("<template>"),
Self::Func(v) => Debug::fmt(v, f),
+ Self::Class(v) => Debug::fmt(v, f),
Self::Dyn(v) => Debug::fmt(v, f),
}
}
@@ -394,6 +398,7 @@ primitive! { Array: "array", Array }
primitive! { Dict: "dictionary", Dict }
primitive! { Node: "template", Node }
primitive! { Function: "function", Func }
+primitive! { Class: "class", Class }
impl Cast<Value> for Value {
fn is(_: &Value) -> bool {