eyeprolog 1.6.0 → 1.6.1
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/parser.js +12 -3
- package/src/write.js +8 -4
- package/test/regression/cases-white-box.mjs +54 -0
package/package.json
CHANGED
package/src/parser.js
CHANGED
|
@@ -1036,8 +1036,17 @@ class Parser {
|
|
|
1036
1036
|
// mode, a double-quoted chars/codes prefix may be followed by `||Tail`.
|
|
1037
1037
|
// It is equivalent to the corresponding list prefix with Tail as its
|
|
1038
1038
|
// final list tail, e.g. "ab"||T is [a,b|T] with double_quotes(chars).
|
|
1039
|
-
//
|
|
1040
|
-
//
|
|
1039
|
+
// Per the WG17-accepted design (issue #116; see
|
|
1040
|
+
// https://www.complang.tuwien.ac.at/ulrich/iso-prolog/double_bar),
|
|
1041
|
+
// the whole `dql||term` production, and the tail `term` itself, both
|
|
1042
|
+
// have priority 0: the tail is a *primary* term, not an
|
|
1043
|
+
// operator-priority-1 term. An operator argument therefore requires
|
|
1044
|
+
// explicit parentheses, e.g. "abc"||(1 op 2) with `op` any infix
|
|
1045
|
+
// operator, however low its own declared priority. This also stops
|
|
1046
|
+
// an infix operator immediately following the tail from being
|
|
1047
|
+
// absorbed into it: with `op(1, xfy, op)` declared, "abc"||1 op 2
|
|
1048
|
+
// must parse as ("abc"||1) op 2, not "abc"||(1 op 2). The extension
|
|
1049
|
+
// is deliberately absent from the strict ISO profile.
|
|
1041
1050
|
if (!this.strictIso && this.token.type === TOK.BAR) {
|
|
1042
1051
|
const state = {
|
|
1043
1052
|
pos: this.pos,
|
|
@@ -1048,7 +1057,7 @@ class Parser {
|
|
|
1048
1057
|
this.advance();
|
|
1049
1058
|
if (this.token.type === TOK.BAR) {
|
|
1050
1059
|
this.advance();
|
|
1051
|
-
tail = this.parseTerm(operatorStrength(
|
|
1060
|
+
tail = this.parseTerm(operatorStrength(0), false, allowBar, false);
|
|
1052
1061
|
} else {
|
|
1053
1062
|
// A single bar remains ordinary bar syntax. We had to advance once
|
|
1054
1063
|
// to distinguish it from `||`, so restore the tokenizer state.
|
package/src/write.js
CHANGED
|
@@ -356,10 +356,14 @@ function format(term, env, options, table, maxPriority = 1200, context = 'term')
|
|
|
356
356
|
if (quotedSplice != null && (quotedSplice.tail == null || options.doubleBar)) {
|
|
357
357
|
const prefix = writeString(quotedSplice.text);
|
|
358
358
|
if (quotedSplice.tail == null) return prefix;
|
|
359
|
-
// `||` is a normal-profile syntax extension
|
|
360
|
-
//
|
|
361
|
-
//
|
|
362
|
-
|
|
359
|
+
// `||` is a normal-profile syntax extension whose tail has priority 0
|
|
360
|
+
// (issue #116; see
|
|
361
|
+
// https://www.complang.tuwien.ac.at/ulrich/iso-prolog/double_bar), so
|
|
362
|
+
// any operator term in the right operand must be parenthesized to
|
|
363
|
+
// keep the emitted text readable back as the same term — including
|
|
364
|
+
// one whose own declared priority is as low as 1, since the parser
|
|
365
|
+
// accepts nothing but a bare primary term after `||` unparenthesized.
|
|
366
|
+
return `${prefix}||${format(quotedSplice.tail, env, options, table, 0, 'term')}`;
|
|
363
367
|
}
|
|
364
368
|
|
|
365
369
|
if (!options.ignoreOps) {
|
|
@@ -201,6 +201,60 @@ export function whiteBoxCases() {
|
|
|
201
201
|
}
|
|
202
202
|
},
|
|
203
203
|
},
|
|
204
|
+
{
|
|
205
|
+
// https://www.complang.tuwien.ac.at/ulrich/iso-prolog/double_bar: the
|
|
206
|
+
// whole `dql||term` production, and the tail `term` itself, both have
|
|
207
|
+
// priority 0 (the tail is a primary term, not a priority-1 term as an
|
|
208
|
+
// earlier revision of this extension had it). With a custom operator
|
|
209
|
+
// declared as low as priority 1, an unparenthesized tail must stop
|
|
210
|
+
// before that operator rather than absorb it, and a parenthesized tail
|
|
211
|
+
// must print with parentheses to remain distinguishable on readback.
|
|
212
|
+
name: 'double-bar tail has priority 0, so it is not absorbed into a following low-priority operator (issue #116)',
|
|
213
|
+
run: () => {
|
|
214
|
+
const operatorDefinitions = [...ISO_OPERATOR_DEFINITIONS, [1, 'xfy', 'op']];
|
|
215
|
+
|
|
216
|
+
// "abc"||1 op 2 must parse as ("abc"||1) op 2, not "abc"||(1 op 2):
|
|
217
|
+
// op's priority (1) is not <= the tail's max priority (0), so an
|
|
218
|
+
// unparenthesized tail parse must stop at `1` and let `op 2` apply
|
|
219
|
+
// to the whole splice term instead.
|
|
220
|
+
const unparenthesized = parseGoalText('p("abc"||1 op 2)', { operatorDefinitions }).args[0];
|
|
221
|
+
assertEqual(unparenthesized.name, 'op', 'unabsorbed tail: outer functor');
|
|
222
|
+
assertEqual(unparenthesized.args[1].name, '2', 'unabsorbed tail: outer right operand');
|
|
223
|
+
const splice = unparenthesized.args[0];
|
|
224
|
+
assertEqual(splice.args[1].args[1].args[1].name, '1', 'unabsorbed tail: splice tail value');
|
|
225
|
+
|
|
226
|
+
// "abc"||(1 op 2) is a different term: op(1, 2) spliced in as the
|
|
227
|
+
// tail.
|
|
228
|
+
const parenthesizedTail = parseGoalText('p("abc"||(1 op 2))', { operatorDefinitions }).args[0];
|
|
229
|
+
assertEqual(parenthesizedTail.name, '.', 'parenthesized tail: still a cons cell');
|
|
230
|
+
assertEqual(parenthesizedTail.args[1].args[1].args[1].name, 'op', 'parenthesized tail: splice tail is op(1, 2)');
|
|
231
|
+
|
|
232
|
+
// Printing must disambiguate the two terms above, and must agree
|
|
233
|
+
// that ("abc"||1) op 2 (explicit outer parentheses) is the same
|
|
234
|
+
// term as the unparenthesized case, exercising the real top-level
|
|
235
|
+
// answer-printing path (not just the parser).
|
|
236
|
+
const source = ':- op(1, xfy, op).\n';
|
|
237
|
+
const stdout = run(source, {
|
|
238
|
+
goals: [
|
|
239
|
+
'("abc"||1 op 2) == (("abc"||1) op 2)',
|
|
240
|
+
'("abc"||1 op 2) \\== ("abc"||(1 op 2))',
|
|
241
|
+
'L1 = "abc"||1 op 2',
|
|
242
|
+
'L3 = "abc"||(1 op 2)',
|
|
243
|
+
],
|
|
244
|
+
}).stdout;
|
|
245
|
+
assertEqual(
|
|
246
|
+
stdout,
|
|
247
|
+
[
|
|
248
|
+
'"abc"||1 op 2 == "abc"||1 op 2.',
|
|
249
|
+
'"abc"||1 op 2 \\== "abc"||(1 op 2).',
|
|
250
|
+
'"abc"||1 op 2 = "abc"||1 op 2.',
|
|
251
|
+
'"abc"||(1 op 2) = "abc"||(1 op 2).',
|
|
252
|
+
'',
|
|
253
|
+
].join('\n'),
|
|
254
|
+
'unabsorbed and parenthesized-tail terms are distinct and print disambiguated from each other',
|
|
255
|
+
);
|
|
256
|
+
},
|
|
257
|
+
},
|
|
204
258
|
{
|
|
205
259
|
name: 'normal integer syntax accepts WG17 digit separators (issue #89)',
|
|
206
260
|
run: () => {
|