From 67e5de66648c23b7e3c701036e18542b1df63dd8 Mon Sep 17 00:00:00 2001 From: Jeff Date: Thu, 12 Sep 2024 00:39:31 -0400 Subject: [PATCH] Reimplement the Parser and VM with a register-based VM --- dust-lang/src/chunk.rs | 13 +- dust-lang/src/dust_error.rs | 20 ++- dust-lang/src/instruction.rs | 162 ++++++++++++++---------- dust-lang/src/lib.rs | 6 +- dust-lang/src/parser/mod.rs | 27 ++-- dust-lang/src/parser/tests.rs | 19 ++- dust-lang/src/vm.rs | 231 ++++++++++++++++++++++++++++++++++ dust-lang/tests/operations.rs | 21 ++++ dust-lang/tests/values.rs | 61 +++++++++ dust-shell/src/main.rs | 142 ++++++++++----------- 10 files changed, 539 insertions(+), 163 deletions(-) create mode 100644 dust-lang/src/vm.rs create mode 100644 dust-lang/tests/operations.rs create mode 100644 dust-lang/tests/values.rs diff --git a/dust-lang/src/chunk.rs b/dust-lang/src/chunk.rs index 4ee9c3c..cf0fd59 100644 --- a/dust-lang/src/chunk.rs +++ b/dust-lang/src/chunk.rs @@ -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("OFFSET POSITION INSTRUCTION\n"); - output.push_str("------ ------------ ------------\n"); + output.push_str("------ -------- ------------\n"); + output.push_str("OFFSET POSITION INSTRUCTION\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); diff --git a/dust-lang/src/dust_error.rs b/dust-lang/src/dust_error.rs index a83200f..2fe243e 100644 --- a/dust-lang/src/dust_error.rs +++ b/dust-lang/src/dust_error.rs @@ -1,6 +1,6 @@ use annotate_snippets::{Level, Renderer, Snippet}; -use crate::{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()); diff --git a/dust-lang/src/instruction.rs b/dust-lang/src/instruction.rs index 684a3ae..d7fae59 100644 --- a/dust-lang/src/instruction.rs +++ b/dust-lang/src/instruction.rs @@ -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; diff --git a/dust-lang/src/lib.rs b/dust-lang/src/lib.rs index 14271aa..04668ee 100644 --- a/dust-lang/src/lib.rs +++ b/dust-lang/src/lib.rs @@ -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); diff --git a/dust-lang/src/parser/mod.rs b/dust-lang/src/parser/mod.rs index b792d4c..4caeb3c 100644 --- a/dust-lang/src/parser/mod.rs +++ b/dust-lang/src/parser/mod.rs @@ -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, - ); - } + 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()), } } diff --git a/dust-lang/src/parser/tests.rs b/dust-lang/src/parser/tests.rs index eced36a..79ef460 100644 --- a/dust-lang/src/parser/tests.rs +++ b/dust-lang/src/parser/tests.rs @@ -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),], diff --git a/dust-lang/src/vm.rs b/dust-lang/src/vm.rs new file mode 100644 index 0000000..14659a3 --- /dev/null +++ b/dust-lang/src/vm.rs @@ -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, 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>, +} + +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, 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 { + 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 { + 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 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 { + 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, + } + } +} diff --git a/dust-lang/tests/operations.rs b/dust-lang/tests/operations.rs new file mode 100644 index 0000000..84838f3 --- /dev/null +++ b/dust-lang/tests/operations.rs @@ -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)))); +} diff --git a/dust-lang/tests/values.rs b/dust-lang/tests/values.rs new file mode 100644 index 0000000..28b5d60 --- /dev/null +++ b/dust-lang/tests/values.rs @@ -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!"))) + ); +} diff --git a/dust-shell/src/main.rs b/dust-shell/src/main.rs index 932f7e2..66bdaf3 100644 --- a/dust-shell/src/main.rs +++ b/dust-shell/src/main.rs @@ -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, +#[derive(Parser)] +struct Cli { + #[arg(short, long)] + command: Option, -// #[arg(short, long)] -// parse: bool, + #[arg(short, long)] + parse: bool, -// path: Option, -// } - -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, } -// 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()); + } + } +}