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.
- package/examples/access-control-policy.pl +2 -0
- package/examples/d3-group.pl +2 -0
- package/examples/fast-fourier-transform.pl +1 -0
- package/examples/map-four-color-search.pl +2 -0
- package/examples/partial-evaluator.pl +2 -0
- package/examples/weighted-interval-scheduling.pl +2 -0
- package/package.json +1 -1
- package/src/repl.js +83 -8
- package/src/solver.js +1 -1
- package/test/conformance/ISO-COMPLIANCE.md +1 -1
- package/test/conformance/ISO-MATRIX.md +3 -3
- package/test/conformance/cases/aggregation/057_countall_empty_and_nonempty.pl +2 -0
- package/test/conformance/cases/aggregation/075_aggregation_edges.pl +2 -0
- package/test/conformance/cases/aggregation/aggregate_max_no_answers_fails.pl +2 -0
- package/test/conformance/cases/aggregation/countall_zero.pl +2 -0
- package/test/conformance/cases/aggregation/findall_empty_bag.pl +2 -0
- package/test/conformance/cases/aggregation/sumall_empty_zero.pl +2 -0
- package/test/conformance/cases/control/074_forall_edges.pl +2 -0
- package/test/conformance/cases/control/extra_not_unbound_unknown_goal_succeeds.pl +2 -0
- package/test/conformance/cases/control/extra_once_empty_fails.pl +2 -0
- package/test/conformance/cases/control/forall_empty_generator_true.pl +2 -0
- package/test/conformance/cases/negation/not_unknown_goal_succeeds.pl +2 -0
- package/test/conformance/warnings/negation/extra_stratified_multiple_negations_quiet.pl +2 -0
- package/test/conformance/warnings/negation/extra_warning_preserves_empty_stdout.pl +2 -0
- package/test/conformance/warnings/negation/negative_unknown_group_quiet.pl +2 -0
- package/test/conformance/warnings/negation/stratified_negative_unknown_after_positive_quiet.pl +2 -0
- package/test/conformance/warnings/negation/stratified_quiet.pl +2 -0
- package/test/run-regression.mjs +71 -1
- package/the-art-of-eyeprolog.md +29 -11
package/examples/d3-group.pl
CHANGED
|
@@ -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
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
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
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(
|
|
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.
|
|
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
|
|
35
|
-
|
|
36
|
-
|
|
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
|
package/test/run-regression.mjs
CHANGED
|
@@ -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,
|
|
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: () => {
|
package/the-art-of-eyeprolog.md
CHANGED
|
@@ -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` | `
|
|
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
|
-
|
|
5675
|
-
|
|
5676
|
-
|
|
5677
|
-
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
|
|
5681
|
-
|
|
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
|
-
|
|
6220
|
-
|
|
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
|
|