# Path: purplequery/bq_abstract_syntax_tree.py
# _SELECTOR_TABLE = '__selector__'
# EMPTY_NODE = _EmptyNode()
# EMPTY_CONTEXT = EvaluationContext(TableContext())
# class AbstractSyntaxTreeNode(object):
# class MarkerSyntaxTreeNode(AbstractSyntaxTreeNode):
# class EvaluatableNode(AbstractSyntaxTreeNode):
# class EvaluatableLeafNode(EvaluatableNode):
# class EvaluatableNodeWithChildren(EvaluatableNode):
# class EvaluatableNodeThatAggregatesOrGroups(EvaluatableNodeWithChildren):
# class Result(object):
# class DataframeNode(AbstractSyntaxTreeNode):
# class GroupedBy(EvaluatableNodeThatAggregatesOrGroups):
# class Field(EvaluatableLeafNode):
# class _EmptyNode(AbstractSyntaxTreeNode):
# class EvaluationContext:
#     def __repr__(self):
#     def strexpr(self):
#     def literal(cls):
#     def literal(cls):
#     def evaluate(self, context):
#     def pre_group_by_partially_evaluate(self, context):
#     def name(self):
#     def mark_grouped_by(self, group_by_paths, context):
#     def is_constant(self):
#     def is_aggregated(self):
#     def evaluate(self, context):
#     def pre_group_by_partially_evaluate(self, context):
#     def _evaluate_leaf_node(self, context):
#     def __init__(self, children):
#     def pre_group_by_partially_evaluate(self, context):
#     def _ensure_fully_evaluated(self, evaluated_children):
#     def evaluate(self, context):
#     def _evaluate_node(self, evaluated_children):
#     def copy(self, new_children):
#     def mark_grouped_by(self, group_by_paths, context):
#     def is_constant(self):
#     def is_aggregated(self):
#     def pre_group_by_partially_evaluate(self, context):
#     def mark_grouped_by(self, group_by_paths, context):
#     def is_constant(self):
#     def is_aggregated(self):
#     def __init__(self, statement_type, path=None, table=None):
#     def get_dataframe(self, table_context, outer_context=None):
#     def execute(self, table_context):
#     def __init__(self, expression):
#     def copy(self, new_children):
#     def _evaluate_node(self, evaluated_children):
#     def __init__(self, path):
#     def strexpr(self):
#     def mark_grouped_by(self, group_by_paths, context):
#     def name(self):
#     def _evaluate_leaf_node(self, context):
#     def __eq__(self, other):
#     def is_constant(self):
#     def strexpr(self):
#     def __init__(self, table_context):
#     def add_subcontext(self, subcontext):
#     def maybe_add_column(self, column):
#     def _partially_evaluate(self, selector, group_by_paths):
#     def do_group_by(self, selectors, group_by):
#     def add_table_from_node(self, from_item, alias):
#     def add_table_from_dataframe(self, table,  # type: TypedDataFrame
#                                  table_id,  # type: Optional[str]
#                                  alias  # type: Union[_EmptyNode, str]
#                                  ):
#     def clone_context_new_table(cls, table, old_context):
#     def get_all_canonical_paths(self, path):
#     def get_canonical_path(self, path):
#     def lookup(self, path):
# 
# Path: purplequery/terminals.py
# def grammar_literal(*words):
#     # type: (*str) -> Callable[[List[str]], Tuple[Optional[str], List[str]]]
#     """Checks if the first token(s) is a grammar literal. This includes all the reserved words
#     above, as well as binary operators, parentheses, brackets, etc.
# 
#     Args:
#         words: Grammatic literal(s) to match.
#     Returns:
#         Function that checks whether given tokens match given grammatic literals.
#     """
# 
#     def match_strings_to_tokens(tokens):
#         # type: (List[str]) -> Tuple[Optional[str], List[str]]
#         """Checks if the next token(s) match the previously given grammatic literal(s).
# 
#         Args:
#             tokens: List of tokens, to decide whether the first token matches a grammatic literal.
#         Returns:
#             Tuple of grammatic literal(s) (if there was a match, joined by an underscore if there
#             is more than one), and the rest of the unidentified tokens.
#         """
# 
#         # Convert all requested words to upper case
#         words_upper = tuple(word.upper() for word in words)
# 
#         # If there are enough tokens for the given reserved word(s) AND
#         # the first n tokens match the given reserved word(s)
#         if (len(tokens) >= len(words) and
#                 words_upper == tuple(token.upper() for token in tokens[:len(words)])):
# 
#             # Put underscores between the found reserved words, and return the
#             # unused tokens
#             return '_'.join(words_upper), tokens[len(words):]
#         return None, tokens
# 
#     return match_strings_to_tokens


