@slim-lang/core 1.2.0

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.
Files changed (52) hide show
  1. package/README.md +666 -0
  2. package/package.json +55 -0
  3. package/packages/slim/.spm +7 -0
  4. package/packages/slim/converters/main.slim +106 -0
  5. package/packages/slim/helpers/array.slim +25 -0
  6. package/packages/slim/helpers/path.slim +3 -0
  7. package/packages/slim/helpers/request.slim +102 -0
  8. package/packages/slim/helpers/string.slim +27 -0
  9. package/packages/slim/main.slim +42 -0
  10. package/packages/slim/parse/main.slim +25 -0
  11. package/packages/slim/server/main.slim +423 -0
  12. package/packages/slim/time/main.slim +66 -0
  13. package/packages/slim/types/common.slim +6 -0
  14. package/packages/slim/types/formats.slim +23 -0
  15. package/packages/slim/types/hash.slim +6 -0
  16. package/packages/slim/types/mails.slim +3 -0
  17. package/packages/slim/types/numerical.slim +9 -0
  18. package/packages/slim/types/time.slim +3 -0
  19. package/run-dev-slim.js +133 -0
  20. package/run-slim.js +20 -0
  21. package/src/bin/api/github_auth.js +89 -0
  22. package/src/bin/api/github_get.js +139 -0
  23. package/src/bin/api/github_req.js +455 -0
  24. package/src/bin/api/lock.js +37 -0
  25. package/src/bin/api/spm.js +103 -0
  26. package/src/bin/api/storage.js +30 -0
  27. package/src/bin/cli.js +404 -0
  28. package/src/bin/config.default.json +5 -0
  29. package/src/bin/helpers.js +147 -0
  30. package/src/bin/parsers/spm.js +174 -0
  31. package/src/bin/spm.js +519 -0
  32. package/src/checker.js +926 -0
  33. package/src/compile.js +230 -0
  34. package/src/external/classErrors.js +202 -0
  35. package/src/external/client.js +38 -0
  36. package/src/external/core.js +861 -0
  37. package/src/external/defaults.js +25 -0
  38. package/src/external/helpers.js +541 -0
  39. package/src/external/slim-globals.d.ts +65 -0
  40. package/src/external/types.js +38 -0
  41. package/src/format.js +81 -0
  42. package/src/handlers/errorHandler.js +43 -0
  43. package/src/handlers/parser/components.js +250 -0
  44. package/src/handlers/parserHandler.js +793 -0
  45. package/src/jsdoc.js +273 -0
  46. package/src/lexer.js +174 -0
  47. package/src/modulePaths.js +74 -0
  48. package/src/parser.js +818 -0
  49. package/src/repl.js +32 -0
  50. package/src/sourcemap.js +0 -0
  51. package/src/test-runner.js +62 -0
  52. package/src/transform.js +765 -0
