Browse Source

Functions

pull/1/head
Stephen 1 month ago
parent
commit
82a4f13cb0
17 changed files with 862 additions and 107 deletions
  1. +8
    -0
      examples/fib_benchmark.bas
  2. +0
    -0
      examples/fibonnaci.bas
  3. +8
    -1
      src/chunk.rs
  4. +68
    -12
      src/compiler.rs
  5. +6
    -0
      src/error_handler.rs
  6. +4
    -9
      src/expr.rs
  7. +32
    -0
      src/function.rs
  8. +2
    -0
      src/lib.rs
  9. +4
    -0
      src/opcodes.rs
  10. +169
    -55
      src/parser.rs
  11. +147
    -0
      src/resolver.rs
  12. +17
    -1
      src/stmt.rs
  13. +4
    -3
      src/visitor.rs
  14. +95
    -21
      src/vm.rs
  15. +15
    -5
      src/xbasic.rs
  16. +255
    -0
      tests/functions.rs
  17. +28
    -0
      tests/misc.rs

+ 8
- 0
examples/fib_benchmark.bas View File

@@ -0,0 +1,8 @@
print fib(35)

function fib(n)
if n < 2 then
return n
end if
return fib(n - 1) + fib(n - 2)
end function

examples/factorial.bas → examples/fibonnaci.bas View File


+ 8
- 1
src/chunk.rs View File

@@ -4,15 +4,22 @@ pub struct Chunk {
pub(crate) instructions: Vec<u8>,
pub(crate) lines: Vec<usize>,
pub(crate) literals: Vec<ExprValue>,
pub(crate) arity: usize,
}

