ArithmaticStatement CheckForArithmaticStatement(WordTokens[] tokens)
        {
            if (tokens?.Length == 0)
            {
                return(null);
            }
            var typesForAdd         = new TokenType[] { TokenType.String, TokenType.StringVariable, TokenType.RealVariable, TokenType.IntegerVariable, TokenType.Number, TokenType.Real, TokenType.Integer };
            var typesForRest        = new TokenType[] { TokenType.RealVariable, TokenType.IntegerVariable, TokenType.Number, TokenType.Real, TokenType.Integer };
            var firstToken          = tokens.First().tokens.First();
            var arithmaticOperators = new TokenType[] { TokenType.AddArithmaticOperator, TokenType.SubArithmaticOperator, TokenType.MulArithmaticOperator, TokenType.DivArithmaticOperator };

            if (tokens.Length > 2)
            {
                var secondToken = tokens[1].tokens.First();
                var thirdToken  = tokens[2].tokens.First();
                if (arithmaticOperators.Any(op => secondToken.type == op))
                {
                    switch (secondToken.type)
                    {
                    case TokenType.AddArithmaticOperator:
                        if (typesForAdd.Any(t => t == firstToken.type) && typesForAdd.Any(t => t == thirdToken.type))
                        {
                            return(new ArithmaticStatement(tokens, tokens.First(), tokens[2], tokens[1]));
                        }
                        break;

                    case TokenType.SubArithmaticOperator:
                        if (typesForRest.Any(t => t == firstToken.type) && typesForRest.Any(t => t == thirdToken.type))
                        {
                            return(new ArithmaticStatement(tokens, tokens.First(), tokens[2], tokens[1]));
                        }
                        break;

                    case TokenType.MulArithmaticOperator:
                        if (typesForRest.Any(t => t == firstToken.type) && typesForRest.Any(t => t == thirdToken.type))
                        {
                            return(new ArithmaticStatement(tokens, tokens.First(), tokens[2], tokens[1]));
                        }
                        break;

                    case TokenType.DivArithmaticOperator:
                        if (typesForRest.Any(t => t == firstToken.type) && typesForRest.Any(t => t == thirdToken.type))
                        {
                            return(new ArithmaticStatement(tokens, tokens.First(), tokens[2], tokens[1]));
                        }
                        break;
                    }
                }
                else
                {
                    OnError?.Invoke("Second argument must be an arithmatic operator");
                    return(null);
                }
            }
            OnError?.Invoke("Not enough arguments for Arithmatic Statement");
            return(null);
        }
        ReadStatement CheckForReadStatement(WordTokens[] tokens)
        {
            var allowedTokenTypes = new TokenType[] { TokenType.StringVariable,
                                                      TokenType.RealVariable,
                                                      TokenType.IntegerVariable };

            if (tokens.Length == 2)
            {
                var secondToken = tokens[1];
                if (allowedTokenTypes.Any(t => t == secondToken.tokens.First().type))
                {
                    return(new ReadStatement(tokens, secondToken));
                }
                else
                {
                    var errorString = allowedTokenTypes.Select(t => t.ToString()).Aggregate((current, next) => { return(current + "\n" + next); });
                    errorString = "Error: Second token must be any of the following:\n" + errorString;
                    OnError?.Invoke(errorString);
                    return(null);
                }
            }
            else if (tokens.Length > 2)
            {
                OnError?.Invoke("Too many arguments for print statement.");
                return(null);
            }
            OnError?.Invoke("Unidentified Error.");
            return(null);
        }
        RelationalStatement CheckForRelational(WordTokens[] tokens)
        {
            var conditionalOperators = new TokenType[] {
                TokenType.EqualRelationalOperator, TokenType.NotEqualRelationalOperator,
                TokenType.GreaterThanRelationalOperator, TokenType.LessThanRelationalOperator,
                TokenType.EqualGreaterRelationalOperator, TokenType.EqualLessRelationalOperator,
            };

            var stringTypes = new TokenType[] { TokenType.String, TokenType.StringVariable };
            var realTypes   = new TokenType[] { TokenType.Real, TokenType.RealVariable };
            var intTypes    = new TokenType[] { TokenType.Integer, TokenType.IntegerVariable, TokenType.UnsignedInteger };

            var  firstToken   = tokens.First();
            bool isRelational = false;

            if (tokens.Length >= 3)
            {
                var op = tokens.Select((value, index) => new { value, index }).FirstOrDefault(t => t.value.tokens.Any(to => conditionalOperators.Any(o => o == to.type)));
                if (op != null)
                {
                    var index  = op.index;
                    var before = tokens[index - 1];
                    if (index + 1 >= tokens.Length)
                    {
                        return(null);
                    }
                    var after     = tokens[index + 1];
                    var isStrings = (stringTypes.Any(t => before.tokens.Any(tk => tk.type == t)) && stringTypes.Any(t => after.tokens.Any(tk => tk.type == t)));
                    var isReals   = (realTypes.Any(t => before.tokens.Any(tk => tk.type == t)) && realTypes.Any(t => after.tokens.Any(tk => tk.type == t)));
                    var isInts    = (intTypes.Any(t => before.tokens.Any(tk => tk.type == t)) && intTypes.Any(t => after.tokens.Any(tk => tk.type == t)));
                    isRelational = isStrings || isReals || isInts;
                    if (isRelational)
                    {
                        return(new RelationalStatement(tokens, op.value, before, after));
                    }
                }
            }
            return(null);
        }
        LogicalStatement CheckForConditional(WordTokens[] tokens)
        {
            var firstToken     = tokens.First();
            var validOperators = new TokenType[] { TokenType.AndLogicalOperator, TokenType.OrLogicalOperator };

            if (tokens.First().tokens.Any(t => t.type == TokenType.NotLogicalOperator))
            {
                var statement     = tokens.Skip(1).ToArray();
                var isConditional = CheckForConditional(statement);
                var isRelational  = CheckForRelational(statement);
                if (isConditional != null)
                {
                    return(new LogicalStatement(tokens, null, isConditional, tokens.First()));
                }
                if (isRelational != null)
                {
                    return(new LogicalStatement(tokens, null, isRelational, tokens.First()));
                }
            }
            else
            {
                var op = tokens.Select((value, index) => new { value, index }).FirstOrDefault(t => t.value.tokens.Any(to => validOperators.Any(vOp => vOp == to.type)));
                if (op != null)
                {
                    var index           = op.index;
                    var firstStatement  = tokens.Take(index).ToArray();
                    var secondStatement = tokens.Skip(index + 1).ToArray();

                    var firstConditional = CheckForConditional(firstStatement);
                    var firstRelational  = CheckForRelational(firstStatement);

                    var secondConditional = CheckForConditional(secondStatement);
                    var secondRelational  = CheckForRelational(secondStatement);

                    ConditionStatement first  = ((ConditionStatement)firstConditional) == null ? (ConditionStatement)firstRelational : (ConditionStatement)firstConditional;
                    ConditionStatement second = ((ConditionStatement)secondConditional) == null ? (ConditionStatement)secondRelational : (ConditionStatement)secondConditional;
                    if (first != null && second != null)
                    {
                        return(new LogicalStatement(tokens, first, second, op.value));
                    }
                }
            }
            return(null);
        }
        AssignmentStatement CheckForAssignment(WordTokens[] tokens)
        {
            var intTypes    = new TokenType[] { TokenType.Integer, TokenType.IntegerVariable, TokenType.UnsignedInteger };
            var realTypes   = new TokenType[] { TokenType.Real, TokenType.RealVariable };
            var stringTypes = new TokenType[] { TokenType.String, TokenType.StringVariable };

            if (tokens.Length > 2)
            {
                var firstTokenType = tokens.First().tokens.First().type;
                var firstToken     = tokens.First();

                var secondTokenType = tokens[1].tokens.First().type;
                var thirdTokenType  = tokens[2].tokens;

                if (secondTokenType == TokenType.AssignmentOperator)
                {
                    var isArithmatic = CheckForArithmaticStatement(tokens.Skip(2).ToArray());
                    switch (firstTokenType)
                    {
                    case TokenType.IntegerVariable:

                        if (isArithmatic == null && thirdTokenType.Any(t => intTypes.Any(type => t.type == type)))
                        {
                            return(new AssignmentStatement(tokens, firstToken, tokens[2]));
                        }
                        else if (isArithmatic != null && thirdTokenType.Any(t => intTypes.Any(type => t.type == type)))
                        {
                            return(new AssignmentStatement(tokens, firstToken, tokens[2], isArithmatic));
                        }
                        break;

                    case TokenType.RealVariable:
                        if (isArithmatic == null && thirdTokenType.Any(t => realTypes.Any(type => t.type == type)))
                        {
                            return(new AssignmentStatement(tokens, firstToken, tokens[2]));
                        }
                        else if (isArithmatic != null && thirdTokenType.Any(t => realTypes.Any(type => t.type == type)))
                        {
                            return(new AssignmentStatement(tokens, firstToken, tokens[2], isArithmatic));
                        }
                        break;

                    case TokenType.StringVariable:
                        if (isArithmatic == null && thirdTokenType.Any(t => stringTypes.Any(type => t.type == type)))
                        {
                            return(new AssignmentStatement(tokens, firstToken, tokens[2]));
                        }
                        else if (isArithmatic != null && thirdTokenType.Any(t => intTypes.Any(type => t.type == type)))
                        {
                            return(new AssignmentStatement(tokens, firstToken, tokens[2], isArithmatic));
                        }

                        break;

                    default:
                        OnError?.Invoke("First token type is not a variable.");
                        return(null);
                    }
                }
                else
                {
                    OnError?.Invoke("Second argument has to be an assignment operator.");
                    return(null);
                }
            }
            OnError?.Invoke("Not enough arguments for Assignment Statement");
            return(null);
        }