eyeprolog 1.3.24 → 1.3.26

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.26",
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
@@ -6,7 +6,10 @@ import {
6
6
  properListItems, termIsGround, termToString, unify, variable, variantTerms,
7
7
  } from './term.js';
8
8
  import { sameNumberValue } from './number-value.js';
9
- import { NumberRepresentationError, createParserOperatorState, parseGoalText, parseNumberTokenText, parseTermText } from './parser.js';
9
+ import {
10
+ NumberRepresentationError, createParserOperatorState, floatRepresentationErrorFormal,
11
+ parseGoalText, parseNumberTokenText, parseTermText,
12
+ } from './parser.js';
10
13
  import { formatTermForWrite } from './write.js';
11
14
  import { emptyTerminalSequence, expandDcgBody, isListOrPartialList, validateDcgEmbeddedGoals } from './dcg.js';
12
15
  import {
@@ -1025,10 +1028,21 @@ function inputUnitBuiltin(name) {
1025
1028
  throw new PrologError('permission_error(input, past_end_of_stream)', streamHandle(stream.id));
1026
1029
  }
1027
1030
  if (stream.pastEnd && stream.eofAction === 'reset') {
1028
- stream.position = 0;
1031
+ // A terminal EOF (Ctrl-D) is local to one input operation. The next
1032
+ // operation should wait for fresh terminal input rather than rewinding
1033
+ // and replaying the already-consumed interactive buffer.
1034
+ if (typeof stream.interactiveReadUnit !== 'function') stream.position = 0;
1029
1035
  stream.pastEnd = false;
1030
1036
  }
1031
1037
  const peek = name.startsWith('peek');
1038
+ if (stream.position >= stream.content.length &&
1039
+ typeof stream.interactiveReadUnit === 'function') {
1040
+ const text = stream.interactiveReadUnit();
1041
+ if (text != null) {
1042
+ stream.content += String(text);
1043
+ stream.pastEnd = false;
1044
+ }
1045
+ }
1032
1046
  let unit;
1033
1047
  try {
1034
1048
  unit = solver.io.readUnit(stream, peek);
@@ -1684,7 +1698,7 @@ function canonicalNumberText(value) {
1684
1698
  return value.name;
1685
1699
  }
1686
1700
  const text = numberTextFromDouble(finite);
1687
- if (text == null) throw new PrologError('representation_error(max_float)');
1701
+ if (text == null) throw new PrologError(floatRepresentationErrorFormal(value.name));
1688
1702
  return text;
1689
1703
  }
1690
1704
 
package/src/parser.js CHANGED
@@ -12,10 +12,16 @@ export class NumberRepresentationError extends Error {
12
12
  }
13
13
  }
14
14
 
15
+ export function floatRepresentationErrorFormal(text) {
16
+ return String(text ?? '').startsWith('-')
17
+ ? 'representation_error(min_float)'
18
+ : 'representation_error(max_float)';
19
+ }
20
+
15
21
  function finiteFloatTokenText(text) {
16
22
  const value = Number(text);
17
23
  if (!Number.isFinite(value)) {
18
- throw new NumberRepresentationError('representation_error(max_float)');
24
+ throw new NumberRepresentationError(floatRepresentationErrorFormal(text));
19
25
  }
20
26
  return value === 0 ? '0.0' : text;
21
27
  }
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) {
@@ -745,6 +745,7 @@ c4 ?- call((!;1)).
745
745
  input: [
746
746
  'T = 1.0e-99999, F is T.',
747
747
  'T = 1.0e99999, float(T).',
748
+ 'T = -1.0e99999, float(T).',
748
749
  'T = 1.0e99999, float(T), F is T.',
749
750
  'T = 1.0e99999, U = 2.0e99999, T > U.',
750
751
  'T = 1.0e99999, U = 2.0e99999, T < U.',
@@ -759,7 +760,12 @@ c4 ?- call((!;1)).
759
760
  assertEqual(
760
761
  (result.stdout.match(/error\(representation_error\(max_float\)\)\./g) ?? []).length,
761
762
  6,
762
- 'all overflowing literals fail while being read',
763
+ 'positive overflowing literals fail while being read',
764
+ );
765
+ assertEqual(
766
+ (result.stdout.match(/error\(representation_error\(min_float\)\)\./g) ?? []).length,
767
+ 1,
768
+ 'negative overflowing literal reports min_float',
763
769
  );
764
770
  assertEqual(result.stderr, '', 'stderr');
765
771
 
@@ -769,7 +775,14 @@ c4 ?- call((!;1)).
769
775
  } catch (error) {
770
776
  overflow = error;
771
777
  }
772
- assertEqual(overflow?.formal, 'representation_error(max_float)', 'number token overflow');
778
+ assertEqual(overflow?.formal, 'representation_error(max_float)', 'positive number token overflow');
779
+ let negativeOverflow = null;
780
+ try {
781
+ parseNumberTokenText('-1.0e99999');
782
+ } catch (error) {
783
+ negativeOverflow = error;
784
+ }
785
+ assertEqual(negativeOverflow?.formal, 'representation_error(min_float)', 'negative number token overflow');
773
786
  assertEqual(parseNumberTokenText('1.0e-99999').name, '0.0', 'number token underflow');
774
787
 
775
788
  const chars = Array.from('1.0e99999', atom);
@@ -783,7 +796,34 @@ c4 ?- call((!;1)).
783
796
  } catch (error) {
784
797
  numberCharsError = error;
785
798
  }
