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 +1 -1
- package/src/iso.js +17 -3
- package/src/parser.js +7 -1
- package/src/repl.js +18 -4
- package/test/run-regression.mjs +71 -3
package/package.json
CHANGED
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 {
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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.
|
|
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
|
@@ -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
|
-
'
|
|
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: () => {
|