package/src/parser.js ADDED
@@ -0,0 +1,818 @@
1
+ import {
2
+ extractExpr,
3
+ extractExprBackward,
4
+ extractExprRaw,
5
+ parseTypedArgs,
6
+ isInsideString,
7
+ parseTypesEdits,
8
+ buildTypedArgsResult,
9
+ readTypeAnnotation
10
+ } from "./handlers/parserHandler.js"
11
+ import { parseComponentsEdits } from "./handlers/parser/components.js"
12
+ import { createMapped, applyEdits } from "./sourcemap.js"
13
+ import { tokenize } from "./lexer.js"
14
+
15
+ export function stripComments(code) {
16
+ return tokenize(code).map(token => token.type === "comment" ? "" : token.value).join("")
17
+ }
18
+
19
+ function commentEdits(code) {
20
+ const edits = []
21
+ for (const token of tokenize(code)) {
22
+ if (token.type === "comment") {
23
+ edits.push({ start: token.start, end: token.end, replacement: "" })
24
+ }
25
+ }
26
+ return edits
27
+ }
28
+
29
+ const EXPRESSION_ENDS = new Set([")", "]", "}", "`", "++", "--"])
30
+
31
+ function endsExpression(token) {
32
+ if (!token) return false
33
+ if (token.type === "punct") return EXPRESSION_ENDS.has(token.value)
34
+ return token.type === "name" || token.type === "number" ||
35
+ token.type === "string" || token.type === "regex" || token.type === "template"
36
+ }
37
+
38
+ function wordOperatorEdits(code) {
39
+ const replacements = { __proto__: null, or: "||", and: "&&" }
40
+ const edits = []
41
+ let prev = null
42
+
43
+ for (const token of tokenize(code)) {
44
+ if (token.type === "ws" || token.type === "newline" || token.type === "comment") continue
45
+
46
+ // Lower word operators only after an operand.
47
+ const replacement = token.type === "name" ? replacements[token.value] : undefined
48
+ if (replacement && endsExpression(prev)) {
49
+ edits.push({ start: token.start, end: token.end, replacement })
50
+ }
51
+
52
+ prev = token
53
+ }
54
+ return edits
55
+ }
56
+
57
+ function operatorEdits(code, keyword, fn) {
58
+ const edits = []
59
+
60
+ for (const token of tokenize(code)) {
61
+ if (token.type !== "name" || token.value !== keyword) continue
62
+
63
+ let afterKeyword = token.end
64
+ while (afterKeyword < code.length && /\s/.test(code[afterKeyword])) afterKeyword++
65
+ if (afterKeyword === token.end) continue
66
+
67
+ const expr = extractExpr(code, afterKeyword)
68
+ if (!expr) continue
69
+
70
+ edits.push({ start: token.start, end: afterKeyword + expr.length, replacement: `${fn}(${expr})` })
71
+ }
72
+
73
+ return edits
74
+ }
75
+
76
+ function applyBinaryOperator(mapped, token, fn) {
77
+ let searchFrom = mapped.text.length
78
+
79
+ while (searchFrom >= 0) {
80
+ const text = mapped.text
81
+ const idx = text.lastIndexOf(token, searchFrom)
82
+ if (idx === -1) break
83
+ searchFrom = idx - 1
84
+
85
+ if (isInsideString(text, idx)) continue
86
+
87
+ const { expr: left, start: leftStart } = extractExprBackward(text, idx)
88
+ const { expr: right, end: rightEnd } = extractExprForwardLocal(text, idx + token.length)
89
+
90
+ if (!left || !right) continue
91
+
92
+ mapped = applyEdits(mapped, [{
93
+ start: leftStart,
94
+ end: rightEnd,
95
+ replacement: `${fn}(${left}, ${right})`
96
+ }])
97
+ }
98
+
99
+ return mapped
100
+ }
101
+
102
+ function extractExprForwardLocal(str, startPos) {
103
+ let depth = 0
104
+ let i = startPos
105
+
106
+ while (i < str.length) {
107
+ const ch = str[i]
108
+
109
+ if (ch === '"' || ch === "'" || ch === "`") {
110
+ const quote = ch
111
+ i++
112
+ while (i < str.length) {
113
+ if (str[i] === "\\") { i += 2; continue }
114
+ if (str[i] === quote) { i++; break }
115
+ i++
116
+ }
117
+ continue
118
+ }
119
+
120
+ if (ch === "(" || ch === "[" || ch === "{") { depth++; i++; continue }
121
+ if (ch === ")" || ch === "]" || ch === "}") {
122
+ if (depth === 0) break
123
+ depth--; i++; continue
124
+ }
125
+
126
+ if (depth === 0) {
127
+ const two = str.slice(i, i + 2)
128
+ if (["==", "!=", ">=", "<=", "&&", "||", "??"].includes(two)) break
129
+ if (["+", "-", "*", "/", "%", "<", ">", "?", ":", ";", ",", "\n"].includes(ch)) break
130
+ }
131
+
132
+ i++
133
+ }
134
+
135
+ return { expr: str.slice(startPos, i).trim(), end: i }
136
+ }
137
+
138
+ function collectEdits(text, register) {
139
+ const replacements = []
140
+ const tokens = tokenize(text)
141
+
142
+ const isCode = offset => {
143
+ let lo = 0
144
+ let hi = tokens.length - 1
145
+ while (lo <= hi) {
146
+ const mid = (lo + hi) >> 1
147
+ const token = tokens[mid]
148
+ if (offset < token.start) hi = mid - 1
149
+ else if (offset >= token.end) lo = mid + 1
150
+ else return token.type !== "string" && token.type !== "template" &&
151
+ token.type !== "comment" && token.type !== "regex"
152
+ }
153
+ return true
154
+ }
155
+
156
+ // Handlers may preserve offsets for verbatim source slices.
157
+ const edit = (start, end, produced) => {
158
+ const body = typeof produced === "string" ? { replacement: produced } : produced
159
+ replacements.push({ start, end, ...body })
160
+ }
161
+
162
+ function collect(pattern, handler) {
163
+ const re = new RegExp(pattern.source,
164
+ pattern.flags.includes("g") ? pattern.flags : pattern.flags + "g"
165
+ )
166
+ let match
167
+ while ((match = re.exec(text)) !== null) {
168
+ if (!isCode(match.index)) continue
169
+ edit(match.index, match.index + match[0].length, handler(...match))
170
+ }
171
+ }
172
+
173
+ function collectCustom(matcher, handler) {
174
+ let i = 0
175
+ while (i < text.length) {
176
+ const result = matcher(text, i)
177
+ if (!result) { i++; continue }
178
+
179
+ if (isCode(result.start)) edit(result.start, result.end, handler(result))
180
+ i = result.end
181
+ }
182
+ }
183
+
184
+ register(collect, collectCustom)
185
+ return replacements
186
+ }
187
+
188
+ const NOT_A_CALL = "(?!if\\b|else\\b|for\\b|while\\b|switch\\b|catch\\b|do\\b|with\\b|return\\b|function\\b|class\\b|try\\b|finally\\b|async\\b)"
189
+ const OPENS_BODY = "\\s*\\{"
190
+
191
+ function functionEdits(text) {
192
+ return collectEdits(text, (collect, collectCustom) => {
193
+ const signature = (head, handler, tail = OPENS_BODY) =>
194
+ collectCustom(headMatcher(head, tail), handler)
195
+
196
+ // Carry return contracts into the AST pass for function-local checks.
197
+ const lower = ({ args, returns }, name, emit) => {
198
+ const parsed = parseTypedArgs(args)
199
+ const { signature: params, checks } = buildTypedArgsResult(parsed, name)
200
+ const declaration = returns ? `__declare_return__("${returns}", "${name}")` : ""
201
+ const prelude = [checks, declaration].filter(Boolean).join("\n ")
202
+
203
+ return prelude ? emit(params, `\n ${prelude}\n`) : emit(params, "")
204
+ }
205
+
206
+ signature(
207
+ "\\bstatic\\s+async\\s+(#?[\\w$]+)\\s*\\(",
208
+ ({ groups: [name], args, returns }) =>
209
+ lower({ args, returns }, name, (params, checks) => `static async ${name}(${params}) {${checks}`)
210
+ )
211
+
212
+ signature(
213
+ "\\bstatic\\s+(?!async\\s)(#?[\\w$]+)\\s*\\(",
214
+ ({ groups: [name], args, returns }) =>
215
+ lower({ args, returns }, name, (params, checks) => `static ${name}(${params}) {${checks}`)
216
+ )
217
+
218
+ signature(
219
+ "^([ \\t]*)async\\s+(#?[\\w$]+)\\s*\\(",
220
+ ({ groups: [indent, name], args, returns }) => {
221
+ const keyword = indent ? "async" : "async function"
222
+ return lower({ args, returns }, name, (params, checks) =>
223
+ `${indent}${keyword} ${name}(${params}) {${checks}`)
224
+ }
225
+ )
226
+
227
+ signature(
228
+ `^([ \\t]*)${NOT_A_CALL}([\\w$]+)\\s*\\(`,
229
+ ({ groups: [indent, name], args, returns }) => {
230
+ const keyword = indent ? "" : "function "
231
+ return lower({ args, returns }, name, (params, checks) =>
232
+ `${indent}${keyword}${name}(${params}) {${checks}`)
233
+ }
234
+ )
235
+
236
+ signature(
237
+ `(\\{[ \\t]*)(async\\s+)?${NOT_A_CALL}([\\w$]+)\\s*\\(`,
238
+ ({ groups: [brace, asyncKw, name], args, returns }) => {
239
+ const prefix = asyncKw ? "async " : ""
240
+ return lower({ args, returns }, name, (params, checks) =>
241
+ `${brace}${prefix}${name}(${params}) {${checks}`)
242
+ }
243
+ )
244
+
245
+ signature(
246
+ "\\b(let|const|var)\\s+([\\w$]+)\\s*=\\s*(async\\s*)?\\(",
247
+ ({ groups: [keyword, name, asyncKw], args, returns }) =>
248
+ lower({ args, returns }, name, (params, checks) =>
249
+ `${keyword} ${name} = ${asyncKw ?? ""}(${params}) => {${checks}`),
250
+ "\\s*=>\\s*\\{"
251
+ )
252
+
253
+ const arrowHead = headMatcher(
254
+ "\\b(let|const|var)\\s+([\\w$]+)\\s*=\\s*(async\\s*)?\\(",
255
+ "\\s*=>\\s*(?!\\{)"
256
+ )
257
+ collectCustom(
258
+ (src, i) => {
259
+ const head = arrowHead(src, i)
260
+ if (!head) return null
261
+ const { expr, end } = extractExprRaw(src, head.end)
262
+ return { ...head, end, expr }
263
+ },
264
+ ({ groups: [keyword, name, asyncKw], args, returns, expr }) =>
265
+ lower({ args, returns }, name, (params, checks) => {
266
+ const head = `${keyword} ${name} = ${asyncKw ?? ""}(${params}) =>`
267
+ if (!checks) return `${head} ${expr}`
268
+ return `${head} {${checks} return ${expr}\n}`
269
+ })
270
+ )
271
+
272
+ signature(
273
+ "\\basync\\s+func\\s+([\\w$]+)\\s*\\(",
274
+ ({ groups: [name], args, returns }) =>
275
+ lower({ args, returns }, name, (params, checks) => `async function ${name}(${params}) {${checks}`)
276
+ )
277
+
278
+ signature(
279
+ "\\bfunc\\s+([\\w$]+)\\s*\\(",
280
+ ({ groups: [name], args, returns }) =>
281
+ lower({ args, returns }, name, (params, checks) => `function ${name}(${params}) {${checks}`)
282
+ )
283
+
284
+ signature(
285
+ "\\b(async\\s+)?function\\s+([\\w$]+)\\s*\\(",
286
+ ({ groups: [asyncKw, name], args, returns }) =>
287
+ lower({ args, returns }, name, (params, checks) =>
288
+ `${asyncKw ?? ""}function ${name}(${params}) {${checks}`)
289
+ )
290
+ })
291
+ }
292
+
293
+ // Cache tokens for whole-file and extracted-body scans.
294
+ const tokenCache = new Map()
295
+
296
+ function tokensFor(src) {
297
+ const hit = tokenCache.get(src)
298
+ if (hit) return hit
299
+
300
+ const tokens = tokenize(src)
301
+ const starts = new Map()
302
+ for (let index = 0; index < tokens.length; index++) {
303
+ starts.set(tokens[index].start, index)
304
+ }
305
+
306
+ if (tokenCache.size >= 8) tokenCache.delete(tokenCache.keys().next().value)
307
+ const entry = { tokens, starts }
308
+ tokenCache.set(src, entry)
309
+ return entry
310
+ }
311
+
312
+ // Match brackets by tokens so literals and comments do not affect depth.
313
+ function readBalanced(src, pos, open = "{", close = "}") {
314
+ const { tokens, starts } = tokensFor(src)
315
+ let index = starts.get(pos)
316
+ if (index === undefined) return -1
317
+
318
+ let depth = 0
319
+ for (; index < tokens.length; index++) {
320
+ const token = tokens[index]
321
+ if (token.type !== "punct") continue
322
+
323
+ if (token.value === open || (open === "{" && token.value === "${")) depth++
324
+ else if (token.value === close) {
325
+ depth--
326
+ if (depth === 0) return token.end
327
+ }
328
+ }
329
+ return -1
330
+ }
331
+
332
+ // Read balanced arguments after matching a construct head.
333
+ // Return type may be introduced by `->` or `:` (e.g. `func f(): number`).
334
+ const returnArrow = /\s*(?:->|:)\s*/y
335
+
336
+ function headMatcher(head, tail) {
337
+ const headRe = new RegExp(head, "ym")
338
+ const tailRe = new RegExp(tail, "ym")
339
+
340
+ return (src, i) => {
341
+ headRe.lastIndex = i
342
+ const start = headRe.exec(src)
343
+ if (!start) return null
344
+
345
+ const parenPos = i + start[0].length - 1
346
+ const parenEnd = readBalanced(src, parenPos, "(", ")")
347
+ if (parenEnd === -1) return null
348
+
349
+ let cursor = parenEnd
350
+ let returns = null
351
+
352
+ returnArrow.lastIndex = cursor
353
+ const arrow = returnArrow.exec(src)
354
+ if (arrow) {
355
+ const annotation = readTypeAnnotation(src, cursor + arrow[0].length)
356
+ if (!annotation) return null
357
+ returns = annotation.type
358
+ cursor = annotation.end
359
+ }
360
+
361
+ tailRe.lastIndex = cursor
362
+ const end = tailRe.exec(src)
363
+ if (!end) return null
364
+
365
+ return {
366
+ start: i,
367
+ end: cursor + end[0].length,
368
+ groups: start.slice(1),
369
+ args: src.slice(parenPos + 1, parenEnd - 1),
370
+ returns
371
+ }
372
+ }
373
+ }
374
+
375
+ function splitTopLevel(str, separator) {
376
+ const parts = []
377
+ let depth = 0
378
+ let current = ""
379
+
380
+ for (let i = 0; i < str.length; i++) {
381
+ const c = str[i]
382
+ if (c === '"' || c === "'" || c === "`") {
383
+ const quote = c
384
+ current += c
385
+ i++
386
+ while (i < str.length) {
387
+ current += str[i]
388
+ if (str[i] === "\\") { i++; if (i < str.length) current += str[i]; i++; continue }
389
+ if (str[i] === quote) break
390
+ i++
391
+ }
392
+ continue
393
+ }
394
+ if (c === "(" || c === "[" || c === "{") depth++
395
+ else if (c === ")" || c === "]" || c === "}") depth--
396
+ else if (c === separator && depth === 0) { parts.push(current); current = ""; continue }
397
+ current += c
398
+ }
399
+ if (current.trim()) parts.push(current)
400
+ return parts
401
+ }
402
+
403
+ function topLevelArrow(str) {
404
+ let depth = 0
405
+ for (let i = 0; i < str.length - 1; i++) {
406
+ const c = str[i]
407
+ if (c === '"' || c === "'" || c === "`") {
408
+ const quote = c
409
+ i++
410
+ while (i < str.length) { if (str[i] === "\\") { i += 2; continue } if (str[i] === quote) break; i++ }
411
+ continue
412
+ }
413
+ if (c === "(" || c === "[" || c === "{") depth++
414
+ else if (c === ")" || c === "]" || c === "}") depth--
415
+ else if (depth === 0 && c === "=" && str[i + 1] === ">") return i
416
+ }
417
+ return -1
418
+ }
419
+
420
+ function matchMatcher(src, i) {
421
+ if (i > 0 && /[\w$.]/.test(src[i - 1])) return null
422
+
423
+ const re = /match\s*\(/y
424
+ re.lastIndex = i
425
+ const m = re.exec(src)
426
+ if (!m || m.index !== i) return null
427
+
428
+ const parenPos = i + m[0].length - 1
429
+ const parenEnd = readBalanced(src, parenPos, "(", ")")
430
+ if (parenEnd === -1) return null
431
+
432
+ let j = parenEnd
433
+ while (j < src.length && /\s/.test(src[j])) j++
434
+ if (src[j] !== "{") return null
435
+
436
+ const braceEnd = readBalanced(src, j)
437
+ if (braceEnd === -1) return null
438
+
439
+ return {
440
+ start: i,
441
+ end: braceEnd,
442
+ scrutinee: src.slice(parenPos + 1, parenEnd - 1).trim(),
443
+ body: src.slice(j + 1, braceEnd - 1)
444
+ }
445
+ }
446
+
447
+ function topLevelWhen(str) {
448
+ let depth = 0
449
+ for (let i = 0; i < str.length; i++) {
450
+ const c = str[i]
451
+ if (c === '"' || c === "'" || c === "`") {
452
+ const quote = c
453
+ i++
454
+ while (i < str.length) { if (str[i] === "\\") { i += 2; continue } if (str[i] === quote) break; i++ }
455
+ continue
456
+ }
457
+ if (c === "(" || c === "[" || c === "{") depth++
458
+ else if (c === ")" || c === "]" || c === "}") depth--
459
+ else if (depth === 0 && str.startsWith("when", i) &&
460
+ !/[\w$]/.test(str[i - 1] || "") && !/[\w$]/.test(str[i + 4] || "")) return i
461
+ }
462
+ return -1
463
+ }
464
+
465
+ // Recursively lower nested match expressions without touching strings.
466
+ function lowerMatches(str) {
467
+ if (!str.includes("match")) return str
468
+
469
+ const { tokens } = tokensFor(str)
470
+ const edits = []
471
+
472
+ for (let index = 0; index < tokens.length; index++) {
473
+ const token = tokens[index]
474
+ if (token.type !== "name" || token.value !== "match") continue
475
+
476
+ const found = matchMatcher(str, token.start)
477
+ if (!found) continue
478
+
479
+ edits.push({ start: found.start, end: found.end, replacement: buildMatch(found) })
480
+ while (index + 1 < tokens.length && tokens[index + 1].start < found.end) index++
481
+ }
482
+
483
+ if (edits.length === 0) return str
484
+
485
+ let out = ""
486
+ let cursor = 0
487
+ for (const edit of edits) {
488
+ out += str.slice(cursor, edit.start) + edit.replacement
489
+ cursor = edit.end
490
+ }
491
+ return out + str.slice(cursor)
492
+ }
493
+
494
+ function buildMatch({ scrutinee, body }) {
495
+ const statements = []
496
+ let fallback = "return undefined"
497
+
498
+ for (const rawCase of splitTopLevel(body, ",")) {
499
+ const arrow = topLevelArrow(rawCase)
500
+ if (arrow === -1) continue
501
+
502
+ const head = rawCase.slice(0, arrow).trim()
503
+ const result = lowerMatches(rawCase.slice(arrow + 2).trim())
504
+ if (!head || !result) continue
505
+
506
+ const when = topLevelWhen(head)
507
+ if (when !== -1) {
508
+ const binding = head.slice(0, when).trim()
509
+ const guard = head.slice(when + 4).trim()
510
+ const bind = binding && binding !== "_" ? `const ${binding} = __match; ` : ""
511
+ statements.push(`{ ${bind}if (${guard}) return ${result} }`)
512
+ } else if (head === "_") {
513
+ fallback = `return ${result}`
514
+ } else {
515
+ statements.push(`if (__match_eq__(__match, ${head})) return ${result}`)
516
+ }
517
+ }
518
+
519
+ return `((__match) => { ${[...statements, fallback].join("; ")} })(${lowerMatches(scrutinee)})`
520
+ }
521
+
522
+ function declMatcher(keyword) {
523
+ return (src, i) => {
524
+ const re = new RegExp(`(export\\s+)?${keyword}\\s+([A-Z][\\w$]*)(?:\\s+extends\\s+([A-Z][\\w$]*))?\\s*\\{`, "y")
525
+ re.lastIndex = i
526
+ const m = re.exec(src)
527
+ if (!m) return null
528
+
529
+ const bracePos = i + m[0].length - 1
530
+ const end = readBalanced(src, bracePos)
531
+ if (end === -1) return null
532
+
533
+ return { start: i, end, exportKw: m[1], name: m[2], extendsName: m[3], body: src.slice(bracePos + 1, end - 1) }
534
+ }
535
+ }
536
+
537
+ const declarationHead = /\b(let|const|var|static)\s+(#?[\w$]+|\{[^{}]*\}|\[[^\[\]]*\])\s*:\s*/y
538
+
539
+ const structMethodHead = /([\w$]+)\s*\(/y
540
+
541
+ function structMethod(body, i) {
542
+ structMethodHead.lastIndex = i
543
+ const head = structMethodHead.exec(body)
544
+ if (!head) return null
545
+
546
+ const parenPos = i + head[0].length - 1
547
+ const parenEnd = readBalanced(body, parenPos, "(", ")")
548
+ if (parenEnd === -1) return null
549
+
550
+ let brace = parenEnd
551
+ while (brace < body.length && /\s/.test(body[brace])) brace++
552
+ if (body[brace] !== "{") return null
553
+
554
+ const end = readBalanced(body, brace)
555
+ if (end === -1) return null
556
+
557
+ return {
558
+ name: head[1],
559
+ params: body.slice(parenPos + 1, parenEnd - 1),
560
+ body: body.slice(brace + 1, end - 1),
561
+ end
562
+ }
563
+ }
564
+
565
+ function parseStructBody(body) {
566
+ const fieldLines = []
567
+ const methods = []
568
+ let i = 0
569
+
570
+ while (i < body.length) {
571
+ while (i < body.length && /[\s,]/.test(body[i])) i++
572
+ if (i >= body.length) break
573
+
574
+ const method = structMethod(body, i)
575
+ if (method) {
576
+ methods.push(`"${method.name}": function(${method.params}) {${method.body}}`)
577
+ i = method.end
578
+ continue
579
+ }
580
+
581
+ const start = i
582
+ while (i < body.length && body[i] !== "\n") i++
583
+ fieldLines.push(body.slice(start, i))
584
+ }
585
+
586
+ return { fieldLines, methods }
587
+ }
588
+
589
+ function structuralEdits(text) {
590
+ return collectEdits(text, (collect, collectCustom) => {
591
+ collectCustom(matchMatcher, buildMatch)
592
+
593
+ collect(
594
+ /use\s+(\*\s+as\s+[\w$]+|\{[^}]+\}|[a-zA-Z_$][\w$]*\s+as\s+[\w$]+|[a-zA-Z_$][\w$]*)\s+from\s+["']([^"']+)["']\s*;?/g,
595
+ (_, name, source) =>
596
+ `__use__(${JSON.stringify(name.trim())}, ${JSON.stringify(source)})\n`
597
+ )
598
+ collect(
599
+ /use\s+(\*\s+as\s+[\w$]+|\{[^}]+\}|[a-zA-Z_$][\w$]*\s+as\s+[\w$]+|[a-zA-Z_$][\w$]*)\s+from\s+(@[\w$\/.-]+)\s*;?/g,
600
+ (_, name, source) =>
601
+ `__use__(${JSON.stringify(name.trim())}, ${JSON.stringify(source)})\n`
602
+ )
603
+ collect(
604
+ /use\s+(@[\w$\/.-]+)\s*;?/g,
605
+ (_, source) => `__use_all__(${JSON.stringify(source)})\n`
606
+ )
607
+ collect(
608
+ /use\s+["']([^"']+)["']\s*;?/g,
609
+ (_, source) => `__use_all__(${JSON.stringify(source)})\n`
610
+ )
611
+
612
+ collectCustom(
613
+ declMatcher("struct"),
614
+ ({ exportKw, name, extendsName, body }) => {
615
+ const { fieldLines, methods } = parseStructBody(body)
616
+ const schema = []
617
+ const defaults = []
618
+
619
+ for (let line of fieldLines) {
620
+ line = line.trim().replace(/,\s*$/, "").trim()
621
+ if (!line) continue
622
+
623
+ const idx = line.indexOf(":")
624
+ if (idx === -1) continue
625
+
626
+ let field = line.slice(0, idx).trim()
627
+ let rest = line.slice(idx + 1).trim()
628
+ if (!field) continue
629
+
630
+ // Optional fields may be written as `*key` or `key?`; normalize
631
+ // both to the `*key` form the struct runtime understands.
632
+ let optional = false
633
+ if (field.startsWith("*")) { optional = true; field = field.slice(1).trim() }
634
+ if (field.endsWith("?")) { optional = true; field = field.slice(0, -1).trim() }
635
+ if (optional) field = "*" + field
636
+
637
+ const eq = rest.indexOf("=")
638
+ if (eq !== -1) {
639
+ const value = rest.slice(eq + 1).trim()
640
+ rest = rest.slice(0, eq).trim()
641
+ if (value) defaults.push(`"${field.replace(/^\*/, "").trim()}": () => (${value})`)
642
+ }
643
+
644
+ if (!rest) continue
645
+ schema.push(`"${field}": "${rest}"`)
646
+ }
647
+
648
+ const defaultsArg = defaults.length ? `{ ${defaults.join(", ")} }` : "{}"
649
+ const args = [`"${name}"`, `{ ${schema.join(", ")} }`, "{}", defaultsArg]
650
+ if (extendsName || methods.length) args.push(extendsName ? `"${extendsName}"` : "null")
651
+ if (methods.length) args.push(`{ ${methods.join(", ")} }`)
652
+
653
+ const decl = `const ${name} = __def_struct__(${args.join(", ")})`
654
+ return exportKw ? `export ${decl}` : decl
655
+ }
656
+ )
657
+ collectCustom(
658
+ declMatcher("enum"),
659
+ ({ exportKw, name, body }) => {
660
+ const fields = body
661
+ .split("\n")
662
+ .map(line => line.replace(/,\s*$/, "").trim())
663
+ .filter(Boolean)
664
+ .map(line => {
665
+ const idx = line.indexOf(":")
666
+ if (idx === -1) {
667
+ const field = line.trim()
668
+ return `"${field}": "${field}"`
669
+ }
670
+ const field = line.slice(0, idx).trim()
671
+ const value = line.slice(idx + 1).trim()
672
+ if (!field || !value) return null
673
+ return `"${field}": ${value}`
674
+ })
675
+ .filter(Boolean)
676
+ .join(", ")
677
+
678
+ const decl = `const ${name} = __def_enum__("${name}", { ${fields} })`
679
+ return exportKw ? `export ${decl}` : decl
680
+ }
681
+ )
682
+
683
+ collectCustom(
684
+ (src, i) => {
685
+ if (isInsideString(src, i)) return null
686
+
687
+ declarationHead.lastIndex = i
688
+ const head = declarationHead.exec(src)
689
+ if (!head) return null
690
+
691
+ // `export default const X: T = ...` is not valid JS as a single
692
+ // statement; lower it to a typed declaration plus `export default X`.
693
+ const leadingDefault = src.slice(0, i).match(/export\s+default\s+$/)
694
+ const start = leadingDefault ? i - leadingDefault[0].length : i
695
+
696
+ const annotation = readTypeAnnotation(src, i + head[0].length)
697
+ if (!annotation) return null
698
+
699
+ let equals = annotation.end
700
+ while (equals < src.length && /\s/.test(src[equals])) equals++
701
+ if (src[equals] !== "=" || src[equals + 1] === "=" || src[equals + 1] === ">") return null
702
+
703
+ const { expr, start: exprStart, end } = extractExprRaw(src, equals + 1)
704
+ return {
705
+ start, end, keyword: head[1], name: head[2], type: annotation.type,
706
+ expr, exprStart, defaultExport: !!leadingDefault
707
+ }
708
+ },
709
+ ({ keyword, name, type, expr, exprStart, defaultExport }) => {
710
+ const pattern = name.startsWith("{") || name.startsWith("[")
711
+ // Keep the declaration exported so the module wrapper leaves it at
712
+ // top level, then re-export the binding as default. Emitting a bare
713
+ // `const` plus `export default X` would trap the `const` in the
714
+ // module's try/catch, leaving the default export undefined.
715
+ const prefix = defaultExport ? "export " : ""
716
+ const head = pattern
717
+ ? `${prefix}${keyword} ${name} = __typed_pattern__(`
718
+ : `${prefix}${keyword} ${name} = __typed_variable__(`
719
+ const tail = pattern ? `, "${type}")` : `, "${type}", "${name}")`
720
+ const suffix = defaultExport ? `\nexport { ${name} as default }` : ""
721
+
722
+ return {
723
+ replacement: `${head}${expr}${tail}${suffix}`,
724
+ spans: [{ at: head.length, from: exprStart, length: expr.length }]
725
+ }
726
+ }
727
+ )
728
+
729
+ collect(
730
+ /(\w[\w$.]*(?:\[.*?\])?)\s*(?:=>\s*([\w$]+))?\s*\n((?:\s*\|(?!\|)[^\n]+\n?)+)/g,
731
+ (match, source, alias, pipes) => {
732
+ const steps = [...pipes.matchAll(/\|\s*([\w$]+)\(([^)]*)\)/g)]
733
+ // Preserve non-pipe bars, such as multiline union types.
734
+ if (steps.length === 0) return match
735
+
736
+ const callbackMethods = new Set([
737
+ "map", "filter", "find", "findIndex",
738
+ "some", "every", "flatMap", "forEach",
739
+ "reduce", "reduceRight"
740
+ ])
741
+ const chain = steps.map(([, method, args]) => {
742
+ if (alias) {
743
+ if (args.includes("=>")) return `.${method}(${args.trim()})`
744
+ if (callbackMethods.has(method)) return `.${method}(${alias} => ${args.trim()})`
745
+ return `.${method}(${args.trim()})`
746
+ }
747
+ return `.${method}(${args.trim()})`
748
+ }).join("")
749
+ return `${source}${chain}`
750
+ }
751
+ )
752
+
753
+ collectCustom(
754
+ (code, i) => {
755
+ if (i > 0 && /[\w$]/.test(code[i - 1])) return null
756
+ const match = code.slice(i).match(/^lock\s+const\s+([\w$]+)\s*=\s*/)
757
+ if (!match) return null
758
+ const name = match[1]
759
+ const afterEq = i + match[0].length
760
+ const expr = extractExpr(code, afterEq)
761
+ return { start: i, end: afterEq + expr.length, name, expr }
762
+ },
763
+ ({ name, expr }) => `const ${name} = __lock_object__(${expr})`
764
+ )
765
+
766
+ collectCustom(
767
+ (code, i) => {
768
+ if (i > 0 && /[\w$]/.test(code[i - 1])) return null
769
+ const match = code.slice(i).match(/^lock\s+(?!const\s)/)
770
+ if (!match) return null
771
+ const afterKeyword = i + match[0].length
772
+ const expr = extractExpr(code, afterKeyword)
773
+ return { start: i, end: afterKeyword + expr.length, expr }
774
+ },
775
+ ({ expr }) => `__lock_object__(${expr})`
776
+ )
777
+
778
+ collect(
779
+ /\}\s*elif\s*\(/g,
780
+ match => match.replace("elif", "else if")
781
+ )
782
+
783
+ collect(
784
+ /mode\s+["']([^"']+)["']/,
785
+ (_, name) => {
786
+ if(name == "strict") {
787
+ return `"use strict"`
788
+ }
789
+ else {
790
+ return ''
791
+ }
792
+ }
793
+ )
794
+ })
795
+ }
796
+
797
+ export function preprocess(code, sourceFile = "index.slim") {
798
+ let mapped = createMapped(code)
799
+
800
+ mapped = applyEdits(mapped, commentEdits(mapped.text))
801
+ mapped = applyEdits(mapped, wordOperatorEdits(mapped.text))
802
+
803
+ mapped = applyEdits(mapped, parseTypesEdits(mapped.text))
804
+
805
+ mapped = applyEdits(mapped, operatorEdits(mapped.text, "sizeof", "__sizeof__"))
806
+ mapped = applyEdits(mapped, operatorEdits(mapped.text, "kindof", "type"))
807
+ mapped = applyEdits(mapped, operatorEdits(mapped.text, "empty", "__is_empty__"))
808
+ mapped = applyEdits(mapped, operatorEdits(mapped.text, "copyof", "__copyof__"))
809
+ mapped = applyBinaryOperator(mapped, "~/", "__intdiv__")
810
+
811
+ mapped = applyEdits(mapped, parseComponentsEdits(mapped.text))
812
+
813
+ mapped = applyEdits(mapped, functionEdits(mapped.text))
814
+
815
+ mapped = applyEdits(mapped, structuralEdits(mapped.text))
816
+
817
+ return { code: mapped.text, mapped, source: sourceFile }
818
+ }