@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.
- package/dist/src/cleanup/generated-file.d.ts +6 -0
- package/dist/src/cleanup/generated-file.js +61 -0
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +110 -0
- package/dist/src/decorators.d.ts +56 -0
- package/dist/src/decorators.js +177 -0
- package/dist/src/emitter.d.ts +13 -0
- package/dist/src/emitter.js +137 -0
- package/dist/src/generate.d.ts +86 -0
- package/dist/src/generate.js +104 -0
- package/dist/src/index.d.ts +4 -0
- package/dist/src/index.js +5 -0
- package/dist/src/ir/ast.d.ts +235 -0
- package/dist/src/ir/ast.js +589 -0
- package/dist/src/ir/declarations.d.ts +364 -0
- package/dist/src/ir/declarations.js +23 -0
- package/dist/src/ir/expansion.d.ts +140 -0
- package/dist/src/ir/expansion.js +407 -0
- package/dist/src/ir/lower.d.ts +53 -0
- package/dist/src/ir/lower.js +480 -0
- package/dist/src/ir/utilities.d.ts +12 -0
- package/dist/src/ir/utilities.js +39 -0
- package/dist/src/ir/visitor.d.ts +29 -0
- package/dist/src/ir/visitor.js +48 -0
- package/dist/src/languages/csharp/driver.d.ts +5 -0
- package/dist/src/languages/csharp/driver.js +315 -0
- package/dist/src/languages/csharp/emitter.d.ts +33 -0
- package/dist/src/languages/csharp/emitter.js +1140 -0
- package/dist/src/languages/csharp/scaffolding.d.ts +18 -0
- package/dist/src/languages/csharp/scaffolding.js +591 -0
- package/dist/src/languages/csharp/test-emitter.d.ts +43 -0
- package/dist/src/languages/csharp/test-emitter.js +274 -0
- package/dist/src/languages/csharp/visitor.d.ts +14 -0
- package/dist/src/languages/csharp/visitor.js +79 -0
- package/dist/src/languages/go/driver.d.ts +12 -0
- package/dist/src/languages/go/driver.js +128 -0
- package/dist/src/languages/go/emitter.d.ts +33 -0
- package/dist/src/languages/go/emitter.js +879 -0
- package/dist/src/languages/go/scaffolding.d.ts +18 -0
- package/dist/src/languages/go/scaffolding.js +53 -0
- package/dist/src/languages/go/test-emitter.d.ts +20 -0
- package/dist/src/languages/go/test-emitter.js +300 -0
- package/dist/src/languages/go/visitor.d.ts +14 -0
- package/dist/src/languages/go/visitor.js +78 -0
- package/dist/src/languages/markdown/driver.d.ts +19 -0
- package/dist/src/languages/markdown/driver.js +408 -0
- package/dist/src/languages/python/driver.d.ts +14 -0
- package/dist/src/languages/python/driver.js +372 -0
- package/dist/src/languages/python/emitter.d.ts +31 -0
- package/dist/src/languages/python/emitter.js +856 -0
- package/dist/src/languages/python/scaffolding.d.ts +33 -0
- package/dist/src/languages/python/scaffolding.js +279 -0
- package/dist/src/languages/python/test-emitter.d.ts +29 -0
- package/dist/src/languages/python/test-emitter.js +388 -0
- package/dist/src/languages/python/visitor.d.ts +14 -0
- package/dist/src/languages/python/visitor.js +65 -0
- package/dist/src/languages/rust/driver.d.ts +13 -0
- package/dist/src/languages/rust/driver.js +624 -0
- package/dist/src/languages/rust/emitter.d.ts +45 -0
- package/dist/src/languages/rust/emitter.js +1596 -0
- package/dist/src/languages/rust/visitor.d.ts +25 -0
- package/dist/src/languages/rust/visitor.js +153 -0
- package/dist/src/languages/typescript/driver.d.ts +8 -0
- package/dist/src/languages/typescript/driver.js +209 -0
- package/dist/src/languages/typescript/emitter.d.ts +42 -0
- package/dist/src/languages/typescript/emitter.js +904 -0
- package/dist/src/languages/typescript/scaffolding.d.ts +32 -0
- package/dist/src/languages/typescript/scaffolding.js +303 -0
- package/dist/src/languages/typescript/test-emitter.d.ts +23 -0
- package/dist/src/languages/typescript/test-emitter.js +204 -0
- package/dist/src/languages/typescript/visitor.d.ts +14 -0
- package/dist/src/languages/typescript/visitor.js +64 -0
- package/dist/src/lib.d.ts +33 -0
- package/dist/src/lib.js +101 -0
- package/dist/src/testing/index.d.ts +2 -0
- package/dist/src/testing/index.js +8 -0
- package/dist/src/testing/test-context.d.ts +63 -0
- package/dist/src/testing/test-context.js +355 -0
- package/fixtures/shapes/main.tsp +43 -0
- package/fixtures/tspconfig.yaml +13 -0
- package/package.json +76 -0
- 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
|