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/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 runStreamMessagesMode(sourceLabels, { rdfMode }) {
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
- for (const messageSourceLabel of messageSourceLabels) {
647
- try {
648
- await __forEachRdfMessageChunk(messageSourceLabel, ({ messageIndex, chunk, directives }) => {
649
- const messageText = buildSingleMessageReplayDocument({
650
- sourceLabel: messageSourceLabel,
651
- messageIndex,
652
- chunk,
653
- directives,
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
- } catch (e) {
665
- if (e && e.name === 'N3SyntaxError') {
666
- console.error(formatN3SyntaxError(e, messageText, `${messageSourceLabel}#message-${messageIndex}`));
667
- process.exit(1);
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
- const merged = mergeParsedDocuments(programSources.concat([messageDoc]));
673
- runParsedDocumentOnce(merged, { rdfMode, outputPrefixes: fullIriPrefixes });
674
- });
675
- } catch (e) {
676
- console.error(`Error streaming RDF Message Log ${JSON.stringify(messageSourceLabel)}: ${e && e.message ? e.message : String(e)}`);
677
- process.exit(1);
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
- const sourceLabels = useImplicitStdin ? ['<stdin>'] : positional.map((item) => (item === '-' ? '<stdin>' : item));
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] === '.' && i + 1 < n && isAsciiDigit(chars[i + 1])) {
1580
- numChars.push('.');
1581
- i++;
1582
- while (i < n && isAsciiDigit(chars[i])) {
1583
- numChars.push(chars[i]);
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
- this.db = new ClassicLevel(location, { valueEncoding: 'json' });
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyeling",
3
- "version": "1.29.0",
3
+ "version": "1.29.2",
4
4
  "description": "A minimal Notation3 (N3) reasoner in JavaScript.",
5
5
  "main": "./index.js",
6
6
  "keywords": [
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 },
@@ -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() {