1
0

Make chunk disassembly the prettiest thing ever

This commit is contained in:
Jeff 2024-09-12 19:25:20 -04:00
parent 9144257524
commit 5c54a5b9bd
6 changed files with 144 additions and 111 deletions

1
Cargo.lock generated
View File

@ -168,6 +168,7 @@ name = "dust-lang"
version = "0.5.0" version = "0.5.0"
dependencies = [ dependencies = [
"annotate-snippets", "annotate-snippets",
"colored",
"env_logger", "env_logger",
"log", "log",
"rand", "rand",

View File

@ -10,6 +10,7 @@ repository.workspace = true
[dependencies] [dependencies]
annotate-snippets = "0.11.4" annotate-snippets = "0.11.4"
colored = "2.1.0"
env_logger = "0.11.3" env_logger = "0.11.3"
log = "0.4.22" log = "0.4.22"
rand = "0.8.5" rand = "0.8.5"

View File

@ -1,5 +1,9 @@
use std::fmt::{self, Debug, Display, Formatter}; use std::{
fmt::{self, Debug, Display, Formatter},
rc::Weak,
};
use colored::Colorize;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{AnnotatedError, Identifier, Instruction, Span, Value}; use crate::{AnnotatedError, Identifier, Instruction, Span, Value};
@ -161,6 +165,7 @@ impl Chunk {
.locals .locals
.get_mut(index) .get_mut(index)
.ok_or_else(|| ChunkError::LocalIndexOutOfBounds { index, position })?; .ok_or_else(|| ChunkError::LocalIndexOutOfBounds { index, position })?;
let value = value.into_reference();
local.value = Some(value); local.value = Some(value);
@ -189,8 +194,8 @@ impl Chunk {
self.locals.pop() self.locals.pop()
} }
pub fn disassemble<'a>(&self, name: &'a str) -> DisassembledChunk<'a> { pub fn disassembler<'a>(&'a self, name: &'a str) -> ChunkDisassembler<'a> {
DisassembledChunk::new(name, self) ChunkDisassembler::new(name, self)
} }
} }
@ -202,13 +207,24 @@ impl Default for Chunk {
impl Display for Chunk { impl Display for Chunk {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", self.disassemble("Chunk Display")) write!(
f,
"{}",
self.disassembler("Chunk Display")
.styled()
.width(80)
.disassemble()
)
} }
} }
impl Debug for Chunk { impl Debug for Chunk {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", self.disassemble("Chunk Debug Display")) write!(
f,
"{}",
self.disassembler("Chunk Debug Display").disassemble()
)
} }
} }
@ -239,70 +255,83 @@ impl Local {
} }
} }
pub struct DisassembledChunk<'a> { pub struct ChunkDisassembler<'a> {
name: &'a str, name: &'a str,
body: String, chunk: &'a Chunk,
width: usize,
styled: bool,
} }
impl DisassembledChunk<'_> { impl<'a> ChunkDisassembler<'a> {
pub fn new<'a>(name: &'a str, chunk: &Chunk) -> DisassembledChunk<'a> { pub fn new(name: &'a str, chunk: &'a Chunk) -> Self {
Self {
name,
chunk,
width: 0,
styled: false,
}
}
pub fn disassemble(&self) -> String {
let mut disassembled = String::new(); let mut disassembled = String::new();
let mut longest_line = 0; let mut push_centered_line = |section: &str| {
let width = self.width;
let is_header = section.contains('\n');
if is_header && self.styled {
disassembled.push('\n'); disassembled.push('\n');
disassembled.push_str(&DisassembledChunk::instructions_header());
disassembled.push('\n');
for (offset, (instruction, position)) in chunk.instructions.iter().enumerate() {
let position = position.to_string();
let operation = instruction.operation.to_string();
let info_option = instruction.disassembly_info(Some(chunk));
let instruction_display = if let Some(info) = info_option {
format!("{offset:<6} {operation:16} {info:20} {position:8}\n")
} else {
format!("{offset:<6} {operation:16} {:20} {position:8}\n", " ")
};
disassembled.push_str(&instruction_display);
let line_length = instruction_display.len();
if line_length > longest_line {
longest_line = line_length;
}
}
let mut push_centered = |section: &str| {
let mut centered = String::new();
for line in section.lines() { for line in section.lines() {
centered.push_str(&format!("{line:^longest_line$}\n")); if line.is_empty() {
continue;
} }
let centered = format!("{:^width$}\n", line.bold());
disassembled.push_str(&centered); disassembled.push_str(&centered);
}
} else {
let centered = format!("{section:^width$}\n");
disassembled.push_str(&centered);
}
}; };
push_centered("\n"); push_centered_line(&self.name_header());
push_centered(&mut DisassembledChunk::constant_header()); push_centered_line(self.instructions_header());
for (index, value_option) in chunk.constants.iter().enumerate() { for (offset, (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:<7} {operation:14} {info:20} {position:8}")
} else {
format!("{offset:<7} {operation:14} {:20} {position:8}", " ")
};
push_centered_line(&instruction_display);
}
push_centered_line(Self::constant_header());
for (index, value_option) in self.chunk.constants.iter().enumerate() {
let value_kind_display = match value_option { let value_kind_display = match value_option {
Some(Value::Raw(_)) => "RAW ", Some(Value::Raw(_)) => "RAW",
Some(Value::Reference(_)) => "REF ", Some(Value::Reference(_)) => "REF",
Some(Value::Mutable(_)) => "MUT ", Some(Value::Mutable(_)) => "MUT",
None => "EMPTY", None => "EMPTY",
}; };
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_else(|| "EMPTY".to_string());
let constant_display = format!("{index:<5} {value_kind_display:<4} {value_display:<5}"); let constant_display = format!("{index:<5} {value_kind_display:<5} {value_display:<5}");
push_centered(&constant_display); push_centered_line(&constant_display);
} }
push_centered("\n"); push_centered_line(Self::local_header());
push_centered(&mut DisassembledChunk::local_header());
for ( for (
index, index,
@ -311,12 +340,12 @@ impl DisassembledChunk<'_> {
depth, depth,
value, value,
}, },
) in chunk.locals.iter().enumerate() ) in self.chunk.locals.iter().enumerate()
{ {
let value_kind_display = match value { let value_kind_display = match value {
Some(Value::Raw(_)) => "RAW ", Some(Value::Raw(_)) => "RAW",
Some(Value::Reference(_)) => "REF ", Some(Value::Reference(_)) => "REF",
Some(Value::Mutable(_)) => "MUT ", Some(Value::Mutable(_)) => "MUT",
None => "EMPTY", None => "EMPTY",
}; };
let value_display = value let value_display = value
@ -327,76 +356,49 @@ impl DisassembledChunk<'_> {
let local_display = let local_display =
format!("{index:<5} {identifier_display:<10} {depth:<5} {value_kind_display:<4} {value_display:<5}"); format!("{index:<5} {identifier_display:<10} {depth:<5} {value_kind_display:<4} {value_display:<5}");
push_centered(&local_display); push_centered_line(&local_display);
} }
DisassembledChunk { disassembled
name,
body: disassembled,
}
} }
pub fn to_string_with_width(&self, width: usize) -> String { pub fn width(&mut self, width: usize) -> &mut Self {
let mut display = String::new(); self.width = width;
for line in self.to_string().lines() { self
display.push_str(&format!("{line:^width$}\n"));
} }
display pub fn styled(&mut self) -> &mut Self {
self.styled = true;
self
} }
fn name_header(&self) -> String { fn name_header(&self) -> String {
let name_length = self.name.len(); let name_length = self.name.len();
format!("{:^50}\n{:^50}", self.name, "=".repeat(name_length)) format!("\n{}\n{}\n", self.name, "=".repeat(name_length))
} }
fn instructions_header() -> String { fn instructions_header(&self) -> &'static str {
format!( "\nInstructions\n\
"{:^50}\n{:^50}\n{:<6} {:<16} {:<20} {}\n{} {} {} {}", ------------\n\
"Instructions", OFFSET OPERATION INFO POSITION\n\
"------------", ------- -------------- -------------------- --------\n"
"OFFSET",
"INSTRUCTION",
"INFO",
"POSITION",
"------",
"----------------",
"--------------------",
"--------"
)
} }
fn constant_header() -> String { fn constant_header() -> &'static str {
format!( "\nConstants\n\
"{}\n{}\n{:<5} {:<4} {}\n{} {} {}", ---------\n\
"Constants", "---------", "INDEX", "KIND", "VALUE", "-----", "----", "-----" INDEX KIND VALUE\n\
) ----- ----- -----\n"
} }
fn local_header() -> String { fn local_header() -> &'static str {
format!( "\nLocals\n\
"{}\n{}\n{:<5} {:<10} {:<5} {:<5} {}\n{} {} {} {} {}", ------\n\
"Locals", INDEX IDENTIFIER DEPTH KIND VALUE\n\
"------", ----- ---------- ----- ----- -----\n"
"INDEX",
"IDENTIFIER",
"DEPTH",
"KIND",
"VALUE",
"-----",
"----------",
"-----",
"-----",
"-----"
)
}
}
impl Display for DisassembledChunk<'_> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}\n{}", self.name_header(), self.body)
} }
} }

