WIP: Add imports and modules

This is a big change because it touches a lot of stuff, but here is the
overview:

* Import syntax:
    ```
    import foo
    import bar from foo
    import bar from "foo.npp"
    import bar, baz from foo
    import * from foo
    import "foo.npp"
    ```
    * These are all valid imports. They should be pretty
      straightforward, maybe with exception of the last item. If you are
      importing a path directly, but not importing any members from it,
      it does not insert anything into the current namespace, and just
      executes the file. This is probably going to be unused but I want
      to include it for completeness. We can always remove it later
      before a hypothetical 1.0 release.
    * The "from" keyword is only ever used as a keyword here, and I am
      allowing it to be used as an identifier elsewhere. Don't export
      it, because that's weird and wrong and won't work.
* Modules:
    * Doing an `import foo` will look for "foo.npp" at compile-time,
      relative to the importer's directory, parse it, and compile it.
      The importer will then attempt to execute the module with the new
      `EnterModule` op. This instruction will execute the module kind of
      like a function, assigning the module's global namespace to an
      object that you can pass around.
    * `import bar from foo` and `import bar from "foo.npp"` et al syntax
      is not currently implemented in the compiler.
    * There is a new "Module" object that represents a potentially
      un-initialized module. This can't be referred to directly in code.
* VM:
    * The VM operates around Module objects now. If you want to "call" a
      new module, you should call `enter_module`. This is how the main
      chunk is invoked.
* TODOs:
    * `exit_module` function in the VM
    * Finish up module implementation in compiler
    * Built-in modules
    * Sub-modules - e.g. `import foo.bar` - how does naming work for
      this?
    * Module directories. In Python you have `foo/__init__.py` and in
      Rust you have `foo/mod.rs`.
    * Probably a "Namespace" object that explicitly denotes "this is an
      imported module that you're dealing with"
    * Tests, tests, tests

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
This commit is contained in:
2024-10-04 10:11:49 -07:00
parent 4a7644b84a
commit f0de5f7850
12 changed files with 753 additions and 268 deletions

View File

