diff --git a/src/AST/Enumeration.cs b/src/AST/Enumeration.cs index 94a651f0..339551e8 100644 --- a/src/AST/Enumeration.cs +++ b/src/AST/Enumeration.cs @@ -36,6 +36,18 @@ namespace CppSharp.AST } } + public Item() + { + + } + + public Item(Item item) : base(item) + { + Value = item.Value; + Expression = item.Expression; + ExplicitValue = item.ExplicitValue; + } + public override T Visit(IDeclVisitor visitor) { return visitor.VisitEnumItemDecl(this); diff --git a/src/Generator.Tests/Passes/TestPasses.cs b/src/Generator.Tests/Passes/TestPasses.cs index 5e46c204..6eb251d5 100644 --- a/src/Generator.Tests/Passes/TestPasses.cs +++ b/src/Generator.Tests/Passes/TestPasses.cs @@ -109,15 +109,16 @@ namespace CppSharp.Generator.Tests.Passes Assert.IsNotNull(@enum); // Testing the values read for the enum - Assert.AreEqual(0, @enum.Items[0].Value); // Decimal literal - Assert.AreEqual(1, @enum.Items[1].Value); // Hex literal - Assert.AreEqual(2, @enum.Items[2].Value); // Hex literal with suffix + Assert.AreEqual(3, @enum.Items[0].Value); // Decimal literal + Assert.AreEqual(0, @enum.Items[1].Value); // Hex literal + Assert.AreEqual(1, @enum.Items[2].Value); // Hex literal with suffix + Assert.AreEqual(2, @enum.Items[3].Value); // Enum item passBuilder.RemovePrefix("TEST_ENUM_ITEM_NAME_", RenameTargets.EnumItem); passBuilder.AddPass(new CleanInvalidDeclNamesPass()); passBuilder.RunPasses(pass => pass.VisitASTContext(AstContext)); - Assert.That(@enum.Items[0].Name, Is.EqualTo("_0")); + Assert.That(@enum.Items[1].Name, Is.EqualTo("_0")); } [Test] diff --git a/src/Generator/Library.cs b/src/Generator/Library.cs index a720e382..84395aec 100644 --- a/src/Generator/Library.cs +++ b/src/Generator/Library.cs @@ -6,6 +6,7 @@ using System.Text.RegularExpressions; using CodingSeb.ExpressionEvaluator; using CppSharp.AST; using CppSharp.Generators; +using CppSharp.Generators.CSharp; using CppSharp.Passes; namespace CppSharp @@ -98,67 +99,105 @@ namespace CppSharp { Name = macro.Name, Expression = macro.Expression, - Value = ParseMacroExpression(macro.Expression, @enum), + Value = ParseEnumItemMacroExpression(macro, @enum), Namespace = @enum }; } - static bool ParseToNumber(string num, Enumeration @enum, out long val) + static bool EvaluateEnumExpression(string expr, Enumeration @enum, out object eval) { - //ExpressionEvaluator does not work with hex - if (num.StartsWith("0x", StringComparison.CurrentCultureIgnoreCase)) - { - num = num.Substring(2); - - // This is in case the literal contains suffix - num = Regex.Replace(num, "(?i)[ul]*$", String.Empty); - - return long.TryParse(num, NumberStyles.HexNumber, - CultureInfo.CurrentCulture, out val); - } + var evaluator = new ExpressionEvaluator { Variables = new Dictionary() }; - ExpressionEvaluator evaluator = new ExpressionEvaluator(); - //Include values of past items - evaluator.Variables = new Dictionary(); - foreach (Enumeration.Item item in @enum.Items) + // Include values of past items + foreach (var item in @enum.Items) { - //ExpressionEvaluator is requires lowercase variables - evaluator.Variables.Add(item.Name.ToLower(), item.Value); + evaluator.Variables.Add(item.Name, item.Value); } + try { - var ret = evaluator.Evaluate("(long)" + num.ReplaceLineBreaks(" ").Replace('\\',' ')); - val = (long)ret; + expr = expr.ReplaceLineBreaks(" ").Replace('\\', ' '); + eval = evaluator.Evaluate(expr); return true; } - catch (ExpressionEvaluatorSyntaxErrorException) + catch (Exception ex) { - val = 0; - return false; + throw new Exception($"Failed to evaluate expression: {ex.Message}"); } } - static ulong ParseMacroExpression(string expression, Enumeration @enum) + static ulong ParseEnumItemMacroExpression(MacroDefinition macro, Enumeration @enum) { + var expression = macro.Expression; + // TODO: Handle string expressions if (expression.Length == 3 && expression[0] == '\'' && expression[2] == '\'') // '0' || 'A' - return expression[1]; // return the ASCI code of this character + return expression[1]; // return the ASCII code of this character + + object eval; + try + { + EvaluateEnumExpression(expression, @enum, out eval); + } + catch (Exception) + { + // TODO: This should just throw, but we have a pre-existing behavior that expects malformed + // macro expressions to default to 0, see CSharp.h (MY_MACRO_TEST2_0), so do it for now. + return 0; + } + + if (eval is ulong number) + return number; - long val; - return ParseToNumber(expression, @enum, out val) ? (ulong)val : 0; + unchecked + { + ulong val = ((ulong) (int) eval); + return val; + } } - public static Enumeration GenerateEnumFromMacros(this ASTContext context, string name, - params string[] macros) + class CollectEnumItems : AstVisitor { - var @enum = new Enumeration { Name = name }; + public List Items; + private Regex regex; - var regexPattern = string.Join("|", macros.Select(pattern => $"({pattern}$)")); - var regex = new Regex(regexPattern); + public CollectEnumItems(Regex regex) + { + Items = new List(); + this.regex = regex; + } + + public override bool VisitEnumItemDecl(Enumeration.Item item) + { + if (AlreadyVisited(item)) + return true; + + // Prevents collecting previously generated items when generating enums from macros. + if (item.IsImplicit) + return true; + + var match = regex.Match(item.Name); + if (match.Success) + Items.Add(item); + + return true; + } + } + public static List FindMatchingEnumItems(this ASTContext context, Regex regex) + { + var collector = new CollectEnumItems(regex); + foreach (var unit in context.TranslationUnits) + collector.VisitTranslationUnit(unit); + + return collector.Items; + } + + public static List FindMatchingMacros(this ASTContext context, + Regex regex, ref TranslationUnit unitToAttach) + { int maxItems = 0; - TranslationUnit unitToAttach = null; - ulong maxValue = 0; + var macroDefinitions = new List(); foreach (var unit in context.TranslationUnits) { @@ -167,20 +206,16 @@ namespace CppSharp foreach (var macro in unit.PreprocessedEntities.OfType()) { var match = regex.Match(macro.Name); - if (!match.Success) continue; - - if (macro.Enumeration != null) + if (!match.Success) continue; - // Skip this macro if the enum already has an item with same name. - if (@enum.Items.Exists(it => it.Name == macro.Name)) - continue; + // if (macro.Enumeration != null) + // continue; - var item = @enum.GenerateEnumItemFromMacro(macro); - @enum.AddItem(item); - macro.Enumeration = @enum; + if (macroDefinitions.Exists(m => macro.Name == m.Name)) + continue; - maxValue = Math.Max(maxValue, item.Value); + macroDefinitions.Add(macro); numItems++; } @@ -191,15 +226,57 @@ namespace CppSharp } } - if (@enum.Items.Count > 0) + return macroDefinitions; + } + + public static Enumeration GenerateEnumFromMacros(this ASTContext context, string name, + params string[] values) + { + TranslationUnit GetUnitFromItems(List list) + { + var units = list.Select(item => item.TranslationUnit) + .GroupBy(x => x) + .Select(y => new {Element = y.Key, Counter = y.Count()}); + + var translationUnit = units.OrderByDescending(u => u.Counter) + .FirstOrDefault()?.Element ?? null; + + return translationUnit; + } + + var @enum = new Enumeration { Name = name }; + + var regexPattern = string.Join("|", values.Select(pattern => $"({pattern}$)")); + var regex = new Regex(regexPattern); + + var items = FindMatchingEnumItems(context, regex); + foreach (var item in items) + @enum.AddItem(item); + + TranslationUnit macroUnit = null; + var macroDefs = FindMatchingMacros(context, regex, ref macroUnit); + foreach (var macro in macroDefs) { - @enum.BuiltinType = new BuiltinType(GetUnderlyingTypeForEnumValue(maxValue)); - @enum.Type = @enum.BuiltinType; - @enum.Namespace = unitToAttach; + // Skip this macro if the enum already has an item with same name. + if (@enum.Items.Exists(it => it.Name == macro.Name)) + continue; - unitToAttach.Declarations.Add(@enum); + var item = @enum.GenerateEnumItemFromMacro(macro); + @enum.AddItem(item); + macro.Enumeration = @enum; } + if (@enum.Items.Count <= 0) + return @enum; + + var maxValue = @enum.Items.Max(i => i.Value); + @enum.BuiltinType = new BuiltinType(GetUnderlyingTypeForEnumValue(maxValue)); + @enum.Type = @enum.BuiltinType; + + var unit = macroUnit ?? GetUnitFromItems(items); + @enum.Namespace = unit; + unit.Declarations.Add(@enum); + return @enum; } @@ -223,6 +300,12 @@ namespace CppSharp if (maxValue < uint.MaxValue) return PrimitiveType.UInt; + if (maxValue < long.MaxValue) + return PrimitiveType.Long; + + if (maxValue < ulong.MaxValue) + return PrimitiveType.ULong; + throw new NotImplementedException(); } diff --git a/src/Generator/Utils/ExpressionEvaluator.cs b/src/Generator/Utils/ExpressionEvaluator.cs index 4e6937a0..27a93f2a 100644 --- a/src/Generator/Utils/ExpressionEvaluator.cs +++ b/src/Generator/Utils/ExpressionEvaluator.cs @@ -1577,6 +1577,17 @@ namespace CodingSeb.ExpressionEvaluator protected virtual bool EvaluateNumber(string expression, Stack stack, ref int i) { string restOfExpression = expression.Substring(i); + +// CPPSHARP + if (restOfExpression.StartsWith("0x", StringComparison.CurrentCultureIgnoreCase)) + { + // This is in case the literal contains suffix + var cleanedUp = Regex.Replace(restOfExpression, "(?i)[ul]*$", string.Empty); + i += restOfExpression.Length - cleanedUp.Length; + restOfExpression = cleanedUp; + } +// + Match numberMatch = Regex.Match(restOfExpression, numberRegexPattern, RegexOptions.IgnoreCase); Match otherBaseMatch = otherBasesNumberRegex.Match(restOfExpression); diff --git a/tests/Native/Passes.h b/tests/Native/Passes.h index 40d794c3..c7687806 100644 --- a/tests/Native/Passes.h +++ b/tests/Native/Passes.h @@ -42,6 +42,11 @@ struct TestReadOnlyProperties #define TEST_ENUM_ITEM_NAME_1 0x1 #define TEST_ENUM_ITEM_NAME_2 0x2U +enum +{ + TEST_ENUM_ITEM_NAME_3 = 3 +}; + // TestStructInheritance struct S1 { int F1, F2; }; struct S2 : S1 { int F3; };