Add LoadBoolean; Refactor; Improve disassembly output

This commit is contained in:
Jeff 2024-09-18 13:42:32 -04:00
parent 85b95a56aa
commit 915340fbdb
7 changed files with 250 additions and 188 deletions

View File

@ -262,7 +262,7 @@ impl Local {
pub struct ChunkDisassembler<'a> { pub struct ChunkDisassembler<'a> {
name: &'a str, name: &'a str,
chunk: &'a Chunk, chunk: &'a Chunk,
width: usize, width: Option<usize>,
styled: bool, styled: bool,
} }
@ -271,16 +271,16 @@ impl<'a> ChunkDisassembler<'a> {
"", "",
"Instructions", "Instructions",
"------------", "------------",
"OFFSET OPERATION INFO POSITION", "INDEX OPERATION INFO POSITION",
"------ -------------- ------------------------- --------", "----- -------------- ------------------------- --------",
]; ];
const CONSTANT_HEADER: [&'static str; 5] = [ const CONSTANT_HEADER: [&'static str; 5] = [
"", "",
"Constants", "Constants",
"---------", "---------",
"INDEX KIND VALUE", "INDEX VALUE ",
"----- ----- -----", "----- ---------",
]; ];
const LOCAL_HEADER: [&'static str; 5] = [ 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 /// The default width of the disassembly output. To correctly align the output, this should
/// set to the width of the longest line that the disassembler is guaranteed to produce. /// return the width of the longest line that the disassembler is guaranteed to produce.
const DEFAULT_WIDTH: usize = Self::INSTRUCTION_HEADER[4].len() + 1; 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 { pub fn new(name: &'a str, chunk: &'a Chunk) -> Self {
Self { Self {
name, name,
chunk, chunk,
width: Self::DEFAULT_WIDTH, width: None,
styled: false, styled: false,
} }
} }
pub fn width(&mut self, width: usize) -> &mut Self { pub fn width(&mut self, width: usize) -> &mut Self {
self.width = width; self.width = Some(width);
self self
} }
@ -317,7 +325,11 @@ impl<'a> ChunkDisassembler<'a> {
} }
pub fn disassemble(&self) -> String { 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| { let style = |line: String| {
if self.styled { if self.styled {
line.bold().to_string() 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)); let name_line = style(center(self.name));
disassembled.push_str(&name_line); disassembly.push_str(&name_line);
let info_line = center(&format!( let info_line = center(&format!(
"{} instructions, {} constants, {} locals", "{} instructions, {} constants, {} locals",
@ -339,52 +351,53 @@ impl<'a> ChunkDisassembler<'a> {
)); ));
let styled_info_line = { let styled_info_line = {
if self.styled { if self.styled {
info_line.bold().dimmed().to_string() info_line.dimmed().to_string()
} else { } else {
info_line info_line
} }
}; };
disassembled.push_str(&styled_info_line); disassembly.push_str(&styled_info_line);
for line in Self::INSTRUCTION_HEADER { 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 position = position.to_string();
let operation = instruction.operation().to_string(); let operation = instruction.operation().to_string();
let info_option = instruction.disassembly_info(Some(self.chunk)); let info_option = instruction.disassembly_info(Some(self.chunk));
let instruction_display = if let Some(info) = info_option { 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 { } else {
format!("{offset:<6} {operation:14} {:25} {position:8}", " ") format!("{index:<5} {operation:14} {:25} {position:8}", " ")
}; };
disassembled.push_str(&center(&instruction_display)); disassembly.push_str(&center(&instruction_display));
} }
for line in Self::CONSTANT_HEADER { 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() { 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 let value_display = value_option
.as_ref() .as_ref()
.map(|value| value.to_string()) .map(|value| value.to_string())
.unwrap_or_else(|| "empty".to_string()); .unwrap_or("empty".to_string());
let constant_display = format!("{index:<5} {value_kind_display:<5} {value_display:<5}"); 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(&center(&constant_display)); disassembly.push_str(&center(&constant_display));
} }
for line in Self::LOCAL_HEADER { for line in Self::LOCAL_HEADER {
disassembled.push_str(&style(center(line))); disassembly.push_str(&style(center(line)));
} }
for ( for (
@ -403,14 +416,14 @@ impl<'a> ChunkDisassembler<'a> {
.unwrap_or_else(|| "empty".to_string()); .unwrap_or_else(|| "empty".to_string());
let identifier_display = identifier.as_str(); let identifier_display = identifier.as_str();
let local_display = format!( 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(&center(&local_display)); disassembly.push_str(&center(&local_display));
} }
let expected_length = self.predict_length(); let expected_length = self.predict_length();
let actual_length = disassembled.len(); let actual_length = disassembly.len();
if !self.styled && expected_length != actual_length { if !self.styled && expected_length != actual_length {
log::debug!( 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 /// 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 /// the ANSI escape codes will make the result too low. It still works as a lower bound in that
/// case. /// case.
fn predict_length(&self) -> usize { 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 = let static_line_count = Self::INSTRUCTION_HEADER.len()
Self::INSTRUCTION_HEADER.len() + Self::CONSTANT_HEADER.len() + Self::LOCAL_HEADER.len(); + Self::CONSTANT_HEADER.len()
+ Self::LOCAL_HEADER.len()
+ EXTRA_LINES;
let dynamic_line_count = let dynamic_line_count =
self.chunk.instructions.len() + self.chunk.constants.len() + self.chunk.locals.len(); 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
} }
} }

View File

@ -1,6 +1,6 @@
use std::fmt::{self, Display, Formatter}; use std::fmt::{self, Display, Formatter};
use crate::{Chunk, Span}; use crate::{Chunk, Operation, Span};
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Instruction(u32); pub struct Instruction(u32);
@ -24,6 +24,16 @@ impl Instruction {
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 { pub fn load_constant(to_register: u8, constant_index: u8) -> Instruction {
let mut instruction = Instruction(Operation::LoadConstant as u32); let mut instruction = Instruction(Operation::LoadConstant as u32);
@ -251,6 +261,17 @@ impl Instruction {
format!("R({from_register})..=R({to_register})") 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 => { Operation::LoadConstant => {
let constant_index = self.first_argument(); 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<u8> 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<Operation> 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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@ -4,6 +4,7 @@ mod dust_error;
mod identifier; mod identifier;
mod instruction; mod instruction;
mod lexer; mod lexer;
mod operation;
mod parser; mod parser;
mod token; mod token;
mod r#type; mod r#type;
@ -16,8 +17,9 @@ pub use chunk::{Chunk, ChunkError, Local};
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, Operation}; pub use instruction::Instruction;
pub use lexer::{lex, LexError, Lexer}; pub use lexer::{lex, LexError, Lexer};
pub use operation::Operation;
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};

150
dust-lang/src/operation.rs Normal file
View File

@ -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<u8> 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<Operation> 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"),
}
}
}

View File

@ -148,9 +148,11 @@ impl<'src> Parser<'src> {
self.advance()?; self.advance()?;
let boolean = text.parse::<bool>().unwrap(); let boolean = text.parse::<bool>().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(()) Ok(())

View File

@ -1287,7 +1287,7 @@ impl Display for ValueData {
ValueData::Range(range_value) => { ValueData::Range(range_value) => {
write!(f, "{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::Struct(r#struct) => write!(f, "{struct}"),
ValueData::Tuple(fields) => { ValueData::Tuple(fields) => {
write!(f, "(")?; write!(f, "(")?;

View File

@ -50,12 +50,6 @@ impl Vm {
vm.chunk vm.chunk
.take_constant(instruction.second_argument(), position)? .take_constant(instruction.second_argument(), position)?
} else { } else {
if let Operation::GetLocal = instruction.operation() {
println!("GetLocal: {}", instruction);
}
println!("{}", instruction);
vm.clone(instruction.second_argument(), position)? vm.clone(instruction.second_argument(), position)?
}; };
@ -81,6 +75,18 @@ impl Vm {
self.register_stack[register_index as usize] = None; 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 => { Operation::LoadConstant => {
let to_register = instruction.destination(); let to_register = instruction.destination();
let from_constant = instruction.first_argument(); let from_constant = instruction.first_argument();