1
0
dust/src/main.rs

281 lines
7.7 KiB
Rust
Raw Normal View History

2023-10-26 20:00:06 +00:00
//! Command line interface for the dust programming language.
2024-01-17 17:48:51 +00:00
2024-01-06 10:00:36 +00:00
use clap::{Parser, Subcommand};
use crossterm::event::{KeyCode, KeyModifiers};
use nu_ansi_term::Style;
use reedline::{
2024-01-26 22:14:57 +00:00
default_emacs_keybindings, ColumnarMenu, DefaultCompleter, DefaultHinter, EditCommand, Emacs,
Highlighter, Prompt, Reedline, ReedlineEvent, ReedlineMenu, Signal, SqliteBackedHistory,
StyledText,
2023-08-22 15:40:50 +00:00
};
2024-01-26 22:14:57 +00:00
use std::{borrow::Cow, fs::read_to_string, path::PathBuf, time::SystemTime};
2023-08-22 15:40:50 +00:00
use dust_lang::{built_in_values, Interpreter, Map, Result, 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>,
2024-01-17 17:48:51 +00:00
/// File whose contents will be assigned to the "input" variable.
2023-10-26 20:00:06 +00:00
#[arg(short = 'p', long)]
input_path: Option<String>,
2024-01-17 17:48:51 +00:00
/// Command for alternate functionality besides running the source.
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-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
}
2024-01-24 23:57:36 +00:00
if args.path.is_none() && args.command.is_none() {
let run_shell_result = run_shell(context);
match run_shell_result {
Ok(_) => {}
Err(error) => eprintln!("{error}"),
}
return;
2024-01-24 23:57:36 +00:00
}
let source = if let Some(path) = &args.path {
read_to_string(path).unwrap()
} else if let Some(command) = args.command {
command
} else {
String::with_capacity(0)
};
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
}
2024-01-13 18:39:30 +00:00
if let Some(CliCommand::Format) = args.cli_command {
println!("{}", interpreter.format());
return;
}
2024-01-24 23:57:36 +00:00
let eval_result = interpreter.run(&source);
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
}
}
struct DustHighlighter {
context: Map,
}
2023-08-22 15:40:50 +00:00
impl DustHighlighter {
fn new(context: Map) -> Self {
Self { context }
}
2023-08-28 20:14:55 +00:00
}
impl Highlighter for DustHighlighter {
fn highlight(&self, line: &str, _cursor: usize) -> reedline::StyledText {
fn highlight_identifier(styled: &mut StyledText, word: &str, map: &Map) -> bool {
for (key, (value, _type)) in map.variables().unwrap().iter() {
2024-01-25 07:17:45 +00:00
if key == &word {
styled.push((Style::new().bold(), word.to_string()));
return true;
}
if let Value::Map(nested_map) = value {
return highlight_identifier(styled, key, nested_map);
}
}
for built_in_value in built_in_values() {
2024-01-25 07:17:45 +00:00
if built_in_value.name() == word {
styled.push((Style::new().bold(), word.to_string()));
2024-01-25 07:17:45 +00:00
return true;
}
}
false
2023-08-22 15:40:50 +00:00
}
let mut styled = StyledText::new();
2024-01-25 07:17:45 +00:00
let terminators = [' ', ':', '(', ')', '{', '}', '[', ']'];
for word in line.split_inclusive(&terminators) {
let word_is_highlighted =
highlight_identifier(&mut styled, &word[0..word.len() - 1], &self.context);
2023-08-22 15:40:50 +00:00
2024-01-25 07:17:45 +00:00
if word_is_highlighted {
let final_char = word.chars().last().unwrap();
2023-08-22 15:40:50 +00:00
2024-01-25 07:17:45 +00:00
if terminators.contains(&final_char) {
styled.push((Style::new(), final_char.to_string()));
}
} else {
styled.push((Style::new(), word.to_string()));
}
2023-08-28 20:14:55 +00:00
}
2023-08-22 15:40:50 +00:00
styled
2023-08-22 15:40:50 +00:00
}
2023-08-28 20:14:55 +00:00
}
2023-08-22 15:40:50 +00:00
2024-01-26 22:14:57 +00:00
struct DustPrompt;
impl Prompt for DustPrompt {
fn render_prompt_left(&self) -> Cow<str> {
let path = std::env::current_dir()
.map(|path| path.file_name().unwrap().to_string_lossy().to_string())
.unwrap_or_else(|_| "No workdir".to_string());
Cow::Owned(path)
}
fn render_prompt_right(&self) -> Cow<str> {
let time = humantime::format_rfc3339_seconds(SystemTime::now()).to_string();
Cow::Owned(time)
}
fn render_prompt_indicator(&self, _prompt_mode: reedline::PromptEditMode) -> Cow<str> {
Cow::Borrowed(" > ")
}
2024-01-26 22:14:57 +00:00
fn render_prompt_multiline_indicator(&self) -> Cow<str> {
Cow::Borrowed(" > ")
}
2024-01-26 22:14:57 +00:00
fn render_prompt_history_search_indicator(
&self,
_history_search: reedline::PromptHistorySearch,
) -> Cow<str> {
Cow::Borrowed(" ? ")
}
}
fn run_shell(context: Map) -> Result<()> {
let mut interpreter = Interpreter::new(context.clone());
let mut keybindings = default_emacs_keybindings();
2024-01-25 07:17:45 +00:00
keybindings.add_binding(
KeyModifiers::CONTROL,
KeyCode::Char(' '),
ReedlineEvent::Edit(vec![EditCommand::InsertNewline]),
);
keybindings.add_binding(
2024-01-26 22:14:57 +00:00
KeyModifiers::NONE,
KeyCode::Tab,
ReedlineEvent::UntilFound(vec![
ReedlineEvent::Menu("completion_menu".to_string()),
ReedlineEvent::MenuNext,
]),
);
keybindings.add_binding(
2024-01-26 22:14:57 +00:00
KeyModifiers::CONTROL,
KeyCode::Tab,
ReedlineEvent::Edit(vec![EditCommand::InsertString(" ".to_string())]),
);
let edit_mode = Box::new(Emacs::new(keybindings));
let history = Box::new(
SqliteBackedHistory::with_file(PathBuf::from("target/history"), None, None)
.expect("Error loading history."),
);
2024-01-26 22:14:57 +00:00
let hinter = Box::new(DefaultHinter::default());
let mut line_editor = Reedline::create()
.with_edit_mode(edit_mode)
.with_history(history)
2024-01-26 22:14:57 +00:00
.with_highlighter(Box::new(DustHighlighter::new(context)))
.with_hinter(hinter)
.with_menu(ReedlineMenu::WithCompleter {
menu: Box::new(ColumnarMenu::default().with_name("completion_menu")),
completer: Box::new(DefaultCompleter::new_with_wordlen(
vec!["test".to_string()],
2,
)),
});
let prompt = DustPrompt;
2023-08-28 20:14:55 +00:00
loop {
let sig = line_editor.read_line(&prompt);
match sig {
Ok(Signal::Success(buffer)) => {
2024-01-25 07:17:45 +00:00
if buffer.trim().is_empty() {
continue;
}
2023-08-28 20:14:55 +00:00
let run_result = interpreter.run(&buffer);
2023-08-28 20:14:55 +00:00
match run_result {
Ok(value) => {
if !value.is_none() {
println!("{value}")
}
2024-01-03 20:25:53 +00:00
}
Err(error) => println!("Error: {error}"),
2023-08-28 20:14:55 +00:00
}
}
Ok(Signal::CtrlD) | Ok(Signal::CtrlC) => {
println!("\nAborted!");
break;
}
x => {
println!("Event: {:?}", x);
}
2023-08-28 20:14:55 +00:00
}
2023-08-22 15:40:50 +00:00
}
Ok(())
2023-08-22 15:40:50 +00:00
}