eyeling 1.28.8 → 1.29.0
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 +60 -1
- package/dist/browser/eyeling.browser.js +995 -49
- package/dist/browser/index.mjs +10 -0
- package/examples/complex-matrix-stability.n3 +23 -23
- package/examples/deep-taxonomy-10.n3 +2 -2
- package/examples/deep-taxonomy-100.n3 +2 -2
- package/examples/deep-taxonomy-1000.n3 +2 -2
- package/examples/deep-taxonomy-10000.n3 +2 -2
- package/eyeling.js +995 -49
- package/index.d.ts +59 -0
- package/index.js +17 -0
- package/lib/cli.js +102 -0
- package/lib/engine.js +120 -0
- package/lib/entry.js +4 -0
- package/lib/lexer.js +80 -49
- package/lib/rdfjs.js +1 -0
- package/lib/store.js +685 -0
- package/package.json +8 -3
- package/test/api.test.js +127 -1
- package/test/playground.test.js +17 -3
- package/test/store.test.js +138 -0
- package/tools/bundle.js +10 -0
package/test/api.test.js
CHANGED
|
@@ -769,6 +769,132 @@ bad.:example a bad.:Person.
|
|
|
769
769
|
`,
|
|
770
770
|
expectError: true,
|
|
771
771
|
},
|
|
772
|
+
{
|
|
773
|
+
name: '12k2 invalid syntax: raw newline in short string literal should throw',
|
|
774
|
+
opt: { proofComments: false },
|
|
775
|
+
input: `
|
|
776
|
+
@prefix : <http://example.org/> .
|
|
777
|
+
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
|
778
|
+
|
|
779
|
+
:my :value "
|
|
780
|
+
123".
|
|
781
|
+
|
|
782
|
+
{
|
|
783
|
+
:my :value "\n123".
|
|
784
|
+
}
|
|
785
|
+
=>
|
|
786
|
+
{
|
|
787
|
+
:result :has :crash-syntax-11.
|
|
788
|
+
}.
|
|
789
|
+
|
|
790
|
+
{} => {
|
|
791
|
+
:test :contains :crash-syntax-11.
|
|
792
|
+
}.
|
|
793
|
+
|
|
794
|
+
{
|
|
795
|
+
:result :has :crash-syntax-11.
|
|
796
|
+
}
|
|
797
|
+
=>
|
|
798
|
+
{
|
|
799
|
+
:test :is false.
|
|
800
|
+
}.
|
|
801
|
+
`,
|
|
802
|
+
expectError: true,
|
|
803
|
+
},
|
|
804
|
+
{
|
|
805
|
+
name: '12k3 invalid syntax: repeated dots in numeric-looking literal should throw',
|
|
806
|
+
opt: { proofComments: false },
|
|
807
|
+
input: `
|
|
808
|
+
@prefix : <http://example.org/> .
|
|
809
|
+
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
|
810
|
+
|
|
811
|
+
:my :value 1.2.3.4.5.
|
|
812
|
+
|
|
813
|
+
{
|
|
814
|
+
:my :value 1.2.3.4.5.
|
|
815
|
+
}
|
|
816
|
+
=>
|
|
817
|
+
{
|
|
818
|
+
:result :has :crash-syntax-12.
|
|
819
|
+
}.
|
|
820
|
+
|
|
821
|
+
{} => {
|
|
822
|
+
:test :contains :crash-syntax-12.
|
|
823
|
+
}.
|
|
824
|
+
|
|
825
|
+
{
|
|
826
|
+
:result :has :crash-syntax-12.
|
|
827
|
+
}
|
|
828
|
+
=>
|
|
829
|
+
{
|
|
830
|
+
:test :is false.
|
|
831
|
+
}.
|
|
832
|
+
`,
|
|
833
|
+
expectError: true,
|
|
834
|
+
},
|
|
835
|
+
{
|
|
836
|
+
name: '12k4 success literal: signed integer shorthand parses',
|
|
837
|
+
opt: { proofComments: false },
|
|
838
|
+
input: `
|
|
839
|
+
@prefix : <http://example.org/> .
|
|
840
|
+
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
|
841
|
+
|
|
842
|
+
:my :value +42.
|
|
843
|
+
|
|
844
|
+
{
|
|
845
|
+
:my :value +42.
|
|
846
|
+
}
|
|
847
|
+
=>
|
|
848
|
+
{
|
|
849
|
+
:result :has :success-literal-32.
|
|
850
|
+
}.
|
|
851
|
+
|
|
852
|
+
{} => {
|
|
853
|
+
:test :contains :success-literal-32.
|
|
854
|
+
}.
|
|
855
|
+
|
|
856
|
+
{
|
|
857
|
+
:result :has :success-literal-32.
|
|
858
|
+
}
|
|
859
|
+
=>
|
|
860
|
+
{
|
|
861
|
+
:test :is true.
|
|
862
|
+
}.
|
|
863
|
+
`,
|
|
864
|
+
expect: [/:result\s+:has\s+:success-literal-32\s*\./, /:test\s+:is\s+true\s*\./],
|
|
865
|
+
},
|
|
866
|
+
{
|
|
867
|
+
name: '12k5 success literal: leading-dot decimal shorthand parses',
|
|
868
|
+
opt: { proofComments: false },
|
|
869
|
+
input: `
|
|
870
|
+
@prefix : <http://example.org/> .
|
|
871
|
+
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
|
872
|
+
|
|
873
|
+
:my :value .5.
|
|
874
|
+
|
|
875
|
+
{
|
|
876
|
+
:my :value .5.
|
|
877
|
+
}
|
|
878
|
+
=>
|
|
879
|
+
{
|
|
880
|
+
:result :has :success-literal-33.
|
|
881
|
+
}.
|
|
882
|
+
|
|
883
|
+
{} => {
|
|
884
|
+
:test :contains :success-literal-33.
|
|
885
|
+
}.
|
|
886
|
+
|
|
887
|
+
{
|
|
888
|
+
:result :has :success-literal-33.
|
|
889
|
+
}
|
|
890
|
+
=>
|
|
891
|
+
{
|
|
892
|
+
:test :is true.
|
|
893
|
+
}.
|
|
894
|
+
`,
|
|
895
|
+
expect: [/:result\s+:has\s+:success-literal-33\s*\./, /:test\s+:is\s+true\s*\./],
|
|
896
|
+
},
|
|
897
|
+
|
|
772
898
|
{
|
|
773
899
|
name: '12l regression: IRIREF \\u escape decodes before log:uri comparison (mismatch stays falsey)',
|
|
774
900
|
opt: { proofComments: false },
|
|
@@ -1360,7 +1486,7 @@ _:l2 rdf:rest rdf:nil.
|
|
|
1360
1486
|
odrl:action ?Ignore ;
|
|
1361
1487
|
odrl:duty [ odrl:action ?A ]
|
|
1362
1488
|
].
|
|
1363
|
-
( "%% Duty_(a,t)(action:%s)
|
|
1489
|
+
( "%% Duty_(a,t)(action:%s)\\n%% => ~Possible_(a,t)(~action:%s)\\n" ?A ?A ) string:format ?Str.
|
|
1364
1490
|
}
|
|
1365
1491
|
=>
|
|
1366
1492
|
{
|
package/test/playground.test.js
CHANGED
|
@@ -906,16 +906,30 @@ ${JSON.stringify(last, null, 2)}`);
|
|
|
906
906
|
const fuseProgram = fs.readFileSync(path.join(ROOT, 'examples', 'fuse.n3'), 'utf8');
|
|
907
907
|
const outputStringProgram = `@prefix : <#> .
|
|
908
908
|
@prefix log: <http://www.w3.org/2000/10/swap/log#> .
|
|
909
|
-
:report log:outputString "## Hello from output string
|
|
909
|
+
:report log:outputString """## Hello from output string
|
|
910
|
+
|
|
911
|
+
Line 2 with **bold** and [Eyeling](https://example.org/eyeling)
|
|
912
|
+
""" .
|
|
910
913
|
`;
|
|
911
914
|
const riskMarkdownOutputStringProgram = `@prefix : <#> .
|
|
912
915
|
@prefix log: <http://www.w3.org/2000/10/swap/log#> .
|
|
913
|
-
:report log:outputString "# Risk report
|
|
916
|
+
:report log:outputString """# Risk report
|
|
917
|
+
|
|
918
|
+
### Clause H1 — score 100
|
|
919
|
+
|
|
920
|
+
Risk: secondary use is permitted without a safeguard. Clause H1: Hospital may provide electronic health data for secondary use.
|
|
921
|
+
|
|
922
|
+
- **Mitigation for clause H1:** Require a permit before secondary use.
|
|
923
|
+
""" .
|
|
914
924
|
`;
|
|
915
925
|
const baseOnlyMarkdownProgram = `@base <https://raw.githubusercontent.com/eyereasoner/eyeling/refs/heads/main/examples/smoke-arithmetic.n3> .
|
|
916
926
|
@prefix : <#> .
|
|
917
927
|
@prefix log: <http://www.w3.org/2000/10/swap/log#> .
|
|
918
|
-
:report log:outputString "# stateurl link base
|
|
928
|
+
:report log:outputString """# stateurl link base
|
|
929
|
+
|
|
930
|
+
[N3 rules](../smoke-arithmetic.n3)
|
|
931
|
+
[Input TriG](../input/smoke-arithmetic.trig)
|
|
932
|
+
""" .
|
|
919
933
|
`;
|
|
920
934
|
const logQueryTurtleProgram = `@prefix : <#> .
|
|
921
935
|
@prefix log: <http://www.w3.org/2000/10/swap/log#> .
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
const os = require('node:os');
|
|
6
|
+
const path = require('node:path');
|
|
7
|
+
|
|
8
|
+
const { pass, failResult, info } = require('./report');
|
|
9
|
+
const { Iri, Triple } = require('../lib/prelude');
|
|
10
|
+
const { createFactStore } = require('../lib/store');
|
|
11
|
+
const { runAsync } = require('../index.js');
|
|
12
|
+
|
|
13
|
+
const EX = 'http://example.org/';
|
|
14
|
+
const a = new Iri(EX + 'a');
|
|
15
|
+
const p = new Iri(EX + 'p');
|
|
16
|
+
const q = new Iri(EX + 'q');
|
|
17
|
+
const b = new Iri(EX + 'b');
|
|
18
|
+
const c = new Iri(EX + 'c');
|
|
19
|
+
const x = new Iri(EX + 'x');
|
|
20
|
+
const y = new Iri(EX + 'y');
|
|
21
|
+
const r = new Iri(EX + 'r');
|
|
22
|
+
const z = new Iri(EX + 'z');
|
|
23
|
+
|
|
24
|
+
const triples = [
|
|
25
|
+
new Triple(a, p, b),
|
|
26
|
+
new Triple(a, p, c),
|
|
27
|
+
new Triple(a, q, b),
|
|
28
|
+
new Triple(x, p, b),
|
|
29
|
+
new Triple(y, r, z),
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
async function collect(iterable) {
|
|
33
|
+
const out = [];
|
|
34
|
+
for await (const item of iterable) out.push(item);
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function withStore(factory, fn) {
|
|
39
|
+
const store = await factory();
|
|
40
|
+
try {
|
|
41
|
+
await store.batchAdd(triples, 'explicit');
|
|
42
|
+
await fn(store);
|
|
43
|
+
} finally {
|
|
44
|
+
if (store && typeof store.close === 'function') await store.close();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function expectCount(store, label, args, expected) {
|
|
49
|
+
const out = await collect(store.match(...args));
|
|
50
|
+
if (out.length !== expected) {
|
|
51
|
+
throw new Error(`${label}: expected ${expected} match(es), got ${out.length}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const tests = [
|
|
56
|
+
{
|
|
57
|
+
name: 'memory store supports all eight triple match shapes',
|
|
58
|
+
fn: async () => withStore(() => createFactStore({ type: 'memory' }), async (store) => {
|
|
59
|
+
await expectCount(store, '(s,p,o)', [a, p, b], 1);
|
|
60
|
+
await expectCount(store, '(s,p,?)', [a, p, null], 2);
|
|
61
|
+
await expectCount(store, '(s,?,o)', [a, null, b], 2);
|
|
62
|
+
await expectCount(store, '(s,?,?)', [a, null, null], 3);
|
|
63
|
+
await expectCount(store, '(?,p,o)', [null, p, b], 2);
|
|
64
|
+
await expectCount(store, '(?,p,?)', [null, p, null], 3);
|
|
65
|
+
await expectCount(store, '(?,?,o)', [null, null, b], 3);
|
|
66
|
+
await expectCount(store, '(?,?,?)', [null, null, null], 5);
|
|
67
|
+
}),
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
name: 'persistent store supports all eight triple match shapes and duplicate prevention',
|
|
71
|
+
fn: async () => {
|
|
72
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'eyeling-store-test-'));
|
|
73
|
+
try {
|
|
74
|
+
await withStore(() => createFactStore({ name: 'patterns', path: dir, clear: true }), async (store) => {
|
|
75
|
+
await expectCount(store, '(s,p,o)', [a, p, b], 1);
|
|
76
|
+
await expectCount(store, '(s,p,?)', [a, p, null], 2);
|
|
77
|
+
await expectCount(store, '(s,?,o)', [a, null, b], 2);
|
|
78
|
+
await expectCount(store, '(s,?,?)', [a, null, null], 3);
|
|
79
|
+
await expectCount(store, '(?,p,o)', [null, p, b], 2);
|
|
80
|
+
await expectCount(store, '(?,p,?)', [null, p, null], 3);
|
|
81
|
+
await expectCount(store, '(?,?,o)', [null, null, b], 3);
|
|
82
|
+
await expectCount(store, '(?,?,?)', [null, null, null], 5);
|
|
83
|
+
const added = await store.add(new Triple(a, p, b), 'inferred');
|
|
84
|
+
if (added) throw new Error('duplicate insertion should return false');
|
|
85
|
+
if (typeof store.kindOf === 'function') {
|
|
86
|
+
const mask = await store.kindOf(new Triple(a, p, b));
|
|
87
|
+
if ((mask & 1) === 0 || (mask & 2) === 0) throw new Error('explicit/inferred bitmask should be preserved');
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
} finally {
|
|
91
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: 'runAsync reuses a named persistent store across runs',
|
|
97
|
+
fn: async () => {
|
|
98
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'eyeling-runasync-store-'));
|
|
99
|
+
try {
|
|
100
|
+
const first = await runAsync('@prefix : <http://example.org/> .\n:a :p :b .', { store: { name: 'dataset', path: dir, clear: true } });
|
|
101
|
+
if (first.store) await first.store.close();
|
|
102
|
+
|
|
103
|
+
const second = await runAsync('@prefix : <http://example.org/> .\n{ ?s :p ?o } => { ?s :q ?o } .', { store: { name: 'dataset', path: dir } });
|
|
104
|
+
try {
|
|
105
|
+
const text = second.closureN3 || '';
|
|
106
|
+
if (!text.includes(':a :q :b .') && !text.includes(':a :q :b.')) throw new Error(`expected stored fact to feed rule, got:\n${text}`);
|
|
107
|
+
if (!second.store) throw new Error('runAsync should return the opened store');
|
|
108
|
+
const qMatches = await collect(second.store.match(a, q, b));
|
|
109
|
+
if (qMatches.length !== 1) throw new Error('expected inferred fact to be persisted');
|
|
110
|
+
} finally {
|
|
111
|
+
if (second.store) await second.store.close();
|
|
112
|
+
}
|
|
113
|
+
} finally {
|
|
114
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
];
|
|
119
|
+
|
|
120
|
+
(async function main() {
|
|
121
|
+
const startAll = Date.now();
|
|
122
|
+
let passed = 0;
|
|
123
|
+
for (let i = 0; i < tests.length; i += 1) {
|
|
124
|
+
const n = i + 1;
|
|
125
|
+
const t0 = Date.now();
|
|
126
|
+
try {
|
|
127
|
+
await tests[i].fn();
|
|
128
|
+
pass(n, tests[i].name, Date.now() - t0);
|
|
129
|
+
passed += 1;
|
|
130
|
+
} catch (e) {
|
|
131
|
+
failResult(n, tests[i].name, Date.now() - t0);
|
|
132
|
+
console.error(e && e.stack ? e.stack : e);
|
|
133
|
+
process.exitCode = 1;
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
info(`Store tests: ${passed}/${tests.length} passed`, Date.now() - startAll);
|
|
138
|
+
})();
|
package/tools/bundle.js
CHANGED
|
@@ -240,6 +240,10 @@ function getBrowserApi() {
|
|
|
240
240
|
|
|
241
241
|
export const INFERENCE_FUSE_EXIT_CODE = 65;
|
|
242
242
|
|
|
243
|
+
export function runAsync(input, opts) {
|
|
244
|
+
return getBrowserApi().runAsync(input, opts);
|
|
245
|
+
}
|
|
246
|
+
|
|
243
247
|
export function reasonStream(input, opts) {
|
|
244
248
|
return getBrowserApi().reasonStream(input, opts);
|
|
245
249
|
}
|
|
@@ -248,6 +252,10 @@ export function reasonRdfJs(input, opts) {
|
|
|
248
252
|
return getBrowserApi().reasonRdfJs(input, opts);
|
|
249
253
|
}
|
|
250
254
|
|
|
255
|
+
export function createFactStore(options) {
|
|
256
|
+
return getBrowserApi().createFactStore(options);
|
|
257
|
+
}
|
|
258
|
+
|
|
251
259
|
export function registerBuiltin(iri, handler) {
|
|
252
260
|
return getBrowserApi().registerBuiltin(iri, handler);
|
|
253
261
|
}
|
|
@@ -295,9 +303,11 @@ const eyeling = {
|
|
|
295
303
|
return getBrowserApi().version;
|
|
296
304
|
},
|
|
297
305
|
INFERENCE_FUSE_EXIT_CODE,
|
|
306
|
+
runAsync,
|
|
298
307
|
reasonStream,
|
|
299
308
|
reasonRdfJs,
|
|
300
309
|
rdfjs,
|
|
310
|
+
createFactStore,
|
|
301
311
|
registerBuiltin,
|
|
302
312
|
unregisterBuiltin,
|
|
303
313
|
registerBuiltinModule,
|