eyeprolog 1.1.27 → 1.1.29
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 +1 -1
- package/src/repl.js +34 -42
- package/src/write.js +6 -1
- package/test/run-regression.mjs +33 -2
- package/the-art-of-eyeprolog.md +13 -7
package/package.json
CHANGED
package/src/iso.js
CHANGED
|
@@ -1783,7 +1783,7 @@ function* phraseBuiltin({ solver, goal, env }) {
|
|
|
1783
1783
|
solver.absorbStatsFrom(child);
|
|
1784
1784
|
}
|
|
1785
1785
|
}
|
|
1786
|
-
function formalErrorTerm(error) {
|
|
1786
|
+
export function formalErrorTerm(error) {
|
|
1787
1787
|
const context = error.contextTerm ?? atom('eyeprolog');
|
|
1788
1788
|
if (error.formalTerm != null) return compound('error', [error.formalTerm, context]);
|
|
1789
1789
|
const parse = (text) => {
|
package/src/repl.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import fs from 'node:fs/promises';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { createInterface } from 'node:readline';
|
|
5
|
+
import { formalErrorTerm } from './iso.js';
|
|
5
6
|
|
|
6
7
|
const ANSWER_HELP = `
|
|
7
8
|
SPACE, "n" or ";": next solution, if any
|
|
@@ -163,11 +164,33 @@ async function readQuery(reader) {
|
|
|
163
164
|
}
|
|
164
165
|
}
|
|
165
166
|
|
|
167
|
+
function quotedEscapeEnd(source, index) {
|
|
168
|
+
const escaped = source[index + 1] ?? '';
|
|
169
|
+
if (!escaped) return index;
|
|
170
|
+
|
|
171
|
+
// ISO 6.4.2.1 octal and hexadecimal escapes are terminated by a
|
|
172
|
+
// backslash. Consume that terminator as part of the escape so the REPL
|
|
173
|
+
// scanner does not mistake it for an escape of the following quote.
|
|
174
|
+
if (escaped === 'x') {
|
|
175
|
+
let cursor = index + 2;
|
|
176
|
+
while (/^[0-9A-Fa-f]$/.test(source[cursor] ?? '')) cursor++;
|
|
177
|
+
return source[cursor] === '\\' ? cursor : Math.max(index + 1, cursor - 1);
|
|
178
|
+
}
|
|
179
|
+
if (/^[0-7]$/.test(escaped)) {
|
|
180
|
+
let cursor = index + 1;
|
|
181
|
+
while (/^[0-7]$/.test(source[cursor] ?? '')) cursor++;
|
|
182
|
+
return source[cursor] === '\\' ? cursor : Math.max(index + 1, cursor - 1);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Meta escapes, symbolic control escapes, and continuation escapes consume
|
|
186
|
+
// one character after the backslash. The parser performs validity checks.
|
|
187
|
+
return index + 1;
|
|
188
|
+
}
|
|
189
|
+
|
|
166
190
|
function terminalFullStop(source) {
|
|
167
191
|
let quote = null;
|
|
168
192
|
let lineComment = false;
|
|
169
193
|
let blockComment = false;
|
|
170
|
-
let escaped = false;
|
|
171
194
|
let depth = 0;
|
|
172
195
|
|
|
173
196
|
for (let i = 0; i < source.length; i++) {
|
|
@@ -185,10 +208,8 @@ function terminalFullStop(source) {
|
|
|
185
208
|
continue;
|
|
186
209
|
}
|
|
187
210
|
if (quote != null) {
|
|
188
|
-
if (
|
|
189
|
-
|
|
190
|
-
} else if (ch === '\\') {
|
|
191
|
-
escaped = true;
|
|
211
|
+
if (ch === '\\') {
|
|
212
|
+
i = quotedEscapeEnd(source, i);
|
|
192
213
|
} else if (ch === quote) {
|
|
193
214
|
if (next === quote) i++;
|
|
194
215
|
else quote = null;
|
|
@@ -425,46 +446,17 @@ function formatError(engine, state, error) {
|
|
|
425
446
|
const env = new engine.Env();
|
|
426
447
|
const variableNames = new Map();
|
|
427
448
|
let generated = 0;
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
operators: [...state.program.operators.values()],
|
|
436
|
-
variableNames,
|
|
437
|
-
});
|
|
438
|
-
if (error.contextTerm != null) {
|
|
439
|
-
const context = engine.formatTermForWrite(error.contextTerm, env, {
|
|
440
|
-
quoted: true,
|
|
441
|
-
operators: [...state.program.operators.values()],
|
|
442
|
-
variableNames,
|
|
443
|
-
});
|
|
444
|
-
return `error(${formal}, ${context}).`;
|
|
445
|
-
}
|
|
446
|
-
return `error(${formal}).`;
|
|
447
|
-
}
|
|
448
|
-
if (error.culprit != null) {
|
|
449
|
-
collectUnboundVariables(engine, error.culprit, env, variableNames, () => `_${letterName(generated++)}`);
|
|
450
|
-
}
|
|
451
|
-
if (error.contextTerm != null) {
|
|
452
|
-
collectUnboundVariables(engine, error.contextTerm, env, variableNames, () => `_${letterName(generated++)}`);
|
|
453
|
-
}
|
|
454
|
-
const culprit = error.culprit == null ? '' : `, ${engine.formatTermForWrite(error.culprit, env, {
|
|
449
|
+
// ISO 7.12.1 represents processor errors as error(ErrorTerm, ImpDef).
|
|
450
|
+
// Reuse the same conversion as catch/3 so uncaught errors at the top
|
|
451
|
+
// level cannot lose the implementation-defined context or misplace a
|
|
452
|
+
// culprit as the second argument of error/2.
|
|
453
|
+
const term = formalErrorTerm(error);
|
|
454
|
+
collectUnboundVariables(engine, term, env, variableNames, () => `_${letterName(generated++)}`);
|
|
455
|
+
return `${engine.formatTermForWrite(term, env, {
|
|
455
456
|
quoted: true,
|
|
456
457
|
operators: [...state.program.operators.values()],
|
|
457
458
|
variableNames,
|
|
458
|
-
})}
|
|
459
|
-
if (error.contextTerm != null) {
|
|
460
|
-
const context = engine.formatTermForWrite(error.contextTerm, env, {
|
|
461
|
-
quoted: true,
|
|
462
|
-
operators: [...state.program.operators.values()],
|
|
463
|
-
variableNames,
|
|
464
|
-
});
|
|
465
|
-
return `error(${error.formal}${culprit}, ${context}).`;
|
|
466
|
-
}
|
|
467
|
-
return `error(${error.formal}${culprit}).`;
|
|
459
|
+
})}.`;
|
|
468
460
|
}
|
|
469
461
|
const message = error?.message ?? String(error);
|
|
470
462
|
return message.endsWith('.') ? message : `${message}.`;
|
package/src/write.js
CHANGED
|
@@ -22,8 +22,13 @@ function quoteAtom(name) {
|
|
|
22
22
|
for (const ch of name) {
|
|
23
23
|
if (ch === "'") out += "''";
|
|
24
24
|
else if (ch === '\\') out += '\\\\';
|
|
25
|
-
else if (ch === '\
|
|
25
|
+
else if (ch === '\x07') out += '\\a';
|
|
26
|
+
else if (ch === '\b') out += '\\b';
|
|
27
|
+
else if (ch === '\r') out += '\\r';
|
|
28
|
+
else if (ch === '\f') out += '\\f';
|
|
26
29
|
else if (ch === '\t') out += '\\t';
|
|
30
|
+
else if (ch === '\n') out += '\\n';
|
|
31
|
+
else if (ch === '\v') out += '\\v';
|
|
27
32
|
else out += ch;
|
|
28
33
|
}
|
|
29
34
|
return out + "'";
|
package/test/run-regression.mjs
CHANGED
|
@@ -533,8 +533,23 @@ c4 ?- call((!;1)).
|
|
|
533
533
|
});
|
|
534
534
|
assertEqual(result.status, 0, 'exit status');
|
|
535
535
|
assertEqual(result.stdout,
|
|
536
|
-
'?- error(type_error(list
|
|
537
|
-
'?- error(type_error(list
|
|
536
|
+
'?- error(type_error(list, [1, [], _A | 2]), eyeprolog).\n' +
|
|
537
|
+
'?- error(type_error(list, [1, [], _A | 2]), eyeprolog).\n' +
|
|
538
|
+
'?- ',
|
|
539
|
+
'stdout');
|
|
540
|
+
assertEqual(result.stderr, '', 'stderr');
|
|
541
|
+
},
|
|
542
|
+
},
|
|
543
|
+
{
|
|
544
|
+
name: 'REPL uncaught ISO errors retain the error/2 implementation context',
|
|
545
|
+
run: () => {
|
|
546
|
+
const result = runCli([], {
|
|
547
|
+
input: '_ is _.\ncatch(_ is _, error(Error, Imp_def), true).\nhalt.\n',
|
|
548
|
+
});
|
|
549
|
+
assertEqual(result.status, 0, 'exit status');
|
|
550
|
+
assertEqual(result.stdout,
|
|
551
|
+
'?- error(instantiation_error, eyeprolog).\n' +
|
|
552
|
+
'?- Error = instantiation_error, Imp_def = eyeprolog.\n' +
|
|
538
553
|
'?- ',
|
|
539
554
|
'stdout');
|
|
540
555
|
assertEqual(result.stderr, '', 'stderr');
|
|
@@ -602,6 +617,22 @@ c4 ?- call((!;1)).
|
|
|
602
617
|
assertEqual(result.stderr, '', 'stderr');
|
|
603
618
|
},
|
|
604
619
|
},
|
|
620
|
+
{
|
|
621
|
+
name: 'REPL recognizes ISO octal and hexadecimal escapes in quoted atoms',
|
|
622
|
+
run: () => {
|
|
623
|
+
const result = runCli([], {
|
|
624
|
+
input: "writeq('\\7\\').\nwriteq('\\x7\\').\nwriteq('\\a').\nhalt.\n",
|
|
625
|
+
});
|
|
626
|
+
assertEqual(result.status, 0, 'exit status');
|
|
627
|
+
assertEqual(result.stdout,
|
|
628
|
+
"?- '\\a' true.\n" +
|
|
629
|
+
"?- '\\a' true.\n" +
|
|
630
|
+
"?- '\\a' true.\n" +
|
|
631
|
+
'?- ',
|
|
632
|
+
'stdout');
|
|
633
|
+
assertEqual(result.stderr, '', 'stderr');
|
|
634
|
+
},
|
|
635
|
+
},
|
|
605
636
|
{
|
|
606
637
|
name: 'REPL accepts multiline period-terminated queries',
|
|
607
638
|
run: () => {
|
package/the-art-of-eyeprolog.md
CHANGED
|
@@ -5268,8 +5268,11 @@ city('München').
|
|
|
5268
5268
|
message("café").
|
|
5269
5269
|
```
|
|
5270
5270
|
|
|
5271
|
-
Inside a quoted atom, a single quote is doubled: `'don''t'`.
|
|
5272
|
-
the
|
|
5271
|
+
Inside a quoted atom, a single quote is doubled: `'don''t'`. Quoted characters
|
|
5272
|
+
support the ISO symbolic control escapes such as `\a`, `\n`, and `\t`, and numeric
|
|
5273
|
+
octal or hexadecimal escapes are terminated by a backslash; for example, `'\7\'`
|
|
5274
|
+
and `'\x7\'` both denote the alert character. Double-quoted lists use the same
|
|
5275
|
+
quoted-character escapes. Whitespace is insignificant
|
|
5273
5276
|
between tokens, and a `%` comment continues to the end of its line. Doubling
|
|
5274
5277
|
the active delimiter is also accepted inside either quoted form, so `""`
|
|
5275
5278
|
inside double-quoted notation denotes one literal double quote character.
|
|
@@ -5803,11 +5806,14 @@ instances whose message contains the corresponding Prolog error term.
|
|
|
5803
5806
|
`error(Formal,eyeprolog)` term. `throw/1` copies its ball before unwinding: bound
|
|
5804
5807
|
parts are preserved, repeated variables remain shared within the copied ball,
|
|
5805
5808
|
and unbound variables are fresh with respect to the protected goal and catcher.
|
|
5806
|
-
Catchable error terms follow the same variable-freshening rule.
|
|
5807
|
-
top level
|
|
5808
|
-
|
|
5809
|
-
|
|
5810
|
-
|
|
5809
|
+
Catchable error terms follow the same variable-freshening rule. The interactive
|
|
5810
|
+
top level uses the same ISO error envelope for uncaught processor errors, so an
|
|
5811
|
+
ordinary runtime error is displayed as `error(Formal,eyeprolog)` rather than
|
|
5812
|
+
dropping the implementation-defined second argument. Variables that occur in an
|
|
5813
|
+
uncaught ISO error are rendered with fresh answer names such as `_A` rather than
|
|
5814
|
+
reusing query-variable spellings such as `X` or `Xx`; this keeps the displayed
|
|
5815
|
+
error consistent with the copied exception term. An unmatched ball or error
|
|
5816
|
+
continues outward.
|
|
5811
5817
|
|
|
5812
5818
|
Streams belong to one solver run and are shared by nested calls, exceptions,
|
|
5813
5819
|
and solution collectors. `user_input` and `user_output` are always present.
|