diff options
| author | Gustav Sörnäs <gustav@sornas.net> | 2021-02-19 18:24:33 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-02-19 18:24:33 +0100 |
| commit | 3715433024e2df742a6ad16488ab2a580e397b86 (patch) | |
| tree | cd240988148d04273720dab9314e8c20f7d765cb | |
| parent | b5e2585346c3bf317dc1fe86fc1be9dc4b613752 (diff) | |
| parent | 930401a3ee2e45e449b1927cabee32c2c7bb705e (diff) | |
| download | sylt-3715433024e2df742a6ad16488ab2a580e397b86.tar.gz | |
Merge pull request #67 from FredTheDino/unusued-variables
unusued variables
| -rw-r--r-- | src/compiler.rs | 189 | ||||
| -rw-r--r-- | src/lib.rs | 85 | ||||
| -rw-r--r-- | src/vm.rs | 33 |
3 files changed, 218 insertions, 89 deletions
diff --git a/src/compiler.rs b/src/compiler.rs index 94ae2aa..cd4ffda 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -113,12 +113,13 @@ nextable_enum!(Prec { Factor, }); -#[derive(Clone)] +#[derive(Clone, Debug)] struct Variable { name: String, typ: Type, scope: usize, slot: usize, + line: usize, outer_slot: usize, outer_upvalue: bool, @@ -127,6 +128,28 @@ struct Variable { upvalue: bool, captured: bool, mutable: bool, + read: bool, +} + +impl Variable { + fn new(name: &str, mutable: bool, typ: Type) -> Self { + Self { + name: String::from(name), + typ, + scope: 0, + slot: 0, + line: 0, + + outer_slot: 0, + outer_upvalue: false, + + active: false, + upvalue: false, + captured: false, + mutable, + read: false, + } + } } enum LoopOp { @@ -242,10 +265,27 @@ macro_rules! push_frame { $compiler.frames.push(Frame::new()); // Return value stored as a variable - $compiler.define_variable("", Type::Unknown, &mut $block).unwrap(); + let var = Variable::new("", true, Type::Unknown); + $compiler.define(var).unwrap(); + $code - $compiler.frames.pop().unwrap(); + let frame = $compiler.frames.pop().unwrap(); + // 0-th slot is the function itself. + for var in frame.stack.iter().skip(1) { + if !(var.read || var.upvalue) { + let e = ErrorKind::SyntaxError( + var.line, + Token::Identifier(var.name.clone() + )); + $compiler.error_on_line( + e, + var.line, + Some(format!("Unused value '{}'.", var.name)) + ); + } + $compiler.panic = false; + } // The 0th slot is the return value, which is passed out // from functions, and should not be popped. 0 @@ -262,13 +302,30 @@ macro_rules! push_scope { $compiler.frame_mut().scope -= 1; - for var in $compiler.frame().stack[ss..$compiler.stack().len()].iter().rev() { + let mut errors = Vec::new(); + for var in $compiler.frame().stack.iter().skip(ss).rev() { + if !(var.read || var.upvalue) { + let e = ErrorKind::SyntaxError( + var.line, + Token::Identifier(var.name.clone() + )); + errors.push(( + e, + var.line, + format!("Usage of undefined value: '{}'.", var.name),) + ); + } if var.captured { add_op($compiler, $block, Op::PopUpvalue); } else { add_op($compiler, $block, Op::Pop); } } + + for (e, l, m) in errors.iter() { + $compiler.error_on_line(e.clone(), *l, Some(m.clone())); + $compiler.panic = false; + } $compiler.stack_mut().truncate(ss); }; } @@ -337,6 +394,26 @@ impl Compiler { &mut self.frames[last] } + /// Marks a variable as read. Also marks upvalues. + fn mark_read(&mut self, frame_id: usize, var: &Variable) { + // Early out + if var.read { + return; + } + + + if if let Some(up) = self.frames[frame_id].upvalues.get(var.slot) { + up.name == var.name + } else { false } { + let mut inner_var = self.frames[frame_id].upvalues[var.slot].clone(); + inner_var.slot = inner_var.outer_slot; + self.mark_read(frame_id - 1, &inner_var); + self.frames[frame_id].upvalues[var.slot].read = true; + } else { + self.frames[frame_id].stack[var.slot].read = true; + } + } + fn stack(&self) -> &[Variable] { &self.frame().stack.as_ref() } @@ -719,10 +796,10 @@ impl Compiler { let mut args = Vec::new(); let mut return_type = Type::Void; - let mut function_block = Block::new(&name, &self.current_file, self.line()); + let mut function_block = Block::new(&name, &self.current_file); let block_id = self.blocks.len(); - let temp_block = Block::new(&name, &self.current_file, self.line()); + let temp_block = Block::new(&name, &self.current_file); self.blocks.push(Rc::new(RefCell::new(temp_block))); let _ret = push_frame!(self, function_block, { @@ -733,7 +810,8 @@ impl Compiler { expect!(self, Token::Colon, "Expected ':' after parameter name."); if let Ok(typ) = self.parse_type() { args.push(typ.clone()); - if let Ok(slot) = self.define_variable(&name, typ, &mut function_block) { + let var = Variable::new(&name, true, typ); + if let Ok(slot) = self.define(var) { self.stack_mut()[slot].active = true; } } else { @@ -814,6 +892,7 @@ impl Compiler { // Variables if let Some(var) = self.find_variable(&name) { + self.mark_read(self.frames.len() - 1, &var); if var.upvalue { add_op(self, block, Op::ReadUpvalue(var.slot)); } else { @@ -846,58 +925,26 @@ impl Compiler { parse_branch!(self, block, self.call(block)); } - fn define_variable(&mut self, name: &str, typ: Type, _block: &mut Block) -> Result<usize, ()> { - if let Some(var) = self.find_variable(&name) { - if var.scope == self.frame().scope { - error!(self, format!("Multiple definitions of {} in this block.", name)); - return Err(()); - } - } - - let slot = self.stack().len(); - let scope = self.frame().scope; - self.stack_mut().push(Variable { - name: String::from(name), - captured: false, - outer_upvalue: false, - outer_slot: 0, - slot, - typ, - scope, - active: false, - upvalue: false, - mutable: true, - }); - Ok(slot) - } - - fn define_constant(&mut self, name: &str, typ: Type, _block: &mut Block) -> Result<usize, ()> { - if let Some(var) = self.find_variable(&name) { + fn define(&mut self, mut var: Variable) -> Result<usize, ()> { + if let Some(var) = self.find_variable(&var.name) { if var.scope == self.frame().scope { - error!(self, format!("Multiple definitions of {} in this block.", name)); + error!(self, format!("Multiple definitions of '{}' in this block.", + var.name)); return Err(()); } } let slot = self.stack().len(); - let scope = self.frame().scope; - self.stack_mut().push(Variable { - name: String::from(name), - captured: false, - outer_upvalue: false, - outer_slot: 0, - slot, - typ, - scope, - active: false, - upvalue: false, - mutable: false, - }); + var.slot = slot; + var.scope = self.frame().scope; + var.line = self.line(); + self.stack_mut().push(var); Ok(slot) } fn definition_statement(&mut self, name: &str, typ: Type, block: &mut Block) { - let slot = self.define_variable(name, typ.clone(), block); + let var = Variable::new(name, true, typ.clone()); + let slot = self.define(var); self.expression(block); let constant = self.add_constant(Value::Ty(typ)); add_op(self, block, Op::Define(constant)); @@ -914,18 +961,25 @@ impl Compiler { // Remove the function, since it's a constant and we already // added it. block.ops.pop().unwrap(); - if let Entry::Occupied(entry) = self.unknown.entry(String::from(name)) { + let slot = if let Entry::Occupied(entry) = self.unknown.entry(String::from(name)) { let (_, (slot, _)) = entry.remove_entry(); self.constants[slot] = self.constants.pop().unwrap(); - add_op(self, block, Op::Link(slot)); + slot } else { - add_op(self, block, Op::Link(self.constants.len() - 1)); + self.constants.len() - 1 + }; + add_op(self, block, Op::Link(slot)); + if let Value::Function(_, block) = &self.constants[slot] { + block.borrow_mut().mark_constant(); + } else { + unreachable!(); } return; } } - let slot = self.define_constant(name, typ.clone(), block); + let var = Variable::new(name, false, typ); + let slot = self.define(var); self.expression(block); if let Ok(slot) = slot { @@ -1202,6 +1256,7 @@ impl Compiler { _ => unreachable!(), }; if let Some(var) = self.find_variable(&name) { + self.mark_read(self.frames.len() - 1, &var); if var.upvalue { add_op(self, block, Op::ReadUpvalue(var.slot)); } else { @@ -1376,23 +1431,14 @@ impl Compiler { .enumerate() .map(|(i, (s, f))| (s, (i, f))) .collect(); - self.stack_mut().push(Variable { - name: String::from("/main/"), - typ: Type::Void, - outer_upvalue: false, - outer_slot: 0, - slot: 0, - scope: 0, - active: false, - captured: false, - upvalue: false, - mutable: true, - }); + let main = Variable::new("/main/", false, Type::Void); + let _ = self.define(main); - let mut block = Block::new(name, file, 0); + let mut block = Block::new(name, file); while self.peek() != Token::EOF { self.statement(&mut block); - expect!(self, Token::Newline | Token::EOF, "Expect newline or EOF after expression."); + expect!(self, Token::Newline | Token::EOF, + "Expect newline or EOF after expression."); } add_op(self, &mut block, Op::Constant(self.nil_value())); add_op(self, &mut block, Op::Return); @@ -1410,6 +1456,15 @@ impl Compiler { } } + for var in self.frames.pop().unwrap().stack.iter().skip(1) { + if !(var.read || var.upvalue) { + let e = ErrorKind::SyntaxError(var.line, Token::Identifier(var.name.clone())); + let m = format!("Unused value '{}'.", var.name); + self.error_on_line(e, var.line, Some(m)); + } + self.panic = false; + } + self.blocks.insert(0, Rc::new(RefCell::new(block))); if self.errors.is_empty() { @@ -151,7 +151,7 @@ impl From<&Type> for Value { Type::String => Value::String(Rc::new("".to_string())), Type::Function(_, _) => Value::Function( Vec::new(), - Rc::new(RefCell::new(Block::empty_with_type(ty)))), + Rc::new(RefCell::new(Block::stubbed_block(ty)))), } } } @@ -626,35 +626,58 @@ mod op { } #[derive(Debug)] +enum BlockLinkState { + Linked, + Unlinked, + Nothing, +} + +#[derive(Debug)] pub struct Block { pub ty: Type, upvalues: Vec<(usize, bool, Type)>, + linking: BlockLinkState, pub name: String, pub file: PathBuf, ops: Vec<Op>, last_line_offset: usize, line_offsets: HashMap<usize, usize>, - line: usize, } impl Block { - fn new(name: &str, file: &Path, line: usize) -> Self { + fn new(name: &str, file: &Path) -> Self { Self { ty: Type::Void, upvalues: Vec::new(), + linking: BlockLinkState::Nothing, + name: String::from(name), file: file.to_owned(), ops: Vec::new(), last_line_offset: 0, line_offsets: HashMap::new(), - line, } } + fn mark_constant(&mut self) { + if self.upvalues.is_empty() { + return; + } + self.linking = BlockLinkState::Unlinked; + } + + fn link(&mut self) { + self.linking = BlockLinkState::Linked; + } + + fn needs_linking(&self) -> bool { + matches!(self.linking, BlockLinkState::Unlinked) + } + // Used to create empty functions. - fn empty_with_type(ty: &Type) -> Self { - let mut block = Block::new("/empty/", Path::new(""), 0); + fn stubbed_block(ty: &Type) -> Self { + let mut block = Block::new("/empty/", Path::new("")); block.ty = ty.clone(); block } @@ -865,7 +888,7 @@ mod tests { #[test] fn assign_to_constant_upvalue() { - assert_errs!(run_string("a :: 2\nq :: fn { a = 2 }\n", true, Vec::new()), [ErrorKind::SyntaxError(_, _)]); + assert_errs!(run_string("a :: 2\nq :: fn { a = 2 }\nq()\na", true, Vec::new()), [ErrorKind::SyntaxError(_, _)]); } #[test] @@ -873,6 +896,35 @@ mod tests { assert_errs!(run_string("a :: B()\n", true, Vec::new()), [ErrorKind::SyntaxError(_, _)]); } + #[test] + fn call_before_link() { + let prog = " +a := 1 +f() +c := 5 + +f :: fn { + c <=> 5 +} +a + "; + assert_errs!(run_string(prog, true, Vec::new()), [ErrorKind::InvalidProgram, ErrorKind::TypeError(_, _)]); + } + + #[test] + fn unused_variable() { + assert_errs!(run_string("a := 1", true, Vec::new()), [ErrorKind::SyntaxError(1, _)]); + } + + #[test] + fn unused_upvalue() { + assert_errs!(run_string("a := 1\nf :: fn { a = 2 }\nf()", true, Vec::new()), [ErrorKind::SyntaxError(1, _)]); + } + + #[test] + fn unused_function() { + assert_errs!(run_string("a := 1\nf := fn { a }\n", true, Vec::new()), [ErrorKind::SyntaxError(2, _)]); + } macro_rules! test_multiple { ($mod:ident, $( $fn:ident : $prog:literal ),+ $( , )? ) => { @@ -1046,7 +1098,8 @@ a() <=> 4 blob, simple: "blob A {}", instantiate: "blob A {} - a := A()", + a := A() + a", field: "blob A { a: int }", field_assign: "blob A { a: int } a := A() @@ -1068,6 +1121,7 @@ a() <=> 4 blob_infer: " blob A { } a : A = A() +a ", ); @@ -1075,8 +1129,8 @@ a : A = A() add: "(1, 2, 3, 4) + (4, 3, 2, 1) <=> (5, 5, 5, 5)", sub: "(1, -2, 3, -4) - (4, 3, -2, -1) <=> (-3, 1, 1, -5)", mul: "(0, 1, 2) * (2, 3, 4) <=> (0, 3, 8)", - types: "a: (int, float, int) = (1, 1., 1)", - more_types: "a: (str, bool, int) = (\"abc\", true, 1)", + types: "a: (int, float, int) = (1, 1., 1)\na", + more_types: "a: (str, bool, int) = (\"abc\", true, 1)\na", ); test_file!(scoping, "progs/tests/scoping.sy"); @@ -1181,6 +1235,7 @@ a <=> 1 b := 2 { a <=> 1 + b <=> 2 }", ); @@ -1191,6 +1246,7 @@ a := 0 b := 99999 a += 1 a <=> 1 +b <=> 99999 ", simple_sub: " @@ -1198,6 +1254,7 @@ a := 0 b := 99999 a -= 1 a <=> -1 +b <=> 99999 ", strange: " @@ -1217,6 +1274,7 @@ a <=> -1 declaration_order, blob_simple: " a := A() +a blob A { a: int @@ -1229,6 +1287,11 @@ b := B() c := C() b2 := B() +a +b +c +b2 + blob A { c: C } @@ -1240,6 +1303,7 @@ blob B { } blob A { } a : A = A() +a ", @@ -1302,4 +1366,5 @@ q <=> 3 ", ); + } @@ -554,6 +554,13 @@ impl VM { Value::Function(_, block) => { self.push(Value::Function(Vec::new(), block.clone())); + if block.borrow().needs_linking() { + error!(self, + ErrorKind::InvalidProgram, + format!("Calling function '{}' before all captured variables are declared.", + block.borrow().name)); + } + let mut types = Vec::new(); for (slot, is_up, ty) in block.borrow().upvalues.iter() { if *is_up { @@ -669,10 +676,10 @@ impl VM { } Op::Link(slot) => { - println!("{:?}", self.constants); - println!("{:?} - {}", self.constant(slot), slot); match self.constant(slot).clone() { - Value::Function(_, _) => {} + Value::Function(_, block) => { + block.borrow_mut().link(); + } value => { error!(self, ErrorKind::TypeError(op, vec![Type::from(&value)]), @@ -728,8 +735,9 @@ impl VM { self.push(res); } _ => { - error!(self, ErrorKind::ValueError(op, vec![self.stack[new_base].clone()]), - "Tried to call non-function."); + error!(self, + ErrorKind::InvalidProgram, + format!("Tried to call non-function {:?}", self.stack[new_base])); } } } @@ -768,7 +776,7 @@ impl VM { }); if self.print_blocks { - println!("\n [[{}]]\n", "TYPECHECK".purple()); + println!("\n [[{} - {}]]\n", "TYPECHECKING".purple(), self.frame().block.borrow().name); self.frame().block.borrow().debug_print(); } @@ -821,23 +829,24 @@ impl VM { mod tests { mod typing { use crate::error::ErrorKind; - use crate::{test_string, Op, Type}; + use crate::{test_string, Type}; test_string!(uncallable_type, " f := fn i: int { i() - }", - [ErrorKind::ValueError(Op::Call(0), _)]); + } + f", + [ErrorKind::InvalidProgram]); - test_string!(invalid_assign, "a := 1\na = 0.1\n", + test_string!(invalid_assign, "a := 1\na = 0.1\na", [ErrorKind::TypeMismatch(Type::Int, Type::Float)]); test_string!(wrong_params, " - f : fn -> int = fn a: int -> int {}", + f : fn -> int = fn a: int -> int {}\nf", [ErrorKind::TypeMismatch(_, _), ErrorKind::TypeMismatch(Type::Void, Type::Int)]); test_string!(wrong_ret, " - f : fn -> int = fn {}", + f : fn -> int = fn {}\nf", [ErrorKind::TypeMismatch(_, _)]); } } |
