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] Integers (signed 64-bit)
- [ ] Ranges
- Composite Values
- [X] Strings (UTF-8)
- Composite Values
- [X] Concrete lists
- [X] Abstract lists (optimization)
- [ ] Concrete maps

View File

@ -1,38 +1,86 @@
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<String>,
#[command(flatten)]
global_arguments: GlobalArguments,
/// Whether to output the disassembled chunk
#[arg(short, long)]
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>,
#[command(subcommand)]
mode: Option<CliMode>,
}
fn main() {
let args = Cli::parse();
#[derive(Subcommand)]
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();
logger.format(|buf, record| {
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(),
@ -45,53 +93,103 @@ fn main() {
.map(|path| path.split("::").last().unwrap_or(path))
.unwrap_or("unknown")
.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}")
});
if let Some(level) = args.log {
if let Some(level) = log {
logger.filter_level(level).init();
} else {
logger.parse_env("DUST_LOG").init();
}
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
if let Some(source) = command {
source
} else {
eprintln!("No input provided");
let path = path.expect("Path is required when command is not 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);
}
Err(error) => {
eprintln!("{}", error.report());
read_to_string(path).expect("Failed to read file")
}
}
}
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) => {}
Err(error) => {
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)]

View File

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

View File

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

View File

@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize};
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 ", "------------- ----------"];
writeln!(writer, "{}", HEADER[0]).unwrap();