diff --git a/dust-lang/src/chunk.rs b/dust-lang/src/chunk.rs index 7da8b96..c14dc14 100644 --- a/dust-lang/src/chunk.rs +++ b/dust-lang/src/chunk.rs @@ -1,7 +1,7 @@ use std::{ + cmp::Ordering, env::current_exe, fmt::{self, Debug, Display}, - rc::Weak, }; use colored::Colorize; @@ -12,11 +12,13 @@ use crate::{Instruction, Span, Type, Value}; #[derive(Clone, PartialOrd, Ord, Serialize, Deserialize)] pub struct Chunk { name: Option, + instructions: Vec<(Instruction, Span)>, constants: Vec, locals: Vec, + current_scope: Scope, - block_count: usize, + scope_index: u8, } impl Chunk { @@ -27,7 +29,7 @@ impl Chunk { constants: Vec::new(), locals: Vec::new(), current_scope: Scope::default(), - block_count: 0, + scope_index: 0, } } @@ -43,7 +45,7 @@ impl Chunk { constants, locals, current_scope: Scope::default(), - block_count: 0, + scope_index: 0, } } @@ -122,19 +124,19 @@ impl Chunk { } pub fn begin_scope(&mut self) { + self.scope_index += 1; + self.current_scope.width = self.scope_index; self.current_scope.depth += 1; - self.current_scope.block = self.block_count; } pub fn end_scope(&mut self) { self.current_scope.depth -= 1; if self.current_scope.depth == 0 { - self.block_count += 1; - self.current_scope.block = 0; + self.current_scope.width = 0; } else { - self.current_scope.block = self.block_count; - }; + self.current_scope.width -= 1; + } } pub fn disassembler(&self) -> ChunkDisassembler { @@ -200,20 +202,28 @@ impl Local { #[derive(Debug, Clone, Copy, Default, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)] pub struct Scope { /// The level of block nesting. - pub depth: usize, - /// The nth top-level block in the chunk. - pub block: usize, + pub depth: u8, + /// The nth scope in the block. + pub width: u8, } impl Scope { - pub fn new(depth: usize, block: usize) -> Self { - Self { depth, block } + pub fn new(depth: u8, width: u8) -> Self { + Self { depth, width } + } + + pub fn contains(&self, other: &Self) -> bool { + match self.depth.cmp(&other.depth) { + Ordering::Less => false, + Ordering::Greater => self.width >= other.width, + Ordering::Equal => self.width == other.width, + } } } impl Display for Scope { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "({}, {})", self.depth, self.block) + write!(f, "({}, {})", self.depth, self.width) } } diff --git a/dust-lang/src/parser.rs b/dust-lang/src/parser.rs index 1c03b08..c36601d 100644 --- a/dust-lang/src/parser.rs +++ b/dust-lang/src/parser.rs @@ -861,9 +861,23 @@ impl<'src> Parser<'src> { self.advance()?; - if self.allow(Token::Equal)? { - let is_mutable = self.get_local(local_index)?.is_mutable; + let (is_mutable, local_scope) = { + let local = self.get_local(local_index)?; + (local.is_mutable, local.scope) + }; + let current_scope = self.chunk.current_scope(); + + if !current_scope.contains(&local_scope) { + return Err(ParseError::VariableOutOfScope { + identifier: self.chunk.get_identifier(local_index).unwrap(), + position: start_position, + variable_scope: local_scope, + access_scope: current_scope, + }); + } + + if self.allow(Token::Equal)? { if !allowed.assignment { return Err(ParseError::InvalidAssignmentTarget { found: self.current_token.to_owned(), @@ -1953,6 +1967,12 @@ pub enum ParseError { identifier: String, position: Span, }, + VariableOutOfScope { + identifier: String, + variable_scope: Scope, + access_scope: Scope, + position: Span, + }, // Statement errors InvalidAssignmentTarget { @@ -2009,6 +2029,7 @@ impl AnnotatedError for ParseError { Self::RegisterUnderflow { .. } => "Register underflow", Self::UndeclaredVariable { .. } => "Undeclared variable", Self::UnexpectedReturn { .. } => "Unexpected return", + Self::VariableOutOfScope { .. } => "Variable out of scope", } } @@ -2063,6 +2084,9 @@ impl AnnotatedError for ParseError { Some(format!("Undeclared variable {identifier}")) } Self::UnexpectedReturn { .. } => None, + Self::VariableOutOfScope { identifier, .. } => { + Some(format!("Variable {identifier} is out of scope")) + } } } @@ -2083,6 +2107,7 @@ impl AnnotatedError for ParseError { Self::RegisterUnderflow { position } => *position, Self::UndeclaredVariable { position, .. } => *position, Self::UnexpectedReturn { position } => *position, + Self::VariableOutOfScope { position, .. } => *position, } } } diff --git a/dust-lang/tests/control_flow.rs b/dust-lang/tests/control_flow.rs index 8f84bae..0958b6e 100644 --- a/dust-lang/tests/control_flow.rs +++ b/dust-lang/tests/control_flow.rs @@ -23,7 +23,7 @@ fn equality_assignment_long() { (Instruction::r#return(true), Span(44, 44)), ], vec![Value::integer(4), Value::string("a")], - vec![Local::new(1, None, false, Scope { depth: 0, block: 0 }, 0)] + vec![Local::new(1, None, false, Scope { depth: 0, width: 0 }, 0)] )), ); diff --git a/dust-lang/tests/expressions.rs b/dust-lang/tests/expressions.rs index a66fc2c..aaee12f 100644 --- a/dust-lang/tests/expressions.rs +++ b/dust-lang/tests/expressions.rs @@ -540,37 +540,3 @@ fn variable_and() { assert_eq!(run(source), Ok(Some(Value::boolean(false)))); } - -#[test] -fn r#while() { - let source = "let mut x = 0; while x < 5 { x = x + 1 } x"; - - assert_eq!( - parse(source), - Ok(Chunk::with_data( - None, - vec![ - (Instruction::load_constant(0, 0, false), Span(12, 13)), - (Instruction::define_local(0, 0, true), Span(8, 9)), - ( - *Instruction::less(true, 0, 2).set_c_is_constant(), - Span(23, 24) - ), - (Instruction::jump(2, true), Span(41, 42)), - (*Instruction::add(0, 0, 3).set_c_is_constant(), Span(39, 40)), - (Instruction::jump(3, false), Span(41, 42)), - (Instruction::get_local(1, 0), Span(41, 42)), - (Instruction::r#return(true), Span(42, 42)), - ], - vec![ - Value::integer(0), - Value::string("x"), - Value::integer(5), - Value::integer(1), - ], - vec![Local::new(1, None, true, Scope::default(), 0),] - )), - ); - - assert_eq!(run(source), Ok(Some(Value::integer(5)))); -} diff --git a/dust-lang/tests/loops.rs b/dust-lang/tests/loops.rs new file mode 100644 index 0000000..9d96957 --- /dev/null +++ b/dust-lang/tests/loops.rs @@ -0,0 +1,35 @@ +use dust_lang::*; + +#[test] +fn r#while() { + let source = "let mut x = 0; while x < 5 { x = x + 1 } x"; + + assert_eq!( + parse(source), + Ok(Chunk::with_data( + None, + vec![ + (Instruction::load_constant(0, 0, false), Span(12, 13)), + (Instruction::define_local(0, 0, true), Span(8, 9)), + ( + *Instruction::less(true, 0, 2).set_c_is_constant(), + Span(23, 24) + ), + (Instruction::jump(2, true), Span(41, 42)), + (*Instruction::add(0, 0, 3).set_c_is_constant(), Span(39, 40)), + (Instruction::jump(3, false), Span(41, 42)), + (Instruction::get_local(1, 0), Span(41, 42)), + (Instruction::r#return(true), Span(42, 42)), + ], + vec![ + Value::integer(0), + Value::string("x"), + Value::integer(5), + Value::integer(1), + ], + vec![Local::new(1, None, true, Scope::default(), 0),] + )), + ); + + assert_eq!(run(source), Ok(Some(Value::integer(5)))); +} diff --git a/dust-lang/tests/scopes.rs b/dust-lang/tests/scopes.rs index 5603de3..9831177 100644 --- a/dust-lang/tests/scopes.rs +++ b/dust-lang/tests/scopes.rs @@ -56,9 +56,9 @@ fn block_scope() { ], vec![ Local::new(1, None, false, Scope::new(0, 0), 0), - Local::new(3, None, false, Scope::new(1, 0), 1), - Local::new(5, None, false, Scope::new(2, 0), 2), - Local::new(7, None, false, Scope::new(1, 0), 3), + Local::new(3, None, false, Scope::new(1, 1), 1), + Local::new(5, None, false, Scope::new(2, 2), 2), + Local::new(7, None, false, Scope::new(1, 1), 3), Local::new(8, None, false, Scope::new(0, 0), 4), ] )), @@ -128,13 +128,13 @@ fn multiple_block_scopes() { ], vec![ Local::new(1, None, false, Scope::new(0, 0), 0), - Local::new(3, None, false, Scope::new(1, 0), 1), - Local::new(5, None, false, Scope::new(2, 0), 2), - Local::new(7, None, false, Scope::new(1, 0), 3), + Local::new(3, None, false, Scope::new(1, 1), 1), + Local::new(5, None, false, Scope::new(2, 2), 2), + Local::new(7, None, false, Scope::new(1, 1), 3), Local::new(8, None, false, Scope::new(0, 0), 4), - Local::new(3, None, false, Scope::new(1, 1), 5), - Local::new(5, None, false, Scope::new(2, 1), 6), - Local::new(7, None, false, Scope::new(1, 1), 7), + Local::new(3, None, false, Scope::new(1, 3), 5), + Local::new(5, None, false, Scope::new(2, 4), 6), + Local::new(7, None, false, Scope::new(1, 3), 7), Local::new(9, None, false, Scope::new(0, 0), 8), ] )), @@ -142,46 +142,103 @@ fn multiple_block_scopes() { assert_eq!(run(source), Ok(None)); } -// #[test] -// fn disallow_access_to_child_scope() { -// let source = r#" -// { -// let x = 1; -// } -// x -// "#; -// assert_eq!( -// run(source), -// Err(DustError::Parse { -// error: ParseError::Chunk(ChunkError::LocalOutOfScope { -// identifier: Identifier::new("x"), -// position: Span(52, 53) -// }), -// source -// }) -// ); -// } +#[test] +fn disallow_access_to_child_scope() { + let source = r#" + { + let x = 1; + } + x + "#; -// #[test] -// fn disallow_access_to_sibling_scope() { -// let source = r#" -// { -// let x = 1; -// } -// { -// x -// } -// "#; + assert_eq!( + run(source), + Err(DustError::Parse { + error: ParseError::VariableOutOfScope { + identifier: "x".to_string(), + position: Span(52, 53), + variable_scope: Scope::new(1, 1), + access_scope: Scope::new(0, 0), + }, + source + }) + ); +} -// assert_eq!( -// run(source), -// Err(DustError::Parse { -// error: ParseError::Chunk(ChunkError::LocalOutOfScope { -// identifier: Identifier::new("x"), -// position: Span(52, 53) -// }), -// source -// }) -// ); -// } +#[test] +fn disallow_access_to_child_scope_nested() { + let source = r#" + { + { + let x = 1; + } + x + } + "#; + + assert_eq!( + run(source), + Err(DustError::Parse { + error: ParseError::VariableOutOfScope { + identifier: "x".to_string(), + position: Span(78, 79), + variable_scope: Scope::new(2, 2), + access_scope: Scope::new(1, 1), + }, + source + }) + ); +} + +#[test] +fn disallow_access_to_sibling_scope() { + let source = r#" + { + let x = 1; + } + { + x + } + "#; + + assert_eq!( + run(source), + Err(DustError::Parse { + error: ParseError::VariableOutOfScope { + identifier: "x".to_string(), + variable_scope: Scope::new(1, 1), + access_scope: Scope::new(1, 2), + position: Span(66, 67), + }, + source + }) + ); +} + +#[test] +fn disallow_access_to_sibling_scope_nested() { + let source = r#" + { + { + let x = 1; + } + { + x + } + } + "#; + + assert_eq!( + run(source), + Err(DustError::Parse { + error: ParseError::VariableOutOfScope { + identifier: "x".to_string(), + variable_scope: Scope::new(2, 2), + access_scope: Scope::new(2, 3), + position: Span(96, 97), + }, + source + }) + ); +}