diff options
| author | Laurenz <laurmaedje@gmail.com> | 2021-07-10 20:01:18 +0200 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2021-07-10 23:10:17 +0200 |
| commit | 6a4823461f491aef63451f097ddfe5602e0b2157 (patch) | |
| tree | ad11b0ad169d030942d950573c729d50f7b3291b /src/eval/function.rs | |
| parent | 36b3067c19c8743032a44f888ee48702b88d135b (diff) | |
Reference-count complex values
Rename some nodes types
Diffstat (limited to 'src/eval/function.rs')
| -rw-r--r-- | src/eval/function.rs | 176 |
1 files changed, 176 insertions, 0 deletions
diff --git a/src/eval/function.rs b/src/eval/function.rs new file mode 100644 index 00000000..c986d71a --- /dev/null +++ b/src/eval/function.rs @@ -0,0 +1,176 @@ +use std::fmt::{self, Debug, Formatter}; +use std::ops::Deref; +use std::rc::Rc; + +use super::{Cast, CastResult, EvalContext, Value}; +use crate::eco::EcoString; +use crate::syntax::{Span, Spanned}; + +/// An evaluatable function. +#[derive(Clone)] +pub struct Function { + /// The name of the function. + /// + /// The string is boxed to make the whole struct fit into 24 bytes, so that + /// a value fits into 32 bytes. + name: Option<Box<EcoString>>, + /// The closure that defines the function. + f: Rc<dyn Fn(&mut EvalContext, &mut FuncArgs) -> Value>, +} + +impl Function { + /// Create a new function from a rust closure. + pub fn new<F>(name: Option<EcoString>, f: F) -> Self + where + F: Fn(&mut EvalContext, &mut FuncArgs) -> Value + 'static, + { + Self { name: name.map(Box::new), f: Rc::new(f) } + } + + /// The name of the function. + pub fn name(&self) -> Option<&EcoString> { + self.name.as_ref().map(|s| &**s) + } +} + +impl Deref for Function { + type Target = dyn Fn(&mut EvalContext, &mut FuncArgs) -> Value; + + fn deref(&self) -> &Self::Target { + self.f.as_ref() + } +} + +impl Debug for Function { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.debug_struct("ValueFunc").field("name", &self.name).finish() + } +} + +impl PartialEq for Function { + fn eq(&self, other: &Self) -> bool { + // We cast to thin pointers because we don't want to compare vtables. + Rc::as_ptr(&self.f) as *const () == Rc::as_ptr(&other.f) as *const () + } +} + +/// Evaluated arguments to a function. +#[derive(Debug, Clone, PartialEq)] +pub struct FuncArgs { + /// The span of the whole argument list. + pub span: Span, + /// The positional arguments. + pub items: Vec<FuncArg>, +} + +/// An argument to a function call: `12` or `draw: false`. +#[derive(Debug, Clone, PartialEq)] +pub struct FuncArg { + /// 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 FuncArgs { + /// Find and consume the first castable positional argument. + pub fn eat<T>(&mut self, ctx: &mut EvalContext) -> Option<T> + where + T: Cast<Spanned<Value>>, + { + (0 .. self.items.len()).find_map(|index| { + let slot = &mut self.items[index]; + if slot.name.is_some() { + return None; + } + + let value = std::mem::replace(&mut slot.value, Spanned::zero(Value::None)); + let span = value.span; + + match T::cast(value) { + CastResult::Ok(t) => { + self.items.remove(index); + Some(t) + } + CastResult::Warn(t, m) => { + self.items.remove(index); + ctx.diag(warning!(span, "{}", m)); + Some(t) + } + CastResult::Err(value) => { + slot.value = value; + None + } + } + }) + } + + /// Find and consume the first castable positional argument, producing a + /// `missing argument: {what}` error if no match was found. + pub fn expect<T>(&mut self, ctx: &mut EvalContext, what: &str) -> Option<T> + where + T: Cast<Spanned<Value>>, + { + let found = self.eat(ctx); + if found.is_none() { + ctx.diag(error!(self.span, "missing argument: {}", what)); + } + found + } + + /// Find, consume and collect all castable positional arguments. + /// + /// This function returns a vector instead of an iterator because the + /// iterator would require unique access to the context, rendering it rather + /// unusable. If you need to process arguments one-by-one, you probably want + /// to use a while-let loop together with [`eat()`](Self::eat). + pub fn all<T>(&mut self, ctx: &mut EvalContext) -> Vec<T> + where + T: Cast<Spanned<Value>>, + { + std::iter::from_fn(|| self.eat(ctx)).collect() + } + + /// Cast and remove the value for the given named argument, producing an + /// error if the conversion fails. + pub fn named<T>(&mut self, ctx: &mut EvalContext, name: &str) -> Option<T> + where + T: Cast<Spanned<Value>>, + { + let index = self + .items + .iter() + .position(|arg| arg.name.as_ref().map_or(false, |other| other == name))?; + + let value = self.items.remove(index).value; + let span = value.span; + + match T::cast(value) { + CastResult::Ok(t) => Some(t), + CastResult::Warn(t, m) => { + ctx.diag(warning!(span, "{}", m)); + Some(t) + } + CastResult::Err(value) => { + ctx.diag(error!( + span, + "expected {}, found {}", + T::TYPE_NAME, + value.v.type_name(), + )); + None + } + } + } + + /// Produce "unexpected argument" errors for all remaining arguments. + pub fn finish(self, ctx: &mut EvalContext) { + for arg in &self.items { + if arg.value.v != Value::Error { + ctx.diag(error!(arg.span, "unexpected argument")); + } + } + } +} |
