Make the CodeWriter more efficient

This commit is contained in:
Ryan Nowak 2016-01-15 09:49:00 -08:00
parent 08fef95969
commit 315d79ff2b
3 changed files with 90 additions and 72 deletions

View File

@ -3,6 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using Microsoft.AspNet.Razor.Chunks.Generators; using Microsoft.AspNet.Razor.Chunks.Generators;
@ -15,6 +16,19 @@ namespace Microsoft.AspNet.Razor.CodeGenerators
{ {
private const string InstanceMethodFormat = "{0}.{1}"; private const string InstanceMethodFormat = "{0}.{1}";
private static readonly char[] CStyleStringLiteralEscapeChars = new char[]
{
'\r',
'\t',
'\"',
'\'',
'\\',
'\0',
'\n',
'\u2028',
'\u2029',
};
public CSharpCodeWriter() public CSharpCodeWriter()
{ {
LineMappingManager = new LineMappingManager(); LineMappingManager = new LineMappingManager();
@ -195,8 +209,7 @@ namespace Microsoft.AspNet.Razor.CodeGenerators
file = location.FilePath; file = location.FilePath;
} }
if (!string.IsNullOrEmpty(LastWrite) && if (Builder.Length >= NewLine.Length && !IsAfterNewLine)
!LastWrite.EndsWith(NewLine, StringComparison.Ordinal))
{ {
WriteLine(); WriteLine();
} }
@ -445,18 +458,23 @@ namespace Microsoft.AspNet.Razor.CodeGenerators
{ {
Write("@\""); 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(literal, start, end - start);
{
Write("\"\""); Write("\"\"");
}
else start = end + 1;
{
Write(c.ToString());
}
} }
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("\""); Write("\"");
} }
@ -464,9 +482,15 @@ namespace Microsoft.AspNet.Razor.CodeGenerators
{ {
// From CSharpCodeGenerator.QuoteSnippetStringCStyle in CodeDOM // From CSharpCodeGenerator.QuoteSnippetStringCStyle in CodeDOM
Write("\""); 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': case '\r':
Write("\\r"); Write("\\r");
@ -492,13 +516,21 @@ namespace Microsoft.AspNet.Razor.CodeGenerators
case '\u2028': case '\u2028':
case '\u2029': case '\u2029':
Write("\\u"); Write("\\u");
Write(((int)literal[i]).ToString("X4", CultureInfo.InvariantCulture)); Write(((int)literal[end]).ToString("X4", CultureInfo.InvariantCulture));
break; break;
default: default:
Write(literal[i].ToString()); Debug.Assert(false, "Unknown escape character.");
break; 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("\""); Write("\"");
} }

View File

@ -60,7 +60,9 @@ namespace Microsoft.AspNet.Razor.CodeGenerators
private void TryAutoSpace(string spaceCharacter) 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); _writer.Write(spaceCharacter);
} }

