eyelang 1.6.0 → 1.6.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyelang",
3
- "version": "1.6.0",
3
+ "version": "1.6.1",
4
4
  "type": "module",
5
5
  "description": "A small rule engine for Prolog-style Horn clauses",
6
6
  "keywords": [
@@ -2,7 +2,7 @@
2
2
  // RDF parser lowers body triples with math:, string:, list:, crypto:, time:, and
3
3
  // selected log: predicates to n3_* eyelang builtins. The handlers work on the
4
4
  // explicit RDF term representation from src/rdf.js.
5
- import crypto from 'node:crypto';
5
+ import { hashHex } from '../hash.js';
6
6
  import { compareLexicalOrNumeric } from './arithmetic.js';
7
7
  import { deref, isDecimalInteger, listFromItems, parseFiniteNumber, properListItems, termToString, unify } from '../term.js';
8
8
  import { RDF_DIR_LANG_STRING, RDF_LANG_STRING, XSD_BOOLEAN, XSD_DECIMAL, XSD_DOUBLE, XSD_INTEGER, XSD_STRING, rdfIri, rdfLiteral } from '../rdf.js';
@@ -423,7 +423,7 @@ function cryptoHash(name) {
423
423
  return function* ({ goal, env }) {
424
424
  const s = jsString(goal.args[0], env);
425
425
  if (s == null) return;
426
- const out = crypto.createHash(algo).update(s).digest('hex');
426
+ const out = hashHex(algo, s);
427
427
  yield* bindOrCheck(goal.args[1], rdfLiteral(out, XSD_STRING, '', ''), env);
428
428
  };
429
429
  }
@@ -445,7 +445,7 @@ function timeComponent(name) {
445
445
  }
446
446
 
447
447
  function* localTime({ goal, env }) {
448
- const fixed = process.env.EYELANG_LOCAL_TIME;
448
+ const fixed = typeof process !== 'undefined' ? process.env?.EYELANG_LOCAL_TIME : null;
449
449
  const now = fixed || new Date().toISOString().replace(/\.\d{3}Z$/, 'Z');
450
450
  yield* bindOrCheck(goal.args[1], rdfLiteral(now, XSD_DATE_TIME, '', ''), env);
451
451
  }
package/src/hash.js ADDED
@@ -0,0 +1,294 @@
1
+ // Small dependency-free hashing helpers shared by Node and browser builds.
2
+ // They intentionally avoid node:crypto so src/index.js remains importable by the
3
+ // browser playground when the N3 crypto builtins are registered.
4
+
5
+ const textEncoder = new TextEncoder();
6
+
7
+ export function hashHex(algorithm, text) {
8
+ const name = String(algorithm).toLowerCase().replace(/[^a-z0-9]/g, '');
9
+ const bytes = textEncoder.encode(String(text));
10
+ if (name === 'md5') return md5Hex(bytes);
11
+ if (name === 'sha' || name === 'sha1') return sha1Hex(bytes);
12
+ if (name === 'sha256') return sha256Hex(bytes);
13
+ if (name === 'sha512') return sha512Hex(bytes);
14
+ throw new Error(`Unsupported hash algorithm: ${algorithm}`);
15
+ }
16
+
17
+ function hex32(x) {
18
+ return (x >>> 0).toString(16).padStart(8, '0');
19
+ }
20
+
21
+ function rotr32(x, n) {
22
+ return (x >>> n) | (x << (32 - n));
23
+ }
24
+
25
+ function rotl32(x, n) {
26
+ return (x << n) | (x >>> (32 - n));
27
+ }
28
+
29
+ function bytesToHex(bytes) {
30
+ let out = '';
31
+ for (const b of bytes) out += b.toString(16).padStart(2, '0');
32
+ return out;
33
+ }
34
+
35
+ function paddedBytes(bytes, blockBytes, lengthBytes, littleLength = false) {
36
+ const bitLength = BigInt(bytes.length) * 8n;
37
+ const withOne = bytes.length + 1;
38
+ const zeroCount = (blockBytes - ((withOne + lengthBytes) % blockBytes)) % blockBytes;
39
+ const out = new Uint8Array(withOne + zeroCount + lengthBytes);
40
+ out.set(bytes, 0);
41
+ out[bytes.length] = 0x80;
42
+ for (let i = 0; i < lengthBytes; i++) {
43
+ const shift = BigInt(littleLength ? i * 8 : (lengthBytes - 1 - i) * 8);
44
+ out[out.length - lengthBytes + i] = Number((bitLength >> shift) & 0xffn);
45
+ }
46
+ return out;
47
+ }
48
+
49
+ function sha1Hex(bytes) {
50
+ const msg = paddedBytes(bytes, 64, 8);
51
+ let h0 = 0x67452301;
52
+ let h1 = 0xefcdab89;
53
+ let h2 = 0x98badcfe;
54
+ let h3 = 0x10325476;
55
+ let h4 = 0xc3d2e1f0;
56
+ const w = new Array(80);
57
+
58
+ for (let offset = 0; offset < msg.length; offset += 64) {
59
+ for (let i = 0; i < 16; i++) {
60
+ const j = offset + i * 4;
61
+ w[i] = ((msg[j] << 24) | (msg[j + 1] << 16) | (msg[j + 2] << 8) | msg[j + 3]) >>> 0;
62
+ }
63
+ for (let i = 16; i < 80; i++) w[i] = rotl32(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1) >>> 0;
64
+
65
+ let a = h0, b = h1, c = h2, d = h3, e = h4;
66
+ for (let i = 0; i < 80; i++) {
67
+ let f, k;
68
+ if (i < 20) { f = (b & c) | (~b & d); k = 0x5a827999; }
69
+ else if (i < 40) { f = b ^ c ^ d; k = 0x6ed9eba1; }
70
+ else if (i < 60) { f = (b & c) | (b & d) | (c & d); k = 0x8f1bbcdc; }
71
+ else { f = b ^ c ^ d; k = 0xca62c1d6; }
72
+ const temp = (rotl32(a, 5) + f + e + k + w[i]) >>> 0;
73
+ e = d;
74
+ d = c;
75
+ c = rotl32(b, 30) >>> 0;
76
+ b = a;
77
+ a = temp;
78
+ }
79
+ h0 = (h0 + a) >>> 0;
80
+ h1 = (h1 + b) >>> 0;
81
+ h2 = (h2 + c) >>> 0;
82
+ h3 = (h3 + d) >>> 0;
83
+ h4 = (h4 + e) >>> 0;
84
+ }
85
+
86
+ return [h0, h1, h2, h3, h4].map(hex32).join('');
87
+ }
88
+
89
+ const SHA256_K = [
90
+ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
91
+ 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
92
+ 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
93
+ 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
94
+ 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
95
+ 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
96
+ 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
97
+ 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
98
+ ];
99
+
100
+ function sha256Hex(bytes) {
101
+ const msg = paddedBytes(bytes, 64, 8);
102
+ let h0 = 0x6a09e667;
103
+ let h1 = 0xbb67ae85;
104
+ let h2 = 0x3c6ef372;
105
+ let h3 = 0xa54ff53a;
106
+ let h4 = 0x510e527f;
107
+ let h5 = 0x9b05688c;
108
+ let h6 = 0x1f83d9ab;
109
+ let h7 = 0x5be0cd19;
110
+ const w = new Array(64);
111
+
112
+ for (let offset = 0; offset < msg.length; offset += 64) {
113
+ for (let i = 0; i < 16; i++) {
114
+ const j = offset + i * 4;
115
+ w[i] = ((msg[j] << 24) | (msg[j + 1] << 16) | (msg[j + 2] << 8) | msg[j + 3]) >>> 0;
116
+ }
117
+ for (let i = 16; i < 64; i++) {
118
+ const s0 = (rotr32(w[i - 15], 7) ^ rotr32(w[i - 15], 18) ^ (w[i - 15] >>> 3)) >>> 0;
119
+ const s1 = (rotr32(w[i - 2], 17) ^ rotr32(w[i - 2], 19) ^ (w[i - 2] >>> 10)) >>> 0;
120
+ w[i] = (w[i - 16] + s0 + w[i - 7] + s1) >>> 0;
121
+ }
122
+
123
+ let a = h0, b = h1, c = h2, d = h3, e = h4, f = h5, g = h6, h = h7;
124
+ for (let i = 0; i < 64; i++) {
125
+ const S1 = (rotr32(e, 6) ^ rotr32(e, 11) ^ rotr32(e, 25)) >>> 0;
126
+ const ch = ((e & f) ^ (~e & g)) >>> 0;
127
+ const temp1 = (h + S1 + ch + SHA256_K[i] + w[i]) >>> 0;
128
+ const S0 = (rotr32(a, 2) ^ rotr32(a, 13) ^ rotr32(a, 22)) >>> 0;
129
+ const maj = ((a & b) ^ (a & c) ^ (b & c)) >>> 0;
130
+ const temp2 = (S0 + maj) >>> 0;
131
+ h = g;
132
+ g = f;
133
+ f = e;
134
+ e = (d + temp1) >>> 0;
135
+ d = c;
136
+ c = b;
137
+ b = a;
138
+ a = (temp1 + temp2) >>> 0;
139
+ }
140
+
141
+ h0 = (h0 + a) >>> 0;
142
+ h1 = (h1 + b) >>> 0;
143
+ h2 = (h2 + c) >>> 0;
144
+ h3 = (h3 + d) >>> 0;
145
+ h4 = (h4 + e) >>> 0;
146
+ h5 = (h5 + f) >>> 0;
147
+ h6 = (h6 + g) >>> 0;
148
+ h7 = (h7 + h) >>> 0;
149
+ }
150
+
151
+ return [h0, h1, h2, h3, h4, h5, h6, h7].map(hex32).join('');
152
+ }
153
+
154
+ const MD5_S = [
155
+ 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
156
+ 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
157
+ 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
158
+ 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21,
159
+ ];
160
+ const MD5_K = Array.from({ length: 64 }, (_, i) => Math.floor(Math.abs(Math.sin(i + 1)) * 2 ** 32) >>> 0);
161
+
162
+ function md5Hex(bytes) {
163
+ const msg = paddedBytes(bytes, 64, 8, true);
164
+ let a0 = 0x67452301;
165
+ let b0 = 0xefcdab89;
166
+ let c0 = 0x98badcfe;
167
+ let d0 = 0x10325476;
168
+ const m = new Array(16);
169
+
170
+ for (let offset = 0; offset < msg.length; offset += 64) {
171
+ for (let i = 0; i < 16; i++) {
172
+ const j = offset + i * 4;
173
+ m[i] = (msg[j] | (msg[j + 1] << 8) | (msg[j + 2] << 16) | (msg[j + 3] << 24)) >>> 0;
174
+ }
175
+ let a = a0, b = b0, c = c0, d = d0;
176
+
177
+ for (let i = 0; i < 64; i++) {
178
+ let f, g;
179
+ if (i < 16) { f = (b & c) | (~b & d); g = i; }
180
+ else if (i < 32) { f = (d & b) | (~d & c); g = (5 * i + 1) % 16; }
181
+ else if (i < 48) { f = b ^ c ^ d; g = (3 * i + 5) % 16; }
182
+ else { f = c ^ (b | ~d); g = (7 * i) % 16; }
183
+ f = (f + a + MD5_K[i] + m[g]) >>> 0;
184
+ a = d;
185
+ d = c;
186
+ c = b;
187
+ b = (b + rotl32(f, MD5_S[i])) >>> 0;
188
+ }
189
+
190
+ a0 = (a0 + a) >>> 0;
191
+ b0 = (b0 + b) >>> 0;
192
+ c0 = (c0 + c) >>> 0;
193
+ d0 = (d0 + d) >>> 0;
194
+ }
195
+
196
+ const out = new Uint8Array(16);
197
+ for (const [i, word] of [a0, b0, c0, d0].entries()) {
198
+ out[i * 4] = word & 0xff;
199
+ out[i * 4 + 1] = (word >>> 8) & 0xff;
200
+ out[i * 4 + 2] = (word >>> 16) & 0xff;
201
+ out[i * 4 + 3] = (word >>> 24) & 0xff;
202
+ }
203
+ return bytesToHex(out);
204
+ }
205
+
206
+ const MASK64 = (1n << 64n) - 1n;
207
+ const SHA512_H0 = [
208
+ 0x6a09e667f3bcc908n, 0xbb67ae8584caa73bn, 0x3c6ef372fe94f82bn, 0xa54ff53a5f1d36f1n,
209
+ 0x510e527fade682d1n, 0x9b05688c2b3e6c1fn, 0x1f83d9abfb41bd6bn, 0x5be0cd19137e2179n,
210
+ ];
211
+ const SHA512_K = [
212
+ 0x428a2f98d728ae22n, 0x7137449123ef65cdn, 0xb5c0fbcfec4d3b2fn, 0xe9b5dba58189dbbcn,
213
+ 0x3956c25bf348b538n, 0x59f111f1b605d019n, 0x923f82a4af194f9bn, 0xab1c5ed5da6d8118n,
214
+ 0xd807aa98a3030242n, 0x12835b0145706fben, 0x243185be4ee4b28cn, 0x550c7dc3d5ffb4e2n,
215
+ 0x72be5d74f27b896fn, 0x80deb1fe3b1696b1n, 0x9bdc06a725c71235n, 0xc19bf174cf692694n,
216
+ 0xe49b69c19ef14ad2n, 0xefbe4786384f25e3n, 0x0fc19dc68b8cd5b5n, 0x240ca1cc77ac9c65n,
217
+ 0x2de92c6f592b0275n, 0x4a7484aa6ea6e483n, 0x5cb0a9dcbd41fbd4n, 0x76f988da831153b5n,
218
+ 0x983e5152ee66dfabn, 0xa831c66d2db43210n, 0xb00327c898fb213fn, 0xbf597fc7beef0ee4n,
219
+ 0xc6e00bf33da88fc2n, 0xd5a79147930aa725n, 0x06ca6351e003826fn, 0x142929670a0e6e70n,
220
+ 0x27b70a8546d22ffcn, 0x2e1b21385c26c926n, 0x4d2c6dfc5ac42aedn, 0x53380d139d95b3dfn,
221
+ 0x650a73548baf63den, 0x766a0abb3c77b2a8n, 0x81c2c92e47edaee6n, 0x92722c851482353bn,
222
+ 0xa2bfe8a14cf10364n, 0xa81a664bbc423001n, 0xc24b8b70d0f89791n, 0xc76c51a30654be30n,
223
+ 0xd192e819d6ef5218n, 0xd69906245565a910n, 0xf40e35855771202an, 0x106aa07032bbd1b8n,
224
+ 0x19a4c116b8d2d0c8n, 0x1e376c085141ab53n, 0x2748774cdf8eeb99n, 0x34b0bcb5e19b48a8n,
225
+ 0x391c0cb3c5c95a63n, 0x4ed8aa4ae3418acbn, 0x5b9cca4f7763e373n, 0x682e6ff3d6b2b8a3n,
226
+ 0x748f82ee5defb2fcn, 0x78a5636f43172f60n, 0x84c87814a1f0ab72n, 0x8cc702081a6439ecn,
227
+ 0x90befffa23631e28n, 0xa4506cebde82bde9n, 0xbef9a3f7b2c67915n, 0xc67178f2e372532bn,
228
+ 0xca273eceea26619cn, 0xd186b8c721c0c207n, 0xeada7dd6cde0eb1en, 0xf57d4f7fee6ed178n,
229
+ 0x06f067aa72176fban, 0x0a637dc5a2c898a6n, 0x113f9804bef90daen, 0x1b710b35131c471bn,
230
+ 0x28db77f523047d84n, 0x32caab7b40c72493n, 0x3c9ebe0a15c9bebcn, 0x431d67c49c100d4cn,
231
+ 0x4cc5d4becb3e42b6n, 0x597f299cfc657e2an, 0x5fcb6fab3ad6faecn, 0x6c44198c4a475817n,
232
+ ];
233
+
234
+ function rotr64(x, n) {
235
+ return ((x >> BigInt(n)) | (x << BigInt(64 - n))) & MASK64;
236
+ }
237
+
238
+ function shr64(x, n) {
239
+ return x >> BigInt(n);
240
+ }
241
+
242
+ function add64(...xs) {
243
+ let out = 0n;
244
+ for (const x of xs) out = (out + x) & MASK64;
245
+ return out;
246
+ }
247
+
248
+ function sha512Hex(bytes) {
249
+ const msg = paddedBytes(bytes, 128, 16);
250
+ const h = [...SHA512_H0];
251
+ const w = new Array(80).fill(0n);
252
+
253
+ for (let offset = 0; offset < msg.length; offset += 128) {
254
+ for (let i = 0; i < 16; i++) {
255
+ let word = 0n;
256
+ for (let j = 0; j < 8; j++) word = (word << 8n) | BigInt(msg[offset + i * 8 + j]);
257
+ w[i] = word;
258
+ }
259
+ for (let i = 16; i < 80; i++) {
260
+ const s0 = rotr64(w[i - 15], 1) ^ rotr64(w[i - 15], 8) ^ shr64(w[i - 15], 7);
261
+ const s1 = rotr64(w[i - 2], 19) ^ rotr64(w[i - 2], 61) ^ shr64(w[i - 2], 6);
262
+ w[i] = add64(w[i - 16], s0, w[i - 7], s1);
263
+ }
264
+
265
+ let [a, b, c, d, e, f, g, hh] = h;
266
+ for (let i = 0; i < 80; i++) {
267
+ const S1 = rotr64(e, 14) ^ rotr64(e, 18) ^ rotr64(e, 41);
268
+ const ch = (e & f) ^ (~e & g);
269
+ const temp1 = add64(hh, S1, ch, SHA512_K[i], w[i]);
270
+ const S0 = rotr64(a, 28) ^ rotr64(a, 34) ^ rotr64(a, 39);
271
+ const maj = (a & b) ^ (a & c) ^ (b & c);
272
+ const temp2 = add64(S0, maj);
273
+ hh = g;
274
+ g = f;
275
+ f = e;
276
+ e = add64(d, temp1);
277
+ d = c;
278
+ c = b;
279
+ b = a;
280
+ a = add64(temp1, temp2);
281
+ }
282
+
283
+ h[0] = add64(h[0], a);
284
+ h[1] = add64(h[1], b);
285
+ h[2] = add64(h[2], c);
286
+ h[3] = add64(h[3], d);
287
+ h[4] = add64(h[4], e);
288
+ h[5] = add64(h[5], f);
289
+ h[6] = add64(h[6], g);
290
+ h[7] = add64(h[7], hh);
291
+ }
292
+
293
+ return h.map((x) => x.toString(16).padStart(16, '0')).join('');
294
+ }
@@ -35,6 +35,7 @@ import {
35
35
  import { parseGoalText } from '../src/parser.js';
36
36
  import { selectClauseCandidates } from '../src/program.js';
37
37
  import { TestReporter, isMainModule } from './test-style.js';
38
+ import { hashHex } from '../src/hash.js';
38
39
 
39
40
  const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
40
41
  const bin = path.join(root, 'bin', 'eyelang');
@@ -196,6 +197,21 @@ why(
196
197
  assertEqual(playgroundExamples.join('\n'), examples.join('\n'), 'playground examples');
197
198
  },
198
199
  },
200
+ {
201
+ name: 'playground import graph is browser-safe',
202
+ run: () => {
203
+ const files = playgroundImportGraph();
204
+ assertEqual(files.includes(path.join(root, 'src', 'index.js')), true, 'src/index.js reachable');
205
+ for (const file of files) {
206
+ const rel = path.relative(root, file).replace(/\\/g, '/');
207
+ const text = fs.readFileSync(file, 'utf8');
208
+ for (const spec of moduleSpecifiers(text)) {
209
+ if (spec.startsWith('node:')) throw new Error(`${rel} imports ${spec}`);
210
+ if (!spec.startsWith('.')) throw new Error(`${rel} has browser-unresolved bare import ${spec}`);
211
+ }
212
+ }
213
+ },
214
+ },
199
215
  {
200
216
  name: 'stdin input is accepted',
201
217
  run: () => {
@@ -332,6 +348,16 @@ function apiCases() {
332
348
  },
333
349
  },
334
350
 
351
+ {
352
+ name: 'portable hash helpers match standard vectors',
353
+ run: () => {
354
+ assertEqual(hashHex('md5', 'abc'), '900150983cd24fb0d6963f7d28e17f72', 'md5');
355
+ assertEqual(hashHex('sha', 'abc'), 'a9993e364706816aba3e25717850c26c9cd0d89d', 'sha1');
356
+ assertEqual(hashHex('sha256', 'abc'), 'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad', 'sha256');
357
+ assertEqual(hashHex('sha512', 'abc'), 'ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f', 'sha512');
358
+ },
359
+ },
360
+
335
361
 
336
362
  {
337
363
  name: 'run materialization can enable proof explanations',
@@ -558,6 +584,32 @@ function playgroundExampleNames() {
558
584
  .sort();
559
585
  }
560
586
 
587
+ function playgroundImportGraph() {
588
+ const entry = path.join(root, 'playground-worker.mjs');
589
+ const seen = new Set();
590
+ const stack = [entry];
591
+ while (stack.length > 0) {
592
+ const file = stack.pop();
593
+ if (seen.has(file)) continue;
594
+ seen.add(file);
595
+ const text = fs.readFileSync(file, 'utf8');
596
+ for (const spec of moduleSpecifiers(text)) {
597
+ if (spec.startsWith('node:')) throw new Error(`${path.relative(root, file)} imports ${spec}`);
598
+ if (!spec.startsWith('.')) continue;
599
+ const next = path.resolve(path.dirname(file), spec);
600
+ if (next.startsWith(root) && fs.existsSync(next)) stack.push(next);
601
+ }
602
+ }
603
+ return [...seen].sort();
604
+ }
605
+
606
+ function moduleSpecifiers(text) {
607
+ const specs = [];
608
+ for (const match of text.matchAll(/(?:import|export)\s+(?:[^'"]*?\s+from\s+)?['"]([^'"]+)['"]/g)) specs.push(match[1]);
609
+ for (const match of text.matchAll(/import\(\s*['"]([^'"]+)['"]\s*\)/g)) specs.push(match[1]);
610
+ return specs;
611
+ }
612
+
561
613
  function between(text, startMarker, endMarker) {
562
614
  const start = text.indexOf(startMarker);
563
615
  if (start === -1) throw new Error(`${startMarker} not found`);