Add bytecode vm interpreter
This commit is contained in:
parent
e28b3c4f37
commit
5eae0712bf
191
src/bytecode.rs
Normal file
191
src/bytecode.rs
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use crate::ast::{Ast, Expr, Stmt, BinOpType};
|
||||||
|
|
||||||
|
#[repr(u32)]
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum OP {
|
||||||
|
Push,
|
||||||
|
Pop,
|
||||||
|
Load,
|
||||||
|
Store,
|
||||||
|
|
||||||
|
Add,
|
||||||
|
Subtract,
|
||||||
|
Multiply,
|
||||||
|
Divide,
|
||||||
|
Modulo,
|
||||||
|
|
||||||
|
Eq,
|
||||||
|
Neq,
|
||||||
|
|
||||||
|
Gt,
|
||||||
|
Lt,
|
||||||
|
|
||||||
|
Jump,
|
||||||
|
|
||||||
|
JumpTrue,
|
||||||
|
|
||||||
|
JumpFalse,
|
||||||
|
|
||||||
|
Value(u32),
|
||||||
|
|
||||||
|
Print,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct Compiler {
|
||||||
|
ops: Vec<OP>,
|
||||||
|
global_vars: HashMap<String, u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Compiler {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Compiler::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn compile(&mut self, ast: &Ast) {
|
||||||
|
for stmt in &ast.prog {
|
||||||
|
match stmt {
|
||||||
|
Stmt::Expr(expr) => {
|
||||||
|
self.compile_expr(expr);
|
||||||
|
self.ops.push(OP::Pop);
|
||||||
|
}
|
||||||
|
Stmt::Let(name, rhs) => {
|
||||||
|
let id = self.global_vars.len() as u64;
|
||||||
|
self.global_vars.insert(name.clone(), id);
|
||||||
|
|
||||||
|
self.compile_expr(rhs);
|
||||||
|
self.gen_store(id);
|
||||||
|
}
|
||||||
|
Stmt::While(cond, body) => {
|
||||||
|
let idx_start = self.ops.len();
|
||||||
|
self.compile_expr(cond);
|
||||||
|
|
||||||
|
self.ops.push(OP::JumpFalse);
|
||||||
|
let idx_jmp = self.ops.len();
|
||||||
|
self.gen_i64(0);
|
||||||
|
|
||||||
|
self.compile(body);
|
||||||
|
|
||||||
|
self.ops.push(OP::Jump);
|
||||||
|
self.gen_i64(idx_start as i64);
|
||||||
|
|
||||||
|
self.overwrite_i64(idx_jmp, self.ops.len() as i64);
|
||||||
|
}
|
||||||
|
Stmt::For(_, _, _, _) => todo!(),
|
||||||
|
Stmt::If(cond, if_block, else_block) => {
|
||||||
|
self.compile_expr(cond);
|
||||||
|
|
||||||
|
self.ops.push(OP::JumpFalse);
|
||||||
|
let idx_if = self.ops.len();
|
||||||
|
self.gen_i64(0);
|
||||||
|
|
||||||
|
self.compile(if_block);
|
||||||
|
|
||||||
|
self.ops.push(OP::Jump);
|
||||||
|
let idx_else = self.ops.len();
|
||||||
|
self.gen_i64(0);
|
||||||
|
|
||||||
|
self.overwrite_i64(idx_if, self.ops.len() as i64);
|
||||||
|
|
||||||
|
self.compile(else_block);
|
||||||
|
|
||||||
|
self.overwrite_i64(idx_else, self.ops.len() as i64);
|
||||||
|
|
||||||
|
},
|
||||||
|
Stmt::DbgPrint(_) => todo!(),
|
||||||
|
Stmt::Print(expr) => {
|
||||||
|
self.compile_expr(expr);
|
||||||
|
self.ops.push(OP::Print);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_ops(self) -> Vec<OP> {
|
||||||
|
self.ops
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn compile_expr(&mut self, expr: &Expr) {
|
||||||
|
match expr {
|
||||||
|
Expr::I64(val) => {
|
||||||
|
self.ops.push(OP::Push);
|
||||||
|
self.gen_i64(*val)
|
||||||
|
}
|
||||||
|
Expr::Ident(name) => {
|
||||||
|
match self.global_vars.get(name).copied() {
|
||||||
|
Some(addr) => self.gen_load(addr),
|
||||||
|
None => panic!("Variable '{}' used before declaration", name),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Expr::BinOp(bo, lhs, rhs) => self.compile_binop(bo, lhs, rhs),
|
||||||
|
Expr::UnOp(_, _) => todo!(),
|
||||||
|
Expr::Str(_) => todo!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compile_binop(&mut self, bo: &BinOpType, lhs: &Expr, rhs: &Expr) {
|
||||||
|
|
||||||
|
if matches!(bo, BinOpType::Assign) {
|
||||||
|
self.compile_expr(rhs);
|
||||||
|
|
||||||
|
if let Expr::Ident(name) = lhs {
|
||||||
|
let addr = *self.global_vars.get(name).expect("Trying to assign var before decl");
|
||||||
|
self.gen_store(addr);
|
||||||
|
} else {
|
||||||
|
panic!("Trying to assign value to rvalue");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.compile_expr(lhs);
|
||||||
|
self.compile_expr(rhs);
|
||||||
|
|
||||||
|
match bo {
|
||||||
|
BinOpType::Add => self.ops.push(OP::Add),
|
||||||
|
BinOpType::Sub => self.ops.push(OP::Subtract),
|
||||||
|
BinOpType::Mul => self.ops.push(OP::Multiply),
|
||||||
|
BinOpType::Div => self.ops.push(OP::Divide),
|
||||||
|
BinOpType::Mod => self.ops.push(OP::Modulo),
|
||||||
|
BinOpType::BOr => todo!(),
|
||||||
|
BinOpType::BAnd => todo!(),
|
||||||
|
BinOpType::BXor => todo!(),
|
||||||
|
BinOpType::Shl => todo!(),
|
||||||
|
BinOpType::Shr => todo!(),
|
||||||
|
BinOpType::Equ => self.ops.push(OP::Eq),
|
||||||
|
BinOpType::Neq => self.ops.push(OP::Neq),
|
||||||
|
BinOpType::Gt => self.ops.push(OP::Gt),
|
||||||
|
BinOpType::Ge => todo!(),
|
||||||
|
BinOpType::Lt => self.ops.push(OP::Lt),
|
||||||
|
BinOpType::Le => todo!(),
|
||||||
|
BinOpType::Assign => todo!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_i64(&mut self, val: i64) {
|
||||||
|
self.ops.push(OP::Value((val & u32::MAX as i64) as u32));
|
||||||
|
self.ops.push(OP::Value((val >> 32) as u32));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn overwrite_i64(&mut self, idx: usize, val: i64) {
|
||||||
|
self.ops[idx] = OP::Value((val & u32::MAX as i64) as u32);
|
||||||
|
self.ops[idx+1] = OP::Value((val >> 32) as u32);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_load(&mut self, addr: u64) {
|
||||||
|
self.ops.push(OP::Load);
|
||||||
|
self.gen_i64(addr as i64)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_store(&mut self, addr: u64) {
|
||||||
|
self.ops.push(OP::Store);
|
||||||
|
self.gen_i64(addr as i64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn compile(ast: &Ast) -> Vec<OP> {
|
||||||
|
let mut compiler = Compiler::new();
|
||||||
|
compiler.compile(ast);
|
||||||
|
compiler.into_ops()
|
||||||
|
}
|
||||||
@ -3,3 +3,5 @@ pub mod parser;
|
|||||||
pub mod interpreter;
|
pub mod interpreter;
|
||||||
pub mod token;
|
pub mod token;
|
||||||
pub mod ast;
|
pub mod ast;
|
||||||
|
pub mod bytecode;
|
||||||
|
pub mod vm;
|
||||||
|
|||||||
15
src/main.rs
15
src/main.rs
@ -1,6 +1,6 @@
|
|||||||
use std::{env::args, io::Write};
|
use std::{env::args, io::Write};
|
||||||
|
|
||||||
use nek_lang::interpreter::Interpreter;
|
use nek_lang::{interpreter::Interpreter, lexer::lex, parser::parse, bytecode::compile, vm::Vm};
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
struct CliConfig {
|
struct CliConfig {
|
||||||
@ -27,7 +27,18 @@ fn main() {
|
|||||||
|
|
||||||
if let Some(file) = &cfg.file {
|
if let Some(file) = &cfg.file {
|
||||||
let code = std::fs::read_to_string(file).expect(&format!("File not found: '{}'", file));
|
let code = std::fs::read_to_string(file).expect(&format!("File not found: '{}'", file));
|
||||||
interpreter.run_text(&code, cfg.print_tokens, cfg.print_ast);
|
let tokens = lex(&code);
|
||||||
|
let ast = parse(tokens);
|
||||||
|
|
||||||
|
let prog = compile(&ast);
|
||||||
|
|
||||||
|
// println!("{:?}", prog);
|
||||||
|
|
||||||
|
let mut vm = Vm::new(prog);
|
||||||
|
|
||||||
|
vm.run();
|
||||||
|
|
||||||
|
// interpreter.run_text(&code, cfg.print_tokens, cfg.print_ast);
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.interactive || cfg.file.is_none() {
|
if cfg.interactive || cfg.file.is_none() {
|
||||||
|
|||||||
153
src/vm.rs
Normal file
153
src/vm.rs
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
use crate::{bytecode::OP, interpreter::Value};
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct Vm {
|
||||||
|
prog: Vec<OP>,
|
||||||
|
ip: usize,
|
||||||
|
stack: Vec<Value>,
|
||||||
|
|
||||||
|
/// This isn't actually a heap. It's actually still more of a f*cked up stack
|
||||||
|
heap: Vec<Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Vm {
|
||||||
|
pub fn new(prog: Vec<OP>) -> Self {
|
||||||
|
Self {
|
||||||
|
prog,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(&mut self) {
|
||||||
|
while let Some(op) = self.prog.get(self.ip).copied() {
|
||||||
|
self.ip += 1;
|
||||||
|
|
||||||
|
match op {
|
||||||
|
OP::Push => {
|
||||||
|
let val = self.read_i64();
|
||||||
|
self.stack.push(Value::I64(val));
|
||||||
|
}
|
||||||
|
OP::Pop => {
|
||||||
|
self.stack.pop();
|
||||||
|
}
|
||||||
|
OP::Load => {
|
||||||
|
let addr = self.read_i64() as usize;
|
||||||
|
|
||||||
|
if let Some(val) = self.heap.get(addr) {
|
||||||
|
self.stack.push(val.clone());
|
||||||
|
} else {
|
||||||
|
panic!("Trying to load from uninitialized heap");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
OP::Store => {
|
||||||
|
let val = self
|
||||||
|
.stack
|
||||||
|
.pop()
|
||||||
|
.expect("Trying to pop value from stack for storing");
|
||||||
|
let addr = self.read_i64() as usize;
|
||||||
|
if self.heap.len() == addr {
|
||||||
|
self.heap.push(val);
|
||||||
|
} else {
|
||||||
|
self.heap[addr] = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
OP::Print => {
|
||||||
|
let val = self
|
||||||
|
.stack
|
||||||
|
.pop()
|
||||||
|
.expect("Trying to pop value from stack for printing");
|
||||||
|
print!("{}", val);
|
||||||
|
}
|
||||||
|
|
||||||
|
OP::Add => {
|
||||||
|
let vals = self.pop2_i64();
|
||||||
|
self.stack.push(Value::I64(vals.0 + vals.1))
|
||||||
|
}
|
||||||
|
OP::Subtract => {
|
||||||
|
let vals = self.pop2_i64();
|
||||||
|
self.stack.push(Value::I64(vals.0 - vals.1))
|
||||||
|
}
|
||||||
|
OP::Multiply => {
|
||||||
|
let vals = self.pop2_i64();
|
||||||
|
self.stack.push(Value::I64(vals.0 * vals.1))
|
||||||
|
}
|
||||||
|
OP::Divide => {
|
||||||
|
let vals = self.pop2_i64();
|
||||||
|
self.stack.push(Value::I64(vals.0 / vals.1))
|
||||||
|
}
|
||||||
|
OP::Modulo => {
|
||||||
|
let vals = self.pop2_i64();
|
||||||
|
self.stack.push(Value::I64(vals.0 % vals.1))
|
||||||
|
}
|
||||||
|
OP::Eq => {
|
||||||
|
let vals = self.pop2_i64();
|
||||||
|
self.stack
|
||||||
|
.push(Value::I64(if vals.0 == vals.1 { 1 } else { 0 }))
|
||||||
|
}
|
||||||
|
OP::Neq => {
|
||||||
|
let vals = self.pop2_i64();
|
||||||
|
self.stack
|
||||||
|
.push(Value::I64(if vals.0 != vals.1 { 1 } else { 0 }))
|
||||||
|
}
|
||||||
|
OP::Gt => {
|
||||||
|
let vals = self.pop2_i64();
|
||||||
|
self.stack
|
||||||
|
.push(Value::I64(if vals.0 > vals.1 { 1 } else { 0 }))
|
||||||
|
}
|
||||||
|
OP::Lt => {
|
||||||
|
let vals = self.pop2_i64();
|
||||||
|
self.stack
|
||||||
|
.push(Value::I64(if vals.0 < vals.1 { 1 } else { 0 }))
|
||||||
|
}
|
||||||
|
OP::Value(_) => {
|
||||||
|
panic!("This is not an instruction, but data. This should never be evaluated")
|
||||||
|
}
|
||||||
|
OP::Jump => {
|
||||||
|
self.ip = self.read_i64() as usize;
|
||||||
|
}
|
||||||
|
OP::JumpTrue => {
|
||||||
|
let jmp_target = self.read_i64() as usize;
|
||||||
|
if !matches!(self.stack.pop(), Some(Value::I64(0))) {
|
||||||
|
self.ip = jmp_target;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
OP::JumpFalse => {
|
||||||
|
let jmp_target = self.read_i64() as usize;
|
||||||
|
if matches!(self.stack.pop(), Some(Value::I64(0))) {
|
||||||
|
self.ip = jmp_target;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pop2_i64(&mut self) -> (i64, i64) {
|
||||||
|
let rhs = self.stack.pop();
|
||||||
|
let lhs = self.stack.pop();
|
||||||
|
match (lhs, rhs) {
|
||||||
|
(Some(Value::I64(lhs)), Some(Value::I64(rhs))) => (lhs, rhs),
|
||||||
|
_ => panic!("Invalid data for add"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_i64(&mut self) -> i64 {
|
||||||
|
let mut val = if let Some(OP::Value(val)) = self.prog.get(self.ip).copied() {
|
||||||
|
val
|
||||||
|
} else {
|
||||||
|
panic!("Expected Value as next OP")
|
||||||
|
} as i64;
|
||||||
|
|
||||||
|
self.ip += 1;
|
||||||
|
|
||||||
|
val |= (if let Some(OP::Value(val)) = self.prog.get(self.ip).copied() {
|
||||||
|
val
|
||||||
|
} else {
|
||||||
|
panic!("Expected Value as next OP")
|
||||||
|
} as i64)
|
||||||
|
<< 32;
|
||||||
|
|
||||||
|
self.ip += 1;
|
||||||
|
|
||||||
|
val
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user