diff --git a/OpenRA.Game/FieldLoader.cs b/OpenRA.Game/FieldLoader.cs index 6130367e14..ac753db0cc 100644 --- a/OpenRA.Game/FieldLoader.cs +++ b/OpenRA.Game/FieldLoader.cs @@ -398,13 +398,29 @@ namespace OpenRA return InvalidValueAction(value, fieldType, fieldName); } - else if (fieldType == typeof(VariableExpression)) + else if (fieldType == typeof(BooleanExpression)) { if (value != null) { 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) { diff --git a/OpenRA.Game/Support/VariableExpression.cs b/OpenRA.Game/Support/VariableExpression.cs index d9d553cea6..ff56b090a6 100644 --- a/OpenRA.Game/Support/VariableExpression.cs +++ b/OpenRA.Game/Support/VariableExpression.cs @@ -18,14 +18,12 @@ using Expressions = System.Linq.Expressions; namespace OpenRA.Support { - public class VariableExpression + public abstract class VariableExpression { public readonly string Expression; readonly HashSet variables = new HashSet(); public IEnumerable Variables { get { return variables; } } - readonly Func, int> asFunction; - enum CharClass { Whitespace, Operator, Mixed, Id, Digit } static CharClass CharClassOf(char c) @@ -528,12 +526,16 @@ namespace OpenRA.Support public VariableExpression(string expression) { Expression = expression; + } + + Expression Build(ExpressionType resultType) + { var tokens = new List(); var currentOpeners = new Stack(); Token lastToken = null; 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) { // Sanity check parsed tree @@ -591,7 +593,20 @@ namespace OpenRA.Support if (currentOpeners.Count > 0) 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, T> Compile() + { + 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, T>>(Build(resultType), SymbolsParam).Compile(); } static int ParseSymbol(string symbol, IReadOnlyDictionary symbols) @@ -712,7 +727,7 @@ namespace OpenRA.Support { readonly AstStack ast = new AstStack(); - public Func, int> Compile(Token[] postfix) + public Expression Build(Token[] postfix, ExpressionType resultType) { foreach (var t in postfix) { @@ -877,10 +892,34 @@ namespace OpenRA.Support } } - return Expressions.Expression.Lambda, int>>( - ast.Pop(ExpressionType.Int), SymbolsParam).Compile(); + return ast.Pop(resultType); } } + } + + public class BooleanExpression : VariableExpression + { + readonly Func, bool> asFunction; + + public BooleanExpression(string expression) : base(expression) + { + asFunction = Compile(); + } + + public bool Evaluate(IReadOnlyDictionary symbols) + { + return asFunction(symbols); + } + } + + public class IntegerExpression : VariableExpression + { + readonly Func, int> asFunction; + + public IntegerExpression(string expression) : base(expression) + { + asFunction = Compile(); + } public int Evaluate(IReadOnlyDictionary symbols) { diff --git a/OpenRA.Mods.Common/Lint/LintExts.cs b/OpenRA.Mods.Common/Lint/LintExts.cs index c53e05a503..c6f770ee62 100644 --- a/OpenRA.Mods.Common/Lint/LintExts.cs +++ b/OpenRA.Mods.Common/Lint/LintExts.cs @@ -29,13 +29,13 @@ namespace OpenRA.Mods.Common.Lint if (typeof(IEnumerable).IsAssignableFrom(type)) return fieldInfo.GetValue(ruleInfo) as IEnumerable; - if (type == typeof(VariableExpression)) + if (type == typeof(BooleanExpression) || type == typeof(IntegerExpression)) { var expr = (VariableExpression)fieldInfo.GetValue(ruleInfo); return expr != null ? expr.Variables : Enumerable.Empty(); } - throw new InvalidOperationException("Bad type for reference on {0}.{1}. Supported types: string, IEnumerable, BooleanExpression" + throw new InvalidOperationException("Bad type for reference on {0}.{1}. Supported types: string, IEnumerable, BooleanExpression, IntegerExpression" .F(ruleInfo.GetType().Name, fieldInfo.Name)); } @@ -48,13 +48,13 @@ namespace OpenRA.Mods.Common.Lint if (typeof(IEnumerable).IsAssignableFrom(type)) return (IEnumerable)propertyInfo.GetValue(ruleInfo); - if (type == typeof(VariableExpression)) + if (type == typeof(BooleanExpression) || type == typeof(IntegerExpression)) { var expr = (VariableExpression)propertyInfo.GetValue(ruleInfo); return expr != null ? expr.Variables : Enumerable.Empty(); } - throw new InvalidOperationException("Bad type for reference on {0}.{1}. Supported types: string, IEnumerable, BooleanExpression" + throw new InvalidOperationException("Bad type for reference on {0}.{1}. Supported types: string, IEnumerable, BooleanExpression, IntegerExpression" .F(ruleInfo.GetType().Name, propertyInfo.Name)); } } diff --git a/OpenRA.Mods.Common/Traits/Conditions/ConditionalTrait.cs b/OpenRA.Mods.Common/Traits/Conditions/ConditionalTrait.cs index 25cef9a099..0e7e8986fa 100644 --- a/OpenRA.Mods.Common/Traits/Conditions/ConditionalTrait.cs +++ b/OpenRA.Mods.Common/Traits/Conditions/ConditionalTrait.cs @@ -23,7 +23,7 @@ namespace OpenRA.Mods.Common.Traits [ConsumedConditionReference] [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); @@ -34,7 +34,7 @@ namespace OpenRA.Mods.Common.Traits 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; var wasDisabled = IsTraitDisabled; - IsTraitDisabled = Info.RequiresCondition.Evaluate(conditions) <= 0; + IsTraitDisabled = !Info.RequiresCondition.Evaluate(conditions); if (IsTraitDisabled != wasDisabled) { diff --git a/OpenRA.Mods.Common/Traits/Pluggable.cs b/OpenRA.Mods.Common/Traits/Pluggable.cs index 751e86f56e..60a937b748 100644 --- a/OpenRA.Mods.Common/Traits/Pluggable.cs +++ b/OpenRA.Mods.Common/Traits/Pluggable.cs @@ -30,7 +30,7 @@ namespace OpenRA.Mods.Common.Traits [Desc("Requirements for accepting a plug type.", "Key is the plug type that the requirements applies to.", "Value is the condition expression defining the requirements to place the plug.")] - public readonly Dictionary Requirements = new Dictionary(); + public readonly Dictionary Requirements = new Dictionary(); [GrantedConditionReference] public IEnumerable LinterConditions { get { return Conditions.Values; } } @@ -119,7 +119,7 @@ namespace OpenRA.Mods.Common.Traits void IConditionConsumer.ConditionsChanged(Actor self, IReadOnlyDictionary conditions) { foreach (var req in Info.Requirements) - plugTypesAvailability[req.Key] = req.Value.Evaluate(conditions) != 0; + plugTypesAvailability[req.Key] = req.Value.Evaluate(conditions); } } diff --git a/OpenRA.Test/OpenRA.Game/VariableExpressionTest.cs b/OpenRA.Test/OpenRA.Game/VariableExpressionTest.cs index afbf05f0a7..9be3c411d4 100644 --- a/OpenRA.Test/OpenRA.Game/VariableExpressionTest.cs +++ b/OpenRA.Test/OpenRA.Game/VariableExpressionTest.cs @@ -29,28 +29,28 @@ namespace OpenRA.Test 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) { - Assert.True(new VariableExpression(expression).Evaluate(testValues) > 0, expression); + Assert.True(new BooleanExpression(expression).Evaluate(testValues), expression); } 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) { - 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) { var actualErrorMessage = Assert.Throws(typeof(InvalidDataException), - () => new VariableExpression(expression).Evaluate(testValues), + () => new IntegerExpression(expression).Evaluate(testValues), expression).Message; Assert.AreEqual(errorMessage, actualErrorMessage, expression + " ===> " + actualErrorMessage); }