use std::fmt::{self, Debug, Formatter}; use std::hash::{Hash, Hasher}; use std::sync::Arc; use comemo::{Track, Tracked}; use super::{Args, Eval, Flow, Route, Scope, Scopes, Value, Vm}; use crate::diag::{SourceResult, StrResult}; use crate::model::{Content, NodeId, StyleMap}; use crate::source::SourceId; use crate::syntax::ast::Expr; use crate::util::EcoString; use crate::World; /// An evaluatable function. #[derive(Clone, Hash)] pub struct Func(Arc); /// The different kinds of function representations. #[derive(Hash)] enum Repr { /// A native rust function. Native(Native), /// A user-defined closure. Closure(Closure), /// A nested function with pre-applied arguments. With(Func, Args), } impl Func { /// Create a new function from a native rust function. pub fn from_fn( name: &'static str, func: fn(&mut Vm, &mut Args) -> SourceResult, ) -> Self { Self(Arc::new(Repr::Native(Native { name, func, set: None, node: None, }))) } /// Create a new function from a native rust node. pub fn from_node(name: &'static str) -> Self { Self(Arc::new(Repr::Native(Native { name, func: |ctx, args| { let styles = T::set(args, true)?; let content = T::construct(ctx, args)?; Ok(Value::Content(content.styled_with_map(styles.scoped()))) }, set: Some(|args| T::set(args, false)), node: T::SHOWABLE.then(|| NodeId::of::()), }))) } /// Create a new function from a closure. 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() { Repr::Native(native) => Some(native.name), Repr::Closure(closure) => closure.name.as_deref(), Repr::With(func, _) => func.name(), } } /// The number of positional arguments this function takes, if known. pub fn argc(&self) -> Option { match self.0.as_ref() { Repr::Closure(closure) => Some( closure.params.iter().filter(|(_, default)| default.is_none()).count(), ), Repr::With(wrapped, applied) => Some(wrapped.argc()?.saturating_sub( applied.items.iter().filter(|arg| arg.name.is_none()).count(), )), _ => None, } } /// Call the function with the given arguments. pub fn call(&self, vm: &mut Vm, mut args: Args) -> SourceResult { let value = match self.0.as_ref() { Repr::Native(native) => (native.func)(vm, &mut args)?, Repr::Closure(closure) => closure.call(vm, &mut args)?, Repr::With(wrapped, applied) => { args.items.splice(.. 0, applied.items.iter().cloned()); return wrapped.call(vm, args); } }; args.finish()?; Ok(value) } /// Call the function without an existing virtual machine. pub fn call_detached( &self, world: Tracked, args: Args, ) -> SourceResult { let route = Route::default(); let mut vm = Vm::new(world, route.track(), None, Scopes::new(None)); self.call(&mut vm, args) } /// Execute the function's set rule and return the resulting style map. pub fn set(&self, mut args: Args) -> SourceResult { let styles = match self.0.as_ref() { Repr::Native(Native { set: Some(set), .. }) => set(&mut args)?, _ => StyleMap::new(), }; args.finish()?; Ok(styles) } /// The id of the node to customize with this function's show rule. pub fn node(&self) -> StrResult { match self.0.as_ref() { Repr::Native(Native { node: Some(id), .. }) => Ok(*id), _ => Err("this function cannot be customized with show")?, } } } impl Debug for Func { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self.name() { Some(name) => f.write_str(name), None => f.write_str("(..) => {..}"), } } } impl PartialEq for Func { fn eq(&self, other: &Self) -> bool { Arc::ptr_eq(&self.0, &other.0) } } /// A function defined by a native rust function or node. struct Native { /// The name of the function. pub name: &'static str, /// The function pointer. pub func: fn(&mut Vm, &mut Args) -> SourceResult, /// The set rule. pub set: Option SourceResult>, /// The id of the node to customize with this function's show rule. pub node: Option, } impl Hash for Native { fn hash(&self, state: &mut H) { self.name.hash(state); (self.func as usize).hash(state); self.set.map(|set| set as usize).hash(state); self.node.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(vm: &mut Vm, args: &mut Args) -> SourceResult; /// Parse relevant arguments into style properties for this node. /// /// When `constructor` is true, [`construct`](Self::construct) will run /// after this invocation of `set` with the remaining arguments. fn set(args: &mut Args, constructor: bool) -> SourceResult; } /// A user-defined closure. #[derive(Hash)] pub struct Closure { /// The source file where the closure was defined. pub location: Option, /// The name of the closure. pub name: Option, /// Captured values from outer scopes. pub captured: Scope, /// The parameter names and default values. Parameters with default value /// are named parameters. pub params: Vec<(EcoString, Option)>, /// The name of an argument sink where remaining arguments are placed. pub sink: Option, /// The expression the closure should evaluate to. pub body: Expr, } impl Closure { /// Call the function in the context with the arguments. pub fn call(&self, vm: &mut Vm, args: &mut Args) -> SourceResult { // Don't leak the scopes from the call site. Instead, we use the scope // of captured variables we collected earlier. let mut scopes = Scopes::new(None); scopes.top = self.captured.clone(); // Parse the arguments according to the parameter list. for (param, default) in &self.params { scopes.top.define(param.clone(), match default { None => args.expect::(param)?, Some(default) => { args.named::(param)?.unwrap_or_else(|| default.clone()) } }); } // Put the remaining arguments into the sink. if let Some(sink) = &self.sink { scopes.top.define(sink.clone(), args.take()); } // Determine the route inside the closure. let detached = vm.location.is_none(); let fresh = Route::new(self.location); let route = if detached { fresh.track() } else { vm.route }; // Evaluate the body. let mut sub = Vm::new(vm.world, route, self.location, scopes); let result = self.body.eval(&mut sub); // Handle control flow. match sub.flow { Some(Flow::Return(_, Some(explicit))) => return Ok(explicit), Some(Flow::Return(_, None)) => {} Some(flow) => bail!(flow.forbidden()), None => {} } result } }