Merge pull request #13108 from atlimit8/ConditionsToVariables

ConditionExpression to (Boolean|Integer)Expression refactor
This commit is contained in:
reaperrr
2017-04-17 14:52:21 +02:00
committed by GitHub
8 changed files with 85 additions and 30 deletions

View File

@@ -398,13 +398,29 @@ namespace OpenRA
return InvalidValueAction(value, fieldType, fieldName); return InvalidValueAction(value, fieldType, fieldName);
} }
else if (fieldType == typeof(ConditionExpression)) else if (fieldType == typeof(BooleanExpression))
{ {
if (value != null) if (value != null)
{ {
try try
{ {
return new ConditionExpression(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

@@ -259,7 +259,7 @@
<Compile Include="Primitives\float3.cs" /> <Compile Include="Primitives\float3.cs" />
<Compile Include="InstalledMods.cs" /> <Compile Include="InstalledMods.cs" />
<Compile Include="CryptoUtil.cs" /> <Compile Include="CryptoUtil.cs" />
<Compile Include="Support\ConditionExpression.cs" /> <Compile Include="Support\VariableExpression.cs" />
<Compile Include="ExternalMods.cs" /> <Compile Include="ExternalMods.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -18,14 +18,12 @@ using Expressions = System.Linq.Expressions;
namespace OpenRA.Support namespace OpenRA.Support
{ {
public class ConditionExpression 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)
@@ -525,15 +523,19 @@ namespace OpenRA.Support
} }
} }
public ConditionExpression(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(ConditionExpression)) if (type == typeof(BooleanExpression) || type == typeof(IntegerExpression))
{ {
var expr = (ConditionExpression)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(ConditionExpression)) if (type == typeof(BooleanExpression) || type == typeof(IntegerExpression))
{ {
var expr = (ConditionExpression)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 ConditionExpression 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, ConditionExpression> Requirements = new Dictionary<string, ConditionExpression>(); 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

@@ -19,7 +19,7 @@ using OpenRA.Support;
namespace OpenRA.Test namespace OpenRA.Test
{ {
[TestFixture] [TestFixture]
public class ConditionExpressionTest public class VariableExpressionTest
{ {
IReadOnlyDictionary<string, int> testValues = new ReadOnlyDictionary<string, int>(new Dictionary<string, int>() IReadOnlyDictionary<string, int> testValues = new ReadOnlyDictionary<string, int>(new Dictionary<string, int>()
{ {
@@ -29,28 +29,28 @@ namespace OpenRA.Test
void AssertFalse(string expression) void AssertFalse(string expression)
{ {
Assert.False(new ConditionExpression(expression).Evaluate(testValues) > 0, expression); Assert.False(new BooleanExpression(expression).Evaluate(testValues), expression);
} }
void AssertTrue(string expression) void AssertTrue(string expression)
{ {
Assert.True(new ConditionExpression(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 ConditionExpression(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 ConditionExpression(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 ConditionExpression(expression).Evaluate(testValues), () => new IntegerExpression(expression).Evaluate(testValues),
expression).Message; expression).Message;
Assert.AreEqual(errorMessage, actualErrorMessage, expression + " ===> " + actualErrorMessage); Assert.AreEqual(errorMessage, actualErrorMessage, expression + " ===> " + actualErrorMessage);
} }

View File

@@ -55,7 +55,7 @@
<Compile Include="OpenRA.Mods.Common\ShapeTest.cs" /> <Compile Include="OpenRA.Mods.Common\ShapeTest.cs" />
<Compile Include="OpenRA.Game\OrderTest.cs" /> <Compile Include="OpenRA.Game\OrderTest.cs" />
<Compile Include="OpenRA.Game\PlatformTest.cs" /> <Compile Include="OpenRA.Game\PlatformTest.cs" />
<Compile Include="OpenRA.Game\ConditionExpressionTest.cs" /> <Compile Include="OpenRA.Game\VariableExpressionTest.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\OpenRA.Game\OpenRA.Game.csproj"> <ProjectReference Include="..\OpenRA.Game\OpenRA.Game.csproj">