public List <NamedConstruct> Run(CApiGeneratorOptions opts, IEnumerable <ClangTranslationUnit> tus) { var members = new List <NamedConstruct> (); ClassDeclaration current = null; CXXAccessSpecifier current_access_specifier = default(CXXAccessSpecifier); string current_namespace = ""; List <TypeParameter> current_type_parameters = null; foreach (var tu in tus) { for (int i = 0; i < tu.DiagnosticCount; i++) { Console.Error.WriteLine("[diag] " + tu.GetDiagnostic(i).Spelling); } Func <ClangCursor, ClangCursor, IntPtr, ChildVisitResult> func = null, doVisit = null; Exception error = null; func = (cursor, parent, clientData) => { try { return(doVisit(cursor, parent, clientData)); } catch (Exception ex) { error = ex; return(ChildVisitResult.Break); } }; doVisit = (cursor, parent, clientData) => { if (cursor.Location.FileLocation?.File?.FileName == null) { return(ChildVisitResult.Continue); // skip non-file input } if (!opts.ShouldParse(cursor.Location.FileLocation?.File?.FileName)) { return(ChildVisitResult.Continue); // skip unmatched files. } string id = cursor.Kind + " " + cursor.Location; if (processed.Contains(id)) { return(ChildVisitResult.Continue); } processed.Add(id); if (cursor.Kind == CursorKind.Namespace) { current_namespace = cursor.Spelling; cursor.VisitChildren(func, IntPtr.Zero); current_namespace = ""; return(ChildVisitResult.Continue); } else if (cursor.Kind == CursorKind.TypeAliasDeclaration || cursor.Kind == CursorKind.TypeAliasTemplateDecl || cursor.Kind == CursorKind.ClassTemplatePartialSpecialization || cursor.Kind == CursorKind.NonTypeTemplateParameter) { // no need to care. return(ChildVisitResult.Continue); } else if (cursor.Kind == CursorKind.CXXFinalAttribute) { // "class XXX final" return(ChildVisitResult.Continue); } else if (cursor.Kind == CursorKind.ClassDeclaration || cursor.Kind == CursorKind.ClassTemplate // FIXME: examine if they should apply here... || cursor.Kind == CursorKind.StructDeclaration || cursor.Kind == CursorKind.UnionDeclaration) { current_type_parameters = cursor.Kind == CursorKind.ClassTemplate ? new List <TypeParameter> () : null; current = new ClassDeclaration() { Namespace = current_namespace, Name = cursor.Spelling, TypeParameters = current_type_parameters, SourceFile = cursor.Location.FileLocation.File.FileName, Line = cursor.Location.FileLocation.Line, Column = cursor.Location.FileLocation.Column, }; members.Add(current); cursor.VisitChildren(func, IntPtr.Zero); current = null; return(ChildVisitResult.Continue); } else if (cursor.Kind == CursorKind.CXXAccessSpecifier) { current_access_specifier = cursor.CxxAccessSpecifier; } else if (cursor.Kind == CursorKind.CXXBaseSpecifier) { if (current != null) { current.BaseType = cursor.Spelling; } return(ChildVisitResult.Continue); } else if (cursor.Kind == CursorKind.TemplateTypeParameter) { Console.Error.WriteLine($" --- <{cursor.Spelling}> {cursor.TemplateCursorKind} | [{cursor.SpecializedCursorTemplate.Spelling}] at {cursor.Location.SpellingLocation}"); if (current_type_parameters != null) { current_type_parameters.Add(new TypeParameter { Name = cursor.Spelling, }); } } else if (cursor.Kind == CursorKind.FieldDeclaration || cursor.Kind == CursorKind.VarDeclaration) { var f = new Variable() { Access = current_access_specifier, Kind = cursor.Kind, Type = cursor.CursorType.Spelling, ArraySize = cursor.CursorType.ArraySize, SizeOf = cursor.CursorType.SizeOf, IsStatic = cursor.IsCxxStatic, //FIXME: it doesn't work IsConst = cursor.IsCxxConst, //FIXME: it doesn't work Namespace = current_namespace, Name = cursor.Spelling, SourceFile = cursor.Location.FileLocation.File.FileName, Line = cursor.Location.FileLocation.Line, Column = cursor.Location.FileLocation.Column, }; if (current != null) { current.Fields.Add(f); return(ChildVisitResult.Continue); } else if (cursor.SemanticParent == null || (cursor.SemanticParent.Kind != CursorKind.ClassDeclaration && cursor.SemanticParent.Kind != CursorKind.ClassTemplate)) { members.Add(f); return(ChildVisitResult.Continue); } } else if (cursor.Kind == CursorKind.CXXMethod || cursor.Kind == CursorKind.ConversionFunction || cursor.Kind == CursorKind.FunctionDeclaration || cursor.Kind == CursorKind.FunctionTemplate) { current_type_parameters = cursor.Kind == CursorKind.FunctionTemplate ? new List <TypeParameter> () : null; int ac = 0; var f = new Function() { Namespace = current_namespace, Name = cursor.Spelling, Kind = cursor.Kind, TypeParameters = Enumerable.Range(0, (ac = cursor.TemplateArgumentCount) < 0 ? 0 : ac) .Select(i => cursor.GetTemplateArgumentType(i)) .Select(a => new TypeParameter { Name = a.Spelling }) .ToList(), Access = current_access_specifier, Return = cursor.ResultType.Spelling, IsVirtual = cursor.IsCxxVirtual, IsPureVirtual = cursor.IsCxxPureVirtual, SourceFile = cursor.Location.FileLocation.File.FileName, Line = cursor.Location.FileLocation.Line, Column = cursor.Location.FileLocation.Column, // wait, FunctionTemplate comes with no arguments?? Parameters = Enumerable.Range(0, (ac = cursor.ArgumentCount) < 0 ? 0 : ac) .Select(i => cursor.GetArgument(i)) .Select(a => new Variable() { Name = a.Spelling, Type = a.CursorType.Spelling, }) .ToArray() }; if (current != null) { current.Functions.Add(f); } else if (cursor.SemanticParent == null || (cursor.SemanticParent.Kind != CursorKind.ClassDeclaration && cursor.SemanticParent.Kind != CursorKind.ClassTemplate)) { members.Add(f); } // otherwise it's a member definition return(ChildVisitResult.Continue); } else if (cursor.Kind == CursorKind.Constructor) { var f = new Function() { Name = cursor.Spelling, Access = current_access_specifier, Kind = cursor.Kind, Return = null, SourceFile = cursor.Location.FileLocation.File.FileName, Line = cursor.Location.FileLocation.Line, Column = cursor.Location.FileLocation.Column, Parameters = Enumerable.Range(0, cursor.ArgumentCount) .Select(i => cursor.GetArgument(i)) .Select(a => new Variable() { Name = a.Spelling, Type = a.CursorType.Spelling, }) .ToArray() }; if (current != null) { current.Constructors.Add(f); } else if (cursor.SemanticParent == null || (cursor.SemanticParent.Kind != CursorKind.ClassDeclaration && cursor.SemanticParent.Kind != CursorKind.ClassTemplate)) { members.Add(f); } // otherwise it's a member definition return(ChildVisitResult.Continue); } else { switch (cursor.Kind) { case CursorKind.TypedefDeclaration: case CursorKind.Destructor: case CursorKind.StructDeclaration: case CursorKind.FriendDecl: case CursorKind.EnumDeclaration: // they are safe to ignore return(ChildVisitResult.Continue); default: Console.Error.WriteLine($"{cursor.Location.SpellingLocation} Unhandled token: [{cursor.Kind}] {cursor.Spelling}"); break; } } return(ChildVisitResult.Continue); }; tu.GetCursor().VisitChildren(func, IntPtr.Zero); if (error != null) { throw error; } } return(members); }
void Run(string [] args) { var idx = ClangService.CreateIndex(); var tus = new List <ClangTranslationUnit> (); TextWriter output = Console.Out; var opts = new CApiGeneratorOptions(); // We are going to parse C++ sources. opts.ClangArgs.Add("-x"); opts.ClangArgs.Add("c++"); opts.ClangArgs.Add("-std=c++1y"); foreach (var arg in args) { if (arg == "--help" || arg == "-?") { Console.Error.WriteLine($"[USAGE] {GetType ().Assembly.GetName ().CodeBase} [options] [inputs]"); Console.Error.WriteLine(@"options: --out:[filename] output source file name. --lib:[library] library name specified on [DllImport]. --match:[regex] process only matching files to [regex]. --arg:[namespace] compiler arguments to parse the sources." ); return; } else if (arg.StartsWith("--out:", StringComparison.Ordinal)) { output = File.CreateText(arg.Substring(6)); } else if (arg.StartsWith("--lib:", StringComparison.Ordinal)) { opts.LibraryName = arg.Substring(6); } else if (arg.StartsWith("--arg:", StringComparison.Ordinal)) { opts.ClangArgs.Add(arg.Substring(6)); } else if (arg == "--only-explicit") { opts.OnlyExplicit = true; } else if (arg.StartsWith("--match:", StringComparison.Ordinal)) { opts.FileMatches.Add(arg.Substring(8)); } else if (arg.Contains(Path.DirectorySeparatorChar) || arg.Contains(Path.AltDirectorySeparatorChar)) { foreach (var file in Directory.GetFiles(Path.GetDirectoryName(arg), Path.GetFileName(arg))) { opts.Sources.Add(file); } } else { opts.Sources.Add(arg); } } foreach (var source in opts.Sources) { if (!File.Exists(source)) { throw new ArgumentException("File not found: " + source); } tus.Add(idx.ParseTranslationUnit(source, opts.ClangArgs.ToArray(), null, TranslationUnitFlags.None)); } var members = new HeaderParser().Run(opts, tus); new CCodeWriter().Write(output, members, opts); output.Close(); }
public void Write(TextWriter output, IList <NamedConstruct> members, CApiGeneratorOptions opts) { output.WriteLine("/*****************************************************************"); output.WriteLine(" This file is automatically generated by nclang CApiGenerator.exe."); output.WriteLine(" *****************************************************************/"); foreach (var source in opts.Sources) { output.WriteLine("#include \"" + source + "\""); } output.WriteLine("extern \"C\" {"); // forward decls. foreach (var o in members.OfType <ClassDeclaration> ()) { if (!opts.ShouldGenerateCodeFor(o)) { continue; // skip unmatched file } output.WriteLine($"class {o.CTypeName ()} : {o.Namespace}{o.nssep}{o.Name}; // {o.SourceFileName} ({o.Line}, {o.Column})"); output.WriteLine(); } foreach (var o in members.OfType <ClassDeclaration> ()) { if (!opts.ShouldGenerateCodeFor(o)) { continue; // skip unmatched file } output.WriteLine($"// {o.Name} Constructors and destructor."); if (!o.Constructors.Any()) { output.WriteLine($"{o.CTypeName ()}* {o.CName ()}_new () {{ return new {o.CTypeName ()} (); }}"); } foreach (var c in o.Constructors) { var pdefs = string.Join(", ", c.Parameters.Select(p => p.Type + " " + p.Name)); var pcalls = string.Join(", ", c.Parameters.Select(p => p.Name)); output.WriteLine($"void* {o.CName ()}_new ({pdefs}) {{ return new {o.CTypeName ()} ({pcalls}); }}"); } output.WriteLine($"void {o.CName ()}_delete ({o.CTypeName ()} *instance) {{ delete instance; }}"); output.WriteLine(); var targetFields = o.Fields.Where(f => f.Access != CXXAccessSpecifier.Private); if (targetFields.Any()) { output.WriteLine($"// {o.Name} Field accessors"); } foreach (var f in targetFields) { if (f.IsStatic || f.IsConst) { // static/const output.WriteLine($"{f.CTypeName ()} {o.CName ()}_get_{f.CName ()} () {{ return {o.Name}::{f.Name}; }}"); if (!f.IsConst) { output.WriteLine($"void {o.CName ()}_set_{f.CName ()} ({f.CTypeName ()} value) {{ {o.Name}::{f.Name} = value; }}"); } } else { // instance output.WriteLine($"{f.CTypeName ()} {o.CName ()}_get_{f.CName ()} ({o.CName ()} *instance) {{ return (({o.Name}*)instance)->{f.Name}; }}"); output.WriteLine($"void {o.CName ()}_set_{f.CName ()} ({o.CName ()} *instance, {f.Type} value) {{ (({o.Name}*)instance)->{f.Name} = value; }}"); } output.WriteLine(); } var targetFunctions = o.Functions.Where(f => f.Access != CXXAccessSpecifier.Private); if (targetFunctions.Any()) { output.WriteLine($"// {o.Name} Function delegates"); } foreach (var f in targetFunctions) { // static/const string optComma = f.Parameters.Any() ? ", " : ""; output.Write($"{f.CTypeReturn ()} {o.CName ()}_{f.CName ()} ({o.CTypeName ()} *instance{optComma} {string.Join (", ", f.Parameters.Select (a => a.ToTypeAndName ()))}) "); string retspec = f.Return == "void" ? string.Empty : "return "; output.WriteLine($"{{ {retspec}(({o.Name}*)instance)->{f.Name} ({string.Join (", ", f.Parameters.Select (a => a.Name))}); }}"); } output.WriteLine(); } foreach (var o in members.OfType <Variable> ()) { if (!opts.ShouldGenerateCodeFor(o)) { continue; // skip unmatched file } output.WriteLine(); } foreach (var o in members.OfType <Function> ().Where(m => m.Kind != CursorKind.Constructor)) { if (!opts.ShouldGenerateCodeFor(o)) { continue; // skip unmatched file } o.Write(output); output.WriteLine(); } output.WriteLine("} // extern \"C\""); output.Close(); }