diff --git a/build/static_string_indexing.rs b/build/static_string_indexing.rs index 390c5405..6f57b37f 100644 --- a/build/static_string_indexing.rs +++ b/build/static_string_indexing.rs @@ -170,7 +170,7 @@ pub fn index_static_strings(instruction_rs_path: &std::path::Path) -> TokenStrea #((#static_strs) => { Atom { index: #indices_iter } };)* } - static STATIC_ATOMS_MAP: phf::Map<&'static str, Atom> = phf::phf_map! { + pub static STATIC_ATOMS_MAP: phf::Map<&'static str, Atom> = phf::phf_map! { #(#static_strs => { Atom { index: #indices } },)* }; } diff --git a/src/lib.rs b/src/lib.rs index 99c5d9f9..fc8d8ca5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,6 +28,7 @@ mod iterators; pub mod machine; mod raw_block; pub mod read; +mod repl_helper; mod targets; pub mod types; diff --git a/src/machine/machine_state.rs b/src/machine/machine_state.rs index a6b375b6..d19242f2 100644 --- a/src/machine/machine_state.rs +++ b/src/machine/machine_state.rs @@ -454,6 +454,23 @@ impl MachineState { self.b0 = self.b; } + // Safety: the atom_tbl lives for the lifetime of the machine, as does the helper, so the ptr + // will always be valid. + pub fn read_term_from_user_input(&mut self, stream: Stream, indices: &mut IndexStore) -> CallResult { + let atoms_ptr = (&self.atom_tbl.table) as *const indexmap::IndexSet; + + if let Stream::Readline(ptr) = stream { + unsafe { + let readline = ptr.as_ptr().as_mut().unwrap(); + readline.set_atoms_for_completion(atoms_ptr); + let ret = self.read_term(stream, indices); + return ret + } + } + + unreachable!("Stream must be a Stream::Readline(_)") + } + pub fn read_term(&mut self, stream: Stream, indices: &mut IndexStore) -> CallResult { fn push_var_eq_functors<'a>( heap: &mut Heap, diff --git a/src/machine/system_calls.rs b/src/machine/system_calls.rs index 6272bcee..2e016abd 100644 --- a/src/machine/system_calls.rs +++ b/src/machine/system_calls.rs @@ -4239,7 +4239,8 @@ impl Machine { self.user_input.reset(); set_prompt(true); - let result = self.machine_st.read_term(self.user_input, &mut self.indices); + // let result = self.machine_st.read_term(self.user_input, &mut self.indices); + let result = self.machine_st.read_term_from_user_input(self.user_input, &mut self.indices); set_prompt(false); match result { diff --git a/src/read.rs b/src/read.rs index c58e99f4..5ef89d04 100644 --- a/src/read.rs +++ b/src/read.rs @@ -9,12 +9,14 @@ use crate::machine::machine_indices::*; use crate::machine::machine_state::MachineState; use crate::machine::streams::*; use crate::parser::char_reader::*; +use crate::repl_helper::Helper; use crate::types::*; use fxhash::FxBuildHasher; +use indexmap::IndexSet; use rustyline::error::ReadlineError; -use rustyline::{Cmd, Config, Editor, KeyEvent}; +use rustyline::{Config, Editor}; use std::collections::VecDeque; use std::io::{Cursor, Error, ErrorKind, Read}; @@ -77,7 +79,7 @@ fn get_prompt() -> &'static str { #[derive(Debug)] pub struct ReadlineStream { - rl: Editor<()>, + rl: Editor, pending_input: Cursor, add_history: bool, } @@ -86,7 +88,10 @@ impl ReadlineStream { #[inline] pub fn new(pending_input: &str, add_history: bool) -> Self { let config = Config::builder().check_cursor_position(true).build(); - let mut rl = Editor::<()>::with_config(config); + let helper = Helper::new(); + + let mut rl = Editor::with_config(config); + rl.set_helper(Some(helper)); if let Some(mut path) = dirs_next::home_dir() { path.push(HISTORY_FILE); @@ -95,7 +100,7 @@ impl ReadlineStream { } } - rl.bind_sequence(KeyEvent::from('\t'), Cmd::Insert(1, "\t".to_string())); + // rl.bind_sequence(KeyEvent::from('\t'), Cmd::Insert(1, "\t".to_string())); ReadlineStream { rl, @@ -104,6 +109,11 @@ impl ReadlineStream { } } + pub fn set_atoms_for_completion(&mut self, atoms: *const IndexSet) { + let helper = self.rl.helper_mut().unwrap(); + helper.atoms = atoms; + } + #[inline] pub fn reset(&mut self) { self.pending_input.get_mut().clear(); diff --git a/src/repl_helper.rs b/src/repl_helper.rs new file mode 100644 index 00000000..21dc9800 --- /dev/null +++ b/src/repl_helper.rs @@ -0,0 +1,110 @@ +use indexmap::IndexSet; +use rustyline::completion::{Completer, Candidate}; +use rustyline::hint::Hinter; +use rustyline::validate::Validator; +use rustyline::highlight::{MatchingBracketHighlighter, Highlighter}; +use rustyline::{Helper as RlHelper, Result, Context}; + +use crate::atom_table::{Atom, STATIC_ATOMS_MAP}; + +// TODO: Maybe add validation to the helper +pub struct Helper { + highligher: MatchingBracketHighlighter, + pub atoms: *const IndexSet, +} + +impl Helper { + pub fn new() -> Self { + Self { + highligher: MatchingBracketHighlighter::new(), + atoms: std::ptr::null(), + } + } +} + +impl RlHelper for Helper {} + +fn get_prefix(line: &str, pos: usize) -> Option { + let mut start_of_atom = None; + let mut first_letter = true; + + for (i, char) in line.chars().enumerate() { + if first_letter { + if char.is_alphabetic() && char.is_lowercase() { + start_of_atom = Some(i); + } + + first_letter = false; + } + + if !char.is_alphanumeric() && char != '_' { + first_letter = true; + start_of_atom = None; + } + + if i == pos { + break + } + } + + if first_letter || pos == 0 { + start_of_atom = Some(pos) + } + + start_of_atom +} + +pub struct StrPtr(*const str); + +impl Candidate for StrPtr { + fn display(&self) -> &str { + unsafe { + self.0.as_ref().unwrap() + } + } + + fn replacement(&self) -> &str { + unsafe { + self.0.as_ref().unwrap() + } + } +} + +impl Completer for Helper { + type Candidate = StrPtr; + + fn complete(&self, line: &str, pos: usize, _ctx: &Context<'_>) -> Result<(usize, Vec)> { + let start_of_prefix = get_prefix(line, pos); + if let Some(idx) = start_of_prefix { + let sub_str = line.get(idx..pos).unwrap(); + Ok((idx, unsafe { + let mut matching = (*self.atoms).iter() + .chain(STATIC_ATOMS_MAP.values()) + .filter(|a| a.as_str().starts_with(sub_str)) + .map(|s| StrPtr(s.as_str())) + .collect::>(); + + matching.sort_unstable_by(|a, b| Ord::cmp(&(*a.0).len(), &(*b.0).len())); + matching + })) + } else { + Ok((0, vec![])) + } + } +} + +impl Highlighter for Helper { + fn highlight<'l>(&self, line: &'l str, pos: usize) -> std::borrow::Cow<'l, str> { + self.highligher.highlight(line, pos) + } + + fn highlight_char(&self, line: &str, pos: usize) -> bool { + self.highligher.highlight_char(line, pos) + } +} + +impl Validator for Helper {} + +impl Hinter for Helper { + type Hint = String; +}