Compare commits
2 commits
4c061fbd28
...
a3d4c24f11
| Author | SHA1 | Date | |
|---|---|---|---|
| a3d4c24f11 | |||
| ab477cd783 |
17 changed files with 692 additions and 484 deletions
6
fine/TODO
Normal file
6
fine/TODO
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
- 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
|
||||||
|
|
@ -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(_module.clone(), #expected);
|
crate::assert_eval_ok(&program, _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 runtime = crate::test_runtime(#display_path, source.clone());
|
let mut program = crate::test_runtime(#display_path, source.clone());
|
||||||
let (_errors, _module) = runtime.load_module("__test__").unwrap();
|
let (_errors, _module) = program.load_module("__test__").unwrap();
|
||||||
|
|
||||||
#(#assertions)*
|
#(#assertions)*
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,9 @@ use std::rc::Rc;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
parser::{Child, SyntaxTree, Tree, TreeKind, TreeRef},
|
parser::{Child, SyntaxTree, Tree, TreeKind, TreeRef},
|
||||||
semantics::{string_constant_to_string, Declaration, Location, Origin, Semantics, Type},
|
semantics::{
|
||||||
|
string_constant_to_string, Declaration, Location, ModuleId, Origin, Semantics, Type,
|
||||||
|
},
|
||||||
tokens::TokenKind,
|
tokens::TokenKind,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -62,6 +64,8 @@ pub enum Instruction {
|
||||||
StoreSlot(usize),
|
StoreSlot(usize),
|
||||||
StringAdd,
|
StringAdd,
|
||||||
NewList(usize),
|
NewList(usize),
|
||||||
|
|
||||||
|
ModulePrefix(ModuleId),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum Export {
|
pub enum Export {
|
||||||
|
|
@ -69,23 +73,31 @@ pub enum Export {
|
||||||
Global(usize),
|
Global(usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Module {
|
pub struct CompiledModule {
|
||||||
|
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 Module {
|
impl CompiledModule {
|
||||||
pub fn new() -> Self {
|
pub fn new(id: ModuleId) -> Self {
|
||||||
Module {
|
CompiledModule {
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|
@ -142,21 +154,12 @@ 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,
|
||||||
|
|
||||||
function_bindings: HashMap<FunctionKey, usize>,
|
module: CompiledModule,
|
||||||
pending_functions: Vec<(FunctionKey, usize, Function)>,
|
|
||||||
temp_functions: Vec<Option<Rc<Function>>>,
|
|
||||||
|
|
||||||
module: Module,
|
|
||||||
function: Function,
|
function: Function,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -249,18 +252,35 @@ macro_rules! ice {
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
// macro_rules! inst_panic {
|
type CR = Result<(), &'static str>;
|
||||||
// ($($t:tt)+) => {{
|
const OK: CR = CR::Ok(());
|
||||||
// // eprintln!($($t)*);
|
|
||||||
// Instruction::Panic
|
|
||||||
// }};
|
|
||||||
// }
|
|
||||||
|
|
||||||
// macro_rules! ice {
|
fn function_from_function_decl(
|
||||||
// ($compiler:expr, $tr:expr, $($t:tt)*) => {{}};
|
source: &str,
|
||||||
// }
|
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);
|
||||||
|
|
||||||
pub fn compile(semantics: &Semantics) -> Rc<Module> {
|
let param_list = tree
|
||||||
|
.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();
|
||||||
|
|
||||||
|
|
@ -269,35 +289,48 @@ pub fn compile(semantics: &Semantics) -> Rc<Module> {
|
||||||
semantics: &semantics,
|
semantics: &semantics,
|
||||||
syntax: &syntax_tree,
|
syntax: &syntax_tree,
|
||||||
|
|
||||||
function_bindings: HashMap::new(),
|
module: CompiledModule::new(semantics.mid()),
|
||||||
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);
|
||||||
compiler.temp_functions[0] = Some(Rc::new(compiler.function));
|
let index = functions.len() - 1;
|
||||||
compiler.module.init = 0;
|
functions[index] = Some(Rc::new(compiler.function));
|
||||||
|
compiler.module.init = index;
|
||||||
}
|
}
|
||||||
|
|
||||||
while let Some((fk, idx, func)) = compiler.pending_functions.pop() {
|
for t in semantics.tree().trees() {
|
||||||
if idx >= compiler.temp_functions.len() {
|
if let Some(function_index) = semantics.get_function_index(t) {
|
||||||
compiler.temp_functions.resize(idx + 1, None);
|
let tree = &semantics.tree()[t];
|
||||||
|
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 compiler.temp_functions {
|
for f in functions {
|
||||||
module.functions.push(f.unwrap());
|
module.functions.push(f.unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
module.deps.append(&mut semantics.import_ids());
|
||||||
Rc::new(module)
|
Rc::new(module)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -317,9 +350,6 @@ 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 {
|
||||||
|
|
@ -633,8 +663,6 @@ 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 => {
|
||||||
|
|
@ -648,7 +676,12 @@ 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 => {
|
||||||
|
|
@ -657,32 +690,9 @@ fn compile_load_declaration(c: &mut Compiler, t: TreeRef, declaration: &Declarat
|
||||||
}
|
}
|
||||||
|
|
||||||
Location::Function => {
|
Location::Function => {
|
||||||
// TODO: Assert declaration is local
|
if declaration.module != c.semantics.mid() {
|
||||||
let Origin::Source(ft) = declaration.origin else {
|
c.push(Instruction::ModulePrefix(declaration.module));
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -691,8 +701,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,
|
||||||
|
|
||||||
// fix later
|
// Imports are handled with an instruction prefix.
|
||||||
Location::Import => ice!(c, t, "import compile not supported"),
|
Location::Import => return OK,
|
||||||
};
|
};
|
||||||
|
|
||||||
c.push(instruction);
|
c.push(instruction);
|
||||||
|
|
@ -988,24 +998,7 @@ 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 => {
|
||||||
let Origin::Source(classdecl) = declaration.origin else {
|
c.push(Instruction::LoadFunction(declaration.index));
|
||||||
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"),
|
||||||
}
|
}
|
||||||
|
|
@ -1039,20 +1032,7 @@ 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 = match &typ {
|
let environment = c.semantics.member_environment(t, &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
|
||||||
|
|
@ -1092,14 +1072,16 @@ 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, t, tree, gen_value),
|
TreeKind::ClassDecl => compile_class_declaration(c, 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, t, tree, gen_value),
|
TreeKind::FunctionDecl => compile_function_declaration(c, 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),
|
||||||
};
|
};
|
||||||
|
|
@ -1112,6 +1094,11 @@ 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 {
|
||||||
|
|
@ -1161,7 +1148,9 @@ 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 index >= c.module.globals {
|
if declaration.module != c.semantics.mid() {
|
||||||
|
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)
|
||||||
|
|
@ -1176,32 +1165,7 @@ fn compile_let_statement(c: &mut Compiler, t: TreeRef, tree: &Tree, gen_value: b
|
||||||
OK
|
OK
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compile_function_declaration(c: &mut Compiler, t: TreeRef, tree: &Tree, gen_value: bool) -> CR {
|
fn compile_function_declaration(c: &mut Compiler, 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);
|
||||||
}
|
}
|
||||||
|
|
@ -1209,26 +1173,7 @@ fn compile_function_declaration(c: &mut Compiler, t: TreeRef, tree: &Tree, gen_v
|
||||||
OK
|
OK
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compile_class_declaration(c: &mut Compiler, t: TreeRef, tree: &Tree, gen_value: bool) -> CR {
|
fn compile_class_declaration(c: &mut Compiler, 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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
196
fine/src/lib.rs
196
fine/src/lib.rs
|
|
@ -1,181 +1,51 @@
|
||||||
use std::{collections::HashMap, fs, path::PathBuf, rc::Rc};
|
use std::{path::PathBuf, rc::Rc};
|
||||||
|
|
||||||
use compiler::compile;
|
use compiler::compile_module;
|
||||||
use parser::parse;
|
use program::{Module, Program, StandardModuleLoader};
|
||||||
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;
|
||||||
|
|
||||||
pub enum ModuleSource {
|
fn load_module(
|
||||||
SourceText(String),
|
program: &Program,
|
||||||
}
|
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());
|
||||||
|
|
||||||
#[derive(Debug)]
|
for dep in module.deps.iter() {
|
||||||
pub enum ModuleLoadError {
|
if let Some(dep_mod) = program.get_module(dep) {
|
||||||
IO(String, std::io::Error),
|
load_module(program, dep_mod, context)?;
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
eval(context, module.id, module.init, &[])?;
|
||||||
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> {
|
Ok(())
|
||||||
match fs::read_to_string(name) {
|
|
||||||
Ok(c) => Ok(ModuleSource::SourceText(c)),
|
|
||||||
Err(e) => Err(ModuleLoadError::IO(name.clone(), e)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Module {
|
pub fn compile_program(program: &Program, context: &mut Context) -> Result<(), vm::VMError> {
|
||||||
id: ModuleId,
|
for module in program.modules() {
|
||||||
semantics: Rc<Semantics>,
|
load_module(program, module, context)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Module {
|
Ok(())
|
||||||
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 runtime = Runtime::new(Box::new(StandardModuleLoader::new(PathBuf::from("."))));
|
let mut program = Program::new(Box::new(StandardModuleLoader::new(PathBuf::from("."))));
|
||||||
|
|
||||||
let (errors, module) = match runtime.load_module(file) {
|
// Load the program text
|
||||||
|
let (errors, _) = match program.load_module(file) {
|
||||||
Ok(r) => r,
|
Ok(r) => r,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
eprintln!("Error loading module");
|
eprintln!("Error loading module");
|
||||||
|
|
@ -183,7 +53,7 @@ pub fn process_file(file: &str) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// OK now there might be errors.
|
// OK now there might be semantic errors or whatnot.
|
||||||
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);
|
||||||
|
|
@ -191,16 +61,10 @@ pub fn process_file(file: &str) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// shrug
|
// This is weird, why run the init function as main? Maybe just run main?
|
||||||
let semantics = module.semantics();
|
let mut context = Context::new();
|
||||||
let module = compile(&semantics);
|
match compile_program(&program, &mut context) {
|
||||||
let main_function = module.functions[module.init].clone();
|
Ok(_) => {}
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
196
fine/src/program.rs
Normal file
196
fine/src/program.rs
Normal file
|
|
@ -0,0 +1,196 @@
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -116,6 +116,12 @@ 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,
|
||||||
|
|
@ -344,9 +350,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 (index unrelated)
|
Function, // A function in a module
|
||||||
ExternalFunction, // An external function (module unrelated)
|
ExternalFunction, // An external function (module unrelated)
|
||||||
Class, // A class in a module (index unrelated)
|
Class, // A class in a module
|
||||||
Import, // An import in a module (index unrelated)
|
Import, // An import in a module (index unrelated)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -683,6 +689,9 @@ 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>>>,
|
||||||
|
|
@ -706,6 +715,19 @@ 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,
|
||||||
|
|
@ -714,6 +736,8 @@ 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()]),
|
||||||
|
|
@ -744,6 +768,12 @@ 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()?;
|
||||||
|
|
@ -797,6 +827,10 @@ 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,
|
||||||
|
|
@ -892,6 +926,27 @@ 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.
|
||||||
|
|
@ -952,7 +1007,7 @@ impl Semantics {
|
||||||
name.as_str(&self.source).into(),
|
name.as_str(&self.source).into(),
|
||||||
Declaration {
|
Declaration {
|
||||||
location: Location::Function,
|
location: Location::Function,
|
||||||
index: 0,
|
index: self.function_index_of(*t),
|
||||||
module: self.mid,
|
module: self.mid,
|
||||||
origin: Origin::Source(*t),
|
origin: Origin::Source(*t),
|
||||||
exported: false,
|
exported: false,
|
||||||
|
|
@ -1007,7 +1062,7 @@ impl Semantics {
|
||||||
|
|
||||||
let declaration = Declaration {
|
let declaration = Declaration {
|
||||||
location: Location::Function,
|
location: Location::Function,
|
||||||
index: 0,
|
index: self.function_index_of(t),
|
||||||
module: self.mid,
|
module: self.mid,
|
||||||
origin: Origin::Source(t),
|
origin: Origin::Source(t),
|
||||||
exported,
|
exported,
|
||||||
|
|
@ -1024,7 +1079,7 @@ impl Semantics {
|
||||||
|
|
||||||
let declaration = Declaration {
|
let declaration = Declaration {
|
||||||
location: Location::Class,
|
location: Location::Class,
|
||||||
index: 0,
|
index: self.function_index_of(t),
|
||||||
module: self.mid,
|
module: self.mid,
|
||||||
origin: Origin::Source(t),
|
origin: Origin::Source(t),
|
||||||
exported,
|
exported,
|
||||||
|
|
@ -1393,7 +1448,7 @@ impl Semantics {
|
||||||
(&*method.name).into(),
|
(&*method.name).into(),
|
||||||
Declaration {
|
Declaration {
|
||||||
location: Location::Function,
|
location: Location::Function,
|
||||||
index: 0,
|
index: self.function_index_of(method.declaration),
|
||||||
module: self.mid,
|
module: self.mid,
|
||||||
origin: Origin::Source(method.declaration),
|
origin: Origin::Source(method.declaration),
|
||||||
exported: false,
|
exported: false,
|
||||||
|
|
|
||||||
230
fine/src/vm.rs
230
fine/src/vm.rs
|
|
@ -1,11 +1,13 @@
|
||||||
|
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::{
|
||||||
Export, Function, Instruction, Module, EXTERN_BUILTIN_LIST_GET_ITERATOR,
|
CompiledModule, Export, Function, Instruction, EXTERN_BUILTIN_LIST_GET_ITERATOR,
|
||||||
EXTERN_BUILTIN_LIST_ITERATOR_NEXT, EXTERN_BUILTIN_NOOP,
|
EXTERN_BUILTIN_LIST_ITERATOR_NEXT, EXTERN_BUILTIN_NOOP,
|
||||||
};
|
};
|
||||||
use crate::semantics::Type;
|
use crate::semantics::{ModuleId, Type};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
|
|
@ -44,6 +46,13 @@ 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)]
|
||||||
|
|
@ -80,6 +89,21 @@ 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,
|
||||||
|
|
@ -87,8 +111,7 @@ pub enum StackValue {
|
||||||
Float(f64),
|
Float(f64),
|
||||||
Int(i64),
|
Int(i64),
|
||||||
String(Rc<str>),
|
String(Rc<str>),
|
||||||
Function(Rc<Function>),
|
Function(Box<FuncValue>),
|
||||||
ExternFunction(usize),
|
|
||||||
Object(Rc<Object>),
|
Object(Rc<Object>),
|
||||||
List(Rc<Vec<StackValue>>),
|
List(Rc<Vec<StackValue>>),
|
||||||
ListIterator(Rc<ListIterator>),
|
ListIterator(Rc<ListIterator>),
|
||||||
|
|
@ -131,13 +154,9 @@ 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>,
|
||||||
|
|
@ -146,11 +165,12 @@ pub struct Frame {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Frame {
|
impl Frame {
|
||||||
fn from_function(func: Rc<Function>, args: Vec<StackValue>) -> Self {
|
fn from_function(module: Rc<RuntimeModule>, 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,
|
||||||
|
|
@ -159,8 +179,12 @@ impl Frame {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn func(&self) -> Rc<Function> {
|
pub fn module(&self) -> &Rc<RuntimeModule> {
|
||||||
self.func.clone()
|
&self.module
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn func(&self) -> &Rc<Function> {
|
||||||
|
&self.func
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn args(&self) -> &[StackValue] {
|
pub fn args(&self) -> &[StackValue] {
|
||||||
|
|
@ -230,12 +254,12 @@ impl Frame {
|
||||||
self.push_value(StackValue::String(v))
|
self.push_value(StackValue::String(v))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_function(&mut self, v: Rc<Function>) {
|
fn push_function(&mut self, m: Rc<RuntimeModule>, v: Rc<Function>) {
|
||||||
self.push_value(StackValue::Function(v));
|
self.push_value(StackValue::Function(Box::new(FuncValue::Function(m, v))));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_extern_function(&mut self, v: usize) {
|
fn push_extern_function(&mut self, v: usize) {
|
||||||
self.push_value(StackValue::ExternFunction(v));
|
self.push_value(StackValue::Function(Box::new(FuncValue::ExternFunction(v))));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_object(&mut self, v: Rc<Object>) {
|
fn push_object(&mut self, v: Rc<Object>) {
|
||||||
|
|
@ -292,55 +316,86 @@ 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(FuncValue::Function(f)),
|
StackValue::Function(f) => Ok(*f),
|
||||||
StackValue::ExternFunction(i) => Ok(FuncValue::ExternFunction(i)),
|
|
||||||
v => Err(VMErrorCode::StackExpectedFunction(v)),
|
v => Err(VMErrorCode::StackExpectedFunction(v)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Context {
|
pub struct RuntimeModule {
|
||||||
module: Rc<Module>,
|
code: Rc<CompiledModule>,
|
||||||
globals: Vec<StackValue>,
|
globals: RefCell<Vec<StackValue>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Context {
|
impl fmt::Debug for RuntimeModule {
|
||||||
pub fn new(module: Rc<Module>) -> Context {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
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(module.globals, StackValue::Nothing);
|
globals.resize(code.globals, StackValue::Nothing);
|
||||||
Context { module, globals }
|
RuntimeModule {
|
||||||
|
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(&mut self, i: usize, v: StackValue) -> Result<()> {
|
fn set_global(&self, i: usize, v: StackValue) -> Result<()> {
|
||||||
if i >= self.globals.len() {
|
let mut globals = self.globals.borrow_mut();
|
||||||
|
if i >= globals.len() {
|
||||||
Err(VMErrorCode::GlobalOutOfRange(i)) // TODO: Test
|
Err(VMErrorCode::GlobalOutOfRange(i)) // TODO: Test
|
||||||
} else {
|
} else {
|
||||||
self.globals[i] = v;
|
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.module.functions();
|
let functions = self.code.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 {
|
||||||
|
|
@ -393,6 +448,8 @@ 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
|
||||||
|
|
@ -457,7 +514,15 @@ fn eval_one(
|
||||||
f.push_value(v);
|
f.push_value(v);
|
||||||
}
|
}
|
||||||
Instruction::LoadModule(i) => {
|
Instruction::LoadModule(i) => {
|
||||||
let v = c.get_global(i)?;
|
let module = module_prefix
|
||||||
|
.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 => {
|
||||||
|
|
@ -486,7 +551,15 @@ fn eval_one(
|
||||||
}
|
}
|
||||||
Instruction::StoreModule(i) => {
|
Instruction::StoreModule(i) => {
|
||||||
let v = f.pop_value()?;
|
let v = f.pop_value()?;
|
||||||
c.set_global(i, v)?;
|
let module = module_prefix
|
||||||
|
.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()?;
|
||||||
|
|
@ -494,8 +567,16 @@ fn eval_one(
|
||||||
o.values.borrow_mut()[i] = v;
|
o.values.borrow_mut()[i] = v;
|
||||||
}
|
}
|
||||||
Instruction::LoadFunction(i) => {
|
Instruction::LoadFunction(i) => {
|
||||||
let v = c.get_function(i)?;
|
let module = module_prefix
|
||||||
f.push_function(v);
|
.map(|id| {
|
||||||
|
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);
|
||||||
|
|
@ -507,8 +588,8 @@ fn eval_one(
|
||||||
args.push(f.pop_value()?);
|
args.push(f.pop_value()?);
|
||||||
}
|
}
|
||||||
match function {
|
match function {
|
||||||
FuncValue::Function(func) => {
|
FuncValue::Function(module, func) => {
|
||||||
let mut frame = Frame::from_function(func, args);
|
let mut frame = Frame::from_function(module, 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);
|
||||||
|
|
@ -533,8 +614,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 = x.to_string();
|
let mut new_string = y.to_string();
|
||||||
new_string.push_str(&y);
|
new_string.push_str(&x);
|
||||||
|
|
||||||
f.push_string(new_string.into());
|
f.push_string(new_string.into());
|
||||||
}
|
}
|
||||||
|
|
@ -624,18 +705,22 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn eval(
|
fn eval_core(
|
||||||
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(function, args);
|
let mut f = Frame::from_function(module, function, args);
|
||||||
|
|
||||||
let mut index = 0;
|
let mut index = 0;
|
||||||
loop {
|
loop {
|
||||||
|
|
@ -680,18 +765,61 @@ pub fn eval(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 export = match c.module.exports.get(name) {
|
let Some(module) = c.modules.get(&module) else {
|
||||||
Some(Export::Function(id)) => id,
|
return Err(VMError {
|
||||||
Some(_) => todo!(),
|
code: VMErrorCode::ModuleNotFound(module),
|
||||||
None => todo!(),
|
stack: Box::new([]),
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
let function = c.module.functions[*export].clone();
|
let export = match module.code.exports.get(name) {
|
||||||
let args = args.iter().map(|a| a.clone()).collect();
|
Some(Export::Function(id)) => id,
|
||||||
eval(c, function, args)
|
Some(_) => {
|
||||||
|
return Err(VMError {
|
||||||
|
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 args = args.iter().map(|a| a.clone()).collect();
|
||||||
|
eval_core(c, module.clone(), function, args)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
use fine::compiler::{compile, Function, Module};
|
use fine::compile_program;
|
||||||
|
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};
|
use fine::vm::{eval_export_fn, Context, VMError};
|
||||||
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 _;
|
||||||
|
|
@ -168,11 +171,11 @@ impl ModuleLoader for TestLoader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_runtime(_source_path: &str, source: Rc<str>) -> Runtime {
|
fn test_runtime(_source_path: &str, source: Rc<str>) -> Program {
|
||||||
Runtime::new(TestLoader::new(_source_path.into(), source))
|
Program::new(TestLoader::new(_source_path.into(), source))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn assert_type_at(module: Rc<fine::Module>, pos: usize, expected: &str, _source_path: &str) {
|
fn assert_type_at(module: Rc<Module>, pos: usize, expected: &str, _source_path: &str) {
|
||||||
let semantics = module.semantics();
|
let semantics = module.semantics();
|
||||||
let tree = semantics.tree();
|
let tree = semantics.tree();
|
||||||
|
|
||||||
|
|
@ -197,7 +200,7 @@ fn assert_type_at(module: Rc<fine::Module>, pos: usize, expected: &str, _source_
|
||||||
}
|
}
|
||||||
|
|
||||||
fn assert_type_error_at(
|
fn assert_type_error_at(
|
||||||
module: Rc<fine::Module>,
|
module: Rc<Module>,
|
||||||
errors: &[Rc<Error>],
|
errors: &[Rc<Error>],
|
||||||
pos: usize,
|
pos: usize,
|
||||||
expected: &str,
|
expected: &str,
|
||||||
|
|
@ -256,7 +259,7 @@ fn dump_function(out: &mut String, function: &Function) -> std::fmt::Result {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dump_module(out: &mut String, module: &Module) -> std::fmt::Result {
|
fn dump_module(out: &mut String, module: &CompiledModule) -> std::fmt::Result {
|
||||||
for function in module.functions() {
|
for function in module.functions() {
|
||||||
dump_function(out, function)?;
|
dump_function(out, function)?;
|
||||||
}
|
}
|
||||||
|
|
@ -264,9 +267,9 @@ fn dump_module(out: &mut String, module: &Module) -> std::fmt::Result {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn assert_compiles_to(module: Rc<fine::Module>, expected: &str, source_path: &str) {
|
fn assert_compiles_to(module: Rc<Module>, expected: &str, source_path: &str) {
|
||||||
let semantics = module.semantics();
|
let semantics = module.semantics();
|
||||||
let module = compile(&semantics);
|
let module = compile_module(&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?");
|
||||||
|
|
@ -286,7 +289,7 @@ fn assert_compiles_to(module: Rc<fine::Module>, expected: &str, source_path: &st
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn assert_no_errors(module: Rc<fine::Module>, errors: &[Rc<Error>]) {
|
fn assert_no_errors(module: Rc<Module>, errors: &[Rc<Error>]) {
|
||||||
let semantics = module.semantics();
|
let semantics = module.semantics();
|
||||||
|
|
||||||
let expected_errors: &[Rc<Error>] = &[];
|
let expected_errors: &[Rc<Error>] = &[];
|
||||||
|
|
@ -299,31 +302,16 @@ fn assert_no_errors(module: Rc<fine::Module>, errors: &[Rc<Error>]) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn assert_eval_ok(module: Rc<fine::Module>, expected: &str) {
|
fn dump_runtime_error(module: &Rc<Module>, context: &Context, e: VMError) -> ! {
|
||||||
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() {
|
||||||
|
|
@ -338,10 +326,30 @@ fn assert_eval_ok(module: Rc<fine::Module>, expected: &str) {
|
||||||
|
|
||||||
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<fine::Module>, errors: &[Rc<Error>], expected_errors: Vec<&str>) {
|
fn assert_errors(module: Rc<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();
|
||||||
|
|
@ -355,7 +363,7 @@ fn assert_errors(module: Rc<fine::Module>, errors: &[Rc<Error>], expected_errors
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn assert_check_error(module: Rc<fine::Module>, errors: &[Rc<Error>], expected: &str) {
|
fn assert_check_error(module: Rc<Module>, errors: &[Rc<Error>], expected: &str) {
|
||||||
let semantics = module.semantics();
|
let semantics = module.semantics();
|
||||||
|
|
||||||
semantic_assert!(
|
semantic_assert!(
|
||||||
|
|
|
||||||
|
|
@ -64,11 +64,6 @@ 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):
|
||||||
|
|
@ -80,7 +75,12 @@ fun test() -> f64 {
|
||||||
// | strings (0):
|
// | strings (0):
|
||||||
// | code (4):
|
// | code (4):
|
||||||
// | 0: PushFloat(1.0)
|
// | 0: PushFloat(1.0)
|
||||||
// | 1: LoadFunction(1)
|
// | 1: LoadFunction(0)
|
||||||
// | 2: Call(1)
|
// | 2: Call(1)
|
||||||
// | 3: Return
|
// | 3: Return
|
||||||
|
// | function << module >> (0 args, 0 locals):
|
||||||
|
// | strings (0):
|
||||||
|
// | code (2):
|
||||||
|
// | 0: PushNothing
|
||||||
|
// | 1: Return
|
||||||
// |
|
// |
|
||||||
|
|
|
||||||
|
|
@ -40,11 +40,6 @@ 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):
|
||||||
|
|
@ -58,4 +53,9 @@ 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
|
||||||
// |
|
// |
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,6 @@ 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):
|
||||||
|
|
@ -30,5 +25,10 @@ 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)
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,12 @@ fun test() {
|
||||||
|
|
||||||
// @no-errors
|
// @no-errors
|
||||||
// @compiles-to:
|
// @compiles-to:
|
||||||
// | function << module >> (0 args, 0 locals):
|
// | function test (0 args, 0 locals):
|
||||||
// | strings (0):
|
// | strings (0):
|
||||||
// | code (2):
|
// | code (2):
|
||||||
// | 0: PushNothing
|
// | 0: PushNothing
|
||||||
// | 1: Return
|
// | 1: Return
|
||||||
// | function test (0 args, 0 locals):
|
// | function << module >> (0 args, 0 locals):
|
||||||
// | strings (0):
|
// | strings (0):
|
||||||
// | code (2):
|
// | code (2):
|
||||||
// | 0: PushNothing
|
// | 0: PushNothing
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,6 @@ 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):
|
||||||
|
|
@ -27,6 +22,11 @@ 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
|
||||||
|
|
|
||||||
|
|
@ -30,11 +30,26 @@ fun test() -> f64 {
|
||||||
// @no-errors
|
// @no-errors
|
||||||
// @eval: Float(597.0)
|
// @eval: Float(597.0)
|
||||||
// @compiles-to:
|
// @compiles-to:
|
||||||
// | function << module >> (0 args, 0 locals):
|
// | 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
|
||||||
|
// | 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"
|
||||||
|
|
@ -60,13 +75,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(1)
|
// | 2: LoadFunction(2)
|
||||||
// | 3: Call(2)
|
// | 3: Call(2)
|
||||||
// | 4: PushFloat(23.0)
|
// | 4: PushFloat(23.0)
|
||||||
// | 5: PushFloat(7.0)
|
// | 5: PushFloat(7.0)
|
||||||
// | 6: LoadFunction(1)
|
// | 6: LoadFunction(2)
|
||||||
// | 7: Call(2)
|
// | 7: Call(2)
|
||||||
// | 8: LoadFunction(2)
|
// | 8: LoadFunction(3)
|
||||||
// | 9: Call(2)
|
// | 9: Call(2)
|
||||||
// | 10: StoreLocal(0)
|
// | 10: StoreLocal(0)
|
||||||
// | 11: LoadLocal(0)
|
// | 11: LoadLocal(0)
|
||||||
|
|
@ -76,33 +91,18 @@ fun test() -> f64 {
|
||||||
// | 15: LoadSlot(0)
|
// | 15: LoadSlot(0)
|
||||||
// | 16: LoadSlot(0)
|
// | 16: LoadSlot(0)
|
||||||
// | 17: LoadLocal(1)
|
// | 17: LoadLocal(1)
|
||||||
// | 18: LoadFunction(4)
|
// | 18: LoadFunction(1)
|
||||||
// | 19: Call(1)
|
// | 19: Call(1)
|
||||||
// | 20: FloatAdd
|
// | 20: FloatAdd
|
||||||
// | 21: LoadFunction(5)
|
// | 21: LoadFunction(0)
|
||||||
// | 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 square_length (1 args, 0 locals):
|
// | function << module >> (0 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: PushFloat(12.0)
|
// | 0: PushNothing
|
||||||
// | 1: Return
|
// | 1: Return
|
||||||
// |
|
// |
|
||||||
|
|
|
||||||
|
|
@ -58,11 +58,6 @@ 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"
|
||||||
|
|
@ -75,5 +70,10 @@ 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)
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,13 @@ 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):
|
||||||
|
|
@ -79,11 +86,4 @@ 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
|
|
||||||
// |
|
// |
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,14 @@
|
||||||
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")
|
||||||
Loading…
Add table
Add a link
Reference in a new issue