@@ -1,33 +1,26 @@
// This is an auto-generated file. Any changes made to this file may be overwritten. // 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)] #![allow(dead_code)]
use std::fmt::Debug; use std::fmt::Debug;
use std::any::Any; use std::any::Any;
use crate::token::Token; use crate::token::Token;
pub trait ExprVisitor { type Error = Box<dyn std::error::Error>;
fn visit_binary_expr(&mut self, expr: &BinaryExpr) -> Result<(), Box<dyn std::error::Error>>;
fn visit_unary_expr(&mut self, expr: &UnaryExpr) -> Result<(), Box<dyn std::error::Error>>;
fn visit_call_expr(&mut self, expr: &CallExpr) -> Result<(), Box<dyn std::error::Error>>;
fn visit_get_expr(&mut self, expr: &GetExpr) -> Result<(), Box<dyn std::error::Error>>;
fn visit_index_expr(&mut self, expr: &IndexExpr) -> Result<(), Box<dyn std::error::Error>>;
fn visit_primary_expr(&mut self, expr: &PrimaryExpr) -> Result<(), Box<dyn std::error::Error>>;
fn visit_function_expr(&mut self, expr: &FunctionExpr) -> Result<(), Box<dyn std::error::Error>>;
fn visit_list_expr(&mut self, expr: &ListExpr) -> Result<(), Box<dyn std::error::Error>>;
}
pub trait StmtVisitor { pub trait ExprVisitor {
fn visit_expr_stmt(&mut self, stmt: &ExprStmt) -> Result<(), Box<dyn std::error::Error>>; fn visit_binary_expr(&mut self, expr: &BinaryExpr) -> Result<(), Error>;
fn visit_assign_stmt(&mut self, stmt: &AssignStmt) -> Result<(), Box<dyn std::error::Error>>; fn visit_unary_expr(&mut self, expr: &UnaryExpr) -> Result<(), Error>;
fn visit_set_stmt(&mut self, stmt: &SetStmt) -> Result<(), Box<dyn std::error::Error>>; fn visit_call_expr(&mut self, expr: &CallExpr) -> Result<(), Error>;
fn visit_block_stmt(&mut self, stmt: &BlockStmt) -> Result<(), Box<dyn std::error::Error>>; fn visit_get_expr(&mut self, expr: &GetExpr) -> Result<(), Error>;
fn visit_return_stmt(&mut self, stmt: &ReturnStmt) -> Result<(), Box<dyn std::error::Error>>; fn visit_index_expr(&mut self, expr: &IndexExpr) -> Result<(), Error>;
fn visit_if_stmt(&mut self, stmt: &IfStmt) -> Result<(), Box<dyn std::error::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 { pub trait Expr: Debug + Any {
fn accept(&self, visitor: &mut dyn ExprVisitor) -> Result<(), Box<dyn std::error::Error>>; fn accept(&self, visitor: &mut dyn ExprVisitor) -> Result<(), Error>;
fn as_any(self: Box<Self>) -> Box<dyn Any>; fn as_any(self: Box<Self>) -> Box<dyn Any>;
fn as_any_ref(&self) -> &dyn Any; fn as_any_ref(&self) -> &dyn Any;
} }
@@ -42,17 +35,12 @@ pub struct BinaryExpr {
} }
impl Expr for BinaryExpr { impl Expr for BinaryExpr {
fn accept(&self, visitor: &mut dyn ExprVisitor) -> Result<(), Box<dyn std::error::Error>> { fn accept(&self, visitor: &mut dyn ExprVisitor) -> Result<(), Error> {
visitor.visit_binary_expr(self) visitor.visit_binary_expr(self)
} }
fn as_any(self: Box<Self>) -> Box<dyn Any> { fn as_any(self: Box<Self>) -> Box<dyn Any> { self }
self fn as_any_ref(&self) -> &dyn Any { self }
}
fn as_any_ref(&self) -> &dyn Any {
self
}
} }
#[derive(Debug)] #[derive(Debug)]
@@ -62,17 +50,12 @@ pub struct UnaryExpr {
} }
impl Expr for UnaryExpr { impl Expr for UnaryExpr {
fn accept(&self, visitor: &mut dyn ExprVisitor) -> Result<(), Box<dyn std::error::Error>> { fn accept(&self, visitor: &mut dyn ExprVisitor) -> Result<(), Error> {
visitor.visit_unary_expr(self) visitor.visit_unary_expr(self)
} }
fn as_any(self: Box<Self>) -> Box<dyn Any> { fn as_any(self: Box<Self>) -> Box<dyn Any> { self }
self fn as_any_ref(&self) -> &dyn Any { self }
}
fn as_any_ref(&self) -> &dyn Any {
self
}
} }
#[derive(Debug)] #[derive(Debug)]
@@ -83,17 +66,12 @@ pub struct CallExpr {
} }
impl Expr for CallExpr { impl Expr for CallExpr {
fn accept(&self, visitor: &mut dyn ExprVisitor) -> Result<(), Box<dyn std::error::Error>> { fn accept(&self, visitor: &mut dyn ExprVisitor) -> Result<(), Error> {
visitor.visit_call_expr(self) visitor.visit_call_expr(self)
} }
fn as_any(self: Box<Self>) -> Box<dyn Any> { fn as_any(self: Box<Self>) -> Box<dyn Any> { self }
self fn as_any_ref(&self) -> &dyn Any { self }
}
fn as_any_ref(&self) -> &dyn Any {
self
}
} }
#[derive(Debug)] #[derive(Debug)]
@@ -103,17 +81,12 @@ pub struct GetExpr {
} }
impl Expr for GetExpr { impl Expr for GetExpr {
fn accept(&self, visitor: &mut dyn ExprVisitor) -> Result<(), Box<dyn std::error::Error>> { fn accept(&self, visitor: &mut dyn ExprVisitor) -> Result<(), Error> {
visitor.visit_get_expr(self) visitor.visit_get_expr(self)
} }
fn as_any(self: Box<Self>) -> Box<dyn Any> { fn as_any(self: Box<Self>) -> Box<dyn Any> { self }
self fn as_any_ref(&self) -> &dyn Any { self }
}
fn as_any_ref(&self) -> &dyn Any {
self
}
} }
#[derive(Debug)] #[derive(Debug)]
@@ -124,17 +97,12 @@ pub struct IndexExpr {
} }
impl Expr for IndexExpr { impl Expr for IndexExpr {
fn accept(&self, visitor: &mut dyn ExprVisitor) -> Result<(), Box<dyn std::error::Error>> { fn accept(&self, visitor: &mut dyn ExprVisitor) -> Result<(), Error> {
visitor.visit_index_expr(self) visitor.visit_index_expr(self)
} }
fn as_any(self: Box<Self>) -> Box<dyn Any> { fn as_any(self: Box<Self>) -> Box<dyn Any> { self }
self fn as_any_ref(&self) -> &dyn Any { self }
}
fn as_any_ref(&self) -> &dyn Any {
self
}
} }
#[derive(Debug)] #[derive(Debug)]
@@ -143,40 +111,30 @@ pub struct PrimaryExpr {
} }
impl Expr for PrimaryExpr { impl Expr for PrimaryExpr {
fn accept(&self, visitor: &mut dyn ExprVisitor) -> Result<(), Box<dyn std::error::Error>> { fn accept(&self, visitor: &mut dyn ExprVisitor) -> Result<(), Error> {
visitor.visit_primary_expr(self) visitor.visit_primary_expr(self)
} }
fn as_any(self: Box<Self>) -> Box<dyn Any> { fn as_any(self: Box<Self>) -> Box<dyn Any> { self }
self fn as_any_ref(&self) -> &dyn Any { self }
}
fn as_any_ref(&self) -> &dyn Any {
self
}
} }
#[derive(Debug)] #[derive(Debug)]
pub struct FunctionExpr { pub struct FunctionExpr {
pub lparen: Token, pub lparen: Token,
pub params: Vec<(Token , Option<ExprP>)>, pub params: Vec<(Token, Option<ExprP>)>,
pub return_type: Option<ExprP>, pub return_type: Option<ExprP>,
pub body: Vec<StmtP>, pub body: Vec<StmtP>,
pub rbrace: Token, pub rbrace: Token,
} }
impl Expr for FunctionExpr { impl Expr for FunctionExpr {
fn accept(&self, visitor: &mut dyn ExprVisitor) -> Result<(), Box<dyn std::error::Error>> { fn accept(&self, visitor: &mut dyn ExprVisitor) -> Result<(), Error> {
visitor.visit_function_expr(self) visitor.visit_function_expr(self)
} }
fn as_any(self: Box<Self>) -> Box<dyn Any> { fn as_any(self: Box<Self>) -> Box<dyn Any> { self }
self fn as_any_ref(&self) -> &dyn Any { self }
}
fn as_any_ref(&self) -> &dyn Any {
self
}
} }
#[derive(Debug)] #[derive(Debug)]
@@ -187,44 +145,60 @@ pub struct ListExpr {
} }
impl Expr for ListExpr { impl Expr for ListExpr {
fn accept(&self, visitor: &mut dyn ExprVisitor) -> Result<(), Box<dyn std::error::Error>> { fn accept(&self, visitor: &mut dyn ExprVisitor) -> Result<(), Error> {
visitor.visit_list_expr(self) visitor.visit_list_expr(self)
} }
fn as_any(self: Box<Self>) -> Box<dyn Any> { fn as_any(self: Box<Self>) -> Box<dyn Any> { self }
self fn as_any_ref(&self) -> &dyn Any { self }
} }
fn as_any_ref(&self) -> &dyn Any { pub trait StmtVisitor {
self 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 { pub trait Stmt: Debug + Any {
fn accept(&self, visitor: &mut dyn StmtVisitor) -> Result<(), Box<dyn std::error::Error>>; fn accept(&self, visitor: &mut dyn StmtVisitor) -> Result<(), Error>;
fn as_any(self: Box<Self>) -> Box<dyn Any>; fn as_any(self: Box<Self>) -> Box<dyn Any>;
fn as_any_ref(&self) -> &dyn Any; fn as_any_ref(&self) -> &dyn Any;
} }
pub type StmtP = Box<dyn Stmt + 'static>; pub type StmtP = Box<dyn Stmt + 'static>;
#[derive(Debug)]
pub struct ImportStmt {
pub import_kw: Token,
pub what: Vec<Token>,
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<Self>) -> Box<dyn Any> { self }
fn as_any_ref(&self) -> &dyn Any { self }
}
#[derive(Debug)] #[derive(Debug)]
pub struct ExprStmt { pub struct ExprStmt {
pub expr: ExprP, pub expr: ExprP,
} }
impl Stmt for ExprStmt { impl Stmt for ExprStmt {
fn accept(&self, visitor: &mut dyn StmtVisitor) -> Result<(), Box<dyn std::error::Error>> { fn accept(&self, visitor: &mut dyn StmtVisitor) -> Result<(), Error> {
visitor.visit_expr_stmt(self) visitor.visit_expr_stmt(self)
} }
fn as_any(self: Box<Self>) -> Box<dyn Any> { fn as_any(self: Box<Self>) -> Box<dyn Any> { self }
self fn as_any_ref(&self) -> &dyn Any { self }
}
fn as_any_ref(&self) -> &dyn Any {
self
}
} }
#[derive(Debug)] #[derive(Debug)]
@@ -234,17 +208,12 @@ pub struct AssignStmt {
} }
impl Stmt for AssignStmt { impl Stmt for AssignStmt {
fn accept(&self, visitor: &mut dyn StmtVisitor) -> Result<(), Box<dyn std::error::Error>> { fn accept(&self, visitor: &mut dyn StmtVisitor) -> Result<(), Error> {
visitor.visit_assign_stmt(self) visitor.visit_assign_stmt(self)
} }
fn as_any(self: Box<Self>) -> Box<dyn Any> { fn as_any(self: Box<Self>) -> Box<dyn Any> { self }
self fn as_any_ref(&self) -> &dyn Any { self }
}
fn as_any_ref(&self) -> &dyn Any {
self
}
} }
#[derive(Debug)] #[derive(Debug)]
@@ -255,17 +224,12 @@ pub struct SetStmt {
} }
impl Stmt for SetStmt { impl Stmt for SetStmt {
fn accept(&self, visitor: &mut dyn StmtVisitor) -> Result<(), Box<dyn std::error::Error>> { fn accept(&self, visitor: &mut dyn StmtVisitor) -> Result<(), Error> {
visitor.visit_set_stmt(self) visitor.visit_set_stmt(self)
} }
fn as_any(self: Box<Self>) -> Box<dyn Any> { fn as_any(self: Box<Self>) -> Box<dyn Any> { self }
self fn as_any_ref(&self) -> &dyn Any { self }
}
fn as_any_ref(&self) -> &dyn Any {
self
}
} }
#[derive(Debug)] #[derive(Debug)]
@@ -276,17 +240,12 @@ pub struct BlockStmt {
} }
impl Stmt for BlockStmt { impl Stmt for BlockStmt {
fn accept(&self, visitor: &mut dyn StmtVisitor) -> Result<(), Box<dyn std::error::Error>> { fn accept(&self, visitor: &mut dyn StmtVisitor) -> Result<(), Error> {
visitor.visit_block_stmt(self) visitor.visit_block_stmt(self)
} }
fn as_any(self: Box<Self>) -> Box<dyn Any> { fn as_any(self: Box<Self>) -> Box<dyn Any> { self }
self fn as_any_ref(&self) -> &dyn Any { self }
}
fn as_any_ref(&self) -> &dyn Any {
self
}
} }
#[derive(Debug)] #[derive(Debug)]
@@ -296,17 +255,12 @@ pub struct ReturnStmt {
} }
impl Stmt for ReturnStmt { impl Stmt for ReturnStmt {
fn accept(&self, visitor: &mut dyn StmtVisitor) -> Result<(), Box<dyn std::error::Error>> { fn accept(&self, visitor: &mut dyn StmtVisitor) -> Result<(), Error> {
visitor.visit_return_stmt(self) visitor.visit_return_stmt(self)
} }
fn as_any(self: Box<Self>) -> Box<dyn Any> { fn as_any(self: Box<Self>) -> Box<dyn Any> { self }
self fn as_any_ref(&self) -> &dyn Any { self }
}
fn as_any_ref(&self) -> &dyn Any {
self
}
} }
#[derive(Debug)] #[derive(Debug)]
@@ -318,16 +272,10 @@ pub struct IfStmt {
} }
impl Stmt for IfStmt { impl Stmt for IfStmt {
fn accept(&self, visitor: &mut dyn StmtVisitor) -> Result<(), Box<dyn std::error::Error>> { fn accept(&self, visitor: &mut dyn StmtVisitor) -> Result<(), Error> {
visitor.visit_if_stmt(self) visitor.visit_if_stmt(self)
} }
fn as_any(self: Box<Self>) -> Box<dyn Any> { fn as_any(self: Box<Self>) -> Box<dyn Any> { self }
self fn as_any_ref(&self) -> &dyn Any { self }
}
fn as_any_ref(&self) -> &dyn Any {
self
}
} }

