VariableExpression: require whitespace around bool/arithmetic binary operators.

This commit is contained in:
atlimit8
2017-04-18 16:15:38 -05:00
parent 2053aec5f9
commit 8a04156280
2 changed files with 157 additions and 19 deletions

View File

@@ -160,6 +160,7 @@ namespace OpenRA.Support
public readonly string Symbol;
public readonly Precedence Precedence;
public readonly Sides OperandSides;
public readonly Sides WhitespaceSides;
public readonly Associativity Associativity;
public readonly Grouping Opens;
public readonly Grouping Closes;
@@ -171,6 +172,21 @@ namespace OpenRA.Support
Symbol = symbol;
Precedence = precedence;
OperandSides = operandSides;
WhitespaceSides = Sides.None;
Associativity = associativity;
Opens = opens;
Closes = closes;
}
public TokenTypeInfo(string symbol, Precedence precedence, Sides operandSides,
Sides whitespaceSides,
Associativity associativity = Associativity.Left,
Grouping opens = Grouping.None, Grouping closes = Grouping.None)
{
Symbol = symbol;
Precedence = precedence;
OperandSides = operandSides;
WhitespaceSides = whitespaceSides;
Associativity = associativity;
Opens = opens;
Closes = closes;
@@ -181,6 +197,7 @@ namespace OpenRA.Support
{
Symbol = symbol;
Precedence = precedence;
WhitespaceSides = Sides.None;
OperandSides = opens == Grouping.None ?
(closes == Grouping.None ? Sides.None : Sides.Left)
:
@@ -228,43 +245,43 @@ namespace OpenRA.Support
yield return new TokenTypeInfo("-", Precedence.Unary, Sides.Right, Associativity.Right);
continue;
case TokenType.And:
yield return new TokenTypeInfo("&&", Precedence.And, Sides.Both);
yield return new TokenTypeInfo("&&", Precedence.And, Sides.Both, Sides.Both);
continue;
case TokenType.Or:
yield return new TokenTypeInfo("||", Precedence.Or, Sides.Both);
yield return new TokenTypeInfo("||", Precedence.Or, Sides.Both, Sides.Both);
continue;
case TokenType.Equals:
yield return new TokenTypeInfo("==", Precedence.Equality, Sides.Both);
yield return new TokenTypeInfo("==", Precedence.Equality, Sides.Both, Sides.Both);
continue;
case TokenType.NotEquals:
yield return new TokenTypeInfo("!=", Precedence.Equality, Sides.Both);
yield return new TokenTypeInfo("!=", Precedence.Equality, Sides.Both, Sides.Both);
continue;
case TokenType.LessThan:
yield return new TokenTypeInfo("<", Precedence.Relation, Sides.Both);
yield return new TokenTypeInfo("<", Precedence.Relation, Sides.Both, Sides.Both);
continue;
case TokenType.LessThanOrEqual:
yield return new TokenTypeInfo("<=", Precedence.Relation, Sides.Both);
yield return new TokenTypeInfo("<=", Precedence.Relation, Sides.Both, Sides.Both);
continue;
case TokenType.GreaterThan:
yield return new TokenTypeInfo(">", Precedence.Relation, Sides.Both);
yield return new TokenTypeInfo(">", Precedence.Relation, Sides.Both, Sides.Both);
continue;
case TokenType.GreaterThanOrEqual:
yield return new TokenTypeInfo(">=", Precedence.Relation, Sides.Both);
yield return new TokenTypeInfo(">=", Precedence.Relation, Sides.Both, Sides.Both);
continue;
case TokenType.Add:
yield return new TokenTypeInfo("+", Precedence.Addition, Sides.Both);
yield return new TokenTypeInfo("+", Precedence.Addition, Sides.Both, Sides.Both);
continue;
case TokenType.Subtract:
yield return new TokenTypeInfo("-", Precedence.Addition, Sides.Both);
yield return new TokenTypeInfo("-", Precedence.Addition, Sides.Both, Sides.Both);
continue;
case TokenType.Multiply:
yield return new TokenTypeInfo("*", Precedence.Multiplication, Sides.Both);
yield return new TokenTypeInfo("*", Precedence.Multiplication, Sides.Both, Sides.Both);
continue;
case TokenType.Divide:
yield return new TokenTypeInfo("/", Precedence.Multiplication, Sides.Both);
yield return new TokenTypeInfo("/", Precedence.Multiplication, Sides.Both, Sides.Both);
continue;
case TokenType.Modulo:
yield return new TokenTypeInfo("%", Precedence.Multiplication, Sides.Both);
yield return new TokenTypeInfo("%", Precedence.Multiplication, Sides.Both, Sides.Both);
continue;
}
@@ -285,6 +302,21 @@ namespace OpenRA.Support
return type == TokenType.Invalid || HasRightOperand(type);
}
static bool RequiresWhitespaceAfter(TokenType type)
{
return ((int)TokenTypeInfos[(int)type].WhitespaceSides & (int)Sides.Right) != 0;
}
static bool RequiresWhitespaceBefore(TokenType type)
{
return ((int)TokenTypeInfos[(int)type].WhitespaceSides & (int)Sides.Left) != 0;
}
static string GetTokenSymbol(TokenType type)
{
return TokenTypeInfos[(int)type].Symbol;
}
class Token
{
public readonly TokenType Type;
@@ -484,16 +516,30 @@ namespace OpenRA.Support
if (i == expression.Length)
return null;
// Ignore whitespace
while (CharClassOf(expression[i]) == CharClass.Whitespace)
// Check and eat whitespace
var whitespaceBefore = false;
if (CharClassOf(expression[i]) == CharClass.Whitespace)
{
if (++i == expression.Length)
return null;
whitespaceBefore = true;
while (CharClassOf(expression[i]) == CharClass.Whitespace)
{
if (++i == expression.Length)
return null;
}
}
else if (lastType == TokenType.Invalid)
whitespaceBefore = true;
else if (RequiresWhitespaceAfter(lastType))
throw new InvalidDataException("Missing whitespace at index {0}, after `{1}` operator."
.F(i, GetTokenSymbol(lastType)));
var start = i;
var type = GetNextType(expression, ref i, lastType);
if (!whitespaceBefore && RequiresWhitespaceBefore(type))
throw new InvalidDataException("Missing whitespace at index {0}, before `{1}` operator."
.F(i, GetTokenSymbol(type)));
switch (type)
{
case TokenType.Number:

View File

@@ -304,7 +304,6 @@ namespace OpenRA.Test
AssertValue("-t-1", -7);
AssertValue("t - 1", 4);
AssertValue("-1", -1);
AssertValue("6- 3", 3);
}
[TestCase(TestName = "Parenthesis and mixed operations")]
@@ -353,7 +352,8 @@ namespace OpenRA.Test
AssertParseFailure("-", "Missing value or sub-expression at end for `-` operator");
AssertParseFailure("-1-1", "Missing binary operation before `-1` at index 2");
AssertParseFailure("5-1", "Missing binary operation before `-1` at index 1");
AssertParseFailure("6 -3", "Missing binary operation before `-3` at index 2");
AssertParseFailure("6 -1", "Missing binary operation before `-1` at index 2");
AssertParseFailure("t -1", "Missing binary operation before `-1` at index 2");
}
[TestCase(TestName = "Test mixed charaters at end of identifier parser errors")]
@@ -369,6 +369,98 @@ namespace OpenRA.Test
AssertParseFailure("t$", "Invalid identifier end character at index 1 for `t$`");
}
[TestCase(TestName = "Test binary operator whitespace parser errors")]
public void TestParseSpacedBinaryOperatorErrors()
{
// `t-1` is valid variable name and `t- 1` starts with an invalid variable name.
// `6 -1`, `6-1`, `t -1` contain `-1` and are missing a binary operator.
AssertParseFailure("6- 1", "Missing whitespace at index 2, before `-` operator.");
AssertParseFailure("6+ 1", "Missing whitespace at index 2, before `+` operator.");
AssertParseFailure("t+ 1", "Missing whitespace at index 2, before `+` operator.");
AssertParseFailure("6 +1", "Missing whitespace at index 3, after `+` operator.");
AssertParseFailure("t +1", "Missing whitespace at index 3, after `+` operator.");
AssertParseFailure("6+1", "Missing whitespace at index 2, before `+` operator.");
AssertParseFailure("t+1", "Missing whitespace at index 2, before `+` operator.");
AssertParseFailure("6* 1", "Missing whitespace at index 2, before `*` operator.");
AssertParseFailure("t* 1", "Missing whitespace at index 2, before `*` operator.");
AssertParseFailure("6 *1", "Missing whitespace at index 3, after `*` operator.");
AssertParseFailure("t *1", "Missing whitespace at index 3, after `*` operator.");
AssertParseFailure("6*1", "Missing whitespace at index 2, before `*` operator.");
AssertParseFailure("t*1", "Missing whitespace at index 2, before `*` operator.");
AssertParseFailure("6/ 1", "Missing whitespace at index 2, before `/` operator.");
AssertParseFailure("t/ 1", "Missing whitespace at index 2, before `/` operator.");
AssertParseFailure("6 /1", "Missing whitespace at index 3, after `/` operator.");
AssertParseFailure("t /1", "Missing whitespace at index 3, after `/` operator.");
AssertParseFailure("6/1", "Missing whitespace at index 2, before `/` operator.");
AssertParseFailure("t/1", "Missing whitespace at index 2, before `/` operator.");
AssertParseFailure("6% 1", "Missing whitespace at index 2, before `%` operator.");
AssertParseFailure("t% 1", "Missing whitespace at index 2, before `%` operator.");
AssertParseFailure("6 %1", "Missing whitespace at index 3, after `%` operator.");
AssertParseFailure("t %1", "Missing whitespace at index 3, after `%` operator.");
AssertParseFailure("6%1", "Missing whitespace at index 2, before `%` operator.");
AssertParseFailure("t%1", "Missing whitespace at index 2, before `%` operator.");
AssertParseFailure("6< 1", "Missing whitespace at index 2, before `<` operator.");
AssertParseFailure("t< 1", "Missing whitespace at index 2, before `<` operator.");
AssertParseFailure("6 <1", "Missing whitespace at index 3, after `<` operator.");
AssertParseFailure("t <1", "Missing whitespace at index 3, after `<` operator.");
AssertParseFailure("6<1", "Missing whitespace at index 2, before `<` operator.");
AssertParseFailure("t<1", "Missing whitespace at index 2, before `<` operator.");
AssertParseFailure("6> 1", "Missing whitespace at index 2, before `>` operator.");
AssertParseFailure("t> 1", "Missing whitespace at index 2, before `>` operator.");
AssertParseFailure("6 >1", "Missing whitespace at index 3, after `>` operator.");
AssertParseFailure("t >1", "Missing whitespace at index 3, after `>` operator.");
AssertParseFailure("6>1", "Missing whitespace at index 2, before `>` operator.");
AssertParseFailure("t>1", "Missing whitespace at index 2, before `>` operator.");
AssertParseFailure("6&& 1", "Missing whitespace at index 3, before `&&` operator.");
AssertParseFailure("t&& 1", "Missing whitespace at index 3, before `&&` operator.");
AssertParseFailure("6 &&1", "Missing whitespace at index 4, after `&&` operator.");
AssertParseFailure("t &&1", "Missing whitespace at index 4, after `&&` operator.");
AssertParseFailure("6&&1", "Missing whitespace at index 3, before `&&` operator.");
AssertParseFailure("t&&1", "Missing whitespace at index 3, before `&&` operator.");
AssertParseFailure("6|| 1", "Missing whitespace at index 3, before `||` operator.");
AssertParseFailure("t|| 1", "Missing whitespace at index 3, before `||` operator.");
AssertParseFailure("6 ||1", "Missing whitespace at index 4, after `||` operator.");
AssertParseFailure("t ||1", "Missing whitespace at index 4, after `||` operator.");
AssertParseFailure("6||1", "Missing whitespace at index 3, before `||` operator.");
AssertParseFailure("t||1", "Missing whitespace at index 3, before `||` operator.");
AssertParseFailure("6== 1", "Missing whitespace at index 3, before `==` operator.");
AssertParseFailure("t== 1", "Missing whitespace at index 3, before `==` operator.");
AssertParseFailure("6 ==1", "Missing whitespace at index 4, after `==` operator.");
AssertParseFailure("t ==1", "Missing whitespace at index 4, after `==` operator.");
AssertParseFailure("6==1", "Missing whitespace at index 3, before `==` operator.");
AssertParseFailure("t==1", "Missing whitespace at index 3, before `==` operator.");
AssertParseFailure("6!= 1", "Missing whitespace at index 3, before `!=` operator.");
AssertParseFailure("t!= 1", "Missing whitespace at index 3, before `!=` operator.");
AssertParseFailure("6 !=1", "Missing whitespace at index 4, after `!=` operator.");
AssertParseFailure("t !=1", "Missing whitespace at index 4, after `!=` operator.");
AssertParseFailure("6!=1", "Missing whitespace at index 3, before `!=` operator.");
AssertParseFailure("t!=1", "Missing whitespace at index 3, before `!=` operator.");
AssertParseFailure("6<= 1", "Missing whitespace at index 3, before `<=` operator.");
AssertParseFailure("t<= 1", "Missing whitespace at index 3, before `<=` operator.");
AssertParseFailure("6 <=1", "Missing whitespace at index 4, after `<=` operator.");
AssertParseFailure("t <=1", "Missing whitespace at index 4, after `<=` operator.");
AssertParseFailure("6<=1", "Missing whitespace at index 3, before `<=` operator.");
AssertParseFailure("t<=1", "Missing whitespace at index 3, before `<=` operator.");
AssertParseFailure("6>= 1", "Missing whitespace at index 3, before `>=` operator.");
AssertParseFailure("t>= 1", "Missing whitespace at index 3, before `>=` operator.");
AssertParseFailure("6 >=1", "Missing whitespace at index 4, after `>=` operator.");
AssertParseFailure("t >=1", "Missing whitespace at index 4, after `>=` operator.");
AssertParseFailure("6>=1", "Missing whitespace at index 3, before `>=` operator.");
AssertParseFailure("t>=1", "Missing whitespace at index 3, before `>=` operator.");
}
[TestCase(TestName = "Undefined symbols are treated as `false` (0) values")]
public void TestUndefinedSymbols()
{