@typra/emitter 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/dist/src/cleanup/generated-file.d.ts +6 -0
  2. package/dist/src/cleanup/generated-file.js +61 -0
  3. package/dist/src/cli.d.ts +2 -0
  4. package/dist/src/cli.js +110 -0
  5. package/dist/src/decorators.d.ts +56 -0
  6. package/dist/src/decorators.js +177 -0
  7. package/dist/src/emitter.d.ts +13 -0
  8. package/dist/src/emitter.js +137 -0
  9. package/dist/src/generate.d.ts +86 -0
  10. package/dist/src/generate.js +104 -0
  11. package/dist/src/index.d.ts +4 -0
  12. package/dist/src/index.js +5 -0
  13. package/dist/src/ir/ast.d.ts +235 -0
  14. package/dist/src/ir/ast.js +589 -0
  15. package/dist/src/ir/declarations.d.ts +364 -0
  16. package/dist/src/ir/declarations.js +23 -0
  17. package/dist/src/ir/expansion.d.ts +140 -0
  18. package/dist/src/ir/expansion.js +407 -0
  19. package/dist/src/ir/lower.d.ts +53 -0
  20. package/dist/src/ir/lower.js +480 -0
  21. package/dist/src/ir/utilities.d.ts +12 -0
  22. package/dist/src/ir/utilities.js +39 -0
  23. package/dist/src/ir/visitor.d.ts +29 -0
  24. package/dist/src/ir/visitor.js +48 -0
  25. package/dist/src/languages/csharp/driver.d.ts +5 -0
  26. package/dist/src/languages/csharp/driver.js +315 -0
  27. package/dist/src/languages/csharp/emitter.d.ts +33 -0
  28. package/dist/src/languages/csharp/emitter.js +1140 -0
  29. package/dist/src/languages/csharp/scaffolding.d.ts +18 -0
  30. package/dist/src/languages/csharp/scaffolding.js +591 -0
  31. package/dist/src/languages/csharp/test-emitter.d.ts +43 -0
  32. package/dist/src/languages/csharp/test-emitter.js +274 -0
  33. package/dist/src/languages/csharp/visitor.d.ts +14 -0
  34. package/dist/src/languages/csharp/visitor.js +79 -0
  35. package/dist/src/languages/go/driver.d.ts +12 -0
  36. package/dist/src/languages/go/driver.js +128 -0
  37. package/dist/src/languages/go/emitter.d.ts +33 -0
  38. package/dist/src/languages/go/emitter.js +879 -0
  39. package/dist/src/languages/go/scaffolding.d.ts +18 -0
  40. package/dist/src/languages/go/scaffolding.js +53 -0
  41. package/dist/src/languages/go/test-emitter.d.ts +20 -0
  42. package/dist/src/languages/go/test-emitter.js +300 -0
  43. package/dist/src/languages/go/visitor.d.ts +14 -0
  44. package/dist/src/languages/go/visitor.js +78 -0
  45. package/dist/src/languages/markdown/driver.d.ts +19 -0
  46. package/dist/src/languages/markdown/driver.js +408 -0
  47. package/dist/src/languages/python/driver.d.ts +14 -0
  48. package/dist/src/languages/python/driver.js +372 -0
  49. package/dist/src/languages/python/emitter.d.ts +31 -0
  50. package/dist/src/languages/python/emitter.js +856 -0
  51. package/dist/src/languages/python/scaffolding.d.ts +33 -0
  52. package/dist/src/languages/python/scaffolding.js +279 -0
  53. package/dist/src/languages/python/test-emitter.d.ts +29 -0
  54. package/dist/src/languages/python/test-emitter.js +388 -0
  55. package/dist/src/languages/python/visitor.d.ts +14 -0
  56. package/dist/src/languages/python/visitor.js +65 -0
  57. package/dist/src/languages/rust/driver.d.ts +13 -0
  58. package/dist/src/languages/rust/driver.js +624 -0
  59. package/dist/src/languages/rust/emitter.d.ts +45 -0
  60. package/dist/src/languages/rust/emitter.js +1596 -0
  61. package/dist/src/languages/rust/visitor.d.ts +25 -0
  62. package/dist/src/languages/rust/visitor.js +153 -0
  63. package/dist/src/languages/typescript/driver.d.ts +8 -0
  64. package/dist/src/languages/typescript/driver.js +209 -0
  65. package/dist/src/languages/typescript/emitter.d.ts +42 -0
  66. package/dist/src/languages/typescript/emitter.js +904 -0
  67. package/dist/src/languages/typescript/scaffolding.d.ts +32 -0
  68. package/dist/src/languages/typescript/scaffolding.js +303 -0
  69. package/dist/src/languages/typescript/test-emitter.d.ts +23 -0
  70. package/dist/src/languages/typescript/test-emitter.js +204 -0
  71. package/dist/src/languages/typescript/visitor.d.ts +14 -0
  72. package/dist/src/languages/typescript/visitor.js +64 -0
  73. package/dist/src/lib.d.ts +33 -0
  74. package/dist/src/lib.js +101 -0
  75. package/dist/src/testing/index.d.ts +2 -0
  76. package/dist/src/testing/index.js +8 -0
  77. package/dist/src/testing/test-context.d.ts +63 -0
  78. package/dist/src/testing/test-context.js +355 -0
  79. package/fixtures/shapes/main.tsp +43 -0
  80. package/fixtures/tspconfig.yaml +13 -0
  81. package/package.json +76 -0
  82. package/src/lib/main.tsp +110 -0
