Go to great lengths to avoid cloning Values; Extend error reports
This commit is contained in:
parent
0ed2733991
commit
f936c30b4f
@ -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();
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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),
|
||||
|
@ -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;
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user