using System; using System.Collections.Generic; using UniLinq; using Sprache; using static Sprache.Parse; namespace CodeWriter.ExpressionParser { public delegate T Expression(); public abstract class ExpressionParser { private readonly Dictionary _builderCached = new Dictionary(); private Parser _parserCached; public Expression CompilePredicate(string input, ExpressionContext context, bool cache) { var expr = Compile(input, context, cache); return () => IsTrue(expr.Invoke()); } public Expression Compile(string input, ExpressionContext context, bool cache) { context = context ?? new ExpressionContext(); _parserCached = _parserCached ?? CreateParser(); ExprBuilder builder; try { if (cache) { if (!_builderCached.TryGetValue(input, out builder)) { builder = _parserCached.Parse(input); _builderCached.Add(input, builder); } } else { builder = _parserCached.Parse(input); } } catch (ParseException parseException) { throw new ExpressionParseException(input, parseException); } return builder.Invoke(context); } private static Parser Operator(string op, BinaryFunc fun) { return String(op).Token().Return(fun).Named(op); } private static Parser BinaryOperator(Parser op, Parser operand, Func apply) => ( from l in operand from rest in ( from f in op from r in operand select apply(f, l, r) ).Optional() select rest.IsEmpty ? l : rest.Get() ); private Parser CreateParser() { var letterOrUnderscore = Char(c => char.IsLetter(c) || c == '_', "letter or underscore"); var letterOrDigitOrUnderscore = Char(c => char.IsLetterOrDigit(c) || c == '_', "letter or digit or underscore"); var constant = ( from number in DecimalInvariant select MakeConstant(number, Parse) ).Named("number"); var add = Operator("+", Add); var sub = Operator("-", Sub); var mul = Operator("*", Mul); var div = Operator("/", Div); var mod = Operator("%", Mod); var pow = Operator("^", Pow); var eq = Operator("=", Equal); var neq = Operator("!=", NotEqual); var and = Operator("AND", And); var or = Operator("OR", Or); var lt = Operator("<", LessThan); var lte = Operator("<=", LessThanOrEqual); var gt = Operator(">", GreaterThan); var gte = Operator(">=", GreaterThanOrEqual); var variable = ( from nameHead in letterOrUnderscore.Once().Text() from nameTail in letterOrDigitOrUnderscore.Many().Text() select MakeVariable(nameHead + nameTail) ).Named("variable"); Parser expression = null; var function = ( from name in Letter.AtLeastOnce().Text() from lparen in Char('(') // ReSharper disable once AccessToModifiedClosure from expr in Ref(() => expression).DelimitedBy(Char(',').Token()) from rparen in Char(')') select MakeFunction(name, expr.ToList()) ).Named("function"); var factor = ( from lparen in Char('(') // ReSharper disable once AccessToModifiedClosure from expr in Ref(() => expression) from rparen in Char(')') select expr ).Named("expression") .XOr(constant) .XOr(function) .Or(variable); var operand = ( ( from sign in Char('-') from fact in factor select MakeUnary(Negate, fact) ) .XOr(factor) ).Token(); var term1 = ChainRightOperator(pow, operand, MakeBinary); var term2 = ChainOperator(mul.Or(div).Or(mod), term1, MakeBinary); var term3 = ChainOperator(add.Or(sub), term2, MakeBinary); var term4 = BinaryOperator(lte.Or(lt).Or(gte).Or(gt), term3, MakeBinary); var term5 = BinaryOperator(eq.Or(neq), term4, MakeBinary); var term6 = ChainOperator(and, term5, MakeBinary); var term7 = ChainOperator(or, term6, MakeBinary); expression = term7; return expression.End(); } protected abstract T False { get; } protected abstract T True { get; } protected abstract T Parse(string input); protected abstract T Negate(T v); protected abstract T Add(T a, T b); protected abstract T Sub(T a, T b); protected abstract T Mul(T a, T b); protected abstract T Div(T a, T b); protected abstract T Mod(T a, T b); protected abstract T Pow(T a, T b); protected abstract T Equal(T a, T b); protected abstract T NotEqual(T a, T b); protected abstract T LessThan(T a, T b); protected abstract T LessThanOrEqual(T a, T b); protected abstract T GreaterThan(T a, T b); protected abstract T GreaterThanOrEqual(T a, T b); protected abstract bool IsTrue(T v); protected abstract T Round(T v); protected abstract T Floor(T v); protected abstract T Ceiling(T v); protected abstract T Log10(T v); protected virtual T Log(T v, T newBase) => Div(Log10(v), Log10(newBase)); private T Not(T v) => IsTrue(v) ? False : True; private T And(T a, T b) => IsTrue(a) ? b : a; private T Or(T a, T b) => IsTrue(a) ? a : b; private T Min(T a, T b) => IsTrue(GreaterThan(a, b)) ? b : a; private T Max(T a, T b) => IsTrue(GreaterThan(b, a)) ? b : a; private delegate Expression ExprBuilder(ExpressionContext context); private delegate T UnaryFunc(T a); private delegate T BinaryFunc(T a, T b); private ExprBuilder MakeFunction(string name, List parameterBuilders) { switch (name) { case "NOT": return MakeFunction1(Not); case "ROUND": return MakeFunction1(Round); case "CEILING": return MakeFunction1(Ceiling); case "FLOOR": return MakeFunction1(Floor); case "LOG": return parameterBuilders.Count == 2 ? MakeBinary(Log, parameterBuilders[0], parameterBuilders[1]) : MakeFunction1(Log10); case "MIN": return MakeFunctionFold(Min); case "MAX": return MakeFunctionFold(Max); case "IF": if (parameterBuilders.Count < 3 || parameterBuilders.Count % 2 != 1) { throw new FunctionNotDefinedException(name, "Wrong parameters count"); } return context => { var conditions = new List>(); var results = new List>(); var defaultResult = parameterBuilders[parameterBuilders.Count - 1].Invoke(context); for (var i = 0; i < parameterBuilders.Count - 1; i += 2) { conditions.Add(parameterBuilders[i].Invoke(context)); results.Add(parameterBuilders[i + 1].Invoke(context)); } return () => { for (var i = 0; i < conditions.Count; i++) { if (IsTrue(conditions[i].Invoke())) { return results[i].Invoke(); } } return defaultResult.Invoke(); }; }; default: throw new FunctionNotDefinedException(name, "Unknown name"); } ExprBuilder MakeFunction1(Func func) { if (parameterBuilders.Count != 1) { throw new FunctionNotDefinedException(name, "Wrong parameters count"); } return context => { var inner = parameterBuilders[0].Invoke(context); return () => func(inner.Invoke()); }; } ExprBuilder MakeFunctionFold(Func func) { if (parameterBuilders.Count < 1) { throw new FunctionNotDefinedException(name, "Wrong parameters count"); } return context => { var inner = new List>(); for (var i = 0; i < parameterBuilders.Count; i++) { inner.Add(parameterBuilders[i].Invoke(context)); } return () => { var result = inner[0].Invoke(); for (var i = 1; i < inner.Count; i++) { result = func(result, inner[i].Invoke()); } return result; }; }; } } private static ExprBuilder MakeUnary(UnaryFunc func, ExprBuilder innerBuilder) { return context => { var inner = innerBuilder.Invoke(context); return () => func(inner.Invoke()); }; } private static ExprBuilder MakeBinary(BinaryFunc func, ExprBuilder l, ExprBuilder r) { return context => { var left = l.Invoke(context); var right = r.Invoke(context); return () => func(left.Invoke(), right.Invoke()); }; } private ExprBuilder MakeVariable(string name) { if (name.Equals("TRUE", StringComparison.Ordinal)) { return context => () => True; } if (name.Equals("FALSE", StringComparison.Ordinal)) { return context => () => False; } return context => { var variable = context.GetVariable(name); return variable; }; } private static ExprBuilder MakeConstant(string valueString, Func parser) { var value = parser(valueString); return context => () => value; } } public class ExpressionContext { private readonly ExpressionContext _parent; private readonly Func> _unregisteredVariableResolver; private readonly Dictionary> _variables = new Dictionary>(); public ExpressionContext(ExpressionContext parent = null, Func> unregisteredVariableResolver = null) { _parent = parent; _unregisteredVariableResolver = unregisteredVariableResolver; } public void RegisterVariable(string name, Expression value) { if (_variables.ContainsKey(name)) { throw new InvalidOperationException($"Variable {name} already registered"); } _variables.Add(name, value); } public Expression GetVariable(string name, bool nullIsOk = false) { if (_variables.TryGetValue(name, out var variable)) { return variable; } if (_unregisteredVariableResolver != null) { variable = _unregisteredVariableResolver.Invoke(name); if (variable != null) { return variable; } } var parentVariable = _parent?.GetVariable(name, nullIsOk: true); if (parentVariable != null) { return parentVariable; } if (nullIsOk) { return null; } throw new VariableNotDefinedException(name); } } [Obsolete("ExpresionContext contains a typo. Use ExpressionContext instead", true)] public class ExpresionContext : ExpressionContext { } public class VariableNotDefinedException : Exception { public VariableNotDefinedException(string name) : base($"Variable '{name}' not defined") { } } public class FunctionNotDefinedException : Exception { public FunctionNotDefinedException(string name, string reason) : base( $"Function '{name}' not defined: {reason}") { } } public class ExpressionParseException : Exception { public ExpressionParseException(string expression, ParseException parseException) : base($"Failed to parse expression '{expression}'{Environment.NewLine}{parseException.Message}") { } } }