diff --git a/src/machine/lib_machine/mod.rs b/src/machine/lib_machine/mod.rs index 87b64074..5e8d887f 100644 --- a/src/machine/lib_machine/mod.rs +++ b/src/machine/lib_machine/mod.rs @@ -568,7 +568,7 @@ impl Machine { self.run_module_predicate(atom!("loader"), (atom!("consult_stream"), 2)); } - fn allocate_stub_choice_point(&mut self) { + pub(crate) fn allocate_stub_choice_point(&mut self) { // NOTE: create a choice point to terminate the dispatch_loop // if an exception is thrown. diff --git a/src/machine/mod.rs b/src/machine/mod.rs index 57a6213b..09d9f642 100644 --- a/src/machine/mod.rs +++ b/src/machine/mod.rs @@ -271,6 +271,11 @@ impl Machine { .unwrap() } + /// Runs the predicate `key` in `module_name` until completion. + /// Siltently ignores failure, thrown errors and choice points. + /// + /// Consider using [`Machine::run_query`] if you wish to handle + /// predicates that may fail, leave a choice point or throw. pub(crate) fn run_module_predicate( &mut self, module_name: Atom, @@ -279,6 +284,8 @@ impl Machine { if let Some(module) = self.indices.modules.get(&module_name) { if let Some(code_index) = module.code_dir.get(&key) { let p = code_index.local().unwrap(); + // Leave a halting choice point to backtrack to in case the predicate fails or throws. + self.allocate_stub_choice_point(); self.machine_st.cp = BREAK_FROM_DISPATCH_LOOP_LOC; self.machine_st.p = p; @@ -1248,3 +1255,24 @@ impl Machine { } } } + +#[cfg(test)] +mod tests { + use super::config::*; + use super::*; + + #[test] + #[cfg_attr(miri, ignore)] + fn test_run_module_predicate_throw() { + let mut machine = MachineBuilder::default() + .with_toplevel( + r#" + :- module('$toplevel', []). + repl :- throw(kaboom). + "#, + ) + .build(); + + machine.run_module_predicate(atom!("$toplevel"), (atom!("repl"), 0)); + } +} diff --git a/src/toplevel.pl b/src/toplevel.pl index b43ac3cb..0727aee0 100644 --- a/src/toplevel.pl +++ b/src/toplevel.pl @@ -29,6 +29,19 @@ load_scryerrc :- ). '$repl' :- + catch( + start_repl, + _, + % Something bad enough happened that the REPL itself threw an error. + % This can be caused by a broken user_output stream, so we cannot + % print an error. + % + % The best we can do now is halt with an error code, + % so that users can try to diagnose the issue: + halt(99) + ). + +start_repl :- asserta('$toplevel':started), raw_argv(Args0), ( append(Args1, ["--"|_], Args0) ->