kin-lang 1.0.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/LICENSE +21 -0
- package/README.md +138 -0
- package/dist/app.js +11 -0
- package/dist/kin-cli.js +2 -0
- package/dist/kin.js +7428 -0
- package/dist/kin_std/json.js +14 -0
- package/dist/kin_std/math.js +16 -0
- package/dist/kin_std/time.js +12 -0
- package/dist/lsp/server.js +3425 -0
- package/package.json +64 -0
- package/src/std/console.js +5 -0
- package/src/std/fs.js +26 -0
- package/src/std/http.js +17 -0
- package/src/std/json.js +14 -0
- package/src/std/math.js +16 -0
- package/src/std/time.js +12 -0
- package/src/std/ui.js +173 -0
|
@@ -0,0 +1,3425 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __esm = (fn, res) => function __init() {
|
|
9
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
10
|
+
};
|
|
11
|
+
var __export = (target, all) => {
|
|
12
|
+
for (var name in all)
|
|
13
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
14
|
+
};
|
|
15
|
+
var __copyProps = (to, from, except, desc) => {
|
|
16
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
17
|
+
for (let key of __getOwnPropNames(from))
|
|
18
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
19
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
20
|
+
}
|
|
21
|
+
return to;
|
|
22
|
+
};
|
|
23
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
24
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
25
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
26
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
27
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
28
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
29
|
+
mod
|
|
30
|
+
));
|
|
31
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
32
|
+
|
|
33
|
+
// src/lexer/token.ts
|
|
34
|
+
var TokenType;
|
|
35
|
+
var init_token = __esm({
|
|
36
|
+
"src/lexer/token.ts"() {
|
|
37
|
+
"use strict";
|
|
38
|
+
TokenType = /* @__PURE__ */ ((TokenType2) => {
|
|
39
|
+
TokenType2[TokenType2["NUMBER"] = 0] = "NUMBER";
|
|
40
|
+
TokenType2[TokenType2["STRING"] = 1] = "STRING";
|
|
41
|
+
TokenType2[TokenType2["IDENTIFIER"] = 2] = "IDENTIFIER";
|
|
42
|
+
TokenType2[TokenType2["KEYWORD"] = 3] = "KEYWORD";
|
|
43
|
+
TokenType2[TokenType2["EQUALS"] = 4] = "EQUALS";
|
|
44
|
+
TokenType2[TokenType2["PLUS"] = 5] = "PLUS";
|
|
45
|
+
TokenType2[TokenType2["MINUS"] = 6] = "MINUS";
|
|
46
|
+
TokenType2[TokenType2["STAR"] = 7] = "STAR";
|
|
47
|
+
TokenType2[TokenType2["SLASH"] = 8] = "SLASH";
|
|
48
|
+
TokenType2[TokenType2["GREATER_THAN"] = 9] = "GREATER_THAN";
|
|
49
|
+
TokenType2[TokenType2["LESS_THAN"] = 10] = "LESS_THAN";
|
|
50
|
+
TokenType2[TokenType2["GREATER_EQ"] = 11] = "GREATER_EQ";
|
|
51
|
+
TokenType2[TokenType2["LESS_EQ"] = 12] = "LESS_EQ";
|
|
52
|
+
TokenType2[TokenType2["EQUAL_EQUAL"] = 13] = "EQUAL_EQUAL";
|
|
53
|
+
TokenType2[TokenType2["NOT_EQUAL"] = 14] = "NOT_EQUAL";
|
|
54
|
+
TokenType2[TokenType2["LPAREN"] = 15] = "LPAREN";
|
|
55
|
+
TokenType2[TokenType2["RPAREN"] = 16] = "RPAREN";
|
|
56
|
+
TokenType2[TokenType2["COMMA"] = 17] = "COMMA";
|
|
57
|
+
TokenType2[TokenType2["DOT"] = 18] = "DOT";
|
|
58
|
+
TokenType2[TokenType2["EOF"] = 19] = "EOF";
|
|
59
|
+
TokenType2[TokenType2["LBRACE"] = 20] = "LBRACE";
|
|
60
|
+
TokenType2[TokenType2["RBRACE"] = 21] = "RBRACE";
|
|
61
|
+
TokenType2[TokenType2["COLON"] = 22] = "COLON";
|
|
62
|
+
TokenType2[TokenType2["SEMICOLON"] = 23] = "SEMICOLON";
|
|
63
|
+
TokenType2[TokenType2["AT"] = 24] = "AT";
|
|
64
|
+
TokenType2[TokenType2["LBRACKET"] = 25] = "LBRACKET";
|
|
65
|
+
TokenType2[TokenType2["RBRACKET"] = 26] = "RBRACKET";
|
|
66
|
+
TokenType2[TokenType2["QUESTION_DOT"] = 27] = "QUESTION_DOT";
|
|
67
|
+
TokenType2[TokenType2["TEMPLATE_STRING"] = 28] = "TEMPLATE_STRING";
|
|
68
|
+
TokenType2[TokenType2["BANG"] = 29] = "BANG";
|
|
69
|
+
TokenType2[TokenType2["AMPERSAND"] = 30] = "AMPERSAND";
|
|
70
|
+
TokenType2[TokenType2["PIPE"] = 31] = "PIPE";
|
|
71
|
+
return TokenType2;
|
|
72
|
+
})(TokenType || {});
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// src/plugins/plugin_engine.ts
|
|
77
|
+
var PluginEngine;
|
|
78
|
+
var init_plugin_engine = __esm({
|
|
79
|
+
"src/plugins/plugin_engine.ts"() {
|
|
80
|
+
"use strict";
|
|
81
|
+
PluginEngine = class _PluginEngine {
|
|
82
|
+
constructor() {
|
|
83
|
+
this.plugins = [];
|
|
84
|
+
}
|
|
85
|
+
static getInstance() {
|
|
86
|
+
if (!_PluginEngine.instance) {
|
|
87
|
+
_PluginEngine.instance = new _PluginEngine();
|
|
88
|
+
}
|
|
89
|
+
return _PluginEngine.instance;
|
|
90
|
+
}
|
|
91
|
+
register(plugin) {
|
|
92
|
+
if (this.plugins.some((p) => p.name === plugin.name)) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
this.plugins.push(plugin);
|
|
96
|
+
}
|
|
97
|
+
getPlugins() {
|
|
98
|
+
return this.plugins;
|
|
99
|
+
}
|
|
100
|
+
clear() {
|
|
101
|
+
this.plugins = [];
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Run the extendLexer hook across all plugins.
|
|
105
|
+
*/
|
|
106
|
+
extendLexer(keywords) {
|
|
107
|
+
let currentKeywords = [...keywords];
|
|
108
|
+
for (const plugin of this.plugins) {
|
|
109
|
+
if (plugin.extendLexer) {
|
|
110
|
+
currentKeywords = plugin.extendLexer(currentKeywords);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return currentKeywords;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Run the extendParser hook until a plugin parses the keyword.
|
|
117
|
+
*/
|
|
118
|
+
extendParser(parser, keyword) {
|
|
119
|
+
for (const plugin of this.plugins) {
|
|
120
|
+
if (plugin.extendParser) {
|
|
121
|
+
const node = plugin.extendParser(parser, keyword);
|
|
122
|
+
if (node) {
|
|
123
|
+
return node;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Run the extendGenerator hook until a plugin generates code.
|
|
131
|
+
*/
|
|
132
|
+
extendGenerator(node, target) {
|
|
133
|
+
for (const plugin of this.plugins) {
|
|
134
|
+
if (plugin.extendGenerator) {
|
|
135
|
+
const code = plugin.extendGenerator(node, target);
|
|
136
|
+
if (code !== null) {
|
|
137
|
+
return code;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// src/lexer/lexer.ts
|
|
148
|
+
var lexer_exports = {};
|
|
149
|
+
__export(lexer_exports, {
|
|
150
|
+
Lexer: () => Lexer
|
|
151
|
+
});
|
|
152
|
+
var KEYWORDS, Lexer;
|
|
153
|
+
var init_lexer = __esm({
|
|
154
|
+
"src/lexer/lexer.ts"() {
|
|
155
|
+
"use strict";
|
|
156
|
+
init_token();
|
|
157
|
+
init_plugin_engine();
|
|
158
|
+
KEYWORDS = [
|
|
159
|
+
"number",
|
|
160
|
+
"string",
|
|
161
|
+
"boolean",
|
|
162
|
+
"log",
|
|
163
|
+
"if",
|
|
164
|
+
"elseif",
|
|
165
|
+
"else",
|
|
166
|
+
"end",
|
|
167
|
+
"repeat",
|
|
168
|
+
"times",
|
|
169
|
+
"for",
|
|
170
|
+
"in",
|
|
171
|
+
"while",
|
|
172
|
+
"break",
|
|
173
|
+
"continue",
|
|
174
|
+
"function",
|
|
175
|
+
"return",
|
|
176
|
+
"import",
|
|
177
|
+
"export",
|
|
178
|
+
"from",
|
|
179
|
+
"style",
|
|
180
|
+
"class",
|
|
181
|
+
"extends",
|
|
182
|
+
"backend",
|
|
183
|
+
"route",
|
|
184
|
+
"method",
|
|
185
|
+
"auth",
|
|
186
|
+
"required",
|
|
187
|
+
"serverless",
|
|
188
|
+
"app",
|
|
189
|
+
"screen",
|
|
190
|
+
"on",
|
|
191
|
+
"go",
|
|
192
|
+
"to",
|
|
193
|
+
"click",
|
|
194
|
+
"change",
|
|
195
|
+
"state",
|
|
196
|
+
"navigate",
|
|
197
|
+
"placeholder",
|
|
198
|
+
"value",
|
|
199
|
+
"component",
|
|
200
|
+
"with",
|
|
201
|
+
"const",
|
|
202
|
+
"true",
|
|
203
|
+
"false",
|
|
204
|
+
"null",
|
|
205
|
+
"async",
|
|
206
|
+
"await",
|
|
207
|
+
"try",
|
|
208
|
+
"catch",
|
|
209
|
+
"enum",
|
|
210
|
+
"switch",
|
|
211
|
+
"case",
|
|
212
|
+
"default",
|
|
213
|
+
"typeof",
|
|
214
|
+
"computed",
|
|
215
|
+
"namespace",
|
|
216
|
+
"middleware",
|
|
217
|
+
"object",
|
|
218
|
+
"list",
|
|
219
|
+
// Stage 9.6 — UI Framework keywords
|
|
220
|
+
"textarea",
|
|
221
|
+
"icon",
|
|
222
|
+
"card",
|
|
223
|
+
"divider",
|
|
224
|
+
"avatar",
|
|
225
|
+
"badge",
|
|
226
|
+
"checkbox",
|
|
227
|
+
"toggle",
|
|
228
|
+
"radio",
|
|
229
|
+
"dropdown",
|
|
230
|
+
"select",
|
|
231
|
+
"form",
|
|
232
|
+
"modal",
|
|
233
|
+
"dialog",
|
|
234
|
+
"sheet",
|
|
235
|
+
"tabs",
|
|
236
|
+
"tab",
|
|
237
|
+
"grid",
|
|
238
|
+
"video",
|
|
239
|
+
"audio",
|
|
240
|
+
"webview",
|
|
241
|
+
"scroll",
|
|
242
|
+
"stack",
|
|
243
|
+
"center",
|
|
244
|
+
"theme",
|
|
245
|
+
"animate",
|
|
246
|
+
"bind",
|
|
247
|
+
"show",
|
|
248
|
+
"slot",
|
|
249
|
+
"token",
|
|
250
|
+
"errorBoundary",
|
|
251
|
+
"fallback",
|
|
252
|
+
"submit",
|
|
253
|
+
"mount",
|
|
254
|
+
"unmount",
|
|
255
|
+
"focus",
|
|
256
|
+
"mobile",
|
|
257
|
+
"tablet",
|
|
258
|
+
"desktop",
|
|
259
|
+
"ariaLabel",
|
|
260
|
+
"accessibilityRole",
|
|
261
|
+
"duration",
|
|
262
|
+
"scale",
|
|
263
|
+
"fade",
|
|
264
|
+
"min",
|
|
265
|
+
"max",
|
|
266
|
+
"pattern",
|
|
267
|
+
// Stage 10 — Testing framework keywords
|
|
268
|
+
"test",
|
|
269
|
+
"describe",
|
|
270
|
+
"assert",
|
|
271
|
+
"expect",
|
|
272
|
+
"should",
|
|
273
|
+
"equal",
|
|
274
|
+
"contain",
|
|
275
|
+
"throws",
|
|
276
|
+
"toBeTruthy",
|
|
277
|
+
"toBeFalsy",
|
|
278
|
+
"not",
|
|
279
|
+
"toBe",
|
|
280
|
+
"typeOf"
|
|
281
|
+
];
|
|
282
|
+
Lexer = class {
|
|
283
|
+
constructor() {
|
|
284
|
+
this.pos = 0;
|
|
285
|
+
this.input = "";
|
|
286
|
+
}
|
|
287
|
+
tokenize(input) {
|
|
288
|
+
this.input = input;
|
|
289
|
+
this.pos = 0;
|
|
290
|
+
const tokens = [];
|
|
291
|
+
const lineStarts = [0];
|
|
292
|
+
for (let i = 0; i < input.length; i++) {
|
|
293
|
+
if (input[i] === "\n") {
|
|
294
|
+
lineStarts.push(i + 1);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
const getLineNumber = (offset) => {
|
|
298
|
+
let low = 0;
|
|
299
|
+
let high = lineStarts.length - 1;
|
|
300
|
+
let result = 0;
|
|
301
|
+
while (low <= high) {
|
|
302
|
+
const mid = low + high >> 1;
|
|
303
|
+
if (lineStarts[mid] <= offset) {
|
|
304
|
+
result = mid;
|
|
305
|
+
low = mid + 1;
|
|
306
|
+
} else {
|
|
307
|
+
high = mid - 1;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return result + 1;
|
|
311
|
+
};
|
|
312
|
+
while (this.pos < this.input.length) {
|
|
313
|
+
this.skipWhitespace();
|
|
314
|
+
if (this.pos >= this.input.length) break;
|
|
315
|
+
if (this.input[this.pos] === "#") {
|
|
316
|
+
this.skipLineComment();
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
if (this.input[this.pos] === "/" && this.input[this.pos + 1] === "*") {
|
|
320
|
+
this.skipBlockComment();
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
const start = this.pos;
|
|
324
|
+
const ch = this.input[this.pos];
|
|
325
|
+
if (ch === '"') {
|
|
326
|
+
const str = this.readString();
|
|
327
|
+
if (this.hasInterpolation(str)) {
|
|
328
|
+
tokens.push({ type: 28 /* TEMPLATE_STRING */, value: str, start, length: this.pos - start });
|
|
329
|
+
} else {
|
|
330
|
+
tokens.push({ type: 1 /* STRING */, value: str, start, length: this.pos - start });
|
|
331
|
+
}
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
const two = this.input.slice(this.pos, this.pos + 2);
|
|
335
|
+
if (two === "?.") {
|
|
336
|
+
tokens.push({ type: 27 /* QUESTION_DOT */, value: "?.", start, length: 2 });
|
|
337
|
+
this.pos += 2;
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
if (two === ">=") {
|
|
341
|
+
tokens.push({ type: 11 /* GREATER_EQ */, value: ">=", start, length: 2 });
|
|
342
|
+
this.pos += 2;
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
345
|
+
if (two === "<=") {
|
|
346
|
+
tokens.push({ type: 12 /* LESS_EQ */, value: "<=", start, length: 2 });
|
|
347
|
+
this.pos += 2;
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
if (two === "==") {
|
|
351
|
+
tokens.push({ type: 13 /* EQUAL_EQUAL */, value: "==", start, length: 2 });
|
|
352
|
+
this.pos += 2;
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
if (two === "!=") {
|
|
356
|
+
tokens.push({ type: 14 /* NOT_EQUAL */, value: "!=", start, length: 2 });
|
|
357
|
+
this.pos += 2;
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
if (ch === "=") {
|
|
361
|
+
tokens.push({ type: 4 /* EQUALS */, value: "=", start, length: 1 });
|
|
362
|
+
this.pos++;
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
365
|
+
if (ch === "+") {
|
|
366
|
+
tokens.push({ type: 5 /* PLUS */, value: "+", start, length: 1 });
|
|
367
|
+
this.pos++;
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
if (ch === "-") {
|
|
371
|
+
tokens.push({ type: 6 /* MINUS */, value: "-", start, length: 1 });
|
|
372
|
+
this.pos++;
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
if (ch === "*") {
|
|
376
|
+
tokens.push({ type: 7 /* STAR */, value: "*", start, length: 1 });
|
|
377
|
+
this.pos++;
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
if (ch === "/") {
|
|
381
|
+
tokens.push({ type: 8 /* SLASH */, value: "/", start, length: 1 });
|
|
382
|
+
this.pos++;
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
if (ch === ">") {
|
|
386
|
+
tokens.push({ type: 9 /* GREATER_THAN */, value: ">", start, length: 1 });
|
|
387
|
+
this.pos++;
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
if (ch === "<") {
|
|
391
|
+
tokens.push({ type: 10 /* LESS_THAN */, value: "<", start, length: 1 });
|
|
392
|
+
this.pos++;
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
if (ch === "(") {
|
|
396
|
+
tokens.push({ type: 15 /* LPAREN */, value: "(", start, length: 1 });
|
|
397
|
+
this.pos++;
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
400
|
+
if (ch === ")") {
|
|
401
|
+
tokens.push({ type: 16 /* RPAREN */, value: ")", start, length: 1 });
|
|
402
|
+
this.pos++;
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
if (ch === ",") {
|
|
406
|
+
tokens.push({ type: 17 /* COMMA */, value: ",", start, length: 1 });
|
|
407
|
+
this.pos++;
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
if (ch === ".") {
|
|
411
|
+
tokens.push({ type: 18 /* DOT */, value: ".", start, length: 1 });
|
|
412
|
+
this.pos++;
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
if (ch === "{") {
|
|
416
|
+
tokens.push({ type: 20 /* LBRACE */, value: "{", start, length: 1 });
|
|
417
|
+
this.pos++;
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
if (ch === "}") {
|
|
421
|
+
tokens.push({ type: 21 /* RBRACE */, value: "}", start, length: 1 });
|
|
422
|
+
this.pos++;
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
if (ch === ":") {
|
|
426
|
+
tokens.push({ type: 22 /* COLON */, value: ":", start, length: 1 });
|
|
427
|
+
this.pos++;
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
if (ch === ";") {
|
|
431
|
+
tokens.push({ type: 23 /* SEMICOLON */, value: ";", start, length: 1 });
|
|
432
|
+
this.pos++;
|
|
433
|
+
continue;
|
|
434
|
+
}
|
|
435
|
+
if (ch === "@") {
|
|
436
|
+
tokens.push({ type: 24 /* AT */, value: "@", start, length: 1 });
|
|
437
|
+
this.pos++;
|
|
438
|
+
continue;
|
|
439
|
+
}
|
|
440
|
+
if (ch === "[") {
|
|
441
|
+
tokens.push({ type: 25 /* LBRACKET */, value: "[", start, length: 1 });
|
|
442
|
+
this.pos++;
|
|
443
|
+
continue;
|
|
444
|
+
}
|
|
445
|
+
if (ch === "]") {
|
|
446
|
+
tokens.push({ type: 26 /* RBRACKET */, value: "]", start, length: 1 });
|
|
447
|
+
this.pos++;
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
450
|
+
if (ch === "!") {
|
|
451
|
+
if (this.input[this.pos + 1] === "=") {
|
|
452
|
+
this.pos++;
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
tokens.push({ type: 29 /* BANG */, value: "!", start, length: 1 });
|
|
456
|
+
this.pos++;
|
|
457
|
+
continue;
|
|
458
|
+
}
|
|
459
|
+
if (ch === "&") {
|
|
460
|
+
tokens.push({ type: 30 /* AMPERSAND */, value: "&", start, length: 1 });
|
|
461
|
+
this.pos++;
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
464
|
+
if (ch === "|") {
|
|
465
|
+
tokens.push({ type: 31 /* PIPE */, value: "|", start, length: 1 });
|
|
466
|
+
this.pos++;
|
|
467
|
+
continue;
|
|
468
|
+
}
|
|
469
|
+
if (/\d/.test(ch)) {
|
|
470
|
+
const num = this.readWhile(/\d/);
|
|
471
|
+
tokens.push({ type: 0 /* NUMBER */, value: num, start, length: this.pos - start });
|
|
472
|
+
continue;
|
|
473
|
+
}
|
|
474
|
+
if (/[a-zA-Z_]/.test(ch)) {
|
|
475
|
+
const word = this.readWhile(/[a-zA-Z0-9_]/);
|
|
476
|
+
const keywords = PluginEngine.getInstance().extendLexer(KEYWORDS);
|
|
477
|
+
const type = keywords.includes(word) ? 3 /* KEYWORD */ : 2 /* IDENTIFIER */;
|
|
478
|
+
tokens.push({ type, value: word, start, length: this.pos - start });
|
|
479
|
+
continue;
|
|
480
|
+
}
|
|
481
|
+
this.pos++;
|
|
482
|
+
}
|
|
483
|
+
tokens.push({ type: 19 /* EOF */, value: "", start: this.pos, length: 0 });
|
|
484
|
+
for (const token of tokens) {
|
|
485
|
+
if (token.start !== void 0) {
|
|
486
|
+
token.line = getLineNumber(token.start);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
return tokens;
|
|
490
|
+
}
|
|
491
|
+
skipWhitespace() {
|
|
492
|
+
while (this.pos < this.input.length && /\s/.test(this.input[this.pos])) {
|
|
493
|
+
this.pos++;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
readWhile(pattern) {
|
|
497
|
+
let result = "";
|
|
498
|
+
while (this.pos < this.input.length && pattern.test(this.input[this.pos])) {
|
|
499
|
+
result += this.input[this.pos++];
|
|
500
|
+
}
|
|
501
|
+
return result;
|
|
502
|
+
}
|
|
503
|
+
readString() {
|
|
504
|
+
this.pos++;
|
|
505
|
+
let result = "";
|
|
506
|
+
while (this.pos < this.input.length && this.input[this.pos] !== '"') {
|
|
507
|
+
result += this.input[this.pos++];
|
|
508
|
+
}
|
|
509
|
+
this.pos++;
|
|
510
|
+
return result;
|
|
511
|
+
}
|
|
512
|
+
/** Skip from # to end of line */
|
|
513
|
+
skipLineComment() {
|
|
514
|
+
while (this.pos < this.input.length && this.input[this.pos] !== "\n") {
|
|
515
|
+
this.pos++;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
/** Skip from /* to * / */
|
|
519
|
+
skipBlockComment() {
|
|
520
|
+
this.pos += 2;
|
|
521
|
+
while (this.pos < this.input.length) {
|
|
522
|
+
if (this.input[this.pos] === "*" && this.input[this.pos + 1] === "/") {
|
|
523
|
+
this.pos += 2;
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
this.pos++;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
/** Check if a string contains {expression} interpolation patterns */
|
|
530
|
+
hasInterpolation(str) {
|
|
531
|
+
for (let i = 0; i < str.length - 1; i++) {
|
|
532
|
+
if (str[i] === "{" && str[i + 1] !== "}") {
|
|
533
|
+
let depth = 1;
|
|
534
|
+
let j = i + 1;
|
|
535
|
+
while (j < str.length && depth > 0) {
|
|
536
|
+
if (str[j] === "{") depth++;
|
|
537
|
+
if (str[j] === "}") depth--;
|
|
538
|
+
j++;
|
|
539
|
+
}
|
|
540
|
+
if (depth === 0) return true;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
return false;
|
|
544
|
+
}
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
// src/lsp/server.ts
|
|
550
|
+
var server_exports = {};
|
|
551
|
+
__export(server_exports, {
|
|
552
|
+
startLspServer: () => startLspServer
|
|
553
|
+
});
|
|
554
|
+
module.exports = __toCommonJS(server_exports);
|
|
555
|
+
|
|
556
|
+
// src/ide/language_service.ts
|
|
557
|
+
init_lexer();
|
|
558
|
+
|
|
559
|
+
// src/parser/parser.ts
|
|
560
|
+
init_token();
|
|
561
|
+
init_plugin_engine();
|
|
562
|
+
var UI_TAGS = /* @__PURE__ */ new Set([
|
|
563
|
+
"text",
|
|
564
|
+
"button",
|
|
565
|
+
"input",
|
|
566
|
+
"view",
|
|
567
|
+
"image",
|
|
568
|
+
"list",
|
|
569
|
+
"column",
|
|
570
|
+
"row",
|
|
571
|
+
"container",
|
|
572
|
+
// Stage 9.6 — new UI elements
|
|
573
|
+
"textarea",
|
|
574
|
+
"icon",
|
|
575
|
+
"card",
|
|
576
|
+
"divider",
|
|
577
|
+
"avatar",
|
|
578
|
+
"badge",
|
|
579
|
+
"checkbox",
|
|
580
|
+
"toggle",
|
|
581
|
+
"radio",
|
|
582
|
+
"dropdown",
|
|
583
|
+
"select",
|
|
584
|
+
"modal",
|
|
585
|
+
"dialog",
|
|
586
|
+
"sheet",
|
|
587
|
+
"tabs",
|
|
588
|
+
"tab",
|
|
589
|
+
"grid",
|
|
590
|
+
"video",
|
|
591
|
+
"audio",
|
|
592
|
+
"webview",
|
|
593
|
+
"scroll",
|
|
594
|
+
"stack",
|
|
595
|
+
"center"
|
|
596
|
+
]);
|
|
597
|
+
var PRECEDENCE = {
|
|
598
|
+
"==": 1,
|
|
599
|
+
"!=": 1,
|
|
600
|
+
"<": 2,
|
|
601
|
+
">": 2,
|
|
602
|
+
"<=": 2,
|
|
603
|
+
">=": 2,
|
|
604
|
+
"+": 3,
|
|
605
|
+
"-": 3,
|
|
606
|
+
"*": 4,
|
|
607
|
+
"/": 4
|
|
608
|
+
};
|
|
609
|
+
var OPERATOR_TYPES = /* @__PURE__ */ new Set([
|
|
610
|
+
5 /* PLUS */,
|
|
611
|
+
6 /* MINUS */,
|
|
612
|
+
7 /* STAR */,
|
|
613
|
+
8 /* SLASH */,
|
|
614
|
+
9 /* GREATER_THAN */,
|
|
615
|
+
10 /* LESS_THAN */,
|
|
616
|
+
11 /* GREATER_EQ */,
|
|
617
|
+
12 /* LESS_EQ */,
|
|
618
|
+
13 /* EQUAL_EQUAL */,
|
|
619
|
+
14 /* NOT_EQUAL */
|
|
620
|
+
]);
|
|
621
|
+
var Parser = class _Parser {
|
|
622
|
+
constructor(tokens) {
|
|
623
|
+
this.tokens = tokens;
|
|
624
|
+
this.current = 0;
|
|
625
|
+
this.errors = [];
|
|
626
|
+
}
|
|
627
|
+
// ─────────────────────────────────────────
|
|
628
|
+
// Top-level parse
|
|
629
|
+
// ─────────────────────────────────────────
|
|
630
|
+
parse() {
|
|
631
|
+
return this.parseStatements(() => this.isEOF());
|
|
632
|
+
}
|
|
633
|
+
// ─────────────────────────────────────────
|
|
634
|
+
// Generic statement list parser
|
|
635
|
+
// ─────────────────────────────────────────
|
|
636
|
+
parseStatements(until) {
|
|
637
|
+
const nodes = [];
|
|
638
|
+
while (!until()) {
|
|
639
|
+
const tok = this.tokens[this.current];
|
|
640
|
+
if (!tok) {
|
|
641
|
+
this.current++;
|
|
642
|
+
continue;
|
|
643
|
+
}
|
|
644
|
+
const startLine = tok.line;
|
|
645
|
+
let node = null;
|
|
646
|
+
if (tok.type === 3 /* KEYWORD */) {
|
|
647
|
+
if (tok.value === "number" || tok.value === "string" || tok.value === "boolean" || tok.value === "list" || tok.value === "object") {
|
|
648
|
+
node = this.parseVariable();
|
|
649
|
+
} else if (tok.value === "const") {
|
|
650
|
+
node = this.parseConstDeclaration();
|
|
651
|
+
} else if (tok.value === "log") {
|
|
652
|
+
node = this.parseLog();
|
|
653
|
+
} else if (tok.value === "if") {
|
|
654
|
+
node = this.parseIf();
|
|
655
|
+
} else if (tok.value === "repeat") {
|
|
656
|
+
node = this.parseRepeatOrForIn();
|
|
657
|
+
} else if (tok.value === "for") {
|
|
658
|
+
node = this.parseForInStatement();
|
|
659
|
+
} else if (tok.value === "while") {
|
|
660
|
+
node = this.parseWhileStatement();
|
|
661
|
+
} else if (tok.value === "break") {
|
|
662
|
+
node = this.parseBreakStatement();
|
|
663
|
+
} else if (tok.value === "continue") {
|
|
664
|
+
node = this.parseContinueStatement();
|
|
665
|
+
} else if (tok.value === "function") {
|
|
666
|
+
node = this.parseFunctionDeclaration(false);
|
|
667
|
+
} else if (tok.value === "async") {
|
|
668
|
+
node = this.parseAsyncFunctionOrAwait();
|
|
669
|
+
} else if (tok.value === "return") {
|
|
670
|
+
node = this.parseReturn();
|
|
671
|
+
} else if (tok.value === "import") {
|
|
672
|
+
node = this.parseImport();
|
|
673
|
+
} else if (tok.value === "export") {
|
|
674
|
+
node = this.parseExport();
|
|
675
|
+
} else if (tok.value === "style") {
|
|
676
|
+
node = this.parseStyleBlock();
|
|
677
|
+
} else if (tok.value === "class") {
|
|
678
|
+
node = this.parseClassReference();
|
|
679
|
+
} else if (tok.value === "backend") {
|
|
680
|
+
node = this.parseBackendDeclaration();
|
|
681
|
+
} else if (tok.value === "app") {
|
|
682
|
+
node = this.parseAppDeclaration();
|
|
683
|
+
} else if (tok.value === "component") {
|
|
684
|
+
node = this.parseComponentDeclaration();
|
|
685
|
+
} else if (tok.value === "screen") {
|
|
686
|
+
node = this.parseScreenDeclaration();
|
|
687
|
+
} else if (tok.value === "go") {
|
|
688
|
+
node = this.parseGoToStatement();
|
|
689
|
+
} else if (tok.value === "navigate") {
|
|
690
|
+
node = this.parseNavigateStatement();
|
|
691
|
+
} else if (tok.value === "state") {
|
|
692
|
+
node = this.parseStateDeclaration();
|
|
693
|
+
} else if (tok.value === "try") {
|
|
694
|
+
node = this.parseTryCatch();
|
|
695
|
+
} else if (tok.value === "enum") {
|
|
696
|
+
node = this.parseEnumDeclaration();
|
|
697
|
+
} else if (tok.value === "switch") {
|
|
698
|
+
node = this.parseSwitchStatement();
|
|
699
|
+
} else if (tok.value === "computed") {
|
|
700
|
+
node = this.parseComputedDeclaration();
|
|
701
|
+
} else if (tok.value === "namespace") {
|
|
702
|
+
node = this.parseNamespaceDeclaration();
|
|
703
|
+
} else if (tok.value === "middleware") {
|
|
704
|
+
node = this.parseMiddlewareDeclaration();
|
|
705
|
+
} else if (tok.value === "theme") {
|
|
706
|
+
node = this.parseThemeDeclaration();
|
|
707
|
+
} else if (tok.value === "form") {
|
|
708
|
+
node = this.parseFormDeclaration();
|
|
709
|
+
} else if (tok.value === "token") {
|
|
710
|
+
node = this.parseDesignToken();
|
|
711
|
+
} else if (tok.value === "show") {
|
|
712
|
+
node = this.parseShowIf();
|
|
713
|
+
} else if (tok.value === "errorBoundary") {
|
|
714
|
+
node = this.parseErrorBoundary();
|
|
715
|
+
} else if (tok.value === "slot") {
|
|
716
|
+
this.current++;
|
|
717
|
+
node = { type: "SlotDeclaration" };
|
|
718
|
+
} else if (tok.value === "on") {
|
|
719
|
+
const next = this.peek(1);
|
|
720
|
+
if (next && (next.value === "mount" || next.value === "unmount" || next.value === "focus")) {
|
|
721
|
+
node = this.parseLifecycleBlock();
|
|
722
|
+
} else {
|
|
723
|
+
node = PluginEngine.getInstance().extendParser(this, tok.value);
|
|
724
|
+
if (!node) {
|
|
725
|
+
this.current++;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
} else if (tok.value === "mobile" || tok.value === "tablet" || tok.value === "desktop") {
|
|
729
|
+
node = this.parseResponsiveBlock();
|
|
730
|
+
} else if (tok.value === "animate") {
|
|
731
|
+
node = this.parseAnimateBlock();
|
|
732
|
+
} else if (tok.value === "describe") {
|
|
733
|
+
node = this.parseDescribeBlock();
|
|
734
|
+
} else if (tok.value === "test") {
|
|
735
|
+
node = this.parseTestBlock();
|
|
736
|
+
} else if (tok.value === "assert") {
|
|
737
|
+
node = this.parseAssertStatement();
|
|
738
|
+
} else if (tok.value === "switch") {
|
|
739
|
+
const next = this.peek(1);
|
|
740
|
+
if (next && (next.value === "bind" || next.value === "toggle")) {
|
|
741
|
+
node = this.parseUIElement("toggle");
|
|
742
|
+
if (node) {
|
|
743
|
+
node.elementType = "switch";
|
|
744
|
+
}
|
|
745
|
+
} else {
|
|
746
|
+
node = this.parseSwitchStatement();
|
|
747
|
+
}
|
|
748
|
+
} else {
|
|
749
|
+
node = PluginEngine.getInstance().extendParser(this, tok.value);
|
|
750
|
+
if (!node) {
|
|
751
|
+
this.current++;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
} else if (tok.type === 24 /* AT */) {
|
|
755
|
+
node = this.parseDecoratorAndFunction();
|
|
756
|
+
} else if (tok.type === 2 /* IDENTIFIER */) {
|
|
757
|
+
const next1 = this.peek(1);
|
|
758
|
+
const next2 = this.peek(2);
|
|
759
|
+
if (UI_TAGS.has(tok.value)) {
|
|
760
|
+
node = this.parseUIElement(tok.value);
|
|
761
|
+
} else if (next1?.type === 4 /* EQUALS */) {
|
|
762
|
+
node = this.parseStateAssignment();
|
|
763
|
+
} else if (next1?.type === 18 /* DOT */ && next2?.type === 2 /* IDENTIFIER */) {
|
|
764
|
+
const next3 = this.peek(3);
|
|
765
|
+
if (next3?.type === 15 /* LPAREN */) {
|
|
766
|
+
node = this.parseDottedCallStatement();
|
|
767
|
+
} else {
|
|
768
|
+
this.current++;
|
|
769
|
+
}
|
|
770
|
+
} else if (next1?.type === 25 /* LBRACKET */) {
|
|
771
|
+
node = this.parseIndexOrAssignmentStatement();
|
|
772
|
+
} else if (next1?.type === 15 /* LPAREN */) {
|
|
773
|
+
if (/^[A-Z]/.test(tok.value)) {
|
|
774
|
+
node = this.parseComponentCallStatement();
|
|
775
|
+
} else {
|
|
776
|
+
node = this.parseFunctionCallStatement();
|
|
777
|
+
}
|
|
778
|
+
} else {
|
|
779
|
+
this.current++;
|
|
780
|
+
}
|
|
781
|
+
} else {
|
|
782
|
+
this.current++;
|
|
783
|
+
}
|
|
784
|
+
if (node) {
|
|
785
|
+
if (startLine !== void 0) {
|
|
786
|
+
node.line = startLine;
|
|
787
|
+
}
|
|
788
|
+
nodes.push(node);
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
return nodes;
|
|
792
|
+
}
|
|
793
|
+
// ─────────────────────────────────────────
|
|
794
|
+
// Statement parsers
|
|
795
|
+
// ─────────────────────────────────────────
|
|
796
|
+
parseVariable() {
|
|
797
|
+
const dataType = this.tokens[this.current++].value;
|
|
798
|
+
const name = this.tokens[this.current++].value;
|
|
799
|
+
if (this.tokens[this.current]?.type === 4 /* EQUALS */) {
|
|
800
|
+
this.current++;
|
|
801
|
+
}
|
|
802
|
+
const value = this.parseExpression();
|
|
803
|
+
return { type: "VariableDeclaration", dataType, name, value };
|
|
804
|
+
}
|
|
805
|
+
parseConstDeclaration() {
|
|
806
|
+
this.current++;
|
|
807
|
+
const name = this.tokens[this.current++].value;
|
|
808
|
+
if (this.tokens[this.current]?.type === 4 /* EQUALS */) {
|
|
809
|
+
this.current++;
|
|
810
|
+
}
|
|
811
|
+
const value = this.parseExpression();
|
|
812
|
+
return { type: "VariableDeclaration", dataType: "const", name, value, isConst: true };
|
|
813
|
+
}
|
|
814
|
+
parseLog() {
|
|
815
|
+
this.current++;
|
|
816
|
+
const value = this.parseExpression();
|
|
817
|
+
return { type: "LogStatement", value };
|
|
818
|
+
}
|
|
819
|
+
parseIf() {
|
|
820
|
+
this.current++;
|
|
821
|
+
const condition = this.parseExpression();
|
|
822
|
+
const body = this.parseBody();
|
|
823
|
+
const elseifs = [];
|
|
824
|
+
while (this.tokens[this.current]?.type === 3 /* KEYWORD */ && this.tokens[this.current]?.value === "elseif") {
|
|
825
|
+
this.current++;
|
|
826
|
+
const elseifCond = this.parseExpression();
|
|
827
|
+
const elseifBody = this.parseBody();
|
|
828
|
+
elseifs.push({ condition: elseifCond, body: elseifBody });
|
|
829
|
+
}
|
|
830
|
+
let elseBody;
|
|
831
|
+
if (this.tokens[this.current]?.type === 3 /* KEYWORD */ && this.tokens[this.current]?.value === "else") {
|
|
832
|
+
this.current++;
|
|
833
|
+
elseBody = this.parseBody();
|
|
834
|
+
}
|
|
835
|
+
return { type: "IfStatement", condition, body, elseifs: elseifs.length > 0 ? elseifs : void 0, elseBody };
|
|
836
|
+
}
|
|
837
|
+
parseRepeatOrForIn() {
|
|
838
|
+
this.current++;
|
|
839
|
+
const next1 = this.peek(1);
|
|
840
|
+
if (next1?.type === 3 /* KEYWORD */ && next1.value === "in") {
|
|
841
|
+
return this.parseForInBody();
|
|
842
|
+
}
|
|
843
|
+
const count = this.parseExpression();
|
|
844
|
+
if (this.tokens[this.current]?.type === 3 /* KEYWORD */ && this.tokens[this.current]?.value === "times") {
|
|
845
|
+
this.current++;
|
|
846
|
+
}
|
|
847
|
+
const body = this.parseBody();
|
|
848
|
+
return { type: "RepeatStatement", count, body };
|
|
849
|
+
}
|
|
850
|
+
parseForInStatement() {
|
|
851
|
+
this.current++;
|
|
852
|
+
return this.parseForInBody();
|
|
853
|
+
}
|
|
854
|
+
parseForInBody() {
|
|
855
|
+
const variable = this.tokens[this.current++].value;
|
|
856
|
+
if (this.tokens[this.current]?.type === 3 /* KEYWORD */ && this.tokens[this.current]?.value === "in") {
|
|
857
|
+
this.current++;
|
|
858
|
+
}
|
|
859
|
+
const iterable = this.parseExpression();
|
|
860
|
+
const body = this.parseBody();
|
|
861
|
+
return { type: "ForInStatement", variable, iterable, body };
|
|
862
|
+
}
|
|
863
|
+
parseWhileStatement() {
|
|
864
|
+
this.current++;
|
|
865
|
+
const condition = this.parseExpression();
|
|
866
|
+
const body = this.parseBody();
|
|
867
|
+
return { type: "WhileStatement", condition, body };
|
|
868
|
+
}
|
|
869
|
+
parseBreakStatement() {
|
|
870
|
+
this.current++;
|
|
871
|
+
return { type: "BreakStatement" };
|
|
872
|
+
}
|
|
873
|
+
parseContinueStatement() {
|
|
874
|
+
this.current++;
|
|
875
|
+
return { type: "ContinueStatement" };
|
|
876
|
+
}
|
|
877
|
+
parseFunctionDeclaration(exported, decorators) {
|
|
878
|
+
this.current++;
|
|
879
|
+
const name = this.tokens[this.current++].value;
|
|
880
|
+
let typeParams;
|
|
881
|
+
if (this.tokens[this.current]?.type === 10 /* LESS_THAN */) {
|
|
882
|
+
typeParams = this.parseTypeParams();
|
|
883
|
+
}
|
|
884
|
+
const params = [];
|
|
885
|
+
if (this.tokens[this.current]?.type === 15 /* LPAREN */) {
|
|
886
|
+
this.current++;
|
|
887
|
+
while (this.current < this.tokens.length && this.tokens[this.current].type !== 16 /* RPAREN */ && this.tokens[this.current].type !== 19 /* EOF */) {
|
|
888
|
+
if (this.tokens[this.current].type === 17 /* COMMA */) {
|
|
889
|
+
this.current++;
|
|
890
|
+
continue;
|
|
891
|
+
}
|
|
892
|
+
params.push(this.tokens[this.current++].value);
|
|
893
|
+
}
|
|
894
|
+
if (this.tokens[this.current]?.type === 16 /* RPAREN */) {
|
|
895
|
+
this.current++;
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
const body = this.parseBody();
|
|
899
|
+
return { type: "FunctionDeclaration", name, params, body, exported, typeParams, decorators };
|
|
900
|
+
}
|
|
901
|
+
parseAsyncFunctionOrAwait() {
|
|
902
|
+
if (this.peek(1)?.type === 3 /* KEYWORD */ && this.peek(1)?.value === "function") {
|
|
903
|
+
this.current++;
|
|
904
|
+
const fn = this.parseFunctionDeclaration(false);
|
|
905
|
+
fn.isAsync = true;
|
|
906
|
+
return fn;
|
|
907
|
+
}
|
|
908
|
+
this.current++;
|
|
909
|
+
return { type: "LogStatement", value: { type: "Identifier", name: "async" } };
|
|
910
|
+
}
|
|
911
|
+
parseDecoratorAndFunction() {
|
|
912
|
+
const decorators = [];
|
|
913
|
+
while (this.tokens[this.current]?.type === 24 /* AT */) {
|
|
914
|
+
this.current++;
|
|
915
|
+
const name = this.tokens[this.current++].value;
|
|
916
|
+
let args;
|
|
917
|
+
if (this.tokens[this.current]?.type === 15 /* LPAREN */) {
|
|
918
|
+
args = this.parseArgList();
|
|
919
|
+
}
|
|
920
|
+
decorators.push({ name, args });
|
|
921
|
+
}
|
|
922
|
+
if (this.tokens[this.current]?.type === 3 /* KEYWORD */ && this.tokens[this.current]?.value === "function") {
|
|
923
|
+
return this.parseFunctionDeclaration(false, decorators);
|
|
924
|
+
}
|
|
925
|
+
if (this.tokens[this.current]?.type === 3 /* KEYWORD */ && this.tokens[this.current]?.value === "async") {
|
|
926
|
+
this.current++;
|
|
927
|
+
const fn = this.parseFunctionDeclaration(false, decorators);
|
|
928
|
+
fn.isAsync = true;
|
|
929
|
+
return fn;
|
|
930
|
+
}
|
|
931
|
+
this.errors.push("Expected 'function' after decorator(s)");
|
|
932
|
+
return { type: "FunctionDeclaration", name: "_unknown", params: [], body: [], exported: false, decorators };
|
|
933
|
+
}
|
|
934
|
+
parseReturn() {
|
|
935
|
+
this.current++;
|
|
936
|
+
const value = this.parseExpression();
|
|
937
|
+
return { type: "ReturnStatement", value };
|
|
938
|
+
}
|
|
939
|
+
parseTryCatch() {
|
|
940
|
+
this.current++;
|
|
941
|
+
const body = this.parseBody();
|
|
942
|
+
let catchParam = "error";
|
|
943
|
+
if (this.tokens[this.current]?.type === 3 /* KEYWORD */ && this.tokens[this.current]?.value === "catch") {
|
|
944
|
+
this.current++;
|
|
945
|
+
if (this.tokens[this.current]?.type === 2 /* IDENTIFIER */) {
|
|
946
|
+
catchParam = this.tokens[this.current++].value;
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
const catchBody = this.parseBody();
|
|
950
|
+
return { type: "TryCatchStatement", body, catchParam, catchBody };
|
|
951
|
+
}
|
|
952
|
+
parseEnumDeclaration() {
|
|
953
|
+
this.current++;
|
|
954
|
+
const name = this.tokens[this.current++].value;
|
|
955
|
+
const members = [];
|
|
956
|
+
while (!this.isEOF() && !(this.tokens[this.current]?.type === 3 /* KEYWORD */ && this.tokens[this.current]?.value === "end")) {
|
|
957
|
+
if (this.tokens[this.current]?.type === 2 /* IDENTIFIER */ || this.tokens[this.current]?.type === 3 /* KEYWORD */) {
|
|
958
|
+
members.push(this.tokens[this.current++].value);
|
|
959
|
+
} else {
|
|
960
|
+
this.current++;
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
if (this.tokens[this.current]?.type === 3 /* KEYWORD */ && this.tokens[this.current]?.value === "end") {
|
|
964
|
+
this.current++;
|
|
965
|
+
}
|
|
966
|
+
return { type: "EnumDeclaration", name, members };
|
|
967
|
+
}
|
|
968
|
+
parseSwitchStatement() {
|
|
969
|
+
this.current++;
|
|
970
|
+
const discriminant = this.parseExpression();
|
|
971
|
+
const cases = [];
|
|
972
|
+
let defaultBody;
|
|
973
|
+
while (!this.isEOF() && !(this.tokens[this.current]?.type === 3 /* KEYWORD */ && this.tokens[this.current]?.value === "end")) {
|
|
974
|
+
if (this.tokens[this.current]?.type === 3 /* KEYWORD */ && this.tokens[this.current]?.value === "case") {
|
|
975
|
+
this.current++;
|
|
976
|
+
const condition = this.parseExpression();
|
|
977
|
+
const body = this.parseStatements(() => {
|
|
978
|
+
if (this.isEOF()) return true;
|
|
979
|
+
const t = this.tokens[this.current];
|
|
980
|
+
return t.type === 3 /* KEYWORD */ && t.value === "end" || t.type === 3 /* KEYWORD */ && t.value === "case" || t.type === 3 /* KEYWORD */ && t.value === "default";
|
|
981
|
+
});
|
|
982
|
+
cases.push({ condition, body });
|
|
983
|
+
} else if (this.tokens[this.current]?.type === 3 /* KEYWORD */ && this.tokens[this.current]?.value === "default") {
|
|
984
|
+
this.current++;
|
|
985
|
+
defaultBody = this.parseStatements(() => {
|
|
986
|
+
if (this.isEOF()) return true;
|
|
987
|
+
const t = this.tokens[this.current];
|
|
988
|
+
return t.type === 3 /* KEYWORD */ && t.value === "end";
|
|
989
|
+
});
|
|
990
|
+
} else {
|
|
991
|
+
this.current++;
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
if (this.tokens[this.current]?.type === 3 /* KEYWORD */ && this.tokens[this.current]?.value === "end") {
|
|
995
|
+
this.current++;
|
|
996
|
+
}
|
|
997
|
+
return { type: "SwitchStatement", discriminant, cases, defaultBody };
|
|
998
|
+
}
|
|
999
|
+
parseComputedDeclaration() {
|
|
1000
|
+
this.current++;
|
|
1001
|
+
const name = this.tokens[this.current++].value;
|
|
1002
|
+
if (this.tokens[this.current]?.type === 4 /* EQUALS */) {
|
|
1003
|
+
this.current++;
|
|
1004
|
+
}
|
|
1005
|
+
const value = this.parseExpression();
|
|
1006
|
+
return { type: "ComputedDeclaration", name, value };
|
|
1007
|
+
}
|
|
1008
|
+
parseNamespaceDeclaration() {
|
|
1009
|
+
this.current++;
|
|
1010
|
+
const name = this.tokens[this.current++].value;
|
|
1011
|
+
const body = this.parseBody();
|
|
1012
|
+
return { type: "NamespaceDeclaration", name, body };
|
|
1013
|
+
}
|
|
1014
|
+
parseMiddlewareDeclaration() {
|
|
1015
|
+
this.current++;
|
|
1016
|
+
const name = this.tokens[this.current++].value;
|
|
1017
|
+
const body = this.parseBody();
|
|
1018
|
+
return { type: "MiddlewareDeclaration", name, body };
|
|
1019
|
+
}
|
|
1020
|
+
// ────────────────────────────────────────────────────────
|
|
1021
|
+
// Stage 9.6 — UI Framework parsers
|
|
1022
|
+
// ────────────────────────────────────────────────────────
|
|
1023
|
+
/** theme Light primary: "#3498db" end */
|
|
1024
|
+
parseThemeDeclaration() {
|
|
1025
|
+
this.current++;
|
|
1026
|
+
const name = this.tokens[this.current++].value;
|
|
1027
|
+
const properties = [];
|
|
1028
|
+
while (!this.isEOF() && !(this.tokens[this.current].type === 3 /* KEYWORD */ && this.tokens[this.current].value === "end")) {
|
|
1029
|
+
const key = this.tokens[this.current++].value;
|
|
1030
|
+
if (this.tokens[this.current]?.type === 22 /* COLON */) this.current++;
|
|
1031
|
+
const value = this.parseExpression();
|
|
1032
|
+
properties.push({ key, value });
|
|
1033
|
+
}
|
|
1034
|
+
if (this.tokens[this.current]?.type === 3 /* KEYWORD */ && this.tokens[this.current].value === "end") this.current++;
|
|
1035
|
+
return { type: "ThemeDeclaration", name, properties };
|
|
1036
|
+
}
|
|
1037
|
+
/** form LoginForm ... end */
|
|
1038
|
+
parseFormDeclaration() {
|
|
1039
|
+
this.current++;
|
|
1040
|
+
const name = this.tokens[this.current++].value;
|
|
1041
|
+
const body = this.parseBody();
|
|
1042
|
+
return { type: "FormDeclaration", name, body };
|
|
1043
|
+
}
|
|
1044
|
+
/** token primaryColor = "#3498db" */
|
|
1045
|
+
parseDesignToken() {
|
|
1046
|
+
this.current++;
|
|
1047
|
+
const name = this.tokens[this.current++].value;
|
|
1048
|
+
if (this.tokens[this.current]?.type === 4 /* EQUALS */) this.current++;
|
|
1049
|
+
const value = this.parseExpression();
|
|
1050
|
+
return { type: "DesignToken", name, value };
|
|
1051
|
+
}
|
|
1052
|
+
/** show if condition body end */
|
|
1053
|
+
parseShowIf() {
|
|
1054
|
+
this.current++;
|
|
1055
|
+
if (this.tokens[this.current]?.type === 3 /* KEYWORD */ && this.tokens[this.current].value === "if") {
|
|
1056
|
+
this.current++;
|
|
1057
|
+
}
|
|
1058
|
+
const condition = this.parseExpression();
|
|
1059
|
+
const body = this.parseStatements(
|
|
1060
|
+
() => this.isEOF() || this.tokens[this.current].type === 3 /* KEYWORD */ && this.tokens[this.current].value === "end"
|
|
1061
|
+
);
|
|
1062
|
+
if (this.tokens[this.current]?.type === 3 /* KEYWORD */ && this.tokens[this.current].value === "end") this.current++;
|
|
1063
|
+
return { type: "ShowIfStatement", condition, body };
|
|
1064
|
+
}
|
|
1065
|
+
/** errorBoundary fallback ... end */
|
|
1066
|
+
parseErrorBoundary() {
|
|
1067
|
+
this.current++;
|
|
1068
|
+
const fallback = [];
|
|
1069
|
+
if (this.tokens[this.current]?.type === 3 /* KEYWORD */ && this.tokens[this.current].value === "fallback") {
|
|
1070
|
+
this.current++;
|
|
1071
|
+
}
|
|
1072
|
+
const body = this.parseStatements(
|
|
1073
|
+
() => this.isEOF() || this.tokens[this.current].type === 3 /* KEYWORD */ && this.tokens[this.current].value === "end"
|
|
1074
|
+
);
|
|
1075
|
+
if (this.tokens[this.current]?.type === 3 /* KEYWORD */ && this.tokens[this.current].value === "end") this.current++;
|
|
1076
|
+
return { type: "ErrorBoundary", fallback: body };
|
|
1077
|
+
}
|
|
1078
|
+
/** on mount / on unmount / on focus — lifecycle blocks */
|
|
1079
|
+
parseLifecycleBlock() {
|
|
1080
|
+
this.current++;
|
|
1081
|
+
const event = this.tokens[this.current++].value;
|
|
1082
|
+
const body = this.parseBody();
|
|
1083
|
+
return { type: "LifecycleBlock", event, body };
|
|
1084
|
+
}
|
|
1085
|
+
/** if mobile / if tablet / if desktop — responsive blocks */
|
|
1086
|
+
parseResponsiveBlock() {
|
|
1087
|
+
const platform = this.tokens[this.current++].value;
|
|
1088
|
+
const body = this.parseBody();
|
|
1089
|
+
return { type: "ResponsiveBlock", platform, body };
|
|
1090
|
+
}
|
|
1091
|
+
/** animate duration: 300 scale: 1.2 fade: "in" end */
|
|
1092
|
+
parseAnimateBlock() {
|
|
1093
|
+
this.current++;
|
|
1094
|
+
const properties = {};
|
|
1095
|
+
while (!this.isEOF() && !(this.tokens[this.current].type === 3 /* KEYWORD */ && this.tokens[this.current].value === "end")) {
|
|
1096
|
+
const key = this.tokens[this.current++].value;
|
|
1097
|
+
if (this.tokens[this.current]?.type === 22 /* COLON */) this.current++;
|
|
1098
|
+
properties[key] = this.parseExpression();
|
|
1099
|
+
}
|
|
1100
|
+
if (this.tokens[this.current]?.type === 3 /* KEYWORD */ && this.tokens[this.current].value === "end") this.current++;
|
|
1101
|
+
return { type: "AnimateBlock", properties };
|
|
1102
|
+
}
|
|
1103
|
+
// ────────────────────────────────────────────────────────
|
|
1104
|
+
// Stage 10 — Testing Framework parsers
|
|
1105
|
+
// ────────────────────────────────────────────────────────
|
|
1106
|
+
/** describe "name" body end */
|
|
1107
|
+
parseDescribeBlock() {
|
|
1108
|
+
this.current++;
|
|
1109
|
+
const name = this.parseExpression();
|
|
1110
|
+
const body = this.parseBody();
|
|
1111
|
+
return { type: "DescribeBlock", name, body };
|
|
1112
|
+
}
|
|
1113
|
+
/** test "description" body end */
|
|
1114
|
+
parseTestBlock() {
|
|
1115
|
+
this.current++;
|
|
1116
|
+
const description = this.parseExpression();
|
|
1117
|
+
const body = this.parseBody();
|
|
1118
|
+
return { type: "TestBlock", description, body };
|
|
1119
|
+
}
|
|
1120
|
+
/**
|
|
1121
|
+
* assert expr == expr -> equal
|
|
1122
|
+
* assert expr != expr -> notEqual
|
|
1123
|
+
* assert expr -> truthy
|
|
1124
|
+
* assert not expr -> falsy
|
|
1125
|
+
* assert throws expr -> throws
|
|
1126
|
+
* assert contain expr expr -> contains
|
|
1127
|
+
* assert typeOf expr "type" -> typeOf
|
|
1128
|
+
*/
|
|
1129
|
+
parseAssertStatement() {
|
|
1130
|
+
this.current++;
|
|
1131
|
+
const tok = this.tokens[this.current];
|
|
1132
|
+
if (tok?.type === 3 /* KEYWORD */ && tok.value === "not") {
|
|
1133
|
+
this.current++;
|
|
1134
|
+
const left2 = this.parseExpression();
|
|
1135
|
+
return { type: "AssertStatement", variant: "falsy", left: left2 };
|
|
1136
|
+
}
|
|
1137
|
+
if (tok?.type === 3 /* KEYWORD */ && tok.value === "throws") {
|
|
1138
|
+
this.current++;
|
|
1139
|
+
const left2 = this.parseExpression();
|
|
1140
|
+
return { type: "AssertStatement", variant: "throws", left: left2 };
|
|
1141
|
+
}
|
|
1142
|
+
if (tok?.type === 3 /* KEYWORD */ && tok.value === "contain") {
|
|
1143
|
+
this.current++;
|
|
1144
|
+
const left2 = this.parseExpression();
|
|
1145
|
+
const right = this.parseExpression();
|
|
1146
|
+
return { type: "AssertStatement", variant: "contains", left: left2, right };
|
|
1147
|
+
}
|
|
1148
|
+
if (tok?.type === 3 /* KEYWORD */ && tok.value === "typeOf") {
|
|
1149
|
+
this.current++;
|
|
1150
|
+
const left2 = this.parseExpression();
|
|
1151
|
+
let expectedType = "any";
|
|
1152
|
+
if (this.tokens[this.current]?.type === 1 /* STRING */) {
|
|
1153
|
+
expectedType = this.tokens[this.current++].value;
|
|
1154
|
+
}
|
|
1155
|
+
return { type: "AssertStatement", variant: "typeOf", left: left2, expectedType };
|
|
1156
|
+
}
|
|
1157
|
+
const left = this.parseExpression();
|
|
1158
|
+
if (left.type === "BinaryExpression") {
|
|
1159
|
+
if (left.operator === "==") {
|
|
1160
|
+
return { type: "AssertStatement", variant: "equal", left: left.left, right: left.right };
|
|
1161
|
+
}
|
|
1162
|
+
if (left.operator === "!=") {
|
|
1163
|
+
return { type: "AssertStatement", variant: "notEqual", left: left.left, right: left.right };
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
const nextTok = this.tokens[this.current];
|
|
1167
|
+
if (nextTok?.type === 13 /* EQUAL_EQUAL */) {
|
|
1168
|
+
this.current++;
|
|
1169
|
+
const right = this.parseExpression();
|
|
1170
|
+
return { type: "AssertStatement", variant: "equal", left, right };
|
|
1171
|
+
}
|
|
1172
|
+
if (nextTok?.type === 14 /* NOT_EQUAL */) {
|
|
1173
|
+
this.current++;
|
|
1174
|
+
const right = this.parseExpression();
|
|
1175
|
+
return { type: "AssertStatement", variant: "notEqual", left, right };
|
|
1176
|
+
}
|
|
1177
|
+
return { type: "AssertStatement", variant: "truthy", left };
|
|
1178
|
+
}
|
|
1179
|
+
// ─────────────────────────────────────────
|
|
1180
|
+
// UI Element parsers
|
|
1181
|
+
// ─────────────────────────────────────────
|
|
1182
|
+
parseUIElement(tag) {
|
|
1183
|
+
this.current++;
|
|
1184
|
+
const element = { type: "UIElement", elementType: tag };
|
|
1185
|
+
if (this.tokens[this.current]?.type === 3 /* KEYWORD */ && this.tokens[this.current].value === "bind") {
|
|
1186
|
+
this.current++;
|
|
1187
|
+
element.bindTarget = this.tokens[this.current++].value;
|
|
1188
|
+
return element;
|
|
1189
|
+
}
|
|
1190
|
+
const nextTok = this.tokens[this.current];
|
|
1191
|
+
if (nextTok && nextTok.type !== 19 /* EOF */ && nextTok.type !== 3 /* KEYWORD */ && !(nextTok.type === 2 /* IDENTIFIER */ && UI_TAGS.has(nextTok.value))) {
|
|
1192
|
+
if (nextTok.type === 1 /* STRING */ || nextTok.type === 28 /* TEMPLATE_STRING */ || nextTok.type === 0 /* NUMBER */ || nextTok.type === 2 /* IDENTIFIER */ || nextTok.type === 15 /* LPAREN */) {
|
|
1193
|
+
element.content = this.parseExpression();
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
if (tag === "card" && element.content) {
|
|
1197
|
+
const next2 = this.tokens[this.current];
|
|
1198
|
+
if (next2 && next2.type !== 19 /* EOF */ && next2.type !== 3 /* KEYWORD */) {
|
|
1199
|
+
element.secondaryContent = this.parseExpression();
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
if ((tag === "input" || tag === "textarea") && this.tokens[this.current]?.type === 3 /* KEYWORD */ && this.tokens[this.current].value === "placeholder") {
|
|
1203
|
+
this.current++;
|
|
1204
|
+
if (this.tokens[this.current]?.type === 1 /* STRING */) {
|
|
1205
|
+
element.placeholder = this.tokens[this.current++].value;
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
if (this.tokens[this.current]?.type === 3 /* KEYWORD */ && this.tokens[this.current].value === "bind") {
|
|
1209
|
+
this.current++;
|
|
1210
|
+
element.bindTarget = this.tokens[this.current++].value;
|
|
1211
|
+
}
|
|
1212
|
+
const validatable = ["input", "textarea", "select", "dropdown"];
|
|
1213
|
+
if (validatable.includes(tag)) {
|
|
1214
|
+
const rules = [];
|
|
1215
|
+
while (!this.isEOF()) {
|
|
1216
|
+
const tok = this.tokens[this.current];
|
|
1217
|
+
if (tok?.type === 3 /* KEYWORD */ && tok.value === "required") {
|
|
1218
|
+
this.current++;
|
|
1219
|
+
rules.push({ rule: "required" });
|
|
1220
|
+
} else if (tok?.type === 3 /* KEYWORD */ && tok.value === "min") {
|
|
1221
|
+
this.current++;
|
|
1222
|
+
rules.push({ rule: "min", value: this.parseExpression() });
|
|
1223
|
+
} else if (tok?.type === 3 /* KEYWORD */ && tok.value === "max") {
|
|
1224
|
+
this.current++;
|
|
1225
|
+
rules.push({ rule: "max", value: this.parseExpression() });
|
|
1226
|
+
} else if (tok?.type === 3 /* KEYWORD */ && tok.value === "pattern") {
|
|
1227
|
+
this.current++;
|
|
1228
|
+
rules.push({ rule: "pattern", value: this.parseExpression() });
|
|
1229
|
+
} else {
|
|
1230
|
+
break;
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
if (rules.length > 0) element.validation = rules;
|
|
1234
|
+
}
|
|
1235
|
+
const afterContent = this.tokens[this.current];
|
|
1236
|
+
if (afterContent?.type === 3 /* KEYWORD */ && afterContent.value === "style") {
|
|
1237
|
+
element.inlineStyle = this.parseStyleBlock();
|
|
1238
|
+
} else if (afterContent?.type === 3 /* KEYWORD */ && afterContent.value === "class") {
|
|
1239
|
+
this.current++;
|
|
1240
|
+
if (this.tokens[this.current]?.type === 1 /* STRING */) {
|
|
1241
|
+
element.className = this.tokens[this.current++].value;
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
if (this.tokens[this.current]?.type === 3 /* KEYWORD */ && (this.tokens[this.current].value === "ariaLabel" || this.tokens[this.current].value === "accessibilityRole")) {
|
|
1245
|
+
element.accessibility = {};
|
|
1246
|
+
while (this.tokens[this.current]?.type === 3 /* KEYWORD */ && (this.tokens[this.current].value === "ariaLabel" || this.tokens[this.current].value === "accessibilityRole")) {
|
|
1247
|
+
const propName = this.tokens[this.current].value;
|
|
1248
|
+
this.current++;
|
|
1249
|
+
if (this.tokens[this.current]?.type === 22 /* COLON */) this.current++;
|
|
1250
|
+
if (propName === "ariaLabel") {
|
|
1251
|
+
element.accessibility.ariaLabel = this.parseExpression();
|
|
1252
|
+
} else {
|
|
1253
|
+
const roleExpr = this.parseExpression();
|
|
1254
|
+
element.accessibility.role = roleExpr.type === "StringLiteral" ? roleExpr.value : "generic";
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
if (this.tokens[this.current]?.type === 3 /* KEYWORD */ && this.tokens[this.current].value === "animate") {
|
|
1259
|
+
const animBlock = this.parseAnimateBlock();
|
|
1260
|
+
element.animateProps = animBlock.properties;
|
|
1261
|
+
}
|
|
1262
|
+
const containers = /* @__PURE__ */ new Set([
|
|
1263
|
+
"column",
|
|
1264
|
+
"row",
|
|
1265
|
+
"container",
|
|
1266
|
+
"list",
|
|
1267
|
+
"scroll",
|
|
1268
|
+
"stack",
|
|
1269
|
+
"center",
|
|
1270
|
+
"grid",
|
|
1271
|
+
"modal",
|
|
1272
|
+
"dialog",
|
|
1273
|
+
"sheet",
|
|
1274
|
+
"tabs",
|
|
1275
|
+
"tab"
|
|
1276
|
+
]);
|
|
1277
|
+
if (containers.has(tag)) {
|
|
1278
|
+
element.children = this.parseStatements(() => {
|
|
1279
|
+
if (this.isEOF()) return true;
|
|
1280
|
+
const t = this.tokens[this.current];
|
|
1281
|
+
return t.type === 3 /* KEYWORD */ && t.value === "end";
|
|
1282
|
+
});
|
|
1283
|
+
if (this.tokens[this.current]?.type === 3 /* KEYWORD */ && this.tokens[this.current]?.value === "end") {
|
|
1284
|
+
this.current++;
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
element.events = [];
|
|
1288
|
+
while (!this.isEOF() && this.tokens[this.current]?.type === 3 /* KEYWORD */ && this.tokens[this.current]?.value === "on") {
|
|
1289
|
+
element.events.push(this.parseEventHandler());
|
|
1290
|
+
}
|
|
1291
|
+
return element;
|
|
1292
|
+
}
|
|
1293
|
+
parseEventHandler() {
|
|
1294
|
+
this.current++;
|
|
1295
|
+
const eventName = this.tokens[this.current++].value;
|
|
1296
|
+
let valueBinding;
|
|
1297
|
+
if (eventName === "change" && this.tokens[this.current]?.type === 3 /* KEYWORD */ && this.tokens[this.current]?.value === "value") {
|
|
1298
|
+
this.current++;
|
|
1299
|
+
if (this.tokens[this.current]?.type === 2 /* IDENTIFIER */) {
|
|
1300
|
+
valueBinding = this.tokens[this.current++].value;
|
|
1301
|
+
} else {
|
|
1302
|
+
valueBinding = "value";
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
const body = this.parseBody();
|
|
1306
|
+
return { type: "EventHandler", eventName, valueBinding, body };
|
|
1307
|
+
}
|
|
1308
|
+
// ─────────────────────────────────────────
|
|
1309
|
+
// Component parsers
|
|
1310
|
+
// ─────────────────────────────────────────
|
|
1311
|
+
parseComponentDeclaration() {
|
|
1312
|
+
this.current++;
|
|
1313
|
+
const nameTok = this.tokens[this.current];
|
|
1314
|
+
const name = nameTok && nameTok.type !== 19 /* EOF */ ? nameTok.value : "";
|
|
1315
|
+
if (nameTok && nameTok.type !== 19 /* EOF */) {
|
|
1316
|
+
this.current++;
|
|
1317
|
+
}
|
|
1318
|
+
const params = [];
|
|
1319
|
+
if (this.tokens[this.current]?.type === 15 /* LPAREN */) {
|
|
1320
|
+
this.current++;
|
|
1321
|
+
while (this.current < this.tokens.length && this.tokens[this.current].type !== 16 /* RPAREN */ && this.tokens[this.current].type !== 19 /* EOF */) {
|
|
1322
|
+
if (this.tokens[this.current].type === 17 /* COMMA */) {
|
|
1323
|
+
this.current++;
|
|
1324
|
+
continue;
|
|
1325
|
+
}
|
|
1326
|
+
params.push(this.tokens[this.current++].value);
|
|
1327
|
+
}
|
|
1328
|
+
if (this.tokens[this.current]?.type === 16 /* RPAREN */) {
|
|
1329
|
+
this.current++;
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
const body = this.parseBody();
|
|
1333
|
+
return { type: "ComponentDeclaration", name, params, body };
|
|
1334
|
+
}
|
|
1335
|
+
parseComponentCallStatement() {
|
|
1336
|
+
const name = this.tokens[this.current++].value;
|
|
1337
|
+
const args = this.parseArgList();
|
|
1338
|
+
return { type: "ComponentCall", name, args };
|
|
1339
|
+
}
|
|
1340
|
+
parseFunctionCallStatement() {
|
|
1341
|
+
const callee = this.tokens[this.current++].value;
|
|
1342
|
+
const args = this.parseArgList();
|
|
1343
|
+
return { type: "FunctionCall", callee, args };
|
|
1344
|
+
}
|
|
1345
|
+
parseDottedCallStatement() {
|
|
1346
|
+
const obj = this.tokens[this.current++].value;
|
|
1347
|
+
this.current++;
|
|
1348
|
+
const method = this.tokens[this.current++].value;
|
|
1349
|
+
const callee = `${obj}.${method}`;
|
|
1350
|
+
const args = this.parseArgList();
|
|
1351
|
+
return { type: "FunctionCall", callee, args };
|
|
1352
|
+
}
|
|
1353
|
+
parseIndexOrAssignmentStatement() {
|
|
1354
|
+
const obj = this.tokens[this.current++].value;
|
|
1355
|
+
this.current++;
|
|
1356
|
+
const index = this.parseExpression();
|
|
1357
|
+
if (this.tokens[this.current]?.type === 26 /* RBRACKET */) {
|
|
1358
|
+
this.current++;
|
|
1359
|
+
}
|
|
1360
|
+
const indexExpr = {
|
|
1361
|
+
type: "IndexExpression",
|
|
1362
|
+
object: { type: "Identifier", name: obj },
|
|
1363
|
+
index
|
|
1364
|
+
};
|
|
1365
|
+
if (this.tokens[this.current]?.type === 4 /* EQUALS */) {
|
|
1366
|
+
this.current++;
|
|
1367
|
+
const value = this.parseExpression();
|
|
1368
|
+
return { type: "StateAssignment", name: obj, value };
|
|
1369
|
+
}
|
|
1370
|
+
return { type: "FunctionCall", callee: "_index_access", args: [indexExpr] };
|
|
1371
|
+
}
|
|
1372
|
+
// ─────────────────────────────────────────
|
|
1373
|
+
// Import / Export
|
|
1374
|
+
// ─────────────────────────────────────────
|
|
1375
|
+
parseImport() {
|
|
1376
|
+
this.current++;
|
|
1377
|
+
if (this.tokens[this.current]?.type === 20 /* LBRACE */) {
|
|
1378
|
+
this.current++;
|
|
1379
|
+
const namedImports = [];
|
|
1380
|
+
while (this.current < this.tokens.length && this.tokens[this.current].type !== 21 /* RBRACE */ && this.tokens[this.current].type !== 19 /* EOF */) {
|
|
1381
|
+
if (this.tokens[this.current].type === 17 /* COMMA */) {
|
|
1382
|
+
this.current++;
|
|
1383
|
+
continue;
|
|
1384
|
+
}
|
|
1385
|
+
namedImports.push(this.tokens[this.current++].value);
|
|
1386
|
+
}
|
|
1387
|
+
if (this.tokens[this.current]?.type === 21 /* RBRACE */) this.current++;
|
|
1388
|
+
if (this.tokens[this.current]?.type === 3 /* KEYWORD */ && this.tokens[this.current].value === "from") this.current++;
|
|
1389
|
+
const path3 = this.tokens[this.current++].value;
|
|
1390
|
+
return { type: "ImportDeclaration", alias: namedImports[0] || "", path: path3, namedImports };
|
|
1391
|
+
}
|
|
1392
|
+
const alias = this.tokens[this.current++].value;
|
|
1393
|
+
if (this.tokens[this.current]?.type === 3 /* KEYWORD */ && this.tokens[this.current]?.value === "from") {
|
|
1394
|
+
this.current++;
|
|
1395
|
+
}
|
|
1396
|
+
const path2 = this.tokens[this.current++].value;
|
|
1397
|
+
return { type: "ImportDeclaration", alias, path: path2 };
|
|
1398
|
+
}
|
|
1399
|
+
parseExport() {
|
|
1400
|
+
this.current++;
|
|
1401
|
+
let isDefault = false;
|
|
1402
|
+
if (this.tokens[this.current]?.type === 3 /* KEYWORD */ && this.tokens[this.current].value === "default") {
|
|
1403
|
+
this.current++;
|
|
1404
|
+
isDefault = true;
|
|
1405
|
+
}
|
|
1406
|
+
if (this.tokens[this.current]?.type === 3 /* KEYWORD */ && this.tokens[this.current]?.value === "function") {
|
|
1407
|
+
const decl = this.parseFunctionDeclaration(true);
|
|
1408
|
+
return { type: "ExportDeclaration", declaration: decl, isDefault };
|
|
1409
|
+
}
|
|
1410
|
+
if (this.tokens[this.current]?.type === 3 /* KEYWORD */ && this.tokens[this.current]?.value === "async") {
|
|
1411
|
+
this.current++;
|
|
1412
|
+
const decl = this.parseFunctionDeclaration(true);
|
|
1413
|
+
decl.isAsync = true;
|
|
1414
|
+
return { type: "ExportDeclaration", declaration: decl, isDefault };
|
|
1415
|
+
}
|
|
1416
|
+
if (this.tokens[this.current]?.type === 3 /* KEYWORD */ && this.tokens[this.current]?.value === "component") {
|
|
1417
|
+
const decl = this.parseComponentDeclaration();
|
|
1418
|
+
return { type: "ExportDeclaration", declaration: decl, isDefault };
|
|
1419
|
+
}
|
|
1420
|
+
if (this.tokens[this.current]?.type === 3 /* KEYWORD */ && this.tokens[this.current]?.value === "const") {
|
|
1421
|
+
const decl = this.parseConstDeclaration();
|
|
1422
|
+
return { type: "ExportDeclaration", declaration: decl, isDefault };
|
|
1423
|
+
}
|
|
1424
|
+
throw new Error("'export' must be followed by 'function', 'component', 'const', or 'async function'");
|
|
1425
|
+
}
|
|
1426
|
+
// ─────────────────────────────────────────
|
|
1427
|
+
// Style parsers
|
|
1428
|
+
// ─────────────────────────────────────────
|
|
1429
|
+
parseStyleBlock(name) {
|
|
1430
|
+
this.current++;
|
|
1431
|
+
let extendsName;
|
|
1432
|
+
if (this.tokens[this.current]?.type === 3 /* KEYWORD */ && this.tokens[this.current]?.value === "extends") {
|
|
1433
|
+
this.current++;
|
|
1434
|
+
if (this.tokens[this.current]?.type === 1 /* STRING */) {
|
|
1435
|
+
extendsName = this.tokens[this.current++].value;
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
if (this.tokens[this.current]?.type === 20 /* LBRACE */) {
|
|
1439
|
+
this.current++;
|
|
1440
|
+
}
|
|
1441
|
+
const properties = [];
|
|
1442
|
+
while (!this.isEOF() && this.tokens[this.current].type !== 21 /* RBRACE */) {
|
|
1443
|
+
const tok = this.tokens[this.current];
|
|
1444
|
+
if (tok.type !== 2 /* IDENTIFIER */) {
|
|
1445
|
+
this.current++;
|
|
1446
|
+
continue;
|
|
1447
|
+
}
|
|
1448
|
+
let key = this.tokens[this.current++].value;
|
|
1449
|
+
while (this.tokens[this.current]?.type === 6 /* MINUS */ && this.tokens[this.current + 1]?.type === 2 /* IDENTIFIER */) {
|
|
1450
|
+
this.current++;
|
|
1451
|
+
key += "-" + this.tokens[this.current++].value;
|
|
1452
|
+
}
|
|
1453
|
+
if (this.tokens[this.current]?.type === 22 /* COLON */) {
|
|
1454
|
+
this.current++;
|
|
1455
|
+
}
|
|
1456
|
+
const value = this.parseExpression();
|
|
1457
|
+
if (this.tokens[this.current]?.type === 23 /* SEMICOLON */) {
|
|
1458
|
+
this.current++;
|
|
1459
|
+
}
|
|
1460
|
+
properties.push({ key, value });
|
|
1461
|
+
}
|
|
1462
|
+
if (this.tokens[this.current]?.type === 21 /* RBRACE */) {
|
|
1463
|
+
this.current++;
|
|
1464
|
+
}
|
|
1465
|
+
const block = { type: "StyleBlock", properties };
|
|
1466
|
+
if (name !== void 0) block.name = name;
|
|
1467
|
+
if (extendsName !== void 0) block.extends = extendsName;
|
|
1468
|
+
return block;
|
|
1469
|
+
}
|
|
1470
|
+
parseClassReference() {
|
|
1471
|
+
this.current++;
|
|
1472
|
+
let name = "";
|
|
1473
|
+
if (this.tokens[this.current]?.type === 1 /* STRING */) {
|
|
1474
|
+
name = this.tokens[this.current++].value;
|
|
1475
|
+
}
|
|
1476
|
+
return { type: "ClassReference", name };
|
|
1477
|
+
}
|
|
1478
|
+
parseKinstyleFile() {
|
|
1479
|
+
const blocks = [];
|
|
1480
|
+
while (!this.isEOF()) {
|
|
1481
|
+
const tok = this.tokens[this.current];
|
|
1482
|
+
if (tok.type !== 2 /* IDENTIFIER */) {
|
|
1483
|
+
this.current++;
|
|
1484
|
+
continue;
|
|
1485
|
+
}
|
|
1486
|
+
const blockName = this.tokens[this.current++].value;
|
|
1487
|
+
let extendsName;
|
|
1488
|
+
if (this.tokens[this.current]?.type === 3 /* KEYWORD */ && this.tokens[this.current]?.value === "extends") {
|
|
1489
|
+
this.current++;
|
|
1490
|
+
if (this.tokens[this.current]?.type === 1 /* STRING */) {
|
|
1491
|
+
extendsName = this.tokens[this.current++].value;
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
if (this.tokens[this.current]?.type === 20 /* LBRACE */) {
|
|
1495
|
+
this.current++;
|
|
1496
|
+
}
|
|
1497
|
+
const properties = [];
|
|
1498
|
+
while (!this.isEOF() && this.tokens[this.current].type !== 21 /* RBRACE */) {
|
|
1499
|
+
const propTok = this.tokens[this.current];
|
|
1500
|
+
if (propTok.type !== 2 /* IDENTIFIER */) {
|
|
1501
|
+
this.current++;
|
|
1502
|
+
continue;
|
|
1503
|
+
}
|
|
1504
|
+
let key = this.tokens[this.current++].value;
|
|
1505
|
+
while (this.tokens[this.current]?.type === 6 /* MINUS */ && this.tokens[this.current + 1]?.type === 2 /* IDENTIFIER */) {
|
|
1506
|
+
this.current++;
|
|
1507
|
+
key += "-" + this.tokens[this.current++].value;
|
|
1508
|
+
}
|
|
1509
|
+
if (this.tokens[this.current]?.type === 22 /* COLON */) {
|
|
1510
|
+
this.current++;
|
|
1511
|
+
}
|
|
1512
|
+
const value = this.parseExpression();
|
|
1513
|
+
if (this.tokens[this.current]?.type === 23 /* SEMICOLON */) {
|
|
1514
|
+
this.current++;
|
|
1515
|
+
}
|
|
1516
|
+
properties.push({ key, value });
|
|
1517
|
+
}
|
|
1518
|
+
if (this.tokens[this.current]?.type === 21 /* RBRACE */) {
|
|
1519
|
+
this.current++;
|
|
1520
|
+
}
|
|
1521
|
+
const block = { type: "StyleBlock", name: blockName, properties };
|
|
1522
|
+
if (extendsName !== void 0) block.extends = extendsName;
|
|
1523
|
+
blocks.push(block);
|
|
1524
|
+
}
|
|
1525
|
+
return { type: "StyleSheet", blocks };
|
|
1526
|
+
}
|
|
1527
|
+
// ─────────────────────────────────────────
|
|
1528
|
+
// Backend DSL parsers
|
|
1529
|
+
// ─────────────────────────────────────────
|
|
1530
|
+
parseMethodStatement() {
|
|
1531
|
+
this.current++;
|
|
1532
|
+
const tok = this.tokens[this.current];
|
|
1533
|
+
if (!tok || tok.type === 19 /* EOF */) {
|
|
1534
|
+
this.errors.push("Expected HTTP method after 'method' keyword");
|
|
1535
|
+
return null;
|
|
1536
|
+
}
|
|
1537
|
+
const value = tok.value.toUpperCase();
|
|
1538
|
+
const valid = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "DELETE", "PATCH"]);
|
|
1539
|
+
if (!valid.has(value)) {
|
|
1540
|
+
this.errors.push(`Unknown HTTP method: '${tok.value}'`);
|
|
1541
|
+
this.current++;
|
|
1542
|
+
return null;
|
|
1543
|
+
}
|
|
1544
|
+
this.current++;
|
|
1545
|
+
return { type: "MethodStatement", httpMethod: value };
|
|
1546
|
+
}
|
|
1547
|
+
parseRouteDeclaration() {
|
|
1548
|
+
this.current++;
|
|
1549
|
+
const pathTok = this.tokens[this.current];
|
|
1550
|
+
const path2 = pathTok && pathTok.type !== 19 /* EOF */ ? pathTok.value : "";
|
|
1551
|
+
if (pathTok && pathTok.type !== 19 /* EOF */) {
|
|
1552
|
+
this.current++;
|
|
1553
|
+
}
|
|
1554
|
+
let method = null;
|
|
1555
|
+
let auth = false;
|
|
1556
|
+
const bodyNodes = [];
|
|
1557
|
+
while (!this.isEOF()) {
|
|
1558
|
+
const tok = this.tokens[this.current];
|
|
1559
|
+
if (tok.type === 3 /* KEYWORD */ && tok.value === "end") break;
|
|
1560
|
+
if (tok.type === 3 /* KEYWORD */ && tok.value === "method") {
|
|
1561
|
+
method = this.parseMethodStatement();
|
|
1562
|
+
continue;
|
|
1563
|
+
}
|
|
1564
|
+
if (tok.type === 3 /* KEYWORD */ && tok.value === "auth") {
|
|
1565
|
+
this.current++;
|
|
1566
|
+
if (this.tokens[this.current]?.type === 3 /* KEYWORD */ && this.tokens[this.current]?.value === "required") {
|
|
1567
|
+
this.current++;
|
|
1568
|
+
}
|
|
1569
|
+
auth = true;
|
|
1570
|
+
continue;
|
|
1571
|
+
}
|
|
1572
|
+
const stmtsBefore = bodyNodes.length;
|
|
1573
|
+
const parsed = this.parseStatements(() => {
|
|
1574
|
+
if (this.isEOF()) return true;
|
|
1575
|
+
const t = this.tokens[this.current];
|
|
1576
|
+
return t.type === 3 /* KEYWORD */ && t.value === "end" || t.type === 3 /* KEYWORD */ && t.value === "method" || t.type === 3 /* KEYWORD */ && t.value === "auth";
|
|
1577
|
+
});
|
|
1578
|
+
bodyNodes.push(...parsed);
|
|
1579
|
+
if (bodyNodes.length === stmtsBefore) {
|
|
1580
|
+
this.current++;
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
if (this.tokens[this.current]?.type === 3 /* KEYWORD */ && this.tokens[this.current]?.value === "end") {
|
|
1584
|
+
this.current++;
|
|
1585
|
+
}
|
|
1586
|
+
if (!method) {
|
|
1587
|
+
this.errors.push(`Route '${path2}' is missing a method declaration`);
|
|
1588
|
+
method = { type: "MethodStatement", httpMethod: "GET" };
|
|
1589
|
+
}
|
|
1590
|
+
return { type: "RouteDeclaration", path: path2, method, body: bodyNodes, auth };
|
|
1591
|
+
}
|
|
1592
|
+
parseBackendDeclaration() {
|
|
1593
|
+
this.current++;
|
|
1594
|
+
const nameTok = this.tokens[this.current];
|
|
1595
|
+
const name = nameTok && nameTok.type !== 19 /* EOF */ ? nameTok.value : "";
|
|
1596
|
+
if (nameTok && nameTok.type !== 19 /* EOF */) {
|
|
1597
|
+
this.current++;
|
|
1598
|
+
}
|
|
1599
|
+
const routes = [];
|
|
1600
|
+
while (!this.isEOF()) {
|
|
1601
|
+
const tok = this.tokens[this.current];
|
|
1602
|
+
if (tok.type === 3 /* KEYWORD */ && tok.value === "end") break;
|
|
1603
|
+
if (tok.type === 3 /* KEYWORD */ && tok.value === "route") {
|
|
1604
|
+
routes.push(this.parseRouteDeclaration());
|
|
1605
|
+
continue;
|
|
1606
|
+
}
|
|
1607
|
+
this.current++;
|
|
1608
|
+
}
|
|
1609
|
+
if (this.tokens[this.current]?.type === 3 /* KEYWORD */ && this.tokens[this.current]?.value === "end") {
|
|
1610
|
+
this.current++;
|
|
1611
|
+
}
|
|
1612
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1613
|
+
for (const route of routes) {
|
|
1614
|
+
const key = `${route.method.httpMethod} ${route.path}`;
|
|
1615
|
+
if (seen.has(key)) {
|
|
1616
|
+
this.errors.push(`Duplicate route ${route.method.httpMethod} ${route.path} in backend '${name}'`);
|
|
1617
|
+
} else {
|
|
1618
|
+
seen.add(key);
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
return { type: "BackendDeclaration", name, routes };
|
|
1622
|
+
}
|
|
1623
|
+
// ─────────────────────────────────────────
|
|
1624
|
+
// App DSL parsers
|
|
1625
|
+
// ─────────────────────────────────────────
|
|
1626
|
+
parseAppDeclaration() {
|
|
1627
|
+
this.current++;
|
|
1628
|
+
const name = this.tokens[this.current++].value;
|
|
1629
|
+
return { type: "AppDeclaration", name };
|
|
1630
|
+
}
|
|
1631
|
+
parseScreenDeclaration() {
|
|
1632
|
+
this.current++;
|
|
1633
|
+
const name = this.tokens[this.current++].value;
|
|
1634
|
+
const params = [];
|
|
1635
|
+
if (this.tokens[this.current]?.type === 15 /* LPAREN */) {
|
|
1636
|
+
this.current++;
|
|
1637
|
+
while (this.current < this.tokens.length && this.tokens[this.current].type !== 16 /* RPAREN */ && this.tokens[this.current].type !== 19 /* EOF */) {
|
|
1638
|
+
if (this.tokens[this.current].type === 17 /* COMMA */) {
|
|
1639
|
+
this.current++;
|
|
1640
|
+
continue;
|
|
1641
|
+
}
|
|
1642
|
+
params.push(this.tokens[this.current++].value);
|
|
1643
|
+
}
|
|
1644
|
+
if (this.tokens[this.current]?.type === 16 /* RPAREN */) {
|
|
1645
|
+
this.current++;
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
const body = this.parseBody();
|
|
1649
|
+
return { type: "ScreenDeclaration", name, params, body };
|
|
1650
|
+
}
|
|
1651
|
+
parseGoToStatement() {
|
|
1652
|
+
this.current++;
|
|
1653
|
+
if (this.tokens[this.current]?.type === 3 /* KEYWORD */ && this.tokens[this.current]?.value === "to") {
|
|
1654
|
+
this.current++;
|
|
1655
|
+
} else {
|
|
1656
|
+
this.errors.push("Expected 'to' after 'go'");
|
|
1657
|
+
}
|
|
1658
|
+
const target = this.tokens[this.current++].value;
|
|
1659
|
+
const args = [];
|
|
1660
|
+
if (this.tokens[this.current]?.type === 3 /* KEYWORD */ && this.tokens[this.current]?.value === "with") {
|
|
1661
|
+
this.current++;
|
|
1662
|
+
while (!this.isEOF()) {
|
|
1663
|
+
const tok = this.tokens[this.current];
|
|
1664
|
+
if (tok?.type !== 2 /* IDENTIFIER */) break;
|
|
1665
|
+
const key = this.tokens[this.current++].value;
|
|
1666
|
+
if (this.tokens[this.current]?.type === 4 /* EQUALS */) {
|
|
1667
|
+
this.current++;
|
|
1668
|
+
} else {
|
|
1669
|
+
this.errors.push(`Expected '=' after navigation argument key '${key}'`);
|
|
1670
|
+
}
|
|
1671
|
+
const value = this.parseExpression();
|
|
1672
|
+
args.push({ key, value });
|
|
1673
|
+
if (this.tokens[this.current]?.type === 17 /* COMMA */) {
|
|
1674
|
+
this.current++;
|
|
1675
|
+
} else {
|
|
1676
|
+
if (this.tokens[this.current]?.type !== 2 /* IDENTIFIER */) break;
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
return { type: "GoToStatement", target, args };
|
|
1681
|
+
}
|
|
1682
|
+
parseNavigateStatement() {
|
|
1683
|
+
this.current++;
|
|
1684
|
+
const tok = this.tokens[this.current];
|
|
1685
|
+
let target = "";
|
|
1686
|
+
if (tok?.type === 1 /* STRING */ || tok?.type === 2 /* IDENTIFIER */) {
|
|
1687
|
+
target = this.tokens[this.current++].value;
|
|
1688
|
+
}
|
|
1689
|
+
return { type: "NavigateStatement", target };
|
|
1690
|
+
}
|
|
1691
|
+
parseStateDeclaration() {
|
|
1692
|
+
this.current++;
|
|
1693
|
+
const name = this.tokens[this.current++].value;
|
|
1694
|
+
if (this.tokens[this.current]?.type === 4 /* EQUALS */) this.current++;
|
|
1695
|
+
const value = this.parseExpression();
|
|
1696
|
+
return { type: "StateDeclaration", name, value };
|
|
1697
|
+
}
|
|
1698
|
+
parseStateAssignment() {
|
|
1699
|
+
const name = this.tokens[this.current++].value;
|
|
1700
|
+
this.current++;
|
|
1701
|
+
const value = this.parseExpression();
|
|
1702
|
+
return { type: "StateAssignment", name, value };
|
|
1703
|
+
}
|
|
1704
|
+
// ─────────────────────────────────────────
|
|
1705
|
+
// Body parser
|
|
1706
|
+
// ─────────────────────────────────────────
|
|
1707
|
+
parseBody() {
|
|
1708
|
+
const nodes = this.parseStatements(
|
|
1709
|
+
() => this.isEOF() || this.tokens[this.current].type === 3 /* KEYWORD */ && this.tokens[this.current].value === "end" || this.tokens[this.current].type === 3 /* KEYWORD */ && this.tokens[this.current].value === "elseif" || this.tokens[this.current].type === 3 /* KEYWORD */ && this.tokens[this.current].value === "else" || this.tokens[this.current].type === 3 /* KEYWORD */ && this.tokens[this.current].value === "catch" || this.tokens[this.current].type === 3 /* KEYWORD */ && this.tokens[this.current].value === "case" || this.tokens[this.current].type === 3 /* KEYWORD */ && this.tokens[this.current].value === "default"
|
|
1710
|
+
);
|
|
1711
|
+
if (this.tokens[this.current]?.type === 3 /* KEYWORD */ && this.tokens[this.current]?.value === "end") {
|
|
1712
|
+
this.current++;
|
|
1713
|
+
}
|
|
1714
|
+
return nodes;
|
|
1715
|
+
}
|
|
1716
|
+
// ─────────────────────────────────────────
|
|
1717
|
+
// Expression parser — precedence climbing
|
|
1718
|
+
// ─────────────────────────────────────────
|
|
1719
|
+
parseExpression(minPrec = 0) {
|
|
1720
|
+
let left = this.parsePrimary();
|
|
1721
|
+
while (this.tokens[this.current]?.type === 25 /* LBRACKET */) {
|
|
1722
|
+
this.current++;
|
|
1723
|
+
const index = this.parseExpression(0);
|
|
1724
|
+
if (this.tokens[this.current]?.type === 26 /* RBRACKET */) {
|
|
1725
|
+
this.current++;
|
|
1726
|
+
}
|
|
1727
|
+
left = { type: "IndexExpression", object: left, index };
|
|
1728
|
+
}
|
|
1729
|
+
while (true) {
|
|
1730
|
+
const tok = this.tokens[this.current];
|
|
1731
|
+
if (!tok || !OPERATOR_TYPES.has(tok.type)) break;
|
|
1732
|
+
const prec = PRECEDENCE[tok.value] ?? 0;
|
|
1733
|
+
if (prec <= minPrec) break;
|
|
1734
|
+
const operator = tok.value;
|
|
1735
|
+
this.current++;
|
|
1736
|
+
const right = this.parseExpression(prec);
|
|
1737
|
+
left = {
|
|
1738
|
+
type: "BinaryExpression",
|
|
1739
|
+
left,
|
|
1740
|
+
operator,
|
|
1741
|
+
right
|
|
1742
|
+
};
|
|
1743
|
+
while (this.tokens[this.current]?.type === 25 /* LBRACKET */) {
|
|
1744
|
+
this.current++;
|
|
1745
|
+
const index = this.parseExpression(0);
|
|
1746
|
+
if (this.tokens[this.current]?.type === 26 /* RBRACKET */) {
|
|
1747
|
+
this.current++;
|
|
1748
|
+
}
|
|
1749
|
+
left = { type: "IndexExpression", object: left, index };
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
return left;
|
|
1753
|
+
}
|
|
1754
|
+
parsePrimary() {
|
|
1755
|
+
const tok = this.tokens[this.current];
|
|
1756
|
+
if (!tok || tok.type === 19 /* EOF */) {
|
|
1757
|
+
throw new Error("Unexpected end of input while parsing expression");
|
|
1758
|
+
}
|
|
1759
|
+
if (tok.type === 0 /* NUMBER */) {
|
|
1760
|
+
this.current++;
|
|
1761
|
+
return { type: "NumberLiteral", value: Number(tok.value) };
|
|
1762
|
+
}
|
|
1763
|
+
if (tok.type === 3 /* KEYWORD */ && (tok.value === "true" || tok.value === "false")) {
|
|
1764
|
+
this.current++;
|
|
1765
|
+
return { type: "BooleanLiteral", value: tok.value === "true" };
|
|
1766
|
+
}
|
|
1767
|
+
if (tok.type === 3 /* KEYWORD */ && tok.value === "null") {
|
|
1768
|
+
this.current++;
|
|
1769
|
+
return { type: "NullLiteral" };
|
|
1770
|
+
}
|
|
1771
|
+
if (tok.type === 3 /* KEYWORD */ && tok.value === "typeof") {
|
|
1772
|
+
this.current++;
|
|
1773
|
+
const operand = this.parsePrimary();
|
|
1774
|
+
return { type: "TypeofExpression", operand };
|
|
1775
|
+
}
|
|
1776
|
+
if (tok.type === 3 /* KEYWORD */ && tok.value === "await") {
|
|
1777
|
+
this.current++;
|
|
1778
|
+
const operand = this.parsePrimary();
|
|
1779
|
+
return { type: "AwaitExpression", operand };
|
|
1780
|
+
}
|
|
1781
|
+
if (tok.type === 25 /* LBRACKET */) {
|
|
1782
|
+
return this.parseArrayLiteral();
|
|
1783
|
+
}
|
|
1784
|
+
if (tok.type === 20 /* LBRACE */) {
|
|
1785
|
+
return this.parseObjectLiteral();
|
|
1786
|
+
}
|
|
1787
|
+
if (tok.type === 28 /* TEMPLATE_STRING */) {
|
|
1788
|
+
this.current++;
|
|
1789
|
+
return this.parseStringInterpolation(tok.value);
|
|
1790
|
+
}
|
|
1791
|
+
if (tok.type === 1 /* STRING */) {
|
|
1792
|
+
this.current++;
|
|
1793
|
+
return { type: "StringLiteral", value: tok.value };
|
|
1794
|
+
}
|
|
1795
|
+
if (tok.type === 2 /* IDENTIFIER */) {
|
|
1796
|
+
this.current++;
|
|
1797
|
+
if (this.tokens[this.current]?.type === 27 /* QUESTION_DOT */) {
|
|
1798
|
+
return this.parseOptionalChain(tok.value);
|
|
1799
|
+
}
|
|
1800
|
+
if (this.tokens[this.current]?.type === 18 /* DOT */) {
|
|
1801
|
+
this.current++;
|
|
1802
|
+
const property = this.tokens[this.current++].value;
|
|
1803
|
+
const dotted = `${tok.value}.${property}`;
|
|
1804
|
+
if (this.tokens[this.current]?.type === 15 /* LPAREN */) {
|
|
1805
|
+
const args = this.parseArgList();
|
|
1806
|
+
return { type: "CallExpression", callee: dotted, args };
|
|
1807
|
+
}
|
|
1808
|
+
return { type: "MemberExpression", object: tok.value, property };
|
|
1809
|
+
}
|
|
1810
|
+
if (this.tokens[this.current]?.type === 15 /* LPAREN */) {
|
|
1811
|
+
const args = this.parseArgList();
|
|
1812
|
+
return { type: "CallExpression", callee: tok.value, args };
|
|
1813
|
+
}
|
|
1814
|
+
if (this.tokens[this.current]?.type === 25 /* LBRACKET */) {
|
|
1815
|
+
this.current++;
|
|
1816
|
+
const index = this.parseExpression(0);
|
|
1817
|
+
if (this.tokens[this.current]?.type === 26 /* RBRACKET */) {
|
|
1818
|
+
this.current++;
|
|
1819
|
+
}
|
|
1820
|
+
return {
|
|
1821
|
+
type: "IndexExpression",
|
|
1822
|
+
object: { type: "Identifier", name: tok.value },
|
|
1823
|
+
index
|
|
1824
|
+
};
|
|
1825
|
+
}
|
|
1826
|
+
return { type: "Identifier", name: tok.value };
|
|
1827
|
+
}
|
|
1828
|
+
if (tok.type === 15 /* LPAREN */) {
|
|
1829
|
+
this.current++;
|
|
1830
|
+
const expr = this.parseExpression(0);
|
|
1831
|
+
if (this.tokens[this.current]?.type === 16 /* RPAREN */) {
|
|
1832
|
+
this.current++;
|
|
1833
|
+
}
|
|
1834
|
+
return expr;
|
|
1835
|
+
}
|
|
1836
|
+
throw new Error(
|
|
1837
|
+
`Unexpected token in expression: "${tok.value}" (type ${TokenType[tok.type]})`
|
|
1838
|
+
);
|
|
1839
|
+
}
|
|
1840
|
+
// ─────────────────────────────────────────
|
|
1841
|
+
// Data structure parsers
|
|
1842
|
+
// ─────────────────────────────────────────
|
|
1843
|
+
parseArrayLiteral() {
|
|
1844
|
+
this.current++;
|
|
1845
|
+
const elements = [];
|
|
1846
|
+
while (this.current < this.tokens.length && this.tokens[this.current].type !== 26 /* RBRACKET */ && this.tokens[this.current].type !== 19 /* EOF */) {
|
|
1847
|
+
if (this.tokens[this.current].type === 17 /* COMMA */) {
|
|
1848
|
+
this.current++;
|
|
1849
|
+
continue;
|
|
1850
|
+
}
|
|
1851
|
+
elements.push(this.parseExpression(0));
|
|
1852
|
+
}
|
|
1853
|
+
if (this.tokens[this.current]?.type === 26 /* RBRACKET */) {
|
|
1854
|
+
this.current++;
|
|
1855
|
+
}
|
|
1856
|
+
return { type: "ArrayLiteral", elements };
|
|
1857
|
+
}
|
|
1858
|
+
parseObjectLiteral() {
|
|
1859
|
+
this.current++;
|
|
1860
|
+
const properties = [];
|
|
1861
|
+
while (!this.isEOF() && this.tokens[this.current].type !== 21 /* RBRACE */) {
|
|
1862
|
+
let key;
|
|
1863
|
+
if (this.tokens[this.current].type === 2 /* IDENTIFIER */) {
|
|
1864
|
+
key = this.tokens[this.current++].value;
|
|
1865
|
+
} else if (this.tokens[this.current].type === 1 /* STRING */) {
|
|
1866
|
+
key = this.tokens[this.current++].value;
|
|
1867
|
+
} else {
|
|
1868
|
+
this.current++;
|
|
1869
|
+
continue;
|
|
1870
|
+
}
|
|
1871
|
+
if (this.tokens[this.current]?.type === 22 /* COLON */) {
|
|
1872
|
+
this.current++;
|
|
1873
|
+
}
|
|
1874
|
+
const value = this.parseExpression(0);
|
|
1875
|
+
properties.push({ key, value });
|
|
1876
|
+
if (this.tokens[this.current]?.type === 17 /* COMMA */) {
|
|
1877
|
+
this.current++;
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
if (this.tokens[this.current]?.type === 21 /* RBRACE */) {
|
|
1881
|
+
this.current++;
|
|
1882
|
+
}
|
|
1883
|
+
return { type: "ObjectLiteral", properties };
|
|
1884
|
+
}
|
|
1885
|
+
parseOptionalChain(baseName) {
|
|
1886
|
+
this.current++;
|
|
1887
|
+
const property = this.tokens[this.current++].value;
|
|
1888
|
+
if (this.tokens[this.current]?.type === 15 /* LPAREN */) {
|
|
1889
|
+
const args = this.parseArgList();
|
|
1890
|
+
return {
|
|
1891
|
+
type: "CallExpression",
|
|
1892
|
+
callee: `${baseName}?.${property}`,
|
|
1893
|
+
args
|
|
1894
|
+
};
|
|
1895
|
+
}
|
|
1896
|
+
if (this.tokens[this.current]?.type === 27 /* QUESTION_DOT */) {
|
|
1897
|
+
const rest = this.parseOptionalChain(`${baseName}?.${property}`);
|
|
1898
|
+
return rest;
|
|
1899
|
+
}
|
|
1900
|
+
if (this.tokens[this.current]?.type === 18 /* DOT */) {
|
|
1901
|
+
this.current++;
|
|
1902
|
+
const nextProp = this.tokens[this.current++].value;
|
|
1903
|
+
if (this.tokens[this.current]?.type === 15 /* LPAREN */) {
|
|
1904
|
+
const args = this.parseArgList();
|
|
1905
|
+
return {
|
|
1906
|
+
type: "CallExpression",
|
|
1907
|
+
callee: `${baseName}?.${property}.${nextProp}`,
|
|
1908
|
+
args
|
|
1909
|
+
};
|
|
1910
|
+
}
|
|
1911
|
+
return {
|
|
1912
|
+
type: "MemberExpression",
|
|
1913
|
+
object: `${baseName}?.${property}`,
|
|
1914
|
+
property: nextProp,
|
|
1915
|
+
optional: true
|
|
1916
|
+
};
|
|
1917
|
+
}
|
|
1918
|
+
return {
|
|
1919
|
+
type: "MemberExpression",
|
|
1920
|
+
object: baseName,
|
|
1921
|
+
property,
|
|
1922
|
+
optional: true
|
|
1923
|
+
};
|
|
1924
|
+
}
|
|
1925
|
+
parseStringInterpolation(raw) {
|
|
1926
|
+
const parts = [];
|
|
1927
|
+
let current = "";
|
|
1928
|
+
let i = 0;
|
|
1929
|
+
while (i < raw.length) {
|
|
1930
|
+
if (raw[i] === "{") {
|
|
1931
|
+
let depth = 1;
|
|
1932
|
+
let j = i + 1;
|
|
1933
|
+
while (j < raw.length && depth > 0) {
|
|
1934
|
+
if (raw[j] === "{") depth++;
|
|
1935
|
+
if (raw[j] === "}") depth--;
|
|
1936
|
+
j++;
|
|
1937
|
+
}
|
|
1938
|
+
if (depth === 0) {
|
|
1939
|
+
if (current.length > 0) {
|
|
1940
|
+
parts.push(current);
|
|
1941
|
+
current = "";
|
|
1942
|
+
}
|
|
1943
|
+
const exprStr = raw.slice(i + 1, j - 1);
|
|
1944
|
+
const subLexer = new (init_lexer(), __toCommonJS(lexer_exports)).Lexer();
|
|
1945
|
+
const subTokens = subLexer.tokenize(exprStr);
|
|
1946
|
+
const subParser = new _Parser(subTokens);
|
|
1947
|
+
try {
|
|
1948
|
+
const expr = subParser.parseExpression(0);
|
|
1949
|
+
parts.push(expr);
|
|
1950
|
+
} catch {
|
|
1951
|
+
parts.push(exprStr);
|
|
1952
|
+
}
|
|
1953
|
+
i = j;
|
|
1954
|
+
} else {
|
|
1955
|
+
current += raw[i];
|
|
1956
|
+
i++;
|
|
1957
|
+
}
|
|
1958
|
+
} else {
|
|
1959
|
+
current += raw[i];
|
|
1960
|
+
i++;
|
|
1961
|
+
}
|
|
1962
|
+
}
|
|
1963
|
+
if (current.length > 0) {
|
|
1964
|
+
parts.push(current);
|
|
1965
|
+
}
|
|
1966
|
+
return { type: "StringInterpolation", parts };
|
|
1967
|
+
}
|
|
1968
|
+
// ─────────────────────────────────────────
|
|
1969
|
+
// Generics type params
|
|
1970
|
+
// ─────────────────────────────────────────
|
|
1971
|
+
parseTypeParams() {
|
|
1972
|
+
this.current++;
|
|
1973
|
+
const params = [];
|
|
1974
|
+
while (this.current < this.tokens.length && this.tokens[this.current].type !== 9 /* GREATER_THAN */ && this.tokens[this.current].type !== 19 /* EOF */) {
|
|
1975
|
+
if (this.tokens[this.current].type === 17 /* COMMA */) {
|
|
1976
|
+
this.current++;
|
|
1977
|
+
continue;
|
|
1978
|
+
}
|
|
1979
|
+
params.push(this.tokens[this.current++].value);
|
|
1980
|
+
}
|
|
1981
|
+
if (this.tokens[this.current]?.type === 9 /* GREATER_THAN */) {
|
|
1982
|
+
this.current++;
|
|
1983
|
+
}
|
|
1984
|
+
return params;
|
|
1985
|
+
}
|
|
1986
|
+
// ─────────────────────────────────────────
|
|
1987
|
+
// Argument list
|
|
1988
|
+
// ─────────────────────────────────────────
|
|
1989
|
+
parseArgList() {
|
|
1990
|
+
const args = [];
|
|
1991
|
+
if (this.tokens[this.current]?.type !== 15 /* LPAREN */) {
|
|
1992
|
+
return args;
|
|
1993
|
+
}
|
|
1994
|
+
this.current++;
|
|
1995
|
+
while (this.current < this.tokens.length && this.tokens[this.current].type !== 16 /* RPAREN */ && this.tokens[this.current].type !== 19 /* EOF */) {
|
|
1996
|
+
if (this.tokens[this.current].type === 17 /* COMMA */) {
|
|
1997
|
+
this.current++;
|
|
1998
|
+
continue;
|
|
1999
|
+
}
|
|
2000
|
+
args.push(this.parseExpression(0));
|
|
2001
|
+
}
|
|
2002
|
+
if (this.tokens[this.current]?.type === 16 /* RPAREN */) {
|
|
2003
|
+
this.current++;
|
|
2004
|
+
}
|
|
2005
|
+
return args;
|
|
2006
|
+
}
|
|
2007
|
+
// ─────────────────────────────────────────
|
|
2008
|
+
// Utilities
|
|
2009
|
+
// ─────────────────────────────────────────
|
|
2010
|
+
isEOF() {
|
|
2011
|
+
return this.current >= this.tokens.length || this.tokens[this.current].type === 19 /* EOF */;
|
|
2012
|
+
}
|
|
2013
|
+
peek(offset) {
|
|
2014
|
+
return this.tokens[this.current + offset];
|
|
2015
|
+
}
|
|
2016
|
+
getCurrentToken() {
|
|
2017
|
+
return this.tokens[this.current];
|
|
2018
|
+
}
|
|
2019
|
+
};
|
|
2020
|
+
|
|
2021
|
+
// src/ide/language_service.ts
|
|
2022
|
+
init_token();
|
|
2023
|
+
|
|
2024
|
+
// src/checker/type_checker.ts
|
|
2025
|
+
var TypeChecker = class {
|
|
2026
|
+
constructor() {
|
|
2027
|
+
this.errors = [];
|
|
2028
|
+
// Maps: name -> type/info
|
|
2029
|
+
this.variables = /* @__PURE__ */ new Map();
|
|
2030
|
+
// name -> "number" | "string" | "boolean" | "list" | "object" | "null"
|
|
2031
|
+
this.constVars = /* @__PURE__ */ new Set();
|
|
2032
|
+
// names declared with const
|
|
2033
|
+
this.functions = /* @__PURE__ */ new Map();
|
|
2034
|
+
this.components = /* @__PURE__ */ new Map();
|
|
2035
|
+
this.screens = /* @__PURE__ */ new Map();
|
|
2036
|
+
this.enums = /* @__PURE__ */ new Map();
|
|
2037
|
+
}
|
|
2038
|
+
// enum name -> member names
|
|
2039
|
+
check(nodes, filename) {
|
|
2040
|
+
this.errors = [];
|
|
2041
|
+
this.variables.clear();
|
|
2042
|
+
this.constVars.clear();
|
|
2043
|
+
this.functions.clear();
|
|
2044
|
+
this.components.clear();
|
|
2045
|
+
this.screens.clear();
|
|
2046
|
+
this.enums.clear();
|
|
2047
|
+
for (const node of nodes) {
|
|
2048
|
+
this.collectDeclarations(node);
|
|
2049
|
+
}
|
|
2050
|
+
for (const node of nodes) {
|
|
2051
|
+
this.checkNode(node, filename);
|
|
2052
|
+
}
|
|
2053
|
+
return this.errors;
|
|
2054
|
+
}
|
|
2055
|
+
collectDeclarations(node) {
|
|
2056
|
+
switch (node.type) {
|
|
2057
|
+
case "VariableDeclaration":
|
|
2058
|
+
this.variables.set(node.name, node.dataType);
|
|
2059
|
+
if (node.isConst) {
|
|
2060
|
+
this.constVars.add(node.name);
|
|
2061
|
+
}
|
|
2062
|
+
break;
|
|
2063
|
+
case "FunctionDeclaration":
|
|
2064
|
+
this.functions.set(node.name, { arity: node.params.length });
|
|
2065
|
+
break;
|
|
2066
|
+
case "ComponentDeclaration":
|
|
2067
|
+
this.components.set(node.name, { arity: node.params.length });
|
|
2068
|
+
break;
|
|
2069
|
+
case "ScreenDeclaration":
|
|
2070
|
+
this.screens.set(node.name, { params: node.params || [] });
|
|
2071
|
+
break;
|
|
2072
|
+
case "ExportDeclaration":
|
|
2073
|
+
if (node.declaration) {
|
|
2074
|
+
const decl = node.declaration;
|
|
2075
|
+
if (decl.type === "FunctionDeclaration") {
|
|
2076
|
+
this.functions.set(decl.name, { arity: decl.params.length });
|
|
2077
|
+
} else if (decl.type === "ComponentDeclaration") {
|
|
2078
|
+
this.components.set(decl.name, { arity: decl.params.length });
|
|
2079
|
+
} else if (decl.type === "VariableDeclaration") {
|
|
2080
|
+
this.variables.set(decl.name, decl.dataType);
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
break;
|
|
2084
|
+
case "EnumDeclaration":
|
|
2085
|
+
this.enums.set(node.name, node.members);
|
|
2086
|
+
break;
|
|
2087
|
+
case "NamespaceDeclaration":
|
|
2088
|
+
for (const child of node.body) {
|
|
2089
|
+
this.collectDeclarations(child);
|
|
2090
|
+
}
|
|
2091
|
+
break;
|
|
2092
|
+
case "ComputedDeclaration":
|
|
2093
|
+
this.variables.set(node.name, this.inferExprType(node.value));
|
|
2094
|
+
break;
|
|
2095
|
+
case "StateDeclaration":
|
|
2096
|
+
this.variables.set(node.name, this.inferExprType(node.value));
|
|
2097
|
+
break;
|
|
2098
|
+
// Stage 9.6
|
|
2099
|
+
case "ThemeDeclaration":
|
|
2100
|
+
break;
|
|
2101
|
+
// themes are always valid
|
|
2102
|
+
case "DesignToken":
|
|
2103
|
+
this.variables.set(node.name, this.inferExprType(node.value));
|
|
2104
|
+
break;
|
|
2105
|
+
case "FormDeclaration":
|
|
2106
|
+
for (const child of node.body) {
|
|
2107
|
+
this.collectDeclarations(child);
|
|
2108
|
+
}
|
|
2109
|
+
break;
|
|
2110
|
+
// Stage 10 — Testing constructs
|
|
2111
|
+
case "DescribeBlock":
|
|
2112
|
+
for (const child of node.body) {
|
|
2113
|
+
this.collectDeclarations(child);
|
|
2114
|
+
}
|
|
2115
|
+
break;
|
|
2116
|
+
case "TestBlock":
|
|
2117
|
+
for (const child of node.body) {
|
|
2118
|
+
this.collectDeclarations(child);
|
|
2119
|
+
}
|
|
2120
|
+
break;
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
checkNode(node, filename) {
|
|
2124
|
+
switch (node.type) {
|
|
2125
|
+
case "VariableDeclaration": {
|
|
2126
|
+
const valueType = this.inferExprType(node.value);
|
|
2127
|
+
if (valueType !== "any" && node.dataType !== "any" && node.dataType !== "const" && valueType !== node.dataType) {
|
|
2128
|
+
this.errors.push({
|
|
2129
|
+
message: `Type mismatch: Cannot assign type '${valueType}' to variable '${node.name}' of type '${node.dataType}'`,
|
|
2130
|
+
file: filename,
|
|
2131
|
+
line: node.line
|
|
2132
|
+
});
|
|
2133
|
+
}
|
|
2134
|
+
break;
|
|
2135
|
+
}
|
|
2136
|
+
case "StateDeclaration": {
|
|
2137
|
+
const valType = this.inferExprType(node.value);
|
|
2138
|
+
this.variables.set(node.name, valType);
|
|
2139
|
+
break;
|
|
2140
|
+
}
|
|
2141
|
+
case "StateAssignment": {
|
|
2142
|
+
if (this.constVars.has(node.name)) {
|
|
2143
|
+
this.errors.push({
|
|
2144
|
+
message: `Cannot reassign constant '${node.name}'`,
|
|
2145
|
+
file: filename,
|
|
2146
|
+
line: node.line
|
|
2147
|
+
});
|
|
2148
|
+
}
|
|
2149
|
+
const varType = this.variables.get(node.name);
|
|
2150
|
+
if (varType) {
|
|
2151
|
+
const valType = this.inferExprType(node.value);
|
|
2152
|
+
if (varType !== "any" && valType !== "any" && varType !== valType) {
|
|
2153
|
+
this.errors.push({
|
|
2154
|
+
message: `Type mismatch: Cannot assign type '${valType}' to state variable '${node.name}' of type '${varType}'`,
|
|
2155
|
+
file: filename,
|
|
2156
|
+
line: node.line
|
|
2157
|
+
});
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2160
|
+
break;
|
|
2161
|
+
}
|
|
2162
|
+
case "FunctionCall": {
|
|
2163
|
+
const func = this.functions.get(node.callee);
|
|
2164
|
+
if (func) {
|
|
2165
|
+
if (node.args.length !== func.arity) {
|
|
2166
|
+
this.errors.push({
|
|
2167
|
+
message: `Arity mismatch: Function '${node.callee}' expects ${func.arity} arguments, but got ${node.args.length}`,
|
|
2168
|
+
file: filename,
|
|
2169
|
+
line: node.line
|
|
2170
|
+
});
|
|
2171
|
+
}
|
|
2172
|
+
}
|
|
2173
|
+
break;
|
|
2174
|
+
}
|
|
2175
|
+
case "ComponentCall": {
|
|
2176
|
+
const comp = this.components.get(node.name);
|
|
2177
|
+
if (comp) {
|
|
2178
|
+
if (node.args.length !== comp.arity) {
|
|
2179
|
+
this.errors.push({
|
|
2180
|
+
message: `Arity mismatch: Component '${node.name}' expects ${comp.arity} arguments, but got ${node.args.length}`,
|
|
2181
|
+
file: filename,
|
|
2182
|
+
line: node.line
|
|
2183
|
+
});
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
2186
|
+
break;
|
|
2187
|
+
}
|
|
2188
|
+
case "GoToStatement": {
|
|
2189
|
+
const screen = this.screens.get(node.target);
|
|
2190
|
+
if (screen) {
|
|
2191
|
+
const screenParams = screen.params;
|
|
2192
|
+
const passedArgs = node.args || [];
|
|
2193
|
+
for (const param of screenParams) {
|
|
2194
|
+
const passed = passedArgs.some((a) => a.key === param);
|
|
2195
|
+
if (!passed) {
|
|
2196
|
+
this.errors.push({
|
|
2197
|
+
message: `Navigation error: Missing required parameter '${param}' for screen '${node.target}'`,
|
|
2198
|
+
file: filename,
|
|
2199
|
+
line: node.line
|
|
2200
|
+
});
|
|
2201
|
+
}
|
|
2202
|
+
}
|
|
2203
|
+
for (const arg of passedArgs) {
|
|
2204
|
+
if (!screenParams.includes(arg.key)) {
|
|
2205
|
+
this.errors.push({
|
|
2206
|
+
message: `Navigation error: Screen '${node.target}' does not accept parameter '${arg.key}'`,
|
|
2207
|
+
file: filename,
|
|
2208
|
+
line: node.line
|
|
2209
|
+
});
|
|
2210
|
+
}
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2213
|
+
break;
|
|
2214
|
+
}
|
|
2215
|
+
case "FunctionDeclaration":
|
|
2216
|
+
for (const child of node.body) {
|
|
2217
|
+
this.checkNode(child, filename);
|
|
2218
|
+
}
|
|
2219
|
+
break;
|
|
2220
|
+
case "ComponentDeclaration":
|
|
2221
|
+
for (const child of node.body) {
|
|
2222
|
+
this.checkNode(child, filename);
|
|
2223
|
+
}
|
|
2224
|
+
break;
|
|
2225
|
+
case "ScreenDeclaration":
|
|
2226
|
+
if (node.params) {
|
|
2227
|
+
for (const p of node.params) {
|
|
2228
|
+
this.variables.set(p, "any");
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2231
|
+
for (const child of node.body) {
|
|
2232
|
+
this.checkNode(child, filename);
|
|
2233
|
+
}
|
|
2234
|
+
break;
|
|
2235
|
+
case "IfStatement":
|
|
2236
|
+
for (const child of node.body) {
|
|
2237
|
+
this.checkNode(child, filename);
|
|
2238
|
+
}
|
|
2239
|
+
if (node.elseifs) {
|
|
2240
|
+
for (const elseif of node.elseifs) {
|
|
2241
|
+
for (const child of elseif.body) {
|
|
2242
|
+
this.checkNode(child, filename);
|
|
2243
|
+
}
|
|
2244
|
+
}
|
|
2245
|
+
}
|
|
2246
|
+
if (node.elseBody) {
|
|
2247
|
+
for (const child of node.elseBody) {
|
|
2248
|
+
this.checkNode(child, filename);
|
|
2249
|
+
}
|
|
2250
|
+
}
|
|
2251
|
+
break;
|
|
2252
|
+
case "RepeatStatement":
|
|
2253
|
+
for (const child of node.body) {
|
|
2254
|
+
this.checkNode(child, filename);
|
|
2255
|
+
}
|
|
2256
|
+
break;
|
|
2257
|
+
case "WhileStatement":
|
|
2258
|
+
for (const child of node.body) {
|
|
2259
|
+
this.checkNode(child, filename);
|
|
2260
|
+
}
|
|
2261
|
+
break;
|
|
2262
|
+
case "ForInStatement":
|
|
2263
|
+
this.variables.set(node.variable, "any");
|
|
2264
|
+
for (const child of node.body) {
|
|
2265
|
+
this.checkNode(child, filename);
|
|
2266
|
+
}
|
|
2267
|
+
break;
|
|
2268
|
+
case "TryCatchStatement":
|
|
2269
|
+
for (const child of node.body) {
|
|
2270
|
+
this.checkNode(child, filename);
|
|
2271
|
+
}
|
|
2272
|
+
this.variables.set(node.catchParam, "any");
|
|
2273
|
+
for (const child of node.catchBody) {
|
|
2274
|
+
this.checkNode(child, filename);
|
|
2275
|
+
}
|
|
2276
|
+
break;
|
|
2277
|
+
case "SwitchStatement":
|
|
2278
|
+
for (const clause of node.cases) {
|
|
2279
|
+
for (const child of clause.body) {
|
|
2280
|
+
this.checkNode(child, filename);
|
|
2281
|
+
}
|
|
2282
|
+
}
|
|
2283
|
+
if (node.defaultBody) {
|
|
2284
|
+
for (const child of node.defaultBody) {
|
|
2285
|
+
this.checkNode(child, filename);
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
2288
|
+
break;
|
|
2289
|
+
case "NamespaceDeclaration":
|
|
2290
|
+
for (const child of node.body) {
|
|
2291
|
+
this.checkNode(child, filename);
|
|
2292
|
+
}
|
|
2293
|
+
break;
|
|
2294
|
+
case "MiddlewareDeclaration":
|
|
2295
|
+
for (const child of node.body) {
|
|
2296
|
+
this.checkNode(child, filename);
|
|
2297
|
+
}
|
|
2298
|
+
break;
|
|
2299
|
+
case "UIElement":
|
|
2300
|
+
if (node.children) {
|
|
2301
|
+
for (const child of node.children) {
|
|
2302
|
+
this.checkNode(child, filename);
|
|
2303
|
+
}
|
|
2304
|
+
}
|
|
2305
|
+
if (node.events) {
|
|
2306
|
+
for (const ev of node.events) {
|
|
2307
|
+
for (const child of ev.body) {
|
|
2308
|
+
this.checkNode(child, filename);
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
2311
|
+
}
|
|
2312
|
+
if (node.bindTarget && !this.variables.has(node.bindTarget)) {
|
|
2313
|
+
this.errors.push({
|
|
2314
|
+
message: `Bind target '${node.bindTarget}' is not a declared variable`,
|
|
2315
|
+
file: filename,
|
|
2316
|
+
line: node.line
|
|
2317
|
+
});
|
|
2318
|
+
}
|
|
2319
|
+
break;
|
|
2320
|
+
// Stage 9.6 — UI Framework checking
|
|
2321
|
+
case "FormDeclaration":
|
|
2322
|
+
for (const child of node.body) {
|
|
2323
|
+
this.checkNode(child, filename);
|
|
2324
|
+
}
|
|
2325
|
+
break;
|
|
2326
|
+
case "ThemeDeclaration":
|
|
2327
|
+
break;
|
|
2328
|
+
case "LifecycleBlock":
|
|
2329
|
+
for (const child of node.body) {
|
|
2330
|
+
this.checkNode(child, filename);
|
|
2331
|
+
}
|
|
2332
|
+
break;
|
|
2333
|
+
case "ShowIfStatement":
|
|
2334
|
+
for (const child of node.body) {
|
|
2335
|
+
this.checkNode(child, filename);
|
|
2336
|
+
}
|
|
2337
|
+
break;
|
|
2338
|
+
case "ErrorBoundary":
|
|
2339
|
+
for (const child of node.fallback) {
|
|
2340
|
+
this.checkNode(child, filename);
|
|
2341
|
+
}
|
|
2342
|
+
break;
|
|
2343
|
+
case "ResponsiveBlock":
|
|
2344
|
+
for (const child of node.body) {
|
|
2345
|
+
this.checkNode(child, filename);
|
|
2346
|
+
}
|
|
2347
|
+
break;
|
|
2348
|
+
case "AnimateBlock":
|
|
2349
|
+
break;
|
|
2350
|
+
case "SlotDeclaration":
|
|
2351
|
+
break;
|
|
2352
|
+
case "DesignToken":
|
|
2353
|
+
break;
|
|
2354
|
+
// Stage 10 — Testing framework
|
|
2355
|
+
case "DescribeBlock":
|
|
2356
|
+
for (const child of node.body) {
|
|
2357
|
+
this.checkNode(child, filename);
|
|
2358
|
+
}
|
|
2359
|
+
break;
|
|
2360
|
+
case "TestBlock":
|
|
2361
|
+
for (const child of node.body) {
|
|
2362
|
+
this.checkNode(child, filename);
|
|
2363
|
+
}
|
|
2364
|
+
break;
|
|
2365
|
+
case "AssertStatement":
|
|
2366
|
+
break;
|
|
2367
|
+
}
|
|
2368
|
+
}
|
|
2369
|
+
inferExprType(expr) {
|
|
2370
|
+
switch (expr.type) {
|
|
2371
|
+
case "NumberLiteral":
|
|
2372
|
+
return "number";
|
|
2373
|
+
case "StringLiteral":
|
|
2374
|
+
return "string";
|
|
2375
|
+
case "BooleanLiteral":
|
|
2376
|
+
return "boolean";
|
|
2377
|
+
case "NullLiteral":
|
|
2378
|
+
return "null";
|
|
2379
|
+
case "ArrayLiteral":
|
|
2380
|
+
return "list";
|
|
2381
|
+
case "ObjectLiteral":
|
|
2382
|
+
return "object";
|
|
2383
|
+
case "StringInterpolation":
|
|
2384
|
+
return "string";
|
|
2385
|
+
case "Identifier":
|
|
2386
|
+
return this.variables.get(expr.name) || "any";
|
|
2387
|
+
case "TypeofExpression":
|
|
2388
|
+
return "string";
|
|
2389
|
+
case "MemberExpression":
|
|
2390
|
+
return "any";
|
|
2391
|
+
case "IndexExpression":
|
|
2392
|
+
return "any";
|
|
2393
|
+
case "AwaitExpression":
|
|
2394
|
+
return "any";
|
|
2395
|
+
case "CallExpression":
|
|
2396
|
+
return "any";
|
|
2397
|
+
case "BinaryExpression": {
|
|
2398
|
+
const leftType = this.inferExprType(expr.left);
|
|
2399
|
+
const rightType = this.inferExprType(expr.right);
|
|
2400
|
+
if (leftType === "number" && rightType === "number") {
|
|
2401
|
+
return "number";
|
|
2402
|
+
}
|
|
2403
|
+
if (expr.operator === "+") {
|
|
2404
|
+
if (leftType === "string" || rightType === "string") {
|
|
2405
|
+
return "string";
|
|
2406
|
+
}
|
|
2407
|
+
}
|
|
2408
|
+
if (["==", "!=", "<", ">", "<=", ">="].includes(expr.operator)) {
|
|
2409
|
+
return "boolean";
|
|
2410
|
+
}
|
|
2411
|
+
return "any";
|
|
2412
|
+
}
|
|
2413
|
+
case "TernaryExpression":
|
|
2414
|
+
return this.inferExprType(expr.consequent);
|
|
2415
|
+
default:
|
|
2416
|
+
return "any";
|
|
2417
|
+
}
|
|
2418
|
+
}
|
|
2419
|
+
};
|
|
2420
|
+
|
|
2421
|
+
// src/ide/language_service.ts
|
|
2422
|
+
var STYLE_PROPERTIES = [
|
|
2423
|
+
"color",
|
|
2424
|
+
"background-color",
|
|
2425
|
+
"font-size",
|
|
2426
|
+
"font-weight",
|
|
2427
|
+
"padding",
|
|
2428
|
+
"margin",
|
|
2429
|
+
"border-radius",
|
|
2430
|
+
"width",
|
|
2431
|
+
"height",
|
|
2432
|
+
"display",
|
|
2433
|
+
"flex-direction",
|
|
2434
|
+
"align-items",
|
|
2435
|
+
"justify-content",
|
|
2436
|
+
"border",
|
|
2437
|
+
"opacity"
|
|
2438
|
+
];
|
|
2439
|
+
var BACKEND_KEYWORDS = [
|
|
2440
|
+
"route",
|
|
2441
|
+
"method",
|
|
2442
|
+
"auth",
|
|
2443
|
+
"required",
|
|
2444
|
+
"end",
|
|
2445
|
+
"middleware",
|
|
2446
|
+
"GET",
|
|
2447
|
+
"POST",
|
|
2448
|
+
"PUT",
|
|
2449
|
+
"DELETE",
|
|
2450
|
+
"PATCH"
|
|
2451
|
+
];
|
|
2452
|
+
var GLOBAL_KEYWORDS = [
|
|
2453
|
+
"number",
|
|
2454
|
+
"string",
|
|
2455
|
+
"boolean",
|
|
2456
|
+
"log",
|
|
2457
|
+
"if",
|
|
2458
|
+
"elseif",
|
|
2459
|
+
"else",
|
|
2460
|
+
"end",
|
|
2461
|
+
"repeat",
|
|
2462
|
+
"times",
|
|
2463
|
+
"for",
|
|
2464
|
+
"in",
|
|
2465
|
+
"while",
|
|
2466
|
+
"break",
|
|
2467
|
+
"continue",
|
|
2468
|
+
"function",
|
|
2469
|
+
"return",
|
|
2470
|
+
"import",
|
|
2471
|
+
"export",
|
|
2472
|
+
"from",
|
|
2473
|
+
"style",
|
|
2474
|
+
"class",
|
|
2475
|
+
"extends",
|
|
2476
|
+
"backend",
|
|
2477
|
+
"const",
|
|
2478
|
+
"true",
|
|
2479
|
+
"false",
|
|
2480
|
+
"null",
|
|
2481
|
+
"async",
|
|
2482
|
+
"await",
|
|
2483
|
+
"try",
|
|
2484
|
+
"catch",
|
|
2485
|
+
"enum",
|
|
2486
|
+
"switch",
|
|
2487
|
+
"case",
|
|
2488
|
+
"default",
|
|
2489
|
+
"typeof",
|
|
2490
|
+
"computed",
|
|
2491
|
+
"namespace",
|
|
2492
|
+
"middleware",
|
|
2493
|
+
"object",
|
|
2494
|
+
"list",
|
|
2495
|
+
"state",
|
|
2496
|
+
"component",
|
|
2497
|
+
"screen",
|
|
2498
|
+
"text",
|
|
2499
|
+
"button",
|
|
2500
|
+
"input",
|
|
2501
|
+
"view",
|
|
2502
|
+
// Stage 9.6 — UI Framework keywords
|
|
2503
|
+
"textarea",
|
|
2504
|
+
"icon",
|
|
2505
|
+
"card",
|
|
2506
|
+
"divider",
|
|
2507
|
+
"avatar",
|
|
2508
|
+
"badge",
|
|
2509
|
+
"checkbox",
|
|
2510
|
+
"toggle",
|
|
2511
|
+
"radio",
|
|
2512
|
+
"dropdown",
|
|
2513
|
+
"select",
|
|
2514
|
+
"form",
|
|
2515
|
+
"modal",
|
|
2516
|
+
"dialog",
|
|
2517
|
+
"sheet",
|
|
2518
|
+
"tabs",
|
|
2519
|
+
"tab",
|
|
2520
|
+
"grid",
|
|
2521
|
+
"video",
|
|
2522
|
+
"audio",
|
|
2523
|
+
"webview",
|
|
2524
|
+
"scroll",
|
|
2525
|
+
"stack",
|
|
2526
|
+
"center",
|
|
2527
|
+
"theme",
|
|
2528
|
+
"animate",
|
|
2529
|
+
"bind",
|
|
2530
|
+
"show",
|
|
2531
|
+
"slot",
|
|
2532
|
+
"token",
|
|
2533
|
+
"errorBoundary",
|
|
2534
|
+
"fallback",
|
|
2535
|
+
"submit",
|
|
2536
|
+
"mount",
|
|
2537
|
+
"unmount",
|
|
2538
|
+
"focus",
|
|
2539
|
+
"mobile",
|
|
2540
|
+
"tablet",
|
|
2541
|
+
"desktop",
|
|
2542
|
+
"ariaLabel",
|
|
2543
|
+
"accessibilityRole",
|
|
2544
|
+
"on",
|
|
2545
|
+
"navigate",
|
|
2546
|
+
"go",
|
|
2547
|
+
"app",
|
|
2548
|
+
"placeholder",
|
|
2549
|
+
// Stage 10 — Testing framework keywords
|
|
2550
|
+
"describe",
|
|
2551
|
+
"test",
|
|
2552
|
+
"assert",
|
|
2553
|
+
"expect",
|
|
2554
|
+
"should",
|
|
2555
|
+
"equal",
|
|
2556
|
+
"contain",
|
|
2557
|
+
"throws",
|
|
2558
|
+
"toBeTruthy",
|
|
2559
|
+
"toBeFalsy",
|
|
2560
|
+
"not",
|
|
2561
|
+
"toBe",
|
|
2562
|
+
"typeOf"
|
|
2563
|
+
];
|
|
2564
|
+
var MODULE_MEMBERS = {
|
|
2565
|
+
math: ["add", "subtract", "multiply", "divide", "square", "PI"],
|
|
2566
|
+
request: ["body", "query", "params", "headers"]
|
|
2567
|
+
};
|
|
2568
|
+
var HOVER_DOCS = {
|
|
2569
|
+
math: "Standard math module containing trigonometric and arithmetic operations.",
|
|
2570
|
+
"math.add": "math.add(a, b): Returns the sum of two numbers.",
|
|
2571
|
+
"math.subtract": "math.subtract(a, b): Returns the difference of two numbers.",
|
|
2572
|
+
"math.multiply": "math.multiply(a, b): Returns the product of two numbers.",
|
|
2573
|
+
"math.divide": "math.divide(a, b): Returns the quotient of two numbers.",
|
|
2574
|
+
"math.square": "math.square(n): Returns the square of a number.",
|
|
2575
|
+
"math.PI": "math.PI: The mathematical constant PI (approx. 3.14159).",
|
|
2576
|
+
request: "API route Request object containing body, query, params, and headers.",
|
|
2577
|
+
"request.body": "JSON body payload sent with the request.",
|
|
2578
|
+
"request.query": "Query parameters in the request URL.",
|
|
2579
|
+
"request.params": "Route path parameters (e.g. /users/:id).",
|
|
2580
|
+
"request.headers": "HTTP headers sent with the request.",
|
|
2581
|
+
number: "Declares a double-precision float variable.",
|
|
2582
|
+
string: "Declares a UTF-8 character string variable.",
|
|
2583
|
+
boolean: "Declares a boolean variable (true or false).",
|
|
2584
|
+
const: "Declares an immutable constant that cannot be reassigned.",
|
|
2585
|
+
null: "Represents the absence of a value.",
|
|
2586
|
+
true: "Boolean literal representing a truthy value.",
|
|
2587
|
+
false: "Boolean literal representing a falsy value.",
|
|
2588
|
+
log: "Logs an expression value to the standard output / console.",
|
|
2589
|
+
if: "Conditional statement: `if condition ... elseif ... else ... end`.",
|
|
2590
|
+
elseif: "Additional condition branch inside an if block.",
|
|
2591
|
+
else: "Fallback branch executed when no if/elseif conditions are true.",
|
|
2592
|
+
for: "Loop construct: `for item in list body ... end`.",
|
|
2593
|
+
in: "Used with for loops: `for item in collection`.",
|
|
2594
|
+
while: "Loop that continues while a condition is true: `while cond body ... end`.",
|
|
2595
|
+
break: "Immediately exits the innermost loop.",
|
|
2596
|
+
continue: "Skips to the next iteration of the innermost loop.",
|
|
2597
|
+
repeat: "Loop shorthand: `repeat 5 times body ... end` or `repeat item in list`.",
|
|
2598
|
+
function: "Declares a reusable function: `function name(params) body ... end`.",
|
|
2599
|
+
return: "Returns a value from a function or route handler.",
|
|
2600
|
+
async: "Declares an asynchronous function: `async function name() body ... end`.",
|
|
2601
|
+
await: "Pauses execution until a Promise resolves inside an async function.",
|
|
2602
|
+
try: "Begins a try/catch error-handling block: `try ... catch err ... end`.",
|
|
2603
|
+
catch: "Catches errors thrown inside a try block.",
|
|
2604
|
+
enum: "Declares an enumeration: `enum Name members ... end`.",
|
|
2605
|
+
switch: "Multi-branch selection: `switch expr case val ... default ... end`.",
|
|
2606
|
+
case: "Matches a value inside a switch statement.",
|
|
2607
|
+
default: "Fallback branch inside a switch statement.",
|
|
2608
|
+
typeof: "Returns the type of an expression as a string.",
|
|
2609
|
+
computed: "Declares a derived/computed value: `computed name = expr`.",
|
|
2610
|
+
namespace: "Groups related declarations: `namespace Name body ... end`.",
|
|
2611
|
+
middleware: "Declares Express middleware: `middleware name body ... end`.",
|
|
2612
|
+
state: "Declares reactive UI state: `state name = value`.",
|
|
2613
|
+
component: "Declares a reusable UI component.",
|
|
2614
|
+
screen: "Declares a top-level UI screen / page.",
|
|
2615
|
+
backend: "Declares a backend microservice configuration block.",
|
|
2616
|
+
route: "Defines an HTTP route endpoint inside a backend microservice.",
|
|
2617
|
+
style: "Defines inline styles or stylesheet classes.",
|
|
2618
|
+
class: "Applies a styling class reference to a UI element.",
|
|
2619
|
+
text: "UI Element: Renders a span text label.",
|
|
2620
|
+
button: "UI Element: Renders a clickable button component.",
|
|
2621
|
+
input: "UI Element: Renders an interactive text input form field.",
|
|
2622
|
+
view: "UI Element: Renders a container block layout (div).",
|
|
2623
|
+
import: 'Imports a module: `import module from path` or `import { A, B } from "path"`.',
|
|
2624
|
+
export: "Exports a declaration for use by other modules.",
|
|
2625
|
+
// Stage 9.6 — UI Framework docs
|
|
2626
|
+
textarea: "UI Element: Renders a multi-line text input area.",
|
|
2627
|
+
icon: 'UI Element: Renders a named icon (e.g. `icon "home"`).',
|
|
2628
|
+
card: "UI Element: Renders a card with title and body.",
|
|
2629
|
+
divider: "UI Element: Renders a horizontal divider/separator.",
|
|
2630
|
+
avatar: "UI Element: Renders a user avatar image.",
|
|
2631
|
+
badge: "UI Element: Renders a small badge/label.",
|
|
2632
|
+
checkbox: "UI Element: Renders a checkbox input. Use with `bind` for two-way binding.",
|
|
2633
|
+
toggle: "UI Element: Renders a toggle/switch control.",
|
|
2634
|
+
radio: "UI Element: Renders a radio button input.",
|
|
2635
|
+
dropdown: "UI Element: Renders a dropdown select menu.",
|
|
2636
|
+
select: "UI Element: Renders a selection list.",
|
|
2637
|
+
form: "Declares a form container: `form Name ... end`.",
|
|
2638
|
+
modal: "UI Element: Renders a modal dialog overlay.",
|
|
2639
|
+
dialog: "UI Element: Renders a dialog/popup.",
|
|
2640
|
+
sheet: "UI Element: Renders a bottom sheet overlay.",
|
|
2641
|
+
tabs: "UI Element: Renders a tabbed container.",
|
|
2642
|
+
tab: "UI Element: Renders a single tab inside `tabs`.",
|
|
2643
|
+
grid: "UI Element: Renders a CSS grid layout container.",
|
|
2644
|
+
video: "UI Element: Renders a video player.",
|
|
2645
|
+
audio: "UI Element: Renders an audio player.",
|
|
2646
|
+
webview: "UI Element: Renders an embedded web view / iframe.",
|
|
2647
|
+
scroll: "UI Element: Renders a scrollable container.",
|
|
2648
|
+
stack: "UI Element: Renders a vertical stack layout.",
|
|
2649
|
+
center: "UI Element: Renders a centered flex container.",
|
|
2650
|
+
theme: "Declares a theme: `theme Name key: value ... end`.",
|
|
2651
|
+
animate: "Declares animation properties: `animate duration: 300 ... end`.",
|
|
2652
|
+
bind: "Creates two-way data binding: `input bind varName`.",
|
|
2653
|
+
show: "Conditional UI rendering: `show if condition body ... end`.",
|
|
2654
|
+
slot: "Marks where children are rendered inside a component.",
|
|
2655
|
+
token: "Declares a design token: `token name = value`.",
|
|
2656
|
+
errorBoundary: "Declares an error boundary with fallback UI.",
|
|
2657
|
+
fallback: "The fallback UI inside an errorBoundary.",
|
|
2658
|
+
mount: "Lifecycle event: `on mount body ... end` (runs when screen mounts).",
|
|
2659
|
+
unmount: "Lifecycle event: `on unmount body ... end` (runs when screen unmounts).",
|
|
2660
|
+
focus: "Lifecycle event: `on focus body ... end` (runs when screen gains focus).",
|
|
2661
|
+
mobile: "Responsive condition: renders body only on mobile screens.",
|
|
2662
|
+
tablet: "Responsive condition: renders body only on tablet screens.",
|
|
2663
|
+
desktop: "Responsive condition: renders body only on desktop screens.",
|
|
2664
|
+
ariaLabel: "Accessibility property for screen readers.",
|
|
2665
|
+
accessibilityRole: "Accessibility role property (button, link, etc.).",
|
|
2666
|
+
// Stage 10 — Testing framework docs
|
|
2667
|
+
describe: 'Groups related test cases: `describe "name" body ... end`.',
|
|
2668
|
+
test: 'Defines a test case: `test "description" body ... end`.',
|
|
2669
|
+
assert: "Asserts a condition: `assert expr == value`, `assert expr`, `assert throws fn`, etc.",
|
|
2670
|
+
expect: "Alternative assertion syntax.",
|
|
2671
|
+
should: "BDD-style assertion keyword.",
|
|
2672
|
+
equal: "Assert equality: `assert a equal b`.",
|
|
2673
|
+
contain: "Assert containment: `assert list contain item`.",
|
|
2674
|
+
throws: "Assert that a function throws: `assert throws fn`.",
|
|
2675
|
+
toBeTruthy: "Assert that a value is truthy.",
|
|
2676
|
+
toBeFalsy: "Assert that a value is falsy.",
|
|
2677
|
+
typeOf: 'Assert the type of a value: `assert x typeOf "string"`.'
|
|
2678
|
+
};
|
|
2679
|
+
var KinLanguageService = class {
|
|
2680
|
+
constructor() {
|
|
2681
|
+
this.lexer = new Lexer();
|
|
2682
|
+
}
|
|
2683
|
+
/**
|
|
2684
|
+
* Run compilation diagnostics on a file's source code.
|
|
2685
|
+
*/
|
|
2686
|
+
getDiagnostics(source) {
|
|
2687
|
+
const diagnostics = [];
|
|
2688
|
+
try {
|
|
2689
|
+
const tokens = this.lexer.tokenize(source);
|
|
2690
|
+
const parser = new Parser(tokens);
|
|
2691
|
+
const ast = parser.parse();
|
|
2692
|
+
const typeChecker = new TypeChecker();
|
|
2693
|
+
const typeErrors = typeChecker.check(ast);
|
|
2694
|
+
for (const err of parser.errors) {
|
|
2695
|
+
diagnostics.push({
|
|
2696
|
+
message: err,
|
|
2697
|
+
line: 1,
|
|
2698
|
+
character: 1,
|
|
2699
|
+
severity: "error"
|
|
2700
|
+
});
|
|
2701
|
+
}
|
|
2702
|
+
for (const err of typeErrors) {
|
|
2703
|
+
diagnostics.push({
|
|
2704
|
+
message: err.message,
|
|
2705
|
+
line: err.line ?? 1,
|
|
2706
|
+
character: 1,
|
|
2707
|
+
severity: "error"
|
|
2708
|
+
});
|
|
2709
|
+
}
|
|
2710
|
+
} catch (e) {
|
|
2711
|
+
const tokens = this.lexer.tokenize(source);
|
|
2712
|
+
const parser = new Parser(tokens);
|
|
2713
|
+
try {
|
|
2714
|
+
parser.parse();
|
|
2715
|
+
} catch (_) {
|
|
2716
|
+
}
|
|
2717
|
+
const currentTok = parser.getCurrentToken() || tokens[tokens.length - 1];
|
|
2718
|
+
const startOffset = currentTok?.start ?? source.length;
|
|
2719
|
+
const { line, character } = this.getLineCol(source, startOffset);
|
|
2720
|
+
diagnostics.push({
|
|
2721
|
+
message: e.message || String(e),
|
|
2722
|
+
line,
|
|
2723
|
+
character,
|
|
2724
|
+
severity: "error"
|
|
2725
|
+
});
|
|
2726
|
+
}
|
|
2727
|
+
return diagnostics;
|
|
2728
|
+
}
|
|
2729
|
+
/**
|
|
2730
|
+
* Suggest autocompletions based on cursor coordinates.
|
|
2731
|
+
*/
|
|
2732
|
+
getAutocomplete(source, line, character) {
|
|
2733
|
+
const tokens = this.lexer.tokenize(source);
|
|
2734
|
+
const offset = this.getOffset(source, line, character);
|
|
2735
|
+
let activeIndex = -1;
|
|
2736
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
2737
|
+
const t = tokens[i];
|
|
2738
|
+
if (t.start !== void 0 && t.length !== void 0) {
|
|
2739
|
+
if (t.start <= offset && offset <= t.start + t.length) {
|
|
2740
|
+
activeIndex = i;
|
|
2741
|
+
break;
|
|
2742
|
+
}
|
|
2743
|
+
}
|
|
2744
|
+
}
|
|
2745
|
+
if (activeIndex === -1) {
|
|
2746
|
+
for (let i = tokens.length - 1; i >= 0; i--) {
|
|
2747
|
+
const t = tokens[i];
|
|
2748
|
+
if (t.start !== void 0 && t.start < offset) {
|
|
2749
|
+
activeIndex = i;
|
|
2750
|
+
break;
|
|
2751
|
+
}
|
|
2752
|
+
}
|
|
2753
|
+
}
|
|
2754
|
+
if (activeIndex === -1) {
|
|
2755
|
+
return this.toCompletions(GLOBAL_KEYWORDS, "keyword");
|
|
2756
|
+
}
|
|
2757
|
+
let dotIndex = -1;
|
|
2758
|
+
if (tokens[activeIndex]?.type === 18 /* DOT */) {
|
|
2759
|
+
dotIndex = activeIndex;
|
|
2760
|
+
} else if (tokens[activeIndex - 1]?.type === 18 /* DOT */) {
|
|
2761
|
+
dotIndex = activeIndex - 1;
|
|
2762
|
+
}
|
|
2763
|
+
if (dotIndex > 0) {
|
|
2764
|
+
const prev = tokens[dotIndex - 1];
|
|
2765
|
+
if (prev && prev.type === 2 /* IDENTIFIER */) {
|
|
2766
|
+
const objName = prev.value;
|
|
2767
|
+
let fullPath = objName;
|
|
2768
|
+
if (dotIndex > 2 && tokens[dotIndex - 2]?.type === 18 /* DOT */) {
|
|
2769
|
+
const baseObj = tokens[dotIndex - 3];
|
|
2770
|
+
if (baseObj && baseObj.type === 2 /* IDENTIFIER */) {
|
|
2771
|
+
fullPath = `${baseObj.value}.${objName}`;
|
|
2772
|
+
}
|
|
2773
|
+
}
|
|
2774
|
+
const members = MODULE_MEMBERS[fullPath];
|
|
2775
|
+
if (members) {
|
|
2776
|
+
return this.toCompletions(members, "property");
|
|
2777
|
+
}
|
|
2778
|
+
}
|
|
2779
|
+
return [];
|
|
2780
|
+
}
|
|
2781
|
+
let isInsideStyle = false;
|
|
2782
|
+
let braceCount = 0;
|
|
2783
|
+
for (let i = activeIndex; i >= 0; i--) {
|
|
2784
|
+
const tok = tokens[i];
|
|
2785
|
+
if (tok.type === 21 /* RBRACE */) {
|
|
2786
|
+
braceCount++;
|
|
2787
|
+
} else if (tok.type === 20 /* LBRACE */) {
|
|
2788
|
+
if (braceCount > 0) {
|
|
2789
|
+
braceCount--;
|
|
2790
|
+
} else {
|
|
2791
|
+
const prev = tokens[i - 1];
|
|
2792
|
+
if (prev && prev.type === 3 /* KEYWORD */ && prev.value === "style") {
|
|
2793
|
+
isInsideStyle = true;
|
|
2794
|
+
}
|
|
2795
|
+
break;
|
|
2796
|
+
}
|
|
2797
|
+
}
|
|
2798
|
+
}
|
|
2799
|
+
if (isInsideStyle) {
|
|
2800
|
+
return this.toCompletions(STYLE_PROPERTIES, "property");
|
|
2801
|
+
}
|
|
2802
|
+
let isInsideBackend = false;
|
|
2803
|
+
let backendEndCount = 0;
|
|
2804
|
+
for (let i = activeIndex; i >= 0; i--) {
|
|
2805
|
+
const tok = tokens[i];
|
|
2806
|
+
if (tok.type === 3 /* KEYWORD */ && tok.value === "end") {
|
|
2807
|
+
backendEndCount++;
|
|
2808
|
+
} else if (tok.type === 3 /* KEYWORD */ && tok.value === "backend") {
|
|
2809
|
+
if (backendEndCount > 0) {
|
|
2810
|
+
backendEndCount--;
|
|
2811
|
+
} else {
|
|
2812
|
+
isInsideBackend = true;
|
|
2813
|
+
break;
|
|
2814
|
+
}
|
|
2815
|
+
}
|
|
2816
|
+
}
|
|
2817
|
+
if (isInsideBackend) {
|
|
2818
|
+
return this.toCompletions(BACKEND_KEYWORDS, "keyword");
|
|
2819
|
+
}
|
|
2820
|
+
const completions = this.toCompletions(GLOBAL_KEYWORDS, "keyword");
|
|
2821
|
+
try {
|
|
2822
|
+
const parser = new Parser(tokens);
|
|
2823
|
+
const ast = parser.parse();
|
|
2824
|
+
for (const node of ast) {
|
|
2825
|
+
if (node.type === "VariableDeclaration") {
|
|
2826
|
+
const prefix = node.isConst ? "const" : node.dataType;
|
|
2827
|
+
completions.push({ label: node.name, kind: "variable", detail: `${prefix} variable` });
|
|
2828
|
+
} else if (node.type === "FunctionDeclaration") {
|
|
2829
|
+
const asyncTag = node.isAsync ? "async " : "";
|
|
2830
|
+
completions.push({ label: node.name, kind: "function", detail: `${asyncTag}function (${node.params.join(", ")})` });
|
|
2831
|
+
} else if (node.type === "EnumDeclaration") {
|
|
2832
|
+
completions.push({ label: node.name, kind: "variable", detail: "enum" });
|
|
2833
|
+
} else if (node.type === "NamespaceDeclaration") {
|
|
2834
|
+
completions.push({ label: node.name, kind: "module", detail: "namespace" });
|
|
2835
|
+
} else if (node.type === "ComputedDeclaration") {
|
|
2836
|
+
completions.push({ label: node.name, kind: "variable", detail: "computed value" });
|
|
2837
|
+
} else if (node.type === "StateDeclaration") {
|
|
2838
|
+
completions.push({ label: node.name, kind: "variable", detail: "state" });
|
|
2839
|
+
} else if (node.type === "ComponentDeclaration") {
|
|
2840
|
+
completions.push({ label: node.name, kind: "function", detail: "component" });
|
|
2841
|
+
} else if (node.type === "MiddlewareDeclaration") {
|
|
2842
|
+
completions.push({ label: node.name, kind: "function", detail: "middleware" });
|
|
2843
|
+
} else if (node.type === "ThemeDeclaration") {
|
|
2844
|
+
completions.push({ label: node.name, kind: "variable", detail: "theme" });
|
|
2845
|
+
} else if (node.type === "FormDeclaration") {
|
|
2846
|
+
completions.push({ label: node.name, kind: "variable", detail: "form" });
|
|
2847
|
+
} else if (node.type === "DesignToken") {
|
|
2848
|
+
completions.push({ label: node.name, kind: "variable", detail: "design token" });
|
|
2849
|
+
} else if (node.type === "DescribeBlock") {
|
|
2850
|
+
completions.push({ label: "describe", kind: "function", detail: "test suite" });
|
|
2851
|
+
} else if (node.type === "TestBlock") {
|
|
2852
|
+
completions.push({ label: "test", kind: "function", detail: "test case" });
|
|
2853
|
+
}
|
|
2854
|
+
}
|
|
2855
|
+
} catch (_) {
|
|
2856
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
2857
|
+
const tok = tokens[i];
|
|
2858
|
+
if (tok.type === 3 /* KEYWORD */ && (tok.value === "number" || tok.value === "string")) {
|
|
2859
|
+
const next = tokens[i + 1];
|
|
2860
|
+
if (next && next.type === 2 /* IDENTIFIER */) {
|
|
2861
|
+
completions.push({ label: next.value, kind: "variable" });
|
|
2862
|
+
}
|
|
2863
|
+
} else if (tok.type === 3 /* KEYWORD */ && tok.value === "function") {
|
|
2864
|
+
const next = tokens[i + 1];
|
|
2865
|
+
if (next && next.type === 2 /* IDENTIFIER */) {
|
|
2866
|
+
completions.push({ label: next.value, kind: "function" });
|
|
2867
|
+
}
|
|
2868
|
+
}
|
|
2869
|
+
}
|
|
2870
|
+
}
|
|
2871
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2872
|
+
return completions.filter((item) => {
|
|
2873
|
+
if (seen.has(item.label)) return false;
|
|
2874
|
+
seen.add(item.label);
|
|
2875
|
+
return true;
|
|
2876
|
+
});
|
|
2877
|
+
}
|
|
2878
|
+
/**
|
|
2879
|
+
* Return hover details for symbol under cursor.
|
|
2880
|
+
*/
|
|
2881
|
+
getHover(source, line, character) {
|
|
2882
|
+
const tokens = this.lexer.tokenize(source);
|
|
2883
|
+
const offset = this.getOffset(source, line, character);
|
|
2884
|
+
let activeTok = null;
|
|
2885
|
+
let activeIdx = -1;
|
|
2886
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
2887
|
+
const t = tokens[i];
|
|
2888
|
+
if (t.start !== void 0 && t.length !== void 0) {
|
|
2889
|
+
if (t.start <= offset && offset <= t.start + t.length) {
|
|
2890
|
+
activeTok = t;
|
|
2891
|
+
activeIdx = i;
|
|
2892
|
+
break;
|
|
2893
|
+
}
|
|
2894
|
+
}
|
|
2895
|
+
}
|
|
2896
|
+
if (!activeTok) return null;
|
|
2897
|
+
let symbol = activeTok.value;
|
|
2898
|
+
if (activeTok.type === 2 /* IDENTIFIER */) {
|
|
2899
|
+
if (activeIdx > 1 && tokens[activeIdx - 1]?.type === 18 /* DOT */) {
|
|
2900
|
+
const base = tokens[activeIdx - 2];
|
|
2901
|
+
if (base && base.type === 2 /* IDENTIFIER */) {
|
|
2902
|
+
symbol = `${base.value}.${symbol}`;
|
|
2903
|
+
if (activeIdx > 3 && tokens[activeIdx - 3]?.type === 18 /* DOT */) {
|
|
2904
|
+
const baseBase = tokens[activeIdx - 4];
|
|
2905
|
+
if (baseBase && baseBase.type === 2 /* IDENTIFIER */) {
|
|
2906
|
+
symbol = `${baseBase.value}.${symbol}`;
|
|
2907
|
+
}
|
|
2908
|
+
}
|
|
2909
|
+
}
|
|
2910
|
+
} else if (activeIdx < tokens.length - 2 && tokens[activeIdx + 1]?.type === 18 /* DOT */) {
|
|
2911
|
+
const prop = tokens[activeIdx + 2];
|
|
2912
|
+
if (prop && prop.type === 2 /* IDENTIFIER */) {
|
|
2913
|
+
symbol = `${symbol}.${prop.value}`;
|
|
2914
|
+
}
|
|
2915
|
+
}
|
|
2916
|
+
}
|
|
2917
|
+
const documentation = HOVER_DOCS[symbol];
|
|
2918
|
+
if (documentation) {
|
|
2919
|
+
return { contents: documentation };
|
|
2920
|
+
}
|
|
2921
|
+
return null;
|
|
2922
|
+
}
|
|
2923
|
+
// ─────────────────────────────────────────
|
|
2924
|
+
// Position Helpers
|
|
2925
|
+
// ─────────────────────────────────────────
|
|
2926
|
+
getLineCol(input, offset) {
|
|
2927
|
+
let line = 1;
|
|
2928
|
+
let character = 1;
|
|
2929
|
+
for (let i = 0; i < Math.min(offset, input.length); i++) {
|
|
2930
|
+
if (input[i] === "\n") {
|
|
2931
|
+
line++;
|
|
2932
|
+
character = 1;
|
|
2933
|
+
} else {
|
|
2934
|
+
character++;
|
|
2935
|
+
}
|
|
2936
|
+
}
|
|
2937
|
+
return { line, character };
|
|
2938
|
+
}
|
|
2939
|
+
getOffset(input, line, character) {
|
|
2940
|
+
let currentLine = 1;
|
|
2941
|
+
let currentCol = 1;
|
|
2942
|
+
for (let i = 0; i < input.length; i++) {
|
|
2943
|
+
if (currentLine === line && currentCol === character) {
|
|
2944
|
+
return i;
|
|
2945
|
+
}
|
|
2946
|
+
if (input[i] === "\n") {
|
|
2947
|
+
currentLine++;
|
|
2948
|
+
currentCol = 1;
|
|
2949
|
+
} else {
|
|
2950
|
+
currentCol++;
|
|
2951
|
+
}
|
|
2952
|
+
}
|
|
2953
|
+
return input.length;
|
|
2954
|
+
}
|
|
2955
|
+
toCompletions(list, kind) {
|
|
2956
|
+
return list.map((item) => ({
|
|
2957
|
+
label: item,
|
|
2958
|
+
kind,
|
|
2959
|
+
detail: HOVER_DOCS[item] ?? `${kind} element`
|
|
2960
|
+
}));
|
|
2961
|
+
}
|
|
2962
|
+
};
|
|
2963
|
+
|
|
2964
|
+
// src/cli/formatter.ts
|
|
2965
|
+
var import_fs = __toESM(require("fs"));
|
|
2966
|
+
var import_path = __toESM(require("path"));
|
|
2967
|
+
init_lexer();
|
|
2968
|
+
init_token();
|
|
2969
|
+
var INDENT_SIZE = 2;
|
|
2970
|
+
var BLOCK_OPENERS = /* @__PURE__ */ new Set([
|
|
2971
|
+
"if",
|
|
2972
|
+
"function",
|
|
2973
|
+
"async",
|
|
2974
|
+
"repeat",
|
|
2975
|
+
"while",
|
|
2976
|
+
"for",
|
|
2977
|
+
"try",
|
|
2978
|
+
"switch",
|
|
2979
|
+
"describe",
|
|
2980
|
+
"test",
|
|
2981
|
+
"backend",
|
|
2982
|
+
"route",
|
|
2983
|
+
"middleware",
|
|
2984
|
+
"namespace",
|
|
2985
|
+
"enum",
|
|
2986
|
+
"component",
|
|
2987
|
+
"screen",
|
|
2988
|
+
"form",
|
|
2989
|
+
"theme",
|
|
2990
|
+
"animate",
|
|
2991
|
+
"on",
|
|
2992
|
+
"show",
|
|
2993
|
+
"responsive",
|
|
2994
|
+
"errorBoundary"
|
|
2995
|
+
]);
|
|
2996
|
+
var INDENT_DEDENT_KEYWORDS = /* @__PURE__ */ new Set([
|
|
2997
|
+
"end",
|
|
2998
|
+
"elseif",
|
|
2999
|
+
"else",
|
|
3000
|
+
"case",
|
|
3001
|
+
"default",
|
|
3002
|
+
"catch",
|
|
3003
|
+
"fallback"
|
|
3004
|
+
]);
|
|
3005
|
+
var KinFormatter = class {
|
|
3006
|
+
constructor() {
|
|
3007
|
+
this.lexer = new Lexer();
|
|
3008
|
+
}
|
|
3009
|
+
/**
|
|
3010
|
+
* Format a single Kin source file and return the formatted string.
|
|
3011
|
+
*/
|
|
3012
|
+
format(source) {
|
|
3013
|
+
const lines = source.split("\n");
|
|
3014
|
+
return this.formatLines(lines);
|
|
3015
|
+
}
|
|
3016
|
+
/**
|
|
3017
|
+
* Format one or more files/directories in-place.
|
|
3018
|
+
* Returns the list of files that were changed.
|
|
3019
|
+
*/
|
|
3020
|
+
formatFiles(targets, check = false) {
|
|
3021
|
+
const changed = [];
|
|
3022
|
+
const unchanged = [];
|
|
3023
|
+
for (const target of targets) {
|
|
3024
|
+
const files = this.collectFiles(target);
|
|
3025
|
+
for (const file of files) {
|
|
3026
|
+
const original = import_fs.default.readFileSync(file, "utf8");
|
|
3027
|
+
const formatted = this.format(original);
|
|
3028
|
+
if (original !== formatted) {
|
|
3029
|
+
changed.push(file);
|
|
3030
|
+
if (!check) {
|
|
3031
|
+
import_fs.default.writeFileSync(file, formatted, "utf8");
|
|
3032
|
+
}
|
|
3033
|
+
} else {
|
|
3034
|
+
unchanged.push(file);
|
|
3035
|
+
}
|
|
3036
|
+
}
|
|
3037
|
+
}
|
|
3038
|
+
return { changed, unchanged };
|
|
3039
|
+
}
|
|
3040
|
+
// ───────────────────────────────────────────────────────────────────────
|
|
3041
|
+
// Core formatting logic
|
|
3042
|
+
// ───────────────────────────────────────────────────────────────────────
|
|
3043
|
+
formatLines(rawLines) {
|
|
3044
|
+
const output = [];
|
|
3045
|
+
let indentLevel = 0;
|
|
3046
|
+
let prevBlank = false;
|
|
3047
|
+
for (const rawLine of rawLines) {
|
|
3048
|
+
const trimmed = rawLine.trim();
|
|
3049
|
+
if (trimmed === "") {
|
|
3050
|
+
if (!prevBlank && output.length > 0) {
|
|
3051
|
+
output.push("");
|
|
3052
|
+
prevBlank = true;
|
|
3053
|
+
}
|
|
3054
|
+
continue;
|
|
3055
|
+
}
|
|
3056
|
+
prevBlank = false;
|
|
3057
|
+
const tokens = this.lexer.tokenize(trimmed);
|
|
3058
|
+
const keywords = tokens.filter((t) => t.type === 3 /* KEYWORD */).map((t) => t.value);
|
|
3059
|
+
const firstKeyword = keywords[0];
|
|
3060
|
+
if (firstKeyword && INDENT_DEDENT_KEYWORDS.has(firstKeyword)) {
|
|
3061
|
+
indentLevel = Math.max(0, indentLevel - 1);
|
|
3062
|
+
}
|
|
3063
|
+
const indent = " ".repeat(indentLevel * INDENT_SIZE);
|
|
3064
|
+
output.push(indent + trimmed);
|
|
3065
|
+
const hasOpener = keywords.some((k) => BLOCK_OPENERS.has(k));
|
|
3066
|
+
if (hasOpener) {
|
|
3067
|
+
if (!INDENT_DEDENT_KEYWORDS.has(firstKeyword || "")) {
|
|
3068
|
+
indentLevel++;
|
|
3069
|
+
}
|
|
3070
|
+
}
|
|
3071
|
+
}
|
|
3072
|
+
while (output.length > 0 && output[output.length - 1] === "") {
|
|
3073
|
+
output.pop();
|
|
3074
|
+
}
|
|
3075
|
+
return output.join("\n") + "\n";
|
|
3076
|
+
}
|
|
3077
|
+
// ───────────────────────────────────────────────────────────────────────
|
|
3078
|
+
// File collection
|
|
3079
|
+
// ───────────────────────────────────────────────────────────────────────
|
|
3080
|
+
collectFiles(target) {
|
|
3081
|
+
const abs = import_path.default.resolve(target);
|
|
3082
|
+
const stat = import_fs.default.statSync(abs, { throwIfNoEntry: false });
|
|
3083
|
+
if (!stat) return [];
|
|
3084
|
+
if (stat.isFile() && abs.endsWith(".kin")) return [abs];
|
|
3085
|
+
if (stat.isDirectory()) return this.walkDir(abs);
|
|
3086
|
+
return [];
|
|
3087
|
+
}
|
|
3088
|
+
walkDir(dir) {
|
|
3089
|
+
const results = [];
|
|
3090
|
+
for (const entry of import_fs.default.readdirSync(dir, { withFileTypes: true })) {
|
|
3091
|
+
const full = import_path.default.join(dir, entry.name);
|
|
3092
|
+
if (entry.isDirectory()) {
|
|
3093
|
+
if (entry.name === "node_modules" || entry.name === "dist" || entry.name === ".git") continue;
|
|
3094
|
+
results.push(...this.walkDir(full));
|
|
3095
|
+
} else if (entry.isFile() && entry.name.endsWith(".kin")) {
|
|
3096
|
+
results.push(full);
|
|
3097
|
+
}
|
|
3098
|
+
}
|
|
3099
|
+
return results;
|
|
3100
|
+
}
|
|
3101
|
+
};
|
|
3102
|
+
|
|
3103
|
+
// src/lsp/server.ts
|
|
3104
|
+
var langService = new KinLanguageService();
|
|
3105
|
+
var kinFormatter = new KinFormatter();
|
|
3106
|
+
var documents = /* @__PURE__ */ new Map();
|
|
3107
|
+
function sendMessage(obj) {
|
|
3108
|
+
const body = JSON.stringify(obj);
|
|
3109
|
+
const header = `Content-Length: ${Buffer.byteLength(body, "utf8")}\r
|
|
3110
|
+
\r
|
|
3111
|
+
`;
|
|
3112
|
+
process.stdout.write(header + body);
|
|
3113
|
+
}
|
|
3114
|
+
function response(id, result) {
|
|
3115
|
+
sendMessage({ jsonrpc: "2.0", id, result });
|
|
3116
|
+
}
|
|
3117
|
+
function notification(method, params) {
|
|
3118
|
+
sendMessage({ jsonrpc: "2.0", method, params });
|
|
3119
|
+
}
|
|
3120
|
+
var MessageReader = class {
|
|
3121
|
+
constructor() {
|
|
3122
|
+
this.buffer = "";
|
|
3123
|
+
}
|
|
3124
|
+
feed(chunk) {
|
|
3125
|
+
this.buffer += chunk;
|
|
3126
|
+
this.processBuffer();
|
|
3127
|
+
}
|
|
3128
|
+
processBuffer() {
|
|
3129
|
+
while (true) {
|
|
3130
|
+
const headerEnd = this.buffer.indexOf("\r\n\r\n");
|
|
3131
|
+
if (headerEnd === -1) break;
|
|
3132
|
+
const header = this.buffer.slice(0, headerEnd);
|
|
3133
|
+
const match = header.match(/Content-Length:\s*(\d+)/i);
|
|
3134
|
+
if (!match) {
|
|
3135
|
+
this.buffer = this.buffer.slice(headerEnd + 4);
|
|
3136
|
+
continue;
|
|
3137
|
+
}
|
|
3138
|
+
const length = parseInt(match[1], 10);
|
|
3139
|
+
const bodyStart = headerEnd + 4;
|
|
3140
|
+
if (this.buffer.length < bodyStart + length) break;
|
|
3141
|
+
const body = this.buffer.slice(bodyStart, bodyStart + length);
|
|
3142
|
+
this.buffer = this.buffer.slice(bodyStart + length);
|
|
3143
|
+
try {
|
|
3144
|
+
const msg = JSON.parse(body);
|
|
3145
|
+
handleMessage(msg);
|
|
3146
|
+
} catch {
|
|
3147
|
+
}
|
|
3148
|
+
}
|
|
3149
|
+
}
|
|
3150
|
+
};
|
|
3151
|
+
function handleMessage(msg) {
|
|
3152
|
+
const { id, method, params } = msg;
|
|
3153
|
+
switch (method) {
|
|
3154
|
+
case "initialize":
|
|
3155
|
+
response(id, {
|
|
3156
|
+
capabilities: {
|
|
3157
|
+
textDocumentSync: {
|
|
3158
|
+
openClose: true,
|
|
3159
|
+
change: 1
|
|
3160
|
+
/* full */
|
|
3161
|
+
},
|
|
3162
|
+
completionProvider: { triggerCharacters: [".", " "] },
|
|
3163
|
+
hoverProvider: true,
|
|
3164
|
+
definitionProvider: true,
|
|
3165
|
+
referencesProvider: true,
|
|
3166
|
+
renameProvider: true,
|
|
3167
|
+
documentFormattingProvider: true,
|
|
3168
|
+
codeActionProvider: true
|
|
3169
|
+
},
|
|
3170
|
+
serverInfo: { name: "kin-language-server", version: "1.0.0" }
|
|
3171
|
+
});
|
|
3172
|
+
break;
|
|
3173
|
+
case "initialized":
|
|
3174
|
+
break;
|
|
3175
|
+
case "shutdown":
|
|
3176
|
+
response(id, null);
|
|
3177
|
+
break;
|
|
3178
|
+
case "exit":
|
|
3179
|
+
process.exit(0);
|
|
3180
|
+
case "textDocument/didOpen": {
|
|
3181
|
+
const uri = params.textDocument.uri;
|
|
3182
|
+
const content = params.textDocument.text;
|
|
3183
|
+
documents.set(uri, content);
|
|
3184
|
+
publishDiagnostics(uri, content);
|
|
3185
|
+
break;
|
|
3186
|
+
}
|
|
3187
|
+
case "textDocument/didChange": {
|
|
3188
|
+
const uri = params.textDocument.uri;
|
|
3189
|
+
const content = params.contentChanges[0].text;
|
|
3190
|
+
documents.set(uri, content);
|
|
3191
|
+
publishDiagnostics(uri, content);
|
|
3192
|
+
break;
|
|
3193
|
+
}
|
|
3194
|
+
case "textDocument/didClose": {
|
|
3195
|
+
const uri = params.textDocument.uri;
|
|
3196
|
+
documents.delete(uri);
|
|
3197
|
+
notification("textDocument/publishDiagnostics", { uri, diagnostics: [] });
|
|
3198
|
+
break;
|
|
3199
|
+
}
|
|
3200
|
+
case "textDocument/completion": {
|
|
3201
|
+
const uri = params.textDocument.uri;
|
|
3202
|
+
const pos = params.position;
|
|
3203
|
+
const text = documents.get(uri) ?? "";
|
|
3204
|
+
const items = langService.getAutocomplete(text, pos.line + 1, pos.character + 1);
|
|
3205
|
+
response(id, {
|
|
3206
|
+
isIncomplete: false,
|
|
3207
|
+
items: items.map((i) => ({
|
|
3208
|
+
label: i.label,
|
|
3209
|
+
kind: completionKindMap(i.kind),
|
|
3210
|
+
detail: i.detail ?? "",
|
|
3211
|
+
documentation: i.documentation ?? i.detail ?? ""
|
|
3212
|
+
}))
|
|
3213
|
+
});
|
|
3214
|
+
break;
|
|
3215
|
+
}
|
|
3216
|
+
case "textDocument/hover": {
|
|
3217
|
+
const uri = params.textDocument.uri;
|
|
3218
|
+
const pos = params.position;
|
|
3219
|
+
const text = documents.get(uri) ?? "";
|
|
3220
|
+
const info = langService.getHover(text, pos.line + 1, pos.character + 1);
|
|
3221
|
+
response(
|
|
3222
|
+
id,
|
|
3223
|
+
info ? { contents: { kind: "markdown", value: info.contents } } : null
|
|
3224
|
+
);
|
|
3225
|
+
break;
|
|
3226
|
+
}
|
|
3227
|
+
case "textDocument/definition": {
|
|
3228
|
+
const uri = params.textDocument.uri;
|
|
3229
|
+
const pos = params.position;
|
|
3230
|
+
const text = documents.get(uri) ?? "";
|
|
3231
|
+
const loc = findDefinition(text, uri, pos.line, pos.character);
|
|
3232
|
+
response(id, loc);
|
|
3233
|
+
break;
|
|
3234
|
+
}
|
|
3235
|
+
case "textDocument/references": {
|
|
3236
|
+
const uri = params.textDocument.uri;
|
|
3237
|
+
const pos = params.position;
|
|
3238
|
+
const text = documents.get(uri) ?? "";
|
|
3239
|
+
const refs = findReferences(text, uri, pos.line, pos.character);
|
|
3240
|
+
response(id, refs);
|
|
3241
|
+
break;
|
|
3242
|
+
}
|
|
3243
|
+
case "textDocument/rename": {
|
|
3244
|
+
const uri = params.textDocument.uri;
|
|
3245
|
+
const pos = params.position;
|
|
3246
|
+
const newName = params.newName;
|
|
3247
|
+
const text = documents.get(uri) ?? "";
|
|
3248
|
+
const edit = renameSymbol(text, uri, pos.line, pos.character, newName);
|
|
3249
|
+
response(id, edit);
|
|
3250
|
+
break;
|
|
3251
|
+
}
|
|
3252
|
+
case "textDocument/formatting": {
|
|
3253
|
+
const uri = params.textDocument.uri;
|
|
3254
|
+
const text = documents.get(uri) ?? "";
|
|
3255
|
+
const formatted = kinFormatter.format(text);
|
|
3256
|
+
if (formatted === text) {
|
|
3257
|
+
response(id, []);
|
|
3258
|
+
} else {
|
|
3259
|
+
const lineCount = text.split("\n").length;
|
|
3260
|
+
response(id, [{
|
|
3261
|
+
range: {
|
|
3262
|
+
start: { line: 0, character: 0 },
|
|
3263
|
+
end: { line: lineCount, character: 0 }
|
|
3264
|
+
},
|
|
3265
|
+
newText: formatted
|
|
3266
|
+
}]);
|
|
3267
|
+
}
|
|
3268
|
+
break;
|
|
3269
|
+
}
|
|
3270
|
+
case "textDocument/codeAction": {
|
|
3271
|
+
const uri = params.textDocument.uri;
|
|
3272
|
+
const text = documents.get(uri) ?? "";
|
|
3273
|
+
const diags = params.context?.diagnostics ?? [];
|
|
3274
|
+
response(id, buildCodeActions(uri, text, diags));
|
|
3275
|
+
break;
|
|
3276
|
+
}
|
|
3277
|
+
default:
|
|
3278
|
+
if (id !== void 0) {
|
|
3279
|
+
response(id, null);
|
|
3280
|
+
}
|
|
3281
|
+
}
|
|
3282
|
+
}
|
|
3283
|
+
function publishDiagnostics(uri, text) {
|
|
3284
|
+
const diagnostics = langService.getDiagnostics(text);
|
|
3285
|
+
notification("textDocument/publishDiagnostics", {
|
|
3286
|
+
uri,
|
|
3287
|
+
diagnostics: diagnostics.map((d) => ({
|
|
3288
|
+
severity: d.severity === "error" ? 1 : 2,
|
|
3289
|
+
range: {
|
|
3290
|
+
start: { line: Math.max(0, d.line - 1), character: Math.max(0, d.character - 1) },
|
|
3291
|
+
end: { line: Math.max(0, d.line - 1), character: Math.max(0, d.character + 20) }
|
|
3292
|
+
},
|
|
3293
|
+
message: d.message,
|
|
3294
|
+
source: "kin"
|
|
3295
|
+
}))
|
|
3296
|
+
});
|
|
3297
|
+
}
|
|
3298
|
+
function findDefinition(text, uri, line, character) {
|
|
3299
|
+
const lines = text.split("\n");
|
|
3300
|
+
const targetLine = lines[line] ?? "";
|
|
3301
|
+
const word = wordAt(targetLine, character);
|
|
3302
|
+
if (!word) return null;
|
|
3303
|
+
const declPattern = new RegExp(
|
|
3304
|
+
`^\\s*(?:function|component|screen|state|number|string|boolean|list|object|const|enum|namespace)\\s+${escapeRegex(word)}\\b`
|
|
3305
|
+
);
|
|
3306
|
+
for (let i = 0; i < lines.length; i++) {
|
|
3307
|
+
if (declPattern.test(lines[i])) {
|
|
3308
|
+
const col = lines[i].indexOf(word);
|
|
3309
|
+
return {
|
|
3310
|
+
uri,
|
|
3311
|
+
range: {
|
|
3312
|
+
start: { line: i, character: col },
|
|
3313
|
+
end: { line: i, character: col + word.length }
|
|
3314
|
+
}
|
|
3315
|
+
};
|
|
3316
|
+
}
|
|
3317
|
+
}
|
|
3318
|
+
return null;
|
|
3319
|
+
}
|
|
3320
|
+
function findReferences(text, uri, line, character) {
|
|
3321
|
+
const lines = text.split("\n");
|
|
3322
|
+
const word = wordAt(lines[line] ?? "", character);
|
|
3323
|
+
if (!word) return [];
|
|
3324
|
+
const refs = [];
|
|
3325
|
+
const pattern = new RegExp(`\\b${escapeRegex(word)}\\b`, "g");
|
|
3326
|
+
for (let i = 0; i < lines.length; i++) {
|
|
3327
|
+
let match;
|
|
3328
|
+
pattern.lastIndex = 0;
|
|
3329
|
+
while ((match = pattern.exec(lines[i])) !== null) {
|
|
3330
|
+
refs.push({
|
|
3331
|
+
uri,
|
|
3332
|
+
range: {
|
|
3333
|
+
start: { line: i, character: match.index },
|
|
3334
|
+
end: { line: i, character: match.index + word.length }
|
|
3335
|
+
}
|
|
3336
|
+
});
|
|
3337
|
+
}
|
|
3338
|
+
}
|
|
3339
|
+
return refs;
|
|
3340
|
+
}
|
|
3341
|
+
function renameSymbol(text, uri, line, character, newName) {
|
|
3342
|
+
const lines = text.split("\n");
|
|
3343
|
+
const word = wordAt(lines[line] ?? "", character);
|
|
3344
|
+
if (!word) return null;
|
|
3345
|
+
const edits = [];
|
|
3346
|
+
const pattern = new RegExp(`\\b${escapeRegex(word)}\\b`, "g");
|
|
3347
|
+
for (let i = 0; i < lines.length; i++) {
|
|
3348
|
+
let match;
|
|
3349
|
+
pattern.lastIndex = 0;
|
|
3350
|
+
while ((match = pattern.exec(lines[i])) !== null) {
|
|
3351
|
+
edits.push({
|
|
3352
|
+
range: {
|
|
3353
|
+
start: { line: i, character: match.index },
|
|
3354
|
+
end: { line: i, character: match.index + word.length }
|
|
3355
|
+
},
|
|
3356
|
+
newText: newName
|
|
3357
|
+
});
|
|
3358
|
+
}
|
|
3359
|
+
}
|
|
3360
|
+
return {
|
|
3361
|
+
changes: { [uri]: edits }
|
|
3362
|
+
};
|
|
3363
|
+
}
|
|
3364
|
+
function buildCodeActions(uri, text, diagnostics) {
|
|
3365
|
+
const actions = [];
|
|
3366
|
+
for (const diag of diagnostics) {
|
|
3367
|
+
const msg = diag.message ?? "";
|
|
3368
|
+
if (msg.includes("Cannot resolve module")) {
|
|
3369
|
+
const match = msg.match(/"([^"]+)"/);
|
|
3370
|
+
if (match) {
|
|
3371
|
+
const pkgName = match[1];
|
|
3372
|
+
actions.push({
|
|
3373
|
+
title: `Install missing package: ${pkgName}`,
|
|
3374
|
+
kind: "quickfix",
|
|
3375
|
+
diagnostics: [diag],
|
|
3376
|
+
command: {
|
|
3377
|
+
title: `kin install ${pkgName}`,
|
|
3378
|
+
command: "kin.install",
|
|
3379
|
+
arguments: [pkgName]
|
|
3380
|
+
}
|
|
3381
|
+
});
|
|
3382
|
+
}
|
|
3383
|
+
}
|
|
3384
|
+
if (msg.includes("Type mismatch")) {
|
|
3385
|
+
actions.push({
|
|
3386
|
+
title: "Suppress type error (use 'any')",
|
|
3387
|
+
kind: "quickfix",
|
|
3388
|
+
diagnostics: [diag]
|
|
3389
|
+
});
|
|
3390
|
+
}
|
|
3391
|
+
}
|
|
3392
|
+
return actions;
|
|
3393
|
+
}
|
|
3394
|
+
function wordAt(line, character) {
|
|
3395
|
+
const start = line.slice(0, character).search(/\w+$/) ?? -1;
|
|
3396
|
+
if (start === -1) return "";
|
|
3397
|
+
const end = line.slice(character).search(/\W/);
|
|
3398
|
+
return end === -1 ? line.slice(start) : line.slice(start, character + end);
|
|
3399
|
+
}
|
|
3400
|
+
function escapeRegex(str) {
|
|
3401
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3402
|
+
}
|
|
3403
|
+
function completionKindMap(kind) {
|
|
3404
|
+
const map = {
|
|
3405
|
+
keyword: 14,
|
|
3406
|
+
variable: 6,
|
|
3407
|
+
function: 3,
|
|
3408
|
+
property: 10,
|
|
3409
|
+
module: 9
|
|
3410
|
+
};
|
|
3411
|
+
return map[kind] ?? 1;
|
|
3412
|
+
}
|
|
3413
|
+
function startLspServer() {
|
|
3414
|
+
process.stdin.setEncoding("utf8");
|
|
3415
|
+
const reader = new MessageReader();
|
|
3416
|
+
process.stdin.on("data", (chunk) => reader.feed(chunk));
|
|
3417
|
+
process.stdin.on("end", () => process.exit(0));
|
|
3418
|
+
}
|
|
3419
|
+
if (require.main === module) {
|
|
3420
|
+
startLspServer();
|
|
3421
|
+
}
|
|
3422
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
3423
|
+
0 && (module.exports = {
|
|
3424
|
+
startLspServer
|
|
3425
|
+
});
|