eyelang 1.3.4 → 1.3.5
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/docs/guide.md +1 -1
- package/docs/language-reference.md +1 -1
- package/index.d.ts +1 -0
- package/package.json +1 -1
- package/src/program.js +22 -6
- package/test/run-regression.mjs +17 -0
package/docs/guide.md
CHANGED
|
@@ -577,7 +577,7 @@ semidet(edge, 2).
|
|
|
577
577
|
|
|
578
578
|
For large programs, keep helper predicates selective, bind arguments early, document intended calling patterns with `mode/3` when helpful, and declare focused output predicates with `materialize/2` when default output would otherwise solve broad helper goals.
|
|
579
579
|
|
|
580
|
-
When using `not/1` over user-defined predicates, keep the dependency graph stratified: negative dependencies should not participate in recursion. The JavaScript API exposes `program.stratifiedNegation`, `program.negationStratificationErrors`, and `program.assertStratifiedNegation()` so host tools can warn or reject programs that rely on unstratified negation.
|
|
580
|
+
When using `not/1` over user-defined predicates, keep the dependency graph stratified: negative dependencies should not participate in recursion. The JavaScript API exposes `program.stratifiedNegation`, `program.negationStratificationErrors`, and `program.assertStratifiedNegation()` so host tools can warn or reject programs that rely on unstratified negation. The diagnostic is lazy by default; use `{ analyzeNegation: true }` to compute it during parsing or `{ strictNegation: true }` to compute and reject unstratified programs.
|
|
581
581
|
|
|
582
582
|
## Implementation limits
|
|
583
583
|
|
|
@@ -371,7 +371,7 @@ p(?x) :- q(?x).
|
|
|
371
371
|
q(?x) :- not(p(?x)).
|
|
372
372
|
```
|
|
373
373
|
|
|
374
|
-
The JavaScript implementation records stratification metadata on `Program` instances: `stratifiedNegation`, `negationStratificationErrors`, `negationDependencies`, and per-group `negationStratum`. Embedders that want to reject non-portable negation can parse with `{ strictNegation: true }` or call `program.assertStratifiedNegation()`.
|
|
374
|
+
The JavaScript implementation records stratification metadata on `Program` instances: `stratifiedNegation`, `negationStratificationErrors`, `negationDependencies`, and per-group `negationStratum`. This diagnostic is computed lazily when one of those properties or helper methods is first read, or eagerly when parsing with `{ analyzeNegation: true }` or `{ strictNegation: true }`. Embedders that want to reject non-portable negation can parse with `{ strictNegation: true }` or call `program.assertStratifiedNegation()`.
|
|
375
375
|
|
|
376
376
|
## 9. Standard built-in predicates
|
|
377
377
|
|
package/index.d.ts
CHANGED
package/package.json
CHANGED
package/src/program.js
CHANGED
|
@@ -14,6 +14,7 @@ export class Program {
|
|
|
14
14
|
clause.index = index;
|
|
15
15
|
this.indexClause(clause);
|
|
16
16
|
}
|
|
17
|
+
this._negationAnalysis = null;
|
|
17
18
|
this.applyDeclarations(options);
|
|
18
19
|
}
|
|
19
20
|
static parse(source, options = {}) {
|
|
@@ -101,7 +102,7 @@ export class Program {
|
|
|
101
102
|
}
|
|
102
103
|
}
|
|
103
104
|
if (options.markRecursive !== false) this.markRecursivePredicates();
|
|
104
|
-
this.analyzeNegationStratification();
|
|
105
|
+
if (options.analyzeNegation === true || options.strictNegation === true) this.analyzeNegationStratification();
|
|
105
106
|
if (options.strictNegation === true) this.assertStratifiedNegation();
|
|
106
107
|
}
|
|
107
108
|
markRecursivePredicates() {
|
|
@@ -192,19 +193,34 @@ export class Program {
|
|
|
192
193
|
const strata = computeNegationStrata(groups, edges, indexByKey);
|
|
193
194
|
for (const group of groups) group.negationStratum = strata.get(groupKeys.get(group)) ?? null;
|
|
194
195
|
|
|
195
|
-
this.
|
|
196
|
-
|
|
197
|
-
|
|
196
|
+
this._negationAnalysis = {
|
|
197
|
+
dependencies: edges,
|
|
198
|
+
errors: violations,
|
|
199
|
+
stratified: violations.length === 0,
|
|
200
|
+
};
|
|
198
201
|
return violations;
|
|
199
202
|
}
|
|
203
|
+
ensureNegationStratification() {
|
|
204
|
+
if (!this._negationAnalysis) this.analyzeNegationStratification();
|
|
205
|
+
return this._negationAnalysis;
|
|
206
|
+
}
|
|
207
|
+
get negationDependencies() {
|
|
208
|
+
return this.ensureNegationStratification().dependencies;
|
|
209
|
+
}
|
|
210
|
+
get negationStratificationErrors() {
|
|
211
|
+
return this.ensureNegationStratification().errors;
|
|
212
|
+
}
|
|
213
|
+
get stratifiedNegation() {
|
|
214
|
+
return this.ensureNegationStratification().stratified;
|
|
215
|
+
}
|
|
200
216
|
assertStratifiedNegation() {
|
|
201
|
-
const violations = this.
|
|
217
|
+
const violations = this.ensureNegationStratification().errors;
|
|
202
218
|
if (violations.length === 0) return true;
|
|
203
219
|
const details = violations.map((edge) => `${edge.from} depends negatively on ${edge.to}`).join('; ');
|
|
204
220
|
throw new Error(`unstratified negation: ${details}`);
|
|
205
221
|
}
|
|
206
222
|
isStratifiedNegation() {
|
|
207
|
-
return this.
|
|
223
|
+
return this.ensureNegationStratification().stratified;
|
|
208
224
|
}
|
|
209
225
|
|
|
210
226
|
hasMaterializeDeclarations() {
|
package/test/run-regression.mjs
CHANGED
|
@@ -396,6 +396,23 @@ function apiCases() {
|
|
|
396
396
|
assertEqual(group.arity, 2, 'group arity');
|
|
397
397
|
},
|
|
398
398
|
},
|
|
399
|
+
{
|
|
400
|
+
name: 'program keeps negation diagnostics lazy by default',
|
|
401
|
+
run: () => {
|
|
402
|
+
const program = Program.parse('p(a).\nq(?x) :- not(p(?x)).\n');
|
|
403
|
+
assertEqual(program._negationAnalysis, null, 'analysis starts lazy');
|
|
404
|
+
assertEqual(program.negationDependencies.length, 1, 'dependency count');
|
|
405
|
+
assertEqual(program._negationAnalysis !== null, true, 'analysis computed on demand');
|
|
406
|
+
},
|
|
407
|
+
},
|
|
408
|
+
{
|
|
409
|
+
name: 'analyzeNegation option computes diagnostics eagerly',
|
|
410
|
+
run: () => {
|
|
411
|
+
const program = Program.parse('p(a).\nq(?x) :- not(p(?x)).\n', { analyzeNegation: true });
|
|
412
|
+
assertEqual(program._negationAnalysis !== null, true, 'analysis computed eagerly');
|
|
413
|
+
assertEqual(program.stratifiedNegation, true, 'stratified negation');
|
|
414
|
+
},
|
|
415
|
+
},
|
|
399
416
|
{
|
|
400
417
|
name: 'program reports stratified negation metadata',
|
|
401
418
|
run: () => {
|