eyeprolog 1.6.37 → 1.6.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.
- package/examples/output/bayes-therapy.pl +1 -1
- package/package.json +1 -1
- package/src/explain.js +100 -5
- package/src/parser.js +66 -22
- package/src/program.js +13 -0
- package/src/proof-document.js +2 -1
- package/src/write.js +11 -0
- package/test/conformance/cases/arithmetic/024_numeric_literal_readback.pl +1 -1
- package/test/conformance/expected/arithmetic/024_numeric_literal_readback.pl +2 -2
- package/test/conformance/expected/terms/003_terms_and_readback.pl +1 -1
- package/test/regression/cases-regression.mjs +10 -15
|
@@ -10,7 +10,7 @@ expectedSuccess(oseltamivir, 0.28514101258814745).
|
|
|
10
10
|
expectedSuccess(antihistamine, 0.10026891936485297).
|
|
11
11
|
expectedSuccess(antibiotic, 0.1109525797960936).
|
|
12
12
|
expectedSuccess(supportiveCare, 0.2915119539701381).
|
|
13
|
-
expectedAdverse(paxlovid, 0.
|
|
13
|
+
expectedAdverse(paxlovid, 0.1).
|
|
14
14
|
expectedAdverse(oseltamivir, 0.08).
|
|
15
15
|
expectedAdverse(antihistamine, 0.03).
|
|
16
16
|
expectedAdverse(antibiotic, 0.07).
|
package/package.json
CHANGED
package/src/explain.js
CHANGED
|
@@ -54,6 +54,10 @@ let liveSolver = null;
|
|
|
54
54
|
// sets an explicit `maxDepth` gets that depth as a hard limit instead.
|
|
55
55
|
let chainBeyondDepth = true;
|
|
56
56
|
|
|
57
|
+
// Proofs of ground goals already found while explaining one document, keyed by
|
|
58
|
+
// module and goal; null outside such a document.
|
|
59
|
+
let groundProofs = null;
|
|
60
|
+
|
|
57
61
|
function hostSolver(program, registry) {
|
|
58
62
|
return liveSolver && liveSolver.program === program ? liveSolver : new Solver(program, { registry });
|
|
59
63
|
}
|
|
@@ -177,6 +181,23 @@ function* proveGoalAll(program, goal, env, depth, maxDepth, registry, active, de
|
|
|
177
181
|
}
|
|
178
182
|
return;
|
|
179
183
|
}
|
|
184
|
+
// A goal that is already ground binds nothing when proved, so which of its
|
|
185
|
+
// proofs is used cannot matter to the goals after it. Within one proof
|
|
186
|
+
// document its first proof is therefore recorded once and reused: a run
|
|
187
|
+
// whose answers share subderivations -- tabled trajectories, closures --
|
|
188
|
+
// no longer replays the shared part once per answer. This stays inside one
|
|
189
|
+
// generator: a replay nests a generator per level, and one more frame per
|
|
190
|
+
// level would overflow the host stack on deep derivations.
|
|
191
|
+
const memoKey = groundProofs && termIsGround(goal, env)
|
|
192
|
+
? `${group.module}\u0000${termToString(goal, env, true)}`
|
|
193
|
+
: null;
|
|
194
|
+
if (memoKey) {
|
|
195
|
+
const known = groundProofs.get(memoKey);
|
|
196
|
+
if (known) {
|
|
197
|
+
yield { env, node: known };
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
180
201
|
// Explanation replay does not use the solver's answer tables, so its cycle
|
|
181
202
|
// guard applies even when normal execution tables this predicate.
|
|
182
203
|
if (activeVariant(goal, env, active)) return;
|
|
@@ -199,7 +220,7 @@ function* proveGoalAll(program, goal, env, depth, maxDepth, registry, active, de
|
|
|
199
220
|
const substitutions = collectClauseSubstitutions(clause, freshHead, freshBody);
|
|
200
221
|
|
|
201
222
|
if (freshBody.length === 0) {
|
|
202
|
-
|
|
223
|
+
const proved = {
|
|
203
224
|
env: next,
|
|
204
225
|
node: {
|
|
205
226
|
raw: goal,
|
|
@@ -215,6 +236,11 @@ function* proveGoalAll(program, goal, env, depth, maxDepth, registry, active, de
|
|
|
215
236
|
children: [],
|
|
216
237
|
},
|
|
217
238
|
};
|
|
239
|
+
if (memoKey) {
|
|
240
|
+
yield rememberGroundProof(memoKey, proved);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
yield proved;
|
|
218
244
|
continue;
|
|
219
245
|
}
|
|
220
246
|
|
|
@@ -224,7 +250,7 @@ function* proveGoalAll(program, goal, env, depth, maxDepth, registry, active, de
|
|
|
224
250
|
for (const proved of proveGoalsAll(program, freshBody, next, depth + 1, maxDepth, registry, active, detail)) {
|
|
225
251
|
active.pop();
|
|
226
252
|
activePushed = false;
|
|
227
|
-
|
|
253
|
+
const result = {
|
|
228
254
|
env: proved.env,
|
|
229
255
|
node: {
|
|
230
256
|
raw: goal,
|
|
@@ -237,6 +263,11 @@ function* proveGoalAll(program, goal, env, depth, maxDepth, registry, active, de
|
|
|
237
263
|
children: proved.children,
|
|
238
264
|
},
|
|
239
265
|
};
|
|
266
|
+
if (memoKey) {
|
|
267
|
+
yield rememberGroundProof(memoKey, result);
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
yield result;
|
|
240
271
|
active.push({ goal, env });
|
|
241
272
|
activePushed = true;
|
|
242
273
|
}
|
|
@@ -472,6 +503,59 @@ function sourceMethod(clause, kind) {
|
|
|
472
503
|
};
|
|
473
504
|
}
|
|
474
505
|
|
|
506
|
+
// Built-in and library steps whose replay has no effect beyond its bindings:
|
|
507
|
+
// arithmetic, comparison, term inspection, and pure list relations. Anything
|
|
508
|
+
// else -- I/O, database updates, constraint stores, generated names -- keeps a
|
|
509
|
+
// derivation from being reused across answers.
|
|
510
|
+
const PURE_REPLAY_STEPS = new Set([
|
|
511
|
+
'true/0', 'is/2', '=:=/2', '=\\=/2', '</2', '>/2', '=</2', '>=/2',
|
|
512
|
+
'=/2', '\\=/2', '==/2', '\\==/2', '@</2', '@>/2', '@=</2', '@>=/2', 'compare/3',
|
|
513
|
+
'var/1', 'nonvar/1', 'atom/1', 'number/1', 'integer/1', 'float/1', 'atomic/1',
|
|
514
|
+
'compound/1', 'callable/1', 'ground/1', 'is_list/1',
|
|
515
|
+
'functor/3', 'arg/3', '=../2', 'copy_term/2', 'succ/2', 'plus/3',
|
|
516
|
+
'char_code/2', 'atom_codes/2', 'atom_chars/2', 'atom_length/2', 'atom_concat/3',
|
|
517
|
+
'sub_atom/5', 'number_codes/2', 'number_chars/2', 'atom_number/2',
|
|
518
|
+
'sort/2', 'msort/2', 'keysort/2', 'between/3', 'length/2',
|
|
519
|
+
'member/2', 'memberchk/2', 'append/3', 'append/2', 'reverse/2', 'nth0/3', 'nth1/3',
|
|
520
|
+
'last/2', 'select/3', 'sum_list/2', 'max_list/2', 'min_list/2', 'list_to_set/2',
|
|
521
|
+
'once/1', 'findall/3', 'forall/2', '\\+/1',
|
|
522
|
+
]);
|
|
523
|
+
|
|
524
|
+
// Settle a ground goal's first proof while its environment still holds every
|
|
525
|
+
// binding inside it, and keep it for reuse when replaying it has no effects:
|
|
526
|
+
// a proof that writes a stream or updates the database must perform those
|
|
527
|
+
// effects again, so it is not recorded.
|
|
528
|
+
function rememberGroundProof(key, proved) {
|
|
529
|
+
const node = settleProofNode(proved.node, proved.env);
|
|
530
|
+
if (replayIsPure(node)) groundProofs.set(key, node);
|
|
531
|
+
return { env: proved.env, node };
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// The answer is cached on the node, so a deep derivation whose every level is
|
|
535
|
+
// remembered asks it once per level rather than once per level per descendant.
|
|
536
|
+
function replayIsPure(root) {
|
|
537
|
+
if (root.pureReplay !== undefined) return root.pureReplay;
|
|
538
|
+
const stack = [root];
|
|
539
|
+
let pure = true;
|
|
540
|
+
while (stack.length > 0) {
|
|
541
|
+
const node = stack.pop();
|
|
542
|
+
if (node !== root && node.pureReplay !== undefined) {
|
|
543
|
+
if (node.pureReplay) continue;
|
|
544
|
+
pure = false;
|
|
545
|
+
break;
|
|
546
|
+
}
|
|
547
|
+
const method = node.method;
|
|
548
|
+
if (method && typeof method === 'object' && (method.type === 'builtin' || method.type === 'library') &&
|
|
549
|
+
!PURE_REPLAY_STEPS.has(`${method.name}/${method.arity}`)) {
|
|
550
|
+
pure = false;
|
|
551
|
+
break;
|
|
552
|
+
}
|
|
553
|
+
for (const child of node.children ?? []) stack.push(child);
|
|
554
|
+
}
|
|
555
|
+
root.pureReplay = pure;
|
|
556
|
+
return pure;
|
|
557
|
+
}
|
|
558
|
+
|
|
475
559
|
function goalMethod(type, goal) {
|
|
476
560
|
return {
|
|
477
561
|
type,
|
|
@@ -946,12 +1030,18 @@ export function clauseNumbering(program) {
|
|
|
946
1030
|
offset += span;
|
|
947
1031
|
}
|
|
948
1032
|
|
|
1033
|
+
// Keyed by file, then by clause position: one string key per clause cost
|
|
1034
|
+
// more than the numbering itself on programs with hundreds of thousands of
|
|
1035
|
+
// clauses.
|
|
949
1036
|
const bySource = new Map();
|
|
950
1037
|
const byNumber = new Map();
|
|
951
1038
|
for (const clause of program.clauses ?? []) {
|
|
952
1039
|
if (!isProgramClause(clause) || isUnnumberedDeclaration(clause)) continue;
|
|
953
|
-
const
|
|
954
|
-
|
|
1040
|
+
const { filename, clause: position } = clause.source;
|
|
1041
|
+
const number = (offsets.get(filename) ?? 0) + position;
|
|
1042
|
+
let numbers = bySource.get(filename);
|
|
1043
|
+
if (!numbers) bySource.set(filename, numbers = new Map());
|
|
1044
|
+
numbers.set(position, number);
|
|
955
1045
|
byNumber.set(number, { head: clause.head, body: clause.body ?? [] });
|
|
956
1046
|
}
|
|
957
1047
|
return { bySource, byNumber };
|
|
@@ -971,7 +1061,7 @@ export function isLibraryClause(clause) {
|
|
|
971
1061
|
}
|
|
972
1062
|
|
|
973
1063
|
function clauseNumberFor(numbering, method) {
|
|
974
|
-
return numbering.bySource.get(
|
|
1064
|
+
return numbering.bySource.get(method.filename)?.get(method.clause) ?? null;
|
|
975
1065
|
}
|
|
976
1066
|
|
|
977
1067
|
// The conclusion a node contributes, which is how a step names what it used.
|
|
@@ -1125,6 +1215,10 @@ function settleProofNode(root, env) {
|
|
|
1125
1215
|
const stack = [root];
|
|
1126
1216
|
while (stack.length) {
|
|
1127
1217
|
const node = stack.pop();
|
|
1218
|
+
// A settled node was settled with its whole subtree; a remembered ground
|
|
1219
|
+
// proof reached again from a deeper level need not be walked again.
|
|
1220
|
+
if (node.settled) continue;
|
|
1221
|
+
node.settled = true;
|
|
1128
1222
|
if (node.raw) {
|
|
1129
1223
|
node.goal = resolve(node.raw);
|
|
1130
1224
|
if (node.method?.type === 'builtin' || node.method?.type === 'library') node.sourceHead = node.goal;
|
|
@@ -1210,6 +1304,7 @@ function settlingResolver(env) {
|
|
|
1210
1304
|
export function proofNodeFor(program, goal, options = {}) {
|
|
1211
1305
|
liveSolver = options.solver ?? null;
|
|
1212
1306
|
chainBeyondDepth = options.maxDepth == null;
|
|
1307
|
+
groundProofs = options.groundProofs ?? null;
|
|
1213
1308
|
const maxDepth = options.maxDepth ?? 256;
|
|
1214
1309
|
const registry = options.registry ?? getEyePrologRegistry();
|
|
1215
1310
|
const env = options.env ?? new Env();
|
package/src/parser.js
CHANGED
|
@@ -317,7 +317,9 @@ class Parser {
|
|
|
317
317
|
this.filename = options.filename ?? '<input>';
|
|
318
318
|
this.pos = 0;
|
|
319
319
|
this.line = 1;
|
|
320
|
-
|
|
320
|
+
// Anonymous variables are numbered through a whole text; a caller parsing
|
|
321
|
+
// one text in pieces continues the count.
|
|
322
|
+
this.anonymous = options.anonymousStart ?? 0;
|
|
321
323
|
this.variables = new Map();
|
|
322
324
|
this.sourceMetadata = options.sourceMetadata !== false;
|
|
323
325
|
this.onWarning = typeof options.onWarning === 'function' ? options.onWarning : null;
|
|
@@ -346,8 +348,10 @@ class Parser {
|
|
|
346
348
|
this.token = this.nextToken();
|
|
347
349
|
}
|
|
348
350
|
convertCharacter(character) {
|
|
349
|
-
|
|
350
|
-
|
|
351
|
+
const state = this.parserFlagState;
|
|
352
|
+
// Almost no text installs a conversion; skip the map lookup per character.
|
|
353
|
+
if (state.charConversions.size === 0 || state.charConversion !== 'on' || !character) return character;
|
|
354
|
+
return state.charConversions.get(character) ?? character;
|
|
351
355
|
}
|
|
352
356
|
rawPeek(offset = 0) {
|
|
353
357
|
// The processor character set is an implementation-defined processor
|
|
@@ -1482,6 +1486,18 @@ function parseClausesFastNoSource(source, emit = null, emitBinary = null, option
|
|
|
1482
1486
|
let anonymous = 0;
|
|
1483
1487
|
let chunk = '';
|
|
1484
1488
|
let chunkStartLine = 1;
|
|
1489
|
+
let operatorState = null;
|
|
1490
|
+
// With source metadata, this path numbers clauses exactly as the general
|
|
1491
|
+
// parser does: one number per clause and per counted directive, none for a
|
|
1492
|
+
// query or a quad. Compact binary clauses carry no source, so they are not
|
|
1493
|
+
// used then.
|
|
1494
|
+
const trackSource = options.sourceMetadata === true;
|
|
1495
|
+
const sourceFilename = options.filename ?? '<input>';
|
|
1496
|
+
let clauseNumber = 0;
|
|
1497
|
+
const numbered = (clause, line) => {
|
|
1498
|
+
if (trackSource) clause.source = { filename: sourceFilename, line, clause: ++clauseNumber };
|
|
1499
|
+
return clause;
|
|
1500
|
+
};
|
|
1485
1501
|
|
|
1486
1502
|
const cached = (cache, key, create) => {
|
|
1487
1503
|
const existing = cache.get(key);
|
|
@@ -1491,22 +1507,19 @@ function parseClausesFastNoSource(source, emit = null, emitBinary = null, option
|
|
|
1491
1507
|
return value;
|
|
1492
1508
|
};
|
|
1493
1509
|
|
|
1494
|
-
//
|
|
1495
|
-
//
|
|
1496
|
-
//
|
|
1497
|
-
//
|
|
1498
|
-
//
|
|
1499
|
-
//
|
|
1500
|
-
//
|
|
1501
|
-
// zero, or the "-0" from issue #114, must collapse the same way the
|
|
1502
|
-
// scanner's own adjacent-minus BigInt construction already does), and a
|
|
1503
|
-
// float whose value is exactly zero must never print with a stray minus --
|
|
1504
|
-
// this implementation deliberately never produces -0.0 (term.js
|
|
1505
|
-
// numberTextFromDouble) -- without reformatting any non-zero float.
|
|
1510
|
+
// A number is a value, not a spelling (ISO 7.1.3), so the fast path writes
|
|
1511
|
+
// numbers exactly as the general parser does: an integer in canonical
|
|
1512
|
+
// decimal (a leading zero or "-0" collapses, issue #114), and a float as the
|
|
1513
|
+
// shortest text that reads back to the same double (term.js
|
|
1514
|
+
// numberTextFromDouble), so 0.10 is 0.1 and 6.02e23 is 6.02e+23 whichever
|
|
1515
|
+
// parser read the literal. A run therefore prints the same numbers with and
|
|
1516
|
+
// without --proof.
|
|
1506
1517
|
const canonicalFastNumberText = (text) => {
|
|
1507
|
-
if (text.includes('.')
|
|
1508
|
-
|
|
1518
|
+
if (text.includes('.')) {
|
|
1519
|
+
const value = Number(text);
|
|
1520
|
+
return Number.isFinite(value) ? numberTextFromDouble(value) : text;
|
|
1509
1521
|
}
|
|
1522
|
+
if (text.includes('e') || text.includes('E')) return text;
|
|
1510
1523
|
const first = text.charCodeAt(0);
|
|
1511
1524
|
if (first !== 45 && (text.length === 1 || first !== 48)) return text;
|
|
1512
1525
|
return BigInt(text).toString();
|
|
@@ -1826,15 +1839,46 @@ function parseClausesFastNoSource(source, emit = null, emitBinary = null, option
|
|
|
1826
1839
|
const simple = preparationConversionActive() ? null : parseSimple(text);
|
|
1827
1840
|
if (simple) {
|
|
1828
1841
|
reportClauseSingletonWarnings(simple, options.onWarning, options.filename ?? '<input>', sourceLine);
|
|
1829
|
-
accept(simple);
|
|
1842
|
+
accept(numbered(simple, sourceLine));
|
|
1830
1843
|
return true;
|
|
1831
1844
|
}
|
|
1832
1845
|
try {
|
|
1833
1846
|
const nestedWarning = typeof options.onWarning === 'function'
|
|
1834
1847
|
? (warning) => options.onWarning({ ...warning, line: warning.line + sourceLine - 1 })
|
|
1835
1848
|
: null;
|
|
1836
|
-
|
|
1837
|
-
|
|
1849
|
+
// One operator table serves every clause of this source: building it
|
|
1850
|
+
// per clause dominated the parse of large generated programs, and an
|
|
1851
|
+
// op/3 directive in one clause must govern the clauses after it anyway.
|
|
1852
|
+
operatorState ??= options.operatorState ?? createParserOperatorState(
|
|
1853
|
+
options.operatorDefinitions ?? [],
|
|
1854
|
+
options.includeDefaultOperators !== false,
|
|
1855
|
+
{ isoStrict: options.isoStrict === true },
|
|
1856
|
+
);
|
|
1857
|
+
const parser = new Parser(text, {
|
|
1858
|
+
...options,
|
|
1859
|
+
operatorState,
|
|
1860
|
+
sourceMetadata: trackSource,
|
|
1861
|
+
filename: sourceFilename,
|
|
1862
|
+
onWarning: nestedWarning,
|
|
1863
|
+
anonymousStart: trackSource ? anonymous : 0,
|
|
1864
|
+
});
|
|
1865
|
+
const parsed = parser.parseProgram();
|
|
1866
|
+
if (trackSource) anonymous = parser.anonymous;
|
|
1867
|
+
// The nested parser numbers lines and clauses from the start of this
|
|
1868
|
+
// chunk; place them in the whole source.
|
|
1869
|
+
let counted = 0;
|
|
1870
|
+
for (const clause of parsed) {
|
|
1871
|
+
if (trackSource && clause.source) {
|
|
1872
|
+
const relative = clause.source.clause;
|
|
1873
|
+
clause.source = { ...clause.source, line: clause.source.line + sourceLine - 1 };
|
|
1874
|
+
if (relative != null) {
|
|
1875
|
+
clause.source.clause = relative + clauseNumber;
|
|
1876
|
+
if (relative > counted) counted = relative;
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
accept(clause);
|
|
1880
|
+
}
|
|
1881
|
+
clauseNumber += counted;
|
|
1838
1882
|
return true;
|
|
1839
1883
|
} catch (_) {
|
|
1840
1884
|
return false;
|
|
@@ -1853,13 +1897,13 @@ function parseClausesFastNoSource(source, emit = null, emitBinary = null, option
|
|
|
1853
1897
|
contentEnd = trimRangeEnd(source, contentStart, contentEnd);
|
|
1854
1898
|
if (contentStart < contentEnd && source.charCodeAt(contentStart) !== 37) {
|
|
1855
1899
|
if (!chunk && source.charCodeAt(contentEnd - 1) === 46 && !preparationConversionActive()) {
|
|
1856
|
-
if (emitFastBinaryRange(source, contentStart, contentEnd, lineNumber)) {
|
|
1900
|
+
if (!trackSource && emitFastBinaryRange(source, contentStart, contentEnd, lineNumber)) {
|
|
1857
1901
|
// The program builder accepted a compact binary clause directly.
|
|
1858
1902
|
} else {
|
|
1859
1903
|
const simple = parseFastRange(source, contentStart, contentEnd);
|
|
1860
1904
|
if (simple) {
|
|
1861
1905
|
reportClauseSingletonWarnings(simple, options.onWarning, options.filename ?? '<input>', lineNumber);
|
|
1862
|
-
accept(simple);
|
|
1906
|
+
accept(numbered(simple, lineNumber));
|
|
1863
1907
|
} else {
|
|
1864
1908
|
chunkStartLine = lineNumber;
|
|
1865
1909
|
chunk = source.slice(lineStart, lineEnd) + '\n';
|
package/src/program.js
CHANGED
|
@@ -958,6 +958,19 @@ function buildProgramFromSources(sources, options) {
|
|
|
958
958
|
const builder = new ProgramBuilder(options);
|
|
959
959
|
if (loadSourcesIntoBuilder(builder, sources, options, 'hybrid')) return builder.finish();
|
|
960
960
|
}
|
|
961
|
+
// With source metadata the compact parser still applies; it numbers clauses
|
|
962
|
+
// as the general parser does, and falls back to it for anything it cannot
|
|
963
|
+
// read. Strict ISO keeps the general parser throughout, and so does a text
|
|
964
|
+
// with a `?-` outside a comment: a quad's indented answer lines are layout
|
|
965
|
+
// the line-oriented compact parser does not read.
|
|
966
|
+
const hasQueries = sources.some((source) => {
|
|
967
|
+
const text = typeof source === 'string' ? source : source?.text ?? source?.source ?? '';
|
|
968
|
+
return /^[^%\n]*\?-/m.test(text);
|
|
969
|
+
});
|
|
970
|
+
if (options.sourceMetadata !== false && !hasModuleDirectives && !hasQueries && options.isoStrict !== true) {
|
|
971
|
+
const builder = new ProgramBuilder(options);
|
|
972
|
+
if (loadSourcesIntoBuilder(builder, sources, options, true)) return builder.finish();
|
|
973
|
+
}
|
|
961
974
|
|
|
962
975
|
const builder = new ProgramBuilder(options);
|
|
963
976
|
loadSourcesIntoBuilder(builder, sources, options, false);
|
package/src/proof-document.js
CHANGED
|
@@ -36,8 +36,9 @@ export function checkedProofBlocks(program, claimed, { registry, proofDetail = '
|
|
|
36
36
|
const roots = [];
|
|
37
37
|
const unexplained = [];
|
|
38
38
|
const questions = goals.filter((goal) => typeof goal !== 'string');
|
|
39
|
+
const groundProofs = new Map();
|
|
39
40
|
for (const fact of claimed) {
|
|
40
|
-
const node = proofNodeFor(program, fact, { registry, proofDetail, solver, questions });
|
|
41
|
+
const node = proofNodeFor(program, fact, { registry, proofDetail, solver, questions, groundProofs });
|
|
41
42
|
if (node) roots.push(node);
|
|
42
43
|
else unexplained.push(fact);
|
|
43
44
|
}
|
package/src/write.js
CHANGED
|
@@ -211,7 +211,18 @@ function operatorName(name) {
|
|
|
211
211
|
return quoteAtom(name);
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
+
// Writers pass the same definitions array for every term of a run, so build
|
|
215
|
+
// its lookup table once instead of once per written term.
|
|
216
|
+
const operatorTables = new WeakMap();
|
|
217
|
+
|
|
214
218
|
function operatorTable(definitions) {
|
|
219
|
+
if (!definitions) return buildOperatorTable(definitions);
|
|
220
|
+
let table = operatorTables.get(definitions);
|
|
221
|
+
if (!table) operatorTables.set(definitions, table = buildOperatorTable(definitions));
|
|
222
|
+
return table;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function buildOperatorTable(definitions) {
|
|
215
226
|
const table = new Map();
|
|
216
227
|
for (const definition of definitions ?? []) {
|
|
217
228
|
if (!definition || definition.priority === 0) continue;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
%
|
|
1
|
+
% Signed, decimal, and exponent numeric literals read back as their values (ISO 7.1.3, 7.10.5 c).
|
|
2
2
|
raw(negative_decimal, -3.5).
|
|
3
3
|
raw(positive_exp, 6.02e23).
|
|
4
4
|
raw(negative_exp, -1.0e-3).
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
answer(negative_decimal, -3.5).
|
|
2
|
-
answer(positive_exp, 6.
|
|
3
|
-
answer(negative_exp, -
|
|
2
|
+
answer(positive_exp, 6.02e+23).
|
|
3
|
+
answer(negative_exp, -0.001).
|
|
@@ -5,7 +5,7 @@ value(empty_atom, '').
|
|
|
5
5
|
value(char_list, "line\nquote: \"ok\"").
|
|
6
6
|
value(integer, -42).
|
|
7
7
|
value(decimal, 0.25).
|
|
8
|
-
value(scientific,
|
|
8
|
+
value(scientific, 0.00125).
|
|
9
9
|
value(compound, pair(3, nested(atom, "xy"))).
|
|
10
10
|
value(arity_zero_atom, nil).
|
|
11
11
|
value(empty_list, []).
|
|
@@ -6013,21 +6013,16 @@ answer(Result) :- countdown(2048, Result), Result = 2048.
|
|
|
6013
6013
|
const stdout = run(program, { goal }).stdout;
|
|
6014
6014
|
assertEqual(stdout.split('\n')[0], expected, `${program.trim()}: ${goal}`);
|
|
6015
6015
|
}
|
|
6016
|
-
//
|
|
6017
|
-
//
|
|
6018
|
-
//
|
|
6019
|
-
//
|
|
6020
|
-
|
|
6021
|
-
|
|
6022
|
-
run(
|
|
6023
|
-
|
|
6024
|
-
|
|
6025
|
-
|
|
6026
|
-
assertEqual(
|
|
6027
|
-
run('raw(a, -1.0e-3).\n', { goal: 'raw(_, B), write_canonical(B), nl' }).stdout.split('\n')[0],
|
|
6028
|
-
'-1.0e-3',
|
|
6029
|
-
'non-zero negative exponent notation is preserved, not reformatted to -0.001',
|
|
6030
|
-
);
|
|
6016
|
+
// A float is a value, not a spelling (ISO 7.1.3, 7.10.5 c): the fast
|
|
6017
|
+
// loader writes it as the general parser does, so a run prints the same
|
|
6018
|
+
// number with and without --proof. A three-argument fact is outside the
|
|
6019
|
+
// compact shape and so is read by the general parser.
|
|
6020
|
+
for (const [literal, expected] of [['6.02e23', '6.02e+23'], ['-1.0e-3', '-0.001'], ['0.10', '0.1']]) {
|
|
6021
|
+
const fast = run(`raw(a, ${literal}).\n`, { goal: 'raw(_, B), write_canonical(B), nl' }).stdout.split('\n')[0];
|
|
6022
|
+
const general = run(`raw(a, b, ${literal}).\n`, { goal: 'raw(_, _, B), write_canonical(B), nl' }).stdout.split('\n')[0];
|
|
6023
|
+
assertEqual(fast, expected, `${literal} through the fast loader`);
|
|
6024
|
+
assertEqual(general, expected, `${literal} through the general parser`);
|
|
6025
|
+
}
|
|
6031
6026
|
},
|
|
6032
6027
|
},
|
|
6033
6028
|
{
|