eyeleng 1.1.0 → 1.1.2
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 +25 -17
- package/dist/browser/eyeleng.browser.js +80 -21
- package/examples/fibonacci.srl +89 -30009
- package/examples/output/bayes-diagnosis.trig +0 -4
- package/examples/output/bmi.trig +0 -2
- package/examples/output/dijkstra.trig +0 -8
- package/examples/output/fibonacci.trig +2 -19996
- package/examples/output/hanoi.trig +0 -10
- package/examples/output/spec-2-5-assignment-with-negation.trig +0 -1
- package/examples/output/sudoku.trig +0 -1
- package/examples/output/turing.trig +0 -5
- package/eyeleng.js +83 -23
- package/package.json +1 -1
- package/reports/w3c-shacl12-rules-earl.ttl +89 -89
- package/src/backward.js +52 -7
- package/src/cli.js +3 -2
- package/src/engine.js +2 -1
- package/src/parser.js +15 -5
- package/src/query.js +6 -3
- package/src/rdfSyntax.js +5 -5
- package/test/api.test.js +89 -0
- package/test/cli.test.js +2 -0
- package/test/perf-baseline.json +7 -7
|
@@ -34,16 +34,6 @@ _:http://example/eyeling/hanoi/root-second-second :start 6 .
|
|
|
34
34
|
_:http://example/eyeling/hanoi/root-second-second :startPeg :left .
|
|
35
35
|
_:http://example/eyeling/hanoi/root-second-second :targetPeg :right .
|
|
36
36
|
_:http://example/eyeling/hanoi/root-second-second a :HanoiProblem .
|
|
37
|
-
:level1 :pow2 2 .
|
|
38
|
-
:level10 :pow2 1024 .
|
|
39
|
-
:level2 :pow2 4 .
|
|
40
|
-
:level3 :pow2 8 .
|
|
41
|
-
:level4 :pow2 16 .
|
|
42
|
-
:level5 :pow2 32 .
|
|
43
|
-
:level6 :pow2 64 .
|
|
44
|
-
:level7 :pow2 128 .
|
|
45
|
-
:level8 :pow2 256 .
|
|
46
|
-
:level9 :pow2 512 .
|
|
47
37
|
:move-1 :disk 1 .
|
|
48
38
|
:move-1 :from :left .
|
|
49
39
|
:move-1 :step 1 .
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
:route1 :distanceKm 16.0934 .
|
|
@@ -13,7 +13,6 @@
|
|
|
13
13
|
:case1 :hasStep <http://example/eyeling/turing/case1/step-7> .
|
|
14
14
|
:case1 :hasStep <http://example/eyeling/turing/case1/step-8> .
|
|
15
15
|
:case1 :hasStep <http://example/eyeling/turing/case1/step-9> .
|
|
16
|
-
:case1 :output "101010" .
|
|
17
16
|
:case1 :rawOutput "101010#" .
|
|
18
17
|
:case1 :reportLine "101001 + 1 = 101010" .
|
|
19
18
|
:case1 log:outputString "101001 + 1 = 101010" .
|
|
@@ -31,7 +30,6 @@
|
|
|
31
30
|
:case2 :hasStep <http://example/eyeling/turing/case2/step-7> .
|
|
32
31
|
:case2 :hasStep <http://example/eyeling/turing/case2/step-8> .
|
|
33
32
|
:case2 :hasStep <http://example/eyeling/turing/case2/step-9> .
|
|
34
|
-
:case2 :output "110000" .
|
|
35
33
|
:case2 :rawOutput "110000#" .
|
|
36
34
|
:case2 :reportLine "101111 + 1 = 110000" .
|
|
37
35
|
:case2 log:outputString "101111 + 1 = 110000" .
|
|
@@ -51,7 +49,6 @@
|
|
|
51
49
|
:case3 :hasStep <http://example/eyeling/turing/case3/step-7> .
|
|
52
50
|
:case3 :hasStep <http://example/eyeling/turing/case3/step-8> .
|
|
53
51
|
:case3 :hasStep <http://example/eyeling/turing/case3/step-9> .
|
|
54
|
-
:case3 :output "1000000" .
|
|
55
52
|
:case3 :rawOutput "1000000#" .
|
|
56
53
|
:case3 :reportLine "111111 + 1 = 1000000" .
|
|
57
54
|
:case3 log:outputString "111111 + 1 = 1000000" .
|
|
@@ -59,7 +56,6 @@
|
|
|
59
56
|
:case4 :hasStep <http://example/eyeling/turing/case4/step-0> .
|
|
60
57
|
:case4 :hasStep <http://example/eyeling/turing/case4/step-1> .
|
|
61
58
|
:case4 :hasStep <http://example/eyeling/turing/case4/step-2> .
|
|
62
|
-
:case4 :output "1" .
|
|
63
59
|
:case4 :rawOutput "1#" .
|
|
64
60
|
:case4 :reportLine "empty tape + 1 = 1" .
|
|
65
61
|
:case4 log:outputString "empty tape + 1 = 1" .
|
|
@@ -73,7 +69,6 @@
|
|
|
73
69
|
:case5 :hasStep <http://example/eyeling/turing/case5/step-6> .
|
|
74
70
|
:case5 :hasStep <http://example/eyeling/turing/case5/step-7> .
|
|
75
71
|
:case5 :hasStep <http://example/eyeling/turing/case5/step-8> .
|
|
76
|
-
:case5 :output "100001" .
|
|
77
72
|
:case5 :rawOutput "100001#" .
|
|
78
73
|
:case5 :reportLine "100000 + 1 = 100001" .
|
|
79
74
|
:case5 log:outputString "100000 + 1 = 100001" .
|
package/eyeleng.js
CHANGED
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
const VERSION = readPackageVersion();
|
|
43
43
|
|
|
44
44
|
function help() {
|
|
45
|
-
return `eyeleng ${VERSION}\n\nA dependency-free JavaScript implementation experiment for the SHACL 1.2 Rules draft, including SRL and RDF Rules syntax front-ends.\n\nUsage:\n eyeleng [options] [file ...]\n\nOptions:\n --all Print the full closure, including input facts\n --json Print JSON instead of compact triples/bindings\n --trace Print derivation trace to stderr, or include it in JSON\n --stats Print iteration and triple counts to stderr\n --check Parse and analyze only; do not run rules\n --strict Treat static warnings as errors, including recursive term generation\n --deps Print rule dependency edges during --check\n --query TEXT Run a raw SRL body pattern over the closure or backward planner\n --query-file FILE Read a raw SRL body pattern from a file\n --query-mode MODE Use auto, forward, or backward query planning (default auto)\n --hybrid
|
|
45
|
+
return `eyeleng ${VERSION}\n\nA dependency-free JavaScript implementation experiment for the SHACL 1.2 Rules draft, including SRL and RDF Rules syntax front-ends.\n\nUsage:\n eyeleng [options] [file ...]\n\nOptions:\n --all Print the full closure, including input facts\n --json Print JSON instead of compact triples/bindings\n --trace Print derivation trace to stderr, or include it in JSON\n --stats Print iteration and triple counts to stderr\n --check Parse and analyze only; do not run rules\n --strict Treat static warnings as errors, including recursive term generation\n --deps Print rule dependency edges during --check\n --query TEXT Run a raw SRL body pattern over the closure or backward planner\n --query-file FILE Read a raw SRL body pattern from a file\n --query-mode MODE Use auto, forward, or backward query planning (default auto)\n --hybrid Force aggressive hybrid orientation for function-like rules\n --no-hybrid Disable automatic hybrid forward/backward execution\n --max-iterations N Stop after N fixpoint iterations within a recursive layer\n --no-imports Parse IMPORTS/owl:imports but do not load imported rule sets\n --rdf-messages Parse input as an RDF Message Log\n --include-message-facts Include payload facts while parsing RDF Message Logs\n --syntax MODE Use srl, rdf, or auto syntax detection (default auto)\n --ruleset TERM In RDF syntax, run only the selected srl:RuleSet\n --version Print version\n -h, --help Print this help\n\nWith no file arguments, eyeleng reads from stdin.\n`;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
function parseArgs(argv) {
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
query: null,
|
|
58
58
|
queryFile: null,
|
|
59
59
|
queryMode: 'auto',
|
|
60
|
-
hybrid:
|
|
60
|
+
hybrid: 'auto',
|
|
61
61
|
maxIterations: 10000,
|
|
62
62
|
imports: true,
|
|
63
63
|
syntax: 'auto',
|
|
@@ -77,6 +77,7 @@
|
|
|
77
77
|
else if (arg === '--deps') options.deps = true;
|
|
78
78
|
else if (arg === '--no-imports') options.imports = false;
|
|
79
79
|
else if (arg === '--hybrid') options.hybrid = true;
|
|
80
|
+
else if (arg === '--no-hybrid') options.hybrid = false;
|
|
80
81
|
else if (arg === '--rdf-messages') options.rdfMessages = true;
|
|
81
82
|
else if (arg === '--include-message-facts') options.includeMessageFacts = true;
|
|
82
83
|
else if (arg === '--syntax') {
|
|
@@ -709,7 +710,7 @@
|
|
|
709
710
|
}
|
|
710
711
|
|
|
711
712
|
parseTripleStatement(options = {}) {
|
|
712
|
-
const subjectNode = this.parseGraphNode(options);
|
|
713
|
+
const subjectNode = this.parseGraphNode({ ...options, position: 'subject' });
|
|
713
714
|
const triples = [...subjectNode.triples];
|
|
714
715
|
triples.push(...this.parsePropertyListForSubject(subjectNode.term, options));
|
|
715
716
|
return triples;
|
|
@@ -723,7 +724,7 @@
|
|
|
723
724
|
if (terminators.some((value) => this.checkValue(value)) || this.checkValue('.')) break;
|
|
724
725
|
const predicate = options.allowPath ? this.parseVerbPathOrSimple(options) : this.parseVerbTerm(options);
|
|
725
726
|
do {
|
|
726
|
-
const objectNode = this.parseGraphNode(options);
|
|
727
|
+
const objectNode = this.parseGraphNode({ ...options, position: 'object' });
|
|
727
728
|
triples.push(...objectNode.triples);
|
|
728
729
|
const baseTriple = { s: subject, p: predicate, o: objectNode.term };
|
|
729
730
|
triples.push(baseTriple);
|
|
@@ -790,6 +791,7 @@
|
|
|
790
791
|
currentReifier = this.parseOptionalReifier(options);
|
|
791
792
|
triples.push({ s: currentReifier, p: iri(RDF_REIFIES), o: reified });
|
|
792
793
|
} else if (this.matchValue('{|')) {
|
|
794
|
+
if (this.checkValue('|}')) throw this.error('Annotation blocks may not be empty');
|
|
793
795
|
const annotationSubject = currentReifier || this.freshGraphNode(options);
|
|
794
796
|
triples.push({ s: annotationSubject, p: iri(RDF_REIFIES), o: reified });
|
|
795
797
|
triples.push(...this.parsePropertyListForSubject(annotationSubject, options, ['|}']));
|
|
@@ -839,7 +841,7 @@
|
|
|
839
841
|
}
|
|
840
842
|
|
|
841
843
|
parseVerbTerm(options = {}) {
|
|
842
|
-
const term = this.parseTerm(options);
|
|
844
|
+
const term = this.parseTerm({ ...options, position: 'predicate' });
|
|
843
845
|
if (term.type !== 'iri' && term.type !== 'var') throw this.error('Expected IRI or variable as predicate');
|
|
844
846
|
return term;
|
|
845
847
|
}
|
|
@@ -962,7 +964,10 @@
|
|
|
962
964
|
if (token.value === '<<(') return this.parseTripleTermAfterOpen(options);
|
|
963
965
|
if (token.value === '<<') throw this.error('Use << s p o >> as a graph node reifier; use <<( s p o )>> for a triple term', token);
|
|
964
966
|
if (token.type === 'word') {
|
|
965
|
-
if (token.value === 'a')
|
|
967
|
+
if (token.value === 'a') {
|
|
968
|
+
if (options.position !== 'predicate') throw this.error('a is only allowed as a predicate', token);
|
|
969
|
+
return iri(RDF_TYPE);
|
|
970
|
+
}
|
|
966
971
|
if (token.value === 'true') return literal(true, XSD_BOOLEAN);
|
|
967
972
|
if (token.value === 'false') return literal(false, XSD_BOOLEAN);
|
|
968
973
|
if (token.value.startsWith('_:')) return blankNode(token.value.slice(2));
|
|
@@ -996,7 +1001,13 @@
|
|
|
996
1001
|
return literal(coerceLexicalLiteral(token.value, datatype), datatype, null);
|
|
997
1002
|
}
|
|
998
1003
|
if (this.checkType('word') && /^@[A-Za-z]+(?:-[A-Za-z0-9]+)*(?:--[A-Za-z]+)?$/.test(this.peek().value)) {
|
|
999
|
-
const
|
|
1004
|
+
const tagToken = this.advance();
|
|
1005
|
+
const rawTag = tagToken.value.slice(1);
|
|
1006
|
+
const direction = rawTag.includes('--') ? rawTag.slice(rawTag.lastIndexOf('--') + 2) : null;
|
|
1007
|
+
if (direction && direction !== 'ltr' && direction !== 'rtl') {
|
|
1008
|
+
throw this.error(`Invalid base direction --${direction}; expected --ltr or --rtl`, tagToken);
|
|
1009
|
+
}
|
|
1010
|
+
const tag = rawTag.toLowerCase();
|
|
1000
1011
|
const [lang, langDir = null] = tag.split('--');
|
|
1001
1012
|
return literal(token.value, null, lang, langDir);
|
|
1002
1013
|
}
|
|
@@ -2775,6 +2786,7 @@
|
|
|
2775
2786
|
const syntaxProfile = String(options.profile || options.profileId || '').toLowerCase();
|
|
2776
2787
|
const rdf12Surface = syntaxProfile === 'turtle' || syntaxProfile === 'trig';
|
|
2777
2788
|
const implicitStatementNodes = new Set();
|
|
2789
|
+
function implicitStatementNodeKey(term) { return `${term.kind}:${term.value}`; }
|
|
2778
2790
|
|
|
2779
2791
|
function freshBlank() { bnodeCounter += 1; return blank(`b${bnodeCounter}`); }
|
|
2780
2792
|
function peek(offset = 0) { return tokens[i + offset]; }
|
|
@@ -2925,7 +2937,7 @@
|
|
|
2925
2937
|
expect('>>');
|
|
2926
2938
|
const node = reifier || freshBlank();
|
|
2927
2939
|
out.push(triple(node, iri(RDF_REIFIES), tripleTerm(s, p, o), graph));
|
|
2928
|
-
|
|
2940
|
+
implicitStatementNodes.add(implicitStatementNodeKey(node));
|
|
2929
2941
|
return node;
|
|
2930
2942
|
}
|
|
2931
2943
|
|
|
@@ -2954,7 +2966,7 @@
|
|
|
2954
2966
|
if (accept(']')) return node;
|
|
2955
2967
|
parsePredicateObjectList(node, out, graph);
|
|
2956
2968
|
expect(']');
|
|
2957
|
-
if (node.kind === 'blank') implicitStatementNodes.add(node
|
|
2969
|
+
if (node.kind === 'blank') implicitStatementNodes.add(implicitStatementNodeKey(node));
|
|
2958
2970
|
return node;
|
|
2959
2971
|
}
|
|
2960
2972
|
|
|
@@ -3032,9 +3044,8 @@
|
|
|
3032
3044
|
if (options3.requireDot) expect('.'); else accept('.');
|
|
3033
3045
|
return;
|
|
3034
3046
|
}
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
if ((peek()?.type === '.' || peek()?.type === '}' || peek()?.type === undefined) && subject.kind === 'blank' && implicitStatementNodes.has(subject.value)) {
|
|
3047
|
+
const subject = parseTerm(out, graph, { noLiteral: !rdf12Surface, noA: true });
|
|
3048
|
+
if ((peek()?.type === '.' || peek()?.type === '}' || peek()?.type === undefined) && implicitStatementNodes.has(implicitStatementNodeKey(subject))) {
|
|
3038
3049
|
if (options3.requireDot) expect('.'); else accept('.');
|
|
3039
3050
|
return;
|
|
3040
3051
|
}
|
|
@@ -4356,7 +4367,8 @@
|
|
|
4356
4367
|
const relaxedRecursiveRunOnce = options.relaxedRecursion === false
|
|
4357
4368
|
? new Set()
|
|
4358
4369
|
: recursiveTermGenerationRuleIndexes(analysis);
|
|
4359
|
-
const
|
|
4370
|
+
const useHybrid = options.hybrid !== false && !options.shacl12Conformance;
|
|
4371
|
+
const hybridBackwardPredicates = useHybrid || options.backwardBodyCalls
|
|
4360
4372
|
? preferredBackwardPredicates(program, options)
|
|
4361
4373
|
: new Set();
|
|
4362
4374
|
const hybridBackwardRules = new Set();
|
|
@@ -5892,10 +5904,7 @@
|
|
|
5892
5904
|
const entry = this.memo.get(key);
|
|
5893
5905
|
if (entry && entry.complete) {
|
|
5894
5906
|
this.stats.memoHits += 1;
|
|
5895
|
-
|
|
5896
|
-
const next = unifyTriples(pattern, answer, binding);
|
|
5897
|
-
if (next) yield next;
|
|
5898
|
-
}
|
|
5907
|
+
yield* this.replayAnswers(pattern, binding, entry.answers);
|
|
5899
5908
|
return;
|
|
5900
5909
|
}
|
|
5901
5910
|
if (this.active.has(key)) return;
|
|
@@ -5909,7 +5918,6 @@
|
|
|
5909
5918
|
if (!next) continue;
|
|
5910
5919
|
rememberAnswer(answers, answerKeys, pattern, next);
|
|
5911
5920
|
this.stats.facts += 1;
|
|
5912
|
-
yield next;
|
|
5913
5921
|
}
|
|
5914
5922
|
|
|
5915
5923
|
for (const item of this.ruleCandidates(resolvedPattern)) {
|
|
@@ -5921,7 +5929,6 @@
|
|
|
5921
5929
|
for (const solved of this.solveBody(freshBody, next, depth + 1, 0)) {
|
|
5922
5930
|
rememberAnswer(answers, answerKeys, pattern, solved);
|
|
5923
5931
|
this.stats.rules += 1;
|
|
5924
|
-
yield solved;
|
|
5925
5932
|
}
|
|
5926
5933
|
}
|
|
5927
5934
|
} finally {
|
|
@@ -5930,6 +5937,14 @@
|
|
|
5930
5937
|
|
|
5931
5938
|
this.memo.set(key, { complete: true, answers });
|
|
5932
5939
|
this.stats.memoStores += 1;
|
|
5940
|
+
yield* this.replayAnswers(pattern, binding, answers);
|
|
5941
|
+
}
|
|
5942
|
+
|
|
5943
|
+
*replayAnswers(pattern, binding, answers) {
|
|
5944
|
+
for (const answer of answers) {
|
|
5945
|
+
const next = unifyTriples(pattern, answer, binding);
|
|
5946
|
+
if (next) yield next;
|
|
5947
|
+
}
|
|
5933
5948
|
}
|
|
5934
5949
|
|
|
5935
5950
|
factCandidates(pattern, binding) {
|
|
@@ -6209,15 +6224,57 @@
|
|
|
6209
6224
|
if (explicit) return supportedBackwardPredicates(program, { ...options, hybridPredicates: explicit });
|
|
6210
6225
|
const supported = supportedBackwardPredicates(program, options);
|
|
6211
6226
|
const preferred = new Set();
|
|
6227
|
+
const force = options.hybrid === true || options.hybridMode === 'force';
|
|
6228
|
+
const demanded = force ? null : demandedBodyPredicates(program);
|
|
6212
6229
|
for (const rule of program.rules || []) {
|
|
6213
6230
|
if (!ruleIsFunctionLike(rule)) continue;
|
|
6231
|
+
if (!force && ruleCreatesHeadTerms(rule)) continue;
|
|
6214
6232
|
for (const head of rule.head || []) {
|
|
6215
|
-
if (head
|
|
6233
|
+
if (!head || !head.p || head.p.type !== 'iri' || !supported.has(head.p.value)) continue;
|
|
6234
|
+
if (force || demanded.has(head.p.value)) preferred.add(head.p.value);
|
|
6216
6235
|
}
|
|
6217
6236
|
}
|
|
6218
6237
|
return preferred;
|
|
6219
6238
|
}
|
|
6220
6239
|
|
|
6240
|
+
function demandedBodyPredicates(program) {
|
|
6241
|
+
const out = new Set();
|
|
6242
|
+
for (const rule of program.rules || []) {
|
|
6243
|
+
for (const predicate of bodyPredicateDemands(rule.body || [])) if (predicate) out.add(predicate);
|
|
6244
|
+
}
|
|
6245
|
+
return out;
|
|
6246
|
+
}
|
|
6247
|
+
|
|
6248
|
+
|
|
6249
|
+
function ruleCreatesHeadTerms(rule) {
|
|
6250
|
+
const headVars = new Set();
|
|
6251
|
+
for (const triple of rule.head || []) {
|
|
6252
|
+
for (const term of [triple.s, triple.p, triple.o]) {
|
|
6253
|
+
if (!term) continue;
|
|
6254
|
+
if (term.type === 'blank') return true;
|
|
6255
|
+
if (term.type === 'var') headVars.add(term.value);
|
|
6256
|
+
}
|
|
6257
|
+
}
|
|
6258
|
+
if (headVars.size === 0) return false;
|
|
6259
|
+
for (const clause of rule.body || []) {
|
|
6260
|
+
if ((clause.type === 'set' || clause.type === 'bind') && headVars.has(clause.variable) && expressionCreatesTerm(clause.expr)) return true;
|
|
6261
|
+
}
|
|
6262
|
+
return false;
|
|
6263
|
+
}
|
|
6264
|
+
|
|
6265
|
+
function expressionCreatesTerm(expr) {
|
|
6266
|
+
if (!expr) return false;
|
|
6267
|
+
if (expr.type === 'call') {
|
|
6268
|
+
const name = String(expr.name || '').toUpperCase();
|
|
6269
|
+
if (name === 'BNODE' || name === 'IRI' || name === 'URI' || name === 'TRIPLE' || name === 'UUID' || name === 'STRUUID') return true;
|
|
6270
|
+
return (expr.args || []).some(expressionCreatesTerm);
|
|
6271
|
+
}
|
|
6272
|
+
if (expr.type === 'binary') return expressionCreatesTerm(expr.left) || expressionCreatesTerm(expr.right);
|
|
6273
|
+
if (expr.type === 'unary') return expressionCreatesTerm(expr.expr);
|
|
6274
|
+
if (expr.type === 'in') return expressionCreatesTerm(expr.left) || (expr.values || []).some(expressionCreatesTerm);
|
|
6275
|
+
return false;
|
|
6276
|
+
}
|
|
6277
|
+
|
|
6221
6278
|
function ruleIsFunctionLike(rule) {
|
|
6222
6279
|
return (rule.body || []).some((clause) => clause.type === 'set' || clause.type === 'bind');
|
|
6223
6280
|
}
|
|
@@ -6355,7 +6412,7 @@
|
|
|
6355
6412
|
prefixes: result.prefixes,
|
|
6356
6413
|
select,
|
|
6357
6414
|
bindings: projectBindings(bindings, select),
|
|
6358
|
-
mode:
|
|
6415
|
+
mode: result.hybridStats ? 'hybrid' : 'forward',
|
|
6359
6416
|
};
|
|
6360
6417
|
}
|
|
6361
6418
|
|
|
@@ -6421,13 +6478,16 @@
|
|
|
6421
6478
|
}
|
|
6422
6479
|
|
|
6423
6480
|
function queryRunOptions(program, querySpec, options = {}) {
|
|
6424
|
-
|
|
6481
|
+
const mode = options.queryMode || 'auto';
|
|
6482
|
+
if (mode === 'forward') return { ...options, hybrid: false };
|
|
6483
|
+
if (shouldUseHybridForQuery(program, querySpec, options)) return { ...options, hybrid: options.hybrid ?? 'auto' };
|
|
6425
6484
|
return options;
|
|
6426
6485
|
}
|
|
6427
6486
|
|
|
6428
6487
|
function shouldUseHybridForQuery(program, querySpec, options = {}) {
|
|
6429
6488
|
const mode = options.queryMode || 'auto';
|
|
6430
|
-
if (options.hybrid
|
|
6489
|
+
if (options.hybrid === false) return false;
|
|
6490
|
+
if (options.hybrid === true) return true;
|
|
6431
6491
|
if (mode !== 'auto') return false;
|
|
6432
6492
|
if (!querySpec) return false;
|
|
6433
6493
|
return preferredBackwardPredicates(program, options).size > 0;
|