From 6e1a19f341fc1f88effed8f3b6338b85f22bf180 Mon Sep 17 00:00:00 2001 From: Alek Ratzloff Date: Fri, 13 Nov 2020 20:00:31 -0800 Subject: [PATCH] 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 --- examples/branch.not | 20 +- src/compile/basic_block.rs | 56 +++--- src/compile/list.rs | 371 +++++++++++++++++++++---------------- src/compile/locals.rs | 6 + src/compile/thunk.rs | 142 +++++++++++--- src/obj/fun.rs | 2 +- src/syn/ast.rs | 9 +- src/syn/lexer.l | 4 + src/syn/parser.y | 17 ++ src/syn/visit.rs | 4 + src/vm/inst.rs | 6 +- src/vm/mod.rs | 7 +- 12 files changed, 407 insertions(+), 237 deletions(-) diff --git a/examples/branch.not b/examples/branch.not index f08e65f..145fbec 100644 --- a/examples/branch.not +++ b/examples/branch.not @@ -1,13 +1,9 @@ -check = fn(num) { - 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!") - } -} +num = 10 -check(1) -check(10) -check(100) +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!") +} diff --git a/src/compile/basic_block.rs b/src/compile/basic_block.rs index 13a5cdc..eaf52d8 100644 --- a/src/compile/basic_block.rs +++ b/src/compile/basic_block.rs @@ -11,11 +11,11 @@ pub enum BasicBlock { /// A linear block of executable instructions. Block { exit: usize, block: Vec }, - /// A branch, instructing the basic block where to jump for the true and false blocks. - Branch { - block_true: usize, - block_false: usize, - }, + /// A branch that will jump forward if the given condition is false. + JumpForward(usize), + + /// A branch that will jump backward if the given condition is true. + Jump(usize), } #[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, // there will be a conditional jump added. BasicBlock::Block { block, .. } => block.len() + 1, - // 2 because of: - // - conditional jump if true at start - // - unconditional jump to false block - BasicBlock::Branch { .. } => 2, + // 1 because of: + // - conditional jump to false block + BasicBlock::JumpForward(_) => 1, + // 1 because of: + // - conditional jump to false block + BasicBlock::Jump(_) => 1, }; addr += inst_len; @@ -69,16 +71,13 @@ impl BasicBlockList { body.push(Inst::Jump(addr)); } } - BasicBlock::Branch { - block_true, - block_false, - } => { - // insert conditional jump to true statement, and unconditional jump to false - // statement - let addr_true = addr_rev[&block_true]; - let addr_false = addr_rev[&block_false]; - body.push(Inst::JumpTrue(addr_true)); - body.push(Inst::Jump(addr_false)); + BasicBlock::JumpForward(block) => { + let addr = addr_rev[&block]; + body.push(Inst::JumpFalse(addr)); + } + BasicBlock::Jump(block) => { + let addr = addr_rev[&block]; + body.push(Inst::Jump(addr)); } } } @@ -102,13 +101,9 @@ impl BasicBlockList { let mut last_block_index = 0; 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 - match block { - BasicBlock::Block { exit, .. } => last_block_index = exit.max(last_block_index), - BasicBlock::Branch { - block_true, - block_false, - } => last_block_index = block_true.max(block_false.max(last_block_index)), - } + last_block_index = match block { + BasicBlock::Block { exit, .. } | BasicBlock::JumpForward(exit) | BasicBlock::Jump(exit) => exit, + }; blocks.push(block); entry_map.insert(index, new_index); } @@ -123,13 +118,8 @@ impl BasicBlockList { exit: entry_map[&exit], block, }, - BasicBlock::Branch { - block_true, - block_false, - } => BasicBlock::Branch { - block_true: entry_map[&block_true], - block_false: entry_map[&block_false], - }, + BasicBlock::JumpForward(block) => BasicBlock::JumpForward(entry_map[&block]), + BasicBlock::Jump(block) => BasicBlock::Jump(entry_map[&block]), }) .collect() } diff --git a/src/compile/list.rs b/src/compile/list.rs index 6273388..fc2b653 100644 --- a/src/compile/list.rs +++ b/src/compile/list.rs @@ -17,6 +17,10 @@ pub enum List { body: Box, el: Box, }, + Loop { + cond: Option>, + body: Box, + }, Lambda { params: Vec, expr: Box, @@ -34,6 +38,8 @@ pub enum List { name: String, value: Box, }, + Break, + Continue, Return(Box), Call(Box, Vec), Do(Vec), @@ -41,164 +47,7 @@ pub enum List { impl List { pub fn thunkify(self, compile: &mut Compile) -> Thunk { - match 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()) - } - } + Thunkify { compile }.thunkify(self) } } @@ -240,7 +89,15 @@ impl<'c> Visit for CompileList<'c> { } 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 { @@ -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 { 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, + } + } +} diff --git a/src/compile/locals.rs b/src/compile/locals.rs index fe9303f..14d579c 100644 --- a/src/compile/locals.rs +++ b/src/compile/locals.rs @@ -46,6 +46,12 @@ impl Visit for CollectLocals<'_> { 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 { match lhs_expr { LhsExpr::Name(name) => { diff --git a/src/compile/thunk.rs b/src/compile/thunk.rs index 7d6bd20..065ec61 100644 --- a/src/compile/thunk.rs +++ b/src/compile/thunk.rs @@ -1,7 +1,4 @@ -use crate::{ - compile::basic_block::*, - vm::inst::*, -}; +use crate::{compile::basic_block::*, vm::inst::*}; use std::mem; /// A basic block of VM code. @@ -17,9 +14,9 @@ pub enum Thunk { /// A list of thunks. List(Vec), - /// 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 /// continue at the address following this branch. @@ -29,16 +26,16 @@ pub enum Thunk { thunk_false: Box, }, - /// Based on the conditional flag in the VM, code for this loop will continue to execute. - /// - /// The conditional flag is expected to be set upon entry to this 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. - /// - /// At the end of the body, the program jumps back to the start where the condition is checked - /// again. - Loop(Box), + Loop { + preamble: Option>, + body: Box, + }, + + /// Return to the top of the currently executing loop. + Continue, + + /// Exit to the bottom of the currently executing loop. + Break, /// A placeholder/default thunk that compiles to nothing. Nop, @@ -97,9 +94,22 @@ impl Thunk { preamble, thunk_true, thunk_false, - } => preamble.basic_block_count() + thunk_true.basic_block_count() + thunk_false.basic_block_count() + 1, - // length is thunk, + 1 for branch at the start of the loop - Thunk::Loop(thunk) => thunk.basic_block_count() + 1, + // length is + 1 for BranchBlock after the preamble + } => { + 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, } } @@ -136,6 +146,8 @@ impl From> for Thunk { struct Flatten { // using a btreemap instead of a vec because we can insert things out-of-order blocks: BasicBlockList, + // loop start/end addresses + loop_anchors: Vec<(usize, usize)>, } // @@ -151,11 +163,18 @@ impl Flatten { assert_eq!(self.blocks.len(), last_block); // push an extra null block at the very end so that anything pointing to `last_block` will // 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 } fn flatten_next(&mut self, next_block: usize, thunk: Thunk) { + let end_block = self.this_block() + thunk.basic_block_count(); match thunk { Thunk::Body(thunk) => { let this_block = self.this_block(); @@ -178,14 +197,17 @@ impl Flatten { } 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 { preamble, thunk_true, thunk_false, } => { + // Branch: + // + preamble blocks + // + branch block + // + true blocks + // + false blocks let preamble_block = self.this_block(); let branch_block = preamble_block + preamble.basic_block_count(); let block_true = branch_block + 1; @@ -194,20 +216,84 @@ impl Flatten { assert_eq!(self.this_block(), branch_block); self.blocks.insert( branch_block, - BasicBlock::Branch { - block_true, - block_false, - }, + BasicBlock::JumpForward(block_false), ); assert_eq!(self.this_block(), block_true); self.flatten_next(next_block, *thunk_true); assert_eq!(self.this_block(), block_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 => {} } + assert_eq!(self.this_block(), end_block); } fn this_block(&self) -> usize { diff --git a/src/obj/fun.rs b/src/obj/fun.rs index c2748ba..cd3be7a 100644 --- a/src/obj/fun.rs +++ b/src/obj/fun.rs @@ -179,7 +179,7 @@ impl UserFun { ("(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()), _ => (String::new(), String::new()), }; diff --git a/src/syn/ast.rs b/src/syn/ast.rs index 533b5b2..e320609 100644 --- a/src/syn/ast.rs +++ b/src/syn/ast.rs @@ -24,6 +24,10 @@ pub enum Stmt { Expr(Expr), Assign(AssignStmt), Return(ReturnStmt), + Loop(Body), + While(CondBody), + Continue, + Break, } // @@ -38,12 +42,15 @@ impl Accept for Stmt { // // impl DefaultAccept for Stmt // -impl DefaultAccept for Stmt { +impl> DefaultAccept for Stmt { fn default_accept(&self, visitor: &mut V) -> V::Out { match self { Stmt::Expr(e) => e.accept(visitor), Stmt::Assign(a) => a.accept(visitor), Stmt::Return(r) => r.accept(visitor), + Stmt::Loop(b) => b.accept(visitor), + Stmt::While(c) => c.accept(visitor), + Stmt::Continue | Stmt::Break => {}, } } } diff --git a/src/syn/lexer.l b/src/syn/lexer.l index 1338512..0d8d636 100644 --- a/src/syn/lexer.l +++ b/src/syn/lexer.l @@ -4,6 +4,10 @@ return "return" if "if" elif "elif" el "el" +loop "loop" +while "while" +break "break" +continue "continue" [\r\n;]+ "EOL" [a-zA-Z_][a-zA-Z0-9_]* "IDENT" diff --git a/src/syn/parser.y b/src/syn/parser.y index 37cb67b..df32931 100644 --- a/src/syn/parser.y +++ b/src/syn/parser.y @@ -22,6 +22,10 @@ Stmt -> Result: Expr { Ok(Stmt::Expr($1?)) } | Assign { Ok(Stmt::Assign($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: @@ -46,6 +50,19 @@ Return -> Result: | 'return' { Ok(ReturnStmt { expr: None, }) } ; +LoopStmt -> Result: + 'loop' '{' Body '}' { $3 } + ; + +WhileStmt -> Result: + 'while' Expr '{' Body '}' { + Ok(CondBody { + cond: $2?, + body: $4?, + }) + } + ; + Expr -> Result: BinExpr { $1 }; BinExpr -> Result: diff --git a/src/syn/visit.rs b/src/syn/visit.rs index 4f01964..ac14aa8 100644 --- a/src/syn/visit.rs +++ b/src/syn/visit.rs @@ -15,6 +15,8 @@ pub trait Visit { fn visit_stmt(&mut self, stmt: &Stmt) -> Self::Out; fn visit_assign_stmt(&mut self, assign: &AssignStmt) -> 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_expr(&mut self, expr: &Expr) -> 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_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_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_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); } diff --git a/src/vm/inst.rs b/src/vm/inst.rs index f3db323..589c2de 100644 --- a/src/vm/inst.rs +++ b/src/vm/inst.rs @@ -42,10 +42,11 @@ pub enum Inst { Jump(usize), /// 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), + /// 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. /// /// 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::Jump(_) => "JUMP", Inst::JumpTrue(_) => "JUMP_TRUE", + Inst::JumpFalse(_) => "JUMP_FALSE", Inst::Call(_) => "CALL", Inst::Index => "INDEX", Inst::Return => "RETURN", diff --git a/src/vm/mod.rs b/src/vm/mod.rs index 744d6a2..5163470 100644 --- a/src/vm/mod.rs +++ b/src/vm/mod.rs @@ -267,7 +267,12 @@ impl<'c> Vm<'c> { next_pc = addr; } Inst::JumpTrue(addr) => { - if self.condition { + if !self.condition { + next_pc = addr; + } + } + Inst::JumpFalse(addr) => { + if !self.condition { next_pc = addr; } }