eyeprolog 1.2.19 → 1.2.21
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/parser.js +10 -1
- package/src/repl.js +17 -5
- package/test/run-regression.mjs +84 -0
- package/the-art-of-eyeprolog.md +6 -3
package/package.json
CHANGED
package/src/parser.js
CHANGED
|
@@ -436,7 +436,16 @@ class Parser {
|
|
|
436
436
|
if (!value || (value !== ' ' && isWhitespaceCode(value.charCodeAt(0)))) {
|
|
437
437
|
throw new Error(`parse line ${line}: bad character code constant`);
|
|
438
438
|
}
|
|
439
|
-
if (value === '
|
|
439
|
+
if (value === "'") {
|
|
440
|
+
// In the single-quoted-character notation used after 0', an
|
|
441
|
+
// apostrophe is doubled just as it is inside a quoted atom. Thus
|
|
442
|
+
// 0''' is one numeric token denoting character code 39, while the
|
|
443
|
+
// undoubled 0'' is not a complete single quoted character.
|
|
444
|
+
if (this.peek() !== "'") throw new Error(`parse line ${line}: bad character code constant`);
|
|
445
|
+
this.take();
|
|
446
|
+
} else if (value === '\\') {
|
|
447
|
+
value = this.readEscape(line, { allowContinuation: false });
|
|
448
|
+
}
|
|
440
449
|
const code = value.codePointAt(0);
|
|
441
450
|
return { type: TOK.NUMBER, text: String(negative ? -code : code), line };
|
|
442
451
|
}
|
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
|
@@ -538,6 +538,35 @@ c4 ?- call((!;1)).
|
|
|
538
538
|
}
|
|
539
539
|
},
|
|
540
540
|
},
|
|
541
|
+
{
|
|
542
|
+
name: 'apostrophe character-code constants parse in source and number conversion',
|
|
543
|
+
run: () => {
|
|
544
|
+
const source = String.raw`
|
|
545
|
+
?- N = 0'''.
|
|
546
|
+
N = 39.
|
|
547
|
+
?- number_chars(N,"0'''").
|
|
548
|
+
N = 39.
|
|
549
|
+
?- number_chars(N,"0'\\'").
|
|
550
|
+
N = 39.
|
|
551
|
+
?- number_codes(N,[48,39,39,39]).
|
|
552
|
+
N = 39.
|
|
553
|
+
`;
|
|
554
|
+
const result = publicApi.runQuads(source);
|
|
555
|
+
assertEqual(result.total, 4, 'quad total');
|
|
556
|
+
assertEqual(result.passed, 4, 'quad passed');
|
|
557
|
+
assertEqual(result.stdout, 'quads: 4 run, 4 passed, 0 failed.\n', 'quad report');
|
|
558
|
+
|
|
559
|
+
for (const goal of ["N = 0''", 'number_chars(N,"0\'\'")']) {
|
|
560
|
+
let caught = null;
|
|
561
|
+
try {
|
|
562
|
+
publicApi.run('', { goal });
|
|
563
|
+
} catch (error) {
|
|
564
|
+
caught = error;
|
|
565
|
+
}
|
|
566
|
+
if (caught == null) throw new Error(`${goal} should reject an undoubled apostrophe`);
|
|
567
|
+
}
|
|
568
|
+
},
|
|
569
|
+
},
|
|
541
570
|
{
|
|
542
571
|
name: 'number conversion rejects parenthesized numeric terms',
|
|
543
572
|
run: () => {
|
|
@@ -790,6 +819,61 @@ c4 ?- call((!;1)).
|
|
|
790
819
|
assertEqual(result.stderr, '', 'stderr');
|
|
791
820
|
},
|
|
792
821
|
},
|
|
822
|
+
{
|
|
823
|
+
name: 'REPL answer prompt distinguishes waiting from computation',
|
|
824
|
+
run: () => {
|
|
825
|
+
const helper = `
|
|
826
|
+
import { spawn } from 'node:child_process';
|
|
827
|
+
|
|
828
|
+
const child = spawn(${JSON.stringify(process.execPath)}, [${JSON.stringify(bin)}], {
|
|
829
|
+
cwd: ${JSON.stringify(packageRoot)},
|
|
830
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
831
|
+
});
|
|
832
|
+
child.stdout.setEncoding('utf8');
|
|
833
|
+
child.stderr.setEncoding('utf8');
|
|
834
|
+
let stdout = '';
|
|
835
|
+
let stderr = '';
|
|
836
|
+
let sawComputingPrompt = false;
|
|
837
|
+
child.stdout.on('data', (text) => {
|
|
838
|
+
stdout += text;
|
|
839
|
+
if (stdout.endsWith('\\n; ')) sawComputingPrompt = true;
|
|
840
|
+
});
|
|
841
|
+
child.stderr.on('data', (text) => { stderr += text; });
|
|
842
|
+
|
|
843
|
+
async function waitFor(predicate, label) {
|
|
844
|
+
const deadline = Date.now() + 5000;
|
|
845
|
+
while (!predicate()) {
|
|
846
|
+
if (Date.now() >= deadline) {
|
|
847
|
+
throw new Error(label + ' timeout; stdout=' + JSON.stringify(stdout) + '; stderr=' + JSON.stringify(stderr));
|
|
848
|
+
}
|
|
849
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
child.stdin.write('use_module(library(prologue)).\\n');
|
|
854
|
+
await waitFor(() => stdout.includes(' true.\\n?- '), 'module import');
|
|
855
|
+
child.stdin.write('(N = 0; N = 1; (call_nth(repeat, 100000), N = 2)).\\n');
|
|
856
|
+
await waitFor(() => stdout.endsWith(' N = 0\\n;'), 'waiting prompt');
|
|
857
|
+
child.stdin.write(';\\n');
|
|
858
|
+
await waitFor(() => sawComputingPrompt, 'computing prompt');
|
|
859
|
+
await waitFor(() => stdout.endsWith('; N = 1\\n;'), 'formatted answer');
|
|
860
|
+
child.stdin.write('\\n');
|
|
861
|
+
await waitFor(() => stdout.endsWith(' ... .\\n?- '), 'stopped enumeration');
|
|
862
|
+
child.stdin.write('halt.\\n');
|
|
863
|
+
const status = await new Promise((resolve) => child.once('exit', resolve));
|
|
864
|
+
if (status !== 0) throw new Error('child status ' + status + '; stderr=' + stderr);
|
|
865
|
+
process.stdout.write('waiting;computing;formatting');
|
|
866
|
+
`;
|
|
867
|
+
const result = spawnSync(process.execPath, [
|
|
868
|
+
'--input-type=module',
|
|
869
|
+
'--eval',
|
|
870
|
+
helper,
|
|
871
|
+
], { cwd: packageRoot, encoding: 'utf8', timeout: 10000 });
|
|
872
|
+
if (result.error) throw result.error;
|
|
873
|
+
assertEqual(result.status, 0, `prompt helper status; stderr=${result.stderr}`);
|
|
874
|
+
assertEqual(result.stdout, 'waiting;computing;formatting', 'prompt state sequence');
|
|
875
|
+
},
|
|
876
|
+
},
|
|
793
877
|
{
|
|
794
878
|
name: 'REPL f stops at five-answer boundaries instead of adding five answers',
|
|
795
879
|
run: () => {
|
package/the-art-of-eyeprolog.md
CHANGED
|
@@ -5776,8 +5776,9 @@ minus token and the following numeric token. A single-line `%...` comment may
|
|
|
5776
5776
|
therefore follow `-` directly because `%` cannot continue a graphic token; an
|
|
5777
5777
|
adjacent bracketed comment in `-/**/1` remains a syntax error under the eager
|
|
5778
5778
|
token-consumer rule. Decimal fractions and decimal exponents are supported;
|
|
5779
|
-
|
|
5780
|
-
|
|
5779
|
+
the apostrophe character code is written with a doubled apostrophe as `0'''`
|
|
5780
|
+
and has value 39. Trailing material and non-finite values are rejected. The
|
|
5781
|
+
regression gate vendors all 74 numbered cases from Ulrich Neumerkel's contemporary
|
|
5781
5782
|
`number_chars/2` comparison, including the Cor.2 error-precedence cases;
|
|
5782
5783
|
`number_codes/2` shares the same numeric parser and has mirrored coverage for
|
|
5783
5784
|
the recent numeric-syntax regressions.
|
|
@@ -6284,7 +6285,9 @@ When another answer exists in an interactive terminal, press `;`, Space, or
|
|
|
6284
6285
|
enumeration, `a` enumerates all remaining answers, and `f` advances to the
|
|
6285
6286
|
next five-answer boundary (5, 10, 15, ... displayed leaf answers), regardless
|
|
6286
6287
|
of how many answers were stepped through individually beforehand. `h` displays
|
|
6287
|
-
the answer-control help.
|
|
6288
|
+
the answer-control help. The answer prompt is `;` with no trailing space while
|
|
6289
|
+
it waits for input; after an advance command, one space marks active search and
|
|
6290
|
+
a second marks an answer ready for formatting. While a query is actively
|
|
6288
6291
|
computing, EyeProlog releases readline's terminal signal handling: `Ctrl-C`
|
|
6289
6292
|
therefore terminates the current EyeProlog process immediately, and on POSIX
|
|
6290
6293
|
terminals `Ctrl-Z` suspends it in the usual shell-managed way. This remains a
|