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::{ use std::{
cmp::Ordering,
env::current_exe, env::current_exe,
fmt::{self, Debug, Display}, fmt::{self, Debug, Display},
rc::Weak,
}; };
use colored::Colorize; use colored::Colorize;
@ -12,11 +12,13 @@ use crate::{Instruction, Span, Type, Value};
#[derive(Clone, PartialOrd, Ord, Serialize, Deserialize)] #[derive(Clone, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Chunk { pub struct Chunk {
name: Option<String>, name: Option<String>,
instructions: Vec<(Instruction, Span)>, instructions: Vec<(Instruction, Span)>,
constants: Vec<Value>, constants: Vec<Value>,
locals: Vec<Local>, locals: Vec<Local>,
current_scope: Scope, current_scope: Scope,
block_count: usize, scope_index: u8,
} }
impl Chunk { impl Chunk {
@ -27,7 +29,7 @@ impl Chunk {
constants: Vec::new(), constants: Vec::new(),
locals: Vec::new(), locals: Vec::new(),
current_scope: Scope::default(), current_scope: Scope::default(),
block_count: 0, scope_index: 0,
} }
} }
@ -43,7 +45,7 @@ impl Chunk {
constants, constants,
locals, locals,
current_scope: Scope::default(), current_scope: Scope::default(),
block_count: 0, scope_index: 0,
} }
} }
@ -122,19 +124,19 @@ impl Chunk {
} }
pub fn begin_scope(&mut self) { 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.depth += 1;
self.current_scope.block = self.block_count;
} }
pub fn end_scope(&mut self) { pub fn end_scope(&mut self) {
self.current_scope.depth -= 1; self.current_scope.depth -= 1;
if self.current_scope.depth == 0 { if self.current_scope.depth == 0 {
self.block_count += 1; self.current_scope.width = 0;
self.current_scope.block = 0;
} else { } else {
self.current_scope.block = self.block_count; self.current_scope.width -= 1;
}; }
} }
pub fn disassembler(&self) -> ChunkDisassembler { pub fn disassembler(&self) -> ChunkDisassembler {
@ -200,20 +202,28 @@ impl Local {
#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, Default, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Scope { pub struct Scope {
/// The level of block nesting. /// The level of block nesting.
pub depth: usize, pub depth: u8,
/// The nth top-level block in the chunk. /// The nth scope in the block.
pub block: usize, pub width: u8,
} }
impl Scope { impl Scope {
pub fn new(depth: usize, block: usize) -> Self { pub fn new(depth: u8, width: u8) -> Self {
Self { depth, block } 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 { impl Display for Scope {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 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()?; self.advance()?;
if self.allow(Token::Equal)? { let (is_mutable, local_scope) = {
let is_mutable = self.get_local(local_index)?.is_mutable; 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 { if !allowed.assignment {
return Err(ParseError::InvalidAssignmentTarget { return Err(ParseError::InvalidAssignmentTarget {
found: self.current_token.to_owned(), found: self.current_token.to_owned(),
@ -1953,6 +1967,12 @@ pub enum ParseError {
identifier: String, identifier: String,
position: Span, position: Span,
}, },
VariableOutOfScope {
identifier: String,
variable_scope: Scope,
access_scope: Scope,
position: Span,
},
// Statement errors // Statement errors
InvalidAssignmentTarget { InvalidAssignmentTarget {
@ -2009,6 +2029,7 @@ impl AnnotatedError for ParseError {
Self::RegisterUnderflow { .. } => "Register underflow", Self::RegisterUnderflow { .. } => "Register underflow",
Self::UndeclaredVariable { .. } => "Undeclared variable", Self::UndeclaredVariable { .. } => "Undeclared variable",
Self::UnexpectedReturn { .. } => "Unexpected return", Self::UnexpectedReturn { .. } => "Unexpected return",
Self::VariableOutOfScope { .. } => "Variable out of scope",
} }
} }
@ -2063,6 +2084,9 @@ impl AnnotatedError for ParseError {
Some(format!("Undeclared variable {identifier}")) Some(format!("Undeclared variable {identifier}"))
} }
Self::UnexpectedReturn { .. } => None, 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::RegisterUnderflow { position } => *position,
Self::UndeclaredVariable { position, .. } => *position, Self::UndeclaredVariable { position, .. } => *position,
Self::UnexpectedReturn { 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)), (Instruction::r#return(true), Span(44, 44)),
], ],
vec![Value::integer(4), Value::string("a")], 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)))); 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![ vec![
Local::new(1, None, false, Scope::new(0, 0), 0), Local::new(1, None, false, Scope::new(0, 0), 0),
Local::new(3, None, false, Scope::new(1, 0), 1), Local::new(3, None, false, Scope::new(1, 1), 1),
Local::new(5, None, false, Scope::new(2, 0), 2), Local::new(5, None, false, Scope::new(2, 2), 2),
Local::new(7, None, false, Scope::new(1, 0), 3), Local::new(7, None, false, Scope::new(1, 1), 3),
Local::new(8, None, false, Scope::new(0, 0), 4), Local::new(8, None, false, Scope::new(0, 0), 4),
] ]
)), )),
@ -128,13 +128,13 @@ fn multiple_block_scopes() {
], ],
vec![ vec![
Local::new(1, None, false, Scope::new(0, 0), 0), Local::new(1, None, false, Scope::new(0, 0), 0),
Local::new(3, None, false, Scope::new(1, 0), 1), Local::new(3, None, false, Scope::new(1, 1), 1),
Local::new(5, None, false, Scope::new(2, 0), 2), Local::new(5, None, false, Scope::new(2, 2), 2),
Local::new(7, None, false, Scope::new(1, 0), 3), Local::new(7, None, false, Scope::new(1, 1), 3),
Local::new(8, None, false, Scope::new(0, 0), 4), Local::new(8, None, false, Scope::new(0, 0), 4),
Local::new(3, None, false, Scope::new(1, 1), 5), Local::new(3, None, false, Scope::new(1, 3), 5),
Local::new(5, None, false, Scope::new(2, 1), 6), Local::new(5, None, false, Scope::new(2, 4), 6),
Local::new(7, None, false, Scope::new(1, 1), 7), Local::new(7, None, false, Scope::new(1, 3), 7),
Local::new(9, None, false, Scope::new(0, 0), 8), Local::new(9, None, false, Scope::new(0, 0), 8),
] ]
)), )),
@ -142,46 +142,103 @@ fn multiple_block_scopes() {
assert_eq!(run(source), Ok(None)); assert_eq!(run(source), Ok(None));
} }
// #[test]
// fn disallow_access_to_child_scope() {
// let source = r#"
// {
// let x = 1;
// }
// x
// "#;
// assert_eq!( #[test]
// run(source), fn disallow_access_to_child_scope() {
// Err(DustError::Parse { let source = r#"
// error: ParseError::Chunk(ChunkError::LocalOutOfScope { {
// identifier: Identifier::new("x"), let x = 1;
// position: Span(52, 53) }
// }), x
// source "#;
// })
// );
// }
// #[test] assert_eq!(
// fn disallow_access_to_sibling_scope() { run(source),
// let source = r#" Err(DustError::Parse {
// { error: ParseError::VariableOutOfScope {
// let x = 1; identifier: "x".to_string(),
// } position: Span(52, 53),
// { variable_scope: Scope::new(1, 1),
// x access_scope: Scope::new(0, 0),
// } },
// "#; source
})
);
}
// assert_eq!( #[test]
// run(source), fn disallow_access_to_child_scope_nested() {
// Err(DustError::Parse { let source = r#"
// error: ParseError::Chunk(ChunkError::LocalOutOfScope { {
// identifier: Identifier::new("x"), {
// position: Span(52, 53) let x = 1;
// }), }
// source 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
})
);
}