impl Chunk {
pub(crate) fn new(instructions: Vec<u8>, lines: Vec<usize>, literals: Vec<ExprValue>) -> Self {
pub(crate) fn new(
instructions: Vec<u8>,
lines: Vec<usize>,
literals: Vec<ExprValue>,
arity: usize,
) -> Self {
assert_eq!(lines.len(), instructions.len());
Self {
instructions,
lines,
literals,
arity,
}
}
}

+ 68
- 12
src/compiler.rs View File

@@ -1,15 +1,19 @@
use crate::chunk::Chunk;
use crate::error_handler::ErrorHandler;
use crate::expr::{BinaryExpr, CallExpr, ExprValue, LogicalExpr, UnaryExpr, VariableExpr};
use crate::function::Function;
use crate::opcodes::OpCode;
use crate::stmt::{AssignStmt, ExpressionStmt, ForStmt, IfStmt, PrintStmt, Stmt, WhileStmt};
use crate::stmt::{
AssignStmt, ExpressionStmt, ForStmt, IfStmt, PrintStmt, ReturnStmt, Stmt, WhileStmt,
};
use crate::tokens::{Token, TokenType};
use crate::visitor::{ExprVisitor, StmtVisitor};
use std::collections::HashMap;

pub struct Compiler {
pub(crate) struct Compiler {
current_line: usize,
variables: HashMap<String, usize>,
functions: Vec<Function>,
cur_variable: usize,
error_handler: ErrorHandler,
instructions: Vec<u8>,
@@ -18,10 +22,11 @@ pub struct Compiler {
}

impl Compiler {
pub fn new() -> Self {
pub(crate) fn new() -> Self {
Self {
current_line: 0,
variables: HashMap::new(),
functions: Vec::new(),
cur_variable: 0,
error_handler: ErrorHandler::new(),
instructions: Vec::new(),
@@ -30,7 +35,13 @@ impl Compiler {
}
}

pub fn compile(&mut self, stmts: Vec<Stmt>, error_handler: &mut ErrorHandler) -> Option<Chunk> {
pub(crate) fn compile(
&mut self,
stmts: Vec<Stmt>,
functions: Vec<Function>,
error_handler: &mut ErrorHandler,
) -> Option<(Chunk, Vec<Chunk>)> {
self.functions = functions.clone();
self.reset();

for stmt in stmts.clone() {
@@ -38,16 +49,43 @@ impl Compiler {
stmt.accept(self);
}

error_handler.merge(&self.error_handler);
if self.error_handler.had_errors {
None
} else {
Some(Chunk::new(
let chunk = Chunk::new(
self.instructions.clone(),
self.lines.clone(),
self.literals.clone(),
0,
);

let mut fun_chunks = Vec::new();
let vars = self.variables.clone();
for func in &functions {
self.reset();
self.variables = HashMap::new();
for (i, param) in func.parameters.iter().enumerate() {
self.variables.insert(param.lexeme.clone(), i);
}

for stmt in &func.stmts {
self.current_line = stmt.line;
stmt.accept(self);
}
self.emit_opcode(OpCode::Nil);
self.emit_opcode(OpCode::Ret);
fun_chunks.push(Chunk::new(
self.instructions.clone(),
self.lines.clone(),
self.literals.clone(),
func.parameters.len(),
))
}
self.variables = vars;

error_handler.merge(&self.error_handler);
if self.error_handler.had_errors {
None
} else {
Some((chunk, fun_chunks))
}
}

pub(crate) fn clear_errors(&mut self) {
@@ -205,6 +243,11 @@ impl StmtVisitor<()> for Compiler {
self.patch_jmp(for_end_jmp, line);
self.emit_opcode(OpCode::Pop);
}

fn visit_return_stmt(&mut self, stmt: &ReturnStmt, _: usize) {
stmt.return_value.accept(self);
self.emit_opcode(OpCode::Ret);
}
}

impl ExprVisitor<()> for Compiler {
@@ -237,7 +280,7 @@ impl ExprVisitor<()> for Compiler {
TokenType::LessEqual => OpCode::LessEqual,
TokenType::Greater => OpCode::Greater,
TokenType::GreaterEqual => OpCode::GreaterEqual,
_ => unimplemented!(),
_ => unreachable!(),
});
}

@@ -250,8 +293,21 @@ impl ExprVisitor<()> for Compiler {
});
}

fn visit_call_expr(&mut self, _expr: &CallExpr) {
unimplemented!()
fn visit_call_expr(&mut self, expr: &CallExpr) {
for (i, function) in self.functions.iter().enumerate() {
if function.name == expr.name {
// Arguments
for arg in &expr.arguments {
arg.accept(self);
}

// Call
self.emit_opcode(OpCode::Call);
self.emit_byte(i as u8);
return;
}
}
unreachable!();
}

fn visit_literal_expr(&mut self, expr: &ExprValue) {


+ 6
- 0
src/error_handler.rs View File

@@ -59,3 +59,9 @@ impl ErrorHandler {
.push(format!("[line {}] Error {}: {}", line, location, msg));
}
}

impl Default for ErrorHandler {
fn default() -> Self {
Self::new()
}
}

+ 4
- 9
src/expr.rs View File

@@ -38,19 +38,15 @@ impl Expr {
})
}

pub fn new_call(callee: Expr, paren: Token, arguments: Vec<Expr>) -> Self {
Self::Call(CallExpr {
callee: Box::new(callee),
paren,
arguments,
})
pub fn new_call(name: String, arguments: Vec<Expr>) -> Self {
Self::Call(CallExpr { name, arguments })
}

pub fn new_literal(value: ExprValue) -> Self {
Self::Literal(value)
}

pub fn accept<U, V>(&self, visitor: &mut V) -> U
pub(crate) fn accept<U, V>(&self, visitor: &mut V) -> U
where
V: ExprVisitor<U>,
{
@@ -98,8 +94,7 @@ pub struct UnaryExpr {

#[derive(Clone)]
pub struct CallExpr {
pub callee: Box<Expr>,
pub paren: Token,
pub name: String,
pub arguments: Vec<Expr>,
}



+ 32
- 0
src/function.rs View File

@@ -0,0 +1,32 @@
use crate::stmt::Stmt;
use crate::tokens::Token;

#[derive(Clone)]
pub(crate) struct FunctionDefinition {
pub(crate) name: String,
pub(crate) arity: u8,
pub(crate) line: usize,
}

impl FunctionDefinition {
pub(crate) fn new(name: String, arity: u8, line: usize) -> Self {
Self { name, arity, line }
}
}

#[derive(Clone)]
pub(crate) struct Function {
pub(crate) name: String,
pub(crate) parameters: Vec<Token>,
pub(crate) stmts: Vec<Stmt>,
}

impl Function {
pub fn new(name: String, parameters: Vec<Token>, stmts: Vec<Stmt>) -> Self {
Self {
name,
parameters,
stmts,
}
}
}

+ 2
- 0
src/lib.rs View File

@@ -3,8 +3,10 @@ mod chunk;
mod compiler;
mod error_handler;
mod expr;
mod function;
mod opcodes;
mod parser;
mod resolver;
mod scanner;
mod stmt;
mod tokens;


+ 4
- 0
src/opcodes.rs View File

@@ -6,6 +6,7 @@ pub(crate) enum OpCode {
Print,
Literal8,
Literal16,
Nil,

Var,
NewVar,
@@ -32,4 +33,7 @@ pub(crate) enum OpCode {
Jne,
Jmp,
JmpBack,

Call,
Ret,
}

+ 169
- 55
src/parser.rs View File

@@ -1,40 +1,100 @@
use crate::error_handler::ErrorHandler;
use crate::expr::{Expr, ExprValue};
use crate::function::{Function, FunctionDefinition};
use crate::stmt::Stmt;
use crate::tokens::{Token, TokenType};

pub struct Parser<'a> {
pub(crate) struct Parser<'a> {
tokens: Vec<Token>,
functions: Vec<FunctionDefinition>,
current: usize,
error_handler: &'a mut ErrorHandler,
}

impl<'a> Parser<'a> {
pub fn new(tokens: Vec<Token>, error_handler: &'a mut ErrorHandler) -> Self {
pub(crate) fn new(
tokens: Vec<Token>,
functions: Vec<FunctionDefinition>,
error_handler: &'a mut ErrorHandler,
) -> Self {
Self {
tokens,
functions,
current: 0,
error_handler,
tokens,
}
}

pub fn parse(&mut self) -> Option<Vec<Stmt>> {
pub(crate) fn parse(&mut self) -> Option<(Vec<Stmt>, Vec<Function>)> {
let mut v: Vec<Stmt> = Vec::new();
let mut f: Vec<Function> = Vec::new();
while !self.is_at_end() {
let stmt = self.declaration()?;
v.push(stmt);
while self.match_type(&TokenType::Newline).is_some() {}

if self.match_type(&TokenType::Function).is_some() {
f.push(self.function()?);
} else {
match self.statement() {
Some(stmt) => v.push(stmt),
None => {
self.synchronize(); // TODO this is useless
return None;
}
}
}
}
Some(v)
}

fn declaration(&mut self) -> Option<Stmt> {
match self.statement() {
Some(stmt) => Some(stmt),
None => {
self.synchronize();
None
Some((v, f))
}

fn function(&mut self) -> Option<Function> {
// Read in identifier
let name = self.consume(TokenType::Identifier, "Expected identifier after FUNCTION.")?;
// Read in (
self.consume(TokenType::LeftParen, "Expected '(' after function name.")?;

// Get parameters
let mut params = Vec::new();
if self.peek().token_type != TokenType::RightParen {
let mut another = true;
while !self.is_at_end() && another {
// Should never fail since we already checked this in the resolver
params.push(self.consume(TokenType::Identifier, "Expected parameter name.")?);
another = self.match_type(&TokenType::Comma).is_some();
}
}

// Read in )
self.consume(
TokenType::RightParen,
"Expected ')' after function parameters.",
)?;

// Newline
self.consume(
TokenType::Newline,
"Expected newline after function parameters.",
);

// Keep going until we read in "end function"
let mut statements = Vec::new();
while !self.is_at_end()
&& (self.peek().token_type != TokenType::End
|| self.peek_next() != &TokenType::Function)
{
statements.push(self.statement()?)
}

self.consume(
TokenType::End,
"Expected END FUNCTION after function declaration.",
);
self.consume(
TokenType::Function,
"Expected END FUNCTION after function declaration",
)?;
self.consume(TokenType::Newline, "Expected newline after END FUBNCTION")?;

Some(Function::new(name.lexeme, params, statements))
}

fn statement(&mut self) -> Option<Stmt> {
@@ -42,6 +102,10 @@ impl<'a> Parser<'a> {
return self.print_statement();
}

if self.match_type(&TokenType::Return).is_some() {
return self.return_statement();
}

if self.match_type(&TokenType::If).is_some() {
return self.if_statement("IF");
}
@@ -91,6 +155,20 @@ impl<'a> Parser<'a> {
Some(Stmt::new_print(values, line))
}

fn return_statement(&mut self) -> Option<Stmt> {
let return_value = if self.match_type(&TokenType::Newline).is_none() {
let return_value = self.expression()?;
self.consume(
TokenType::Newline,
"Expected newline after RETURN statement.",
)?;
return_value
} else {
Expr::new_literal(ExprValue::Single(0.0))
};
Some(Stmt::new_return(return_value, self.previous().line))
}

fn if_statement(&mut self, type_str: &str) -> Option<Stmt> {
let line = self.previous().line;
let condition = self.expression()?;
@@ -280,34 +358,7 @@ impl<'a> Parser<'a> {
};
}

self.call()
}

fn call(&mut self) -> Option<Expr> {
let mut expr = self.primary()?;

while self.match_type(&TokenType::LeftParen).is_some() {
expr = self.finish_call(expr)?;
}

Some(expr)
}

fn finish_call(&mut self, callee: Expr) -> Option<Expr> {
let mut args: Vec<Expr> = Vec::new();
if !self.check(&TokenType::RightParen) {
args.push(self.expression()?);
while self.match_type(&TokenType::Comma).is_some() {
if args.len() >= 255 {
self.error_handler
.error_token(&self.peek(), "Cannot have more than 255 arguments.");
}
args.push(self.expression()?);
}
}

let paren = self.consume(TokenType::RightParen, "Expected ')' after arguments.")?;
Some(Expr::new_call(callee, paren, args))
self.primary()
}

fn primary(&mut self) -> Option<Expr> {
@@ -329,16 +380,21 @@ impl<'a> Parser<'a> {
}
match self.expression() {
Some(expr) => {
self.consume(TokenType::RightParen, "Expected ')' after expression.");

// TODO I don't think we need a grouping here.
// Expr::new_grouping(expr)
self.consume(TokenType::RightParen, "Expected ')' after expression.")?;
expr
}
None => return None,
}
}
TokenType::Identifier => Expr::new_variable(self.previous()),
TokenType::Identifier => {
// Could be a variable or a function
let name = self.previous();
if self.match_type(&TokenType::LeftParen).is_some() {
self.function_call(name)?
} else {
Expr::new_variable(name)
}
}
_ => {
self.error_handler
.error_token(&self.peek(), "Expected expression.");
@@ -347,18 +403,76 @@ impl<'a> Parser<'a> {
})
}

fn function_call(&mut self, identifier: Token) -> Option<Expr> {
// Verify function exists
let mut definition = None;
for def in self.functions.clone() {
if def.name == identifier.lexeme {
definition = Some(def);
break;
}
}

let definition = match definition {
Some(x) => x,
None => {
self.error_handler
.error_token(&identifier, "Not a function.");
return None;
}
};

// Collect args
let mut args = Vec::new();
if self.peek().token_type != TokenType::RightParen {
let mut again = true;
while !self.is_at_end() && again {
args.push(self.expression()?);
again = self.match_type(&TokenType::Comma).is_some();
}
}

// Consume )
self.consume(
TokenType::RightParen,
"Expected ')' after function call arguments.",
)?;

if args.len() > 255 {
self.error_handler.error_token(
&identifier,
"Cannot call function with more than 255 arguments.",
);
return None;
}

// Check arity
if (definition.arity) != args.len() as u8 {
self.error_handler.error_token(
&identifier,
&format!(
"Expected {} arguments, got {}.",
definition.arity,
args.len()
),
);
return None;
}

// Build function call expression
Some(Expr::new_call(identifier.lexeme, args))
}

fn synchronize(&mut self) {
self.advance();
while !self.is_at_end() {
// TODO more here?
match self.peek().token_type {
TokenType::Newline => {
// jump over newline
self.advance();
return;
}
_ => (),
if self.peek().token_type == TokenType::Newline {
// jump over newline
self.advance();
return;
}

self.advance();
}
}


+ 147
- 0
src/resolver.rs View File

@@ -0,0 +1,147 @@
use crate::error_handler::ErrorHandler;
use crate::function::FunctionDefinition;
use crate::tokens::{Token, TokenType};

pub(crate) struct Resolver<'a> {
tokens: Vec<Token>,
current: usize,
functions: Vec<FunctionDefinition>,
error_handler: &'a mut ErrorHandler,
}

impl<'a> Resolver<'a> {
pub(crate) fn new(tokens: Vec<Token>, error_handler: &'a mut ErrorHandler) -> Self {
Self {
tokens,
current: 0,
functions: Vec::new(),
error_handler,
}
}

pub(crate) fn resolve(&mut self) -> Option<Vec<FunctionDefinition>> {
self.functions = Vec::new();
while !self.is_at_end() {
self.advance();
if self.previous().token_type == TokenType::Function {
// TODO is 256 functions enough?
// May want to bump this to 65536 functions
if self.functions.len() >= 256 {
// Kind of gross
// Clone is b/c we're borrowing mutably and immutably at the same time
self.error_handler.error_token(
&self.previous().clone(),
"Cannot have more than 256 functions",
);
return None;
}

let func = self.function()?;
self.functions.push(func);
}
}
Some(self.functions.clone())
}

fn function(&mut self) -> Option<FunctionDefinition> {
// Read in identifier
let name = self.consume(TokenType::Identifier, "Expected identifier after FUNCTION.")?;

// Confirm name isn't already in use
for func in &self.functions {
if func.name == name.lexeme {
self.error_handler.error_token(
&name,
&format!("Function is already defined on line {}.", func.line),
);
return None;
}
}

// Read in (
self.consume(TokenType::LeftParen, "Expected '(' after function name.")?;

// Count parameters
let mut arity = 0;
if self.peek().token_type != TokenType::RightParen {
let mut another = true;
while !self.is_at_end() && another {
if arity == 255 {
self.error_handler
.error_token(&name, "Function cannot have more than 255 parameters.");
return None;
}
arity += 1;
self.consume(TokenType::Identifier, "Expected parameter name.")?;
another = self.match_type(TokenType::Comma);
}
}

// Read in )
self.consume(
TokenType::RightParen,
"Expected ')' after function parameters.",
)?;

// Keep going until we read in "end function"
while !self.is_at_end()
&& (self.peek().token_type != TokenType::End
|| self.peek_next().token_type != TokenType::Function)
{
self.advance();
}

self.consume(
TokenType::End,
"Expected END FUNCTION after function declaration.",
);
self.consume(
TokenType::Function,
"Expected END FUNCTION after function declaration",
)?;

Some(FunctionDefinition::new(name.lexeme, arity, name.line))
}

fn consume(&mut self, token_type: TokenType, msg: &str) -> Option<Token> {
if self.peek().token_type == token_type {
self.advance();
Some(self.tokens[self.current - 1].clone())
} else {
self.error_handler
.error_token(&self.tokens[self.current], msg);
None
}
}

fn match_type(&mut self, token_type: TokenType) -> bool {
if self.peek().token_type == token_type {
self.advance();
true
} else {
false
}
}

fn is_at_end(&self) -> bool {
self.peek().token_type == TokenType::Eof
}

fn peek(&self) -> &Token {
&self.tokens[self.current]
}

fn peek_next(&self) -> &Token {
&self.tokens[self.current + 1]
}

fn previous(&self) -> &Token {
&self.tokens[self.current - 1]
}

fn advance(&mut self) {
if !self.is_at_end() {
self.current += 1;
}
}
}

+ 17
- 1
src/stmt.rs View File

@@ -2,6 +2,8 @@ use crate::expr::Expr;
use crate::tokens::Token;
use crate::visitor::StmtVisitor;

// TODO make everything in here pub(crate)

#[derive(Clone)]
pub struct Stmt {
content: StmtContent,
@@ -16,6 +18,7 @@ enum StmtContent {
If(IfStmt),
While(WhileStmt),
For(ForStmt),
Return(ReturnStmt),
}

impl Stmt {
@@ -81,7 +84,14 @@ impl Stmt {
}
}

pub fn accept<U, V>(&self, visitor: &mut V) -> U
pub fn new_return(return_value: Expr, line: usize) -> Self {
Self {
line,
content: StmtContent::Return(ReturnStmt { return_value }),
}
}

pub(crate) fn accept<U, V>(&self, visitor: &mut V) -> U
where
V: StmtVisitor<U>,
{
@@ -93,6 +103,7 @@ impl Stmt {
StmtContent::If(stmt) => visitor.visit_if_stmt(stmt, line),
StmtContent::While(stmt) => visitor.visit_while_stmt(stmt, line),
StmtContent::For(stmt) => visitor.visit_for_stmt(stmt, line),
StmtContent::Return(stmt) => visitor.visit_return_stmt(stmt, line),
}
}
}
@@ -133,3 +144,8 @@ pub struct ForStmt {
pub(crate) max_value: Expr,
pub(crate) body: Vec<Stmt>,
}

#[derive(Clone)]
pub(crate) struct ReturnStmt {
pub(crate) return_value: Expr,
}

+ 4
- 3
src/visitor.rs View File

@@ -1,18 +1,19 @@
use crate::expr::{BinaryExpr, CallExpr, ExprValue, LogicalExpr, UnaryExpr, VariableExpr};
use crate::stmt::{AssignStmt, ExpressionStmt, ForStmt, IfStmt, PrintStmt, WhileStmt};
use crate::stmt::{AssignStmt, ExpressionStmt, ForStmt, IfStmt, PrintStmt, ReturnStmt, WhileStmt};

// TODO generate these with macros

pub trait StmtVisitor<U> {
pub(crate) trait StmtVisitor<U> {
fn visit_expression_stmt(&mut self, stmt: &ExpressionStmt, line: usize) -> U;
fn visit_print_stmt(&mut self, stmt: &PrintStmt, line: usize) -> U;
fn visit_assign_stmt(&mut self, stmt: &AssignStmt, line: usize) -> U;
fn visit_if_stmt(&mut self, stmt: &IfStmt, line: usize) -> U;
fn visit_while_stmt(&mut self, stmt: &WhileStmt, line: usize) -> U;
fn visit_for_stmt(&mut self, stmt: &ForStmt, line: usize) -> U;
fn visit_return_stmt(&mut self, stmt: &ReturnStmt, line: usize) -> U;
}

pub trait ExprVisitor<U> {
pub(crate) trait ExprVisitor<U> {
fn visit_variable_expr(&mut self, expr: &VariableExpr) -> U;
fn visit_logical_expr(&mut self, expr: &LogicalExpr) -> U;
fn visit_binary_expr(&mut self, expr: &BinaryExpr) -> U;


+ 95
- 21
src/vm.rs View File

@@ -5,14 +5,31 @@ use crate::expr::ExprValue;
use crate::opcodes::OpCode;
use num_traits::FromPrimitive;

struct CallFrame {
chunk_id: usize,
offset: usize,
ip: usize,
}

impl CallFrame {
fn new(chunk_id: usize, ip: usize, offset: usize) -> Self {
Self {
chunk_id,
ip,
offset,
}
}
}

pub(crate) struct VirtualMachine<'a, T> {
ip: usize,
stack: Vec<ExprValue>,
stdio: &'a mut T,
error_handler: ErrorHandler,
instructions: Vec<u8>,
lines: Vec<usize>,
literals: Vec<ExprValue>,
chunks: Vec<Chunk>, // Holds core + function chunks
cur_chunk: usize,
call_stack: Vec<CallFrame>,
offset: usize,
buffer: String,
}

@@ -26,20 +43,26 @@ where
stack: Vec::new(),
stdio,
error_handler: ErrorHandler::new(),
instructions: Vec::new(),
lines: Vec::new(),
literals: Vec::new(),
chunks: Vec::new(),
cur_chunk: 0,
call_stack: Vec::new(),
offset: 0,
buffer: String::new(),
}
}

pub(crate) fn run(&mut self, chunk: Chunk, error_handler: &mut ErrorHandler) -> bool {
pub(crate) fn run(
&mut self,
chunk: Chunk,
function_chunks: Vec<Chunk>,
error_handler: &mut ErrorHandler,
) -> bool {
self.ip = 0;
self.instructions = chunk.instructions;
self.lines = chunk.lines;
self.literals = chunk.literals;
self.cur_chunk = 0;
self.chunks = vec![chunk];
self.chunks.extend(function_chunks);

while self.ip < self.instructions.len() {
while self.ip < self.instructions().len() {
if self.error_handler.had_runtime_error {
break;
}
@@ -55,7 +78,7 @@ where
}

fn process_instruction(&mut self) {
let opcode = FromPrimitive::from_u8(self.instructions[self.ip]).unwrap();
let opcode = FromPrimitive::from_u8(self.instructions()[self.ip]).unwrap();
self.ip += 1;
match opcode {
OpCode::Pop => {
@@ -72,11 +95,11 @@ where
OpCode::Literal8 => {
let byte = self.read_byte();
// TODO cloning is very slow - especially if the literal is a string!
self.stack.push(self.literals[byte as usize].clone())
self.stack.push(self.literal(byte as usize).clone())
}
OpCode::Literal16 => {
let word = self.read_word();
self.stack.push(self.literals[word as usize].clone());
self.stack.push(self.literal(word as usize).clone());
}
OpCode::Add => {
let v2 = self.pop();
@@ -97,7 +120,7 @@ where

if v1.is_string() || v2.is_string() {
self.error_handler
.runtime_error(self.lines[self.ip - 1], "Cannot subtract strings.")
.runtime_error(self.line(), "Cannot subtract strings.")
} else if v1.is_integer() && v2.is_integer() {
self.push(ExprValue::Long(v1.into_integer() - v2.into_integer()))
} else {
@@ -110,7 +133,7 @@ where

if v1.is_string() || v2.is_string() {
self.error_handler
.runtime_error(self.lines[self.ip - 1], "Cannot multiply strings.")
.runtime_error(self.line(), "Cannot multiply strings.")
} else if v1.is_integer() && v2.is_integer() {
self.push(ExprValue::Long(v1.into_integer() * v2.into_integer()))
} else {
@@ -123,7 +146,7 @@ where

if v1.is_string() || v2.is_string() {
self.error_handler
.runtime_error(self.lines[self.ip - 1], "Cannot divide strings.")
.runtime_error(self.line(), "Cannot divide strings.")
} else {
self.push(ExprValue::Double(v1.into_decimal() / v2.into_decimal()))
}
@@ -176,14 +199,14 @@ where
}
OpCode::Var => {
let var_id = self.read_byte() as usize;
self.push(self.stack[var_id].clone());
self.push(self.stack[self.offset + var_id].clone());
}
OpCode::NewVar => {
self.push(ExprValue::Single(0.0));
}
OpCode::SetVar => {
let var_id = self.read_byte() as usize;
self.stack[var_id] = self.pop();
self.stack[self.offset + var_id] = self.pop();
}
OpCode::Equal => {
let v1 = self.pop();
@@ -277,11 +300,50 @@ where
Err(x) => self.error_handler.error(self.line(), x),
}
}
OpCode::Call => {
// Check for stack overflow
// TODO is this too high?
if self.call_stack.len() > 50000 {
self.error_handler
.runtime_error(self.line(), "Stack overflow.");
return;
}

let old_chunk = self.cur_chunk;
self.cur_chunk = self.read_byte() as usize + 1; // Switch to function chunk
self.call_stack
.push(CallFrame::new(old_chunk, self.ip, self.offset));
self.offset = self.stack.len() - self.arity();
self.ip = 0; // Reset IP
}
OpCode::Ret => {
if self.call_stack.is_empty() {
// We're in the core chunk. Exit gracefully
return;
}
let return_value = self.stack.pop().unwrap();
// Pop off function variables
while self.stack.len() > self.offset {
self.stack.pop().unwrap();
}
// Push actual return value
self.push(return_value);

// Restore cur chunk
let new_frame = self.call_stack.pop().unwrap();
self.ip = new_frame.ip;
self.cur_chunk = new_frame.chunk_id;
self.offset = new_frame.offset;
}
OpCode::Nil => {
// TODO a nil type is probably a good idea
self.push(ExprValue::Single(0.0));
}
}
}

fn read_byte(&mut self) -> u8 {
let byte = self.instructions[self.ip];
let byte = self.instructions()[self.ip];
self.ip += 1;
byte
}
@@ -314,6 +376,18 @@ where
}

fn line(&self) -> usize {
self.lines[self.ip - 1]
self.chunks[self.cur_chunk].lines[self.ip - 1]
}

fn instructions(&self) -> &Vec<u8> {
&self.chunks[self.cur_chunk].instructions
}

fn literal(&self, i: usize) -> &ExprValue {
&self.chunks[self.cur_chunk].literals[i]
}

fn arity(&self) -> usize {
self.chunks[self.cur_chunk].arity
}
}

+ 15
- 5
src/xbasic.rs View File

@@ -2,6 +2,7 @@ use crate::basic_io::BasicIO;
use crate::compiler::Compiler;
use crate::error_handler::ErrorHandler;
use crate::parser::Parser;
use crate::resolver::Resolver;
use crate::scanner::Scanner;
use crate::tokens::TokenType;
use crate::vm::VirtualMachine;
@@ -38,21 +39,30 @@ where
for token in &tokens {
if token.token_type != TokenType::Newline && token.token_type != TokenType::Eof {
only_whitespace = false;
break;
}
}
if only_whitespace {
return Ok(());
}

let mut parser = Parser::new(tokens, &mut self.error_handler);
let stmts = match parser.parse() {
let function_definitions = {
let mut resolver = Resolver::new(tokens.clone(), &mut self.error_handler);
match resolver.resolve() {
Some(x) => x,
None => return Err(()),
}
};

let mut parser = Parser::new(tokens, function_definitions, &mut self.error_handler);
let (stmts, funcs) = match parser.parse() {
Some(x) => x,
None => return Err(()),
};

match self.compiler.compile(stmts, &mut self.error_handler) {
Some(chunk) => {
self.vm.run(chunk, &mut self.error_handler);
match self.compiler.compile(stmts, funcs, &mut self.error_handler) {
Some((chunk, func_chunks)) => {
self.vm.run(chunk, func_chunks, &mut self.error_handler);
}
None => return Err(()),
}


+ 255
- 0
tests/functions.rs View File

@@ -0,0 +1,255 @@
use xbasic::xbasic::XBasic;

mod common;

#[test]
fn function_empty() {
common::test_program(
"
a()

function a()
end function
",
"",
)
}

#[test]
fn function_uncalled() {
common::test_program(
"
function a()
print \"Hello world\"
end function
",
"",
)
}

#[test]
fn function_no_return() {
common::test_program(
"
a()

function a()
print \"hi from function\"
end function
",
"hi from function\n",
)
}

#[test]
fn function_parameter() {
common::test_program(
"
a(\"world\", \"hello\")

function a(b, c)
print c b
end function
",
"hello world\n",
)
}

#[test]
fn function_variable_same_name() {
common::test_program(
"
a = 3
print a()
print a

function a()
return 3 + 4
end function
",
"7\n3\n",
)
}

#[test]
fn function_return() {
common::test_program(
"
print a()

function a()
return 3 + 4
end function
",
"7\n",
)
}

#[test]
fn recursion_ok() {
common::test_program(
"
print fib(20)

function fib(n)
if n = 1 or n = 2 then
return 1
end if
return fib(n - 1) + fib(n - 2)
end function
",
"6765\n",
)
}

#[test]
fn recursion_stack_overflow() {
let mut tio = common::TestIO::new("");
let mut xb = XBasic::new(&mut tio);
assert!(xb
.run(
"
function a()
a()
end function

a()
"
)
.is_err());
assert!(xb.error_handler.had_errors);
assert_eq!(
xb.error_handler.errors,
["[line 3] Error : Stack overflow."]
);
tio.check();
}

#[test]
fn function_does_not_exist() {
let mut tio = common::TestIO::new("");
let mut xb = XBasic::new(&mut tio);
assert!(xb
.run(
"
a()
"
)
.is_err());
assert!(xb.error_handler.had_errors);
assert_eq!(
xb.error_handler.errors,
["[line 2] Error at 'a': Not a function."]
);
tio.check();
}

#[test]
fn function_already_defined() {
let mut tio = common::TestIO::new("");
let mut xb = XBasic::new(&mut tio);
assert!(xb
.run(
"
function a()
end function

function a()
end function
"
)
.is_err());
assert!(xb.error_handler.had_errors);
assert_eq!(
xb.error_handler.errors,
["[line 5] Error at 'a': Function is already defined on line 2."]
);
tio.check();
}

#[test]
fn function_mismatched_arity() {
let mut tio = common::TestIO::new("");
let mut xb = XBasic::new(&mut tio);
assert!(xb
.run(
"
a(1, 2, 3, 4)
function a(b, c, d)
end function
"
)
.is_err());
assert!(xb.error_handler.had_errors);
assert_eq!(
xb.error_handler.errors,
["[line 2] Error at 'a': Expected 3 arguments, got 4."]
);
tio.check();
}

#[test]
fn function_named_same_as_param() {
common::test_program(
"
print a(3)

function a(a)
return a + 4
end function
",
"7\n",
)
}

#[test]
fn nested_functions() {
common::test_program(
"
print a(3)

function a(a)
return b(a) + 2
end function

function b(c)
return c + 5
end function
",
"10\n",
)
}

#[test]
fn stack_cool_after_function() {
common::test_program(
"
c = 4
print a(3)

b = 3
print b
print c
print b + c

function a(a)
return b(a) + 2
end function

function b(c)
return c + 5
end function
",
"10\n3\n4\n7\n",
)
}

#[test]
fn return_in_main() {
common::test_program(
"
return
",
"",
)
}

+ 28
- 0
tests/misc.rs View File

@@ -12,3 +12,31 @@ fn empty_program() {
", "",
);
}

#[test]
fn prefix_newlines() {
common::test_program(
"




a = 2
print a
",
"2\n",
);
}

#[test]
fn infix_newlines() {
common::test_program(
"a = 2



print a
",
"2\n",
);
}

Loading…
Cancel
Save