diff --git a/dust-lang/src/chunk.rs b/dust-lang/src/chunk.rs index 5c16aaa..70d164a 100644 --- a/dust-lang/src/chunk.rs +++ b/dust-lang/src/chunk.rs @@ -1,12 +1,10 @@ -use std::{ - f32::DIGITS, - fmt::{self, Debug, Display, Formatter}, -}; +use std::fmt::{self, Debug, Display}; use colored::Colorize; +use rayon::{iter::ParallelIterator, str::ParallelString}; use serde::{Deserialize, Serialize}; -use crate::{AnnotatedError, Identifier, Instruction, Operation, Span, Type, Value}; +use crate::{AnnotatedError, Formatter, Identifier, Instruction, Operation, Span, Type, Value}; #[derive(Clone, PartialOrd, Ord, Serialize, Deserialize)] pub struct Chunk { @@ -281,7 +279,7 @@ impl Default for Chunk { } impl Display for Chunk { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( f, "{}", @@ -291,7 +289,7 @@ impl Display for Chunk { } impl Debug for Chunk { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( f, "{}", @@ -448,6 +446,7 @@ impl<'a> ChunkDisassembler<'a> { } else { line_characters.iter().collect::() }; + let length_before_content = disassembly.chars().count(); for _ in 0..indent { disassembly.push_str("│ "); @@ -461,10 +460,18 @@ impl<'a> ChunkDisassembler<'a> { disassembly.push_str(&content); disassembly.push_str(&" ".repeat(right_pad_length)); + let length_after_content = disassembly.chars().count(); + let line_length = length_after_content - length_before_content; + + if line_length < content_width - 1 { + disassembly.push_str(&" ".repeat(content_width - line_length)); + } + if add_border { disassembly.push('│'); } + disassembly.push_str(&line_length.to_string()); disassembly.push('\n'); if !remainder.is_empty() { @@ -480,6 +487,7 @@ impl<'a> ChunkDisassembler<'a> { ); } } + let push_header = |header: &str, disassembly: &mut String| { push( header, @@ -527,19 +535,6 @@ impl<'a> ChunkDisassembler<'a> { push_border(&top_border, &mut disassembly); push_header(self.name, &mut disassembly); - if let Some(source) = self.source { - push( - &source.split_whitespace().collect::>().join(" "), - &mut disassembly, - self.width, - self.indent, - false, - false, - true, - true, - ) - } - let info_line = format!( "{} instructions, {} constants, {} locals", self.chunk.instructions.len(), @@ -658,6 +653,12 @@ impl<'a> ChunkDisassembler<'a> { push_border(&bottom_border, &mut disassembly); + if let Some(source) = self.source { + let formatted = Formatter::new(source).origin(self.name).format(); + + disassembly.push_str(&formatted); + } + let expected_length = self.predict_length(); let actual_length = disassembly.len(); diff --git a/dust-lang/src/dust_error.rs b/dust-lang/src/dust_error.rs index 2fe243e..074a05f 100644 --- a/dust-lang/src/dust_error.rs +++ b/dust-lang/src/dust_error.rs @@ -32,7 +32,7 @@ impl<'src> DustError<'src> { .unwrap_or_else(|| "While running this code".to_string()); let message = Level::Error.title(&label).snippet( Snippet::source(source) - .fold(true) + .fold(false) .annotation(Level::Error.span(position.0..position.1).label(&details)), ); @@ -46,7 +46,7 @@ impl<'src> DustError<'src> { .unwrap_or_else(|| "While parsing this code".to_string()); let message = Level::Error.title(&label).snippet( Snippet::source(source) - .fold(true) + .fold(false) .annotation(Level::Error.span(position.0..position.1).label(&details)), ); diff --git a/dust-lang/src/formatter.rs b/dust-lang/src/formatter.rs new file mode 100644 index 0000000..5331eab --- /dev/null +++ b/dust-lang/src/formatter.rs @@ -0,0 +1,106 @@ +use annotate_snippets::{renderer::Style, Level, Renderer, Snippet}; +use colored::{Colorize, CustomColor}; + +use crate::{lex, Token}; + +#[derive(Debug, Copy, Clone)] +pub struct Formatter<'src> { + source: &'src str, + origin: Option<&'src str>, + footer: Option<&'src str>, +} + +impl<'src> Formatter<'src> { + pub fn new(source: &'src str) -> Self { + Self { + source, + origin: None, + footer: None, + } + } + + pub fn origin(&mut self, origin: &'src str) -> &mut Self { + self.origin = Some(origin); + + self + } + + pub fn footer(&mut self, footer: &'src str) -> &mut Self { + self.source = footer; + + self + } + + pub fn format(&self) -> String { + let tokens = match lex(self.source) { + Ok(tokens) => tokens, + Err(error) => return format!("{}", error), + }; + let mut block_depth = 0; + let mut formatted = String::new(); + let line_break = |formatted: &mut String, block_depth: i32| { + formatted.push('\n'); + + for _ in 0..block_depth { + formatted.push_str(" "); + } + }; + + for (token, _) in tokens { + match token { + Token::Boolean(boolean) => formatted.push_str(&boolean.red()), + Token::Byte(byte) => formatted.push_str(&byte.green()), + Token::Character(character) => formatted.push_str( + &character + .to_string() + .custom_color(CustomColor::new(225, 150, 150)), + ), + Token::Float(float) => formatted.push_str(&float.yellow()), + Token::Identifier(identifier) => { + formatted.push_str(&identifier.blue()); + formatted.push(' '); + } + Token::Integer(integer) => formatted.push_str(&integer.cyan()), + Token::String(string) => formatted.push_str(&string.magenta()), + Token::LeftCurlyBrace => { + block_depth += 1; + + formatted.push_str(token.as_str()); + line_break(&mut formatted, block_depth) + } + Token::RightCurlyBrace => { + block_depth -= 1; + + line_break(&mut formatted, block_depth); + formatted.push_str(token.as_str()); + } + Token::Semicolon => { + formatted.push_str(token.as_str()); + line_break(&mut formatted, block_depth); + } + Token::Eof => continue, + token => { + formatted.push_str(token.as_str()); + formatted.push(' '); + } + } + } + + let renderer = Renderer::styled(); + let mut snippet = Snippet::source(&formatted).fold(false); + + if let Some(origin) = self.origin { + snippet = snippet.origin(origin); + } + + let mut message = Level::Info.title("Formatted source").snippet(snippet); + + if let Some(footer) = self.footer { + message = message.footer(Level::Note.title("Footer").snippet(Snippet::source(footer))); + } + + let formatted = renderer.info(Style::new()).render(message).to_string(); + + formatted + } +} diff --git a/dust-lang/src/instruction.rs b/dust-lang/src/instruction.rs index 371f4dc..1779aff 100644 --- a/dust-lang/src/instruction.rs +++ b/dust-lang/src/instruction.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -use crate::{Chunk, Operation, Span}; +use crate::{Chunk, Operation}; #[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub struct Instruction(u32); @@ -208,11 +208,11 @@ impl Instruction { instruction } - pub fn call(function_index: u8, argument_count: u8) -> Instruction { + pub fn call(to_register: u8, function_index: u8) -> Instruction { let mut instruction = Instruction(Operation::Call as u32); - instruction.set_a(function_index); - instruction.set_b(argument_count); + instruction.set_a(to_register); + instruction.set_b(function_index); instruction } @@ -286,8 +286,8 @@ impl Instruction { self } - pub fn set_a(&mut self, destination: u8) { - self.0 |= (destination as u32) << 24; + pub fn set_a(&mut self, to_register: u8) { + self.0 |= (to_register as u32) << 24; } pub fn set_b(&mut self, argument: u8) { @@ -404,14 +404,14 @@ impl Instruction { Some(format!("R{register_index} = C{constant_index} {jump}",)) } Operation::LoadList => { - let destination = self.a(); + let to_register = self.a(); let first_index = self.b(); let last_index = self.c(); - Some(format!("R{destination} = [R{first_index}..=R{last_index}]",)) + Some(format!("R{to_register} = [R{first_index}..=R{last_index}]",)) } Operation::DefineLocal => { - let destination = self.a(); + let to_register = self.a(); let local_index = self.b(); let identifier_display = if let Some(chunk) = chunk { match chunk.get_identifier(local_index) { @@ -424,7 +424,7 @@ impl Instruction { let mutable_display = if self.c_as_boolean() { "mut" } else { "" }; Some(format!( - "L{local_index} = R{destination} {mutable_display} {identifier_display}" + "L{local_index} = R{to_register} {mutable_display} {identifier_display}" )) } Operation::GetLocal => { @@ -451,55 +451,55 @@ impl Instruction { )) } Operation::Add => { - let destination = self.a(); + let to_register = self.a(); let (first_argument, second_argument) = format_arguments(); Some(format!( - "R{destination} = {first_argument} + {second_argument}", + "R{to_register} = {first_argument} + {second_argument}", )) } Operation::Subtract => { - let destination = self.a(); + let to_register = self.a(); let (first_argument, second_argument) = format_arguments(); Some(format!( - "R{destination} = {first_argument} - {second_argument}", + "R{to_register} = {first_argument} - {second_argument}", )) } Operation::Multiply => { - let destination = self.a(); + let to_register = self.a(); let (first_argument, second_argument) = format_arguments(); Some(format!( - "R{destination} = {first_argument} * {second_argument}", + "R{to_register} = {first_argument} * {second_argument}", )) } Operation::Divide => { - let destination = self.a(); + let to_register = self.a(); let (first_argument, second_argument) = format_arguments(); Some(format!( - "R{destination} = {first_argument} / {second_argument}", + "R{to_register} = {first_argument} / {second_argument}", )) } Operation::Modulo => { - let destination = self.a(); + let to_register = self.a(); let (first_argument, second_argument) = format_arguments(); Some(format!( - "R{destination} = {first_argument} % {second_argument}", + "R{to_register} = {first_argument} % {second_argument}", )) } Operation::Test => { - let destination = self.a(); + let to_register = self.a(); let test_value = self.c_as_boolean(); jump_offset = Some(1); - Some(format!("if R{destination} != {test_value} {{ JUMP }}",)) + Some(format!("if R{to_register} != {test_value} {{ JUMP }}",)) } Operation::TestSet => { - let destination = self.a(); + let to_register = self.a(); let argument = format!("R{}", self.b()); let test_value = self.c_as_boolean(); let bang = if test_value { "" } else { "!" }; @@ -507,7 +507,7 @@ impl Instruction { jump_offset = Some(1); Some(format!( - "if {bang}R{destination} {{ R{destination} = R{argument} }}", + "if {bang}R{to_register} {{ R{to_register} = R{argument} }}", )) } Operation::Equal => { @@ -539,24 +539,24 @@ impl Instruction { )) } Operation::Negate => { - let destination = self.a(); + let to_register = self.a(); let argument = if self.b_is_constant() { format!("C{}", self.b()) } else { format!("R{}", self.b()) }; - Some(format!("R{destination} = -{argument}")) + Some(format!("R{to_register} = -{argument}")) } Operation::Not => { - let destination = self.a(); + let to_register = self.a(); let argument = if self.b_is_constant() { format!("C{}", self.b()) } else { format!("R{}", self.b()) }; - Some(format!("R{destination} = !{argument}")) + Some(format!("R{to_register} = !{argument}")) } Operation::Jump => { let offset = self.b() as isize; @@ -571,14 +571,12 @@ impl Instruction { None } Operation::Call => { - let function_index = self.a(); - let argument_count = self.b(); - let first_argument = function_index + 1; - let last_argument = function_index + argument_count; + let to_register = self.a(); + let function_index = self.b(); let mut output = format!("R{function_index}("); - for register in first_argument..=last_argument { + for register in function_index + 1..to_register { output.push_str(&format!("R{}", register)); } @@ -795,11 +793,11 @@ mod tests { #[test] fn call() { - let instruction = Instruction::call(4, 1); + let instruction = Instruction::call(4, 3); assert_eq!(instruction.operation(), Operation::Call); assert_eq!(instruction.a(), 4); - assert_eq!(instruction.b(), 1); + assert_eq!(instruction.b(), 3); } #[test] diff --git a/dust-lang/src/lexer.rs b/dust-lang/src/lexer.rs index 1328acd..7aa22d4 100644 --- a/dust-lang/src/lexer.rs +++ b/dust-lang/src/lexer.rs @@ -4,6 +4,8 @@ //! - [`lex`], which lexes the entire input and returns a vector of tokens and their positions //! - [`Lexer`], which lexes the input a token at a time +use std::fmt::{self, Display, Formatter}; + use serde::{Deserialize, Serialize}; use crate::{dust_error::AnnotatedError, Span, Token}; @@ -670,6 +672,18 @@ impl AnnotatedError for LexError { } } +impl Display for LexError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.description())?; + + if let Some(details) = self.details() { + write!(f, ": {}", details)?; + } + + Ok(()) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/dust-lang/src/lib.rs b/dust-lang/src/lib.rs index 56f350b..05a3ae4 100644 --- a/dust-lang/src/lib.rs +++ b/dust-lang/src/lib.rs @@ -1,5 +1,6 @@ mod chunk; mod dust_error; +mod formatter; mod identifier; mod instruction; mod lexer; @@ -14,6 +15,7 @@ use std::fmt::Display; pub use chunk::{Chunk, ChunkDisassembler, ChunkError, Local}; pub use dust_error::{AnnotatedError, DustError}; +pub use formatter::Formatter; pub use identifier::Identifier; pub use instruction::Instruction; pub use lexer::{lex, LexError, Lexer}; diff --git a/dust-lang/src/parser.rs b/dust-lang/src/parser.rs index 6cd87f1..bd0a553 100644 --- a/dust-lang/src/parser.rs +++ b/dust-lang/src/parser.rs @@ -437,14 +437,7 @@ impl<'src> Parser<'src> { } self.advance()?; - self.parse( - rule.precedence.increment(), - Allowed { - assignment: false, - explicit_return: false, - implicit_return: false, - }, - )?; + self.parse_sub_expression(&rule.precedence)?; let (right_instruction, right_position) = self.chunk.pop_instruction(self.current_position)?; @@ -562,14 +555,7 @@ impl<'src> Parser<'src> { let rule = ParseRule::from(&operator); self.advance()?; - self.parse( - rule.precedence.increment(), - Allowed { - assignment: false, - explicit_return: false, - implicit_return: false, - }, - )?; + self.parse_sub_expression(&rule.precedence)?; let (right_instruction, right_position) = self.chunk.pop_instruction(self.current_position)?; @@ -646,14 +632,7 @@ impl<'src> Parser<'src> { self.increment_register()?; self.advance()?; - self.parse( - rule.precedence.increment(), - Allowed { - assignment: false, - explicit_return: false, - implicit_return: false, - }, - )?; + self.parse_sub_expression(&rule.precedence)?; let (right_instruction, right_position) = self.chunk.pop_instruction(self.current_position)?; @@ -916,7 +895,14 @@ impl<'src> Parser<'src> { } fn parse_statement(&mut self, allowed: Allowed, context: Context) -> Result<(), ParseError> { - self.parse(Precedence::None, allowed)?; + self.parse( + Precedence::None, + Allowed { + assignment: true, + explicit_return: true, + implicit_return: true, + }, + )?; let previous_instructions = self.chunk.get_last_n_instructions(); @@ -962,6 +948,17 @@ impl<'src> Parser<'src> { ) } + fn parse_sub_expression(&mut self, precedence: &Precedence) -> Result<(), ParseError> { + self.parse( + precedence.increment(), + Allowed { + assignment: false, + explicit_return: false, + implicit_return: false, + }, + ) + } + fn parse_return(&mut self, allowed: Allowed) -> Result<(), ParseError> { if !allowed.explicit_return { return Err(ParseError::UnexpectedReturn { @@ -1160,26 +1157,21 @@ impl<'src> Parser<'src> { self.advance()?; let function_register = self.current_register; - let mut argument_count = 0; self.increment_register()?; while !self.allow(Token::RightParenthesis)? { - if argument_count > 0 { - self.expect(Token::Comma)?; - } - self.parse_expression()?; - - argument_count += 1; + self.allow(Token::Comma)?; } let end = self.current_position.1; self.emit_instruction( - Instruction::call(function_register, argument_count), + Instruction::call(self.current_register, function_register), Span(start, end), ); + self.increment_register()?; self.parsed_expression = true; diff --git a/dust-lang/src/token.rs b/dust-lang/src/token.rs index bb7e1ae..bbaf088 100644 --- a/dust-lang/src/token.rs +++ b/dust-lang/src/token.rs @@ -132,6 +132,66 @@ impl<'src> Token<'src> { } } + pub fn as_str(&self) -> &str { + match self { + Token::Eof => "", + Token::Boolean(text) => text, + Token::Byte(text) => text, + Token::Character(_) => "character token", + Token::Float(text) => text, + Token::Identifier(text) => text, + Token::Integer(text) => text, + Token::String(text) => text, + Token::Async => "async", + Token::Bool => "bool", + Token::Break => "break", + Token::Else => "else", + Token::FloatKeyword => "float", + Token::Fn => "fn", + Token::If => "if", + Token::Int => "int", + Token::Let => "let", + Token::Loop => "loop", + Token::Map => "map", + Token::Mut => "mut", + Token::Str => "str", + Token::Struct => "struct", + Token::While => "while", + Token::BangEqual => "!=", + Token::Bang => "!", + Token::Colon => ":", + Token::Comma => ",", + Token::Dot => ".", + Token::DoubleAmpersand => "&&", + Token::DoubleDot => "..", + Token::DoubleEqual => "==", + Token::DoublePipe => "||", + Token::Equal => "=", + Token::Greater => ">", + Token::GreaterEqual => ">=", + Token::LeftCurlyBrace => "{", + Token::LeftParenthesis => "(", + Token::LeftSquareBrace => "[", + Token::Less => "<", + Token::LessEqual => "<=", + Token::Minus => "-", + Token::MinusEqual => "-=", + Token::Percent => "%", + Token::PercentEqual => "%=", + Token::Plus => "+", + Token::PlusEqual => "+=", + Token::Return => "return", + Token::RightCurlyBrace => "}", + Token::RightParenthesis => ")", + Token::RightSquareBrace => "]", + Token::Semicolon => ";", + Token::Slash => "/", + Token::SlashEqual => "/=", + Token::Star => "*", + Token::StarEqual => "*=", + } + } + pub fn to_owned(&self) -> TokenOwned { match self { Token::Async => TokenOwned::Async, diff --git a/dust-lang/src/vm.rs b/dust-lang/src/vm.rs index d1b91fd..171251d 100644 --- a/dust-lang/src/vm.rs +++ b/dust-lang/src/vm.rs @@ -368,8 +368,8 @@ impl Vm { self.ip = new_ip; } Operation::Call => { - let function_index = instruction.a(); - let argument_count = instruction.b(); + let to_register = instruction.a(); + let function_index = instruction.b(); let value = self.get(function_index, position)?.clone(); let function = if let Value::Function(function) = value { function @@ -380,13 +380,12 @@ impl Vm { }); }; let mut function_vm = Vm::new(function.take_chunk()); - let first_argument_index = function_index + 1; - let last_argument_index = first_argument_index + argument_count - 1; - for argument_index in first_argument_index..=last_argument_index { + for argument_index in function_index + 1..to_register { let argument = self.get(argument_index, position)?.clone(); + let top_of_stack = function_vm.stack.len() as u8; - function_vm.stack.push(Register::Value(argument)); + function_vm.set(top_of_stack, argument, position)?; } let return_value = function_vm.run()?;