Add implicit returns and fix variable declaration and resolution
This commit is contained in:
parent
f936c30b4f
commit
4ba3a47ae5
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 "),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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),
|
||||
|
@ -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};
|
||||
|
@ -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![],
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
|
@ -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();
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user