Reimplement the Parser and VM with a register-based VM
This commit is contained in:
parent
7b055d79b5
commit
67e5de6664
@ -181,21 +181,20 @@ impl Chunk {
|
||||
let name_length = name.len();
|
||||
let buffer_length = 34_usize.saturating_sub(name_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));
|
||||
|
||||
output.push_str(&name_line);
|
||||
output.push_str(&name_underline);
|
||||
output.push_str("\n Code \n");
|
||||
output.push_str("------ ------------ ------------\n");
|
||||
output.push_str("------ -------- ------------\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() {
|
||||
let display = format!(
|
||||
"{offset:4} {:12} {}\n",
|
||||
position.to_string(),
|
||||
instruction.disassemble(self, offset)
|
||||
"{offset:04} {position} {}\n",
|
||||
instruction.disassemble(self)
|
||||
);
|
||||
|
||||
output.push_str(&display);
|
||||
|
@ -1,6 +1,6 @@
|
||||
use annotate_snippets::{Level, Renderer, Snippet};
|
||||
|
||||
use crate::{LexError, ParseError, Span};
|
||||
use crate::{vm::VmError, LexError, ParseError, Span};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum DustError<'src> {
|
||||
@ -12,6 +12,10 @@ pub enum DustError<'src> {
|
||||
error: ParseError,
|
||||
source: &'src str,
|
||||
},
|
||||
Runtime {
|
||||
error: VmError,
|
||||
source: &'src str,
|
||||
},
|
||||
}
|
||||
|
||||
impl<'src> DustError<'src> {
|
||||
@ -20,6 +24,20 @@ impl<'src> DustError<'src> {
|
||||
let renderer = Renderer::styled();
|
||||
|
||||
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 } => {
|
||||
let position = error.position();
|
||||
let label = format!("Parse error: {}", error.description());
|
||||
|
@ -1,16 +1,18 @@
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
use crate::{Chunk, Span};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct Instruction {
|
||||
opcode: OpCode,
|
||||
to_register: u8,
|
||||
arguments: [u8; 2],
|
||||
pub operation: Operation,
|
||||
pub to_register: u8,
|
||||
pub arguments: [u8; 2],
|
||||
}
|
||||
|
||||
impl Instruction {
|
||||
pub fn r#move(to_register: u8, from_register: u8) -> Instruction {
|
||||
Instruction {
|
||||
opcode: OpCode::Move,
|
||||
operation: Operation::Move,
|
||||
to_register,
|
||||
arguments: [from_register, 0],
|
||||
}
|
||||
@ -18,7 +20,7 @@ impl Instruction {
|
||||
|
||||
pub fn close(to_register: u8) -> Instruction {
|
||||
Instruction {
|
||||
opcode: OpCode::Close,
|
||||
operation: Operation::Close,
|
||||
to_register,
|
||||
arguments: [0, 0],
|
||||
}
|
||||
@ -26,7 +28,7 @@ impl Instruction {
|
||||
|
||||
pub fn load_constant(to_register: u8, constant_index: u16) -> Instruction {
|
||||
Instruction {
|
||||
opcode: OpCode::LoadConstant,
|
||||
operation: Operation::LoadConstant,
|
||||
to_register,
|
||||
arguments: constant_index.to_le_bytes(),
|
||||
}
|
||||
@ -34,7 +36,7 @@ impl Instruction {
|
||||
|
||||
pub fn declare_variable(to_register: u8, variable_index: u16) -> Instruction {
|
||||
Instruction {
|
||||
opcode: OpCode::DeclareVariable,
|
||||
operation: Operation::DeclareVariable,
|
||||
to_register,
|
||||
arguments: variable_index.to_le_bytes(),
|
||||
}
|
||||
@ -42,7 +44,7 @@ impl Instruction {
|
||||
|
||||
pub fn get_variable(to_register: u8, variable_index: u16) -> Instruction {
|
||||
Instruction {
|
||||
opcode: OpCode::GetVariable,
|
||||
operation: Operation::GetVariable,
|
||||
to_register,
|
||||
arguments: variable_index.to_le_bytes(),
|
||||
}
|
||||
@ -50,7 +52,7 @@ impl Instruction {
|
||||
|
||||
pub fn set_variable(from_register: u8, variable_index: u16) -> Instruction {
|
||||
Instruction {
|
||||
opcode: OpCode::SetVariable,
|
||||
operation: Operation::SetVariable,
|
||||
to_register: from_register,
|
||||
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 {
|
||||
Instruction {
|
||||
opcode: OpCode::Add,
|
||||
operation: Operation::Add,
|
||||
to_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 {
|
||||
Instruction {
|
||||
opcode: OpCode::Subtract,
|
||||
operation: Operation::Subtract,
|
||||
to_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 {
|
||||
Instruction {
|
||||
opcode: OpCode::Multiply,
|
||||
operation: Operation::Multiply,
|
||||
to_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 {
|
||||
Instruction {
|
||||
opcode: OpCode::Divide,
|
||||
operation: Operation::Divide,
|
||||
to_register,
|
||||
arguments: [left_register, right_register],
|
||||
}
|
||||
@ -90,7 +92,7 @@ impl Instruction {
|
||||
|
||||
pub fn negate(to_register: u8, from_register: u8) -> Instruction {
|
||||
Instruction {
|
||||
opcode: OpCode::Negate,
|
||||
operation: Operation::Negate,
|
||||
to_register,
|
||||
arguments: [from_register, 0],
|
||||
}
|
||||
@ -98,84 +100,97 @@ impl Instruction {
|
||||
|
||||
pub fn r#return() -> Instruction {
|
||||
Instruction {
|
||||
opcode: OpCode::Return,
|
||||
operation: Operation::Return,
|
||||
to_register: 0,
|
||||
arguments: [0, 0],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn disassemble(&self, chunk: &Chunk, offset: usize) -> String {
|
||||
match self.opcode {
|
||||
OpCode::Move => format!(
|
||||
"{:04} MOVE R{} R{}",
|
||||
offset, self.to_register, self.arguments[0]
|
||||
),
|
||||
OpCode::Close => {
|
||||
format!("{:04} CLOSE R{}", offset, self.to_register)
|
||||
}
|
||||
OpCode::LoadConstant => {
|
||||
pub fn disassemble(&self, chunk: &Chunk) -> String {
|
||||
match self.operation {
|
||||
Operation::LoadConstant => {
|
||||
let constant_index = u16::from_le_bytes(self.arguments);
|
||||
let constant_display = match chunk.get_constant(constant_index, Span(0, 0)) {
|
||||
Ok(value) => value.to_string(),
|
||||
Err(error) => format!("{:?}", error),
|
||||
};
|
||||
|
||||
format!(
|
||||
"{:04} LOAD_CONSTANT R{} C{} {}",
|
||||
offset, self.to_register, constant_index, constant_display
|
||||
)
|
||||
format!("{self} {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]]);
|
||||
|
||||
format!(
|
||||
"{:04} DECLARE_VARIABLE V{} R{}",
|
||||
offset, variable_index, self.to_register
|
||||
write!(
|
||||
f,
|
||||
"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]]);
|
||||
|
||||
format!(
|
||||
"{:04} GET_VARIABLE R{} V{}",
|
||||
offset, self.to_register, variable_index
|
||||
)
|
||||
write!(f, "GET_VARIABLE R{} V{}", self.to_register, variable_index)
|
||||
}
|
||||
OpCode::SetVariable => {
|
||||
Operation::SetVariable => {
|
||||
let variable_index = u16::from_le_bytes([self.arguments[0], self.arguments[1]]);
|
||||
|
||||
format!(
|
||||
"{:04} SET_VARIABLE V{} R{}",
|
||||
offset, variable_index, self.to_register
|
||||
write!(f, "SET_VARIABLE V{} R{}", 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!(
|
||||
"{:04} ADD R{} = R{} + R{}",
|
||||
offset, self.to_register, self.arguments[0], self.arguments[1]
|
||||
),
|
||||
OpCode::Subtract => format!(
|
||||
"{:04} SUBTRACT R{} = R{} - R{}",
|
||||
offset, self.to_register, self.arguments[0], self.arguments[1]
|
||||
),
|
||||
OpCode::Multiply => format!(
|
||||
"{:04} MULTIPLY R{} = R{} * R{}",
|
||||
offset, 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]
|
||||
),
|
||||
OpCode::Negate => format!(
|
||||
"{:04} NEGATE R{} = !R{}",
|
||||
offset, self.to_register, self.arguments[0]
|
||||
),
|
||||
OpCode::Return => format!("{:04} RETURN", offset),
|
||||
Operation::Subtract => {
|
||||
write!(
|
||||
f,
|
||||
"SUBTRACT R{} = R{} - R{}",
|
||||
self.to_register, self.arguments[0], self.arguments[1]
|
||||
)
|
||||
}
|
||||
Operation::Multiply => {
|
||||
write!(
|
||||
f,
|
||||
"MULTIPLY R{} = R{} * R{}",
|
||||
self.to_register, self.arguments[0], self.arguments[1]
|
||||
)
|
||||
}
|
||||
Operation::Divide => {
|
||||
write!(
|
||||
f,
|
||||
"DIVIDE R{} = R{} / R{}",
|
||||
self.to_register, self.arguments[0], self.arguments[1]
|
||||
)
|
||||
}
|
||||
Operation::Negate => {
|
||||
write!(f, "NEGATE R{} = !R{}", self.to_register, self.arguments[0])
|
||||
}
|
||||
Operation::Return => {
|
||||
write!(f, "RETURN")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
enum OpCode {
|
||||
pub enum Operation {
|
||||
// Stack manipulation
|
||||
Move,
|
||||
Close,
|
||||
@ -201,6 +216,25 @@ enum OpCode {
|
||||
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)]
|
||||
mod tests {
|
||||
use std::mem::size_of;
|
||||
|
@ -8,6 +8,7 @@ mod parser;
|
||||
mod token;
|
||||
mod r#type;
|
||||
mod value;
|
||||
mod vm;
|
||||
|
||||
use std::fmt::Display;
|
||||
|
||||
@ -15,12 +16,13 @@ pub use chunk::{Chunk, ChunkError};
|
||||
pub use constructor::Constructor;
|
||||
pub use dust_error::{AnnotatedError, DustError};
|
||||
pub use identifier::Identifier;
|
||||
pub use instruction::Instruction;
|
||||
pub use instruction::{Instruction, Operation};
|
||||
pub use lexer::{lex, LexError, Lexer};
|
||||
pub use parser::{parse, ParseError, Parser};
|
||||
pub use r#type::{EnumType, FunctionType, RangeableType, StructType, Type, TypeConflict};
|
||||
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)]
|
||||
pub struct Span(pub usize, pub usize);
|
||||
|
@ -214,6 +214,7 @@ impl<'src> Parser<'src> {
|
||||
}
|
||||
};
|
||||
|
||||
self.increment_register()?;
|
||||
self.parse_expression()?;
|
||||
self.emit_instruction(byte, operator_position);
|
||||
|
||||
@ -234,8 +235,8 @@ impl<'src> Parser<'src> {
|
||||
} else {
|
||||
self.current_register
|
||||
};
|
||||
let left_register = to_register - 1;
|
||||
let right_register = to_register - 2;
|
||||
let left_register = to_register - 2;
|
||||
let right_register = to_register - 1;
|
||||
let byte = match operator {
|
||||
TokenKind::Plus => Instruction::add(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);
|
||||
|
||||
Ok(())
|
||||
@ -319,19 +321,10 @@ impl<'src> Parser<'src> {
|
||||
}
|
||||
|
||||
self.chunk.end_scope();
|
||||
|
||||
while self
|
||||
.chunk
|
||||
.identifiers()
|
||||
.iter()
|
||||
.next_back()
|
||||
.map_or(false, |local| local.depth > self.chunk.scope_depth())
|
||||
{
|
||||
self.emit_instruction(
|
||||
Instruction::close(self.current_register),
|
||||
self.current_position,
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -392,7 +385,7 @@ impl<'src> Parser<'src> {
|
||||
let identifier_index = self.chunk.declare_variable(identifier, position)?;
|
||||
|
||||
self.emit_instruction(
|
||||
Instruction::set_variable(self.current_register, identifier_index),
|
||||
Instruction::declare_variable(self.current_register, identifier_index),
|
||||
position,
|
||||
);
|
||||
self.increment_register()?;
|
||||
@ -714,7 +707,7 @@ impl AnnotatedError for ParseError {
|
||||
}
|
||||
Self::RegisterOverflow { .. } => None,
|
||||
Self::Chunk(error) => error.details(),
|
||||
Self::Lex(error) => Some(error.to_string()),
|
||||
Self::Lex(error) => error.details(),
|
||||
Self::ParseIntError { error, .. } => Some(error.to_string()),
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,24 @@ fn add() {
|
||||
vec![
|
||||
(Instruction::load_constant(0, 0), Span(0, 1)),
|
||||
(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)),
|
||||
],
|
||||
vec![Value::integer(1), Value::integer(2),],
|
||||
|
231
dust-lang/src/vm.rs
Normal file
231
dust-lang/src/vm.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
21
dust-lang/tests/operations.rs
Normal file
21
dust-lang/tests/operations.rs
Normal 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
61
dust-lang/tests/values.rs
Normal 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!")))
|
||||
);
|
||||
}
|
@ -1,78 +1,78 @@
|
||||
// use std::{fs::read_to_string, io::Write};
|
||||
use std::{fs::read_to_string, io::Write};
|
||||
|
||||
// use clap::Parser;
|
||||
// use colored::Colorize;
|
||||
// use dust_lang::{parse, run};
|
||||
// use log::Level;
|
||||
use clap::Parser;
|
||||
use colored::Colorize;
|
||||
use dust_lang::{parse, run};
|
||||
use log::Level;
|
||||
|
||||
// #[derive(Parser)]
|
||||
// struct Cli {
|
||||
// #[arg(short, long)]
|
||||
// command: Option<String>,
|
||||
#[derive(Parser)]
|
||||
struct Cli {
|
||||
#[arg(short, long)]
|
||||
command: Option<String>,
|
||||
|
||||
// #[arg(short, long)]
|
||||
// parse: bool,
|
||||
#[arg(short, long)]
|
||||
parse: bool,
|
||||
|
||||
// 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);
|
||||
// }
|
||||
// }
|
||||
path: Option<String>,
|
||||
}
|
||||
|
||||
// fn parse_and_display_errors(source: &str) {
|
||||
// match parse(source) {
|
||||
// Ok(chunk) => println!("{}", chunk.disassemble("Dust CLI Input")),
|
||||
// Err(error) => {
|
||||
// eprintln!("{:?}", error);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
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();
|
||||
|
||||
// fn run_and_display_errors(source: &str) {
|
||||
// match run(source) {
|
||||
// Ok(Some(value)) => println!("{}", value),
|
||||
// Ok(_) => {}
|
||||
// Err(error) => {
|
||||
// eprintln!("{}", error.report());
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
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) {
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user