eyeling 1.32.0 → 1.32.1

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
@@ -8,6 +8,8 @@
8
8
 
9
9
  const {
10
10
  RDF_NS,
11
+ RDFS_NS,
12
+ OWL_NS,
11
13
  XSD_NS,
12
14
  CRYPTO_NS,
13
15
  MATH_NS,
@@ -1035,6 +1037,9 @@ const XSD_HEX_BINARY_DT = XSD_NS + 'hexBinary';
1035
1037
  const XSD_BASE64_BINARY_DT = XSD_NS + 'base64Binary';
1036
1038
  const XSD_ANY_URI_DT = XSD_NS + 'anyURI';
1037
1039
  const RDF_LANGSTRING_DT = RDF_NS + 'langString';
1040
+ const RDF_PLAIN_LITERAL_DT = RDF_NS + 'PlainLiteral';
1041
+ const RDF_XML_LITERAL_DT = RDF_NS + 'XMLLiteral';
1042
+ const RDFS_LITERAL_DT = RDFS_NS + 'Literal';
1038
1043
 
1039
1044
  const XSD_STRING_LIKE_DTS = new Set([
1040
1045
  XSD_STRING_DT,
@@ -1063,6 +1068,9 @@ const XSD_INTEGER_BOUNDS = new Map([
1063
1068
 
1064
1069
  const XSD_DATATYPE_BUILTIN_DTS = new Set([
1065
1070
  RDF_LANGSTRING_DT,
1071
+ RDF_PLAIN_LITERAL_DT,
1072
+ RDF_XML_LITERAL_DT,
1073
+ RDFS_LITERAL_DT,
1066
1074
  XSD_STRING_DT,
1067
1075
  XSD_NS + 'normalizedString',
1068
1076
  XSD_NS + 'token',
@@ -1084,6 +1092,11 @@ const XSD_DATATYPE_BUILTIN_DTS = new Set([
1084
1092
  ]);
1085
1093
 
1086
1094
 
1095
+ const XSD_FLOAT_MAX_VALUE = 3.4028234663852886e38;
1096
+ const XSD_FLOAT_MIN_POSITIVE_VALUE = 1.401298464324817e-45;
1097
+ const XSD_DOUBLE_MAX_VALUE = Number.MAX_VALUE;
1098
+ const XSD_DOUBLE_MIN_POSITIVE_VALUE = Number.MIN_VALUE;
1099
+
1087
1100
  // Integer-derived datatypes from XML Schema Part 2 (and commonly used ones).
1088
1101
  const XSD_INTEGER_DERIVED_DTS = new Set([
1089
1102
  XSD_INTEGER_DT,
@@ -1142,13 +1155,31 @@ function inferDatatypeForShorthandLexical(lex) {
1142
1155
 
1143
1156
  function extractLiteralLanguageTag(litVal) {
1144
1157
  if (typeof litVal !== 'string' || !literalHasLangTag(litVal)) return null;
1145
- const lastQuote = litVal.lastIndexOf('"');
1158
+
1159
+ let lastQuote;
1160
+ let quoteLen;
1161
+ if (litVal.startsWith('\"\"\"')) {
1162
+ lastQuote = litVal.lastIndexOf('\"\"\"');
1163
+ quoteLen = 3;
1164
+ } else {
1165
+ lastQuote = litVal.lastIndexOf('"');
1166
+ quoteLen = 1;
1167
+ }
1168
+
1146
1169
  if (lastQuote < 0) return null;
1147
- const after = lastQuote + 1;
1170
+ const after = lastQuote + quoteLen;
1148
1171
  if (after >= litVal.length || litVal[after] !== '@') return null;
1149
1172
  const tag = litVal.slice(after + 1);
1150
- if (!/^[A-Za-z]+(?:-[A-Za-z0-9]+)*(?:--(?:ltr|rtl))?$/.test(tag)) return null;
1151
- return tag;
1173
+ if (!isValidLanguageTag(tag, true)) return null;
1174
+ return tag.toLowerCase();
1175
+ }
1176
+
1177
+ function isValidLanguageTag(tag, allowDirectionalSuffix) {
1178
+ if (typeof tag !== 'string') return false;
1179
+ const pattern = allowDirectionalSuffix
1180
+ ? /^[A-Za-z]{1,8}(?:-[A-Za-z0-9]{1,8})*(?:--(?:ltr|rtl))?$/
1181
+ : /^[A-Za-z]{1,8}(?:-[A-Za-z0-9]{1,8})*$/;
1182
+ return pattern.test(tag);
1152
1183
  }
1153
1184
 
1154
1185
  function literalLexicalValue(litVal) {
@@ -1207,17 +1238,18 @@ function canonicalFloatDoubleLex(n) {
1207
1238
  }
1208
1239
 
1209
1240
  function parseStringLikeDatatypeValue(lexical, dt) {
1210
- let value = String(lexical);
1241
+ const value = String(lexical);
1211
1242
 
1212
1243
  if (dt === XSD_NS + 'normalizedString') {
1213
- value = value.replace(/[\t\n\r]/g, ' ');
1244
+ if (/[\t\n\r]/.test(value)) return null;
1214
1245
  } else if (dt !== XSD_STRING_DT) {
1215
- value = value.replace(/[\t\n\r ]+/g, ' ').trim();
1246
+ // String-derived datatypes with whitespace facet collapse are considered
1247
+ // lexically valid only when the spelling is already in collapsed form.
1248
+ if (/[\t\n\r]/.test(value) || /^ | $/.test(value) || / {2,}/.test(value)) return null;
1216
1249
  }
1217
1250
 
1218
1251
  if (dt === XSD_NS + 'language') {
1219
- if (!/^[A-Za-z]{1,8}(?:-[A-Za-z0-9]{1,8})*$/.test(value)) return null;
1220
- value = value.toLowerCase();
1252
+ if (!isValidLanguageTag(value, false)) return null;
1221
1253
  } else if (dt === XSD_NS + 'Name') {
1222
1254
  if (!/^[:_\p{L}][:_\p{L}\p{N}.\-\u00B7\u0300-\u036F\u203F-\u2040]*$/u.test(value)) return null;
1223
1255
  } else if (dt === XSD_NS + 'NCName') {
@@ -1229,9 +1261,51 @@ function parseStringLikeDatatypeValue(lexical, dt) {
1229
1261
  return {
1230
1262
  family: 'string',
1231
1263
  dt,
1232
- value,
1233
- canonicalLex: value,
1234
- canonicalLiteral: makeDatatypeTypedLiteral(value, dt),
1264
+ value: dt === XSD_NS + 'language' ? value.toLowerCase() : value,
1265
+ canonicalLex: dt === XSD_NS + 'language' ? value.toLowerCase() : value,
1266
+ canonicalLiteral: makeDatatypeTypedLiteral(dt === XSD_NS + 'language' ? value.toLowerCase() : value, dt),
1267
+ comparable: true,
1268
+ };
1269
+ }
1270
+
1271
+ function parsePlainLiteralDatatypeValue(lexical) {
1272
+ const parsed = splitPlainLiteralLexical(lexical);
1273
+ if (!parsed) return null;
1274
+ const canonicalLex = `${parsed.text}@${parsed.lang}`;
1275
+ return {
1276
+ family: 'plainLiteral',
1277
+ dt: RDF_PLAIN_LITERAL_DT,
1278
+ value: parsed.text,
1279
+ lang: parsed.lang,
1280
+ canonicalLex,
1281
+ canonicalLiteral: makeDatatypeTypedLiteral(canonicalLex, RDF_PLAIN_LITERAL_DT),
1282
+ comparable: true,
1283
+ };
1284
+ }
1285
+
1286
+ function splitPlainLiteralLexical(lexical) {
1287
+ const s = String(lexical);
1288
+ const at = s.lastIndexOf('@');
1289
+ // rdf:PlainLiteral lexical forms encode the literal as text + "@" +
1290
+ // language-tag. The language tag may be empty, e.g. "abc@".
1291
+ // A lexical with no separator at all, e.g. "abc", is ill-typed.
1292
+ if (at < 0) return null;
1293
+ const text = s.slice(0, at);
1294
+ const lang = s.slice(at + 1);
1295
+ if (lang !== '' && !isValidLanguageTag(lang, false)) return null;
1296
+ return { text, lang: lang.toLowerCase() };
1297
+ }
1298
+
1299
+ function parseXmlLiteralDatatypeValue(lexical) {
1300
+ const s = String(lexical);
1301
+ if (!isWellFormedXmlFragment(s)) return null;
1302
+ const canonicalLex = s.trim();
1303
+ return {
1304
+ family: 'xmlLiteral',
1305
+ dt: RDF_XML_LITERAL_DT,
1306
+ value: canonicalLex,
1307
+ canonicalLex,
1308
+ canonicalLiteral: makeDatatypeTypedLiteral(canonicalLex, RDF_XML_LITERAL_DT),
1235
1309
  comparable: true,
1236
1310
  };
1237
1311
  }
@@ -1319,6 +1393,11 @@ function parseFloatDoubleDatatypeValue(lexical, dt) {
1319
1393
  const special = parseXsdFloatSpecialLex(s);
1320
1394
  const value = special !== null ? special : Number(s);
1321
1395
  if (Number.isNaN(value) && s !== 'NaN') return null;
1396
+ if (Number.isFinite(value)) {
1397
+ const abs = Math.abs(value);
1398
+ if (dt === XSD_FLOAT_DT && abs > 0 && (abs > XSD_FLOAT_MAX_VALUE || abs < XSD_FLOAT_MIN_POSITIVE_VALUE)) return null;
1399
+ if (dt === XSD_DOUBLE_DT && abs > 0 && (abs > XSD_DOUBLE_MAX_VALUE || abs < XSD_DOUBLE_MIN_POSITIVE_VALUE)) return null;
1400
+ }
1322
1401
  const canonicalLex = canonicalFloatDoubleLex(value);
1323
1402
  return {
1324
1403
  family: 'numeric',
@@ -1408,7 +1487,11 @@ function parseBinaryDatatypeValue(lexical, dt) {
1408
1487
 
1409
1488
  function parseAnyUriDatatypeValue(lexical) {
1410
1489
  const value = String(lexical);
1411
- if (/[\u0000-\u001F\u007F]/.test(value)) return null;
1490
+ if (/[^\u0021-\u007E]/.test(value) || /[<>"{}|\\^`]/.test(value)) return null;
1491
+ for (let i = 0; i < value.length; i += 1) {
1492
+ if (value[i] === '%' && !/^[0-9A-Fa-f]{2}$/.test(value.slice(i + 1, i + 3))) return null;
1493
+ }
1494
+ if (!isValidIriReferenceLexical(value)) return null;
1412
1495
  return {
1413
1496
  family: 'anyURI',
1414
1497
  dt: XSD_ANY_URI_DT,
@@ -1419,6 +1502,116 @@ function parseAnyUriDatatypeValue(lexical) {
1419
1502
  };
1420
1503
  }
1421
1504
 
1505
+ function isValidIriReferenceLexical(value) {
1506
+ // Minimal RFC 3987/3986 structural check for IRI references. In
1507
+ // particular, a relative reference may not start with a colon-bearing first
1508
+ // path segment such as ":abc"; this is the OWL 2 RL anyURI illtyped case.
1509
+ // Empty references and fragment-only references are allowed.
1510
+ const s = String(value);
1511
+ if (s === '') return true;
1512
+ if (/^[A-Za-z][A-Za-z0-9+.-]*:/.test(s)) return true; // absolute scheme
1513
+ if (s.startsWith('//') || s.startsWith('/') || s.startsWith('?') || s.startsWith('#')) return true;
1514
+ const cut = s.search(/[/?#]/);
1515
+ const firstSegment = cut < 0 ? s : s.slice(0, cut);
1516
+ return !firstSegment.includes(':');
1517
+ }
1518
+
1519
+ function isWellFormedXmlFragment(input) {
1520
+ const source = String(input).trim();
1521
+ if (!source) return false;
1522
+ const stack = [];
1523
+ let seenElement = false;
1524
+ let position = 0;
1525
+
1526
+ while (position < source.length) {
1527
+ const open = source.indexOf('<', position);
1528
+ if (open < 0) return stack.length === 0 && isValidXmlText(source.slice(position)) && seenElement;
1529
+ if (!isValidXmlText(source.slice(position, open))) return false;
1530
+
1531
+ if (source.startsWith('<!--', open)) {
1532
+ const close = source.indexOf('-->', open + 4);
1533
+ if (close < 0 || source.slice(open + 4, close).includes('--')) return false;
1534
+ position = close + 3;
1535
+ continue;
1536
+ }
1537
+
1538
+ if (source.startsWith('<![CDATA[', open)) {
1539
+ const close = source.indexOf(']]>', open + 9);
1540
+ if (close < 0) return false;
1541
+ position = close + 3;
1542
+ continue;
1543
+ }
1544
+
1545
+ if (source.startsWith('<?', open)) {
1546
+ const close = source.indexOf('?>', open + 2);
1547
+ if (close < 0) return false;
1548
+ position = close + 2;
1549
+ continue;
1550
+ }
1551
+
1552
+ if (source.startsWith('</', open)) {
1553
+ const close = source.indexOf('>', open + 2);
1554
+ if (close < 0) return false;
1555
+ const name = source.slice(open + 2, close).trim();
1556
+ if (!isValidXmlName(name) || stack.pop() !== name) return false;
1557
+ position = close + 1;
1558
+ continue;
1559
+ }
1560
+
1561
+ const close = findXmlTagEnd(source, open + 1);
1562
+ if (close < 0) return false;
1563
+ const raw = source.slice(open + 1, close);
1564
+ const selfClosing = /\/\s*$/.test(raw);
1565
+ const content = selfClosing ? raw.replace(/\/\s*$/, '').trim() : raw.trim();
1566
+ const nameMatch = /^([^\s/>]+)/.exec(content);
1567
+ if (!nameMatch || !isValidXmlName(nameMatch[1]) || !areValidXmlAttributes(content.slice(nameMatch[0].length))) return false;
1568
+ seenElement = true;
1569
+ if (!selfClosing) stack.push(nameMatch[1]);
1570
+ position = close + 1;
1571
+ }
1572
+
1573
+ return stack.length === 0 && seenElement;
1574
+ }
1575
+
1576
+ function findXmlTagEnd(source, start) {
1577
+ let quote = null;
1578
+ for (let i = start; i < source.length; i += 1) {
1579
+ const ch = source[i];
1580
+ if (quote) {
1581
+ if (ch === quote) quote = null;
1582
+ } else if (ch === '"' || ch === "'") {
1583
+ quote = ch;
1584
+ } else if (ch === '>') {
1585
+ return i;
1586
+ }
1587
+ }
1588
+ return -1;
1589
+ }
1590
+
1591
+ function areValidXmlAttributes(source) {
1592
+ let rest = String(source).trim();
1593
+ const seen = new Set();
1594
+ while (rest) {
1595
+ const m = /^([A-Za-z_:\u0080-\uFFFF][A-Za-z0-9_.:\-\u0080-\uFFFF]*)\s*=\s*("[^"]*"|'[^']*')\s*/u.exec(rest);
1596
+ if (!m || seen.has(m[1]) || !isValidXmlAttributeValue(m[2].slice(1, -1))) return false;
1597
+ seen.add(m[1]);
1598
+ rest = rest.slice(m[0].length).trim();
1599
+ }
1600
+ return true;
1601
+ }
1602
+
1603
+ function isValidXmlAttributeValue(value) {
1604
+ return !/[<]/.test(value) && isValidXmlText(value);
1605
+ }
1606
+
1607
+ function isValidXmlText(value) {
1608
+ return !/[<&]/.test(String(value).replace(/&(?:amp|lt|gt|quot|apos|#\d+|#x[0-9A-Fa-f]+);/g, ''));
1609
+ }
1610
+
1611
+ function isValidXmlName(name) {
1612
+ return /^[A-Za-z_:\u0080-\uFFFF][A-Za-z0-9_.:\-\u0080-\uFFFF]*$/u.test(name) && !/^xml$/i.test(name);
1613
+ }
1614
+
1422
1615
  function isLeapYearBigInt(year) {
1423
1616
  if (year % 400n === 0n) return true;
1424
1617
  if (year % 100n === 0n) return false;
@@ -1575,11 +1768,33 @@ function parseLangStringDatatypeValue(t, lexical) {
1575
1768
  };
1576
1769
  }
1577
1770
 
1771
+ function literalTermDatatypeKey(t) {
1772
+ return `${literalLexicalValue(t.value)}^^${literalDatatypeIri(t) || ''}`;
1773
+ }
1774
+
1578
1775
  function parseDatatypeValueForDatatype(t, dt) {
1579
1776
  if (!(t instanceof Literal) || typeof dt !== 'string' || !isSupportedDatatypeIri(dt)) return null;
1777
+
1778
+ if (dt === RDFS_LITERAL_DT) {
1779
+ const ownDt = literalDatatypeIri(t);
1780
+ if (ownDt === null || !isSupportedDatatypeIri(ownDt)) return null;
1781
+ const ownValue = parseDatatypeValueForDatatype(t, ownDt);
1782
+ if (!ownValue) return null;
1783
+ return {
1784
+ family: 'literal',
1785
+ dt: RDFS_LITERAL_DT,
1786
+ value: literalTermDatatypeKey(t),
1787
+ canonicalLex: literalLexicalValue(t.value),
1788
+ canonicalLiteral: t,
1789
+ comparable: false,
1790
+ };
1791
+ }
1792
+
1580
1793
  const lexical = literalLexicalValue(t.value);
1581
1794
 
1582
1795
  if (dt === RDF_LANGSTRING_DT) return parseLangStringDatatypeValue(t, lexical);
1796
+ if (dt === RDF_PLAIN_LITERAL_DT) return parsePlainLiteralDatatypeValue(lexical);
1797
+ if (dt === RDF_XML_LITERAL_DT) return parseXmlLiteralDatatypeValue(lexical);
1583
1798
  if (literalHasLangTag(t.value)) return null;
1584
1799
  if (XSD_STRING_LIKE_DTS.has(dt)) return parseStringLikeDatatypeValue(lexical, dt);
1585
1800
  if (dt === XSD_BOOLEAN_DT) return parseBooleanDatatypeValue(lexical);
@@ -1605,10 +1820,11 @@ function compareDecimalExactValues(a, b) {
1605
1820
 
1606
1821
  function datatypeValuesSame(a, b) {
1607
1822
  if (!a || !b || !a.comparable || !b.comparable) return false;
1608
- if (a.family === 'langString' || b.family === 'langString') {
1823
+ if (a.family === 'langString' || b.family === 'langString' || a.family === 'plainLiteral' || b.family === 'plainLiteral') {
1609
1824
  return a.family === b.family && a.value === b.value && a.lang === b.lang;
1610
1825
  }
1611
1826
  if (a.family === 'string' && b.family === 'string') return a.value === b.value;
1827
+ if (a.family === 'xmlLiteral' && b.family === 'xmlLiteral') return a.value === b.value;
1612
1828
  if (a.family === 'boolean' && b.family === 'boolean') return a.value === b.value;
1613
1829
  if (a.family === 'anyURI' && b.family === 'anyURI') return a.value === b.value;
1614
1830
  if (a.family === 'binary' && b.family === 'binary') {
@@ -1635,7 +1851,9 @@ function datatypeValuesSame(a, b) {
1635
1851
  function datatypeValuesComparable(a, b) {
1636
1852
  if (!a || !b || !a.comparable || !b.comparable) return false;
1637
1853
  if (a.family === 'dateTime' && b.family === 'dateTime' && a.hasTimezone !== b.hasTimezone) return false;
1854
+ if (a.family === 'numeric' && b.family === 'numeric') return true;
1638
1855
  if (a.family === 'langString' || b.family === 'langString') return a.family === b.family;
1856
+ if (a.family === 'plainLiteral' || b.family === 'plainLiteral') return a.family === b.family;
1639
1857
  if (a.family === 'string' || b.family === 'string') return a.family === b.family;
1640
1858
  return a.family === b.family;
1641
1859
  }
@@ -1659,6 +1877,17 @@ function evalDatatypeInspectionBuiltin(g, subst, kind) {
1659
1877
  const dt = literalDatatypeIri(g.s);
1660
1878
  if (dt === null) return [];
1661
1879
  valueTerm = internIri(dt);
1880
+ const out = evalBindBuiltinObject(g.o, valueTerm, subst);
1881
+
1882
+ // OWL 2 RL datatype rules often need common string literals to also
1883
+ // participate in generic literal comparisons, while application rules still
1884
+ // ask for their precise datatype explicitly. When the datatype output is
1885
+ // unbound, expose rdfs:Literal as an additional super-datatype answer for
1886
+ // xsd:string and rdf:langString; bound-object calls remain strictly exact.
1887
+ if (g.o instanceof Var && (dt === XSD_STRING_DT || dt === RDF_LANGSTRING_DT)) {
1888
+ out.push(...evalBindBuiltinObject(g.o, internIri(RDFS_LITERAL_DT), subst));
1889
+ }
1890
+ return out;
1662
1891
  } else if (kind === 'lexicalForm') {
1663
1892
  valueTerm = makeStringLiteral(literalLexicalValue(g.s.value));
1664
1893
  } else if (kind === 'language') {
@@ -1727,6 +1956,57 @@ function evalDatatypeValueComparisonBuiltin(g, subst, same) {
1727
1956
  return [];
1728
1957
  }
1729
1958
 
1959
+ function evalOwlSameAsBuiltin(g, subst, facts, maxResults) {
1960
+ const out = [];
1961
+ const seen = new Set();
1962
+ const limit = typeof maxResults === 'number' && maxResults > 0 ? maxResults : Infinity;
1963
+
1964
+ function addPair(left, right) {
1965
+ if (out.length >= limit) return true;
1966
+ const key = `${termFastKey ? termFastKey(left) : termToN3(left)} ${termFastKey ? termFastKey(right) : termToN3(right)}`;
1967
+ if (seen.has(key)) return false;
1968
+ let s2 = unifyTerm(g.s, left, subst);
1969
+ if (s2 === null) return false;
1970
+ s2 = unifyTerm(g.o, right, s2);
1971
+ if (s2 === null) return false;
1972
+ seen.add(key);
1973
+ out.push(s2);
1974
+ return out.length >= limit;
1975
+ }
1976
+
1977
+ function addReflexive(term) {
1978
+ return addPair(term, term);
1979
+ }
1980
+
1981
+ const sameAsIri = OWL_NS + 'sameAs';
1982
+ const differentFromIri = OWL_NS + 'differentFrom';
1983
+
1984
+ for (const tr of facts || []) {
1985
+ if (tr && tr.p instanceof Iri && tr.p.value === sameAsIri) {
1986
+ if (addPair(tr.s, tr.o)) return out;
1987
+ }
1988
+ }
1989
+
1990
+ // owl:sameAs is reflexive. Avoid enumerating the entire active term universe
1991
+ // for fully-unbound goals; only add the reflexive pairs that are immediately
1992
+ // needed to detect explicit self-diversity (?x owl:differentFrom ?x).
1993
+ if (!(g.s instanceof Var) && !(g.o instanceof Var)) {
1994
+ if (termsEqual(g.s, g.o)) addReflexive(g.s);
1995
+ } else if (!(g.s instanceof Var)) {
1996
+ addReflexive(g.s);
1997
+ } else if (!(g.o instanceof Var)) {
1998
+ addReflexive(g.o);
1999
+ } else {
2000
+ for (const tr of facts || []) {
2001
+ if (tr && tr.p instanceof Iri && tr.p.value === differentFromIri && termsEqual(tr.s, tr.o)) {
2002
+ if (addReflexive(tr.s)) return out;
2003
+ }
2004
+ }
2005
+ }
2006
+
2007
+ return out;
2008
+ }
2009
+
1730
2010
  // ===========================================================================
1731
2011
  // Math builtin helpers
1732
2012
  // ===========================================================================
@@ -2866,6 +3146,8 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
2866
3146
  const pv = iriValue(g.p);
2867
3147
  if (pv === null) return null;
2868
3148
 
3149
+ if (pv === OWL_NS + 'sameAs') return evalOwlSameAsBuiltin(g, subst, facts, maxResults);
3150
+
2869
3151
  // Super restricted mode: disable *all* builtins except => / <= (log:implies / log:impliedBy)
2870
3152
  if (typeof getSuperRestrictedMode === 'function' && getSuperRestrictedMode()) {
2871
3153
  const allow1 = LOG_NS + 'implies';
@@ -5208,6 +5490,7 @@ function isBuiltinPred(p) {
5208
5490
  }
5209
5491
 
5210
5492
  if (__customBuiltinHandlers.has(v)) return true;
5493
+ if (v === OWL_NS + 'sameAs') return true;
5211
5494
 
5212
5495
  return (
5213
5496
  v.startsWith(CRYPTO_NS) ||
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyeling",
3
- "version": "1.32.0",
3
+ "version": "1.32.1",
4
4
  "description": "A minimal Notation3 (N3) reasoner in JavaScript.",
5
5
  "main": "./index.js",
6
6
  "keywords": [
@@ -25,6 +25,7 @@
25
25
  "eyeling.js",
26
26
  "index.js",
27
27
  "index.d.ts",
28
+ "browser.d.ts",
28
29
  "bin",
29
30
  "dist",
30
31
  "lib",
@@ -75,7 +76,7 @@
75
76
  "default": "./index.js"
76
77
  },
77
78
  "./browser": {
78
- "types": "./index.d.ts",
79
+ "types": "./browser.d.ts",
79
80
  "default": "./dist/browser/index.mjs"
80
81
  },
81
82
  "./eyeling.js": "./eyeling.js",
@@ -84,7 +85,7 @@
84
85
  "typesVersions": {
85
86
  "*": {
86
87
  "browser": [
87
- "index.d.ts"
88
+ "browser.d.ts"
88
89
  ]
89
90
  }
90
91
  },
@@ -159,6 +159,8 @@ const cases = [
159
159
  @prefix : <http://example.org/datatype-tests#> .
160
160
  @prefix dt: <https://eyereasoner.github.io/eyeling/datatype#> .
161
161
  @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
162
+ @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
163
+ @prefix owl: <http://www.w3.org/2002/07/owl#> .
162
164
  @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
163
165
 
164
166
  { "01"^^xsd:integer dt:datatype ?d . } => { :integer :datatype ?d } .
@@ -166,20 +168,39 @@ const cases = [
166
168
  { "hello"@en dt:language ?lang . } => { :language :tag ?lang } .
167
169
  { "plain" dt:datatype ?d . } => { :plain :datatype ?d } .
168
170
  { "hello"@EN dt:datatype ?d . } => { :langString :datatype ?d } .
171
+ { "hello@en"^^rdf:PlainLiteral dt:datatype ?d . } => { :plainLiteral :datatype ?d } .
169
172
 
170
173
  { "1"^^xsd:integer dt:validForDatatype xsd:integer . } => { :valid :integer true } .
174
+ { "hello@en"^^rdf:PlainLiteral dt:validForDatatype rdf:PlainLiteral . } => { :valid :plainLiteral true } .
175
+ { "abc@"^^rdf:PlainLiteral dt:validForDatatype rdf:PlainLiteral . } => { :valid :plainLiteralEmptyTag true } .
176
+ { "<a/>"^^rdf:XMLLiteral dt:validForDatatype rdf:XMLLiteral . } => { :valid :xmlLiteral true } .
177
+ { "anything" dt:validForDatatype rdfs:Literal . } => { :valid :rdfsLiteral true } .
171
178
  { "2147483648"^^xsd:int dt:invalidForDatatype xsd:int . } => { :invalid :int true } .
172
179
  { "2"^^xsd:boolean dt:invalidForDatatype xsd:boolean . } => { :invalid :boolean true } .
173
180
  { "2026-02-31T00:00:00Z"^^xsd:dateTime dt:invalidForDatatype xsd:dateTime . } => { :invalid :dateTime true } .
174
181
  { " 1.0 "^^xsd:decimal dt:invalidForDatatype xsd:decimal . } => { :invalid :decimalWhitespace true } .
175
182
  { "02026-06-10T12:00:00Z"^^xsd:dateTime dt:invalidForDatatype xsd:dateTime . } => { :invalid :dateTimeYear true } .
183
+ { "http://example.org/a b"^^xsd:anyURI dt:invalidForDatatype xsd:anyURI . } => { :invalid :anyURI true } .
184
+ { ":abc"^^xsd:anyURI dt:invalidForDatatype xsd:anyURI . } => { :invalid :anyURIRelativeColon true } .
185
+ { "3.5E38"^^xsd:float dt:invalidForDatatype xsd:float . } => { :invalid :floatHigh true } .
186
+ { "1.0E-46"^^xsd:float dt:invalidForDatatype xsd:float . } => { :invalid :floatLow true } .
187
+ { "a b"^^xsd:token dt:invalidForDatatype xsd:token . } => { :invalid :token true } .
188
+ { "hello"^^rdf:PlainLiteral dt:invalidForDatatype rdf:PlainLiteral . } => { :invalid :plainLiteralNoTag true } .
189
+ { "hello@bad_tag"^^rdf:PlainLiteral dt:invalidForDatatype rdf:PlainLiteral . } => { :invalid :plainLiteralBadTag true } .
190
+ { "<a>"^^rdf:XMLLiteral dt:invalidForDatatype rdf:XMLLiteral . } => { :invalid :xmlLiteral true } .
176
191
 
177
192
  { "01"^^xsd:integer dt:sameValueAs "1.0"^^xsd:decimal . } => { :same :numeric true } .
193
+ { "hello@EN"^^rdf:PlainLiteral dt:sameValueAs "hello@en"^^rdf:PlainLiteral . } => { :same :plainLiteral true } .
178
194
  { "true"^^xsd:boolean dt:sameValueAs "1"^^xsd:boolean . } => { :same :boolean true } .
179
195
  { "2026-06-10T12:00:00Z"^^xsd:dateTime dt:sameValueAs "2026-06-10T14:00:00+02:00"^^xsd:dateTime . } => { :same :dateTime true } .
180
196
  { "2026-12-31T24:00:00Z"^^xsd:dateTime dt:sameValueAs "2027-01-01T00:00:00Z"^^xsd:dateTime . } => { :same :midnightRollover true } .
181
197
  { "AQID"^^xsd:base64Binary dt:sameValueAs "010203"^^xsd:hexBinary . } => { :same :binary true } .
198
+ { "<a/>"^^rdf:XMLLiteral dt:sameValueAs "<a/>"^^rdf:XMLLiteral . } => { :same :xmlLiteral true } .
182
199
  { "11"^^xsd:integer dt:differentValueFrom "12"^^xsd:integer . } => { :different :numeric true } .
200
+ { "hello@en"^^rdf:PlainLiteral dt:differentValueFrom "bye@en"^^rdf:PlainLiteral . } => { :different :plainLiteral true } .
201
+ { "a" dt:differentValueFrom "b" . } => { :different :string true } .
202
+ { "a" dt:datatype ?sd . ?sd <http://www.w3.org/2000/10/swap/log#notEqualTo> xsd:string . } => { :string :comparisonDatatype ?sd } .
203
+ { "<a/>"^^rdf:XMLLiteral dt:differentValueFrom "<b/>"^^rdf:XMLLiteral . } => { :different :xmlLiteral true } .
183
204
 
184
205
  { ("1"^^xsd:integer xsd:integer) dt:validForDatatype true . } => { :tuple :valid true } .
185
206
  { ("abc"^^xsd:integer xsd:integer) dt:validForDatatype false . } => { :tuple :invalidBoolean true } .
@@ -187,9 +208,14 @@ const cases = [
187
208
 
188
209
  { "01"^^xsd:integer dt:canonicalLiteral ?ci . } => { :canonical :integer ?ci } .
189
210
  { "1"^^xsd:boolean dt:canonicalLiteral ?cb . } => { :canonical :boolean ?cb } .
190
- { " a\t b "^^xsd:token dt:canonicalLiteral ?ct . } => { :canonical :token ?ct } .
211
+ { "a b"^^xsd:token dt:canonicalLiteral ?ct . } => { :canonical :token ?ct } .
212
+ { "hello@EN"^^rdf:PlainLiteral dt:canonicalLiteral ?cp . } => { :canonical :plainLiteral ?cp } .
213
+ { "<a/>"^^rdf:XMLLiteral dt:canonicalLiteral ?cx . } => { :canonical :xmlLiteral ?cx } .
191
214
  { "2026-06-10T14:00:00+02:00"^^xsd:dateTime dt:canonicalLiteral ?cd . } => { :canonical :dateTime ?cd } .
192
215
  { "2026-12-31T24:00:00Z"^^xsd:dateTime dt:canonicalLiteral ?cm . } => { :canonical :midnightRollover ?cm } .
216
+
217
+ :x owl:differentFrom :x .
218
+ { :x owl:sameAs :x . } => { :sameAs :reflexive true } .
193
219
  `);
194
220
 
195
221
  assert.match(out, /:integer :datatype xsd:integer \./);
@@ -197,26 +223,48 @@ const cases = [
197
223
  assert.match(out, /:language :tag "en" \./);
198
224
  assert.match(out, /:plain :datatype xsd:string \./);
199
225
  assert.match(out, /:langString :datatype rdf:langString \./);
226
+ assert.match(out, /:plainLiteral :datatype rdf:PlainLiteral \./);
200
227
  assert.match(out, /:valid :integer true \./);
228
+ assert.match(out, /:valid :plainLiteral true \./);
229
+ assert.match(out, /:valid :plainLiteralEmptyTag true \./);
230
+ assert.match(out, /:valid :xmlLiteral true \./);
231
+ assert.match(out, /:valid :rdfsLiteral true \./);
201
232
  assert.match(out, /:invalid :int true \./);
202
233
  assert.match(out, /:invalid :boolean true \./);
203
234
  assert.match(out, /:invalid :dateTime true \./);
204
235
  assert.match(out, /:invalid :decimalWhitespace true \./);
205
236
  assert.match(out, /:invalid :dateTimeYear true \./);
237
+ assert.match(out, /:invalid :anyURI true \./);
238
+ assert.match(out, /:invalid :anyURIRelativeColon true \./);
239
+ assert.match(out, /:invalid :floatHigh true \./);
240
+ assert.match(out, /:invalid :floatLow true \./);
241
+ assert.match(out, /:invalid :token true \./);
242
+ assert.match(out, /:invalid :plainLiteralNoTag true \./);
243
+ assert.match(out, /:invalid :plainLiteralBadTag true \./);
244
+ assert.match(out, /:invalid :xmlLiteral true \./);
206
245
  assert.match(out, /:same :numeric true \./);
246
+ assert.match(out, /:same :plainLiteral true \./);
207
247
  assert.match(out, /:same :boolean true \./);
208
248
  assert.match(out, /:same :dateTime true \./);
209
249
  assert.match(out, /:same :midnightRollover true \./);
210
250
  assert.match(out, /:same :binary true \./);
251
+ assert.match(out, /:same :xmlLiteral true \./);
211
252
  assert.match(out, /:different :numeric true \./);
253
+ assert.match(out, /:different :plainLiteral true \./);
254
+ assert.match(out, /:different :string true \./);
255
+ assert.match(out, /:string :comparisonDatatype rdfs:Literal \./);
256
+ assert.match(out, /:different :xmlLiteral true \./);
212
257
  assert.match(out, /:tuple :valid true \./);
213
258
  assert.match(out, /:tuple :invalidBoolean true \./);
214
259
  assert.match(out, /:tuple :invalidResult true \./);
215
260
  assert.match(out, /:canonical :integer "1"\^\^xsd:integer \./);
216
261
  assert.match(out, /:canonical :boolean true \./);
217
262
  assert.match(out, /:canonical :token "a b"\^\^xsd:token \./);
263
+ assert.match(out, /:canonical :plainLiteral "hello@en"\^\^rdf:PlainLiteral \./);
264
+ assert.match(out, /:canonical :xmlLiteral "<a\/>"\^\^rdf:XMLLiteral \./);
218
265
  assert.match(out, /:canonical :dateTime "2026-06-10T12:00:00Z"\^\^xsd:dateTime \./);
219
266
  assert.match(out, /:canonical :midnightRollover "2027-01-01T00:00:00Z"\^\^xsd:dateTime \./);
267
+ assert.match(out, /:sameAs :reflexive true \./);
220
268
  },
221
269
  },
222
270
  {