Make the disassembly prettier than ever before
This commit is contained in:
parent
5d43674000
commit
85a706e0fb
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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(§ion_border);
|
||||
self.push_local_section();
|
||||
self.write_local_section()?;
|
||||
}
|
||||
|
||||
if !self.chunk.constants().is_empty() {
|
||||
self.push_border(§ion_border);
|
||||
self.push_constant_section();
|
||||
self.write_constant_section()?;
|
||||
}
|
||||
|
||||
self.push_border(&bottom_border);
|
||||
|
||||
self.output.to_string()
|
||||
self.write_page_border(BOTTOM_BORDER)
|
||||
}
|
||||
}
|
||||
|
31
dust-lang/src/chunk/local.rs
Normal file
31
dust-lang/src/chunk/local.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
});
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
|
40
dust-lang/src/native_function/assertion.rs
Normal file
40
dust-lang/src/native_function/assertion.rs
Normal 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!();
|
||||
}
|
79
dust-lang/src/native_function/io.rs
Normal file
79
dust-lang/src/native_function/io.rs
Normal 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)
|
||||
}
|
@ -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)
|
||||
}
|
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
25
dust-lang/src/native_function/string.rs
Normal file
25
dust-lang/src/native_function/string.rs
Normal 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,
|
||||
))))
|
||||
}
|
@ -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() {
|
||||
|
@ -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,
|
||||
|
@ -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()),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)],
|
||||
|
Loading…
Reference in New Issue
Block a user