def test_any_repetition(self):
        edge_a = Edge('a', 'state_a')
        edge_b = Edge('b', 'state_b')
        state_a = State('state_a')
        state_b = State('state_b')

        edge_out_a = Edge('a', 'state_a')
        edge_out_a_e = Edge('', 'state_a')
        edge_out_b = Edge('b', 'state_b')
        edge_out_b_e = Edge('', 'state_b')
        state_out_a = State('state_a', [edge_out_a, edge_out_b])
        state_out_b = State('state_b', [edge_out_a, edge_out_b])

        in_edges = [edge_a, edge_b]
        new_end_states = [state_a, state_b]
        new_states = [state_a, state_b]
        repetition = (0, float('inf'))
        is_optional = False

        in_edges_out = [edge_out_a, edge_out_b, edge_out_a_e, edge_out_b_e]
        new_end_states_out = [state_out_a, state_out_b]
        new_states_out = [state_out_a, state_out_b]
        expected = [in_edges_out, new_end_states_out, new_states_out]
        actual = repetition_applicator.apply_repetition(
            in_edges, new_end_states, new_states, repetition, is_optional)

        logger.debug(
            f'Actual result:\nin_edges: {actual[0]}\nnew_end_states: {actual[1]}\nnew_states: {actual[2]}'
        )
        logger.debug(f'start_edges')
        self.assertTrue(edges_equal(actual[0], expected[0]))
        logger.debug(f'end_states')
        assert_states_equal(actual[1], expected[1])
        logger.debug(f'states')
        assert_states_equal(actual[2], expected[2])
    def test_N_repetition(self):
        edge = Edge('a', 'state_a')
        state = State('state_a')

        edge_out = Edge('a', 'state_a')
        edge_2_out = Edge('a', 'state_a_2')
        state_out = State('state_a', [edge_2_out])
        state_2_out = State('state_a_2')

        in_edges = [edge]
        new_end_states = [state]
        new_states = [state]
        repetition = (2, 2)
        is_optional = False

        in_edges_out = [edge_out]
        new_end_states_out = [state_2_out]
        new_states_out = [state_out, state_2_out]
        expected = [in_edges_out, new_end_states_out, new_states_out]
        actual = repetition_applicator.apply_repetition(
            in_edges, new_end_states, new_states, repetition, is_optional)

        logger.debug(
            f'Actual result:\nin_edges: {actual[0]}\nnew_end_states: {actual[1]}\nnew_states: {actual[2]}'
        )
        logger.debug(f'start_edges')
        self.assertTrue(edges_equal(actual[0], expected[0]))
        logger.debug(f'end_states')
        assert_states_equal(actual[1], expected[1])
        logger.debug(f'states')
        assert_states_equal(actual[2], expected[2])
    def test_N_plus_repetition(self):
        in_edge = Edge('a', 'state_a')
        in_state = State('state_a')

        edge = Edge('a', 'state_a')
        edge2 = Edge('a', 'state_a_2')
        edge3 = Edge('a', 'state_a_3')
        edge3e = Edge('', 'state_a_3')
        state = State('state_a', [edge2])
        state2 = State('state_a_2', [edge3])
        state3 = State('state_a_3', [edge3])

        in_edges = [in_edge]
        new_end_states = [in_state]
        new_states = [in_state]
        repetition = (3, float('inf'))
        is_optional = False

        in_edges_out = [edge]
        new_end_states_out = [state3]
        new_states_out = [state, state2, state3]
        expected = [in_edges_out, new_end_states_out, new_states_out]
        actual = repetition_applicator.apply_repetition(
            in_edges, new_end_states, new_states, repetition, is_optional)

        logger.debug(
            f'Actual result:\nin_edges: {actual[0]}\nnew_end_states: {actual[1]}\nnew_states: {actual[2]}'
        )
        logger.debug(f'start_edges')
        self.assertTrue(edges_equal(actual[0], expected[0]))
        logger.debug(f'end_states')
        assert_states_equal(actual[1], expected[1])
        logger.debug(f'states')
        assert_states_equal(actual[2], expected[2])
    def test_N_plus_repetition(self):
        edge_a = Edge('a', 'state_a')
        edge_b = Edge('b', 'state_b')
        state_a = State('state_a', edge_b)
        state_b = State('state_b')

        edge_out_a = Edge('a', 'state_a')
        edge_out_b = Edge('b', 'state_b')
        edge_out_a_2 = Edge('a', 'state_a_2')
        edge_out_b_2 = Edge('b', 'state_b_2')
        edge_out_a_3 = Edge('a', 'state_a_3')
        edge_out_a_3_i = Edge('a', 'state_a_3')
        edge_out_b_3 = Edge('b', 'state_b_3')
        state_out_a = State('state_a', edge_out_b)
        state_out_b = State('state_b', edge_out_a_2)
        state_out_a_2 = State('state_a_2', edge_out_b_2)
        state_out_b_2 = State('state_b_2', edge_out_a_3)
        state_out_a_3 = State('state_a_3', edge_out_b_3)
        state_out_b_3 = State('state_b_3', edge_out_a_3_i)

        in_edges = [edge_a]
        new_end_states = [state_b]
        new_states = [state_a, state_b]
        repetition = (3, float('inf'))
        is_optional = False

        in_edges_out = [edge_out_a]
        new_end_states_out = [state_out_b_3]
        new_states_out = [
            state_out_a, state_out_b, state_out_a_2, state_out_b_2,
            state_out_a_3, state_out_b_3
        ]
        expected = [in_edges_out, new_end_states_out, new_states_out]
        actual = repetition_applicator.apply_repetition(
            in_edges, new_end_states, new_states, repetition, is_optional)

        logger.debug(
            f'Actual result:\nin_edges: {actual[0]}\nnew_end_states: {actual[1]}\nnew_states: {actual[2]}'
        )
        logger.debug(f'start_edges')
        self.assertTrue(edges_equal(actual[0], expected[0]))
        logger.debug(f'end_states')
        assert_states_equal(actual[1], expected[1])
        logger.debug(f'states')
        assert_states_equal(actual[2], expected[2])
