eyeling 1.7.14 → 1.7.16

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 CHANGED
@@ -8,6 +8,7 @@ A [Notation3 (N3)](https://notation3.org/) reasoner in **JavaScript**.
8
8
  - a practical N3/Turtle superset (enough for lots of real rulesets)
9
9
  - supports forward (`=>`) + backward (`<=`) chaining over Horn-style rules
10
10
  - prints only newly derived forward facts, optionally preceded by compact proof comments
11
+ - can report derived triples as they are produced (streaming callback via `reasonStream`)
11
12
  - “pass-only-new” style output (we never want to leak raw input data; backward rules can act like “functions” over raw data)
12
13
  - works fully client-side (browser) and in Node.js
13
14
 
@@ -16,6 +17,7 @@ A [Notation3 (N3)](https://notation3.org/) reasoner in **JavaScript**.
16
17
  Try it here:
17
18
 
18
19
  - [Eyeling playground](https://eyereasoner.github.io/eyeling/demo)
20
+ - [Eyeling streaming playground](https://eyereasoner.github.io/eyeling/stream)
19
21
 
20
22
  The playground runs `eyeling` client-side. You can:
21
23
 
@@ -72,6 +74,15 @@ const output = eyeling.reason({ proofComments: false }, input);
72
74
  console.log(output);
73
75
  ```
74
76
 
77
+ Streaming (browser/worker, direct `eyeling.js`):
78
+
79
+ ```js
80
+ const { closureN3 } = eyeling.reasonStream(input, {
81
+ proof: false,
82
+ onDerived: ({ triple }) => console.log(triple),
83
+ });
84
+ ```
85
+
75
86
  Note: the API currently shells out to the bundled `eyeling.js` CLI under the hood (simple + robust).
76
87
 
77
88
  ## Testing
@@ -216,10 +227,10 @@ Commonly used N3/Turtle features:
216
227
 
217
228
  - **crypto**: `crypto:md5` `crypto:sha` `crypto:sha256` `crypto:sha512`
218
229
  - **list**: `list:append` `list:first` `list:firstRest` `list:in` `list:iterate` `list:last` `list:length` `list:map` `list:member` `list:memberAt` `list:notMember` `list:remove` `list:rest` `list:reverse` `list:sort`
219
- - **log**: `log:collectAllIn` `log:content` `log:dtlit` `log:equalTo` `log:forAllIn` `log:impliedBy` `log:implies` `log:includes` `log:langlit` `log:notEqualTo` `log:notIncludes` `log:outputString` `log:parsedAsN3` `log:rawType` `log:semantics` `log:semanticsOrError` `log:skolem` `log:uri`
230
+ - **log**: `log:collectAllIn` `log:content` `log:dtlit` `log:equalTo` `log:forAllIn` `log:impliedBy` `log:implies` `log:includes` `log:langlit` `log:notEqualTo` `log:notIncludes` `log:outputString` `log:parsedAsN3` `log:rawType` `log:semantics` `log:semanticsOrError` `log:skolem` `log:trace` `log:uri`
220
231
  - **math**: `math:absoluteValue` `math:acos` `math:asin` `math:atan` `math:cos` `math:cosh` `math:degrees` `math:difference` `math:equalTo` `math:exponentiation` `math:greaterThan` `math:integerQuotient` `math:lessThan` `math:negation` `math:notEqualTo` `math:notGreaterThan` `math:notLessThan` `math:product` `math:quotient` `math:remainder` `math:rounded` `math:sin` `math:sinh` `math:sum` `math:tan` `math:tanh`
221
232
  - **string**: `string:concatenation` `string:contains` `string:containsIgnoringCase` `string:endsWith` `string:equalIgnoringCase` `string:format` `string:greaterThan` `string:jsonPointer` `string:lessThan` `string:matches` `string:notEqualIgnoringCase` `string:notGreaterThan` `string:notLessThan` `string:notMatches` `string:replace` `string:scrape` `string:startsWith`
222
- - **time**: `time:day` `time:localTime` `time:minute` `time:month` `time:second` `time:timeZone` `time:year`
233
+ - **time**: `time:day` `time:hour` `time:localTime` `time:minute` `time:month` `time:second` `time:timeZone` `time:year`
223
234
 
224
235
  ## License
225
236
 
@@ -0,0 +1,338 @@
1
+ @prefix ex: <https://eyereasoner.github.io/eyeling/vocab#> .
2
+ @prefix crypto: <http://www.w3.org/2000/10/swap/crypto#> .
3
+ @prefix math: <http://www.w3.org/2000/10/swap/math#> .
4
+ @prefix time: <http://www.w3.org/2000/10/swap/time#> .
5
+ @prefix list: <http://www.w3.org/2000/10/swap/list#> .
6
+ @prefix log: <http://www.w3.org/2000/10/swap/log#> .
7
+ @prefix string: <http://www.w3.org/2000/10/swap/string#> .
8
+ @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
9
+ @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
10
+ @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
11
+
12
+ # ------------------------------------------------------------------------------
13
+ # Builtin catalog (eyeling.js)
14
+ #
15
+ # This file lists the SWAP-style builtins that eyeling.js currently implements
16
+ # in evalBuiltin(), plus rdf:first/rdf:rest aliases for list:first/list:rest.
17
+ #
18
+ # Notes:
19
+ # - ex:kind is intentionally coarse:
20
+ # ex:Test = succeeds/fails, no new variable bindings (a "constraint")
21
+ # ex:Function = computes an output and may bind variables
22
+ # ex:Relation = unification-based, may bind variables
23
+ # ex:Generator = may yield multiple solutions
24
+ # ex:IO = dereferences/parses external content (Node or browser)
25
+ # ex:Meta = formula/type/meta-level operations
26
+ # ex:SideEffect= produces output; does not bind variables
27
+ # - ex:isConstraint corresponds to isConstraintBuiltin(tr) in eyeling.js, with
28
+ # one suggested addition noted in the comments below.
29
+ # ------------------------------------------------------------------------------
30
+
31
+ ex:EyelingBuiltinCatalog a ex:Catalog ;
32
+ rdfs:comment "Builtins implemented by eyeling.js (main branch, raw GitHub URL). Namespaces follow SWAP (crypto/math/time/list/log/string) plus rdf:first/rest aliases." .
33
+
34
+ ex:Test a ex:Kind .
35
+ ex:Function a ex:Kind .
36
+ ex:Relation a ex:Kind .
37
+ ex:Generator a ex:Kind .
38
+ ex:IO a ex:Kind .
39
+ ex:Meta a ex:Kind .
40
+ ex:SideEffect a ex:Kind .
41
+
42
+ ex:isConstraint a rdf:Property ;
43
+ rdfs:comment "True for builtins classified as constraint/test builtins by isConstraintBuiltin(tr) (premise reordering helper)." .
44
+ ex:kind a rdf:Property .
45
+ ex:aliasOf a rdf:Property .
46
+
47
+ # --- crypto: ---------------------------------------------------------
48
+
49
+ crypto:sha a ex:Builtin ; ex:kind ex:Function ;
50
+ rdfs:comment "Hash builtin (SHA-1). Computes digest over the subject (string-ish) and unifies/binds the object with the digest." .
51
+
52
+ crypto:md5 a ex:Builtin ; ex:kind ex:Function ;
53
+ rdfs:comment "Hash builtin (MD5). Computes digest over the subject (string-ish) and unifies/binds the object with the digest." .
54
+
55
+ crypto:sha256 a ex:Builtin ; ex:kind ex:Function ;
56
+ rdfs:comment "Hash builtin (SHA-256). Computes digest over the subject (string-ish) and unifies/binds the object with the digest." .
57
+
58
+ crypto:sha512 a ex:Builtin ; ex:kind ex:Function ;
59
+ rdfs:comment "Hash builtin (SHA-512). Computes digest over the subject (string-ish) and unifies/binds the object with the digest." .
60
+
61
+ # --- math: comparisons (constraint/test) -----------------------------
62
+
63
+ math:equalTo a ex:Builtin ; ex:kind ex:Test ; ex:isConstraint true ;
64
+ rdfs:comment "Numeric comparison (=). No bindings; succeeds iff subject and object are numerically equal (supports XSD numeric literals, including special float/double lexicals)." .
65
+
66
+ math:notEqualTo a ex:Builtin ; ex:kind ex:Test ; ex:isConstraint true ;
67
+ rdfs:comment "Numeric comparison (!=). No bindings; succeeds iff subject and object are numerically different." .
68
+
69
+ math:greaterThan a ex:Builtin ; ex:kind ex:Test ; ex:isConstraint true ;
70
+ rdfs:comment "Numeric comparison (>). No bindings." .
71
+
72
+ math:lessThan a ex:Builtin ; ex:kind ex:Test ; ex:isConstraint true ;
73
+ rdfs:comment "Numeric comparison (<). No bindings." .
74
+
75
+ math:notLessThan a ex:Builtin ; ex:kind ex:Test ; ex:isConstraint true ;
76
+ rdfs:comment "Numeric comparison (>=). No bindings." .
77
+
78
+ math:notGreaterThan a ex:Builtin ; ex:kind ex:Test ; ex:isConstraint true ;
79
+ rdfs:comment "Numeric comparison (<=). No bindings." .
80
+
81
+ # --- math: arithmetic / numeric functions ----------------------------
82
+
83
+ math:sum a ex:Builtin ; ex:kind ex:Function ;
84
+ rdfs:comment "Sum of a list of 2+ numeric terms. Binds/unifies object with the total (integer mode uses BigInt when possible)." .
85
+
86
+ math:product a ex:Builtin ; ex:kind ex:Function ;
87
+ rdfs:comment "Product of a list of 2+ numeric terms. Binds/unifies object with the product (integer mode uses BigInt when possible)." .
88
+
89
+ math:difference a ex:Builtin ; ex:kind ex:Function ;
90
+ rdfs:comment "Difference of a 2-element list (a b). Supports datetime-datetime -> duration; datetime-duration -> datetime; integer BigInt; otherwise numeric a-b." .
91
+
92
+ math:quotient a ex:Builtin ; ex:kind ex:Function ;
93
+ rdfs:comment "Quotient of a 2-element list (a b). Binds/unifies object with a/b (guards division by zero and non-finite results)." .
94
+
95
+ math:integerQuotient a ex:Builtin ; ex:kind ex:Function ;
96
+ rdfs:comment "Integer quotient of (a b), truncating toward zero. Prefers BigInt integer division when possible." .
97
+
98
+ math:remainder a ex:Builtin ; ex:kind ex:Function ;
99
+ rdfs:comment "Remainder/modulus of (a b). Prefers exact BigInt remainder when possible; otherwise requires integer-valued Numbers." .
100
+
101
+ math:rounded a ex:Builtin ; ex:kind ex:Function ;
102
+ rdfs:comment "Rounds subject to nearest integer (JS tie-breaking: toward +∞). Binds/unifies object with the rounded integer value." .
103
+
104
+ math:exponentiation a ex:Builtin ; ex:kind ex:Function ;
105
+ rdfs:comment "Exponentiation. Forward: (base exponent) -> result. Limited inverse: if base is numeric and exponent is a variable, may solve exponent via logs for positive base != 1 and positive result." .
106
+
107
+ math:absoluteValue a ex:Builtin ; ex:kind ex:Function ;
108
+ rdfs:comment "Absolute value. Computes |s| and unifies/binds object; output datatype follows common numeric datatype selection." .
109
+
110
+ math:acos a ex:Builtin ; ex:kind ex:Function ;
111
+ rdfs:comment "acos relation (supports inverse via cos for supported cases)." .
112
+
113
+ math:asin a ex:Builtin ; ex:kind ex:Function ;
114
+ rdfs:comment "asin relation (supports inverse via sin for supported cases)." .
115
+
116
+ math:atan a ex:Builtin ; ex:kind ex:Function ;
117
+ rdfs:comment "atan relation (supports inverse via tan for supported cases)." .
118
+
119
+ math:sin a ex:Builtin ; ex:kind ex:Function ;
120
+ rdfs:comment "sin relation (inverse uses principal asin)." .
121
+
122
+ math:cos a ex:Builtin ; ex:kind ex:Function ;
123
+ rdfs:comment "cos relation (inverse uses principal acos)." .
124
+
125
+ math:tan a ex:Builtin ; ex:kind ex:Function ;
126
+ rdfs:comment "tan relation (inverse uses principal atan)." .
127
+
128
+ math:sinh a ex:Builtin ; ex:kind ex:Function ;
129
+ rdfs:comment "sinh relation (only if JS Math.sinh/Math.asinh exist)." .
130
+
131
+ math:cosh a ex:Builtin ; ex:kind ex:Function ;
132
+ rdfs:comment "cosh relation (only if JS Math.cosh/Math.acosh exist)." .
133
+
134
+ math:tanh a ex:Builtin ; ex:kind ex:Function ;
135
+ rdfs:comment "tanh relation (only if JS Math.tanh/Math.atanh exist)." .
136
+
137
+ math:degrees a ex:Builtin ; ex:kind ex:Function ;
138
+ rdfs:comment "Converts radians->degrees; inverse converts degrees->radians." .
139
+
140
+ math:negation a ex:Builtin ; ex:kind ex:Function ;
141
+ rdfs:comment "Numeric negation; inverse is itself." .
142
+
143
+ # --- time: -----------------------------------------------------------
144
+
145
+ time:day a ex:Builtin ; ex:kind ex:Function ;
146
+ rdfs:comment "Extracts day component from xsd:dateTime subject; binds/unifies object with integer token." .
147
+
148
+ time:hour a ex:Builtin ; ex:kind ex:Function ;
149
+ rdfs:comment "Extracts hour component from xsd:dateTime subject; binds/unifies object with integer token." .
150
+
151
+ time:minute a ex:Builtin ; ex:kind ex:Function ;
152
+ rdfs:comment "Extracts minute component from xsd:dateTime subject; binds/unifies object with integer token." .
153
+
154
+ time:month a ex:Builtin ; ex:kind ex:Function ;
155
+ rdfs:comment "Extracts month component from xsd:dateTime subject; binds/unifies object with integer token." .
156
+
157
+ time:second a ex:Builtin ; ex:kind ex:Function ;
158
+ rdfs:comment "Extracts second component from xsd:dateTime subject; binds/unifies object with integer token." .
159
+
160
+ time:timeZone a ex:Builtin ; ex:kind ex:Function ;
161
+ rdfs:comment "Extracts timezone suffix from xsd:dateTime subject (e.g., 'Z' or '-05:00'); binds/unifies object with a string literal." .
162
+
163
+ time:year a ex:Builtin ; ex:kind ex:Function ;
164
+ rdfs:comment "Extracts year component from xsd:dateTime subject; binds/unifies object with integer token." .
165
+
166
+ time:localTime a ex:Builtin ; ex:kind ex:Function ;
167
+ rdfs:comment "Binds object to 'now' as an xsd:dateTime literal (supports deterministic override via internal setting)." .
168
+
169
+ # --- list: (plus rdf:first/rest aliases) -----------------------------
170
+
171
+ list:append a ex:Builtin ; ex:kind ex:Function ;
172
+ rdfs:comment "Concatenates all subject lists; binds/unifies object with the concatenation. If object is a list, can also split." .
173
+
174
+ list:first a ex:Builtin ; ex:kind ex:Function ;
175
+ rdfs:comment "First element of a list. Supports open lists in limited cases." .
176
+
177
+ rdf:first a ex:Builtin ; ex:kind ex:Function ; ex:aliasOf list:first ;
178
+ rdfs:comment "Alias of list:first (RDF Collections support)." .
179
+
180
+ list:rest a ex:Builtin ; ex:kind ex:Function ;
181
+ rdfs:comment "Tail of a (non-empty) list. Supports open lists in limited cases." .
182
+
183
+ rdf:rest a ex:Builtin ; ex:kind ex:Function ; ex:aliasOf list:rest ;
184
+ rdfs:comment "Alias of list:rest (RDF Collections support)." .
185
+
186
+ list:iterate a ex:Builtin ; ex:kind ex:Generator ;
187
+ rdfs:comment "Multi-solution: for each element in the subject list, unifies object with (index value). Index is 0-based." .
188
+
189
+ list:last a ex:Builtin ; ex:kind ex:Function ;
190
+ rdfs:comment "Unifies/binds object with the last element of the subject list." .
191
+
192
+ list:memberAt a ex:Builtin ; ex:kind ex:Generator ;
193
+ rdfs:comment "Multi-solution: (list index) -> value. Index is 0-based; can bind index and/or value." .
194
+
195
+ list:remove a ex:Builtin ; ex:kind ex:Function ;
196
+ rdfs:comment "Removes all occurrences of an item from a list; requires the item be ground; binds/unifies object with filtered list." .
197
+
198
+ list:member a ex:Builtin ; ex:kind ex:Generator ;
199
+ rdfs:comment "Multi-solution: yields each list element as object (unification-based)." .
200
+
201
+ list:in a ex:Builtin ; ex:kind ex:Generator ;
202
+ rdfs:comment "Multi-solution: yields each list element as subject (unification-based)." .
203
+
204
+ list:length a ex:Builtin ; ex:kind ex:Function ;
205
+ rdfs:comment "Length of a list as an integer token. Strict when object is ground (no integer<->decimal equality)." .
206
+
207
+ list:notMember a ex:Builtin ; ex:kind ex:Test ; ex:isConstraint true ;
208
+ rdfs:comment "Constraint/test: succeeds iff object does not unify with any element of the subject list. No new bindings." .
209
+
210
+ list:reverse a ex:Builtin ; ex:kind ex:Function ;
211
+ rdfs:comment "Reverses a list. Supports either direction when one side is a ListTerm." .
212
+
213
+ list:sort a ex:Builtin ; ex:kind ex:Function ;
214
+ rdfs:comment "Sorts a ground list (numbers numerically when both sides parse as numbers; otherwise lexical; lists lexicographically). Requires all elements ground." .
215
+
216
+ list:map a ex:Builtin ; ex:kind ex:Function ;
217
+ rdfs:comment "Maps a predicate over a ground input list, collecting *all* solutions for each element and concatenating results into the output list." .
218
+
219
+ list:firstRest a ex:Builtin ; ex:kind ex:Function ;
220
+ rdfs:comment "Pairs/unpairs a list with (first rest). Supports constructing open lists in some cases." .
221
+
222
+ # --- log: ------------------------------------------------------------
223
+
224
+ log:equalTo a ex:Builtin ; ex:kind ex:Relation ;
225
+ rdfs:comment "Unification: succeeds iff subject and object unify; may bind variables." .
226
+
227
+ log:notEqualTo a ex:Builtin ; ex:kind ex:Test ; ex:isConstraint true ;
228
+ rdfs:comment "Constraint/test: succeeds iff subject and object do NOT unify (implemented as: if unification succeeds, builtin fails). No new bindings returned." .
229
+
230
+ log:conjunction a ex:Builtin ; ex:kind ex:Meta ;
231
+ rdfs:comment "Merges a list of formulas into one formula; removes duplicate triples; accepts 'true' as empty formula." .
232
+
233
+ log:conclusion a ex:Builtin ; ex:kind ex:Meta ;
234
+ rdfs:comment "Computes deductive closure of a formula (including rule inferences) and unifies/binds object with that formula." .
235
+
236
+ log:content a ex:Builtin ; ex:kind ex:IO ;
237
+ rdfs:comment "Dereferences subject IRI (fragment-stripped) and returns retrieved bytes as xsd:string." .
238
+
239
+ log:semantics a ex:Builtin ; ex:kind ex:IO ;
240
+ rdfs:comment "Dereferences and parses subject IRI as N3/Turtle into a formula." .
241
+
242
+ log:semanticsOrError a ex:Builtin ; ex:kind ex:IO ;
243
+ rdfs:comment "Like log:semantics, but returns an xsd:string error message term when dereference/parse fails." .
244
+
245
+ log:parsedAsN3 a ex:Builtin ; ex:kind ex:Meta ;
246
+ rdfs:comment "Parses an N3 string (xsd:string-ish) into a formula." .
247
+
248
+ log:rawType a ex:Builtin ; ex:kind ex:Meta ;
249
+ rdfs:comment "Returns one of log:Formula, log:Literal, rdf:List, or log:Other for the subject term." .
250
+
251
+ log:dtlit a ex:Builtin ; ex:kind ex:Function ;
252
+ rdfs:comment "Builds a datatype literal from (lex datatypeIri). Binds/unifies object with the resulting Literal." .
253
+
254
+ log:langlit a ex:Builtin ; ex:kind ex:Function ;
255
+ rdfs:comment "Builds a language-tagged literal from (lex lang). Binds/unifies object with the resulting Literal." .
256
+
257
+ log:implies a ex:Builtin ; ex:kind ex:Relation ;
258
+ rdfs:comment "Rule/formula relation used for =>. In superRestrictedMode, this and log:impliedBy are the only builtins treated as builtins." .
259
+
260
+ log:impliedBy a ex:Builtin ; ex:kind ex:Relation ;
261
+ rdfs:comment "Rule/formula relation used for <=." .
262
+
263
+ log:includes a ex:Builtin ; ex:kind ex:Generator ;
264
+ rdfs:comment "Proves the object formula in the (possibly scoped) facts/rules; returns the set of proof substitutions (may bind variables)." .
265
+
266
+ log:notIncludes a ex:Builtin ; ex:kind ex:Test ; ex:isConstraint true ;
267
+ rdfs:comment "Constraint/test: succeeds iff proving the object formula yields no solutions. No new bindings." .
268
+
269
+ log:collectAllIn a ex:Builtin ; ex:kind ex:Function ;
270
+ rdfs:comment "Scoped collector: given (valueTemplate whereClause outList) and a scope formula, collects all solutions of whereClause and binds/unifies outList with the list of instantiated valueTemplate results." .
271
+
272
+ log:forAllIn a ex:Builtin ; ex:kind ex:Test ; ex:isConstraint true ;
273
+ rdfs:comment "Constraint/test: for all solutions of whereClause, thenClause must have at least one solution in the same scope. No new bindings." .
274
+
275
+ log:skolem a ex:Builtin ; ex:kind ex:Function ;
276
+ rdfs:comment "Deterministically maps a ground subject term to a Skolem IRI under https://eyereasoner.github.io/.well-known/genid/ and binds/unifies object to it." .
277
+
278
+ log:uri a ex:Builtin ; ex:kind ex:Function ;
279
+ rdfs:comment "Converts between an IRI and its string representation (with safety checks on IRIREF characters)." .
280
+
281
+ log:trace a ex:Builtin ; ex:kind ex:SideEffect ;
282
+ rdfs:comment "Side-effect (debug tracing). Prints '<subject> TRACE <object>' to stderr (Node: process.stderr; browser: console.error). Always succeeds once; does not bind variables.
283
+
284
+ log:outputString a ex:Builtin ; ex:kind ex:SideEffect ;
285
+ rdfs:comment "Side-effect (printing). Requires ground subject and non-variable object string. Does not bind variables.
286
+
287
+ # --- string: (tests + functions) ------------------------------------
288
+
289
+ string:concatenation a ex:Builtin ; ex:kind ex:Function ;
290
+ rdfs:comment "Concatenates a list of string-ish terms; binds/unifies object with the resulting string literal." .
291
+
292
+ string:contains a ex:Builtin ; ex:kind ex:Test ; ex:isConstraint true ;
293
+ rdfs:comment "Constraint/test: subject contains object (substring). No bindings." .
294
+
295
+ string:containsIgnoringCase a ex:Builtin ; ex:kind ex:Test ; ex:isConstraint true ;
296
+ rdfs:comment "Constraint/test: case-insensitive contains. No bindings." .
297
+
298
+ string:endsWith a ex:Builtin ; ex:kind ex:Test ; ex:isConstraint true ;
299
+ rdfs:comment "Constraint/test: subject ends with object. No bindings." .
300
+
301
+ string:startsWith a ex:Builtin ; ex:kind ex:Test ; ex:isConstraint true ;
302
+ rdfs:comment "Constraint/test: subject starts with object. No bindings." .
303
+
304
+ string:equalIgnoringCase a ex:Builtin ; ex:kind ex:Test ; ex:isConstraint true ;
305
+ rdfs:comment "Constraint/test: case-insensitive equality. No bindings." .
306
+
307
+ string:notEqualIgnoringCase a ex:Builtin ; ex:kind ex:Test ; ex:isConstraint true ;
308
+ rdfs:comment "Constraint/test: case-insensitive inequality. No bindings." .
309
+
310
+ string:greaterThan a ex:Builtin ; ex:kind ex:Test ; ex:isConstraint true ;
311
+ rdfs:comment "Constraint/test: Unicode codepoint order (>) on decoded strings. No bindings." .
312
+
313
+ string:lessThan a ex:Builtin ; ex:kind ex:Test ; ex:isConstraint true ;
314
+ rdfs:comment "Constraint/test: Unicode codepoint order (<) on decoded strings. No bindings." .
315
+
316
+ string:notGreaterThan a ex:Builtin ; ex:kind ex:Test ; ex:isConstraint true ;
317
+ rdfs:comment "Constraint/test: Unicode codepoint order (<=). No bindings." .
318
+
319
+ string:notLessThan a ex:Builtin ; ex:kind ex:Test ; ex:isConstraint true ;
320
+ rdfs:comment "Constraint/test: Unicode codepoint order (>=). No bindings." .
321
+
322
+ string:matches a ex:Builtin ; ex:kind ex:Test ; ex:isConstraint true ;
323
+ rdfs:comment "Constraint/test: regex match. Pattern is compiled with swap-style escaping helper. No bindings." .
324
+
325
+ string:notMatches a ex:Builtin ; ex:kind ex:Test ; ex:isConstraint true ;
326
+ rdfs:comment "Constraint/test: negated regex match. No bindings." .
327
+
328
+ string:replace a ex:Builtin ; ex:kind ex:Function ;
329
+ rdfs:comment "Regex replace: subject list (data pattern replacement) -> output string literal." .
330
+
331
+ string:scrape a ex:Builtin ; ex:kind ex:Function ;
332
+ rdfs:comment "Regex scrape: subject list (data pattern) -> first capturing group (as string literal) if match succeeds." .
333
+
334
+ string:format a ex:Builtin ; ex:kind ex:Function ;
335
+ rdfs:comment "Simple formatter: (fmt arg1 ... argN) -> output string. Only %s and %% are supported; other specifiers fail." .
336
+
337
+ string:jsonPointer a ex:Builtin ; ex:kind ex:Function ;
338
+ rdfs:comment "JSON Pointer lookup: (jsonText pointer) -> value term. Expects rdf:JSON-typed literal (or treated as rdf:JSON); caches parsed JSON and pointer lookups." .
package/eyeling.js CHANGED
@@ -400,6 +400,27 @@ let proofCommentsEnabled = false;
400
400
  // Super restricted mode: disable *all* builtins except => / <= (log:implies / log:impliedBy)
401
401
  let superRestrictedMode = false;
402
402
 
403
+ // Debug/trace printing support (log:trace)
404
+ let __tracePrefixes = null;
405
+
406
+ function __traceWriteLine(line) {
407
+ // Prefer stderr in Node, fall back to console.error elsewhere.
408
+ try {
409
+ if (
410
+ __IS_NODE &&
411
+ typeof process !== 'undefined' &&
412
+ process.stderr &&
413
+ typeof process.stderr.write === 'function'
414
+ ) {
415
+ process.stderr.write(String(line) + '\n');
416
+ return;
417
+ }
418
+ } catch (_) {}
419
+ try {
420
+ if (typeof console !== 'undefined' && typeof console.error === 'function') console.error(line);
421
+ } catch (_) {}
422
+ }
423
+
403
424
  // ----------------------------------------------------------------------------
404
425
  // Deterministic time support
405
426
  // ----------------------------------------------------------------------------
@@ -4131,7 +4152,7 @@ function evalCryptoHashBuiltin(g, subst, algo) {
4131
4152
  // ===========================================================================
4132
4153
  // Backward proof & builtins mutual recursion — declarations first
4133
4154
 
4134
- function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
4155
+ function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
4135
4156
  const g = applySubstTriple(goal, subst);
4136
4157
  const pv = iriValue(g.p);
4137
4158
  if (pv === null) return null;
@@ -5648,7 +5669,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
5648
5669
 
5649
5670
  const visited2 = [];
5650
5671
  // Start from the incoming substitution so bindings flow outward.
5651
- return proveGoals(Array.from(g.o.triples), { ...subst }, scopeFacts, [], depth + 1, visited2, varGen);
5672
+ return proveGoals(Array.from(g.o.triples), { ...subst }, scopeFacts, [], depth + 1, visited2, varGen, maxResults);
5652
5673
  }
5653
5674
 
5654
5675
  // log:notIncludes
@@ -5669,10 +5690,25 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
5669
5690
  });
5670
5691
 
5671
5692
  const visited2 = [];
5672
- const sols = proveGoals(Array.from(g.o.triples), { ...subst }, scopeFacts, [], depth + 1, visited2, varGen);
5693
+ const sols = proveGoals(Array.from(g.o.triples), { ...subst }, scopeFacts, [], depth + 1, visited2, varGen, 1);
5673
5694
  return sols.length ? [] : [{ ...subst }];
5674
5695
  }
5675
5696
 
5697
+ // log:trace
5698
+ // Schema: $s? log:trace $o?
5699
+ // Side-effecting debug output (to stderr). Always succeeds once.
5700
+ // to mimic EYE's fm(...) formatting branch.
5701
+ if (pv === LOG_NS + 'trace') {
5702
+ const pref = __tracePrefixes || PrefixEnv.newDefault();
5703
+ const xStr = termToN3(g.s, pref);
5704
+
5705
+ const xNum = parseNum(g.s);
5706
+ const yStr = termToN3(g.o, pref);
5707
+
5708
+ __traceWriteLine(`${xStr} TRACE ${yStr}`);
5709
+ return [{ ...subst }];
5710
+ }
5711
+
5676
5712
  // log:outputString
5677
5713
  // Schema: $s+ log:outputString $o+
5678
5714
  // Side-effecting output directive. As a builtin goal, we simply succeed
@@ -6244,10 +6280,11 @@ function maybeCompactSubst(subst, goals, answerVars, depth) {
6244
6280
  return gcCompactForGoals(subst, goals, answerVars);
6245
6281
  }
6246
6282
 
6247
- function proveGoals(goals, subst, facts, backRules, depth, visited, varGen) {
6283
+ function proveGoals(goals, subst, facts, backRules, depth, visited, varGen, maxResults) {
6248
6284
  // Iterative DFS over proof states using an explicit stack.
6249
6285
  // Each state carries its own substitution and remaining goals.
6250
6286
  const results = [];
6287
+ const max = typeof maxResults === 'number' && maxResults > 0 ? maxResults : Infinity;
6251
6288
 
6252
6289
  const initialGoals = Array.isArray(goals) ? goals.slice() : [];
6253
6290
  const initialSubst = subst ? { ...subst } : {};
@@ -6258,6 +6295,8 @@ function proveGoals(goals, subst, facts, backRules, depth, visited, varGen) {
6258
6295
  gcCollectVarsInGoals(initialGoals, answerVars);
6259
6296
  if (!initialGoals.length) {
6260
6297
  results.push(gcCompactForGoals(initialSubst, [], answerVars));
6298
+
6299
+ if (results.length >= max) return results;
6261
6300
  return results;
6262
6301
  }
6263
6302
 
@@ -6275,6 +6314,8 @@ function proveGoals(goals, subst, facts, backRules, depth, visited, varGen) {
6275
6314
 
6276
6315
  if (!state.goals.length) {
6277
6316
  results.push(gcCompactForGoals(state.subst, [], answerVars));
6317
+
6318
+ if (results.length >= max) return results;
6278
6319
  continue;
6279
6320
  }
6280
6321
 
@@ -6284,13 +6325,18 @@ function proveGoals(goals, subst, facts, backRules, depth, visited, varGen) {
6284
6325
 
6285
6326
  // 1) Builtins
6286
6327
  if (isBuiltinPred(goal0.p)) {
6287
- const deltas = evalBuiltin(goal0, {}, facts, backRules, state.depth, varGen);
6328
+ const remaining = max - results.length;
6329
+ if (remaining <= 0) return results;
6330
+ const builtinMax = Number.isFinite(remaining) && !restGoals.length ? remaining : undefined;
6331
+ const deltas = evalBuiltin(goal0, {}, facts, backRules, state.depth, varGen, builtinMax);
6288
6332
  const nextStates = [];
6289
6333
  for (const delta of deltas) {
6290
6334
  const composed = composeSubst(state.subst, delta);
6291
6335
  if (composed === null) continue;
6292
6336
  if (!restGoals.length) {
6293
6337
  results.push(gcCompactForGoals(composed, [], answerVars));
6338
+
6339
+ if (results.length >= max) return results;
6294
6340
  } else {
6295
6341
  const nextSubst = maybeCompactSubst(composed, restGoals, answerVars, state.depth + 1);
6296
6342
  nextStates.push({
@@ -6321,6 +6367,8 @@ function proveGoals(goals, subst, facts, backRules, depth, visited, varGen) {
6321
6367
  if (composed === null) continue;
6322
6368
  if (!restGoals.length) {
6323
6369
  results.push(gcCompactForGoals(composed, [], answerVars));
6370
+
6371
+ if (results.length >= max) return results;
6324
6372
  } else {
6325
6373
  const nextSubst = maybeCompactSubst(composed, restGoals, answerVars, state.depth + 1);
6326
6374
  nextStates.push({
@@ -6342,6 +6390,8 @@ function proveGoals(goals, subst, facts, backRules, depth, visited, varGen) {
6342
6390
  if (composed === null) continue;
6343
6391
  if (!restGoals.length) {
6344
6392
  results.push(gcCompactForGoals(composed, [], answerVars));
6393
+
6394
+ if (results.length >= max) return results;
6345
6395
  } else {
6346
6396
  const nextSubst = maybeCompactSubst(composed, restGoals, answerVars, state.depth + 1);
6347
6397
  nextStates.push({
@@ -6454,7 +6504,38 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */)
6454
6504
  const r = forwardRules[i];
6455
6505
  const empty = {};
6456
6506
  const visited = [];
6457
- const sols = proveGoals(r.premise.slice(), empty, facts, backRules, 0, visited, varGen);
6507
+ // Optimization: if the rule head is **structurally ground** (no vars anywhere, even inside
6508
+ // quoted formulas) and has no head blanks, then the head does not depend on which body
6509
+ // solution we pick. In that case, we only need *one* proof of the body, and once all head
6510
+ // triples are already known we can skip proving the body entirely.
6511
+ function isStrictGroundTerm(t) {
6512
+ if (t instanceof Var) return false;
6513
+ if (t instanceof Blank) return false;
6514
+ if (t instanceof OpenListTerm) return false;
6515
+ if (t instanceof ListTerm) return t.elems.every(isStrictGroundTerm);
6516
+ if (t instanceof GraphTerm) return t.triples.every(isStrictGroundTriple);
6517
+ return true; // Iri/Literal and any other atomic terms
6518
+ }
6519
+ function isStrictGroundTriple(tr) {
6520
+ return isStrictGroundTerm(tr.s) && isStrictGroundTerm(tr.p) && isStrictGroundTerm(tr.o);
6521
+ }
6522
+
6523
+ const headIsStrictGround =
6524
+ !r.isFuse && (!r.headBlankLabels || r.headBlankLabels.size === 0) && r.conclusion.every(isStrictGroundTriple);
6525
+
6526
+ if (headIsStrictGround) {
6527
+ let allKnown = true;
6528
+ for (const tr of r.conclusion) {
6529
+ if (!hasFactIndexed(facts, tr)) {
6530
+ allKnown = false;
6531
+ break;
6532
+ }
6533
+ }
6534
+ if (allKnown) continue;
6535
+ }
6536
+
6537
+ const maxSols = (r.isFuse || headIsStrictGround) ? 1 : undefined;
6538
+ const sols = proveGoals(r.premise.slice(), empty, facts, backRules, 0, visited, varGen, maxSols);
6458
6539
 
6459
6540
  // Inference fuse
6460
6541
  if (r.isFuse && sols.length) {
@@ -6921,6 +7002,8 @@ function reasonStream(n3Text, opts = {}) {
6921
7002
 
6922
7003
  let prefixes, triples, frules, brules;
6923
7004
  [prefixes, triples, frules, brules] = parser.parseDocument();
7005
+ // Make the parsed prefixes available to log:trace output
7006
+ __tracePrefixes = prefixes;
6924
7007
 
6925
7008
  materializeRdfLists(triples, frules, brules);
6926
7009
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyeling",
3
- "version": "1.7.14",
3
+ "version": "1.7.16",
4
4
  "description": "A minimal Notation3 (N3) reasoner in JavaScript.",
5
5
  "main": "./index.js",
6
6
  "keywords": [
@@ -20,6 +20,7 @@
20
20
  "files": [
21
21
  "index.js",
22
22
  "eyeling.js",
23
+ "eyeling-builtins.ttl",
23
24
  "test",
24
25
  "examples",
25
26
  "README.md",