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 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?: EyePrologRunOptions & { initialize?: boolean }): EyePrologQuadRunResult;
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.35",
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
@@ -5,7 +5,7 @@ import {
5
5
  ATOM, COMPOUND, VAR, Env, atom, compactListLength, compound, deref, emptyList,
6
6
  flattenConjunction, variable,
7
7
  } from './term.js';
8
- import { PrologError } from './iso.js';
8
+ import { PrologError } from './errors.js';
9
9
 
10
10
  let dcgFresh = 0;
11
11
 
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 class PrologError extends Error {
22
- constructor(formal, culprit = null) {
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
- function evaluate(term, env) {
2322
- term = deref(term, env);
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 {