1
0

Extend CLI to cover more formatting options; Extend formatting

This commit is contained in:
Jeff 2024-10-13 16:46:45 -04:00
parent 0c758c9768
commit 44659ec34a
9 changed files with 246 additions and 138 deletions

View File

@ -273,20 +273,37 @@ impl Display for Chunk {
write!( write!(
f, f,
"{}", "{}",
self.disassembler("Chunk").styled(true).disassemble() self.disassembler("Dust Program").styled(true).disassemble()
) )
} }
} }
impl Debug for Chunk { impl Debug for Chunk {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
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!( write!(
f, f,
"{}", "{}",
self.disassembler("Chunk").styled(false).disassemble() self.disassembler(&format!("Dust Program 0x{timestamp:x}"))
.styled(false)
.disassemble()
) )
} }
} }
}
impl Eq for Chunk {} impl Eq for Chunk {}
@ -389,12 +406,6 @@ impl<'a> ChunkDisassembler<'a> {
self self
} }
pub fn indent(&mut self, indent: usize) -> &mut Self {
self.indent = indent;
self
}
pub fn disassemble(&self) -> String { pub fn disassemble(&self) -> String {
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
fn push( fn push(
@ -624,15 +635,13 @@ impl<'a> ChunkDisassembler<'a> {
if let Some(function_disassembly) = if let Some(function_disassembly) =
value_option.as_ref().and_then(|value| match value { value_option.as_ref().and_then(|value| match value {
Value::Function(function) => Some( Value::Function(function) => Some({
function let mut disassembler = function.chunk().disassembler("function");
.chunk() disassembler.indent = self.indent + 1;
.disassembler("function")
.styled(self.styled) disassembler.styled(self.styled);
.width(self.width) disassembler.disassemble()
.indent(self.indent + 1) }),
.disassemble(),
),
Value::Primitive(_) => None, Value::Primitive(_) => None,
Value::Object(_) => None, Value::Object(_) => None,
}) })

View File

@ -1,55 +1,81 @@
use std::mem::replace;
use colored::{ColoredString, Colorize, CustomColor}; 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<String, DustError> {
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)] #[derive(Debug)]
pub struct Formatter<'src> { pub struct Formatter<'src> {
source: &'src str, lexer: Lexer<'src>,
lines: Vec<(String, LineKind, usize)>, output_lines: Vec<(String, LineKind, usize)>,
next_line: String, next_line: String,
indent: usize, indent: usize,
current_token: Token<'src>,
previous_token: Token<'src>,
// Options
line_numbers: bool,
colored: bool,
} }
impl<'src> Formatter<'src> { 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 { Self {
source, lexer,
lines: Vec::new(), output_lines: Vec::new(),
next_line: String::new(), next_line: String::new(),
indent: 0, indent: 0,
current_token,
previous_token: Token::Eof,
line_numbers: false,
colored: false,
} }
} }
pub fn footer(&mut self, footer: &'src str) -> &mut Self { pub fn line_numbers(mut self, line_numbers: bool) -> Self {
self.source = footer; self.line_numbers = line_numbers;
self self
} }
pub fn format(&mut self) -> String { pub fn colored(mut self, colored: bool) -> Self {
let tokens = match lex(self.source) { self.colored = colored;
Ok(tokens) => tokens,
Err(error) => return format!("{}", error), self
}; }
pub fn format(&mut self) -> Result<String, LexError> {
let mut line_kind = LineKind::Empty; let mut line_kind = LineKind::Empty;
for (token, _) in tokens { self.advance()?;
while self.current_token != Token::Eof {
use Token::*; use Token::*;
match token { if self.current_token.is_expression() && line_kind != LineKind::Assignment {
Boolean(boolean) => {
self.push_colored(boolean.red());
if line_kind != LineKind::Assignment {
line_kind = LineKind::Expression; line_kind = LineKind::Expression;
} }
match self.current_token {
Boolean(boolean) => {
self.push_colored(boolean.red());
} }
Byte(byte) => { Byte(byte) => {
self.push_colored(byte.green()); self.push_colored(byte.green());
if line_kind != LineKind::Assignment {
line_kind = LineKind::Expression;
}
} }
Character(character) => { Character(character) => {
self.push_colored( self.push_colored(
@ -57,49 +83,29 @@ impl<'src> Formatter<'src> {
.to_string() .to_string()
.custom_color(CustomColor::new(225, 150, 150)), .custom_color(CustomColor::new(225, 150, 150)),
); );
if line_kind != LineKind::Assignment {
line_kind = LineKind::Expression;
}
} }
Float(float) => { Float(float) => {
self.push_colored(float.yellow()); self.push_colored(float.yellow());
if line_kind != LineKind::Assignment {
line_kind = LineKind::Expression;
}
} }
Identifier(identifier) => { Identifier(identifier) => {
self.push_colored(identifier.blue()); self.push_colored(identifier.blue());
self.next_line.push(' '); self.next_line.push(' ');
if line_kind != LineKind::Assignment {
line_kind = LineKind::Expression;
}
} }
Integer(integer) => { Integer(integer) => {
self.push_colored(integer.cyan()); self.push_colored(integer.cyan());
if line_kind != LineKind::Assignment {
line_kind = LineKind::Expression;
}
} }
String(string) => { String(string) => {
self.push_colored(string.magenta()); self.push_colored(string.magenta());
if line_kind != LineKind::Assignment {
line_kind = LineKind::Expression;
}
} }
LeftCurlyBrace => { 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.commit_line(LineKind::OpenBlock);
self.indent += 1; self.indent += 1;
} }
RightCurlyBrace => { RightCurlyBrace => {
self.commit_line(LineKind::CloseBlock); 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; self.indent -= 1;
} }
@ -108,22 +114,21 @@ impl<'src> Formatter<'src> {
line_kind = LineKind::Statement; 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); self.commit_line(line_kind);
} }
Let => { Let => {
line_kind = LineKind::Assignment; line_kind = LineKind::Assignment;
self.push_colored(token.as_str().bold()); self.push_colored(self.current_token.as_str().bold());
self.next_line.push(' '); self.next_line.push(' ');
} }
Break | Loop | Return | While => { Break | Loop | Return | While => {
line_kind = LineKind::Statement; line_kind = LineKind::Statement;
self.push_colored(token.as_str().bold()); self.push_colored(self.current_token.as_str().bold());
self.next_line.push(' '); self.next_line.push(' ');
} }
Eof => continue,
token => { token => {
self.next_line.push_str(token.as_str()); self.next_line.push_str(token.as_str());
self.next_line.push(' '); self.next_line.push(' ');
@ -134,11 +139,9 @@ impl<'src> Formatter<'src> {
let mut previous_index = 0; let mut previous_index = 0;
let mut current_index = 1; let mut current_index = 1;
while current_index < self.lines.len() { while current_index < self.output_lines.len() {
let (_, previous, _) = &self.lines[previous_index]; let (_, previous, _) = &self.output_lines[previous_index];
let (_, current, _) = &self.lines[current_index]; let (_, current, _) = &self.output_lines[current_index];
println!("{:?} {:?}", previous, current);
match (previous, current) { match (previous, current) {
(LineKind::Empty, _) (LineKind::Empty, _)
@ -147,7 +150,7 @@ impl<'src> Formatter<'src> {
| (_, LineKind::CloseBlock) => {} | (_, LineKind::CloseBlock) => {}
(left, right) if left == right => {} (left, right) if left == right => {}
_ => { _ => {
self.lines self.output_lines
.insert(current_index, ("".to_string(), LineKind::Empty, 0)); .insert(current_index, ("".to_string(), LineKind::Empty, 0));
} }
} }
@ -157,22 +160,42 @@ impl<'src> Formatter<'src> {
} }
let formatted = String::with_capacity( let formatted = String::with_capacity(
self.lines self.output_lines
.iter() .iter()
.fold(0, |total, (line, _, _)| total + line.len()), .fold(0, |total, (line, _, _)| total + line.len()),
); );
self.lines Ok(self.output_lines.iter().enumerate().fold(
.iter() formatted,
.enumerate() |acc, (index, (line, _, indent))| {
.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); let left_pad = " ".repeat(*indent);
if index == 0 { format!("{}{}{}{}", acc, index, left_pad, line)
return format!("{:<3}| {}{}", index + 1, left_pad, line); },
))
} }
format!("{}\n{:<3}| {}{}", acc, index + 1, 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) { fn push_colored(&mut self, colored: ColoredString) {
@ -180,7 +203,7 @@ impl<'src> Formatter<'src> {
} }
fn commit_line(&mut self, line_kind: LineKind) { fn commit_line(&mut self, line_kind: LineKind) {
self.lines self.output_lines
.push((self.next_line.clone(), line_kind, self.indent)); .push((self.next_line.clone(), line_kind, self.indent));
self.next_line.clear(); self.next_line.clear();
} }
@ -188,11 +211,12 @@ impl<'src> Formatter<'src> {
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
pub enum LineKind { pub enum LineKind {
Assignment,
FunctionCall,
Statement,
Expression,
Empty, Empty,
Assignment,
Expression,
Statement,
OpenBlock, OpenBlock,
CloseBlock, CloseBlock,
Call,
Primary,
} }

View File

@ -8,7 +8,7 @@ use std::fmt::{self, Display, Formatter};
use serde::{Deserialize, Serialize}; 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. /// 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, source: &'src str,
) -> Result<Vec<(Token<'chars>, Span)>, LexError> { ) -> Result<Vec<(Token<'src>, Span)>, DustError> {
let mut lexer = Lexer::new(source); let mut lexer = Lexer::new(source);
let mut tokens = Vec::new(); let mut tokens = Vec::new();
loop { while !lexer.is_eof() {
let (token, span) = lexer.next_token()?; let (token, span) = lexer
let is_eof = matches!(token, Token::Eof); .next_token()
.map_err(|error| DustError::Lex { error, source })?;
let length = tokens.len();
tokens.push((token, span)); tokens[length] = (token, span);
if is_eof {
break;
}
} }
Ok(tokens) Ok(tokens)
@ -101,6 +99,10 @@ impl<'src> Lexer<'src> {
self.source self.source
} }
pub fn is_eof(&self) -> bool {
self.position >= self.source.len()
}
pub fn skip_to(&mut self, position: usize) { pub fn skip_to(&mut self, position: usize) {
self.position = position; self.position = position;
} }

View File

@ -15,7 +15,7 @@ use std::fmt::Display;
pub use chunk::{Chunk, ChunkDisassembler, ChunkError, Local}; pub use chunk::{Chunk, ChunkDisassembler, ChunkError, Local};
pub use dust_error::{AnnotatedError, DustError}; pub use dust_error::{AnnotatedError, DustError};
pub use formatter::Formatter; pub use formatter::{format, Formatter};
pub use identifier::Identifier; pub use identifier::Identifier;
pub use instruction::Instruction; pub use instruction::Instruction;
pub use lexer::{lex, LexError, Lexer}; pub use lexer::{lex, LexError, Lexer};

View File

@ -654,6 +654,10 @@ impl<'src> Parser<'src> {
self.advance()?; self.advance()?;
let local_index = self.parse_identifier_from(token, start_position)?; 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 self.allow(Token::Equal)? {
if !allowed.assignment { if !allowed.assignment {
@ -699,11 +703,12 @@ impl<'src> Parser<'src> {
Instruction::set_local(self.current_register, local_index), Instruction::set_local(self.current_register, local_index),
start_position, start_position,
); );
self.increment_register()?;
self.parsed_expression = false; self.parsed_expression = false;
} else { } else {
self.emit_instruction( self.emit_instruction(
Instruction::get_local(self.current_register, local_index), Instruction::get_local(to_register, local_index),
self.previous_position, self.previous_position,
); );
@ -792,7 +797,7 @@ impl<'src> Parser<'src> {
self.allow(Token::Comma)?; 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; let end = self.current_position.1;
self.emit_instruction( self.emit_instruction(

View File

@ -4,9 +4,10 @@ use std::fmt::{self, Display, Formatter};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// Source code token. /// 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> { pub enum Token<'src> {
// End of file // End of file
#[default]
Eof, Eof,
// Hard-coded values // Hard-coded values

View File

@ -2,7 +2,7 @@ use std::{cmp::Ordering, mem::replace};
use crate::{ use crate::{
parse, value::Primitive, AnnotatedError, Chunk, ChunkError, DustError, Identifier, Instruction, parse, value::Primitive, AnnotatedError, Chunk, ChunkError, DustError, Identifier, Instruction,
Operation, Span, Value, ValueError, Operation, Span, Type, Value, ValueError,
}; };
pub fn run(source: &str) -> Result<Option<Value>, DustError> { pub fn run(source: &str) -> Result<Option<Value>, DustError> {
@ -111,7 +111,13 @@ impl Vm {
let to_register = instruction.a(); let to_register = instruction.a();
let first_register = instruction.b(); let first_register = instruction.b();
let last_register = instruction.c(); 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); let value = Value::list(first_register, last_register, item_type);
self.set(to_register, value, position)?; self.set(to_register, value, position)?;
@ -685,7 +691,7 @@ impl AnnotatedError for VmError {
Self::EmptyRegister { index, .. } => Some(format!("Register {index} is empty")), Self::EmptyRegister { index, .. } => Some(format!("Register {index} is empty")),
Self::ExpectedFunction { found, .. } => Some(format!("{found} is not a function")), Self::ExpectedFunction { found, .. } => Some(format!("{found} is not a function")),
Self::RegisterIndexOutOfBounds { index, .. } => { Self::RegisterIndexOutOfBounds { index, .. } => {
Some(format!("R{index} does not exist at this time")) Some(format!("Register {index} does not exist"))
} }
Self::UndefinedVariable { identifier, .. } => { Self::UndefinedVariable { identifier, .. } => {
Some(format!("{identifier} is not in scope")) Some(format!("{identifier} is not in scope"))

View File

@ -212,6 +212,25 @@ fn empty() {
assert_eq!(run(source), Ok(None)); 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] #[test]
fn equal() { fn equal() {
let source = "1 == 2"; let source = "1 == 2";

View File

@ -2,26 +2,40 @@ use std::{fs::read_to_string, io::Write};
use clap::Parser; use clap::Parser;
use colored::Colorize; use colored::Colorize;
use dust_lang::{parse, run, Formatter}; use dust_lang::{format, parse, run, Chunk, DustError, Vm};
use log::Level; use log::Level;
#[derive(Parser)] #[derive(Parser)]
struct Cli { struct Cli {
/// Source code send via command line
#[arg(short, long)] #[arg(short, long)]
command: Option<String>, command: Option<String>,
/// Whether to output formatted source code
#[arg(short, long)] #[arg(short, long)]
format: bool, 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)] #[arg(short, long)]
no_run: bool, no_run: bool,
/// Whether to output the disassembled chunk
#[arg(short, long)] #[arg(short, long)]
parse: bool, parse: bool,
/// Whether to style the disassembled chunk
#[arg(short, long)] #[arg(short, long)]
styled: bool, style_disassembly: bool,
/// Path to a source code file
path: Option<String>, path: Option<String>,
} }
@ -48,55 +62,83 @@ fn main() {
.init(); .init();
let args = Cli::parse(); let args = Cli::parse();
let source = if let Some(path) = &args.path { let source = if let Some(path) = &args.path {
&read_to_string(path).expect("Failed to read file") &read_to_string(path).expect("Failed to read file")
} else if let Some(command) = &args.command { } else if let Some(command) = &args.command {
command command
} else { } else {
eprintln!("No input provided"); eprintln!("No input provided");
return; return;
}; };
if args.parse {
parse_source(source, args.styled);
}
if args.format {
format_source(source);
}
if !args.no_run { if !args.no_run {
run_source(source); if args.format {
} format_source(source, args.format_line_numbers, args.format_colored);
} }
fn format_source(source: &str) { let run_result = if args.parse {
println!("{}", Formatter::new(source).format()) let chunk = parse(source).unwrap();
} let disassembly = chunk
fn parse_source(source: &str, styled: bool) {
match parse(source) {
Ok(chunk) => println!(
"{}",
chunk
.disassembler("Dust CLI Input") .disassembler("Dust CLI Input")
.source(source) .source(source)
.styled(styled) .styled(args.style_disassembly)
.disassemble() .disassemble();
),
Err(error) => {
eprintln!("{}", error.report());
}
}
}
fn run_source(source: &str) { println!("{}", disassembly);
match run(source) {
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(Some(value)) => println!("{}", value),
Ok(_) => {} Ok(_) => {}
Err(error) => { Err(error) => {
eprintln!("{}", error.report()); eprintln!("{}", error.report());
} }
} }
return;
}
if args.format {
format_source(source, args.format_line_numbers, args.format_colored);
}
if args.parse {
parse_source(source, args.style_disassembly);
}
}
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) -> Option<Chunk> {
parse(source)
.inspect(|chunk| {
let disassembly = chunk
.disassembler("Dust CLI Input")
.source(source)
.styled(styled)
.disassemble();
println!("{disassembly}",);
})
.inspect_err(|error| {
eprintln!("{}", error.report());
})
.ok()
} }