@@ -0,0 +1,879 @@
1
+ /**
2
+ * Go code emitter — Declaration IR → Go source code.
3
+ *
4
+ * Replaces `file.go.njk` Nunjucks template with a typed TypeScript function
5
+ * that walks the TypeDecl tree and produces a complete Go source file.
6
+ *
7
+ * Each file contains one type hierarchy (parent + children).
8
+ * Go has no inheritance — child structs duplicate parent fields.
9
+ * Polymorphic types use interface{} and type switches.
10
+ *
11
+ * Structural blocks emitted per type (in order):
12
+ * 1. Description comment
13
+ * 2. Struct definition
14
+ * 3. Load function
15
+ * 4. Save method
16
+ * 5. ToJSON method
17
+ * 6. ToYAML method
18
+ * 7. FromJSON function
19
+ * 8. FromYAML function
20
+ */
21
+ import { toPascalCase } from "../../ir/visitor.js";
22
+ // ============================================================================
23
+ // Type maps
24
+ // ============================================================================
25
+ const GO_TYPE_MAP = {
26
+ string: "string",
27
+ number: "float64",
28
+ boolean: "bool",
29
+ int64: "int64",
30
+ int32: "int32",
31
+ float64: "float64",
32
+ float32: "float32",
33
+ integer: "int",
34
+ float: "float64",
35
+ numeric: "float64",
36
+ any: "interface{}",
37
+ unknown: "interface{}",
38
+ dictionary: "map[string]interface{}",
39
+ };
40
+ /** Go types that need numeric type-switch coercion in Load. */
41
+ const NUMERIC_GO_TYPES = new Set(["int", "int32", "int64", "float32", "float64"]);
42
+ /** Float Go types get an identity case (v = n) in the type switch. */
43
+ const FLOAT_GO_TYPES = new Set(["float32", "float64"]);
44
+ // ============================================================================
45
+ // Main entry point
46
+ // ============================================================================
47
+ /**
48
+ * Emit a complete Go source file for a type hierarchy.
49
+ *
50
+ * @param types - All TypeDecls in this file (parent first, then children)
51
+ * @param packageName - Go package name (lowercase)
52
+ * @param visitor - Expression visitor for coercion rendering
53
+ * @param polymorphicTypeNames - Set of type names that are polymorphic bases
54
+ * @param enums - Enum definitions used in this file
55
+ * @param group - Semantic group from TSP source subfolder (used as header comment only; Go stays flat)
56
+ */
57
+ export function emitGoFileContent(types, packageName, visitor, polymorphicTypeNames, enums = [], group = "") {
58
+ const lines = [];
59
+ // Protocol-only files have a simpler header (no JSON/YAML imports)
60
+ const hasNonProtocol = types.some(t => !t.isProtocol);
61
+ if (hasNonProtocol) {
62
+ emitHeader(lines, packageName, group);
63
+ }
64
+ else {
65
+ emitProtocolHeader(lines, packageName, group);
66
+ }
67
+ // Emit enum type definitions
68
+ for (const enumDef of enums) {
69
+ emitGoEnum(enumDef, lines);
70
+ }
71
+ // Emit each type in the hierarchy
72
+ for (const type of types) {
73
+ emitTypeBlock(type, lines, visitor, polymorphicTypeNames);
74
+ }
75
+ return lines.join("\n");
76
+ }
77
+ // ============================================================================
78
+ // File header
79
+ // ============================================================================
80
+ function emitHeader(lines, packageName, group = "") {
81
+ lines.push("// Code generated by Typra emitter; DO NOT EDIT.");
82
+ if (group) {
83
+ lines.push(`// Group: ${group}`);
84
+ }
85
+ lines.push("");
86
+ lines.push(`package ${packageName}`);
87
+ lines.push("");
88
+ lines.push("import (");
89
+ lines.push('\t"encoding/json"');
90
+ lines.push("");
91
+ lines.push('\t"gopkg.in/yaml.v3"');
92
+ lines.push(")");
93
+ lines.push("");
94
+ }
95
+ function emitProtocolHeader(lines, packageName, group = "") {
96
+ lines.push("// Code generated by Typra emitter; DO NOT EDIT.");
97
+ if (group) {
98
+ lines.push(`// Group: ${group}`);
99
+ }
100
+ lines.push("");
101
+ lines.push(`package ${packageName}`);
102
+ lines.push("");
103
+ }
104
+ /**
105
+ * Emit a Go enum type (type alias + const block).
106
+ */
107
+ function emitGoEnum(enumDef, lines) {
108
+ lines.push(`// ${enumDef.name} represents the allowed values for ${enumDef.name}.`);
109
+ lines.push(`type ${enumDef.name} string`);
110
+ lines.push("");
111
+ lines.push("const (");
112
+ for (const value of enumDef.values) {
113
+ const constName = `${enumDef.name}${toPascalCase(value)}`;
114
+ lines.push(`\t${constName} ${enumDef.name} = "${value}"`);
115
+ }
116
+ lines.push(")");
117
+ lines.push("");
118
+ }
119
+ // ============================================================================
120
+ // Type block — one complete type definition
121
+ // ============================================================================
122
+ function emitTypeBlock(type, lines, visitor, polymorphicTypeNames) {
123
+ const typeName = type.typeName.name;
124
+ // Protocol types → emit as Go interface
125
+ if (type.isProtocol) {
126
+ emitProtocolInterface(type, lines);
127
+ return;
128
+ }
129
+ const isPolymorphicBase = type.polymorphicDispatch !== null;
130
+ // Description comment
131
+ emitDescriptionComment(typeName, type.description, lines);
132
+ // Struct definition
133
+ emitStruct(type, lines, polymorphicTypeNames);
134
+ // Load function
135
+ emitLoadFunction(type, lines, polymorphicTypeNames);
136
+ // Save method
137
+ emitSaveMethod(type, lines, polymorphicTypeNames);
138
+ // ToWire method (only when wire mappings exist)
139
+ if (type.wire) {
140
+ emitToWireMethod(type, lines);
141
+ }
142
+ // ToJSON method
143
+ emitToJSON(typeName, lines);
144
+ // ToYAML method
145
+ emitToYAML(typeName, lines);
146
+ // FromJSON function
147
+ emitFromJSON(typeName, isPolymorphicBase, lines);
148
+ // FromYAML function
149
+ emitFromYAML(typeName, isPolymorphicBase, lines);
150
+ // Factory functions
151
+ if (type.factories.length > 0) {
152
+ for (const factory of type.factories) {
153
+ emitFactoryFunction(typeName, factory, visitor, lines);
154
+ }
155
+ }
156
+ // Method stubs
157
+ if (type.methods.length > 0) {
158
+ emitMethodStubs(typeName, type.methods, lines);
159
+ }
160
+ }
161
+ // ============================================================================
162
+ // Description comment
163
+ // ============================================================================
164
+ function emitDescriptionComment(typeName, description, lines) {
165
+ if (!description) {
166
+ lines.push(`// ${typeName} represents a schema type`);
167
+ return;
168
+ }
169
+ const descLines = description.split(/\r?\n/);
170
+ const firstLine = descLines[0];
171
+ lines.push(`// ${typeName} represents ${firstLine}`);
172
+ for (let i = 1; i < descLines.length; i++) {
173
+ const line = descLines[i];
174
+ if (line.trim() === "") {
175
+ lines.push("//");
176
+ }
177
+ else {
178
+ lines.push(`// ${line}`);
179
+ }
180
+ }
181
+ // Blank line after description (unless it's "a schema type")
182
+ if (description !== "a schema type") {
183
+ lines.push("");
184
+ }
185
+ }
186
+ // ============================================================================
187
+ // Struct definition
188
+ // ============================================================================
189
+ function emitStruct(type, lines, polymorphicTypeNames) {
190
+ const typeName = type.typeName.name;
191
+ lines.push(`type ${typeName} struct {`);
192
+ for (const field of type.fields) {
193
+ const goType = getGoFieldType(field.category, field.isOptional, polymorphicTypeNames, field.enumName);
194
+ const fieldName = toPascalCase(field.name);
195
+ const tag = getStructTag(field.name, field.isOptional);
196
+ lines.push(`\t${fieldName} ${goType} ${tag}`);
197
+ }
198
+ lines.push("}");
199
+ lines.push("");
200
+ }
201
+ // ============================================================================
202
+ // Load function
203
+ // ============================================================================
204
+ function emitLoadFunction(type, lines, polymorphicTypeNames) {
205
+ const typeName = type.typeName.name;
206
+ const isPolymorphicBase = type.polymorphicDispatch !== null;
207
+ const hasCoercions = type.load.coercions.length > 0;
208
+ const returnType = isPolymorphicBase ? "interface{}" : typeName;
209
+ // Comment
210
+ lines.push(`// Load${typeName} creates a ${typeName} from a map[string]interface{}`);
211
+ if (isPolymorphicBase) {
212
+ lines.push("// Returns interface{} because this is a polymorphic base type that can resolve to different child types");
213
+ }
214
+ // Signature
215
+ lines.push(`func Load${typeName}(data interface{}, ctx *LoadContext) (${returnType}, error) {`);
216
+ lines.push(`\tresult := ${typeName}{}`);
217
+ lines.push("");
218
+ // 1. Polymorphic dispatch
219
+ if (type.polymorphicDispatch) {
220
+ emitPolymorphicDispatch(type.polymorphicDispatch, lines);
221
+ }
222
+ // 2. Coercions
223
+ if (hasCoercions) {
224
+ emitCoercions(type.load.coercions, typeName, lines);
225
+ }
226
+ // 3. Map loading
227
+ lines.push("\t// Load from map");
228
+ lines.push("\tif m, ok := data.(map[string]interface{}); ok {");
229
+ for (const assign of type.load.assignments) {
230
+ emitLoadAssignment(assign, polymorphicTypeNames, lines);
231
+ }
232
+ lines.push("\t}");
233
+ lines.push("");
234
+ lines.push("\treturn result, nil");
235
+ lines.push("}");
236
+ lines.push("");
237
+ }
238
+ // ============================================================================
239
+ // Polymorphic dispatch
240
+ // ============================================================================
241
+ function emitPolymorphicDispatch(dispatch, lines) {
242
+ lines.push("\t// Handle polymorphic types based on discriminator");
243
+ lines.push("\tif m, ok := data.(map[string]interface{}); ok {");
244
+ lines.push(`\t\tif discriminator, ok := m["${dispatch.discriminatorField}"]; ok {`);
245
+ lines.push("\t\t\tswitch discriminator {");
246
+ for (const variant of dispatch.variants) {
247
+ lines.push(`\t\t\tcase "${variant.value}":`);
248
+ lines.push(`\t\t\t\treturn Load${variant.typeName.name}(data, ctx)`);
249
+ }
250
+ // Default variant
251
+ if (dispatch.defaultVariant && !dispatch.defaultVariant.isSelfReference) {
252
+ lines.push("\t\t\tdefault:");
253
+ lines.push(`\t\t\t\treturn Load${dispatch.defaultVariant.typeName.name}(data, ctx)`);
254
+ }
255
+ lines.push("\t\t\t}");
256
+ lines.push("\t\t}");
257
+ lines.push("\t}");
258
+ }
259
+ // ============================================================================
260
+ // Coercions
261
+ // ============================================================================
262
+ function emitCoercions(coercions, typeName, lines) {
263
+ lines.push("\t// Handle alternate scalar representations");
264
+ lines.push("\tswitch v := data.(type) {");
265
+ for (const coercion of coercions) {
266
+ const goType = GO_TYPE_MAP[coercion.scalarType] || coercion.scalarType;
267
+ lines.push(`\tcase ${goType}:`);
268
+ lines.push(`\t\t// Shorthand: ${goType} -> ${typeName}`);
269
+ // Build expansion map
270
+ const entries = [];
271
+ for (const assign of coercion.assignments) {
272
+ if (assign.isInput) {
273
+ entries.push(`"${assign.fieldName}": v`);
274
+ }
275
+ else {
276
+ entries.push(`"${assign.fieldName}": "${assign.literalValue}"`);
277
+ }
278
+ }
279
+ lines.push(`\t\texpansion := map[string]interface{}{${entries.join(", ")}}`);
280
+ lines.push(`\t\treturn Load${typeName}(expansion, ctx)`);
281
+ }
282
+ lines.push("\t}");
283
+ }
284
+ // ============================================================================
285
+ // Load assignment (per-property)
286
+ // ============================================================================
287
+ function emitLoadAssignment(assign, polymorphicTypeNames, lines) {
288
+ const fieldName = toPascalCase(assign.fieldName);
289
+ const cat = assign.category;
290
+ switch (cat.kind) {
291
+ case "scalar":
292
+ emitLoadScalar(assign, fieldName, cat.scalarType, lines);
293
+ break;
294
+ case "complex":
295
+ emitLoadComplex(assign, fieldName, cat.typeName, polymorphicTypeNames, lines);
296
+ break;
297
+ case "collection_scalar":
298
+ emitLoadCollectionScalar(assign, fieldName, cat.scalarType, lines);
299
+ break;
300
+ case "collection_complex":
301
+ emitLoadCollectionComplex(assign, fieldName, cat.typeName, polymorphicTypeNames, lines);
302
+ break;
303
+ case "dict":
304
+ emitLoadDict(assign, fieldName, lines);
305
+ break;
306
+ }
307
+ }
308
+ function emitLoadScalar(assign, fieldName, scalarType, lines) {
309
+ const goType = GO_TYPE_MAP[scalarType] || "interface{}";
310
+ if (NUMERIC_GO_TYPES.has(goType)) {
311
+ emitLoadNumeric(assign, fieldName, goType, lines);
312
+ return;
313
+ }
314
+ if (goType === "interface{}") {
315
+ // any/unknown type
316
+ if (assign.isOptional) {
317
+ lines.push(`\t\tif val, ok := m["${assign.sourceName}"]; ok && val != nil {`);
318
+ lines.push(`\t\t\tresult.${fieldName} = &val`);
319
+ lines.push("\t\t}");
320
+ }
321
+ else {
322
+ lines.push(`\t\tif val, ok := m["${assign.sourceName}"]; ok && val != nil {`);
323
+ lines.push(`\t\t\tresult.${fieldName} = val`);
324
+ lines.push("\t\t}");
325
+ }
326
+ return;
327
+ }
328
+ if (goType === "bool") {
329
+ if (assign.isOptional) {
330
+ lines.push(`\t\tif val, ok := m["${assign.sourceName}"]; ok && val != nil {`);
331
+ lines.push(`\t\t\tv := val.(bool)`);
332
+ lines.push(`\t\t\tresult.${fieldName} = &v`);
333
+ lines.push("\t\t}");
334
+ }
335
+ else {
336
+ lines.push(`\t\tif val, ok := m["${assign.sourceName}"]; ok && val != nil {`);
337
+ lines.push(`\t\t\tresult.${fieldName} = val.(bool)`);
338
+ lines.push("\t\t}");
339
+ }
340
+ return;
341
+ }
342
+ // string (and others) — if the field is a named enum type, cast from string
343
+ const castType = assign.enumName ?? goType;
344
+ if (assign.isOptional) {
345
+ lines.push(`\t\tif val, ok := m["${assign.sourceName}"]; ok && val != nil {`);
346
+ lines.push(`\t\t\tv := ${castType}(val.(string))`);
347
+ lines.push(`\t\t\tresult.${fieldName} = &v`);
348
+ lines.push("\t\t}");
349
+ }
350
+ else {
351
+ lines.push(`\t\tif val, ok := m["${assign.sourceName}"]; ok && val != nil {`);
352
+ lines.push(`\t\t\tresult.${fieldName} = ${castType}(val.(string))`);
353
+ lines.push("\t\t}");
354
+ }
355
+ }
356
+ function emitLoadNumeric(assign, fieldName, goType, lines) {
357
+ const isFloat = FLOAT_GO_TYPES.has(goType);
358
+ // Build the coercion cases
359
+ const cases = [];
360
+ // int types: int, int32, int64, float64
361
+ // float types: int, int32, int64, float32, float64
362
+ cases.push({ caseType: "int", expr: `${goType}(n)` });
363
+ cases.push({ caseType: "int32", expr: `${goType}(n)` });
364
+ cases.push({ caseType: "int64", expr: `${goType}(n)` });
365
+ if (isFloat) {
366
+ // Identity case for float types uses direct assignment
367
+ if (goType === "float32") {
368
+ cases.push({ caseType: "float32", expr: "n" });
369
+ cases.push({ caseType: "float64", expr: `${goType}(n)` });
370
+ }
371
+ else {
372
+ // float64
373
+ cases.push({ caseType: "float32", expr: `${goType}(n)` });
374
+ cases.push({ caseType: "float64", expr: "n" });
375
+ }
376
+ }
377
+ else {
378
+ // int types don't include float32
379
+ cases.push({ caseType: "float64", expr: `${goType}(n)` });
380
+ }
381
+ if (assign.isOptional) {
382
+ lines.push(`\t\tif val, ok := m["${assign.sourceName}"]; ok && val != nil { // Handle various numeric types from JSON/YAML/roundtrip`);
383
+ lines.push(`\t\t\tvar v ${goType}`);
384
+ lines.push("\t\t\tswitch n := val.(type) {");
385
+ for (const c of cases) {
386
+ lines.push(`\t\t\tcase ${c.caseType}:`);
387
+ lines.push(`\t\t\t\tv = ${c.expr}`);
388
+ }
389
+ lines.push("\t\t\t}");
390
+ lines.push(`\t\t\tresult.${fieldName} = &v`);
391
+ lines.push("\t\t}");
392
+ }
393
+ else {
394
+ lines.push(`\t\tif val, ok := m["${assign.sourceName}"]; ok && val != nil { // Handle various numeric types from JSON/YAML/roundtrip`);
395
+ lines.push(`\t\t\tvar v ${goType}`);
396
+ lines.push("\t\t\tswitch n := val.(type) {");
397
+ for (const c of cases) {
398
+ lines.push(`\t\t\tcase ${c.caseType}:`);
399
+ lines.push(`\t\t\t\tv = ${c.expr}`);
400
+ }
401
+ lines.push("\t\t\t}");
402
+ lines.push(`\t\t\tresult.${fieldName} = v`);
403
+ lines.push("\t\t}");
404
+ }
405
+ }
406
+ function emitLoadComplex(assign, fieldName, typeName, polymorphicTypeNames, lines) {
407
+ const isPolymorphic = polymorphicTypeNames.has(typeName);
408
+ lines.push(`\t\tif val, ok := m["${assign.sourceName}"]; ok && val != nil {`);
409
+ lines.push(`\t\t\tif m, ok := val.(map[string]interface{}); ok {`);
410
+ lines.push(`\t\t\t\tloaded, _ := Load${typeName}(m, ctx)`);
411
+ if (isPolymorphic) {
412
+ if (assign.isOptional) {
413
+ lines.push(`\t\t\t\t// Polymorphic type - keep as interface{} (no pointer needed, interface{} can be nil)`);
414
+ }
415
+ else {
416
+ lines.push(`\t\t\t\t// Polymorphic type - keep as interface{}`);
417
+ }
418
+ lines.push(`\t\t\t\tresult.${fieldName} = loaded`);
419
+ }
420
+ else {
421
+ if (assign.isOptional) {
422
+ lines.push(`\t\t\t\tresult.${fieldName} = &loaded`);
423
+ }
424
+ else {
425
+ lines.push(`\t\t\t\tresult.${fieldName} = loaded`);
426
+ }
427
+ }
428
+ lines.push("\t\t\t}");
429
+ lines.push("\t\t}");
430
+ }
431
+ function emitLoadCollectionScalar(assign, fieldName, scalarType, lines) {
432
+ const goType = GO_TYPE_MAP[scalarType] || "interface{}";
433
+ lines.push(`\t\tif val, ok := m["${assign.sourceName}"]; ok && val != nil {`);
434
+ lines.push("\t\t\tif arr, ok := val.([]interface{}); ok {");
435
+ if (goType === "interface{}") {
436
+ // any/unknown — assign entire array directly
437
+ lines.push(`\t\t\t\tresult.${fieldName} = arr`);
438
+ }
439
+ else {
440
+ // Typed elements — per-element cast
441
+ lines.push(`\t\t\t\tresult.${fieldName} = make([]${goType}, len(arr))`);
442
+ lines.push("\t\t\t\tfor i, v := range arr {");
443
+ lines.push(`\t\t\t\t\tresult.${fieldName}[i] = v.(${goType})`);
444
+ lines.push("\t\t\t\t}");
445
+ }
446
+ lines.push("\t\t\t}");
447
+ lines.push("\t\t}");
448
+ }
449
+ function emitLoadCollectionComplex(assign, fieldName, typeName, polymorphicTypeNames, lines) {
450
+ const isPolymorphic = polymorphicTypeNames.has(typeName);
451
+ const goElemType = isPolymorphic ? "interface{}" : typeName;
452
+ lines.push(`\t\tif val, ok := m["${assign.sourceName}"]; ok && val != nil {`);
453
+ lines.push("\t\t\tif arr, ok := val.([]interface{}); ok {");
454
+ lines.push(`\t\t\t\tresult.${fieldName} = make([]${goElemType}, len(arr))`);
455
+ lines.push("\t\t\t\tfor i, v := range arr {");
456
+ lines.push("\t\t\t\t\tif item, ok := v.(map[string]interface{}); ok {");
457
+ lines.push(`\t\t\t\t\t\tloaded, _ := Load${typeName}(item, ctx)`);
458
+ if (isPolymorphic) {
459
+ lines.push("\t\t\t\t\t\t// Polymorphic type - store as interface{}");
460
+ lines.push(`\t\t\t\t\t\tresult.${fieldName}[i] = loaded`);
461
+ }
462
+ else {
463
+ lines.push(`\t\t\t\t\t\tresult.${fieldName}[i] = loaded`);
464
+ }
465
+ lines.push("\t\t\t\t\t}");
466
+ lines.push("\t\t\t\t}");
467
+ lines.push("\t\t\t}");
468
+ lines.push("\t\t}");
469
+ }
470
+ function emitLoadDict(assign, fieldName, lines) {
471
+ lines.push(`\t\tif val, ok := m["${assign.sourceName}"]; ok && val != nil {`);
472
+ lines.push("\t\t\tif m, ok := val.(map[string]interface{}); ok {");
473
+ lines.push(`\t\t\t\tresult.${fieldName} = m`);
474
+ lines.push("\t\t\t}");
475
+ lines.push("\t\t}");
476
+ }
477
+ // ============================================================================
478
+ // Save method
479
+ // ============================================================================
480
+ function emitSaveMethod(type, lines, polymorphicTypeNames) {
481
+ const typeName = type.typeName.name;
482
+ lines.push(`// Save serializes ${typeName} to map[string]interface{}`);
483
+ lines.push(`func (obj *${typeName}) Save(ctx *SaveContext) map[string]interface{} {`);
484
+ lines.push("\tresult := make(map[string]interface{})");
485
+ for (const assign of type.save.assignments) {
486
+ emitSaveAssignment(assign, polymorphicTypeNames, lines);
487
+ }
488
+ lines.push("");
489
+ lines.push("\treturn result");
490
+ lines.push("}");
491
+ lines.push("");
492
+ }
493
+ // ============================================================================
494
+ // Save assignment (per-property)
495
+ // ============================================================================
496
+ function emitSaveAssignment(assign, polymorphicTypeNames, lines) {
497
+ const fieldName = toPascalCase(assign.fieldName);
498
+ const cat = assign.category;
499
+ switch (cat.kind) {
500
+ case "scalar":
501
+ emitSaveScalar(assign, fieldName, cat.scalarType, lines);
502
+ break;
503
+ case "complex":
504
+ emitSaveComplex(assign, fieldName, cat.typeName, polymorphicTypeNames, lines);
505
+ break;
506
+ case "collection_scalar":
507
+ emitSaveCollectionScalar(assign, fieldName, lines);
508
+ break;
509
+ case "collection_complex":
510
+ emitSaveCollectionComplex(assign, fieldName, cat.typeName, polymorphicTypeNames, lines);
511
+ break;
512
+ case "dict":
513
+ emitSaveDict(assign, fieldName, lines);
514
+ break;
515
+ }
516
+ }
517
+ function emitSaveScalar(assign, fieldName, scalarType, lines) {
518
+ const goType = GO_TYPE_MAP[scalarType] || "interface{}";
519
+ // Named enum fields must be cast back to string so roundtrip Load can do val.(string)
520
+ const saveExpr = assign.enumName ? `string(obj.${fieldName})` : `obj.${fieldName}`;
521
+ const saveExprDeref = assign.enumName ? `string(*obj.${fieldName})` : `*obj.${fieldName}`;
522
+ if (assign.isOptional) {
523
+ lines.push(`\tif obj.${fieldName} != nil {`);
524
+ lines.push(`\t\tresult["${assign.targetName}"] = ${saveExprDeref}`);
525
+ lines.push("\t}");
526
+ }
527
+ else {
528
+ lines.push(`\tresult["${assign.targetName}"] = ${saveExpr}`);
529
+ }
530
+ }
531
+ function emitSaveComplex(assign, fieldName, typeName, polymorphicTypeNames, lines) {
532
+ const isPolymorphic = polymorphicTypeNames.has(typeName);
533
+ if (isPolymorphic) {
534
+ if (assign.isOptional) {
535
+ // Optional polymorphic complex — double nil check pattern
536
+ lines.push(`\tif obj.${fieldName} != nil {`);
537
+ lines.push(`\t\t// Handle polymorphic type (stored as interface{} without pointer)`);
538
+ lines.push(`\t\tif obj.${fieldName} != nil {`);
539
+ lines.push(`\t\t\tswitch v := obj.${fieldName}.(type) {`);
540
+ lines.push("\t\t\tcase interface {");
541
+ lines.push("\t\t\t\tSave(*SaveContext) map[string]interface{}");
542
+ lines.push("\t\t\t}:");
543
+ lines.push(`\t\t\t\tresult["${assign.targetName}"] = v.Save(ctx)`);
544
+ lines.push("\t\t\tdefault:");
545
+ lines.push(`\t\t\t\tresult["${assign.targetName}"] = obj.${fieldName}`);
546
+ lines.push("\t\t\t}");
547
+ lines.push("\t\t}");
548
+ lines.push("\t}");
549
+ }
550
+ else {
551
+ // Required polymorphic complex — blank line before, type switch
552
+ lines.push("");
553
+ lines.push("\t// Handle polymorphic type via type switch");
554
+ lines.push(`\tswitch v := obj.${fieldName}.(type) {`);
555
+ lines.push("\tcase interface {");
556
+ lines.push("\t\tSave(*SaveContext) map[string]interface{}");
557
+ lines.push("\t}:");
558
+ lines.push(`\t\tresult["${assign.targetName}"] = v.Save(ctx)`);
559
+ lines.push("\tdefault:");
560
+ lines.push(`\t\tresult["${assign.targetName}"] = obj.${fieldName}`);
561
+ lines.push("\t}");
562
+ }
563
+ }
564
+ else {
565
+ if (assign.isOptional) {
566
+ lines.push(`\tif obj.${fieldName} != nil {`);
567
+ lines.push(`\t\tresult["${assign.targetName}"] = obj.${fieldName}.Save(ctx)`);
568
+ lines.push("\t}");
569
+ }
570
+ else {
571
+ // Required non-polymorphic complex — blank line before
572
+ lines.push("");
573
+ lines.push(`\tresult["${assign.targetName}"] = obj.${fieldName}.Save(ctx)`);
574
+ }
575
+ }
576
+ }
577
+ function emitSaveCollectionScalar(assign, fieldName, lines) {
578
+ // Collection scalars are always saved directly (no nil check)
579
+ lines.push(`\tresult["${assign.targetName}"] = obj.${fieldName}`);
580
+ }
581
+ function emitSaveCollectionComplex(assign, fieldName, typeName, polymorphicTypeNames, lines) {
582
+ const isPolymorphic = polymorphicTypeNames.has(typeName);
583
+ lines.push(`\tif obj.${fieldName} != nil {`);
584
+ if (isPolymorphic) {
585
+ lines.push(`\t\tarr := make([]interface{}, len(obj.${fieldName}))`);
586
+ lines.push(`\t\tfor i, item := range obj.${fieldName} {`);
587
+ lines.push("\t\t\t// Handle polymorphic type via type switch");
588
+ lines.push("\t\t\tswitch v := item.(type) {");
589
+ lines.push("\t\t\tcase interface {");
590
+ lines.push("\t\t\t\tSave(*SaveContext) map[string]interface{}");
591
+ lines.push("\t\t\t}:");
592
+ lines.push("\t\t\t\tarr[i] = v.Save(ctx)");
593
+ lines.push("\t\t\tdefault:");
594
+ lines.push("\t\t\t\tarr[i] = item");
595
+ lines.push("\t\t\t}");
596
+ lines.push("\t\t}");
597
+ lines.push(`\t\tresult["${assign.targetName}"] = arr`);
598
+ }
599
+ else {
600
+ lines.push(`\t\tarr := make([]interface{}, len(obj.${fieldName}))`);
601
+ lines.push(`\t\tfor i, item := range obj.${fieldName} {`);
602
+ lines.push("\t\t\tarr[i] = item.Save(ctx)");
603
+ lines.push("\t\t}");
604
+ lines.push(`\t\tresult["${assign.targetName}"] = arr`);
605
+ }
606
+ lines.push("\t}");
607
+ }
608
+ function emitSaveDict(assign, fieldName, lines) {
609
+ if (assign.isOptional) {
610
+ lines.push(`\tif obj.${fieldName} != nil {`);
611
+ lines.push(`\t\tresult["${assign.targetName}"] = obj.${fieldName}`);
612
+ lines.push("\t}");
613
+ }
614
+ else {
615
+ lines.push(`\tresult["${assign.targetName}"] = obj.${fieldName}`);
616
+ }
617
+ }
618
+ // ============================================================================
619
+ // ToWire method (provider-specific wire format)
620
+ // ============================================================================
621
+ function emitToWireMethod(type, lines) {
622
+ const typeName = type.typeName.name;
623
+ const wire = type.wire;
624
+ lines.push(`// ToWire converts to provider-specific wire format.`);
625
+ lines.push(`func (obj *${typeName}) ToWire(provider string) map[string]interface{} {`);
626
+ lines.push(`\tdata := obj.Save(nil)`);
627
+ lines.push(`\tresult := make(map[string]interface{})`);
628
+ lines.push(`\twireMap := map[string]map[string]string{`);
629
+ for (const mapping of wire.mappings) {
630
+ const entries = Object.entries(mapping.wireNames)
631
+ .map(([provider, wireName]) => `"${provider}": "${wireName}"`)
632
+ .join(", ");
633
+ lines.push(`\t\t"${mapping.fieldName}": {${entries}},`);
634
+ }
635
+ lines.push(`\t}`);
636
+ lines.push(`\tfor key, value := range data {`);
637
+ lines.push(`\t\tif mapping, ok := wireMap[key]; ok {`);
638
+ lines.push(`\t\t\tif wireName, ok := mapping[provider]; ok {`);
639
+ lines.push(`\t\t\t\tresult[wireName] = value`);
640
+ lines.push(`\t\t\t}`);
641
+ lines.push(`\t\t}`);
642
+ lines.push(`\t}`);
643
+ lines.push(`\treturn result`);
644
+ lines.push(`}`);
645
+ lines.push(``);
646
+ }
647
+ // ============================================================================
648
+ // ToJSON / ToYAML methods
649
+ // ============================================================================
650
+ function emitToJSON(typeName, lines) {
651
+ lines.push(`// ToJSON serializes ${typeName} to JSON string`);
652
+ lines.push(`func (obj *${typeName}) ToJSON() (string, error) {`);
653
+ lines.push("\tctx := NewSaveContext()");
654
+ lines.push("\tdata := obj.Save(ctx)");
655
+ lines.push("\tbytes, err := json.Marshal(data)");
656
+ lines.push("\tif err != nil {");
657
+ lines.push('\t\treturn "", err');
658
+ lines.push("\t}");
659
+ lines.push("\treturn string(bytes), nil");
660
+ lines.push("}");
661
+ lines.push("");
662
+ }
663
+ function emitToYAML(typeName, lines) {
664
+ lines.push(`// ToYAML serializes ${typeName} to YAML string`);
665
+ lines.push(`func (obj *${typeName}) ToYAML() (string, error) {`);
666
+ lines.push("\tctx := NewSaveContext()");
667
+ lines.push("\tdata := obj.Save(ctx)");
668
+ lines.push("\tbytes, err := yaml.Marshal(data)");
669
+ lines.push("\tif err != nil {");
670
+ lines.push('\t\treturn "", err');
671
+ lines.push("\t}");
672
+ lines.push("\treturn string(bytes), nil");
673
+ lines.push("}");
674
+ lines.push("");
675
+ }
676
+ // ============================================================================
677
+ // FromJSON / FromYAML functions
678
+ // ============================================================================
679
+ function emitFromJSON(typeName, isPolymorphic, lines) {
680
+ lines.push(`// FromJSON creates ${typeName} from JSON string`);
681
+ if (isPolymorphic) {
682
+ lines.push("// Returns interface{} because this is a polymorphic base type that can resolve to different child types");
683
+ }
684
+ const returnType = isPolymorphic ? "interface{}" : typeName;
685
+ const errorReturn = isPolymorphic ? "nil" : `${typeName}{}`;
686
+ lines.push(`func ${typeName}FromJSON(jsonStr string) (${returnType}, error) {`);
687
+ lines.push("\tvar data map[string]interface{}");
688
+ lines.push("\tif err := json.Unmarshal([]byte(jsonStr), &data); err != nil {");
689
+ lines.push(`\t\treturn ${errorReturn}, err`);
690
+ lines.push("\t}");
691
+ lines.push("\tctx := NewLoadContext()");
692
+ lines.push(`\treturn Load${typeName}(data, ctx)`);
693
+ lines.push("}");
694
+ lines.push("");
695
+ }
696
+ function emitFromYAML(typeName, isPolymorphic, lines) {
697
+ lines.push(`// FromYAML creates ${typeName} from YAML string`);
698
+ if (isPolymorphic) {
699
+ lines.push("// Returns interface{} because this is a polymorphic base type that can resolve to different child types");
700
+ }
701
+ const returnType = isPolymorphic ? "interface{}" : typeName;
702
+ const errorReturn = isPolymorphic ? "nil" : `${typeName}{}`;
703
+ lines.push(`func ${typeName}FromYAML(yamlStr string) (${returnType}, error) {`);
704
+ lines.push("\tvar data map[string]interface{}");
705
+ lines.push("\tif err := yaml.Unmarshal([]byte(yamlStr), &data); err != nil {");
706
+ lines.push(`\t\treturn ${errorReturn}, err`);
707
+ lines.push("\t}");
708
+ lines.push("\tctx := NewLoadContext()");
709
+ lines.push(`\treturn Load${typeName}(data, ctx)`);
710
+ lines.push("}");
711
+ lines.push("");
712
+ }
713
+ // ============================================================================
714
+ // Type helpers
715
+ // ============================================================================
716
+ function getGoFieldType(category, isOptional, polymorphicTypeNames, enumName) {
717
+ // Named enum — use enum type alias
718
+ if (enumName) {
719
+ return isOptional ? `*${enumName}` : enumName;
720
+ }
721
+ switch (category.kind) {
722
+ case "scalar": {
723
+ const goType = GO_TYPE_MAP[category.scalarType] || "interface{}";
724
+ if (goType === "interface{}") {
725
+ return isOptional ? "*interface{}" : "interface{}";
726
+ }
727
+ return isOptional ? `*${goType}` : goType;
728
+ }
729
+ case "complex": {
730
+ const isPolymorphic = polymorphicTypeNames.has(category.typeName);
731
+ if (isPolymorphic) {
732
+ // Polymorphic complex: always interface{} (no pointer even for optional)
733
+ return "interface{}";
734
+ }
735
+ return isOptional ? `*${category.typeName}` : category.typeName;
736
+ }
737
+ case "collection_scalar": {
738
+ const goType = GO_TYPE_MAP[category.scalarType] || "interface{}";
739
+ return `[]${goType}`;
740
+ }
741
+ case "collection_complex": {
742
+ const isPolymorphic = polymorphicTypeNames.has(category.typeName);
743
+ if (isPolymorphic) {
744
+ return "[]interface{}";
745
+ }
746
+ return `[]${category.typeName}`;
747
+ }
748
+ case "dict":
749
+ return "map[string]interface{}";
750
+ }
751
+ }
752
+ function getStructTag(fieldName, isOptional) {
753
+ const jsonTag = isOptional ? `${fieldName},omitempty` : fieldName;
754
+ const yamlTag = isOptional ? `${fieldName},omitempty` : fieldName;
755
+ return `\`json:"${jsonTag}" yaml:"${yamlTag}"\``;
756
+ }
757
+ // ============================================================================
758
+ // Factory functions
759
+ // ============================================================================
760
+ /** Emit a package-level factory function that creates a pre-populated struct. */
761
+ function emitFactoryFunction(typeName, factory, visitor, lines) {
762
+ const funcName = getGoFactoryName(factory.name, typeName);
763
+ const params = Object.entries(factory.params)
764
+ .map(([pName, pType]) => `${pName} ${goFactoryParamType(pType)}`)
765
+ .join(", ");
766
+ lines.push(`// ${funcName} creates a ${typeName} with preset field values.`);
767
+ lines.push(`func ${funcName}(${params}) ${typeName} {`);
768
+ lines.push(`\treturn ${visitor.visitExpr(factory.body)}`);
769
+ lines.push("}");
770
+ lines.push("");
771
+ }
772
+ /** Map factory name to an exported Go function name, prefixed with the type for disambiguation. */
773
+ function getGoFactoryName(factoryName, typeName) {
774
+ // Go factories are package-level functions, so prefix with type name to avoid collisions.
775
+ // e.g., Message.user → NewUserMessage, GuardrailResult.allow → NewAllowGuardrailResult
776
+ const capitalizedFactory = factoryName.charAt(0).toUpperCase() + factoryName.slice(1);
777
+ return `New${capitalizedFactory}${typeName}`;
778
+ }
779
+ /** Map IR type strings to Go parameter types. */
780
+ function goFactoryParamType(typeStr) {
781
+ switch (typeStr) {
782
+ case "string":
783
+ return "string";
784
+ case "boolean":
785
+ return "bool";
786
+ case "int32":
787
+ return "int32";
788
+ case "int64":
789
+ case "integer":
790
+ return "int64";
791
+ case "float32":
792
+ return "float32";
793
+ case "float64":
794
+ case "float":
795
+ return "float64";
796
+ case "unknown":
797
+ case "any":
798
+ return "interface{}";
799
+ default:
800
+ return typeStr;
801
+ }
802
+ }
803
+ // ============================================================================
804
+ // Method stubs
805
+ // ============================================================================
806
+ /** Emit Go interface + comment stubs for @method-decorated helpers. */
807
+ function emitMethodStubs(typeName, methods, lines) {
808
+ // Emit an interface that the user implements in a separate file.
809
+ const interfaceName = `${typeName}Helpers`;
810
+ lines.push(`// ${interfaceName} defines helper methods for ${typeName}.`);
811
+ lines.push(`// Implement these in a separate file (e.g., ${typeName.toLowerCase()}_helpers.go).`);
812
+ lines.push(`type ${interfaceName} interface {`);
813
+ for (const method of methods) {
814
+ if (method.description) {
815
+ lines.push(`\t// ${toPascalCase(method.name)} — ${method.description}`);
816
+ }
817
+ lines.push(`\t${toPascalCase(method.name)}() ${goMethodReturnType(method.returns)}`);
818
+ }
819
+ lines.push("}");
820
+ lines.push("");
821
+ }
822
+ function goMethodReturnType(returns) {
823
+ return GO_TYPE_MAP[returns] || returns;
824
+ }
825
+ // ============================================================================
826
+ // Protocol interface emission
827
+ // ============================================================================
828
+ /** Map a protocol type string to a Go type. */
829
+ function protocolGoType(typeStr) {
830
+ // Handle nullable types
831
+ if (typeStr.endsWith("?")) {
832
+ const inner = typeStr.slice(0, -1);
833
+ return `*${protocolGoType(inner)}`;
834
+ }
835
+ // Handle array types
836
+ if (typeStr.endsWith("[]")) {
837
+ const inner = typeStr.slice(0, -2);
838
+ return `[]${protocolGoType(inner)}`;
839
+ }
840
+ if (typeStr === "Record<unknown>" || typeStr === "dictionary")
841
+ return "map[string]interface{}";
842
+ if (typeStr === "unknown" || typeStr === "any")
843
+ return "interface{}";
844
+ return GO_TYPE_MAP[typeStr] || typeStr;
845
+ }
846
+ /**
847
+ * Emit a Go interface for a protocol type.
848
+ */
849
+ function emitProtocolInterface(type, lines) {
850
+ const name = type.typeName.name;
851
+ if (type.description) {
852
+ emitDescriptionComment(name, type.description, lines);
853
+ }
854
+ lines.push(`type ${name} interface {`);
855
+ for (const method of type.methods) {
856
+ if (method.description) {
857
+ lines.push(`\t// ${toPascalCase(method.name)} — ${method.description}`);
858
+ }
859
+ const params = Object.entries(method.params)
860
+ .map(([pName, pType]) => `${pName} ${protocolGoType(pType)}`)
861
+ .join(", ");
862
+ const ret = protocolGoType(method.returns);
863
+ if (method.sync) {
864
+ // Sync method — return type without error for optional
865
+ if (method.optional) {
866
+ lines.push(`\t${toPascalCase(method.name)}(${params}) ${ret}`);
867
+ }
868
+ else {
869
+ lines.push(`\t${toPascalCase(method.name)}(${params}) (${ret}, error)`);
870
+ }
871
+ }
872
+ else {
873
+ lines.push(`\t${toPascalCase(method.name)}(${params}) (${ret}, error)`);
874
+ }
875
+ }
876
+ lines.push("}");
877
+ lines.push("");
878
+ }
879
+ //# sourceMappingURL=emitter.js.map