diff --git a/src/ast.rs b/src/ast.rs index 16b7c861..835e679f 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -60,7 +60,7 @@ pub enum StmtKind { Functio { iden: Iden, - args: Vec, + params: Vec, body: Block, }, BfFunctio { diff --git a/src/error.rs b/src/error.rs index b1da3128..7d1e489c 100644 --- a/src/error.rs +++ b/src/error.rs @@ -20,6 +20,7 @@ pub enum ErrorKind { TopLevelBreak, ArithmeticError, BfInterpretError(InterpretError), + MismatchedArgumentError, MissingLhs, } diff --git a/src/interpret.rs b/src/interpret.rs index f47b33ad..28f792f4 100644 --- a/src/interpret.rs +++ b/src/interpret.rs @@ -76,7 +76,7 @@ impl ExecEnv { // It's an error to issue a `break` outside of a // `loop` statement. kind: ErrorKind::TopLevelBreak, - span: span, + span, }), } } @@ -203,11 +203,13 @@ impl ExecEnv { self.decl_var(&iden.iden, init); } - StmtKind::Functio { - iden: _, - args: _, - body: _, - } => todo!(), + StmtKind::Functio { iden, params, body } => self.decl_var( + &iden.iden, + Value::Functio(Functio::AbleFunctio { + params: params.iter().map(|iden| iden.iden.to_string()).collect(), + body: body.block.to_owned(), + }), + ), StmtKind::BfFunctio { iden, tape_len, @@ -235,49 +237,19 @@ impl ExecEnv { } StmtKind::Call { iden, args } => { let func = self.get_var(&iden)?; - match func { - Value::Functio(func) => { - match func { - Functio::BfFunctio { - instructions, - tape_len, - } => { - let mut input: Vec = vec![]; - for arg in args { - self.eval_expr(arg)?.bf_write(&mut input); - } - println!("input = {:?}", input); - let mut output = vec![]; - crate::brian::Interpreter::from_ascii_with_tape_limit( - &instructions, - &input as &[_], - tape_len, - ) - .interpret_with_output(&mut output) - .map_err(|e| Error { - kind: ErrorKind::BfInterpretError(e), - span: stmt.span.clone(), - })?; + let args = args + .iter() + .map(|arg| self.eval_expr(arg)) + .collect::>()?; - // I guess Brainfuck functions write - // output to stdout? It's not quite - // clear to me what else to do. ~~Alex - stdout() - .write_all(&output) - .expect("Failed to write to stdout"); - } - Functio::AbleFunctio(_) => { - todo!() - } - } - } - _ => { - return Err(Error { - kind: ErrorKind::TypeError(iden.iden.to_owned()), - span: stmt.span.clone(), - }) - } + if let Value::Functio(func) = func { + self.fn_call(func, args, &stmt.span)?; + } else { + return Err(Error { + kind: ErrorKind::TypeError(iden.iden.to_owned()), + span: stmt.span.clone(), + }); } } StmtKind::Loop { body } => loop { @@ -313,6 +285,65 @@ impl ExecEnv { Ok(HaltStatus::Finished) } + /// Call a function with the given arguments (i.e., actual + /// parameters). If the function invocation fails for some reason, + /// report the error at `span`. + fn fn_call( + &mut self, + func: Functio, + args: Vec, + span: &Range, + ) -> Result<(), Error> { + match func { + Functio::BfFunctio { + instructions, + tape_len, + } => { + let mut input: Vec = vec![]; + for arg in args { + arg.bf_write(&mut input); + } + println!("input = {:?}", input); + let mut output = vec![]; + + crate::brian::Interpreter::from_ascii_with_tape_limit( + &instructions, + &input as &[_], + tape_len, + ) + .interpret_with_output(&mut output) + .map_err(|e| Error { + kind: ErrorKind::BfInterpretError(e), + span: span.to_owned(), + })?; + + stdout() + .write_all(&output) + .expect("Failed to write to stdout"); + } + Functio::AbleFunctio { params, body } => { + if params.len() != args.len() { + return Err(Error { + kind: ErrorKind::MismatchedArgumentError, + span: span.to_owned(), + }); + } + + self.stack.push(Default::default()); + + for (param, arg) in params.iter().zip(args.iter()) { + self.decl_var(param, arg.to_owned()); + } + + let res = self.eval_stmts_hs(&body, false); + + self.stack.pop(); + res?; + } + } + Ok(()) + } + /// Get the value of a variable. Throw an error if the variable is /// inaccessible or banned. fn get_var(&self, name: &Iden) -> Result { diff --git a/src/parser.rs b/src/parser.rs index 59f10120..53b4cee4 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -347,7 +347,7 @@ impl<'source> Parser<'source> { self.require(Token::LeftParen)?; - let mut args = vec![]; + let mut params = vec![]; loop { match self .lexer @@ -356,7 +356,7 @@ impl<'source> Parser<'source> { { Token::RightParen => break, Token::Identifier(i) => { - args.push(Iden::new(i, self.lexer.span())); + params.push(Iden::new(i, self.lexer.span())); // Require comma (next) or right paren (end) after identifier match self @@ -380,7 +380,7 @@ impl<'source> Parser<'source> { let body = self.get_block()?; - Ok(StmtKind::Functio { iden, args, body }) + Ok(StmtKind::Functio { iden, params, body }) } /// Parse BF function declaration diff --git a/src/variables.rs b/src/variables.rs index 978635cc..89f14c54 100644 --- a/src/variables.rs +++ b/src/variables.rs @@ -40,7 +40,10 @@ pub enum Functio { instructions: Vec, tape_len: usize, }, - AbleFunctio(Vec), + AbleFunctio { + params: Vec, + body: Vec, + }, } #[derive(Debug, PartialEq, Clone)] @@ -76,7 +79,7 @@ impl Value { /// break a significant amount of AbleScript code. If more types /// are added in the future, they should be assigned the remaining /// discriminant bytes from 06..FF. - pub fn bf_write(&mut self, stream: &mut impl Write) { + pub fn bf_write(&self, stream: &mut impl Write) { match self { Value::Nul => stream.write_all(&[0]), Value::Str(s) => stream @@ -108,7 +111,7 @@ impl Value { .and_then(|_| stream.write_all(&(instructions.len() as u32).to_le_bytes())) .and_then(|_| stream.write_all(&instructions)) } - Functio::AbleFunctio(_) => { + Functio::AbleFunctio { params: _, body: _ } => { todo!() } }), @@ -158,7 +161,10 @@ impl Display for Value { Value::Bool(v) => write!(f, "{}", v), Value::Abool(v) => write!(f, "{}", v), Value::Functio(v) => match v { - Functio::BfFunctio { instructions, tape_len } => { + Functio::BfFunctio { + instructions, + tape_len, + } => { write!( f, "({}) {}", @@ -167,10 +173,15 @@ impl Display for Value { .expect("Brainfuck functio source should be UTF-8") ) } - Functio::AbleFunctio(source) => { - // TODO: what's the proper way to display an - // AbleScript functio? - write!(f, "{:?}", source) + Functio::AbleFunctio { params, body } => { + write!( + f, + "({}) -> {:?}", + params.join(", "), + // Maybe we should have a pretty-printer for + // statement blocks at some point? + body, + ) } }, }