1
0

Make the disassembly prettier than ever before

This commit is contained in:
Jeff 2024-12-10 01:34:53 -05:00
parent 5d43674000
commit 85a706e0fb
18 changed files with 623 additions and 441 deletions

View File

@ -115,14 +115,14 @@ fn main() {
return;
}
};
let disassembly = chunk
.disassembler()
let mut stdout = stdout().lock();
chunk
.disassembler(&mut stdout)
.style(mode.style)
.source(&source)
.disassemble();
println!("{}", disassembly);
return;
}

View File

@ -38,64 +38,77 @@
//! │ 0 str Hello world! │
//! └───────────────────────────────────────────────────────────────┘
//! ```
use std::env::current_exe;
use std::{
env::current_exe,
io::{self, ErrorKind, Write},
iter::empty,
str::Chars,
};
use colored::Colorize;
use colored::{ColoredString, Colorize};
use crate::{value::ConcreteValue, Chunk, Local};
const INSTRUCTION_HEADER: [&str; 4] = [
"Instructions",
"------------",
" i POSITION OPERATION INFO ",
"--- ---------- ------------- --------------------------------",
const INSTRUCTION_COLUMNS: [(&str, usize); 4] =
[("i", 5), ("POSITION", 12), ("OPERATION", 17), ("INFO", 24)];
const INSTRUCTION_BORDERS: [&str; 3] = [
"╭─────┬────────────┬─────────────────┬────────────────────────╮",
"├─────┼────────────┼─────────────────┼────────────────────────┤",
"╰─────┴────────────┴─────────────────┴────────────────────────╯",
];
const CONSTANT_HEADER: [&str; 4] = [
"Constants",
"---------",
" i TYPE VALUE ",
"--- ---------------- -----------------",
const LOCAL_COLUMNS: [(&str, usize); 5] = [
("i", 5),
("IDENTIFIER", 16),
("VALUE", 10),
("SCOPE", 7),
("MUTABLE", 7),
];
const LOCAL_BORDERS: [&str; 3] = [
"╭─────┬────────────────┬──────────┬───────┬───────╮",
"├─────┼────────────────┼──────────┼───────┼───────┤",
"╰─────┴────────────────┴──────────┴───────┴───────╯",
];
const LOCAL_HEADER: [&str; 4] = [
"Locals",
"------",
" i IDENTIFIER REGISTER SCOPE MUTABLE",
"--- ---------------- -------- ------- -------",
const CONSTANT_COLUMNS: [(&str, usize); 3] = [("i", 5), ("TYPE", 26), ("VALUE", 26)];
const CONSTANT_BORDERS: [&str; 3] = [
"╭─────┬──────────────────────────┬──────────────────────────╮",
"├─────┼──────────────────────────┼──────────────────────────┤",
"╰─────┴──────────────────────────┴──────────────────────────╯",
];
const INDENTATION: &str = "";
const TOP_BORDER: [char; 3] = ['╭', '─', '╮'];
const LEFT_BORDER: char = '│';
const RIGHT_BORDER: char = '│';
const BOTTOM_BORDER: [char; 3] = ['╰', '─', '╯'];
/// Builder that constructs a human-readable representation of a chunk.
///
/// See the [module-level documentation](index.html) for more information.
pub struct Disassembler<'a> {
output: String,
pub struct Disassembler<'a, W> {
writer: &'a mut W,
chunk: &'a Chunk,
source: Option<&'a str>,
// Options
style: bool,
indent: usize,
output_width: usize,
}
impl<'a> Disassembler<'a> {
pub fn new(chunk: &'a Chunk) -> Self {
impl<'a, W: Write> Disassembler<'a, W> {
pub fn new(chunk: &'a Chunk, writer: &'a mut W) -> Self {
Self {
output: String::new(),
writer,
chunk,
source: None,
style: false,
indent: 0,
output_width: Self::content_length(),
}
}
/// The default width of the disassembly output, including borders and padding.
pub fn default_width() -> usize {
let longest_line = INSTRUCTION_HEADER[3];
longest_line.chars().count() + 4 // Allow for one border and one padding space on each side.
}
pub fn source(mut self, source: &'a str) -> Self {
self.source = Some(source);
@ -108,123 +121,168 @@ impl<'a> Disassembler<'a> {
self
}
fn push(
fn indent(mut self, indent: usize) -> Self {
self.indent = indent;
self
}
fn content_length() -> usize {
let longest_line_length = INSTRUCTION_BORDERS[0].chars().count();
longest_line_length
}
fn line_length(&self) -> usize {
let indentation_length = INDENTATION.chars().count();
self.output_width + (indentation_length * self.indent) + 2 // Left and right border
}
fn write_char(&mut self, c: char) -> Result<(), io::Error> {
write!(&mut self.writer, "{}", c)
}
fn write_str(&mut self, text: &str) -> Result<(), io::Error> {
write!(&mut self.writer, "{}", text)
}
fn write_content(
&mut self,
text: &str,
center: bool,
style_bold: bool,
style_dim: bool,
add_border: bool,
) {
let width = Disassembler::default_width();
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(), &[]));
) -> Result<(), io::Error> {
let (line_content, overflow) = {
if text.len() > self.output_width {
let split_index = text
.char_indices()
.nth(self.output_width)
.map(|(index, _)| index)
.unwrap_or_else(|| text.len());
text.split_at(split_index)
} else {
(text, "")
}
};
let (left_pad_length, right_pad_length) = {
let extra_space = content_width.saturating_sub(characters.len());
let width = self.line_length();
let line_content_length = line_content.chars().count();
let extra_space = width.saturating_sub(line_content_length);
let half = extra_space / 2;
let remainder = extra_space % 2;
if center {
(extra_space / 2, extra_space / 2 + extra_space % 2)
(half, half + remainder)
} else {
(0, extra_space)
}
};
let mut content = line_characters.iter().collect::<String>();
if style_bold {
content = content.bold().to_string();
}
if style_dim {
content = content.dimmed().to_string();
}
let length_before_content = self.output.chars().count();
for _ in 0..self.indent {
self.output.push_str("");
self.write_str(INDENTATION)?;
}
if add_border {
self.output.push('│');
self.write_char(LEFT_BORDER)?;
}
self.output.push_str(&" ".repeat(left_pad_length));
self.output.push_str(&content);
self.output.push_str(&" ".repeat(right_pad_length));
if center {
for _ in 0..left_pad_length {
self.write_char(' ')?;
}
}
let length_after_content = self.output.chars().count();
let line_length = length_after_content - length_before_content;
self.write_str(line_content)?;
if line_length < content_width - 1 {
self.output
.push_str(&" ".repeat(content_width - line_length));
if center {
for _ in 0..right_pad_length {
self.write_char(' ')?;
}
}
if add_border {
self.output.push('│');
self.write_char(RIGHT_BORDER)?;
}
self.output.push('\n');
self.write_char('\n')?;
if !remainder.is_empty() {
self.push(
remainder.iter().collect::<String>().as_str(),
center,
style_bold,
style_dim,
add_border,
);
}
if !overflow.is_empty() {
self.write_content(overflow, center, style_bold, style_dim, add_border)?;
}
fn push_source(&mut self, source: &str) {
self.push(source, true, false, false, true);
Ok(())
}
fn push_chunk_info(&mut self, info: &str) {
self.push(info, true, false, true, true);
fn write_centered_with_border(&mut self, text: &str) -> Result<(), io::Error> {
self.write_content(text, true, false, false, true)
}
fn push_header(&mut self, header: &str) {
self.push(header, true, self.style, false, true);
fn write_centered_with_border_dim(&mut self, text: &str) -> Result<(), io::Error> {
self.write_content(text, true, false, self.style, true)
}
fn push_details(&mut self, details: &str) {
self.push(details, true, false, false, true);
fn write_centered_with_border_bold(&mut self, text: &str) -> Result<(), io::Error> {
self.write_content(text, true, self.style, false, true)
}
fn push_border(&mut self, border: &str) {
self.push(border, false, false, false, false);
fn write_page_border(&mut self, border: [char; 3]) -> Result<(), io::Error> {
for _ in 0..self.indent {
self.write_str(INDENTATION)?;
}
fn push_empty(&mut self) {
self.push("", false, false, false, true);
self.write_char(border[0])?;
for _ in 0..self.line_length() {
self.write_char(border[1])?;
}
fn push_instruction_section(&mut self) {
for line in INSTRUCTION_HEADER {
self.push_header(line);
self.write_char(border[2])
}
fn write_instruction_section(&mut self) -> Result<(), io::Error> {
let mut column_name_line = String::new();
for (column_name, width) in INSTRUCTION_COLUMNS {
column_name_line.push_str(&format!("{column_name:^width$}", width = width));
}
column_name_line.push('│');
self.write_centered_with_border_bold("Instructions")?;
self.write_centered_with_border(INSTRUCTION_BORDERS[0])?;
self.write_centered_with_border(&column_name_line)?;
self.write_centered_with_border(INSTRUCTION_BORDERS[1])?;
for (index, (instruction, position)) in self.chunk.instructions().iter().enumerate() {
let position = position.to_string();
let operation = instruction.operation().to_string();
let info = instruction.disassembly_info();
let instruction_display =
format!("{index:^3} {position:^10} {operation:13} {info:^32}");
let row = format!("{index:^5}{position:^12}{operation:^17}{info:^24}");
self.push_details(&instruction_display);
}
self.write_centered_with_border(&row)?;
}
fn push_local_section(&mut self) {
for line in LOCAL_HEADER {
self.push_header(line);
self.write_centered_with_border_bold(INSTRUCTION_BORDERS[2])?;
Ok(())
}
fn write_local_section(&mut self) -> Result<(), io::Error> {
let mut column_name_line = String::new();
for (column_name, width) in LOCAL_COLUMNS {
column_name_line.push_str(&format!("{:^width$}", column_name, width = width));
}
column_name_line.push('│');
self.write_centered_with_border_bold("Locals")?;
self.write_centered_with_border(LOCAL_BORDERS[0])?;
self.write_centered_with_border(&column_name_line)?;
self.write_centered_with_border(LOCAL_BORDERS[1])?;
for (
index,
Local {
@ -243,34 +301,37 @@ impl<'a> Disassembler<'a> {
.unwrap_or_else(|| "unknown".to_string());
let register_display = format!("R{register_index}");
let scope = scope.to_string();
let local_display = format!(
"{index:^3} {identifier_display:^16} {register_display:^8} {scope:^7} {is_mutable:^7}"
let row = format!(
"│{index:^5}│{identifier_display:^16}│{register_display:^10}│{scope:^7}│{is_mutable:^7}│"
);
self.push_details(&local_display);
}
self.write_centered_with_border(&row)?;
}
fn push_constant_section(&mut self) {
for line in CONSTANT_HEADER {
self.push_header(line);
self.write_centered_with_border(LOCAL_BORDERS[2])?;
Ok(())
}
fn write_constant_section(&mut self) -> Result<(), io::Error> {
let mut column_name_line = String::new();
for (column_name, width) in CONSTANT_COLUMNS {
column_name_line.push_str(&format!("{:^width$}", column_name, width = width));
}
column_name_line.push('│');
self.write_centered_with_border_bold("Constants")?;
self.write_centered_with_border(CONSTANT_BORDERS[0])?;
self.write_centered_with_border(&column_name_line)?;
self.write_centered_with_border(CONSTANT_BORDERS[1])?;
for (index, value) in self.chunk.constants().iter().enumerate() {
if let ConcreteValue::Function(chunk) = value {
let mut function_disassembler = chunk.disassembler().style(self.style);
function_disassembler.indent = self.indent + 1;
let function_disassembly = function_disassembler.disassemble();
self.output.push_str(&function_disassembly);
continue;
}
let is_function = matches!(value, ConcreteValue::Function(_));
let type_display = value.r#type().to_string();
let value_display = {
let value_display = if is_function {
"Function displayed below".to_string()
} else {
let mut value_string = value.to_string();
if value_string.len() > 15 {
@ -279,35 +340,31 @@ impl<'a> Disassembler<'a> {
value_string
};
let constant_display = format!("{index:^3} {type_display:^16} {value_display:^17}");
let constant_display = format!("{index:^5}{type_display:^26}{value_display:^26}");
self.push_details(&constant_display);
self.write_centered_with_border(&constant_display)?;
if let ConcreteValue::Function(chunk) = value {
let function_disassembler = chunk
.disassembler(self.writer)
.style(self.style)
.indent(self.indent + 1);
let original_output_width = self.output_width;
self.output_width = function_disassembler.output_width;
function_disassembler.disassemble()?;
self.write_char('\n')?;
self.output_width = original_output_width;
}
}
pub fn disassemble(mut self) -> String {
let width = Disassembler::default_width();
let top_border = {
let mut border = "".to_string();
border += &"".repeat(width - 2);
border += "";
self.write_centered_with_border(CONSTANT_BORDERS[2])?;
border
};
let section_border = {
let mut border = "".to_string();
border += &"".repeat(width - 2);
border += "";
Ok(())
}
border
};
let bottom_border = {
let mut border = "".to_string();
border += &"".repeat(width - 2);
border += "";
border
};
pub fn disassemble(mut self) -> Result<(), io::Error> {
let name_display = self
.chunk
.name()
@ -324,16 +381,19 @@ impl<'a> Disassembler<'a> {
file_name
})
.unwrap_or("Chunk Disassembly".to_string())
.unwrap_or("Dust Chunk Disassembly".to_string())
});
self.push_border(&top_border);
self.push_header(&name_display);
self.write_page_border(TOP_BORDER)?;
self.write_char('\n')?;
self.write_centered_with_border_bold(&name_display)?;
if let Some(source) = self.source {
self.push_empty();
self.push_source(&source.split_whitespace().collect::<Vec<&str>>().join(" "));
self.push_empty();
let lazily_formatted = source.split_whitespace().collect::<Vec<&str>>().join(" ");
self.write_centered_with_border("")?;
self.write_centered_with_border(&lazily_formatted)?;
self.write_centered_with_border("")?;
}
let info_line = format!(
@ -344,25 +404,21 @@ impl<'a> Disassembler<'a> {
self.chunk.r#type().return_type
);
self.push_chunk_info(&info_line);
self.push_empty();
self.write_centered_with_border_dim(&info_line)?;
self.write_centered_with_border("")?;
if !self.chunk.is_empty() {
self.push_instruction_section();
self.write_instruction_section()?;
}
if !self.chunk.locals().is_empty() {
self.push_border(&section_border);
self.push_local_section();
self.write_local_section()?;
}
if !self.chunk.constants().is_empty() {
self.push_border(&section_border);
self.push_constant_section();
self.write_constant_section()?;
}
self.push_border(&bottom_border);
self.output.to_string()
self.write_page_border(BOTTOM_BORDER)
}
}

View File

@ -0,0 +1,31 @@
use serde::{Deserialize, Serialize};
use crate::Scope;
/// A scoped variable.
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Local {
/// The index of the identifier in the constants table.
pub identifier_index: u8,
/// Stack index where the local's value is stored.
pub register_index: u8,
/// Whether the local is mutable.
pub is_mutable: bool,
/// Scope where the variable was declared.
pub scope: Scope,
}
impl Local {
/// Creates a new Local instance.
pub fn new(identifier_index: u8, register_index: u8, is_mutable: bool, scope: Scope) -> Self {
Self {
identifier_index,
register_index,
is_mutable,
scope,
}
}
}

View File

@ -4,23 +4,27 @@
//! list of locals that can be executed by the Dust virtual machine. Chunks have a name when they
//! belong to a named function.
mod disassembler;
mod local;
mod scope;
pub use disassembler::Disassembler;
pub use local::Local;
pub use scope::Scope;
use std::fmt::{self, Debug, Display, Write};
use std::fmt::{self, Debug, Display, Formatter, Write as FmtWrite};
use std::io::Write;
use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
use smartstring::alias::String;
use crate::{ConcreteValue, FunctionType, Instruction, Scope, Span, Type};
use crate::{ConcreteValue, DustString, FunctionType, Instruction, Span, Type};
/// In-memory representation of a Dust program or function.
///
/// See the [module-level documentation](index.html) for more information.
#[derive(Clone, PartialOrd, Serialize, Deserialize)]
pub struct Chunk {
name: Option<String>,
name: Option<DustString>,
r#type: FunctionType,
instructions: SmallVec<[(Instruction, Span); 32]>,
@ -29,7 +33,7 @@ pub struct Chunk {
}
impl Chunk {
pub fn new(name: Option<String>) -> Self {
pub fn new(name: Option<DustString>) -> Self {
Self {
name,
instructions: SmallVec::new(),
@ -42,24 +46,23 @@ impl Chunk {
},
}
}
pub fn with_data(
name: Option<String>,
name: Option<DustString>,
r#type: FunctionType,
instructions: SmallVec<[(Instruction, Span); 32]>,
constants: SmallVec<[ConcreteValue; 16]>,
locals: SmallVec<[Local; 8]>,
instructions: impl Into<SmallVec<[(Instruction, Span); 32]>>,
constants: impl Into<SmallVec<[ConcreteValue; 16]>>,
locals: impl Into<SmallVec<[Local; 8]>>,
) -> Self {
Self {
name,
r#type,
instructions,
constants,
locals,
instructions: instructions.into(),
constants: constants.into(),
locals: locals.into(),
}
}
pub fn name(&self) -> Option<&String> {
pub fn name(&self) -> Option<&DustString> {
self.name.as_ref()
}
@ -101,28 +104,42 @@ impl Chunk {
.unwrap_or(0)
}
pub fn disassembler(&self) -> Disassembler {
Disassembler::new(self)
pub fn disassembler<'a, W: Write>(&'a self, writer: &'a mut W) -> Disassembler<W> {
Disassembler::new(self, writer)
}
}
impl Display for Chunk {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let disassembly = self.disassembler().style(true).disassemble();
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let mut output = Vec::new();
write!(f, "{disassembly}")
self.disassembler(&mut output)
.style(true)
.disassemble()
.unwrap();
let string = String::from_utf8_lossy(&output);
write!(f, "{string}")
}
}
impl Debug for Chunk {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let disassembly = self.disassembler().style(false).disassemble();
let mut output = Vec::new();
if cfg!(debug_assertions) {
self.disassembler(&mut output)
.style(true)
.disassemble()
.unwrap();
let string = String::from_utf8_lossy(&output);
if cfg!(test) {
f.write_char('\n')?;
}
write!(f, "{}", disassembly)
write!(f, "{string}")
}
}
@ -135,31 +152,3 @@ impl PartialEq for Chunk {
&& self.locals == other.locals
}
}
/// A scoped variable.
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Local {
/// The index of the identifier in the constants table.
pub identifier_index: u8,
/// Stack index where the local's value is stored.
pub register_index: u8,
/// Whether the local is mutable.
pub is_mutable: bool,
/// Scope where the variable was declared.
pub scope: Scope,
}
impl Local {
/// Creates a new Local instance.
pub fn new(identifier_index: u8, register_index: u8, is_mutable: bool, scope: Scope) -> Self {
Self {
identifier_index,
register_index,
is_mutable,
scope,
}
}
}

View File

@ -119,8 +119,12 @@ impl<'src> Compiler<'src> {
.instructions
.into_iter()
.map(|(instruction, _, position)| (instruction, position))
.collect();
let locals = self.locals.into_iter().map(|(local, _)| local).collect();
.collect::<SmallVec<[(Instruction, Span); 32]>>();
let locals = self
.locals
.into_iter()
.map(|(local, _)| local)
.collect::<SmallVec<[Local; 8]>>();
Chunk::with_data(self.self_name, r#type, instructions, self.constants, locals)
}
@ -960,7 +964,7 @@ impl<'src> Compiler<'src> {
let local_index = if let Ok(local_index) = self.get_local_index(identifier) {
local_index
} else if let Some(native_function) = NativeFunction::from_str(identifier) {
return self.parse_native_call(native_function);
return self.parse_call_native(native_function);
} else if self.self_name.as_deref() == Some(identifier) {
let destination = self.next_register();
let load_self = Instruction::from(LoadSelf { destination });
@ -1274,7 +1278,7 @@ impl<'src> Compiler<'src> {
Ok(())
}
fn parse_native_call(&mut self, function: NativeFunction) -> Result<(), CompileError> {
fn parse_call_native(&mut self, function: NativeFunction) -> Result<(), CompileError> {
let start = self.previous_position.0;
let start_register = self.next_register();
@ -1286,8 +1290,9 @@ impl<'src> Compiler<'src> {
self.parse_expression()?;
let actual_register = self.next_register() - 1;
let registers_to_close = actual_register - expected_register;
if expected_register < actual_register {
if registers_to_close > 0 {
let close = Instruction::from(Close {
from: expected_register,
to: actual_register,
@ -1547,8 +1552,8 @@ impl<'src> Compiler<'src> {
self.lexer.skip_to(self.current_position.1);
let function_end = function_compiler.previous_position.1;
let function =
ConcreteValue::function(function_compiler.finish(None, value_parameters.clone()));
let chunk = function_compiler.finish(None, value_parameters.clone());
let function = ConcreteValue::function(chunk);
let constant_index = self.push_or_get_constant(function);
let destination = self.next_register();
let function_type = FunctionType {
@ -1557,23 +1562,22 @@ impl<'src> Compiler<'src> {
return_type,
};
if let Some((identifier, position)) = identifier_info {
let (local_index, _) = self.declare_local(
if let Some((identifier, _)) = identifier_info {
self.declare_local(
identifier,
destination,
Type::function(function_type.clone()),
false,
self.current_scope,
);
let load_constant = Instruction::load_constant(destination, constant_index, false);
let set_local = Instruction::set_local(destination, local_index);
self.emit_instruction(
load_constant,
Type::function(function_type),
Span(function_start, function_end),
);
self.emit_instruction(set_local, Type::None, position);
} else {
let load_constant = Instruction::from(LoadConstant {
destination,
@ -1592,7 +1596,7 @@ impl<'src> Compiler<'src> {
}
fn parse_call(&mut self) -> Result<(), CompileError> {
let (last_instruction, _, _) =
let (last_instruction, last_instruction_type, _) =
self.instructions
.last()
.ok_or_else(|| CompileError::ExpectedExpression {
@ -1607,21 +1611,20 @@ impl<'src> Compiler<'src> {
});
}
let function =
let argument =
last_instruction
.as_argument()
.ok_or_else(|| CompileError::ExpectedExpression {
found: self.previous_token.to_owned(),
position: self.previous_position,
})?;
let register_type = self.get_register_type(function.index())?;
let function_return_type = match register_type {
Type::Function(function_type) => function_type.return_type,
let function_return_type = match last_instruction_type {
Type::Function(function_type) => function_type.return_type.clone(),
Type::SelfChunk => self.return_type.clone().unwrap_or(Type::None),
_ => {
return Err(CompileError::ExpectedFunction {
found: self.previous_token.to_owned(),
actual_type: register_type,
actual_type: last_instruction_type.clone(),
position: self.previous_position,
});
}
@ -1638,7 +1641,7 @@ impl<'src> Compiler<'src> {
self.parse_expression()?;
let actual_register = self.next_register() - 1;
let registers_to_close = actual_register - expected_register;
let registers_to_close = (actual_register - expected_register).saturating_sub(1);
if registers_to_close > 0 {
let close = Instruction::from(Close {
@ -1658,7 +1661,7 @@ impl<'src> Compiler<'src> {
let destination = self.next_register();
let call = Instruction::from(Call {
destination,
function,
function: argument,
argument_count,
});

View File

@ -370,8 +370,16 @@ impl Instruction {
| Operation::LessEqual
| Operation::Negate
| Operation::Not
| Operation::Call
| Operation::CallNative => Some(Argument::Register(self.a)),
| Operation::Call => Some(Argument::Register(self.a)),
Operation::CallNative => {
let function = NativeFunction::from(self.b);
if function.returns_value() {
Some(Argument::Register(self.a))
} else {
None
}
}
_ => None,
}
}
@ -565,9 +573,9 @@ impl Instruction {
let TestSet {
destination,
argument,
test_value: value,
test_value,
} = TestSet::from(self);
let bang = if value { "" } else { "!" };
let bang = if test_value { "" } else { "!" };
format!("if {bang}{argument} {{ JUMP +1 }} else {{ R{destination} = {argument} }}")
}
@ -641,8 +649,14 @@ impl Instruction {
let arguments_start = destination.saturating_sub(argument_count);
let arguments_end = arguments_start + argument_count;
match argument_count {
0 => format!("R{destination} = {function}()"),
1 => format!("R{destination} = {function}(R{arguments_start})"),
_ => {
format!("R{destination} = {function}(R{arguments_start}..R{arguments_end})")
}
}
}
Operation::CallNative => {
let CallNative {
destination,
@ -651,12 +665,23 @@ impl Instruction {
} = CallNative::from(self);
let arguments_start = destination.saturating_sub(argument_count);
let arguments_end = arguments_start + argument_count;
if function.returns_value() {
format!("R{destination} = {function}(R{arguments_start}..R{arguments_end})")
let mut info_string = if function.returns_value() {
format!("R{destination} = ")
} else {
format!("{function}(R{arguments_start}..R{arguments_end})")
String::new()
};
match argument_count {
0 => {
info_string.push_str(function.as_str());
info_string.push_str("()");
}
1 => info_string.push_str(&format!("{function}(R{arguments_start})")),
_ => info_string
.push_str(&format!("{function}(R{arguments_start}..R{arguments_end})")),
}
info_string
}
Operation::Return => {
let Return {

View File

@ -34,20 +34,18 @@ pub mod dust_error;
pub mod instruction;
pub mod lexer;
pub mod native_function;
pub mod scope;
pub mod token;
pub mod r#type;
pub mod value;
pub mod vm;
pub use crate::chunk::{Chunk, Disassembler, Local};
pub use crate::chunk::{Chunk, Disassembler, Local, Scope};
pub use crate::compiler::{compile, CompileError, Compiler};
pub use crate::dust_error::{AnnotatedError, DustError};
pub use crate::instruction::{Argument, Instruction, Operation};
pub use crate::lexer::{lex, LexError, Lexer};
pub use crate::native_function::{NativeFunction, NativeFunctionError};
pub use crate::r#type::{EnumType, FunctionType, StructType, Type, TypeConflict};
pub use crate::scope::Scope;
pub use crate::token::{write_token_list, Token, TokenKind, TokenOwned};
pub use crate::value::{
AbstractValue, ConcreteValue, DustString, RangeValue, Value, ValueError, ValueRef,

View File

@ -0,0 +1,40 @@
use std::panic::{self, Location, PanicHookInfo};
use annotate_snippets::{Level, Renderer, Snippet};
use smallvec::SmallVec;
use crate::{NativeFunctionError, Value, ValueRef, Vm};
pub fn panic(
vm: &Vm,
arguments: SmallVec<[ValueRef; 4]>,
) -> Result<Option<Value>, NativeFunctionError> {
let mut message = String::new();
for value_ref in arguments {
let string = match value_ref.display(vm) {
Ok(string) => string,
Err(error) => return Err(NativeFunctionError::Vm(Box::new(error))),
};
message.push_str(&string);
}
let position = vm.current_position();
let error_output = Level::Error.title("Explicit Panic").snippet(
Snippet::source(vm.source()).fold(false).annotation(
Level::Error
.span(position.0..position.1)
.label("Explicit panic occured here"),
),
);
let renderer = Renderer::plain();
let report = renderer.render(error_output).to_string();
panic::set_hook(Box::new(move |_| {
println!("{}", report);
println!("Panic Message: {}", message);
}));
panic!();
}

View File

@ -0,0 +1,79 @@
use std::io::{stdin, stdout, Write};
use smallvec::SmallVec;
use crate::{ConcreteValue, NativeFunctionError, Value, ValueRef, Vm};
pub fn read_line(
vm: &Vm,
_: SmallVec<[ValueRef; 4]>,
) -> Result<Option<Value>, NativeFunctionError> {
let mut buffer = String::new();
match stdin().read_line(&mut buffer) {
Ok(_) => {
let length = buffer.len();
buffer.truncate(length.saturating_sub(1));
Ok(Some(Value::Concrete(ConcreteValue::string(buffer))))
}
Err(error) => Err(NativeFunctionError::Io {
error: error.kind(),
position: vm.current_position(),
}),
}
}
pub fn write(
vm: &Vm,
arguments: SmallVec<[ValueRef; 4]>,
) -> Result<Option<Value>, NativeFunctionError> {
let mut stdout = stdout();
for argument in arguments {
let string = match argument.display(vm) {
Ok(string) => string,
Err(error) => return Err(NativeFunctionError::Vm(Box::new(error))),
};
stdout
.write_all(string.as_bytes())
.map_err(|io_error| NativeFunctionError::Io {
error: io_error.kind(),
position: vm.current_position(),
})?;
}
Ok(None)
}
pub fn write_line(
vm: &Vm,
arguments: SmallVec<[ValueRef; 4]>,
) -> Result<Option<Value>, NativeFunctionError> {
let mut stdout = stdout();
for argument in arguments {
let string = match argument.display(vm) {
Ok(string) => string,
Err(error) => return Err(NativeFunctionError::Vm(Box::new(error))),
};
stdout
.write_all(string.as_bytes())
.map_err(|io_error| NativeFunctionError::Io {
error: io_error.kind(),
position: vm.current_position(),
})?;
}
stdout
.write(b"\n")
.map_err(|io_error| NativeFunctionError::Io {
error: io_error.kind(),
position: vm.current_position(),
})?;
Ok(None)
}

View File

@ -1,151 +0,0 @@
use std::io::{self, stdout, Write};
use crate::{ConcreteValue, Instruction, NativeFunctionError, Value, Vm, VmError};
pub fn panic<'a>(vm: &'a Vm<'a>, instruction: Instruction) -> Result<Option<Value>, VmError> {
let argument_count = instruction.c;
let message = if argument_count == 0 {
None
} else {
let mut message = String::new();
for argument_index in 0..argument_count {
if argument_index != 0 {
message.push(' ');
}
let argument = if let Some(value) = vm.open_register_allow_empty(argument_index)? {
value
} else {
continue;
};
let argument_string = argument.display(vm)?;
message.push_str(&argument_string);
}
Some(message)
};
Err(VmError::NativeFunction(NativeFunctionError::Panic {
message,
position: vm.current_position(),
}))
}
pub fn to_string<'a>(vm: &'a Vm<'a>, instruction: Instruction) -> Result<Option<Value>, VmError> {
let argument_count = instruction.c;
if argument_count != 1 {
return Err(VmError::NativeFunction(
NativeFunctionError::ExpectedArgumentCount {
expected: 1,
found: argument_count as usize,
position: vm.current_position(),
},
));
}
let mut string = String::new();
for argument_index in 0..argument_count {
let argument = if let Some(value) = vm.open_register_allow_empty(argument_index)? {
value
} else {
continue;
};
let argument_string = argument.display(vm)?;
string.push_str(&argument_string);
}
Ok(Some(Value::Concrete(ConcreteValue::string(string))))
}
pub fn read_line<'a>(vm: &'a Vm<'a>, instruction: Instruction) -> Result<Option<Value>, VmError> {
let argument_count = instruction.c;
if argument_count != 0 {
return Err(VmError::NativeFunction(
NativeFunctionError::ExpectedArgumentCount {
expected: 0,
found: argument_count as usize,
position: vm.current_position(),
},
));
}
let mut buffer = String::new();
match io::stdin().read_line(&mut buffer) {
Ok(_) => {
buffer = buffer.trim_end_matches('\n').to_string();
Ok(Some(Value::Concrete(ConcreteValue::string(buffer))))
}
Err(error) => Err(VmError::NativeFunction(NativeFunctionError::Io {
error: error.kind(),
position: vm.current_position(),
})),
}
}
pub fn write<'a>(vm: &'a Vm<'a>, instruction: Instruction) -> Result<Option<Value>, VmError> {
let to_register = instruction.a;
let argument_count = instruction.c;
let mut stdout = stdout();
let map_err = |io_error: io::Error| {
VmError::NativeFunction(NativeFunctionError::Io {
error: io_error.kind(),
position: vm.current_position(),
})
};
let first_argument = to_register.saturating_sub(argument_count);
for argument_index in first_argument..to_register {
let argument = if let Some(value) = vm.open_register_allow_empty(argument_index)? {
value
} else {
continue;
};
let argument_string = argument.display(vm)?;
stdout
.write_all(argument_string.as_bytes())
.map_err(map_err)?;
}
Ok(None)
}
pub fn write_line<'a>(vm: &'a Vm<'a>, instruction: Instruction) -> Result<Option<Value>, VmError> {
let to_register = instruction.a;
let argument_count = instruction.c;
let mut stdout = stdout();
let map_err = |io_error: io::Error| {
VmError::NativeFunction(NativeFunctionError::Io {
error: io_error.kind(),
position: vm.current_position(),
})
};
let first_argument = to_register.saturating_sub(argument_count);
for argument_index in first_argument..to_register {
let argument = if let Some(value) = vm.open_register_allow_empty(argument_index)? {
value
} else {
continue;
};
let argument_string = argument.display(vm)?;
stdout
.write_all(argument_string.as_bytes())
.map_err(map_err)?;
}
stdout.write(b"\n").map_err(map_err)?;
Ok(None)
}

View File

@ -2,18 +2,20 @@
//!
//! Native functions are used to implement features that are not possible to implement in Dust
//! itself or that are more efficient to implement in Rust.
mod logic;
mod assertion;
mod io;
mod string;
use std::{
fmt::{self, Display, Formatter},
io::{self},
string::{self},
io::ErrorKind as IoErrorKind,
string::ParseError,
};
use serde::{Deserialize, Serialize};
use smallvec::smallvec;
use smallvec::{smallvec, SmallVec};
use crate::{AnnotatedError, FunctionType, Instruction, Span, Type, Value, Vm, VmError};
use crate::{AnnotatedError, FunctionType, Span, Type, Value, ValueRef, Vm, VmError};
macro_rules! define_native_function {
($(($name:ident, $bytes:literal, $str:expr, $type:expr, $function:expr)),*) => {
@ -28,14 +30,14 @@ macro_rules! define_native_function {
}
impl NativeFunction {
pub fn call(
pub fn call<'a>(
&self,
vm: &mut Vm,
instruction: Instruction,
) -> Result<Option<Value>, VmError> {
vm: &Vm<'a>,
arguments: SmallVec<[ValueRef<'a>; 4]>,
) -> Result<Option<Value>, NativeFunctionError> {
match self {
$(
NativeFunction::$name => $function(vm, instruction),
NativeFunction::$name => $function(vm, arguments),
)*
}
}
@ -134,7 +136,7 @@ define_native_function! {
value_parameters: None,
return_type: Type::None
},
logic::panic
assertion::panic
),
// // Type conversion
@ -151,7 +153,7 @@ define_native_function! {
value_parameters: Some(smallvec![(0, Type::Any)]),
return_type: Type::String
},
logic::to_string
string::to_string
),
// // List and string
@ -212,7 +214,7 @@ define_native_function! {
value_parameters: None,
return_type: Type::String
},
logic::read_line
io::read_line
),
// (ReadTo, 51_u8, "read_to", false),
// (ReadUntil, 52_u8, "read_until", true),
@ -228,7 +230,7 @@ define_native_function! {
value_parameters: Some(smallvec![(0, Type::String)]),
return_type: Type::None
},
logic::write
io::write
),
// (WriteFile, 56_u8, "write_file", false),
(
@ -240,7 +242,7 @@ define_native_function! {
value_parameters: Some(smallvec![(0, Type::String)]),
return_type: Type::None
},
logic::write_line
io::write_line
)
// // Random
@ -260,13 +262,14 @@ pub enum NativeFunctionError {
position: Span,
},
Parse {
error: string::ParseError,
error: ParseError,
position: Span,
},
Io {
error: io::ErrorKind,
error: IoErrorKind,
position: Span,
},
Vm(Box<VmError>),
}
impl AnnotatedError for NativeFunctionError {
@ -282,6 +285,7 @@ impl AnnotatedError for NativeFunctionError {
NativeFunctionError::Panic { .. } => "Explicit panic",
NativeFunctionError::Parse { .. } => "Failed to parse value",
NativeFunctionError::Io { .. } => "I/O error",
NativeFunctionError::Vm(error) => error.description(),
}
}
@ -293,6 +297,7 @@ impl AnnotatedError for NativeFunctionError {
NativeFunctionError::Panic { message, .. } => message.clone(),
NativeFunctionError::Parse { error, .. } => Some(format!("{}", error)),
NativeFunctionError::Io { error, .. } => Some(format!("{}", error)),
NativeFunctionError::Vm(error) => error.details(),
}
}
@ -302,6 +307,7 @@ impl AnnotatedError for NativeFunctionError {
NativeFunctionError::Panic { position, .. } => *position,
NativeFunctionError::Parse { position, .. } => *position,
NativeFunctionError::Io { position, .. } => *position,
NativeFunctionError::Vm(error) => error.position(),
}
}
}

View File

@ -0,0 +1,25 @@
use smallvec::SmallVec;
use crate::{ConcreteValue, NativeFunctionError, Value, ValueRef, Vm};
pub fn to_string(
vm: &Vm,
arguments: SmallVec<[ValueRef; 4]>,
) -> Result<Option<Value>, NativeFunctionError> {
if arguments.len() != 1 {
return Err(NativeFunctionError::ExpectedArgumentCount {
expected: 1,
found: 0,
position: vm.current_position(),
});
}
let argument_string = match arguments[0].display(vm) {
Ok(string) => string,
Err(error) => return Err(NativeFunctionError::Vm(Box::new(error))),
};
Ok(Some(Value::Concrete(ConcreteValue::string(
argument_string,
))))
}

View File

@ -1,11 +1,11 @@
use std::fmt::{self, Display, Formatter};
use crate::{vm::Pointer, ConcreteValue, Value, ValueRef, Vm, VmError};
use crate::{vm::Pointer, ConcreteValue, DustString, Value, ValueRef, Vm, VmError};
#[derive(Debug, PartialEq, PartialOrd)]
pub enum AbstractValue {
FunctionSelf,
List { items: Vec<Pointer> },
List { item_pointers: Vec<Pointer> },
}
impl AbstractValue {
@ -20,25 +20,34 @@ impl AbstractValue {
pub fn to_concrete_owned(&self, vm: &Vm) -> Result<ConcreteValue, VmError> {
match self {
AbstractValue::FunctionSelf => Ok(ConcreteValue::function(vm.chunk().clone())),
AbstractValue::List { items, .. } => {
let mut resolved_items = Vec::with_capacity(items.len());
AbstractValue::List { item_pointers, .. } => {
let mut items: Vec<ConcreteValue> = Vec::with_capacity(item_pointers.len());
for pointer in items {
let resolved_item = vm.follow_pointer(*pointer)?.into_concrete_owned(vm)?;
for pointer in item_pointers {
let item_option = vm.follow_pointer_allow_empty(*pointer)?;
let item = match item_option {
Some(value_ref) => value_ref.into_concrete_owned(vm)?,
None => continue,
};
resolved_items.push(resolved_item);
items.push(item);
}
Ok(ConcreteValue::List(resolved_items))
Ok(ConcreteValue::List(items))
}
}
}
pub fn display(&self, vm: &Vm) -> Result<String, VmError> {
pub fn to_dust_string(&self, vm: &Vm) -> Result<DustString, VmError> {
let mut display = DustString::new();
match self {
AbstractValue::FunctionSelf => Ok("self".to_string()),
AbstractValue::List { items, .. } => {
let mut display = "[".to_string();
AbstractValue::FunctionSelf => display.push_str("self"),
AbstractValue::List {
item_pointers: items,
..
} => {
display.push('[');
for (i, item) in items.iter().enumerate() {
if i > 0 {
@ -51,12 +60,12 @@ impl AbstractValue {
}
display.push(']');
}
}
Ok(display)
}
}
}
}
impl Clone for AbstractValue {
fn clone(&self) -> Self {
@ -64,8 +73,10 @@ impl Clone for AbstractValue {
match self {
AbstractValue::FunctionSelf => AbstractValue::FunctionSelf,
AbstractValue::List { items } => AbstractValue::List {
items: items.clone(),
AbstractValue::List {
item_pointers: items,
} => AbstractValue::List {
item_pointers: items.clone(),
},
}
}
@ -75,7 +86,10 @@ impl Display for AbstractValue {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
AbstractValue::FunctionSelf => write!(f, "self"),
AbstractValue::List { items, .. } => {
AbstractValue::List {
item_pointers: items,
..
} => {
write!(f, "[")?;
for (i, item) in items.iter().enumerate() {

View File

@ -51,6 +51,10 @@ impl ConcreteValue {
}
}
pub fn to_dust_string(&self) -> DustString {
DustString::from(self.to_string())
}
pub fn r#type(&self) -> Type {
match self {
ConcreteValue::Boolean(_) => Type::Boolean,

View File

@ -71,10 +71,10 @@ impl ValueRef<'_> {
}
}
pub fn display(&self, vm: &Vm) -> Result<String, VmError> {
pub fn display(&self, vm: &Vm) -> Result<DustString, VmError> {
match self {
ValueRef::Abstract(abstract_value) => abstract_value.display(vm),
ValueRef::Concrete(concrete_value) => Ok(concrete_value.to_string()),
ValueRef::Abstract(abstract_value) => abstract_value.to_dust_string(vm),
ValueRef::Concrete(concrete_value) => Ok(concrete_value.to_dust_string()),
}
}

View File

@ -49,6 +49,10 @@ impl<'a> Vm<'a> {
self.chunk
}
pub fn source(&self) -> &'a str {
self.source
}
pub fn current_position(&self) -> Span {
let index = self.ip.saturating_sub(1);
let (_, position) = self.chunk.instructions()[index];
@ -140,8 +144,12 @@ impl<'a> Vm<'a> {
pointers.push(pointer);
}
let register =
Register::Value(AbstractValue::List { items: pointers }.to_value());
let register = Register::Value(
AbstractValue::List {
item_pointers: pointers,
}
.to_value(),
);
self.set_register(destination, register)?;
}
@ -501,7 +509,33 @@ impl<'a> Vm<'a> {
function,
argument_count,
} = CallNative::from(&instruction);
let return_value = function.call(self, instruction)?;
let first_argument_index = (destination - argument_count) as usize;
let argument_range = first_argument_index..destination as usize;
let argument_registers = &self.stack[argument_range];
let mut arguments: SmallVec<[ValueRef; 4]> = SmallVec::new();
for register in argument_registers {
let value = match register {
Register::Value(value) => value.to_ref(),
Register::Pointer(pointer) => {
let value_option = self.follow_pointer_allow_empty(*pointer)?;
match value_option {
Some(value) => value,
None => continue,
}
}
Register::Empty => continue,
};
arguments.push(value);
}
let call_result = function.call(self, arguments);
let return_value = match call_result {
Ok(value_option) => value_option,
Err(error) => return Err(VmError::NativeFunction(error)),
};
if let Some(value) = return_value {
let register = Register::Value(value);
@ -567,6 +601,41 @@ impl<'a> Vm<'a> {
}
}
pub(crate) fn follow_pointer_allow_empty(
&self,
pointer: Pointer,
) -> Result<Option<ValueRef>, VmError> {
match pointer {
Pointer::Stack(register_index) => self.open_register_allow_empty(register_index),
Pointer::Constant(constant_index) => {
let constant = self.get_constant(constant_index);
Ok(Some(ValueRef::Concrete(constant)))
}
Pointer::ParentStack(register_index) => {
let parent = self
.parent
.as_ref()
.ok_or_else(|| VmError::ExpectedParent {
position: self.current_position(),
})?;
parent.open_register_allow_empty(register_index)
}
Pointer::ParentConstant(constant_index) => {
let parent = self
.parent
.as_ref()
.ok_or_else(|| VmError::ExpectedParent {
position: self.current_position(),
})?;
let constant = parent.get_constant(constant_index);
Ok(Some(ValueRef::Concrete(constant)))
}
}
}
fn open_register(&self, register_index: u8) -> Result<ValueRef, VmError> {
let register_index = register_index as usize;
@ -589,10 +658,7 @@ impl<'a> Vm<'a> {
}
}
pub(crate) fn open_register_allow_empty(
&self,
register_index: u8,
) -> Result<Option<ValueRef>, VmError> {
fn open_register_allow_empty(&self, register_index: u8) -> Result<Option<ValueRef>, VmError> {
let register_index = register_index as usize;
let register =
self.stack

View File

@ -14,10 +14,7 @@ fn constant() {
return_type: Type::Integer
},
vec![
(
Instruction::load_constant(Destination::Register(0), 0, false),
Span(0, 2)
),
(Instruction::load_constant(0, 0, false), Span(0, 2)),
(Instruction::r#return(true), Span(2, 2))
],
vec![ConcreteValue::Integer(42)],