diff --git a/dust-lang/src/chunk.rs b/dust-lang/src/chunk.rs index 44a98b6..2a10247 100644 --- a/dust-lang/src/chunk.rs +++ b/dust-lang/src/chunk.rs @@ -262,7 +262,7 @@ impl Local { pub struct ChunkDisassembler<'a> { name: &'a str, chunk: &'a Chunk, - width: usize, + width: Option, styled: bool, } @@ -271,16 +271,16 @@ impl<'a> ChunkDisassembler<'a> { "", "Instructions", "------------", - "OFFSET OPERATION INFO POSITION", - "------ -------------- ------------------------- --------", + "INDEX OPERATION INFO POSITION", + "----- -------------- ------------------------- --------", ]; const CONSTANT_HEADER: [&'static str; 5] = [ "", "Constants", "---------", - "INDEX KIND VALUE", - "----- ----- -----", + "INDEX VALUE ", + "----- ---------", ]; const LOCAL_HEADER: [&'static str; 5] = [ @@ -291,21 +291,29 @@ impl<'a> ChunkDisassembler<'a> { "----- ---------- ------- ----- --------", ]; - /// The default width of the disassembly output. To correctly align the output, this should be - /// set to the width of the longest line that the disassembler is guaranteed to produce. - const DEFAULT_WIDTH: usize = Self::INSTRUCTION_HEADER[4].len() + 1; + /// The default width of the disassembly output. To correctly align the output, this should + /// return the width of the longest line that the disassembler is guaranteed to produce. + pub fn default_width(styled: bool) -> usize { + let longest_line = Self::INSTRUCTION_HEADER[4]; + + if styled { + longest_line.bold().chars().count() + } else { + longest_line.chars().count() + } + } pub fn new(name: &'a str, chunk: &'a Chunk) -> Self { Self { name, chunk, - width: Self::DEFAULT_WIDTH, + width: None, styled: false, } } pub fn width(&mut self, width: usize) -> &mut Self { - self.width = width; + self.width = Some(width); self } @@ -317,7 +325,11 @@ impl<'a> ChunkDisassembler<'a> { } pub fn disassemble(&self) -> String { - let center = |line: &str| format!("{line:^width$}\n", width = self.width); + let width = self + .width + .unwrap_or_else(|| Self::default_width(self.styled)) + + 1; + let center = |line: &str| format!("{line:^width$}\n"); let style = |line: String| { if self.styled { line.bold().to_string() @@ -326,10 +338,10 @@ impl<'a> ChunkDisassembler<'a> { } }; - let mut disassembled = String::with_capacity(self.predict_length()); + let mut disassembly = String::with_capacity(self.predict_length()); let name_line = style(center(self.name)); - disassembled.push_str(&name_line); + disassembly.push_str(&name_line); let info_line = center(&format!( "{} instructions, {} constants, {} locals", @@ -339,52 +351,53 @@ impl<'a> ChunkDisassembler<'a> { )); let styled_info_line = { if self.styled { - info_line.bold().dimmed().to_string() + info_line.dimmed().to_string() } else { info_line } }; - disassembled.push_str(&styled_info_line); + disassembly.push_str(&styled_info_line); for line in Self::INSTRUCTION_HEADER { - disassembled.push_str(&style(center(line))); + disassembly.push_str(&style(center(line))); } - for (offset, (instruction, position)) in self.chunk.instructions.iter().enumerate() { + for (index, (instruction, position)) in self.chunk.instructions.iter().enumerate() { let position = position.to_string(); let operation = instruction.operation().to_string(); let info_option = instruction.disassembly_info(Some(self.chunk)); + let instruction_display = if let Some(info) = info_option { - format!("{offset:<6} {operation:14} {info:25} {position:8}") + format!("{index:<5} {operation:14} {info:25} {position:8}") } else { - format!("{offset:<6} {operation:14} {:25} {position:8}", " ") + format!("{index:<5} {operation:14} {:25} {position:8}", " ") }; - disassembled.push_str(¢er(&instruction_display)); + disassembly.push_str(¢er(&instruction_display)); } for line in Self::CONSTANT_HEADER { - disassembled.push_str(&style(center(line))); + disassembly.push_str(&style(center(line))); } for (index, value_option) in self.chunk.constants.iter().enumerate() { - let value_kind_display = if let Some(value) = value_option { - value.kind().to_string() - } else { - "empty".to_string() - }; let value_display = value_option .as_ref() .map(|value| value.to_string()) - .unwrap_or_else(|| "empty".to_string()); - let constant_display = format!("{index:<5} {value_kind_display:<5} {value_display:<5}"); + .unwrap_or("empty".to_string()); + let elipsis = if value_display.len() > 9 { "..." } else { "" }; + let constant_display = if value_display.len() > 9 { + format!("{index:<5} {value_display:^7.7}{elipsis}") + } else { + format!("{index:<5} {value_display:10}") + }; - disassembled.push_str(¢er(&constant_display)); + disassembly.push_str(¢er(&constant_display)); } for line in Self::LOCAL_HEADER { - disassembled.push_str(&style(center(line))); + disassembly.push_str(&style(center(line))); } for ( @@ -403,14 +416,14 @@ impl<'a> ChunkDisassembler<'a> { .unwrap_or_else(|| "empty".to_string()); let identifier_display = identifier.as_str(); let local_display = format!( - "{index:<5} {identifier_display:<10} {mutable:<7} {depth:<5} {register_display:<8}" + "{index:<5} {identifier_display:10} {mutable:7} {depth:<5} {register_display:8}" ); - disassembled.push_str(¢er(&local_display)); + disassembly.push_str(¢er(&local_display)); } let expected_length = self.predict_length(); - let actual_length = disassembled.len(); + let actual_length = disassembly.len(); if !self.styled && expected_length != actual_length { log::debug!( @@ -424,7 +437,7 @@ impl<'a> ChunkDisassembler<'a> { ); } - disassembled + disassembly } /// Predicts the capacity of the disassembled output. This is used to pre-allocate the string @@ -440,15 +453,21 @@ impl<'a> ChunkDisassembler<'a> { /// the ANSI escape codes will make the result too low. It still works as a lower bound in that /// case. fn predict_length(&self) -> usize { - const EXTRA_LINES: usize = 2; // There is one empty line after the name of the chunk + const EXTRA_LINES: usize = 2; // There is one info line and one empty line after the name - let static_line_count = - Self::INSTRUCTION_HEADER.len() + Self::CONSTANT_HEADER.len() + Self::LOCAL_HEADER.len(); + let static_line_count = Self::INSTRUCTION_HEADER.len() + + Self::CONSTANT_HEADER.len() + + Self::LOCAL_HEADER.len() + + EXTRA_LINES; let dynamic_line_count = self.chunk.instructions.len() + self.chunk.constants.len() + self.chunk.locals.len(); - let total_line_count = static_line_count + dynamic_line_count + EXTRA_LINES; + let total_line_count = static_line_count + dynamic_line_count; + let width = self + .width + .unwrap_or_else(|| Self::default_width(self.styled)) + + 1; - total_line_count * (self.width + 1) + total_line_count * width } } diff --git a/dust-lang/src/instruction.rs b/dust-lang/src/instruction.rs index fd668f9..a79c417 100644 --- a/dust-lang/src/instruction.rs +++ b/dust-lang/src/instruction.rs @@ -1,6 +1,6 @@ use std::fmt::{self, Display, Formatter}; -use crate::{Chunk, Span}; +use crate::{Chunk, Operation, Span}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Instruction(u32); @@ -24,6 +24,16 @@ impl Instruction { instruction } + pub fn load_boolean(to_register: u8, value: bool, skip: bool) -> Instruction { + let mut instruction = Instruction(Operation::LoadBoolean as u32); + + instruction.set_destination(to_register); + instruction.set_first_argument(if value { 1 } else { 0 }); + instruction.set_second_argument(if skip { 1 } else { 0 }); + + instruction + } + pub fn load_constant(to_register: u8, constant_index: u8) -> Instruction { let mut instruction = Instruction(Operation::LoadConstant as u32); @@ -251,6 +261,17 @@ impl Instruction { format!("R({from_register})..=R({to_register})") } + Operation::LoadBoolean => { + let to_register = self.destination(); + let boolean = if self.first_argument() == 0 { + "false" + } else { + "true" + }; + let skip = self.second_argument() != 0; + + format!("R({to_register}) = {boolean}; if {skip} ip++",) + } Operation::LoadConstant => { let constant_index = self.first_argument(); @@ -405,144 +426,6 @@ impl Display for Instruction { } } -const MOVE: u8 = 0b0000_0000; -const CLOSE: u8 = 0b000_0001; -const LOAD_CONSTANT: u8 = 0b0000_0010; -const LOAD_LIST: u8 = 0b0000_0011; -const DECLARE_LOCAL: u8 = 0b0000_0100; -const GET_LOCAL: u8 = 0b0000_0101; -const SET_LOCAL: u8 = 0b0000_0110; -const ADD: u8 = 0b0000_0111; -const SUBTRACT: u8 = 0b0000_1000; -const MULTIPLY: u8 = 0b0000_1001; -const MODULO: u8 = 0b0000_1010; -const AND: u8 = 0b0000_1011; -const OR: u8 = 0b0000_1100; -const DIVIDE: u8 = 0b0000_1101; -const NEGATE: u8 = 0b0000_1110; -const NOT: u8 = 0b0000_1111; -const RETURN: u8 = 0b0001_0000; - -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum Operation { - // Stack manipulation - Move = MOVE as isize, - Close = CLOSE as isize, - - // Value loading - LoadConstant = LOAD_CONSTANT as isize, - LoadList = LOAD_LIST as isize, - - // Variables - DefineLocal = DECLARE_LOCAL as isize, - GetLocal = GET_LOCAL as isize, - SetLocal = SET_LOCAL as isize, - - // Binary operations - Add = ADD as isize, - Subtract = SUBTRACT as isize, - Multiply = MULTIPLY as isize, - Divide = DIVIDE as isize, - Modulo = MODULO as isize, - And = AND as isize, - Or = OR as isize, - - // Unary operations - Negate = NEGATE as isize, - Not = NOT as isize, - - // Control flow - Return = RETURN as isize, -} - -impl Operation { - pub fn is_binary(&self) -> bool { - matches!( - self, - Operation::Add - | Operation::Subtract - | Operation::Multiply - | Operation::Divide - | Operation::Modulo - | Operation::And - | Operation::Or - ) - } -} - -impl From for Operation { - fn from(byte: u8) -> Self { - match byte { - MOVE => Operation::Move, - CLOSE => Operation::Close, - LOAD_CONSTANT => Operation::LoadConstant, - LOAD_LIST => Operation::LoadList, - DECLARE_LOCAL => Operation::DefineLocal, - GET_LOCAL => Operation::GetLocal, - SET_LOCAL => Operation::SetLocal, - ADD => Operation::Add, - SUBTRACT => Operation::Subtract, - MULTIPLY => Operation::Multiply, - DIVIDE => Operation::Divide, - MODULO => Operation::Modulo, - AND => Operation::And, - OR => Operation::Or, - NEGATE => Operation::Negate, - NOT => Operation::Not, - RETURN => Operation::Return, - _ => panic!("Invalid operation byte: {}", byte), - } - } -} - -impl From for u8 { - fn from(operation: Operation) -> Self { - match operation { - Operation::Move => MOVE, - Operation::Close => CLOSE, - Operation::LoadConstant => LOAD_CONSTANT, - Operation::LoadList => LOAD_LIST, - Operation::DefineLocal => DECLARE_LOCAL, - Operation::GetLocal => GET_LOCAL, - Operation::SetLocal => SET_LOCAL, - Operation::Add => ADD, - Operation::Subtract => SUBTRACT, - Operation::Multiply => MULTIPLY, - Operation::Divide => DIVIDE, - Operation::Modulo => MODULO, - Operation::And => AND, - Operation::Or => OR, - Operation::Negate => NEGATE, - Operation::Not => NOT, - Operation::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::LoadList => write!(f, "LOAD_LIST"), - Operation::DefineLocal => write!(f, "DEFINE_LOCAL"), - Operation::GetLocal => write!(f, "GET_LOCAL"), - Operation::SetLocal => write!(f, "SET_LOCAL"), - Operation::Add => write!(f, "ADD"), - Operation::Subtract => write!(f, "SUBTRACT"), - Operation::Multiply => write!(f, "MULTIPLY"), - Operation::Divide => write!(f, "DIVIDE"), - Operation::Modulo => write!(f, "MODULO"), - Operation::And => write!(f, "AND"), - Operation::Or => write!(f, "OR"), - Operation::Negate => write!(f, "NEGATE"), - Operation::Not => write!(f, "NOT"), - Operation::Return => write!(f, "RETURN"), - } - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/dust-lang/src/lib.rs b/dust-lang/src/lib.rs index 9739235..85a12eb 100644 --- a/dust-lang/src/lib.rs +++ b/dust-lang/src/lib.rs @@ -4,6 +4,7 @@ mod dust_error; mod identifier; mod instruction; mod lexer; +mod operation; mod parser; mod token; mod r#type; @@ -16,8 +17,9 @@ pub use chunk::{Chunk, ChunkError, Local}; pub use constructor::Constructor; pub use dust_error::{AnnotatedError, DustError}; pub use identifier::Identifier; -pub use instruction::{Instruction, Operation}; +pub use instruction::Instruction; pub use lexer::{lex, LexError, Lexer}; +pub use operation::Operation; pub use parser::{parse, ParseError, Parser}; pub use r#type::{EnumType, FunctionType, RangeableType, StructType, Type, TypeConflict}; pub use token::{Token, TokenKind, TokenOwned}; diff --git a/dust-lang/src/operation.rs b/dust-lang/src/operation.rs new file mode 100644 index 0000000..61af3a5 --- /dev/null +++ b/dust-lang/src/operation.rs @@ -0,0 +1,150 @@ +use std::fmt::{self, Display, Formatter}; + +const MOVE: u8 = 0b0000_0000; +const CLOSE: u8 = 0b000_0001; +const LOAD_BOOLEAN: u8 = 0b0000_0010; +const LOAD_CONSTANT: u8 = 0b0000_0011; +const LOAD_LIST: u8 = 0b0000_0100; +const DECLARE_LOCAL: u8 = 0b0000_0101; +const GET_LOCAL: u8 = 0b0000_0110; +const SET_LOCAL: u8 = 0b0000_0111; +const ADD: u8 = 0b0000_1000; +const SUBTRACT: u8 = 0b0000_1001; +const MULTIPLY: u8 = 0b0000_1010; +const MODULO: u8 = 0b0000_1011; +const AND: u8 = 0b0000_1100; +const OR: u8 = 0b0000_1101; +const DIVIDE: u8 = 0b0000_1110; +const NEGATE: u8 = 0b0000_1111; +const NOT: u8 = 0b0001_0000; +const RETURN: u8 = 0b0001_0001; + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum Operation { + // Stack manipulation + Move = MOVE as isize, + Close = CLOSE as isize, + + // Value loading + LoadBoolean = LOAD_BOOLEAN as isize, + LoadConstant = LOAD_CONSTANT as isize, + LoadList = LOAD_LIST as isize, + + // Variables + DefineLocal = DECLARE_LOCAL as isize, + GetLocal = GET_LOCAL as isize, + SetLocal = SET_LOCAL as isize, + + // Binary operations + Add = ADD as isize, + Subtract = SUBTRACT as isize, + Multiply = MULTIPLY as isize, + Divide = DIVIDE as isize, + Modulo = MODULO as isize, + And = AND as isize, + Or = OR as isize, + + // Unary operations + Negate = NEGATE as isize, + Not = NOT as isize, + + // Control flow + Return = RETURN as isize, +} + +impl Operation { + pub fn is_binary(&self) -> bool { + matches!( + self, + Operation::Add + | Operation::Subtract + | Operation::Multiply + | Operation::Divide + | Operation::Modulo + | Operation::And + | Operation::Or + ) + } +} + +impl From for Operation { + fn from(byte: u8) -> Self { + match byte { + MOVE => Operation::Move, + CLOSE => Operation::Close, + LOAD_BOOLEAN => Operation::LoadBoolean, + LOAD_CONSTANT => Operation::LoadConstant, + LOAD_LIST => Operation::LoadList, + DECLARE_LOCAL => Operation::DefineLocal, + GET_LOCAL => Operation::GetLocal, + SET_LOCAL => Operation::SetLocal, + ADD => Operation::Add, + SUBTRACT => Operation::Subtract, + MULTIPLY => Operation::Multiply, + DIVIDE => Operation::Divide, + MODULO => Operation::Modulo, + AND => Operation::And, + OR => Operation::Or, + NEGATE => Operation::Negate, + NOT => Operation::Not, + RETURN => Operation::Return, + _ => { + if cfg!(test) { + panic!("Invalid operation byte: {}", byte) + } else { + Operation::Return + } + } + } + } +} + +impl From for u8 { + fn from(operation: Operation) -> Self { + match operation { + Operation::Move => MOVE, + Operation::Close => CLOSE, + Operation::LoadBoolean => LOAD_BOOLEAN, + Operation::LoadConstant => LOAD_CONSTANT, + Operation::LoadList => LOAD_LIST, + Operation::DefineLocal => DECLARE_LOCAL, + Operation::GetLocal => GET_LOCAL, + Operation::SetLocal => SET_LOCAL, + Operation::Add => ADD, + Operation::Subtract => SUBTRACT, + Operation::Multiply => MULTIPLY, + Operation::Divide => DIVIDE, + Operation::Modulo => MODULO, + Operation::And => AND, + Operation::Or => OR, + Operation::Negate => NEGATE, + Operation::Not => NOT, + Operation::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::LoadBoolean => write!(f, "LOAD_BOOLEAN"), + Operation::LoadConstant => write!(f, "LOAD_CONSTANT"), + Operation::LoadList => write!(f, "LOAD_LIST"), + Operation::DefineLocal => write!(f, "DEFINE_LOCAL"), + Operation::GetLocal => write!(f, "GET_LOCAL"), + Operation::SetLocal => write!(f, "SET_LOCAL"), + Operation::Add => write!(f, "ADD"), + Operation::Subtract => write!(f, "SUBTRACT"), + Operation::Multiply => write!(f, "MULTIPLY"), + Operation::Divide => write!(f, "DIVIDE"), + Operation::Modulo => write!(f, "MODULO"), + Operation::And => write!(f, "AND"), + Operation::Or => write!(f, "OR"), + Operation::Negate => write!(f, "NEGATE"), + Operation::Not => write!(f, "NOT"), + Operation::Return => write!(f, "RETURN"), + } + } +} diff --git a/dust-lang/src/parser/mod.rs b/dust-lang/src/parser/mod.rs index 6bf2b7a..28c4d0d 100644 --- a/dust-lang/src/parser/mod.rs +++ b/dust-lang/src/parser/mod.rs @@ -148,9 +148,11 @@ impl<'src> Parser<'src> { self.advance()?; let boolean = text.parse::().unwrap(); - let value = Value::boolean(boolean); - self.emit_constant(value)?; + self.emit_instruction( + Instruction::load_boolean(self.current_register, boolean, false), + self.previous_position, + ); } Ok(()) diff --git a/dust-lang/src/value.rs b/dust-lang/src/value.rs index 38e69a4..f8cb65b 100644 --- a/dust-lang/src/value.rs +++ b/dust-lang/src/value.rs @@ -1287,7 +1287,7 @@ impl Display for ValueData { ValueData::Range(range_value) => { write!(f, "{range_value}") } - ValueData::String(string) => write!(f, "\"{string}\""), + ValueData::String(string) => write!(f, "{string}"), ValueData::Struct(r#struct) => write!(f, "{struct}"), ValueData::Tuple(fields) => { write!(f, "(")?; diff --git a/dust-lang/src/vm.rs b/dust-lang/src/vm.rs index cf98355..953fa29 100644 --- a/dust-lang/src/vm.rs +++ b/dust-lang/src/vm.rs @@ -50,12 +50,6 @@ impl Vm { vm.chunk .take_constant(instruction.second_argument(), position)? } else { - if let Operation::GetLocal = instruction.operation() { - println!("GetLocal: {}", instruction); - } - - println!("{}", instruction); - vm.clone(instruction.second_argument(), position)? }; @@ -81,6 +75,18 @@ impl Vm { self.register_stack[register_index as usize] = None; } } + Operation::LoadBoolean => { + let to_register = instruction.destination(); + let boolean = instruction.first_argument() != 0; + let skip = instruction.second_argument() != 0; + let value = Value::boolean(boolean); + + self.insert(value, to_register, position)?; + + if skip { + self.ip += 1; + } + } Operation::LoadConstant => { let to_register = instruction.destination(); let from_constant = instruction.first_argument();