Reimplement the Parser and VM with a register-based VM

This commit is contained in:
Jeff 2024-09-12 00:39:31 -04:00
parent 7b055d79b5
commit 67e5de6664
10 changed files with 539 additions and 163 deletions

View File

@ -181,21 +181,20 @@ impl Chunk {
let name_length = name.len(); let name_length = name.len();
let buffer_length = 34_usize.saturating_sub(name_length + 2); let buffer_length = 34_usize.saturating_sub(name_length + 2);
let name_buffer = " ".repeat(buffer_length / 2); let name_buffer = " ".repeat(buffer_length / 2);
let name_line = format!("\n{name_buffer}{name}{name_buffer}\n"); let name_line = format!("{name_buffer}{name}{name_buffer}\n");
let name_underline = format!("{name_buffer}{}{name_buffer}\n", "-".repeat(name_length)); let name_underline = format!("{name_buffer}{}{name_buffer}\n", "-".repeat(name_length));
output.push_str(&name_line); output.push_str(&name_line);
output.push_str(&name_underline); output.push_str(&name_underline);
output.push_str("\n Code \n"); output.push_str("\n Code \n");
output.push_str("------ ------------ ------------\n"); output.push_str("------ -------- ------------\n");
output.push_str("OFFSET POSITION INSTRUCTION\n"); output.push_str("OFFSET POSITION INSTRUCTION\n");
output.push_str("------ ------------ ------------\n"); output.push_str("------ -------- ------------\n");
for (offset, (instruction, position)) in self.code.iter().enumerate() { for (offset, (instruction, position)) in self.code.iter().enumerate() {
let display = format!( let display = format!(
"{offset:4} {:12} {}\n", "{offset:04} {position} {}\n",
position.to_string(), instruction.disassemble(self)
instruction.disassemble(self, offset)
); );
output.push_str(&display); output.push_str(&display);

View File

@ -1,6 +1,6 @@
use annotate_snippets::{Level, Renderer, Snippet}; use annotate_snippets::{Level, Renderer, Snippet};
use crate::{LexError, ParseError, Span}; use crate::{vm::VmError, LexError, ParseError, Span};
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum DustError<'src> { pub enum DustError<'src> {
@ -12,6 +12,10 @@ pub enum DustError<'src> {
error: ParseError, error: ParseError,
source: &'src str, source: &'src str,
}, },
Runtime {
error: VmError,
source: &'src str,
},
} }
impl<'src> DustError<'src> { impl<'src> DustError<'src> {
@ -20,6 +24,20 @@ impl<'src> DustError<'src> {
let renderer = Renderer::styled(); let renderer = Renderer::styled();
match self { match self {
DustError::Runtime { error, source } => {
let position = error.position();
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 } => { DustError::Parse { error, source } => {
let position = error.position(); let position = error.position();
let label = format!("Parse error: {}", error.description()); let label = format!("Parse error: {}", error.description());

View File

@ -1,16 +1,18 @@
use std::fmt::{self, Display, Formatter};
use crate::{Chunk, Span}; use crate::{Chunk, Span};
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
pub struct Instruction { pub struct Instruction {
opcode: OpCode, pub operation: Operation,
to_register: u8, pub to_register: u8,
arguments: [u8; 2], pub arguments: [u8; 2],
} }
impl Instruction { impl Instruction {
pub fn r#move(to_register: u8, from_register: u8) -> Instruction { pub fn r#move(to_register: u8, from_register: u8) -> Instruction {
Instruction { Instruction {
opcode: OpCode::Move, operation: Operation::Move,
to_register, to_register,
arguments: [from_register, 0], arguments: [from_register, 0],
} }
@ -18,7 +20,7 @@ impl Instruction {
pub fn close(to_register: u8) -> Instruction { pub fn close(to_register: u8) -> Instruction {
Instruction { Instruction {
opcode: OpCode::Close, operation: Operation::Close,
to_register, to_register,
arguments: [0, 0], arguments: [0, 0],
} }
@ -26,7 +28,7 @@ impl Instruction {
pub fn load_constant(to_register: u8, constant_index: u16) -> Instruction { pub fn load_constant(to_register: u8, constant_index: u16) -> Instruction {
Instruction { Instruction {
opcode: OpCode::LoadConstant, operation: Operation::LoadConstant,
to_register, to_register,
arguments: constant_index.to_le_bytes(), arguments: constant_index.to_le_bytes(),
} }
@ -34,7 +36,7 @@ impl Instruction {
pub fn declare_variable(to_register: u8, variable_index: u16) -> Instruction { pub fn declare_variable(to_register: u8, variable_index: u16) -> Instruction {
Instruction { Instruction {
opcode: OpCode::DeclareVariable, operation: Operation::DeclareVariable,
to_register, to_register,
arguments: variable_index.to_le_bytes(), arguments: variable_index.to_le_bytes(),
} }
@ -42,7 +44,7 @@ impl Instruction {
pub fn get_variable(to_register: u8, variable_index: u16) -> Instruction { pub fn get_variable(to_register: u8, variable_index: u16) -> Instruction {
Instruction { Instruction {
opcode: OpCode::GetVariable, operation: Operation::GetVariable,
to_register, to_register,
arguments: variable_index.to_le_bytes(), arguments: variable_index.to_le_bytes(),
} }
@ -50,7 +52,7 @@ impl Instruction {
pub fn set_variable(from_register: u8, variable_index: u16) -> Instruction { pub fn set_variable(from_register: u8, variable_index: u16) -> Instruction {
Instruction { Instruction {
opcode: OpCode::SetVariable, operation: Operation::SetVariable,
to_register: from_register, to_register: from_register,
arguments: variable_index.to_le_bytes(), arguments: variable_index.to_le_bytes(),
} }
@ -58,7 +60,7 @@ impl Instruction {
pub fn add(to_register: u8, left_register: u8, right_register: u8) -> Instruction { pub fn add(to_register: u8, left_register: u8, right_register: u8) -> Instruction {
Instruction { Instruction {
opcode: OpCode::Add, operation: Operation::Add,
to_register, to_register,
arguments: [left_register, right_register], arguments: [left_register, right_register],
} }
@ -66,7 +68,7 @@ impl Instruction {
pub fn subtract(to_register: u8, left_register: u8, right_register: u8) -> Instruction { pub fn subtract(to_register: u8, left_register: u8, right_register: u8) -> Instruction {
Instruction { Instruction {
opcode: OpCode::Subtract, operation: Operation::Subtract,
to_register, to_register,
arguments: [left_register, right_register], arguments: [left_register, right_register],
} }
@ -74,7 +76,7 @@ impl Instruction {
pub fn multiply(to_register: u8, left_register: u8, right_register: u8) -> Instruction { pub fn multiply(to_register: u8, left_register: u8, right_register: u8) -> Instruction {
Instruction { Instruction {
opcode: OpCode::Multiply, operation: Operation::Multiply,
to_register, to_register,
arguments: [left_register, right_register], arguments: [left_register, right_register],
} }
@ -82,7 +84,7 @@ impl Instruction {
pub fn divide(to_register: u8, left_register: u8, right_register: u8) -> Instruction { pub fn divide(to_register: u8, left_register: u8, right_register: u8) -> Instruction {
Instruction { Instruction {
opcode: OpCode::Divide, operation: Operation::Divide,
to_register, to_register,
arguments: [left_register, right_register], arguments: [left_register, right_register],
} }
@ -90,7 +92,7 @@ impl Instruction {
pub fn negate(to_register: u8, from_register: u8) -> Instruction { pub fn negate(to_register: u8, from_register: u8) -> Instruction {
Instruction { Instruction {
opcode: OpCode::Negate, operation: Operation::Negate,
to_register, to_register,
arguments: [from_register, 0], arguments: [from_register, 0],
} }
@ -98,84 +100,97 @@ impl Instruction {
pub fn r#return() -> Instruction { pub fn r#return() -> Instruction {
Instruction { Instruction {
opcode: OpCode::Return, operation: Operation::Return,
to_register: 0, to_register: 0,
arguments: [0, 0], arguments: [0, 0],
} }
} }
pub fn disassemble(&self, chunk: &Chunk, offset: usize) -> String { pub fn disassemble(&self, chunk: &Chunk) -> String {
match self.opcode { match self.operation {
OpCode::Move => format!( Operation::LoadConstant => {
"{:04} MOVE R{} R{}",
offset, self.to_register, self.arguments[0]
),
OpCode::Close => {
format!("{:04} CLOSE R{}", offset, self.to_register)
}
OpCode::LoadConstant => {
let constant_index = u16::from_le_bytes(self.arguments); let constant_index = u16::from_le_bytes(self.arguments);
let constant_display = match chunk.get_constant(constant_index, Span(0, 0)) { let constant_display = match chunk.get_constant(constant_index, Span(0, 0)) {
Ok(value) => value.to_string(), Ok(value) => value.to_string(),
Err(error) => format!("{:?}", error), Err(error) => format!("{:?}", error),
}; };
format!( format!("{self} {constant_display}")
"{:04} LOAD_CONSTANT R{} C{} {}",
offset, self.to_register, constant_index, constant_display
)
} }
OpCode::DeclareVariable => { _ => format!("{self}"),
}
}
}
impl Display for Instruction {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self.operation {
Operation::Move => write!(f, "MOVE R{} R{}", self.to_register, self.arguments[0]),
Operation::Close => write!(f, "CLOSE R{}", self.to_register),
Operation::LoadConstant => {
let constant_index = u16::from_le_bytes(self.arguments);
write!(f, "LOAD_CONSTANT R{} C{}", self.to_register, constant_index)
}
Operation::DeclareVariable => {
let variable_index = u16::from_le_bytes([self.arguments[0], self.arguments[1]]); let variable_index = u16::from_le_bytes([self.arguments[0], self.arguments[1]]);
format!( write!(
"{:04} DECLARE_VARIABLE V{} R{}", f,
offset, variable_index, self.to_register "DECLARE_VARIABLE V{} R{}",
variable_index, self.to_register
) )
} }
OpCode::GetVariable => { Operation::GetVariable => {
let variable_index = u16::from_le_bytes([self.arguments[0], self.arguments[1]]); let variable_index = u16::from_le_bytes([self.arguments[0], self.arguments[1]]);
format!( write!(f, "GET_VARIABLE R{} V{}", self.to_register, variable_index)
"{:04} GET_VARIABLE R{} V{}",
offset, self.to_register, variable_index
)
} }
OpCode::SetVariable => { Operation::SetVariable => {
let variable_index = u16::from_le_bytes([self.arguments[0], self.arguments[1]]); let variable_index = u16::from_le_bytes([self.arguments[0], self.arguments[1]]);
format!( write!(f, "SET_VARIABLE V{} R{}", variable_index, self.to_register)
"{:04} SET_VARIABLE V{} R{}", }
offset, variable_index, self.to_register Operation::Add => {
write!(
f,
"ADD R{} = R{} + R{}",
self.to_register, self.arguments[0], self.arguments[1]
) )
} }
OpCode::Add => format!( Operation::Subtract => {
"{:04} ADD R{} = R{} + R{}", write!(
offset, self.to_register, self.arguments[0], self.arguments[1] f,
), "SUBTRACT R{} = R{} - R{}",
OpCode::Subtract => format!( self.to_register, self.arguments[0], self.arguments[1]
"{:04} SUBTRACT R{} = R{} - R{}", )
offset, self.to_register, self.arguments[0], self.arguments[1] }
), Operation::Multiply => {
OpCode::Multiply => format!( write!(
"{:04} MULTIPLY R{} = R{} * R{}", f,
offset, self.to_register, self.arguments[0], self.arguments[1] "MULTIPLY R{} = R{} * R{}",
), self.to_register, self.arguments[0], self.arguments[1]
OpCode::Divide => format!( )
"{:04} DIVIDE R{} = R{} / R{}", }
offset, self.to_register, self.arguments[0], self.arguments[1] Operation::Divide => {
), write!(
OpCode::Negate => format!( f,
"{:04} NEGATE R{} = !R{}", "DIVIDE R{} = R{} / R{}",
offset, self.to_register, self.arguments[0] self.to_register, self.arguments[0], self.arguments[1]
), )
OpCode::Return => format!("{:04} RETURN", offset), }
Operation::Negate => {
write!(f, "NEGATE R{} = !R{}", self.to_register, self.arguments[0])
}
Operation::Return => {
write!(f, "RETURN")
}
} }
} }
} }
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
enum OpCode { pub enum Operation {
// Stack manipulation // Stack manipulation
Move, Move,
Close, Close,
@ -201,6 +216,25 @@ enum OpCode {
Return, Return,
} }
impl Display for Operation {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Operation::Move => write!(f, "MOVE"),
Operation::Close => write!(f, "CLOSE"),
Operation::LoadConstant => write!(f, "LOAD_CONSTANT"),
Operation::DeclareVariable => write!(f, "DECLARE_VARIABLE"),
Operation::GetVariable => write!(f, "GET_VARIABLE"),
Operation::SetVariable => write!(f, "SET_VARIABLE"),
Operation::Add => write!(f, "ADD"),
Operation::Subtract => write!(f, "SUBTRACT"),
Operation::Multiply => write!(f, "MULTIPLY"),
Operation::Divide => write!(f, "DIVIDE"),
Operation::Negate => write!(f, "NEGATE"),
Operation::Return => write!(f, "RETURN"),
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::mem::size_of; use std::mem::size_of;

View File

@ -8,6 +8,7 @@ mod parser;
mod token; mod token;
mod r#type; mod r#type;
mod value; mod value;
mod vm;
use std::fmt::Display; use std::fmt::Display;
@ -15,12 +16,13 @@ pub use chunk::{Chunk, ChunkError};
pub use constructor::Constructor; pub use constructor::Constructor;
pub use dust_error::{AnnotatedError, DustError}; pub use dust_error::{AnnotatedError, DustError};
pub use identifier::Identifier; pub use identifier::Identifier;
pub use instruction::Instruction; pub use instruction::{Instruction, Operation};
pub use lexer::{lex, LexError, Lexer}; pub use lexer::{lex, LexError, Lexer};
pub use parser::{parse, ParseError, Parser}; pub use parser::{parse, ParseError, Parser};
pub use r#type::{EnumType, FunctionType, RangeableType, StructType, Type, TypeConflict}; pub use r#type::{EnumType, FunctionType, RangeableType, StructType, Type, TypeConflict};
pub use token::{Token, TokenKind, TokenOwned}; pub use token::{Token, TokenKind, TokenOwned};
pub use value::{Enum, Function, Struct, Value}; pub use value::{Enum, Function, Struct, Value, ValueError};
pub use vm::{run, Vm, VmError};
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
pub struct Span(pub usize, pub usize); pub struct Span(pub usize, pub usize);

View File

@ -214,6 +214,7 @@ impl<'src> Parser<'src> {
} }
}; };
self.increment_register()?;
self.parse_expression()?; self.parse_expression()?;
self.emit_instruction(byte, operator_position); self.emit_instruction(byte, operator_position);
@ -234,8 +235,8 @@ impl<'src> Parser<'src> {
} else { } else {
self.current_register self.current_register
}; };
let left_register = to_register - 1; let left_register = to_register - 2;
let right_register = to_register - 2; let right_register = to_register - 1;
let byte = match operator { let byte = match operator {
TokenKind::Plus => Instruction::add(to_register, left_register, right_register), TokenKind::Plus => Instruction::add(to_register, left_register, right_register),
TokenKind::Minus => Instruction::subtract(to_register, left_register, right_register), TokenKind::Minus => Instruction::subtract(to_register, left_register, right_register),
@ -255,6 +256,7 @@ impl<'src> Parser<'src> {
} }
}; };
self.increment_register()?;
self.emit_instruction(byte, operator_position); self.emit_instruction(byte, operator_position);
Ok(()) Ok(())
@ -319,19 +321,10 @@ impl<'src> Parser<'src> {
} }
self.chunk.end_scope(); self.chunk.end_scope();
while self
.chunk
.identifiers()
.iter()
.next_back()
.map_or(false, |local| local.depth > self.chunk.scope_depth())
{
self.emit_instruction( self.emit_instruction(
Instruction::close(self.current_register), Instruction::close(self.current_register),
self.current_position, self.current_position,
); );
}
Ok(()) Ok(())
} }
@ -392,7 +385,7 @@ impl<'src> Parser<'src> {
let identifier_index = self.chunk.declare_variable(identifier, position)?; let identifier_index = self.chunk.declare_variable(identifier, position)?;
self.emit_instruction( self.emit_instruction(
Instruction::set_variable(self.current_register, identifier_index), Instruction::declare_variable(self.current_register, identifier_index),
position, position,
); );
self.increment_register()?; self.increment_register()?;
@ -714,7 +707,7 @@ impl AnnotatedError for ParseError {
} }
Self::RegisterOverflow { .. } => None, Self::RegisterOverflow { .. } => None,
Self::Chunk(error) => error.details(), Self::Chunk(error) => error.details(),
Self::Lex(error) => Some(error.to_string()), Self::Lex(error) => error.details(),
Self::ParseIntError { error, .. } => Some(error.to_string()), Self::ParseIntError { error, .. } => Some(error.to_string()),
} }
} }

View File

@ -23,7 +23,24 @@ fn add() {
vec![ vec![
(Instruction::load_constant(0, 0), Span(0, 1)), (Instruction::load_constant(0, 0), Span(0, 1)),
(Instruction::load_constant(1, 1), Span(4, 5)), (Instruction::load_constant(1, 1), Span(4, 5)),
(Instruction::add(2, 1, 0), Span(2, 3)), (Instruction::add(2, 0, 1), Span(2, 3)),
(Instruction::r#return(), Span(0, 5)),
],
vec![Value::integer(1), Value::integer(2),],
vec![]
))
);
}
#[test]
fn subtract() {
assert_eq!(
parse("1 - 2"),
Ok(Chunk::with_data(
vec![
(Instruction::load_constant(0, 0), Span(0, 1)),
(Instruction::load_constant(1, 1), Span(4, 5)),
(Instruction::subtract(2, 0, 1), Span(2, 3)),
(Instruction::r#return(), Span(0, 5)), (Instruction::r#return(), Span(0, 5)),
], ],
vec![Value::integer(1), Value::integer(2),], vec![Value::integer(1), Value::integer(2),],

231
dust-lang/src/vm.rs Normal file
View File

@ -0,0 +1,231 @@
use crate::{
dust_error::AnnotatedError, parse, Chunk, ChunkError, DustError, Identifier, Instruction,
Operation, Span, Value, ValueError,
};
pub fn run(source: &str) -> Result<Option<Value>, DustError> {
let chunk = parse(source)?;
let mut vm = Vm::new(chunk);
vm.run()
.map_err(|error| DustError::Runtime { error, source })
}
#[derive(Debug, Eq, PartialEq)]
pub struct Vm {
chunk: Chunk,
ip: usize,
register_stack: Vec<Option<Value>>,
}
impl Vm {
const STACK_LIMIT: usize = u16::MAX as usize;
pub fn new(chunk: Chunk) -> Self {
Self {
chunk,
ip: 0,
register_stack: Vec::new(),
}
}
pub fn run(&mut self) -> Result<Option<Value>, VmError> {
while let Ok((instruction, position)) = self.read(Span(0, 0)).copied() {
log::trace!("Running instruction {instruction} at {position}");
match instruction.operation {
Operation::Move => todo!(),
Operation::Close => todo!(),
Operation::LoadConstant => {
let register_index = instruction.to_register as usize;
let constant_index = u16::from_le_bytes(instruction.arguments);
let value = self.chunk.use_constant(constant_index, position)?;
self.insert(value, register_index, position)?;
}
Operation::DeclareVariable => todo!(),
Operation::GetVariable => todo!(),
Operation::SetVariable => todo!(),
Operation::Add => {
let left = self.take(instruction.arguments[0] as usize, position)?;
let right = self.take(instruction.arguments[1] as usize, position)?;
let sum = left
.add(&right)
.map_err(|error| VmError::Value { error, position })?;
self.insert(sum, instruction.to_register as usize, position)?;
}
Operation::Subtract => {
let left = self.take(instruction.arguments[0] as usize, position)?;
let right = self.take(instruction.arguments[1] as usize, position)?;
let difference = left
.subtract(&right)
.map_err(|error| VmError::Value { error, position })?;
self.insert(difference, instruction.to_register as usize, position)?;
}
Operation::Multiply => {
let left = self.take(instruction.arguments[0] as usize, position)?;
let right = self.take(instruction.arguments[1] as usize, position)?;
let product = left
.multiply(&right)
.map_err(|error| VmError::Value { error, position })?;
self.insert(product, instruction.to_register as usize, position)?;
}
Operation::Divide => {
let left = self.take(instruction.arguments[0] as usize, position)?;
let right = self.take(instruction.arguments[1] as usize, position)?;
let quotient = left
.divide(&right)
.map_err(|error| VmError::Value { error, position })?;
self.insert(quotient, instruction.to_register as usize, position)?;
}
Operation::Negate => {
let value = self.take(instruction.arguments[0] as usize, position)?;
let negated = value
.negate()
.map_err(|error| VmError::Value { error, position })?;
self.insert(negated, instruction.to_register as usize, position)?;
}
Operation::Return => {
let value = self.pop(position)?;
return Ok(Some(value));
}
}
}
Ok(None)
}
fn insert(&mut self, value: Value, index: usize, position: Span) -> Result<(), VmError> {
if self.register_stack.len() == Self::STACK_LIMIT {
Err(VmError::StackOverflow { position })
} else {
self.register_stack.insert(index, Some(value));
Ok(())
}
}
pub fn take(&mut self, index: usize, position: Span) -> Result<Value, VmError> {
if let Some(register) = self.register_stack.get_mut(index) {
let value = register
.take()
.ok_or(VmError::EmptyRegister { index, position })?;
Ok(value)
} else {
Err(VmError::RegisterIndexOutOfBounds { position })
}
}
fn pop(&mut self, position: Span) -> Result<Value, VmError> {
if let Some(register) = self.register_stack.pop() {
let value = register.ok_or(VmError::RegisterIndexOutOfBounds { position })?;
Ok(value)
} else {
Err(VmError::StackUnderflow { position })
}
}
fn read(&mut self, position: Span) -> Result<&(Instruction, Span), VmError> {
let current = self.chunk.get_code(self.ip, position)?;
self.ip += 1;
Ok(current)
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum VmError {
EmptyRegister {
index: usize,
position: Span,
},
RegisterIndexOutOfBounds {
position: Span,
},
InvalidInstruction {
instruction: Instruction,
position: Span,
},
StackOverflow {
position: Span,
},
StackUnderflow {
position: Span,
},
UndeclaredVariable {
position: Span,
},
UndefinedVariable {
identifier: Identifier,
position: Span,
},
// Wrappers for foreign errors
Chunk(ChunkError),
Value {
error: ValueError,
position: Span,
},
}
impl From<ChunkError> for VmError {
fn from(v: ChunkError) -> Self {
Self::Chunk(v)
}
}
impl AnnotatedError for VmError {
fn title() -> &'static str {
"Runtime Error"
}
fn description(&self) -> &'static str {
match self {
Self::EmptyRegister { .. } => "Empty register",
Self::RegisterIndexOutOfBounds { .. } => "Register index out of bounds",
Self::InvalidInstruction { .. } => "Invalid instruction",
Self::StackOverflow { .. } => "Stack overflow",
Self::StackUnderflow { .. } => "Stack underflow",
Self::UndeclaredVariable { .. } => "Undeclared variable",
Self::UndefinedVariable { .. } => "Undefined variable",
Self::Chunk(_) => "Chunk error",
Self::Value { .. } => "Value error",
}
}
fn details(&self) -> Option<String> {
match self {
Self::EmptyRegister { index, .. } => Some(format!("Register {index} is empty")),
Self::UndefinedVariable { identifier, .. } => {
Some(format!("{identifier} is not in scope"))
}
Self::Chunk(error) => error.details(),
Self::Value { error, .. } => Some(error.to_string()),
_ => None,
}
}
fn position(&self) -> Span {
match self {
Self::EmptyRegister { position, .. } => *position,
Self::RegisterIndexOutOfBounds { position } => *position,
Self::InvalidInstruction { position, .. } => *position,
Self::StackUnderflow { position } => *position,
Self::StackOverflow { position } => *position,
Self::UndeclaredVariable { position } => *position,
Self::UndefinedVariable { position, .. } => *position,
Self::Chunk(error) => error.position(),
Self::Value { position, .. } => *position,
}
}
}

View File

@ -0,0 +1,21 @@
use dust_lang::*;
#[test]
fn add() {
assert_eq!(run("1 + 2"), Ok(Some(Value::integer(3))));
}
#[test]
fn subtract() {
assert_eq!(run("1 - 2"), Ok(Some(Value::integer(-1))));
}
#[test]
fn multiply() {
assert_eq!(run("2 * 3"), Ok(Some(Value::integer(6))));
}
#[test]
fn divide() {
assert_eq!(run("6 / 3"), Ok(Some(Value::integer(2))));
}

61
dust-lang/tests/values.rs Normal file
View File

@ -0,0 +1,61 @@
use dust_lang::*;
#[test]
fn boolean() {
assert_eq!(run("true"), Ok(Some(Value::boolean(true))));
}
#[test]
fn byte() {
assert_eq!(run("0xff"), Ok(Some(Value::byte(0xff))));
}
#[test]
fn float_simple() {
assert_eq!(run("42.0"), Ok(Some(Value::float(42.0))));
}
#[test]
fn float_negative() {
assert_eq!(run("-42.0"), Ok(Some(Value::float(-42.0))));
}
#[test]
fn float_exponential() {
assert_eq!(run("4.2e1"), Ok(Some(Value::float(42.0))));
}
#[test]
fn float_exponential_positive() {
assert_eq!(run("4.2e+1"), Ok(Some(Value::float(42.0))));
}
#[test]
fn float_exponential_negative() {
assert_eq!(run("4.2e-1"), Ok(Some(Value::float(0.42))));
}
#[test]
fn float_special_values() {
assert_eq!(run("Infinity"), Ok(Some(Value::float(f64::INFINITY))));
assert_eq!(run("-Infinity"), Ok(Some(Value::float(f64::NEG_INFINITY))));
assert!(run("NaN").unwrap().unwrap().as_float().unwrap().is_nan());
}
#[test]
fn integer() {
assert_eq!(run("42"), Ok(Some(Value::integer(42))));
}
#[test]
fn integer_negative() {
assert_eq!(run("-42"), Ok(Some(Value::integer(-42))));
}
#[test]
fn string() {
assert_eq!(
run("\"Hello, world!\""),
Ok(Some(Value::string("Hello, world!")))
);
}

View File

@ -1,78 +1,78 @@
// use std::{fs::read_to_string, io::Write}; use std::{fs::read_to_string, io::Write};
// use clap::Parser; use clap::Parser;
// use colored::Colorize; use colored::Colorize;
// use dust_lang::{parse, run}; use dust_lang::{parse, run};
// use log::Level; use log::Level;
// #[derive(Parser)] #[derive(Parser)]
// struct Cli { struct Cli {
// #[arg(short, long)] #[arg(short, long)]
// command: Option<String>, command: Option<String>,
// #[arg(short, long)] #[arg(short, long)]
// parse: bool, parse: bool,
// path: Option<String>, path: Option<String>,
// }
fn main() {
// env_logger::builder()
// .parse_env("DUST_LOG")
// .format(|buf, record| {
// let level = match record.level() {
// Level::Error => "ERROR".red(),
// Level::Warn => "WARN".yellow(),
// Level::Info => "INFO".white(),
// Level::Debug => "DEBUG".blue(),
// Level::Trace => "TRACE".purple(),
// }
// .bold();
// let level_display = format!("[{level:^5}]").white().on_black();
// let module = record
// .module_path()
// .map(|path| path.split("::").last().unwrap_or("unknown"))
// .unwrap_or("unknown")
// .dimmed();
// writeln!(buf, "{level_display} {module:^6} {}", record.args())
// })
// .init();
// let args = Cli::parse();
// if let Some(command) = &args.command {
// if args.parse {
// parse_and_display_errors(command);
// } else {
// run_and_display_errors(command);
// }
// } else if let Some(path) = &args.path {
// let source = read_to_string(path).expect("Failed to read file");
// if args.parse {
// parse_and_display_errors(&source);
// } else {
// run_and_display_errors(&source);
// }
// }
} }
// fn parse_and_display_errors(source: &str) { fn main() {
// match parse(source) { env_logger::builder()
// Ok(chunk) => println!("{}", chunk.disassemble("Dust CLI Input")), .parse_env("DUST_LOG")
// Err(error) => { .format(|buf, record| {
// eprintln!("{:?}", error); let level = match record.level() {
// } Level::Error => "ERROR".red(),
// } Level::Warn => "WARN".yellow(),
// } Level::Info => "INFO".white(),
Level::Debug => "DEBUG".blue(),
Level::Trace => "TRACE".purple(),
}
.bold();
let level_display = format!("[{level:^5}]").white().on_black();
let module = record
.module_path()
.map(|path| path.split("::").last().unwrap_or("unknown"))
.unwrap_or("unknown")
.dimmed();
// fn run_and_display_errors(source: &str) { writeln!(buf, "{level_display} {module:^6} {}", record.args())
// match run(source) { })
// Ok(Some(value)) => println!("{}", value), .init();
// Ok(_) => {}
// Err(error) => { let args = Cli::parse();
// eprintln!("{}", error.report());
// } if let Some(command) = &args.command {
// } if args.parse {
// } parse_and_display_errors(command);
} else {
run_and_display_errors(command);
}
} else if let Some(path) = &args.path {
let source = read_to_string(path).expect("Failed to read file");
if args.parse {
parse_and_display_errors(&source);
} else {
run_and_display_errors(&source);
}
}
}
fn parse_and_display_errors(source: &str) {
match parse(source) {
Ok(chunk) => println!("{}", chunk.disassemble("Dust CLI Input")),
Err(error) => {
eprintln!("{:?}", error);
}
}
}
fn run_and_display_errors(source: &str) {
match run(source) {
Ok(Some(value)) => println!("{}", value),
Ok(_) => {}
Err(error) => {
eprintln!("{}", error.report());
}
}
}