Skip to content

Instantly share code, notes, and snippets.

@mlehmk
Created June 12, 2014 10:57
Show Gist options
  • Save mlehmk/405b4c3adee5117b0ba6 to your computer and use it in GitHub Desktop.
Save mlehmk/405b4c3adee5117b0ba6 to your computer and use it in GitHub Desktop.
ExpressionMaker
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
;
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