eyeprolog 1.5.45 → 1.5.47

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.
@@ -0,0 +1,36 @@
1
+ # Acknowledgements
2
+
3
+ This work was carried out at [IDLab](https://idlab.ugent.be/), Ghent University
4
+ – imec, within the [KNoWS](https://knows.idlab.ugent.be/) (Knowledge on Web
5
+ Scale) research team, as part of the **Koreografeye** project.
6
+
7
+ ## Standards
8
+
9
+ Conformance work on this repository targets the following standards.
10
+ Licensed standards documents are not redistributed here:
11
+
12
+ - ISO/IEC 13211-1:1995, *Information technology — Programming languages —
13
+ Prolog — Part 1: General core*
14
+ - ISO/IEC 13211-1:1995/Cor.1:2007
15
+ - ISO/IEC 13211-1:1995/Cor.2:2012
16
+ - ISO/IEC 13211-1:1995/Cor.3:2017
17
+ - ISO/IEC 13211-2:2000, *Part 2: Modules*, and its 2013 amendment
18
+ - ISO/IEC TS 13211-3:2025, *Part 3: Definite clause grammar rules*
19
+
20
+ ## External conformance corpora
21
+
22
+ The test suite executes third-party material that is vendored unmodified and
23
+ credited in place:
24
+
25
+ - Ulrich Neumerkel's ISO Prolog conformity corpora (TU Wien), including the
26
+ `phrase_quad.pl` and `variable_names_quad.pl` snapshots under
27
+ `test/fixtures/`.
28
+ - The WG17 syntax conformity-testing matrix.
29
+ - Test cases adapted from the Logtalk ISO conformance suite, marked as such in
30
+ the corpus files that use them.
31
+
32
+ ## Citation
33
+
34
+ When referring to this work in a publication, please acknowledge the project as
35
+ described above. A suggested sentence is available in the paper template used by
36
+ the KNoWS team.
package/README.md CHANGED
@@ -72,7 +72,8 @@ printf 'human(socrates).\nmortal(X) :- human(X).\n' |
72
72
  - [rdf-prolog-roundtrip](https://github.com/eyereasoner/rdf-prolog-roundtrip) — standalone RDF 1.2 ↔ ISO Prolog bridge used by the RDF examples
73
73
  - [ISO conformance audit](test/conformance/ISO-COMPLIANCE.md) — supported Part 1 profile
74
74
  - [Latest Neumerkel conformity](test/conformance/NEUMERKEL-LATEST.md) — tracked result from the current live upstream inventory
75
- - [Conformance report](conformance-report.md) — generated executable conformance status and local corpus summary
75
+ - [Conformance report](conformance-report.md) — generated executable conformance status, local corpus summary, and known deviations
76
+ - [Acknowledgements](ACKNOWLEDGEMENTS.md) — funding, standards consulted, and credits for vendored conformance corpora
76
77
  - [OpenRuleBench](openrulebench/README.md) — portable benchmark profile
77
78
  ## RDF, Prolog, and symbiotic knowledge graphs
78
79
 
@@ -25,14 +25,14 @@ spelling therefore changes this report even when no corpus file is added or remo
25
25
 
26
26
  | Category | Positive | Errors | Warnings | Proofs | Total |
27
27
  |---|---:|---:|---:|---:|---:|
28
- | aggregation | 17 | 0 | 0 | 0 | 17 |
28
+ | aggregation | 18 | 0 | 0 | 0 | 18 |
29
29
  | arithmetic | 38 | 0 | 0 | 0 | 38 |
30
30
  | atoms | 23 | 8 | 0 | 0 | 31 |
31
31
  | builtins | 11 | 0 | 0 | 0 | 11 |
32
32
  | context | 11 | 0 | 0 | 0 | 11 |
33
33
  | control | 15 | 0 | 0 | 0 | 15 |
34
34
  | explicit-tabling | 6 | 0 | 0 | 0 | 6 |
35
- | iso | 169 | 217 | 0 | 0 | 386 |
35
+ | iso | 171 | 217 | 0 | 0 | 388 |
36
36
  | lists | 52 | 3 | 0 | 0 | 55 |
37
37
  | modules | 2 | 0 | 0 | 0 | 2 |
38
38
  | negation | 8 | 0 | 19 | 0 | 27 |
@@ -45,4 +45,23 @@ spelling therefore changes this report even when no corpus file is added or remo
45
45
  | terms | 26 | 3 | 0 | 0 | 29 |
46
46
  | unification | 18 | 0 | 0 | 0 | 18 |
47
47
  | variables | 16 | 7 | 0 | 0 | 23 |
48
- | **Total** | **490** | **272** | **19** | **21** | **802** |
48
+ | **Total** | **493** | **272** | **19** | **21** | **805** |
49
+
50
+ ## DCG conformance clarification
51
+
52
+ EyeProlog checks the input and remainder of `phrase/2-3` and reports
53
+ `type_error(list, S)` when an argument is neither a list nor a partial list.
54
+ These implementation-defined checks follow ISO/IEC TS 13211-3:2025,
55
+ 8.18.1.3 g and h; this behavior is not a known deviation.
56
+
57
+ The Part 3 implementation target is
58
+ [ISO/IEC TS 13211-3:2025](https://www.iso.org/standard/83635.html).
59
+ The public [error subclause](https://www.complang.tuwien.ac.at/ulrich/iso-prolog/draft-8.18.1.3)
60
+ also specifies `list` for these checks.
61
+
62
+ The [phrase comparison](https://www.complang.tuwien.ac.at/ulrich/iso-prolog/phrase)
63
+ links the machine-readable `phrase_quad.pl` corpus. `npm test` fetches and
64
+ runs that corpus live through the Neumerkel gate; the offline regression
65
+ suite runs all 58 vendored quads. Neither expectations nor error matching
66
+ are relaxed for quads 41-44.
67
+
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.5.45",
6
+ "version": "1.5.47",
7
7
  "description": "EyeProlog turns facts and rules into answers and proofs.",
8
8
  "type": "module",
9
9
  "main": "./index.js",
@@ -28,6 +28,7 @@
28
28
  "files": [
29
29
  "LICENSE.md",
30
30
  "README.md",
31
+ "ACKNOWLEDGEMENTS.md",
31
32
  "index.js",
32
33
  "index.d.ts",
33
34
  "playground.html",
package/src/cli.js CHANGED
@@ -9,6 +9,15 @@ import { memoryStatistics } from './platform.js';
9
9
  let engineModule = null;
10
10
  let explanationModule = null;
11
11
 
12
+ // The usage text documents the input file as optional, and `--goal` is
13
+ // meaningful on its own. Only fall back to stdin when it is actually
14
+ // redirected: on an interactive terminal there is nothing to read, and
15
+ // blocking on it makes `eyeprolog --goal G` look like it does nothing.
16
+ // An explicit `-` argument still selects stdin in either case.
17
+ export function defaultsToStdin(fileCount, stdinIsTty) {
18
+ return fileCount === 0 && !stdinIsTty;
19
+ }
20
+
12
21
  export async function main(argv) {
13
22
  if (argv.length === 0) {
14
23
  const engine = await loadEngine();
@@ -132,7 +141,7 @@ export async function main(argv) {
132
141
  return;
133
142
  }
134
143
 
135
- if (options.files.length === 0) {
144
+ if (defaultsToStdin(options.files.length, process.stdin.isTTY)) {
136
145
  options.files.push('-');
137
146
  }
138
147
 
@@ -157,6 +166,12 @@ export async function main(argv) {
157
166
  }
158
167
  }
159
168
 
169
+ if (sourceParts.length === 0) {
170
+ // No file and no redirected stdin: run the requested goals against an
171
+ // empty database rather than against nothing at all.
172
+ sourceParts.push({ text: '', filename: '<empty>' });
173
+ }
174
+
160
175
  if (options.goals.length === 0 && !options.quads && options.verifyProof == null) {
161
176
  for (const source of sourceParts) options.goals.push(...goalsFromSource(source.text));
162
177
  }
package/src/iso.js CHANGED
@@ -3022,6 +3022,10 @@ function* phraseSolutions({ solver, goal, env }, state) {
3022
3022
  const input = goal.args[1];
3023
3023
  const requestedOutput = goal.arity === 2 ? emptyTerminalSequence() : goal.args[2];
3024
3024
  validateDcgEmbeddedGoals(grammarBody, input, requestedOutput);
3025
+ // ISO/IEC TS 13211-3:2025, 8.18.1.3 g and h permit these checks
3026
+ // and specify type_error(list, S) for invalid terminal-sequence arguments.
3027
+ // Keep both diagnostics and the unmodified upstream phrase quad gate.
3028
+ // See conformance-report.md and issue #94.
3025
3029
  if (!isListOrPartialList(input, env)) {
3026
3030
  throw new PrologError('type_error(list)', deref(input, env));
3027
3031
  }
@@ -445,6 +445,15 @@ export function directGoalDependencyKey(goal) {
445
445
  return `${goal.name}/${goal.arity}`;
446
446
  }
447
447
 
448
+ // ISO 13211-1, 7.1.6.3: `V1^V2^...^Goal` has the iterated goal Goal.
449
+ export function iteratedGoal(goal) {
450
+ let current = goal;
451
+ while (current?.type === COMPOUND && current.name === '^' && current.arity === 2) {
452
+ current = current.args[1];
453
+ }
454
+ return current;
455
+ }
456
+
448
457
  export function collectGoalDependencies(goal, negated, traverseConditionals = false, wfs = false) {
449
458
  if (goal.type === ATOM) return [{ key: `${goal.name}/0`, name: goal.name, arity: 0, module: goal.module, negative: negated, wfs }];
450
459
  if (goal.type !== COMPOUND) return [];
@@ -478,6 +487,11 @@ export function collectGoalDependencies(goal, negated, traverseConditionals = fa
478
487
  if ((goal.name === 'findall' || goal.name === 'sumall') && goal.arity === 3) {
479
488
  return collectGoalDependencies(goal.args[1], negated, traverseConditionals, wfs);
480
489
  }
490
+ if ((goal.name === 'bagof' || goal.name === 'setof') && goal.arity === 3) {
491
+ // The goal argument is an iterated-goal term (ISO 13211-1, 7.1.6.3), so
492
+ // strip its `^` qualifiers before recording the call it really performs.
493
+ return collectGoalDependencies(iteratedGoal(goal.args[1]), negated, traverseConditionals, wfs);
494
+ }
481
495
  if (goal.name === 'countall' && goal.arity === 2) {
482
496
  return collectGoalDependencies(goal.args[0], negated, traverseConditionals, wfs);
483
497
  }
package/src/program.js CHANGED
@@ -1136,6 +1136,18 @@ function collectForwardRulePremiseDependencies(term, collect, out) {
1136
1136
  // intentionally looks through those wrappers; the autoloader must see both the
1137
1137
  // wrapper and any statically visible nested calls so a bundled library can be
1138
1138
  // loaded before execution reaches it.
1139
+ // ISO 13211-1, 7.1.6.3: the iterated goal of `V1^V2^...^Goal` is Goal. The
1140
+ // qualifiers only mark witness variables as existentially quantified, so every
1141
+ // static analysis of a bagof/3 or setof/3 goal argument has to look through
1142
+ // them before it reaches the callable term.
1143
+ function iteratedGoalTerm(goal) {
1144
+ let current = goal;
1145
+ while (current?.type === COMPOUND && current.name === '^' && current.arity === 2) {
1146
+ current = current.args[1];
1147
+ }
1148
+ return current;
1149
+ }
1150
+
1139
1151
  function collectAutoloadGoalDependencies(goal, out = []) {
1140
1152
  if (goal?.type === ATOM) {
1141
1153
  out.push({ key: `${goal.name}/0`, name: goal.name, arity: 0, module: goal.module });
@@ -1167,7 +1179,12 @@ function collectAutoloadGoalDependencies(goal, out = []) {
1167
1179
  collectAutoloadGoalDependencies(goal.args[0], out);
1168
1180
  collectAutoloadGoalDependencies(goal.args[1], out);
1169
1181
  } else if ((goal.name === 'findall' || goal.name === 'bagof' || goal.name === 'setof' || goal.name === 'sumall') && goal.arity === 3) {
1170
- collectAutoloadGoalDependencies(goal.args[1], out);
1182
+ // bagof/3 and setof/3 take an iterated-goal term (ISO 13211-1, 7.1.6.3):
1183
+ // any number of `Witness^Goal` qualifiers wrap the goal that is actually
1184
+ // executed. Autoloading has to see through them, otherwise
1185
+ // setof(X, Y^member(X,L), S) raises existence_error(member/2) while the
1186
+ // unqualified setof(X, member(X,L), S) resolves normally.
1187
+ collectAutoloadGoalDependencies(iteratedGoalTerm(goal.args[1]), out);
1171
1188
  } else if (goal.name === 'countall' && goal.arity === 2) {
1172
1189
  collectAutoloadGoalDependencies(goal.args[0], out);
1173
1190
  } else if ((goal.name === 'aggregate_min' || goal.name === 'aggregate_max') && goal.arity === 5) {
@@ -140,7 +140,7 @@ Selected cases are adapted from the ISO and standard-core suites of Logtalk,
140
140
  Scryer Prolog, Trealla Prolog, and SWI-Prolog. Their upstream identifiers and licenses
141
141
  are recorded in [THIRD_PARTY.md](THIRD_PARTY.md).
142
142
 
143
- The corpus has 386 cases in `iso/` and 802 file-based conformance cases in total. Of those, 11 cases in `stc/` are explicitly labelled working-draft review evidence rather than normative ISO claims. The separate vendored strict-reader WG17 matrix is a deterministic regression snapshot; the live Neumerkel gate discovers the current upstream inventory at run time; release/report checks separately verify the tracked `NEUMERKEL-LATEST.md`. The generated `conformance-report.md` records local corpus totals and links to that live evidence. Together with regression, documentation-sync, API, example, and book-example checks, `npm test` is the release gate.
143
+ The corpus has 388 cases in `iso/` and 805 file-based conformance cases in total. Of those, 11 cases in `stc/` are explicitly labelled working-draft review evidence rather than normative ISO claims. The separate vendored strict-reader WG17 matrix is a deterministic regression snapshot; the live Neumerkel gate discovers the current upstream inventory at run time; release/report checks separately verify the tracked `NEUMERKEL-LATEST.md`. The generated `conformance-report.md` records local corpus totals and links to that live evidence. Together with regression, documentation-sync, API, example, and book-example checks, `npm test` is the release gate.
144
144
 
145
145
  ## Updating expected output
146
146
 
@@ -0,0 +1,12 @@
1
+ % ISO/IEC 13211-1:1995, 7.1.6.3 (iterated-goal term) with 8.10.2/8.10.3.
2
+ % The goal argument of bagof/3 and setof/3 is `V1^...^Goal`, so every static
3
+ % analysis has to look through the `^` qualifiers to reach the callable term.
4
+ % Regression: the library autoloader used to stop at `^/2`, which made
5
+ % setof(X, Y^member(X,L), S) raise existence_error(member/2) while the
6
+ % unqualified setof(X, member(X,L), S) resolved normally.
7
+ answer(plain, S) :- setof(X, member(X, [b,a,b]), S).
8
+ answer(qualified, S) :- setof(X, Y^member(X, [b,a,b]), S).
9
+ answer(qualified_bag, S) :- bagof(X, Y^member(X, [b,a,b]), S).
10
+ answer(nested, S) :- setof(X, Y^Z^member(X-Y-Z, [1-a-p, 2-b-q]), S).
11
+ answer(witness_kept, S) :- setof(X-Y, Y^member(X-Y, [2-b, 1-a]), S).
12
+ %% goal: answer(X0, X1)
@@ -0,0 +1,34 @@
1
+ % ISO/IEC TS 13211-3:2025 grammar-body semantics regressions.
2
+ digit(D) --> [D], { member(D, [a,b]) }.
3
+
4
+ % phrase([], S0, S) is true iff S0 = S.
5
+ answer(empty_sequence, S) :- phrase([], [a,b], S).
6
+
7
+ % phrase((A,B), S0, S) iff phrase(A, S0, S1), phrase(B, S1, S).
8
+ answer(concatenation, S) :- phrase(([a], [b]), [a,b,c], S).
9
+
10
+ % phrase((A;B), S0, S) iff ( phrase(A,S0,S) ; phrase(B,S0,S) ).
11
+ answer(alternative, S) :- phrase(([a] ; [b]), [b,c], S).
12
+
13
+ % if-then-else regression examples.
14
+ answer(ite_13, yes) :- phrase((("1" | "2") -> "3" ; "4"), "13").
15
+ answer(ite_23, yes) :- phrase((("1" | "2") -> "3" ; "4"), "23").
16
+ answer(ite_4, yes) :- phrase((("1" | "2") -> "3" ; "4"), "4").
17
+ answer(ite_single, no) :- \+ phrase((("1" | "2") -> "3" ; "4"), [_]).
18
+
19
+ % phrase({G}, S0, S) is true iff ( G, S0 = S ).
20
+ answer(grammar_body_goal, S) :- phrase({true}, [a], S).
21
+
22
+ % phrase(phrase(NT), S0, S) iff phrase(NT, S0, S).
23
+ answer(phrase_nonterminal, S) :- phrase(phrase([a]), [a,b], S).
24
+
25
+ % phrase(!, S0, S) is true iff S0 = S.
26
+ answer(cut_identity, S) :- phrase(!, [a,b], S).
27
+
28
+ % phrase(\+ GRBody, S0, S) iff ( \+ phrase(GRBody, S0, _), S0 = S ).
29
+ answer(negation_identity, S) :- phrase(\+ [a], [b,c], S).
30
+ answer(negation_blocks, no) :- \+ phrase(\+ [a], [a], _).
31
+
32
+ % ('.')//2 separates a terminal from the rest of the sequence.
33
+ answer(terminal_split, D) :- phrase(digit(D), [b]).
34
+ %% goal: answer(X0, X1)
@@ -0,0 +1,41 @@
1
+ % ISO/IEC TS 13211-3:2025 phrase/2 and phrase/3 regression examples.
2
+ determiner --> [the].
3
+ determiner --> [a].
4
+
5
+ noun --> [boy].
6
+ noun --> [girl].
7
+
8
+ verb --> [likes].
9
+ verb --> [scares].
10
+
11
+ noun_phrase --> determiner, noun.
12
+ noun_phrase --> noun.
13
+
14
+ verb_phrase --> verb.
15
+ verb_phrase --> verb, noun_phrase.
16
+
17
+ sentence --> noun_phrase, verb_phrase.
18
+
19
+ % ?- phrase([the], [the]). true.
20
+ answer(terminal_only, yes) :- phrase([the], [the]).
21
+
22
+ % ?- phrase(sentence, [the, girl, likes, the, boy]). true ; false.
23
+ answer(full_sentence, yes) :- phrase(sentence, [the, girl, likes, the, boy]).
24
+
25
+ % ?- phrase(sentence, [the, girl, likes, the, boy, today]). false.
26
+ answer(trailing_word, no) :- \+ phrase(sentence, [the, girl, likes, the, boy, today]).
27
+
28
+ % ?- phrase(sentence, [the, girl, likes]). true ; false.
29
+ answer(intransitive, yes) :- phrase(sentence, [the, girl, likes]).
30
+
31
+ % ?- phrase(noun_phrase, [the, girl, scares, the, boy], Rest).
32
+ % Rest = [scares, the, boy] ; false.
33
+ answer(remainder, Rest) :- phrase(noun_phrase, [the, girl, scares, the, boy], Rest).
34
+
35
+ % ?- phrase(sentence, Sentence). Sentence = [the, boy, likes] ; ... .
36
+ answer(first_generated, Sentence) :-
37
+ findall(S, phrase(sentence, S), All),
38
+ first_four(All, Sentence).
39
+
40
+ first_four([A,B,C,D|_], [A,B,C,D]).
41
+ %% goal: answer(X0, X1)
@@ -0,0 +1,5 @@
1
+ answer(plain, "ab").
2
+ answer(qualified, "ab").
3
+ answer(qualified_bag, "bab").
4
+ answer(nested, [1, 2]).
5
+ answer(witness_kept, [1 - a, 2 - b]).
@@ -0,0 +1,13 @@
1
+ answer(empty_sequence, "ab").
2
+ answer(concatenation, "c").
3
+ answer(alternative, "c").
4
+ answer(ite_13, yes).
5
+ answer(ite_23, yes).
6
+ answer(ite_4, yes).
7
+ answer(ite_single, no).
8
+ answer(grammar_body_goal, "a").
9
+ answer(phrase_nonterminal, "b").
10
+ answer(cut_identity, "ab").
11
+ answer(negation_identity, "bc").
12
+ answer(negation_blocks, no).
13
+ answer(terminal_split, b).
@@ -0,0 +1,6 @@
1
+ answer(terminal_only, yes).
2
+ answer(full_sentence, yes).
3
+ answer(trailing_word, no).
4
+ answer(intransitive, yes).
5
+ answer(remainder, [scares, the, boy]).
6
+ answer(first_generated, [[the, boy, likes], [the, boy, scares], [the, boy, likes, the, boy], [the, boy, likes, the, girl]]).
@@ -119,6 +119,8 @@ export function formatConformanceReport(report = buildConformanceReport()) {
119
119
  }
120
120
  lines.push(`| **Total** | **${report.total.positive}** | **${report.total.errors}** | **${report.total.warnings}** | **${report.total.proofs}** | **${report.total.total}** |`);
121
121
 
122
+ lines.push(...dcgConformanceSection());
123
+
122
124
  if (report.corpusIssues.length > 0) {
123
125
  lines.push('', '## Corpus issues', '');
124
126
  for (const issue of report.corpusIssues) lines.push(`- ${issue}`);
@@ -131,6 +133,31 @@ export function formatConformanceReport(report = buildConformanceReport()) {
131
133
  return `${lines.join('\n')}\n`;
132
134
  }
133
135
 
136
+ // Keep the standards clarification in the generated report.
137
+ function dcgConformanceSection() {
138
+ return [
139
+ '',
140
+ '## DCG conformance clarification',
141
+ '',
142
+ 'EyeProlog checks the input and remainder of `phrase/2-3` and reports',
143
+ '`type_error(list, S)` when an argument is neither a list nor a partial list.',
144
+ 'These implementation-defined checks follow ISO/IEC TS 13211-3:2025,',
145
+ '8.18.1.3 g and h; this behavior is not a known deviation.',
146
+ '',
147
+ 'The Part 3 implementation target is',
148
+ '[ISO/IEC TS 13211-3:2025](https://www.iso.org/standard/83635.html).',
149
+ 'The public [error subclause](https://www.complang.tuwien.ac.at/ulrich/iso-prolog/draft-8.18.1.3)',
150
+ 'also specifies `list` for these checks.',
151
+ '',
152
+ 'The [phrase comparison](https://www.complang.tuwien.ac.at/ulrich/iso-prolog/phrase)',
153
+ 'links the machine-readable `phrase_quad.pl` corpus. `npm test` fetches and',
154
+ 'runs that corpus live through the Neumerkel gate; the offline regression',
155
+ 'suite runs all 58 vendored quads. Neither expectations nor error matching',
156
+ 'are relaxed for quads 41-44.',
157
+ '',
158
+ ];
159
+ }
160
+
134
161
  function executeGate(name, runSuite) {
135
162
  const failures = [];
136
163
  const reporter = {
@@ -54,6 +54,7 @@ import {
54
54
  import { ISO_OPERATOR_DEFINITIONS, parseGoalText, parseNumberTokenText, tryParseClausesFastInto } from '../src/parser.js';
55
55
  import { PrologError, formalErrorTerm } from '../src/iso.js';
56
56
  import { compareTerms } from '../src/term.js';
57
+ import { defaultsToStdin } from '../src/cli.js';
57
58
  import { formatTermForWrite } from '../src/write.js';
58
59
  import { selectClauseCandidates } from '../src/program.js';
59
60
  import {
@@ -1596,6 +1597,25 @@ c4 ?- call((!;1)).
1596
1597
  assertEqual(result.stdout, '250000', 'number_chars/read cross-check count');
1597
1598
  },
1598
1599
  },
1600
+ {
1601
+ name: '--goal without an input file does not block on an interactive stdin',
1602
+ run: () => {
1603
+ // Usage documents the input file as optional. The CLI used to append
1604
+ // '-' unconditionally, so `eyeprolog --goal G` on a terminal waited for
1605
+ // an EOF that never came and appeared to do nothing. stdin is only a
1606
+ // default source when it is actually redirected.
1607
+ assertEqual(defaultsToStdin(0, true), false, 'tty stdin, no file');
1608
+ assertEqual(defaultsToStdin(0, false), true, 'redirected stdin, no file');
1609
+ assertEqual(defaultsToStdin(0, undefined), true, 'non-tty stdin reported as undefined');
1610
+ assertEqual(defaultsToStdin(1, true), false, 'tty stdin, one file');
1611
+ assertEqual(defaultsToStdin(1, false), false, 'redirected stdin, one file');
1612
+
1613
+ // An empty database is still a usable program for --goal.
1614
+ const result = runCli(['--goal', 'X = 1, write(X), nl'], { input: '' });
1615
+ assertEqual(result.status, 0, 'exit status');
1616
+ assertIncludes(result.stdout, '1\n', 'goal output without an input file');
1617
+ },
1618
+ },
1599
1619
  {
1600
1620
  name: 'float literals canonicalize to their represented value and mix with generated floats (issue #92)',
1601
1621
  run: () => {
@@ -10353,7 +10353,7 @@ must preserve the same observable outcome. Additional normal-mode syntax may
10353
10353
  accept texts outside the strict grammar, but it may not reinterpret an accepted
10354
10354
  standard case.
10355
10355
 
10356
- The file-based conformance corpus contains 802 cases, including 386 focused ISO cases derived from the success, failure, mode, and error behavior in ISO/IEC 13211-1 clauses 7 and 8, Part 2 modules, and Part 3 grammar rules.
10356
+ The file-based conformance corpus contains 805 cases, including 388 focused ISO cases derived from the success, failure, mode, and error behavior in ISO/IEC 13211-1 clauses 7 and 8, Part 2 modules, and Part 3 grammar rules.
10357
10357
  Separate exact-output suites check 210 normal examples and 61 proof examples; all executable chapter programs are parsed and their declared goals are executed. The eight-case
10358
10358
  playground contract suite imports the production worker, sends real reasoning
10359
10359
  requests through its message protocol, and crawls the served module graph for