1
0

Refactor chunk disassembly output

This commit is contained in:
Jeff 2024-10-13 02:33:58 -04:00
parent 743679371d
commit d5fc68e466
4 changed files with 174 additions and 104 deletions

View File

@ -1,4 +1,7 @@
use std::fmt::{self, Debug, Display, Formatter}; use std::{
f32::DIGITS,
fmt::{self, Debug, Display, Formatter},
};
use colored::Colorize; use colored::Colorize;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -344,24 +347,17 @@ pub struct ChunkDisassembler<'a> {
} }
impl<'a> ChunkDisassembler<'a> { impl<'a> ChunkDisassembler<'a> {
const INSTRUCTION_HEADER: [&'static str; 5] = [ const INSTRUCTION_HEADER: [&'static str; 4] = [
"",
"Instructions", "Instructions",
"------------", "------------",
"INDEX BYTECODE OPERATION INFO JUMP POSITION", "INDEX BYTECODE OPERATION INFO JUMP POSITION",
"----- -------- --------------- ------------------------- -------- --------", "----- -------- --------------- ------------------------- -------- --------",
]; ];
const CONSTANT_HEADER: [&'static str; 5] = [ const CONSTANT_HEADER: [&'static str; 4] =
"", ["Constants", "---------", "INDEX VALUE", "----- -----"];
"Constants",
"---------",
"INDEX VALUE ",
"----- ---------",
];
const LOCAL_HEADER: [&'static str; 5] = [ const LOCAL_HEADER: [&'static str; 4] = [
"",
"Locals", "Locals",
"------", "------",
"INDEX IDENTIFIER TYPE MUTABLE DEPTH REGISTER", "INDEX IDENTIFIER TYPE MUTABLE DEPTH REGISTER",
@ -371,7 +367,7 @@ impl<'a> ChunkDisassembler<'a> {
/// The default width of the disassembly output. To correctly align the output, this should /// 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. /// return the width of the longest line that the disassembler is guaranteed to produce.
pub fn default_width() -> usize { pub fn default_width() -> usize {
let longest_line = Self::INSTRUCTION_HEADER[4]; let longest_line = Self::INSTRUCTION_HEADER[3];
longest_line.chars().count().max(80) longest_line.chars().count().max(80)
} }
@ -412,54 +408,136 @@ impl<'a> ChunkDisassembler<'a> {
} }
pub fn disassemble(&self) -> String { pub fn disassemble(&self) -> String {
let mut disassembly = String::with_capacity(self.predict_length()); #[allow(clippy::too_many_arguments)]
let indent = "".repeat(self.indent); fn push(
let top_border = "".to_string() + &"".repeat(self.width - 2) + ""; text: &str,
disassembly: &mut String,
width: usize,
indent: usize,
center: bool,
style_bold: bool,
style_dim: bool,
add_border: bool,
) {
let characters = text.chars().collect::<Vec<char>>();
let content_width = if add_border { width - 2 } else { width };
let (line_characters, remainder) = characters
.split_at_checked(content_width)
.unwrap_or((characters.as_slice(), &[]));
let (left_pad_length, right_pad_length) = {
let extra_space = content_width.saturating_sub(characters.len());
disassembly.push_str(&indent); if center {
disassembly.push_str(&top_border); (extra_space / 2, extra_space / 2 + extra_space % 2)
disassembly.push('\n');
let center_and_style = |line: &str, style: bool| {
if style {
format!(
"│{line:^width$}│",
line = line.bold(),
width = self.width - 2
)
} else { } else {
format!("{line:^width$}", width = self.width - 2) (0, extra_space)
} }
}; };
let mut push = |line: &str, style: bool| { let content = if style_bold {
if line.lines().count() > 1 { line_characters
disassembly.push_str(line); .iter()
disassembly.push('\n'); .collect::<String>()
.bold()
.to_string()
} else if style_dim {
line_characters
.iter()
.collect::<String>()
.dimmed()
.to_string()
} else {
line_characters.iter().collect::<String>()
};
return; for _ in 0..indent {
}
for _ in 0..self.indent {
disassembly.push_str(""); disassembly.push_str("");
} }
let line = center_and_style(line, style); if add_border {
disassembly.push('│');
}
disassembly.push_str(&" ".repeat(left_pad_length));
disassembly.push_str(&content);
disassembly.push_str(&" ".repeat(right_pad_length));
if add_border {
disassembly.push('│');
}
disassembly.push_str(&line);
disassembly.push('\n'); disassembly.push('\n');
};
push(self.name, self.styled); if !remainder.is_empty() {
push(
remainder.iter().collect::<String>().as_str(),
disassembly,
width,
indent,
center,
style_bold,
style_dim,
add_border,
);
}
}
let push_header = |header: &str, disassembly: &mut String| {
push(
header,
disassembly,
self.width,
self.indent,
true,
self.styled,
false,
true,
);
};
let push_details = |details: &str, disassembly: &mut String| {
push(
details,
disassembly,
self.width,
self.indent,
true,
false,
false,
true,
);
};
let push_border = |border: &str, disassembly: &mut String| {
push(
border,
disassembly,
self.width,
self.indent,
false,
false,
false,
false,
)
};
let push_function_disassembly = |function_disassembly: &str, disassembly: &mut String| {
disassembly.push_str(function_disassembly);
};
let mut disassembly = String::new();
let top_border = "".to_string() + &"".repeat(self.width - 2) + "";
let section_border = "".to_string() + &"".repeat(self.width - 2) + "";
let bottom_border = "".to_string() + &"".repeat(self.width - 2) + "";
push_border(&top_border, &mut disassembly);
push_header(self.name, &mut disassembly);
if let Some(source) = self.source { if let Some(source) = self.source {
let length = if source.len() < self.width { push(
source.len() - 2 &source.replace("\n", ""),
} else { &mut disassembly,
self.width - 2 self.width,
}; self.indent,
let source_line = format!("\"{}\"", &source[..length]).dimmed(); false,
false,
push(&source_line, false); true,
true,
)
} }
let info_line = format!( let info_line = format!(
@ -467,13 +545,21 @@ impl<'a> ChunkDisassembler<'a> {
self.chunk.instructions.len(), self.chunk.instructions.len(),
self.chunk.constants.len(), self.chunk.constants.len(),
self.chunk.locals.len() self.chunk.locals.len()
) );
.dimmed();
push(&info_line, false); push(
&info_line,
&mut disassembly,
self.width,
self.indent,
true,
false,
false,
true,
);
for line in Self::INSTRUCTION_HEADER { for line in &Self::INSTRUCTION_HEADER {
push(line, self.styled); push_header(line, &mut disassembly);
} }
for (index, (instruction, position)) in self.chunk.instructions.iter().enumerate() { for (index, (instruction, position)) in self.chunk.instructions.iter().enumerate() {
@ -504,11 +590,13 @@ impl<'a> ChunkDisassembler<'a> {
"{index:<5} {bytecode:<08X} {operation:15} {info:25} {jump_offset:8} {position:8}" "{index:<5} {bytecode:<08X} {operation:15} {info:25} {jump_offset:8} {position:8}"
); );
push(&instruction_display, false); push_details(&instruction_display, &mut disassembly);
} }
for line in Self::LOCAL_HEADER { push_border(&section_border, &mut disassembly);
push(line, self.styled);
for line in &Self::LOCAL_HEADER {
push_header(line, &mut disassembly);
} }
for ( for (
@ -531,11 +619,13 @@ impl<'a> ChunkDisassembler<'a> {
"{index:<5} {identifier_display:10} {type_display:8} {mutable:7} {depth:<5} {register_index:8}" "{index:<5} {identifier_display:10} {type_display:8} {mutable:7} {depth:<5} {register_index:8}"
); );
push(&local_display, false); push_details(&local_display, &mut disassembly);
} }
for line in Self::CONSTANT_HEADER { push_border(&section_border, &mut disassembly);
push(line, self.styled);
for line in &Self::CONSTANT_HEADER {
push_header(line, &mut disassembly);
} }
for (index, value_option) in self.chunk.constants.iter().enumerate() { for (index, value_option) in self.chunk.constants.iter().enumerate() {
@ -543,15 +633,9 @@ impl<'a> ChunkDisassembler<'a> {
.as_ref() .as_ref()
.map(|value| value.to_string()) .map(|value| value.to_string())
.unwrap_or("empty".to_string()); .unwrap_or("empty".to_string());
let trucated_length = value_display.len().min(self.width - 2); let constant_display = format!("{index:<5} {value_display:<5}");
let with_elipsis = trucated_length.saturating_sub(3);
let constant_display = if with_elipsis > self.width - 2 {
format!("{index:<5} {value_display:.<trucated_length$.with_elipsis$}")
} else {
format!("{index:<5} {value_display:<trucated_length$}")
};
push(&constant_display, false); push_details(&constant_display, &mut disassembly);
if let Some(function_disassembly) = if let Some(function_disassembly) =
value_option.as_ref().and_then(|value| match value { value_option.as_ref().and_then(|value| match value {
@ -560,23 +644,19 @@ impl<'a> ChunkDisassembler<'a> {
.chunk() .chunk()
.disassembler("function") .disassembler("function")
.styled(self.styled) .styled(self.styled)
.indent(self.indent + 1)
.width(self.width) .width(self.width)
.indent(self.indent + 1)
.disassemble(), .disassemble(),
), ),
Value::Primitive(_) => None, Value::Primitive(_) => None,
Value::Object(_) => None, Value::Object(_) => None,
}) })
{ {
push(&function_disassembly, false); push_function_disassembly(&function_disassembly, &mut disassembly);
} }
} }
let indent = "".repeat(self.indent); push_border(&bottom_border, &mut disassembly);
let bottom_border = "".to_string() + &"".repeat(self.width - 2) + "";
disassembly.push_str(&indent);
disassembly.push_str(&bottom_border);
let expected_length = self.predict_length(); let expected_length = self.predict_length();
let actual_length = disassembly.len(); let actual_length = disassembly.len();

View File

@ -401,18 +401,7 @@ impl Instruction {
"" ""
}; };
let constant = if let Some(chunk) = chunk { Some(format!("R{register_index} = C{constant_index} {jump}",))
match chunk.get_constant(constant_index, Span(0, 0)) {
Ok(value) => value.to_string(),
Err(error) => format!("{error:?}"),
}
} else {
"???".to_string()
};
Some(format!(
"R{register_index} = C{constant_index} ({constant}) {jump}",
))
} }
Operation::LoadList => { Operation::LoadList => {
let destination = self.a(); let destination = self.a();

View File

@ -1159,7 +1159,7 @@ impl<'src> Parser<'src> {
self.advance()?; self.advance()?;
let function_register = self.current_register; let function_register = self.current_register - 1;
let mut argument_count = 0; let mut argument_count = 0;
while !self.allow(Token::RightParenthesis)? { while !self.allow(Token::RightParenthesis)? {
@ -1167,16 +1167,8 @@ impl<'src> Parser<'src> {
self.expect(Token::Comma)?; self.expect(Token::Comma)?;
} }
let register = self.current_register;
self.parse_expression()?; self.parse_expression()?;
self.increment_register()?;
if self.current_register == register {
return Err(ParseError::ExpectedExpression {
found: self.previous_token.to_owned(),
position: self.previous_position,
});
}
argument_count += 1; argument_count += 1;
} }

View File

@ -1,8 +1,8 @@
use std::mem::replace; use std::mem::replace;
use crate::{ use crate::{
parse, value::Primitive, AnnotatedError, Chunk, ChunkError, DustError, Function, Identifier, parse, value::Primitive, AnnotatedError, Chunk, ChunkError, DustError, Identifier, Instruction,
Instruction, Operation, Span, Value, ValueError, Operation, Span, Value, ValueError,
}; };
pub fn run(source: &str) -> Result<Option<Value>, DustError> { pub fn run(source: &str) -> Result<Option<Value>, DustError> {
@ -371,12 +371,14 @@ impl Vm {
Operation::Call => { Operation::Call => {
let function_index = instruction.a(); let function_index = instruction.a();
let argument_count = instruction.b(); let argument_count = instruction.b();
let function = if let Value::Function(function) = let value = self.get(function_index, position)?.clone();
self.get(function_index, position)?.clone() let function = if let Value::Function(function) = value {
{
function function
} else { } else {
todo!() return Err(VmError::ExpectedFunction {
found: value,
position,
});
}; };
let mut function_vm = Vm::new(function.take_chunk()); let mut function_vm = Vm::new(function.take_chunk());
let first_argument_index = function_index + 1; let first_argument_index = function_index + 1;
@ -580,6 +582,10 @@ pub enum VmError {
found: Value, found: Value,
position: Span, position: Span,
}, },
ExpectedFunction {
found: Value,
position: Span,
},
RegisterIndexOutOfBounds { RegisterIndexOutOfBounds {
position: Span, position: Span,
}, },
@ -627,6 +633,7 @@ impl AnnotatedError for VmError {
Self::CannotMutateImmutableLocal { .. } => "Cannot mutate immutable variable", Self::CannotMutateImmutableLocal { .. } => "Cannot mutate immutable variable",
Self::EmptyRegister { .. } => "Empty register", Self::EmptyRegister { .. } => "Empty register",
Self::ExpectedBoolean { .. } => "Expected boolean", Self::ExpectedBoolean { .. } => "Expected boolean",
Self::ExpectedFunction { .. } => "Expected function",
Self::RegisterIndexOutOfBounds { .. } => "Register index out of bounds", Self::RegisterIndexOutOfBounds { .. } => "Register index out of bounds",
Self::InvalidInstruction { .. } => "Invalid instruction", Self::InvalidInstruction { .. } => "Invalid instruction",
Self::SkippedRegister { .. } => "Skipped register", Self::SkippedRegister { .. } => "Skipped register",
@ -641,6 +648,7 @@ impl AnnotatedError for VmError {
fn details(&self) -> Option<String> { fn details(&self) -> Option<String> {
match self { match self {
Self::EmptyRegister { index, .. } => Some(format!("Register {index} is empty")), Self::EmptyRegister { index, .. } => Some(format!("Register {index} is empty")),
Self::ExpectedFunction { found, .. } => Some(format!("{found} is not a function")),
Self::UndefinedVariable { identifier, .. } => { Self::UndefinedVariable { identifier, .. } => {
Some(format!("{identifier} is not in scope")) Some(format!("{identifier} is not in scope"))
} }
@ -655,6 +663,7 @@ impl AnnotatedError for VmError {
Self::CannotMutateImmutableLocal { position, .. } => *position, Self::CannotMutateImmutableLocal { position, .. } => *position,
Self::EmptyRegister { position, .. } => *position, Self::EmptyRegister { position, .. } => *position,
Self::ExpectedBoolean { position, .. } => *position, Self::ExpectedBoolean { position, .. } => *position,
Self::ExpectedFunction { position, .. } => *position,
Self::RegisterIndexOutOfBounds { position } => *position, Self::RegisterIndexOutOfBounds { position } => *position,
Self::InvalidInstruction { position, .. } => *position, Self::InvalidInstruction { position, .. } => *position,
Self::SkippedRegister { position, .. } => *position, Self::SkippedRegister { position, .. } => *position,