diff --git a/src/error.rs b/src/error.rs index 18f0829..0bb3b82 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,4 +1,4 @@ -use std::{fmt, io, rc::Rc}; +use std::{fmt, io, ops::RangeInclusive, rc::Rc}; use crate::{ compile::CompileError, @@ -37,6 +37,13 @@ pub enum ReadError { Io(io::Error), } +#[derive(Debug, PartialEq)] +pub struct ArgumentCountError { + pub function: Rc, + pub expected_range: RangeInclusive, + pub actual: usize, +} + #[derive(Debug, PartialEq, thiserror::Error)] pub enum MachineError { // VM itself @@ -70,6 +77,8 @@ pub enum MachineError { InvalidBranchTarget(usize, isize), #[error("GET_TEMP with an empty temp register")] TempRegisterEmpty, + #[error("{0}")] + ArgumentCount(ArgumentCountError), // Syntax+evaluation // #[error("evaluation error: {0}")] @@ -131,3 +140,30 @@ impl PartialEq for ReadError { } } } + +impl fmt::Display for ArgumentCountError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let min_arity = *self.expected_range.start(); + let max_arity = *self.expected_range.end(); + let one_argument = min_arity == max_arity; + let too_few = self.actual < min_arity; + + write!( + f, + "too {} arguments for function {}: expected ", + if too_few { "few" } else { "many" }, + self.function + )?; + + if one_argument { + write!(f, "{min_arity}")?; + write!(f, " argument")?; + if min_arity != 1 { + write!(f, "s")?; + } + } else { + write!(f, "{min_arity}-{max_arity} arguments")?; + } + write!(f, ", got {}", self.actual) + } +} diff --git a/src/vm/machine.rs b/src/vm/machine.rs index 0ec9da4..8eda2b1 100644 --- a/src/vm/machine.rs +++ b/src/vm/machine.rs @@ -1,6 +1,9 @@ use crate::{ compile::{CompileContext, CompileOptions}, - error::{MachineError, MachineErrorAt, MachineErrorLocation, ValueConversionError}, + error::{ + ArgumentCountError, MachineError, MachineErrorAt, MachineErrorLocation, + ValueConversionError, + }, vm::{ Value, env::Environment, @@ -241,11 +244,12 @@ impl Machine { } }; - if argument_count < closure.function.min_arity { - todo!("TODO function called with less arguments than expected") - } - if argument_count > closure.function.max_arity { - todo!("TODO function called with more arguments than expected") + if !(closure.function.min_arity..=closure.function.max_arity).contains(&argument_count) { + return Err(MachineError::ArgumentCount(ArgumentCountError { + function: closure.function.clone(), + expected_range: closure.function.min_arity..=closure.function.max_arity, + actual: argument_count, + })); } if closure.function.max_arity == usize::MAX { todo!("VM support for &rest argument") @@ -594,11 +598,13 @@ impl Machine { args: &[Value], ) -> Result { let max_arity = closure.function.max_arity; - if args.len() < closure.function.min_arity { - todo!() - } - if args.len() > closure.function.max_arity { - todo!() + if !(closure.function.min_arity..=closure.function.max_arity).contains(&args.len()) { + return Err(MachineError::ArgumentCount(ArgumentCountError { + function: closure.function.clone(), + expected_range: closure.function.min_arity..=closure.function.max_arity, + actual: args.len(), + }) + .at_unknown()); } self.push(Value::Closure(closure)) .map_err(MachineErrorAt::at_unknown)?;