eyeprolog 1.2.14 → 1.2.16

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/README.md CHANGED
@@ -79,10 +79,12 @@ member_test ?- member(X, [prolog, logic]).
79
79
  ; X = logic.
80
80
  ```
81
81
 
82
- A label may contain several comma-separated metadata fields. When a query has
83
- multiple indented answer descriptions, each description is checked and counted
84
- independently, so one failed expectation does not prevent the later ones from
85
- running.
82
+ A label is simply the first argument of the ordinary `(?-)/2` term, so it may
83
+ use any normal Prolog term syntax; EyeProlog only requires it to be ground when
84
+ the quad is checked. A non-ground label is a quad failure, not a source syntax
85
+ error, and later quads still run. When a query has multiple indented answer
86
+ descriptions, each description is checked and counted independently, so one
87
+ failed expectation does not prevent the later ones from running.
86
88
 
87
89
  ## Strict ISO/IEC 13211-1 core
88
90
 
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.2.14",
6
+ "version": "1.2.16",
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
@@ -794,13 +794,47 @@ class Parser {
794
794
  accept(clause);
795
795
  continue;
796
796
  }
797
+ // Program clauses historically parse comma separately from the head, so
798
+ // keep that grammar unchanged. A quad id, however, is simply the first
799
+ // argument of the ordinary ?-/2 term. If the initial head parse stops at
800
+ // a comma, tentatively reparse that left operand with the normal term
801
+ // grammar (where comma is the predefined priority-1000 xfy operator).
802
+ // Only when the resulting term is actually followed by ?- do we keep the
803
+ // tentative parse; otherwise restore the parser and retain the existing
804
+ // clause/DCG handling below.
805
+ const headState = {
806
+ pos: this.pos,
807
+ line: this.line,
808
+ anonymous: this.anonymous,
809
+ variables: new Map(this.variables),
810
+ previousToken: this.previousToken,
811
+ token: this.token,
812
+ };
813
+ const restoreHeadState = () => {
814
+ this.pos = headState.pos;
815
+ this.line = headState.line;
816
+ this.anonymous = headState.anonymous;
817
+ this.variables = new Map(headState.variables);
818
+ this.previousToken = headState.previousToken;
819
+ this.token = headState.token;
820
+ };
821
+
797
822
  let head = this.parseTerm(3);
798
- // Both a quad label and a TS 13211-3 semicontext may contain one or more
799
- // unparenthesized commas before their priority-1200 operator. Parse the
800
- // complete comma sequence here instead of stopping after one separator;
801
- // portable quad labels commonly carry several metadata fields, e.g.
802
- // `9, "case", passes ?- Goal.`. Build the standard right-associative
803
- // comma term so this is the same label as `(9, "case", passes)`.
823
+ if (this.token.type === TOK.COMMA && !this.strictIso) {
824
+ restoreHeadState();
825
+ const quadId = this.parseTerm(3, true);
826
+ if (this.operatorTokenName() === '?-') {
827
+ this.advance();
828
+ this.parseQuad(quadId, line, accept);
829
+ continue;
830
+ }
831
+ restoreHeadState();
832
+ head = this.parseTerm(3);
833
+ }
834
+
835
+ // Outside quad syntax, preserve the existing program-level comma rule,
836
+ // including the TS 13211-3 semicontext boundary. This is deliberately
837
+ // not a grammar for quad ids.
804
838
  if (this.token.type === TOK.COMMA) {
805
839
  const items = [head];
806
840
  let extraCommaLine = null;
@@ -809,9 +843,6 @@ class Parser {
809
843
  this.advance();
810
844
  items.push(this.parseTerm(3));
811
845
  }
812
- // Historically the program grammar admitted exactly one comma in a
813
- // DCG semicontext. Keep that boundary: the broader comma sequence is
814
- // specifically the quad-label extension, not a new DCG syntax.
815
846
  if (extraCommaLine != null && this.operatorTokenName() !== '?-') {
816
847
  throw new Error(`parse line ${extraCommaLine}: expected ., got ,`);
817
848
  }
package/src/solver.js CHANGED
@@ -490,10 +490,11 @@ function normalizeHostResourceError(error) {
490
490
  const message = String(error?.message ?? '');
491
491
  // V8 reports exhausted Map/Set capacity as a host RangeError. ISO 7.12.2 h
492
492
  // requires processor resource exhaustion to surface as resource_error/1,
493
- // with the resource atom implementation dependent. EyeProlog uses the
494
- // finite_memory spelling already accepted by its ISO conformance corpus.
493
+ // with the resource atom implementation dependent. A finite host capacity
494
+ // ceiling is reported as `memory`; reserve `finite_memory` for the separate
495
+ // convention where no finite amount of memory can complete the computation.
495
496
  if (/^(?:Map|Set) maximum size exceeded$/.test(message)) {
496
- return new PrologError('resource_error(finite_memory)');
497
+ return new PrologError('resource_error(memory)');
497
498
  }
498
499
  return error;
499
500
  }
@@ -279,8 +279,11 @@ why(
279
279
  },
280
280
  },
281
281
  {
282
- name: 'quad labels accept multiple metadata fields and each answer description is independent',
282
+ name: 'quad ids use ordinary term syntax and each answer description is independent',
283
283
  run: () => {
284
+ // Issue #21: the first argument of ?-/2 is an ordinary Prolog term.
285
+ // The commas here are the normal priority-1000 comma operator, not a
286
+ // special metadata grammar owned by the quad parser.
284
287
  const source = `9, "✳54·43", passes
