diff --git a/.rustfmt.toml b/.rustfmt.toml new file mode 100644 index 0000000..4d7dd9e --- /dev/null +++ b/.rustfmt.toml @@ -0,0 +1,3 @@ +imports_granularity = "Crate" +newline_style = "Unix" +group_imports = "StdExternalCrate" \ No newline at end of file diff --git a/src/ast.rs b/src/ast.rs index c2a75c4..b1b534e 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -3,8 +3,7 @@ //! //! All AST nodes are bump allocated into the lifetime `'ast` -use crate::errors::Span; -use crate::gc::Symbol; +use crate::{errors::Span, gc::Symbol}; #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] #[cfg_attr(feature = "_debug", derive(dbg_pls::DebugPls))] diff --git a/src/bytecode.rs b/src/bytecode.rs index e47fde2..2e6f1a3 100644 --- a/src/bytecode.rs +++ b/src/bytecode.rs @@ -58,11 +58,12 @@ //! //! ``` -use crate::errors::Span; -use crate::vm::Value; -use bumpalo::collections::Vec; use std::fmt::{Debug, Formatter}; +use bumpalo::collections::Vec; + +use crate::{errors::Span, vm::Value}; + /// This struct contains all data for a function. pub struct FnBlock<'bc> { /// The bytecode of the function @@ -129,9 +130,12 @@ pub enum Instr { /// Calls the function at the top of the stack, after the parameters Call, - + /// Returns from the function, removing that stack frame Return, + /// Stop the program + Exit, + /// Shrinks the stack by `usize` elements, should always be emitted before backwards jumps ShrinkStack(usize), } diff --git a/src/compile.rs b/src/compile.rs index a49aed0..943d2a5 100644 --- a/src/compile.rs +++ b/src/compile.rs @@ -1,18 +1,20 @@ //! The compiler that compiles the AST down to bytecode -use crate::ast::{ - Assignment, BinaryOp, BinaryOpKind, Block, Call, CallKind, Declaration, ElsePart, Expr, FnDecl, - Ident, IfStmt, Literal, Program, Stmt, UnaryOp, WhileStmt, +use std::{cell::RefCell, rc::Rc}; + +use bumpalo::{collections::Vec, Bump}; + +use crate::{ + ast::{ + Assignment, BinaryOp, BinaryOpKind, Block, Call, CallKind, Declaration, ElsePart, Expr, + FnDecl, Ident, IfStmt, Literal, Program, Stmt, UnaryOp, WhileStmt, + }, + bytecode::{FnBlock, Instr}, + errors::{CompilerError, Span}, + gc::Symbol, + vm::Value, + HashMap, RtAlloc, }; -use crate::bytecode::{FnBlock, Instr}; -use crate::errors::{CompilerError, Span}; -use crate::gc::Symbol; -use crate::vm::Value; -use crate::{HashMap, RtAlloc}; -use bumpalo::collections::Vec; -use bumpalo::Bump; -use std::cell::RefCell; -use std::rc::Rc; type CResult = Result; @@ -119,8 +121,8 @@ impl<'bc, 'gc> Compiler<'bc, 'gc> { StackChange::Grow, Span::dummy(), ); - // exit the program. - self.push_instr(Instr::Return, StackChange::None, Span::dummy()); + // exit the program. here, we use `exit` instead of `return` because there is no stack frame + self.push_instr(Instr::Exit, StackChange::None, Span::dummy()); Ok(()) } @@ -128,7 +130,7 @@ impl<'bc, 'gc> Compiler<'bc, 'gc> { // padding for backwards jumps self.push_instr(Instr::Nop, StackChange::None, block.span); - self.compile_stmts(&block.stmts) + self.compile_stmts(block.stmts) } fn compile_stmts(&mut self, stmts: &[Stmt]) -> CResult { @@ -222,7 +224,7 @@ impl<'bc, 'gc> Compiler<'bc, 'gc> { .push(decl.params.len() + CALLCONV_OFFSET_DATA); } - self.compile_stmts(&decl.body.stmts)?; + self.compile_stmts(decl.body.stmts)?; self.push_instr(Instr::PushVal(Value::Null), StackChange::Grow, decl.span); self.push_instr(Instr::Return, StackChange::None, decl.span); @@ -380,7 +382,7 @@ impl<'bc, 'gc> Compiler<'bc, 'gc> { let next_env = Env::new_inner(self.env.clone(), OuterEnvKind::Block); self.env = next_env; - self.compile_stmts(&block.stmts)?; + self.compile_stmts(block.stmts)?; let outer = self.env.borrow().outer.clone().expect("outer env got lost"); self.env = outer; diff --git a/src/gc.rs b/src/gc.rs index b3b8cdd..38284ba 100644 --- a/src/gc.rs +++ b/src/gc.rs @@ -2,14 +2,17 @@ //! //! The structure of the GC might change, but for now it's simply a `LinkedList` of `Object`s. -use crate::vm::Value; -use crate::{HashMap, HashSet}; +use std::{ + collections::LinkedList, + fmt::{Debug, Formatter}, + hash::{Hash, Hasher}, + ops::Deref, + ptr::NonNull, +}; + use dbg_pls::DebugPls; -use std::collections::LinkedList; -use std::fmt::{Debug, Formatter}; -use std::hash::{Hash, Hasher}; -use std::ops::Deref; -use std::ptr::NonNull; + +use crate::{vm::Value, HashMap, HashSet}; /// A pointer to a garbage collected value. This pointer *must* always be valid, and a value /// is only allowed to be freed once no Gc is pointing at it anymore. This is achieved through diff --git a/src/lex.rs b/src/lex.rs index 04e205c..3a66144 100644 --- a/src/lex.rs +++ b/src/lex.rs @@ -4,11 +4,13 @@ //! For error handling, there is a single `Error` token, which contains the error. The lexer //! is an iterator, and can therefore be used without any allocations -use crate::errors::{CompilerError, Span}; -use crate::gc::Symbol; -use crate::RtAlloc; -use std::iter::Peekable; -use std::str::CharIndices; +use std::{iter::Peekable, str::CharIndices}; + +use crate::{ + errors::{CompilerError, Span}, + gc::Symbol, + RtAlloc, +}; /// /// A single token generated from the lexer @@ -360,8 +362,7 @@ fn is_valid_ident_start(char: char) -> bool { #[cfg(test)] mod test { - use crate::lex::Lexer; - use crate::RtAlloc; + use crate::{lex::Lexer, RtAlloc}; type StdString = std::string::String; diff --git a/src/lib.rs b/src/lib.rs index 8b29407..acce4f4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -#![deny(clippy::disallowed_type)] +#![deny(clippy::disallowed_types)] mod ast; mod bytecode; @@ -10,14 +10,14 @@ mod parse; mod util; mod vm; -use crate::ast::Program; -use crate::gc::RtAlloc; use std::io::Write; pub use bumpalo::Bump; pub use lex::*; pub use parse::*; +use crate::{ast::Program, gc::RtAlloc}; + #[cfg(not(feature = "fxhash"))] #[allow(clippy::disallowed_types)] type HashMap = std::collections::HashMap; @@ -73,8 +73,8 @@ fn process_ast(program: &str, ast: &Program, mut runtime: RtAlloc, cfg: &mut Con } let result = vm::execute(code, runtime, cfg); - if let Err(result) = result { - eprintln!("error: {}", result); + if let Err(msg) = result { + eprintln!("error: {msg}"); } } Err(err) => errors::display_error(program, err), diff --git a/src/main.rs b/src/main.rs index dfe7190..c782988 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ -use dilaria::Config; use std::io; +use dilaria::Config; + fn main() { let mut args = std::env::args(); diff --git a/src/parse.rs b/src/parse.rs index 49d46da..5e1209a 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -6,13 +6,16 @@ #[cfg(test)] mod test; -use crate::ast::*; -use crate::errors::{CompilerError, Span}; -use crate::lex::{Token, TokenKind}; -use bumpalo::collections::Vec; -use bumpalo::Bump; use std::iter::Peekable; +use bumpalo::{collections::Vec, Bump}; + +use crate::{ + ast::*, + errors::{CompilerError, Span}, + lex::{Token, TokenKind}, +}; + #[derive(Debug)] struct Parser<'ast, I> where diff --git a/src/parse/test.rs b/src/parse/test.rs index be1fcee..a1b90c8 100644 --- a/src/parse/test.rs +++ b/src/parse/test.rs @@ -3,16 +3,17 @@ //! These tests are horrible and break all the time. Never do it like this again. //! That said it's too late to fix it. -use crate::errors::Span; -use crate::parse::Parser; -use crate::RtAlloc; use bumpalo::Bump; use prelude::*; +use crate::{errors::Span, parse::Parser, RtAlloc}; + mod prelude { pub(super) use super::{parser, rt, test_literal_bin_op, test_number_literal, token}; - pub(super) use crate::ast::{Expr, Stmt}; - pub(super) use crate::lex::TokenKind::*; + pub(super) use crate::{ + ast::{Expr, Stmt}, + lex::TokenKind::*, + }; pub type Token = crate::lex::Token; pub type TokenType = crate::lex::TokenKind; pub(super) use bumpalo::Bump; @@ -58,9 +59,10 @@ fn test_number_literal, &Bump) -> Expr>(parser: F) { } mod assignment { + use bumpalo::Bump; + use super::prelude::*; use crate::parse::test::rt; - use bumpalo::Bump; fn parse_assignment(tokens: Vec, alloc: &Bump) -> Stmt { let mut parser = parser(tokens, alloc); diff --git a/src/util.rs b/src/util.rs index d6767b3..242bd16 100644 --- a/src/util.rs +++ b/src/util.rs @@ -6,9 +6,10 @@ macro_rules! assert_size { }; } -pub(crate) use assert_size; use std::fmt::Display; +pub(crate) use assert_size; + #[cfg(feature = "_debug")] pub fn dbg(prefix: impl Display, x: impl dbg_pls::DebugPls) { eprintln!("{prefix}{}", dbg_pls::color(&x)) diff --git a/src/vm.rs b/src/vm.rs index 395b4c6..0f8252d 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -1,21 +1,36 @@ -use crate::bytecode::{FnBlock, Function, Instr}; -use crate::gc::{Object, RtAlloc, Symbol}; -use crate::util; -use crate::Config; -use std::fmt::{Debug, Display, Formatter}; -use std::io::{Read, Write}; +use std::{ + fmt::{Debug, Display, Formatter}, + io::{Read, Write}, +}; + +use crate::{ + bytecode::{FnBlock, Function, Instr}, + gc::{Object, RtAlloc, Symbol}, + util, Config, +}; + +type ActualBackingVmError = &'static str; + +type VmError = Box; + +#[derive(Debug)] +enum VmErrorInner { + Exit, + Error(ActualBackingVmError), +} -type VmError = Box<&'static str>; type VmResult = Result<(), VmError>; // never get bigger than a machine word. util::assert_size!(VmResult <= std::mem::size_of::()); +type PublicVmError = ActualBackingVmError; + pub fn execute<'bc>( bytecode: &'bc [FnBlock<'bc>], alloc: RtAlloc, cfg: &mut Config, -) -> Result<(), VmError> { +) -> Result<(), PublicVmError> { let mut vm = Vm { blocks: bytecode, current: bytecode.first().ok_or("no bytecode found")?, @@ -28,7 +43,13 @@ pub fn execute<'bc>( step: cfg.step, }; - vm.execute_function() + match vm.execute_function() { + Ok(()) => Ok(()), + Err(boxed) => match *boxed { + VmErrorInner::Exit => Ok(()), + VmErrorInner::Error(err) => Err(err), + }, + } } #[derive(Debug, Clone, Copy)] @@ -86,7 +107,7 @@ impl<'bc> Vm<'bc, '_> { None => return Ok(()), } if self.pc > 0 { - //debug_assert_eq!(self.current.stack_sizes[self.pc - 1], self.stack.len()); + // debug_assert_eq!(self.current.stack_sizes[self.pc - 1], self.stack.len()); } } } @@ -109,56 +130,56 @@ impl<'bc> Vm<'bc, '_> { match val { Value::Bool(bool) => self.stack.push(Value::Bool(!bool)), Value::Num(float) => self.stack.push(Value::Num(-float)), - _ => return Err(self.type_error()), + _ => return Err(err("bad type")), } } Instr::BinAdd => self.bin_op(|lhs, rhs| match (lhs, rhs) { (Value::Num(a), Value::Num(b)) => Ok(Value::Num(a + b)), - _ => Err("bad type".into()), + _ => Err(err("bad type")), })?, Instr::BinSub => self.bin_op(|lhs, rhs| match (lhs, rhs) { (Value::Num(a), Value::Num(b)) => Ok(Value::Num(a - b)), - _ => Err("bad type".into()), + _ => Err(err("bad type")), })?, Instr::BinMul => self.bin_op(|lhs, rhs| match (lhs, rhs) { (Value::Num(a), Value::Num(b)) => Ok(Value::Num(a * b)), - _ => Err("bad type".into()), + _ => Err(err("bad type")), })?, Instr::BinDiv => self.bin_op(|lhs, rhs| match (lhs, rhs) { (Value::Num(a), Value::Num(b)) => Ok(Value::Num(a / b)), - _ => Err("bad type".into()), + _ => Err(err("bad type")), })?, Instr::BinMod => self.bin_op(|lhs, rhs| match (lhs, rhs) { (Value::Num(a), Value::Num(b)) => Ok(Value::Num(a % b)), - _ => Err("bad type".into()), + _ => Err(err("bad type")), })?, Instr::BinAnd => self.bin_op(|lhs, rhs| match (lhs, rhs) { (Value::Bool(a), Value::Bool(b)) => Ok(Value::Bool(a && b)), - _ => Err("bad type".into()), + _ => Err(err("bad type")), })?, Instr::BinOr => self.bin_op(|lhs, rhs| match (lhs, rhs) { (Value::Bool(a), Value::Bool(b)) => Ok(Value::Bool(a || b)), - _ => Err("bad type".into()), + _ => Err(err("bad type")), })?, Instr::CmpGreater => self.bin_op(|lhs, rhs| match (lhs, rhs) { (Value::Num(a), Value::Num(b)) => Ok(Value::Bool(a > b)), (Value::String(a), Value::String(b)) => Ok(Value::Bool(a.as_str() > b.as_str())), - _ => Err("bad type".into()), + _ => Err(err("bad type")), })?, Instr::CmpGreaterEq => self.bin_op(|lhs, rhs| match (lhs, rhs) { (Value::Num(a), Value::Num(b)) => Ok(Value::Bool(a >= b)), (Value::String(a), Value::String(b)) => Ok(Value::Bool(a.as_str() >= b.as_str())), - _ => Err("bad type".into()), + _ => Err(err("bad type")), })?, Instr::CmpLess => self.bin_op(|lhs, rhs| match (lhs, rhs) { (Value::Num(a), Value::Num(b)) => Ok(Value::Bool(a < b)), (Value::String(a), Value::String(b)) => Ok(Value::Bool(a.as_str() < b.as_str())), - _ => Err("bad type".into()), + _ => Err(err("bad type")), })?, Instr::CmpLessEq => self.bin_op(|lhs, rhs| match (lhs, rhs) { (Value::Num(a), Value::Num(b)) => Ok(Value::Bool(a <= b)), (Value::String(a), Value::String(b)) => Ok(Value::Bool(a.as_str() <= b.as_str())), - _ => Err("bad type".into()), + _ => Err(err("bad type")), })?, Instr::CmpEq => self.bin_op(|lhs, rhs| match (lhs, rhs) { (Value::Null, Value::Null) => Ok(TRUE), @@ -166,7 +187,7 @@ impl<'bc> Vm<'bc, '_> { (Value::String(a), Value::String(b)) => Ok(Value::Bool(a == b)), (Value::Object(_a), Value::Object(_b)) => todo!(), (Value::Array, Value::Array) => Ok(TRUE), - _ => Err("bad type".into()), + _ => Err(err("bad type")), })?, Instr::CmpNotEq => self.bin_op(|lhs, rhs| match (lhs, rhs) { (Value::Null, Value::Null) => Ok(FALSE), @@ -174,23 +195,24 @@ impl<'bc> Vm<'bc, '_> { (Value::String(a), Value::String(b)) => Ok(Value::Bool(a != b)), (Value::Object(_a), Value::Object(_b)) => todo!(), (Value::Array, Value::Array) => Ok(FALSE), - _ => Err("bad type".into()), + _ => Err(err("bad type")), })?, Instr::Print => { let val = self.stack.pop().unwrap(); - writeln!(self.stdout, "{}", val).map_err(|_| "failed to write to stdout")?; + writeln!(self.stdout, "{}", val).map_err(|_| err("failed to write to stdout"))?; } Instr::JmpFalse(pos) => { let val = self.stack.pop().unwrap(); match val { Value::Bool(false) => self.pc = (self.pc as isize + pos) as usize, Value::Bool(true) => {} - _ => return Err("bad type".into()), + _ => return Err(err("bad type")), } } Instr::Jmp(pos) => self.pc = (self.pc as isize + pos) as usize, Instr::Call => self.call()?, Instr::Return => self.ret()?, + Instr::Exit => return Err(Box::new(VmErrorInner::Exit)), Instr::ShrinkStack(size) => { assert!(self.stack.len() >= size); let new_len = self.stack.len() - size; @@ -246,6 +268,8 @@ impl<'bc> Vm<'bc, '_> { let bookkeeping_offset = self.stack_offset + current_arity; + let inner_stack_offset = self.stack_offset; + // now, we get all the bookkeeping info out let old_stack_offset = self.stack[bookkeeping_offset].unwrap_native_int(); let old_pc = self.stack[bookkeeping_offset + 1].unwrap_native_int(); @@ -259,15 +283,14 @@ impl<'bc> Vm<'bc, '_> { // and kill the function stack frame // note: don't emit a return instruction from the whole global script. - todo!(); + unsafe { self.stack.set_len(inner_stack_offset) }; + + // everything that remains... + self.stack.push(return_value); Ok(()) } - fn type_error(&self) -> VmError { - "bad type".into() - } - fn step_debug(&self, current_instr: Instr) { let curr_stack_size = self.stack.len(); // at this point, we've always incremented the pc already @@ -320,3 +343,7 @@ impl Display for Value { } } } + +fn err(msg: &'static str) -> VmError { + Box::new(VmErrorInner::Error(msg)) +}