eyeprolog 1.2.5 → 1.2.6
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 +35 -6
- package/test/run-regression.mjs +25 -0
- package/the-art-of-eyeprolog.md +5 -1
package/package.json
CHANGED
package/src/repl.js
CHANGED
|
@@ -24,7 +24,7 @@ export async function runRepl(engine, options = {}) {
|
|
|
24
24
|
let exitCode = 0;
|
|
25
25
|
|
|
26
26
|
try {
|
|
27
|
-
state.solver.runInitializations();
|
|
27
|
+
runWithTerminalSignals(reader, () => state.solver.runInitializations());
|
|
28
28
|
while (true) {
|
|
29
29
|
const text = await readQuery(reader);
|
|
30
30
|
if (text == null) break;
|
|
@@ -35,7 +35,7 @@ export async function runRepl(engine, options = {}) {
|
|
|
35
35
|
if (!options.isoStrict && isUseModuleGoal(goal)) {
|
|
36
36
|
sources.push({ text: `:- ${text}.\n`, filename: '<repl>' });
|
|
37
37
|
state = makeState(engine, sources, output, options, state);
|
|
38
|
-
state.solver.runInitializations();
|
|
38
|
+
runWithTerminalSignals(reader, () => state.solver.runInitializations());
|
|
39
39
|
output.write(' true.\n');
|
|
40
40
|
continue;
|
|
41
41
|
}
|
|
@@ -43,7 +43,7 @@ export async function runRepl(engine, options = {}) {
|
|
|
43
43
|
if (consultFiles != null) {
|
|
44
44
|
for (const filename of consultFiles) sources.push(await readSource(filename));
|
|
45
45
|
state = makeState(engine, sources, output, options, state);
|
|
46
|
-
state.solver.runInitializations();
|
|
46
|
+
runWithTerminalSignals(reader, () => state.solver.runInitializations());
|
|
47
47
|
output.write(' true.\n');
|
|
48
48
|
continue;
|
|
49
49
|
}
|
|
@@ -142,12 +142,39 @@ class LineReader {
|
|
|
142
142
|
return control;
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
+
suspendForComputation() {
|
|
146
|
+
if (!this.terminal || !this.readline) return false;
|
|
147
|
+
// Node readline installs terminal signal handling while the interface is
|
|
148
|
+
// open. During a synchronous Prolog search that prevents the terminal's
|
|
149
|
+
// normal SIGINT/SIGTSTP actions from taking effect until JavaScript yields.
|
|
150
|
+
// Close readline while the solver is running so Ctrl-C can terminate and
|
|
151
|
+
// Ctrl-Z can suspend an otherwise non-terminating computation immediately.
|
|
152
|
+
this.history = [...this.readline.history];
|
|
153
|
+
this.readline.close();
|
|
154
|
+
this.readline = null;
|
|
155
|
+
this.lines = null;
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
resumeAfterComputation(suspended) {
|
|
160
|
+
if (suspended && !this.readline) this.open();
|
|
161
|
+
}
|
|
162
|
+
|
|
145
163
|
close() {
|
|
146
164
|
if (this.input.isRaw) this.input.setRawMode(false);
|
|
147
165
|
this.readline?.close();
|
|
148
166
|
}
|
|
149
167
|
}
|
|
150
168
|
|
|
169
|
+
function runWithTerminalSignals(reader, operation) {
|
|
170
|
+
const suspended = reader.suspendForComputation();
|
|
171
|
+
try {
|
|
172
|
+
return operation();
|
|
173
|
+
} finally {
|
|
174
|
+
reader.resumeAfterComputation(suspended);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
151
178
|
function makeState(engine, sources, output, options = {}, previousState = null) {
|
|
152
179
|
const strictIso = options.isoStrict === true;
|
|
153
180
|
const program = engine.Program.parseSources(sources, { strictIso, sourceMetadata: strictIso });
|
|
@@ -369,7 +396,7 @@ async function solveQuery(engine, state, goal, reader, output) {
|
|
|
369
396
|
const solver = state.solver;
|
|
370
397
|
solver.solutionsSeen = 0;
|
|
371
398
|
const solutions = solver.solve([goal], new engine.Env(), 0);
|
|
372
|
-
let current = pullSolution(solver, solutions);
|
|
399
|
+
let current = pullSolution(solver, solutions, reader);
|
|
373
400
|
if (current.error) {
|
|
374
401
|
if (current.error?.name === 'HaltSignal') return { halted: true, code: current.error.code };
|
|
375
402
|
throw current.error;
|
|
@@ -383,7 +410,7 @@ async function solveQuery(engine, state, goal, reader, output) {
|
|
|
383
410
|
let automatic = 0;
|
|
384
411
|
let firstAnswer = true;
|
|
385
412
|
while (!current.result.done) {
|
|
386
|
-
const next = pullSolution(solver, solutions);
|
|
413
|
+
const next = pullSolution(solver, solutions, reader);
|
|
387
414
|
output.write(current.output);
|
|
388
415
|
output.write(`${firstAnswer ? ' ' : ''}${formatAnswer(engine, state, variables, current.result.value)}`);
|
|
389
416
|
firstAnswer = false;
|
|
@@ -436,16 +463,18 @@ async function solveQuery(engine, state, goal, reader, output) {
|
|
|
436
463
|
return null;
|
|
437
464
|
}
|
|
438
465
|
|
|
439
|
-
function pullSolution(solver, solutions) {
|
|
466
|
+
function pullSolution(solver, solutions, reader) {
|
|
440
467
|
const stream = solver.io.resolve('user_output');
|
|
441
468
|
const originalWrite = stream?.write;
|
|
442
469
|
let captured = '';
|
|
443
470
|
if (stream) stream.write = (text) => { captured += String(text); };
|
|
471
|
+
const suspended = reader.suspendForComputation();
|
|
444
472
|
try {
|
|
445
473
|
return { result: solutions.next(), output: captured };
|
|
446
474
|
} catch (error) {
|
|
447
475
|
return { error, output: captured };
|
|
448
476
|
} finally {
|
|
477
|
+
reader.resumeAfterComputation(suspended);
|
|
449
478
|
if (stream) stream.write = originalWrite;
|
|
450
479
|
}
|
|
451
480
|
}
|
package/test/run-regression.mjs
CHANGED
|
@@ -719,6 +719,27 @@ c4 ?- call((!;1)).
|
|
|
719
719
|
assertEqual(result.stderr, '', 'stderr');
|
|
720
720
|
},
|
|
721
721
|
},
|
|
722
|
+
{
|
|
723
|
+
name: 'REPL releases terminal signals while a query computes',
|
|
724
|
+
run: () => {
|
|
725
|
+
if (process.platform === 'win32') return;
|
|
726
|
+
const available = spawnSync('sh', ['-c',
|
|
727
|
+
'command -v script >/dev/null 2>&1 && script --version 2>/dev/null | grep -qi util-linux']);
|
|
728
|
+
if (available.status !== 0) return;
|
|
729
|
+
const command = `${shellQuote(process.execPath)} ${shellQuote(bin)}`;
|
|
730
|
+
const scriptCommand =
|
|
731
|
+
`{ printf 'repeat, fail.\n'; sleep 0.2; printf '\\003'; } | ` +
|
|
732
|
+
`script -qefc ${shellQuote(command)} /dev/null`;
|
|
733
|
+
const result = spawnSync('sh', ['-c', scriptCommand], {
|
|
734
|
+
cwd: packageRoot,
|
|
735
|
+
encoding: 'utf8',
|
|
736
|
+
timeout: 3000,
|
|
737
|
+
});
|
|
738
|
+
assertEqual(result.error?.code, undefined, 'terminal interrupt timeout');
|
|
739
|
+
assertEqual(result.status, 130, 'SIGINT exit status');
|
|
740
|
+
assertIncludes(result.stdout, '?- repeat, fail.', 'terminal query echo');
|
|
741
|
+
},
|
|
742
|
+
},
|
|
722
743
|
{
|
|
723
744
|
name: 'REPL accepts multiline period-terminated queries',
|
|
724
745
|
run: () => {
|
|
@@ -3109,6 +3130,10 @@ function between(text, startMarker, endMarker) {
|
|
|
3109
3130
|
return text.slice(contentStart, end);
|
|
3110
3131
|
}
|
|
3111
3132
|
|
|
3133
|
+
function shellQuote(value) {
|
|
3134
|
+
return `'${String(value).replaceAll("'", "'\"'\"'")}'`;
|
|
3135
|
+
}
|
|
3136
|
+
|
|
3112
3137
|
function runCli(args, options = {}) {
|
|
3113
3138
|
return spawnSync(process.execPath, [bin, ...args], {
|
|
3114
3139
|
cwd: packageRoot,
|
package/the-art-of-eyeprolog.md
CHANGED
|
@@ -6216,7 +6216,11 @@ may span lines and end with a full stop, as in Scryer Prolog:
|
|
|
6216
6216
|
When another answer exists in an interactive terminal, press `;`, Space, or
|
|
6217
6217
|
`n` to ask for it immediately; no Return is needed. Return or `.` stops
|
|
6218
6218
|
enumeration, `a` enumerates all remaining answers, `f` enumerates the next
|
|
6219
|
-
five, and `h` displays the answer-control help.
|
|
6219
|
+
five, and `h` displays the answer-control help. While a query is actively
|
|
6220
|
+
computing, EyeProlog releases readline's terminal signal handling: `Ctrl-C`
|
|
6221
|
+
therefore terminates the current EyeProlog process immediately, and on POSIX
|
|
6222
|
+
terminals `Ctrl-Z` suspends it in the usual shell-managed way. This remains a
|
|
6223
|
+
host top-level convention rather than an ISO/IEC 13211-1 language feature. A
|
|
6220
6224
|
period-terminated query with no solutions prints `false.`; a solution without
|
|
6221
6225
|
visible variable bindings prints `true.`. Answer substitutions are rendered as
|
|
6222
6226
|
valid Prolog syntax under the current operator table: when a bound value would
|