@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.
- package/README.md +666 -0
- package/package.json +55 -0
- package/packages/slim/.spm +7 -0
- package/packages/slim/converters/main.slim +106 -0
- package/packages/slim/helpers/array.slim +25 -0
- package/packages/slim/helpers/path.slim +3 -0
- package/packages/slim/helpers/request.slim +102 -0
- package/packages/slim/helpers/string.slim +27 -0
- package/packages/slim/main.slim +42 -0
- package/packages/slim/parse/main.slim +25 -0
- package/packages/slim/server/main.slim +423 -0
- package/packages/slim/time/main.slim +66 -0
- package/packages/slim/types/common.slim +6 -0
- package/packages/slim/types/formats.slim +23 -0
- package/packages/slim/types/hash.slim +6 -0
- package/packages/slim/types/mails.slim +3 -0
- package/packages/slim/types/numerical.slim +9 -0
- package/packages/slim/types/time.slim +3 -0
- package/run-dev-slim.js +133 -0
- package/run-slim.js +20 -0
- package/src/bin/api/github_auth.js +89 -0
- package/src/bin/api/github_get.js +139 -0
- package/src/bin/api/github_req.js +455 -0
- package/src/bin/api/lock.js +37 -0
- package/src/bin/api/spm.js +103 -0
- package/src/bin/api/storage.js +30 -0
- package/src/bin/cli.js +404 -0
- package/src/bin/config.default.json +5 -0
- package/src/bin/helpers.js +147 -0
- package/src/bin/parsers/spm.js +174 -0
- package/src/bin/spm.js +519 -0
- package/src/checker.js +926 -0
- package/src/compile.js +230 -0
- package/src/external/classErrors.js +202 -0
- package/src/external/client.js +38 -0
- package/src/external/core.js +861 -0
- package/src/external/defaults.js +25 -0
- package/src/external/helpers.js +541 -0
- package/src/external/slim-globals.d.ts +65 -0
- package/src/external/types.js +38 -0
- package/src/format.js +81 -0
- package/src/handlers/errorHandler.js +43 -0
- package/src/handlers/parser/components.js +250 -0
- package/src/handlers/parserHandler.js +793 -0
- package/src/jsdoc.js +273 -0
- package/src/lexer.js +174 -0
- package/src/modulePaths.js +74 -0
- package/src/parser.js +818 -0
- package/src/repl.js +32 -0
- package/src/sourcemap.js +0 -0
- package/src/test-runner.js +62 -0
- 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
|
+
}
|