Verified Commit ed972bb2 authored by Alberto Miranda's avatar Alberto Miranda ♨️
Browse files

Refactor exceptions

parent 84b843a9
Loading
Loading
Loading
Loading
+3 −3
Original line number Diff line number Diff line
@@ -6,7 +6,7 @@ class DiagnosticHighlighter(RegexHighlighter):

    base_style = "diagnostic."
    highlights = [
        r"(?P<location>.*?:\d+:\d+:)\s(?P<error>error:)\s(?P<message>.*?)(?P<context>(?:\n.*){2})(?P<caret>\^)\n",
        r"(?P<location>.*?:\d+:\d+:)\s(?P<note>note:)\s(?P<message>.*?)(?P<context>(?:\n.*){2})(?P<caret>\^)\n",
        r"Expected:\n\s+’(?P<token>.*?)’"
        r"(?P<location>.*?:\d+:\d+:)\s(?P<error>error:)\s(?P<message>.*?)(?:’(?P<ident>.*?)’)?(?P<context>(?:\n.*){2})(?P<caret>\^)\n",
        r"(?P<location>.*?:\d+:\d+:)\s(?P<note>note:)\s(?:’(?P<ident>.*?)’)?(?P<message>.*?)(?P<context>(?:\n.*){2})(?P<caret>\^)\n",
        r"Expected:(?:\n\s+’(?P<token>.*?)’)+"
    ]
+3 −2
Original line number Diff line number Diff line
@@ -5,8 +5,9 @@ diagnostics_theme = Theme({
    "diagnostic.location": "bold bright_white",
    "diagnostic.error": "bold bright_red",
    "diagnostic.note": "bold cyan",
    "diagnostic.message": "bold bright_white",
    "diagnostic.message": "bright_white",
    "diagnostic.context": "default",
    "diagnostic.caret": "bold bright_green",
    "diagnostic.token": "bold bright_cyan"
    "diagnostic.token": "bold bright_cyan",
    "diagnostic.ident": "bold bright_white"
})
+55 −25
Original line number Diff line number Diff line
import lark
from rpcc.meta import FileLocation


class RpccError(SyntaxError):
    label = "syntax error"

    def __init__(self, location: FileLocation) -> None:
        self.location = location

    def __str__(self):
        context, line, column = self.args
        return f'{self.filename}:{line}:{column}: error: {self.label}.\n\n{context}'
        return f"{self.location.filename}:{self.location.line}:{self.location.column}: error: {self.label}"


class UnexpectedToken(RpccError):
    label = "unexpected token"

    def __init__(self, location: FileLocation, token, expected, terminals):
        super().__init__(location)
        self.token = token
        self.expected_tokens = expected
        self.terminals = terminals

    @staticmethod
    def _format_expected(expected, terminals):
        d = {t.name: t for t in terminals}

        expected = [str(d[t_name].pattern).replace('\\', "") if t_name in d else t_name for t_name in expected]
        expected = [str(d[t_name].pattern).replace('\\', "").replace('\'', '') if t_name in d else t_name for t_name in expected]

        if len(expected) > 1:
            return "Expected one of:\n\t* %s\n" % '\n\t* '.join(expected)
@@ -24,10 +32,9 @@ class UnexpectedToken(RpccError):
            return "Expected:\n\t%s\n" % '\n\t* '.join(expected)

    def __str__(self):
        filename, line, column, token, expected, terminals, context = self.args
        return (f"{filename}:{line}:{column}: error: {self.label}{token}\n\n"
                f"{context}\n"
                f"{self._format_expected(expected, terminals)}")
        return (super().__str__() + f"{self.token}\n" +
                f"{get_context(self.location)}\n" +
                f"{self._format_expected(self.expected_tokens, self.terminals)}")


class UnexpectedCharacters(RpccError):
@@ -37,42 +44,65 @@ class UnexpectedCharacters(RpccError):
class UnexpectedEOF(RpccError):
    label = "unexpected End-of-File."

    def __init__(self):
        pass


class MissingRpcDefinition(RpccError):
    label = "missing rpc definition"

    def __str__(self):
        return (super().__str__() + "\n" +
                get_context(self.location))


class MissingRpcName(RpccError):
    label = "missing rpc name"

    def __str__(self):
        return (super().__str__() + "\n" +
                get_context(self.location))


class EmptyRpcDefinition(RpccError):
    label = "rpc definition is empty"

    def __str__(self):
        return (super().__str__() + "\n" +
                get_context(self.location))

class RpcRedefinition(Exception):

