eyeprolog 1.5.57 → 1.5.58
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/package.json +1 -1
- package/src/errors.js +26 -1
- package/src/iso.js +13 -3
- package/src/solver.js +9 -2
- package/test/conformance/ISO-IMPLEMENTATION-DEFINED.md +1 -1
- package/test/conformance/expected/iso/exceptions_and_flags.pl +2 -2
- package/test/regression/cases-regression.mjs +7 -7
- package/test/run-iso-strict.mjs +10 -10
package/package.json
CHANGED
package/src/errors.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Keep these independent of the ISO builtin registry so syntax, DCG, program,
|
|
3
3
|
// and solver layers can report Prolog errors without importing the whole ISO
|
|
4
4
|
// implementation (and without creating semantic-layer import cycles).
|
|
5
|
-
import { termToString } from './term.js';
|
|
5
|
+
import { atom, compound, numberTerm, termToString } from './term.js';
|
|
6
6
|
|
|
7
7
|
export class PrologError extends Error {
|
|
8
8
|
constructor(formal, culprit = null) {
|
|
@@ -21,3 +21,28 @@ export class HaltSignal extends Error {
|
|
|
21
21
|
this.code = code;
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
|
+
|
|
25
|
+
// ISO 7.12.2 leaves the second argument of error/2 implementation defined.
|
|
26
|
+
// Reporting which built-in raised the error is far more useful than a constant,
|
|
27
|
+
// so built-in call sites attach the predicate indicator when the raising code
|
|
28
|
+
// did not supply a context of its own. The indicator term is built once per
|
|
29
|
+
// registry entry and reused, so the error path allocates nothing extra.
|
|
30
|
+
function builtinErrorContext(def, goal) {
|
|
31
|
+
let context = def._errorContextTerm;
|
|
32
|
+
if (context === undefined) {
|
|
33
|
+
context = compound('/', [atom(goal.name), numberTerm(goal.arity)]);
|
|
34
|
+
def._errorContextTerm = context;
|
|
35
|
+
}
|
|
36
|
+
return context;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Shared, pre-built error instances are thrown many times from different
|
|
40
|
+
// built-ins, so tagging one with a context would leak the first thrower's
|
|
41
|
+
// indicator into every later report. They keep the default context, which also
|
|
42
|
+
// preserves the ground-error-term cache in formalErrorTerm.
|
|
43
|
+
export function attachBuiltinErrorContext(error, def, goal) {
|
|
44
|
+
if (!(error instanceof PrologError)) return error;
|
|
45
|
+
if (error.contextTerm != null || error._sharedInstance === true) return error;
|
|
46
|
+
error.contextTerm = builtinErrorContext(def, goal);
|
|
47
|
+
return error;
|
|
48
|
+
}
|
package/src/iso.js
CHANGED
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
let isoFresh = 0;
|
|
23
23
|
|
|
24
24
|
export { PrologError, HaltSignal } from './errors.js';
|
|
25
|
-
import { PrologError,
|
|
25
|
+
import { HaltSignal, PrologError, attachBuiltinErrorContext } from './errors.js';
|
|
26
26
|
|
|
27
27
|
class ThrownTerm extends Error {
|
|
28
28
|
constructor(term) {
|
|
@@ -2452,6 +2452,7 @@ function numberListText(list, env, kind, valueIsBound, solver = null) {
|
|
|
2452
2452
|
}
|
|
2453
2453
|
|
|
2454
2454
|
const numberSyntaxError = new PrologError('syntax_error(number)');
|
|
2455
|
+
numberSyntaxError._sharedInstance = true;
|
|
2455
2456
|
|
|
2456
2457
|
function numberListBuiltin(kind) {
|
|
2457
2458
|
return function* ({ solver, goal, env }) {
|
|
@@ -3161,8 +3162,17 @@ function* catchSolutions({ solver, goal, env }, state) {
|
|
|
3161
3162
|
// isolated in a child solver. Running it directly avoids constructing a
|
|
3162
3163
|
// complete Solver for hot caught failures such as number_chars/2 syntax
|
|
3163
3164
|
// probes, while the cloned environment keeps catch/3's rollback boundary.
|
|
3164
|
-
|
|
3165
|
-
|
|
3165
|
+
let iterator;
|
|
3166
|
+
let result;
|
|
3167
|
+
try {
|
|
3168
|
+
iterator = direct.handler({ solver, goal: invoked, env: env.clone() });
|
|
3169
|
+
result = iterator.next();
|
|
3170
|
+
} catch (caught) {
|
|
3171
|
+
// This fast path bypasses the solver's builtin frame, so it has to
|
|
3172
|
+
// attach the raising predicate's indicator itself (see
|
|
3173
|
+
// attachBuiltinErrorContext in solver.js).
|
|
3174
|
+
throw attachBuiltinErrorContext(caught, direct, invoked);
|
|
3175
|
+
}
|
|
3166
3176
|
if (result.done) solver.stats.deterministic_builtin_failures++;
|
|
3167
3177
|
else {
|
|
3168
3178
|
solver.stats.deterministic_builtin_successes++;
|
package/src/solver.js
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
numberTerm, numberTextFromDouble, properListItems, termIsGround, termToString, unify, variable, variantTerms,
|
|
7
7
|
} from './term.js';
|
|
8
8
|
import { numberValueKey, sameNumberValue } from './number-value.js';
|
|
9
|
+
import { attachBuiltinErrorContext } from './errors.js';
|
|
9
10
|
import { PrologError, getStrictIsoRegistry } from './iso.js';
|
|
10
11
|
import { getEyePrologRegistry } from './standard-library.js';
|
|
11
12
|
import { selectClauseCandidates, selectClauseCandidatesForValues, selectGroundClauseCandidates } from './program-indexing.js';
|
|
@@ -757,8 +758,14 @@ export class Solver {
|
|
|
757
758
|
if (builtinReady) {
|
|
758
759
|
const deterministic = def.deterministic ||
|
|
759
760
|
def.deterministicWhen?.({ solver: this, goal, env }) === true;
|
|
760
|
-
|
|
761
|
-
|
|
761
|
+
let iterator;
|
|
762
|
+
let firstResult;
|
|
763
|
+
try {
|
|
764
|
+
iterator = def.handler({ solver: this, goal, env });
|
|
765
|
+
firstResult = iterator.next();
|
|
766
|
+
} catch (caught) {
|
|
767
|
+
throw attachBuiltinErrorContext(caught, def, goal);
|
|
768
|
+
}
|
|
762
769
|
if (deterministic) {
|
|
763
770
|
if (!firstResult.done) this.stats.deterministic_builtin_successes++;
|
|
764
771
|
else this.stats.deterministic_builtin_failures++;
|
|
@@ -67,7 +67,7 @@ Status values are:
|
|
|
67
67
|
| 7.11.2.2 | Effect when `debug=on` | The flag is accepted and stored; it does not change goal semantics or enable a debugger. | **defined** — `src/solver.js`; no semantic branch depends on `debug`. |
|
|
68
68
|
| 7.11.2.3 | Default `max_arity` | `unbounded`: EyeProlog imposes no fixed semantic ceiling on compound-term arity. Practical host allocation exhaustion is a resource condition. This flag is distinct from any potential implementation-specific procedure-arity limit; EyeProlog currently declares no separate finite procedure limit. | **defined** — `src/iso-limits.js`, `src/solver.js`, `src/parser.js`, `src/iso.js`; strict regression coverage. |
|
|
69
69
|
| 7.11.2.5 | Default `double_quotes` | `chars`. | **defined** — `src/solver.js`, parser flag state. |
|
|
70
|
-
| 7.12.1 | Second argument of `error/2` |
|
|
70
|
+
| 7.12.1 | Second argument of `error/2` | Built-in predicates report the predicate indicator of the predicate that raised the error, for example `error(type_error(atom, 1.0), atom_length/2)`. Errors raised outside a built-in frame, and errors thrown from shared pre-built instances that several built-ins reuse, fall back to the atom `eyeprolog`. A few implementation-specific diagnostics deliberately supply their own context term. | **defined** — `attachBuiltinErrorContext()` in `src/errors.js`, `formalErrorTerm()` in `src/iso.js`. |
|
|
71
71
|
| 7.12.2(f) | Implementation-defined representation limits | Character and character-code operations are limited to Unicode scalar values; surrogates and values above U+10FFFF are representation errors. Arity/integer values are modeled as unbounded but may hit host/resource limits. Float input overflow uses the implementation-specific `max_float`/`min_float` representation names documented by the STC-oriented tests. | **defined** — parser/ISO numeric and character guards. |
|
|
72
72
|
| 8.17.1 | Implementation-defined flag value ranges | Strict mode exposes only Part 1 core flags and their standard value sets. Normal mode additionally exposes EyeProlog's `occurs_check` and `default_procedure_access` flags. With `bounded=false`, `max_integer` and `min_integer` have no current or selectable value and their `current_prolog_flag/2` queries fail. Valid alternative values of fixed standard flags are distinguished from invalid values so `set_prolog_flag/2` reports permission versus domain errors as prescribed. | **defined** — strict registry/flag filtering in `src/solver.js`; strict flag tests. |
|
|
73
73
|
| 8.17.3 | Other effects of `halt/0` | Terminates EyeProlog execution and returns host/process status `0`; it produces no Prolog solution. | **defined** — `HaltSignal`, `haltBuiltin()`, CLI/runner handling. |
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
answer(7, red, caught(
|
|
2
|
-
answer(7, red, caught(
|
|
1
|
+
answer(7, red, caught(atom_length / 2), first, caught(eyeprolog)).
|
|
2
|
+
answer(7, red, caught(atom_length / 2), second, caught(eyeprolog)).
|
|
3
3
|
flags(off, on, [pair(bounded, false), pair(integer_rounding_function, toward_zero), pair(char_conversion, on), pair(debug, off), pair(max_arity, unbounded), pair(unknown, fail), pair(double_quotes, chars), pair(occurs_check, true), pair(default_procedure_access, private)]).
|
|
@@ -1891,7 +1891,7 @@ c4 ?- call((!;1)).
|
|
|
1891
1891
|
assertIncludes(repl.stdout, 'T = ./*. .', 'REPL dotted graphic atom answer');
|
|
1892
1892
|
assertNotIncludes(repl.stdout, "T = './*.'", 'REPL dotted graphic atom has no spurious quotes');
|
|
1893
1893
|
assertIncludes(repl.stdout, 'T = ok.', 'REPL following read answer');
|
|
1894
|
-
assertIncludes(repl.stdout, 'error(syntax_error(read_term),
|
|
1894
|
+
assertIncludes(repl.stdout, 'error(syntax_error(read_term), read / 1)', 'REPL syntax error');
|
|
1895
1895
|
assertEqual(repl.stderr, '', 'REPL stderr');
|
|
1896
1896
|
|
|
1897
1897
|
const continuedGraphic = runCli([], {
|
|
@@ -2719,8 +2719,8 @@ c4 ?- call((!;1)).
|
|
|
2719
2719
|
});
|
|
2720
2720
|
assertEqual(result.status, 0, 'exit status');
|
|
2721
2721
|
assertEqual(result.stdout,
|
|
2722
|
-
'?- error(type_error(list, [1, [], _A | 2]),
|
|
2723
|
-
'?- error(type_error(list, [1, [], _A | 2]),
|
|
2722
|
+
'?- error(type_error(list, [1, [], _A | 2]), number_chars / 2).\n' +
|
|
2723
|
+
'?- error(type_error(list, [1, [], _A | 2]), number_chars / 2).\n' +
|
|
2724
2724
|
'?- ',
|
|
2725
2725
|
'stdout');
|
|
2726
2726
|
assertEqual(result.stderr, '', 'stderr');
|
|
@@ -2734,8 +2734,8 @@ c4 ?- call((!;1)).
|
|
|
2734
2734
|
});
|
|
2735
2735
|
assertEqual(result.status, 0, 'exit status');
|
|
2736
2736
|
assertEqual(result.stdout,
|
|
2737
|
-
'?- error(instantiation_error,
|
|
2738
|
-
'?- Error = instantiation_error, Imp_def =
|
|
2737
|
+
'?- error(instantiation_error, (is) / 2).\n' +
|
|
2738
|
+
'?- Error = instantiation_error, Imp_def = (is)/2.\n' +
|
|
2739
2739
|
'?- ',
|
|
2740
2740
|
'stdout');
|
|
2741
2741
|
assertEqual(result.stderr, '', 'stderr');
|
|
@@ -4044,7 +4044,7 @@ child.stdin.write(\`consult(${consultedAtom}).\\n\`);
|
|
|
4044
4044
|
const result = runCli([], { input: 'statistics(nonsense, Value).\nhalt.\n' });
|
|
4045
4045
|
assertEqual(result.status, 0, 'exit status');
|
|
4046
4046
|
assertIncludes(result.stdout,
|
|
4047
|
-
'error(domain_error(statistics_key, nonsense),
|
|
4047
|
+
'error(domain_error(statistics_key, nonsense), statistics / 2).',
|
|
4048
4048
|
'statistics key error');
|
|
4049
4049
|
assertEqual(result.stderr, '', 'stderr');
|
|
4050
4050
|
},
|
|
@@ -5082,7 +5082,7 @@ answer(Result) :- countdown(2048, Result), Result = 2048.
|
|
|
5082
5082
|
goal: 'answer(T)',
|
|
5083
5083
|
ioOptions: { input: invalidOctal },
|
|
5084
5084
|
}).stdout,
|
|
5085
|
-
'answer(error(syntax_error(read_term),
|
|
5085
|
+
'answer(error(syntax_error(read_term), read / 1)).\n',
|
|
5086
5086
|
'read/1 rejects non-octal numeric escape',
|
|
5087
5087
|
);
|
|
5088
5088
|
},
|
package/test/run-iso-strict.mjs
CHANGED
|
@@ -649,7 +649,7 @@ export function runIsoStrict(reporter = new TestReporter()) {
|
|
|
649
649
|
'close(tmp_in)',
|
|
650
650
|
'current_input(C)',
|
|
651
651
|
'stream_property(C,alias(user_input))',
|
|
652
|
-
'catch(set_input(tmp_in),error(existence_error(stream,tmp_in),
|
|
652
|
+
'catch(set_input(tmp_in),error(existence_error(stream,tmp_in),set_input/1),true)',
|
|
653
653
|
].join(','),
|
|
654
654
|
}).stats.completed_goal_lists, 1, 'stream-term, alias lifetime, target/current input, and close fallback');
|
|
655
655
|
|
|
@@ -856,14 +856,14 @@ export function runIsoStrict(reporter = new TestReporter()) {
|
|
|
856
856
|
|
|
857
857
|
reporter.test('closes the ISO 7.12 processor error envelope and classification rows', () => {
|
|
858
858
|
const caught = [
|
|
859
|
-
"catch(atom_length(X,N),error(instantiation_error,
|
|
860
|
-
"catch(atom_length(1,N),error(type_error(atom,1),
|
|
861
|
-
"catch(op(1300,xfx,foo),error(domain_error(operator_priority,1300),
|
|
859
|
+
"catch(atom_length(X,N),error(instantiation_error,atom_length/2),true)",
|
|
860
|
+
"catch(atom_length(1,N),error(type_error(atom,1),atom_length/2),true)",
|
|
861
|
+
"catch(op(1300,xfx,foo),error(domain_error(operator_priority,1300),op/3),true)",
|
|
862
862
|
"catch(call(no_such_predicate),error(existence_error(procedure,no_such_predicate/0),eyeprolog),true)",
|
|
863
|
-
"catch(abolish(atom/1),error(permission_error(modify,static_procedure,atom/1),
|
|
864
|
-
"catch(char_code(C,1114112),error(representation_error(character_code),
|
|
865
|
-
"catch(X is 1/0,error(evaluation_error(zero_divisor),
|
|
866
|
-
"catch(X is 1<<4294967296,error(resource_error(memory),
|
|
863
|
+
"catch(abolish(atom/1),error(permission_error(modify,static_procedure,atom/1),abolish/1),true)",
|
|
864
|
+
"catch(char_code(C,1114112),error(representation_error(character_code),char_code/2),true)",
|
|
865
|
+
"catch(X is 1/0,error(evaluation_error(zero_divisor),(is)/2),true)",
|
|
866
|
+
"catch(X is 1<<4294967296,error(resource_error(memory),(is)/2),true)",
|
|
867
867
|
];
|
|
868
868
|
for (const goal of caught) {
|
|
869
869
|
equal(run('', { isoStrict: true, goal }).stats.completed_goal_lists, 1, goal);
|
|
@@ -871,14 +871,14 @@ export function runIsoStrict(reporter = new TestReporter()) {
|
|
|
871
871
|
|
|
872
872
|
equal(run('', {
|
|
873
873
|
isoStrict: true,
|
|
874
|
-
goal: "catch(read_term(T,[]),error(syntax_error(read_term),
|
|
874
|
+
goal: "catch(read_term(T,[]),error(syntax_error(read_term),read_term/2),true)",
|
|
875
875
|
ioOptions: { input: "'unterminated." },
|
|
876
876
|
}).stats.completed_goal_lists, 1, 'syntax error uses error/2 envelope and implementation-defined context');
|
|
877
877
|
|
|
878
878
|
const systemSolver = new Solver(Program.parse('', { isoStrict: true }), { isoStrict: true });
|
|
879
879
|
systemSolver.io.flush = () => { throw new Error('simulated host I/O failure'); };
|
|
880
880
|
equal([...systemSolver.solve([
|
|
881
|
-
parseGoalText('catch(flush_output(user_output),error(system_error,
|
|
881
|
+
parseGoalText('catch(flush_output(user_output),error(system_error,flush_output/1),true)', { isoStrict: true }),
|
|
882
882
|
], new Env(), 0)].length, 1, 'system error uses error/2 envelope and implementation-defined context');
|
|
883
883
|
|
|
884
884
|
// ISO 7.12 deliberately leaves the choice implementation-dependent when
|