@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/format.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import fs from "node:fs"
|
|
2
|
+
|
|
3
|
+
function analyze(code) {
|
|
4
|
+
const info = [{ reindentable: true, depth: 0 }]
|
|
5
|
+
let state = "code"
|
|
6
|
+
let depth = 0
|
|
7
|
+
let teDepth = 0
|
|
8
|
+
|
|
9
|
+
for (let i = 0; i < code.length; i++) {
|
|
10
|
+
const c = code[i]
|
|
11
|
+
const n = code[i + 1]
|
|
12
|
+
|
|
13
|
+
if (c === "\n") {
|
|
14
|
+
if (state === "line") state = "code"
|
|
15
|
+
info.push({ reindentable: state === "code", depth })
|
|
16
|
+
continue
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (state === "code") {
|
|
20
|
+
if (c === "'") state = "single"
|
|
21
|
+
else if (c === '"') state = "double"
|
|
22
|
+
else if (c === "`") state = "template"
|
|
23
|
+
else if (c === "/" && n === "/") { state = "line"; i++ }
|
|
24
|
+
else if (c === "/" && n === "*") { state = "block"; i++ }
|
|
25
|
+
else if (c === "{" || c === "(" || c === "[") depth++
|
|
26
|
+
else if (c === "}" || c === ")" || c === "]") depth = Math.max(0, depth - 1)
|
|
27
|
+
} else if (state === "single") {
|
|
28
|
+
if (c === "\\") i++
|
|
29
|
+
else if (c === "'") state = "code"
|
|
30
|
+
} else if (state === "double") {
|
|
31
|
+
if (c === "\\") i++
|
|
32
|
+
else if (c === '"') state = "code"
|
|
33
|
+
} else if (state === "template") {
|
|
34
|
+
if (c === "\\") i++
|
|
35
|
+
else if (c === "`") state = "code"
|
|
36
|
+
else if (c === "$" && n === "{") { i++; teDepth = 1; state = "templateExpr" }
|
|
37
|
+
} else if (state === "templateExpr") {
|
|
38
|
+
if (c === "{") teDepth++
|
|
39
|
+
else if (c === "}") { teDepth--; if (teDepth === 0) state = "template" }
|
|
40
|
+
else if (c === "'" || c === '"' || c === "`") {
|
|
41
|
+
const quote = c
|
|
42
|
+
i++
|
|
43
|
+
while (i < code.length && code[i] !== quote) {
|
|
44
|
+
if (code[i] === "\\") i++
|
|
45
|
+
i++
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
} else if (state === "block") {
|
|
49
|
+
if (c === "*" && n === "/") { i++; state = "code" }
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return info
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function formatSlim(code) {
|
|
57
|
+
const normalized = code.replace(/\r\n/g, "\n")
|
|
58
|
+
const lines = normalized.split("\n")
|
|
59
|
+
const perLine = analyze(normalized)
|
|
60
|
+
|
|
61
|
+
const out = lines.map((line, index) => {
|
|
62
|
+
const info = perLine[index]
|
|
63
|
+
if (!info || !info.reindentable) return line
|
|
64
|
+
|
|
65
|
+
const trimmed = line.trim()
|
|
66
|
+
if (trimmed === "") return ""
|
|
67
|
+
|
|
68
|
+
let depth = info.depth
|
|
69
|
+
if (/^[}\])]/.test(trimmed)) depth = Math.max(0, depth - 1)
|
|
70
|
+
return " ".repeat(depth) + trimmed
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
return out.join("\n").replace(/\n+$/, "") + "\n"
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function formatFile(file) {
|
|
77
|
+
const original = fs.readFileSync(file, "utf8")
|
|
78
|
+
const formatted = formatSlim(original)
|
|
79
|
+
if (formatted !== original) fs.writeFileSync(file, formatted)
|
|
80
|
+
return formatted !== original
|
|
81
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { formatError } from "../external/classErrors.js"
|
|
2
|
+
|
|
3
|
+
process.on("unhandledRejection", (err, promise) => {
|
|
4
|
+
if (err?.tag) {
|
|
5
|
+
console.error(formatError(
|
|
6
|
+
err.tag,
|
|
7
|
+
err.message,
|
|
8
|
+
err.file ?? null,
|
|
9
|
+
err.line ?? null,
|
|
10
|
+
err.col ?? null,
|
|
11
|
+
err.sourceLine ?? null,
|
|
12
|
+
))
|
|
13
|
+
process.exit(1)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
console.error(formatError(
|
|
17
|
+
"Error",
|
|
18
|
+
err?.message ?? String(err),
|
|
19
|
+
null, null, null, null
|
|
20
|
+
))
|
|
21
|
+
process.exit(1)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
process.on("uncaughtException", (err) => {
|
|
25
|
+
if (err?.tag) {
|
|
26
|
+
console.error(formatError(
|
|
27
|
+
err.tag,
|
|
28
|
+
err.message,
|
|
29
|
+
err.file ?? null,
|
|
30
|
+
err.line ?? null,
|
|
31
|
+
err.col ?? null,
|
|
32
|
+
err.sourceLine ?? null,
|
|
33
|
+
))
|
|
34
|
+
process.exit(1)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
console.error(formatError(
|
|
38
|
+
"Error",
|
|
39
|
+
err?.message ?? String(err),
|
|
40
|
+
null, null, null, null
|
|
41
|
+
))
|
|
42
|
+
process.exit(1)
|
|
43
|
+
})
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import { tokenize } from "../../lexer.js"
|
|
2
|
+
|
|
3
|
+
const OPENERS = new Set(["(", "[", "{", "${"])
|
|
4
|
+
const CLOSERS = new Set([")", "]", "}"])
|
|
5
|
+
|
|
6
|
+
// Find the top-level template return, skipping setup callbacks.
|
|
7
|
+
function templateReturnStart(body) {
|
|
8
|
+
let depth = 0
|
|
9
|
+
for (const t of tokenize(body)) {
|
|
10
|
+
if (t.type === "punct") {
|
|
11
|
+
if (OPENERS.has(t.value)) depth++
|
|
12
|
+
else if (CLOSERS.has(t.value)) depth--
|
|
13
|
+
} else if (depth === 0 && t.type === "name" && t.keyword && t.value === "return") {
|
|
14
|
+
return t.start
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return -1
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function parseComponentsEdits(code) {
|
|
21
|
+
const edits = [];
|
|
22
|
+
let i = 0;
|
|
23
|
+
|
|
24
|
+
while (i < code.length) {
|
|
25
|
+
const match = code.slice(i).match(
|
|
26
|
+
/\b(?:(isolated|element)(?:\s*\(\s*["']([^"']+)["']\s*\))?\s+)?component\b/
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
if (!match) break;
|
|
30
|
+
|
|
31
|
+
const modifier = match[1] ?? null;
|
|
32
|
+
const tag = match[2] ?? null;
|
|
33
|
+
const start = i + match.index;
|
|
34
|
+
|
|
35
|
+
let p = start + match[0].length;
|
|
36
|
+
|
|
37
|
+
while (/\s/.test(code[p])) p++;
|
|
38
|
+
|
|
39
|
+
const nameStart = p;
|
|
40
|
+
while (/[a-zA-Z0-9_$]/.test(code[p])) p++;
|
|
41
|
+
const name = code.slice(nameStart, p);
|
|
42
|
+
|
|
43
|
+
while (/\s/.test(code[p])) p++;
|
|
44
|
+
|
|
45
|
+
if (code[p] !== "(")
|
|
46
|
+
throw new Error(`Expected "(" after component ${name}`);
|
|
47
|
+
|
|
48
|
+
let depth = 1;
|
|
49
|
+
const argsStart = ++p;
|
|
50
|
+
|
|
51
|
+
while (depth) {
|
|
52
|
+
if (code[p] === "(") depth++;
|
|
53
|
+
else if (code[p] === ")") depth--;
|
|
54
|
+
p++;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const args = code.slice(argsStart, p - 1).trim();
|
|
58
|
+
|
|
59
|
+
while (/\s/.test(code[p])) p++;
|
|
60
|
+
|
|
61
|
+
if (code[p] !== "{")
|
|
62
|
+
throw new Error(`Expected "{" after component ${name}`);
|
|
63
|
+
|
|
64
|
+
depth = 1;
|
|
65
|
+
const bodyStart = ++p;
|
|
66
|
+
|
|
67
|
+
while (depth) {
|
|
68
|
+
if (code[p] === "{") depth++;
|
|
69
|
+
else if (code[p] === "}") depth--;
|
|
70
|
+
p++;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const body = code.slice(bodyStart, p - 1);
|
|
74
|
+
|
|
75
|
+
edits.push({ start, end: p, replacement: buildComponent(name, args, body, modifier, tag) });
|
|
76
|
+
|
|
77
|
+
i = p;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return edits;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function parseComponents(code) {
|
|
84
|
+
const edits = parseComponentsEdits(code);
|
|
85
|
+
let out = "";
|
|
86
|
+
let cursor = 0;
|
|
87
|
+
for (const { start, end, replacement } of edits) {
|
|
88
|
+
out += code.slice(cursor, start) + replacement;
|
|
89
|
+
cursor = end;
|
|
90
|
+
}
|
|
91
|
+
return out + code.slice(cursor);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function componentBinding(name, args) {
|
|
95
|
+
const trimmed = args.trim();
|
|
96
|
+
|
|
97
|
+
if (!trimmed) return "";
|
|
98
|
+
if (trimmed.startsWith("{") && trimmed.endsWith("}")) return trimmed;
|
|
99
|
+
if (/^[A-Za-z_$][\w$]*$/.test(trimmed)) return trimmed;
|
|
100
|
+
|
|
101
|
+
throw new Error(
|
|
102
|
+
`Component "${name}" arguments must be a single object name (e.g. "props") or a destructuring pattern (e.g. "{ content }"), got: "${trimmed}"`
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Escape template text without changing nested interpolations.
|
|
107
|
+
function escapeTemplateBackticks(s) {
|
|
108
|
+
const n = s.length;
|
|
109
|
+
let out = "";
|
|
110
|
+
let i = 0;
|
|
111
|
+
let depth = 0;
|
|
112
|
+
while (i < n) {
|
|
113
|
+
const c = s[i];
|
|
114
|
+
if (c === "\\") { out += c + (s[i + 1] ?? ""); i += 2; continue; }
|
|
115
|
+
if (depth === 0) {
|
|
116
|
+
if (c === "`") { out += "\\`"; i++; continue; }
|
|
117
|
+
if (c === "$" && s[i + 1] === "{") { out += "${"; i += 2; depth = 1; continue; }
|
|
118
|
+
out += c; i++; continue;
|
|
119
|
+
}
|
|
120
|
+
if (c === "'" || c === "\"" || c === "`") {
|
|
121
|
+
const q = c;
|
|
122
|
+
out += c; i++;
|
|
123
|
+
while (i < n) {
|
|
124
|
+
if (s[i] === "\\") { out += s[i] + (s[i + 1] ?? ""); i += 2; continue; }
|
|
125
|
+
out += s[i];
|
|
126
|
+
if (s[i] === q) { i++; break; }
|
|
127
|
+
i++;
|
|
128
|
+
}
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
if (c === "{") depth++;
|
|
132
|
+
else if (c === "}") depth--;
|
|
133
|
+
out += c; i++;
|
|
134
|
+
}
|
|
135
|
+
return out;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Explicit tags need a hyphen; implicit tags use the slim- kebab-case prefix.
|
|
139
|
+
function elementTag(name, explicit) {
|
|
140
|
+
if (explicit) {
|
|
141
|
+
if (!explicit.includes("-")) {
|
|
142
|
+
throw new Error(
|
|
143
|
+
`Component "${name}": a custom element tag must contain a hyphen, got "${explicit}"`
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
return explicit;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const kebab = name
|
|
150
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1-$2")
|
|
151
|
+
.replace(/[_\s]+/g, "-")
|
|
152
|
+
.toLowerCase();
|
|
153
|
+
|
|
154
|
+
return `slim-${kebab}`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// The same factory supports plain components and custom-element hosts.
|
|
158
|
+
function buildElementComponent(name, binding, before, html, explicitTag) {
|
|
159
|
+
const tag = elementTag(name, explicitTag);
|
|
160
|
+
const param = binding ? `${binding} = {}` : "__unused__ = {}";
|
|
161
|
+
const quoted = JSON.stringify(tag);
|
|
162
|
+
|
|
163
|
+
return `
|
|
164
|
+
const ${name} = (__props__ = {}) => ${name}.__render__(__props__);
|
|
165
|
+
${name}.__component__ = true
|
|
166
|
+
${name}.tag = ${quoted}
|
|
167
|
+
${name}.__render__ = (${param}, __host__ = null) => {
|
|
168
|
+
const s = {};
|
|
169
|
+
const __mounts__ = [];
|
|
170
|
+
const __connects__ = [];
|
|
171
|
+
const __unmounts__ = [];
|
|
172
|
+
const onMount = (fn) => __mounts__.push(fn);
|
|
173
|
+
const onConnect = (fn) => __connects__.push(fn);
|
|
174
|
+
const onUnmount = (fn) => __unmounts__.push(fn);
|
|
175
|
+
${before};
|
|
176
|
+
const __el__ = htmlToVdom(__html__\`${html}\`).toElement();
|
|
177
|
+
|
|
178
|
+
if (!__host__) {
|
|
179
|
+
for (const fn of __mounts__) fn(__el__);
|
|
180
|
+
__lifecycle__(__el__, __connects__, __unmounts__);
|
|
181
|
+
return __el__;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
__adopt_into__(__host__, __el__);
|
|
185
|
+
for (const fn of __mounts__) fn(__host__);
|
|
186
|
+
return { connects: __connects__, unmounts: __unmounts__ };
|
|
187
|
+
};
|
|
188
|
+
__define_element__(${quoted}, ${name}.__render__)
|
|
189
|
+
`;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function buildComponent(name, args, body, modifier = null, tag = null) {
|
|
193
|
+
const returnStart = templateReturnStart(body);
|
|
194
|
+
|
|
195
|
+
if (returnStart === -1) {
|
|
196
|
+
throw new Error(`Component "${name}" must contain return`);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const before = body.slice(0, returnStart).trim();
|
|
200
|
+
|
|
201
|
+
let template = body.slice(returnStart + "return".length).trim();
|
|
202
|
+
if (template.startsWith("(") && template.endsWith(")")) {
|
|
203
|
+
template = template.slice(1, -1).trim();
|
|
204
|
+
}
|
|
205
|
+
const html = escapeTemplateBackticks(template);
|
|
206
|
+
const binding = componentBinding(name, args);
|
|
207
|
+
const param = binding ? `${binding} = {}` : "";
|
|
208
|
+
|
|
209
|
+
if (modifier === "element") {
|
|
210
|
+
return buildElementComponent(name, binding, before, html, tag);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const isolated = modifier === "isolated";
|
|
214
|
+
|
|
215
|
+
if(!isolated) {
|
|
216
|
+
return `
|
|
217
|
+
const ${name} = (${param}) => {
|
|
218
|
+
const s = {};
|
|
219
|
+
const __mounts__ = [];
|
|
220
|
+
const __connects__ = [];
|
|
221
|
+
const __unmounts__ = [];
|
|
222
|
+
const onMount = (fn) => __mounts__.push(fn);
|
|
223
|
+
const onConnect = (fn) => __connects__.push(fn);
|
|
224
|
+
const onUnmount = (fn) => __unmounts__.push(fn);
|
|
225
|
+
${before};
|
|
226
|
+
const __el__ = htmlToVdom(__html__\`${html}\`).toElement();
|
|
227
|
+
for (const fn of __mounts__) fn(__el__);
|
|
228
|
+
__lifecycle__(__el__, __connects__, __unmounts__);
|
|
229
|
+
return __el__;
|
|
230
|
+
};
|
|
231
|
+
${name}.__component__ = true
|
|
232
|
+
`;
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
const fnBody = `const __mounts__=[];const __connects__=[];const __unmounts__=[];const onMount=(fn)=>__mounts__.push(fn);const onConnect=(fn)=>__connects__.push(fn);const onUnmount=(fn)=>__unmounts__.push(fn);${before}; const __el__ = htmlToVdom(__html__\`${html}\`).toElement(); for (const fn of __mounts__) fn(__el__); __lifecycle__(__el__, __connects__, __unmounts__); return __el__;`;
|
|
236
|
+
|
|
237
|
+
return `
|
|
238
|
+
const ${name} = (__props__ = {}) => {
|
|
239
|
+
return new Function(${JSON.stringify(binding)}, ${JSON.stringify(fnBody)})(__props__);
|
|
240
|
+
};
|
|
241
|
+
${name}.__component__ = true
|
|
242
|
+
`;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export {
|
|
247
|
+
parseComponents,
|
|
248
|
+
parseComponentsEdits,
|
|
249
|
+
buildComponent
|
|
250
|
+
}
|