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 CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.2.19",
6
+ "version": "1.2.20",
7
7
  "description": "EyeProlog turns facts and rules into answers and proofs.",
8
8
  "type": "module",
9
9
  "main": "./index.js",
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(`${formatAnswer(engine, state, variables, current.result.value)}`);
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;
@@ -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: () => {
@@ -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. While a query is actively
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