1
0

Go to great lengths to avoid cloning Values; Extend error reports

This commit is contained in:
Jeff 2024-09-10 03:42:25 -04:00
parent 0ed2733991
commit f936c30b4f
6 changed files with 254 additions and 100 deletions

View File

@ -56,6 +56,16 @@ impl Chunk {
.ok_or(ChunkError::ConstantIndexOutOfBounds(index))
}
pub fn remove_constant(&mut self, index: u8) -> Result<Value, ChunkError> {
let index = index as usize;
if index >= self.constants.len() {
Err(ChunkError::ConstantIndexOutOfBounds(index as u8))
} else {
Ok(self.constants.remove(index))
}
}
pub fn push_constant(&mut self, value: Value) -> Result<u8, ChunkError> {
let starting_length = self.constants.len();

View File

@ -26,9 +26,21 @@ impl<'src> DustError<'src> {
match self {
DustError::Runtime { error, source } => {
let position = error.position();
let label = format!("Runtime {}", error.title());
let description = error.description();
let message = Level::Error.title(&label).snippet(
let message = Level::Error.title(VmError::title()).snippet(
Snippet::source(source).fold(true).annotation(
Level::Error
.span(position.0..position.1)
.label(&description),
),
);
report.push_str(&renderer.render(message).to_string());
}
DustError::Parse { error, source } => {
let position = error.position();
let description = error.description();
let message = Level::Error.title(ParseError::title()).snippet(
Snippet::source(source).fold(true).annotation(
Level::Error
.span(position.0..position.1)

View File

@ -530,6 +530,24 @@ pub enum LexError {
}
impl LexError {
pub fn title() -> &'static str {
"Lex Error"
}
pub fn description(&self) -> String {
match self {
Self::ExpectedCharacter {
expected, actual, ..
} => {
format!("Expected character \"{}\", found \"{}\"", expected, actual)
}
Self::UnexpectedCharacter { actual, .. } => {
format!("Unexpected character \"{}\"", actual)
}
Self::UnexpectedEndOfFile { .. } => "Unexpected end of file".to_string(),
}
}
pub fn position(&self) -> Span {
match self {
Self::ExpectedCharacter { position, .. } => Span(*position, *position),

View File

@ -95,8 +95,11 @@ impl<'src> Parser<'src> {
}
fn emit_constant(&mut self, value: Value) -> Result<(), ParseError> {
let constant_index = self.chunk.push_constant(value)?;
let position = self.previous_position;
let constant_index = self
.chunk
.push_constant(value)
.map_err(|error| ParseError::Chunk { error, position })?;
self.emit_byte(Instruction::Constant as u8, position);
self.emit_byte(constant_index, position);
@ -117,7 +120,11 @@ impl<'src> Parser<'src> {
fn parse_byte(&mut self, _allow_assignment: bool) -> Result<(), ParseError> {
if let Token::Byte(text) = self.previous_token {
let byte = u8::from_str_radix(&text[2..], 16)?;
let byte =
u8::from_str_radix(&text[2..], 16).map_err(|error| ParseError::ParseIntError {
error,
position: self.previous_position,
})?;
let value = Value::byte(byte);
self.emit_constant(value)?;
@ -249,7 +256,13 @@ impl<'src> Parser<'src> {
fn parse_identifier_from(&mut self, token: TokenOwned) -> Result<u8, ParseError> {
if let TokenOwned::Identifier(text) = token {
let identifier = Identifier::new(text);
let identifier_index = self.chunk.get_identifier_index(&identifier)?;
let identifier_index =
self.chunk
.get_identifier_index(&identifier)
.map_err(|error| ParseError::Chunk {
error,
position: self.previous_position,
})?;
Ok(identifier_index)
} else {
@ -300,7 +313,9 @@ impl<'src> Parser<'src> {
let identifier = Identifier::new(text);
self.chunk.push_identifier(identifier)?
self.chunk
.push_identifier(identifier)
.map_err(|error| ParseError::Chunk { error, position })?
} else {
return Err(ParseError::ExpectedToken {
expected: TokenKind::Identifier,
@ -566,14 +581,54 @@ pub enum ParseError {
},
// Wrappers around foreign errors
Chunk(ChunkError),
Chunk {
error: ChunkError,
position: Span,
},
Lex(LexError),
ParseIntError(ParseIntError),
ParseIntError {
error: ParseIntError,
position: Span,
},
}
impl From<ParseIntError> for ParseError {
fn from(error: ParseIntError) -> Self {
Self::ParseIntError(error)
impl ParseError {
pub fn title() -> &'static str {
"Parse Error"
}
pub fn description(&self) -> String {
match self {
Self::ExpectedExpression { found, .. } => {
format!("Expected an expression, found \"{found}\"")
}
Self::ExpectedToken {
expected, found, ..
} => {
format!("Expected \"{expected}\", found \"{found}\"")
}
Self::ExpectedTokenMultiple {
expected, found, ..
} => format!("Expected one of {:?}, found \"{found}\"", expected,),
Self::InvalidAssignmentTarget { found, .. } => {
format!("Invalid assignment target \"{found}\"")
}
Self::Chunk { error, .. } => error.description(),
Self::Lex(error) => error.description(),
Self::ParseIntError { error, .. } => error.to_string(),
}
}
pub fn position(&self) -> Span {
match self {
Self::ExpectedExpression { position, .. } => *position,
Self::ExpectedToken { position, .. } => *position,
Self::ExpectedTokenMultiple { position, .. } => *position,
Self::InvalidAssignmentTarget { position, .. } => *position,
Self::Chunk { position, .. } => *position,
Self::Lex(error) => error.position(),
Self::ParseIntError { position, .. } => *position,
}
}
}
@ -583,12 +638,6 @@ impl From<LexError> for ParseError {
}
}
impl From<ChunkError> for ParseError {
fn from(error: ChunkError) -> Self {
Self::Chunk(error)
}
}
#[cfg(test)]
mod tests {
use crate::identifier_stack::Local;

View File

@ -46,7 +46,7 @@ use crate::{EnumType, FunctionType, Identifier, RangeableType, StructType, Type}
///
/// assert_eq!(value.r#type(), Type::Integer);
/// ```
#[derive(Debug, Clone)]
#[derive(Debug)]
pub enum Value {
Raw(ValueData),
Reference(Arc<ValueData>),
@ -794,6 +794,18 @@ impl From<&str> for Value {
}
}
impl Clone for Value {
fn clone(&self) -> Self {
log::trace!("Cloning value: {:?}", self);
match self {
Value::Raw(data) => Value::Raw(data.clone()),
Value::Reference(data) => Value::Reference(data.clone()),
Value::Mutable(data) => Value::Mutable(data.clone()),
}
}
}
impl Eq for Value {}
impl PartialEq for Value {

View File

@ -1,3 +1,5 @@
use std::rc::Rc;
use crate::{
parse, Chunk, ChunkError, DustError, Identifier, Instruction, Span, Value, ValueError,
};
@ -13,9 +15,9 @@ pub fn run(source: &str) -> Result<Option<Value>, DustError> {
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Vm {
chunk: Chunk,
chunk: Rc<Chunk>,
ip: usize,
stack: Vec<Value>,
stack: Vec<StackedValue>,
}
impl Vm {
@ -23,17 +25,17 @@ impl Vm {
pub fn new(chunk: Chunk) -> Self {
Self {
chunk,
chunk: Rc::new(chunk),
ip: 0,
stack: Vec::with_capacity(Self::STACK_SIZE),
}
}
pub fn run(&mut self) -> Result<Option<Value>, VmError> {
let mut current_postion = Span(0, 0);
let mut current_position = Span(0, 0);
while let Ok((byte, position)) = self.read(current_postion).copied() {
current_postion = position;
while let Ok((byte, position)) = self.read(current_position).copied() {
current_position = position;
let instruction = Instruction::from_byte(byte)
.ok_or_else(|| VmError::InvalidInstruction(byte, position))?;
@ -43,12 +45,11 @@ impl Vm {
match instruction {
Instruction::Constant => {
let (index, _) = self.read(position).copied()?;
let value = self.read_constant(index, position)?.clone();
self.push(value, position)?;
self.push_constant_value(index, position)?;
}
Instruction::Return => {
let value = self.pop(position)?;
let value = self.pop(position)?.resolve(&self.chunk, position)?.clone();
return Ok(Some(value));
}
@ -59,18 +60,14 @@ impl Vm {
// Variables
Instruction::DefineVariable => {
let (index, _) = *self.read(position)?;
let value = self
.read_constant(index, position)?
.clone()
.into_reference();
self.stack.insert(index as usize, value);
self.stack
.insert(index as usize, StackedValue::Constant(index));
}
Instruction::GetVariable => {
let (index, _) = *self.read(position)?;
let value = self.stack[index as usize].clone();
self.push(value, position)?;
self.push_constant_value(index, position)?;
}
Instruction::SetVariable => {
let (index, _) = *self.read(position)?;
@ -84,157 +81,205 @@ impl Vm {
return Err(VmError::UndefinedVariable(identifier, position));
}
let value = self.pop(position)?;
let stacked = self.pop(position)?;
self.stack[index as usize] = value;
self.stack[index as usize] = stacked;
}
// Unary
Instruction::Negate => {
let negated = self
.pop(position)?
.resolve(&self.chunk, position)?
.negate()
.map_err(|error| VmError::Value { error, position })?;
self.push(negated, position)?;
self.push_runtime_value(negated, position)?;
}
Instruction::Not => {
let not = self
.pop(position)?
.resolve(&self.chunk, position)?
.not()
.map_err(|error| VmError::Value { error, position })?;
self.push(not, position)?;
self.push_runtime_value(not, position)?;
}
// Binary
Instruction::Add => {
let right = self.pop(position)?;
let left = self.pop(position)?;
let chunk = self.chunk.clone();
let right_stacked = self.pop(position)?;
let right = right_stacked.resolve(chunk.as_ref(), position)?;
let left_stacked = self.pop(position)?;
let left = left_stacked.resolve(&self.chunk, position)?;
let sum = left
.add(&right)
.add(right)
.map_err(|error| VmError::Value { error, position })?;
self.push(sum, position)?;
self.push_runtime_value(sum, position)?;
}
Instruction::Subtract => {
let right = self.pop(position)?;
let left = self.pop(position)?;
let chunk = self.chunk.clone();
let right_stacked = self.pop(position)?;
let right = right_stacked.resolve(chunk.as_ref(), position)?;
let left_stacked = self.pop(position)?;
let left = left_stacked.resolve(&self.chunk, position)?;
let difference = left
.subtract(&right)
.subtract(right)
.map_err(|error| VmError::Value { error, position })?;
self.push(difference, position)?;
self.push_runtime_value(difference, position)?;
}
Instruction::Multiply => {
let right = self.pop(position)?;
let left = self.pop(position)?;
let chunk = self.chunk.clone();
let right_stacked = self.pop(position)?;
let right = right_stacked.resolve(chunk.as_ref(), position)?;
let left_stacked = self.pop(position)?;
let left = left_stacked.resolve(&self.chunk, position)?;
let product = left
.multiply(&right)
.multiply(right)
.map_err(|error| VmError::Value { error, position })?;
self.push(product, position)?;
self.push_runtime_value(product, position)?;
}
Instruction::Divide => {
let right = self.pop(position)?;
let left = self.pop(position)?;
let chunk = self.chunk.clone();
let right_stacked = self.pop(position)?;
let right = right_stacked.resolve(chunk.as_ref(), position)?;
let left_stacked = self.pop(position)?;
let left = left_stacked.resolve(&self.chunk, position)?;
let quotient = left
.divide(&right)
.divide(right)
.map_err(|error| VmError::Value { error, position })?;
self.push(quotient, position)?;
self.push_runtime_value(quotient, position)?;
}
Instruction::Greater => {
let right = self.pop(position)?;
let left = self.pop(position)?;
let chunk = self.chunk.clone();
let right_stacked = self.pop(position)?;
let right = right_stacked.resolve(chunk.as_ref(), position)?;
let left_stacked = self.pop(position)?;
let left = left_stacked.resolve(&self.chunk, position)?;
let greater = left
.greater_than(&right)
.greater_than(right)
.map_err(|error| VmError::Value { error, position })?;
self.push(greater, position)?;
self.push_runtime_value(greater, position)?;
}
Instruction::Less => {
let right = self.pop(position)?;
let left = self.pop(position)?;
let chunk = self.chunk.clone();
let right_stacked = self.pop(position)?;
let right = right_stacked.resolve(chunk.as_ref(), position)?;
let left_stacked = self.pop(position)?;
let left = left_stacked.resolve(&self.chunk, position)?;
let less = left
.less_than(&right)
.less_than(right)
.map_err(|error| VmError::Value { error, position })?;
self.push(less, position)?;
self.push_runtime_value(less, position)?;
}
Instruction::GreaterEqual => {
let right = self.pop(position)?;
let left = self.pop(position)?;
let chunk = self.chunk.clone();
let right_stacked = self.pop(position)?;
let right = right_stacked.resolve(chunk.as_ref(), position)?;
let left_stacked = self.pop(position)?;
let left = left_stacked.resolve(&self.chunk, position)?;
let greater_equal = left
.greater_than_or_equal(&right)
.greater_than_or_equal(right)
.map_err(|error| VmError::Value { error, position })?;
self.push(greater_equal, position)?;
self.push_runtime_value(greater_equal, position)?;
}
Instruction::LessEqual => {
let right = self.pop(position)?;
let left = self.pop(position)?;
let chunk = self.chunk.clone();
let right_stacked = self.pop(position)?;
let right = right_stacked.resolve(chunk.as_ref(), position)?;
let left_stacked = self.pop(position)?;
let left = left_stacked.resolve(&self.chunk, position)?;
let less_equal = left
.less_than_or_equal(&right)
.less_than_or_equal(right)
.map_err(|error| VmError::Value { error, position })?;
self.push(less_equal, position)?;
self.push_runtime_value(less_equal, position)?;
}
Instruction::Equal => {
let right = self.pop(position)?;
let left = self.pop(position)?;
let chunk = self.chunk.clone();
let right_stacked = self.pop(position)?;
let right = right_stacked.resolve(chunk.as_ref(), position)?;
let left_stacked = self.pop(position)?;
let left = left_stacked.resolve(&self.chunk, position)?;
let equal = left
.equal(&right)
.equal(right)
.map_err(|error| VmError::Value { error, position })?;
self.push(equal, position)?;
self.push_runtime_value(equal, position)?;
}
Instruction::NotEqual => {
let right = self.pop(position)?;
let left = self.pop(position)?;
let chunk = self.chunk.clone();
let right_stacked = self.pop(position)?;
let right = right_stacked.resolve(chunk.as_ref(), position)?;
let left_stacked = self.pop(position)?;
let left = left_stacked.resolve(&self.chunk, position)?;
let not_equal = left
.not_equal(&right)
.not_equal(right)
.map_err(|error| VmError::Value { error, position })?;
self.push(not_equal, position)?;
self.push_runtime_value(not_equal, position)?;
}
Instruction::And => {
let right = self.pop(position)?;
let left = self.pop(position)?;
let chunk = self.chunk.clone();
let right_stacked = self.pop(position)?;
let right = right_stacked.resolve(chunk.as_ref(), position)?;
let left_stacked = self.pop(position)?;
let left = left_stacked.resolve(&self.chunk, position)?;
let and = left
.and(&right)
.and(right)
.map_err(|error| VmError::Value { error, position })?;
self.push(and, position)?;
self.push_runtime_value(and, position)?;
}
Instruction::Or => {
let right = self.pop(position)?;
let left = self.pop(position)?;
let chunk = self.chunk.clone();
let right_stacked = self.pop(position)?;
let right = right_stacked.resolve(chunk.as_ref(), position)?;
let left_stacked = self.pop(position)?;
let left = left_stacked.resolve(&self.chunk, position)?;
let or = left
.or(&right)
.or(right)
.map_err(|error| VmError::Value { error, position })?;
self.push(or, position)?;
self.push_runtime_value(or, position)?;
}
}
}
Ok(self.stack.pop())
Ok(None)
}
fn push(&mut self, value: Value, position: Span) -> Result<(), VmError> {
fn push_runtime_value(&mut self, value: Value, position: Span) -> Result<(), VmError> {
if self.stack.len() == Self::STACK_SIZE {
Err(VmError::StackOverflow(position))
} else {
self.stack.push(value);
self.stack.push(StackedValue::Runtime(value));
Ok(())
}
}
fn pop(&mut self, position: Span) -> Result<Value, VmError> {
if let Some(value) = self.stack.pop() {
Ok(value)
fn push_constant_value(&mut self, index: u8, position: Span) -> Result<(), VmError> {
if self.stack.len() == Self::STACK_SIZE {
Err(VmError::StackOverflow(position))
} else {
self.stack.push(StackedValue::Constant(index));
Ok(())
}
}
fn pop(&mut self, position: Span) -> Result<StackedValue, VmError> {
if let Some(stacked) = self.stack.pop() {
Ok(stacked)
} else {
Err(VmError::StackUnderflow(position))
}
@ -250,14 +295,22 @@ impl Vm {
Ok(current)
}
}
fn read_constant(&self, index: u8, position: Span) -> Result<&Value, VmError> {
let value = self
.chunk
.get_constant(index)
.map_err(|error| VmError::Chunk { error, position })?;
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum StackedValue {
Runtime(Value),
Constant(u8),
}
Ok(value)
impl StackedValue {
fn resolve<'a>(&'a self, chunk: &'a Chunk, position: Span) -> Result<&'a Value, VmError> {
match self {
Self::Runtime(value) => Ok(value),
Self::Constant(index) => chunk
.get_constant(*index)
.map_err(|error| VmError::Chunk { error, position }),
}
}
}
@ -282,8 +335,8 @@ impl VmError {
Self::Value { error, position }
}
pub fn title(&self) -> &'static str {
"VM Error"
pub fn title() -> &'static str {
"Runtime Error"
}
pub fn description(&self) -> String {