Finish up branch implementation
* while and unconditional loops are now supported fully * break and continue keywords for loop control * List::thunkify() has been broken into its own structure so it can be broken out further as necessary Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
This commit is contained in:
@@ -1,13 +1,9 @@
|
|||||||
check = fn(num) {
|
num = 10
|
||||||
if num < 10 {
|
|
||||||
println("The number is pretty small.")
|
|
||||||
} elif num < 100 {
|
|
||||||
println("That number is getting pretty big.")
|
|
||||||
} el {
|
|
||||||
println("Whoa! That number is huge!")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
check(1)
|
if num < 10 {
|
||||||
check(10)
|
println("The number is pretty small.")
|
||||||
check(100)
|
} elif num < 100 {
|
||||||
|
println("That number is getting pretty big.")
|
||||||
|
} el {
|
||||||
|
println("Whoa! That number is huge!")
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,11 +11,11 @@ pub enum BasicBlock {
|
|||||||
/// A linear block of executable instructions.
|
/// A linear block of executable instructions.
|
||||||
Block { exit: usize, block: Vec<Inst> },
|
Block { exit: usize, block: Vec<Inst> },
|
||||||
|
|
||||||
/// A branch, instructing the basic block where to jump for the true and false blocks.
|
/// A branch that will jump forward if the given condition is false.
|
||||||
Branch {
|
JumpForward(usize),
|
||||||
block_true: usize,
|
|
||||||
block_false: usize,
|
/// A branch that will jump backward if the given condition is true.
|
||||||
},
|
Jump(usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, PartialEq)]
|
#[derive(Debug, Default, Clone, PartialEq)]
|
||||||
@@ -46,10 +46,12 @@ impl BasicBlockList {
|
|||||||
// If the block is going to exit to someplace that *isn't* next in the sequence,
|
// If the block is going to exit to someplace that *isn't* next in the sequence,
|
||||||
// there will be a conditional jump added.
|
// there will be a conditional jump added.
|
||||||
BasicBlock::Block { block, .. } => block.len() + 1,
|
BasicBlock::Block { block, .. } => block.len() + 1,
|
||||||
// 2 because of:
|
// 1 because of:
|
||||||
// - conditional jump if true at start
|
// - conditional jump to false block
|
||||||
// - unconditional jump to false block
|
BasicBlock::JumpForward(_) => 1,
|
||||||
BasicBlock::Branch { .. } => 2,
|
// 1 because of:
|
||||||
|
// - conditional jump to false block
|
||||||
|
BasicBlock::Jump(_) => 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
addr += inst_len;
|
addr += inst_len;
|
||||||
@@ -69,16 +71,13 @@ impl BasicBlockList {
|
|||||||
body.push(Inst::Jump(addr));
|
body.push(Inst::Jump(addr));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BasicBlock::Branch {
|
BasicBlock::JumpForward(block) => {
|
||||||
block_true,
|
let addr = addr_rev[&block];
|
||||||
block_false,
|
body.push(Inst::JumpFalse(addr));
|
||||||
} => {
|
}
|
||||||
// insert conditional jump to true statement, and unconditional jump to false
|
BasicBlock::Jump(block) => {
|
||||||
// statement
|
let addr = addr_rev[&block];
|
||||||
let addr_true = addr_rev[&block_true];
|
body.push(Inst::Jump(addr));
|
||||||
let addr_false = addr_rev[&block_false];
|
|
||||||
body.push(Inst::JumpTrue(addr_true));
|
|
||||||
body.push(Inst::Jump(addr_false));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -102,13 +101,9 @@ impl BasicBlockList {
|
|||||||
let mut last_block_index = 0;
|
let mut last_block_index = 0;
|
||||||
for (new_index, (index, block)) in self.blocks.into_iter().enumerate() {
|
for (new_index, (index, block)) in self.blocks.into_iter().enumerate() {
|
||||||
// search for the largest block index referred to in here - that is the last block
|
// search for the largest block index referred to in here - that is the last block
|
||||||
match block {
|
last_block_index = match block {
|
||||||
BasicBlock::Block { exit, .. } => last_block_index = exit.max(last_block_index),
|
BasicBlock::Block { exit, .. } | BasicBlock::JumpForward(exit) | BasicBlock::Jump(exit) => exit,
|
||||||
BasicBlock::Branch {
|
};
|
||||||
block_true,
|
|
||||||
block_false,
|
|
||||||
} => last_block_index = block_true.max(block_false.max(last_block_index)),
|
|
||||||
}
|
|
||||||
blocks.push(block);
|
blocks.push(block);
|
||||||
entry_map.insert(index, new_index);
|
entry_map.insert(index, new_index);
|
||||||
}
|
}
|
||||||
@@ -123,13 +118,8 @@ impl BasicBlockList {
|
|||||||
exit: entry_map[&exit],
|
exit: entry_map[&exit],
|
||||||
block,
|
block,
|
||||||
},
|
},
|
||||||
BasicBlock::Branch {
|
BasicBlock::JumpForward(block) => BasicBlock::JumpForward(entry_map[&block]),
|
||||||
block_true,
|
BasicBlock::Jump(block) => BasicBlock::Jump(entry_map[&block]),
|
||||||
block_false,
|
|
||||||
} => BasicBlock::Branch {
|
|
||||||
block_true: entry_map[&block_true],
|
|
||||||
block_false: entry_map[&block_false],
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ pub enum List {
|
|||||||
body: Box<List>,
|
body: Box<List>,
|
||||||
el: Box<List>,
|
el: Box<List>,
|
||||||
},
|
},
|
||||||
|
Loop {
|
||||||
|
cond: Option<Box<List>>,
|
||||||
|
body: Box<List>,
|
||||||
|
},
|
||||||
Lambda {
|
Lambda {
|
||||||
params: Vec<String>,
|
params: Vec<String>,
|
||||||
expr: Box<List>,
|
expr: Box<List>,
|
||||||
@@ -34,6 +38,8 @@ pub enum List {
|
|||||||
name: String,
|
name: String,
|
||||||
value: Box<List>,
|
value: Box<List>,
|
||||||
},
|
},
|
||||||
|
Break,
|
||||||
|
Continue,
|
||||||
Return(Box<List>),
|
Return(Box<List>),
|
||||||
Call(Box<List>, Vec<List>),
|
Call(Box<List>, Vec<List>),
|
||||||
Do(Vec<List>),
|
Do(Vec<List>),
|
||||||
@@ -41,164 +47,7 @@ pub enum List {
|
|||||||
|
|
||||||
impl List {
|
impl List {
|
||||||
pub fn thunkify(self, compile: &mut Compile) -> Thunk {
|
pub fn thunkify(self, compile: &mut Compile) -> Thunk {
|
||||||
match self {
|
Thunkify { compile }.thunkify(self)
|
||||||
List::Sym(sym) => {
|
|
||||||
Inst::PushSym(global_sym(sym.clone())).into()
|
|
||||||
}
|
|
||||||
List::Ident(ident) => {
|
|
||||||
// Small gotcha:
|
|
||||||
// Looking up a name will either result in a local or a global lookup. If it's
|
|
||||||
// a local variable first, then it's determined as a local and that's the end
|
|
||||||
// of the story... except when we're at the top scope level, we're both "local"
|
|
||||||
// *and* global.
|
|
||||||
//
|
|
||||||
// This checks to make sure that it's both a local variable and that there's more
|
|
||||||
// than one scope layer.
|
|
||||||
let sym = global_sym(ident.to_string());
|
|
||||||
if let (true, Some(local)) = (
|
|
||||||
compile.scope().layers_len() > 1,
|
|
||||||
compile.lookup_local(sym),
|
|
||||||
) {
|
|
||||||
// get local
|
|
||||||
Inst::LoadLocal(local).into()
|
|
||||||
} else {
|
|
||||||
// get or create global
|
|
||||||
// create_global only makes a new global with this symbol name if one has not
|
|
||||||
// been created yet
|
|
||||||
let global = compile.create_global(sym);
|
|
||||||
Inst::LoadGlobal(global).into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
List::Int(int) => {
|
|
||||||
// push const
|
|
||||||
let (hdl, _) = compile.const_int(int);
|
|
||||||
Inst::PushConst(hdl).into()
|
|
||||||
}
|
|
||||||
List::String(s) => {
|
|
||||||
// push const
|
|
||||||
let (hdl, _) = compile.const_str(s);
|
|
||||||
Inst::PushConst(hdl).into()
|
|
||||||
}
|
|
||||||
List::If { cond, body, el, } => {
|
|
||||||
let mut preamble = cond.thunkify(compile);
|
|
||||||
// push CheckTruth here since there's not much of a better place to do so
|
|
||||||
preamble.push_thunk(vec![
|
|
||||||
Inst::GetAttr(BOOL_MEMBER_NAME.sym),
|
|
||||||
Inst::Call(0),
|
|
||||||
Inst::CheckTruth,
|
|
||||||
]);
|
|
||||||
let thunk_true = body.thunkify(compile).into();
|
|
||||||
let thunk_false = el.thunkify(compile).into();
|
|
||||||
Thunk::Branch {
|
|
||||||
preamble: preamble.into(),
|
|
||||||
thunk_true,
|
|
||||||
thunk_false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
List::Lambda { params, expr } => {
|
|
||||||
// TODO(fun) : need captures for functions, built dynamically (or statically?)
|
|
||||||
// - static is not possible, since captures are *created* at runtime, and there's no
|
|
||||||
// instruction that will look up just one scope level - it's either locals or globals.
|
|
||||||
// - an entire "create function" instruction is probably the best way to solve it, don't
|
|
||||||
// try to be clever, just implement it like that (since I mean, python does too...)
|
|
||||||
|
|
||||||
// - push const
|
|
||||||
// (functions are unique const values so a new function will be created for every literal
|
|
||||||
// function defined in code)
|
|
||||||
|
|
||||||
// This is pretty much the only place where a new scope layer gets pushed beyond the start
|
|
||||||
// of the program
|
|
||||||
compile.push_scope_layer();
|
|
||||||
let params_len = params.len();
|
|
||||||
for param in params.into_iter() {
|
|
||||||
let sym = global_sym(param);
|
|
||||||
compile.create_local(sym);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compile function body
|
|
||||||
let mut code = expr.thunkify(compile)
|
|
||||||
.flatten()
|
|
||||||
.to_vec();
|
|
||||||
|
|
||||||
// If the last instruction is not a return, or if there are no instructions, then return
|
|
||||||
// :nil value.
|
|
||||||
if !matches!(code.last(), Some(Inst::Return)) {
|
|
||||||
code.push(Inst::PushSym(NIL_NAME.sym));
|
|
||||||
code.push(Inst::Return);
|
|
||||||
}
|
|
||||||
|
|
||||||
// remap (Sym -> Name) to be (Name -> Sym) and make sure it's all in order.
|
|
||||||
let scope_locals: BTreeMap<_, _> = compile
|
|
||||||
.pop_scope_layer()
|
|
||||||
.unwrap()
|
|
||||||
.into_iter()
|
|
||||||
.map(|(sym, name)| (name, sym))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// this should be in numeric order since:
|
|
||||||
// 1. locals are created exactly once or looked up
|
|
||||||
// 2. scope_locals is a btreemap, keyed by names, which are in order from 0..N
|
|
||||||
let locals: FunLocals = scope_locals
|
|
||||||
.into_iter()
|
|
||||||
.enumerate()
|
|
||||||
.map(|(index, (name, sym))| {
|
|
||||||
assert_eq!(index, name.index());
|
|
||||||
sym
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let (hdl, _fun) =
|
|
||||||
compile.push_const(UserFun::new_obj(code, locals, params_len));
|
|
||||||
|
|
||||||
// TODO(compile) : determine return value at the end of the body (preferably at parse-time)
|
|
||||||
|
|
||||||
// oh yeah, we were compiling a function body weren't we
|
|
||||||
Inst::PushConst(hdl).into()
|
|
||||||
}
|
|
||||||
List::Assign { name, rhs, } => {
|
|
||||||
let mut thunk = rhs.thunkify(compile);
|
|
||||||
let sym = global_sym(name.to_string());
|
|
||||||
if let Some(local) = compile.lookup_local(sym) {
|
|
||||||
thunk.push(Inst::PopLocal(Some(local)));
|
|
||||||
} else {
|
|
||||||
let global = compile.lookup_global(sym)
|
|
||||||
.expect("name expected to exist someplace(?)");
|
|
||||||
thunk.push(Inst::PopGlobal(Some(global)));
|
|
||||||
}
|
|
||||||
thunk
|
|
||||||
}
|
|
||||||
List::Access { expr, access, } => {
|
|
||||||
let mut thunk = expr.thunkify(compile);
|
|
||||||
thunk.push(Inst::GetAttr(global_sym(access.to_string())));
|
|
||||||
thunk
|
|
||||||
}
|
|
||||||
List::Update { expr, name, value, } => {
|
|
||||||
let mut thunk = expr.thunkify(compile);
|
|
||||||
let (hdl, _) = compile.const_str(name);
|
|
||||||
thunk.push(Inst::PushConst(hdl));
|
|
||||||
thunk.push_thunk(value.thunkify(compile));
|
|
||||||
thunk
|
|
||||||
}
|
|
||||||
List::Return(expr) => {
|
|
||||||
let mut thunk = expr.thunkify(compile);
|
|
||||||
thunk.push(Inst::Return);
|
|
||||||
thunk
|
|
||||||
}
|
|
||||||
List::Call(fun, args) => {
|
|
||||||
let argc = args.len();
|
|
||||||
let mut thunk = fun.thunkify(compile);
|
|
||||||
for arg in args {
|
|
||||||
thunk.push_thunk(arg.thunkify(compile));
|
|
||||||
}
|
|
||||||
thunk.push(Inst::Call(argc));
|
|
||||||
thunk
|
|
||||||
}
|
|
||||||
List::Do(stmts) => {
|
|
||||||
Thunk::List(stmts.into_iter()
|
|
||||||
.map(|stmt| stmt.thunkify(compile))
|
|
||||||
.collect())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,7 +89,15 @@ impl<'c> Visit for CompileList<'c> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn visit_stmt(&mut self, stmt: &Stmt) -> Self::Out {
|
fn visit_stmt(&mut self, stmt: &Stmt) -> Self::Out {
|
||||||
DefaultAccept::default_accept(stmt, self)
|
match stmt {
|
||||||
|
Stmt::Expr(e) => self.visit_expr(e),
|
||||||
|
Stmt::Assign(a) => self.visit_assign_stmt(a),
|
||||||
|
Stmt::Return(r) => self.visit_return_stmt(r),
|
||||||
|
Stmt::Loop(b) => self.visit_loop_stmt(b),
|
||||||
|
Stmt::While(c) => self.visit_while_stmt(c),
|
||||||
|
Stmt::Break => List::Break,
|
||||||
|
Stmt::Continue => List::Continue,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visit_assign_stmt(&mut self, assign: &AssignStmt) -> Self::Out {
|
fn visit_assign_stmt(&mut self, assign: &AssignStmt) -> Self::Out {
|
||||||
@@ -274,6 +131,20 @@ impl<'c> Visit for CompileList<'c> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn visit_while_stmt(&mut self, cond_body: &CondBody) -> Self::Out {
|
||||||
|
List::Loop {
|
||||||
|
cond: Some(Box::new(self.visit_expr(&cond_body.cond))),
|
||||||
|
body: Box::new(self.visit_body(&cond_body.body)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_loop_stmt(&mut self, body: &Body) -> Self::Out {
|
||||||
|
List::Loop {
|
||||||
|
cond: None,
|
||||||
|
body: Box::new(self.visit_body(body)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn visit_expr(&mut self, expr: &Expr) -> Self::Out {
|
fn visit_expr(&mut self, expr: &Expr) -> Self::Out {
|
||||||
DefaultAccept::default_accept(expr, self)
|
DefaultAccept::default_accept(expr, self)
|
||||||
}
|
}
|
||||||
@@ -378,3 +249,185 @@ impl<'c> Visit for CompileList<'c> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct Thunkify<'c> {
|
||||||
|
compile: &'c mut Compile,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Thunkify<'_> {
|
||||||
|
pub fn thunkify(&mut self, list: List) -> Thunk {
|
||||||
|
match list {
|
||||||
|
List::Sym(sym) => {
|
||||||
|
Inst::PushSym(global_sym(sym.clone())).into()
|
||||||
|
}
|
||||||
|
List::Ident(ident) => {
|
||||||
|
// Small gotcha:
|
||||||
|
// Looking up a name will either result in a local or a global lookup. If it's
|
||||||
|
// a local variable first, then it's determined as a local and that's the end
|
||||||
|
// of the story... except when we're at the top scope level, we're both "local"
|
||||||
|
// *and* global.
|
||||||
|
//
|
||||||
|
// This checks to make sure that it's both a local variable and that there's more
|
||||||
|
// than one scope layer.
|
||||||
|
let sym = global_sym(ident.to_string());
|
||||||
|
if let (true, Some(local)) = (
|
||||||
|
self.compile.scope().layers_len() > 1,
|
||||||
|
self.compile.lookup_local(sym),
|
||||||
|
) {
|
||||||
|
// get local
|
||||||
|
Inst::LoadLocal(local).into()
|
||||||
|
} else {
|
||||||
|
// get or create global
|
||||||
|
// create_global only makes a new global with this symbol name if one has not
|
||||||
|
// been created yet
|
||||||
|
let global = self.compile.create_global(sym);
|
||||||
|
Inst::LoadGlobal(global).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
List::Int(int) => {
|
||||||
|
// push const
|
||||||
|
let (hdl, _) = self.compile.const_int(int);
|
||||||
|
Inst::PushConst(hdl).into()
|
||||||
|
}
|
||||||
|
List::String(s) => {
|
||||||
|
// push const
|
||||||
|
let (hdl, _) = self.compile.const_str(s);
|
||||||
|
Inst::PushConst(hdl).into()
|
||||||
|
}
|
||||||
|
List::If { cond, body, el, } => {
|
||||||
|
let mut preamble = self.thunkify(*cond);
|
||||||
|
// push CheckTruth here since there's not much of a better place to do so
|
||||||
|
preamble.push_thunk(vec![
|
||||||
|
Inst::GetAttr(BOOL_MEMBER_NAME.sym),
|
||||||
|
Inst::Call(0),
|
||||||
|
Inst::CheckTruth,
|
||||||
|
]);
|
||||||
|
let thunk_true = self.thunkify(*body).into();
|
||||||
|
let thunk_false = self.thunkify(*el).into();
|
||||||
|
Thunk::Branch {
|
||||||
|
preamble: preamble.into(),
|
||||||
|
thunk_true,
|
||||||
|
thunk_false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
List::Loop { cond, body, } => {
|
||||||
|
let preamble = cond.map(|list| {
|
||||||
|
let mut preamble = self.thunkify(*list);
|
||||||
|
preamble.push_thunk(vec![
|
||||||
|
Inst::GetAttr(BOOL_MEMBER_NAME.sym),
|
||||||
|
Inst::Call(0),
|
||||||
|
Inst::CheckTruth,
|
||||||
|
]);
|
||||||
|
preamble.into()
|
||||||
|
});
|
||||||
|
let body = self.thunkify(*body).into();
|
||||||
|
Thunk::Loop { preamble, body, }
|
||||||
|
}
|
||||||
|
List::Lambda { params, expr } => {
|
||||||
|
// TODO(fun) : need captures for functions, built dynamically (or statically?)
|
||||||
|
// - static is not possible, since captures are *created* at runtime, and there's no
|
||||||
|
// instruction that will look up just one scope level - it's either locals or globals.
|
||||||
|
// - an entire "create function" instruction is probably the best way to solve it, don't
|
||||||
|
// try to be clever, just implement it like that (since I mean, python does too...)
|
||||||
|
|
||||||
|
// - push const
|
||||||
|
// (functions are unique const values so a new function will be created for every literal
|
||||||
|
// function defined in code)
|
||||||
|
|
||||||
|
// This is pretty much the only place where a new scope layer gets pushed beyond the start
|
||||||
|
// of the program
|
||||||
|
self.compile.push_scope_layer();
|
||||||
|
let params_len = params.len();
|
||||||
|
for param in params.into_iter() {
|
||||||
|
let sym = global_sym(param);
|
||||||
|
self.compile.create_local(sym);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile function body
|
||||||
|
let mut code = self.thunkify(*expr)
|
||||||
|
.flatten()
|
||||||
|
.to_vec();
|
||||||
|
|
||||||
|
// If the last instruction is not a return, or if there are no instructions, then return
|
||||||
|
// :nil value.
|
||||||
|
if !matches!(code.last(), Some(Inst::Return)) {
|
||||||
|
code.push(Inst::PushSym(NIL_NAME.sym));
|
||||||
|
code.push(Inst::Return);
|
||||||
|
}
|
||||||
|
|
||||||
|
// remap (Sym -> Name) to be (Name -> Sym) and make sure it's all in order.
|
||||||
|
let scope_locals: BTreeMap<_, _> = self.compile
|
||||||
|
.pop_scope_layer()
|
||||||
|
.unwrap()
|
||||||
|
.into_iter()
|
||||||
|
.map(|(sym, name)| (name, sym))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// this should be in numeric order since:
|
||||||
|
// 1. locals are created exactly once or looked up
|
||||||
|
// 2. scope_locals is a btreemap, keyed by names, which are in order from 0..N
|
||||||
|
let locals: FunLocals = scope_locals
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(index, (name, sym))| {
|
||||||
|
assert_eq!(index, name.index());
|
||||||
|
sym
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let (hdl, _fun) =
|
||||||
|
self.compile.push_const(UserFun::new_obj(code, locals, params_len));
|
||||||
|
|
||||||
|
// TODO(compile) : determine return value at the end of the body (preferably at parse-time)
|
||||||
|
|
||||||
|
// oh yeah, we were compiling a function body weren't we
|
||||||
|
Inst::PushConst(hdl).into()
|
||||||
|
}
|
||||||
|
List::Assign { name, rhs, } => {
|
||||||
|
let mut thunk = self.thunkify(*rhs);
|
||||||
|
let sym = global_sym(name.to_string());
|
||||||
|
if let Some(local) = self.compile.lookup_local(sym) {
|
||||||
|
thunk.push(Inst::PopLocal(Some(local)));
|
||||||
|
} else {
|
||||||
|
let global = self.compile.lookup_global(sym)
|
||||||
|
.expect("name expected to exist someplace(?)");
|
||||||
|
thunk.push(Inst::PopGlobal(Some(global)));
|
||||||
|
}
|
||||||
|
thunk
|
||||||
|
}
|
||||||
|
List::Access { expr, access, } => {
|
||||||
|
let mut thunk = self.thunkify(*expr);
|
||||||
|
thunk.push(Inst::GetAttr(global_sym(access.to_string())));
|
||||||
|
thunk
|
||||||
|
}
|
||||||
|
List::Update { expr, name, value, } => {
|
||||||
|
let mut thunk = self.thunkify(*expr);
|
||||||
|
let (hdl, _) = self.compile.const_str(name);
|
||||||
|
thunk.push(Inst::PushConst(hdl));
|
||||||
|
thunk.push_thunk(self.thunkify(*value));
|
||||||
|
thunk
|
||||||
|
}
|
||||||
|
List::Return(expr) => {
|
||||||
|
let mut thunk = self.thunkify(*expr);
|
||||||
|
thunk.push(Inst::Return);
|
||||||
|
thunk
|
||||||
|
}
|
||||||
|
List::Call(fun, args) => {
|
||||||
|
let argc = args.len();
|
||||||
|
let mut thunk = self.thunkify(*fun);
|
||||||
|
for arg in args {
|
||||||
|
thunk.push_thunk(self.thunkify(arg));
|
||||||
|
}
|
||||||
|
thunk.push(Inst::Call(argc));
|
||||||
|
thunk
|
||||||
|
}
|
||||||
|
List::Do(stmts) => {
|
||||||
|
Thunk::List(stmts.into_iter()
|
||||||
|
.map(|stmt| self.thunkify(stmt))
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
List::Break => Thunk::Break,
|
||||||
|
List::Continue => Thunk::Continue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -46,6 +46,12 @@ impl Visit for CollectLocals<'_> {
|
|||||||
fn visit_return_stmt(&mut self, ret: &ReturnStmt) -> Self::Out {
|
fn visit_return_stmt(&mut self, ret: &ReturnStmt) -> Self::Out {
|
||||||
DefaultAccept::default_accept(ret, self);
|
DefaultAccept::default_accept(ret, self);
|
||||||
}
|
}
|
||||||
|
fn visit_while_stmt(&mut self, cond_body: &CondBody) -> Self::Out {
|
||||||
|
DefaultAccept::default_accept(cond_body, self);
|
||||||
|
}
|
||||||
|
fn visit_loop_stmt(&mut self, body: &Body) -> Self::Out {
|
||||||
|
DefaultAccept::default_accept(body, self);
|
||||||
|
}
|
||||||
fn visit_lhs_expr(&mut self, lhs_expr: &LhsExpr) -> Self::Out {
|
fn visit_lhs_expr(&mut self, lhs_expr: &LhsExpr) -> Self::Out {
|
||||||
match lhs_expr {
|
match lhs_expr {
|
||||||
LhsExpr::Name(name) => {
|
LhsExpr::Name(name) => {
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
use crate::{
|
use crate::{compile::basic_block::*, vm::inst::*};
|
||||||
compile::basic_block::*,
|
|
||||||
vm::inst::*,
|
|
||||||
};
|
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
|
||||||
/// A basic block of VM code.
|
/// A basic block of VM code.
|
||||||
@@ -17,9 +14,9 @@ pub enum Thunk {
|
|||||||
/// A list of thunks.
|
/// A list of thunks.
|
||||||
List(Vec<Thunk>),
|
List(Vec<Thunk>),
|
||||||
|
|
||||||
/// Based on the conditional flag in the VM, code for one of these thunks will be executed.
|
/// Based on the a condition, code for one of these thunks will be executed.
|
||||||
///
|
///
|
||||||
/// The conditional flag is expected to be set upon entry to this thunk.
|
/// The conditional flag is set in the `preamble` section of this thunk.
|
||||||
///
|
///
|
||||||
/// Only one of these thunks will be executed. At the end of either thunk, the program will
|
/// Only one of these thunks will be executed. At the end of either thunk, the program will
|
||||||
/// continue at the address following this branch.
|
/// continue at the address following this branch.
|
||||||
@@ -29,16 +26,16 @@ pub enum Thunk {
|
|||||||
thunk_false: Box<Thunk>,
|
thunk_false: Box<Thunk>,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Based on the conditional flag in the VM, code for this loop will continue to execute.
|
Loop {
|
||||||
///
|
preamble: Option<Box<Thunk>>,
|
||||||
/// The conditional flag is expected to be set upon entry to this thunk.
|
body: Box<Thunk>,
|
||||||
///
|
},
|
||||||
/// At the start of the body, the condition flag is initially checked. If it is not true,
|
|
||||||
/// the program jumps to the end of the body and continues.
|
/// Return to the top of the currently executing loop.
|
||||||
///
|
Continue,
|
||||||
/// At the end of the body, the program jumps back to the start where the condition is checked
|
|
||||||
/// again.
|
/// Exit to the bottom of the currently executing loop.
|
||||||
Loop(Box<Thunk>),
|
Break,
|
||||||
|
|
||||||
/// A placeholder/default thunk that compiles to nothing.
|
/// A placeholder/default thunk that compiles to nothing.
|
||||||
Nop,
|
Nop,
|
||||||
@@ -97,9 +94,22 @@ impl Thunk {
|
|||||||
preamble,
|
preamble,
|
||||||
thunk_true,
|
thunk_true,
|
||||||
thunk_false,
|
thunk_false,
|
||||||
} => preamble.basic_block_count() + thunk_true.basic_block_count() + thunk_false.basic_block_count() + 1,
|
// length is + 1 for BranchBlock after the preamble
|
||||||
// length is thunk, + 1 for branch at the start of the loop
|
} => {
|
||||||
Thunk::Loop(thunk) => thunk.basic_block_count() + 1,
|
preamble.basic_block_count()
|
||||||
|
+ thunk_true.basic_block_count()
|
||||||
|
+ thunk_false.basic_block_count()
|
||||||
|
+ 1
|
||||||
|
}
|
||||||
|
// length is + 2 for:
|
||||||
|
// * BranchBlock at the start of the loop after the preamble
|
||||||
|
// * Jump at the end of the loop to the start of the preamble
|
||||||
|
Thunk::Loop { preamble: Some(preamble), body } => preamble.basic_block_count() + body.basic_block_count() + 2,
|
||||||
|
// length is + 1 for:
|
||||||
|
// * Jump at the end of the loop to the start of the body
|
||||||
|
Thunk::Loop { preamble: None, body } => body.basic_block_count() + 1,
|
||||||
|
Thunk::Continue => 1,
|
||||||
|
Thunk::Break => 1,
|
||||||
Thunk::Nop => 0,
|
Thunk::Nop => 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -136,6 +146,8 @@ impl From<Vec<Thunk>> for Thunk {
|
|||||||
struct Flatten {
|
struct Flatten {
|
||||||
// using a btreemap instead of a vec because we can insert things out-of-order
|
// using a btreemap instead of a vec because we can insert things out-of-order
|
||||||
blocks: BasicBlockList,
|
blocks: BasicBlockList,
|
||||||
|
// loop start/end addresses
|
||||||
|
loop_anchors: Vec<(usize, usize)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -151,11 +163,18 @@ impl Flatten {
|
|||||||
assert_eq!(self.blocks.len(), last_block);
|
assert_eq!(self.blocks.len(), last_block);
|
||||||
// push an extra null block at the very end so that anything pointing to `last_block` will
|
// push an extra null block at the very end so that anything pointing to `last_block` will
|
||||||
// have a valid block index
|
// have a valid block index
|
||||||
self.blocks.insert(last_block, BasicBlock::Block { exit: last_block + 1, block: Default::default() });
|
self.blocks.insert(
|
||||||
|
last_block,
|
||||||
|
BasicBlock::Block {
|
||||||
|
exit: last_block + 1,
|
||||||
|
block: Default::default(),
|
||||||
|
},
|
||||||
|
);
|
||||||
self.blocks
|
self.blocks
|
||||||
}
|
}
|
||||||
|
|
||||||
fn flatten_next(&mut self, next_block: usize, thunk: Thunk) {
|
fn flatten_next(&mut self, next_block: usize, thunk: Thunk) {
|
||||||
|
let end_block = self.this_block() + thunk.basic_block_count();
|
||||||
match thunk {
|
match thunk {
|
||||||
Thunk::Body(thunk) => {
|
Thunk::Body(thunk) => {
|
||||||
let this_block = self.this_block();
|
let this_block = self.this_block();
|
||||||
@@ -178,14 +197,17 @@ impl Flatten {
|
|||||||
}
|
}
|
||||||
self.flatten_next(next_block, tail);
|
self.flatten_next(next_block, tail);
|
||||||
}
|
}
|
||||||
// don't assert_eq here because the "next_block" really should be interpreted as an
|
|
||||||
// "exit_block"
|
|
||||||
}
|
}
|
||||||
Thunk::Branch {
|
Thunk::Branch {
|
||||||
preamble,
|
preamble,
|
||||||
thunk_true,
|
thunk_true,
|
||||||
thunk_false,
|
thunk_false,
|
||||||
} => {
|
} => {
|
||||||
|
// Branch:
|
||||||
|
// + preamble blocks
|
||||||
|
// + branch block
|
||||||
|
// + true blocks
|
||||||
|
// + false blocks
|
||||||
let preamble_block = self.this_block();
|
let preamble_block = self.this_block();
|
||||||
let branch_block = preamble_block + preamble.basic_block_count();
|
let branch_block = preamble_block + preamble.basic_block_count();
|
||||||
let block_true = branch_block + 1;
|
let block_true = branch_block + 1;
|
||||||
@@ -194,20 +216,84 @@ impl Flatten {
|
|||||||
assert_eq!(self.this_block(), branch_block);
|
assert_eq!(self.this_block(), branch_block);
|
||||||
self.blocks.insert(
|
self.blocks.insert(
|
||||||
branch_block,
|
branch_block,
|
||||||
BasicBlock::Branch {
|
BasicBlock::JumpForward(block_false),
|
||||||
block_true,
|
|
||||||
block_false,
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
assert_eq!(self.this_block(), block_true);
|
assert_eq!(self.this_block(), block_true);
|
||||||
self.flatten_next(next_block, *thunk_true);
|
self.flatten_next(next_block, *thunk_true);
|
||||||
assert_eq!(self.this_block(), block_false);
|
assert_eq!(self.this_block(), block_false);
|
||||||
self.flatten_next(next_block, *thunk_false);
|
self.flatten_next(next_block, *thunk_false);
|
||||||
assert_eq!(self.this_block(), next_block);
|
|
||||||
}
|
}
|
||||||
Thunk::Loop(_) => todo!(),
|
Thunk::Loop { preamble, body } => {
|
||||||
|
// Conditional loop:
|
||||||
|
// + preamble blocks
|
||||||
|
// + branch block
|
||||||
|
// + body
|
||||||
|
// + "jump back to preamble" block
|
||||||
|
let preamble_block = self.this_block();
|
||||||
|
let branch_block;
|
||||||
|
let body_block;
|
||||||
|
/*
|
||||||
|
let branch_block = preamble_block
|
||||||
|
+ preamble
|
||||||
|
.as_ref()
|
||||||
|
.map(|thunk| thunk.basic_block_count())
|
||||||
|
.unwrap_or(0);
|
||||||
|
let body_block = branch_block + 1;
|
||||||
|
let jump_block = body_block + body.basic_block_count();
|
||||||
|
*/
|
||||||
|
if let Some(preamble) = preamble {
|
||||||
|
branch_block = preamble_block + preamble.basic_block_count();
|
||||||
|
body_block = branch_block + 1;
|
||||||
|
self.flatten_next(branch_block, *preamble);
|
||||||
|
assert_eq!(self.this_block(), branch_block);
|
||||||
|
self.blocks.insert(
|
||||||
|
branch_block,
|
||||||
|
// TODO - JumpForward should be Jump if this is a while loop.
|
||||||
|
// This is only the case when `preamble` above is None.
|
||||||
|
BasicBlock::JumpForward(next_block),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
branch_block = preamble_block;
|
||||||
|
body_block = branch_block;
|
||||||
|
}
|
||||||
|
let jump_block = body_block + body.basic_block_count();
|
||||||
|
assert_eq!(self.this_block(), body_block);
|
||||||
|
self.loop_anchors.push((preamble_block, end_block));
|
||||||
|
self.flatten_next(jump_block, *body);
|
||||||
|
self.loop_anchors.pop().expect("mismatched loop anchors");
|
||||||
|
assert_eq!(self.this_block(), jump_block);
|
||||||
|
self.blocks.insert(
|
||||||
|
jump_block,
|
||||||
|
BasicBlock::Jump(preamble_block),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Thunk::Continue => {
|
||||||
|
let (start, _end) = self.loop_anchors.last()
|
||||||
|
.copied()
|
||||||
|
.expect("TODO: throw an error for calling a 'continue' outside of a loop");
|
||||||
|
self.blocks.insert(
|
||||||
|
self.this_block(),
|
||||||
|
BasicBlock::Block {
|
||||||
|
exit: start,
|
||||||
|
block: Default::default(),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Thunk::Break => {
|
||||||
|
let (_start, end) = self.loop_anchors.last()
|
||||||
|
.copied()
|
||||||
|
.expect("TODO: throw an error for calling a 'break' outside of a loop");
|
||||||
|
self.blocks.insert(
|
||||||
|
self.this_block(),
|
||||||
|
BasicBlock::Block {
|
||||||
|
exit: end,
|
||||||
|
block: Default::default(),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
Thunk::Nop => {}
|
Thunk::Nop => {}
|
||||||
}
|
}
|
||||||
|
assert_eq!(self.this_block(), end_block);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn this_block(&self) -> usize {
|
fn this_block(&self) -> usize {
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ impl UserFun {
|
|||||||
("(discarded)".to_string(), String::new())
|
("(discarded)".to_string(), String::new())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Inst::Jump(addr) | Inst::JumpTrue(addr) => (format!("{:0addr_w$x}", addr, addr_w = addr_w), String::new()),
|
Inst::Jump(addr) | Inst::JumpTrue(addr) | Inst::JumpFalse(addr) => (format!("{:0addr_w$x}", addr, addr_w = addr_w), String::new()),
|
||||||
Inst::Call(argc) => (argc.to_string(), String::new()),
|
Inst::Call(argc) => (argc.to_string(), String::new()),
|
||||||
_ => (String::new(), String::new()),
|
_ => (String::new(), String::new()),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -24,6 +24,10 @@ pub enum Stmt {
|
|||||||
Expr(Expr),
|
Expr(Expr),
|
||||||
Assign(AssignStmt),
|
Assign(AssignStmt),
|
||||||
Return(ReturnStmt),
|
Return(ReturnStmt),
|
||||||
|
Loop(Body),
|
||||||
|
While(CondBody),
|
||||||
|
Continue,
|
||||||
|
Break,
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -38,12 +42,15 @@ impl<V: Visit> Accept<V> for Stmt {
|
|||||||
//
|
//
|
||||||
// impl DefaultAccept for Stmt
|
// impl DefaultAccept for Stmt
|
||||||
//
|
//
|
||||||
impl<V: Visit> DefaultAccept<V> for Stmt {
|
impl<V: Visit<Out=()>> DefaultAccept<V> for Stmt {
|
||||||
fn default_accept(&self, visitor: &mut V) -> V::Out {
|
fn default_accept(&self, visitor: &mut V) -> V::Out {
|
||||||
match self {
|
match self {
|
||||||
Stmt::Expr(e) => e.accept(visitor),
|
Stmt::Expr(e) => e.accept(visitor),
|
||||||
Stmt::Assign(a) => a.accept(visitor),
|
Stmt::Assign(a) => a.accept(visitor),
|
||||||
Stmt::Return(r) => r.accept(visitor),
|
Stmt::Return(r) => r.accept(visitor),
|
||||||
|
Stmt::Loop(b) => b.accept(visitor),
|
||||||
|
Stmt::While(c) => c.accept(visitor),
|
||||||
|
Stmt::Continue | Stmt::Break => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ return "return"
|
|||||||
if "if"
|
if "if"
|
||||||
elif "elif"
|
elif "elif"
|
||||||
el "el"
|
el "el"
|
||||||
|
loop "loop"
|
||||||
|
while "while"
|
||||||
|
break "break"
|
||||||
|
continue "continue"
|
||||||
|
|
||||||
[\r\n;]+ "EOL"
|
[\r\n;]+ "EOL"
|
||||||
[a-zA-Z_][a-zA-Z0-9_]* "IDENT"
|
[a-zA-Z_][a-zA-Z0-9_]* "IDENT"
|
||||||
|
|||||||
@@ -22,6 +22,10 @@ Stmt -> Result<Stmt>:
|
|||||||
Expr { Ok(Stmt::Expr($1?)) }
|
Expr { Ok(Stmt::Expr($1?)) }
|
||||||
| Assign { Ok(Stmt::Assign($1?)) }
|
| Assign { Ok(Stmt::Assign($1?)) }
|
||||||
| Return { Ok(Stmt::Return($1?)) }
|
| Return { Ok(Stmt::Return($1?)) }
|
||||||
|
| LoopStmt { Ok(Stmt::Loop($1?)) }
|
||||||
|
| WhileStmt { Ok(Stmt::While($1?)) }
|
||||||
|
| 'continue' { Ok(Stmt::Continue) }
|
||||||
|
| 'break' { Ok(Stmt::Break) }
|
||||||
;
|
;
|
||||||
|
|
||||||
Assign -> Result<AssignStmt>:
|
Assign -> Result<AssignStmt>:
|
||||||
@@ -46,6 +50,19 @@ Return -> Result<ReturnStmt>:
|
|||||||
| 'return' { Ok(ReturnStmt { expr: None, }) }
|
| 'return' { Ok(ReturnStmt { expr: None, }) }
|
||||||
;
|
;
|
||||||
|
|
||||||
|
LoopStmt -> Result<Body>:
|
||||||
|
'loop' '{' Body '}' { $3 }
|
||||||
|
;
|
||||||
|
|
||||||
|
WhileStmt -> Result<CondBody>:
|
||||||
|
'while' Expr '{' Body '}' {
|
||||||
|
Ok(CondBody {
|
||||||
|
cond: $2?,
|
||||||
|
body: $4?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
Expr -> Result<Expr>: BinExpr { $1 };
|
Expr -> Result<Expr>: BinExpr { $1 };
|
||||||
|
|
||||||
BinExpr -> Result<Expr>:
|
BinExpr -> Result<Expr>:
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ pub trait Visit {
|
|||||||
fn visit_stmt(&mut self, stmt: &Stmt) -> Self::Out;
|
fn visit_stmt(&mut self, stmt: &Stmt) -> Self::Out;
|
||||||
fn visit_assign_stmt(&mut self, assign: &AssignStmt) -> Self::Out;
|
fn visit_assign_stmt(&mut self, assign: &AssignStmt) -> Self::Out;
|
||||||
fn visit_return_stmt(&mut self, ret: &ReturnStmt) -> Self::Out;
|
fn visit_return_stmt(&mut self, ret: &ReturnStmt) -> Self::Out;
|
||||||
|
fn visit_while_stmt(&mut self, cond_body: &CondBody) -> Self::Out;
|
||||||
|
fn visit_loop_stmt(&mut self, body: &Body) -> Self::Out;
|
||||||
fn visit_lhs_expr(&mut self, lhs_expr: &LhsExpr) -> Self::Out;
|
fn visit_lhs_expr(&mut self, lhs_expr: &LhsExpr) -> Self::Out;
|
||||||
fn visit_expr(&mut self, expr: &Expr) -> Self::Out;
|
fn visit_expr(&mut self, expr: &Expr) -> Self::Out;
|
||||||
fn visit_bin_expr(&mut self, expr: &BinExpr) -> Self::Out;
|
fn visit_bin_expr(&mut self, expr: &BinExpr) -> Self::Out;
|
||||||
@@ -35,6 +37,8 @@ copy/paste of default_accepts
|
|||||||
fn visit_stmt(&mut self, stmt: &Stmt) -> Self::Out { DefaultAccept::default_accept(stmt, self); }
|
fn visit_stmt(&mut self, stmt: &Stmt) -> Self::Out { DefaultAccept::default_accept(stmt, self); }
|
||||||
fn visit_assign_stmt(&mut self, assign: &AssignStmt) -> Self::Out { DefaultAccept::default_accept(assign, self); }
|
fn visit_assign_stmt(&mut self, assign: &AssignStmt) -> Self::Out { DefaultAccept::default_accept(assign, self); }
|
||||||
fn visit_return_stmt(&mut self, ret: &ReturnStmt) -> Self::Out { DefaultAccept::default_accept(ret, self); }
|
fn visit_return_stmt(&mut self, ret: &ReturnStmt) -> Self::Out { DefaultAccept::default_accept(ret, self); }
|
||||||
|
fn visit_while_stmt(&mut self, cond_body: &CondBody) -> Self::Out { DefaultAccept::default_accept(cond_body, self); }
|
||||||
|
fn visit_loop_stmt(&mut self, body: &Body) -> Self::Out { DefaultAccept::default_accept(body, self); }
|
||||||
fn visit_lhs_expr(&mut self, lhs_expr: &LhsExpr) -> Self::Out { DefaultAccept::default_accept(lhs_expr, self); }
|
fn visit_lhs_expr(&mut self, lhs_expr: &LhsExpr) -> Self::Out { DefaultAccept::default_accept(lhs_expr, self); }
|
||||||
fn visit_expr(&mut self, expr: &Expr) -> Self::Out { DefaultAccept::default_accept(expr, self); }
|
fn visit_expr(&mut self, expr: &Expr) -> Self::Out { DefaultAccept::default_accept(expr, self); }
|
||||||
fn visit_bin_expr(&mut self, expr: &BinExpr) -> Self::Out { DefaultAccept::default_accept(expr, self); }
|
fn visit_bin_expr(&mut self, expr: &BinExpr) -> Self::Out { DefaultAccept::default_accept(expr, self); }
|
||||||
|
|||||||
@@ -42,10 +42,11 @@ pub enum Inst {
|
|||||||
Jump(usize),
|
Jump(usize),
|
||||||
|
|
||||||
/// Jump to a given address in the current function if the condition flag is true.
|
/// Jump to a given address in the current function if the condition flag is true.
|
||||||
///
|
|
||||||
/// The condition flag may be set by an internal function.
|
|
||||||
JumpTrue(usize),
|
JumpTrue(usize),
|
||||||
|
|
||||||
|
/// Jump to a given address in the current function if the condition flag is false.
|
||||||
|
JumpFalse(usize),
|
||||||
|
|
||||||
/// Calls a function with the supplied number of arguments.
|
/// Calls a function with the supplied number of arguments.
|
||||||
///
|
///
|
||||||
/// The stack, from bottom to top, should contain the function followed by the arguments.
|
/// The stack, from bottom to top, should contain the function followed by the arguments.
|
||||||
@@ -83,6 +84,7 @@ impl Inst {
|
|||||||
Inst::CheckTruth => "CHECK_TRUTH",
|
Inst::CheckTruth => "CHECK_TRUTH",
|
||||||
Inst::Jump(_) => "JUMP",
|
Inst::Jump(_) => "JUMP",
|
||||||
Inst::JumpTrue(_) => "JUMP_TRUE",
|
Inst::JumpTrue(_) => "JUMP_TRUE",
|
||||||
|
Inst::JumpFalse(_) => "JUMP_FALSE",
|
||||||
Inst::Call(_) => "CALL",
|
Inst::Call(_) => "CALL",
|
||||||
Inst::Index => "INDEX",
|
Inst::Index => "INDEX",
|
||||||
Inst::Return => "RETURN",
|
Inst::Return => "RETURN",
|
||||||
|
|||||||
@@ -267,7 +267,12 @@ impl<'c> Vm<'c> {
|
|||||||
next_pc = addr;
|
next_pc = addr;
|
||||||
}
|
}
|
||||||
Inst::JumpTrue(addr) => {
|
Inst::JumpTrue(addr) => {
|
||||||
if self.condition {
|
if !self.condition {
|
||||||
|
next_pc = addr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Inst::JumpFalse(addr) => {
|
||||||
|
if !self.condition {
|
||||||
next_pc = addr;
|
next_pc = addr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user