From aeedfb4ef28d2a67d85aec9ffb4792ae891ee5ea Mon Sep 17 00:00:00 2001 From: Daniel M Date: Thu, 10 Feb 2022 01:26:11 +0100 Subject: [PATCH] Implement functions - Implement function declaration and call - Change the precalculated variable stack positions to contain the offset from the end instead of the absolute position. This is important for passing fun args on the stack - Add the ability to offset the stackframes. This is used to delete the stack where the fun args have been stored before the block executes - Implement exit type for blocks in interpreter. This is used to get the return values and propagate them where needed - Add recursive fibonacci examples --- examples/recursive_fib.nek | 9 +++ examples/recursive_fib.py | 6 ++ src/ast.rs | 14 ++++ src/astoptimizer.rs | 2 + src/interpreter.rs | 101 ++++++++++++++++++++++++---- src/lexer.rs | 3 + src/parser.rs | 133 +++++++++++++++++++++++++++++++++++-- src/token.rs | 24 ++++++- 8 files changed, 272 insertions(+), 20 deletions(-) create mode 100644 examples/recursive_fib.nek create mode 100644 examples/recursive_fib.py diff --git a/examples/recursive_fib.nek b/examples/recursive_fib.nek new file mode 100644 index 0000000..b99fa05 --- /dev/null +++ b/examples/recursive_fib.nek @@ -0,0 +1,9 @@ +fun fib(n) { + if n <= 1 { + return n; + } else { + return fib(n-1) + fib(n-2); + } +} + +print fib(30); \ No newline at end of file diff --git a/examples/recursive_fib.py b/examples/recursive_fib.py new file mode 100644 index 0000000..eb6ec29 --- /dev/null +++ b/examples/recursive_fib.py @@ -0,0 +1,6 @@ +def fib(n): + if n <= 1: + return n + return fib(n-1) + fib(n-2) + +print(fib(30)) \ No newline at end of file diff --git a/src/ast.rs b/src/ast.rs index 4e5edc4..2443161 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -1,3 +1,5 @@ +use std::rc::Rc; + use crate::stringstore::{StringStore, Sid}; /// Types for binary operators @@ -86,6 +88,8 @@ pub enum Expression { /// Array access with name, stackpos and position ArrayAccess(Sid, usize, Box), + FunCall(Sid, usize, Vec), + /// Variable Var(Sid, usize), /// Binary operation. Consists of type, left hand side and right hand side @@ -114,9 +118,19 @@ pub struct If { pub body_false: BlockScope, } +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct FunDecl { + pub name: Sid, + pub fun_stackpos: usize, + pub argnames: Vec, + pub body: Rc, +} + #[derive(Debug, PartialEq, Eq, Clone)] pub enum Statement { + Return(Expression), Declaration(Sid, usize, Expression), + FunDeclare(FunDecl), Expr(Expression), Block(BlockScope), Loop(Loop), diff --git a/src/astoptimizer.rs b/src/astoptimizer.rs index c97d25e..5017083 100644 --- a/src/astoptimizer.rs +++ b/src/astoptimizer.rs @@ -41,6 +41,8 @@ impl SimpleAstOptimizer { } Statement::Print(expr) => Self::optimize_expr(expr), Statement::Declaration(_, _, expr) => Self::optimize_expr(expr), + Statement::FunDeclare(_) => (), + Statement::Return(expr) => Self::optimize_expr(expr), } } } diff --git a/src/interpreter.rs b/src/interpreter.rs index e378860..bb4ee2d 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -1,8 +1,8 @@ -use std::cell::RefCell; +use std::{cell::RefCell, rc::Rc}; use thiserror::Error; use crate::{ - ast::{Ast, BinOpType, BlockScope, Expression, If, Statement, UnOpType}, + ast::{Ast, BinOpType, BlockScope, Expression, FunDecl, If, Statement, UnOpType}, astoptimizer::{AstOptimizer, SimpleAstOptimizer}, lexer::lex, parser::parse, @@ -25,6 +25,8 @@ pub enum RuntimeError { ArrayOutOfBounds(usize, usize), #[error("Division by zero")] DivideByZero, + #[error("Invalid number of arguments for function {0}. Expected {1}, got {2}")] + InvalidNumberOfArgs(String, usize, usize), } #[derive(Debug, PartialEq, Eq, Clone)] @@ -32,6 +34,13 @@ pub enum Value { I64(i64), String(Sid), Array(RefCell>), + Void, +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum BlockExit { + Normal, + Return(Value), } #[derive(Default)] @@ -47,6 +56,8 @@ pub struct Interpreter { // Variable table stores the runtime values of variables vartable: Vec, + funtable: Vec, + stringstore: StringStore, } @@ -63,10 +74,11 @@ impl Interpreter { } fn get_var(&self, idx: usize) -> Option { - self.vartable.get(idx).cloned() + self.vartable.get(self.vartable.len() - idx - 1).cloned() } fn get_var_mut(&mut self, idx: usize) -> Option<&mut Value> { + let idx = self.vartable.len() - idx - 1; self.vartable.get_mut(idx) } @@ -92,14 +104,30 @@ impl Interpreter { self.stringstore = ast.stringstore; - self.run_block(&ast.main) + self.run_block(&ast.main)?; + Ok(()) } - pub fn run_block(&mut self, prog: &BlockScope) -> Result<(), RuntimeError> { - let framepointer = self.vartable.len(); + pub fn run_block(&mut self, prog: &BlockScope) -> Result { + self.run_block_fp_offset(prog, 0) + } + + pub fn run_block_fp_offset( + &mut self, + prog: &BlockScope, + framepointer_offset: usize, + ) -> Result { + let framepointer = self.vartable.len() - framepointer_offset; for stmt in prog { match stmt { + Statement::Return(expr) => { + let val = self.resolve_expr(expr)?; + + self.vartable.truncate(framepointer); + return Ok(BlockExit::Return(val)); + } + Statement::Expr(expr) => { self.resolve_expr(expr)?; } @@ -110,7 +138,13 @@ impl Interpreter { } Statement::Block(block) => { - self.run_block(block)?; + match self.run_block(block)? { + BlockExit::Return(val) => { + self.vartable.truncate(framepointer); + return Ok(BlockExit::Return(val)); + } + _ => () + } } Statement::Loop(looop) => { @@ -143,18 +177,29 @@ impl Interpreter { body_true, body_false, }) => { - if matches!(self.resolve_expr(condition)?, Value::I64(0)) { - self.run_block(body_false)?; + let exit = if matches!(self.resolve_expr(condition)?, Value::I64(0)) { + self.run_block(body_false)? } else { - self.run_block(body_true)?; + self.run_block(body_true)? + }; + match exit { + BlockExit::Return(val) => { + self.vartable.truncate(framepointer); + return Ok(BlockExit::Return(val)); + } + _ => (), } } + + Statement::FunDeclare(fundec) => { + self.funtable.push(fundec.clone()); + } } } self.vartable.truncate(framepointer); - Ok(()) + Ok(BlockExit::Normal) } fn resolve_expr(&mut self, expr: &Expression) -> Result { @@ -174,6 +219,29 @@ impl Interpreter { Expression::ArrayAccess(name, idx, arr_idx) => { self.resolve_array_access(*name, *idx, arr_idx)? } + + Expression::FunCall(fun_name, fun_stackpos, args) => { + for arg in args { + let arg = self.resolve_expr(arg)?; + self.vartable.push(arg); + } + + // Function existance has been verified in the parser, so unwrap here shouldn't fail + let num_args = self.funtable.get(*fun_stackpos).unwrap().argnames.len(); + + if num_args != args.len() { + let fun_name = self.stringstore.lookup(*fun_name).cloned().unwrap_or("".to_string()); + return Err(RuntimeError::InvalidNumberOfArgs(fun_name, num_args, args.len())); + } + + match self.run_block_fp_offset( + &Rc::clone(&self.funtable.get(*fun_stackpos).unwrap().body), + num_args, + )? { + BlockExit::Normal => Value::Void, + BlockExit::Return(val) => val, + } + } }; Ok(val) @@ -310,8 +378,12 @@ impl Interpreter { BinOpType::Add => Value::I64(lhs + rhs), BinOpType::Mul => Value::I64(lhs * rhs), BinOpType::Sub => Value::I64(lhs - rhs), - BinOpType::Div => Value::I64(lhs.checked_div(rhs).ok_or(RuntimeError::DivideByZero)?), - BinOpType::Mod => Value::I64(lhs.checked_rem(rhs).ok_or(RuntimeError::DivideByZero)?), + BinOpType::Div => { + Value::I64(lhs.checked_div(rhs).ok_or(RuntimeError::DivideByZero)?) + } + BinOpType::Mod => { + Value::I64(lhs.checked_rem(rhs).ok_or(RuntimeError::DivideByZero)?) + } BinOpType::BOr => Value::I64(lhs | rhs), BinOpType::BAnd => Value::I64(lhs & rhs), BinOpType::BXor => Value::I64(lhs ^ rhs), @@ -338,7 +410,8 @@ impl Interpreter { match val { Value::I64(val) => format!("{}", val), Value::Array(val) => format!("{:?}", val.borrow()), - Value::String(text) => format!("{}", self.stringstore.lookup(*text).unwrap()), + Value::String(text) => format!("{}", self.stringstore.lookup(*text).unwrap_or(&"".to_string())), + Value::Void => format!("void"), } } } diff --git a/src/lexer.rs b/src/lexer.rs index 5b14a99..9c592cc 100644 --- a/src/lexer.rs +++ b/src/lexer.rs @@ -70,6 +70,7 @@ impl<'a> Lexer<'a> { ('|', '|') => self.push_tok_consume(T![||]), // Single character tokens + (',', _) => self.push_tok(T![,]), (';', _) => self.push_tok(T![;]), ('+', _) => self.push_tok(T![+]), ('-', _) => self.push_tok(T![-]), @@ -199,6 +200,8 @@ impl<'a> Lexer<'a> { "print" => T![print], "if" => T![if], "else" => T![else], + "fun" => T![fun], + "return" => T![return], // If it doesn't match a keyword, it is a normal identifier _ => T![ident(ident)], diff --git a/src/parser.rs b/src/parser.rs index 47afd2b..906ad6b 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,7 +1,7 @@ use thiserror::Error; use crate::{ - ast::{Ast, BlockScope, Expression, If, Loop, Statement}, + ast::{Ast, BlockScope, Expression, FunDecl, If, Loop, Statement}, stringstore::{Sid, StringStore}, token::Token, util::{PutBackIter, PutBackableExt}, @@ -16,6 +16,10 @@ pub enum ParseErr { DeclarationOfNonVar, #[error("Use of undefined variable \"{0}\"")] UseOfUndeclaredVar(String), + #[error("Use of undefined function \"{0}\"")] + UseOfUndeclaredFun(String), + #[error("Redeclation of function \"{0}\"")] + RedeclarationFun(String), } type ResPE = Result; @@ -39,6 +43,7 @@ struct Parser> { tokens: PutBackIter, string_store: StringStore, var_stack: Vec, + fun_stack: Vec, } impl> Parser { @@ -47,10 +52,12 @@ impl> Parser { let tokens = tokens.into_iter().putbackable(); let string_store = StringStore::new(); let var_stack = Vec::new(); + let fun_stack = Vec::new(); Self { tokens, string_store, var_stack, + fun_stack, } } @@ -62,10 +69,14 @@ impl> Parser { }) } + fn parse_scoped_block(&mut self) -> ResPE { + self.parse_scoped_block_fp_offset(0) + } + /// Parse tokens into an abstract syntax tree. This will continuously parse statements until /// encountering end-of-file or a block end '}' . - fn parse_scoped_block(&mut self) -> ResPE { - let framepointer = self.var_stack.len(); + fn parse_scoped_block_fp_offset(&mut self, framepoint_offset: usize) -> ResPE { + let framepointer = self.var_stack.len() - framepoint_offset; let mut prog = Vec::new(); loop { @@ -109,8 +120,80 @@ impl> Parser { Statement::Print(expr) } + T![return] => { + self.next(); + let stmt = Statement::Return(self.parse_expr()?); + + // After a statement, there must be a semicolon + validate_next!(self, T![;], ";"); + + stmt + } + T![if] => Statement::If(self.parse_if()?), + T![fun] => { + self.next(); + + let fun_name = match self.next() { + T![ident(fun_name)] => fun_name, + tok => return Err(ParseErr::UnexpectedToken(tok, "".to_string())), + }; + + let fun_name = self.string_store.intern_or_lookup(&fun_name); + + if self.fun_stack.contains(&fun_name) { + return Err(ParseErr::RedeclarationFun( + self.string_store + .lookup(fun_name) + .cloned() + .unwrap_or("".to_string()), + )); + } + + let fun_stackpos = self.fun_stack.len(); + self.fun_stack.push(fun_name); + + let mut arg_names = Vec::new(); + + validate_next!(self, T!['('], "("); + + while matches!(self.peek(), T![ident(_)]) { + let var_name = match self.next() { + T![ident(var_name)] => var_name, + _ => unreachable!(), + }; + + let var_name = self.string_store.intern_or_lookup(&var_name); + arg_names.push(var_name); + + // Push the variable onto the varstack + self.var_stack.push(var_name); + + // If there are more args skip the comma so that the loop will read the argname + if self.peek() == &T![,] { + self.next(); + } + } + + validate_next!(self, T![')'], ")"); + + validate_next!(self, T!['{'], "{"); + + // Create the scoped block with a stack offset. This will pop the args that are + // added to the stack while parsing args + let body = self.parse_scoped_block_fp_offset(arg_names.len())?; + + validate_next!(self, T!['}'], "}"); + + Statement::FunDeclare(FunDecl { + name: fun_name, + fun_stackpos, + argnames: arg_names, + body: body.into(), + }) + } + _ => { let first = self.next(); @@ -118,11 +201,12 @@ impl> Parser { (T![ident(name)], T![<-]) => { self.next(); + let rhs = self.parse_expr()?; + let sid = self.string_store.intern_or_lookup(&name); let sp = self.var_stack.len(); self.var_stack.push(sid); - let rhs = self.parse_expr()?; Statement::Declaration(sid, sp, rhs) } (first, _) => { @@ -272,6 +356,31 @@ impl> Parser { Expression::ArrayAccess(sid, stackpos, index.into()) } + T![ident(name)] if self.peek() == &T!['('] => { + // Skip the opening parenthesis + self.next(); + + let sid = self.string_store.intern_or_lookup(&name); + + let mut args = Vec::new(); + + while !matches!(self.peek(), T![')']) { + let arg = self.parse_expr()?; + args.push(arg); + + // If there are more args skip the comma so that the loop will read the argname + if self.peek() == &T![,] { + self.next(); + } + } + + validate_next!(self, T![')'], ")"); + + let fun_stackpos = self.get_fun_stackpos(sid)?; + + Expression::FunCall(sid, fun_stackpos, args) + } + T![ident(name)] => { let sid = self.string_store.intern_or_lookup(&name); let stackpos = self.get_stackpos(sid)?; @@ -303,7 +412,7 @@ impl> Parser { .iter() .rev() .position(|it| *it == varid) - .map(|it| self.var_stack.len() - it - 1) + .map(|it| it) .ok_or(ParseErr::UseOfUndeclaredVar( self.string_store .lookup(varid) @@ -312,6 +421,20 @@ impl> Parser { )) } + fn get_fun_stackpos(&self, varid: Sid) -> ResPE { + self.fun_stack + .iter() + .rev() + .position(|it| *it == varid) + .map(|it| self.fun_stack.len() - it - 1) + .ok_or(ParseErr::UseOfUndeclaredFun( + self.string_store + .lookup(varid) + .map(String::from) + .unwrap_or("".to_string()), + )) + } + /// Get the next Token without removing it fn peek(&mut self) -> &Token { self.tokens.peek().unwrap_or(&T![EoF]) diff --git a/src/token.rs b/src/token.rs index 33cb5b7..add510c 100644 --- a/src/token.rs +++ b/src/token.rs @@ -1,4 +1,7 @@ -use crate::{ast::{BinOpType, UnOpType}, T}; +use crate::{ + ast::{BinOpType, UnOpType}, + T, +}; /// Language keywords #[derive(Debug, PartialEq, Eq)] @@ -11,6 +14,10 @@ pub enum Keyword { If, /// Else keyword ("else") Else, + /// Function declaration keyword ("fun") + Fun, + /// Return keyword ("return") + Return, } /// Literal values @@ -67,6 +74,9 @@ pub enum Token { /// Combined tokens consisting of multiple characters Combo(Combo), + /// Comma (",") + Comma, + /// Equal Sign ("=") Equal, @@ -199,6 +209,14 @@ macro_rules! T { crate::token::Token::Keyword(crate::token::Keyword::Else) }; + [fun] => { + crate::token::Token::Keyword(crate::token::Keyword::Fun) + }; + + [return] => { + crate::token::Token::Keyword(crate::token::Keyword::Return) + }; + // Literals [i64($($val:tt)*)] => { crate::token::Token::Literal(crate::token::Literal::I64($($val)*)) @@ -251,6 +269,10 @@ macro_rules! T { }; // Normal Tokens + [,] => { + crate::token::Token::Comma + }; + [=] => { crate::token::Token::Equal };