@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
+ * Core emitter types
3
+ */
4
+
5
+ import type { MetadataFile } from "@tsonic/frontend/types/metadata.js";
6
+ import type { TypeBinding } from "@tsonic/frontend/types/bindings.js";
7
+
8
+ /**
9
+ * Module identity for import resolution
10
+ */
11
+ export type ModuleIdentity = {
12
+ readonly namespace: string;
13
+ readonly className: string;
14
+ readonly filePath: string;
15
+ };
16
+
17
+ /**
18
+ * Module map for resolving cross-file imports
19
+ */
20
+ export type ModuleMap = ReadonlyMap<string, ModuleIdentity>;
21
+
22
+ /**
23
+ * Export source: where an export actually comes from
24
+ * Used to resolve re-exports to their original source
25
+ */
26
+ export type ExportSource = {
27
+ /** Canonical file path of the actual source */
28
+ readonly sourceFile: string;
29
+ /** Name of the export in the source file */
30
+ readonly sourceName: string;
31
+ };
32
+
33
+ /**
34
+ * Map from (moduleFilePath, exportName) to actual source
35
+ * Key format: "moduleFilePath:exportName"
36
+ */
37
+ export type ExportMap = ReadonlyMap<string, ExportSource>;
38
+
39
+ /**
40
+ * Options for C# code generation
41
+ */
42
+ export type EmitterOptions = {
43
+ /** Root namespace for the application */
44
+ readonly rootNamespace: string;
45
+ /** Whether to include source map comments */
46
+ readonly includeSourceMaps?: boolean;
47
+ /** Indentation style (spaces) */
48
+ readonly indent?: number;
49
+ /** Maximum line length */
50
+ readonly maxLineLength?: number;
51
+ /** Include timestamp in generated files */
52
+ readonly includeTimestamp?: boolean;
53
+ /** Whether this module is an entry point (needs Main method) */
54
+ readonly isEntryPoint?: boolean;
55
+ /** Entry point file path (for batch emit) */
56
+ readonly entryPointPath?: string;
57
+ /** External library paths (contain .metadata and .bindings directories) */
58
+ readonly libraries?: readonly string[];
59
+ /** Runtime mode: "js" uses Tsonic.JSRuntime extensions, "dotnet" uses pure .NET */
60
+ readonly runtime?: "js" | "dotnet";
61
+ /** Module map for resolving cross-file imports (populated during batch emission) */
62
+ readonly moduleMap?: ModuleMap;
63
+ /** Export map for resolving re-exports to actual source (populated during batch emission) */
64
+ readonly exportMap?: ExportMap;
65
+ };
66
+
67
+ /**
68
+ * Import binding information for qualifying imported identifiers.
69
+ * Local module imports are always emitted as fully-qualified references
70
+ * to avoid C# name ambiguity.
71
+ *
72
+ * All CLR name resolution is done in the frontend - the emitter just uses
73
+ * the pre-computed clrName directly (no string parsing or type lookup).
74
+ */
75
+ export type ImportBinding = {
76
+ /** Import kind: type (interface/class), value (function/variable), or namespace (import *) */
77
+ readonly kind: "type" | "value" | "namespace";
78
+ /**
79
+ * Fully-qualified CLR name.
80
+ * - For types: the type's FQN (e.g., "MultiFileTypes.models.User")
81
+ * - For values/namespaces: the container class FQN (e.g., "MultiFileTypes.models.user")
82
+ */
83
+ readonly clrName: string;
84
+ /** For value imports: the member name inside the container (e.g., "createUser") */
85
+ readonly member?: string;
86
+ };
87
+
88
+ /**
89
+ * Context passed through emission process
90
+ */
91
+ export type EmitterContext = {
92
+ /** Current indentation level */
93
+ readonly indentLevel: number;
94
+ /** Options for emission */
95
+ readonly options: EmitterOptions;
96
+ /** Whether currently in static context */
97
+ readonly isStatic: boolean;
98
+ /** Whether currently in async context */
99
+ readonly isAsync: boolean;
100
+ /** Whether currently emitting an array index (omit .0 from integer literals) */
101
+ readonly isArrayIndex?: boolean;
102
+ /** Current class name (for constructor emission) */
103
+ readonly className?: string;
104
+ /** Whether the current class has a superclass (for virtual/override) */
105
+ readonly hasSuperClass?: boolean;
106
+ /** Whether the module has any inheritance (to decide virtual methods) */
107
+ readonly hasInheritance?: boolean;
108
+ /** Loaded .NET metadata files (for CLR type information) */
109
+ readonly metadata?: ReadonlyArray<MetadataFile>;
110
+ /** Registry mapping TypeScript emit names to type bindings */
111
+ readonly bindingsRegistry?: ReadonlyMap<string, TypeBinding>;
112
+ /** Map of local names to import binding info (for qualifying imported identifiers) */
113
+ readonly importBindings?: ReadonlyMap<string, ImportBinding>;
114
+ /** Set of variable names known to be int (from canonical for-loop counters) */
115
+ readonly intLoopVars?: ReadonlySet<string>;
116
+ };
117
+
118
+ /**
119
+ * Result of emitting C# code
120
+ */
121
+ export type EmitResult = {
122
+ /** The generated C# code */
123
+ readonly code: string;
124
+ /** Updated context after emission */
125
+ readonly context: EmitterContext;
126
+ };
127
+
128
+ /**
129
+ * Helper type for C# code fragments
130
+ */
131
+ export type CSharpFragment = {
132
+ /** The code fragment */
133
+ readonly text: string;
134
+ /** Whether this needs parentheses when used in expressions */
135
+ readonly needsParens?: boolean;
136
+ /** Precedence level for operator expressions */
137
+ readonly precedence?: number;
138
+ };
@@ -0,0 +1,42 @@
1
+ /**
2
+ * C# language-specific types
3
+ */
4
+
5
+ /**
6
+ * C# access modifiers
7
+ */
8
+ export type CSharpAccessModifier =
9
+ | "public"
10
+ | "private"
11
+ | "protected"
12
+ | "internal"
13
+ | "protected internal";
14
+
15
+ /**
16
+ * C# class modifiers
17
+ */
18
+ export type CSharpClassModifier = "static" | "abstract" | "sealed" | "partial";
19
+
20
+ /**
21
+ * C# method modifiers
22
+ */
23
+ export type CSharpMethodModifier =
24
+ | "static"
25
+ | "virtual"
26
+ | "override"
27
+ | "abstract"
28
+ | "async"
29
+ | "new"
30
+ | "sealed";
31
+
32
+ /**
33
+ * Represents a C# using statement
34
+ */
35
+ export type CSharpUsing = {
36
+ /** The namespace to import */
37
+ readonly namespace: string;
38
+ /** Whether this is a static import */
39
+ readonly isStatic?: boolean;
40
+ /** Alias for the import */
41
+ readonly alias?: string;
42
+ };
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Formatting helper functions
3
+ */
4
+
5
+ import { EmitterContext } from "./core.js";
6
+
7
+ /**
8
+ * Get indentation string for current level
9
+ */
10
+ export const getIndent = (context: EmitterContext): string => {
11
+ const spaces = context.options.indent ?? 4;
12
+ return " ".repeat(spaces * context.indentLevel);
13
+ };
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Fully Qualified Name (FQN) rendering utilities
3
+ *
4
+ * All types and members are emitted with global:: prefix to eliminate
5
+ * any ambiguity from C# using statements or namespace resolution.
6
+ */
7
+
8
+ /**
9
+ * Render a fully qualified type name with global:: prefix
10
+ * e.g., "System.Collections.Generic.List" → "global::System.Collections.Generic.List"
11
+ */
12
+ export const renderTypeFQN = (namespace: string, typeName: string): string => {
13
+ return `global::${namespace}.${typeName}`;
14
+ };
15
+
16
+ /**
17
+ * Render a fully qualified static member access with global:: prefix
18
+ * e.g., ("System", "Console", "WriteLine") → "global::System.Console.WriteLine"
19
+ */
20
+ export const renderMemberFQN = (
21
+ namespace: string,
22
+ typeName: string,
23
+ member: string
24
+ ): string => {
25
+ return `global::${namespace}.${typeName}.${member}`;
26
+ };
27
+
28
+ /**
29
+ * Render a simple namespace-qualified name with global:: prefix
30
+ * e.g., "System.Console" → "global::System.Console"
31
+ */
32
+ export const renderFQN = (qualifiedName: string): string => {
33
+ return `global::${qualifiedName}`;
34
+ };
35
+
36
+ /**
37
+ * Common fully qualified type names used throughout emission
38
+ */
39
+ export const FQN = {
40
+ // System types
41
+ Object: "global::System.Object",
42
+ String: "global::System.String",
43
+ Int32: "global::System.Int32",
44
+ Int64: "global::System.Int64",
45
+ Double: "global::System.Double",
46
+ Boolean: "global::System.Boolean",
47
+ Void: "void",
48
+
49
+ // System.Collections.Generic
50
+ List: (elementType: string) =>
51
+ `global::System.Collections.Generic.List<${elementType}>`,
52
+ Dictionary: (keyType: string, valueType: string) =>
53
+ `global::System.Collections.Generic.Dictionary<${keyType}, ${valueType}>`,
54
+ IEnumerable: (elementType: string) =>
55
+ `global::System.Collections.Generic.IEnumerable<${elementType}>`,
56
+
57
+ // System.Threading.Tasks
58
+ Task: "global::System.Threading.Tasks.Task",
59
+ TaskOf: (resultType: string) =>
60
+ `global::System.Threading.Tasks.Task<${resultType}>`,
61
+
62
+ // Tsonic.Runtime
63
+ TsonicRuntime: {
64
+ DynamicObject: "global::Tsonic.Runtime.DynamicObject",
65
+ Union: (types: string) => `global::Tsonic.Runtime.Union<${types}>`,
66
+ },
67
+
68
+ // Tsonic.JSRuntime
69
+ TsonicJSRuntime: {
70
+ Array: "global::Tsonic.JSRuntime.Array",
71
+ String: "global::Tsonic.JSRuntime.String",
72
+ Number: "global::Tsonic.JSRuntime.Number",
73
+ Math: "global::Tsonic.JSRuntime.Math",
74
+ Console: "global::Tsonic.JSRuntime.Console",
75
+ },
76
+
77
+ // System.Func delegates
78
+ Func: (typeArgs: string) => `global::System.Func<${typeArgs}>`,
79
+ Action: "global::System.Action",
80
+ ActionOf: (typeArgs: string) => `global::System.Action<${typeArgs}>`,
81
+ } as const;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Emitter types - Public API
3
+ */
4
+
5
+ export type {
6
+ EmitterOptions,
7
+ EmitterContext,
8
+ EmitResult,
9
+ CSharpFragment,
10
+ ImportBinding,
11
+ ModuleIdentity,
12
+ ModuleMap,
13
+ ExportSource,
14
+ ExportMap,
15
+ } from "./core.js";
16
+ export type {
17
+ CSharpAccessModifier,
18
+ CSharpClassModifier,
19
+ CSharpMethodModifier,
20
+ CSharpUsing,
21
+ } from "./csharp-types.js";
22
+ export {
23
+ createContext,
24
+ indent,
25
+ dedent,
26
+ withStatic,
27
+ withAsync,
28
+ withClassName,
29
+ } from "./context.js";
30
+ export { getIndent } from "./formatting.js";
31
+ export { renderTypeFQN, renderMemberFQN, renderFQN, FQN } from "./fqn.js";
package/src/emitter.ts ADDED
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Main C# Emitter - Public API
3
+ * Orchestrates code generation from IR
4
+ */
5
+
6
+ import { IrModule, Diagnostic } from "@tsonic/frontend";
7
+ import { EmitterOptions } from "./types.js";
8
+ import { emitModule } from "./core/module-emitter.js";
9
+ import { buildModuleMap } from "./core/module-map.js";
10
+
11
+ /**
12
+ * Result of batch emission
13
+ */
14
+ export type EmitResult =
15
+ | { readonly ok: true; readonly files: Map<string, string> }
16
+ | { readonly ok: false; readonly errors: readonly Diagnostic[] };
17
+
18
+ /**
19
+ * Emit a complete C# file from an IR module
20
+ */
21
+ export const emitCSharpFile = (
22
+ module: IrModule,
23
+ options: Partial<EmitterOptions> = {}
24
+ ): string => {
25
+ return emitModule(module, options);
26
+ };
27
+
28
+ /**
29
+ * Batch emit multiple IR modules.
30
+ * Returns an error if there are file name collisions after normalization.
31
+ */
32
+ export const emitCSharpFiles = (
33
+ modules: readonly IrModule[],
34
+ options: Partial<EmitterOptions> = {}
35
+ ): EmitResult => {
36
+ // Build module map for cross-file import resolution
37
+ const moduleMapResult = buildModuleMap(modules);
38
+
39
+ if (!moduleMapResult.ok) {
40
+ return { ok: false, errors: moduleMapResult.errors };
41
+ }
42
+
43
+ const moduleMap = moduleMapResult.value;
44
+ const exportMap = moduleMapResult.exportMap;
45
+ const results = new Map<string, string>();
46
+
47
+ // Find common root directory for all modules
48
+ const commonRoot = findCommonRoot(modules.map((m) => m.filePath));
49
+
50
+ for (const module of modules) {
51
+ // Create relative path from common root
52
+ const relativePath = module.filePath.startsWith(commonRoot)
53
+ ? module.filePath.slice(commonRoot.length).replace(/^\//, "")
54
+ : module.filePath;
55
+ const outputPath = relativePath.replace(/\.ts$/, ".cs");
56
+
57
+ // Mark this module as entry point if it matches the entry point path
58
+ const isEntryPoint = !!(
59
+ options.entryPointPath && module.filePath === options.entryPointPath
60
+ );
61
+ const moduleOptions = {
62
+ ...options,
63
+ isEntryPoint,
64
+ moduleMap, // Pass module map to each module emission
65
+ exportMap, // Pass export map for re-export resolution
66
+ };
67
+ const code = emitModule(module, moduleOptions);
68
+ results.set(outputPath, code);
69
+ }
70
+
71
+ return { ok: true, files: results };
72
+ };
73
+
74
+ /**
75
+ * Find the common root directory for a set of file paths
76
+ */
77
+ const findCommonRoot = (paths: readonly string[]): string => {
78
+ if (paths.length === 0) return "";
79
+ if (paths.length === 1) {
80
+ const firstPath = paths[0];
81
+ if (!firstPath) return "";
82
+ const lastSlash = firstPath.lastIndexOf("/");
83
+ return lastSlash >= 0 ? firstPath.slice(0, lastSlash + 1) : "";
84
+ }
85
+
86
+ // Split all paths into segments
87
+ const segments = paths.map((p) => p.split("/"));
88
+ const firstSegments = segments[0];
89
+ if (!firstSegments) return "";
90
+
91
+ const minLength = Math.min(...segments.map((s) => s.length));
92
+
93
+ let commonLength = 0;
94
+ for (let i = 0; i < minLength; i++) {
95
+ const segment = firstSegments[i];
96
+ if (segment && segments.every((s) => s[i] === segment)) {
97
+ commonLength = i + 1;
98
+ } else {
99
+ break;
100
+ }
101
+ }
102
+
103
+ return firstSegments.slice(0, commonLength).join("/") + "/";
104
+ };
105
+
106
+ // Re-export emitModule for backward compatibility
107
+ export { emitModule } from "./core/module-emitter.js";
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Expression Emitter - IR expressions to C# code
3
+ * Main dispatcher - delegates to specialized modules
4
+ */
5
+
6
+ import { IrExpression, IrType } from "@tsonic/frontend";
7
+ import { EmitterContext, CSharpFragment } from "./types.js";
8
+
9
+ // Import expression emitters from specialized modules
10
+ import { emitLiteral } from "./expressions/literals.js";
11
+ import { emitIdentifier } from "./expressions/identifiers.js";
12
+ import { emitArray, emitObject } from "./expressions/collections.js";
13
+ import { emitMemberAccess } from "./expressions/access.js";
14
+ import { emitCall, emitNew } from "./expressions/calls.js";
15
+ import {
16
+ emitBinary,
17
+ emitLogical,
18
+ emitUnary,
19
+ emitUpdate,
20
+ emitAssignment,
21
+ emitConditional,
22
+ } from "./expressions/operators.js";
23
+ import {
24
+ emitFunctionExpression,
25
+ emitArrowFunction,
26
+ } from "./expressions/functions.js";
27
+ import {
28
+ emitTemplateLiteral,
29
+ emitSpread,
30
+ emitAwait,
31
+ } from "./expressions/other.js";
32
+
33
+ /**
34
+ * Emit a C# expression from an IR expression
35
+ * @param expr The IR expression to emit
36
+ * @param context The emitter context
37
+ * @param expectedType Optional expected type for contextual typing (e.g., array element type inference)
38
+ */
39
+ export const emitExpression = (
40
+ expr: IrExpression,
41
+ context: EmitterContext,
42
+ expectedType?: IrType
43
+ ): [CSharpFragment, EmitterContext] => {
44
+ switch (expr.kind) {
45
+ case "literal":
46
+ return emitLiteral(expr, context);
47
+
48
+ case "identifier":
49
+ return emitIdentifier(expr, context);
50
+
51
+ case "array":
52
+ return emitArray(expr, context, expectedType);
53
+
54
+ case "object":
55
+ return emitObject(expr, context);
56
+
57
+ case "memberAccess":
58
+ return emitMemberAccess(expr, context);
59
+
60
+ case "call":
61
+ return emitCall(expr, context);
62
+
63
+ case "new":
64
+ return emitNew(expr, context);
65
+
66
+ case "binary":
67
+ return emitBinary(expr, context);
68
+
69
+ case "logical":
70
+ return emitLogical(expr, context);
71
+
72
+ case "unary":
73
+ return emitUnary(expr, context);
74
+
75
+ case "update":
76
+ return emitUpdate(expr, context);
77
+
78
+ case "assignment":
79
+ return emitAssignment(expr, context);
80
+
81
+ case "conditional":
82
+ return emitConditional(expr, context);
83
+
84
+ case "functionExpression":
85
+ return emitFunctionExpression(expr, context);
86
+
87
+ case "arrowFunction":
88
+ return emitArrowFunction(expr, context);
89
+
90
+ case "templateLiteral":
91
+ return emitTemplateLiteral(expr, context);
92
+
93
+ case "spread":
94
+ return emitSpread(expr, context);
95
+
96
+ case "await":
97
+ return emitAwait(expr, context);
98
+
99
+ case "this":
100
+ return [{ text: "this" }, context];
101
+
102
+ default:
103
+ // Fallback for unhandled expressions
104
+ return [{ text: "/* TODO: unhandled expression */" }, context];
105
+ }
106
+ };
107
+
108
+ // Re-export commonly used functions for backward compatibility
109
+ export {
110
+ emitTypeArguments,
111
+ generateSpecializedName,
112
+ } from "./expressions/identifiers.js";
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Member access expression emitters
3
+ */
4
+
5
+ import { IrExpression } from "@tsonic/frontend";
6
+ import { EmitterContext, CSharpFragment } from "../types.js";
7
+ import { emitExpression } from "../expression-emitter.js";
8
+ import {
9
+ isExplicitViewProperty,
10
+ extractInterfaceNameFromView,
11
+ } from "@tsonic/frontend/types/explicit-views.js";
12
+
13
+ /**
14
+ * Emit a member access expression (dot notation or bracket notation)
15
+ */
16
+ export const emitMemberAccess = (
17
+ expr: Extract<IrExpression, { kind: "memberAccess" }>,
18
+ context: EmitterContext
19
+ ): [CSharpFragment, EmitterContext] => {
20
+ // Check if this is a hierarchical member binding
21
+ if (expr.memberBinding) {
22
+ // Emit the full CLR type and member with global:: prefix
23
+ const { assembly, type, member } = expr.memberBinding;
24
+ const text = `global::${assembly}.${type}.${member}`;
25
+ return [{ text }, context];
26
+ }
27
+
28
+ const [objectFrag, newContext] = emitExpression(expr.object, context);
29
+
30
+ // Default runtime to "js" when not specified
31
+ const runtime = context.options.runtime ?? "js";
32
+
33
+ if (expr.isComputed) {
34
+ // Check if this is array index access - rewrite to static helper
35
+ const objectType = expr.object.inferredType;
36
+ const isArrayType = objectType?.kind === "arrayType";
37
+
38
+ // For TS arrays, use Tsonic.Runtime.Array.get() in BOTH modes
39
+ // This provides TS array semantics (auto-grow, sparse arrays, etc.)
40
+ // Note: Tsonic.Runtime is compiler support for lowered TS constructs (both modes)
41
+ // Tsonic.JSRuntime is JS built-ins like .map/.filter (js mode only)
42
+ if (isArrayType) {
43
+ const indexContext = { ...newContext, isArrayIndex: true };
44
+ const [propFrag, contextWithIndex] = emitExpression(
45
+ expr.property as IrExpression,
46
+ indexContext
47
+ );
48
+ const finalContext = { ...contextWithIndex, isArrayIndex: false };
49
+ const text = `global::Tsonic.Runtime.Array.get(${objectFrag.text}, ${propFrag.text})`;
50
+ return [{ text }, finalContext];
51
+ }
52
+
53
+ // CLR indexer access (non-TS-array types like List<T>, string, etc.)
54
+ // CLR indexers require integral indices.
55
+ // This applies in BOTH js and dotnet modes - CLR type requirements are mode-independent.
56
+ const indexContext = { ...newContext, isArrayIndex: true };
57
+ const [propFrag, contextWithIndex] = emitExpression(
58
+ expr.property as IrExpression,
59
+ indexContext
60
+ );
61
+ const finalContext = { ...contextWithIndex, isArrayIndex: false };
62
+ const accessor = expr.isOptional ? "?[" : "[";
63
+
64
+ // Check if the index is already known to be int (e.g., canonical loop counter)
65
+ const indexExpr = expr.property as IrExpression;
66
+ const isKnownInt =
67
+ indexExpr.kind === "identifier" &&
68
+ context.intLoopVars?.has(indexExpr.name);
69
+
70
+ // Skip cast if index is known int, otherwise cast for safety
71
+ const indexText = isKnownInt ? propFrag.text : `(int)(${propFrag.text})`;
72
+ const text = `${objectFrag.text}${accessor}${indexText}]`;
73
+ return [{ text }, finalContext];
74
+ }
75
+
76
+ // Property access
77
+ const prop = expr.property as string;
78
+ const objectType = expr.object.inferredType;
79
+ const isArrayType = objectType?.kind === "arrayType";
80
+
81
+ // In JS runtime mode, rewrite array.length → global::Tsonic.Runtime.Array.length(array)
82
+ // In dotnet mode, there is no JS emulation - users access .Count directly on List<T>
83
+ if (isArrayType && prop === "length" && runtime === "js") {
84
+ const text = `global::Tsonic.Runtime.Array.length(${objectFrag.text})`;
85
+ return [{ text }, newContext];
86
+ }
87
+
88
+ // Handle explicit interface view properties (As_IInterface)
89
+ if (isExplicitViewProperty(prop)) {
90
+ const interfaceName = extractInterfaceNameFromView(prop);
91
+ if (interfaceName) {
92
+ // Emit as C# interface cast: ((IInterface)obj)
93
+ // TODO: Need to look up full interface name from metadata
94
+ // For now, use the extracted short name
95
+ const text = `((${interfaceName})${objectFrag.text})`;
96
+ return [{ text }, newContext];
97
+ }
98
+ }
99
+
100
+ // Regular property access
101
+ const accessor = expr.isOptional ? "?." : ".";
102
+ const text = `${objectFrag.text}${accessor}${prop}`;
103
+ return [{ text }, newContext];
104
+ };