[fine] A wild VM appears!
Untested though
This commit is contained in:
parent
53f18e729b
commit
866830b485
8 changed files with 464 additions and 26 deletions
9
Cargo.lock
generated
9
Cargo.lock
generated
|
|
@ -677,6 +677,7 @@ dependencies = [
|
|||
"prettyplease",
|
||||
"quote",
|
||||
"syn 2.0.47",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2615,18 +2616,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.40"
|
||||
version = "1.0.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac"
|
||||
checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.40"
|
||||
version = "1.0.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
|
||||
checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
|
|||
21
fine/Cargo.lock
generated
21
fine/Cargo.lock
generated
|
|
@ -17,6 +17,7 @@ dependencies = [
|
|||
"prettyplease",
|
||||
"quote",
|
||||
"syn",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -74,6 +75,26 @@ dependencies = [
|
|||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.12"
|
||||
|
|
|
|||
|
|
@ -11,3 +11,6 @@ glob = "0.3.1"
|
|||
prettyplease = "0.2.16"
|
||||
quote = "1.0.35"
|
||||
syn = "2.0.47"
|
||||
|
||||
[dependencies]
|
||||
thiserror = "1.0.56"
|
||||
|
|
|
|||
|
|
@ -82,6 +82,11 @@ fn generate_test_for_file(path: PathBuf) -> String {
|
|||
assertions.push(quote! {
|
||||
crate::assert_no_errors(&_tree, &_lines);
|
||||
});
|
||||
} else if let Some(line) = line.strip_prefix("@eval:") {
|
||||
let expected = line.trim();
|
||||
assertions.push(quote! {
|
||||
crate::assert_eval_ok(&_tree, &_lines, #expected);
|
||||
});
|
||||
} else if line.starts_with("@") {
|
||||
panic!("Test file {display_path} has unknown directive: {line}");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::{
|
||||
parser::{SyntaxTree, Tree, TreeKind, TreeRef},
|
||||
|
|
@ -8,7 +9,7 @@ use crate::{
|
|||
|
||||
// TODO: If I were cool this would by actual bytecode.
|
||||
// But I'm not cool.
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Instruction {
|
||||
Panic,
|
||||
|
||||
|
|
@ -32,9 +33,9 @@ pub enum Instruction {
|
|||
StoreLocal(usize),
|
||||
StoreModule(usize),
|
||||
LoadFunction(usize),
|
||||
LoadExtern(usize),
|
||||
LoadExternFunction(usize), // NOTE: FUNKY, might want to indirect this index.
|
||||
Call(usize),
|
||||
Return,
|
||||
}
|
||||
|
||||
pub enum Export {
|
||||
|
|
@ -43,7 +44,7 @@ pub enum Export {
|
|||
}
|
||||
|
||||
pub struct Module {
|
||||
pub functions: Vec<Function>, // Functions
|
||||
pub functions: Vec<Rc<Function>>, // Functions
|
||||
pub globals: usize, // The number of global variables
|
||||
pub exports: HashMap<String, Export>, // Exports by name
|
||||
pub init: usize, // The index of the initialization function
|
||||
|
|
@ -59,7 +60,7 @@ impl Module {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn functions(&self) -> &[Function] {
|
||||
pub fn functions(&self) -> &[Rc<Function>] {
|
||||
&self.functions
|
||||
}
|
||||
}
|
||||
|
|
@ -68,7 +69,7 @@ impl Module {
|
|||
pub struct Function {
|
||||
name: String,
|
||||
instructions: Vec<Instruction>,
|
||||
strings: Vec<String>,
|
||||
strings: Vec<Rc<str>>,
|
||||
args: usize, // TODO: Probably type information too?
|
||||
locals: usize, // TODO: Same?
|
||||
}
|
||||
|
|
@ -96,7 +97,7 @@ impl Function {
|
|||
self.locals
|
||||
}
|
||||
|
||||
pub fn strings(&self) -> &[String] {
|
||||
pub fn strings(&self) -> &[Rc<str>] {
|
||||
&self.strings
|
||||
}
|
||||
|
||||
|
|
@ -105,6 +106,16 @@ impl Function {
|
|||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Function {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"fn {} ({} args, {} locals) ...",
|
||||
self.name, self.args, self.locals
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
struct Compiler<'a> {
|
||||
semantics: &'a Semantics<'a>,
|
||||
syntax: &'a SyntaxTree<'a>,
|
||||
|
|
@ -123,7 +134,7 @@ impl<'a> Compiler<'a> {
|
|||
|
||||
fn add_string(&mut self, result: String) -> usize {
|
||||
let index = self.function.strings.len();
|
||||
self.function.strings.push(result);
|
||||
self.function.strings.push(result.into());
|
||||
index
|
||||
}
|
||||
|
||||
|
|
@ -195,7 +206,7 @@ macro_rules! ice {
|
|||
// ($compiler:expr, $tr:expr, $($t:tt)*) => {{}};
|
||||
// }
|
||||
|
||||
pub fn compile(semantics: &Semantics) -> Module {
|
||||
pub fn compile(semantics: &Semantics) -> Rc<Module> {
|
||||
let mut compiler = Compiler {
|
||||
semantics,
|
||||
syntax: semantics.tree(),
|
||||
|
|
@ -210,10 +221,10 @@ pub fn compile(semantics: &Semantics) -> Module {
|
|||
|
||||
let mut module = compiler.module;
|
||||
let index = module.functions.len();
|
||||
module.functions.push(compiler.function);
|
||||
module.functions.push(Rc::new(compiler.function));
|
||||
module.init = index;
|
||||
|
||||
module
|
||||
Rc::new(module)
|
||||
}
|
||||
|
||||
fn file(c: &mut Compiler, t: TreeRef) {
|
||||
|
|
@ -565,7 +576,7 @@ fn compile_function_declaration(c: &mut Compiler, t: TreeRef, tree: &Tree, gen_v
|
|||
compile_expression(c, block);
|
||||
|
||||
std::mem::swap(&mut c.function, &mut prev);
|
||||
c.module.functions.push(prev);
|
||||
c.module.functions.push(Rc::new(prev));
|
||||
}
|
||||
|
||||
if gen_value {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
use std::fs;
|
||||
|
||||
use compiler::compile;
|
||||
use parser::parse;
|
||||
use semantics::{check, Semantics};
|
||||
use vm::{eval, Context};
|
||||
|
||||
pub mod compiler;
|
||||
pub mod parser;
|
||||
|
|
@ -25,8 +27,24 @@ pub fn process_file(file: &str) {
|
|||
|
||||
// OK now there might be errors.
|
||||
let mut errors = semantics.snapshot_errors();
|
||||
errors.reverse();
|
||||
for e in errors {
|
||||
eprintln!("{file}: {}:{}: {}", e.start.0, e.start.1, e.message);
|
||||
if errors.len() > 0 {
|
||||
errors.reverse();
|
||||
for e in errors {
|
||||
eprintln!("{file}: {}:{}: {}", e.start.0, e.start.1, e.message);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let module = compile(&semantics);
|
||||
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) => {
|
||||
eprintln!("{:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
375
fine/src/vm.rs
375
fine/src/vm.rs
|
|
@ -1,12 +1,371 @@
|
|||
// use crate::compiler::{Function, Module};
|
||||
use std::rc::Rc;
|
||||
|
||||
// // TODO: VM state structure
|
||||
// // TODO: Runtime module vs compiled module
|
||||
use crate::compiler::{Function, Instruction, Module};
|
||||
use crate::semantics::Type;
|
||||
use thiserror::Error;
|
||||
|
||||
// struct StackFrame<'a> {
|
||||
// function: &'a Function,
|
||||
// }
|
||||
#[derive(Error, Debug)]
|
||||
pub enum VMErrorCode {
|
||||
#[error("code panic (syntax or semantic error)")]
|
||||
Panic,
|
||||
#[error("internal error: stack underflow")]
|
||||
StackUnderflow,
|
||||
#[error("internal error: stack type mismatch: {0:?} is not {1:?}")]
|
||||
StackTypeMismatch(StackValue, Type),
|
||||
|
||||
// pub fn eval(module: &Module) {
|
||||
// TODO: This one is *not* like the others!
|
||||
#[error("divide by zero")]
|
||||
DivideByZero,
|
||||
|
||||
// }
|
||||
#[error("internal error: argument {0} out of range")]
|
||||
ArgumentOutOfRange(usize),
|
||||
#[error("internal error: global {0} out of range")]
|
||||
GlobalOutOfRange(usize),
|
||||
#[error("internal error: local {0} out of range")]
|
||||
LocalOutOfRange(usize),
|
||||
#[error("internal error: string {0} out of range")]
|
||||
StringOutOfRange(usize),
|
||||
#[error("internal error: function {0} out of range")]
|
||||
FunctionOutOfRange(usize),
|
||||
#[error("internal error: stack type mismatch ({0:?} is not function)")]
|
||||
StackExpectedFunction(StackValue),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct VMError {
|
||||
pub code: VMErrorCode,
|
||||
pub stack: Box<[Frame]>,
|
||||
}
|
||||
|
||||
type Result<T> = std::result::Result<T, VMErrorCode>;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum StackValue {
|
||||
Nothing,
|
||||
Bool(bool),
|
||||
Float(f64),
|
||||
String(Rc<str>),
|
||||
Function(Rc<Function>),
|
||||
ExternFunction(usize),
|
||||
}
|
||||
|
||||
enum FuncValue {
|
||||
Function(Rc<Function>),
|
||||
ExternFunction(usize),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Frame {
|
||||
func: Rc<Function>,
|
||||
args: Vec<StackValue>,
|
||||
locals: Vec<StackValue>,
|
||||
stack: Vec<StackValue>,
|
||||
pc: usize,
|
||||
}
|
||||
|
||||
impl Frame {
|
||||
fn from_function(func: Rc<Function>, args: Vec<StackValue>) -> Self {
|
||||
let mut locals = Vec::new();
|
||||
locals.resize(func.locals(), StackValue::Nothing);
|
||||
|
||||
Frame {
|
||||
func,
|
||||
args,
|
||||
locals,
|
||||
stack: Vec::new(),
|
||||
pc: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn pop_value(&mut self) -> Result<StackValue> {
|
||||
self.stack.pop().ok_or_else(|| VMErrorCode::StackUnderflow)
|
||||
}
|
||||
|
||||
fn pop_bool(&mut self) -> Result<bool> {
|
||||
match self.pop_value()? {
|
||||
StackValue::Bool(v) => Ok(v),
|
||||
v => Err(VMErrorCode::StackTypeMismatch(v, Type::Bool)),
|
||||
}
|
||||
}
|
||||
|
||||
fn pop_float(&mut self) -> Result<f64> {
|
||||
match self.pop_value()? {
|
||||
StackValue::Float(v) => Ok(v),
|
||||
v => Err(VMErrorCode::StackTypeMismatch(v, Type::F64)),
|
||||
}
|
||||
}
|
||||
|
||||
fn push_value(&mut self, v: StackValue) {
|
||||
self.stack.push(v)
|
||||
}
|
||||
|
||||
fn push_bool(&mut self, value: bool) {
|
||||
self.push_value(StackValue::Bool(value));
|
||||
}
|
||||
|
||||
fn push_float(&mut self, value: f64) {
|
||||
self.push_value(StackValue::Float(value));
|
||||
}
|
||||
|
||||
fn push_nothing(&mut self) {
|
||||
self.push_value(StackValue::Nothing);
|
||||
}
|
||||
|
||||
fn push_string(&mut self, v: Rc<str>) {
|
||||
self.push_value(StackValue::String(v))
|
||||
}
|
||||
|
||||
fn push_function(&mut self, v: Rc<Function>) {
|
||||
self.push_value(StackValue::Function(v));
|
||||
}
|
||||
|
||||
fn push_extern_function(&mut self, v: usize) {
|
||||
self.push_value(StackValue::ExternFunction(v));
|
||||
}
|
||||
|
||||
fn get_argument(&self, i: usize) -> Result<StackValue> {
|
||||
self.args
|
||||
.get(i)
|
||||
.map(|v| v.clone())
|
||||
.ok_or_else(|| VMErrorCode::ArgumentOutOfRange(i))
|
||||
}
|
||||
|
||||
fn get_local(&self, i: usize) -> Result<StackValue> {
|
||||
self.locals
|
||||
.get(i)
|
||||
.map(|v| v.clone())
|
||||
.ok_or_else(|| VMErrorCode::LocalOutOfRange(i))
|
||||
}
|
||||
|
||||
fn get_string(&self, i: usize) -> Result<Rc<str>> {
|
||||
let strings = self.func.strings();
|
||||
strings
|
||||
.get(i)
|
||||
.map(|v| v.clone())
|
||||
.ok_or_else(|| VMErrorCode::StringOutOfRange(i))
|
||||
}
|
||||
|
||||
fn store_local(&mut self, i: usize, v: StackValue) -> Result<()> {
|
||||
if i >= self.locals.len() {
|
||||
Err(VMErrorCode::LocalOutOfRange(i))
|
||||
} else {
|
||||
self.locals[i] = v;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn pop_function(&mut self) -> Result<FuncValue> {
|
||||
match self.pop_value()? {
|
||||
StackValue::Function(f) => Ok(FuncValue::Function(f)),
|
||||
StackValue::ExternFunction(i) => Ok(FuncValue::ExternFunction(i)),
|
||||
v => Err(VMErrorCode::StackExpectedFunction(v)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Context {
|
||||
module: Rc<Module>,
|
||||
globals: Vec<StackValue>,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
pub fn new(module: Rc<Module>) -> Context {
|
||||
let mut globals = Vec::new();
|
||||
globals.resize(module.globals, StackValue::Nothing);
|
||||
Context { module, globals }
|
||||
}
|
||||
|
||||
fn get_global(&self, i: usize) -> Result<StackValue> {
|
||||
self.globals
|
||||
.get(i)
|
||||
.map(|v| v.clone())
|
||||
.ok_or_else(|| VMErrorCode::GlobalOutOfRange(i)) // TODO: Test
|
||||
}
|
||||
|
||||
fn set_global(&mut self, i: usize, v: StackValue) -> Result<()> {
|
||||
if i >= self.globals.len() {
|
||||
Err(VMErrorCode::GlobalOutOfRange(i)) // TODO: Test
|
||||
} else {
|
||||
self.globals[i] = v;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn get_function(&self, i: usize) -> Result<Rc<Function>> {
|
||||
let functions = self.module.functions();
|
||||
functions
|
||||
.get(i)
|
||||
.map(|v| v.clone())
|
||||
.ok_or_else(|| VMErrorCode::FunctionOutOfRange(i)) // TODO: Test
|
||||
}
|
||||
}
|
||||
|
||||
enum Flow {
|
||||
Break,
|
||||
Continue,
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn eval_one(
|
||||
instruction: Instruction,
|
||||
index: &mut usize,
|
||||
c: &mut Context,
|
||||
f: &mut Frame,
|
||||
stack: &mut Vec<Frame>,
|
||||
) -> Result<Flow> {
|
||||
match instruction {
|
||||
Instruction::Panic => return Err(VMErrorCode::Panic),
|
||||
Instruction::BoolNot => {
|
||||
let value = f.pop_bool()?;
|
||||
f.push_bool(!value);
|
||||
}
|
||||
Instruction::Discard => {
|
||||
f.pop_value()?;
|
||||
}
|
||||
Instruction::FloatAdd => {
|
||||
let x = f.pop_float()?;
|
||||
let y = f.pop_float()?;
|
||||
f.push_float(x + y);
|
||||
}
|
||||
Instruction::FloatDivide => {
|
||||
let x = f.pop_float()?;
|
||||
let y = f.pop_float()?;
|
||||
if y == 0. {
|
||||
return Err(VMErrorCode::DivideByZero);
|
||||
}
|
||||
f.push_float(x / y);
|
||||
}
|
||||
Instruction::FloatMultiply => {
|
||||
let x = f.pop_float()?;
|
||||
let y = f.pop_float()?;
|
||||
f.push_float(x * y);
|
||||
}
|
||||
Instruction::FloatSubtract => {
|
||||
let x = f.pop_float()?;
|
||||
let y = f.pop_float()?;
|
||||
f.push_float(x - y);
|
||||
}
|
||||
Instruction::Jump(i) => {
|
||||
*index = i;
|
||||
}
|
||||
Instruction::JumpFalse(i) => {
|
||||
if !(f.pop_bool()?) {
|
||||
*index = i;
|
||||
}
|
||||
}
|
||||
Instruction::JumpTrue(i) => {
|
||||
if f.pop_bool()? {
|
||||
*index = i;
|
||||
}
|
||||
}
|
||||
Instruction::LoadArgument(i) => {
|
||||
let v = f.get_argument(i)?;
|
||||
f.push_value(v);
|
||||
}
|
||||
Instruction::LoadLocal(i) => {
|
||||
let v = f.get_local(i)?;
|
||||
f.push_value(v);
|
||||
}
|
||||
Instruction::LoadModule(i) => {
|
||||
let v = c.get_global(i)?;
|
||||
f.push_value(v);
|
||||
}
|
||||
Instruction::PushFalse => {
|
||||
f.push_bool(false);
|
||||
}
|
||||
Instruction::PushFloat(v) => {
|
||||
f.push_float(v);
|
||||
}
|
||||
Instruction::PushNothing => {
|
||||
f.push_nothing();
|
||||
}
|
||||
Instruction::PushString(s) => {
|
||||
let v = f.get_string(s)?;
|
||||
f.push_string(v);
|
||||
}
|
||||
Instruction::PushTrue => {
|
||||
f.push_bool(true);
|
||||
}
|
||||
Instruction::StoreLocal(i) => {
|
||||
let v = f.pop_value()?;
|
||||
f.store_local(i, v)?;
|
||||
}
|
||||
Instruction::StoreModule(i) => {
|
||||
let v = f.pop_value()?;
|
||||
c.set_global(i, v)?;
|
||||
}
|
||||
Instruction::LoadFunction(i) => {
|
||||
let v = c.get_function(i)?;
|
||||
f.push_function(v);
|
||||
}
|
||||
Instruction::LoadExternFunction(i) => {
|
||||
f.push_extern_function(i);
|
||||
}
|
||||
Instruction::Call(arg_count) => {
|
||||
let function = f.pop_function()?;
|
||||
let mut args = Vec::new();
|
||||
for _ in 0..arg_count {
|
||||
args.push(f.pop_value()?);
|
||||
}
|
||||
match function {
|
||||
FuncValue::Function(func) => {
|
||||
let mut frame = Frame::from_function(func, args);
|
||||
std::mem::swap(&mut frame, f);
|
||||
frame.pc = *index;
|
||||
stack.push(frame);
|
||||
*index = 0;
|
||||
}
|
||||
FuncValue::ExternFunction(_) => todo!(),
|
||||
}
|
||||
}
|
||||
Instruction::Return => match stack.pop() {
|
||||
Some(mut frame) => {
|
||||
// The return value is at the top of the stack already.
|
||||
std::mem::swap(&mut frame, f);
|
||||
*index = f.pc;
|
||||
}
|
||||
None => return Ok(Flow::Break),
|
||||
},
|
||||
}
|
||||
|
||||
Ok(Flow::Continue)
|
||||
}
|
||||
|
||||
pub fn eval(
|
||||
c: &mut Context,
|
||||
function: Rc<Function>,
|
||||
args: Vec<StackValue>,
|
||||
) -> std::result::Result<StackValue, VMError> {
|
||||
let mut stack = Vec::new();
|
||||
let mut f = Frame::from_function(function, args);
|
||||
|
||||
let mut index = 0;
|
||||
loop {
|
||||
let instructions = f.func.instructions();
|
||||
let instruction = instructions[index];
|
||||
index += 1;
|
||||
|
||||
match eval_one(instruction, &mut index, c, &mut f, &mut stack) {
|
||||
Ok(Flow::Break) => match f.pop_value() {
|
||||
Ok(v) => return Ok(v),
|
||||
Err(e) => {
|
||||
f.pc = index;
|
||||
stack.push(f);
|
||||
return Err(VMError {
|
||||
code: e,
|
||||
stack: stack.into(),
|
||||
});
|
||||
}
|
||||
},
|
||||
Ok(Flow::Continue) => (),
|
||||
Err(e) => {
|
||||
f.pc = index;
|
||||
stack.push(f);
|
||||
return Err(VMError {
|
||||
code: e,
|
||||
stack: stack.into(),
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ use fine::compiler::{compile, Function, Module};
|
|||
use fine::parser::SyntaxTree;
|
||||
use fine::semantics::{check, Error, Semantics, Type};
|
||||
use fine::tokens::Lines;
|
||||
use fine::vm::{eval, Context};
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::fmt::Write as _;
|
||||
|
||||
|
|
@ -258,4 +259,23 @@ fn assert_no_errors(tree: &SyntaxTree, lines: &Lines) {
|
|||
);
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn assert_eval_ok(tree: &SyntaxTree, lines: &Lines, expected: &str) {
|
||||
let semantics = Semantics::new(tree, lines);
|
||||
|
||||
let module = compile(&semantics);
|
||||
let main_function = module.functions[module.init].clone();
|
||||
|
||||
let mut context = Context::new(module.clone());
|
||||
match eval(&mut context, main_function, vec![]) {
|
||||
Ok(v) => {
|
||||
let actual = format!("{:?}", v);
|
||||
semantic_assert_eq!(&semantics, None, expected, &actual, "module evaluated");
|
||||
}
|
||||
Err(e) => {
|
||||
semantic_panic!(&semantics, None, "error occurred while running: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/generated_tests.rs"));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue