val 0.4.0

An arbitrary precision calculator language
Documentation
use super::*;

#[derive(Clone, Debug)]
pub enum Function<'src> {
  Builtin {
    arity: BuiltinArity,
    function: BuiltinFunction,
    name: &'src str,
  },
  UserDefined {
    body: Vec<Spanned<Statement<'src>>>,
    environment: Environment<'src>,
    name: Option<&'src str>,
    parameters: Vec<&'src str>,
  },
}

impl<'src> Function<'src> {
  pub(crate) fn call(
    &self,
    arguments: Vec<Value<'src>>,
    config: Config,
    span: Span,
  ) -> Result<Value<'src>, Error> {
    self.check_arity(arguments.len(), span)?;

    match self {
      Self::Builtin { function, .. } => {
        function.call(&BuiltinFunctionPayload {
          arguments,
          config,
          span,
        })
      }
      Self::UserDefined {
        body,
        environment,
        name,
        parameters,
      } => {
        let call_environment = Environment::with_parent(environment.clone());

        if let Some(name) = name {
          call_environment.add_function(name, self.clone());
        }

        for (parameter, argument) in parameters.iter().zip(arguments.iter()) {
          call_environment.add_symbol(parameter, argument.clone());
        }

        Evaluator::from(call_environment).enter_function(|evaluator| {
          match evaluator.evaluate_statements(body)? {
            Completion::Return(value) | Completion::Value(value) => Ok(value),
            Completion::Break | Completion::Continue => Ok(Value::Null),
          }
        })
      }
    }
  }

  pub(crate) fn check_arity(
    &self,
    len: usize,
    span: Span,
  ) -> Result<(), Error> {
    match self {
      Self::Builtin { arity, name, .. } => arity.check(name, len, span),
      Self::UserDefined { parameters, .. } => {
        if parameters.len() == len {
          return Ok(());
        }

        Err(Error::new(
          span,
          format!(
            "Function `{}` expects {} arguments, got {}",
            self.name(),
            parameters.len(),
            len
          ),
        ))
      }
    }
  }

  pub(crate) fn name(&self) -> &str {
    match self {
      Self::Builtin { name, .. } => name,
      Self::UserDefined { name, .. } => name.unwrap_or("<anonymous>"),
    }
  }
}

impl PartialEq for Function<'_> {
  fn eq(&self, other: &Self) -> bool {
    match (self, other) {
      (Self::Builtin { name: a, .. }, Self::Builtin { name: b, .. })
      | (
        Self::UserDefined { name: Some(a), .. },
        Self::UserDefined { name: Some(b), .. },
      ) => a == b,
      _ => false,
    }
  }
}