summaryrefslogtreecommitdiff
path: root/src/eval
diff options
context:
space:
mode:
Diffstat (limited to 'src/eval')
-rw-r--r--src/eval/capture.rs2
-rw-r--r--src/eval/class.rs8
-rw-r--r--src/eval/mod.rs52
-rw-r--r--src/eval/styles.rs387
-rw-r--r--src/eval/template.rs13
-rw-r--r--src/eval/value.rs12
6 files changed, 272 insertions, 202 deletions
diff --git a/src/eval/capture.rs b/src/eval/capture.rs
index d62ec55c..fd6b0101 100644
--- a/src/eval/capture.rs
+++ b/src/eval/capture.rs
@@ -50,7 +50,7 @@ impl<'a> CapturesVisitor<'a> {
Some(Expr::Ident(ident)) => self.capture(ident),
// A closure contains parameter bindings, which are bound before the
- // body is evaluated. Take must be taken so that the default values
+ // body is evaluated. Care must be taken so that the default values
// of named parameters cannot access previous parameter bindings.
Some(Expr::Closure(expr)) => {
for param in expr.params() {
diff --git a/src/eval/class.rs b/src/eval/class.rs
index 28e49a99..5601fb0b 100644
--- a/src/eval/class.rs
+++ b/src/eval/class.rs
@@ -1,3 +1,4 @@
+use std::any::TypeId;
use std::fmt::{self, Debug, Formatter, Write};
use std::hash::{Hash, Hasher};
@@ -36,6 +37,7 @@ use crate::diag::TypResult;
#[derive(Clone)]
pub struct Class {
name: &'static str,
+ id: TypeId,
construct: fn(&mut Vm, &mut Args) -> TypResult<Value>,
set: fn(&mut Args, &mut StyleMap) -> TypResult<()>,
}
@@ -48,6 +50,7 @@ impl Class {
{
Self {
name,
+ id: TypeId::of::<T>(),
construct: |ctx, args| {
let mut styles = StyleMap::new();
T::set(args, &mut styles)?;
@@ -63,6 +66,11 @@ impl Class {
self.name
}
+ /// The type id of the class.
+ pub fn id(&self) -> TypeId {
+ self.id
+ }
+
/// Return the class constructor as a function.
pub fn constructor(&self) -> Func {
Func::native(self.name, self.construct)
diff --git a/src/eval/mod.rs b/src/eval/mod.rs
index 32ffb0c9..1b61ac15 100644
--- a/src/eval/mod.rs
+++ b/src/eval/mod.rs
@@ -36,7 +36,6 @@ use unicode_segmentation::UnicodeSegmentation;
use crate::diag::{At, Error, StrResult, Trace, Tracepoint, TypResult};
use crate::geom::{Angle, Fractional, Length, Relative};
-use crate::layout::Layout;
use crate::library;
use crate::syntax::ast::*;
use crate::syntax::{Span, Spanned};
@@ -80,15 +79,12 @@ fn eval_markup(
while let Some(node) = nodes.next() {
seq.push(match node {
MarkupNode::Expr(Expr::Set(set)) => {
- let class = set.class();
- let class = class.eval(vm)?.cast::<Class>().at(class.span())?;
- let args = set.args().eval(vm)?;
- let styles = class.set(args)?;
- let tail = eval_markup(vm, nodes)?;
- tail.styled_with_map(styles)
+ let styles = set.eval(vm)?;
+ eval_markup(vm, nodes)?.styled_with_map(styles)
}
MarkupNode::Expr(Expr::Show(show)) => {
- return Err("show rules are not yet implemented").at(show.span());
+ let styles = show.eval(vm)?;
+ eval_markup(vm, nodes)?.styled_with_map(styles)
}
MarkupNode::Expr(Expr::Wrap(wrap)) => {
let tail = eval_markup(vm, nodes)?;
@@ -182,7 +178,7 @@ impl Eval for ListNode {
fn eval(&self, vm: &mut Vm) -> TypResult<Self::Output> {
Ok(Template::List(library::ListItem {
number: None,
- body: self.body().eval(vm)?.pack(),
+ body: Box::new(self.body().eval(vm)?),
}))
}
}
@@ -193,7 +189,7 @@ impl Eval for EnumNode {
fn eval(&self, vm: &mut Vm) -> TypResult<Self::Output> {
Ok(Template::Enum(library::ListItem {
number: self.number(),
- body: self.body().eval(vm)?.pack(),
+ body: Box::new(self.body().eval(vm)?),
}))
}
}
@@ -216,9 +212,10 @@ impl Eval for Expr {
Self::Unary(v) => v.eval(vm),
Self::Binary(v) => v.eval(vm),
Self::Let(v) => v.eval(vm),
- Self::Set(v) => v.eval(vm),
- Self::Show(v) => v.eval(vm),
- Self::Wrap(v) => v.eval(vm),
+ Self::Set(_) | Self::Show(_) | Self::Wrap(_) => {
+ Err("set, show and wrap are only allowed directly in markup")
+ .at(self.span())
+ }
Self::If(v) => v.eval(vm),
Self::While(v) => v.eval(vm),
Self::For(v) => v.eval(vm),
@@ -551,26 +548,27 @@ impl Eval for LetExpr {
}
impl Eval for SetExpr {
- type Output = Value;
+ type Output = StyleMap;
- fn eval(&self, _: &mut Vm) -> TypResult<Self::Output> {
- Err("set is only allowed directly in markup").at(self.span())
+ fn eval(&self, vm: &mut Vm) -> TypResult<Self::Output> {
+ let class = self.class();
+ let class = class.eval(vm)?.cast::<Class>().at(class.span())?;
+ let args = self.args().eval(vm)?;
+ class.set(args)
}
}
impl Eval for ShowExpr {
- type Output = Value;
+ type Output = StyleMap;
- fn eval(&self, _: &mut Vm) -> TypResult<Self::Output> {
- Err("show is only allowed directly in markup").at(self.span())
- }
-}
-
-impl Eval for WrapExpr {
- type Output = Value;
-
- fn eval(&self, _: &mut Vm) -> TypResult<Self::Output> {
- Err("wrap is only allowed directly in markup").at(self.span())
+ fn eval(&self, vm: &mut Vm) -> TypResult<Self::Output> {
+ let class = self.class();
+ let class = class.eval(vm)?.cast::<Class>().at(class.span())?;
+ let closure = self.closure();
+ let func = closure.eval(vm)?.cast::<Func>().at(closure.span())?;
+ let mut styles = StyleMap::new();
+ styles.set_recipe(class.id(), func, self.span());
+ Ok(styles)
}
}
diff --git a/src/eval/styles.rs b/src/eval/styles.rs
index 2bf3f239..346eb784 100644
--- a/src/eval/styles.rs
+++ b/src/eval/styles.rs
@@ -3,21 +3,28 @@ use std::fmt::{self, Debug, Formatter};
use std::hash::{Hash, Hasher};
use std::sync::Arc;
+use super::{Args, Func, Span, Template, Value, Vm};
+use crate::diag::{At, TypResult};
use crate::library::{PageNode, ParNode};
/// A map of style properties.
#[derive(Default, Clone, PartialEq, Hash)]
-pub struct StyleMap(Vec<Entry>);
+pub struct StyleMap {
+ /// Settable properties.
+ props: Vec<Entry>,
+ /// Show rule recipes.
+ recipes: Vec<Recipe>,
+}
impl StyleMap {
/// Create a new, empty style map.
pub fn new() -> Self {
- Self(vec![])
+ Self { props: vec![], recipes: vec![] }
}
/// Whether this map contains no styles.
pub fn is_empty(&self) -> bool {
- self.0.is_empty()
+ self.props.is_empty() && self.recipes.is_empty()
}
/// Create a style map from a single property-value pair.
@@ -29,7 +36,7 @@ impl StyleMap {
/// Set the value for a style property.
pub fn set<P: Property>(&mut self, key: P, value: P::Value) {
- self.0.push(Entry::new(key, value));
+ self.props.push(Entry::new(key, value));
}
/// Set a value for a style property if it is `Some(_)`.
@@ -39,11 +46,16 @@ impl StyleMap {
}
}
+ /// Set a recipe.
+ pub fn set_recipe(&mut self, node: TypeId, func: Func, span: Span) {
+ self.recipes.push(Recipe { node, func, span });
+ }
+
/// Mark all contained properties as _scoped_. This means that they only
/// apply to the first descendant node (of their type) in the hierarchy and
/// not its children, too. This is used by class constructors.
pub fn scoped(mut self) -> Self {
- for entry in &mut self.0 {
+ for entry in &mut self.props {
entry.scoped = true;
}
self
@@ -51,7 +63,7 @@ impl StyleMap {
/// Whether this map contains scoped styles.
pub fn has_scoped(&self) -> bool {
- self.0.iter().any(|e| e.scoped)
+ self.props.iter().any(|e| e.scoped)
}
/// Make `self` the first link of the style chain `outer`.
@@ -78,20 +90,24 @@ impl StyleMap {
/// lifetime, for example, because you want to store the style map inside a
/// packed node.
pub fn apply(&mut self, outer: &Self) {
- self.0.splice(0 .. 0, outer.0.clone());
+ self.props.splice(0 .. 0, outer.props.iter().cloned());
+ self.recipes.splice(0 .. 0, outer.recipes.iter().cloned());
}
/// The highest-level interruption of the map.
pub fn interruption(&self) -> Option<Interruption> {
- self.0.iter().filter_map(|entry| entry.interruption()).max()
+ self.props.iter().filter_map(|entry| entry.interruption()).max()
}
}
impl Debug for StyleMap {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- for entry in self.0.iter().rev() {
+ for entry in self.props.iter().rev() {
writeln!(f, "{:#?}", entry)?;
}
+ for recipe in self.recipes.iter().rev() {
+ writeln!(f, "#[Recipe for {:?} from {:?}]", recipe.node, recipe.span)?;
+ }
Ok(())
}
}
@@ -105,6 +121,169 @@ pub enum Interruption {
Page,
}
+/// Style property keys.
+///
+/// This trait is not intended to be implemented manually, but rather through
+/// the `#[class]` proc-macro.
+pub trait Property: Sync + Send + 'static {
+ /// The type of value that is returned when getting this property from a
+ /// style map. For example, this could be [`Length`](crate::geom::Length)
+ /// for a `WIDTH` property.
+ type Value: Debug + Clone + PartialEq + Hash + Sync + Send + 'static;
+
+ /// The name of the property, used for debug printing.
+ const NAME: &'static str;
+
+ /// Whether the property needs folding.
+ const FOLDING: bool = false;
+
+ /// The type id of the node this property belongs to.
+ fn node_id() -> TypeId;
+
+ /// The default value of the property.
+ fn default() -> Self::Value;
+
+ /// A static reference to the default value of the property.
+ ///
+ /// This is automatically implemented through lazy-initialization in the
+ /// `#[class]` macro. This way, expensive defaults don't need to be
+ /// recreated all the time.
+ fn default_ref() -> &'static Self::Value;
+
+ /// Fold the property with an outer value.
+ ///
+ /// For example, this would fold a relative font size with an outer
+ /// absolute font size.
+ #[allow(unused_variables)]
+ fn fold(inner: Self::Value, outer: Self::Value) -> Self::Value {
+ inner
+ }
+}
+
+/// Marker trait that indicates that a property doesn't need folding.
+pub trait Nonfolding {}
+
+/// An entry for a single style property.
+#[derive(Clone)]
+struct Entry {
+ pair: Arc<dyn Bounds>,
+ scoped: bool,
+}
+
+impl Entry {
+ fn new<P: Property>(key: P, value: P::Value) -> Self {
+ Self {
+ pair: Arc::new((key, value)),
+ scoped: false,
+ }
+ }
+
+ fn is<P: Property>(&self) -> bool {
+ self.pair.style_id() == TypeId::of::<P>()
+ }
+
+ fn is_of<T: 'static>(&self) -> bool {
+ self.pair.node_id() == TypeId::of::<T>()
+ }
+
+ fn is_of_id(&self, node: TypeId) -> bool {
+ self.pair.node_id() == node
+ }
+
+ fn downcast<P: Property>(&self) -> Option<&P::Value> {
+ self.pair.as_any().downcast_ref()
+ }
+
+ fn interruption(&self) -> Option<Interruption> {
+ if self.is_of::<PageNode>() {
+ Some(Interruption::Page)
+ } else if self.is_of::<ParNode>() {
+ Some(Interruption::Par)
+ } else {
+ None
+ }
+ }
+}
+
+impl Debug for Entry {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ f.write_str("#[")?;
+ self.pair.dyn_fmt(f)?;
+ if self.scoped {
+ f.write_str(" (scoped)")?;
+ }
+ f.write_str("]")
+ }
+}
+
+impl PartialEq for Entry {
+ fn eq(&self, other: &Self) -> bool {
+ self.pair.dyn_eq(other) && self.scoped == other.scoped
+ }
+}
+
+impl Hash for Entry {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ state.write_u64(self.pair.hash64());
+ state.write_u8(self.scoped as u8);
+ }
+}
+
+/// This trait is implemented for pairs of zero-sized property keys and their
+/// value types below. Although it is zero-sized, the property `P` must be part
+/// of the implementing type so that we can use it in the methods (it must be a
+/// constrained type parameter).
+trait Bounds: Sync + Send + 'static {
+ fn as_any(&self) -> &dyn Any;
+ fn dyn_fmt(&self, f: &mut Formatter) -> fmt::Result;
+ fn dyn_eq(&self, other: &Entry) -> bool;
+ fn hash64(&self) -> u64;
+ fn node_id(&self) -> TypeId;
+ fn style_id(&self) -> TypeId;
+}
+
+impl<P: Property> Bounds for (P, P::Value) {
+ fn as_any(&self) -> &dyn Any {
+ &self.1
+ }
+
+ fn dyn_fmt(&self, f: &mut Formatter) -> fmt::Result {
+ write!(f, "{} = {:?}", P::NAME, self.1)
+ }
+
+ fn dyn_eq(&self, other: &Entry) -> bool {
+ self.style_id() == other.pair.style_id()
+ && if let Some(other) = other.downcast::<P>() {
+ &self.1 == other
+ } else {
+ false
+ }
+ }
+
+ fn hash64(&self) -> u64 {
+ let mut state = fxhash::FxHasher64::default();
+ self.style_id().hash(&mut state);
+ self.1.hash(&mut state);
+ state.finish()
+ }
+
+ fn node_id(&self) -> TypeId {
+ P::node_id()
+ }
+
+ fn style_id(&self) -> TypeId {
+ TypeId::of::<P>()
+ }
+}
+
+/// A show rule recipe.
+#[derive(Debug, Clone, PartialEq, Hash)]
+struct Recipe {
+ node: TypeId,
+ func: Func,
+ span: Span,
+}
+
/// A chain of style maps, similar to a linked list.
///
/// A style chain allows to conceptually merge (and fold) properties from
@@ -162,11 +341,18 @@ impl<'a> StyleChain<'a> {
*self = self.outer.copied().unwrap_or_default();
}
+ /// Return the span of a recipe for the given node.
+ pub(crate) fn recipe_span(self, node: TypeId) -> Option<Span> {
+ self.recipes(node).next().map(|recipe| recipe.span)
+ }
+
/// Return the chain, but without the last link if that one contains only
/// scoped styles. This is a hack.
pub(crate) fn unscoped(mut self, node: TypeId) -> Self {
if let Some(Link::Map(map)) = self.link {
- if map.0.iter().all(|e| e.scoped && e.is_of_id(node)) {
+ if map.props.iter().all(|e| e.scoped && e.is_of_id(node))
+ && map.recipes.is_empty()
+ {
self.pop();
}
}
@@ -225,6 +411,21 @@ impl<'a> StyleChain<'a> {
}
}
+ /// Execute a user recipe for a node.
+ pub fn show(
+ self,
+ node: &dyn Any,
+ vm: &mut Vm,
+ values: impl IntoIterator<Item = Value>,
+ ) -> TypResult<Option<Template>> {
+ Ok(if let Some(recipe) = self.recipes(node.type_id()).next() {
+ let args = Args::from_values(recipe.span, values);
+ Some(recipe.func.call(vm, args)?.cast().at(recipe.span)?)
+ } else {
+ None
+ })
+ }
+
/// Insert a barrier into the style chain.
///
/// Barriers interact with [scoped](StyleMap::scoped) styles: A scoped style
@@ -233,7 +434,7 @@ impl<'a> StyleChain<'a> {
pub fn barred<'b>(&'b self, node: TypeId) -> StyleChain<'b> {
if self
.maps()
- .any(|map| map.0.iter().any(|entry| entry.scoped && entry.is_of_id(node)))
+ .any(|map| map.props.iter().any(|entry| entry.scoped && entry.is_of_id(node)))
{
StyleChain {
link: Some(Link::Barrier(node)),
@@ -252,7 +453,7 @@ impl<'a> StyleChain<'a> {
self.links().flat_map(move |link| {
let mut entries: &[Entry] = &[];
match link {
- Link::Map(map) => entries = &map.0,
+ Link::Map(map) => entries = &map.props,
Link::Barrier(id) => depth += (id == P::node_id()) as usize,
}
entries
@@ -263,6 +464,13 @@ impl<'a> StyleChain<'a> {
})
}
+ /// Iterate over the recipes for the given node.
+ fn recipes(self, node: TypeId) -> impl Iterator<Item = &'a Recipe> {
+ self.maps()
+ .flat_map(|map| map.recipes.iter().rev())
+ .filter(move |recipe| recipe.node == node)
+ }
+
/// Iterate over the map links of the chain.
fn maps(self) -> impl Iterator<Item = &'a StyleMap> {
self.links().filter_map(|link| match link {
@@ -443,158 +651,3 @@ impl<'a, T> Default for StyleVecBuilder<'a, T> {
Self::new()
}
}
-
-/// Style property keys.
-///
-/// This trait is not intended to be implemented manually, but rather through
-/// the `#[class]` proc-macro.
-pub trait Property: Sync + Send + 'static {
- /// The type of value that is returned when getting this property from a
- /// style map. For example, this could be [`Length`](crate::geom::Length)
- /// for a `WIDTH` property.
- type Value: Debug + Clone + PartialEq + Hash + Sync + Send + 'static;
-
- /// The name of the property, used for debug printing.
- const NAME: &'static str;
-
- /// Whether the property needs folding.
- const FOLDING: bool = false;
-
- /// The type id of the node this property belongs to.
- fn node_id() -> TypeId;
-
- /// The default value of the property.
- fn default() -> Self::Value;
-
- /// A static reference to the default value of the property.
- ///
- /// This is automatically implemented through lazy-initialization in the
- /// `#[class]` macro. This way, expensive defaults don't need to be
- /// recreated all the time.
- fn default_ref() -> &'static Self::Value;
-
- /// Fold the property with an outer value.
- ///
- /// For example, this would fold a relative font size with an outer
- /// absolute font size.
- #[allow(unused_variables)]
- fn fold(inner: Self::Value, outer: Self::Value) -> Self::Value {
- inner
- }
-}
-
-/// Marker trait that indicates that a property doesn't need folding.
-pub trait Nonfolding {}
-
-/// An entry for a single style property.
-#[derive(Clone)]
-struct Entry {
- pair: Arc<dyn Bounds>,
- scoped: bool,
-}
-
-impl Entry {
- fn new<P: Property>(key: P, value: P::Value) -> Self {
- Self {
- pair: Arc::new((key, value)),
- scoped: false,
- }
- }
-
- fn is<P: Property>(&self) -> bool {
- self.pair.style_id() == TypeId::of::<P>()
- }
-
- fn is_of<T: 'static>(&self) -> bool {
- self.pair.node_id() == TypeId::of::<T>()
- }
-
- fn is_of_id(&self, node: TypeId) -> bool {
- self.pair.node_id() == node
- }
-
- fn downcast<P: Property>(&self) -> Option<&P::Value> {
- self.pair.as_any().downcast_ref()
- }
-
- fn interruption(&self) -> Option<Interruption> {
- if self.is_of::<PageNode>() {
- Some(Interruption::Page)
- } else if self.is_of::<ParNode>() {
- Some(Interruption::Par)
- } else {
- None
- }
- }
-}
-
-impl Debug for Entry {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- f.write_str("#[")?;
- self.pair.dyn_fmt(f)?;
- if self.scoped {
- f.write_str(" (scoped)")?;
- }
- f.write_str("]")
- }
-}
-
-impl PartialEq for Entry {
- fn eq(&self, other: &Self) -> bool {
- self.pair.dyn_eq(other) && self.scoped == other.scoped
- }
-}
-
-impl Hash for Entry {
- fn hash<H: Hasher>(&self, state: &mut H) {
- state.write_u64(self.pair.hash64());
- state.write_u8(self.scoped as u8);
- }
-}
-
-/// This trait is implemented for pairs of zero-sized property keys and their
-/// value types below. Although it is zero-sized, the property `P` must be part
-/// of the implementing type so that we can use it in the methods (it must be a
-/// constrained type parameter).
-trait Bounds: Sync + Send + 'static {
- fn as_any(&self) -> &dyn Any;
- fn dyn_fmt(&self, f: &mut Formatter) -> fmt::Result;
- fn dyn_eq(&self, other: &Entry) -> bool;
- fn hash64(&self) -> u64;
- fn node_id(&self) -> TypeId;
- fn style_id(&self) -> TypeId;
-}
-
-impl<P: Property> Bounds for (P, P::Value) {
- fn as_any(&self) -> &dyn Any {
- &self.1
- }
-
- fn dyn_fmt(&self, f: &mut Formatter) -> fmt::Result {
- write!(f, "{} = {:?}", P::NAME, self.1)
- }
-
- fn dyn_eq(&self, other: &Entry) -> bool {
- self.style_id() == other.pair.style_id()
- && if let Some(other) = other.downcast::<P>() {
- &self.1 == other
- } else {
- false
- }
- }
-
- fn hash64(&self) -> u64 {
- let mut state = fxhash::FxHasher64::default();
- self.style_id().hash(&mut state);
- self.1.hash(&mut state);
- state.finish()
- }
-
- fn node_id(&self) -> TypeId {
- P::node_id()
- }
-
- fn style_id(&self) -> TypeId {
- TypeId::of::<P>()
- }
-}
diff --git a/src/eval/template.rs b/src/eval/template.rs
index 1f1544e6..465e9195 100644
--- a/src/eval/template.rs
+++ b/src/eval/template.rs
@@ -403,9 +403,16 @@ impl<'a> Builder<'a> {
}
}
Template::Show(node) => {
+ let id = node.id();
+ if vm.rules.contains(&id) {
+ let span = styles.recipe_span(id).unwrap();
+ return Err("show rule is recursive").at(span)?;
+ }
+ vm.rules.push(id);
let template = node.show(vm, styles)?;
let stored = self.tpa.alloc(template);
- self.process(vm, stored, styles.unscoped(node.id()))?;
+ self.process(vm, stored, styles.unscoped(id))?;
+ vm.rules.pop();
}
Template::Styled(styled) => {
let (sub, map) = styled.as_ref();
@@ -455,8 +462,8 @@ impl<'a> Builder<'a> {
};
let template = match kind {
- UNORDERED => Template::show(ListNode::<UNORDERED> { items, wide, start: 1 }),
- ORDERED | _ => Template::show(ListNode::<ORDERED> { items, wide, start: 1 }),
+ UNORDERED => Template::show(ListNode::<UNORDERED> { start: 1, wide, items }),
+ ORDERED | _ => Template::show(ListNode::<ORDERED> { start: 1, wide, items }),
};
let stored = self.tpa.alloc(template);
diff --git a/src/eval/value.rs b/src/eval/value.rs
index 2de210d5..952c7293 100644
--- a/src/eval/value.rs
+++ b/src/eval/value.rs
@@ -336,7 +336,7 @@ pub trait Cast<V = Value>: Sized {
macro_rules! primitive {
(
$type:ty: $name:literal, $variant:ident
- $(, $other:ident($binding:ident) => $out:expr)*
+ $(, $other:ident$(($binding:ident))? => $out:expr)*
) => {
impl Type for $type {
const TYPE_NAME: &'static str = $name;
@@ -344,13 +344,14 @@ macro_rules! primitive {
impl Cast for $type {
fn is(value: &Value) -> bool {
- matches!(value, Value::$variant(_) $(| Value::$other(_))*)
+ matches!(value, Value::$variant(_)
+ $(| primitive!(@$other $(($binding))?))*)
}
fn cast(value: Value) -> StrResult<Self> {
match value {
Value::$variant(v) => Ok(v),
- $(Value::$other($binding) => Ok($out),)*
+ $(Value::$other$(($binding))? => Ok($out),)*
v => Err(format!(
"expected {}, found {}",
Self::TYPE_NAME,
@@ -366,6 +367,9 @@ macro_rules! primitive {
}
}
};
+
+ (@$other:ident($binding:ident)) => { Value::$other(_) };
+ (@$other:ident) => { Value::$other };
}
/// Implement traits for dynamic types.
@@ -440,7 +444,7 @@ primitive! { Color: "color", Color }
primitive! { EcoString: "string", Str }
primitive! { Array: "array", Array }
primitive! { Dict: "dictionary", Dict }
-primitive! { Template: "template", Template }
+primitive! { Template: "template", Template, None => Template::new() }
primitive! { Func: "function", Func, Class(v) => v.constructor() }
primitive! { Args: "arguments", Args }
primitive! { Class: "class", Class }