eyeprolog 1.2.36 → 1.2.38
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/parser.js
CHANGED
|
@@ -606,7 +606,9 @@ class Parser {
|
|
|
606
606
|
this.advance();
|
|
607
607
|
return atom('{}');
|
|
608
608
|
}
|
|
609
|
-
|
|
609
|
+
// As with a parenthesized term, a current operator atom may be the entire
|
|
610
|
+
// curly-bracket content: `{*}` denotes {}(*), not an incomplete infix use.
|
|
611
|
+
const term = this.parseTerm(0, true, true, true);
|
|
610
612
|
this.expect(TOK.RBRACE, '}');
|
|
611
613
|
this.advance();
|
|
612
614
|
return compound('{}', [term]);
|
|
@@ -732,7 +734,7 @@ class Parser {
|
|
|
732
734
|
// an argument delimiter there is no operand for prefix syntax, so keep
|
|
733
735
|
// the operator as an atom instead of reporting a misleading bad-term
|
|
734
736
|
// error.
|
|
735
|
-
if ([TOK.COMMA, TOK.RPAREN, TOK.RBRACKET, TOK.BAR, TOK.DOT].includes(this.token.type)) {
|
|
737
|
+
if ([TOK.COMMA, TOK.RPAREN, TOK.RBRACKET, TOK.RBRACE, TOK.BAR, TOK.DOT].includes(this.token.type)) {
|
|
736
738
|
if (!allowOperatorAtom && this.token.type !== TOK.DOT) {
|
|
737
739
|
throw new Error(`parse line ${this.token.line}: operator atom ${op} requires argument context or parentheses`);
|
|
738
740
|
}
|
package/src/repl.js
CHANGED
|
@@ -617,6 +617,7 @@ function formatAnswer(engine, state, variables, env) {
|
|
|
617
617
|
// current operator atoms in argument and list-element positions without
|
|
618
618
|
// quotes, just as writeq/1 already prints them.
|
|
619
619
|
operatorAtomsAsArgs: true,
|
|
620
|
+
dottedGraphicAtoms: true,
|
|
620
621
|
doubleQuotes: state.solver.prologFlags.get('double_quotes')?.value?.name ?? 'chars',
|
|
621
622
|
})}`);
|
|
622
623
|
}
|
package/src/write.js
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
} from './term.js';
|
|
6
6
|
|
|
7
7
|
const graphicAtomCharacters = new Set('!#$&*+-/<=>?@^~\\'.split(''));
|
|
8
|
+
const dottedGraphicAtomCharacters = new Set([...graphicAtomCharacters, '.']);
|
|
8
9
|
const compactInfixOperators = new Set([':', '..']);
|
|
9
10
|
|
|
10
11
|
function quotedControlEscape(ch) {
|
|
@@ -48,6 +49,11 @@ function writeAtom(name) {
|
|
|
48
49
|
return atomNeedsQuotes(name) ? quoteAtom(name) : name;
|
|
49
50
|
}
|
|
50
51
|
|
|
52
|
+
function isDottedGraphicAtom(name) {
|
|
53
|
+
return name.includes('.') && [...name].some((ch) => ch !== '.') && !name.startsWith('/*') &&
|
|
54
|
+
[...name].every((ch) => dottedGraphicAtomCharacters.has(ch));
|
|
55
|
+
}
|
|
56
|
+
|
|
51
57
|
function legacyVariableToIso(name) {
|
|
52
58
|
if (name === '?') return '_';
|
|
53
59
|
const tail = name.slice(1);
|
|
@@ -172,6 +178,10 @@ function format(term, env, options, table, maxPriority = 1200, context = 'term')
|
|
|
172
178
|
if (resolved.type === STRING) return writeString(resolved.name);
|
|
173
179
|
if (resolved.type === ATOM) {
|
|
174
180
|
if (!options.quoted) return resolved.name;
|
|
181
|
+
// Top-level bindings are already delimited by their answer punctuation.
|
|
182
|
+
// Keep valid dotted graphic tokens readable there without weakening the
|
|
183
|
+
// ISO writeq/1 policy tested by WG17 #308.
|
|
184
|
+
if (options.dottedGraphicAtoms && isDottedGraphicAtom(resolved.name)) return resolved.name;
|
|
175
185
|
// ISO 6.3.3.1 gives functional arguments and list elements a special
|
|
176
186
|
// `arg` production: an atom that is a current operator is valid there
|
|
177
187
|
// without quoting. Keep lexical exceptions such as `|` quoted.
|
|
@@ -217,7 +227,9 @@ function format(term, env, options, table, maxPriority = 1200, context = 'term')
|
|
|
217
227
|
}
|
|
218
228
|
|
|
219
229
|
if (!options.ignoreOps && resolved.name === '{}' && resolved.arity === 1) {
|
|
220
|
-
|
|
230
|
+
// A current operator atom is valid as the complete curly-bracket content,
|
|
231
|
+
// just as it is in a functional argument or list element.
|
|
232
|
+
return `{${format(resolved.args[0], env, options, table, 1200, 'argument')}}`;
|
|
221
233
|
}
|
|
222
234
|
|
|
223
235
|
if (!options.ignoreOps) {
|
|
@@ -269,6 +281,7 @@ export function formatTermForWrite(term, env = new Env(), options = {}) {
|
|
|
269
281
|
variableNames: printableReadVariableNames(term, env, explicitVariableNames),
|
|
270
282
|
compact: options.compact === true,
|
|
271
283
|
operatorAtomsAsArgs: options.operatorAtomsAsArgs === true,
|
|
284
|
+
dottedGraphicAtoms: options.dottedGraphicAtoms === true,
|
|
272
285
|
};
|
|
273
286
|
const maxPriority = Number.isInteger(options.maxPriority)
|
|
274
287
|
? Math.max(0, Math.min(1200, options.maxPriority))
|
|
@@ -9,6 +9,7 @@ answer(Binary, Octal, Hex, Character, Escaped, Curly, EmptyCurly, IntegerPart, F
|
|
|
9
9
|
=(Character, 0'\n),
|
|
10
10
|
=(Escaped, 'A\x42\\101\'),
|
|
11
11
|
=(Curly, {pair(a, b)}),
|
|
12
|
+
{*} = {}(*),
|
|
12
13
|
=(EmptyCurly, {}),
|
|
13
14
|
IntegerPart is float_integer_part(-3.75),
|
|
14
15
|
FractionalPart is float_fractional_part(-3.75).
|
package/test/run-regression.mjs
CHANGED
|
@@ -38,7 +38,7 @@ import {
|
|
|
38
38
|
variantTerms,
|
|
39
39
|
parseProgramText,
|
|
40
40
|
} from '../src/index.js';
|
|
41
|
-
import { parseGoalText, parseNumberTokenText } from '../src/parser.js';
|
|
41
|
+
import { ISO_OPERATOR_DEFINITIONS, parseGoalText, parseNumberTokenText } from '../src/parser.js';
|
|
42
42
|
import { compareTerms } from '../src/term.js';
|
|
43
43
|
import { formatTermForWrite } from '../src/write.js';
|
|
44
44
|
import { selectClauseCandidates } from '../src/program.js';
|
|
@@ -764,7 +764,8 @@ c4 ?- call((!;1)).
|
|
|
764
764
|
input: 'read(T).\n./*. .\nread(T).\nok.\nread(T).\n!.!.\nhalt.\n',
|
|
765
765
|
});
|
|
766
766
|
assertEqual(repl.status, 0, 'REPL exit status');
|
|
767
|
-
assertIncludes(repl.stdout,
|
|
767
|
+
assertIncludes(repl.stdout, 'T = ./*..', 'REPL dotted graphic atom answer');
|
|
768
|
+
assertNotIncludes(repl.stdout, "T = './*.'", 'REPL dotted graphic atom has no spurious quotes');
|
|
768
769
|
assertIncludes(repl.stdout, 'T = ok.', 'REPL following read answer');
|
|
769
770
|
assertIncludes(repl.stdout, 'error(syntax_error(read_term), eyeprolog)', 'REPL syntax error');
|
|
770
771
|
assertEqual(repl.stderr, '', 'REPL stderr');
|
|
@@ -781,6 +782,38 @@ c4 ?- call((!;1)).
|
|
|
781
782
|
assertNotIncludes(result.stdout, "'?-'", 'writeq(?-) has no quotes');
|
|
782
783
|
},
|
|
783
784
|
},
|
|
785
|
+
{
|
|
786
|
+
name: 'curly brackets accept ISO and custom operator atoms (issue #41)',
|
|
787
|
+
run: () => {
|
|
788
|
+
const operatorNames = [...new Set(ISO_OPERATOR_DEFINITIONS.map(([, , name]) => name))]
|
|
789
|
+
.filter((name) => name !== ',');
|
|
790
|
+
for (const name of operatorNames) {
|
|
791
|
+
const holder = parseGoalText(`holder({${name}})`);
|
|
792
|
+
const curly = holder.args[0];
|
|
793
|
+
assertEqual(curly.name, '{}', `curly functor for ${name}`);
|
|
794
|
+
assertEqual(curly.args[0].name, name, `curly operator atom ${name}`);
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
const customProgram = parseProgramText([
|
|
798
|
+
':- op(100, fx, pre).',
|
|
799
|
+
':- op(100, xf, post).',
|
|
800
|
+
':- op(100, xfx, infix).',
|
|
801
|
+
'custom({pre}, {post}, {infix}).',
|
|
802
|
+
'',
|
|
803
|
+
].join('\n'));
|
|
804
|
+
const custom = customProgram.find((clause) => clause.head.name === 'custom').head;
|
|
805
|
+
assertEqual(
|
|
806
|
+
custom.args.map((curly) => curly.args[0].name).join(','),
|
|
807
|
+
'pre,post,infix',
|
|
808
|
+
'custom prefix, postfix, and infix operator atoms',
|
|
809
|
+
);
|
|
810
|
+
|
|
811
|
+
const repl = runCli([], { input: 'read(T).\n{*}.\nhalt.\n' });
|
|
812
|
+
assertEqual(repl.status, 0, 'curly operator REPL status');
|
|
813
|
+
assertIncludes(repl.stdout, 'T = {*}.', 'curly operator REPL answer');
|
|
814
|
+
assertNotIncludes(repl.stdout, '{(*)}', 'curly operator has no unnecessary parentheses');
|
|
815
|
+
},
|
|
816
|
+
},
|
|
784
817
|
{
|
|
785
818
|
name: 'normal parser rejects non-conforming bare operator operands',
|
|
786
819
|
run: () => {
|
package/the-art-of-eyeprolog.md
CHANGED
|
@@ -5197,12 +5197,14 @@ parsing of subsequent text, place them before their first use. ISO argument
|
|
|
5197
5197
|
syntax also permits an atom that is currently an operator to appear directly
|
|
5198
5198
|
as a functional argument or list element, so forms such as
|
|
5199
5199
|
`current_op(Priority, Specifier, :-)` and `[:-,-]` are valid without quoting
|
|
5200
|
-
or parenthesizing those operator atoms.
|
|
5201
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
|
|
5200
|
+
or parenthesizing those operator atoms. A current operator atom may likewise
|
|
5201
|
+
be the complete content of parentheses or curly brackets: `(+)` denotes the
|
|
5202
|
+
atom `+`, and `{*}` denotes the curly term `{}(*)`. Term output observes the
|
|
5203
|
+
same context rules: with `quoted(true)`, an operator atom is not quoted merely
|
|
5204
|
+
because it occurs as a functional argument, list element, or sole curly-bracket
|
|
5205
|
+
content. Thus `writeq({*})` emits `{*}`, `writeq([:-,-])` emits `[:-,-]`, and
|
|
5206
|
+
`writeq(f(;,'|',';;'))` emits `f(;,'|',';;')`; the bar stays quoted because
|
|
5207
|
+
ISO treats the unquoted `|` token as a list separator rather than an atom. The ISO initial operator table also
|
|
5206
5208
|
contains `?-` at priority 1200 with specifier `fx`, so
|
|
5207
5209
|
`current_op(1200, fx, ?-)` succeeds. EyeProlog's embedded quad syntax permits
|
|
5208
5210
|
an optional label before the query marker (`Label ?- Query.`), so while quad
|