using System; using System.IO; using System.Collections; using System.Diagnostics; using LibClang; namespace Cpp2Beef; abstract class Cpp2BeefGenerator { protected abstract Span Args { get; } protected abstract StreamWriter GetWriterForHeader(StringView header); protected abstract Flags Flags { get; } public enum Flags { None = 0, PreseveColumns = 1, } protected virtual void GetNameInBindings(CXCursor cursor, String outString) { let spelling = GetCursorSpelling!(cursor); getName: switch (cursor.kind) { case .TypedefDecl: switch (spelling) { case "size_t": outString.Append("c_size"); case "wchar_t": outString.Append("c_wchar"); case "va_list": outString.Append("VarArgs"); case "int8_t": outString.Append("int8"); case "int16_t": outString.Append("int16"); case "int32_t": outString.Append("int32"); case "int64_t": outString.Append("int64"); case "uint8_t": outString.Append("uint8"); 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: Compiler.Identifier.GetSourceName(spelling, outString); } } enum AttrFlags { None = 0, Packed = 1, NoDiscard = 2 } protected virtual void WriteCustomAttributes(CXCursor cursor) { AttrFlags attrs = 0; Clang.VisitChildren(cursor, (cursor, parent, client_data) => { AttrFlags* attrs = (.)client_data; switch (cursor.kind) { case .PackedAttr: *attrs |= .Packed; case .WarnUnusedResultAttr: *attrs |= .NoDiscard; default: } return .Continue; }, &attrs); if (Clang.GetCursorAvailability(cursor) == .Deprecated) str.Append("[Obsolete] "); if (cursor.kind == .EnumDecl) str.Append("[AllowDuplicates] "); else if (attrs.HasFlag(.Packed)) str.Append("[Packed] "); if (attrs.HasFlag(.NoDiscard)) str.Append("[NoDiscard] "); } protected enum Block { case NoBlock, StaticBlock, CustomBlock(StringView); } protected virtual Block GetCursorBlock(CXCursor cursor) { switch (cursor.kind) { case .StructDecl, .ClassDecl, .UnionDecl, .EnumDecl, .TypeAliasDecl, .TypedefDecl, .Namespace: return .NoBlock; case .FunctionDecl, .CXXMethod, .Constructor, .Destructor, .ConversionFunction, .FieldDecl, .VarDecl, .MacroDefinition: return .StaticBlock; default: Internal.FatalError(scope $"Missing GetCursorBlock implementation {FatalErrorMsg(..scope .(256))}"); } } protected void WriteBlock(Block block) { StringView target; switch (block) { case .NoBlock: target = ""; case .StaticBlock: target = "static"; case .CustomBlock(let p0): target = p0; } if (fileInfo.block == target) return; if (!fileInfo.block.IsEmpty) { str.TrimEnd(); str.Append("\n", cursorIndent, "}\n\n"); } fileInfo.block.Set(target); switch (block) { case .NoBlock: case .StaticBlock: str.Append("static\n{\n"); case .CustomBlock(let p0): str.Append("extension ", p0, "\n{\n"); } } protected void BeginCursor(CXCursor cursor) { WriteComments(cursor, canChangeBlock ? (Block?)GetCursorBlock(cursor) : null); fileInfo.prevEnd = Clang.GetRangeEnd(Clang.GetCursorExtent(cursor)); } protected virtual void HandleCursor(CXCursor cursor) { 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); if (_ case .EnumDecl) Enum(cursor); else Record(cursor); case .TypeAliasDecl, .TypedefDecl: if (Clang.Cursor_IsNull(lastRecordOrEnum) == 0) { let lastName = GetNameInBindings(lastRecordOrEnum, ..scope .(64)); let curName = GetNameInBindings(cursor, ..scope .(64)); if (lastName == curName) return; } BeginCursor(cursor); TypeAlias(cursor); case .Namespace: BeginCursor(cursor); Namespace(cursor); case .FunctionDecl: BeginCursor(cursor); FunctionDecl(cursor); case .FunctionTemplate, .ClassTemplate, .TypeAliasTemplateDecl: Runtime.FatalError("Templates are currently unsupported but Coming Soon tm"); case .CXXMethod, .Constructor, .Destructor, .ConversionFunction: if (templateParams.IsEmpty && Clang.EqualCursors(Clang.GetCursorLexicalParent(cursor), Clang.GetCursorSemanticParent(cursor)) == 0) return; BeginCursor(cursor); CXXMethod(cursor); case .FieldDecl: BeginCursor(cursor); FieldDecl(cursor); case .VarDecl: BeginCursor(cursor); VarDecl(cursor); case .MacroDefinition: if (Clang.Cursor_IsMacroFunctionLike(cursor) != 0) return; let tokens = ScopeTokenize!(cursor, unit); if (tokens.Length <= 1) return; BeginCursor(cursor); MacroDefinition(cursor); case .InclusionDirective, .MacroExpansion, .CXXBaseSpecifier, .CXXAccessSpecifier, .LinkageSpec, .TemplateTypeParameter, .NonTypeTemplateParameter, .StaticAssert: // ignored default: Debug.WriteLine(scope $"Unhandled cursor: {_}"); } } protected virtual 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) continue; if (ScopeCXString!(Clang.ParamCommandComment_GetParamName(param)) != spelling) continue; return Clang.ParamCommandComment_GetDirection(param) == .Out; } return false; } protected virtual void ModifyWrapperPrintingPolicy(CXPrintingPolicy policy) {} protected CXIndex index = Clang.CreateIndex(excludeDeclarationsFromPCH: 0, displayDiagnostics: 1) ~ Clang.DisposeIndex(_); protected CXTranslationUnit unit; protected append String str = .(1024); protected append String wrapperBuf = .(1024); protected CXCursor currentCursor = Clang.GetNullCursor(); protected append String cursorIndent = .(16); private bool canChangeBlock = true; private CXCursor lastRecordOrEnum = Clang.GetNullCursor(); protected append String fullCursorName = .(256); protected CXPrintingPolicy printingPolicy; private StreamWriter currentWritter; private StreamWriter wrapperWritter; protected StringView WrapperFilePath; private append String wrapperTemplateChain = .(16); private append String templateParams = .(16); private append String templateParamsWhere = .(64); private String defferedWrapperWrite = null; private struct UnitMacroIndex : this(uint32 line, CXFile file), IHashable { public int GetHashCode() => line; public static bool operator==(Self lhs, Self rhs) => lhs.line == rhs.line && Clang.File_IsEqual(lhs.file, rhs.file) != 0; } protected class FileInfo : this(CXSourceLocation prevEnd, CXFile file) { public append String block = .(64); 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!(_); private append Dictionary unitMacros = .(128); public static mixin ScopeCXString(CXString str) { defer:mixin Clang.DisposeString(str); StringView(Clang.GetCString(str)) } public static mixin GetCursorSpelling(CXCursor cursor) { ScopeCXString!:mixin(Clang.GetCursorSpelling(cursor)) } public static mixin GetTypeSpelling(CXType type) { ScopeCXString!:mixin(Clang.GetTypeSpelling(type)) } public static mixin GetFilePath(CXFile file) { ScopeCXString!:mixin(Clang.GetFileName(file)) } protected mixin GetTokenSpelling(CXToken token) { 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() {} public enum GenerationError { ParsingFailed } public Result Generate(char8* headerPath, StringView wrapperPath = null) { unitMacros.Clear(); fileInfos.Clear(); lastRecordOrEnum = Clang.GetNullCursor(); if (!wrapperPath.IsNull) { wrapperWritter = scope:: .(); wrapperWritter.Create(wrapperPath); WrapperFilePath = wrapperPath; wrapperBuf.Set(""" #define private public #define protected public #include " """); Path.GetFileName(.(headerPath), wrapperBuf); wrapperBuf.Append(""" " #include template using __type = T; extern "C" { """); FlushWrapper(); } else wrapperWritter = null; let args = Args; #if DEBUG findLang: do { for (let arg in args) { StringView view = .(arg); if (view == "-x" || view.StartsWith("--language")) break findLang; } Runtime.FatalError("You must set a language via Args (e.g. --language=c++)"); } #endif CXTranslationUnit_Flags unitFlags = .SkipFunctionBodies | .DetailedPreprocessingRecord; unit = Clang.ParseTranslationUnit(index, headerPath, args.Ptr, (.)args.Length, null, 0, (.)unitFlags); if (unit == default) return .Err(.ParsingFailed); printingPolicy = Clang.GetCursorPrintingPolicy(Clang.GetTranslationUnitCursor(unit)); ModifyWrapperPrintingPolicy(printingPolicy); defer Clang.PrintingPolicy_Dispose(printingPolicy); PreGeneration(); Clang.VisitChildren(Clang.GetTranslationUnitCursor(unit), (cursor, parent, client_data) => { Self self = (.)Internal.UnsafeCastToObject(client_data); { let location = Clang.GetCursorLocation(cursor); 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[.(line, file)] = cursor; return .Continue; } if (!self.fileInfos.TryGetAlt(header, ?, out self.fileInfo)) { self.fileInfo = new .(Clang.GetLocation(self.unit, file, 1, 1), file); self.fileInfos.Add(new .(header), self.fileInfo); } } self.WriteCursor(cursor); switch (cursor.kind) { case .ClassDecl, .StructDecl, .EnumDecl, .UnionDecl: self.lastRecordOrEnum = cursor; default: } return .Continue; }, Internal.UnsafeCastToPtr(this)); PostGeneration(); if (wrapperBuf.IsEmpty) wrapperWritter.Write("}\n\n//begin-comptime\n"); for (let kv in fileInfos) { fileInfo = kv.value; currentWritter = GetWriterForHeader(kv.key); Clang.GetFileContents(unit, kv.value.file, let size); WriteComments(Clang.GetLocationForOffset(unit, kv.value.file, (.)size-1), .NoBlock); Flush(); } return .Ok; } protected void WriteCursor(CXCursor cursor, bool macro = false) { if ((macro) != (cursor.kind == .MacroDefinition)) return; CXCursor prevCursor = currentCursor; currentCursor = cursor; defer { currentCursor = prevCursor; } int removeLenFullCursorName; { int curLen = fullCursorName.Length; if (!fullCursorName.IsEmpty) fullCursorName.Append("::"); fullCursorName.Append(GetCursorSpelling!(cursor)); removeLenFullCursorName = fullCursorName.Length - curLen; } defer { fullCursorName.Length -= removeLenFullCursorName; } var cursor; int removeLenWrapperTemplateChain = 0; defer { wrapperTemplateChain.Length -= removeLenWrapperTemplateChain; } if (!macro) { templateParams.Clear(); templateParamsWhere.Clear(); switch (cursor.kind) { case .FunctionTemplate, .ClassTemplate, .TypeAliasTemplateDecl: templateParams.Append('<'); let len = wrapperTemplateChain.Length; Clang.VisitChildren(cursor, (cursor, parent, client_data) => { Self self = (.)Internal.UnsafeCastToObject(client_data); switch (cursor.kind) { case .TemplateTypeParameter: case .NonTypeTemplateParameter: self.templateParamsWhere.Append(" where ", GetCursorSpelling!(cursor), " : const "); self.Flush(); self.Type(Clang.GetCursorType(cursor)); self.templateParamsWhere.Append(self.str); self.str.Clear(); case .TemplateTemplateParameter: Internal.FatalError(scope $"C++ template template parameters are not supported {self.FatalErrorMsg(..scope .(256))}"); default: return .Continue; } let spelling = GetCursorSpelling!(cursor); self.templateParams.Append(spelling, ", "); self.wrapperTemplateChain.Append("__{typeof(", spelling, ")}"); return .Continue; }, Internal.UnsafeCastToPtr(this)); templateParams.Length -= 2; templateParams.Append('>'); removeLenWrapperTemplateChain = wrapperTemplateChain.Length - len; cursor.kind = Clang.GetTemplateCursorKind(cursor); default: } } HandleCursor(cursor); Flush(); } protected void FatalErrorMsg(String outString) { 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)); } protected void Flush() { currentWritter.Write(str); str.Clear(); } protected void FlushWrapper() { wrapperWritter.Write(wrapperBuf); wrapperBuf.Clear(); } protected virtual void GetIndentation(CXSourceLocation location, String outString) { StringView file; uint32 offset; { Clang.GetFileLocation(location, let cxfile, ?, ?, out offset); char8* ptr = Clang.GetFileContents(unit, cxfile, let size); file = .(ptr, (.)size); } int i = offset; for (; i >= 0 && file[i] != '\n'; i--) {} i++; for (; i < file.Length && file[i].IsWhiteSpace; i++) outString.Append(file[i]); } protected virtual void AllWhiteSpaceUntil(CXSourceLocation from, enum { NoNewLines, YesNewLines } newlineMode) { StringView file; uint32 offset; { Clang.GetFileLocation(from, let cxfile, ?, ?, out offset); char8* ptr = Clang.GetFileContents(unit, cxfile, let size); file = .(ptr, (.)size); } for (int i = offset-1; i >= 0; i--) { char8 c = file[i]; if (c == '/' || c == '*') continue; if ((!c.IsWhiteSpace) || (newlineMode == .NoNewLines && c == '\n')) break; str.Append(c); } } protected virtual void AllWhiteSpaceBetween(CXSourceLocation start, CXSourceLocation end) { if (Clang.EqualLocations(start, Clang.GetNullLocation()) != 0 || Clang.EqualLocations(end, Clang.GetNullLocation()) != 0) return; StringView file; 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); for (int i = startOffset; i < endOffset; i++) { char8 c = file[i]; //if (c.IsWhiteSpace) str.Append(c); str.Append(c.IsWhiteSpace ? c : ' '); } } 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) { if (block.HasValue) WriteBlock(block.Value); GetIndentation(location, str); return; } cursorIndent.Clear(); GetIndentation(location, cursorIndent); WriteComments(location, block); } protected virtual void WriteComments(CXSourceLocation writeUntil, Block? block = null) { var block; if (fileInfo.queuedTokens != .None) block = null; 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(scope $"{GetFilePath!(curFile)} != {GetFilePath!(prevFile)}"); var between = Clang.GetRange( Clang.GetLocationForOffset(unit, prevFile, prevOffset), Clang.GetLocationForOffset(unit, curFile, curOffset) ); CXToken* tokenPtr = null; uint32 tokenCount = 0; Clang.Tokenize(unit, between, &tokenPtr, &tokenCount); defer Clang.DisposeTokens(unit, tokenPtr, tokenCount); Span tokens = .(tokenPtr, tokenCount); uint32 line = prevLine; mixin WriteLinesUntil(uint32 targetLine, CXSourceLocation from) { int numNewLines = targetLine - line; for (let i < numNewLines) { str.Append('\n'); line++; if (unitMacros.TryGetValue(.(line, curFile), let macro)) { WriteCursor(macro, macro: true); if (block.HasValue) remainingMacros: do { let line = line; for (uint32 ln = line+1; ln < curLine; ln++) if (unitMacros.ContainsKey(.(ln, curFile))) break remainingMacros; WriteBlock(block.Value); } } } if (numNewLines == 0) AllWhiteSpaceUntil(from, .NoNewLines); else GetIndentation(from, str); } void Block() { if (fileInfo.queuedTokens != .None) return; findMacros: do { for (uint32 ln = line; ln < curLine; ln++) { if (!unitMacros.TryGetValue(.(ln, curFile), let macro)) continue; WriteBlock(GetCursorBlock(macro)); break findMacros; } if (block.HasValue) WriteBlock(block.Value); } } bool first = true; for (let token in tokens) { bool isWritingQueuedToken = false; switch (Clang.GetTokenKind(token)) { case .Comment: case .Punctuation: mixin QueuedToken(var queuedToken, StringView spelling) { if (fileInfo.queuedTokens.HasFlag(queuedToken) && GetTokenSpelling!(token) == spelling) { fileInfo.queuedTokens ^= queuedToken; isWritingQueuedToken = true; } } QueuedToken!(decltype(fileInfo.queuedTokens).LSquirly, "{"); QueuedToken!(decltype(fileInfo.queuedTokens).RSquirly, "}"); if (!isWritingQueuedToken) continue; if (fileInfo.queuedTokens == .None) block = @block; default: continue; } let location = Clang.GetTokenLocation(unit, token); Clang.GetFileLocation(location, ?, let startLine, ?, ?); WriteLinesUntil!(startLine, location); if (!isWritingQueuedToken) { if (first) Block(); first = false; } WriteToken(token); for (let c in GetTokenSpelling!(token)) if (c == '\n') line++; Flush(); } WriteLinesUntil!(curLine, writeUntil); if (@block.HasValue) WriteBlock(@block.Value); } protected enum TypeParamMode { None, InParam, OutParam } protected virtual void Type(CXType type, TypeParamMode paramMode = .None) { switch (type.kind) { case .Void: str.Append("void"); case .Bool: str.Append("bool"); case .Char_U, .UChar: str.Append("c_uchar"); case .Char16: str.Append("char16"); case .Char32: str.Append("char32"); case .UShort: str.Append("c_ushort"); case .UInt: str.Append("c_uint"); case .ULong: str.Append("c_ulong"); case .ULongLong: str.Append("c_ulonglong"); case .Char_S, .SChar: str.Append("c_char"); case .WChar: str.Append("c_wchar"); case .Short: str.Append("c_short"); case .Int: str.Append("c_int"); case .Long: str.Append("c_long"); case .LongLong: str.Append("c_longlong"); case .Float: str.Append("float"); case .Double: str.Append("double"); case .NullPtr: str.Append("decltype(null)"); case .Pointer: let pointee = Clang.GetPointeeType(type); if (paramMode == .OutParam) str.Append("out "); Type(pointee); if (paramMode != .OutParam && pointee.kind != .FunctionProto && pointee.kind != .FunctionNoProto) str.Append('*'); case .LValueReference, .RValueReference: let nonRefType = Clang.GetNonReferenceType(type); switch (paramMode) { case .None: str.Append("ref "); case .InParam: switch (type.kind) { case .LValueReference: if (Clang.IsConstQualifiedType(nonRefType) == 0) str.Append("ref "); else fallthrough; case .RValueReference: str.Append("in "); default: Runtime.FatalError(); } case .OutParam: str.Append("out "); } Type(nonRefType); case .FunctionNoProto: str.Append("function "); Type(Clang.GetResultType(type)); str.Append("()"); case .FunctionProto: str.Append("function "); Type(Clang.GetResultType(type)); WriteFunctionProtoParams(type, ScopeTokenize!(currentCursor, unit)); case .ConstantArray: Type(Clang.GetArrayElementType(type)); str.Append('['); Clang.GetArraySize(type).ToString(str); str.Append(']'); case .IncompleteArray: Type(Clang.GetArrayElementType(type)); str.Append('*'); case .Auto: str.Append("var"); case .Elaborated, .Record, .Enum, .Typedef: CXCursor decl = Clang.GetTypeDeclaration(type); if (Clang.Cursor_IsAnonymous(decl) != 0) { switch (decl.kind) { case .EnumDecl: Enum(decl); 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; } { String qualified = scope .(256); String buffer = scope .(256); GetNameInBindings(decl, qualified); CXCursor parent = decl; while (true) { parent = Clang.GetCursorSemanticParent(parent); if (Clang.IsDeclaration(parent.kind) == 0 || parent.kind == .LinkageSpec) break; buffer.Clear(); switch (parent.kind) { case .FunctionTemplate, .ClassTemplate, .TypeAliasTemplateDecl: GetNameInBindings(parent, buffer); buffer.Append('<'); Clang.VisitChildren(parent, (cursor, parent, client_data) => { String buffer = (.)Internal.UnsafeCastToObject(client_data); switch (cursor.kind) { case .TemplateTypeParameter, .NonTypeTemplateParameter: case .TemplateTemplateParameter: Runtime.FatalError(scope $"C++ template template parameters are not supported"); default: return .Continue; } buffer.Append(GetCursorSpelling!(cursor), ", "); return .Continue; }, Internal.UnsafeCastToPtr(buffer)); buffer.Length -= 2; buffer.Append('>'); default: Flush(); Type(Clang.GetCursorType(parent)); buffer.Append(str); str.Clear(); } buffer.Append('.'); qualified.Insert(0, buffer); } str.Append(qualified); } let numTemplateArgs = Clang.Type_GetNumTemplateArguments(type); if (numTemplateArgs > 0) { str.Append('<'); for (let i < numTemplateArgs) { if (i > 0) str.Append(", "); Type(Clang.Type_GetTemplateArgumentAsType(type, (.)i)); } str.Append('>'); } case .Unexposed, .DependentSizedArray: str.Append(ScopeCXString!(Clang.GetTypeSpelling(Clang.GetUnqualifiedType(type)))); // template param default: Runtime.FatalError(scope $"Unhandled type: {_} \"{GetTypeSpelling!(type)}\""); } } protected virtual void WriteFunctionProtoParams(CXType type, Span tokens = null) { var iter = tokens.GetEnumerator(); for (let i < 2) for (let token in iter) if (Clang.GetTokenKind(token) == .Punctuation && GetTokenSpelling!(token) == "(") break; str.Append('('); let numArgs = Clang.GetNumArgTypes(type); for (let i < numArgs) { if (i > 0) str.Append(", "); Type(Clang.GetArgType(type, (.)i)); CXToken last = default; for (let token in iter) { if (Clang.GetTokenKind(token) == .Punctuation && { let spelling = GetTokenSpelling!(token); spelling == "," || spelling == ")" }) { if (Clang.GetTokenKind(last) != .Identifier) break; str.Append(' '); Compiler.Identifier.GetSourceName(GetTokenSpelling!(last), str); break; } last = token; } } if (Clang.IsFunctionTypeVariadic(type) != 0) { if (numArgs > 0) str.Append(", "); str.Append("..."); } str.Append(')'); } protected void AccessSpecifier(CXCursor cursor) { switch (Clang.GetCXXAccessSpecifier(cursor)) { case .InvalidAccessSpecifier, .Public: str.Append("public "); case .Protected: str.Append("protected "); case .Private: str.Append("private "); } } protected mixin BeginBody(CXCursor cursor) { fileInfo.prevEnd = GetCursorAnchor(cursor); fileInfo.queuedTokens |= .LSquirly; // [Friend] is needed in case you use BeginBody! in your code if (canChangeBlock) { defer:mixin { this.[Friend]canChangeBlock = true; } this.[Friend]canChangeBlock = false; } defer:mixin { if (this.[Friend]defferedWrapperWrite != null) { str.Append("\n\n", cursorIndent, "const String _wrapperText = \"\\n\"\n", this.[Friend]defferedWrapperWrite, ";"); delete this.[Friend]defferedWrapperWrite; this.[Friend]defferedWrapperWrite = null; } if (fileInfo.queuedTokens.HasFlag(.LSquirly)) { switch (cursor.kind) { case .StructDecl, .UnionDecl, .ClassDecl: str.Append(';'); default: str.Append(" {}"); } fileInfo.queuedTokens = .None; } else fileInfo.queuedTokens |= .RSquirly; } } const int int_maxDigits = scope $"{int.MaxValue:X}".Length + 1; protected virtual void CppWrapperName(CXCursor cursor, String outString) { String hashCode = ScopeCXString!(Clang.GetCursorUSR(cursor)) .GetHashCode().ToString(..scope .(int_maxDigits), "X", null); outString.Append("cpp2beef_"); if (hashCode.StartsWith('-')) hashCode[0] = 'm'; outString.Append('0', int_maxDigits - hashCode.Length); outString.Append(hashCode); } protected virtual enum { C, Cpp } Linkable_Attributes(CXCursor cursor) { let mangledName = ScopeCXString!(Clang.Cursor_GetMangling(cursor)); let name = GetNameInBindings(cursor, ..scope .(mangledName.Length)); WriteCustomAttributes(cursor); if (mangledName == name) { str.Append("[CLink] "); return .C; } else if (!mangledName.IsEmpty && mangledName[0].IsLetterOrDigit) { str.Append("[LinkName("); mangledName.QuoteString(str); str.Append(")] "); return .C; } else { str.Append("[LinkName(\""); CppWrapperName(cursor, str); str.Append('"'); if (!wrapperTemplateChain.IsEmpty) if (templateParams.IsEmpty) str.Append(" + __template_chain"); else str.Append(" + CppWrapperF($\"", wrapperTemplateChain, "\")"); str.Append(")] "); return .Cpp; } } protected virtual void Method_Parameters(CXCursor cursor) { let numArgs = Clang.Cursor_GetNumArguments(cursor); for (let i < numArgs) { if (i > 0) str.Append(", "); let arg = Clang.Cursor_GetArgument(cursor, (.)i); //AllWhiteSpaceUntil(Clang.GetCursorLocation(arg)); currentCursor = arg; bool isOutParam = IsOutParam(arg, cursor); Type(Clang.GetCursorType(arg), isOutParam ? .OutParam : .InParam); str.Append(' '); GetNameInBindings(arg, str); str.TrimEnd(); WriteTokensAfterEquals(arg); } if (Clang.Cursor_IsVariadic(cursor) != 0) { if (numArgs > 0) str.Append(", "); str.Append("..."); } currentCursor = cursor; } protected virtual void WriteTypeAndName(CXCursor cursor, CXType type, enum { Standard, TypeAlias, ConversionFunction, Ctor, Dtor } format = .Standard) { bool preserveColumns = Flags.HasFlag(.PreseveColumns); Flush(); if (preserveColumns) AllWhiteSpaceBetween( Clang.GetRangeStart(Clang.GetCursorExtent(cursor)), Clang.GetRangeStart(Clang.Cursor_GetSpellingNameRange(cursor, 0, 0)) ); if (format == .Ctor) { str.Append("this"); return; } String whitespace; if (preserveColumns) { whitespace = scope:: .(str); str.Clear(); Type(type); whitespace.Length -= Math.Min(str.Length, whitespace.Length); if (whitespace.IsEmpty) whitespace.Append(' '); } else { whitespace = " "; Type(type); } switch (format) { case .Standard: str.Append(whitespace); GetNameInBindings(cursor, str); str.Append(templateParams); case .TypeAlias: String typeStr = scope .(str); str.Clear(); str.Append(' '); GetNameInBindings(cursor, str); str.Append(templateParams, " = ", typeStr, ";"); if (preserveColumns) str.Append(whitespace); case .ConversionFunction: String typeStr = scope .(str); str.Clear(); str.Append("operator", whitespace, typeStr, templateParams); case .Ctor: Runtime.FatalError(); case .Dtor: str.Append(whitespace, "Dispose"); } } protected static mixin ScopeTokenize(CXCursor cursor, CXTranslationUnit unit) { let range = Clang.GetCursorExtent(cursor); CXToken* tokenPtr = null; uint32 tokenCount = 0; Clang.Tokenize(unit, range, &tokenPtr, &tokenCount); defer:mixin Clang.DisposeTokens(unit, tokenPtr, tokenCount); Span(tokenPtr, (.)tokenCount) } protected virtual void WriteTokensAfterEquals(CXCursor cursor) { var tokens = ScopeTokenize!(cursor, unit); CXSourceLocation prevEnd = Clang.GetNullLocation(); findEquals: do { for (let token in tokens) { let spelling = GetTokenSpelling!(token); if (spelling != "=") { prevEnd = Clang.GetRangeEnd(Clang.GetTokenExtent(unit, token)); continue; } tokens.RemoveFromStart(@token.Index); break findEquals; } return; } WriteTokens(tokens, prevEnd, .Identifier); } protected virtual void WriteToken(CXToken token) { let spelling = ScopeCXString!(Clang.GetTokenSpelling(unit, token)); let kind = Clang.GetTokenKind(token); token: switch (kind) { case .Literal: str.Append(spelling); if (str.EndsWith("LL")) str.Length--; case .Identifier: var cursor = Clang.GetCursor(unit, Clang.GetTokenLocation(unit, token)); if (Clang.Cursor_IsNull(cursor) != 0) fallthrough; cursor = Clang.GetCursorDefinition(cursor); if (Clang.Cursor_IsNull(cursor) != 0) fallthrough; GetNameInBindings(cursor, str); case .Punctuation, .Comment, .Keyword: str.Append(spelling); } } protected virtual void WriteTokens(Span tokens, CXSourceLocation prevTokenEnd, CXTokenKind prevKind) { var prevTokenEnd; for (let token in tokens) { let extent = Clang.GetTokenExtent(unit, token); AllWhiteSpaceBetween(prevTokenEnd, Clang.GetRangeStart(extent)); WriteToken(token); prevTokenEnd = Clang.GetRangeEnd(extent); } //TODO: move to custom utils /* // This formats the tokens instead of copying the format 1:1 const CXTokenKind Unary = (.)-1; var prevKind; for (let token in tokens) { let spelling = ScopeCXString!(Clang.GetTokenSpelling(unit, token)); let kind = Clang.GetTokenKind(token); token: switch (kind) { case .Keyword, .Identifier, .Literal: if (prevKind == .Keyword || prevKind == .Identifier || prevKind == .Punctuation) str.Append(' '); if (kind == .Identifier) do { var cursor = Clang.GetCursor(unit, Clang.GetTokenLocation(unit, token)); if (Clang.Cursor_IsNull(cursor) != 0) break; cursor = Clang.GetCursorDefinition(cursor); if (Clang.Cursor_IsNull(cursor) != 0) break; GetNameInBindings(cursor, str); break token; } str.Append(spelling); if (kind == .Literal && str.EndsWith("LL")) str.Length--; case .Punctuation: defer str.Append(spelling); if (prevKind != Unary && !(spelling == "(" && prevKind != .Punctuation) && spelling != ")") { if (spelling != ",") str.Append(' '); if (prevKind != .Punctuation) break; } prevKind = Unary; continue; case .Comment: } prevKind = kind; }*/ } protected virtual void Namespace(CXCursor cursor) { WriteCustomAttributes(cursor); str.Append("extension "); GetNameInBindings(cursor, str); { BeginBody!(cursor); Clang.VisitChildren(cursor, (cursor, parent, client_data) => { Self self = (.)Internal.UnsafeCastToObject(client_data); self.WriteCursor(cursor); return .Continue; }, Internal.UnsafeCastToPtr(this)); } if (Clang.Cursor_IsInlineNamespace(cursor) != 0) { GetIndentation(GetCursorAnchor(cursor), str); str.Append("public static using "); GetNameInBindings(cursor, str); str.Append(";\n"); } } protected virtual void WriteMethodWrapper(CXCursor cursor) { let parent = Clang.GetCursorType(Clang.GetCursorSemanticParent(cursor)); var resultType = Clang.GetCursorResultType(cursor); bool nonStatic = (cursor.kind == .CXXMethod && Clang.CXXMethod_IsStatic(cursor) == 0) || cursor.kind == .Destructor || cursor.kind == .ConversionFunction; if (!wrapperTemplateChain.IsEmpty) FlushWrapper(); StringView parentSpelling = "\" + __cpp_type + \""; if (wrapperTemplateChain.IsEmpty) parentSpelling = ScopeCXString!::(Clang.GetFullyQualifiedName(parent, printingPolicy, 0)); if (cursor.kind == .Constructor) wrapperBuf.Append("__type<", parentSpelling, "> "); else { wrapperBuf.Append("__type<"); wrapperBuf.Append(ScopeCXString!(Clang.GetFullyQualifiedName(resultType, printingPolicy, 0))); wrapperBuf.Append("> "); } CppWrapperName(cursor, wrapperBuf); if (!wrapperTemplateChain.IsEmpty) { if (templateParams.IsEmpty) wrapperBuf.Append("\" + __template_chain + \""); else wrapperBuf.Append("\" + CppWrapperF($\"", wrapperTemplateChain, "\") + \""); } wrapperBuf.Append('('); if (nonStatic) wrapperBuf.Append(parentSpelling, " *self"); for (int i < Clang.Cursor_GetNumArguments(cursor)) { let arg = Clang.Cursor_GetArgument(cursor, (.)i); if (nonStatic || i > 0) wrapperBuf.Append(", "); wrapperBuf.Append("__type<"); wrapperBuf.Append(ScopeCXString!(Clang.GetFullyQualifiedName(Clang.GetCursorType(arg), printingPolicy, 0))); wrapperBuf.Append("> p"); i.ToString(wrapperBuf); } wrapperBuf.Append(") { "); if (resultType.kind != .Void) wrapperBuf.Append("return "); if (nonStatic) { if (cursor.kind != .Destructor) wrapperBuf.Append("self->", GetCursorSpelling!(cursor)); else wrapperBuf.Append("self->~", parentSpelling); } else if (cursor.kind == .Constructor) wrapperBuf.Append(parentSpelling); else wrapperBuf.Append(fullCursorName); wrapperBuf.Append('('); for (int i < Clang.Cursor_GetNumArguments(cursor)) { if (i > 0) wrapperBuf.Append(", "); wrapperBuf.Append('p'); i.ToString(wrapperBuf); } wrapperBuf.Append("); }"); if (wrapperTemplateChain.IsEmpty) { wrapperBuf.Append('\n'); FlushWrapper(); } else { if (defferedWrapperWrite == null) defferedWrapperWrite = new .(1024); defferedWrapperWrite.Append(cursorIndent, "\t + \"extern \\\"C\\\" "); wrapperBuf.ToString(defferedWrapperWrite); defferedWrapperWrite.Append("\\n\"\n"); wrapperBuf.Clear(); } } protected virtual void FunctionDecl(CXCursor cursor) { if (Linkable_Attributes(cursor) == .Cpp) WriteMethodWrapper(cursor); AccessSpecifier(cursor); str.Append("static extern "); WriteTypeAndName(cursor, Clang.GetCursorResultType(cursor)); str.Append('('); Method_Parameters(cursor); str.Append(')'); str.Append(templateParamsWhere); str.Append(';'); } protected virtual void CXXMethod(CXCursor cursor) //TODO: conversion function { void Attributes() { if (Linkable_Attributes(cursor) == .Cpp) WriteMethodWrapper(cursor); } let spelling = GetCursorSpelling!(cursor); if (spelling.StartsWith("operator")) { if (spelling == "operator[]") { Runtime.Assert(templateParams.IsEmpty, "Properties can't have generics"); AccessSpecifier(cursor); if (Clang.CXXMethod_IsStatic(cursor) != 0) str.Append("static "); str.Append("extern "); Type(Clang.GetCursorResultType(cursor)); str.Append(" this["); Method_Parameters(cursor); str.Append("] { "); Attributes(); str.Append("get; }"); return; } Attributes(); AccessSpecifier(cursor); str.Append("static extern "); WriteTypeAndName(cursor, Clang.GetCursorResultType(cursor), cursor.kind == .ConversionFunction ? .ConversionFunction : .Standard); str.Append("(in Self, "); Method_Parameters(cursor); if (str.EndsWith(", ")) str.Length -= 2; str.Append(')'); str.Append(templateParamsWhere); str.Append(';'); return; } Attributes(); AccessSpecifier(cursor); if (Clang.CXXMethod_IsStatic(cursor) != 0) str.Append("static "); str.Append("extern "); switch (cursor.kind) { case .Constructor: WriteTypeAndName(cursor, default, .Ctor); case .Destructor: WriteTypeAndName(cursor, Clang.GetCursorResultType(cursor), .Dtor); case .CXXMethod: WriteTypeAndName(cursor, Clang.GetCursorResultType(cursor)); default: Runtime.FatalError("Unhandled c++ method kind"); } str.Append('('); Method_Parameters(cursor); str.Append(')'); if (Clang.CXXMethod_IsStatic(cursor) == 0 && Clang.CXXMethod_IsConst(cursor) == 0 && cursor.kind == .CXXMethod) str.Append(" mut"); str.Append(templateParamsWhere); 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, type); str.Append(';'); } protected virtual void VarDecl(CXCursor cursor) { WriteCustomAttributes(cursor); let type = Clang.GetCursorType(cursor); switch (Clang.GetCursorLinkage(cursor)) { case .Internal when Clang.IsConstQualifiedType(type) != 0: AccessSpecifier(cursor); str.Append("const "); WriteTypeAndName(cursor, type); WriteTokensAfterEquals(cursor); str.Append(';'); case .External, .UniqueExternal: Flush(); let linkLang = Linkable_Attributes(cursor); if (linkLang == .Cpp) str.Clear(); AccessSpecifier(cursor); str.Append("static extern "); if (linkLang == .Cpp && type.kind != .LValueReference && type.kind != .RValueReference) str.Append("ref "); WriteTypeAndName(cursor, type); if (linkLang == .Cpp) { let wrapperName = CppWrapperName(cursor, ..scope .()); str.Append(" { [LinkName(\"", wrapperName, "\")] get; }"); wrapperBuf.Append("__type<"); wrapperBuf.Append(ScopeCXString!(Clang.GetFullyQualifiedName(type, printingPolicy, 0))); wrapperBuf.Append("> "); if (type.kind != .LValueReference && type.kind != .RValueReference) wrapperBuf.Append('&'); wrapperBuf.Append(wrapperName, "() { return ", fullCursorName, "; }\n"); FlushWrapper(); } str.Append(';'); default: Runtime.FatalError(scope $"Unhandled var linkage: {_}"); } } public static bool HasVTable(CXCursor cursor) { return Clang.VisitChildren(cursor, (cursor, parent, client_data) => { switch (cursor.kind) { case .CXXBaseSpecifier: if (HasVTable(Clang.GetCursorDefinition(cursor))) return .Break; case .CXXMethod, .Constructor, .Destructor, .ConversionFunction: if (Clang.CXXMethod_IsVirtual(cursor) != 0) return .Break; default: } return .Continue; }, null) != 0; } protected virtual void Record(CXCursor cursor) { WriteCustomAttributes(cursor); switch (cursor.kind) { case .StructDecl, .ClassDecl: str.Append("[CRepr] "); case .UnionDecl: str.Append("[CRepr, Union] "); default: Runtime.FatalError("Unhandled record type"); } AccessSpecifier(cursor); str.Append("struct "); if (Clang.Cursor_IsAnonymous(cursor) == 0) GetNameInBindings(cursor, str); str.Append(templateParams); Clang.VisitChildren(cursor, (cursor, parent, client_data) => { if (cursor.kind != .Destructor) return .Continue; String str = (.)Internal.UnsafeCastToObject(client_data); str.Append(" : IDisposable"); return .Break; }, Internal.UnsafeCastToPtr(str)); str.Append(templateParamsWhere); BeginBody!(cursor); //TODO /*Clang.VisitChildren(cursor, (cursor, parent, client_data) => { Self self = (.)Internal.UnsafeCastToObject(client_data); switch (cursor.kind) { case .FieldDecl, .CXXMethod, .Constructor, .Destructor, .ConversionFunction, .FunctionDecl, .VarDecl, .FunctionTemplate, .ClassTemplate, .TypeAliasTemplateDecl, .StructDecl, .ClassDecl, .UnionDecl, .EnumDecl, .TypeAliasDecl, .TypedefDecl: self.WriteComments(cursor); return .Break; default: return .Continue; } }, Internal.UnsafeCastToPtr(this));*/ if (!wrapperTemplateChain.IsEmpty) { //WriteQueuedOpenSquirly(); str.Append("private const String __cpp_type = \"", GetCursorSpelling!(cursor), "<\""); Clang.VisitChildren(cursor, (cursor, parent, client_data) => { Self self = (.)Internal.UnsafeCastToObject(client_data); switch (cursor.kind) { case .TemplateTypeParameter: self.str.Append(" + CppTypeToC(typeof(", GetCursorSpelling!(cursor), "))"); case .NonTypeTemplateParameter: self.str.Append(" + CppConstValue<"); self.Type(Clang.GetCursorType(cursor)); self.str.Append(">(", GetCursorSpelling!(cursor), ")"); default: return .Continue; } self.str.Append(" + \", \""); return .Continue; }, Internal.UnsafeCastToPtr(this)); str.Length -= 3; str.Append(">\";\n", cursorIndent); str.Append("private const String __template_chain = CppWrapperF($\"", wrapperTemplateChain, "\");\n\n", cursorIndent); /*str.Append("[CppWriteToWrapper<\"", WrapperFilePath, "\">(\"\\ntemplate "); switch (cursor.kind) { case .StructDecl: str.Append("struct "); case . ClassDecl: str.Append("class " ); case . UnionDecl: str.Append("union " ); default: Runtime.FatalError(); } str.Append("\" + __cpp_type + \";\\n\")]\n\n", indent);*/ } bool firstBase = true; bool hasVTable = HasVTable(cursor); Clang.VisitCXXBaseClasses(Clang.GetCursorType(cursor), (cursor, client_data) => { (Self self, bool* firstBase, bool hasVTable) = *(.)client_data; Runtime.Assert(Clang.IsVirtualBase(cursor) == 0, "Virtual bases are not supported"); //self.WriteQueuedOpenSquirly(); if (*firstBase && hasVTable && !HasVTable(Clang.GetCursorDefinition(cursor))) self.str.Append(self.cursorIndent, "private void* cppVtable;\n"); self.str.Append(self.cursorIndent); self.AccessSpecifier(cursor); self.str.Append("using "); self.Type(Clang.GetCursorType(cursor)); self.str.Append(";\n"); *firstBase = false; return .Continue; }, &(this, &firstBase, hasVTable)); if (firstBase && hasVTable) str.Append(cursorIndent, "private void* cppVtable;\n"); 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) { WriteCustomAttributes(cursor); AccessSpecifier(cursor); str.Append("enum "); if (Clang.Cursor_IsAnonymous(cursor) == 0) GetNameInBindings(cursor, str); str.Append(templateParams); if (Clang.VisitChildren(cursor, (cursor, parent, client_data) => { if (cursor.kind == .PackedAttr) return .Break; return .Continue; }, null) == 0) { str.Append(" : "); Type(Clang.GetEnumDeclIntegerType(cursor)); str.Append(templateParamsWhere); } BeginBody!(cursor); Clang.VisitChildren(cursor, (cursor, parent, client_data) => { Self self = (.)Internal.UnsafeCastToObject(client_data); Runtime.Assert(cursor.kind == .EnumConstantDecl); self.BeginCursor(cursor); self.WriteCustomAttributes(cursor); self.GetNameInBindings(cursor, self.str); self.WriteTokensAfterEquals(cursor); //Clang.GetEnumConstantDeclValue(cursor).ToString(self.str); self.str.Append(','); return .Continue; }, Internal.UnsafeCastToPtr(this)); } protected virtual void TypeAlias(CXCursor cursor) { CXType type; switch (cursor.kind) { case .TypedefDecl: type = Clang.GetTypedefDeclUnderlyingType(cursor); case .TypeAliasDecl: type = Clang.GetCursorType(cursor); default: Runtime.FatalError("Unhandled type alias cursor kind"); } if (type.kind == .Pointer) do { let pointee = Clang.GetPointeeType(type); if (pointee.kind != .FunctionProto && pointee.kind != .FunctionNoProto) break; WriteCustomAttributes(cursor); AccessSpecifier(cursor); str.Append("function "); WriteTypeAndName(cursor, Clang.GetResultType(pointee)); WriteFunctionProtoParams(pointee, ScopeTokenize!(cursor, unit)); str.Append(templateParamsWhere); str.Append(';'); return; } WriteCustomAttributes(cursor); AccessSpecifier(cursor); str.Append("typealias"); WriteTypeAndName(cursor, type, .TypeAlias); Runtime.Assert(templateParamsWhere.IsEmpty, "type aliases can't have constraints (yet)"); } protected virtual void MacroDefinition(CXCursor cursor) { let tokens = ScopeTokenize!(cursor, unit); WriteCustomAttributes(cursor); str.Append("public const let "); GetNameInBindings(cursor, str); AllWhiteSpaceBetween(Clang.GetRangeEnd(Clang.GetTokenExtent(unit, tokens[0])), Clang.GetRangeStart(Clang.GetTokenExtent(unit, tokens[1]))); str.Append("= "); WriteTokens(tokens[1...], Clang.GetNullLocation(), .Punctuation); str.Append(';'); } }