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 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 isLeapYearNumber(year) {
1309
- if (year % 400 === 0) return true;
1310
- if (year % 100 === 0) return false;
1311
- return year % 4 === 0;
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 daysInMonthNumber(year, month) {
1315
- if (month === 2) return isLeapYearNumber(year) ? 29 : 28;
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 floorDivNumber(a, b) {
1320
- return Math.floor(a / b);
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 daysFromCivilNumber(year, month, day) {
1326
+ function daysFromCivilBigInt(year, month, day) {
1324
1327
  let y = year;
1325
- y -= month <= 2 ? 1 : 0;
1326
- const era = floorDivNumber(y, 400);
1327
- const yoe = y - era * 400;
1328
- const mp = month + (month > 2 ? -3 : 9);
1329
- const doy = Math.floor((153 * mp + 2) / 5) + day - 1;
1330
- const doe = yoe * 365 + Math.floor(yoe / 4) - Math.floor(yoe / 100) + doy;
1331
- return era * 146097 + doe - 719468;
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 = Number(m[1]);
1340
- const month = Number(m[2]);
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 <= daysInMonthNumber(year, month))) return null;
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
- const d = new Date(Date.UTC(year, month - 1, day));
1357
- d.setUTCFullYear(year);
1358
- d.setUTCDate(d.getUTCDate() + 1);
1359
- day = d.getUTCDate();
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
- const dayCount = daysFromCivilNumber(year, month, day);
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 = `${m[1]}-${m[2]}-${String(day).padStart(2, '0')}T${String(hour).padStart(2, '0')}:${m[5]}:${m[6]}${frac ? `.${frac}` : ''}`;
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 minSafeSeconds = BigInt(Math.ceil(Number.MIN_SAFE_INTEGER / 1000));
1407
- const maxSafeSeconds = BigInt(Math.floor(Number.MAX_SAFE_INTEGER / 1000));
1408
- if (totalSeconds < minSafeSeconds || totalSeconds > maxSafeSeconds) return `${localKey}Z`;
1409
-
1410
- const d = new Date(Number(totalSeconds) * 1000);
1411
- if (Number.isNaN(d.getTime())) return `${localKey}Z`;
1412
- let iso = d.toISOString().replace(/\.000Z$/, 'Z');
1413
- if (frac) iso = iso.replace('Z', `${frac}Z`);
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).trim();
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) Single-character punctuation. Use a switch rather than allocating a
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
- const rawContent = sChars === null ? sliceChars(contentStart, closed ? i - 1 : i) : sChars.join('');
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
- const rawContent = sChars === null ? sliceChars(contentStart, closed ? i - 1 : i) : sChars.join('');
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);
@@ -253,38 +253,38 @@
253
253
  :Check :c4 ?C4.
254
254
  :Check :c5 ?C5.
255
255
  (
256
- "Complex Matrix Stability — ARC-style
256
+ """Complex Matrix Stability — ARC-style
257
257
 
258
- "
259
- "## Answer
260
- "
258
+ """
259
+ """## Answer
260
+ """
261
261
  "We compare three diagonal 2x2 complex matrices for discrete-time stability: "
262
262
  "A_unstable = " ?MuS ", A_stable = " ?MsS ", and A_damped = " ?MdS ". "
263
263
  "Their spectral radii are ρ(A_unstable) = " ?Ru ", ρ(A_stable) = " ?Rs ", and ρ(A_damped) = " ?Rd ". "
264
- "So A_unstable is unstable, A_stable is marginally stable, and A_damped is damped.
264
+ """So A_unstable is unstable, A_stable is marginally stable, and A_damped is damped.
265
265
 
266
- "
267
- "## Reason Why
268
- "
266
+ """
267
+ """## Reason Why
268
+ """
269
269
  "For a discrete-time linear system x_{k+1} = A x_k, the eigenvalues of A govern the behaviour of the modes. "
270
270
  "Because these matrices are diagonal, the eigenvalues are just the diagonal entries. "
271
271
  "The spectral radius is the maximum modulus of the eigenvalues: if it is greater than 1 a mode grows, "
272
272
  "if it equals 1 the modes remain bounded without decaying, and if it is less than 1 all modes decay to zero. "
273
- "Here the diagonal entries give radii 2, 1, and 0 respectively, which explains the three classifications.
274
-
275
- "
276
- "## Check
277
- "
278
- "C1 " ?C1 "
279
- "
280
- "C2 " ?C2 "
281
- "
282
- "C3 " ?C3 "
283
- "
284
- "C4 " ?C4 "
285
- "
286
- "C5 " ?C5 "
287
- "
273
+ """Here the diagonal entries give radii 2, 1, and 0 respectively, which explains the three classifications.
274
+
275
+ """
276
+ """## Check
277
+ """
278
+ "C1 " ?C1 """
279
+ """
280
+ "C2 " ?C2 """
281
+ """
282
+ "C3 " ?C3 """
283
+ """
284
+ "C4 " ?C4 """
285
+ """
286
+ "C5 " ?C5 """
287
+ """
288
288
  ) string:concatenation ?Block. }
