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/lib/lexer.js
CHANGED
|
@@ -1547,7 +1547,76 @@ function lex(inputText, opts = {}) {
|
|
|
1547
1547
|
continue;
|
|
1548
1548
|
}
|
|
1549
1549
|
|
|
1550
|
-
// 5)
|
|
1550
|
+
// 5) Numeric literal (integer, decimal, or double). Turtle/N3 numeric
|
|
1551
|
+
// literals may have a leading sign and decimals may start with '.', e.g.
|
|
1552
|
+
// '+42' and '.5'. Reject repeated decimal points so '1.2.3'
|
|
1553
|
+
// cannot be misread as one impossible numeric-like token.
|
|
1554
|
+
if (
|
|
1555
|
+
isAsciiDigit(c) ||
|
|
1556
|
+
((c === '+' || c === '-') && (isAsciiDigit(peek(1)) || (peek(1) === '.' && isAsciiDigit(peek(2))))) ||
|
|
1557
|
+
(c === '.' && isAsciiDigit(peek(1)) && !isAsciiDigit(peek(-1)))
|
|
1558
|
+
) {
|
|
1559
|
+
const start = i;
|
|
1560
|
+
const numChars = [];
|
|
1561
|
+
|
|
1562
|
+
if (chars[i] === '+' || chars[i] === '-') {
|
|
1563
|
+
numChars.push(chars[i]);
|
|
1564
|
+
i++;
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
if (chars[i] === '.') {
|
|
1568
|
+
numChars.push('.');
|
|
1569
|
+
i++;
|
|
1570
|
+
while (i < n && isAsciiDigit(chars[i])) {
|
|
1571
|
+
numChars.push(chars[i]);
|
|
1572
|
+
i++;
|
|
1573
|
+
}
|
|
1574
|
+
} else {
|
|
1575
|
+
while (i < n && isAsciiDigit(chars[i])) {
|
|
1576
|
+
numChars.push(chars[i]);
|
|
1577
|
+
i++;
|
|
1578
|
+
}
|
|
1579
|
+
if (i < n && chars[i] === '.' && i + 1 < n && isAsciiDigit(chars[i + 1])) {
|
|
1580
|
+
numChars.push('.');
|
|
1581
|
+
i++;
|
|
1582
|
+
while (i < n && isAsciiDigit(chars[i])) {
|
|
1583
|
+
numChars.push(chars[i]);
|
|
1584
|
+
i++;
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
if (i < n && chars[i] === '.' && i + 1 < n && isAsciiDigit(chars[i + 1])) {
|
|
1590
|
+
throw new N3SyntaxError('Malformed numeric literal: multiple decimal points', start);
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
// Optional exponent part: e.g., 1e0, 1.1e-3, .5E+0.
|
|
1594
|
+
if (i < n && (chars[i] === 'e' || chars[i] === 'E')) {
|
|
1595
|
+
let j = i + 1;
|
|
1596
|
+
if (j < n && (chars[j] === '+' || chars[j] === '-')) j++;
|
|
1597
|
+
if (j < n && isAsciiDigit(chars[j])) {
|
|
1598
|
+
numChars.push(chars[i]); // e/E
|
|
1599
|
+
i++;
|
|
1600
|
+
if (i < n && (chars[i] === '+' || chars[i] === '-')) {
|
|
1601
|
+
numChars.push(chars[i]);
|
|
1602
|
+
i++;
|
|
1603
|
+
}
|
|
1604
|
+
while (i < n && isAsciiDigit(chars[i])) {
|
|
1605
|
+
numChars.push(chars[i]);
|
|
1606
|
+
i++;
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
if (i < n && chars[i] === '.' && i + 1 < n && isAsciiDigit(chars[i + 1])) {
|
|
1612
|
+
throw new N3SyntaxError('Malformed numeric literal: multiple decimal points', start);
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
tokens.push(new Token('Literal', numChars.join(''), start));
|
|
1616
|
+
continue;
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
// 6) Single-character punctuation. Use a switch rather than allocating a
|
|
1551
1620
|
// mapping object for every punctuation token in large inputs.
|
|
1552
1621
|
switch (c) {
|
|
1553
1622
|
case '{':
|
|
@@ -1668,13 +1737,17 @@ function lex(inputText, opts = {}) {
|
|
|
1668
1737
|
}
|
|
1669
1738
|
continue;
|
|
1670
1739
|
}
|
|
1740
|
+
if (cc === '\n' || cc === '\r') {
|
|
1741
|
+
throw new N3SyntaxError('Unescaped newline in short string literal', start);
|
|
1742
|
+
}
|
|
1671
1743
|
if (cc === '"') {
|
|
1672
1744
|
closed = true;
|
|
1673
1745
|
break;
|
|
1674
1746
|
}
|
|
1675
1747
|
if (sChars !== null) sChars.push(cc);
|
|
1676
1748
|
}
|
|
1677
|
-
|
|
1749
|
+
if (!closed) throw new N3SyntaxError('Unterminated short string literal "..."', start);
|
|
1750
|
+
const rawContent = sChars === null ? sliceChars(contentStart, i - 1) : sChars.join('');
|
|
1678
1751
|
const decoded = sChars === null ? rawContent : decodeN3StringEscapes(rawContent, start);
|
|
1679
1752
|
if (sChars !== null || inputMayContainInvalidStringChar) assertValidStringLiteralValue(decoded, start);
|
|
1680
1753
|
const s = JSON.stringify(decoded); // canonical short quoted form
|
|
@@ -1758,13 +1831,17 @@ function lex(inputText, opts = {}) {
|
|
|
1758
1831
|
}
|
|
1759
1832
|
continue;
|
|
1760
1833
|
}
|
|
1834
|
+
if (cc === '\n' || cc === '\r') {
|
|
1835
|
+
throw new N3SyntaxError('Unescaped newline in short string literal', start);
|
|
1836
|
+
}
|
|
1761
1837
|
if (cc === "'") {
|
|
1762
1838
|
closed = true;
|
|
1763
1839
|
break;
|
|
1764
1840
|
}
|
|
1765
1841
|
if (sChars !== null) sChars.push(cc);
|
|
1766
1842
|
}
|
|
1767
|
-
|
|
1843
|
+
if (!closed) throw new N3SyntaxError("Unterminated short string literal '...'", start);
|
|
1844
|
+
const rawContent = sChars === null ? sliceChars(contentStart, i - 1) : sChars.join('');
|
|
1768
1845
|
const decoded = sChars === null ? rawContent : decodeN3StringEscapes(rawContent, start);
|
|
1769
1846
|
if (sChars !== null || inputMayContainInvalidStringChar) assertValidStringLiteralValue(decoded, start);
|
|
1770
1847
|
const s = JSON.stringify(decoded); // canonical short quoted form
|
|
@@ -1847,52 +1924,6 @@ function lex(inputText, opts = {}) {
|
|
|
1847
1924
|
continue;
|
|
1848
1925
|
}
|
|
1849
1926
|
|
|
1850
|
-
// 6) Numeric literal (integer or float)
|
|
1851
|
-
if (isAsciiDigit(c) || (c === '-' && peek(1) !== null && isAsciiDigit(peek(1)))) {
|
|
1852
|
-
const start = i;
|
|
1853
|
-
const numChars = [c];
|
|
1854
|
-
i++;
|
|
1855
|
-
while (i < n) {
|
|
1856
|
-
const cc = chars[i];
|
|
1857
|
-
if (isAsciiDigit(cc)) {
|
|
1858
|
-
numChars.push(cc);
|
|
1859
|
-
i++;
|
|
1860
|
-
continue;
|
|
1861
|
-
}
|
|
1862
|
-
if (cc === '.') {
|
|
1863
|
-
if (i + 1 < n && isAsciiDigit(chars[i + 1])) {
|
|
1864
|
-
numChars.push('.');
|
|
1865
|
-
i++;
|
|
1866
|
-
continue;
|
|
1867
|
-
} else {
|
|
1868
|
-
break;
|
|
1869
|
-
}
|
|
1870
|
-
}
|
|
1871
|
-
break;
|
|
1872
|
-
}
|
|
1873
|
-
|
|
1874
|
-
// Optional exponent part: e.g., 1e0, 1.1e-3, 1.1E+0
|
|
1875
|
-
if (i < n && (chars[i] === 'e' || chars[i] === 'E')) {
|
|
1876
|
-
let j = i + 1;
|
|
1877
|
-
if (j < n && (chars[j] === '+' || chars[j] === '-')) j++;
|
|
1878
|
-
if (j < n && isAsciiDigit(chars[j])) {
|
|
1879
|
-
numChars.push(chars[i]); // e/E
|
|
1880
|
-
i++;
|
|
1881
|
-
if (i < n && (chars[i] === '+' || chars[i] === '-')) {
|
|
1882
|
-
numChars.push(chars[i]);
|
|
1883
|
-
i++;
|
|
1884
|
-
}
|
|
1885
|
-
while (i < n && isAsciiDigit(chars[i])) {
|
|
1886
|
-
numChars.push(chars[i]);
|
|
1887
|
-
i++;
|
|
1888
|
-
}
|
|
1889
|
-
}
|
|
1890
|
-
}
|
|
1891
|
-
|
|
1892
|
-
tokens.push(new Token('Literal', numChars.join(''), start));
|
|
1893
|
-
continue;
|
|
1894
|
-
}
|
|
1895
|
-
|
|
1896
1927
|
// 7) Identifiers / keywords / QNames
|
|
1897
1928
|
const start = i;
|
|
1898
1929
|
const word = readIdentText(start);
|
package/package.json
CHANGED
package/test/api.test.js
CHANGED
|
@@ -769,6 +769,132 @@ bad.:example a bad.:Person.
|
|
|
769
769
|
`,
|
|
770
770
|
expectError: true,
|
|
771
771
|
},
|
|
772
|
+
{
|
|
773
|
+
name: '12k2 invalid syntax: raw newline in short string literal should throw',
|
|
774
|
+
opt: { proofComments: false },
|
|
775
|
+
input: `
|
|
776
|
+
@prefix : <http://example.org/> .
|
|
777
|
+
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
|
778
|
+
|
|
779
|
+
:my :value "
|
|
780
|
+
123".
|
|
781
|
+
|
|
782
|
+
{
|
|
783
|
+
:my :value "\n123".
|
|
784
|
+
}
|
|
785
|
+
=>
|
|
786
|
+
{
|
|
787
|
+
:result :has :crash-syntax-11.
|
|
788
|
+
}.
|
|
789
|
+
|
|
790
|
+
{} => {
|
|
791
|
+
:test :contains :crash-syntax-11.
|
|
792
|
+
}.
|
|
793
|
+
|
|
794
|
+
{
|
|
795
|
+
:result :has :crash-syntax-11.
|
|
796
|
+
}
|
|
797
|
+
=>
|
|
798
|
+
{
|
|
799
|
+
:test :is false.
|
|
800
|
+
}.
|
|
801
|
+
`,
|
|
802
|
+
expectError: true,
|
|
803
|
+
},
|
|
804
|
+
{
|
|
805
|
+
name: '12k3 invalid syntax: repeated dots in numeric-looking literal should throw',
|
|
806
|
+
opt: { proofComments: false },
|
|
807
|
+
input: `
|
|
808
|
+
@prefix : <http://example.org/> .
|
|
809
|
+
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
|
810
|
+
|
|
811
|
+
:my :value 1.2.3.4.5.
|
|
812
|
+
|
|
813
|
+
{
|
|
814
|
+
:my :value 1.2.3.4.5.
|
|
815
|
+
}
|
|
816
|
+
=>
|
|
817
|
+
{
|
|
818
|
+
:result :has :crash-syntax-12.
|
|
819
|
+
}.
|
|
820
|
+
|
|
821
|
+
{} => {
|
|
822
|
+
:test :contains :crash-syntax-12.
|
|
823
|
+
}.
|
|
824
|
+
|
|
825
|
+
{
|
|
826
|
+
:result :has :crash-syntax-12.
|
|
827
|
+
}
|
|
828
|
+
=>
|
|
829
|
+
{
|
|
830
|
+
:test :is false.
|
|
831
|
+
}.
|
|
832
|
+
`,
|
|
833
|
+
expectError: true,
|
|
834
|
+
},
|
|
835
|
+
{
|
|
836
|
+
name: '12k4 success literal: signed integer shorthand parses',
|
|
837
|
+
opt: { proofComments: false },
|
|
838
|
+
input: `
|
|
839
|
+
@prefix : <http://example.org/> .
|
|
840
|
+
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
|
841
|
+
|
|
842
|
+
:my :value +42.
|
|
843
|
+
|
|
844
|
+
{
|
|
845
|
+
:my :value +42.
|
|
846
|
+
}
|
|
847
|
+
=>
|
|
848
|
+
{
|
|
849
|
+
:result :has :success-literal-32.
|
|
850
|
+
}.
|
|
851
|
+
|
|
852
|
+
{} => {
|
|
853
|
+
:test :contains :success-literal-32.
|
|
854
|
+
}.
|
|
855
|
+
|
|
856
|
+
{
|
|
857
|
+
:result :has :success-literal-32.
|
|
858
|
+
}
|
|
859
|
+
=>
|
|
860
|
+
{
|
|
861
|
+
:test :is true.
|
|
862
|
+
}.
|
|
863
|
+
`,
|
|
864
|
+
expect: [/:result\s+:has\s+:success-literal-32\s*\./, /:test\s+:is\s+true\s*\./],
|
|
865
|
+
},
|
|
866
|
+
{
|
|
867
|
+
name: '12k5 success literal: leading-dot decimal shorthand parses',
|
|
868
|
+
opt: { proofComments: false },
|
|
869
|
+
input: `
|
|
870
|
+
@prefix : <http://example.org/> .
|
|
871
|
+
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
|
872
|
+
|
|
873
|
+
:my :value .5.
|
|
874
|
+
|
|
875
|
+
{
|
|
876
|
+
:my :value .5.
|
|
877
|
+
}
|
|
878
|
+
=>
|
|
879
|
+
{
|
|
880
|
+
:result :has :success-literal-33.
|
|
881
|
+
}.
|
|
882
|
+
|
|
883
|
+
{} => {
|
|
884
|
+
:test :contains :success-literal-33.
|
|
885
|
+
}.
|
|
886
|
+
|
|
887
|
+
{
|
|
888
|
+
:result :has :success-literal-33.
|
|
889
|
+
}
|
|
890
|
+
=>
|
|
891
|
+
{
|
|
892
|
+
:test :is true.
|
|
893
|
+
}.
|
|
894
|
+
`,
|
|
895
|
+
expect: [/:result\s+:has\s+:success-literal-33\s*\./, /:test\s+:is\s+true\s*\./],
|
|
896
|
+
},
|
|
897
|
+
|
|
772
898
|
{
|
|
773
899
|
name: '12l regression: IRIREF \\u escape decodes before log:uri comparison (mismatch stays falsey)',
|
|
774
900
|
opt: { proofComments: false },
|
|
@@ -1360,7 +1486,7 @@ _:l2 rdf:rest rdf:nil.
|
|
|
1360
1486
|
odrl:action ?Ignore ;
|
|
1361
1487
|
odrl:duty [ odrl:action ?A ]
|
|
1362
1488
|
].
|
|
1363
|
-
( "%% Duty_(a,t)(action:%s)
|
|
1489
|
+
( "%% Duty_(a,t)(action:%s)\\n%% => ~Possible_(a,t)(~action:%s)\\n" ?A ?A ) string:format ?Str.
|
|
1364
1490
|
}
|
|
1365
1491
|
=>
|
|
1366
1492
|
{
|
package/test/builtins.test.js
CHANGED
|
@@ -170,17 +170,25 @@ const cases = [
|
|
|
170
170
|
{ "2147483648"^^xsd:int dt:invalidForDatatype xsd:int . } => { :invalid :int true } .
|
|
171
171
|
{ "2"^^xsd:boolean dt:invalidForDatatype xsd:boolean . } => { :invalid :boolean true } .
|
|
172
172
|
{ "2026-02-31T00:00:00Z"^^xsd:dateTime dt:invalidForDatatype xsd:dateTime . } => { :invalid :dateTime true } .
|
|
173
|
+
{ " 1.0 "^^xsd:decimal dt:invalidForDatatype xsd:decimal . } => { :invalid :decimalWhitespace true } .
|
|
174
|
+
{ "02026-06-10T12:00:00Z"^^xsd:dateTime dt:invalidForDatatype xsd:dateTime . } => { :invalid :dateTimeYear true } .
|
|
173
175
|
|
|
174
176
|
{ "01"^^xsd:integer dt:sameValueAs "1.0"^^xsd:decimal . } => { :same :numeric true } .
|
|
175
177
|
{ "true"^^xsd:boolean dt:sameValueAs "1"^^xsd:boolean . } => { :same :boolean true } .
|
|
176
178
|
{ "2026-06-10T12:00:00Z"^^xsd:dateTime dt:sameValueAs "2026-06-10T14:00:00+02:00"^^xsd:dateTime . } => { :same :dateTime true } .
|
|
179
|
+
{ "2026-12-31T24:00:00Z"^^xsd:dateTime dt:sameValueAs "2027-01-01T00:00:00Z"^^xsd:dateTime . } => { :same :midnightRollover true } .
|
|
177
180
|
{ "AQID"^^xsd:base64Binary dt:sameValueAs "010203"^^xsd:hexBinary . } => { :same :binary true } .
|
|
178
181
|
{ "11"^^xsd:integer dt:differentValueFrom "12"^^xsd:integer . } => { :different :numeric true } .
|
|
179
182
|
|
|
183
|
+
{ ("1"^^xsd:integer xsd:integer) dt:validForDatatype true . } => { :tuple :valid true } .
|
|
184
|
+
{ ("abc"^^xsd:integer xsd:integer) dt:validForDatatype false . } => { :tuple :invalidBoolean true } .
|
|
185
|
+
{ ("abc"^^xsd:integer xsd:integer) dt:invalidForDatatype ?invalid . } => { :tuple :invalidResult ?invalid } .
|
|
186
|
+
|
|
180
187
|
{ "01"^^xsd:integer dt:canonicalLiteral ?ci . } => { :canonical :integer ?ci } .
|
|
181
188
|
{ "1"^^xsd:boolean dt:canonicalLiteral ?cb . } => { :canonical :boolean ?cb } .
|
|
182
189
|
{ " a\t b "^^xsd:token dt:canonicalLiteral ?ct . } => { :canonical :token ?ct } .
|
|
183
190
|
{ "2026-06-10T14:00:00+02:00"^^xsd:dateTime dt:canonicalLiteral ?cd . } => { :canonical :dateTime ?cd } .
|
|
191
|
+
{ "2026-12-31T24:00:00Z"^^xsd:dateTime dt:canonicalLiteral ?cm . } => { :canonical :midnightRollover ?cm } .
|
|
184
192
|
`);
|
|
185
193
|
|
|
186
194
|
assert.match(out, /:integer :datatype xsd:integer \./);
|
|
@@ -192,15 +200,22 @@ const cases = [
|
|
|
192
200
|
assert.match(out, /:invalid :int true \./);
|
|
193
201
|
assert.match(out, /:invalid :boolean true \./);
|
|
194
202
|
assert.match(out, /:invalid :dateTime true \./);
|
|
203
|
+
assert.match(out, /:invalid :decimalWhitespace true \./);
|
|
204
|
+
assert.match(out, /:invalid :dateTimeYear true \./);
|
|
195
205
|
assert.match(out, /:same :numeric true \./);
|
|
196
206
|
assert.match(out, /:same :boolean true \./);
|
|
197
207
|
assert.match(out, /:same :dateTime true \./);
|
|
208
|
+
assert.match(out, /:same :midnightRollover true \./);
|
|
198
209
|
assert.match(out, /:same :binary true \./);
|
|
199
210
|
assert.match(out, /:different :numeric true \./);
|
|
211
|
+
assert.match(out, /:tuple :valid true \./);
|
|
212
|
+
assert.match(out, /:tuple :invalidBoolean true \./);
|
|
213
|
+
assert.match(out, /:tuple :invalidResult true \./);
|
|
200
214
|
assert.match(out, /:canonical :integer "1"\^\^xsd:integer \./);
|
|
201
215
|
assert.match(out, /:canonical :boolean true \./);
|
|
202
216
|
assert.match(out, /:canonical :token "a b"\^\^xsd:token \./);
|
|
203
217
|
assert.match(out, /:canonical :dateTime "2026-06-10T12:00:00Z"\^\^xsd:dateTime \./);
|
|
218
|
+
assert.match(out, /:canonical :midnightRollover "2027-01-01T00:00:00Z"\^\^xsd:dateTime \./);
|
|
204
219
|
},
|
|
205
220
|
},
|
|
206
221
|
|
package/test/playground.test.js
CHANGED
|
@@ -906,16 +906,30 @@ ${JSON.stringify(last, null, 2)}`);
|
|
|
906
906
|
const fuseProgram = fs.readFileSync(path.join(ROOT, 'examples', 'fuse.n3'), 'utf8');
|
|
907
907
|
const outputStringProgram = `@prefix : <#> .
|
|
908
908
|
@prefix log: <http://www.w3.org/2000/10/swap/log#> .
|
|
909
|
-
:report log:outputString "## Hello from output string
|
|
909
|
+
:report log:outputString """## Hello from output string
|
|
910
|
+
|
|
911
|
+
Line 2 with **bold** and [Eyeling](https://example.org/eyeling)
|
|
912
|
+
""" .
|
|
910
913
|
`;
|
|
911
914
|
const riskMarkdownOutputStringProgram = `@prefix : <#> .
|
|
912
915
|
@prefix log: <http://www.w3.org/2000/10/swap/log#> .
|
|
913
|
-
:report log:outputString "# Risk report
|
|
916
|
+
:report log:outputString """# Risk report
|
|
917
|
+
|
|
918
|
+
### Clause H1 — score 100
|
|
919
|
+
|
|
920
|
+
Risk: secondary use is permitted without a safeguard. Clause H1: Hospital may provide electronic health data for secondary use.
|
|
921
|
+
|
|
922
|
+
- **Mitigation for clause H1:** Require a permit before secondary use.
|
|
923
|
+
""" .
|
|
914
924
|
`;
|
|
915
925
|
const baseOnlyMarkdownProgram = `@base <https://raw.githubusercontent.com/eyereasoner/eyeling/refs/heads/main/examples/smoke-arithmetic.n3> .
|
|
916
926
|
@prefix : <#> .
|
|
917
927
|
@prefix log: <http://www.w3.org/2000/10/swap/log#> .
|
|
918
|
-
:report log:outputString "# stateurl link base
|
|
928
|
+
:report log:outputString """# stateurl link base
|
|
929
|
+
|
|
930
|
+
[N3 rules](../smoke-arithmetic.n3)
|
|
931
|
+
[Input TriG](../input/smoke-arithmetic.trig)
|
|
932
|
+
""" .
|
|
919
933
|
`;
|
|
920
934
|
const logQueryTurtleProgram = `@prefix : <#> .
|
|
921
935
|
@prefix log: <http://www.w3.org/2000/10/swap/log#> .
|