From d5fc68e466640ac87652fbead2a6aba7f73a450a Mon Sep 17 00:00:00 2001 From: Jeff Date: Sun, 13 Oct 2024 02:33:58 -0400 Subject: [PATCH] Refactor chunk disassembly output --- dust-lang/src/chunk.rs | 232 +++++++++++++++++++++++------------ dust-lang/src/instruction.rs | 13 +- dust-lang/src/parser.rs | 12 +- dust-lang/src/vm.rs | 21 +++- 4 files changed, 174 insertions(+), 104 deletions(-) diff --git a/dust-lang/src/chunk.rs b/dust-lang/src/chunk.rs index bb64b64..51c4730 100644 --- a/dust-lang/src/chunk.rs +++ b/dust-lang/src/chunk.rs @@ -1,4 +1,7 @@ -use std::fmt::{self, Debug, Display, Formatter}; +use std::{ + f32::DIGITS, + fmt::{self, Debug, Display, Formatter}, +}; use colored::Colorize; use serde::{Deserialize, Serialize}; @@ -344,24 +347,17 @@ pub struct ChunkDisassembler<'a> { } impl<'a> ChunkDisassembler<'a> { - const INSTRUCTION_HEADER: [&'static str; 5] = [ - "", + const INSTRUCTION_HEADER: [&'static str; 4] = [ "Instructions", "------------", "INDEX BYTECODE OPERATION INFO JUMP POSITION", "----- -------- --------------- ------------------------- -------- --------", ]; - const CONSTANT_HEADER: [&'static str; 5] = [ - "", - "Constants", - "---------", - "INDEX VALUE ", - "----- ---------", - ]; + const CONSTANT_HEADER: [&'static str; 4] = + ["Constants", "---------", "INDEX VALUE", "----- -----"]; - const LOCAL_HEADER: [&'static str; 5] = [ - "", + const LOCAL_HEADER: [&'static str; 4] = [ "Locals", "------", "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 /// return the width of the longest line that the disassembler is guaranteed to produce. 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) } @@ -412,54 +408,136 @@ impl<'a> ChunkDisassembler<'a> { } pub fn disassemble(&self) -> String { - let mut disassembly = String::with_capacity(self.predict_length()); - let indent = "│ ".repeat(self.indent); - let top_border = "┌".to_string() + &"─".repeat(self.width - 2) + "┐"; + #[allow(clippy::too_many_arguments)] + fn push( + 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::>(); + 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); - disassembly.push_str(&top_border); - disassembly.push('\n'); - - let center_and_style = |line: &str, style: bool| { - if style { - format!( - "│{line:^width$}│", - line = line.bold(), - width = self.width - 2 - ) + if center { + (extra_space / 2, extra_space / 2 + extra_space % 2) + } else { + (0, extra_space) + } + }; + let content = if style_bold { + line_characters + .iter() + .collect::() + .bold() + .to_string() + } else if style_dim { + line_characters + .iter() + .collect::() + .dimmed() + .to_string() } else { - format!("│{line:^width$}│", width = self.width - 2) - } - }; - let mut push = |line: &str, style: bool| { - if line.lines().count() > 1 { - disassembly.push_str(line); - disassembly.push('\n'); + line_characters.iter().collect::() + }; - return; + for _ in 0..indent { + disassembly.push_str("│ "); } - for _ in 0..self.indent { - disassembly.push_str("│ "); + if add_border { + 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'); - }; - push(self.name, self.styled); + if !remainder.is_empty() { + push( + remainder.iter().collect::().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 { - let length = if source.len() < self.width { - source.len() - 2 - } else { - self.width - 2 - }; - let source_line = format!("\"{}\"", &source[..length]).dimmed(); - - push(&source_line, false); + push( + &source.replace("\n", ""), + &mut disassembly, + self.width, + self.indent, + false, + false, + true, + true, + ) } let info_line = format!( @@ -467,13 +545,21 @@ impl<'a> ChunkDisassembler<'a> { self.chunk.instructions.len(), self.chunk.constants.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 { - push(line, self.styled); + for line in &Self::INSTRUCTION_HEADER { + push_header(line, &mut disassembly); } 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}" ); - push(&instruction_display, false); + push_details(&instruction_display, &mut disassembly); } - for line in Self::LOCAL_HEADER { - push(line, self.styled); + push_border(§ion_border, &mut disassembly); + + for line in &Self::LOCAL_HEADER { + push_header(line, &mut disassembly); } for ( @@ -531,11 +619,13 @@ impl<'a> ChunkDisassembler<'a> { "{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(line, self.styled); + push_border(§ion_border, &mut disassembly); + + for line in &Self::CONSTANT_HEADER { + push_header(line, &mut disassembly); } for (index, value_option) in self.chunk.constants.iter().enumerate() { @@ -543,15 +633,9 @@ impl<'a> ChunkDisassembler<'a> { .as_ref() .map(|value| value.to_string()) .unwrap_or("empty".to_string()); - let trucated_length = value_display.len().min(self.width - 2); - let with_elipsis = trucated_length.saturating_sub(3); - let constant_display = if with_elipsis > self.width - 2 { - format!("{index:<5} {value_display:. ChunkDisassembler<'a> { .chunk() .disassembler("function") .styled(self.styled) - .indent(self.indent + 1) .width(self.width) + .indent(self.indent + 1) .disassemble(), ), Value::Primitive(_) => None, Value::Object(_) => None, }) { - push(&function_disassembly, false); + push_function_disassembly(&function_disassembly, &mut disassembly); } } - let indent = "│ ".repeat(self.indent); - let bottom_border = "└".to_string() + &"─".repeat(self.width - 2) + "┘"; - - disassembly.push_str(&indent); - disassembly.push_str(&bottom_border); + push_border(&bottom_border, &mut disassembly); let expected_length = self.predict_length(); let actual_length = disassembly.len(); @@ -589,7 +669,7 @@ impl<'a> ChunkDisassembler<'a> { if self.styled && expected_length > actual_length { 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}", ); } diff --git a/dust-lang/src/instruction.rs b/dust-lang/src/instruction.rs index 571c6aa..5d2e3d6 100644 --- a/dust-lang/src/instruction.rs +++ b/dust-lang/src/instruction.rs @@ -401,18 +401,7 @@ impl Instruction { "" }; - let constant = if let Some(chunk) = chunk { - 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}", - )) + Some(format!("R{register_index} = C{constant_index} {jump}",)) } Operation::LoadList => { let destination = self.a(); diff --git a/dust-lang/src/parser.rs b/dust-lang/src/parser.rs index 4af2625..bbbf828 100644 --- a/dust-lang/src/parser.rs +++ b/dust-lang/src/parser.rs @@ -1159,7 +1159,7 @@ impl<'src> Parser<'src> { self.advance()?; - let function_register = self.current_register; + let function_register = self.current_register - 1; let mut argument_count = 0; while !self.allow(Token::RightParenthesis)? { @@ -1167,16 +1167,8 @@ impl<'src> Parser<'src> { self.expect(Token::Comma)?; } - let register = self.current_register; - self.parse_expression()?; - - if self.current_register == register { - return Err(ParseError::ExpectedExpression { - found: self.previous_token.to_owned(), - position: self.previous_position, - }); - } + self.increment_register()?; argument_count += 1; } diff --git a/dust-lang/src/vm.rs b/dust-lang/src/vm.rs index 05ab315..be14c53 100644 --- a/dust-lang/src/vm.rs +++ b/dust-lang/src/vm.rs @@ -1,8 +1,8 @@ use std::mem::replace; use crate::{ - parse, value::Primitive, AnnotatedError, Chunk, ChunkError, DustError, Function, Identifier, - Instruction, Operation, Span, Value, ValueError, + parse, value::Primitive, AnnotatedError, Chunk, ChunkError, DustError, Identifier, Instruction, + Operation, Span, Value, ValueError, }; pub fn run(source: &str) -> Result, DustError> { @@ -371,12 +371,14 @@ impl Vm { Operation::Call => { let function_index = instruction.a(); let argument_count = instruction.b(); - let function = if let Value::Function(function) = - self.get(function_index, position)?.clone() - { + let value = self.get(function_index, position)?.clone(); + let function = if let Value::Function(function) = value { function } else { - todo!() + return Err(VmError::ExpectedFunction { + found: value, + position, + }); }; let mut function_vm = Vm::new(function.take_chunk()); let first_argument_index = function_index + 1; @@ -580,6 +582,10 @@ pub enum VmError { found: Value, position: Span, }, + ExpectedFunction { + found: Value, + position: Span, + }, RegisterIndexOutOfBounds { position: Span, }, @@ -627,6 +633,7 @@ impl AnnotatedError for VmError { Self::CannotMutateImmutableLocal { .. } => "Cannot mutate immutable variable", Self::EmptyRegister { .. } => "Empty register", Self::ExpectedBoolean { .. } => "Expected boolean", + Self::ExpectedFunction { .. } => "Expected function", Self::RegisterIndexOutOfBounds { .. } => "Register index out of bounds", Self::InvalidInstruction { .. } => "Invalid instruction", Self::SkippedRegister { .. } => "Skipped register", @@ -641,6 +648,7 @@ impl AnnotatedError for VmError { fn details(&self) -> Option { match self { Self::EmptyRegister { index, .. } => Some(format!("Register {index} is empty")), + Self::ExpectedFunction { found, .. } => Some(format!("{found} is not a function")), Self::UndefinedVariable { identifier, .. } => { Some(format!("{identifier} is not in scope")) } @@ -655,6 +663,7 @@ impl AnnotatedError for VmError { Self::CannotMutateImmutableLocal { position, .. } => *position, Self::EmptyRegister { position, .. } => *position, Self::ExpectedBoolean { position, .. } => *position, + Self::ExpectedFunction { position, .. } => *position, Self::RegisterIndexOutOfBounds { position } => *position, Self::InvalidInstruction { position, .. } => *position, Self::SkippedRegister { position, .. } => *position,