From 806f27381a8e49bb78af1f1cbbfb2fc1f72775c7 Mon Sep 17 00:00:00 2001 From: Rune Date: Sun, 22 Feb 2026 15:25:43 +0100 Subject: [PATCH] c part ready to go --- .gitignore | 4 +- .gitmodules | 3 + BeefProj.toml | 4 +- BeefSpace.toml | 3 +- BeefSpace_Lock.toml | 6 ++ Clang-C | 1 + CxxBuilder/.gitignore | 6 ++ CxxBuilder/BeefProj.toml | 29 ++++++ CxxBuilder/BeefSpace.toml | 5 + CxxBuilder/src/FileMatcher.bf | 137 ++++++++++++++++++++++++++ CxxBuilder/src/Program.bf | 134 ++++++++++++++++++++++++++ Setup/.gitignore | 3 + Setup/BeefProj.toml | 5 + Setup/BeefSpace.toml | 5 + Setup/src/Program.bf | 27 ++++++ src/ClangC.bf | 171 +++++++++++++++++++++++++++++++++ src/Generator.bf | 174 ++++++++++++++++++++++++++-------- 17 files changed, 674 insertions(+), 43 deletions(-) create mode 100644 .gitmodules create mode 100644 BeefSpace_Lock.toml create mode 160000 Clang-C create mode 100644 CxxBuilder/.gitignore create mode 100644 CxxBuilder/BeefProj.toml create mode 100644 CxxBuilder/BeefSpace.toml create mode 100644 CxxBuilder/src/FileMatcher.bf create mode 100644 CxxBuilder/src/Program.bf create mode 100644 Setup/.gitignore create mode 100644 Setup/BeefProj.toml create mode 100644 Setup/BeefSpace.toml create mode 100644 Setup/src/Program.bf create mode 100644 src/ClangC.bf diff --git a/.gitignore b/.gitignore index 1a97e19..2cfe158 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ build recovery -BeefSpace_User.toml \ No newline at end of file +BeefSpace_User.toml + +clang-c.h diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..53bb37e --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "Clang-C"] + path = Clang-C + url = https://git.unicon-gmbh.de/BeefBindings/Clang-C.git diff --git a/BeefProj.toml b/BeefProj.toml index 414096c..4d5b411 100644 --- a/BeefProj.toml +++ b/BeefProj.toml @@ -1,6 +1,6 @@ FileVersion = 1 +Dependencies = {corlib = "*", "Clang-C.git" = {Git = "https://git.unicon-gmbh.de/BeefBindings/Clang-C.git"}} [Project] Name = "Cpp2Beef" -TargetType = "BeefLib" -StartupObject = "Cpp2Beef.Program" +StartupObject = "ClangC.Setup.Program" diff --git a/BeefSpace.toml b/BeefSpace.toml index 8cd4fbc..62a01b2 100644 --- a/BeefSpace.toml +++ b/BeefSpace.toml @@ -1,5 +1,6 @@ FileVersion = 1 -Projects = {Cpp2Beef = {Path = "."}} +Projects = {Cpp2Beef = {Path = "."}, "Clang-C.git" = {Git = "https://git.unicon-gmbh.de/BeefBindings/Clang-C.git"}} +Unlocked = ["Clang-C.git"] [Workspace] StartupProject = "Cpp2Beef" diff --git a/BeefSpace_Lock.toml b/BeefSpace_Lock.toml new file mode 100644 index 0000000..b772165 --- /dev/null +++ b/BeefSpace_Lock.toml @@ -0,0 +1,6 @@ +FileVersion = 1 + +[Locks."Clang-C.git".Git] +URL = "https://git.unicon-gmbh.de/BeefBindings/Clang-C.git" +Tag = "" +Hash = "a64e7a0cac26e4844385b87e595e27e95b46e5a0" diff --git a/Clang-C b/Clang-C new file mode 160000 index 0000000..86db016 --- /dev/null +++ b/Clang-C @@ -0,0 +1 @@ +Subproject commit 86db0167f15d08a63a91a19e46f983a5511bac1a diff --git a/CxxBuilder/.gitignore b/CxxBuilder/.gitignore new file mode 100644 index 0000000..e39ba07 --- /dev/null +++ b/CxxBuilder/.gitignore @@ -0,0 +1,6 @@ +build +recovery +test +BeefSpace_User.toml + +dist/* diff --git a/CxxBuilder/BeefProj.toml b/CxxBuilder/BeefProj.toml new file mode 100644 index 0000000..ac03748 --- /dev/null +++ b/CxxBuilder/BeefProj.toml @@ -0,0 +1,29 @@ +FileVersion = 1 + +[Project] +Name = "CxxBuilder" +StartupObject = "CxxBuilder.Program" + +[Configs.Debug.Win32] +TargetDirectory = "$(ProjectDir)/dist" + +[Configs.Debug.Win64] +TargetDirectory = "$(ProjectDir)/dist" + +[Configs.Release.Win32] +TargetDirectory = "$(ProjectDir)/dist" + +[Configs.Release.Win64] +TargetDirectory = "$(ProjectDir)/dist" + +[Configs.Paranoid.Win32] +TargetDirectory = "$(ProjectDir)/dist" + +[Configs.Paranoid.Win64] +TargetDirectory = "$(ProjectDir)/dist" + +[Configs.Test.Win32] +TargetDirectory = "$(ProjectDir)/dist" + +[Configs.Test.Win64] +TargetDirectory = "$(ProjectDir)/dist" diff --git a/CxxBuilder/BeefSpace.toml b/CxxBuilder/BeefSpace.toml new file mode 100644 index 0000000..0a4bbc5 --- /dev/null +++ b/CxxBuilder/BeefSpace.toml @@ -0,0 +1,5 @@ +FileVersion = 1 +Projects = {CxxBuilder = {Path = "."}} + +[Workspace] +StartupProject = "CxxBuilder" diff --git a/CxxBuilder/src/FileMatcher.bf b/CxxBuilder/src/FileMatcher.bf new file mode 100644 index 0000000..58ef8b9 --- /dev/null +++ b/CxxBuilder/src/FileMatcher.bf @@ -0,0 +1,137 @@ +using System; +using System.IO; +using System.Collections; +using System.Diagnostics; + +namespace CxxBuilder; + +static class FileMatcher +{ + public static void HandleMatches(Span patterns, StringView directory, delegate void(StringView match) callback) + { + Runtime.Assert(Directory.Exists(directory), scope $"No such directory {directory}"); + void Dir(StringView dir) + { + for (let element in Directory.Enumerate(dir)) + { + let path = element.GetFilePath(..scope .()); + path.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); + if (element.IsDirectory) + { + Dir(path); + continue; + } + let relpath = Path.GetRelativePath(path, directory, ..scope .()); + for (let pattern in patterns) + if (IsMatch(pattern, relpath)) + { + callback(relpath); + break; + } + } + } + Dir(directory); + } + + public static bool IsMatch(StringView pattern, StringView path) + { + var pattern, path; + + bool Matches(StringView pattern, char8 c) + { + if (pattern.IsEmpty) return false; + + switch (pattern[0]) + { + case '*': + if (pattern.Length > 1 && pattern[1] == '*') + return true; + return !Path.IsDirectorySeparatorChar(c); + case '?': return true; + case '\\': + Runtime.Assert(pattern.Length > 1, "Expected escape sequence"); + return c == pattern[1]; + case '/': return Path.IsDirectorySeparatorChar(c); + case '[': + int i = 0; + char8 Next() + { + if (pattern.Length <= ++i) + Runtime.FatalError("Character class not closed"); + return pattern[i]; + } + bool negated = false; + char8 current; + switch (Next()) + { + case '^', '!': negated = true; fallthrough; + case '\\': current = Next(); + case ']': return false; + default: current = _; + } + while (true) + { + if (c == current) return !negated; + switch (Next()) + { + case ']': return negated; + case '-': + let end = Next(); + if (c >= current && c <= end) + return !negated; + current = Next(); + case '\\': current = Next(); + default: current = _; + } + } + default: + return c == _; + } + } + + reduce: while (true) + { + if (path.IsEmpty) return true; + if (pattern.IsEmpty) return false; + if (!Matches(pattern, path[0])) return false; + switch (pattern[0]) + { + case '*': + StringView lazy; + if (pattern.Length > 1) + { + lazy = pattern; + lazy.RemoveFromStart(pattern[1] == '*' ? 2 : 1); + } + else lazy = .(); + defer { pattern = lazy; } + while (true) + { + if (IsMatch(lazy, path)) continue reduce; + if (!Matches(pattern, path[0])) continue reduce; + path.RemoveFromStart(1); + if (path.IsEmpty) return lazy.IsEmpty; + } + case '[': + bool escaped = false; + while (true) + { + let c = pattern[0]; + pattern.RemoveFromStart(1); + if (c == ']' && !escaped) + { + path.RemoveFromStart(1); + continue reduce; + } + escaped = c == '\\'; + } + case '\\': + pattern.RemoveFromStart(2); + path.RemoveFromStart(1); + default: + pattern.RemoveFromStart(1); + path.RemoveFromStart(1); + } + } + } +} \ No newline at end of file diff --git a/CxxBuilder/src/Program.bf b/CxxBuilder/src/Program.bf new file mode 100644 index 0000000..4dcdce9 --- /dev/null +++ b/CxxBuilder/src/Program.bf @@ -0,0 +1,134 @@ +using System; +using System.IO; +using System.Collections; +using System.Diagnostics; + +namespace CxxBuilder; + +static class Program +{ + [CLink] static extern int32 system(char8*); + + public static int Main(String[] args) + { + mixin PrintUsage() + { + Console.WriteLine(""" + Simple and fast build tool using clang and ninja. + + Usage: $(Var CxxBuilderExe) -- src=<> target=<> builddir=<> output=<> [cflags=<>] + Example: $(Var CxxBuilderExe) **.c vk_*.cpp -- "src=$(ProjectDir)/MyLib/src" target=$(TargetTriple) "builddir=$(BuildDir)" output=MyLib cflags=-Isome/dir + + src - The source dir, all patterns are rooted here + target - the llvm target triple passed to clang + builddir - contains build artifacts and ninja files + output - the name of the output object archive $builddir/$output.lib on windows and $builddir/$output.a elsewhere + cflags - additional flags passed to clang + """); + return 1; + } + + StringView src = null, target = null, builddir = null, output = null, cflags = null; + var iter = args.GetEnumerator(); + skipPatterns: do + { + for (let arg in iter) + if (arg == "--") break skipPatterns; + PrintUsage!(); + } + for (let arg in iter) + { + var parts = arg.Split('=', 2); + StringView key = parts.GetNext().Value; + StringView value; + switch (parts.GetNext()) + { + case .Err: PrintUsage!(); + case .Ok(out value): + } + key.Trim(); + value.Trim(); + switch (key) + { + case "src": Runtime.Assert(src.IsNull, "Duplicate var 'src'"); src = value; + case "target": Runtime.Assert(target.IsNull, "Duplicate var 'target'"); target = value; + case "builddir": Runtime.Assert(builddir.IsNull, "Duplicate var 'builddir'"); builddir = value; + case "output": Runtime.Assert(output.IsNull, "Duplicate var 'output'"); output = value; + case "cflags": Runtime.Assert(cflags.IsNull, "Duplicate var 'cflags'"); cflags = value; + default: PrintUsage!(); + } + } + Runtime.Assert(!src.IsNull, "Missing var 'src'"); + Runtime.Assert(!target.IsNull, "Missing var 'target'"); + Runtime.Assert(!builddir.IsNull, "Missing var 'builddir'"); + Runtime.Assert(!builddir.IsNull, "Missing var 'output'"); + + { + StreamWriter writer = scope .()..Create(scope $"{builddir}/build.ninja"); + { + String buffer = scope .(1024); + let currentDir = Directory.GetCurrentDirectory(..scope .(128)); + void WriteVarPath(StringView key, StringView value) + { + buffer.Append(key); + buffer.Append(" = "); + Path.GetAbsolutePath(value, currentDir, buffer); + buffer.Append('\n'); + } + WriteVarPath("src", src); + WriteVarPath("builddir", builddir); + buffer.AppendF($""" + target = {target} + cflags = {cflags} + """); + writer.Write(buffer); + } + writer.Write(""" + \n + cc = clang + ar = ar + + rule cc + command = $cc $cflags -target $target -MD -MF $out.d -c -o $out $in + deps = gcc + depfile = $out.d + description = Building $in + + rule ar + command = $ar crs $out $in + description = Creating static lib $out + + + """); + String ar = scope .(1024); + ar.AppendF($"\nbuild $builddir/{output}.{target.Contains("windows") ? "lib" : "a"}: ar"); + + int pcount = 0; + for (let arg in args) + { + if (arg == "--") break; + pcount++; + } + StringView[] patterns = scope .[pcount]; + for (var pattern in ref patterns) + { + pattern = args[@pattern]; + } + + void HandleMatch(StringView match) + { + writer.Write($"build $builddir/{match}.o: cc $src/{match}\n"); + ar.Append(" $builddir/", match, ".o"); + } + let abssrc = Path.GetAbsolutePath(src, Directory.GetCurrentDirectory(..scope .(128)), ..scope .(128)); + FileMatcher.HandleMatches(patterns, abssrc, scope => HandleMatch); + + writer.WriteLine(ar); + } + + let ret = system(scope $"ninja -C {builddir}"); + if (ret != 0) + Console.WriteLine($"Failed to build {output}"); + return ret; + } +} \ No newline at end of file diff --git a/Setup/.gitignore b/Setup/.gitignore new file mode 100644 index 0000000..8043483 --- /dev/null +++ b/Setup/.gitignore @@ -0,0 +1,3 @@ +build +recovery +BeefSapce_user.toml diff --git a/Setup/BeefProj.toml b/Setup/BeefProj.toml new file mode 100644 index 0000000..4bb5965 --- /dev/null +++ b/Setup/BeefProj.toml @@ -0,0 +1,5 @@ +FileVersion = 1 + +[Project] +Name = "Cpp2Beef.Setup" +StartupObject = "Cpp2Beef.Setup.Program" diff --git a/Setup/BeefSpace.toml b/Setup/BeefSpace.toml new file mode 100644 index 0000000..f354dc7 --- /dev/null +++ b/Setup/BeefSpace.toml @@ -0,0 +1,5 @@ +FileVersion = 1 +Projects = {"Cpp2Beef.Setup" = {Path = "."}} + +[Workspace] +StartupProject = "Cpp2Beef.Setup" diff --git a/Setup/src/Program.bf b/Setup/src/Program.bf new file mode 100644 index 0000000..2ffe7bb --- /dev/null +++ b/Setup/src/Program.bf @@ -0,0 +1,27 @@ +using System; +using System.IO; + +namespace Cpp2Beef.Setup; + +static class Program +{ + [CLink] static extern int32 system(char8*); + static void RunCommand(char8* cmd) + { + Console.WriteLine($"> {StringView(cmd)}"); + if (system(cmd) != 0) + Runtime.FatalError("Command failed"); + } + + public static int Main(String[] args) + { + RunCommand("beefbuild -workspace=\"../CxxBuilder\" -config=Release"); + String setCxxBuilderExe = scope .(512); + Path.GetAbsolutePath("../CxxBuilder/dist/CxxBuilder", Directory.GetCurrentDirectory(..scope .(128)), setCxxBuilderExe); +#if BF_PLATFORM_WINDOWS + setCxxBuilderExe.Append(".exe"); +#endif + File.WriteAllText("../CxxBuilder/dist/CxxBuilderPath.txt", setCxxBuilderExe); + return 0; + } +} \ No newline at end of file diff --git a/src/ClangC.bf b/src/ClangC.bf new file mode 100644 index 0000000..2473d52 --- /dev/null +++ b/src/ClangC.bf @@ -0,0 +1,171 @@ +using System; +using System.IO; +using System.Collections; +using System.Diagnostics; + +using Cpp2Beef; +using LibClang; + +namespace ClangC.Setup; + +class ClangCGenerator : Cpp2BeefGenerator, this(Span args) +{ + protected override Span Args => args; + protected override Cpp2BeefGenerator.Flags Flags => .None; + + public Dictionary writers = new .(8) ~ DeleteDictionaryAndKeysAndValues!(_); + protected override StreamWriter GetWriterForHeader(StringView header) + { + if (header.IsNull || !header.Contains("clang-c")) return null; + let headerPath = Path.GetActualPathName(header, ..scope .(header.Length)); + if (writers.TryAddAlt(headerPath, let keyPtr, let valuePtr)) + { + String outPath = scope .(64); + outPath.Append("../Cpp2Beef_dist/Clang-C/src/"); + Path.GetFileNameWithoutExtension(headerPath, outPath); + outPath.Append(".bf"); + *keyPtr = new .(headerPath); + *valuePtr = new .()..Create(outPath)..Write(""" + // This file was auto-generated by Cpp2Beef + + using System; + using System.Interop; + + namespace LibClang; + + + """); + } + return *valuePtr; + } + + protected override Block GetCursorBlock(CXCursor cursor) + { + if (cursor.kind == .FunctionDecl) + return .CustomBlock("Clang"); + return base.GetCursorBlock(cursor); + } + + protected override bool IsOutParam(CXCursor arg, CXCursor method) + { + let comment = Clang.Cursor_GetParsedComment(method); + let spelling = GetCursorSpelling!(arg); + for (let i < Clang.Comment_GetNumChildren(comment)) + { + let param = Clang.Comment_GetChild(comment, i); + if (Clang.Comment_GetKind(param) != .ParamCommand || + ScopeCXString!(Clang.ParamCommandComment_GetParamName(param)) != spelling) continue; + let paragraph = Clang.BlockCommandComment_GetParagraph(param); + if (Clang.Comment_GetNumChildren(paragraph) == 0) continue; + let paragraphChild = Clang.Comment_GetChild(paragraph, 0); + if (Clang.Comment_GetKind(paragraphChild) != .Text) continue; + StringView paragraphText = ScopeCXString!(Clang.TextComment_GetText(paragraphChild)); + if (paragraphText..TrimStart().StartsWith("[out]")) return true; + else break; + } + return base.IsOutParam(arg, method); + } + + protected override void GetNameInBindings(CXCursor cursor, String outString) + { + switch (cursor.kind) + { + case .FunctionDecl: + StringView spelling = GetCursorSpelling!(cursor); + if (spelling.StartsWith("clang_")) spelling.RemoveFromStart(6); + bool upper = true; + for (let c in spelling) + { + if (upper) + { + outString.Append(c.ToUpper); + upper = false; + } + else + outString.Append(c); + if (c == '_') upper = true; + } + case .EnumConstantDecl: + let parentSpelling = GetCursorSpelling!(Clang.GetCursorSemanticParent(cursor)); + var spelling = GetCursorSpelling!(cursor); + int i = 0; + for (; i < parentSpelling.Length && i < spelling.Length + && parentSpelling[i] == spelling[i]; i++) {} + spelling.RemoveFromStart(i); + if (spelling.StartsWith('_')) spelling.RemoveFromStart(1); + Compiler.Identifier.GetSourceName(spelling, outString); + default: + base.GetNameInBindings(cursor, outString); + } + } + + protected override void HandleCursor(CXCursor cursor) + { + if (cursor.kind == .TypedefDecl) do + { + let type = Clang.GetTypedefDeclUnderlyingType(cursor); + if (type.kind != .Pointer) break; + let pointee = Clang.GetPointeeType(type); + if (pointee.kind == .FunctionProto || pointee.kind == .FunctionNoProto || + (pointee.kind == .Void && GetCursorSpelling!(cursor) == "CXClientData")) break; + BeginCursor(cursor); + AccessSpecifier(cursor); + str.Append("struct "); + GetNameInBindings(cursor, str); + str.Append(" : int {}"); + return; + } + base.HandleCursor(cursor); + } + + protected override void WriteCustomAttributes(CXCursor cursor) + { + if (cursor.kind == .FunctionDecl) + str.Append("[Import(Clang.dll)] "); + base.WriteCustomAttributes(cursor); + } +} + +static class Program +{ + [CLink] extern static int32 system(char8*); + + const String[?] clangFiles = .( + "BuildSystem", + "CXCompilationDatabase", + "CXDiagnostic", + "CXErrorCode", + "CXFile", + "CXSourceLocation", + "CXString", + "Documentation", + "ExternC", + "FatalErrorHandler", + "Index", + "Platform", + "Rewrite", + ); + const String clangHeaders = "./Clang-C/clang-c"; + const String IclangHeaders = "-IClang-C"; + + public static int Main(String[] args) + { + ClangCGenerator generator = scope .(char8*[?]("--language=c", IclangHeaders)); + + Directory.CreateDirectory(clangHeaders); + { + StreamWriter includeAll = scope .()..Create("clang-c.h"); + for (let file in clangFiles) + { + //if (system(scope $"curl -o {clangHeaders}/{file}.h https://raw.githubusercontent.com/llvm/llvm-project/refs/heads/main/clang/include/clang-c/{file}.h") != 0) + // Runtime.FatalError(scope $"Failed to download clang-c/{file}"); + includeAll.Write($"#include \n"); + } + } + + Directory.CreateDirectory("clang-c"); + generator.Generate("clang-c.h", null); + + return 0; + } +} diff --git a/src/Generator.bf b/src/Generator.bf index 229397d..f83a9bb 100644 --- a/src/Generator.bf +++ b/src/Generator.bf @@ -38,6 +38,8 @@ abstract class Cpp2BeefGenerator case "uint16_t": outString.Append("uint16"); case "uint32_t": outString.Append("uint32"); case "uint64_t": outString.Append("uint64"); + case "intptr_t": outString.Append("c_intptr"); + case "uintptr_t": outString.Append("c_uintptr"); default: fallthrough getName; } default: @@ -93,7 +95,7 @@ abstract class Cpp2BeefGenerator } } - void WriteBlock(Block block) + protected void WriteBlock(Block block) { StringView target; switch (block) @@ -131,6 +133,8 @@ abstract class Cpp2BeefGenerator switch (cursor.kind) { case .StructDecl, .ClassDecl, .UnionDecl, .EnumDecl: + if (Clang.Cursor_IsAnonymous(cursor) != 0) + return; if (templateParams.IsEmpty && Clang.EqualCursors(cursor, Clang.GetTypeDeclaration(Clang.GetCursorType(cursor))) == 0) return; BeginCursor(cursor); @@ -170,7 +174,7 @@ abstract class Cpp2BeefGenerator MacroDefinition(cursor); case .InclusionDirective, .MacroExpansion, .CXXBaseSpecifier, .CXXAccessSpecifier, - .LinkageSpec, .TemplateTypeParameter, .NonTypeTemplateParameter: // ignored + .LinkageSpec, .TemplateTypeParameter, .NonTypeTemplateParameter, .StaticAssert: // ignored default: Debug.WriteLine(scope $"Unhandled cursor: {_}"); } } @@ -222,7 +226,8 @@ abstract class Cpp2BeefGenerator protected class FileInfo : this(CXSourceLocation prevEnd, CXFile file) { public append String block = .(64); - public enum { None, LSquirly, RSquirly } queuedToken = .None; + public enum { None = 0, LSquirly = 1, RSquirly = 0b10 } queuedTokens = .None; + public (CXType type, int32 curWidth) bitfield = default; } protected FileInfo fileInfo = null; private Dictionary fileInfos = new .(32) ~ DeleteDictionaryAndKeysAndValues!(_); @@ -251,6 +256,18 @@ abstract class Cpp2BeefGenerator ScopeCXString!:mixin(Clang.GetTokenSpelling(unit, token)) } + public static void UpperSnakeCase2PascalCase(StringView snakeCase, String outString) + { + bool upper = true; + for (let c in snakeCase) + if (c == '_') upper = true; + else + { + outString.Append(upper ? c.ToUpper : c.ToLower); + upper = false; + } + } + protected virtual void PreGeneration() {} protected virtual void PostGeneration() {} @@ -305,8 +322,8 @@ abstract class Cpp2BeefGenerator #endif CXTranslationUnit_Flags unitFlags = .SkipFunctionBodies | .DetailedPreprocessingRecord; unit = Clang.ParseTranslationUnit(index, headerPath, args.Ptr, (.)args.Length, null, 0, (.)unitFlags); - if (unit == null) return .Err(.ParsingFailed); - + if (unit == default) return .Err(.ParsingFailed); + printingPolicy = Clang.GetCursorPrintingPolicy(Clang.GetTranslationUnitCursor(unit)); ModifyWrapperPrintingPolicy(printingPolicy); defer Clang.PrintingPolicy_Dispose(printingPolicy); @@ -318,14 +335,14 @@ abstract class Cpp2BeefGenerator { let location = Clang.GetCursorLocation(cursor); - Clang.GetSpellingLocation(location, let file, let line, ?, ?); + Clang.GetFileLocation(location, let file, let line, ?, ?); let header = ScopeCXString!(Clang.GetFileName(file)); self.currentWritter = self.GetWriterForHeader(header); if (self.currentWritter == null) return .Continue; if (cursor.kind == .MacroDefinition) { - self.unitMacros.Add(.(line, file), cursor); + self.unitMacros[.(line, file)] = cursor; return .Continue; } @@ -429,7 +446,7 @@ abstract class Cpp2BeefGenerator protected void FatalErrorMsg(String outString) { - Clang.GetSpellingLocation(Clang.GetCursorLocation(currentCursor), let file, let line, let col, ?); + Clang.GetFileLocation(Clang.GetCursorLocation(currentCursor), let file, let line, let col, ?); outString.Append("in cursor '", GetCursorSpelling!(currentCursor), "' at line "); outString.AppendF($"{line}:{col}"); outString.Append(" in file ", GetFilePath!(file)); @@ -451,7 +468,7 @@ abstract class Cpp2BeefGenerator StringView file; uint32 offset; { - Clang.GetSpellingLocation(location, let cxfile, ?, ?, out offset); + Clang.GetFileLocation(location, let cxfile, ?, ?, out offset); char8* ptr = Clang.GetFileContents(unit, cxfile, let size); file = .(ptr, (.)size); } @@ -467,7 +484,7 @@ abstract class Cpp2BeefGenerator StringView file; uint32 offset; { - Clang.GetSpellingLocation(from, let cxfile, ?, ?, out offset); + Clang.GetFileLocation(from, let cxfile, ?, ?, out offset); char8* ptr = Clang.GetFileContents(unit, cxfile, let size); file = .(ptr, (.)size); } @@ -487,8 +504,8 @@ abstract class Cpp2BeefGenerator Clang.EqualLocations(end, Clang.GetNullLocation()) != 0) return; StringView file; - Clang.GetSpellingLocation(start, let startFile, ?, ?, let startOffset); - Clang.GetSpellingLocation(end, let endfile, ?, ?, let endOffset); + Clang.GetFileLocation(start, let startFile, ?, ?, let startOffset); + Clang.GetFileLocation(end, let endfile, ?, ?, let endOffset); if (Clang.File_IsEqual(startFile, endfile) == 0) return; char8* ptr = Clang.GetFileContents(unit, startFile, let size); file = .(ptr, (.)size); @@ -501,24 +518,35 @@ abstract class Cpp2BeefGenerator } } + protected virtual CXSourceLocation GetCursorAnchor(CXCursor cursor) + { + let cursorLocation = Clang.GetCursorLocation(cursor); + //let rangeLocation = Clang.GetRangeStart(Clang.GetCursorExtent(cursor)); + //if (Clang.File_IsEqual(Clang.GetSpellingLocation(cursorLocation, file: ..?, ?, ?, ?), Clang.GetSpellingLocation(rangeLocation, file: ..?, ?, ?, ?)) == 0) + return cursorLocation; + //return rangeLocation; + } + protected virtual void WriteComments(CXCursor cursor, Block? block = null) { + let location = GetCursorAnchor(cursor); if (cursor.kind == .MacroDefinition) { - GetIndentation(Clang.GetCursorLocation(cursor), str); + if (block.HasValue) WriteBlock(block.Value); + GetIndentation(location, str); return; } cursorIndent.Clear(); - GetIndentation(Clang.GetCursorLocation(cursor), cursorIndent); - WriteComments(Clang.GetCursorLocation(cursor), block); + GetIndentation(location, cursorIndent); + WriteComments(location, block); } protected virtual void WriteComments(CXSourceLocation writeUntil, Block? block = null) { - Clang.GetSpellingLocation(writeUntil, let curFile, let curLine, let curColumn, let curOffset); - Clang.GetSpellingLocation(fileInfo.prevEnd, let prevFile, let prevLine, let prevColumn, let prevOffset); + Clang.GetFileLocation(writeUntil, let curFile, let curLine, let curColumn, let curOffset); + Clang.GetFileLocation(fileInfo.prevEnd, let prevFile, let prevLine, let prevColumn, let prevOffset); defer { fileInfo.prevEnd = writeUntil; } - if (Clang.File_IsEqual(curFile, prevFile) == 0) Runtime.FatalError(); + if (Clang.File_IsEqual(curFile, prevFile) == 0) Runtime.FatalError(scope $"{GetFilePath!(curFile)} != {GetFilePath!(prevFile)}"); var between = Clang.GetRange( Clang.GetLocationForOffset(unit, prevFile, prevOffset), @@ -579,18 +607,23 @@ abstract class Cpp2BeefGenerator { case .Comment: case .Punctuation: - switch (fileInfo.queuedToken) + mixin QueuedToken(var queuedToken, StringView spelling) { - case .LSquirly: if (GetTokenSpelling!(token) != "{") continue; - case .RSquirly: if (GetTokenSpelling!(token) != "}") continue; - case .None: continue; + if (fileInfo.queuedTokens.HasFlag(queuedToken) && GetTokenSpelling!(token) == spelling) + { + fileInfo.queuedTokens ^= queuedToken; + isWritingQueuedToken = true; + } } - fileInfo.queuedToken = .None; - isWritingQueuedToken = true; + + QueuedToken!(decltype(fileInfo.queuedTokens).LSquirly, "{"); + QueuedToken!(decltype(fileInfo.queuedTokens).RSquirly, "}"); + + if (!isWritingQueuedToken) continue; default: continue; } let location = Clang.GetTokenLocation(unit, token); - Clang.GetSpellingLocation(location, ?, let startLine, ?, ?); + Clang.GetFileLocation(location, ?, let startLine, ?, ?); WriteLinesUntil!(startLine, location); if (!isWritingQueuedToken) { @@ -671,6 +704,13 @@ abstract class Cpp2BeefGenerator case .StructDecl, .ClassDecl, .UnionDecl: Record(decl); default: Runtime.FatalError(scope $"Unhandled anon type: {_}"); } + if (fileInfo.queuedTokens.HasFlag(.RSquirly)) + { + str.Append('\n'); + GetIndentation(GetCursorAnchor(decl), str); + str.Append('}'); + fileInfo.queuedTokens ^= .RSquirly; + } break; } @@ -751,6 +791,7 @@ abstract class Cpp2BeefGenerator { if (Clang.GetTokenKind(token) == .Punctuation && { let spelling = GetTokenSpelling!(token); spelling == "," || spelling == ")" }) { + if (Clang.GetTokenKind(last) != .Identifier) break; str.Append(' '); str.Append(GetTokenSpelling!(last)); break; @@ -779,26 +820,27 @@ abstract class Cpp2BeefGenerator protected mixin BeginBody(CXCursor cursor) { - fileInfo.prevEnd = Clang.GetCursorLocation(cursor); - fileInfo.queuedToken = .LSquirly; + fileInfo.prevEnd = GetCursorAnchor(cursor); + fileInfo.queuedTokens |= .LSquirly; + // [Friend] is needed in case you use BeginBody! in your code if (canChangeBlock) { - defer:mixin { canChangeBlock = true; } - canChangeBlock = false; + defer:mixin { this.[Friend]canChangeBlock = true; } + this.[Friend]canChangeBlock = false; } defer:mixin { - if (defferedWrapperWrite != null) + if (this.[Friend]defferedWrapperWrite != null) { str.Append("\n\n", cursorIndent, "const String _wrapperText = \"\\n\"\n", - defferedWrapperWrite, ";"); - delete defferedWrapperWrite; - defferedWrapperWrite = null; + this.[Friend]defferedWrapperWrite, ";"); + delete this.[Friend]defferedWrapperWrite; + this.[Friend]defferedWrapperWrite = null; } - if (fileInfo.queuedToken == .LSquirly) + if (fileInfo.queuedTokens.HasFlag(.LSquirly)) { switch (cursor.kind) { @@ -807,11 +849,10 @@ abstract class Cpp2BeefGenerator default: str.Append(" {}"); } - fileInfo.queuedToken = .None; + fileInfo.queuedTokens = .None; } else - fileInfo.queuedToken = .RSquirly; - + fileInfo.queuedTokens |= .RSquirly; } } @@ -1053,7 +1094,7 @@ abstract class Cpp2BeefGenerator } if (Clang.Cursor_IsInlineNamespace(cursor) != 0) { - GetIndentation(Clang.GetCursorLocation(cursor), str); + GetIndentation(GetCursorAnchor(cursor), str); str.Append("public static using "); GetNameInBindings(cursor, str); str.Append(";\n"); @@ -1215,11 +1256,61 @@ abstract class Cpp2BeefGenerator str.Append(';'); } + int bitfieldUniquenessCounter = 0; + protected virtual void DumpBitfieldStorage() + { + str.Append("\n", cursorIndent, "private "); + Type(fileInfo.bitfield.type); + str.Append(" __bitfield_"); + bitfieldUniquenessCounter++.ToString(str); + str.Append(';'); + fileInfo.bitfield = default; + } + protected virtual void FieldDecl(CXCursor cursor) { + let type = Clang.GetCursorType(cursor); + if (Clang.Cursor_IsBitField(cursor) != 0) + { + if (fileInfo.bitfield.type != default && Clang.EqualTypes(fileInfo.bitfield.type, type) == 0) + DumpBitfieldStorage(); + if (fileInfo.bitfield == default) + fileInfo.bitfield.type = type; + int32 width = Clang.GetFieldDeclBitWidth(cursor); + Debug.Assert(width <= Clang.Type_GetSizeOf(type) * 8); + fileInfo.bitfield.curWidth += width; + if (fileInfo.bitfield.curWidth > Clang.Type_GetSizeOf(type) * 8) + { + DumpBitfieldStorage(); + fileInfo.bitfield.curWidth = width; + } + + str.Append("[Bitfield(."); + if (GetCursorSpelling!(cursor).IsEmpty) + { + str.Append("Private, .Bits("); + width.ToString(str); + str.Append("), \"__anon_bitfield_"); + bitfieldUniquenessCounter++.ToString(str); + str.Append("\")]"); + } + else + { + Flush(); + AccessSpecifier(cursor); + str[0] = str[0].ToUpper; + str.Append(", .Bits("); + width.ToString(str); + str.Append("), \""); + GetNameInBindings(cursor, str); + str.Append("\")]"); + } + return; + } + WriteCustomAttributes(cursor); AccessSpecifier(cursor); - WriteTypeAndName(cursor, Clang.GetCursorType(cursor)); + WriteTypeAndName(cursor, type); str.Append(';'); } @@ -1380,11 +1471,16 @@ abstract class Cpp2BeefGenerator Clang.VisitChildren(cursor, (cursor, parent, client_data) => { Self self = (.)Internal.UnsafeCastToObject(client_data); + if (self.fileInfo.bitfield != default && Clang.Cursor_IsBitField(cursor) == 0) + self.DumpBitfieldStorage(); self.WriteCursor(cursor); if (Clang.Cursor_IsAnonymousRecordDecl(cursor) != 0) self.str..TrimEnd()..Append(";"); return .Continue; }, Internal.UnsafeCastToPtr(this)); + + if (fileInfo.bitfield != default) + DumpBitfieldStorage(); } protected virtual void Enum(CXCursor cursor)