From 61614a56e3b7330b903ba40634c4e515240d7707 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 21 Jul 2013 20:50:50 +0200 Subject: [PATCH] move all formatting to CSharpOutputVisitor, move all "extra" work to decorating ITokenWriter --- .../Src/Completion/CSharpInsightItem.cs | 2 +- .../SegmentTrackingOutputFormatter.cs | 2 +- .../NRefactory/ExpressionEvaluationVisitor.cs | 6 +- .../ICSharpCode.Decompiler/Ast/AstBuilder.cs | 9 +- .../Ast/TextOutputFormatter.cs | 25 +- .../Disassembler/DisassemblerHelpers.cs | 4 +- .../Disassembler/ReflectionDisassembler.cs | 12 +- .../ICSharpCode.NRefactory.CSharp.csproj | 1 + .../OutputVisitor/CSharpAmbience.cs | 154 ++-- .../OutputVisitor/CSharpOutputVisitor.cs | 762 +++++++++--------- .../InsertRequiredSpacesDecorator.cs | 183 +++++ .../TextWriterOutputFormatter.cs | 250 ++++-- .../Refactoring/Script.cs | 6 +- .../TypeSystem/CSharpUnresolvedFile.cs | 2 +- .../CSharp/CSharpOutputVisitorTests.cs | 2 +- .../CSharp/InsertParenthesesVisitorTests.cs | 4 +- 16 files changed, 841 insertions(+), 583 deletions(-) create mode 100644 src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/OutputVisitor/InsertRequiredSpacesDecorator.cs diff --git a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Completion/CSharpInsightItem.cs b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Completion/CSharpInsightItem.cs index 0aeaa92216..3c6328355f 100644 --- a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Completion/CSharpInsightItem.cs +++ b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Completion/CSharpInsightItem.cs @@ -63,7 +63,7 @@ namespace CSharpBinding.Completion get { return null; } } - sealed class ParameterHighlightingOutputFormatter : TextWriterOutputFormatter + sealed class ParameterHighlightingOutputFormatter : TextWriterTokenWriter { StringBuilder b; int highlightedParameterIndex; diff --git a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Completion/SegmentTrackingOutputFormatter.cs b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Completion/SegmentTrackingOutputFormatter.cs index ba90bffa24..d0cbc4d054 100644 --- a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Completion/SegmentTrackingOutputFormatter.cs +++ b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Completion/SegmentTrackingOutputFormatter.cs @@ -15,7 +15,7 @@ namespace CSharpBinding.Completion /// /// Output formatter that creates a dictionary from AST nodes to segments in the output text. /// - public class SegmentTrackingOutputFormatter : TextWriterOutputFormatter + public class SegmentTrackingOutputFormatter : TextWriterTokenWriter { Dictionary segments = new Dictionary(); Stack startOffsets = new Stack(); diff --git a/src/AddIns/Debugger/Debugger.AddIn/NRefactory/ExpressionEvaluationVisitor.cs b/src/AddIns/Debugger/Debugger.AddIn/NRefactory/ExpressionEvaluationVisitor.cs index 37083cacf1..3706a68490 100644 --- a/src/AddIns/Debugger/Debugger.AddIn/NRefactory/ExpressionEvaluationVisitor.cs +++ b/src/AddIns/Debugger/Debugger.AddIn/NRefactory/ExpressionEvaluationVisitor.cs @@ -424,11 +424,11 @@ namespace Debugger.AddIn sb.Append("}"); return sb.ToString(); } else if (val.Type.IsKnownType(KnownTypeCode.Char)) { - return "'" + CSharpOutputVisitor.ConvertChar((char)val.PrimitiveValue) + "'"; + return "'" + TextWriterTokenWriter.ConvertChar((char)val.PrimitiveValue) + "'"; } else if (val.Type.IsKnownType(KnownTypeCode.String)) { - return "\"" + CSharpOutputVisitor.ConvertString((string)val.PrimitiveValue) + "\""; + return "\"" + TextWriterTokenWriter.ConvertString((string)val.PrimitiveValue) + "\""; } else if (val.Type.IsPrimitiveType()) { - return CSharpOutputVisitor.PrintPrimitiveValue(val.PrimitiveValue); + return TextWriterTokenWriter.PrintPrimitiveValue(val.PrimitiveValue); } else { return val.InvokeToString(evalThread); } diff --git a/src/Libraries/ICSharpCode.Decompiler/Ast/AstBuilder.cs b/src/Libraries/ICSharpCode.Decompiler/Ast/AstBuilder.cs index b915fc5040..4a41825ca9 100644 --- a/src/Libraries/ICSharpCode.Decompiler/Ast/AstBuilder.cs +++ b/src/Libraries/ICSharpCode.Decompiler/Ast/AstBuilder.cs @@ -150,6 +150,13 @@ namespace ICSharpCode.Decompiler.Ast get { return syntaxTree; } } + /// + /// Gets the context used by this AstBuilder. + /// + public DecompilerContext Context { + get { return context; } + } + /// /// Generates C# code from the abstract source tree. /// @@ -160,7 +167,7 @@ namespace ICSharpCode.Decompiler.Ast RunTransformations(); syntaxTree.AcceptVisitor(new InsertParenthesesVisitor { InsertParenthesesForReadability = true }); - var outputFormatter = new TextOutputFormatter(output) { FoldBraces = context.Settings.FoldBraces }; + var outputFormatter = new TextTokenWriter(output) { FoldBraces = context.Settings.FoldBraces }; var formattingPolicy = context.Settings.CSharpFormattingOptions; syntaxTree.AcceptVisitor(new CSharpOutputVisitor(outputFormatter, formattingPolicy)); } diff --git a/src/Libraries/ICSharpCode.Decompiler/Ast/TextOutputFormatter.cs b/src/Libraries/ICSharpCode.Decompiler/Ast/TextOutputFormatter.cs index 613ac1c582..08da545bce 100644 --- a/src/Libraries/ICSharpCode.Decompiler/Ast/TextOutputFormatter.cs +++ b/src/Libraries/ICSharpCode.Decompiler/Ast/TextOutputFormatter.cs @@ -27,7 +27,7 @@ using Mono.Cecil; namespace ICSharpCode.Decompiler.Ast { - public class TextOutputFormatter : IOutputFormatter + public class TextTokenWriter : ITokenWriter { readonly ITextOutput output; readonly Stack nodeStack = new Stack(); @@ -40,37 +40,37 @@ namespace ICSharpCode.Decompiler.Ast public bool FoldBraces = false; - public TextOutputFormatter(ITextOutput output) + public TextTokenWriter(ITextOutput output) { if (output == null) throw new ArgumentNullException("output"); this.output = output; } - public void WriteIdentifier(string identifier) + public void WriteIdentifier(Identifier identifier) { var definition = GetCurrentDefinition(); if (definition != null) { - output.WriteDefinition(identifier, definition, false); + output.WriteDefinition(identifier.Name, definition, false); return; } object memberRef = GetCurrentMemberReference(); if (memberRef != null) { - output.WriteReference(identifier, memberRef); + output.WriteReference(identifier.Name, memberRef); return; } definition = GetCurrentLocalDefinition(); if (definition != null) { - output.WriteDefinition(identifier, definition); + output.WriteDefinition(identifier.Name, definition); return; } memberRef = GetCurrentLocalReference(); if (memberRef != null) { - output.WriteReference(identifier, memberRef, true); + output.WriteReference(identifier.Name, memberRef, true); return; } @@ -79,7 +79,7 @@ namespace ICSharpCode.Decompiler.Ast firstUsingDeclaration = false; } - output.Write(identifier); + output.Write(identifier.Name); } MemberReference GetCurrentMemberReference() @@ -160,12 +160,12 @@ namespace ICSharpCode.Decompiler.Ast return null; } - public void WriteKeyword(string keyword) + public void WriteKeyword(Role role, string keyword) { output.Write(keyword); } - public void WriteToken(string token) + public void WriteToken(Role role, string token) { // Attach member reference to token only if there's no identifier in the current node. MemberReference memberRef = GetCurrentMemberReference(); @@ -267,6 +267,11 @@ namespace ICSharpCode.Decompiler.Ast output.WriteLine(); } + public void WritePrimitiveValue(object value) + { + + } + Stack startLocations = new Stack(); Stack symbolsStack = new Stack(); diff --git a/src/Libraries/ICSharpCode.Decompiler/Disassembler/DisassemblerHelpers.cs b/src/Libraries/ICSharpCode.Decompiler/Disassembler/DisassemblerHelpers.cs index 63567c71ff..4c2080045e 100644 --- a/src/Libraries/ICSharpCode.Decompiler/Disassembler/DisassemblerHelpers.cs +++ b/src/Libraries/ICSharpCode.Decompiler/Disassembler/DisassemblerHelpers.cs @@ -222,7 +222,7 @@ namespace ICSharpCode.Decompiler.Disassembler } else { // The ECMA specification says that ' inside SQString should be ecaped using an octal escape sequence, // but we follow Microsoft's ILDasm and use \'. - return "'" + NRefactory.CSharp.CSharpOutputVisitor.ConvertString(identifier).Replace("'", "\\'") + "'"; + return "'" + NRefactory.CSharp.TextWriterTokenWriter.ConvertString(identifier).Replace("'", "\\'") + "'"; } } @@ -353,7 +353,7 @@ namespace ICSharpCode.Decompiler.Disassembler string s = operand as string; if (s != null) { - writer.Write("\"" + NRefactory.CSharp.CSharpOutputVisitor.ConvertString(s) + "\""); + writer.Write("\"" + NRefactory.CSharp.TextWriterTokenWriter.ConvertString(s) + "\""); } else if (operand is char) { writer.Write(((int)(char)operand).ToString()); } else if (operand is float) { diff --git a/src/Libraries/ICSharpCode.Decompiler/Disassembler/ReflectionDisassembler.cs b/src/Libraries/ICSharpCode.Decompiler/Disassembler/ReflectionDisassembler.cs index ec20cfa72a..1a7d1a4f71 100644 --- a/src/Libraries/ICSharpCode.Decompiler/Disassembler/ReflectionDisassembler.cs +++ b/src/Libraries/ICSharpCode.Decompiler/Disassembler/ReflectionDisassembler.cs @@ -126,10 +126,10 @@ namespace ICSharpCode.Decompiler.Disassembler output.Write("pinvokeimpl"); if (method.HasPInvokeInfo && method.PInvokeInfo != null) { PInvokeInfo info = method.PInvokeInfo; - output.Write("(\"" + NRefactory.CSharp.CSharpOutputVisitor.ConvertString(info.Module.Name) + "\""); + output.Write("(\"" + NRefactory.CSharp.TextWriterTokenWriter.ConvertString(info.Module.Name) + "\""); if (!string.IsNullOrEmpty(info.EntryPoint) && info.EntryPoint != method.Name) - output.Write(" as \"" + NRefactory.CSharp.CSharpOutputVisitor.ConvertString(info.EntryPoint) + "\""); + output.Write(" as \"" + NRefactory.CSharp.TextWriterTokenWriter.ConvertString(info.EntryPoint) + "\""); if (info.IsNoMangle) output.Write(" nomangle"); @@ -346,7 +346,7 @@ namespace ICSharpCode.Decompiler.Disassembler output.Write(" = "); if (na.Argument.Value is string) { // secdecls use special syntax for strings - output.Write("string('{0}')", NRefactory.CSharp.CSharpOutputVisitor.ConvertString((string)na.Argument.Value).Replace("'", "\'")); + output.Write("string('{0}')", NRefactory.CSharp.TextWriterTokenWriter.ConvertString((string)na.Argument.Value).Replace("'", "\'")); } else { WriteConstant(na.Argument.Value); } @@ -574,10 +574,10 @@ namespace ICSharpCode.Decompiler.Disassembler if (cmi == null) goto default; output.Write("custom(\"{0}\", \"{1}\"", - NRefactory.CSharp.CSharpOutputVisitor.ConvertString(cmi.ManagedType.FullName), - NRefactory.CSharp.CSharpOutputVisitor.ConvertString(cmi.Cookie)); + NRefactory.CSharp.TextWriterTokenWriter.ConvertString(cmi.ManagedType.FullName), + NRefactory.CSharp.TextWriterTokenWriter.ConvertString(cmi.Cookie)); if (cmi.Guid != Guid.Empty || !string.IsNullOrEmpty(cmi.UnmanagedType)) { - output.Write(", \"{0}\", \"{1}\"", cmi.Guid.ToString(), NRefactory.CSharp.CSharpOutputVisitor.ConvertString(cmi.UnmanagedType)); + output.Write(", \"{0}\", \"{1}\"", cmi.Guid.ToString(), NRefactory.CSharp.TextWriterTokenWriter.ConvertString(cmi.UnmanagedType)); } output.Write(')'); break; diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj index aa3954d20b..fbc4919866 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj @@ -208,6 +208,7 @@ + diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/OutputVisitor/CSharpAmbience.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/OutputVisitor/CSharpAmbience.cs index 8d7522529b..6c150c72a9 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/OutputVisitor/CSharpAmbience.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/OutputVisitor/CSharpAmbience.cs @@ -37,98 +37,98 @@ namespace ICSharpCode.NRefactory.CSharp throw new ArgumentNullException("entity"); StringWriter writer = new StringWriter(); - ConvertEntity(entity, new TextWriterOutputFormatter(writer), FormattingOptionsFactory.CreateMono ()); + ConvertEntity(entity, new TextWriterTokenWriter(writer), FormattingOptionsFactory.CreateMono ()); return writer.ToString(); } - public void ConvertEntity(IEntity entity, IOutputFormatter formatter, CSharpFormattingOptions formattingPolicy) + public void ConvertEntity(IEntity entity, ITokenWriter writer, CSharpFormattingOptions formattingPolicy) { if (entity == null) throw new ArgumentNullException("entity"); - if (formatter == null) - throw new ArgumentNullException("formatter"); + if (writer == null) + throw new ArgumentNullException("writer"); if (formattingPolicy == null) throw new ArgumentNullException("options"); TypeSystemAstBuilder astBuilder = CreateAstBuilder(); EntityDeclaration node = astBuilder.ConvertEntity(entity); - PrintModifiers(node.Modifiers, formatter); + PrintModifiers(node.Modifiers, writer); if ((ConversionFlags & ConversionFlags.ShowDefinitionKeyword) == ConversionFlags.ShowDefinitionKeyword) { if (node is TypeDeclaration) { switch (((TypeDeclaration)node).ClassType) { case ClassType.Class: - formatter.WriteKeyword("class"); + writer.WriteKeyword(Roles.ClassKeyword, "class"); break; case ClassType.Struct: - formatter.WriteKeyword("struct"); + writer.WriteKeyword(Roles.StructKeyword, "struct"); break; case ClassType.Interface: - formatter.WriteKeyword("interface"); + writer.WriteKeyword(Roles.InterfaceKeyword, "interface"); break; case ClassType.Enum: - formatter.WriteKeyword("enum"); + writer.WriteKeyword(Roles.EnumKeyword, "enum"); break; default: throw new Exception("Invalid value for ClassType"); } - formatter.Space(); + writer.Space(); } else if (node is DelegateDeclaration) { - formatter.WriteKeyword("delegate"); - formatter.Space(); + writer.WriteKeyword(Roles.DelegateKeyword, "delegate"); + writer.Space(); } else if (node is EventDeclaration) { - formatter.WriteKeyword("event"); - formatter.Space(); + writer.WriteKeyword(EventDeclaration.EventKeywordRole, "event"); + writer.Space(); } } if ((ConversionFlags & ConversionFlags.ShowReturnType) == ConversionFlags.ShowReturnType) { var rt = node.GetChildByRole(Roles.Type); if (!rt.IsNull) { - rt.AcceptVisitor(new CSharpOutputVisitor(formatter, formattingPolicy)); - formatter.Space(); + rt.AcceptVisitor(new CSharpOutputVisitor(writer, formattingPolicy)); + writer.Space(); } } if (entity is ITypeDefinition) - WriteTypeDeclarationName((ITypeDefinition)entity, formatter, formattingPolicy); + WriteTypeDeclarationName((ITypeDefinition)entity, writer, formattingPolicy); else - WriteMemberDeclarationName((IMember)entity, formatter, formattingPolicy); + WriteMemberDeclarationName((IMember)entity, writer, formattingPolicy); if ((ConversionFlags & ConversionFlags.ShowParameterList) == ConversionFlags.ShowParameterList && HasParameters(entity)) { - formatter.WriteToken(entity.SymbolKind == SymbolKind.Indexer ? "[" : "("); + writer.WriteToken(entity.SymbolKind == SymbolKind.Indexer ? Roles.LBracket : Roles.LPar, entity.SymbolKind == SymbolKind.Indexer ? "[" : "("); bool first = true; foreach (var param in node.GetChildrenByRole(Roles.Parameter)) { if (first) { first = false; } else { - formatter.WriteToken(","); - formatter.Space(); + writer.WriteToken(Roles.Comma, ","); + writer.Space(); } - param.AcceptVisitor(new CSharpOutputVisitor(formatter, formattingPolicy)); + param.AcceptVisitor(new CSharpOutputVisitor(writer, formattingPolicy)); } - formatter.WriteToken(entity.SymbolKind == SymbolKind.Indexer ? "]" : ")"); + writer.WriteToken(entity.SymbolKind == SymbolKind.Indexer ? Roles.RBracket : Roles.RPar, entity.SymbolKind == SymbolKind.Indexer ? "]" : ")"); } if ((ConversionFlags & ConversionFlags.ShowBody) == ConversionFlags.ShowBody && !(node is TypeDeclaration)) { IProperty property = entity as IProperty; if (property != null) { - formatter.Space(); - formatter.WriteToken("{"); - formatter.Space(); + writer.Space(); + writer.WriteToken(Roles.LBrace, "{"); + writer.Space(); if (property.CanGet) { - formatter.WriteKeyword("get"); - formatter.WriteToken(";"); - formatter.Space(); + writer.WriteKeyword(PropertyDeclaration.GetKeywordRole, "get"); + writer.WriteToken(Roles.Semicolon, ";"); + writer.Space(); } if (property.CanSet) { - formatter.WriteKeyword("set"); - formatter.WriteToken(";"); - formatter.Space(); + writer.WriteKeyword(PropertyDeclaration.SetKeywordRole, "set"); + writer.WriteToken(Roles.Semicolon, ";"); + writer.Space(); } - formatter.WriteToken("}"); + writer.WriteToken(Roles.RBrace, "}"); } else { - formatter.WriteToken(";"); + writer.WriteToken(Roles.Semicolon, ";"); } } } @@ -160,87 +160,97 @@ namespace ICSharpCode.NRefactory.CSharp return astBuilder; } - void WriteTypeDeclarationName(ITypeDefinition typeDef, IOutputFormatter formatter, CSharpFormattingOptions formattingPolicy) + void WriteTypeDeclarationName(ITypeDefinition typeDef, ITokenWriter writer, CSharpFormattingOptions formattingPolicy) { TypeSystemAstBuilder astBuilder = CreateAstBuilder(); + TypeDeclaration node = (TypeDeclaration)astBuilder.ConvertEntity(typeDef); if (typeDef.DeclaringTypeDefinition != null) { - WriteTypeDeclarationName(typeDef.DeclaringTypeDefinition, formatter, formattingPolicy); - formatter.WriteToken("."); + WriteTypeDeclarationName(typeDef.DeclaringTypeDefinition, writer, formattingPolicy); + writer.WriteToken(Roles.Dot, "."); } else if ((ConversionFlags & ConversionFlags.UseFullyQualifiedTypeNames) == ConversionFlags.UseFullyQualifiedTypeNames) { - formatter.WriteIdentifier(typeDef.Namespace); - formatter.WriteToken("."); + WriteQualifiedName(typeDef.Namespace, writer, formattingPolicy); + writer.WriteToken(Roles.Dot, "."); } - formatter.WriteIdentifier(typeDef.Name); + writer.WriteIdentifier(node.NameToken); if ((ConversionFlags & ConversionFlags.ShowTypeParameterList) == ConversionFlags.ShowTypeParameterList) { - var outputVisitor = new CSharpOutputVisitor(formatter, formattingPolicy); - outputVisitor.WriteTypeParameters(astBuilder.ConvertEntity(typeDef).GetChildrenByRole(Roles.TypeParameter)); + var outputVisitor = new CSharpOutputVisitor(writer, formattingPolicy); + outputVisitor.WriteTypeParameters(node.GetChildrenByRole(Roles.TypeParameter)); } } - void WriteMemberDeclarationName(IMember member, IOutputFormatter formatter, CSharpFormattingOptions formattingPolicy) + void WriteMemberDeclarationName(IMember member, ITokenWriter writer, CSharpFormattingOptions formattingPolicy) { TypeSystemAstBuilder astBuilder = CreateAstBuilder(); + EntityDeclaration node = astBuilder.ConvertEntity(member); + TypeDeclaration typeDecl = (TypeDeclaration)astBuilder.ConvertEntity(member.DeclaringTypeDefinition); if ((ConversionFlags & ConversionFlags.ShowDeclaringType) == ConversionFlags.ShowDeclaringType) { - ConvertType(member.DeclaringType, formatter, formattingPolicy); - formatter.WriteToken("."); + ConvertType(member.DeclaringType, writer, formattingPolicy); + writer.WriteToken(Roles.Dot, "."); } switch (member.SymbolKind) { case SymbolKind.Indexer: - formatter.WriteKeyword("this"); + writer.WriteKeyword(Roles.Identifier, "this"); break; case SymbolKind.Constructor: - formatter.WriteIdentifier(member.DeclaringType.Name); + WriteQualifiedName(member.DeclaringType.Name, writer, formattingPolicy); break; case SymbolKind.Destructor: - formatter.WriteToken("~"); - formatter.WriteIdentifier(member.DeclaringType.Name); + writer.WriteToken(DestructorDeclaration.TildeRole, "~"); + WriteQualifiedName(member.DeclaringType.Name, writer, formattingPolicy); break; case SymbolKind.Operator: switch (member.Name) { case "op_Implicit": - formatter.WriteKeyword("implicit"); - formatter.Space(); - formatter.WriteKeyword("operator"); - formatter.Space(); - ConvertType(member.ReturnType, formatter, formattingPolicy); + writer.WriteKeyword(OperatorDeclaration.ImplicitRole, "implicit"); + writer.Space(); + writer.WriteKeyword(OperatorDeclaration.OperatorKeywordRole, "operator"); + writer.Space(); + ConvertType(member.ReturnType, writer, formattingPolicy); break; case "op_Explicit": - formatter.WriteKeyword("explicit"); - formatter.Space(); - formatter.WriteKeyword("operator"); - formatter.Space(); - ConvertType(member.ReturnType, formatter, formattingPolicy); + writer.WriteKeyword(OperatorDeclaration.ExplicitRole, "explicit"); + writer.Space(); + writer.WriteKeyword(OperatorDeclaration.OperatorKeywordRole, "operator"); + writer.Space(); + ConvertType(member.ReturnType, writer, formattingPolicy); break; default: - formatter.WriteKeyword("operator"); - formatter.Space(); + writer.WriteKeyword(OperatorDeclaration.OperatorKeywordRole, "operator"); + writer.Space(); var operatorType = OperatorDeclaration.GetOperatorType(member.Name); if (operatorType.HasValue) - formatter.WriteToken(OperatorDeclaration.GetToken(operatorType.Value)); + writer.WriteToken(OperatorDeclaration.GetRole(operatorType.Value), OperatorDeclaration.GetToken(operatorType.Value)); else - formatter.WriteIdentifier(member.Name); + writer.WriteIdentifier(node.NameToken); break; } break; default: - formatter.WriteIdentifier(member.Name); + writer.WriteIdentifier(node.NameToken); break; } if ((ConversionFlags & ConversionFlags.ShowTypeParameterList) == ConversionFlags.ShowTypeParameterList && member.SymbolKind == SymbolKind.Method) { - var outputVisitor = new CSharpOutputVisitor(formatter, formattingPolicy); - outputVisitor.WriteTypeParameters(astBuilder.ConvertEntity(member).GetChildrenByRole(Roles.TypeParameter)); + var outputVisitor = new CSharpOutputVisitor(writer, formattingPolicy); + outputVisitor.WriteTypeParameters(node.GetChildrenByRole(Roles.TypeParameter)); } } - void PrintModifiers(Modifiers modifiers, IOutputFormatter formatter) + void PrintModifiers(Modifiers modifiers, ITokenWriter writer) { foreach (var m in CSharpModifierToken.AllModifiers) { if ((modifiers & m) == m) { - formatter.WriteKeyword(CSharpModifierToken.GetModifierName(m)); - formatter.Space(); + writer.WriteKeyword(EntityDeclaration.ModifierRole, CSharpModifierToken.GetModifierName(m)); + writer.Space(); } } } + + void WriteQualifiedName(string name, ITokenWriter writer, CSharpFormattingOptions formattingPolicy) + { + var node = AstType.Create(name); + var outputVisitor = new CSharpOutputVisitor(writer, formattingPolicy); + node.AcceptVisitor(outputVisitor); + } #endregion public string ConvertVariable(IVariable v) @@ -260,16 +270,16 @@ namespace ICSharpCode.NRefactory.CSharp return astType.ToString(); } - public void ConvertType(IType type, IOutputFormatter formatter, CSharpFormattingOptions formattingPolicy) + public void ConvertType(IType type, ITokenWriter writer, CSharpFormattingOptions formattingPolicy) { TypeSystemAstBuilder astBuilder = CreateAstBuilder(); AstType astType = astBuilder.ConvertType(type); - astType.AcceptVisitor(new CSharpOutputVisitor(formatter, formattingPolicy)); + astType.AcceptVisitor(new CSharpOutputVisitor(writer, formattingPolicy)); } public string ConvertConstantValue(object constantValue) { - return CSharpOutputVisitor.PrintPrimitiveValue(constantValue); + return TextWriterTokenWriter.PrintPrimitiveValue(constantValue); } public string WrapComment(string comment) diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/OutputVisitor/CSharpOutputVisitor.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/OutputVisitor/CSharpOutputVisitor.cs index a669d624dd..ee72a53c80 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/OutputVisitor/CSharpOutputVisitor.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/OutputVisitor/CSharpOutputVisitor.cs @@ -25,6 +25,7 @@ using System.Text; using System.Threading.Tasks; using ICSharpCode.NRefactory.PatternMatching; using ICSharpCode.NRefactory.TypeSystem; +using ICSharpCode.NRefactory.CSharp; namespace ICSharpCode.NRefactory.CSharp { @@ -33,27 +34,9 @@ namespace ICSharpCode.NRefactory.CSharp /// public class CSharpOutputVisitor : IAstVisitor { - readonly IOutputFormatter formatter; + readonly ITokenWriter writer; readonly CSharpFormattingOptions policy; readonly Stack containerStack = new Stack (); - readonly Stack positionStack = new Stack (); - - /// - /// Used to insert the minimal amount of spaces so that the lexer recognizes the tokens that were written. - /// - LastWritten lastWritten; - - enum LastWritten - { - Whitespace, - Other, - KeywordOrIdentifier, - Plus, - Minus, - Ampersand, - QuestionMark, - Division - } public CSharpOutputVisitor (TextWriter textWriter, CSharpFormattingOptions formattingPolicy) { @@ -63,19 +46,19 @@ namespace ICSharpCode.NRefactory.CSharp if (formattingPolicy == null) { throw new ArgumentNullException ("formattingPolicy"); } - this.formatter = new TextWriterOutputFormatter (textWriter); + this.writer = new InsertRequiredSpacesDecorator(new TextWriterTokenWriter(textWriter)); this.policy = formattingPolicy; } - public CSharpOutputVisitor (IOutputFormatter formatter, CSharpFormattingOptions formattingPolicy) + public CSharpOutputVisitor (ITokenWriter writer, CSharpFormattingOptions formattingPolicy) { - if (formatter == null) { - throw new ArgumentNullException ("formatter"); + if (writer == null) { + throw new ArgumentNullException ("writer"); } if (formattingPolicy == null) { throw new ArgumentNullException ("formattingPolicy"); } - this.formatter = formatter; + this.writer = new InsertRequiredSpacesDecorator(writer); this.policy = formattingPolicy; } @@ -85,84 +68,15 @@ namespace ICSharpCode.NRefactory.CSharp // Ensure that nodes are visited in the proper nested order. // Jumps to different subtrees are allowed only for the child of a placeholder node. Debug.Assert(containerStack.Count == 0 || node.Parent == containerStack.Peek() || containerStack.Peek().NodeType == NodeType.Pattern); - if (positionStack.Count > 0) { - WriteSpecialsUpToNode(node); - } containerStack.Push(node); - positionStack.Push(node.FirstChild); - formatter.StartNode(node); + writer.StartNode(node); } void EndNode(AstNode node) { Debug.Assert(node == containerStack.Peek()); - AstNode pos = positionStack.Pop(); - Debug.Assert(pos == null || pos.Parent == node); - WriteSpecials(pos, null); containerStack.Pop(); - formatter.EndNode(node); - } - #endregion - - #region WriteSpecials - /// - /// Writes all specials from start to end (exclusive). Does not touch the positionStack. - /// - void WriteSpecials(AstNode start, AstNode end) - { - for (AstNode pos = start; pos != end; pos = pos.NextSibling) { - if (pos.Role == Roles.Comment || pos.Role == Roles.NewLine || pos.Role == Roles.PreProcessorDirective) { - pos.AcceptVisitor(this); - } - } - } - - /// - /// Writes all specials between the current position (in the positionStack) and the next - /// node with the specified role. Advances the current position. - /// - void WriteSpecialsUpToRole(Role role) - { - WriteSpecialsUpToRole(role, null); - } - - void WriteSpecialsUpToRole(Role role, AstNode nextNode) - { - if (positionStack.Count == 0) { - return; - } - // Look for the role between the current position and the nextNode. - for (AstNode pos = positionStack.Peek(); pos != null && pos != nextNode; pos = pos.NextSibling) { - if (pos.Role == role) { - WriteSpecials(positionStack.Pop(), pos); - // Push the next sibling because the node matching the role is not a special, - // and should be considered to be already handled. - positionStack.Push(pos.NextSibling); - // This is necessary for OptionalComma() to work correctly. - break; - } - } - } - - /// - /// Writes all specials between the current position (in the positionStack) and the specified node. - /// Advances the current position. - /// - void WriteSpecialsUpToNode(AstNode node) - { - if (positionStack.Count == 0) { - return; - } - for (AstNode pos = positionStack.Peek(); pos != null; pos = pos.NextSibling) { - if (pos == node) { - WriteSpecials(positionStack.Pop(), pos); - // Push the next sibling because the node itself is not a special, - // and should be considered to be already handled. - positionStack.Push(pos.NextSibling); - // This is necessary for OptionalComma() to work correctly. - break; - } - } + writer.EndNode(node); } #endregion @@ -174,11 +88,9 @@ namespace ICSharpCode.NRefactory.CSharp /// When set prevents printing a space after comma. void Comma(AstNode nextNode, bool noSpaceAfterComma = false) { - WriteSpecialsUpToRole(Roles.Comma, nextNode); Space(policy.SpaceBeforeBracketComma); // TODO: Comma policy has changed. - formatter.WriteToken(","); - lastWritten = LastWritten.Other; + writer.WriteToken(Roles.Comma, ","); Space(!noSpaceAfterComma && policy.SpaceAfterBracketComma); // TODO: Comma policy has changed. } @@ -186,10 +98,9 @@ namespace ICSharpCode.NRefactory.CSharp /// /// Writes an optional comma, e.g. at the end of an enum declaration or in an array initializer /// - void OptionalComma() + void OptionalComma(AstNode pos) { // Look if there's a comma after the current node, and insert it if it exists. - AstNode pos = positionStack.Peek(); while (pos != null && pos.NodeType == NodeType.Whitespace) { pos = pos.NextSibling; } @@ -201,12 +112,11 @@ namespace ICSharpCode.NRefactory.CSharp /// /// Writes an optional semicolon, e.g. at the end of a type or namespace declaration. /// - void OptionalSemicolon() + void OptionalSemicolon(AstNode pos) { // Look if there's a semicolon after the current node, and insert it if it exists. - AstNode pos = positionStack.Peek(); while (pos != null && pos.NodeType == NodeType.Whitespace) { - pos = pos.NextSibling; + pos = pos.PrevSibling; } if (pos != null && pos.Role == Roles.Semicolon) { Semicolon(); @@ -284,6 +194,8 @@ namespace ICSharpCode.NRefactory.CSharp #endregion #region Write tokens + bool isAtStartOfLine = true; + /// /// Writes a keyword, and all specials up to /// @@ -294,40 +206,20 @@ namespace ICSharpCode.NRefactory.CSharp void WriteKeyword(string token, Role tokenRole = null) { - if (tokenRole != null) { - WriteSpecialsUpToRole(tokenRole); - } - if (lastWritten == LastWritten.KeywordOrIdentifier) { - formatter.Space(); - } - formatter.WriteKeyword(token); - lastWritten = LastWritten.KeywordOrIdentifier; + writer.WriteKeyword(tokenRole, token); + isAtStartOfLine = false; } -/* void WriteKeyword (string keyword, Role tokenRole) + void WriteIdentifier(Identifier identifier) { - WriteSpecialsUpToRole (tokenRole); - if (lastWritten == LastWritten.KeywordOrIdentifier) - formatter.Space (); - formatter.WriteKeyword (keyword); - lastWritten = LastWritten.KeywordOrIdentifier; - }*/ + writer.WriteIdentifier(identifier); + isAtStartOfLine = false; + } - void WriteIdentifier(string identifier, Role identifierRole = null) + void WriteIdentifier(string identifier) { - WriteSpecialsUpToRole(identifierRole ?? Roles.Identifier); - if (IsKeyword(identifier, containerStack.Peek())) { - if (lastWritten == LastWritten.KeywordOrIdentifier) { - Space(); - } - // this space is not strictly required, so we call Space() - formatter.WriteToken("@"); - } else if (lastWritten == LastWritten.KeywordOrIdentifier) { - formatter.Space(); - // this space is strictly required, so we directly call the formatter - } - formatter.WriteIdentifier(identifier); - lastWritten = LastWritten.KeywordOrIdentifier; + AstType.Create(identifier).AcceptVisitor(this); + isAtStartOfLine = false; } void WriteToken(TokenRole tokenRole) @@ -337,34 +229,8 @@ namespace ICSharpCode.NRefactory.CSharp void WriteToken(string token, Role tokenRole) { - WriteSpecialsUpToRole(tokenRole); - // Avoid that two +, - or ? tokens are combined into a ++, -- or ?? token. - // Note that we don't need to handle tokens like = because there's no valid - // C# program that contains the single token twice in a row. - // (for +, - and &, this can happen with unary operators; - // for ?, this can happen in "a is int? ? b : c" or "a as int? ?? 0"; - // and for /, this can happen with "1/ *ptr" or "1/ //comment".) - if (lastWritten == LastWritten.Plus && token [0] == '+' - || lastWritten == LastWritten.Minus && token [0] == '-' - || lastWritten == LastWritten.Ampersand && token [0] == '&' - || lastWritten == LastWritten.QuestionMark && token [0] == '?' - || lastWritten == LastWritten.Division && token [0] == '*') { - formatter.Space(); - } - formatter.WriteToken(token); - if (token == "+") { - lastWritten = LastWritten.Plus; - } else if (token == "-") { - lastWritten = LastWritten.Minus; - } else if (token == "&") { - lastWritten = LastWritten.Ampersand; - } else if (token == "?") { - lastWritten = LastWritten.QuestionMark; - } else if (token == "/") { - lastWritten = LastWritten.Division; - } else { - lastWritten = LastWritten.Other; - } + writer.WriteToken(tokenRole, token); + isAtStartOfLine = false; } void LPar() @@ -396,29 +262,78 @@ namespace ICSharpCode.NRefactory.CSharp void Space(bool addSpace = true) { if (addSpace) { - formatter.Space(); - lastWritten = LastWritten.Whitespace; + writer.Space(); } } void NewLine() { - formatter.NewLine(); - lastWritten = LastWritten.Whitespace; + writer.NewLine(); + isAtStartOfLine = true; } void OpenBrace(BraceStyle style) { - WriteSpecialsUpToRole(Roles.LBrace); - formatter.OpenBrace(style); - lastWritten = LastWritten.Other; + switch (style) { + case BraceStyle.DoNotChange: + case BraceStyle.EndOfLine: + case BraceStyle.BannerStyle: + if (!isAtStartOfLine) + writer.Space(); + writer.WriteToken(Roles.LBrace, "{"); + break; + case BraceStyle.EndOfLineWithoutSpace: + writer.WriteToken(Roles.LBrace, "{"); + break; + case BraceStyle.NextLine: + if (!isAtStartOfLine) + NewLine(); + writer.WriteToken(Roles.LBrace, "{"); + break; + case BraceStyle.NextLineShifted: + NewLine(); + writer.Indent(); + writer.WriteToken(Roles.LBrace, "{"); + NewLine(); + return; + case BraceStyle.NextLineShifted2: + NewLine(); + writer.Indent(); + writer.WriteToken(Roles.LBrace, "{"); + break; + default: + throw new ArgumentOutOfRangeException (); + } + writer.Indent(); + NewLine(); } void CloseBrace(BraceStyle style) { - WriteSpecialsUpToRole(Roles.RBrace); - formatter.CloseBrace(style); - lastWritten = LastWritten.Other; + switch (style) { + case BraceStyle.DoNotChange: + case BraceStyle.EndOfLine: + case BraceStyle.EndOfLineWithoutSpace: + case BraceStyle.NextLine: + writer.Unindent(); + writer.WriteToken(Roles.RBrace, "}"); + isAtStartOfLine = false; + break; + case BraceStyle.BannerStyle: + case BraceStyle.NextLineShifted: + writer.WriteToken(Roles.RBrace, "}"); + isAtStartOfLine = false; + writer.Unindent(); + break; + case BraceStyle.NextLineShifted2: + writer.Unindent(); + writer.WriteToken(Roles.RBrace, "}"); + isAtStartOfLine = false; + writer.Unindent(); + break; + default: + throw new ArgumentOutOfRangeException(); + } } #endregion @@ -502,17 +417,10 @@ namespace ICSharpCode.NRefactory.CSharp foreach (Identifier ident in identifiers) { if (first) { first = false; - if (lastWritten == LastWritten.KeywordOrIdentifier) { - formatter.Space(); - } } else { - WriteSpecialsUpToRole(Roles.Dot, ident); - formatter.WriteToken("."); - lastWritten = LastWritten.Other; + writer.WriteToken(Roles.Dot, "."); } - WriteSpecialsUpToNode(ident); - formatter.WriteIdentifier(ident.Name); - lastWritten = LastWritten.KeywordOrIdentifier; + writer.WriteIdentifier(ident); } } @@ -527,9 +435,9 @@ namespace ICSharpCode.NRefactory.CSharp VisitBlockStatement(block); } else { NewLine(); - formatter.Indent(); + writer.Indent(); embeddedStatement.AcceptVisitor(this); - formatter.Unindent(); + writer.Unindent(); } } @@ -625,8 +533,8 @@ namespace ICSharpCode.NRefactory.CSharp // The output visitor will output nested braces only if they are necessary, // or if the braces tokens exist in the AST. bool bracesAreOptional = arrayInitializerExpression.Elements.Count == 1 - && IsObjectOrCollectionInitializer(arrayInitializerExpression.Parent) - && !CanBeConfusedWithObjectInitializer(arrayInitializerExpression.Elements.Single()); + && IsObjectOrCollectionInitializer(arrayInitializerExpression.Parent) + && !CanBeConfusedWithObjectInitializer(arrayInitializerExpression.Elements.Single()); if (bracesAreOptional && arrayInitializerExpression.LBraceToken.IsNull) { arrayInitializerExpression.Elements.Single().AcceptVisitor(this); } else { @@ -667,6 +575,7 @@ namespace ICSharpCode.NRefactory.CSharp } OpenBrace(style); bool isFirst = true; + AstNode last = null; foreach (AstNode node in elements) { if (isFirst) { isFirst = false; @@ -674,9 +583,11 @@ namespace ICSharpCode.NRefactory.CSharp Comma(node, noSpaceAfterComma: true); NewLine(); } + last = node; node.AcceptVisitor(this); } - OptionalComma(); + if (last != null) + OptionalComma(last.NextSibling); NewLine(); CloseBrace(style); } @@ -843,7 +754,7 @@ namespace ICSharpCode.NRefactory.CSharp public void VisitIdentifierExpression(IdentifierExpression identifierExpression) { StartNode(identifierExpression); - WriteIdentifier(identifierExpression.Identifier); + WriteIdentifier(identifierExpression.IdentifierToken); WriteTypeArguments(identifierExpression.TypeArguments); EndNode(identifierExpression); } @@ -909,7 +820,7 @@ namespace ICSharpCode.NRefactory.CSharp StartNode(memberReferenceExpression); memberReferenceExpression.Target.AcceptVisitor(this); WriteToken(Roles.Dot); - WriteIdentifier(memberReferenceExpression.MemberName); + WriteIdentifier(memberReferenceExpression.MemberNameToken); WriteTypeArguments(memberReferenceExpression.TypeArguments); EndNode(memberReferenceExpression); } @@ -984,7 +895,7 @@ namespace ICSharpCode.NRefactory.CSharp StartNode(pointerReferenceExpression); pointerReferenceExpression.Target.AcceptVisitor(this); WriteToken(PointerReferenceExpression.ArrowRole); - WriteIdentifier(pointerReferenceExpression.MemberName); + WriteIdentifier(pointerReferenceExpression.MemberNameToken); WriteTypeArguments(pointerReferenceExpression.TypeArguments); EndNode(pointerReferenceExpression); } @@ -994,183 +905,12 @@ namespace ICSharpCode.NRefactory.CSharp { StartNode(primitiveExpression); if (!string.IsNullOrEmpty(primitiveExpression.LiteralValue)) { - formatter.WriteToken(primitiveExpression.LiteralValue); + writer.WriteToken(primitiveExpression.Role, primitiveExpression.LiteralValue); } else { - WritePrimitiveValue(primitiveExpression.Value); + TextWriterTokenWriter.PrintPrimitiveValue(primitiveExpression.Value); } EndNode(primitiveExpression); } - - public static string PrintPrimitiveValue(object val) - { - StringWriter writer = new StringWriter(); - CSharpOutputVisitor visitor = new CSharpOutputVisitor(writer, new CSharpFormattingOptions()); - visitor.WritePrimitiveValue(val); - return writer.ToString(); - } - - void WritePrimitiveValue(object val) - { - if (val == null) { - // usually NullReferenceExpression should be used for this, but we'll handle it anyways - WriteKeyword("null"); - return; - } - - if (val is bool) { - if ((bool)val) { - WriteKeyword("true"); - } else { - WriteKeyword("false"); - } - return; - } - - if (val is string) { - formatter.WriteToken("\"" + ConvertString(val.ToString()) + "\""); - lastWritten = LastWritten.Other; - } else if (val is char) { - formatter.WriteToken("'" + ConvertCharLiteral((char)val) + "'"); - lastWritten = LastWritten.Other; - } else if (val is decimal) { - formatter.WriteToken(((decimal)val).ToString(NumberFormatInfo.InvariantInfo) + "m"); - lastWritten = LastWritten.Other; - } else if (val is float) { - float f = (float)val; - 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. - WriteKeyword("float"); - WriteToken(Roles.Dot); - if (float.IsPositiveInfinity(f)) { - WriteIdentifier("PositiveInfinity"); - } else if (float.IsNegativeInfinity(f)) { - WriteIdentifier("NegativeInfinity"); - } else { - WriteIdentifier("NaN"); - } - return; - } - if (f == 0 && 1 / f == float.NegativeInfinity) { - // 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) - formatter.WriteToken("-"); - } - formatter.WriteToken(f.ToString("R", NumberFormatInfo.InvariantInfo) + "f"); - lastWritten = LastWritten.Other; - } else if (val is double) { - double f = (double)val; - 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. - WriteKeyword("double"); - WriteToken(Roles.Dot); - if (double.IsPositiveInfinity(f)) { - WriteIdentifier("PositiveInfinity"); - } else if (double.IsNegativeInfinity(f)) { - WriteIdentifier("NegativeInfinity"); - } else { - WriteIdentifier("NaN"); - } - return; - } - if (f == 0 && 1 / f == double.NegativeInfinity) { - // 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) - formatter.WriteToken("-"); - } - string number = f.ToString("R", NumberFormatInfo.InvariantInfo); - if (number.IndexOf('.') < 0 && number.IndexOf('E') < 0) { - number += ".0"; - } - formatter.WriteToken(number); - // needs space if identifier follows number; this avoids mistaking the following identifier as type suffix - lastWritten = LastWritten.KeywordOrIdentifier; - } else if (val is IFormattable) { - StringBuilder b = new StringBuilder (); - // if (primitiveExpression.LiteralFormat == LiteralFormat.HexadecimalNumber) { - // b.Append("0x"); - // b.Append(((IFormattable)val).ToString("x", NumberFormatInfo.InvariantInfo)); - // } else { - b.Append(((IFormattable)val).ToString(null, NumberFormatInfo.InvariantInfo)); - // } - if (val is uint || val is ulong) { - b.Append("u"); - } - if (val is long || val is ulong) { - b.Append("L"); - } - formatter.WriteToken(b.ToString()); - // needs space if identifier follows number; this avoids mistaking the following identifier as type suffix - lastWritten = LastWritten.KeywordOrIdentifier; - } else { - formatter.WriteToken(val.ToString()); - lastWritten = LastWritten.Other; - } - } - - static string ConvertCharLiteral(char ch) - { - if (ch == '\'') { - return "\\'"; - } - return ConvertChar(ch); - } - - /// - /// Gets the escape sequence for the specified character. - /// - /// This method does not convert ' or ". - public 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"; - default: - if (char.IsControl(ch) || char.IsSurrogate(ch) || - // print all uncommon white spaces as numbers - (char.IsWhiteSpace(ch) && ch != ' ')) { - return "\\u" + ((int)ch).ToString("x4"); - } else { - return ch.ToString(); - } - } - } - - /// - /// 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) { - if (ch == '"') { - sb.Append("\\\""); - } else { - sb.Append(ConvertChar(ch)); - } - } - return sb.ToString(); - } - #endregion public void VisitSizeOfExpression(SizeOfExpression sizeOfExpression) @@ -1261,7 +1001,7 @@ namespace ICSharpCode.NRefactory.CSharp StartNode(queryExpression); bool indent = !(queryExpression.Parent is QueryContinuationClause); if (indent) { - formatter.Indent(); + writer.Indent(); NewLine(); } bool first = true; @@ -1276,7 +1016,7 @@ namespace ICSharpCode.NRefactory.CSharp clause.AcceptVisitor(this); } if (indent) { - formatter.Unindent(); + writer.Unindent(); } EndNode(queryExpression); } @@ -1334,7 +1074,7 @@ namespace ICSharpCode.NRefactory.CSharp WriteKeyword(QueryJoinClause.JoinKeywordRole); queryJoinClause.Type.AcceptVisitor(this); Space(); - WriteIdentifier(queryJoinClause.JoinIdentifier, QueryJoinClause.JoinIdentifierRole); + WriteIdentifier(queryJoinClause.JoinIdentifierToken); Space(); WriteKeyword(QueryJoinClause.InKeywordRole); Space(); @@ -1350,7 +1090,7 @@ namespace ICSharpCode.NRefactory.CSharp if (queryJoinClause.IsGroupJoin) { Space(); WriteKeyword(QueryJoinClause.IntoKeywordRole); - WriteIdentifier(queryJoinClause.IntoIdentifier, QueryJoinClause.IntoIdentifierRole); + WriteIdentifier(queryJoinClause.IntoIdentifierToken); } EndNode(queryJoinClause); } @@ -1465,7 +1205,7 @@ namespace ICSharpCode.NRefactory.CSharp member.AcceptVisitor(this); } CloseBrace(policy.NamespaceBraceStyle); - OptionalSemicolon(); + OptionalSemicolon(namespaceDeclaration.LastChild); NewLine(); EndNode(namespaceDeclaration); } @@ -1508,6 +1248,7 @@ namespace ICSharpCode.NRefactory.CSharp OpenBrace(braceStyle); if (typeDeclaration.ClassType == ClassType.Enum) { bool first = true; + AstNode last = null; foreach (var member in typeDeclaration.Members) { if (first) { first = false; @@ -1515,9 +1256,11 @@ namespace ICSharpCode.NRefactory.CSharp Comma(member, noSpaceAfterComma: true); NewLine(); } + last = member; member.AcceptVisitor(this); } - OptionalComma(); + if (last != null) + OptionalComma(last.NextSibling); NewLine(); } else { foreach (var member in typeDeclaration.Members) { @@ -1525,7 +1268,7 @@ namespace ICSharpCode.NRefactory.CSharp } } CloseBrace(braceStyle); - OptionalSemicolon(); + OptionalSemicolon(typeDeclaration.LastChild); NewLine(); EndNode(typeDeclaration); } @@ -1534,7 +1277,7 @@ namespace ICSharpCode.NRefactory.CSharp { StartNode(usingAliasDeclaration); WriteKeyword(UsingAliasDeclaration.UsingKeywordRole); - WriteIdentifier(usingAliasDeclaration.Alias, UsingAliasDeclaration.AliasRole); + WriteIdentifier(usingAliasDeclaration.GetChildByRole(UsingAliasDeclaration.AliasRole)); Space(policy.SpaceAroundEqualityOperator); WriteToken(Roles.Assign); Space(policy.SpaceAroundEqualityOperator); @@ -1745,7 +1488,7 @@ namespace ICSharpCode.NRefactory.CSharp { StartNode(gotoStatement); WriteKeyword(GotoStatement.GotoKeywordRole); - WriteIdentifier(gotoStatement.Label); + WriteIdentifier(gotoStatement.GetChildByRole(Roles.Identifier)); Semicolon(); EndNode(gotoStatement); } @@ -1771,7 +1514,7 @@ namespace ICSharpCode.NRefactory.CSharp public void VisitLabelStatement(LabelStatement labelStatement) { StartNode(labelStatement); - WriteIdentifier(labelStatement.Label); + WriteIdentifier(labelStatement.GetChildByRole(Roles.Identifier)); WriteToken(Roles.Colon); bool foundLabelledStatement = false; for (AstNode tmp = labelStatement.NextSibling; tmp != null; tmp = tmp.NextSibling) { @@ -1825,7 +1568,7 @@ namespace ICSharpCode.NRefactory.CSharp RPar(); OpenBrace(policy.StatementBraceStyle); if (!policy.IndentSwitchBody) { - formatter.Unindent(); + writer.Unindent(); } foreach (var section in switchStatement.SwitchSections) { @@ -1833,7 +1576,7 @@ namespace ICSharpCode.NRefactory.CSharp } if (!policy.IndentSwitchBody) { - formatter.Indent(); + writer.Indent(); } CloseBrace(policy.StatementBraceStyle); NewLine(); @@ -1853,7 +1596,7 @@ namespace ICSharpCode.NRefactory.CSharp } bool isBlock = switchSection.Statements.Count == 1 && switchSection.Statements.Single() is BlockStatement; if (policy.IndentCaseBody && !isBlock) { - formatter.Indent(); + writer.Indent(); } if (!isBlock) @@ -1864,7 +1607,7 @@ namespace ICSharpCode.NRefactory.CSharp } if (policy.IndentCaseBody && !isBlock) { - formatter.Unindent(); + writer.Unindent(); } EndNode(switchSection); @@ -2040,7 +1783,7 @@ namespace ICSharpCode.NRefactory.CSharp var nameToken = constructorDeclaration.NameToken; if (!nameToken.IsNull) StartNode(nameToken); - WriteIdentifier(type != null ? type.Name : constructorDeclaration.Name); + WriteIdentifier(type != null ? type.NameToken : constructorDeclaration.NameToken); if (!nameToken.IsNull) EndNode(nameToken); Space(policy.SpaceBeforeConstructorDeclarationParentheses); @@ -2078,7 +1821,7 @@ namespace ICSharpCode.NRefactory.CSharp var nameToken = destructorDeclaration.NameToken; if (!nameToken.IsNull) StartNode(nameToken); - WriteIdentifier(type != null ? type.Name : destructorDeclaration.Name); + WriteIdentifier(type != null ? type.NameToken : destructorDeclaration.NameToken); if (!nameToken.IsNull) EndNode(nameToken); Space(policy.SpaceBeforeConstructorDeclarationParentheses); @@ -2235,7 +1978,7 @@ namespace ICSharpCode.NRefactory.CSharp WriteKeyword(OperatorDeclaration.OperatorKeywordRole); Space(); if (operatorDeclaration.OperatorType == OperatorType.Explicit - || operatorDeclaration.OperatorType == OperatorType.Implicit) { + || operatorDeclaration.OperatorType == OperatorType.Implicit) { operatorDeclaration.ReturnType.AcceptVisitor(this); } else { WriteToken(OperatorDeclaration.GetToken(operatorDeclaration.OperatorType), OperatorDeclaration.GetRole(operatorDeclaration.OperatorType)); @@ -2328,7 +2071,7 @@ namespace ICSharpCode.NRefactory.CSharp public void VisitSimpleType(SimpleType simpleType) { StartNode(simpleType); - WriteIdentifier(simpleType.Identifier); + WriteIdentifier(simpleType.IdentifierToken); WriteTypeArguments(simpleType.TypeArguments); EndNode(simpleType); } @@ -2342,7 +2085,7 @@ namespace ICSharpCode.NRefactory.CSharp } else { WriteToken(Roles.Dot); } - WriteIdentifier(memberType.MemberName); + WriteIdentifier(memberType.MemberNameToken); WriteTypeArguments(memberType.TypeArguments); EndNode(memberType); } @@ -2368,9 +2111,7 @@ namespace ICSharpCode.NRefactory.CSharp StartNode(arraySpecifier); WriteToken(Roles.LBracket); foreach (var comma in arraySpecifier.GetChildrenByRole(Roles.Comma)) { - WriteSpecialsUpToNode(comma); - formatter.WriteToken(","); - lastWritten = LastWritten.Other; + writer.WriteToken(Roles.Comma, ","); } WriteToken(Roles.RBracket); EndNode(arraySpecifier); @@ -2390,15 +2131,9 @@ namespace ICSharpCode.NRefactory.CSharp public void VisitComment(Comment comment) { - if (lastWritten == LastWritten.Division) { - // When there's a comment starting after a division operator - // "1.0 / /*comment*/a", then we need to insert a space in front of the comment. - formatter.Space(); - } - formatter.StartNode(comment); - formatter.WriteComment(comment.CommentType, comment.Content); - formatter.EndNode(comment); - lastWritten = LastWritten.Whitespace; + writer.StartNode(comment); + writer.WriteComment(comment.CommentType, comment.Content); + writer.EndNode(comment); } public void VisitNewLine(NewLineNode newLineNode) @@ -2420,10 +2155,9 @@ namespace ICSharpCode.NRefactory.CSharp public void VisitPreProcessorDirective(PreProcessorDirective preProcessorDirective) { - formatter.StartNode(preProcessorDirective); - formatter.WritePreProcessorDirective(preProcessorDirective.Type, preProcessorDirective.Argument); - formatter.EndNode(preProcessorDirective); - lastWritten = LastWritten.Whitespace; + writer.StartNode(preProcessorDirective); + writer.WritePreProcessorDirective(preProcessorDirective.Type, preProcessorDirective.Argument); + writer.EndNode(preProcessorDirective); } public void VisitTypeParameterDeclaration(TypeParameterDeclaration typeParameterDeclaration) @@ -2451,7 +2185,7 @@ namespace ICSharpCode.NRefactory.CSharp StartNode(constraint); Space(); WriteKeyword(Roles.WhereKeyword); - WriteIdentifier(constraint.TypeParameter.Identifier); + WriteIdentifier(constraint.TypeParameter.IdentifierToken); Space(); WriteToken(Roles.Colon); Space(); @@ -2474,7 +2208,7 @@ namespace ICSharpCode.NRefactory.CSharp public void VisitIdentifier(Identifier identifier) { StartNode(identifier); - WriteIdentifier(identifier.Name); + WriteIdentifier(identifier); EndNode(identifier); } @@ -2518,7 +2252,7 @@ namespace ICSharpCode.NRefactory.CSharp Space(); LPar(); NewLine(); - formatter.Indent(); + writer.Indent(); foreach (INode alternative in choice) { VisitNodeInPattern(alternative); if (alternative != choice.Last()) { @@ -2526,7 +2260,7 @@ namespace ICSharpCode.NRefactory.CSharp } NewLine(); } - formatter.Unindent(); + writer.Unindent(); RPar(); } @@ -2580,7 +2314,7 @@ namespace ICSharpCode.NRefactory.CSharp } else if (childNode is Repeat) { VisitRepeat((Repeat)childNode); } else { - WritePrimitiveValue(childNode); + TextWriterTokenWriter.PrintPrimitiveValue(childNode); } } #endregion @@ -2618,7 +2352,7 @@ namespace ICSharpCode.NRefactory.CSharp } break; default: - WriteIdentifier(documentationReference.MemberName); + WriteIdentifier(documentationReference.GetChildByRole(Roles.Identifier)); break; } WriteTypeArguments(documentationReference.TypeArguments); @@ -2634,4 +2368,230 @@ namespace ICSharpCode.NRefactory.CSharp } #endregion } + + public interface ITokenWriter + { + void StartNode(AstNode node); + void EndNode(AstNode node); + + /// + /// Writes an identifier. + /// + void WriteIdentifier(Identifier identifier); + + /// + /// Writes a keyword to the output. + /// + void WriteKeyword(Role role, string keyword); + + /// + /// Writes a token to the output. + /// + void WriteToken(Role role, string token); + + /// + /// Writes a primitive/literal value + /// + void WritePrimitiveValue(object value); + + void Space(); + + void Indent(); + void Unindent(); + + void NewLine(); + + void WriteComment(CommentType commentType, string content); + void WritePreProcessorDirective(PreProcessorDirectiveType type, string argument); + } + + public abstract class DecoratingTokenWriter : ITokenWriter + { + ITokenWriter decoratedWriter; + + protected DecoratingTokenWriter(ITokenWriter decoratedWriter) + { + if (decoratedWriter == null) + throw new ArgumentNullException("decoratedWriter"); + this.decoratedWriter = decoratedWriter; + } + + public virtual void StartNode(AstNode node) + { + decoratedWriter.StartNode(node); + } + + public virtual void EndNode(AstNode node) + { + decoratedWriter.EndNode(node); + } + + public virtual void WriteIdentifier(Identifier identifier) + { + decoratedWriter.WriteIdentifier(identifier); + } + + public virtual void WriteKeyword(Role role, string keyword) + { + decoratedWriter.WriteKeyword(role, keyword); + } + + public virtual void WriteToken(Role role, string token) + { + decoratedWriter.WriteToken(role, token); + } + + public virtual void WritePrimitiveValue(object value) + { + decoratedWriter.WritePrimitiveValue(value); + } + + public virtual void Space() + { + decoratedWriter.Space(); + } + + public virtual void Indent() + { + decoratedWriter.Indent(); + } + + public virtual void Unindent() + { + decoratedWriter.Unindent(); + } + + public virtual void NewLine() + { + decoratedWriter.NewLine(); + } + + public virtual void WriteComment(CommentType commentType, string content) + { + decoratedWriter.WriteComment(commentType, content); + } + + public virtual void WritePreProcessorDirective(PreProcessorDirectiveType type, string argument) + { + decoratedWriter.WritePreProcessorDirective(type, argument); + } + } + + class InsertSpecialsDecorator : DecoratingTokenWriter + { + readonly Stack positionStack = new Stack(); + + public InsertSpecialsDecorator(ITokenWriter writer) + : base(writer) + { + } + + public override void StartNode(AstNode node) + { + if (positionStack.Count > 0) { + WriteSpecialsUpToNode(node); + } + positionStack.Push(node.FirstChild); + base.StartNode(node); + } + + public override void EndNode(AstNode node) + { + base.EndNode(node); + AstNode pos = positionStack.Pop(); + Debug.Assert(pos == null || pos.Parent == node); + WriteSpecials(pos, null); + } + + public override void WriteKeyword(Role role, string keyword) + { + if (role != null) { + WriteSpecialsUpToRole(role); + } + base.WriteKeyword(role, keyword); + } + + public override void WriteIdentifier(Identifier identifier) + { + WriteSpecialsUpToRole(identifier.Role ?? Roles.Identifier); + base.WriteIdentifier(identifier); + } + + public override void WriteToken(Role role, string token) + { + WriteSpecialsUpToRole(role); + base.WriteToken(role, token); + } + + #region WriteSpecials + /// + /// Writes all specials from start to end (exclusive). Does not touch the positionStack. + /// + void WriteSpecials(AstNode start, AstNode end) + { + for (AstNode pos = start; pos != end; pos = pos.NextSibling) { + if (pos.Role == Roles.Comment) { + var node = (Comment)pos; + WriteComment(node.CommentType, node.Content); + } + if (pos.Role == Roles.NewLine) { + NewLine(); + } + if (pos.Role == Roles.PreProcessorDirective) { + var node = (PreProcessorDirective)pos; + WritePreProcessorDirective(node.Type, node.Argument); + } + } + } + + /// + /// Writes all specials between the current position (in the positionStack) and the next + /// node with the specified role. Advances the current position. + /// + void WriteSpecialsUpToRole(Role role) + { + WriteSpecialsUpToRole(role, null); + } + + void WriteSpecialsUpToRole(Role role, AstNode nextNode) + { + if (positionStack.Count == 0) { + return; + } + // Look for the role between the current position and the nextNode. + for (AstNode pos = positionStack.Peek(); pos != null && pos != nextNode; pos = pos.NextSibling) { + if (pos.Role == role) { + WriteSpecials(positionStack.Pop(), pos); + // Push the next sibling because the node matching the role is not a special, + // and should be considered to be already handled. + positionStack.Push(pos.NextSibling); + // This is necessary for OptionalComma() to work correctly. + break; + } + } + } + + /// + /// Writes all specials between the current position (in the positionStack) and the specified node. + /// Advances the current position. + /// + void WriteSpecialsUpToNode(AstNode node) + { + if (positionStack.Count == 0) { + return; + } + for (AstNode pos = positionStack.Peek(); pos != null; pos = pos.NextSibling) { + if (pos == node) { + WriteSpecials(positionStack.Pop(), pos); + // Push the next sibling because the node itself is not a special, + // and should be considered to be already handled. + positionStack.Push(pos.NextSibling); + // This is necessary for OptionalComma() to work correctly. + break; + } + } + } + #endregion + } + } diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/OutputVisitor/InsertRequiredSpacesDecorator.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/OutputVisitor/InsertRequiredSpacesDecorator.cs new file mode 100644 index 0000000000..af5f90366f --- /dev/null +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/OutputVisitor/InsertRequiredSpacesDecorator.cs @@ -0,0 +1,183 @@ +// 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.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using ICSharpCode.NRefactory.PatternMatching; +using ICSharpCode.NRefactory.TypeSystem; +using ICSharpCode.NRefactory.CSharp; + +namespace ICSharpCode.NRefactory.CSharp +{ + class InsertRequiredSpacesDecorator : DecoratingTokenWriter + { + /// + /// Used to insert the minimal amount of spaces so that the lexer recognizes the tokens that were written. + /// + LastWritten lastWritten; + + enum LastWritten + { + Whitespace, + Other, + KeywordOrIdentifier, + Plus, + Minus, + Ampersand, + QuestionMark, + Division + } + + public InsertRequiredSpacesDecorator(ITokenWriter writer) + : base(writer) + { + } + + public override void WriteIdentifier(Identifier identifier) + { + if (identifier.IsVerbatim) { + if (lastWritten == LastWritten.KeywordOrIdentifier) { + // this space is not strictly required, so we call Space() + Space(); + } + } else if (lastWritten == LastWritten.KeywordOrIdentifier) { + // this space is strictly required, so we directly call the formatter + base.Space(); + } + base.WriteIdentifier(identifier); + lastWritten = LastWritten.KeywordOrIdentifier; + } + + public override void WriteKeyword(Role role, string keyword) + { + if (lastWritten == LastWritten.KeywordOrIdentifier) { + Space(); + } + base.WriteKeyword(role, keyword); + lastWritten = LastWritten.KeywordOrIdentifier; + } + + public override void WriteToken(Role role, string token) + { + // Avoid that two +, - or ? tokens are combined into a ++, -- or ?? token. + // Note that we don't need to handle tokens like = because there's no valid + // C# program that contains the single token twice in a row. + // (for +, - and &, this can happen with unary operators; + // for ?, this can happen in "a is int? ? b : c" or "a as int? ?? 0"; + // and for /, this can happen with "1/ *ptr" or "1/ //comment".) + if (lastWritten == LastWritten.Plus && token[0] == '+' || + lastWritten == LastWritten.Minus && token[0] == '-' || + lastWritten == LastWritten.Ampersand && token[0] == '&' || + lastWritten == LastWritten.QuestionMark && token[0] == '?' || + lastWritten == LastWritten.Division && token[0] == '*') { + base.Space(); + } + base.WriteToken(role, token); + if (token == "+") { + lastWritten = LastWritten.Plus; + } else if (token == "-") { + lastWritten = LastWritten.Minus; + } else if (token == "&") { + lastWritten = LastWritten.Ampersand; + } else if (token == "?") { + lastWritten = LastWritten.QuestionMark; + } else if (token == "/") { + lastWritten = LastWritten.Division; + } else { + lastWritten = LastWritten.Other; + } + } + + public override void Space() + { + base.Space(); + lastWritten = LastWritten.Whitespace; + } + + public void Indent() + { + throw new NotImplementedException(); + } + + public void Unindent() + { + throw new NotImplementedException(); + } + + public override void NewLine() + { + base.NewLine(); + lastWritten = LastWritten.Whitespace; + } + + public override void WriteComment(CommentType commentType, string content) + { + if (lastWritten == LastWritten.Division) { + // When there's a comment starting after a division operator + // "1.0 / /*comment*/a", then we need to insert a space in front of the comment. + base.Space(); + } + base.WriteComment(commentType, content); + lastWritten = LastWritten.Whitespace; + } + + public override void WritePreProcessorDirective(PreProcessorDirectiveType type, string argument) + { + base.WritePreProcessorDirective(type, argument); + lastWritten = LastWritten.Whitespace; + } + + public override void WritePrimitiveValue(object value) + { + base.WritePrimitiveValue(value); + if (value == null || value is bool) + return; + if (value is string) { + lastWritten = LastWritten.Other; + } else if (value is char) { + lastWritten = LastWritten.Other; + } else if (value is decimal) { + lastWritten = LastWritten.Other; + } else if (value is float) { + float f = (float)value; + if (float.IsInfinity(f) || float.IsNaN(f)) return; + lastWritten = LastWritten.Other; + } else if (value is double) { + double f = (double)value; + if (double.IsInfinity(f) || double.IsNaN(f)) return; + // needs space if identifier follows number; + // this avoids mistaking the following identifier as type suffix + lastWritten = LastWritten.KeywordOrIdentifier; + } else if (value is IFormattable) { + // needs space if identifier follows number; + // this avoids mistaking the following identifier as type suffix + lastWritten = LastWritten.KeywordOrIdentifier; + } else { + lastWritten = LastWritten.Other; + } + } + } +} + + diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/OutputVisitor/TextWriterOutputFormatter.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/OutputVisitor/TextWriterOutputFormatter.cs index 4d1a0d364e..8d0e664998 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/OutputVisitor/TextWriterOutputFormatter.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/OutputVisitor/TextWriterOutputFormatter.cs @@ -17,14 +17,16 @@ // DEALINGS IN THE SOFTWARE. using System; +using System.Globalization; using System.IO; +using System.Text; namespace ICSharpCode.NRefactory.CSharp { /// /// Writes C# code into a TextWriter. /// - public class TextWriterOutputFormatter : IOutputFormatter + public class TextWriterTokenWriter : ITokenWriter { readonly TextWriter textWriter; int indentation; @@ -42,7 +44,7 @@ namespace ICSharpCode.NRefactory.CSharp public string IndentationString { get; set; } - public TextWriterOutputFormatter(TextWriter textWriter) + public TextWriterTokenWriter(TextWriter textWriter) { if (textWriter == null) throw new ArgumentNullException("textWriter"); @@ -50,21 +52,23 @@ namespace ICSharpCode.NRefactory.CSharp this.IndentationString = "\t"; } - public void WriteIdentifier(string ident) + public void WriteIdentifier(Identifier identifier) { WriteIndentation(); - textWriter.Write(ident); + if (identifier.IsVerbatim) + textWriter.Write('@'); + textWriter.Write(identifier.Name); isAtStartOfLine = false; } - public void WriteKeyword(string keyword) + public void WriteKeyword(Role role, string keyword) { WriteIndentation(); textWriter.Write(keyword); isAtStartOfLine = false; } - public void WriteToken(string token) + public void WriteToken(Role role, string token) { WriteIndentation(); textWriter.Write(token); @@ -77,79 +81,6 @@ namespace ICSharpCode.NRefactory.CSharp textWriter.Write(' '); } - public void OpenBrace(BraceStyle style) - { - switch (style) { - case BraceStyle.DoNotChange: - case BraceStyle.EndOfLine: - case BraceStyle.BannerStyle: - WriteIndentation(); - if (!isAtStartOfLine) - textWriter.Write(' '); - textWriter.Write('{'); - break; - case BraceStyle.EndOfLineWithoutSpace: - WriteIndentation(); - textWriter.Write('{'); - break; - case BraceStyle.NextLine: - if (!isAtStartOfLine) - NewLine(); - WriteIndentation(); - textWriter.Write('{'); - break; - - case BraceStyle.NextLineShifted: - NewLine (); - Indent(); - WriteIndentation(); - textWriter.Write('{'); - NewLine(); - return; - case BraceStyle.NextLineShifted2: - NewLine (); - Indent(); - WriteIndentation(); - textWriter.Write('{'); - break; - default: - throw new ArgumentOutOfRangeException (); - } - Indent(); - NewLine(); - } - - public void CloseBrace(BraceStyle style) - { - switch (style) { - case BraceStyle.DoNotChange: - case BraceStyle.EndOfLine: - case BraceStyle.EndOfLineWithoutSpace: - case BraceStyle.NextLine: - Unindent(); - WriteIndentation(); - textWriter.Write('}'); - isAtStartOfLine = false; - break; - case BraceStyle.BannerStyle: - case BraceStyle.NextLineShifted: - WriteIndentation(); - textWriter.Write('}'); - isAtStartOfLine = false; - Unindent(); - break; - case BraceStyle.NextLineShifted2: - Unindent(); - WriteIndentation(); - textWriter.Write('}'); - isAtStartOfLine = false; - Unindent(); - break; - default: - throw new ArgumentOutOfRangeException (); - } - } - protected void WriteIndentation() { if (needsIndent) { @@ -220,6 +151,167 @@ namespace ICSharpCode.NRefactory.CSharp NewLine(); } + public static string PrintPrimitiveValue(object value) + { + TextWriter writer = new StringWriter(); + TextWriterTokenWriter tokenWriter = new TextWriterTokenWriter(writer); + tokenWriter.WritePrimitiveValue(value); + return writer.ToString(); + } + + public void WritePrimitiveValue(object value) + { + if (value == null) { + // usually NullReferenceExpression should be used for this, but we'll handle it anyways + textWriter.Write("null"); + return; + } + + if (value is bool) { + if ((bool)value) { + textWriter.Write("true"); + } else { + textWriter.Write("false"); + } + return; + } + + if (value is string) { + textWriter.Write("\"" + ConvertString(value.ToString()) + "\""); + } else if (value is char) { + textWriter.Write("'" + ConvertCharLiteral((char)value) + "'"); + } else if (value is decimal) { + textWriter.Write(((decimal)value).ToString(NumberFormatInfo.InvariantInfo) + "m"); + } 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"); + WriteToken(Roles.Dot, "."); + if (float.IsPositiveInfinity(f)) { + textWriter.Write("PositiveInfinity"); + } else if (float.IsNegativeInfinity(f)) { + textWriter.Write("NegativeInfinity"); + } else { + textWriter.Write("NaN"); + } + return; + } + if (f == 0 && 1 / f == float.NegativeInfinity) { + // 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) + textWriter.Write("-"); + } + textWriter.Write(f.ToString("R", NumberFormatInfo.InvariantInfo) + "f"); + } 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"); + textWriter.Write("."); + if (double.IsPositiveInfinity(f)) { + textWriter.Write("PositiveInfinity"); + } else if (double.IsNegativeInfinity(f)) { + textWriter.Write("NegativeInfinity"); + } else { + textWriter.Write("NaN"); + } + return; + } + if (f == 0 && 1 / f == double.NegativeInfinity) { + // 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) + textWriter.Write("-"); + } + string number = f.ToString("R", NumberFormatInfo.InvariantInfo); + if (number.IndexOf('.') < 0 && number.IndexOf('E') < 0) { + number += ".0"; + } + textWriter.Write(number); + } else if (value is IFormattable) { + StringBuilder b = new StringBuilder (); +// if (primitiveExpression.LiteralFormat == LiteralFormat.HexadecimalNumber) { +// b.Append("0x"); +// b.Append(((IFormattable)val).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()); + } else { + textWriter.Write(value.ToString()); + } + } + + static string ConvertCharLiteral(char ch) + { + if (ch == '\'') { + return "\\'"; + } + return ConvertChar(ch); + } + + /// + /// Gets the escape sequence for the specified character. + /// + /// This method does not convert ' or ". + public 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"; + default: + if (char.IsControl(ch) || char.IsSurrogate(ch) || + // print all uncommon white spaces as numbers + (char.IsWhiteSpace(ch) && ch != ' ')) { + return "\\u" + ((int)ch).ToString("x4"); + } else { + return ch.ToString(); + } + } + } + + /// + /// 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) { + if (ch == '"') { + sb.Append("\\\""); + } else { + sb.Append(ConvertChar(ch)); + } + } + return sb.ToString(); + } + public virtual void StartNode(AstNode node) { // Write out the indentation, so that overrides of this method diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/Script.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/Script.cs index b133b22287..3a4c90085d 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/Script.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/Script.cs @@ -263,13 +263,13 @@ namespace ICSharpCode.NRefactory.CSharp.Refactoring return 0; } - sealed class SegmentTrackingOutputFormatter : TextWriterOutputFormatter + sealed class SegmentTrackingTokenWriter : TextWriterTokenWriter { internal List> NewSegments = new List>(); Stack startOffsets = new Stack(); readonly StringWriter stringWriter; - public SegmentTrackingOutputFormatter (StringWriter stringWriter) + public SegmentTrackingTokenWriter(StringWriter stringWriter) : base(stringWriter) { this.stringWriter = stringWriter; @@ -293,7 +293,7 @@ namespace ICSharpCode.NRefactory.CSharp.Refactoring protected NodeOutput OutputNode(int indentLevel, AstNode node, bool startWithNewLine = false) { var stringWriter = new StringWriter (); - var formatter = new SegmentTrackingOutputFormatter (stringWriter); + var formatter = new SegmentTrackingTokenWriter(stringWriter); formatter.Indentation = indentLevel; formatter.IndentationString = Options.TabsToSpaces ? new string (' ', Options.IndentSize) : "\t"; stringWriter.NewLine = Options.EolMarker; diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/TypeSystem/CSharpUnresolvedFile.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/TypeSystem/CSharpUnresolvedFile.cs index 88f61f1c93..cbff80cc0a 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/TypeSystem/CSharpUnresolvedFile.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/TypeSystem/CSharpUnresolvedFile.cs @@ -31,7 +31,7 @@ namespace ICSharpCode.NRefactory.CSharp.TypeSystem /// Represents a file that was parsed and converted for the type system. /// [Serializable, FastSerializerVersion(TypeSystemConvertVisitor.version)] - public sealed class CSharpUnresolvedFile : AbstractFreezable, IUnresolvedFile, IUnresolvedDocumentationProvider + public class CSharpUnresolvedFile : AbstractFreezable, IUnresolvedFile, IUnresolvedDocumentationProvider { // The 'FastSerializerVersion' attribute on CSharpUnresolvedFile must be incremented when fixing // bugs in the TypeSystemConvertVisitor diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.Tests/CSharp/CSharpOutputVisitorTests.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.Tests/CSharp/CSharpOutputVisitorTests.cs index e858c26339..a1522e91f4 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.Tests/CSharp/CSharpOutputVisitorTests.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.Tests/CSharp/CSharpOutputVisitorTests.cs @@ -31,7 +31,7 @@ namespace ICSharpCode.NRefactory.CSharp policy = FormattingOptionsFactory.CreateMono(); StringWriter w = new StringWriter(); w.NewLine = "\n"; - node.AcceptVisitor(new CSharpOutputVisitor(new TextWriterOutputFormatter(w) { IndentationString = "$" }, policy)); + node.AcceptVisitor(new CSharpOutputVisitor(new TextWriterTokenWriter(w) { IndentationString = "$" }, policy)); Assert.AreEqual(expected.Replace("\r", ""), w.ToString()); } diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.Tests/CSharp/InsertParenthesesVisitorTests.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.Tests/CSharp/InsertParenthesesVisitorTests.cs index 5b0d3fc36d..5483d82b2f 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.Tests/CSharp/InsertParenthesesVisitorTests.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.Tests/CSharp/InsertParenthesesVisitorTests.cs @@ -39,7 +39,7 @@ namespace ICSharpCode.NRefactory.CSharp expr.AcceptVisitor(new InsertParenthesesVisitor { InsertParenthesesForReadability = true }); StringWriter w = new StringWriter(); w.NewLine = " "; - expr.AcceptVisitor(new CSharpOutputVisitor(new TextWriterOutputFormatter(w) { IndentationString = "" }, policy)); + expr.AcceptVisitor(new CSharpOutputVisitor(new TextWriterTokenWriter(w) { IndentationString = "" }, policy)); return w.ToString(); } @@ -49,7 +49,7 @@ namespace ICSharpCode.NRefactory.CSharp expr.AcceptVisitor(new InsertParenthesesVisitor { InsertParenthesesForReadability = false }); StringWriter w = new StringWriter(); w.NewLine = " "; - expr.AcceptVisitor(new CSharpOutputVisitor(new TextWriterOutputFormatter(w) { IndentationString = "" }, policy)); + expr.AcceptVisitor(new CSharpOutputVisitor(new TextWriterTokenWriter(w) { IndentationString = "" }, policy)); return w.ToString(); }