1
0

Improve CLI

This commit is contained in:
Jeff 2024-12-01 21:17:22 -05:00
parent dbf7dc53c2
commit 3146e70bf1
5 changed files with 170 additions and 71 deletions

View File

@ -41,8 +41,8 @@ Dust is still in development. This list may change as the language evolves.
- [X] Functions - [X] Functions
- [X] Integers (signed 64-bit) - [X] Integers (signed 64-bit)
- [ ] Ranges - [ ] Ranges
- Composite Values
- [X] Strings (UTF-8) - [X] Strings (UTF-8)
- Composite Values
- [X] Concrete lists - [X] Concrete lists
- [X] Abstract lists (optimization) - [X] Abstract lists (optimization)
- [ ] Concrete maps - [ ] Concrete maps

View File

@ -1,38 +1,86 @@
use std::fs::read_to_string; use std::io::{stdout, Write};
use std::io::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 colored::Colorize;
use dust_lang::{compile, run}; use dust_lang::{compile, lex, run, write_token_list};
use log::{Level, LevelFilter}; use log::{Level, LevelFilter};
#[derive(Parser)] #[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 { struct Cli {
/// Source code sent via command line #[command(flatten)]
#[arg(short, long)] global_arguments: GlobalArguments,
command: Option<String>,
/// Whether to output the disassembled chunk #[command(subcommand)]
#[arg(short, long)] mode: Option<CliMode>,
parse: bool,
/// Whether to style the disassembled chunk
#[arg(long)]
style_disassembly: Option<bool>,
/// Log level
#[arg(short, long)]
log: Option<LevelFilter>,
/// Path to a source code file
path: Option<String>,
} }
fn main() { #[derive(Subcommand)]
let args = Cli::parse(); enum CliMode {
/// Run the source code (default)
#[command(short_flag = 'r')]
Run {
#[command(flatten)]
global_arguments: GlobalArguments,
/// 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<LevelFilter>,
/// Source code sent via command line
#[arg(short, long, value_name = "SOURCE", conflicts_with = "path")]
command: Option<String>,
/// File to read source code from
#[arg(required_unless_present = "command")]
path: Option<PathBuf>,
}
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(); let mut logger = env_logger::builder();
logger.format(|buf, record| { logger.format(move |buf, record| {
let elapsed = start_time.elapsed().as_nanos();
let level_display = match record.level() { let level_display = match record.level() {
Level::Info => "INFO".bold().white(), Level::Info => "INFO".bold().white(),
Level::Debug => "DEBUG".bold().blue(), Level::Debug => "DEBUG".bold().blue(),
@ -45,53 +93,103 @@ fn main() {
.map(|path| path.split("::").last().unwrap_or(path)) .map(|path| path.split("::").last().unwrap_or(path))
.unwrap_or("unknown") .unwrap_or("unknown")
.dimmed(); .dimmed();
let display = format!("{level_display:5} {module:^6} {args}", args = record.args()); let display = format!(
"{elapsed} {level_display:5} {module:^6} {args}",
args = record.args()
);
writeln!(buf, "{display}") writeln!(buf, "{display}")
}); });
if let Some(level) = args.log { if let Some(level) = log {
logger.filter_level(level).init(); logger.filter_level(level).init();
} else { } else {
logger.parse_env("DUST_LOG").init(); logger.parse_env("DUST_LOG").init();
} }
let source = if let Some(path) = &args.path { if let Some(source) = command {
&read_to_string(path).expect("Failed to read file") source
} else if let Some(command) = &args.command {
command
} else { } else {
eprintln!("No input provided"); let path = path.expect("Path is required when command is not provided");
return; read_to_string(path).expect("Failed to read file")
};
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);
}
Err(error) => {
eprintln!("{}", error.report());
} }
} }
}
return; fn main() {
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 CliMode::Run {
global_arguments,
no_output,
} = mode
{
let source = global_arguments.set_log_and_get_source(start_time);
let run_result = run(&source);
match run_result {
Ok(Some(value)) => {
if !no_output {
println!("{}", value)
}
} }
match run(source) {
Ok(Some(value)) => println!("{}", value),
Ok(None) => {} Ok(None) => {}
Err(error) => { Err(error) => {
eprintln!("{}", error.report()); eprintln!("{}", error.report());
} }
} }
return;
}
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)
}
} }
#[cfg(test)] #[cfg(test)]

View File

@ -781,8 +781,9 @@ impl<'src> Compiler<'src> {
if let Some([Operation::Test, Operation::Jump]) = self.get_last_operations() {} if let Some([Operation::Test, Operation::Jump]) = self.get_last_operations() {}
let (argument, push_back) = self.handle_binary_argument(&left_instruction)?; 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 self.instructions
.push((left_instruction, left_type.clone(), left_position)); .push((left_instruction, left_type.clone(), left_position));
} }

View File

@ -26,7 +26,7 @@ pub use crate::operation::Operation;
pub use crate::optimize::{optimize_control_flow, optimize_set_local}; pub use crate::optimize::{optimize_control_flow, optimize_set_local};
pub use crate::r#type::{EnumType, FunctionType, StructType, Type, TypeConflict}; pub use crate::r#type::{EnumType, FunctionType, StructType, Type, TypeConflict};
pub use crate::scope::Scope; 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::value::{AbstractValue, ConcreteValue, RangeValue, Value, ValueError, ValueRef};
pub use crate::vm::{run, Vm, VmError}; pub use crate::vm::{run, Vm, VmError};

View File

@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize};
use crate::Span; use crate::Span;
pub fn display_token_list<W: Write>(tokens: &[(Token, Span)], styled: bool, writer: &mut W) { pub fn write_token_list<W: Write>(tokens: &[(Token, Span)], styled: bool, writer: &mut W) {
const HEADER: [&str; 2] = [" TOKEN POSITION ", "------------- ----------"]; const HEADER: [&str; 2] = [" TOKEN POSITION ", "------------- ----------"];
writeln!(writer, "{}", HEADER[0]).unwrap(); writeln!(writer, "{}", HEADER[0]).unwrap();