from typing import Any, Callable, List, Optional, Tuple, Union  # noqa: F401
from .bq_abstract_syntax_tree import (EMPTY_NODE, AbstractSyntaxTreeNode,  # noqa: F401
                                      AppliedRuleNode, AppliedRuleOutputType, RuleType)
from .terminals import grammar_literal
    Frequently used with None (see below) to indicate optional elements.
    '''
    for subrule in rule:
        node, new_tokens = apply_rule(subrule, tokens)
        if node:
            return node, new_tokens
    return None, tokens


def _apply_rule_none(rule, tokens):
    # type: (None, List[str]) -> AppliedRuleOutputType
    '''"None" means this is an epsilon rule - it matches no input tokens and
    always succeeds.  This is mainly used as an element in a list to indicate
    that the list rule is optional.

    Example:
    apply_rule(['ALL', 'DISTINCT', None]), ['ALL'])
        Match: 'ALL'
        Rest of tokens: []

    apply_rule(['ALL', 'DISTINCT', None]), ['DISTINCT'])
        Match: 'DISTINCT'
        Rest of tokens: []

    apply_rule(['ALL', 'DISTINCT', None]), ['*'])
        Match: EMPTY_NODE
        Rest of tokens: ['*']

    The "None" makes the entire list subrule optional.
    '''

# Path: purplequery/bq_abstract_syntax_tree.py
# _SELECTOR_TABLE = '__selector__'
# EMPTY_NODE = _EmptyNode()
# EMPTY_CONTEXT = EvaluationContext(TableContext())
# class AbstractSyntaxTreeNode(object):
# class MarkerSyntaxTreeNode(AbstractSyntaxTreeNode):
# class EvaluatableNode(AbstractSyntaxTreeNode):
# class EvaluatableLeafNode(EvaluatableNode):
# class EvaluatableNodeWithChildren(EvaluatableNode):
# class EvaluatableNodeThatAggregatesOrGroups(EvaluatableNodeWithChildren):
# class Result(object):
# class DataframeNode(AbstractSyntaxTreeNode):
# class GroupedBy(EvaluatableNodeThatAggregatesOrGroups):
# class Field(EvaluatableLeafNode):
# class _EmptyNode(AbstractSyntaxTreeNode):
# class EvaluationContext:
#     def __repr__(self):
#     def strexpr(self):
#     def literal(cls):
#     def literal(cls):
#     def evaluate(self, context):
#     def pre_group_by_partially_evaluate(self, context):
#     def name(self):
#     def mark_grouped_by(self, group_by_paths, context):
#     def is_constant(self):
#     def is_aggregated(self):
#     def evaluate(self, context):
#     def pre_group_by_partially_evaluate(self, context):
#     def _evaluate_leaf_node(self, context):
#     def __init__(self, children):
#     def pre_group_by_partially_evaluate(self, context):
#     def _ensure_fully_evaluated(self, evaluated_children):
#     def evaluate(self, context):
#     def _evaluate_node(self, evaluated_children):
#     def copy(self, new_children):
#     def mark_grouped_by(self, group_by_paths, context):
#     def is_constant(self):
#     def is_aggregated(self):
#     def pre_group_by_partially_evaluate(self, context):
#     def mark_grouped_by(self, group_by_paths, context):
#     def is_constant(self):
#     def is_aggregated(self):
#     def __init__(self, statement_type, path=None, table=None):
#     def get_dataframe(self, table_context, outer_context=None):
#     def execute(self, table_context):
#     def __init__(self, expression):
#     def copy(self, new_children):
#     def _evaluate_node(self, evaluated_children):
#     def __init__(self, path):
#     def strexpr(self):
#     def mark_grouped_by(self, group_by_paths, context):
#     def name(self):
#     def _evaluate_leaf_node(self, context):
#     def __eq__(self, other):
#     def is_constant(self):
#     def strexpr(self):
#     def __init__(self, table_context):
#     def add_subcontext(self, subcontext):
#     def maybe_add_column(self, column):
#     def _partially_evaluate(self, selector, group_by_paths):
#     def do_group_by(self, selectors, group_by):
#     def add_table_from_node(self, from_item, alias):
#     def add_table_from_dataframe(self, table,  # type: TypedDataFrame
#                                  table_id,  # type: Optional[str]
#                                  alias  # type: Union[_EmptyNode, str]
#                                  ):
#     def clone_context_new_table(cls, table, old_context):
#     def get_all_canonical_paths(self, path):
#     def get_canonical_path(self, path):
#     def lookup(self, path):
# 
# Path: purplequery/terminals.py
# def grammar_literal(*words):
#     # type: (*str) -> Callable[[List[str]], Tuple[Optional[str], List[str]]]
#     """Checks if the first token(s) is a grammar literal. This includes all the reserved words
#     above, as well as binary operators, parentheses, brackets, etc.
# 
#     Args:
#         words: Grammatic literal(s) to match.
#     Returns:
#         Function that checks whether given tokens match given grammatic literals.
#     """
# 
#     def match_strings_to_tokens(tokens):
#         # type: (List[str]) -> Tuple[Optional[str], List[str]]
#         """Checks if the next token(s) match the previously given grammatic literal(s).
# 
#         Args:
#             tokens: List of tokens, to decide whether the first token matches a grammatic literal.
#         Returns:
#             Tuple of grammatic literal(s) (if there was a match, joined by an underscore if there
#             is more than one), and the rest of the unidentified tokens.
#         """
# 
#         # Convert all requested words to upper case
#         words_upper = tuple(word.upper() for word in words)
# 
#         # If there are enough tokens for the given reserved word(s) AND
#         # the first n tokens match the given reserved word(s)
#         if (len(tokens) >= len(words) and
#                 words_upper == tuple(token.upper() for token in tokens[:len(words)])):
# 
#             # Put underscores between the found reserved words, and return the
#             # unused tokens
#             return '_'.join(words_upper), tokens[len(words):]
#         return None, tokens
# 
#     return match_strings_to_tokens


# Path: purplequery/query_helper.py
from typing import Any, Callable, List, Optional, Tuple, Union  # noqa: F401
from .bq_abstract_syntax_tree import (EMPTY_NODE, AbstractSyntaxTreeNode,  # noqa: F401
                                      AppliedRuleNode, AppliedRuleOutputType, RuleType)
from .terminals import grammar_literal

    Frequently used with None (see below) to indicate optional elements.
    '''
    for subrule in rule:
        node, new_tokens = apply_rule(subrule, tokens)
        if node:
            return node, new_tokens
    return None, tokens


def _apply_rule_none(rule, tokens):
    # type: (None, List[str]) -> AppliedRuleOutputType
    '''"None" means this is an epsilon rule - it matches no input tokens and
    always succeeds.  This is mainly used as an element in a list to indicate
    that the list rule is optional.

    Example:
    apply_rule(['ALL', 'DISTINCT', None]), ['ALL'])
        Match: 'ALL'
        Rest of tokens: []

    apply_rule(['ALL', 'DISTINCT', None]), ['DISTINCT'])
        Match: 'DISTINCT'
        Rest of tokens: []

    apply_rule(['ALL', 'DISTINCT', None]), ['*'])
        Match: EMPTY_NODE
        Rest of tokens: ['*']

    The "None" makes the entire list subrule optional.
    '''

    return EMPTY_NODE, tokens