/*
Copyright (C) 2014-2019 de4dot@gmail.com
This file is part of dnSpy
dnSpy is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
dnSpy is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with dnSpy. If not, see .
*/
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Text;
using dnlib.DotNet;
using dnSpy.Contracts.Decompiler;
using dnSpy.Contracts.Text;
using dnSpy.Decompiler.Properties;
namespace dnSpy.Decompiler.CSharp {
public struct CSharpFormatter {
const string Keyword_true = "true";
const string Keyword_false = "false";
const string Keyword_null = "null";
const string Keyword_out = "out";
const string Keyword_in = "in";
const string Keyword_ref = "ref";
const string Keyword_readonly = "readonly";
const string Keyword_this = "this";
const string Keyword_get = "get";
const string Keyword_set = "set";
const string Keyword_add = "add";
const string Keyword_remove = "remove";
const string Keyword_enum = "enum";
const string Keyword_struct = "struct";
const string Keyword_interface = "interface";
const string Keyword_class = "class";
const string Keyword_namespace = "namespace";
const string Keyword_params = "params";
const string Keyword_default = "default";
const string Keyword_delegate = "delegate";
const string HexPrefix = "0x";
const string VerbatimStringPrefix = "@";
const string IdentifierEscapeBegin = "@";
const string ModuleNameSeparator = "!";
const string CommentBegin = "/*";
const string CommentEnd = "*/";
const string DeprecatedParenOpen = "[";
const string DeprecatedParenClose = "]";
const string MemberSpecialParenOpen = "(";
const string MemberSpecialParenClose = ")";
const string MethodParenOpen = "(";
const string MethodParenClose = ")";
const string DescriptionParenOpen = "(";
const string DescriptionParenClose = ")";
const string IndexerParenOpen = "[";
const string IndexerParenClose = "]";
const string PropertyParenOpen = "[";
const string PropertyParenClose = "]";
const string ArrayParenOpen = "[";
const string ArrayParenClose = "]";
const string TupleParenOpen = "(";
const string TupleParenClose = ")";
const string GenericParenOpen = "<";
const string GenericParenClose = ">";
const string DefaultParamValueParenOpen = "[";
const string DefaultParamValueParenClose = "]";
int recursionCounter;
int lineLength;
bool outputLengthExceeded;
bool forceWrite;
readonly ITextColorWriter output;
FormatterOptions options;
readonly CultureInfo cultureInfo;
static readonly Dictionary nameToOperatorName = new Dictionary(StringComparer.Ordinal) {
{ "op_Addition", "operator +".Split(' ') },
{ "op_BitwiseAnd", "operator &".Split(' ') },
{ "op_BitwiseOr", "operator |".Split(' ') },
{ "op_Decrement", "operator --".Split(' ') },
{ "op_Division", "operator /".Split(' ') },
{ "op_Equality", "operator ==".Split(' ') },
{ "op_ExclusiveOr", "operator ^".Split(' ') },
{ "op_Explicit", "explicit operator".Split(' ') },
{ "op_False", "operator false".Split(' ') },
{ "op_GreaterThan", "operator >".Split(' ') },
{ "op_GreaterThanOrEqual", "operator >=".Split(' ') },
{ "op_Implicit", "implicit operator".Split(' ') },
{ "op_Increment", "operator ++".Split(' ') },
{ "op_Inequality", "operator !=".Split(' ') },
{ "op_LeftShift", "operator <<".Split(' ') },
{ "op_LessThan", "operator <".Split(' ') },
{ "op_LessThanOrEqual", "operator <=".Split(' ') },
{ "op_LogicalNot", "operator !".Split(' ') },
{ "op_Modulus", "operator %".Split(' ') },
{ "op_Multiply", "operator *".Split(' ') },
{ "op_OnesComplement", "operator ~".Split(' ') },
{ "op_RightShift", "operator >>".Split(' ') },
{ "op_Subtraction", "operator -".Split(' ') },
{ "op_True", "operator true".Split(' ') },
{ "op_UnaryNegation", "operator -".Split(' ') },
{ "op_UnaryPlus", "operator +".Split(' ') },
};
bool ShowModuleNames => (options & FormatterOptions.ShowModuleNames) != 0;
bool ShowParameterTypes => (options & FormatterOptions.ShowParameterTypes) != 0;
bool ShowParameterNames => (options & FormatterOptions.ShowParameterNames) != 0;
bool ShowDeclaringTypes => (options & FormatterOptions.ShowDeclaringTypes) != 0;
bool ShowReturnTypes => (options & FormatterOptions.ShowReturnTypes) != 0;
bool ShowNamespaces => (options & FormatterOptions.ShowNamespaces) != 0;
bool ShowIntrinsicTypeKeywords => (options & FormatterOptions.ShowIntrinsicTypeKeywords) != 0;
bool UseDecimal => (options & FormatterOptions.UseDecimal) != 0;
bool ShowTokens => (options & FormatterOptions.ShowTokens) != 0;
bool ShowArrayValueSizes => (options & FormatterOptions.ShowArrayValueSizes) != 0;
bool ShowFieldLiteralValues => (options & FormatterOptions.ShowFieldLiteralValues) != 0;
bool ShowParameterLiteralValues => (options & FormatterOptions.ShowParameterLiteralValues) != 0;
bool DigitSeparators => (options & FormatterOptions.DigitSeparators) != 0;
public CSharpFormatter(ITextColorWriter output, FormatterOptions options, CultureInfo? cultureInfo) {
this.output = output;
this.options = options;
this.cultureInfo = cultureInfo ?? CultureInfo.InvariantCulture;
recursionCounter = 0;
lineLength = 0;
outputLengthExceeded = false;
forceWrite = false;
}
static readonly HashSet isKeyword = new HashSet(StringComparer.Ordinal) {
"abstract", "as", "base", "bool", "break", "byte", "case", "catch",
"char", "checked", "class", "const", "continue", "decimal", "default", "delegate",
"do", "double", "else", "enum", "event", "explicit", "extern", "false",
"finally", "fixed", "float", "for", "foreach", "goto", "if", "implicit",
"in", "int", "interface", "internal", "is", "lock", "long", "namespace",
"new", "null", "object", "operator", "out", "override", "params", "private",
"protected", "public", "readonly", "ref", "return", "sbyte", "sealed", "short",
"sizeof", "stackalloc", "static", "string", "struct", "switch", "this", "throw",
"true", "try", "typeof", "uint", "ulong", "unchecked", "unsafe", "ushort",
"using", "virtual", "void", "volatile", "while",
};
void WriteIdentifier(string id, object data) {
if (isKeyword.Contains(id))
OutputWrite(IdentifierEscapeBegin + IdentifierEscaper.Escape(id), data);
else
OutputWrite(IdentifierEscaper.Escape(id), data);
}
void OutputWrite(string s, object data) {
if (!forceWrite) {
if (outputLengthExceeded)
return;
if (lineLength + s.Length > TypeFormatterUtils.MAX_OUTPUT_LEN) {
s = s.Substring(0, TypeFormatterUtils.MAX_OUTPUT_LEN - lineLength);
s += "[...]";
outputLengthExceeded = true;
}
}
output.Write(data, s);
lineLength += s.Length;
}
void WriteSpace() => OutputWrite(" ", BoxedTextColor.Text);
void WriteCommaSpace() {
OutputWrite(",", BoxedTextColor.Punctuation);
WriteSpace();
}
void WritePeriod() => OutputWrite(".", BoxedTextColor.Operator);
void WriteError() => OutputWrite("???", BoxedTextColor.Error);
void WriteSystemTypeKeyword(string name, string keyword, bool isValueType) {
if (ShowIntrinsicTypeKeywords)
OutputWrite(keyword, BoxedTextColor.Keyword);
else
WriteSystemType(name, isValueType);
}
void WriteSystemType(string name, bool isValueType) {
if (ShowNamespaces) {
OutputWrite("System", BoxedTextColor.Namespace);
WritePeriod();
}
OutputWrite(name, BoxedTextColor.Type);
}
void WriteToken(IMDTokenProvider tok) {
if (!ShowTokens)
return;
Debug2.Assert(tok is not null);
if (tok is null)
return;
OutputWrite(CommentBegin + ToFormattedUInt32(tok.MDToken.Raw) + CommentEnd, BoxedTextColor.Comment);
}
public void WriteToolTip(IMemberRef? member) {
if (member is null) {
WriteError();
return;
}
if (member is IMethod method && method.MethodSig is not null) {
WriteToolTip(method);
return;
}
if (member is IField field && field.FieldSig is not null) {
WriteToolTip(field);
return;
}
if (member is PropertyDef prop && prop.PropertySig is not null) {
WriteToolTip(prop);
return;
}
if (member is EventDef evt && evt.EventType is not null) {
WriteToolTip(evt);
return;
}
if (member is ITypeDefOrRef tdr) {
WriteToolTip(tdr);
return;
}
if (member is GenericParam gp) {
WriteToolTip(gp);
return;
}
Debug.Fail("Unknown reference");
}
public void Write(IMemberRef? member) {
if (member is null) {
WriteError();
return;
}
if (member is IMethod method && method.MethodSig is not null) {
Write(method);
return;
}
if (member is IField field && field.FieldSig is not null) {
Write(field);
return;
}
if (member is PropertyDef prop && prop.PropertySig is not null) {
Write(prop);
return;
}
if (member is EventDef evt && evt.EventType is not null) {
Write(evt);
return;
}
if (member is ITypeDefOrRef tdr) {
Write(tdr, ShowModuleNames);
return;
}
if (member is GenericParam gp) {
Write(gp);
return;
}
Debug.Fail("Unknown reference");
}
void WriteDeprecated(bool isDeprecated) {
if (isDeprecated) {
OutputWrite(DeprecatedParenOpen, BoxedTextColor.Punctuation);
OutputWrite(dnSpy_Decompiler_Resources.CSharp_Deprecated_Member, BoxedTextColor.Text);
OutputWrite(DeprecatedParenClose, BoxedTextColor.Punctuation);
WriteSpace();
}
}
void Write(MemberSpecialFlags flags) {
if (flags == MemberSpecialFlags.None)
return;
OutputWrite(MemberSpecialParenOpen, BoxedTextColor.Punctuation);
bool comma = false;
if ((flags & MemberSpecialFlags.Awaitable) != 0) {
comma = true;
OutputWrite(dnSpy_Decompiler_Resources.CSharp_Awaitable_Method, BoxedTextColor.Text);
}
if ((flags & MemberSpecialFlags.Extension) != 0) {
if (comma)
WriteCommaSpace();
OutputWrite(dnSpy_Decompiler_Resources.CSharp_Extension_Method, BoxedTextColor.Text);
}
OutputWrite(MemberSpecialParenClose, BoxedTextColor.Punctuation);
WriteSpace();
}
void WriteToolTip(IMethod? method) {
if (method is null) {
WriteError();
return;
}
WriteDeprecated(TypeFormatterUtils.IsDeprecated(method));
Write(TypeFormatterUtils.GetMemberSpecialFlags(method));
Write(method);
var td = method.DeclaringType.ResolveTypeDef();
if (td is not null) {
var s = TypeFormatterUtils.GetNumberOfOverloadsString(td, method);
if (s is not null)
OutputWrite(s, BoxedTextColor.Text);
}
}
void WriteType(ITypeDefOrRef type, bool useNamespaces, bool useTypeKeywords) {
var td = type as TypeDef;
if (td is null && type is TypeRef)
td = ((TypeRef)type).Resolve();
if (td is null ||
td.GenericParameters.Count == 0 ||
(td.DeclaringType is not null && td.DeclaringType.GenericParameters.Count >= td.GenericParameters.Count)) {
var oldFlags = options;
options &= ~(FormatterOptions.ShowNamespaces | FormatterOptions.ShowIntrinsicTypeKeywords);
if (useNamespaces)
options |= FormatterOptions.ShowNamespaces;
if (useTypeKeywords)
options |= FormatterOptions.ShowIntrinsicTypeKeywords;
Write(type);
options = oldFlags;
return;
}
int numGenParams = td.GenericParameters.Count;
if (type.DeclaringType is not null) {
var oldFlags = options;
options &= ~(FormatterOptions.ShowNamespaces | FormatterOptions.ShowIntrinsicTypeKeywords);
if (useNamespaces)
options |= FormatterOptions.ShowNamespaces;
Write(type.DeclaringType);
options = oldFlags;
WritePeriod();
numGenParams = numGenParams - td.DeclaringType!.GenericParameters.Count;
if (numGenParams < 0)
numGenParams = 0;
}
else if (useNamespaces && !UTF8String.IsNullOrEmpty(td.Namespace)) {
foreach (var ns in td.Namespace.String.Split('.')) {
WriteIdentifier(ns, BoxedTextColor.Namespace);
WritePeriod();
}
}
WriteIdentifier(TypeFormatterUtils.RemoveGenericTick(td.Name), CSharpMetadataTextColorProvider.Instance.GetColor(td));
WriteToken(type);
var genParams = td.GenericParameters.Skip(td.GenericParameters.Count - numGenParams).ToArray();
WriteGenerics(genParams, BoxedTextColor.TypeGenericParameter);
}
bool WriteRefIfByRef(TypeSig? typeSig, ParamDef? pd, bool forceReadOnly) {
if (typeSig.RemovePinnedAndModifiers() is ByRefSig) {
if (pd is not null && (!pd.IsIn && pd.IsOut)) {
OutputWrite(Keyword_out, BoxedTextColor.Keyword);
WriteSpace();
}
else if (pd is not null && (pd.IsIn && !pd.IsOut && TypeFormatterUtils.IsReadOnlyParameter(pd))) {
OutputWrite(Keyword_in, BoxedTextColor.Keyword);
WriteSpace();
}
else {
OutputWrite(Keyword_ref, BoxedTextColor.Keyword);
WriteSpace();
if (forceReadOnly) {
OutputWrite(Keyword_readonly, BoxedTextColor.Keyword);
WriteSpace();
}
}
return true;
}
return false;
}
void WriteAccessor(AccessorKind kind) {
string keyword;
switch (kind) {
case AccessorKind.None:
default:
throw new InvalidOperationException();
case AccessorKind.Getter:
keyword = Keyword_get;
break;
case AccessorKind.Setter:
keyword = Keyword_set;
break;
case AccessorKind.Adder:
keyword = Keyword_add;
break;
case AccessorKind.Remover:
keyword = Keyword_remove;
break;
}
OutputWrite(".", BoxedTextColor.Operator);
OutputWrite(keyword, BoxedTextColor.Keyword);
}
void Write(IMethod? method) {
if (method is null) {
WriteError();
return;
}
var propInfo = TypeFormatterUtils.TryGetProperty(method as MethodDef);
if (propInfo.kind != AccessorKind.None) {
Write(propInfo.property, writeAccessors: false);
WriteAccessor(propInfo.kind);
return;
}
var eventInfo = TypeFormatterUtils.TryGetEvent(method as MethodDef);
if (eventInfo.kind != AccessorKind.None) {
Write(eventInfo.@event, writeAccessors: false);
WriteAccessor(eventInfo.kind);
return;
}
var info = new FormatterMethodInfo(method);
WriteModuleName(info);
string[]? operatorInfo;
if (info.MethodDef is not null && info.MethodDef.IsConstructor && method.DeclaringType is not null)
operatorInfo = null;
else if (info.MethodDef is not null && info.MethodDef.Overrides.Count > 0) {
var ovrMeth = (IMemberRef)info.MethodDef.Overrides[0].MethodDeclaration;
operatorInfo = TryGetOperatorInfo(ovrMeth.Name);
}
else
operatorInfo = TryGetOperatorInfo(method.Name);
bool isExplicitOrImplicit = operatorInfo is not null && (operatorInfo[0] == "explicit" || operatorInfo[0] == "implicit");
if (!isExplicitOrImplicit)
WriteReturnType(info, writeSpace: true, isReadOnly: TypeFormatterUtils.IsReadOnlyMethod(info.MethodDef));
if (ShowDeclaringTypes) {
Write(method.DeclaringType);
WritePeriod();
}
if (info.MethodDef is not null && info.MethodDef.IsConstructor && method.DeclaringType is not null)
WriteIdentifier(TypeFormatterUtils.RemoveGenericTick(method.DeclaringType.Name), CSharpMetadataTextColorProvider.Instance.GetColor(method));
else if (info.MethodDef is not null && info.MethodDef.Overrides.Count > 0) {
var ovrMeth = (IMemberRef)info.MethodDef.Overrides[0].MethodDeclaration;
WriteMethodName(method, ovrMeth.Name, operatorInfo);
}
else
WriteMethodName(method, method.Name, operatorInfo);
if (isExplicitOrImplicit) {
WriteToken(method);
WriteSpace();
ForceWriteReturnType(info, writeSpace: false, isReadOnly: TypeFormatterUtils.IsReadOnlyMethod(info.MethodDef));
}
else
WriteToken(method);
WriteGenericArguments(info);
WriteMethodParameterList(info, MethodParenOpen, MethodParenClose);
}
static string[]? TryGetOperatorInfo(string name) {
nameToOperatorName.TryGetValue(name, out var list);
return list;
}
void WriteOperatorInfoString(string s) => OutputWrite(s, 'a' <= s[0] && s[0] <= 'z' ? BoxedTextColor.Keyword : BoxedTextColor.Operator);
void WriteMethodName(IMethod method, string name, string[]? operatorInfo) {
if (operatorInfo is not null) {
for (int i = 0; i < operatorInfo.Length; i++) {
if (i > 0)
WriteSpace();
var s = operatorInfo[i];
WriteOperatorInfoString(s);
}
}
else
WriteIdentifier(name, CSharpMetadataTextColorProvider.Instance.GetColor(method));
}
void WriteToolTip(IField field) {
WriteDeprecated(TypeFormatterUtils.IsDeprecated(field));
Write(field, true);
}
void Write(IField field) => Write(field, false);
void Write(IField? field, bool isToolTip) {
if (field is null) {
WriteError();
return;
}
var sig = field.FieldSig;
var td = field.DeclaringType.ResolveTypeDef();
bool isEnumOwner = td is not null && td.IsEnum;
var fd = field.ResolveFieldDef();
object? constant = null;
bool isConstant = fd is not null && (fd.IsLiteral || (fd.IsStatic && fd.IsInitOnly)) && TypeFormatterUtils.HasConstant(fd, out var constantAttribute) && TypeFormatterUtils.TryGetConstant(fd, constantAttribute, out constant);
if (!isEnumOwner || (fd is not null && !fd.IsLiteral)) {
if (isToolTip) {
OutputWrite(DescriptionParenOpen, BoxedTextColor.Punctuation);
OutputWrite(isConstant ? dnSpy_Decompiler_Resources.ToolTip_Constant : dnSpy_Decompiler_Resources.ToolTip_Field, BoxedTextColor.Text);
OutputWrite(DescriptionParenClose, BoxedTextColor.Punctuation);
WriteSpace();
}
WriteModuleName(fd?.Module);
Write(sig.Type, null, null, null);
WriteSpace();
}
else
WriteModuleName(fd?.Module);
if (ShowDeclaringTypes) {
Write(field.DeclaringType);
WritePeriod();
}
WriteIdentifier(field.Name, CSharpMetadataTextColorProvider.Instance.GetColor(field));
WriteToken(field);
if (ShowFieldLiteralValues && isConstant) {
WriteSpace();
OutputWrite("=", BoxedTextColor.Operator);
WriteSpace();
WriteConstant(constant);
}
}
void WriteConstant(object? obj) {
if (obj is null) {
OutputWrite(Keyword_null, BoxedTextColor.Keyword);
return;
}
switch (Type.GetTypeCode(obj.GetType())) {
case TypeCode.Boolean:
FormatBoolean((bool)obj);
break;
case TypeCode.Char:
FormatChar((char)obj);
break;
case TypeCode.SByte:
FormatSByte((sbyte)obj);
break;
case TypeCode.Byte:
FormatByte((byte)obj);
break;
case TypeCode.Int16:
FormatInt16((short)obj);
break;
case TypeCode.UInt16:
FormatUInt16((ushort)obj);
break;
case TypeCode.Int32:
FormatInt32((int)obj);
break;
case TypeCode.UInt32:
FormatUInt32((uint)obj);
break;
case TypeCode.Int64:
FormatInt64((long)obj);
break;
case TypeCode.UInt64:
FormatUInt64((ulong)obj);
break;
case TypeCode.Single:
FormatSingle((float)obj);
break;
case TypeCode.Double:
FormatDouble((double)obj);
break;
case TypeCode.Decimal:
FormatDecimal((decimal)obj);
break;
case TypeCode.String:
FormatString((string)obj);
break;
default:
Debug.Fail($"Unknown constant: '{obj}'");
OutputWrite(obj.ToString() ?? "???", BoxedTextColor.Text);
break;
}
}
void WriteToolTip(PropertyDef prop) {
WriteDeprecated(TypeFormatterUtils.IsDeprecated(prop));
Write(prop);
}
void Write(PropertyDef? prop) => Write(prop, writeAccessors: true);
void Write(PropertyDef? prop, bool writeAccessors) {
if (prop is null) {
WriteError();
return;
}
var getMethod = prop.GetMethods.FirstOrDefault();
var setMethod = prop.SetMethods.FirstOrDefault();
var md = getMethod ?? setMethod;
if (md is null) {
WriteError();
return;
}
var info = new FormatterMethodInfo(md, md == setMethod);
WriteModuleName(info);
WriteReturnType(info, writeSpace: true, isReadOnly: TypeFormatterUtils.IsReadOnlyProperty(prop));
if (ShowDeclaringTypes) {
Write(prop.DeclaringType);
WritePeriod();
}
var ovrMeth = md is null || md.Overrides.Count == 0 ? null : md.Overrides[0].MethodDeclaration;
if (prop.IsIndexer()) {
OutputWrite(Keyword_this, BoxedTextColor.Keyword);
WriteGenericArguments(info);
WriteMethodParameterList(info, IndexerParenOpen, IndexerParenClose);
}
else if (ovrMeth is not null && TypeFormatterUtils.GetPropertyName(ovrMeth) is string ovrMethPropName)
WriteIdentifier(ovrMethPropName, CSharpMetadataTextColorProvider.Instance.GetColor(prop));
else
WriteIdentifier(prop.Name, CSharpMetadataTextColorProvider.Instance.GetColor(prop));
WriteToken(prop);
if (writeAccessors) {
WriteSpace();
OutputWrite("{", BoxedTextColor.Punctuation);
if (prop.GetMethods.Count > 0) {
WriteSpace();
OutputWrite(Keyword_get, BoxedTextColor.Keyword);
OutputWrite(";", BoxedTextColor.Punctuation);
}
if (prop.SetMethods.Count > 0) {
WriteSpace();
OutputWrite(Keyword_set, BoxedTextColor.Keyword);
OutputWrite(";", BoxedTextColor.Punctuation);
}
WriteSpace();
OutputWrite("}", BoxedTextColor.Punctuation);
}
}
void WriteToolTip(EventDef? evt) {
WriteDeprecated(TypeFormatterUtils.IsDeprecated(evt));
Write(evt);
}
void Write(EventDef? evt) => Write(evt, writeAccessors: true);
void Write(EventDef? evt, bool writeAccessors) {
if (evt is null) {
WriteError();
return;
}
WriteModuleName(evt.Module);
Write(evt.EventType);
WriteSpace();
if (ShowDeclaringTypes) {
Write(evt.DeclaringType);
WritePeriod();
}
WriteIdentifier(evt.Name, CSharpMetadataTextColorProvider.Instance.GetColor(evt));
WriteToken(evt);
}
void WriteToolTip(GenericParam? gp) {
if (gp is null) {
WriteError();
return;
}
Write(gp);
WriteSpace();
OutputWrite(dnSpy_Decompiler_Resources.ToolTip_GenericParameterInTypeOrMethod, BoxedTextColor.Text);
WriteSpace();
if (gp.Owner is TypeDef td)
WriteType(td, ShowNamespaces, ShowIntrinsicTypeKeywords);
else
Write(gp.Owner as MethodDef);
}
void Write(GenericParam? gp) {
if (gp is null) {
WriteError();
return;
}
WriteIdentifier(gp.Name, CSharpMetadataTextColorProvider.Instance.GetColor(gp));
WriteToken(gp);
}
void WriteToolTip(ITypeDefOrRef type) {
var td = type.ResolveTypeDef();
WriteDeprecated(TypeFormatterUtils.IsDeprecated(type));
Write(TypeFormatterUtils.GetMemberSpecialFlags(type));
MethodDef invoke;
if (TypeFormatterUtils.IsDelegate(td) && (invoke = td.FindMethod("Invoke")) is not null && invoke.MethodSig is not null) {
OutputWrite(Keyword_delegate, BoxedTextColor.Keyword);
WriteSpace();
var info = new FormatterMethodInfo(invoke);
WriteModuleName(info);
WriteReturnType(info, writeSpace: true, isReadOnly: TypeFormatterUtils.IsReadOnlyMethod(info.MethodDef));
// Always print the namespace here because that's what VS does
WriteType(td, true, ShowIntrinsicTypeKeywords);
WriteGenericArguments(info);
WriteMethodParameterList(info, MethodParenOpen, MethodParenClose);
return;
}
else
WriteModuleName(td?.Module);
if (td is null) {
Write(type);
return;
}
string keyword;
if (td.IsEnum)
keyword = Keyword_enum;
else if (td.IsValueType) {
if (TypeFormatterUtils.IsReadOnlyType(td)) {
OutputWrite(Keyword_readonly, BoxedTextColor.Keyword);
WriteSpace();
}
if (TypeFormatterUtils.IsByRefLike(td)) {
OutputWrite(Keyword_ref, BoxedTextColor.Keyword);
WriteSpace();
}
keyword = Keyword_struct;
}
else if (td.IsInterface)
keyword = Keyword_interface;
else
keyword = Keyword_class;
OutputWrite(keyword, BoxedTextColor.Keyword);
WriteSpace();
// Always print the namespace here because that's what VS does
WriteType(type, true, false);
}
void Write(ITypeDefOrRef? type, bool showModuleNames = false) {
if (type is null) {
WriteError();
return;
}
if (recursionCounter >= TypeFormatterUtils.MAX_RECURSION)
return;
recursionCounter++;
try {
if (type is TypeSpec ts) {
Write(ts.TypeSig, null, null, null);
return;
}
if (type.DeclaringType is not null) {
Write(type.DeclaringType);
WritePeriod();
}
string? keyword = GetTypeKeyword(type);
if (keyword is not null)
OutputWrite(keyword, BoxedTextColor.Keyword);
else {
if (showModuleNames)
WriteModuleName(type.ResolveTypeDef()?.Module);
WriteNamespace(type.Namespace);
WriteIdentifier(TypeFormatterUtils.RemoveGenericTick(type.Name), CSharpMetadataTextColorProvider.Instance.GetColor(type));
}
WriteToken(type);
}
finally {
recursionCounter--;
}
}
void WriteNamespace(string ns) {
if (!ShowNamespaces || string.IsNullOrEmpty(ns))
return;
var namespaces = ns.Split(nsSep);
for (int i = 0; i < namespaces.Length; i++) {
OutputWrite(namespaces[i], BoxedTextColor.Namespace);
WritePeriod();
}
}
static readonly char[] nsSep = new char[] { '.' };
string? GetTypeKeyword(ITypeDefOrRef? type) {
if (!ShowIntrinsicTypeKeywords)
return null;
if (type is null || type.DeclaringType is not null || type.Namespace != "System" || !type.DefinitionAssembly.IsCorLib())
return null;
switch (type.TypeName) {
case "Void": return "void";
case "Boolean": return "bool";
case "Byte": return "byte";
case "Char": return "char";
case "Decimal": return "decimal";
case "Double": return "double";
case "Int16": return "short";
case "Int32": return "int";
case "Int64": return "long";
case "Object": return "object";
case "SByte": return "sbyte";
case "Single": return "float";
case "String": return "string";
case "UInt16": return "ushort";
case "UInt32": return "uint";
case "UInt64": return "ulong";
default: return null;
}
}
void Write(TypeSig? type, ParamDef? ownerParam, IList? typeGenArgs, IList? methGenArgs, bool forceReadOnly = false) {
WriteRefIfByRef(type, ownerParam, forceReadOnly);
if (type.RemovePinnedAndModifiers() is ByRefSig byRef)
type = byRef.Next;
Write(type, typeGenArgs, methGenArgs);
}
void Write(TypeSig? type, IList? typeGenArgs, IList? methGenArgs) {
if (type is null) {
WriteError();
return;
}
if (recursionCounter >= TypeFormatterUtils.MAX_RECURSION)
return;
recursionCounter++;
try {
if (typeGenArgs is null)
typeGenArgs = Array.Empty();
if (methGenArgs is null)
methGenArgs = Array.Empty();
List? list = null;
while (type is not null && (type.ElementType == ElementType.SZArray || type.ElementType == ElementType.Array)) {
if (list is null)
list = new List();
list.Add((ArraySigBase)type);
type = type.Next;
}
if (list is not null) {
Write(list[list.Count - 1].Next, typeGenArgs, Array.Empty());
foreach (var aryType in list) {
if (aryType.ElementType == ElementType.Array) {
OutputWrite(ArrayParenOpen, BoxedTextColor.Punctuation);
uint rank = aryType.Rank;
if (rank == 0)
OutputWrite("", BoxedTextColor.Error);
else {
var indexes = aryType.GetLowerBounds();
var dims = aryType.GetSizes();
if (ShowArrayValueSizes && (uint)indexes.Count == rank && (uint)dims.Count == rank) {
for (int i = 0; (uint)i < rank; i++) {
if (i > 0)
WriteCommaSpace();
if (i < indexes.Count && indexes[i] == 0)
FormatInt32((int)dims[i]);
else if (i < indexes.Count && i < dims.Count) {
FormatInt32((int)indexes[i]);
OutputWrite("..", BoxedTextColor.Operator);
FormatInt32((int)(indexes[i] + dims[i] - 1));
}
}
}
else {
if (rank == 1)
OutputWrite("*", BoxedTextColor.Operator);
for (uint i = 1; i < rank; i++)
OutputWrite(",", BoxedTextColor.Punctuation);
}
}
OutputWrite(ArrayParenClose, BoxedTextColor.Punctuation);
}
else {
Debug.Assert(aryType.ElementType == ElementType.SZArray);
OutputWrite(ArrayParenOpen, BoxedTextColor.Punctuation);
OutputWrite(ArrayParenClose, BoxedTextColor.Punctuation);
}
}
return;
}
if (type is null)
return;
switch (type.ElementType) {
case ElementType.Void: WriteSystemTypeKeyword("Void", "void", true); break;
case ElementType.Boolean: WriteSystemTypeKeyword("Boolean", "bool", true); break;
case ElementType.Char: WriteSystemTypeKeyword("Char", "char", true); break;
case ElementType.I1: WriteSystemTypeKeyword("SByte", "sbyte", true); break;
case ElementType.U1: WriteSystemTypeKeyword("Byte", "byte", true); break;
case ElementType.I2: WriteSystemTypeKeyword("Int16", "short", true); break;
case ElementType.U2: WriteSystemTypeKeyword("UInt16", "ushort", true); break;
case ElementType.I4: WriteSystemTypeKeyword("Int32", "int", true); break;
case ElementType.U4: WriteSystemTypeKeyword("UInt32", "uint", true); break;
case ElementType.I8: WriteSystemTypeKeyword("Int64", "long", true); break;
case ElementType.U8: WriteSystemTypeKeyword("UInt64", "ulong", true); break;
case ElementType.R4: WriteSystemTypeKeyword("Single", "float", true); break;
case ElementType.R8: WriteSystemTypeKeyword("Double", "double", true); break;
case ElementType.String: WriteSystemTypeKeyword("String", "string", false); break;
case ElementType.Object: WriteSystemTypeKeyword("Object", "object", false); break;
case ElementType.TypedByRef: WriteSystemType("TypedReference", true); break;
case ElementType.I: WriteSystemType("IntPtr", true); break;
case ElementType.U: WriteSystemType("UIntPtr", true); break;
case ElementType.Ptr:
Write(type.Next, typeGenArgs, methGenArgs);
OutputWrite("*", BoxedTextColor.Operator);
break;
case ElementType.ByRef:
Write(type.Next, typeGenArgs, methGenArgs);
OutputWrite("&", BoxedTextColor.Operator);
break;
case ElementType.ValueType:
case ElementType.Class:
var cvt = (TypeDefOrRefSig)type;
Write(cvt.TypeDefOrRef);
break;
case ElementType.Var:
case ElementType.MVar:
var gsType = Read(type.ElementType == ElementType.Var ? typeGenArgs : methGenArgs, (int)((GenericSig)type).Number);
if (gsType is not null)
Write(gsType, typeGenArgs, methGenArgs);
else {
var gp = ((GenericSig)type).GenericParam;
if (gp is not null)
Write(gp);
else {
if (type.ElementType == ElementType.MVar) {
OutputWrite("!!", BoxedTextColor.MethodGenericParameter);
OutputWrite(((GenericSig)type).Number.ToString(), BoxedTextColor.MethodGenericParameter);
}
else {
OutputWrite("!", BoxedTextColor.TypeGenericParameter);
OutputWrite(((GenericSig)type).Number.ToString(), BoxedTextColor.TypeGenericParameter);
}
}
}
break;
case ElementType.GenericInst:
var gis = (GenericInstSig?)type;
Debug2.Assert(gis is not null);
if (TypeFormatterUtils.IsSystemNullable(gis)) {
Write(GenericArgumentResolver.Resolve(gis.GenericArguments[0], typeGenArgs, methGenArgs), null, null);
OutputWrite("?", BoxedTextColor.Operator);
}
else if (TypeFormatterUtils.IsSystemValueTuple(gis)) {
OutputWrite(TupleParenOpen, BoxedTextColor.Punctuation);
bool needComma = false;
for (int i = 0; i < 1000; i++) {
for (int j = 0; j < gis.GenericArguments.Count && j < 7; j++) {
if (needComma)
WriteCommaSpace();
needComma = true;
Write(GenericArgumentResolver.Resolve(gis.GenericArguments[j], typeGenArgs, methGenArgs), null, null);
}
if (gis.GenericArguments.Count != 8)
break;
gis = gis.GenericArguments[gis.GenericArguments.Count - 1] as GenericInstSig;
if (gis is null) {
WriteError();
break;
}
}
OutputWrite(TupleParenClose, BoxedTextColor.Punctuation);
}
else {
Write(gis.GenericType, null, null);
OutputWrite(GenericParenOpen, BoxedTextColor.Punctuation);
for (int i = 0; i < gis.GenericArguments.Count; i++) {
if (i > 0)
WriteCommaSpace();
Write(GenericArgumentResolver.Resolve(gis.GenericArguments[i], typeGenArgs, methGenArgs), null, null);
}
OutputWrite(GenericParenClose, BoxedTextColor.Punctuation);
}
break;
case ElementType.FnPtr:
var sig = ((FnPtrSig)type).MethodSig;
Write(sig.RetType, typeGenArgs, methGenArgs);
WriteSpace();
OutputWrite(MethodParenOpen, BoxedTextColor.Punctuation);
for (int i = 0; i < sig.Params.Count; i++) {
if (i > 0)
WriteCommaSpace();
Write(sig.Params[i], typeGenArgs, methGenArgs);
}
if (sig.ParamsAfterSentinel is not null) {
if (sig.Params.Count > 0)
WriteCommaSpace();
OutputWrite("...", BoxedTextColor.Punctuation);
for (int i = 0; i < sig.ParamsAfterSentinel.Count; i++) {
WriteCommaSpace();
Write(sig.ParamsAfterSentinel[i], typeGenArgs, methGenArgs);
}
}
OutputWrite(MethodParenClose, BoxedTextColor.Punctuation);
break;
case ElementType.CModReqd:
case ElementType.CModOpt:
case ElementType.Pinned:
Write(type.Next, typeGenArgs, methGenArgs);
break;
case ElementType.End:
case ElementType.Array: // handled above
case ElementType.ValueArray:
case ElementType.R:
case ElementType.SZArray: // handled above
case ElementType.Internal:
case ElementType.Module:
case ElementType.Sentinel:
default:
break;
}
}
finally {
recursionCounter--;
}
}
TypeSig? Read(IList list, int index) {
if ((uint)index < (uint)list.Count)
return list[index];
return null;
}
public void WriteToolTip(ISourceVariable? variable) {
if (variable is null) {
WriteError();
return;
}
var isLocal = variable.IsLocal;
var pd = (variable.Variable as Parameter)?.ParamDef;
OutputWrite(DescriptionParenOpen, BoxedTextColor.Punctuation);
OutputWrite(isLocal ? dnSpy_Decompiler_Resources.ToolTip_Local : dnSpy_Decompiler_Resources.ToolTip_Parameter, BoxedTextColor.Text);
OutputWrite(DescriptionParenClose, BoxedTextColor.Punctuation);
WriteSpace();
Write(variable.Type, !isLocal ? ((Parameter)variable.Variable!).ParamDef : null, null, null, forceReadOnly: (variable.Flags & SourceVariableFlags.ReadOnlyReference) != 0);
WriteSpace();
WriteIdentifier(TypeFormatterUtils.GetName(variable), isLocal ? BoxedTextColor.Local : BoxedTextColor.Parameter);
if (pd is not null)
WriteToken(pd);
}
public void WriteNamespaceToolTip(string? @namespace) {
if (@namespace is null) {
WriteError();
return;
}
OutputWrite(Keyword_namespace, BoxedTextColor.Keyword);
WriteSpace();
var parts = @namespace.Split(namespaceSeparators);
for (int i = 0; i < parts.Length; i++) {
if (i > 0)
OutputWrite(".", BoxedTextColor.Operator);
OutputWrite(parts[i], BoxedTextColor.Namespace);
}
}
static readonly char[] namespaceSeparators = new char[] { '.' };
void Write(ModuleDef? module) {
try {
if (recursionCounter++ >= TypeFormatterUtils.MAX_RECURSION)
return;
if (module is null) {
OutputWrite("null module", BoxedTextColor.Error);
return;
}
var name = TypeFormatterUtils.GetFileName(module.Location);
OutputWrite(TypeFormatterUtils.FilterName(name), BoxedTextColor.AssemblyModule);
}
finally {
recursionCounter--;
}
}
void WriteModuleName(in FormatterMethodInfo info) {
if (!ShowModuleNames)
return;
Write(info.ModuleDef);
OutputWrite(ModuleNameSeparator, BoxedTextColor.Operator);
return;
}
void WriteModuleName(ModuleDef? module) {
if (module is null)
return;
if (!ShowModuleNames)
return;
Write(module);
OutputWrite(ModuleNameSeparator, BoxedTextColor.Operator);
return;
}
void WriteReturnType(in FormatterMethodInfo info, bool writeSpace, bool isReadOnly) {
if (!ShowReturnTypes)
return;
if (info.MethodDef?.IsConstructor == true)
return;
ForceWriteReturnType(info, writeSpace, isReadOnly);
}
void ForceWriteReturnType(in FormatterMethodInfo info, bool writeSpace, bool isReadOnly) {
if (!(info.MethodDef is not null && info.MethodDef.IsConstructor)) {
TypeSig? retType;
ParamDef? retParamDef;
if (info.RetTypeIsLastArgType) {
retType = info.MethodSig.Params.LastOrDefault();
if (info.MethodDef is null)
retParamDef = null;
else {
var l = info.MethodDef.Parameters.LastOrDefault();
retParamDef = l is null ? null : l.ParamDef;
}
}
else {
retType = info.MethodSig.RetType;
retParamDef = info.MethodDef is null ? null : info.MethodDef.Parameters.ReturnParameter.ParamDef;
}
if (retType.RemovePinnedAndModifiers() is ByRefSig && isReadOnly) {
retType = retType.RemovePinnedAndModifiers().Next;
OutputWrite(Keyword_ref, BoxedTextColor.Keyword);
WriteSpace();
OutputWrite(Keyword_readonly, BoxedTextColor.Keyword);
WriteSpace();
}
Write(retType, retParamDef, info.TypeGenericParams, info.MethodGenericParams);
if (writeSpace)
WriteSpace();
}
}
void WriteGenericArguments(in FormatterMethodInfo info) {
if (info.MethodSig.GenParamCount > 0) {
if (info.MethodGenericParams is not null)
WriteGenerics(info.MethodGenericParams, BoxedTextColor.MethodGenericParameter, GenericParamContext.Create(info.MethodDef));
else if (info.MethodDef is not null)
WriteGenerics(info.MethodDef.GenericParameters, BoxedTextColor.MethodGenericParameter);
}
}
void WriteMethodParameterList(in FormatterMethodInfo info, string lparen, string rparen) {
if (!ShowParameterTypes && !ShowParameterNames)
return;
OutputWrite(lparen, BoxedTextColor.Punctuation);
int baseIndex = info.MethodSig.HasThis ? 1 : 0;
int count = info.MethodSig.Params.Count;
if (info.RetTypeIsLastArgType)
count--;
for (int i = 0; i < count; i++) {
if (i > 0)
WriteCommaSpace();
ParamDef? pd;
if (info.MethodDef is not null && baseIndex + i < info.MethodDef.Parameters.Count)
pd = info.MethodDef.Parameters[baseIndex + i].ParamDef;
else
pd = null;
bool isDefault = TypeFormatterUtils.HasConstant(pd, out var constantAttribute);
if (isDefault)
OutputWrite(DefaultParamValueParenOpen, BoxedTextColor.Punctuation);
bool needSpace = false;
if (ShowParameterTypes) {
needSpace = true;
if (pd is not null && pd.CustomAttributes.IsDefined("System.ParamArrayAttribute")) {
OutputWrite(Keyword_params, BoxedTextColor.Keyword);
WriteSpace();
}
var paramType = info.MethodSig.Params[i];
Write(paramType, pd, info.TypeGenericParams, info.MethodGenericParams);
}
if (ShowParameterNames) {
if (needSpace)
WriteSpace();
needSpace = true;
if (pd is not null) {
WriteIdentifier(pd.Name, BoxedTextColor.Parameter);
WriteToken(pd);
}
else
WriteIdentifier("A_" + (baseIndex + i).ToString(), BoxedTextColor.Parameter);
}
if (ShowParameterLiteralValues && isDefault && TypeFormatterUtils.TryGetConstant(pd, constantAttribute, out var constant)) {
if (needSpace)
WriteSpace();
needSpace = true;
WriteSpace();
OutputWrite("=", BoxedTextColor.Operator);
WriteSpace();
var t = info.MethodSig.Params[i].RemovePinnedAndModifiers();
if (t.GetElementType() == ElementType.ByRef)
t = t.Next;
if (constant is null && t is not null && t.IsValueType)
OutputWrite(Keyword_default, BoxedTextColor.Keyword);
else
WriteConstant(constant);
}
if (isDefault)
OutputWrite(DefaultParamValueParenClose, BoxedTextColor.Punctuation);
}
OutputWrite(rparen, BoxedTextColor.Punctuation);
}
void WriteGenerics(IList? gps, object gpTokenType) {
if (gps is null || gps.Count == 0)
return;
OutputWrite(GenericParenOpen, BoxedTextColor.Punctuation);
for (int i = 0; i < gps.Count; i++) {
if (i > 0)
WriteCommaSpace();
var gp = gps[i];
if (gp.IsCovariant) {
OutputWrite(Keyword_out, BoxedTextColor.Keyword);
WriteSpace();
}
else if (gp.IsContravariant) {
OutputWrite(Keyword_in, BoxedTextColor.Keyword);
WriteSpace();
}
WriteIdentifier(gp.Name, gpTokenType);
WriteToken(gp);
}
OutputWrite(GenericParenClose, BoxedTextColor.Punctuation);
}
void WriteGenerics(IList? gps, object gpTokenType, GenericParamContext gpContext) {
if (gps is null || gps.Count == 0)
return;
OutputWrite(GenericParenOpen, BoxedTextColor.Punctuation);
for (int i = 0; i < gps.Count; i++) {
if (i > 0)
WriteCommaSpace();
Write(gps[i], null, null, null);
}
OutputWrite(GenericParenClose, BoxedTextColor.Punctuation);
}
void FormatBoolean(bool value) {
if (value)
OutputWrite(Keyword_true, BoxedTextColor.Keyword);
else
OutputWrite(Keyword_false, BoxedTextColor.Keyword);
}
void FormatChar(char value) => OutputWrite(ToFormattedChar(value), BoxedTextColor.Char);
string ToFormattedChar(char value) {
var sb = new StringBuilder();
sb.Append('\'');
switch (value) {
case '\a': sb.Append(@"\a"); break;
case '\b': sb.Append(@"\b"); break;
case '\f': sb.Append(@"\f"); break;
case '\n': sb.Append(@"\n"); break;
case '\r': sb.Append(@"\r"); break;
case '\t': sb.Append(@"\t"); break;
case '\v': sb.Append(@"\v"); break;
case '\\': sb.Append(@"\\"); break;
case '\0': sb.Append(@"\0"); break;
case '\'': sb.Append(@"\'"); break;
default:
if (char.IsControl(value)) {
sb.Append(@"\u");
sb.Append(((ushort)value).ToString("X4"));
}
else
sb.Append(value);
break;
}
sb.Append('\'');
return sb.ToString();
}
static bool CanUseVerbatimString(string s) {
bool foundBackslash = false;
foreach (var c in s) {
switch (c) {
case '"':
break;
case '\\':
foundBackslash = true;
break;
case '\a':
case '\b':
case '\f':
case '\n':
case '\r':
case '\t':
case '\v':
case '\0':
// More newline chars
case '\u0085':
case '\u2028':
case '\u2029':
return false;
default:
if (char.IsControl(c))
return false;
break;
}
}
return foundBackslash;
}
void FormatString(string value) {
var s = ToFormattedString(value, out bool isVerbatim);
OutputWrite(s, isVerbatim ? BoxedTextColor.VerbatimString : BoxedTextColor.String);
}
string ToFormattedString(string value, out bool isVerbatim) {
if (CanUseVerbatimString(value)) {
isVerbatim = true;
return GetFormattedVerbatimString(value);
}
else {
isVerbatim = false;
return GetFormattedString(value);
}
}
string GetFormattedString(string value) {
var sb = new StringBuilder();
sb.Append('"');
foreach (var c in value) {
switch (c) {
case '\a': sb.Append(@"\a"); break;
case '\b': sb.Append(@"\b"); break;
case '\f': sb.Append(@"\f"); break;
case '\n': sb.Append(@"\n"); break;
case '\r': sb.Append(@"\r"); break;
case '\t': sb.Append(@"\t"); break;
case '\v': sb.Append(@"\v"); break;
case '\\': sb.Append(@"\\"); break;
case '\0': sb.Append(@"\0"); break;
case '"': sb.Append("\\\""); break;
default:
if (char.IsControl(c)) {
sb.Append(@"\u");
sb.Append(((ushort)c).ToString("X4"));
}
else
sb.Append(c);
break;
}
}
sb.Append('"');
return sb.ToString();
}
string GetFormattedVerbatimString(string value) {
var sb = new StringBuilder();
sb.Append(VerbatimStringPrefix + "\"");
foreach (var c in value) {
if (c == '"')
sb.Append("\"\"");
else
sb.Append(c);
}
sb.Append('"');
return sb.ToString();
}
string ToFormattedDecimalNumber(string number) => ToFormattedNumber(string.Empty, number, TypeFormatterUtils.DigitGroupSizeDecimal);
string ToFormattedHexNumber(string number) => ToFormattedNumber(HexPrefix, number, TypeFormatterUtils.DigitGroupSizeHex);
string ToFormattedNumber(string prefix, string number, int digitGroupSize) => TypeFormatterUtils.ToFormattedNumber(DigitSeparators, prefix, number, digitGroupSize);
void WriteNumber(string number) => OutputWrite(number, BoxedTextColor.Number);
string ToFormattedSByte(sbyte value) {
if (UseDecimal)
return ToFormattedDecimalNumber(value.ToString(cultureInfo));
else
return ToFormattedHexNumber(value.ToString("X2"));
}
string ToFormattedByte(byte value) {
if (UseDecimal)
return ToFormattedDecimalNumber(value.ToString(cultureInfo));
else
return ToFormattedHexNumber(value.ToString("X2"));
}
string ToFormattedInt16(short value) {
if (UseDecimal)
return ToFormattedDecimalNumber(value.ToString(cultureInfo));
else
return ToFormattedHexNumber(value.ToString("X4"));
}
string ToFormattedUInt16(ushort value) {
if (UseDecimal)
return ToFormattedDecimalNumber(value.ToString(cultureInfo));
else
return ToFormattedHexNumber(value.ToString("X4"));
}
string ToFormattedInt32(int value) {
if (UseDecimal)
return ToFormattedDecimalNumber(value.ToString(cultureInfo));
else
return ToFormattedHexNumber(value.ToString("X8"));
}
string ToFormattedUInt32(uint value) {
if (UseDecimal)
return ToFormattedDecimalNumber(value.ToString(cultureInfo));
else
return ToFormattedHexNumber(value.ToString("X8"));
}
string ToFormattedInt64(long value) {
if (UseDecimal)
return ToFormattedDecimalNumber(value.ToString(cultureInfo));
else
return ToFormattedHexNumber(value.ToString("X16"));
}
string ToFormattedUInt64(ulong value) {
if (UseDecimal)
return ToFormattedDecimalNumber(value.ToString(cultureInfo));
else
return ToFormattedHexNumber(value.ToString("X16"));
}
void FormatSingle(float value) {
if (float.IsNaN(value))
OutputWrite(TypeFormatterUtils.NaN, BoxedTextColor.Number);
else if (float.IsNegativeInfinity(value))
OutputWrite(TypeFormatterUtils.NegativeInfinity, BoxedTextColor.Number);
else if (float.IsPositiveInfinity(value))
OutputWrite(TypeFormatterUtils.PositiveInfinity, BoxedTextColor.Number);
else
OutputWrite(value.ToString(cultureInfo), BoxedTextColor.Number);
}
void FormatDouble(double value) {
if (double.IsNaN(value))
OutputWrite(TypeFormatterUtils.NaN, BoxedTextColor.Number);
else if (double.IsNegativeInfinity(value))
OutputWrite(TypeFormatterUtils.NegativeInfinity, BoxedTextColor.Number);
else if (double.IsPositiveInfinity(value))
OutputWrite(TypeFormatterUtils.PositiveInfinity, BoxedTextColor.Number);
else
OutputWrite(value.ToString(cultureInfo), BoxedTextColor.Number);
}
void FormatSByte(sbyte value) => WriteNumber(ToFormattedSByte(value));
void FormatByte(byte value) => WriteNumber(ToFormattedByte(value));
void FormatInt16(short value) => WriteNumber(ToFormattedInt16(value));
void FormatUInt16(ushort value) => WriteNumber(ToFormattedUInt16(value));
void FormatInt32(int value) => WriteNumber(ToFormattedInt32(value));
void FormatUInt32(uint value) => WriteNumber(ToFormattedUInt32(value));
void FormatInt64(long value) => WriteNumber(ToFormattedInt64(value));
void FormatUInt64(ulong value) => WriteNumber(ToFormattedUInt64(value));
void FormatDecimal(decimal value) => OutputWrite(value.ToString(cultureInfo), BoxedTextColor.Number);
}
}