eyeling 1.34.5 → 1.34.6
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 +3 -90
- package/bin/eyeling.cjs +4 -56
- package/dist/browser/eyeling.browser.js +0 -1
- package/examples/context-schema-audit.n3 +1 -1
- package/eyeling.js +0 -1
- package/index.d.ts +3 -37
- package/index.js +1 -90
- package/lib/cli.js +0 -1
- package/package.json +3 -12
- package/test/packlist.test.js +0 -2
- package/test/run.js +0 -2
- package/docs/eyelang-guide.md +0 -535
- package/docs/eyelang-language-reference.md +0 -697
- package/examples/eyelang/access-control-policy.pl +0 -52
- package/examples/eyelang/ackermann.pl +0 -46
- package/examples/eyelang/age.pl +0 -28
- package/examples/eyelang/aliases-and-namespaces.pl +0 -22
- package/examples/eyelang/alignment-demo.pl +0 -44
- package/examples/eyelang/allen-interval-calculus.pl +0 -64
- package/examples/eyelang/ancestor.pl +0 -21
- package/examples/eyelang/animal.pl +0 -21
- package/examples/eyelang/annotation.pl +0 -34
- package/examples/eyelang/auroracare.pl +0 -309
- package/examples/eyelang/backward.pl +0 -12
- package/examples/eyelang/basic-monadic.pl +0 -10032
- package/examples/eyelang/bayes-diagnosis.pl +0 -108
- package/examples/eyelang/bayes-therapy.pl +0 -182
- package/examples/eyelang/beam-deflection.pl +0 -50
- package/examples/eyelang/blocks-world-planning.pl +0 -75
- package/examples/eyelang/bmi.pl +0 -232
- package/examples/eyelang/braking-safety-worlds.pl +0 -69
- package/examples/eyelang/buck-converter-design.pl +0 -78
- package/examples/eyelang/cache-performance.pl +0 -54
- package/examples/eyelang/canary-release.pl +0 -49
- package/examples/eyelang/cat-koko.pl +0 -24
- package/examples/eyelang/clinical-trial-screening.pl +0 -92
- package/examples/eyelang/combinatorics-findall-sort.pl +0 -37
- package/examples/eyelang/competitive-enzyme-kinetics.pl +0 -78
- package/examples/eyelang/complex.pl +0 -121
- package/examples/eyelang/composition-of-injective-functions-is-injective.pl +0 -50
- package/examples/eyelang/context-association.pl +0 -53
- package/examples/eyelang/context-schema-audit.pl +0 -46
- package/examples/eyelang/control-system.pl +0 -72
- package/examples/eyelang/cyclic-path.pl +0 -16
- package/examples/eyelang/d3-group.pl +0 -100
- package/examples/eyelang/dairy-energy-balance.pl +0 -65
- package/examples/eyelang/data-negotiation.pl +0 -39
- package/examples/eyelang/deep-taxonomy-10.pl +0 -115
- package/examples/eyelang/deep-taxonomy-100.pl +0 -385
- package/examples/eyelang/deep-taxonomy-1000.pl +0 -3085
- package/examples/eyelang/deep-taxonomy-10000.pl +0 -30094
- package/examples/eyelang/deep-taxonomy-100000.pl +0 -300184
- package/examples/eyelang/delfour.pl +0 -281
- package/examples/eyelang/deontic-logic.pl +0 -52
- package/examples/eyelang/derived-backward-rule.pl +0 -30
- package/examples/eyelang/derived-rule.pl +0 -27
- package/examples/eyelang/diamond-property.pl +0 -38
- package/examples/eyelang/dijkstra-findall-sort.pl +0 -44
- package/examples/eyelang/dijkstra-risk-path.pl +0 -86
- package/examples/eyelang/dijkstra.pl +0 -46
- package/examples/eyelang/dining-philosophers.pl +0 -140
- package/examples/eyelang/dog.pl +0 -25
- package/examples/eyelang/dpv-odrl-purpose-mapping.pl +0 -46
- package/examples/eyelang/drone-corridor-planner.pl +0 -51
- package/examples/eyelang/easter-computus.pl +0 -89
- package/examples/eyelang/electrical-rc-filter.pl +0 -36
- package/examples/eyelang/epidemic-policy.pl +0 -67
- package/examples/eyelang/equivalence-classes-overlap-implies-same-class.pl +0 -27
- package/examples/eyelang/eulerian-path.pl +0 -85
- package/examples/eyelang/ev-range-worlds.pl +0 -82
- package/examples/eyelang/existential-rule.pl +0 -18
- package/examples/eyelang/exoplanet-validation-worlds.pl +0 -88
- package/examples/eyelang/expression-eval.pl +0 -43
- package/examples/eyelang/family-cousins.pl +0 -65
- package/examples/eyelang/fastpow.pl +0 -53
- package/examples/eyelang/fft8-numeric.pl +0 -83
- package/examples/eyelang/fibonacci.pl +0 -53
- package/examples/eyelang/field-nitrogen-balance.pl +0 -70
- package/examples/eyelang/flandor.pl +0 -296
- package/examples/eyelang/floating-point.pl +0 -23
- package/examples/eyelang/four-color-map.pl +0 -127
- package/examples/eyelang/fundamental-theorem-arithmetic.pl +0 -113
- package/examples/eyelang/gd-step-certified.pl +0 -158
- package/examples/eyelang/gdpr-compliance.pl +0 -69
- package/examples/eyelang/good-cobbler.pl +0 -14
- package/examples/eyelang/gps.pl +0 -152
- package/examples/eyelang/graph-reachability.pl +0 -36
- package/examples/eyelang/gray-code-counter.pl +0 -48
- package/examples/eyelang/greatest-lower-bound-uniqueness.pl +0 -28
- package/examples/eyelang/group-inverse-uniqueness.pl +0 -34
- package/examples/eyelang/hamiltonian-path.pl +0 -49
- package/examples/eyelang/hamming-code.pl +0 -105
- package/examples/eyelang/hanoi.pl +0 -20
- package/examples/eyelang/heat-loss.pl +0 -51
- package/examples/eyelang/heron-theorem.pl +0 -36
- package/examples/eyelang/ideal-gas-law.pl +0 -37
- package/examples/eyelang/illegitimate-reasoning.pl +0 -88
- package/examples/eyelang/knowledge-engineering-alignment-flow.pl +0 -40
- package/examples/eyelang/law-of-cosines.pl +0 -31
- package/examples/eyelang/least-squares-regression.pl +0 -81
- package/examples/eyelang/list-collection.pl +0 -33
- package/examples/eyelang/lldm.pl +0 -78
- package/examples/eyelang/manufacturing-quality-control.pl +0 -73
- package/examples/eyelang/microgrid-dispatch.pl +0 -85
- package/examples/eyelang/monkey-bananas.pl +0 -45
- package/examples/eyelang/network-sla.pl +0 -48
- package/examples/eyelang/newton-raphson.pl +0 -49
- package/examples/eyelang/nixon-diamond.pl +0 -37
- package/examples/eyelang/observability-log-correlation.pl +0 -34
- package/examples/eyelang/odrl-dpv-fpv-trust-flow.pl +0 -43
- package/examples/eyelang/odrl-dpv-healthcare-risk-ranked.pl +0 -266
- package/examples/eyelang/odrl-dpv-risk-ranked.pl +0 -320
- package/examples/eyelang/orbital-transfer-design.pl +0 -113
- package/examples/eyelang/output/access-control-policy.pl +0 -2
- package/examples/eyelang/output/ackermann.pl +0 -12
- package/examples/eyelang/output/age.pl +0 -2
- package/examples/eyelang/output/aliases-and-namespaces.pl +0 -5
- package/examples/eyelang/output/alignment-demo.pl +0 -32
- package/examples/eyelang/output/allen-interval-calculus.pl +0 -154
- package/examples/eyelang/output/ancestor.pl +0 -6
- package/examples/eyelang/output/animal.pl +0 -4
- package/examples/eyelang/output/annotation.pl +0 -4
- package/examples/eyelang/output/auroracare.pl +0 -117
- package/examples/eyelang/output/backward.pl +0 -1
- package/examples/eyelang/output/basic-monadic.pl +0 -1518
- package/examples/eyelang/output/bayes-diagnosis.pl +0 -13
- package/examples/eyelang/output/bayes-therapy.pl +0 -23
- package/examples/eyelang/output/beam-deflection.pl +0 -5
- package/examples/eyelang/output/blocks-world-planning.pl +0 -4
- package/examples/eyelang/output/bmi.pl +0 -32
- package/examples/eyelang/output/braking-safety-worlds.pl +0 -18
- package/examples/eyelang/output/buck-converter-design.pl +0 -6
- package/examples/eyelang/output/cache-performance.pl +0 -4
- package/examples/eyelang/output/canary-release.pl +0 -5
- package/examples/eyelang/output/cat-koko.pl +0 -3
- package/examples/eyelang/output/clinical-trial-screening.pl +0 -9
- package/examples/eyelang/output/combinatorics-findall-sort.pl +0 -2
- package/examples/eyelang/output/competitive-enzyme-kinetics.pl +0 -6
- package/examples/eyelang/output/complex.pl +0 -1
- package/examples/eyelang/output/composition-of-injective-functions-is-injective.pl +0 -2
- package/examples/eyelang/output/context-association.pl +0 -3
- package/examples/eyelang/output/context-schema-audit.pl +0 -12
- package/examples/eyelang/output/control-system.pl +0 -6
- package/examples/eyelang/output/cyclic-path.pl +0 -16
- package/examples/eyelang/output/d3-group.pl +0 -2
- package/examples/eyelang/output/dairy-energy-balance.pl +0 -13
- package/examples/eyelang/output/data-negotiation.pl +0 -1
- package/examples/eyelang/output/deep-taxonomy-10.pl +0 -16
- package/examples/eyelang/output/deep-taxonomy-100.pl +0 -16
- package/examples/eyelang/output/deep-taxonomy-1000.pl +0 -16
- package/examples/eyelang/output/deep-taxonomy-10000.pl +0 -16
- package/examples/eyelang/output/deep-taxonomy-100000.pl +0 -16
- package/examples/eyelang/output/delfour.pl +0 -31
- package/examples/eyelang/output/deontic-logic.pl +0 -4
- package/examples/eyelang/output/derived-backward-rule.pl +0 -3
- package/examples/eyelang/output/derived-rule.pl +0 -2
- package/examples/eyelang/output/diamond-property.pl +0 -4
- package/examples/eyelang/output/dijkstra-findall-sort.pl +0 -2
- package/examples/eyelang/output/dijkstra-risk-path.pl +0 -29
- package/examples/eyelang/output/dijkstra.pl +0 -16
- package/examples/eyelang/output/dining-philosophers.pl +0 -350
- package/examples/eyelang/output/dog.pl +0 -1
- package/examples/eyelang/output/dpv-odrl-purpose-mapping.pl +0 -18
- package/examples/eyelang/output/drone-corridor-planner.pl +0 -17
- package/examples/eyelang/output/easter-computus.pl +0 -30
- package/examples/eyelang/output/electrical-rc-filter.pl +0 -3
- package/examples/eyelang/output/epidemic-policy.pl +0 -14
- package/examples/eyelang/output/equivalence-classes-overlap-implies-same-class.pl +0 -18
- package/examples/eyelang/output/eulerian-path.pl +0 -3
- package/examples/eyelang/output/ev-range-worlds.pl +0 -19
- package/examples/eyelang/output/existential-rule.pl +0 -2
- package/examples/eyelang/output/exoplanet-validation-worlds.pl +0 -22
- package/examples/eyelang/output/expression-eval.pl +0 -1
- package/examples/eyelang/output/family-cousins.pl +0 -28
- package/examples/eyelang/output/fastpow.pl +0 -6
- package/examples/eyelang/output/fft8-numeric.pl +0 -4
- package/examples/eyelang/output/fibonacci.pl +0 -6
- package/examples/eyelang/output/field-nitrogen-balance.pl +0 -21
- package/examples/eyelang/output/flandor.pl +0 -43
- package/examples/eyelang/output/floating-point.pl +0 -9
- package/examples/eyelang/output/four-color-map.pl +0 -3
- package/examples/eyelang/output/fundamental-theorem-arithmetic.pl +0 -9
- package/examples/eyelang/output/gd-step-certified.pl +0 -79
- package/examples/eyelang/output/gdpr-compliance.pl +0 -6
- package/examples/eyelang/output/good-cobbler.pl +0 -1
- package/examples/eyelang/output/gps.pl +0 -21
- package/examples/eyelang/output/graph-reachability.pl +0 -3
- package/examples/eyelang/output/gray-code-counter.pl +0 -1
- package/examples/eyelang/output/greatest-lower-bound-uniqueness.pl +0 -2
- package/examples/eyelang/output/group-inverse-uniqueness.pl +0 -2
- package/examples/eyelang/output/hamiltonian-path.pl +0 -121
- package/examples/eyelang/output/hamming-code.pl +0 -6
- package/examples/eyelang/output/hanoi.pl +0 -1
- package/examples/eyelang/output/heat-loss.pl +0 -5
- package/examples/eyelang/output/heron-theorem.pl +0 -4
- package/examples/eyelang/output/ideal-gas-law.pl +0 -3
- package/examples/eyelang/output/illegitimate-reasoning.pl +0 -15
- package/examples/eyelang/output/knowledge-engineering-alignment-flow.pl +0 -17
- package/examples/eyelang/output/law-of-cosines.pl +0 -3
- package/examples/eyelang/output/least-squares-regression.pl +0 -5
- package/examples/eyelang/output/list-collection.pl +0 -3
- package/examples/eyelang/output/lldm.pl +0 -6
- package/examples/eyelang/output/manufacturing-quality-control.pl +0 -6
- package/examples/eyelang/output/microgrid-dispatch.pl +0 -6
- package/examples/eyelang/output/monkey-bananas.pl +0 -5
- package/examples/eyelang/output/network-sla.pl +0 -4
- package/examples/eyelang/output/newton-raphson.pl +0 -3
- package/examples/eyelang/output/nixon-diamond.pl +0 -5
- package/examples/eyelang/output/observability-log-correlation.pl +0 -28
- package/examples/eyelang/output/odrl-dpv-fpv-trust-flow.pl +0 -9
- package/examples/eyelang/output/odrl-dpv-healthcare-risk-ranked.pl +0 -42
- package/examples/eyelang/output/odrl-dpv-risk-ranked.pl +0 -120
- package/examples/eyelang/output/orbital-transfer-design.pl +0 -7
- package/examples/eyelang/output/path-discovery.pl +0 -3
- package/examples/eyelang/output/peano-arithmetic.pl +0 -3
- package/examples/eyelang/output/peasant.pl +0 -10
- package/examples/eyelang/output/pendulum-period.pl +0 -4
- package/examples/eyelang/output/polynomial.pl +0 -14
- package/examples/eyelang/output/proof-contrapositive.pl +0 -3
- package/examples/eyelang/output/quadratic-formula.pl +0 -6
- package/examples/eyelang/output/radioactive-decay.pl +0 -5
- package/examples/eyelang/output/reusable-builtins.pl +0 -5
- package/examples/eyelang/output/riemann-hypothesis.pl +0 -12
- package/examples/eyelang/output/security-incident-correlation.pl +0 -3
- package/examples/eyelang/output/service-impact.pl +0 -11
- package/examples/eyelang/output/sieve.pl +0 -1
- package/examples/eyelang/output/skolem-functions.pl +0 -16
- package/examples/eyelang/output/socket-age.pl +0 -1
- package/examples/eyelang/output/socket-family.pl +0 -3
- package/examples/eyelang/output/socrates.pl +0 -2
- package/examples/eyelang/output/statistics-summary.pl +0 -4
- package/examples/eyelang/output/superdense-coding.pl +0 -6
- package/examples/eyelang/output/term-tools.pl +0 -6
- package/examples/eyelang/output/trust-flow-provenance-threshold.pl +0 -6
- package/examples/eyelang/output/turing.pl +0 -12
- package/examples/eyelang/output/vector-similarity.pl +0 -4
- package/examples/eyelang/output/vulnerability-impact.pl +0 -20
- package/examples/eyelang/output/witch.pl +0 -7
- package/examples/eyelang/output/wolf-goat-cabbage.pl +0 -3
- package/examples/eyelang/output/zebra.pl +0 -3
- package/examples/eyelang/path-discovery.pl +0 -45013
- package/examples/eyelang/peano-arithmetic.pl +0 -31
- package/examples/eyelang/peasant.pl +0 -30
- package/examples/eyelang/pendulum-period.pl +0 -50
- package/examples/eyelang/polynomial.pl +0 -124
- package/examples/eyelang/proof/age.pl +0 -71
- package/examples/eyelang/proof/aliases-and-namespaces.pl +0 -78
- package/examples/eyelang/proof/ancestor.pl +0 -140
- package/examples/eyelang/proof/animal.pl +0 -68
- package/examples/eyelang/proof/annotation.pl +0 -80
- package/examples/eyelang/proof/backward.pl +0 -22
- package/examples/eyelang/proof/cat-koko.pl +0 -86
- package/examples/eyelang/proof/data-negotiation.pl +0 -76
- package/examples/eyelang/proof/derived-rule.pl +0 -43
- package/examples/eyelang/proof/dog.pl +0 -31
- package/examples/eyelang/proof/electrical-rc-filter.pl +0 -105
- package/examples/eyelang/proof/existential-rule.pl +0 -40
- package/examples/eyelang/proof/floating-point.pl +0 -160
- package/examples/eyelang/proof/good-cobbler.pl +0 -16
- package/examples/eyelang/proof/group-inverse-uniqueness.pl +0 -84
- package/examples/eyelang/proof/list-collection.pl +0 -52
- package/examples/eyelang/proof/proof-contrapositive.pl +0 -78
- package/examples/eyelang/proof/socket-age.pl +0 -32
- package/examples/eyelang/proof/socket-family.pl +0 -59
- package/examples/eyelang/proof/socrates.pl +0 -38
- package/examples/eyelang/proof-contrapositive.pl +0 -27
- package/examples/eyelang/quadratic-formula.pl +0 -54
- package/examples/eyelang/radioactive-decay.pl +0 -56
- package/examples/eyelang/reusable-builtins.pl +0 -32
- package/examples/eyelang/riemann-hypothesis.pl +0 -110
- package/examples/eyelang/security-incident-correlation.pl +0 -69
- package/examples/eyelang/service-impact.pl +0 -41
- package/examples/eyelang/sieve.pl +0 -20
- package/examples/eyelang/skolem-functions.pl +0 -52
- package/examples/eyelang/socket-age.pl +0 -39
- package/examples/eyelang/socket-family.pl +0 -28
- package/examples/eyelang/socrates.pl +0 -19
- package/examples/eyelang/statistics-summary.pl +0 -54
- package/examples/eyelang/superdense-coding.pl +0 -84
- package/examples/eyelang/term-tools.pl +0 -23
- package/examples/eyelang/trust-flow-provenance-threshold.pl +0 -40
- package/examples/eyelang/turing.pl +0 -67
- package/examples/eyelang/vector-similarity.pl +0 -56
- package/examples/eyelang/vulnerability-impact.pl +0 -70
- package/examples/eyelang/witch.pl +0 -38
- package/examples/eyelang/wolf-goat-cabbage.pl +0 -56
- package/examples/eyelang/zebra.pl +0 -44
- package/eyelang.d.ts +0 -80
- package/lib/eyelang/bin.js +0 -7
- package/lib/eyelang/builtins/aggregation.js +0 -81
- package/lib/eyelang/builtins/arithmetic.js +0 -208
- package/lib/eyelang/builtins/context.js +0 -42
- package/lib/eyelang/builtins/control.js +0 -34
- package/lib/eyelang/builtins/core.js +0 -78
- package/lib/eyelang/builtins/lists.js +0 -283
- package/lib/eyelang/builtins/registry.js +0 -48
- package/lib/eyelang/builtins/strings.js +0 -234
- package/lib/eyelang/builtins/terms.js +0 -66
- package/lib/eyelang/cli.js +0 -180
- package/lib/eyelang/explain.js +0 -324
- package/lib/eyelang/hash.js +0 -294
- package/lib/eyelang/index.js +0 -47
- package/lib/eyelang/package.json +0 -3
- package/lib/eyelang/parser.js +0 -428
- package/lib/eyelang/program.js +0 -237
- package/lib/eyelang/solver.js +0 -237
- package/lib/eyelang/term.js +0 -328
package/lib/eyelang/parser.js
DELETED
|
@@ -1,428 +0,0 @@
|
|
|
1
|
-
// Tokenizer and recursive-descent parser for the eyelang source language.
|
|
2
|
-
// It preserves the compact Prolog-like syntax while producing Term objects for the solver.
|
|
3
|
-
import { atom, compound, cons, emptyList, numberTerm, stringTerm, variable } from './term.js';
|
|
4
|
-
|
|
5
|
-
const TOK = {
|
|
6
|
-
EOF: 'eof', ATOM: 'atom', VAR: 'var', STRING: 'string', NUMBER: 'number',
|
|
7
|
-
LPAREN: '(', RPAREN: ')', LBRACKET: '[', RBRACKET: ']', COMMA: ',', BAR: '|', DOT: '.', IF: ':-'
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
function isWhitespaceCode(code) {
|
|
11
|
-
return code === 32 || code === 9 || code === 10 || code === 13 || code === 12 || code === 11;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function isDigitCode(code) {
|
|
15
|
-
return code >= 48 && code <= 57;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function isVariableStart(text) {
|
|
19
|
-
const code = text.charCodeAt(0);
|
|
20
|
-
return code === 95 || (code >= 65 && code <= 90);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function isAtomCharCode(code) {
|
|
24
|
-
// Stop at whitespace and the punctuation tokens that have syntactic meaning.
|
|
25
|
-
return code > 0 &&
|
|
26
|
-
!isWhitespaceCode(code) &&
|
|
27
|
-
code !== 40 && code !== 41 && code !== 91 && code !== 93 &&
|
|
28
|
-
code !== 44 && code !== 124 && code !== 46 &&
|
|
29
|
-
code !== 39 && code !== 34 && code !== 58;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
class Parser {
|
|
33
|
-
constructor(source, options = {}) {
|
|
34
|
-
this.source = String(source ?? '');
|
|
35
|
-
this.filename = options.filename ?? '<input>';
|
|
36
|
-
this.pos = 0;
|
|
37
|
-
this.line = 1;
|
|
38
|
-
this.anonymous = 0;
|
|
39
|
-
this.sourceMetadata = options.sourceMetadata !== false;
|
|
40
|
-
this.token = this.nextToken();
|
|
41
|
-
}
|
|
42
|
-
peek(offset = 0) {
|
|
43
|
-
return this.source[this.pos + offset] ?? '';
|
|
44
|
-
}
|
|
45
|
-
take() {
|
|
46
|
-
const ch = this.peek();
|
|
47
|
-
if (ch) {
|
|
48
|
-
this.pos++;
|
|
49
|
-
if (ch === '\n') this.line++;
|
|
50
|
-
}
|
|
51
|
-
return ch;
|
|
52
|
-
}
|
|
53
|
-
skipWhitespaceAndComments() {
|
|
54
|
-
const source = this.source;
|
|
55
|
-
const len = source.length;
|
|
56
|
-
while (true) {
|
|
57
|
-
while (this.pos < len) {
|
|
58
|
-
const code = source.charCodeAt(this.pos);
|
|
59
|
-
if (!isWhitespaceCode(code)) break;
|
|
60
|
-
if (code === 10) this.line++;
|
|
61
|
-
this.pos++;
|
|
62
|
-
}
|
|
63
|
-
if (source.charCodeAt(this.pos) === 37) { // % line comment
|
|
64
|
-
while (this.pos < len && source.charCodeAt(this.pos) !== 10) this.pos++;
|
|
65
|
-
continue;
|
|
66
|
-
}
|
|
67
|
-
break;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
nextToken() {
|
|
71
|
-
// The tokenizer keeps just enough state for useful parse-line errors and
|
|
72
|
-
// treats quoted atoms and quoted strings differently, as Prolog syntax does.
|
|
73
|
-
this.skipWhitespaceAndComments();
|
|
74
|
-
const line = this.line;
|
|
75
|
-
const ch = this.peek();
|
|
76
|
-
if (!ch) return { type: TOK.EOF, text: '', line };
|
|
77
|
-
|
|
78
|
-
const punct = { '(': TOK.LPAREN, ')': TOK.RPAREN, '[': TOK.LBRACKET, ']': TOK.RBRACKET, ',': TOK.COMMA, '|': TOK.BAR, '.': TOK.DOT };
|
|
79
|
-
if (punct[ch]) {
|
|
80
|
-
this.take();
|
|
81
|
-
return { type: punct[ch], text: ch, line };
|
|
82
|
-
}
|
|
83
|
-
if (ch === ':' && this.peek(1) === '-') {
|
|
84
|
-
this.pos += 2;
|
|
85
|
-
return { type: TOK.IF, text: ':-', line };
|
|
86
|
-
}
|
|
87
|
-
if (ch === ':') throw new Error('colon names are not supported; use name or prefix_name');
|
|
88
|
-
|
|
89
|
-
if (ch === '"' || ch === "'") {
|
|
90
|
-
const quote = this.take();
|
|
91
|
-
let text = '';
|
|
92
|
-
while (true) {
|
|
93
|
-
if (!this.peek()) throw new Error(`parse line ${line}: unterminated quoted term`);
|
|
94
|
-
let value = this.take();
|
|
95
|
-
if (value === quote) {
|
|
96
|
-
if (this.peek() === quote) {
|
|
97
|
-
this.take();
|
|
98
|
-
value = quote;
|
|
99
|
-
} else {
|
|
100
|
-
break;
|
|
101
|
-
}
|
|
102
|
-
} else if (value === '\\' && this.peek()) {
|
|
103
|
-
const escaped = this.take();
|
|
104
|
-
if (escaped === 'n') value = '\n';
|
|
105
|
-
else if (escaped === 't') value = '\t';
|
|
106
|
-
else value = escaped;
|
|
107
|
-
}
|
|
108
|
-
text += value;
|
|
109
|
-
}
|
|
110
|
-
return { type: quote === '"' ? TOK.STRING : TOK.ATOM, text, line };
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
if (isDigitCode(ch.charCodeAt(0)) || (ch === '-' && isDigitCode(this.peek(1).charCodeAt(0)))) {
|
|
114
|
-
const start = this.pos;
|
|
115
|
-
if (this.peek() === '-') this.take();
|
|
116
|
-
while (isDigitCode(this.peek().charCodeAt(0))) this.take();
|
|
117
|
-
if (this.peek() === '.' && isDigitCode(this.peek(1).charCodeAt(0))) {
|
|
118
|
-
this.take();
|
|
119
|
-
while (isDigitCode(this.peek().charCodeAt(0))) this.take();
|
|
120
|
-
}
|
|
121
|
-
if ((this.peek() === 'e' || this.peek() === 'E')) {
|
|
122
|
-
let idx = this.pos + 1;
|
|
123
|
-
if (this.source[idx] === '+' || this.source[idx] === '-') idx++;
|
|
124
|
-
if (isDigitCode((this.source[idx] ?? '').charCodeAt(0))) {
|
|
125
|
-
this.take();
|
|
126
|
-
if (this.peek() === '+' || this.peek() === '-') this.take();
|
|
127
|
-
while (isDigitCode(this.peek().charCodeAt(0))) this.take();
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
return { type: TOK.NUMBER, text: this.source.slice(start, this.pos), line };
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const start = this.pos;
|
|
134
|
-
while (this.pos < this.source.length && isAtomCharCode(this.source.charCodeAt(this.pos))) this.pos++;
|
|
135
|
-
if (this.pos === start) throw new Error(`parse line ${line}: bad character ${JSON.stringify(ch)}`);
|
|
136
|
-
let text = this.source.slice(start, this.pos);
|
|
137
|
-
let type = isVariableStart(text) ? TOK.VAR : TOK.ATOM;
|
|
138
|
-
if (type === TOK.VAR && text === '_') text = `__anon${this.anonymous++}`;
|
|
139
|
-
return { type, text, line };
|
|
140
|
-
}
|
|
141
|
-
advance() {
|
|
142
|
-
this.token = this.nextToken();
|
|
143
|
-
}
|
|
144
|
-
expect(type, desc = type) {
|
|
145
|
-
if (this.token.type !== type) throw new Error(`parse line ${this.token.line}: expected ${desc}, got ${this.token.text}`);
|
|
146
|
-
}
|
|
147
|
-
parseParenthesizedTerm() {
|
|
148
|
-
// Parenthesized comma terms are represented as right-associated ','/2
|
|
149
|
-
// compounds, which lets the solver flatten conjunctions uniformly.
|
|
150
|
-
this.expect(TOK.LPAREN, '(');
|
|
151
|
-
this.advance();
|
|
152
|
-
const items = [];
|
|
153
|
-
while (true) {
|
|
154
|
-
items.push(this.parseTerm());
|
|
155
|
-
if (this.token.type === TOK.COMMA) {
|
|
156
|
-
this.advance();
|
|
157
|
-
continue;
|
|
158
|
-
}
|
|
159
|
-
break;
|
|
160
|
-
}
|
|
161
|
-
this.expect(TOK.RPAREN, ')');
|
|
162
|
-
this.advance();
|
|
163
|
-
let term = items[items.length - 1];
|
|
164
|
-
for (let i = items.length - 2; i >= 0; i--) term = compound(',', [items[i], term]);
|
|
165
|
-
return term;
|
|
166
|
-
}
|
|
167
|
-
parseList() {
|
|
168
|
-
// Lists are lowered to './2' cons cells and [] so list predicates can work
|
|
169
|
-
// on a single canonical representation.
|
|
170
|
-
this.expect(TOK.LBRACKET, '[');
|
|
171
|
-
this.advance();
|
|
172
|
-
if (this.token.type === TOK.RBRACKET) {
|
|
173
|
-
this.advance();
|
|
174
|
-
return emptyList();
|
|
175
|
-
}
|
|
176
|
-
const items = [];
|
|
177
|
-
let tail = null;
|
|
178
|
-
while (true) {
|
|
179
|
-
items.push(this.parseTerm());
|
|
180
|
-
if (this.token.type === TOK.COMMA) {
|
|
181
|
-
this.advance();
|
|
182
|
-
continue;
|
|
183
|
-
}
|
|
184
|
-
if (this.token.type === TOK.BAR) {
|
|
185
|
-
this.advance();
|
|
186
|
-
tail = this.parseTerm();
|
|
187
|
-
this.expect(TOK.RBRACKET, ']');
|
|
188
|
-
this.advance();
|
|
189
|
-
break;
|
|
190
|
-
}
|
|
191
|
-
this.expect(TOK.RBRACKET, ']');
|
|
192
|
-
this.advance();
|
|
193
|
-
tail = emptyList();
|
|
194
|
-
break;
|
|
195
|
-
}
|
|
196
|
-
for (let i = items.length - 1; i >= 0; i--) tail = cons(items[i], tail);
|
|
197
|
-
return tail;
|
|
198
|
-
}
|
|
199
|
-
parseTerm() {
|
|
200
|
-
if (this.token.type === TOK.LPAREN) return this.parseParenthesizedTerm();
|
|
201
|
-
if (this.token.type === TOK.LBRACKET) return this.parseList();
|
|
202
|
-
if (this.token.type === TOK.VAR) {
|
|
203
|
-
const name = this.token.text;
|
|
204
|
-
this.advance();
|
|
205
|
-
return variable(name);
|
|
206
|
-
}
|
|
207
|
-
if (this.token.type === TOK.STRING) {
|
|
208
|
-
const value = this.token.text;
|
|
209
|
-
this.advance();
|
|
210
|
-
return stringTerm(value);
|
|
211
|
-
}
|
|
212
|
-
if (this.token.type === TOK.NUMBER) {
|
|
213
|
-
const value = this.token.text;
|
|
214
|
-
this.advance();
|
|
215
|
-
return numberTerm(value);
|
|
216
|
-
}
|
|
217
|
-
if (this.token.type === TOK.ATOM) {
|
|
218
|
-
const name = this.token.text;
|
|
219
|
-
this.advance();
|
|
220
|
-
if (this.token.type === TOK.LPAREN) {
|
|
221
|
-
this.advance();
|
|
222
|
-
const args = [];
|
|
223
|
-
if (this.token.type !== TOK.RPAREN) {
|
|
224
|
-
while (true) {
|
|
225
|
-
args.push(this.parseTerm());
|
|
226
|
-
if (this.token.type === TOK.COMMA) {
|
|
227
|
-
this.advance();
|
|
228
|
-
continue;
|
|
229
|
-
}
|
|
230
|
-
break;
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
this.expect(TOK.RPAREN, ')');
|
|
234
|
-
this.advance();
|
|
235
|
-
return compound(name, args);
|
|
236
|
-
}
|
|
237
|
-
return atom(name);
|
|
238
|
-
}
|
|
239
|
-
throw new Error(`parse line ${this.token.line}: bad term`);
|
|
240
|
-
}
|
|
241
|
-
parseProgram() {
|
|
242
|
-
const clauses = [];
|
|
243
|
-
while (this.token.type !== TOK.EOF) {
|
|
244
|
-
const line = this.token.line;
|
|
245
|
-
const head = this.parseTerm();
|
|
246
|
-
const body = [];
|
|
247
|
-
if (this.token.type === TOK.IF) {
|
|
248
|
-
this.advance();
|
|
249
|
-
while (true) {
|
|
250
|
-
body.push(this.parseTerm());
|
|
251
|
-
if (this.token.type === TOK.COMMA) {
|
|
252
|
-
this.advance();
|
|
253
|
-
continue;
|
|
254
|
-
}
|
|
255
|
-
break;
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
this.expect(TOK.DOT, '.');
|
|
259
|
-
this.advance();
|
|
260
|
-
const clause = { head, body };
|
|
261
|
-
if (this.sourceMetadata) clause.source = { filename: this.filename, line, clause: clauses.length + 1 };
|
|
262
|
-
clauses.push(clause);
|
|
263
|
-
}
|
|
264
|
-
return clauses;
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
export function parseClauses(source, options = {}) {
|
|
270
|
-
if (options.sourceMetadata === false) {
|
|
271
|
-
const clauses = parseClausesFastNoSource(source);
|
|
272
|
-
if (clauses) return clauses;
|
|
273
|
-
}
|
|
274
|
-
return new Parser(source, options).parseProgram();
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
function isSimpleName(text) {
|
|
278
|
-
if (!text) return false;
|
|
279
|
-
const first = text.charCodeAt(0);
|
|
280
|
-
if (!(first >= 97 && first <= 122)) return false;
|
|
281
|
-
for (let i = 1; i < text.length; i++) {
|
|
282
|
-
const code = text.charCodeAt(i);
|
|
283
|
-
if (!(code === 95 || (code >= 48 && code <= 57) || (code >= 65 && code <= 90) || (code >= 97 && code <= 122))) return false;
|
|
284
|
-
}
|
|
285
|
-
return true;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
const SIMPLE_NUMBER = /^-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?$/;
|
|
289
|
-
const SIMPLE_ARG_FORBIDDEN = /[\s()[\]|"']/;
|
|
290
|
-
const FAST_BINARY_FACT = /^([a-z][A-Za-z0-9_]*)\(\s*([^,\s()[\]|"']+)\s*,\s*([^,\s()[\]|"']+)\s*\)\.$/;
|
|
291
|
-
const FAST_BINARY_RULE = /^([a-z][A-Za-z0-9_]*)\(\s*([^,\s()[\]|"']+)\s*,\s*([^,\s()[\]|"']+)\s*\)\s*:-\s*([a-z][A-Za-z0-9_]*)\(\s*([^,\s()[\]|"']+)\s*,\s*([^,\s()[\]|"']+)\s*\)\.$/;
|
|
292
|
-
|
|
293
|
-
function parseClausesFastNoSource(source) {
|
|
294
|
-
source = String(source ?? '');
|
|
295
|
-
const numberCache = new Map();
|
|
296
|
-
const stringCache = new Map();
|
|
297
|
-
const variableCache = new Map();
|
|
298
|
-
const clauses = [];
|
|
299
|
-
let anonymous = 0;
|
|
300
|
-
let chunk = '';
|
|
301
|
-
|
|
302
|
-
const cached = (cache, key, create) => {
|
|
303
|
-
const existing = cache.get(key);
|
|
304
|
-
if (existing) return existing;
|
|
305
|
-
const value = create(key);
|
|
306
|
-
cache.set(key, value);
|
|
307
|
-
return value;
|
|
308
|
-
};
|
|
309
|
-
const scalarOrVariableFast = (text) => {
|
|
310
|
-
if (!text) throw new Error('empty simple term');
|
|
311
|
-
const first = text.charCodeAt(0);
|
|
312
|
-
if (text === '_') return variable(`__anon${anonymous++}`);
|
|
313
|
-
if (first === 95 || (first >= 65 && first <= 90)) {
|
|
314
|
-
const existing = variableCache.get(text);
|
|
315
|
-
if (existing) return existing;
|
|
316
|
-
const value = variable(text);
|
|
317
|
-
variableCache.set(text, value);
|
|
318
|
-
return value;
|
|
319
|
-
}
|
|
320
|
-
if ((first === 45 || isDigitCode(first)) && SIMPLE_NUMBER.test(text)) return cached(numberCache, text, numberTerm);
|
|
321
|
-
if (first === 34 && text.endsWith('"')) return cached(stringCache, text.slice(1, -1), stringTerm);
|
|
322
|
-
return atom(text);
|
|
323
|
-
};
|
|
324
|
-
const scalarOrVariable = (text) => scalarOrVariableFast(text.trim());
|
|
325
|
-
const parseBinaryCompound = (text) => {
|
|
326
|
-
text = text.trim();
|
|
327
|
-
const open = text.indexOf('(');
|
|
328
|
-
if (open <= 0 || text[text.length - 1] !== ')') return null;
|
|
329
|
-
const name = text.slice(0, open).trim();
|
|
330
|
-
if (!isSimpleName(name)) return null;
|
|
331
|
-
const inner = text.slice(open + 1, -1);
|
|
332
|
-
if (inner.includes('(') || inner.includes(')') || inner.includes('[') || inner.includes(']') || inner.includes('|') || inner.includes('"') || inner.includes("'")) return null;
|
|
333
|
-
const comma = inner.indexOf(',');
|
|
334
|
-
if (comma < 0 || inner.indexOf(',', comma + 1) >= 0) return null;
|
|
335
|
-
const left = inner.slice(0, comma).trim();
|
|
336
|
-
const right = inner.slice(comma + 1).trim();
|
|
337
|
-
if (!left || !right || SIMPLE_ARG_FORBIDDEN.test(left) || SIMPLE_ARG_FORBIDDEN.test(right)) return null;
|
|
338
|
-
return compound(name, [scalarOrVariable(left), scalarOrVariable(right)]);
|
|
339
|
-
};
|
|
340
|
-
const parseFastBinaryMatch = (match) => {
|
|
341
|
-
return compound(match[1], [scalarOrVariableFast(match[2]), scalarOrVariableFast(match[3])]);
|
|
342
|
-
};
|
|
343
|
-
const parseFastBinaryRuleMatch = (match) => {
|
|
344
|
-
return {
|
|
345
|
-
head: compound(match[1], [scalarOrVariableFast(match[2]), scalarOrVariableFast(match[3])]),
|
|
346
|
-
body: [compound(match[4], [scalarOrVariableFast(match[5]), scalarOrVariableFast(match[6])])],
|
|
347
|
-
};
|
|
348
|
-
};
|
|
349
|
-
const parseFastLine = (text) => {
|
|
350
|
-
if (!text.endsWith('.')) return null;
|
|
351
|
-
const ruleMatch = FAST_BINARY_RULE.exec(text);
|
|
352
|
-
if (ruleMatch) return parseFastBinaryRuleMatch(ruleMatch);
|
|
353
|
-
const factMatch = FAST_BINARY_FACT.exec(text);
|
|
354
|
-
if (factMatch) return { head: parseFastBinaryMatch(factMatch), body: [] };
|
|
355
|
-
return null;
|
|
356
|
-
};
|
|
357
|
-
const parseSimple = (text) => {
|
|
358
|
-
if (!text.endsWith('.') || text.includes('\n')) return null;
|
|
359
|
-
const fast = parseFastLine(text);
|
|
360
|
-
if (fast) return fast;
|
|
361
|
-
text = text.slice(0, -1);
|
|
362
|
-
const rule = text.indexOf(':-');
|
|
363
|
-
if (rule < 0) {
|
|
364
|
-
const head = parseBinaryCompound(text);
|
|
365
|
-
return head ? { head, body: [] } : null;
|
|
366
|
-
}
|
|
367
|
-
const head = parseBinaryCompound(text.slice(0, rule));
|
|
368
|
-
const bodyGoal = parseBinaryCompound(text.slice(rule + 2));
|
|
369
|
-
return head && bodyGoal ? { head, body: [bodyGoal] } : null;
|
|
370
|
-
};
|
|
371
|
-
|
|
372
|
-
const flush = () => {
|
|
373
|
-
const text = chunk.trim();
|
|
374
|
-
chunk = '';
|
|
375
|
-
if (!text) return true;
|
|
376
|
-
const simple = parseSimple(text);
|
|
377
|
-
if (simple) {
|
|
378
|
-
clauses.push(simple);
|
|
379
|
-
return true;
|
|
380
|
-
}
|
|
381
|
-
try {
|
|
382
|
-
const parsed = new Parser(text, { sourceMetadata: false }).parseProgram();
|
|
383
|
-
clauses.push(...parsed);
|
|
384
|
-
return true;
|
|
385
|
-
} catch (_) {
|
|
386
|
-
return false;
|
|
387
|
-
}
|
|
388
|
-
};
|
|
389
|
-
|
|
390
|
-
let lineStart = 0;
|
|
391
|
-
while (lineStart <= source.length) {
|
|
392
|
-
let lineEnd = source.indexOf('\n', lineStart);
|
|
393
|
-
if (lineEnd < 0) lineEnd = source.length;
|
|
394
|
-
let line = source.slice(lineStart, lineEnd);
|
|
395
|
-
if (line.endsWith('\r')) line = line.slice(0, -1);
|
|
396
|
-
const trimmed = line.trim();
|
|
397
|
-
if (trimmed && !trimmed.startsWith('%')) {
|
|
398
|
-
if (!chunk && trimmed.endsWith('.')) {
|
|
399
|
-
const simple = parseFastLine(trimmed) ?? parseSimple(trimmed);
|
|
400
|
-
if (simple) clauses.push(simple);
|
|
401
|
-
else {
|
|
402
|
-
chunk = line + '\n';
|
|
403
|
-
if (!flush()) return null;
|
|
404
|
-
}
|
|
405
|
-
} else {
|
|
406
|
-
chunk += line + '\n';
|
|
407
|
-
if (trimmed.endsWith('.')) {
|
|
408
|
-
if (!flush()) return null;
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
if (lineEnd === source.length) break;
|
|
413
|
-
lineStart = lineEnd + 1;
|
|
414
|
-
}
|
|
415
|
-
if (chunk.trim() && !flush()) return null;
|
|
416
|
-
return clauses;
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
export function parseProgramText(source) {
|
|
420
|
-
return parseClauses(source);
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
export function parseGoalText(text) {
|
|
424
|
-
const clauses = parseClauses(`zz_goal(${text}).`);
|
|
425
|
-
const head = clauses[0]?.head;
|
|
426
|
-
if (!head || head.args.length < 1) throw new Error('bad goal');
|
|
427
|
-
return head.args[0];
|
|
428
|
-
}
|
package/lib/eyelang/program.js
DELETED
|
@@ -1,237 +0,0 @@
|
|
|
1
|
-
// Program representation and clause indexing.
|
|
2
|
-
// Indexes are deliberately conservative: they speed up common scalar arguments but never replace unification as the final check.
|
|
3
|
-
import { ATOM, COMPOUND, Env, compound, deref, flattenConjunction, isScalar, termToString } from './term.js';
|
|
4
|
-
import { parseClauses } from './parser.js';
|
|
5
|
-
|
|
6
|
-
export class Program {
|
|
7
|
-
constructor(clauses = [], options = {}) {
|
|
8
|
-
this.clauses = clauses;
|
|
9
|
-
this.groups = new Map();
|
|
10
|
-
this.materializedGroups = new Set();
|
|
11
|
-
this.hasMaterialize = false;
|
|
12
|
-
for (let index = 0; index < this.clauses.length; index++) {
|
|
13
|
-
const clause = this.clauses[index];
|
|
14
|
-
clause.index = index;
|
|
15
|
-
this.indexClause(clause);
|
|
16
|
-
}
|
|
17
|
-
this.applyDeclarations(options);
|
|
18
|
-
}
|
|
19
|
-
static parse(source, options = {}) {
|
|
20
|
-
return new Program(parseSourceClauses(source, options), options);
|
|
21
|
-
}
|
|
22
|
-
static parseSources(sources = [], options = {}) {
|
|
23
|
-
const clauses = [];
|
|
24
|
-
for (const source of sources) {
|
|
25
|
-
const parsed = typeof source === 'string'
|
|
26
|
-
? parseSourceClauses(source, options)
|
|
27
|
-
: parseSourceClauses(source?.text ?? source?.source ?? '', { ...options, filename: source?.filename ?? '<input>' });
|
|
28
|
-
for (const clause of parsed) clauses.push(clause);
|
|
29
|
-
}
|
|
30
|
-
return new Program(clauses, options);
|
|
31
|
-
}
|
|
32
|
-
makeGroup(name, arity) {
|
|
33
|
-
// A group corresponds to one predicate indicator, for example edge/3.
|
|
34
|
-
// Single-argument and pair indexes cover the common case where queries bind
|
|
35
|
-
// one or two scalar arguments before calling the predicate.
|
|
36
|
-
const group = {
|
|
37
|
-
name,
|
|
38
|
-
arity,
|
|
39
|
-
clauses: [],
|
|
40
|
-
argIndexes: Array.from({ length: arity }, () => ({ buckets: new Map(), fallback: [] })),
|
|
41
|
-
pairIndexes: [],
|
|
42
|
-
memoized: false,
|
|
43
|
-
recursive: false,
|
|
44
|
-
};
|
|
45
|
-
if (arity > 2) {
|
|
46
|
-
for (let left = 0; left < arity; left++) {
|
|
47
|
-
for (let right = left + 1; right < arity; right++) {
|
|
48
|
-
group.pairIndexes.push({ left, right, buckets: new Map(), fallback: [] });
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
return group;
|
|
53
|
-
}
|
|
54
|
-
indexClause(clause) {
|
|
55
|
-
const head = clause.head;
|
|
56
|
-
if (head.type !== COMPOUND) return;
|
|
57
|
-
const key = `${head.name}/${head.arity}`;
|
|
58
|
-
let group = this.groups.get(key);
|
|
59
|
-
if (!group) {
|
|
60
|
-
group = this.makeGroup(head.name, head.arity);
|
|
61
|
-
this.groups.set(key, group);
|
|
62
|
-
}
|
|
63
|
-
group.clauses.push(clause);
|
|
64
|
-
for (let i = 0; i < head.arity; i++) indexOne(group.argIndexes[i], head.args[i], clause);
|
|
65
|
-
for (const pair of group.pairIndexes) indexPair(pair, head, clause);
|
|
66
|
-
}
|
|
67
|
-
findGroup(name, arity) {
|
|
68
|
-
return this.groups.get(`${name}/${arity}`) ?? null;
|
|
69
|
-
}
|
|
70
|
-
applyDeclarations(options = {}) {
|
|
71
|
-
for (const clause of this.clauses) {
|
|
72
|
-
const h = clause.head;
|
|
73
|
-
if (clause.body.length !== 0 || h.type !== COMPOUND || h.arity !== 2) continue;
|
|
74
|
-
const [name, arity] = h.args;
|
|
75
|
-
if (name.type !== ATOM || arity.type !== 'number') continue;
|
|
76
|
-
const key = `${name.name}/${Number(arity.name)}`;
|
|
77
|
-
if (h.name === 'memoize') {
|
|
78
|
-
const group = this.groups.get(key);
|
|
79
|
-
if (group) group.memoized = true;
|
|
80
|
-
} else if (h.name === 'materialize') {
|
|
81
|
-
this.hasMaterialize = true;
|
|
82
|
-
this.materializedGroups.add(key);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
if (options.markRecursive !== false) this.markRecursivePredicates();
|
|
86
|
-
}
|
|
87
|
-
markRecursivePredicates() {
|
|
88
|
-
// Recursion is a group-level diagnostic hint. It is computed from predicate
|
|
89
|
-
// dependencies rather than from individual clauses when callers explicitly ask
|
|
90
|
-
// for it.
|
|
91
|
-
const groups = [...this.groups.values()];
|
|
92
|
-
const indexByGroup = new Map(groups.map((group, i) => [group, i]));
|
|
93
|
-
const deps = groups.map(() => new Set());
|
|
94
|
-
for (const group of groups) {
|
|
95
|
-
const groupIndex = indexByGroup.get(group);
|
|
96
|
-
for (const clause of group.clauses) {
|
|
97
|
-
const bodyGoals = clause.body.flatMap((goal) => goal.type === COMPOUND && goal.name === ',' && goal.arity === 2 ? flattenConjunction(goal) : [goal]);
|
|
98
|
-
for (const goal of bodyGoals) {
|
|
99
|
-
if (goal.type !== COMPOUND) continue;
|
|
100
|
-
const dep = this.findGroup(goal.name, goal.arity);
|
|
101
|
-
if (dep) deps[groupIndex].add(indexByGroup.get(dep));
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
for (const group of groups) {
|
|
106
|
-
const start = indexByGroup.get(group);
|
|
107
|
-
const seen = new Set();
|
|
108
|
-
const stack = [start];
|
|
109
|
-
let recursive = false;
|
|
110
|
-
while (stack.length && !recursive) {
|
|
111
|
-
const current = stack.pop();
|
|
112
|
-
if (seen.has(current)) continue;
|
|
113
|
-
seen.add(current);
|
|
114
|
-
for (const next of deps[current]) {
|
|
115
|
-
if (next === start) { recursive = true; break; }
|
|
116
|
-
if (!seen.has(next)) stack.push(next);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
group.recursive = recursive;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
hasMaterializeDeclarations() {
|
|
123
|
-
return this.hasMaterialize;
|
|
124
|
-
}
|
|
125
|
-
groupIsMaterialized(group) {
|
|
126
|
-
return this.materializedGroups.has(`${group.name}/${group.arity}`);
|
|
127
|
-
}
|
|
128
|
-
groupHasRule(group) {
|
|
129
|
-
return group.clauses.some((clause) => clause.body.length > 0);
|
|
130
|
-
}
|
|
131
|
-
sourceFactLines(predicateKeys = null) {
|
|
132
|
-
// Only facts for predicates that may be materialized need to be remembered.
|
|
133
|
-
// On data-heavy examples this avoids formatting tens of thousands of
|
|
134
|
-
// unrelated source facts just to suppress duplicate derived output.
|
|
135
|
-
const lines = new Set();
|
|
136
|
-
const env = new Env();
|
|
137
|
-
for (const clause of this.clauses) {
|
|
138
|
-
if (clause.body.length !== 0 || clause.head.type !== COMPOUND) continue;
|
|
139
|
-
if (predicateKeys && !predicateKeys.has(`${clause.head.name}/${clause.head.arity}`)) continue;
|
|
140
|
-
lines.add(`${termToString(clause.head, env, true)}.\n`);
|
|
141
|
-
}
|
|
142
|
-
return lines;
|
|
143
|
-
}
|
|
144
|
-
materializationGoals() {
|
|
145
|
-
const hasMaterialize = this.hasMaterializeDeclarations();
|
|
146
|
-
const goals = [];
|
|
147
|
-
for (const group of this.groups.values()) {
|
|
148
|
-
if (hasMaterialize) {
|
|
149
|
-
if (!this.groupIsMaterialized(group)) continue;
|
|
150
|
-
} else if (group.arity !== 2) {
|
|
151
|
-
continue;
|
|
152
|
-
}
|
|
153
|
-
if (!this.groupHasRule(group)) continue;
|
|
154
|
-
const args = [];
|
|
155
|
-
for (let i = 0; i < group.arity; i++) args.push({ type: 'var', name: `X${i}`, args: [] });
|
|
156
|
-
goals.push(compound(group.name, args));
|
|
157
|
-
}
|
|
158
|
-
return goals;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function indexOne(index, arg, clause) {
|
|
163
|
-
if (isScalar(arg)) {
|
|
164
|
-
const bucket = index.buckets.get(arg.name);
|
|
165
|
-
if (bucket) bucket.push(clause);
|
|
166
|
-
else index.buckets.set(arg.name, [clause]);
|
|
167
|
-
} else {
|
|
168
|
-
index.fallback.push(clause);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
function pairKey(left, right) {
|
|
173
|
-
return `${left.name}\x1f${right.name}`;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
function indexPair(pair, head, clause) {
|
|
177
|
-
const left = head.args[pair.left];
|
|
178
|
-
const right = head.args[pair.right];
|
|
179
|
-
if (isScalar(left) && isScalar(right)) {
|
|
180
|
-
const key = pairKey(left, right);
|
|
181
|
-
const bucket = pair.buckets.get(key);
|
|
182
|
-
if (bucket) bucket.push(clause);
|
|
183
|
-
else pair.buckets.set(key, [clause]);
|
|
184
|
-
} else {
|
|
185
|
-
pair.fallback.push(clause);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
export function selectClauseCandidates(group, goal, env) {
|
|
190
|
-
// Pick the narrowest applicable index. Fallback clauses remain in the result
|
|
191
|
-
// because clauses with variables or compound heads can still unify later.
|
|
192
|
-
let bestPrimary = group.clauses;
|
|
193
|
-
let bestFallback = [];
|
|
194
|
-
let bestLen = group.clauses.length;
|
|
195
|
-
let indexed = false;
|
|
196
|
-
if (goal.type !== COMPOUND) return { primary: bestPrimary, fallback: bestFallback };
|
|
197
|
-
|
|
198
|
-
for (let i = 0; i < goal.arity; i++) {
|
|
199
|
-
const arg = deref(goal.args[i], env);
|
|
200
|
-
if (!isScalar(arg)) continue;
|
|
201
|
-
const index = group.argIndexes[i];
|
|
202
|
-
const bucket = index.buckets.get(arg.name) ?? [];
|
|
203
|
-
const candidateLen = bucket.length + index.fallback.length;
|
|
204
|
-
if (!indexed || candidateLen < bestLen) {
|
|
205
|
-
bestPrimary = bucket;
|
|
206
|
-
bestFallback = index.fallback;
|
|
207
|
-
bestLen = candidateLen;
|
|
208
|
-
indexed = true;
|
|
209
|
-
if (bestLen === 0) break;
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
for (const pair of group.pairIndexes) {
|
|
214
|
-
const left = deref(goal.args[pair.left], env);
|
|
215
|
-
const right = deref(goal.args[pair.right], env);
|
|
216
|
-
if (!isScalar(left) || !isScalar(right)) continue;
|
|
217
|
-
const bucket = pair.buckets.get(pairKey(left, right)) ?? [];
|
|
218
|
-
const candidateLen = bucket.length + pair.fallback.length;
|
|
219
|
-
if (!indexed || candidateLen < bestLen) {
|
|
220
|
-
bestPrimary = bucket;
|
|
221
|
-
bestFallback = pair.fallback;
|
|
222
|
-
bestLen = candidateLen;
|
|
223
|
-
indexed = true;
|
|
224
|
-
if (bestLen === 0) break;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
return { primary: bestPrimary, fallback: bestFallback };
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
export function makeProgram(source, options = {}) {
|
|
232
|
-
return Program.parse(source, options);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
export function parseSourceClauses(source, options = {}) {
|
|
236
|
-
return parseClauses(source, options);
|
|
237
|
-
}
|