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] 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
|
||||||
|
@ -1,82 +1,148 @@
|
|||||||
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
|
#[derive(Subcommand)]
|
||||||
#[arg(long)]
|
enum CliMode {
|
||||||
style_disassembly: Option<bool>,
|
/// Run the source code (default)
|
||||||
|
#[command(short_flag = 'r')]
|
||||||
|
Run {
|
||||||
|
#[command(flatten)]
|
||||||
|
global_arguments: GlobalArguments,
|
||||||
|
|
||||||
/// Log level
|
/// Do not print the program's output value
|
||||||
#[arg(short, long)]
|
#[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>,
|
log: Option<LevelFilter>,
|
||||||
|
|
||||||
/// Path to a source code file
|
/// Source code sent via command line
|
||||||
path: Option<String>,
|
#[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() {
|
fn main() {
|
||||||
let args = Cli::parse();
|
let start_time = Instant::now();
|
||||||
let mut logger = env_logger::builder();
|
let Cli {
|
||||||
|
global_arguments,
|
||||||
logger.format(|buf, record| {
|
mode,
|
||||||
let level_display = match record.level() {
|
} = Cli::parse();
|
||||||
Level::Info => "INFO".bold().white(),
|
let mode = mode.unwrap_or(CliMode::Run {
|
||||||
Level::Debug => "DEBUG".bold().blue(),
|
global_arguments,
|
||||||
Level::Warn => "WARN".bold().yellow(),
|
no_output: false,
|
||||||
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}")
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some(level) = args.log {
|
if let CliMode::Run {
|
||||||
logger.filter_level(level).init();
|
global_arguments,
|
||||||
} else {
|
no_output,
|
||||||
logger.parse_env("DUST_LOG").init();
|
} = 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 {
|
match run_result {
|
||||||
&read_to_string(path).expect("Failed to read file")
|
Ok(Some(value)) => {
|
||||||
} else if let Some(command) = &args.command {
|
if !no_output {
|
||||||
command
|
println!("{}", value)
|
||||||
} 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);
|
|
||||||
}
|
}
|
||||||
|
Ok(None) => {}
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
eprintln!("{}", error.report());
|
eprintln!("{}", error.report());
|
||||||
}
|
}
|
||||||
@ -85,12 +151,44 @@ fn main() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
match run(source) {
|
if let CliMode::Disassemble {
|
||||||
Ok(Some(value)) => println!("{}", value),
|
global_arguments,
|
||||||
Ok(None) => {}
|
style,
|
||||||
Err(error) => {
|
} = mode
|
||||||
eprintln!("{}", error.report());
|
{
|
||||||
}
|
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() {}
|
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));
|
||||||
}
|
}
|
||||||
|
@ -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};
|
||||||
|
|
||||||
|
@ -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();
|
||||||
|
Loading…
Reference in New Issue
Block a user