@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.
- package/package.json +34 -0
- package/scripts/update-golden-tests.ts +119 -0
- package/src/adapter-generator.ts +112 -0
- package/src/array.test.ts +301 -0
- package/src/constants.ts +32 -0
- package/src/core/exports.ts +36 -0
- package/src/core/imports.test.ts +83 -0
- package/src/core/imports.ts +243 -0
- package/src/core/index.ts +9 -0
- package/src/core/module-emitter/assembly.ts +83 -0
- package/src/core/module-emitter/header.ts +19 -0
- package/src/core/module-emitter/index.ts +17 -0
- package/src/core/module-emitter/namespace.ts +39 -0
- package/src/core/module-emitter/orchestrator.ts +98 -0
- package/src/core/module-emitter/separation.ts +41 -0
- package/src/core/module-emitter/static-container.ts +75 -0
- package/src/core/module-emitter.test.ts +154 -0
- package/src/core/module-emitter.ts +6 -0
- package/src/core/module-map.ts +218 -0
- package/src/core/options.ts +16 -0
- package/src/core/type-params.ts +34 -0
- package/src/emitter-types/context.ts +91 -0
- package/src/emitter-types/core.ts +138 -0
- package/src/emitter-types/csharp-types.ts +42 -0
- package/src/emitter-types/formatting.ts +13 -0
- package/src/emitter-types/fqn.ts +81 -0
- package/src/emitter-types/index.ts +31 -0
- package/src/emitter.ts +107 -0
- package/src/expression-emitter.ts +112 -0
- package/src/expressions/access.ts +104 -0
- package/src/expressions/calls.ts +264 -0
- package/src/expressions/collections.ts +354 -0
- package/src/expressions/functions.ts +71 -0
- package/src/expressions/identifiers.ts +125 -0
- package/src/expressions/index.test.ts +515 -0
- package/src/expressions/index.ts +38 -0
- package/src/expressions/literals.ts +138 -0
- package/src/expressions/operators.ts +211 -0
- package/src/expressions/other.ts +63 -0
- package/src/generator-exchange.ts +120 -0
- package/src/generator.test.ts +193 -0
- package/src/golden-tests/config-parser.ts +67 -0
- package/src/golden-tests/discovery.ts +61 -0
- package/src/golden-tests/index.ts +10 -0
- package/src/golden-tests/registration.ts +26 -0
- package/src/golden-tests/runner.ts +131 -0
- package/src/golden-tests/tree-builder.ts +43 -0
- package/src/golden-tests/types.ts +21 -0
- package/src/golden.test.ts +40 -0
- package/src/hierarchical-bindings.test.ts +258 -0
- package/src/index.ts +14 -0
- package/src/integration.test.ts +303 -0
- package/src/specialization/call-site-rewriting.test.ts +99 -0
- package/src/specialization/collection/expressions.ts +184 -0
- package/src/specialization/collection/index.ts +7 -0
- package/src/specialization/collection/orchestrator.ts +25 -0
- package/src/specialization/collection/statements.ts +91 -0
- package/src/specialization/collection.ts +10 -0
- package/src/specialization/generation.ts +189 -0
- package/src/specialization/generic-classes.test.ts +59 -0
- package/src/specialization/generic-functions.test.ts +292 -0
- package/src/specialization/helpers.ts +39 -0
- package/src/specialization/index.ts +28 -0
- package/src/specialization/interfaces.test.ts +151 -0
- package/src/specialization/naming.ts +34 -0
- package/src/specialization/substitution.ts +186 -0
- package/src/specialization/type-aliases.test.ts +134 -0
- package/src/specialization/types.ts +19 -0
- package/src/specialization-generator.ts +23 -0
- package/src/statement-emitter.ts +117 -0
- package/src/statements/blocks.ts +115 -0
- package/src/statements/classes/helpers.ts +9 -0
- package/src/statements/classes/index.ts +13 -0
- package/src/statements/classes/inline-types.ts +79 -0
- package/src/statements/classes/members/constructors.ts +123 -0
- package/src/statements/classes/members/index.ts +8 -0
- package/src/statements/classes/members/methods.ts +137 -0
- package/src/statements/classes/members/orchestrator.ts +33 -0
- package/src/statements/classes/members/properties.ts +62 -0
- package/src/statements/classes/members.ts +6 -0
- package/src/statements/classes/parameters.ts +69 -0
- package/src/statements/classes/properties.ts +95 -0
- package/src/statements/classes.ts +14 -0
- package/src/statements/control/conditionals.ts +134 -0
- package/src/statements/control/exceptions.ts +59 -0
- package/src/statements/control/index.ts +11 -0
- package/src/statements/control/loops.ts +250 -0
- package/src/statements/control.ts +14 -0
- package/src/statements/declarations/classes.ts +89 -0
- package/src/statements/declarations/enums.ts +32 -0
- package/src/statements/declarations/functions.ts +147 -0
- package/src/statements/declarations/index.ts +10 -0
- package/src/statements/declarations/interfaces.ts +116 -0
- package/src/statements/declarations/structs.test.ts +182 -0
- package/src/statements/declarations/type-aliases.ts +104 -0
- package/src/statements/declarations/variables.ts +159 -0
- package/src/statements/declarations.ts +13 -0
- package/src/statements/index.test.ts +258 -0
- package/src/statements/index.ts +43 -0
- package/src/type-assertion.test.ts +143 -0
- package/src/type-emitter.ts +18 -0
- package/src/types/arrays.ts +21 -0
- package/src/types/dictionaries.ts +52 -0
- package/src/types/emitter.ts +76 -0
- package/src/types/functions.ts +45 -0
- package/src/types/index.test.ts +116 -0
- package/src/types/index.ts +14 -0
- package/src/types/intersections.ts +19 -0
- package/src/types/literals.ts +26 -0
- package/src/types/objects.ts +27 -0
- package/src/types/parameters.test.ts +146 -0
- package/src/types/parameters.ts +95 -0
- package/src/types/primitives.ts +24 -0
- package/src/types/references.ts +187 -0
- package/src/types/unions.test.ts +397 -0
- package/src/types/unions.ts +62 -0
- package/src/types.ts +33 -0
- package/testcases/README.md +213 -0
- package/testcases/arrays/basic/ArrayLiteral.ts +4 -0
- package/testcases/arrays/basic/config.yaml +1 -0
- package/testcases/arrays/destructuring/ArrayDestructure.ts +4 -0
- package/testcases/arrays/destructuring/config.yaml +1 -0
- package/testcases/arrays/methods/ArrayMethods.ts +6 -0
- package/testcases/arrays/methods/config.yaml +1 -0
- package/testcases/arrays/multidimensional/MultiDimensional.ts +10 -0
- package/testcases/arrays/multidimensional/config.yaml +1 -0
- package/testcases/arrays/spread/ArraySpread.ts +3 -0
- package/testcases/arrays/spread/config.yaml +1 -0
- package/testcases/async/basic/AsyncFunction.ts +5 -0
- package/testcases/async/basic/config.yaml +1 -0
- package/testcases/classes/abstract/AbstractClasses.ts +53 -0
- package/testcases/classes/abstract/config.yaml +1 -0
- package/testcases/classes/basic/Person.ts +12 -0
- package/testcases/classes/basic/config.yaml +1 -0
- package/testcases/classes/constructor/User.ts +11 -0
- package/testcases/classes/constructor/config.yaml +1 -0
- package/testcases/classes/field-inference/Counter.ts +11 -0
- package/testcases/classes/field-inference/config.yaml +1 -0
- package/testcases/classes/inheritance/Inheritance.ts +24 -0
- package/testcases/classes/inheritance/config.yaml +1 -0
- package/testcases/classes/static-members/MathHelper.ts +12 -0
- package/testcases/classes/static-members/config.yaml +1 -0
- package/testcases/control-flow/error-handling/ErrorHandling.ts +13 -0
- package/testcases/control-flow/error-handling/config.yaml +1 -0
- package/testcases/control-flow/loops/Loops.ts +21 -0
- package/testcases/control-flow/loops/config.yaml +1 -0
- package/testcases/control-flow/switch/SwitchStatement.ts +15 -0
- package/testcases/control-flow/switch/config.yaml +1 -0
- package/testcases/edge-cases/complex-expressions/ComplexExpressions.ts +10 -0
- package/testcases/edge-cases/complex-expressions/config.yaml +1 -0
- package/testcases/edge-cases/nested-scopes/NestedScopes.ts +10 -0
- package/testcases/edge-cases/nested-scopes/config.yaml +1 -0
- package/testcases/edge-cases/shadowing/Shadowing.ts +16 -0
- package/testcases/edge-cases/shadowing/config.yaml +1 -0
- package/testcases/functions/arrow/ArrowFunction.ts +5 -0
- package/testcases/functions/arrow/config.yaml +1 -0
- package/testcases/functions/basic/Greet.ts +3 -0
- package/testcases/functions/basic/config.yaml +1 -0
- package/testcases/functions/closures/Closures.ts +11 -0
- package/testcases/functions/closures/config.yaml +1 -0
- package/testcases/functions/default-params/DefaultParams.ts +7 -0
- package/testcases/functions/default-params/config.yaml +1 -0
- package/testcases/functions/rest-params/RestParams.ts +7 -0
- package/testcases/functions/rest-params/config.yaml +1 -0
- package/testcases/functions/type-guards/TypeGuards.ts +52 -0
- package/testcases/functions/type-guards/config.yaml +1 -0
- package/testcases/operators/logical/LogicalOperators.ts +11 -0
- package/testcases/operators/logical/config.yaml +1 -0
- package/testcases/operators/nullish-coalescing/NullishCoalescing.ts +7 -0
- package/testcases/operators/nullish-coalescing/config.yaml +1 -0
- package/testcases/operators/optional-chaining/OptionalChaining.ts +15 -0
- package/testcases/operators/optional-chaining/config.yaml +1 -0
- package/testcases/real-world/advanced-generics/advanced-generics.ts +116 -0
- package/testcases/real-world/advanced-generics/config.yaml +1 -0
- package/testcases/real-world/async-ops/async-ops.ts +67 -0
- package/testcases/real-world/async-ops/config.yaml +1 -0
- package/testcases/real-world/business-logic/business-logic.ts +215 -0
- package/testcases/real-world/business-logic/config.yaml +1 -0
- package/testcases/real-world/calculator/calculator.ts +29 -0
- package/testcases/real-world/calculator/config.yaml +1 -0
- package/testcases/real-world/data-structures/config.yaml +1 -0
- package/testcases/real-world/data-structures/data-structures.ts +133 -0
- package/testcases/real-world/functional/config.yaml +1 -0
- package/testcases/real-world/functional/functional.ts +116 -0
- package/testcases/real-world/shapes/config.yaml +1 -0
- package/testcases/real-world/shapes/shapes.ts +87 -0
- package/testcases/real-world/string-utils/config.yaml +1 -0
- package/testcases/real-world/string-utils/string-utils.ts +47 -0
- package/testcases/real-world/todo-list/config.yaml +1 -0
- package/testcases/real-world/todo-list/todo-list.ts +52 -0
- package/testcases/real-world/type-guards/config.yaml +1 -0
- package/testcases/real-world/type-guards/type-guards.ts +71 -0
- package/testcases/structs/basic/Point.ts +9 -0
- package/testcases/structs/basic/config.yaml +1 -0
- package/testcases/types/conditional/ConditionalTypes.ts +35 -0
- package/testcases/types/conditional/config.yaml +1 -0
- package/testcases/types/constants/ModuleConstants.ts +6 -0
- package/testcases/types/constants/config.yaml +1 -0
- package/testcases/types/generics/Generics.ts +15 -0
- package/testcases/types/generics/config.yaml +1 -0
- package/testcases/types/interfaces/Interfaces.ts +14 -0
- package/testcases/types/interfaces/config.yaml +1 -0
- package/testcases/types/mapped/MappedTypes.ts +27 -0
- package/testcases/types/mapped/config.yaml +1 -0
- package/testcases/types/tuples-intersections/TuplesAndIntersections.ts +46 -0
- package/testcases/types/tuples-intersections/config.yaml +1 -0
- package/testcases/types/unions/UnionTypes.ts +11 -0
- package/testcases/types/unions/config.yaml +1 -0
- 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
|
+
};
|