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> {
name: &'a str,
chunk: &'a Chunk,
width: usize,
width: Option<usize>,
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(&center(&instruction_display));
disassembly.push_str(&center(&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(&center(&constant_display));
disassembly.push_str(&center(&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(&center(&local_display));
disassembly.push_str(&center(&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
}
}

View File

@ -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<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)]
mod tests {
use super::*;

View File

@ -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};

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()?;
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(())

View File

@ -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, "(")?;

View File

@ -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();