// Copyright (c) 2010-2013 AlphaSierraPapa for the SharpDevelop Team // // Permission is hereby granted, free of charge, to any person obtaining a copy of this // software and associated documentation files (the "Software"), to deal in the Software // without restriction, including without limitation the rights to use, copy, modify, merge, // publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons // to whom the Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all copies or // substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; using System.Globalization; using System.IO; using System.Text; using ICSharpCode.Decompiler.CSharp.Syntax; namespace ICSharpCode.Decompiler.CSharp.OutputVisitor { /// /// Writes C# code into a TextWriter. /// public class TextWriterTokenWriter : TokenWriter, ILocatable { readonly TextWriter textWriter; bool needsIndent = true; bool isAtStartOfLine = true; int line, column; public int Indentation { get; set; } public TextLocation Location { get { return new TextLocation(line, column + (needsIndent ? Indentation * IndentationString.Length : 0)); } } public string IndentationString { get; set; } public int Length { get; private set; } public TextWriterTokenWriter(TextWriter textWriter) { if (textWriter == null) throw new ArgumentNullException(nameof(textWriter)); this.textWriter = textWriter; this.IndentationString = "\t"; this.line = 1; this.column = 1; } public override void WriteIdentifier(Identifier identifier) { WriteIndentation(); if (identifier.IsVerbatim || CSharpOutputVisitor.IsKeyword(identifier.Name, identifier)) { textWriter.Write('@'); column++; Length++; } string name = EscapeIdentifier(identifier.Name); textWriter.Write(name); column += name.Length; Length += name.Length; isAtStartOfLine = false; } public override void WriteKeyword(Role role, string keyword) { WriteIndentation(); column += keyword.Length; Length += keyword.Length; textWriter.Write(keyword); isAtStartOfLine = false; } public override void WriteToken(Role role, string token) { WriteIndentation(); column += token.Length; Length += token.Length; textWriter.Write(token); isAtStartOfLine = false; } public override void Space() { WriteIndentation(); column++; Length++; textWriter.Write(' '); } protected void WriteIndentation() { if (needsIndent) { needsIndent = false; for (int i = 0; i < Indentation; i++) { textWriter.Write(this.IndentationString); } column += Indentation * IndentationString.Length; Length += Indentation * IndentationString.Length; } } public override void NewLine() { textWriter.WriteLine(); column = 1; line++; Length += textWriter.NewLine.Length; needsIndent = true; isAtStartOfLine = true; } public override void Indent() { Indentation++; } public override void Unindent() { Indentation--; } public override void WriteComment(CommentType commentType, string content) { WriteIndentation(); switch (commentType) { case CommentType.SingleLine: textWriter.Write("//"); textWriter.WriteLine(content); Length += 2 + content.Length + textWriter.NewLine.Length; column = 1; line++; needsIndent = true; isAtStartOfLine = true; break; case CommentType.MultiLine: textWriter.Write("/*"); textWriter.Write(content); textWriter.Write("*/"); Length += 4 + content.Length; column += 2; UpdateEndLocation(content, ref line, ref column); column += 2; isAtStartOfLine = false; break; case CommentType.Documentation: textWriter.Write("///"); textWriter.WriteLine(content); Length += 3 + content.Length + textWriter.NewLine.Length; column = 1; line++; needsIndent = true; isAtStartOfLine = true; break; case CommentType.MultiLineDocumentation: textWriter.Write("/**"); textWriter.Write(content); textWriter.Write("*/"); Length += 5 + content.Length; column += 3; UpdateEndLocation(content, ref line, ref column); column += 2; isAtStartOfLine = false; break; default: textWriter.Write(content); column += content.Length; Length += content.Length; break; } } static void UpdateEndLocation(string content, ref int line, ref int column) { if (string.IsNullOrEmpty(content)) return; for (int i = 0; i < content.Length; i++) { char ch = content[i]; switch (ch) { case '\r': if (i + 1 < content.Length && content[i + 1] == '\n') i++; goto case '\n'; case '\n': line++; column = 0; break; } column++; } } public override void WritePreProcessorDirective(PreProcessorDirectiveType type, string argument) { // pre-processor directive must start on its own line if (!isAtStartOfLine) NewLine(); WriteIndentation(); textWriter.Write('#'); string directive = type.ToString().ToLowerInvariant(); textWriter.Write(directive); column += 1 + directive.Length; Length += 1 + directive.Length; if (!string.IsNullOrEmpty(argument)) { textWriter.Write(' '); textWriter.Write(argument); column += 1 + argument.Length; Length += 1 + argument.Length; } NewLine(); } public static string PrintPrimitiveValue(object value) { TextWriter writer = new StringWriter(); TextWriterTokenWriter tokenWriter = new TextWriterTokenWriter(writer); tokenWriter.WritePrimitiveValue(value); return writer.ToString(); } public override void WritePrimitiveValue(object value, LiteralFormat format = LiteralFormat.None) { if (value == null) { // usually NullReferenceExpression should be used for this, but we'll handle it anyways textWriter.Write("null"); column += 4; Length += 4; return; } if (value is bool) { if ((bool)value) { textWriter.Write("true"); column += 4; Length += 4; } else { textWriter.Write("false"); column += 5; Length += 5; } return; } if (value is string) { string tmp = ConvertString(value.ToString()); column += tmp.Length + 2; Length += tmp.Length + 2; textWriter.Write('"'); textWriter.Write(tmp); textWriter.Write('"'); if (format == LiteralFormat.Utf8Literal) { textWriter.Write("u8"); column += 2; Length += 2; } } else if (value is char) { string tmp = ConvertCharLiteral((char)value); column += tmp.Length + 2; Length += tmp.Length + 2; textWriter.Write('\''); textWriter.Write(tmp); textWriter.Write('\''); } else if (value is decimal) { string str = ((decimal)value).ToString(NumberFormatInfo.InvariantInfo) + "m"; column += str.Length; Length += str.Length; textWriter.Write(str); } else if (value is float) { float f = (float)value; if (float.IsInfinity(f) || float.IsNaN(f)) { // Strictly speaking, these aren't PrimitiveExpressions; // but we still support writing these to make life easier for code generators. textWriter.Write("float"); column += 5; Length += 5; WriteToken(Roles.Dot, "."); if (float.IsPositiveInfinity(f)) { textWriter.Write("PositiveInfinity"); column += "PositiveInfinity".Length; Length += "PositiveInfinity".Length; } else if (float.IsNegativeInfinity(f)) { textWriter.Write("NegativeInfinity"); column += "NegativeInfinity".Length; Length += "NegativeInfinity".Length; } else { textWriter.Write("NaN"); column += 3; Length += 3; } return; } var str = f.ToString("R", NumberFormatInfo.InvariantInfo) + "f"; if (f == 0 && 1 / f == float.NegativeInfinity && str[0] != '-') { // negative zero is a special case // (again, not a primitive expression, but it's better to handle // the special case here than to do it in all code generators) str = '-' + str; } column += str.Length; Length += str.Length; textWriter.Write(str); } else if (value is double) { double f = (double)value; if (double.IsInfinity(f) || double.IsNaN(f)) { // Strictly speaking, these aren't PrimitiveExpressions; // but we still support writing these to make life easier for code generators. textWriter.Write("double"); column += 6; Length += 6; WriteToken(Roles.Dot, "."); if (double.IsPositiveInfinity(f)) { textWriter.Write("PositiveInfinity"); column += "PositiveInfinity".Length; Length += "PositiveInfinity".Length; } else if (double.IsNegativeInfinity(f)) { textWriter.Write("NegativeInfinity"); column += "NegativeInfinity".Length; Length += "NegativeInfinity".Length; } else { textWriter.Write("NaN"); column += 3; Length += 3; } return; } string number = f.ToString("R", NumberFormatInfo.InvariantInfo); if (f == 0 && 1 / f == double.NegativeInfinity && number[0] != '-') { // negative zero is a special case // (again, not a primitive expression, but it's better to handle // the special case here than to do it in all code generators) number = '-' + number; } if (number.IndexOf('.') < 0 && number.IndexOf('E') < 0) { number += ".0"; } textWriter.Write(number); column += number.Length; Length += number.Length; } else if (value is IFormattable) { StringBuilder b = new StringBuilder(); if (format == LiteralFormat.HexadecimalNumber) { b.Append("0x"); b.Append(((IFormattable)value).ToString("X", NumberFormatInfo.InvariantInfo)); } else { b.Append(((IFormattable)value).ToString(null, NumberFormatInfo.InvariantInfo)); } if (value is uint || value is ulong) { b.Append("u"); } if (value is long || value is ulong) { b.Append("L"); } textWriter.Write(b.ToString()); column += b.Length; Length += b.Length; } else { textWriter.Write(value.ToString()); int length = value.ToString().Length; column += length; Length += length; } } public override void WriteInterpolatedText(string text) { textWriter.Write(ConvertString(text)); } /// /// Gets the escape sequence for the specified character within a char literal. /// Does not include the single quotes surrounding the char literal. /// public static string ConvertCharLiteral(char ch) { if (ch == '\'') { return "\\'"; } return ConvertChar(ch) ?? ch.ToString(); } /// /// Gets the escape sequence for the specified character. /// /// This method does not convert ' or ". static string ConvertChar(char ch) { switch (ch) { case '\\': return "\\\\"; case '\0': return "\\0"; case '\a': return "\\a"; case '\b': return "\\b"; case '\f': return "\\f"; case '\n': return "\\n"; case '\r': return "\\r"; case '\t': return "\\t"; case '\v': return "\\v"; case ' ': case '_': case '`': case '^': // ASCII characters we allow directly in the output even though we don't use // other Unicode characters of the same category. return null; case '\ufffd': return "\\u" + ((int)ch).ToString("x4"); default: switch (char.GetUnicodeCategory(ch)) { case UnicodeCategory.NonSpacingMark: case UnicodeCategory.SpacingCombiningMark: case UnicodeCategory.EnclosingMark: case UnicodeCategory.LineSeparator: case UnicodeCategory.ParagraphSeparator: case UnicodeCategory.Control: case UnicodeCategory.Format: case UnicodeCategory.Surrogate: case UnicodeCategory.PrivateUse: case UnicodeCategory.ConnectorPunctuation: case UnicodeCategory.ModifierSymbol: case UnicodeCategory.OtherNotAssigned: case UnicodeCategory.SpaceSeparator: return "\\u" + ((int)ch).ToString("x4"); default: return null; } } } /// /// Converts special characters to escape sequences within the given string. /// public static string ConvertString(string str) { StringBuilder sb = new StringBuilder(); foreach (char ch in str) { string s = ch == '"' ? "\\\"" : ConvertChar(ch); if (s != null) sb.Append(s); else sb.Append(ch); } return sb.ToString(); } public static string EscapeIdentifier(string identifier) { if (string.IsNullOrEmpty(identifier)) return identifier; StringBuilder sb = new StringBuilder(); for (int i = 0; i < identifier.Length; i++) { if (IsPrintableIdentifierChar(identifier, i)) { if (char.IsSurrogatePair(identifier, i)) { sb.Append(identifier.Substring(i, 2)); i++; } else { sb.Append(identifier[i]); } } else { if (char.IsSurrogatePair(identifier, i)) { sb.AppendFormat("\\U{0:x8}", char.ConvertToUtf32(identifier, i)); i++; } else { sb.AppendFormat("\\u{0:x4}", (int)identifier[i]); } } } return sb.ToString(); } public static bool ContainsNonPrintableIdentifierChar(string identifier) { if (string.IsNullOrEmpty(identifier)) return false; for (int i = 0; i < identifier.Length; i++) { if (char.IsWhiteSpace(identifier[i])) return true; if (!IsPrintableIdentifierChar(identifier, i)) return true; } return false; } static bool IsPrintableIdentifierChar(string identifier, int index) { switch (identifier[index]) { case '\\': return false; case ' ': case '_': case '`': case '^': return true; } switch (char.GetUnicodeCategory(identifier, index)) { case UnicodeCategory.NonSpacingMark: case UnicodeCategory.SpacingCombiningMark: case UnicodeCategory.EnclosingMark: case UnicodeCategory.LineSeparator: case UnicodeCategory.ParagraphSeparator: case UnicodeCategory.Control: case UnicodeCategory.Format: case UnicodeCategory.Surrogate: case UnicodeCategory.PrivateUse: case UnicodeCategory.ConnectorPunctuation: case UnicodeCategory.ModifierSymbol: case UnicodeCategory.OtherNotAssigned: case UnicodeCategory.SpaceSeparator: return false; default: return true; } } public override void WritePrimitiveType(string type) { textWriter.Write(type); column += type.Length; Length += type.Length; if (type == "new") { textWriter.Write("()"); column += 2; Length += 2; } } public override void StartNode(AstNode node) { // Write out the indentation, so that overrides of this method // can rely use the current output length to identify the position of the node // in the output. WriteIndentation(); } public override void EndNode(AstNode node) { } } }