Avoid cloning constant and move identifier stack to the chunk

This commit is contained in:
Jeff 2024-09-11 04:22:54 -04:00
parent e4204c1b0d
commit 86f8e47b0c
5 changed files with 110 additions and 48 deletions

View File

@ -4,10 +4,10 @@ use serde::{Deserialize, Serialize};
use crate::{AnnotatedError, Identifier, Instruction, Span, Value}; use crate::{AnnotatedError, Identifier, Instruction, Span, Value};
#[derive(Clone, Eq, PartialEq, Serialize, Deserialize)] #[derive(Clone, Serialize, Deserialize)]
pub struct Chunk { pub struct Chunk {
code: Vec<(u8, Span)>, code: Vec<(u8, Span)>,
constants: Vec<Value>, constants: Vec<Option<Value>>,
identifiers: Vec<Local>, identifiers: Vec<Local>,
scope_depth: usize, scope_depth: usize,
} }
@ -29,7 +29,7 @@ impl Chunk {
) -> Self { ) -> Self {
Self { Self {
code, code,
constants, constants: constants.into_iter().map(Some).collect(),
identifiers, identifiers,
scope_depth: 0, scope_depth: 0,
} }
@ -61,19 +61,27 @@ impl Chunk {
self.constants self.constants
.get(index as usize) .get(index as usize)
.ok_or(ChunkError::ConstantIndexOutOfBounds { index, position }) .ok_or(ChunkError::ConstantIndexOutOfBounds { index, position })
.and_then(|value| {
value
.as_ref()
.ok_or(ChunkError::ConstantAlreadyUsed { index, position })
})
} }
pub fn remove_constant(&mut self, index: u8, position: Span) -> Result<Value, ChunkError> { pub fn use_constant(&mut self, index: u8, position: Span) -> Result<Value, ChunkError> {
let index = index as usize; let index = index as usize;
if index >= self.constants.len() { self.constants
Err(ChunkError::ConstantIndexOutOfBounds { .get_mut(index)
.ok_or_else(|| ChunkError::ConstantIndexOutOfBounds {
index: index as u8,
position,
})?
.take()
.ok_or(ChunkError::ConstantAlreadyUsed {
index: index as u8, index: index as u8,
position, position,
}) })
} else {
Ok(self.constants.remove(index))
}
} }
pub fn push_constant(&mut self, value: Value, position: Span) -> Result<u8, ChunkError> { pub fn push_constant(&mut self, value: Value, position: Span) -> Result<u8, ChunkError> {
@ -82,7 +90,7 @@ impl Chunk {
if starting_length + 1 > (u8::MAX as usize) { if starting_length + 1 > (u8::MAX as usize) {
Err(ChunkError::ConstantOverflow { position }) Err(ChunkError::ConstantOverflow { position })
} else { } else {
self.constants.push(value); self.constants.push(Some(value));
Ok(starting_length as u8) Ok(starting_length as u8)
} }
@ -100,11 +108,12 @@ impl Chunk {
.ok_or(ChunkError::IdentifierIndexOutOfBounds { index, position }) .ok_or(ChunkError::IdentifierIndexOutOfBounds { index, position })
} }
pub fn get_identifier(&self, index: u8, position: Span) -> Result<&Identifier, ChunkError> { pub fn get_identifier(&self, index: u8) -> Option<&Identifier> {
self.identifiers if let Some(local) = self.identifiers.get(index as usize) {
.get(index as usize) Some(&local.identifier)
.map(|local| &local.identifier) } else {
.ok_or(ChunkError::IdentifierIndexOutOfBounds { index, position }) None
}
} }
pub fn get_identifier_index( pub fn get_identifier_index(
@ -114,8 +123,8 @@ impl Chunk {
) -> Result<u8, ChunkError> { ) -> Result<u8, ChunkError> {
self.identifiers self.identifiers
.iter() .iter()
.enumerate()
.rev() .rev()
.enumerate()
.find_map(|(index, local)| { .find_map(|(index, local)| {
if &local.identifier == identifier { if &local.identifier == identifier {
Some(index as u8) Some(index as u8)
@ -216,20 +225,25 @@ impl Chunk {
output.push_str("INDEX KIND VALUE\n"); output.push_str("INDEX KIND VALUE\n");
output.push_str("----- ---- -----\n"); output.push_str("----- ---- -----\n");
for (index, value) in self.constants.iter().enumerate() { for (index, value_option) in self.constants.iter().enumerate() {
let value_kind_display = match value { let value_kind_display = match value_option {
Value::Raw(_) => "RAW ", Some(Value::Raw(_)) => "RAW ",
Value::Reference(_) => "REF ", Some(Value::Reference(_)) => "REF ",
Value::Mutable(_) => "MUT ", Some(Value::Mutable(_)) => "MUT ",
None => "EMPTY",
}; };
let display = format!("{index:3} {value_kind_display} {value}\n"); let value_display = value_option
.as_ref()
.map(|value| value.to_string())
.unwrap_or_else(|| "EMPTY".to_string());
let display = format!("{index:3} {value_kind_display} {value_display}\n",);
output.push_str(&display); output.push_str(&display);
} }
output.push_str("\n Identifiers\n"); output.push_str("\n Identifiers\n");
output.push_str("----- ---------- -----\n"); output.push_str("----- ---------- -----\n");
output.push_str("INDEX IDENTIFIER DEPTH\n"); output.push_str("INDEX NAME DEPTH\n");
output.push_str("----- ---------- -----\n"); output.push_str("----- ---------- -----\n");
for (index, Local { identifier, depth }) in self.identifiers.iter().enumerate() { for (index, Local { identifier, depth }) in self.identifiers.iter().enumerate() {
@ -259,6 +273,16 @@ impl Debug for Chunk {
} }
} }
impl Eq for Chunk {}
impl PartialEq for Chunk {
fn eq(&self, other: &Self) -> bool {
self.code == other.code
&& self.constants == other.constants
&& self.identifiers == other.identifiers
}
}
#[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,
@ -271,6 +295,10 @@ pub enum ChunkError {
offset: usize, offset: usize,
position: Span, position: Span,
}, },
ConstantAlreadyUsed {
index: u8,
position: Span,
},
ConstantOverflow { ConstantOverflow {
position: Span, position: Span,
}, },
@ -299,6 +327,7 @@ impl AnnotatedError for ChunkError {
fn description(&self) -> &'static str { fn description(&self) -> &'static str {
match self { match self {
ChunkError::CodeIndexOfBounds { .. } => "Code index out of bounds", ChunkError::CodeIndexOfBounds { .. } => "Code index out of bounds",
ChunkError::ConstantAlreadyUsed { .. } => "Constant already used",
ChunkError::ConstantOverflow { .. } => "Constant overflow", ChunkError::ConstantOverflow { .. } => "Constant overflow",
ChunkError::ConstantIndexOutOfBounds { .. } => "Constant index out of bounds", ChunkError::ConstantIndexOutOfBounds { .. } => "Constant index out of bounds",
ChunkError::IdentifierIndexOutOfBounds { .. } => "Identifier index out of bounds", ChunkError::IdentifierIndexOutOfBounds { .. } => "Identifier index out of bounds",
@ -310,6 +339,9 @@ impl AnnotatedError for ChunkError {
fn details(&self) -> Option<String> { fn details(&self) -> Option<String> {
match self { match self {
ChunkError::CodeIndexOfBounds { offset, .. } => Some(format!("Code index: {}", offset)), ChunkError::CodeIndexOfBounds { offset, .. } => Some(format!("Code index: {}", offset)),
ChunkError::ConstantAlreadyUsed { index, .. } => {
Some(format!("Constant index: {}", index))
}
ChunkError::ConstantIndexOutOfBounds { index, .. } => { ChunkError::ConstantIndexOutOfBounds { index, .. } => {
Some(format!("Constant index: {}", index)) Some(format!("Constant index: {}", index))
} }
@ -326,6 +358,7 @@ impl AnnotatedError for ChunkError {
fn position(&self) -> Span { fn position(&self) -> Span {
match self { match self {
ChunkError::CodeIndexOfBounds { position, .. } => *position, ChunkError::CodeIndexOfBounds { position, .. } => *position,
ChunkError::ConstantAlreadyUsed { position, .. } => *position,
ChunkError::ConstantIndexOutOfBounds { position, .. } => *position, ChunkError::ConstantIndexOutOfBounds { position, .. } => *position,
ChunkError::IdentifierNotFound { position, .. } => *position, ChunkError::IdentifierNotFound { position, .. } => *position,
_ => todo!(), _ => todo!(),

View File

@ -80,29 +80,29 @@ impl Instruction {
// Variables // Variables
Instruction::DeclareVariable => { Instruction::DeclareVariable => {
let (argument, _) = chunk.get_code(offset + 1, dummy_position).unwrap(); let (argument, _) = chunk.get_code(offset + 1, dummy_position).unwrap();
let identifier_display = match chunk.get_identifier(*argument, dummy_position) { let identifier_display = chunk
Ok(identifier) => identifier.to_string(), .get_identifier(*argument)
Err(error) => format!("{error:?}"), .map(|identifier| identifier.to_string())
}; .unwrap_or_else(|| "ERROR".to_string());
format!("DECLARE_VARIABLE {identifier_display}") format!("DECLARE_VARIABLE {identifier_display}")
} }
Instruction::GetVariable => { Instruction::GetVariable => {
let (argument, _) = chunk.get_code(offset + 1, dummy_position).unwrap(); let (argument, _) = chunk.get_code(offset + 1, dummy_position).unwrap();
let identifier_display = match chunk.get_identifier(*argument, dummy_position) { let identifier_display = chunk
Ok(identifier) => identifier.to_string(), .get_identifier(*argument)
Err(error) => format!("{error:?}"), .map(|identifier| identifier.to_string())
}; .unwrap_or_else(|| "ERROR".to_string());
format!("GET_VARIABLE {identifier_display}") format!("GET_VARIABLE {identifier_display}")
} }
Instruction::SetVariable => { Instruction::SetVariable => {
let (argument, _) = chunk.get_code(offset + 1, dummy_position).unwrap(); let (argument, _) = chunk.get_code(offset + 1, dummy_position).unwrap();
let identifier_display = match chunk.get_identifier(*argument, dummy_position) { let identifier_display = chunk
Ok(identifier) => identifier.to_string(), .get_identifier(*argument)
Err(error) => format!("{error:?}"), .map(|identifier| identifier.to_string())
}; .unwrap_or_else(|| "ERROR".to_string());
format!("SET_VARIABLE {identifier_display}") format!("SET_VARIABLE {identifier_display}")
} }

View File

@ -288,8 +288,7 @@ impl<'src> Parser<'src> {
.chunk .chunk
.identifiers() .identifiers()
.iter() .iter()
.rev() .next_back()
.next()
.map_or(false, |local| local.depth > self.chunk.scope_depth()) .map_or(false, |local| local.depth > self.chunk.scope_depth())
{ {
self.emit_byte(Instruction::Pop, self.current_position); self.emit_byte(Instruction::Pop, self.current_position);
@ -390,7 +389,7 @@ impl<'src> Parser<'src> {
self.previous_token, self.previous_token,
); );
if allow_assignment && self.allow(TokenKind::Equal)? { if allow_assignment && self.current_token == Token::Equal {
return Err(ParseError::InvalidAssignmentTarget { return Err(ParseError::InvalidAssignmentTarget {
found: self.previous_token.to_owned(), found: self.previous_token.to_owned(),
position: self.previous_position, position: self.previous_position,

View File

@ -1,5 +1,3 @@
use std::rc::{Rc, Weak};
use crate::{ use crate::{
dust_error::AnnotatedError, parse, Chunk, ChunkError, DustError, Identifier, Instruction, Span, dust_error::AnnotatedError, parse, Chunk, ChunkError, DustError, Identifier, Instruction, Span,
Value, ValueError, Value, ValueError,
@ -16,7 +14,7 @@ pub fn run(source: &str) -> Result<Option<Value>, DustError> {
#[derive(Debug, Eq, PartialEq)] #[derive(Debug, Eq, PartialEq)]
pub struct Vm { pub struct Vm {
chunk: Rc<Chunk>, chunk: Chunk,
ip: usize, ip: usize,
stack: Vec<Value>, stack: Vec<Value>,
} }
@ -26,7 +24,7 @@ impl Vm {
pub fn new(chunk: Chunk) -> Self { pub fn new(chunk: Chunk) -> Self {
Self { Self {
chunk: Rc::new(chunk), chunk,
ip: 0, ip: 0,
stack: Vec::with_capacity(Self::STACK_SIZE), stack: Vec::with_capacity(Self::STACK_SIZE),
} }
@ -46,7 +44,7 @@ impl Vm {
match instruction { match instruction {
Instruction::Constant => { Instruction::Constant => {
let (argument, _) = *self.read(position)?; let (argument, _) = *self.read(position)?;
let value = self.chunk.get_constant(argument, position)?.clone(); let value = self.chunk.use_constant(argument, position)?;
log::trace!("Pushing constant {value}"); log::trace!("Pushing constant {value}");
@ -68,7 +66,10 @@ impl Vm {
// Variables // Variables
Instruction::DeclareVariable => { Instruction::DeclareVariable => {
let (argument, _) = *self.read(position)?; let (argument, _) = *self.read(position)?;
let identifier = self.chunk.get_identifier(argument, position)?; let identifier = self
.chunk
.get_identifier(argument)
.ok_or_else(|| VmError::UndeclaredVariable { position })?;
let value = self.stack.remove(argument as usize); let value = self.stack.remove(argument as usize);
log::trace!("Declaring {identifier} as value {value}",); log::trace!("Declaring {identifier} as value {value}",);
@ -77,7 +78,10 @@ impl Vm {
} }
Instruction::GetVariable => { Instruction::GetVariable => {
let (argument, _) = *self.read(position)?; let (argument, _) = *self.read(position)?;
let identifier = self.chunk.get_identifier(argument, position)?; let identifier = self
.chunk
.get_identifier(argument)
.ok_or_else(|| VmError::UndeclaredVariable { position })?;
let value = self.stack.remove(argument as usize); let value = self.stack.remove(argument as usize);
log::trace!("Getting {identifier} as value {value}",); log::trace!("Getting {identifier} as value {value}",);
@ -86,7 +90,11 @@ impl Vm {
} }
Instruction::SetVariable => { Instruction::SetVariable => {
let (argument, _) = *self.read(position)?; let (argument, _) = *self.read(position)?;
let identifier = self.chunk.get_identifier(argument, position)?.clone(); let identifier = self
.chunk
.get_identifier(argument)
.ok_or_else(|| VmError::UndeclaredVariable { position })?
.clone();
if !self.chunk.contains_identifier(&identifier) { if !self.chunk.contains_identifier(&identifier) {
return Err(VmError::UndefinedVariable { return Err(VmError::UndefinedVariable {
@ -273,6 +281,9 @@ pub enum VmError {
InvalidInstruction(u8, Span), InvalidInstruction(u8, Span),
StackOverflow(Span), StackOverflow(Span),
StackUnderflow(Span), StackUnderflow(Span),
UndeclaredVariable {
position: Span,
},
UndefinedVariable { UndefinedVariable {
identifier: Identifier, identifier: Identifier,
position: Span, position: Span,
@ -302,6 +313,7 @@ impl AnnotatedError for VmError {
Self::InvalidInstruction(_, _) => "Invalid instruction", Self::InvalidInstruction(_, _) => "Invalid instruction",
Self::StackOverflow(_) => "Stack overflow", Self::StackOverflow(_) => "Stack overflow",
Self::StackUnderflow(_) => "Stack underflow", Self::StackUnderflow(_) => "Stack underflow",
Self::UndeclaredVariable { .. } => "Undeclared variable",
Self::UndefinedVariable { .. } => "Undefined variable", Self::UndefinedVariable { .. } => "Undefined variable",
Self::Chunk(_) => "Chunk error", Self::Chunk(_) => "Chunk error",
Self::Value { .. } => "Value error", Self::Value { .. } => "Value error",
@ -315,6 +327,7 @@ impl AnnotatedError for VmError {
)), )),
Self::StackOverflow(position) => Some(format!("Stack overflow at {position}")), Self::StackOverflow(position) => Some(format!("Stack overflow at {position}")),
Self::StackUnderflow(position) => Some(format!("Stack underflow at {position}")), Self::StackUnderflow(position) => Some(format!("Stack underflow at {position}")),
Self::UndeclaredVariable { .. } => Some("Variable is not declared".to_string()),
Self::UndefinedVariable { identifier, .. } => { Self::UndefinedVariable { identifier, .. } => {
Some(format!("{identifier} is not in scope")) Some(format!("{identifier} is not in scope"))
} }
@ -328,6 +341,7 @@ impl AnnotatedError for VmError {
Self::InvalidInstruction(_, position) => *position, Self::InvalidInstruction(_, position) => *position,
Self::StackUnderflow(position) => *position, Self::StackUnderflow(position) => *position,
Self::StackOverflow(position) => *position, Self::StackOverflow(position) => *position,
Self::UndeclaredVariable { position } => *position,
Self::UndefinedVariable { position, .. } => *position, Self::UndefinedVariable { position, .. } => *position,
Self::Chunk(error) => error.position(), Self::Chunk(error) => error.position(),
Self::Value { position, .. } => *position, Self::Value { position, .. } => *position,

View File

@ -15,3 +15,19 @@ fn variable() {
assert_eq!(result, Ok(Some(Value::integer(42)))); assert_eq!(result, Ok(Some(Value::integer(42))));
} }
#[test]
fn lots_of_variables() {
env_logger::builder().is_test(true).try_init().unwrap();
let source = "
let foo = 1;
let bar = 2;
let baz = 3;
let qux = 4;
let quux = 5;
foo + bar + baz + qux + quux";
let result = run(source);
assert_eq!(result, Ok(Some(Value::integer(15))));
}