ConditionExpression: Add arithmetic operations
This commit is contained in:
@@ -119,6 +119,8 @@ namespace OpenRA.Support
|
||||
OpenParen,
|
||||
CloseParen,
|
||||
Not,
|
||||
Negate,
|
||||
OnesComplement,
|
||||
And,
|
||||
Or,
|
||||
Equals,
|
||||
@@ -127,6 +129,11 @@ namespace OpenRA.Support
|
||||
LessThanOrEqual,
|
||||
GreaterThan,
|
||||
GreaterThanOrEqual,
|
||||
Add,
|
||||
Subtract,
|
||||
Multiply,
|
||||
Divide,
|
||||
Modulo,
|
||||
|
||||
Invalid
|
||||
}
|
||||
@@ -134,6 +141,8 @@ namespace OpenRA.Support
|
||||
enum Precedence
|
||||
{
|
||||
Unary = 16,
|
||||
Multiplication = 12,
|
||||
Addition = 11,
|
||||
Relation = 9,
|
||||
Equality = 8,
|
||||
And = 4,
|
||||
@@ -204,6 +213,12 @@ namespace OpenRA.Support
|
||||
case TokenType.Not:
|
||||
yield return new TokenTypeInfo("!", Precedence.Unary, OperandSides.Right, Associativity.Right);
|
||||
continue;
|
||||
case TokenType.OnesComplement:
|
||||
yield return new TokenTypeInfo("~", Precedence.Unary, OperandSides.Right, Associativity.Right);
|
||||
continue;
|
||||
case TokenType.Negate:
|
||||
yield return new TokenTypeInfo("-", Precedence.Unary, OperandSides.Right, Associativity.Right);
|
||||
continue;
|
||||
case TokenType.And:
|
||||
yield return new TokenTypeInfo("&&", Precedence.And, OperandSides.Both);
|
||||
continue;
|
||||
@@ -228,6 +243,21 @@ namespace OpenRA.Support
|
||||
case TokenType.GreaterThanOrEqual:
|
||||
yield return new TokenTypeInfo(">=", Precedence.Relation, OperandSides.Both);
|
||||
continue;
|
||||
case TokenType.Add:
|
||||
yield return new TokenTypeInfo("+", Precedence.Addition, OperandSides.Both);
|
||||
continue;
|
||||
case TokenType.Subtract:
|
||||
yield return new TokenTypeInfo("-", Precedence.Addition, OperandSides.Both);
|
||||
continue;
|
||||
case TokenType.Multiply:
|
||||
yield return new TokenTypeInfo("*", Precedence.Multiplication, OperandSides.Both);
|
||||
continue;
|
||||
case TokenType.Divide:
|
||||
yield return new TokenTypeInfo("/", Precedence.Multiplication, OperandSides.Both);
|
||||
continue;
|
||||
case TokenType.Modulo:
|
||||
yield return new TokenTypeInfo("%", Precedence.Multiplication, OperandSides.Both);
|
||||
continue;
|
||||
}
|
||||
|
||||
throw new InvalidProgramException("CreateTokenTypeInfoEnumeration is missing a TokenTypeInfo entry for TokenType.{0}".F(
|
||||
@@ -237,6 +267,16 @@ namespace OpenRA.Support
|
||||
|
||||
static readonly TokenTypeInfo[] TokenTypeInfos = CreateTokenTypeInfoEnumeration().ToArray();
|
||||
|
||||
static bool HasRightOperand(TokenType type)
|
||||
{
|
||||
return ((int)TokenTypeInfos[(int)type].OperandSides & (int)OperandSides.Right) != 0;
|
||||
}
|
||||
|
||||
static bool IsLeftOperandOrNone(TokenType type)
|
||||
{
|
||||
return type == TokenType.Invalid || HasRightOperand(type);
|
||||
}
|
||||
|
||||
class Token
|
||||
{
|
||||
public readonly TokenType Type;
|
||||
@@ -259,7 +299,34 @@ namespace OpenRA.Support
|
||||
Index = index;
|
||||
}
|
||||
|
||||
public static TokenType GetNextType(string expression, ref int i)
|
||||
static bool ScanIsNumber(string expression, int start, ref int i)
|
||||
{
|
||||
var cc = CharClassOf(expression[i]);
|
||||
|
||||
// Scan forwards until we find an non-digit character
|
||||
if (cc == CharClass.Digit)
|
||||
{
|
||||
i++;
|
||||
for (; i < expression.Length; i++)
|
||||
{
|
||||
cc = CharClassOf(expression[i]);
|
||||
if (cc != CharClass.Digit)
|
||||
{
|
||||
if (cc != CharClass.Whitespace && cc != CharClass.Operator)
|
||||
throw new InvalidDataException("Number {0} and variable merged at index {1}".F(
|
||||
int.Parse(expression.Substring(start, i - start)), start));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static TokenType GetNextType(string expression, ref int i, TokenType lastType = TokenType.Invalid)
|
||||
{
|
||||
var start = i;
|
||||
|
||||
@@ -332,30 +399,41 @@ namespace OpenRA.Support
|
||||
case ')':
|
||||
i++;
|
||||
return TokenType.CloseParen;
|
||||
|
||||
case '~':
|
||||
i++;
|
||||
return TokenType.OnesComplement;
|
||||
case '+':
|
||||
i++;
|
||||
return TokenType.Add;
|
||||
|
||||
case '-':
|
||||
if (++i < expression.Length && ScanIsNumber(expression, start, ref i))
|
||||
return TokenType.Number;
|
||||
|
||||
i = start + 1;
|
||||
if (IsLeftOperandOrNone(lastType))
|
||||
return TokenType.Negate;
|
||||
return TokenType.Subtract;
|
||||
|
||||
case '*':
|
||||
i++;
|
||||
return TokenType.Multiply;
|
||||
|
||||
case '/':
|
||||
i++;
|
||||
return TokenType.Divide;
|
||||
|
||||
case '%':
|
||||
i++;
|
||||
return TokenType.Modulo;
|
||||
}
|
||||
|
||||
if (ScanIsNumber(expression, start, ref i))
|
||||
return TokenType.Number;
|
||||
|
||||
var cc = CharClassOf(expression[start]);
|
||||
|
||||
// Scan forwards until we find an non-digit character
|
||||
if (expression[start] == '-' || cc == CharClass.Digit)
|
||||
{
|
||||
i++;
|
||||
for (; i < expression.Length; i++)
|
||||
{
|
||||
cc = CharClassOf(expression[i]);
|
||||
if (cc != CharClass.Digit)
|
||||
{
|
||||
if (cc != CharClass.Whitespace && cc != CharClass.Operator)
|
||||
throw new InvalidDataException("Number {0} and variable merged at index {1}".F(
|
||||
int.Parse(expression.Substring(start, i - start)), start));
|
||||
|
||||
return TokenType.Number;
|
||||
}
|
||||
}
|
||||
|
||||
return TokenType.Number;
|
||||
}
|
||||
|
||||
if (cc != CharClass.Id)
|
||||
throw new InvalidDataException("Invalid character '{0}' at index {1}".F(expression[i], start));
|
||||
|
||||
@@ -370,7 +448,7 @@ namespace OpenRA.Support
|
||||
return TokenType.Variable;
|
||||
}
|
||||
|
||||
public static Token GetNext(string expression, ref int i)
|
||||
public static Token GetNext(string expression, ref int i, TokenType lastType = TokenType.Invalid)
|
||||
{
|
||||
if (i == expression.Length)
|
||||
return null;
|
||||
@@ -384,7 +462,7 @@ namespace OpenRA.Support
|
||||
|
||||
var start = i;
|
||||
|
||||
var type = GetNextType(expression, ref i);
|
||||
var type = GetNextType(expression, ref i, lastType);
|
||||
switch (type)
|
||||
{
|
||||
case TokenType.Number:
|
||||
@@ -431,7 +509,7 @@ namespace OpenRA.Support
|
||||
Token lastToken = null;
|
||||
for (var i = 0;;)
|
||||
{
|
||||
var token = Token.GetNext(expression, ref i);
|
||||
var token = Token.GetNext(expression, ref i, lastToken != null ? lastToken.Type : TokenType.Invalid);
|
||||
if (token == null)
|
||||
{
|
||||
// Sanity check parsed tree
|
||||
@@ -662,6 +740,18 @@ namespace OpenRA.Support
|
||||
continue;
|
||||
}
|
||||
|
||||
case TokenType.Negate:
|
||||
{
|
||||
ast.Push(Expressions.Expression.Negate(ast.Pop(ExpressionType.Int)));
|
||||
continue;
|
||||
}
|
||||
|
||||
case TokenType.OnesComplement:
|
||||
{
|
||||
ast.Push(Expressions.Expression.OnesComplement(ast.Pop(ExpressionType.Int)));
|
||||
continue;
|
||||
}
|
||||
|
||||
case TokenType.LessThan:
|
||||
{
|
||||
var y = ast.Pop(ExpressionType.Int);
|
||||
@@ -694,6 +784,50 @@ namespace OpenRA.Support
|
||||
continue;
|
||||
}
|
||||
|
||||
case TokenType.Add:
|
||||
{
|
||||
var y = ast.Pop(ExpressionType.Int);
|
||||
var x = ast.Pop(ExpressionType.Int);
|
||||
ast.Push(Expressions.Expression.Add(x, y));
|
||||
continue;
|
||||
}
|
||||
|
||||
case TokenType.Subtract:
|
||||
{
|
||||
var y = ast.Pop(ExpressionType.Int);
|
||||
var x = ast.Pop(ExpressionType.Int);
|
||||
ast.Push(Expressions.Expression.Subtract(x, y));
|
||||
continue;
|
||||
}
|
||||
|
||||
case TokenType.Multiply:
|
||||
{
|
||||
var y = ast.Pop(ExpressionType.Int);
|
||||
var x = ast.Pop(ExpressionType.Int);
|
||||
ast.Push(Expressions.Expression.Multiply(x, y));
|
||||
continue;
|
||||
}
|
||||
|
||||
case TokenType.Divide:
|
||||
{
|
||||
var y = ast.Pop(ExpressionType.Int);
|
||||
var x = ast.Pop(ExpressionType.Int);
|
||||
var isNotZero = Expressions.Expression.NotEqual(y, Zero);
|
||||
var divide = Expressions.Expression.Divide(x, y);
|
||||
ast.Push(IfThenElse(isNotZero, divide, Zero));
|
||||
continue;
|
||||
}
|
||||
|
||||
case TokenType.Modulo:
|
||||
{
|
||||
var y = ast.Pop(ExpressionType.Int);
|
||||
var x = ast.Pop(ExpressionType.Int);
|
||||
var isNotZero = Expressions.Expression.NotEqual(y, Zero);
|
||||
var modulo = Expressions.Expression.Modulo(x, y);
|
||||
ast.Push(IfThenElse(isNotZero, modulo, Zero));
|
||||
continue;
|
||||
}
|
||||
|
||||
case TokenType.Number:
|
||||
{
|
||||
ast.Push(Expressions.Expression.Constant(((NumberToken)t).Value));
|
||||
|
||||
@@ -223,6 +223,58 @@ namespace OpenRA.Test
|
||||
AssertFalse("((false))");
|
||||
}
|
||||
|
||||
[TestCase(TestName = "Arithmetic")]
|
||||
public void TestArithmetic()
|
||||
{
|
||||
AssertValue("~0", ~0);
|
||||
AssertValue("-0", 0);
|
||||
AssertValue("-a", 0);
|
||||
AssertValue("-true", -1);
|
||||
AssertValue("~-0", -1);
|
||||
AssertValue("2 + 3", 5);
|
||||
AssertValue("2 + 0", 2);
|
||||
AssertValue("2 + 3", 5);
|
||||
AssertValue("5 - 3", 2);
|
||||
AssertValue("5 - -3", 8);
|
||||
AssertValue("5 - 0", 5);
|
||||
AssertValue("2 * 3", 6);
|
||||
AssertValue("2 * 0", 0);
|
||||
AssertValue("2 * -3", -6);
|
||||
AssertValue("-2 * 3", -6);
|
||||
AssertValue("-2 * -3", 6);
|
||||
AssertValue("6 / 3", 2);
|
||||
AssertValue("7 / 3", 2);
|
||||
AssertValue("-6 / 3", -2);
|
||||
AssertValue("6 / -3", -2);
|
||||
AssertValue("-6 / -3", 2);
|
||||
AssertValue("8 / 3", 2);
|
||||
AssertValue("6 % 3", 0);
|
||||
AssertValue("7 % 3", 1);
|
||||
AssertValue("8 % 3", 2);
|
||||
AssertValue("7 % 0", 0);
|
||||
AssertValue("-7 % 3", -1);
|
||||
AssertValue("7 % -3", 1);
|
||||
AssertValue("-7 % -3", -1);
|
||||
AssertValue("8 / 0", 0);
|
||||
}
|
||||
|
||||
[TestCase(TestName = "Arithmetic Mixed")]
|
||||
public void TestArithmeticMixed()
|
||||
{
|
||||
AssertValue("~~0", 0);
|
||||
AssertValue("-~0", 1);
|
||||
AssertValue("~- 0", -1);
|
||||
AssertValue("2 * 3 + 4", 10);
|
||||
AssertValue("2 * 3 - 4", 2);
|
||||
AssertValue("2 + 3 * 4", 14);
|
||||
AssertValue("2 + 3 % 4", 5);
|
||||
AssertValue("2 + 3 / 4", 2);
|
||||
AssertValue("2 * 3 / 4", 1);
|
||||
AssertValue("8 / 2 == 4", 1);
|
||||
AssertValue("~2 + ~3", -7);
|
||||
AssertValue("~(~2 + ~3)", 6);
|
||||
}
|
||||
|
||||
[TestCase(TestName = "Parenthesis and mixed operations")]
|
||||
public void TestMixedParens()
|
||||
{
|
||||
@@ -259,6 +311,9 @@ namespace OpenRA.Test
|
||||
AssertParseFailure("(true && !)", "Missing value or sub-expression or there is an extra operator `!` at index 9 or `)` at index 10");
|
||||
AssertParseFailure("&& false", "Missing value or sub-expression at beginning for `&&` operator");
|
||||
AssertParseFailure("false ||", "Missing value or sub-expression at end for `||` operator");
|
||||
AssertParseFailure("1 <", "Missing value or sub-expression at end for `<` operator");
|
||||
AssertParseFailure("-1a", "Number -1 and variable merged at index 0");
|
||||
AssertParseFailure("-", "Missing value or sub-expression at end for `-` operator");
|
||||
}
|
||||
|
||||
[TestCase(TestName = "Undefined symbols are treated as `false` (0) values")]
|
||||
|
||||
Reference in New Issue
Block a user