context: #region Copyright & License Information
  /*
   * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
   * This file is part of OpenRA, which is free software. It is made
   * available to you under the terms of the GNU General Public License
   * as published by the Free Software Foundation, either version 3 of
   * the License, or (at your option) any later version. For more
   * information, see COPYING.
   */
  #endregion
  
  using System;
  using System.Collections.Generic;
  using System.IO;
  using System.Linq;
  using System.Linq.Expressions;
  using Expressions = System.Linq.Expressions;
  
  namespace OpenRA.Support
  {
  	public abstract class VariableExpression
  	{
  		public static readonly IReadOnlyDictionary<string, int> NoVariables = new ReadOnlyDictionary<string, int>(new Dictionary<string, int>());
  
  		public readonly string Expression;
  		readonly HashSet<string> variables = new HashSet<string>();
  		public IEnumerable<string> Variables { get { return variables; } }
  
  		enum CharClass { Whitespace, Operator, Mixed, Id, Digit }
  
  		static CharClass CharClassOf(char c)
  		{
  			switch (c)
  			{
  				case '~':
  				case '!':
  				case '%':
  				case '^':
  				case '&':
  				case '*':
  				case '(':
  				case ')':
  				case '+':
  				case '=':
  				case '[':
  				case ']':
  				case '{':
  				case '}':
  				case '|':
  				case ':':
  				case ';':
  				case '\'':
  				case '"':
  				case '<':
  				case '>':
  				case '?':
  				case ',':
  				case '/':
  					return CharClass.Operator;
  
  				case '.':
  				case '$':
  				case '-':
  				case '@':
  					return CharClass.Mixed;
  
  				case '0':
  				case '1':
  				case '2':
  				case '3':
  				case '4':
  				case '5':
  				case '6':
  				case '7':
  				case '8':
  				case '9':
  					return CharClass.Digit;
  
  					// Fast-track normal whitespace
  				case ' ':
  				case '\t':
  				case '\n':
  				case '\r':
  					return CharClass.Whitespace;
  
  					// Should other whitespace be tested?
  				default:
  					return char.IsWhiteSpace(c) ? CharClass.Whitespace : CharClass.Id;
  			}
  		}
  
  		enum Associativity { Left, Right }
  
  		[Flags]
  		enum Sides
  		{
  			// Value type
  			None = 0,
  
  			// Postfix unary operator and/or group closer
  			Left = 1,
  
  			// Prefix unary operator and/or group opener
  			Right = 2,
  
  			// Binary+ operator
  			Both = Left | Right
  		}
  
  		enum Grouping { None, Parens }
  
  		enum TokenType
  		{
  			// fixed values
  			False,
  			True,
  
  			// varying values
  			Number,
  			Variable,
  
  			// operators
  			OpenParen,
  			CloseParen,
  			Not,
  			Negate,
  			OnesComplement,
  			And,
  			Or,
  			Equals,
  			NotEquals,
  			LessThan,
  			LessThanOrEqual,
  			GreaterThan,
  			GreaterThanOrEqual,
  			Add,
  			Subtract,
  			Multiply,
  			Divide,
  			Modulo,
  
  			Invalid
  		}
  
  		enum Precedence
  		{
  			Unary = 16,
  			Multiplication = 12,
  			Addition = 11,
  			Relation = 9,
  			Equality = 8,
  			And = 4,
  			Or = 3,
  			Binary = 0,
  			Value = 0,
  			Parens = -1,
  			Invalid = ~0
  		}
  
  		struct TokenTypeInfo
  		{
  			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;
  
  			public TokenTypeInfo(string symbol, Precedence precedence, Sides operandSides = Sides.None,
  				Associativity associativity = Associativity.Left,
  				Grouping opens = Grouping.None, Grouping closes = Grouping.None)
  			{
  				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;
  			}
  
  			public TokenTypeInfo(string symbol, Precedence precedence, Grouping opens, Grouping closes = Grouping.None,
  				Associativity associativity = Associativity.Left)
  			{
  				Symbol = symbol;
  				Precedence = precedence;
  				WhitespaceSides = Sides.None;
  				OperandSides = opens == Grouping.None ?
  					(closes == Grouping.None ? Sides.None : Sides.Left) :
  					(closes == Grouping.None ? Sides.Right : Sides.Both);
  				Associativity = associativity;
  				Opens = opens;
  				Closes = closes;
  			}
  		}
  
  		static IEnumerable<TokenTypeInfo> CreateTokenTypeInfoEnumeration()
  		{
  			for (var i = 0; i <= (int)TokenType.Invalid; i++)
  			{
  				switch ((TokenType)i)
  				{
  					case TokenType.Invalid:
  						yield return new TokenTypeInfo("(<INVALID>)", Precedence.Invalid);
  						continue;
  					case TokenType.False:
  						yield return new TokenTypeInfo("false", Precedence.Value);
  						continue;
  					case TokenType.True:
  						yield return new TokenTypeInfo("true", Precedence.Value);
  						continue;
  					case TokenType.Number:
  						yield return new TokenTypeInfo("(<number>)", Precedence.Value);
  						continue;
  					case TokenType.Variable:
  						yield return new TokenTypeInfo("(<variable>)", Precedence.Value);
  						continue;
  					case TokenType.OpenParen:
  						yield return new TokenTypeInfo("(", Precedence.Parens, Grouping.Parens);
  						continue;
  					case TokenType.CloseParen:
  						yield return new TokenTypeInfo(")", Precedence.Parens, Grouping.None, Grouping.Parens);
  						continue;
  					case TokenType.Not:
  						yield return new TokenTypeInfo("!", Precedence.Unary, Sides.Right, Associativity.Right);
  						continue;
  					case TokenType.OnesComplement:
  						yield return new TokenTypeInfo("~", Precedence.Unary, Sides.Right, Associativity.Right);
  						continue;
  					case TokenType.Negate:
  						yield return new TokenTypeInfo("-", Precedence.Unary, Sides.Right, Associativity.Right);
  						continue;
  					case TokenType.And:
  						yield return new TokenTypeInfo("&&", Precedence.And, Sides.Both, Sides.Both);
  						continue;
  					case TokenType.Or:
  						yield return new TokenTypeInfo("||", Precedence.Or, Sides.Both, Sides.Both);
  						continue;
  					case TokenType.Equals:
  						yield return new TokenTypeInfo("==", Precedence.Equality, Sides.Both, Sides.Both);
  						continue;
  					case TokenType.NotEquals:
  						yield return new TokenTypeInfo("!=", Precedence.Equality, Sides.Both, Sides.Both);
  						continue;
  					case TokenType.LessThan:
  						yield return new TokenTypeInfo("<", Precedence.Relation, Sides.Both, Sides.Both);
  						continue;
  					case TokenType.LessThanOrEqual:
  						yield return new TokenTypeInfo("<=", Precedence.Relation, Sides.Both, Sides.Both);
  						continue;
  					case TokenType.GreaterThan:
  						yield return new TokenTypeInfo(">", Precedence.Relation, Sides.Both, Sides.Both);
  						continue;
  					case TokenType.GreaterThanOrEqual:
  						yield return new TokenTypeInfo(">=", Precedence.Relation, Sides.Both, Sides.Both);
  						continue;
  					case TokenType.Add:
  						yield return new TokenTypeInfo("+", Precedence.Addition, Sides.Both, Sides.Both);
  						continue;
  					case TokenType.Subtract:
  						yield return new TokenTypeInfo("-", Precedence.Addition, Sides.Both, Sides.Both);
  						continue;
  					case TokenType.Multiply:
  						yield return new TokenTypeInfo("*", Precedence.Multiplication, Sides.Both, Sides.Both);
  						continue;
  					case TokenType.Divide:
  						yield return new TokenTypeInfo("/", Precedence.Multiplication, Sides.Both, Sides.Both);
  						continue;
  					case TokenType.Modulo:
  						yield return new TokenTypeInfo("%", Precedence.Multiplication, Sides.Both, Sides.Both);
  						continue;
  				}
  
  				throw new InvalidProgramException("CreateTokenTypeInfoEnumeration is missing a TokenTypeInfo entry for TokenType.{0}".F(
  					Enum<TokenType>.GetValues()[i]));
  			}
  		}
  
  		static readonly TokenTypeInfo[] TokenTypeInfos = CreateTokenTypeInfoEnumeration().ToArray();
  
  		static bool HasRightOperand(TokenType type)
  		{
  			return ((int)TokenTypeInfos[(int)type].OperandSides & (int)Sides.Right) != 0;
  		}
  
  		static bool IsLeftOperandOrNone(TokenType type)
  		{
  			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;
  			public readonly int Index;
  
  			public virtual string Symbol { get { return TokenTypeInfos[(int)Type].Symbol; } }
  
  			public int Precedence { get { return (int)TokenTypeInfos[(int)Type].Precedence; } }
  			public Sides OperandSides { get { return TokenTypeInfos[(int)Type].OperandSides; } }
  			public Associativity Associativity { get { return TokenTypeInfos[(int)Type].Associativity; } }
  			public bool LeftOperand { get { return ((int)TokenTypeInfos[(int)Type].OperandSides & (int)Sides.Left) != 0; } }
  			public bool RightOperand { get { return ((int)TokenTypeInfos[(int)Type].OperandSides & (int)Sides.Right) != 0; } }
  
  			public Grouping Opens { get { return TokenTypeInfos[(int)Type].Opens; } }
  			public Grouping Closes { get { return TokenTypeInfos[(int)Type].Closes; } }
  
  			public Token(TokenType type, int index)
  			{
  				Type = type;
  				Index = index;
  			}
  
  			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 && cc != CharClass.Mixed)
  								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;
  			}
  
  			static TokenType VariableOrKeyword(string expression, int start, ref int i)
  			{
  
gt: 				if (CharClassOf(expression[i - 1]) == CharClass.Mixed)