-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathJSBindingGenerator.Registry.cs
More file actions
165 lines (138 loc) · 6.33 KB
/
JSBindingGenerator.Registry.cs
File metadata and controls
165 lines (138 loc) · 6.33 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
namespace HakoJS.SourceGenerator;
public partial class JSBindingGenerator
{
private static void GenerateRegistry(SourceProductionContext context,
(ImmutableArray<ObjectModel> Objects,
ImmutableArray<ClassModel> Classes) data)
{
var sb = new StringBuilder();
sb.AppendLine("// <auto-generated/>");
sb.AppendLine("#nullable enable");
sb.AppendLine();
sb.AppendLine("using System;");
sb.AppendLine("using HakoJS.Host;");
sb.AppendLine("using HakoJS.VM;");
sb.AppendLine("using HakoJS.SourceGeneration;");
sb.AppendLine();
sb.AppendLine("namespace HakoJS.Extensions;");
sb.AppendLine();
sb.AppendLine("/// <summary>");
sb.AppendLine("/// Extension methods for registering generated type converters.");
sb.AppendLine("/// </summary>");
sb.AppendLine("internal static class GeneratedMarshalingExtensions");
sb.AppendLine("{");
// RegisterObjectConverters method
GenerateRegisterMethod(sb, data.Objects);
sb.AppendLine();
// ProjectToJSValue method
GenerateProjectorMethod(sb, data.Objects, data.Classes);
sb.AppendLine("}");
context.AddSource("GeneratedMarshalingExtensions.g.cs", SourceText.From(sb.ToString(), Encoding.UTF8));
}
private static void GenerateRegisterMethod(StringBuilder sb, ImmutableArray<ObjectModel> objects)
{
sb.AppendLine(" /// <summary>");
sb.AppendLine(" /// Registers all JSObject reifiers and the projector function defined in this assembly.");
sb.AppendLine(" /// Call this once during application startup.");
sb.AppendLine(" /// </summary>");
sb.AppendLine(" /// <param name=\"runtime\">The HakoRuntime instance.</param>");
sb.AppendLine(" public static void RegisterObjectConverters(this global::HakoJS.Host.HakoRuntime runtime)");
sb.AppendLine(" {");
if (objects.Length > 0)
{
sb.AppendLine(" // Register JSObject reifiers (JS → .NET)");
foreach (var obj in objects)
{
var fullTypeName = string.IsNullOrEmpty(obj.SourceNamespace)
? $"global::{obj.TypeName}"
: $"global::{obj.SourceNamespace}.{obj.TypeName}";
sb.AppendLine(
$" global::HakoJS.SourceGeneration.JSMarshalingRegistry.RegisterObjectReifier((uint){obj.GetTypeId()}, (realm, jsValue) => {fullTypeName}.FromJSValue(realm, jsValue));");
}
sb.AppendLine();
}
else
{
sb.AppendLine(" // No JSObject types to register");
sb.AppendLine();
}
sb.AppendLine(" // Register projector function (.NET → JS)");
sb.AppendLine(
" global::HakoJS.SourceGeneration.JSMarshalingRegistry.RegisterProjector(ProjectToJSValue);");
sb.AppendLine(" }");
}
private static void GenerateProjectorMethod(StringBuilder sb,
ImmutableArray<ObjectModel> objects,
ImmutableArray<ClassModel> classes)
{
sb.AppendLine(" /// <summary>");
sb.AppendLine(" /// Projects (converts) a .NET object to a JavaScript value.");
sb.AppendLine(" /// </summary>");
sb.AppendLine(" /// <param name=\"realm\">The realm to create the value in.</param>");
sb.AppendLine(" /// <param name=\"obj\">The .NET object to convert.</param>");
sb.AppendLine(
" /// <returns>A JSValue representing the object, or null if the type cannot be projected.</returns>");
sb.AppendLine(
" private static global::HakoJS.VM.JSValue? ProjectToJSValue(global::HakoJS.VM.Realm realm, object obj)");
sb.AppendLine(" {");
sb.AppendLine(" return obj switch");
sb.AppendLine(" {");
// Collect all types and sort them by inheritance depth (derived types first)
var allTypes = new List<(string FullTypeName, int Depth, INamedTypeSymbol? Symbol)>();
// Add JSObjects
foreach (var obj in objects)
{
var fullTypeName = string.IsNullOrEmpty(obj.SourceNamespace)
? $"global::{obj.TypeName}"
: $"global::{obj.SourceNamespace}.{obj.TypeName}";
var depth = GetInheritanceDepth(obj.TypeSymbol);
allTypes.Add((fullTypeName, depth, obj.TypeSymbol));
}
// Add JSClasses
foreach (var cls in classes)
{
var fullTypeName = string.IsNullOrEmpty(cls.SourceNamespace)
? $"global::{cls.ClassName}"
: $"global::{cls.SourceNamespace}.{cls.ClassName}";
var depth = GetInheritanceDepth(cls.TypeSymbol);
allTypes.Add((fullTypeName, depth, cls.TypeSymbol));
}
// Sort by depth descending (derived types first), then by name for stability
var sortedTypes = allTypes
.OrderByDescending(t => t.Depth)
.ThenBy(t => t.FullTypeName)
.ToList();
// Generate pattern matching cases
foreach (var (fullTypeName, _, symbol) in sortedTypes)
{
var simpleName = symbol?.Name ?? ExtractSimpleTypeName(fullTypeName);
var varName = EscapeIdentifierIfNeeded(char.ToLower(simpleName[0]) +
(simpleName.Length > 1 ? simpleName.Substring(1) : ""));
// Ensure unique variable names
if (varName == "obj") varName = "instance";
sb.AppendLine($" {fullTypeName} {varName} => {varName}.ToJSValue(realm),");
}
// Default case - return null
sb.AppendLine(" _ => null");
sb.AppendLine(" };");
sb.AppendLine(" }");
}
private static int GetInheritanceDepth(INamedTypeSymbol? symbol)
{
if (symbol == null) return 0;
var depth = 0;
var current = symbol.BaseType;
while (current != null && current.SpecialType != SpecialType.System_Object)
{
depth++;
current = current.BaseType;
}
return depth;
}
}