Skip to content

Commit 2a5a08a

Browse files
committed
feat: cs gen, fix related examples
1 parent b632b0d commit 2a5a08a

14 files changed

Lines changed: 1505 additions & 17 deletions

File tree

src/NativeCodeGen.CSharp/CSharpEmitter.cs

Lines changed: 636 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using NativeCodeGen.Core.Export;
2+
using NativeCodeGen.CSharp.Generation;
3+
4+
namespace NativeCodeGen.CSharp;
5+
6+
public class CSharpExporter : BaseExporter
7+
{
8+
private readonly CSharpGenerator _generator = new();
9+
10+
protected override ICodeGenerator Generator => _generator;
11+
}
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
using NativeCodeGen.Core.Generation;
2+
using NativeCodeGen.Core.Models;
3+
using NativeCodeGen.Core.TypeSystem;
4+
5+
namespace NativeCodeGen.CSharp;
6+
7+
/// <summary>
8+
/// C# type mapper implementation.
9+
/// </summary>
10+
public class CSharpTypeMapper : TypeMapperBase
11+
{
12+
public CSharpTypeMapper() : base(LanguageConfig.CSharp) { }
13+
14+
public override string MapType(TypeInfo type, bool isNullable = false, bool forReturn = false)
15+
{
16+
// Handle fixed-size arrays: int[3] -> int[]
17+
if (type.IsFixedArray)
18+
{
19+
var elementType = MapPrimitive(type.Name);
20+
return $"{elementType}[]";
21+
}
22+
23+
if (type.IsPointer)
24+
{
25+
if (type.Name == "char" || type.Name == "string")
26+
{
27+
return isNullable ? "string?" : "string";
28+
}
29+
if (type.Category == TypeCategory.Struct)
30+
{
31+
return type.Name;
32+
}
33+
return MapPrimitive(type.Name);
34+
}
35+
36+
var baseType = type.Category switch
37+
{
38+
TypeCategory.Void => Config.VoidType,
39+
TypeCategory.Primitive => MapPrimitive(type.Name),
40+
TypeCategory.Handle => Config.UseTypedHandles && TypeInfo.IsClassHandle(type.Name)
41+
? TypeInfo.NormalizeHandleName(type.Name)
42+
: "int",
43+
TypeCategory.Hash => forReturn ? "uint" : "uint", // C# uses uint for hashes
44+
TypeCategory.String => "string",
45+
TypeCategory.Vector2 => Config.Vector2Type,
46+
TypeCategory.Vector3 => Config.Vector3Type,
47+
TypeCategory.Vector4 => Config.Vector4Type,
48+
TypeCategory.Color => Config.ColorType,
49+
TypeCategory.Any => Config.AnyType,
50+
TypeCategory.Struct => type.Name,
51+
TypeCategory.Enum => type.Name,
52+
_ => Config.UseTypedHandles ? type.Name : Config.AnyType
53+
};
54+
55+
// Add nullable suffix for reference types if needed
56+
if (isNullable && type.Category == TypeCategory.Handle && TypeInfo.IsClassHandle(type.Name))
57+
{
58+
return baseType + "?";
59+
}
60+
61+
return baseType;
62+
}
63+
64+
protected new string MapPrimitive(string name) => name switch
65+
{
66+
"int" => "int",
67+
"uint" => "uint",
68+
"float" => "float",
69+
"double" => "double",
70+
"BOOL" or "bool" => "bool",
71+
"u8" => "byte",
72+
"u16" => "ushort",
73+
"u32" => "uint",
74+
"u64" => "ulong",
75+
"i8" => "sbyte",
76+
"i16" => "short",
77+
"i32" => "int",
78+
"i64" => "long",
79+
"f32" => "float",
80+
"f64" => "double",
81+
"Hash" => "uint",
82+
_ => "int"
83+
};
84+
85+
public override string GetResultMarker(TypeInfo type)
86+
{
87+
// C# doesn't use result markers - the generic type parameter handles this
88+
return "";
89+
}
90+
91+
public override bool NeedsResultMarker(TypeInfo type)
92+
{
93+
// C# doesn't need result markers
94+
return false;
95+
}
96+
97+
public override string GetInvokeReturnType(TypeInfo type)
98+
{
99+
return type.Category switch
100+
{
101+
TypeCategory.Void => "void",
102+
TypeCategory.Vector3 => "Vector3",
103+
TypeCategory.String => "string",
104+
TypeCategory.Primitive when type.IsBool => "bool",
105+
TypeCategory.Primitive when type.IsFloat => "float",
106+
TypeCategory.Hash => "uint",
107+
_ => "int"
108+
};
109+
}
110+
111+
public override string GetPointerPlaceholder(TypeInfo type)
112+
{
113+
// C# uses OutputArgument for output parameters
114+
return "new OutputArgument()";
115+
}
116+
117+
public override string GetInitializedPointerFormat(TypeInfo type)
118+
{
119+
// For @in params, we still use OutputArgument but initialize the value differently
120+
// The actual initialization happens in the method body
121+
return "new OutputArgument()";
122+
}
123+
124+
public override string GetOutputParamType(TypeInfo type)
125+
{
126+
if (type.IsVector3)
127+
return Config.Vector3Type;
128+
129+
if (type.Category == TypeCategory.Handle)
130+
{
131+
return Config.UseTypedHandles && TypeInfo.IsClassHandle(type.Name)
132+
? $"{TypeInfo.NormalizeHandleName(type.Name)}?"
133+
: "int";
134+
}
135+
136+
if (type.IsFloat) return "float";
137+
if (type.IsBool) return "bool";
138+
return "int";
139+
}
140+
141+
public override string BuildCombinedReturnType(TypeInfo returnType, IEnumerable<TypeInfo> outputParamTypes)
142+
{
143+
var outputTypes = outputParamTypes.ToList();
144+
145+
if (outputTypes.Count == 0)
146+
{
147+
if (returnType.Category == TypeCategory.Handle && Config.UseTypedHandles && TypeInfo.IsClassHandle(returnType.Name))
148+
{
149+
return MapType(returnType, forReturn: true) + "?";
150+
}
151+
return MapType(returnType, forReturn: true);
152+
}
153+
154+
// For C#, we use out parameters instead of tuples
155+
// The return type is just the native return type
156+
if (returnType.Category == TypeCategory.Void && outputTypes.Count == 1)
157+
{
158+
return GetOutputParamType(outputTypes[0]);
159+
}
160+
161+
// For multiple outputs or return + outputs, we use tuples
162+
var tupleTypes = new List<string>();
163+
164+
if (returnType.Category != TypeCategory.Void)
165+
{
166+
var mapped = MapType(returnType, forReturn: true);
167+
if (returnType.Category == TypeCategory.Handle && TypeInfo.IsClassHandle(returnType.Name))
168+
{
169+
mapped += "?";
170+
}
171+
tupleTypes.Add(mapped);
172+
}
173+
174+
foreach (var outputType in outputTypes)
175+
{
176+
tupleTypes.Add(GetOutputParamType(outputType));
177+
}
178+
179+
return $"({string.Join(", ", tupleTypes)})";
180+
}
181+
182+
public override DataViewAccessorInfo GetDataViewAccessorInfo(TypeInfo type)
183+
{
184+
// C# doesn't use DataView - this is for struct generation which is disabled
185+
var (langType, getMethod, setMethod) = GetDataViewAccessor(type);
186+
return new DataViewAccessorInfo(langType, getMethod, setMethod, "", type.IsBool);
187+
}
188+
189+
public override (string LanguageType, string GetMethod, string SetMethod) GetDataViewAccessor(TypeInfo type)
190+
{
191+
return type.Name switch
192+
{
193+
"u8" => ("byte", "ReadByte", "WriteByte"),
194+
"u16" => ("ushort", "ReadUInt16", "WriteUInt16"),
195+
"u32" or "uint" => ("uint", "ReadUInt32", "WriteUInt32"),
196+
"u64" => ("ulong", "ReadUInt64", "WriteUInt64"),
197+
"i8" => ("sbyte", "ReadSByte", "WriteSByte"),
198+
"i16" => ("short", "ReadInt16", "WriteInt16"),
199+
"i32" or "int" => ("int", "ReadInt32", "WriteInt32"),
200+
"i64" => ("long", "ReadInt64", "WriteInt64"),
201+
"f32" or "float" => ("float", "ReadSingle", "WriteSingle"),
202+
"f64" or "double" => ("double", "ReadDouble", "WriteDouble"),
203+
"bool" or "BOOL" => ("bool", "ReadByte", "WriteByte"),
204+
"Hash" => ("uint", "ReadUInt32", "WriteUInt32"),
205+
_ => ("int", "ReadInt32", "WriteInt32")
206+
};
207+
}
208+
}

0 commit comments

Comments
 (0)