diff --git a/dust-cli/src/main.rs b/dust-cli/src/main.rs index 934d574..a44e2de 100644 --- a/dust-cli/src/main.rs +++ b/dust-cli/src/main.rs @@ -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; } diff --git a/dust-lang/src/chunk/disassembler.rs b/dust-lang/src/chunk/disassembler.rs index 4e5c767..e57bc0f 100644 --- a/dust-lang/src/chunk/disassembler.rs +++ b/dust-lang/src/chunk/disassembler.rs @@ -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::>(); - 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::(); - - 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::().as_str(), - center, - style_bold, - style_dim, - add_border, - ); + if !overflow.is_empty() { + self.write_content(overflow, center, style_bold, style_dim, add_border)?; } + + Ok(()) } - fn push_source(&mut self, source: &str) { - self.push(source, true, false, false, true); + fn write_centered_with_border(&mut self, text: &str) -> Result<(), io::Error> { + self.write_content(text, true, false, false, true) } - fn push_chunk_info(&mut self, info: &str) { - self.push(info, true, false, true, 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_header(&mut self, header: &str) { - self.push(header, true, self.style, 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_details(&mut self, details: &str) { - self.push(details, true, false, false, true); - } - - fn push_border(&mut self, border: &str) { - self.push(border, false, false, false, false); - } - - fn push_empty(&mut self) { - self.push("", false, false, false, true); - } - - fn push_instruction_section(&mut self) { - for line in INSTRUCTION_HEADER { - self.push_header(line); + fn write_page_border(&mut self, border: [char; 3]) -> Result<(), io::Error> { + for _ in 0..self.indent { + self.write_str(INDENTATION)?; } + self.write_char(border[0])?; + + for _ in 0..self.line_length() { + self.write_char(border[1])?; + } + + 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)?; } + + self.write_centered_with_border_bold(INSTRUCTION_BORDERS[2])?; + + Ok(()) } - fn push_local_section(&mut self) { - for line in LOCAL_HEADER { - self.push_header(line); + 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)?; } + + self.write_centered_with_border(LOCAL_BORDERS[2])?; + + Ok(()) } - fn push_constant_section(&mut self) { - for line in CONSTANT_HEADER { - self.push_header(line); + 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; + } } + + self.write_centered_with_border(CONSTANT_BORDERS[2])?; + + Ok(()) } - pub fn disassemble(mut self) -> String { - let width = Disassembler::default_width(); - let top_border = { - let mut border = "┌".to_string(); - border += &"─".repeat(width - 2); - border += "┐"; - - border - }; - let section_border = { - let mut border = "│".to_string(); - border += &"┈".repeat(width - 2); - border += "│"; - - 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::>().join(" ")); - self.push_empty(); + let lazily_formatted = source.split_whitespace().collect::>().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) } } diff --git a/dust-lang/src/chunk/local.rs b/dust-lang/src/chunk/local.rs new file mode 100644 index 0000000..67f9d68 --- /dev/null +++ b/dust-lang/src/chunk/local.rs @@ -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, + } + } +} diff --git a/dust-lang/src/chunk/mod.rs b/dust-lang/src/chunk/mod.rs index 4981e26..e7d3495 100644 --- a/dust-lang/src/chunk/mod.rs +++ b/dust-lang/src/chunk/mod.rs @@ -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, + name: Option, r#type: FunctionType, instructions: SmallVec<[(Instruction, Span); 32]>, @@ -29,7 +33,7 @@ pub struct Chunk { } impl Chunk { - pub fn new(name: Option) -> Self { + pub fn new(name: Option) -> Self { Self { name, instructions: SmallVec::new(), @@ -42,24 +46,23 @@ impl Chunk { }, } } - pub fn with_data( - name: Option, + name: Option, r#type: FunctionType, - instructions: SmallVec<[(Instruction, Span); 32]>, - constants: SmallVec<[ConcreteValue; 16]>, - locals: SmallVec<[Local; 8]>, + instructions: impl Into>, + constants: impl Into>, + locals: impl Into>, ) -> 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 { + 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, - } - } -} diff --git a/dust-lang/src/scope.rs b/dust-lang/src/chunk/scope.rs similarity index 100% rename from dust-lang/src/scope.rs rename to dust-lang/src/chunk/scope.rs diff --git a/dust-lang/src/compiler/mod.rs b/dust-lang/src/compiler/mod.rs index 2155846..cec1ed3 100644 --- a/dust-lang/src/compiler/mod.rs +++ b/dust-lang/src/compiler/mod.rs @@ -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::>(); + let locals = self + .locals + .into_iter() + .map(|(local, _)| local) + .collect::>(); 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, }); diff --git a/dust-lang/src/instruction/mod.rs b/dust-lang/src/instruction/mod.rs index 2ad71c1..742adbc 100644 --- a/dust-lang/src/instruction/mod.rs +++ b/dust-lang/src/instruction/mod.rs @@ -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,7 +649,13 @@ impl Instruction { let arguments_start = destination.saturating_sub(argument_count); let arguments_end = arguments_start + argument_count; - format!("R{destination} = {function}(R{arguments_start}..R{arguments_end})") + 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 { @@ -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 { diff --git a/dust-lang/src/lib.rs b/dust-lang/src/lib.rs index 528a78d..595cb93 100644 --- a/dust-lang/src/lib.rs +++ b/dust-lang/src/lib.rs @@ -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, diff --git a/dust-lang/src/native_function/assertion.rs b/dust-lang/src/native_function/assertion.rs new file mode 100644 index 0000000..7eb5bb4 --- /dev/null +++ b/dust-lang/src/native_function/assertion.rs @@ -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, 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!(); +} diff --git a/dust-lang/src/native_function/io.rs b/dust-lang/src/native_function/io.rs new file mode 100644 index 0000000..f163b0d --- /dev/null +++ b/dust-lang/src/native_function/io.rs @@ -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, 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, 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, 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) +} diff --git a/dust-lang/src/native_function/logic.rs b/dust-lang/src/native_function/logic.rs deleted file mode 100644 index a06dc35..0000000 --- a/dust-lang/src/native_function/logic.rs +++ /dev/null @@ -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, 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, 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, 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, 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, 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) -} diff --git a/dust-lang/src/native_function/mod.rs b/dust-lang/src/native_function/mod.rs index 0ba0949..5968648 100644 --- a/dust-lang/src/native_function/mod.rs +++ b/dust-lang/src/native_function/mod.rs @@ -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, VmError> { + vm: &Vm<'a>, + arguments: SmallVec<[ValueRef<'a>; 4]>, + ) -> Result, 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), } 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(), } } } diff --git a/dust-lang/src/native_function/string.rs b/dust-lang/src/native_function/string.rs new file mode 100644 index 0000000..03caf7e --- /dev/null +++ b/dust-lang/src/native_function/string.rs @@ -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, 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, + )))) +} diff --git a/dust-lang/src/value/abstract_value.rs b/dust-lang/src/value/abstract_value.rs index 7949faa..c51fa57 100644 --- a/dust-lang/src/value/abstract_value.rs +++ b/dust-lang/src/value/abstract_value.rs @@ -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 }, + List { item_pointers: Vec }, } impl AbstractValue { @@ -20,25 +20,34 @@ impl AbstractValue { pub fn to_concrete_owned(&self, vm: &Vm) -> Result { 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 = 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 { + pub fn to_dust_string(&self, vm: &Vm) -> Result { + 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,10 +60,10 @@ impl AbstractValue { } display.push(']'); - - Ok(display) } } + + Ok(display) } } @@ -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() { diff --git a/dust-lang/src/value/concrete_value.rs b/dust-lang/src/value/concrete_value.rs index 2ef299e..13dd2ca 100644 --- a/dust-lang/src/value/concrete_value.rs +++ b/dust-lang/src/value/concrete_value.rs @@ -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, diff --git a/dust-lang/src/value/mod.rs b/dust-lang/src/value/mod.rs index e2d973a..97fade5 100644 --- a/dust-lang/src/value/mod.rs +++ b/dust-lang/src/value/mod.rs @@ -71,10 +71,10 @@ impl ValueRef<'_> { } } - pub fn display(&self, vm: &Vm) -> Result { + pub fn display(&self, vm: &Vm) -> Result { 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()), } } diff --git a/dust-lang/src/vm.rs b/dust-lang/src/vm.rs index 3276d0d..e5c058a 100644 --- a/dust-lang/src/vm.rs +++ b/dust-lang/src/vm.rs @@ -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, 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 { 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, VmError> { + fn open_register_allow_empty(&self, register_index: u8) -> Result, VmError> { let register_index = register_index as usize; let register = self.stack diff --git a/dust-lang/tests/basic.rs b/dust-lang/tests/basic.rs index f57d371..5a3018c 100644 --- a/dust-lang/tests/basic.rs +++ b/dust-lang/tests/basic.rs @@ -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)],