Add implicit returns and fix variable declaration and resolution

This commit is contained in:
Jeff 2024-09-10 09:26:05 -04:00
parent f936c30b4f
commit 4ba3a47ae5
10 changed files with 387 additions and 268 deletions

View File

@ -2,7 +2,9 @@ use std::fmt::{self, Debug, Display, Formatter};
use serde::{Deserialize, Serialize};
use crate::{identifier_stack::Local, Identifier, IdentifierStack, Instruction, Span, Value};
use crate::{
identifier_stack::Local, Identifier, IdentifierStack, Instruction, Span, Value, ValueLocation,
};
#[derive(Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct Chunk {
@ -82,6 +84,12 @@ impl Chunk {
self.identifiers.contains(identifier)
}
pub fn get_local(&self, index: u8) -> Result<&Local, ChunkError> {
self.identifiers
.get(index as usize)
.ok_or(ChunkError::IdentifierIndexOutOfBounds(index))
}
pub fn get_identifier(&self, index: u8) -> Result<&Identifier, ChunkError> {
self.identifiers
.get(index as usize)
@ -95,13 +103,27 @@ impl Chunk {
.ok_or(ChunkError::IdentifierNotFound(identifier.clone()))
}
pub fn push_identifier(&mut self, identifier: Identifier) -> Result<u8, ChunkError> {
pub fn push_constant_identifier(&mut self, identifier: Identifier) -> Result<u8, ChunkError> {
let starting_length = self.identifiers.local_count();
if starting_length + 1 > (u8::MAX as usize) {
Err(ChunkError::IdentifierOverflow)
} else {
self.identifiers.declare(identifier);
self.identifiers
.declare(identifier, ValueLocation::ConstantStack);
Ok(starting_length as u8)
}
}
pub fn push_runtime_identifier(&mut self, identifier: Identifier) -> Result<u8, ChunkError> {
let starting_length = self.identifiers.local_count();
if starting_length + 1 > (u8::MAX as usize) {
Err(ChunkError::IdentifierOverflow)
} else {
self.identifiers
.declare(identifier, ValueLocation::RuntimeStack);
Ok(starting_length as u8)
}
@ -116,17 +138,19 @@ impl Chunk {
pub fn disassemble(&self, name: &str) -> String {
let mut output = String::new();
output.push_str("== ");
output.push_str("# ");
output.push_str(name);
output.push_str(" ==\n--Code--\n");
output.push_str("OFFSET INSTRUCTION POSITION\n");
output.push_str("\n\n## Code\n");
output.push_str("------ ------------ ------------\n");
output.push_str("OFFSET POSITION INSTRUCTION\n");
output.push_str("------ ------------ ------------\n");
let mut previous = None;
for (offset, (byte, position)) in self.code.iter().enumerate() {
if let Some(
Instruction::Constant
| Instruction::DefineVariable
| Instruction::DefineVariableConstant
| Instruction::GetVariable
| Instruction::SetVariable,
) = previous
@ -137,16 +161,21 @@ impl Chunk {
}
let instruction = Instruction::from_byte(*byte).unwrap();
let display = format!("{offset:04} {}", instruction.disassemble(self, offset));
let display_with_postion = format!("{display:27} {position}\n");
let display = format!(
"{offset:4} {:12} {}\n",
position.to_string(),
instruction.disassemble(self, offset)
);
previous = Some(instruction);
output.push_str(&display_with_postion);
output.push_str(&display);
}
output.push_str("--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 {
@ -154,16 +183,29 @@ impl Chunk {
Value::Reference(_) => "REF ",
Value::Mutable(_) => "MUT ",
};
let display = format!("{index:04} {value_kind_display} {value}\n");
let display = format!("{index:3} {value_kind_display} {value}\n");
output.push_str(&display);
}
output.push_str("--Identifiers--\n");
output.push_str("INDEX IDENTIFIER DEPTH\n");
output.push_str("\n## Identifiers\n");
output.push_str("----- ---------- -------- -----\n");
output.push_str("INDEX IDENTIFIER LOCATION DEPTH\n");
output.push_str("----- ---------- -------- -----\n");
for (index, Local { identifier, depth }) in self.identifiers.iter().enumerate() {
let display = format!("{index:04} {:10} {depth}\n", identifier.as_str());
for (
index,
Local {
identifier,
depth,
value_location,
},
) in self.identifiers.iter().enumerate()
{
let display = format!(
"{index:3} {:10} {value_location} {depth}\n",
identifier.as_str()
);
output.push_str(&display);
}
@ -199,26 +241,22 @@ pub enum ChunkError {
IdentifierNotFound(Identifier),
}
impl ChunkError {
pub fn title(&self) -> &'static str {
"Chunk Error"
}
pub fn description(&self) -> String {
impl Display for ChunkError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Self::CodeIndexOfBounds(offset) => format!("{offset} is out of bounds",),
Self::ConstantOverflow => "More than 256 constants declared in one chunk".to_string(),
Self::ConstantIndexOutOfBounds(index) => {
format!("{index} is out of bounds")
ChunkError::CodeIndexOfBounds(offset) => {
write!(f, "Code index out of bounds: {}", offset)
}
Self::IdentifierIndexOutOfBounds(index) => {
format!("{index} is out of bounds")
ChunkError::ConstantOverflow => write!(f, "Constant overflow"),
ChunkError::ConstantIndexOutOfBounds(index) => {
write!(f, "Constant index out of bounds: {}", index)
}
Self::IdentifierOverflow => {
"More than 256 identifiers declared in one chunk".to_string()
ChunkError::IdentifierIndexOutOfBounds(index) => {
write!(f, "Identifier index out of bounds: {}", index)
}
Self::IdentifierNotFound(identifier) => {
format!("{} does not exist in this scope", identifier)
ChunkError::IdentifierOverflow => write!(f, "Identifier overflow"),
ChunkError::IdentifierNotFound(identifier) => {
write!(f, "Identifier not found: {}", identifier)
}
}
}

View File

@ -1,6 +1,6 @@
use annotate_snippets::{Level, Renderer, Snippet};
use crate::{vm::VmError, LexError, ParseError};
use crate::{vm::VmError, LexError, ParseError, Span};
#[derive(Debug, PartialEq)]
pub enum DustError<'src> {
@ -26,26 +26,28 @@ impl<'src> DustError<'src> {
match self {
DustError::Runtime { error, source } => {
let position = error.position();
let description = error.description();
let message = Level::Error.title(VmError::title()).snippet(
Snippet::source(source).fold(true).annotation(
Level::Error
.span(position.0..position.1)
.label(&description),
),
let label = format!("Runtime error: {}", error.description());
let details = error
.details()
.unwrap_or_else(|| "While running this code".to_string());
let message = Level::Error.title(&label).snippet(
Snippet::source(source)
.fold(true)
.annotation(Level::Error.span(position.0..position.1).label(&details)),
);
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)
.label(&description),
),
let label = format!("Parse error: {}", error.description());
let details = error
.details()
.unwrap_or_else(|| "While parsing this code".to_string());
let message = Level::Error.title(&label).snippet(
Snippet::source(source)
.fold(true)
.annotation(Level::Error.span(position.0..position.1).label(&details)),
);
report.push_str(&renderer.render(message).to_string());
@ -56,3 +58,10 @@ impl<'src> DustError<'src> {
report
}
}
pub trait AnnotatedError {
fn title() -> &'static str;
fn description(&self) -> &'static str;
fn details(&self) -> Option<String>;
fn position(&self) -> Span;
}

View File

@ -1,3 +1,5 @@
use std::fmt::Display;
use serde::{Deserialize, Serialize};
use crate::Identifier;
@ -68,10 +70,11 @@ impl IdentifierStack {
self.scope_depth -= 1;
}
pub fn declare(&mut self, identifier: Identifier) {
pub fn declare(&mut self, identifier: Identifier, value_location: ValueLocation) {
self.locals.push(Local {
identifier,
depth: self.scope_depth,
value_location,
});
}
@ -98,4 +101,20 @@ impl PartialEq for IdentifierStack {
pub struct Local {
pub identifier: Identifier,
pub depth: usize,
pub value_location: ValueLocation,
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub enum ValueLocation {
ConstantStack,
RuntimeStack,
}
impl Display for ValueLocation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ValueLocation::ConstantStack => write!(f, "constant"),
ValueLocation::RuntimeStack => write!(f, "runtime "),
}
}
}

View File

@ -11,27 +11,28 @@ pub enum Instruction {
Pop = 2,
// Variables
DefineVariable = 3,
GetVariable = 4,
SetVariable = 5,
DefineVariableRuntime = 3,
DefineVariableConstant = 4,
GetVariable = 5,
SetVariable = 6,
// Unary
Negate = 6,
Not = 7,
Negate = 7,
Not = 8,
// Binary
Add = 8,
Subtract = 9,
Multiply = 10,
Divide = 11,
Greater = 12,
Less = 13,
GreaterEqual = 14,
LessEqual = 15,
Equal = 16,
NotEqual = 17,
And = 18,
Or = 19,
Add = 9,
Subtract = 10,
Multiply = 11,
Divide = 12,
Greater = 13,
Less = 14,
GreaterEqual = 15,
LessEqual = 16,
Equal = 17,
NotEqual = 18,
And = 19,
Or = 20,
}
impl Instruction {
@ -40,23 +41,24 @@ impl Instruction {
0 => Some(Instruction::Constant),
1 => Some(Instruction::Return),
2 => Some(Instruction::Pop),
3 => Some(Instruction::DefineVariable),
4 => Some(Instruction::GetVariable),
5 => Some(Instruction::SetVariable),
6 => Some(Instruction::Negate),
7 => Some(Instruction::Not),
8 => Some(Instruction::Add),
9 => Some(Instruction::Subtract),
10 => Some(Instruction::Multiply),
11 => Some(Instruction::Divide),
12 => Some(Instruction::Greater),
13 => Some(Instruction::Less),
14 => Some(Instruction::GreaterEqual),
15 => Some(Instruction::LessEqual),
16 => Some(Instruction::Equal),
17 => Some(Instruction::NotEqual),
18 => Some(Instruction::And),
19 => Some(Instruction::Or),
3 => Some(Instruction::DefineVariableRuntime),
4 => Some(Instruction::DefineVariableConstant),
5 => Some(Instruction::GetVariable),
6 => Some(Instruction::SetVariable),
7 => Some(Instruction::Negate),
8 => Some(Instruction::Not),
9 => Some(Instruction::Add),
10 => Some(Instruction::Subtract),
11 => Some(Instruction::Multiply),
12 => Some(Instruction::Divide),
13 => Some(Instruction::Greater),
14 => Some(Instruction::Less),
15 => Some(Instruction::GreaterEqual),
16 => Some(Instruction::LessEqual),
17 => Some(Instruction::Equal),
18 => Some(Instruction::NotEqual),
19 => Some(Instruction::And),
20 => Some(Instruction::Or),
_ => None,
}
}
@ -64,55 +66,46 @@ impl Instruction {
pub fn disassemble(&self, chunk: &Chunk, offset: usize) -> String {
match self {
Instruction::Constant => {
let (index_display, value_display) =
if let Ok((index, _)) = chunk.get_code(offset + 1) {
let index_string = index.to_string();
let value_string = chunk
.get_constant(*index)
.map(|value| value.to_string())
.unwrap_or_else(|error| format!("{:?}", error));
let (argument, _) = chunk.get_code(offset + 1).unwrap();
let value_display = chunk
.get_constant(*argument)
.map(|value| value.to_string())
.unwrap_or_else(|error| error.to_string());
(index_string, value_string)
} else {
let index = "ERROR".to_string();
let value = "ERROR".to_string();
(index, value)
};
format!("CONSTANT {index_display} {value_display}")
format!("CONSTANT {argument} {value_display}")
}
Instruction::Return => format!("{offset:04} RETURN"),
Instruction::Pop => format!("{offset:04} POP"),
Instruction::Return => "RETURN".to_string(),
Instruction::Pop => "POP".to_string(),
// Variables
Instruction::DefineVariable => {
let (index, _) = chunk.get_code(offset + 1).unwrap();
let identifier_display = match chunk.get_identifier(*index) {
Instruction::DefineVariableRuntime => "DEFINE_VARIABLE_RUNTIME".to_string(),
Instruction::DefineVariableConstant => {
let (argument, _) = chunk.get_code(offset + 1).unwrap();
let identifier_display = match chunk.get_identifier(*argument) {
Ok(identifier) => identifier.to_string(),
Err(error) => format!("{:?}", error),
Err(error) => error.to_string(),
};
format!("DEFINE_VARIABLE {identifier_display} {index}")
format!("DEFINE_VARIABLE_CONSTANT {argument} {identifier_display}")
}
Instruction::GetVariable => {
let (index, _) = chunk.get_code(offset + 1).unwrap();
let identifier_display = match chunk.get_identifier(*index) {
let (argument, _) = chunk.get_code(offset + 1).unwrap();
let identifier_display = match chunk.get_identifier(*argument) {
Ok(identifier) => identifier.to_string(),
Err(error) => format!("{:?}", error),
Err(error) => error.to_string(),
};
format!("GET_VARIABLE {identifier_display} {index}")
format!("GET_VARIABLE {argument} {identifier_display}")
}
Instruction::SetVariable => {
let (index, _) = chunk.get_code(offset + 1).unwrap();
let identifier_display = match chunk.get_identifier(*index) {
let (argument, _) = chunk.get_code(offset + 1).unwrap();
let identifier_display = match chunk.get_identifier(*argument) {
Ok(identifier) => identifier.to_string(),
Err(error) => format!("{:?}", error),
Err(error) => error.to_string(),
};
format!("SET_VARIABLE {identifier_display} {index}")
format!("SET_VARIABLE {identifier_display}")
}
// Unary

View File

@ -5,7 +5,7 @@
//! - [`Lexer`], which lexes the input a token at a time
use std::fmt::{self, Display, Formatter};
use crate::{Span, Token};
use crate::{dust_error::AnnotatedError, Span, Token};
/// Lexes the input and return a vector of tokens and their positions.
///
@ -529,26 +529,35 @@ pub enum LexError {
},
}
impl LexError {
pub fn title() -> &'static str {
impl AnnotatedError for LexError {
fn title() -> &'static str {
"Lex Error"
}
pub fn description(&self) -> String {
fn description(&self) -> &'static str {
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(),
Self::ExpectedCharacter { .. } => "Expected character",
Self::UnexpectedCharacter { .. } => "Unexpected character",
Self::UnexpectedEndOfFile { .. } => "Unexpected end of file",
}
}
pub fn position(&self) -> Span {
fn details(&self) -> Option<String> {
match self {
Self::ExpectedCharacter {
expected, actual, ..
} => Some(format!(
"Expected character \"{}\", found \"{}\"",
expected, actual
)),
Self::UnexpectedCharacter { actual, .. } => {
Some(format!("Unexpected character \"{}\"", actual))
}
Self::UnexpectedEndOfFile { .. } => Some("Unexpected end of file".to_string()),
}
}
fn position(&self) -> Span {
match self {
Self::ExpectedCharacter { position, .. } => Span(*position, *position),
Self::UnexpectedCharacter { position, .. } => Span(*position, *position),

View File

@ -30,9 +30,9 @@ pub mod vm;
pub use chunk::{Chunk, ChunkError};
pub use constructor::{ConstructError, Constructor};
pub use dust_error::DustError;
pub use dust_error::{AnnotatedError, DustError};
pub use identifier::Identifier;
pub use identifier_stack::IdentifierStack;
pub use identifier_stack::{IdentifierStack, Local, ValueLocation};
pub use instruction::Instruction;
pub use lexer::{lex, LexError, Lexer};
pub use parser::{parse, ParseError, Parser};

View File

@ -5,8 +5,8 @@ use std::{
};
use crate::{
Chunk, ChunkError, DustError, Identifier, Instruction, LexError, Lexer, Span, Token, TokenKind,
TokenOwned, Value,
dust_error::AnnotatedError, Chunk, ChunkError, DustError, Identifier, Instruction, LexError,
Lexer, Span, Token, TokenKind, TokenOwned, Value,
};
pub fn parse(source: &str) -> Result<Chunk, DustError> {
@ -294,10 +294,14 @@ impl<'src> Parser<'src> {
};
let has_semicolon = self.allow(TokenKind::Semicolon)?;
if is_expression_statement && has_semicolon {
if is_expression_statement {
let end = self.previous_position.1;
self.emit_byte(Instruction::Pop as u8, Span(start, end));
if has_semicolon {
self.emit_byte(Instruction::Pop as u8, Span(start, end));
} else {
self.emit_byte(Instruction::Return as u8, Span(start, end));
}
}
Ok(())
@ -307,15 +311,10 @@ impl<'src> Parser<'src> {
self.expect(TokenKind::Let)?;
let position = self.current_position;
let identifier_index = if let Token::Identifier(text) = self.current_token {
let identifier = if let Token::Identifier(text) = self.current_token {
self.advance()?;
let identifier = Identifier::new(text);
self.chunk
.push_identifier(identifier)
.map_err(|error| ParseError::Chunk { error, position })?
Identifier::new(text)
} else {
return Err(ParseError::ExpectedToken {
expected: TokenKind::Identifier,
@ -324,10 +323,35 @@ impl<'src> Parser<'src> {
});
};
self.emit_byte(Instruction::DefineVariable as u8, position);
self.emit_byte(identifier_index, position);
self.expect(TokenKind::Equal)?;
self.parse_expression()?;
let is_constant = matches!(
self.current_token,
Token::Boolean(_)
| Token::Byte(_)
| Token::Character(_)
| Token::Float(_)
| Token::Integer(_)
| Token::String(_)
);
let identifier_index = if is_constant {
self.chunk.push_constant_identifier(identifier)
} else {
self.chunk.push_runtime_identifier(identifier)
}
.map_err(|error| ParseError::Chunk { error, position })?;
if is_constant {
self.emit_byte(Instruction::DefineVariableConstant as u8, position);
self.emit_byte(identifier_index, position);
self.parse_expression()?;
} else {
self.parse_expression()?;
self.emit_byte(Instruction::DefineVariableRuntime as u8, position);
}
Ok(())
}
@ -592,34 +616,44 @@ pub enum ParseError {
},
}
impl ParseError {
pub fn title() -> &'static str {
impl AnnotatedError for ParseError {
fn title() -> &'static str {
"Parse Error"
}
pub fn description(&self) -> String {
fn description(&self) -> &'static str {
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(),
Self::ExpectedExpression { .. } => "Expected an expression",
Self::ExpectedToken { .. } => "Expected a specific token",
Self::ExpectedTokenMultiple { .. } => "Expected one of multiple tokens",
Self::InvalidAssignmentTarget { .. } => "Invalid assignment target",
Self::Chunk { .. } => "Chunk error",
Self::Lex(_) => "Lex error",
Self::ParseIntError { .. } => "Failed to parse integer",
}
}
pub fn position(&self) -> Span {
fn details(&self) -> Option<String> {
match self {
Self::ExpectedExpression { found, .. } => {
Some(format!("Expected an expression, found \"{found}\""))
}
Self::ExpectedToken {
expected, found, ..
} => Some(format!("Expected \"{expected}\", found \"{found}\"")),
Self::ExpectedTokenMultiple {
expected, found, ..
} => Some(format!("Expected one of {expected:?}, found \"{found}\"")),
Self::InvalidAssignmentTarget { found, .. } => {
Some(format!("Invalid assignment target \"{found}\""))
}
Self::Chunk { error, .. } => Some(error.to_string()),
Self::Lex(error) => Some(error.to_string()),
Self::ParseIntError { error, .. } => Some(error.to_string()),
}
}
fn position(&self) -> Span {
match self {
Self::ExpectedExpression { position, .. } => *position,
Self::ExpectedToken { position, .. } => *position,
@ -640,7 +674,7 @@ impl From<LexError> for ParseError {
#[cfg(test)]
mod tests {
use crate::identifier_stack::Local;
use crate::{identifier_stack::Local, ValueLocation};
use super::*;
@ -653,11 +687,11 @@ mod tests {
test_chunk,
Ok(Chunk::with_data(
vec![
(Instruction::DefineVariable as u8, Span(4, 5)),
(Instruction::DefineVariableConstant as u8, Span(4, 5)),
(0, Span(4, 5)),
(Instruction::Constant as u8, Span(8, 10)),
(0, Span(8, 10)),
(Instruction::DefineVariable as u8, Span(16, 17)),
(Instruction::DefineVariableConstant as u8, Span(16, 17)),
(1, Span(16, 17)),
(Instruction::Constant as u8, Span(20, 22)),
(1, Span(20, 22)),
@ -665,17 +699,20 @@ mod tests {
(0, Span(24, 25)),
(Instruction::GetVariable as u8, Span(28, 29)),
(1, Span(28, 29)),
(Instruction::Add as u8, Span(26, 27))
(Instruction::Add as u8, Span(26, 27)),
(Instruction::Return as u8, Span(24, 29)),
],
vec![Value::integer(42), Value::integer(42)],
vec![
Local {
identifier: Identifier::new("x"),
depth: 0
depth: 0,
value_location: ValueLocation::ConstantStack,
},
Local {
identifier: Identifier::new("y"),
depth: 0
depth: 0,
value_location: ValueLocation::ConstantStack,
},
],
))
@ -691,7 +728,7 @@ mod tests {
test_chunk,
Ok(Chunk::with_data(
vec![
(Instruction::DefineVariable as u8, Span(4, 5)),
(Instruction::DefineVariableConstant as u8, Span(4, 5)),
(0, Span(4, 5)),
(Instruction::Constant as u8, Span(8, 10)),
(0, Span(8, 10)),
@ -699,7 +736,8 @@ mod tests {
vec![Value::integer(42)],
vec![Local {
identifier: Identifier::new("x"),
depth: 0
depth: 0,
value_location: ValueLocation::ConstantStack,
}],
))
);
@ -713,7 +751,11 @@ mod tests {
assert_eq!(
test_chunk,
Ok(Chunk::with_data(
vec![(Instruction::Constant as u8, Span(0, 15)), (0, Span(0, 15))],
vec![
(Instruction::Constant as u8, Span(0, 15)),
(0, Span(0, 15)),
(Instruction::Return as u8, Span(0, 15)),
],
vec![Value::string("Hello, World!")],
vec![],
))
@ -728,7 +770,11 @@ mod tests {
assert_eq!(
test_chunk,
Ok(Chunk::with_data(
vec![(Instruction::Constant as u8, Span(0, 2)), (0, Span(0, 2))],
vec![
(Instruction::Constant as u8, Span(0, 2)),
(0, Span(0, 2)),
(Instruction::Return as u8, Span(0, 2)),
],
vec![Value::integer(42)],
vec![],
))
@ -743,7 +789,11 @@ mod tests {
assert_eq!(
test_chunk,
Ok(Chunk::with_data(
vec![(Instruction::Constant as u8, Span(0, 4)), (0, Span(0, 4))],
vec![
(Instruction::Constant as u8, Span(0, 4)),
(0, Span(0, 4)),
(Instruction::Return as u8, Span(0, 4)),
],
vec![Value::boolean(true)],
vec![],
))
@ -767,6 +817,7 @@ mod tests {
(Instruction::Constant as u8, Span(12, 13)),
(2, Span(12, 13)),
(Instruction::Multiply as u8, Span(10, 11)),
(Instruction::Return as u8, Span(0, 13)),
],
vec![Value::integer(42), Value::integer(42), Value::integer(2)],
vec![],
@ -786,6 +837,7 @@ mod tests {
(Instruction::Constant as u8, Span(2, 4)),
(0, Span(2, 4)),
(Instruction::Negate as u8, Span(0, 1)),
(Instruction::Return as u8, Span(0, 5)),
],
vec![Value::integer(42)],
vec![],
@ -807,6 +859,7 @@ mod tests {
(Instruction::Constant as u8, Span(5, 7)),
(1, Span(5, 7)),
(Instruction::Add as u8, Span(3, 4)),
(Instruction::Return as u8, Span(0, 7)),
],
vec![Value::integer(42), Value::integer(42)],
vec![],
@ -828,6 +881,7 @@ mod tests {
(Instruction::Constant as u8, Span(5, 7)),
(1, Span(5, 7)),
(Instruction::Subtract as u8, Span(3, 4)),
(Instruction::Return as u8, Span(0, 7)),
],
vec![Value::integer(42), Value::integer(42)],
vec![],
@ -849,6 +903,7 @@ mod tests {
(Instruction::Constant as u8, Span(5, 7)),
(1, Span(5, 7)),
(Instruction::Multiply as u8, Span(3, 4)),
(Instruction::Return as u8, Span(0, 7)),
],
vec![Value::integer(42), Value::integer(42)],
vec![],
@ -870,6 +925,7 @@ mod tests {
(Instruction::Constant as u8, Span(5, 7)),
(1, Span(5, 7)),
(Instruction::Divide as u8, Span(3, 4)),
(Instruction::Return as u8, Span(0, 7)),
],
vec![Value::integer(42), Value::integer(42)],
vec![],

View File

@ -1783,66 +1783,6 @@ pub enum ValueError {
IndexOutOfBounds { value: Value, index: i64 },
}
impl ValueError {
pub fn title(&self) -> &'static str {
"Value Error"
}
pub fn description(&self) -> String {
match self {
ValueError::CannotAdd(left, right) => format!("Cannot add {} and {}", left, right),
ValueError::CannotAnd(left, right) => {
format!(
"Cannot use logical \"and\" operation on {} and {}",
left, right
)
}
ValueError::CannotDivide(left, right) => {
format!("Cannot divide {} by {}", left, right)
}
ValueError::CannotGreaterThan(left, right) => {
format!("Cannot compare {} and {}", left, right)
}
ValueError::CannotGreaterThanOrEqual(left, right) => {
format!("Cannot compare {} and {}", left, right)
}
ValueError::CannotIndex { value, index } => {
format!("Cannot index {} with {}", value, index)
}
ValueError::CannotLessThan(left, right) => {
format!("Cannot compare {} and {}", left, right)
}
ValueError::CannotLessThanOrEqual(left, right) => {
format!("Cannot compare {} and {}", left, right)
}
ValueError::CannotMakeMutable => "Cannot make this value mutable".to_string(),
ValueError::CannotModulo(left, right) => {
format!("Cannot modulo {} by {}", left, right)
}
ValueError::CannotMultiply(left, right) => {
format!("Cannot multiply {} and {}", left, right)
}
ValueError::CannotMutate(value) => format!("Cannot mutate {}", value),
ValueError::CannotNegate(value) => format!("Cannot negate {}", value),
ValueError::CannotNot(value) => {
format!("Cannot use logical not operation on {}", value)
}
ValueError::CannotSubtract(left, right) => {
format!("Cannot subtract {} and {}", left, right)
}
ValueError::CannotOr(left, right) => {
format!(
"Cannot use logical \"or\" operation on {} and {}",
left, right
)
}
ValueError::IndexOutOfBounds { value, index } => {
format!("Index out of bounds: {} with index {}", value, index)
}
}
}
}
impl Display for ValueError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {

View File

@ -1,7 +1,8 @@
use std::rc::Rc;
use crate::{
parse, Chunk, ChunkError, DustError, Identifier, Instruction, Span, Value, ValueError,
dust_error::AnnotatedError, parse, Chunk, ChunkError, DustError, Identifier, Instruction, Span,
Value, ValueError, ValueLocation,
};
pub fn run(source: &str) -> Result<Option<Value>, DustError> {
@ -13,7 +14,7 @@ pub fn run(source: &str) -> Result<Option<Value>, DustError> {
.map_err(|error| DustError::Runtime { error, source })
}
#[derive(Debug, Clone, Eq, PartialEq)]
#[derive(Debug, Eq, PartialEq)]
pub struct Vm {
chunk: Rc<Chunk>,
ip: usize,
@ -44,12 +45,19 @@ impl Vm {
match instruction {
Instruction::Constant => {
let (index, _) = self.read(position).copied()?;
let (argument, _) = self.read(position).copied()?;
self.push_constant_value(index, position)?;
self.push_constant_value(argument, position)?;
}
Instruction::Return => {
let value = self.pop(position)?.resolve(&self.chunk, position)?.clone();
let stacked = self.pop(position)?;
let value = match stacked {
StackedValue::Runtime(value) => value,
StackedValue::Constant(index) => Rc::get_mut(&mut self.chunk)
.unwrap()
.remove_constant(index)
.map_err(|error| VmError::Chunk { error, position })?,
};
return Ok(Some(value));
}
@ -58,32 +66,55 @@ impl Vm {
}
// Variables
Instruction::DefineVariable => {
let (index, _) = *self.read(position)?;
Instruction::DefineVariableRuntime => {
let value = self.pop(position)?.resolve(&self.chunk, position)?.clone();
self.stack
.insert(index as usize, StackedValue::Constant(index));
self.push_runtime_value(value, position)?;
}
Instruction::DefineVariableConstant => {
let (argument, _) = *self.read(position)?;
self.push_constant_value(argument, position)?;
}
Instruction::GetVariable => {
let (index, _) = *self.read(position)?;
let (argument, _) = *self.read(position)?;
self.push_constant_value(index, position)?;
let local = self
.chunk
.get_local(argument)
.map_err(|error| VmError::Chunk { error, position })?;
match local.value_location {
ValueLocation::ConstantStack => {
let value = self
.chunk
.get_constant(argument)
.map_err(|error| VmError::Chunk { error, position })?
.clone();
self.push_runtime_value(value, position)?;
}
ValueLocation::RuntimeStack => {
let value = self.pop(position)?.resolve(&self.chunk, position)?.clone();
self.push_runtime_value(value, position)?;
}
}
}
Instruction::SetVariable => {
let (index, _) = *self.read(position)?;
let (argument, _) = *self.read(position)?;
let identifier = self
.chunk
.get_identifier(index)
.map_err(|error| VmError::Chunk { error, position })?
.clone();
.get_identifier(argument)
.map_err(|error| VmError::Chunk { error, position })?;
if !self.chunk.contains_identifier(&identifier) {
return Err(VmError::UndefinedVariable(identifier, position));
if !self.chunk.contains_identifier(identifier) {
return Err(VmError::UndefinedVariable(identifier.clone(), position));
}
let stacked = self.pop(position)?;
self.stack[index as usize] = stacked;
self.stack[argument as usize] = stacked;
}
// Unary
@ -261,6 +292,12 @@ impl Vm {
if self.stack.len() == Self::STACK_SIZE {
Err(VmError::StackOverflow(position))
} else {
let value = if value.is_raw() {
value.into_reference()
} else {
value
};
self.stack.push(StackedValue::Runtime(value));
Ok(())
@ -334,28 +371,41 @@ impl VmError {
pub fn value(error: ValueError, position: Span) -> Self {
Self::Value { error, position }
}
}
pub fn title() -> &'static str {
impl AnnotatedError for VmError {
fn title() -> &'static str {
"Runtime Error"
}
pub fn description(&self) -> String {
fn description(&self) -> &'static str {
match self {
Self::InvalidInstruction(byte, _) => {
format!("The byte {byte} does not correspond to a valid instruction")
}
Self::StackOverflow(position) => format!("Stack overflow at {position}"),
Self::StackUnderflow(position) => format!("Stack underflow at {position}"),
Self::UndefinedVariable(identifier, position) => {
format!("{identifier} is not in scope at {position}")
}
Self::Chunk { error, .. } => error.description(),
Self::Value { error, .. } => error.description(),
Self::InvalidInstruction(_, _) => "Invalid instruction",
Self::StackOverflow(_) => "Stack overflow",
Self::StackUnderflow(_) => "Stack underflow",
Self::UndefinedVariable(_, _) => "Undefined variable",
Self::Chunk { .. } => "Chunk error",
Self::Value { .. } => "Value error",
}
}
pub fn position(&self) -> Span {
fn details(&self) -> Option<String> {
match self {
Self::InvalidInstruction(byte, _) => Some(format!(
"The byte {byte} does not correspond to a valid instruction"
)),
Self::StackOverflow(position) => Some(format!("Stack overflow at {position}")),
Self::StackUnderflow(position) => Some(format!("Stack underflow at {position}")),
Self::UndefinedVariable(identifier, position) => {
Some(format!("{identifier} is not in scope at {position}"))
}
Self::Chunk { error, .. } => Some(error.to_string()),
Self::Value { error, .. } => Some(error.to_string()),
}
}
fn position(&self) -> Span {
match self {
Self::InvalidInstruction(_, position) => *position,
Self::StackUnderflow(position) => *position,

View File

@ -2,6 +2,7 @@ use std::fs::read_to_string;
use clap::Parser;
use dust_lang::{parse, run};
use env_logger::WriteStyle;
#[derive(Parser)]
struct Cli {
@ -15,7 +16,11 @@ struct Cli {
}
fn main() {
env_logger::init();
env_logger::builder()
.parse_env("DUST_LOG")
.format_timestamp_secs()
.write_style(WriteStyle::Always)
.init();
let args = Cli::parse();