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"
dependencies = [
"annotate-snippets",
"colored",
"env_logger",
"log",
"rand",

View File

@ -10,6 +10,7 @@ repository.workspace = true
[dependencies]
annotate-snippets = "0.11.4"
colored = "2.1.0"
env_logger = "0.11.3"
log = "0.4.22"
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 crate::{AnnotatedError, Identifier, Instruction, Span, Value};
@ -161,6 +165,7 @@ impl Chunk {
.locals
.get_mut(index)
.ok_or_else(|| ChunkError::LocalIndexOutOfBounds { index, position })?;
let value = value.into_reference();
local.value = Some(value);
@ -189,8 +194,8 @@ impl Chunk {
self.locals.pop()
}
pub fn disassemble<'a>(&self, name: &'a str) -> DisassembledChunk<'a> {
DisassembledChunk::new(name, self)
pub fn disassembler<'a>(&'a self, name: &'a str) -> ChunkDisassembler<'a> {
ChunkDisassembler::new(name, self)
}
}
@ -202,13 +207,24 @@ impl Default for Chunk {
impl Display for Chunk {
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 {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", self.disassemble("Chunk Debug Display"))
write!(
f,
"{}",
self.disassembler("Chunk Debug Display").disassemble()
)
}
}
@ -239,53 +255,67 @@ impl Local {
}
}
pub struct DisassembledChunk<'a> {
pub struct ChunkDisassembler<'a> {
name: &'a str,
body: String,
chunk: &'a Chunk,
width: usize,
styled: bool,
}
impl DisassembledChunk<'_> {
pub fn new<'a>(name: &'a str, chunk: &Chunk) -> DisassembledChunk<'a> {
impl<'a> ChunkDisassembler<'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 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_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() {
centered.push_str(&format!("{line:^longest_line$}\n"));
if line.is_empty() {
continue;
}
let centered = format!("{:^width$}\n", line.bold());
disassembled.push_str(&centered);
}
} else {
let centered = format!("{section:^width$}\n");
disassembled.push_str(&centered);
}
};
push_centered("\n");
push_centered(&mut DisassembledChunk::constant_header());
push_centered_line(&self.name_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 {
Some(Value::Raw(_)) => "RAW",
Some(Value::Reference(_)) => "REF",
@ -296,13 +326,12 @@ impl DisassembledChunk<'_> {
.as_ref()
.map(|value| value.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(&mut DisassembledChunk::local_header());
push_centered_line(Self::local_header());
for (
index,
@ -311,7 +340,7 @@ impl DisassembledChunk<'_> {
depth,
value,
},
) in chunk.locals.iter().enumerate()
) in self.chunk.locals.iter().enumerate()
{
let value_kind_display = match value {
Some(Value::Raw(_)) => "RAW",
@ -327,76 +356,49 @@ impl DisassembledChunk<'_> {
let local_display =
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 {
name,
body: disassembled,
}
disassembled
}
pub fn to_string_with_width(&self, width: usize) -> String {
let mut display = String::new();
pub fn width(&mut self, width: usize) -> &mut Self {
self.width = width;
for line in self.to_string().lines() {
display.push_str(&format!("{line:^width$}\n"));
self
}
display
pub fn styled(&mut self) -> &mut Self {
self.styled = true;
self
}
fn name_header(&self) -> String {
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 {
format!(
"{:^50}\n{:^50}\n{:<6} {:<16} {:<20} {}\n{} {} {} {}",
"Instructions",
"------------",
"OFFSET",
"INSTRUCTION",
"INFO",
"POSITION",
"------",
"----------------",
"--------------------",
"--------"
)
fn instructions_header(&self) -> &'static str {
"\nInstructions\n\
------------\n\
OFFSET OPERATION INFO POSITION\n\
------- -------------- -------------------- --------\n"
}
fn constant_header() -> String {
format!(
"{}\n{}\n{:<5} {:<4} {}\n{} {} {}",
"Constants", "---------", "INDEX", "KIND", "VALUE", "-----", "----", "-----"
)
fn constant_header() -> &'static str {
"\nConstants\n\
---------\n\
INDEX KIND VALUE\n\
----- ----- -----\n"
}
fn local_header() -> String {
format!(
"{}\n{}\n{:<5} {:<10} {:<5} {:<5} {}\n{} {} {} {} {}",
"Locals",
"------",
"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)
fn local_header() -> &'static str {
"\nLocals\n\
------\n\
INDEX IDENTIFIER DEPTH KIND VALUE\n\
----- ---------- ----- ----- -----\n"
}
}

View File

@ -161,8 +161,19 @@ impl Instruction {
}
Operation::DeclareLocal => {
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 => {
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)
}
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 => {
format!(

View File

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

View File

@ -60,7 +60,14 @@ fn main() {
fn parse_and_display_errors(source: &str) {
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) => {
eprintln!("{}", error.report());
}