@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,407 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Expression IR — Type-directed lowering for the emitter.
|
|
3
|
+
*
|
|
4
|
+
* This module implements the "lowering" pass of the transpiler:
|
|
5
|
+
* given an untyped data literal (from @factory sets or @coerce expansion)
|
|
6
|
+
* and a target type (TypeNode/PropertyNode), produce a typed Expr tree.
|
|
7
|
+
*
|
|
8
|
+
* The Expr tree is language-agnostic. Per-language visitors in render-expr.ts
|
|
9
|
+
* walk it to produce target language code.
|
|
10
|
+
*
|
|
11
|
+
* Architecture (following TypeScript/Roslyn/Babel pattern):
|
|
12
|
+
* Data literal + Type graph → resolve() → Expr tree → visit() → code string
|
|
13
|
+
*/
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// Type reference collection — for factory import resolution
|
|
16
|
+
// ============================================================================
|
|
17
|
+
/**
|
|
18
|
+
* Recursively collect all TypeName references from an Expr tree.
|
|
19
|
+
* Used by emitters to determine which additional types need importing
|
|
20
|
+
* when factory/coercion expressions reference types from other modules.
|
|
21
|
+
*/
|
|
22
|
+
export function collectExprTypeRefs(expr) {
|
|
23
|
+
const refs = [];
|
|
24
|
+
function walk(e) {
|
|
25
|
+
switch (e.kind) {
|
|
26
|
+
case "construct":
|
|
27
|
+
refs.push(e.typeName);
|
|
28
|
+
e.fields.forEach(f => walk(f.value));
|
|
29
|
+
break;
|
|
30
|
+
case "variant":
|
|
31
|
+
refs.push(e.baseTypeName);
|
|
32
|
+
refs.push(e.variantTypeName);
|
|
33
|
+
e.fields.forEach(f => walk(f.value));
|
|
34
|
+
break;
|
|
35
|
+
case "array":
|
|
36
|
+
refs.push(e.elementTypeName);
|
|
37
|
+
e.items.forEach(walk);
|
|
38
|
+
break;
|
|
39
|
+
case "dict":
|
|
40
|
+
e.entries.forEach(ent => walk(ent.value));
|
|
41
|
+
break;
|
|
42
|
+
case "string":
|
|
43
|
+
case "number":
|
|
44
|
+
case "boolean":
|
|
45
|
+
case "null":
|
|
46
|
+
case "param":
|
|
47
|
+
case "field_read":
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
walk(expr);
|
|
52
|
+
return refs;
|
|
53
|
+
}
|
|
54
|
+
// ============================================================================
|
|
55
|
+
// Type Registry — flat lookup for type-directed resolution
|
|
56
|
+
// ============================================================================
|
|
57
|
+
/**
|
|
58
|
+
* Registry of TypeNodes by name, enabling the resolver to look up types
|
|
59
|
+
* when processing nested objects and discriminated unions.
|
|
60
|
+
*
|
|
61
|
+
* Built from the emitter's type graph (TypeNode tree + enumerateTypes).
|
|
62
|
+
*/
|
|
63
|
+
export class TypeRegistry {
|
|
64
|
+
types = new Map();
|
|
65
|
+
/** Register a type by its simple name. */
|
|
66
|
+
register(node) {
|
|
67
|
+
this.types.set(node.typeName.name, node);
|
|
68
|
+
}
|
|
69
|
+
/** Look up a type by simple name. Returns undefined if not found. */
|
|
70
|
+
get(name) {
|
|
71
|
+
return this.types.get(name);
|
|
72
|
+
}
|
|
73
|
+
/** Build a registry from a root TypeNode by walking all reachable types. */
|
|
74
|
+
static fromTypeGraph(roots) {
|
|
75
|
+
const registry = new TypeRegistry();
|
|
76
|
+
const visited = new Set();
|
|
77
|
+
function walk(node) {
|
|
78
|
+
const key = `${node.typeName.namespace}.${node.typeName.name}`;
|
|
79
|
+
if (visited.has(key))
|
|
80
|
+
return;
|
|
81
|
+
visited.add(key);
|
|
82
|
+
registry.register(node);
|
|
83
|
+
for (const child of node.childTypes) {
|
|
84
|
+
walk(child);
|
|
85
|
+
}
|
|
86
|
+
for (const prop of node.properties) {
|
|
87
|
+
if (prop.type) {
|
|
88
|
+
walk(prop.type);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
for (const root of roots) {
|
|
93
|
+
walk(root);
|
|
94
|
+
}
|
|
95
|
+
return registry;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// ============================================================================
|
|
99
|
+
// Resolver — type-directed lowering (the "frontend")
|
|
100
|
+
// ============================================================================
|
|
101
|
+
/** Regex matching a `{paramName}` placeholder (must be the entire string). */
|
|
102
|
+
const PARAM_PLACEHOLDER = /^\{(\w+)\}$/;
|
|
103
|
+
/**
|
|
104
|
+
* Resolve a @factory decorator into a typed Expr tree.
|
|
105
|
+
*
|
|
106
|
+
* @param sets - Field assignments from the decorator (e.g., { allowed: true })
|
|
107
|
+
* @param params - Parameter declarations (e.g., { reason: "string" })
|
|
108
|
+
* @param targetType - The TypeNode this factory constructs
|
|
109
|
+
* @param registry - Type registry for resolving nested types
|
|
110
|
+
* @returns A Construct expression representing the factory body
|
|
111
|
+
*/
|
|
112
|
+
export function resolveFactoryExpr(sets, params, targetType, registry) {
|
|
113
|
+
const fields = [];
|
|
114
|
+
// 1. Resolve each explicitly-set field
|
|
115
|
+
for (const [fieldName, value] of Object.entries(sets)) {
|
|
116
|
+
const prop = targetType.properties.find(p => p.name === fieldName);
|
|
117
|
+
if (!prop) {
|
|
118
|
+
throw new Error(`Property '${fieldName}' not found on type '${targetType.typeName.name}'. ` +
|
|
119
|
+
`Available: [${targetType.properties.map(p => p.name).join(", ")}]`);
|
|
120
|
+
}
|
|
121
|
+
fields.push({
|
|
122
|
+
propertyName: fieldName,
|
|
123
|
+
value: resolveValue(value, prop, params, registry),
|
|
124
|
+
isOptional: prop.isOptional,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
// 2. Add flat params — params that match a top-level property not already in sets
|
|
128
|
+
for (const [paramName, paramType] of Object.entries(params)) {
|
|
129
|
+
if (paramName in sets)
|
|
130
|
+
continue; // already handled as a nested placeholder
|
|
131
|
+
// Check if this param was consumed as a placeholder inside sets
|
|
132
|
+
if (isParamConsumedInSets(paramName, sets))
|
|
133
|
+
continue;
|
|
134
|
+
const prop = targetType.properties.find(p => p.name === paramName);
|
|
135
|
+
if (!prop) {
|
|
136
|
+
throw new Error(`Parameter '${paramName}' does not match any property on type '${targetType.typeName.name}'. ` +
|
|
137
|
+
`Available: [${targetType.properties.map(p => p.name).join(", ")}]`);
|
|
138
|
+
}
|
|
139
|
+
fields.push({
|
|
140
|
+
propertyName: paramName,
|
|
141
|
+
value: { kind: "param", name: paramName, paramType },
|
|
142
|
+
isOptional: prop.isOptional,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
kind: "construct",
|
|
147
|
+
typeName: targetType.typeName,
|
|
148
|
+
fields,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Resolve a @coerce decorator into a typed Expr tree.
|
|
153
|
+
*
|
|
154
|
+
* A coercion is essentially a factory with a single implicit parameter named "value".
|
|
155
|
+
* The expansion dict maps property names to values, where "{value}" is the parameter ref.
|
|
156
|
+
*
|
|
157
|
+
* @param expansion - The expansion dict (e.g., { id: "{value}" })
|
|
158
|
+
* @param scalarType - The scalar type string (e.g., "string")
|
|
159
|
+
* @param targetType - The TypeNode this coercion constructs
|
|
160
|
+
* @param registry - Type registry for resolving nested types
|
|
161
|
+
* @returns A Construct expression representing the coercion expansion
|
|
162
|
+
*/
|
|
163
|
+
export function resolveCoerceExpr(expansion, scalarType, targetType, registry, paramName = "value") {
|
|
164
|
+
// The expansion uses {value} as the fixed placeholder. Resolve with "value" as param,
|
|
165
|
+
// then rename the ParamRef to the caller's desired paramName.
|
|
166
|
+
const expr = resolveFactoryExpr(expansion, { value: scalarType }, targetType, registry);
|
|
167
|
+
if (paramName !== "value") {
|
|
168
|
+
renameParam(expr, "value", paramName);
|
|
169
|
+
}
|
|
170
|
+
return expr;
|
|
171
|
+
}
|
|
172
|
+
/** Recursively rename a ParamRef in an Expr tree. */
|
|
173
|
+
function renameParam(expr, from, to) {
|
|
174
|
+
switch (expr.kind) {
|
|
175
|
+
case "param":
|
|
176
|
+
if (expr.name === from)
|
|
177
|
+
expr.name = to;
|
|
178
|
+
break;
|
|
179
|
+
case "construct":
|
|
180
|
+
for (const f of expr.fields)
|
|
181
|
+
renameParam(f.value, from, to);
|
|
182
|
+
break;
|
|
183
|
+
case "variant":
|
|
184
|
+
for (const f of expr.fields)
|
|
185
|
+
renameParam(f.value, from, to);
|
|
186
|
+
break;
|
|
187
|
+
case "array":
|
|
188
|
+
for (const item of expr.items)
|
|
189
|
+
renameParam(item, from, to);
|
|
190
|
+
break;
|
|
191
|
+
case "dict":
|
|
192
|
+
for (const entry of expr.entries)
|
|
193
|
+
renameParam(entry.value, from, to);
|
|
194
|
+
break;
|
|
195
|
+
// Literals and field reads don't contain params
|
|
196
|
+
case "string":
|
|
197
|
+
case "number":
|
|
198
|
+
case "boolean":
|
|
199
|
+
case "null":
|
|
200
|
+
case "field_read":
|
|
201
|
+
break;
|
|
202
|
+
default: {
|
|
203
|
+
const _exhaustive = expr;
|
|
204
|
+
throw new Error(`Unknown expr kind: ${_exhaustive.kind}`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
// ============================================================================
|
|
209
|
+
// Internal resolution — structural recursion, type-directed
|
|
210
|
+
// ============================================================================
|
|
211
|
+
/**
|
|
212
|
+
* Resolve a single value against a property's type.
|
|
213
|
+
* This is the core recursive function — it dispatches on the value's shape
|
|
214
|
+
* and the target property's type information.
|
|
215
|
+
*/
|
|
216
|
+
function resolveValue(value, prop, params, registry) {
|
|
217
|
+
// String value — could be a literal, a param ref, or a nested field
|
|
218
|
+
if (typeof value === "string") {
|
|
219
|
+
return resolveStringValue(value, prop, params);
|
|
220
|
+
}
|
|
221
|
+
// Boolean literal
|
|
222
|
+
if (typeof value === "boolean") {
|
|
223
|
+
return { kind: "boolean", value };
|
|
224
|
+
}
|
|
225
|
+
// Number literal
|
|
226
|
+
if (typeof value === "number") {
|
|
227
|
+
return { kind: "number", value };
|
|
228
|
+
}
|
|
229
|
+
// Null
|
|
230
|
+
if (value === null || value === undefined) {
|
|
231
|
+
return { kind: "null" };
|
|
232
|
+
}
|
|
233
|
+
// Array — resolve each element against the collection's element type
|
|
234
|
+
if (Array.isArray(value)) {
|
|
235
|
+
return resolveArrayValue(value, prop, params, registry);
|
|
236
|
+
}
|
|
237
|
+
// Object — resolve as a typed construction (possibly polymorphic)
|
|
238
|
+
if (typeof value === "object") {
|
|
239
|
+
return resolveObjectValue(value, prop, params, registry);
|
|
240
|
+
}
|
|
241
|
+
throw new Error(`Cannot resolve value of type '${typeof value}' for property '${prop.name}'`);
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Resolve a string value — either a param placeholder or a string literal.
|
|
245
|
+
*/
|
|
246
|
+
function resolveStringValue(value, _prop, params) {
|
|
247
|
+
const match = PARAM_PLACEHOLDER.exec(value);
|
|
248
|
+
if (match) {
|
|
249
|
+
const paramName = match[1];
|
|
250
|
+
if (paramName in params) {
|
|
251
|
+
return { kind: "param", name: paramName, paramType: params[paramName] };
|
|
252
|
+
}
|
|
253
|
+
throw new Error(`Placeholder '{${paramName}}' does not match any declared parameter. ` +
|
|
254
|
+
`Available: [${Object.keys(params).join(", ")}]`);
|
|
255
|
+
}
|
|
256
|
+
return { kind: "string", value };
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Resolve an array value against a collection property.
|
|
260
|
+
*/
|
|
261
|
+
function resolveArrayValue(items, prop, params, registry) {
|
|
262
|
+
if (!prop.isCollection && !prop.type) {
|
|
263
|
+
// If property isn't marked as collection, use its type name for the array
|
|
264
|
+
}
|
|
265
|
+
// Get the element type — from prop.type (which is the element TypeNode for collections)
|
|
266
|
+
const elementType = prop.type;
|
|
267
|
+
const elementTypeName = prop.typeName;
|
|
268
|
+
const resolvedItems = items.map((item, index) => {
|
|
269
|
+
if (elementType && typeof item === "object" && item !== null && !Array.isArray(item)) {
|
|
270
|
+
return resolveObjectAgainstType(item, elementType, params, registry);
|
|
271
|
+
}
|
|
272
|
+
// For scalar array elements, create a synthetic property to resolve against
|
|
273
|
+
if (typeof item === "string") {
|
|
274
|
+
return resolveStringValue(item, prop, params);
|
|
275
|
+
}
|
|
276
|
+
if (typeof item === "boolean") {
|
|
277
|
+
return { kind: "boolean", value: item };
|
|
278
|
+
}
|
|
279
|
+
if (typeof item === "number") {
|
|
280
|
+
return { kind: "number", value: item };
|
|
281
|
+
}
|
|
282
|
+
throw new Error(`Cannot resolve array element at index ${index} for property '${prop.name}'`);
|
|
283
|
+
});
|
|
284
|
+
return {
|
|
285
|
+
kind: "array",
|
|
286
|
+
elementTypeName,
|
|
287
|
+
items: resolvedItems,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Resolve an object value against a property's type.
|
|
292
|
+
* Delegates to resolveObjectAgainstType after finding the target type.
|
|
293
|
+
*/
|
|
294
|
+
function resolveObjectValue(obj, prop, params, registry) {
|
|
295
|
+
// Find the target type — either from prop.type or registry lookup
|
|
296
|
+
let targetType = prop.type;
|
|
297
|
+
if (!targetType) {
|
|
298
|
+
targetType = registry.get(prop.typeName.name);
|
|
299
|
+
}
|
|
300
|
+
if (!targetType) {
|
|
301
|
+
throw new Error(`Cannot resolve object for property '${prop.name}': type '${prop.typeName.name}' not found in registry`);
|
|
302
|
+
}
|
|
303
|
+
return resolveObjectAgainstType(obj, targetType, params, registry);
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Resolve an object against a known TypeNode.
|
|
307
|
+
* Handles discriminated unions (→ VariantConstruct) and plain types (→ Construct).
|
|
308
|
+
*/
|
|
309
|
+
function resolveObjectAgainstType(obj, targetType, params, registry) {
|
|
310
|
+
// Check for discriminated union dispatch
|
|
311
|
+
if (targetType.discriminator && targetType.childTypes.length > 0) {
|
|
312
|
+
const discriminatorValue = obj[targetType.discriminator];
|
|
313
|
+
if (typeof discriminatorValue === "string") {
|
|
314
|
+
return resolveVariantConstruct(obj, targetType, discriminatorValue, params, registry);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
// Plain type — resolve all fields
|
|
318
|
+
return resolveConstruct(obj, targetType, params, registry);
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Resolve a discriminated union variant.
|
|
322
|
+
* Looks up the child type by discriminator value, then resolves fields
|
|
323
|
+
* against the child type's properties.
|
|
324
|
+
*/
|
|
325
|
+
function resolveVariantConstruct(obj, baseType, discriminatorValue, params, registry) {
|
|
326
|
+
// Find the child type matching this discriminator value
|
|
327
|
+
const childType = baseType.childTypes.find(child => {
|
|
328
|
+
const discProp = child.properties.find(p => p.name === baseType.discriminator);
|
|
329
|
+
return discProp?.defaultValue === discriminatorValue;
|
|
330
|
+
});
|
|
331
|
+
if (!childType) {
|
|
332
|
+
throw new Error(`No child type of '${baseType.typeName.name}' has ${baseType.discriminator}='${discriminatorValue}'. ` +
|
|
333
|
+
`Available: [${baseType.childTypes.map(c => {
|
|
334
|
+
const dp = c.properties.find(p => p.name === baseType.discriminator);
|
|
335
|
+
return dp?.defaultValue ?? "*";
|
|
336
|
+
}).join(", ")}]`);
|
|
337
|
+
}
|
|
338
|
+
// Resolve fields against the child type (excluding the discriminator itself)
|
|
339
|
+
const fields = [];
|
|
340
|
+
for (const [fieldName, value] of Object.entries(obj)) {
|
|
341
|
+
if (fieldName === baseType.discriminator)
|
|
342
|
+
continue; // discriminator is implicit
|
|
343
|
+
// Look for the property on the child type first, then base type
|
|
344
|
+
const prop = childType.properties.find(p => p.name === fieldName)
|
|
345
|
+
?? baseType.properties.find(p => p.name === fieldName);
|
|
346
|
+
if (!prop) {
|
|
347
|
+
throw new Error(`Property '${fieldName}' not found on variant '${childType.typeName.name}' ` +
|
|
348
|
+
`or base '${baseType.typeName.name}'`);
|
|
349
|
+
}
|
|
350
|
+
fields.push({
|
|
351
|
+
propertyName: fieldName,
|
|
352
|
+
value: resolveValue(value, prop, params, registry),
|
|
353
|
+
isOptional: prop.isOptional,
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
return {
|
|
357
|
+
kind: "variant",
|
|
358
|
+
baseTypeName: baseType.typeName,
|
|
359
|
+
discriminator: baseType.discriminator,
|
|
360
|
+
discriminatorValue,
|
|
361
|
+
variantTypeName: childType.typeName,
|
|
362
|
+
fields,
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Resolve a plain (non-polymorphic) object construction.
|
|
367
|
+
*/
|
|
368
|
+
function resolveConstruct(obj, targetType, params, registry) {
|
|
369
|
+
const fields = [];
|
|
370
|
+
for (const [fieldName, value] of Object.entries(obj)) {
|
|
371
|
+
const prop = targetType.properties.find(p => p.name === fieldName);
|
|
372
|
+
if (!prop) {
|
|
373
|
+
throw new Error(`Property '${fieldName}' not found on type '${targetType.typeName.name}'. ` +
|
|
374
|
+
`Available: [${targetType.properties.map(p => p.name).join(", ")}]`);
|
|
375
|
+
}
|
|
376
|
+
fields.push({
|
|
377
|
+
propertyName: fieldName,
|
|
378
|
+
value: resolveValue(value, prop, params, registry),
|
|
379
|
+
isOptional: prop.isOptional,
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
return {
|
|
383
|
+
kind: "construct",
|
|
384
|
+
typeName: targetType.typeName,
|
|
385
|
+
fields,
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Check if a param name is used as a {placeholder} anywhere in the sets tree.
|
|
390
|
+
* Used to avoid double-emitting params that appear both as top-level property
|
|
391
|
+
* matches and as nested placeholders.
|
|
392
|
+
*/
|
|
393
|
+
function isParamConsumedInSets(paramName, sets) {
|
|
394
|
+
const placeholder = `{${paramName}}`;
|
|
395
|
+
function search(value) {
|
|
396
|
+
if (value === placeholder)
|
|
397
|
+
return true;
|
|
398
|
+
if (Array.isArray(value))
|
|
399
|
+
return value.some(search);
|
|
400
|
+
if (value !== null && typeof value === "object") {
|
|
401
|
+
return Object.values(value).some(search);
|
|
402
|
+
}
|
|
403
|
+
return false;
|
|
404
|
+
}
|
|
405
|
+
return Object.values(sets).some(search);
|
|
406
|
+
}
|
|
407
|
+
//# sourceMappingURL=expansion.js.map
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lowering pass — TypeNode graph → Declaration IR.
|
|
3
|
+
*
|
|
4
|
+
* This module converts the emitter's type graph (TypeNode/PropertyNode)
|
|
5
|
+
* into the language-agnostic Declaration IR (FileDecl/TypeDecl).
|
|
6
|
+
*
|
|
7
|
+
* The lowering is shared across all 5 target languages. Per-language
|
|
8
|
+
* emitter functions consume the FileDecl tree and emit code.
|
|
9
|
+
*
|
|
10
|
+
* Key responsibilities:
|
|
11
|
+
* - Classify every property into a PropertyCategory
|
|
12
|
+
* - Build load/save method specifications
|
|
13
|
+
* - Resolve polymorphic dispatch
|
|
14
|
+
* - Resolve collection helpers
|
|
15
|
+
* - Resolve factory methods via the Expression IR
|
|
16
|
+
* - Compute file-level imports
|
|
17
|
+
*/
|
|
18
|
+
import { TypeNode, PropertyNode } from "./ast.js";
|
|
19
|
+
import { TypeRegistry } from "./expansion.js";
|
|
20
|
+
import { PropertyCategory, FileDecl, TypeDecl } from "./declarations.js";
|
|
21
|
+
/**
|
|
22
|
+
* Lower a base TypeNode (and all its children) into a FileDecl.
|
|
23
|
+
*
|
|
24
|
+
* This is the main entry point for the lowering pass. It produces a complete
|
|
25
|
+
* FileDecl containing one or more TypeDecls (parent + children for polymorphic types).
|
|
26
|
+
*
|
|
27
|
+
* The result is fully language-agnostic — per-language emitters handle rendering.
|
|
28
|
+
*
|
|
29
|
+
* @param node - The base TypeNode (must not have a parent — i.e., `node.base === null`)
|
|
30
|
+
* @param registry - TypeRegistry for resolving type references
|
|
31
|
+
* @param polymorphicTypeNames - Set of type names that are polymorphic bases
|
|
32
|
+
*/
|
|
33
|
+
export declare function lowerFile(node: TypeNode, registry: TypeRegistry, polymorphicTypeNames?: Set<string>): FileDecl;
|
|
34
|
+
/**
|
|
35
|
+
* Collect all polymorphic type names from a set of nodes.
|
|
36
|
+
*/
|
|
37
|
+
export declare function collectPolymorphicTypeNames(rootNode: TypeNode, registry: TypeRegistry): Set<string>;
|
|
38
|
+
/**
|
|
39
|
+
* Lower a single TypeNode into a TypeDecl.
|
|
40
|
+
*/
|
|
41
|
+
export declare function lowerType(node: TypeNode, registry: TypeRegistry, polymorphicTypeNames: Set<string>): TypeDecl;
|
|
42
|
+
/**
|
|
43
|
+
* Classify a property into one of 5 categories.
|
|
44
|
+
* This is the fundamental decision that drives ALL code generation.
|
|
45
|
+
*
|
|
46
|
+
* Decision tree:
|
|
47
|
+
* isDict → "dict"
|
|
48
|
+
* isCollection && isScalar → "collection_scalar"
|
|
49
|
+
* isCollection && !isScalar → "collection_complex"
|
|
50
|
+
* isScalar → "scalar"
|
|
51
|
+
* !isScalar → "complex"
|
|
52
|
+
*/
|
|
53
|
+
export declare function classifyProperty(prop: PropertyNode, polymorphicTypeNames: Set<string>): PropertyCategory;
|