forked from MochiLibraries/Biohazrd
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathCSharpLibraryGenerator.Constants.cs
180 lines (163 loc) · 9.76 KB
/
CSharpLibraryGenerator.Constants.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
using Biohazrd.CSharp.Infrastructure;
using Biohazrd.Expressions;
using System.Diagnostics;
using static Biohazrd.CSharp.CSharpCodeWriter;
namespace Biohazrd.CSharp
{
partial class CSharpLibraryGenerator
{
private string GetConstantAsString(VisitorContext context, TranslatedDeclaration declaration, ConstantValue constant, TypeReference targetType)
{
switch (constant)
{
case ICustomCSharpConstantValue cSharpConstantValue:
return cSharpConstantValue.GetConstantAsString(this, context, declaration);
case NullPointerConstant:
return "null";
case IntegerConstant integerConstant:
{
// Bools come through as integer types
static bool IsBooleanType(VisitorContext context, TypeReference targetType)
{
if (targetType is CSharpBuiltinTypeReference cSharpTypeReference && cSharpTypeReference.Type == CSharpBuiltinType.Bool)
{ return true; }
// NativeBoolean is used for virtual methods which end up getting wrapped by trampolines which use bool so emitting them as true/false contants are valid in that context
// See https://github.com/InfectedLibraries/Biohazrd/issues/200 for details.
else if (targetType is TranslatedTypeReference translatedTypeReference && translatedTypeReference.TryResolve(context.Library) is NativeBooleanDeclaration)
{ return true; }
else
{ return false; }
}
if (IsBooleanType(context, targetType))
{
if (integerConstant.Value == 0)
{ return "false"; }
else if (integerConstant.Value == 1)
{ return "true"; }
// Clang seems to always coerce constants with a target type of bool to be 1 or 0, so this probably won't happen for C++ bools,
// but in theory it could for older typedef-bools that got transformed to C# bools.
// (The C++ spec states abnormal bools are undefined behavior. https://timsong-cpp.github.io/cppwp/n4659/basic.fundamental#6)
return $"true /* {integerConstant.Value} */";
}
// Enums come through as integer types
if (targetType is TranslatedTypeReference translatedTypeReference && translatedTypeReference.TryResolve(context.Library, out VisitorContext targetEnumContext) is TranslatedEnum targetEnum)
{ return GetEnumConstantAsString(context, declaration, integerConstant, targetEnum, targetEnumContext); }
return GetIntegerConstantAsStringDirect(context, declaration, integerConstant);
}
case DoubleConstant doubleConstant:
{
double value = doubleConstant.Value;
if (double.IsNaN(value))
{
string ret = "double.NaN";
if (value.IsUnusualNaN())
{ ret += $" /* 0x{value.GetBits():X16} */"; }
return ret;
}
else if (double.IsPositiveInfinity(value))
{ return "double.PositiveInfinity"; }
else if (double.IsNegativeInfinity(value))
{ return "double.NegativeInfinity"; }
// Edge case: Negative 0 will ToString as positive 0
else if (value == 0.0 && double.IsNegative(value))
{ return "-0d"; }
// Emit friendly constants for MinValue/MaxValue
else if (value == double.MinValue)
{ return "double.MinValue"; }
else if (value == double.MaxValue)
{ return "double.MaxValue"; }
else if (value == double.Epsilon)
{ return "double.Epsilon"; }
else
{
Debug.Assert(double.IsFinite(value), $"The double must be finite at this point!");
// G17 is the recommended format specifier for round-trippable doubles
// https://docs.microsoft.com/en-us/dotnet/standard/base-types/standard-numeric-format-strings#the-round-trip-r-format-specifier
// The explicit d suffix is not commonly used, but it makes a double constant without the a decimal point.
// https://github.com/dotnet/csharplang/blob/8e7d390f6deaec0a01f690f9689ebf93903f4b00/spec/lexical-structure.md#real-literals
return $"{value:G17}d";
}
}
case FloatConstant floatConstant:
{
float value = floatConstant.Value;
if (float.IsNaN(value))
{
string ret = "float.NaN";
if (value.IsUnusualNaN())
{ ret += $" /* 0x{value.GetBits():X8} */"; }
return ret;
}
else if (float.IsPositiveInfinity(value))
{ return "float.PositiveInfinity"; }
else if (float.IsNegativeInfinity(value))
{ return "float.NegativeInfinity"; }
// Edge case: Negative 0 will ToString as positive 0
else if (value == 0f && float.IsNegative(value))
{ return "-0f"; }
// Emit friendly constants for MinValue/MaxValue
else if (value == float.MinValue)
{ return "float.MinValue"; }
else if (value == float.MaxValue)
{ return "float.MaxValue"; }
else if (value == float.Epsilon)
{ return "float.Epsilon"; }
else
{
Debug.Assert(float.IsFinite(value), $"The float must be finite at this point!");
// G9 is the recommended format specifier for round-trippable floats
// https://docs.microsoft.com/en-us/dotnet/standard/base-types/standard-numeric-format-strings#the-round-trip-r-format-specifier
return $"{value:G9}f";
}
}
case StringConstant stringConstant:
{
string ret = $"\"{SanitizeStringLiteral(stringConstant.Value)}\"";
// Default string parameter values require special translation that we don't handle in the general case because it's hard to objectively translate them.
if (declaration is TranslatedParameter)
{
Fatal(context, declaration, $"String constants are not supported for parameters.");
return $"default /* {SanitizeMultiLineComment(ret)} */";
}
return ret;
}
case UnsupportedConstantExpression unsupportedConstant:
Fatal(context, declaration, $"Cannot emit unsupported constant value: {unsupportedConstant.Message}");
return "default";
default:
Fatal(context, declaration, $"{constant.GetType().Name} is not supported by the C# output generator.");
return "default";
}
}
private string GetEnumConstantAsString(VisitorContext context, TranslatedDeclaration declaration, IntegerConstant constant, TranslatedEnum targetEnum, VisitorContext targetEnumContext)
{
// See if any enum values match the constant
foreach (TranslatedEnumConstant enumConstant in targetEnum.Values)
{
if (enumConstant.Value == constant.Value)
{
// This might look weird (because it does), but GetTypeAsString will properly emit the member access to the enum constant.
// We should probably add a separate method that handles this situation for us rather than abusing the type infrastructure.
return GetTypeAsString(context, declaration, new PreResolvedTypeReference(targetEnumContext.Add(targetEnum), enumConstant));
}
}
// At this point we didn't find a specific enum constant that fits ours
// We could try to infer a reasonable combination of flags for flags enums here, but we don't since the actual combination isn't available at this point and inferring a reasonable one is tedious.
// For enums translated as loose constants, we just return the constant.
string ret = GetIntegerConstantAsStringDirect(context, declaration, constant);
// For non-loose constant enums, we have to cast
if (!targetEnum.TranslateAsLooseConstants)
{
string cast = $"({GetTypeAsString(context, declaration, new PreResolvedTypeReference(targetEnumContext, targetEnum))})";
// Casting a negative number requires parenthesis.
if (constant.IsSigned && constant.SignedValue < 0)
{ ret = $"{cast}({ret})"; }
else
{ ret = $"{cast}{ret}"; }
}
return ret;
}
private string GetIntegerConstantAsStringDirect(VisitorContext context, TranslatedDeclaration declaration, IntegerConstant constant)
=> constant.IsSigned ? constant.SignedValue.ToString() : constant.Value.ToString();
}
}