786
- assertEqual(numberCharsError?.formal, 'representation_error(max_float)', 'number_chars overflow');
799
+ assertEqual(numberCharsError?.formal, 'representation_error(max_float)', 'number_chars positive overflow');
800
+
801
+ const negativeChars = Array.from('-1.0e99999', atom);
802
+ let negativeNumberCharsError = null;
803
+ try {
804
+ numberChars({
805
+ goal: compound('number_chars', [variable('N'), listFromItems(negativeChars)]),
806
+ env: new Env(),
807
+ }).next();
808
+ } catch (error) {
809
+ negativeNumberCharsError = error;
810
+ }
811
+ assertEqual(negativeNumberCharsError?.formal, 'representation_error(min_float)', 'number_chars negative overflow');
812
+
813
+ let boundNegativeNumberCharsError = null;
814
+ try {
815
+ numberChars({
816
+ goal: compound('number_chars', [numberTerm('-1.0e99999'), variable('Chars')]),
817
+ env: new Env(),
818
+ }).next();
819
+ } catch (error) {
820
+ boundNegativeNumberCharsError = error;
821
+ }
822
+ assertEqual(
823
+ boundNegativeNumberCharsError?.formal,
824
+ 'representation_error(min_float)',
825
+ 'number_chars host-created negative overflow',
826
+ );
787
827
 
788
828
  let readError = null;
789
829
  try {
@@ -1961,6 +2001,34 @@ c4 ?- call((!;1)).
1961
2001
  assertIncludes(result.stdout, ' true.', 'post-EOF query executes');
1962
2002
  },
1963
2003
  },
2004
+ {
2005
+ name: 'REPL character input is on demand and Ctrl-D stays local (issue #55)',
2006
+ run: () => {
2007
+ if (process.platform === 'win32') return;
2008
+ const available = spawnSync('sh', ['-c',
2009
+ 'command -v script >/dev/null 2>&1 && script --version 2>/dev/null | grep -qi util-linux']);
2010
+ if (available.status !== 0) return;
2011
+ const command = `${shellQuote(process.execPath)} ${shellQuote(bin)}`;
2012
+ const scriptCommand =
2013
+ `{ printf 'peek_char(P), get_char(C), get_code(K).\n'; sleep 0.3; ` +
2014
+ `printf 'ab\n'; sleep 0.3; printf 'get_char(N).\n'; sleep 0.3; ` +
2015
+ `printf 'z\n'; sleep 0.3; printf 'get_char(E).\n'; sleep 0.3; ` +
2016
+ `printf '\\004'; sleep 0.3; printf 'get_char(D).\n'; sleep 0.3; ` +
2017
+ `printf 'q\n'; sleep 0.3; printf 'halt.\n'; } | ` +
2018
+ `script -qefc ${shellQuote(command)} /dev/null`;
2019
+ const result = spawnSync('sh', ['-c', scriptCommand], {
2020
+ cwd: packageRoot,
2021
+ encoding: 'utf8',
2022
+ timeout: 5000,
2023
+ });
2024
+ assertEqual(result.error?.code, undefined, 'interactive character input timeout');
2025
+ assertEqual(result.status, 0, 'exit status');
2026
+ assertIncludes(result.stdout, 'P = a, C = a, K = 98.', 'peek/get char and code input');
2027
+ assertIncludes(result.stdout, 'N = z.', 'Enter submits but is not buffered as the next character');
2028
+ assertIncludes(result.stdout, 'E = end_of_file.', 'Ctrl-D character result');
2029
+ assertIncludes(result.stdout, 'D = q.', 'character input resumes after Ctrl-D');
2030
+ },
2031
+ },
1964
2032
  {
1965
2033
  name: 'interactive user_input hook serves reads reached through user predicates',
1966
2034
  run: () => {