diff options
Diffstat (limited to 'src/eval/func.rs')
| -rw-r--r-- | src/eval/func.rs | 282 |
1 files changed, 77 insertions, 205 deletions
diff --git a/src/eval/func.rs b/src/eval/func.rs index 451dcbbb..3eae453e 100644 --- a/src/eval/func.rs +++ b/src/eval/func.rs @@ -1,11 +1,12 @@ +use std::any::TypeId; use std::fmt::{self, Debug, Formatter, Write}; use std::hash::{Hash, Hasher}; use std::sync::Arc; -use super::{Cast, Control, Eval, Scope, Scopes, Value}; -use crate::diag::{At, TypResult}; +use super::{Args, Content, Control, Eval, Scope, Scopes, StyleMap, Value}; +use crate::diag::{StrResult, TypResult}; use crate::syntax::ast::Expr; -use crate::syntax::{Span, Spanned}; +use crate::syntax::Span; use crate::util::EcoString; use crate::Context; @@ -26,18 +27,50 @@ enum Repr { impl Func { /// Create a new function from a native rust function. - pub fn native( + pub fn from_fn( name: &'static str, func: fn(&mut Context, &mut Args) -> TypResult<Value>, ) -> Self { - Self(Arc::new(Repr::Native(Native { name, func }))) + Self(Arc::new(Repr::Native(Native { + name, + func, + set: None, + show: None, + }))) + } + + /// Create a new function from a native rust node. + pub fn from_node<T: Node>(name: &'static str) -> Self { + Self(Arc::new(Repr::Native(Native { + name, + func: |ctx, args| { + let styles = T::set(args)?; + let content = T::construct(ctx, args)?; + Ok(Value::Content(content.styled_with_map(styles.scoped()))) + }, + set: Some(T::set), + show: if T::SHOWABLE { + Some(|recipe, span| { + let mut styles = StyleMap::new(); + styles.set_recipe(TypeId::of::<T>(), recipe, span); + styles + }) + } else { + None + }, + }))) } /// Create a new function from a closure. - pub fn closure(closure: Closure) -> Self { + pub fn from_closure(closure: Closure) -> Self { Self(Arc::new(Repr::Closure(closure))) } + /// Apply the given arguments to the function. + pub fn with(self, args: Args) -> Self { + Self(Arc::new(Repr::With(self, args))) + } + /// The name of the function. pub fn name(&self) -> Option<&str> { match self.0.as_ref() { @@ -61,9 +94,22 @@ impl Func { Ok(value) } - /// Apply the given arguments to the function. - pub fn with(self, args: Args) -> Self { - Self(Arc::new(Repr::With(self, args))) + /// Execute the function's set rule. + pub fn set(&self, mut args: Args) -> TypResult<StyleMap> { + let styles = match self.0.as_ref() { + Repr::Native(Native { set: Some(set), .. }) => set(&mut args)?, + _ => StyleMap::new(), + }; + args.finish()?; + Ok(styles) + } + + /// Execute the function's show rule. + pub fn show(&self, recipe: Func, span: Span) -> StrResult<StyleMap> { + match self.0.as_ref() { + Repr::Native(Native { show: Some(show), .. }) => Ok(show(recipe, span)), + _ => Err("this function cannot be customized with show")?, + } } } @@ -90,14 +136,36 @@ struct Native { pub name: &'static str, /// The function pointer. pub func: fn(&mut Context, &mut Args) -> TypResult<Value>, + /// The set rule. + pub set: Option<fn(&mut Args) -> TypResult<StyleMap>>, + /// The show rule. + pub show: Option<fn(Func, Span) -> StyleMap>, } impl Hash for Native { fn hash<H: Hasher>(&self, state: &mut H) { + self.name.hash(state); (self.func as usize).hash(state); + self.set.map(|set| set as usize).hash(state); + self.show.map(|show| show as usize).hash(state); } } +/// A constructable, stylable content node. +pub trait Node: 'static { + /// Whether this node can be customized through a show rule. + const SHOWABLE: bool; + + /// Construct a node from the arguments. + /// + /// This is passed only the arguments that remain after execution of the + /// node's set rule. + fn construct(ctx: &mut Context, args: &mut Args) -> TypResult<Content>; + + /// Parse the arguments into style properties for this node. + fn set(args: &mut Args) -> TypResult<StyleMap>; +} + /// A user-defined closure. #[derive(Hash)] pub struct Closure { @@ -146,199 +214,3 @@ impl Closure { Ok(value) } } - -/// Evaluated arguments to a function. -#[derive(Clone, PartialEq, Hash)] -pub struct Args { - /// The span of the whole argument list. - pub span: Span, - /// The positional and named arguments. - pub items: Vec<Arg>, -} - -/// An argument to a function call: `12` or `draw: false`. -#[derive(Clone, PartialEq, Hash)] -pub struct Arg { - /// The span of the whole argument. - pub span: Span, - /// The name of the argument (`None` for positional arguments). - pub name: Option<EcoString>, - /// The value of the argument. - pub value: Spanned<Value>, -} - -impl Args { - /// Create positional arguments from a span and values. - pub fn from_values(span: Span, values: impl IntoIterator<Item = Value>) -> Self { - Self { - span, - items: values - .into_iter() - .map(|value| Arg { - span, - name: None, - value: Spanned::new(value, span), - }) - .collect(), - } - } - - /// Consume and cast the first positional argument. - /// - /// Returns a `missing argument: {what}` error if no positional argument is - /// left. - pub fn expect<T>(&mut self, what: &str) -> TypResult<T> - where - T: Cast<Spanned<Value>>, - { - match self.eat()? { - Some(v) => Ok(v), - None => bail!(self.span, "missing argument: {}", what), - } - } - - /// Consume and cast the first positional argument if there is one. - pub fn eat<T>(&mut self) -> TypResult<Option<T>> - where - T: Cast<Spanned<Value>>, - { - for (i, slot) in self.items.iter().enumerate() { - if slot.name.is_none() { - let value = self.items.remove(i).value; - let span = value.span; - return T::cast(value).at(span).map(Some); - } - } - Ok(None) - } - - /// Find and consume the first castable positional argument. - pub fn find<T>(&mut self) -> TypResult<Option<T>> - where - T: Cast<Spanned<Value>>, - { - for (i, slot) in self.items.iter().enumerate() { - if slot.name.is_none() && T::is(&slot.value) { - let value = self.items.remove(i).value; - let span = value.span; - return T::cast(value).at(span).map(Some); - } - } - Ok(None) - } - - /// Find and consume all castable positional arguments. - pub fn all<T>(&mut self) -> TypResult<Vec<T>> - where - T: Cast<Spanned<Value>>, - { - let mut list = vec![]; - while let Some(value) = self.find()? { - list.push(value); - } - Ok(list) - } - - /// Cast and remove the value for the given named argument, returning an - /// error if the conversion fails. - pub fn named<T>(&mut self, name: &str) -> TypResult<Option<T>> - where - T: Cast<Spanned<Value>>, - { - // We don't quit once we have a match because when multiple matches - // exist, we want to remove all of them and use the last one. - let mut i = 0; - let mut found = None; - while i < self.items.len() { - if self.items[i].name.as_deref() == Some(name) { - let value = self.items.remove(i).value; - let span = value.span; - found = Some(T::cast(value).at(span)?); - } else { - i += 1; - } - } - Ok(found) - } - - /// Same as named, but with fallback to find. - pub fn named_or_find<T>(&mut self, name: &str) -> TypResult<Option<T>> - where - T: Cast<Spanned<Value>>, - { - match self.named(name)? { - Some(value) => Ok(Some(value)), - None => self.find(), - } - } - - /// Take out all arguments into a new instance. - pub fn take(&mut self) -> Self { - Self { - span: self.span, - items: std::mem::take(&mut self.items), - } - } - - /// Return an "unexpected argument" error if there is any remaining - /// argument. - pub fn finish(self) -> TypResult<()> { - if let Some(arg) = self.items.first() { - bail!(arg.span, "unexpected argument"); - } - Ok(()) - } - - /// Reinterpret these arguments as actually being an array index. - pub fn into_index(self) -> TypResult<i64> { - self.into_castable("index") - } - - /// Reinterpret these arguments as actually being a dictionary key. - pub fn into_key(self) -> TypResult<EcoString> { - self.into_castable("key") - } - - /// Reinterpret these arguments as actually being a single castable thing. - fn into_castable<T: Cast>(self, what: &str) -> TypResult<T> { - let mut iter = self.items.into_iter(); - let value = match iter.next() { - Some(Arg { name: None, value, .. }) => value.v.cast().at(value.span)?, - None => { - bail!(self.span, "missing {}", what); - } - Some(Arg { name: Some(_), span, .. }) => { - bail!(span, "named pair is not allowed here"); - } - }; - - if let Some(arg) = iter.next() { - bail!(arg.span, "only one {} is allowed", what); - } - - Ok(value) - } -} - -impl Debug for Args { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.write_char('(')?; - for (i, arg) in self.items.iter().enumerate() { - arg.fmt(f)?; - if i + 1 < self.items.len() { - f.write_str(", ")?; - } - } - f.write_char(')') - } -} - -impl Debug for Arg { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - if let Some(name) = &self.name { - f.write_str(name)?; - f.write_str(": ")?; - } - Debug::fmt(&self.value.v, f) - } -} |
