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

View File

@ -1,6 +1,6 @@
use annotate_snippets::{Level, Renderer, Snippet}; use annotate_snippets::{Level, Renderer, Snippet};
use crate::{vm::VmError, LexError, ParseError}; use crate::{vm::VmError, LexError, ParseError, Span};
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum DustError<'src> { pub enum DustError<'src> {
@ -26,26 +26,28 @@ impl<'src> DustError<'src> {
match self { match self {
DustError::Runtime { error, source } => { DustError::Runtime { error, source } => {
let position = error.position(); let position = error.position();
let description = error.description(); let label = format!("Runtime error: {}", error.description());
let message = Level::Error.title(VmError::title()).snippet( let details = error
Snippet::source(source).fold(true).annotation( .details()
Level::Error .unwrap_or_else(|| "While running this code".to_string());
.span(position.0..position.1) let message = Level::Error.title(&label).snippet(
.label(&description), Snippet::source(source)
), .fold(true)
.annotation(Level::Error.span(position.0..position.1).label(&details)),
); );
report.push_str(&renderer.render(message).to_string()); report.push_str(&renderer.render(message).to_string());
} }
DustError::Parse { error, source } => { DustError::Parse { error, source } => {
let position = error.position(); let position = error.position();
let description = error.description(); let label = format!("Parse error: {}", error.description());
let message = Level::Error.title(ParseError::title()).snippet( let details = error
Snippet::source(source).fold(true).annotation( .details()
Level::Error .unwrap_or_else(|| "While parsing this code".to_string());
.span(position.0..position.1) let message = Level::Error.title(&label).snippet(
.label(&description), Snippet::source(source)
), .fold(true)
.annotation(Level::Error.span(position.0..position.1).label(&details)),
); );
report.push_str(&renderer.render(message).to_string()); report.push_str(&renderer.render(message).to_string());
@ -56,3 +58,10 @@ impl<'src> DustError<'src> {
report 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 serde::{Deserialize, Serialize};
use crate::Identifier; use crate::Identifier;
@ -68,10 +70,11 @@ impl IdentifierStack {
self.scope_depth -= 1; 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 { self.locals.push(Local {
identifier, identifier,
depth: self.scope_depth, depth: self.scope_depth,
value_location,
}); });
} }
@ -98,4 +101,20 @@ impl PartialEq for IdentifierStack {
pub struct Local { pub struct Local {
pub identifier: Identifier, pub identifier: Identifier,
pub depth: usize, 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, Pop = 2,
// Variables // Variables
DefineVariable = 3, DefineVariableRuntime = 3,
GetVariable = 4, DefineVariableConstant = 4,
SetVariable = 5, GetVariable = 5,
SetVariable = 6,
// Unary // Unary
Negate = 6, Negate = 7,
Not = 7, Not = 8,
// Binary // Binary
Add = 8, Add = 9,
Subtract = 9, Subtract = 10,
Multiply = 10, Multiply = 11,
Divide = 11, Divide = 12,
Greater = 12, Greater = 13,
Less = 13, Less = 14,
GreaterEqual = 14, GreaterEqual = 15,
LessEqual = 15, LessEqual = 16,
Equal = 16, Equal = 17,
NotEqual = 17, NotEqual = 18,
And = 18, And = 19,
Or = 19, Or = 20,
} }
impl Instruction { impl Instruction {
@ -40,23 +41,24 @@ impl Instruction {
0 => Some(Instruction::Constant), 0 => Some(Instruction::Constant),
1 => Some(Instruction::Return), 1 => Some(Instruction::Return),
2 => Some(Instruction::Pop), 2 => Some(Instruction::Pop),
3 => Some(Instruction::DefineVariable), 3 => Some(Instruction::DefineVariableRuntime),
4 => Some(Instruction::GetVariable), 4 => Some(Instruction::DefineVariableConstant),
5 => Some(Instruction::SetVariable), 5 => Some(Instruction::GetVariable),
6 => Some(Instruction::Negate), 6 => Some(Instruction::SetVariable),
7 => Some(Instruction::Not), 7 => Some(Instruction::Negate),
8 => Some(Instruction::Add), 8 => Some(Instruction::Not),
9 => Some(Instruction::Subtract), 9 => Some(Instruction::Add),
10 => Some(Instruction::Multiply), 10 => Some(Instruction::Subtract),
11 => Some(Instruction::Divide), 11 => Some(Instruction::Multiply),
12 => Some(Instruction::Greater), 12 => Some(Instruction::Divide),
13 => Some(Instruction::Less), 13 => Some(Instruction::Greater),
14 => Some(Instruction::GreaterEqual), 14 => Some(Instruction::Less),
15 => Some(Instruction::LessEqual), 15 => Some(Instruction::GreaterEqual),
16 => Some(Instruction::Equal), 16 => Some(Instruction::LessEqual),
17 => Some(Instruction::NotEqual), 17 => Some(Instruction::Equal),
18 => Some(Instruction::And), 18 => Some(Instruction::NotEqual),
19 => Some(Instruction::Or), 19 => Some(Instruction::And),
20 => Some(Instruction::Or),
_ => None, _ => None,
} }
} }
@ -64,55 +66,46 @@ impl Instruction {
pub fn disassemble(&self, chunk: &Chunk, offset: usize) -> String { pub fn disassemble(&self, chunk: &Chunk, offset: usize) -> String {
match self { match self {
Instruction::Constant => { Instruction::Constant => {
let (index_display, value_display) = let (argument, _) = chunk.get_code(offset + 1).unwrap();
if let Ok((index, _)) = chunk.get_code(offset + 1) { let value_display = chunk
let index_string = index.to_string(); .get_constant(*argument)
let value_string = chunk
.get_constant(*index)
.map(|value| value.to_string()) .map(|value| value.to_string())
.unwrap_or_else(|error| format!("{:?}", error)); .unwrap_or_else(|error| error.to_string());
(index_string, value_string) format!("CONSTANT {argument} {value_display}")
} else {
let index = "ERROR".to_string();
let value = "ERROR".to_string();
(index, value)
};
format!("CONSTANT {index_display} {value_display}")
} }
Instruction::Return => format!("{offset:04} RETURN"), Instruction::Return => "RETURN".to_string(),
Instruction::Pop => format!("{offset:04} POP"), Instruction::Pop => "POP".to_string(),
// Variables // Variables
Instruction::DefineVariable => { Instruction::DefineVariableRuntime => "DEFINE_VARIABLE_RUNTIME".to_string(),
let (index, _) = chunk.get_code(offset + 1).unwrap(); Instruction::DefineVariableConstant => {
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(), 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 => { Instruction::GetVariable => {
let (index, _) = chunk.get_code(offset + 1).unwrap(); let (argument, _) = chunk.get_code(offset + 1).unwrap();
let identifier_display = match chunk.get_identifier(*index) { let identifier_display = match chunk.get_identifier(*argument) {
Ok(identifier) => identifier.to_string(), 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 => { Instruction::SetVariable => {
let (index, _) = chunk.get_code(offset + 1).unwrap(); let (argument, _) = chunk.get_code(offset + 1).unwrap();
let identifier_display = match chunk.get_identifier(*index) { let identifier_display = match chunk.get_identifier(*argument) {
Ok(identifier) => identifier.to_string(), 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 // Unary

View File

@ -5,7 +5,7 @@
//! - [`Lexer`], which lexes the input a token at a time //! - [`Lexer`], which lexes the input a token at a time
use std::fmt::{self, Display, Formatter}; 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. /// Lexes the input and return a vector of tokens and their positions.
/// ///
@ -529,26 +529,35 @@ pub enum LexError {
}, },
} }
impl LexError { impl AnnotatedError for LexError {
pub fn title() -> &'static str { fn title() -> &'static str {
"Lex Error" "Lex Error"
} }
pub fn description(&self) -> String { fn description(&self) -> &'static str {
match self { match self {
Self::ExpectedCharacter { Self::ExpectedCharacter { .. } => "Expected character",
expected, actual, .. Self::UnexpectedCharacter { .. } => "Unexpected character",
} => { Self::UnexpectedEndOfFile { .. } => "Unexpected end of file",
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 { 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 { match self {
Self::ExpectedCharacter { position, .. } => Span(*position, *position), Self::ExpectedCharacter { position, .. } => Span(*position, *position),
Self::UnexpectedCharacter { 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 chunk::{Chunk, ChunkError};
pub use constructor::{ConstructError, Constructor}; pub use constructor::{ConstructError, Constructor};
pub use dust_error::DustError; pub use dust_error::{AnnotatedError, DustError};
pub use identifier::Identifier; pub use identifier::Identifier;
pub use identifier_stack::IdentifierStack; pub use identifier_stack::{IdentifierStack, Local, ValueLocation};
pub use instruction::Instruction; pub use instruction::Instruction;
pub use lexer::{lex, LexError, Lexer}; pub use lexer::{lex, LexError, Lexer};
pub use parser::{parse, ParseError, Parser}; pub use parser::{parse, ParseError, Parser};

View File

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

View File

@ -1783,66 +1783,6 @@ pub enum ValueError {
IndexOutOfBounds { value: Value, index: i64 }, 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 { impl Display for ValueError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self { match self {

View File

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

View File

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