mirror of
https://github.com/Noratrieb/dilaria.git
synced 2026-01-14 17:35:03 +01:00
start bytecode compilation
This commit is contained in:
parent
b35d12e041
commit
c6765d7da6
14 changed files with 342 additions and 46 deletions
|
|
@ -2,13 +2,11 @@
|
|||
//! The AST module contains all structs and enums for the abstract syntax tree generated by the parser
|
||||
|
||||
use crate::errors::Span;
|
||||
use crate::value::Symbol;
|
||||
|
||||
/// imagine interning or something here
|
||||
pub type Symbol = String;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct Ident {
|
||||
pub name: Symbol,
|
||||
pub sym: Symbol,
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +0,0 @@
|
|||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
enum Value {
|
||||
Null,
|
||||
Bool(bool),
|
||||
Number(f64),
|
||||
String(Rc<String>),
|
||||
Object(Rc<HashMap<String, Value>>),
|
||||
Array(Rc<Vec<Value>>),
|
||||
Fn(Rc<()>),
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
#![allow(dead_code)]
|
||||
|
||||
mod mem;
|
||||
|
||||
use crate::ast::Program;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Vm {}
|
||||
|
||||
fn execute(_program: Program) {}
|
||||
45
src/bytecode.rs
Normal file
45
src/bytecode.rs
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
use crate::value::{HashMap, Symbol};
|
||||
use std::rc::Rc;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct FnBlock {
|
||||
pub code: Vec<Instr>,
|
||||
pub stack_sizes: Vec<usize>,
|
||||
pub arity: u8,
|
||||
}
|
||||
|
||||
// todo: this should be copy in the end tbh
|
||||
#[derive(Debug)]
|
||||
pub enum Instr {
|
||||
/// Store the current value on the stack to the stack location with the local offset `usize`
|
||||
Store(usize),
|
||||
/// Load the variable value from the local offset `usize` onto the stack
|
||||
Load(usize),
|
||||
/// Push a value onto the stack
|
||||
PushVal(Box<Value>),
|
||||
/// Negate the top value on the stack. Only works with numbers and booleans
|
||||
Neg,
|
||||
BinAdd,
|
||||
BinSub,
|
||||
BinMul,
|
||||
BinDiv,
|
||||
BinMod,
|
||||
BinAnd,
|
||||
BinOr,
|
||||
CmpGreater,
|
||||
CmpGreaterEq,
|
||||
CmpLess,
|
||||
CmpLessEq,
|
||||
CmpEq,
|
||||
CmpNotEq,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Value {
|
||||
Null,
|
||||
Bool(bool),
|
||||
Num(f64),
|
||||
String(Rc<str>),
|
||||
Array(Vec<Value>),
|
||||
Object(HashMap<Symbol, Value>),
|
||||
}
|
||||
238
src/compile.rs
Normal file
238
src/compile.rs
Normal file
|
|
@ -0,0 +1,238 @@
|
|||
use crate::ast::{
|
||||
Assignment, BinaryOp, BinaryOpKind, Block, Call, Declaration, Expr, FnDecl, Ident, IfStmt,
|
||||
Literal, Program, Stmt, UnaryOp, WhileStmt,
|
||||
};
|
||||
use crate::bytecode::{FnBlock, Instr, Value};
|
||||
use crate::errors::{CompilerError, Span};
|
||||
use crate::value::HashMap;
|
||||
|
||||
type CResult<T> = Result<T, CompileError>;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct Compiler {
|
||||
blocks: Vec<FnBlock>,
|
||||
current_block: usize,
|
||||
/// the current local variables that are in scope, only needed for compiling
|
||||
locals: Vec<HashMap<Ident, usize>>,
|
||||
}
|
||||
|
||||
pub fn compile(ast: &Program) -> Result<Vec<FnBlock>, CompileError> {
|
||||
let mut compiler = Compiler::default();
|
||||
|
||||
compiler.compile(ast)?;
|
||||
|
||||
Ok(compiler.blocks)
|
||||
}
|
||||
|
||||
impl Compiler {
|
||||
fn compile(&mut self, ast: &Program) -> CResult<()> {
|
||||
let global_block = FnBlock::default();
|
||||
self.blocks.push(global_block);
|
||||
self.current_block = self.blocks.len() - 1;
|
||||
self.locals.push(HashMap::default());
|
||||
self.compile_fn_block(&ast.0)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn compile_fn_block(&mut self, stmts: &[Stmt]) -> CResult<()> {
|
||||
for stmt in stmts {
|
||||
match stmt {
|
||||
Stmt::Declaration(inner) => self.compile_declaration(inner),
|
||||
Stmt::Assignment(inner) => self.compile_assignment(inner),
|
||||
Stmt::FnDecl(inner) => self.compile_fn_decl(inner),
|
||||
Stmt::If(inner) => self.compile_if(inner),
|
||||
Stmt::Loop(block, span) => self.compile_loop(block, *span),
|
||||
Stmt::While(inner) => self.compile_while(inner),
|
||||
Stmt::Break(span) => self.compile_break(*span),
|
||||
Stmt::Return(expr, span) => self.compile_return(expr, *span),
|
||||
Stmt::Block(inner) => self.compile_block(inner),
|
||||
Stmt::Expr(inner) => self.compile_expr(inner),
|
||||
}?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn compile_declaration(&mut self, declaration: &Declaration) -> CResult<()> {
|
||||
// Compile the expression, the result of the expression will be the last thing left on the stack
|
||||
self.compile_expr(&declaration.init)?;
|
||||
// Now just remember that the value at this stack location is this variable name
|
||||
let stack_pos = self.current_stack_top();
|
||||
self.locals().insert(declaration.name.clone(), stack_pos);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn compile_assignment(&mut self, assignment: &Assignment) -> CResult<()> {
|
||||
let local = match &assignment.lhs {
|
||||
Expr::Ident(ident) => ident,
|
||||
_ => todo!(),
|
||||
};
|
||||
|
||||
let stack_pos = self.lookup_local(local)?;
|
||||
|
||||
self.compile_expr(&assignment.rhs)?;
|
||||
|
||||
self.push_instr(Instr::Store(stack_pos), StackChange::Shrink);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn compile_fn_decl(&mut self, _: &FnDecl) -> CResult<()> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn compile_if(&mut self, _: &IfStmt) -> CResult<()> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn compile_loop(&mut self, _: &Block, _: Span) -> CResult<()> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn compile_while(&mut self, _: &WhileStmt) -> CResult<()> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn compile_break(&mut self, _: Span) -> CResult<()> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn compile_return(&mut self, _: &Option<Expr>, _: Span) -> CResult<()> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn compile_block(&mut self, _: &Block) -> CResult<()> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn compile_expr(&mut self, expr: &Expr) -> CResult<()> {
|
||||
match expr {
|
||||
Expr::Ident(inner) => self.compile_expr_ident(inner),
|
||||
Expr::Literal(inner) => self.compile_expr_literal(inner),
|
||||
Expr::UnaryOp(inner) => self.compile_expr_unary(inner),
|
||||
Expr::BinaryOp(inner) => self.compile_expr_binary(inner),
|
||||
Expr::Call(inner) => self.compile_expr_call(inner),
|
||||
}
|
||||
}
|
||||
|
||||
fn compile_expr_ident(&mut self, name: &Ident) -> CResult<()> {
|
||||
let offset = self.lookup_local(name)?;
|
||||
self.push_instr(Instr::Load(offset), StackChange::Grow);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn compile_expr_literal(&mut self, lit: &Literal) -> CResult<()> {
|
||||
let value = match lit {
|
||||
Literal::String(str, _) => Value::String(str.clone().into()),
|
||||
Literal::Number(num, _) => Value::Num(*num),
|
||||
Literal::Array(vec, _) => {
|
||||
if vec.is_empty() {
|
||||
Value::Array(Vec::new())
|
||||
} else {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
Literal::Object(_) => Value::Object(HashMap::default()),
|
||||
Literal::Boolean(bool, _) => Value::Bool(*bool),
|
||||
Literal::Null(_) => Value::Null,
|
||||
};
|
||||
|
||||
self.push_instr(Instr::PushVal(Box::new(value)), StackChange::Grow);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn compile_expr_unary(&mut self, inner: &UnaryOp) -> CResult<()> {
|
||||
self.compile_expr(&inner.expr)?;
|
||||
|
||||
// not and neg compile to the same instruction
|
||||
self.push_instr(Instr::Neg, StackChange::None);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn compile_expr_binary(&mut self, inner: &BinaryOp) -> CResult<()> {
|
||||
// todo: is this the correct ordering?
|
||||
self.compile_expr(&inner.lhs)?;
|
||||
self.compile_expr(&inner.rhs)?;
|
||||
|
||||
let instruction = match inner.kind {
|
||||
BinaryOpKind::Add => Instr::BinAdd,
|
||||
BinaryOpKind::And => Instr::BinAnd,
|
||||
BinaryOpKind::Or => Instr::BinOr,
|
||||
BinaryOpKind::Equal => Instr::CmpEq,
|
||||
BinaryOpKind::GreaterEqual => Instr::CmpGreaterEq,
|
||||
BinaryOpKind::Greater => Instr::CmpGreater,
|
||||
BinaryOpKind::LessEqual => Instr::CmpLessEq,
|
||||
BinaryOpKind::Less => Instr::CmpLess,
|
||||
BinaryOpKind::NotEqual => Instr::CmpNotEq,
|
||||
BinaryOpKind::Sub => Instr::BinSub,
|
||||
BinaryOpKind::Mul => Instr::BinMul,
|
||||
BinaryOpKind::Div => Instr::BinDiv,
|
||||
BinaryOpKind::Mod => Instr::BinMod,
|
||||
};
|
||||
|
||||
self.push_instr(instruction, StackChange::Shrink);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn compile_expr_call(&mut self, _: &Call) -> CResult<()> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn locals(&mut self) -> &mut HashMap<Ident, usize> {
|
||||
self.locals.last_mut().expect("no locals found")
|
||||
}
|
||||
|
||||
fn lookup_local(&self, name: &Ident) -> CResult<usize> {
|
||||
for locals in self.locals.iter().rev() {
|
||||
if let Some(&position) = locals.get(name) {
|
||||
return Ok(position);
|
||||
}
|
||||
}
|
||||
|
||||
Err(CompileError)
|
||||
}
|
||||
|
||||
fn current_stack_top(&self) -> usize {
|
||||
let block = &self.blocks[self.current_block];
|
||||
*block.stack_sizes.last().expect("empty stack")
|
||||
}
|
||||
|
||||
fn push_instr(&mut self, instr: Instr, stack_change: StackChange) {
|
||||
let block = &mut self.blocks[self.current_block];
|
||||
let stack_top = block.stack_sizes.last().copied().unwrap_or(0);
|
||||
let new_stack_top = stack_top as isize + stack_change as isize;
|
||||
assert!(new_stack_top >= 0, "instruction popped stack below 0");
|
||||
let new_stack_top = new_stack_top as usize;
|
||||
|
||||
block.code.push(instr);
|
||||
block.stack_sizes.push(new_stack_top);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[repr(i8)]
|
||||
enum StackChange {
|
||||
Shrink = -1,
|
||||
None = 0,
|
||||
Grow = 1,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CompileError;
|
||||
|
||||
impl CompilerError for CompileError {
|
||||
fn span(&self) -> Span {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn message(&self) -> String {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn note(&self) -> Option<String> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
19
src/lib.rs
19
src/lib.rs
|
|
@ -1,8 +1,12 @@
|
|||
#![deny(clippy::disallowed_type)]
|
||||
|
||||
mod ast;
|
||||
mod bird;
|
||||
mod bytecode;
|
||||
mod compile;
|
||||
mod errors;
|
||||
mod lex;
|
||||
mod parse;
|
||||
mod value;
|
||||
|
||||
pub use lex::*;
|
||||
pub use parse::*;
|
||||
|
|
@ -15,14 +19,23 @@ pub fn run_program(program: &str) {
|
|||
let tokens = success.into_iter().collect::<Result<Vec<_>, _>>().unwrap();
|
||||
|
||||
println!(
|
||||
"{:?}",
|
||||
"Tokens:\n{:?}\n",
|
||||
tokens.iter().map(|token| &token.kind).collect::<Vec<_>>()
|
||||
);
|
||||
|
||||
let ast = parse::parse(tokens);
|
||||
|
||||
match ast {
|
||||
Ok(ast) => println!("{:?}", ast),
|
||||
Ok(ast) => {
|
||||
println!("AST:\n{:?}\n", ast);
|
||||
|
||||
let bytecode = compile::compile(&ast);
|
||||
|
||||
match bytecode {
|
||||
Ok(code) => println!("Bytecode:\n{:#?}\n", code),
|
||||
Err(err) => errors::display_error(program, err),
|
||||
}
|
||||
}
|
||||
Err(err) => errors::display_error(program, err),
|
||||
}
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -495,7 +495,7 @@ impl<'code> Parser<'code> {
|
|||
TokenType::Ident(name) => {
|
||||
let name_owned = name.to_owned();
|
||||
Ok(Expr::Ident(Ident {
|
||||
name: name_owned,
|
||||
sym: name_owned,
|
||||
span: next.span,
|
||||
}))
|
||||
}
|
||||
|
|
@ -513,7 +513,7 @@ impl<'code> Parser<'code> {
|
|||
TokenType::Ident(name) => {
|
||||
let name_owned = name.to_owned();
|
||||
Ok(Ident {
|
||||
name: name_owned,
|
||||
sym: name_owned,
|
||||
span,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ fn num_lit(number: f64) -> Expr {
|
|||
|
||||
fn ident(name: &str) -> Ident {
|
||||
Ident {
|
||||
name: name.to_string(),
|
||||
sym: name.to_string(),
|
||||
span: Default::default(),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
9
src/value.rs
Normal file
9
src/value.rs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
/// imagine interning or something here
|
||||
pub type Symbol = String;
|
||||
|
||||
#[cfg(not(feature = "fxhash"))]
|
||||
#[allow(clippy::disallowed_type)]
|
||||
pub type HashMap<K, V> = std::collections::HashMap<K, V>;
|
||||
|
||||
#[cfg(feature = "fxhash")]
|
||||
pub type HashMap<K, V> = rustc_hash::FxHashMap<K, V>;
|
||||
Loading…
Add table
Add a link
Reference in a new issue