eyeprolog 1.2.2 → 1.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/examples/access-control-policy.pl +2 -0
  2. package/examples/d3-group.pl +2 -0
  3. package/examples/fast-fourier-transform.pl +1 -0
  4. package/examples/map-four-color-search.pl +2 -0
  5. package/examples/partial-evaluator.pl +2 -0
  6. package/examples/weighted-interval-scheduling.pl +2 -0
  7. package/package.json +1 -1
  8. package/src/repl.js +83 -8
  9. package/src/solver.js +1 -1
  10. package/test/conformance/ISO-COMPLIANCE.md +1 -1
  11. package/test/conformance/ISO-MATRIX.md +3 -3
  12. package/test/conformance/cases/aggregation/057_countall_empty_and_nonempty.pl +2 -0
  13. package/test/conformance/cases/aggregation/075_aggregation_edges.pl +2 -0
  14. package/test/conformance/cases/aggregation/aggregate_max_no_answers_fails.pl +2 -0
  15. package/test/conformance/cases/aggregation/countall_zero.pl +2 -0
  16. package/test/conformance/cases/aggregation/findall_empty_bag.pl +2 -0
  17. package/test/conformance/cases/aggregation/sumall_empty_zero.pl +2 -0
  18. package/test/conformance/cases/control/074_forall_edges.pl +2 -0
  19. package/test/conformance/cases/control/extra_not_unbound_unknown_goal_succeeds.pl +2 -0
  20. package/test/conformance/cases/control/extra_once_empty_fails.pl +2 -0
  21. package/test/conformance/cases/control/forall_empty_generator_true.pl +2 -0
  22. package/test/conformance/cases/negation/not_unknown_goal_succeeds.pl +2 -0
  23. package/test/conformance/warnings/negation/extra_stratified_multiple_negations_quiet.pl +2 -0
  24. package/test/conformance/warnings/negation/extra_warning_preserves_empty_stdout.pl +2 -0
  25. package/test/conformance/warnings/negation/negative_unknown_group_quiet.pl +2 -0
  26. package/test/conformance/warnings/negation/stratified_negative_unknown_after_positive_quiet.pl +2 -0
  27. package/test/conformance/warnings/negation/stratified_quiet.pl +2 -0
  28. package/test/run-regression.mjs +71 -1
  29. package/the-art-of-eyeprolog.md +29 -11
@@ -53,3 +53,5 @@ status(test1, policy_passed) :-
53
53
 
54
54
  reason(test1, "all required claims are present, one allowed claim is present, and no forbidden claim is present") :-
55
55
  passes_policy(test1, policy_x).
56
+
57
+ :- set_prolog_flag(unknown, fail).
@@ -102,3 +102,5 @@ subgroupCount(d3_group, Count) :-
102
102
  length(Groups, Count).
103
103
 
104
104
  reason(d3_group, "findall enumerates candidate subgroups and sort gives canonical subgroup order").
105
+
106
+ :- set_prolog_flag(unknown, fail).
@@ -53,3 +53,4 @@ node(n(A1, N1), [n(A, N)|T]-[n(A1, N1), n(A, N)|T]) :-
53
53
  % query
54
54
  %% goal: fft([0, 1, 2, 3, 4, 5, 6, 7], _)
55
55
 
56
+ :- set_prolog_flag(unknown, fail).
@@ -66,3 +66,5 @@ four_color_answer(european_union, Coloring) :-
66
66
  place_order(Places),
67
67
  place_pairs(Places, Coloring),
68
68
  once(color_places(Coloring)).
