Added delegate creation & evaluation to ConditionExpression

This commit is contained in:
atlimit8
2017-02-14 09:03:49 -06:00
parent d752e10799
commit 0fc2008f10
2 changed files with 176 additions and 53 deletions

View File

@@ -13,6 +13,8 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using Expressions = System.Linq.Expressions;
namespace OpenRA.Support
{
@@ -22,7 +24,7 @@ namespace OpenRA.Support
readonly HashSet<string> variables = new HashSet<string>();
public IEnumerable<string> Variables { get { return variables; } }
readonly Token[] postfix;
readonly Func<IReadOnlyDictionary<string, int>, int> asFunction;
enum CharClass { Whitespace, Operator, Mixed, Id, Digit }
@@ -447,30 +449,16 @@ namespace OpenRA.Support
if (currentOpeners.Count > 0)
throw new InvalidDataException("Unclosed opening parenthesis at index {0}".F(currentOpeners.Peek().Index));
// Convert to postfix (discarding parentheses) ready for evaluation
postfix = ToPostfix(tokens).ToArray();
asFunction = new Compiler().Compile(ToPostfix(tokens).ToArray());
}
static int ParseSymbol(VariableToken t, IReadOnlyDictionary<string, int> symbols)
static int ParseSymbol(string symbol, IReadOnlyDictionary<string, int> symbols)
{
int value;
symbols.TryGetValue(t.Symbol, out value);
symbols.TryGetValue(symbol, out value);
return value;
}
static void ApplyBinaryOperation(Stack<int> s, Func<int, int, int> f)
{
var x = s.Pop();
var y = s.Pop();
s.Push(f(x, y));
}
static void ApplyUnaryOperation(Stack<int> s, Func<int, int> f)
{
var x = s.Pop();
s.Push(f(x));
}
static IEnumerable<Token> ToPostfix(IEnumerable<Token> tokens)
{
var s = new Stack<Token>();
@@ -500,41 +488,169 @@ namespace OpenRA.Support
yield return s.Pop();
}
public int Evaluate(IReadOnlyDictionary<string, int> symbols)
enum ExpressionType { Int, Bool }
static readonly ParameterExpression SymbolsParam =
Expressions.Expression.Parameter(typeof(IReadOnlyDictionary<string, int>), "symbols");
static readonly ConstantExpression Zero = Expressions.Expression.Constant(0);
static readonly ConstantExpression One = Expressions.Expression.Constant(1);
static readonly ConstantExpression False = Expressions.Expression.Constant(false);
static readonly ConstantExpression True = Expressions.Expression.Constant(true);
static Expression AsBool(Expression expression)
{
var s = new Stack<int>();
foreach (var t in postfix)
return Expressions.Expression.GreaterThan(expression, Zero);
}
static Expression AsNegBool(Expression expression)
{
return Expressions.Expression.LessThanOrEqual(expression, Zero);
}
static Expression IfThenElse(Expression test, Expression ifTrue, Expression ifFalse)
{
return Expressions.Expression.Condition(test, ifTrue, ifFalse);
}
class AstStack
{
readonly List<Expression> expressions = new List<Expression>();
readonly List<ExpressionType> types = new List<ExpressionType>();
public ExpressionType PeekType() { return types[types.Count - 1]; }
public Expression Peek(ExpressionType toType)
{
switch (t.Type)
var fromType = types[types.Count - 1];
var expression = expressions[expressions.Count - 1];
if (toType == fromType)
return expression;
switch (toType)
{
case TokenType.And:
ApplyBinaryOperation(s, (x, y) => y > 0 ? x : y);
continue;
case TokenType.NotEquals:
ApplyBinaryOperation(s, (x, y) => (y != x) ? 1 : 0);
continue;
case TokenType.Or:
ApplyBinaryOperation(s, (x, y) => y > 0 ? y : x);
continue;
case TokenType.Equals:
ApplyBinaryOperation(s, (x, y) => (y == x) ? 1 : 0);
continue;
case TokenType.Not:
ApplyUnaryOperation(s, x => (x > 0) ? 0 : 1);
continue;
case TokenType.Number:
s.Push(((NumberToken)t).Value);
continue;
case TokenType.Variable:
s.Push(ParseSymbol((VariableToken)t, symbols));
continue;
default:
throw new InvalidProgramException("Evaluate is missing an evaluator for TokenType.{0}".F(
Enum<TokenType>.GetValues()[(int)t.Type]));
case ExpressionType.Bool:
return IfThenElse(AsBool(expression), True, False);
case ExpressionType.Int:
return IfThenElse(expression, One, Zero);
}
throw new InvalidProgramException("Unable to convert ExpressionType.{0} to ExpressionType.{1}".F(
Enum<ExpressionType>.GetValues()[(int)fromType], Enum<ExpressionType>.GetValues()[(int)toType]));
}
return s.Pop();
public Expression Pop(ExpressionType type)
{
var expression = Peek(type);
expressions.RemoveAt(expressions.Count - 1);
types.RemoveAt(types.Count - 1);
return expression;
}
public void Push(Expression expression, ExpressionType type)
{
expressions.Add(expression);
if (type == ExpressionType.Int)
if (expression.Type != typeof(int))
throw new InvalidOperationException("Expected System.Int type instead of {0} for {1}".F(expression.Type, expression));
if (type == ExpressionType.Bool)
if (expression.Type != typeof(bool))
throw new InvalidOperationException("Expected System.Boolean type instead of {0} for {1}".F(expression.Type, expression));
types.Add(type);
}
public void Push(Expression expression)
{
expressions.Add(expression);
if (expression.Type == typeof(int))
types.Add(ExpressionType.Int);
else if (expression.Type == typeof(bool))
types.Add(ExpressionType.Bool);
else
throw new InvalidOperationException("Unhandled result type {0} for {1}".F(expression.Type, expression));
}
}
class Compiler
{
readonly AstStack ast = new AstStack();
public Func<IReadOnlyDictionary<string, int>, int> Compile(Token[] postfix)
{
foreach (var t in postfix)
{
switch (t.Type)
{
case TokenType.And:
{
var y = ast.Pop(ExpressionType.Bool);
var x = ast.Pop(ExpressionType.Bool);
ast.Push(Expressions.Expression.And(x, y));
continue;
}
case TokenType.Or:
{
var y = ast.Pop(ExpressionType.Bool);
var x = ast.Pop(ExpressionType.Bool);
ast.Push(Expressions.Expression.Or(x, y));
continue;
}
case TokenType.NotEquals:
{
var y = ast.Pop(ExpressionType.Int);
var x = ast.Pop(ExpressionType.Int);
ast.Push(Expressions.Expression.NotEqual(x, y));
continue;
}
case TokenType.Equals:
{
var y = ast.Pop(ExpressionType.Int);
var x = ast.Pop(ExpressionType.Int);
ast.Push(Expressions.Expression.Equal(x, y));
continue;
}
case TokenType.Not:
{
if (ast.PeekType() == ExpressionType.Bool)
ast.Push(Expressions.Expression.Not(ast.Pop(ExpressionType.Bool)));
else
ast.Push(AsNegBool(ast.Pop(ExpressionType.Int)));
continue;
}
case TokenType.Number:
{
ast.Push(Expressions.Expression.Constant(((NumberToken)t).Value));
continue;
}
case TokenType.Variable:
{
var symbol = Expressions.Expression.Constant(((VariableToken)t).Symbol);
Func<string, IReadOnlyDictionary<string, int>, int> parseSymbol = ParseSymbol;
ast.Push(Expressions.Expression.Call(parseSymbol.Method, symbol, SymbolsParam));
continue;
}
default:
throw new InvalidProgramException(
"ConditionExpression.Compiler.Compile() is missing an expression builder for TokenType.{0}".F(
Enum<TokenType>.GetValues()[(int)t.Type]));
}
}
return Expressions.Expression.Lambda<Func<IReadOnlyDictionary<string, int>, int>>(
ast.Pop(ExpressionType.Int), SymbolsParam).Compile();
}
}
public int Evaluate(IReadOnlyDictionary<string, int> symbols)
{
return asFunction(symbols);
}
}
}

View File

@@ -66,6 +66,13 @@ namespace OpenRA.Test
AssertValue("-12", -12);
}
[TestCase(TestName = "Booleans")]
public void TestBooleans()
{
AssertValue("false", 0);
AssertValue("true", 1);
}
[TestCase(TestName = "AND operation")]
public void TestAnd()
{
@@ -75,8 +82,8 @@ namespace OpenRA.Test
AssertFalse("false && true");
AssertValue("2 && false", 0);
AssertValue("false && 2", 0);
AssertValue("3 && 2", 2);
AssertValue("2 && 3", 3);
AssertValue("3 && 2", 1);
AssertValue("2 && 3", 1);
}
[TestCase(TestName = "OR operation")]
@@ -86,10 +93,10 @@ namespace OpenRA.Test
AssertFalse("false || false");
AssertTrue("true || false");
AssertTrue("false || true");
AssertValue("2 || false", 2);
AssertValue("false || 2", 2);
AssertValue("3 || 2", 3);
AssertValue("2 || 3", 2);
AssertValue("2 || false", 1);
AssertValue("false || 2", 1);
AssertValue("3 || 2", 1);
AssertValue("2 || 3", 1);
}
[TestCase(TestName = "Equals operation")]