|
|
|
@ -0,0 +1,272 @@ |
|
|
|
|
using System; |
|
|
|
|
using System.Collections.Generic; |
|
|
|
|
using System.IO; |
|
|
|
|
using System.Linq; |
|
|
|
|
using System.Text; |
|
|
|
|
using ClangSharp.Interop; |
|
|
|
|
|
|
|
|
|
namespace gaemstone.ECS.BindGen; |
|
|
|
|
|
|
|
|
|
public unsafe static class Program |
|
|
|
|
{ |
|
|
|
|
private const string FlecsHeaderFileLocation = "../flecs/flecs.h"; |
|
|
|
|
private const string OutputFolderLocation = "../.."; |
|
|
|
|
|
|
|
|
|
public static void Main() |
|
|
|
|
{ |
|
|
|
|
var output = new StringBuilder(); |
|
|
|
|
output.AppendLine("""
|
|
|
|
|
// <auto-generated/> |
|
|
|
|
using System.Runtime.InteropServices; // For use with unions. |
|
|
|
|
|
|
|
|
|
#pragma warning disable CS8981 |
|
|
|
|
public static unsafe partial class flecs |
|
|
|
|
{ |
|
|
|
|
""");
|
|
|
|
|
|
|
|
|
|
var args = new string[] { |
|
|
|
|
// "-fparse-all-comments", |
|
|
|
|
"--comments-in-macros", |
|
|
|
|
"--comments", |
|
|
|
|
}; |
|
|
|
|
using var index = CXIndex.Create(); |
|
|
|
|
using var unit = CXTranslationUnit.CreateFromSourceFile(index, FlecsHeaderFileLocation, args, default); |
|
|
|
|
|
|
|
|
|
unit.Cursor.VisitChildren((cursor, _, _) => { |
|
|
|
|
switch (cursor) { |
|
|
|
|
case { Location.IsFromMainFile: false }: |
|
|
|
|
// Not from the file we're trying to parse. |
|
|
|
|
break; |
|
|
|
|
case { Kind: CXCursorKind.CXCursor_MacroInstantiation |
|
|
|
|
or CXCursorKind.CXCursor_InclusionDirective }: |
|
|
|
|
// Ignore these altogether. |
|
|
|
|
break; |
|
|
|
|
case { Kind: CXCursorKind.CXCursor_MacroDefinition }: |
|
|
|
|
WriteMacro(cursor, output); |
|
|
|
|
break; |
|
|
|
|
// case { Kind: CXCursorKind.CXCursor_TypedefDecl }: |
|
|
|
|
// WriteTypedef(cursor, output); |
|
|
|
|
// break; |
|
|
|
|
case { Kind: CXCursorKind.CXCursor_StructDecl }: |
|
|
|
|
WriteStruct(cursor, output); |
|
|
|
|
break; |
|
|
|
|
case { Kind: CXCursorKind.CXCursor_FunctionDecl }: |
|
|
|
|
WriteFunction(cursor, output); |
|
|
|
|
break; |
|
|
|
|
default: |
|
|
|
|
Console.WriteLine($"{cursor.Kind} {cursor}"); |
|
|
|
|
Console.WriteLine(GetSource(unit, cursor.Extent)); |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
return CXChildVisitResult.CXChildVisit_Continue; |
|
|
|
|
}, default); |
|
|
|
|
|
|
|
|
|
output.AppendLine("""
|
|
|
|
|
} |
|
|
|
|
""");
|
|
|
|
|
|
|
|
|
|
File.WriteAllText(Path.Combine(OutputFolderLocation, "flecs.g.cs"), output.ToString()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static void WriteMacro(CXCursor cursor, StringBuilder output, int indent = 1) |
|
|
|
|
{ |
|
|
|
|
if (cursor.IsMacroFunctionLike) return; |
|
|
|
|
var unit = cursor.TranslationUnit; |
|
|
|
|
var tokens = unit.Tokenize(cursor.Extent).ToArray(); |
|
|
|
|
if (tokens.Length < 2) return; // No value. |
|
|
|
|
|
|
|
|
|
var name = cursor.Spelling.ToString(); |
|
|
|
|
if (name is "NULL" or "ECS_VECTOR_T_SIZE" |
|
|
|
|
or "EcsLastInternalComponentId" or "ECS_FUNC_NAME_BACK") return; |
|
|
|
|
|
|
|
|
|
var type = "uint"; |
|
|
|
|
var value = GetSource(unit, Range(unit, tokens[1], tokens[^1])); |
|
|
|
|
|
|
|
|
|
if (value is not [ '(', .., ')']) return; |
|
|
|
|
if (value.Contains('\\')) value = value.Replace("\\\n", "").Replace(" ", ""); |
|
|
|
|
|
|
|
|
|
if (value.Contains("ull")) { value = value.Replace("ull", "ul"); type = "ulong"; } |
|
|
|
|
// if (value.Contains("|") || value.Contains("<<")) type = "ulong"; |
|
|
|
|
|
|
|
|
|
if (name == "ECS_MAX_COMPONENT_ID") value.Replace("(uint32_t)", "(uint)"); |
|
|
|
|
|
|
|
|
|
WriteComment(cursor, output, indent); |
|
|
|
|
output.Append('\t', indent).AppendLine($"public const {type} {name} = {value};"); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private static void WriteStruct(CXCursor cursor, StringBuilder output, |
|
|
|
|
int indent = 1, string? name = null, bool noComment = false) |
|
|
|
|
{ |
|
|
|
|
// Skip forward declarations, unless they're not defined at all. |
|
|
|
|
if (!cursor.IsDefinition && !cursor.Definition.IsNull) return; |
|
|
|
|
|
|
|
|
|
if (!noComment) WriteComment(cursor, output, indent); |
|
|
|
|
|
|
|
|
|
if (cursor.Kind == CXCursorKind.CXCursor_UnionDecl) |
|
|
|
|
output.Append('\t', indent).AppendLine("[StructLayout(LayoutKind.Explicit)]"); |
|
|
|
|
|
|
|
|
|
name ??= cursor.Type.Declaration.ToString(); |
|
|
|
|
output.Append('\t', indent).Append("public struct ").Append(name); |
|
|
|
|
if (name == "") throw new Exception(); |
|
|
|
|
|
|
|
|
|
var hasFields = false; // For creating a shorthand struct definition that doesn't take up 3 lines. |
|
|
|
|
cursor.VisitChildren((field, _, _) => { |
|
|
|
|
switch (field) { |
|
|
|
|
case { IsAnonymous: true }: |
|
|
|
|
// Anonymous struct and union declarations |
|
|
|
|
// will be handled when field is written. |
|
|
|
|
break; |
|
|
|
|
case { Kind: CXCursorKind.CXCursor_FieldDecl |
|
|
|
|
or CXCursorKind.CXCursor_StructDecl }: |
|
|
|
|
if (!hasFields) { |
|
|
|
|
output.AppendLine(); |
|
|
|
|
output.Append('\t', indent).AppendLine("{"); |
|
|
|
|
hasFields = true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (cursor.Kind == CXCursorKind.CXCursor_UnionDecl) |
|
|
|
|
output.Append('\t', indent + 1).AppendLine("[FieldOffset(0)]"); |
|
|
|
|
|
|
|
|
|
WriteComment(field, output, indent + 1); |
|
|
|
|
output.Append('\t', indent + 1).Append("public "); |
|
|
|
|
var name = field.DisplayName; |
|
|
|
|
if (field.Type.Declaration.IsAnonymous) { |
|
|
|
|
output.AppendLine($"_{name} {name};"); |
|
|
|
|
WriteStruct(field.Type.Declaration, output, indent + 1, |
|
|
|
|
name: $"_{name}", noComment: true); |
|
|
|
|
} else if (field.Type.kind == CXTypeKind.CXType_ConstantArray) { |
|
|
|
|
output.AppendLine($"fixed {field.Type.ArrayElementType} {name}[{field.Type.ArraySize}];"); |
|
|
|
|
} else { |
|
|
|
|
WriteType(field.Type, output); |
|
|
|
|
output.AppendLine($" {name};"); |
|
|
|
|
} |
|
|
|
|
break; |
|
|
|
|
default: |
|
|
|
|
throw new NotSupportedException(); |
|
|
|
|
} |
|
|
|
|
return CXChildVisitResult.CXChildVisit_Continue; |
|
|
|
|
}, default); |
|
|
|
|
if (!hasFields) output.AppendLine(" { }"); |
|
|
|
|
else output.Append('\t', indent).AppendLine("}"); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private static void WriteFunction(CXCursor cursor, StringBuilder output, int indent = 1) |
|
|
|
|
{ |
|
|
|
|
WriteComment(cursor, output, indent); |
|
|
|
|
|
|
|
|
|
output.Append('\t', indent).Append($"public static "); |
|
|
|
|
WriteType(cursor.ReturnType, output); |
|
|
|
|
output.Append($" {cursor.Spelling}("); |
|
|
|
|
|
|
|
|
|
for (var i = 0; i < cursor.NumArguments; i++) { |
|
|
|
|
var arg = cursor.GetArgument((uint)i); |
|
|
|
|
if (i > 0) output.Append(", "); |
|
|
|
|
WriteType(arg.Type, output); |
|
|
|
|
output.Append($" {arg.DisplayName}"); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
output.AppendLine(");"); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private static void WriteComment(CXCursor cursor, StringBuilder output, int indent) |
|
|
|
|
{ |
|
|
|
|
var comment = cursor.ParsedComment; |
|
|
|
|
if (comment.Kind == CXCommentKind.CXComment_Null) return; |
|
|
|
|
Ensure(comment, CXCommentKind.CXComment_FullComment); |
|
|
|
|
|
|
|
|
|
foreach (var child in Children(comment)) |
|
|
|
|
switch (child.Kind) { |
|
|
|
|
case CXCommentKind.CXComment_Paragraph: |
|
|
|
|
if (GetText(child) is string text) |
|
|
|
|
output.Append('\t', indent).AppendLine($"/// <summary> {text} </summary>"); |
|
|
|
|
break; |
|
|
|
|
case CXCommentKind.CXComment_ParamCommand: |
|
|
|
|
var paramName = child.ParamCommandComment_ParamName; |
|
|
|
|
var paramComment = GetText(Children(child).Single()); |
|
|
|
|
output.Append('\t', indent).AppendLine($$"""/// <param name="{{ paramName }}"> {{ paramComment }} </param>"""); |
|
|
|
|
break; |
|
|
|
|
// case CXCommentKind.CXComment_BlockCommand: |
|
|
|
|
// break; |
|
|
|
|
default: |
|
|
|
|
output.Append('\t', indent).AppendLine($"//// {child.Kind}"); |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static CXComment Ensure(CXComment comment, CXCommentKind expected) |
|
|
|
|
{ if (comment.Kind != expected) throw new NotSupportedException(); return comment; } |
|
|
|
|
|
|
|
|
|
static IEnumerable<CXComment> Children(CXComment parent) |
|
|
|
|
=> Enumerable.Range(0, (int)parent.NumChildren).Select(i => parent.GetChild((uint)i)); |
|
|
|
|
|
|
|
|
|
static string? GetText(CXComment paragraph) |
|
|
|
|
{ |
|
|
|
|
Ensure(paragraph, CXCommentKind.CXComment_Paragraph); |
|
|
|
|
var parts = Children(paragraph) |
|
|
|
|
.Select(c => Ensure(c, CXCommentKind.CXComment_Text)) |
|
|
|
|
.Select(c => c.TextComment_Text.ToString().Trim()) |
|
|
|
|
.Where (s => s != string.Empty) |
|
|
|
|
.Select(s => s.Replace("<", "<").Replace(">", ">")) |
|
|
|
|
.ToArray(); |
|
|
|
|
return (parts.Length > 0) ? string.Join(" ", parts) : null; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static void WriteType(CXType type, StringBuilder output) |
|
|
|
|
{ |
|
|
|
|
// FIXME: Handle reserved keywords. |
|
|
|
|
string name; |
|
|
|
|
switch (type) { |
|
|
|
|
case { kind: CXTypeKind.CXType_Pointer }: |
|
|
|
|
WriteType(type.PointeeType, output); |
|
|
|
|
output.Append('*'); |
|
|
|
|
break; |
|
|
|
|
|
|
|
|
|
case { kind: CXTypeKind.CXType_VariableArray |
|
|
|
|
or CXTypeKind.CXType_IncompleteArray }: |
|
|
|
|
WriteType(type.ArrayElementType, output); |
|
|
|
|
output.Append("[]"); |
|
|
|
|
break; |
|
|
|
|
|
|
|
|
|
case { kind: CXTypeKind.CXType_Record }: |
|
|
|
|
case { kind: CXTypeKind.CXType_Elaborated }: |
|
|
|
|
name = type.Declaration.Spelling.ToString(); |
|
|
|
|
if (name == "") throw new Exception(); |
|
|
|
|
output.Append(name); |
|
|
|
|
break; |
|
|
|
|
|
|
|
|
|
case { kind: CXTypeKind.CXType_FunctionProto }: |
|
|
|
|
// TODO: Handle this somehow? |
|
|
|
|
name = type.Spelling.ToString(); |
|
|
|
|
output.Append(name); |
|
|
|
|
break; |
|
|
|
|
|
|
|
|
|
default: |
|
|
|
|
name = type.Spelling.ToString(); |
|
|
|
|
if (name == "") throw new Exception(); |
|
|
|
|
if (type.IsConstQualified) { |
|
|
|
|
// TODO: Do something with const-ness? |
|
|
|
|
if (!name.StartsWith("const ")) throw new Exception(); |
|
|
|
|
name = name[("const ".Length)..]; |
|
|
|
|
} |
|
|
|
|
if (name.Contains(' ')) throw new Exception(); |
|
|
|
|
output.Append(name); |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static CXSourceRange Range(CXTranslationUnit unit, CXToken start, CXToken end) |
|
|
|
|
=> CXSourceRange.Create(start.GetExtent(unit).Start, end.GetExtent(unit).End); |
|
|
|
|
|
|
|
|
|
private static string GetSource(CXTranslationUnit unit, CXSourceRange range) |
|
|
|
|
{ |
|
|
|
|
range.Start.GetFileLocation(out var startFile, out _, out _, out var start); |
|
|
|
|
range.End .GetFileLocation(out var endFile , out _, out _, out var end); |
|
|
|
|
if (startFile != endFile) return string.Empty; |
|
|
|
|
return Encoding.UTF8.GetString( |
|
|
|
|
unit.GetFileContents(startFile, out _) |
|
|
|
|
.Slice((int)start, (int)(end - start))); |
|
|
|
|
} |
|
|
|
|
} |