use std::fmt::{self, Debug, Formatter}; use std::ops::Deref; use std::rc::Rc; use super::{Cast, 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>, /// The closure that defines the function. f: Rc Value>, } impl Function { /// Create a new function from a rust closure. pub fn new(name: Option, 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, } /// 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, /// The value of the argument. pub value: Spanned, } impl FuncArgs { /// Find and consume the first castable positional argument. pub fn eat(&mut self) -> Option where T: Cast>, { (0 .. self.items.len()).find_map(|index| { let slot = self.items.get_mut(index)?; if slot.name.is_none() { if T::is(&slot.value) { let value = self.items.remove(index).value; return T::cast(value).ok(); } } None }) } /// Find and consume the first castable positional argument, producing a /// `missing argument: {what}` error if no match was found. pub fn expect(&mut self, ctx: &mut EvalContext, what: &str) -> Option where T: Cast>, { let found = self.eat(); if found.is_none() { ctx.diag(error!(self.span, "missing argument: {}", what)); } found } /// Find, consume and collect all castable positional arguments. pub fn all(&mut self) -> impl Iterator + '_ where T: Cast>, { std::iter::from_fn(move || self.eat()) } /// Cast and remove the value for the given named argument, producing an /// error if the conversion fails. pub fn named(&mut self, ctx: &mut EvalContext, name: &str) -> Option where T: Cast>, { 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) { Ok(t) => Some(t), Err(msg) => { ctx.diag(error!(span, "{}", msg)); 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")); } } } }