eyeprolog 1.3.36 → 1.3.38

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,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 {