Compare commits

..

No commits in common. "a3d4c24f119d2aaa5ba7b30b227ad0bf20cd55b0" and "4c061fbd28f113b281eca011b472269c38131c11" have entirely different histories.

17 changed files with 484 additions and 692 deletions

View file

@ -1,6 +0,0 @@
- The Export enum is stupid I think, for runtime modules. Why do we even have them? We should just put all the names in, like Declaration {} but for runtime.
- Module IDs must be globally resolvable from within a given semantics object
- When adding PANIC instructions, push a diagnostic that I find if I can find one instead of a hard-coded string.
- runtime should have `new` with 0 args and `with_loader<T : ModuleLoader>` that does the boxing, and `new` should just make the standard one

View file

@ -107,7 +107,7 @@ fn generate_test_for_file(path: PathBuf) -> String {
} else if let Some(line) = line.strip_prefix("@eval:") { } else if let Some(line) = line.strip_prefix("@eval:") {
let expected = line.trim(); let expected = line.trim();
assertions.push(quote! { assertions.push(quote! {
crate::assert_eval_ok(&program, _module.clone(), #expected); crate::assert_eval_ok(_module.clone(), #expected);
}); });
} else if let Some(line) = line.strip_prefix("@check-error:") { } else if let Some(line) = line.strip_prefix("@check-error:") {
let expected = line.trim(); let expected = line.trim();
@ -139,8 +139,8 @@ fn generate_test_for_file(path: PathBuf) -> String {
#disabled #disabled
fn #name() { fn #name() {
let source : std::rc::Rc<str> = #contents.into(); let source : std::rc::Rc<str> = #contents.into();
let mut program = crate::test_runtime(#display_path, source.clone()); let mut runtime = crate::test_runtime(#display_path, source.clone());
let (_errors, _module) = program.load_module("__test__").unwrap(); let (_errors, _module) = runtime.load_module("__test__").unwrap();
#(#assertions)* #(#assertions)*
} }

View file

@ -3,9 +3,7 @@ use std::rc::Rc;
use crate::{ use crate::{
parser::{Child, SyntaxTree, Tree, TreeKind, TreeRef}, parser::{Child, SyntaxTree, Tree, TreeKind, TreeRef},
semantics::{ semantics::{string_constant_to_string, Declaration, Location, Origin, Semantics, Type},
string_constant_to_string, Declaration, Location, ModuleId, Origin, Semantics, Type,
},
tokens::TokenKind, tokens::TokenKind,
}; };
@ -64,8 +62,6 @@ pub enum Instruction {
StoreSlot(usize), StoreSlot(usize),
StringAdd, StringAdd,
NewList(usize), NewList(usize),
ModulePrefix(ModuleId),
} }
pub enum Export { pub enum Export {
@ -73,31 +69,23 @@ pub enum Export {
Global(usize), Global(usize),
} }
pub struct CompiledModule { pub struct Module {
pub id: ModuleId,
pub functions: Vec<Rc<Function>>, // Functions pub functions: Vec<Rc<Function>>, // Functions
pub globals: usize, // The number of global variables pub globals: usize, // The number of global variables
pub exports: HashMap<String, Export>, // Exports by name pub exports: HashMap<String, Export>, // Exports by name
pub init: usize, // The index of the initialization function pub init: usize, // The index of the initialization function
pub deps: Vec<ModuleId>, // Modules I depend on
} }
impl CompiledModule { impl Module {
pub fn new(id: ModuleId) -> Self { pub fn new() -> Self {
CompiledModule { Module {
id,
functions: Vec::new(), functions: Vec::new(),
globals: 0, globals: 0,
exports: HashMap::new(), exports: HashMap::new(),
init: 0, init: 0,
deps: Vec::new(),
} }
} }
pub fn init_function(&self) -> &Rc<Function> {
&self.functions[self.init]
}
pub fn functions(&self) -> &[Rc<Function>] { pub fn functions(&self) -> &[Rc<Function>] {
&self.functions &self.functions
} }
@ -154,12 +142,21 @@ impl std::fmt::Debug for Function {
} }
} }
#[derive(Eq, PartialEq, Hash, Clone)]
struct FunctionKey {
tree: TreeRef,
}
struct Compiler<'a> { struct Compiler<'a> {
source: &'a str, source: &'a str,
semantics: &'a Semantics, semantics: &'a Semantics,
syntax: &'a SyntaxTree, syntax: &'a SyntaxTree,
module: CompiledModule, function_bindings: HashMap<FunctionKey, usize>,
pending_functions: Vec<(FunctionKey, usize, Function)>,
temp_functions: Vec<Option<Rc<Function>>>,
module: Module,
function: Function, function: Function,
} }
@ -252,35 +249,18 @@ macro_rules! ice {
}} }}
} }
type CR = Result<(), &'static str>; // macro_rules! inst_panic {
const OK: CR = CR::Ok(()); // ($($t:tt)+) => {{
// // eprintln!($($t)*);
// Instruction::Panic
// }};
// }
fn function_from_function_decl( // macro_rules! ice {
source: &str, // ($compiler:expr, $tr:expr, $($t:tt)*) => {{}};
syntax: &SyntaxTree, // }
tree: &Tree,
) -> Result<Function, &'static str> {
// TODO: If this is a method the name should be different.
let name = tree.nth_token(1).ok_or("no id")?.as_str(source);
let param_list = tree pub fn compile(semantics: &Semantics) -> Rc<Module> {
.child_tree_of_kind(syntax, TreeKind::ParamList)
.ok_or("no paramlist")?;
let param_count = param_list.children.len() - 2;
Ok(Function::new(name, param_count))
}
fn function_from_class_decl(source: &str, tree: &Tree) -> Result<Function, &'static str> {
let name = tree.nth_token(1).ok_or("no name")?.as_str(source);
// TODO: I think this is incorrect!
let field_count = tree.children.len() - 2;
Ok(Function::new(name, field_count))
}
pub fn compile_module(semantics: &Semantics) -> Rc<CompiledModule> {
let source = semantics.source(); let source = semantics.source();
let syntax_tree = semantics.tree(); let syntax_tree = semantics.tree();
@ -289,48 +269,35 @@ pub fn compile_module(semantics: &Semantics) -> Rc<CompiledModule> {
semantics: &semantics, semantics: &semantics,
syntax: &syntax_tree, syntax: &syntax_tree,
module: CompiledModule::new(semantics.mid()), function_bindings: HashMap::new(),
pending_functions: Vec::new(),
temp_functions: Vec::new(),
module: Module::new(),
function: Function::new("<< module >>", 0), function: Function::new("<< module >>", 0),
}; };
let mut functions = vec![None; semantics.function_count() + 1];
if let Some(t) = semantics.tree().root() { if let Some(t) = semantics.tree().root() {
compiler.temp_functions.push(None);
file(&mut compiler, t); file(&mut compiler, t);
let index = functions.len() - 1; compiler.temp_functions[0] = Some(Rc::new(compiler.function));
functions[index] = Some(Rc::new(compiler.function)); compiler.module.init = 0;
compiler.module.init = index;
} }
for t in semantics.tree().trees() { while let Some((fk, idx, func)) = compiler.pending_functions.pop() {
if let Some(function_index) = semantics.get_function_index(t) { if idx >= compiler.temp_functions.len() {
let tree = &semantics.tree()[t]; compiler.temp_functions.resize(idx + 1, None);
let function = match tree.kind {
TreeKind::FunctionDecl => function_from_function_decl(&source, &syntax_tree, tree),
TreeKind::ClassDecl => function_from_class_decl(&source, tree),
_ => Err("don't know how to make a function of this"),
};
if let Ok(function) = function {
compiler.function = function;
let _ = compile_function(&mut compiler, t);
let function = Rc::new(compiler.function);
compiler
.module
.exports
.insert(function.name.clone(), Export::Function(function_index));
functions[function_index] = Some(function);
}
} }
compiler.function = func;
let _ = compile_function(&mut compiler, fk.tree);
compiler.temp_functions[idx] = Some(Rc::new(compiler.function));
} }
let mut module = compiler.module; let mut module = compiler.module;
for f in functions { for f in compiler.temp_functions {
module.functions.push(f.unwrap()); module.functions.push(f.unwrap());
} }
module.deps.append(&mut semantics.import_ids());
Rc::new(module) Rc::new(module)
} }
@ -350,6 +317,9 @@ fn file(c: &mut Compiler, t: TreeRef) {
c.push(Instruction::Return); c.push(Instruction::Return);
} }
type CR = Result<(), &'static str>;
const OK: CR = CR::Ok(());
fn compile_expression(c: &mut Compiler, t: TreeRef) { fn compile_expression(c: &mut Compiler, t: TreeRef) {
let tree = &c.syntax[t]; let tree = &c.syntax[t];
let cr = match tree.kind { let cr = match tree.kind {
@ -663,6 +633,8 @@ fn compile_identifier_expression(c: &mut Compiler, t: TreeRef, tree: &Tree) -> C
} }
fn compile_load_declaration(c: &mut Compiler, t: TreeRef, declaration: &Declaration) -> CR { fn compile_load_declaration(c: &mut Compiler, t: TreeRef, declaration: &Declaration) -> CR {
// TODO: Handle load of non-local value.
let index = declaration.index; let index = declaration.index;
let instruction = match declaration.location { let instruction = match declaration.location {
Location::Local => { Location::Local => {
@ -676,12 +648,7 @@ fn compile_load_declaration(c: &mut Compiler, t: TreeRef, declaration: &Declarat
Instruction::LoadArgument(index) Instruction::LoadArgument(index)
} }
Location::Module => { Location::Module => {
if declaration.module != c.semantics.mid() {
// TODO: Assert here too?
c.push(Instruction::ModulePrefix(declaration.module));
} else {
compiler_assert!(c, t, index < c.module.globals); compiler_assert!(c, t, index < c.module.globals);
}
Instruction::LoadModule(index) Instruction::LoadModule(index)
} }
Location::Slot => { Location::Slot => {
@ -690,9 +657,32 @@ fn compile_load_declaration(c: &mut Compiler, t: TreeRef, declaration: &Declarat
} }
Location::Function => { Location::Function => {
if declaration.module != c.semantics.mid() { // TODO: Assert declaration is local
c.push(Instruction::ModulePrefix(declaration.module)); let Origin::Source(ft) = declaration.origin else {
ice!(c, t, "Function location but external origin?");
};
let key = FunctionKey { tree: ft };
let index = match c.function_bindings.get(&key) {
Some(index) => *index,
None => {
let tree = &c.syntax[ft];
compiler_assert_eq!(c, t, tree.kind, TreeKind::FunctionDecl);
compile_function_declaration(c, ft, tree, false)?;
match c.function_bindings.get(&key) {
Some(index) => *index,
None => {
ice!(
c,
t,
"did not compile the function with key {:?}!",
declaration
)
} }
}
}
};
Instruction::LoadFunction(index) Instruction::LoadFunction(index)
} }
@ -701,8 +691,8 @@ fn compile_load_declaration(c: &mut Compiler, t: TreeRef, declaration: &Declarat
// Must be a static don't worry about it. // Must be a static don't worry about it.
Location::Class => return OK, Location::Class => return OK,
// Imports are handled with an instruction prefix. // fix later
Location::Import => return OK, Location::Import => ice!(c, t, "import compile not supported"),
}; };
c.push(instruction); c.push(instruction);
@ -998,7 +988,24 @@ fn compile_new_object_expression(c: &mut Compiler, t: TreeRef, tree: &Tree) -> C
let declaration = environment.bind(identifier).ok_or("cannot bind type")?; let declaration = environment.bind(identifier).ok_or("cannot bind type")?;
match declaration.location { match declaration.location {
Location::Class => { Location::Class => {
c.push(Instruction::LoadFunction(declaration.index)); let Origin::Source(classdecl) = declaration.origin else {
ice!(c, t, "this class declaration has no source?");
};
let key = FunctionKey { tree: classdecl };
let index = match c.function_bindings.get(&key) {
Some(index) => *index,
None => {
let tree = &c.syntax[classdecl];
compiler_assert_eq!(c, t, tree.kind, TreeKind::ClassDecl);
compile_class_declaration(c, t, tree, false)?;
*c.function_bindings
.get(&key)
.expect("did not compile the class constructor!")
}
};
c.push(Instruction::LoadFunction(index));
} }
_ => return Err("unsupported type for construction"), _ => return Err("unsupported type for construction"),
} }
@ -1032,7 +1039,20 @@ fn compile_member_access(c: &mut Compiler, t: TreeRef, tree: &Tree) -> CR {
let typ = c.semantics.type_of(lhs); let typ = c.semantics.type_of(lhs);
let ident = tree.nth_token(2).ok_or("no ident")?.as_str(&c.source); let ident = tree.nth_token(2).ok_or("no ident")?.as_str(&c.source);
let environment = c.semantics.member_environment(t, &typ); let environment = match &typ {
Type::Object(mid, ct, _) => {
let class = c.semantics.class_of(*mid, *ct);
class.env.clone()
}
Type::Class(mid, ct, _) => {
let class = c.semantics.class_of(*mid, *ct);
class.static_env.clone()
}
_ => {
c.push_panic("cannot get environment of {typ}");
return Err("cannot get environment");
}
};
let declaration = environment.bind(ident).ok_or("cannot bind")?; let declaration = environment.bind(ident).ok_or("cannot bind")?;
// NOTE: If this is a method call we still don't have to do anything // NOTE: If this is a method call we still don't have to do anything
@ -1072,16 +1092,14 @@ fn compile_statement(c: &mut Compiler, t: TreeRef, gen_value: bool) {
TreeKind::Import => compile_import_statement(c, gen_value), TreeKind::Import => compile_import_statement(c, gen_value),
TreeKind::Block => compile_block_statement(c, t, gen_value), TreeKind::Block => compile_block_statement(c, t, gen_value),
TreeKind::ClassDecl => compile_class_declaration(c, gen_value), TreeKind::ClassDecl => compile_class_declaration(c, t, tree, gen_value),
TreeKind::ExpressionStatement => compile_expression_statement(c, tree, gen_value), TreeKind::ExpressionStatement => compile_expression_statement(c, tree, gen_value),
TreeKind::ForStatement => compile_for_statement(c, tree, gen_value), TreeKind::ForStatement => compile_for_statement(c, tree, gen_value),
TreeKind::FunctionDecl => compile_function_declaration(c, gen_value), TreeKind::FunctionDecl => compile_function_declaration(c, t, tree, gen_value),
TreeKind::IfStatement => compile_if_statement(c, tree, gen_value), TreeKind::IfStatement => compile_if_statement(c, tree, gen_value),
TreeKind::LetStatement => compile_let_statement(c, t, tree, gen_value), TreeKind::LetStatement => compile_let_statement(c, t, tree, gen_value),
TreeKind::ReturnStatement => compile_return_statement(c, tree), TreeKind::ReturnStatement => compile_return_statement(c, tree),
TreeKind::WhileStatement => compile_while_statement(c, tree, gen_value), TreeKind::WhileStatement => compile_while_statement(c, tree, gen_value),
TreeKind::Export => compile_export_statement(c, tree, gen_value),
TreeKind::ExportList => OK,
_ => ice!(c, t, "unsupported statement tree kind {:?}", tree.kind), _ => ice!(c, t, "unsupported statement tree kind {:?}", tree.kind),
}; };
@ -1094,11 +1112,6 @@ fn compile_statement(c: &mut Compiler, t: TreeRef, gen_value: bool) {
} }
} }
fn compile_export_statement(c: &mut Compiler, tree: &Tree, gen_value: bool) -> CR {
compile_statement(c, tree.nth_tree(1).ok_or("nothing to export")?, gen_value);
OK
}
fn compile_if_statement(c: &mut Compiler, tree: &Tree, gen_value: bool) -> CR { fn compile_if_statement(c: &mut Compiler, tree: &Tree, gen_value: bool) -> CR {
compile_expression(c, tree.nth_tree(0).ok_or("no expr")?); compile_expression(c, tree.nth_tree(0).ok_or("no expr")?);
if !gen_value { if !gen_value {
@ -1148,9 +1161,7 @@ fn compile_let_statement(c: &mut Compiler, t: TreeRef, tree: &Tree, gen_value: b
Instruction::StoreLocal(index) Instruction::StoreLocal(index)
} }
Location::Module => { Location::Module => {
if declaration.module != c.semantics.mid() { if index >= c.module.globals {
c.push(Instruction::ModulePrefix(declaration.module));
} else if index >= c.module.globals {
c.module.globals = index + 1; c.module.globals = index + 1;
} }
Instruction::StoreModule(index) Instruction::StoreModule(index)
@ -1165,7 +1176,32 @@ fn compile_let_statement(c: &mut Compiler, t: TreeRef, tree: &Tree, gen_value: b
OK OK
} }
fn compile_function_declaration(c: &mut Compiler, gen_value: bool) -> CR { fn compile_function_declaration(c: &mut Compiler, t: TreeRef, tree: &Tree, gen_value: bool) -> CR {
// Only compile a given function once.
//
// TODO: When it's time for generics, this should only actually compile
// if we have no unbound type variables.
let fk = FunctionKey { tree: t };
if !c.function_bindings.contains_key(&fk) {
// TODO: If this is a method the name should be different.
let name = tree.nth_token(1).ok_or("no id")?.as_str(&c.source);
let param_list = tree
.child_tree_of_kind(&c.syntax, TreeKind::ParamList)
.ok_or("no paramlist")?;
let param_count = param_list.children.len() - 2;
let function_index = c.temp_functions.len();
c.temp_functions.push(None);
c.pending_functions
.push((fk.clone(), function_index, Function::new(name, param_count)));
c.function_bindings.insert(fk, function_index);
c.module
.exports
.insert(name.to_string(), Export::Function(function_index));
}
if gen_value { if gen_value {
c.push(Instruction::PushNothing); c.push(Instruction::PushNothing);
} }
@ -1173,7 +1209,26 @@ fn compile_function_declaration(c: &mut Compiler, gen_value: bool) -> CR {
OK OK
} }
fn compile_class_declaration(c: &mut Compiler, gen_value: bool) -> CR { fn compile_class_declaration(c: &mut Compiler, t: TreeRef, tree: &Tree, gen_value: bool) -> CR {
// Only compile a given function once.
// Classes get compiled as constructor functions which get called.
let fk = FunctionKey { tree: t };
if !c.function_bindings.contains_key(&fk) {
let name = tree.nth_token(1).ok_or("no name")?.as_str(&c.source);
let field_count = tree.children.len() - 2;
let function_index = c.temp_functions.len();
c.temp_functions.push(None);
c.pending_functions
.push((fk.clone(), function_index, Function::new(name, field_count)));
c.function_bindings.insert(fk, function_index);
c.module
.exports
.insert(name.to_string(), Export::Function(function_index));
}
if gen_value { if gen_value {
c.push(Instruction::PushNothing); c.push(Instruction::PushNothing);
} }

View file

@ -1,51 +1,181 @@
use std::{path::PathBuf, rc::Rc}; use std::{collections::HashMap, fs, path::PathBuf, rc::Rc};
use compiler::compile_module; use compiler::compile;
use program::{Module, Program, StandardModuleLoader}; use parser::parse;
use semantics::{check, Error, ImportRecord, ModuleId, Semantics};
use vm::{eval, Context}; use vm::{eval, Context};
pub mod compiler; pub mod compiler;
pub mod parser; pub mod parser;
pub mod program;
pub mod semantics; pub mod semantics;
pub mod tokens; pub mod tokens;
pub mod vm; pub mod vm;
fn load_module( pub enum ModuleSource {
program: &Program, SourceText(String),
module: &Rc<Module>,
context: &mut Context,
) -> Result<(), vm::VMError> {
if !context.loaded(module.id()) {
let semantics = module.semantics();
let module = compile_module(&semantics);
context.set_module(module.clone());
for dep in module.deps.iter() {
if let Some(dep_mod) = program.get_module(dep) {
load_module(program, dep_mod, context)?;
}
}
eval(context, module.id, module.init, &[])?;
}
Ok(())
} }
pub fn compile_program(program: &Program, context: &mut Context) -> Result<(), vm::VMError> { #[derive(Debug)]
for module in program.modules() { pub enum ModuleLoadError {
load_module(program, module, context)?; IO(String, std::io::Error),
}
pub trait ModuleLoader {
fn normalize_module_name(&self, source: &str, name: String) -> String;
fn load_module(&self, name: &String) -> Result<ModuleSource, ModuleLoadError>;
}
pub struct StandardModuleLoader {
base_path: PathBuf,
}
impl StandardModuleLoader {
pub fn new(base_path: PathBuf) -> Self {
StandardModuleLoader { base_path }
}
}
impl ModuleLoader for StandardModuleLoader {
fn normalize_module_name(&self, source: &str, name: String) -> String {
let p = self.base_path.join(source).join(name.clone());
let result = match std::fs::canonicalize(&p) {
Ok(p) => match p.into_os_string().into_string() {
Ok(s) => s,
Err(_e) => name.clone(),
},
Err(_e) => name.clone(),
};
result
} }
Ok(()) fn load_module(&self, name: &String) -> Result<ModuleSource, ModuleLoadError> {
match fs::read_to_string(name) {
Ok(c) => Ok(ModuleSource::SourceText(c)),
Err(e) => Err(ModuleLoadError::IO(name.clone(), e)),
}
}
}
pub struct Module {
id: ModuleId,
semantics: Rc<Semantics>,
}
impl Module {
pub fn id(&self) -> ModuleId {
self.id
}
pub fn semantics(&self) -> Rc<Semantics> {
self.semantics.clone()
}
}
pub struct Runtime {
next_module_id: u64,
modules: HashMap<String, Rc<Module>>,
loader: Box<dyn ModuleLoader>,
}
impl Runtime {
pub fn new(loader: Box<dyn ModuleLoader>) -> Self {
Runtime {
next_module_id: 0,
modules: HashMap::new(),
loader,
}
}
pub fn load_module(
&mut self,
name: &str,
) -> Result<(Vec<Rc<Error>>, Rc<Module>), ModuleLoadError> {
let mut init_pending = HashMap::new();
let mut names = Vec::new();
let name = self.loader.normalize_module_name("", name.to_string());
names.push(name.clone());
let mut id_assign = self.next_module_id;
while let Some(name) = names.pop() {
if self.modules.contains_key(&name) || init_pending.contains_key(&name) {
// Either already loaded or pending load.
continue;
}
// TODO: Errors here are bad! Remember, run everything!
match self.loader.load_module(&name)? {
ModuleSource::SourceText(source) => {
let mid = ModuleId::from(id_assign);
id_assign += 1;
let source: Rc<str> = source.into();
let (tree, lines) = parse(&source);
let semantics = Rc::new(Semantics::new(
mid,
name.clone().into(),
source,
tree,
lines,
));
let mut normalized_imports = Vec::new();
for import in semantics.imports() {
let normalized = self.loader.normalize_module_name(&name, import.clone());
names.push(normalized.clone());
normalized_imports.push((import, normalized));
}
init_pending.insert(name, (mid, normalized_imports, semantics));
}
}
}
for (_, (_, imports, semantics)) in init_pending.iter() {
let mut import_table = HashMap::new();
for (import, normalized) in imports.iter() {
// NOTE: We look up the load(ed|ing) module here by normalized name, because that's how
// we track it...
let target = if let Some(module) = self.modules.get(&*normalized) {
ImportRecord {
name: normalized.clone(),
module_id: module.id(),
semantics: Rc::downgrade(&module.semantics),
}
} else {
let (module_id, _, semantics) = init_pending.get(&*normalized).unwrap();
ImportRecord {
name: normalized.clone(),
module_id: *module_id,
semantics: Rc::downgrade(semantics),
}
};
// ...but we set it into the import table here with the name
// that the source code used, for more better binding.
import_table.insert(import.clone(), target);
}
semantics.set_imports(import_table);
}
let mut errors = Vec::new();
for (name, (id, _, semantics)) in init_pending.into_iter() {
check(&semantics);
errors.append(&mut semantics.snapshot_errors());
self.modules.insert(name, Rc::new(Module { id, semantics }));
}
self.next_module_id = id_assign;
let result = self.modules.get(&name).unwrap().clone();
Ok((errors, result))
}
} }
pub fn process_file(file: &str) { pub fn process_file(file: &str) {
let mut program = Program::new(Box::new(StandardModuleLoader::new(PathBuf::from(".")))); let mut runtime = Runtime::new(Box::new(StandardModuleLoader::new(PathBuf::from("."))));
// Load the program text let (errors, module) = match runtime.load_module(file) {
let (errors, _) = match program.load_module(file) {
Ok(r) => r, Ok(r) => r,
Err(_) => { Err(_) => {
eprintln!("Error loading module"); eprintln!("Error loading module");
@ -53,7 +183,7 @@ pub fn process_file(file: &str) {
} }
}; };
// OK now there might be semantic errors or whatnot. // OK now there might be errors.
if errors.len() > 0 { if errors.len() > 0 {
for e in errors { for e in errors {
eprintln!("{file}: {}:{}: {}", e.start.0, e.start.1, e.message); eprintln!("{file}: {}:{}: {}", e.start.0, e.start.1, e.message);
@ -61,10 +191,16 @@ pub fn process_file(file: &str) {
return; return;
} }
// This is weird, why run the init function as main? Maybe just run main? // shrug
let mut context = Context::new(); let semantics = module.semantics();
match compile_program(&program, &mut context) { let module = compile(&semantics);
Ok(_) => {} let main_function = module.functions[module.init].clone();
let mut context = Context::new(module.clone());
match eval(&mut context, main_function, vec![]) {
Ok(v) => {
println!("{:?}", v);
}
Err(e) => { Err(e) => {
eprintln!("{:?}", e); eprintln!("{:?}", e);
} }

View file

@ -1,196 +0,0 @@
use std::{collections::HashMap, fs, path::PathBuf, rc::Rc};
use crate::parser::parse;
use crate::semantics::{check, Error, ImportRecord, ModuleId, Semantics};
pub enum ModuleSource {
SourceText(String),
}
#[derive(Debug)]
pub enum ModuleLoadError {
IO(String, std::io::Error),
}
pub trait ModuleLoader {
fn normalize_module_name(&self, source: &str, name: String) -> String;
fn load_module(&self, name: &String) -> Result<ModuleSource, ModuleLoadError>;
}
pub struct StandardModuleLoader {
base_path: PathBuf,
}
impl StandardModuleLoader {
pub fn new(base_path: PathBuf) -> Self {
StandardModuleLoader { base_path }
}
}
impl ModuleLoader for StandardModuleLoader {
fn normalize_module_name(&self, source: &str, name: String) -> String {
let p = self.base_path.join(source).join(name.clone());
let result = match std::fs::canonicalize(&p) {
Ok(p) => match p.into_os_string().into_string() {
Ok(s) => s,
Err(_e) => name.clone(),
},
Err(_e) => name.clone(),
};
result
}
fn load_module(&self, name: &String) -> Result<ModuleSource, ModuleLoadError> {
match fs::read_to_string(name) {
Ok(c) => Ok(ModuleSource::SourceText(c)),
Err(e) => Err(ModuleLoadError::IO(name.clone(), e)),
}
}
}
pub struct Module {
id: ModuleId,
semantics: Rc<Semantics>,
}
impl Module {
pub fn id(&self) -> ModuleId {
self.id
}
pub fn semantics(&self) -> Rc<Semantics> {
self.semantics.clone()
}
}
struct PendingModule {
mid: ModuleId,
imports: Vec<(String, String)>, // (raw, normalized)
semantics: Rc<Semantics>,
}
pub struct Program {
next_module_id: u64,
modules: HashMap<String, Rc<Module>>,
modules_by_id: HashMap<ModuleId, Rc<Module>>,
loader: Box<dyn ModuleLoader>,
}
impl Program {
pub fn new(loader: Box<dyn ModuleLoader>) -> Self {
Program {
next_module_id: 0,
modules: HashMap::new(),
modules_by_id: HashMap::new(),
loader,
}
}
pub fn modules(&self) -> impl Iterator<Item = &Rc<Module>> {
self.modules.values()
}
pub fn get_module(&self, id: &ModuleId) -> Option<&Rc<Module>> {
self.modules_by_id.get(id)
}
pub fn load_module(
&mut self,
name: &str,
) -> Result<(Vec<Rc<Error>>, Rc<Module>), ModuleLoadError> {
let mut init_pending = HashMap::new();
let mut names = Vec::new();
let name = self.loader.normalize_module_name("", name.to_string());
names.push(name.clone());
let mut id_assign = self.next_module_id;
while let Some(name) = names.pop() {
if self.modules.contains_key(&name) || init_pending.contains_key(&name) {
// Either already loaded or pending load.
continue;
}
// TODO: Errors here are bad! Remember, run everything!
match self.loader.load_module(&name)? {
ModuleSource::SourceText(source) => {
let mid = ModuleId::from(id_assign);
id_assign += 1;
let source: Rc<str> = source.into();
let (tree, lines) = parse(&source);
let semantics = Rc::new(Semantics::new(
mid,
name.clone().into(),
source,
tree,
lines,
));
let mut imports = Vec::new();
for import in semantics.imports() {
let normalized = self.loader.normalize_module_name(&name, import.clone());
names.push(normalized.clone());
imports.push((import, normalized));
}
init_pending.insert(
name,
PendingModule {
semantics,
mid,
imports,
},
);
}
}
}
for (_, pending) in init_pending.iter() {
let mut import_table = HashMap::new();
for (import, normalized) in pending.imports.iter() {
// NOTE: We look up the load(ed|ing) module here by normalized name, because that's how
// we track it...
let target = if let Some(module) = self.modules.get(&*normalized) {
ImportRecord {
name: normalized.clone(),
module_id: module.id(),
semantics: Rc::downgrade(&module.semantics),
}
} else {
let other = init_pending.get(&*normalized).unwrap();
ImportRecord {
name: normalized.clone(),
module_id: other.mid,
semantics: Rc::downgrade(&other.semantics),
}
};
// ...but we set it into the import table here with the name
// that the source code used, for more better binding.
import_table.insert(import.clone(), target);
}
// Now tell the semantics object about its import table.
pending.semantics.set_imports(import_table);
}
let mut errors = Vec::new();
for (name, pending) in init_pending.into_iter() {
check(&pending.semantics);
errors.append(&mut pending.semantics.snapshot_errors());
let module = Rc::new(Module {
id: pending.mid,
semantics: pending.semantics,
});
self.modules.insert(name, module.clone());
self.modules_by_id.insert(pending.mid, module);
}
self.next_module_id = id_assign;
let result = self.modules.get(&name).unwrap().clone();
Ok((errors, result))
}
}

View file

@ -116,12 +116,6 @@ impl From<u64> for ModuleId {
} }
} }
impl fmt::Display for ModuleId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "#{}", self.0)
}
}
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct ImportRecord { pub struct ImportRecord {
pub name: String, pub name: String,
@ -350,9 +344,9 @@ pub enum Location {
Local, // A local in an frame Local, // A local in an frame
Slot, // A slot in an object Slot, // A slot in an object
Module, // A global in a module Module, // A global in a module
Function, // A function in a module Function, // A function in a module (index unrelated)
ExternalFunction, // An external function (module unrelated) ExternalFunction, // An external function (module unrelated)
Class, // A class in a module Class, // A class in a module (index unrelated)
Import, // An import in a module (index unrelated) Import, // An import in a module (index unrelated)
} }
@ -689,9 +683,6 @@ pub struct Semantics {
// This is what is used for binding. // This is what is used for binding.
logical_parents: Vec<Option<TreeRef>>, logical_parents: Vec<Option<TreeRef>>,
function_count: usize,
function_indices: Vec<Option<usize>>,
// TODO: State should be externalized instead of this refcell nonsense. // TODO: State should be externalized instead of this refcell nonsense.
errors: RefCell<Vec<Rc<Error>>>, errors: RefCell<Vec<Rc<Error>>>,
types: RefCell<Vec<Incremental<Type>>>, types: RefCell<Vec<Incremental<Type>>>,
@ -715,19 +706,6 @@ impl Semantics {
let root_environment = Environment::new(mid, None, Location::Module); let root_environment = Environment::new(mid, None, Location::Module);
let mut function_count = 0;
let mut function_indices = vec![None; tree.len()];
for t in tree.trees() {
let tree = &tree[t];
match tree.kind {
TreeKind::FunctionDecl | TreeKind::ClassDecl => {
function_indices[t.index()] = Some(function_count);
function_count += 1;
}
_ => {}
}
}
let mut semantics = Semantics { let mut semantics = Semantics {
mid, mid,
file, file,
@ -736,8 +714,6 @@ impl Semantics {
lines, lines,
import_map: OnceCell::new(), import_map: OnceCell::new(),
logical_parents, logical_parents,
function_count,
function_indices,
errors: RefCell::new(vec![]), errors: RefCell::new(vec![]),
types: RefCell::new(vec![Incremental::None; tree.len()]), types: RefCell::new(vec![Incremental::None; tree.len()]),
environments: RefCell::new(vec![Incremental::None; tree.len()]), environments: RefCell::new(vec![Incremental::None; tree.len()]),
@ -768,12 +744,6 @@ impl Semantics {
self.import_map.set(imports).expect("imports already set"); self.import_map.set(imports).expect("imports already set");
} }
pub fn import_ids(&self) -> Vec<ModuleId> {
// TODO: Pull from by_name when we go global
let import_map = self.import_map.get().unwrap();
import_map.by_id.keys().map(|id| *id).collect()
}
pub fn import_by_id(&self, mid: ModuleId) -> Option<Rc<Semantics>> { pub fn import_by_id(&self, mid: ModuleId) -> Option<Rc<Semantics>> {
// TODO: ACTUALLY THIS IS WRONG, WE NEED THE GLOBAL MAP HERE, NOT THE LOCAL ONE. // TODO: ACTUALLY THIS IS WRONG, WE NEED THE GLOBAL MAP HERE, NOT THE LOCAL ONE.
let import_map = self.import_map.get()?; let import_map = self.import_map.get()?;
@ -827,10 +797,6 @@ impl Semantics {
} }
} }
pub fn mid(&self) -> ModuleId {
self.mid
}
fn report_error_span<T>(&self, start_pos: usize, end_pos: usize, error: T) -> Rc<Error> fn report_error_span<T>(&self, start_pos: usize, end_pos: usize, error: T) -> Rc<Error>
where where
T: ToString, T: ToString,
@ -926,27 +892,6 @@ impl Semantics {
Environment::error(error) Environment::error(error)
} }
pub fn function_count(&self) -> usize {
self.function_count
}
pub fn get_function_index(&self, t: TreeRef) -> Option<usize> {
let index = t.index();
if index >= self.function_indices.len() {
None
} else {
self.function_indices[t.index()]
}
}
pub fn function_index_of(&self, t: TreeRef) -> usize {
let Some(index) = self.function_indices[t.index()] else {
self.internal_compiler_error(Some(t), "Why didn't I get a function index for this?");
};
index
}
pub fn environment_of(&self, t: TreeRef) -> EnvironmentRef { pub fn environment_of(&self, t: TreeRef) -> EnvironmentRef {
{ {
// I want to make sure that this borrow is dropped after this block. // I want to make sure that this borrow is dropped after this block.
@ -1007,7 +952,7 @@ impl Semantics {
name.as_str(&self.source).into(), name.as_str(&self.source).into(),
Declaration { Declaration {
location: Location::Function, location: Location::Function,
index: self.function_index_of(*t), index: 0,
module: self.mid, module: self.mid,
origin: Origin::Source(*t), origin: Origin::Source(*t),
exported: false, exported: false,
@ -1062,7 +1007,7 @@ impl Semantics {
let declaration = Declaration { let declaration = Declaration {
location: Location::Function, location: Location::Function,
index: self.function_index_of(t), index: 0,
module: self.mid, module: self.mid,
origin: Origin::Source(t), origin: Origin::Source(t),
exported, exported,
@ -1079,7 +1024,7 @@ impl Semantics {
let declaration = Declaration { let declaration = Declaration {
location: Location::Class, location: Location::Class,
index: self.function_index_of(t), index: 0,
module: self.mid, module: self.mid,
origin: Origin::Source(t), origin: Origin::Source(t),
exported, exported,
@ -1448,7 +1393,7 @@ impl Semantics {
(&*method.name).into(), (&*method.name).into(),
Declaration { Declaration {
location: Location::Function, location: Location::Function,
index: self.function_index_of(method.declaration), index: 0,
module: self.mid, module: self.mid,
origin: Origin::Source(method.declaration), origin: Origin::Source(method.declaration),
exported: false, exported: false,

View file

@ -1,13 +1,11 @@
use core::fmt;
use std::cell::{Cell, RefCell}; use std::cell::{Cell, RefCell};
use std::collections::HashMap;
use std::rc::Rc; use std::rc::Rc;
use crate::compiler::{ use crate::compiler::{
CompiledModule, Export, Function, Instruction, EXTERN_BUILTIN_LIST_GET_ITERATOR, Export, Function, Instruction, Module, EXTERN_BUILTIN_LIST_GET_ITERATOR,
EXTERN_BUILTIN_LIST_ITERATOR_NEXT, EXTERN_BUILTIN_NOOP, EXTERN_BUILTIN_LIST_ITERATOR_NEXT, EXTERN_BUILTIN_NOOP,
}; };
use crate::semantics::{ModuleId, Type}; use crate::semantics::Type;
use thiserror::Error; use thiserror::Error;
#[derive(Error, Debug)] #[derive(Error, Debug)]
@ -46,13 +44,6 @@ pub enum VMErrorCode {
SlotOutOfRange(usize, Rc<str>, usize), SlotOutOfRange(usize, Rc<str>, usize),
#[error("internal error: the extern function with ID {0} was not registered")] #[error("internal error: the extern function with ID {0} was not registered")]
UnregisteredExternFunction(usize), UnregisteredExternFunction(usize),
#[error("the requested export was not found: {0}")]
ExportNotFound(String),
#[error("the requested export is not a function: {0}")]
ExportNotFunction(String),
#[error("a module with id {0} has not been loaded")]
ModuleNotFound(ModuleId),
} }
#[derive(Debug)] #[derive(Debug)]
@ -89,21 +80,6 @@ pub struct ListIterator {
index: Cell<usize>, index: Cell<usize>,
} }
#[derive(Clone)]
pub enum FuncValue {
Function(Rc<RuntimeModule>, Rc<Function>),
ExternFunction(usize),
}
impl fmt::Debug for FuncValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
FuncValue::Function(m, func) => write!(f, "fuction #{:?}:{:?}", m, func),
FuncValue::ExternFunction(i) => write!(f, "external {i}"),
}
}
}
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum StackValue { pub enum StackValue {
Nothing, Nothing,
@ -111,7 +87,8 @@ pub enum StackValue {
Float(f64), Float(f64),
Int(i64), Int(i64),
String(Rc<str>), String(Rc<str>),
Function(Box<FuncValue>), Function(Rc<Function>),
ExternFunction(usize),
Object(Rc<Object>), Object(Rc<Object>),
List(Rc<Vec<StackValue>>), List(Rc<Vec<StackValue>>),
ListIterator(Rc<ListIterator>), ListIterator(Rc<ListIterator>),
@ -154,9 +131,13 @@ impl StackValue {
} }
} }
enum FuncValue {
Function(Rc<Function>),
ExternFunction(usize),
}
#[derive(Debug)] #[derive(Debug)]
pub struct Frame { pub struct Frame {
module: Rc<RuntimeModule>,
func: Rc<Function>, func: Rc<Function>,
args: Vec<StackValue>, args: Vec<StackValue>,
locals: Vec<StackValue>, locals: Vec<StackValue>,
@ -165,12 +146,11 @@ pub struct Frame {
} }
impl Frame { impl Frame {
fn from_function(module: Rc<RuntimeModule>, func: Rc<Function>, args: Vec<StackValue>) -> Self { fn from_function(func: Rc<Function>, args: Vec<StackValue>) -> Self {
let mut locals = Vec::new(); let mut locals = Vec::new();
locals.resize(func.locals(), StackValue::Nothing); locals.resize(func.locals(), StackValue::Nothing);
Frame { Frame {
module,
func, func,
args, args,
locals, locals,
@ -179,12 +159,8 @@ impl Frame {
} }
} }
pub fn module(&self) -> &Rc<RuntimeModule> { pub fn func(&self) -> Rc<Function> {
&self.module self.func.clone()
}
pub fn func(&self) -> &Rc<Function> {
&self.func
} }
pub fn args(&self) -> &[StackValue] { pub fn args(&self) -> &[StackValue] {
@ -254,12 +230,12 @@ impl Frame {
self.push_value(StackValue::String(v)) self.push_value(StackValue::String(v))
} }
fn push_function(&mut self, m: Rc<RuntimeModule>, v: Rc<Function>) { fn push_function(&mut self, v: Rc<Function>) {
self.push_value(StackValue::Function(Box::new(FuncValue::Function(m, v)))); self.push_value(StackValue::Function(v));
} }
fn push_extern_function(&mut self, v: usize) { fn push_extern_function(&mut self, v: usize) {
self.push_value(StackValue::Function(Box::new(FuncValue::ExternFunction(v)))); self.push_value(StackValue::ExternFunction(v));
} }
fn push_object(&mut self, v: Rc<Object>) { fn push_object(&mut self, v: Rc<Object>) {
@ -316,86 +292,55 @@ impl Frame {
fn pop_function(&mut self) -> Result<FuncValue> { fn pop_function(&mut self) -> Result<FuncValue> {
match self.pop_value()? { match self.pop_value()? {
StackValue::Function(f) => Ok(*f), StackValue::Function(f) => Ok(FuncValue::Function(f)),
StackValue::ExternFunction(i) => Ok(FuncValue::ExternFunction(i)),
v => Err(VMErrorCode::StackExpectedFunction(v)), v => Err(VMErrorCode::StackExpectedFunction(v)),
} }
} }
} }
pub struct RuntimeModule { pub struct Context {
code: Rc<CompiledModule>, module: Rc<Module>,
globals: RefCell<Vec<StackValue>>, globals: Vec<StackValue>,
} }
impl fmt::Debug for RuntimeModule { impl Context {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { pub fn new(module: Rc<Module>) -> Context {
write!(f, "#{}", self.code.id)
}
}
impl RuntimeModule {
fn new(code: Rc<CompiledModule>) -> RuntimeModule {
let mut globals = Vec::new(); let mut globals = Vec::new();
globals.resize(code.globals, StackValue::Nothing); globals.resize(module.globals, StackValue::Nothing);
RuntimeModule { Context { module, globals }
code,
globals: RefCell::new(globals),
} }
pub fn init(&mut self) -> std::result::Result<(), VMError> {
let init_index = self.module.init;
let init_fn = self.module.functions[init_index].clone();
eval(self, init_fn, vec![])?;
Ok(())
} }
fn get_global(&self, i: usize) -> Result<StackValue> { fn get_global(&self, i: usize) -> Result<StackValue> {
self.globals self.globals
.borrow()
.get(i) .get(i)
.map(|v| v.clone()) .map(|v| v.clone())
.ok_or_else(|| VMErrorCode::GlobalOutOfRange(i)) // TODO: Test .ok_or_else(|| VMErrorCode::GlobalOutOfRange(i)) // TODO: Test
} }
fn set_global(&self, i: usize, v: StackValue) -> Result<()> { fn set_global(&mut self, i: usize, v: StackValue) -> Result<()> {
let mut globals = self.globals.borrow_mut(); if i >= self.globals.len() {
if i >= globals.len() {
Err(VMErrorCode::GlobalOutOfRange(i)) // TODO: Test Err(VMErrorCode::GlobalOutOfRange(i)) // TODO: Test
} else { } else {
globals[i] = v; self.globals[i] = v;
Ok(()) Ok(())
} }
} }
fn get_function(&self, i: usize) -> Result<Rc<Function>> { fn get_function(&self, i: usize) -> Result<Rc<Function>> {
let functions = self.code.functions(); let functions = self.module.functions();
functions functions
.get(i) .get(i)
.map(|v| v.clone()) .map(|v| v.clone())
.ok_or_else(|| VMErrorCode::FunctionOutOfRange(i)) // TODO: Test .ok_or_else(|| VMErrorCode::FunctionOutOfRange(i)) // TODO: Test
} }
}
pub struct Context {
modules: HashMap<ModuleId, Rc<RuntimeModule>>,
module_prefix: Option<ModuleId>,
}
impl Context {
pub fn new() -> Context {
Context {
modules: HashMap::new(),
module_prefix: None,
}
}
pub fn loaded(&self, module: ModuleId) -> bool {
self.modules.contains_key(&module)
}
pub fn get_module(&self, module: ModuleId) -> Option<&Rc<CompiledModule>> {
self.modules.get(&module).map(|rm| &rm.code)
}
pub fn set_module(&mut self, module: Rc<CompiledModule>) {
let id = module.id;
let runtime_module = Rc::new(RuntimeModule::new(module));
self.modules.insert(id, runtime_module.clone());
}
fn call_extern_function(&self, index: usize, args: &[StackValue]) -> Result<StackValue> { fn call_extern_function(&self, index: usize, args: &[StackValue]) -> Result<StackValue> {
match index { match index {
@ -448,8 +393,6 @@ fn eval_one(
f: &mut Frame, f: &mut Frame,
stack: &mut Vec<Frame>, stack: &mut Vec<Frame>,
) -> Result<Flow> { ) -> Result<Flow> {
let module_prefix = c.module_prefix.take();
match instruction { match instruction {
Instruction::Panic(index) => { Instruction::Panic(index) => {
let v = f let v = f
@ -514,15 +457,7 @@ fn eval_one(
f.push_value(v); f.push_value(v);
} }
Instruction::LoadModule(i) => { Instruction::LoadModule(i) => {
let module = module_prefix let v = c.get_global(i)?;
.map(|id| {
c.modules
.get(&id)
.ok_or_else(|| VMErrorCode::ModuleNotFound(id))
})
.unwrap_or(Ok(&f.module))?;
let v = module.get_global(i)?;
f.push_value(v); f.push_value(v);
} }
Instruction::PushFalse => { Instruction::PushFalse => {
@ -551,15 +486,7 @@ fn eval_one(
} }
Instruction::StoreModule(i) => { Instruction::StoreModule(i) => {
let v = f.pop_value()?; let v = f.pop_value()?;
let module = module_prefix c.set_global(i, v)?;
.map(|id| {
c.modules
.get(&id)
.ok_or_else(|| VMErrorCode::ModuleNotFound(id))
})
.unwrap_or(Ok(&f.module))?;
module.set_global(i, v)?;
} }
Instruction::StoreSlot(i) => { Instruction::StoreSlot(i) => {
let o = f.pop_object()?; let o = f.pop_object()?;
@ -567,16 +494,8 @@ fn eval_one(
o.values.borrow_mut()[i] = v; o.values.borrow_mut()[i] = v;
} }
Instruction::LoadFunction(i) => { Instruction::LoadFunction(i) => {
let module = module_prefix let v = c.get_function(i)?;
.map(|id| { f.push_function(v);
c.modules
.get(&id)
.ok_or_else(|| VMErrorCode::ModuleNotFound(id))
})
.unwrap_or(Ok(&f.module))?;
let v = module.get_function(i)?;
f.push_function(module.clone(), v);
} }
Instruction::LoadExternFunction(i) => { Instruction::LoadExternFunction(i) => {
f.push_extern_function(i); f.push_extern_function(i);
@ -588,8 +507,8 @@ fn eval_one(
args.push(f.pop_value()?); args.push(f.pop_value()?);
} }
match function { match function {
FuncValue::Function(module, func) => { FuncValue::Function(func) => {
let mut frame = Frame::from_function(module, func, args); let mut frame = Frame::from_function(func, args);
std::mem::swap(&mut frame, f); std::mem::swap(&mut frame, f);
frame.pc = *index; frame.pc = *index;
stack.push(frame); stack.push(frame);
@ -614,8 +533,8 @@ fn eval_one(
let x = f.pop_string()?; let x = f.pop_string()?;
let y = f.pop_string()?; let y = f.pop_string()?;
let mut new_string = y.to_string(); let mut new_string = x.to_string();
new_string.push_str(&x); new_string.push_str(&y);
f.push_string(new_string.into()); f.push_string(new_string.into());
} }
@ -705,22 +624,18 @@ fn eval_one(
} }
f.push_list(Rc::new(v)); f.push_list(Rc::new(v));
} }
Instruction::ModulePrefix(mid) => {
c.module_prefix = Some(mid);
}
} }
Ok(Flow::Continue) Ok(Flow::Continue)
} }
fn eval_core( pub fn eval(
c: &mut Context, c: &mut Context,
module: Rc<RuntimeModule>,
function: Rc<Function>, function: Rc<Function>,
args: Vec<StackValue>, args: Vec<StackValue>,
) -> std::result::Result<StackValue, VMError> { ) -> std::result::Result<StackValue, VMError> {
let mut stack = Vec::new(); let mut stack = Vec::new();
let mut f = Frame::from_function(module, function, args); let mut f = Frame::from_function(function, args);
let mut index = 0; let mut index = 0;
loop { loop {
@ -765,61 +680,18 @@ fn eval_core(
} }
} }
pub fn eval(
c: &mut Context,
module: ModuleId,
function: usize,
args: &[StackValue],
) -> std::result::Result<StackValue, VMError> {
let Some(module) = c.modules.get(&module) else {
return Err(VMError {
code: VMErrorCode::ModuleNotFound(module),
stack: Box::new([]),
});
};
let Some(function) = module.code.functions.get(function) else {
return Err(VMError {
code: VMErrorCode::FunctionOutOfRange(function),
stack: Box::new([]),
});
};
let function = function.clone();
let args = args.iter().map(|a| a.clone()).collect();
eval_core(c, module.clone(), function, args)
}
pub fn eval_export_fn( pub fn eval_export_fn(
c: &mut Context, c: &mut Context,
module: ModuleId,
name: &str, name: &str,
args: &[StackValue], args: &[StackValue],
) -> std::result::Result<StackValue, VMError> { ) -> std::result::Result<StackValue, VMError> {
let Some(module) = c.modules.get(&module) else { let export = match c.module.exports.get(name) {
return Err(VMError {
code: VMErrorCode::ModuleNotFound(module),
stack: Box::new([]),
});
};
let export = match module.code.exports.get(name) {
Some(Export::Function(id)) => id, Some(Export::Function(id)) => id,
Some(_) => { Some(_) => todo!(),
return Err(VMError { None => todo!(),
code: VMErrorCode::ExportNotFunction(name.to_string()),
stack: Box::new([]),
})
}
None => {
return Err(VMError {
code: VMErrorCode::ExportNotFound(name.to_string()),
stack: Box::new([]),
})
}
}; };
let function = module.code.functions[*export].clone(); let function = c.module.functions[*export].clone();
let args = args.iter().map(|a| a.clone()).collect(); let args = args.iter().map(|a| a.clone()).collect();
eval_core(c, module.clone(), function, args) eval(c, function, args)
} }

View file

@ -1,10 +1,7 @@
use fine::compile_program; use fine::compiler::{compile, Function, Module};
use fine::compiler::{compile_module, CompiledModule, Function};
use fine::program::{
Module, ModuleLoadError, ModuleLoader, ModuleSource, Program, StandardModuleLoader,
};
use fine::semantics::{Error, Type}; use fine::semantics::{Error, Type};
use fine::vm::{eval_export_fn, Context, VMError}; use fine::vm::{eval_export_fn, Context};
use fine::{ModuleLoadError, ModuleLoader, ModuleSource, Runtime, StandardModuleLoader};
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use std::fmt::Write as _; use std::fmt::Write as _;
@ -171,11 +168,11 @@ impl ModuleLoader for TestLoader {
} }
} }
fn test_runtime(_source_path: &str, source: Rc<str>) -> Program { fn test_runtime(_source_path: &str, source: Rc<str>) -> Runtime {
Program::new(TestLoader::new(_source_path.into(), source)) Runtime::new(TestLoader::new(_source_path.into(), source))
} }
fn assert_type_at(module: Rc<Module>, pos: usize, expected: &str, _source_path: &str) { fn assert_type_at(module: Rc<fine::Module>, pos: usize, expected: &str, _source_path: &str) {
let semantics = module.semantics(); let semantics = module.semantics();
let tree = semantics.tree(); let tree = semantics.tree();
@ -200,7 +197,7 @@ fn assert_type_at(module: Rc<Module>, pos: usize, expected: &str, _source_path:
} }
fn assert_type_error_at( fn assert_type_error_at(
module: Rc<Module>, module: Rc<fine::Module>,
errors: &[Rc<Error>], errors: &[Rc<Error>],
pos: usize, pos: usize,
expected: &str, expected: &str,
@ -259,7 +256,7 @@ fn dump_function(out: &mut String, function: &Function) -> std::fmt::Result {
Ok(()) Ok(())
} }
fn dump_module(out: &mut String, module: &CompiledModule) -> std::fmt::Result { fn dump_module(out: &mut String, module: &Module) -> std::fmt::Result {
for function in module.functions() { for function in module.functions() {
dump_function(out, function)?; dump_function(out, function)?;
} }
@ -267,9 +264,9 @@ fn dump_module(out: &mut String, module: &CompiledModule) -> std::fmt::Result {
Ok(()) Ok(())
} }
fn assert_compiles_to(module: Rc<Module>, expected: &str, source_path: &str) { fn assert_compiles_to(module: Rc<fine::Module>, expected: &str, source_path: &str) {
let semantics = module.semantics(); let semantics = module.semantics();
let module = compile_module(&semantics); let module = compile(&semantics);
let mut actual = String::new(); let mut actual = String::new();
dump_module(&mut actual, &module).expect("no dumping?"); dump_module(&mut actual, &module).expect("no dumping?");
@ -289,7 +286,7 @@ fn assert_compiles_to(module: Rc<Module>, expected: &str, source_path: &str) {
} }
} }
fn assert_no_errors(module: Rc<Module>, errors: &[Rc<Error>]) { fn assert_no_errors(module: Rc<fine::Module>, errors: &[Rc<Error>]) {
let semantics = module.semantics(); let semantics = module.semantics();
let expected_errors: &[Rc<Error>] = &[]; let expected_errors: &[Rc<Error>] = &[];
@ -302,16 +299,31 @@ fn assert_no_errors(module: Rc<Module>, errors: &[Rc<Error>]) {
); );
} }
fn dump_runtime_error(module: &Rc<Module>, context: &Context, e: VMError) -> ! { fn assert_eval_ok(module: Rc<fine::Module>, expected: &str) {
let semantics = module.semantics(); let semantics = module.semantics();
let module = compile(&semantics);
let mut context = Context::new(module.clone());
context.init().expect("Unable to initialize module");
match eval_export_fn(&mut context, "test", &[]) {
Ok(v) => {
let actual = format!("{:?}", v);
semantic_assert_eq!(
&semantics,
None,
expected,
&actual,
"wrong return from test function"
);
}
Err(e) => {
semantics.dump_compiler_state(None); semantics.dump_compiler_state(None);
if let Some(module) = context.get_module(module.id()) {
let mut actual = String::new(); let mut actual = String::new();
let _ = dump_module(&mut actual, &module); let _ = dump_module(&mut actual, &module);
eprintln!("{actual}"); eprintln!("{actual}");
}
eprintln!("Backtrace:"); eprintln!("Backtrace:");
for frame in e.stack.iter() { for frame in e.stack.iter() {
@ -325,31 +337,11 @@ fn dump_runtime_error(module: &Rc<Module>, context: &Context, e: VMError) -> ! {
eprintln!(); eprintln!();
panic!("error occurred while running: {:?}", e.code); panic!("error occurred while running: {:?}", e.code);
}
fn assert_eval_ok(program: &Program, module: Rc<Module>, expected: &str) {
let semantics = module.semantics();
let mut context = Context::new();
if let Err(e) = compile_program(&program, &mut context) {
dump_runtime_error(&module, &context, e);
};
match eval_export_fn(&mut context, module.id(), "test", &[]) {
Ok(v) => {
let actual = format!("{:?}", v);
semantic_assert_eq!(
&semantics,
None,
expected,
&actual,
"wrong return from test function"
);
} }
Err(e) => dump_runtime_error(&module, &context, e),
} }
} }
fn assert_errors(module: Rc<Module>, errors: &[Rc<Error>], expected_errors: Vec<&str>) { fn assert_errors(module: Rc<fine::Module>, errors: &[Rc<Error>], expected_errors: Vec<&str>) {
let semantics = module.semantics(); let semantics = module.semantics();
let errors: Vec<String> = errors.iter().map(|e| format!("{}", e)).collect(); let errors: Vec<String> = errors.iter().map(|e| format!("{}", e)).collect();
@ -363,7 +355,7 @@ fn assert_errors(module: Rc<Module>, errors: &[Rc<Error>], expected_errors: Vec<
); );
} }
fn assert_check_error(module: Rc<Module>, errors: &[Rc<Error>], expected: &str) { fn assert_check_error(module: Rc<fine::Module>, errors: &[Rc<Error>], expected: &str) {
let semantics = module.semantics(); let semantics = module.semantics();
semantic_assert!( semantic_assert!(

View file

@ -64,6 +64,11 @@ fun test() -> f64 {
// | RightBrace:'"}"' // | RightBrace:'"}"'
// | // |
// @compiles-to: // @compiles-to:
// | function << module >> (0 args, 0 locals):
// | strings (0):
// | code (2):
// | 0: PushNothing
// | 1: Return
// | function foo (1 args, 0 locals): // | function foo (1 args, 0 locals):
// | strings (0): // | strings (0):
// | code (4): // | code (4):
@ -75,12 +80,7 @@ fun test() -> f64 {
// | strings (0): // | strings (0):
// | code (4): // | code (4):
// | 0: PushFloat(1.0) // | 0: PushFloat(1.0)
// | 1: LoadFunction(0) // | 1: LoadFunction(1)
// | 2: Call(1) // | 2: Call(1)
// | 3: Return // | 3: Return
// | function << module >> (0 args, 0 locals):
// | strings (0):
// | code (2):
// | 0: PushNothing
// | 1: Return
// | // |

View file

@ -40,6 +40,11 @@ fun test() -> f64 {
// | RightBrace:'"}"' // | RightBrace:'"}"'
// | // |
// @compiles-to: // @compiles-to:
// | function << module >> (0 args, 0 locals):
// | strings (0):
// | code (2):
// | 0: PushNothing
// | 1: Return
// | function test (0 args, 0 locals): // | function test (0 args, 0 locals):
// | strings (0): // | strings (0):
// | code (10): // | code (10):
@ -53,9 +58,4 @@ fun test() -> f64 {
// | 7: FloatMultiply // | 7: FloatMultiply
// | 8: FloatAdd // | 8: FloatAdd
// | 9: Return // | 9: Return
// | function << module >> (0 args, 0 locals):
// | strings (0):
// | code (2):
// | 0: PushNothing
// | 1: Return
// | // |

View file

@ -8,6 +8,11 @@ fun test() -> f64 {
// @no-errors // @no-errors
// @compiles-to: // @compiles-to:
// | function << module >> (0 args, 0 locals):
// | strings (0):
// | code (2):
// | 0: PushNothing
// | 1: Return
// | function test (0 args, 3 locals): // | function test (0 args, 3 locals):
// | strings (0): // | strings (0):
// | code (14): // | code (14):
@ -25,10 +30,5 @@ fun test() -> f64 {
// | 11: Discard // | 11: Discard
// | 12: LoadLocal(0) // | 12: LoadLocal(0)
// | 13: Return // | 13: Return
// | function << module >> (0 args, 0 locals):
// | strings (0):
// | code (2):
// | 0: PushNothing
// | 1: Return
// | // |
// @eval: Float(2.0) // @eval: Float(2.0)

View file

@ -4,12 +4,12 @@ fun test() {
// @no-errors // @no-errors
// @compiles-to: // @compiles-to:
// | function test (0 args, 0 locals): // | function << module >> (0 args, 0 locals):
// | strings (0): // | strings (0):
// | code (2): // | code (2):
// | 0: PushNothing // | 0: PushNothing
// | 1: Return // | 1: Return
// | function << module >> (0 args, 0 locals): // | function test (0 args, 0 locals):
// | strings (0): // | strings (0):
// | code (2): // | code (2):
// | 0: PushNothing // | 0: PushNothing

View file

@ -4,6 +4,11 @@ fun test() -> bool {
// @no-errors // @no-errors
// @compiles-to: // @compiles-to:
// | function << module >> (0 args, 0 locals):
// | strings (0):
// | code (2):
// | 0: PushNothing
// | 1: Return
// | function test (0 args, 0 locals): // | function test (0 args, 0 locals):
// | strings (0): // | strings (0):
// | code (15): // | code (15):
@ -22,11 +27,6 @@ fun test() -> bool {
// | 12: PushTrue // | 12: PushTrue
// | 13: BoolNot // | 13: BoolNot
// | 14: Return // | 14: Return
// | function << module >> (0 args, 0 locals):
// | strings (0):
// | code (2):
// | 0: PushNothing
// | 1: Return
// | // |
// @eval: Bool(false) // @eval: Bool(false)
// @type: 15 bool // @type: 15 bool

View file

@ -30,26 +30,11 @@ fun test() -> f64 {
// @no-errors // @no-errors
// @eval: Float(597.0) // @eval: Float(597.0)
// @compiles-to: // @compiles-to:
// | function something_static (0 args, 0 locals): // | function << module >> (0 args, 0 locals):
// | strings (0): // | strings (0):
// | code (2): // | code (2):
// | 0: PushFloat(12.0) // | 0: PushNothing
// | 1: Return // | 1: Return
// | function square_length (1 args, 0 locals):
// | strings (0):
// | code (12):
// | 0: LoadArgument(0)
// | 1: LoadSlot(0)
// | 2: LoadArgument(0)
// | 3: LoadSlot(0)
// | 4: FloatMultiply
// | 5: LoadArgument(0)
// | 6: LoadSlot(1)
// | 7: LoadArgument(0)
// | 8: LoadSlot(1)
// | 9: FloatMultiply
// | 10: FloatAdd
// | 11: Return
// | function Point (6 args, 0 locals): // | function Point (6 args, 0 locals):
// | strings (1): // | strings (1):
// | 0: "Point" // | 0: "Point"
@ -75,13 +60,13 @@ fun test() -> f64 {
// | code (27): // | code (27):
// | 0: PushFloat(99.0) // | 0: PushFloat(99.0)
// | 1: PushFloat(999.0) // | 1: PushFloat(999.0)
// | 2: LoadFunction(2) // | 2: LoadFunction(1)
// | 3: Call(2) // | 3: Call(2)
// | 4: PushFloat(23.0) // | 4: PushFloat(23.0)
// | 5: PushFloat(7.0) // | 5: PushFloat(7.0)
// | 6: LoadFunction(2) // | 6: LoadFunction(1)
// | 7: Call(2) // | 7: Call(2)
// | 8: LoadFunction(3) // | 8: LoadFunction(2)
// | 9: Call(2) // | 9: Call(2)
// | 10: StoreLocal(0) // | 10: StoreLocal(0)
// | 11: LoadLocal(0) // | 11: LoadLocal(0)
@ -91,18 +76,33 @@ fun test() -> f64 {
// | 15: LoadSlot(0) // | 15: LoadSlot(0)
// | 16: LoadSlot(0) // | 16: LoadSlot(0)
// | 17: LoadLocal(1) // | 17: LoadLocal(1)
// | 18: LoadFunction(1) // | 18: LoadFunction(4)
// | 19: Call(1) // | 19: Call(1)
// | 20: FloatAdd // | 20: FloatAdd
// | 21: LoadFunction(0) // | 21: LoadFunction(5)
// | 22: Call(0) // | 22: Call(0)
// | 23: FloatAdd // | 23: FloatAdd
// | 24: StoreLocal(2) // | 24: StoreLocal(2)
// | 25: LoadLocal(2) // | 25: LoadLocal(2)
// | 26: Return // | 26: Return
// | function << module >> (0 args, 0 locals): // | function square_length (1 args, 0 locals):
// | strings (0):
// | code (12):
// | 0: LoadArgument(0)
// | 1: LoadSlot(0)
// | 2: LoadArgument(0)
// | 3: LoadSlot(0)
// | 4: FloatMultiply
// | 5: LoadArgument(0)
// | 6: LoadSlot(1)
// | 7: LoadArgument(0)
// | 8: LoadSlot(1)
// | 9: FloatMultiply
// | 10: FloatAdd
// | 11: Return
// | function something_static (0 args, 0 locals):
// | strings (0): // | strings (0):
// | code (2): // | code (2):
// | 0: PushNothing // | 0: PushFloat(12.0)
// | 1: Return // | 1: Return
// | // |

View file

@ -58,6 +58,11 @@ fun test() -> f64 {
// | RightBrace:'"}"' // | RightBrace:'"}"'
// //
// @compiles-to: // @compiles-to:
// | function << module >> (0 args, 0 locals):
// | strings (0):
// | code (2):
// | 0: PushNothing
// | 1: Return
// | function test (0 args, 0 locals): // | function test (0 args, 0 locals):
// | strings (1): // | strings (1):
// | 0: "discarded" // | 0: "discarded"
@ -70,10 +75,5 @@ fun test() -> f64 {
// | 5: Jump(7) // | 5: Jump(7)
// | 6: PushFloat(45.0) // | 6: PushFloat(45.0)
// | 7: Return // | 7: Return
// | function << module >> (0 args, 0 locals):
// | strings (0):
// | code (2):
// | 0: PushNothing
// | 1: Return
// | // |
// @eval: Float(23.0) // @eval: Float(23.0)

View file

@ -64,13 +64,6 @@ fun test() -> f64 {
// | RightBrace:'"}"' // | RightBrace:'"}"'
// | // |
// @compiles-to: // @compiles-to:
// | function test (0 args, 0 locals):
// | strings (0):
// | code (4):
// | 0: LoadModule(0)
// | 1: LoadModule(1)
// | 2: FloatAdd
// | 3: Return
// | function << module >> (0 args, 0 locals): // | function << module >> (0 args, 0 locals):
// | strings (0): // | strings (0):
// | code (12): // | code (12):
@ -86,4 +79,11 @@ fun test() -> f64 {
// | 9: Discard // | 9: Discard
// | 10: PushNothing // | 10: PushNothing
// | 11: Return // | 11: Return
// | function test (0 args, 0 locals):
// | strings (0):
// | code (4):
// | 0: LoadModule(0)
// | 1: LoadModule(1)
// | 2: FloatAdd
// | 3: Return
// | // |

View file

@ -1,14 +1,8 @@
import "./foo.fine" as foo; import "./foo.fine" as foo;
// NOTE: This is right here because a known miscompilation will cause us to
// call this function instead of the actual target.
fun wrong_function() -> string {
"VERY WRONG"
}
fun test() -> string { fun test() -> string {
foo.hello() + " world" foo.hello() + " world"
} }
// TODO: Obviously run the code duh
// @no-errors // @no-errors
// @eval: String("hello world")