nek-lang/src/interpreter.rs
2022-02-11 01:01:31 +01:00

507 lines
17 KiB
Rust

use std::{cell::RefCell, rc::Rc};
use thiserror::Error;
use crate::{
ast::{Ast, BinOpType, BlockScope, Expression, FunDecl, If, Statement, UnOpType},
astoptimizer::{AstOptimizer, SimpleAstOptimizer},
lexer::lex,
nice_panic,
parser::parse,
stringstore::{Sid, StringStore},
};
#[derive(Debug, Error)]
pub enum RuntimeError {
#[error("Invalid array 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,
#[error("Invalid number of arguments for function {0}. Expected {1}, got {2}")]
InvalidNumberOfArgs(String, usize, usize),
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum Value {
I64(i64),
String(Sid),
Array(Rc<RefCell<Vec<Value>>>),
Void,
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum BlockExit {
Normal,
Break,
Continue,
Return(Value),
}
#[derive(Default)]
pub struct Interpreter {
pub optimize_ast: bool,
pub print_tokens: bool,
pub print_ast: bool,
pub capture_output: bool,
output: Vec<Value>,
// Variable table stores the runtime values of variables
vartable: Vec<Value>,
funtable: Vec<FunDecl>,
stringstore: StringStore,
}
impl Interpreter {
pub fn new() -> Self {
Self {
optimize_ast: true,
..Self::default()
}
}
pub fn output(&self) -> &[Value] {
&self.output
}
fn get_var(&self, idx: usize) -> Option<Value> {
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)
}
pub fn run_str(&mut self, code: &str) {
let tokens = match lex(code) {
Ok(tokens) => tokens,
Err(e) => nice_panic!("Lexing error: {}", e),
};
if self.print_tokens {
println!("Tokens: {:?}", tokens);
}
let ast = match parse(tokens) {
Ok(ast) => ast,
Err(e) => nice_panic!("Parsing error: {}", e),
};
match self.run_ast(ast) {
Ok(_) => (),
Err(e) => nice_panic!("Runtime error: {}", e),
}
}
pub fn run_ast(&mut self, mut ast: Ast) -> Result<(), RuntimeError> {
if self.optimize_ast {
ast = SimpleAstOptimizer::optimize(ast);
}
if self.print_ast {
println!("{:#?}", ast.main);
}
self.stringstore = ast.stringstore;
self.run_block(&ast.main)?;
Ok(())
}
pub fn run_block(&mut self, prog: &BlockScope) -> Result<BlockExit, RuntimeError> {
self.run_block_fp_offset(prog, 0)
}
pub fn run_block_fp_offset(
&mut self,
prog: &BlockScope,
framepointer_offset: usize,
) -> Result<BlockExit, RuntimeError> {
let framepointer = self.vartable.len() - framepointer_offset;
for stmt in prog {
match stmt {
Statement::Break => return Ok(BlockExit::Break),
Statement::Continue => return Ok(BlockExit::Continue),
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)?;
}
Statement::Declaration(decl) => {
let rhs = self.resolve_expr(&decl.rhs)?;
self.vartable.push(rhs);
}
Statement::Block(block) => match self.run_block(block)? {
// Propagate return, continue and break
be @ (BlockExit::Return(_) | BlockExit::Continue | BlockExit::Break) => {
self.vartable.truncate(framepointer);
return Ok(be);
}
_ => (),
},
Statement::Loop(looop) => {
// loop runs as long condition != 0
loop {
if let Some(condition) = &looop.condition {
if matches!(self.resolve_expr(condition)?, Value::I64(0)) {
break;
}
}
let be = self.run_block(&looop.body)?;
match be {
// Propagate return
be @ BlockExit::Return(_) => {
self.vartable.truncate(framepointer);
return Ok(be);
}
BlockExit::Break => break,
BlockExit::Continue | BlockExit::Normal => (),
}
if let Some(adv) = &looop.advancement {
self.resolve_expr(&adv)?;
}
}
}
Statement::Print(expr) => {
let result = self.resolve_expr(expr)?;
if self.capture_output {
self.output.push(result)
} else {
print!("{}", self.value_to_string(&result));
}
}
Statement::If(If {
condition,
body_true,
body_false,
}) => {
let exit = if matches!(self.resolve_expr(condition)?, Value::I64(0)) {
self.run_block(body_false)?
} else {
self.run_block(body_true)?
};
match exit {
// Propagate return, continue and break
be @ (BlockExit::Return(_) | BlockExit::Continue | BlockExit::Break) => {
self.vartable.truncate(framepointer);
return Ok(be);
}
_ => (),
}
}
Statement::FunDeclare(fundec) => {
self.funtable.push(fundec.clone());
}
}
}
self.vartable.truncate(framepointer);
Ok(BlockExit::Normal)
}
fn resolve_expr(&mut self, expr: &Expression) -> Result<Value, RuntimeError> {
let val = match expr {
Expression::I64(val) => Value::I64(*val),
Expression::ArrayLiteral(size) => {
let size = match self.resolve_expr(size)? {
Value::I64(size) if !size.is_negative() => size,
val => return Err(RuntimeError::InvalidArrayIndex(val)),
};
Value::Array(Rc::new(RefCell::new(vec![Value::I64(0); size as usize])))
}
Expression::String(text) => Value::String(text.clone()),
Expression::BinOp(bo, lhs, rhs) => self.resolve_binop(bo, lhs, rhs)?,
Expression::UnOp(uo, operand) => self.resolve_unop(uo, operand)?,
Expression::Var(name, idx) => self.resolve_var(*name, *idx)?,
Expression::ArrayAccess(name, idx, arr_idx) => {
self.resolve_array_access(*name, *idx, arr_idx)?
}
Expression::FunCall(fun_name, fun_stackpos, args) => {
let args_len = args.len();
// All of the arg expressions must be resolved before pushing the vars on the stack,
// otherwise the stack positions are incorrect while resolving
let args = args
.iter()
.map(|arg| self.resolve_expr(arg))
.collect::<Vec<_>>();
for arg in args {
self.vartable.push(arg?);
}
// Function existance has been verified in the parser, so unwrap here shouldn't fail
let expected_num_args = self.funtable.get(*fun_stackpos).unwrap().argnames.len();
if expected_num_args != args_len {
let fun_name = self
.stringstore
.lookup(*fun_name)
.cloned()
.unwrap_or("<unknown>".to_string());
return Err(RuntimeError::InvalidNumberOfArgs(
fun_name,
expected_num_args,
args_len,
));
}
match self.run_block_fp_offset(
&Rc::clone(&self.funtable.get(*fun_stackpos).unwrap().body),
expected_num_args,
)? {
BlockExit::Normal | BlockExit::Continue | BlockExit::Break => Value::Void,
BlockExit::Return(val) => val,
}
}
};
Ok(val)
}
fn resolve_array_access(
&mut self,
name: Sid,
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) {
Some(val) => val,
None => {
return Err(RuntimeError::VarUsedNotDeclared(
self.stringstore
.lookup(name)
.cloned()
.unwrap_or_else(|| "<unknown>".to_string()),
))
}
};
let arr = match val {
Value::Array(arr) => arr,
_ => {
return Err(RuntimeError::TryingToIndexNonArray(
self.stringstore
.lookup(name)
.cloned()
.unwrap_or_else(|| "<unknown>".to_string()),
))
}
};
let arr = arr.borrow_mut();
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) -> Result<Value, RuntimeError> {
match self.get_var(idx) {
Some(val) => Ok(val),
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) -> Result<Value, RuntimeError> {
let operand = self.resolve_expr(operand)?;
Ok(match (operand, uo) {
(Value::I64(val), UnOpType::Negate) => Value::I64(-val),
(Value::I64(val), UnOpType::BNot) => Value::I64(!val),
(Value::I64(val), UnOpType::LNot) => Value::I64(if val == 0 { 1 } else { 0 }),
(val, _) => return Err(RuntimeError::UnOpInvalidType(val)),
})
}
fn resolve_binop(
&mut self,
bo: &BinOpType,
lhs: &Expression,
rhs: &Expression,
) -> Result<Value, RuntimeError> {
let rhs = self.resolve_expr(rhs)?;
match (&bo, &lhs) {
(BinOpType::Assign, Expression::Var(name, idx)) => {
match self.get_var_mut(*idx) {
Some(val) => *val = rhs.clone(),
None => {
return Err(RuntimeError::VarUsedNotDeclared(
self.stringstore
.lookup(*name)
.cloned()
.unwrap_or_else(|| "<unknown>".to_string()),
))
}
}
return Ok(rhs);
}
(BinOpType::Assign, Expression::ArrayAccess(name, idx, arr_idx)) => {
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_mut(*idx) {
Some(val) => val,
None => {
return Err(RuntimeError::VarUsedNotDeclared(
self.stringstore
.lookup(*name)
.cloned()
.unwrap_or_else(|| "<unknown>".to_string()),
))
}
};
match val {
Value::Array(arr) => arr.borrow_mut()[arr_idx as usize] = rhs.clone(),
_ => {
return Err(RuntimeError::TryingToIndexNonArray(
self.stringstore
.lookup(*name)
.cloned()
.unwrap_or_else(|| "<unknown>".to_string()),
))
}
}
return Ok(rhs);
}
_ => (),
}
let lhs = self.resolve_expr(lhs)?;
let result = match (lhs, rhs) {
(Value::I64(lhs), Value::I64(rhs)) => match bo {
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::BOr => Value::I64(lhs | rhs),
BinOpType::BAnd => Value::I64(lhs & rhs),
BinOpType::BXor => Value::I64(lhs ^ rhs),
BinOpType::LAnd => Value::I64(if (lhs != 0) && (rhs != 0) { 1 } else { 0 }),
BinOpType::LOr => Value::I64(if (lhs != 0) || (rhs != 0) { 1 } else { 0 }),
BinOpType::Shr => Value::I64(lhs >> rhs),
BinOpType::Shl => Value::I64(lhs << rhs),
BinOpType::EquEqu => Value::I64(if lhs == rhs { 1 } else { 0 }),
BinOpType::NotEqu => Value::I64(if lhs != rhs { 1 } else { 0 }),
BinOpType::Less => Value::I64(if lhs < rhs { 1 } else { 0 }),
BinOpType::LessEqu => Value::I64(if lhs <= rhs { 1 } else { 0 }),
BinOpType::Greater => Value::I64(if lhs > rhs { 1 } else { 0 }),
BinOpType::GreaterEqu => Value::I64(if lhs >= rhs { 1 } else { 0 }),
BinOpType::Assign => unreachable!(),
},
(lhs, rhs) => return Err(RuntimeError::BinOpIncompatibleTypes(lhs, rhs)),
};
Ok(result)
}
fn value_to_string(&self, val: &Value) -> String {
match val {
Value::I64(val) => format!("{}", val),
Value::Array(val) => format!("{:?}", val.borrow()),
Value::String(text) => format!(
"{}",
self.stringstore
.lookup(*text)
.unwrap_or(&"<invalid string>".to_string())
),
Value::Void => format!("void"),
}
}
}
#[cfg(test)]
mod test {
use super::{Interpreter, Value};
use crate::ast::{BinOpType, Expression};
#[test]
fn test_interpreter_expr() {
// Expression: 1 + 2 * 3 + 4
// With precedence: (1 + (2 * 3)) + 4
let ast = Expression::BinOp(
BinOpType::Add,
Expression::BinOp(
BinOpType::Add,
Expression::I64(1).into(),
Expression::BinOp(
BinOpType::Mul,
Expression::I64(2).into(),
Expression::I64(3).into(),
)
.into(),
)
.into(),
Expression::I64(4).into(),
);
let expected = Value::I64(11);
let mut interpreter = Interpreter::new();
let actual = interpreter.resolve_expr(&ast).unwrap();
assert_eq!(expected, actual);
}
}