@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/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
+ }