eyeprolog 1.3.35 → 1.3.37
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/README.md +6 -0
- package/index.d.ts +14 -2
- package/package.json +3 -2
- package/src/ARCHITECTURE.md +44 -0
- package/src/cli.js +1 -0
- package/src/dcg.js +1 -1
- package/src/errors.js +23 -0
- package/src/iso-arithmetic.js +166 -0
- package/src/iso.js +4 -176
- package/src/program-analysis.js +515 -0
- package/src/program-indexing.js +378 -0
- package/src/program.js +8 -873
- package/src/quads.js +208 -27
- package/test/conformance/ISO-IMPLEMENTATION-DEFINED.md +8 -8
- package/test/run-all.mjs +2 -0
- package/test/run-architecture.mjs +82 -0
- package/test/run-regression.mjs +47 -0
- package/the-art-of-eyeprolog.md +48 -23
- package/why-eyeprolog.md +9 -0
package/README.md
CHANGED
|
@@ -312,4 +312,10 @@ The GitHub test workflow runs the complete suite and an npm package dry-run on
|
|
|
312
312
|
both the minimum supported Node.js 18 release line and Node.js 24. Publishing
|
|
313
313
|
repeats those release checks before uploading the package.
|
|
314
314
|
|
|
315
|
+
The runtime JavaScript modules stay flat under `src/`; the existing `src/lib/`
|
|
316
|
+
directory contains the portable Prolog library modules. See
|
|
317
|
+
[`src/ARCHITECTURE.md`](src/ARCHITECTURE.md) for the source-layer boundaries,
|
|
318
|
+
facade modules, dependency rule, and the requirement that architectural cleanup
|
|
319
|
+
must preserve the existing solver hot paths and benchmark performance.
|
|
320
|
+
|
|
315
321
|
EyeProlog is released under the [MIT License](LICENSE.md).
|
package/index.d.ts
CHANGED
|
@@ -77,8 +77,9 @@ export interface EyePrologQuad {
|
|
|
77
77
|
|
|
78
78
|
export interface EyePrologQuadResult {
|
|
79
79
|
ok: boolean;
|
|
80
|
-
kind?: 'failed' | 'malformed' | 'bad_identifier' | 'unsupported';
|
|
80
|
+
kind?: 'failed' | 'malformed' | 'bad_identifier' | 'unsupported' | 'undecided';
|
|
81
81
|
expected?: EyePrologTerm;
|
|
82
|
+
reason?: string;
|
|
82
83
|
}
|
|
83
84
|
|
|
84
85
|
export interface EyePrologQuadRunResult {
|
|
@@ -86,9 +87,20 @@ export interface EyePrologQuadRunResult {
|
|
|
86
87
|
total: number;
|
|
87
88
|
passed: number;
|
|
88
89
|
failed: number;
|
|
90
|
+
undecided: number;
|
|
89
91
|
results: EyePrologQuadResult[];
|
|
90
92
|
}
|
|
91
93
|
|
|
94
|
+
export interface EyePrologQuadRunOptions extends EyePrologRunOptions {
|
|
95
|
+
initialize?: boolean;
|
|
96
|
+
/** Search budget for ordinary quad descriptions before reporting an undecided result. */
|
|
97
|
+
quadMaxInferences?: number;
|
|
98
|
+
/** Depth bound used when a quad explicitly expects loops. */
|
|
99
|
+
loopMaxDepth?: number;
|
|
100
|
+
/** Inference bound used when a quad explicitly expects loops. */
|
|
101
|
+
loopMaxInferences?: number;
|
|
102
|
+
}
|
|
103
|
+
|
|
92
104
|
export interface EyePrologPredicateGroup {
|
|
93
105
|
name: string;
|
|
94
106
|
arity: number;
|
|
@@ -271,7 +283,7 @@ export class HaltSignal extends Error {
|
|
|
271
283
|
constructor(code?: number);
|
|
272
284
|
}
|
|
273
285
|
export function run(source: string | Program, options?: EyePrologRunOptions): EyePrologRunResult;
|
|
274
|
-
export function runQuads(source: string | Program, options?:
|
|
286
|
+
export function runQuads(source: string | Program, options?: EyePrologQuadRunOptions): EyePrologQuadRunResult;
|
|
275
287
|
export function whyProof(program: Program, goal: EyePrologTerm, options?: EyePrologRunOptions): { ok: boolean; text: string };
|
|
276
288
|
export function whyNoProof(goal: EyePrologTerm): string;
|
|
277
289
|
export function explainProof(program: Program, goal: EyePrologTerm, options?: EyePrologRunOptions): { ok: boolean; text: string };
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "1.3.
|
|
6
|
+
"version": "1.3.37",
|
|
7
7
|
"description": "EyeProlog turns facts and rules into answers and proofs.",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"main": "./index.js",
|
|
@@ -64,6 +64,7 @@
|
|
|
64
64
|
"report:wg17": "node tools/report-wg17-syntax-coverage.mjs",
|
|
65
65
|
"report:wg17-syntax": "node tools/report-wg17-syntax-coverage.mjs",
|
|
66
66
|
"preversion": "npm test && node test/run-conformance-report.mjs conformance-report.md",
|
|
67
|
-
"postversion": "git push origin HEAD --follow-tags"
|
|
67
|
+
"postversion": "git push origin HEAD --follow-tags",
|
|
68
|
+
"test:architecture": "node test/run-architecture.mjs"
|
|
68
69
|
}
|
|
69
70
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# EyeProlog source architecture
|
|
2
|
+
|
|
3
|
+
The runtime is intentionally layered so semantic modules do not depend back on
|
|
4
|
+
higher-level frontends.
|
|
5
|
+
|
|
6
|
+
## Layers
|
|
7
|
+
|
|
8
|
+
1. **Kernel representation and syntax** — `term.js`, `number-value.js`,
|
|
9
|
+
`syntax-scan.js`, `parser.js`, `write.js`, `errors.js`.
|
|
10
|
+
2. **Program preparation** — `program.js` plus `program-analysis.js` and
|
|
11
|
+
`program-indexing.js`. Static recursion/Datalog/WFS classification lives in
|
|
12
|
+
`program-analysis.js`; compact clauses and candidate indexes live in
|
|
13
|
+
`program-indexing.js`.
|
|
14
|
+
3. **Execution** — `solver.js`, `io.js`, `datalog.js`, `wfs.js`, `clpz.js`.
|
|
15
|
+
4. **Language services** — `iso.js`, `iso-arithmetic.js`, `dcg.js`,
|
|
16
|
+
`standard-library.js`, and `src/lib/`.
|
|
17
|
+
5. **Frontends/tools** — `execute.js`, `repl.js`, `cli.js`, `quads.js`,
|
|
18
|
+
`explain.js`, and the playground worker.
|
|
19
|
+
|
|
20
|
+
`iso.js` and `program.js` remain facade modules for their existing exports, so
|
|
21
|
+
this refactor does not change the public JavaScript API.
|
|
22
|
+
|
|
23
|
+
## Dependency rule
|
|
24
|
+
|
|
25
|
+
Dependencies should point down or sideways within a layer, never back from a
|
|
26
|
+
kernel component into the ISO registry or a frontend. In particular,
|
|
27
|
+
`errors.js` owns `PrologError` and `HaltSignal`; DCG expansion can therefore
|
|
28
|
+
report processor errors without importing `iso.js` and creating an
|
|
29
|
+
`iso.js <-> dcg.js` cycle.
|
|
30
|
+
|
|
31
|
+
The JavaScript runtime stays flat directly under `src/`; the existing `src/lib/`
|
|
32
|
+
contains Prolog library sources rather than JavaScript runtime modules. The architecture
|
|
33
|
+
test rejects JavaScript import cycles under `src/`.
|
|
34
|
+
|
|
35
|
+
## Performance rule
|
|
36
|
+
|
|
37
|
+
Architecture changes must not add runtime strategy objects, callbacks, or
|
|
38
|
+
extra dispatch in solver hot paths. Existing scalar/indexed solver paths stay
|
|
39
|
+
as direct function calls. Candidate indexing is separated physically but
|
|
40
|
+
retains the same data structures and selection functions.
|
|
41
|
+
|
|
42
|
+
Large solver fast paths deliberately remain co-located in `solver.js` until a
|
|
43
|
+
split can demonstrate benchmark parity. A cleaner file layout is not worth a
|
|
44
|
+
runtime regression.
|
package/src/cli.js
CHANGED
|
@@ -168,6 +168,7 @@ export async function main(argv) {
|
|
|
168
168
|
const result = engine.runQuads(program, { initialize: options.goals.length === 0 });
|
|
169
169
|
process.stdout.write(result.stdout);
|
|
170
170
|
if (result.failed > 0) process.exitCode = 1;
|
|
171
|
+
else if (result.undecided > 0) process.exitCode = 2;
|
|
171
172
|
}
|
|
172
173
|
}
|
|
173
174
|
|
package/src/dcg.js
CHANGED
package/src/errors.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Runtime control and ISO processor error types shared across subsystems.
|
|
2
|
+
// Keep these independent of the ISO builtin registry so syntax, DCG, program,
|
|
3
|
+
// and solver layers can report Prolog errors without importing the whole ISO
|
|
4
|
+
// implementation (and without creating semantic-layer import cycles).
|
|
5
|
+
import { termToString } from './term.js';
|
|
6
|
+
|
|
7
|
+
export class PrologError extends Error {
|
|
8
|
+
constructor(formal, culprit = null) {
|
|
9
|
+
const detail = culprit == null ? formal : `${formal}, ${termToString(culprit)}`;
|
|
10
|
+
super(`error(${detail})`);
|
|
11
|
+
this.name = 'PrologError';
|
|
12
|
+
this.formal = formal;
|
|
13
|
+
this.culprit = culprit;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class HaltSignal extends Error {
|
|
18
|
+
constructor(code = 0) {
|
|
19
|
+
super(`halt(${code})`);
|
|
20
|
+
this.name = 'HaltSignal';
|
|
21
|
+
this.code = code;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
// ISO arithmetic evaluation and comparison semantics.
|
|
2
|
+
import {
|
|
3
|
+
ATOM, COMPOUND, NUMBER, VAR, atom, compound, deref, isDecimalInteger,
|
|
4
|
+
numberTerm, numberTextFromDouble, unify,
|
|
5
|
+
} from './term.js';
|
|
6
|
+
import { PrologError } from './errors.js';
|
|
7
|
+
|
|
8
|
+
function evaluate(term, env) {
|
|
9
|
+
term = deref(term, env);
|
|
10
|
+
if (term.type === VAR) throw new PrologError('instantiation_error');
|
|
11
|
+
if (term.type === NUMBER) {
|
|
12
|
+
if (isDecimalInteger(term.name)) return { integer: true, value: BigInt(term.name) };
|
|
13
|
+
const value = Number(term.name);
|
|
14
|
+
if (!Number.isFinite(value)) throw new PrologError('evaluation_error(float_overflow)');
|
|
15
|
+
return { integer: false, value };
|
|
16
|
+
}
|
|
17
|
+
if (term.type === ATOM) {
|
|
18
|
+
if (term.name === 'pi') return { integer: false, value: Math.PI };
|
|
19
|
+
if (term.name === 'e') return { integer: false, value: Math.E };
|
|
20
|
+
}
|
|
21
|
+
if (term.type !== COMPOUND) throw new PrologError('type_error(evaluable)', term);
|
|
22
|
+
const args = term.args.map((arg) => evaluate(arg, env));
|
|
23
|
+
return evaluateOperation(term, args);
|
|
24
|
+
}
|
|
25
|
+
function evaluateOperation(term, args) {
|
|
26
|
+
const name = term.name;
|
|
27
|
+
const arity = term.arity;
|
|
28
|
+
if (arity === 1 && (name === '+' || name === '-')) {
|
|
29
|
+
return name === '+' ? args[0] : args[0].integer
|
|
30
|
+
? { integer: true, value: -args[0].value }
|
|
31
|
+
: { integer: false, value: -args[0].value };
|
|
32
|
+
}
|
|
33
|
+
if (arity === 1 && name === '\\') {
|
|
34
|
+
if (!args[0].integer) throw new PrologError('type_error(integer)', numericTerm(args[0]));
|
|
35
|
+
return { integer: true, value: ~args[0].value };
|
|
36
|
+
}
|
|
37
|
+
if (arity === 1 && ['abs', 'sign', 'float', 'truncate', 'round', 'ceiling', 'floor',
|
|
38
|
+
'float_integer_part', 'float_fractional_part',
|
|
39
|
+
'sin', 'cos', 'atan', 'asin', 'acos', 'tan', 'exp', 'log', 'sqrt'].includes(name)) {
|
|
40
|
+
const a = Number(args[0].value);
|
|
41
|
+
if (name === 'abs' && args[0].integer) return { integer: true, value: args[0].value < 0n ? -args[0].value : args[0].value };
|
|
42
|
+
if (name === 'sign' && args[0].integer) return { integer: true, value: args[0].value < 0n ? -1n : args[0].value > 0n ? 1n : 0n };
|
|
43
|
+
if (name === 'truncate' || name === 'round' || name === 'ceiling' || name === 'floor') {
|
|
44
|
+
const fn = name === 'truncate' ? Math.trunc : name === 'round' ? Math.round : name === 'ceiling' ? Math.ceil : Math.floor;
|
|
45
|
+
return { integer: true, value: BigInt(fn(a)) };
|
|
46
|
+
}
|
|
47
|
+
if (name === 'float_integer_part' || name === 'float_fractional_part') {
|
|
48
|
+
if (args[0].integer) throw new PrologError('type_error(float)', numericTerm(args[0]));
|
|
49
|
+
const value = name === 'float_integer_part' ? Math.trunc(a) : a - Math.trunc(a);
|
|
50
|
+
return { integer: false, value };
|
|
51
|
+
}
|
|
52
|
+
const fn = name === 'float' ? (x) => x : name === 'abs' ? Math.abs : name === 'sign' ? Math.sign : Math[name];
|
|
53
|
+
const value = fn(a);
|
|
54
|
+
if (Number.isNaN(value) || (name === 'log' && a === 0)) throw new PrologError('evaluation_error(undefined)');
|
|
55
|
+
if (!Number.isFinite(value)) throw new PrologError('evaluation_error(float_overflow)');
|
|
56
|
+
return { integer: false, value };
|
|
57
|
+
}
|
|
58
|
+
if (arity !== 2) throw new PrologError('type_error(evaluable)', compound('/', [atom(name), numberTerm(arity)]));
|
|
59
|
+
const bothInteger = args[0].integer && args[1].integer;
|
|
60
|
+
const a = args[0].value, b = args[1].value;
|
|
61
|
+
if (['//', 'div', 'mod', 'rem', '/\\', '\\/', 'xor', '<<', '>>'].includes(name) && !bothInteger) {
|
|
62
|
+
const invalid = !args[0].integer ? args[0] : args[1];
|
|
63
|
+
throw new PrologError('type_error(integer)', numericTerm(invalid));
|
|
64
|
+
}
|
|
65
|
+
if (bothInteger && name === '^') {
|
|
66
|
+
if (b >= 0n) return { integer: true, value: a ** b };
|
|
67
|
+
if (a === 0n) throw new PrologError('evaluation_error(undefined)');
|
|
68
|
+
if (a === 1n) return { integer: true, value: 1n };
|
|
69
|
+
if (a === -1n) return { integer: true, value: (-b) % 2n === 0n ? 1n : -1n };
|
|
70
|
+
// Corrigendum 3: the defined real result needs a floating-point base.
|
|
71
|
+
throw new PrologError('type_error(float)', numericTerm(args[0]));
|
|
72
|
+
}
|
|
73
|
+
if (bothInteger && ['+', '-', '*', '//', 'div', 'mod', 'rem', '/\\', '\\/', 'xor', '<<', '>>'].includes(name)) {
|
|
74
|
+
if ((name === '//' || name === 'div' || name === 'mod' || name === 'rem') && b === 0n) throw new PrologError('evaluation_error(zero_divisor)');
|
|
75
|
+
if (name === '+') return { integer: true, value: a + b };
|
|
76
|
+
if (name === '-') return { integer: true, value: a - b };
|
|
77
|
+
if (name === '*') return { integer: true, value: a * b };
|
|
78
|
+
if (name === '//') return { integer: true, value: a / b };
|
|
79
|
+
if (name === 'div') {
|
|
80
|
+
const quotient = a / b;
|
|
81
|
+
const remainder = a % b;
|
|
82
|
+
return { integer: true, value: remainder !== 0n && ((a < 0n) !== (b < 0n)) ? quotient - 1n : quotient };
|
|
83
|
+
}
|
|
84
|
+
if (name === 'rem') return { integer: true, value: a % b };
|
|
85
|
+
if (name === 'mod') return { integer: true, value: ((a % b) + b) % b };
|
|
86
|
+
if (name === '/\\') return { integer: true, value: a & b };
|
|
87
|
+
if (name === '\\/') return { integer: true, value: a | b };
|
|
88
|
+
if (name === 'xor') return { integer: true, value: a ^ b };
|
|
89
|
+
if (name === '<<') return { integer: true, value: a << b };
|
|
90
|
+
if (name === '>>') return { integer: true, value: a >> b };
|
|
91
|
+
}
|
|
92
|
+
const x = Number(a), y = Number(b);
|
|
93
|
+
if ((!Number.isFinite(x) || !Number.isFinite(y)) && name !== 'max' && name !== 'min') {
|
|
94
|
+
throw new PrologError('evaluation_error(float_overflow)');
|
|
95
|
+
}
|
|
96
|
+
if (name === '/' && y === 0) throw new PrologError('evaluation_error(zero_divisor)');
|
|
97
|
+
let value;
|
|
98
|
+
if (name === 'max' || name === 'min') {
|
|
99
|
+
const cmp = compareArithmeticValues(args[0], args[1]);
|
|
100
|
+
const chooseLeft = name === 'max' ? cmp >= 0 : cmp <= 0;
|
|
101
|
+
return chooseLeft ? args[0] : args[1];
|
|
102
|
+
}
|
|
103
|
+
if (name === 'atan2') {
|
|
104
|
+
if (x === 0 && y === 0) throw new PrologError('evaluation_error(undefined)');
|
|
105
|
+
value = Math.atan2(x, y);
|
|
106
|
+
}
|
|
107
|
+
else if (name === '+') value = x + y;
|
|
108
|
+
else if (name === '-') value = x - y;
|
|
109
|
+
else if (name === '*') value = x * y;
|
|
110
|
+
else if (name === '/') value = x / y;
|
|
111
|
+
else if (name === '**' || name === '^') value = Math.pow(x, y);
|
|
112
|
+
else throw new PrologError('type_error(evaluable)', compound('/', [atom(name), numberTerm(arity)]));
|
|
113
|
+
if (Number.isNaN(value)) throw new PrologError('evaluation_error(undefined)');
|
|
114
|
+
if (!Number.isFinite(value)) throw new PrologError('evaluation_error(float_overflow)');
|
|
115
|
+
return { integer: false, value };
|
|
116
|
+
}
|
|
117
|
+
export function arithmeticValueTerm(value) {
|
|
118
|
+
return value.integer ? numberTerm(value.value.toString()) : numberTerm(numberTextFromDouble(value.value));
|
|
119
|
+
}
|
|
120
|
+
function numericTerm(value) {
|
|
121
|
+
return arithmeticValueTerm(value);
|
|
122
|
+
}
|
|
123
|
+
export function evaluateArithmetic(term, env) {
|
|
124
|
+
return evaluate(term, env);
|
|
125
|
+
}
|
|
126
|
+
function compareIntegerToFloat(integerValue, floatValue) {
|
|
127
|
+
if (!Number.isFinite(floatValue)) throw new PrologError('evaluation_error(float_overflow)');
|
|
128
|
+
|
|
129
|
+
// Do not round an unbounded integer through JavaScript Number before a
|
|
130
|
+
// mixed arithmetic comparison. Every integral IEEE-754 double can be
|
|
131
|
+
// converted back to the exact integer value it represents; fractional
|
|
132
|
+
// doubles necessarily have magnitude below 2^53, so their truncation is
|
|
133
|
+
// also exact. This preserves mathematical ordering across the I/F boundary
|
|
134
|
+
// (STC #50), e.g. 9007199254740993 > 9007199254740992.0.
|
|
135
|
+
if (Number.isInteger(floatValue)) {
|
|
136
|
+
const floatInteger = BigInt(floatValue);
|
|
137
|
+
return integerValue < floatInteger ? -1 : integerValue > floatInteger ? 1 : 0;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const truncated = BigInt(Math.trunc(floatValue));
|
|
141
|
+
if (integerValue < truncated) return -1;
|
|
142
|
+
if (integerValue > truncated) return 1;
|
|
143
|
+
return floatValue > 0 ? -1 : 1;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function compareArithmeticValues(left, right) {
|
|
147
|
+
const a = left.value;
|
|
148
|
+
const b = right.value;
|
|
149
|
+
if (left.integer && right.integer) return a < b ? -1 : a > b ? 1 : 0;
|
|
150
|
+
if (left.integer) return compareIntegerToFloat(a, b);
|
|
151
|
+
if (right.integer) return -compareIntegerToFloat(b, a);
|
|
152
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
153
|
+
}
|
|
154
|
+
export function* isBuiltin({ goal, env }) {
|
|
155
|
+
const result = arithmeticValueTerm(evaluateArithmetic(goal.args[1], env));
|
|
156
|
+
const next = env.clone();
|
|
157
|
+
if (unify(goal.args[0], result, next)) yield next;
|
|
158
|
+
}
|
|
159
|
+
export function arithmeticComparison(test) {
|
|
160
|
+
return function* ({ goal, env }) {
|
|
161
|
+
const left = evaluateArithmetic(goal.args[0], env);
|
|
162
|
+
const right = evaluateArithmetic(goal.args[1], env);
|
|
163
|
+
const cmp = compareArithmeticValues(left, right);
|
|
164
|
+
if (test(cmp)) yield env;
|
|
165
|
+
};
|
|
166
|
+
}
|
package/src/iso.js
CHANGED
|
@@ -18,23 +18,8 @@ import {
|
|
|
18
18
|
|
|
19
19
|
let isoFresh = 0;
|
|
20
20
|
|
|
21
|
-
export
|
|
22
|
-
|
|
23
|
-
const detail = culprit == null ? formal : `${formal}, ${termToString(culprit)}`;
|
|
24
|
-
super(`error(${detail})`);
|
|
25
|
-
this.name = 'PrologError';
|
|
26
|
-
this.formal = formal;
|
|
27
|
-
this.culprit = culprit;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export class HaltSignal extends Error {
|
|
32
|
-
constructor(code = 0) {
|
|
33
|
-
super(`halt(${code})`);
|
|
34
|
-
this.name = 'HaltSignal';
|
|
35
|
-
this.code = code;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
21
|
+
export { PrologError, HaltSignal } from './errors.js';
|
|
22
|
+
import { PrologError, HaltSignal } from './errors.js';
|
|
38
23
|
|
|
39
24
|
class ThrownTerm extends Error {
|
|
40
25
|
constructor(term) {
|
|
@@ -2318,165 +2303,8 @@ function* ifThenBuiltin({ solver, goal, env }) {
|
|
|
2318
2303
|
}
|
|
2319
2304
|
}
|
|
2320
2305
|
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
if (term.type === VAR) throw new PrologError('instantiation_error');
|
|
2324
|
-
if (term.type === NUMBER) {
|
|
2325
|
-
if (isDecimalInteger(term.name)) return { integer: true, value: BigInt(term.name) };
|
|
2326
|
-
const value = Number(term.name);
|
|
2327
|
-
if (!Number.isFinite(value)) throw new PrologError('evaluation_error(float_overflow)');
|
|
2328
|
-
return { integer: false, value };
|
|
2329
|
-
}
|
|
2330
|
-
if (term.type === ATOM) {
|
|
2331
|
-
if (term.name === 'pi') return { integer: false, value: Math.PI };
|
|
2332
|
-
if (term.name === 'e') return { integer: false, value: Math.E };
|
|
2333
|
-
}
|
|
2334
|
-
if (term.type !== COMPOUND) throw new PrologError('type_error(evaluable)', term);
|
|
2335
|
-
const args = term.args.map((arg) => evaluate(arg, env));
|
|
2336
|
-
return evaluateOperation(term, args);
|
|
2337
|
-
}
|
|
2338
|
-
function evaluateOperation(term, args) {
|
|
2339
|
-
const name = term.name;
|
|
2340
|
-
const arity = term.arity;
|
|
2341
|
-
if (arity === 1 && (name === '+' || name === '-')) {
|
|
2342
|
-
return name === '+' ? args[0] : args[0].integer
|
|
2343
|
-
? { integer: true, value: -args[0].value }
|
|
2344
|
-
: { integer: false, value: -args[0].value };
|
|
2345
|
-
}
|
|
2346
|
-
if (arity === 1 && name === '\\') {
|
|
2347
|
-
if (!args[0].integer) throw new PrologError('type_error(integer)', numericTerm(args[0]));
|
|
2348
|
-
return { integer: true, value: ~args[0].value };
|
|
2349
|
-
}
|
|
2350
|
-
if (arity === 1 && ['abs', 'sign', 'float', 'truncate', 'round', 'ceiling', 'floor',
|
|
2351
|
-
'float_integer_part', 'float_fractional_part',
|
|
2352
|
-
'sin', 'cos', 'atan', 'asin', 'acos', 'tan', 'exp', 'log', 'sqrt'].includes(name)) {
|
|
2353
|
-
const a = Number(args[0].value);
|
|
2354
|
-
if (name === 'abs' && args[0].integer) return { integer: true, value: args[0].value < 0n ? -args[0].value : args[0].value };
|
|
2355
|
-
if (name === 'sign' && args[0].integer) return { integer: true, value: args[0].value < 0n ? -1n : args[0].value > 0n ? 1n : 0n };
|
|
2356
|
-
if (name === 'truncate' || name === 'round' || name === 'ceiling' || name === 'floor') {
|
|
2357
|
-
const fn = name === 'truncate' ? Math.trunc : name === 'round' ? Math.round : name === 'ceiling' ? Math.ceil : Math.floor;
|
|
2358
|
-
return { integer: true, value: BigInt(fn(a)) };
|
|
2359
|
-
}
|
|
2360
|
-
if (name === 'float_integer_part' || name === 'float_fractional_part') {
|
|
2361
|
-
if (args[0].integer) throw new PrologError('type_error(float)', numericTerm(args[0]));
|
|
2362
|
-
const value = name === 'float_integer_part' ? Math.trunc(a) : a - Math.trunc(a);
|
|
2363
|
-
return { integer: false, value };
|
|
2364
|
-
}
|
|
2365
|
-
const fn = name === 'float' ? (x) => x : name === 'abs' ? Math.abs : name === 'sign' ? Math.sign : Math[name];
|
|
2366
|
-
const value = fn(a);
|
|
2367
|
-
if (Number.isNaN(value) || (name === 'log' && a === 0)) throw new PrologError('evaluation_error(undefined)');
|
|
2368
|
-
if (!Number.isFinite(value)) throw new PrologError('evaluation_error(float_overflow)');
|
|
2369
|
-
return { integer: false, value };
|
|
2370
|
-
}
|
|
2371
|
-
if (arity !== 2) throw new PrologError('type_error(evaluable)', compound('/', [atom(name), numberTerm(arity)]));
|
|
2372
|
-
const bothInteger = args[0].integer && args[1].integer;
|
|
2373
|
-
const a = args[0].value, b = args[1].value;
|
|
2374
|
-
if (['//', 'div', 'mod', 'rem', '/\\', '\\/', 'xor', '<<', '>>'].includes(name) && !bothInteger) {
|
|
2375
|
-
const invalid = !args[0].integer ? args[0] : args[1];
|
|
2376
|
-
throw new PrologError('type_error(integer)', numericTerm(invalid));
|
|
2377
|
-
}
|
|
2378
|
-
if (bothInteger && name === '^') {
|
|
2379
|
-
if (b >= 0n) return { integer: true, value: a ** b };
|
|
2380
|
-
if (a === 0n) throw new PrologError('evaluation_error(undefined)');
|
|
2381
|
-
if (a === 1n) return { integer: true, value: 1n };
|
|
2382
|
-
if (a === -1n) return { integer: true, value: (-b) % 2n === 0n ? 1n : -1n };
|
|
2383
|
-
// Corrigendum 3: the defined real result needs a floating-point base.
|
|
2384
|
-
throw new PrologError('type_error(float)', numericTerm(args[0]));
|
|
2385
|
-
}
|
|
2386
|
-
if (bothInteger && ['+', '-', '*', '//', 'div', 'mod', 'rem', '/\\', '\\/', 'xor', '<<', '>>'].includes(name)) {
|
|
2387
|
-
if ((name === '//' || name === 'div' || name === 'mod' || name === 'rem') && b === 0n) throw new PrologError('evaluation_error(zero_divisor)');
|
|
2388
|
-
if (name === '+') return { integer: true, value: a + b };
|
|
2389
|
-
if (name === '-') return { integer: true, value: a - b };
|
|
2390
|
-
if (name === '*') return { integer: true, value: a * b };
|
|
2391
|
-
if (name === '//') return { integer: true, value: a / b };
|
|
2392
|
-
if (name === 'div') {
|
|
2393
|
-
const quotient = a / b;
|
|
2394
|
-
const remainder = a % b;
|
|
2395
|
-
return { integer: true, value: remainder !== 0n && ((a < 0n) !== (b < 0n)) ? quotient - 1n : quotient };
|
|
2396
|
-
}
|
|
2397
|
-
if (name === 'rem') return { integer: true, value: a % b };
|
|
2398
|
-
if (name === 'mod') return { integer: true, value: ((a % b) + b) % b };
|
|
2399
|
-
if (name === '/\\') return { integer: true, value: a & b };
|
|
2400
|
-
if (name === '\\/') return { integer: true, value: a | b };
|
|
2401
|
-
if (name === 'xor') return { integer: true, value: a ^ b };
|
|
2402
|
-
if (name === '<<') return { integer: true, value: a << b };
|
|
2403
|
-
if (name === '>>') return { integer: true, value: a >> b };
|
|
2404
|
-
}
|
|
2405
|
-
const x = Number(a), y = Number(b);
|
|
2406
|
-
if ((!Number.isFinite(x) || !Number.isFinite(y)) && name !== 'max' && name !== 'min') {
|
|
2407
|
-
throw new PrologError('evaluation_error(float_overflow)');
|
|
2408
|
-
}
|
|
2409
|
-
if (name === '/' && y === 0) throw new PrologError('evaluation_error(zero_divisor)');
|
|
2410
|
-
let value;
|
|
2411
|
-
if (name === 'max' || name === 'min') {
|
|
2412
|
-
const cmp = compareArithmeticValues(args[0], args[1]);
|
|
2413
|
-
const chooseLeft = name === 'max' ? cmp >= 0 : cmp <= 0;
|
|
2414
|
-
return chooseLeft ? args[0] : args[1];
|
|
2415
|
-
}
|
|
2416
|
-
if (name === 'atan2') {
|
|
2417
|
-
if (x === 0 && y === 0) throw new PrologError('evaluation_error(undefined)');
|
|
2418
|
-
value = Math.atan2(x, y);
|
|
2419
|
-
}
|
|
2420
|
-
else if (name === '+') value = x + y;
|
|
2421
|
-
else if (name === '-') value = x - y;
|
|
2422
|
-
else if (name === '*') value = x * y;
|
|
2423
|
-
else if (name === '/') value = x / y;
|
|
2424
|
-
else if (name === '**' || name === '^') value = Math.pow(x, y);
|
|
2425
|
-
else throw new PrologError('type_error(evaluable)', compound('/', [atom(name), numberTerm(arity)]));
|
|
2426
|
-
if (Number.isNaN(value)) throw new PrologError('evaluation_error(undefined)');
|
|
2427
|
-
if (!Number.isFinite(value)) throw new PrologError('evaluation_error(float_overflow)');
|
|
2428
|
-
return { integer: false, value };
|
|
2429
|
-
}
|
|
2430
|
-
export function arithmeticValueTerm(value) {
|
|
2431
|
-
return value.integer ? numberTerm(value.value.toString()) : numberTerm(numberTextFromDouble(value.value));
|
|
2432
|
-
}
|
|
2433
|
-
function numericTerm(value) {
|
|
2434
|
-
return arithmeticValueTerm(value);
|
|
2435
|
-
}
|
|
2436
|
-
export function evaluateArithmetic(term, env) {
|
|
2437
|
-
return evaluate(term, env);
|
|
2438
|
-
}
|
|
2439
|
-
function compareIntegerToFloat(integerValue, floatValue) {
|
|
2440
|
-
if (!Number.isFinite(floatValue)) throw new PrologError('evaluation_error(float_overflow)');
|
|
2441
|
-
|
|
2442
|
-
// Do not round an unbounded integer through JavaScript Number before a
|
|
2443
|
-
// mixed arithmetic comparison. Every integral IEEE-754 double can be
|
|
2444
|
-
// converted back to the exact integer value it represents; fractional
|
|
2445
|
-
// doubles necessarily have magnitude below 2^53, so their truncation is
|
|
2446
|
-
// also exact. This preserves mathematical ordering across the I/F boundary
|
|
2447
|
-
// (STC #50), e.g. 9007199254740993 > 9007199254740992.0.
|
|
2448
|
-
if (Number.isInteger(floatValue)) {
|
|
2449
|
-
const floatInteger = BigInt(floatValue);
|
|
2450
|
-
return integerValue < floatInteger ? -1 : integerValue > floatInteger ? 1 : 0;
|
|
2451
|
-
}
|
|
2452
|
-
|
|
2453
|
-
const truncated = BigInt(Math.trunc(floatValue));
|
|
2454
|
-
if (integerValue < truncated) return -1;
|
|
2455
|
-
if (integerValue > truncated) return 1;
|
|
2456
|
-
return floatValue > 0 ? -1 : 1;
|
|
2457
|
-
}
|
|
2458
|
-
|
|
2459
|
-
export function compareArithmeticValues(left, right) {
|
|
2460
|
-
const a = left.value;
|
|
2461
|
-
const b = right.value;
|
|
2462
|
-
if (left.integer && right.integer) return a < b ? -1 : a > b ? 1 : 0;
|
|
2463
|
-
if (left.integer) return compareIntegerToFloat(a, b);
|
|
2464
|
-
if (right.integer) return -compareIntegerToFloat(b, a);
|
|
2465
|
-
return a < b ? -1 : a > b ? 1 : 0;
|
|
2466
|
-
}
|
|
2467
|
-
function* isBuiltin({ goal, env }) {
|
|
2468
|
-
const result = arithmeticValueTerm(evaluateArithmetic(goal.args[1], env));
|
|
2469
|
-
const next = env.clone();
|
|
2470
|
-
if (unify(goal.args[0], result, next)) yield next;
|
|
2471
|
-
}
|
|
2472
|
-
function arithmeticComparison(test) {
|
|
2473
|
-
return function* ({ goal, env }) {
|
|
2474
|
-
const left = evaluateArithmetic(goal.args[0], env);
|
|
2475
|
-
const right = evaluateArithmetic(goal.args[1], env);
|
|
2476
|
-
const cmp = compareArithmeticValues(left, right);
|
|
2477
|
-
if (test(cmp)) yield env;
|
|
2478
|
-
};
|
|
2479
|
-
}
|
|
2306
|
+
export { arithmeticValueTerm, evaluateArithmetic, compareArithmeticValues } from './iso-arithmetic.js';
|
|
2307
|
+
import { isBuiltin, arithmeticComparison } from './iso-arithmetic.js';
|
|
2480
2308
|
|
|
2481
2309
|
|
|
2482
2310
|
export class BuiltinRegistry {
|