eyeprolog 1.2.19 → 1.2.20
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 +17 -5
- package/test/run-regression.mjs +55 -0
- package/the-art-of-eyeprolog.md +3 -1
package/package.json
CHANGED
package/src/repl.js
CHANGED
|
@@ -488,8 +488,15 @@ async function solveQuery(engine, state, goal, reader, output) {
|
|
|
488
488
|
let automatic = 0;
|
|
489
489
|
let answersShown = 0;
|
|
490
490
|
let firstAnswer = true;
|
|
491
|
+
let formattingAfterAdvance = false;
|
|
491
492
|
while (!current.result.done) {
|
|
492
493
|
const next = pullSolution(solver, solutions, reader);
|
|
494
|
+
// The control prompt has no trailing space while it waits for input. The
|
|
495
|
+
// first space appears as soon as the user requests another solution and
|
|
496
|
+
// remains visible while pullSolution() computes; the second appears only
|
|
497
|
+
// when the requested leaf answer is ready to format.
|
|
498
|
+
if (formattingAfterAdvance) output.write(' ');
|
|
499
|
+
formattingAfterAdvance = false;
|
|
493
500
|
output.write(current.output);
|
|
494
501
|
output.write(`${firstAnswer ? ' ' : ''}${formatAnswer(engine, state, variables, current.result.value)}`);
|
|
495
502
|
answersShown++;
|
|
@@ -501,14 +508,15 @@ async function solveQuery(engine, state, goal, reader, output) {
|
|
|
501
508
|
|
|
502
509
|
if (automatic > 0 || automatic === Infinity) {
|
|
503
510
|
if (automatic !== Infinity) automatic--;
|
|
504
|
-
output.write('\n;
|
|
511
|
+
output.write('\n; ');
|
|
512
|
+
formattingAfterAdvance = true;
|
|
505
513
|
} else {
|
|
506
514
|
while (true) {
|
|
507
|
-
const controlLine = await reader.readControl('\n;
|
|
515
|
+
const controlLine = await reader.readControl('\n;');
|
|
508
516
|
if (controlLine == null || controlLine === '' || controlLine === '\r' || controlLine === '\n' ||
|
|
509
517
|
controlLine.trimStart().startsWith('.')) {
|
|
510
518
|
if (typeof solutions.return === 'function') solutions.return();
|
|
511
|
-
output.write('... .\n');
|
|
519
|
+
output.write(' ... .\n');
|
|
512
520
|
return null;
|
|
513
521
|
}
|
|
514
522
|
const control = controlLine === ' ' ? ' ' : controlLine.trimStart()[0];
|
|
@@ -527,18 +535,22 @@ async function solveQuery(engine, state, goal, reader, output) {
|
|
|
527
535
|
break;
|
|
528
536
|
}
|
|
529
537
|
if (control === 'w' || control === 'p') {
|
|
530
|
-
output.write(
|
|
538
|
+
output.write(` ${formatAnswer(engine, state, variables, current.result.value)}`);
|
|
531
539
|
continue;
|
|
532
540
|
}
|
|
533
541
|
if (control === 'h') {
|
|
534
542
|
output.write(ANSWER_HELP);
|
|
535
543
|
continue;
|
|
536
544
|
}
|
|
537
|
-
output.write('Action? ');
|
|
545
|
+
output.write(' Action? ');
|
|
538
546
|
}
|
|
547
|
+
output.write(' ');
|
|
548
|
+
formattingAfterAdvance = true;
|
|
539
549
|
}
|
|
540
550
|
|
|
541
551
|
if (next.error) {
|
|
552
|
+
if (formattingAfterAdvance) output.write(' ');
|
|
553
|
+
formattingAfterAdvance = false;
|
|
542
554
|
output.write(next.output);
|
|
543
555
|
if (next.error?.name === 'HaltSignal') return { halted: true, code: next.error.code };
|
|
544
556
|
throw next.error;
|
package/test/run-regression.mjs
CHANGED
|
@@ -790,6 +790,61 @@ c4 ?- call((!;1)).
|
|
|
790
790
|
assertEqual(result.stderr, '', 'stderr');
|
|
791
791
|
},
|
|
792
792
|
},
|
|
793
|
+
{
|
|
794
|
+
name: 'REPL answer prompt distinguishes waiting from computation',
|
|
795
|
+
run: () => {
|
|
796
|
+
const helper = `
|
|
797
|
+
import { spawn } from 'node:child_process';
|
|
798
|
+
|
|
799
|
+
const child = spawn(${JSON.stringify(process.execPath)}, [${JSON.stringify(bin)}], {
|
|
800
|
+
cwd: ${JSON.stringify(packageRoot)},
|
|
801
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
802
|
+
});
|
|
803
|
+
child.stdout.setEncoding('utf8');
|
|
804
|
+
child.stderr.setEncoding('utf8');
|
|
805
|
+
let stdout = '';
|
|
806
|
+
let stderr = '';
|
|
807
|
+
let sawComputingPrompt = false;
|
|
808
|
+
child.stdout.on('data', (text) => {
|
|
809
|
+
stdout += text;
|
|
810
|
+
if (stdout.endsWith('\\n; ')) sawComputingPrompt = true;
|
|
811
|
+
});
|
|
812
|
+
child.stderr.on('data', (text) => { stderr += text; });
|
|
813
|
+
|
|
814
|
+
async function waitFor(predicate, label) {
|
|
815
|
+
const deadline = Date.now() + 5000;
|
|
816
|
+
while (!predicate()) {
|
|
817
|
+
if (Date.now() >= deadline) {
|
|
818
|
+
throw new Error(label + ' timeout; stdout=' + JSON.stringify(stdout) + '; stderr=' + JSON.stringify(stderr));
|
|
819
|
+
}
|
|
820
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
child.stdin.write('use_module(library(prologue)).\\n');
|
|
825
|
+
await waitFor(() => stdout.includes(' true.\\n?- '), 'module import');
|
|
826
|
+
child.stdin.write('(N = 0; N = 1; (call_nth(repeat, 100000), N = 2)).\\n');
|
|
827
|
+
await waitFor(() => stdout.endsWith(' N = 0\\n;'), 'waiting prompt');
|
|
828
|
+
child.stdin.write(';\\n');
|
|
829
|
+
await waitFor(() => sawComputingPrompt, 'computing prompt');
|
|
830
|
+
await waitFor(() => stdout.endsWith('; N = 1\\n;'), 'formatted answer');
|
|
831
|
+
child.stdin.write('\\n');
|
|
832
|
+
await waitFor(() => stdout.endsWith(' ... .\\n?- '), 'stopped enumeration');
|
|
833
|
+
child.stdin.write('halt.\\n');
|
|
834
|
+
const status = await new Promise((resolve) => child.once('exit', resolve));
|
|
835
|
+
if (status !== 0) throw new Error('child status ' + status + '; stderr=' + stderr);
|
|
836
|
+
process.stdout.write('waiting;computing;formatting');
|
|
837
|
+
`;
|
|
838
|
+
const result = spawnSync(process.execPath, [
|
|
839
|
+
'--input-type=module',
|
|
840
|
+
'--eval',
|
|
841
|
+
helper,
|
|
842
|
+
], { cwd: packageRoot, encoding: 'utf8', timeout: 10000 });
|
|
843
|
+
if (result.error) throw result.error;
|
|
844
|
+
assertEqual(result.status, 0, `prompt helper status; stderr=${result.stderr}`);
|
|
845
|
+
assertEqual(result.stdout, 'waiting;computing;formatting', 'prompt state sequence');
|
|
846
|
+
},
|
|
847
|
+
},
|
|
793
848
|
{
|
|
794
849
|
name: 'REPL f stops at five-answer boundaries instead of adding five answers',
|
|
795
850
|
run: () => {
|
package/the-art-of-eyeprolog.md
CHANGED
|
@@ -6284,7 +6284,9 @@ When another answer exists in an interactive terminal, press `;`, Space, or
|
|
|
6284
6284
|
enumeration, `a` enumerates all remaining answers, and `f` advances to the
|
|
6285
6285
|
next five-answer boundary (5, 10, 15, ... displayed leaf answers), regardless
|
|
6286
6286
|
of how many answers were stepped through individually beforehand. `h` displays
|
|
6287
|
-
the answer-control help.
|
|
6287
|
+
the answer-control help. The answer prompt is `;` with no trailing space while
|
|
6288
|
+
it waits for input; after an advance command, one space marks active search and
|
|
6289
|
+
a second marks an answer ready for formatting. While a query is actively
|
|
6288
6290
|
computing, EyeProlog releases readline's terminal signal handling: `Ctrl-C`
|
|
6289
6291
|
therefore terminates the current EyeProlog process immediately, and on POSIX
|
|
6290
6292
|
terminals `Ctrl-Z` suspends it in the usual shell-managed way. This remains a
|