Add mutable variables

This commit is contained in:
Jeff 2024-09-18 11:27:41 -04:00
parent 2cb03297c5
commit 85b95a56aa
6 changed files with 150 additions and 49 deletions

View File

@ -144,6 +144,7 @@ impl Chunk {
pub fn declare_local( pub fn declare_local(
&mut self, &mut self,
identifier: Identifier, identifier: Identifier,
mutable: bool,
register_index: u8, register_index: u8,
position: Span, position: Span,
) -> Result<u8, ChunkError> { ) -> Result<u8, ChunkError> {
@ -154,6 +155,7 @@ impl Chunk {
} else { } else {
self.locals.push(Local::new( self.locals.push(Local::new(
identifier, identifier,
mutable,
self.scope_depth, self.scope_depth,
Some(register_index), Some(register_index),
)); ));
@ -236,14 +238,21 @@ impl PartialEq for Chunk {
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct Local { pub struct Local {
pub identifier: Identifier, pub identifier: Identifier,
pub mutable: bool,
pub depth: usize, pub depth: usize,
pub register_index: Option<u8>, pub register_index: Option<u8>,
} }
impl Local { impl Local {
pub fn new(identifier: Identifier, depth: usize, register_index: Option<u8>) -> Self { pub fn new(
identifier: Identifier,
mutable: bool,
depth: usize,
register_index: Option<u8>,
) -> Self {
Self { Self {
identifier, identifier,
mutable,
depth, depth,
register_index, register_index,
} }
@ -278,8 +287,8 @@ impl<'a> ChunkDisassembler<'a> {
"", "",
"Locals", "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 /// The default width of the disassembly output. To correctly align the output, this should be
@ -384,6 +393,7 @@ impl<'a> ChunkDisassembler<'a> {
identifier, identifier,
depth, depth,
register_index, register_index,
mutable,
}, },
) in self.chunk.locals.iter().enumerate() ) in self.chunk.locals.iter().enumerate()
{ {
@ -392,8 +402,9 @@ impl<'a> ChunkDisassembler<'a> {
.map(|value| value.to_string()) .map(|value| value.to_string())
.unwrap_or_else(|| "empty".to_string()); .unwrap_or_else(|| "empty".to_string());
let identifier_display = identifier.as_str(); let identifier_display = identifier.as_str();
let local_display = let local_display = format!(
format!("{index:<5} {identifier_display:<10} {depth:<5} {register_display:<8}"); "{index:<5} {identifier_display:<10} {mutable:<7} {depth:<5} {register_display:<8}"
);
disassembled.push_str(&center(&local_display)); disassembled.push_str(&center(&local_display));
} }

View File

@ -42,11 +42,12 @@ impl Instruction {
instruction instruction
} }
pub fn declare_local(to_register: u8, variable_index: u8) -> Instruction { pub fn define_local(to_register: u8, variable_index: u8, is_mutable: bool) -> Instruction {
let mut instruction = Instruction(Operation::DeclareLocal as u32); let mut instruction = Instruction(Operation::DefineLocal as u32);
instruction.set_destination(to_register); instruction.set_destination(to_register);
instruction.set_first_argument(variable_index); instruction.set_first_argument(variable_index);
instruction.set_second_argument(if is_mutable { 1 } else { 0 });
instruction instruction
} }
@ -284,7 +285,7 @@ impl Instruction {
destination, first_index, last_index destination, first_index, last_index
) )
} }
Operation::DeclareLocal => { Operation::DefineLocal => {
let local_index = self.first_argument(); let local_index = self.first_argument();
let identifier_display = if let Some(chunk) = chunk { let identifier_display = if let Some(chunk) = chunk {
match chunk.get_identifier(local_index) { match chunk.get_identifier(local_index) {
@ -433,7 +434,7 @@ pub enum Operation {
LoadList = LOAD_LIST as isize, LoadList = LOAD_LIST as isize,
// Variables // Variables
DeclareLocal = DECLARE_LOCAL as isize, DefineLocal = DECLARE_LOCAL as isize,
GetLocal = GET_LOCAL as isize, GetLocal = GET_LOCAL as isize,
SetLocal = SET_LOCAL as isize, SetLocal = SET_LOCAL as isize,
@ -476,7 +477,7 @@ impl From<u8> for Operation {
CLOSE => Operation::Close, CLOSE => Operation::Close,
LOAD_CONSTANT => Operation::LoadConstant, LOAD_CONSTANT => Operation::LoadConstant,
LOAD_LIST => Operation::LoadList, LOAD_LIST => Operation::LoadList,
DECLARE_LOCAL => Operation::DeclareLocal, DECLARE_LOCAL => Operation::DefineLocal,
GET_LOCAL => Operation::GetLocal, GET_LOCAL => Operation::GetLocal,
SET_LOCAL => Operation::SetLocal, SET_LOCAL => Operation::SetLocal,
ADD => Operation::Add, ADD => Operation::Add,
@ -501,7 +502,7 @@ impl From<Operation> for u8 {
Operation::Close => CLOSE, Operation::Close => CLOSE,
Operation::LoadConstant => LOAD_CONSTANT, Operation::LoadConstant => LOAD_CONSTANT,
Operation::LoadList => LOAD_LIST, Operation::LoadList => LOAD_LIST,
Operation::DeclareLocal => DECLARE_LOCAL, Operation::DefineLocal => DECLARE_LOCAL,
Operation::GetLocal => GET_LOCAL, Operation::GetLocal => GET_LOCAL,
Operation::SetLocal => SET_LOCAL, Operation::SetLocal => SET_LOCAL,
Operation::Add => ADD, Operation::Add => ADD,
@ -525,7 +526,7 @@ impl Display for Operation {
Operation::Close => write!(f, "CLOSE"), Operation::Close => write!(f, "CLOSE"),
Operation::LoadConstant => write!(f, "LOAD_CONSTANT"), Operation::LoadConstant => write!(f, "LOAD_CONSTANT"),
Operation::LoadList => write!(f, "LOAD_LIST"), 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::GetLocal => write!(f, "GET_LOCAL"),
Operation::SetLocal => write!(f, "SET_LOCAL"), Operation::SetLocal => write!(f, "SET_LOCAL"),
Operation::Add => write!(f, "ADD"), Operation::Add => write!(f, "ADD"),
@ -585,16 +586,15 @@ mod tests {
#[test] #[test]
fn declare_local() { 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_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.destination(), 0);
assert_eq!(instruction.first_argument(), 1); assert_eq!(instruction.first_argument(), 1);
assert_eq!(instruction.second_argument(), true as u8);
assert!(instruction.first_argument_is_constant()); assert!(instruction.first_argument_is_constant());
assert!(instruction.second_argument_is_constant());
} }
#[test] #[test]

View File

@ -295,12 +295,22 @@ impl<'src> Parser<'src> {
let mut left_is_constant = false; let mut left_is_constant = false;
let left = match left_instruction.operation() { let left = match left_instruction.operation() {
Operation::LoadConstant => { Operation::LoadConstant => {
log::trace!(
"Condensing {} to binary expression",
left_instruction.operation()
);
left_is_constant = true; left_is_constant = true;
self.decrement_register()?; self.decrement_register()?;
left_instruction.first_argument() left_instruction.first_argument()
} }
Operation::GetLocal => { Operation::GetLocal => {
log::trace!(
"Condensing {} to binary expression",
left_instruction.operation()
);
self.decrement_register()?; self.decrement_register()?;
left_instruction.first_argument() left_instruction.first_argument()
} }
@ -330,12 +340,22 @@ impl<'src> Parser<'src> {
let mut right_is_constant = false; let mut right_is_constant = false;
let right = match right_instruction.operation() { let right = match right_instruction.operation() {
Operation::LoadConstant => { Operation::LoadConstant => {
log::trace!(
"Condensing {} to binary expression",
right_instruction.operation()
);
right_is_constant = true; right_is_constant = true;
self.decrement_register()?; self.decrement_register()?;
right_instruction.first_argument() right_instruction.first_argument()
} }
Operation::GetLocal => { Operation::GetLocal => {
log::trace!(
"Condensing {} to binary expression",
right_instruction.operation()
);
self.decrement_register()?; self.decrement_register()?;
right_instruction.first_argument() right_instruction.first_argument()
} }
@ -423,6 +443,8 @@ impl<'src> Parser<'src> {
.register_index; .register_index;
if let Some(register_index) = previous_register { if let Some(register_index) = previous_register {
log::trace!("Condensing SET_LOCAL to binary expression");
previous_instruction.set_destination(register_index); previous_instruction.set_destination(register_index);
self.emit_instruction(previous_instruction, self.current_position); self.emit_instruction(previous_instruction, self.current_position);
} else { } else {
@ -592,6 +614,7 @@ impl<'src> Parser<'src> {
self.advance()?; self.advance()?;
let is_mutable = self.allow(TokenKind::Mut)?;
let position = self.current_position; let position = self.current_position;
let identifier = if let Token::Identifier(text) = self.current_token { let identifier = if let Token::Identifier(text) = self.current_token {
self.advance()?; self.advance()?;
@ -609,11 +632,14 @@ impl<'src> Parser<'src> {
self.parse_expression()?; self.parse_expression()?;
let register = self.chunk.get_last_instruction(position)?.0.destination(); let register = self.chunk.get_last_instruction(position)?.0.destination();
let local_index = self let local_index =
.chunk self.chunk
.declare_local(identifier, register, self.current_position)?; .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)?; self.allow(TokenKind::Semicolon)?;
Ok(()) Ok(())
@ -824,8 +850,16 @@ impl From<&TokenKind> for ParseRule<'_> {
precedence: Precedence::Term, precedence: Precedence::Term,
}, },
TokenKind::MinusEqual => todo!(), TokenKind::MinusEqual => todo!(),
TokenKind::Mut => todo!(), TokenKind::Mut => ParseRule {
TokenKind::Percent => todo!(), prefix: None,
infix: None,
precedence: Precedence::None,
},
TokenKind::Percent => ParseRule {
prefix: None,
infix: Some(Parser::parse_binary),
precedence: Precedence::Factor,
},
TokenKind::Plus => ParseRule { TokenKind::Plus => ParseRule {
prefix: None, prefix: None,
infix: Some(Parser::parse_binary), infix: Some(Parser::parse_binary),

View File

@ -71,17 +71,17 @@ fn block_scope() {
Ok(Chunk::with_data( Ok(Chunk::with_data(
vec![ vec![
(Instruction::load_constant(0, 0), Span(17, 18)), (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::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::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::close(2, 3), Span(84, 124)),
(Instruction::load_constant(3, 3), Span(129, 130)), (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::close(1, 4), Span(42, 153)),
(Instruction::load_constant(4, 4), Span(158, 159)), (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![ vec![
Value::integer(0), Value::integer(0),
@ -91,11 +91,11 @@ fn block_scope() {
Value::integer(1) Value::integer(1)
], ],
vec![ vec![
Local::new(Identifier::new("a"), 0, Some(0)), Local::new(Identifier::new("a"), false, 0, Some(0)),
Local::new(Identifier::new("b"), 1, Some(1)), Local::new(Identifier::new("b"), false, 1, Some(1)),
Local::new(Identifier::new("c"), 2, Some(2)), Local::new(Identifier::new("c"), false, 2, Some(2)),
Local::new(Identifier::new("d"), 1, Some(3)), Local::new(Identifier::new("d"), false, 1, Some(3)),
Local::new(Identifier::new("e"), 0, Some(4)), Local::new(Identifier::new("e"), false, 0, Some(4)),
] ]
)), )),
); );
@ -113,12 +113,12 @@ fn set_local() {
Ok(Chunk::with_data( Ok(Chunk::with_data(
vec![ vec![
(Instruction::load_constant(0, 0), Span(8, 10)), (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::load_constant(1, 1), Span(16, 18)),
(Instruction::set_local(1, 0), Span(12, 13)), (Instruction::set_local(1, 0), Span(12, 13)),
], ],
vec![Value::integer(41), Value::integer(42)], 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( Ok(Chunk::with_data(
vec![ vec![
(Instruction::load_constant(0, 0), Span(8, 10)), (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![Value::integer(42)],
vec![Local::new(Identifier::new("x"), 0, Some(0))] vec![Local::new(Identifier::new("x"), false, 0, Some(0))]
)), )),
); );
} }

View File

@ -34,7 +34,7 @@
use std::{ use std::{
cmp::Ordering, cmp::Ordering,
collections::HashMap, collections::HashMap,
fmt::{self, Display, Formatter}, fmt::{self, Debug, Display, Formatter},
ops::{Range, RangeInclusive}, ops::{Range, RangeInclusive},
sync::{Arc, RwLock}, sync::{Arc, RwLock},
}; };
@ -50,7 +50,6 @@ use crate::{EnumType, FunctionType, Identifier, RangeableType, StructType, Type}
/// Dust value representation /// Dust value representation
/// ///
/// See the [module-level documentation][self] for more. /// See the [module-level documentation][self] for more.
#[derive(Debug)]
pub enum Value { pub enum Value {
Raw(ValueData), Raw(ValueData),
Reference(Arc<ValueData>), Reference(Arc<ValueData>),
@ -127,6 +126,8 @@ impl Value {
} }
pub fn into_reference(self) -> Self { pub fn into_reference(self) -> Self {
log::trace!("Converting to reference: {self:?}");
match self { match self {
Value::Raw(data) => Value::Reference(Arc::new(data)), Value::Raw(data) => Value::Reference(Arc::new(data)),
Value::Reference(_) => self, Value::Reference(_) => self,
@ -140,8 +141,16 @@ impl Value {
pub fn into_mutable(self) -> Self { pub fn into_mutable(self) -> Self {
match self { match self {
Value::Raw(data) => Value::Mutable(Arc::new(RwLock::new(data))), Value::Raw(data) => {
Value::Reference(data) => Value::Mutable(Arc::new(RwLock::new(data.as_ref().clone()))), 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, 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 Eq for Value {}
impl PartialEq for Value { impl PartialEq for Value {

View File

@ -35,6 +35,7 @@ impl Vm {
} }
pub fn run(&mut self) -> Result<Option<Value>, VmError> { pub fn run(&mut self) -> Result<Option<Value>, VmError> {
// DRY helper closure to take a constant or clone a register
let take_constants_or_clone = |vm: &mut Vm, let take_constants_or_clone = |vm: &mut Vm,
instruction: Instruction, instruction: Instruction,
position: Span| position: Span|
@ -107,7 +108,7 @@ impl Vm {
self.insert(Value::list(list), to_register, position)?; self.insert(Value::list(list), to_register, position)?;
} }
Operation::DeclareLocal => { Operation::DefineLocal => {
let from_register = instruction.destination(); let from_register = instruction.destination();
let to_local = instruction.first_argument(); let to_local = instruction.first_argument();
@ -122,10 +123,21 @@ impl Vm {
self.insert(value, register_index, position)?; self.insert(value, register_index, position)?;
} }
Operation::SetLocal => { Operation::SetLocal => {
let from_register = instruction.destination(); let register_index = instruction.destination();
let to_local = instruction.first_argument(); 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 => { Operation::Add => {
let (left, right) = take_constants_or_clone(self, instruction, position)?; 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<Value, VmError> {
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<Value, VmError> { fn clone_as_variable(&mut self, local: Local, position: Span) -> Result<Value, VmError> {
let index = if let Some(index) = local.register_index { let index = if let Some(index) = local.register_index {
index index
} else { } else {
return Err(VmError::UndefinedVariable { return Err(VmError::UndefinedVariable {
identifier: local.identifier.clone(), identifier: local.identifier,
position, 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 { match clone_result {
Ok(value) => Ok(value),
Err(VmError::EmptyRegister { .. }) => Err(VmError::UndefinedVariable { Err(VmError::EmptyRegister { .. }) => Err(VmError::UndefinedVariable {
identifier: local.identifier, identifier: local.identifier,
position, position,
@ -334,8 +367,8 @@ pub enum VmError {
} }
impl From<ChunkError> for VmError { impl From<ChunkError> for VmError {
fn from(v: ChunkError) -> Self { fn from(error: ChunkError) -> Self {
Self::Chunk(v) Self::Chunk(error)
} }
} }