dust/src/main.rs

220 lines
5.1 KiB
Rust
Raw Normal View History

2023-10-26 20:00:06 +00:00
//! Command line interface for the dust programming language.
2024-01-06 10:00:36 +00:00
use clap::{Parser, Subcommand};
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
};
use std::{borrow::Cow, fs::read_to_string};
2023-08-22 15:40:50 +00:00
2024-01-06 10:00:36 +00:00
use dust_lang::{Interpreter, 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>,
2024-01-06 10:00:36 +00:00
#[command(subcommand)]
cli_command: Option<CliCommand>,
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
}
2024-01-06 10:00:36 +00:00
#[derive(Subcommand, Debug)]
pub enum CliCommand {
2024-01-13 18:39:30 +00:00
/// Output a formatted version of the input.
2024-01-06 10:00:36 +00:00
Format,
2024-01-13 18:39:30 +00:00
/// Output a concrete syntax tree of the input.
Syntax,
2024-01-06 10:00:36 +00:00
}
fn main() {
2024-01-13 18:30:50 +00:00
env_logger::init();
2023-08-22 15:40:50 +00:00
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 source = if let Some(path) = &args.path {
read_to_string(path).unwrap()
} else if let Some(command) = &args.command {
command.clone()
} else {
"".to_string()
};
2023-12-29 19:52:51 +00:00
let context = Map::new();
2023-10-26 20:00:06 +00:00
2023-10-28 14:28:43 +00:00
if let Some(input) = args.input {
context
2024-01-10 01:38:40 +00:00
.set("input".to_string(), Value::string(input))
.unwrap();
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();
2023-10-26 20:00:06 +00:00
context
2024-01-10 01:38:40 +00:00
.set("input".to_string(), Value::string(file_contents))
.unwrap();
2023-10-26 20:00:06 +00:00
}
2023-12-30 17:02:58 +00:00
let mut interpreter = Interpreter::new(context);
2023-11-15 00:37:19 +00:00
2024-01-13 18:39:30 +00:00
if let Some(CliCommand::Syntax) = args.cli_command {
interpreter.parse(&source).unwrap();
2023-12-29 19:35:52 +00:00
println!("{}", interpreter.syntax_tree().unwrap());
2024-01-13 18:39:30 +00:00
return;
2023-11-15 00:37:19 +00:00
}
2023-12-29 21:27:13 +00:00
let eval_result = interpreter.run(&source);
2023-08-22 15:40:50 +00:00
2024-01-13 18:39:30 +00:00
if let Some(CliCommand::Format) = args.cli_command {
println!("{}", interpreter.format());
return;
}
2023-10-09 19:54:47 +00:00
match eval_result {
2023-10-24 00:45:47 +00:00
Ok(value) => {
if !value.is_none() {
2023-10-24 00:45:47 +00:00
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
2024-01-13 18:30:50 +00:00
fn hint(&self, line: &str, pos: usize, _ctx: &Context) -> Option<Self::Hint> {
2023-08-28 20:14:55 +00:00
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 {
2024-01-13 18:30:50 +00:00
fn highlight_hint<'h>(&self, hint: &'h str) -> 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-12-29 19:52:51 +00:00
let context = Map::new();
2024-01-03 20:25:53 +00:00
let mut interpreter = Interpreter::new(context);
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) => {
2024-01-13 18:30:50 +00:00
let input = line.to_string();
2023-08-28 20:14:55 +00:00
rl.add_history_entry(line).unwrap();
2024-01-03 20:25:53 +00:00
let eval_result = interpreter.run(&input);
2023-08-28 20:14:55 +00:00
2023-10-09 19:54:47 +00:00
match eval_result {
Ok(value) => println!("{value}"),
2024-01-03 20:25:53 +00:00
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
}