Created
December 27, 2016 20:06
-
-
Save nyk0r/a0d12788670f0314412f2e40c41c3456 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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