@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/jsdoc.js
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import _traverse from "@babel/traverse"
|
|
2
|
+
import * as t from "@babel/types"
|
|
3
|
+
|
|
4
|
+
const traverse = _traverse.default ?? _traverse
|
|
5
|
+
|
|
6
|
+
const PRIMITIVES = {
|
|
7
|
+
int: "number", float: "number", number: "number",
|
|
8
|
+
string: "string", bool: "boolean", null: "null",
|
|
9
|
+
any: "any", object: "object", array: "any[]",
|
|
10
|
+
function: "Function", element: "HTMLElement"
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function mapType(label) {
|
|
14
|
+
label = String(label).trim()
|
|
15
|
+
if (!label) return "any"
|
|
16
|
+
if (label.length > 2 && label.startsWith("[") && label.endsWith("]")) {
|
|
17
|
+
return `[${label.slice(1, -1).split(",").map(part => mapType(part.trim())).join(", ")}]`
|
|
18
|
+
}
|
|
19
|
+
if (label.endsWith("?")) return mapType(label.slice(0, -1)) + " | null | undefined"
|
|
20
|
+
if (label.endsWith("[]")) return mapType(label.slice(0, -2)) + "[]"
|
|
21
|
+
if (label.includes("|")) return [...new Set(label.split("|").map(mapType))].join(" | ")
|
|
22
|
+
if (label.includes("&")) return [...new Set(label.split("&").map(mapType))].join(" & ")
|
|
23
|
+
if (label.includes("::")) return "number | string"
|
|
24
|
+
return PRIMITIVES[label] ?? label
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function jsdocComment(lines) {
|
|
28
|
+
return "*\n" + lines.map(line => ` * ${line}`).join("\n") + "\n "
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function enumTypedef(name, schema) {
|
|
32
|
+
const types = new Set()
|
|
33
|
+
|
|
34
|
+
for (const property of schema.properties) {
|
|
35
|
+
if (!t.isObjectProperty(property)) continue
|
|
36
|
+
if (t.isStringLiteral(property.value)) types.add("string")
|
|
37
|
+
else types.add("number")
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const type = types.size ? [...types].join(" | ") : "number | string"
|
|
41
|
+
return `@typedef {${type}} ${name}`
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function structTypedef(name, schema) {
|
|
45
|
+
const fields = []
|
|
46
|
+
|
|
47
|
+
for (const property of schema.properties) {
|
|
48
|
+
if (!t.isObjectProperty(property) || !t.isStringLiteral(property.value)) continue
|
|
49
|
+
|
|
50
|
+
const rawKey = t.isStringLiteral(property.key)
|
|
51
|
+
? property.key.value
|
|
52
|
+
: t.isIdentifier(property.key)
|
|
53
|
+
? property.key.name
|
|
54
|
+
: null
|
|
55
|
+
if (rawKey === null) continue
|
|
56
|
+
|
|
57
|
+
const optional = rawKey.startsWith("*")
|
|
58
|
+
const field = optional ? rawKey.slice(1).trim() : rawKey
|
|
59
|
+
fields.push(`${field}${optional ? "?" : ""}: ${mapType(property.value.value)}`)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return `@typedef {{ ${fields.join(", ")} }} ${name}`
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function returnLabel(fn) {
|
|
66
|
+
if (!t.isBlockStatement(fn.body)) return null
|
|
67
|
+
|
|
68
|
+
for (const statement of fn.body.body) {
|
|
69
|
+
if (!t.isExpressionStatement(statement)) continue
|
|
70
|
+
const call = statement.expression
|
|
71
|
+
if (!t.isCallExpression(call) || !t.isIdentifier(call.callee, { name: "__declare_return__" })) continue
|
|
72
|
+
if (t.isStringLiteral(call.arguments[0])) return call.arguments[0].value
|
|
73
|
+
}
|
|
74
|
+
return null
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function paramLines(fn) {
|
|
78
|
+
if (!t.isBlockStatement(fn.body)) return []
|
|
79
|
+
|
|
80
|
+
const lines = []
|
|
81
|
+
for (const statement of fn.body.body) {
|
|
82
|
+
if (!t.isExpressionStatement(statement)) continue
|
|
83
|
+
const call = statement.expression
|
|
84
|
+
if (!t.isCallExpression(call) || !t.isIdentifier(call.callee, { name: "__typed_parameter__" })) continue
|
|
85
|
+
|
|
86
|
+
const [value, typeArg] = call.arguments
|
|
87
|
+
if (!t.isIdentifier(value) || !t.isStringLiteral(typeArg)) continue
|
|
88
|
+
|
|
89
|
+
lines.push(`@param {${mapType(typeArg.value)}} ${value.name}`)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const returns = returnLabel(fn)
|
|
93
|
+
if (returns) {
|
|
94
|
+
const asyncReturn = fn.async ? `Promise<${mapType(returns)}>` : mapType(returns)
|
|
95
|
+
lines.push(`@returns {${asyncReturn}}`)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return lines
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function structInterface(schema) {
|
|
102
|
+
const fields = []
|
|
103
|
+
|
|
104
|
+
for (const property of schema.properties) {
|
|
105
|
+
if (!t.isObjectProperty(property) || !t.isStringLiteral(property.value)) continue
|
|
106
|
+
|
|
107
|
+
const rawKey = t.isStringLiteral(property.key)
|
|
108
|
+
? property.key.value
|
|
109
|
+
: t.isIdentifier(property.key)
|
|
110
|
+
? property.key.name
|
|
111
|
+
: null
|
|
112
|
+
if (rawKey === null) continue
|
|
113
|
+
|
|
114
|
+
const optional = rawKey.startsWith("*")
|
|
115
|
+
const field = optional ? rawKey.slice(1).trim() : rawKey
|
|
116
|
+
fields.push(`${field}${optional ? "?" : ""}: ${mapType(property.value.value)}`)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return `{ ${fields.join("; ")} }`
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function enumValueType(schema) {
|
|
123
|
+
const types = new Set()
|
|
124
|
+
for (const property of schema.properties) {
|
|
125
|
+
if (!t.isObjectProperty(property)) continue
|
|
126
|
+
types.add(t.isStringLiteral(property.value) ? "string" : "number")
|
|
127
|
+
}
|
|
128
|
+
return types.size ? [...types].join(" | ") : "number | string"
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function paramTypes(fn) {
|
|
132
|
+
const types = new Map()
|
|
133
|
+
if (!t.isBlockStatement(fn.body)) return types
|
|
134
|
+
|
|
135
|
+
for (const statement of fn.body.body) {
|
|
136
|
+
if (!t.isExpressionStatement(statement)) continue
|
|
137
|
+
const call = statement.expression
|
|
138
|
+
if (!t.isCallExpression(call) || !t.isIdentifier(call.callee, { name: "__typed_parameter__" })) continue
|
|
139
|
+
|
|
140
|
+
const [value, typeArg] = call.arguments
|
|
141
|
+
if (t.isIdentifier(value) && t.isStringLiteral(typeArg)) types.set(value.name, typeArg.value)
|
|
142
|
+
}
|
|
143
|
+
return types
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function functionSignature(name, fn) {
|
|
147
|
+
const types = paramTypes(fn)
|
|
148
|
+
|
|
149
|
+
const params = fn.params.map(param => {
|
|
150
|
+
if (t.isAssignmentPattern(param) && t.isIdentifier(param.left)) {
|
|
151
|
+
const label = types.get(param.left.name)
|
|
152
|
+
return `${param.left.name}?: ${label ? mapType(label) : "any"}`
|
|
153
|
+
}
|
|
154
|
+
if (t.isRestElement(param) && t.isIdentifier(param.argument)) {
|
|
155
|
+
return `...${param.argument.name}: any[]`
|
|
156
|
+
}
|
|
157
|
+
if (t.isIdentifier(param)) {
|
|
158
|
+
const label = types.get(param.name)
|
|
159
|
+
return `${param.name}: ${label ? mapType(label) : "any"}`
|
|
160
|
+
}
|
|
161
|
+
return "arg: any"
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
const returns = returnLabel(fn)
|
|
165
|
+
const result = returns
|
|
166
|
+
? (fn.async ? `Promise<${mapType(returns)}>` : mapType(returns))
|
|
167
|
+
: "any"
|
|
168
|
+
|
|
169
|
+
return `export declare function ${name}(${params.join(", ")}): ${result}`
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function emitDeclarations(ast) {
|
|
173
|
+
const lines = []
|
|
174
|
+
let usesStruct = false
|
|
175
|
+
|
|
176
|
+
traverse(ast, {
|
|
177
|
+
ExportNamedDeclaration(path_) {
|
|
178
|
+
const decl = path_.node.declaration
|
|
179
|
+
if (!decl) return
|
|
180
|
+
|
|
181
|
+
if (t.isFunctionDeclaration(decl) && decl.id) {
|
|
182
|
+
lines.push(functionSignature(decl.id.name, decl))
|
|
183
|
+
return
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (!t.isVariableDeclaration(decl)) return
|
|
187
|
+
|
|
188
|
+
for (const declarator of decl.declarations) {
|
|
189
|
+
if (!t.isIdentifier(declarator.id)) continue
|
|
190
|
+
const name = declarator.id.name
|
|
191
|
+
const init = declarator.init
|
|
192
|
+
|
|
193
|
+
if (t.isArrowFunctionExpression(init) || t.isFunctionExpression(init)) {
|
|
194
|
+
lines.push(functionSignature(name, init))
|
|
195
|
+
continue
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (!t.isCallExpression(init)) continue
|
|
199
|
+
|
|
200
|
+
if (t.isIdentifier(init.callee, { name: "__def_struct__" }) && t.isObjectExpression(init.arguments[1])) {
|
|
201
|
+
usesStruct = true
|
|
202
|
+
lines.push(`export interface ${name} ${structInterface(init.arguments[1])}`)
|
|
203
|
+
lines.push(`export declare const ${name}: SlimStruct<${name}>`)
|
|
204
|
+
} else if (t.isIdentifier(init.callee, { name: "__def_enum__" }) && t.isObjectExpression(init.arguments[1])) {
|
|
205
|
+
lines.push(`export type ${name} = ${enumValueType(init.arguments[1])}`)
|
|
206
|
+
lines.push(`export declare const ${name}: Record<string, any>`)
|
|
207
|
+
} else if (t.isIdentifier(init.callee, { name: "__type_def__" })) {
|
|
208
|
+
lines.push(`export type ${name} = any`)
|
|
209
|
+
lines.push(`export declare const ${name}: (value: unknown) => boolean`)
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
if (lines.length === 0) return null
|
|
216
|
+
|
|
217
|
+
const header = usesStruct
|
|
218
|
+
? "type SlimStruct<T> = { verify(value: unknown): T; verifySafe(value: unknown): { success: boolean; result: T } }\n\n"
|
|
219
|
+
: ""
|
|
220
|
+
|
|
221
|
+
return header + lines.join("\n") + "\n"
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export function emitJsDoc(ast) {
|
|
225
|
+
const typedefs = []
|
|
226
|
+
|
|
227
|
+
traverse(ast, {
|
|
228
|
+
VariableDeclaration(path_) {
|
|
229
|
+
for (const declarator of path_.node.declarations) {
|
|
230
|
+
const init = declarator.init
|
|
231
|
+
if (!t.isCallExpression(init)) continue
|
|
232
|
+
|
|
233
|
+
if (t.isIdentifier(init.callee, { name: "__def_struct__" }) &&
|
|
234
|
+
t.isStringLiteral(init.arguments[0]) &&
|
|
235
|
+
t.isObjectExpression(init.arguments[1])) {
|
|
236
|
+
typedefs.push(structTypedef(init.arguments[0].value, init.arguments[1]))
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (t.isIdentifier(init.callee, { name: "__def_enum__" }) &&
|
|
240
|
+
t.isStringLiteral(init.arguments[0]) &&
|
|
241
|
+
t.isObjectExpression(init.arguments[1])) {
|
|
242
|
+
typedefs.push(enumTypedef(init.arguments[0].value, init.arguments[1]))
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (t.isIdentifier(init.callee, { name: "__type_def__" }) &&
|
|
246
|
+
t.isStringLiteral(init.arguments[0])) {
|
|
247
|
+
typedefs.push(`@typedef {any} ${init.arguments[0].value}`)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (t.isIdentifier(init.callee, { name: "__typed_variable__" }) &&
|
|
251
|
+
t.isStringLiteral(init.arguments[1])) {
|
|
252
|
+
t.addComment(path_.node, "leading",
|
|
253
|
+
jsdocComment([`@type {${mapType(init.arguments[1].value)}}`]), false)
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
|
|
258
|
+
Function(path_) {
|
|
259
|
+
const lines = paramLines(path_.node)
|
|
260
|
+
if (lines.length === 0) return
|
|
261
|
+
|
|
262
|
+
const target = t.isFunctionDeclaration(path_.node)
|
|
263
|
+
? path_.node
|
|
264
|
+
: path_.parentPath.isVariableDeclarator()
|
|
265
|
+
? path_.parentPath.parentPath.node
|
|
266
|
+
: path_.node
|
|
267
|
+
|
|
268
|
+
t.addComment(target, "leading", jsdocComment(lines), false)
|
|
269
|
+
}
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
return typedefs
|
|
273
|
+
}
|
package/src/lexer.js
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
const KEYWORDS = new Set([
|
|
2
|
+
"struct", "enum", "type", "func", "component", "use", "lock", "mode", "elif",
|
|
3
|
+
"and", "or", "sizeof", "kindof", "empty", "copyof",
|
|
4
|
+
"break", "case", "catch", "class", "const", "continue", "debugger", "default",
|
|
5
|
+
"delete", "do", "else", "export", "extends", "finally", "for", "function",
|
|
6
|
+
"if", "import", "in", "instanceof", "new", "return", "super", "switch", "this",
|
|
7
|
+
"throw", "try", "typeof", "var", "void", "while", "with", "yield", "let",
|
|
8
|
+
"static", "async", "await", "of", "as", "from"
|
|
9
|
+
])
|
|
10
|
+
|
|
11
|
+
const REGEX_KEYWORDS = new Set([
|
|
12
|
+
"return", "typeof", "instanceof", "in", "of", "new", "delete", "void",
|
|
13
|
+
"do", "else", "yield", "await", "case", "throw"
|
|
14
|
+
])
|
|
15
|
+
|
|
16
|
+
const PUNCTUATORS = [
|
|
17
|
+
">>>=", "===", "!==", ">>>", "**=", "<<=", ">>=", "&&=", "||=", "??=", "...",
|
|
18
|
+
"~/", "::", "=>", "==", "!=", "<=", ">=", "&&", "||", "??", "?.", "**", "++",
|
|
19
|
+
"--", "+=", "-=", "*=", "/=", "%=", "&=", "|=", "^=", "<<", ">>",
|
|
20
|
+
"{", "}", "(", ")", "[", "]", ";", ",", "<", ">", "+", "-", "*", "/", "%",
|
|
21
|
+
"&", "|", "^", "!", "~", "?", ":", "=", ".", "@"
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
const NUMBER = /(?:0[xX][0-9a-fA-F_]+|0[oO][0-7_]+|0[bB][01_]+|(?:\d[\d_]*)?\.?\d[\d_]*(?:[eE][+-]?\d+)?)n?/y
|
|
25
|
+
const NAME = /[A-Za-z_$][\w$]*/y
|
|
26
|
+
|
|
27
|
+
function regexAllowed(prev) {
|
|
28
|
+
if (!prev) return true
|
|
29
|
+
if (prev.type === "number" || prev.type === "string" || prev.type === "regex" || prev.type === "template") {
|
|
30
|
+
return false
|
|
31
|
+
}
|
|
32
|
+
if (prev.type === "name") {
|
|
33
|
+
return prev.keyword && REGEX_KEYWORDS.has(prev.value)
|
|
34
|
+
}
|
|
35
|
+
if (prev.type === "punct") {
|
|
36
|
+
return prev.value !== ")" && prev.value !== "]"
|
|
37
|
+
}
|
|
38
|
+
return true
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function scanRegex(code, start) {
|
|
42
|
+
let i = start + 1
|
|
43
|
+
let inClass = false
|
|
44
|
+
|
|
45
|
+
while (i < code.length) {
|
|
46
|
+
const c = code[i]
|
|
47
|
+
if (c === "\n") return start
|
|
48
|
+
if (c === "\\") { i += 2; continue }
|
|
49
|
+
if (c === "[") inClass = true
|
|
50
|
+
else if (c === "]") inClass = false
|
|
51
|
+
else if (c === "/" && !inClass) { i++; break }
|
|
52
|
+
i++
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (i > code.length) return start
|
|
56
|
+
while (i < code.length && /[a-z]/i.test(code[i])) i++
|
|
57
|
+
return i
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function tokenize(code) {
|
|
61
|
+
const tokens = []
|
|
62
|
+
const modes = [{ kind: "code", brace: 0, interp: false }]
|
|
63
|
+
let prev = null
|
|
64
|
+
let i = 0
|
|
65
|
+
|
|
66
|
+
const emit = (type, start, end, keyword = false) => {
|
|
67
|
+
const token = { type, value: code.slice(start, end), start, end }
|
|
68
|
+
if (keyword) token.keyword = true
|
|
69
|
+
tokens.push(token)
|
|
70
|
+
if (type !== "ws" && type !== "newline" && type !== "comment") prev = token
|
|
71
|
+
return token
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
while (i < code.length) {
|
|
75
|
+
const mode = modes[modes.length - 1]
|
|
76
|
+
|
|
77
|
+
if (mode.kind === "template") {
|
|
78
|
+
const start = i
|
|
79
|
+
while (i < code.length) {
|
|
80
|
+
const c = code[i]
|
|
81
|
+
if (c === "\\") { i += 2; continue }
|
|
82
|
+
if (c === "`" || (c === "$" && code[i + 1] === "{")) break
|
|
83
|
+
i++
|
|
84
|
+
}
|
|
85
|
+
if (i > start) emit("template", start, i)
|
|
86
|
+
|
|
87
|
+
if (code[i] === "`") {
|
|
88
|
+
emit("punct", i, i + 1); i++
|
|
89
|
+
modes.pop()
|
|
90
|
+
} else if (code[i] === "$") {
|
|
91
|
+
emit("punct", i, i + 2); i += 2
|
|
92
|
+
modes.push({ kind: "code", brace: 0, interp: true })
|
|
93
|
+
}
|
|
94
|
+
continue
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const c = code[i]
|
|
98
|
+
|
|
99
|
+
if (c === " " || c === "\t" || c === "\r") {
|
|
100
|
+
const start = i
|
|
101
|
+
while (i < code.length && (code[i] === " " || code[i] === "\t" || code[i] === "\r")) i++
|
|
102
|
+
emit("ws", start, i); continue
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (c === "\n") { emit("newline", i, i + 1); i++; continue }
|
|
106
|
+
|
|
107
|
+
if (c === "/" && code[i + 1] === "/") {
|
|
108
|
+
const start = i
|
|
109
|
+
while (i < code.length && code[i] !== "\n") i++
|
|
110
|
+
emit("comment", start, i); continue
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (c === "/" && code[i + 1] === "*") {
|
|
114
|
+
const start = i
|
|
115
|
+
i += 2
|
|
116
|
+
while (i < code.length && !(code[i] === "*" && code[i + 1] === "/")) i++
|
|
117
|
+
i = Math.min(i + 2, code.length)
|
|
118
|
+
emit("comment", start, i); continue
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (c === "'" || c === '"') {
|
|
122
|
+
const start = i
|
|
123
|
+
i++
|
|
124
|
+
while (i < code.length) {
|
|
125
|
+
if (code[i] === "\\") { i += 2; continue }
|
|
126
|
+
if (code[i] === c || code[i] === "\n") { break }
|
|
127
|
+
i++
|
|
128
|
+
}
|
|
129
|
+
if (code[i] === c) i++
|
|
130
|
+
emit("string", start, i); continue
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (c === "`") {
|
|
134
|
+
emit("punct", i, i + 1); i++
|
|
135
|
+
modes.push({ kind: "template" }); continue
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (c === "}" && mode.interp && mode.brace === 0) {
|
|
139
|
+
emit("punct", i, i + 1); i++
|
|
140
|
+
modes.pop(); continue
|
|
141
|
+
}
|
|
142
|
+
if (c === "{" && mode.interp) mode.brace++
|
|
143
|
+
if (c === "}" && mode.interp) mode.brace--
|
|
144
|
+
|
|
145
|
+
if (c === "/" && regexAllowed(prev)) {
|
|
146
|
+
const end = scanRegex(code, i)
|
|
147
|
+
if (end > i) { emit("regex", i, end); i = end; continue }
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
NUMBER.lastIndex = i
|
|
151
|
+
const num = NUMBER.exec(code)
|
|
152
|
+
if (num && num.index === i && num[0].length > 0) {
|
|
153
|
+
emit("number", i, i + num[0].length); i += num[0].length; continue
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
NAME.lastIndex = i
|
|
157
|
+
const name = NAME.exec(code)
|
|
158
|
+
if (name && name.index === i) {
|
|
159
|
+
emit("name", i, i + name[0].length, KEYWORDS.has(name[0])); i += name[0].length; continue
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
let matched = null
|
|
163
|
+
for (const p of PUNCTUATORS) {
|
|
164
|
+
if (code.startsWith(p, i)) { matched = p; break }
|
|
165
|
+
}
|
|
166
|
+
if (matched) {
|
|
167
|
+
emit("punct", i, i + matched.length); i += matched.length; continue
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
emit("other", i, i + 1); i++
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return tokens
|
|
174
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import fs from "node:fs"
|
|
2
|
+
import path from "node:path"
|
|
3
|
+
import { isBuiltin } from "node:module"
|
|
4
|
+
|
|
5
|
+
const slimExtension = ".slim"
|
|
6
|
+
|
|
7
|
+
function isWithin(parent, target) {
|
|
8
|
+
const relative = path.relative(parent, target)
|
|
9
|
+
return relative !== "" && !relative.startsWith("..") && !path.isAbsolute(relative)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function bareName(raw) {
|
|
13
|
+
const segments = raw.split("/")
|
|
14
|
+
return raw.startsWith("@") ? segments.slice(0, 2).join("/") : segments[0]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function isNodeModule(raw) {
|
|
18
|
+
if (raw.startsWith(".") || path.isAbsolute(raw)) return false
|
|
19
|
+
return fs.existsSync(path.resolve("node_modules", bareName(raw)))
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function getDistPath(slimFile) {
|
|
23
|
+
const abs = path.resolve(slimFile)
|
|
24
|
+
const srcRoot = path.resolve("src")
|
|
25
|
+
const projectRoot = path.resolve(".")
|
|
26
|
+
const relative = isWithin(srcRoot, abs)
|
|
27
|
+
? path.relative(srcRoot, abs)
|
|
28
|
+
: path.relative(projectRoot, abs)
|
|
29
|
+
|
|
30
|
+
return path.resolve("dist", relative.replace(/\.slim$/, ".js"))
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function resolveSlimSource(raw, fromFile) {
|
|
34
|
+
// Node builtins (e.g. "node:crypto", "fs", "path") are not Slim sources;
|
|
35
|
+
// leave them for the JS import to resolve untouched.
|
|
36
|
+
if (isBuiltin(raw)) return null
|
|
37
|
+
|
|
38
|
+
if (raw.startsWith("@")) {
|
|
39
|
+
const packagesRoot = path.resolve("packages")
|
|
40
|
+
const packageName = raw.slice("@".length)
|
|
41
|
+
const fileSource = path.resolve(packagesRoot, packageName + slimExtension)
|
|
42
|
+
|
|
43
|
+
if (fs.existsSync(fileSource)) return fileSource
|
|
44
|
+
|
|
45
|
+
const directorySource = path.resolve(packagesRoot, packageName, "main.slim")
|
|
46
|
+
if (fs.existsSync(directorySource)) return directorySource
|
|
47
|
+
|
|
48
|
+
if (isNodeModule(raw)) return null
|
|
49
|
+
|
|
50
|
+
return fileSource
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (raw.endsWith(".js")) return null
|
|
54
|
+
|
|
55
|
+
const localSource = path.resolve(path.dirname(fromFile), raw + slimExtension)
|
|
56
|
+
if (fs.existsSync(localSource)) return localSource
|
|
57
|
+
|
|
58
|
+
if (isNodeModule(raw)) return null
|
|
59
|
+
|
|
60
|
+
return localSource
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function resolveSlimImport(raw, fromFile) {
|
|
64
|
+
if (raw.endsWith(".js")) return raw
|
|
65
|
+
|
|
66
|
+
const slimSource = resolveSlimSource(raw, fromFile)
|
|
67
|
+
if (slimSource === null) return raw
|
|
68
|
+
|
|
69
|
+
const distTarget = getDistPath(slimSource)
|
|
70
|
+
const distFrom = getDistPath(fromFile)
|
|
71
|
+
const relative = path.relative(path.dirname(distFrom), distTarget).replace(/\\/g, "/")
|
|
72
|
+
|
|
73
|
+
return relative.startsWith(".") ? relative : "./" + relative
|
|
74
|
+
}
|