Created
June 12, 2014 10:57
-
-
Save mlehmk/405b4c3adee5117b0ba6 to your computer and use it in GitHub Desktop.
ExpressionMaker
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
grammar CsExpression; | |
options { | |
language=CSharp3; | |
TokenLabelType=CommonToken; | |
output=AST; | |
ASTLabelType=CommonTree; | |
} | |
@lexer::namespace{ExpressionParser} | |
@parser::namespace{ExpressionParser} | |
/* | |
* Parser Rules | |
*/ | |
public | |
compileUnit | |
: WS? ( COMMENT WS? )* expression EOF | |
; | |
expression | |
: conditionalExpression | |
; | |
conditionalExpression | |
: orElseExpression ( '?'^ WS? conditionalExpression ':'! WS? conditionalExpression )? | |
; | |
orElseExpression | |
: andAlsoExpression ( '||'^ WS? andAlsoExpression )* | |
; | |
andAlsoExpression | |
: orExpression ( '&&'^ WS? orExpression )* | |
; | |
orExpression | |
: xorExpression ( '|'^ WS? xorExpression )* | |
; | |
xorExpression | |
: andExpression ( '^'^ WS? andExpression )* | |
; | |
andExpression | |
: equalityExpression ( '&'^ WS? equalityExpression )* | |
; | |
equalityExpression | |
: comparisionExpression ( ( 'in'^ WS? '('! WS? elementList ')'! WS? ) | ( EQUALITY^ WS? comparisionExpression ) )? | |
; | |
elementList | |
: (ADDITIVE WS?)? DECIMAL WS? ( ','! WS? (ADDITIVE WS?)? DECIMAL WS? )* | |
| STRING WS? ( ','! WS? STRING WS? )* | |
; | |
comparisionExpression | |
: additionExpression ( COMPARISION^ WS? additionExpression )? | |
; | |
additionExpression | |
: multiplicationExpression ( ADDITIVE^ WS? multiplicationExpression )* | |
; | |
multiplicationExpression | |
: unaryExpression ( ('*' | '/' | '%' )^ WS? unaryExpression )* | |
; | |
unaryExpression | |
: ADDITIVE^ WS? unaryExpression | |
| UNARY^ WS? unaryExpression | |
| term | |
; | |
term | |
: BOOL WS? | |
| STRING WS? | |
| DATE WS? | |
| DECIMAL WS? | |
| memberAccess | |
| '('! WS? expression ')'! WS? | |
; | |
memberAccess | |
: ID^ WS? ( '.'! WS? ID WS? )* callOrIndex? | |
; | |
callOrIndex | |
: '(' WS? ( expression ( ','! WS? expression )* )? ')' WS? | |
| '[' WS? ( expression ( ','! WS? expression )* )? ']' WS? | |
; | |
/* | |
* Lexer Rules | |
*/ | |
public | |
BOOL | |
: 'true' | 'false' | |
; | |
public | |
ID : ('a'..'z'|'A'..'Z'|'_') ('a'..'z'|'A'..'Z'|'0'..'9'|'_')* | |
; | |
public | |
DECIMAL | |
: '0'..'9'+ ( '.' '0'..'9'* )? | |
; | |
public | |
DATE | |
: '#' ('-' | '.' | '0'..'9')+ '#' | |
; | |
public | |
ADDITIVE | |
: '+' | '-' | |
; | |
public | |
UNARY | |
: '!' | '~' | |
; | |
public | |
EQUALITY | |
: '==' | '!=' | 'is' | 'as' | |
; | |
public | |
COMPARISION | |
: '<' | '<=' | '>=' | '>' | |
; | |
WS | |
: ('\t' | '\r' | '\n' | ' ')+ {Skip();} | |
; | |
COMMENT | |
: '//' ~('\n'|'\r')* '\r'? '\n' {Skip();} | |
| '/*' ( options {greedy=false;} : . )* '*/' {Skip();} | |
; | |
public | |
STRING | |
: '"' ( ESC_SEQ | ~('\\'|'"') )* '"' | |
| '@"' ( '""' | ~('"') )* '"' | |
; | |
fragment | |
HEX_DIGIT : ('0'..'9'|'a'..'f'|'A'..'F') ; | |
fragment | |
ESC_SEQ | |
: '\\' ('b'|'t'|'n'|'f'|'r'|'\"'|'\''|'\\') | |
| UNICODE_ESC | |
| OCTAL_ESC | |
; | |
fragment | |
OCTAL_ESC | |
: '\\' ('0'..'3') ('0'..'7') ('0'..'7') | |
| '\\' ('0'..'7') ('0'..'7') | |
| '\\' ('0'..'7') | |
; | |
fragment | |
UNICODE_ESC | |
: '\\' 'u' HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT | |
; |
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; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Linq.Expressions; | |
using System.Text; | |
using System.Threading.Tasks; | |
using Antlr.Runtime; | |
using Antlr.Runtime.Tree; | |
using System.Reflection; | |
using System.Globalization; | |
namespace ExpressionParser | |
{ | |
public static class Expressions | |
{ | |
public static Expression<Func<T,bool>> Parse<T>(string expression) | |
{ | |
ANTLRStringStream source = new ANTLRStringStream(expression); | |
CsExpressionLexer lexer = new CsExpressionLexer(source); | |
BufferedTokenStream tokenstream = new BufferedTokenStream(lexer); | |
CsExpressionParser parser = new CsExpressionParser(tokenstream); | |
var result = parser.compileUnit(); | |
ParameterExpression p = Expression.Parameter(typeof(T), "p"); | |
return Expression.Lambda<Func<T,bool>>(MakeExpression(result.Tree, typeof(T), p), p); | |
} | |
private class ExpressionMaker | |
{ | |
public Type type { get; set; } | |
public ParameterExpression parameter { get; set; } | |
public ExpressionMaker(Type type, ParameterExpression parameter) | |
{ | |
this.type = type; | |
this.parameter = parameter; | |
} | |
internal Expression UpCast(Func<Expression, Expression, BinaryExpression> @operator, Expression left, Expression right) | |
{ | |
if (left.Type.Equals(right.Type)) | |
{ | |
return @operator(left, right); | |
} | |
if (left.Type.IsAssignableFrom(right.Type) || CanUpCast(right.Type, left.Type)) | |
{ | |
return @operator(left, Expression.Convert(right, left.Type)); | |
} | |
if (right.Type.IsAssignableFrom(left.Type) || CanUpCast(left.Type, right.Type)) | |
{ | |
return @operator(Expression.Convert(left, right.Type), right); | |
} | |
return null; | |
} | |
internal bool CanUpCast(Type from, Type to) | |
{ | |
if(from.Equals(typeof(int))) | |
{ | |
if (to.Equals(typeof(long))) | |
{ | |
return true; | |
} | |
if (to.Equals(typeof(decimal))) | |
{ | |
return true; | |
} | |
} | |
if (from.Equals(typeof(long))) | |
{ | |
if (to.Equals(typeof(decimal))) | |
{ | |
return true; | |
} | |
} | |
return false; | |
} | |
private readonly string[] allowed_string_methods = new string[] | |
{ | |
"StartsWith", | |
"EndsWith", | |
"Contains" | |
}; | |
internal Expression MakeExpression(ITree tree) | |
{ | |
switch (tree.Type) | |
{ | |
case CsExpressionParser.ID: // member access | |
{ | |
PropertyInfo member = (PropertyInfo)type.GetMember(tree.Text, MemberTypes.Property, BindingFlags.Public | BindingFlags.Instance)[0]; | |
Expression expression = Expression.MakeMemberAccess(parameter, member); | |
if (tree.ChildCount == 0) | |
{ | |
return expression; | |
} | |
if (member.PropertyType == typeof(string)) | |
{ | |
if (tree.GetChild(0).Text == "StartsWith" && tree.GetChild(1).Text == "(" && tree.GetChild(3).Text == ")") | |
{ | |
string str = Expression.Lambda<Func<string>>(MakeExpression(tree.GetChild(2))).Compile().Invoke(); | |
MethodInfo method = (MethodInfo)typeof(string).GetMethod(tree.GetChild(0).Text, new Type[] { typeof(string) }); | |
expression = Expression.Call(expression, method, Expression.Constant(str, typeof(string))); | |
return expression; | |
} | |
} | |
throw new Exception("Unhandled member access"); | |
} | |
case CsExpressionParser.EQUALITY: | |
switch (tree.Text) | |
{ | |
case "==": | |
return UpCast(Expression.Equal, MakeExpression(tree.GetChild(0)), MakeExpression(tree.GetChild(1))); | |
case "!=": | |
return UpCast(Expression.NotEqual, MakeExpression(tree.GetChild(0)), MakeExpression(tree.GetChild(1))); | |
case "is": | |
return Expression.TypeEqual(MakeExpression(tree.GetChild(0)), GetType(tree.GetChild(1))); | |
case "as": | |
return Expression.TypeAs(MakeExpression(tree.GetChild(0)), GetType(tree.GetChild(1))); | |
} | |
break; | |
case CsExpressionParser.COMPARISION: | |
switch (tree.Text) | |
{ | |
case "<": | |
return UpCast(Expression.LessThan, MakeExpression(tree.GetChild(0)), MakeExpression(tree.GetChild(1))); | |
case "<=": | |
return UpCast(Expression.LessThanOrEqual, MakeExpression(tree.GetChild(0)), MakeExpression(tree.GetChild(1))); | |
case ">": | |
return UpCast(Expression.GreaterThan, MakeExpression(tree.GetChild(0)), MakeExpression(tree.GetChild(1))); | |
case ">=": | |
return UpCast(Expression.GreaterThanOrEqual, MakeExpression(tree.GetChild(0)), MakeExpression(tree.GetChild(1))); | |
} | |
break; | |
case CsExpressionParser.STRING: | |
return Expression.Constant(GetString(tree.Text), typeof(string)); | |
case CsExpressionParser.DECIMAL: | |
return FromNumber(tree.Text); | |
case CsExpressionParser.DATE: | |
return Expression.Constant(DateTime.Parse(tree.Text.Replace("#", "")), typeof(DateTime)); | |
case CsExpressionParser.BOOL: | |
return Expression.Constant(bool.Parse(tree.Text), typeof(bool)); | |
case CsExpressionParser.ADDITIVE: | |
switch (tree.Text) | |
{ | |
case "+": | |
if (tree.ChildCount == 1) | |
{ | |
return Expression.UnaryPlus(MakeExpression(tree.GetChild(0))); | |
} | |
else | |
{ | |
return Expression.Add(MakeExpression(tree.GetChild(0)), MakeExpression(tree.GetChild(1))); | |
} | |
case "-": | |
if (tree.ChildCount == 1) | |
{ | |
return Expression.Negate(MakeExpression(tree.GetChild(0))); | |
} | |
else | |
{ | |
return Expression.Subtract(MakeExpression(tree.GetChild(0)), MakeExpression(tree.GetChild(1))); | |
} | |
} | |
break; | |
case CsExpressionParser.UNARY: | |
switch (tree.Text) | |
{ | |
case "!": | |
return Expression.Not(MakeExpression(tree.GetChild(0))); | |
case "~": | |
return Expression.OnesComplement(MakeExpression(tree.GetChild(0))); | |
} | |
break; | |
default: | |
switch (tree.Text) | |
{ | |
case "&&": | |
return Expression.AndAlso(MakeExpression(tree.GetChild(0)), MakeExpression(tree.GetChild(1))); | |
case "||": | |
return Expression.OrElse(MakeExpression(tree.GetChild(0)), MakeExpression(tree.GetChild(1))); | |
case "?": | |
return Expression.Condition(MakeExpression(tree.GetChild(0)), MakeExpression(tree.GetChild(1)), MakeExpression(tree.GetChild(2))); | |
case "*": | |
return UpCast(Expression.Multiply, MakeExpression(tree.GetChild(0)), MakeExpression(tree.GetChild(1))); | |
case "/": | |
return UpCast(Expression.Divide, MakeExpression(tree.GetChild(0)), MakeExpression(tree.GetChild(1))); | |
case "%": | |
return UpCast(Expression.Modulo, MakeExpression(tree.GetChild(0)), MakeExpression(tree.GetChild(1))); | |
case "in": | |
{ | |
if (tree.GetChild(1).Type == CsExpressionParser.DECIMAL || tree.GetChild(1).Type == CsExpressionParser.ADDITIVE) | |
{ | |
ICollection<decimal> collection = new HashSet<decimal>(); | |
bool negate = false; | |
for (int i = 1; i < tree.ChildCount; ++i) | |
{ | |
ITree child = tree.GetChild(i); | |
if (child.Type == CsExpressionParser.ADDITIVE) | |
{ | |
negate = child.Text == "-"; | |
} | |
else | |
{ | |
if (negate) | |
{ | |
collection.Add(-decimal.Parse(child.Text)); | |
} | |
else | |
{ | |
collection.Add(decimal.Parse(child.Text)); | |
} | |
negate = false; | |
} | |
} | |
MethodInfo contains = typeof(ICollection<decimal>).GetMethod("Contains"); | |
ConstantExpression c = Expression.Constant(collection, typeof(ICollection<decimal>)); | |
return Expression.Call(c, contains, MakeExpression(tree.GetChild(0))); | |
} | |
else if (tree.GetChild(1).Type == CsExpressionParser.STRING) | |
{ | |
ICollection<string> collection = new HashSet<string>(); | |
for (int i = 1; i < tree.ChildCount; ++i) | |
{ | |
collection.Add(GetString(tree.GetChild(i).Text)); | |
} | |
MethodInfo contains = typeof(ICollection<string>).GetMethod("Contains"); | |
ConstantExpression c = Expression.Constant(collection, typeof(ICollection<string>)); | |
return Expression.Call(c, contains, MakeExpression(tree.GetChild(0))); | |
} | |
return Expression.Constant(false, typeof(bool)); | |
} | |
} | |
break; | |
} | |
return null; | |
} | |
internal Type GetType(ITree tree) | |
{ | |
return null; | |
} | |
internal ConstantExpression FromNumber(string number) | |
{ | |
decimal d = decimal.Parse(number, CultureInfo.InvariantCulture); | |
if (number.IndexOf('.') < 0) | |
{ | |
if (d >= int.MinValue && d <= int.MaxValue) | |
{ | |
return Expression.Constant((int)d, typeof(int)); | |
} | |
else | |
{ | |
return Expression.Constant((long)d, typeof(long)); | |
} | |
} | |
else | |
{ | |
return Expression.Constant(d, typeof(decimal)); | |
} | |
} | |
internal string GetString(string text) | |
{ | |
StringBuilder sb = new StringBuilder(); | |
if (text[0] == '"') | |
{ | |
int i = 1; | |
while (i < text.Length - 1) | |
{ | |
switch (text[i]) | |
{ | |
case '\\': | |
++i; | |
switch (text[i]) | |
{ | |
case 't': | |
sb.Append('\t'); | |
break; | |
case 'n': | |
sb.Append('\n'); | |
break; | |
case 'r': | |
sb.Append('\r'); | |
break; | |
case '"': | |
case '\\': | |
default: | |
sb.Append(text[i]); | |
break; | |
} | |
break; | |
default: | |
sb.Append(text[i]); | |
break; | |
} | |
++i; | |
} | |
} | |
else if (text[0] == '@' && text[1] == '"') | |
{ | |
int i = 2; | |
while (i < text.Length - 1) | |
{ | |
switch (text[i]) | |
{ | |
case '"': | |
++i; | |
sb.Append('"'); | |
break; | |
default: | |
sb.Append(text[i]); | |
break; | |
} | |
++i; | |
} | |
} | |
return sb.ToString(); | |
} | |
} | |
public static Expression MakeExpression(CommonTree tree, Type t, ParameterExpression p) | |
{ | |
if (tree.ChildCount != 2 && tree.Children[1].Type != CsExpressionLexer.EOF) | |
throw new Exception("Parser error"); | |
var em = new ExpressionMaker(t, p); | |
var exp = em.MakeExpression(tree.Children[0]); | |
while (exp.CanReduce) | |
{ | |
exp = exp.Reduce(); | |
} | |
return exp; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment