`get_n_chars/3` causes a panic when run via `:- initialization`
I am trying to build a small language interpreter using scryer prolog, and as part of that, I use :- initialization(run). to automatically run my repl loop (run/0). However, if I hit the tab key while inside get_n_chars/3, scryer crashes.
A cut-down test case is this: (save it in test.pl, and run it using scryer-prolog test.pl, then hit the tab key)
:- use_module(library(charsio)).
:- initialization(run).
run :- get_n_chars(user_input, 1, _).
If I comment out the :- initialization(run) line, and evaluate run/0 manually, the tab key no longer causes a crash.
The version of scryer prolog I use is build from source today (revision 7ffd93c0), and the operating system is Linux, x86_64, in case that makes any difference.
Here is a backtrace.
peter@fedora:~/src/lpa$ RUST_BACKTRACE=full scryer-prolog test.pl
thread 'main' panicked at src/repl_helper.rs:71:51:
called `Option::unwrap()` on a `None` value
stack backtrace:
0: 0x55de5f8debd2 - std::backtrace_rs::backtrace::libunwind::trace::h77485d2069ba1ee3
at /builddir/build/BUILD/rust-1.89.0-build/rustc-1.89.0-src/library/std/src/../../backtrace/src/backtrace/libunwind.rs:117:9
1: 0x55de5f8debd2 - std::backtrace_rs::backtrace::trace_unsynchronized::h3a27612f82c06f3a
at /builddir/build/BUILD/rust-1.89.0-build/rustc-1.89.0-src/library/std/src/../../backtrace/src/backtrace/mod.rs:66:14
2: 0x55de5f8debd2 - std::sys::backtrace::_print_fmt::hd7ddbd4e59565ff8
at /builddir/build/BUILD/rust-1.89.0-build/rustc-1.89.0-src/library/std/src/sys/backtrace.rs:66:9
3: 0x55de5f8debd2 - <std::sys::backtrace::BacktraceLock::print::DisplayBacktrace as core::fmt::Display>::fmt::h5cfe18ea6f2fdfd7
at /builddir/build/BUILD/rust-1.89.0-build/rustc-1.89.0-src/library/std/src/sys/backtrace.rs:39:26
4: 0x55de5f4501b3 - core::fmt::rt::Argument::fmt::h1714e199d350f3ea
at /builddir/build/BUILD/rust-1.89.0-build/rustc-1.89.0-src/library/core/src/fmt/rt.rs:173:76
5: 0x55de5f4501b3 - core::fmt::write::h7d767db0d1b2d697
at /builddir/build/BUILD/rust-1.89.0-build/rustc-1.89.0-src/library/core/src/fmt/mod.rs:1465:25
6: 0x55de5f8de75f - std::io::default_write_fmt::h64f07b16a0687c70
at /builddir/build/BUILD/rust-1.89.0-build/rustc-1.89.0-src/library/std/src/io/mod.rs:639:11
7: 0x55de5f8de75f - std::io::Write::write_fmt::ha40dd83d8c3eb1a3
at /builddir/build/BUILD/rust-1.89.0-build/rustc-1.89.0-src/library/std/src/io/mod.rs:1954:13
8: 0x55de5f8dea33 - std::sys::backtrace::BacktraceLock::print::h92bcbc0d077a2dbc
at /builddir/build/BUILD/rust-1.89.0-build/rustc-1.89.0-src/library/std/src/sys/backtrace.rs:42:9
9: 0x55de5f8de403 - std::panicking::default_hook::{{closure}}::hb6b1ab4366c73b14
10: 0x55de5f8de403 - std::panicking::default_hook::hd656a13b4c89a3bc
at /builddir/build/BUILD/rust-1.89.0-build/rustc-1.89.0-src/library/std/src/panicking.rs:327:9
11: 0x55de5f8de403 - std::panicking::rust_panic_with_hook::h29abf28177e63c84
at /builddir/build/BUILD/rust-1.89.0-build/rustc-1.89.0-src/library/std/src/panicking.rs:833:13
12: 0x55de5f916ab5 - std::panicking::begin_panic_handler::{{closure}}::hcdd4955adcd749e1
at /builddir/build/BUILD/rust-1.89.0-build/rustc-1.89.0-src/library/std/src/panicking.rs:699:13
13: 0x55de5f916a49 - std::sys::backtrace::__rust_end_short_backtrace::ha7685e8a98c61a2b
at /builddir/build/BUILD/rust-1.89.0-build/rustc-1.89.0-src/library/std/src/sys/backtrace.rs:168:18
14: 0x55de5f917dfc - __rustc[80c2b0bdb72b0a6c]::rust_begin_unwind
at /builddir/build/BUILD/rust-1.89.0-build/rustc-1.89.0-src/library/std/src/panicking.rs:697:5
15: 0x55de5f41b91f - core::panicking::panic_fmt::hc89b0bc3fafe8271
at /builddir/build/BUILD/rust-1.89.0-build/rustc-1.89.0-src/library/core/src/panicking.rs:75:14
16: 0x55de5f41ba5b - core::panicking::panic::h13f78b0d410218f2
at /builddir/build/BUILD/rust-1.89.0-build/rustc-1.89.0-src/library/core/src/panicking.rs:145:5
17: 0x55de5f41bc38 - core::option::unwrap_failed::h5189ee9f1cf5c131
at /builddir/build/BUILD/rust-1.89.0-build/rustc-1.89.0-src/library/core/src/option.rs:2072:5
18: 0x55de5f782383 - <scryer_prolog::repl_helper::Helper as rustyline::completion::Completer>::complete::hdbaded81d79f6e27
19: 0x55de5f782383 - rustyline::complete_line::h13259633e788d687
at /home/peter/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rustyline-14.0.0/src/lib.rs:90:41
20: 0x55de5f782383 - rustyline::Editor<H,I>::readline_edit::h92f245a57d5a1e5c
at /home/peter/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rustyline-14.0.0/src/lib.rs:734:28
21: 0x55de5f782383 - rustyline::Editor<H,I>::readline_with::h99dc0714fb9a0327
at /home/peter/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rustyline-14.0.0/src/lib.rs:668:35
22: 0x55de5f7f0b6e - rustyline::Editor<H,I>::readline::h9a5c1b92e4c460de
at /home/peter/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rustyline-14.0.0/src/lib.rs:642:14
23: 0x55de5f7f0b6e - scryer_prolog::read::ReadlineStream::call_readline::h89e458c405089538
at /home/peter/.cargo/git/checkouts/scryer-prolog-72624e9e4bca59c1/7ffd93c/src/read.rs:175:23
24: 0x55de5f8548d7 - <scryer_prolog::read::ReadlineStream as scryer_prolog::parser::char_reader::CharRead>::peek_char::h9860e36660db23e9
at /home/peter/.cargo/git/checkouts/scryer-prolog-72624e9e4bca59c1/7ffd93c/src/read.rs:272:33
25: 0x55de5f8548d7 - <scryer_prolog::machine::streams::Stream as scryer_prolog::parser::char_reader::CharRead>::peek_char::h2c341af224e8d165
at /home/peter/.cargo/git/checkouts/scryer-prolog-72624e9e4bca59c1/7ffd93c/src/machine/streams.rs:875:57
26: 0x55de5f8cf436 - scryer_prolog::machine::streams::<impl scryer_prolog::machine::machine_state::MachineState>::open_parsing_stream::hf4b3d0867f4fb9bf
at /home/peter/.cargo/git/checkouts/scryer-prolog-72624e9e4bca59c1/7ffd93c/src/machine/streams.rs:1950:22
27: 0x55de5f6902f8 - scryer_prolog::machine::system_calls::<impl scryer_prolog::machine::Machine>::get_n_chars::h76884cdafe1a6a51
at /home/peter/.cargo/git/checkouts/scryer-prolog-72624e9e4bca59c1/7ffd93c/src/machine/system_calls.rs:3652:44
28: 0x55de5f6902f8 - scryer_prolog::machine::dispatch::<impl scryer_prolog::machine::Machine>::dispatch_loop::h7ebf3060d7167bc0
at /home/peter/.cargo/git/checkouts/scryer-prolog-72624e9e4bca59c1/7ffd93c/src/machine/dispatch.rs:3713:61
29: 0x55de5f6902f8 - scryer_prolog::machine::Machine::run_module_predicate::hd71982fb45bb91df
at /home/peter/.cargo/git/checkouts/scryer-prolog-72624e9e4bca59c1/7ffd93c/src/machine/mod.rs:269:29
30: 0x55de5f7a6c22 - scryer_prolog::run_binary::{{closure}}::h1c3e117168389653
at /home/peter/.cargo/git/checkouts/scryer-prolog-72624e9e4bca59c1/7ffd93c/src/lib.rs:81:13
31: 0x55de5f7a6c22 - tokio::runtime::park::CachedParkThread::block_on::{{closure}}::he90c797f404ed0fd
at /home/peter/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tokio-1.45.0/src/runtime/park.rs:284:71
32: 0x55de5f7a6c22 - tokio::task::coop::with_budget::hb0bd1a396e119866
at /home/peter/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tokio-1.45.0/src/task/coop/mod.rs:167:5
33: 0x55de5f7a6c22 - tokio::task::coop::budget::h036feec077c1235a
at /home/peter/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tokio-1.45.0/src/task/coop/mod.rs:133:5
34: 0x55de5f7a6c22 - tokio::runtime::park::CachedParkThread::block_on::h30bf4fcb541d415a
at /home/peter/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tokio-1.45.0/src/runtime/park.rs:284:31
35: 0x55de5f7a6c22 - tokio::runtime::context::blocking::BlockingRegionGuard::block_on::h46f08a9cd1e55ada
at /home/peter/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tokio-1.45.0/src/runtime/context/blocking.rs:66:14
36: 0x55de5f7a6c22 - tokio::runtime::scheduler::multi_thread::MultiThread::block_on::{{closure}}::h2f7f1cfc899e163b
at /home/peter/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tokio-1.45.0/src/runtime/scheduler/multi_thread/mod.rs:87:22
37: 0x55de5f7a6c22 - tokio::runtime::context::runtime::enter_runtime::hedd9e060e4b53cc0
at /home/peter/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tokio-1.45.0/src/runtime/context/runtime.rs:65:16
38: 0x55de5f7a6c22 - tokio::runtime::scheduler::multi_thread::MultiThread::block_on::h9014a1cb3a2e530f
at /home/peter/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tokio-1.45.0/src/runtime/scheduler/multi_thread/mod.rs:86:9
39: 0x55de5f7a6c22 - tokio::runtime::runtime::Runtime::block_on_inner::h1c9a3c9aba5a9b02
at /home/peter/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tokio-1.45.0/src/runtime/runtime.rs:358:50
40: 0x55de5f7a6c22 - tokio::runtime::runtime::Runtime::block_on::hdb20448a65bd8356
at /home/peter/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tokio-1.45.0/src/runtime/runtime.rs:330:18
41: 0x55de5f7a6c22 - scryer_prolog::run_binary::hb9de05935ea3fa33
at /home/peter/.cargo/git/checkouts/scryer-prolog-72624e9e4bca59c1/7ffd93c/src/lib.rs:77:13
42: 0x55de5f43f013 - core::ops::function::FnOnce::call_once::h7518abea803d4efc
at /builddir/build/BUILD/rust-1.89.0-build/rustc-1.89.0-src/library/core/src/ops/function.rs:250:5
43: 0x55de5f43f013 - std::sys::backtrace::__rust_begin_short_backtrace::hf28ff8d951e0ae8b
at /builddir/build/BUILD/rust-1.89.0-build/rustc-1.89.0-src/library/std/src/sys/backtrace.rs:152:18
44: 0x55de5f43f4b1 - main
45: 0x7fa625924575 - __libc_start_call_main
46: 0x7fa625924628 - __libc_start_main_impl
47: 0x55de5f43ef45 - _start
48: 0x0 - <unknown>
As a workaround, does it work if you invoke Scryer with -g run file.pl instead of using the initialization/1 directive?
EDIT: It also crashes:
$ scryer-prolog -g run issue3057.pl [tab] thread 'main' panicked at src/repl_helper.rs:71:51: called `Option::unwrap()` on a `None` value note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
The interesting thing to me is that the point where the crash occurs is not the "reading of input" Rust part I would expect to be invoked in this example:
https://github.com/mthom/scryer-prolog/blob/7ffd93c0d7a9521222d1e87acff17aad39e69e9e/src/repl_helper.rs#L71
This code seems to be part of the toplevel completion, i.e., something that is invoked via repl/0 → read_and_match/0 → '$read_query_term'/5, to eventually read_term_from_user_input:
https://github.com/mthom/scryer-prolog/blob/7ffd93c0d7a9521222d1e87acff17aad39e69e9e/src/machine/machine_state.rs#L744
On the toplevel interaction, I expect such completions to be taken into account.
get_n_chars/3, on the other hand, is expected to read characters without any completion. It is interesting that it works correctly when run/0 is invoked from the toplevel, but not when invoked via the -g command line option, because in both cases, the goal is invoked from toplevel.pl, in roughly the same way.
If you want to take a look at it, goals that are posted as queries on the toplevel are run here:
https://github.com/mthom/scryer-prolog/blob/7ffd93c0d7a9521222d1e87acff17aad39e69e9e/src/toplevel.pl#L258
For comparison, goals that are specified via the -g command line option are invoked here, in the same file:
https://github.com/mthom/scryer-prolog/blob/7ffd93c0d7a9521222d1e87acff17aad39e69e9e/src/toplevel.pl#L141
There may be an unintended difference between these cases, so that Scryer is in an unexpected state when running the -g goals, and thus the textual completion is unintentionally invoked when pressing TAB in this case.
I would greatly appreciate if you could take a look at this, thank you a lot!
Thanks, I will take a look and see if I can make some sense of it.
Okay, I think the general issue is that standard input is internally a ReadlineStream, and thus any attempt to read a character from that stream will enter the readline code, which is what handles tab-completion and so on.
I haven't yet found out why it crashes, but I suspect some state which is uses to provide completions, hasn't been setup yet. However, this is a red herring. The real issue is that input in get_char, peek_char, and get_n_chars (perhaps others), shouldn't have readline semantics at all in my opinion. It should just read a character from the underlying input stream directly.
Interestingly, I also ran into issue #2787 earlier today, and this seems to be caused by the same code. When the readline library (rustyline) is called to get a character, it starts by clearing the current line on standard output, and then printing a prompt, which in some cases is just an empty string, if no ?- prompt is needed. This means a call to peek_char/1 will emit a newline, which is unexpected: ?- put_char('>'), flush_output(user_output), peek_char(X). shows this behaviour.
I suspect a proper fix for this issue and #2787 will be to perform raw character reads instead of invoking the readline functionality, for the predicates where it makes sense. I will see if I am able to create a pull request for this at some point in the near future.
Your PR solves this nicely!
I would also like to add: For your concrete use case, get_single_char/1 from library(charsio) may also be useful, it truly reads a single character and you do not have to press RET on the terminal.
@pmikkelsen: I found another issue where rustyline is mentioned: https://github.com/mthom/scryer-prolog/issues/1897
Is this related, and can it be addressed analogously?
My guess would be that instead of "blindly" opening standard input as a ReadlineStream internally, we should attempt to check if standard input is indeed connected to a terminal first, and if not, open it as another stream type internally (I am not familiar enough with the code to say which, but InputFileStream or PipeReader sound like they might be fitting). is_terminal() seems like a good and standard way to check if stdin is a terminal or not. As far as I know, other language interpreters typically do this, and then they have a flag to force interactive mode in case the autodetection is wrong.
Yes indeed, PipeReader could be fitting! I would greatly appreciate if you could take a look also at this conceptually related issue in case you are interested, thank you a lot!