From c406039c99f1e2b0d64451eb231521559d856ae2 Mon Sep 17 00:00:00 2001 From: Jeff Date: Mon, 9 Sep 2024 19:23:49 -0400 Subject: [PATCH] Replace global variables with locals --- dust-lang/src/chunk.rs | 43 +++++++---- dust-lang/src/identifier_stack.rs | 80 +++++++++++++++++++++ dust-lang/src/lib.rs | 2 + dust-lang/src/parser.rs | 92 +++++++++++++++--------- dust-lang/src/vm.rs | 114 +++++++++++++----------------- 5 files changed, 219 insertions(+), 112 deletions(-) create mode 100644 dust-lang/src/identifier_stack.rs diff --git a/dust-lang/src/chunk.rs b/dust-lang/src/chunk.rs index 6da6edc..5f18987 100644 --- a/dust-lang/src/chunk.rs +++ b/dust-lang/src/chunk.rs @@ -2,13 +2,13 @@ use std::fmt::{self, Debug, Display, Formatter}; use serde::{Deserialize, Serialize}; -use crate::{Identifier, Instruction, Span, Value}; +use crate::{identifier_stack::Local, Identifier, IdentifierStack, Instruction, Span, Value}; #[derive(Clone, Eq, PartialEq, Serialize, Deserialize)] pub struct Chunk { code: Vec<(u8, Span)>, constants: Vec, - identifiers: Vec, + identifiers: IdentifierStack, } impl Chunk { @@ -16,19 +16,19 @@ impl Chunk { Self { code: Vec::new(), constants: Vec::new(), - identifiers: Vec::new(), + identifiers: IdentifierStack::new(), } } pub fn with_data( code: Vec<(u8, Span)>, constants: Vec, - identifiers: Vec, + identifiers: Vec, ) -> Self { Self { code, constants, - identifiers, + identifiers: IdentifierStack::with_data(identifiers, 0), } } @@ -54,9 +54,9 @@ impl Chunk { self.code.push((instruction, position)); } - pub fn get_constant(&self, index: usize) -> Result<&Value, ChunkError> { + pub fn get_constant(&self, index: u8) -> Result<&Value, ChunkError> { self.constants - .get(index) + .get(index as usize) .ok_or(ChunkError::ConstantIndexOutOfBounds(index)) } @@ -72,19 +72,30 @@ impl Chunk { } } - pub fn get_identifier(&self, index: usize) -> Result<&Identifier, ChunkError> { + pub fn contains_identifier(&self, identifier: &Identifier) -> bool { + self.identifiers.contains(identifier) + } + + pub fn get_identifier(&self, index: u8) -> Result<&Identifier, ChunkError> { self.identifiers - .get(index) + .get(index as usize) + .map(|local| &local.identifier) .ok_or(ChunkError::IdentifierIndexOutOfBounds(index)) } + pub fn get_identifier_index(&self, identifier: &Identifier) -> Result { + self.identifiers + .get_index(identifier) + .ok_or(ChunkError::IdentifierNotFound(identifier.clone())) + } + pub fn push_identifier(&mut self, identifier: Identifier) -> Result { - let starting_length = self.constants.len(); + let starting_length = self.identifiers.local_count(); if starting_length + 1 > (u8::MAX as usize) { Err(ChunkError::IdentifierOverflow) } else { - self.identifiers.push(identifier); + self.identifiers.declare(identifier); Ok(starting_length as u8) } @@ -93,6 +104,7 @@ impl Chunk { pub fn clear(&mut self) { self.code.clear(); self.constants.clear(); + self.identifiers.clear(); } pub fn disassemble(&self, name: &str) -> String { @@ -115,7 +127,7 @@ impl Chunk { } if let Some( - Instruction::DefineGlobal | Instruction::GetGlobal | Instruction::SetGlobal, + Instruction::DefineVariable | Instruction::GetVariable | Instruction::SetVariable, ) = previous { let display = format!("{position} {offset:04} IDENTIFIER_INDEX {byte}\n"); @@ -155,11 +167,12 @@ impl Debug for Chunk { } } -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, PartialEq)] pub enum ChunkError { CodeIndextOfBounds(usize), ConstantOverflow, - ConstantIndexOutOfBounds(usize), - IdentifierIndexOutOfBounds(usize), + ConstantIndexOutOfBounds(u8), + IdentifierIndexOutOfBounds(u8), IdentifierOverflow, + IdentifierNotFound(Identifier), } diff --git a/dust-lang/src/identifier_stack.rs b/dust-lang/src/identifier_stack.rs new file mode 100644 index 0000000..811278b --- /dev/null +++ b/dust-lang/src/identifier_stack.rs @@ -0,0 +1,80 @@ +use serde::{Deserialize, Serialize}; + +use crate::Identifier; + +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub struct IdentifierStack { + locals: Vec, + scope_depth: usize, +} + +impl IdentifierStack { + pub fn new() -> Self { + Self { + locals: Vec::new(), + scope_depth: 0, + } + } + + pub fn with_data(locals: Vec, scope_depth: usize) -> Self { + Self { + locals, + scope_depth, + } + } + + pub fn clear(&mut self) { + self.locals.clear(); + self.scope_depth = 0; + } + + pub fn local_count(&self) -> usize { + self.locals.len() + } + + pub fn contains(&self, identifier: &Identifier) -> bool { + self.locals + .iter() + .rev() + .any(|local| &local.identifier == identifier) + } + + pub fn get(&self, index: usize) -> Option<&Local> { + self.locals.get(index) + } + + pub fn get_index(&self, identifier: &Identifier) -> Option { + self.locals + .iter() + .rev() + .position(|local| &local.identifier == identifier) + .map(|index| index as u8) + } + + pub fn begin_scope(&mut self) { + self.scope_depth += 1; + } + + pub fn end_scope(&mut self) { + self.scope_depth -= 1; + } + + pub fn declare(&mut self, identifier: Identifier) { + self.locals.push(Local { + identifier, + depth: self.scope_depth, + }); + } +} + +impl Default for IdentifierStack { + fn default() -> Self { + Self::new() + } +} + +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub struct Local { + pub identifier: Identifier, + pub depth: usize, +} diff --git a/dust-lang/src/lib.rs b/dust-lang/src/lib.rs index 34156e5..63bac84 100644 --- a/dust-lang/src/lib.rs +++ b/dust-lang/src/lib.rs @@ -19,6 +19,7 @@ pub mod chunk; pub mod constructor; pub mod dust_error; pub mod identifier; +pub mod identifier_stack; pub mod lexer; pub mod parser; pub mod run; @@ -31,6 +32,7 @@ pub use chunk::{Chunk, ChunkError}; pub use constructor::{ConstructError, Constructor}; pub use dust_error::DustError; pub use identifier::Identifier; +pub use identifier_stack::IdentifierStack; pub use lexer::{lex, LexError, Lexer}; pub use parser::{parse, ParseError, Parser}; pub use r#type::{EnumType, FunctionType, RangeableType, StructType, Type, TypeConflict}; diff --git a/dust-lang/src/parser.rs b/dust-lang/src/parser.rs index dd159ca..71d1a3f 100644 --- a/dust-lang/src/parser.rs +++ b/dust-lang/src/parser.rs @@ -211,10 +211,10 @@ impl<'src> Parser<'src> { if allow_assignment && self.allow(TokenKind::Equal)? { self.parse_expression()?; - self.emit_byte(Instruction::SetGlobal as u8, self.previous_position); + self.emit_byte(Instruction::SetVariable as u8, self.previous_position); self.emit_byte(identifier_index, self.previous_position); } else { - self.emit_byte(Instruction::GetGlobal as u8, self.previous_position); + self.emit_byte(Instruction::GetVariable as u8, self.previous_position); self.emit_byte(identifier_index, self.previous_position); } @@ -226,7 +226,7 @@ impl<'src> Parser<'src> { self.advance()?; let identifier = Identifier::new(text); - let identifier_index = self.chunk.push_identifier(identifier)?; + let identifier_index = self.chunk.get_identifier_index(&identifier)?; Ok(identifier_index) } else { @@ -271,16 +271,25 @@ impl<'src> Parser<'src> { self.expect(TokenKind::Let)?; let position = self.current_position; - let identifier_index = self.parse_identifier_from(self.current_token.to_owned())?; + let identifier_index = if let Token::Identifier(text) = self.current_token { + self.advance()?; + + let identifier = Identifier::new(text); + + self.chunk.push_identifier(identifier)? + } else { + return Err(ParseError::ExpectedToken { + expected: TokenKind::Identifier, + found: self.current_token.to_owned(), + position: self.current_position, + }); + }; + + self.emit_byte(Instruction::DefineVariable as u8, position); + self.emit_byte(identifier_index, position); self.expect(TokenKind::Equal)?; self.parse_expression()?; - self.define_variable(identifier_index, position) - } - - fn define_variable(&mut self, identifier_index: u8, position: Span) -> Result<(), ParseError> { - self.emit_byte(Instruction::DefineGlobal as u8, position); - self.emit_byte(identifier_index, position); Ok(()) } @@ -549,13 +558,15 @@ impl From for ParseError { #[cfg(test)] mod tests { + use crate::identifier_stack::Local; + use super::*; #[test] fn add_variables() { let source = " - let x = 42 - let y = 42 + let x = 42; + let y = 42; x + y "; let test_chunk = parse(source); @@ -566,25 +577,37 @@ mod tests { vec![ (Instruction::Constant as u8, Span(21, 23)), (0, Span(21, 23)), - (Instruction::DefineGlobal as u8, Span(17, 18)), + (Instruction::DefineVariable as u8, Span(17, 18)), (0, Span(17, 18)), (Instruction::Constant as u8, Span(44, 46)), (1, Span(44, 46)), - (Instruction::DefineGlobal as u8, Span(40, 41)), + (Instruction::DefineVariable as u8, Span(40, 41)), (1, Span(40, 41)), - (Instruction::GetGlobal as u8, Span(61, 62)), + (Instruction::GetVariable as u8, Span(61, 62)), (0, Span(61, 62)), - (Instruction::GetGlobal as u8, Span(52, 53)), + (Instruction::GetVariable as u8, Span(52, 53)), (1, Span(52, 53)), (Instruction::Add as u8, Span(48, 53)) ], vec![Value::integer(42), Value::integer(42)], vec![ - Identifier::new("x"), - Identifier::new("y"), - Identifier::new("x"), - Identifier::new("y") - ] + Local { + identifier: Identifier::new("x"), + depth: 0 + }, + Local { + identifier: Identifier::new("y"), + depth: 0 + }, + Local { + identifier: Identifier::new("x"), + depth: 0 + }, + Local { + identifier: Identifier::new("y"), + depth: 0 + }, + ], )) ); } @@ -598,13 +621,16 @@ mod tests { test_chunk, Ok(Chunk::with_data( vec![ + (Instruction::DefineVariable as u8, Span(4, 5)), + (0, Span(4, 5)), (Instruction::Constant as u8, Span(8, 10)), (0, Span(8, 10)), - (Instruction::DefineGlobal as u8, Span(4, 5)), - (0, Span(4, 5)) ], vec![Value::integer(42)], - vec![Identifier::new("x")] + vec![Local { + identifier: Identifier::new("x"), + depth: 0 + }], )) ); } @@ -619,7 +645,7 @@ mod tests { Ok(Chunk::with_data( vec![(Instruction::Constant as u8, Span(0, 15)), (0, Span(0, 15))], vec![Value::string("Hello, World!")], - vec![] + vec![], )) ); } @@ -634,7 +660,7 @@ mod tests { Ok(Chunk::with_data( vec![(Instruction::Constant as u8, Span(0, 2)), (0, Span(0, 2))], vec![Value::integer(42)], - vec![] + vec![], )) ); } @@ -649,7 +675,7 @@ mod tests { Ok(Chunk::with_data( vec![(Instruction::Constant as u8, Span(0, 4)), (0, Span(0, 4))], vec![Value::boolean(true)], - vec![] + vec![], )) ); } @@ -673,7 +699,7 @@ mod tests { (Instruction::Multiply as u8, Span(10, 11)), ], vec![Value::integer(42), Value::integer(42), Value::integer(2)], - vec![] + vec![], )) ); } @@ -692,7 +718,7 @@ mod tests { (Instruction::Negate as u8, Span(0, 1)), ], vec![Value::integer(42)], - vec![] + vec![], )) ); } @@ -713,7 +739,7 @@ mod tests { (Instruction::Add as u8, Span(3, 4)), ], vec![Value::integer(42), Value::integer(42)], - vec![] + vec![], )) ); } @@ -734,7 +760,7 @@ mod tests { (Instruction::Subtract as u8, Span(3, 4)), ], vec![Value::integer(42), Value::integer(42)], - vec![] + vec![], )) ); } @@ -755,7 +781,7 @@ mod tests { (Instruction::Multiply as u8, Span(3, 4)), ], vec![Value::integer(42), Value::integer(42)], - vec![] + vec![], )) ); } @@ -776,7 +802,7 @@ mod tests { (Instruction::Divide as u8, Span(3, 4)), ], vec![Value::integer(42), Value::integer(42)], - vec![] + vec![], )) ); } diff --git a/dust-lang/src/vm.rs b/dust-lang/src/vm.rs index d1907a8..005dcea 100644 --- a/dust-lang/src/vm.rs +++ b/dust-lang/src/vm.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::fmt::{self, Display, Formatter}; use serde::{Deserialize, Serialize}; @@ -9,7 +9,6 @@ pub struct Vm { chunk: Chunk, ip: usize, stack: Vec, - globals: HashMap, } impl Vm { @@ -20,7 +19,6 @@ impl Vm { chunk, ip: 0, stack: Vec::with_capacity(Self::STACK_SIZE), - globals: HashMap::new(), } } @@ -29,10 +27,12 @@ impl Vm { let instruction = Instruction::from_byte(byte) .ok_or_else(|| VmError::InvalidInstruction(byte, position))?; + log::trace!("Running instruction {instruction} at {position}"); + match instruction { Instruction::Constant => { let (index, _) = self.read().copied()?; - let value = self.read_constant(index as usize)?; + let value = self.read_constant(index)?; self.push(value)?; } @@ -46,34 +46,29 @@ impl Vm { } // Variables - Instruction::DefineGlobal => { - let (index, _) = self.read().copied()?; - let identifier = self.chunk.get_identifier(index as usize)?.clone(); - let value = self.pop()?; + Instruction::DefineVariable => { + let (index, _) = *self.read()?; + let value = self.read_constant(index)?; - self.globals.insert(identifier, value); + self.stack.insert(index as usize, value); } - Instruction::GetGlobal => { - let (index, _) = self.read().copied()?; - let identifier = self.chunk.get_identifier(index as usize)?; - let value = - self.globals.get(identifier).cloned().ok_or_else(|| { - VmError::UndefinedGlobal(identifier.clone(), position) - })?; + Instruction::GetVariable => { + let (index, _) = *self.read()?; + let value = self.stack[index as usize].clone(); self.push(value)?; } - Instruction::SetGlobal => { - let (index, _) = self.read().copied()?; - let identifier = self.chunk.get_identifier(index as usize)?.clone(); + Instruction::SetVariable => { + let (index, _) = *self.read()?; + let identifier = self.chunk.get_identifier(index)?.clone(); - if !self.globals.contains_key(&identifier) { - return Err(VmError::UndefinedGlobal(identifier, position)); + if !self.chunk.contains_identifier(&identifier) { + return Err(VmError::UndefinedVariable(identifier, position)); } let value = self.pop()?; - self.globals.insert(identifier, value); + self.stack[index as usize] = value; } // Unary @@ -205,7 +200,7 @@ impl Vm { Ok(current) } - fn read_constant(&self, index: usize) -> Result { + fn read_constant(&self, index: u8) -> Result { Ok(self.chunk.get_constant(index)?.clone()) } } @@ -215,7 +210,7 @@ pub enum VmError { InvalidInstruction(u8, Span), StackUnderflow, StackOverflow, - UndefinedGlobal(Identifier, Span), + UndefinedVariable(Identifier, Span), // Wrappers for foreign errors Chunk(ChunkError), @@ -241,9 +236,9 @@ pub enum Instruction { Pop = 2, // Variables - DefineGlobal = 3, - GetGlobal = 4, - SetGlobal = 5, + DefineVariable = 3, + GetVariable = 4, + SetVariable = 5, // Unary Negate = 6, @@ -270,9 +265,9 @@ impl Instruction { 0 => Some(Instruction::Constant), 1 => Some(Instruction::Return), 2 => Some(Instruction::Pop), - 3 => Some(Instruction::DefineGlobal), - 4 => Some(Instruction::GetGlobal), - 5 => Some(Instruction::SetGlobal), + 3 => Some(Instruction::DefineVariable), + 4 => Some(Instruction::GetVariable), + 5 => Some(Instruction::SetVariable), 6 => Some(Instruction::Negate), 7 => Some(Instruction::Not), 8 => Some(Instruction::Add), @@ -298,7 +293,7 @@ impl Instruction { { let index_string = index.to_string(); let value_string = chunk - .get_constant(*index as usize) + .get_constant(*index) .map(|value| value.to_string()) .unwrap_or_else(|error| format!("{:?}", error)); @@ -316,48 +311,33 @@ impl Instruction { Instruction::Pop => format!("{offset:04} POP"), // Variables - Instruction::DefineGlobal => { + Instruction::DefineVariable => { let (index, _) = chunk.read(offset + 1).unwrap(); - let index = *index as usize; - let identifier_display = chunk - .get_identifier(index) - .map(|identifier| identifier.to_string()) - .unwrap_or_else(|error| format!("{:?}", error)); - let value_display = chunk - .get_constant(index) - .map(|value| value.to_string()) - .unwrap_or_else(|error| format!("{:?}", error)); + let identifier_display = match chunk.get_identifier(*index) { + Ok(identifier) => identifier.to_string(), + Err(error) => format!("{:?}", error), + }; - format!("{offset:04} DEFINE_GLOBAL {identifier_display} {value_display}") + format!("{offset:04} DEFINE_VARIABLE {identifier_display} {index}") } - Instruction::GetGlobal => { + Instruction::GetVariable => { let (index, _) = chunk.read(offset + 1).unwrap(); - let index = *index as usize; - let identifier_display = chunk - .get_identifier(index) - .map(|identifier| identifier.to_string()) - .unwrap_or_else(|error| format!("{:?}", error)); - let value_display = chunk - .get_constant(index) - .map(|value| value.to_string()) - .unwrap_or_else(|error| format!("{:?}", error)); + let identifier_display = match chunk.get_identifier(*index) { + Ok(identifier) => identifier.to_string(), + Err(error) => format!("{:?}", error), + }; - format!("{offset:04} GET_GLOBAL {identifier_display} {value_display}") + format!("{offset:04} GET_VARIABLE {identifier_display} {index}") } - Instruction::SetGlobal => { + Instruction::SetVariable => { let (index, _) = chunk.read(offset + 1).unwrap(); - let index = *index as usize; - let identifier_display = chunk - .get_identifier(index) - .map(|identifier| identifier.to_string()) - .unwrap_or_else(|error| format!("{:?}", error)); - let value_display = chunk - .get_constant(index) - .map(|value| value.to_string()) - .unwrap_or_else(|error| format!("{:?}", error)); + let identifier_display = match chunk.get_identifier(*index) { + Ok(identifier) => identifier.to_string(), + Err(error) => format!("{:?}", error), + }; - format!("{offset:04} SET_GLOBAL {identifier_display} {value_display}") + format!("{offset:04} SET_VARIABLE {identifier_display} {index}") } // Unary @@ -381,6 +361,12 @@ impl Instruction { } } +impl Display for Instruction { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{self:?}") + } +} + #[cfg(test)] pub mod tests { use super::*;