diff --git a/dust-lang/src/chunk.rs b/dust-lang/src/chunk.rs index b5e5b1f..3432fd9 100644 --- a/dust-lang/src/chunk.rs +++ b/dust-lang/src/chunk.rs @@ -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 { + pub fn push_constant_identifier(&mut self, identifier: Identifier) -> Result { 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 { + 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) } } } diff --git a/dust-lang/src/dust_error.rs b/dust-lang/src/dust_error.rs index ffef00e..2fe243e 100644 --- a/dust-lang/src/dust_error.rs +++ b/dust-lang/src/dust_error.rs @@ -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; + fn position(&self) -> Span; +} diff --git a/dust-lang/src/identifier_stack.rs b/dust-lang/src/identifier_stack.rs index d7d0504..4dc3d39 100644 --- a/dust-lang/src/identifier_stack.rs +++ b/dust-lang/src/identifier_stack.rs @@ -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 "), + } + } } diff --git a/dust-lang/src/instruction.rs b/dust-lang/src/instruction.rs index 118a858..9d43d75 100644 --- a/dust-lang/src/instruction.rs +++ b/dust-lang/src/instruction.rs @@ -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 diff --git a/dust-lang/src/lexer.rs b/dust-lang/src/lexer.rs index af40d0b..a6dfbd1 100644 --- a/dust-lang/src/lexer.rs +++ b/dust-lang/src/lexer.rs @@ -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 { + 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), diff --git a/dust-lang/src/lib.rs b/dust-lang/src/lib.rs index d36f5ab..e9bd64f 100644 --- a/dust-lang/src/lib.rs +++ b/dust-lang/src/lib.rs @@ -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}; diff --git a/dust-lang/src/parser.rs b/dust-lang/src/parser.rs index ec58a2a..e0efc14 100644 --- a/dust-lang/src/parser.rs +++ b/dust-lang/src/parser.rs @@ -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 { @@ -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 { + 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 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![], diff --git a/dust-lang/src/value.rs b/dust-lang/src/value.rs index ef92c69..a5b88a6 100644 --- a/dust-lang/src/value.rs +++ b/dust-lang/src/value.rs @@ -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 { diff --git a/dust-lang/src/vm.rs b/dust-lang/src/vm.rs index fb3f633..b5e9702 100644 --- a/dust-lang/src/vm.rs +++ b/dust-lang/src/vm.rs @@ -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, DustError> { @@ -13,7 +14,7 @@ pub fn run(source: &str) -> Result, DustError> { .map_err(|error| DustError::Runtime { error, source }) } -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Debug, Eq, PartialEq)] pub struct Vm { chunk: Rc, 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 { + 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, diff --git a/dust-shell/src/main.rs b/dust-shell/src/main.rs index 5f43c23..889c2f5 100644 --- a/dust-shell/src/main.rs +++ b/dust-shell/src/main.rs @@ -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();