From 3146e70bf14630e0935ccd929f506d8748271726 Mon Sep 17 00:00:00 2001 From: Jeff Date: Sun, 1 Dec 2024 21:17:22 -0500 Subject: [PATCH] Improve CLI --- README.md | 2 +- dust-cli/src/main.rs | 232 +++++++++++++++++++++++++++----------- dust-lang/src/compiler.rs | 3 +- dust-lang/src/lib.rs | 2 +- dust-lang/src/token.rs | 2 +- 5 files changed, 170 insertions(+), 71 deletions(-) diff --git a/README.md b/README.md index eb0e3be..08f7473 100644 --- a/README.md +++ b/README.md @@ -41,8 +41,8 @@ Dust is still in development. This list may change as the language evolves. - [X] Functions - [X] Integers (signed 64-bit) - [ ] Ranges -- Composite Values - [X] Strings (UTF-8) +- Composite Values - [X] Concrete lists - [X] Abstract lists (optimization) - [ ] Concrete maps diff --git a/dust-cli/src/main.rs b/dust-cli/src/main.rs index cdd2978..c9bf715 100644 --- a/dust-cli/src/main.rs +++ b/dust-cli/src/main.rs @@ -1,82 +1,148 @@ -use std::fs::read_to_string; -use std::io::Write; +use std::io::{stdout, Write}; +use std::time::Instant; +use std::{fs::read_to_string, path::PathBuf}; -use clap::Parser; +use clap::{Args, Parser, Subcommand}; use colored::Colorize; -use dust_lang::{compile, run}; +use dust_lang::{compile, lex, run, write_token_list}; use log::{Level, LevelFilter}; #[derive(Parser)] +#[clap( + version = env!("CARGO_PKG_VERSION"), + author = env!("CARGO_PKG_AUTHORS"), + about = env!("CARGO_PKG_DESCRIPTION"), + +)] +#[command(args_conflicts_with_subcommands = true)] struct Cli { - /// Source code sent via command line - #[arg(short, long)] - command: Option, + #[command(flatten)] + global_arguments: GlobalArguments, - /// Whether to output the disassembled chunk - #[arg(short, long)] - parse: bool, + #[command(subcommand)] + mode: Option, +} - /// Whether to style the disassembled chunk - #[arg(long)] - style_disassembly: Option, +#[derive(Subcommand)] +enum CliMode { + /// Run the source code (default) + #[command(short_flag = 'r')] + Run { + #[command(flatten)] + global_arguments: GlobalArguments, - /// Log level - #[arg(short, long)] + /// Do not print the program's output value + #[arg(short, long)] + no_output: bool, + }, + + /// Compile a chunk and show the disassembly + #[command(short_flag = 'd')] + Disassemble { + #[command(flatten)] + global_arguments: GlobalArguments, + + /// Style the disassembly output + #[arg(short, long)] + style: bool, + }, + + /// Create and display tokens from the source code + #[command(short_flag = 't')] + Tokenize { + #[command(flatten)] + global_arguments: GlobalArguments, + + /// Style the disassembly output + #[arg(short, long)] + style: bool, + }, +} + +#[derive(Args, Clone)] +struct GlobalArguments { + /// Log level, overrides the DUST_LOG environment variable + #[arg(short, long, value_name = "LOG_LEVEL")] log: Option, - /// Path to a source code file - path: Option, + /// Source code sent via command line + #[arg(short, long, value_name = "SOURCE", conflicts_with = "path")] + command: Option, + + /// File to read source code from + #[arg(required_unless_present = "command")] + path: Option, +} + +impl GlobalArguments { + fn set_log_and_get_source(self, start_time: Instant) -> String { + let GlobalArguments { command, path, log } = self; + let mut logger = env_logger::builder(); + + logger.format(move |buf, record| { + let elapsed = start_time.elapsed().as_nanos(); + let level_display = match record.level() { + Level::Info => "INFO".bold().white(), + Level::Debug => "DEBUG".bold().blue(), + Level::Warn => "WARN".bold().yellow(), + Level::Error => "ERROR".bold().red(), + Level::Trace => "TRACE".bold().purple(), + }; + let module = record + .module_path() + .map(|path| path.split("::").last().unwrap_or(path)) + .unwrap_or("unknown") + .dimmed(); + let display = format!( + "{elapsed} {level_display:5} {module:^6} {args}", + args = record.args() + ); + + writeln!(buf, "{display}") + }); + + if let Some(level) = log { + logger.filter_level(level).init(); + } else { + logger.parse_env("DUST_LOG").init(); + } + + if let Some(source) = command { + source + } else { + let path = path.expect("Path is required when command is not provided"); + + read_to_string(path).expect("Failed to read file") + } + } } fn main() { - let args = Cli::parse(); - let mut logger = env_logger::builder(); - - logger.format(|buf, record| { - let level_display = match record.level() { - Level::Info => "INFO".bold().white(), - Level::Debug => "DEBUG".bold().blue(), - Level::Warn => "WARN".bold().yellow(), - Level::Error => "ERROR".bold().red(), - Level::Trace => "TRACE".bold().purple(), - }; - let module = record - .module_path() - .map(|path| path.split("::").last().unwrap_or(path)) - .unwrap_or("unknown") - .dimmed(); - let display = format!("{level_display:5} {module:^6} {args}", args = record.args()); - - writeln!(buf, "{display}") + let start_time = Instant::now(); + let Cli { + global_arguments, + mode, + } = Cli::parse(); + let mode = mode.unwrap_or(CliMode::Run { + global_arguments, + no_output: false, }); - if let Some(level) = args.log { - logger.filter_level(level).init(); - } else { - logger.parse_env("DUST_LOG").init(); - } + if let CliMode::Run { + global_arguments, + no_output, + } = mode + { + let source = global_arguments.set_log_and_get_source(start_time); + let run_result = run(&source); - 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 { - let style = args.style_disassembly.unwrap_or(true); - - log::info!("Parsing source"); - - match compile(source) { - Ok(chunk) => { - let disassembly = chunk.disassembler().style(style).disassemble(); - - println!("{}", disassembly); + match run_result { + Ok(Some(value)) => { + if !no_output { + println!("{}", value) + } } + Ok(None) => {} Err(error) => { eprintln!("{}", error.report()); } @@ -85,12 +151,44 @@ fn main() { return; } - match run(source) { - Ok(Some(value)) => println!("{}", value), - Ok(None) => {} - Err(error) => { - eprintln!("{}", error.report()); - } + if let CliMode::Disassemble { + global_arguments, + style, + } = mode + { + let source = global_arguments.set_log_and_get_source(start_time); + let chunk = match compile(&source) { + Ok(chunk) => chunk, + Err(error) => { + eprintln!("{}", error.report()); + + return; + } + }; + let disassembly = chunk.disassembler().style(style).disassemble(); + + println!("{}", disassembly); + + return; + } + + if let CliMode::Tokenize { + global_arguments, + style, + } = mode + { + let source = global_arguments.set_log_and_get_source(start_time); + let tokens = match lex(&source) { + Ok(tokens) => tokens, + Err(error) => { + eprintln!("{}", error.report()); + + return; + } + }; + let mut stdout = stdout().lock(); + + write_token_list(&tokens, style, &mut stdout) } } diff --git a/dust-lang/src/compiler.rs b/dust-lang/src/compiler.rs index ddc52f4..8dd466b 100644 --- a/dust-lang/src/compiler.rs +++ b/dust-lang/src/compiler.rs @@ -781,8 +781,9 @@ impl<'src> Compiler<'src> { if let Some([Operation::Test, Operation::Jump]) = self.get_last_operations() {} let (argument, push_back) = self.handle_binary_argument(&left_instruction)?; + let is_local = matches!(argument, Argument::Local(_)); - if push_back { + if push_back || is_local { self.instructions .push((left_instruction, left_type.clone(), left_position)); } diff --git a/dust-lang/src/lib.rs b/dust-lang/src/lib.rs index 55a2cb6..ef48525 100644 --- a/dust-lang/src/lib.rs +++ b/dust-lang/src/lib.rs @@ -26,7 +26,7 @@ pub use crate::operation::Operation; pub use crate::optimize::{optimize_control_flow, optimize_set_local}; pub use crate::r#type::{EnumType, FunctionType, StructType, Type, TypeConflict}; pub use crate::scope::Scope; -pub use crate::token::{display_token_list, Token, TokenKind, TokenOwned}; +pub use crate::token::{write_token_list, Token, TokenKind, TokenOwned}; pub use crate::value::{AbstractValue, ConcreteValue, RangeValue, Value, ValueError, ValueRef}; pub use crate::vm::{run, Vm, VmError}; diff --git a/dust-lang/src/token.rs b/dust-lang/src/token.rs index 96d0d7f..4d3e48c 100644 --- a/dust-lang/src/token.rs +++ b/dust-lang/src/token.rs @@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize}; use crate::Span; -pub fn display_token_list(tokens: &[(Token, Span)], styled: bool, writer: &mut W) { +pub fn write_token_list(tokens: &[(Token, Span)], styled: bool, writer: &mut W) { const HEADER: [&str; 2] = [" TOKEN POSITION ", "------------- ----------"]; writeln!(writer, "{}", HEADER[0]).unwrap();