knave 0.14.3

A Lisp programming language for humans and agents that infers types and ownership and compiles to readable Rust
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
mod add;
mod build;
mod build_directory;
mod capped_report;
mod check;
mod check_flag;
mod checkout_bounds;
mod color_when;
mod counter_report;
mod crate_section;
mod definition_buffer;
mod definition_rounds;
mod diagnostic_output;
mod distinct_output;
mod doc;
mod doc_arguments;
mod doc_folder;
mod doc_pages;
mod doc_write;
mod edited_lines;
mod emit;
mod enter_key;
mod entry_tree;
mod error_location;
mod eval;
mod evaluation_output;
mod exit_status;
mod expand;
mod finished_program;
mod fmt;
mod front_end_stack;
mod input_helper;
mod interruption;
mod json_replies;
mod library_walk;
mod located_program;
mod located_project;
mod name_records;
mod new;
mod open_input;
mod painted_program;
mod program_file;
mod program_signal;
mod program_stream;
mod project_crates;
mod project_file_edit;
mod project_task;
mod prompt_diagnostics;
mod prompt_history;
mod prompt_lines;
mod prompt_module;
mod remove;
mod repl;
mod run;
mod skipped_directory;
mod source_walk;
mod stage_work;
mod standard_error;
mod streamed_program;
mod task;
mod task_catalog;
mod task_runners;
mod temporary_directory;
mod term_parser;
mod terminal;
mod test;
mod test_binary;
mod test_entries;
mod test_record;
mod test_report;
mod test_run;
mod token_colors;
mod tree_bounds;
mod types;
mod user_label_output;
mod value_marker;
mod whole_characters;

use std::env;
use std::ffi::OsString;
use std::io;
use std::path::PathBuf;
use std::process::ExitCode;

use anstream::AutoStream;
use clap::{Args, CommandFactory, FromArgMatches, Parser, Subcommand};

use crate::build::BuildProfile;
use crate::counters::CountingAllocator;
use crate::language::Language;
use crate::source::TextPosition;
use add::AddCommand;
use build::BuildCommand;
use check::CheckCommand;
use check_flag::CheckFlag;
use color_when::ColorWhen;
use counter_report::CounterReport;
use crate_section::CrateSection;
use doc::DocCommand;
use doc_arguments::DocArguments;
use emit::EmitCommand;
use eval::{EvalCommand, EvaluationSettings, FormsText, ModuleText};
use evaluation_output::{EvaluationOutput, JsonEvaluation, PrintedEvaluation};
use exit_status::ExitStatus;
use expand::ExpandCommand;
use fmt::FmtCommand;
use front_end_stack::FrontEndStack;
use new::NewCommand;
use remove::RemoveCommand;
use repl::ReplCommand;
use run::RunCommand;
use task::TaskCommand;
use terminal::{BuildOptions, Terminal};
use test::{TestCommand, TestSelection};
use types::TypesCommand;

const DEV_HELP: &str = "Edit `dev-dependencies`, which only `test` builds with";
const ENTRY_HELP: &str = "The program's entry file [default: the project's `:entry`]";

#[derive(Parser)]
#[command(
    name = Language::NAME,
    version,
    about = "Compile Lisp source files to Rust",
    after_help = "With no command, start the prompt as repl does."
)]
pub struct Cli {
    #[command(subcommand)]
    command: Option<Command>,
    #[arg(long, global = true, hide = true)]
    counters: bool,
    #[arg(
        long,
        global = true,
        value_enum,
        value_name = "WHEN",
        default_value_t,
        help = "When to color the output"
    )]
    color: ColorWhen,
}

#[derive(Subcommand)]
enum Command {
    #[command(flatten)]
    Inspection(InspectionCommand),
    #[command(flatten)]
    Program(ProgramCommand),
    #[command(flatten)]
    Project(ProjectCommand),
}

#[derive(Subcommand)]
enum ProjectCommand {
    #[command(about = "Add a crate to the project's dependencies, at the version Cargo picks")]
    Add {
        #[arg(help = "The crate's name, as crates.io writes it")]
        name: String,
        #[arg(
            long,
            value_delimiter = ',',
            help = "Features to turn on, separated by commas"
        )]
        features: Vec<String>,
        #[arg(long = CrateSection::DEV_FLAG_NAME, help = DEV_HELP)]
        dev: bool,
    },
    #[command(about = "Remove a crate from the project's dependencies")]
    Remove {
        #[arg(help = "The crate's name, as the project file writes it")]
        name: String,
        #[arg(long = CrateSection::DEV_FLAG_NAME, help = DEV_HELP)]
        dev: bool,
    },
    #[command(
        name = TaskCommand::NAME,
        about = "Run a task from the project's `tasks` directory, or list the tasks"
    )]
    Task {
        #[arg(
            trailing_var_arg = true,
            allow_hyphen_values = true,
            value_name = "NAME [ARGUMENTS]",
            help = "The task's name, then the arguments it takes [default: list the tasks]"
        )]
        command: Vec<String>,
        #[command(flatten)]
        release: ReleaseFlag,
    },
    #[command(about = "Create a project in a new directory of its name")]
    New {
        #[arg(
            help = "The project's name, in kebab-case",
            value_parser = clap::builder::NonEmptyStringValueParser::new()
        )]
        name: String,
    },
}

