Avoid cloning constant and move identifier stack to the chunk
This commit is contained in:
parent
e4204c1b0d
commit
86f8e47b0c
@ -4,10 +4,10 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{AnnotatedError, Identifier, Instruction, Span, Value};
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct Chunk {
|
||||
code: Vec<(u8, Span)>,
|
||||
constants: Vec<Value>,
|
||||
constants: Vec<Option<Value>>,
|
||||
identifiers: Vec<Local>,
|
||||
scope_depth: usize,
|
||||
}
|
||||
@ -29,7 +29,7 @@ impl Chunk {
|
||||
) -> Self {
|
||||
Self {
|
||||
code,
|
||||
constants,
|
||||
constants: constants.into_iter().map(Some).collect(),
|
||||
identifiers,
|
||||
scope_depth: 0,
|
||||
}
|
||||
@ -61,19 +61,27 @@ impl Chunk {
|
||||
self.constants
|
||||
.get(index as usize)
|
||||
.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;
|
||||
|
||||
if index >= self.constants.len() {
|
||||
Err(ChunkError::ConstantIndexOutOfBounds {
|
||||
self.constants
|
||||
.get_mut(index)
|
||||
.ok_or_else(|| ChunkError::ConstantIndexOutOfBounds {
|
||||
index: index as u8,
|
||||
position,
|
||||
})?
|
||||
.take()
|
||||
.ok_or(ChunkError::ConstantAlreadyUsed {
|
||||
index: index as u8,
|
||||
position,
|
||||
})
|
||||
} else {
|
||||
Ok(self.constants.remove(index))
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
Err(ChunkError::ConstantOverflow { position })
|
||||
} else {
|
||||
self.constants.push(value);
|
||||
self.constants.push(Some(value));
|
||||
|
||||
Ok(starting_length as u8)
|
||||
}
|
||||
@ -100,11 +108,12 @@ impl Chunk {
|
||||
.ok_or(ChunkError::IdentifierIndexOutOfBounds { index, position })
|
||||
}
|
||||
|
||||
pub fn get_identifier(&self, index: u8, position: Span) -> Result<&Identifier, ChunkError> {
|
||||
self.identifiers
|
||||
.get(index as usize)
|
||||
.map(|local| &local.identifier)
|
||||
.ok_or(ChunkError::IdentifierIndexOutOfBounds { index, position })
|
||||
pub fn get_identifier(&self, index: u8) -> Option<&Identifier> {
|
||||
if let Some(local) = self.identifiers.get(index as usize) {
|
||||
Some(&local.identifier)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_identifier_index(
|
||||
@ -114,8 +123,8 @@ impl Chunk {
|
||||
) -> Result<u8, ChunkError> {
|
||||
self.identifiers
|
||||
.iter()
|
||||
.enumerate()
|
||||
.rev()
|
||||
.enumerate()
|
||||
.find_map(|(index, local)| {
|
||||
if &local.identifier == identifier {
|
||||
Some(index as u8)
|
||||
@ -211,25 +220,30 @@ impl Chunk {
|
||||
previous = Some(instruction);
|
||||
}
|
||||
|
||||
output.push_str("\n Constants \n");
|
||||
output.push_str("\n Constants\n");
|
||||
output.push_str("----- ---- -----\n");
|
||||
output.push_str("INDEX KIND VALUE\n");
|
||||
output.push_str("----- ---- -----\n");
|
||||
|
||||
for (index, value) in self.constants.iter().enumerate() {
|
||||
let value_kind_display = match value {
|
||||
Value::Raw(_) => "RAW ",
|
||||
Value::Reference(_) => "REF ",
|
||||
Value::Mutable(_) => "MUT ",
|
||||
for (index, value_option) in self.constants.iter().enumerate() {
|
||||
let value_kind_display = match value_option {
|
||||
Some(Value::Raw(_)) => "RAW ",
|
||||
Some(Value::Reference(_)) => "REF ",
|
||||
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("\n Identifiers \n");
|
||||
output.push_str("\n Identifiers\n");
|
||||
output.push_str("----- ---------- -----\n");
|
||||
output.push_str("INDEX IDENTIFIER DEPTH\n");
|
||||
output.push_str("INDEX NAME DEPTH\n");
|
||||
output.push_str("----- ---------- -----\n");
|
||||
|
||||
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)]
|
||||
pub struct Local {
|
||||
pub identifier: Identifier,
|
||||
@ -271,6 +295,10 @@ pub enum ChunkError {
|
||||
offset: usize,
|
||||
position: Span,
|
||||
},
|
||||
ConstantAlreadyUsed {
|
||||
index: u8,
|
||||
position: Span,
|
||||
},
|
||||
ConstantOverflow {
|
||||
position: Span,
|
||||
},
|
||||
@ -299,6 +327,7 @@ impl AnnotatedError for ChunkError {
|
||||
fn description(&self) -> &'static str {
|
||||
match self {
|
||||
ChunkError::CodeIndexOfBounds { .. } => "Code index out of bounds",
|
||||
ChunkError::ConstantAlreadyUsed { .. } => "Constant already used",
|
||||
ChunkError::ConstantOverflow { .. } => "Constant overflow",
|
||||
ChunkError::ConstantIndexOutOfBounds { .. } => "Constant index out of bounds",
|
||||
ChunkError::IdentifierIndexOutOfBounds { .. } => "Identifier index out of bounds",
|
||||
@ -310,6 +339,9 @@ impl AnnotatedError for ChunkError {
|
||||
fn details(&self) -> Option<String> {
|
||||
match self {
|
||||
ChunkError::CodeIndexOfBounds { offset, .. } => Some(format!("Code index: {}", offset)),
|
||||
ChunkError::ConstantAlreadyUsed { index, .. } => {
|
||||
Some(format!("Constant index: {}", index))
|
||||
}
|
||||
ChunkError::ConstantIndexOutOfBounds { index, .. } => {
|
||||
Some(format!("Constant index: {}", index))
|
||||
}
|
||||
@ -326,6 +358,7 @@ impl AnnotatedError for ChunkError {
|
||||
fn position(&self) -> Span {
|
||||
match self {
|
||||
ChunkError::CodeIndexOfBounds { position, .. } => *position,
|
||||
ChunkError::ConstantAlreadyUsed { position, .. } => *position,
|
||||
ChunkError::ConstantIndexOutOfBounds { position, .. } => *position,
|
||||
ChunkError::IdentifierNotFound { position, .. } => *position,
|
||||
_ => todo!(),
|
||||
|
@ -80,29 +80,29 @@ impl Instruction {
|
||||
// Variables
|
||||
Instruction::DeclareVariable => {
|
||||
let (argument, _) = chunk.get_code(offset + 1, dummy_position).unwrap();
|
||||
let identifier_display = match chunk.get_identifier(*argument, dummy_position) {
|
||||
Ok(identifier) => identifier.to_string(),
|
||||
Err(error) => format!("{error:?}"),
|
||||
};
|
||||
let identifier_display = chunk
|
||||
.get_identifier(*argument)
|
||||
.map(|identifier| identifier.to_string())
|
||||
.unwrap_or_else(|| "ERROR".to_string());
|
||||
|
||||
format!("DECLARE_VARIABLE {identifier_display}")
|
||||
}
|
||||
Instruction::GetVariable => {
|
||||
let (argument, _) = chunk.get_code(offset + 1, dummy_position).unwrap();
|
||||
let identifier_display = match chunk.get_identifier(*argument, dummy_position) {
|
||||
Ok(identifier) => identifier.to_string(),
|
||||
Err(error) => format!("{error:?}"),
|
||||
};
|
||||
let identifier_display = chunk
|
||||
.get_identifier(*argument)
|
||||
.map(|identifier| identifier.to_string())
|
||||
.unwrap_or_else(|| "ERROR".to_string());
|
||||
|
||||
format!("GET_VARIABLE {identifier_display}")
|
||||
}
|
||||
|
||||
Instruction::SetVariable => {
|
||||
let (argument, _) = chunk.get_code(offset + 1, dummy_position).unwrap();
|
||||
let identifier_display = match chunk.get_identifier(*argument, dummy_position) {
|
||||
Ok(identifier) => identifier.to_string(),
|
||||
Err(error) => format!("{error:?}"),
|
||||
};
|
||||
let identifier_display = chunk
|
||||
.get_identifier(*argument)
|
||||
.map(|identifier| identifier.to_string())
|
||||
.unwrap_or_else(|| "ERROR".to_string());
|
||||
|
||||
format!("SET_VARIABLE {identifier_display}")
|
||||
}
|
||||
|
@ -288,8 +288,7 @@ impl<'src> Parser<'src> {
|
||||
.chunk
|
||||
.identifiers()
|
||||
.iter()
|
||||
.rev()
|
||||
.next()
|
||||
.next_back()
|
||||
.map_or(false, |local| local.depth > self.chunk.scope_depth())
|
||||
{
|
||||
self.emit_byte(Instruction::Pop, self.current_position);
|
||||
@ -390,7 +389,7 @@ impl<'src> Parser<'src> {
|
||||
self.previous_token,
|
||||
);
|
||||
|
||||
if allow_assignment && self.allow(TokenKind::Equal)? {
|
||||
if allow_assignment && self.current_token == Token::Equal {
|
||||
return Err(ParseError::InvalidAssignmentTarget {
|
||||
found: self.previous_token.to_owned(),
|
||||
position: self.previous_position,
|
||||
|
@ -1,5 +1,3 @@
|
||||
use std::rc::{Rc, Weak};
|
||||
|
||||
use crate::{
|
||||
dust_error::AnnotatedError, parse, Chunk, ChunkError, DustError, Identifier, Instruction, Span,
|
||||
Value, ValueError,
|
||||
@ -16,7 +14,7 @@ pub fn run(source: &str) -> Result<Option<Value>, DustError> {
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub struct Vm {
|
||||
chunk: Rc<Chunk>,
|
||||
chunk: Chunk,
|
||||
ip: usize,
|
||||
stack: Vec<Value>,
|
||||
}
|
||||
@ -26,7 +24,7 @@ impl Vm {
|
||||
|
||||
pub fn new(chunk: Chunk) -> Self {
|
||||
Self {
|
||||
chunk: Rc::new(chunk),
|
||||
chunk,
|
||||
ip: 0,
|
||||
stack: Vec::with_capacity(Self::STACK_SIZE),
|
||||
}
|
||||
@ -46,7 +44,7 @@ impl Vm {
|
||||
match instruction {
|
||||
Instruction::Constant => {
|
||||
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}");
|
||||
|
||||
@ -68,7 +66,10 @@ impl Vm {
|
||||
// Variables
|
||||
Instruction::DeclareVariable => {
|
||||
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);
|
||||
|
||||
log::trace!("Declaring {identifier} as value {value}",);
|
||||
@ -77,7 +78,10 @@ impl Vm {
|
||||
}
|
||||
Instruction::GetVariable => {
|
||||
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);
|
||||
|
||||
log::trace!("Getting {identifier} as value {value}",);
|
||||
@ -86,7 +90,11 @@ impl Vm {
|
||||
}
|
||||
Instruction::SetVariable => {
|
||||
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) {
|
||||
return Err(VmError::UndefinedVariable {
|
||||
@ -273,6 +281,9 @@ pub enum VmError {
|
||||
InvalidInstruction(u8, Span),
|
||||
StackOverflow(Span),
|
||||
StackUnderflow(Span),
|
||||
UndeclaredVariable {
|
||||
position: Span,
|
||||
},
|
||||
UndefinedVariable {
|
||||
identifier: Identifier,
|
||||
position: Span,
|
||||
@ -302,6 +313,7 @@ impl AnnotatedError for VmError {
|
||||
Self::InvalidInstruction(_, _) => "Invalid instruction",
|
||||
Self::StackOverflow(_) => "Stack overflow",
|
||||
Self::StackUnderflow(_) => "Stack underflow",
|
||||
Self::UndeclaredVariable { .. } => "Undeclared variable",
|
||||
Self::UndefinedVariable { .. } => "Undefined variable",
|
||||
Self::Chunk(_) => "Chunk error",
|
||||
Self::Value { .. } => "Value error",
|
||||
@ -315,6 +327,7 @@ impl AnnotatedError for VmError {
|
||||
)),
|
||||
Self::StackOverflow(position) => Some(format!("Stack overflow at {position}")),
|
||||
Self::StackUnderflow(position) => Some(format!("Stack underflow at {position}")),
|
||||
Self::UndeclaredVariable { .. } => Some("Variable is not declared".to_string()),
|
||||
Self::UndefinedVariable { identifier, .. } => {
|
||||
Some(format!("{identifier} is not in scope"))
|
||||
}
|
||||
@ -328,6 +341,7 @@ impl AnnotatedError for VmError {
|
||||
Self::InvalidInstruction(_, position) => *position,
|
||||
Self::StackUnderflow(position) => *position,
|
||||
Self::StackOverflow(position) => *position,
|
||||
Self::UndeclaredVariable { position } => *position,
|
||||
Self::UndefinedVariable { position, .. } => *position,
|
||||
Self::Chunk(error) => error.position(),
|
||||
Self::Value { position, .. } => *position,
|
||||
|
@ -15,3 +15,19 @@ fn variable() {
|
||||
|
||||
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))));
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user