eyeprolog 1.2.0 → 1.2.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 CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.2.0",
6
+ "version": "1.2.1",
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
@@ -663,8 +663,7 @@ class Parser {
663
663
  }
664
664
  return this.source[start] === ' ' || this.source[start] === '\t';
665
665
  }
666
- parseQuad(id, line, accept) {
667
- const query = this.parseTerm(0, true);
666
+ parseQuadAnswers(id, query, line, accept) {
668
667
  this.expect(TOK.DOT, '.');
669
668
  this.advance();
670
669
 
@@ -684,28 +683,38 @@ class Parser {
684
683
  source: { filename: this.filename, line },
685
684
  });
686
685
  }
686
+ parseQuad(id, line, accept) {
687
+ const query = this.parseTerm(0, true);
688
+ this.parseQuadAnswers(id, query, line, accept);
689
+ }
690
+ parseQuadTerm(term, line, accept) {
691
+ if (term.type !== COMPOUND || term.name !== '?-' || ![1, 2].includes(term.arity)) return false;
692
+ const id = term.arity === 2 ? term.args[0] : null;
693
+ const query = term.args[term.arity - 1];
694
+ this.parseQuadAnswers(id, query, line, accept);
695
+ return true;
696
+ }
687
697
  parseProgram(emit = null) {
688
698
  const clauses = emit ? null : [];
689
699
  let clauseNumber = 0;
690
700
  const accept = emit ?? ((clause) => clauses.push(clause));
691
701
  while (this.token.type !== TOK.EOF) {
692
702
  const line = this.token.line;
693
- if (this.operatorTokenName() === '?-') {
694
- if (this.strictIso) {
695
- // In Part 1, ?- is the predefined 1200 fx operator. A strict-core
696
- // source therefore reads it as an ordinary term rather than giving
697
- // it EyeProlog's top-level quad meaning.
698
- const head = this.parseTerm(0, true);
699
- this.expect(TOK.DOT, '.');
703
+ // In the normal EyeProlog profile, canonical functional ?-/1 and
704
+ // ?-/2 notation denotes the same quad marker as the corresponding
705
+ // operator notation. Keep the existing operator-form query parsing so
706
+ // a query containing comma remains wholly to the right of `?-`, while
707
+ // functional notation is parsed as a term and then decomposed by arity.
708
+ if (this.operatorTokenName() === '?-' && !this.strictIso) {
709
+ if (this.peek() === '(') {
710
+ const quadTerm = this.parseTerm(0, true);
711
+ if (!this.parseQuadTerm(quadTerm, line, accept)) {
712
+ throw new Error(`parse line ${line}: bad quad term`);
713
+ }
714
+ } else {
700
715
  this.advance();
701
- const clause = { head, body: [] };
702
- clauseNumber++;
703
- if (this.sourceMetadata) clause.source = { filename: this.filename, line, clause: clauseNumber };
704
- accept(clause);
705
- continue;
716
+ this.parseQuad(null, line, accept);
706
717
  }
707
- this.advance();
708
- this.parseQuad(null, line, accept);
709
718
  continue;
710
719
  }
711
720
  if (this.token.type === TOK.IF) {
@@ -740,6 +749,13 @@ class Parser {
740
749
  continue;
741
750
  }
742
751
  let head = this.parseTerm(3);
752
+ // Both a quad label and a TS 13211-3 semicontext may contain an
753
+ // unparenthesized comma before their priority-1200 operator. Assemble
754
+ // that left operand before deciding whether the marker is ?- or -->.
755
+ if (this.token.type === TOK.COMMA) {
756
+ this.advance();
757
+ head = compound(',', [head, this.parseTerm(3)]);
758
+ }
743
759
  if (this.operatorTokenName() === '?-') {
744
760
  if (this.strictIso) {
745
761
  // There is no predefined infix ?-/2 in strict core mode. If a
@@ -761,13 +777,6 @@ class Parser {
761
777
  this.parseQuad(head, line, accept);
762
778
  continue;
763
779
  }
764
- // ISO/IEC TS 13211-3 grammar rules use -->/2 at the same top-level
765
- // priority as clauses. The left side may include an unparenthesized
766
- // semicontext: NonTerminal, Terminals --> Body.
767
- if (this.token.type === TOK.COMMA) {
768
- this.advance();
769
- head = compound(',', [head, this.parseTerm(3)]);
770
- }
771
780
  if (this.operatorTokenName() === '-->') {
772
781
  this.advance();
773
782
  const grammarBody = this.parseTerm(0, true);
@@ -221,6 +221,43 @@ why(
221
221
  assertEqual(program.quads[1].query.args[1].name, '.', 'dot atom');
222
222
  },
223
223
  },
224
+ {
225
+ name: 'quad parser treats operator and functional ?- notation equivalently',
226
+ run: () => {
227
+ const labelled = Program.parse(
228
+ `0,passes
229
+ ?- X = 1.
230
+ X = 1.
231
+ `,
232
+ );
233
+ assertEqual(labelled.quads.length, 1, 'labelled quad count');
234
+ assertEqual(labelled.clauses.length, 0, 'labelled quad clause count');
235
+ assertEqual(labelled.quads[0].id.name, ',', 'comma label functor');
236
+ assertEqual(labelled.quads[0].query.name, '=', 'labelled quad query');
237
+ const labelledReport = publicApi.runQuads(labelled);
238
+ assertEqual(labelledReport.stdout, 'quads: 1 run, 1 passed, 0 failed.\n', 'labelled quad report');
239
+
240
+ const functional = Program.parse(
241
+ `?-(','(0,passes),=(X,1)).
242
+ X = 1.
243
+ `,
244
+ );
245
+ assertEqual(functional.quads.length, 1, 'functional quad count');
246
+ assertEqual(functional.clauses.length, 0, 'functional quad clause count');
247
+ assertEqual(functional.quads[0].id.name, ',', 'functional comma label functor');
248
+ assertEqual(functional.quads[0].query.name, '=', 'functional quad query');
249
+ assertEqual(termToString(functional.quads[0].id), termToString(labelled.quads[0].id),
250
+ 'operator and functional labels are equivalent');
251
+ assertEqual(termToString(functional.quads[0].query), termToString(labelled.quads[0].query),
252
+ 'operator and functional queries are equivalent');
253
+ const functionalReport = publicApi.runQuads(functional);
254
+ assertEqual(functionalReport.stdout, 'quads: 1 run, 1 passed, 0 failed.\n', 'functional quad report');
255
+
256
+ const strict = Program.parse(`?-(','(0,passes),=(X,1)).\n`, { isoStrict: true });
257
+ assertEqual(strict.quads.length, 0, 'strict mode has no quads');
258
+ assertEqual(strict.clauses.length, 1, 'strict functional ?-/2 remains an ordinary term');
259
+ },
260
+ },
224
261
  {
225
262
  name: 'runQuads checks portable answer descriptions',
226
263
  run: () => {
@@ -5146,7 +5146,11 @@ contains `?-` at priority 1200 with specifier `fx`, so
5146
5146
  an optional label before the query marker (`Label ?- Query.`), so while quad
5147
5147
  syntax is supported it additionally exposes `?-` at priority 1200 with
5148
5148
  specifier `xfx` as an implementation-specific operator. Consequently
5149
- `current_op(Priority, Specifier, ?-)` enumerates both definitions.
5149
+ `current_op(Priority, Specifier, ?-)` enumerates both definitions. At top level in the normal EyeProlog profile, the quad marker is recognized
5150
+ after term parsing, so equivalent syntax stays equivalent: `Label ?- Query.`
5151
+ and canonical `?-(Label, Query).` denote the same labelled quad when followed
5152
+ by its indented answer descriptions. In `--iso-strict` mode this quad
5153
+ interpretation is disabled, and `?-/2` remains ordinary Prolog term syntax.
5150
5154
 
5151
5155
  Run [`iso-dynamic-database.pl`](https://github.com/eyereasoner/eyeprolog/blob/main/examples/iso-dynamic-database.pl)
5152
5156
  for an explicitly stateful queue and
@@ -6361,7 +6365,10 @@ console.log(report.passed, report.failed, report.stdout);
6361
6365
  The syntax follows the “queries using answer descriptions” convention used by
6362
6366
  Trealla and the ISO Prolog working examples. Because answer descriptions are
6363
6367
  layout-sensitive, indent every description while keeping ordinary clause heads
6364
- and the next quad query at the left margin.
6368
+ and the next quad query at the left margin. A comma may be part of a quad label,
6369
+ including across layout before `?-`. Canonical functional notation is
6370
+ semantically equivalent: `?-(Label, Query).` followed by the same indented
6371
+ answer descriptions creates the same labelled quad as `Label ?- Query.`.
6365
6372
 
6366
6373
  Statistics are comparative evidence, not a score in isolation. Preserve the
6367
6374
  program, input, runtime version, selected query, answers, and counters together.