#[derive(Subcommand)]
enum InspectionCommand {
    #[command(about = "Read, expand and check a program, then print its diagnostics")]
    Check {
        #[arg(help = ENTRY_HELP)]
        file: Option<PathBuf>,
    },
    #[command(about = "Print the inferred signature of each function in a program")]
    Types {
        #[arg(help = ENTRY_HELP)]
        file: Option<PathBuf>,
    },
    #[command(
        name = DocCommand::NAME,
        about = "Print the page of a prelude term, or of a program's term with --file"
    )]
    Doc(DocArguments),
    #[command(about = "Print a program as kernel forms after macro expansion")]
    Expand {
        #[arg(help = ENTRY_HELP)]
        file: Option<PathBuf>,
    },
    #[command(about = "Print the Rust code generated for a program")]
    Emit {
        #[arg(help = ENTRY_HELP)]
        file: Option<PathBuf>,
    },
    #[command(about = "Lay out source files in place, or standard input given `-`")]
    Fmt {
        #[arg(help = "The files and directories to lay out [default: the project's root]")]
        paths: Vec<PathBuf>,
        #[command(flatten)]
        check: CheckFlag,
        #[arg(
            long,
            value_name = "FILE",
            help = "Find the modules that standard input uses as if it were this file"
        )]
        stdin_path: Option<PathBuf>,
    },
}

#[derive(Subcommand)]
enum ProgramCommand {
    #[command(about = "Compile a program to a native binary")]
    Build {
        #[arg(help = ENTRY_HELP)]
        file: Option<PathBuf>,
        #[command(flatten)]
        release: ReleaseFlag,
        #[arg(short, long)]
        output: Option<PathBuf>,
    },
    #[command(about = "Check a module, then run forms once in its scope and print their values")]
    Eval {
        file: PathBuf,
        #[arg(allow_hyphen_values = true)]
        forms: String,
        #[command(flatten)]
        stdin: StdinFlag,
        #[arg(
            long,
            help = "Resolve `use` from this directory [default: the project's source root, or the file's directory]"
        )]
        root: Option<PathBuf>,
        #[command(flatten)]
        position: PositionFlags,
        #[command(flatten)]
        release: ReleaseFlag,
        #[command(flatten)]
        json: JsonFlags,
    },
    #[command(about = "Read forms at a prompt, keep the definitions and run the rest")]
    Repl {
        #[arg(
            long,
            help = "Resolve `use` from this directory [default: the project's source root, or the working directory]"
        )]
        root: Option<PathBuf>,
        #[command(flatten)]
        release: ReleaseFlag,
    },
    #[command(about = "Build the tests of a file or a directory and run them")]
    Test {
        #[arg(help = "The test file or directory [default: the project's `tests` directory]")]
        path: Option<PathBuf>,
        #[arg(hide = true)]
        positional_filter: Option<String>,
        #[arg(
            long,
            value_name = "TEXT",
            help = "Run only the tests whose full name contains this text"
        )]
        filter: Option<String>,
        #[arg(
            long,
            value_name = "NAME",
            conflicts_with = "filter",
            help = "Run only the test with this full name"
        )]
        exact: Option<String>,
        #[arg(
            long,
            value_name = "MODULE",
            help = "List the names that MODULE exports and no test writes"
        )]
        untested: Option<String>,
        #[command(flatten)]
        release: ReleaseFlag,
    },
    #[command(about = "Build a program and run it")]
    Run {
        #[arg(help = ENTRY_HELP)]
        file: Option<PathBuf>,
        #[command(flatten)]
        release: ReleaseFlag,
        #[arg(last = true)]
        args: Vec<String>,
    },
}

#[derive(Args)]
struct ReleaseFlag {
    #[arg(long)]
    release: bool,
}

#[derive(Args)]
struct StdinFlag {
    #[arg(long, help = "Read the module's text from standard input")]
    stdin: bool,
}

#[derive(Args)]
struct PositionFlags {
    #[arg(
        long,
        requires = "column",
        help = "The line where the forms start in the module's text"
    )]
    line: Option<usize>,
    #[arg(
        long,
        requires = "line",
        help = "The byte column where the forms start, from 1"
    )]
    column: Option<usize>,
}

#[derive(Args)]
struct JsonFlags {
    #[arg(
        long,
        help = "Write one JSON object per line for each output, value and diagnostic"
    )]
    json: bool,
    #[arg(
        long,
        requires = "json",
        help = "Echo this text as `id` on every JSON object"
    )]
    id: Option<String>,
}

impl Cli {
    pub fn parse_arguments() -> Self {
        let arguments: Vec<OsString> = env::args_os().collect();
        let mut command = Self::command()
            .color(ColorWhen::written_in(&arguments).into())
            .mut_subcommand(DocCommand::NAME, |doc| {
                doc.override_usage(DocCommand::usage())
            });
        Self::parse_with(&mut command, arguments).unwrap_or_else(|error| error.exit())
    }