69
+
70
+ :- set_prolog_flag(unknown, fail).
@@ -72,3 +72,5 @@ residual_program(Name, Residual) :- program(Name, Expr, Env), pe(Env, Expr, Resi
72
72
 
73
73
  partialEvalAnswer(residual(Name), Residual) :- residual_program(Name, Residual).
74
74
  partialEvalAnswer(note, "static inputs are folded while dynamic variables remain as residual code") :- residual_program(poly_y, _).
75
+
76
+ :- set_prolog_flag(unknown, fail).
@@ -82,3 +82,5 @@ weighted_interval_answer(chosen_interval, interval(I, Start, Finish, Value)) :-
82
82
  chosen_from(1, I),
83
83
  interval(I, Start, Finish, Value).
84
84
  weighted_interval_answer(candidate_count, Count) :- countall(interval(_i, _start, _finish, _value), Count).
85
+
86
+ :- set_prolog_flag(unknown, fail).
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.2.2",
6
+ "version": "1.2.4",
7
7
  "description": "EyeProlog turns facts and rules into answers and proofs.",
8
8
  "type": "module",
9
9
  "main": "./index.js",
package/src/repl.js CHANGED
@@ -34,7 +34,7 @@ export async function runRepl(engine, options = {}) {
34
34
  const goal = parseGoal(engine, state, text);
35
35
  if (!options.isoStrict && isUseModuleGoal(goal)) {
36
36
  sources.push({ text: `:- ${text}.\n`, filename: '<repl>' });
37
- state = makeState(engine, sources, output, options);
37
+ state = makeState(engine, sources, output, options, state);
38
38
  state.solver.runInitializations();
39
39
  output.write(' true.\n');
40
40
  continue;
@@ -42,16 +42,22 @@ export async function runRepl(engine, options = {}) {
42
42
  const consultFiles = options.isoStrict ? null : consultDesignations(engine, goal);
43
43
  if (consultFiles != null) {
44
44
  for (const filename of consultFiles) sources.push(await readSource(filename));
45
- state = makeState(engine, sources, output, options);
45
+ state = makeState(engine, sources, output, options, state);
46
46
  state.solver.runInitializations();
47
47
  output.write(' true.\n');
48
48
  continue;
49
49
  }
50
50
 
51
- const result = await solveQuery(engine, state, goal, reader, output);
52
- if (result?.halted) {
53
- exitCode = result.code;
54
- break;
51
+ const flagsBefore = snapshotFlagValues(state.solver);
52
+ try {
53
+ await prepareInteractiveTermInput(state, goal, reader);
54
+ const result = await solveQuery(engine, state, goal, reader, output);
55
+ if (result?.halted) {
56
+ exitCode = result.code;
57
+ break;
58
+ }
59
+ } finally {
60
+ rememberFlagOverrides(state, flagsBefore);
55
61
  }
56
62
  } catch (error) {
57
63
  if (error?.name === 'HaltSignal') {
@@ -142,7 +148,7 @@ class LineReader {
142
148
  }
143
149
  }
144
150
 
145
- function makeState(engine, sources, output, options = {}) {
151
+ function makeState(engine, sources, output, options = {}, previousState = null) {
146
152
  const strictIso = options.isoStrict === true;
147
153
  const program = engine.Program.parseSources(sources, { strictIso, sourceMetadata: strictIso });
148
154
  const solver = new engine.Solver(program, {
@@ -150,7 +156,25 @@ function makeState(engine, sources, output, options = {}) {
150
156
  isoStrict: strictIso,
151
157
  ioOptions: { write: (text) => output.write(String(text)) },
152
158
  });
153
- return { program: solver.program, solver, strictIso };
159
+ const flagOverrides = new Map(previousState?.flagOverrides ?? []);
160
+ for (const [name, value] of flagOverrides) {
161
+ const definition = solver.prologFlags.get(name);
162
+ if (definition?.changeable && definition.allowed.includes(value)) {
163
+ definition.value = engine.atom(value);
164
+ }
165
+ }
166
+ return { program: solver.program, solver, strictIso, flagOverrides };
167
+ }
168
+
169
+ function snapshotFlagValues(solver) {
170
+ return new Map([...solver.prologFlags].map(([name, definition]) => [name, definition.value?.name]));
171
+ }
172
+
173
+ function rememberFlagOverrides(state, before) {
174
+ for (const [name, definition] of state.solver.prologFlags) {
175
+ const value = definition.value?.name;
176
+ if (before.get(name) !== value) state.flagOverrides.set(name, value);
177
+ }
154
178
  }
155
179
 
156
180
  async function readQuery(reader) {
@@ -166,6 +190,57 @@ async function readQuery(reader) {
166
190
  }
167
191
  }
168
192
 
193
+ async function prepareInteractiveTermInput(state, goal, reader) {
194
+ const stream = interactiveTermInputStream(state, goal);
195
+ if (stream == null || terminalFullStop(String(stream.content).slice(stream.position)) >= 0) return;
196
+
197
+ const text = await readInteractiveTerm(reader);
198
+ if (text == null) return;
199
+ stream.content += text;
200
+ stream.pastEnd = false;
201
+ }
202
+
203
+ function interactiveTermInputStream(state, goal) {
204
+ if (goal.type !== 'compound') return null;
205
+ let reference = null;
206
+ if (goal.name === 'read' && goal.arity === 1) {
207
+ reference = state.solver.io.currentInput;
208
+ } else if (goal.name === 'read' && goal.arity === 2) {
209
+ reference = explicitInputReference(goal.args[0]);
210
+ } else if (goal.name === 'read_term' && goal.arity === 2) {
211
+ reference = state.solver.io.currentInput;
212
+ } else if (goal.name === 'read_term' && goal.arity === 3) {
213
+ reference = explicitInputReference(goal.args[0]);
214
+ } else {
215
+ return null;
216
+ }
217
+
218
+ const stream = reference == null ? null : state.solver.io.resolve(reference);
219
+ return stream?.standard && stream.alias === 'user_input' && stream.mode === 'read' ? stream : null;
220
+ }
221
+
222
+ function explicitInputReference(term) {
223
+ if (term.type === 'atom') return term.name;
224
+ if (term.type === 'compound' && term.name === '$stream' && term.arity === 1 &&
225
+ term.args[0].type === 'number' && /^\d+$/.test(term.args[0].name)) {
226
+ return Number(term.args[0].name);
227
+ }
228
+ return null;
229
+ }
230
+
231
+ async function readInteractiveTerm(reader) {
232
+ let source = '';
233
+ let prompt = '|: ';
234
+ while (true) {
235
+ const line = await reader.read(prompt);
236
+ if (line == null) return source.trim() ? source : null;
237
+ source += `${line}\n`;
238
+ const end = terminalFullStop(source);
239
+ if (end >= 0) return source.slice(0, end + 1) + '\n';
240
+ prompt = '| ';
241
+ }
242
+ }
243
+
169
244
  function quotedEscapeEnd(source, index) {
170
245
  const escaped = source[index + 1] ?? '';
171
246
  if (!escaped) return index;
package/src/solver.js CHANGED
@@ -49,7 +49,7 @@ export class Solver {
49
49
  this.inferenceLimitExceeded = false;
50
50
  this.solutionLimit = options.solutionLimit ?? 10000000;
51
51
  this.solutionsSeen = 0;
52
- this.prologFlags = options.prologFlags ?? defaultPrologFlags(this.registry?.eyePrologLibrary ? 'fail' : 'error', this.isoStrict);
52
+ this.prologFlags = options.prologFlags ?? defaultPrologFlags('error', this.isoStrict);
53
53
  if (this.isoStrict) {
54
54
  for (const name of [...this.prologFlags.keys()]) {
55
55
  if (!ISO_CORE_FLAG_NAMES.has(name)) this.prologFlags.delete(name);
@@ -38,7 +38,7 @@ error-ordering alternative to an individual executable assertion.
38
38
  | 7.8 — control constructs and exceptions | audit | call, cut, conjunction, disjunction, if-then, catch/throw, renamed-copy tests. |
39
39
  | 7.9 — expression evaluation | audit | Arithmetic/evaluation/error suites, including Corrigenda. Exceptional-value/error-precedence matrix remains to be exhaustively enumerated. |
40
40
  | 7.10 — input/output concepts | audit | Stream, character/byte I/O, read/write options, operator-sensitive write-back, Corrigendum 3 writer cases. Full option cross-product remains open. |
41
- | 7.11 — flags | audit | Required Part 1 flags implemented. Strict mode excludes the EyeProlog `occurs_check` extension and uses ISO `unknown=error`. |
41
+ | 7.11 — flags | audit | Required Part 1 flags implemented. Normal and strict modes use the ISO `unknown=error` default; strict mode excludes the EyeProlog `occurs_check` extension. |
42
42
  | 7.12 — errors | audit | ISO `error(Error, Context)` envelope, type/domain/permission/representation/evaluation/syntax families and focused error cases. Complete prescribed-error ordering remains open. |
43
43
  | 8.2-8.17 — built-in predicates | audit | Predicate-family coverage is mapped in ISO-MATRIX.md; Corrigendum 2 additions (`subsumes_term/2`, `term_variables/2`, `call/2..8`, `false/0`) are in the strict registry. Mode/error matrix is not yet one-row-per-standard-row. |
44
44
  | Clause 9 — evaluable functors | audit | Integer/float/rounding/transcendental/bitwise suites and corrigendum cases. Host floating-point representation choices remain documented implementation-defined behavior. |
@@ -31,9 +31,9 @@ integer powers (Cor.3).
31
31
  Implementation-defined choices are documented in *The Art of EyeProlog*:
32
32
  integers and arity are unbounded by the Prolog model (subject to host memory),
33
33
  ordinary unification performs an occurs check, `double_quotes` defaults to
34
- `chars`, strict core mode uses ISO `unknown=error` while normal EyeProlog
35
- defaults `unknown` to `fail`, `//` rounds toward zero, floating-point operations
36
- use finite ECMAScript numbers, and character codes use Unicode scalar values.
34
+ `chars`, both strict core mode and normal EyeProlog use the ISO
35
+ `unknown=error` default, `//` rounds toward zero, floating-point operations use
36
+ finite ECMAScript numbers, and character codes use Unicode scalar values.
37
37
 
38
38
  This is an executable conformance matrix, not a certification issued by an
39
39
  independent standards body. Release gating runs the ISO cases and the dedicated
@@ -1,3 +1,5 @@
1
+ :- set_prolog_flag(unknown, fail).
2
+
1
3
  item(a).
2
4
  item(b).
3
5
  answer(counts, counts(C, Z)) :- countall(item(X), C), countall(missing(X), Z).
@@ -1,3 +1,5 @@
1
+ :- set_prolog_flag(unknown, fail).
2
+
1
3
  % Reference 9.1: aggregation handles empty result sets, structured templates, and ordered best answers.
2
4
  %% goal: answer(X0, X1)
3
5
 
@@ -1,3 +1,5 @@
1
+ :- set_prolog_flag(unknown, fail).
2
+
1
3
  %% goal: answer(X0)
2
4
 
3
5
  answer(ok) :- \+ aggregate_max(Key, Value, missing(Value), BestKey, BestValue).
@@ -1,3 +1,5 @@
1
+ :- set_prolog_flag(unknown, fail).
2
+
1
3
  %% goal: answer(X0)
2
4
 
3
5
  answer(N) :- countall(missing(X), N).
@@ -1,3 +1,5 @@
1
+ :- set_prolog_flag(unknown, fail).
2
+
1
3
  %% goal: answer(X0)
2
4
 
3
5
  answer(Bag) :- findall(X, missing(X), Bag).
@@ -1,3 +1,5 @@
1
+ :- set_prolog_flag(unknown, fail).
2
+
1
3
  %% goal: answer(X0)
2
4
 
3
5
  answer(N) :- sumall(X, missing(X), N).
@@ -1,3 +1,5 @@
1
+ :- set_prolog_flag(unknown, fail).
2
+
1
3
  % Reference 9.1: forall/2 succeeds for every generated binding, including the empty generator case.
2
4
  %% goal: answer(X0, X1)
3
5
 
@@ -1,3 +1,5 @@
1
+ :- set_prolog_flag(unknown, fail).
2
+
1
3
  %% goal: answer(X0)
2
4
 
3
5
  answer(not_unbound_unknown_goal_succeeds) :- \+ missing(X).
@@ -1,3 +1,5 @@
1
+ :- set_prolog_flag(unknown, fail).
2
+
1
3
  %% goal: answer(X0)
2
4
 
3
5
  answer(once_empty_fails) :- once(missing).
@@ -1,3 +1,5 @@
1
+ :- set_prolog_flag(unknown, fail).
2
+
1
3
  %% goal: answer(X0)
2
4
 
3
5
  answer(ok) :- \+ empty_counterexample.
@@ -1,3 +1,5 @@
1
+ :- set_prolog_flag(unknown, fail).
2
+
1
3
  % Negation succeeds when its inner goal has no solution.
2
4
  %% goal: answer(X0)
3
5
 
@@ -1,3 +1,5 @@
1
+ :- set_prolog_flag(unknown, fail).
2
+
1
3
  %% goal: answer(X0)
2
4
 
3
5
  p(a).
@@ -1,3 +1,5 @@
1
+ :- set_prolog_flag(unknown, fail).
2
+
1
3
  %% goal: answer(X0)
2
4
 
3
5
  p(a) :- \+ q(a).
@@ -1,3 +1,5 @@
1
+ :- set_prolog_flag(unknown, fail).
2
+
1
3
  %% goal: answer(X0)
2
4
 
3
5
  answer(ok) :- \+ missing(ok).
@@ -1,3 +1,5 @@
1
+ :- set_prolog_flag(unknown, fail).
2
+
1
3
  %% goal: answer(X0)
2
4
 
3
5
  item(a).
@@ -1,3 +1,5 @@
1
+ :- set_prolog_flag(unknown, fail).
2
+
1
3
  % Stratified negation emits no portability warning.
2
4
  %% goal: answer(X0)
3
5
 
@@ -671,6 +671,29 @@ c4 ?- call((!;1)).
671
671
  assertEqual(result.stderr, '', 'stderr');
672
672
  },
673
673
  },
674
+ {
675
+ name: 'REPL read predicates consume following interactive term input',
676
+ run: () => {
677
+ const result = runCli([], {
678
+ input:
679
+ 'read(X).\n' +
680
+ 'foo.\n' +
681
+ 'read_term(Y, []).\n' +
682
+ "'\\7\\'.\n" +
683
+ 'read(user_input, Z).\n' +
684
+ 'bar.\n' +
685
+ 'halt.\n',
686
+ });
687
+ assertEqual(result.status, 0, 'exit status');
688
+ assertEqual(result.stdout,
689
+ '?- |: X = foo.\n' +
690
+ "?- |: Y = '\\a'.\n" +
691
+ '?- |: Z = bar.\n' +
692
+ '?- ',
693
+ 'stdout');
694
+ assertEqual(result.stderr, '', 'stderr');
695
+ },
696
+ },
674
697
  {
675
698
  name: 'REPL accepts multiline period-terminated queries',
676
699
  run: () => {
@@ -693,6 +716,36 @@ c4 ?- call((!;1)).
693
716
  assertEqual(result.stderr, '', 'stderr');
694
717
  },
695
718
  },
719
+ {
720
+ name: 'REPL preserves runtime unknown flag across consultation',
721
+ run: () => {
722
+ const filename = path.join(tmp, `repl-empty-${++tmpCounter}.pl`);
723
+ fs.writeFileSync(filename, '');
724
+ const result = runCli([], {
725
+ input:
726
+ 'current_prolog_flag(unknown, V).\n' +
727
+ 'set_prolog_flag(unknown, fail).\n' +
728
+ `[${sourceAtom(filename)}].\n` +
729
+ 'current_prolog_flag(unknown, V).\n' +
730
+ 'set_prolog_flag(unknown, error).\n' +
731
+ `[${sourceAtom(filename)}].\n` +
732
+ 'missing_after_consult.\n' +
733
+ 'halt.\n',
734
+ });
735
+ assertEqual(result.status, 0, 'exit status');
736
+ assertEqual(result.stdout,
737
+ '?- V = error.\n' +
738
+ '?- true.\n' +
739
+ '?- true.\n' +
740
+ '?- V = fail.\n' +
741
+ '?- true.\n' +
742
+ '?- true.\n' +
743
+ '?- error(existence_error(procedure, missing_after_consult / 0), eyeprolog).\n' +
744
+ '?- ',
745
+ 'stdout');
746
+ assertEqual(result.stderr, '', 'stderr');
747
+ },
748
+ },
696
749
  {
697
750
  name: 'REPL use_module imports Part 2 library predicates',
698
751
  run: () => {
@@ -700,7 +753,11 @@ c4 ?- call((!;1)).
700
753
  input: 'append(X, Y, [1, 2, 3, 4]).\nuse_module(library(lists)).\nappend(X, Y, [1, 2, 3, 4]).\n\nhalt.\n',
701
754
  });
702
755
  assertEqual(result.status, 0, 'exit status');
703
- assertEqual(result.stdout, '?- false.\n?- true.\n?- X = [], Y = [1, 2, 3, 4]\n; ... .\n?- ', 'stdout');
756
+ assertEqual(result.stdout,
757
+ '?- error(existence_error(procedure, append / 3), eyeprolog).\n' +
758
+ '?- true.\n' +
759
+ '?- X = [], Y = [1, 2, 3, 4]\n; ... .\n?- ',
760
+ 'stdout');
704
761
  assertEqual(result.stderr, '', 'stderr');
705
762
  },
706
763
  },
@@ -1097,6 +1154,19 @@ c4 ?- call((!;1)).
1097
1154
  assertEqual(infix.arity, 2, 'quad query arity');
1098
1155
  },
1099
1156
  },
1157
+ {
1158
+ name: 'normal EyeProlog uses the ISO unknown=error default',
1159
+ run: () => {
1160
+ const program = Program.parse('');
1161
+ const solver = new Solver(program, { registry: getEyePrologRegistry() });
1162
+ assertEqual(solver.prologFlags.get('unknown')?.value?.name, 'error', 'normal unknown default');
1163
+ assertEqual(
1164
+ run('', { goal: 'current_prolog_flag(unknown, V)' }).stdout,
1165
+ 'current_prolog_flag(unknown, error).\n',
1166
+ 'public runner unknown default',
1167
+ );
1168
+ },
1169
+ },
1100
1170
  {
1101
1171
  name: 'strict ISO core mode exposes only the Part 1 registry and flag surface',
1102
1172
  run: () => {
@@ -5667,18 +5667,23 @@ silently changing a static program.
5667
5667
  | `max_integer` | `unbounded` | `unbounded` | no |
5668
5668
  | `min_integer` | `unbounded` | `unbounded` | no |
5669
5669
  | `max_arity` | `unbounded` | `unbounded` | no |
5670
- | `unknown` | `fail` | `error`, `fail`, `warning` | yes |
5670
+ | `unknown` | `error` | `error`, `fail`, `warning` | yes |
5671
5671
  | `double_quotes` | `chars` | `chars`, `codes`, `atom` | yes |
5672
5672
  | `occurs_check` | `true` | `true`, `error` | yes |
5673
5673
 
5674
- Strict ISO core mode defaults `unknown` to `error`; the normal EyeProlog
5675
- environment defaults it to `fail`. The `occurs_check` flag is an EyeProlog
5676
- diagnostic extension rather than an ISO-defined core flag: it is absent in
5677
- strict mode, while normal mode keeps its `true` default and optional `error`
5678
- diagnostic for STO attempts. Operator and flag directives are processed per
5679
- program rather than changing global JavaScript state. The `double_quotes`
5680
- setting affects subsequent source text, included files, command-line and API
5681
- goal text, and terms read by `read_term/*`:
5674
+ Both normal EyeProlog and strict ISO core mode use the ISO `unknown=error`
5675
+ default. Interactive `set_prolog_flag/2` changes are retained when the REPL
5676
+ consults another file or imports a module instead of being reset by the host
5677
+ rebuild of the program. Programs that intentionally treat an undefined
5678
+ predicate as failure must opt in with `set_prolog_flag(unknown, fail)`; bundled
5679
+ examples and non-ISO corpus cases that depend on that policy do so explicitly.
5680
+ The `occurs_check` flag is an EyeProlog diagnostic
5681
+ extension rather than an ISO-defined core flag: it is absent in strict mode,
5682
+ while normal mode keeps its `true` default and optional `error` diagnostic for
5683
+ STO attempts. Operator and flag directives are processed per program rather
5684
+ than changing global JavaScript state. The `double_quotes` setting affects
5685
+ subsequent source text, included files, command-line and API goal text, and
5686
+ terms read by `read_term/*`:
5682
5687
 
5683
5688
  ```text
5684
5689
  % Default: a list of one-character atoms.
@@ -6216,8 +6221,21 @@ not be a valid right operand of the displayed `=/2`, EyeProlog adds parentheses,
6216
6221
  for example `T = (a = b).` rather than the invalid `T = a = b.`. Use `[file].`
6217
6222
  or `['file.pl'].` to
6218
6223
  consult local source, and `halt.` or `halt(Status).` to leave the top level.
6219
- Up and Down recall queries from the current session. Explicit `eyeprolog -h`
6220
- displays command-line help.
6224
+ A direct top-level `read/1-2` or `read_term/2-3` from `user_input` requests the
6225
+ next full-stop-terminated Prolog term with a `|: ` input prompt instead of
6226
+ treating the interactive input stream as already exhausted. For example:
6227
+
6228
+ ```text
6229
+ ?- read(X).
6230
+ |: hello.
6231
+ X = hello.
6232
+ ```
6233
+
6234
+ The top-level prompt itself is a host-interface convention rather than part of
6235
+ ISO/IEC 13211-1; the term that follows it is parsed by the same ISO term-input
6236
+ machinery as `read/1-2` and `read_term/2-3` on other text streams. Up and Down
6237
+ recall queries from the current session. Explicit `eyeprolog -h` displays
6238
+ command-line help.
6221
6239
 
6222
6240
  ### Selecting goals
6223
6241