View File

@@ -1,5 +1,8 @@
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::fmt::{self, Display}; use std::fmt::{self, Display};
use std::fs::File;
use std::io::Read;
use std::path::{Path, PathBuf};
use std::rc::Rc; use std::rc::Rc;
use std::sync::LazyLock; use std::sync::LazyLock;
@@ -9,7 +12,9 @@ use thiserror::Error;
use crate::ast::*; use crate::ast::*;
use crate::obj::prelude::*; use crate::obj::prelude::*;
use crate::obj::Ptr;
use crate::obj::BUILTINS; use crate::obj::BUILTINS;
use crate::parser::Parser;
use crate::token::TokenKind; use crate::token::TokenKind;
use crate::vm::*; use crate::vm::*;
@@ -40,6 +45,12 @@ impl LineNumber {
} }
impl StmtVisitor for 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<()> { fn visit_expr_stmt(&mut self, stmt: &ExprStmt) -> Result<()> {
stmt.expr.accept(self).unwrap(); stmt.expr.accept(self).unwrap();
Ok(()) Ok(())
@@ -165,6 +176,25 @@ impl LocalAssignCollector {
} }
impl StmtVisitor for 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<()> { fn visit_expr_stmt(&mut self, stmt: &ExprStmt) -> Result<()> {
stmt.expr.accept(self)?; stmt.expr.accept(self)?;
Ok(()) Ok(())
@@ -285,6 +315,25 @@ impl LocalNameCollector {
} }
impl StmtVisitor for 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<()> { fn visit_expr_stmt(&mut self, stmt: &ExprStmt) -> Result<()> {
stmt.expr.accept(self)?; stmt.expr.accept(self)?;
Ok(()) Ok(())
@@ -441,19 +490,27 @@ impl Display for CompileError {
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
#[derive(Debug)] #[derive(Debug)]
pub struct Compiler { pub struct Compiler<'c> {
path: PathBuf,
chunks: Vec<Chunk>, chunks: Vec<Chunk>,
scopes: Vec<Scope>, scopes: Vec<Scope>,
constants: Vec<ObjP>, constants: &'c mut Vec<ObjP>,
imported: &'c mut HashMap<String, Ptr<Module>>,
globals: Vec<String>, globals: Vec<String>,
} }
impl Compiler { impl<'c> Compiler<'c> {
pub fn new() -> Self { pub fn new(
path: PathBuf,
constants: &'c mut Vec<ObjP>,
imported: &'c mut HashMap<String, Ptr<Module>>,
) -> Self {
Compiler { Compiler {
path,
chunks: Default::default(), chunks: Default::default(),
scopes: Default::default(), scopes: Default::default(),
constants: Default::default(), constants,
imported,
globals: BUILTINS globals: BUILTINS
.with_borrow(|builtins| builtins.keys().map(ToString::to_string).collect()), .with_borrow(|builtins| builtins.keys().map(ToString::to_string).collect()),
} }
@@ -479,10 +536,33 @@ impl Compiler {
self.scopes.is_empty() self.scopes.is_empty()
} }
pub fn compile_path(self, path: impl AsRef<Path>) -> Result<Ptr<Module>> {
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. /// Compiles a body of code.
/// ///
/// This returns a tuple of `Chunk`, the constants table, and the list of globals. /// This returns the module of the compiled program.
pub fn compile(mut self, body: &Vec<StmtP>) -> Result<(Chunk, Vec<ObjP>, Vec<String>)> { pub fn compile(mut self, path: impl ToString, body: &Vec<StmtP>) -> Result<Ptr<Module>> {
self.chunks.push(Chunk::default()); self.chunks.push(Chunk::default());
for stmt in body { for stmt in body {
@@ -494,11 +574,19 @@ impl Compiler {
if let Some(last) = body.last() { if let Some(last) = body.last() {
last_line = stmt_line_number(last.as_ref()); 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"); 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<Module>;
Ptr::from_raw(ptr)
};
Ok(module)
} }
fn compile_stmt(&mut self, stmt: &StmtP) -> Result<()> { fn compile_stmt(&mut self, stmt: &StmtP) -> Result<()> {
@@ -673,21 +761,13 @@ impl Compiler {
chunk.code.push(op); chunk.code.push(op);
chunk.lines.push(line); chunk.lines.push(line);
} }
}
impl StmtVisitor for Compiler { /// Emit an assign statement based on the current scope - i.e. `Op::SetGlobal` if we're
fn visit_expr_stmt(&mut self, stmt: &ExprStmt) -> Result<()> { /// in global scope, or `Op::SetLocal` if we're in a function or local scope.
self.compile_expr(&stmt.expr)?; fn emit_assign(&mut self, line: LineRange, name: &str) -> Result<()> {
self.emit(stmt_line_number(stmt), Op::Pop);
Ok(())
}
fn visit_assign_stmt(&mut self, stmt: &AssignStmt) -> Result<()> {
let name = &stmt.lhs.text;
if self.is_global_scope() { if self.is_global_scope() {
let global = self.insert_global(name)?; let global = self.insert_global(name)?;
self.compile_expr(&stmt.rhs)?; self.emit(line, Op::SetGlobal(global));
self.emit(stmt_line_number(stmt), Op::SetGlobal(global));
} else { } else {
let mut declare = false; let mut declare = false;
let local = if let Some(local) = self.get_local(name) { let local = if let Some(local) = self.get_local(name) {
@@ -696,12 +776,93 @@ impl StmtVisitor for Compiler {
declare = true; declare = true;
self.insert_local(name.to_string())? self.insert_local(name.to_string())?
} }
.clone(); // gotta clone so we can borrow self as mutable for compile_expr .clone();
self.compile_expr(&stmt.rhs)?;
if !declare { 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 // 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 // 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(()) 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<()> { fn visit_binary_expr(&mut self, expr: &BinaryExpr) -> Result<()> {
static OP_NAMES: LazyLock<HashMap<TokenKind, &'static str>> = LazyLock::new(|| { static OP_NAMES: LazyLock<HashMap<TokenKind, &'static str>> = LazyLock::new(|| {
hash_map! { hash_map! {
@@ -1024,7 +1188,11 @@ impl ExprVisitor for Compiler {
// create the function // create the function
let chunk = self.chunks.pop().unwrap(); 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 // register the function as a constant
let fun_constant = self.insert_constant(fun)?; let fun_constant = self.insert_constant(fun)?;

View File

@@ -4,7 +4,7 @@ use crate::vm::{Chunk, JumpOpArg, Op};
type Row = (String, String, &'static str, String, String); type Row = (String, String, &'static str, String, String);
fn disassemble_chunk(chunk: &Chunk, constants: &Vec<ObjP>, globals: &Vec<String>) { fn disassemble_chunk(chunk: &Chunk, globals: &Vec<String>, constants: &Vec<ObjP>) {
let mut rows: Vec<Row> = vec![( let mut rows: Vec<Row> = vec![(
"ADDR".into(), "ADDR".into(),
"LINE".into(), "LINE".into(),
@@ -109,8 +109,13 @@ fn disassemble_chunk(chunk: &Chunk, constants: &Vec<ObjP>, globals: &Vec<String>
arg = String::new(); arg = String::new();
info = String::new(); info = String::new();
} }
Op::Halt => { Op::EnterModule => {
op_str = "HALT"; op_str = "ENTER_MODULE";
arg = String::new();
info = String::new();
}
Op::ExitModule => {
op_str = "EXIT_MODULE";
arg = String::new(); arg = String::new();
info = String::new(); info = String::new();
} }
@@ -150,10 +155,10 @@ fn display_rows(rows: &Vec<Row>) {
} }
} }
pub fn disassemble(chunk: &Chunk, constants: &Vec<ObjP>, globals: &Vec<String>) { pub fn disassemble(chunk: &Chunk, globals: &Vec<String>, constants: &Vec<ObjP>) {
println!("== main chunk"); println!("== main chunk");
println!(); println!();
disassemble_chunk(chunk, constants, globals); disassemble_chunk(chunk, globals, constants);
for constant in constants { for constant in constants {
if let Some(fun) = constant.borrow().as_any().downcast_ref::<UserFunction>() { if let Some(fun) = constant.borrow().as_any().downcast_ref::<UserFunction>() {
@@ -164,7 +169,7 @@ pub fn disassemble(chunk: &Chunk, constants: &Vec<ObjP>, globals: &Vec<String>)
fun.chunk().lines[0].0 fun.chunk().lines[0].0
); );
println!(); println!();
disassemble_chunk(fun.chunk(), constants, globals); disassemble_chunk(fun.chunk(), globals, constants);
} }
} }
} }

View File

@@ -7,13 +7,11 @@ mod parser;
mod token; mod token;
mod vm; mod vm;
use std::fmt;
use std::fs::File;
use std::io::prelude::*;
use std::path::PathBuf; use std::path::PathBuf;
use clap::Parser as ClapParser; use clap::Parser as ClapParser;
use thiserror::Error;
use crate::obj::upcast_obj;
#[derive(ClapParser, Debug)] #[derive(ClapParser, Debug)]
#[command(version, about, long_about = None)] #[command(version, about, long_about = None)]
@@ -24,43 +22,33 @@ struct Args {
path: PathBuf, 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<dyn std::error::Error>> { fn main() -> Result<(), Box<dyn std::error::Error>> {
let args = Args::parse(); 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 // initialize type system
obj::ty::init_types(); obj::ty::init_types();
crate::builtins::init_global_builtins(); crate::builtins::init_global_builtins();
// compile let absolute_path = std::path::absolute(&args.path).unwrap();
let (chunk, constants, globals) = compiler::Compiler::new().compile(&ast)?;
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 { if args.disassemble {
disassemble::disassemble(&chunk, &constants, &globals); disassemble::disassemble(
module.borrow().chunk(),
module.borrow().globals(),
&constants,
);
return Ok(()); return Ok(());
} }
// run // 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(); vm.run();
Ok(()) Ok(())

View File

@@ -7,6 +7,7 @@ pub mod float;
pub mod function; pub mod function;
pub mod int; pub mod int;
pub mod list; pub mod list;
pub mod module;
pub mod str; pub mod str;
pub mod ty; pub mod ty;
@@ -41,7 +42,9 @@ pub mod prelude {
pub use crate::obj::ObjP; pub use crate::obj::ObjP;
pub use crate::obj::function::{BuiltinFunction, Method, UserFunction}; 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}; pub use crate::obj::{Nil, Obj};
// Other auxiliary types and functions // Other auxiliary types and functions
@@ -226,7 +229,7 @@ impl Clone for BaseObj {
impl Display for BaseObj { impl Display for BaseObj {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "<BaseObj at {:x}>", (self as *const _ as usize)) write!(fmt, "<BaseObj at {:#x}>", (self as *const _ as usize))
} }
} }
@@ -430,7 +433,7 @@ impl Debug for Obj {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!( write!(
fmt, fmt,
"<{} at {:x}>", "<{} at {:#x}>",
self.ty_name(), self.ty_name(),
(self as *const _ as usize) (self as *const _ as usize)
) )

View File

@@ -138,6 +138,8 @@ impl Object for BuiltinFunction {
pub struct UserFunction { pub struct UserFunction {
base: BaseObj, base: BaseObj,
#[unsafe_ignore_trace] #[unsafe_ignore_trace]
path: Rc<String>,
#[unsafe_ignore_trace]
name: Rc<String>, name: Rc<String>,
#[unsafe_ignore_trace] #[unsafe_ignore_trace]
chunk: Rc<Chunk>, chunk: Rc<Chunk>,
@@ -146,9 +148,10 @@ pub struct UserFunction {
} }
impl UserFunction { impl UserFunction {
pub fn new(chunk: Chunk, arity: Argc) -> Self { pub fn new(path: impl ToString, chunk: Chunk, arity: Argc) -> Self {
Self { Self {
base: Default::default(), base: Default::default(),
path: Rc::new(path.to_string()),
name: Rc::new("(anonymous)".to_string()), name: Rc::new("(anonymous)".to_string()),
chunk: Rc::new(chunk), chunk: Rc::new(chunk),
arity, 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<String> {
&self.path
}
pub fn name(&self) -> &Rc<String> { pub fn name(&self) -> &Rc<String> {
&self.name &self.name

123
src/obj/module.rs Normal file
View File

@@ -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<String>,
#[unsafe_ignore_trace]
chunk: Rc<Chunk>,
globals: Vec<String>,
evaluated_value: Option<ObjP>,
}
impl Module {
pub fn new(path: impl ToString, chunk: Rc<Chunk>, globals: Vec<String>) -> Self {
Module {
base: Default::default(),
path: Rc::new(path.to_string()),
chunk,
globals,
evaluated_value: None,
}
}
pub fn path(&self) -> &Rc<String> {
&self.path
}
pub fn chunk(&self) -> &Rc<Chunk> {
&self.chunk
}
pub fn globals(&self) -> &Vec<String> {
&self.globals
}
pub fn evaluated_value(&self) -> &Option<ObjP> {
&self.evaluated_value
}
pub fn set_evaluated_value(&mut self, value: Option<ObjP>) {
self.evaluated_value = value;
}
impl_create!(path: impl ToString, chunk: Rc<Chunk>, globals: Vec<String>);
}
impl Debug for Module {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "<Module {}>", 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::<Module>() {
// 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!(),
}
}
}
*/

View File

@@ -186,6 +186,7 @@ pub fn init_types() {
}, },
Obj { Obj {
//__call__ => BuiltinFunction::create("__call__", //__call__ => BuiltinFunction::create("__call__",
// Methods
}, },
List { List {
// Conversion methods // Conversion methods
@@ -248,6 +249,7 @@ pub fn init_types() {
__le__ => BuiltinFunction::create("__le__", Int::le, 2), __le__ => BuiltinFunction::create("__le__", Int::le, 2),
__pos__ => BuiltinFunction::create("__pos__", Int::pos, 1), __pos__ => BuiltinFunction::create("__pos__", Int::pos, 1),
__neg__ => BuiltinFunction::create("__neg__", Int::neg, 1), __neg__ => BuiltinFunction::create("__neg__", Int::neg, 1),
// Methods
}, },
Float { Float {
// Conversion methods // Conversion methods
@@ -269,6 +271,7 @@ pub fn init_types() {
__le__ => BuiltinFunction::create("__le__", Float::le, 2), __le__ => BuiltinFunction::create("__le__", Float::le, 2),
__pos__ => BuiltinFunction::create("__pos__", Float::pos, 1), __pos__ => BuiltinFunction::create("__pos__", Float::pos, 1),
__neg__ => BuiltinFunction::create("__neg__", Float::neg, 1), __neg__ => BuiltinFunction::create("__neg__", Float::neg, 1),
// Methods
}, },
Bool { Bool {
// Conversion methods // Conversion methods
@@ -280,6 +283,7 @@ pub fn init_types() {
__init__ => BuiltinFunction::create("__init__", Bool::init, 2), __init__ => BuiltinFunction::create("__init__", Bool::init, 2),
// Operators // Operators
// Methods
}, },
Nil { Nil {
// Conversion methods // Conversion methods
@@ -289,9 +293,31 @@ pub fn init_types() {
__init__ => BuiltinFunction::create("__init__", Nil::init, 1), __init__ => BuiltinFunction::create("__init__", Nil::init, 1),
// Operators // 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 { },
} }
} }

View File

@@ -80,13 +80,8 @@ impl Lexer {
} }
pub fn lexeme(&self) -> &str { 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 { pub fn was_error(&self) -> bool {
self.was_error self.was_error
@@ -292,6 +287,8 @@ impl Lexer {
"true" => TokenKind::True, "true" => TokenKind::True,
"false" => TokenKind::False, "false" => TokenKind::False,
"nil" => TokenKind::Nil, "nil" => TokenKind::Nil,
"import" => TokenKind::Import,
"from" => TokenKind::From,
} }
}); });
@@ -553,6 +550,8 @@ impl Parser {
self.return_stmt() self.return_stmt()
} else if self.mat(TokenKind::If)? { } else if self.mat(TokenKind::If)? {
self.if_stmt() self.if_stmt()
} else if self.mat(TokenKind::Import)? {
self.import_stmt()
} else if self.mat(TokenKind::LBrace)? { } else if self.mat(TokenKind::LBrace)? {
let lbrace = self.prev.clone().unwrap(); let lbrace = self.prev.clone().unwrap();
let stmts = self.block()?; let stmts = self.block()?;
@@ -630,6 +629,81 @@ impl Parser {
})) }))
} }
fn import_stmt(&mut self) -> Result<StmtP> {
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<BlockStmt> { fn block_stmt(&mut self) -> Result<BlockStmt> {
let lbrace = self.prev.clone().unwrap(); let lbrace = self.prev.clone().unwrap();
assert_eq!(lbrace.kind, TokenKind::LBrace); assert_eq!(lbrace.kind, TokenKind::LBrace);
@@ -795,6 +869,11 @@ impl Parser {
Ok(Box::new(PrimaryExpr { Ok(Box::new(PrimaryExpr {
token: self.prev.clone().unwrap(), 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)? { } else if self.mat(TokenKind::LParen)? {
let expr: ExprP; let expr: ExprP;
// check if we're defining a function // check if we're defining a function

View File

@@ -7,6 +7,8 @@ pub enum TokenKind {
True, True,
False, False,
Nil, Nil,
Import,
From,
// Expressions // Expressions
Name, Name,

144
src/vm.rs
View File

@@ -34,7 +34,8 @@ pub enum Op {
// VM control // VM control
Nop, Nop,
Halt, EnterModule,
ExitModule,
} }
pub type LineRange = (usize, usize); pub type LineRange = (usize, usize);
@@ -68,6 +69,7 @@ pub struct Chunk {
#[derive(Debug)] #[derive(Debug)]
pub(crate) enum Function { pub(crate) enum Function {
Chunk(Rc<Chunk>), Chunk(Rc<Chunk>),
Module(Rc<Chunk>),
Builtin(BuiltinFunctionPtr, FunctionState), Builtin(BuiltinFunctionPtr, FunctionState),
} }
@@ -90,49 +92,23 @@ impl Frame {
} }
} }
pub struct Vm { pub struct Vm<'c> {
constants: Vec<ObjP>, constants: &'c Vec<ObjP>,
//global_names: Vec<String>, //global_names: Vec<String>,
globals: Vec<ObjP>, globals: Vec<Vec<ObjP>>,
stack: Vec<ObjP>, stack: Vec<ObjP>,
frames: Vec<Frame>, frames: Vec<Frame>,
} }
impl Vm { impl<'c> Vm<'c> {
/// Create a new virtual machine with the given chunk, constants, and global names. /// Create a new virtual machine with the given chunk, constants, and global names.
pub fn new(chunk: Rc<Chunk>, constants: Vec<ObjP>, global_names: Vec<String>) -> Self { pub fn new(constants: &'c Vec<ObjP>) -> 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,
)];
Vm { Vm {
constants, constants,
//global_names, //global_names,
globals, globals: Default::default(),
stack, stack: Default::default(),
frames, frames: Default::default(),
} }
} }
@@ -196,6 +172,24 @@ impl Vm {
self.frame_mut().ip = ip; self.frame_mut().ip = ip;
} }
/*
/// Gets the path of the current function, if it is user-defined.
pub fn path(&self) -> Option<&Rc<String>> {
if let Function::Chunk(path, _) = &self.frame().function {
Some(&path)
} else {
None
}
}
*/
pub fn globals(&self) -> &Vec<ObjP> {
self.globals.last().expect("no globals")
}
pub fn globals_mut(&mut self) -> &mut Vec<ObjP> {
self.globals.last_mut().expect("no globals")
}
/* /*
/// Gets the line of the current instruction. /// Gets the line of the current instruction.
fn line(&self, offset: isize) -> LineRange { 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. /// 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. /// This may actually end up calling a function on top of the stack, if it's a builtin function.
fn dispatch(&mut self) -> Op { fn dispatch(&mut self) -> Op {
let mut ip = self.ip(); let mut ip = self.ip();
let op = match &self.frame().function { let op = match &self.frame().function {
Function::Chunk(chunk) => { Function::Chunk(chunk) | Function::Module(chunk) => {
let op = chunk.code[ip]; let op = chunk.code[ip];
ip += 1; ip += 1;
op op
@@ -288,12 +311,12 @@ impl Vm {
self.stack[index] = value; self.stack[index] = value;
} }
Op::GetGlobal(global_index) => { 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); self.push(value);
} }
Op::SetGlobal(global_index) => { Op::SetGlobal(global_index) => {
let value = self.pop(); let value = self.pop();
self.globals[global_index as usize] = value; self.globals_mut()[global_index as usize] = value;
} }
Op::GetAttr(constant_id) => { Op::GetAttr(constant_id) => {
// need both declarations to borrow cell value // need both declarations to borrow cell value
@@ -407,9 +430,52 @@ impl Vm {
Op::Nop => { Op::Nop => {
continue; continue;
} }
Op::Halt => { 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; 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);
}
} }
} }
} }

View File

@@ -63,7 +63,7 @@ class GenerateStruct(Generator):
sb.line(f"impl {self.base} for {self.name}{self.base} " + "{") sb.line(f"impl {self.base} for {self.name}{self.base} " + "{")
sb.indent() sb.indent()
sb.line( sb.line(
f"fn accept(&self, visitor: &mut dyn {self.base}Visitor) -> Result<(), Box<dyn std::error::Error>> " f"fn accept(&self, visitor: &mut dyn {self.base}Visitor) -> Result<(), Error> "
+ "{" + "{"
) )
sb.indent() sb.indent()
@@ -81,7 +81,69 @@ class GenerateStruct(Generator):
return str(sb) return str(sb)
def generate_visitor(self) -> str: 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<dyn std::error::Error>>;" 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<Self>) -> Box<dyn Any> { 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): class GenerateGroup(Generator):
@@ -95,10 +157,15 @@ class GenerateGroup(Generator):
sb = SB() sb = SB()
sb.line(self.generate_visitor()) sb.line(self.generate_visitor())
sb.line()
sb.line(self.generate_members()) sb.line(self.generate_members())
return str(sb) 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: def add_struct(self, name: str, rules: list[str]) -> Self:
self.group += [GenerateStruct(self.name, name, rules)] self.group += [GenerateStruct(self.name, name, rules)]
return self return self
@@ -110,7 +177,7 @@ class GenerateGroup(Generator):
sb.line(f"pub trait {self.name}: Debug + Any " + "{") sb.line(f"pub trait {self.name}: Debug + Any " + "{")
sb.indent() sb.indent()
sb.line( sb.line(
f"fn accept(&self, visitor: &mut dyn {self.name}Visitor) -> Result<(), Box<dyn std::error::Error>>;" f"fn accept(&self, visitor: &mut dyn {self.name}Visitor) -> Result<(), Error>;"
) )
sb.line("fn as_any(self: Box<Self>) -> Box<dyn Any>;") sb.line("fn as_any(self: Box<Self>) -> Box<dyn Any>;")
sb.line("fn as_any_ref(&self) -> &dyn Any;") sb.line("fn as_any_ref(&self) -> &dyn Any;")
@@ -120,13 +187,14 @@ class GenerateGroup(Generator):
# Type # Type
sb.line(f"pub type {self.name}P = Box<dyn {self.name} + 'static>;") sb.line(f"pub type {self.name}P = Box<dyn {self.name} + 'static>;")
sb.line()
# All members and rules # All members and rules
for g in self.group: for g in self.group:
sb.line(g.generate_members()) sb.line(g.generate_members())
sb.line() sb.line()
return str(sb) return str(sb).strip()
def generate_visitor(self) -> str: def generate_visitor(self) -> str:
sb = SB() sb = SB()
@@ -139,7 +207,7 @@ class GenerateGroup(Generator):
sb.dedent() sb.dedent()
sb.line("}") sb.line("}")
return str(sb) return str(sb).strip()
GENERATE = [ GENERATE = [
@@ -164,7 +232,7 @@ GENERATE = [
.add_struct("List", ["lbracket: Token", "exprs: Vec<ExprP>", "rbracket: Token"]), .add_struct("List", ["lbracket: Token", "exprs: Vec<ExprP>", "rbracket: Token"]),
# Stmt # Stmt
GenerateGroup("Stmt") GenerateGroup("Stmt")
.add_struct("Import", ["import_kw: Token", "module: Token"]) .add_struct("Import", ["import_kw: Token", "what: Vec<Token>", "module: Token"])
.add_struct("Expr", ["expr: ExprP"]) .add_struct("Expr", ["expr: ExprP"])
.add_struct("Assign", ["lhs: Token", "rhs: ExprP"]) .add_struct("Assign", ["lhs: Token", "rhs: ExprP"])
.add_struct("Set", ["expr: ExprP", "name: Token", "rhs: ExprP"]) .add_struct("Set", ["expr: ExprP", "name: Token", "rhs: ExprP"])
@@ -206,6 +274,8 @@ def main():
sb.line() sb.line()
sb.line("use crate::token::Token;") sb.line("use crate::token::Token;")
sb.line() sb.line()
sb.line("type Error = Box<dyn std::error::Error>;")
sb.line()
for g in GENERATE: for g in GENERATE:
sb.line(g.generate()) sb.line(g.generate())