eyeprolog 1.2.34 → 1.2.35
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/package.json +1 -1
- package/src/iso.js +15 -11
- package/test/run-regression.mjs +26 -0
- package/the-art-of-eyeprolog.md +13 -4
package/package.json
CHANGED
package/src/iso.js
CHANGED
|
@@ -2029,35 +2029,39 @@ function* negationBuiltin({ solver, goal, env }) {
|
|
|
2029
2029
|
for (const _ of solver.cloneForInnerGoal(1).solve([callable(goal.args[0], env)], env.clone(), 0)) return;
|
|
2030
2030
|
yield env;
|
|
2031
2031
|
}
|
|
2032
|
+
function* solveControlBranch(solver, goal, env) {
|
|
2033
|
+
for (const answer of solver.solve([callable(goal, env)], env, 0)) {
|
|
2034
|
+
// A branch answer is internal to its enclosing control construct. The
|
|
2035
|
+
// surrounding solve will count the completed control goal after the
|
|
2036
|
+
// builtin yields it. Leaving both counts in place makes a bounded search
|
|
2037
|
+
// such as once/1 or negation stop before it can observe the branch answer.
|
|
2038
|
+
if (solver.solutionsSeen > 0) solver.solutionsSeen--;
|
|
2039
|
+
yield answer;
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2032
2042
|
function* disjunctionBuiltin({ solver, goal, env }) {
|
|
2033
2043
|
const left = deref(goal.args[0], env);
|
|
2034
2044
|
if (left.type === COMPOUND && left.name === '->' && left.arity === 2) {
|
|
2035
2045
|
for (const conditionEnv of solver.cloneForInnerGoal(1).solve([callable(left.args[0], env)], env.clone(), 0)) {
|
|
2036
|
-
yield* solver
|
|
2046
|
+
yield* solveControlBranch(solver, left.args[1], conditionEnv);
|
|
2037
2047
|
return;
|
|
2038
2048
|
}
|
|
2039
|
-
yield* solver
|
|
2049
|
+
yield* solveControlBranch(solver, goal.args[1], env.clone());
|
|
2040
2050
|
return;
|
|
2041
2051
|
}
|
|
2042
2052
|
const marker = solver.active[solver.active.length - 1] ?? null;
|
|
2043
2053
|
const markerCutEpoch = marker?.cutEpoch ?? 0;
|
|
2044
2054
|
const solverCutEpoch = solver.cutEpoch;
|
|
2045
|
-
yield* solver
|
|
2055
|
+
yield* solveControlBranch(solver, goal.args[0], env.clone());
|
|
2046
2056
|
const cutThisScope = marker == null
|
|
2047
2057
|
? solver.cutEpoch !== solverCutEpoch
|
|
2048
2058
|
: (marker.cutEpoch ?? 0) !== markerCutEpoch;
|
|
2049
2059
|
if (cutThisScope) return;
|
|
2050
|
-
yield* solver
|
|
2060
|
+
yield* solveControlBranch(solver, goal.args[1], env.clone());
|
|
2051
2061
|
}
|
|
2052
2062
|
function* ifThenBuiltin({ solver, goal, env }) {
|
|
2053
2063
|
for (const conditionEnv of solver.cloneForInnerGoal(1).solve([callable(goal.args[0], env)], env.clone(), 0)) {
|
|
2054
|
-
|
|
2055
|
-
// The consequent is an internal part of the current solution, not a
|
|
2056
|
-
// completed top-level solution. Keep a surrounding bounded search (for
|
|
2057
|
-
// example nested ISO once-as-if-then) from consuming its limit early.
|
|
2058
|
-
if (solver.solutionsSeen > 0) solver.solutionsSeen--;
|
|
2059
|
-
yield consequentEnv;
|
|
2060
|
-
}
|
|
2064
|
+
yield* solveControlBranch(solver, goal.args[1], conditionEnv);
|
|
2061
2065
|
return;
|
|
2062
2066
|
}
|
|
2063
2067
|
}
|
package/test/run-regression.mjs
CHANGED
|
@@ -477,6 +477,32 @@ c4 ?- call((!;1)).
|
|
|
477
477
|
assertEqual(result.stdout, 'quads: 22 run, 22 passed, 0 failed.\n', 'quad report');
|
|
478
478
|
},
|
|
479
479
|
},
|
|
480
|
+
{
|
|
481
|
+
name: 'negation observes disjunction through direct and call/1 execution',
|
|
482
|
+
run: () => {
|
|
483
|
+
const reported = publicApi.runQuads(String.raw`?- \+ (true ; true).
|
|
484
|
+
false.
|
|
485
|
+
|
|
486
|
+
?- call(\+ (true ; true)).
|
|
487
|
+
false.
|
|
488
|
+
`);
|
|
489
|
+
assertEqual(reported.total, 2, 'reported query count');
|
|
490
|
+
assertEqual(reported.passed, 2, 'reported queries pass');
|
|
491
|
+
|
|
492
|
+
const program = Program.parse('');
|
|
493
|
+
const answerCount = (text) => {
|
|
494
|
+
const solver = new Solver(program, { registry: getEyePrologRegistry() });
|
|
495
|
+
return [...solver.solve([parseGoalText(text)], new Env(), 0)].length;
|
|
496
|
+
};
|
|
497
|
+
|
|
498
|
+
assertEqual(answerCount(String.raw`\+ (true ; true)`), 0, 'direct successful disjunction is negated');
|
|
499
|
+
assertEqual(answerCount(String.raw`call(\+ (true ; true))`), 0, 'called negation fails');
|
|
500
|
+
assertEqual(answerCount(String.raw`\+ (true ; fail)`), 0, 'successful left branch is observed');
|
|
501
|
+
assertEqual(answerCount(String.raw`\+ (fail ; true)`), 0, 'successful right branch is observed');
|
|
502
|
+
assertEqual(answerCount(String.raw`\+ (fail ; fail)`), 1, 'failed disjunction is negated');
|
|
503
|
+
assertEqual(answerCount('once((true ; true))'), 1, 'once keeps the first disjunction answer');
|
|
504
|
+
},
|
|
505
|
+
},
|
|
480
506
|
{
|
|
481
507
|
name: 'runQuads passes the complete vendored ISO phrase quad corpus',
|
|
482
508
|
run: () => {
|
package/the-art-of-eyeprolog.md
CHANGED
|
@@ -1844,7 +1844,10 @@ normalized at the solver boundary instead of leaking a JavaScript `RangeError`.
|
|
|
1844
1844
|
ISO 13211-1 leaves the resource atom implementation dependent. EyeProlog uses
|
|
1845
1845
|
`memory` for a finite host allocation/capacity ceiling and reserves the
|
|
1846
1846
|
`finite_memory` spelling for the distinct convention where no finite amount of
|
|
1847
|
-
memory could complete the computation.
|
|
1847
|
+
memory could complete the computation. After a recoverable memory error, the
|
|
1848
|
+
solver keeps a bounded recovery window while the failed search unwinds so the
|
|
1849
|
+
host can collect released query terms. The same solver can then run later
|
|
1850
|
+
queries; this recovery does not resume the query that exhausted its limit.
|
|
1848
1851
|
|
|
1849
1852
|
The iterative solver keeps active-call frames only where they are semantically
|
|
1850
1853
|
needed for cut scope or recursive variant guards. Bundled-library helpers whose
|
|
@@ -1853,9 +1856,15 @@ guard therefore do not copy a growing active-call sequence at every step.
|
|
|
1853
1856
|
Under the normal EyeProlog registry, the bundled Prologue `length/2` also has a
|
|
1854
1857
|
scoped iterative execution path: named lists are counted or constructed without
|
|
1855
1858
|
recursive interpreter frames, and an anonymous list is not materialized because
|
|
1856
|
-
its binding cannot be observed.
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
+
its binding cannot be observed. A newly constructed fixed-length suffix starts
|
|
1860
|
+
as a lazy compact skeleton and expands one ordinary `./2` cell at a time when
|
|
1861
|
+
unification, another list predicate, or answer readback inspects it. This is a
|
|
1862
|
+
storage optimization, not a distinct Prolog term or list semantics. Embedders
|
|
1863
|
+
that inspect the JavaScript term model can recognize this representation with
|
|
1864
|
+
`CompactListTerm`, `isCompactList`, and `compactListLength`, or construct one
|
|
1865
|
+
with `compactVariableList`. The ordinary clauses remain the authoritative module
|
|
1866
|
+
definition and are used unchanged by the ISO-only registry and whenever delays
|
|
1867
|
+
or finite-domain constraints require their normal wake-up points.
|
|
1859
1868
|
|
|
1860
1869
|
### Implementation boundary
|
|
1861
1870
|
|