@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,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exception handling emitters (try, throw)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { IrStatement } from "@tsonic/frontend";
|
|
6
|
+
import { EmitterContext, getIndent } from "../../types.js";
|
|
7
|
+
import { emitExpression } from "../../expression-emitter.js";
|
|
8
|
+
import { emitBlockStatement } from "../blocks.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Emit a try statement
|
|
12
|
+
*/
|
|
13
|
+
export const emitTryStatement = (
|
|
14
|
+
stmt: Extract<IrStatement, { kind: "tryStatement" }>,
|
|
15
|
+
context: EmitterContext
|
|
16
|
+
): [string, EmitterContext] => {
|
|
17
|
+
const ind = getIndent(context);
|
|
18
|
+
const [tryBlock, tryContext] = emitBlockStatement(stmt.tryBlock, context);
|
|
19
|
+
|
|
20
|
+
let code = `${ind}try\n${tryBlock}`;
|
|
21
|
+
let currentContext = tryContext;
|
|
22
|
+
|
|
23
|
+
if (stmt.catchClause) {
|
|
24
|
+
const param =
|
|
25
|
+
stmt.catchClause.parameter?.kind === "identifierPattern"
|
|
26
|
+
? stmt.catchClause.parameter.name
|
|
27
|
+
: "ex";
|
|
28
|
+
|
|
29
|
+
const [catchBlock, catchContext] = emitBlockStatement(
|
|
30
|
+
stmt.catchClause.body,
|
|
31
|
+
currentContext
|
|
32
|
+
);
|
|
33
|
+
code += `\n${ind}catch (Exception ${param})\n${catchBlock}`;
|
|
34
|
+
currentContext = catchContext;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (stmt.finallyBlock) {
|
|
38
|
+
const [finallyBlock, finallyContext] = emitBlockStatement(
|
|
39
|
+
stmt.finallyBlock,
|
|
40
|
+
currentContext
|
|
41
|
+
);
|
|
42
|
+
code += `\n${ind}finally\n${finallyBlock}`;
|
|
43
|
+
currentContext = finallyContext;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return [code, currentContext];
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Emit a throw statement
|
|
51
|
+
*/
|
|
52
|
+
export const emitThrowStatement = (
|
|
53
|
+
stmt: Extract<IrStatement, { kind: "throwStatement" }>,
|
|
54
|
+
context: EmitterContext
|
|
55
|
+
): [string, EmitterContext] => {
|
|
56
|
+
const ind = getIndent(context);
|
|
57
|
+
const [exprFrag, newContext] = emitExpression(stmt.expression, context);
|
|
58
|
+
return [`${ind}throw ${exprFrag.text};`, newContext];
|
|
59
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Control flow statement emitters - Public API
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export {
|
|
6
|
+
emitWhileStatement,
|
|
7
|
+
emitForStatement,
|
|
8
|
+
emitForOfStatement,
|
|
9
|
+
} from "./loops.js";
|
|
10
|
+
export { emitIfStatement, emitSwitchStatement } from "./conditionals.js";
|
|
11
|
+
export { emitTryStatement, emitThrowStatement } from "./exceptions.js";
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loop statement emitters (while, for, for-of)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { IrStatement, IrExpression } from "@tsonic/frontend";
|
|
6
|
+
import { EmitterContext, getIndent, indent, dedent } from "../../types.js";
|
|
7
|
+
import { emitExpression } from "../../expression-emitter.js";
|
|
8
|
+
import { emitStatement } from "../../statement-emitter.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Information about a canonical integer loop counter.
|
|
12
|
+
* Canonical form: `for (let i = 0; i < n; i++)`
|
|
13
|
+
*/
|
|
14
|
+
type CanonicalIntLoop = {
|
|
15
|
+
readonly varName: string;
|
|
16
|
+
readonly initialValue: number;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Detect if a for-loop has a canonical integer loop counter pattern.
|
|
21
|
+
*
|
|
22
|
+
* Canonical patterns:
|
|
23
|
+
* - Initializer: `let i = 0` (or any integer literal)
|
|
24
|
+
* - Update: `i++`, `++i`, `i += 1`, `i = i + 1`
|
|
25
|
+
*
|
|
26
|
+
* Returns the variable name and initial value if canonical, undefined otherwise.
|
|
27
|
+
*/
|
|
28
|
+
const detectCanonicalIntLoop = (
|
|
29
|
+
stmt: Extract<IrStatement, { kind: "forStatement" }>
|
|
30
|
+
): CanonicalIntLoop | undefined => {
|
|
31
|
+
const { initializer, update } = stmt;
|
|
32
|
+
|
|
33
|
+
// Check initializer: must be `let varName = <integer literal>`
|
|
34
|
+
if (!initializer || initializer.kind !== "variableDeclaration") {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (initializer.declarationKind !== "let") {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (initializer.declarations.length !== 1) {
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const decl = initializer.declarations[0];
|
|
47
|
+
if (!decl || decl.name.kind !== "identifierPattern") {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const varName = decl.name.name;
|
|
52
|
+
|
|
53
|
+
// Check initializer value: must be an integer literal
|
|
54
|
+
const declInit = decl.initializer;
|
|
55
|
+
if (!declInit || declInit.kind !== "literal") {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const initValue = declInit.value;
|
|
60
|
+
if (typeof initValue !== "number" || !Number.isInteger(initValue)) {
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Check update: must be i++, ++i, i += 1, or i = i + 1
|
|
65
|
+
if (!update) {
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (!isIntegerIncrement(update, varName)) {
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return { varName, initialValue: initValue };
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Check if an expression is an integer increment of a variable.
|
|
78
|
+
* Matches: i++, ++i, i += 1, i = i + 1
|
|
79
|
+
*/
|
|
80
|
+
const isIntegerIncrement = (expr: IrExpression, varName: string): boolean => {
|
|
81
|
+
// i++ or ++i
|
|
82
|
+
if (expr.kind === "update") {
|
|
83
|
+
if (expr.operator !== "++") {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
if (expr.expression.kind !== "identifier") {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
return expr.expression.name === varName;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// i += 1 or i = i + 1
|
|
93
|
+
if (expr.kind === "assignment") {
|
|
94
|
+
if (expr.left.kind !== "identifier" || expr.left.name !== varName) {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// i += 1
|
|
99
|
+
if (expr.operator === "+=") {
|
|
100
|
+
if (expr.right.kind !== "literal") {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
return expr.right.value === 1;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// i = i + 1
|
|
107
|
+
if (expr.operator === "=") {
|
|
108
|
+
if (expr.right.kind !== "binary" || expr.right.operator !== "+") {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
const binExpr = expr.right;
|
|
112
|
+
// Check i + 1 or 1 + i
|
|
113
|
+
const isVarPlusOne =
|
|
114
|
+
binExpr.left.kind === "identifier" &&
|
|
115
|
+
binExpr.left.name === varName &&
|
|
116
|
+
binExpr.right.kind === "literal" &&
|
|
117
|
+
binExpr.right.value === 1;
|
|
118
|
+
const isOnePlusVar =
|
|
119
|
+
binExpr.left.kind === "literal" &&
|
|
120
|
+
binExpr.left.value === 1 &&
|
|
121
|
+
binExpr.right.kind === "identifier" &&
|
|
122
|
+
binExpr.right.name === varName;
|
|
123
|
+
return isVarPlusOne || isOnePlusVar;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return false;
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Emit a while statement
|
|
132
|
+
*/
|
|
133
|
+
export const emitWhileStatement = (
|
|
134
|
+
stmt: Extract<IrStatement, { kind: "whileStatement" }>,
|
|
135
|
+
context: EmitterContext
|
|
136
|
+
): [string, EmitterContext] => {
|
|
137
|
+
const ind = getIndent(context);
|
|
138
|
+
const [condFrag, condContext] = emitExpression(stmt.condition, context);
|
|
139
|
+
|
|
140
|
+
const [bodyCode, bodyContext] = emitStatement(stmt.body, indent(condContext));
|
|
141
|
+
|
|
142
|
+
const code = `${ind}while (${condFrag.text})\n${bodyCode}`;
|
|
143
|
+
return [code, dedent(bodyContext)];
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Emit a for statement
|
|
148
|
+
*
|
|
149
|
+
* Special handling for canonical integer loop counters:
|
|
150
|
+
* `for (let i = 0; i < n; i++)` emits as `for (int i = 0; ...)` in C#.
|
|
151
|
+
* This avoids the double→int conversion cost when using loop variables
|
|
152
|
+
* as CLR indexers (e.g., list[i]).
|
|
153
|
+
*/
|
|
154
|
+
export const emitForStatement = (
|
|
155
|
+
stmt: Extract<IrStatement, { kind: "forStatement" }>,
|
|
156
|
+
context: EmitterContext
|
|
157
|
+
): [string, EmitterContext] => {
|
|
158
|
+
const ind = getIndent(context);
|
|
159
|
+
let currentContext = context;
|
|
160
|
+
|
|
161
|
+
// Check for canonical integer loop pattern
|
|
162
|
+
const canonicalLoop = detectCanonicalIntLoop(stmt);
|
|
163
|
+
|
|
164
|
+
// Initializer
|
|
165
|
+
let init = "";
|
|
166
|
+
if (stmt.initializer) {
|
|
167
|
+
if (canonicalLoop) {
|
|
168
|
+
// Canonical integer loop: emit `int varName = value` directly
|
|
169
|
+
init = `int ${canonicalLoop.varName} = ${canonicalLoop.initialValue}`;
|
|
170
|
+
} else if (stmt.initializer.kind === "variableDeclaration") {
|
|
171
|
+
const [initCode, newContext] = emitStatement(
|
|
172
|
+
stmt.initializer,
|
|
173
|
+
currentContext
|
|
174
|
+
);
|
|
175
|
+
currentContext = newContext;
|
|
176
|
+
// Strip trailing semicolon from variable declaration
|
|
177
|
+
init = initCode.trim().replace(/;$/, "");
|
|
178
|
+
} else {
|
|
179
|
+
const [initFrag, newContext] = emitExpression(
|
|
180
|
+
stmt.initializer,
|
|
181
|
+
currentContext
|
|
182
|
+
);
|
|
183
|
+
currentContext = newContext;
|
|
184
|
+
init = initFrag.text;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Condition
|
|
189
|
+
let cond = "";
|
|
190
|
+
if (stmt.condition) {
|
|
191
|
+
const [condFrag, newContext] = emitExpression(
|
|
192
|
+
stmt.condition,
|
|
193
|
+
currentContext
|
|
194
|
+
);
|
|
195
|
+
currentContext = newContext;
|
|
196
|
+
cond = condFrag.text;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Update
|
|
200
|
+
let update = "";
|
|
201
|
+
if (stmt.update) {
|
|
202
|
+
const [updateFrag, newContext] = emitExpression(
|
|
203
|
+
stmt.update,
|
|
204
|
+
currentContext
|
|
205
|
+
);
|
|
206
|
+
currentContext = newContext;
|
|
207
|
+
update = updateFrag.text;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Body - if canonical loop, add the var to intLoopVars so indexers don't cast
|
|
211
|
+
let bodyContext: EmitterContext;
|
|
212
|
+
if (canonicalLoop) {
|
|
213
|
+
const existingIntVars = currentContext.intLoopVars ?? new Set<string>();
|
|
214
|
+
const newIntVars = new Set([...existingIntVars, canonicalLoop.varName]);
|
|
215
|
+
const contextWithIntVar = {
|
|
216
|
+
...indent(currentContext),
|
|
217
|
+
intLoopVars: newIntVars,
|
|
218
|
+
};
|
|
219
|
+
const [code, ctx] = emitStatement(stmt.body, contextWithIntVar);
|
|
220
|
+
// Remove the var from intLoopVars after body (restore previous scope)
|
|
221
|
+
bodyContext = { ...ctx, intLoopVars: existingIntVars };
|
|
222
|
+
const finalCode = `${ind}for (${init}; ${cond}; ${update})\n${code}`;
|
|
223
|
+
return [finalCode, dedent(bodyContext)];
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const [bodyCode, ctx] = emitStatement(stmt.body, indent(currentContext));
|
|
227
|
+
bodyContext = ctx;
|
|
228
|
+
|
|
229
|
+
const code = `${ind}for (${init}; ${cond}; ${update})\n${bodyCode}`;
|
|
230
|
+
return [code, dedent(bodyContext)];
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Emit a for-of statement
|
|
235
|
+
*/
|
|
236
|
+
export const emitForOfStatement = (
|
|
237
|
+
stmt: Extract<IrStatement, { kind: "forOfStatement" }>,
|
|
238
|
+
context: EmitterContext
|
|
239
|
+
): [string, EmitterContext] => {
|
|
240
|
+
const ind = getIndent(context);
|
|
241
|
+
const [exprFrag, exprContext] = emitExpression(stmt.expression, context);
|
|
242
|
+
|
|
243
|
+
const [bodyCode, bodyContext] = emitStatement(stmt.body, indent(exprContext));
|
|
244
|
+
|
|
245
|
+
// Use foreach in C#
|
|
246
|
+
const varName =
|
|
247
|
+
stmt.variable.kind === "identifierPattern" ? stmt.variable.name : "item";
|
|
248
|
+
const code = `${ind}foreach (var ${varName} in ${exprFrag.text})\n${bodyCode}`;
|
|
249
|
+
return [code, dedent(bodyContext)];
|
|
250
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Control flow statement emitters (if, while, for, switch, try, throw)
|
|
3
|
+
* Main dispatcher - re-exports from control/ subdirectory
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export {
|
|
7
|
+
emitIfStatement,
|
|
8
|
+
emitWhileStatement,
|
|
9
|
+
emitForStatement,
|
|
10
|
+
emitForOfStatement,
|
|
11
|
+
emitSwitchStatement,
|
|
12
|
+
emitTryStatement,
|
|
13
|
+
emitThrowStatement,
|
|
14
|
+
} from "./control/index.js";
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Class declaration emission
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { IrStatement } from "@tsonic/frontend";
|
|
6
|
+
import {
|
|
7
|
+
EmitterContext,
|
|
8
|
+
getIndent,
|
|
9
|
+
indent,
|
|
10
|
+
withClassName,
|
|
11
|
+
} from "../../types.js";
|
|
12
|
+
import { emitExpression } from "../../expression-emitter.js";
|
|
13
|
+
import { emitType, emitTypeParameters } from "../../type-emitter.js";
|
|
14
|
+
import { emitClassMember } from "../classes.js";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Emit a class declaration
|
|
18
|
+
*/
|
|
19
|
+
export const emitClassDeclaration = (
|
|
20
|
+
stmt: Extract<IrStatement, { kind: "classDeclaration" }>,
|
|
21
|
+
context: EmitterContext
|
|
22
|
+
): [string, EmitterContext] => {
|
|
23
|
+
const ind = getIndent(context);
|
|
24
|
+
let currentContext = context;
|
|
25
|
+
const parts: string[] = [];
|
|
26
|
+
|
|
27
|
+
// Access modifiers
|
|
28
|
+
const accessibility = stmt.isExported ? "public" : "internal";
|
|
29
|
+
parts.push(accessibility);
|
|
30
|
+
|
|
31
|
+
// Emit struct or class based on isStruct flag
|
|
32
|
+
parts.push(stmt.isStruct ? "struct" : "class");
|
|
33
|
+
parts.push(stmt.name);
|
|
34
|
+
|
|
35
|
+
// Type parameters
|
|
36
|
+
const [typeParamsStr, whereClauses, typeParamContext] = emitTypeParameters(
|
|
37
|
+
stmt.typeParameters,
|
|
38
|
+
currentContext
|
|
39
|
+
);
|
|
40
|
+
currentContext = typeParamContext;
|
|
41
|
+
|
|
42
|
+
// Base class and interfaces
|
|
43
|
+
const heritage: string[] = [];
|
|
44
|
+
|
|
45
|
+
// Handle superclass (extends clause)
|
|
46
|
+
if (stmt.superClass) {
|
|
47
|
+
const [superClassFrag, newContext] = emitExpression(
|
|
48
|
+
stmt.superClass,
|
|
49
|
+
currentContext
|
|
50
|
+
);
|
|
51
|
+
currentContext = newContext;
|
|
52
|
+
heritage.push(superClassFrag.text);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Handle interfaces (implements clause)
|
|
56
|
+
if (stmt.implements && stmt.implements.length > 0) {
|
|
57
|
+
for (const iface of stmt.implements) {
|
|
58
|
+
const [ifaceType, newContext] = emitType(iface, currentContext);
|
|
59
|
+
currentContext = newContext;
|
|
60
|
+
heritage.push(ifaceType);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const heritageStr = heritage.length > 0 ? ` : ${heritage.join(", ")}` : "";
|
|
65
|
+
const whereClause =
|
|
66
|
+
whereClauses.length > 0
|
|
67
|
+
? `\n${ind} ${whereClauses.join(`\n${ind} `)}`
|
|
68
|
+
: "";
|
|
69
|
+
|
|
70
|
+
// Class body
|
|
71
|
+
const baseContext = withClassName(indent(currentContext), stmt.name);
|
|
72
|
+
// Only set hasSuperClass flag if there's actually a superclass (for inheritance)
|
|
73
|
+
const bodyContext = stmt.superClass
|
|
74
|
+
? { ...baseContext, hasSuperClass: true }
|
|
75
|
+
: baseContext;
|
|
76
|
+
const members: string[] = [];
|
|
77
|
+
|
|
78
|
+
for (const member of stmt.members) {
|
|
79
|
+
const [memberCode, newContext] = emitClassMember(member, bodyContext);
|
|
80
|
+
members.push(memberCode);
|
|
81
|
+
currentContext = newContext;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const signature = parts.join(" ");
|
|
85
|
+
const memberCode = members.join("\n\n");
|
|
86
|
+
const code = `${ind}${signature}${typeParamsStr}${heritageStr}${whereClause}\n${ind}{\n${memberCode}\n${ind}}`;
|
|
87
|
+
|
|
88
|
+
return [code, currentContext];
|
|
89
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enum declaration emission
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { IrStatement } from "@tsonic/frontend";
|
|
6
|
+
import { EmitterContext, getIndent, indent } from "../../types.js";
|
|
7
|
+
import { emitExpression } from "../../expression-emitter.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Emit an enum declaration
|
|
11
|
+
*/
|
|
12
|
+
export const emitEnumDeclaration = (
|
|
13
|
+
stmt: Extract<IrStatement, { kind: "enumDeclaration" }>,
|
|
14
|
+
context: EmitterContext
|
|
15
|
+
): [string, EmitterContext] => {
|
|
16
|
+
const ind = getIndent(context);
|
|
17
|
+
const memberInd = getIndent(indent(context));
|
|
18
|
+
|
|
19
|
+
const accessibility = stmt.isExported ? "public" : "internal";
|
|
20
|
+
const members = stmt.members
|
|
21
|
+
.map((member) => {
|
|
22
|
+
if (member.initializer) {
|
|
23
|
+
const [initFrag] = emitExpression(member.initializer, context);
|
|
24
|
+
return `${memberInd}${member.name} = ${initFrag.text}`;
|
|
25
|
+
}
|
|
26
|
+
return `${memberInd}${member.name}`;
|
|
27
|
+
})
|
|
28
|
+
.join(",\n");
|
|
29
|
+
|
|
30
|
+
const code = `${ind}${accessibility} enum ${stmt.name}\n${ind}{\n${members}\n${ind}}`;
|
|
31
|
+
return [code, context];
|
|
32
|
+
};
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Function declaration emission
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { IrStatement } from "@tsonic/frontend";
|
|
6
|
+
import {
|
|
7
|
+
EmitterContext,
|
|
8
|
+
getIndent,
|
|
9
|
+
indent,
|
|
10
|
+
withAsync,
|
|
11
|
+
withStatic,
|
|
12
|
+
} from "../../types.js";
|
|
13
|
+
import { emitType, emitTypeParameters } from "../../type-emitter.js";
|
|
14
|
+
import { emitBlockStatement } from "../blocks.js";
|
|
15
|
+
import { emitParameters } from "../classes.js";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Emit a function declaration
|
|
19
|
+
*/
|
|
20
|
+
export const emitFunctionDeclaration = (
|
|
21
|
+
stmt: Extract<IrStatement, { kind: "functionDeclaration" }>,
|
|
22
|
+
context: EmitterContext
|
|
23
|
+
): [string, EmitterContext] => {
|
|
24
|
+
const ind = getIndent(context);
|
|
25
|
+
let currentContext = context;
|
|
26
|
+
const parts: string[] = [];
|
|
27
|
+
|
|
28
|
+
// Access modifiers
|
|
29
|
+
const accessibility = stmt.isExported ? "public" : "private";
|
|
30
|
+
parts.push(accessibility);
|
|
31
|
+
|
|
32
|
+
if (context.isStatic) {
|
|
33
|
+
parts.push("static");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (stmt.isAsync && !stmt.isGenerator) {
|
|
37
|
+
parts.push("async");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Return type
|
|
41
|
+
if (stmt.isGenerator) {
|
|
42
|
+
// Generator functions return IEnumerable<exchange> or IAsyncEnumerable<exchange>
|
|
43
|
+
const exchangeName = `${stmt.name}_exchange`;
|
|
44
|
+
if (stmt.isAsync) {
|
|
45
|
+
parts.push(
|
|
46
|
+
`async global::System.Collections.Generic.IAsyncEnumerable<${exchangeName}>`
|
|
47
|
+
);
|
|
48
|
+
} else {
|
|
49
|
+
parts.push(
|
|
50
|
+
`global::System.Collections.Generic.IEnumerable<${exchangeName}>`
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
} else if (stmt.returnType) {
|
|
54
|
+
const [returnType, newContext] = emitType(stmt.returnType, currentContext);
|
|
55
|
+
currentContext = newContext;
|
|
56
|
+
// If async and return type is Promise, it's already converted to Task
|
|
57
|
+
// Don't wrap it again
|
|
58
|
+
if (
|
|
59
|
+
stmt.isAsync &&
|
|
60
|
+
stmt.returnType.kind === "referenceType" &&
|
|
61
|
+
stmt.returnType.name === "Promise"
|
|
62
|
+
) {
|
|
63
|
+
parts.push(returnType); // Already Task<T> from emitType
|
|
64
|
+
} else {
|
|
65
|
+
parts.push(
|
|
66
|
+
stmt.isAsync
|
|
67
|
+
? `global::System.Threading.Tasks.Task<${returnType}>`
|
|
68
|
+
: returnType
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
} else {
|
|
72
|
+
parts.push(stmt.isAsync ? "global::System.Threading.Tasks.Task" : "void");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Function name
|
|
76
|
+
parts.push(stmt.name);
|
|
77
|
+
|
|
78
|
+
// Type parameters
|
|
79
|
+
const [typeParamsStr, whereClauses, typeParamContext] = emitTypeParameters(
|
|
80
|
+
stmt.typeParameters,
|
|
81
|
+
currentContext
|
|
82
|
+
);
|
|
83
|
+
currentContext = typeParamContext;
|
|
84
|
+
|
|
85
|
+
// Parameters
|
|
86
|
+
const params = emitParameters(stmt.parameters, currentContext);
|
|
87
|
+
currentContext = params[1];
|
|
88
|
+
|
|
89
|
+
// Function body (not a static context - local variables)
|
|
90
|
+
const bodyContext = withAsync(
|
|
91
|
+
withStatic(indent(currentContext), false),
|
|
92
|
+
stmt.isAsync
|
|
93
|
+
);
|
|
94
|
+
const [bodyCode] = emitBlockStatement(stmt.body, bodyContext);
|
|
95
|
+
|
|
96
|
+
// Collect out parameters that need initialization
|
|
97
|
+
const outParams: Array<{ name: string; type: string }> = [];
|
|
98
|
+
for (const param of stmt.parameters) {
|
|
99
|
+
// Use param.passing to detect out parameters (type is already unwrapped by frontend)
|
|
100
|
+
if (param.passing === "out" && param.pattern.kind === "identifierPattern") {
|
|
101
|
+
// Get the type for default value
|
|
102
|
+
let typeName = "object";
|
|
103
|
+
if (param.type) {
|
|
104
|
+
const [typeStr] = emitType(param.type, currentContext);
|
|
105
|
+
typeName = typeStr;
|
|
106
|
+
}
|
|
107
|
+
outParams.push({ name: param.pattern.name, type: typeName });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Inject initialization code for generators and out parameters
|
|
112
|
+
let finalBodyCode = bodyCode;
|
|
113
|
+
const bodyInd = getIndent(bodyContext);
|
|
114
|
+
const injectLines: string[] = [];
|
|
115
|
+
|
|
116
|
+
// Add generator exchange initialization
|
|
117
|
+
if (stmt.isGenerator) {
|
|
118
|
+
const exchangeName = `${stmt.name}_exchange`;
|
|
119
|
+
injectLines.push(`${bodyInd}var exchange = new ${exchangeName}();`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Add out parameter initializations
|
|
123
|
+
if (outParams.length > 0) {
|
|
124
|
+
for (const outParam of outParams) {
|
|
125
|
+
injectLines.push(`${bodyInd}${outParam.name} = default;`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Inject lines after opening brace
|
|
130
|
+
if (injectLines.length > 0) {
|
|
131
|
+
const lines = bodyCode.split("\n");
|
|
132
|
+
if (lines.length > 1) {
|
|
133
|
+
lines.splice(1, 0, ...injectLines, "");
|
|
134
|
+
finalBodyCode = lines.join("\n");
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const signature = parts.join(" ");
|
|
139
|
+
const whereClause =
|
|
140
|
+
whereClauses.length > 0
|
|
141
|
+
? `\n${ind} ${whereClauses.join(`\n${ind} `)}`
|
|
142
|
+
: "";
|
|
143
|
+
const code = `${ind}${signature}${typeParamsStr}(${params[0]})${whereClause}\n${finalBodyCode}`;
|
|
144
|
+
|
|
145
|
+
// Return context - no usings tracking needed with global:: FQN approach
|
|
146
|
+
return [code, currentContext];
|
|
147
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Declaration emitters - Public API
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export { emitVariableDeclaration } from "./variables.js";
|
|
6
|
+
export { emitFunctionDeclaration } from "./functions.js";
|
|
7
|
+
export { emitClassDeclaration } from "./classes.js";
|
|
8
|
+
export { emitInterfaceDeclaration } from "./interfaces.js";
|
|
9
|
+
export { emitEnumDeclaration } from "./enums.js";
|
|
10
|
+
export { emitTypeAliasDeclaration } from "./type-aliases.js";
|