Snazz up the CLI
This commit is contained in:
parent
d7289414f4
commit
cee9f0d95c
48
Cargo.lock
generated
48
Cargo.lock
generated
@ -102,6 +102,12 @@ version = "1.3.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "2.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bumpalo"
|
name = "bumpalo"
|
||||||
version = "3.16.0"
|
version = "3.16.0"
|
||||||
@ -132,7 +138,7 @@ version = "2.34.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
|
checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags 1.3.2",
|
||||||
"textwrap",
|
"textwrap",
|
||||||
"unicode-width",
|
"unicode-width",
|
||||||
]
|
]
|
||||||
@ -157,6 +163,7 @@ dependencies = [
|
|||||||
"anstyle",
|
"anstyle",
|
||||||
"clap_lex",
|
"clap_lex",
|
||||||
"strsim",
|
"strsim",
|
||||||
|
"terminal_size",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -345,6 +352,16 @@ dependencies = [
|
|||||||
"log",
|
"log",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "errno"
|
||||||
|
version = "0.3.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.2.15"
|
version = "0.2.15"
|
||||||
@ -450,6 +467,12 @@ version = "0.2.161"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1"
|
checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "linux-raw-sys"
|
||||||
|
version = "0.4.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lock_api"
|
name = "lock_api"
|
||||||
version = "0.4.12"
|
version = "0.4.12"
|
||||||
@ -647,6 +670,19 @@ dependencies = [
|
|||||||
"semver",
|
"semver",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustix"
|
||||||
|
version = "0.38.42"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.6.0",
|
||||||
|
"errno",
|
||||||
|
"libc",
|
||||||
|
"linux-raw-sys",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ryu"
|
name = "ryu"
|
||||||
version = "1.0.18"
|
version = "1.0.18"
|
||||||
@ -775,6 +811,16 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "terminal_size"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5352447f921fda68cf61b4101566c0bdb5104eff6804d0678e5227580ab6a4e9"
|
||||||
|
dependencies = [
|
||||||
|
"rustix",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "textwrap"
|
name = "textwrap"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
let mut i = 0
|
let mut i = 0
|
||||||
|
|
||||||
while i < 5_000_000 {
|
while i < 5_000_000 {
|
||||||
|
|
||||||
|
if i % 100000 == 0 {
|
||||||
|
write_line(i)
|
||||||
|
}
|
||||||
i += 1
|
i += 1
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "dust-cli"
|
name = "dust-cli"
|
||||||
description = "The Dust Programming Language CLI"
|
description = "Dust Programming Language CLI"
|
||||||
authors = ["Jeff Anderson"]
|
authors = ["Jeff Anderson"]
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
@ -13,7 +13,7 @@ name = "dust"
|
|||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { version = "4.5.14", features = ["derive"] }
|
clap = { version = "4.5.14", features = ["cargo", "color", "derive", "help", "wrap_help"] }
|
||||||
colored = "2.1.0"
|
colored = "2.1.0"
|
||||||
dust-lang = { path = "../dust-lang" }
|
dust-lang = { path = "../dust-lang" }
|
||||||
env_logger = "0.11.5"
|
env_logger = "0.11.5"
|
||||||
|
@ -1,77 +1,137 @@
|
|||||||
use std::io::{stdout, Write};
|
use std::io::{self, stdout, Read, Write};
|
||||||
use std::time::Instant;
|
use std::time::{Duration, Instant};
|
||||||
use std::{fs::read_to_string, path::PathBuf};
|
use std::{fs::read_to_string, path::PathBuf};
|
||||||
|
|
||||||
use clap::{Args, Parser};
|
use clap::builder::StyledStr;
|
||||||
|
use clap::{
|
||||||
|
builder::{styling::AnsiColor, Styles},
|
||||||
|
ArgAction, Args, ColorChoice, Parser, ValueHint,
|
||||||
|
};
|
||||||
|
use clap::{crate_authors, crate_description, crate_version};
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use dust_lang::{compile, CompileError, Compiler, DustError, DustString, Lexer, Span, Token, Vm};
|
use dust_lang::{CompileError, Compiler, DustError, DustString, Lexer, Span, Token, Vm};
|
||||||
use log::{Level, LevelFilter};
|
use log::{Level, LevelFilter};
|
||||||
|
|
||||||
const DEFAULT_PROGRAM_NAME: &str = "Dust CLI Input";
|
const HELP_TEMPLATE: &str = "\
|
||||||
|
{about}
|
||||||
|
{version}
|
||||||
|
{author}
|
||||||
|
|
||||||
|
{usage-heading}
|
||||||
|
{usage}
|
||||||
|
|
||||||
|
{all-args}
|
||||||
|
";
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[clap(
|
#[clap(
|
||||||
name = env!("CARGO_PKG_NAME"),
|
version = crate_version!(),
|
||||||
version = env!("CARGO_PKG_VERSION"),
|
author = crate_authors!(),
|
||||||
author = env!("CARGO_PKG_AUTHORS"),
|
about = crate_description!(),
|
||||||
about = env!("CARGO_PKG_DESCRIPTION"),
|
term_width = 80,
|
||||||
|
color = ColorChoice::Auto,
|
||||||
|
styles = Styles::styled()
|
||||||
|
.header(AnsiColor::BrightMagenta.on_default().bold())
|
||||||
|
.usage(AnsiColor::BrightWhite.on_default().bold())
|
||||||
|
.literal(AnsiColor::BrightCyan.on_default())
|
||||||
|
.placeholder(AnsiColor::BrightGreen.on_default())
|
||||||
|
.error(AnsiColor::BrightRed.on_default().bold())
|
||||||
|
.valid(AnsiColor::Blue.on_default())
|
||||||
|
.invalid(AnsiColor::BrightRed.on_default()),
|
||||||
|
disable_help_flag = true,
|
||||||
|
disable_version_flag = true,
|
||||||
|
help_template = StyledStr::from(HELP_TEMPLATE.bright_white().bold().to_string()),
|
||||||
)]
|
)]
|
||||||
struct Cli {
|
struct Cli {
|
||||||
/// Log level, overrides the DUST_LOG environment variable
|
/// Log level, overrides the DUST_LOG environment variable
|
||||||
///
|
#[arg(
|
||||||
/// Possible values: trace, debug, info, warn, error, off
|
short,
|
||||||
#[arg(short, long, value_name = "LOG_LEVEL")]
|
long,
|
||||||
|
value_name = "LOG_LEVEL",
|
||||||
|
value_parser = ["info", "trace", "debug"],
|
||||||
|
)]
|
||||||
|
#[clap(help_heading = Some("- Options"))]
|
||||||
log: Option<LevelFilter>,
|
log: Option<LevelFilter>,
|
||||||
|
|
||||||
|
#[arg(short, long, action = ArgAction::Help)]
|
||||||
|
#[clap(help_heading = Some("- Options"))]
|
||||||
|
help: bool,
|
||||||
|
|
||||||
|
#[arg(short, long, action = ArgAction::Version)]
|
||||||
|
#[clap(help_heading = Some("- Options"))]
|
||||||
|
version: bool,
|
||||||
|
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
mode: ModeFlags,
|
mode: Modes,
|
||||||
|
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
source: Source,
|
source: Source,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Args)]
|
#[derive(Args)]
|
||||||
#[group(multiple = false)]
|
#[group(multiple = true, requires = "run")]
|
||||||
struct ModeFlags {
|
struct RunOptions {
|
||||||
/// Run the source code (default)
|
/// Print the time taken for compilation and execution
|
||||||
#[arg(short, long)]
|
#[arg(long)]
|
||||||
run: bool,
|
#[clap(help_heading = Some("- Run Options"))]
|
||||||
|
|
||||||
/// Print the time taken to compile and run the source code
|
|
||||||
#[arg(long, requires("run"))]
|
|
||||||
time: bool,
|
time: bool,
|
||||||
|
|
||||||
#[arg(long, requires("run"))]
|
|
||||||
/// Do not print the run result
|
/// Do not print the run result
|
||||||
|
#[arg(long)]
|
||||||
|
#[clap(help_heading = Some("- Run Options"))]
|
||||||
no_output: bool,
|
no_output: bool,
|
||||||
|
|
||||||
|
/// Custom program name, overrides the file name
|
||||||
|
#[arg(long)]
|
||||||
|
#[clap(help_heading = Some("- Run Options"))]
|
||||||
|
program_name: Option<DustString>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Args)]
|
||||||
|
#[group(multiple = false)]
|
||||||
|
struct Modes {
|
||||||
|
/// Run the source code (default)
|
||||||
|
///
|
||||||
|
/// Use the RUN OPTIONS to control this mode
|
||||||
|
#[arg(short, long, default_value = "true")]
|
||||||
|
#[clap(help_heading = Some("- Modes"))]
|
||||||
|
run: bool,
|
||||||
|
|
||||||
|
#[command(flatten)]
|
||||||
|
run_options: RunOptions,
|
||||||
|
|
||||||
/// Compile a chunk and show the disassembly
|
/// Compile a chunk and show the disassembly
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
|
#[clap(help_heading = Some("- Modes"))]
|
||||||
disassemble: bool,
|
disassemble: bool,
|
||||||
|
|
||||||
/// Lex and display tokens from the source code
|
/// Lex and display tokens from the source code
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
|
#[clap(help_heading = Some("- Modes"))]
|
||||||
tokenize: bool,
|
tokenize: bool,
|
||||||
|
|
||||||
/// Style disassembly or tokenization output
|
/// Style disassembly or tokenization output
|
||||||
#[arg(
|
#[arg(short, long, default_value = "true")]
|
||||||
short,
|
#[clap(help_heading = Some("- Modes"))]
|
||||||
long,
|
|
||||||
default_value = "true",
|
|
||||||
requires("disassemble"),
|
|
||||||
requires("tokenize")
|
|
||||||
)]
|
|
||||||
style: bool,
|
style: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Args)]
|
#[derive(Args, Clone)]
|
||||||
#[group(required = true, multiple = false)]
|
#[group(required = true, multiple = false)]
|
||||||
struct Source {
|
struct Source {
|
||||||
/// Source code
|
/// Source code to use instead of a file
|
||||||
#[arg(short, long, value_name = "SOURCE")]
|
#[arg(short, long, value_hint = ValueHint::Other, value_name = "SOURCE")]
|
||||||
|
#[clap(help_heading = Some("- Input"))]
|
||||||
command: Option<String>,
|
command: Option<String>,
|
||||||
|
|
||||||
|
/// Read source code from stdin
|
||||||
|
#[arg(long)]
|
||||||
|
#[clap(help_heading = Some("- Input"))]
|
||||||
|
stdin: bool,
|
||||||
|
|
||||||
/// Path to a source code file
|
/// Path to a source code file
|
||||||
|
#[arg(value_hint = ValueHint::FilePath)]
|
||||||
|
#[clap(help_heading = Some("- Input"))]
|
||||||
file: Option<PathBuf>,
|
file: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,8 +155,13 @@ fn main() {
|
|||||||
|
|
||||||
let Cli {
|
let Cli {
|
||||||
log,
|
log,
|
||||||
source: Source { command, file },
|
source: Source {
|
||||||
|
command,
|
||||||
|
file,
|
||||||
|
stdin,
|
||||||
|
},
|
||||||
mode,
|
mode,
|
||||||
|
..
|
||||||
} = Cli::parse();
|
} = Cli::parse();
|
||||||
|
|
||||||
if let Some(level) = log {
|
if let Some(level) = log {
|
||||||
@ -105,29 +170,56 @@ fn main() {
|
|||||||
logger.parse_env("DUST_LOG").init();
|
logger.parse_env("DUST_LOG").init();
|
||||||
}
|
}
|
||||||
|
|
||||||
let source = if let Some(source) = command {
|
let (source, file_name) = if let Some(source) = command {
|
||||||
source
|
(source, None)
|
||||||
|
} else if stdin {
|
||||||
|
let mut source = String::new();
|
||||||
|
|
||||||
|
io::stdin()
|
||||||
|
.read_to_string(&mut source)
|
||||||
|
.expect("Failed to read from stdin");
|
||||||
|
|
||||||
|
(source, None)
|
||||||
} else {
|
} else {
|
||||||
let path = file.expect("Path is required when command is not provided");
|
let path = file.expect("Path is required when command is not provided");
|
||||||
|
let source = read_to_string(&path).expect("Failed to read file");
|
||||||
|
let file_name = path
|
||||||
|
.file_name()
|
||||||
|
.and_then(|os_str| os_str.to_str())
|
||||||
|
.map(DustString::from);
|
||||||
|
|
||||||
read_to_string(path).expect("Failed to read file")
|
(source, file_name)
|
||||||
};
|
};
|
||||||
|
let program_name = mode.run_options.program_name.or(file_name);
|
||||||
|
|
||||||
if mode.disassemble {
|
if mode.disassemble {
|
||||||
let chunk = match compile(&source) {
|
let lexer = Lexer::new(&source);
|
||||||
Ok(chunk) => chunk,
|
let mut compiler = match Compiler::new(lexer) {
|
||||||
|
Ok(compiler) => compiler,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
eprintln!("{}", error);
|
handle_compile_error(error, &source);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
match compiler.compile() {
|
||||||
|
Ok(()) => {}
|
||||||
|
Err(error) => {
|
||||||
|
handle_compile_error(error, &source);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let chunk = compiler.finish(program_name);
|
||||||
let mut stdout = stdout().lock();
|
let mut stdout = stdout().lock();
|
||||||
|
|
||||||
chunk
|
chunk
|
||||||
.disassembler(&mut stdout)
|
.disassembler(&mut stdout)
|
||||||
.style(mode.style)
|
.style(mode.style)
|
||||||
.source(&source)
|
.source(&source)
|
||||||
|
.width(70)
|
||||||
.disassemble()
|
.disassemble()
|
||||||
.expect("Failed to write disassembly to stdout");
|
.expect("Failed to write disassembly to stdout");
|
||||||
|
|
||||||
@ -198,30 +290,50 @@ fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let chunk = compiler.finish(Some(DEFAULT_PROGRAM_NAME));
|
let chunk = compiler.finish(program_name);
|
||||||
let compile_end = start_time.elapsed();
|
let compile_end = start_time.elapsed();
|
||||||
|
|
||||||
|
if mode.run_options.time {
|
||||||
|
print_time(compile_end);
|
||||||
|
}
|
||||||
|
|
||||||
let vm = Vm::new(chunk);
|
let vm = Vm::new(chunk);
|
||||||
let return_value = vm.run();
|
let return_value = vm.run();
|
||||||
let run_end = start_time.elapsed();
|
let run_end = start_time.elapsed();
|
||||||
|
|
||||||
if let Some(value) = return_value {
|
if let Some(value) = return_value {
|
||||||
if !mode.no_output {
|
if !mode.run_options.no_output {
|
||||||
println!("{}", value)
|
println!("{}", value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if mode.time {
|
if mode.run_options.time {
|
||||||
let compile_time = compile_end.as_micros();
|
|
||||||
let run_time = run_end - compile_end;
|
let run_time = run_end - compile_end;
|
||||||
|
|
||||||
|
print_time(run_time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_time(instant: Duration) {
|
||||||
|
let seconds = instant.as_secs_f64();
|
||||||
|
|
||||||
|
match seconds {
|
||||||
|
..=0.001 => {
|
||||||
println!(
|
println!(
|
||||||
"Compile time: {compile_time}µs Run time: {}s{}ms{}µs",
|
"Compile time: {microseconds} microseconds",
|
||||||
run_time.as_secs(),
|
microseconds = seconds * 1_000_000.0
|
||||||
run_time.subsec_millis(),
|
|
||||||
run_time.subsec_micros()
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
..=0.1 => {
|
||||||
|
println!(
|
||||||
|
"Compile time: {milliseconds} milliseconds",
|
||||||
|
milliseconds = seconds * 1000.0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
println!("Compile time: {seconds} seconds");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_compile_error(error: CompileError, source: &str) {
|
fn handle_compile_error(error: CompileError, source: &str) {
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
//! A chunk is output by the compiler to represent all of the information needed to execute a Dust
|
//! A chunk is output by the compiler to represent all of the information needed to execute a Dust
|
||||||
//! program. In addition to the program itself, each function in the source is compiled into its own
|
//! program. In addition to the program itself, each function in the source is compiled into its own
|
||||||
//! chunk and stored in the `prototypes` field of its parent. Thus, a chunk is also the
|
//! chunk and stored in the `prototypes` field of its parent. Thus, a chunk is also the
|
||||||
//! representation of a function prototype, i.e. a function declaration as opposed to an individual
|
//! representation of a function prototype, i.e. a function declaration, as opposed to an individual
|
||||||
//! instance.
|
//! instance.
|
||||||
//!
|
//!
|
||||||
//! Chunks have a name when they belong to a named function. They also have a type, so the input
|
//! Chunks have a name when they belong to a named function. They also have a type, so the input
|
||||||
@ -12,7 +12,7 @@
|
|||||||
//! cannot be instantiated directly and must be created by the compiler. However, when the Rust
|
//! cannot be instantiated directly and must be created by the compiler. However, when the Rust
|
||||||
//! compiler is in the "test" configuration (used for all types of test), [`Chunk::with_data`] can
|
//! compiler is in the "test" configuration (used for all types of test), [`Chunk::with_data`] can
|
||||||
//! be used to create a chunk for comparison to the compiler output. Do not try to run these chunks
|
//! be used to create a chunk for comparison to the compiler output. Do not try to run these chunks
|
||||||
//! in a virtual machine. Due to their missing stack size, they will cause a panic.
|
//! in a virtual machine. Due to their missing stack size and record index, they will cause a panic.
|
||||||
mod disassembler;
|
mod disassembler;
|
||||||
mod local;
|
mod local;
|
||||||
mod scope;
|
mod scope;
|
||||||
|
Loading…
Reference in New Issue
Block a user