289
289
  =>
290
290
  { :report log:outputString ?Block. } .
@@ -59,7 +59,7 @@
59
59
  }
60
60
  =>
61
61
  {
62
- :report log:outputString "Deep Taxonomy - deep classification benchmark
62
+ :report log:outputString """Deep Taxonomy - deep classification benchmark
63
63
 
64
64
  ## Answer
65
65
  The test succeeds: starting from one individual classified as N0, the rules eventually classify it as N10 and then as A2.
@@ -74,5 +74,5 @@ C3 OK - the chain reaches the midpoint N5 and still carries both side-label bran
74
74
  C4 OK - the final taxonomy step from N9 to N10 was completed.
75
75
  C5 OK - once N10 is reached, the terminal class A2 is derived.
76
76
  C6 OK - the success flag is raised only after the terminal class A2 is present.
77
- " .
77
+ """ .
78
78
  } .
@@ -149,7 +149,7 @@
149
149
  }
150
150
  =>
151
151
  {
152
- :report log:outputString "Deep Taxonomy - deep classification benchmark
152
+ :report log:outputString """Deep Taxonomy - deep classification benchmark
153
153
 
154
154
  ## Answer
155
155
  The test succeeds: starting from one individual classified as N0, the rules eventually classify it as N100 and then as A2.
@@ -164,5 +164,5 @@ C3 OK - the chain reaches the midpoint N50 and still carries both side-label bra
164
164
  C4 OK - the final taxonomy step from N99 to N100 was completed.
165
165
  C5 OK - once N100 is reached, the terminal class A2 is derived.
166
166
  C6 OK - the success flag is raised only after the terminal class A2 is present.
167
- " .
167
+ """ .
168
168
  } .
@@ -1049,7 +1049,7 @@
1049
1049
  }
1050
1050
  =>
1051
1051
  {
1052
- :report log:outputString "Deep Taxonomy - deep classification benchmark
1052
+ :report log:outputString """Deep Taxonomy - deep classification benchmark
1053
1053
 
1054
1054
  ## Answer
1055
1055
  The test succeeds: starting from one individual classified as N0, the rules eventually classify it as N1000 and then as A2.
@@ -1064,5 +1064,5 @@ C3 OK - the chain reaches the midpoint N500 and still carries both side-label br
1064
1064
  C4 OK - the final taxonomy step from N999 to N1000 was completed.
1065
1065
  C5 OK - once N1000 is reached, the terminal class A2 is derived.
1066
1066
  C6 OK - the success flag is raised only after the terminal class A2 is present.
1067
- " .
1067
+ """ .
1068
1068
  } .
@@ -10049,7 +10049,7 @@
10049
10049
  }
10050
10050
  =>
10051
10051
  {
10052
- :report log:outputString "Deep Taxonomy - deep classification benchmark
10052
+ :report log:outputString """Deep Taxonomy - deep classification benchmark
10053
10053
 
10054
10054
  ## Answer
10055
10055
  The test succeeds: starting from one individual classified as N0, the rules eventually classify it as N10000 and then as A2.
@@ -10064,5 +10064,5 @@ C3 OK - the chain reaches the midpoint N5000 and still carries both side-label b
10064
10064
  C4 OK - the final taxonomy step from N9999 to N10000 was completed.
10065
10065
  C5 OK - once N10000 is reached, the terminal class A2 is derived.
10066
10066
  C6 OK - the success flag is raised only after the terminal class A2 is present.
10067
- " .
10067
+ """ .
10068
10068
  } .
@@ -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