Write docs; Improve disassembly output
This commit is contained in:
parent
dc9b451b37
commit
a997665d1a
@ -58,7 +58,7 @@ pub struct Chunk {
|
|||||||
locals: Vec<Local>,
|
locals: Vec<Local>,
|
||||||
|
|
||||||
current_scope: Scope,
|
current_scope: Scope,
|
||||||
scope_index: u8,
|
block_index: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Chunk {
|
impl Chunk {
|
||||||
@ -70,7 +70,7 @@ impl Chunk {
|
|||||||
constants: Vec::new(),
|
constants: Vec::new(),
|
||||||
locals: Vec::new(),
|
locals: Vec::new(),
|
||||||
current_scope: Scope::default(),
|
current_scope: Scope::default(),
|
||||||
scope_index: 0,
|
block_index: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,7 +87,7 @@ impl Chunk {
|
|||||||
constants,
|
constants,
|
||||||
locals,
|
locals,
|
||||||
current_scope: Scope::default(),
|
current_scope: Scope::default(),
|
||||||
scope_index: 0,
|
block_index: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,8 +192,8 @@ impl Chunk {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn begin_scope(&mut self) {
|
pub fn begin_scope(&mut self) {
|
||||||
self.scope_index += 1;
|
self.block_index += 1;
|
||||||
self.current_scope.index = self.scope_index;
|
self.current_scope.block_index = self.block_index;
|
||||||
self.current_scope.depth += 1;
|
self.current_scope.depth += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -201,9 +201,9 @@ impl Chunk {
|
|||||||
self.current_scope.depth -= 1;
|
self.current_scope.depth -= 1;
|
||||||
|
|
||||||
if self.current_scope.depth == 0 {
|
if self.current_scope.depth == 0 {
|
||||||
self.current_scope.index = 0;
|
self.current_scope.block_index = 0;
|
||||||
} else {
|
} else {
|
||||||
self.current_scope.index -= 1;
|
self.current_scope.block_index -= 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -334,16 +334,27 @@ impl PartialEq for Chunk {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A scoped variable.
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||||
pub struct Local {
|
pub struct Local {
|
||||||
|
/// The index of the identifier in the constants table.
|
||||||
pub identifier_index: u8,
|
pub identifier_index: u8,
|
||||||
|
|
||||||
|
/// The expected type of the local's value.
|
||||||
pub r#type: Option<Type>,
|
pub r#type: Option<Type>,
|
||||||
|
|
||||||
|
/// Whether the local is mutable.
|
||||||
pub is_mutable: bool,
|
pub is_mutable: bool,
|
||||||
|
|
||||||
|
/// Scope where the variable was declared.
|
||||||
pub scope: Scope,
|
pub scope: Scope,
|
||||||
|
|
||||||
|
/// Expected location of a local's value.
|
||||||
pub register_index: u8,
|
pub register_index: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Local {
|
impl Local {
|
||||||
|
/// Creates a new Local instance.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
identifier_index: u8,
|
identifier_index: u8,
|
||||||
r#type: Option<Type>,
|
r#type: Option<Type>,
|
||||||
@ -361,41 +372,48 @@ impl Local {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Variable locality, as defined by its depth and block index.
|
||||||
|
///
|
||||||
|
/// The `block index` is a unique identifier for a block within a chunk. It is used to differentiate
|
||||||
|
/// between blocks that are not nested together but have the same depth, i.e. sibling scopes. If the
|
||||||
|
/// `block_index` is 0, then the scope is the root scope of the chunk. The `block_index` is always 0
|
||||||
|
/// when the `depth` is 0. See [Chunk::begin_scope][] and [Chunk::end_scope][] to see how scopes are
|
||||||
|
/// incremented and decremented.
|
||||||
#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||||
pub struct Scope {
|
pub struct Scope {
|
||||||
/// The level of block nesting.
|
/// Level of block nesting.
|
||||||
pub depth: u8,
|
pub depth: u8,
|
||||||
/// The nth scope in the chunk.
|
/// Index of the block in the chunk.
|
||||||
pub index: u8,
|
pub block_index: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Scope {
|
impl Scope {
|
||||||
pub fn new(index: u8, width: u8) -> Self {
|
pub fn new(depth: u8, block_index: u8) -> Self {
|
||||||
Self {
|
Self { depth, block_index }
|
||||||
depth: index,
|
|
||||||
index: width,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn contains(&self, other: &Self) -> bool {
|
pub fn contains(&self, other: &Self) -> bool {
|
||||||
match self.depth.cmp(&other.depth) {
|
match self.depth.cmp(&other.depth) {
|
||||||
Ordering::Less => false,
|
Ordering::Less => false,
|
||||||
Ordering::Greater => self.index >= other.index,
|
Ordering::Greater => self.block_index >= other.block_index,
|
||||||
Ordering::Equal => self.index == other.index,
|
Ordering::Equal => self.block_index == other.block_index,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Scope {
|
impl Display for Scope {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, "({}, {})", self.depth, self.index)
|
write!(f, "({}, {})", self.depth, self.block_index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Builder that constructs a human-readable representation of a chunk.
|
||||||
pub struct ChunkDisassembler<'a> {
|
pub struct ChunkDisassembler<'a> {
|
||||||
output: String,
|
output: String,
|
||||||
chunk: &'a Chunk,
|
chunk: &'a Chunk,
|
||||||
source: Option<&'a str>,
|
source: Option<&'a str>,
|
||||||
|
|
||||||
|
// Options
|
||||||
width: usize,
|
width: usize,
|
||||||
styled: bool,
|
styled: bool,
|
||||||
indent: usize,
|
indent: usize,
|
||||||
@ -405,8 +423,8 @@ impl<'a> ChunkDisassembler<'a> {
|
|||||||
const INSTRUCTION_HEADER: [&'static str; 4] = [
|
const INSTRUCTION_HEADER: [&'static str; 4] = [
|
||||||
"Instructions",
|
"Instructions",
|
||||||
"------------",
|
"------------",
|
||||||
"INDEX BYTECODE OPERATION INFO TYPE POSITION ",
|
" i BYTECODE OPERATION INFO TYPE POSITION ",
|
||||||
"----- -------- ------------- ------------------------- --------- -----------",
|
"--- -------- ------------- -------------------- --------------- ------------",
|
||||||
];
|
];
|
||||||
|
|
||||||
const CONSTANT_HEADER: [&'static str; 4] = [
|
const CONSTANT_HEADER: [&'static str; 4] = [
|
||||||
@ -419,8 +437,8 @@ impl<'a> ChunkDisassembler<'a> {
|
|||||||
const LOCAL_HEADER: [&'static str; 4] = [
|
const LOCAL_HEADER: [&'static str; 4] = [
|
||||||
"Locals",
|
"Locals",
|
||||||
"------",
|
"------",
|
||||||
"INDEX IDENTIFIER TYPE MUTABLE SCOPE REGISTER",
|
"INDEX IDENTIFIER TYPE MUTABLE SCOPE REGISTER",
|
||||||
"----- ---------- -------- ------- ------- --------",
|
"----- ---------- ---------- ------- ------- --------",
|
||||||
];
|
];
|
||||||
|
|
||||||
/// 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
|
||||||
@ -558,6 +576,10 @@ impl<'a> ChunkDisassembler<'a> {
|
|||||||
self.push(border, false, false, false, false);
|
self.push(border, false, false, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn push_empty(&mut self) {
|
||||||
|
self.push("", false, false, false, true);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn disassemble(mut self) -> String {
|
pub fn disassemble(mut self) -> String {
|
||||||
let top_border = "┌".to_string() + &"─".repeat(self.width - 2) + "┐";
|
let top_border = "┌".to_string() + &"─".repeat(self.width - 2) + "┐";
|
||||||
let section_border = "│".to_string() + &"┈".repeat(self.width - 2) + "│";
|
let section_border = "│".to_string() + &"┈".repeat(self.width - 2) + "│";
|
||||||
@ -576,6 +598,17 @@ impl<'a> ChunkDisassembler<'a> {
|
|||||||
self.push_border(&top_border);
|
self.push_border(&top_border);
|
||||||
self.push_header(&name_display);
|
self.push_header(&name_display);
|
||||||
|
|
||||||
|
if let Some(source) = self.source {
|
||||||
|
self.push_empty();
|
||||||
|
self.push_details(
|
||||||
|
&source
|
||||||
|
.replace(" ", "")
|
||||||
|
.replace("\n\n", " ")
|
||||||
|
.replace('\n', " "),
|
||||||
|
);
|
||||||
|
self.push_empty();
|
||||||
|
}
|
||||||
|
|
||||||
let info_line = format!(
|
let info_line = format!(
|
||||||
"{} instructions, {} constants, {} locals, returns {}",
|
"{} instructions, {} constants, {} locals, returns {}",
|
||||||
self.chunk.instructions.len(),
|
self.chunk.instructions.len(),
|
||||||
@ -587,24 +620,33 @@ impl<'a> ChunkDisassembler<'a> {
|
|||||||
.unwrap_or("none".to_string())
|
.unwrap_or("none".to_string())
|
||||||
);
|
);
|
||||||
|
|
||||||
self.push(&info_line, true, false, false, true);
|
self.push(&info_line, true, false, true, true);
|
||||||
|
self.push_empty();
|
||||||
|
|
||||||
for line in &Self::INSTRUCTION_HEADER {
|
for line in &Self::INSTRUCTION_HEADER {
|
||||||
self.push_header(line);
|
self.push_header(line);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (index, (instruction, position)) in self.chunk.instructions.iter().enumerate() {
|
for (index, (instruction, position)) in self.chunk.instructions.iter().enumerate() {
|
||||||
let bytecode = u32::from(instruction);
|
let bytecode = format!("{:02X}", u32::from(instruction));
|
||||||
let operation = instruction.operation().to_string();
|
let operation = instruction.operation().to_string();
|
||||||
let info = instruction.disassembly_info(self.chunk);
|
let info = instruction.disassembly_info(self.chunk);
|
||||||
let type_display = instruction
|
let type_display = instruction
|
||||||
.yielded_type(self.chunk)
|
.yielded_type(self.chunk)
|
||||||
.map(|r#type| r#type.to_string())
|
.map(|r#type| {
|
||||||
|
let type_string = r#type.to_string();
|
||||||
|
|
||||||
|
if type_string.len() > 15 {
|
||||||
|
format!("{type_string:.12}...")
|
||||||
|
} else {
|
||||||
|
type_string
|
||||||
|
}
|
||||||
|
})
|
||||||
.unwrap_or(String::with_capacity(0));
|
.unwrap_or(String::with_capacity(0));
|
||||||
let position = position.to_string();
|
let position = position.to_string();
|
||||||
|
|
||||||
let instruction_display = format!(
|
let instruction_display = format!(
|
||||||
"{index:<5} {bytecode:08X} {operation:13} {info:25} {type_display:9} {position:11}"
|
"{index:^3} {bytecode:>8} {operation:13} {info:20} {type_display:^15} {position:12}"
|
||||||
);
|
);
|
||||||
|
|
||||||
self.push_details(&instruction_display);
|
self.push_details(&instruction_display);
|
||||||
@ -635,10 +677,18 @@ impl<'a> ChunkDisassembler<'a> {
|
|||||||
.unwrap_or_else(|| "unknown".to_string());
|
.unwrap_or_else(|| "unknown".to_string());
|
||||||
let type_display = r#type
|
let type_display = r#type
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|r#type| r#type.to_string())
|
.map(|r#type| {
|
||||||
|
let type_string = r#type.to_string();
|
||||||
|
|
||||||
|
if type_string.len() > 10 {
|
||||||
|
format!("{type_string:.7}...")
|
||||||
|
} else {
|
||||||
|
type_string
|
||||||
|
}
|
||||||
|
})
|
||||||
.unwrap_or("unknown".to_string());
|
.unwrap_or("unknown".to_string());
|
||||||
let local_display = format!(
|
let local_display = format!(
|
||||||
"{index:<5} {identifier_display:10} {type_display:8} {mutable:7} {scope:7} {register_index:8}"
|
"{index:<5} {identifier_display:10} {type_display:10} {mutable:7} {scope:7} {register_index:8}"
|
||||||
);
|
);
|
||||||
|
|
||||||
self.push_details(&local_display);
|
self.push_details(&local_display);
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
//! Tools used by the compiler to optimize a chunk's bytecode.
|
||||||
use std::{iter::Map, slice::Iter};
|
use std::{iter::Map, slice::Iter};
|
||||||
|
|
||||||
use crate::{Instruction, Operation, Span};
|
use crate::{Instruction, Operation, Span};
|
||||||
@ -6,24 +7,24 @@ type MapToOperation = fn(&(Instruction, Span)) -> Operation;
|
|||||||
|
|
||||||
type OperationIter<'iter> = Map<Iter<'iter, (Instruction, Span)>, MapToOperation>;
|
type OperationIter<'iter> = Map<Iter<'iter, (Instruction, Span)>, MapToOperation>;
|
||||||
|
|
||||||
|
/// Performs optimizations on a subset of instructions.
|
||||||
pub fn optimize(instructions: &mut [(Instruction, Span)]) -> usize {
|
pub fn optimize(instructions: &mut [(Instruction, Span)]) -> usize {
|
||||||
Optimizer::new(instructions).optimize()
|
Optimizer::new(instructions).optimize()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An instruction optimizer that mutably borrows instructions from a chunk.
|
||||||
#[derive(Debug, Eq, PartialEq, PartialOrd, Ord)]
|
#[derive(Debug, Eq, PartialEq, PartialOrd, Ord)]
|
||||||
pub struct Optimizer<'chunk> {
|
pub struct Optimizer<'chunk> {
|
||||||
instructions: &'chunk mut [(Instruction, Span)],
|
instructions: &'chunk mut [(Instruction, Span)],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'chunk> Optimizer<'chunk> {
|
impl<'chunk> Optimizer<'chunk> {
|
||||||
|
/// Creates a new optimizer with a mutable reference to some of a chunk's instructions.
|
||||||
pub fn new(instructions: &'chunk mut [(Instruction, Span)]) -> Self {
|
pub fn new(instructions: &'chunk mut [(Instruction, Span)]) -> Self {
|
||||||
Self { instructions }
|
Self { instructions }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_instructions(&mut self, instructions: &'chunk mut [(Instruction, Span)]) {
|
/// Potentially mutates the instructions to optimize them.
|
||||||
self.instructions = instructions;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn optimize(&mut self) -> usize {
|
pub fn optimize(&mut self) -> usize {
|
||||||
let mut optimizations = 0;
|
let mut optimizations = 0;
|
||||||
|
|
||||||
@ -44,6 +45,13 @@ impl<'chunk> Optimizer<'chunk> {
|
|||||||
optimizations
|
optimizations
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Optimizes a comparison operation.
|
||||||
|
///
|
||||||
|
/// The instructions must be in the following order:
|
||||||
|
/// - `Operation::Equal | Operation::Less | Operation::LessEqual`
|
||||||
|
/// - `Operation::Jump`
|
||||||
|
/// - `Operation::LoadBoolean | Operation::LoadConstant`
|
||||||
|
/// - `Operation::LoadBoolean | Operation::LoadConstant`
|
||||||
fn optimize_comparison(&mut self) {
|
fn optimize_comparison(&mut self) {
|
||||||
log::debug!("Optimizing comparison");
|
log::debug!("Optimizing comparison");
|
||||||
|
|
||||||
|
@ -23,7 +23,16 @@ fn equality_assignment_long() {
|
|||||||
(Instruction::r#return(true), Span(44, 44)),
|
(Instruction::r#return(true), Span(44, 44)),
|
||||||
],
|
],
|
||||||
vec![Value::integer(4), Value::string("a")],
|
vec![Value::integer(4), Value::string("a")],
|
||||||
vec![Local::new(1, None, false, Scope { depth: 0, index: 0 }, 0)]
|
vec![Local::new(
|
||||||
|
1,
|
||||||
|
None,
|
||||||
|
false,
|
||||||
|
Scope {
|
||||||
|
depth: 0,
|
||||||
|
block_index: 0
|
||||||
|
},
|
||||||
|
0
|
||||||
|
)]
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -124,3 +124,15 @@ fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use clap::CommandFactory;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn verify_cli() {
|
||||||
|
Cli::command().debug_assert();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user