Fix scopes
This commit is contained in:
parent
8c72e921dc
commit
cfb4fa66b5
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)]
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -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
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![
|
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
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user