Skip to content

Commit 98ae0cf

Browse files
committed
get class codegen to work ans compile
1 parent 8519f8d commit 98ae0cf

23 files changed

Lines changed: 2021 additions & 426 deletions

File tree

src/NativeCodeGen.Cli/Program.cs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using NativeCodeGen.Core.Parsing;
66
using NativeCodeGen.Core.Registry;
77
using NativeCodeGen.Core.Utilities;
8+
using NativeCodeGen.Core.Validation;
89
using NativeCodeGen.Lua;
910
using NativeCodeGen.TypeScript;
1011

@@ -300,6 +301,9 @@ static async Task Validate(string input, bool strict)
300301
var allNatives = new ConcurrentBag<NativeDefinition>();
301302
var processedCount = 0;
302303

304+
// Create type validator for type resolution and validation
305+
var typeValidator = new TypeValidator(enumRegistry, structRegistry);
306+
303307
// Process files in parallel (CPU-bound parsing)
304308
Parallel.ForEach(mdxFiles, file =>
305309
{
@@ -316,12 +320,10 @@ static async Task Validate(string input, bool strict)
316320

317321
if (result.Value != null)
318322
{
319-
// Resolve enum types for parameters and return type
320-
foreach (var param in result.Value.Parameters)
321-
{
322-
param.Type.ResolveEnumType(enumRegistry.GetBaseType);
323-
}
324-
result.Value.ReturnType.ResolveEnumType(enumRegistry.GetBaseType);
323+
// Validate all types in the native definition
324+
var validationErrors = typeValidator.ValidateNative(result.Value, file);
325+
foreach (var error in validationErrors)
326+
allErrors.Add(error);
325327

326328
allNatives.Add(result.Value);
327329
}

src/NativeCodeGen.Core/Generation/ArgumentBuilder.cs

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -34,18 +34,23 @@ public static string GetArgumentExpression(NativeParameter param, ITypeMapper ty
3434
if (param.IsInOut)
3535
{
3636
var format = typeMapper.GetInitializedPointerFormat(param.Type);
37-
// Handle types need to pass .handle (unless raw mode)
38-
var value = typeMapper.IsHandleType(param.Type) && !rawMode ? $"{param.Name}.handle" : param.Name;
37+
// Only class handles (with generated classes) have .handle property
38+
var value = typeMapper.IsHandleType(param.Type) && TypeInfo.IsClassHandle(param.Type.Name) && !rawMode
39+
? $"{param.Name}.handle"
40+
: param.Name;
3941
return string.Format(format, value);
4042
}
4143

42-
// Vector3 expansion (non-pointer Vector3) - components are floats
43-
if (typeMapper.IsVector3(param.Type) && !param.Type.IsPointer)
44+
// Vector expansion (non-pointer Vector2/Vector3/Vector4) - components are floats
45+
if (param.Type.IsVector && !param.Type.IsPointer)
4446
{
45-
if (useFloat)
46-
return $"{f}({param.Name}.x), {f}({param.Name}.y), {f}({param.Name}.z)";
47-
else
48-
return $"{param.Name}.x, {param.Name}.y, {param.Name}.z";
47+
return ExpandVector(param.Name, param.Type.VectorComponentCount, useFloat, f);
48+
}
49+
50+
// Color expansion (non-pointer Color) - components are integers (r, g, b, a)
51+
if (param.Type.Category == TypeCategory.Color && !param.Type.IsPointer)
52+
{
53+
return $"{param.Name}.r, {param.Name}.g, {param.Name}.b, {param.Name}.a";
4954
}
5055

5156
// Struct buffer
@@ -54,8 +59,9 @@ public static string GetArgumentExpression(NativeParameter param, ITypeMapper ty
5459
return $"{param.Name}.buffer";
5560
}
5661

57-
// Handle types - in raw mode, just pass the number directly
58-
if (typeMapper.IsHandleType(param.Type) && !rawMode)
62+
// Handle types - only class handles have a .handle property
63+
// Non-class handles (ScrHandle, Prompt) are just numbers
64+
if (typeMapper.IsHandleType(param.Type) && TypeInfo.IsClassHandle(param.Type.Name) && !rawMode)
5965
{
6066
return $"{param.Name}.handle";
6167
}
@@ -70,12 +76,9 @@ public static string GetArgumentExpression(NativeParameter param, ITypeMapper ty
7076
}
7177

7278
// Float type - wrap with f() to prevent bundler optimization
73-
if (param.Type.Name is "float" or "f32" or "f64" or "double")
79+
if (param.Type.IsFloat)
7480
{
75-
if (useFloat)
76-
return $"{f}({param.Name})";
77-
else
78-
return param.Name;
81+
return useFloat ? $"{f}({param.Name})" : param.Name;
7982
}
8083

8184
// Regular value
@@ -135,4 +138,15 @@ public static List<string> BuildInvokeArgsWithFirst(
135138

136139
return args;
137140
}
141+
142+
/// <summary>
143+
/// Expands a vector parameter to its component arguments (x, y, [z], [w]).
144+
/// </summary>
145+
private static string ExpandVector(string paramName, int componentCount, bool useFloat, string floatAlias)
146+
{
147+
var components = TypeInfo.VectorComponents[..componentCount];
148+
return useFloat
149+
? string.Join(", ", components.Select(c => $"{floatAlias}({paramName}.{c})"))
150+
: string.Join(", ", components.Select(c => $"{paramName}.{c}"));
151+
}
138152
}

src/NativeCodeGen.Core/Generation/DocBuilder.cs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,15 @@ public void Build()
8080
/// </summary>
8181
public class JsDocBuilder : DocBuilder
8282
{
83+
/// <summary>
84+
/// Escapes text that could break JSDoc comments (e.g., */ would close the comment prematurely).
85+
/// </summary>
86+
private static string EscapeJsDoc(string text)
87+
{
88+
// Replace */ with *\/ to prevent closing the JSDoc comment
89+
return text.Replace("*/", "*\\/");
90+
}
91+
8392
public override void Render(CodeBuilder cb)
8493
{
8594
if (IsEmpty) return;
@@ -88,7 +97,7 @@ public override void Render(CodeBuilder cb)
8897

8998
foreach (var line in DescriptionLines)
9099
{
91-
cb.AppendLine($" * {line}");
100+
cb.AppendLine($" * {EscapeJsDoc(line)}");
92101
}
93102

94103
if (DescriptionLines.Count > 0 && (Params.Count > 0 || Throws.Count > 0 || Return != null))
@@ -98,17 +107,17 @@ public override void Render(CodeBuilder cb)
98107

99108
foreach (var param in Params)
100109
{
101-
cb.AppendLine($" * @param {param.Name} {param.Description}");
110+
cb.AppendLine($" * @param {param.Name} {EscapeJsDoc(param.Description)}");
102111
}
103112

104113
foreach (var throws in Throws)
105114
{
106-
cb.AppendLine($" * @throws {{{throws.Type}}} {throws.Description}");
115+
cb.AppendLine($" * @throws {{{throws.Type}}} {EscapeJsDoc(throws.Description)}");
107116
}
108117

109118
if (Return != null)
110119
{
111-
var desc = string.IsNullOrEmpty(Return.Description) ? "" : $" {Return.Description}";
120+
var desc = string.IsNullOrEmpty(Return.Description) ? "" : $" {EscapeJsDoc(Return.Description)}";
112121
cb.AppendLine($" * @returns{desc}");
113122
}
114123

src/NativeCodeGen.Core/Generation/ILanguageEmitter.cs

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,20 @@ public interface ILanguageEmitter
5454
// === Class Generation ===
5555

5656
/// <summary>
57-
/// Emits imports for handle types used in a namespace class.
57+
/// Emits imports for handle types (class handles) used in a namespace class.
5858
/// </summary>
5959
void EmitHandleImports(CodeBuilder cb, IEnumerable<string> handleTypes);
6060

61+
/// <summary>
62+
/// Emits imports for non-class handle types (ScrHandle, Prompt, FireId, etc.).
63+
/// </summary>
64+
void EmitNonClassHandleImports(CodeBuilder cb, IEnumerable<string> handleTypes);
65+
66+
/// <summary>
67+
/// Emits imports for enum and struct types used in a class.
68+
/// </summary>
69+
void EmitTypeImports(CodeBuilder cb, IEnumerable<string> enumTypes, IEnumerable<string> structTypes);
70+
6171
/// <summary>
6272
/// Emits the start of a class definition.
6373
/// </summary>
@@ -66,7 +76,7 @@ public interface ILanguageEmitter
6676
/// <summary>
6777
/// Emits the end of a class definition.
6878
/// </summary>
69-
void EmitClassEnd(CodeBuilder cb, string className);
79+
void EmitClassEnd(CodeBuilder cb, string className, ClassKind kind);
7080

7181
/// <summary>
7282
/// Emits a constructor for a handle-based class.
@@ -81,12 +91,17 @@ public interface ILanguageEmitter
8191
/// <summary>
8292
/// Emits a constructor for a task class (takes entity).
8393
/// </summary>
84-
void EmitTaskConstructor(CodeBuilder cb, string className, string entityType);
94+
void EmitTaskConstructor(CodeBuilder cb, string className, string entityType, string? baseClass);
8595

8696
/// <summary>
8797
/// Emits a constructor for a model class (takes hash).
8898
/// </summary>
89-
void EmitModelConstructor(CodeBuilder cb, string className);
99+
void EmitModelConstructor(CodeBuilder cb, string className, string? baseClass);
100+
101+
/// <summary>
102+
/// Emits a constructor for a weapon class (takes ped).
103+
/// </summary>
104+
void EmitWeaponConstructor(CodeBuilder cb, string className);
90105

91106
// === Method Generation ===
92107

@@ -170,6 +185,8 @@ public enum ClassKind
170185
Task,
171186
/// <summary>Model class that wraps a hash</summary>
172187
Model,
188+
/// <summary>Weapon class that wraps a ped</summary>
189+
Weapon,
173190
/// <summary>Namespace utility class with static methods</summary>
174191
Namespace
175192
}

src/NativeCodeGen.Core/Generation/NativeClassifier.cs

Lines changed: 73 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Collections.Frozen;
12
using NativeCodeGen.Core.Models;
23
using NativeCodeGen.Core.Parsing;
34

@@ -9,50 +10,63 @@ namespace NativeCodeGen.Core.Generation;
910
/// </summary>
1011
public class NativeClassifier
1112
{
12-
public static readonly Dictionary<string, string?> HandleClassHierarchy = new()
13-
{
14-
["Entity"] = null,
15-
["Ped"] = "Entity",
16-
["Vehicle"] = "Entity",
17-
["Prop"] = "Entity",
18-
["Pickup"] = null,
19-
["Player"] = null,
20-
["Cam"] = null,
21-
["Blip"] = null,
22-
["Interior"] = null,
23-
["FireId"] = null,
24-
["AnimScene"] = null,
25-
["ItemSet"] = null,
26-
["PersChar"] = null,
27-
["PopZone"] = null,
28-
["PropSet"] = null,
29-
["Volume"] = null,
30-
["PedGroup"] = null,
31-
["BaseTask"] = null,
32-
["PedTask"] = "BaseTask",
33-
["VehicleTask"] = "BaseTask",
34-
["BaseModel"] = null,
35-
["PedModel"] = "BaseModel",
36-
["VehicleModel"] = "BaseModel",
37-
["WeaponModel"] = "BaseModel"
38-
};
39-
40-
public static readonly Dictionary<string, string> TypeToNamespace = new()
41-
{
42-
["Entity"] = "ENTITY",
43-
["Ped"] = "PED",
44-
["Vehicle"] = "VEHICLE",
45-
["Object"] = "OBJECT",
46-
["Pickup"] = "OBJECT",
47-
["Player"] = "PLAYER",
48-
["Cam"] = "CAM",
49-
["Blip"] = "HUD",
50-
["Interior"] = "INTERIOR"
51-
};
52-
53-
public static readonly HashSet<string> TaskClasses = new() { "BaseTask", "PedTask", "VehicleTask" };
54-
public static readonly HashSet<string> ModelClasses = new() { "BaseModel", "PedModel", "VehicleModel", "WeaponModel" };
55-
public static readonly HashSet<string> EntitySubclasses = new() { "Ped", "Vehicle", "Object", "Prop" };
13+
public static readonly FrozenDictionary<string, string?> HandleClassHierarchy =
14+
new Dictionary<string, string?>
15+
{
16+
["Entity"] = null,
17+
["Ped"] = "Entity",
18+
["Vehicle"] = "Entity",
19+
["Prop"] = "Entity",
20+
["Pickup"] = null,
21+
["Player"] = null,
22+
["Cam"] = null,
23+
["Blip"] = null,
24+
["Interior"] = null,
25+
["FireId"] = null,
26+
["AnimScene"] = null,
27+
["ItemSet"] = null,
28+
["PersChar"] = null,
29+
["PopZone"] = null,
30+
["PropSet"] = null,
31+
["Volume"] = null,
32+
["PedGroup"] = null,
33+
["BaseTask"] = null,
34+
["PedTask"] = "BaseTask",
35+
["VehicleTask"] = "BaseTask",
36+
["BaseModel"] = null,
37+
["PedModel"] = "BaseModel",
38+
["VehicleModel"] = "BaseModel",
39+
["WeaponModel"] = "BaseModel",
40+
["Weapon"] = null
41+
}.ToFrozenDictionary(StringComparer.Ordinal);
42+
43+
public static readonly FrozenDictionary<string, string> TypeToNamespace =
44+
new Dictionary<string, string>
45+
{
46+
["Entity"] = "ENTITY",
47+
["Ped"] = "PED",
48+
["Vehicle"] = "VEHICLE",
49+
["Object"] = "OBJECT",
50+
["Pickup"] = "OBJECT",
51+
["Player"] = "PLAYER",
52+
["Cam"] = "CAM",
53+
["Blip"] = "HUD",
54+
["Interior"] = "INTERIOR",
55+
["AnimScene"] = "ANIMSCENE",
56+
["ItemSet"] = "ITEMSET",
57+
["PersChar"] = "PERSCHAR",
58+
["PropSet"] = "PROPSET",
59+
["Volume"] = "VOLUME"
60+
}.ToFrozenDictionary(StringComparer.Ordinal);
61+
62+
public static readonly FrozenSet<string> TaskClasses =
63+
new HashSet<string> { "BaseTask", "PedTask", "VehicleTask" }.ToFrozenSet(StringComparer.Ordinal);
64+
65+
public static readonly FrozenSet<string> ModelClasses =
66+
new HashSet<string> { "BaseModel", "PedModel", "VehicleModel", "WeaponModel" }.ToFrozenSet(StringComparer.Ordinal);
67+
68+
public static readonly FrozenSet<string> EntitySubclasses =
69+
new HashSet<string> { "Ped", "Vehicle", "Object", "Prop" }.ToFrozenSet(StringComparer.Ordinal);
5670

5771
public ClassifiedNatives Classify(NativeDatabase db)
5872
{
@@ -65,27 +79,29 @@ public ClassifiedNatives Classify(NativeDatabase db)
6579
var targetClass = DetermineTargetClass(native);
6680
if (targetClass != null)
6781
{
68-
if (!result.HandleClasses.ContainsKey(targetClass))
82+
if (!result.HandleClasses.TryGetValue(targetClass, out var list))
6983
{
70-
result.HandleClasses[targetClass] = new List<NativeDefinition>();
84+
list = new List<NativeDefinition>();
85+
result.HandleClasses[targetClass] = list;
7186
}
72-
result.HandleClasses[targetClass].Add(native);
87+
list.Add(native);
7388
}
7489
else
7590
{
76-
if (!result.NamespaceClasses.ContainsKey(ns.Name))
91+
if (!result.NamespaceClasses.TryGetValue(ns.Name, out var list))
7792
{
78-
result.NamespaceClasses[ns.Name] = new List<NativeDefinition>();
93+
list = new List<NativeDefinition>();
94+
result.NamespaceClasses[ns.Name] = list;
7995
}
80-
result.NamespaceClasses[ns.Name].Add(native);
96+
list.Add(native);
8197
}
8298
}
8399
}
84100

85101
return result;
86102
}
87103

88-
private string? DetermineTargetClass(NativeDefinition native)
104+
private static string? DetermineTargetClass(NativeDefinition native)
89105
{
90106
if (native.Parameters.Count == 0)
91107
return null;
@@ -113,6 +129,11 @@ public ClassifiedNatives Classify(NativeDatabase db)
113129
};
114130
}
115131

132+
if (native.Namespace.Equals("WEAPON", StringComparison.OrdinalIgnoreCase) && handleType == "Ped")
133+
{
134+
return "Weapon";
135+
}
136+
116137
if (TypeToNamespace.TryGetValue(handleType, out var expectedNs))
117138
{
118139
if (native.Namespace.Equals(expectedNs, StringComparison.OrdinalIgnoreCase))
@@ -145,6 +166,7 @@ public ClassifiedNatives Classify(NativeDatabase db)
145166

146167
public static bool IsTaskClass(string className) => TaskClasses.Contains(className);
147168
public static bool IsModelClass(string className) => ModelClasses.Contains(className);
169+
public static bool IsWeaponClass(string className) => className == "Weapon";
148170

149171
public static string GetTaskEntityType(string className) => className switch
150172
{

0 commit comments

Comments
 (0)