eyeprolog 1.2.1 → 1.2.3
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
package/src/iso.js
CHANGED
|
@@ -1063,16 +1063,36 @@ function isTerminatingFullStop(source, index) {
|
|
|
1063
1063
|
return true;
|
|
1064
1064
|
}
|
|
1065
1065
|
|
|
1066
|
+
function quotedEscapeEnd(source, index) {
|
|
1067
|
+
const escaped = source[index + 1] ?? '';
|
|
1068
|
+
if (!escaped) return index;
|
|
1069
|
+
|
|
1070
|
+
// ISO 6.4.2.1 numeric escapes include a terminating backslash. Consume
|
|
1071
|
+
// that delimiter as part of the quoted character so stream scanning does
|
|
1072
|
+
// not mistake it for an escape of the quote which follows.
|
|
1073
|
+
if (escaped === 'x') {
|
|
1074
|
+
let cursor = index + 2;
|
|
1075
|
+
while (/^[0-9A-Fa-f]$/.test(source[cursor] ?? '')) cursor++;
|
|
1076
|
+
return source[cursor] === '\\' ? cursor : Math.max(index + 1, cursor - 1);
|
|
1077
|
+
}
|
|
1078
|
+
if (/^[0-7]$/.test(escaped)) {
|
|
1079
|
+
let cursor = index + 1;
|
|
1080
|
+
while (/^[0-7]$/.test(source[cursor] ?? '')) cursor++;
|
|
1081
|
+
return source[cursor] === '\\' ? cursor : Math.max(index + 1, cursor - 1);
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
return index + 1;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1066
1087
|
function* termTextCandidates(stream) {
|
|
1067
1088
|
const source = String(stream.content);
|
|
1068
|
-
let quote = null,
|
|
1089
|
+
let quote = null, lineComment = false, blockComment = false;
|
|
1069
1090
|
for (let i = stream.position; i < source.length; i++) {
|
|
1070
1091
|
const ch = source[i], next = source[i + 1];
|
|
1071
1092
|
if (lineComment) { if (ch === '\n') lineComment = false; continue; }
|
|
1072
1093
|
if (blockComment) { if (ch === '*' && next === '/') { blockComment = false; i++; } continue; }
|
|
1073
1094
|
if (quote) {
|
|
1074
|
-
if (
|
|
1075
|
-
else if (ch === '\\') escaped = true;
|
|
1095
|
+
if (ch === '\\') i = quotedEscapeEnd(source, i);
|
|
1076
1096
|
else if (ch === quote && next === quote) i++;
|
|
1077
1097
|
else if (ch === quote) quote = null;
|
|
1078
1098
|
continue;
|
|
@@ -1087,13 +1107,21 @@ function* termTextCandidates(stream) {
|
|
|
1087
1107
|
}
|
|
1088
1108
|
function convertedTermText(text, solver) {
|
|
1089
1109
|
if (solver.prologFlags.get('char_conversion')?.value?.name !== 'on' || solver.charConversions.size === 0) return text;
|
|
1090
|
-
let result = '', quote = null
|
|
1091
|
-
for (
|
|
1110
|
+
let result = '', quote = null;
|
|
1111
|
+
for (let i = 0; i < text.length; i++) {
|
|
1112
|
+
const ch = text[i], next = text[i + 1];
|
|
1092
1113
|
if (quote) {
|
|
1093
1114
|
result += ch;
|
|
1094
|
-
if (
|
|
1095
|
-
|
|
1096
|
-
|
|
1115
|
+
if (ch === '\\') {
|
|
1116
|
+
const end = quotedEscapeEnd(text, i);
|
|
1117
|
+
if (end > i) result += text.slice(i + 1, end + 1);
|
|
1118
|
+
i = end;
|
|
1119
|
+
} else if (ch === quote && next === quote) {
|
|
1120
|
+
result += next;
|
|
1121
|
+
i++;
|
|
1122
|
+
} else if (ch === quote) {
|
|
1123
|
+
quote = null;
|
|
1124
|
+
}
|
|
1097
1125
|
} else if (ch === "'" || ch === '"') {
|
|
1098
1126
|
quote = ch;
|
|
1099
1127
|
result += ch;
|
package/src/repl.js
CHANGED
|
@@ -48,6 +48,7 @@ export async function runRepl(engine, options = {}) {
|
|
|
48
48
|
continue;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
await prepareInteractiveTermInput(state, goal, reader);
|
|
51
52
|
const result = await solveQuery(engine, state, goal, reader, output);
|
|
52
53
|
if (result?.halted) {
|
|
53
54
|
exitCode = result.code;
|
|
@@ -166,6 +167,57 @@ async function readQuery(reader) {
|
|
|
166
167
|
}
|
|
167
168
|
}
|
|
168
169
|
|
|
170
|
+
async function prepareInteractiveTermInput(state, goal, reader) {
|
|
171
|
+
const stream = interactiveTermInputStream(state, goal);
|
|
172
|
+
if (stream == null || terminalFullStop(String(stream.content).slice(stream.position)) >= 0) return;
|
|
173
|
+
|
|
174
|
+
const text = await readInteractiveTerm(reader);
|
|
175
|
+
if (text == null) return;
|
|
176
|
+
stream.content += text;
|
|
177
|
+
stream.pastEnd = false;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function interactiveTermInputStream(state, goal) {
|
|
181
|
+
if (goal.type !== 'compound') return null;
|
|
182
|
+
let reference = null;
|
|
183
|
+
if (goal.name === 'read' && goal.arity === 1) {
|
|
184
|
+
reference = state.solver.io.currentInput;
|
|
185
|
+
} else if (goal.name === 'read' && goal.arity === 2) {
|
|
186
|
+
reference = explicitInputReference(goal.args[0]);
|
|
187
|
+
} else if (goal.name === 'read_term' && goal.arity === 2) {
|
|
188
|
+
reference = state.solver.io.currentInput;
|
|
189
|
+
} else if (goal.name === 'read_term' && goal.arity === 3) {
|
|
190
|
+
reference = explicitInputReference(goal.args[0]);
|
|
191
|
+
} else {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const stream = reference == null ? null : state.solver.io.resolve(reference);
|
|
196
|
+
return stream?.standard && stream.alias === 'user_input' && stream.mode === 'read' ? stream : null;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function explicitInputReference(term) {
|
|
200
|
+
if (term.type === 'atom') return term.name;
|
|
201
|
+
if (term.type === 'compound' && term.name === '$stream' && term.arity === 1 &&
|
|
202
|
+
term.args[0].type === 'number' && /^\d+$/.test(term.args[0].name)) {
|
|
203
|
+
return Number(term.args[0].name);
|
|
204
|
+
}
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async function readInteractiveTerm(reader) {
|
|
209
|
+
let source = '';
|
|
210
|
+
let prompt = '|: ';
|
|
211
|
+
while (true) {
|
|
212
|
+
const line = await reader.read(prompt);
|
|
213
|
+
if (line == null) return source.trim() ? source : null;
|
|
214
|
+
source += `${line}\n`;
|
|
215
|
+
const end = terminalFullStop(source);
|
|
216
|
+
if (end >= 0) return source.slice(0, end + 1) + '\n';
|
|
217
|
+
prompt = '| ';
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
169
221
|
function quotedEscapeEnd(source, index) {
|
|
170
222
|
const escaped = source[index + 1] ?? '';
|
|
171
223
|
if (!escaped) return index;
|
|
@@ -65,3 +65,23 @@ default_streams(ok) :-
|
|
|
65
65
|
standard_write(ok) :-
|
|
66
66
|
write(io_marker),
|
|
67
67
|
nl.
|
|
68
|
+
|
|
69
|
+
%% goal: numeric_escape_term_input(ok)
|
|
70
|
+
|
|
71
|
+
numeric_escape_term_input(ok) :-
|
|
72
|
+
open('/tmp/eyeprolog-iso-read-escapes.txt', write, Output, []),
|
|
73
|
+
put_code(Output, 39), put_code(Output, 92), put_code(Output, 55),
|
|
74
|
+
put_code(Output, 92), put_code(Output, 39), put_code(Output, 46), nl(Output),
|
|
75
|
+
put_code(Output, 39), put_code(Output, 92), put_code(Output, 120), put_code(Output, 55),
|
|
76
|
+
put_code(Output, 92), put_code(Output, 39), put_code(Output, 46), nl(Output),
|
|
77
|
+
close(Output),
|
|
78
|
+
current_input(Old),
|
|
79
|
+
open('/tmp/eyeprolog-iso-read-escapes.txt', read, Input, []),
|
|
80
|
+
set_input(Input),
|
|
81
|
+
read(A),
|
|
82
|
+
read_term(B, []),
|
|
83
|
+
set_input(Old),
|
|
84
|
+
close(Input),
|
|
85
|
+
A == '\a',
|
|
86
|
+
B == '\a'.
|
|
87
|
+
|
package/test/run-regression.mjs
CHANGED
|
@@ -671,6 +671,29 @@ c4 ?- call((!;1)).
|
|
|
671
671
|
assertEqual(result.stderr, '', 'stderr');
|
|
672
672
|
},
|
|
673
673
|
},
|
|
674
|
+
{
|
|
675
|
+
name: 'REPL read predicates consume following interactive term input',
|
|
676
|
+
run: () => {
|
|
677
|
+
const result = runCli([], {
|
|
678
|
+
input:
|
|
679
|
+
'read(X).\n' +
|
|
680
|
+
'foo.\n' +
|
|
681
|
+
'read_term(Y, []).\n' +
|
|
682
|
+
"'\\7\\'.\n" +
|
|
683
|
+
'read(user_input, Z).\n' +
|
|
684
|
+
'bar.\n' +
|
|
685
|
+
'halt.\n',
|
|
686
|
+
});
|
|
687
|
+
assertEqual(result.status, 0, 'exit status');
|
|
688
|
+
assertEqual(result.stdout,
|
|
689
|
+
'?- |: X = foo.\n' +
|
|
690
|
+
"?- |: Y = '\\a'.\n" +
|
|
691
|
+
'?- |: Z = bar.\n' +
|
|
692
|
+
'?- ',
|
|
693
|
+
'stdout');
|
|
694
|
+
assertEqual(result.stderr, '', 'stderr');
|
|
695
|
+
},
|
|
696
|
+
},
|
|
674
697
|
{
|
|
675
698
|
name: 'REPL accepts multiline period-terminated queries',
|
|
676
699
|
run: () => {
|
|
@@ -1206,6 +1229,44 @@ c4 ?- call((!;1)).
|
|
|
1206
1229
|
assertIncludes(result.stderr, 'implementation-specific directive use_module/1', 'stderr');
|
|
1207
1230
|
},
|
|
1208
1231
|
},
|
|
1232
|
+
{
|
|
1233
|
+
name: 'term input accepts ISO numeric escapes through read predicates',
|
|
1234
|
+
run: () => {
|
|
1235
|
+
const escapePath = path.join(tmp, `read-escapes-${++tmpCounter}.term`);
|
|
1236
|
+
// Two raw read-terms: '\7\'. and '\x7\'. Build the file from
|
|
1237
|
+
// character codes so this regression tests stream parsing rather than
|
|
1238
|
+
// the parser which reads this JavaScript fixture.
|
|
1239
|
+
fs.writeFileSync(escapePath, String.fromCharCode(
|
|
1240
|
+
39, 92, 55, 92, 39, 46, 10,
|
|
1241
|
+
39, 92, 120, 55, 92, 39, 46, 10,
|
|
1242
|
+
));
|
|
1243
|
+
const source = [
|
|
1244
|
+
`read_escapes(A, B) :-`,
|
|
1245
|
+
` current_input(Old),`,
|
|
1246
|
+
` open(${sourceAtom(escapePath)}, read, Input, []),`,
|
|
1247
|
+
` set_input(Input),`,
|
|
1248
|
+
` read(A),`,
|
|
1249
|
+
` read_term(B, []),`,
|
|
1250
|
+
` set_input(Old),`,
|
|
1251
|
+
` close(Input).`,
|
|
1252
|
+
'',
|
|
1253
|
+
].join('\n');
|
|
1254
|
+
assertEqual(
|
|
1255
|
+
run(source, { goal: 'read_escapes(A, B)' }).stdout,
|
|
1256
|
+
"read_escapes('\\a', '\\a').\n",
|
|
1257
|
+
'read/1 and read_term/2 numeric escapes',
|
|
1258
|
+
);
|
|
1259
|
+
|
|
1260
|
+
assertEqual(
|
|
1261
|
+
run('answer(T) :- read(T).\n', {
|
|
1262
|
+
goal: 'answer(T)',
|
|
1263
|
+
ioOptions: { input: "'\\7\\'." },
|
|
1264
|
+
}).stdout,
|
|
1265
|
+
"answer('\\a').\n",
|
|
1266
|
+
'read/1 user_input numeric escape',
|
|
1267
|
+
);
|
|
1268
|
+
},
|
|
1269
|
+
},
|
|
1209
1270
|
{
|
|
1210
1271
|
name: 'term input keeps dotted operators intact and uses program operators',
|
|
1211
1272
|
run: () => {
|
package/the-art-of-eyeprolog.md
CHANGED
|
@@ -5751,9 +5751,11 @@ the beginning. A peek does not mark the stream as past-end.
|
|
|
5751
5751
|
| `write_canonical(+Term)`, `write_canonical(+Stream,+Term)` | Writes quoted canonical functor notation while ignoring operators and without interpreting *$VAR/1*. |
|
|
5752
5752
|
| `write_term(+Term,+Options)`, `write_term(+Stream,+Term,+Options)` | Writes with *quoted(true or false)*, *ignore_ops(true or false)*, *numbervars(true or false)*, and *variable_names([Name=Variable,...])*. |
|
|
5753
5753
|
|
|
5754
|
-
Term input uses the program's current operator table and
|
|
5755
|
-
|
|
5756
|
-
|
|
5754
|
+
Term input uses the program's current operator table and the same ISO quoted-character
|
|
5755
|
+
syntax as source text, including backslash-terminated octal and hexadecimal
|
|
5756
|
+
escapes such as `'\7\'` and `'\x7\'`. This applies equally to `read/1-2` and
|
|
5757
|
+
`read_term/2-3`. Installed character conversions are applied outside quoted text
|
|
5758
|
+
when the `char_conversion` flag is `on`. `variable_names/1` and `singletons/1` omit anonymous variables. Output
|
|
5757
5759
|
predicates do not append a period or newline; call `write/1`, then `write('.')`
|
|
5758
5760
|
and `nl/0` when emitting a complete source term manually.
|
|
5759
5761
|
|
|
@@ -6214,8 +6216,21 @@ not be a valid right operand of the displayed `=/2`, EyeProlog adds parentheses,
|
|
|
6214
6216
|
for example `T = (a = b).` rather than the invalid `T = a = b.`. Use `[file].`
|
|
6215
6217
|
or `['file.pl'].` to
|
|
6216
6218
|
consult local source, and `halt.` or `halt(Status).` to leave the top level.
|
|
6217
|
-
|
|
6218
|
-
|
|
6219
|
+
A direct top-level `read/1-2` or `read_term/2-3` from `user_input` requests the
|
|
6220
|
+
next full-stop-terminated Prolog term with a `|: ` input prompt instead of
|
|
6221
|
+
treating the interactive input stream as already exhausted. For example:
|
|
6222
|
+
|
|
6223
|
+
```text
|
|
6224
|
+
?- read(X).
|
|
6225
|
+
|: hello.
|
|
6226
|
+
X = hello.
|
|
6227
|
+
```
|
|
6228
|
+
|
|
6229
|
+
The top-level prompt itself is a host-interface convention rather than part of
|
|
6230
|
+
ISO/IEC 13211-1; the term that follows it is parsed by the same ISO term-input
|
|
6231
|
+
machinery as `read/1-2` and `read_term/2-3` on other text streams. Up and Down
|
|
6232
|
+
recall queries from the current session. Explicit `eyeprolog -h` displays
|
|
6233
|
+
command-line help.
|
|
6219
6234
|
|
|
6220
6235
|
### Selecting goals
|
|
6221
6236
|
|