Skip to content

Commit ea044c4

Browse files
committed
fix(cli): honor implicit run flags and share run options 🛠️
1 parent 3ffed1c commit ea044c4

1 file changed

Lines changed: 81 additions & 99 deletions

File tree

ndc_bin/src/main.rs

Lines changed: 81 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
#![allow(clippy::print_stdout, clippy::print_stderr, clippy::exit)]
22

33
use crate::docs::docs;
4-
use anyhow::{Context, anyhow};
5-
use clap::{Parser, Subcommand};
4+
use anyhow::anyhow;
5+
use clap::{Args, Parser, Subcommand};
66
use highlighter::AndycppHighlighter;
77
use ndc_interpreter::Interpreter;
88
use phase_timing::write_phase_timings;
@@ -34,25 +34,8 @@ enum Command {
3434
/// Execute an .ndc file or start the repl (this default action may be omitted)
3535
Run {
3636
file: Option<PathBuf>,
37-
/// Print total time spent in each interpreter phase
38-
#[arg(long)]
39-
time: bool,
40-
/// Print each instruction as it is dispatched
41-
#[cfg(feature = "trace")]
42-
#[arg(long)]
43-
trace_print: bool,
44-
/// Print a histogram of instruction dispatch counts
45-
#[cfg(feature = "trace")]
46-
#[arg(long)]
47-
trace_histogram: bool,
48-
/// Print cumulative time spent per instruction type
49-
#[cfg(feature = "trace")]
50-
#[arg(long)]
51-
trace_time: bool,
52-
/// Render source as a heat map colored by time spent per span
53-
#[cfg(feature = "trace")]
54-
#[arg(long)]
55-
trace_span: bool,
37+
#[command(flatten)]
38+
options: RunOptions,
5639
},
5740
/// Output an .ndc file using the built-in syntax highlighting engine
5841
Highlight { file: PathBuf },
@@ -79,19 +62,42 @@ enum Command {
7962
Unknown(Vec<String>),
8063
}
8164

65+
#[derive(Args, Clone, Copy, Default)]
66+
struct RunOptions {
67+
/// Print total time spent in each interpreter phase
68+
#[arg(long)]
69+
time: bool,
70+
/// Print each instruction as it is dispatched
71+
#[cfg(feature = "trace")]
72+
#[arg(long)]
73+
trace_print: bool,
74+
/// Print a histogram of instruction dispatch counts
75+
#[cfg(feature = "trace")]
76+
#[arg(long)]
77+
trace_histogram: bool,
78+
/// Print cumulative time spent per instruction type
79+
#[cfg(feature = "trace")]
80+
#[arg(long)]
81+
trace_time: bool,
82+
/// Render source as a heat map colored by time spent per span
83+
#[cfg(feature = "trace")]
84+
#[arg(long)]
85+
trace_span: bool,
86+
}
87+
88+
#[derive(Parser)]
89+
#[command(name = "ndc", disable_help_subcommand = true)]
90+
struct ImplicitRunArgs {
91+
file: PathBuf,
92+
#[command(flatten)]
93+
options: RunOptions,
94+
}
95+
8296
impl Default for Command {
8397
fn default() -> Self {
8498
Self::Run {
8599
file: None,
86-
time: false,
87-
#[cfg(feature = "trace")]
88-
trace_print: false,
89-
#[cfg(feature = "trace")]
90-
trace_histogram: false,
91-
#[cfg(feature = "trace")]
92-
trace_time: false,
93-
#[cfg(feature = "trace")]
94-
trace_span: false,
100+
options: RunOptions::default(),
95101
}
96102
}
97103
}
@@ -100,15 +106,7 @@ enum Action {
100106
RunLsp,
101107
RunFile {
102108
path: PathBuf,
103-
time: bool,
104-
#[cfg(feature = "trace")]
105-
trace_print: bool,
106-
#[cfg(feature = "trace")]
107-
trace_histogram: bool,
108-
#[cfg(feature = "trace")]
109-
trace_time: bool,
110-
#[cfg(feature = "trace")]
111-
trace_span: bool,
109+
options: RunOptions,
112110
},
113111
DisassembleFile(PathBuf),
114112
HighlightFile(PathBuf),
@@ -126,51 +124,22 @@ impl TryFrom<Command> for Action {
126124
let action = match value {
127125
Command::Run {
128126
file: Some(file),
129-
time,
130-
#[cfg(feature = "trace")]
131-
trace_print,
132-
#[cfg(feature = "trace")]
133-
trace_histogram,
134-
#[cfg(feature = "trace")]
135-
trace_time,
136-
#[cfg(feature = "trace")]
137-
trace_span,
138-
} => Self::RunFile {
139-
path: file,
140-
time,
141-
#[cfg(feature = "trace")]
142-
trace_print,
143-
#[cfg(feature = "trace")]
144-
trace_histogram,
145-
#[cfg(feature = "trace")]
146-
trace_time,
147-
#[cfg(feature = "trace")]
148-
trace_span,
149-
},
127+
options,
128+
} => Self::RunFile { path: file, options },
150129
Command::Run { file: None, .. } => Self::StartRepl,
151130
Command::Lsp { stdio: _ } => Self::RunLsp,
152131
Command::Disassemble { file } => Self::DisassembleFile(file),
153132
Command::Highlight { file } => Self::HighlightFile(file),
154133
Command::Docs { query, no_color } => Self::Docs { query, no_color },
155134
Command::Unknown(args) => {
156-
match args.len() {
157-
0 => {
158-
// This case should have defaulted to `Command::Run { file: None }`
159-
unreachable!("fallback case reached with 0 arguments (should never happen)")
160-
}
161-
1 => Self::RunFile {
162-
path: args[0].parse::<PathBuf>().context("invalid path")?,
163-
time: false,
164-
#[cfg(feature = "trace")]
165-
trace_print: false,
166-
#[cfg(feature = "trace")]
167-
trace_histogram: false,
168-
#[cfg(feature = "trace")]
169-
trace_time: false,
170-
#[cfg(feature = "trace")]
171-
trace_span: false,
172-
},
173-
n => return Err(anyhow!("invalid number of arguments: {n}")),
135+
let implicit_run = ImplicitRunArgs::try_parse_from(
136+
std::iter::once("ndc").chain(args.iter().map(String::as_str)),
137+
)
138+
.map_err(|err| anyhow!(err.render().to_string()))?;
139+
140+
Self::RunFile {
141+
path: implicit_run.file,
142+
options: implicit_run.options,
174143
}
175144
}
176145
};
@@ -186,15 +155,7 @@ fn main() -> anyhow::Result<()> {
186155
match action {
187156
Action::RunFile {
188157
path,
189-
time,
190-
#[cfg(feature = "trace")]
191-
trace_print,
192-
#[cfg(feature = "trace")]
193-
trace_histogram,
194-
#[cfg(feature = "trace")]
195-
trace_time,
196-
#[cfg(feature = "trace")]
197-
trace_span,
158+
options,
198159
} => {
199160
let filename = path
200161
.file_name()
@@ -210,16 +171,16 @@ fn main() -> anyhow::Result<()> {
210171
{
211172
use ndc_interpreter::tracer;
212173
let mut tracers: Vec<Box<dyn tracer::VmTracer>> = Vec::new();
213-
if trace_print {
174+
if options.trace_print {
214175
tracers.push(Box::new(tracer::PrintTracer));
215176
}
216-
if trace_histogram {
177+
if options.trace_histogram {
217178
tracers.push(Box::new(tracer::HistogramTracer::new()));
218179
}
219-
if trace_time {
180+
if options.trace_time {
220181
tracers.push(Box::new(tracer::TimingTracer::new()));
221182
}
222-
if trace_span {
183+
if options.trace_span {
223184
tracers.push(Box::new(span_tracer::SpanTracer::new()));
224185
}
225186
if !tracers.is_empty() {
@@ -228,16 +189,19 @@ fn main() -> anyhow::Result<()> {
228189
}
229190

230191
let name = filename.as_deref().unwrap_or("<input>");
231-
match interpreter.eval_named_with_timings(name, &string) {
232-
Ok((_, timings)) => {
233-
if time {
192+
if options.time {
193+
match interpreter.eval_named_with_timings(name, &string) {
194+
Ok((_, timings)) => {
234195
write_phase_timings(&mut std::io::stderr(), &timings)?;
235196
}
197+
Err(err) => {
198+
diagnostic::emit_error(interpreter.source_db(), err);
199+
process::exit(1);
200+
}
236201
}
237-
Err(err) => {
238-
diagnostic::emit_error(interpreter.source_db(), err);
239-
process::exit(1);
240-
}
202+
} else if let Err(err) = interpreter.eval_named(name, &string) {
203+
diagnostic::emit_error(interpreter.source_db(), err);
204+
process::exit(1);
241205
}
242206
}
243207
Action::DisassembleFile(path) => {
@@ -289,11 +253,29 @@ fn start_lsp() {
289253
#[cfg(test)]
290254
mod test {
291255
use clap::CommandFactory;
256+
use std::path::PathBuf;
292257

293-
use crate::Cli;
258+
use crate::{Action, Cli, Command};
294259

295260
#[test]
296261
fn test_clap() {
297262
Cli::command().debug_assert();
298263
}
264+
265+
#[test]
266+
fn implicit_run_honors_time_flag() {
267+
let action = Action::try_from(Command::Unknown(vec![
268+
"script.ndc".to_string(),
269+
"--time".to_string(),
270+
]))
271+
.expect("implicit run flags should parse");
272+
273+
match action {
274+
Action::RunFile { path, options } => {
275+
assert_eq!(path, PathBuf::from("script.ndc"));
276+
assert!(options.time);
277+
}
278+
_ => panic!("expected run action"),
279+
}
280+
}
299281
}

0 commit comments

Comments
 (0)