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,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,
})

View File

@ -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<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)]
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<String, LexError> {
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,
}

View File

@ -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<Vec<(Token<'chars>, Span)>, LexError> {
) -> Result<Vec<(Token<'src>, 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;
}

View File

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

View File

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

View File

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

View File

@ -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<Option<Value>, 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"))

View File

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

View File

@ -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<String>,
/// 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<String>,
}
@ -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<Chunk> {
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()
}