@ball-lang/encoder 0.1.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.
@@ -0,0 +1,895 @@
1
+ import ts from "typescript";
2
+ const BINARY_OPS = {
3
+ [ts.SyntaxKind.PlusToken]: { module: "std", function: "add" },
4
+ [ts.SyntaxKind.MinusToken]: { module: "std", function: "subtract" },
5
+ [ts.SyntaxKind.AsteriskToken]: { module: "std", function: "multiply" },
6
+ [ts.SyntaxKind.SlashToken]: { module: "std", function: "divide_double" },
7
+ [ts.SyntaxKind.PercentToken]: { module: "std", function: "modulo" },
8
+ [ts.SyntaxKind.AmpersandToken]: { module: "std", function: "bitwise_and" },
9
+ [ts.SyntaxKind.BarToken]: { module: "std", function: "bitwise_or" },
10
+ [ts.SyntaxKind.CaretToken]: { module: "std", function: "bitwise_xor" },
11
+ [ts.SyntaxKind.LessThanLessThanToken]: { module: "std", function: "shift_left" },
12
+ [ts.SyntaxKind.GreaterThanGreaterThanToken]: { module: "std", function: "shift_right" },
13
+ [ts.SyntaxKind.EqualsEqualsEqualsToken]: { module: "std", function: "equals" },
14
+ [ts.SyntaxKind.ExclamationEqualsEqualsToken]: { module: "std", function: "not_equals" },
15
+ [ts.SyntaxKind.EqualsEqualsToken]: { module: "std", function: "equals" },
16
+ [ts.SyntaxKind.ExclamationEqualsToken]: { module: "std", function: "not_equals" },
17
+ [ts.SyntaxKind.LessThanToken]: { module: "std", function: "less_than" },
18
+ [ts.SyntaxKind.GreaterThanToken]: { module: "std", function: "greater_than" },
19
+ [ts.SyntaxKind.LessThanEqualsToken]: { module: "std", function: "less_than_or_equal" },
20
+ [ts.SyntaxKind.GreaterThanEqualsToken]: { module: "std", function: "greater_than_or_equal" },
21
+ [ts.SyntaxKind.AmpersandAmpersandToken]: { module: "std", function: "and" },
22
+ [ts.SyntaxKind.BarBarToken]: { module: "std", function: "or" },
23
+ [ts.SyntaxKind.QuestionQuestionToken]: { module: "std", function: "null_coalesce" },
24
+ [ts.SyntaxKind.InstanceOfKeyword]: { module: "std", function: "is" },
25
+ };
26
+ const COMPOUND_OPS = {
27
+ [ts.SyntaxKind.PlusEqualsToken]: "+=",
28
+ [ts.SyntaxKind.MinusEqualsToken]: "-=",
29
+ [ts.SyntaxKind.AsteriskEqualsToken]: "*=",
30
+ [ts.SyntaxKind.SlashEqualsToken]: "/=",
31
+ [ts.SyntaxKind.PercentEqualsToken]: "%=",
32
+ [ts.SyntaxKind.AmpersandEqualsToken]: "&=",
33
+ [ts.SyntaxKind.BarEqualsToken]: "|=",
34
+ [ts.SyntaxKind.CaretEqualsToken]: "^=",
35
+ [ts.SyntaxKind.LessThanLessThanEqualsToken]: "<<=",
36
+ [ts.SyntaxKind.GreaterThanGreaterThanEqualsToken]: ">>=",
37
+ [ts.SyntaxKind.QuestionQuestionEqualsToken]: "??=",
38
+ };
39
+ export class TsEncoder {
40
+ stdFunctions = new Set();
41
+ warnings = [];
42
+ encode(source, options = {}) {
43
+ const modName = options.moduleName ?? "main";
44
+ const entryFn = options.entryFunction ?? "main";
45
+ const sourceFile = ts.createSourceFile("input.ts", source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
46
+ const functions = [];
47
+ const typeDefs = [];
48
+ const typeAliases = [];
49
+ const enums = [];
50
+ for (const stmt of sourceFile.statements) {
51
+ if (ts.isFunctionDeclaration(stmt) && stmt.name) {
52
+ functions.push(this.encodeFunction(stmt));
53
+ }
54
+ else if (ts.isClassDeclaration(stmt) && stmt.name) {
55
+ this.encodeClass(stmt, functions, typeDefs);
56
+ }
57
+ else if (ts.isInterfaceDeclaration(stmt)) {
58
+ typeDefs.push(this.encodeInterface(stmt));
59
+ }
60
+ else if (ts.isTypeAliasDeclaration(stmt)) {
61
+ typeAliases.push(this.encodeTypeAlias(stmt));
62
+ }
63
+ else if (ts.isEnumDeclaration(stmt)) {
64
+ enums.push(this.encodeEnum(stmt));
65
+ }
66
+ else if (ts.isVariableStatement(stmt)) {
67
+ for (const decl of stmt.declarationList.declarations) {
68
+ if (ts.isIdentifier(decl.name)) {
69
+ functions.push({
70
+ name: decl.name.text,
71
+ body: decl.initializer ? this.encodeExpr(decl.initializer) : { literal: { stringValue: "" } },
72
+ metadata: { kind: "top_level_variable" },
73
+ });
74
+ }
75
+ else if (ts.isObjectBindingPattern(decl.name) && decl.initializer) {
76
+ for (const element of decl.name.elements) {
77
+ const propName = element.propertyName
78
+ ? (ts.isIdentifier(element.propertyName) ? element.propertyName.text : element.propertyName.getText())
79
+ : (ts.isIdentifier(element.name) ? element.name.text : element.name.getText());
80
+ const varName = ts.isIdentifier(element.name) ? element.name.text : element.name.getText();
81
+ functions.push({
82
+ name: varName,
83
+ body: { fieldAccess: { object: this.encodeExpr(decl.initializer), field: propName } },
84
+ metadata: { kind: "top_level_variable", destructured: true },
85
+ });
86
+ }
87
+ }
88
+ else if (ts.isArrayBindingPattern(decl.name) && decl.initializer) {
89
+ for (let i = 0; i < decl.name.elements.length; i++) {
90
+ const element = decl.name.elements[i];
91
+ if (ts.isOmittedExpression(element))
92
+ continue;
93
+ const varName = ts.isIdentifier(element.name) ? element.name.text : element.name.getText();
94
+ functions.push({
95
+ name: varName,
96
+ body: this.stdCall("index", [
97
+ { name: "target", value: this.encodeExpr(decl.initializer) },
98
+ { name: "index", value: { literal: { intValue: `${i}` } } },
99
+ ]),
100
+ metadata: { kind: "top_level_variable", destructured: true },
101
+ });
102
+ }
103
+ }
104
+ }
105
+ }
106
+ else if (ts.isExpressionStatement(stmt)) {
107
+ functions.push({
108
+ name: `__top_${functions.length}`,
109
+ body: this.encodeExpr(stmt.expression),
110
+ });
111
+ }
112
+ }
113
+ const stdModule = this.buildStdModule();
114
+ const modules = [stdModule];
115
+ const userModule = {
116
+ name: modName,
117
+ functions,
118
+ ...(typeDefs.length > 0 ? { typeDefs } : {}),
119
+ ...(typeAliases.length > 0 ? { typeAliases } : {}),
120
+ ...(enums.length > 0 ? { enums } : {}),
121
+ };
122
+ modules.push(userModule);
123
+ return {
124
+ name: modName,
125
+ version: "1.0.0",
126
+ modules,
127
+ entryModule: modName,
128
+ entryFunction: entryFn,
129
+ };
130
+ }
131
+ encodeFunction(node) {
132
+ const name = node.name ? (ts.isIdentifier(node.name) ? node.name.text : node.name.getText()) : "";
133
+ const params = node.parameters.map(p => ts.isIdentifier(p.name) ? p.name.text : p.name.getText());
134
+ const metadata = {};
135
+ if (params.length > 0)
136
+ metadata["params"] = params;
137
+ if (node.type)
138
+ metadata["returnType"] = node.type.getText();
139
+ // Async functions
140
+ if (node.modifiers?.some(m => m.kind === ts.SyntaxKind.AsyncKeyword)) {
141
+ metadata["is_async"] = true;
142
+ }
143
+ // Rest parameters
144
+ const lastParam = node.parameters[node.parameters.length - 1];
145
+ if (lastParam?.dotDotDotToken) {
146
+ metadata["rest_param"] = ts.isIdentifier(lastParam.name) ? lastParam.name.text : lastParam.name.getText();
147
+ }
148
+ // Default parameter values
149
+ const defaults = {};
150
+ for (const p of node.parameters) {
151
+ if (p.initializer && ts.isIdentifier(p.name)) {
152
+ defaults[p.name.text] = p.initializer.getText();
153
+ }
154
+ }
155
+ if (Object.keys(defaults).length > 0)
156
+ metadata["param_defaults"] = defaults;
157
+ const body = node.body ? this.encodeBody(node.body) : undefined;
158
+ const fn = { name };
159
+ if (body)
160
+ fn.body = body;
161
+ if (Object.keys(metadata).length > 0)
162
+ fn.metadata = metadata;
163
+ if (node.type)
164
+ fn.outputType = node.type.getText();
165
+ return fn;
166
+ }
167
+ encodeBody(body) {
168
+ if (ts.isBlock(body)) {
169
+ return this.encodeBlock(body);
170
+ }
171
+ return this.encodeExpr(body);
172
+ }
173
+ encodeBlock(block) {
174
+ const stmts = [];
175
+ for (const s of block.statements) {
176
+ stmts.push(...this.encodeStatement(s));
177
+ }
178
+ return { block: { statements: stmts } };
179
+ }
180
+ encodeStatement(node) {
181
+ if (ts.isVariableStatement(node)) {
182
+ const results = [];
183
+ for (const decl of node.declarationList.declarations) {
184
+ if (ts.isObjectBindingPattern(decl.name) && decl.initializer) {
185
+ // Object destructuring: const { a, b } = obj
186
+ const source = this.encodeExpr(decl.initializer);
187
+ for (const element of decl.name.elements) {
188
+ const propName = element.propertyName
189
+ ? (ts.isIdentifier(element.propertyName) ? element.propertyName.text : element.propertyName.getText())
190
+ : (ts.isIdentifier(element.name) ? element.name.text : element.name.getText());
191
+ const varName = ts.isIdentifier(element.name) ? element.name.text : element.name.getText();
192
+ let value = { fieldAccess: { object: source, field: propName } };
193
+ if (element.initializer) {
194
+ value = this.stdCall("null_coalesce", [
195
+ { name: "left", value },
196
+ { name: "right", value: this.encodeExpr(element.initializer) },
197
+ ]);
198
+ }
199
+ results.push({ let: { name: varName, value } });
200
+ }
201
+ }
202
+ else if (ts.isArrayBindingPattern(decl.name) && decl.initializer) {
203
+ // Array destructuring: const [x, y] = arr
204
+ const source = this.encodeExpr(decl.initializer);
205
+ for (let i = 0; i < decl.name.elements.length; i++) {
206
+ const element = decl.name.elements[i];
207
+ if (ts.isOmittedExpression(element))
208
+ continue;
209
+ const varName = ts.isIdentifier(element.name) ? element.name.text : element.name.getText();
210
+ let value = this.stdCall("index", [
211
+ { name: "target", value: source },
212
+ { name: "index", value: { literal: { intValue: `${i}` } } },
213
+ ]);
214
+ if (element.initializer) {
215
+ value = this.stdCall("null_coalesce", [
216
+ { name: "left", value },
217
+ { name: "right", value: this.encodeExpr(element.initializer) },
218
+ ]);
219
+ }
220
+ results.push({ let: { name: varName, value } });
221
+ }
222
+ }
223
+ else {
224
+ results.push({
225
+ let: {
226
+ name: ts.isIdentifier(decl.name) ? decl.name.text : decl.name.getText(),
227
+ value: decl.initializer ? this.encodeExpr(decl.initializer) : undefined,
228
+ },
229
+ });
230
+ }
231
+ }
232
+ return results;
233
+ }
234
+ if (ts.isExpressionStatement(node)) {
235
+ return [{ expression: this.encodeExpr(node.expression) }];
236
+ }
237
+ if (ts.isReturnStatement(node)) {
238
+ return [{ expression: this.stdCall("return", node.expression
239
+ ? [{ name: "value", value: this.encodeExpr(node.expression) }]
240
+ : []) }];
241
+ }
242
+ if (ts.isIfStatement(node)) {
243
+ return [{ expression: this.encodeIf(node) }];
244
+ }
245
+ if (ts.isForStatement(node)) {
246
+ return [{ expression: this.encodeFor(node) }];
247
+ }
248
+ if (ts.isForOfStatement(node) || ts.isForInStatement(node)) {
249
+ return [{ expression: this.encodeForOf(node) }];
250
+ }
251
+ if (ts.isWhileStatement(node)) {
252
+ return [{ expression: this.encodeWhile(node) }];
253
+ }
254
+ if (ts.isDoStatement(node)) {
255
+ return [{ expression: this.encodeDoWhile(node) }];
256
+ }
257
+ if (ts.isTryStatement(node)) {
258
+ return [{ expression: this.encodeTry(node) }];
259
+ }
260
+ if (ts.isThrowStatement(node)) {
261
+ return [{ expression: this.stdCall("throw", [
262
+ { name: "value", value: this.encodeExpr(node.expression) },
263
+ ]) }];
264
+ }
265
+ if (ts.isBreakStatement(node)) {
266
+ const fields = [];
267
+ if (node.label)
268
+ fields.push({ name: "label", value: { literal: { stringValue: node.label.text } } });
269
+ return [{ expression: this.stdCall("break", fields) }];
270
+ }
271
+ if (ts.isContinueStatement(node)) {
272
+ const fields = [];
273
+ if (node.label)
274
+ fields.push({ name: "label", value: { literal: { stringValue: node.label.text } } });
275
+ return [{ expression: this.stdCall("continue", fields) }];
276
+ }
277
+ if (ts.isLabeledStatement(node)) {
278
+ return [{ expression: this.stdCall("labeled", [
279
+ { name: "label", value: { literal: { stringValue: node.label.text } } },
280
+ { name: "body", value: this.encodeBody(ts.isBlock(node.statement) ? node.statement : ts.factory.createBlock([node.statement])) },
281
+ ]) }];
282
+ }
283
+ if (ts.isSwitchStatement(node)) {
284
+ return [{ expression: this.encodeSwitch(node) }];
285
+ }
286
+ if (ts.isBlock(node)) {
287
+ return [{ expression: this.encodeBlock(node) }];
288
+ }
289
+ this.warn(`Unhandled statement kind: ${ts.SyntaxKind[node.kind]}`);
290
+ return [{ expression: { literal: { stringValue: `/* unhandled: ${ts.SyntaxKind[node.kind]} */` } } }];
291
+ }
292
+ encodeExpr(node) {
293
+ if (ts.isNumericLiteral(node)) {
294
+ const text = node.text;
295
+ if (text.includes(".") || text.includes("e") || text.includes("E")) {
296
+ return { literal: { doubleValue: parseFloat(text) } };
297
+ }
298
+ return { literal: { intValue: text } };
299
+ }
300
+ if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) {
301
+ return { literal: { stringValue: node.text } };
302
+ }
303
+ if (node.kind === ts.SyntaxKind.TrueKeyword) {
304
+ return { literal: { boolValue: true } };
305
+ }
306
+ if (node.kind === ts.SyntaxKind.FalseKeyword) {
307
+ return { literal: { boolValue: false } };
308
+ }
309
+ if (node.kind === ts.SyntaxKind.NullKeyword || node.kind === ts.SyntaxKind.UndefinedKeyword) {
310
+ return { literal: { stringValue: "" } };
311
+ }
312
+ if (ts.isIdentifier(node)) {
313
+ return { reference: { name: node.text } };
314
+ }
315
+ if (ts.isBinaryExpression(node)) {
316
+ return this.encodeBinary(node);
317
+ }
318
+ if (ts.isPrefixUnaryExpression(node)) {
319
+ return this.encodePrefixUnary(node);
320
+ }
321
+ if (ts.isPostfixUnaryExpression(node)) {
322
+ return this.encodePostfixUnary(node);
323
+ }
324
+ if (ts.isCallExpression(node)) {
325
+ return this.encodeCall(node);
326
+ }
327
+ if (ts.isPropertyAccessExpression(node)) {
328
+ if (node.questionDotToken) {
329
+ return this.stdCall("optional_access", [
330
+ { name: "object", value: this.encodeExpr(node.expression) },
331
+ { name: "field", value: { literal: { stringValue: node.name.text } } },
332
+ ], "ts_std");
333
+ }
334
+ return { fieldAccess: { object: this.encodeExpr(node.expression), field: node.name.text } };
335
+ }
336
+ if (ts.isElementAccessExpression(node)) {
337
+ if (node.questionDotToken) {
338
+ return this.stdCall("optional_access", [
339
+ { name: "object", value: this.encodeExpr(node.expression) },
340
+ { name: "field", value: this.encodeExpr(node.argumentExpression) },
341
+ ], "ts_std");
342
+ }
343
+ return this.stdCall("index", [
344
+ { name: "target", value: this.encodeExpr(node.expression) },
345
+ { name: "index", value: this.encodeExpr(node.argumentExpression) },
346
+ ]);
347
+ }
348
+ if (ts.isParenthesizedExpression(node)) {
349
+ return this.encodeExpr(node.expression);
350
+ }
351
+ if (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) {
352
+ return this.encodeLambda(node);
353
+ }
354
+ if (ts.isArrayLiteralExpression(node)) {
355
+ return {
356
+ literal: {
357
+ listValue: { elements: node.elements.map(e => this.encodeExpr(e)) },
358
+ },
359
+ };
360
+ }
361
+ if (ts.isObjectLiteralExpression(node)) {
362
+ const fields = [];
363
+ for (const prop of node.properties) {
364
+ if (ts.isPropertyAssignment(prop)) {
365
+ if (ts.isComputedPropertyName(prop.name)) {
366
+ // Computed property: { [key]: value }
367
+ fields.push({
368
+ name: "__computed",
369
+ value: this.stdCall("computed_property", [
370
+ { name: "key", value: this.encodeExpr(prop.name.expression) },
371
+ { name: "value", value: this.encodeExpr(prop.initializer) },
372
+ ], "ts_std"),
373
+ });
374
+ }
375
+ else {
376
+ const propName = ts.isIdentifier(prop.name)
377
+ ? prop.name.text
378
+ : ts.isStringLiteral(prop.name) ? prop.name.text : prop.name.getText();
379
+ fields.push({ name: propName, value: this.encodeExpr(prop.initializer) });
380
+ }
381
+ }
382
+ else if (ts.isShorthandPropertyAssignment(prop)) {
383
+ fields.push({ name: prop.name.text, value: { reference: { name: prop.name.text } } });
384
+ }
385
+ else if (ts.isSpreadAssignment(prop)) {
386
+ fields.push({
387
+ name: "__spread",
388
+ value: this.stdCall("spread", [
389
+ { name: "value", value: this.encodeExpr(prop.expression) },
390
+ ], "ts_std"),
391
+ });
392
+ }
393
+ }
394
+ return { messageCreation: { typeName: "", fields } };
395
+ }
396
+ if (ts.isConditionalExpression(node)) {
397
+ return this.stdCall("if", [
398
+ { name: "condition", value: this.encodeExpr(node.condition) },
399
+ { name: "then", value: { lambda: { name: "", body: this.encodeExpr(node.whenTrue) } } },
400
+ { name: "else", value: { lambda: { name: "", body: this.encodeExpr(node.whenFalse) } } },
401
+ ]);
402
+ }
403
+ if (ts.isTemplateExpression(node)) {
404
+ return this.encodeTemplate(node);
405
+ }
406
+ if (ts.isNewExpression(node)) {
407
+ return this.encodeNew(node);
408
+ }
409
+ if (ts.isTypeOfExpression(node)) {
410
+ return this.stdCall("type_of", [
411
+ { name: "value", value: this.encodeExpr(node.expression) },
412
+ ]);
413
+ }
414
+ if (ts.isAsExpression(node) || ts.isTypeAssertionExpression(node)) {
415
+ return this.encodeExpr(node.expression);
416
+ }
417
+ if (ts.isNonNullExpression(node)) {
418
+ return this.stdCall("null_check", [
419
+ { name: "value", value: this.encodeExpr(node.expression) },
420
+ ]);
421
+ }
422
+ if (ts.isAwaitExpression(node)) {
423
+ return this.stdCall("await", [
424
+ { name: "value", value: this.encodeExpr(node.expression) },
425
+ ]);
426
+ }
427
+ if (ts.isSpreadElement(node)) {
428
+ return this.stdCall("spread", [
429
+ { name: "value", value: this.encodeExpr(node.expression) },
430
+ ], "ts_std");
431
+ }
432
+ if (ts.isVoidExpression(node)) {
433
+ return { literal: { stringValue: "" } };
434
+ }
435
+ if (ts.isTaggedTemplateExpression(node)) {
436
+ return this.encodeTaggedTemplate(node);
437
+ }
438
+ this.warn(`Unhandled expression kind: ${ts.SyntaxKind[node.kind]}`);
439
+ return { literal: { stringValue: `/* unhandled: ${ts.SyntaxKind[node.kind]} */` } };
440
+ }
441
+ encodeBinary(node) {
442
+ const op = node.operatorToken.kind;
443
+ if (op === ts.SyntaxKind.EqualsToken) {
444
+ return this.stdCall("assign", [
445
+ { name: "target", value: this.encodeExpr(node.left) },
446
+ { name: "value", value: this.encodeExpr(node.right) },
447
+ ]);
448
+ }
449
+ const compound = COMPOUND_OPS[op];
450
+ if (compound) {
451
+ return this.stdCall("assign", [
452
+ { name: "target", value: this.encodeExpr(node.left) },
453
+ { name: "value", value: this.encodeExpr(node.right) },
454
+ { name: "op", value: { literal: { stringValue: compound } } },
455
+ ]);
456
+ }
457
+ if (op === ts.SyntaxKind.PlusToken && this.looksLikeStringConcat(node)) {
458
+ return this.stdCall("concat", [
459
+ { name: "left", value: this.encodeExpr(node.left) },
460
+ { name: "right", value: this.encodeExpr(node.right) },
461
+ ]);
462
+ }
463
+ const stdRef = BINARY_OPS[op];
464
+ if (stdRef) {
465
+ return this.stdCall(stdRef.function, [
466
+ { name: "left", value: this.encodeExpr(node.left) },
467
+ { name: "right", value: this.encodeExpr(node.right) },
468
+ ], stdRef.module);
469
+ }
470
+ if (op === ts.SyntaxKind.InKeyword) {
471
+ return this.stdCall("contains_key", [
472
+ { name: "map", value: this.encodeExpr(node.right) },
473
+ { name: "key", value: this.encodeExpr(node.left) },
474
+ ], "std_collections");
475
+ }
476
+ this.warn(`Unhandled binary operator: ${ts.SyntaxKind[op]}`);
477
+ return { literal: { stringValue: `/* binary: ${ts.SyntaxKind[op]} */` } };
478
+ }
479
+ looksLikeStringConcat(node) {
480
+ return ts.isStringLiteral(node.left) || ts.isStringLiteral(node.right) ||
481
+ ts.isTemplateExpression(node.left) || ts.isNoSubstitutionTemplateLiteral(node.left);
482
+ }
483
+ encodePrefixUnary(node) {
484
+ const op = node.operator;
485
+ if (op === ts.SyntaxKind.MinusToken) {
486
+ return this.stdCall("negate", [
487
+ { name: "value", value: this.encodeExpr(node.operand) },
488
+ ]);
489
+ }
490
+ if (op === ts.SyntaxKind.ExclamationToken) {
491
+ return this.stdCall("not", [
492
+ { name: "value", value: this.encodeExpr(node.operand) },
493
+ ]);
494
+ }
495
+ if (op === ts.SyntaxKind.TildeToken) {
496
+ return this.stdCall("bitwise_not", [
497
+ { name: "value", value: this.encodeExpr(node.operand) },
498
+ ]);
499
+ }
500
+ if (op === ts.SyntaxKind.PlusPlusToken) {
501
+ return this.stdCall("pre_increment", [
502
+ { name: "value", value: this.encodeExpr(node.operand) },
503
+ ]);
504
+ }
505
+ if (op === ts.SyntaxKind.MinusMinusToken) {
506
+ return this.stdCall("pre_decrement", [
507
+ { name: "value", value: this.encodeExpr(node.operand) },
508
+ ]);
509
+ }
510
+ this.warn(`Unhandled prefix operator: ${ts.SyntaxKind[op]}`);
511
+ return this.encodeExpr(node.operand);
512
+ }
513
+ encodePostfixUnary(node) {
514
+ if (node.operator === ts.SyntaxKind.PlusPlusToken) {
515
+ return this.stdCall("post_increment", [
516
+ { name: "value", value: this.encodeExpr(node.operand) },
517
+ ]);
518
+ }
519
+ return this.stdCall("post_decrement", [
520
+ { name: "value", value: this.encodeExpr(node.operand) },
521
+ ]);
522
+ }
523
+ encodeCall(node) {
524
+ const args = node.arguments.map((a, i) => ({
525
+ name: `arg${i}`,
526
+ value: this.encodeExpr(a),
527
+ }));
528
+ if (ts.isPropertyAccessExpression(node.expression)) {
529
+ const obj = this.encodeExpr(node.expression.expression);
530
+ const method = node.expression.name.text;
531
+ // Optional chaining call: obj?.method()
532
+ if (node.expression.questionDotToken || node.questionDotToken) {
533
+ return this.stdCall("optional_call", [
534
+ { name: "object", value: obj },
535
+ { name: "method", value: { literal: { stringValue: method } } },
536
+ ...args,
537
+ ], "ts_std");
538
+ }
539
+ return {
540
+ call: {
541
+ function: method,
542
+ input: {
543
+ messageCreation: {
544
+ typeName: "",
545
+ fields: [{ name: "self", value: obj }, ...args],
546
+ },
547
+ },
548
+ },
549
+ };
550
+ }
551
+ if (ts.isIdentifier(node.expression)) {
552
+ const fnName = node.expression.text;
553
+ return {
554
+ call: {
555
+ module: "",
556
+ function: fnName,
557
+ input: args.length > 0 ? {
558
+ messageCreation: { typeName: "", fields: args },
559
+ } : undefined,
560
+ },
561
+ };
562
+ }
563
+ return {
564
+ call: {
565
+ function: "__invoke",
566
+ input: {
567
+ messageCreation: {
568
+ typeName: "",
569
+ fields: [
570
+ { name: "callee", value: this.encodeExpr(node.expression) },
571
+ ...args,
572
+ ],
573
+ },
574
+ },
575
+ },
576
+ };
577
+ }
578
+ encodeLambda(node) {
579
+ const params = node.parameters.map(p => ts.isIdentifier(p.name) ? p.name.text : p.name.getText());
580
+ const body = node.body ? this.encodeBody(ts.isBlock(node.body) ? node.body : node.body) : undefined;
581
+ const metadata = {};
582
+ if (params.length > 0)
583
+ metadata["params"] = params;
584
+ if (node.modifiers?.some(m => m.kind === ts.SyntaxKind.AsyncKeyword)) {
585
+ metadata["is_async"] = true;
586
+ }
587
+ // Rest parameters
588
+ const lastParam = node.parameters[node.parameters.length - 1];
589
+ if (lastParam?.dotDotDotToken) {
590
+ metadata["rest_param"] = ts.isIdentifier(lastParam.name) ? lastParam.name.text : lastParam.name.getText();
591
+ }
592
+ // Default parameter values
593
+ const defaults = {};
594
+ for (const p of node.parameters) {
595
+ if (p.initializer && ts.isIdentifier(p.name)) {
596
+ defaults[p.name.text] = p.initializer.getText();
597
+ }
598
+ }
599
+ if (Object.keys(defaults).length > 0)
600
+ metadata["param_defaults"] = defaults;
601
+ // Destructured parameters
602
+ const destructured = {};
603
+ for (const p of node.parameters) {
604
+ if (ts.isObjectBindingPattern(p.name) || ts.isArrayBindingPattern(p.name)) {
605
+ const idx = node.parameters.indexOf(p);
606
+ destructured[`param${idx}`] = p.name.getText();
607
+ }
608
+ }
609
+ if (Object.keys(destructured).length > 0)
610
+ metadata["destructured_params"] = destructured;
611
+ return {
612
+ lambda: {
613
+ name: "",
614
+ body,
615
+ ...(Object.keys(metadata).length > 0 ? { metadata } : {}),
616
+ },
617
+ };
618
+ }
619
+ encodeIf(node) {
620
+ const fields = [
621
+ { name: "condition", value: this.encodeExpr(node.expression) },
622
+ { name: "then", value: { lambda: { name: "", body: this.encodeBody(ts.isBlock(node.thenStatement) ? node.thenStatement : ts.factory.createBlock([node.thenStatement])) } } },
623
+ ];
624
+ if (node.elseStatement) {
625
+ if (ts.isIfStatement(node.elseStatement)) {
626
+ fields.push({ name: "else", value: { lambda: { name: "", body: this.encodeIf(node.elseStatement) } } });
627
+ }
628
+ else {
629
+ fields.push({ name: "else", value: { lambda: { name: "", body: this.encodeBody(ts.isBlock(node.elseStatement) ? node.elseStatement : ts.factory.createBlock([node.elseStatement])) } } });
630
+ }
631
+ }
632
+ return this.stdCall("if", fields);
633
+ }
634
+ encodeFor(node) {
635
+ const fields = [];
636
+ if (node.initializer) {
637
+ if (ts.isVariableDeclarationList(node.initializer)) {
638
+ const decls = node.initializer.declarations;
639
+ if (decls.length > 0) {
640
+ const d = decls[0];
641
+ fields.push({ name: "variable", value: { literal: { stringValue: ts.isIdentifier(d.name) ? d.name.text : d.name.getText() } } });
642
+ if (d.initializer)
643
+ fields.push({ name: "start", value: this.encodeExpr(d.initializer) });
644
+ }
645
+ }
646
+ else {
647
+ fields.push({ name: "init", value: { lambda: { name: "", body: this.encodeExpr(node.initializer) } } });
648
+ }
649
+ }
650
+ if (node.condition) {
651
+ fields.push({ name: "condition", value: { lambda: { name: "", body: this.encodeExpr(node.condition) } } });
652
+ }
653
+ if (node.incrementor) {
654
+ fields.push({ name: "update", value: { lambda: { name: "", body: this.encodeExpr(node.incrementor) } } });
655
+ }
656
+ fields.push({ name: "body", value: { lambda: { name: "", body: this.encodeBody(ts.isBlock(node.statement) ? node.statement : ts.factory.createBlock([node.statement])) } } });
657
+ return this.stdCall("for", fields);
658
+ }
659
+ encodeForOf(node) {
660
+ let varName = "";
661
+ if (ts.isVariableDeclarationList(node.initializer)) {
662
+ const d = node.initializer.declarations[0];
663
+ varName = ts.isIdentifier(d.name) ? d.name.text : d.name.getText();
664
+ }
665
+ const fnName = ts.isForInStatement(node) ? "for_in" : "for_each";
666
+ return this.stdCall(fnName, [
667
+ { name: "variable", value: { literal: { stringValue: varName } } },
668
+ { name: "iterable", value: this.encodeExpr(node.expression) },
669
+ { name: "body", value: { lambda: { name: "", body: this.encodeBody(ts.isBlock(node.statement) ? node.statement : ts.factory.createBlock([node.statement])) } } },
670
+ ]);
671
+ }
672
+ encodeWhile(node) {
673
+ return this.stdCall("while", [
674
+ { name: "condition", value: { lambda: { name: "", body: this.encodeExpr(node.expression) } } },
675
+ { name: "body", value: { lambda: { name: "", body: this.encodeBody(ts.isBlock(node.statement) ? node.statement : ts.factory.createBlock([node.statement])) } } },
676
+ ]);
677
+ }
678
+ encodeDoWhile(node) {
679
+ return this.stdCall("do_while", [
680
+ { name: "condition", value: { lambda: { name: "", body: this.encodeExpr(node.expression) } } },
681
+ { name: "body", value: { lambda: { name: "", body: this.encodeBody(ts.isBlock(node.statement) ? node.statement : ts.factory.createBlock([node.statement])) } } },
682
+ ]);
683
+ }
684
+ encodeTry(node) {
685
+ const fields = [
686
+ { name: "body", value: { lambda: { name: "", body: this.encodeBlock(node.tryBlock) } } },
687
+ ];
688
+ if (node.catchClause) {
689
+ const cc = node.catchClause;
690
+ const catchFields = [];
691
+ if (cc.variableDeclaration && ts.isIdentifier(cc.variableDeclaration.name)) {
692
+ catchFields.push({ name: "variable", value: { literal: { stringValue: cc.variableDeclaration.name.text } } });
693
+ }
694
+ catchFields.push({ name: "body", value: { lambda: { name: "", body: this.encodeBlock(cc.block) } } });
695
+ fields.push({
696
+ name: "catch",
697
+ value: { messageCreation: { typeName: "", fields: catchFields } },
698
+ });
699
+ }
700
+ if (node.finallyBlock) {
701
+ fields.push({ name: "finally", value: { lambda: { name: "", body: this.encodeBlock(node.finallyBlock) } } });
702
+ }
703
+ return this.stdCall("try", fields);
704
+ }
705
+ encodeSwitch(node) {
706
+ const cases = [];
707
+ for (const clause of node.caseBlock.clauses) {
708
+ const caseFields = [];
709
+ if (ts.isCaseClause(clause)) {
710
+ caseFields.push({ name: "value", value: this.encodeExpr(clause.expression) });
711
+ }
712
+ else {
713
+ caseFields.push({ name: "isDefault", value: { literal: { boolValue: true } } });
714
+ }
715
+ const stmts = [];
716
+ for (const s of clause.statements) {
717
+ stmts.push(...this.encodeStatement(s));
718
+ }
719
+ caseFields.push({ name: "body", value: { lambda: { name: "", body: { block: { statements: stmts } } } } });
720
+ cases.push({ messageCreation: { typeName: "", fields: caseFields } });
721
+ }
722
+ return this.stdCall("switch", [
723
+ { name: "value", value: this.encodeExpr(node.expression) },
724
+ { name: "cases", value: { literal: { listValue: { elements: cases } } } },
725
+ ]);
726
+ }
727
+ encodeTemplate(node) {
728
+ let result = { literal: { stringValue: node.head.text } };
729
+ for (const span of node.templateSpans) {
730
+ const part = this.stdCall("to_string", [
731
+ { name: "value", value: this.encodeExpr(span.expression) },
732
+ ]);
733
+ result = this.stdCall("concat", [
734
+ { name: "left", value: result },
735
+ { name: "right", value: part },
736
+ ]);
737
+ if (span.literal.text) {
738
+ result = this.stdCall("concat", [
739
+ { name: "left", value: result },
740
+ { name: "right", value: { literal: { stringValue: span.literal.text } } },
741
+ ]);
742
+ }
743
+ }
744
+ return result;
745
+ }
746
+ encodeNew(node) {
747
+ const typeName = node.expression.getText();
748
+ const args = (node.arguments ?? []).map((a, i) => ({
749
+ name: `arg${i}`,
750
+ value: this.encodeExpr(a),
751
+ }));
752
+ return {
753
+ messageCreation: {
754
+ typeName,
755
+ fields: args,
756
+ },
757
+ };
758
+ }
759
+ encodeClass(node, functions, typeDefs) {
760
+ const className = node.name.text;
761
+ const descriptor = { name: className, field: [] };
762
+ const metadata = { kind: "class" };
763
+ if (node.heritageClauses) {
764
+ for (const hc of node.heritageClauses) {
765
+ if (hc.token === ts.SyntaxKind.ExtendsKeyword && hc.types.length > 0) {
766
+ metadata["superclass"] = hc.types[0].expression.getText();
767
+ }
768
+ if (hc.token === ts.SyntaxKind.ImplementsKeyword) {
769
+ metadata["interfaces"] = hc.types.map(t => t.expression.getText());
770
+ }
771
+ }
772
+ }
773
+ const fieldInitializers = {};
774
+ let fieldNum = 1;
775
+ for (const member of node.members) {
776
+ if (ts.isPropertyDeclaration(member) && ts.isIdentifier(member.name)) {
777
+ const fieldMeta = {};
778
+ if (member.initializer) {
779
+ fieldInitializers[member.name.text] = member.initializer.getText();
780
+ fieldMeta["initializer"] = member.initializer.getText();
781
+ }
782
+ const isStatic = member.modifiers?.some(m => m.kind === ts.SyntaxKind.StaticKeyword);
783
+ if (isStatic)
784
+ fieldMeta["is_static"] = true;
785
+ descriptor.field.push({
786
+ name: member.name.text,
787
+ number: fieldNum++,
788
+ type: member.type ? member.type.getText() : "any",
789
+ ...(Object.keys(fieldMeta).length > 0 ? { label: JSON.stringify(fieldMeta) } : {}),
790
+ });
791
+ }
792
+ if (ts.isMethodDeclaration(member) && ts.isIdentifier(member.name)) {
793
+ const fn = this.encodeFunction(member);
794
+ fn.name = `${className}.${fn.name}`;
795
+ const isStatic = member.modifiers?.some(m => m.kind === ts.SyntaxKind.StaticKeyword);
796
+ if (isStatic) {
797
+ if (!fn.metadata)
798
+ fn.metadata = {};
799
+ fn.metadata["is_static"] = true;
800
+ }
801
+ functions.push(fn);
802
+ }
803
+ if (ts.isConstructorDeclaration(member)) {
804
+ const fn = this.encodeFunction(member);
805
+ fn.name = `${className}.constructor`;
806
+ functions.push(fn);
807
+ }
808
+ }
809
+ if (Object.keys(fieldInitializers).length > 0) {
810
+ metadata["field_initializers"] = fieldInitializers;
811
+ }
812
+ typeDefs.push({ name: className, descriptor, metadata });
813
+ }
814
+ encodeInterface(node) {
815
+ const descriptor = { name: node.name.text, field: [] };
816
+ let fieldNum = 1;
817
+ for (const member of node.members) {
818
+ if (ts.isPropertySignature(member) && ts.isIdentifier(member.name)) {
819
+ descriptor.field.push({
820
+ name: member.name.text,
821
+ number: fieldNum++,
822
+ type: member.type ? member.type.getText() : "any",
823
+ });
824
+ }
825
+ }
826
+ return { name: node.name.text, descriptor, metadata: { kind: "interface" } };
827
+ }
828
+ encodeTypeAlias(node) {
829
+ return {
830
+ name: node.name.text,
831
+ targetType: node.type.getText(),
832
+ };
833
+ }
834
+ encodeEnum(node) {
835
+ return {
836
+ name: node.name.text,
837
+ values: node.members.map((m, i) => ({
838
+ name: ts.isIdentifier(m.name) ? m.name.text : m.name.getText(),
839
+ intValue: m.initializer && ts.isNumericLiteral(m.initializer)
840
+ ? parseInt(m.initializer.text) : i,
841
+ })),
842
+ };
843
+ }
844
+ encodeTaggedTemplate(node) {
845
+ const tag = this.encodeExpr(node.tag);
846
+ const parts = [];
847
+ const exprs = [];
848
+ if (ts.isNoSubstitutionTemplateLiteral(node.template)) {
849
+ parts.push({ literal: { stringValue: node.template.text } });
850
+ }
851
+ else {
852
+ parts.push({ literal: { stringValue: node.template.head.text } });
853
+ for (const span of node.template.templateSpans) {
854
+ exprs.push(this.encodeExpr(span.expression));
855
+ parts.push({ literal: { stringValue: span.literal.text } });
856
+ }
857
+ }
858
+ return this.stdCall("tagged_template", [
859
+ { name: "tag", value: tag },
860
+ { name: "strings", value: { literal: { listValue: { elements: parts } } } },
861
+ { name: "expressions", value: { literal: { listValue: { elements: exprs } } } },
862
+ ], "ts_std");
863
+ }
864
+ stdCall(fn, fields, module = "std") {
865
+ this.stdFunctions.add(`${module}:${fn}`);
866
+ return {
867
+ call: {
868
+ module,
869
+ function: fn,
870
+ input: fields.length > 0 ? {
871
+ messageCreation: { typeName: "", fields },
872
+ } : undefined,
873
+ },
874
+ };
875
+ }
876
+ buildStdModule() {
877
+ const functions = [];
878
+ for (const ref of this.stdFunctions) {
879
+ const [, fn] = ref.split(":");
880
+ functions.push({ name: fn, isBase: true });
881
+ }
882
+ functions.sort((a, b) => a.name.localeCompare(b.name));
883
+ return { name: "std", functions };
884
+ }
885
+ warn(msg) {
886
+ this.warnings.push(msg);
887
+ }
888
+ getWarnings() {
889
+ return [...this.warnings];
890
+ }
891
+ }
892
+ export function encode(source, options = {}) {
893
+ return new TsEncoder().encode(source, options);
894
+ }
895
+ //# sourceMappingURL=encoder.js.map