eyeling 1.28.7 → 1.28.9
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/README.md +3 -2
- package/dist/browser/eyeling.browser.js +178 -89
- package/examples/complex-matrix-stability.n3 +23 -23
- package/examples/deep-taxonomy-10.n3 +2 -2
- package/examples/deep-taxonomy-100.n3 +2 -2
- package/examples/deep-taxonomy-1000.n3 +2 -2
- package/examples/deep-taxonomy-10000.n3 +2 -2
- package/eyeling-builtins.ttl +4 -4
- package/eyeling.js +178 -89
- package/lib/builtins.js +98 -40
- package/lib/lexer.js +80 -49
- package/package.json +1 -1
- package/test/api.test.js +127 -1
- package/test/builtins.test.js +15 -0
- package/test/playground.test.js +17 -3
package/eyeling.js
CHANGED
|
@@ -1179,7 +1179,7 @@ function parseIntegerDatatypeValue(lexical, dt) {
|
|
|
1179
1179
|
}
|
|
1180
1180
|
|
|
1181
1181
|
function parseDecimalDatatypeValue(lexical) {
|
|
1182
|
-
const parsed = parseXsdDecimalToBigIntScale(String(lexical));
|
|
1182
|
+
const parsed = parseXsdDecimalToBigIntScale(String(lexical), { trim: false });
|
|
1183
1183
|
if (!parsed) return null;
|
|
1184
1184
|
const canonicalLex = canonicalDecimalLexFromParts(parsed.num, parsed.scale);
|
|
1185
1185
|
return {
|
|
@@ -1305,58 +1305,93 @@ function parseAnyUriDatatypeValue(lexical) {
|
|
|
1305
1305
|
};
|
|
1306
1306
|
}
|
|
1307
1307
|
|
|
1308
|
-
function
|
|
1309
|
-
if (year %
|
|
1310
|
-
if (year %
|
|
1311
|
-
return year %
|
|
1308
|
+
function isLeapYearBigInt(year) {
|
|
1309
|
+
if (year % 400n === 0n) return true;
|
|
1310
|
+
if (year % 100n === 0n) return false;
|
|
1311
|
+
return year % 4n === 0n;
|
|
1312
1312
|
}
|
|
1313
1313
|
|
|
1314
|
-
function
|
|
1315
|
-
if (month === 2) return
|
|
1314
|
+
function daysInMonthBigInt(year, month) {
|
|
1315
|
+
if (month === 2) return isLeapYearBigInt(year) ? 29 : 28;
|
|
1316
1316
|
return [4, 6, 9, 11].includes(month) ? 30 : 31;
|
|
1317
1317
|
}
|
|
1318
1318
|
|
|
1319
|
-
function
|
|
1320
|
-
|
|
1319
|
+
function floorDivBigInt(a, b) {
|
|
1320
|
+
let q = a / b;
|
|
1321
|
+
const r = a % b;
|
|
1322
|
+
if (r !== 0n && ((r > 0n) !== (b > 0n))) q -= 1n;
|
|
1323
|
+
return q;
|
|
1321
1324
|
}
|
|
1322
1325
|
|
|
1323
|
-
function
|
|
1326
|
+
function daysFromCivilBigInt(year, month, day) {
|
|
1324
1327
|
let y = year;
|
|
1325
|
-
|
|
1326
|
-
const era =
|
|
1327
|
-
const yoe = y - era *
|
|
1328
|
-
const mp = month + (month > 2 ? -3 : 9);
|
|
1329
|
-
const doy =
|
|
1330
|
-
const doe = yoe *
|
|
1331
|
-
return era *
|
|
1328
|
+
if (month <= 2) y -= 1n;
|
|
1329
|
+
const era = floorDivBigInt(y, 400n);
|
|
1330
|
+
const yoe = y - era * 400n;
|
|
1331
|
+
const mp = BigInt(month + (month > 2 ? -3 : 9));
|
|
1332
|
+
const doy = (153n * mp + 2n) / 5n + BigInt(day) - 1n;
|
|
1333
|
+
const doe = yoe * 365n + yoe / 4n - yoe / 100n + doy;
|
|
1334
|
+
return era * 146097n + doe - 719468n;
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
function civilFromDaysBigInt(days) {
|
|
1338
|
+
const z = days + 719468n;
|
|
1339
|
+
const era = floorDivBigInt(z, 146097n);
|
|
1340
|
+
const doe = z - era * 146097n;
|
|
1341
|
+
const yoe = (doe - doe / 1460n + doe / 36524n - doe / 146096n) / 365n;
|
|
1342
|
+
let year = yoe + era * 400n;
|
|
1343
|
+
const doy = doe - (365n * yoe + yoe / 4n - yoe / 100n);
|
|
1344
|
+
const mp = (5n * doy + 2n) / 153n;
|
|
1345
|
+
const day = doy - (153n * mp + 2n) / 5n + 1n;
|
|
1346
|
+
const month = mp < 10n ? mp + 3n : mp - 9n;
|
|
1347
|
+
if (month <= 2n) year += 1n;
|
|
1348
|
+
return { year, month: Number(month), day: Number(day) };
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
function isValidXsdYearLex(s) {
|
|
1352
|
+
const body = s.startsWith('-') ? s.slice(1) : s;
|
|
1353
|
+
if (!/^\d{4,}$/.test(body)) return false;
|
|
1354
|
+
return body.length === 4 || body[0] !== '0';
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
function canonicalYearLex(year) {
|
|
1358
|
+
if (year < 0n) return `-${(-year).toString().padStart(4, '0')}`;
|
|
1359
|
+
return year.toString().padStart(4, '0');
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
function formatDateTimeLocalKey(year, month, day, hour, minute, second, frac) {
|
|
1363
|
+
return `${canonicalYearLex(year)}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}T${String(hour).padStart(2, '0')}:${String(minute).padStart(2, '0')}:${String(second).padStart(2, '0')}${frac ? `.${frac}` : ''}`;
|
|
1332
1364
|
}
|
|
1333
1365
|
|
|
1334
1366
|
function parseDateTimeDatatypeValue(lexical, dt) {
|
|
1335
1367
|
const s = String(lexical);
|
|
1336
1368
|
const m = /^(-?\d{4,})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d+))?(Z|[+-]\d{2}:\d{2})?$/.exec(s);
|
|
1337
|
-
if (!m) return null;
|
|
1369
|
+
if (!m || !isValidXsdYearLex(m[1])) return null;
|
|
1338
1370
|
|
|
1339
|
-
const year =
|
|
1340
|
-
|
|
1371
|
+
const year = BigInt(m[1]);
|
|
1372
|
+
let month = Number(m[2]);
|
|
1341
1373
|
let day = Number(m[3]);
|
|
1342
1374
|
let hour = Number(m[4]);
|
|
1343
1375
|
const minute = Number(m[5]);
|
|
1344
1376
|
const second = Number(m[6]);
|
|
1345
1377
|
let frac = m[7] || '';
|
|
1346
1378
|
const tz = m[8] || null;
|
|
1347
|
-
if (!Number.isSafeInteger(year)) return null;
|
|
1348
1379
|
if (dt === XSD_DATETIME_STAMP_DT && !tz) return null;
|
|
1349
1380
|
if (!(month >= 1 && month <= 12)) return null;
|
|
1350
|
-
if (!(day >= 1 && day <=
|
|
1381
|
+
if (!(day >= 1 && day <= daysInMonthBigInt(year, month))) return null;
|
|
1351
1382
|
if (!(minute >= 0 && minute <= 59)) return null;
|
|
1352
1383
|
if (!(second >= 0 && second <= 59)) return null;
|
|
1384
|
+
|
|
1385
|
+
let dayCount = daysFromCivilBigInt(year, month, day);
|
|
1386
|
+
let normalizedYear = year;
|
|
1353
1387
|
if (hour === 24) {
|
|
1354
1388
|
if (minute !== 0 || second !== 0 || /[1-9]/.test(frac)) return null;
|
|
1355
1389
|
hour = 0;
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1390
|
+
dayCount += 1n;
|
|
1391
|
+
const civil = civilFromDaysBigInt(dayCount);
|
|
1392
|
+
normalizedYear = civil.year;
|
|
1393
|
+
month = civil.month;
|
|
1394
|
+
day = civil.day;
|
|
1360
1395
|
} else if (!(hour >= 0 && hour <= 23)) {
|
|
1361
1396
|
return null;
|
|
1362
1397
|
}
|
|
@@ -1379,11 +1414,10 @@ function parseDateTimeDatatypeValue(lexical, dt) {
|
|
|
1379
1414
|
}
|
|
1380
1415
|
}
|
|
1381
1416
|
|
|
1382
|
-
|
|
1383
|
-
let totalSeconds = BigInt(dayCount) * 86400n + BigInt(hour * 3600 + minute * 60 + second);
|
|
1417
|
+
let totalSeconds = dayCount * 86400n + BigInt(hour * 3600 + minute * 60 + second);
|
|
1384
1418
|
if (offsetMinutes !== null) totalSeconds -= BigInt(offsetMinutes * 60);
|
|
1385
1419
|
|
|
1386
|
-
const localKey =
|
|
1420
|
+
const localKey = formatDateTimeLocalKey(normalizedYear, month, day, hour, minute, second, frac);
|
|
1387
1421
|
const canonicalLex = canonicalDateTimeLex(totalSeconds, fracNum, fracScale, offsetMinutes !== null, localKey);
|
|
1388
1422
|
return {
|
|
1389
1423
|
family: 'dateTime',
|
|
@@ -1403,15 +1437,14 @@ function canonicalDateTimeLex(totalSeconds, fracNum, fracScale, hasTimezone, loc
|
|
|
1403
1437
|
const frac = fracScale > 0 ? `.${fracNum.toString().padStart(fracScale, '0')}` : '';
|
|
1404
1438
|
if (!hasTimezone) return localKey;
|
|
1405
1439
|
|
|
1406
|
-
const
|
|
1407
|
-
const
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
const
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
return iso;
|
|
1440
|
+
const secondsPerDay = 86400n;
|
|
1441
|
+
const dayCount = floorDivBigInt(totalSeconds, secondsPerDay);
|
|
1442
|
+
const secondOfDay = totalSeconds - dayCount * secondsPerDay;
|
|
1443
|
+
const civil = civilFromDaysBigInt(dayCount);
|
|
1444
|
+
const hour = Number(secondOfDay / 3600n);
|
|
1445
|
+
const minute = Number((secondOfDay % 3600n) / 60n);
|
|
1446
|
+
const second = Number(secondOfDay % 60n);
|
|
1447
|
+
return formatDateTimeLocalKey(civil.year, civil.month, civil.day, hour, minute, second, frac) + 'Z';
|
|
1415
1448
|
}
|
|
1416
1449
|
|
|
1417
1450
|
function parseLangStringDatatypeValue(t, lexical) {
|
|
@@ -1529,7 +1562,31 @@ function evalDatatypeInspectionBuiltin(g, subst, kind) {
|
|
|
1529
1562
|
return evalBindBuiltinObject(g.o, valueTerm, subst);
|
|
1530
1563
|
}
|
|
1531
1564
|
|
|
1565
|
+
function booleanTermValue(t) {
|
|
1566
|
+
const info = parseBooleanLiteralInfo(t);
|
|
1567
|
+
return info ? info.value : null;
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
function makeBooleanLiteral(value) {
|
|
1571
|
+
return internLiteral(value ? 'true' : 'false');
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
function evalDatatypeValidityBooleanResult(outTerm, result, subst) {
|
|
1575
|
+
if (outTerm instanceof Var || outTerm instanceof Blank) return evalBindBuiltinObject(outTerm, makeBooleanLiteral(result), subst);
|
|
1576
|
+
const expected = booleanTermValue(outTerm);
|
|
1577
|
+
return expected === result ? [__cloneSubst(subst)] : [];
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1532
1580
|
function evalDatatypeValidityBuiltin(g, subst, positive) {
|
|
1581
|
+
if (g.s instanceof ListTerm && g.s.elems.length === 2) {
|
|
1582
|
+
const [lit, dtTerm] = g.s.elems;
|
|
1583
|
+
if (!(lit instanceof Literal) || !(dtTerm instanceof Iri)) return [];
|
|
1584
|
+
const dt = dtTerm.value;
|
|
1585
|
+
if (!isSupportedDatatypeIri(dt)) return [];
|
|
1586
|
+
const ok = parseDatatypeValueForDatatype(lit, dt) !== null;
|
|
1587
|
+
return evalDatatypeValidityBooleanResult(g.o, positive ? ok : !ok, subst);
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1533
1590
|
if (!(g.s instanceof Literal)) return [];
|
|
1534
1591
|
|
|
1535
1592
|
let dt = null;
|
|
@@ -1723,8 +1780,9 @@ function formatNum(n) {
|
|
|
1723
1780
|
return String(n);
|
|
1724
1781
|
}
|
|
1725
1782
|
|
|
1726
|
-
function parseXsdDecimalToBigIntScale(s) {
|
|
1727
|
-
let t = String(s)
|
|
1783
|
+
function parseXsdDecimalToBigIntScale(s, options = null) {
|
|
1784
|
+
let t = String(s);
|
|
1785
|
+
if (!options || options.trim !== false) t = t.trim();
|
|
1728
1786
|
let sign = 1n;
|
|
1729
1787
|
|
|
1730
1788
|
if (t.startsWith('+')) t = t.slice(1);
|
|
@@ -13240,7 +13298,76 @@ function lex(inputText, opts = {}) {
|
|
|
13240
13298
|
continue;
|
|
13241
13299
|
}
|
|
13242
13300
|
|
|
13243
|
-
// 5)
|
|
13301
|
+
// 5) Numeric literal (integer, decimal, or double). Turtle/N3 numeric
|
|
13302
|
+
// literals may have a leading sign and decimals may start with '.', e.g.
|
|
13303
|
+
// '+42' and '.5'. Reject repeated decimal points so '1.2.3'
|
|
13304
|
+
// cannot be misread as one impossible numeric-like token.
|
|
13305
|
+
if (
|
|
13306
|
+
isAsciiDigit(c) ||
|
|
13307
|
+
((c === '+' || c === '-') && (isAsciiDigit(peek(1)) || (peek(1) === '.' && isAsciiDigit(peek(2))))) ||
|
|
13308
|
+
(c === '.' && isAsciiDigit(peek(1)) && !isAsciiDigit(peek(-1)))
|
|
13309
|
+
) {
|
|
13310
|
+
const start = i;
|
|
13311
|
+
const numChars = [];
|
|
13312
|
+
|
|
13313
|
+
if (chars[i] === '+' || chars[i] === '-') {
|
|
13314
|
+
numChars.push(chars[i]);
|
|
13315
|
+
i++;
|
|
13316
|
+
}
|
|
13317
|
+
|
|
13318
|
+
if (chars[i] === '.') {
|
|
13319
|
+
numChars.push('.');
|
|
13320
|
+
i++;
|
|
13321
|
+
while (i < n && isAsciiDigit(chars[i])) {
|
|
13322
|
+
numChars.push(chars[i]);
|
|
13323
|
+
i++;
|
|
13324
|
+
}
|
|
13325
|
+
} else {
|
|
13326
|
+
while (i < n && isAsciiDigit(chars[i])) {
|
|
13327
|
+
numChars.push(chars[i]);
|
|
13328
|
+
i++;
|
|
13329
|
+
}
|
|
13330
|
+
if (i < n && chars[i] === '.' && i + 1 < n && isAsciiDigit(chars[i + 1])) {
|
|
13331
|
+
numChars.push('.');
|
|
13332
|
+
i++;
|
|
13333
|
+
while (i < n && isAsciiDigit(chars[i])) {
|
|
13334
|
+
numChars.push(chars[i]);
|
|
13335
|
+
i++;
|
|
13336
|
+
}
|
|
13337
|
+
}
|
|
13338
|
+
}
|
|
13339
|
+
|
|
13340
|
+
if (i < n && chars[i] === '.' && i + 1 < n && isAsciiDigit(chars[i + 1])) {
|
|
13341
|
+
throw new N3SyntaxError('Malformed numeric literal: multiple decimal points', start);
|
|
13342
|
+
}
|
|
13343
|
+
|
|
13344
|
+
// Optional exponent part: e.g., 1e0, 1.1e-3, .5E+0.
|
|
13345
|
+
if (i < n && (chars[i] === 'e' || chars[i] === 'E')) {
|
|
13346
|
+
let j = i + 1;
|
|
13347
|
+
if (j < n && (chars[j] === '+' || chars[j] === '-')) j++;
|
|
13348
|
+
if (j < n && isAsciiDigit(chars[j])) {
|
|
13349
|
+
numChars.push(chars[i]); // e/E
|
|
13350
|
+
i++;
|
|
13351
|
+
if (i < n && (chars[i] === '+' || chars[i] === '-')) {
|
|
13352
|
+
numChars.push(chars[i]);
|
|
13353
|
+
i++;
|
|
13354
|
+
}
|
|
13355
|
+
while (i < n && isAsciiDigit(chars[i])) {
|
|
13356
|
+
numChars.push(chars[i]);
|
|
13357
|
+
i++;
|
|
13358
|
+
}
|
|
13359
|
+
}
|
|
13360
|
+
}
|
|
13361
|
+
|
|
13362
|
+
if (i < n && chars[i] === '.' && i + 1 < n && isAsciiDigit(chars[i + 1])) {
|
|
13363
|
+
throw new N3SyntaxError('Malformed numeric literal: multiple decimal points', start);
|
|
13364
|
+
}
|
|
13365
|
+
|
|
13366
|
+
tokens.push(new Token('Literal', numChars.join(''), start));
|
|
13367
|
+
continue;
|
|
13368
|
+
}
|
|
13369
|
+
|
|
13370
|
+
// 6) Single-character punctuation. Use a switch rather than allocating a
|
|
13244
13371
|
// mapping object for every punctuation token in large inputs.
|
|
13245
13372
|
switch (c) {
|
|
13246
13373
|
case '{':
|
|
@@ -13361,13 +13488,17 @@ function lex(inputText, opts = {}) {
|
|
|
13361
13488
|
}
|
|
13362
13489
|
continue;
|
|
13363
13490
|
}
|
|
13491
|
+
if (cc === '\n' || cc === '\r') {
|
|
13492
|
+
throw new N3SyntaxError('Unescaped newline in short string literal', start);
|
|
13493
|
+
}
|
|
13364
13494
|
if (cc === '"') {
|
|
13365
13495
|
closed = true;
|
|
13366
13496
|
break;
|
|
13367
13497
|
}
|
|
13368
13498
|
if (sChars !== null) sChars.push(cc);
|
|
13369
13499
|
}
|
|
13370
|
-
|
|
13500
|
+
if (!closed) throw new N3SyntaxError('Unterminated short string literal "..."', start);
|
|
13501
|
+
const rawContent = sChars === null ? sliceChars(contentStart, i - 1) : sChars.join('');
|
|
13371
13502
|
const decoded = sChars === null ? rawContent : decodeN3StringEscapes(rawContent, start);
|
|
13372
13503
|
if (sChars !== null || inputMayContainInvalidStringChar) assertValidStringLiteralValue(decoded, start);
|
|
13373
13504
|
const s = JSON.stringify(decoded); // canonical short quoted form
|
|
@@ -13451,13 +13582,17 @@ function lex(inputText, opts = {}) {
|
|
|
13451
13582
|
}
|
|
13452
13583
|
continue;
|
|
13453
13584
|
}
|
|
13585
|
+
if (cc === '\n' || cc === '\r') {
|
|
13586
|
+
throw new N3SyntaxError('Unescaped newline in short string literal', start);
|
|
13587
|
+
}
|
|
13454
13588
|
if (cc === "'") {
|
|
13455
13589
|
closed = true;
|
|
13456
13590
|
break;
|
|
13457
13591
|
}
|
|
13458
13592
|
if (sChars !== null) sChars.push(cc);
|
|
13459
13593
|
}
|
|
13460
|
-
|
|
13594
|
+
if (!closed) throw new N3SyntaxError("Unterminated short string literal '...'", start);
|
|
13595
|
+
const rawContent = sChars === null ? sliceChars(contentStart, i - 1) : sChars.join('');
|
|
13461
13596
|
const decoded = sChars === null ? rawContent : decodeN3StringEscapes(rawContent, start);
|
|
13462
13597
|
if (sChars !== null || inputMayContainInvalidStringChar) assertValidStringLiteralValue(decoded, start);
|
|
13463
13598
|
const s = JSON.stringify(decoded); // canonical short quoted form
|
|
@@ -13540,52 +13675,6 @@ function lex(inputText, opts = {}) {
|
|
|
13540
13675
|
continue;
|
|
13541
13676
|
}
|
|
13542
13677
|
|
|
13543
|
-
// 6) Numeric literal (integer or float)
|
|
13544
|
-
if (isAsciiDigit(c) || (c === '-' && peek(1) !== null && isAsciiDigit(peek(1)))) {
|
|
13545
|
-
const start = i;
|
|
13546
|
-
const numChars = [c];
|
|
13547
|
-
i++;
|
|
13548
|
-
while (i < n) {
|
|
13549
|
-
const cc = chars[i];
|
|
13550
|
-
if (isAsciiDigit(cc)) {
|
|
13551
|
-
numChars.push(cc);
|
|
13552
|
-
i++;
|
|
13553
|
-
continue;
|
|
13554
|
-
}
|
|
13555
|
-
if (cc === '.') {
|
|
13556
|
-
if (i + 1 < n && isAsciiDigit(chars[i + 1])) {
|
|
13557
|
-
numChars.push('.');
|
|
13558
|
-
i++;
|
|
13559
|
-
continue;
|
|
13560
|
-
} else {
|
|
13561
|
-
break;
|
|
13562
|
-
}
|
|
13563
|
-
}
|
|
13564
|
-
break;
|
|
13565
|
-
}
|
|
13566
|
-
|
|
13567
|
-
// Optional exponent part: e.g., 1e0, 1.1e-3, 1.1E+0
|
|
13568
|
-
if (i < n && (chars[i] === 'e' || chars[i] === 'E')) {
|
|
13569
|
-
let j = i + 1;
|
|
13570
|
-
if (j < n && (chars[j] === '+' || chars[j] === '-')) j++;
|
|
13571
|
-
if (j < n && isAsciiDigit(chars[j])) {
|
|
13572
|
-
numChars.push(chars[i]); // e/E
|
|
13573
|
-
i++;
|
|
13574
|
-
if (i < n && (chars[i] === '+' || chars[i] === '-')) {
|
|
13575
|
-
numChars.push(chars[i]);
|
|
13576
|
-
i++;
|
|
13577
|
-
}
|
|
13578
|
-
while (i < n && isAsciiDigit(chars[i])) {
|
|
13579
|
-
numChars.push(chars[i]);
|
|
13580
|
-
i++;
|
|
13581
|
-
}
|
|
13582
|
-
}
|
|
13583
|
-
}
|
|
13584
|
-
|
|
13585
|
-
tokens.push(new Token('Literal', numChars.join(''), start));
|
|
13586
|
-
continue;
|
|
13587
|
-
}
|
|
13588
|
-
|
|
13589
13678
|
// 7) Identifiers / keywords / QNames
|
|
13590
13679
|
const start = i;
|
|
13591
13680
|
const word = readIdentText(start);
|
package/lib/builtins.js
CHANGED
|
@@ -1168,7 +1168,7 @@ function parseIntegerDatatypeValue(lexical, dt) {
|
|
|
1168
1168
|
}
|
|
1169
1169
|
|
|
1170
1170
|
function parseDecimalDatatypeValue(lexical) {
|
|
1171
|
-
const parsed = parseXsdDecimalToBigIntScale(String(lexical));
|
|
1171
|
+
const parsed = parseXsdDecimalToBigIntScale(String(lexical), { trim: false });
|
|
1172
1172
|
if (!parsed) return null;
|
|
1173
1173
|
const canonicalLex = canonicalDecimalLexFromParts(parsed.num, parsed.scale);
|
|
1174
1174
|
return {
|
|
@@ -1294,58 +1294,93 @@ function parseAnyUriDatatypeValue(lexical) {
|
|
|
1294
1294
|
};
|
|
1295
1295
|
}
|
|
1296
1296
|
|
|
1297
|
-
function
|
|
1298
|
-
if (year %
|
|
1299
|
-
if (year %
|
|
1300
|
-
return year %
|
|
1297
|
+
function isLeapYearBigInt(year) {
|
|
1298
|
+
if (year % 400n === 0n) return true;
|
|
1299
|
+
if (year % 100n === 0n) return false;
|
|
1300
|
+
return year % 4n === 0n;
|
|
1301
1301
|
}
|
|
1302
1302
|
|
|
1303
|
-
function
|
|
1304
|
-
if (month === 2) return
|
|
1303
|
+
function daysInMonthBigInt(year, month) {
|
|
1304
|
+
if (month === 2) return isLeapYearBigInt(year) ? 29 : 28;
|
|
1305
1305
|
return [4, 6, 9, 11].includes(month) ? 30 : 31;
|
|
1306
1306
|
}
|
|
1307
1307
|
|
|
1308
|
-
function
|
|
1309
|
-
|
|
1308
|
+
function floorDivBigInt(a, b) {
|
|
1309
|
+
let q = a / b;
|
|
1310
|
+
const r = a % b;
|
|
1311
|
+
if (r !== 0n && ((r > 0n) !== (b > 0n))) q -= 1n;
|
|
1312
|
+
return q;
|
|
1310
1313
|
}
|
|
1311
1314
|
|
|
1312
|
-
function
|
|
1315
|
+
function daysFromCivilBigInt(year, month, day) {
|
|
1313
1316
|
let y = year;
|
|
1314
|
-
|
|
1315
|
-
const era =
|
|
1316
|
-
const yoe = y - era *
|
|
1317
|
-
const mp = month + (month > 2 ? -3 : 9);
|
|
1318
|
-
const doy =
|
|
1319
|
-
const doe = yoe *
|
|
1320
|
-
return era *
|
|
1317
|
+
if (month <= 2) y -= 1n;
|
|
1318
|
+
const era = floorDivBigInt(y, 400n);
|
|
1319
|
+
const yoe = y - era * 400n;
|
|
1320
|
+
const mp = BigInt(month + (month > 2 ? -3 : 9));
|
|
1321
|
+
const doy = (153n * mp + 2n) / 5n + BigInt(day) - 1n;
|
|
1322
|
+
const doe = yoe * 365n + yoe / 4n - yoe / 100n + doy;
|
|
1323
|
+
return era * 146097n + doe - 719468n;
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
function civilFromDaysBigInt(days) {
|
|
1327
|
+
const z = days + 719468n;
|
|
1328
|
+
const era = floorDivBigInt(z, 146097n);
|
|
1329
|
+
const doe = z - era * 146097n;
|
|
1330
|
+
const yoe = (doe - doe / 1460n + doe / 36524n - doe / 146096n) / 365n;
|
|
1331
|
+
let year = yoe + era * 400n;
|
|
1332
|
+
const doy = doe - (365n * yoe + yoe / 4n - yoe / 100n);
|
|
1333
|
+
const mp = (5n * doy + 2n) / 153n;
|
|
1334
|
+
const day = doy - (153n * mp + 2n) / 5n + 1n;
|
|
1335
|
+
const month = mp < 10n ? mp + 3n : mp - 9n;
|
|
1336
|
+
if (month <= 2n) year += 1n;
|
|
1337
|
+
return { year, month: Number(month), day: Number(day) };
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
function isValidXsdYearLex(s) {
|
|
1341
|
+
const body = s.startsWith('-') ? s.slice(1) : s;
|
|
1342
|
+
if (!/^\d{4,}$/.test(body)) return false;
|
|
1343
|
+
return body.length === 4 || body[0] !== '0';
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
function canonicalYearLex(year) {
|
|
1347
|
+
if (year < 0n) return `-${(-year).toString().padStart(4, '0')}`;
|
|
1348
|
+
return year.toString().padStart(4, '0');
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
function formatDateTimeLocalKey(year, month, day, hour, minute, second, frac) {
|
|
1352
|
+
return `${canonicalYearLex(year)}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}T${String(hour).padStart(2, '0')}:${String(minute).padStart(2, '0')}:${String(second).padStart(2, '0')}${frac ? `.${frac}` : ''}`;
|
|
1321
1353
|
}
|
|
1322
1354
|
|
|
1323
1355
|
function parseDateTimeDatatypeValue(lexical, dt) {
|
|
1324
1356
|
const s = String(lexical);
|
|
1325
1357
|
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;
|
|
1358
|
+
if (!m || !isValidXsdYearLex(m[1])) return null;
|
|
1327
1359
|
|
|
1328
|
-
const year =
|
|
1329
|
-
|
|
1360
|
+
const year = BigInt(m[1]);
|
|
1361
|
+
let month = Number(m[2]);
|
|
1330
1362
|
let day = Number(m[3]);
|
|
1331
1363
|
let hour = Number(m[4]);
|
|
1332
1364
|
const minute = Number(m[5]);
|
|
1333
1365
|
const second = Number(m[6]);
|
|
1334
1366
|
let frac = m[7] || '';
|
|
1335
1367
|
const tz = m[8] || null;
|
|
1336
|
-
if (!Number.isSafeInteger(year)) return null;
|
|
1337
1368
|
if (dt === XSD_DATETIME_STAMP_DT && !tz) return null;
|
|
1338
1369
|
if (!(month >= 1 && month <= 12)) return null;
|
|
1339
|
-
if (!(day >= 1 && day <=
|
|
1370
|
+
if (!(day >= 1 && day <= daysInMonthBigInt(year, month))) return null;
|
|
1340
1371
|
if (!(minute >= 0 && minute <= 59)) return null;
|
|
1341
1372
|
if (!(second >= 0 && second <= 59)) return null;
|
|
1373
|
+
|
|
1374
|
+
let dayCount = daysFromCivilBigInt(year, month, day);
|
|
1375
|
+
let normalizedYear = year;
|
|
1342
1376
|
if (hour === 24) {
|
|
1343
1377
|
if (minute !== 0 || second !== 0 || /[1-9]/.test(frac)) return null;
|
|
1344
1378
|
hour = 0;
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1379
|
+
dayCount += 1n;
|
|
1380
|
+
const civil = civilFromDaysBigInt(dayCount);
|
|
1381
|
+
normalizedYear = civil.year;
|
|
1382
|
+
month = civil.month;
|
|
1383
|
+
day = civil.day;
|
|
1349
1384
|
} else if (!(hour >= 0 && hour <= 23)) {
|
|
1350
1385
|
return null;
|
|
1351
1386
|
}
|
|
@@ -1368,11 +1403,10 @@ function parseDateTimeDatatypeValue(lexical, dt) {
|
|
|
1368
1403
|
}
|
|
1369
1404
|
}
|
|
1370
1405
|
|
|
1371
|
-
|
|
1372
|
-
let totalSeconds = BigInt(dayCount) * 86400n + BigInt(hour * 3600 + minute * 60 + second);
|
|
1406
|
+
let totalSeconds = dayCount * 86400n + BigInt(hour * 3600 + minute * 60 + second);
|
|
1373
1407
|
if (offsetMinutes !== null) totalSeconds -= BigInt(offsetMinutes * 60);
|
|
1374
1408
|
|
|
1375
|
-
const localKey =
|
|
1409
|
+
const localKey = formatDateTimeLocalKey(normalizedYear, month, day, hour, minute, second, frac);
|
|
1376
1410
|
const canonicalLex = canonicalDateTimeLex(totalSeconds, fracNum, fracScale, offsetMinutes !== null, localKey);
|
|
1377
1411
|
return {
|
|
1378
1412
|
family: 'dateTime',
|
|
@@ -1392,15 +1426,14 @@ function canonicalDateTimeLex(totalSeconds, fracNum, fracScale, hasTimezone, loc
|
|
|
1392
1426
|
const frac = fracScale > 0 ? `.${fracNum.toString().padStart(fracScale, '0')}` : '';
|
|
1393
1427
|
if (!hasTimezone) return localKey;
|
|
1394
1428
|
|
|
1395
|
-
const
|
|
1396
|
-
const
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
const
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
return iso;
|
|
1429
|
+
const secondsPerDay = 86400n;
|
|
1430
|
+
const dayCount = floorDivBigInt(totalSeconds, secondsPerDay);
|
|
1431
|
+
const secondOfDay = totalSeconds - dayCount * secondsPerDay;
|
|
1432
|
+
const civil = civilFromDaysBigInt(dayCount);
|
|
1433
|
+
const hour = Number(secondOfDay / 3600n);
|
|
1434
|
+
const minute = Number((secondOfDay % 3600n) / 60n);
|
|
1435
|
+
const second = Number(secondOfDay % 60n);
|
|
1436
|
+
return formatDateTimeLocalKey(civil.year, civil.month, civil.day, hour, minute, second, frac) + 'Z';
|
|
1404
1437
|
}
|
|
1405
1438
|
|
|
1406
1439
|
function parseLangStringDatatypeValue(t, lexical) {
|
|
@@ -1518,7 +1551,31 @@ function evalDatatypeInspectionBuiltin(g, subst, kind) {
|
|
|
1518
1551
|
return evalBindBuiltinObject(g.o, valueTerm, subst);
|
|
1519
1552
|
}
|
|
1520
1553
|
|
|
1554
|
+
function booleanTermValue(t) {
|
|
1555
|
+
const info = parseBooleanLiteralInfo(t);
|
|
1556
|
+
return info ? info.value : null;
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
function makeBooleanLiteral(value) {
|
|
1560
|
+
return internLiteral(value ? 'true' : 'false');
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
function evalDatatypeValidityBooleanResult(outTerm, result, subst) {
|
|
1564
|
+
if (outTerm instanceof Var || outTerm instanceof Blank) return evalBindBuiltinObject(outTerm, makeBooleanLiteral(result), subst);
|
|
1565
|
+
const expected = booleanTermValue(outTerm);
|
|
1566
|
+
return expected === result ? [__cloneSubst(subst)] : [];
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1521
1569
|
function evalDatatypeValidityBuiltin(g, subst, positive) {
|
|
1570
|
+
if (g.s instanceof ListTerm && g.s.elems.length === 2) {
|
|
1571
|
+
const [lit, dtTerm] = g.s.elems;
|
|
1572
|
+
if (!(lit instanceof Literal) || !(dtTerm instanceof Iri)) return [];
|
|
1573
|
+
const dt = dtTerm.value;
|
|
1574
|
+
if (!isSupportedDatatypeIri(dt)) return [];
|
|
1575
|
+
const ok = parseDatatypeValueForDatatype(lit, dt) !== null;
|
|
1576
|
+
return evalDatatypeValidityBooleanResult(g.o, positive ? ok : !ok, subst);
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1522
1579
|
if (!(g.s instanceof Literal)) return [];
|
|
1523
1580
|
|
|
1524
1581
|
let dt = null;
|
|
@@ -1712,8 +1769,9 @@ function formatNum(n) {
|
|
|
1712
1769
|
return String(n);
|
|
1713
1770
|
}
|
|
1714
1771
|
|
|
1715
|
-
function parseXsdDecimalToBigIntScale(s) {
|
|
1716
|
-
let t = String(s)
|
|
1772
|
+
function parseXsdDecimalToBigIntScale(s, options = null) {
|
|
1773
|
+
let t = String(s);
|
|
1774
|
+
if (!options || options.trim !== false) t = t.trim();
|
|
1717
1775
|
let sign = 1n;
|
|
1718
1776
|
|
|
1719
1777
|
if (t.startsWith('+')) t = t.slice(1);
|