From 85b95a56aa35aef8cfbecadcff4633ef910d6785 Mon Sep 17 00:00:00 2001 From: Jeff Date: Wed, 18 Sep 2024 11:27:41 -0400 Subject: [PATCH] Add mutable variables --- dust-lang/src/chunk.rs | 21 +++++++++++---- dust-lang/src/instruction.rs | 22 +++++++-------- dust-lang/src/parser/mod.rs | 46 ++++++++++++++++++++++++++----- dust-lang/src/parser/tests.rs | 28 +++++++++---------- dust-lang/src/value.rs | 31 ++++++++++++++++++--- dust-lang/src/vm.rs | 51 ++++++++++++++++++++++++++++------- 6 files changed, 150 insertions(+), 49 deletions(-) diff --git a/dust-lang/src/chunk.rs b/dust-lang/src/chunk.rs index 1916ac9..44a98b6 100644 --- a/dust-lang/src/chunk.rs +++ b/dust-lang/src/chunk.rs @@ -144,6 +144,7 @@ impl Chunk { pub fn declare_local( &mut self, identifier: Identifier, + mutable: bool, register_index: u8, position: Span, ) -> Result { @@ -154,6 +155,7 @@ impl Chunk { } else { self.locals.push(Local::new( identifier, + mutable, self.scope_depth, Some(register_index), )); @@ -236,14 +238,21 @@ impl PartialEq for Chunk { #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] pub struct Local { pub identifier: Identifier, + pub mutable: bool, pub depth: usize, pub register_index: Option, } impl Local { - pub fn new(identifier: Identifier, depth: usize, register_index: Option) -> Self { + pub fn new( + identifier: Identifier, + mutable: bool, + depth: usize, + register_index: Option, + ) -> Self { Self { identifier, + mutable, depth, register_index, } @@ -278,8 +287,8 @@ impl<'a> ChunkDisassembler<'a> { "", "Locals", "------", - "INDEX IDENTIFIER DEPTH REGISTER", - "----- ---------- ----- --------", + "INDEX IDENTIFIER MUTABLE DEPTH REGISTER", + "----- ---------- ------- ----- --------", ]; /// The default width of the disassembly output. To correctly align the output, this should be @@ -384,6 +393,7 @@ impl<'a> ChunkDisassembler<'a> { identifier, depth, register_index, + mutable, }, ) in self.chunk.locals.iter().enumerate() { @@ -392,8 +402,9 @@ impl<'a> ChunkDisassembler<'a> { .map(|value| value.to_string()) .unwrap_or_else(|| "empty".to_string()); let identifier_display = identifier.as_str(); - let local_display = - format!("{index:<5} {identifier_display:<10} {depth:<5} {register_display:<8}"); + let local_display = format!( + "{index:<5} {identifier_display:<10} {mutable:<7} {depth:<5} {register_display:<8}" + ); disassembled.push_str(¢er(&local_display)); } diff --git a/dust-lang/src/instruction.rs b/dust-lang/src/instruction.rs index a5ce850..fd668f9 100644 --- a/dust-lang/src/instruction.rs +++ b/dust-lang/src/instruction.rs @@ -42,11 +42,12 @@ impl Instruction { instruction } - pub fn declare_local(to_register: u8, variable_index: u8) -> Instruction { - let mut instruction = Instruction(Operation::DeclareLocal as u32); + pub fn define_local(to_register: u8, variable_index: u8, is_mutable: bool) -> Instruction { + let mut instruction = Instruction(Operation::DefineLocal as u32); instruction.set_destination(to_register); instruction.set_first_argument(variable_index); + instruction.set_second_argument(if is_mutable { 1 } else { 0 }); instruction } @@ -284,7 +285,7 @@ impl Instruction { destination, first_index, last_index ) } - Operation::DeclareLocal => { + Operation::DefineLocal => { let local_index = self.first_argument(); let identifier_display = if let Some(chunk) = chunk { match chunk.get_identifier(local_index) { @@ -433,7 +434,7 @@ pub enum Operation { LoadList = LOAD_LIST as isize, // Variables - DeclareLocal = DECLARE_LOCAL as isize, + DefineLocal = DECLARE_LOCAL as isize, GetLocal = GET_LOCAL as isize, SetLocal = SET_LOCAL as isize, @@ -476,7 +477,7 @@ impl From for Operation { CLOSE => Operation::Close, LOAD_CONSTANT => Operation::LoadConstant, LOAD_LIST => Operation::LoadList, - DECLARE_LOCAL => Operation::DeclareLocal, + DECLARE_LOCAL => Operation::DefineLocal, GET_LOCAL => Operation::GetLocal, SET_LOCAL => Operation::SetLocal, ADD => Operation::Add, @@ -501,7 +502,7 @@ impl From for u8 { Operation::Close => CLOSE, Operation::LoadConstant => LOAD_CONSTANT, Operation::LoadList => LOAD_LIST, - Operation::DeclareLocal => DECLARE_LOCAL, + Operation::DefineLocal => DECLARE_LOCAL, Operation::GetLocal => GET_LOCAL, Operation::SetLocal => SET_LOCAL, Operation::Add => ADD, @@ -525,7 +526,7 @@ impl Display for Operation { Operation::Close => write!(f, "CLOSE"), Operation::LoadConstant => write!(f, "LOAD_CONSTANT"), Operation::LoadList => write!(f, "LOAD_LIST"), - Operation::DeclareLocal => write!(f, "DECLARE_LOCAL"), + Operation::DefineLocal => write!(f, "DEFINE_LOCAL"), Operation::GetLocal => write!(f, "GET_LOCAL"), Operation::SetLocal => write!(f, "SET_LOCAL"), Operation::Add => write!(f, "ADD"), @@ -585,16 +586,15 @@ mod tests { #[test] fn declare_local() { - let mut instruction = Instruction::declare_local(0, 1); + let mut instruction = Instruction::define_local(0, 1, true); instruction.set_first_argument_to_constant(); - instruction.set_second_argument_to_constant(); - assert_eq!(instruction.operation(), Operation::DeclareLocal); + assert_eq!(instruction.operation(), Operation::DefineLocal); assert_eq!(instruction.destination(), 0); assert_eq!(instruction.first_argument(), 1); + assert_eq!(instruction.second_argument(), true as u8); assert!(instruction.first_argument_is_constant()); - assert!(instruction.second_argument_is_constant()); } #[test] diff --git a/dust-lang/src/parser/mod.rs b/dust-lang/src/parser/mod.rs index 8f89591..6bf2b7a 100644 --- a/dust-lang/src/parser/mod.rs +++ b/dust-lang/src/parser/mod.rs @@ -295,12 +295,22 @@ impl<'src> Parser<'src> { let mut left_is_constant = false; let left = match left_instruction.operation() { Operation::LoadConstant => { + log::trace!( + "Condensing {} to binary expression", + left_instruction.operation() + ); + left_is_constant = true; self.decrement_register()?; left_instruction.first_argument() } Operation::GetLocal => { + log::trace!( + "Condensing {} to binary expression", + left_instruction.operation() + ); + self.decrement_register()?; left_instruction.first_argument() } @@ -330,12 +340,22 @@ impl<'src> Parser<'src> { let mut right_is_constant = false; let right = match right_instruction.operation() { Operation::LoadConstant => { + log::trace!( + "Condensing {} to binary expression", + right_instruction.operation() + ); + right_is_constant = true; self.decrement_register()?; right_instruction.first_argument() } Operation::GetLocal => { + log::trace!( + "Condensing {} to binary expression", + right_instruction.operation() + ); + self.decrement_register()?; right_instruction.first_argument() } @@ -423,6 +443,8 @@ impl<'src> Parser<'src> { .register_index; if let Some(register_index) = previous_register { + log::trace!("Condensing SET_LOCAL to binary expression"); + previous_instruction.set_destination(register_index); self.emit_instruction(previous_instruction, self.current_position); } else { @@ -592,6 +614,7 @@ impl<'src> Parser<'src> { self.advance()?; + let is_mutable = self.allow(TokenKind::Mut)?; let position = self.current_position; let identifier = if let Token::Identifier(text) = self.current_token { self.advance()?; @@ -609,11 +632,14 @@ impl<'src> Parser<'src> { self.parse_expression()?; let register = self.chunk.get_last_instruction(position)?.0.destination(); - let local_index = self - .chunk - .declare_local(identifier, register, self.current_position)?; + let local_index = + self.chunk + .declare_local(identifier, is_mutable, register, self.current_position)?; - self.emit_instruction(Instruction::declare_local(register, local_index), position); + self.emit_instruction( + Instruction::define_local(register, local_index, is_mutable), + position, + ); self.allow(TokenKind::Semicolon)?; Ok(()) @@ -824,8 +850,16 @@ impl From<&TokenKind> for ParseRule<'_> { precedence: Precedence::Term, }, TokenKind::MinusEqual => todo!(), - TokenKind::Mut => todo!(), - TokenKind::Percent => todo!(), + TokenKind::Mut => ParseRule { + prefix: None, + infix: None, + precedence: Precedence::None, + }, + TokenKind::Percent => ParseRule { + prefix: None, + infix: Some(Parser::parse_binary), + precedence: Precedence::Factor, + }, TokenKind::Plus => ParseRule { prefix: None, infix: Some(Parser::parse_binary), diff --git a/dust-lang/src/parser/tests.rs b/dust-lang/src/parser/tests.rs index 89728f2..6c93974 100644 --- a/dust-lang/src/parser/tests.rs +++ b/dust-lang/src/parser/tests.rs @@ -71,17 +71,17 @@ fn block_scope() { Ok(Chunk::with_data( vec![ (Instruction::load_constant(0, 0), Span(17, 18)), - (Instruction::declare_local(0, 0), Span(13, 14)), + (Instruction::define_local(0, 0, false), Span(13, 14)), (Instruction::load_constant(1, 1), Span(50, 52)), - (Instruction::declare_local(1, 1), Span(46, 47)), + (Instruction::define_local(1, 1, false), Span(46, 47)), (Instruction::load_constant(2, 2), Span(92, 93)), - (Instruction::declare_local(2, 2), Span(88, 89)), + (Instruction::define_local(2, 2, false), Span(88, 89)), (Instruction::close(2, 3), Span(84, 124)), (Instruction::load_constant(3, 3), Span(129, 130)), - (Instruction::declare_local(3, 3), Span(125, 126)), + (Instruction::define_local(3, 3, false), Span(125, 126)), (Instruction::close(1, 4), Span(42, 153)), (Instruction::load_constant(4, 4), Span(158, 159)), - (Instruction::declare_local(4, 4), Span(154, 155)), + (Instruction::define_local(4, 4, false), Span(154, 155)), ], vec![ Value::integer(0), @@ -91,11 +91,11 @@ fn block_scope() { Value::integer(1) ], vec![ - Local::new(Identifier::new("a"), 0, Some(0)), - Local::new(Identifier::new("b"), 1, Some(1)), - Local::new(Identifier::new("c"), 2, Some(2)), - Local::new(Identifier::new("d"), 1, Some(3)), - Local::new(Identifier::new("e"), 0, Some(4)), + Local::new(Identifier::new("a"), false, 0, Some(0)), + Local::new(Identifier::new("b"), false, 1, Some(1)), + Local::new(Identifier::new("c"), false, 2, Some(2)), + Local::new(Identifier::new("d"), false, 1, Some(3)), + Local::new(Identifier::new("e"), false, 0, Some(4)), ] )), ); @@ -113,12 +113,12 @@ fn set_local() { Ok(Chunk::with_data( vec![ (Instruction::load_constant(0, 0), Span(8, 10)), - (Instruction::declare_local(0, 0), Span(4, 5)), + (Instruction::define_local(0, 0, false), Span(4, 5)), (Instruction::load_constant(1, 1), Span(16, 18)), (Instruction::set_local(1, 0), Span(12, 13)), ], vec![Value::integer(41), Value::integer(42)], - vec![Local::new(Identifier::new("x"), 0, Some(0)),] + vec![Local::new(Identifier::new("x"), false, 0, Some(0)),] )), ); } @@ -191,10 +191,10 @@ fn declare_local() { Ok(Chunk::with_data( vec![ (Instruction::load_constant(0, 0), Span(8, 10)), - (Instruction::declare_local(0, 0), Span(4, 5)), + (Instruction::define_local(0, 0, false), Span(4, 5)), ], vec![Value::integer(42)], - vec![Local::new(Identifier::new("x"), 0, Some(0))] + vec![Local::new(Identifier::new("x"), false, 0, Some(0))] )), ); } diff --git a/dust-lang/src/value.rs b/dust-lang/src/value.rs index c4ec211..38e69a4 100644 --- a/dust-lang/src/value.rs +++ b/dust-lang/src/value.rs @@ -34,7 +34,7 @@ use std::{ cmp::Ordering, collections::HashMap, - fmt::{self, Display, Formatter}, + fmt::{self, Debug, Display, Formatter}, ops::{Range, RangeInclusive}, sync::{Arc, RwLock}, }; @@ -50,7 +50,6 @@ use crate::{EnumType, FunctionType, Identifier, RangeableType, StructType, Type} /// Dust value representation /// /// See the [module-level documentation][self] for more. -#[derive(Debug)] pub enum Value { Raw(ValueData), Reference(Arc), @@ -127,6 +126,8 @@ impl Value { } pub fn into_reference(self) -> Self { + log::trace!("Converting to reference: {self:?}"); + match self { Value::Raw(data) => Value::Reference(Arc::new(data)), Value::Reference(_) => self, @@ -140,8 +141,16 @@ impl Value { pub fn into_mutable(self) -> Self { match self { - Value::Raw(data) => Value::Mutable(Arc::new(RwLock::new(data))), - Value::Reference(data) => Value::Mutable(Arc::new(RwLock::new(data.as_ref().clone()))), + Value::Raw(data) => { + log::trace!("Converting to mutable: {data:?}"); + + Value::Mutable(Arc::new(RwLock::new(data))) + } + Value::Reference(data) => { + log::trace!("Converting to mutable: {data:?}"); + + Value::Mutable(Arc::new(RwLock::new(data.as_ref().clone()))) + } Value::Mutable(_) => self, } } @@ -818,6 +827,20 @@ impl Clone for Value { } } +impl Debug for Value { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + Value::Raw(data) => write!(f, "Value::Raw({data:?})"), + Value::Reference(data) => write!(f, "Value::Reference({data:?})"), + Value::Mutable(data) => { + let data = data.read().unwrap(); + + write!(f, "Value::Mutable({data:?})") + } + } + } +} + impl Eq for Value {} impl PartialEq for Value { diff --git a/dust-lang/src/vm.rs b/dust-lang/src/vm.rs index 906135d..cf98355 100644 --- a/dust-lang/src/vm.rs +++ b/dust-lang/src/vm.rs @@ -35,6 +35,7 @@ impl Vm { } pub fn run(&mut self) -> Result, VmError> { + // DRY helper closure to take a constant or clone a register let take_constants_or_clone = |vm: &mut Vm, instruction: Instruction, position: Span| @@ -107,7 +108,7 @@ impl Vm { self.insert(Value::list(list), to_register, position)?; } - Operation::DeclareLocal => { + Operation::DefineLocal => { let from_register = instruction.destination(); let to_local = instruction.first_argument(); @@ -122,10 +123,21 @@ impl Vm { self.insert(value, register_index, position)?; } Operation::SetLocal => { - let from_register = instruction.destination(); - let to_local = instruction.first_argument(); + let register_index = instruction.destination(); + let local_index = instruction.first_argument(); + let local = self.chunk.get_local(local_index, position)?.clone(); + let value = self.clone_as_variable(local, position)?; + let new_value = if instruction.first_argument_is_constant() { + self.chunk.take_constant(register_index, position)? + } else { + self.clone(register_index, position)? + }; - self.chunk.define_local(to_local, from_register, position)?; + value + .mutate(new_value) + .map_err(|error| VmError::Value { error, position })?; + + self.insert(value, register_index, position)?; } Operation::Add => { let (left, right) = take_constants_or_clone(self, instruction, position)?; @@ -258,19 +270,40 @@ impl Vm { } } + fn clone_mutable(&mut self, index: u8, position: Span) -> Result { + let index = index as usize; + + if let Some(register) = self.register_stack.get_mut(index) { + let cloneable = if let Some(value) = register.take() { + value.into_mutable() + } else { + return Err(VmError::EmptyRegister { index, position }); + }; + + *register = Some(cloneable.clone()); + + Ok(cloneable) + } else { + Err(VmError::RegisterIndexOutOfBounds { position }) + } + } + fn clone_as_variable(&mut self, local: Local, position: Span) -> Result { let index = if let Some(index) = local.register_index { index } else { return Err(VmError::UndefinedVariable { - identifier: local.identifier.clone(), + identifier: local.identifier, position, }); }; - let clone_result = self.clone(index, position); + let clone_result = if local.mutable { + self.clone_mutable(index, position) + } else { + self.clone(index, position) + }; match clone_result { - Ok(value) => Ok(value), Err(VmError::EmptyRegister { .. }) => Err(VmError::UndefinedVariable { identifier: local.identifier, position, @@ -334,8 +367,8 @@ pub enum VmError { } impl From for VmError { - fn from(v: ChunkError) -> Self { - Self::Chunk(v) + fn from(error: ChunkError) -> Self { + Self::Chunk(error) } }