Remove panics from interpreter, worse performance

- Replaced the interpreters panics with actual errors and results
- Added a few extra checks for arrays and div-by-zero
- These changes significantly reduced runtime performance, even without
  the extra checks
This commit is contained in:
Daniel M 2022-02-09 18:18:21 +01:00
parent 421fbbc873
commit f0c2bd8dde

View File

@ -1,11 +1,32 @@
use std::cell::RefCell; use std::cell::RefCell;
use thiserror::Error;
use crate::{ use crate::{
ast::{BlockScope, BinOpType, Expression, If, Statement, UnOpType, Ast}, ast::{Ast, BinOpType, BlockScope, Expression, If, Statement, UnOpType},
astoptimizer::{AstOptimizer, SimpleAstOptimizer},
lexer::lex, lexer::lex,
parser::parse, stringstore::{Sid, StringStore}, astoptimizer::{SimpleAstOptimizer, AstOptimizer}, parser::parse,
stringstore::{Sid, StringStore},
}; };
#[derive(Debug, Error)]
pub enum RuntimeError {
#[error("Invalid error Index: {}", 0.to_string())]
InvalidArrayIndex(Value),
#[error("Variable used but not declared: {0}")]
VarUsedNotDeclared(String),
#[error("Can't index into non-array variable: {0}")]
TryingToIndexNonArray(String),
#[error("Invalid value type for unary operation: {}", 0.to_string())]
UnOpInvalidType(Value),
#[error("Incompatible binary operations. Operands don't match: {} {}", 0.to_string(), 1.to_string())]
BinOpIncompatibleTypes(Value, Value),
#[error("Array access out of bounds: Accessed {0}, size is {1}")]
ArrayOutOfBounds(usize, usize),
#[error("Division by zero")]
DivideByZero,
}
#[derive(Debug, PartialEq, Eq, Clone)] #[derive(Debug, PartialEq, Eq, Clone)]
pub enum Value { pub enum Value {
I64(i64), I64(i64),
@ -23,7 +44,6 @@ pub struct Interpreter {
pub capture_output: bool, pub capture_output: bool,
output: Vec<Value>, output: Vec<Value>,
// Variable table stores the runtime values of variables // Variable table stores the runtime values of variables
vartable: Vec<Value>, vartable: Vec<Value>,
@ -32,7 +52,10 @@ pub struct Interpreter {
impl Interpreter { impl Interpreter {
pub fn new() -> Self { pub fn new() -> Self {
Self { optimize_ast: true, ..Self::default() } Self {
optimize_ast: true,
..Self::default()
}
} }
pub fn output(&self) -> &[Value] { pub fn output(&self) -> &[Value] {
@ -55,63 +78,63 @@ impl Interpreter {
let ast = parse(tokens).unwrap(); let ast = parse(tokens).unwrap();
self.run_ast(ast); self.run_ast(ast).unwrap();
} }
pub fn run_ast(&mut self, mut ast: Ast) { pub fn run_ast(&mut self, mut ast: Ast) -> Result<(), RuntimeError> {
if self.optimize_ast { if self.optimize_ast {
ast = SimpleAstOptimizer::optimize(ast); ast = SimpleAstOptimizer::optimize(ast);
} }
if self.print_ast { if self.print_ast {
println!("{:#?}", ast.main); println!("{:#?}", ast.main);
} }
self.stringstore = ast.stringstore; self.stringstore = ast.stringstore;
self.run_block(&ast.main); self.run_block(&ast.main)
} }
pub fn run_block(&mut self, prog: &BlockScope) { pub fn run_block(&mut self, prog: &BlockScope) -> Result<(), RuntimeError> {
let framepointer = self.vartable.len(); let framepointer = self.vartable.len();
for stmt in prog { for stmt in prog {
match stmt { match stmt {
Statement::Expr(expr) => { Statement::Expr(expr) => {
self.resolve_expr(expr); self.resolve_expr(expr)?;
} }
Statement::Declaration(_sid, _idx, rhs) => { Statement::Declaration(_sid, _idx, rhs) => {
let rhs = self.resolve_expr(rhs); let rhs = self.resolve_expr(rhs)?;
self.vartable.push(rhs); self.vartable.push(rhs);
} }
Statement::Block(block) => { Statement::Block(block) => {
self.run_block(block); self.run_block(block)?;
} }
Statement::Loop(looop) => { Statement::Loop(looop) => {
// loop runs as long condition != 0 // loop runs as long condition != 0
loop { loop {
if matches!(self.resolve_expr(&looop.condition), Value::I64(0)) { if matches!(self.resolve_expr(&looop.condition)?, Value::I64(0)) {
break; break;
} }
self.run_block(&looop.body); self.run_block(&looop.body)?;
if let Some(adv) = &looop.advancement { if let Some(adv) = &looop.advancement {
self.resolve_expr(&adv); self.resolve_expr(&adv)?;
} }
} }
} }
Statement::Print(expr) => { Statement::Print(expr) => {
let result = self.resolve_expr(expr); let result = self.resolve_expr(expr)?;
if self.capture_output { if self.capture_output {
self.output.push(result) self.output.push(result)
} else { } else {
self.print_value(&result); print!("{}", self.value_to_string(&result));
} }
} }
@ -120,115 +143,175 @@ impl Interpreter {
body_true, body_true,
body_false, body_false,
}) => { }) => {
if matches!(self.resolve_expr(condition), Value::I64(0)) { if matches!(self.resolve_expr(condition)?, Value::I64(0)) {
self.run_block(body_false); self.run_block(body_false)?;
} else { } else {
self.run_block(body_true); self.run_block(body_true)?;
} }
} }
} }
} }
self.vartable.truncate(framepointer); self.vartable.truncate(framepointer);
Ok(())
} }
fn resolve_expr(&mut self, expr: &Expression) -> Value { fn resolve_expr(&mut self, expr: &Expression) -> Result<Value, RuntimeError> {
match expr { let val = match expr {
Expression::I64(val) => Value::I64(*val), Expression::I64(val) => Value::I64(*val),
Expression::ArrayLiteral(size) => { Expression::ArrayLiteral(size) => {
let size = match self.resolve_expr(size) { let size = match self.resolve_expr(size)? {
Value::I64(size) => size, Value::I64(size) if !size.is_negative() => size,
_ => panic!("Array size needs to be I64"), val => return Err(RuntimeError::InvalidArrayIndex(val)),
}; };
Value::Array(RefCell::new(vec![Value::I64(0); size as usize])) Value::Array(RefCell::new(vec![Value::I64(0); size as usize]))
} }
Expression::String(text) => Value::String(text.clone()), Expression::String(text) => Value::String(text.clone()),
Expression::BinOp(bo, lhs, rhs) => self.resolve_binop(bo, lhs, rhs), Expression::BinOp(bo, lhs, rhs) => self.resolve_binop(bo, lhs, rhs)?,
Expression::UnOp(uo, operand) => self.resolve_unop(uo, operand), Expression::UnOp(uo, operand) => self.resolve_unop(uo, operand)?,
Expression::Var(name, idx) => self.resolve_var(*name, *idx), Expression::Var(name, idx) => self.resolve_var(*name, *idx)?,
Expression::ArrayAccess(name, idx, arr_idx) => self.resolve_array_access(*name, *idx, arr_idx), Expression::ArrayAccess(name, idx, arr_idx) => {
} self.resolve_array_access(*name, *idx, arr_idx)?
}
};
Ok(val)
} }
fn resolve_array_access(&mut self, name: Sid, idx: usize, arr_idx: &Expression) -> Value { fn resolve_array_access(
let arr_idx = match self.resolve_expr(arr_idx) { &mut self,
Value::I64(size) => size, name: Sid,
_ => panic!("Array index needs to be I64"), idx: usize,
arr_idx: &Expression,
) -> Result<Value, RuntimeError> {
let arr_idx = match self.resolve_expr(arr_idx)? {
Value::I64(size) if !size.is_negative() => size,
val => return Err(RuntimeError::InvalidArrayIndex(val)),
}; };
let val = match self.get_var(idx) { let val = match self.get_var(idx) {
Some(val) => val, Some(val) => val,
None => panic!("Variable '{}' used but not declared", self.stringstore.lookup(name).unwrap()), None => {
return Err(RuntimeError::VarUsedNotDeclared(
self.stringstore
.lookup(name)
.cloned()
.unwrap_or_else(|| "<unknown>".to_string()),
))
}
}; };
let arr = match val { let arr = match val {
Value::Array(arr) => arr, Value::Array(arr) => arr,
_ => panic!("Variable '{}' used but not declared", self.stringstore.lookup(name).unwrap()), _ => {
return Err(RuntimeError::TryingToIndexNonArray(
self.stringstore
.lookup(name)
.cloned()
.unwrap_or_else(|| "<unknown>".to_string()),
))
}
}; };
let arr = arr.borrow_mut(); let arr = arr.borrow_mut();
arr.get(arr_idx as usize).cloned().expect("Runtime error: Invalid array index") arr.get(arr_idx as usize)
.cloned()
.ok_or(RuntimeError::ArrayOutOfBounds(arr_idx as usize, arr.len()))
} }
fn resolve_var(&mut self, name: Sid, idx: usize) -> Value { fn resolve_var(&mut self, name: Sid, idx: usize) -> Result<Value, RuntimeError> {
match self.get_var(idx) { match self.get_var(idx) {
Some(val) => val, Some(val) => Ok(val),
None => panic!("Variable '{}' used but not declared", self.stringstore.lookup(name).unwrap()), None => {
return Err(RuntimeError::VarUsedNotDeclared(
self.stringstore
.lookup(name)
.cloned()
.unwrap_or_else(|| "<unknown>".to_string()),
))
}
} }
} }
fn resolve_unop(&mut self, uo: &UnOpType, operand: &Expression) -> Value { fn resolve_unop(&mut self, uo: &UnOpType, operand: &Expression) -> Result<Value, RuntimeError> {
let operand = self.resolve_expr(operand); let operand = self.resolve_expr(operand)?;
match (operand, uo) { Ok(match (operand, uo) {
(Value::I64(val), UnOpType::Negate) => Value::I64(-val), (Value::I64(val), UnOpType::Negate) => Value::I64(-val),
(Value::I64(val), UnOpType::BNot) => Value::I64(!val), (Value::I64(val), UnOpType::BNot) => Value::I64(!val),
(Value::I64(val), UnOpType::LNot) => Value::I64(if val == 0 { 1 } else { 0 }), (Value::I64(val), UnOpType::LNot) => Value::I64(if val == 0 { 1 } else { 0 }),
_ => panic!("Value type is not compatible with unary operation"), (val, _) => return Err(RuntimeError::UnOpInvalidType(val)),
} })
} }
fn resolve_binop(&mut self, bo: &BinOpType, lhs: &Expression, rhs: &Expression) -> Value { fn resolve_binop(
let rhs = self.resolve_expr(rhs); &mut self,
bo: &BinOpType,
lhs: &Expression,
rhs: &Expression,
) -> Result<Value, RuntimeError> {
let rhs = self.resolve_expr(rhs)?;
match (&bo, &lhs) { match (&bo, &lhs) {
(BinOpType::Assign, Expression::Var(name, idx)) => { (BinOpType::Assign, Expression::Var(name, idx)) => {
match self.get_var_mut(*idx) { match self.get_var_mut(*idx) {
Some(val) => *val = rhs.clone(), Some(val) => *val = rhs.clone(),
None => panic!("Runtime Error: Trying to assign value to undeclared variable: {:?}", self.stringstore.lookup(*name)), None => {
return Err(RuntimeError::VarUsedNotDeclared(
self.stringstore
.lookup(*name)
.cloned()
.unwrap_or_else(|| "<unknown>".to_string()),
))
}
} }
return rhs; return Ok(rhs);
} }
(BinOpType::Assign, Expression::ArrayAccess(name, idx, arr_idx)) => { (BinOpType::Assign, Expression::ArrayAccess(name, idx, arr_idx)) => {
let arr_idx = match self.resolve_expr(arr_idx) { let arr_idx = match self.resolve_expr(arr_idx)? {
Value::I64(size) => size, Value::I64(size) if !size.is_negative() => size,
_ => panic!("Array index needs to be I64"), val => return Err(RuntimeError::InvalidArrayIndex(val)),
}; };
let val = match self.get_var_mut(*idx) { let val = match self.get_var_mut(*idx) {
Some(val) => val, Some(val) => val,
None => panic!("Runtime Error: Trying to assign value to undeclared variable: {:?}", self.stringstore.lookup(*name)), None => {
return Err(RuntimeError::VarUsedNotDeclared(
self.stringstore
.lookup(*name)
.cloned()
.unwrap_or_else(|| "<unknown>".to_string()),
))
}
}; };
match val { match val {
Value::Array(arr) => arr.borrow_mut()[arr_idx as usize] = rhs.clone(), Value::Array(arr) => arr.borrow_mut()[arr_idx as usize] = rhs.clone(),
_ => panic!("Variable '{}' used but not declared", self.stringstore.lookup(*name).unwrap()), _ => {
return Err(RuntimeError::TryingToIndexNonArray(
self.stringstore
.lookup(*name)
.cloned()
.unwrap_or_else(|| "<unknown>".to_string()),
))
}
} }
return rhs; return Ok(rhs);
} }
_ => (), _ => (),
} }
let lhs = self.resolve_expr(lhs); let lhs = self.resolve_expr(lhs)?;
match (lhs, rhs) { let result = match (lhs, rhs) {
(Value::I64(lhs), Value::I64(rhs)) => match bo { (Value::I64(lhs), Value::I64(rhs)) => match bo {
BinOpType::Add => Value::I64(lhs + rhs), BinOpType::Add => Value::I64(lhs + rhs),
BinOpType::Mul => Value::I64(lhs * rhs), BinOpType::Mul => Value::I64(lhs * rhs),
BinOpType::Sub => Value::I64(lhs - rhs), BinOpType::Sub => Value::I64(lhs - rhs),
BinOpType::Div => Value::I64(lhs / rhs), BinOpType::Div => Value::I64(lhs.checked_div(rhs).ok_or(RuntimeError::DivideByZero)?),
BinOpType::Mod => Value::I64(lhs % rhs), BinOpType::Mod => Value::I64(lhs.checked_rem(rhs).ok_or(RuntimeError::DivideByZero)?),
BinOpType::BOr => Value::I64(lhs | rhs), BinOpType::BOr => Value::I64(lhs | rhs),
BinOpType::BAnd => Value::I64(lhs & rhs), BinOpType::BAnd => Value::I64(lhs & rhs),
BinOpType::BXor => Value::I64(lhs ^ rhs), BinOpType::BXor => Value::I64(lhs ^ rhs),
@ -245,18 +328,19 @@ impl Interpreter {
BinOpType::Assign => unreachable!(), BinOpType::Assign => unreachable!(),
}, },
_ => panic!("Value types are not compatible"), (lhs, rhs) => return Err(RuntimeError::BinOpIncompatibleTypes(lhs, rhs)),
} };
Ok(result)
} }
fn print_value(&self, val: &Value) { fn value_to_string(&self, val: &Value) -> String {
match val { match val {
Value::I64(val) => print!("{}", val), Value::I64(val) => format!("{}", val),
Value::Array(val) => print!("{:?}", val.borrow()), Value::Array(val) => format!("{:?}", val.borrow()),
Value::String(text) => print!("{}", self.stringstore.lookup(*text).unwrap()), Value::String(text) => format!("{}", self.stringstore.lookup(*text).unwrap()),
} }
} }
} }
#[cfg(test)] #[cfg(test)]
@ -287,7 +371,7 @@ mod test {
let expected = Value::I64(11); let expected = Value::I64(11);
let mut interpreter = Interpreter::new(); let mut interpreter = Interpreter::new();
let actual = interpreter.resolve_expr(&ast); let actual = interpreter.resolve_expr(&ast).unwrap();
assert_eq!(expected, actual); assert_eq!(expected, actual);
} }