Example #1
0
    def generate_actions(self, state):
        """
        Determine all zero-cost action according to current state
        :param state: current State of the parser
        :return: generator of Action items to perform
        """
        if not ((state.buffer or state.stack) and (self.edges_remaining or any(
                map(self.need_label, state.stack + list(state.buffer))))):
            yield Actions.Finish
            if state.stack and not self.need_label(state.stack[-1]):
                yield Actions.Reduce
            return

        self.found = False
        if state.stack:
            s0 = state.stack[-1]
            incoming = self.edges_remaining.intersection(s0.orig_node.incoming)
            outgoing = self.edges_remaining.intersection(s0.orig_node.outgoing)
            if not incoming and not outgoing and not self.need_label(s0):
                yield Actions.Reduce
                return
            else:
                # Check for node label action: if all terminals have already been connected
                if self.need_label(s0) and not any(
                        e.tag == layer1.EdgeTags.Terminal for e in outgoing):
                    self.found = True
                    yield Actions.Label(0, orig_node=s0.orig_node, oracle=self)

                # Check for actions to create new nodes
                for edge in incoming:
                    if edge.parent.ID in self.nodes_remaining and not edge.parent.attrib.get(
                            "implicit"
                    ) and (not edge.attrib.get("remote") or
                           # Allow remote parent if all its children are remote/implicit
                           all(
                               e.attrib.get("remote")
                               or e.child.attrib.get("implicit")
                               for e in edge.parent)):
                        yield self.action(edge, NODE,
                                          PARENT)  # Node or RemoteNode

                for edge in outgoing:
                    if edge.child.ID in self.nodes_remaining and edge.child.attrib.get(
                            "implicit") and (
                                not edge.attrib.get("remote")
                            ):  # Allow implicit child if it is not remote
                        yield self.action(edge, NODE, CHILD)  # Implicit

                if len(state.stack) > 1:
                    s1 = state.stack[-2]
                    # Check for node label action: if all terminals have already been connected
                    if self.need_label(s1) and not any(
                            e.tag == layer1.EdgeTags.Terminal
                            for e in self.edges_remaining.intersection(
                                s1.orig_node.outgoing)):
                        self.found = True
                        yield Actions.Label(1,
                                            orig_node=s1.orig_node,
                                            oracle=self)

                    # Check for actions to create binary edges
                    for edge in incoming:
                        if edge.parent.ID == s1.node_id:
                            yield self.action(
                                edge, EDGE, RIGHT)  # RightEdge or RightRemote

                    for edge in outgoing:
                        if edge.child.ID == s1.node_id:
                            yield self.action(edge, EDGE,
                                              LEFT)  # LeftEdge or LeftRemote
                        elif state.buffer and edge.child.ID == state.buffer[0].node_id and \
                                len(state.buffer[0].orig_node.incoming) == 1:
                            yield Actions.Shift  # Special case to allow getting rid of simple children quickly

                    if not self.found:
                        # Check if a swap is necessary, and how far (if compound swap is enabled)
                        related = dict([(edge.child.ID, edge)
                                        for edge in outgoing] +
                                       [(edge.parent.ID, edge)
                                        for edge in incoming])
                        distance = None  # Swap distance (how many nodes in the stack to swap)
                        for i, s in enumerate(
                                state.stack[-3::-1], start=1
                        ):  # Skip top two: checked above, not related
                            edge = related.pop(s.node_id, None)
                            if edge is not None:
                                swap = 'regular'
                                max_swap = 3
                                if not swap == COMPOUND:  # We have no chance to reach it, so stop trying
                                    self.remove(edge)
                                    continue
                                if distance is None and swap == COMPOUND:  # Save the first one
                                    distance = min(
                                        i, max_swap
                                    )  # Do not swap more than allowed
                                if not related:  # All related nodes are in the stack
                                    yield Actions.Swap(distance)
                                    return

        if state.buffer and not self.found:
            yield Actions.Shift