示例#1
0
 def ensure_poly_running(self):
     """Starts the Poly/ML process if it is not already running."""
     if self.process == None or not self.process.is_alive():
         # reset state, in case poly just died
         self.compile_in_progress = False
         self._parse_trees = {}
         self.process = PolyProcess(self.poly_bin)
示例#2
0
class Poly:
    """The core class for interacting with a Poly/ML instance.

    poly_bin -- the path to the Poly/ML binary; only used when creating
                self.process
    process -- the Poly/ML process (lazily created)
    compile_in_progress -- whether there is currently an async compilation
                           happening
    """

    def __init__(self, poly_bin="poly"):
        self.poly_bin = poly_bin
        self.process = None
        self.compile_in_progress = False
        self._parse_trees = {}

        # for _clean_text()
        import re

        self._clean_rexp = re.compile(r"\s+")

    def has_built(self, path):
        """Return whether a file has been compiled."""
        return path in self._parse_trees.keys()

    def ensure_poly_running(self):
        """Starts the Poly/ML process if it is not already running."""
        if self.process == None or not self.process.is_alive():
            # reset state, in case poly just died
            self.compile_in_progress = False
            self._parse_trees = {}
            self.process = PolyProcess(self.poly_bin)

    def node_for_position(self, path, position):
        """Get the PolyNode at a given position.

        This will return None if the file has not been
        compiled.  The information is only accurate for
        the last successful compile of that file.

        path -- the path of the file, as passed to compile or compile_sync
        position -- a zero-indexed offset in the ml code last passed to
                    compile or compile_sync for path

        returns a PolyNode object, or None
        raises poly.process.Timeout if the request to Poly/ML times out
        raises poly.process.ProtocolError if communication with Poly/ML failed
        """
        if path in self._parse_trees.keys():
            node = PolyNode()
            node.file_name = path
            p = self.process.sync_request("O", [self._parse_trees[path], position, position])
            p.popcode("O")
            p.pop()  # ignire RID
            p.popcode(",")
            node.parse_tree = p.pop()
            p.popcode(",")
            node.start = p.popint()
            p.popcode(",")
            node.end = p.popint()
            while p.popcode().code == ",":
                node.commands.append(p.popstr())
            return node
        else:
            return None

    def type_for_node(self, node):
        """Get the type of a PolyNode

        This will return None if the node does not have a type.
        The information is only accurate if the file has not been
        recompiled since the PolyNode object was created.

        node -- a PolyNode, as returned by node_for_position or
                declaration_for_node

        returns a string giving the type, or None
        raises poly.process.Timeout if the request to Poly/ML times out
        raises poly.process.ProtocolError if communication with Poly/ML failed
        """
        if node and "T" in node.commands:
            p = self.process.sync_request("T", [node.parse_tree, node.start, node.end])
            p.popcode("T")
            for i in range(7):
                p.pop()  # ignore info about the ident.
            if p.popcode().code == ",":
                return p.popstr().strip()
            else:
                return None
        else:
            return None

    def declaration_for_node(self, node):
        """Get the PolyNode for the declaration of a PolyNode

        This will return None if the node does not have a declaration.
        The information is only accurate if the file has not been
        recompiled since the PolyLocation object was created.

        node -- a PolyNode, as returned by node_for_position

        Returns a PolyLocation for the declaration, or None.
        This may be a PolyNode.

        Due to a shortcoming of Poly/ML, the location filename will not
        include the path to the file (FIXME: need some way to specify
        the search path?)

        raises poly.process.Timeout if the request to Poly/ML times out
        raises poly.process.ProtocolError if communication with Poly/ML failed
        """
        if node and "I" in node.commands:
            p = self.process.sync_request("I", [node.parse_tree, node.start, node.end, "I"])

            p.popcode("I")
            for i in range(7):
                p.pop()  # ignore info about the ident.
            if p.popcode().code == ",":
                file_name = p.popstr()
                p.popcode(",")
                line = p.popint()
                p.popcode(",")
                start = p.popint()
                p.popcode(",")
                end = p.popint()

                if file_name in self._parse_trees.keys():
                    if line:
                        print("Got line number ({0}) for self-compiled file!".format(line))
                        return PolyLocation(file_name, line, start, end)
                    else:
                        return PolyNode(file_name, start, end, self._parse_trees[file_name])
                else:
                    return PolyLocation(file_name, line, start, end)

                return dnode
            else:
                return None
        else:
            return None

    def _clean_text(self, text):
        """Removes excess whitespace from a string.

        Returns the given text with all contiguous whitespace replaced by
        a single space, and any whitespace at the start or end removed.

        Poly/ML puts newlines into its messages; this allows us to strip
        them out.
        """
        return self._clean_rexp.sub(" ", text.strip())

    def _pop_d_message(self, p):
        """Reads a D message from a Packet

        Returns a pair of PolyLocation and identifier
        """
        p.popcode("D")
        file_name = p.popstr()
        p.popcode(",")
        line = p.popint()
        if not line:
            line = None
        p.popcode(",")
        start = p.popint()
        p.popcode(",")
        end = p.popint()
        p.popcode(";")
        text = p.popstr()
        p.popcode("d")
        return PolyLocation(file_name, line, start, end), text

    def _pop_output_until_code(self, p, closing_code):
        """Reads some output, possibly including markup, up to (and including)
        the next occurrence of closing_code (which should be lower case)

        Returns the output, and the last found location
        """
        text = ""
        location = None
        if not p.nextiscode():
            text = p.popstr()
        code = p.popcode().code
        while code != closing_code:
            if code == "D":
                p.pushcode("D")
                location, t = self._pop_d_message(p)
                if len(text):
                    text += " "
                text += t
            else:
                p.popuntilcode(";")
                text += " " + p.popstr().strip()
                p.popcode(code.lower())
            if not p.nextiscode():
                if len(text):
                    text += " "
                n = p.popstr()
                text += n
            code = p.popcode().code
        return location, text

    def _pop_compile_result_header(self, p, file):
        """Reads an R response and returns the result code as a string

        Also saves the parse tree ID in self._parse_trees.

        p -- a poly.process.Packet containing a compilation result block
        file -- the key to use for saving the parse tree ID
        """
        p.popcode("R")  # pop off leading p code
        p.pop()  # ignore RID
        p.popcode(",")
        self._parse_trees[file] = p.pop()  # save parse tree ID
        p.popcode(",")
        result_code = p.popstr()
        p.popcode(",")
        p.popint()  # ignore final offset
        p.popcode(";")
        return result_code

    def _pop_compile_exception_message(self, p):
        """Reads an exception message from an R response

        p -- a poly.process.Packet containing a compilation result block,
             that has already had _pop_compile_result_header called on it,
             and had the result 'X'

        Returns a PolyException message
        """
        p.pop_until_nonempty()
        p.popcode("X")  # pop off leading p code
        exp = PolyException("")
        exp.location, message = self._pop_output_until_code(p, "x")
        exp.text = self._clean_text(message)
        return exp

    def _pop_compile_error_messages(self, p):
        """Reads the error list from an R response

        p -- a poly.process.Packet containing a compilation result block,
             that has already had _pop_compile_result_header called on it (and
             _pop_compile_exception_message if the result was 'X')

        Returns a list of PolyErrorMessage objects.
        """
        messages = []
        p.pop_until_nonempty()
        while p.popcode().code == "E":
            message_code = p.popstr()
            p.popcode(",")
            file_name = p.popstr()
            p.popcode(",")
            line = p.popint()
            if not line:
                line = None
            p.popcode(",")
            start_pos = p.popint()
            p.popcode(",")
            end_pos = p.popint()

            p.popcode(";")
            location, text = self._pop_output_until_code(p, "e")

            p.popempty()  # empty string between escape codes

            text = self._clean_text(text)
            messages.append(PolyErrorMessage(message_code, file_name, line, start_pos, end_pos, text))
        return messages

    def _read_compile_response(self, p, file):
        """Parses the response packet for a compilation

        p -- a poly.process.Packet containing a compilation result block
        file -- the file name for the compilation

        Returns a pair of result code (a single-character string) and
        a list of PolyMessage objects.
        """
        result_code = self._pop_compile_result_header(p, file)
        messages = []
        if result_code == "L":
            location, text = self._pop_output_until_code(p, "r")
            # this clearly counts as an error
            messages = [PolyMessage("E", text)]
        else:
            if result_code == "X":
                messages = [self._pop_compile_exception_message(p)]
            messages += self._pop_compile_error_messages(p)
        return result_code, messages

    def compile_sync(self, file, prelude, source, timeout=10):
        """Sends ML code for compilation, and waits for the result

        file -- the file name for the compilation
        prelude -- ML code to set up the compilation state (eg:
                   loading a saved state)
        source -- the ML code to compile
        timeout -- (optional) how long to wait, in seconds (default: 10)

        Returns a pair of result code (a single-character string) and
        a list of PolyMessage objects.  If the result code is 'X', the
        first will be a PolyException and the remainder PolyErrorMessage
        objects, otherwise they will all be PolyErrorMessage objects.
        """
        if self.compile_in_progress:
            return None, []

        self.ensure_poly_running()
        p = self.process.sync_request("R", [file, 0, len(prelude), len(source), prelude, source], timeout)
        return self._read_compile_response(p, file)

    def compile(self, file, prelude, source, handler):
        """Sends ML code for compilation

        file -- the file name for the compilation
        prelude -- ML code to set up the compilation state (eg:
                   loading a saved state)
        source -- the ML code to compile
        handler -- a method to call when the compilation has finished

        The handler will be passed two arguments: the result code (a
        single-character string) and a list of PolyMessage objects.  If the
        result code is 'X', the first will be a PolyException and the remainder
        PolyErrorMessage objects, otherwise they will all be PolyErrorMessage
        objects.

        Returns the request ID (an integer).
        """
        if self.compile_in_progress:
            return -1

        self.ensure_poly_running()
        self.compile_in_progress = True

        def run_handler(p):
            self.compile_in_progress = False
            result_code, messages = self._read_compile_response(p, file)
            handler(result_code, messages)

        rid = self.process.send_request("R", [file, 0, len(prelude), len(source), prelude, source], run_handler)

        return rid

    def cancel_compile(self, rid):
        """Cancels a compilation in progress

        rid -- the request id, as returned by compile()
        """
        self.process.send_request("K", [rid])