diff --git a/dust-lang/src/chunk.rs b/dust-lang/src/chunk.rs index e3471d8..d6b4322 100644 --- a/dust-lang/src/chunk.rs +++ b/dust-lang/src/chunk.rs @@ -273,18 +273,35 @@ impl Display for Chunk { write!( f, "{}", - self.disassembler("Chunk").styled(true).disassemble() + self.disassembler("Dust Program").styled(true).disassemble() ) } } impl Debug for Chunk { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "{}", - self.disassembler("Chunk").styled(false).disassemble() - ) + let timestamp = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_secs(); + + if cfg!(debug_assertions) { + write!( + f, + "\n{}\n", + self.disassembler(&format!("Dust Program 0x{timestamp:x}")) + .styled(false) + .disassemble() + ) + } else { + write!( + f, + "{}", + self.disassembler(&format!("Dust Program 0x{timestamp:x}")) + .styled(false) + .disassemble() + ) + } } } @@ -389,12 +406,6 @@ impl<'a> ChunkDisassembler<'a> { self } - pub fn indent(&mut self, indent: usize) -> &mut Self { - self.indent = indent; - - self - } - pub fn disassemble(&self) -> String { #[allow(clippy::too_many_arguments)] fn push( @@ -624,15 +635,13 @@ impl<'a> ChunkDisassembler<'a> { if let Some(function_disassembly) = value_option.as_ref().and_then(|value| match value { - Value::Function(function) => Some( - function - .chunk() - .disassembler("function") - .styled(self.styled) - .width(self.width) - .indent(self.indent + 1) - .disassemble(), - ), + Value::Function(function) => Some({ + let mut disassembler = function.chunk().disassembler("function"); + disassembler.indent = self.indent + 1; + + disassembler.styled(self.styled); + disassembler.disassemble() + }), Value::Primitive(_) => None, Value::Object(_) => None, }) diff --git a/dust-lang/src/formatter.rs b/dust-lang/src/formatter.rs index 6c6b8c6..2617e75 100644 --- a/dust-lang/src/formatter.rs +++ b/dust-lang/src/formatter.rs @@ -1,55 +1,81 @@ +use std::mem::replace; + use colored::{ColoredString, Colorize, CustomColor}; -use crate::{lex, Token}; +use crate::{DustError, LexError, Lexer, Token}; + +pub fn format(source: &str, line_numbers: bool, colored: bool) -> Result { + let lexer = Lexer::new(source); + let formatted = Formatter::new(lexer) + .line_numbers(line_numbers) + .colored(colored) + .format() + .map_err(|error| DustError::Lex { error, source })?; + + Ok(formatted) +} #[derive(Debug)] pub struct Formatter<'src> { - source: &'src str, - lines: Vec<(String, LineKind, usize)>, + lexer: Lexer<'src>, + output_lines: Vec<(String, LineKind, usize)>, next_line: String, indent: usize, + + current_token: Token<'src>, + previous_token: Token<'src>, + + // Options + line_numbers: bool, + colored: bool, } impl<'src> Formatter<'src> { - pub fn new(source: &'src str) -> Self { + pub fn new(mut lexer: Lexer<'src>) -> Self { + let (current_token, _) = lexer.next_token().unwrap(); + Self { - source, - lines: Vec::new(), + lexer, + output_lines: Vec::new(), next_line: String::new(), indent: 0, + current_token, + previous_token: Token::Eof, + line_numbers: false, + colored: false, } } - pub fn footer(&mut self, footer: &'src str) -> &mut Self { - self.source = footer; + pub fn line_numbers(mut self, line_numbers: bool) -> Self { + self.line_numbers = line_numbers; self } - pub fn format(&mut self) -> String { - let tokens = match lex(self.source) { - Ok(tokens) => tokens, - Err(error) => return format!("{}", error), - }; + pub fn colored(mut self, colored: bool) -> Self { + self.colored = colored; + + self + } + + pub fn format(&mut self) -> Result { let mut line_kind = LineKind::Empty; - for (token, _) in tokens { + self.advance()?; + + while self.current_token != Token::Eof { use Token::*; - match token { + if self.current_token.is_expression() && line_kind != LineKind::Assignment { + line_kind = LineKind::Expression; + } + + match self.current_token { Boolean(boolean) => { self.push_colored(boolean.red()); - - if line_kind != LineKind::Assignment { - line_kind = LineKind::Expression; - } } Byte(byte) => { self.push_colored(byte.green()); - - if line_kind != LineKind::Assignment { - line_kind = LineKind::Expression; - } } Character(character) => { self.push_colored( @@ -57,49 +83,29 @@ impl<'src> Formatter<'src> { .to_string() .custom_color(CustomColor::new(225, 150, 150)), ); - - if line_kind != LineKind::Assignment { - line_kind = LineKind::Expression; - } } Float(float) => { self.push_colored(float.yellow()); - - if line_kind != LineKind::Assignment { - line_kind = LineKind::Expression; - } } Identifier(identifier) => { self.push_colored(identifier.blue()); self.next_line.push(' '); - - if line_kind != LineKind::Assignment { - line_kind = LineKind::Expression; - } } Integer(integer) => { self.push_colored(integer.cyan()); - - if line_kind != LineKind::Assignment { - line_kind = LineKind::Expression; - } } String(string) => { self.push_colored(string.magenta()); - - if line_kind != LineKind::Assignment { - line_kind = LineKind::Expression; - } } LeftCurlyBrace => { - self.next_line.push_str(token.as_str()); + self.next_line.push_str(self.current_token.as_str()); self.commit_line(LineKind::OpenBlock); self.indent += 1; } RightCurlyBrace => { self.commit_line(LineKind::CloseBlock); - self.next_line.push_str(token.as_str()); + self.next_line.push_str(self.current_token.as_str()); self.indent -= 1; } @@ -108,22 +114,21 @@ impl<'src> Formatter<'src> { line_kind = LineKind::Statement; } - self.next_line.push_str(token.as_str()); + self.next_line.push_str(self.current_token.as_str()); self.commit_line(line_kind); } Let => { line_kind = LineKind::Assignment; - self.push_colored(token.as_str().bold()); + self.push_colored(self.current_token.as_str().bold()); self.next_line.push(' '); } Break | Loop | Return | While => { line_kind = LineKind::Statement; - self.push_colored(token.as_str().bold()); + self.push_colored(self.current_token.as_str().bold()); self.next_line.push(' '); } - Eof => continue, token => { self.next_line.push_str(token.as_str()); self.next_line.push(' '); @@ -134,11 +139,9 @@ impl<'src> Formatter<'src> { let mut previous_index = 0; let mut current_index = 1; - while current_index < self.lines.len() { - let (_, previous, _) = &self.lines[previous_index]; - let (_, current, _) = &self.lines[current_index]; - - println!("{:?} {:?}", previous, current); + while current_index < self.output_lines.len() { + let (_, previous, _) = &self.output_lines[previous_index]; + let (_, current, _) = &self.output_lines[current_index]; match (previous, current) { (LineKind::Empty, _) @@ -147,7 +150,7 @@ impl<'src> Formatter<'src> { | (_, LineKind::CloseBlock) => {} (left, right) if left == right => {} _ => { - self.lines + self.output_lines .insert(current_index, ("".to_string(), LineKind::Empty, 0)); } } @@ -157,22 +160,42 @@ impl<'src> Formatter<'src> { } let formatted = String::with_capacity( - self.lines + self.output_lines .iter() .fold(0, |total, (line, _, _)| total + line.len()), ); - self.lines - .iter() - .enumerate() - .fold(formatted, |acc, (index, (line, _, indent))| { + Ok(self.output_lines.iter().enumerate().fold( + formatted, + |acc, (index, (line, _, indent))| { + let index = if index == 0 { + format!("{:<3}| ", index + 1).dimmed() + } else { + format!("\n{:<3}| ", index + 1).dimmed() + }; let left_pad = " ".repeat(*indent); - if index == 0 { - return format!("{:<3}| {}{}", index + 1, left_pad, line); - } - format!("{}\n{:<3}| {}{}", acc, index + 1, left_pad, line) - }) + format!("{}{}{}{}", acc, index, left_pad, line) + }, + )) + } + + fn advance(&mut self) -> Result<(), LexError> { + if self.lexer.is_eof() { + return Ok(()); + } + + let (new_token, position) = self.lexer.next_token()?; + + log::info!( + "Parsing {} at {}", + new_token.to_string().bold(), + position.to_string() + ); + + self.previous_token = replace(&mut self.current_token, new_token); + + Ok(()) } fn push_colored(&mut self, colored: ColoredString) { @@ -180,7 +203,7 @@ impl<'src> Formatter<'src> { } fn commit_line(&mut self, line_kind: LineKind) { - self.lines + self.output_lines .push((self.next_line.clone(), line_kind, self.indent)); self.next_line.clear(); } @@ -188,11 +211,12 @@ impl<'src> Formatter<'src> { #[derive(Debug, Clone, Copy, PartialEq)] pub enum LineKind { - Assignment, - FunctionCall, - Statement, - Expression, Empty, + Assignment, + Expression, + Statement, OpenBlock, CloseBlock, + Call, + Primary, } diff --git a/dust-lang/src/lexer.rs b/dust-lang/src/lexer.rs index 7aa22d4..a7f6ce4 100644 --- a/dust-lang/src/lexer.rs +++ b/dust-lang/src/lexer.rs @@ -8,7 +8,7 @@ use std::fmt::{self, Display, Formatter}; use serde::{Deserialize, Serialize}; -use crate::{dust_error::AnnotatedError, Span, Token}; +use crate::{dust_error::AnnotatedError, DustError, Span, Token}; /// Lexes the input and return a vector of tokens and their positions. /// @@ -30,21 +30,19 @@ use crate::{dust_error::AnnotatedError, Span, Token}; /// ] /// ); /// ``` -pub fn lex<'chars, 'src: 'chars>( +pub fn lex<'tokens, 'src: 'tokens>( source: &'src str, -) -> Result, Span)>, LexError> { +) -> Result, Span)>, DustError> { let mut lexer = Lexer::new(source); let mut tokens = Vec::new(); - loop { - let (token, span) = lexer.next_token()?; - let is_eof = matches!(token, Token::Eof); + while !lexer.is_eof() { + let (token, span) = lexer + .next_token() + .map_err(|error| DustError::Lex { error, source })?; + let length = tokens.len(); - tokens.push((token, span)); - - if is_eof { - break; - } + tokens[length] = (token, span); } Ok(tokens) @@ -101,6 +99,10 @@ impl<'src> Lexer<'src> { self.source } + pub fn is_eof(&self) -> bool { + self.position >= self.source.len() + } + pub fn skip_to(&mut self, position: usize) { self.position = position; } diff --git a/dust-lang/src/lib.rs b/dust-lang/src/lib.rs index 05a3ae4..a8f8b9a 100644 --- a/dust-lang/src/lib.rs +++ b/dust-lang/src/lib.rs @@ -15,7 +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 formatter::{format, 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 425a6fa..82d34c4 100644 --- a/dust-lang/src/parser.rs +++ b/dust-lang/src/parser.rs @@ -654,6 +654,10 @@ impl<'src> Parser<'src> { self.advance()?; let local_index = self.parse_identifier_from(token, start_position)?; + let to_register = self + .chunk + .get_local(local_index, start_position)? + .register_index; if self.allow(Token::Equal)? { if !allowed.assignment { @@ -699,11 +703,12 @@ impl<'src> Parser<'src> { Instruction::set_local(self.current_register, local_index), start_position, ); + self.increment_register()?; self.parsed_expression = false; } else { self.emit_instruction( - Instruction::get_local(self.current_register, local_index), + Instruction::get_local(to_register, local_index), self.previous_position, ); @@ -792,7 +797,7 @@ impl<'src> Parser<'src> { self.allow(Token::Comma)?; } - let end_register = self.current_register - 1; + let end_register = self.current_register.saturating_sub(1); let end = self.current_position.1; self.emit_instruction( diff --git a/dust-lang/src/token.rs b/dust-lang/src/token.rs index dd94534..5360fc7 100644 --- a/dust-lang/src/token.rs +++ b/dust-lang/src/token.rs @@ -4,9 +4,10 @@ use std::fmt::{self, Display, Formatter}; use serde::{Deserialize, Serialize}; /// Source code token. -#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Default, Serialize, Deserialize)] pub enum Token<'src> { // End of file + #[default] Eof, // Hard-coded values diff --git a/dust-lang/src/vm.rs b/dust-lang/src/vm.rs index 171251d..68f2c4b 100644 --- a/dust-lang/src/vm.rs +++ b/dust-lang/src/vm.rs @@ -2,7 +2,7 @@ use std::{cmp::Ordering, mem::replace}; use crate::{ parse, value::Primitive, AnnotatedError, Chunk, ChunkError, DustError, Identifier, Instruction, - Operation, Span, Value, ValueError, + Operation, Span, Type, Value, ValueError, }; pub fn run(source: &str) -> Result, DustError> { @@ -111,7 +111,13 @@ impl Vm { let to_register = instruction.a(); let first_register = instruction.b(); let last_register = instruction.c(); - let item_type = self.get(first_register, position)?.r#type(); + + let is_empty = to_register == first_register && first_register == last_register; + let item_type = if is_empty { + Type::Any + } else { + self.get(first_register, position)?.r#type() + }; let value = Value::list(first_register, last_register, item_type); self.set(to_register, value, position)?; @@ -685,7 +691,7 @@ impl AnnotatedError for VmError { Self::EmptyRegister { index, .. } => Some(format!("Register {index} is empty")), Self::ExpectedFunction { found, .. } => Some(format!("{found} is not a function")), Self::RegisterIndexOutOfBounds { index, .. } => { - Some(format!("R{index} does not exist at this time")) + Some(format!("Register {index} does not exist")) } Self::UndefinedVariable { identifier, .. } => { Some(format!("{identifier} is not in scope")) diff --git a/dust-lang/tests/expressions.rs b/dust-lang/tests/expressions.rs index 7c5caa0..f04868e 100644 --- a/dust-lang/tests/expressions.rs +++ b/dust-lang/tests/expressions.rs @@ -212,6 +212,25 @@ fn empty() { assert_eq!(run(source), Ok(None)); } +#[test] +fn empty_list() { + let source = "[]"; + + assert_eq!( + parse(source), + Ok(Chunk::with_data( + vec![ + (Instruction::load_list(0, 0, 0), Span(0, 2)), + (Instruction::r#return(true), Span(2, 2)), + ], + vec![], + vec![] + )), + ); + + assert_eq!(run(source), Ok(Some(Value::list(0, 0, Type::Any)))); +} + #[test] fn equal() { let source = "1 == 2"; diff --git a/dust-shell/src/main.rs b/dust-shell/src/main.rs index 2fcaf1b..d5d3fb7 100644 --- a/dust-shell/src/main.rs +++ b/dust-shell/src/main.rs @@ -2,26 +2,40 @@ use std::{fs::read_to_string, io::Write}; use clap::Parser; use colored::Colorize; -use dust_lang::{parse, run, Formatter}; +use dust_lang::{format, parse, run, Chunk, DustError, Vm}; use log::Level; #[derive(Parser)] struct Cli { + /// Source code send via command line #[arg(short, long)] command: Option, + /// Whether to output formatted source code #[arg(short, long)] format: bool, + /// Whether to output line numbers in formatted source code + #[arg(short = 'l', long)] + format_line_numbers: bool, + + /// Whether to output colors in formatted source code + #[arg(short = 'o', long)] + format_colored: bool, + + /// Whether to run the source code #[arg(short, long)] no_run: bool, + /// Whether to output the disassembled chunk #[arg(short, long)] parse: bool, + /// Whether to style the disassembled chunk #[arg(short, long)] - styled: bool, + style_disassembly: bool, + /// Path to a source code file path: Option, } @@ -48,55 +62,83 @@ fn main() { .init(); let args = Cli::parse(); - let source = if let Some(path) = &args.path { &read_to_string(path).expect("Failed to read file") } else if let Some(command) = &args.command { command } else { eprintln!("No input provided"); + return; }; - if args.parse { - parse_source(source, args.styled); + if !args.no_run { + if args.format { + format_source(source, args.format_line_numbers, args.format_colored); + } + + let run_result = if args.parse { + let chunk = parse(source).unwrap(); + let disassembly = chunk + .disassembler("Dust CLI Input") + .source(source) + .styled(args.style_disassembly) + .disassemble(); + + println!("{}", disassembly); + + let mut vm = Vm::new(chunk); + + vm.run() + .map_err(|error| DustError::Runtime { error, source }) + } else { + run(source) + }; + + match run_result { + Ok(Some(value)) => println!("{}", value), + Ok(_) => {} + Err(error) => { + eprintln!("{}", error.report()); + } + } + + return; } if args.format { - format_source(source); + format_source(source, args.format_line_numbers, args.format_colored); } - if !args.no_run { - run_source(source); + if args.parse { + parse_source(source, args.style_disassembly); } } -fn format_source(source: &str) { - println!("{}", Formatter::new(source).format()) +pub fn format_source(source: &str, line_numbers: bool, colored: bool) { + log::info!("Formatting source"); + + match format(source, line_numbers, colored) { + Ok(formatted) => println!("{}", formatted), + Err(error) => { + eprintln!("{}", error.report()); + } + } } -fn parse_source(source: &str, styled: bool) { - match parse(source) { - Ok(chunk) => println!( - "{}", - chunk +fn parse_source(source: &str, styled: bool) -> Option { + parse(source) + .inspect(|chunk| { + let disassembly = chunk .disassembler("Dust CLI Input") .source(source) .styled(styled) - .disassemble() - ), - Err(error) => { - eprintln!("{}", error.report()); - } - } -} + .disassemble(); -fn run_source(source: &str) { - match run(source) { - Ok(Some(value)) => println!("{}", value), - Ok(_) => {} - Err(error) => { + println!("{disassembly}",); + }) + .inspect_err(|error| { eprintln!("{}", error.report()); - } - } + }) + .ok() }