Refactor chunk disassembly output
This commit is contained in:
parent
743679371d
commit
d5fc68e466
@ -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');
|
} else {
|
||||||
|
(0, extra_space)
|
||||||
let center_and_style = |line: &str, style: bool| {
|
}
|
||||||
if style {
|
};
|
||||||
format!(
|
let content = if style_bold {
|
||||||
"│{line:^width$}│",
|
line_characters
|
||||||
line = line.bold(),
|
.iter()
|
||||||
width = self.width - 2
|
.collect::<String>()
|
||||||
)
|
.bold()
|
||||||
|
.to_string()
|
||||||
|
} else if style_dim {
|
||||||
|
line_characters
|
||||||
|
.iter()
|
||||||
|
.collect::<String>()
|
||||||
|
.dimmed()
|
||||||
|
.to_string()
|
||||||
} else {
|
} else {
|
||||||
format!("│{line:^width$}│", width = self.width - 2)
|
line_characters.iter().collect::<String>()
|
||||||
}
|
};
|
||||||
};
|
|
||||||
let mut push = |line: &str, style: bool| {
|
|
||||||
if line.lines().count() > 1 {
|
|
||||||
disassembly.push_str(line);
|
|
||||||
disassembly.push('\n');
|
|
||||||
|
|
||||||
return;
|
for _ in 0..indent {
|
||||||
|
disassembly.push_str("│ ");
|
||||||
}
|
}
|
||||||
|
|
||||||
for _ in 0..self.indent {
|
if add_border {
|
||||||
disassembly.push_str("│ ");
|
disassembly.push('│');
|
||||||
}
|
}
|
||||||
|
|
||||||
let line = center_and_style(line, style);
|
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(§ion_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(§ion_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();
|
||||||
@ -589,7 +669,7 @@ impl<'a> ChunkDisassembler<'a> {
|
|||||||
|
|
||||||
if self.styled && expected_length > actual_length {
|
if self.styled && expected_length > actual_length {
|
||||||
log::debug!(
|
log::debug!(
|
||||||
"Chunk disassembly was not optimized correctly, expected string length to be at least{expected_length}, got {actual_length}",
|
"Chunk disassembly was not optimized correctly, expected string length to be at least {expected_length}, got {actual_length}",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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();
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
Loading…
Reference in New Issue
Block a user