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
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.2.36",
6
+ "version": "1.2.38",
7
7
  "description": "EyeProlog turns facts and rules into answers and proofs.",
8
8
  "type": "module",
9
9
  "main": "./index.js",
package/src/parser.js CHANGED
@@ -606,7 +606,9 @@ class Parser {
606
606
  this.advance();
607
607
  return atom('{}');
608
608
  }
609
- const term = this.parseTerm(0, true);
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
- return `{${format(resolved.args[0], env, options, table, 1200)}}`;
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).
@@ -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, "T = './*.'.", 'REPL dotted graphic atom answer');
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: () => {
@@ -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. Term output observes the same `arg`
5201
- rule: with `quoted(true)`, an operator atom is not quoted merely because it is
5202
- an operator when it occurs as a functional argument or list element. Thus
5203
- `writeq([:-,-])` emits `[:-,-]`, and `writeq(f(;,'|',';;'))` emits
5204
- `f(;,'|',';;')`; the bar stays quoted because ISO treats the unquoted `|`
5205
- token as a list separator rather than an atom. The ISO initial operator table also
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