eyeling 1.10.20 → 1.10.21

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.
Files changed (45) hide show
  1. package/HANDBOOK.md +88 -2
  2. package/examples/bind-builtins.n3 +11 -0
  3. package/examples/bind.n3 +7 -0
  4. package/examples/brussels-brew-club.n3 +119 -0
  5. package/examples/builtins-string-math.n3 +11 -0
  6. package/examples/builtins-triple-termtests.n3 +7 -0
  7. package/examples/family.n3 +10 -0
  8. package/examples/filter-demorgan.n3 +9 -0
  9. package/examples/filter-in-notin.n3 +10 -0
  10. package/examples/filter-nested-or.n3 +10 -0
  11. package/examples/filter.n3 +8 -0
  12. package/examples/input/bind-builtins.srl +30 -0
  13. package/examples/input/bind.srl +12 -0
  14. package/examples/input/builtins-string-math.srl +38 -0
  15. package/examples/input/builtins-triple-termtests.srl +27 -0
  16. package/examples/input/family.srl +12 -0
  17. package/examples/input/filter-demorgan.srl +15 -0
  18. package/examples/input/filter-in-notin.srl +15 -0
  19. package/examples/input/filter-nested-or.srl +15 -0
  20. package/examples/input/filter.srl +9 -0
  21. package/examples/input/snaf.srl +6 -0
  22. package/examples/json-pointer.n3 +75 -0
  23. package/examples/json-reconcile-vat.n3 +361 -0
  24. package/examples/output/bind-builtins.n3 +9 -0
  25. package/examples/output/bind.n3 +3 -0
  26. package/examples/output/brussels-brew-club.n3 +22 -0
  27. package/examples/output/builtins-string-math.n3 +0 -0
  28. package/examples/output/builtins-triple-termtests.n3 +0 -0
  29. package/examples/output/family.n3 +13 -0
  30. package/examples/output/filter-demorgan.n3 +3 -0
  31. package/examples/output/filter-in-notin.n3 +4 -0
  32. package/examples/output/filter-nested-or.n3 +4 -0
  33. package/examples/output/filter.n3 +3 -0
  34. package/examples/output/json-pointer.n3 +13 -0
  35. package/examples/output/json-reconcile-vat.n3 +226 -0
  36. package/examples/output/snaf.n3 +3 -0
  37. package/examples/snaf.n3 +6 -0
  38. package/eyeling-builtins.ttl +48 -0
  39. package/eyeling.js +312 -1
  40. package/lib/engine.js +307 -1
  41. package/lib/rules.js +5 -0
  42. package/package.json +1 -1
  43. package/test/n3gen.test.js +4 -4
  44. package/test/package.test.js +1 -1
  45. package/tools/n3gen.js +1883 -6
package/HANDBOOK.md CHANGED
@@ -702,7 +702,7 @@ With that, we can tour the builtin families as Eyeling actually implements them.
702
702
 
703
703
  These builtins hash a string and return a lowercase hex digest as a plain string literal.
704
704
 
705
- ### `crypto:sha`, `crypto:md5`, `crypto:sha256`, `crypto:sha512`
705
+ ### `crypto:sha`, `crypto:md5`, `crypto:sha256`, `crypto:sha384`, `crypto:sha512`
706
706
 
707
707
  **Shape:**
708
708
  `$literal crypto:sha256 $digest`
@@ -1224,6 +1224,22 @@ Returns one of four IRIs:
1224
1224
  * `rdf:List` (closed or open list terms)
1225
1225
  * `log:Other` (IRIs, blank nodes, etc.)
1226
1226
 
1227
+
1228
+
1229
+ #### `log:isIRI` / `log:isLiteral` / `log:isBlank` / `log:isNumeric` / `log:isTriple` (extensions)
1230
+
1231
+ Convenience *test* builtins (useful for SHACL/Sparql-style mappings). They **succeed** when the test holds and typically use `true` as the object.
1232
+
1233
+ - **Shapes:**
1234
+ - `$t+ log:isIRI true .`
1235
+ - `$t+ log:isLiteral true .`
1236
+ - `$t+ log:isBlank true .`
1237
+ - `$t+ log:isNumeric true .`
1238
+ - `$t+ log:isTriple true .` (formula with exactly one triple)
1239
+
1240
+ These are treated as **constraints** for rule-body ordering.
1241
+
1242
+
1227
1243
  ### Literal constructors