View File

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.IO;
using System.Text; using System.Text;
namespace Microsoft.AspNet.Razor.CodeGenerators namespace Microsoft.AspNet.Razor.CodeGenerators
@ -10,32 +9,21 @@ namespace Microsoft.AspNet.Razor.CodeGenerators
public class CodeWriter : IDisposable public class CodeWriter : IDisposable
{ {
private static readonly char[] NewLineCharacters = new char[] { '\r', '\n' }; private static readonly char[] NewLineCharacters = new char[] { '\r', '\n' };
private readonly StringWriter _writer = new StringWriter();
private bool _newLine;
private string _cache = string.Empty; private string _cache = string.Empty;
private bool _dirty = false; private bool _dirty;
private int _absoluteIndex; private int _absoluteIndex;
private int _currentLineIndex; private int _currentLineIndex;
private int _currentLineCharacterIndex; private int _currentLineCharacterIndex;
public StringBuilder Builder => _writer.GetStringBuilder(); public StringBuilder Builder { get; } = new StringBuilder();
public string LastWrite { get; private set; }
public int CurrentIndent { get; private set; } public int CurrentIndent { get; private set; }
public string NewLine public bool IsAfterNewLine { get; private set; }
{
get public string NewLine { get; set; } = Environment.NewLine;
{
return _writer.NewLine;
}
set
{
_writer.NewLine = value;
}
}
public CodeWriter ResetIndent() public CodeWriter ResetIndent()
{ {
@ -65,16 +53,15 @@ namespace Microsoft.AspNet.Razor.CodeGenerators
public CodeWriter Indent(int size) public CodeWriter Indent(int size)
{ {
if (_newLine) if (IsAfterNewLine)
{ {
_writer.Write(new string(' ', size)); Builder.Append(' ', size);
Flush();
_currentLineCharacterIndex += size; _currentLineCharacterIndex += size;
_absoluteIndex += size; _absoluteIndex += size;
_dirty = true; _dirty = true;
_newLine = false; IsAfterNewLine = false;
} }
return this; return this;
@ -82,35 +69,42 @@ namespace Microsoft.AspNet.Razor.CodeGenerators
public CodeWriter Write(string data) public CodeWriter Write(string data)
{ {
Indent(CurrentIndent); if (data == null)
_writer.Write(data);
Flush();
LastWrite = data;
_dirty = true;
_newLine = false;
if (data == null || data.Length == 0)
{ {
return this; 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 // The data string might contain a partial newline where the previously
// written string has part of the newline. // written string has part of the newline.
var i = 0; var i = index;
int? trailingPartStart = null; int? trailingPartStart = null;
var builder = _writer.GetStringBuilder();
if ( if (
// Check the last character of the previous write operation. // Check the last character of the previous write operation.
builder.Length - data.Length - 1 >= 0 && Builder.Length - count - 1 >= 0 &&
builder[builder.Length - data.Length - 1] == '\r' && Builder[Builder.Length - count - 1] == '\r' &&
// Check the first character of the current write operation. // 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 // This is newline that's spread across two writes. Skip the first character of the
// current write operation. // 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 // 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. // start the next search after it.
if (data.Length > i && if (count > i &&
data[i - 1] == '\r' && data[i - 1] == '\r' &&
data[i] == '\n') data[i] == '\n')
{ {
@ -147,12 +141,12 @@ namespace Microsoft.AspNet.Razor.CodeGenerators
if (trailingPartStart == null) if (trailingPartStart == null)
{ {
// No newlines, just add the length of the data buffer // No newlines, just add the length of the data buffer
_currentLineCharacterIndex += data.Length; _currentLineCharacterIndex += count;
} }
else else
{ {
// Newlines found, add the trailing part of 'data' // Newlines found, add the trailing part of 'data'
_currentLineCharacterIndex += (data.Length - trailingPartStart.Value); _currentLineCharacterIndex += (count - trailingPartStart.Value);
} }
return this; return this;
@ -160,17 +154,14 @@ namespace Microsoft.AspNet.Razor.CodeGenerators
public CodeWriter WriteLine() public CodeWriter WriteLine()
{ {
LastWrite = _writer.NewLine; Builder.Append(NewLine);
_writer.WriteLine();
Flush();
_currentLineIndex++; _currentLineIndex++;
_currentLineCharacterIndex = 0; _currentLineCharacterIndex = 0;
_absoluteIndex += _writer.NewLine.Length; _absoluteIndex += NewLine.Length;
_dirty = true; _dirty = true;
_newLine = true; IsAfterNewLine = true;
return this; return this;
} }
@ -180,18 +171,11 @@ namespace Microsoft.AspNet.Razor.CodeGenerators
return Write(data).WriteLine(); return Write(data).WriteLine();
} }
public CodeWriter Flush()
{
_writer.Flush();
return this;
}
public string GenerateCode() public string GenerateCode()
{ {
if (_dirty) if (_dirty)
{ {
_cache = _writer.ToString(); _cache = Builder.ToString();
_dirty = false; _dirty = false;
} }
@ -207,7 +191,7 @@ namespace Microsoft.AspNet.Razor.CodeGenerators
{ {
if (disposing) if (disposing)
{ {
_writer.Dispose(); Builder.Clear();
} }
} }