Improve CLI
This commit is contained in:
parent
dbf7dc53c2
commit
3146e70bf1
@ -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
|
||||
|
@ -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<String>,
|
||||
#[command(flatten)]
|
||||
global_arguments: GlobalArguments,
|
||||
|
||||
/// Whether to output the disassembled chunk
|
||||
#[arg(short, long)]
|
||||
parse: bool,
|
||||
#[command(subcommand)]
|
||||
mode: Option<CliMode>,
|
||||
}
|
||||
|
||||
/// Whether to style the disassembled chunk
|
||||
#[arg(long)]
|
||||
style_disassembly: Option<bool>,
|
||||
#[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<LevelFilter>,
|
||||
|
||||
/// Path to a source code file
|
||||
path: Option<String>,
|
||||
/// 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(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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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};
|
||||
|
||||
|
@ -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();
|
||||
|
Loading…
Reference in New Issue
Block a user