2023-10-26 20:00:06 +00:00
|
|
|
//! Command line interface for the dust programming language.
|
2023-08-22 15:40:50 +00:00
|
|
|
use clap::Parser;
|
2023-08-28 20:14:55 +00:00
|
|
|
use rustyline::{
|
|
|
|
completion::FilenameCompleter,
|
|
|
|
error::ReadlineError,
|
|
|
|
highlight::Highlighter,
|
|
|
|
hint::{Hint, Hinter, HistoryHinter},
|
|
|
|
history::DefaultHistory,
|
2023-08-28 21:24:15 +00:00
|
|
|
Completer, Context, Editor, Helper, Validator,
|
2023-08-22 15:40:50 +00:00
|
|
|
};
|
|
|
|
|
2023-08-28 21:24:15 +00:00
|
|
|
use std::{borrow::Cow, fs::read_to_string};
|
2023-08-22 15:40:50 +00:00
|
|
|
|
2023-10-27 01:23:39 +00:00
|
|
|
use dust_lang::{evaluate_with_context, Map, Value};
|
2023-08-22 15:40:50 +00:00
|
|
|
|
|
|
|
/// Command-line arguments to be parsed.
|
|
|
|
#[derive(Parser, Debug)]
|
|
|
|
#[command(author, version, about, long_about = None)]
|
|
|
|
struct Args {
|
2023-10-26 20:00:06 +00:00
|
|
|
/// Dust source code to evaluate.
|
2023-08-22 15:40:50 +00:00
|
|
|
#[arg(short, long)]
|
|
|
|
command: Option<String>,
|
|
|
|
|
2023-10-26 20:00:06 +00:00
|
|
|
/// Data to assign to the "input" variable.
|
|
|
|
#[arg(short, long)]
|
|
|
|
input: Option<String>,
|
|
|
|
|
|
|
|
/// A path to file whose contents will be assigned to the "input" variable.
|
|
|
|
#[arg(short = 'p', long)]
|
|
|
|
input_path: Option<String>,
|
|
|
|
|
2023-08-23 04:39:46 +00:00
|
|
|
/// Location of the file to run.
|
|
|
|
path: Option<String>,
|
2023-08-22 15:40:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
let args = Args::parse();
|
|
|
|
|
2023-08-28 18:28:59 +00:00
|
|
|
if args.path.is_none() && args.command.is_none() {
|
2023-08-22 15:40:50 +00:00
|
|
|
return run_cli_shell();
|
|
|
|
}
|
|
|
|
|
2023-10-26 20:00:06 +00:00
|
|
|
let mut context = Map::new();
|
|
|
|
|
2023-10-28 14:28:43 +00:00
|
|
|
if let Some(input) = args.input {
|
|
|
|
context
|
2023-10-29 23:31:06 +00:00
|
|
|
.variables_mut()
|
2023-10-30 19:42:06 +00:00
|
|
|
.insert("input".to_string(), Value::String(input));
|
2023-10-28 14:28:43 +00:00
|
|
|
}
|
|
|
|
|
2023-10-26 20:00:06 +00:00
|
|
|
if let Some(path) = args.input_path {
|
|
|
|
let file_contents = read_to_string(path).unwrap();
|
|
|
|
|
|
|
|
context
|
2023-10-29 23:31:06 +00:00
|
|
|
.variables_mut()
|
2023-10-30 19:42:06 +00:00
|
|
|
.insert("input".to_string(), Value::String(file_contents));
|
2023-10-26 20:00:06 +00:00
|
|
|
}
|
|
|
|
|
2023-10-09 19:54:47 +00:00
|
|
|
let eval_result = if let Some(path) = args.path {
|
2023-08-22 15:40:50 +00:00
|
|
|
let file_contents = read_to_string(path).unwrap();
|
|
|
|
|
2023-10-26 20:00:06 +00:00
|
|
|
evaluate_with_context(&file_contents, &mut context)
|
2023-08-22 15:40:50 +00:00
|
|
|
} else if let Some(command) = args.command {
|
2023-10-26 20:00:06 +00:00
|
|
|
evaluate_with_context(&command, &mut context)
|
2023-08-22 15:40:50 +00:00
|
|
|
} else {
|
2023-10-09 19:54:47 +00:00
|
|
|
Ok(Value::Empty)
|
2023-08-22 15:40:50 +00:00
|
|
|
};
|
|
|
|
|
2023-10-09 19:54:47 +00:00
|
|
|
match eval_result {
|
2023-10-24 00:45:47 +00:00
|
|
|
Ok(value) => {
|
|
|
|
if !value.is_empty() {
|
|
|
|
println!("{value}")
|
|
|
|
}
|
|
|
|
}
|
2023-10-09 19:54:47 +00:00
|
|
|
Err(error) => eprintln!("{error}"),
|
2023-08-22 15:40:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-28 20:14:55 +00:00
|
|
|
#[derive(Helper, Completer, Validator)]
|
|
|
|
struct DustReadline {
|
|
|
|
#[rustyline(Completer)]
|
|
|
|
completer: FilenameCompleter,
|
2023-08-22 15:40:50 +00:00
|
|
|
|
2023-08-28 20:14:55 +00:00
|
|
|
tool_hints: Vec<ToolHint>,
|
2023-08-22 15:40:50 +00:00
|
|
|
|
2023-08-28 20:14:55 +00:00
|
|
|
#[rustyline(Hinter)]
|
2023-08-28 21:24:15 +00:00
|
|
|
_hinter: HistoryHinter,
|
2023-08-28 20:14:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl DustReadline {
|
|
|
|
fn new() -> Self {
|
|
|
|
Self {
|
|
|
|
completer: FilenameCompleter::new(),
|
2023-08-28 21:24:15 +00:00
|
|
|
_hinter: HistoryHinter {},
|
2023-10-02 21:15:05 +00:00
|
|
|
tool_hints: Vec::new(),
|
2023-08-22 15:40:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-28 20:14:55 +00:00
|
|
|
struct ToolHint {
|
|
|
|
display: String,
|
|
|
|
complete_to: usize,
|
2023-08-22 15:40:50 +00:00
|
|
|
}
|
|
|
|
|
2023-08-28 20:14:55 +00:00
|
|
|
impl Hint for ToolHint {
|
|
|
|
fn display(&self) -> &str {
|
|
|
|
&self.display
|
|
|
|
}
|
|
|
|
|
|
|
|
fn completion(&self) -> Option<&str> {
|
|
|
|
if self.complete_to > 0 {
|
|
|
|
Some(&self.display[..self.complete_to])
|
|
|
|
} else {
|
|
|
|
None
|
2023-08-22 15:40:50 +00:00
|
|
|
}
|
|
|
|
}
|
2023-08-28 20:14:55 +00:00
|
|
|
}
|
2023-08-22 15:40:50 +00:00
|
|
|
|
2023-08-28 20:14:55 +00:00
|
|
|
impl ToolHint {
|
|
|
|
fn suffix(&self, strip_chars: usize) -> ToolHint {
|
|
|
|
ToolHint {
|
|
|
|
display: self.display[strip_chars..].to_string(),
|
|
|
|
complete_to: self.complete_to.saturating_sub(strip_chars),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-08-22 15:40:50 +00:00
|
|
|
|
2023-08-28 20:14:55 +00:00
|
|
|
impl Hinter for DustReadline {
|
|
|
|
type Hint = ToolHint;
|
2023-08-22 15:40:50 +00:00
|
|
|
|
2023-08-28 20:14:55 +00:00
|
|
|
fn hint(&self, line: &str, pos: usize, _ctx: &Context<'_>) -> Option<Self::Hint> {
|
|
|
|
if line.is_empty() || pos < line.len() {
|
|
|
|
return None;
|
|
|
|
}
|
2023-08-22 15:40:50 +00:00
|
|
|
|
2023-08-28 20:14:55 +00:00
|
|
|
self.tool_hints.iter().find_map(|tool_hint| {
|
|
|
|
if tool_hint.display.starts_with(line) {
|
|
|
|
Some(tool_hint.suffix(pos))
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
})
|
2023-08-22 15:40:50 +00:00
|
|
|
}
|
2023-08-28 20:14:55 +00:00
|
|
|
}
|
2023-08-22 15:40:50 +00:00
|
|
|
|
2023-08-28 20:14:55 +00:00
|
|
|
impl Highlighter for DustReadline {
|
|
|
|
fn highlight_hint<'h>(&self, hint: &'h str) -> std::borrow::Cow<'h, str> {
|
2023-08-28 21:15:05 +00:00
|
|
|
let highlighted = ansi_term::Colour::Red.paint(hint).to_string();
|
2023-08-22 15:40:50 +00:00
|
|
|
|
2023-08-28 21:15:05 +00:00
|
|
|
Cow::Owned(highlighted)
|
2023-08-22 15:40:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-28 20:14:55 +00:00
|
|
|
fn run_cli_shell() {
|
2023-10-25 20:44:50 +00:00
|
|
|
let mut context = Map::new();
|
2023-08-28 20:14:55 +00:00
|
|
|
let mut rl: Editor<DustReadline, DefaultHistory> = Editor::new().unwrap();
|
|
|
|
|
|
|
|
rl.set_helper(Some(DustReadline::new()));
|
2023-08-22 15:40:50 +00:00
|
|
|
|
2023-08-28 20:14:55 +00:00
|
|
|
if rl.load_history("target/history.txt").is_err() {
|
|
|
|
println!("No previous history.");
|
|
|
|
}
|
|
|
|
|
|
|
|
loop {
|
2023-08-28 21:24:15 +00:00
|
|
|
let readline = rl.readline("* ");
|
2023-08-28 20:14:55 +00:00
|
|
|
match readline {
|
|
|
|
Ok(line) => {
|
|
|
|
let line = line.as_str();
|
|
|
|
|
|
|
|
rl.add_history_entry(line).unwrap();
|
|
|
|
|
2023-10-09 19:54:47 +00:00
|
|
|
let eval_result = evaluate_with_context(line, &mut context);
|
2023-08-28 20:14:55 +00:00
|
|
|
|
2023-10-09 19:54:47 +00:00
|
|
|
match eval_result {
|
|
|
|
Ok(value) => println!("{value}"),
|
|
|
|
Err(error) => eprintln!("{error}"),
|
2023-08-28 20:14:55 +00:00
|
|
|
}
|
|
|
|
}
|
2023-10-09 19:54:47 +00:00
|
|
|
Err(ReadlineError::Interrupted) => break,
|
|
|
|
Err(ReadlineError::Eof) => break,
|
2023-08-28 21:24:15 +00:00
|
|
|
Err(error) => eprintln!("{error}"),
|
2023-08-28 20:14:55 +00:00
|
|
|
}
|
2023-08-22 15:40:50 +00:00
|
|
|
}
|
|
|
|
|
2023-08-28 20:14:55 +00:00
|
|
|
rl.save_history("target/history.txt").unwrap();
|
2023-08-22 15:40:50 +00:00
|
|
|
}
|