Loading rpcc/__main__.py +16 −7 Original line number Diff line number Diff line import argparse import sys from pathlib import Path import lark.exceptions from rpcc.exceptions import RpccError from rpcc.version import __version__ as rpcc_version from rpcc.parser import Parser from rpcc.transformers.cxx import Transformer as CxxTransformer from pathlib import Path class PrintVersion(argparse.Action): Loading Loading @@ -48,13 +51,19 @@ def main(args=None): ) args = parser.parse_args() # try: ast = Parser(args.rpc_proto_file).parse() print(ast.pretty()) print(CxxTransformer().transform(ast).pretty()) try: parser = Parser() ast = parser.parse(args.rpc_proto_file) except RpccError as e: print(e, file=sys.stderr) return 1 # except Exception: # return 1 try: ast = CxxTransformer(parser).transform(ast) except lark.exceptions.VisitError as e: exc = e.orig_exc print(exc, file=sys.stderr) return 0 if __name__ == "__main__": Loading rpcc/exceptions.py 0 → 100644 +76 −0 Original line number Diff line number Diff line import lark class RpccError(SyntaxError): label = "syntax error" def __str__(self): context, line, column = self.args return f'{self.filename}:{line}:{column}: error: {self.label}.\n\n{context}' class UnexpectedToken(RpccError): label = "unexpected token" @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] if len(expected) > 1: return "Expected one of: \n\t* %s\n" % '\n\t* '.join(expected) else: 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)}") class UnexpectedCharacters(RpccError): label = "unexpected character" class UnexpectedEOF(RpccError): label = "unexpected End-of-File." class MissingRpcDefinition(RpccError): label = "missing rpc definition" class MissingRpcName(RpccError): label = "missing rpc name" class EmptyRpcDefinition(RpccError): label = "rpc definition is empty" class RpcRedefinition(Exception): label = "redefinition of rpc" 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.start_pos)}") def get_context(text: str, start_pos: int, span: int = 40) -> str: """Returns a pretty string pinpointing the error in the text, with span amount of context characters around it. """ pos = start_pos start = max(pos - span, 0) end = pos + span if not isinstance(text, bytes): before = text[start:pos].rsplit('\n', 1)[-1] after = text[pos:end].split('\n', 1)[0] return before + after + '\n' + ' ' * 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") rpcc/parser.py +52 −8 Original line number Diff line number Diff line import pathlib from lark import Lark, Tree import lark.exceptions from loguru import logger from rpcc.exceptions import MissingRpcDefinition, EmptyRpcDefinition, MissingRpcName, UnexpectedToken, \ UnexpectedCharacters, UnexpectedEOF GRAMMAR = r""" start : ( rpc )+ rpc : "rpc" rpc_name "{" (rpc_args rpc_return | rpc_args | rpc_return) "}" Loading @@ -25,18 +29,38 @@ GRAMMAR = r""" %ignore ";" """ INVALID_INPUT_EXAMPLES = { EmptyRpcDefinition: ['rpc foo {}', 'rpc foo { arguments {} returns {} } rpc bar {}'], MissingRpcDefinition: ['', 'foo', 'foo {', 'foo {}', '{', 'arguments {', 'returns {' ], MissingRpcName: ['rpc {'] } class Parser: """The main parser class """The main parser class""" def __init__(self) -> None: self.input_file = None self.text = None self.parser = Lark(GRAMMAR, propagate_positions=True, parser='lalr') Parameters: input_file: A pathlib.Path containing the path to the RPC description file to parse. def parse(self, input_file: pathlib.Path) -> Tree: """Parse an input file. :param input_file: A `pathlib.Path` containing the path to the RPC description file to parse. :return: An AST tree with a representation of the parsed text. """ def __init__(self, input_file: pathlib.Path) -> None: self.input_file = input_file self.parser = Lark(GRAMMAR, parser='lalr') def parse(self) -> Tree: try: file = open(self.input_file, "r", encoding="utf-8") except (FileNotFoundError, EnvironmentError) as err: Loading @@ -44,4 +68,24 @@ class Parser: raise else: with file: return self.parser.parse(file.read()) self.text = file.read() try: return self.parser.parse(self.text) except lark.exceptions.UnexpectedInput as u: 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)) 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)) if isinstance(u, lark.exceptions.UnexpectedCharacters): raise UnexpectedCharacters(u.line, u.column, u.get_context(self.text), ) if isinstance(u, lark.exceptions.UnexpectedEOF): raise UnexpectedEOF() raise u rpcc/transformers/cxx.py +18 −4 Original line number Diff line number Diff line from typing import List, Tuple, Any import lark from lark import v_args, Discard from loguru import logger from lark import v_args from rpcc.parser import Parser from rpcc.exceptions import RpcRedefinition from rpcc.rpc import RemoteProcedure, Argument, ReturnVariable fwdecls_template = ( Loading @@ -23,6 +24,11 @@ _cxx_type_map = { class Transformer(lark.Transformer): def __init__(self, parser: Parser): self.rpcs = set() self.parser = parser # variable names can be coverted directly into strings NAME = str Loading Loading @@ -50,16 +56,24 @@ class Transformer(lark.Transformer): (typename, varname) = t return ReturnVariable(varname, typename) @v_args(inline=True) def rpc(self, name: str, args: List[Argument], retval: ReturnVariable) -> RemoteProcedure: @v_args(inline=True, meta=True) def rpc(self, meta: lark.tree.Meta, name: str, args: List[Argument] = None, retval: ReturnVariable = None) -> RemoteProcedure: """Transform a `rpc` node from the AST into a `RemoteProcedure` object from a name, a list of `Argument`s and a `ReturnVariable`. :param meta: Metainformation about the current Token such as line, column, start_pos, etc. :param name: A name for the remote procedure. :param args: A list of `Argument` describing the input arguments for the remote procedure. :param retval: A `ReturnValue` describing the remote procedure's return value. :return: A `RemoteProcedure` object describing the remote procedure. """ if name in self.rpcs: raise RpcRedefinition(self.parser.input_file, self.parser.text, meta, name) else: self.rpcs.add(name) return RemoteProcedure(name, args, retval) @v_args(inline=True) Loading Loading
rpcc/__main__.py +16 −7 Original line number Diff line number Diff line import argparse import sys from pathlib import Path import lark.exceptions from rpcc.exceptions import RpccError from rpcc.version import __version__ as rpcc_version from rpcc.parser import Parser from rpcc.transformers.cxx import Transformer as CxxTransformer from pathlib import Path class PrintVersion(argparse.Action): Loading Loading @@ -48,13 +51,19 @@ def main(args=None): ) args = parser.parse_args() # try: ast = Parser(args.rpc_proto_file).parse() print(ast.pretty()) print(CxxTransformer().transform(ast).pretty()) try: parser = Parser() ast = parser.parse(args.rpc_proto_file) except RpccError as e: print(e, file=sys.stderr) return 1 # except Exception: # return 1 try: ast = CxxTransformer(parser).transform(ast) except lark.exceptions.VisitError as e: exc = e.orig_exc print(exc, file=sys.stderr) return 0 if __name__ == "__main__": Loading
rpcc/exceptions.py 0 → 100644 +76 −0 Original line number Diff line number Diff line import lark class RpccError(SyntaxError): label = "syntax error" def __str__(self): context, line, column = self.args return f'{self.filename}:{line}:{column}: error: {self.label}.\n\n{context}' class UnexpectedToken(RpccError): label = "unexpected token" @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] if len(expected) > 1: return "Expected one of: \n\t* %s\n" % '\n\t* '.join(expected) else: 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)}") class UnexpectedCharacters(RpccError): label = "unexpected character" class UnexpectedEOF(RpccError): label = "unexpected End-of-File." class MissingRpcDefinition(RpccError): label = "missing rpc definition" class MissingRpcName(RpccError): label = "missing rpc name" class EmptyRpcDefinition(RpccError): label = "rpc definition is empty" class RpcRedefinition(Exception): label = "redefinition of rpc" 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.start_pos)}") def get_context(text: str, start_pos: int, span: int = 40) -> str: """Returns a pretty string pinpointing the error in the text, with span amount of context characters around it. """ pos = start_pos start = max(pos - span, 0) end = pos + span if not isinstance(text, bytes): before = text[start:pos].rsplit('\n', 1)[-1] after = text[pos:end].split('\n', 1)[0] return before + after + '\n' + ' ' * 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")
rpcc/parser.py +52 −8 Original line number Diff line number Diff line import pathlib from lark import Lark, Tree import lark.exceptions from loguru import logger from rpcc.exceptions import MissingRpcDefinition, EmptyRpcDefinition, MissingRpcName, UnexpectedToken, \ UnexpectedCharacters, UnexpectedEOF GRAMMAR = r""" start : ( rpc )+ rpc : "rpc" rpc_name "{" (rpc_args rpc_return | rpc_args | rpc_return) "}" Loading @@ -25,18 +29,38 @@ GRAMMAR = r""" %ignore ";" """ INVALID_INPUT_EXAMPLES = { EmptyRpcDefinition: ['rpc foo {}', 'rpc foo { arguments {} returns {} } rpc bar {}'], MissingRpcDefinition: ['', 'foo', 'foo {', 'foo {}', '{', 'arguments {', 'returns {' ], MissingRpcName: ['rpc {'] } class Parser: """The main parser class """The main parser class""" def __init__(self) -> None: self.input_file = None self.text = None self.parser = Lark(GRAMMAR, propagate_positions=True, parser='lalr') Parameters: input_file: A pathlib.Path containing the path to the RPC description file to parse. def parse(self, input_file: pathlib.Path) -> Tree: """Parse an input file. :param input_file: A `pathlib.Path` containing the path to the RPC description file to parse. :return: An AST tree with a representation of the parsed text. """ def __init__(self, input_file: pathlib.Path) -> None: self.input_file = input_file self.parser = Lark(GRAMMAR, parser='lalr') def parse(self) -> Tree: try: file = open(self.input_file, "r", encoding="utf-8") except (FileNotFoundError, EnvironmentError) as err: Loading @@ -44,4 +68,24 @@ class Parser: raise else: with file: return self.parser.parse(file.read()) self.text = file.read() try: return self.parser.parse(self.text) except lark.exceptions.UnexpectedInput as u: 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)) 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)) if isinstance(u, lark.exceptions.UnexpectedCharacters): raise UnexpectedCharacters(u.line, u.column, u.get_context(self.text), ) if isinstance(u, lark.exceptions.UnexpectedEOF): raise UnexpectedEOF() raise u
rpcc/transformers/cxx.py +18 −4 Original line number Diff line number Diff line from typing import List, Tuple, Any import lark from lark import v_args, Discard from loguru import logger from lark import v_args from rpcc.parser import Parser from rpcc.exceptions import RpcRedefinition from rpcc.rpc import RemoteProcedure, Argument, ReturnVariable fwdecls_template = ( Loading @@ -23,6 +24,11 @@ _cxx_type_map = { class Transformer(lark.Transformer): def __init__(self, parser: Parser): self.rpcs = set() self.parser = parser # variable names can be coverted directly into strings NAME = str Loading Loading @@ -50,16 +56,24 @@ class Transformer(lark.Transformer): (typename, varname) = t return ReturnVariable(varname, typename) @v_args(inline=True) def rpc(self, name: str, args: List[Argument], retval: ReturnVariable) -> RemoteProcedure: @v_args(inline=True, meta=True) def rpc(self, meta: lark.tree.Meta, name: str, args: List[Argument] = None, retval: ReturnVariable = None) -> RemoteProcedure: """Transform a `rpc` node from the AST into a `RemoteProcedure` object from a name, a list of `Argument`s and a `ReturnVariable`. :param meta: Metainformation about the current Token such as line, column, start_pos, etc. :param name: A name for the remote procedure. :param args: A list of `Argument` describing the input arguments for the remote procedure. :param retval: A `ReturnValue` describing the remote procedure's return value. :return: A `RemoteProcedure` object describing the remote procedure. """ if name in self.rpcs: raise RpcRedefinition(self.parser.input_file, self.parser.text, meta, name) else: self.rpcs.add(name) return RemoteProcedure(name, args, retval) @v_args(inline=True) Loading