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.
@@ -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 .
@@ -1,4 +1,3 @@
1
- :case :solution "162857493534129678789643521475312986913586742628794135356478219241935867897261354" .
2
1
  :r1c1 :value 1 .
3
2
  :r1c2 :value 6 .
4
3
  :r1c3 :value 2 .
@@ -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 Orient function-like rules backward during forward execution and queries\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`;
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: false,
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') return iri(RDF_TYPE);
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 tag = this.advance().value.slice(1).toLowerCase();
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
- if (node.kind === 'blank') implicitStatementNodes.add(node.value);
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.value);
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
- if (peek()?.type === '<<' && peek(1)?.type === '(') throw new Error('Triple term cannot be used as a subject');
3036
- const subject = parseTerm(out, graph, { noLiteral: true, noA: true });
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 hybridBackwardPredicates = options.hybrid || options.backwardBodyCalls
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
- for (const answer of entry.answers) {
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 && head.p && head.p.type === 'iri' && supported.has(head.p.value)) preferred.add(head.p.value);
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: options.queryMode === 'hybrid' || options.hybrid ? 'hybrid' : 'forward',
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
- if (shouldUseHybridForQuery(program, querySpec, options)) return { ...options, hybrid: true };
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 || mode === 'hybrid') return true;
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyeleng",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
4
4
  "description": "The EYE Logic Engine: a JavaScript implementation of SHACL Rules, including SRL and RDF Rules syntax front-ends.",
5
5
  "keywords": [
6
6
  "SRL",