1
0

Fix scopes

This commit is contained in:
Jeff 2024-11-05 16:07:51 -05:00
parent 8c72e921dc
commit cfb4fa66b5
6 changed files with 194 additions and 101 deletions

View File

@ -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)
}
}

View File

@ -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,
}
}
}

View File

@ -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)]
)),
);

View File

@ -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
View 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))));
}

View File

@ -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
})
);
}