eyeprolog 1.1.12 → 1.1.14
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 +3 -3
- package/index.d.ts +5 -0
- package/package.json +1 -1
- package/src/cli.js +10 -0
- package/src/iso.js +151 -10
- package/src/lib/eyeprolog.pl +15 -1
- package/src/lib/prologue.pl +4 -0
- package/src/quads.js +28 -6
- package/src/solver.js +38 -2
- package/src/standard-library.js +1 -1
- package/src/term.js +20 -0
- package/test/fixtures/length_quad.pl +94 -0
- package/test/fixtures/number_chars_cont_quad.pl +235 -0
- package/test/run-regression.mjs +46 -8
- package/the-art-of-eyeprolog.md +21 -14
package/README.md
CHANGED
|
@@ -91,10 +91,10 @@ noun --> [world] | [prolog].
|
|
|
91
91
|
%% goal: phrase(sentence, Words)
|
|
92
92
|
```
|
|
93
93
|
|
|
94
|
-
EyeProlog also adds
|
|
94
|
+
EyeProlog also adds 58 public library predicate indicators to its 129-entry ISO
|
|
95
95
|
profile. **56 are implemented entirely as ordinary Prolog clauses** in
|
|
96
|
-
`src/lib/eyeprolog.pl`, `src/lib/lists.pl`, and `src/lib/prologue.pl`;
|
|
97
|
-
`call_nth/2`
|
|
96
|
+
`src/lib/eyeprolog.pl`, `src/lib/lists.pl`, and `src/lib/prologue.pl`;
|
|
97
|
+
`call_nth/2` and `freeze/2` use private host adapters for their control behavior.
|
|
98
98
|
They are ISO/IEC 13211-2 modules,
|
|
99
99
|
loaded explicitly with `use_module(library(eyeprolog))`,
|
|
100
100
|
`use_module(library(lists))`, or `use_module(library(prologue))`. Portable text
|
package/index.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ export interface EyePrologRunOptions {
|
|
|
11
11
|
why?: boolean;
|
|
12
12
|
explain?: boolean;
|
|
13
13
|
maxDepth?: number;
|
|
14
|
+
maxInferences?: number;
|
|
14
15
|
solutionLimit?: number;
|
|
15
16
|
registry?: BuiltinRegistry;
|
|
16
17
|
sourceMetadata?: boolean;
|
|
@@ -161,6 +162,10 @@ export class Solver {
|
|
|
161
162
|
program: Program;
|
|
162
163
|
registry: BuiltinRegistry;
|
|
163
164
|
maxDepth: number;
|
|
165
|
+
depthLimitExceeded: boolean;
|
|
166
|
+
maxInferences: number;
|
|
167
|
+
inferences: number;
|
|
168
|
+
inferenceLimitExceeded: boolean;
|
|
164
169
|
solutionLimit: number;
|
|
165
170
|
solutionsSeen: number;
|
|
166
171
|
active: unknown[];
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -110,6 +110,16 @@ export async function main(argv) {
|
|
|
110
110
|
for (const source of sourceParts) options.goals.push(...goalsFromSource(source.text));
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
+
// The ISO Prolog working-example quad files assume the Prologue predicates
|
|
114
|
+
// are available as system predicates and therefore contain no use_module/1
|
|
115
|
+
// directive. Import their portable EyeProlog counterparts in quad mode.
|
|
116
|
+
if (options.quads) {
|
|
117
|
+
sourceParts.unshift({
|
|
118
|
+
text: ':- use_module(library(prologue)).\n',
|
|
119
|
+
filename: '<quad-prelude>',
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
113
123
|
const engine = await loadEngine();
|
|
114
124
|
let program = engine.Program.parseSources(sourceParts, { sourceMetadata: options.proof });
|
|
115
125
|
|
package/src/iso.js
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
isDecimalInteger, listFromItems, numberTerm, numberTextFromDouble,
|
|
6
6
|
properListItems, termIsGround, termToString, unify, variable, variantTerms,
|
|
7
7
|
} from './term.js';
|
|
8
|
-
import { createParserOperatorState, parseClauses } from './parser.js';
|
|
8
|
+
import { createParserOperatorState, parseClauses, parseGoalText } from './parser.js';
|
|
9
9
|
import { formatTermForWrite } from './write.js';
|
|
10
10
|
import { emptyTerminalSequence, expandDcgBody, isListOrPartialList, validateDcgEmbeddedGoals } from './dcg.js';
|
|
11
11
|
|
|
@@ -164,12 +164,13 @@ export const isoBuiltins = {
|
|
|
164
164
|
}
|
|
165
165
|
};
|
|
166
166
|
|
|
167
|
-
//
|
|
168
|
-
//
|
|
169
|
-
//
|
|
167
|
+
// These Prologue library predicates have control behavior that cannot be
|
|
168
|
+
// expressed portably as ordinary Prolog clauses. Keep their public wrappers in
|
|
169
|
+
// library(prologue) and expose only private adapters to the host registry.
|
|
170
170
|
export const eyePrologLibraryBuiltins = {
|
|
171
171
|
register(registry) {
|
|
172
172
|
registry.add('eyeprolog__call_nth', 2, callNthBuiltin, { eyePrologLibrary: true });
|
|
173
|
+
registry.add('eyeprolog__freeze', 2, freezeBuiltin, { eyePrologLibrary: true });
|
|
173
174
|
},
|
|
174
175
|
};
|
|
175
176
|
|
|
@@ -1406,12 +1407,129 @@ function* charCodeBuiltin({ goal, env }) {
|
|
|
1406
1407
|
} else if (unify(goal.args[0], atom(String.fromCodePoint(Number(code.name))), next)) yield next;
|
|
1407
1408
|
}
|
|
1408
1409
|
|
|
1410
|
+
function skipNumberLayout(text, start) {
|
|
1411
|
+
let position = start;
|
|
1412
|
+
while (true) {
|
|
1413
|
+
while (position < text.length && /[\u0009-\u000d\u0020]/.test(text[position])) {
|
|
1414
|
+
position++;
|
|
1415
|
+
}
|
|
1416
|
+
if (text[position] === '%') {
|
|
1417
|
+
const newline = text.indexOf('\n', position + 1);
|
|
1418
|
+
if (newline < 0) return text.length;
|
|
1419
|
+
position = newline + 1;
|
|
1420
|
+
continue;
|
|
1421
|
+
}
|
|
1422
|
+
if (text.startsWith('/*', position)) {
|
|
1423
|
+
const end = text.indexOf('*/', position + 2);
|
|
1424
|
+
if (end < 0) return text.length;
|
|
1425
|
+
position = end + 2;
|
|
1426
|
+
continue;
|
|
1427
|
+
}
|
|
1428
|
+
return position;
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
function quotedNumberSign(text, start) {
|
|
1433
|
+
if (text[start] !== "'") return null;
|
|
1434
|
+
let position = start + 1;
|
|
1435
|
+
let value = '';
|
|
1436
|
+
while (position < text.length) {
|
|
1437
|
+
let character = text[position++];
|
|
1438
|
+
if (character === "'") {
|
|
1439
|
+
if (text[position] === "'") {
|
|
1440
|
+
position++;
|
|
1441
|
+
value += "'";
|
|
1442
|
+
continue;
|
|
1443
|
+
}
|
|
1444
|
+
return { value, position };
|
|
1445
|
+
}
|
|
1446
|
+
if (character !== '\\') {
|
|
1447
|
+
value += character;
|
|
1448
|
+
continue;
|
|
1449
|
+
}
|
|
1450
|
+
if (position >= text.length) return null;
|
|
1451
|
+
character = text[position++];
|
|
1452
|
+
if (character === '\n') continue;
|
|
1453
|
+
const controls = { a: '\x07', b: '\b', r: '\r', f: '\f', t: '\t', n: '\n', v: '\v' };
|
|
1454
|
+
if (controls[character] != null) {
|
|
1455
|
+
value += controls[character];
|
|
1456
|
+
continue;
|
|
1457
|
+
}
|
|
1458
|
+
value += character;
|
|
1459
|
+
}
|
|
1460
|
+
return null;
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1409
1463
|
function parseIsoNumber(text) {
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
if (
|
|
1413
|
-
|
|
1414
|
-
|
|
1464
|
+
// number_chars/2 accepts leading layout, but its character list must end
|
|
1465
|
+
// with the numeric token itself rather than trailing layout text.
|
|
1466
|
+
if (text.length === 0 || /[\u0009-\u000d\u0020]$/.test(text)) return null;
|
|
1467
|
+
let position = skipNumberLayout(text, 0);
|
|
1468
|
+
let sign = '';
|
|
1469
|
+
|
|
1470
|
+
if (text[position] === '-') {
|
|
1471
|
+
if (/[\u0009-\u000d\u0020]/.test(text[position + 1] ?? '')) {
|
|
1472
|
+
position = skipNumberLayout(text, position + 1);
|
|
1473
|
+
sign = '-';
|
|
1474
|
+
}
|
|
1475
|
+
} else {
|
|
1476
|
+
const quoted = quotedNumberSign(text, position);
|
|
1477
|
+
if (quoted?.value === '-') {
|
|
1478
|
+
position = skipNumberLayout(text, quoted.position);
|
|
1479
|
+
sign = '-';
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
const numericText = `${sign}${text.slice(position)}`;
|
|
1484
|
+
// ISO floating-point syntax requires a decimal fraction before an exponent.
|
|
1485
|
+
if (/^-?\d+[eE][+-]?\d+$/.test(numericText)) return null;
|
|
1486
|
+
try {
|
|
1487
|
+
const parsed = parseGoalText(`number_chars_value(${numericText})`);
|
|
1488
|
+
if (parsed.type !== COMPOUND || parsed.name !== 'number_chars_value' ||
|
|
1489
|
+
parsed.arity !== 1 || parsed.args[0].type !== NUMBER) return null;
|
|
1490
|
+
const value = parsed.args[0];
|
|
1491
|
+
if (isDecimalInteger(value.name)) return numberTerm(BigInt(value.name).toString());
|
|
1492
|
+
const finite = Number(value.name);
|
|
1493
|
+
if (!Number.isFinite(finite)) return null;
|
|
1494
|
+
return numberTerm(numberTextFromDouble(finite));
|
|
1495
|
+
} catch (_) {
|
|
1496
|
+
return null;
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
function sameNumber(left, right) {
|
|
1501
|
+
const leftInteger = isDecimalInteger(left.name);
|
|
1502
|
+
const rightInteger = isDecimalInteger(right.name);
|
|
1503
|
+
if (leftInteger || rightInteger) {
|
|
1504
|
+
return leftInteger && rightInteger && BigInt(left.name) === BigInt(right.name);
|
|
1505
|
+
}
|
|
1506
|
+
const leftValue = Number(left.name);
|
|
1507
|
+
const rightValue = Number(right.name);
|
|
1508
|
+
return Number.isFinite(leftValue) && Number.isFinite(rightValue) && leftValue === rightValue;
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
function numberListText(list, env, kind, valueIsBound) {
|
|
1512
|
+
const whole = deref(list, env);
|
|
1513
|
+
const { items, tail } = listElements(list, env);
|
|
1514
|
+
const proper = tail.type === ATOM && tail.name === '[]';
|
|
1515
|
+
if (tail.type !== VAR && !proper) throw new PrologError('type_error(list)', whole);
|
|
1516
|
+
|
|
1517
|
+
const invalid = items.find((item) => item.type !== VAR &&
|
|
1518
|
+
(kind === 'chars' ? !oneChar(item) : !validCharacterCode(item)));
|
|
1519
|
+
if (invalid) {
|
|
1520
|
+
if (kind === 'chars') throw new PrologError('type_error(character)', invalid);
|
|
1521
|
+
if (invalid.type !== NUMBER || !isDecimalInteger(invalid.name)) {
|
|
1522
|
+
throw new PrologError('type_error(integer)', invalid);
|
|
1523
|
+
}
|
|
1524
|
+
throw new PrologError('representation_error(character_code)');
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
const hasVariable = tail.type === VAR || items.some((item) => item.type === VAR);
|
|
1528
|
+
if (!valueIsBound && hasVariable) throw new PrologError('instantiation_error');
|
|
1529
|
+
if (hasVariable) return null;
|
|
1530
|
+
return items.map((item) => kind === 'chars'
|
|
1531
|
+
? item.name
|
|
1532
|
+
: String.fromCodePoint(Number(item.name))).join('');
|
|
1415
1533
|
}
|
|
1416
1534
|
|
|
1417
1535
|
function numberListBuiltin(kind) {
|
|
@@ -1420,14 +1538,21 @@ function numberListBuiltin(kind) {
|
|
|
1420
1538
|
if (value.type !== VAR && value.type !== NUMBER) throw new PrologError('type_error(number)', value);
|
|
1421
1539
|
const list = deref(goal.args[1], env);
|
|
1422
1540
|
if (value.type === VAR && list.type === VAR) throw new PrologError('instantiation_error');
|
|
1541
|
+
const text = numberListText(list, env, kind, value.type === NUMBER);
|
|
1423
1542
|
const next = env.clone();
|
|
1424
1543
|
if (value.type === NUMBER) {
|
|
1544
|
+
if (text != null) {
|
|
1545
|
+
const parsed = parseIsoNumber(text);
|
|
1546
|
+
if (parsed == null) throw new PrologError('syntax_error(number)');
|
|
1547
|
+
if (sameNumber(value, parsed)) yield next;
|
|
1548
|
+
return;
|
|
1549
|
+
}
|
|
1425
1550
|
const items = characters(value.name).map((ch) =>
|
|
1426
1551
|
kind === 'chars' ? atom(ch) : numberTerm(ch.codePointAt(0)));
|
|
1427
1552
|
if (unify(goal.args[1], listFromItems(items), next)) yield next;
|
|
1428
1553
|
return;
|
|
1429
1554
|
}
|
|
1430
|
-
const parsed = parseIsoNumber(
|
|
1555
|
+
const parsed = parseIsoNumber(text);
|
|
1431
1556
|
if (parsed == null) throw new PrologError('syntax_error(number)');
|
|
1432
1557
|
if (unify(goal.args[0], parsed, next)) yield next;
|
|
1433
1558
|
};
|
|
@@ -1603,6 +1728,22 @@ function* callNthBuiltin({ solver, goal, env }) {
|
|
|
1603
1728
|
}
|
|
1604
1729
|
}
|
|
1605
1730
|
|
|
1731
|
+
function* freezeBuiltin({ solver, goal, env }) {
|
|
1732
|
+
const watched = deref(goal.args[0], env);
|
|
1733
|
+
if (watched.type !== VAR) {
|
|
1734
|
+
const child = solver.cloneForInnerGoal();
|
|
1735
|
+
try {
|
|
1736
|
+
yield* child.solve([callable(goal.args[1], env)], env, 0);
|
|
1737
|
+
} finally {
|
|
1738
|
+
solver.absorbStatsFrom(child);
|
|
1739
|
+
}
|
|
1740
|
+
return;
|
|
1741
|
+
}
|
|
1742
|
+
const next = env.clone();
|
|
1743
|
+
next.delay(watched.name, goal.args[1], goal.module ?? 'user');
|
|
1744
|
+
yield next;
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1606
1747
|
function* phraseBuiltin({ solver, goal, env }) {
|
|
1607
1748
|
const grammarBody = deref(goal.args[0], env);
|
|
1608
1749
|
if (grammarBody.type === VAR) throw new PrologError('instantiation_error');
|
package/src/lib/eyeprolog.pl
CHANGED
|
@@ -622,7 +622,21 @@ number_string(Number, Text) :-
|
|
|
622
622
|
number_string(Number, Text) :-
|
|
623
623
|
var(Number),
|
|
624
624
|
eyeprolog__text_chars(Text, Chars),
|
|
625
|
-
catch(number_chars(Number, Chars), _,
|
|
625
|
+
catch(number_chars(Number, Chars), _,
|
|
626
|
+
catch(( eyeprolog__integer_exponent_chars(Chars, Expanded),
|
|
627
|
+
number_chars(Number, Expanded)
|
|
628
|
+
), _, fail)).
|
|
629
|
+
|
|
630
|
+
eyeprolog__integer_exponent_chars(Chars, Expanded) :-
|
|
631
|
+
eyeprolog__split_exponent(Chars, Mantissa, Marker, Exponent),
|
|
632
|
+
number_chars(Integer, Mantissa),
|
|
633
|
+
integer(Integer),
|
|
634
|
+
eyeprolog__append(Mantissa, ['.','0',Marker|Exponent], Expanded).
|
|
635
|
+
|
|
636
|
+
eyeprolog__split_exponent([e|Exponent], [], e, Exponent).
|
|
637
|
+
eyeprolog__split_exponent(['E'|Exponent], [], 'E', Exponent).
|
|
638
|
+
eyeprolog__split_exponent([C|Chars], [C|Mantissa], Marker, Exponent) :-
|
|
639
|
+
eyeprolog__split_exponent(Chars, Mantissa, Marker, Exponent).
|
|
626
640
|
|
|
627
641
|
atom_string(Atom, Text) :-
|
|
628
642
|
atom(Atom),
|
package/src/lib/prologue.pl
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
nth1/3,
|
|
20
20
|
nth1/4,
|
|
21
21
|
call_nth/2,
|
|
22
|
+
freeze/2,
|
|
22
23
|
foldl/4,
|
|
23
24
|
foldl/5,
|
|
24
25
|
foldl/6,
|
|
@@ -33,6 +34,7 @@
|
|
|
33
34
|
:- meta_predicate(maplist(6, '?', '?', '?', '?', '?', '?')).
|
|
34
35
|
:- meta_predicate(maplist(7, '?', '?', '?', '?', '?', '?', '?')).
|
|
35
36
|
:- meta_predicate(call_nth(0, '?')).
|
|
37
|
+
:- meta_predicate(freeze('?', 0)).
|
|
36
38
|
:- meta_predicate(foldl(3, '?', '?', '?')).
|
|
37
39
|
:- meta_predicate(foldl(4, '?', '?', '?', '?')).
|
|
38
40
|
:- meta_predicate(foldl(5, '?', '?', '?', '?', '?')).
|
|
@@ -159,6 +161,8 @@ nth1(N, List, Elem, Rest) :-
|
|
|
159
161
|
|
|
160
162
|
call_nth(Goal, Nth) :- eyeprolog__call_nth(Goal, Nth).
|
|
161
163
|
|
|
164
|
+
freeze(Var, Goal) :- eyeprolog__freeze(Var, Goal).
|
|
165
|
+
|
|
162
166
|
foldl(_, [], Acc, Acc).
|
|
163
167
|
foldl(Closure, [A|As], Acc0, Acc) :-
|
|
164
168
|
call(Closure, A, Acc0, Acc1),
|
package/src/quads.js
CHANGED
|
@@ -56,12 +56,16 @@ function checkQuad(program, quad, options) {
|
|
|
56
56
|
|
|
57
57
|
function checkDescription(program, quad, description, options) {
|
|
58
58
|
const alternatives = splitOperator(description, '|');
|
|
59
|
-
|
|
59
|
+
// Probe an explicitly accepted nontermination outcome before alternatives
|
|
60
|
+
// that would run the same query without a bound.
|
|
61
|
+
const ordered = [...alternatives].sort((left, right) =>
|
|
62
|
+
Number(alternativeDescribesLoop(right)) - Number(alternativeDescribesLoop(left)));
|
|
63
|
+
for (const alternative of ordered) {
|
|
60
64
|
const malformed = malformedAlternative(quad.query, alternative);
|
|
61
65
|
if (malformed != null) return { ok: false, kind: 'malformed', expected: malformed };
|
|
62
66
|
}
|
|
63
67
|
let unsupported = null;
|
|
64
|
-
for (const alternative of
|
|
68
|
+
for (const alternative of ordered) {
|
|
65
69
|
const checked = checkAlternative(program, quad, alternative, options);
|
|
66
70
|
if (checked.ok) return checked;
|
|
67
71
|
if (checked.kind === 'unsupported') unsupported ??= checked;
|
|
@@ -86,7 +90,10 @@ function checkAlternative(program, quad, alternative, options) {
|
|
|
86
90
|
const maxSolutions = inputSpecs.length > 0
|
|
87
91
|
? 1
|
|
88
92
|
: moreAt < 0 ? describedCount + 1 : Math.max(describedCount, 1);
|
|
89
|
-
const actual = executeQuery(program, quad.query, input, maxSolutions,
|
|
93
|
+
const actual = executeQuery(program, quad.query, input, maxSolutions, {
|
|
94
|
+
...options,
|
|
95
|
+
detectLoops: leaves.some((leaf) => leaf.loops),
|
|
96
|
+
});
|
|
90
97
|
|
|
91
98
|
if (inputSpecs.length > 0) {
|
|
92
99
|
const leaf = leaves[0];
|
|
@@ -137,6 +144,7 @@ function describeLeaf(term) {
|
|
|
137
144
|
sto: false,
|
|
138
145
|
false: false,
|
|
139
146
|
truth: false,
|
|
147
|
+
loops: false,
|
|
140
148
|
error: null,
|
|
141
149
|
input: null,
|
|
142
150
|
output: null,
|
|
@@ -149,7 +157,8 @@ function describeLeaf(term) {
|
|
|
149
157
|
if (item.name === 'unexpected' || item.name === 'inattendue') leaf.unexpected = true;
|
|
150
158
|
else if (item.name === '...' || item.name === 'ad_infinitum') leaf.more = true;
|
|
151
159
|
else if (item.name === 'sto') leaf.sto = true;
|
|
152
|
-
else if (item.name === 'loops'
|
|
160
|
+
else if (item.name === 'loops') leaf.loops = true;
|
|
161
|
+
else if (item.name === 'waits' || item.name === 'other_answer_sequence') {
|
|
153
162
|
leaf.unsupported ??= item;
|
|
154
163
|
} else if (item.name === 'false') leaf.false = true;
|
|
155
164
|
else if (item.name === 'true') leaf.truth = true;
|
|
@@ -181,7 +190,7 @@ function describeLeaf(term) {
|
|
|
181
190
|
if (isErrorDescription(item)) leaf.error = item;
|
|
182
191
|
else leaf.malformed ??= item;
|
|
183
192
|
}
|
|
184
|
-
leaf.hasExpectation = leaf.bindings.length > 0 || leaf.truth || leaf.false || leaf.error != null || leaf.output != null;
|
|
193
|
+
leaf.hasExpectation = leaf.bindings.length > 0 || leaf.truth || leaf.false || leaf.loops || leaf.error != null || leaf.output != null;
|
|
185
194
|
if (!leaf.hasExpectation && !leaf.more && !leaf.sto && leaf.unsupported == null) leaf.malformed ??= term;
|
|
186
195
|
if ([leaf.false, leaf.truth, leaf.error != null].filter(Boolean).length > 1) leaf.malformed ??= term;
|
|
187
196
|
return leaf;
|
|
@@ -192,6 +201,8 @@ function executeQuery(program, query, input, maxSolutions, options) {
|
|
|
192
201
|
const solver = new Solver(program, {
|
|
193
202
|
...options,
|
|
194
203
|
registry: options.registry ?? getEyePrologRegistry(),
|
|
204
|
+
maxDepth: options.detectLoops ? (options.loopMaxDepth ?? 1000) : options.maxDepth,
|
|
205
|
+
maxInferences: options.detectLoops ? (options.loopMaxInferences ?? 10000) : options.maxInferences,
|
|
195
206
|
// The solver's counter also observes completed nested searches (for
|
|
196
207
|
// example each arm of a DCG disjunction). Bound the public iterator here
|
|
197
208
|
// instead of letting those internal completions consume the quad's answer
|
|
@@ -225,10 +236,17 @@ function executeQuery(program, query, input, maxSolutions, options) {
|
|
|
225
236
|
error = { term: errorTerm(caught), output: pendingOutput };
|
|
226
237
|
}
|
|
227
238
|
const inputPosition = solver.io.resolve('user_input')?.position ?? 0;
|
|
228
|
-
return {
|
|
239
|
+
return {
|
|
240
|
+
solutions,
|
|
241
|
+
error,
|
|
242
|
+
tailOutput,
|
|
243
|
+
inputPosition,
|
|
244
|
+
loops: solver.depthLimitExceeded || solver.inferenceLimitExceeded,
|
|
245
|
+
};
|
|
229
246
|
}
|
|
230
247
|
|
|
231
248
|
function matchLeaf(query, leaf, actual, position) {
|
|
249
|
+
if (leaf.loops) return actual.loops;
|
|
232
250
|
if (leaf.false) {
|
|
233
251
|
return position >= actual.solutions.length && actual.error == null && outputMatches(leaf.output, actual.tailOutput);
|
|
234
252
|
}
|
|
@@ -241,6 +259,10 @@ function matchLeaf(query, leaf, actual, position) {
|
|
|
241
259
|
return substitutionMatches(query, leaf.bindings, solution.env);
|
|
242
260
|
}
|
|
243
261
|
|
|
262
|
+
function alternativeDescribesLoop(alternative) {
|
|
263
|
+
return splitOperator(alternative, ';').some((term) => describeLeaf(term).loops);
|
|
264
|
+
}
|
|
265
|
+
|
|
244
266
|
function substitutionMatches(query, bindings, actualEnv) {
|
|
245
267
|
const queryVariables = namedVariables(query);
|
|
246
268
|
const queryNames = new Set(queryVariables.map((variable) => variable.name));
|
package/src/solver.js
CHANGED
|
@@ -29,6 +29,10 @@ export class Solver {
|
|
|
29
29
|
this.mutableProgram = program.mutable === true;
|
|
30
30
|
this.programRevision = this.program.revision ?? 0;
|
|
31
31
|
this.maxDepth = options.maxDepth ?? 100000;
|
|
32
|
+
this.depthLimitExceeded = false;
|
|
33
|
+
this.maxInferences = options.maxInferences ?? Infinity;
|
|
34
|
+
this.inferences = 0;
|
|
35
|
+
this.inferenceLimitExceeded = false;
|
|
32
36
|
this.solutionLimit = options.solutionLimit ?? 10000000;
|
|
33
37
|
this.solutionsSeen = 0;
|
|
34
38
|
this.prologFlags = options.prologFlags ?? defaultPrologFlags(this.registry?.eyePrologLibrary ? 'fail' : 'error');
|
|
@@ -83,6 +87,7 @@ export class Solver {
|
|
|
83
87
|
const solver = new Solver(this.program, {
|
|
84
88
|
registry: this.registry,
|
|
85
89
|
maxDepth: this.maxDepth,
|
|
90
|
+
maxInferences: this.maxInferences,
|
|
86
91
|
solutionLimit,
|
|
87
92
|
prologFlags: this.prologFlags,
|
|
88
93
|
charConversions: this.charConversions,
|
|
@@ -110,6 +115,8 @@ export class Solver {
|
|
|
110
115
|
|
|
111
116
|
absorbStatsFrom(child) {
|
|
112
117
|
if (!child || child === this || !child.stats) return;
|
|
118
|
+
this.depthLimitExceeded ||= child.depthLimitExceeded;
|
|
119
|
+
this.inferenceLimitExceeded ||= child.inferenceLimitExceeded;
|
|
113
120
|
for (const [key, value] of Object.entries(child.stats)) {
|
|
114
121
|
if (key === 'max_depth' || key === 'max_goal_count') {
|
|
115
122
|
this.stats[key] = Math.max(this.stats[key] ?? 0, value ?? 0);
|
|
@@ -140,6 +147,11 @@ export class Solver {
|
|
|
140
147
|
registeredStack = stack;
|
|
141
148
|
this.solveStacks.push(stack);
|
|
142
149
|
while (stack.length) {
|
|
150
|
+
this.inferences++;
|
|
151
|
+
if (this.inferences > this.maxInferences) {
|
|
152
|
+
this.inferenceLimitExceeded = true;
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
143
155
|
const frame = stack.pop();
|
|
144
156
|
this.syncProgramRevision();
|
|
145
157
|
if (frame.kind === 'resumeBuiltin') {
|
|
@@ -185,11 +197,31 @@ export class Solver {
|
|
|
185
197
|
let active = frame.active;
|
|
186
198
|
|
|
187
199
|
while (true) {
|
|
200
|
+
this.inferences++;
|
|
201
|
+
if (this.inferences > this.maxInferences) {
|
|
202
|
+
this.inferenceLimitExceeded = true;
|
|
203
|
+
stack.length = 0;
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
188
206
|
this.syncProgramRevision();
|
|
189
207
|
this.stats.solve_goals_calls++;
|
|
190
208
|
this.stats.max_depth = Math.max(this.stats.max_depth, depth);
|
|
191
209
|
this.stats.max_goal_count = Math.max(this.stats.max_goal_count, goals.length);
|
|
192
|
-
if (depth > this.maxDepth
|
|
210
|
+
if (depth > this.maxDepth) {
|
|
211
|
+
this.depthLimitExceeded = true;
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
if (this.solutionsSeen >= this.solutionLimit) break;
|
|
215
|
+
|
|
216
|
+
const readyDelays = env.takeReadyDelays();
|
|
217
|
+
if (readyDelays.length > 0) {
|
|
218
|
+
const awakened = readyDelays.map(({ goal, module }) => {
|
|
219
|
+
const delayed = copyResolved(goal, env);
|
|
220
|
+
qualifyTerm(delayed, module);
|
|
221
|
+
return delayed;
|
|
222
|
+
});
|
|
223
|
+
goals = [...awakened, ...goals];
|
|
224
|
+
}
|
|
193
225
|
|
|
194
226
|
if (goals.length === 0) {
|
|
195
227
|
this.solutionsSeen++;
|
|
@@ -348,7 +380,11 @@ export class Solver {
|
|
|
348
380
|
|
|
349
381
|
*solveUserGoal(goal, rest, env, depth) {
|
|
350
382
|
this.stats.solve_one_goal_calls++;
|
|
351
|
-
if (depth > this.maxDepth
|
|
383
|
+
if (depth > this.maxDepth) {
|
|
384
|
+
this.depthLimitExceeded = true;
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
if (this.solutionsSeen >= this.solutionLimit) return;
|
|
352
388
|
if (goal.type !== COMPOUND && goal.type !== 'atom') return;
|
|
353
389
|
const group = this.program.findGroup(goal.name, goal.arity, goal.module ?? 'user');
|
|
354
390
|
if (!group) return;
|
package/src/standard-library.js
CHANGED
|
@@ -35,7 +35,7 @@ function libraryUrl(filename) {
|
|
|
35
35
|
return url;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
export const eyePrologNativeLibraryIndicators = Object.freeze(['call_nth/2']);
|
|
38
|
+
export const eyePrologNativeLibraryIndicators = Object.freeze(['call_nth/2', 'freeze/2']);
|
|
39
39
|
export const eyePrologPortableLibraryIndicators = Object.freeze([
|
|
40
40
|
'uuid/3', 'difference/3', 'maplist/2', 'maplist/3', 'maplist/4', 'maplist/5',
|
|
41
41
|
'maplist/6', 'maplist/7', 'maplist/8', 'lt/2', 'gt/2', 'le/2', 'ge/2',
|
package/src/term.js
CHANGED
|
@@ -45,6 +45,7 @@ export class Env {
|
|
|
45
45
|
cacheValue: undefined,
|
|
46
46
|
cache: null,
|
|
47
47
|
};
|
|
48
|
+
this._delays = null;
|
|
48
49
|
}
|
|
49
50
|
clone() {
|
|
50
51
|
// Most speculative environments are either rejected without a binding or
|
|
@@ -54,6 +55,7 @@ export class Env {
|
|
|
54
55
|
// occasionally flattened.
|
|
55
56
|
const clone = Object.create(Env.prototype);
|
|
56
57
|
clone._state = this._state;
|
|
58
|
+
clone._delays = this._delays;
|
|
57
59
|
return clone;
|
|
58
60
|
}
|
|
59
61
|
has(name) {
|
|
@@ -125,6 +127,24 @@ export class Env {
|
|
|
125
127
|
cache: null,
|
|
126
128
|
};
|
|
127
129
|
}
|
|
130
|
+
delay(name, goal, module = 'user') {
|
|
131
|
+
const delays = new Map(this._delays ?? []);
|
|
132
|
+
delays.set(name, [...(delays.get(name) ?? []), { goal, module }]);
|
|
133
|
+
this._delays = delays;
|
|
134
|
+
}
|
|
135
|
+
takeReadyDelays() {
|
|
136
|
+
if (this._delays == null || this._delays.size === 0) return [];
|
|
137
|
+
const ready = [];
|
|
138
|
+
let remaining = this._delays;
|
|
139
|
+
for (const [name, delays] of this._delays) {
|
|
140
|
+
if (deref(variable(name), this).type === VAR) continue;
|
|
141
|
+
if (remaining === this._delays) remaining = new Map(this._delays);
|
|
142
|
+
remaining.delete(name);
|
|
143
|
+
ready.push(...delays);
|
|
144
|
+
}
|
|
145
|
+
if (ready.length > 0) this._delays = remaining;
|
|
146
|
+
return ready;
|
|
147
|
+
}
|
|
128
148
|
}
|
|
129
149
|
|
|
130
150
|
export function deref(term, env) {
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
% ids refer to http://www.complang.tuwien.ac.at/ulrich/iso-prolog/length#1
|
|
2
|
+
|
|
3
|
+
a1 ?- atom_length(A,N).
|
|
4
|
+
instantiation_error.
|
|
5
|
+
a2 ?- atom_length(a,a).
|
|
6
|
+
type_error(integer,a).
|
|
7
|
+
a3 ?- atom_length(a,1.1).
|
|
8
|
+
type_error(integer,1.1).
|
|
9
|
+
a4 ?- atom_length(a,-1).
|
|
10
|
+
domain_error(not_less_than_zero,-1).
|
|
11
|
+
a5 ?- atom_length(1,N).
|
|
12
|
+
type_error(atom,1).
|
|
13
|
+
1 ?- length(L,N).
|
|
14
|
+
L = [], N = 0
|
|
15
|
+
; L = [_A], N = 1
|
|
16
|
+
; L = [_A,_B], N = 2
|
|
17
|
+
; ... .
|
|
18
|
+
2 ?- length(L,0).
|
|
19
|
+
L = [].
|
|
20
|
+
3 ?- length([_|L],0).
|
|
21
|
+
false.
|
|
22
|
+
4 ?- length(2,0).
|
|
23
|
+
false.
|
|
24
|
+
5 ?- length([_|2],0).
|
|
25
|
+
false.
|
|
26
|
+
6 ?- length([_|2],N).
|
|
27
|
+
false.
|
|
28
|
+
7 ?- length([_|2],2).
|
|
29
|
+
false.
|
|
30
|
+
8 ?- length(L,-1).
|
|
31
|
+
domain_error(not_less_than_zero,-1).
|
|
32
|
+
9 ?- length([],-1).
|
|
33
|
+
domain_error(not_less_than_zero,-1).
|
|
34
|
+
10 ?- length(a,-1).
|
|
35
|
+
domain_error(not_less_than_zero,-1).
|
|
36
|
+
11 ?- length([],-0.1).
|
|
37
|
+
type_error(integer,-0.1).
|
|
38
|
+
12 ?- length(L,-0.1).
|
|
39
|
+
type_error(integer,-0.1).
|
|
40
|
+
13 ?- length([a],1.0).
|
|
41
|
+
type_error(integer,1.0).
|
|
42
|
+
14 ?- length(L,1.0).
|
|
43
|
+
type_error(integer,1.0).
|
|
44
|
+
15 ?- length(L,1.1).
|
|
45
|
+
type_error(integer,1.1).
|
|
46
|
+
16 ?- length(L,1.0e99).
|
|
47
|
+
type_error(integer,1.0e99).
|
|
48
|
+
17 ?- N is 2^52, length([], N).
|
|
49
|
+
false.
|
|
50
|
+
18 ?- length([],0+0).
|
|
51
|
+
type_error(integer,0+0).
|
|
52
|
+
19 ?- length([],-_).
|
|
53
|
+
type_error(integer,-_).
|
|
54
|
+
20 ?- length([a],-_).
|
|
55
|
+
type_error(integer,-_).
|
|
56
|
+
21 ?- length([a,b|X],X).
|
|
57
|
+
resource_error(finite_memory)
|
|
58
|
+
| resource_error(...)
|
|
59
|
+
| loops.
|
|
60
|
+
22 ?- length(L,L).
|
|
61
|
+
resource_error(finite_memory)
|
|
62
|
+
| resource_error(...)
|
|
63
|
+
| loops.
|
|
64
|
+
23 ?- L = [_|_], length(L,L).
|
|
65
|
+
type_error(integer,[_|_]).
|
|
66
|
+
24 ?- L = [_], length(L,L).
|
|
67
|
+
type_error(integer,[_]).
|
|
68
|
+
25 ?- L = [1], length(L,L).
|
|
69
|
+
type_error(integer,[1]).
|
|
70
|
+
26 ?- L = [a|L], length(L,N).
|
|
71
|
+
sto, false % current_prolog_flag(occurs_check, true)
|
|
72
|
+
| sto, resource_error(finite_memory)
|
|
73
|
+
| sto, resource_error(...)
|
|
74
|
+
| sto, loops
|
|
75
|
+
| sto, L = [a,a], N = 2
|
|
76
|
+
; L = [a,a,_A], N = 3
|
|
77
|
+
; ... . % dag representation, literal substitution, Tau
|
|
78
|
+
27 ?- L = [a|L], length(L,0).
|
|
79
|
+
sto, false.
|
|
80
|
+
28 ?- L = [a|L], length(L,7).
|
|
81
|
+
sto, false
|
|
82
|
+
| sto, L = [a,a,_A,_B,_C,_D,_E]. % tau
|
|
83
|
+
29 ?- freeze(L,L=[]), length(L,L).
|
|
84
|
+
false.
|
|
85
|
+
30 ?- freeze(L,L=[_|L]), length(L,N).
|
|
86
|
+
sto, loops
|
|
87
|
+
| sto, resource_error(...).
|
|
88
|
+
31 ?- freeze(L,L=[_|L]), N is 2^64, length(L,N).
|
|
89
|
+
sto, false.
|
|
90
|
+
32 ?- length([a,b|L], N).
|
|
91
|
+
L = [], N = 2
|
|
92
|
+
; L = [_A], N = 3
|
|
93
|
+
; L = [_A,_B], N = 4
|
|
94
|
+
; ... .
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
% ids refer to https://www.complang.tuwien.ac.at/ulrich/iso-prolog/number_chars_cont
|
|
2
|
+
|
|
3
|
+
1 ?- number_chars(1.2,"1.2").
|
|
4
|
+
true.
|
|
5
|
+
|
|
6
|
+
55 ?- number_chars(1.2,"1.20").
|
|
7
|
+
true.
|
|
8
|
+
|
|
9
|
+
2 ?- number_chars(1.0e9,"1.0E9").
|
|
10
|
+
true.
|
|
11
|
+
|
|
12
|
+
56 ?- number_chars(1.0e9,"1.0e9").
|
|
13
|
+
true.
|
|
14
|
+
|
|
15
|
+
64 ?- number_chars(0.0,"-0.0").
|
|
16
|
+
true.
|
|
17
|
+
|
|
18
|
+
3 ?- number_chars(1,"01").
|
|
19
|
+
true.
|
|
20
|
+
|
|
21
|
+
65 ?- number_chars(10,"010").
|
|
22
|
+
true.
|
|
23
|
+
|
|
24
|
+
66 ?- number_chars(N,"010").
|
|
25
|
+
N=10.
|
|
26
|
+
|
|
27
|
+
67 ?- number_chars(N,"08").
|
|
28
|
+
N=8.
|
|
29
|
+
|
|
30
|
+
68 ?- number_chars(N,"0b11").
|
|
31
|
+
N=3.
|
|
32
|
+
|
|
33
|
+
69 ?- number_chars(N,"0o11").
|
|
34
|
+
N=9.
|
|
35
|
+
|
|
36
|
+
70 ?- number_chars(N,"0x11").
|
|
37
|
+
N=17.
|
|
38
|
+
|
|
39
|
+
4 ?- number_chars(1,"a").
|
|
40
|
+
syntax_error(...).
|
|
41
|
+
|
|
42
|
+
5 ?- number_chars(1,[]).
|
|
43
|
+
syntax_error(...).
|
|
44
|
+
|
|
45
|
+
6 ?- number_chars(1,[[]]).
|
|
46
|
+
type_error(character,[]).
|
|
47
|
+
|
|
48
|
+
7 ?- number_chars(1,[' ',[]]).
|
|
49
|
+
type_error(character,[]).
|
|
50
|
+
|
|
51
|
+
8 ?- number_chars(1,[0]).
|
|
52
|
+
type_error(character,0).
|
|
53
|
+
|
|
54
|
+
9 ?- number_chars(1,[_,[]]).
|
|
55
|
+
type_error(character,[]).
|
|
56
|
+
|
|
57
|
+
10 ?- number_chars(N,[X]).
|
|
58
|
+
instantiation_error.
|
|
59
|
+
|
|
60
|
+
11 ?- number_chars(N,['0'|_]).
|
|
61
|
+
instantiation_error.
|
|
62
|
+
|
|
63
|
+
12 ?- number_chars(N,'1').
|
|
64
|
+
type_error(list,'1').
|
|
65
|
+
|
|
66
|
+
13 ?- number_chars(N,[a|a]).
|
|
67
|
+
type_error(list,[a|a]).
|
|
68
|
+
|
|
69
|
+
14 ?- number_chars(N,[49]).
|
|
70
|
+
type_error(character,49).
|
|
71
|
+
|
|
72
|
+
15 ?- number_chars(N,[]).
|
|
73
|
+
syntax_error(...).
|
|
74
|
+
|
|
75
|
+
16 ?- number_chars(N,"3 ").
|
|
76
|
+
syntax_error(...).
|
|
77
|
+
|
|
78
|
+
17 ?- number_chars(N,"3.").
|
|
79
|
+
syntax_error(...).
|
|
80
|
+
|
|
81
|
+
18 ?- number_chars(N," 1").
|
|
82
|
+
N = 1.
|
|
83
|
+
|
|
84
|
+
19 ?- number_chars(N,"\n1").
|
|
85
|
+
N = 1.
|
|
86
|
+
|
|
87
|
+
20 ?- number_chars(N," 0'a").
|
|
88
|
+
N = 0'a. N = 97.
|
|
89
|
+
|
|
90
|
+
58 ?- number_chars(N,"0'").
|
|
91
|
+
syntax_error(...).
|
|
92
|
+
|
|
93
|
+
59 ?- number_chars(N,"0'\n").
|
|
94
|
+
syntax_error(...).
|
|
95
|
+
|
|
96
|
+
60 ?- number_chars(N,"0'\\n").
|
|
97
|
+
N=0'\n. N=10.
|
|
98
|
+
|
|
99
|
+
61 ?- number_chars(N,"0'\\7\\").
|
|
100
|
+
N=7.
|
|
101
|
+
|
|
102
|
+
71 ?- number_chars(N,"0'\\7").
|
|
103
|
+
syntax_error(...).
|
|
104
|
+
|
|
105
|
+
62 ?- number_chars(N,"0'.").
|
|
106
|
+
N=0'. . N = 46.
|
|
107
|
+
|
|
108
|
+
21 ?- number_chars(N,"- 1").
|
|
109
|
+
N = -1.
|
|
110
|
+
|
|
111
|
+
54 ?- number_chars(N,"'-'1").
|
|
112
|
+
N = -1.
|
|
113
|
+
|
|
114
|
+
22 ?- number_chars(N,"/**/1").
|
|
115
|
+
N = 1.
|
|
116
|
+
|
|
117
|
+
23 ?- number_chars(N,"%\n1").
|
|
118
|
+
N = 1.
|
|
119
|
+
|
|
120
|
+
57 ?- number_chars(N,"- /**/1").
|
|
121
|
+
N = -1.
|
|
122
|
+
|
|
123
|
+
24 ?- number_chars(N,"-/**/1").
|
|
124
|
+
syntax_error(...).
|
|
125
|
+
|
|
126
|
+
63 ?- number_chars(N,"'\\\n-' 3").
|
|
127
|
+
N= -3.
|
|
128
|
+
|
|
129
|
+
25 ?- number_chars(N,"1e1").
|
|
130
|
+
syntax_error(...).
|
|
131
|
+
|
|
132
|
+
26 ?- number_chars(N,"1.0e").
|
|
133
|
+
syntax_error(...).
|
|
134
|
+
|
|
135
|
+
27 ?- number_chars(N,"1.0ee").
|
|
136
|
+
syntax_error(...).
|
|
137
|
+
|
|
138
|
+
28 ?- number_chars(N,"0x1").
|
|
139
|
+
N = 1.
|
|
140
|
+
|
|
141
|
+
29 ?- number_chars(N,"0X1").
|
|
142
|
+
syntax_error(...).
|
|
143
|
+
|
|
144
|
+
30 ?- number_chars(N,"1E1").
|
|
145
|
+
syntax_error(...).
|
|
146
|
+
|
|
147
|
+
47 ?- number_chars(1,['.'|_]).
|
|
148
|
+
false.
|
|
149
|
+
|
|
150
|
+
48 ?- number_chars(N,"+1").
|
|
151
|
+
syntax_error(...).
|
|
152
|
+
|
|
153
|
+
49 ?- number_chars(N,"+ 1").
|
|
154
|
+
syntax_error(...).
|
|
155
|
+
|
|
156
|
+
50 ?- number_chars(N,"'+'1").
|
|
157
|
+
syntax_error(...).
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
51 ?- number_chars(N,['11']).
|
|
161
|
+
type_error(character,'11').
|
|
162
|
+
|
|
163
|
+
52 ?- number_chars(N,['1.1']).
|
|
164
|
+
type_error(character,'1.1').
|
|
165
|
+
|
|
166
|
+
53 ?- number_chars(1+1,"2").
|
|
167
|
+
type_error(number,1+1).
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
31 ?- number_chars(1,[C]).
|
|
171
|
+
C = '1'.
|
|
172
|
+
|
|
173
|
+
32 ?- number_chars(1,[C,D]).
|
|
174
|
+
false.
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
33 ?- number_chars(1,[C,C]).
|
|
178
|
+
false.
|
|
179
|
+
|
|
180
|
+
34 ?- number_chars(0,[C,C]).
|
|
181
|
+
false.
|
|
182
|
+
|
|
183
|
+
35 ?- number_chars(10,[C,D]).
|
|
184
|
+
C = '1', D = '0'.
|
|
185
|
+
|
|
186
|
+
36 ?- number_chars(100,[C,D]).
|
|
187
|
+
false.
|
|
188
|
+
|
|
189
|
+
37 ?- number_chars(N,[X|2]).
|
|
190
|
+
type_error(list,[_|2])
|
|
191
|
+
| instantiation_error.
|
|
192
|
+
|
|
193
|
+
38 ?- number_chars(N,[1|_]).
|
|
194
|
+
instantiation_error
|
|
195
|
+
| type_error(character,1).
|
|
196
|
+
|
|
197
|
+
39 ?- number_chars(V,[1|2]).
|
|
198
|
+
type_error(list,[1|2])
|
|
199
|
+
| type_error(character,1).
|
|
200
|
+
|
|
201
|
+
40 ?- number_chars([],1).
|
|
202
|
+
type_error(number,[])
|
|
203
|
+
| type_error(list,1).
|
|
204
|
+
|
|
205
|
+
41 ?- number_chars(1,1).
|
|
206
|
+
type_error(list,1).
|
|
207
|
+
|
|
208
|
+
42 ?- number_chars(1,[a|2]).
|
|
209
|
+
type_error(list,[a|2]).
|
|
210
|
+
|
|
211
|
+
43 ?- number_chars(1,[_|2]).
|
|
212
|
+
type_error(list,[_|2]).
|
|
213
|
+
|
|
214
|
+
44 ?- number_chars(1,[[]|_]).
|
|
215
|
+
type_error(character,[]).
|
|
216
|
+
|
|
217
|
+
45 ?- number_chars(1,[[]|2]).
|
|
218
|
+
type_error(character,[])
|
|
219
|
+
| type_error(list,[[]|2]).
|
|
220
|
+
|
|
221
|
+
72 ?- number_chars(V,[1,[],X|2]).
|
|
222
|
+
type_error(list,[1,[],_A|2])
|
|
223
|
+
| type_error(character,1)
|
|
224
|
+
| type_error(character,[])
|
|
225
|
+
| instantiation_error.
|
|
226
|
+
|
|
227
|
+
46 ?- L=['1'|L], number_chars(N,L).
|
|
228
|
+
sto, ... ; ... .
|
|
229
|
+
( sto, type_error(list,['1'|...]) % rational trees
|
|
230
|
+
| sto, false % occurs-check
|
|
231
|
+
| sto, representation_error(term)
|
|
232
|
+
| sto, instantiation_error % literal substitutions
|
|
233
|
+
| sto, resource_error(...)
|
|
234
|
+
| sto, loops
|
|
235
|
+
).
|
package/test/run-regression.mjs
CHANGED
|
@@ -322,6 +322,20 @@ c4 ?- call((!;1)).
|
|
|
322
322
|
assertEqual(result.stdout, 'quads: 58 run, 58 passed, 0 failed.\n', 'quad report');
|
|
323
323
|
},
|
|
324
324
|
},
|
|
325
|
+
{
|
|
326
|
+
name: 'runQuads passes the complete vendored number_chars continuation corpus',
|
|
327
|
+
run: () => {
|
|
328
|
+
const filename = path.join(testRoot, 'fixtures', 'number_chars_cont_quad.pl');
|
|
329
|
+
const source = fs.readFileSync(filename, 'utf8');
|
|
330
|
+
const result = publicApi.runQuads(Program.parseSources([{
|
|
331
|
+
text: source,
|
|
332
|
+
filename,
|
|
333
|
+
}]));
|
|
334
|
+
assertEqual(result.total, 72, 'quad total');
|
|
335
|
+
assertEqual(result.passed, 72, 'quad passed');
|
|
336
|
+
assertEqual(result.stdout, 'quads: 72 run, 72 passed, 0 failed.\n', 'quad report');
|
|
337
|
+
},
|
|
338
|
+
},
|
|
325
339
|
{
|
|
326
340
|
name: 'runQuads passes the complete vendored Prolog Prologue quad corpus',
|
|
327
341
|
run: () => {
|
|
@@ -352,6 +366,28 @@ c4 ?- call((!;1)).
|
|
|
352
366
|
assertEqual(result.stdout, 'quads: 13 run, 13 passed, 0 failed.\n', 'quad report');
|
|
353
367
|
},
|
|
354
368
|
},
|
|
369
|
+
{
|
|
370
|
+
name: 'CLI passes the complete authoritative length quad corpus',
|
|
371
|
+
run: () => {
|
|
372
|
+
const filename = path.join(testRoot, 'fixtures', 'length_quad.pl');
|
|
373
|
+
const source = fs.readFileSync(filename, 'utf8');
|
|
374
|
+
assertEqual(Program.parse(source).quads.length, 37, 'vendored quad total');
|
|
375
|
+
const result = runCli(['-q', filename]);
|
|
376
|
+
assertEqual(result.status, 0, 'quad exit status');
|
|
377
|
+
assertEqual(result.stdout, 'quads: 37 run, 37 passed, 0 failed.\n', 'quad report');
|
|
378
|
+
assertEqual(result.stderr, '', 'quad stderr');
|
|
379
|
+
},
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
name: 'Prologue freeze wakes delayed goals with their bindings',
|
|
383
|
+
run: () => {
|
|
384
|
+
const result = runEyeProlog(
|
|
385
|
+
':- use_module(library(prologue)).\nwake(X, Y) :- freeze(X, Y = awake), X = ready.\n',
|
|
386
|
+
{ goal: 'wake(X, Y)' },
|
|
387
|
+
);
|
|
388
|
+
assertEqual(result.stdout, 'wake(ready, awake).\n', 'freeze answer');
|
|
389
|
+
},
|
|
390
|
+
},
|
|
355
391
|
{
|
|
356
392
|
name: 'runQuads covers the remaining finite Prologue examples and arities',
|
|
357
393
|
run: () => {
|
|
@@ -378,11 +414,11 @@ c4 ?- call((!;1)).
|
|
|
378
414
|
},
|
|
379
415
|
},
|
|
380
416
|
{
|
|
381
|
-
name: 'runQuads
|
|
417
|
+
name: 'runQuads recognizes bounded nontermination descriptions',
|
|
382
418
|
run: () => {
|
|
383
419
|
const result = publicApi.runQuads(`?- repeat, fail.\n loops.\n`);
|
|
384
|
-
assertEqual(result.
|
|
385
|
-
|
|
420
|
+
assertEqual(result.passed, 1, 'quad passed');
|
|
421
|
+
assertEqual(result.stdout, 'quads: 1 run, 1 passed, 0 failed.\n', 'quad report');
|
|
386
422
|
},
|
|
387
423
|
},
|
|
388
424
|
{
|
|
@@ -1378,17 +1414,19 @@ open(X) :- candidate(X), \\+ closed(X).
|
|
|
1378
1414
|
assertEqual(Boolean(registry.get('is', 2)), true, 'ISO is/2 exists');
|
|
1379
1415
|
assertEqual(Boolean(registry.get('append', 3)), false, 'append/3 is not ISO core');
|
|
1380
1416
|
assertEqual(library.eyePrologLibrary, true, 'complete registry marker');
|
|
1381
|
-
assertEqual(library.defs.size,
|
|
1417
|
+
assertEqual(library.defs.size, 131, 'EyeProlog registry contains ISO definitions and two private library adapters');
|
|
1382
1418
|
assertEqual(Boolean(registry.get('phrase', 2)), true, 'Part 3 phrase/2 exists');
|
|
1383
1419
|
assertEqual(Boolean(registry.get('phrase', 3)), true, 'Part 3 phrase/3 exists');
|
|
1384
|
-
assertEqual(registeredNativeEyePrologLibraryNames().length,
|
|
1420
|
+
assertEqual(registeredNativeEyePrologLibraryNames().length, 2, 'public native EyeProlog builtin count');
|
|
1385
1421
|
assertEqual(eyePrologPortableLibraryIndicators.length, 56, 'portable Prolog library count');
|
|
1386
|
-
assertEqual(eyePrologNativeLibraryIndicators.length,
|
|
1387
|
-
assertEqual(eyePrologNativeLibraryIndicators.join(','), 'call_nth/2', 'control
|
|
1388
|
-
assertEqual(eyePrologLibraryIndicators.length,
|
|
1422
|
+
assertEqual(eyePrologNativeLibraryIndicators.length, 2, 'native host library count');
|
|
1423
|
+
assertEqual(eyePrologNativeLibraryIndicators.join(','), 'call_nth/2,freeze/2', 'control predicates requiring host support');
|
|
1424
|
+
assertEqual(eyePrologLibraryIndicators.length, 58, 'complete EyeProlog library surface');
|
|
1389
1425
|
assertEqual(registry.get('eyeprolog__call_nth', 2), null, 'private call_nth adapter is absent from ISO registry');
|
|
1390
1426
|
assertEqual(Boolean(library.get('eyeprolog__call_nth', 2)), true, 'private call_nth adapter is registered for EyeProlog');
|
|
1391
1427
|
assertEqual(library.get('eyeprolog__call_nth', 2)?.eyePrologLibrary, true, 'private adapter is marked as library support');
|
|
1428
|
+
assertEqual(registry.get('eyeprolog__freeze', 2), null, 'private freeze adapter is absent from ISO registry');
|
|
1429
|
+
assertEqual(Boolean(library.get('eyeprolog__freeze', 2)), true, 'private freeze adapter is registered for EyeProlog');
|
|
1392
1430
|
assertEqual(library.get('between', 3), null, 'between/3 remains portable Prolog');
|
|
1393
1431
|
assertEqual(library.get('smallest_divisor_from', 3), null, 'smallest_divisor_from/3 remains portable Prolog');
|
|
1394
1432
|
assertEqual(library.get('random', 3), null, 'random/3 remains portable Prolog');
|
package/the-art-of-eyeprolog.md
CHANGED
|
@@ -5636,7 +5636,7 @@ quoted_atom("ab"). % quoted_atom(ab)
|
|
|
5636
5636
|
| `sub_atom(+Atom,?Before,?Length,?After,?SubAtom)` | Enumerates substrings and their Unicode-code-point offsets. Supplied counts must be nonnegative integers. |
|
|
5637
5637
|
| `atom_chars(?Atom,?Chars)`, `atom_codes(?Atom,?Codes)` | Convert between an atom and a proper list of one-character atoms or Unicode scalar codes. At least one side must be instantiated. |
|
|
5638
5638
|
| `char_code(?Character,?Code)` | Converts one character atom and one Unicode scalar code. Surrogates and values outside `0..0x10ffff` raise a representation error. |
|
|
5639
|
-
| `number_chars(?Number,?Chars)`, `number_codes(?Number,?Codes)` | Convert finite numbers to canonical text or parse a proper character/code list. At least one side must be instantiated; malformed numeric input raises *syntax_error(number)*. |
|
|
5639
|
+
| `number_chars(?Number,?Chars)`, `number_codes(?Number,?Codes)` | Convert finite numbers to canonical text or parse a proper character/code list using ISO numeric-token syntax, including radix integers, character-code constants, and leading layout. At least one side must be instantiated; malformed numeric input raises *syntax_error(number)*. |
|
|
5640
5640
|
|
|
5641
5641
|
Conversions accept partial output lists when the atomic input is known, but
|
|
5642
5642
|
constructing an atom or number requires a complete proper list with no unbound
|
|
@@ -5761,13 +5761,13 @@ so side effects occur in Prolog execution order.
|
|
|
5761
5761
|
|
|
5762
5762
|
### The EyeProlog library
|
|
5763
5763
|
|
|
5764
|
-
EyeProlog exposes **
|
|
5764
|
+
EyeProlog exposes **58 library predicate indicators** in addition to the 129
|
|
5765
5765
|
indicators in its isolated ISO profile. **56 are implemented entirely as
|
|
5766
5766
|
ordinary Prolog clauses** across `src/lib/eyeprolog.pl`, `src/lib/lists.pl`, and
|
|
5767
|
-
`src/lib/prologue.pl`. The Prologue `call_nth/2`
|
|
5768
|
-
|
|
5769
|
-
surface is therefore **
|
|
5770
|
-
runtime registry contains the 129 ISO definitions plus
|
|
5767
|
+
`src/lib/prologue.pl`. The Prologue `call_nth/2` and `freeze/2` clauses delegate
|
|
5768
|
+
their control behavior to private host adapters. The resulting normal EyeProlog
|
|
5769
|
+
language surface is therefore **187 public predicate indicators**. Internally, the
|
|
5770
|
+
runtime registry contains the 129 ISO definitions plus those two private adapters;
|
|
5771
5771
|
the remaining EyeProlog relations are module source clauses.
|
|
5772
5772
|
|
|
5773
5773
|
The three Prolog files declare `eyeprolog`, `lists`, and `prologue` with
|
|
@@ -5776,8 +5776,8 @@ The three Prolog files declare `eyeprolog`, `lists`, and `prologue` with
|
|
|
5776
5776
|
`use_module(library(prologue))`; `use_module/2` can select a smaller import
|
|
5777
5777
|
list. The last module implements p.p.1 through p.p.11 of the
|
|
5778
5778
|
[working-draft Prologue](https://www.complang.tuwien.ac.at/ulrich/iso-prolog/prologue),
|
|
5779
|
-
with the published `call_nth/2
|
|
5780
|
-
regressions. `src/standard-library.js` registers the module sources and private
|
|
5779
|
+
with the published Prologue, `call_nth/2`, and `length/2` quads retained as
|
|
5780
|
+
offline regressions. `src/standard-library.js` registers the module sources and private
|
|
5781
5781
|
control adapter for Node and browser resolution, and never adds clauses
|
|
5782
5782
|
implicitly.
|
|
5783
5783
|
The isolated ISO-only registry remains
|
|
@@ -5785,13 +5785,18 @@ available through `createDefaultRegistry()` and `getDefaultRegistry()` for
|
|
|
5785
5785
|
conformance work and advanced embedders. Module-local predicate identity keeps
|
|
5786
5786
|
private helpers and same-named predicates in different modules separate.
|
|
5787
5787
|
|
|
5788
|
+
`freeze(?Term,:Goal)` runs `Goal` immediately when `Term` is already nonvariable;
|
|
5789
|
+
otherwise it delays the goal until `Term` becomes nonvariable. Suspensions are
|
|
5790
|
+
kept in the logical environment, so bindings and backtracking remain isolated
|
|
5791
|
+
between solution branches.
|
|
5792
|
+
|
|
5788
5793
|
<!-- eyeprolog-library-catalog:start -->
|
|
5789
5794
|
|
|
5790
5795
|
| Module | Exported predicate indicators |
|
|
5791
5796
|
| --- | --- |
|
|
5792
5797
|
| `library(lists)` | `maplist/3`, `append/3`, `member/2`, `select/3`, `last/2`, `nth0/3`, `nth1/3`, `reverse/2`, `length/2`, `sum_list/2`, `min_list/2`, `max_list/2`, `list_to_set/2`, `countall/2` |
|
|
5793
5798
|
| `library(eyeprolog)` | `uuid/3`, `difference/3`, `lt/2`, `le/2`, `gt/2`, `ge/2`, `between/3`, `smallest_divisor_from/3`, `random/3`, `matches/3`, `split/3`, `replace/4`, `lowercase/2`, `uppercase/2`, `trim/2`, `number_string/2`, `atom_string/2`, `term_string/2`, `string_concat/3`, `contains/2`, `matches/2`, `join/3`, `substring/4`, `set_nth0/4`, `take/3`, `drop/3`, `slice/4`, `sumall/3`, `aggregate_min/5`, `aggregate_max/5` |
|
|
5794
|
-
| `library(prologue)` | `member/2`, `append/3`, `length/2`, `between/3`, `select/3`, `succ/2`, `maplist/2`, `maplist/3`, `maplist/4`, `maplist/5`, `maplist/6`, `maplist/7`, `maplist/8`, `nth0/3`, `nth0/4`, `nth1/3`, `nth1/4`, `call_nth/2`, `foldl/4`, `foldl/5`, `foldl/6`, `countall/2` |
|
|
5799
|
+
| `library(prologue)` | `member/2`, `append/3`, `length/2`, `between/3`, `select/3`, `succ/2`, `maplist/2`, `maplist/3`, `maplist/4`, `maplist/5`, `maplist/6`, `maplist/7`, `maplist/8`, `nth0/3`, `nth0/4`, `nth1/3`, `nth1/4`, `call_nth/2`, `freeze/2`, `foldl/4`, `foldl/5`, `foldl/6`, `countall/2` |
|
|
5795
5800
|
|
|
5796
5801
|
<!-- eyeprolog-library-catalog:end -->
|
|
5797
5802
|
|
|
@@ -6169,7 +6174,9 @@ Run all quads in a file with `eyeprolog --quads file.pl` or `eyeprolog -q
|
|
|
6169
6174
|
file.pl`. A label such as `colors` is optional. Loading the file normally only
|
|
6170
6175
|
records its quads; it does not execute them or add their queries and answers as
|
|
6171
6176
|
program clauses. A quad run prints a summary and exits with status `1` when any
|
|
6172
|
-
description fails.
|
|
6177
|
+
description fails. Quad mode imports `library(prologue)` as a compatibility
|
|
6178
|
+
prelude because the ISO Prolog working-example files use those predicates as
|
|
6179
|
+
system predicates without an explicit module directive.
|
|
6173
6180
|
|
|
6174
6181
|
Unless the source explicitly selects another `unknown` flag, quad execution
|
|
6175
6182
|
uses `unknown=error`, so an undefined predicate is reported rather than being
|
|
@@ -6181,10 +6188,10 @@ and the `unexpected` annotation for an answer that must not occur (`inattendue`
|
|
|
6181
6188
|
is its synonym). `...` and `ad_infinitum` accept further answers. Multiple
|
|
6182
6189
|
indented descriptions after one query must all hold. `inputs/1` supplies and
|
|
6183
6190
|
checks consumed characters; `outputs/1` checks emitted characters. `sto` marks
|
|
6184
|
-
an answer description that this finite-tree implementation skips.
|
|
6185
|
-
|
|
6186
|
-
and the unordered `other_answer_sequence`
|
|
6187
|
-
current runner.
|
|
6191
|
+
an answer description that this finite-tree implementation skips. `loops` is
|
|
6192
|
+
checked with a deterministic solver-depth budget. The advanced stream
|
|
6193
|
+
annotations `peeks/1` and `waits`, and the unordered `other_answer_sequence`
|
|
6194
|
+
annotation, are not executed by the current runner.
|
|
6188
6195
|
|
|
6189
6196
|
The JavaScript API exposes the same operation without process I/O:
|
|
6190
6197
|
|