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; return;
} }
}; };
let disassembly = chunk let mut stdout = stdout().lock();
.disassembler()
chunk
.disassembler(&mut stdout)
.style(mode.style) .style(mode.style)
.source(&source) .source(&source)
.disassemble(); .disassemble();
println!("{}", disassembly);
return; return;
} }

View File

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

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 //! list of locals that can be executed by the Dust virtual machine. Chunks have a name when they
//! belong to a named function. //! belong to a named function.
mod disassembler; mod disassembler;
mod local;
mod scope;
pub use disassembler::Disassembler; 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 serde::{Deserialize, Serialize};
use smallvec::SmallVec; 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. /// In-memory representation of a Dust program or function.
/// ///
/// See the [module-level documentation](index.html) for more information. /// See the [module-level documentation](index.html) for more information.
#[derive(Clone, PartialOrd, Serialize, Deserialize)] #[derive(Clone, PartialOrd, Serialize, Deserialize)]
pub struct Chunk { pub struct Chunk {
name: Option<String>, name: Option<DustString>,
r#type: FunctionType, r#type: FunctionType,
instructions: SmallVec<[(Instruction, Span); 32]>, instructions: SmallVec<[(Instruction, Span); 32]>,
@ -29,7 +33,7 @@ pub struct Chunk {
} }
impl Chunk { impl Chunk {
pub fn new(name: Option<String>) -> Self { pub fn new(name: Option<DustString>) -> Self {
Self { Self {
name, name,
instructions: SmallVec::new(), instructions: SmallVec::new(),
@ -42,24 +46,23 @@ impl Chunk {
}, },
} }
} }
pub fn with_data( pub fn with_data(
name: Option<String>, name: Option<DustString>,
r#type: FunctionType, r#type: FunctionType,
instructions: SmallVec<[(Instruction, Span); 32]>, instructions: impl Into<SmallVec<[(Instruction, Span); 32]>>,
constants: SmallVec<[ConcreteValue; 16]>, constants: impl Into<SmallVec<[ConcreteValue; 16]>>,
locals: SmallVec<[Local; 8]>, locals: impl Into<SmallVec<[Local; 8]>>,
) -> Self { ) -> Self {
Self { Self {
name, name,
r#type, r#type,
instructions, instructions: instructions.into(),
constants, constants: constants.into(),
locals, locals: locals.into(),
} }
} }
pub fn name(&self) -> Option<&String> { pub fn name(&self) -> Option<&DustString> {
self.name.as_ref() self.name.as_ref()
} }
@ -101,28 +104,42 @@ impl Chunk {
.unwrap_or(0) .unwrap_or(0)
} }
pub fn disassembler(&self) -> Disassembler { pub fn disassembler<'a, W: Write>(&'a self, writer: &'a mut W) -> Disassembler<W> {
Disassembler::new(self) Disassembler::new(self, writer)
} }
} }
impl Display for Chunk { impl Display for Chunk {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let disassembly = self.disassembler().style(true).disassemble(); 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 { impl Debug for Chunk {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 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')?; f.write_char('\n')?;
} }
write!(f, "{}", disassembly) write!(f, "{string}")
} }
} }
@ -135,31 +152,3 @@ impl PartialEq for Chunk {
&& self.locals == other.locals && 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 .instructions
.into_iter() .into_iter()
.map(|(instruction, _, position)| (instruction, position)) .map(|(instruction, _, position)| (instruction, position))
.collect(); .collect::<SmallVec<[(Instruction, Span); 32]>>();
let locals = self.locals.into_iter().map(|(local, _)| local).collect(); 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) 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) { let local_index = if let Ok(local_index) = self.get_local_index(identifier) {
local_index local_index
} else if let Some(native_function) = NativeFunction::from_str(identifier) { } 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) { } else if self.self_name.as_deref() == Some(identifier) {
let destination = self.next_register(); let destination = self.next_register();
let load_self = Instruction::from(LoadSelf { destination }); let load_self = Instruction::from(LoadSelf { destination });
@ -1274,7 +1278,7 @@ impl<'src> Compiler<'src> {
Ok(()) 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 = self.previous_position.0;
let start_register = self.next_register(); let start_register = self.next_register();
@ -1286,8 +1290,9 @@ impl<'src> Compiler<'src> {
self.parse_expression()?; self.parse_expression()?;
let actual_register = self.next_register() - 1; 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 { let close = Instruction::from(Close {
from: expected_register, from: expected_register,
to: actual_register, to: actual_register,
@ -1547,8 +1552,8 @@ impl<'src> Compiler<'src> {
self.lexer.skip_to(self.current_position.1); self.lexer.skip_to(self.current_position.1);
let function_end = function_compiler.previous_position.1; let function_end = function_compiler.previous_position.1;
let function = let chunk = function_compiler.finish(None, value_parameters.clone());
ConcreteValue::function(function_compiler.finish(None, value_parameters.clone())); let function = ConcreteValue::function(chunk);
let constant_index = self.push_or_get_constant(function); let constant_index = self.push_or_get_constant(function);
let destination = self.next_register(); let destination = self.next_register();
let function_type = FunctionType { let function_type = FunctionType {
@ -1557,23 +1562,22 @@ impl<'src> Compiler<'src> {
return_type, return_type,
}; };
if let Some((identifier, position)) = identifier_info { if let Some((identifier, _)) = identifier_info {
let (local_index, _) = self.declare_local( self.declare_local(
identifier, identifier,
destination, destination,
Type::function(function_type.clone()), Type::function(function_type.clone()),
false, false,
self.current_scope, self.current_scope,
); );
let load_constant = Instruction::load_constant(destination, constant_index, false); let load_constant = Instruction::load_constant(destination, constant_index, false);
let set_local = Instruction::set_local(destination, local_index);
self.emit_instruction( self.emit_instruction(
load_constant, load_constant,
Type::function(function_type), Type::function(function_type),
Span(function_start, function_end), Span(function_start, function_end),
); );
self.emit_instruction(set_local, Type::None, position);
} else { } else {
let load_constant = Instruction::from(LoadConstant { let load_constant = Instruction::from(LoadConstant {
destination, destination,
@ -1592,7 +1596,7 @@ impl<'src> Compiler<'src> {
} }
fn parse_call(&mut self) -> Result<(), CompileError> { fn parse_call(&mut self) -> Result<(), CompileError> {
let (last_instruction, _, _) = let (last_instruction, last_instruction_type, _) =
self.instructions self.instructions
.last() .last()
.ok_or_else(|| CompileError::ExpectedExpression { .ok_or_else(|| CompileError::ExpectedExpression {
@ -1607,21 +1611,20 @@ impl<'src> Compiler<'src> {
}); });
} }
let function = let argument =
last_instruction last_instruction
.as_argument() .as_argument()
.ok_or_else(|| CompileError::ExpectedExpression { .ok_or_else(|| CompileError::ExpectedExpression {
found: self.previous_token.to_owned(), found: self.previous_token.to_owned(),
position: self.previous_position, position: self.previous_position,
})?; })?;
let register_type = self.get_register_type(function.index())?; let function_return_type = match last_instruction_type {
let function_return_type = match register_type { Type::Function(function_type) => function_type.return_type.clone(),
Type::Function(function_type) => function_type.return_type,
Type::SelfChunk => self.return_type.clone().unwrap_or(Type::None), Type::SelfChunk => self.return_type.clone().unwrap_or(Type::None),
_ => { _ => {
return Err(CompileError::ExpectedFunction { return Err(CompileError::ExpectedFunction {
found: self.previous_token.to_owned(), found: self.previous_token.to_owned(),
actual_type: register_type, actual_type: last_instruction_type.clone(),
position: self.previous_position, position: self.previous_position,
}); });
} }
@ -1638,7 +1641,7 @@ impl<'src> Compiler<'src> {
self.parse_expression()?; self.parse_expression()?;
let actual_register = self.next_register() - 1; 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 { if registers_to_close > 0 {
let close = Instruction::from(Close { let close = Instruction::from(Close {
@ -1658,7 +1661,7 @@ impl<'src> Compiler<'src> {
let destination = self.next_register(); let destination = self.next_register();
let call = Instruction::from(Call { let call = Instruction::from(Call {
destination, destination,
function, function: argument,
argument_count, argument_count,
}); });

View File

@ -370,8 +370,16 @@ impl Instruction {
| Operation::LessEqual | Operation::LessEqual
| Operation::Negate | Operation::Negate
| Operation::Not | Operation::Not
| Operation::Call | Operation::Call => Some(Argument::Register(self.a)),
| Operation::CallNative => 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, _ => None,
} }
} }
@ -565,9 +573,9 @@ impl Instruction {
let TestSet { let TestSet {
destination, destination,
argument, argument,
test_value: value, test_value,
} = TestSet::from(self); } = TestSet::from(self);
let bang = if value { "" } else { "!" }; let bang = if test_value { "" } else { "!" };
format!("if {bang}{argument} {{ JUMP +1 }} else {{ R{destination} = {argument} }}") 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_start = destination.saturating_sub(argument_count);
let arguments_end = arguments_start + 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})") format!("R{destination} = {function}(R{arguments_start}..R{arguments_end})")
} }
}
}
Operation::CallNative => { Operation::CallNative => {
let CallNative { let CallNative {
destination, destination,
@ -651,12 +665,23 @@ impl Instruction {
} = CallNative::from(self); } = CallNative::from(self);
let arguments_start = destination.saturating_sub(argument_count); let arguments_start = destination.saturating_sub(argument_count);
let arguments_end = arguments_start + argument_count; let arguments_end = arguments_start + argument_count;
let mut info_string = if function.returns_value() {
if function.returns_value() { format!("R{destination} = ")
format!("R{destination} = {function}(R{arguments_start}..R{arguments_end})")
} else { } 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 => { Operation::Return => {
let Return { let Return {

View File

@ -34,20 +34,18 @@ pub mod dust_error;
pub mod instruction; pub mod instruction;
pub mod lexer; pub mod lexer;
pub mod native_function; pub mod native_function;
pub mod scope;
pub mod token; pub mod token;
pub mod r#type; pub mod r#type;
pub mod value; pub mod value;
pub mod vm; 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::compiler::{compile, CompileError, Compiler};
pub use crate::dust_error::{AnnotatedError, DustError}; pub use crate::dust_error::{AnnotatedError, DustError};
pub use crate::instruction::{Argument, Instruction, Operation}; pub use crate::instruction::{Argument, Instruction, Operation};
pub use crate::lexer::{lex, LexError, Lexer}; pub use crate::lexer::{lex, LexError, Lexer};
pub use crate::native_function::{NativeFunction, NativeFunctionError}; pub use crate::native_function::{NativeFunction, NativeFunctionError};
pub use crate::r#type::{EnumType, FunctionType, StructType, Type, TypeConflict}; 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::token::{write_token_list, Token, TokenKind, TokenOwned};
pub use crate::value::{ pub use crate::value::{
AbstractValue, ConcreteValue, DustString, RangeValue, Value, ValueError, ValueRef, 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 //! 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. //! itself or that are more efficient to implement in Rust.
mod logic; mod assertion;
mod io;
mod string;
use std::{ use std::{
fmt::{self, Display, Formatter}, fmt::{self, Display, Formatter},
io::{self}, io::ErrorKind as IoErrorKind,
string::{self}, string::ParseError,
}; };
use serde::{Deserialize, Serialize}; 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 { macro_rules! define_native_function {
($(($name:ident, $bytes:literal, $str:expr, $type:expr, $function:expr)),*) => { ($(($name:ident, $bytes:literal, $str:expr, $type:expr, $function:expr)),*) => {
@ -28,14 +30,14 @@ macro_rules! define_native_function {
} }
impl NativeFunction { impl NativeFunction {
pub fn call( pub fn call<'a>(
&self, &self,
vm: &mut Vm, vm: &Vm<'a>,
instruction: Instruction, arguments: SmallVec<[ValueRef<'a>; 4]>,
) -> Result<Option<Value>, VmError> { ) -> Result<Option<Value>, NativeFunctionError> {
match self { match self {
$( $(
NativeFunction::$name => $function(vm, instruction), NativeFunction::$name => $function(vm, arguments),
)* )*
} }
} }
@ -134,7 +136,7 @@ define_native_function! {
value_parameters: None, value_parameters: None,
return_type: Type::None return_type: Type::None
}, },
logic::panic assertion::panic
), ),
// // Type conversion // // Type conversion
@ -151,7 +153,7 @@ define_native_function! {
value_parameters: Some(smallvec![(0, Type::Any)]), value_parameters: Some(smallvec![(0, Type::Any)]),
return_type: Type::String return_type: Type::String
}, },
logic::to_string string::to_string
), ),
// // List and string // // List and string
@ -212,7 +214,7 @@ define_native_function! {
value_parameters: None, value_parameters: None,
return_type: Type::String return_type: Type::String
}, },
logic::read_line io::read_line
), ),
// (ReadTo, 51_u8, "read_to", false), // (ReadTo, 51_u8, "read_to", false),
// (ReadUntil, 52_u8, "read_until", true), // (ReadUntil, 52_u8, "read_until", true),
@ -228,7 +230,7 @@ define_native_function! {
value_parameters: Some(smallvec![(0, Type::String)]), value_parameters: Some(smallvec![(0, Type::String)]),
return_type: Type::None return_type: Type::None
}, },
logic::write io::write
), ),
// (WriteFile, 56_u8, "write_file", false), // (WriteFile, 56_u8, "write_file", false),
( (
@ -240,7 +242,7 @@ define_native_function! {
value_parameters: Some(smallvec![(0, Type::String)]), value_parameters: Some(smallvec![(0, Type::String)]),
return_type: Type::None return_type: Type::None
}, },
logic::write_line io::write_line
) )
// // Random // // Random
@ -260,13 +262,14 @@ pub enum NativeFunctionError {
position: Span, position: Span,
}, },
Parse { Parse {
error: string::ParseError, error: ParseError,
position: Span, position: Span,
}, },
Io { Io {
error: io::ErrorKind, error: IoErrorKind,
position: Span, position: Span,
}, },
Vm(Box<VmError>),
} }
impl AnnotatedError for NativeFunctionError { impl AnnotatedError for NativeFunctionError {
@ -282,6 +285,7 @@ impl AnnotatedError for NativeFunctionError {
NativeFunctionError::Panic { .. } => "Explicit panic", NativeFunctionError::Panic { .. } => "Explicit panic",
NativeFunctionError::Parse { .. } => "Failed to parse value", NativeFunctionError::Parse { .. } => "Failed to parse value",
NativeFunctionError::Io { .. } => "I/O error", NativeFunctionError::Io { .. } => "I/O error",
NativeFunctionError::Vm(error) => error.description(),
} }
} }
@ -293,6 +297,7 @@ impl AnnotatedError for NativeFunctionError {
NativeFunctionError::Panic { message, .. } => message.clone(), NativeFunctionError::Panic { message, .. } => message.clone(),
NativeFunctionError::Parse { error, .. } => Some(format!("{}", error)), NativeFunctionError::Parse { error, .. } => Some(format!("{}", error)),
NativeFunctionError::Io { 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::Panic { position, .. } => *position,
NativeFunctionError::Parse { position, .. } => *position, NativeFunctionError::Parse { position, .. } => *position,
NativeFunctionError::Io { 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 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)] #[derive(Debug, PartialEq, PartialOrd)]
pub enum AbstractValue { pub enum AbstractValue {
FunctionSelf, FunctionSelf,
List { items: Vec<Pointer> }, List { item_pointers: Vec<Pointer> },
} }
impl AbstractValue { impl AbstractValue {
@ -20,25 +20,34 @@ impl AbstractValue {
pub fn to_concrete_owned(&self, vm: &Vm) -> Result<ConcreteValue, VmError> { pub fn to_concrete_owned(&self, vm: &Vm) -> Result<ConcreteValue, VmError> {
match self { match self {
AbstractValue::FunctionSelf => Ok(ConcreteValue::function(vm.chunk().clone())), AbstractValue::FunctionSelf => Ok(ConcreteValue::function(vm.chunk().clone())),
AbstractValue::List { items, .. } => { AbstractValue::List { item_pointers, .. } => {
let mut resolved_items = Vec::with_capacity(items.len()); let mut items: Vec<ConcreteValue> = Vec::with_capacity(item_pointers.len());
for pointer in items { for pointer in item_pointers {
let resolved_item = vm.follow_pointer(*pointer)?.into_concrete_owned(vm)?; 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 { match self {
AbstractValue::FunctionSelf => Ok("self".to_string()), AbstractValue::FunctionSelf => display.push_str("self"),
AbstractValue::List { items, .. } => { AbstractValue::List {
let mut display = "[".to_string(); item_pointers: items,
..
} => {
display.push('[');
for (i, item) in items.iter().enumerate() { for (i, item) in items.iter().enumerate() {
if i > 0 { if i > 0 {
@ -51,11 +60,11 @@ impl AbstractValue {
} }
display.push(']'); display.push(']');
}
}
Ok(display) Ok(display)
} }
}
}
} }
impl Clone for AbstractValue { impl Clone for AbstractValue {
@ -64,8 +73,10 @@ impl Clone for AbstractValue {
match self { match self {
AbstractValue::FunctionSelf => AbstractValue::FunctionSelf, AbstractValue::FunctionSelf => AbstractValue::FunctionSelf,
AbstractValue::List { items } => AbstractValue::List { AbstractValue::List {
items: items.clone(), 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 { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self { match self {
AbstractValue::FunctionSelf => write!(f, "self"), AbstractValue::FunctionSelf => write!(f, "self"),
AbstractValue::List { items, .. } => { AbstractValue::List {
item_pointers: items,
..
} => {
write!(f, "[")?; write!(f, "[")?;
for (i, item) in items.iter().enumerate() { 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 { pub fn r#type(&self) -> Type {
match self { match self {
ConcreteValue::Boolean(_) => Type::Boolean, 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 { match self {
ValueRef::Abstract(abstract_value) => abstract_value.display(vm), ValueRef::Abstract(abstract_value) => abstract_value.to_dust_string(vm),
ValueRef::Concrete(concrete_value) => Ok(concrete_value.to_string()), ValueRef::Concrete(concrete_value) => Ok(concrete_value.to_dust_string()),
} }
} }

View File

@ -49,6 +49,10 @@ impl<'a> Vm<'a> {
self.chunk self.chunk
} }
pub fn source(&self) -> &'a str {
self.source
}
pub fn current_position(&self) -> Span { pub fn current_position(&self) -> Span {
let index = self.ip.saturating_sub(1); let index = self.ip.saturating_sub(1);
let (_, position) = self.chunk.instructions()[index]; let (_, position) = self.chunk.instructions()[index];
@ -140,8 +144,12 @@ impl<'a> Vm<'a> {
pointers.push(pointer); pointers.push(pointer);
} }
let register = let register = Register::Value(
Register::Value(AbstractValue::List { items: pointers }.to_value()); AbstractValue::List {
item_pointers: pointers,
}
.to_value(),
);
self.set_register(destination, register)?; self.set_register(destination, register)?;
} }
@ -501,7 +509,33 @@ impl<'a> Vm<'a> {
function, function,
argument_count, argument_count,
} = CallNative::from(&instruction); } = 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 { if let Some(value) = return_value {
let register = Register::Value(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> { fn open_register(&self, register_index: u8) -> Result<ValueRef, VmError> {
let register_index = register_index as usize; let register_index = register_index as usize;
@ -589,10 +658,7 @@ impl<'a> Vm<'a> {
} }
} }
pub(crate) fn open_register_allow_empty( fn open_register_allow_empty(&self, register_index: u8) -> Result<Option<ValueRef>, VmError> {
&self,
register_index: u8,
) -> Result<Option<ValueRef>, VmError> {
let register_index = register_index as usize; let register_index = register_index as usize;
let register = let register =
self.stack self.stack

View File

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