Make chunk disassembly the prettiest thing ever
This commit is contained in:
parent
9144257524
commit
5c54a5b9bd
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -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",
|
||||||
|
@ -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"
|
||||||
|
@ -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 {
|
||||||
let mut disassembled = String::new();
|
Self {
|
||||||
let mut longest_line = 0;
|
name,
|
||||||
|
chunk,
|
||||||
disassembled.push('\n');
|
width: 0,
|
||||||
disassembled.push_str(&DisassembledChunk::instructions_header());
|
styled: false,
|
||||||
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| {
|
pub fn disassemble(&self) -> String {
|
||||||
let mut centered = String::new();
|
let mut disassembled = String::new();
|
||||||
|
let mut push_centered_line = |section: &str| {
|
||||||
|
let width = self.width;
|
||||||
|
let is_header = section.contains('\n');
|
||||||
|
|
||||||
for line in section.lines() {
|
if is_header && self.styled {
|
||||||
centered.push_str(&format!("{line:^longest_line$}\n"));
|
disassembled.push('\n');
|
||||||
|
|
||||||
|
for line in section.lines() {
|
||||||
|
if line.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let centered = format!("{:^width$}\n", line.bold());
|
||||||
|
|
||||||
|
disassembled.push_str(¢ered);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let centered = format!("{section:^width$}\n");
|
||||||
|
|
||||||
|
disassembled.push_str(¢ered);
|
||||||
}
|
}
|
||||||
|
|
||||||
disassembled.push_str(¢ered);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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!(
|
||||||
|
@ -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",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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());
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user