Tags: , , , , , | Categories: General Posted by nurih on 3/6/2009 1:59 AM | Comments (0)

So I wrote the thing up. Compared with a fairly common alternative, a run of the more type safe and render friendly template took 260 ms to merge 10k iterations vs. 480 ms for the alternative. In addition, the alternative went through almost twice the allocations (meaning more GC would result).

I'm kind of pleased that this is faster than the alternative, but am going to do some more thinking of a better way.  A 46% reduction is nice, but it's just under twice as fast. Still, the benefits of a cached template and token/key checking beats the alternative.

Toying with the idea of a strongly typed template leads to a dead end pretty much. Sure, one could dynamically emit a class that exposes the token names as properties, but then how would you bind to these in compile time? since the tokens are NOT known at compile time, the programmer using the class would have to populate a name value pair in some fashion, so that at run time the binding could be made.

 

//------------------------------------
namespace Nuri.FasterTemplate
{
    public interface IFasterTemplate
    {
        string ID { get; }
        void Render(System.IO.TextWriter writer, IRuntimeValues runtimeValues);
        IRuntimeValues GetRuntimeValuesPrototype();
    }
}
//------------------------------------
namespace Nuri.FasterTemplate
{
    public interface IRuntimeValues
    {
        bool AddValue(string tokenName, string value);
        bool AddValues(IEnumerable values);
        string[] GetAllowedValues();
        IRuntimeValues GetCopy();
        bool IsAlowed(string tokenName);
        string this[string tokenName] { get; }
        string ID { get; }
    }
}
//------------------------------------
namespace Nuri.FasterTemplate
{
    public static class FasterTempateFactory
    {
        public static IFasterTemplate GetFasterTemplate(TokenConfiguration config, ref string template)
        {
            string id = Guid.NewGuid().ToString();
            TemplateParts templateParts = processTemplateParts(id, config, ref template);
            IRuntimeValues runtimeValuesPrototype = processTokens(id, templateParts);
            IFasterTemplate result = new FasterTemplate(id, templateParts, runtimeValuesPrototype);
            return result;
        }
        private static IRuntimeValues processTokens(string id, TemplateParts templateParts)
        {
            RuntimeValues result = new RuntimeValues(id);
            for (int i = 0; i < templateParts.Count; i++)
                if (templateParts[i].IsToken)
                    result[templateParts[i].Value] = string.Empty;
            return result;
        }
        private static TemplateParts processTemplateParts(string id, TokenConfiguration config, ref string template)
        {
            TemplateParts result = new TemplateParts();
            char currentChar;
            bool isToken = false;
            StringBuilder sbText = new StringBuilder(template.Length / 2);
            StringBuilder sbToken = new StringBuilder(64);
            for (int idx = 0; idx < template.Length; idx++)
            {
                currentChar = template[idx];
                if (currentChar == config.TokenStartMarker)
                { isToken = true; result.Add(new TemplatePart(sbText.ToString(), false)); sbText.Length = 0; }
                else if (currentChar == config.TokenEndMarker) { isToken = false; result.Add(new TemplatePart(sbToken.ToString(), true)); sbToken.Length = 0; } else { if (isToken)                        sbToken.Append(currentChar); else                        sbText.Append(currentChar); }
            } if (isToken == true) throw new ArgumentException("Template has unclosed token marker"); if (sbText.Length > 0) result.Add(new TemplatePart(sbText.ToString(), false)); return result;
        }
    }
}
//------------------------------------
namespace Nuri.FasterTemplate
{
    internal class FasterTemplate : IFasterTemplate
    {
        private string _ID;
        private TemplateParts _TemplateParts;
        private IRuntimeValues _RuntimeValuesPrototype;
        internal FasterTemplate(string ID, TemplateParts templateParts, IRuntimeValues runtimeValuesPrototype) { _ID = ID; _TemplateParts = templateParts; _RuntimeValuesPrototype = runtimeValuesPrototype; } public IRuntimeValues GetRuntimeValuesPrototype() { return _RuntimeValuesPrototype.GetCopy(); } public string ID { get { return _ID; } }
        public void Render(System.IO.TextWriter writer, IRuntimeValues runtimeValues)
        {
            if (runtimeValues.ID != this._ID) throw new ArgumentException("The runtime values supplied are not compatible with this template! Ensure you got the runtime values object from the template with ID " + this._ID); for (int i = 0, count = _TemplateParts.Count; i < count; i++)
            {
                TemplatePart part = _TemplateParts[i]; if (part.IsToken) { writer.Write(runtimeValues[part.Value]); }
                else
                {
                    writer.Write(part.Value);
                }
            }
        }
    }
}
//------------------------------------
namespace Nuri.FasterTemplate
{
    internal class RuntimeValues : Nuri.FasterTemplate.IRuntimeValues
    {
        private Dictionary<string,string> _AllowedValues;
        internal string _ID;
        internal RuntimeValues(string ID, int capacity)
        {
            _AllowedValues = new Dictionary<string, string>(capacity);
            _ID = ID;
        }
        internal RuntimeValues(string ID) : this(ID, 0x10) { }
        internal Dictionary<string,string> AllowedValues
        {
            get { return _AllowedValues; }
        }
        public string[] GetAllowedValues()
        {
            string[] result = new string[_AllowedValues.Count];
            int i = 0;
            foreach (string key in _AllowedValues.Keys)
            { result[i++] = key; }
            return result;
        }
        public bool IsAlowed(string tokenName)
        {
            return _AllowedValues.ContainsKey(tokenName);
        }
        public bool AddValue(string tokenName, string value)
        {
            if (_AllowedValues.ContainsKey(tokenName))
            {
                _AllowedValues[tokenName] = value;
                return true;
            }
            else
                return false;
        }

        public bool AddValues(IEnumerable values)
        {
            bool result = true;
            foreach (KeyValuePair<string, string> pair in values)
            {
                result = result && this.AddValue(pair.Key, pair.Value);
            }
            return result;
        }
        public string this[string tokenName]
        {
            get
            {
                return _AllowedValues[tokenName];
            }
            internal set
            { _AllowedValues[tokenName] = value; }
        }
        public IRuntimeValues GetCopy()
        {
            RuntimeValues result = new RuntimeValues(this._ID, _AllowedValues.Count);
            foreach (string key in _AllowedValues.Keys)
                result.AllowedValues[key] = string.Empty;

            return result;
        }
        public string ID
        {
            get { return _ID; }
        }
    }
}
//------------------------------------
namespace Nuri.FasterTemplate
{
    internal struct TemplatePart
    {
        public bool IsToken;
        public string Value;
        public TemplatePart(string value, bool isToken)
        {
            this.Value = value;
            this.IsToken = isToken;
        }
    }
}
//------------------------------------
namespace Nuri.FasterTemplate
{
    class TemplateParts : List<TemplatePart> { }
}
//------------------------------------ 
namespace Nuri.FasterTemplate
{
    public struct TokenConfiguration
    {
        public readonly char TokenStartMarker;
        public readonly char TokenEndMarker;
        public TokenConfiguration(char tokenStartMarker, char tokenEndMarker)
        {
            TokenStartMarker = tokenStartMarker;
            TokenEndMarker = tokenEndMarker;
        }
    }
}