eyeling 1.23.4 → 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 CHANGED
@@ -1967,7 +1967,7 @@ It deliberately does **not** expose `loadBuiltinModule(...)`, because loading bu
1967
1967
 
1968
1968
  For browser apps, prefer running Eyeling in a **Web Worker** and importing `eyeling/browser` there.
1969
1969
 
1970
- ### 14.3 `lib/entry.js`: bundler-friendly exports
1970
+ ### 14.4 `lib/entry.js`: bundler-friendly exports
1971
1971
 
1972
1972
  `lib/entry.js` exports:
1973
1973
 
@@ -1976,14 +1976,14 @@ For browser apps, prefer running Eyeling in a **Web Worker** and importing `eyel
1976
1976
 
1977
1977
  `rdfjs` is a small built-in RDF/JS `DataFactory`, so browser / worker code can construct quads without pulling in another package first.
1978
1978
 
1979
- ### 14.4 JavaScript API
1979
+ ### 14.5 JavaScript API
1980
1980
 
1981
1981
  Eyeling exposes two JavaScript entry styles:
1982
1982
 
1983
1983
  - `reason(...)` from `index.js` when you want the same text output as the CLI
1984
1984
  - `reasonStream(...)` / `reasonRdfJs(...)` from the Node bundle or `eyeling/browser` when you want in-process reasoning and structured RDF/JS results
1985
1985
 
1986
- #### 14.4.1 npm helper: `reason(...)`
1986
+ #### 14.5.1 npm helper: `reason(...)`
1987
1987
 
1988
1988
  The npm `reason(...)` function does something intentionally simple and robust:
1989
1989
 
@@ -2036,7 +2036,7 @@ Notes:
2036
2036
  - By default, the npm helper keeps output machine-friendly (`proofComments: false`).
2037
2037
  - Use this path when you want CLI-equivalent behavior inside JavaScript.
2038
2038
 
2039
- #### 14.4.2 RDF-JS and Eyeling rule-object interoperability
2039
+ #### 14.5.2 RDF-JS and Eyeling rule-object interoperability
2040
2040
 
2041
2041
  The JavaScript APIs accept four input styles:
2042
2042
 
@@ -2114,7 +2114,7 @@ console.log(out);
2114
2114
 
2115
2115
  You can also pass a full AST bundle directly, for example `[prefixes, triples, forwardRules, backwardRules]`.
2116
2116
 
2117
- #### 14.4.3 In-process bundle API: `reasonStream(...)` and `reasonRdfJs(...)`
2117
+ #### 14.5.3 In-process bundle API: `reasonStream(...)` and `reasonRdfJs(...)`
2118
2118
 
2119
2119
  Use the bundle entry if you want structured results while the engine is running instead of final CLI text after the fact.
2120
2120
 
@@ -2153,7 +2153,7 @@ Use these entry points when you need one or more of the following:
2153
2153
  - derived results consumed as RDF/JS quads
2154
2154
  - streaming derived RDF/JS quads during reasoning
2155
2155
 
2156
- ### 14.5 Choosing the right entry point
2156
+ ### 14.6 Choosing the right entry point
2157
2157
 
2158
2158
  A practical rule of thumb:
2159
2159
 
@@ -11158,6 +11158,37 @@ ${triples.map((tr) => ` ${tripleToN3(tr, prefixes)}`).join('\n')}
11158
11158
 
11159
11159
  const __literalPartsCache = new Map(); // lit string -> [lex, dt]
11160
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
+
11161
11192
  function literalParts(lit) {
11162
11193
  // Avoid caching extremely large literals (notably huge numeric intermediates)
11163
11194
  // to prevent unbounded memory growth.
@@ -11175,24 +11206,21 @@ ${triples.map((tr) => ` ${tripleToN3(tr, prefixes)}`).join('\n')}
11175
11206
  let lex = lit;
11176
11207
  let dt = null;
11177
11208
 
11178
- const re = /^(['"]{1,3})([\s\S]*?)\1\^\^(.+)$/;
11179
- const match = lit.match(re);
11180
- if (match) {
11181
- lex = match[1] + match[2] + match[1];
11182
- 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);
11183
11213
  if (dt.startsWith('<') && dt.endsWith('>')) {
11184
11214
  dt = dt.slice(1, -1);
11185
11215
  }
11186
11216
  }
11187
11217
 
11188
11218
  // Strip LANGTAG from the lexical form when present.
11189
- if (lex.length >= 2 && lex[0] === '"') {
11190
- const lastQuote = lex.lastIndexOf('"');
11191
- if (lastQuote > 0 && lastQuote < lex.length - 1 && lex[lastQuote + 1] === '@') {
11192
- const lang = lex.slice(lastQuote + 2);
11193
- if (/^[A-Za-z]+(?:-[A-Za-z0-9]+)*$/.test(lang)) {
11194
- lex = lex.slice(0, lastQuote + 1);
11195
- }
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);
11196
11224
  }
11197
11225
  }
11198
11226
 
package/eyeling.js CHANGED
@@ -11130,6 +11130,37 @@ const MAX_LITERAL_PARTS_CACHE_LEN = 1024;
11130
11130
 
11131
11131
  const __literalPartsCache = new Map(); // lit string -> [lex, dt]
11132
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
+
11133
11164
  function literalParts(lit) {
11134
11165
  // Avoid caching extremely large literals (notably huge numeric intermediates)
11135
11166
  // to prevent unbounded memory growth.
@@ -11147,24 +11178,21 @@ function literalParts(lit) {
11147
11178
  let lex = lit;
11148
11179
  let dt = null;
11149
11180
 
11150
- const re = /^(['"]{1,3})([\s\S]*?)\1\^\^(.+)$/;
11151
- const match = lit.match(re);
11152
- if (match) {
11153
- lex = match[1] + match[2] + match[1];
11154
- 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);
11155
11185
  if (dt.startsWith('<') && dt.endsWith('>')) {
11156
11186
  dt = dt.slice(1, -1);
11157
11187
  }
11158
11188
  }
11159
11189
 
11160
11190
  // Strip LANGTAG from the lexical form when present.
11161
- if (lex.length >= 2 && lex[0] === '"') {
11162
- const lastQuote = lex.lastIndexOf('"');
11163
- if (lastQuote > 0 && lastQuote < lex.length - 1 && lex[lastQuote + 1] === '@') {
11164
- const lang = lex.slice(lastQuote + 2);
11165
- if (/^[A-Za-z]+(?:-[A-Za-z0-9]+)*$/.test(lang)) {
11166
- lex = lex.slice(0, lastQuote + 1);
11167
- }
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);
11168
11196
  }
11169
11197
  }
11170
11198
 
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 re = /^(['"]{1,3})([\s\S]*?)\1\^\^(.+)$/;
64
- const match = lit.match(re);
65
- if (match) {
66
- lex = match[1] + match[2] + match[1];
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
- if (lex.length >= 2 && lex[0] === '"') {
75
- const lastQuote = lex.lastIndexOf('"');
76
- if (lastQuote > 0 && lastQuote < lex.length - 1 && lex[lastQuote + 1] === '@') {
77
- const lang = lex.slice(lastQuote + 2);
78
- if (/^[A-Za-z]+(?:-[A-Za-z0-9]+)*$/.test(lang)) {
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyeling",
3
- "version": "1.23.4",
3
+ "version": "1.23.5",
4
4
  "description": "A minimal Notation3 (N3) reasoner in JavaScript.",
5
5
  "main": "./index.js",
6
6
  "keywords": [
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 },