From 2c1c1b7d12141fe4fd5d5b059b1fc78581cb2c4e Mon Sep 17 00:00:00 2001 From: Mark Thom Date: Thu, 26 Sep 2019 11:25:05 -0600 Subject: [PATCH] migrate to rustyline, add history support --- Cargo.toml | 7 +- src/main.rs | 4 +- src/prolog/clause_types.rs | 3 + src/prolog/machine/mod.rs | 9 +-- src/prolog/machine/system_calls.rs | 125 ++++++++++++++++------------- src/prolog/mod.rs | 1 + src/prolog/read.rs | 125 ++++++++++------------------- src/prolog/toplevel.pl | 3 +- 8 files changed, 122 insertions(+), 155 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ec8c2b85..93ab3852 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,14 +1,11 @@ [package] name = "scryer-prolog" -version = "0.8.93" +version = "0.8.94" authors = ["Mark Thom "] repository = "https://github.com/mthom/scryer-prolog" description = "A modern Prolog implementation written mostly in Rust." license = "BSD-3-Clause" -[features] -default = ["readline_rs_compat"] - [dependencies] cfg-if = "0.1.7" dirs = "2.0.2" @@ -16,9 +13,9 @@ downcast = "0.10.0" indexmap = "1.0.2" ordered-float = "0.5.0" prolog_parser = "0.8.29" -readline_rs_compat = { version = "0.1.9", optional = true } ref_thread_local = "0.0.0" rug = "1.4.0" +rustyline = "5.0.3" [dependencies.termion] version = "1.4.0" diff --git a/src/main.rs b/src/main.rs index 948c0abc..61ed623b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,8 +25,8 @@ use prolog::read::*; mod tests; fn main() { - #[cfg(feature = "readline_rs_compat")] - readline::readline_initialize(); +// #[cfg(feature = "readline_rs_compat")] +// readline::readline_initialize(); let mut wam = Machine::new(readline::input_stream()); wam.run_toplevel(); diff --git a/src/prolog/clause_types.rs b/src/prolog/clause_types.rs index 07d3d589..3d0d1aef 100644 --- a/src/prolog/clause_types.rs +++ b/src/prolog/clause_types.rs @@ -201,6 +201,7 @@ pub enum SystemClauseType { NumberToCodes, OpDeclaration, REPL(REPLCodePtr), + ReadQueryTerm, ReadTerm, RedoAttrVarBindings, RemoveCallPolicyCheck, @@ -322,6 +323,7 @@ impl SystemClauseType { &SystemClauseType::GetCurrentBlock => clause_name!("$get_current_block"), &SystemClauseType::InstallNewBlock => clause_name!("$install_new_block"), &SystemClauseType::ModuleRetractClause => clause_name!("$module_retract_clause"), + &SystemClauseType::ReadQueryTerm => clause_name!("$read_query_term"), &SystemClauseType::ReadTerm => clause_name!("$read_term"), &SystemClauseType::ResetGlobalVarAtKey => clause_name!("$reset_global_var_at_key"), &SystemClauseType::RetractClause => clause_name!("$retract_clause"), @@ -416,6 +418,7 @@ impl SystemClauseType { ("$get_current_block", 1) => Some(SystemClauseType::GetCurrentBlock), ("$get_cp", 1) => Some(SystemClauseType::GetCutPoint), ("$install_new_block", 1) => Some(SystemClauseType::InstallNewBlock), + ("$read_query_term", 2) => Some(SystemClauseType::ReadQueryTerm), ("$read_term", 2) => Some(SystemClauseType::ReadTerm), ("$reset_block", 1) => Some(SystemClauseType::ResetBlock), ("$reset_global_var_at_key", 1) => Some(SystemClauseType::ResetGlobalVarAtKey), diff --git a/src/prolog/machine/mod.rs b/src/prolog/machine/mod.rs index b9e90cc7..73159f38 100644 --- a/src/prolog/machine/mod.rs +++ b/src/prolog/machine/mod.rs @@ -354,14 +354,9 @@ impl Machine { fn handle_toplevel_command(&mut self, code_ptr: REPLCodePtr, p: LocalCodePtr) { match code_ptr { REPLCodePtr::CompileBatch => { - #[cfg(feature = "readline_rs_compat")] - readline::set_line_mode(readline::LineMode::Multi); - let src = readline::input_stream(); - - #[cfg(feature = "readline_rs_compat")] - readline::set_line_mode(readline::LineMode::Single); - + readline::set_prompt(false); + match compile_user_module(self, src) { EvalSession::Error(e) => self.throw_session_error(e, (clause_name!("repl"), 0)), _ => {} diff --git a/src/prolog/machine/system_calls.rs b/src/prolog/machine/system_calls.rs index 81a634df..0ed454f6 100644 --- a/src/prolog/machine/system_calls.rs +++ b/src/prolog/machine/system_calls.rs @@ -251,6 +251,63 @@ impl MachineState { Ok(()) } + fn read_term(&mut self, + current_input_stream: &mut PrologStream, + indices: &mut IndexStore) + -> CallResult + { + match self.read( + current_input_stream, + indices.atom_tbl.clone(), + &indices.op_dir, + ) { + Ok(term_write_result) => { + let a1 = self[temp_v!(1)].clone(); + self.unify(Addr::HeapCell(term_write_result.heap_loc), a1); + + if self.fail { + return Ok(()); + } + + let mut list_of_var_eqs = vec![]; + + for (var, binding) in term_write_result.var_dict.into_iter().rev() { + let var_atom = clause_name!(var.to_string(), indices.atom_tbl); + let var_atom = Constant::Atom(var_atom, None); + + let h = self.heap.h; + let spec = fetch_atom_op_spec(clause_name!("="), None, &indices.op_dir); + + self.heap.push(HeapCellValue::NamedStr(2, clause_name!("="), spec)); + self.heap.push(HeapCellValue::Addr(Addr::Con(var_atom))); + self.heap.push(HeapCellValue::Addr(binding)); + + list_of_var_eqs.push(Addr::Str(h)); + } + + let a2 = self[temp_v!(2)].clone(); + let list_offset = + Addr::HeapCell(self.heap.to_list(list_of_var_eqs.into_iter())); + + Ok(self.unify(list_offset, a2)) + } + Err(err) => { + if let ParserError::UnexpectedEOF = err { + std::process::exit(0); + } + + // reset the input stream after an input failure. + *current_input_stream = readline::input_stream(); + + let h = self.heap.h; + let syntax_error = MachineError::syntax_error(h, err); + let stub = MachineError::functor_stub(clause_name!("read_term"), 2); + + Err(self.error_form(syntax_error, stub)) + } + } + } + #[inline] fn install_new_block(&mut self, r: RegType) -> usize { self.block = self.b; @@ -1674,69 +1731,27 @@ impl MachineState { &SystemClauseType::InstallNewBlock => { self.install_new_block(temp_v!(1)); } - &SystemClauseType::ReadTerm => { - match self.read( - current_input_stream, - indices.atom_tbl.clone(), - &indices.op_dir, - ) { - Ok(term_write_result) => { - let a1 = self[temp_v!(1)].clone(); - self.unify(Addr::HeapCell(term_write_result.heap_loc), a1); - - if self.fail { - return Ok(()); - } - - let mut list_of_var_eqs = vec![]; - - for (var, binding) in term_write_result.var_dict.into_iter().rev() { - let var_atom = clause_name!(var.to_string(), indices.atom_tbl); - let var_atom = Constant::Atom(var_atom, None); - - let h = self.heap.h; - let spec = fetch_atom_op_spec(clause_name!("="), None, &indices.op_dir); - - self.heap - .push(HeapCellValue::NamedStr(2, clause_name!("="), spec)); - self.heap.push(HeapCellValue::Addr(Addr::Con(var_atom))); - self.heap.push(HeapCellValue::Addr(binding)); - - list_of_var_eqs.push(Addr::Str(h)); - } - - let a2 = self[temp_v!(2)].clone(); - let list_offset = - Addr::HeapCell(self.heap.to_list(list_of_var_eqs.into_iter())); + &SystemClauseType::ReadQueryTerm => { + readline::set_prompt(true); + let result = self.read_term(current_input_stream, indices); + readline::set_prompt(false); - self.unify(list_offset, a2); - } - Err(err) => { - if let ParserError::UnexpectedEOF = err { - std::process::exit(0); - } - - // reset the input stream after an input failure. - *current_input_stream = readline::input_stream(); - - let h = self.heap.h; - let syntax_error = MachineError::syntax_error(h, err); - let stub = MachineError::functor_stub(clause_name!("read_term"), 2); - - return Err(self.error_form(syntax_error, stub)); - } - } + let _ = result?; } + &SystemClauseType::ReadTerm => { + readline::set_prompt(false); + self.read_term(current_input_stream, indices)?; + }, &SystemClauseType::ResetBlock => { let addr = self.deref(self[temp_v!(1)].clone()); self.reset_block(addr); } - &SystemClauseType::SetBall => self.set_ball(), - &SystemClauseType::SkipMaxList => { + &SystemClauseType::SetBall => + self.set_ball(), + &SystemClauseType::SkipMaxList => if let Err(err) = self.skip_max_list() { return Err(err); - } - } + }, &SystemClauseType::StoreGlobalVar => { let key = self[temp_v!(1)].clone(); diff --git a/src/prolog/mod.rs b/src/prolog/mod.rs index c98c3f27..3ec028e4 100644 --- a/src/prolog/mod.rs +++ b/src/prolog/mod.rs @@ -2,6 +2,7 @@ extern crate dirs; extern crate ordered_float; extern crate prolog_parser; extern crate rug; +extern crate rustyline; #[macro_use] mod macros; diff --git a/src/prolog/read.rs b/src/prolog/read.rs index ddebca49..a56e957a 100644 --- a/src/prolog/read.rs +++ b/src/prolog/read.rs @@ -25,35 +25,60 @@ impl<'a> TermRef<'a> { pub type PrologStream = ParsingStream>; -#[cfg(feature = "readline_rs_compat")] pub mod readline { use prolog_parser::ast::*; - use readline_rs_compat::readline::*; - use std::io::{Error, Read}; + use prolog::rustyline::error::ReadlineError; + use prolog::rustyline::{Cmd, Editor, KeyPress}; + use std::io::Read; - #[derive(Clone, Copy)] - pub enum LineMode { - Single, - Multi, + static mut PROMPT: bool = false; + + pub fn set_prompt(value: bool) { + unsafe { + PROMPT = value; + } } + #[inline] + fn get_prompt() -> &'static str { + unsafe { + if PROMPT { "?- " } else { "" } + } + } + pub struct ReadlineStream { + rl: Editor<()>, pending_input: String, } impl ReadlineStream { - #[inline] - fn new(pending_input: String) -> Self { - ReadlineStream { pending_input } + fn input_stream(pending_input: String) -> Self { + let mut rl = Editor::<()>::new(); + + rl.bind_sequence(KeyPress::Tab, Cmd::Insert(1, "\t".to_string())); + + ReadlineStream { rl, pending_input } } - fn call_readline(&mut self, prompt: &str, buf: &mut [u8]) -> std::io::Result { - match readline_rl(prompt) { - Some(text) => { + fn call_readline(&mut self, buf: &mut [u8]) -> std::io::Result { + match self.rl.readline(get_prompt()) { + Ok(text) => { self.pending_input += &text; + + unsafe { + if PROMPT { + self.rl.history_mut().add(&self.pending_input); + PROMPT = false; + } + } + + self.pending_input += "\n"; Ok(self.write_to_buf(buf)) } - None => Err(Error::last_os_error()), + Err(ReadlineError::Eof) => + Ok(self.write_to_buf(buf)), + Err(e) => + Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, e)) } } @@ -84,84 +109,16 @@ pub mod readline { impl Read for ReadlineStream { fn read(&mut self, buf: &mut [u8]) -> std::io::Result { if self.pending_input.is_empty() { - self.call_readline("", buf) + self.call_readline(buf) } else { Ok(self.write_to_buf(buf)) } } } - static mut LINE_MODE: LineMode = LineMode::Single; - static mut END_OF_LINE: bool = false; - - pub fn set_line_mode(mode: LineMode) { - unsafe { - LINE_MODE = mode; - END_OF_LINE = false; - rl_done = 0; - } - } - - unsafe extern "C" fn bind_end_chord(_: i32, _: i32) -> i32 { - if let LineMode::Multi = LINE_MODE { - rl_done = 1; - } - - 0 - } - - unsafe extern "C" fn bind_cr(_: i32, _: i32) -> i32 { - if let LineMode::Single = LINE_MODE { - insert_text_rl("\n"); - println!(""); - rl_done = 1; - } else { - insert_text_rl("\n"); - } - - 0 - } - - pub fn readline_initialize() { - let rc = initialize_rl(); // initialize editline. - - if rc != 0 { - panic!("initialize_rl() failed with return code {}", rc); - } - - bind_key_rl('\t' as i32, rl_insert); // just insert tabs when typed. - bind_key_rl('\n' as i32, bind_cr); - bind_key_rl('\r' as i32, bind_cr); - bind_keyseq_rl("\\C-d", bind_end_chord); - } - - #[inline] - pub fn input_stream() -> ::PrologStream { - let reader: Box = Box::new(ReadlineStream::new(String::from(""))); - parsing_stream(reader) - } -} - -#[cfg(not(feature = "readline_rs_compat"))] -pub mod readline { - use prolog_parser::ast::*; - use std::io::{stdin, BufReader, Read, Stdin}; - - struct StdinWrapper { - buf: BufReader, - } - - impl Read for StdinWrapper { - fn read(&mut self, buf: &mut [u8]) -> std::io::Result { - self.buf.read(buf) - } - } - #[inline] pub fn input_stream() -> ::PrologStream { - let reader: Box = Box::new(StdinWrapper { - buf: BufReader::new(stdin()), - }); + let reader: Box = Box::new(ReadlineStream::input_stream(String::from(""))); parsing_stream(reader) } } diff --git a/src/prolog/toplevel.pl b/src/prolog/toplevel.pl index 532291f9..b4937520 100644 --- a/src/prolog/toplevel.pl +++ b/src/prolog/toplevel.pl @@ -4,8 +4,7 @@ '$repl' :- '$repl'. '$read_and_match' :- - write_term('?- ', [quoted(false)]), - read_term(Term, [variable_names(VarList)]), + '$read_query_term'(Term, VarList), '$instruction_match'(Term, VarList). '$instruction_match'([user], []) :- -- 2.54.0