ConditionExpression: Run syntax checks while lexing

This commit is contained in:
atlimit8
2017-02-14 06:42:05 -06:00
parent 8e6436d71c
commit d752e10799
2 changed files with 53 additions and 51 deletions

View File

@@ -325,7 +325,6 @@ namespace OpenRA.Support
return TokenType.Variable; return TokenType.Variable;
} }
// Take the rest of the string
return TokenType.Variable; return TokenType.Variable;
} }
@@ -385,67 +384,68 @@ namespace OpenRA.Support
public ConditionExpression(string expression) public ConditionExpression(string expression)
{ {
Expression = expression; Expression = expression;
var openParens = 0;
var closeParens = 0;
var tokens = new List<Token>(); var tokens = new List<Token>();
var currentOpeners = new Stack<Token>();
Token lastToken = null;
for (var i = 0;;) for (var i = 0;;)
{ {
var token = Token.GetNext(expression, ref i); var token = Token.GetNext(expression, ref i);
if (token == null) if (token == null)
break;
switch (token.Type)
{ {
case TokenType.OpenParen:
openParens++;
break;
case TokenType.CloseParen:
if (++closeParens > openParens)
throw new InvalidDataException("Unmatched closing parenthesis at index {0}".F(i - 1));
break;
case TokenType.Variable:
variables.Add(token.Symbol);
break;
}
tokens.Add(token);
}
// Sanity check parsed tree // Sanity check parsed tree
if (!tokens.Any()) if (lastToken == null)
throw new InvalidDataException("Empty expression"); throw new InvalidDataException("Empty expression");
if (closeParens != openParens) // Expressions can't end with a binary or unary prefix operation
throw new InvalidDataException("Mismatched opening and closing parentheses"); if (lastToken.RightOperand)
throw new InvalidDataException("Missing value or sub-expression at end for `{0}` operator".F(lastToken.Symbol));
break;
}
if (token.Closes != Grouping.None)
{
if (currentOpeners.Count == 0)
throw new InvalidDataException("Unmatched closing parenthesis at index {0}".F(token.Index));
currentOpeners.Pop();
}
if (token.Opens != Grouping.None)
currentOpeners.Push(token);
if (lastToken == null)
{
// Expressions can't start with a binary or unary postfix operation or closer // Expressions can't start with a binary or unary postfix operation or closer
if (tokens[0].LeftOperand) if (token.LeftOperand)
throw new InvalidDataException("Missing value or sub-expression at beginning for `{0}` operator".F(tokens[0].Symbol)); throw new InvalidDataException("Missing value or sub-expression at beginning for `{0}` operator".F(token.Symbol));
}
for (var i = 0; i < tokens.Count - 1; i++) else
{ {
// Disallow empty parentheses // Disallow empty parentheses
if (tokens[i].Opens != Grouping.None && tokens[i + 1].Closes != Grouping.None) if (lastToken.Opens != Grouping.None && token.Closes != Grouping.None)
throw new InvalidDataException("Empty parenthesis at index {0}".F(tokens[i].Index)); throw new InvalidDataException("Empty parenthesis at index {0}".F(lastToken.Index));
// Exactly one of two consective tokens must take the other's sub-expression evaluation as an operand // Exactly one of two consective tokens must take the other's sub-expression evaluation as an operand
if (tokens[i].RightOperand == tokens[i + 1].LeftOperand) if (lastToken.RightOperand == token.LeftOperand)
{ {
if (tokens[i].RightOperand) if (lastToken.RightOperand)
throw new InvalidDataException( throw new InvalidDataException(
"Missing value or sub-expression or there is an extra operator `{0}` at index {1} or `{2}` at index {3}".F( "Missing value or sub-expression or there is an extra operator `{0}` at index {1} or `{2}` at index {3}".F(
tokens[i].Symbol, tokens[i].Index, tokens[i + 1].Symbol, tokens[i + 1].Index)); lastToken.Symbol, lastToken.Index, token.Symbol, token.Index));
throw new InvalidDataException("Missing binary operation before `{0}` at index {1}".F( throw new InvalidDataException("Missing binary operation before `{0}` at index {1}".F(token.Symbol, token.Index));
tokens[i + 1].Symbol, tokens[i + 1].Index));
} }
} }
// Expressions can't end with a binary or unary prefix operation if (token.Type == TokenType.Variable)
if (tokens[tokens.Count - 1].RightOperand) variables.Add(token.Symbol);
throw new InvalidDataException("Missing value or sub-expression at end for `{0}` operator".F(tokens[tokens.Count - 1].Symbol));
tokens.Add(token);
lastToken = token;
}
if (currentOpeners.Count > 0)
throw new InvalidDataException("Unclosed opening parenthesis at index {0}".F(currentOpeners.Peek().Index));
// Convert to postfix (discarding parentheses) ready for evaluation // Convert to postfix (discarding parentheses) ready for evaluation
postfix = ToPostfix(tokens).ToArray(); postfix = ToPostfix(tokens).ToArray();

View File

@@ -178,10 +178,12 @@ namespace OpenRA.Test
{ {
AssertParseFailure("()", "Empty parenthesis at index 0"); AssertParseFailure("()", "Empty parenthesis at index 0");
AssertParseFailure("! && true", "Missing value or sub-expression or there is an extra operator `!` at index 0 or `&&` at index 2"); AssertParseFailure("! && true", "Missing value or sub-expression or there is an extra operator `!` at index 0 or `&&` at index 2");
AssertParseFailure("(true", "Mismatched opening and closing parentheses"); AssertParseFailure("(true", "Unclosed opening parenthesis at index 0");
AssertParseFailure(")true", "Unmatched closing parenthesis at index 0"); AssertParseFailure(")true", "Unmatched closing parenthesis at index 0");
AssertParseFailure("false)", "Unmatched closing parenthesis at index 5"); AssertParseFailure("false)", "Unmatched closing parenthesis at index 5");
AssertParseFailure("false(", "Mismatched opening and closing parentheses"); AssertParseFailure("false(", "Missing binary operation before `(` at index 5");
AssertParseFailure("(", "Missing value or sub-expression at end for `(` operator");
AssertParseFailure(")", "Unmatched closing parenthesis at index 0");
AssertParseFailure("false!", "Missing binary operation before `!` at index 5"); AssertParseFailure("false!", "Missing binary operation before `!` at index 5");
AssertParseFailure("true false", "Missing binary operation before `false` at index 5"); AssertParseFailure("true false", "Missing binary operation before `false` at index 5");
AssertParseFailure("true & false", "Unexpected character '&' at index 5 - should it be `&&`?"); AssertParseFailure("true & false", "Unexpected character '&' at index 5 - should it be `&&`?");