From 790f856b440a5620a8e1f2034aab381f55edfa82 Mon Sep 17 00:00:00 2001 From: Travis Parks Date: Wed, 16 Jan 2013 15:10:25 -0500 Subject: [PATCH] Optimized text generation. The way the code was implemented before, each block of text was generating a string which was then being added to a StringBuilder. This only improved performance within a block itself. Needing to then copy the results of that builder into the parent tag's builder was wasteful. Now, a single TextWriter is used for all tags. If a block needs to be processed after-the-fact, the tag can indicate that it wants to provide a new text writer and that it wants to consolidate the text. --- mustache-sharp.test/FormatCompilerTester.cs | 10 ++-- .../Properties/AssemblyInfo.cs | 4 +- mustache-sharp/CompoundGenerator.cs | 18 +++---- mustache-sharp/EachTagDefinition.cs | 10 ++-- mustache-sharp/Generator.cs | 5 +- mustache-sharp/IGenerator.cs | 5 +- mustache-sharp/InlineGenerator.cs | 5 +- mustache-sharp/InlineTagDefinition.cs | 21 +------- mustache-sharp/KeyGenerator.cs | 5 +- mustache-sharp/NestedContext.cs | 49 +++++++++++++++++++ mustache-sharp/Properties/AssemblyInfo.cs | 4 +- mustache-sharp/StaticGenerator.cs | 5 +- mustache-sharp/TagDefinition.cs | 27 ++++++---- mustache-sharp/WithGenerator.cs | 12 +++-- mustache-sharp/mustache-sharp.csproj | 1 + 15 files changed, 114 insertions(+), 67 deletions(-) create mode 100644 mustache-sharp/NestedContext.cs diff --git a/mustache-sharp.test/FormatCompilerTester.cs b/mustache-sharp.test/FormatCompilerTester.cs index d9e812e..97c567b 100644 --- a/mustache-sharp.test/FormatCompilerTester.cs +++ b/mustache-sharp.test/FormatCompilerTester.cs @@ -2,6 +2,7 @@ using System.Globalization; using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Collections.Generic; +using System.IO; namespace mustache.test { @@ -901,19 +902,14 @@ Last"; { } - protected override bool GetIsContextSensitive() - { - return false; - } - protected override IEnumerable GetParameters() { return new TagParameter[] { new TagParameter("param") { IsRequired = false, DefaultValue = 123 } }; } - protected override string GetText(IFormatProvider provider, Dictionary arguments) + public override void GetText(TextWriter writer, Dictionary arguments) { - return arguments["param"].ToString(); + writer.Write(arguments["param"]); } } diff --git a/mustache-sharp.test/Properties/AssemblyInfo.cs b/mustache-sharp.test/Properties/AssemblyInfo.cs index 8d276b2..458323d 100644 --- a/mustache-sharp.test/Properties/AssemblyInfo.cs +++ b/mustache-sharp.test/Properties/AssemblyInfo.cs @@ -31,5 +31,5 @@ using System.Runtime.InteropServices; // // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("0.0.1.0")] -[assembly: AssemblyFileVersion("0.0.1.0")] +[assembly: AssemblyVersion("0.0.2.0")] +[assembly: AssemblyFileVersion("0.0.2.0")] diff --git a/mustache-sharp/CompoundGenerator.cs b/mustache-sharp/CompoundGenerator.cs index c374f2b..a7d0306 100644 --- a/mustache-sharp/CompoundGenerator.cs +++ b/mustache-sharp/CompoundGenerator.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using System.Text; +using System.IO; namespace mustache { @@ -72,11 +72,10 @@ namespace mustache } } - string IGenerator.GetText(IFormatProvider provider, KeyScope scope) + void IGenerator.GetText(KeyScope scope, TextWriter writer) { - StringBuilder builder = new StringBuilder(); Dictionary arguments = _arguments.GetArguments(scope); - IEnumerable scopes = _definition.GetChildScopes(scope, arguments); + IEnumerable contexts = _definition.GetChildContext(writer, scope, arguments); LinkedList generators; if (_definition.ShouldGeneratePrimaryGroup(arguments)) { @@ -90,16 +89,17 @@ namespace mustache generators.AddLast(_subGenerator); } } - foreach (KeyScope childScope in scopes) + foreach (NestedContext context in contexts) { foreach (IGenerator generator in generators) { - builder.Append(generator.GetText(provider, childScope)); + generator.GetText(context.KeyScope, context.Writer); + if (context.WriterNeedsConsidated) + { + writer.Write(_definition.ConsolidateWriter(context.Writer, arguments)); + } } } - string innerText = builder.ToString(); - string outerText = _definition.Decorate(provider, innerText, arguments); - return outerText; } } } \ No newline at end of file diff --git a/mustache-sharp/EachTagDefinition.cs b/mustache-sharp/EachTagDefinition.cs index 70d5257..983593d 100644 --- a/mustache-sharp/EachTagDefinition.cs +++ b/mustache-sharp/EachTagDefinition.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.IO; namespace mustache { @@ -38,12 +39,13 @@ namespace mustache } /// - /// Gets the scopes for each of the items found in the argument. + /// Gets the context to use when building the inner text of the tag. /// + /// The text writer passed /// The current scope. /// The arguments passed to the tag. - /// The scopes for each of the items found in the argument. - public override IEnumerable GetChildScopes(KeyScope scope, Dictionary arguments) + /// The scope to use when building the inner text of the tag. + public override IEnumerable GetChildContext(TextWriter writer, KeyScope scope, Dictionary arguments) { object value = arguments[collectionParameter]; IEnumerable enumerable = value as IEnumerable; @@ -53,7 +55,7 @@ namespace mustache } foreach (object item in enumerable) { - yield return scope.CreateChildScope(item); + yield return new NestedContext() { KeyScope = scope.CreateChildScope(item), Writer = writer }; } } diff --git a/mustache-sharp/Generator.cs b/mustache-sharp/Generator.cs index 7f3852e..bcb9fa6 100644 --- a/mustache-sharp/Generator.cs +++ b/mustache-sharp/Generator.cs @@ -1,5 +1,6 @@ using System; using System.Globalization; +using System.IO; namespace mustache { @@ -47,7 +48,9 @@ namespace mustache private string render(IFormatProvider provider, object source) { KeyScope scope = new KeyScope(source); - return _generator.GetText(provider, scope); + StringWriter writer = new StringWriter(provider); + _generator.GetText(scope, writer); + return writer.ToString(); } } } diff --git a/mustache-sharp/IGenerator.cs b/mustache-sharp/IGenerator.cs index 6ee122d..ca883b5 100644 --- a/mustache-sharp/IGenerator.cs +++ b/mustache-sharp/IGenerator.cs @@ -1,4 +1,5 @@ using System; +using System.IO; namespace mustache { @@ -10,9 +11,9 @@ namespace mustache /// /// Generates the text when applying the format plan. /// - /// The format provider to use when formatting the keys. /// The current lexical scope of the keys. + /// The text writer to send all text to. /// The generated text. - string GetText(IFormatProvider provider, KeyScope scope); + void GetText(KeyScope scope, TextWriter writer); } } diff --git a/mustache-sharp/InlineGenerator.cs b/mustache-sharp/InlineGenerator.cs index 0a64e18..d244a05 100644 --- a/mustache-sharp/InlineGenerator.cs +++ b/mustache-sharp/InlineGenerator.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; namespace mustache { @@ -22,10 +23,10 @@ namespace mustache _arguments = arguments; } - string IGenerator.GetText(IFormatProvider provider, KeyScope scope) + void IGenerator.GetText(KeyScope scope, TextWriter writer) { Dictionary arguments = _arguments.GetArguments(scope); - return _definition.Decorate(provider, String.Empty, arguments); + _definition.GetText(writer, arguments); } } } diff --git a/mustache-sharp/InlineTagDefinition.cs b/mustache-sharp/InlineTagDefinition.cs index 075dae4..e087e64 100644 --- a/mustache-sharp/InlineTagDefinition.cs +++ b/mustache-sharp/InlineTagDefinition.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; namespace mustache { @@ -35,25 +36,5 @@ namespace mustache { return false; } - - /// - /// Generates the text for the tag. - /// - /// The format provider to use. - /// The text to decorate. This will always be an empty string. - /// The arguments passed to the tag. - /// The generated text. - public sealed override string Decorate(IFormatProvider provider, string innerText, Dictionary arguments) - { - return GetText(provider, arguments); - } - - /// - /// Gets the text of the inline tag. - /// - /// The format provider to use. - /// The arguments passed to the tag. - /// The generated text. - protected abstract string GetText(IFormatProvider provider, Dictionary arguments); } } diff --git a/mustache-sharp/KeyGenerator.cs b/mustache-sharp/KeyGenerator.cs index 10f97b3..2675ccb 100644 --- a/mustache-sharp/KeyGenerator.cs +++ b/mustache-sharp/KeyGenerator.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using System.Text; namespace mustache @@ -41,10 +42,10 @@ namespace mustache return formatBuilder.ToString(); } - string IGenerator.GetText(IFormatProvider provider, KeyScope scope) + void IGenerator.GetText(KeyScope scope, TextWriter writer) { object value = scope.Find(_key); - return String.Format(provider, _format, value); + writer.Write(_format, value); } } } diff --git a/mustache-sharp/NestedContext.cs b/mustache-sharp/NestedContext.cs new file mode 100644 index 0000000..7838c38 --- /dev/null +++ b/mustache-sharp/NestedContext.cs @@ -0,0 +1,49 @@ +using System; +using System.IO; + +namespace mustache +{ + /// + /// Holds the objects to use when processing a child context of another tag. + /// + public sealed class NestedContext + { + /// + /// Initializes a new instance of a NestedContext. + /// + public NestedContext() + { + } + + /// + /// Gets or sets the writer to use when generating the child context. + /// + /// Setting the writer to null will indicate that the tag's writer should be used. + public TextWriter Writer + { + get; + set; + } + + /// + /// Gets or sets whether the text sent to the returned writer needs to be added + /// to the parent tag's writer. This should be false if the parent writer is + /// being returned or is being wrapped. + /// + public bool WriterNeedsConsidated + { + get; + set; + } + + /// + /// Gets or sets the scope to use when generating the child context. + /// + /// Setting the scope to null will indicate that the current scope should be used. + public KeyScope KeyScope + { + get; + set; + } + } +} diff --git a/mustache-sharp/Properties/AssemblyInfo.cs b/mustache-sharp/Properties/AssemblyInfo.cs index 9615c6b..c1b8c43 100644 --- a/mustache-sharp/Properties/AssemblyInfo.cs +++ b/mustache-sharp/Properties/AssemblyInfo.cs @@ -34,6 +34,6 @@ using System.Runtime.CompilerServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("0.0.1.0")] -[assembly: AssemblyFileVersion("0.0.1.0")] +[assembly: AssemblyVersion("0.0.2.0")] +[assembly: AssemblyFileVersion("0.0.2.0")] [assembly: InternalsVisibleTo("mustache-sharp.test")] \ No newline at end of file diff --git a/mustache-sharp/StaticGenerator.cs b/mustache-sharp/StaticGenerator.cs index e83aab5..39ee8d4 100644 --- a/mustache-sharp/StaticGenerator.cs +++ b/mustache-sharp/StaticGenerator.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; namespace mustache { @@ -45,9 +46,9 @@ namespace mustache } } - string IGenerator.GetText(IFormatProvider provider, KeyScope scope) + void IGenerator.GetText(KeyScope scope, TextWriter writer) { - return Value; + writer.Write(Value); } } } diff --git a/mustache-sharp/TagDefinition.cs b/mustache-sharp/TagDefinition.cs index 934b556..9566afd 100644 --- a/mustache-sharp/TagDefinition.cs +++ b/mustache-sharp/TagDefinition.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; using mustache.Properties; +using System.IO; namespace mustache { @@ -130,26 +130,35 @@ namespace mustache } /// - /// Gets the scope to use when building the inner text of the tag. + /// Gets the context to use when building the inner text of the tag. /// + /// The text writer passed /// The current scope. /// The arguments passed to the tag. /// The scope to use when building the inner text of the tag. - public virtual IEnumerable GetChildScopes(KeyScope scope, Dictionary arguments) + public virtual IEnumerable GetChildContext(TextWriter writer, KeyScope scope, Dictionary arguments) { - yield return scope; + yield return new NestedContext() { KeyScope = scope, Writer = writer }; } /// /// Applies additional formatting to the inner text of the tag. /// - /// The format provider to use. - /// The inner text of the tag. + /// The text writer to write to. /// The arguments passed to the tag. - /// The decorated inner text. - public virtual string Decorate(IFormatProvider provider, string innerText, Dictionary arguments) + public virtual void GetText(TextWriter writer, Dictionary arguments) { - return innerText; + } + + /// + /// Consolidates the text in the given writer to a string, using the given arguments as necessary. + /// + /// The writer containing the text to consolidate. + /// The arguments passed to the tag. + /// The consolidated string. + public virtual string ConsolidateWriter(TextWriter writer, Dictionary arguments) + { + return writer.ToString(); } /// diff --git a/mustache-sharp/WithGenerator.cs b/mustache-sharp/WithGenerator.cs index 80bb8d8..8fa3a67 100644 --- a/mustache-sharp/WithGenerator.cs +++ b/mustache-sharp/WithGenerator.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; namespace mustache { @@ -36,15 +37,16 @@ namespace mustache } /// - /// Gets the scopes to use for generating the tag's content. + /// Gets the context to use when building the inner text of the tag. /// + /// The text writer passed /// The current scope. - /// The arguments that were passed to the tag. - /// The scopes to use for generating the tag's content. - public override IEnumerable GetChildScopes(KeyScope scope, Dictionary arguments) + /// The arguments passed to the tag. + /// The scope to use when building the inner text of the tag. + public override IEnumerable GetChildContext(TextWriter writer, KeyScope scope, Dictionary arguments) { object context = arguments[contextParameter]; - yield return scope.CreateChildScope(context); + yield return new NestedContext() { KeyScope = scope.CreateChildScope(context), Writer = writer }; } } } diff --git a/mustache-sharp/mustache-sharp.csproj b/mustache-sharp/mustache-sharp.csproj index 262b540..820ace1 100644 --- a/mustache-sharp/mustache-sharp.csproj +++ b/mustache-sharp/mustache-sharp.csproj @@ -49,6 +49,7 @@ + True