1
0

Create formatter

This commit is contained in:
Jeff 2024-10-13 07:14:12 -04:00
parent 5eb901f468
commit c7bba88875
9 changed files with 268 additions and 96 deletions

View File

@ -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::<String>()
};
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::<Vec<&str>>().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();

View File

@ -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)),
);

106
dust-lang/src/formatter.rs Normal file
View File

@ -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
}
}

View File

@ -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]

View File

@ -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::*;

View File

@ -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};

View File

@ -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;

View File

@ -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,

View File

@ -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()?;