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.
- package/eyeling.js +137 -11
- 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)
|
|
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
|
-
//
|
|
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
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
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 =
|
|
1895
|
-
const bn =
|
|
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
|
// ============================================================================
|