Added (Boolean|Integer)Expression subclasses of VariableExpression

This commit is contained in:
atlimit8
2017-04-11 02:52:54 -05:00
parent e73d3922dd
commit b0187dd646
6 changed files with 79 additions and 24 deletions

View File

@@ -398,13 +398,29 @@ namespace OpenRA
return InvalidValueAction(value, fieldType, fieldName); return InvalidValueAction(value, fieldType, fieldName);
} }
else if (fieldType == typeof(VariableExpression)) else if (fieldType == typeof(BooleanExpression))
{ {
if (value != null) if (value != null)
{ {
try try
{ {
return new VariableExpression(value); return new BooleanExpression(value);
}
catch (InvalidDataException e)
{
throw new YamlException(e.Message);
}
}
return InvalidValueAction(value, fieldType, fieldName);
}
else if (fieldType == typeof(IntegerExpression))
{
if (value != null)
{
try
{
return new IntegerExpression(value);
} }
catch (InvalidDataException e) catch (InvalidDataException e)
{ {

View File

@@ -18,14 +18,12 @@ using Expressions = System.Linq.Expressions;
namespace OpenRA.Support namespace OpenRA.Support
{ {
public class VariableExpression public abstract class VariableExpression
{ {
public readonly string Expression; public readonly string Expression;
readonly HashSet<string> variables = new HashSet<string>(); readonly HashSet<string> variables = new HashSet<string>();
public IEnumerable<string> Variables { get { return variables; } } public IEnumerable<string> Variables { get { return variables; } }
readonly Func<IReadOnlyDictionary<string, int>, int> asFunction;
enum CharClass { Whitespace, Operator, Mixed, Id, Digit } enum CharClass { Whitespace, Operator, Mixed, Id, Digit }
static CharClass CharClassOf(char c) static CharClass CharClassOf(char c)
@@ -528,12 +526,16 @@ namespace OpenRA.Support
public VariableExpression(string expression) public VariableExpression(string expression)
{ {
Expression = expression; Expression = expression;
}
Expression Build(ExpressionType resultType)
{
var tokens = new List<Token>(); var tokens = new List<Token>();
var currentOpeners = new Stack<Token>(); var currentOpeners = new Stack<Token>();
Token lastToken = null; Token lastToken = null;
for (var i = 0;;) for (var i = 0;;)
{ {
var token = Token.GetNext(expression, ref i, lastToken != null ? lastToken.Type : TokenType.Invalid); var token = Token.GetNext(Expression, ref i, lastToken != null ? lastToken.Type : TokenType.Invalid);
if (token == null) if (token == null)
{ {
// Sanity check parsed tree // Sanity check parsed tree
@@ -591,7 +593,20 @@ namespace OpenRA.Support
if (currentOpeners.Count > 0) if (currentOpeners.Count > 0)
throw new InvalidDataException("Unclosed opening parenthesis at index {0}".F(currentOpeners.Peek().Index)); throw new InvalidDataException("Unclosed opening parenthesis at index {0}".F(currentOpeners.Peek().Index));
asFunction = new Compiler().Compile(ToPostfix(tokens).ToArray()); return new Compiler().Build(ToPostfix(tokens).ToArray(), resultType);
}
protected Func<IReadOnlyDictionary<string, int>, T> Compile<T>()
{
ExpressionType resultType;
if (typeof(T) == typeof(int))
resultType = ExpressionType.Int;
else if (typeof(T) == typeof(bool))
resultType = ExpressionType.Bool;
else
throw new InvalidCastException("Variable expressions can only be int or bool.");
return Expressions.Expression.Lambda<Func<IReadOnlyDictionary<string, int>, T>>(Build(resultType), SymbolsParam).Compile();
} }
static int ParseSymbol(string symbol, IReadOnlyDictionary<string, int> symbols) static int ParseSymbol(string symbol, IReadOnlyDictionary<string, int> symbols)
@@ -712,7 +727,7 @@ namespace OpenRA.Support
{ {
readonly AstStack ast = new AstStack(); readonly AstStack ast = new AstStack();
public Func<IReadOnlyDictionary<string, int>, int> Compile(Token[] postfix) public Expression Build(Token[] postfix, ExpressionType resultType)
{ {
foreach (var t in postfix) foreach (var t in postfix)
{ {
@@ -877,10 +892,34 @@ namespace OpenRA.Support
} }
} }
return Expressions.Expression.Lambda<Func<IReadOnlyDictionary<string, int>, int>>( return ast.Pop(resultType);
ast.Pop(ExpressionType.Int), SymbolsParam).Compile();
} }
} }
}
public class BooleanExpression : VariableExpression
{
readonly Func<IReadOnlyDictionary<string, int>, bool> asFunction;
public BooleanExpression(string expression) : base(expression)
{
asFunction = Compile<bool>();
}
public bool Evaluate(IReadOnlyDictionary<string, int> symbols)
{
return asFunction(symbols);
}
}
public class IntegerExpression : VariableExpression
{
readonly Func<IReadOnlyDictionary<string, int>, int> asFunction;
public IntegerExpression(string expression) : base(expression)
{
asFunction = Compile<int>();
}
public int Evaluate(IReadOnlyDictionary<string, int> symbols) public int Evaluate(IReadOnlyDictionary<string, int> symbols)
{ {

View File

@@ -29,13 +29,13 @@ namespace OpenRA.Mods.Common.Lint
if (typeof(IEnumerable<string>).IsAssignableFrom(type)) if (typeof(IEnumerable<string>).IsAssignableFrom(type))
return fieldInfo.GetValue(ruleInfo) as IEnumerable<string>; return fieldInfo.GetValue(ruleInfo) as IEnumerable<string>;
if (type == typeof(VariableExpression)) if (type == typeof(BooleanExpression) || type == typeof(IntegerExpression))
{ {
var expr = (VariableExpression)fieldInfo.GetValue(ruleInfo); var expr = (VariableExpression)fieldInfo.GetValue(ruleInfo);
return expr != null ? expr.Variables : Enumerable.Empty<string>(); return expr != null ? expr.Variables : Enumerable.Empty<string>();
} }
throw new InvalidOperationException("Bad type for reference on {0}.{1}. Supported types: string, IEnumerable<string>, BooleanExpression" throw new InvalidOperationException("Bad type for reference on {0}.{1}. Supported types: string, IEnumerable<string>, BooleanExpression, IntegerExpression"
.F(ruleInfo.GetType().Name, fieldInfo.Name)); .F(ruleInfo.GetType().Name, fieldInfo.Name));
} }
@@ -48,13 +48,13 @@ namespace OpenRA.Mods.Common.Lint
if (typeof(IEnumerable).IsAssignableFrom(type)) if (typeof(IEnumerable).IsAssignableFrom(type))
return (IEnumerable<string>)propertyInfo.GetValue(ruleInfo); return (IEnumerable<string>)propertyInfo.GetValue(ruleInfo);
if (type == typeof(VariableExpression)) if (type == typeof(BooleanExpression) || type == typeof(IntegerExpression))
{ {
var expr = (VariableExpression)propertyInfo.GetValue(ruleInfo); var expr = (VariableExpression)propertyInfo.GetValue(ruleInfo);
return expr != null ? expr.Variables : Enumerable.Empty<string>(); return expr != null ? expr.Variables : Enumerable.Empty<string>();
} }
throw new InvalidOperationException("Bad type for reference on {0}.{1}. Supported types: string, IEnumerable<string>, BooleanExpression" throw new InvalidOperationException("Bad type for reference on {0}.{1}. Supported types: string, IEnumerable<string>, BooleanExpression, IntegerExpression"
.F(ruleInfo.GetType().Name, propertyInfo.Name)); .F(ruleInfo.GetType().Name, propertyInfo.Name));
} }
} }

View File

@@ -23,7 +23,7 @@ namespace OpenRA.Mods.Common.Traits
[ConsumedConditionReference] [ConsumedConditionReference]
[Desc("Boolean expression defining the condition to enable this trait.")] [Desc("Boolean expression defining the condition to enable this trait.")]
public readonly VariableExpression RequiresCondition = null; public readonly BooleanExpression RequiresCondition = null;
public abstract object Create(ActorInitializer init); public abstract object Create(ActorInitializer init);
@@ -34,7 +34,7 @@ namespace OpenRA.Mods.Common.Traits
public virtual void RulesetLoaded(Ruleset rules, ActorInfo ai) public virtual void RulesetLoaded(Ruleset rules, ActorInfo ai)
{ {
EnabledByDefault = RequiresCondition != null ? RequiresCondition.Evaluate(NoConditions) > 0 : true; EnabledByDefault = RequiresCondition == null || RequiresCondition.Evaluate(NoConditions);
} }
} }
@@ -83,7 +83,7 @@ namespace OpenRA.Mods.Common.Traits
return; return;
var wasDisabled = IsTraitDisabled; var wasDisabled = IsTraitDisabled;
IsTraitDisabled = Info.RequiresCondition.Evaluate(conditions) <= 0; IsTraitDisabled = !Info.RequiresCondition.Evaluate(conditions);
if (IsTraitDisabled != wasDisabled) if (IsTraitDisabled != wasDisabled)
{ {

View File

@@ -30,7 +30,7 @@ namespace OpenRA.Mods.Common.Traits
[Desc("Requirements for accepting a plug type.", [Desc("Requirements for accepting a plug type.",
"Key is the plug type that the requirements applies to.", "Key is the plug type that the requirements applies to.",
"Value is the condition expression defining the requirements to place the plug.")] "Value is the condition expression defining the requirements to place the plug.")]
public readonly Dictionary<string, VariableExpression> Requirements = new Dictionary<string, VariableExpression>(); public readonly Dictionary<string, BooleanExpression> Requirements = new Dictionary<string, BooleanExpression>();
[GrantedConditionReference] [GrantedConditionReference]
public IEnumerable<string> LinterConditions { get { return Conditions.Values; } } public IEnumerable<string> LinterConditions { get { return Conditions.Values; } }
@@ -119,7 +119,7 @@ namespace OpenRA.Mods.Common.Traits
void IConditionConsumer.ConditionsChanged(Actor self, IReadOnlyDictionary<string, int> conditions) void IConditionConsumer.ConditionsChanged(Actor self, IReadOnlyDictionary<string, int> conditions)
{ {
foreach (var req in Info.Requirements) foreach (var req in Info.Requirements)
plugTypesAvailability[req.Key] = req.Value.Evaluate(conditions) != 0; plugTypesAvailability[req.Key] = req.Value.Evaluate(conditions);
} }
} }

View File

@@ -29,28 +29,28 @@ namespace OpenRA.Test
void AssertFalse(string expression) void AssertFalse(string expression)
{ {
Assert.False(new VariableExpression(expression).Evaluate(testValues) > 0, expression); Assert.False(new BooleanExpression(expression).Evaluate(testValues), expression);
} }
void AssertTrue(string expression) void AssertTrue(string expression)
{ {
Assert.True(new VariableExpression(expression).Evaluate(testValues) > 0, expression); Assert.True(new BooleanExpression(expression).Evaluate(testValues), expression);
} }
void AssertValue(string expression, int value) void AssertValue(string expression, int value)
{ {
Assert.AreEqual(value, new VariableExpression(expression).Evaluate(testValues), expression); Assert.AreEqual(value, new IntegerExpression(expression).Evaluate(testValues), expression);
} }
void AssertParseFailure(string expression) void AssertParseFailure(string expression)
{ {
Assert.Throws(typeof(InvalidDataException), () => new VariableExpression(expression).Evaluate(testValues), expression); Assert.Throws(typeof(InvalidDataException), () => new IntegerExpression(expression).Evaluate(testValues), expression);
} }
void AssertParseFailure(string expression, string errorMessage) void AssertParseFailure(string expression, string errorMessage)
{ {
var actualErrorMessage = Assert.Throws(typeof(InvalidDataException), var actualErrorMessage = Assert.Throws(typeof(InvalidDataException),
() => new VariableExpression(expression).Evaluate(testValues), () => new IntegerExpression(expression).Evaluate(testValues),
expression).Message; expression).Message;
Assert.AreEqual(errorMessage, actualErrorMessage, expression + " ===> " + actualErrorMessage); Assert.AreEqual(errorMessage, actualErrorMessage, expression + " ===> " + actualErrorMessage);
} }