Skip to content

Instantly share code, notes, and snippets.

@nyk0r
Created December 27, 2016 20:06
Show Gist options
  • Save nyk0r/a0d12788670f0314412f2e40c41c3456 to your computer and use it in GitHub Desktop.
Save nyk0r/a0d12788670f0314412f2e40c41c3456 to your computer and use it in GitHub Desktop.
using System;
namespace FormatCompiler
{
using System.Text;
using System.Linq.Expressions;
using System.Reflection;
using System.Collections.Generic;
enum TokenType { Unknown, OpenBrace, CloseBrace, Identifier, Literal }
struct Token
{
private readonly TokenType _type;
private readonly string _value;
public Token(TokenType type, string value)
{
_type = type;
_value = value;
}
public TokenType Type { get { return _type; } }
public string Value { get { return _value; } }
}
class Tokenizer
{
private readonly string _source;
private int _pos = 0;
private bool _inExpression = false;
public Tokenizer(string source)
{
_source = source;
}
private string Peak
{
get
{
if (_pos < _source.Length)
{
return _source[_pos].ToString();
}
return null;
}
}
private string GetCh()
{
var ch = Peak;
if (ch != null)
{
++_pos;
}
return ch;
}
private void ReturnCh()
{
--_pos;
}
public Token GetNext()
{
TokenType type;
StringBuilder value = new StringBuilder();
var ch = GetCh();
if (ch == null)
{
return new Token(TokenType.Unknown, "");
}
if (ch == "{" && Peak == "{")
{
if (!_inExpression)
{
_inExpression = true;
type = TokenType.OpenBrace;
GetCh();
value.Append("{{");
}
else
{
throw new Exception(string.Format("Unxpected token in postions {0}", _pos - 1));
}
} else if (ch == "}" && Peak == "}")
{
if (_inExpression)
{
_inExpression = false;
type = TokenType.CloseBrace;
GetCh();
value.Append("}}");
}
else
{
throw new Exception(string.Format("Unxpected token in postions {0}", _pos - 1));
}
} else {
type = _inExpression ? TokenType.Identifier : TokenType.Literal;
while (ch != null) {
if (ch == "{" && Peak == "{" || ch == "}" && Peak == "}") {
ReturnCh();
break;
}
if (type == TokenType.Identifier && !char.IsWhiteSpace(ch, 0) || type == TokenType.Literal) {
value.Append(ch);
}
ch = GetCh();
}
}
return new Token(type, value.ToString());
}
}
public static class Compiler
{
class LambdaHelper<T>
{
private readonly ParameterExpression _source;
private readonly ParameterExpression _builder;
private readonly List<Expression> _statements;
public LambdaHelper()
{
_source = Expression.Parameter(typeof(T), "source");
_builder = Expression.Variable(typeof(StringBuilder), "builder");
_statements = new List<Expression>(20);
_statements.Add(Expression.Assign(_builder, Expression.New(typeof(StringBuilder))));
}
public void Append(string literal)
{
var method = typeof(StringBuilder).GetMethod("Append", new[] { typeof(string) });
_statements.Add(
Expression.Call(
_builder,
method,
Expression.Constant(literal, typeof(string))
)
);
}
public void Append(PropertyInfo prop, string format)
{
var method = typeof(StringBuilder).GetMethod("AppendFormat", new[] { typeof(string), typeof(object) });
_statements.Add(
Expression.Call(
_builder,
method,
Expression.Constant(format),
Expression.Convert(Expression.Property(_source, prop), typeof(object))
)
);
}
public void Append(PropertyInfo prop)
{
var method = typeof(StringBuilder).GetMethod("Append", new[] { typeof(object) });
_statements.Add(
Expression.Call(
_builder,
method,
Expression.Convert(Expression.Property(_source, prop), typeof(object))
)
);
}
public Expression<Func<T, string>> Compile()
{
_statements.Add(Expression.Call(_builder, typeof(StringBuilder).GetMethod("ToString", new Type[] {})));
return Expression.Lambda<Func<T, string>>(
Expression.Block(new[] { _builder }, _statements),
new[] { _source }
);
}
}
public static Func<T, string> Compile<T>(string format)
{
var ttype = typeof(T);
var tokenizer = new Tokenizer(format);
var helper = new LambdaHelper<T>();
for (var token = tokenizer.GetNext(); token.Type != TokenType.Unknown; token = tokenizer.GetNext()){
if (token.Type == TokenType.Literal) {
helper.Append(token.Value);
} else if (token.Type == TokenType.Identifier) {
var parts = token.Value.Split(':');
string name = parts[0], varFormat = parts.Length > 1 ? string.Join(":", parts, 1, parts.Length-1) : "";
var prop = ttype.GetProperty(name);
if (!string.IsNullOrWhiteSpace(varFormat))
{
helper.Append(prop, "{0:" + varFormat + "}");
}
else
{
helper.Append(prop);
}
}
}
return helper.Compile().Compile();
}
}
}
public static class Program
{
class Member
{
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
public string NickName { get; set; }
public DateTime BirthDate { get; set; }
}
class Account
{
public string Type { get; set; }
public string Name { get; set; }
public string Ownership { get; set; }
public decimal Balance { get; set; }
public DateTime CreationDate { get; set; }
}
public static void Main()
{
var members = new[]
{
new Member
{
FirstName = "John",
MiddleName = "J",
LastName = "Doe",
NickName = "Master",
BirthDate = new DateTime(1988, 7, 12)
},
new Member
{
FirstName = "Jack",
MiddleName = "M",
LastName = "Lamber",
NickName = "Dark Lord",
BirthDate = new DateTime(1978, 5, 10)
},
new Member
{
FirstName = "Will",
MiddleName = "A",
LastName = "Raymor",
NickName = "",
BirthDate = new DateTime(1987, 5, 10)
},
};
var accounts = new[]
{
new Account
{
Type = "Saving",
Name = "Retirement",
Ownership = "Joint",
Balance = 900,
CreationDate = new DateTime(2015, 1, 1)
},
new Account
{
Type = "Checking",
Name = "Every day checks",
Ownership = "Own",
Balance = 800,
CreationDate = new DateTime(2015, 1, 1)
},
new Account
{
Type = "Loan",
Name = "Dream Car",
Ownership = "Own",
Balance = 3500,
CreationDate = new DateTime(2015, 1, 1)
}
};
var memberFormat = FormatCompiler.Compiler.Compile<Member>("{{FirstName}} *{{NickName}}* {{LastName}} {{BirthDate}}");
foreach (var member in members)
{
Console.WriteLine(memberFormat(member));
}
var accountFormat = FormatCompiler.Compiler.Compile<Account>("{{Name}} - {{Type}} ({{Balance:c}})");
foreach (var account in accounts)
{
Console.WriteLine(accountFormat(account));
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment