eyeprolog 1.3.15 → 1.3.17
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/repl.js +6 -5
- package/src/solver.js +64 -0
- package/test/run-regression.mjs +50 -0
package/package.json
CHANGED
package/src/repl.js
CHANGED
|
@@ -366,7 +366,6 @@ function terminalFullStop(source, solver = null) {
|
|
|
366
366
|
let quote = null;
|
|
367
367
|
let lineComment = false;
|
|
368
368
|
let blockComment = false;
|
|
369
|
-
let depth = 0;
|
|
370
369
|
|
|
371
370
|
for (let i = 0; i < source.length; i++) {
|
|
372
371
|
const ch = source[i];
|
|
@@ -415,10 +414,12 @@ function terminalFullStop(source, solver = null) {
|
|
|
415
414
|
quote = ch;
|
|
416
415
|
continue;
|
|
417
416
|
}
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
417
|
+
// ISO 8.14.1.1 locates the end token lexically before read-term parsing.
|
|
418
|
+
// An unmatched opening bracket therefore must not make the top level wait
|
|
419
|
+
// for more input after a terminating full stop; parsing the collected text
|
|
420
|
+
// is what reports the syntax error (for example `[l.` or `{.`).
|
|
421
|
+
if (isTerminatingFullStop(source, i, convert) &&
|
|
422
|
+
onlyLayoutAndComments(source.slice(i + 1))) return i;
|
|
422
423
|
}
|
|
423
424
|
return -1;
|
|
424
425
|
}
|
package/src/solver.js
CHANGED
|
@@ -513,6 +513,23 @@ export class Solver {
|
|
|
513
513
|
break;
|
|
514
514
|
}
|
|
515
515
|
|
|
516
|
+
const betweenIterator = bundledBetweenIterator(this, group, goal, env);
|
|
517
|
+
if (betweenIterator != null) {
|
|
518
|
+
const firstResult = betweenIterator.next();
|
|
519
|
+
if (firstResult.done) break;
|
|
520
|
+
stack.push({
|
|
521
|
+
kind: 'resumeBuiltin',
|
|
522
|
+
iterator: betweenIterator,
|
|
523
|
+
goals: rest,
|
|
524
|
+
depth: depth + 1,
|
|
525
|
+
active,
|
|
526
|
+
});
|
|
527
|
+
goals = rest;
|
|
528
|
+
env = firstResult.value;
|
|
529
|
+
depth++;
|
|
530
|
+
continue;
|
|
531
|
+
}
|
|
532
|
+
|
|
516
533
|
const memberIterator = bundledMemberIterator(this, group, goal, env);
|
|
517
534
|
if (memberIterator != null) {
|
|
518
535
|
const firstResult = memberIterator.next();
|
|
@@ -1216,6 +1233,53 @@ function pushWfsAnswerFrames(stack, model, group, goal, rest, env, depth, active
|
|
|
1216
1233
|
}
|
|
1217
1234
|
}
|
|
1218
1235
|
|
|
1236
|
+
function bundledBetweenIterator(solver, group, goal, env) {
|
|
1237
|
+
if (solver.registry.eyePrologLibrary !== true ||
|
|
1238
|
+
group.module !== 'prologue' || group.name !== 'between' || group.arity !== 3 ||
|
|
1239
|
+
group.bundledLibrary !== true || group.clauses.length !== 1) {
|
|
1240
|
+
return null;
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
// The portable Prologue definition is intentionally kept as the semantic
|
|
1244
|
+
// source of between/3. Its recursive helper, however, carries the output
|
|
1245
|
+
// variable through one fresh clause instance per integer. With persistent
|
|
1246
|
+
// environments that builds a growing variable-alias chain, so dereferencing
|
|
1247
|
+
// the generated value in the caller repeatedly revisits all earlier frames.
|
|
1248
|
+
// Enumerate the canonical bundled relation directly while leaving user
|
|
1249
|
+
// definitions and non-EyeProlog registries on the ordinary Prolog path.
|
|
1250
|
+
return bundledBetweenSolutions(solver, goal, env);
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
function requireBetweenInteger(term, env) {
|
|
1254
|
+
const value = deref(term, env);
|
|
1255
|
+
if (value.type === VAR) throw new PrologError('instantiation_error');
|
|
1256
|
+
if (value.type !== NUMBER || !isDecimalInteger(value.name)) {
|
|
1257
|
+
throw new PrologError('type_error(integer)', value);
|
|
1258
|
+
}
|
|
1259
|
+
return BigInt(value.name);
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
function* bundledBetweenSolutions(solver, goal, env) {
|
|
1263
|
+
const lower = requireBetweenInteger(goal.args[0], env);
|
|
1264
|
+
const upper = requireBetweenInteger(goal.args[1], env);
|
|
1265
|
+
const requested = deref(goal.args[2], env);
|
|
1266
|
+
|
|
1267
|
+
if (requested.type !== VAR) {
|
|
1268
|
+
if (requested.type !== NUMBER || !isDecimalInteger(requested.name)) {
|
|
1269
|
+
throw new PrologError('type_error(integer)', requested);
|
|
1270
|
+
}
|
|
1271
|
+
const value = BigInt(requested.name);
|
|
1272
|
+
if (value >= lower && value <= upper) yield env;
|
|
1273
|
+
return;
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
for (let value = lower; value <= upper; value++) {
|
|
1277
|
+
const next = env.clone();
|
|
1278
|
+
solver.stats.unify_calls++;
|
|
1279
|
+
if (unify(goal.args[2], numberTerm(value), next)) yield next;
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1219
1283
|
function bundledMemberIterator(solver, group, goal, env) {
|
|
1220
1284
|
if (solver.registry.eyePrologLibrary !== true ||
|
|
1221
1285
|
!['lists', 'prologue'].includes(group.module) || group.name !== 'member' || group.arity !== 2 ||
|
package/test/run-regression.mjs
CHANGED
|
@@ -1410,6 +1410,27 @@ c4 ?- call((!;1)).
|
|
|
1410
1410
|
assertEqual(result.stdout, 'ok', 'DCG hand-off benchmark result');
|
|
1411
1411
|
},
|
|
1412
1412
|
},
|
|
1413
|
+
{
|
|
1414
|
+
name: 'DCG state hand-off reaches the next non-terminal at 8192 cells (issue #49 comment 5347991607)',
|
|
1415
|
+
run: () => {
|
|
1416
|
+
const filename = path.join(tmp, `issue49-small-handoff-${tmpCounter++}.pl`);
|
|
1417
|
+
fs.writeFileSync(filename,
|
|
1418
|
+
':- set_prolog_flag(occurs_check, true).\na --> ..., epsilon.\nepsilon --> [].\n');
|
|
1419
|
+
const result = runCli([], {
|
|
1420
|
+
input:
|
|
1421
|
+
`[${sourceAtom(filename)}].\n` +
|
|
1422
|
+
'use_module(library(lists)).\n' +
|
|
1423
|
+
'\\+ \\+ (length(L,8192), phrase(a,L)).\n' +
|
|
1424
|
+
'halt.\n',
|
|
1425
|
+
timeout: 3000,
|
|
1426
|
+
});
|
|
1427
|
+
if (result.error) throw result.error;
|
|
1428
|
+
assertEqual(result.status, 0, `small DCG hand-off status; stderr=${result.stderr}`);
|
|
1429
|
+
assertNotIncludes(result.stdout, 'resource_error', 'small DCG hand-off resource error');
|
|
1430
|
+
assertNotIncludes(result.stdout, 'depth_limit_exceeded', 'small DCG hand-off depth error');
|
|
1431
|
+
assertEqual(result.stderr, '', 'small DCG hand-off stderr');
|
|
1432
|
+
},
|
|
1433
|
+
},
|
|
1413
1434
|
{
|
|
1414
1435
|
name: 'Trealla-style DCG hand-off reaches 65536 cells without the solver depth ceiling',
|
|
1415
1436
|
run: () => {
|
|
@@ -1910,6 +1931,19 @@ c4 ?- call((!;1)).
|
|
|
1910
1931
|
assertIncludes(result.stdout, '?- repeat, fail.', 'terminal query echo');
|
|
1911
1932
|
},
|
|
1912
1933
|
},
|
|
1934
|
+
{
|
|
1935
|
+
name: 'REPL stops at the end token before parsing unmatched brackets (issue #51)',
|
|
1936
|
+
run: () => {
|
|
1937
|
+
const result = runCli([], { input: '[l.\ntrue.\n{.\ntrue.\nhalt.\n' });
|
|
1938
|
+
assertEqual(result.status, 0, 'exit status');
|
|
1939
|
+
assertNotIncludes(result.stdout, '| ', 'no continuation prompt after malformed end token');
|
|
1940
|
+
assertEqual((result.stdout.match(/\?- true\./g) ?? []).length, 2,
|
|
1941
|
+
'following lines remain separate top-level queries');
|
|
1942
|
+
assertEqual((result.stdout.match(/parse line 1:/g) ?? []).length, 2,
|
|
1943
|
+
'both malformed bracketed queries report syntax errors');
|
|
1944
|
+
assertEqual(result.stderr, '', 'stderr');
|
|
1945
|
+
},
|
|
1946
|
+
},
|
|
1913
1947
|
{
|
|
1914
1948
|
name: 'REPL accepts multiline period-terminated queries',
|
|
1915
1949
|
run: () => {
|
|
@@ -2398,6 +2432,22 @@ child.stdin.write(\`consult(${consultedAtom}).\\n\`);
|
|
|
2398
2432
|
assertEqual(result.stderr, '', 'stderr');
|
|
2399
2433
|
},
|
|
2400
2434
|
},
|
|
2435
|
+
{
|
|
2436
|
+
name: 'between/3 generated values avoid recursive environment chains (issue #52)',
|
|
2437
|
+
run: () => {
|
|
2438
|
+
const goalText = 'between(1, 1024, X), X < 0';
|
|
2439
|
+
const program = Program.parse('', { autoloadGoals: [goalText] });
|
|
2440
|
+
const solver = new Solver(program, { registry: getEyePrologRegistry() });
|
|
2441
|
+
const goal = parseGoalText(goalText, {
|
|
2442
|
+
operatorDefinitions: [...solver.program.operators.values()],
|
|
2443
|
+
});
|
|
2444
|
+
let answers = 0;
|
|
2445
|
+
for (const _env of solver.solve([goal], new Env(), 0)) answers++;
|
|
2446
|
+
assertEqual(answers, 0, 'positive generated values fail X < 0');
|
|
2447
|
+
assertEqual(solver.stats.unify_calls, 1024, 'one output unification per generated integer');
|
|
2448
|
+
assertEqual(solver.stats.max_depth <= 4, true, 'generation stays at bounded solver depth');
|
|
2449
|
+
},
|
|
2450
|
+
},
|
|
2401
2451
|
{
|
|
2402
2452
|
name: 'library(lists) length/2 stays relational and call_nth/2 autoloads (issue #28)',
|
|
2403
2453
|
run: () => {
|