Browse Source

Enhanced ExpressionEvaluator to return implicitly typed uint, long, and ulong values from EvaluateNumber when there is no literal type suffix applied to a numeric expression string.

Changed Library.GenerateEnumFromMacro and friends to track the primitive type of the Enumeration while building an Enumeration's list of items so that we can support negative items within an Enumeration.

Fixed bugs in GetUnderlyingTypeForEnumValue where "<" should have been "<=" and Long should have been LongLong. Extended it to support negative values.

Added tests.
pull/1612/head
Joe Hull 4 years ago committed by João Matos
parent
commit
03bf194b63
  1. 194
      src/Generator.Tests/Passes/TestPasses.cs
  2. 188
      src/Generator/Library.cs
  3. 48
      src/Generator/Utils/ExpressionEvaluator.cs
  4. 2
      tests/CSharp/CSharp.Gen.cs
  5. 20
      tests/CSharp/CSharp.Tests.cs
  6. 8
      tests/CSharp/CSharp.h
  7. 30
      tests/Native/Passes.h

194
src/Generator.Tests/Passes/TestPasses.cs

@ -1,8 +1,14 @@ @@ -1,8 +1,14 @@
using CppSharp.AST;
using CodingSeb.ExpressionEvaluator;
using CppSharp.AST;
using CppSharp.Generators.CSharp;
using CppSharp.Passes;
using NUnit.Framework;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
namespace CppSharp.Generator.Tests.Passes
{
@ -84,7 +90,7 @@ namespace CppSharp.Generator.Tests.Passes @@ -84,7 +90,7 @@ namespace CppSharp.Generator.Tests.Passes
passBuilder.AddPass(new CleanCommentsPass());
passBuilder.RunPasses(pass => pass.VisitDeclaration(c));
var para = (ParagraphComment) c.Comment.FullComment.Blocks[0];
var para = (ParagraphComment)c.Comment.FullComment.Blocks[0];
var textGenerator = new TextGenerator();
textGenerator.Print(para, CommentKind.BCPLSlash);
@ -135,6 +141,190 @@ namespace CppSharp.Generator.Tests.Passes @@ -135,6 +141,190 @@ namespace CppSharp.Generator.Tests.Passes
Assert.That(@enum.Items[1].Name, Is.EqualTo("_0"));
}
[Test]
public void TestEnumBaseTypesFromMacroValues()
{
AstContext.GenerateEnumFromMacros("TestEnumMaxSbyte", "TEST_ENUM_MAX_SBYTE");
var @enum = AstContext.Enum("TestEnumMaxSbyte");
Assert.IsNotNull(@enum);
Assert.AreEqual(sbyte.MaxValue, @enum.Items[0].Value);
Assert.AreEqual(PrimitiveType.UChar, @enum.BuiltinType.Type);
AstContext.GenerateEnumFromMacros("TestEnumMaxByte", "TEST_ENUM_MAX_BYTE");
@enum = AstContext.Enum("TestEnumMaxByte");
Assert.IsNotNull(@enum);
Assert.AreEqual(byte.MaxValue, @enum.Items[0].Value);
Assert.AreEqual(PrimitiveType.UChar, @enum.BuiltinType.Type);
AstContext.GenerateEnumFromMacros("TestEnumMaxShort", "TEST_ENUM_MAX_SHORT");
@enum = AstContext.Enum("TestEnumMaxShort");
Assert.IsNotNull(@enum);
Assert.AreEqual(short.MaxValue, @enum.Items[0].Value);
Assert.AreEqual(PrimitiveType.Short, @enum.BuiltinType.Type);
AstContext.GenerateEnumFromMacros("TestEnumMaxUshort", "TEST_ENUM_MAX_USHORT");
@enum = AstContext.Enum("TestEnumMaxUshort");
Assert.IsNotNull(@enum);
Assert.AreEqual(ushort.MaxValue, @enum.Items[0].Value);
Assert.AreEqual(PrimitiveType.UShort, @enum.BuiltinType.Type);
AstContext.GenerateEnumFromMacros("TestEnumMaxInt", "TEST_ENUM_MAX_INT");
@enum = AstContext.Enum("TestEnumMaxInt");
Assert.IsNotNull(@enum);
Assert.AreEqual(int.MaxValue, @enum.Items[0].Value);
Assert.AreEqual(PrimitiveType.Int, @enum.BuiltinType.Type);
AstContext.GenerateEnumFromMacros("TestEnumMaxLong", "TEST_ENUM_MAX_LONG");
@enum = AstContext.Enum("TestEnumMaxLong");
Assert.IsNotNull(@enum);
Assert.AreEqual(long.MaxValue, @enum.Items[0].Value);
Assert.AreEqual(PrimitiveType.LongLong, @enum.BuiltinType.Type);
AstContext.GenerateEnumFromMacros("TestEnumMaxUint", "TEST_ENUM_MAX_UINT");
@enum = AstContext.Enum("TestEnumMaxUint");
Assert.IsNotNull(@enum);
Assert.AreEqual(uint.MaxValue, @enum.Items[0].Value);
Assert.AreEqual(PrimitiveType.UInt, @enum.BuiltinType.Type);
AstContext.GenerateEnumFromMacros("TestEnumMaxUlong", "TEST_ENUM_MAX_ULONG");
@enum = AstContext.Enum("TestEnumMaxUlong");
Assert.IsNotNull(@enum);
Assert.AreEqual(ulong.MaxValue, @enum.Items[0].Value);
Assert.AreEqual(PrimitiveType.ULongLong, @enum.BuiltinType.Type);
AstContext.GenerateEnumFromMacros("TestEnumMinInt", "TEST_ENUM_MIN_INT");
@enum = AstContext.Enum("TestEnumMinInt");
Assert.IsNotNull(@enum);
Assert.AreEqual(int.MinValue, (long)@enum.Items[0].Value);
Assert.AreEqual(PrimitiveType.Int, @enum.BuiltinType.Type);
AstContext.GenerateEnumFromMacros("TestEnumMinLong", "TEST_ENUM_MIN_LONG");
@enum = AstContext.Enum("TestEnumMinLong");
Assert.IsNotNull(@enum);
Assert.AreEqual(long.MinValue, (long)@enum.Items[0].Value);
Assert.AreEqual(PrimitiveType.LongLong, @enum.BuiltinType.Type);
}
// These tests have nothing to do with testing passes except that the expression evaluator
// is only used by GenerateEnumFromMacros. Add a separate TestFixture?
static ExprEvalTestParams[] TestExpressionEvaluatorTestCases =
{
new ExprEvalTestParams("2147483647", int.MaxValue),
new ExprEvalTestParams("-2147483648", int.MinValue),
new ExprEvalTestParams("4294967295", uint.MaxValue),
new ExprEvalTestParams("9223372036854775807", long.MaxValue),
new ExprEvalTestParams("-9223372036854775808", long.MinValue),
new ExprEvalTestParams("18446744073709551615", ulong.MaxValue),
// Must use "L" suffix here or the expression evaluator will overflow with no warning. Note
// that it won't compile in C# without it.
new ExprEvalTestParams("-2147483648L - 1", -2147483648L - 1),
// Note that the dynamic subsystem used by the expression evaluator types this result as
// long, but the compiler types the expression a uint. Adding the casts to dynamic in
// the expressions below matches the result of the expression evaluator. Not sure we
// care that they're different.
new ExprEvalTestParams("2147483648 + 5", (dynamic)2147483648 + (dynamic)5),
new ExprEvalTestParams("5 + 2147483648", (dynamic)5 + (dynamic)2147483648),
new ExprEvalTestParams("0x2A828670572C << 1", 0x2A828670572C << 1),
new ExprEvalTestParams("-0xFC84D76B0482", -0xFC84D76B0482),
new ExprEvalTestParams("27 - 9223372036854775807", 27 - 9223372036854775807),
new ExprEvalTestParams("9223372036854775807", 9223372036854775807),
new ExprEvalTestParams("18446744073709551615", 18446744073709551615),
new ExprEvalTestParams("1 << 5", 1 << 5),
new ExprEvalTestParams("1 << 32", 1 << 32),
new ExprEvalTestParams("1L << 32", 1L << 32),
new ExprEvalTestParams("5u", 5u),
new ExprEvalTestParams("5ul", 5ul),
new ExprEvalTestParams("\"This is\" + \" a string expression\"", "This is" + " a string expression"),
new ExprEvalTestParams("(17 - 48 + 80 * 81 - 88) + (74 + 79 - 50) - ((76 - 51 - 88 + (9 + 98 - 47)))", (17 - 48 + 80 * 81 - 88) + (74 + 79 - 50) - ((76 - 51 - 88 + (9 + 98 - 47)))),
new ExprEvalTestParams("3.14159265", 3.14159265),
new ExprEvalTestParams("16 - 47 * (75 / 91) + 44", 16 - 47 * (75 / 91) + 44), // Does C# truncate the same way?
new ExprEvalTestParams("16 - 47 * (75d / 91d) + 44", 16 - 47 * (75d / 91d) + 44),
new ExprEvalTestParams("69d / 5 - 48 - 47 / (82d - 71 + 2 + 6 / 39d) - 56", 69d / 5 - 48 - 47 / (82d - 71 + 2 + 6 / 39d) - 56),
new ExprEvalTestParams("55.59m", 55.59m),
new ExprEvalTestParams("55.59m + 23", 55.59m + 23),
new ExprEvalTestParams("'A'", 'A'),
new ExprEvalTestParams("'A'+'B'", 'A' + 'B'),
new ExprEvalTestParams("(int)'A'", (int)'A'),
// C++ specific sufixes not supported. C++ octal not supported.
//new EETestParams("5ll", 5L),
//new EETestParams("5ull", 5UL),
//new EETestParams("016", 8 + 6),
new ExprEvalTestParams("V1 | V2", 3, ("V1", 1), ("V2", 2)),
new ExprEvalTestParams("TRUE && FALSE", false, ("TRUE", true), ("FALSE", false)),
};
class ExprEvalTestParams
{
public readonly string Expression;
public readonly object ExpectedResult;
public readonly (string Name, object Value)[] Symbols;
public ExprEvalTestParams(string expression, object expectedResult, params (string Name, object Value)[] symbols)
{
Expression = expression;
ExpectedResult = expectedResult;
Symbols = symbols;
}
}
static IEnumerable GetExpressionEvaluatorTestCases()
{
var testNumber = 100;
foreach (var tc in TestExpressionEvaluatorTestCases)
{
// "testNumber" is just used to cause the test runner to show the tests in the order
// we've listed them which is simply a development time convenience.
yield return new TestCaseData(testNumber++, tc.Expression, tc.ExpectedResult, tc.Symbols);
}
}
[TestCaseSource(nameof(GetExpressionEvaluatorTestCases))]
public void TestExpressionEvaluator(int testNumber, string expression, object expectedResult, (string SymbolName, object SymbolValue)[] symbols)
{
var evaluator = symbols == null || symbols.Length == 0
? new ExpressionEvaluator()
: new ExpressionEvaluator(symbols.ToDictionary(s => s.SymbolName, s => s.SymbolValue));
var result = evaluator.Evaluate(expression);
Assert.AreEqual(expectedResult, result);
Assert.AreEqual(expectedResult.GetType(), result.GetType());
}
[Test]
public void TestEnumsWithBitwiseExpressionMacroValues()
{
var @enum = AstContext.GenerateEnumFromMacros("TestBitwiseShift", "TEST_BITWISE_SHIFT_(.*)");
Assert.IsNotNull(@enum);
Assert.AreEqual(PrimitiveType.LongLong, @enum.BuiltinType.Type);
Assert.AreEqual(0x2A828670572C << 1, (long)@enum.Items[0].Value);
@enum = AstContext.GenerateEnumFromMacros("TestNegativeHex", "TEST_NEGATIVE_HEX_(.*)");
Assert.IsNotNull(@enum);
Assert.AreEqual(PrimitiveType.LongLong, @enum.BuiltinType.Type);
Assert.AreEqual(-0xFC84D76B0482, (long)@enum.Items[0].Value);
@enum = AstContext.GenerateEnumFromMacros("TestBitwiseOr", "TEST_BITWISE_OR_1");
Assert.IsNotNull(@enum);
Assert.AreEqual(PrimitiveType.UChar, @enum.BuiltinType.Type);
Assert.AreEqual(0x7F | 0x80, @enum.Items[0].Value);
@enum = AstContext.GenerateEnumFromMacros("TestBitwiseAnd", "TEST_BITWISE_AND_(.*)");
Assert.IsNotNull(@enum);
Assert.AreEqual(PrimitiveType.UChar, @enum.BuiltinType.Type);
Assert.AreEqual(0x7F & 0xFF, @enum.Items[0].Value);
Assert.AreEqual(0x73 & -1, @enum.Items[1].Value);
Assert.AreEqual(0x42 & ~0x2, @enum.Items[2].Value);
@enum = AstContext.GenerateEnumFromMacros("TestBitwiseXor", "TEST_BITWISE_XOR_(.*)");
Assert.IsNotNull(@enum);
Assert.AreEqual(PrimitiveType.UChar, @enum.BuiltinType.Type);
Assert.AreEqual(0x7F ^ 0x03, @enum.Items[0].Value);
}
[Test]
public void TestUnnamedEnumSupport()
{

188
src/Generator/Library.cs

@ -92,67 +92,117 @@ namespace CppSharp @@ -92,67 +92,117 @@ namespace CppSharp
select @enum).FirstOrDefault();
}
/// <summary>
/// Constructs an <see cref="Enumeration.Item"/> by parsing the <see
/// cref="MacroDefinition.Expression"/> in <paramref name="macro"/>.
/// </summary>
/// <remarks>
/// As a side effect, updates <see cref="Enumeration.Type"/> and <see
/// cref="Enumeration.BuiltinType"/> in <paramref name="@enum"/> to reflect an appropriate
/// primitive type that can hold the newly-parsed <paramref name="macro"/> expression value
/// as well as all previously discovered <see cref="Enumeration.Items"/>. The intent is to
/// preserve sign information about the values held in <paramref name="enum"/>.
/// </remarks>
public static Enumeration.Item GenerateEnumItemFromMacro(this Enumeration @enum,
MacroDefinition macro)
{
return new Enumeration.Item
{
Name = macro.Name,
Expression = macro.Expression,
Value = ParseEnumItemMacroExpression(macro, @enum),
Namespace = @enum
};
}
static bool EvaluateEnumExpression(string expr, Enumeration @enum, out object eval)
{
var evaluator = new ExpressionEvaluator { Variables = new Dictionary<string, object>() };
// Include values of past items
foreach (var item in @enum.Items)
var (value, type) = ParseEnumItemMacroExpression(macro, @enum);
if (type > @enum.BuiltinType.Type)
{
evaluator.Variables.Add(item.Name, item.Value);
@enum.BuiltinType = new BuiltinType(type);
@enum.Type = @enum.BuiltinType;
}
try
{
expr = expr.ReplaceLineBreaks(" ").Replace('\\', ' ');
eval = evaluator.Evaluate(expr);
return true;
}
catch (Exception ex)
return new Enumeration.Item
{
throw new Exception($"Failed to evaluate expression: {ex.Message}");
}
Name = macro.Name,
Expression = macro.Expression,
Value = value,
Namespace = @enum
};
}
static object EvaluateEnumExpression(string expr, Enumeration @enum)
{
// Include values of past items. Convert values to reflect the current
// estimate of the PrimitiveType. In particular, recover sign information.
var evaluator = new ExpressionEvaluator(@enum.Items.ToDictionary(
item => item.Name,
item => @enum.BuiltinType.Type switch
{
PrimitiveType.SChar => (sbyte)item.Value,
PrimitiveType.Short => (short)item.Value,
PrimitiveType.Int => (int)item.Value,
PrimitiveType.LongLong => (long)item.Value,
PrimitiveType.UChar => (byte)item.Value,
PrimitiveType.UShort => (ushort)item.Value,
PrimitiveType.UInt => (uint)item.Value,
PrimitiveType.ULongLong => (object)item.Value,
_ => item.Value
}
));
expr = expr.ReplaceLineBreaks(" ").Replace('\\', ' ');
return evaluator.Evaluate(expr);
}
static ulong ParseEnumItemMacroExpression(MacroDefinition macro, Enumeration @enum)
/// <summary>
/// Parse an enum value from a macro expression.
/// </summary>
/// <returns>
/// The result of the expression evaluation cast to a ulong together with the primitive type
/// of the result as returned from the expression evaluator. Note that the expression
/// evaluator biases towards returning <see cref="int"/> values: it doesn't attempt to
/// discover that a value could be stored in a <see cref="byte"/>.
/// </returns>
static (ulong value, PrimitiveType type) ParseEnumItemMacroExpression(MacroDefinition macro, Enumeration @enum)
{
var expression = macro.Expression;
// The expression evaluator already handles character and string expressions. No need to
// special case them here.
//
// TODO: Handle string expressions
if (expression.Length == 3 && expression[0] == '\'' && expression[2] == '\'') // '0' || 'A'
return expression[1]; // return the ASCII code of this character
//if (expression.Length == 3 && expression[0] == '\'' && expression[2] == '\'') // '0' || 'A'
// return expression[1]; // return the ASCII code of this character
object eval;
try
{
EvaluateEnumExpression(expression, @enum, out eval);
var eval = EvaluateEnumExpression(expression, @enum);
// Verify we have some sort of integer type. Enums can have negative values: record
// that fact so that we can set the underlying primitive type correctly in the enum
// item.
switch (System.Type.GetTypeCode(eval.GetType()))
{
// Must unbox eval which is typed as object to be able to cast it to a ulong.
case TypeCode.SByte: return ((ulong)(sbyte)eval, PrimitiveType.SChar);
case TypeCode.Int16: return ((ulong)(short)eval, PrimitiveType.Short);
case TypeCode.Int32: return ((ulong)(int)eval, PrimitiveType.Int);
case TypeCode.Int64: return ((ulong)(long)eval, PrimitiveType.LongLong);
case TypeCode.Byte: return ((byte)eval, PrimitiveType.UChar);
case TypeCode.UInt16: return ((ushort)eval, PrimitiveType.UShort);
case TypeCode.UInt32: return ((uint)eval, PrimitiveType.UInt);
case TypeCode.UInt64: return ((ulong)eval, PrimitiveType.ULongLong);
case TypeCode.Char: return ((char)eval, PrimitiveType.UShort);
// C++ allows booleans as enum values - they're translated to 1, 0.
case TypeCode.Boolean: return ((bool)eval ? 1UL : 0UL, PrimitiveType.UChar);
default: throw new Exception($"Expression {expression} is not a valid expression type for an enum");
}
}
catch (Exception)
catch (Exception ex)
{
// 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;
// Like other paths, we can however, write a diagnostic message to the console.
unchecked
{
ulong val = ((ulong) (int) eval);
return val;
Diagnostics.PushIndent();
Diagnostics.Warning($"Unable to translate macro '{macro.Name}' to en enum value: {ex.Message}");
Diagnostics.PopIndent();
return (0, PrimitiveType.Int);
}
}
@ -269,8 +319,30 @@ namespace CppSharp @@ -269,8 +319,30 @@ namespace CppSharp
if (@enum.Items.Count <= 0)
return @enum;
var maxValue = @enum.Items.Max(i => i.Value);
@enum.BuiltinType = new BuiltinType(GetUnderlyingTypeForEnumValue(maxValue));
// The value of @enum.BuiltinType has been adjusted on the fly via
// GenerateEnumItemFromMacro. However, its notion of PrimitiveType corresponds with
// what the ExpressionEvaluator returns. In particular, the expression "1" will result
// in PrimitiveType.Int from the expression evaluator. Narrow the type to account for
// types narrower than int.
//
// Note that there is no guarantee that all items can be held in any builtin type. For
// example, some value > long.MaxValue and some negative value. That case will result in
// compile errors of the generated code. If needed, we could detect it and print a
// diagnostic.
ulong maxValue;
long minValue;
if (@enum.BuiltinType.IsUnsigned)
{
maxValue = @enum.Items.Max(i => i.Value);
minValue = 0;
}
else
{
maxValue = (ulong)@enum.Items.Max(i => Math.Max(0, unchecked((long)i.Value)));
minValue = @enum.Items.Min(i => unchecked((long)i.Value));
}
@enum.BuiltinType = new BuiltinType(GetUnderlyingTypeForEnumValue(maxValue, minValue));
@enum.Type = @enum.BuiltinType;
var unit = macroUnit ?? GetUnitFromItems(items);
@ -280,33 +352,39 @@ namespace CppSharp @@ -280,33 +352,39 @@ namespace CppSharp
return @enum;
}
private static PrimitiveType GetUnderlyingTypeForEnumValue(ulong maxValue)
private static PrimitiveType GetUnderlyingTypeForEnumValue(ulong maxValue, long minValue)
{
if (maxValue < (byte)sbyte.MaxValue)
return PrimitiveType.SChar;
if (maxValue <= (ulong)sbyte.MaxValue && minValue >= sbyte.MinValue)
// Previously, returned PrimitiveType.SChar. But that type doesn't seem to be well-supported elsewhere.
// Bias towards UChar (byte), since that's what would normally be used in C#.
return minValue >= 0 ? PrimitiveType.UChar : PrimitiveType.SChar;
if (maxValue < byte.MaxValue)
if (maxValue <= byte.MaxValue && minValue >= 0)
return PrimitiveType.UChar;
if (maxValue < (ushort)short.MaxValue)
if (maxValue <= (ulong)short.MaxValue && minValue >= short.MinValue)
return PrimitiveType.Short;
if (maxValue < ushort.MaxValue)
if (maxValue <= ushort.MaxValue && minValue >= 0)
return PrimitiveType.UShort;
if (maxValue < int.MaxValue)
if (maxValue <= int.MaxValue && minValue >= int.MinValue)
return PrimitiveType.Int;
if (maxValue < uint.MaxValue)
if (maxValue <= uint.MaxValue && minValue >= 0)
return PrimitiveType.UInt;
if (maxValue < long.MaxValue)
return PrimitiveType.Long;
if (maxValue <= long.MaxValue && minValue >= long.MinValue)
// Previously returned Long. However, LongLong is unambiguous and seems to be supported
// wherever PrimitiveType.Long is
return PrimitiveType.LongLong;
if (maxValue < ulong.MaxValue)
return PrimitiveType.ULong;
if (maxValue <= ulong.MaxValue && minValue >= 0)
// Previously returned ULong. However, ULongLong is unambiguous and seems to be supported
// wherever PrimitiveType.ULong is
return PrimitiveType.ULongLong; // Same as above.
throw new NotImplementedException();
throw new NotImplementedException($"Cannot identify an integral underlying enum type supporting a maximum value of {maxValue} and a minimum value of {minValue}.");
}
#endregion