1228
1244
 
1229
1245
  These two are classic N3 “bridge” operators between structured data and concrete RDF literal forms.
@@ -1321,6 +1337,19 @@ This is treated as a constraint builtin.
1321
1337
 
1322
1338
  Deterministically maps a *ground* term to a Skolem IRI in Eyeling’s well-known namespace. This is extremely useful when you want a repeatable identifier derived from structured content.
1323
1339
 
1340
+
1341
+
1342
+ #### `log:uuid` / `log:struuid` (extensions)
1343
+
1344
+ Generate fresh UUID values.
1345
+
1346
+ - **Shapes:**
1347
+ - `"" log:uuid ?u .` binds `?u` to an IRI like `<urn:uuid:...>`.
1348
+ - `"" log:struuid ?s .` binds `?s` to a UUID string literal.
1349
+
1350
+ **Warning:** These are non-deterministic and can affect termination; prefer `log:skolem` for deterministic identifiers.
1351
+
1352
+
1324
1353
  #### `log:uri`
1325
1354
 
1326
1355
  Bidirectional conversion between IRIs and their string form:
@@ -1354,7 +1383,7 @@ This is treated as a constraint builtin (it shouldn’t drive search; it should
1354
1383
 
1355
1384
  ---
1356
1385
 
1357
- ## 11.3.6 `string:` — string casting, tests, and regexes
1386
+ ## 11.3.6 `string:` — string casting, tests, regexes, and JSON pointers
1358
1387
 
1359
1388
  Eyeling implements string builtins with a deliberate interpretation of “domain is `xsd:string`”:
1360
1389
 
@@ -1385,6 +1414,35 @@ A tiny `sprintf` subset:
1385
1414
  * Any other specifier (`%d`, `%f`, …) causes the builtin to fail.
1386
1415
  * Missing arguments are treated as empty strings.
1387
1416
 
1417
+
1418
+
1419
+ #### `string:length` (extension)
1420
+
1421
+ Length of the subject string, like SPARQL `STRLEN`.
1422
+
1423
+ - **Shape:** `$s+ string:length $o-`
1424
+ - **Output:** `$o` is unified/bound to an integer token.
1425
+
1426
+ #### `string:substring` (extension)
1427
+
1428
+ Substring, like SPARQL `SUBSTR`.
1429
+
1430
+ - **Shape:** `( $s+ $start+ [$len+] ) string:substring $o-`
1431
+ - **Notes:** `start` is **1-based**. `len` is optional; if omitted, the substring runs to the end.
1432
+
1433
+ #### `string:upperCase` / `string:lowerCase` (extension)
1434
+
1435
+ Case conversion, like SPARQL `UCASE` / `LCASE`.
1436
+
1437
+ - **Shape:** `$s+ string:upperCase $o-` and `$s+ string:lowerCase $o-`
1438
+
1439
+ #### `string:encodeForURI` (extension)
1440
+
1441
+ Percent-encode a string, like SPARQL `ENCODE_FOR_URI`.
1442
+
1443
+ - **Shape:** `$s+ string:encodeForURI $o-`
1444
+
1445
+
1388
1446
  ### Containment and prefix/suffix tests (constraints)
1389
1447
 
1390
1448
  * `string:contains`
@@ -1438,6 +1496,34 @@ Tests whether `pattern` matches `data`.
1438
1496
 
1439
1497
  Matches the regex once and returns the **first capturing group** (group 1). If there is no match or no group, it fails.
1440
1498
 
1499
+ ### JSON pointer lookup
1500
+
1501
+ #### `string:jsonPointer`
1502
+
1503
+ **Shape:**
1504
+ `( jsonText pointer ) string:jsonPointer value`
1505
+
1506
+ This builtin is intentionally “bridgey”: it lets you reach into JSON and get back an RDF/N3 term.
1507
+
1508
+ Rules:
1509
+
1510
+ * `jsonText` must be an `rdf:JSON` literal (Eyeling is permissive and may accept a couple of equivalent datatype spellings).
1511
+ * `pointer` is a string; Eyeling supports:
1512
+
1513
+ * standard RFC 6901 pointers like `/a/b/0`
1514
+ * URI fragment form like `#/a/b` (it is decoded first)
1515
+ * The JSON is parsed and cached; pointer results are cached per `(jsonText, pointer)`.
1516
+
1517
+ Returned terms follow Eyeling’s `jsonToTerm` mapping:
1518
+
1519
+ * JSON `null` → `"null"` (a plain string literal)
1520
+ * JSON string → plain string literal
1521
+ * JSON number → numeric token literal (untyped)
1522
+ * JSON boolean → `true` / `false` token literal (untyped boolean token)
1523
+ * JSON array → an N3 list term whose elements are recursively converted
1524
+ * JSON object → an `rdf:JSON` literal containing the object’s JSON text
1525
+
1526
+ This design keeps the builtin total and predictable even for nested structures.
1441
1527
 
1442
1528
  ## 11.4 `log:outputString` as a controlled side effect
1443
1529
 
@@ -0,0 +1,11 @@
1
+ @prefix : <http://example/#> .
2
+ @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
3
+ @prefix string: <http://www.w3.org/2000/10/swap/string#> .
4
+ @prefix time: <http://www.w3.org/2000/10/swap/time#> .
5
+ @prefix log: <http://www.w3.org/2000/10/swap/log#> .
6
+ @prefix math: <http://www.w3.org/2000/10/swap/math#> .
7
+ @prefix crypto: <http://www.w3.org/2000/10/swap/crypto#> .
8
+
9
+ :p1 :givenName "Ada" ; :familyName "Lovelace" ; :born "1815-12-10T00:00:00Z"^^xsd:dateTime .
10
+
11
+ { ?p :givenName ?g . ?p :familyName ?f . ?p :born ?d . (?g " " ?f) string:concatenation ?full . (?full " " "_") string:replace ?lbl . (?lbl "en") log:langlit ?lblEn . ?lbl crypto:sha ?h . ?d time:year ?y . (?y 1) math:sum ?__e1 . (?__e1 2) math:product ?z . ("42" xsd:integer) log:dtlit ?i . } => { ?p :label ?lbl ; :labelEn ?lblEn ; :hash ?h ; :birthYear ?y ; :calc ?z ; :intLit ?i . } .
@@ -0,0 +1,7 @@
1
+ @prefix : <http://example/#> .
2
+ @prefix log: <http://www.w3.org/2000/10/swap/log#> .
3
+ @prefix string: <http://www.w3.org/2000/10/swap/string#> .
4
+
5
+ :phayes a :Person ; :givenName "Pat"; :familyName "Hayes" .
6
+
7
+ { ?x a :Person . ?SCOPE log:notIncludes { ?x :name ?someName . } . ?x :givenName ?name1 . ?x :familyName ?name2 . (?name1 " " ?name2) string:concatenation ?FN . } => { ?x :name ?FN } .
@@ -0,0 +1,119 @@
1
+ # ==================
2
+ # Brussels Brew Club
3
+ # ==================
4
+ #
5
+ # A tiny loyalty + compliance reasoner with:
6
+ # - JSON profile parsing (rdf:JSON + string:jsonPointer)
7
+ # - backward rules (<=) used during forward chaining
8
+ # - stable IDs with log:skolem
9
+ # - timestamps with time:localTime
10
+
11
+ @prefix : <http://example.org/brewclub#> .
12
+ @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
13
+ @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
14
+ @prefix log: <http://www.w3.org/2000/10/swap/log#> .
15
+ @prefix math: <http://www.w3.org/2000/10/swap/math#> .
16
+ @prefix string:<http://www.w3.org/2000/10/swap/string#> .
17
+ @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
18
+
19
+ # ---------------------------
20
+ # Deterministic "run context"
21
+ # ---------------------------
22
+
23
+ :run :now "2000-01-01T00:00:00+00:00"^^xsd:dateTime .
24
+
25
+ # -----
26
+ # Facts
27
+ # -----
28
+
29
+ :alice :profileJson """{ "name": "Alice", "age": 23, "city": "Brussels" }"""^^rdf:JSON .
30
+ :bob :profileJson """{ "name": "Bob", "age": 16, "city": "Brussels" }"""^^rdf:JSON .
31
+
32
+ :order1 a :Order ;
33
+ :id "ORD-2025-001" ;
34
+ :by :alice ;
35
+ :total "31.50"^^xsd:decimal ;
36
+ :contains :Espresso, :Croissant .
37
+
38
+ :order2 a :Order ;
39
+ :id "ORD-2025-002" ;
40
+ :by :bob ;
41
+ :total "12.00"^^xsd:decimal ;
42
+ :contains :HotChocolate .
43
+
44
+ :Beer a :AlcoholicDrink .
45
+ :Espresso a :Drink .
46
+ :HotChocolate a :Drink .
47
+ :Croissant a :Food .
48
+
49
+ # -----
50
+ # Rules
51
+ # -----
52
+
53
+ # 1) Extract fields from JSON profile into normal triples
54
+ {
55
+ ?p :profileJson ?j .
56
+ (?j "/age") string:jsonPointer ?age .
57
+ (?j "/name") string:jsonPointer ?name .
58
+ (?j "/city") string:jsonPointer ?city .
59
+ }
60
+ =>
61
+ {
62
+ ?p :age ?age ;
63
+ :name ?name ;
64
+ :city ?city .
65
+ }.
66
+
67
+ # 2) Backward rule: Adult is defined by age > 17
68
+ { ?p a :Adult }
69
+ <=
70
+ { ?p :age ?a . ?a math:greaterThan 17 }.
71
+
72
+ # 3) Backward rule: BigOrder is defined by total > 25.00
73
+ { ?o a :BigOrder }
74
+ <=
75
+ { ?o :total ?t . ?t math:greaterThan "25.00"^^xsd:decimal }.
76
+
77
+ # 4) Deterministic membership card rule:
78
+ # Uses :run :now ?now instead of time:localTime ?now
79
+ {
80
+ ?p a :Adult .
81
+ ?p :name ?name .
82
+ :run :now ?now .
83
+
84
+ (?p) log:skolem ?card .
85
+ ("Brew Club card for " ?name) string:concatenation ?label .
86
+ }
87
+ =>
88
+ {
89
+ ?card a :MembershipCard ;
90
+ :holder ?p ;
91
+ :issuedAt ?now ;
92
+ rdfs:label ?label .
93
+ }.
94
+
95
+ # 5) Discounts: BigOrder + Adult -> discount eligibility
96
+ {
97
+ ?o a :Order .
98
+ ?o a :BigOrder .
99
+ ?o :by ?p .
100
+ ?p a :Adult .
101
+ }
102
+ =>
103
+ {
104
+ ?o :eligibleDiscount "true"^^xsd:boolean .
105
+ }.
106
+
107
+ # 6) Stable receipt IRI from the order id
108
+ {
109
+ ?o a :Order .
110
+ ?o :id ?id .
111
+ (?id) log:skolem ?receipt .
112
+ }
113
+ =>
114
+ {
115
+ ?receipt a :Receipt ;
116
+ :forOrder ?o ;
117
+ :receiptId ?id .
118
+ }.
119
+
@@ -0,0 +1,11 @@
1
+ @prefix : <http://example/#> .
2
+ @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
3
+ @prefix string: <http://www.w3.org/2000/10/swap/string#> .
4
+ @prefix time: <http://www.w3.org/2000/10/swap/time#> .
5
+ @prefix math: <http://www.w3.org/2000/10/swap/math#> .
6
+
7
+ :x :num 1.2 ;
8
+ :txt "Hello World" ;
9
+ :dt "2020-01-01T13:14:15Z"^^xsd:dateTime .
10
+
11
+ { :x :num ?v . :x :txt ?s . :x :dt ?dt . ?v math:ceiling ?c . ?v math:floor ?f . (?s 1 5) string:substring ?sub . ?s string:length ?len . ?s string:upperCase ?up . ?s string:lowerCase ?lo . ?s string:encodeForURI ?enc . ?dt time:hour ?h . } => { :x :ceil ?c ; :floor ?f ; :substr ?sub ; :strlen ?len ; :ucase ?up ; :lcase ?lo ; :encoded ?enc ; :hours ?h . } .
@@ -0,0 +1,7 @@
1
+ @prefix : <http://example/#> .
2
+ @prefix log: <http://www.w3.org/2000/10/swap/log#> .
3
+
4
+ :alice :knows :bob .
5
+ :bob :age 42 .
6
+
7
+ { :alice :knows :bob . ?t log:isTriple true . ?s log:isIRI true . ?t log:equalTo { :alice :knows :bob . } . ?t log:includes { ?s ?__e2 ?__e3 . } . ?t log:includes { ?__e4 ?p ?__e6 . } . ?t log:includes { ?__e7 ?__e8 ?o . } . } => { :test :triple ?t ; :subject ?s ; :predicate ?p ; :object ?o ; :subjectIsIRI true ; :tripleOk true . } .
@@ -0,0 +1,10 @@
1
+ @prefix : <http://example.org/#> .
2
+
3
+ :A :fatherOf :X . :B :motherOf :X . :C :motherOf :A .
4
+
5
+ { ?y :fatherOf ?x . } => { ?x :childOf ?y } .
6
+ { ?y :motherOf ?x . } => { ?x :childOf ?y } .
7
+ { ?x :childOf ?y . } => { ?x :descendedFrom ?y } .
8
+ { ?x :childOf ?z . ?z :childOf ?y . } => { ?x :descendedFrom ?y } .
9
+ { ?y :descendedFrom ?x . } => { ?x :ancestorOf ?y } .
10
+ { ?a :ancestorOf ?c . ?c :ancestorOf ?b . } => { ?a :ancestorOf ?b } .
@@ -0,0 +1,9 @@
1
+ @prefix : <http://example/#> .
2
+ @prefix string: <http://www.w3.org/2000/10/swap/string#> .
3
+ @prefix log: <http://www.w3.org/2000/10/swap/log#> .
4
+
5
+ :alice :name "Alice" .
6
+ :bob :name "Bob" .
7
+ :eve :name "Eve" .
8
+
9
+ { ?p :name ?n . ?SCOPE log:notIncludes { ?n string:contains "o" . } . ?SCOPE log:notIncludes { ?n string:startsWith "E" . } . } => { ?p :passesNameFilter true . } .
@@ -0,0 +1,10 @@
1
+ @prefix : <http://example/#> .
2
+ @prefix log: <http://www.w3.org/2000/10/swap/log#> .
3
+ @prefix list: <http://www.w3.org/2000/10/swap/list#> .
4
+
5
+ :a :score 1 .
6
+ :b :score 2 .
7
+ :c :score 3 .
8
+ :d :score 4 .
9
+
10
+ { ?x :score ?s . ?s list:in (1 3 5) . ?SCOPE log:notIncludes { ?s list:in (2 4) . } . } => { ?x :isSpecial true . } .
@@ -0,0 +1,10 @@
1
+ @prefix : <http://example/#> .
2
+ @prefix math: <http://www.w3.org/2000/10/swap/math#> .
3
+ @prefix string: <http://www.w3.org/2000/10/swap/string#> .
4
+
5
+ :bob :age 20 ; :name "Bob" .
6
+ :alice :age 15 ; :name "Alice" .
7
+ :bea :age 22 ; :name "Bea" .
8
+
9
+ { ?p :age ?age . ?p :name ?name . ?age math:notLessThan 18 . ?name string:startsWith "A" . } => { ?p :adultNamedByRule true . } .
10
+ { ?p :age ?age . ?p :name ?name . ?age math:notLessThan 18 . ?name string:matches "^B" . } => { ?p :adultNamedByRule true . } .
@@ -0,0 +1,8 @@
1
+ @prefix : <http://example/#> .
2
+ @prefix math: <http://www.w3.org/2000/10/swap/math#> .
3
+
4
+ :x :p 1 ; :q 2 .
5
+
6
+ { ?x :p ?v1 . ?x :q ?v2 . ?v1 math:greaterThan 0 . ?v2 math:greaterThan 0 . } => { ?x :bothPositive true . } .
7
+ { ?x :p ?v1 . ?x :q ?v2 . ?v1 math:equalTo 0 . } => { ?x :oneIsZero true . } .
8
+ { ?x :p ?v1 . ?x :q ?v2 . ?v2 math:equalTo 0 . } => { ?x :oneIsZero true . } .
@@ -0,0 +1,30 @@
1
+ PREFIX : <http://example/#>
2
+ PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
3
+
4
+ DATA {
5
+ # NOTE: Eyeling's time:* extractors currently operate on xsd:dateTime (not xsd:date).
6
+ :p1 :givenName "Ada" ; :familyName "Lovelace" ; :born "1815-12-10T00:00:00Z"^^xsd:dateTime .
7
+ }
8
+
9
+ # Demonstrates BIND compilation to N3 builtins:
10
+ # - concat/replace (string:)
11
+ # - sha1 (crypto:)
12
+ # - year (time:)
13
+ # - arithmetic (+,*) (math:)
14
+ # - strlang/strdt (log:)
15
+ RULE {
16
+ ?p :label ?lbl ; :labelEn ?lblEn ; :hash ?h ; :birthYear ?y ; :calc ?z ; :intLit ?i .
17
+ }
18
+ WHERE {
19
+ ?p :givenName ?g ; :familyName ?f ; :born ?d .
20
+
21
+ BIND(concat(?g, " ", ?f) AS ?full)
22
+ BIND(replace(?full, " ", "_") AS ?lbl)
23
+ BIND(strlang(?lbl, "en") AS ?lblEn)
24
+ BIND(sha1(?lbl) AS ?h)
25
+
26
+ BIND(year(?d) AS ?y)
27
+ BIND((?y + 1) * 2 AS ?z)
28
+
29
+ BIND(strdt("42", xsd:integer) AS ?i)
30
+ }
@@ -0,0 +1,12 @@
1
+ PREFIX : <http://example/#>
2
+
3
+ DATA { :phayes a :Person ; :givenName "Pat"; :familyName "Hayes" . }
4
+
5
+ # Default value - calculate a name
6
+ RULE { ?x :name ?FN } WHERE {
7
+ ?x a :Person
8
+ NOT { ?x :name ?someName }
9
+ ?x :givenName ?name1 ;
10
+ :familyName ?name2 .
11
+ BIND(concat(?name1, " ", ?name2) AS ?FN)
12
+ }
@@ -0,0 +1,38 @@
1
+ PREFIX : <http://example/#>
2
+ PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
3
+
4
+ DATA {
5
+ :x :num 1.2 ;
6
+ :txt "Hello World" ;
7
+ :dt "2020-01-01T13:14:15Z"^^xsd:dateTime .
8
+ }
9
+
10
+ RULE {
11
+ :x :ceil ?c ;
12
+ :floor ?f ;
13
+ :substr ?sub ;
14
+ :strlen ?len ;
15
+ :ucase ?up ;
16
+ :lcase ?lo ;
17
+ :encoded ?enc ;
18
+ :hours ?h .
19
+ }
20
+ WHERE {
21
+ :x :num ?v ;
22
+ :txt ?s ;
23
+ :dt ?dt .
24
+
25
+ BIND(CEIL(?v) AS ?c)
26
+ BIND(FLOOR(?v) AS ?f)
27
+
28
+ # SPARQL SUBSTR is 1-based; this should produce "Hello"
29
+ BIND(SUBSTR(?s, 1, 5) AS ?sub)
30
+
31
+ BIND(STRLEN(?s) AS ?len)
32
+ BIND(UCASE(?s) AS ?up)
33
+ BIND(LCASE(?s) AS ?lo)
34
+ BIND(ENCODE_FOR_URI(?s) AS ?enc)
35
+
36
+ BIND(HOURS(?dt) AS ?h)
37
+ }
38
+
@@ -0,0 +1,27 @@
1
+ PREFIX : <http://example/#>
2
+
3
+ DATA {
4
+ :alice :knows :bob .
5
+ :bob :age 42 .
6
+ }
7
+
8
+ RULE {
9
+ :test :triple ?t ;
10
+ :subject ?s ;
11
+ :predicate ?p ;
12
+ :object ?o ;
13
+ :subjectIsIRI true ;
14
+ :tripleOk true .
15
+ }
16
+ WHERE {
17
+ :alice :knows :bob .
18
+
19
+ BIND(TRIPLE(:alice, :knows, :bob) AS ?t)
20
+ BIND(SUBJECT(?t) AS ?s)
21
+ BIND(PREDICATE(?t) AS ?p)
22
+ BIND(OBJECT(?t) AS ?o)
23
+
24
+ # Term-test builtins (as in SPARQL)
25
+ FILTER ( isTRIPLE(?t) && isIRI(?s) )
26
+ }
27
+
@@ -0,0 +1,12 @@
1
+ PREFIX : <http://example.org/#>
2
+
3
+ DATA { :A :fatherOf :X . :B :motherOf :X . :C :motherOf :A . }
4
+
5
+ RULE { ?x :childOf ?y } WHERE { ?y :fatherOf ?x }
6
+ RULE { ?x :childOf ?y } WHERE { ?y :motherOf ?x }
7
+
8
+ RULE { ?x :descendedFrom ?y } WHERE { ?x :childOf ?y }
9
+ RULE { ?x :descendedFrom ?y } WHERE { ?x :childOf ?z . ?z :childOf ?y }
10
+
11
+ RULE { ?x :ancestorOf ?y } WHERE { ?y :descendedFrom ?x }
12
+ RULE { ?a :ancestorOf ?b } WHERE { ?a :ancestorOf ?c . ?c :ancestorOf ?b }
@@ -0,0 +1,15 @@
1
+ PREFIX : <http://example/#>
2
+
3
+ DATA {
4
+ :alice :name "Alice" .
5
+ :bob :name "Bob" .
6
+ :eve :name "Eve" .
7
+ }
8
+
9
+ # Demonstrates De Morgan push-down:
10
+ # !(A || B) ==> (!A && !B)
11
+ RULE { ?p :passesNameFilter true . }
12
+ WHERE {
13
+ ?p :name ?n .
14
+ FILTER ( !( CONTAINS(?n, "o") || STRSTARTS(?n, "E") ) )
15
+ }
@@ -0,0 +1,15 @@
1
+ PREFIX : <http://example/#>
2
+
3
+ DATA {
4
+ :a :score 1 .
5
+ :b :score 2 .
6
+ :c :score 3 .
7
+ :d :score 4 .
8
+ }
9
+
10
+ # Demonstrates IN/NOT IN (mapped to list:in + log:notIncludes)
11
+ RULE { ?x :isSpecial true . }
12
+ WHERE {
13
+ ?x :score ?s .
14
+ FILTER ( (?s IN (1, 3, 5)) && (?s NOT IN (2, 4)) )
15
+ }
@@ -0,0 +1,15 @@
1
+ PREFIX : <http://example/#>
2
+
3
+ DATA {
4
+ :bob :age 20 ; :name "Bob" .
5
+ :alice :age 15 ; :name "Alice" .
6
+ :bea :age 22 ; :name "Bea" .
7
+ }
8
+
9
+ # Demonstrates nested OR distribution:
10
+ # ?age >= 18 && ( STRSTARTS(?name,"A") || REGEX(?name,"^B") )
11
+ RULE { ?p :adultNamedByRule true . }
12
+ WHERE {
13
+ ?p :age ?age ; :name ?name .
14
+ FILTER ( ?age >= 18 && ( STRSTARTS(?name, "A") || REGEX(?name, "^B") ) )
15
+ }
@@ -0,0 +1,9 @@
1
+ PREFIX : <http://example/#>
2
+
3
+ DATA { :x :p 1 ; :q 2 . }
4
+
5
+ RULE { ?x :bothPositive true . }
6
+ WHERE { ?x :p ?v1 FILTER ( ?v1 > 0 ) ?x :q ?v2 FILTER ( ?v2 > 0 ) }
7
+
8
+ RULE { ?x :oneIsZero true . }
9
+ WHERE { ?x :p ?v1 ; :q ?v2 FILTER ( ( ?v1 = 0 ) || ( ?v2 = 0 ) ) }
@@ -0,0 +1,6 @@
1
+ PREFIX log: <http://www.w3.org/2000/10/swap/log#>
2
+ PREFIX : <http://example.org/#>
3
+
4
+ DATA { :Alice :loves :Bob. :Bob a :Person. }
5
+
6
+ RULE { :Alice :hates :Nobody. } WHERE { NOT { :Alice :hates ?X } ?X a :Person. }
@@ -0,0 +1,75 @@
1
+ # =================================================
2
+ # JSON Pointer example
3
+ # See https://datatracker.ietf.org/doc/html/rfc6901
4
+ # =================================================
5
+
6
+ @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
7
+ @prefix string: <http://www.w3.org/2000/10/swap/string#> .
8
+ @prefix list: <http://www.w3.org/2000/10/swap/list#> .
9
+ @prefix log: <http://www.w3.org/2000/10/swap/log#> .
10
+ @prefix ex: <http://example.org/#> .
11
+
12
+ ex:doc ex:json """{
13
+ "users": [
14
+ { "id": "u1", "email": "ada@example.org", "profile/name": "Ada Lovelace" },
15
+ { "id": "u2", "email": "bob@evil.invalid", "profile/name": "Bob Mallory" }
16
+ ],
17
+ "policy": { "allowedDomains": ["example.org", "example.com"] }
18
+ }"""^^rdf:JSON .
19
+
20
+ # Sanity check: key contains "/" so JSON Pointer must use "~1"
21
+ {
22
+ ex:doc ex:json ?J .
23
+ (?J "/users/0/profile~1name") string:jsonPointer "Ada Lovelace" .
24
+ } => {
25
+ ex:checks ex:firstUserNameOk true .
26
+ } .
27
+
28
+ # Allowed users: email domain is in policy.allowedDomains
29
+ {
30
+ ex:doc ex:json ?J .
31
+ (?J "/policy/allowedDomains") string:jsonPointer ?Allowed .
32
+ (?J "/users") string:jsonPointer ?Users .
33
+
34
+ ?Users list:iterate (?Idx ?UserJson) .
35
+ (?UserJson "/id") string:jsonPointer ?Id .
36
+ (?UserJson "/email") string:jsonPointer ?Email .
37
+ (?UserJson "/profile~1name") string:jsonPointer ?Name .
38
+
39
+ (?Email "@([^@]+)$") string:scrape ?EmailDomain .
40
+ ?Allowed list:member ?EmailDomain .
41
+
42
+ ("urn:example:user:%s" ?Id) string:format ?UriStr .
43
+ ?User log:uri ?UriStr .
44
+ } => {
45
+ ?User a ex:AllowedUser ;
46
+ ex:name ?Name ;
47
+ ex:email ?Email ;
48
+ ex:emailDomain ?EmailDomain ;
49
+ ex:userIndex ?Idx .
50
+ } .
51
+
52
+ # Blocked users: email domain is NOT in the allowlist
53
+ {
54
+ ex:doc ex:json ?J .
55
+ (?J "/policy/allowedDomains") string:jsonPointer ?Allowed .
56
+ (?J "/users") string:jsonPointer ?Users .
57
+
58
+ ?Users list:iterate (?Idx ?UserJson) .
59
+ (?UserJson "/id") string:jsonPointer ?Id .
60
+ (?UserJson "/email") string:jsonPointer ?Email .
61
+ (?UserJson "/profile~1name") string:jsonPointer ?Name .
62
+
63
+ (?Email "@([^@]+)$") string:scrape ?EmailDomain .
64
+ ?Allowed list:notMember ?EmailDomain .
65
+
66
+ ("urn:example:user:%s" ?Id) string:format ?UriStr .
67
+ ?User log:uri ?UriStr .
68
+ } => {
69
+ ?User a ex:BlockedUser ;
70
+ ex:name ?Name ;
71
+ ex:email ?Email ;
72
+ ex:emailDomain ?EmailDomain ;
73
+ ex:userIndex ?Idx .
74
+ } .
75
+