1
0
dust/src/bin/dust.rs

211 lines
5.2 KiB
Rust
Raw Normal View History

2023-08-22 15:40:50 +00:00
//! Command line interface for the whale programming language.
use clap::Parser;
2023-08-28 20:14:55 +00:00
use eframe::epaint::ahash::HashSet;
use rustyline::{
completion::FilenameCompleter,
error::ReadlineError,
highlight::Highlighter,
hint::{Hint, Hinter, HistoryHinter},
history::DefaultHistory,
validate::MatchingBracketValidator,
Completer, Context, DefaultEditor, Editor, Helper, Hinter, Validator,
2023-08-22 15:40:50 +00:00
};
use std::{
2023-08-28 20:14:55 +00:00
borrow::Cow,
collections::BTreeSet,
2023-08-22 15:40:50 +00:00
fs::{self, read_to_string},
path::PathBuf,
};
2023-08-28 18:28:59 +00:00
use dust_lib::{eval, eval_with_context, Tool, ToolInfo, Value, VariableMap, TOOL_LIST};
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 {
/// Whale source code to evaluate.
#[arg(short, long)]
command: 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();
}
let eval_result = if let Some(path) = args.path {
let file_contents = read_to_string(path).unwrap();
eval(&file_contents)
} else if let Some(command) = args.command {
eval(&command)
} else {
Ok(Value::Empty)
};
match eval_result {
Ok(value) => println!("{value}"),
Err(error) => eprintln!("{error}"),
}
}
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(Validator)]
validator: MatchingBracketValidator,
2023-08-22 15:40:50 +00:00
2023-08-28 20:14:55 +00:00
#[rustyline(Hinter)]
hinter: HistoryHinter,
}
impl DustReadline {
fn new() -> Self {
Self {
completer: FilenameCompleter::new(),
validator: MatchingBracketValidator::new(),
hinter: HistoryHinter {},
tool_hints: TOOL_LIST
.iter()
.map(|tool| ToolHint {
display: tool.info().identifier.to_string(),
complete_to: tool.info().identifier.len(),
})
.collect(),
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<'l>(&self, line: &'l str, pos: usize) -> std::borrow::Cow<'l, str> {
let _ = pos;
Cow::Borrowed(line)
}
2023-08-22 15:40:50 +00:00
2023-08-28 20:14:55 +00:00
fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
&'s self,
prompt: &'p str,
default: bool,
) -> std::borrow::Cow<'b, str> {
let _ = default;
Cow::Borrowed(prompt)
}
2023-08-22 15:40:50 +00:00
2023-08-28 20:14:55 +00:00
fn highlight_hint<'h>(&self, hint: &'h str) -> std::borrow::Cow<'h, str> {
Cow::Borrowed(hint)
}
2023-08-22 15:40:50 +00:00
2023-08-28 20:14:55 +00:00
fn highlight_candidate<'c>(
&self,
candidate: &'c str, // FIXME should be Completer::Candidate
completion: rustyline::CompletionType,
) -> std::borrow::Cow<'c, str> {
let _ = completion;
Cow::Borrowed(candidate)
}
2023-08-22 15:40:50 +00:00
2023-08-28 20:14:55 +00:00
fn highlight_char(&self, line: &str, pos: usize) -> bool {
let _ = (line, pos);
false
2023-08-22 15:40:50 +00:00
}
}
2023-08-28 20:14:55 +00:00
fn run_cli_shell() {
let mut context = VariableMap::new();
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 {
let readline = rl.readline(">> ");
match readline {
Ok(line) => {
let line = line.as_str();
rl.add_history_entry(line).unwrap();
let eval_result = eval_with_context(line, &mut context);
match eval_result {
Ok(value) => println!("{value}"),
Err(error) => eprintln!("{error}"),
}
}
Err(ReadlineError::Interrupted) => {
println!("CTRL-C");
break;
}
Err(ReadlineError::Eof) => {
println!("CTRL-D");
break;
}
Err(err) => {
println!("Error: {:?}", err);
break;
}
}
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
}