    fn parse_with(
        command: &mut clap::Command,
        arguments: Vec<OsString>,
    ) -> Result<Self, clap::Error> {
        let mut matches = command.try_get_matches_from_mut(arguments)?;
        Self::from_arg_matches_mut(&mut matches).map_err(|error| error.format(command))
    }

    pub fn run(self, allocator: Option<&CountingAllocator>) -> ExitCode {
        FrontEndStack::run(move || self.run_on_this_stack(allocator))
    }

    fn run_on_this_stack(self, allocator: Option<&CountingAllocator>) -> ExitCode {
        anstream::ColorChoice::from(self.color).write_global();
        let mut terminal = Terminal::new(AutoStream::choice(&io::stderr()));
        let command = self.command.unwrap_or_else(Command::prompt);
        let status = command.execute(&mut terminal);
        if self.counters {
            let mut report = CounterReport::new(allocator);
            report.add_stage_work(terminal.work());
            report.print();
        }
        status.die_by_the_program_signal();
        status.into()
    }
}

impl Command {
    fn prompt() -> Self {
        Self::Program(ProgramCommand::Repl {
            root: None,
            release: ReleaseFlag { release: false },
        })
    }

    fn execute(self, terminal: &mut Terminal) -> ExitStatus {
        match self {
            Command::Inspection(command) => command.execute(terminal),
            Command::Program(command) => command.execute(terminal),
            Command::Project(command) => command.execute(terminal),
        }
    }
}

impl InspectionCommand {
    fn execute(self, terminal: &mut Terminal) -> ExitStatus {
        match self {
            Self::Check { file } => CheckCommand::new(file).run(terminal),
            Self::Types { file } => TypesCommand::new(file).run(terminal),
            Self::Doc(arguments) => arguments.run(terminal),
            Self::Expand { file } => ExpandCommand::new(file).run(terminal),
            Self::Emit { file } => EmitCommand::new(file).run(terminal),
            Self::Fmt {
                paths,
                check,
                stdin_path,
            } => FmtCommand::new(paths, check.mode(), stdin_path).run(terminal),
        }
    }
}

impl ProjectCommand {
    fn execute(self, terminal: &mut Terminal) -> ExitStatus {
        match self {
            Self::New { name } => NewCommand::new(name).run(terminal),
            Self::Add {
                name,
                features,
                dev,
            } => AddCommand::new(name, features, CrateSection::of(dev)).run(terminal),
            Self::Remove { name, dev } => {
                RemoveCommand::new(name, CrateSection::of(dev)).run(terminal)
            }
            Self::Task { command, release } => {
                TaskCommand::new(command, release.profile()).run(terminal)
            }
        }
    }
}

impl ProgramCommand {
    fn execute(self, terminal: &mut Terminal) -> ExitStatus {
        match self {
            Self::Build {
                file,
                release,
                output,
            } => BuildCommand::new(
                file,
                BuildOptions {
                    profile: release.profile(),
                    output,
                },
            )
            .run(terminal),
            Self::Eval {
                file,
                forms,
                stdin,
                root,
                position,
                release,
                json,
            } => EvalCommand::new(
                stdin.module_text(file),
                root,
                FormsText::new(forms, position.position()),
                EvaluationSettings {
                    profile: release.profile(),
                    output: json.output(),
                },
            )
            .run(terminal),
            Self::Repl { root, release } => ReplCommand::new(root, release.profile()).run(terminal),
            Self::Test {
                positional_filter: Some(filter),
                ..
            } => TestSelection::report_positional_filter(&filter),
            Self::Test {
                path,
                positional_filter: None,
                filter,
                exact,
                untested,
                release,
            } => TestCommand::new(
                path,
                TestSelection::of(filter, exact),
                untested,
                release.profile(),
            )
            .run(terminal),
            Self::Run {
                file,
                release,
                args,
            } => RunCommand::new(
                file,
                BuildOptions {
                    profile: release.profile(),
                    output: None,
                },
                args,
            )
            .run(terminal),
        }
    }
}

impl ReleaseFlag {
    fn profile(&self) -> BuildProfile {
        if self.release {
            BuildProfile::Release
        } else {
            BuildProfile::Debug
        }
    }
}

impl StdinFlag {
    fn module_text(&self, file: PathBuf) -> ModuleText {
        if self.stdin {
            ModuleText::StandardInput(file)
        } else {
            ModuleText::File(file)
        }
    }
}

impl PositionFlags {
    fn position(&self) -> Option<TextPosition> {
        Some(TextPosition::new(self.line?, self.column?))
    }
}

impl JsonFlags {
    fn output(self) -> Box<dyn EvaluationOutput> {
        if self.json {
            Box::new(JsonEvaluation::new(self.id))
        } else {
            Box::new(PrintedEvaluation::for_standard_output())
        }
    }
}