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 CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.3.24",
6
+ "version": "1.3.25",
7
7
  "description": "EyeProlog turns facts and rules into answers and proofs.",
8
8
  "type": "module",
9
9
  "main": "./index.js",
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
- stream.position = 0;
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. While pullSolution() has readline suspended,
261
- // let ISO term input request a complete terminal term exactly when read/1-2
262
- // or read_term/2-3 actually executes. This also works inside conjunctions
263
- // and user predicates instead of only when read/* is the whole REPL goal.
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) {
@@ -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: () => {