48
src/Generator/Utils/ExpressionEvaluator.cs

@ -1604,11 +1604,13 @@ namespace CodingSeb.ExpressionEvaluator @@ -1604,11 +1604,13 @@ namespace CodingSeb.ExpressionEvaluator
if (otherBaseMatch.Groups["sign"].Success)
{
string value = otherBaseMatch.Groups["value"].Value.Replace("_", "").Substring(2);
stack.Push(otherBaseMatch.Groups["sign"].Value.Equals("-") ? -Convert.ToInt32(value, baseValue) : Convert.ToInt32(value, baseValue));
var sign = otherBaseMatch.Groups["sign"].Value.Equals("-") ? -1 : 1;
stack.Push(ParseInteger(value, baseValue, sign));
}
else
{
stack.Push(Convert.ToInt32(otherBaseMatch.Value.Replace("_", "").Substring(2), baseValue));
string value = otherBaseMatch.Value.Replace("_", "").Substring(2);
stack.Push(ParseInteger(value, baseValue, 1));
}
return true;
@ -1637,7 +1639,9 @@ namespace CodingSeb.ExpressionEvaluator @@ -1637,7 +1639,9 @@ namespace CodingSeb.ExpressionEvaluator
}
else
{
stack.Push(int.Parse(numberMatch.Value.Replace("_", ""), NumberStyles.Any, CultureInfoForNumberParsing));
string numberNoSign = numberMatch.Groups[1].Value.Replace("_", "");
int sign = numberMatch.Groups["sign"].Success ? -1 : 1;
stack.Push(ParseInteger(numberNoSign, 10, sign));
}
return true;
@ -1648,6 +1652,44 @@ namespace CodingSeb.ExpressionEvaluator @@ -1648,6 +1652,44 @@ namespace CodingSeb.ExpressionEvaluator
}
}
// CPPSHARP
/// <summary>
/// Heuristically convert <paramref name="numberToParse"/> to an int, uint, long, or ulong
/// depending on the size of the number parsed. This method should only be called when no
/// number type suffix is available.
/// </summary>
/// <remarks>
/// The original implementation always converted to ints. This method extends support to
/// uints, longs and ulongs where needed. The intent is to match C#'s implicit typing for
/// integer types. For example, C# implicitly types 2147483648 as a uint.
/// </remarks>
/// <param name="numberToParse">
/// the number to parse - should not include any number-type suffix, radix, or sign prefix.
/// Just the digits/letters.
/// </param>
/// <param name="fromBase">
/// the radix to apply when parsing the number. Must be 2, 8, 10, or 16.
/// </param>
/// <param name="sign">
/// the result is negative if <paramref name="sign"/> is less than zero. Otherwise, the
/// result is positive.
/// </param>
private static object ParseInteger(string numberToParse, int fromBase, int sign)
{
var number = Convert.ToUInt64(numberToParse, fromBase);
if (sign < 0)
{
if (number <= 2147483648) return -(int)number;
return -(long)number;
}
if (number <= int.MaxValue) return (int)number;
if (number <= uint.MaxValue) return (uint)number;
if (number <= long.MaxValue) return (long)number;
return number;
}
//
protected virtual bool EvaluateInstanceCreationWithNewKeyword(string expression, Stack<object> stack, ref int i)
{
if (!OptionNewKeywordEvaluationActive)

2
tests/CSharp/CSharp.Gen.cs

@ -57,6 +57,8 @@ namespace CppSharp.Tests @@ -57,6 +57,8 @@ namespace CppSharp.Tests
var enumTest = ctx.GenerateEnumFromMacros("MyMacroTestEnum", list.ToArray());
ctx.GenerateEnumFromMacros("MyMacroTest2Enum", "MY_MACRO_TEST2_.*");
ctx.GenerateEnumFromMacros("SignedMacroValuesToEnumTest", "SIGNED_MACRO_VALUES_TO_ENUM_TEST_.*");
ctx.GenerateEnumFromMacros("TestBoolValuedEnums", "TEST_BOOL_VALUED_ENUMS_.*");
enumTest.Namespace = new Namespace()
{

20
tests/CSharp/CSharp.Tests.cs

@ -1243,6 +1243,7 @@ public unsafe class CSharpTests @@ -1243,6 +1243,7 @@ public unsafe class CSharpTests
var a = (MyMacroTestEnum)'1';
var b = (MyMacroTestEnum)'2';
Assert.IsTrue(a == MyMacroTestEnum.MY_MACRO_TEST_1 && b == MyMacroTestEnum.MY_MACRO_TEST_2);
Assert.AreEqual(typeof(byte), Enum.GetUnderlyingType(typeof(MyMacroTestEnum)));
}
[Test]
@ -1262,6 +1263,25 @@ public unsafe class CSharpTests @@ -1262,6 +1263,25 @@ public unsafe class CSharpTests
g == MyMacroTest2Enum.MY_MACRO_TEST2_4 && h == MyMacroTest2Enum.MY_MACRO_TEST2ALL);
}
[Test]
public void TestSignedMacroToEnums()
{
Assert.AreEqual(typeof(long), Enum.GetUnderlyingType(typeof(SignedMacroValuesToEnumTest)));
Assert.AreEqual(1 << 5, (long)SignedMacroValuesToEnumTest.SIGNED_MACRO_VALUES_TO_ENUM_TEST_1);
Assert.AreEqual(1 << 22, (long)SignedMacroValuesToEnumTest.SIGNED_MACRO_VALUES_TO_ENUM_TEST_2);
Assert.AreEqual(1L << 32, (long)SignedMacroValuesToEnumTest.SIGNED_MACRO_VALUES_TO_ENUM_TEST_3);
Assert.AreEqual(-1, (long)SignedMacroValuesToEnumTest.SIGNED_MACRO_VALUES_TO_ENUM_TEST_4);
}
[Test]
public void BoolValuedEnumsTest()
{
Assert.AreEqual(typeof(byte), Enum.GetUnderlyingType(typeof(TestBoolValuedEnums)));
Assert.AreEqual(Convert.ToByte(true), (byte)TestBoolValuedEnums.TEST_BOOL_VALUED_ENUMS_V1);
Assert.AreEqual(Convert.ToByte(false), (byte)TestBoolValuedEnums.TEST_BOOL_VALUED_ENUMS_V2);
Assert.AreEqual(Convert.ToByte(42), (byte)TestBoolValuedEnums.TEST_BOOL_VALUED_ENUMS_V3);
}
[Test]
public void TestGenerationOfArraySetter()
{

8
tests/CSharp/CSharp.h

@ -1288,6 +1288,14 @@ struct StructTestArrayTypeFromTypedef @@ -1288,6 +1288,14 @@ struct StructTestArrayTypeFromTypedef
#define MY_MACRO_TEST2_4 (1 << 3)
#define MY_MACRO_TEST2_ALL (1 << 4) - 1
#define SIGNED_MACRO_VALUES_TO_ENUM_TEST_1 1 << 5
#define SIGNED_MACRO_VALUES_TO_ENUM_TEST_2 1 << 22
#define SIGNED_MACRO_VALUES_TO_ENUM_TEST_3 1L << 32
#define SIGNED_MACRO_VALUES_TO_ENUM_TEST_4 -1
enum TEST_BOOL_VALUED_ENUMS { TEST_BOOL_VALUED_ENUMS_V1 = true, TEST_BOOL_VALUED_ENUMS_V2 = false};
#define TEST_BOOL_VALUED_ENUMS_V3 42
struct DLL_API ComplexArrayElement
{
ComplexArrayElement();

30
tests/Native/Passes.h

@ -47,6 +47,36 @@ enum @@ -47,6 +47,36 @@ enum
TEST_ENUM_ITEM_NAME_3 = 3
};
// Test #defines at underlying type break points.
#define TEST_ENUM_MAX_SBYTE 127
#define TEST_ENUM_MAX_BYTE 255
#define TEST_ENUM_MAX_SHORT 32767
#define TEST_ENUM_MAX_USHORT 65535
#define TEST_ENUM_MAX_INT 2147483647
#define TEST_ENUM_MAX_UINT 4294967295
#define TEST_ENUM_MAX_LONG 9223372036854775807
#define TEST_ENUM_MAX_ULONG 18446744073709551615
#define TEST_ENUM_MIN_SBYTE -128
#define TEST_ENUM_MIN_SHORT -32768
#define TEST_ENUM_MIN_INT -2147483648
#define TEST_ENUM_MIN_LONG -9223372036854775808
// Test #defines with bit operators
#define TEST_BITWISE_OR_1 0x7F | 0x80
#define TEST_BITWISE_AND_1 0x7F & 0xFF
#define TEST_BITWISE_AND_2 0x73 & -1
#define TEST_BITWISE_AND_3 0x42 & ~0x2
#define TEST_BITWISE_XOR_1 0x7F ^ 0x03
#define TEST_BITWISE_SHIFT_1 0x2A828670572C << 1
#define TEST_NEGATIVE_HEX_1 -0xFC84D76B0482
#define TEST_NEGATIVE_EXPR_1 27 - 9223372036854775807
#define TEST_INT32_MINVALUE -2147483648;
#define TEST_INT64_MAXVALUE 9223372036854775807
#define TEST_UINT64_MAXVALUE 18446744073709551615
#define TEST_LONG_PLUS_INT 2147483648 + 5
#define TEST_INT_PLUS_LONG 5 + 2147483648
// TestStructInheritance
struct S1 { int F1, F2; };
struct S2 : S1 { int F3; };

Loading…
Cancel
Save