eyeling 1.28.4 → 1.28.6

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/builtins.js CHANGED
@@ -15,6 +15,7 @@ const {
15
15
  LIST_NS,
16
16
  LOG_NS,
17
17
  STRING_NS,
18
+ DT_NS,
18
19
  Literal,
19
20
  Iri,
20
21
  Var,
@@ -131,6 +132,10 @@ function __assertBuiltinHandlerResult(iri, out) {
131
132
  }
132
133
  }
133
134
 
135
+ function apiTermToN3(term, prefixes = PrefixEnv.newDefault()) {
136
+ return termToN3(term, prefixes || PrefixEnv.newDefault());
137
+ }
138
+
134
139
  function __buildBuiltinRegistrationApi() {
135
140
  if (__builtinApiSingleton) return __builtinApiSingleton;
136
141
 
@@ -143,7 +148,7 @@ function __buildBuiltinRegistrationApi() {
143
148
  literalParts,
144
149
  termToJsString,
145
150
  termToJsStringDecoded,
146
- termToN3,
151
+ termToN3: apiTermToN3,
147
152
  iriValue,
148
153
  unifyTerm,
149
154
  applySubstTerm,
@@ -160,7 +165,7 @@ function __buildBuiltinRegistrationApi() {
160
165
  literalsEquivalentAsXsdString,
161
166
  materializeRdfLists,
162
167
  terms: { Literal, Iri, Var, Blank, ListTerm, OpenListTerm, GraphTerm, Triple, Rule },
163
- ns: { RDF_NS, XSD_NS, CRYPTO_NS, MATH_NS, TIME_NS, LIST_NS, LOG_NS, STRING_NS },
168
+ ns: { RDF_NS, XSD_NS, CRYPTO_NS, MATH_NS, TIME_NS, LIST_NS, LOG_NS, STRING_NS, DT_NS },
164
169
  };
165
170
 
166
171
  __builtinApiSingleton = __freezeBuiltinApi(api);
@@ -897,6 +902,63 @@ const XSD_DOUBLE_DT = XSD_NS + 'double';
897
902
  const XSD_FLOAT_DT = XSD_NS + 'float';
898
903
  const XSD_INTEGER_DT = XSD_NS + 'integer';
899
904
 
905
+ const XSD_STRING_DT = XSD_NS + 'string';
906
+ const XSD_BOOLEAN_DT = XSD_NS + 'boolean';
907
+ const XSD_DATETIME_DT = XSD_NS + 'dateTime';
908
+ const XSD_DATETIME_STAMP_DT = XSD_NS + 'dateTimeStamp';
909
+ const XSD_HEX_BINARY_DT = XSD_NS + 'hexBinary';
910
+ const XSD_BASE64_BINARY_DT = XSD_NS + 'base64Binary';
911
+ const XSD_ANY_URI_DT = XSD_NS + 'anyURI';
912
+ const RDF_LANGSTRING_DT = RDF_NS + 'langString';
913
+
914
+ const XSD_STRING_LIKE_DTS = new Set([
915
+ XSD_STRING_DT,
916
+ XSD_NS + 'normalizedString',
917
+ XSD_NS + 'token',
918
+ XSD_NS + 'language',
919
+ XSD_NS + 'Name',
920
+ XSD_NS + 'NCName',
921
+ XSD_NS + 'NMTOKEN',
922
+ ]);
923
+
924
+ const XSD_INTEGER_BOUNDS = new Map([
925
+ [XSD_NS + 'nonPositiveInteger', [null, 0n]],
926
+ [XSD_NS + 'negativeInteger', [null, -1n]],
927
+ [XSD_NS + 'long', [-(2n ** 63n), 2n ** 63n - 1n]],
928
+ [XSD_NS + 'int', [-(2n ** 31n), 2n ** 31n - 1n]],
929
+ [XSD_NS + 'short', [-(2n ** 15n), 2n ** 15n - 1n]],
930
+ [XSD_NS + 'byte', [-(2n ** 7n), 2n ** 7n - 1n]],
931
+ [XSD_NS + 'nonNegativeInteger', [0n, null]],
932
+ [XSD_NS + 'unsignedLong', [0n, 2n ** 64n - 1n]],
933
+ [XSD_NS + 'unsignedInt', [0n, 2n ** 32n - 1n]],
934
+ [XSD_NS + 'unsignedShort', [0n, 2n ** 16n - 1n]],
935
+ [XSD_NS + 'unsignedByte', [0n, 2n ** 8n - 1n]],
936
+ [XSD_NS + 'positiveInteger', [1n, null]],
937
+ ]);
938
+
939
+ const XSD_DATATYPE_BUILTIN_DTS = new Set([
940
+ RDF_LANGSTRING_DT,
941
+ XSD_STRING_DT,
942
+ XSD_NS + 'normalizedString',
943
+ XSD_NS + 'token',
944
+ XSD_NS + 'language',
945
+ XSD_NS + 'Name',
946
+ XSD_NS + 'NCName',
947
+ XSD_NS + 'NMTOKEN',
948
+ XSD_BOOLEAN_DT,
949
+ XSD_DECIMAL_DT,
950
+ XSD_INTEGER_DT,
951
+ XSD_FLOAT_DT,
952
+ XSD_DOUBLE_DT,
953
+ XSD_HEX_BINARY_DT,
954
+ XSD_BASE64_BINARY_DT,
955
+ XSD_ANY_URI_DT,
956
+ XSD_DATETIME_DT,
957
+ XSD_DATETIME_STAMP_DT,
958
+ ...XSD_INTEGER_BOUNDS.keys(),
959
+ ]);
960
+
961
+
900
962
  // Integer-derived datatypes from XML Schema Part 2 (and commonly used ones).
901
963
  const XSD_INTEGER_DERIVED_DTS = new Set([
902
964
  XSD_INTEGER_DT,
@@ -952,6 +1014,537 @@ function inferDatatypeForShorthandLexical(lex) {
952
1014
  return null;
953
1015
  }
954
1016
 
1017
+
1018
+ function extractLiteralLanguageTag(litVal) {
1019
+ if (typeof litVal !== 'string' || !literalHasLangTag(litVal)) return null;
1020
+ const lastQuote = litVal.lastIndexOf('"');
1021
+ if (lastQuote < 0) return null;
1022
+ const after = lastQuote + 1;
1023
+ if (after >= litVal.length || litVal[after] !== '@') return null;
1024
+ const tag = litVal.slice(after + 1);
1025
+ if (!/^[A-Za-z]+(?:-[A-Za-z0-9]+)*(?:--(?:ltr|rtl))?$/.test(tag)) return null;
1026
+ return tag;
1027
+ }
1028
+
1029
+ function literalLexicalValue(litVal) {
1030
+ const [lex] = literalParts(litVal);
1031
+ if (isQuotedLexical(lex)) return decodeN3StringEscapes(stripQuotes(lex));
1032
+ return typeof lex === 'string' ? lex : String(lex);
1033
+ }
1034
+
1035
+ function literalDatatypeIri(t) {
1036
+ if (!(t instanceof Literal)) return null;
1037
+ if (literalHasLangTag(t.value)) return RDF_LANGSTRING_DT;
1038
+
1039
+ const [lex, dt] = literalParts(t.value);
1040
+ if (dt !== null) return dt;
1041
+ if (isPlainStringLiteralValue(t.value)) return XSD_STRING_DT;
1042
+
1043
+ return inferDatatypeForShorthandLexical(lex) || XSD_STRING_DT;
1044
+ }
1045
+
1046
+ function isSupportedDatatypeIri(dt) {
1047
+ return XSD_DATATYPE_BUILTIN_DTS.has(dt);
1048
+ }
1049
+
1050
+ function makeDatatypeTypedLiteral(lexical, dt) {
1051
+ return internLiteral(`${JSON.stringify(String(lexical))}^^<${dt}>`);
1052
+ }
1053
+
1054
+ function makeLangStringLiteral(lexical, lang) {
1055
+ return internLiteral(`${JSON.stringify(String(lexical))}@${String(lang).toLowerCase()}`);
1056
+ }
1057
+
1058
+ function canonicalIntegerLex(v) {
1059
+ return v === 0n ? '0' : v.toString();
1060
+ }
1061
+
1062
+ function canonicalDecimalLexFromParts(num, scale) {
1063
+ if (num === 0n) return '0.0';
1064
+ const neg = num < 0n;
1065
+ let digits = (neg ? -num : num).toString();
1066
+ if (scale > 0) {
1067
+ while (digits.length <= scale) digits = `0${digits}`;
1068
+ }
1069
+ let intPart = scale > 0 ? digits.slice(0, digits.length - scale) : digits;
1070
+ let fracPart = scale > 0 ? digits.slice(digits.length - scale) : '';
1071
+ intPart = intPart.replace(/^0+(?=\d)/, '') || '0';
1072
+ fracPart = fracPart.replace(/0+$/g, '');
1073
+ if (!fracPart) fracPart = '0';
1074
+ return `${neg ? '-' : ''}${intPart}.${fracPart}`;
1075
+ }
1076
+
1077
+ function canonicalFloatDoubleLex(n) {
1078
+ const special = formatXsdFloatSpecialLex(n);
1079
+ if (special !== null) return special;
1080
+ if (Object.is(n, -0)) return '0';
1081
+ return String(n).replace('e', 'E');
1082
+ }
1083
+
1084
+ function parseStringLikeDatatypeValue(lexical, dt) {
1085
+ let value = String(lexical);
1086
+
1087
+ if (dt === XSD_NS + 'normalizedString') {
1088
+ value = value.replace(/[\t\n\r]/g, ' ');
1089
+ } else if (dt !== XSD_STRING_DT) {
1090
+ value = value.replace(/[\t\n\r ]+/g, ' ').trim();
1091
+ }
1092
+
1093
+ if (dt === XSD_NS + 'language') {
1094
+ if (!/^[A-Za-z]{1,8}(?:-[A-Za-z0-9]{1,8})*$/.test(value)) return null;
1095
+ value = value.toLowerCase();
1096
+ } else if (dt === XSD_NS + 'Name') {
1097
+ if (!/^[:_\p{L}][:_\p{L}\p{N}.\-\u00B7\u0300-\u036F\u203F-\u2040]*$/u.test(value)) return null;
1098
+ } else if (dt === XSD_NS + 'NCName') {
1099
+ if (!/^[_\p{L}][_\p{L}\p{N}.\-\u00B7\u0300-\u036F\u203F-\u2040]*$/u.test(value)) return null;
1100
+ } else if (dt === XSD_NS + 'NMTOKEN') {
1101
+ if (!/^[:_\p{L}\p{N}.\-\u00B7\u0300-\u036F\u203F-\u2040]+$/u.test(value)) return null;
1102
+ }
1103
+
1104
+ return {
1105
+ family: 'string',
1106
+ dt,
1107
+ value,
1108
+ canonicalLex: value,
1109
+ canonicalLiteral: makeDatatypeTypedLiteral(value, dt),
1110
+ comparable: true,
1111
+ };
1112
+ }
1113
+
1114
+ function parseBooleanDatatypeValue(lexical) {
1115
+ const s = String(lexical);
1116
+ if (s === 'true' || s === '1') {
1117
+ return {
1118
+ family: 'boolean',
1119
+ dt: XSD_BOOLEAN_DT,
1120
+ value: true,
1121
+ canonicalLex: 'true',
1122
+ canonicalLiteral: makeDatatypeTypedLiteral('true', XSD_BOOLEAN_DT),
1123
+ comparable: true,
1124
+ };
1125
+ }
1126
+ if (s === 'false' || s === '0') {
1127
+ return {
1128
+ family: 'boolean',
1129
+ dt: XSD_BOOLEAN_DT,
1130
+ value: false,
1131
+ canonicalLex: 'false',
1132
+ canonicalLiteral: makeDatatypeTypedLiteral('false', XSD_BOOLEAN_DT),
1133
+ comparable: true,
1134
+ };
1135
+ }
1136
+ return null;
1137
+ }
1138
+
1139
+ function parseIntegerDatatypeValue(lexical, dt) {
1140
+ const s = String(lexical);
1141
+ if (!/^[+-]?\d+$/.test(s)) return null;
1142
+ let value;
1143
+ try {
1144
+ value = BigInt(s);
1145
+ } catch (_) {
1146
+ return null;
1147
+ }
1148
+
1149
+ const bounds = XSD_INTEGER_BOUNDS.get(dt);
1150
+ if (bounds) {
1151
+ const [min, max] = bounds;
1152
+ if (min !== null && value < min) return null;
1153
+ if (max !== null && value > max) return null;
1154
+ }
1155
+
1156
+ const canonicalLex = canonicalIntegerLex(value);
1157
+ return {
1158
+ family: 'numeric',
1159
+ numericKind: 'decimalExact',
1160
+ dt,
1161
+ decimalNum: value,
1162
+ decimalScale: 0,
1163
+ value,
1164
+ canonicalLex,
1165
+ canonicalLiteral: makeDatatypeTypedLiteral(canonicalLex, dt),
1166
+ comparable: true,
1167
+ };
1168
+ }
1169
+
1170
+ function parseDecimalDatatypeValue(lexical) {
1171
+ const parsed = parseXsdDecimalToBigIntScale(String(lexical));
1172
+ if (!parsed) return null;
1173
+ const canonicalLex = canonicalDecimalLexFromParts(parsed.num, parsed.scale);
1174
+ return {
1175
+ family: 'numeric',
1176
+ numericKind: 'decimalExact',
1177
+ dt: XSD_DECIMAL_DT,
1178
+ decimalNum: parsed.num,
1179
+ decimalScale: parsed.scale,
1180
+ canonicalLex,
1181
+ canonicalLiteral: makeDatatypeTypedLiteral(canonicalLex, XSD_DECIMAL_DT),
1182
+ comparable: true,
1183
+ };
1184
+ }
1185
+
1186
+ function isXsdFloatDoubleLexical(s) {
1187
+ if (parseXsdFloatSpecialLex(s) !== null) return true;
1188
+ return /^[+-]?(?:(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?|\d+(?:[eE][+-]?\d+))$/.test(s);
1189
+ }
1190
+
1191
+ function parseFloatDoubleDatatypeValue(lexical, dt) {
1192
+ const s = String(lexical);
1193
+ if (!isXsdFloatDoubleLexical(s)) return null;
1194
+ const special = parseXsdFloatSpecialLex(s);
1195
+ const value = special !== null ? special : Number(s);
1196
+ if (Number.isNaN(value) && s !== 'NaN') return null;
1197
+ const canonicalLex = canonicalFloatDoubleLex(value);
1198
+ return {
1199
+ family: 'numeric',
1200
+ numericKind: 'floatDouble',
1201
+ dt,
1202
+ value,
1203
+ canonicalLex,
1204
+ canonicalLiteral: makeDatatypeTypedLiteral(canonicalLex, dt),
1205
+ comparable: !Number.isNaN(value),
1206
+ };
1207
+ }
1208
+
1209
+ function normalizeBase64Lexical(s) {
1210
+ return String(s).replace(/[\t\n\r ]+/g, '');
1211
+ }
1212
+
1213
+ const BASE64_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
1214
+ const BASE64_LOOKUP = (() => {
1215
+ const m = Object.create(null);
1216
+ for (let i = 0; i < BASE64_ALPHABET.length; i++) m[BASE64_ALPHABET[i]] = i;
1217
+ return m;
1218
+ })();
1219
+
1220
+ function decodeBase64Bytes(input) {
1221
+ const s = normalizeBase64Lexical(input);
1222
+ if (s.length === 0) return [];
1223
+ if (s.length % 4 !== 0) return null;
1224
+ if (!/^[A-Za-z0-9+/]*={0,2}$/.test(s)) return null;
1225
+ const pad = s.endsWith('==') ? 2 : s.endsWith('=') ? 1 : 0;
1226
+ if (pad && s.slice(0, -pad).includes('=')) return null;
1227
+
1228
+ const out = [];
1229
+ for (let i = 0; i < s.length; i += 4) {
1230
+ const c0 = BASE64_LOOKUP[s[i]];
1231
+ const c1 = BASE64_LOOKUP[s[i + 1]];
1232
+ const c2 = s[i + 2] === '=' ? 0 : BASE64_LOOKUP[s[i + 2]];
1233
+ const c3 = s[i + 3] === '=' ? 0 : BASE64_LOOKUP[s[i + 3]];
1234
+ if (c0 == null || c1 == null || c2 == null || c3 == null) return null;
1235
+ const n = (c0 << 18) | (c1 << 12) | (c2 << 6) | c3;
1236
+ out.push((n >> 16) & 0xff);
1237
+ if (s[i + 2] !== '=') out.push((n >> 8) & 0xff);
1238
+ if (s[i + 3] !== '=') out.push(n & 0xff);
1239
+ if ((s[i + 2] === '=' || s[i + 3] === '=') && i + 4 !== s.length) return null;
1240
+ }
1241
+ return out;
1242
+ }
1243
+
1244
+ function encodeBase64Bytes(bytes) {
1245
+ let out = '';
1246
+ for (let i = 0; i < bytes.length; i += 3) {
1247
+ const b0 = bytes[i];
1248
+ const b1 = i + 1 < bytes.length ? bytes[i + 1] : 0;
1249
+ const b2 = i + 2 < bytes.length ? bytes[i + 2] : 0;
1250
+ const n = (b0 << 16) | (b1 << 8) | b2;
1251
+ out += BASE64_ALPHABET[(n >> 18) & 63];
1252
+ out += BASE64_ALPHABET[(n >> 12) & 63];
1253
+ out += i + 1 < bytes.length ? BASE64_ALPHABET[(n >> 6) & 63] : '=';
1254
+ out += i + 2 < bytes.length ? BASE64_ALPHABET[n & 63] : '=';
1255
+ }
1256
+ return out;
1257
+ }
1258
+
1259
+ function parseBinaryDatatypeValue(lexical, dt) {
1260
+ const s = String(lexical);
1261
+ let bytes;
1262
+ let canonicalLex;
1263
+ if (dt === XSD_HEX_BINARY_DT) {
1264
+ if (!/^(?:[0-9A-Fa-f]{2})*$/.test(s)) return null;
1265
+ bytes = [];
1266
+ for (let i = 0; i < s.length; i += 2) bytes.push(parseInt(s.slice(i, i + 2), 16));
1267
+ canonicalLex = s.toUpperCase();
1268
+ } else {
1269
+ bytes = decodeBase64Bytes(s);
1270
+ if (bytes === null) return null;
1271
+ canonicalLex = encodeBase64Bytes(bytes);
1272
+ }
1273
+
1274
+ return {
1275
+ family: 'binary',
1276
+ dt,
1277
+ bytes,
1278
+ canonicalLex,
1279
+ canonicalLiteral: makeDatatypeTypedLiteral(canonicalLex, dt),
1280
+ comparable: true,
1281
+ };
1282
+ }
1283
+
1284
+ function parseAnyUriDatatypeValue(lexical) {
1285
+ const value = String(lexical);
1286
+ if (/[\u0000-\u001F\u007F]/.test(value)) return null;
1287
+ return {
1288
+ family: 'anyURI',
1289
+ dt: XSD_ANY_URI_DT,
1290
+ value,
1291
+ canonicalLex: value,
1292
+ canonicalLiteral: makeDatatypeTypedLiteral(value, XSD_ANY_URI_DT),
1293
+ comparable: true,
1294
+ };
1295
+ }
1296
+
1297
+ function isLeapYearNumber(year) {
1298
+ if (year % 400 === 0) return true;
1299
+ if (year % 100 === 0) return false;
1300
+ return year % 4 === 0;
1301
+ }
1302
+
1303
+ function daysInMonthNumber(year, month) {
1304
+ if (month === 2) return isLeapYearNumber(year) ? 29 : 28;
1305
+ return [4, 6, 9, 11].includes(month) ? 30 : 31;
1306
+ }
1307
+
1308
+ function floorDivNumber(a, b) {
1309
+ return Math.floor(a / b);
1310
+ }
1311
+
1312
+ function daysFromCivilNumber(year, month, day) {
1313
+ let y = year;
1314
+ y -= month <= 2 ? 1 : 0;
1315
+ const era = floorDivNumber(y, 400);
1316
+ const yoe = y - era * 400;
1317
+ const mp = month + (month > 2 ? -3 : 9);
1318
+ const doy = Math.floor((153 * mp + 2) / 5) + day - 1;
1319
+ const doe = yoe * 365 + Math.floor(yoe / 4) - Math.floor(yoe / 100) + doy;
1320
+ return era * 146097 + doe - 719468;
1321
+ }
1322
+
1323
+ function parseDateTimeDatatypeValue(lexical, dt) {
1324
+ const s = String(lexical);
1325
+ const m = /^(-?\d{4,})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d+))?(Z|[+-]\d{2}:\d{2})?$/.exec(s);
1326
+ if (!m) return null;
1327
+
1328
+ const year = Number(m[1]);
1329
+ const month = Number(m[2]);
1330
+ let day = Number(m[3]);
1331
+ let hour = Number(m[4]);
1332
+ const minute = Number(m[5]);
1333
+ const second = Number(m[6]);
1334
+ let frac = m[7] || '';
1335
+ const tz = m[8] || null;
1336
+ if (!Number.isSafeInteger(year)) return null;
1337
+ if (dt === XSD_DATETIME_STAMP_DT && !tz) return null;
1338
+ if (!(month >= 1 && month <= 12)) return null;
1339
+ if (!(day >= 1 && day <= daysInMonthNumber(year, month))) return null;
1340
+ if (!(minute >= 0 && minute <= 59)) return null;
1341
+ if (!(second >= 0 && second <= 59)) return null;
1342
+ if (hour === 24) {
1343
+ if (minute !== 0 || second !== 0 || /[1-9]/.test(frac)) return null;
1344
+ hour = 0;
1345
+ const d = new Date(Date.UTC(year, month - 1, day));
1346
+ d.setUTCFullYear(year);
1347
+ d.setUTCDate(d.getUTCDate() + 1);
1348
+ day = d.getUTCDate();
1349
+ } else if (!(hour >= 0 && hour <= 23)) {
1350
+ return null;
1351
+ }
1352
+
1353
+ frac = frac.replace(/0+$/g, '');
1354
+ const fracScale = frac.length;
1355
+ const fracNum = frac ? BigInt(frac) : 0n;
1356
+
1357
+ let offsetMinutes = null;
1358
+ if (tz) {
1359
+ if (tz === 'Z') {
1360
+ offsetMinutes = 0;
1361
+ } else {
1362
+ const sign = tz[0] === '-' ? -1 : 1;
1363
+ const hh = Number(tz.slice(1, 3));
1364
+ const mm = Number(tz.slice(4, 6));
1365
+ if (!(hh >= 0 && hh <= 14) || !(mm >= 0 && mm <= 59)) return null;
1366
+ if (hh === 14 && mm !== 0) return null;
1367
+ offsetMinutes = sign * (hh * 60 + mm);
1368
+ }
1369
+ }
1370
+
1371
+ const dayCount = daysFromCivilNumber(year, month, day);
1372
+ let totalSeconds = BigInt(dayCount) * 86400n + BigInt(hour * 3600 + minute * 60 + second);
1373
+ if (offsetMinutes !== null) totalSeconds -= BigInt(offsetMinutes * 60);
1374
+
1375
+ const localKey = `${m[1]}-${m[2]}-${String(day).padStart(2, '0')}T${String(hour).padStart(2, '0')}:${m[5]}:${m[6]}${frac ? `.${frac}` : ''}`;
1376
+ const canonicalLex = canonicalDateTimeLex(totalSeconds, fracNum, fracScale, offsetMinutes !== null, localKey);
1377
+ return {
1378
+ family: 'dateTime',
1379
+ dt,
1380
+ hasTimezone: offsetMinutes !== null,
1381
+ totalSeconds,
1382
+ fracNum,
1383
+ fracScale,
1384
+ localKey,
1385
+ canonicalLex,
1386
+ canonicalLiteral: makeDatatypeTypedLiteral(canonicalLex, dt),
1387
+ comparable: true,
1388
+ };
1389
+ }
1390
+
1391
+ function canonicalDateTimeLex(totalSeconds, fracNum, fracScale, hasTimezone, localKey) {
1392
+ const frac = fracScale > 0 ? `.${fracNum.toString().padStart(fracScale, '0')}` : '';
1393
+ if (!hasTimezone) return localKey;
1394
+
1395
+ const minSafeSeconds = BigInt(Math.ceil(Number.MIN_SAFE_INTEGER / 1000));
1396
+ const maxSafeSeconds = BigInt(Math.floor(Number.MAX_SAFE_INTEGER / 1000));
1397
+ if (totalSeconds < minSafeSeconds || totalSeconds > maxSafeSeconds) return `${localKey}Z`;
1398
+
1399
+ const d = new Date(Number(totalSeconds) * 1000);
1400
+ if (Number.isNaN(d.getTime())) return `${localKey}Z`;
1401
+ let iso = d.toISOString().replace(/\.000Z$/, 'Z');
1402
+ if (frac) iso = iso.replace('Z', `${frac}Z`);
1403
+ return iso;
1404
+ }
1405
+
1406
+ function parseLangStringDatatypeValue(t, lexical) {
1407
+ const tag = extractLiteralLanguageTag(t.value);
1408
+ if (tag === null) return null;
1409
+ return {
1410
+ family: 'langString',
1411
+ dt: RDF_LANGSTRING_DT,
1412
+ value: String(lexical),
1413
+ lang: tag.toLowerCase(),
1414
+ canonicalLex: String(lexical),
1415
+ canonicalLiteral: makeLangStringLiteral(lexical, tag),
1416
+ comparable: true,
1417
+ };
1418
+ }
1419
+
1420
+ function parseDatatypeValueForDatatype(t, dt) {
1421
+ if (!(t instanceof Literal) || typeof dt !== 'string' || !isSupportedDatatypeIri(dt)) return null;
1422
+ const lexical = literalLexicalValue(t.value);
1423
+
1424
+ if (dt === RDF_LANGSTRING_DT) return parseLangStringDatatypeValue(t, lexical);
1425
+ if (literalHasLangTag(t.value)) return null;
1426
+ if (XSD_STRING_LIKE_DTS.has(dt)) return parseStringLikeDatatypeValue(lexical, dt);
1427
+ if (dt === XSD_BOOLEAN_DT) return parseBooleanDatatypeValue(lexical);
1428
+ if (dt === XSD_INTEGER_DT || XSD_INTEGER_BOUNDS.has(dt)) return parseIntegerDatatypeValue(lexical, dt);
1429
+ if (dt === XSD_DECIMAL_DT) return parseDecimalDatatypeValue(lexical);
1430
+ if (dt === XSD_FLOAT_DT || dt === XSD_DOUBLE_DT) return parseFloatDoubleDatatypeValue(lexical, dt);
1431
+ if (dt === XSD_HEX_BINARY_DT || dt === XSD_BASE64_BINARY_DT) return parseBinaryDatatypeValue(lexical, dt);
1432
+ if (dt === XSD_ANY_URI_DT) return parseAnyUriDatatypeValue(lexical);
1433
+ if (dt === XSD_DATETIME_DT || dt === XSD_DATETIME_STAMP_DT) return parseDateTimeDatatypeValue(lexical, dt);
1434
+
1435
+ return null;
1436
+ }
1437
+
1438
+ function parseDatatypeValue(t) {
1439
+ const dt = literalDatatypeIri(t);
1440
+ return dt === null ? null : parseDatatypeValueForDatatype(t, dt);
1441
+ }
1442
+
1443
+ function compareDecimalExactValues(a, b) {
1444
+ const scale = Math.max(a.decimalScale, b.decimalScale);
1445
+ return a.decimalNum * pow10n(scale - a.decimalScale) === b.decimalNum * pow10n(scale - b.decimalScale);
1446
+ }
1447
+
1448
+ function datatypeValuesSame(a, b) {
1449
+ if (!a || !b || !a.comparable || !b.comparable) return false;
1450
+ if (a.family === 'langString' || b.family === 'langString') {
1451
+ return a.family === b.family && a.value === b.value && a.lang === b.lang;
1452
+ }
1453
+ if (a.family === 'string' && b.family === 'string') return a.value === b.value;
1454
+ if (a.family === 'boolean' && b.family === 'boolean') return a.value === b.value;
1455
+ if (a.family === 'anyURI' && b.family === 'anyURI') return a.value === b.value;
1456
+ if (a.family === 'binary' && b.family === 'binary') {
1457
+ if (a.bytes.length !== b.bytes.length) return false;
1458
+ for (let i = 0; i < a.bytes.length; i++) if (a.bytes[i] !== b.bytes[i]) return false;
1459
+ return true;
1460
+ }
1461
+ if (a.family === 'numeric' && b.family === 'numeric') {
1462
+ if (a.numericKind === 'decimalExact' && b.numericKind === 'decimalExact') return compareDecimalExactValues(a, b);
1463
+ const av = a.numericKind === 'decimalExact' ? Number(a.decimalNum) / 10 ** a.decimalScale : a.value;
1464
+ const bv = b.numericKind === 'decimalExact' ? Number(b.decimalNum) / 10 ** b.decimalScale : b.value;
1465
+ return !Number.isNaN(av) && !Number.isNaN(bv) && av === bv;
1466
+ }
1467
+ if (a.family === 'dateTime' && b.family === 'dateTime') {
1468
+ if (a.hasTimezone !== b.hasTimezone) return false;
1469
+ if (!a.hasTimezone) return a.localKey === b.localKey;
1470
+ if (a.totalSeconds !== b.totalSeconds) return false;
1471
+ const scale = Math.max(a.fracScale, b.fracScale);
1472
+ return a.fracNum * pow10n(scale - a.fracScale) === b.fracNum * pow10n(scale - b.fracScale);
1473
+ }
1474
+ return false;
1475
+ }
1476
+
1477
+ function datatypeValuesComparable(a, b) {
1478
+ if (!a || !b || !a.comparable || !b.comparable) return false;
1479
+ if (a.family === 'dateTime' && b.family === 'dateTime' && a.hasTimezone !== b.hasTimezone) return false;
1480
+ if (a.family === 'langString' || b.family === 'langString') return a.family === b.family;
1481
+ if (a.family === 'string' || b.family === 'string') return a.family === b.family;
1482
+ return a.family === b.family;
1483
+ }
1484
+
1485
+ function evalBindBuiltinObject(outTerm, valueTerm, subst) {
1486
+ if (outTerm instanceof Var) {
1487
+ const s2 = __cloneSubst(subst);
1488
+ s2[outTerm.name] = valueTerm;
1489
+ return [s2];
1490
+ }
1491
+ if (outTerm instanceof Blank) return [__cloneSubst(subst)];
1492
+ const s2 = unifyTerm(outTerm, valueTerm, subst);
1493
+ return s2 !== null ? [s2] : [];
1494
+ }
1495
+
1496
+ function evalDatatypeInspectionBuiltin(g, subst, kind) {
1497
+ if (!(g.s instanceof Literal)) return [];
1498
+
1499
+ let valueTerm;
1500
+ if (kind === 'datatype') {
1501
+ const dt = literalDatatypeIri(g.s);
1502
+ if (dt === null) return [];
1503
+ valueTerm = internIri(dt);
1504
+ } else if (kind === 'lexicalForm') {
1505
+ valueTerm = makeStringLiteral(literalLexicalValue(g.s.value));
1506
+ } else if (kind === 'language') {
1507
+ const tag = extractLiteralLanguageTag(g.s.value);
1508
+ if (tag === null) return [];
1509
+ valueTerm = makeStringLiteral(tag);
1510
+ } else if (kind === 'canonicalLiteral') {
1511
+ const info = parseDatatypeValue(g.s);
1512
+ if (!info) return [];
1513
+ valueTerm = info.canonicalLiteral;
1514
+ } else {
1515
+ return [];
1516
+ }
1517
+
1518
+ return evalBindBuiltinObject(g.o, valueTerm, subst);
1519
+ }
1520
+
1521
+ function evalDatatypeValidityBuiltin(g, subst, positive) {
1522
+ if (!(g.s instanceof Literal)) return [];
1523
+
1524
+ let dt = null;
1525
+ if (g.o instanceof Iri) dt = g.o.value;
1526
+ else if (g.o instanceof Var) dt = literalDatatypeIri(g.s);
1527
+ else return [];
1528
+
1529
+ if (dt === null || !isSupportedDatatypeIri(dt)) return [];
1530
+ const ok = parseDatatypeValueForDatatype(g.s, dt) !== null;
1531
+ if (positive && !ok) return [];
1532
+ if (!positive && ok) return [];
1533
+
1534
+ if (g.o instanceof Var) return evalBindBuiltinObject(g.o, internIri(dt), subst);
1535
+ return [__cloneSubst(subst)];
1536
+ }
1537
+
1538
+ function evalDatatypeValueComparisonBuiltin(g, subst, same) {
1539
+ if (!(g.s instanceof Literal) || !(g.o instanceof Literal)) return [];
1540
+ const a = parseDatatypeValue(g.s);
1541
+ const b = parseDatatypeValue(g.o);
1542
+ if (!datatypeValuesComparable(a, b)) return [];
1543
+ const eq = datatypeValuesSame(a, b);
1544
+ if (same ? eq : !eq) return [__cloneSubst(subst)];
1545
+ return [];
1546
+ }
1547
+
955
1548
  // ===========================================================================
956
1549
  // Math builtin helpers
957
1550
  // ===========================================================================
@@ -2100,6 +2693,19 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
2100
2693
  const registeredBuiltinResult = __evalRegisteredBuiltin(pv, g, subst, facts, backRules, depth, varGen, maxResults);
2101
2694
  if (registeredBuiltinResult !== null) return registeredBuiltinResult;
2102
2695
 
2696
+ // -----------------------------------------------------------------
2697
+ // 4.0 datatype: builtins (Eyeling datatype namespace)
2698
+ // -----------------------------------------------------------------
2699
+
2700
+ if (pv === DT_NS + 'datatype') return evalDatatypeInspectionBuiltin(g, subst, 'datatype');
2701
+ if (pv === DT_NS + 'lexicalForm') return evalDatatypeInspectionBuiltin(g, subst, 'lexicalForm');
2702
+ if (pv === DT_NS + 'language') return evalDatatypeInspectionBuiltin(g, subst, 'language');
2703
+ if (pv === DT_NS + 'canonicalLiteral') return evalDatatypeInspectionBuiltin(g, subst, 'canonicalLiteral');
2704
+ if (pv === DT_NS + 'validForDatatype') return evalDatatypeValidityBuiltin(g, subst, true);
2705
+ if (pv === DT_NS + 'invalidForDatatype') return evalDatatypeValidityBuiltin(g, subst, false);
2706
+ if (pv === DT_NS + 'sameValueAs') return evalDatatypeValueComparisonBuiltin(g, subst, true);
2707
+ if (pv === DT_NS + 'differentValueFrom') return evalDatatypeValueComparisonBuiltin(g, subst, false);
2708
+
2103
2709
  // -----------------------------------------------------------------
2104
2710
  // 4.1 crypto: builtins
2105
2711
  // -----------------------------------------------------------------
@@ -4425,6 +5031,7 @@ function isBuiltinPred(p) {
4425
5031
  v.startsWith(MATH_NS) ||
4426
5032
  v.startsWith(LOG_NS) ||
4427
5033
  v.startsWith(STRING_NS) ||
5034
+ v.startsWith(DT_NS) ||
4428
5035
  v.startsWith(TIME_NS) ||
4429
5036
  v.startsWith(LIST_NS)
4430
5037
  );
package/lib/prelude.js CHANGED
@@ -21,6 +21,7 @@ const TIME_NS = 'http://www.w3.org/2000/10/swap/time#';
21
21
  const LIST_NS = 'http://www.w3.org/2000/10/swap/list#';
22
22
  const LOG_NS = 'http://www.w3.org/2000/10/swap/log#';
23
23
  const STRING_NS = 'http://www.w3.org/2000/10/swap/string#';
24
+ const DT_NS = 'https://eyereasoner.github.io/eyeling/datatype#';
24
25
  const SKOLEM_NS = 'https://eyereasoner.github.io/.well-known/genid/';
25
26
  const RDF_JSON_DT = RDF_NS + 'JSON';
26
27
 
@@ -539,6 +540,7 @@ class PrefixEnv {
539
540
  m['string'] = STRING_NS;
540
541
  m['list'] = LIST_NS;
541
542
  m['time'] = TIME_NS;
543
+ m['dt'] = DT_NS;
542
544
  m['genid'] = SKOLEM_NS;
543
545
  m[''] = ''; // empty prefix default namespace
544
546
  return new PrefixEnv(m, ''); // base IRI starts empty
@@ -723,6 +725,7 @@ module.exports = {
723
725
  LIST_NS,
724
726
  LOG_NS,
725
727
  STRING_NS,
728
+ DT_NS,
726
729
  SKOLEM_NS,
727
730
  RDF_JSON_DT,
728
731
  resolveIriRef,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyeling",
3
- "version": "1.28.4",
3
+ "version": "1.28.6",
4
4
  "description": "A minimal Notation3 (N3) reasoner in JavaScript.",
5
5
  "main": "./index.js",
6
6
  "keywords": [