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 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<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>>;
}
type Error = Box<dyn std::error::Error>;
pub trait StmtVisitor {
fn visit_expr_stmt(&mut self, stmt: &ExprStmt) -> Result<(), Box<dyn std::error::Error>>;
fn visit_assign_stmt(&mut self, stmt: &AssignStmt) -> Result<(), Box<dyn std::error::Error>>;
fn visit_set_stmt(&mut self, stmt: &SetStmt) -> Result<(), Box<dyn std::error::Error>>;
fn visit_block_stmt(&mut self, stmt: &BlockStmt) -> Result<(), Box<dyn std::error::Error>>;
fn visit_return_stmt(&mut self, stmt: &ReturnStmt) -> Result<(), Box<dyn std::error::Error>>;
fn visit_if_stmt(&mut self, stmt: &IfStmt) -> Result<(), Box<dyn std::error::Error>>;
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<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_ref(&self) -> &dyn Any;
}
@@ -42,17 +35,12 @@ pub struct 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)
}
fn as_any(self: Box<Self>) -> Box<dyn Any> {
self
}
fn as_any_ref(&self) -> &dyn Any {
self
}
fn as_any(self: Box<Self>) -> Box<dyn Any> { 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<dyn std::error::Error>> {
fn accept(&self, visitor: &mut dyn ExprVisitor) -> Result<(), Error> {
visitor.visit_unary_expr(self)
}
fn as_any(self: Box<Self>) -> Box<dyn Any> {
self
}
fn as_any_ref(&self) -> &dyn Any {
self
}
fn as_any(self: Box<Self>) -> Box<dyn Any> { 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<dyn std::error::Error>> {
fn accept(&self, visitor: &mut dyn ExprVisitor) -> Result<(), Error> {
visitor.visit_call_expr(self)
}
fn as_any(self: Box<Self>) -> Box<dyn Any> {
self
}
fn as_any_ref(&self) -> &dyn Any {
self
}
fn as_any(self: Box<Self>) -> Box<dyn Any> { 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<dyn std::error::Error>> {
fn accept(&self, visitor: &mut dyn ExprVisitor) -> Result<(), Error> {
visitor.visit_get_expr(self)
}
fn as_any(self: Box<Self>) -> Box<dyn Any> {
self
}
fn as_any_ref(&self) -> &dyn Any {
self
}
fn as_any(self: Box<Self>) -> Box<dyn Any> { 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<dyn std::error::Error>> {
fn accept(&self, visitor: &mut dyn ExprVisitor) -> Result<(), Error> {
visitor.visit_index_expr(self)
}
fn as_any(self: Box<Self>) -> Box<dyn Any> {
self
}
fn as_any_ref(&self) -> &dyn Any {
self
}
fn as_any(self: Box<Self>) -> Box<dyn Any> { 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<dyn std::error::Error>> {
fn accept(&self, visitor: &mut dyn ExprVisitor) -> Result<(), Error> {
visitor.visit_primary_expr(self)
}
fn as_any(self: Box<Self>) -> Box<dyn Any> {
self
}
fn as_any_ref(&self) -> &dyn Any {
self
}
fn as_any(self: Box<Self>) -> Box<dyn Any> { self }
fn as_any_ref(&self) -> &dyn Any { self }
}
#[derive(Debug)]
pub struct FunctionExpr {
pub lparen: Token,
pub params: Vec<(Token , Option<ExprP>)>,
pub params: Vec<(Token, Option<ExprP>)>,
pub return_type: Option<ExprP>,
pub body: Vec<StmtP>,
pub rbrace: Token,
}
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)
}
fn as_any(self: Box<Self>) -> Box<dyn Any> {
self
}
fn as_any_ref(&self) -> &dyn Any {
self
}
fn as_any(self: Box<Self>) -> Box<dyn Any> { 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<dyn std::error::Error>> {
fn accept(&self, visitor: &mut dyn ExprVisitor) -> Result<(), Error> {
visitor.visit_list_expr(self)
}
fn as_any(self: Box<Self>) -> Box<dyn Any> {
self
}
fn as_any(self: Box<Self>) -> Box<dyn Any> { self }
fn as_any_ref(&self) -> &dyn Any { 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<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_ref(&self) -> &dyn Any;
}
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)]
pub struct ExprStmt {
pub expr: ExprP,
}
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)
}
fn as_any(self: Box<Self>) -> Box<dyn Any> {
self
}
fn as_any_ref(&self) -> &dyn Any {
self
}
fn as_any(self: Box<Self>) -> Box<dyn Any> { 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<dyn std::error::Error>> {
fn accept(&self, visitor: &mut dyn StmtVisitor) -> Result<(), Error> {
visitor.visit_assign_stmt(self)
}
fn as_any(self: Box<Self>) -> Box<dyn Any> {
self
}
fn as_any_ref(&self) -> &dyn Any {
self
}
fn as_any(self: Box<Self>) -> Box<dyn Any> { 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<dyn std::error::Error>> {
fn accept(&self, visitor: &mut dyn StmtVisitor) -> Result<(), Error> {
visitor.visit_set_stmt(self)
}
fn as_any(self: Box<Self>) -> Box<dyn Any> {
self
}
fn as_any_ref(&self) -> &dyn Any {
self
}
fn as_any(self: Box<Self>) -> Box<dyn Any> { 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<dyn std::error::Error>> {
fn accept(&self, visitor: &mut dyn StmtVisitor) -> Result<(), Error> {
visitor.visit_block_stmt(self)
}
fn as_any(self: Box<Self>) -> Box<dyn Any> {
self
}
fn as_any_ref(&self) -> &dyn Any {
self
}
fn as_any(self: Box<Self>) -> Box<dyn Any> { 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<dyn std::error::Error>> {
fn accept(&self, visitor: &mut dyn StmtVisitor) -> Result<(), Error> {
visitor.visit_return_stmt(self)
}
fn as_any(self: Box<Self>) -> Box<dyn Any> {
self
}
fn as_any_ref(&self) -> &dyn Any {
self
}
fn as_any(self: Box<Self>) -> Box<dyn Any> { 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<dyn std::error::Error>> {
fn accept(&self, visitor: &mut dyn StmtVisitor) -> Result<(), Error> {
visitor.visit_if_stmt(self)
}
fn as_any(self: Box<Self>) -> Box<dyn Any> {
self
}
fn as_any_ref(&self) -> &dyn Any {
self
}
fn as_any(self: Box<Self>) -> Box<dyn Any> { self }
fn as_any_ref(&self) -> &dyn Any { self }
}

View File

@@ -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<Chunk>,
scopes: Vec<Scope>,
constants: Vec<ObjP>,
constants: &'c mut Vec<ObjP>,
imported: &'c mut HashMap<String, Ptr<Module>>,
globals: Vec<String>,
}
impl Compiler {
pub fn new() -> Self {
impl<'c> Compiler<'c> {
pub fn new(
path: PathBuf,
constants: &'c mut Vec<ObjP>,
imported: &'c mut HashMap<String, Ptr<Module>>,
) -> 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<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.
///
/// This returns a tuple of `Chunk`, the constants table, and the list of globals.
pub fn compile(mut self, body: &Vec<StmtP>) -> Result<(Chunk, Vec<ObjP>, Vec<String>)> {
/// This returns the module of the compiled program.
pub fn compile(mut self, path: impl ToString, body: &Vec<StmtP>) -> Result<Ptr<Module>> {
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<Module>;
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<HashMap<TokenKind, &'static str>> = 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)?;

View File

@@ -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<ObjP>, globals: &Vec<String>) {
fn disassemble_chunk(chunk: &Chunk, globals: &Vec<String>, constants: &Vec<ObjP>) {
let mut rows: Vec<Row> = vec![(
"ADDR".into(),
"LINE".into(),
@@ -109,8 +109,13 @@ fn disassemble_chunk(chunk: &Chunk, constants: &Vec<ObjP>, globals: &Vec<String>
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<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!();
disassemble_chunk(chunk, constants, globals);
disassemble_chunk(chunk, globals, constants);
for constant in constants {
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
);
println!();
disassemble_chunk(fun.chunk(), constants, globals);
disassemble_chunk(fun.chunk(), globals, constants);
}
}
}

View File

@@ -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<dyn std::error::Error>> {
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(())

View File

@@ -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, "<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 {
write!(
fmt,
"<{} at {:x}>",
"<{} at {:#x}>",
self.ty_name(),
(self as *const _ as usize)
)

View File

@@ -138,6 +138,8 @@ impl Object for BuiltinFunction {
pub struct UserFunction {
base: BaseObj,
#[unsafe_ignore_trace]
path: Rc<String>,
#[unsafe_ignore_trace]
name: Rc<String>,
#[unsafe_ignore_trace]
chunk: Rc<Chunk>,
@@ -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<String> {
&self.path
}
pub fn name(&self) -> &Rc<String> {
&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 {
//__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 { },
}
}

View File

@@ -80,13 +80,8 @@ 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]
}
}
pub fn was_error(&self) -> bool {
self.was_error
@@ -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<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> {
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

View File

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

144
src/vm.rs
View File

@@ -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<Chunk>),
Module(Rc<Chunk>),
Builtin(BuiltinFunctionPtr, FunctionState),
}
@@ -90,49 +92,23 @@ impl Frame {
}
}
pub struct Vm {
constants: Vec<ObjP>,
pub struct Vm<'c> {
constants: &'c Vec<ObjP>,
//global_names: Vec<String>,
globals: Vec<ObjP>,
globals: Vec<Vec<ObjP>>,
stack: Vec<ObjP>,
frames: Vec<Frame>,
}
impl Vm {
impl<'c> Vm<'c> {
/// 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 {
// 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<ObjP>) -> 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<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.
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,9 +430,52 @@ impl Vm {
Op::Nop => {
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;
}
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.indent()
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()
@@ -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<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):
@@ -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<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_ref(&self) -> &dyn Any;")
@@ -120,13 +187,14 @@ class GenerateGroup(Generator):
# Type
sb.line(f"pub type {self.name}P = Box<dyn {self.name} + 'static>;")
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<ExprP>", "rbracket: Token"]),
# 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("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<dyn std::error::Error>;")
sb.line()
for g in GENERATE:
sb.line(g.generate())