1
0
dust/dust-lang/src/chunk.rs

500 lines
15 KiB
Rust
Raw Normal View History

use std::fmt::{self, Debug, Display, Formatter};
2024-09-07 10:38:12 +00:00
use colored::Colorize;
2024-09-07 10:38:12 +00:00
use serde::{Deserialize, Serialize};
2024-09-11 07:10:12 +00:00
use crate::{AnnotatedError, Identifier, Instruction, Span, Value};
2024-09-07 10:38:12 +00:00
#[derive(Clone)]
2024-09-07 10:38:12 +00:00
pub struct Chunk {
instructions: Vec<(Instruction, Span)>,
constants: Vec<Option<Value>>,
locals: Vec<Local>,
2024-09-11 07:10:12 +00:00
scope_depth: usize,
2024-09-07 10:38:12 +00:00
}
impl Chunk {
pub fn new() -> Self {
Self {
instructions: Vec::new(),
2024-09-07 10:38:12 +00:00
constants: Vec::new(),
locals: Vec::new(),
2024-09-11 07:10:12 +00:00
scope_depth: 0,
2024-09-07 10:38:12 +00:00
}
}
2024-09-07 16:15:47 +00:00
pub fn with_data(
instructions: Vec<(Instruction, Span)>,
2024-09-07 16:15:47 +00:00
constants: Vec<Value>,
2024-09-14 19:06:13 +00:00
locals: Vec<Local>,
2024-09-07 16:15:47 +00:00
) -> Self {
Self {
instructions,
constants: constants.into_iter().map(Some).collect(),
2024-09-14 19:06:13 +00:00
locals,
2024-09-11 07:10:12 +00:00
scope_depth: 0,
2024-09-07 16:15:47 +00:00
}
2024-09-07 10:38:12 +00:00
}
2024-09-10 22:19:59 +00:00
pub fn scope_depth(&self) -> usize {
2024-09-11 07:10:12 +00:00
self.scope_depth
2024-09-10 22:19:59 +00:00
}
pub fn get_instruction(
&self,
offset: usize,
position: Span,
) -> Result<&(Instruction, Span), ChunkError> {
self.instructions
2024-09-07 16:15:47 +00:00
.get(offset)
2024-09-10 22:19:59 +00:00
.ok_or(ChunkError::CodeIndexOfBounds { offset, position })
2024-09-07 10:38:12 +00:00
}
pub fn push_instruction(&mut self, instruction: Instruction, position: Span) {
self.instructions.push((instruction, position));
2024-09-07 10:38:12 +00:00
}
2024-09-13 05:10:07 +00:00
pub fn pop_instruction(&mut self, position: Span) -> Result<(Instruction, Span), ChunkError> {
self.instructions
.pop()
.ok_or(ChunkError::InstructionUnderflow { position })
}
2024-09-15 01:05:03 +00:00
pub fn get_constant(&self, index: u8, position: Span) -> Result<&Value, ChunkError> {
let index = index as usize;
2024-09-07 10:38:12 +00:00
self.constants
.get(index)
2024-09-10 22:19:59 +00:00
.ok_or(ChunkError::ConstantIndexOutOfBounds { index, position })
.and_then(|value| {
value
.as_ref()
.ok_or(ChunkError::ConstantAlreadyUsed { index, position })
})
2024-09-07 10:38:12 +00:00
}
2024-09-15 01:05:03 +00:00
pub fn take_constant(&mut self, index: u8, position: Span) -> Result<Value, ChunkError> {
let index = index as usize;
self.constants
.get_mut(index)
.ok_or_else(|| ChunkError::ConstantIndexOutOfBounds { index, position })?
.take()
.ok_or(ChunkError::ConstantAlreadyUsed { index, position })
}
2024-09-15 01:05:03 +00:00
pub fn push_constant(&mut self, value: Value, position: Span) -> Result<u8, ChunkError> {
2024-09-07 10:38:12 +00:00
let starting_length = self.constants.len();
if starting_length + 1 > (u8::MAX as usize) {
2024-09-11 07:10:12 +00:00
Err(ChunkError::ConstantOverflow { position })
2024-09-07 10:38:12 +00:00
} else {
self.constants.push(Some(value));
2024-09-07 10:38:12 +00:00
2024-09-15 01:05:03 +00:00
Ok(starting_length as u8)
2024-09-07 10:38:12 +00:00
}
}
2024-09-15 01:05:03 +00:00
pub fn get_local(&self, index: u8, position: Span) -> Result<&Local, ChunkError> {
let index = index as usize;
self.locals
.get(index)
.ok_or(ChunkError::LocalIndexOutOfBounds { index, position })
2024-09-10 22:19:59 +00:00
}
2024-09-15 01:05:03 +00:00
pub fn get_identifier(&self, index: u8) -> Option<&Identifier> {
let index = index as usize;
self.locals.get(index).map(|local| &local.identifier)
2024-09-07 16:15:47 +00:00
}
pub fn get_local_index(
2024-09-10 22:19:59 +00:00
&self,
identifier: &Identifier,
position: Span,
2024-09-15 01:05:03 +00:00
) -> Result<u8, ChunkError> {
self.locals
2024-09-11 07:10:12 +00:00
.iter()
.enumerate()
.rev()
2024-09-11 07:10:12 +00:00
.find_map(|(index, local)| {
if &local.identifier == identifier {
2024-09-15 01:05:03 +00:00
Some(index as u8)
2024-09-11 07:10:12 +00:00
} else {
None
}
})
2024-09-10 22:19:59 +00:00
.ok_or(ChunkError::IdentifierNotFound {
identifier: identifier.clone(),
position,
})
2024-09-09 23:23:49 +00:00
}
pub fn declare_local(
2024-09-11 07:10:12 +00:00
&mut self,
identifier: Identifier,
2024-09-15 01:05:03 +00:00
register_index: u8,
2024-09-11 07:10:12 +00:00
position: Span,
2024-09-15 01:05:03 +00:00
) -> Result<u8, ChunkError> {
let starting_length = self.locals.len();
2024-09-07 16:15:47 +00:00
if starting_length + 1 > (u8::MAX as usize) {
2024-09-11 07:10:12 +00:00
Err(ChunkError::IdentifierOverflow { position })
2024-09-07 16:15:47 +00:00
} else {
2024-09-15 01:05:03 +00:00
self.locals.push(Local::new(
identifier,
self.scope_depth,
Some(register_index),
));
2024-09-07 16:15:47 +00:00
2024-09-15 01:05:03 +00:00
Ok(starting_length as u8)
2024-09-07 16:15:47 +00:00
}
}
pub fn define_local(
&mut self,
local_index: usize,
register_index: u8,
position: Span,
) -> Result<(), ChunkError> {
let local =
self.locals
.get_mut(local_index)
.ok_or_else(|| ChunkError::LocalIndexOutOfBounds {
index: local_index,
position,
})?;
local.register_index = Some(register_index);
Ok(())
}
2024-09-10 14:44:15 +00:00
pub fn begin_scope(&mut self) {
2024-09-11 07:10:12 +00:00
self.scope_depth += 1;
2024-09-10 14:44:15 +00:00
}
pub fn end_scope(&mut self) {
2024-09-11 07:10:12 +00:00
self.scope_depth -= 1;
2024-09-10 14:44:15 +00:00
}
pub fn disassembler<'a>(&'a self, name: &'a str) -> ChunkDisassembler<'a> {
ChunkDisassembler::new(name, self)
}
}
impl Default for Chunk {
fn default() -> Self {
Self::new()
}
}
impl Display for Chunk {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(
f,
"{}",
self.disassembler("Chunk Display")
.styled(true)
.disassemble()
)
}
}
impl Debug for Chunk {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(
f,
"{}",
self.disassembler("Chunk Debug Display")
.styled(false)
.disassemble()
)
}
}
impl Eq for Chunk {}
impl PartialEq for Chunk {
fn eq(&self, other: &Self) -> bool {
self.instructions == other.instructions
&& self.constants == other.constants
&& self.locals == other.locals
}
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct Local {
pub identifier: Identifier,
pub depth: usize,
pub register_index: Option<u8>,
}
impl Local {
pub fn new(identifier: Identifier, depth: usize, register_index: Option<u8>) -> Self {
Self {
identifier,
depth,
register_index,
}
}
}
pub struct ChunkDisassembler<'a> {
name: &'a str,
chunk: &'a Chunk,
width: usize,
styled: bool,
}
2024-09-07 10:38:12 +00:00
impl<'a> ChunkDisassembler<'a> {
2024-09-13 05:10:07 +00:00
const INSTRUCTION_HEADER: [&'static str; 5] = [
"",
"Instructions",
"------------",
2024-09-15 01:05:03 +00:00
"OFFSET OPERATION INFO POSITION",
"------- -------------- ------------------------- --------",
2024-09-13 05:10:07 +00:00
];
const CONSTANT_HEADER: [&'static str; 5] = [
"",
"Constants",
"---------",
"INDEX KIND VALUE",
"----- ----- -----",
];
const LOCAL_HEADER: [&'static str; 5] = [
"",
"Locals",
"------",
"INDEX IDENTIFIER DEPTH REGISTER",
"----- ---------- ----- --------",
2024-09-13 05:10:07 +00:00
];
/// The default width of the disassembly output. To correctly align the output, this should be
/// set to the width of the longest line that the disassembler is guaranteed to produce.
2024-09-14 17:59:11 +00:00
const DEFAULT_WIDTH: usize = Self::INSTRUCTION_HEADER[4].len() + 1;
2024-09-13 05:10:07 +00:00
pub fn new(name: &'a str, chunk: &'a Chunk) -> Self {
Self {
name,
chunk,
2024-09-13 05:10:07 +00:00
width: Self::DEFAULT_WIDTH,
styled: false,
}
}
2024-09-15 01:05:03 +00:00
pub fn width(&mut self, width: usize) -> &mut Self {
self.width = width;
self
}
pub fn styled(&mut self, styled: bool) -> &mut Self {
self.styled = styled;
self
}
pub fn disassemble(&self) -> String {
2024-09-13 05:10:07 +00:00
let center = |line: &str| format!("{line:^width$}\n", width = self.width);
let style = |line: String| {
if self.styled {
line.bold().to_string()
} else {
2024-09-13 05:10:07 +00:00
line
}
};
2024-09-10 00:55:00 +00:00
2024-09-14 17:59:11 +00:00
let mut disassembled = String::with_capacity(self.predict_length());
let name_line = style(center(self.name));
disassembled.push_str(&name_line);
2024-09-13 05:10:07 +00:00
for line in Self::INSTRUCTION_HEADER {
disassembled.push_str(&style(center(line)));
}
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 {
2024-09-15 01:05:03 +00:00
format!("{offset:<7} {operation:14} {info:25} {position:8}")
} else {
2024-09-15 01:05:03 +00:00
format!("{offset:<7} {operation:14} {:25} {position:8}", " ")
};
2024-09-13 05:10:07 +00:00
disassembled.push_str(&center(&instruction_display));
}
2024-09-10 00:55:00 +00:00
2024-09-13 05:10:07 +00:00
for line in Self::CONSTANT_HEADER {
disassembled.push_str(&style(center(line)));
}
for (index, value_option) in self.chunk.constants.iter().enumerate() {
2024-09-13 05:10:07 +00:00
let value_kind_display = if let Some(value) = value_option {
value.kind().to_string()
} else {
"empty".to_string()
2024-09-10 00:55:00 +00:00
};
let value_display = value_option
.as_ref()
.map(|value| value.to_string())
2024-09-14 17:59:11 +00:00
.unwrap_or_else(|| "empty".to_string());
let constant_display = format!("{index:<5} {value_kind_display:<5} {value_display:<5}");
2024-09-10 00:55:00 +00:00
2024-09-13 05:10:07 +00:00
disassembled.push_str(&center(&constant_display));
2024-09-10 00:55:00 +00:00
}
2024-09-13 05:10:07 +00:00
for line in Self::LOCAL_HEADER {
disassembled.push_str(&style(center(line)));
}
for (
index,
Local {
identifier,
depth,
register_index,
},
) in self.chunk.locals.iter().enumerate()
{
let register_display = register_index
.as_ref()
.map(|value| value.to_string())
2024-09-14 17:59:11 +00:00
.unwrap_or_else(|| "empty".to_string());
let identifier_display = identifier.as_str();
let local_display =
format!("{index:<5} {identifier_display:<10} {depth:<5} {register_display:<8}");
2024-09-13 05:10:07 +00:00
disassembled.push_str(&center(&local_display));
2024-09-07 10:38:12 +00:00
}
let expected_length = self.predict_length();
let actual_length = disassembled.len();
if !self.styled && expected_length != actual_length {
log::debug!(
"Chunk disassembly was not optimized correctly, expected string length {expected_length}, got {actual_length}",
);
}
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}",
);
}
2024-09-13 05:10:07 +00:00
disassembled
2024-09-07 10:38:12 +00:00
}
2024-09-13 05:10:07 +00:00
/// Predicts the capacity of the disassembled output. This is used to pre-allocate the string
/// buffer to avoid reallocations.
///
/// The capacity is calculated as follows:
/// - Get the number of static lines, i.e. lines that are always present in the disassembly
/// - Get the number of dynamic lines, i.e. lines that are generated from the chunk
/// - Add 1 to the width to account for the newline character
/// - Multiply the total number of lines by the width of the disassembly output
2024-09-14 17:59:11 +00:00
///
/// The result is accurate only if the output is not styled. Otherwise the extra bytes added by
/// the ANSI escape codes will make the result too low. It still works as a lower bound in that
/// case.
fn predict_length(&self) -> usize {
const EXTRA_LINES: usize = 1; // There is one empty line after the name of the chunk
let static_line_count =
Self::INSTRUCTION_HEADER.len() + Self::CONSTANT_HEADER.len() + Self::LOCAL_HEADER.len();
2024-09-13 05:10:07 +00:00
let dynamic_line_count =
self.chunk.instructions.len() + self.chunk.constants.len() + self.chunk.locals.len();
let total_line_count = static_line_count + dynamic_line_count + EXTRA_LINES;
2024-09-13 05:10:07 +00:00
2024-09-14 17:59:11 +00:00
total_line_count * (self.width + 1)
}
}
2024-09-09 23:23:49 +00:00
#[derive(Debug, Clone, PartialEq)]
2024-09-07 10:38:12 +00:00
pub enum ChunkError {
2024-09-10 22:19:59 +00:00
CodeIndexOfBounds {
offset: usize,
position: Span,
},
ConstantAlreadyUsed {
index: usize,
position: Span,
},
2024-09-11 07:10:12 +00:00
ConstantOverflow {
position: Span,
},
2024-09-10 22:19:59 +00:00
ConstantIndexOutOfBounds {
index: usize,
2024-09-10 22:19:59 +00:00
position: Span,
},
2024-09-13 05:10:07 +00:00
InstructionUnderflow {
position: Span,
},
LocalIndexOutOfBounds {
index: usize,
2024-09-11 07:10:12 +00:00
position: Span,
},
IdentifierOverflow {
position: Span,
},
2024-09-10 22:19:59 +00:00
IdentifierNotFound {
identifier: Identifier,
position: Span,
},
}
impl AnnotatedError for ChunkError {
fn title() -> &'static str {
"Chunk Error"
}
fn description(&self) -> &'static str {
match self {
ChunkError::CodeIndexOfBounds { .. } => "Code index out of bounds",
ChunkError::ConstantAlreadyUsed { .. } => "Constant already used",
2024-09-11 07:10:12 +00:00
ChunkError::ConstantOverflow { .. } => "Constant overflow",
2024-09-10 22:19:59 +00:00
ChunkError::ConstantIndexOutOfBounds { .. } => "Constant index out of bounds",
2024-09-13 05:10:07 +00:00
ChunkError::InstructionUnderflow { .. } => "Instruction underflow",
ChunkError::LocalIndexOutOfBounds { .. } => "Identifier index out of bounds",
2024-09-11 07:10:12 +00:00
ChunkError::IdentifierOverflow { .. } => "Identifier overflow",
2024-09-10 22:19:59 +00:00
ChunkError::IdentifierNotFound { .. } => "Identifier not found",
}
}
fn details(&self) -> Option<String> {
match self {
ChunkError::CodeIndexOfBounds { offset, .. } => Some(format!("Code index: {}", offset)),
ChunkError::ConstantAlreadyUsed { index, .. } => {
Some(format!("Constant index: {}", index))
}
2024-09-10 22:19:59 +00:00
ChunkError::ConstantIndexOutOfBounds { index, .. } => {
Some(format!("Constant index: {}", index))
}
2024-09-13 05:10:07 +00:00
ChunkError::InstructionUnderflow { .. } => None,
ChunkError::LocalIndexOutOfBounds { index, .. } => {
2024-09-10 22:19:59 +00:00
Some(format!("Identifier index: {}", index))
}
ChunkError::IdentifierNotFound { identifier, .. } => {
Some(format!("Identifier: {}", identifier))
}
_ => None,
}
}
fn position(&self) -> Span {
match self {
ChunkError::CodeIndexOfBounds { position, .. } => *position,
ChunkError::ConstantAlreadyUsed { position, .. } => *position,
2024-09-10 22:19:59 +00:00
ChunkError::ConstantIndexOutOfBounds { position, .. } => *position,
ChunkError::IdentifierNotFound { position, .. } => *position,
_ => todo!(),
}
}
2024-09-07 10:38:12 +00:00
}