eyeling 1.23.3 → 1.23.5
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/HANDBOOK.md +9 -7
- package/dist/browser/eyeling.browser.js +59 -15
- package/eyeling.js +59 -15
- package/lib/engine.js +14 -3
- package/lib/entry.js +5 -0
- package/lib/prelude.js +40 -12
- package/package.json +1 -1
- package/test/api.test.js +27 -0
- package/test/playground.test.js +45 -0
package/HANDBOOK.md
CHANGED
|
@@ -1902,6 +1902,8 @@ node eyeling.js --builtin examples/builtin/queens.js examples/queens.n3
|
|
|
1902
1902
|
|
|
1903
1903
|
Examples that do not need a custom builtin should not add a matching file under `examples/builtin/`. Examples that do need one should ship it there and let the examples test runner load it uniformly. For example, `examples/sudoku.n3` is paired with `examples/builtin/sudoku.js`, and `examples/queens.n3` is paired with `examples/builtin/queens.js`.
|
|
1904
1904
|
|
|
1905
|
+
The browser playground follows the same convention for URL-loaded repository examples: when a loaded URL looks like `.../examples/name.n3`, the playground tries to fetch `.../examples/builtin/name.js` and register it before reasoning. If no matching builtin file exists, the N3 program runs normally.
|
|
1906
|
+
|
|
1905
1907
|
### 14.2 The bundled Node CLI/runtime (`eyeling.js`)
|
|
1906
1908
|
|
|
1907
1909
|
The bundle contains the whole engine. The CLI path is the “canonical behavior”:
|
|
@@ -1965,7 +1967,7 @@ It deliberately does **not** expose `loadBuiltinModule(...)`, because loading bu
|
|
|
1965
1967
|
|
|
1966
1968
|
For browser apps, prefer running Eyeling in a **Web Worker** and importing `eyeling/browser` there.
|
|
1967
1969
|
|
|
1968
|
-
### 14.
|
|
1970
|
+
### 14.4 `lib/entry.js`: bundler-friendly exports
|
|
1969
1971
|
|
|
1970
1972
|
`lib/entry.js` exports:
|
|
1971
1973
|
|
|
@@ -1974,14 +1976,14 @@ For browser apps, prefer running Eyeling in a **Web Worker** and importing `eyel
|
|
|
1974
1976
|
|
|
1975
1977
|
`rdfjs` is a small built-in RDF/JS `DataFactory`, so browser / worker code can construct quads without pulling in another package first.
|
|
1976
1978
|
|
|
1977
|
-
### 14.
|
|
1979
|
+
### 14.5 JavaScript API
|
|
1978
1980
|
|
|
1979
1981
|
Eyeling exposes two JavaScript entry styles:
|
|
1980
1982
|
|
|
1981
1983
|
- `reason(...)` from `index.js` when you want the same text output as the CLI
|
|
1982
1984
|
- `reasonStream(...)` / `reasonRdfJs(...)` from the Node bundle or `eyeling/browser` when you want in-process reasoning and structured RDF/JS results
|
|
1983
1985
|
|
|
1984
|
-
#### 14.
|
|
1986
|
+
#### 14.5.1 npm helper: `reason(...)`
|
|
1985
1987
|
|
|
1986
1988
|
The npm `reason(...)` function does something intentionally simple and robust:
|
|
1987
1989
|
|
|
@@ -2034,7 +2036,7 @@ Notes:
|
|
|
2034
2036
|
- By default, the npm helper keeps output machine-friendly (`proofComments: false`).
|
|
2035
2037
|
- Use this path when you want CLI-equivalent behavior inside JavaScript.
|
|
2036
2038
|
|
|
2037
|
-
#### 14.
|
|
2039
|
+
#### 14.5.2 RDF-JS and Eyeling rule-object interoperability
|
|
2038
2040
|
|
|
2039
2041
|
The JavaScript APIs accept four input styles:
|
|
2040
2042
|
|
|
@@ -2112,7 +2114,7 @@ console.log(out);
|
|
|
2112
2114
|
|
|
2113
2115
|
You can also pass a full AST bundle directly, for example `[prefixes, triples, forwardRules, backwardRules]`.
|
|
2114
2116
|
|
|
2115
|
-
#### 14.
|
|
2117
|
+
#### 14.5.3 In-process bundle API: `reasonStream(...)` and `reasonRdfJs(...)`
|
|
2116
2118
|
|
|
2117
2119
|
Use the bundle entry if you want structured results while the engine is running instead of final CLI text after the fact.
|
|
2118
2120
|
|
|
@@ -2151,7 +2153,7 @@ Use these entry points when you need one or more of the following:
|
|
|
2151
2153
|
- derived results consumed as RDF/JS quads
|
|
2152
2154
|
- streaming derived RDF/JS quads during reasoning
|
|
2153
2155
|
|
|
2154
|
-
### 14.
|
|
2156
|
+
### 14.6 Choosing the right entry point
|
|
2155
2157
|
|
|
2156
2158
|
A practical rule of thumb:
|
|
2157
2159
|
|
|
@@ -2392,7 +2394,7 @@ Run it explicitly like this:
|
|
|
2392
2394
|
eyeling --builtin examples/builtin/sudoku.js examples/sudoku.n3
|
|
2393
2395
|
```
|
|
2394
2396
|
|
|
2395
|
-
`npm run test:examples` uses the same convention automatically: when it sees `examples/builtin/sudoku.js` next to `examples/sudoku.n3`, it loads that module for the Sudoku example.
|
|
2397
|
+
`npm run test:examples` uses the same convention automatically: when it sees `examples/builtin/sudoku.js` next to `examples/sudoku.n3`, it loads that module for the Sudoku example. The browser playground uses the convention too for URL-loaded repository examples, so loading the raw `examples/sudoku.n3` URL also fetches and registers the matching `examples/builtin/sudoku.js` module.
|
|
2396
2398
|
|
|
2397
2399
|
That example is useful for two reasons:
|
|
2398
2400
|
|
|
@@ -8702,10 +8702,21 @@ ${triples.map((tr) => ` ${tripleToN3(tr, prefixes)}`).join('\n')}
|
|
|
8702
8702
|
deref.setEnforceHttpsEnabled(!!enforceHttps);
|
|
8703
8703
|
proofCommentsEnabled = !!proof;
|
|
8704
8704
|
|
|
8705
|
+
function registerBuiltinModuleOption(mod, index) {
|
|
8706
|
+
if (!mod) return;
|
|
8707
|
+
if (typeof mod === 'string') {
|
|
8708
|
+
loadBuiltinModule(mod);
|
|
8709
|
+
return;
|
|
8710
|
+
}
|
|
8711
|
+
registerBuiltinModule(mod, `<reasonStream builtinModules[${index}]>`);
|
|
8712
|
+
}
|
|
8713
|
+
|
|
8705
8714
|
if (Array.isArray(builtinModules)) {
|
|
8706
|
-
for (
|
|
8707
|
-
|
|
8708
|
-
|
|
8715
|
+
for (let i = 0; i < builtinModules.length; i += 1) {
|
|
8716
|
+
registerBuiltinModuleOption(builtinModules[i], i);
|
|
8717
|
+
}
|
|
8718
|
+
} else {
|
|
8719
|
+
registerBuiltinModuleOption(builtinModules, 0);
|
|
8709
8720
|
}
|
|
8710
8721
|
|
|
8711
8722
|
let prefixes, triples, frules, brules, logQueryRules;
|
|
@@ -8989,6 +9000,11 @@ ${triples.map((tr) => ` ${tripleToN3(tr, prefixes)}`).join('\n')}
|
|
|
8989
9000
|
tripleToN3: engine.tripleToN3,
|
|
8990
9001
|
collectOutputStringsFromFacts: engine.collectOutputStringsFromFacts,
|
|
8991
9002
|
prettyPrintQueryTriples: engine.prettyPrintQueryTriples,
|
|
9003
|
+
registerBuiltin: engine.registerBuiltin,
|
|
9004
|
+
unregisterBuiltin: engine.unregisterBuiltin,
|
|
9005
|
+
registerBuiltinModule: engine.registerBuiltinModule,
|
|
9006
|
+
loadBuiltinModule: engine.loadBuiltinModule,
|
|
9007
|
+
listBuiltinIris: engine.listBuiltinIris,
|
|
8992
9008
|
getEnforceHttpsEnabled: engine.getEnforceHttpsEnabled,
|
|
8993
9009
|
setEnforceHttpsEnabled: engine.setEnforceHttpsEnabled,
|
|
8994
9010
|
getProofCommentsEnabled: engine.getProofCommentsEnabled,
|
|
@@ -11142,6 +11158,37 @@ ${triples.map((tr) => ` ${tripleToN3(tr, prefixes)}`).join('\n')}
|
|
|
11142
11158
|
|
|
11143
11159
|
const __literalPartsCache = new Map(); // lit string -> [lex, dt]
|
|
11144
11160
|
|
|
11161
|
+
function quotedLiteralEndIndex(str) {
|
|
11162
|
+
if (typeof str !== 'string' || str.length < 2) return -1;
|
|
11163
|
+
|
|
11164
|
+
const quote = str[0];
|
|
11165
|
+
if (quote !== '"' && quote !== "'") return -1;
|
|
11166
|
+
|
|
11167
|
+
const delimLen = str.startsWith(quote.repeat(3)) ? 3 : 1;
|
|
11168
|
+
const delim = quote.repeat(delimLen);
|
|
11169
|
+
|
|
11170
|
+
let i = delimLen;
|
|
11171
|
+
while (i < str.length) {
|
|
11172
|
+
// Stored literals may contain escaped quotes, e.g. \"...\"^^xsd:dateTime
|
|
11173
|
+
// inside a plain string. Those must not terminate the outer lexical form.
|
|
11174
|
+
if (str[i] === '\\') {
|
|
11175
|
+
i += 2;
|
|
11176
|
+
continue;
|
|
11177
|
+
}
|
|
11178
|
+
|
|
11179
|
+
if (delimLen === 1) {
|
|
11180
|
+
if (str[i] === quote) return i + 1;
|
|
11181
|
+
i += 1;
|
|
11182
|
+
continue;
|
|
11183
|
+
}
|
|
11184
|
+
|
|
11185
|
+
if (str.startsWith(delim, i)) return i + delimLen;
|
|
11186
|
+
i += 1;
|
|
11187
|
+
}
|
|
11188
|
+
|
|
11189
|
+
return -1;
|
|
11190
|
+
}
|
|
11191
|
+
|
|
11145
11192
|
function literalParts(lit) {
|
|
11146
11193
|
// Avoid caching extremely large literals (notably huge numeric intermediates)
|
|
11147
11194
|
// to prevent unbounded memory growth.
|
|
@@ -11159,24 +11206,21 @@ ${triples.map((tr) => ` ${tripleToN3(tr, prefixes)}`).join('\n')}
|
|
|
11159
11206
|
let lex = lit;
|
|
11160
11207
|
let dt = null;
|
|
11161
11208
|
|
|
11162
|
-
const
|
|
11163
|
-
|
|
11164
|
-
|
|
11165
|
-
|
|
11166
|
-
dt = match[3];
|
|
11209
|
+
const lexEnd = quotedLiteralEndIndex(lit);
|
|
11210
|
+
if (lexEnd > 0 && lit.startsWith('^^', lexEnd)) {
|
|
11211
|
+
lex = lit.slice(0, lexEnd);
|
|
11212
|
+
dt = lit.slice(lexEnd + 2);
|
|
11167
11213
|
if (dt.startsWith('<') && dt.endsWith('>')) {
|
|
11168
11214
|
dt = dt.slice(1, -1);
|
|
11169
11215
|
}
|
|
11170
11216
|
}
|
|
11171
11217
|
|
|
11172
11218
|
// Strip LANGTAG from the lexical form when present.
|
|
11173
|
-
|
|
11174
|
-
|
|
11175
|
-
|
|
11176
|
-
|
|
11177
|
-
|
|
11178
|
-
lex = lex.slice(0, lastQuote + 1);
|
|
11179
|
-
}
|
|
11219
|
+
const langLexEnd = quotedLiteralEndIndex(lex);
|
|
11220
|
+
if (langLexEnd > 0 && lex[0] === '"' && langLexEnd < lex.length && lex[langLexEnd] === '@') {
|
|
11221
|
+
const lang = lex.slice(langLexEnd + 1);
|
|
11222
|
+
if (/^[A-Za-z]+(?:-[A-Za-z0-9]+)*$/.test(lang)) {
|
|
11223
|
+
lex = lex.slice(0, langLexEnd);
|
|
11180
11224
|
}
|
|
11181
11225
|
}
|
|
11182
11226
|
|
package/eyeling.js
CHANGED
|
@@ -8679,10 +8679,21 @@ function reasonStream(input, opts = {}) {
|
|
|
8679
8679
|
deref.setEnforceHttpsEnabled(!!enforceHttps);
|
|
8680
8680
|
proofCommentsEnabled = !!proof;
|
|
8681
8681
|
|
|
8682
|
+
function registerBuiltinModuleOption(mod, index) {
|
|
8683
|
+
if (!mod) return;
|
|
8684
|
+
if (typeof mod === 'string') {
|
|
8685
|
+
loadBuiltinModule(mod);
|
|
8686
|
+
return;
|
|
8687
|
+
}
|
|
8688
|
+
registerBuiltinModule(mod, `<reasonStream builtinModules[${index}]>`);
|
|
8689
|
+
}
|
|
8690
|
+
|
|
8682
8691
|
if (Array.isArray(builtinModules)) {
|
|
8683
|
-
for (
|
|
8684
|
-
|
|
8685
|
-
|
|
8692
|
+
for (let i = 0; i < builtinModules.length; i += 1) {
|
|
8693
|
+
registerBuiltinModuleOption(builtinModules[i], i);
|
|
8694
|
+
}
|
|
8695
|
+
} else {
|
|
8696
|
+
registerBuiltinModuleOption(builtinModules, 0);
|
|
8686
8697
|
}
|
|
8687
8698
|
|
|
8688
8699
|
let prefixes, triples, frules, brules, logQueryRules;
|
|
@@ -8967,6 +8978,11 @@ module.exports = {
|
|
|
8967
8978
|
tripleToN3: engine.tripleToN3,
|
|
8968
8979
|
collectOutputStringsFromFacts: engine.collectOutputStringsFromFacts,
|
|
8969
8980
|
prettyPrintQueryTriples: engine.prettyPrintQueryTriples,
|
|
8981
|
+
registerBuiltin: engine.registerBuiltin,
|
|
8982
|
+
unregisterBuiltin: engine.unregisterBuiltin,
|
|
8983
|
+
registerBuiltinModule: engine.registerBuiltinModule,
|
|
8984
|
+
loadBuiltinModule: engine.loadBuiltinModule,
|
|
8985
|
+
listBuiltinIris: engine.listBuiltinIris,
|
|
8970
8986
|
getEnforceHttpsEnabled: engine.getEnforceHttpsEnabled,
|
|
8971
8987
|
setEnforceHttpsEnabled: engine.setEnforceHttpsEnabled,
|
|
8972
8988
|
getProofCommentsEnabled: engine.getProofCommentsEnabled,
|
|
@@ -11114,6 +11130,37 @@ const MAX_LITERAL_PARTS_CACHE_LEN = 1024;
|
|
|
11114
11130
|
|
|
11115
11131
|
const __literalPartsCache = new Map(); // lit string -> [lex, dt]
|
|
11116
11132
|
|
|
11133
|
+
function quotedLiteralEndIndex(str) {
|
|
11134
|
+
if (typeof str !== 'string' || str.length < 2) return -1;
|
|
11135
|
+
|
|
11136
|
+
const quote = str[0];
|
|
11137
|
+
if (quote !== '"' && quote !== "'") return -1;
|
|
11138
|
+
|
|
11139
|
+
const delimLen = str.startsWith(quote.repeat(3)) ? 3 : 1;
|
|
11140
|
+
const delim = quote.repeat(delimLen);
|
|
11141
|
+
|
|
11142
|
+
let i = delimLen;
|
|
11143
|
+
while (i < str.length) {
|
|
11144
|
+
// Stored literals may contain escaped quotes, e.g. \"...\"^^xsd:dateTime
|
|
11145
|
+
// inside a plain string. Those must not terminate the outer lexical form.
|
|
11146
|
+
if (str[i] === '\\') {
|
|
11147
|
+
i += 2;
|
|
11148
|
+
continue;
|
|
11149
|
+
}
|
|
11150
|
+
|
|
11151
|
+
if (delimLen === 1) {
|
|
11152
|
+
if (str[i] === quote) return i + 1;
|
|
11153
|
+
i += 1;
|
|
11154
|
+
continue;
|
|
11155
|
+
}
|
|
11156
|
+
|
|
11157
|
+
if (str.startsWith(delim, i)) return i + delimLen;
|
|
11158
|
+
i += 1;
|
|
11159
|
+
}
|
|
11160
|
+
|
|
11161
|
+
return -1;
|
|
11162
|
+
}
|
|
11163
|
+
|
|
11117
11164
|
function literalParts(lit) {
|
|
11118
11165
|
// Avoid caching extremely large literals (notably huge numeric intermediates)
|
|
11119
11166
|
// to prevent unbounded memory growth.
|
|
@@ -11131,24 +11178,21 @@ function literalParts(lit) {
|
|
|
11131
11178
|
let lex = lit;
|
|
11132
11179
|
let dt = null;
|
|
11133
11180
|
|
|
11134
|
-
const
|
|
11135
|
-
|
|
11136
|
-
|
|
11137
|
-
|
|
11138
|
-
dt = match[3];
|
|
11181
|
+
const lexEnd = quotedLiteralEndIndex(lit);
|
|
11182
|
+
if (lexEnd > 0 && lit.startsWith('^^', lexEnd)) {
|
|
11183
|
+
lex = lit.slice(0, lexEnd);
|
|
11184
|
+
dt = lit.slice(lexEnd + 2);
|
|
11139
11185
|
if (dt.startsWith('<') && dt.endsWith('>')) {
|
|
11140
11186
|
dt = dt.slice(1, -1);
|
|
11141
11187
|
}
|
|
11142
11188
|
}
|
|
11143
11189
|
|
|
11144
11190
|
// Strip LANGTAG from the lexical form when present.
|
|
11145
|
-
|
|
11146
|
-
|
|
11147
|
-
|
|
11148
|
-
|
|
11149
|
-
|
|
11150
|
-
lex = lex.slice(0, lastQuote + 1);
|
|
11151
|
-
}
|
|
11191
|
+
const langLexEnd = quotedLiteralEndIndex(lex);
|
|
11192
|
+
if (langLexEnd > 0 && lex[0] === '"' && langLexEnd < lex.length && lex[langLexEnd] === '@') {
|
|
11193
|
+
const lang = lex.slice(langLexEnd + 1);
|
|
11194
|
+
if (/^[A-Za-z]+(?:-[A-Za-z0-9]+)*$/.test(lang)) {
|
|
11195
|
+
lex = lex.slice(0, langLexEnd);
|
|
11152
11196
|
}
|
|
11153
11197
|
}
|
|
11154
11198
|
|
package/lib/engine.js
CHANGED
|
@@ -3384,10 +3384,21 @@ function reasonStream(input, opts = {}) {
|
|
|
3384
3384
|
deref.setEnforceHttpsEnabled(!!enforceHttps);
|
|
3385
3385
|
proofCommentsEnabled = !!proof;
|
|
3386
3386
|
|
|
3387
|
+
function registerBuiltinModuleOption(mod, index) {
|
|
3388
|
+
if (!mod) return;
|
|
3389
|
+
if (typeof mod === 'string') {
|
|
3390
|
+
loadBuiltinModule(mod);
|
|
3391
|
+
return;
|
|
3392
|
+
}
|
|
3393
|
+
registerBuiltinModule(mod, `<reasonStream builtinModules[${index}]>`);
|
|
3394
|
+
}
|
|
3395
|
+
|
|
3387
3396
|
if (Array.isArray(builtinModules)) {
|
|
3388
|
-
for (
|
|
3389
|
-
|
|
3390
|
-
|
|
3397
|
+
for (let i = 0; i < builtinModules.length; i += 1) {
|
|
3398
|
+
registerBuiltinModuleOption(builtinModules[i], i);
|
|
3399
|
+
}
|
|
3400
|
+
} else {
|
|
3401
|
+
registerBuiltinModuleOption(builtinModules, 0);
|
|
3391
3402
|
}
|
|
3392
3403
|
|
|
3393
3404
|
let prefixes, triples, frules, brules, logQueryRules;
|
package/lib/entry.js
CHANGED
|
@@ -34,6 +34,11 @@ module.exports = {
|
|
|
34
34
|
tripleToN3: engine.tripleToN3,
|
|
35
35
|
collectOutputStringsFromFacts: engine.collectOutputStringsFromFacts,
|
|
36
36
|
prettyPrintQueryTriples: engine.prettyPrintQueryTriples,
|
|
37
|
+
registerBuiltin: engine.registerBuiltin,
|
|
38
|
+
unregisterBuiltin: engine.unregisterBuiltin,
|
|
39
|
+
registerBuiltinModule: engine.registerBuiltinModule,
|
|
40
|
+
loadBuiltinModule: engine.loadBuiltinModule,
|
|
41
|
+
listBuiltinIris: engine.listBuiltinIris,
|
|
37
42
|
getEnforceHttpsEnabled: engine.getEnforceHttpsEnabled,
|
|
38
43
|
setEnforceHttpsEnabled: engine.setEnforceHttpsEnabled,
|
|
39
44
|
getProofCommentsEnabled: engine.getProofCommentsEnabled,
|
package/lib/prelude.js
CHANGED
|
@@ -43,6 +43,37 @@ const MAX_LITERAL_PARTS_CACHE_LEN = 1024;
|
|
|
43
43
|
|
|
44
44
|
const __literalPartsCache = new Map(); // lit string -> [lex, dt]
|
|
45
45
|
|
|
46
|
+
function quotedLiteralEndIndex(str) {
|
|
47
|
+
if (typeof str !== 'string' || str.length < 2) return -1;
|
|
48
|
+
|
|
49
|
+
const quote = str[0];
|
|
50
|
+
if (quote !== '"' && quote !== "'") return -1;
|
|
51
|
+
|
|
52
|
+
const delimLen = str.startsWith(quote.repeat(3)) ? 3 : 1;
|
|
53
|
+
const delim = quote.repeat(delimLen);
|
|
54
|
+
|
|
55
|
+
let i = delimLen;
|
|
56
|
+
while (i < str.length) {
|
|
57
|
+
// Stored literals may contain escaped quotes, e.g. \"...\"^^xsd:dateTime
|
|
58
|
+
// inside a plain string. Those must not terminate the outer lexical form.
|
|
59
|
+
if (str[i] === '\\') {
|
|
60
|
+
i += 2;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (delimLen === 1) {
|
|
65
|
+
if (str[i] === quote) return i + 1;
|
|
66
|
+
i += 1;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (str.startsWith(delim, i)) return i + delimLen;
|
|
71
|
+
i += 1;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return -1;
|
|
75
|
+
}
|
|
76
|
+
|
|
46
77
|
function literalParts(lit) {
|
|
47
78
|
// Avoid caching extremely large literals (notably huge numeric intermediates)
|
|
48
79
|
// to prevent unbounded memory growth.
|
|
@@ -60,24 +91,21 @@ function literalParts(lit) {
|
|
|
60
91
|
let lex = lit;
|
|
61
92
|
let dt = null;
|
|
62
93
|
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
dt = match[3];
|
|
94
|
+
const lexEnd = quotedLiteralEndIndex(lit);
|
|
95
|
+
if (lexEnd > 0 && lit.startsWith('^^', lexEnd)) {
|
|
96
|
+
lex = lit.slice(0, lexEnd);
|
|
97
|
+
dt = lit.slice(lexEnd + 2);
|
|
68
98
|
if (dt.startsWith('<') && dt.endsWith('>')) {
|
|
69
99
|
dt = dt.slice(1, -1);
|
|
70
100
|
}
|
|
71
101
|
}
|
|
72
102
|
|
|
73
103
|
// Strip LANGTAG from the lexical form when present.
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
lex = lex.slice(0, lastQuote + 1);
|
|
80
|
-
}
|
|
104
|
+
const langLexEnd = quotedLiteralEndIndex(lex);
|
|
105
|
+
if (langLexEnd > 0 && lex[0] === '"' && langLexEnd < lex.length && lex[langLexEnd] === '@') {
|
|
106
|
+
const lang = lex.slice(langLexEnd + 1);
|
|
107
|
+
if (/^[A-Za-z]+(?:-[A-Za-z0-9]+)*$/.test(lang)) {
|
|
108
|
+
lex = lex.slice(0, langLexEnd);
|
|
81
109
|
}
|
|
82
110
|
}
|
|
83
111
|
|
package/package.json
CHANGED
package/test/api.test.js
CHANGED
|
@@ -288,6 +288,33 @@ const cases = [
|
|
|
288
288
|
assert.equal(String(out).trimEnd(), '^^');
|
|
289
289
|
},
|
|
290
290
|
},
|
|
291
|
+
{
|
|
292
|
+
name: '00c quoted string containing typed literal syntax remains plain string',
|
|
293
|
+
opt: { proofComments: false },
|
|
294
|
+
input: `
|
|
295
|
+
@prefix : <http://example.org/>.
|
|
296
|
+
|
|
297
|
+
:s :p """
|
|
298
|
+
@prefix : <http://example.org/>.
|
|
299
|
+
@prefix xsd: <http://www.w3.org/2001/XMLSchema#>.
|
|
300
|
+
|
|
301
|
+
:Let :param \"2023-04-01T18:06:04Z\"^^xsd:dateTime .
|
|
302
|
+
""".
|
|
303
|
+
|
|
304
|
+
{
|
|
305
|
+
:s :p ?O.
|
|
306
|
+
}
|
|
307
|
+
=>
|
|
308
|
+
{
|
|
309
|
+
:test :is ?O.
|
|
310
|
+
}.
|
|
311
|
+
`,
|
|
312
|
+
expect: [/^:test\s+:is\s+/m],
|
|
313
|
+
notExpect: [/\^\^<xsd:dateTime/],
|
|
314
|
+
check(out) {
|
|
315
|
+
assert.match(out, /\\"2023-04-01T18:06:04Z\\"\^\^xsd:dateTime \./);
|
|
316
|
+
},
|
|
317
|
+
},
|
|
291
318
|
{
|
|
292
319
|
name: '01 forward rule: p -> q',
|
|
293
320
|
opt: { proofComments: false },
|
package/test/playground.test.js
CHANGED
|
@@ -488,6 +488,8 @@ async function main() {
|
|
|
488
488
|
// Intercept CodeMirror + remote GitHub raw URLs (keep test deterministic).
|
|
489
489
|
const localPkg = fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8');
|
|
490
490
|
const localEyeling = fs.readFileSync(path.join(ROOT, 'eyeling.js'), 'utf8');
|
|
491
|
+
const localSudoku = fs.readFileSync(path.join(ROOT, 'examples', 'sudoku.n3'), 'utf8');
|
|
492
|
+
const localSudokuBuiltin = fs.readFileSync(path.join(ROOT, 'examples', 'builtin', 'sudoku.js'), 'utf8');
|
|
491
493
|
|
|
492
494
|
const intercept = new Map([
|
|
493
495
|
// CodeMirror assets (CDN)
|
|
@@ -517,6 +519,14 @@ async function main() {
|
|
|
517
519
|
'https://raw.githubusercontent.com/eyereasoner/eyeling/refs/heads/main/eyeling.js',
|
|
518
520
|
{ ct: 'application/javascript', body: localEyeling },
|
|
519
521
|
],
|
|
522
|
+
[
|
|
523
|
+
'https://raw.githubusercontent.com/eyereasoner/eyeling/refs/heads/main/examples/sudoku.n3',
|
|
524
|
+
{ ct: 'text/plain', body: localSudoku },
|
|
525
|
+
],
|
|
526
|
+
[
|
|
527
|
+
'https://raw.githubusercontent.com/eyereasoner/eyeling/refs/heads/main/examples/builtin/sudoku.js',
|
|
528
|
+
{ ct: 'application/javascript', body: localSudokuBuiltin },
|
|
529
|
+
],
|
|
520
530
|
]);
|
|
521
531
|
|
|
522
532
|
await cdp.send(
|
|
@@ -641,6 +651,21 @@ async function main() {
|
|
|
641
651
|
})()`);
|
|
642
652
|
}
|
|
643
653
|
|
|
654
|
+
async function loadUrlIntoEditor(url) {
|
|
655
|
+
const payload = JSON.stringify(String(url));
|
|
656
|
+
await evalInPage(`(() => {
|
|
657
|
+
const input = document.getElementById('n3-uri');
|
|
658
|
+
const asBackground = document.getElementById('load-as-background');
|
|
659
|
+
const btn = document.getElementById('load-uri-btn');
|
|
660
|
+
if (!input) throw new Error('n3-uri input not found');
|
|
661
|
+
if (!btn) throw new Error('load-uri-btn not found');
|
|
662
|
+
input.value = ${payload};
|
|
663
|
+
if (asBackground) asBackground.checked = false;
|
|
664
|
+
btn.click();
|
|
665
|
+
return true;
|
|
666
|
+
})()`);
|
|
667
|
+
}
|
|
668
|
+
|
|
644
669
|
async function waitForState(label, predicate, timeoutMs = 60000) {
|
|
645
670
|
const deadline = Date.now() + timeoutMs;
|
|
646
671
|
let last = { status: '', output: '', highlighted: [] };
|
|
@@ -731,6 +756,26 @@ ${JSON.stringify(last, null, 2)}`);
|
|
|
731
756
|
);
|
|
732
757
|
ok('playground renders log:outputString cleanly in Output');
|
|
733
758
|
|
|
759
|
+
// 5) URL-loaded repository examples should auto-load matching examples/builtin/<stem>.js.
|
|
760
|
+
await loadUrlIntoEditor('https://raw.githubusercontent.com/eyereasoner/eyeling/refs/heads/main/examples/sudoku.n3');
|
|
761
|
+
await waitForState(
|
|
762
|
+
'sudoku URL loaded with companion builtin',
|
|
763
|
+
(st) => /loaded n3 into the editor and loaded its example builtin/i.test(String(st.status || '')),
|
|
764
|
+
20000,
|
|
765
|
+
);
|
|
766
|
+
await clickRun();
|
|
767
|
+
const sudoku = await waitForState(
|
|
768
|
+
'URL-loaded Sudoku example completion',
|
|
769
|
+
(st) =>
|
|
770
|
+
String(st.status || '')
|
|
771
|
+
.trim()
|
|
772
|
+
.startsWith('Done') && /The puzzle is solved/i.test(String(st.output || '')),
|
|
773
|
+
60000,
|
|
774
|
+
);
|
|
775
|
+
assert.match(sudoku.output, /Completed grid/i, 'Expected Sudoku rendered output');
|
|
776
|
+
assert.match(sudoku.output, /unique valid Sudoku solution/i, 'Expected Sudoku builtin-backed result');
|
|
777
|
+
ok('playground auto-loads a companion example builtin for URL-loaded Sudoku');
|
|
778
|
+
|
|
734
779
|
// Ensure no uncaught runtime exceptions.
|
|
735
780
|
assert.equal(exceptions.length, 0, `Uncaught exceptions in demo.html: ${JSON.stringify(exceptions[0] || {})}`);
|
|
736
781
|
|