@tsonic/emitter 0.0.1

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 (209) hide show
  1. package/package.json +34 -0
  2. package/scripts/update-golden-tests.ts +119 -0
  3. package/src/adapter-generator.ts +112 -0
  4. package/src/array.test.ts +301 -0
  5. package/src/constants.ts +32 -0
  6. package/src/core/exports.ts +36 -0
  7. package/src/core/imports.test.ts +83 -0
  8. package/src/core/imports.ts +243 -0
  9. package/src/core/index.ts +9 -0
  10. package/src/core/module-emitter/assembly.ts +83 -0
  11. package/src/core/module-emitter/header.ts +19 -0
  12. package/src/core/module-emitter/index.ts +17 -0
  13. package/src/core/module-emitter/namespace.ts +39 -0
  14. package/src/core/module-emitter/orchestrator.ts +98 -0
  15. package/src/core/module-emitter/separation.ts +41 -0
  16. package/src/core/module-emitter/static-container.ts +75 -0
  17. package/src/core/module-emitter.test.ts +154 -0
  18. package/src/core/module-emitter.ts +6 -0
  19. package/src/core/module-map.ts +218 -0
  20. package/src/core/options.ts +16 -0
  21. package/src/core/type-params.ts +34 -0
  22. package/src/emitter-types/context.ts +91 -0
  23. package/src/emitter-types/core.ts +138 -0
  24. package/src/emitter-types/csharp-types.ts +42 -0
  25. package/src/emitter-types/formatting.ts +13 -0
  26. package/src/emitter-types/fqn.ts +81 -0
  27. package/src/emitter-types/index.ts +31 -0
  28. package/src/emitter.ts +107 -0
  29. package/src/expression-emitter.ts +112 -0
  30. package/src/expressions/access.ts +104 -0
  31. package/src/expressions/calls.ts +264 -0
  32. package/src/expressions/collections.ts +354 -0
  33. package/src/expressions/functions.ts +71 -0
  34. package/src/expressions/identifiers.ts +125 -0
  35. package/src/expressions/index.test.ts +515 -0
  36. package/src/expressions/index.ts +38 -0
  37. package/src/expressions/literals.ts +138 -0
  38. package/src/expressions/operators.ts +211 -0
  39. package/src/expressions/other.ts +63 -0
  40. package/src/generator-exchange.ts +120 -0
  41. package/src/generator.test.ts +193 -0
  42. package/src/golden-tests/config-parser.ts +67 -0
  43. package/src/golden-tests/discovery.ts +61 -0
  44. package/src/golden-tests/index.ts +10 -0
  45. package/src/golden-tests/registration.ts +26 -0
  46. package/src/golden-tests/runner.ts +131 -0
  47. package/src/golden-tests/tree-builder.ts +43 -0
  48. package/src/golden-tests/types.ts +21 -0
  49. package/src/golden.test.ts +40 -0
  50. package/src/hierarchical-bindings.test.ts +258 -0
  51. package/src/index.ts +14 -0
  52. package/src/integration.test.ts +303 -0
  53. package/src/specialization/call-site-rewriting.test.ts +99 -0
  54. package/src/specialization/collection/expressions.ts +184 -0
  55. package/src/specialization/collection/index.ts +7 -0
  56. package/src/specialization/collection/orchestrator.ts +25 -0
  57. package/src/specialization/collection/statements.ts +91 -0
  58. package/src/specialization/collection.ts +10 -0
  59. package/src/specialization/generation.ts +189 -0
  60. package/src/specialization/generic-classes.test.ts +59 -0
  61. package/src/specialization/generic-functions.test.ts +292 -0
  62. package/src/specialization/helpers.ts +39 -0
  63. package/src/specialization/index.ts +28 -0
  64. package/src/specialization/interfaces.test.ts +151 -0
  65. package/src/specialization/naming.ts +34 -0
  66. package/src/specialization/substitution.ts +186 -0
  67. package/src/specialization/type-aliases.test.ts +134 -0
  68. package/src/specialization/types.ts +19 -0
  69. package/src/specialization-generator.ts +23 -0
  70. package/src/statement-emitter.ts +117 -0
  71. package/src/statements/blocks.ts +115 -0
  72. package/src/statements/classes/helpers.ts +9 -0
  73. package/src/statements/classes/index.ts +13 -0
  74. package/src/statements/classes/inline-types.ts +79 -0
  75. package/src/statements/classes/members/constructors.ts +123 -0
  76. package/src/statements/classes/members/index.ts +8 -0
  77. package/src/statements/classes/members/methods.ts +137 -0
  78. package/src/statements/classes/members/orchestrator.ts +33 -0
  79. package/src/statements/classes/members/properties.ts +62 -0
  80. package/src/statements/classes/members.ts +6 -0
  81. package/src/statements/classes/parameters.ts +69 -0
  82. package/src/statements/classes/properties.ts +95 -0
  83. package/src/statements/classes.ts +14 -0
  84. package/src/statements/control/conditionals.ts +134 -0
  85. package/src/statements/control/exceptions.ts +59 -0
  86. package/src/statements/control/index.ts +11 -0
  87. package/src/statements/control/loops.ts +250 -0
  88. package/src/statements/control.ts +14 -0
  89. package/src/statements/declarations/classes.ts +89 -0
  90. package/src/statements/declarations/enums.ts +32 -0
  91. package/src/statements/declarations/functions.ts +147 -0
  92. package/src/statements/declarations/index.ts +10 -0
  93. package/src/statements/declarations/interfaces.ts +116 -0
  94. package/src/statements/declarations/structs.test.ts +182 -0
  95. package/src/statements/declarations/type-aliases.ts +104 -0
  96. package/src/statements/declarations/variables.ts +159 -0
  97. package/src/statements/declarations.ts +13 -0
  98. package/src/statements/index.test.ts +258 -0
  99. package/src/statements/index.ts +43 -0
  100. package/src/type-assertion.test.ts +143 -0
  101. package/src/type-emitter.ts +18 -0
  102. package/src/types/arrays.ts +21 -0
  103. package/src/types/dictionaries.ts +52 -0
  104. package/src/types/emitter.ts +76 -0
  105. package/src/types/functions.ts +45 -0
  106. package/src/types/index.test.ts +116 -0
  107. package/src/types/index.ts +14 -0
  108. package/src/types/intersections.ts +19 -0
  109. package/src/types/literals.ts +26 -0
  110. package/src/types/objects.ts +27 -0
  111. package/src/types/parameters.test.ts +146 -0
  112. package/src/types/parameters.ts +95 -0
  113. package/src/types/primitives.ts +24 -0
  114. package/src/types/references.ts +187 -0
  115. package/src/types/unions.test.ts +397 -0
  116. package/src/types/unions.ts +62 -0
  117. package/src/types.ts +33 -0
  118. package/testcases/README.md +213 -0
  119. package/testcases/arrays/basic/ArrayLiteral.ts +4 -0
  120. package/testcases/arrays/basic/config.yaml +1 -0
  121. package/testcases/arrays/destructuring/ArrayDestructure.ts +4 -0
  122. package/testcases/arrays/destructuring/config.yaml +1 -0
  123. package/testcases/arrays/methods/ArrayMethods.ts +6 -0
  124. package/testcases/arrays/methods/config.yaml +1 -0
  125. package/testcases/arrays/multidimensional/MultiDimensional.ts +10 -0
  126. package/testcases/arrays/multidimensional/config.yaml +1 -0
  127. package/testcases/arrays/spread/ArraySpread.ts +3 -0
  128. package/testcases/arrays/spread/config.yaml +1 -0
  129. package/testcases/async/basic/AsyncFunction.ts +5 -0
  130. package/testcases/async/basic/config.yaml +1 -0
  131. package/testcases/classes/abstract/AbstractClasses.ts +53 -0
  132. package/testcases/classes/abstract/config.yaml +1 -0
  133. package/testcases/classes/basic/Person.ts +12 -0
  134. package/testcases/classes/basic/config.yaml +1 -0
  135. package/testcases/classes/constructor/User.ts +11 -0
  136. package/testcases/classes/constructor/config.yaml +1 -0
  137. package/testcases/classes/field-inference/Counter.ts +11 -0
  138. package/testcases/classes/field-inference/config.yaml +1 -0
  139. package/testcases/classes/inheritance/Inheritance.ts +24 -0
  140. package/testcases/classes/inheritance/config.yaml +1 -0
  141. package/testcases/classes/static-members/MathHelper.ts +12 -0
  142. package/testcases/classes/static-members/config.yaml +1 -0
  143. package/testcases/control-flow/error-handling/ErrorHandling.ts +13 -0
  144. package/testcases/control-flow/error-handling/config.yaml +1 -0
  145. package/testcases/control-flow/loops/Loops.ts +21 -0
  146. package/testcases/control-flow/loops/config.yaml +1 -0
  147. package/testcases/control-flow/switch/SwitchStatement.ts +15 -0
  148. package/testcases/control-flow/switch/config.yaml +1 -0
  149. package/testcases/edge-cases/complex-expressions/ComplexExpressions.ts +10 -0
  150. package/testcases/edge-cases/complex-expressions/config.yaml +1 -0
  151. package/testcases/edge-cases/nested-scopes/NestedScopes.ts +10 -0
  152. package/testcases/edge-cases/nested-scopes/config.yaml +1 -0
  153. package/testcases/edge-cases/shadowing/Shadowing.ts +16 -0
  154. package/testcases/edge-cases/shadowing/config.yaml +1 -0
  155. package/testcases/functions/arrow/ArrowFunction.ts +5 -0
  156. package/testcases/functions/arrow/config.yaml +1 -0
  157. package/testcases/functions/basic/Greet.ts +3 -0
  158. package/testcases/functions/basic/config.yaml +1 -0
  159. package/testcases/functions/closures/Closures.ts +11 -0
  160. package/testcases/functions/closures/config.yaml +1 -0
  161. package/testcases/functions/default-params/DefaultParams.ts +7 -0
  162. package/testcases/functions/default-params/config.yaml +1 -0
  163. package/testcases/functions/rest-params/RestParams.ts +7 -0
  164. package/testcases/functions/rest-params/config.yaml +1 -0
  165. package/testcases/functions/type-guards/TypeGuards.ts +52 -0
  166. package/testcases/functions/type-guards/config.yaml +1 -0
  167. package/testcases/operators/logical/LogicalOperators.ts +11 -0
  168. package/testcases/operators/logical/config.yaml +1 -0
  169. package/testcases/operators/nullish-coalescing/NullishCoalescing.ts +7 -0
  170. package/testcases/operators/nullish-coalescing/config.yaml +1 -0
  171. package/testcases/operators/optional-chaining/OptionalChaining.ts +15 -0
  172. package/testcases/operators/optional-chaining/config.yaml +1 -0
  173. package/testcases/real-world/advanced-generics/advanced-generics.ts +116 -0
  174. package/testcases/real-world/advanced-generics/config.yaml +1 -0
  175. package/testcases/real-world/async-ops/async-ops.ts +67 -0
  176. package/testcases/real-world/async-ops/config.yaml +1 -0
  177. package/testcases/real-world/business-logic/business-logic.ts +215 -0
  178. package/testcases/real-world/business-logic/config.yaml +1 -0
  179. package/testcases/real-world/calculator/calculator.ts +29 -0
  180. package/testcases/real-world/calculator/config.yaml +1 -0
  181. package/testcases/real-world/data-structures/config.yaml +1 -0
  182. package/testcases/real-world/data-structures/data-structures.ts +133 -0
  183. package/testcases/real-world/functional/config.yaml +1 -0
  184. package/testcases/real-world/functional/functional.ts +116 -0
  185. package/testcases/real-world/shapes/config.yaml +1 -0
  186. package/testcases/real-world/shapes/shapes.ts +87 -0
  187. package/testcases/real-world/string-utils/config.yaml +1 -0
  188. package/testcases/real-world/string-utils/string-utils.ts +47 -0
  189. package/testcases/real-world/todo-list/config.yaml +1 -0
  190. package/testcases/real-world/todo-list/todo-list.ts +52 -0
  191. package/testcases/real-world/type-guards/config.yaml +1 -0
  192. package/testcases/real-world/type-guards/type-guards.ts +71 -0
  193. package/testcases/structs/basic/Point.ts +9 -0
  194. package/testcases/structs/basic/config.yaml +1 -0
  195. package/testcases/types/conditional/ConditionalTypes.ts +35 -0
  196. package/testcases/types/conditional/config.yaml +1 -0
  197. package/testcases/types/constants/ModuleConstants.ts +6 -0
  198. package/testcases/types/constants/config.yaml +1 -0
  199. package/testcases/types/generics/Generics.ts +15 -0
  200. package/testcases/types/generics/config.yaml +1 -0
  201. package/testcases/types/interfaces/Interfaces.ts +14 -0
  202. package/testcases/types/interfaces/config.yaml +1 -0
  203. package/testcases/types/mapped/MappedTypes.ts +27 -0
  204. package/testcases/types/mapped/config.yaml +1 -0
  205. package/testcases/types/tuples-intersections/TuplesAndIntersections.ts +46 -0
  206. package/testcases/types/tuples-intersections/config.yaml +1 -0
  207. package/testcases/types/unions/UnionTypes.ts +11 -0
  208. package/testcases/types/unions/config.yaml +1 -0
  209. package/tsconfig.json +14 -0
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Literal expression emitters
3
+ */
4
+
5
+ import { IrExpression, IrType } from "@tsonic/frontend";
6
+ import { EmitterContext, CSharpFragment } from "../types.js";
7
+
8
+ /**
9
+ * C# types that require integer values (not double).
10
+ * Used to determine when numeric literals should emit as int vs double.
11
+ */
12
+ const INT_REQUIRED_TYPES = new Set([
13
+ "int",
14
+ "Int32",
15
+ "System.Int32",
16
+ "long",
17
+ "Int64",
18
+ "System.Int64",
19
+ "short",
20
+ "Int16",
21
+ "System.Int16",
22
+ "byte",
23
+ "Byte",
24
+ "System.Byte",
25
+ "sbyte",
26
+ "SByte",
27
+ "System.SByte",
28
+ "uint",
29
+ "UInt32",
30
+ "System.UInt32",
31
+ "ulong",
32
+ "UInt64",
33
+ "System.UInt64",
34
+ "ushort",
35
+ "UInt16",
36
+ "System.UInt16",
37
+ ]);
38
+
39
+ /**
40
+ * Check if a C# type requires integer values
41
+ */
42
+ export const requiresInteger = (clrType: string | undefined): boolean => {
43
+ if (!clrType) return false;
44
+ return INT_REQUIRED_TYPES.has(clrType);
45
+ };
46
+
47
+ /**
48
+ * Emit a literal value (string, number, boolean, null, undefined)
49
+ *
50
+ * @param expr - The literal expression
51
+ * @param context - Emitter context
52
+ * @param expectedClrType - Optional expected C# type (for int-required contexts)
53
+ */
54
+ export const emitLiteral = (
55
+ expr: Extract<IrExpression, { kind: "literal" }>,
56
+ context: EmitterContext,
57
+ expectedClrType?: string
58
+ ): [CSharpFragment, EmitterContext] => {
59
+ const { value } = expr;
60
+
61
+ if (value === null) {
62
+ return [{ text: "null" }, context];
63
+ }
64
+
65
+ if (value === undefined) {
66
+ return [{ text: "default" }, context];
67
+ }
68
+
69
+ if (typeof value === "string") {
70
+ // Escape the string for C#
71
+ const escaped = value
72
+ .replace(/\\/g, "\\\\")
73
+ .replace(/"/g, '\\"')
74
+ .replace(/\n/g, "\\n")
75
+ .replace(/\r/g, "\\r")
76
+ .replace(/\t/g, "\\t");
77
+ return [{ text: `"${escaped}"` }, context];
78
+ }
79
+
80
+ if (typeof value === "number") {
81
+ // Check if context requires integer (array index, int parameter, etc.)
82
+ // Priority: explicit expectedClrType > context.isArrayIndex > default (double)
83
+ const needsInteger =
84
+ requiresInteger(expectedClrType) || context.isArrayIndex === true;
85
+
86
+ if (needsInteger) {
87
+ // Integer-required context: emit without decimal
88
+ const text = Number.isInteger(value)
89
+ ? String(value)
90
+ : String(Math.floor(value));
91
+ return [{ text }, context];
92
+ }
93
+
94
+ // TypeScript `number` is always `double` in C#
95
+ // Emit as double: ensure decimal point for integer values
96
+ if (Number.isInteger(value) && !String(value).includes(".")) {
97
+ return [{ text: `${value}.0` }, context];
98
+ }
99
+ return [{ text: String(value) }, context];
100
+ }
101
+
102
+ if (typeof value === "boolean") {
103
+ return [{ text: value ? "true" : "false" }, context];
104
+ }
105
+
106
+ return [{ text: String(value) }, context];
107
+ };
108
+
109
+ /**
110
+ * Helper to determine expected CLR type from IrType for numeric contexts.
111
+ * Returns the CLR type name if it requires integer, undefined otherwise.
112
+ *
113
+ * TODO: Expand this to handle more cases:
114
+ * - Method parameter types (requires signature lookup)
115
+ * - LINQ methods: Take(int), Skip(int), ElementAt(int)
116
+ * - String methods: Substring(int, int)
117
+ * - Generic type argument inference contexts
118
+ */
119
+ export const getExpectedClrTypeForNumeric = (
120
+ irType: IrType | undefined
121
+ ): string | undefined => {
122
+ if (!irType) return undefined;
123
+
124
+ if (irType.kind === "primitiveType") {
125
+ // TS number -> C# double (default)
126
+ // TS doesn't have int type, so this won't trigger
127
+ return undefined;
128
+ }
129
+
130
+ if (irType.kind === "referenceType") {
131
+ // Check if it's an int type from .NET interop
132
+ if (requiresInteger(irType.name)) {
133
+ return irType.name;
134
+ }
135
+ }
136
+
137
+ return undefined;
138
+ };
@@ -0,0 +1,211 @@
1
+ /**
2
+ * Operator expression emitters (binary, logical, unary, update, assignment, conditional)
3
+ */
4
+
5
+ import { IrExpression } from "@tsonic/frontend";
6
+ import { EmitterContext, CSharpFragment } from "../types.js";
7
+ import { emitExpression } from "../expression-emitter.js";
8
+
9
+ /**
10
+ * Get operator precedence for proper parenthesization
11
+ */
12
+ const getPrecedence = (operator: string): number => {
13
+ const precedences: Record<string, number> = {
14
+ "||": 5,
15
+ "??": 5,
16
+ "&&": 6,
17
+ "|": 7,
18
+ "^": 8,
19
+ "&": 9,
20
+ "==": 10,
21
+ "!=": 10,
22
+ "===": 10,
23
+ "!==": 10,
24
+ "<": 11,
25
+ ">": 11,
26
+ "<=": 11,
27
+ ">=": 11,
28
+ instanceof: 11,
29
+ in: 11,
30
+ "<<": 12,
31
+ ">>": 12,
32
+ ">>>": 12,
33
+ "+": 13,
34
+ "-": 13,
35
+ "*": 14,
36
+ "/": 14,
37
+ "%": 14,
38
+ "**": 15,
39
+ };
40
+
41
+ return precedences[operator] ?? 16;
42
+ };
43
+
44
+ /**
45
+ * Emit a binary operator expression
46
+ */
47
+ export const emitBinary = (
48
+ expr: Extract<IrExpression, { kind: "binary" }>,
49
+ context: EmitterContext
50
+ ): [CSharpFragment, EmitterContext] => {
51
+ const [leftFrag, leftContext] = emitExpression(expr.left, context);
52
+ const [rightFrag, rightContext] = emitExpression(expr.right, leftContext);
53
+
54
+ // Map JavaScript operators to C# operators
55
+ const operatorMap: Record<string, string> = {
56
+ "===": "==",
57
+ "!==": "!=",
58
+ "==": "==", // Loose equality - needs special handling
59
+ "!=": "!=", // Loose inequality - needs special handling
60
+ instanceof: "is",
61
+ in: "/* in */", // Needs special handling
62
+ };
63
+
64
+ const op = operatorMap[expr.operator] ?? expr.operator;
65
+
66
+ // Handle typeof operator specially
67
+ if (expr.operator === "instanceof") {
68
+ const text = `${leftFrag.text} is ${rightFrag.text}`;
69
+ return [{ text, precedence: 7 }, rightContext];
70
+ }
71
+
72
+ const text = `${leftFrag.text} ${op} ${rightFrag.text}`;
73
+ return [{ text, precedence: getPrecedence(expr.operator) }, rightContext];
74
+ };
75
+
76
+ /**
77
+ * Check if an IR type is boolean
78
+ */
79
+ const isBooleanType = (type: IrExpression["inferredType"]): boolean => {
80
+ if (!type) return false;
81
+ return type.kind === "primitiveType" && type.name === "boolean";
82
+ };
83
+
84
+ /**
85
+ * Emit a logical operator expression (&&, ||, ??)
86
+ *
87
+ * In TypeScript, || is used both for:
88
+ * 1. Boolean OR (when operands are booleans)
89
+ * 2. Nullish coalescing fallback (when left operand is nullable)
90
+ *
91
+ * In C#:
92
+ * - || only works with booleans
93
+ * - ?? is used for nullish coalescing
94
+ *
95
+ * We check if || is used with non-boolean operands and emit ?? instead.
96
+ */
97
+ export const emitLogical = (
98
+ expr: Extract<IrExpression, { kind: "logical" }>,
99
+ context: EmitterContext
100
+ ): [CSharpFragment, EmitterContext] => {
101
+ const [leftFrag, leftContext] = emitExpression(expr.left, context);
102
+ const [rightFrag, rightContext] = emitExpression(expr.right, leftContext);
103
+
104
+ // If || is used with non-boolean left operand, use ?? instead for nullish coalescing
105
+ const operator =
106
+ expr.operator === "||" && !isBooleanType(expr.left.inferredType)
107
+ ? "??"
108
+ : expr.operator;
109
+
110
+ const text = `${leftFrag.text} ${operator} ${rightFrag.text}`;
111
+ return [{ text, precedence: getPrecedence(expr.operator) }, rightContext];
112
+ };
113
+
114
+ /**
115
+ * Emit a unary operator expression (-, +, !, ~, typeof, void, delete)
116
+ */
117
+ export const emitUnary = (
118
+ expr: Extract<IrExpression, { kind: "unary" }>,
119
+ context: EmitterContext
120
+ ): [CSharpFragment, EmitterContext] => {
121
+ const [operandFrag, newContext] = emitExpression(expr.expression, context);
122
+
123
+ if (expr.operator === "typeof") {
124
+ // typeof becomes global::Tsonic.Runtime.Operators.typeof()
125
+ const text = `global::Tsonic.Runtime.Operators.@typeof(${operandFrag.text})`;
126
+ return [{ text }, newContext];
127
+ }
128
+
129
+ if (expr.operator === "void") {
130
+ // void expression - evaluate and discard
131
+ const text = `(${operandFrag.text}, default)`;
132
+ return [{ text }, newContext];
133
+ }
134
+
135
+ if (expr.operator === "delete") {
136
+ // delete needs special handling
137
+ const text = `/* delete ${operandFrag.text} */`;
138
+ return [{ text }, newContext];
139
+ }
140
+
141
+ const text = `${expr.operator}${operandFrag.text}`;
142
+ return [{ text, precedence: 15 }, newContext];
143
+ };
144
+
145
+ /**
146
+ * Emit an update operator expression (++, --)
147
+ */
148
+ export const emitUpdate = (
149
+ expr: Extract<IrExpression, { kind: "update" }>,
150
+ context: EmitterContext
151
+ ): [CSharpFragment, EmitterContext] => {
152
+ const [operandFrag, newContext] = emitExpression(expr.expression, context);
153
+
154
+ const text = expr.prefix
155
+ ? `${expr.operator}${operandFrag.text}`
156
+ : `${operandFrag.text}${expr.operator}`;
157
+
158
+ return [{ text, precedence: 15 }, newContext];
159
+ };
160
+
161
+ /**
162
+ * Emit an assignment expression (=, +=, -=, etc.)
163
+ */
164
+ export const emitAssignment = (
165
+ expr: Extract<IrExpression, { kind: "assignment" }>,
166
+ context: EmitterContext
167
+ ): [CSharpFragment, EmitterContext] => {
168
+ // Left side can be an expression or a pattern (for destructuring)
169
+ let leftText: string;
170
+ let leftContext: EmitterContext;
171
+
172
+ if (
173
+ "kind" in expr.left &&
174
+ (expr.left.kind === "identifierPattern" ||
175
+ expr.left.kind === "arrayPattern" ||
176
+ expr.left.kind === "objectPattern")
177
+ ) {
178
+ // It's a pattern - for now emit a comment for destructuring
179
+ if (expr.left.kind === "identifierPattern") {
180
+ leftText = expr.left.name;
181
+ leftContext = context;
182
+ } else {
183
+ leftText = "/* destructuring */";
184
+ leftContext = context;
185
+ }
186
+ } else {
187
+ const [leftFrag, ctx] = emitExpression(expr.left as IrExpression, context);
188
+ leftText = leftFrag.text;
189
+ leftContext = ctx;
190
+ }
191
+
192
+ const [rightFrag, rightContext] = emitExpression(expr.right, leftContext);
193
+
194
+ const text = `${leftText} ${expr.operator} ${rightFrag.text}`;
195
+ return [{ text, precedence: 3 }, rightContext];
196
+ };
197
+
198
+ /**
199
+ * Emit a conditional (ternary) expression
200
+ */
201
+ export const emitConditional = (
202
+ expr: Extract<IrExpression, { kind: "conditional" }>,
203
+ context: EmitterContext
204
+ ): [CSharpFragment, EmitterContext] => {
205
+ const [condFrag, condContext] = emitExpression(expr.condition, context);
206
+ const [trueFrag, trueContext] = emitExpression(expr.whenTrue, condContext);
207
+ const [falseFrag, falseContext] = emitExpression(expr.whenFalse, trueContext);
208
+
209
+ const text = `${condFrag.text} ? ${trueFrag.text} : ${falseFrag.text}`;
210
+ return [{ text, precedence: 4 }, falseContext];
211
+ };
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Miscellaneous expression emitters (template literals, spread, await)
3
+ */
4
+
5
+ import { IrExpression } from "@tsonic/frontend";
6
+ import { EmitterContext, CSharpFragment } from "../types.js";
7
+ import { emitExpression } from "../expression-emitter.js";
8
+
9
+ /**
10
+ * Emit a template literal as C# interpolated string
11
+ */
12
+ export const emitTemplateLiteral = (
13
+ expr: Extract<IrExpression, { kind: "templateLiteral" }>,
14
+ context: EmitterContext
15
+ ): [CSharpFragment, EmitterContext] => {
16
+ let currentContext = context;
17
+ const parts: string[] = [];
18
+
19
+ for (let i = 0; i < expr.quasis.length; i++) {
20
+ const quasi = expr.quasis[i];
21
+ if (quasi !== undefined && quasi !== null) {
22
+ parts.push(quasi);
23
+ }
24
+
25
+ const exprAtIndex = expr.expressions[i];
26
+ if (i < expr.expressions.length && exprAtIndex) {
27
+ const [exprFrag, newContext] = emitExpression(
28
+ exprAtIndex,
29
+ currentContext
30
+ );
31
+ parts.push(`{${exprFrag.text}}`);
32
+ currentContext = newContext;
33
+ }
34
+ }
35
+
36
+ const text = `$"${parts.join("")}"`;
37
+ return [{ text }, currentContext];
38
+ };
39
+
40
+ /**
41
+ * Emit a spread expression
42
+ */
43
+ export const emitSpread = (
44
+ expr: Extract<IrExpression, { kind: "spread" }>,
45
+ context: EmitterContext
46
+ ): [CSharpFragment, EmitterContext] => {
47
+ const [exprFrag, newContext] = emitExpression(expr.expression, context);
48
+ // Spread syntax needs context-specific handling
49
+ const text = `...${exprFrag.text}`;
50
+ return [{ text }, newContext];
51
+ };
52
+
53
+ /**
54
+ * Emit an await expression
55
+ */
56
+ export const emitAwait = (
57
+ expr: Extract<IrExpression, { kind: "await" }>,
58
+ context: EmitterContext
59
+ ): [CSharpFragment, EmitterContext] => {
60
+ const [exprFrag, newContext] = emitExpression(expr.expression, context);
61
+ const text = `await ${exprFrag.text}`;
62
+ return [{ text }, newContext];
63
+ };
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Generator Exchange Object Generator
3
+ * Per spec/13-generators.md - Generate exchange objects for bidirectional communication
4
+ */
5
+
6
+ import { IrModule, IrFunctionDeclaration } from "@tsonic/frontend";
7
+ import { EmitterContext, getIndent, indent } from "./types.js";
8
+ import { emitType } from "./type-emitter.js";
9
+
10
+ /**
11
+ * Collect all generator functions from a module
12
+ */
13
+ const collectGenerators = (module: IrModule): IrFunctionDeclaration[] => {
14
+ const generators: IrFunctionDeclaration[] = [];
15
+
16
+ for (const stmt of module.body) {
17
+ if (stmt.kind === "functionDeclaration" && stmt.isGenerator) {
18
+ generators.push(stmt);
19
+ }
20
+ // TODO: Also handle generator methods in classes
21
+ }
22
+
23
+ return generators;
24
+ };
25
+
26
+ /**
27
+ * Generate exchange object class for a generator function
28
+ *
29
+ * Example:
30
+ * function* accumulator(start = 0): Generator<number, void, number> { }
31
+ *
32
+ * Generates:
33
+ * public sealed class accumulator_exchange
34
+ * {
35
+ * public double? Input { get; set; }
36
+ * public double Output { get; set; }
37
+ * }
38
+ */
39
+ const generateExchangeClass = (
40
+ func: IrFunctionDeclaration,
41
+ context: EmitterContext
42
+ ): [string, EmitterContext] => {
43
+ const ind = getIndent(context);
44
+ const bodyInd = getIndent(indent(context));
45
+ const parts: string[] = [];
46
+ let currentContext = context;
47
+
48
+ const exchangeName = `${func.name}_exchange`;
49
+
50
+ parts.push(`${ind}public sealed class ${exchangeName}`);
51
+ parts.push(`${ind}{`);
52
+
53
+ // Determine output type from return type or yield expressions
54
+ // For now, use 'object' as default, will refine based on type inference
55
+ let outputType = "object";
56
+ let inputType = "object";
57
+
58
+ if (func.returnType && func.returnType.kind === "referenceType") {
59
+ // Generator<Yield, Return, Next>
60
+ // typeArguments[0] is the Yield type (Output)
61
+ // typeArguments[2] is the Next type (Input)
62
+ const typeRef = func.returnType;
63
+ if (typeRef.typeArguments && typeRef.typeArguments.length > 0) {
64
+ const yieldTypeArg = typeRef.typeArguments[0];
65
+ if (yieldTypeArg) {
66
+ const [yieldType, newContext1] = emitType(yieldTypeArg, currentContext);
67
+ currentContext = newContext1;
68
+ outputType = yieldType;
69
+ }
70
+
71
+ if (typeRef.typeArguments.length > 2) {
72
+ const nextTypeArg = typeRef.typeArguments[2];
73
+ if (nextTypeArg) {
74
+ const [nextType, newContext2] = emitType(nextTypeArg, currentContext);
75
+ currentContext = newContext2;
76
+ inputType = nextType;
77
+ }
78
+ }
79
+ }
80
+ }
81
+
82
+ // Input property (always nullable since generator might not receive value)
83
+ parts.push(`${bodyInd}public ${inputType}? Input { get; set; }`);
84
+
85
+ // Output property
86
+ parts.push(`${bodyInd}public ${outputType} Output { get; set; }`);
87
+
88
+ parts.push(`${ind}}`);
89
+
90
+ return [parts.join("\n"), currentContext];
91
+ };
92
+
93
+ /**
94
+ * Generate all exchange objects for generators in a module
95
+ */
96
+ export const generateGeneratorExchanges = (
97
+ module: IrModule,
98
+ context: EmitterContext
99
+ ): [string, EmitterContext] => {
100
+ const generators = collectGenerators(module);
101
+
102
+ if (generators.length === 0) {
103
+ return ["", context];
104
+ }
105
+
106
+ const parts: string[] = [];
107
+ let currentContext = context;
108
+
109
+ for (const generator of generators) {
110
+ const [exchangeCode, newContext] = generateExchangeClass(
111
+ generator,
112
+ currentContext
113
+ );
114
+ currentContext = newContext;
115
+ parts.push(exchangeCode);
116
+ parts.push(""); // Blank line between exchange classes
117
+ }
118
+
119
+ return [parts.join("\n"), currentContext];
120
+ };