aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGustav Sörnäs <gustav@sornas.net>2021-02-19 18:24:33 +0100
committerGitHub <noreply@github.com>2021-02-19 18:24:33 +0100
commit3715433024e2df742a6ad16488ab2a580e397b86 (patch)
treecd240988148d04273720dab9314e8c20f7d765cb
parentb5e2585346c3bf317dc1fe86fc1be9dc4b613752 (diff)
parent930401a3ee2e45e449b1927cabee32c2c7bb705e (diff)
downloadsylt-3715433024e2df742a6ad16488ab2a580e397b86.tar.gz
Merge pull request #67 from FredTheDino/unusued-variables
unusued variables
-rw-r--r--src/compiler.rs189
-rw-r--r--src/lib.rs85
-rw-r--r--src/vm.rs33
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() {
diff --git a/src/lib.rs b/src/lib.rs
index 7f79a66..91938f1 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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
",
);
+
}
diff --git a/src/vm.rs b/src/vm.rs
index 020e90c..f5e5c58 100644
--- a/src/vm.rs
+++ b/src/vm.rs
@@ -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(_, _)]);
}
}