From 6a6753cb69f7c29e857fd465eecf66a02ff76aa3 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Mon, 31 Jan 2022 17:57:20 +0100 Subject: [PATCH] Better function representation --- src/eval/class.rs | 32 ++++---- src/eval/{function.rs => func.rs} | 124 ++++++++++++++++++++++++------ src/eval/mod.rs | 65 +++++----------- src/eval/scope.rs | 21 +++-- src/eval/value.rs | 17 ++-- 5 files changed, 152 insertions(+), 107 deletions(-) rename src/eval/{function.rs => func.rs} (62%) diff --git a/src/eval/class.rs b/src/eval/class.rs index 307bebccb..91a9daefe 100644 --- a/src/eval/class.rs +++ b/src/eval/class.rs @@ -1,8 +1,7 @@ use std::fmt::{self, Debug, Formatter, Write}; -use super::{Args, EvalContext, Node, StyleMap}; +use super::{Args, EvalContext, Func, Node, StyleMap, Value}; use crate::diag::TypResult; -use crate::util::EcoString; /// A class of [nodes](Node). /// @@ -35,38 +34,40 @@ use crate::util::EcoString; /// [`set`]: Self::set #[derive(Clone)] pub struct Class { - name: EcoString, - construct: fn(&mut EvalContext, &mut Args) -> TypResult, + name: &'static str, + construct: fn(&mut EvalContext, &mut Args) -> TypResult, set: fn(&mut Args, &mut StyleMap) -> TypResult<()>, } impl Class { /// Create a new class. - pub fn new(name: EcoString) -> Self + pub fn new(name: &'static str) -> Self where T: Construct + Set + 'static, { Self { name, - construct: T::construct, + construct: |ctx, args| { + let mut styles = StyleMap::new(); + T::set(args, &mut styles)?; + let node = T::construct(ctx, args)?; + Ok(Value::Node(node.styled_with_map(styles.scoped()))) + }, set: T::set, } } /// The name of the class. - pub fn name(&self) -> &EcoString { - &self.name + pub fn name(&self) -> &'static str { + self.name } /// Construct an instance of the class. /// /// This parses both property and data arguments (in this order) and styles /// the node constructed from the data with the style properties. - pub fn construct(&self, ctx: &mut EvalContext, args: &mut Args) -> TypResult { - let mut styles = StyleMap::new(); - self.set(args, &mut styles)?; - let node = (self.construct)(ctx, args)?; - Ok(node.styled_with_map(styles.scoped())) + pub fn construct(&self, ctx: &mut EvalContext, args: &mut Args) -> TypResult { + (self.construct)(ctx, args) } /// Execute the class's set rule. @@ -76,6 +77,11 @@ impl Class { pub fn set(&self, args: &mut Args, styles: &mut StyleMap) -> TypResult<()> { (self.set)(args, styles) } + + /// Return the class constructor as a function. + pub fn constructor(&self) -> Func { + Func::native(self.name, self.construct) + } } impl Debug for Class { diff --git a/src/eval/function.rs b/src/eval/func.rs similarity index 62% rename from src/eval/function.rs rename to src/eval/func.rs index 0edc1e786..ccd0932f2 100644 --- a/src/eval/function.rs +++ b/src/eval/func.rs @@ -1,44 +1,68 @@ use std::fmt::{self, Debug, Formatter, Write}; use std::sync::Arc; -use super::{Cast, EvalContext, Value}; +use super::{Cast, Eval, EvalContext, Scope, Value}; use crate::diag::{At, TypResult}; +use crate::syntax::ast::Expr; use crate::syntax::{Span, Spanned}; use crate::util::EcoString; /// An evaluatable function. #[derive(Clone)] -pub struct Function(Arc>); +pub struct Func(Arc); -/// The unsized structure behind the [`Arc`]. -struct Inner { - name: Option, - func: T, +/// The different kinds of function representations. +enum Repr { + /// A native rust function. + Native(Native), + /// A user-defined closure. + Closure(Closure), + /// A nested function with pre-applied arguments. + With(Func, Args), } -type Func = dyn Fn(&mut EvalContext, &mut Args) -> TypResult; +impl Func { + /// Create a new function from a native rust function. + pub fn native( + name: &'static str, + func: fn(&mut EvalContext, &mut Args) -> TypResult, + ) -> Self { + Self(Arc::new(Repr::Native(Native { name, func }))) + } -impl Function { - /// Create a new function from a rust closure. - pub fn new(name: Option, func: F) -> Self - where - F: Fn(&mut EvalContext, &mut Args) -> TypResult + 'static, - { - Self(Arc::new(Inner { name, func })) + /// Create a new function from a closure. + pub fn closure(closure: Closure) -> Self { + Self(Arc::new(Repr::Closure(closure))) } /// The name of the function. - pub fn name(&self) -> Option<&EcoString> { - self.0.name.as_ref() + 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(), + } } /// Call the function in the context with the arguments. pub fn call(&self, ctx: &mut EvalContext, args: &mut Args) -> TypResult { - (&self.0.func)(ctx, args) + match self.0.as_ref() { + Repr::Native(native) => (native.func)(ctx, args), + Repr::Closure(closure) => closure.call(ctx, args), + Repr::With(wrapped, applied) => { + args.items.splice(.. 0, applied.items.iter().cloned()); + wrapped.call(ctx, args) + } + } + } + + /// Apply the given arguments to the function. + pub fn with(self, args: Args) -> Self { + Self(Arc::new(Repr::With(self, args))) } } -impl Debug for Function { +impl Debug for Func { fn fmt(&self, f: &mut Formatter) -> fmt::Result { f.write_str(" bool { - // We cast to thin pointers for comparison. - std::ptr::eq( - Arc::as_ptr(&self.0) as *const (), - Arc::as_ptr(&other.0) as *const (), - ) + Arc::ptr_eq(&self.0, &other.0) + } +} + +/// A native rust function. +struct Native { + /// The name of the function. + pub name: &'static str, + /// The function pointer. + pub func: fn(&mut EvalContext, &mut Args) -> TypResult, +} + +/// A user-defined closure. +pub struct Closure { + /// 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, ctx: &mut EvalContext, args: &mut Args) -> TypResult { + // Don't leak the scopes from the call site. Instead, we use the + // scope of captured variables we collected earlier. + let prev_scopes = std::mem::take(&mut ctx.scopes); + ctx.scopes.top = self.captured.clone(); + + // Parse the arguments according to the parameter list. + for (param, default) in &self.params { + ctx.scopes.def_mut(param, 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 { + ctx.scopes.def_mut(sink, args.take()); + } + + // Evaluate the body. + let value = self.body.eval(ctx)?; + + // Restore the call site scopes. + ctx.scopes = prev_scopes; + + Ok(value) } } diff --git a/src/eval/mod.rs b/src/eval/mod.rs index a453a3570..ae680d95a 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -10,7 +10,7 @@ mod value; mod styles; mod capture; mod class; -mod function; +mod func; mod node; mod ops; mod scope; @@ -19,7 +19,7 @@ pub use array::*; pub use capture::*; pub use class::*; pub use dict::*; -pub use function::*; +pub use func::*; pub use node::*; pub use scope::*; pub use styles::*; @@ -602,9 +602,9 @@ impl Eval for CallExpr { Value::Class(class) => { let point = || Tracepoint::Call(Some(class.name().to_string())); - let node = class.construct(ctx, &mut args).trace(point, self.span())?; + let value = class.construct(ctx, &mut args).trace(point, self.span())?; args.finish()?; - Ok(Value::Node(node)) + Ok(value) } v => bail!( @@ -669,6 +669,9 @@ impl Eval for ClosureExpr { type Output = Value; fn eval(&self, ctx: &mut EvalContext) -> TypResult { + // The closure's name is defined by its let binding if there's one. + let name = self.name().map(Ident::take); + // Collect captured variables. let captured = { let mut visitor = CapturesVisitor::new(&ctx.scopes); @@ -676,8 +679,8 @@ impl Eval for ClosureExpr { visitor.finish() }; - let mut sink = None; let mut params = Vec::new(); + let mut sink = None; // Collect parameters and an optional sink parameter. for param in self.params() { @@ -697,39 +700,14 @@ impl Eval for ClosureExpr { } } - // Clone the body expression so that we don't have a lifetime - // dependence on the AST. - let name = self.name().map(Ident::take); - let body = self.body(); - // Define the actual function. - let func = Function::new(name, move |ctx, args| { - // Don't leak the scopes from the call site. Instead, we use the - // scope of captured variables we collected earlier. - let prev_scopes = mem::take(&mut ctx.scopes); - ctx.scopes.top = captured.clone(); - - // Parse the arguments according to the parameter list. - for (param, default) in ¶ms { - ctx.scopes.def_mut(param, 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) = &sink { - ctx.scopes.def_mut(sink, args.take()); - } - - let value = body.eval(ctx)?; - ctx.scopes = prev_scopes; - Ok(value) - }); - - Ok(Value::Func(func)) + Ok(Value::Func(Func::closure(Closure { + name, + captured, + params, + sink, + body: self.body(), + }))) } } @@ -738,16 +716,9 @@ impl Eval for WithExpr { fn eval(&self, ctx: &mut EvalContext) -> TypResult { let callee = self.callee(); - let wrapped = callee.eval(ctx)?.cast::().at(callee.span())?; - let applied = self.args().eval(ctx)?; - - let name = wrapped.name().cloned(); - let func = Function::new(name, move |ctx, args| { - args.items.splice(.. 0, applied.items.iter().cloned()); - wrapped.call(ctx, args) - }); - - Ok(Value::Func(func)) + let func = callee.eval(ctx)?.cast::().at(callee.span())?; + let args = self.args().eval(ctx)?; + Ok(Value::Func(func.with(args))) } } diff --git a/src/eval/scope.rs b/src/eval/scope.rs index 34da68d4a..3e79afc1f 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::sync::Arc; -use super::{Args, Class, Construct, EvalContext, Function, Set, Value}; +use super::{Args, Class, Construct, EvalContext, Func, Set, Value}; use crate::diag::TypResult; use crate::util::EcoString; @@ -98,22 +98,21 @@ impl Scope { self.values.insert(var.into(), slot); } - /// Define a constant function. - pub fn def_func(&mut self, name: &str, f: F) - where - F: Fn(&mut EvalContext, &mut Args) -> TypResult + 'static, - { - let name = EcoString::from(name); - self.def_const(name.clone(), Function::new(Some(name), f)); + /// Define a constant native function. + pub fn def_func( + &mut self, + name: &'static str, + func: fn(&mut EvalContext, &mut Args) -> TypResult, + ) { + self.def_const(name, Func::native(name, func)); } /// Define a constant class. - pub fn def_class(&mut self, name: &str) + pub fn def_class(&mut self, name: &'static str) where T: Construct + Set + 'static, { - let name = EcoString::from(name); - self.def_const(name.clone(), Class::new::(name)); + self.def_const(name, Class::new::(name)); } /// Look up the value of a variable. diff --git a/src/eval/value.rs b/src/eval/value.rs index 7d65d5af1..c19794dfc 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::sync::Arc; -use super::{ops, Args, Array, Class, Dict, Function, Node}; +use super::{ops, Args, Array, Class, Dict, Func, Node}; use crate::diag::StrResult; use crate::geom::{Angle, Color, Fractional, Length, Linear, Relative, RgbaColor}; use crate::layout::Layout; @@ -45,7 +45,7 @@ pub enum Value { /// A node value: `[*Hi* there]`. Node(Node), /// An executable function. - Func(Function), + Func(Func), /// Captured arguments to a function. Args(Args), /// A class of nodes. @@ -89,7 +89,7 @@ impl Value { Self::Array(_) => Array::TYPE_NAME, Self::Dict(_) => Dict::TYPE_NAME, Self::Node(_) => Node::TYPE_NAME, - Self::Func(_) => Function::TYPE_NAME, + Self::Func(_) => Func::TYPE_NAME, Self::Args(_) => Args::TYPE_NAME, Self::Class(_) => Class::TYPE_NAME, Self::Dyn(v) => v.type_name(), @@ -401,13 +401,7 @@ primitive! { EcoString: "string", Str } primitive! { Array: "array", Array } primitive! { Dict: "dictionary", Dict } primitive! { Node: "template", Node } -primitive! { Function: "function", - Func, - Class(v) => Function::new( - Some(v.name().clone()), - move |ctx, args| v.construct(ctx, args).map(Value::Node) - ) -} +primitive! { Func: "function", Func, Class(v) => v.constructor() } primitive! { Args: "arguments", Args } primitive! { Class: "class", Class } @@ -554,9 +548,8 @@ mod tests { test(dict!["two" => false, "one" => 1], "(one: 1, two: false)"); // Functions. - test(Function::new(None, |_, _| Ok(Value::None)), ""); test( - Function::new(Some("nil".into()), |_, _| Ok(Value::None)), + Func::native("nil", |_, _| Ok(Value::None)), "", );