eyeprolog 1.2.20 → 1.2.22
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 +20 -5
- package/test/run-regression.mjs +46 -11
- package/the-art-of-eyeprolog.md +10 -6
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
|
@@ -31,13 +31,23 @@ export async function runRepl(engine, options = {}) {
|
|
|
31
31
|
if (text == null) break;
|
|
32
32
|
if (!text.trim()) continue;
|
|
33
33
|
|
|
34
|
+
let resultIndent = ' ';
|
|
34
35
|
try {
|
|
35
36
|
const goal = parseGoal(engine, state, text);
|
|
37
|
+
// A complete, successfully parsed query has left the top-level reader
|
|
38
|
+
// and is about to execute. Show two spaces immediately so a terminal
|
|
39
|
+
// user can distinguish that state from a reader waiting for more text;
|
|
40
|
+
// the final result adds the third indentation space below. A direct
|
|
41
|
+
// halt has no result and exits immediately, so it needs no marker.
|
|
42
|
+
if (!isHaltGoal(goal)) {
|
|
43
|
+
output.write(' ');
|
|
44
|
+
resultIndent = ' ';
|
|
45
|
+
}
|
|
36
46
|
if (!options.isoStrict && isUseModuleGoal(goal)) {
|
|
37
47
|
sources.push({ text: `:- ${text}.\n`, filename: '<repl>' });
|
|
38
48
|
state = makeState(engine, sources, output, options, state, reader);
|
|
39
49
|
runWithTerminalSignals(reader, () => state.solver.runInitializations());
|
|
40
|
-
output.write('
|
|
50
|
+
output.write(' true.\n');
|
|
41
51
|
continue;
|
|
42
52
|
}
|
|
43
53
|
const consultFiles = options.isoStrict ? null : consultDesignations(engine, goal);
|
|
@@ -45,7 +55,7 @@ export async function runRepl(engine, options = {}) {
|
|
|
45
55
|
for (const filename of consultFiles) sources.push(await readSource(filename));
|
|
46
56
|
state = makeState(engine, sources, output, options, state, reader);
|
|
47
57
|
runWithTerminalSignals(reader, () => state.solver.runInitializations());
|
|
48
|
-
output.write('
|
|
58
|
+
output.write(' true.\n');
|
|
49
59
|
continue;
|
|
50
60
|
}
|
|
51
61
|
|
|
@@ -65,7 +75,7 @@ export async function runRepl(engine, options = {}) {
|
|
|
65
75
|
exitCode = error.code;
|
|
66
76
|
break;
|
|
67
77
|
}
|
|
68
|
-
output.write(
|
|
78
|
+
output.write(`${resultIndent}${formatError(engine, state, error)}\n`);
|
|
69
79
|
}
|
|
70
80
|
}
|
|
71
81
|
} catch (error) {
|
|
@@ -442,6 +452,11 @@ function isUseModuleGoal(goal) {
|
|
|
442
452
|
return goal.type === 'compound' && goal.name === 'use_module' && [1, 2].includes(goal.arity);
|
|
443
453
|
}
|
|
444
454
|
|
|
455
|
+
function isHaltGoal(goal) {
|
|
456
|
+
return (goal.type === 'atom' && goal.name === 'halt') ||
|
|
457
|
+
(goal.type === 'compound' && goal.name === 'halt' && goal.arity === 1);
|
|
458
|
+
}
|
|
459
|
+
|
|
445
460
|
function consultDesignations(engine, goal) {
|
|
446
461
|
if (goal.type === 'atom' && goal.name === '[]') return [];
|
|
447
462
|
if (goal.type !== 'compound' || goal.name !== '.' || goal.arity !== 2) return null;
|
|
@@ -481,7 +496,7 @@ async function solveQuery(engine, state, goal, reader, output) {
|
|
|
481
496
|
}
|
|
482
497
|
|
|
483
498
|
if (current.result.done) {
|
|
484
|
-
output.write('
|
|
499
|
+
output.write(' false.\n');
|
|
485
500
|
return null;
|
|
486
501
|
}
|
|
487
502
|
|
|
@@ -498,7 +513,7 @@ async function solveQuery(engine, state, goal, reader, output) {
|
|
|
498
513
|
if (formattingAfterAdvance) output.write(' ');
|
|
499
514
|
formattingAfterAdvance = false;
|
|
500
515
|
output.write(current.output);
|
|
501
|
-
output.write(`${firstAnswer ? '
|
|
516
|
+
output.write(`${firstAnswer ? ' ' : ''}${formatAnswer(engine, state, variables, current.result.value)}`);
|
|
502
517
|
answersShown++;
|
|
503
518
|
firstAnswer = false;
|
|
504
519
|
if (!next.error && next.result.done) {
|
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: () => {
|
|
@@ -791,7 +820,7 @@ c4 ?- call((!;1)).
|
|
|
791
820
|
},
|
|
792
821
|
},
|
|
793
822
|
{
|
|
794
|
-
name: 'REPL answer
|
|
823
|
+
name: 'REPL query and answer prompts distinguish waiting from computation',
|
|
795
824
|
run: () => {
|
|
796
825
|
const helper = `
|
|
797
826
|
import { spawn } from 'node:child_process';
|
|
@@ -804,9 +833,11 @@ c4 ?- call((!;1)).
|
|
|
804
833
|
child.stderr.setEncoding('utf8');
|
|
805
834
|
let stdout = '';
|
|
806
835
|
let stderr = '';
|
|
836
|
+
let sawQueryComputingPrompt = false;
|
|
807
837
|
let sawComputingPrompt = false;
|
|
808
838
|
child.stdout.on('data', (text) => {
|
|
809
839
|
stdout += text;
|
|
840
|
+
if (stdout.endsWith('?- ')) sawQueryComputingPrompt = true;
|
|
810
841
|
if (stdout.endsWith('\\n; ')) sawComputingPrompt = true;
|
|
811
842
|
});
|
|
812
843
|
child.stderr.on('data', (text) => { stderr += text; });
|
|
@@ -823,6 +854,10 @@ c4 ?- call((!;1)).
|
|
|
823
854
|
|
|
824
855
|
child.stdin.write('use_module(library(prologue)).\\n');
|
|
825
856
|
await waitFor(() => stdout.includes(' true.\\n?- '), 'module import');
|
|
857
|
+
sawQueryComputingPrompt = false;
|
|
858
|
+
child.stdin.write('between(0,0xff,I),I<0.\\n');
|
|
859
|
+
await waitFor(() => sawQueryComputingPrompt, 'query computing prompt');
|
|
860
|
+
await waitFor(() => stdout.endsWith(' false.\\n?- '), 'query result');
|
|
826
861
|
child.stdin.write('(N = 0; N = 1; (call_nth(repeat, 100000), N = 2)).\\n');
|
|
827
862
|
await waitFor(() => stdout.endsWith(' N = 0\\n;'), 'waiting prompt');
|
|
828
863
|
child.stdin.write(';\\n');
|
|
@@ -833,7 +868,7 @@ c4 ?- call((!;1)).
|
|
|
833
868
|
child.stdin.write('halt.\\n');
|
|
834
869
|
const status = await new Promise((resolve) => child.once('exit', resolve));
|
|
835
870
|
if (status !== 0) throw new Error('child status ' + status + '; stderr=' + stderr);
|
|
836
|
-
process.stdout.write('waiting;computing;formatting');
|
|
871
|
+
process.stdout.write('query-computing;waiting;computing;formatting');
|
|
837
872
|
`;
|
|
838
873
|
const result = spawnSync(process.execPath, [
|
|
839
874
|
'--input-type=module',
|
|
@@ -842,7 +877,7 @@ c4 ?- call((!;1)).
|
|
|
842
877
|
], { cwd: packageRoot, encoding: 'utf8', timeout: 10000 });
|
|
843
878
|
if (result.error) throw result.error;
|
|
844
879
|
assertEqual(result.status, 0, `prompt helper status; stderr=${result.stderr}`);
|
|
845
|
-
assertEqual(result.stdout, 'waiting;computing;formatting', 'prompt state sequence');
|
|
880
|
+
assertEqual(result.stdout, 'query-computing;waiting;computing;formatting', 'prompt state sequence');
|
|
846
881
|
},
|
|
847
882
|
},
|
|
848
883
|
{
|
|
@@ -912,9 +947,9 @@ c4 ?- call((!;1)).
|
|
|
912
947
|
});
|
|
913
948
|
assertEqual(result.status, 0, 'exit status');
|
|
914
949
|
assertEqual(result.stdout,
|
|
915
|
-
"?-
|
|
916
|
-
"?-
|
|
917
|
-
"?-
|
|
950
|
+
"?- '\\a' true.\n" +
|
|
951
|
+
"?- '\\a' true.\n" +
|
|
952
|
+
"?- '\\a' true.\n" +
|
|
918
953
|
'?- ',
|
|
919
954
|
'stdout');
|
|
920
955
|
assertEqual(result.stderr, '', 'stderr');
|
|
@@ -927,7 +962,7 @@ c4 ?- call((!;1)).
|
|
|
927
962
|
input: "writeq('\\0\\').\nhalt.\n",
|
|
928
963
|
});
|
|
929
964
|
assertEqual(result.status, 0, 'exit status');
|
|
930
|
-
assertEqual(result.stdout, "?-
|
|
965
|
+
assertEqual(result.stdout, "?- '\\0\\' true.\n?- ", 'stdout');
|
|
931
966
|
assertEqual(result.stderr, '', 'stderr');
|
|
932
967
|
},
|
|
933
968
|
},
|
|
@@ -1062,7 +1097,7 @@ c4 ?- call((!;1)).
|
|
|
1062
1097
|
input: "writeq('\\033\\').\nhalt.\n",
|
|
1063
1098
|
});
|
|
1064
1099
|
assertEqual(result.status, 0, 'exit status');
|
|
1065
|
-
assertEqual(result.stdout, "?-
|
|
1100
|
+
assertEqual(result.stdout, "?- '\\33\\' true.\n?- ", 'stdout');
|
|
1066
1101
|
assertEqual(result.stderr, '', 'stderr');
|
|
1067
1102
|
},
|
|
1068
1103
|
},
|
|
@@ -1081,9 +1116,9 @@ c4 ?- call((!;1)).
|
|
|
1081
1116
|
});
|
|
1082
1117
|
assertEqual(result.status, 0, 'exit status');
|
|
1083
1118
|
assertEqual(result.stdout,
|
|
1084
|
-
'?-
|
|
1085
|
-
"?-
|
|
1086
|
-
'?-
|
|
1119
|
+
'?- |: X = foo.\n' +
|
|
1120
|
+
"?- |: Y = '\\a'.\n" +
|
|
1121
|
+
'?- |: Z = bar.\n' +
|
|
1087
1122
|
'?- ',
|
|
1088
1123
|
'stdout');
|
|
1089
1124
|
assertEqual(result.stderr, '', 'stderr');
|
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,10 +6285,13 @@ 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
|
-
|
|
6289
|
-
|
|
6290
|
-
|
|
6288
|
+
the answer-control help. Once the top-level reader has accepted a complete
|
|
6289
|
+
query, the following line begins with two spaces to mark active execution; a
|
|
6290
|
+
third space appears when its result is ready for formatting. The answer prompt
|
|
6291
|
+
is `;` with no trailing space while it waits for input; after an advance
|
|
6292
|
+
command, one space marks active search and a second marks an answer ready for
|
|
6293
|
+
formatting. While a query is actively computing, EyeProlog releases readline's
|
|
6294
|
+
terminal signal handling: `Ctrl-C`
|
|
6291
6295
|
therefore terminates the current EyeProlog process immediately, and on POSIX
|
|
6292
6296
|
terminals `Ctrl-Z` suspends it in the usual shell-managed way. This remains a
|
|
6293
6297
|
host top-level convention rather than an ISO/IEC 13211-1 language feature. A
|