diff --git a/src/ast.rs b/src/ast.rs index e28e787..d2dbabc 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -1,33 +1,26 @@ // This is an auto-generated file. Any changes made to this file may be overwritten. -// This file was created at: 2024-09-30 16:14:27 +// This file was created at: 2024-10-03 12:06:28 #![allow(dead_code)] use std::fmt::Debug; use std::any::Any; use crate::token::Token; -pub trait ExprVisitor { - fn visit_binary_expr(&mut self, expr: &BinaryExpr) -> Result<(), Box>; - fn visit_unary_expr(&mut self, expr: &UnaryExpr) -> Result<(), Box>; - fn visit_call_expr(&mut self, expr: &CallExpr) -> Result<(), Box>; - fn visit_get_expr(&mut self, expr: &GetExpr) -> Result<(), Box>; - fn visit_index_expr(&mut self, expr: &IndexExpr) -> Result<(), Box>; - fn visit_primary_expr(&mut self, expr: &PrimaryExpr) -> Result<(), Box>; - fn visit_function_expr(&mut self, expr: &FunctionExpr) -> Result<(), Box>; - fn visit_list_expr(&mut self, expr: &ListExpr) -> Result<(), Box>; -} +type Error = Box; -pub trait StmtVisitor { - fn visit_expr_stmt(&mut self, stmt: &ExprStmt) -> Result<(), Box>; - fn visit_assign_stmt(&mut self, stmt: &AssignStmt) -> Result<(), Box>; - fn visit_set_stmt(&mut self, stmt: &SetStmt) -> Result<(), Box>; - fn visit_block_stmt(&mut self, stmt: &BlockStmt) -> Result<(), Box>; - fn visit_return_stmt(&mut self, stmt: &ReturnStmt) -> Result<(), Box>; - fn visit_if_stmt(&mut self, stmt: &IfStmt) -> Result<(), Box>; +pub trait ExprVisitor { + fn visit_binary_expr(&mut self, expr: &BinaryExpr) -> Result<(), Error>; + fn visit_unary_expr(&mut self, expr: &UnaryExpr) -> Result<(), Error>; + fn visit_call_expr(&mut self, expr: &CallExpr) -> Result<(), Error>; + fn visit_get_expr(&mut self, expr: &GetExpr) -> Result<(), Error>; + fn visit_index_expr(&mut self, expr: &IndexExpr) -> Result<(), Error>; + fn visit_primary_expr(&mut self, expr: &PrimaryExpr) -> Result<(), Error>; + fn visit_function_expr(&mut self, expr: &FunctionExpr) -> Result<(), Error>; + fn visit_list_expr(&mut self, expr: &ListExpr) -> Result<(), Error>; } pub trait Expr: Debug + Any { - fn accept(&self, visitor: &mut dyn ExprVisitor) -> Result<(), Box>; + fn accept(&self, visitor: &mut dyn ExprVisitor) -> Result<(), Error>; fn as_any(self: Box) -> Box; fn as_any_ref(&self) -> &dyn Any; } @@ -42,17 +35,12 @@ pub struct BinaryExpr { } impl Expr for BinaryExpr { - fn accept(&self, visitor: &mut dyn ExprVisitor) -> Result<(), Box> { + fn accept(&self, visitor: &mut dyn ExprVisitor) -> Result<(), Error> { visitor.visit_binary_expr(self) } - - fn as_any(self: Box) -> Box { - self - } - - fn as_any_ref(&self) -> &dyn Any { - self - } + + fn as_any(self: Box) -> Box { self } + fn as_any_ref(&self) -> &dyn Any { self } } #[derive(Debug)] @@ -62,17 +50,12 @@ pub struct UnaryExpr { } impl Expr for UnaryExpr { - fn accept(&self, visitor: &mut dyn ExprVisitor) -> Result<(), Box> { + fn accept(&self, visitor: &mut dyn ExprVisitor) -> Result<(), Error> { visitor.visit_unary_expr(self) } - - fn as_any(self: Box) -> Box { - self - } - - fn as_any_ref(&self) -> &dyn Any { - self - } + + fn as_any(self: Box) -> Box { self } + fn as_any_ref(&self) -> &dyn Any { self } } #[derive(Debug)] @@ -83,17 +66,12 @@ pub struct CallExpr { } impl Expr for CallExpr { - fn accept(&self, visitor: &mut dyn ExprVisitor) -> Result<(), Box> { + fn accept(&self, visitor: &mut dyn ExprVisitor) -> Result<(), Error> { visitor.visit_call_expr(self) } - - fn as_any(self: Box) -> Box { - self - } - - fn as_any_ref(&self) -> &dyn Any { - self - } + + fn as_any(self: Box) -> Box { self } + fn as_any_ref(&self) -> &dyn Any { self } } #[derive(Debug)] @@ -103,17 +81,12 @@ pub struct GetExpr { } impl Expr for GetExpr { - fn accept(&self, visitor: &mut dyn ExprVisitor) -> Result<(), Box> { + fn accept(&self, visitor: &mut dyn ExprVisitor) -> Result<(), Error> { visitor.visit_get_expr(self) } - - fn as_any(self: Box) -> Box { - self - } - - fn as_any_ref(&self) -> &dyn Any { - self - } + + fn as_any(self: Box) -> Box { self } + fn as_any_ref(&self) -> &dyn Any { self } } #[derive(Debug)] @@ -124,17 +97,12 @@ pub struct IndexExpr { } impl Expr for IndexExpr { - fn accept(&self, visitor: &mut dyn ExprVisitor) -> Result<(), Box> { + fn accept(&self, visitor: &mut dyn ExprVisitor) -> Result<(), Error> { visitor.visit_index_expr(self) } - - fn as_any(self: Box) -> Box { - self - } - - fn as_any_ref(&self) -> &dyn Any { - self - } + + fn as_any(self: Box) -> Box { self } + fn as_any_ref(&self) -> &dyn Any { self } } #[derive(Debug)] @@ -143,40 +111,30 @@ pub struct PrimaryExpr { } impl Expr for PrimaryExpr { - fn accept(&self, visitor: &mut dyn ExprVisitor) -> Result<(), Box> { + fn accept(&self, visitor: &mut dyn ExprVisitor) -> Result<(), Error> { visitor.visit_primary_expr(self) } - - fn as_any(self: Box) -> Box { - self - } - - fn as_any_ref(&self) -> &dyn Any { - self - } + + fn as_any(self: Box) -> Box { self } + fn as_any_ref(&self) -> &dyn Any { self } } #[derive(Debug)] pub struct FunctionExpr { pub lparen: Token, - pub params: Vec<(Token , Option)>, + pub params: Vec<(Token, Option)>, pub return_type: Option, pub body: Vec, pub rbrace: Token, } impl Expr for FunctionExpr { - fn accept(&self, visitor: &mut dyn ExprVisitor) -> Result<(), Box> { + fn accept(&self, visitor: &mut dyn ExprVisitor) -> Result<(), Error> { visitor.visit_function_expr(self) } - - fn as_any(self: Box) -> Box { - self - } - - fn as_any_ref(&self) -> &dyn Any { - self - } + + fn as_any(self: Box) -> Box { self } + fn as_any_ref(&self) -> &dyn Any { self } } #[derive(Debug)] @@ -187,44 +145,60 @@ pub struct ListExpr { } impl Expr for ListExpr { - fn accept(&self, visitor: &mut dyn ExprVisitor) -> Result<(), Box> { + fn accept(&self, visitor: &mut dyn ExprVisitor) -> Result<(), Error> { visitor.visit_list_expr(self) } + + fn as_any(self: Box) -> Box { self } + fn as_any_ref(&self) -> &dyn Any { self } +} - fn as_any(self: Box) -> Box { - self - } - - fn as_any_ref(&self) -> &dyn Any { - self - } +pub trait StmtVisitor { + fn visit_import_stmt(&mut self, stmt: &ImportStmt) -> Result<(), Error>; + fn visit_expr_stmt(&mut self, stmt: &ExprStmt) -> Result<(), Error>; + fn visit_assign_stmt(&mut self, stmt: &AssignStmt) -> Result<(), Error>; + fn visit_set_stmt(&mut self, stmt: &SetStmt) -> Result<(), Error>; + fn visit_block_stmt(&mut self, stmt: &BlockStmt) -> Result<(), Error>; + fn visit_return_stmt(&mut self, stmt: &ReturnStmt) -> Result<(), Error>; + fn visit_if_stmt(&mut self, stmt: &IfStmt) -> Result<(), Error>; } pub trait Stmt: Debug + Any { - fn accept(&self, visitor: &mut dyn StmtVisitor) -> Result<(), Box>; + fn accept(&self, visitor: &mut dyn StmtVisitor) -> Result<(), Error>; fn as_any(self: Box) -> Box; fn as_any_ref(&self) -> &dyn Any; } pub type StmtP = Box; +#[derive(Debug)] +pub struct ImportStmt { + pub import_kw: Token, + pub what: Vec, + pub module: Token, +} + +impl Stmt for ImportStmt { + fn accept(&self, visitor: &mut dyn StmtVisitor) -> Result<(), Error> { + visitor.visit_import_stmt(self) + } + + fn as_any(self: Box) -> Box { self } + fn as_any_ref(&self) -> &dyn Any { self } +} + #[derive(Debug)] pub struct ExprStmt { pub expr: ExprP, } impl Stmt for ExprStmt { - fn accept(&self, visitor: &mut dyn StmtVisitor) -> Result<(), Box> { + fn accept(&self, visitor: &mut dyn StmtVisitor) -> Result<(), Error> { visitor.visit_expr_stmt(self) } - - fn as_any(self: Box) -> Box { - self - } - - fn as_any_ref(&self) -> &dyn Any { - self - } + + fn as_any(self: Box) -> Box { self } + fn as_any_ref(&self) -> &dyn Any { self } } #[derive(Debug)] @@ -234,17 +208,12 @@ pub struct AssignStmt { } impl Stmt for AssignStmt { - fn accept(&self, visitor: &mut dyn StmtVisitor) -> Result<(), Box> { + fn accept(&self, visitor: &mut dyn StmtVisitor) -> Result<(), Error> { visitor.visit_assign_stmt(self) } - - fn as_any(self: Box) -> Box { - self - } - - fn as_any_ref(&self) -> &dyn Any { - self - } + + fn as_any(self: Box) -> Box { self } + fn as_any_ref(&self) -> &dyn Any { self } } #[derive(Debug)] @@ -255,17 +224,12 @@ pub struct SetStmt { } impl Stmt for SetStmt { - fn accept(&self, visitor: &mut dyn StmtVisitor) -> Result<(), Box> { + fn accept(&self, visitor: &mut dyn StmtVisitor) -> Result<(), Error> { visitor.visit_set_stmt(self) } - - fn as_any(self: Box) -> Box { - self - } - - fn as_any_ref(&self) -> &dyn Any { - self - } + + fn as_any(self: Box) -> Box { self } + fn as_any_ref(&self) -> &dyn Any { self } } #[derive(Debug)] @@ -276,17 +240,12 @@ pub struct BlockStmt { } impl Stmt for BlockStmt { - fn accept(&self, visitor: &mut dyn StmtVisitor) -> Result<(), Box> { + fn accept(&self, visitor: &mut dyn StmtVisitor) -> Result<(), Error> { visitor.visit_block_stmt(self) } - - fn as_any(self: Box) -> Box { - self - } - - fn as_any_ref(&self) -> &dyn Any { - self - } + + fn as_any(self: Box) -> Box { self } + fn as_any_ref(&self) -> &dyn Any { self } } #[derive(Debug)] @@ -296,17 +255,12 @@ pub struct ReturnStmt { } impl Stmt for ReturnStmt { - fn accept(&self, visitor: &mut dyn StmtVisitor) -> Result<(), Box> { + fn accept(&self, visitor: &mut dyn StmtVisitor) -> Result<(), Error> { visitor.visit_return_stmt(self) } - - fn as_any(self: Box) -> Box { - self - } - - fn as_any_ref(&self) -> &dyn Any { - self - } + + fn as_any(self: Box) -> Box { self } + fn as_any_ref(&self) -> &dyn Any { self } } #[derive(Debug)] @@ -318,16 +272,10 @@ pub struct IfStmt { } impl Stmt for IfStmt { - fn accept(&self, visitor: &mut dyn StmtVisitor) -> Result<(), Box> { + fn accept(&self, visitor: &mut dyn StmtVisitor) -> Result<(), Error> { visitor.visit_if_stmt(self) } - - fn as_any(self: Box) -> Box { - self - } - - fn as_any_ref(&self) -> &dyn Any { - self - } -} - + + fn as_any(self: Box) -> Box { self } + fn as_any_ref(&self) -> &dyn Any { self } +} \ No newline at end of file diff --git a/src/compiler.rs b/src/compiler.rs index 303b748..744e46f 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -1,5 +1,8 @@ use std::collections::{HashMap, HashSet}; use std::fmt::{self, Display}; +use std::fs::File; +use std::io::Read; +use std::path::{Path, PathBuf}; use std::rc::Rc; use std::sync::LazyLock; @@ -9,7 +12,9 @@ use thiserror::Error; use crate::ast::*; use crate::obj::prelude::*; +use crate::obj::Ptr; use crate::obj::BUILTINS; +use crate::parser::Parser; use crate::token::TokenKind; use crate::vm::*; @@ -40,6 +45,12 @@ impl LineNumber { } impl StmtVisitor for LineNumber { + fn visit_import_stmt(&mut self, stmt: &ImportStmt) -> Result<()> { + self.update_start(stmt.import_kw.line); + self.update_end(stmt.module.line); + Ok(()) + } + fn visit_expr_stmt(&mut self, stmt: &ExprStmt) -> Result<()> { stmt.expr.accept(self).unwrap(); Ok(()) @@ -165,6 +176,25 @@ impl LocalAssignCollector { } impl StmtVisitor for LocalAssignCollector { + fn visit_import_stmt(&mut self, stmt: &ImportStmt) -> Result<()> { + if stmt.what.is_empty() { + // `import foo` + if stmt.module.kind == TokenKind::Name { + // do not add `import "my_file.ext"` + self.names.insert(stmt.module.text.to_string()); + } + } else { + // `import foo, bar from baz` + for what in &stmt.what { + if what.kind == TokenKind::Name { + // do not add `import * from foo` + self.names.insert(what.text.to_string()); + } + } + } + Ok(()) + } + fn visit_expr_stmt(&mut self, stmt: &ExprStmt) -> Result<()> { stmt.expr.accept(self)?; Ok(()) @@ -285,6 +315,25 @@ impl LocalNameCollector { } impl StmtVisitor for LocalNameCollector { + fn visit_import_stmt(&mut self, stmt: &ImportStmt) -> Result<()> { + if stmt.what.is_empty() { + // `import foo` + if stmt.module.kind == TokenKind::Name { + // do not add `import "my_file.ext"` + self.names.insert(stmt.module.text.to_string()); + } + } else { + // `import foo, bar from baz` + for what in &stmt.what { + if what.kind == TokenKind::Name { + // do not add `import * from foo` + self.names.insert(what.text.to_string()); + } + } + } + Ok(()) + } + fn visit_expr_stmt(&mut self, stmt: &ExprStmt) -> Result<()> { stmt.expr.accept(self)?; Ok(()) @@ -441,19 +490,27 @@ impl Display for CompileError { //////////////////////////////////////////////////////////////////////////////// #[derive(Debug)] -pub struct Compiler { +pub struct Compiler<'c> { + path: PathBuf, chunks: Vec, scopes: Vec, - constants: Vec, + constants: &'c mut Vec, + imported: &'c mut HashMap>, globals: Vec, } -impl Compiler { - pub fn new() -> Self { +impl<'c> Compiler<'c> { + pub fn new( + path: PathBuf, + constants: &'c mut Vec, + imported: &'c mut HashMap>, + ) -> Self { Compiler { + path, chunks: Default::default(), scopes: Default::default(), - constants: Default::default(), + constants, + imported, globals: BUILTINS .with_borrow(|builtins| builtins.keys().map(ToString::to_string).collect()), } @@ -479,10 +536,33 @@ impl Compiler { self.scopes.is_empty() } + pub fn compile_path(self, path: impl AsRef) -> Result> { + let path_str = &path.as_ref().as_os_str().to_str().unwrap(); + let mut file = File::open(path.as_ref()).map_err(|e| CompileError { + line: None, + message: format!("could not open {}: {}", path.as_ref().display(), e), + })?; + let mut contents = String::new(); + file.read_to_string(&mut contents)?; + + let mut parser = Parser::new(contents, &path_str)?; + let ast = parser.parse_all()?; + + if parser.was_error() { + return Err(CompileError { + line: None, + message: format!("error in '{}'", path.as_ref().display()), + } + .into()); + } + + self.compile(&path_str, &ast) + } + /// Compiles a body of code. /// - /// This returns a tuple of `Chunk`, the constants table, and the list of globals. - pub fn compile(mut self, body: &Vec) -> Result<(Chunk, Vec, Vec)> { + /// This returns the module of the compiled program. + pub fn compile(mut self, path: impl ToString, body: &Vec) -> Result> { self.chunks.push(Chunk::default()); for stmt in body { @@ -494,11 +574,19 @@ impl Compiler { if let Some(last) = body.last() { last_line = stmt_line_number(last.as_ref()); } - self.emit(last_line, Op::Halt); + self.emit(last_line, Op::ExitModule); let chunk = self.chunks.pop().expect("no chunk"); - Ok((chunk, self.constants, self.globals)) + // This is allowed because obviously it is a pointer to a Module. We can upcast later. + let module = Module::create(path.to_string(), Rc::new(chunk), self.globals); + + let module = unsafe { + let ptr = Ptr::into_raw(module) as *const gc::GcCell; + Ptr::from_raw(ptr) + }; + + Ok(module) } fn compile_stmt(&mut self, stmt: &StmtP) -> Result<()> { @@ -673,21 +761,13 @@ impl Compiler { chunk.code.push(op); chunk.lines.push(line); } -} -impl StmtVisitor for Compiler { - fn visit_expr_stmt(&mut self, stmt: &ExprStmt) -> Result<()> { - self.compile_expr(&stmt.expr)?; - self.emit(stmt_line_number(stmt), Op::Pop); - Ok(()) - } - - fn visit_assign_stmt(&mut self, stmt: &AssignStmt) -> Result<()> { - let name = &stmt.lhs.text; + /// Emit an assign statement based on the current scope - i.e. `Op::SetGlobal` if we're + /// in global scope, or `Op::SetLocal` if we're in a function or local scope. + fn emit_assign(&mut self, line: LineRange, name: &str) -> Result<()> { if self.is_global_scope() { let global = self.insert_global(name)?; - self.compile_expr(&stmt.rhs)?; - self.emit(stmt_line_number(stmt), Op::SetGlobal(global)); + self.emit(line, Op::SetGlobal(global)); } else { let mut declare = false; let local = if let Some(local) = self.get_local(name) { @@ -696,12 +776,93 @@ impl StmtVisitor for Compiler { declare = true; self.insert_local(name.to_string())? } - .clone(); // gotta clone so we can borrow self as mutable for compile_expr - self.compile_expr(&stmt.rhs)?; + .clone(); if !declare { - self.emit(stmt_line_number(stmt), Op::SetLocal(local.index)); + self.emit(line, Op::SetLocal(local.index)); } } + Ok(()) + } + + fn search_dir(&self) -> &Path { + self.path.parent().unwrap() + } +} + +impl StmtVisitor for Compiler<'_> { + fn visit_import_stmt(&mut self, stmt: &ImportStmt) -> Result<()> { + const EXT: &str = "npp"; + + let line = stmt_line_number(stmt); + + // resolve filename and get full filepath + let path = match stmt.module.kind { + TokenKind::Name => self + .search_dir() + .join(format!("{}.{EXT}", stmt.module.text)), + TokenKind::String => { + let path = PathBuf::from(unescape(&stmt.module.text)); + if path.is_absolute() { + path + } else { + std::path::absolute(self.search_dir().join(path))? + } + } + _ => unreachable!(), + }; + + // check if this has already been registered with our compile session and just use that if + // so + let path_str = path.as_os_str().to_str().unwrap(); + let module = if let Some(imported) = self.imported.get(path_str) { + // use the imported module + imported.clone() + } else { + // otherwise compile and create a new Module object and insert it as a constant and + // also into the modules cache + let module = Compiler::new(path.clone(), self.constants, self.imported) + .compile_path(&path) + .map_err(|e| CompileError { + line: Some(line), + message: format!("while importing module '{}': {}", stmt.module.text, e), + })?; + self.imported.insert(path_str.to_string(), module.clone()); + module + }; + let module_constant = self.insert_constant(upcast_obj(module.clone()))?; + + self.emit(stmt_line_number(stmt), Op::PushConstant(module_constant)); + + if stmt.what.is_empty() { + // evaluate the module, and then assign the resulting object to the module name as + // appropriate + self.emit(line, Op::EnterModule); + // only assign if it's a name, if it's a string we don't assign anything + if stmt.module.kind == TokenKind::Name { + self.emit_assign(line, &stmt.module.text)?; + } else { + self.emit(line, Op::Pop); + } + } else { + // evaluate the module, and then assign all names that were imported as appropriate + // TODO Compiler::visit_import_stmt - visit names from module + todo!("import names from module") + } + + Ok(()) + } + + fn visit_expr_stmt(&mut self, stmt: &ExprStmt) -> Result<()> { + self.compile_expr(&stmt.expr)?; + self.emit(stmt_line_number(stmt), Op::Pop); + Ok(()) + } + + fn visit_assign_stmt(&mut self, stmt: &AssignStmt) -> Result<()> { + // compile RHS + self.compile_expr(&stmt.rhs)?; + + let name = &stmt.lhs.text; // If the last value that was assigned to is a function, set its name here // TODO - maybe this would be smarter to set up in the AST. I'm 99% sure that the last @@ -713,6 +874,9 @@ impl StmtVisitor for Compiler { } } + // compile LHS + self.emit_assign(stmt_line_number(stmt), name)?; + Ok(()) } @@ -794,7 +958,7 @@ impl StmtVisitor for Compiler { } } -impl ExprVisitor for Compiler { +impl ExprVisitor for Compiler<'_> { fn visit_binary_expr(&mut self, expr: &BinaryExpr) -> Result<()> { static OP_NAMES: LazyLock> = LazyLock::new(|| { hash_map! { @@ -1024,7 +1188,11 @@ impl ExprVisitor for Compiler { // create the function let chunk = self.chunks.pop().unwrap(); - let fun = UserFunction::create(chunk, expr.params.len() as Argc); + let fun = UserFunction::create( + &self.path.as_os_str().to_str().unwrap(), + chunk, + expr.params.len() as Argc, + ); // register the function as a constant let fun_constant = self.insert_constant(fun)?; diff --git a/src/disassemble.rs b/src/disassemble.rs index 13fd49d..ab60232 100644 --- a/src/disassemble.rs +++ b/src/disassemble.rs @@ -4,7 +4,7 @@ use crate::vm::{Chunk, JumpOpArg, Op}; type Row = (String, String, &'static str, String, String); -fn disassemble_chunk(chunk: &Chunk, constants: &Vec, globals: &Vec) { +fn disassemble_chunk(chunk: &Chunk, globals: &Vec, constants: &Vec) { let mut rows: Vec = vec![( "ADDR".into(), "LINE".into(), @@ -109,8 +109,13 @@ fn disassemble_chunk(chunk: &Chunk, constants: &Vec, globals: &Vec arg = String::new(); info = String::new(); } - Op::Halt => { - op_str = "HALT"; + Op::EnterModule => { + op_str = "ENTER_MODULE"; + arg = String::new(); + info = String::new(); + } + Op::ExitModule => { + op_str = "EXIT_MODULE"; arg = String::new(); info = String::new(); } @@ -150,10 +155,10 @@ fn display_rows(rows: &Vec) { } } -pub fn disassemble(chunk: &Chunk, constants: &Vec, globals: &Vec) { +pub fn disassemble(chunk: &Chunk, globals: &Vec, constants: &Vec) { println!("== main chunk"); println!(); - disassemble_chunk(chunk, constants, globals); + disassemble_chunk(chunk, globals, constants); for constant in constants { if let Some(fun) = constant.borrow().as_any().downcast_ref::() { @@ -164,7 +169,7 @@ pub fn disassemble(chunk: &Chunk, constants: &Vec, globals: &Vec) fun.chunk().lines[0].0 ); println!(); - disassemble_chunk(fun.chunk(), constants, globals); + disassemble_chunk(fun.chunk(), globals, constants); } } } diff --git a/src/main.rs b/src/main.rs index 719053a..9ae7898 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,13 +7,11 @@ mod parser; mod token; mod vm; -use std::fmt; -use std::fs::File; -use std::io::prelude::*; use std::path::PathBuf; use clap::Parser as ClapParser; -use thiserror::Error; + +use crate::obj::upcast_obj; #[derive(ClapParser, Debug)] #[command(version, about, long_about = None)] @@ -24,43 +22,33 @@ struct Args { path: PathBuf, } -#[derive(Debug, Error)] -struct ProgramError(String); - -impl fmt::Display for ProgramError { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - write!(fmt, "{}", self.0) - } -} - fn main() -> Result<(), Box> { let args = Args::parse(); - let mut file = File::open(&args.path)?; - let mut contents = String::new(); - file.read_to_string(&mut contents)?; - - let mut parser = parser::Parser::new(contents, &args.path)?; - let ast = parser.parse_all()?; - - if parser.was_error() { - return Err(ProgramError("error occurred, exiting".to_string()).into()); - } - // initialize type system obj::ty::init_types(); crate::builtins::init_global_builtins(); - // compile - let (chunk, constants, globals) = compiler::Compiler::new().compile(&ast)?; + let absolute_path = std::path::absolute(&args.path).unwrap(); + + let mut constants = Default::default(); + let mut imported = Default::default(); + + let module = compiler::Compiler::new(absolute_path, &mut constants, &mut imported) + .compile_path(&args.path)?; if args.disassemble { - disassemble::disassemble(&chunk, &constants, &globals); + disassemble::disassemble( + module.borrow().chunk(), + module.borrow().globals(), + &constants, + ); return Ok(()); } // run - let mut vm = vm::Vm::new(chunk.into(), constants, globals); + let mut vm = vm::Vm::new(&constants); + vm.enter_module(upcast_obj(module)); vm.run(); Ok(()) diff --git a/src/obj.rs b/src/obj.rs index c29a94f..4cef325 100644 --- a/src/obj.rs +++ b/src/obj.rs @@ -7,6 +7,7 @@ pub mod float; pub mod function; pub mod int; pub mod list; +pub mod module; pub mod str; pub mod ty; @@ -41,7 +42,9 @@ pub mod prelude { pub use crate::obj::ObjP; pub use crate::obj::function::{BuiltinFunction, Method, UserFunction}; - pub use crate::obj::{bool::Bool, float::Float, int::Int, list::List, str::Str, ty::Ty}; + pub use crate::obj::{ + bool::Bool, float::Float, int::Int, list::List, module::Module, str::Str, ty::Ty, + }; pub use crate::obj::{Nil, Obj}; // Other auxiliary types and functions @@ -226,7 +229,7 @@ impl Clone for BaseObj { impl Display for BaseObj { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - write!(fmt, "", (self as *const _ as usize)) + write!(fmt, "", (self as *const _ as usize)) } } @@ -430,7 +433,7 @@ impl Debug for Obj { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { write!( fmt, - "<{} at {:x}>", + "<{} at {:#x}>", self.ty_name(), (self as *const _ as usize) ) diff --git a/src/obj/function.rs b/src/obj/function.rs index 2db9f22..5afd7de 100644 --- a/src/obj/function.rs +++ b/src/obj/function.rs @@ -138,6 +138,8 @@ impl Object for BuiltinFunction { pub struct UserFunction { base: BaseObj, #[unsafe_ignore_trace] + path: Rc, + #[unsafe_ignore_trace] name: Rc, #[unsafe_ignore_trace] chunk: Rc, @@ -146,9 +148,10 @@ pub struct UserFunction { } impl UserFunction { - pub fn new(chunk: Chunk, arity: Argc) -> Self { + pub fn new(path: impl ToString, chunk: Chunk, arity: Argc) -> Self { Self { base: Default::default(), + path: Rc::new(path.to_string()), name: Rc::new("(anonymous)".to_string()), chunk: Rc::new(chunk), arity, @@ -156,7 +159,11 @@ impl UserFunction { } } - impl_create!(chunk: Chunk, arity: Argc); + impl_create!(path: impl ToString, chunk: Chunk, arity: Argc); + + pub fn path(&self) -> &Rc { + &self.path + } pub fn name(&self) -> &Rc { &self.name diff --git a/src/obj/module.rs b/src/obj/module.rs new file mode 100644 index 0000000..65f8acc --- /dev/null +++ b/src/obj/module.rs @@ -0,0 +1,123 @@ +use std::fmt::{self, Debug, Display}; +use std::rc::Rc; + +use gc::{Finalize, Trace}; + +use crate::obj::macros::*; +use crate::obj::prelude::*; +use crate::obj::BaseObj; +use crate::vm::Chunk; + +#[derive(Trace, Finalize)] +pub struct Module { + base: BaseObj, + #[unsafe_ignore_trace] + path: Rc, + #[unsafe_ignore_trace] + chunk: Rc, + globals: Vec, + evaluated_value: Option, +} + +impl Module { + pub fn new(path: impl ToString, chunk: Rc, globals: Vec) -> Self { + Module { + base: Default::default(), + path: Rc::new(path.to_string()), + chunk, + globals, + evaluated_value: None, + } + } + + pub fn path(&self) -> &Rc { + &self.path + } + + pub fn chunk(&self) -> &Rc { + &self.chunk + } + + pub fn globals(&self) -> &Vec { + &self.globals + } + + pub fn evaluated_value(&self) -> &Option { + &self.evaluated_value + } + + pub fn set_evaluated_value(&mut self, value: Option) { + self.evaluated_value = value; + } + + impl_create!(path: impl ToString, chunk: Rc, globals: Vec); +} + +impl Debug for Module { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + write!(fmt, "", self.path()) + } +} + +impl Display for Module { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fmt::Debug::fmt(self, fmt) + } +} + +impl Object for Module { + fn equals(&self, other: &dyn Object) -> bool { + if let Some(other) = other.as_any().downcast_ref::() { + // only referential identity + std::ptr::addr_eq(self, other) + } else { + false + } + } + + impl_base_obj!(Module); +} + +//////////////////////////////////////////////////////////////////////////////// +// Module method implementations +//////////////////////////////////////////////////////////////////////////////// + +/* +impl Module { + pub(crate) fn execute(vm: &mut Vm, state: FunctionState) -> FunctionResult { + match state { + FunctionState::Begin => { + let this = vm.frame_stack()[0].clone(); + let value = with_obj_downcast(this.clone(), |module: &Module| { + module.evaluated_value.clone() + }); + + // don't evaluate this twice + if let Some(value) = value { + return FunctionResult::ReturnPush(value); + } + + vm.enter_module(this.clone()); + FunctionResult::Yield(0) + } + FunctionState::Resume(0) => { + let this = vm.frame_stack()[0].clone(); + let obj = with_obj_downcast(this.clone(), |module: &Module| { + assert_eq!(module.globals().len(), vm.globals().len()); + let obj = Obj::create(); + module + .globals() + .iter() + .zip(vm.globals()) + .for_each(|(name, value)| { + obj.borrow_mut().set_attr(name, value.clone()); + }); + obj + }); + FunctionResult::ReturnPush(obj) + } + _ => unreachable!(), + } + } +} +*/ diff --git a/src/obj/ty.rs b/src/obj/ty.rs index eecade5..c1e27c0 100644 --- a/src/obj/ty.rs +++ b/src/obj/ty.rs @@ -186,6 +186,7 @@ pub fn init_types() { }, Obj { //__call__ => BuiltinFunction::create("__call__", + // Methods }, List { // Conversion methods @@ -248,6 +249,7 @@ pub fn init_types() { __le__ => BuiltinFunction::create("__le__", Int::le, 2), __pos__ => BuiltinFunction::create("__pos__", Int::pos, 1), __neg__ => BuiltinFunction::create("__neg__", Int::neg, 1), + // Methods }, Float { // Conversion methods @@ -269,6 +271,7 @@ pub fn init_types() { __le__ => BuiltinFunction::create("__le__", Float::le, 2), __pos__ => BuiltinFunction::create("__pos__", Float::pos, 1), __neg__ => BuiltinFunction::create("__neg__", Float::neg, 1), + // Methods }, Bool { // Conversion methods @@ -280,6 +283,7 @@ pub fn init_types() { __init__ => BuiltinFunction::create("__init__", Bool::init, 2), // Operators + // Methods }, Nil { // Conversion methods @@ -289,9 +293,31 @@ pub fn init_types() { __init__ => BuiltinFunction::create("__init__", Nil::init, 1), // Operators + // Methods + }, + BuiltinFunction { + // Conversion methods + // Constructor + // Operators + // Methods + }, + UserFunction { + // Conversion methods + // Constructor + // Operators + // Methods + }, + Method { + // Conversion methods + // Constructor + // Operators + // Methods + }, + Module { + // Conversion methods + // Constructor + // Operators + // Methods }, - BuiltinFunction { }, - UserFunction { }, - Method { }, } } diff --git a/src/parser.rs b/src/parser.rs index 782a24b..e917093 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -80,12 +80,7 @@ impl Lexer { } pub fn lexeme(&self) -> &str { - if self.is_eof() { - // if we're at EOF, the index should not be cut off at the very end - &self.text[self.start..self.index] - } else { - &self.text[self.start..self.index - 1] - } + &self.text[self.start..self.index - 1] } pub fn was_error(&self) -> bool { @@ -292,6 +287,8 @@ impl Lexer { "true" => TokenKind::True, "false" => TokenKind::False, "nil" => TokenKind::Nil, + "import" => TokenKind::Import, + "from" => TokenKind::From, } }); @@ -553,6 +550,8 @@ impl Parser { self.return_stmt() } else if self.mat(TokenKind::If)? { self.if_stmt() + } else if self.mat(TokenKind::Import)? { + self.import_stmt() } else if self.mat(TokenKind::LBrace)? { let lbrace = self.prev.clone().unwrap(); let stmts = self.block()?; @@ -630,6 +629,81 @@ impl Parser { })) } + fn import_stmt(&mut self) -> Result { + let import_kw = self.prev.clone().unwrap(); + + let name = expect!( + self, + "expect name, string, or '*' after import keyword", + TokenKind::String, + TokenKind::Name, + TokenKind::Star + )?; + + let import_stmt = if self.mat(TokenKind::From)? { + if name.kind == TokenKind::String { + return Err(self.error("expect name before 'from' keyword")); + } + let what = vec![name]; + let module = expect!( + self, + "expect name or string after 'from' keyword", + TokenKind::Name, + TokenKind::String + )?; + ImportStmt { + import_kw, + what, + module, + } + } else if self.check(TokenKind::Comma) { + if name.kind == TokenKind::String { + return Err(self.error("expect name in import list, not a string")); + } + + let mut what = vec![name]; + while self.mat(TokenKind::Comma)? { + let name = self + .expect("expect name after comma in import list", TokenKind::Name)? + .clone(); + what.push(name); + } + self.expect("expect 'from' keyword after import list", TokenKind::From)?; + let module = expect!( + self, + "expect name or string after 'from' keyword", + TokenKind::Name, + TokenKind::String + )?; + ImportStmt { + import_kw, + what, + module, + } + } else { + if name.kind == TokenKind::Star { + return Err( + self.error("'import *' does not make any sense without a 'from' keyword") + ); + } + + ImportStmt { + import_kw, + what: vec![], + module: name, + } + }; + + expect!( + self, + "expected end of line after import statement", + TokenKind::Eol, + TokenKind::Eof + )?; + + Ok(Box::new(import_stmt)) + } + fn block_stmt(&mut self) -> Result { let lbrace = self.prev.clone().unwrap(); assert_eq!(lbrace.kind, TokenKind::LBrace); @@ -795,6 +869,11 @@ impl Parser { Ok(Box::new(PrimaryExpr { token: self.prev.clone().unwrap(), })) + } else if mat!(self, TokenKind::From) { + // any other keywords that are acceptable as names in the primary expression context + let mut token = self.prev.clone().unwrap(); + token.kind = TokenKind::Name; + Ok(Box::new(PrimaryExpr { token })) } else if self.mat(TokenKind::LParen)? { let expr: ExprP; // check if we're defining a function diff --git a/src/token.rs b/src/token.rs index ead0bdc..1c45555 100644 --- a/src/token.rs +++ b/src/token.rs @@ -7,6 +7,8 @@ pub enum TokenKind { True, False, Nil, + Import, + From, // Expressions Name, diff --git a/src/vm.rs b/src/vm.rs index a4d6976..7e6a66b 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -34,7 +34,8 @@ pub enum Op { // VM control Nop, - Halt, + EnterModule, + ExitModule, } pub type LineRange = (usize, usize); @@ -68,6 +69,7 @@ pub struct Chunk { #[derive(Debug)] pub(crate) enum Function { Chunk(Rc), + Module(Rc), Builtin(BuiltinFunctionPtr, FunctionState), } @@ -90,49 +92,23 @@ impl Frame { } } -pub struct Vm { - constants: Vec, +pub struct Vm<'c> { + constants: &'c Vec, //global_names: Vec, - globals: Vec, + globals: Vec>, stack: Vec, frames: Vec, } -impl Vm { +impl<'c> Vm<'c> { /// Create a new virtual machine with the given chunk, constants, and global names. - pub fn new(chunk: Rc, constants: Vec, global_names: Vec) -> Self { - // set up globals - let nil = Nil::create(); - let mut globals: Vec<_> = global_names.iter().map(|_| ObjP::clone(&nil)).collect(); - - let mut register_global = |name: &str, value: ObjP| { - let index = global_names - .iter() - .position(|global| global == name) - .expect("could not find global"); - globals[index] = value; - }; - - BUILTINS.with_borrow(|builtins| { - for (name, builtin) in builtins.iter() { - register_global(&name, builtin.clone()); - } - }); - - // stack and frames - let stack = Vec::new(); - let frames = vec![Frame::new( - "__main__".to_string().into(), - Function::Chunk(chunk), - 0, - )]; - + pub fn new(constants: &'c Vec) -> Self { Vm { constants, //global_names, - globals, - stack, - frames, + globals: Default::default(), + stack: Default::default(), + frames: Default::default(), } } @@ -196,6 +172,24 @@ impl Vm { self.frame_mut().ip = ip; } + /* + /// Gets the path of the current function, if it is user-defined. + pub fn path(&self) -> Option<&Rc> { + if let Function::Chunk(path, _) = &self.frame().function { + Some(&path) + } else { + None + } + } + */ + + pub fn globals(&self) -> &Vec { + self.globals.last().expect("no globals") + } + + pub fn globals_mut(&mut self) -> &mut Vec { + self.globals.last_mut().expect("no globals") + } /* /// Gets the line of the current instruction. fn line(&self, offset: isize) -> LineRange { @@ -204,13 +198,42 @@ impl Vm { } */ + pub fn enter_module(&mut self, module_ptr: ObjP) { + with_obj_downcast(module_ptr.clone(), |module: &Module| { + let mut globals: Vec<_> = module.globals().iter().map(|_| Nil::create()).collect(); + + let mut register_global = |name: &str, value: ObjP| { + let index = module + .globals() + .iter() + .position(|global| global == name) + .expect("could not find global"); + globals[index] = value; + }; + + BUILTINS.with_borrow(|builtins| { + for (name, builtin) in builtins.iter() { + register_global(&name, builtin.clone()); + } + }); + + let frame = Frame::new( + Rc::clone(module.path()), + Function::Module(Rc::clone(module.chunk())), + self.stack().len(), + ); + self.push_frame(frame); + self.globals.push(globals); + }); + } + /// Get the current instruction and advance the IP. /// /// This may actually end up calling a function on top of the stack, if it's a builtin function. fn dispatch(&mut self) -> Op { let mut ip = self.ip(); let op = match &self.frame().function { - Function::Chunk(chunk) => { + Function::Chunk(chunk) | Function::Module(chunk) => { let op = chunk.code[ip]; ip += 1; op @@ -288,12 +311,12 @@ impl Vm { self.stack[index] = value; } Op::GetGlobal(global_index) => { - let value = self.globals[global_index as usize].clone(); + let value = self.globals()[global_index as usize].clone(); self.push(value); } Op::SetGlobal(global_index) => { let value = self.pop(); - self.globals[global_index as usize] = value; + self.globals_mut()[global_index as usize] = value; } Op::GetAttr(constant_id) => { // need both declarations to borrow cell value @@ -407,8 +430,51 @@ impl Vm { Op::Nop => { continue; } - Op::Halt => { - break; + Op::EnterModule => { + // check if the module has been evaluated yet + let module = self.peek(); + let value = with_obj_downcast(module.clone(), |module: &Module| { + module.evaluated_value().clone() + }); + if let Some(value) = value { + // there's already a value, so pop the module and just push the evaluated + // value + self.pop(); + self.push(value); + } else { + // otherwise, keep the module on the stack and enter the module + self.enter_module(module); + } + } + Op::ExitModule => { + let globals = self.globals.pop().unwrap(); + + let old_frame = self.pop_frame(); + self.stack + .resize_with(old_frame.stack_base, || unreachable!()); + + // halt execution if there aren't any globals left + if self.globals.is_empty() { + break; + } + + let module = self.pop(); + // create the object and assign it as the module's evaluated value + let obj = with_obj_downcast_mut(module, |module: &mut Module| { + assert_eq!(module.globals().len(), globals.len()); + let obj = Obj::create(); + module + .globals() + .iter() + .zip(globals) + .for_each(|(name, value)| { + obj.borrow_mut().set_attr(name, value.clone()); + }); + module.set_evaluated_value(Some(obj.clone())); + obj + }); + + self.push(obj); } } } diff --git a/tools/genast.py b/tools/genast.py index cacd676..45a0990 100755 --- a/tools/genast.py +++ b/tools/genast.py @@ -63,7 +63,7 @@ class GenerateStruct(Generator): sb.line(f"impl {self.base} for {self.name}{self.base} " + "{") sb.indent() sb.line( - f"fn accept(&self, visitor: &mut dyn {self.base}Visitor) -> Result<(), Box> " + f"fn accept(&self, visitor: &mut dyn {self.base}Visitor) -> Result<(), Error> " + "{" ) sb.indent() @@ -81,7 +81,69 @@ class GenerateStruct(Generator): return str(sb) def generate_visitor(self) -> str: - return f"fn visit_{self.name.lower()}_{self.base.lower()}(&mut self, {self.base.lower()}: &{self.name}{self.base}) -> Result<(), Box>;" + return f"fn visit_{self.name.lower()}_{self.base.lower()}(&mut self, {self.base.lower()}: &{self.name}{self.base}) -> Result<(), Error>;" + + +class GenerateEnum(Generator): + def __init__( + self, base: str, name: str, variants: list[tuple[str, str | list[str]]] + ): + self.base = base + self.name = name + self.variants = [ + (variant, ([members] if isinstance(members, str) else members)) + for (variant, members) in variants + ] + + def generate_members(self) -> str: + sb = SB() + sb.line("#[derive(Debug)]") + sb.line(f"pub enum {self.name}{self.base} " + "{") + sb.indent() + for variant, members in self.variants: + sb.write(f"{variant}") + if members: + struct_like = any(":" in member for member in members) + + if struct_like: + sb.line(" {") + sb.indent() + for member in members: + sb.line(f"{member},") + sb.dedent() + sb.line("},") + else: + sb.write("(") + sb.write(", ".join(members)) + sb.line("),") + else: + sb.line(",") + sb.dedent() + sb.line("}") + sb.line() + + sb.line(f"impl {self.base} for {self.name}{self.base} " + "{") + sb.indent() + sb.line( + f"fn accept(&self, visitor: &mut dyn {self.base}Visitor) -> Result<(), Error> " + + "{" + ) + sb.indent() + sb.line(f"visitor.visit_{self.name.lower()}_{self.base.lower()}(self)") + + sb.dedent() + sb.line("}") + sb.line() + sb.line("fn as_any(self: Box) -> Box { self }") + sb.line("fn as_any_ref(&self) -> &dyn Any { self }") + + sb.dedent() + sb.write("}") + + return str(sb) + + def generate_visitor(self) -> str: + return f"fn visit_{self.name.lower()}_{self.base.lower()}(&mut self, {self.base.lower()}: &{self.name}{self.base}) -> Result<(), Error>;" class GenerateGroup(Generator): @@ -95,10 +157,15 @@ class GenerateGroup(Generator): sb = SB() sb.line(self.generate_visitor()) + sb.line() sb.line(self.generate_members()) return str(sb) + def add_enum(self, name: str, variants: list[tuple[str, str | list[str]]]) -> Self: + self.group += [GenerateEnum(self.name, name, variants)] + return self + def add_struct(self, name: str, rules: list[str]) -> Self: self.group += [GenerateStruct(self.name, name, rules)] return self @@ -110,7 +177,7 @@ class GenerateGroup(Generator): sb.line(f"pub trait {self.name}: Debug + Any " + "{") sb.indent() sb.line( - f"fn accept(&self, visitor: &mut dyn {self.name}Visitor) -> Result<(), Box>;" + f"fn accept(&self, visitor: &mut dyn {self.name}Visitor) -> Result<(), Error>;" ) sb.line("fn as_any(self: Box) -> Box;") sb.line("fn as_any_ref(&self) -> &dyn Any;") @@ -120,13 +187,14 @@ class GenerateGroup(Generator): # Type sb.line(f"pub type {self.name}P = Box;") + sb.line() # All members and rules for g in self.group: sb.line(g.generate_members()) sb.line() - return str(sb) + return str(sb).strip() def generate_visitor(self) -> str: sb = SB() @@ -139,7 +207,7 @@ class GenerateGroup(Generator): sb.dedent() sb.line("}") - return str(sb) + return str(sb).strip() GENERATE = [ @@ -164,7 +232,7 @@ GENERATE = [ .add_struct("List", ["lbracket: Token", "exprs: Vec", "rbracket: Token"]), # Stmt GenerateGroup("Stmt") - .add_struct("Import", ["import_kw: Token", "module: Token"]) + .add_struct("Import", ["import_kw: Token", "what: Vec", "module: Token"]) .add_struct("Expr", ["expr: ExprP"]) .add_struct("Assign", ["lhs: Token", "rhs: ExprP"]) .add_struct("Set", ["expr: ExprP", "name: Token", "rhs: ExprP"]) @@ -206,6 +274,8 @@ def main(): sb.line() sb.line("use crate::token::Token;") sb.line() + sb.line("type Error = Box;") + sb.line() for g in GENERATE: sb.line(g.generate())