class RpcRedefinition(RpccError):
    label = "redefinition of rpc"
    prev_label = "previously defined here"

    def __init__(self, location: FileLocation, name: str, prev_definition: FileLocation):
        super().__init__(location)
        self.name = name
        self.prev = prev_definition

    def __str__(self):
        filename, text, meta, name = self.args
        return (f"{filename}:{meta.line}:{meta.column}: error: {self.label}{name}\n"
                f"{get_context(text, meta)}")
        return (super().__str__() + f"{self.name}\n" +
                get_context(self.location) + "\n" +
                f"{self.prev.filename}:{self.prev.line}:{self.prev.column}: note: ’{self.name}{self.prev_label}\n" +
                get_context(self.prev))


def get_context(text: str, meta: lark.tree.Meta, span: int = 40) -> str:
def get_context(location: FileLocation, span: int = 40) -> str:
    """Returns a pretty string pinpointing the error in the text,
       with span amount of context characters around it.
    """
    pos = meta.start_pos
    pos = location.pos_in_stream
    start = max(pos - span, 0)
    end = pos + span
    if not isinstance(text, bytes):
    max_width = 6
        before = text[start:pos].rsplit('\n', 1)[-1]
        after = text[pos:end].split('\n', 1)[0]
        return f'{meta.line:>{max_width}} | ' + before + after + '\n' + \
               ' ' * len(before.expandtabs()) + ' ' * max_width + ' | ^\n'
    if not isinstance(location.text, bytes):
        before = location.text[start:pos].rsplit('\n', 1)[-1]
        after = location.text[pos:end].split('\n', 1)[0]
        return f'{location.line:>{max_width}} | ' + before + after + '\n' + \
               ' ' * max_width + ' | ' + ' ' * len(before.expandtabs()) + '^\n'
    else:
        before = text[start:pos].rsplit(b'\n', 1)[-1]
        after = text[pos:end].split(b'\n', 1)[0]
        return (before + after + b'\n' + b' ' * len(before.expandtabs()) + b'^\n').decode("ascii", "backslashreplace")
        before = location.text[start:pos].rsplit(b'\n', 1)[-1]
        after = location.text[pos:end].split(b'\n', 1)[0]
        return (before + after + b'\n' + b' ' * len(before.expandtabs()) + b'^\n').decode("ascii",
                                                                                          "backslashreplace")

rpcc/meta.py

0 → 100644
+21 −0
Original line number Diff line number Diff line
import pathlib


class FileLocation:
    """An object representing a specific location in the parsed text"""

    def __init__(self, filename: pathlib.Path, text: str, line: int, column: int, pos_in_stream: int) -> None:
        """Initialize a `FileLocation`.

        :param filename: The filename.
        :param text: The parsed text.
        :param line: The line of interest.
        :param column: The column of interest.
        :param pos_in_stream: The position of the location of interest in the file stream.
        """

        self.filename = filename
        self.text = text
        self.line = line
        self.column = column
        self.pos_in_stream = pos_in_stream
+16 −12
Original line number Diff line number Diff line
@@ -3,8 +3,11 @@ from lark import Lark, Tree
import lark.exceptions
from loguru import logger

from rpcc.exceptions import MissingRpcDefinition, EmptyRpcDefinition, MissingRpcName, UnexpectedToken, \
from rpcc.exceptions import (
    EmptyRpcDefinition, MissingRpcDefinition, MissingRpcName, UnexpectedToken,
    UnexpectedCharacters, UnexpectedEOF
)
from rpcc.meta import FileLocation

GRAMMAR = r"""
    start           : ( rpc )+
@@ -73,17 +76,18 @@ class Parser:
                    return self.parser.parse(self.text)
                except lark.exceptions.UnexpectedInput as u:

                    location = FileLocation(self.input_file, self.text, u.line, u.column, u.pos_in_stream)

                    exc_class = u.match_examples(self.parser.parse, INVALID_INPUT_EXAMPLES, use_accepts=True)

                    if exc_class:
                        raise exc_class(self.input_file, u.line, u.column, u.get_context(self.text))
                        raise exc_class(location)

                    if isinstance(u, lark.exceptions.UnexpectedToken):
                        raise UnexpectedToken(self.input_file, u.line, u.column, u.token, u.expected,
                                              self.parser.terminals, u.get_context(self.text))
                        raise UnexpectedToken(location, u.token, u.expected, self.parser.terminals)

                    if isinstance(u, lark.exceptions.UnexpectedCharacters):
                        raise UnexpectedCharacters(u.line, u.column, u.get_context(self.text), )
                        raise UnexpectedCharacters(location)

                    if isinstance(u, lark.exceptions.UnexpectedEOF):
                        raise UnexpectedEOF()
Loading