eyeling 1.29.0 → 1.29.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.
- package/README.md +14 -2
- package/dist/browser/eyeling.browser.js +391 -46
- package/eyeling.js +391 -46
- package/lib/cli.js +211 -37
- package/lib/engine.js +160 -1
- package/lib/lexer.js +14 -5
- package/lib/parser.js +2 -2
- package/lib/store.js +4 -1
- package/package.json +1 -1
- package/test/api.test.js +69 -0
- package/test/store.test.js +29 -0
- package/test/stream_messages.test.js +13 -0
package/lib/cli.js
CHANGED
|
@@ -562,10 +562,12 @@ function runParsedDocumentOnce(mergedDocument, { rdfMode = false, outputPrefixes
|
|
|
562
562
|
|
|
563
563
|
let derived = [];
|
|
564
564
|
let outTriples = [];
|
|
565
|
+
let queryDerived = [];
|
|
565
566
|
if (hasQueries) {
|
|
566
567
|
const res = engine.forwardChainAndCollectLogQueryConclusions(facts, frules, brules, qrules, { prefixes });
|
|
567
568
|
derived = res.derived;
|
|
568
569
|
outTriples = res.queryTriples;
|
|
570
|
+
queryDerived = res.queryDerived || [];
|
|
569
571
|
} else {
|
|
570
572
|
const skipDerivedCollection = mayAutoRenderOutputStrings;
|
|
571
573
|
derived = engine.forwardChain(facts, frules, brules, null, {
|
|
@@ -579,7 +581,7 @@ function runParsedDocumentOnce(mergedDocument, { rdfMode = false, outputPrefixes
|
|
|
579
581
|
const renderedOutputTriples = hasQueries ? outTriples : facts;
|
|
580
582
|
if (factsContainOutputStrings(renderedOutputTriples)) {
|
|
581
583
|
process.stdout.write(engine.collectOutputStringsFromFacts(renderedOutputTriples, prefixes));
|
|
582
|
-
return;
|
|
584
|
+
return { facts, derived, outTriples, queryDerived, queryMode: !!hasQueries };
|
|
583
585
|
}
|
|
584
586
|
|
|
585
587
|
const outPrefixEnv = outputPrefixes || prefixes;
|
|
@@ -591,9 +593,99 @@ function runParsedDocumentOnce(mergedDocument, { rdfMode = false, outputPrefixes
|
|
|
591
593
|
} else {
|
|
592
594
|
for (const df of derived) console.log(engine.tripleToN3(df.fact, outPrefixEnv));
|
|
593
595
|
}
|
|
596
|
+
return { facts, derived, outTriples, queryDerived, queryMode: !!hasQueries };
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
async function createCliStore({ storeName, storePath = null, storeClear = false } = {}) {
|
|
600
|
+
if (!storeName) return null;
|
|
601
|
+
return engine.createFactStore({ name: storeName, path: storePath || undefined, clear: !!storeClear });
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
async function persistRunResultToStore(store, runResult) {
|
|
605
|
+
if (!store || !runResult) return;
|
|
606
|
+
await store.batchAdd(runResult.facts || [], 'explicit');
|
|
607
|
+
await store.batchAdd((runResult.derived || []).map((df) => df.fact), 'inferred');
|
|
608
|
+
await store.batchAdd((runResult.queryDerived || []).map((df) => df.fact), 'inferred');
|
|
609
|
+
await store.batchAdd(runResult.outTriples || [], 'inferred');
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
function sourceLooksLikeLineBasedRdf(sourceLabel) {
|
|
613
|
+
if (typeof sourceLabel !== 'string') return false;
|
|
614
|
+
const clean = sourceLabel.split(/[?#]/, 1)[0].toLowerCase();
|
|
615
|
+
return /\.(?:nt|nq)(?:\.gz|\.br|\.deflate)?$/.test(clean);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
function parseLineBasedRdfLine(line, { sourceLabel, lineNumber, rdfMode }) {
|
|
619
|
+
const text = String(line || '');
|
|
620
|
+
if (!text.trim() || /^\s*#/.test(text)) return [];
|
|
621
|
+
const doc = parseN3Text(text, {
|
|
622
|
+
baseIri: __sourceLabelToBaseIri(sourceLabel),
|
|
623
|
+
label: `${sourceLabel}:${lineNumber}`,
|
|
624
|
+
keepSourceArtifacts: false,
|
|
625
|
+
sourceLocations: false,
|
|
626
|
+
rdf: rdfMode,
|
|
627
|
+
});
|
|
628
|
+
return doc.triples || [];
|
|
594
629
|
}
|
|
595
630
|
|
|
596
|
-
async function
|
|
631
|
+
async function ingestLineBasedRdfSourceToStore(sourceLabel, store, { rdfMode = true } = {}) {
|
|
632
|
+
let lineNumber = 0;
|
|
633
|
+
let count = 0;
|
|
634
|
+
const onLine = async (line) => {
|
|
635
|
+
lineNumber += 1;
|
|
636
|
+
let triples;
|
|
637
|
+
try {
|
|
638
|
+
triples = parseLineBasedRdfLine(line, { sourceLabel, lineNumber, rdfMode });
|
|
639
|
+
} catch (e) {
|
|
640
|
+
if (e && e.name === 'N3SyntaxError') {
|
|
641
|
+
throw new Error(formatN3SyntaxError(e, line, `${sourceLabel}:${lineNumber}`));
|
|
642
|
+
}
|
|
643
|
+
throw e;
|
|
644
|
+
}
|
|
645
|
+
count += await store.batchAdd(triples, 'explicit');
|
|
646
|
+
};
|
|
647
|
+
|
|
648
|
+
const filePath = __localPathForSource(sourceLabel);
|
|
649
|
+
if (filePath) {
|
|
650
|
+
const fd = fs.openSync(filePath, 'r');
|
|
651
|
+
const decoder = new TextDecoder('utf8');
|
|
652
|
+
const buf = Buffer.allocUnsafe(64 * 1024);
|
|
653
|
+
let carry = '';
|
|
654
|
+
try {
|
|
655
|
+
for (;;) {
|
|
656
|
+
const n = fs.readSync(fd, buf, 0, buf.length, null);
|
|
657
|
+
if (n === 0) break;
|
|
658
|
+
carry += decoder.decode(buf.subarray(0, n), { stream: true });
|
|
659
|
+
for (;;) {
|
|
660
|
+
const m = /\r\n|\n|\r/.exec(carry);
|
|
661
|
+
if (!m) break;
|
|
662
|
+
const end = m.index + m[0].length;
|
|
663
|
+
await onLine(carry.slice(0, end));
|
|
664
|
+
carry = carry.slice(end);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
carry += decoder.decode();
|
|
668
|
+
if (carry) await onLine(carry);
|
|
669
|
+
} finally {
|
|
670
|
+
fs.closeSync(fd);
|
|
671
|
+
}
|
|
672
|
+
return count;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
if (__isHttpSource(sourceLabel)) {
|
|
676
|
+
const body = await __openHttpTextStream(sourceLabel);
|
|
677
|
+
const rl = readline.createInterface({ input: body, crlfDelay: Infinity });
|
|
678
|
+
for await (const line of rl) await onLine(line + '\n');
|
|
679
|
+
return count;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
const text = __readInputSourceSync(sourceLabel);
|
|
683
|
+
for (const line of text.match(/.*(?:\r\n|\n|\r)|.+$/g) || []) await onLine(line);
|
|
684
|
+
return count;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
|
|
688
|
+
async function runStreamMessagesMode(sourceLabels, { rdfMode, storeName = null, storePath = null, storeClear = false } = {}) {
|
|
597
689
|
const ordinarySourceLabels = [];
|
|
598
690
|
const messageSourceLabels = [];
|
|
599
691
|
|
|
@@ -642,40 +734,49 @@ async function runStreamMessagesMode(sourceLabels, { rdfMode }) {
|
|
|
642
734
|
}
|
|
643
735
|
}
|
|
644
736
|
|
|
737
|
+
const store = await createCliStore({ storeName, storePath, storeClear });
|
|
738
|
+
let pendingStoreWrites = Promise.resolve();
|
|
739
|
+
|
|
645
740
|
const fullIriPrefixes = new PrefixEnv({});
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
let messageDoc;
|
|
656
|
-
try {
|
|
657
|
-
messageDoc = parseN3Text(messageText, {
|
|
658
|
-
baseIri: `${__sourceLabelToBaseIri(messageSourceLabel)}#message-${messageIndex}`,
|
|
659
|
-
label: `${messageSourceLabel}#message-${messageIndex}`,
|
|
660
|
-
keepSourceArtifacts: false,
|
|
661
|
-
sourceLocations: false,
|
|
662
|
-
rdf: false,
|
|
741
|
+
try {
|
|
742
|
+
for (const messageSourceLabel of messageSourceLabels) {
|
|
743
|
+
try {
|
|
744
|
+
await __forEachRdfMessageChunk(messageSourceLabel, ({ messageIndex, chunk, directives }) => {
|
|
745
|
+
const messageText = buildSingleMessageReplayDocument({
|
|
746
|
+
sourceLabel: messageSourceLabel,
|
|
747
|
+
messageIndex,
|
|
748
|
+
chunk,
|
|
749
|
+
directives,
|
|
663
750
|
});
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
751
|
+
let messageDoc;
|
|
752
|
+
try {
|
|
753
|
+
messageDoc = parseN3Text(messageText, {
|
|
754
|
+
baseIri: `${__sourceLabelToBaseIri(messageSourceLabel)}#message-${messageIndex}`,
|
|
755
|
+
label: `${messageSourceLabel}#message-${messageIndex}`,
|
|
756
|
+
keepSourceArtifacts: false,
|
|
757
|
+
sourceLocations: false,
|
|
758
|
+
rdf: false,
|
|
759
|
+
});
|
|
760
|
+
} catch (e) {
|
|
761
|
+
if (e && e.name === 'N3SyntaxError') {
|
|
762
|
+
console.error(formatN3SyntaxError(e, messageText, `${messageSourceLabel}#message-${messageIndex}`));
|
|
763
|
+
process.exit(1);
|
|
764
|
+
}
|
|
765
|
+
throw e;
|
|
668
766
|
}
|
|
669
|
-
throw e;
|
|
670
|
-
}
|
|
671
767
|
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
768
|
+
const merged = mergeParsedDocuments(programSources.concat([messageDoc]));
|
|
769
|
+
const result = runParsedDocumentOnce(merged, { rdfMode, outputPrefixes: fullIriPrefixes });
|
|
770
|
+
if (store) pendingStoreWrites = pendingStoreWrites.then(() => persistRunResultToStore(store, result));
|
|
771
|
+
});
|
|
772
|
+
} catch (e) {
|
|
773
|
+
console.error(`Error streaming RDF Message Log ${JSON.stringify(messageSourceLabel)}: ${e && e.message ? e.message : String(e)}`);
|
|
774
|
+
process.exit(1);
|
|
775
|
+
}
|
|
678
776
|
}
|
|
777
|
+
await pendingStoreWrites;
|
|
778
|
+
} finally {
|
|
779
|
+
if (store && typeof store.close === 'function') await store.close();
|
|
679
780
|
}
|
|
680
781
|
}
|
|
681
782
|
|
|
@@ -811,10 +912,6 @@ async function main() {
|
|
|
811
912
|
|
|
812
913
|
|
|
813
914
|
if (streamMessagesMode) {
|
|
814
|
-
if (storeName) {
|
|
815
|
-
console.error('Error: --store cannot be combined with --stream-messages yet.');
|
|
816
|
-
process.exit(1);
|
|
817
|
-
}
|
|
818
915
|
if (!rdfMode) {
|
|
819
916
|
console.error('Error: --stream-messages requires -r/--rdf.');
|
|
820
917
|
process.exit(1);
|
|
@@ -850,17 +947,94 @@ async function main() {
|
|
|
850
947
|
}
|
|
851
948
|
}
|
|
852
949
|
|
|
853
|
-
|
|
950
|
+
let sourceLabels = useImplicitStdin ? ['<stdin>'] : positional.map((item) => (item === '-' ? '<stdin>' : item));
|
|
854
951
|
if (sourceLabels.filter((item) => item === '<stdin>').length > 1) {
|
|
855
952
|
console.error('Error: stdin can only be used once.');
|
|
856
953
|
process.exit(1);
|
|
857
954
|
}
|
|
858
955
|
|
|
859
956
|
if (streamMessagesMode) {
|
|
860
|
-
await runStreamMessagesMode(sourceLabels, { rdfMode });
|
|
957
|
+
await runStreamMessagesMode(sourceLabels, { rdfMode, storeName, storePath, storeClear });
|
|
861
958
|
return;
|
|
862
959
|
}
|
|
863
960
|
|
|
961
|
+
if (storeName && rdfMode) {
|
|
962
|
+
const lineBasedSources = sourceLabels.filter(sourceLooksLikeLineBasedRdf);
|
|
963
|
+
if (lineBasedSources.length) {
|
|
964
|
+
if (showAst) {
|
|
965
|
+
console.error('Error: line-based --store ingestion cannot be combined with --ast.');
|
|
966
|
+
process.exit(1);
|
|
967
|
+
}
|
|
968
|
+
if (streamMode) {
|
|
969
|
+
console.error('Error: line-based --store ingestion cannot be combined with --stream.');
|
|
970
|
+
process.exit(1);
|
|
971
|
+
}
|
|
972
|
+
if (engine.getProofCommentsEnabled()) {
|
|
973
|
+
console.error('Error: line-based --store ingestion currently does not support proof output.');
|
|
974
|
+
process.exit(1);
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
const store = await createCliStore({ storeName, storePath, storeClear });
|
|
978
|
+
try {
|
|
979
|
+
for (const sourceLabel of lineBasedSources) {
|
|
980
|
+
try {
|
|
981
|
+
await ingestLineBasedRdfSourceToStore(sourceLabel, store, { rdfMode });
|
|
982
|
+
} catch (e) {
|
|
983
|
+
console.error(`Error streaming RDF source ${JSON.stringify(sourceLabel)}: ${e && e.message ? e.message : String(e)}`);
|
|
984
|
+
process.exit(1);
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
sourceLabels = sourceLabels.filter((sourceLabel) => !sourceLooksLikeLineBasedRdf(sourceLabel));
|
|
989
|
+
if (!sourceLabels.length) return;
|
|
990
|
+
|
|
991
|
+
const parsedRuleSources = [];
|
|
992
|
+
for (const sourceLabel of sourceLabels) {
|
|
993
|
+
let text;
|
|
994
|
+
try {
|
|
995
|
+
text = __readInputSourceSync(sourceLabel);
|
|
996
|
+
} catch (e) {
|
|
997
|
+
if (sourceLabel === '<stdin>') console.error(`Error reading stdin: ${e.message}`);
|
|
998
|
+
else console.error(`Error reading source ${JSON.stringify(sourceLabel)}: ${e.message}`);
|
|
999
|
+
process.exit(1);
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
try {
|
|
1003
|
+
parsedRuleSources.push(
|
|
1004
|
+
parseN3Text(text, {
|
|
1005
|
+
baseIri: __sourceLabelToBaseIri(sourceLabel),
|
|
1006
|
+
label: sourceLabel,
|
|
1007
|
+
collectUsedPrefixes: false,
|
|
1008
|
+
keepSourceArtifacts: false,
|
|
1009
|
+
sourceLocations: false,
|
|
1010
|
+
rdf: rdfMode,
|
|
1011
|
+
}),
|
|
1012
|
+
);
|
|
1013
|
+
} catch (e) {
|
|
1014
|
+
if (e && e.name === 'N3SyntaxError') {
|
|
1015
|
+
console.error(formatN3SyntaxError(e, text, sourceLabel));
|
|
1016
|
+
process.exit(1);
|
|
1017
|
+
}
|
|
1018
|
+
throw e;
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
const mergedRuleDocument = mergeParsedDocuments(parsedRuleSources);
|
|
1023
|
+
const result = await engine.runStoreBacked(mergedRuleDocument, store, { rdf: rdfMode });
|
|
1024
|
+
const outTriples = result.queryMode ? result.queryTriples || [] : (result.derived || []).map((df) => df.fact);
|
|
1025
|
+
if (result.queryMode) {
|
|
1026
|
+
const bodyText = engine.prettyPrintQueryTriples(outTriples, result.prefixes);
|
|
1027
|
+
if (bodyText) process.stdout.write(String(bodyText).replace(/\s*$/g, '') + '\n');
|
|
1028
|
+
} else {
|
|
1029
|
+
for (const tr of outTriples) console.log(engine.tripleToRdfCompatible(tr, result.prefixes));
|
|
1030
|
+
}
|
|
1031
|
+
return;
|
|
1032
|
+
} finally {
|
|
1033
|
+
if (store && typeof store.close === 'function') await store.close();
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
|
|
864
1038
|
const parsedSources = [];
|
|
865
1039
|
for (const sourceLabel of sourceLabels) {
|
|
866
1040
|
let text;
|
package/lib/engine.js
CHANGED
|
@@ -106,7 +106,7 @@ const trace = require('./trace');
|
|
|
106
106
|
const { deterministicSkolemIdFromKey } = require('./skolem');
|
|
107
107
|
|
|
108
108
|
const deref = require('./deref');
|
|
109
|
-
const { createFactStore, collectStore, MemoryFactStore, PersistentFactStore } = require('./store');
|
|
109
|
+
const { createFactStore, collectStore, MemoryFactStore, PersistentFactStore, tripleToStoreKey } = require('./store');
|
|
110
110
|
|
|
111
111
|
const hasOwn = Object.prototype.hasOwnProperty;
|
|
112
112
|
|
|
@@ -3813,6 +3813,164 @@ async function __parseRunAsyncInput(input, opts) {
|
|
|
3813
3813
|
});
|
|
3814
3814
|
}
|
|
3815
3815
|
|
|
3816
|
+
function __storePatternTerm(t, subst) {
|
|
3817
|
+
const applied = applySubstTerm(t, subst || __emptySubst());
|
|
3818
|
+
return containsVarTerm(applied) ? null : applied;
|
|
3819
|
+
}
|
|
3820
|
+
|
|
3821
|
+
function __mergeStoreSubst(base, delta) {
|
|
3822
|
+
let out = base;
|
|
3823
|
+
for (const k of Object.keys(delta || {})) {
|
|
3824
|
+
const v = delta[k];
|
|
3825
|
+
if (Object.prototype.hasOwnProperty.call(out, k)) {
|
|
3826
|
+
if (!termsEqual(out[k], v)) return null;
|
|
3827
|
+
} else {
|
|
3828
|
+
if (out === base) out = __cloneSubst(base);
|
|
3829
|
+
out[k] = v;
|
|
3830
|
+
}
|
|
3831
|
+
}
|
|
3832
|
+
return out;
|
|
3833
|
+
}
|
|
3834
|
+
|
|
3835
|
+
async function __proveGoalsAgainstStore(goals, subst, store, backRules, localFacts, depth, varGen, opts = {}) {
|
|
3836
|
+
if (!Array.isArray(goals) || goals.length === 0) return [subst || __emptySubst()];
|
|
3837
|
+
if (depth > 64) return [];
|
|
3838
|
+
|
|
3839
|
+
const goal0 = applySubstTriple(goals[0], subst || __emptySubst());
|
|
3840
|
+
const rest = goals.slice(1);
|
|
3841
|
+
const out = [];
|
|
3842
|
+
|
|
3843
|
+
if (isBuiltinPred(goal0.p)) {
|
|
3844
|
+
const deltas = evalBuiltin(goal0, __emptySubst(), localFacts || [], backRules || [], depth, varGen, undefined);
|
|
3845
|
+
for (const delta of deltas) {
|
|
3846
|
+
const nextSubst = __mergeStoreSubst(subst || __emptySubst(), delta);
|
|
3847
|
+
if (nextSubst === null) continue;
|
|
3848
|
+
out.push(...await __proveGoalsAgainstStore(rest, nextSubst, store, backRules, localFacts, depth + 1, varGen, opts));
|
|
3849
|
+
if (opts.maxResults && out.length >= opts.maxResults) return out.slice(0, opts.maxResults);
|
|
3850
|
+
}
|
|
3851
|
+
return out;
|
|
3852
|
+
}
|
|
3853
|
+
|
|
3854
|
+
// Backward rules are kept in memory; they may call back into the persistent
|
|
3855
|
+
// fact store for their own premises.
|
|
3856
|
+
if (goal0.p instanceof Iri && Array.isArray(backRules) && backRules.length) {
|
|
3857
|
+
ensureBackRuleIndexes(backRules);
|
|
3858
|
+
const candRules = (backRules.__byHeadPred.get(goal0.p.__tid) || []).concat(backRules.__wildHeadPred || []);
|
|
3859
|
+
for (const r of candRules) {
|
|
3860
|
+
if (!r || !Array.isArray(r.conclusion) || r.conclusion.length !== 1) continue;
|
|
3861
|
+
const rStd = standardizeRule(r, varGen);
|
|
3862
|
+
const head = rStd.conclusion[0];
|
|
3863
|
+
const s2 = unifyTriple(head, goal0, subst || __emptySubst());
|
|
3864
|
+
if (s2 === null) continue;
|
|
3865
|
+
const newGoals = (rStd.premise || []).concat(rest);
|
|
3866
|
+
out.push(...await __proveGoalsAgainstStore(newGoals, s2, store, backRules, localFacts, depth + 1, varGen, opts));
|
|
3867
|
+
if (opts.maxResults && out.length >= opts.maxResults) return out.slice(0, opts.maxResults);
|
|
3868
|
+
}
|
|
3869
|
+
}
|
|
3870
|
+
|
|
3871
|
+
const sPat = __storePatternTerm(goal0.s, subst || __emptySubst());
|
|
3872
|
+
const pPat = __storePatternTerm(goal0.p, subst || __emptySubst());
|
|
3873
|
+
const oPat = __storePatternTerm(goal0.o, subst || __emptySubst());
|
|
3874
|
+
|
|
3875
|
+
for await (const fact of store.match(sPat, pPat, oPat)) {
|
|
3876
|
+
const s2 = unifyTriple(goal0, fact, subst || __emptySubst());
|
|
3877
|
+
if (s2 === null) continue;
|
|
3878
|
+
out.push(...await __proveGoalsAgainstStore(rest, s2, store, backRules, localFacts, depth + 1, varGen, opts));
|
|
3879
|
+
if (opts.maxResults && out.length >= opts.maxResults) return out.slice(0, opts.maxResults);
|
|
3880
|
+
}
|
|
3881
|
+
|
|
3882
|
+
return out;
|
|
3883
|
+
}
|
|
3884
|
+
|
|
3885
|
+
async function runStoreBacked(input, store, opts = {}) {
|
|
3886
|
+
const parsed = parseN3SourceList(input, {
|
|
3887
|
+
baseIri: opts.baseIri || null,
|
|
3888
|
+
rdf: !!opts.rdf,
|
|
3889
|
+
sourceLocations: !!opts.proof,
|
|
3890
|
+
}) || input;
|
|
3891
|
+
|
|
3892
|
+
const prefixes = parsed.prefixes;
|
|
3893
|
+
const triples = parsed.triples || [];
|
|
3894
|
+
const frules = parsed.frules || [];
|
|
3895
|
+
const brules = parsed.brules || [];
|
|
3896
|
+
const qrules = parsed.logQueryRules || [];
|
|
3897
|
+
|
|
3898
|
+
materializeRdfLists(triples, frules.concat(qrules || []), brules);
|
|
3899
|
+
await store.batchAdd(triples, 'explicit');
|
|
3900
|
+
|
|
3901
|
+
const derived = [];
|
|
3902
|
+
const derivedKeys = new Set();
|
|
3903
|
+
const varGen = [0];
|
|
3904
|
+
const maxIterations = Number.isInteger(opts.maxIterations) && opts.maxIterations > 0 ? opts.maxIterations : 1024;
|
|
3905
|
+
|
|
3906
|
+
for (let iteration = 0; iteration < maxIterations; iteration += 1) {
|
|
3907
|
+
let changed = false;
|
|
3908
|
+
for (let ruleIndex = 0; ruleIndex < frules.length; ruleIndex += 1) {
|
|
3909
|
+
const r = frules[ruleIndex];
|
|
3910
|
+
if (!r || r.isFuse || r.__dynamicConclusionTerm) continue;
|
|
3911
|
+
__prepareForwardRule(r);
|
|
3912
|
+
const solutions = await __proveGoalsAgainstStore(r.premise || [], __emptySubst(), store, brules, triples, 0, varGen, {});
|
|
3913
|
+
for (const subst of solutions) {
|
|
3914
|
+
for (const cpat of r.conclusion || []) {
|
|
3915
|
+
const fact = applySubstTriple(cpat, subst);
|
|
3916
|
+
if (!isGroundTriple(fact)) continue;
|
|
3917
|
+
if (await store.add(fact, 'inferred')) {
|
|
3918
|
+
const df = makeDerivedRecord(fact, r, (r.premise || []).map((p) => applySubstTriple(p, subst)), subst, false);
|
|
3919
|
+
const key = tripleToStoreKey(fact);
|
|
3920
|
+
if (!derivedKeys.has(key)) {
|
|
3921
|
+
derivedKeys.add(key);
|
|
3922
|
+
derived.push(df);
|
|
3923
|
+
}
|
|
3924
|
+
changed = true;
|
|
3925
|
+
}
|
|
3926
|
+
}
|
|
3927
|
+
}
|
|
3928
|
+
}
|
|
3929
|
+
if (!changed) break;
|
|
3930
|
+
}
|
|
3931
|
+
|
|
3932
|
+
let queryTriples = [];
|
|
3933
|
+
const queryDerived = [];
|
|
3934
|
+
if (qrules.length) {
|
|
3935
|
+
for (const r of qrules) {
|
|
3936
|
+
const solutions = await __proveGoalsAgainstStore(r.premise || [], __emptySubst(), store, brules, triples, 0, varGen, {});
|
|
3937
|
+
for (const subst of solutions) {
|
|
3938
|
+
for (const cpat of r.conclusion || []) {
|
|
3939
|
+
const fact = applySubstTriple(cpat, subst);
|
|
3940
|
+
if (!isGroundTriple(fact)) continue;
|
|
3941
|
+
queryTriples.push(fact);
|
|
3942
|
+
queryDerived.push(makeDerivedRecord(fact, r, (r.premise || []).map((p) => applySubstTriple(p, subst)), subst, false));
|
|
3943
|
+
}
|
|
3944
|
+
}
|
|
3945
|
+
}
|
|
3946
|
+
}
|
|
3947
|
+
|
|
3948
|
+
if (queryTriples.length) {
|
|
3949
|
+
const seen = new Set();
|
|
3950
|
+
queryTriples = queryTriples.filter((tr) => {
|
|
3951
|
+
const key = tripleToStoreKey(tr);
|
|
3952
|
+
if (seen.has(key)) return false;
|
|
3953
|
+
seen.add(key);
|
|
3954
|
+
return true;
|
|
3955
|
+
});
|
|
3956
|
+
}
|
|
3957
|
+
|
|
3958
|
+
return {
|
|
3959
|
+
prefixes,
|
|
3960
|
+
facts: [],
|
|
3961
|
+
derived,
|
|
3962
|
+
queryMode: !!qrules.length,
|
|
3963
|
+
queryTriples,
|
|
3964
|
+
queryDerived,
|
|
3965
|
+
closureN3: qrules.length
|
|
3966
|
+
? prettyPrintQueryTriples(queryTriples, prefixes)
|
|
3967
|
+
: derived.map((df) => (opts.rdf ? tripleToRdfCompatible(df.fact, prefixes) : tripleToN3(df.fact, prefixes))).join('\n'),
|
|
3968
|
+
store,
|
|
3969
|
+
storeBacked: true,
|
|
3970
|
+
};
|
|
3971
|
+
}
|
|
3972
|
+
|
|
3973
|
+
|
|
3816
3974
|
function __withoutStoreOptions(opts) {
|
|
3817
3975
|
const out = { ...(opts || {}) };
|
|
3818
3976
|
delete out.store;
|
|
@@ -4007,6 +4165,7 @@ function setTracePrefixes(v) {
|
|
|
4007
4165
|
module.exports = {
|
|
4008
4166
|
reasonStream,
|
|
4009
4167
|
runAsync,
|
|
4168
|
+
runStoreBacked,
|
|
4010
4169
|
reasonRdfJs,
|
|
4011
4170
|
collectLogQueryConclusions,
|
|
4012
4171
|
forwardChainAndCollectLogQueryConclusions,
|
package/lib/lexer.js
CHANGED
|
@@ -1576,12 +1576,21 @@ function lex(inputText, opts = {}) {
|
|
|
1576
1576
|
numChars.push(chars[i]);
|
|
1577
1577
|
i++;
|
|
1578
1578
|
}
|
|
1579
|
-
if (i < n && chars[i] === '.'
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1579
|
+
if (i < n && chars[i] === '.') {
|
|
1580
|
+
const next = i + 1 < n ? chars[i + 1] : null;
|
|
1581
|
+
let exponentAfterDot = false;
|
|
1582
|
+
if (next === 'e' || next === 'E') {
|
|
1583
|
+
let j = i + 2;
|
|
1584
|
+
if (j < n && (chars[j] === '+' || chars[j] === '-')) j++;
|
|
1585
|
+
exponentAfterDot = j < n && isAsciiDigit(chars[j]);
|
|
1586
|
+
}
|
|
1587
|
+
if (isAsciiDigit(next) || exponentAfterDot) {
|
|
1588
|
+
numChars.push('.');
|
|
1584
1589
|
i++;
|
|
1590
|
+
while (i < n && isAsciiDigit(chars[i])) {
|
|
1591
|
+
numChars.push(chars[i]);
|
|
1592
|
+
i++;
|
|
1593
|
+
}
|
|
1585
1594
|
}
|
|
1586
1595
|
}
|
|
1587
1596
|
}
|
package/lib/parser.js
CHANGED
|
@@ -547,7 +547,7 @@ class Parser {
|
|
|
547
547
|
}
|
|
548
548
|
|
|
549
549
|
if (this.peek().typ === 'Semicolon') {
|
|
550
|
-
this.next();
|
|
550
|
+
while (this.peek().typ === 'Semicolon') this.next();
|
|
551
551
|
if (this.peek().typ === closingTyp) break;
|
|
552
552
|
continue;
|
|
553
553
|
}
|
|
@@ -674,7 +674,7 @@ class Parser {
|
|
|
674
674
|
}
|
|
675
675
|
|
|
676
676
|
if (this.peek().typ === 'Semicolon') {
|
|
677
|
-
this.next();
|
|
677
|
+
while (this.peek().typ === 'Semicolon') this.next();
|
|
678
678
|
if (this.peek().typ === 'Dot') break;
|
|
679
679
|
continue;
|
|
680
680
|
}
|
package/lib/store.js
CHANGED
|
@@ -259,7 +259,10 @@ class ClassicLevelKv {
|
|
|
259
259
|
const classic = __dynamicRequire('classic-level');
|
|
260
260
|
const ClassicLevel = classic && (classic.ClassicLevel || classic.Level || classic.default);
|
|
261
261
|
if (!ClassicLevel) throw new Error('classic-level is not installed');
|
|
262
|
-
|
|
262
|
+
const fs = __dynamicRequire('node:fs');
|
|
263
|
+
const path = __dynamicRequire('node:path');
|
|
264
|
+
if (fs && path) fs.mkdirSync(path.dirname(location), { recursive: true });
|
|
265
|
+
this.db = new ClassicLevel(location, { valueEncoding: 'json', createIfMissing: true, errorIfExists: false });
|
|
263
266
|
this.opened = false;
|
|
264
267
|
}
|
|
265
268
|
|
package/package.json
CHANGED
package/test/api.test.js
CHANGED
|
@@ -895,6 +895,75 @@ bad.:example a bad.:Person.
|
|
|
895
895
|
expect: [/:result\s+:has\s+:success-literal-33\s*\./, /:test\s+:is\s+true\s*\./],
|
|
896
896
|
},
|
|
897
897
|
|
|
898
|
+
{
|
|
899
|
+
name: '12k6 success literal: decimal doubles with omitted fractional digits parse',
|
|
900
|
+
opt: { proofComments: false },
|
|
901
|
+
input: `
|
|
902
|
+
@prefix : <http://example.org/> .
|
|
903
|
+
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
|
904
|
+
|
|
905
|
+
:my :value1 4.e2.
|
|
906
|
+
:my :value2 3.e-2.
|
|
907
|
+
:my :value3 2.e+2.
|
|
908
|
+
:my :value4 1.e001.
|
|
909
|
+
|
|
910
|
+
{
|
|
911
|
+
:my :value1 4.e2.
|
|
912
|
+
:my :value2 3.e-2.
|
|
913
|
+
:my :value3 2.e+2.
|
|
914
|
+
:my :value4 1.e001.
|
|
915
|
+
}
|
|
916
|
+
=>
|
|
917
|
+
{
|
|
918
|
+
:result :has :success-literal-34.
|
|
919
|
+
}.
|
|
920
|
+
|
|
921
|
+
{} => {
|
|
922
|
+
:test :contains :success-literal-34.
|
|
923
|
+
}.
|
|
924
|
+
|
|
925
|
+
{
|
|
926
|
+
:result :has :success-literal-34.
|
|
927
|
+
}
|
|
928
|
+
=>
|
|
929
|
+
{
|
|
930
|
+
:test :is true.
|
|
931
|
+
}.
|
|
932
|
+
`,
|
|
933
|
+
expect: [/:result\s+:has\s+:success-literal-34\s*\./, /:test\s+:is\s+true\s*\./],
|
|
934
|
+
},
|
|
935
|
+
{
|
|
936
|
+
name: '12k7 success literal: repeated semicolon separators parse',
|
|
937
|
+
opt: { proofComments: false },
|
|
938
|
+
input: `
|
|
939
|
+
@prefix : <http://example.org/> .
|
|
940
|
+
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
|
941
|
+
|
|
942
|
+
:s :p :o ;;;;;;; .
|
|
943
|
+
|
|
944
|
+
{
|
|
945
|
+
:s :p :o .
|
|
946
|
+
}
|
|
947
|
+
=>
|
|
948
|
+
{
|
|
949
|
+
:result :has :success-literal-35.
|
|
950
|
+
}.
|
|
951
|
+
|
|
952
|
+
{} => {
|
|
953
|
+
:test :contains :success-literal-35.
|
|
954
|
+
}.
|
|
955
|
+
|
|
956
|
+
{
|
|
957
|
+
:result :has :success-literal-35.
|
|
958
|
+
}
|
|
959
|
+
=>
|
|
960
|
+
{
|
|
961
|
+
:test :is true.
|
|
962
|
+
}.
|
|
963
|
+
`,
|
|
964
|
+
expect: [/:result\s+:has\s+:success-literal-35\s*\./, /:test\s+:is\s+true\s*\./],
|
|
965
|
+
},
|
|
966
|
+
|
|
898
967
|
{
|
|
899
968
|
name: '12l regression: IRIREF \\u escape decodes before log:uri comparison (mismatch stays falsey)',
|
|
900
969
|
opt: { proofComments: false },
|
package/test/store.test.js
CHANGED
|
@@ -4,12 +4,16 @@
|
|
|
4
4
|
const fs = require('node:fs');
|
|
5
5
|
const os = require('node:os');
|
|
6
6
|
const path = require('node:path');
|
|
7
|
+
const cp = require('node:child_process');
|
|
7
8
|
|
|
8
9
|
const { pass, failResult, info } = require('./report');
|
|
9
10
|
const { Iri, Triple } = require('../lib/prelude');
|
|
10
11
|
const { createFactStore } = require('../lib/store');
|
|
11
12
|
const { runAsync } = require('../index.js');
|
|
12
13
|
|
|
14
|
+
const root = path.resolve(__dirname, '..');
|
|
15
|
+
const eyelingJsPath = path.join(root, 'eyeling.js');
|
|
16
|
+
|
|
13
17
|
const EX = 'http://example.org/';
|
|
14
18
|
const a = new Iri(EX + 'a');
|
|
15
19
|
const p = new Iri(EX + 'p');
|
|
@@ -115,6 +119,31 @@ const tests = [
|
|
|
115
119
|
}
|
|
116
120
|
},
|
|
117
121
|
},
|
|
122
|
+
{
|
|
123
|
+
name: 'CLI auto-creates stores and streams line-based RDF into them',
|
|
124
|
+
fn: async () => {
|
|
125
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'eyeling-cli-store-'));
|
|
126
|
+
try {
|
|
127
|
+
const facts = path.join(dir, 'facts.nt');
|
|
128
|
+
const rules = path.join(dir, 'rules.n3');
|
|
129
|
+
fs.writeFileSync(facts, '<http://example.org/a> <http://example.org/p> <http://example.org/b> .\n', 'utf8');
|
|
130
|
+
fs.writeFileSync(
|
|
131
|
+
rules,
|
|
132
|
+
'@prefix : <http://example.org/> .\n{ ?s :p ?o } => { ?s :q ?o } .\n',
|
|
133
|
+
'utf8',
|
|
134
|
+
);
|
|
135
|
+
const r = cp.spawnSync(
|
|
136
|
+
process.execPath,
|
|
137
|
+
[eyelingJsPath, '-r', '--store', 'auto-created', '--store-path', path.join(dir, 'store'), facts, rules],
|
|
138
|
+
{ cwd: root, encoding: 'utf8', maxBuffer: 5 * 1024 * 1024 },
|
|
139
|
+
);
|
|
140
|
+
if (r.status !== 0) throw new Error(`eyeling failed:\nSTDOUT:\n${r.stdout}\nSTDERR:\n${r.stderr}`);
|
|
141
|
+
if (!r.stdout.includes(':a :q :b .')) throw new Error(`expected streamed store inference, got:\n${r.stdout}`);
|
|
142
|
+
} finally {
|
|
143
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
},
|
|
118
147
|
];
|
|
119
148
|
|
|
120
149
|
(async function main() {
|