eyeprolog 1.3.24 → 1.3.25
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/iso.js +12 -1
- package/src/repl.js +18 -4
- package/test/run-regression.mjs +28 -0
package/package.json
CHANGED
package/src/iso.js
CHANGED
|
@@ -1025,10 +1025,21 @@ function inputUnitBuiltin(name) {
|
|
|
1025
1025
|
throw new PrologError('permission_error(input, past_end_of_stream)', streamHandle(stream.id));
|
|
1026
1026
|
}
|
|
1027
1027
|
if (stream.pastEnd && stream.eofAction === 'reset') {
|
|
1028
|
-
|
|
1028
|
+
// A terminal EOF (Ctrl-D) is local to one input operation. The next
|
|
1029
|
+
// operation should wait for fresh terminal input rather than rewinding
|
|
1030
|
+
// and replaying the already-consumed interactive buffer.
|
|
1031
|
+
if (typeof stream.interactiveReadUnit !== 'function') stream.position = 0;
|
|
1029
1032
|
stream.pastEnd = false;
|
|
1030
1033
|
}
|
|
1031
1034
|
const peek = name.startsWith('peek');
|
|
1035
|
+
if (stream.position >= stream.content.length &&
|
|
1036
|
+
typeof stream.interactiveReadUnit === 'function') {
|
|
1037
|
+
const text = stream.interactiveReadUnit();
|
|
1038
|
+
if (text != null) {
|
|
1039
|
+
stream.content += String(text);
|
|
1040
|
+
stream.pastEnd = false;
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1032
1043
|
let unit;
|
|
1033
1044
|
try {
|
|
1034
1045
|
unit = solver.io.readUnit(stream, peek);
|
package/src/repl.js
CHANGED
|
@@ -182,6 +182,18 @@ class LineReader {
|
|
|
182
182
|
}
|
|
183
183
|
}
|
|
184
184
|
|
|
185
|
+
readInteractiveUnitSync() {
|
|
186
|
+
if (!this.canReadTermSynchronously()) return null;
|
|
187
|
+
this.output.write('|: ');
|
|
188
|
+
const line = this.readTerminalLineSync();
|
|
189
|
+
if (line == null) return null;
|
|
190
|
+
// The interactive line editor uses Enter to submit character input. Do
|
|
191
|
+
// not leave that submission newline buffered for the next get_/peek_
|
|
192
|
+
// call (matching SWI/Scryer top-level behaviour). An empty submitted
|
|
193
|
+
// line still represents an actual newline character.
|
|
194
|
+
return line.length === 0 ? '\n' : line;
|
|
195
|
+
}
|
|
196
|
+
|
|
185
197
|
readTerminalLineSync() {
|
|
186
198
|
const byte = Buffer.allocUnsafe(1);
|
|
187
199
|
const bytes = [];
|
|
@@ -257,11 +269,13 @@ function makeState(engine, sources, output, options = {}, previousState = null,
|
|
|
257
269
|
});
|
|
258
270
|
const userInput = solver.io.resolve('user_input');
|
|
259
271
|
if (userInput && reader?.canReadTermSynchronously()) {
|
|
260
|
-
// The solver is synchronous.
|
|
261
|
-
// let ISO
|
|
262
|
-
// or
|
|
263
|
-
// and user predicates instead of only when
|
|
272
|
+
// The solver is synchronous. While pullSolution() has readline suspended,
|
|
273
|
+
// let ISO input request terminal data exactly when read/1-2, read_term/2-3,
|
|
274
|
+
// or a character/code input predicate actually executes. This also works
|
|
275
|
+
// inside conjunctions and user predicates instead of only when an input
|
|
276
|
+
// predicate is the whole REPL goal.
|
|
264
277
|
userInput.interactiveReadTerm = () => reader.readInteractiveTermSync(solver);
|
|
278
|
+
userInput.interactiveReadUnit = () => reader.readInteractiveUnitSync();
|
|
265
279
|
}
|
|
266
280
|
const flagOverrides = new Map(previousState?.flagOverrides ?? []);
|
|
267
281
|
for (const [name, value] of flagOverrides) {
|
package/test/run-regression.mjs
CHANGED
|
@@ -1961,6 +1961,34 @@ c4 ?- call((!;1)).
|
|
|
1961
1961
|
assertIncludes(result.stdout, ' true.', 'post-EOF query executes');
|
|
1962
1962
|
},
|
|
1963
1963
|
},
|
|
1964
|
+
{
|
|
1965
|
+
name: 'REPL character input is on demand and Ctrl-D stays local (issue #55)',
|
|
1966
|
+
run: () => {
|
|
1967
|
+
if (process.platform === 'win32') return;
|
|
1968
|
+
const available = spawnSync('sh', ['-c',
|
|
1969
|
+
'command -v script >/dev/null 2>&1 && script --version 2>/dev/null | grep -qi util-linux']);
|
|
1970
|
+
if (available.status !== 0) return;
|
|
1971
|
+
const command = `${shellQuote(process.execPath)} ${shellQuote(bin)}`;
|
|
1972
|
+
const scriptCommand =
|
|
1973
|
+
`{ printf 'peek_char(P), get_char(C), get_code(K).\n'; sleep 0.3; ` +
|
|
1974
|
+
`printf 'ab\n'; sleep 0.3; printf 'get_char(N).\n'; sleep 0.3; ` +
|
|
1975
|
+
`printf 'z\n'; sleep 0.3; printf 'get_char(E).\n'; sleep 0.3; ` +
|
|
1976
|
+
`printf '\\004'; sleep 0.3; printf 'get_char(D).\n'; sleep 0.3; ` +
|
|
1977
|
+
`printf 'q\n'; sleep 0.3; printf 'halt.\n'; } | ` +
|
|
1978
|
+
`script -qefc ${shellQuote(command)} /dev/null`;
|
|
1979
|
+
const result = spawnSync('sh', ['-c', scriptCommand], {
|
|
1980
|
+
cwd: packageRoot,
|
|
1981
|
+
encoding: 'utf8',
|
|
1982
|
+
timeout: 5000,
|
|
1983
|
+
});
|
|
1984
|
+
assertEqual(result.error?.code, undefined, 'interactive character input timeout');
|
|
1985
|
+
assertEqual(result.status, 0, 'exit status');
|
|
1986
|
+
assertIncludes(result.stdout, 'P = a, C = a, K = 98.', 'peek/get char and code input');
|
|
1987
|
+
assertIncludes(result.stdout, 'N = z.', 'Enter submits but is not buffered as the next character');
|
|
1988
|
+
assertIncludes(result.stdout, 'E = end_of_file.', 'Ctrl-D character result');
|
|
1989
|
+
assertIncludes(result.stdout, 'D = q.', 'character input resumes after Ctrl-D');
|
|
1990
|
+
},
|
|
1991
|
+
},
|
|
1964
1992
|
{
|
|
1965
1993
|
name: 'interactive user_input hook serves reads reached through user predicates',
|
|
1966
1994
|
run: () => {
|