eyeling 1.28.6 → 1.28.8
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 +98 -40
- package/eyeling-builtins.ttl +4 -4
- package/eyeling.js +98 -40
- package/lib/builtins.js +98 -40
- package/package.json +1 -1
- package/test/api.test.js +13 -29
- package/test/builtins.test.js +23 -21
- package/test/examples.test.js +27 -42
- package/test/extra.test.js +9 -25
- package/test/manifest.test.js +23 -28
- package/test/package.test.js +17 -24
- package/test/packlist.test.js +5 -14
- package/test/playground.test.js +6 -22
- package/test/rdf12.test.js +15 -54
- package/test/report.js +67 -0
- package/test/stream_messages.test.js +11 -25
package/README.md
CHANGED
|
@@ -621,11 +621,11 @@ Eyeling provides datatype built-ins in the namespace `https://eyereasoner.github
|
|
|
621
621
|
Supported operations include:
|
|
622
622
|
|
|
623
623
|
- `dt:datatype`, `dt:lexicalForm`, and `dt:language` for literal inspection;
|
|
624
|
-
- `dt:validForDatatype` and `dt:invalidForDatatype` for lexical validity and datatype membership checks
|
|
624
|
+
- `dt:validForDatatype` and `dt:invalidForDatatype` for lexical validity and datatype membership checks, either as `?literal dt:validForDatatype ?datatype` tests or as tuple-to-boolean checks like `(?literal ?datatype) dt:validForDatatype true`;
|
|
625
625
|
- `dt:sameValueAs` and `dt:differentValueFrom` for value-space equality and inequality;
|
|
626
626
|
- `dt:canonicalLiteral` for canonical literal production.
|
|
627
627
|
|
|
628
|
-
The built-ins cover RDF language strings and the OWL 2 RL-relevant XSD set: `xsd:string`, `xsd:normalizedString`, `xsd:token`, `xsd:language`, `xsd:Name`, `xsd:NCName`, `xsd:NMTOKEN`, `xsd:boolean`, `xsd:decimal`, `xsd:integer` and its bounded integer subtypes, `xsd:float`, `xsd:double`, `xsd:hexBinary`, `xsd:base64Binary`, `xsd:anyURI`, `xsd:dateTime`, and `xsd:dateTimeStamp`.
|
|
628
|
+
The built-ins use strict datatype lexical validation for these checks, including exact dateTime rollover/canonicalization such as `24:00:00` normalizing to the following day. They cover RDF language strings and the OWL 2 RL-relevant XSD set: `xsd:string`, `xsd:normalizedString`, `xsd:token`, `xsd:language`, `xsd:Name`, `xsd:NCName`, `xsd:NMTOKEN`, `xsd:boolean`, `xsd:decimal`, `xsd:integer` and its bounded integer subtypes, `xsd:float`, `xsd:double`, `xsd:hexBinary`, `xsd:base64Binary`, `xsd:anyURI`, `xsd:dateTime`, and `xsd:dateTimeStamp`.
|
|
629
629
|
|
|
630
630
|
Example:
|
|
631
631
|
|
|
@@ -638,6 +638,7 @@ Example:
|
|
|
638
638
|
"01"^^xsd:integer dt:sameValueAs "1.0"^^xsd:decimal .
|
|
639
639
|
"2026-06-10T12:00:00Z"^^xsd:dateTime
|
|
640
640
|
dt:sameValueAs "2026-06-10T14:00:00+02:00"^^xsd:dateTime .
|
|
641
|
+
("abc"^^xsd:integer xsd:integer) dt:validForDatatype false .
|
|
641
642
|
}
|
|
642
643
|
=>
|
|
643
644
|
{
|
|
@@ -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);
|
package/eyeling-builtins.ttl
CHANGED
|
@@ -54,19 +54,19 @@ dt:language a ex:Builtin ; ex:kind ex:Function ;
|
|
|
54
54
|
rdfs:comment "Extracts the language tag of an rdf:langString literal as a string literal." .
|
|
55
55
|
|
|
56
56
|
dt:validForDatatype a ex:Builtin ; ex:kind ex:Test ;
|
|
57
|
-
rdfs:comment "Succeeds iff the subject literal has a lexical form accepted by the object datatype and denotes a value in that datatype's value space. Supports OWL 2 RL-relevant XSD strings, booleans, numerics, binary types, anyURI, dateTime, and dateTimeStamp." .
|
|
57
|
+
rdfs:comment "Succeeds iff the subject literal has a lexical form accepted by the object datatype and denotes a value in that datatype's value space. Also supports tuple-to-boolean form: (literal datatype) dt:validForDatatype true/false. Supports OWL 2 RL-relevant XSD strings, booleans, numerics, binary types, anyURI, dateTime, and dateTimeStamp." .
|
|
58
58
|
|
|
59
59
|
dt:invalidForDatatype a ex:Builtin ; ex:kind ex:Test ;
|
|
60
|
-
rdfs:comment "Succeeds iff the object datatype is supported and the subject literal is not valid for it." .
|
|
60
|
+
rdfs:comment "Succeeds iff the object datatype is supported and the subject literal is not valid for it. Also supports tuple-to-boolean form: (literal datatype) dt:invalidForDatatype true/false." .
|
|
61
61
|
|
|
62
62
|
dt:sameValueAs a ex:Builtin ; ex:kind ex:Test ;
|
|
63
|
-
rdfs:comment "Value-space equality over supported datatypes, including integer/decimal numeric equality, boolean 1/true equality, timezone-normalized dateTime equality, string-derived whitespace facets, and binary byte equality." .
|
|
63
|
+
rdfs:comment "Value-space equality over supported datatypes, including integer/decimal numeric equality, boolean 1/true equality, timezone-normalized dateTime equality with exact midnight rollover, string-derived whitespace facets, and binary byte equality." .
|
|
64
64
|
|
|
65
65
|
dt:differentValueFrom a ex:Builtin ; ex:kind ex:Test ;
|
|
66
66
|
rdfs:comment "Value-space inequality over supported and comparable datatype values." .
|
|
67
67
|
|
|
68
68
|
dt:canonicalLiteral a ex:Builtin ; ex:kind ex:Function ;
|
|
69
|
-
rdfs:comment "Binds/unifies the object with a canonical literal for the subject literal's datatype value, such as normalized integer, boolean, token, binary, and timezone-normalized dateTime lexicals." .
|
|
69
|
+
rdfs:comment "Binds/unifies the object with a canonical literal for the subject literal's datatype value, such as normalized integer, boolean, token, binary, and timezone-normalized dateTime lexicals, including 24:00:00 rollover." .
|
|
70
70
|
|
|
71
71
|
# --- crypto: ---------------------------------------------------------
|
|
72
72
|
|
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);
|