Fix scopes
This commit is contained in:
parent
8c72e921dc
commit
cfb4fa66b5
@ -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<String>,
|
||||
|
||||
instructions: Vec<(Instruction, Span)>,
|
||||
constants: Vec<Value>,
|
||||
locals: Vec<Local>,
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)]
|
||||
)),
|
||||
);
|
||||
|
||||
|
@ -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))));
|
||||
}
|
||||
|
35
dust-lang/tests/loops.rs
Normal file
35
dust-lang/tests/loops.rs
Normal file
@ -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))));
|
||||
}
|
@ -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
|
||||
})
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user