Add mutable variables
This commit is contained in:
parent
2cb03297c5
commit
85b95a56aa
@ -144,6 +144,7 @@ impl Chunk {
|
||||
pub fn declare_local(
|
||||
&mut self,
|
||||
identifier: Identifier,
|
||||
mutable: bool,
|
||||
register_index: u8,
|
||||
position: Span,
|
||||
) -> Result<u8, ChunkError> {
|
||||
@ -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<u8>,
|
||||
}
|
||||
|
||||
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 {
|
||||
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));
|
||||
}
|
||||
|
@ -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<u8> 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<Operation> 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]
|
||||
|
@ -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),
|
||||
|
@ -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))]
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
@ -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<ValueData>),
|
||||
@ -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 {
|
||||
|
@ -35,6 +35,7 @@ impl Vm {
|
||||
}
|
||||
|
||||
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,
|
||||
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<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> {
|
||||
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<ChunkError> for VmError {
|
||||
fn from(v: ChunkError) -> Self {
|
||||
Self::Chunk(v)
|
||||
fn from(error: ChunkError) -> Self {
|
||||
Self::Chunk(error)
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user