285
288
  ` +
286
289
  `?- X is 1+1.
@@ -298,14 +301,50 @@ why(
298
301
  const program = Program.parseSources([{ text: source, filename: 'issue-21.pl' }]);
299
302
  assertEqual(program.quads.length, 1, 'query group count');
300
303
  assertEqual(program.quads[0].answers.length, 4, 'answer-description count');
301
- assertEqual(program.quads[0].id.name, ',', 'outer label comma');
302
- assertEqual(program.quads[0].id.args[1].name, ',', 'right-associated label comma');
304
+ assertEqual(program.quads[0].id.name, ',', 'ordinary outer comma operator');
305
+ assertEqual(program.quads[0].id.args[1].name, ',', 'ordinary right-associated comma operator');
303
306
  const result = publicApi.runQuads(program);
304
307
  assertEqual(result.total, 4, 'answer-description total');
305
308
  assertEqual(result.passed, 4, 'answer-description passed');
306
309
  assertEqual(result.failed, 0, 'answer-description failed');
307
310
  assertEqual(result.stdout, 'quads: 4 run, 4 passed, 0 failed.\n', 'issue #21 report');
308
311
 
312
+ // No convention is imposed on the id term. Functional/list/curly and
313
+ // non-comma operator forms all go through the same ordinary term parser.
314
+ const ordinaryIds = Program.parse(
315
+ `meta(9, passes) ?- true.
316
+ true.
317
+ ` +
318
+ `[9, passes] ?- true.
319
+ true.
320
+ ` +
321
+ `{passes} ?- true.
322
+ true.
323
+ ` +
324
+ `(alpha ; beta) ?- true.
325
+ true.
326
+ `,
327
+ );
328
+ assertEqual(ordinaryIds.quads.length, 4, 'ordinary id term count');
329
+ assertEqual(publicApi.runQuads(ordinaryIds).stdout, 'quads: 4 run, 4 passed, 0 failed.\n',
330
+ 'ordinary id term report');
331
+
332
+ // Groundness is a quad semantic check, not source syntax. A bad id is
333
+ // reported as a test failure and processing continues to the next quad.
334
+ const nonGround = publicApi.runQuads(
335
+ `Id ?- true.
336
+ true.
337
+ ` +
338
+ `ok ?- true.
339
+ true.
340
+ `,
341
+ );
342
+ assertEqual(nonGround.total, 2, 'non-ground id does not abort parsing');
343
+ assertEqual(nonGround.passed, 1, 'following quad still passes');
344
+ assertEqual(nonGround.failed, 1, 'non-ground id is a quad failure');
345
+ assertIncludes(nonGround.stdout, 'quads: BAD_ID Id, <input>:1', 'non-ground id diagnostic');
346
+ assertIncludes(nonGround.stdout, 'quads: 2 run, 1 passed, 1 failed.', 'non-ground continuation summary');
347
+
309
348
  const continuing = publicApi.runQuads(
310
349
  `case ?- X is 1+1.
311
350
  X = 3.
@@ -2182,22 +2221,27 @@ open(X) :- candidate(X), \\+ closed(X).
2182
2221
  },
2183
2222
  },
2184
2223
  {
2185
- name: 'host Map capacity errors become ISO resource errors',
2224
+ name: 'host Map/Set capacity errors become resource_error(memory)',
2186
2225
  run: () => {
2187
- const registry = new BuiltinRegistry();
2188
- registry.add('exhaust_map', 0, function* () {
2189
- throw new RangeError('Map maximum size exceeded');
2190
- });
2191
- const solver = new Solver(Program.parse(''), { registry });
2192
- const goal = parseGoalText('exhaust_map');
2193
- let caught = null;
2194
- try {
2195
- [...solver.solve([goal], new Env(), 0)];
2196
- } catch (error) {
2197
- caught = error;
2226
+ for (const [predicate, message] of [
2227
+ ['exhaust_map', 'Map maximum size exceeded'],
2228
+ ['exhaust_set', 'Set maximum size exceeded'],
2229
+ ]) {
2230
+ const registry = new BuiltinRegistry();
2231
+ registry.add(predicate, 0, function* () {
2232
+ throw new RangeError(message);
2233
+ });
2234
+ const solver = new Solver(Program.parse(''), { registry });
2235
+ const goal = parseGoalText(predicate);
2236
+ let caught = null;
2237
+ try {
2238
+ [...solver.solve([goal], new Env(), 0)];
2239
+ } catch (error) {
2240
+ caught = error;
2241
+ }
2242
+ assertEqual(caught?.name, 'PrologError', `${predicate} normalized error type`);
2243
+ assertEqual(caught?.formal, 'resource_error(memory)', `${predicate} normalized resource error`);
2198
2244
  }
2199
- assertEqual(caught?.name, 'PrologError', 'normalized error type');
2200
- assertEqual(caught?.formal, 'resource_error(finite_memory)', 'normalized resource error');
2201
2245
  },
2202
2246
  },
2203
2247
  {
@@ -1823,8 +1823,12 @@ sorted-list operation. No process-global variable registry or creation ordinal
1823
1823
  is retained or exposed through later comparisons.
1824
1824
 
1825
1825
  Host capacity failures that V8 reports as `Map maximum size exceeded` or `Set`
1826
- `maximum size exceeded` are normalized at the solver boundary to the ISO error
1827
- `resource_error(finite_memory)` instead of leaking a JavaScript `RangeError`.
1826
+ `maximum size exceeded` are normalized at the solver boundary to
1827
+ `resource_error(memory)` instead of leaking a JavaScript `RangeError`. ISO
1828
+ 13211-1 leaves the resource atom implementation dependent. EyeProlog uses
1829
+ `memory` for a finite host allocation/capacity ceiling and reserves the
1830
+ `finite_memory` spelling for the distinct convention where no finite amount of
1831
+ memory could complete the computation.
1828
1832
 
1829
1833
  ### Implementation boundary
1830
1834
 
@@ -5170,8 +5174,11 @@ from the parsed `?-/1` or `?-/2` term rather than from one privileged surface
5170
5174
  spelling. Thus `Label ?- Query.`, `?-(Label, Query).`, mixed forms such as
5171
5175
  `?-((Label), Query).`, quoted-functor notation, and a parenthesized whole
5172
5176
  `(?-(Label, Query)).` denote the same quad when followed by indented answer
5173
- descriptions. In `--iso-strict` mode this quad interpretation is disabled, and
5174
- `?-/2` remains ordinary Prolog term syntax.
5177
+ descriptions. `Label` itself is parsed with the ordinary Prolog term grammar:
5178
+ there is no quad-specific comma or metadata syntax. The runner requires the
5179
+ resulting first argument to be ground; if it is not, that quad is reported as
5180
+ `BAD_ID` and later quads are still processed. In `--iso-strict` mode this quad
5181
+ interpretation is disabled, and `?-/2` remains ordinary Prolog term syntax.
5175
5182
 
5176
5183
  Run [`iso-dynamic-database.pl`](https://github.com/eyereasoner/eyeprolog/blob/main/examples/iso-dynamic-database.pl)
5177
5184
  for an explicitly stateful queue and
@@ -6401,7 +6408,11 @@ colors ?- color(X).
6401
6408
  ```
6402
6409
 
6403
6410
  Run all quads in a file with `eyeprolog --quads file.pl` or `eyeprolog -q
6404
- file.pl`. A label such as `colors` is optional. Loading the file normally only
6411
+ file.pl`. A label such as `colors` is optional. A label is not a separate
6412
+ mini-language: it is the ordinary first argument of `(?-)/2`, and therefore may
6413
+ be any Prolog term admitted there by the normal term grammar. Quad execution
6414
+ requires that argument to be ground; a non-ground label is reported as a quad
6415
+ failure rather than aborting source parsing. Loading the file normally only
6405
6416
  records its quads; it does not execute them or add their queries and answers as
6406
6417
  program clauses. A quad run prints a summary and exits with status `1` when any
6407
6418
  description fails. Quad mode imports `library(prologue)` as a compatibility