From 06218ec48a85957532821bd6801c0c6c365fc130 Mon Sep 17 00:00:00 2001 From: Elias Holzer Date: Thu, 8 May 2014 16:04:06 +0200 Subject: [PATCH 1/2] Extended the ObjectOverridesPass to generate ToString methods if the insertion operator (<<) is overloaded. --- src/Generator/Generators/CLI/CLIGenerator.cs | 4 + src/Generator/Options.cs | 7 ++ src/Generator/Passes/ObjectOverridesPass.cs | 115 ++++++++++++++++++- tests/CLITemp/CLITemp.Tests.cs | 9 ++ tests/CLITemp/CLITemp.cs | 1 + tests/CLITemp/CLITemp.h | 23 +++- 6 files changed, 156 insertions(+), 3 deletions(-) diff --git a/src/Generator/Generators/CLI/CLIGenerator.cs b/src/Generator/Generators/CLI/CLIGenerator.cs index 23d91c68..5241b3ec 100644 --- a/src/Generator/Generators/CLI/CLIGenerator.cs +++ b/src/Generator/Generators/CLI/CLIGenerator.cs @@ -32,6 +32,10 @@ namespace CppSharp.Generators.CLI public override bool SetupPasses() { + // Note: The ToString override will only work if this pass runs + // after the MoveOperatorToCallPass. + if (Driver.Options.GenerateObjectOverrides) + Driver.TranslationUnitPasses.AddPass(new ObjectOverridesPass()); return true; } } diff --git a/src/Generator/Options.cs b/src/Generator/Options.cs index c1a1d93a..f530a8d0 100644 --- a/src/Generator/Options.cs +++ b/src/Generator/Options.cs @@ -127,6 +127,13 @@ namespace CppSharp /// public bool GenerateConversionOperators; + /// + /// If set to true the CLI generator will use ObjectOverridesPass to create + /// Equals, GetHashCode and (if the insertion operator << is overloaded) ToString + /// methods. + /// + public bool GenerateObjectOverrides; + //List of include directories that are used but not generated public List NoGenIncludeDirs; diff --git a/src/Generator/Passes/ObjectOverridesPass.cs b/src/Generator/Passes/ObjectOverridesPass.cs index 0b67d4e0..9ae4b40f 100644 --- a/src/Generator/Passes/ObjectOverridesPass.cs +++ b/src/Generator/Passes/ObjectOverridesPass.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using CppSharp.AST; using CppSharp.Generators; using CppSharp.Generators.CLI; @@ -7,10 +8,17 @@ using CppSharp.Passes; namespace CppSharp { + // This pass adds Equals and GetHashCode methods to classes. + // It will also add a ToString method if the insertion operator (<<) + // of the class is overloaded. + // Note that the OperatorToClassPass needs to run first in order for + // this to work. public class ObjectOverridesPass : TranslationUnitPass { + private bool needsStreamInclude; private void OnUnitGenerated(GeneratorOutput output) { + needsStreamInclude = false; foreach (var template in output.Templates) { foreach (var block in template.FindBlocks(CLIBlockKind.MethodBody)) @@ -18,6 +26,20 @@ namespace CppSharp var method = block.Declaration as Method; VisitMethod(method, block); } + if (needsStreamInclude) + { + var sourcesTemplate = template as CLISourcesTemplate; + if (sourcesTemplate != null) + { + foreach (var block in sourcesTemplate.FindBlocks(CLIBlockKind.Includes)) + { + block.WriteLine("#include "); + block.WriteLine(""); + break; + } + break; + } + } } } @@ -33,6 +55,9 @@ namespace CppSharp if (method.Name == "Equals" && method.Parameters.Count == 1) GenerateEquals(@class, block, method); + + if (method.Name == "ToString" && method.Parameters.Count == 0) + GenerateToString(@class, block, method); } void GenerateGetHashCode(Block block) @@ -54,6 +79,14 @@ namespace CppSharp block.Write("return __Instance == obj->__Instance;"); } + void GenerateToString(Class @class, Block block, Method method) + { + needsStreamInclude = true; + block.WriteLine("std::ostringstream os;"); + block.WriteLine("os << *NativePtr;"); + block.Write("return clix::marshalString(os.str());"); + } + private bool isHooked; public override bool VisitClassDecl(Class @class) { @@ -67,6 +100,33 @@ namespace CppSharp if (!VisitDeclaration(@class)) return false; + foreach (var method in @class.Methods) + { + if (!IsInsertionOperator(method)) + continue; + + // Create the ToString method + var stringType = GetType("std::string"); + if (stringType == null) + stringType = new CILType(typeof(string)); + var toStringMethod = new Method() + { + Name = "ToString", + Namespace = @class, + ReturnType = new QualifiedType(stringType), + IsSynthetized = true, + IsOverride = true, + IsProxy = true + }; + + @class.Methods.Add(toStringMethod); + + Driver.Diagnostics.Debug("Function converted to ToString: {0}::{1}", + @class.Name, method.Name); + + break; + } + if (@class.IsValueType) return false; @@ -86,8 +146,8 @@ namespace CppSharp IsOverride = true, IsProxy = true }; - @class.Methods.Add(methodEquals); - + @class.Methods.Add(methodEquals); + var methodHashCode = new Method { Name = "GetHashCode", @@ -101,5 +161,56 @@ namespace CppSharp @class.Methods.Add(methodHashCode); return true; } + + private Dictionary typeCache; + protected AST.Type GetType(string typeName) + { + if (typeCache == null) + typeCache = new Dictionary(); + AST.Type result; + if (!typeCache.TryGetValue(typeName, out result)) + { + var typeDef = Driver.ASTContext.FindTypedef(typeName) + .FirstOrDefault(); + if (typeDef != null) + result = new TypedefType() { Declaration = typeDef }; + typeCache.Add(typeName, result); + } + return result; + } + + private bool IsInsertionOperator(Method method) + { + // Do some basic check + if (!method.IsOperator) + return false; + if (method.OperatorKind != CXXOperatorKind.LessLess) + return false; + if (method.Parameters.Count != 2) + return false; + + // Check that first parameter is a std::ostream& + var fstType = method.Parameters[0].Type as PointerType; + if (fstType == null) + return false; + var oStreamType = GetType("std::ostream"); + if (oStreamType == null) + return false; + if (!oStreamType.Equals(fstType.Pointee)) + return false; + + // Check that second parameter is a const CLASS& + var sndType = method.Parameters[1].Type as PointerType; + if (sndType == null) + return false; + if (!sndType.QualifiedPointee.Qualifiers.IsConst) + return false; + var @class = method.Namespace as Class; + var classType = new TagType(@class); + if (!classType.Equals(sndType.Pointee)) + return false; + + return true; + } } } diff --git a/tests/CLITemp/CLITemp.Tests.cs b/tests/CLITemp/CLITemp.Tests.cs index d3dc0605..9870f259 100644 --- a/tests/CLITemp/CLITemp.Tests.cs +++ b/tests/CLITemp/CLITemp.Tests.cs @@ -1,6 +1,7 @@ using CppSharp.Utils; using NUnit.Framework; using CLITemp; +using System; public class CLITests : GeneratorTestFixture { @@ -11,4 +12,12 @@ public class CLITests : GeneratorTestFixture var sum = new Types().AttributedSum(3, 4); Assert.That(sum, Is.EqualTo(7)); } + + [Test] + public void TestToStringOverride() + { + var date = new Date(24, 12, 1924); + var s = date.ToString(); + Assert.AreEqual("24/12/1924", s); + } } \ No newline at end of file diff --git a/tests/CLITemp/CLITemp.cs b/tests/CLITemp/CLITemp.cs index 4e13a56a..71a42f17 100644 --- a/tests/CLITemp/CLITemp.cs +++ b/tests/CLITemp/CLITemp.cs @@ -14,6 +14,7 @@ namespace CppSharp.Tests public override void Setup(Driver driver) { driver.Options.GenerateFinalizers = true; + driver.Options.GenerateObjectOverrides = true; base.Setup(driver); } diff --git a/tests/CLITemp/CLITemp.h b/tests/CLITemp/CLITemp.h index de46ecc7..8d612e51 100644 --- a/tests/CLITemp/CLITemp.h +++ b/tests/CLITemp/CLITemp.h @@ -1,5 +1,7 @@ #include "../Tests.h" +#include + // Tests for C++ types struct DLL_API Types { @@ -23,4 +25,23 @@ class DLL_API TestProtectedDestructors ~TestProtectedDestructors(); }; -TestProtectedDestructors::~TestProtectedDestructors() {} \ No newline at end of file +TestProtectedDestructors::~TestProtectedDestructors() {} + +// Tests the insertion operator (<<) to ToString method pass +class DLL_API Date +{ +public: + Date(int m, int d, int y) + { + mo = m; da = d; yr = y; + } + // Not picked up by parser yet + //friend std::ostream& operator<<(std::ostream& os, const Date& dt); + int mo, da, yr; +}; + +std::ostream& operator<<(std::ostream& os, const Date& dt) +{ + os << dt.mo << '/' << dt.da << '/' << dt.yr; + return os; +} From 1cab6e5f026420a78265d3fd5fe936b6e044ad25 Mon Sep 17 00:00:00 2001 From: Elias Holzer Date: Tue, 13 May 2014 14:30:53 +0200 Subject: [PATCH 2/2] Do not generate ToString override for value types - not handled properly in generated code yet. --- src/Generator/Passes/ObjectOverridesPass.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Generator/Passes/ObjectOverridesPass.cs b/src/Generator/Passes/ObjectOverridesPass.cs index 9ae4b40f..c734b53b 100644 --- a/src/Generator/Passes/ObjectOverridesPass.cs +++ b/src/Generator/Passes/ObjectOverridesPass.cs @@ -100,6 +100,11 @@ namespace CppSharp if (!VisitDeclaration(@class)) return false; + // We can't handle value types yet + // The generated code assumes that a NativePtr is available + if (@class.IsValueType) + return false; + foreach (var method in @class.Methods) { if (!IsInsertionOperator(method)) @@ -127,9 +132,6 @@ namespace CppSharp break; } - if (@class.IsValueType) - return false; - var methodEqualsParam = new Parameter { Name = "object",