View File

@ -161,8 +161,19 @@ impl Instruction {
} }
Operation::DeclareLocal => { Operation::DeclareLocal => {
let local_index = u16::from_le_bytes([self.arguments[0], self.arguments[1]]); let local_index = u16::from_le_bytes([self.arguments[0], self.arguments[1]]);
let identifier_display = if let Some(chunk) = chunk {
match chunk.get_identifier(local_index as usize) {
Some(identifier) => identifier.to_string(),
None => "???".to_string(),
}
} else {
"???".to_string()
};
format!("L({}) = R({})", local_index, self.destination) format!(
"L({}) = R({}) {}",
local_index, self.destination, identifier_display
)
} }
Operation::GetLocal => { Operation::GetLocal => {
let local_index = u16::from_le_bytes([self.arguments[0], self.arguments[1]]); let local_index = u16::from_le_bytes([self.arguments[0], self.arguments[1]]);
@ -170,9 +181,20 @@ impl Instruction {
format!("R({}) = L({})", self.destination, local_index) format!("R({}) = L({})", self.destination, local_index)
} }
Operation::SetLocal => { Operation::SetLocal => {
let identifier_index = u16::from_le_bytes([self.arguments[0], self.arguments[1]]); let local_index = u16::from_le_bytes([self.arguments[0], self.arguments[1]]);
let identifier_display = if let Some(chunk) = chunk {
match chunk.get_identifier(local_index as usize) {
Some(identifier) => identifier.to_string(),
None => "???".to_string(),
}
} else {
"???".to_string()
};
format!("L({}) = R({})", identifier_index, self.destination) format!(
"L({}) = R({}) {}",
local_index, self.destination, identifier_display
)
} }
Operation::Add => { Operation::Add => {
format!( format!(

View File

@ -247,7 +247,7 @@ impl AnnotatedError for VmError {
Self::StackUnderflow { .. } => "Stack underflow", Self::StackUnderflow { .. } => "Stack underflow",
Self::UndeclaredVariable { .. } => "Undeclared variable", Self::UndeclaredVariable { .. } => "Undeclared variable",
Self::UndefinedVariable { .. } => "Undefined variable", Self::UndefinedVariable { .. } => "Undefined variable",
Self::Chunk(_) => "Chunk error", Self::Chunk(error) => error.description(),
Self::Value { .. } => "Value error", Self::Value { .. } => "Value error",
} }
} }

View File

@ -60,7 +60,14 @@ fn main() {
fn parse_and_display_errors(source: &str) { fn parse_and_display_errors(source: &str) {
match parse(source) { match parse(source) {
Ok(chunk) => println!("{}", chunk.disassemble("Dust CLI Input")), Ok(chunk) => println!(
"{}",
chunk
.disassembler("Dust CLI Input")
.styled()
.width(80)
.disassemble()
),
Err(error) => { Err(error) => {
eprintln!("{}", error.report()); eprintln!("{}", error.report());
} }