eyeling 1.24.30 → 1.24.31

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/lexer.js CHANGED
@@ -356,6 +356,24 @@ function stripQuotes(lex) {
356
356
  // This keeps all downstream parsing/reasoning N3-only.
357
357
  const LOG_NAME_OF_IRI = '<http://www.w3.org/2000/10/swap/log#nameOf>';
358
358
  const RDF_REIFIES_IRI = '<http://www.w3.org/1999/02/22-rdf-syntax-ns#reifies>';
359
+ const RDF_TYPE_IRI = '<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>';
360
+ const XSD_INTEGER_IRI = '<http://www.w3.org/2001/XMLSchema#integer>';
361
+ const EYMSG_NS = 'https://eyereasoner.github.io/eyeling/vocab/message#';
362
+ const EYMSG = Object.freeze({
363
+ RDFMessageStream: `<${EYMSG_NS}RDFMessageStream>`,
364
+ MessageEnvelope: `<${EYMSG_NS}MessageEnvelope>`,
365
+ envelope: `<${EYMSG_NS}envelope>`,
366
+ firstEnvelope: `<${EYMSG_NS}firstEnvelope>`,
367
+ lastEnvelope: `<${EYMSG_NS}lastEnvelope>`,
368
+ orderedEnvelopes: `<${EYMSG_NS}orderedEnvelopes>`,
369
+ messageCount: `<${EYMSG_NS}messageCount>`,
370
+ offset: `<${EYMSG_NS}offset>`,
371
+ nextEnvelope: `<${EYMSG_NS}nextEnvelope>`,
372
+ payloadGraph: `<${EYMSG_NS}payloadGraph>`,
373
+ payloadKind: `<${EYMSG_NS}payloadKind>`,
374
+ empty: `<${EYMSG_NS}empty>`,
375
+ nonEmpty: `<${EYMSG_NS}nonEmpty>`,
376
+ });
359
377
 
360
378
  function normalizeRdfCompatibility(inputText) {
361
379
  let text = String(inputText ?? '');
@@ -366,10 +384,11 @@ function normalizeRdfCompatibility(inputText) {
366
384
  // plausible top-level TriG named graph block.
367
385
  const hasTripleTerms = text.includes('<<');
368
386
  const hasVersionDirective = /^\s*(?:@version|VERSION)\s+(["'])1\.2\1\s*\.?\s*(?:#.*)?$/im.test(text);
387
+ const hasMessageVersionDirective = /^\s*(?:@version|VERSION)\s+(["'])(?:1\.1|1\.2|1\.2-basic)-messages\1\s*\.?\s*(?:#.*)?$/im.test(text);
369
388
  const hasNamedGraphCandidate = /(?:^|[.\r\n])\s*(?:GRAPH\s+)?(?:<[^>\r\n]*>|_:[A-Za-z][A-Za-z0-9_-]*|[A-Za-z][A-Za-z0-9_-]*:[^\s{};,.()[\]]*)\s*\{/m.test(text);
370
389
  const hasAnnotationSyntax = /(?:^|\s)~\s*(?:<|_:[A-Za-z]|[A-Za-z][A-Za-z0-9_-]*:|\{\|)|\{\|/.test(text);
371
390
 
372
- if (!hasTripleTerms && !hasVersionDirective && !hasNamedGraphCandidate && !hasAnnotationSyntax) return text;
391
+ if (!hasTripleTerms && !hasVersionDirective && !hasMessageVersionDirective && !hasNamedGraphCandidate && !hasAnnotationSyntax) return text;
373
392
 
374
393
  function isWordChar(ch) {
375
394
  return ch != null && /[A-Za-z0-9_:-]/.test(ch);
@@ -723,7 +742,7 @@ function normalizeRdfCompatibility(inputText) {
723
742
  }
724
743
 
725
744
  function stripVersionDirectives(s) {
726
- return s.replace(/^\s*(?:@version|VERSION)\s+(["'])1\.2\1\s*\.?\s*(?:#.*)?$/gim, '');
745
+ return s.replace(/^\s*(?:@version|VERSION)\s+(["'])(?:1\.1|1\.2|1\.2-basic)(?:-messages)?\1\s*\.?\s*(?:#.*)?$/gim, '');
727
746
  }
728
747
 
729
748
  function skipWsAndComments(s, at) {
@@ -877,10 +896,270 @@ function normalizeRdfCompatibility(inputText) {
877
896
  return out;
878
897
  }
879
898
 
899
+
900
+ function isOnlyWhitespaceAndComments(s) {
901
+ return skipWsAndComments(s, 0) >= s.length;
902
+ }
903
+
904
+
905
+ function skipOldStyleDirective(s, at) {
906
+ let i = at;
907
+ while (i < s.length) {
908
+ const ch = s[i];
909
+ if (ch === '"' || ch === "'") {
910
+ i = readStringAt(s, i).end;
911
+ continue;
912
+ }
913
+ if (ch === '<') {
914
+ i = readIriAt(s, i).end;
915
+ continue;
916
+ }
917
+ if (ch === '#') {
918
+ while (i < s.length && s[i] !== '\n' && s[i] !== '\r') i += 1;
919
+ continue;
920
+ }
921
+ i += 1;
922
+ if (ch === '.') return i;
923
+ }
924
+ return i;
925
+ }
926
+
927
+ function stripDirectivesAndCommentsForEmptiness(s) {
928
+ let out = '';
929
+ let i = 0;
930
+ let statementStart = true;
931
+ while (i < s.length) {
932
+ const ch = s[i];
933
+ if (ch === '"' || ch === "'") {
934
+ const str = readStringAt(s, i);
935
+ out += str.text;
936
+ i = str.end;
937
+ statementStart = false;
938
+ continue;
939
+ }
940
+ if (ch === '<') {
941
+ const iri = readIriAt(s, i);
942
+ out += iri.text;
943
+ i = iri.end;
944
+ statementStart = false;
945
+ continue;
946
+ }
947
+ if (ch === '#') {
948
+ while (i < s.length && s[i] !== '\n' && s[i] !== '\r') i += 1;
949
+ statementStart = true;
950
+ continue;
951
+ }
952
+ if (statementStart) {
953
+ const start = skipWsAndComments(s, i);
954
+ out += s.slice(i, start);
955
+ i = start;
956
+ if (startsWordAt(s, 'PREFIX', i) || startsWordAt(s, 'BASE', i) || startsWordAt(s, 'VERSION', i)) {
957
+ while (i < s.length && s[i] !== '\n' && s[i] !== '\r') i += 1;
958
+ statementStart = true;
959
+ continue;
960
+ }
961
+ if (s[i] === '@') {
962
+ const lower = s.slice(i, i + 9).toLowerCase();
963
+ if (lower.startsWith('@prefix') || lower.startsWith('@base') || lower.startsWith('@version')) {
964
+ i = skipOldStyleDirective(s, i);
965
+ statementStart = true;
966
+ continue;
967
+ }
968
+ }
969
+ }
970
+ out += ch;
971
+ if (ch === '.' || ch === '}' || ch === '\n' || ch === '\r') statementStart = true;
972
+ else if (!/\s/.test(ch)) statementStart = false;
973
+ i += 1;
974
+ }
975
+ return out;
976
+ }
977
+
978
+ function simpleHashText(s) {
979
+ let h = 0x811c9dc5;
980
+ for (let i = 0; i < s.length; i += 1) {
981
+ h ^= s.charCodeAt(i);
982
+ h = Math.imul(h, 0x01000193) >>> 0;
983
+ }
984
+ return h.toString(16).padStart(8, '0');
985
+ }
986
+
987
+ function rewriteMessageBlankLabels(s, messageIndex) {
988
+ let out = '';
989
+ let i = 0;
990
+ const prefix = `_:eyeling_m${String(messageIndex).padStart(3, '0')}_`;
991
+ while (i < s.length) {
992
+ const ch = s[i];
993
+ if (ch === '"' || ch === "'") {
994
+ const str = readStringAt(s, i);
995
+ out += str.text;
996
+ i = str.end;
997
+ continue;
998
+ }
999
+ if (ch === '<' && !s.startsWith('<<', i)) {
1000
+ const iri = readIriAt(s, i);
1001
+ out += iri.text;
1002
+ i = iri.end;
1003
+ continue;
1004
+ }
1005
+ if (ch === '#') {
1006
+ while (i < s.length) {
1007
+ const c = s[i++];
1008
+ out += c;
1009
+ if (c === '\n' || c === '\r') break;
1010
+ }
1011
+ continue;
1012
+ }
1013
+ if (s.startsWith('_:', i)) {
1014
+ let j = i + 2;
1015
+ while (j < s.length && !/\s/.test(s[j]) && !'{}[](),;.'.includes(s[j])) j += 1;
1016
+ const label = s.slice(i + 2, j);
1017
+ if (label) {
1018
+ out += prefix + label.replace(/[^A-Za-z0-9_]/g, '_');
1019
+ i = j;
1020
+ continue;
1021
+ }
1022
+ }
1023
+ out += ch;
1024
+ i += 1;
1025
+ }
1026
+ return out;
1027
+ }
1028
+
1029
+ function findMessageDirectiveAt(s, at) {
1030
+ if (startsWordAt(s, 'MESSAGE', at)) return { start: at, end: at + 'MESSAGE'.length };
1031
+ if (s.slice(at, at + 8).toLowerCase() === '@message' && !isWordChar(s[at + 8])) {
1032
+ let end = at + 8;
1033
+ end = skipWsAndComments(s, end);
1034
+ if (s[end] === '.') end += 1;
1035
+ return { start: at, end };
1036
+ }
1037
+ return null;
1038
+ }
1039
+
1040
+ function splitRdfMessageLog(s) {
1041
+ const chunks = [];
1042
+ let i = 0;
1043
+ let start = 0;
1044
+ let braceDepth = 0;
1045
+ let bracketDepth = 0;
1046
+ let parenDepth = 0;
1047
+ let statementStart = true;
1048
+ let sawDelimiter = false;
1049
+
1050
+ while (i < s.length) {
1051
+ const ch = s[i];
1052
+ if (ch === '"' || ch === "'") {
1053
+ i = readStringAt(s, i).end;
1054
+ statementStart = false;
1055
+ continue;
1056
+ }
1057
+ if (ch === '<' && !s.startsWith('<<', i)) {
1058
+ i = readIriAt(s, i).end;
1059
+ statementStart = false;
1060
+ continue;
1061
+ }
1062
+ if (ch === '#') {
1063
+ while (i < s.length && s[i] !== '\n' && s[i] !== '\r') i += 1;
1064
+ statementStart = true;
1065
+ continue;
1066
+ }
1067
+ if (statementStart && braceDepth === 0 && bracketDepth === 0 && parenDepth === 0) {
1068
+ const termStart = skipWsAndComments(s, i);
1069
+ const msg = findMessageDirectiveAt(s, termStart);
1070
+ if (msg) {
1071
+ chunks.push(s.slice(start, termStart));
1072
+ start = msg.end;
1073
+ i = msg.end;
1074
+ statementStart = true;
1075
+ sawDelimiter = true;
1076
+ continue;
1077
+ }
1078
+ if (termStart !== i) {
1079
+ i = termStart;
1080
+ continue;
1081
+ }
1082
+ }
1083
+ if (ch === '{') braceDepth += 1;
1084
+ else if (ch === '}' && braceDepth > 0) braceDepth -= 1;
1085
+ else if (ch === '[') bracketDepth += 1;
1086
+ else if (ch === ']' && bracketDepth > 0) bracketDepth -= 1;
1087
+ else if (ch === '(') parenDepth += 1;
1088
+ else if (ch === ')' && parenDepth > 0) parenDepth -= 1;
1089
+
1090
+ if (ch === '.' && braceDepth === 0 && bracketDepth === 0 && parenDepth === 0) statementStart = true;
1091
+ else if (ch === '\n' || ch === '\r') statementStart = true;
1092
+ else if (!/\s/.test(ch)) statementStart = false;
1093
+ i += 1;
1094
+ }
1095
+
1096
+ const tail = s.slice(start);
1097
+ if (!sawDelimiter || !isOnlyWhitespaceAndComments(tail)) chunks.push(tail);
1098
+ return chunks;
1099
+ }
1100
+
1101
+ function normalizeMessageChunk(chunk, messageIndex) {
1102
+ let body = String(chunk || '');
1103
+ if (hasTripleTerms || body.includes('<<')) body = convertTripleTerms(body);
1104
+ if (hasAnnotationSyntax || /(?:^|\s)~\s*(?:<|_:[A-Za-z]|[A-Za-z][A-Za-z0-9_-]*:|\{\|)|\{\|/.test(body)) {
1105
+ body = convertAnnotations(body);
1106
+ }
1107
+ body = normalizeNamedGraphs(body);
1108
+ body = rewriteMessageBlankLabels(body, messageIndex);
1109
+ return body.trim();
1110
+ }
1111
+
1112
+ function messageChunkHasRdf(body) {
1113
+ return !isOnlyWhitespaceAndComments(stripDirectivesAndCommentsForEmptiness(body));
1114
+ }
1115
+
1116
+ function normalizeRdfMessageLog(s) {
1117
+ const withoutVersion = stripVersionDirectives(s);
1118
+ const chunks = splitRdfMessageLog(withoutVersion);
1119
+ const hash = simpleHashText(s);
1120
+ const base = `urn:eyeling:message-log:${hash}`;
1121
+ const stream = `<${base}#stream>`;
1122
+ const envelopeIris = chunks.map((unused, idx) => `<${base}#m${String(idx + 1).padStart(3, '0')}>`);
1123
+ const payloadIris = chunks.map((unused, idx) => `<${base}#m${String(idx + 1).padStart(3, '0')}/payload>`);
1124
+ const out = [];
1125
+
1126
+ out.push(`${stream} ${RDF_TYPE_IRI} ${EYMSG.RDFMessageStream} .`);
1127
+ out.push(`${stream} ${EYMSG.messageCount} "${chunks.length}"^^${XSD_INTEGER_IRI} .`);
1128
+ if (envelopeIris.length) {
1129
+ out.push(`${stream} ${EYMSG.orderedEnvelopes} (${envelopeIris.join(' ')}) .`);
1130
+ out.push(`${stream} ${EYMSG.firstEnvelope} ${envelopeIris[0]} .`);
1131
+ out.push(`${stream} ${EYMSG.lastEnvelope} ${envelopeIris[envelopeIris.length - 1]} .`);
1132
+ }
1133
+
1134
+ for (let idx = 0; idx < chunks.length; idx += 1) {
1135
+ const n = idx + 1;
1136
+ const envelope = envelopeIris[idx];
1137
+ const payload = payloadIris[idx];
1138
+ const body = normalizeMessageChunk(chunks[idx], n);
1139
+ const hasBody = messageChunkHasRdf(body);
1140
+
1141
+ out.push(`${stream} ${EYMSG.envelope} ${envelope} .`);
1142
+ out.push(`${envelope} ${RDF_TYPE_IRI} ${EYMSG.MessageEnvelope} .`);
1143
+ out.push(`${envelope} ${EYMSG.offset} "${n}"^^${XSD_INTEGER_IRI} .`);
1144
+ out.push(`${envelope} ${EYMSG.payloadKind} ${hasBody ? EYMSG.nonEmpty : EYMSG.empty} .`);
1145
+ if (idx + 1 < envelopeIris.length) out.push(`${envelope} ${EYMSG.nextEnvelope} ${envelopeIris[idx + 1]} .`);
1146
+ if (hasBody) {
1147
+ out.push(`${envelope} ${EYMSG.payloadGraph} ${payload} .`);
1148
+ out.push(`${payload} ${LOG_NAME_OF_IRI} {`);
1149
+ out.push(body);
1150
+ out.push(`} .`);
1151
+ }
1152
+ }
1153
+
1154
+ return out.join('\n') + '\n';
1155
+ }
1156
+
1157
+ if (hasMessageVersionDirective) return normalizeRdfMessageLog(text);
1158
+
880
1159
  if (hasTripleTerms) text = convertTripleTerms(text);
881
1160
  if (hasAnnotationSyntax) text = convertAnnotations(text);
882
- if (hasVersionDirective) text = stripVersionDirectives(text);
883
- if (hasVersionDirective || hasNamedGraphCandidate) text = normalizeNamedGraphs(text);
1161
+ if (hasVersionDirective || hasMessageVersionDirective) text = stripVersionDirectives(text);
1162
+ if (hasVersionDirective || hasMessageVersionDirective || hasNamedGraphCandidate) text = normalizeNamedGraphs(text);
884
1163
  return text;
885
1164
  }
886
1165
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyeling",
3
- "version": "1.24.30",
3
+ "version": "1.24.31",
4
4
  "description": "A minimal Notation3 (N3) reasoner in JavaScript.",
5
5
  "main": "./index.js",
6
6
  "keywords": [
package/test/api.test.js CHANGED
@@ -2864,6 +2864,64 @@ PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
2864
2864
  /:audit\s+\{[\s\S]*:workOrder\s+:basedOn\s+:factoryDataset\s*\.[\s\S]*\}/m,
2865
2865
  ],
2866
2866
  },
2867
+ {
2868
+ name: 'RDF mode replays RDF 1.2 message logs into eymsg envelopes',
2869
+ opt: { proofComments: false, rdf: true },
2870
+ input: {
2871
+ sources: [
2872
+ {
2873
+ label: 'rules.n3',
2874
+ text: `@prefix : <http://example.org/msgtest#>.
2875
+ @prefix eymsg: <https://eyereasoner.github.io/eyeling/vocab/message#>.
2876
+ @prefix log: <http://www.w3.org/2000/10/swap/log#>.
2877
+ @prefix list: <http://www.w3.org/2000/10/swap/list#>.
2878
+
2879
+ {
2880
+ ?S a eymsg:RDFMessageStream;
2881
+ eymsg:messageCount ?Count;
2882
+ eymsg:orderedEnvelopes ?Envelopes;
2883
+ eymsg:firstEnvelope ?First.
2884
+ ?Envelopes list:length ?Len.
2885
+ ?First eymsg:nextEnvelope ?Second.
2886
+ ?Second eymsg:nextEnvelope ?Third;
2887
+ eymsg:payloadKind eymsg:empty.
2888
+ ?First eymsg:payloadGraph ?P1.
2889
+ ?P1 log:nameOf ?G1.
2890
+ ?G1 log:includes { ?B1 :value 1. }.
2891
+ ?Third eymsg:payloadGraph ?P3.
2892
+ ?P3 log:nameOf ?G3.
2893
+ ?G3 log:includes { ?B3 :value 2. }.
2894
+ } log:query {
2895
+ :result :messageCount ?Count;
2896
+ :listLength ?Len;
2897
+ :empty ?Second;
2898
+ :firstBlank ?B1;
2899
+ :thirdBlank ?B3.
2900
+ }.
2901
+ `,
2902
+ },
2903
+ {
2904
+ label: 'message-log.trig',
2905
+ text: `VERSION "1.2-messages"
2906
+ @prefix : <http://example.org/msgtest#> .
2907
+ _:b :value 1 .
2908
+ MESSAGE
2909
+ # empty message
2910
+ MESSAGE
2911
+ _:b :value 2 .
2912
+ `,
2913
+ },
2914
+ ],
2915
+ },
2916
+ expect: [
2917
+ /:result\s+:messageCount\s+"3"\^\^xsd:integer\s*\./m,
2918
+ /:result\s+:listLength\s+3\s*\./m,
2919
+ /:result\s+:empty\s+<urn:eyeling:message-log:[0-9a-f]+#m002>\s*\./m,
2920
+ /:result\s+:firstBlank\s+_:src2_eyeling_m001_b\s*\./m,
2921
+ /:result\s+:thirdBlank\s+_:src2_eyeling_m003_b\s*\./m,
2922
+ ],
2923
+ },
2924
+
2867
2925
  {
2868
2926
  name: 'API empty input returns empty output',
2869
2927
  opt: { proofComments: false },