eyeprolog 1.2.2 → 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 +1 -1
- package/src/repl.js +52 -0
- package/test/run-regression.mjs +23 -0
- package/the-art-of-eyeprolog.md +15 -2
package/package.json
CHANGED
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;
|
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: () => {
|
package/the-art-of-eyeprolog.md
CHANGED
|
@@ -6216,8 +6216,21 @@ not be a valid right operand of the displayed `=/2`, EyeProlog adds parentheses,
|
|
|
6216
6216
|
for example `T = (a = b).` rather than the invalid `T = a = b.`. Use `[file].`
|
|
6217
6217
|
or `['file.pl'].` to
|
|
6218
6218
|
consult local source, and `halt.` or `halt(Status).` to leave the top level.
|
|
6219
|
-
|
|
6220
|
-
|
|
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.
|
|
6221
6234
|
|
|
6222
6235
|
### Selecting goals
|
|
6223
6236
|
|