def recursive_parse_elements(elements, used_state_ids=None):
    logger.debug('Parsing elements {elements}')
    current_states = []
    next_states = []
    start_edges = []
    end_states = []
    states = []

    current_end_states = []
    new_end_states = []
    new_token_end_states = []
    new_states = []
    current_in_edges = []

    if used_state_ids == None:
        used_state_ids = {} # {state_id:string: times_used:int}
    first_iteration = True

    i = 0
    while i < len(elements):
        logger.debug(f'i: {i} | {elements[i:]}')
        tokens, i = get_concurrent_tokens(elements, i)
        logger.debug(f'Tokens: {tokens}')

        for token in tokens:
            logger.debug(f'Processing token "{token}"')
            token, repetition = get_repetition(token)
            logger.debug(f'Got repetition. token: "{token}", repetition: {repetition}')
            token, is_optional = get_optionality(token)
            logger.debug(f'Got optionality. token: "{token}", is_optional: {is_optional}')

            # If we have a group, we have to recurse down into it, because the internal
            # structure of these states could be anything.
            if isinstance(token, list):
                logger.debug(f'Token "{token}" is list, recursing.')
                in_edges, new_token_end_states, new_states = recursive_parse_elements(token, used_state_ids)

            else:
                token, is_automata = is_automata_token(token)
                logger.debug(f'is_automata: {is_automata}')

                # Make sure the token hasn't been used before. If it has, then we
                # need to add a suffix onto it.
                # TODO: Make sure state machines know to ignore this suffix, or find
                # a better way to register which state machine goes with which token.
                if token in used_state_ids:
                    unique_token = token + f'_#{used_state_ids[token]+1}'
                    logger.debug(f'Token "{token}" is already in use. Changing to {unique_token}.')
                    used_state_ids[token] += 1
                else:
                    unique_token = token
                    used_state_ids[token] = 1

                new_state = State(unique_token, is_automata = is_automata)
                new_token_end_states = [new_state]
                new_states = [new_state]


                in_edge = Edge(token, new_state.id)
                in_edges = [in_edge]


            in_edges, new_token_end_states, new_states = repetition_applicator.apply_repetition(in_edges, new_token_end_states, new_states, repetition, is_optional)
            logger.debug(f'Got apply_repetition result of:\n  in_edges: {in_edges}\n  new_token_end_states: {new_token_end_states}\n  new_states: {new_states}')

            if first_iteration:
                logger.debug(f'Appending edge(s) {in_edges} to start_edges.')
                start_edges += in_edges
            else:
                for current_state in current_end_states:
                    for edge in in_edges:
                        logger.debug(f'Appending edge(s) {edge} to current_end_states {current_end_states}.')
                        current_state.add_edge(edge)
            logger.debug(f'Dumping new states {[state.id for state in new_states]} into states.')
            states += new_states
            logger.debug(f'Dumping new end_states for token {token} ({new_token_end_states}) into new_end_states ({new_end_states}).')
            new_end_states += new_token_end_states
            new_token_end_states = []
        # End for token in tokens loop
        first_iteration = False
        logger.debug(f'Reached end of concurrent tokens, setting current_end_states to new_end_states ({[state.id for state in new_end_states]}).')
        current_end_states = new_end_states
        new_end_states = []
    # End i < len(elements) loop
    end_states = current_end_states

    logger.debug(f'Returning:\n  start_edges: {start_edges}\n  end_states: {end_states}\n  states:{states}')
    return start_edges, end_states, states