Better function representation

This commit is contained in:
Laurenz 2022-01-31 17:57:20 +01:00
parent 20b1a38414
commit 6a6753cb69
5 changed files with 152 additions and 107 deletions

View File

@ -1,8 +1,7 @@
use std::fmt::{self, Debug, Formatter, Write}; 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::diag::TypResult;
use crate::util::EcoString;
/// A class of [nodes](Node). /// A class of [nodes](Node).
/// ///
@ -35,38 +34,40 @@ use crate::util::EcoString;
/// [`set`]: Self::set /// [`set`]: Self::set
#[derive(Clone)] #[derive(Clone)]
pub struct Class { pub struct Class {
name: EcoString, name: &'static str,
construct: fn(&mut EvalContext, &mut Args) -> TypResult<Node>, construct: fn(&mut EvalContext, &mut Args) -> TypResult<Value>,
set: fn(&mut Args, &mut StyleMap) -> TypResult<()>, set: fn(&mut Args, &mut StyleMap) -> TypResult<()>,
} }
impl Class { impl Class {
/// Create a new class. /// Create a new class.
pub fn new<T>(name: EcoString) -> Self pub fn new<T>(name: &'static str) -> Self
where where
T: Construct + Set + 'static, T: Construct + Set + 'static,
{ {
Self { Self {
name, 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, set: T::set,
} }
} }
/// The name of the class. /// The name of the class.
pub fn name(&self) -> &EcoString { pub fn name(&self) -> &'static str {
&self.name self.name
} }
/// Construct an instance of the class. /// Construct an instance of the class.
/// ///
/// This parses both property and data arguments (in this order) and styles /// This parses both property and data arguments (in this order) and styles
/// the node constructed from the data with the style properties. /// the node constructed from the data with the style properties.
pub fn construct(&self, ctx: &mut EvalContext, args: &mut Args) -> TypResult<Node> { pub fn construct(&self, ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
let mut styles = StyleMap::new(); (self.construct)(ctx, args)
self.set(args, &mut styles)?;
let node = (self.construct)(ctx, args)?;
Ok(node.styled_with_map(styles.scoped()))
} }
/// Execute the class's set rule. /// Execute the class's set rule.
@ -76,6 +77,11 @@ impl Class {
pub fn set(&self, args: &mut Args, styles: &mut StyleMap) -> TypResult<()> { pub fn set(&self, args: &mut Args, styles: &mut StyleMap) -> TypResult<()> {
(self.set)(args, styles) (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 { impl Debug for Class {

View File

@ -1,44 +1,68 @@
use std::fmt::{self, Debug, Formatter, Write}; use std::fmt::{self, Debug, Formatter, Write};
use std::sync::Arc; use std::sync::Arc;
use super::{Cast, EvalContext, Value}; use super::{Cast, Eval, EvalContext, Scope, Value};
use crate::diag::{At, TypResult}; use crate::diag::{At, TypResult};
use crate::syntax::ast::Expr;
use crate::syntax::{Span, Spanned}; use crate::syntax::{Span, Spanned};
use crate::util::EcoString; use crate::util::EcoString;
/// An evaluatable function. /// An evaluatable function.
#[derive(Clone)] #[derive(Clone)]
pub struct Function(Arc<Inner<Func>>); pub struct Func(Arc<Repr>);
/// The unsized structure behind the [`Arc`]. /// The different kinds of function representations.
struct Inner<T: ?Sized> { enum Repr {
name: Option<EcoString>, /// A native rust function.
func: T, 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<Value>; impl Func {
/// Create a new function from a native rust function.
pub fn native(
name: &'static str,
func: fn(&mut EvalContext, &mut Args) -> TypResult<Value>,
) -> Self {
Self(Arc::new(Repr::Native(Native { name, func })))
}
impl Function { /// Create a new function from a closure.
/// Create a new function from a rust closure. pub fn closure(closure: Closure) -> Self {
pub fn new<F>(name: Option<EcoString>, func: F) -> Self Self(Arc::new(Repr::Closure(closure)))
where
F: Fn(&mut EvalContext, &mut Args) -> TypResult<Value> + 'static,
{
Self(Arc::new(Inner { name, func }))
} }
/// The name of the function. /// The name of the function.
pub fn name(&self) -> Option<&EcoString> { pub fn name(&self) -> Option<&str> {
self.0.name.as_ref() 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. /// Call the function in the context with the arguments.
pub fn call(&self, ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> { pub fn call(&self, ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
(&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)
}
} }
} }
impl Debug for Function { /// 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 { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.write_str("<function")?; f.write_str("<function")?;
if let Some(name) = self.name() { if let Some(name) = self.name() {
@ -49,13 +73,65 @@ impl Debug for Function {
} }
} }
impl PartialEq for Function { impl PartialEq for Func {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
// We cast to thin pointers for comparison. Arc::ptr_eq(&self.0, &other.0)
std::ptr::eq( }
Arc::as_ptr(&self.0) as *const (), }
Arc::as_ptr(&other.0) as *const (),
) /// 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<Value>,
}
/// A user-defined closure.
pub struct Closure {
/// The name of the closure.
pub name: Option<EcoString>,
/// 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<Value>)>,
/// The name of an argument sink where remaining arguments are placed.
pub sink: Option<EcoString>,
/// 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<Value> {
// 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::<Value>(param)?,
Some(default) => {
args.named::<Value>(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)
} }
} }

View File

@ -10,7 +10,7 @@ mod value;
mod styles; mod styles;
mod capture; mod capture;
mod class; mod class;
mod function; mod func;
mod node; mod node;
mod ops; mod ops;
mod scope; mod scope;
@ -19,7 +19,7 @@ pub use array::*;
pub use capture::*; pub use capture::*;
pub use class::*; pub use class::*;
pub use dict::*; pub use dict::*;
pub use function::*; pub use func::*;
pub use node::*; pub use node::*;
pub use scope::*; pub use scope::*;
pub use styles::*; pub use styles::*;
@ -602,9 +602,9 @@ impl Eval for CallExpr {
Value::Class(class) => { Value::Class(class) => {
let point = || Tracepoint::Call(Some(class.name().to_string())); 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()?; args.finish()?;
Ok(Value::Node(node)) Ok(value)
} }
v => bail!( v => bail!(
@ -669,6 +669,9 @@ impl Eval for ClosureExpr {
type Output = Value; type Output = Value;
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> { fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
// The closure's name is defined by its let binding if there's one.
let name = self.name().map(Ident::take);
// Collect captured variables. // Collect captured variables.
let captured = { let captured = {
let mut visitor = CapturesVisitor::new(&ctx.scopes); let mut visitor = CapturesVisitor::new(&ctx.scopes);
@ -676,8 +679,8 @@ impl Eval for ClosureExpr {
visitor.finish() visitor.finish()
}; };
let mut sink = None;
let mut params = Vec::new(); let mut params = Vec::new();
let mut sink = None;
// Collect parameters and an optional sink parameter. // Collect parameters and an optional sink parameter.
for param in self.params() { 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. // Define the actual function.
let func = Function::new(name, move |ctx, args| { Ok(Value::Func(Func::closure(Closure {
// Don't leak the scopes from the call site. Instead, we use the name,
// scope of captured variables we collected earlier. captured,
let prev_scopes = mem::take(&mut ctx.scopes); params,
ctx.scopes.top = captured.clone(); sink,
body: self.body(),
// Parse the arguments according to the parameter list. })))
for (param, default) in &params {
ctx.scopes.def_mut(param, match default {
None => args.expect::<Value>(param)?,
Some(default) => {
args.named::<Value>(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))
} }
} }
@ -738,16 +716,9 @@ impl Eval for WithExpr {
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> { fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
let callee = self.callee(); let callee = self.callee();
let wrapped = callee.eval(ctx)?.cast::<Function>().at(callee.span())?; let func = callee.eval(ctx)?.cast::<Func>().at(callee.span())?;
let applied = self.args().eval(ctx)?; let args = self.args().eval(ctx)?;
Ok(Value::Func(func.with(args)))
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))
} }
} }

View File

@ -4,7 +4,7 @@ use std::fmt::{self, Debug, Formatter};
use std::iter; use std::iter;
use std::sync::Arc; 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::diag::TypResult;
use crate::util::EcoString; use crate::util::EcoString;
@ -98,22 +98,21 @@ impl Scope {
self.values.insert(var.into(), slot); self.values.insert(var.into(), slot);
} }
/// Define a constant function. /// Define a constant native function.
pub fn def_func<F>(&mut self, name: &str, f: F) pub fn def_func(
where &mut self,
F: Fn(&mut EvalContext, &mut Args) -> TypResult<Value> + 'static, name: &'static str,
{ func: fn(&mut EvalContext, &mut Args) -> TypResult<Value>,
let name = EcoString::from(name); ) {
self.def_const(name.clone(), Function::new(Some(name), f)); self.def_const(name, Func::native(name, func));
} }
/// Define a constant class. /// Define a constant class.
pub fn def_class<T>(&mut self, name: &str) pub fn def_class<T>(&mut self, name: &'static str)
where where
T: Construct + Set + 'static, T: Construct + Set + 'static,
{ {
let name = EcoString::from(name); self.def_const(name, Class::new::<T>(name));
self.def_const(name.clone(), Class::new::<T>(name));
} }
/// Look up the value of a variable. /// Look up the value of a variable.

View File

@ -4,7 +4,7 @@ use std::fmt::{self, Debug, Formatter};
use std::hash::Hash; use std::hash::Hash;
use std::sync::Arc; 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::diag::StrResult;
use crate::geom::{Angle, Color, Fractional, Length, Linear, Relative, RgbaColor}; use crate::geom::{Angle, Color, Fractional, Length, Linear, Relative, RgbaColor};
use crate::layout::Layout; use crate::layout::Layout;
@ -45,7 +45,7 @@ pub enum Value {
/// A node value: `[*Hi* there]`. /// A node value: `[*Hi* there]`.
Node(Node), Node(Node),
/// An executable function. /// An executable function.
Func(Function), Func(Func),
/// Captured arguments to a function. /// Captured arguments to a function.
Args(Args), Args(Args),
/// A class of nodes. /// A class of nodes.
@ -89,7 +89,7 @@ impl Value {
Self::Array(_) => Array::TYPE_NAME, Self::Array(_) => Array::TYPE_NAME,
Self::Dict(_) => Dict::TYPE_NAME, Self::Dict(_) => Dict::TYPE_NAME,
Self::Node(_) => Node::TYPE_NAME, Self::Node(_) => Node::TYPE_NAME,
Self::Func(_) => Function::TYPE_NAME, Self::Func(_) => Func::TYPE_NAME,
Self::Args(_) => Args::TYPE_NAME, Self::Args(_) => Args::TYPE_NAME,
Self::Class(_) => Class::TYPE_NAME, Self::Class(_) => Class::TYPE_NAME,
Self::Dyn(v) => v.type_name(), Self::Dyn(v) => v.type_name(),
@ -401,13 +401,7 @@ primitive! { EcoString: "string", Str }
primitive! { Array: "array", Array } primitive! { Array: "array", Array }
primitive! { Dict: "dictionary", Dict } primitive! { Dict: "dictionary", Dict }
primitive! { Node: "template", Node } primitive! { Node: "template", Node }
primitive! { Function: "function", primitive! { Func: "function", Func, Class(v) => v.constructor() }
Func,
Class(v) => Function::new(
Some(v.name().clone()),
move |ctx, args| v.construct(ctx, args).map(Value::Node)
)
}
primitive! { Args: "arguments", Args } primitive! { Args: "arguments", Args }
primitive! { Class: "class", Class } primitive! { Class: "class", Class }
@ -554,9 +548,8 @@ mod tests {
test(dict!["two" => false, "one" => 1], "(one: 1, two: false)"); test(dict!["two" => false, "one" => 1], "(one: 1, two: false)");
// Functions. // Functions.
test(Function::new(None, |_, _| Ok(Value::None)), "<function>");
test( test(
Function::new(Some("nil".into()), |_, _| Ok(Value::None)), Func::native("nil", |_, _| Ok(Value::None)),
"<function nil>", "<function nil>",
); );