From 6a6753cb69f7c29e857fd465eecf66a02ff76aa3 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Mon, 31 Jan 2022 17:57:20 +0100 Subject: Better function representation --- src/eval/class.rs | 32 +++--- src/eval/func.rs | 304 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/eval/function.rs | 228 -------------------------------------- src/eval/mod.rs | 65 +++-------- src/eval/scope.rs | 21 ++-- src/eval/value.rs | 17 +-- 6 files changed, 356 insertions(+), 311 deletions(-) create mode 100644 src/eval/func.rs delete mode 100644 src/eval/function.rs (limited to 'src') diff --git a/src/eval/class.rs b/src/eval/class.rs index 307bebcc..91a9daef 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/func.rs b/src/eval/func.rs new file mode 100644 index 00000000..ccd0932f --- /dev/null +++ b/src/eval/func.rs @@ -0,0 +1,304 @@ +use std::fmt::{self, Debug, Formatter, Write}; +use std::sync::Arc; + +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 Func(Arc); + +/// 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), +} + +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 }))) + } + + /// 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<&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 { + 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 Func { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.write_str("') + } +} + +impl PartialEq for Func { + fn eq(&self, other: &Self) -> bool { + 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) + } +} + +/// Evaluated arguments to a function. +#[derive(Clone, PartialEq)] +pub struct Args { + /// The span of the whole argument list. + pub span: Span, + /// The positional and named arguments. + pub items: Vec, +} + +/// An argument to a function call: `12` or `draw: false`. +#[derive(Clone, PartialEq)] +pub struct Arg { + /// The span of the whole argument. + pub span: Span, + /// The name of the argument (`None` for positional arguments). + pub name: Option, + /// The value of the argument. + pub value: Spanned, +} + +impl Args { + /// Consume and cast the first positional argument. + /// + /// Returns a `missing argument: {what}` error if no positional argument is + /// left. + pub fn expect(&mut self, what: &str) -> TypResult + where + T: Cast>, + { + 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(&mut self) -> TypResult> + where + T: Cast>, + { + 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(&mut self) -> Option + where + T: Cast>, + { + for (i, slot) in self.items.iter().enumerate() { + if slot.name.is_none() && T::is(&slot.value) { + let value = self.items.remove(i).value; + return T::cast(value).ok(); + } + } + None + } + + /// Find and consume all castable positional arguments. + pub fn all(&mut self) -> impl Iterator + '_ + where + T: Cast>, + { + std::iter::from_fn(move || self.find()) + } + + /// Cast and remove the value for the given named argument, returning an + /// error if the conversion fails. + pub fn named(&mut self, name: &str) -> TypResult> + where + T: Cast>, + { + // 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) + } + + /// 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 { + self.into_castable("index") + } + + /// Reinterpret these arguments as actually being a dictionary key. + pub fn into_key(self) -> TypResult { + self.into_castable("key") + } + + /// Reinterpret these arguments as actually being a single castable thing. + fn into_castable(self, what: &str) -> TypResult + where + T: Cast, + { + 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) + } +} diff --git a/src/eval/function.rs b/src/eval/function.rs deleted file mode 100644 index 0edc1e78..00000000 --- a/src/eval/function.rs +++ /dev/null @@ -1,228 +0,0 @@ -use std::fmt::{self, Debug, Formatter, Write}; -use std::sync::Arc; - -use super::{Cast, EvalContext, Value}; -use crate::diag::{At, TypResult}; -use crate::syntax::{Span, Spanned}; -use crate::util::EcoString; - -/// An evaluatable function. -#[derive(Clone)] -pub struct Function(Arc>); - -/// The unsized structure behind the [`Arc`]. -struct Inner { - name: Option, - func: T, -} - -type Func = dyn Fn(&mut EvalContext, &mut Args) -> TypResult; - -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 })) - } - - /// The name of the function. - pub fn name(&self) -> Option<&EcoString> { - self.0.name.as_ref() - } - - /// 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) - } -} - -impl Debug for Function { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.write_str("') - } -} - -impl PartialEq for Function { - fn eq(&self, other: &Self) -> 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 (), - ) - } -} - -/// Evaluated arguments to a function. -#[derive(Clone, PartialEq)] -pub struct Args { - /// The span of the whole argument list. - pub span: Span, - /// The positional and named arguments. - pub items: Vec, -} - -/// An argument to a function call: `12` or `draw: false`. -#[derive(Clone, PartialEq)] -pub struct Arg { - /// The span of the whole argument. - pub span: Span, - /// The name of the argument (`None` for positional arguments). - pub name: Option, - /// The value of the argument. - pub value: Spanned, -} - -impl Args { - /// Consume and cast the first positional argument. - /// - /// Returns a `missing argument: {what}` error if no positional argument is - /// left. - pub fn expect(&mut self, what: &str) -> TypResult - where - T: Cast>, - { - 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(&mut self) -> TypResult> - where - T: Cast>, - { - 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(&mut self) -> Option - where - T: Cast>, - { - for (i, slot) in self.items.iter().enumerate() { - if slot.name.is_none() && T::is(&slot.value) { - let value = self.items.remove(i).value; - return T::cast(value).ok(); - } - } - None - } - - /// Find and consume all castable positional arguments. - pub fn all(&mut self) -> impl Iterator + '_ - where - T: Cast>, - { - std::iter::from_fn(move || self.find()) - } - - /// Cast and remove the value for the given named argument, returning an - /// error if the conversion fails. - pub fn named(&mut self, name: &str) -> TypResult> - where - T: Cast>, - { - // 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) - } - - /// 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 { - self.into_castable("index") - } - - /// Reinterpret these arguments as actually being a dictionary key. - pub fn into_key(self) -> TypResult { - self.into_castable("key") - } - - /// Reinterpret these arguments as actually being a single castable thing. - fn into_castable(self, what: &str) -> TypResult - where - T: Cast, - { - 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) - } -} diff --git a/src/eval/mod.rs b/src/eval/mod.rs index a453a357..ae680d95 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 34da68d4..3e79afc1 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 7d65d5af..c19794df 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)), "", ); -- cgit v1.2.3