eyeleng 1.0.10 → 1.0.12
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 +21 -14
- package/dist/browser/eyeleng.browser.js +159 -22
- package/examples/assignment.srl +1 -1
- package/examples/base-and-literals.srl +1 -1
- package/examples/output/cat-koko.trig +2 -2
- package/examples/output/turing.trig +500 -0
- package/examples/path-discovery.srl +5 -5
- package/examples/turing.srl +306 -0
- package/eyeleng.js +161 -24
- package/package.json +1 -1
- package/src/analyze.js +59 -16
- package/src/cli.js +2 -2
- package/src/engine.js +100 -6
- package/src/shacl12RulesManifest.js +5 -0
- package/test/api.test.js +82 -1
- package/test/cli.test.js +41 -0
- package/test/run.js +1 -0
package/README.md
CHANGED
|
@@ -76,6 +76,8 @@ then the body matches with `?parent = :alice` and `?child = :bob`, and the head
|
|
|
76
76
|
|
|
77
77
|
Negation is handled by stratified evaluation: rules are grouped into dependency layers, and recursion through negation is rejected so the result stays deterministic.
|
|
78
78
|
|
|
79
|
+
Recursive rules that create new terms through `SET`/`BIND` or head blank nodes run in relaxed mode by default. Relaxed mode can derive useful finite closures, but termination is not guaranteed; use `--max-iterations` as a safety valve. `--strict` rejects these recursive term-generating cycles at analysis time. Head blank nodes are deterministically skolemized per rule and solution mapping, so the same firing reuses the same witness node instead of creating a fresh one each pass.
|
|
80
|
+
|
|
79
81
|
## Language surface
|
|
80
82
|
|
|
81
83
|
SRL supports the practical rule features used by the SHACL 1.2 Rules tests and examples:
|
|
@@ -90,7 +92,7 @@ DATA {
|
|
|
90
92
|
|
|
91
93
|
RULE { ?x :grade ?grade } WHERE {
|
|
92
94
|
?x :score ?score .
|
|
93
|
-
FILTER(?score >= 5)
|
|
95
|
+
FILTER(?score >= 5)
|
|
94
96
|
BIND(concat("pass-", str(?score)) AS ?grade)
|
|
95
97
|
}
|
|
96
98
|
|
|
@@ -197,19 +199,24 @@ Common commands:
|
|
|
197
199
|
Important options:
|
|
198
200
|
|
|
199
201
|
```text
|
|
200
|
-
--all
|
|
201
|
-
--
|
|
202
|
-
--
|
|
203
|
-
--
|
|
204
|
-
--
|
|
205
|
-
--
|
|
206
|
-
--
|
|
207
|
-
--query
|
|
208
|
-
--query-file FILE
|
|
209
|
-
--max-iterations N
|
|
210
|
-
--no-imports
|
|
211
|
-
--rdf-messages
|
|
212
|
-
--stream-messages
|
|
202
|
+
--all print the full closure, including input facts
|
|
203
|
+
--json print JSON instead of compact triples/bindings
|
|
204
|
+
--trace print derivation trace to stderr, or include it in JSON
|
|
205
|
+
--stats print iteration and triple counts to stderr
|
|
206
|
+
--check parse and analyze only; do not run rules
|
|
207
|
+
--strict treat static warnings as errors, including recursive term generation
|
|
208
|
+
--deps print rule dependency edges during --check
|
|
209
|
+
--query TEXT run a raw SRL body pattern over the closure
|
|
210
|
+
--query-file FILE read a raw SRL body pattern from a file
|
|
211
|
+
--max-iterations N stop after N fixpoint iterations within a recursive layer
|
|
212
|
+
--no-imports parse IMPORTS/owl:imports but do not load imported rule sets
|
|
213
|
+
--rdf-messages parse input as an RDF Message Log
|
|
214
|
+
--stream-messages replay RDF Message Log envelopes
|
|
215
|
+
--include-message-facts include payload facts while parsing RDF Message Logs
|
|
216
|
+
--syntax MODE use srl, rdf, or auto syntax detection (default auto)
|
|
217
|
+
--ruleset TERM in RDF syntax, run only the selected srl:RuleSet
|
|
218
|
+
--version print version
|
|
219
|
+
-h, --help print help
|
|
213
220
|
```
|
|
214
221
|
|
|
215
222
|
## Public API
|
|
@@ -4048,8 +4048,8 @@
|
|
|
4048
4048
|
"src/engine.js": function (require, module, exports) {
|
|
4049
4049
|
'use strict';
|
|
4050
4050
|
|
|
4051
|
-
const { TripleStore, bindingKey
|
|
4052
|
-
const { tripleKey, termEquals } = require('./term.js');
|
|
4051
|
+
const { TripleStore, bindingKey } = require('./store.js');
|
|
4052
|
+
const { tripleKey, termKey, termEquals, blankNode, tripleTerm } = require('./term.js');
|
|
4053
4053
|
const { evalExpression, booleanValue, asTerm } = require('./builtins.js');
|
|
4054
4054
|
const { analyze } = require('./analyze.js');
|
|
4055
4055
|
|
|
@@ -4069,7 +4069,7 @@
|
|
|
4069
4069
|
runOnce: !!rule.runOnce,
|
|
4070
4070
|
}));
|
|
4071
4071
|
|
|
4072
|
-
const analysis = options.analysis || analyze(program);
|
|
4072
|
+
const analysis = options.analysis || analyze(program, options);
|
|
4073
4073
|
if (analysis.errors && analysis.errors.length > 0 && !options.ignoreAnalysisErrors) {
|
|
4074
4074
|
throw new Error(`Analysis failed: ${analysis.errors.map((error) => error.message).join('; ')}`);
|
|
4075
4075
|
}
|
|
@@ -4080,6 +4080,9 @@
|
|
|
4080
4080
|
layerIndexes,
|
|
4081
4081
|
analysis.dependency ? analysis.dependency.edges : [],
|
|
4082
4082
|
);
|
|
4083
|
+
const relaxedRecursiveRunOnce = options.relaxedRecursion === false
|
|
4084
|
+
? new Set()
|
|
4085
|
+
: recursiveTermGenerationRuleIndexes(analysis);
|
|
4083
4086
|
const baseContext = {
|
|
4084
4087
|
...evalOptions,
|
|
4085
4088
|
maxIterations,
|
|
@@ -4095,8 +4098,8 @@
|
|
|
4095
4098
|
|
|
4096
4099
|
for (let layerIndex = 0; layerIndex < layerIndexes.length; layerIndex += 1) {
|
|
4097
4100
|
const layer = layerIndexes[layerIndex];
|
|
4098
|
-
const ordinary = layer.filter((ruleIndex) => !program.rules[ruleIndex].runOnce);
|
|
4099
|
-
const runOnce = layer.filter((ruleIndex) => program.rules[ruleIndex].runOnce);
|
|
4101
|
+
const ordinary = layer.filter((ruleIndex) => !program.rules[ruleIndex].runOnce || relaxedRecursiveRunOnce.has(ruleIndex));
|
|
4102
|
+
const runOnce = layer.filter((ruleIndex) => program.rules[ruleIndex].runOnce && !relaxedRecursiveRunOnce.has(ruleIndex));
|
|
4100
4103
|
|
|
4101
4104
|
if (runOnce.length > 0) {
|
|
4102
4105
|
iterations += 1;
|
|
@@ -4196,6 +4199,7 @@
|
|
|
4196
4199
|
let added = 0;
|
|
4197
4200
|
const dedupeBindings = rule.body.some((clause) => clause.type === 'path');
|
|
4198
4201
|
const seenBindings = dedupeBindings ? new Set() : null;
|
|
4202
|
+
const headBlankLabels = collectHeadBlankLabels(rule.head);
|
|
4199
4203
|
|
|
4200
4204
|
const bodyBindings = rule.body.length === 1 && rule.body[0].type === 'triple'
|
|
4201
4205
|
? store.match(rule.body[0].triple, {})
|
|
@@ -4210,8 +4214,10 @@
|
|
|
4210
4214
|
applications += 1;
|
|
4211
4215
|
context.perRule[ruleIndex].applications += 1;
|
|
4212
4216
|
|
|
4217
|
+
const headBlankMap = headBlankLabels.size > 0 ? new Map() : null;
|
|
4218
|
+
const skolemKey = headBlankMap ? skolemizationKey(ruleIndex, binding) : null;
|
|
4213
4219
|
for (const head of rule.head) {
|
|
4214
|
-
const triple =
|
|
4220
|
+
const triple = instantiateHeadTriple(head, binding, headBlankLabels, headBlankMap, skolemKey);
|
|
4215
4221
|
if (!triple) continue;
|
|
4216
4222
|
if (store.add(triple)) {
|
|
4217
4223
|
added += 1;
|
|
@@ -4233,6 +4239,94 @@
|
|
|
4233
4239
|
return { applications, added };
|
|
4234
4240
|
}
|
|
4235
4241
|
|
|
4242
|
+
function recursiveTermGenerationRuleIndexes(analysis) {
|
|
4243
|
+
const out = new Set();
|
|
4244
|
+
if (!analysis || !analysis.dependency || !analysis.diagnostics) return out;
|
|
4245
|
+
const byName = new Map((analysis.dependency.rules || []).map((rule) => [rule.name, rule.index]));
|
|
4246
|
+
for (const diagnostic of analysis.diagnostics) {
|
|
4247
|
+
if (diagnostic.code !== 'recursive-assignment-rule') continue;
|
|
4248
|
+
if (byName.has(diagnostic.rule)) out.add(byName.get(diagnostic.rule));
|
|
4249
|
+
}
|
|
4250
|
+
return out;
|
|
4251
|
+
}
|
|
4252
|
+
|
|
4253
|
+
function instantiateHeadTriple(pattern, binding, headBlankLabels, headBlankMap, skolemKey) {
|
|
4254
|
+
const s = instantiateHeadTerm(pattern.s, binding, headBlankLabels, headBlankMap, skolemKey);
|
|
4255
|
+
const p = instantiateHeadTerm(pattern.p, binding, headBlankLabels, headBlankMap, skolemKey);
|
|
4256
|
+
const o = instantiateHeadTerm(pattern.o, binding, headBlankLabels, headBlankMap, skolemKey);
|
|
4257
|
+
if (!s || !p || !o) return null;
|
|
4258
|
+
if (p.type !== 'iri') return null;
|
|
4259
|
+
return { s, p, o };
|
|
4260
|
+
}
|
|
4261
|
+
|
|
4262
|
+
function instantiateHeadTerm(term, binding, headBlankLabels, headBlankMap, skolemKey) {
|
|
4263
|
+
if (term.type === 'var') return binding[term.value] || null;
|
|
4264
|
+
if (term.type === 'blank' && headBlankLabels.has(term.value)) {
|
|
4265
|
+
let label = headBlankMap.get(term.value);
|
|
4266
|
+
if (!label) {
|
|
4267
|
+
label = `sk_${deterministicSkolemIdFromKey(`${skolemKey}|${term.value}`).replace(/-/g, '_')}`;
|
|
4268
|
+
headBlankMap.set(term.value, label);
|
|
4269
|
+
}
|
|
4270
|
+
return blankNode(label);
|
|
4271
|
+
}
|
|
4272
|
+
if (term.type === 'triple') {
|
|
4273
|
+
const s = instantiateHeadTerm(term.s, binding, headBlankLabels, headBlankMap, skolemKey);
|
|
4274
|
+
const p = instantiateHeadTerm(term.p, binding, headBlankLabels, headBlankMap, skolemKey);
|
|
4275
|
+
const o = instantiateHeadTerm(term.o, binding, headBlankLabels, headBlankMap, skolemKey);
|
|
4276
|
+
if (!s || !p || !o) return null;
|
|
4277
|
+
return tripleTerm(s, p, o);
|
|
4278
|
+
}
|
|
4279
|
+
return term;
|
|
4280
|
+
}
|
|
4281
|
+
|
|
4282
|
+
function collectHeadBlankLabels(head) {
|
|
4283
|
+
const labels = new Set();
|
|
4284
|
+
for (const triple of head || []) {
|
|
4285
|
+
collectBlankLabelsFromTerm(triple.s, labels);
|
|
4286
|
+
collectBlankLabelsFromTerm(triple.p, labels);
|
|
4287
|
+
collectBlankLabelsFromTerm(triple.o, labels);
|
|
4288
|
+
}
|
|
4289
|
+
return labels;
|
|
4290
|
+
}
|
|
4291
|
+
|
|
4292
|
+
function collectBlankLabelsFromTerm(term, labels) {
|
|
4293
|
+
if (!term) return;
|
|
4294
|
+
if (term.type === 'blank') {
|
|
4295
|
+
labels.add(term.value);
|
|
4296
|
+
return;
|
|
4297
|
+
}
|
|
4298
|
+
if (term.type === 'triple') {
|
|
4299
|
+
collectBlankLabelsFromTerm(term.s, labels);
|
|
4300
|
+
collectBlankLabelsFromTerm(term.p, labels);
|
|
4301
|
+
collectBlankLabelsFromTerm(term.o, labels);
|
|
4302
|
+
}
|
|
4303
|
+
}
|
|
4304
|
+
|
|
4305
|
+
function skolemizationKey(ruleIndex, binding) {
|
|
4306
|
+
let out = `rule:${ruleIndex}`;
|
|
4307
|
+
for (const name of Object.keys(binding).sort()) {
|
|
4308
|
+
const value = binding[name];
|
|
4309
|
+
out += `|${name}=${value ? termKey(value) : 'unbound'}`;
|
|
4310
|
+
}
|
|
4311
|
+
return out;
|
|
4312
|
+
}
|
|
4313
|
+
|
|
4314
|
+
function deterministicSkolemIdFromKey(key) {
|
|
4315
|
+
let h1 = 0x811c9dc5;
|
|
4316
|
+
let h2 = 0x811c9dc5;
|
|
4317
|
+
let h3 = 0x811c9dc5;
|
|
4318
|
+
let h4 = 0x811c9dc5;
|
|
4319
|
+
for (let i = 0; i < key.length; i += 1) {
|
|
4320
|
+
const c = key.charCodeAt(i);
|
|
4321
|
+
h1 ^= c; h1 = Math.imul(h1, 0x01000193) >>> 0;
|
|
4322
|
+
h2 ^= c + 1; h2 = Math.imul(h2, 0x01000193) >>> 0;
|
|
4323
|
+
h3 ^= c + 2; h3 = Math.imul(h3, 0x01000193) >>> 0;
|
|
4324
|
+
h4 ^= c + 3; h4 = Math.imul(h4, 0x01000193) >>> 0;
|
|
4325
|
+
}
|
|
4326
|
+
return [h1, h2, h3, h4].map((h) => h.toString(16).padStart(8, '0')).join('');
|
|
4327
|
+
}
|
|
4328
|
+
|
|
4329
|
+
|
|
4236
4330
|
function evaluateBody(clauses, store, initialBinding = {}, options = {}) {
|
|
4237
4331
|
const bindings = [];
|
|
4238
4332
|
const seen = new Set();
|
|
@@ -4589,8 +4683,8 @@
|
|
|
4589
4683
|
function analyze(program, options = {}) {
|
|
4590
4684
|
const diagnostics = [];
|
|
4591
4685
|
const dependency = dependencyGraph(program, options);
|
|
4592
|
-
const
|
|
4593
|
-
const recursiveIndexes =
|
|
4686
|
+
const hasTermGeneratingRules = dependency.rules.some((rule) => rule.createsTerms);
|
|
4687
|
+
const recursiveIndexes = hasTermGeneratingRules ? recursiveRuleIndexes(dependency) : new Set();
|
|
4594
4688
|
|
|
4595
4689
|
program.rules.forEach((rule, index) => {
|
|
4596
4690
|
const name = ruleName(rule, index);
|
|
@@ -4622,12 +4716,13 @@
|
|
|
4622
4716
|
|
|
4623
4717
|
diagnostics.push(...sequentialWellFormednessDiagnostics(rule.body, name, program.prefixes || {}));
|
|
4624
4718
|
|
|
4625
|
-
|
|
4719
|
+
const depRule = dependency.rules[index] || {};
|
|
4720
|
+
if (depRule.createsTerms && recursiveIndexes.has(index)) {
|
|
4626
4721
|
diagnostics.push({
|
|
4627
4722
|
code: 'recursive-assignment-rule',
|
|
4628
4723
|
severity: 'warning',
|
|
4629
4724
|
rule: name,
|
|
4630
|
-
message: `${displayRuleName(name, program.prefixes || {})}
|
|
4725
|
+
message: `${displayRuleName(name, program.prefixes || {})} creates terms in a recursive dependency cycle; relaxed mode allows this but termination is not guaranteed (use --strict to reject it)`,
|
|
4631
4726
|
});
|
|
4632
4727
|
}
|
|
4633
4728
|
|
|
@@ -4674,34 +4769,47 @@
|
|
|
4674
4769
|
negativePredicates: new Set(negativePatterns.flatMap((triple) => predicateIRIs(triple))),
|
|
4675
4770
|
runOnce: !!rule.runOnce,
|
|
4676
4771
|
hasAssignment: ruleHasAssignment(rule, options),
|
|
4772
|
+
hasTermGeneratingAssignment: ruleHasTermGeneratingAssignment(rule, options),
|
|
4677
4773
|
headHasBlankNode: ruleHeadHasBlankNode(rule),
|
|
4774
|
+
createsTerms: ruleCreatesTerms(rule, options),
|
|
4678
4775
|
};
|
|
4679
4776
|
});
|
|
4680
4777
|
|
|
4681
4778
|
const edgeMap = new Map();
|
|
4682
|
-
function addEdge(from, to,
|
|
4779
|
+
function addEdge(from, to, kind, predicate) {
|
|
4683
4780
|
const label = predicate || '*';
|
|
4684
4781
|
const key = `${from.index}->${to.index}:${label}`;
|
|
4782
|
+
const negated = kind === 'negated';
|
|
4783
|
+
const termGeneration = kind === 'term-generation';
|
|
4685
4784
|
const existing = edgeMap.get(key);
|
|
4686
4785
|
if (existing) {
|
|
4687
|
-
existing.
|
|
4786
|
+
existing.negated = existing.negated || negated;
|
|
4787
|
+
existing.termGeneration = existing.termGeneration || termGeneration;
|
|
4788
|
+
existing.negative = existing.negated || existing.termGeneration;
|
|
4688
4789
|
return;
|
|
4689
4790
|
}
|
|
4690
|
-
edgeMap.set(key, {
|
|
4791
|
+
edgeMap.set(key, {
|
|
4792
|
+
from: from.index,
|
|
4793
|
+
to: to.index,
|
|
4794
|
+
negative: negated || termGeneration,
|
|
4795
|
+
negated,
|
|
4796
|
+
termGeneration,
|
|
4797
|
+
predicate,
|
|
4798
|
+
});
|
|
4691
4799
|
}
|
|
4692
4800
|
|
|
4693
4801
|
const headIndex = buildHeadTemplateIndex(rules);
|
|
4694
4802
|
|
|
4695
4803
|
for (const from of rules) {
|
|
4696
|
-
const forceClosed = from.
|
|
4804
|
+
const forceClosed = from.createsTerms;
|
|
4697
4805
|
for (const pattern of from.positivePatterns) {
|
|
4698
4806
|
for (const candidate of candidateHeadTemplates(headIndex, pattern)) {
|
|
4699
|
-
if (canPossiblyGenerate(candidate.template, pattern)) addEdge(from, rules[candidate.ruleIndex], forceClosed, dependencyPredicateLabel(pattern));
|
|
4807
|
+
if (canPossiblyGenerate(candidate.template, pattern)) addEdge(from, rules[candidate.ruleIndex], forceClosed ? 'term-generation' : 'positive', dependencyPredicateLabel(pattern));
|
|
4700
4808
|
}
|
|
4701
4809
|
}
|
|
4702
4810
|
for (const pattern of from.negativePatterns) {
|
|
4703
4811
|
for (const candidate of candidateHeadTemplates(headIndex, pattern)) {
|
|
4704
|
-
if (canPossiblyGenerate(candidate.template, pattern)) addEdge(from, rules[candidate.ruleIndex],
|
|
4812
|
+
if (canPossiblyGenerate(candidate.template, pattern)) addEdge(from, rules[candidate.ruleIndex], 'negated', dependencyPredicateLabel(pattern));
|
|
4705
4813
|
}
|
|
4706
4814
|
}
|
|
4707
4815
|
}
|
|
@@ -4717,7 +4825,7 @@
|
|
|
4717
4825
|
const unstratifiedCycles = [];
|
|
4718
4826
|
const seen = new Set();
|
|
4719
4827
|
for (const edge of edges) {
|
|
4720
|
-
if (!edge.
|
|
4828
|
+
if (!edge.negated) continue;
|
|
4721
4829
|
if (edge.from === edge.to && rules[edge.from].runOnce && !rules[edge.from].headHasBlankNode) continue;
|
|
4722
4830
|
if (componentOf.get(edge.from) !== componentOf.get(edge.to)) continue;
|
|
4723
4831
|
const component = components[componentOf.get(edge.from)];
|
|
@@ -4740,7 +4848,10 @@
|
|
|
4740
4848
|
positivePredicates: Array.from(rule.positivePredicates),
|
|
4741
4849
|
negativePredicates: Array.from(rule.negativePredicates),
|
|
4742
4850
|
runOnce: rule.runOnce,
|
|
4851
|
+
hasAssignment: rule.hasAssignment,
|
|
4852
|
+
hasTermGeneratingAssignment: rule.hasTermGeneratingAssignment,
|
|
4743
4853
|
headHasBlankNode: rule.headHasBlankNode,
|
|
4854
|
+
createsTerms: rule.createsTerms,
|
|
4744
4855
|
})),
|
|
4745
4856
|
edges,
|
|
4746
4857
|
components: components.map((component) => component.map((ruleIndex) => rules[ruleIndex].name)),
|
|
@@ -4910,7 +5021,14 @@
|
|
|
4910
5021
|
function recursiveRuleIndexes(dependency) {
|
|
4911
5022
|
const out = new Set();
|
|
4912
5023
|
const ruleByName = new Map(dependency.rules.map((rule) => [rule.name, rule]));
|
|
4913
|
-
const
|
|
5024
|
+
const componentOf = new Map();
|
|
5025
|
+
|
|
5026
|
+
dependency.components.forEach((component, componentIndex) => {
|
|
5027
|
+
for (const name of component) {
|
|
5028
|
+
const rule = ruleByName.get(name);
|
|
5029
|
+
if (rule) componentOf.set(rule.index, componentIndex);
|
|
5030
|
+
}
|
|
5031
|
+
});
|
|
4914
5032
|
|
|
4915
5033
|
for (const component of dependency.components) {
|
|
4916
5034
|
if (component.length <= 1) continue;
|
|
@@ -4921,9 +5039,9 @@
|
|
|
4921
5039
|
}
|
|
4922
5040
|
|
|
4923
5041
|
for (const edge of dependency.edges) {
|
|
4924
|
-
const rule =
|
|
4925
|
-
if (edge.from === edge.to && edge.
|
|
4926
|
-
out.add(edge.from);
|
|
5042
|
+
const rule = (dependency.rules || []).find((candidate) => candidate.index === edge.from);
|
|
5043
|
+
if (edge.from === edge.to && edge.negated && rule && rule.runOnce && !rule.headHasBlankNode) continue;
|
|
5044
|
+
if (edge.from === edge.to || componentOf.get(edge.from) === componentOf.get(edge.to)) out.add(edge.from);
|
|
4927
5045
|
}
|
|
4928
5046
|
return out;
|
|
4929
5047
|
}
|
|
@@ -5196,7 +5314,26 @@
|
|
|
5196
5314
|
}
|
|
5197
5315
|
|
|
5198
5316
|
function ruleHasAssignment(rule, options = {}) {
|
|
5199
|
-
return
|
|
5317
|
+
return (rule.body || []).some((clause) => clause.type === 'set' || clause.type === 'bind');
|
|
5318
|
+
}
|
|
5319
|
+
|
|
5320
|
+
function ruleHasTermGeneratingAssignment(rule, options = {}) {
|
|
5321
|
+
return (rule.body || []).some((clause) => (clause.type === 'set' || clause.type === 'bind') && assignmentMayCreateNewTerm(clause.expr));
|
|
5322
|
+
}
|
|
5323
|
+
|
|
5324
|
+
function ruleCreatesTerms(rule, options = {}) {
|
|
5325
|
+
return ruleHeadHasBlankNode(rule) || ruleHasTermGeneratingAssignment(rule, options);
|
|
5326
|
+
}
|
|
5327
|
+
|
|
5328
|
+
function assignmentMayCreateNewTerm(expr) {
|
|
5329
|
+
// Simple aliases and constants are not term generators: they select from a
|
|
5330
|
+
// fixed term or from already-bound terms. Expressions that compute values
|
|
5331
|
+
// through operators or functions may create an unbounded sequence when used
|
|
5332
|
+
// recursively, e.g. SET(?v1 := ?v + 1), so they remain part of the strict
|
|
5333
|
+
// recursive-new-terms check.
|
|
5334
|
+
if (!expr) return false;
|
|
5335
|
+
if (expr.type === 'var' || expr.type === 'term' || expr.type === 'literal') return false;
|
|
5336
|
+
return true;
|
|
5200
5337
|
}
|
|
5201
5338
|
|
|
5202
5339
|
function ruleHeadHasBlankNode(rule) {
|
package/examples/assignment.srl
CHANGED
|
@@ -15,6 +15,6 @@ RULE {
|
|
|
15
15
|
} WHERE {
|
|
16
16
|
?person :name ?name ;
|
|
17
17
|
:age ?age .
|
|
18
|
-
FILTER(datatype(?age) = xsd:integer && ?age >= 18 && lang(?name) = "en")
|
|
18
|
+
FILTER(datatype(?age) = xsd:integer && ?age >= 18 && lang(?name) = "en")
|
|
19
19
|
SET(?slug := REPLACE(LCASE(STR(?name)), " ", "-"))
|
|
20
20
|
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
_:
|
|
2
|
-
_:
|
|
1
|
+
_:sk_319b8a2f709c2e2189803dc77b3d3e85 a :BritishShortHair .
|
|
2
|
+
_:sk_bb32b39908778273d11a8e49e72b9547 a :Cat .
|
|
3
3
|
:test :is true .
|