eyeling 1.6.2 → 1.6.3

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.
Files changed (2) hide show
  1. package/eyeling.js +137 -11
  2. package/package.json +1 -1
package/eyeling.js CHANGED
@@ -1264,7 +1264,21 @@ function termsEqual(a, b) {
1264
1264
  if (!a || !b) return false;
1265
1265
  if (a.constructor !== b.constructor) return false;
1266
1266
  if (a instanceof Iri) return a.value === b.value;
1267
- if (a instanceof Literal) return a.value === b.value;
1267
+ if (a instanceof Literal) {
1268
+ if (a.value === b.value) return true;
1269
+ if (literalsEquivalentAsXsdString(a.value, b.value)) return true;
1270
+
1271
+ // Keep in sync with unifyTerm(): numeric-value equality (EYE-style)
1272
+ const av = parseNumberLiteral(a);
1273
+ const bv = parseNumberLiteral(b);
1274
+ if (av !== null && bv !== null) {
1275
+ if (typeof av === 'bigint' && typeof bv === 'bigint') return av === bv;
1276
+ const an = typeof av === 'bigint' ? Number(av) : av;
1277
+ const bn = typeof bv === 'bigint' ? Number(bv) : bv;
1278
+ return !Number.isNaN(an) && !Number.isNaN(bn) && an === bn;
1279
+ }
1280
+ return false;
1281
+ }
1268
1282
  if (a instanceof Var) return a.name === b.name;
1269
1283
  if (a instanceof Blank) return a.label === b.label;
1270
1284
  if (a instanceof ListTerm) {
@@ -1447,7 +1461,7 @@ function hasAlphaEquiv(triples, tr) {
1447
1461
 
1448
1462
  function termFastKey(t) {
1449
1463
  if (t instanceof Iri) return 'I:' + t.value;
1450
- if (t instanceof Literal) return 'L:' + t.value;
1464
+ if (t instanceof Literal) return 'L:' + normalizeLiteralForFastKey(t.value);
1451
1465
  return null;
1452
1466
  }
1453
1467
 
@@ -1882,17 +1896,22 @@ function unifyTerm(a, b, subst) {
1882
1896
  if (a instanceof Literal && b instanceof Literal && a.value === b.value) return { ...subst };
1883
1897
  if (a instanceof Blank && b instanceof Blank && a.label === b.label) return { ...subst };
1884
1898
 
1885
- // Numeric-value match for literals (EYE-style): allow different lexical forms / typing
1886
- // e.g. "1.1"^^xsd:double ≈ 1.1e0
1899
+ // String-literal match (RDF 1.1): treat plain strings and xsd:string as equal (but not @lang)
1887
1900
  if (a instanceof Literal && b instanceof Literal) {
1888
- const av = parseNumberLiteral(a); // BigInt | Number | null
1889
- const bv = parseNumberLiteral(b);
1890
- if (av !== null && bv !== null) {
1891
- if (typeof av === 'bigint' && typeof bv === 'bigint') {
1892
- if (av === bv) return { ...subst };
1901
+ if (literalsEquivalentAsXsdString(a.value, b.value)) return { ...subst };
1902
+ }
1903
+
1904
+ // Numeric-value match for literals, BUT ONLY when datatypes agree (or infer to agree)
1905
+ if (a instanceof Literal && b instanceof Literal) {
1906
+ const ai = parseNumericLiteralInfo(a);
1907
+ const bi = parseNumericLiteralInfo(b);
1908
+
1909
+ if (ai && bi && ai.dt === bi.dt) {
1910
+ if (ai.kind === 'bigint' && bi.kind === 'bigint') {
1911
+ if (ai.value === bi.value) return { ...subst };
1893
1912
  } else {
1894
- const an = typeof av === 'bigint' ? Number(av) : av;
1895
- const bn = typeof bv === 'bigint' ? Number(bv) : bv;
1913
+ const an = ai.kind === 'bigint' ? Number(ai.value) : ai.value;
1914
+ const bn = bi.kind === 'bigint' ? Number(bi.value) : bi.value;
1896
1915
  if (!Number.isNaN(an) && !Number.isNaN(bn) && an === bn) return { ...subst };
1897
1916
  }
1898
1917
  }
@@ -2000,6 +2019,65 @@ function literalParts(lit) {
2000
2019
  return [lex, dt];
2001
2020
  }
2002
2021
 
2022
+ function literalHasLangTag(lit) {
2023
+ // True iff the literal is a quoted string literal with a language tag suffix,
2024
+ // e.g. "hello"@en or """hello"""@en.
2025
+ // (The parser rejects language tags combined with datatypes.)
2026
+ if (typeof lit !== 'string') return false;
2027
+ if (lit.indexOf('^^') >= 0) return false;
2028
+ if (!lit.startsWith('"')) return false;
2029
+
2030
+ if (lit.startsWith('"""')) {
2031
+ const end = lit.lastIndexOf('"""');
2032
+ if (end < 0) return false;
2033
+ const after = end + 3;
2034
+ return after < lit.length && lit[after] === '@';
2035
+ }
2036
+
2037
+ const lastQuote = lit.lastIndexOf('"');
2038
+ if (lastQuote < 0) return false;
2039
+ const after = lastQuote + 1;
2040
+ return after < lit.length && lit[after] === '@';
2041
+ }
2042
+
2043
+ function isPlainStringLiteralValue(lit) {
2044
+ // Plain string literal: quoted, no datatype, no lang.
2045
+ if (typeof lit !== 'string') return false;
2046
+ if (lit.indexOf('^^') >= 0) return false;
2047
+ if (!isQuotedLexical(lit)) return false;
2048
+ return !literalHasLangTag(lit);
2049
+ }
2050
+
2051
+ function literalsEquivalentAsXsdString(aLit, bLit) {
2052
+ // Treat "abc" and "abc"^^xsd:string as equal, but do NOT conflate language-tagged strings.
2053
+ if (typeof aLit !== 'string' || typeof bLit !== 'string') return false;
2054
+
2055
+ const [alex, adt] = literalParts(aLit);
2056
+ const [blex, bdt] = literalParts(bLit);
2057
+ if (alex !== blex) return false;
2058
+
2059
+ const aPlain = adt === null && isPlainStringLiteralValue(aLit);
2060
+ const bPlain = bdt === null && isPlainStringLiteralValue(bLit);
2061
+ const aXsdStr = adt === XSD_NS + 'string';
2062
+ const bXsdStr = bdt === XSD_NS + 'string';
2063
+
2064
+ return (aPlain && bXsdStr) || (bPlain && aXsdStr);
2065
+ }
2066
+
2067
+ function normalizeLiteralForFastKey(lit) {
2068
+ // Canonicalize so that "abc" and "abc"^^xsd:string share the same index/dedup key.
2069
+ if (typeof lit !== 'string') return lit;
2070
+ const [lex, dt] = literalParts(lit);
2071
+
2072
+ if (dt === XSD_NS + 'string') {
2073
+ return `${lex}^^<${XSD_NS}string>`;
2074
+ }
2075
+ if (dt === null && isPlainStringLiteralValue(lit)) {
2076
+ return `${lex}^^<${XSD_NS}string>`;
2077
+ }
2078
+ return lit;
2079
+ }
2080
+
2003
2081
  function stripQuotes(lex) {
2004
2082
  if (lex.length >= 6 && lex.startsWith('"""') && lex.endsWith('"""')) {
2005
2083
  return lex.slice(3, -3);
@@ -2478,6 +2556,54 @@ function numEqualTerm(t, n, eps = 1e-9) {
2478
2556
  return v !== null && Math.abs(v - n) < eps;
2479
2557
  }
2480
2558
 
2559
+ function numericDatatypeFromLex(lex) {
2560
+ if (/[eE]/.test(lex)) return XSD_NS + 'double';
2561
+ if (lex.includes('.')) return XSD_NS + 'decimal';
2562
+ return XSD_NS + 'integer';
2563
+ }
2564
+
2565
+ function parseNumericLiteralInfo(t) {
2566
+ if (!(t instanceof Literal)) return null;
2567
+
2568
+ const v = t.value;
2569
+ const [lex, dt] = literalParts(v);
2570
+
2571
+ let dt2 = dt;
2572
+ let lexStr;
2573
+
2574
+ if (dt2 !== null) {
2575
+ // Only handle the numeric datatypes we want to unify by value
2576
+ if (
2577
+ dt2 !== XSD_NS + 'integer' &&
2578
+ dt2 !== XSD_NS + 'decimal' &&
2579
+ dt2 !== XSD_NS + 'double'
2580
+ ) return null;
2581
+
2582
+ // typed numerics are like "1.1"^^<...>
2583
+ lexStr = stripQuotes(lex);
2584
+ } else {
2585
+ // Raw numeric token like 42, 1.1, 1.1e0 (NOT quoted strings, NOT lang-tagged)
2586
+ if (typeof v !== 'string') return null;
2587
+ if (v.startsWith('"')) return null;
2588
+ if (!/^[+-]?(?:\d+\.\d*|\.\d+|\d+)(?:[eE][+-]?\d+)?$/.test(v)) return null;
2589
+
2590
+ dt2 = numericDatatypeFromLex(v);
2591
+ lexStr = v;
2592
+ }
2593
+
2594
+ if (dt2 === XSD_NS + 'integer') {
2595
+ try {
2596
+ return { dt: dt2, kind: 'bigint', value: BigInt(lexStr) };
2597
+ } catch {
2598
+ return null;
2599
+ }
2600
+ }
2601
+
2602
+ const num = Number(lexStr);
2603
+ if (Number.isNaN(num)) return null;
2604
+ return { dt: dt2, kind: 'number', value: num };
2605
+ }
2606
+
2481
2607
  // ============================================================================
2482
2608
  // Backward proof & builtins mutual recursion — declarations first
2483
2609
  // ============================================================================
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyeling",
3
- "version": "1.6.2",
3
+ "version": "1.6.3",
4
4
  "description": "A minimal Notation3 (N3) reasoner in JavaScript.",
5
5
  "main": "./index.js",
6
6
  "keywords": [