From 315d79ff2b94e98b7bbcc87e957ff029a7aed9b0 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Fri, 15 Jan 2016 09:49:00 -0800 Subject: [PATCH] Make the CodeWriter more efficient --- .../CodeGenerators/CSharpCodeWriter.cs | 62 +++++++++--- .../CodeGenerators/CSharpCodeWritingScope.cs | 4 +- .../CodeGenerators/CodeWriter.cs | 96 ++++++++----------- 3 files changed, 90 insertions(+), 72 deletions(-) diff --git a/src/Microsoft.AspNet.Razor/CodeGenerators/CSharpCodeWriter.cs b/src/Microsoft.AspNet.Razor/CodeGenerators/CSharpCodeWriter.cs index 552f031a4e..2112fc110c 100644 --- a/src/Microsoft.AspNet.Razor/CodeGenerators/CSharpCodeWriter.cs +++ b/src/Microsoft.AspNet.Razor/CodeGenerators/CSharpCodeWriter.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Globalization; using System.Linq; using Microsoft.AspNet.Razor.Chunks.Generators; @@ -15,6 +16,19 @@ namespace Microsoft.AspNet.Razor.CodeGenerators { private const string InstanceMethodFormat = "{0}.{1}"; + private static readonly char[] CStyleStringLiteralEscapeChars = new char[] + { + '\r', + '\t', + '\"', + '\'', + '\\', + '\0', + '\n', + '\u2028', + '\u2029', + }; + public CSharpCodeWriter() { LineMappingManager = new LineMappingManager(); @@ -195,8 +209,7 @@ namespace Microsoft.AspNet.Razor.CodeGenerators file = location.FilePath; } - if (!string.IsNullOrEmpty(LastWrite) && - !LastWrite.EndsWith(NewLine, StringComparison.Ordinal)) + if (Builder.Length >= NewLine.Length && !IsAfterNewLine) { WriteLine(); } @@ -445,18 +458,23 @@ namespace Microsoft.AspNet.Razor.CodeGenerators { Write("@\""); - foreach (char c in literal) + // We need to find the index of each '"' (double-quote) to escape it. + var start = 0; + int end; + while ((end = literal.IndexOf('\"', start)) > -1) { - if (c == '\"') - { - Write("\"\""); - } - else - { - Write(c.ToString()); - } + Write(literal, start, end - start); + + Write("\"\""); + + start = end + 1; } + Debug.Assert(end == -1); // We've hit all of the double-quotes. + + // Write the remainder after the last double-quote. + Write(literal, start, literal.Length - start); + Write("\""); } @@ -464,9 +482,15 @@ namespace Microsoft.AspNet.Razor.CodeGenerators { // From CSharpCodeGenerator.QuoteSnippetStringCStyle in CodeDOM Write("\""); - for (int i = 0; i < literal.Length; i++) + + // We need to find the index of each escapable character to escape it. + var start = 0; + int end; + while ((end = literal.IndexOfAny(CStyleStringLiteralEscapeChars, start)) > -1) { - switch (literal[i]) + Write(literal, start, end - start); + + switch (literal[end]) { case '\r': Write("\\r"); @@ -492,13 +516,21 @@ namespace Microsoft.AspNet.Razor.CodeGenerators case '\u2028': case '\u2029': Write("\\u"); - Write(((int)literal[i]).ToString("X4", CultureInfo.InvariantCulture)); + Write(((int)literal[end]).ToString("X4", CultureInfo.InvariantCulture)); break; default: - Write(literal[i].ToString()); + Debug.Assert(false, "Unknown escape character."); break; } + + start = end + 1; } + + Debug.Assert(end == -1); // We've hit all of chars that need escaping. + + // Write the remainder after the last escaped char. + Write(literal, start, literal.Length - start); + Write("\""); } diff --git a/src/Microsoft.AspNet.Razor/CodeGenerators/CSharpCodeWritingScope.cs b/src/Microsoft.AspNet.Razor/CodeGenerators/CSharpCodeWritingScope.cs index 2616c149b0..4e64c00d74 100644 --- a/src/Microsoft.AspNet.Razor/CodeGenerators/CSharpCodeWritingScope.cs +++ b/src/Microsoft.AspNet.Razor/CodeGenerators/CSharpCodeWritingScope.cs @@ -60,7 +60,9 @@ namespace Microsoft.AspNet.Razor.CodeGenerators private void TryAutoSpace(string spaceCharacter) { - if (_autoSpace && _writer.LastWrite.Length > 0 && !Char.IsWhiteSpace(_writer.LastWrite.Last())) + if (_autoSpace && + _writer.Builder.Length > 0 && + !char.IsWhiteSpace(_writer.Builder[_writer.Builder.Length - 1])) { _writer.Write(spaceCharacter); } diff --git a/src/Microsoft.AspNet.Razor/CodeGenerators/CodeWriter.cs b/src/Microsoft.AspNet.Razor/CodeGenerators/CodeWriter.cs index 171883c22e..0a4043ebe6 100644 --- a/src/Microsoft.AspNet.Razor/CodeGenerators/CodeWriter.cs +++ b/src/Microsoft.AspNet.Razor/CodeGenerators/CodeWriter.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.IO; using System.Text; namespace Microsoft.AspNet.Razor.CodeGenerators @@ -10,32 +9,21 @@ namespace Microsoft.AspNet.Razor.CodeGenerators public class CodeWriter : IDisposable { private static readonly char[] NewLineCharacters = new char[] { '\r', '\n' }; - private readonly StringWriter _writer = new StringWriter(); - private bool _newLine; + private string _cache = string.Empty; - private bool _dirty = false; + private bool _dirty; private int _absoluteIndex; private int _currentLineIndex; private int _currentLineCharacterIndex; - public StringBuilder Builder => _writer.GetStringBuilder(); - - public string LastWrite { get; private set; } + public StringBuilder Builder { get; } = new StringBuilder(); public int CurrentIndent { get; private set; } - public string NewLine - { - get - { - return _writer.NewLine; - } - set - { - _writer.NewLine = value; - } - } + public bool IsAfterNewLine { get; private set; } + + public string NewLine { get; set; } = Environment.NewLine; public CodeWriter ResetIndent() { @@ -65,16 +53,15 @@ namespace Microsoft.AspNet.Razor.CodeGenerators public CodeWriter Indent(int size) { - if (_newLine) + if (IsAfterNewLine) { - _writer.Write(new string(' ', size)); - Flush(); + Builder.Append(' ', size); _currentLineCharacterIndex += size; _absoluteIndex += size; _dirty = true; - _newLine = false; + IsAfterNewLine = false; } return this; @@ -82,35 +69,42 @@ namespace Microsoft.AspNet.Razor.CodeGenerators public CodeWriter Write(string data) { - Indent(CurrentIndent); - - _writer.Write(data); - Flush(); - - LastWrite = data; - _dirty = true; - _newLine = false; - - if (data == null || data.Length == 0) + if (data == null) { return this; } - _absoluteIndex += data.Length; + return Write(data, 0, data.Length); + } + + public CodeWriter Write(string data, int index, int count) + { + if (data == null || count == 0) + { + return this; + } + + Indent(CurrentIndent); + + Builder.Append(data, index, count); + + _dirty = true; + IsAfterNewLine = false; + + _absoluteIndex += count; // The data string might contain a partial newline where the previously // written string has part of the newline. - var i = 0; + var i = index; int? trailingPartStart = null; - var builder = _writer.GetStringBuilder(); if ( // Check the last character of the previous write operation. - builder.Length - data.Length - 1 >= 0 && - builder[builder.Length - data.Length - 1] == '\r' && + Builder.Length - count - 1 >= 0 && + Builder[Builder.Length - count - 1] == '\r' && // Check the first character of the current write operation. - builder[builder.Length - data.Length] == '\n') + Builder[Builder.Length - count] == '\n') { // This is newline that's spread across two writes. Skip the first character of the // current write operation. @@ -133,7 +127,7 @@ namespace Microsoft.AspNet.Razor.CodeGenerators // We might have stopped at a \r, so check if it's followed by \n and then advance the index to // start the next search after it. - if (data.Length > i && + if (count > i && data[i - 1] == '\r' && data[i] == '\n') { @@ -147,12 +141,12 @@ namespace Microsoft.AspNet.Razor.CodeGenerators if (trailingPartStart == null) { // No newlines, just add the length of the data buffer - _currentLineCharacterIndex += data.Length; + _currentLineCharacterIndex += count; } else { // Newlines found, add the trailing part of 'data' - _currentLineCharacterIndex += (data.Length - trailingPartStart.Value); + _currentLineCharacterIndex += (count - trailingPartStart.Value); } return this; @@ -160,17 +154,14 @@ namespace Microsoft.AspNet.Razor.CodeGenerators public CodeWriter WriteLine() { - LastWrite = _writer.NewLine; - - _writer.WriteLine(); - Flush(); + Builder.Append(NewLine); _currentLineIndex++; _currentLineCharacterIndex = 0; - _absoluteIndex += _writer.NewLine.Length; + _absoluteIndex += NewLine.Length; _dirty = true; - _newLine = true; + IsAfterNewLine = true; return this; } @@ -180,18 +171,11 @@ namespace Microsoft.AspNet.Razor.CodeGenerators return Write(data).WriteLine(); } - public CodeWriter Flush() - { - _writer.Flush(); - - return this; - } - public string GenerateCode() { if (_dirty) { - _cache = _writer.ToString(); + _cache = Builder.ToString(); _dirty = false; } @@ -207,7 +191,7 @@ namespace Microsoft.AspNet.Razor.CodeGenerators { if (disposing) { - _writer.Dispose(); + Builder.Clear(); } }