Skip to content
Commits on Source (2)
# rpcc # rpcc
A compiler for Mercury/Margo rpcs **rpcc** is a Python command line tool that allows developers to easily define and work with remote procedure calls (
\ No newline at end of file RPCs) compatible with the [Mercury](https://mercury-hpc.github.io/) framework.
Inspired by Google's [Protocol Buffers](https://developers.google.com/protocol-buffers), **rpcc**
allows developers to easily define RPCs using a language- and platform- neutral language, that will then be used to
generate all the necessary C/C++ boilerplate code required to actually implement them.
# Copyright 2021-2022, Barcelona Supercomputing Center (BSC), Spain
#
# This software was partially supported by the EuroHPC-funded project ADMIRE
# (Project ID: 956748, https://www.admire-eurohpc.eu).
#
# This file is part of rpcc.
#
# rpcc is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
#
# rpcc is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License along with rpcc. If not, see
# <https://www.gnu.org/licenses/>.
#
# SPDX-License-Identifier: GPL-3.0-or-later
from .rpcc import Rpcc from .rpcc import Rpcc
import sys # Copyright 2021-2022, Barcelona Supercomputing Center (BSC), Spain
#
import lark.exceptions # This software was partially supported by the EuroHPC-funded project ADMIRE
# (Project ID: 956748, https://www.admire-eurohpc.eu).
#
# This file is part of rpcc.
#
# rpcc is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
#
# rpcc is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License along with rpcc. If not, see
# <https://www.gnu.org/licenses/>.
#
# SPDX-License-Identifier: GPL-3.0-or-later
import sys
from rpcc import Rpcc from rpcc import Rpcc
from rpcc.arguments import parse_args from rpcc.arguments import parse_args
from rpcc.exceptions import RpccError from rpcc.exceptions import RpccSyntaxError
from rpcc.console import console from rpcc.console import console
from loguru import logger
def main(args=None): def main(args=None):
...@@ -15,13 +31,24 @@ def main(args=None): ...@@ -15,13 +31,24 @@ def main(args=None):
args = sys.argv[1:] args = sys.argv[1:]
args = parse_args(args) args = parse_args(args)
# configure console
console.configure(args.color_diagnostics) console.configure(args.color_diagnostics)
rpcc = Rpcc(args.rpc_proto_file, args.output_lang) # configure logging
logger.remove()
logger.add(sys.stderr, level=args.loglevel)
try: try:
rpcc.transform() Rpcc(
except RpccError as e: args.rpc_proto_file,
console.eprint(e) args.copyright_file,
args.output_lang,
args.header_output_dir,
args.impl_output_dir
).transform()
except (RpccSyntaxError, OSError) as e:
console.eprint(f"ERROR: {e}")
return 1 return 1
return 0 return 0
......
# Copyright 2021-2022, Barcelona Supercomputing Center (BSC), Spain
#
# This software was partially supported by the EuroHPC-funded project ADMIRE
# (Project ID: 956748, https://www.admire-eurohpc.eu).
#
# This file is part of rpcc.
#
# rpcc is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
#
# rpcc is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License along with rpcc. If not, see
# <https://www.gnu.org/licenses/>.
#
# SPDX-License-Identifier: GPL-3.0-or-later
import argparse import argparse
import logging
import sys import sys
from pathlib import Path from pathlib import Path
from rpcc.version import __version__ as rpcc_version from rpcc.version import __version__ as rpcc_version
from rpcc.console import console from rpcc.console import console
class PrintVersion(argparse.Action): class _SetVerbosity(argparse.Action):
def __init__(self, option_strings, dest, const=None, default=None,
required=False, help=None, metavar=None):
super().__init__(option_strings=option_strings,
dest=dest,
nargs=0,
const=const,
default=default,
required=required,
help=help)
self.count = 0
def __call__(self, parser, namespace, values, option_string=None):
levels = [
logging.CRITICAL,
logging.ERROR,
logging.WARNING,
25, # LOGURU_SUCCESS_NO
logging.INFO,
logging.DEBUG,
5 # LOGURU_TRACE_NO
]
setattr(namespace, self.dest, levels[self.count])
self.count = min(self.count + 1, len(levels) - 1)
class _PrintVersion(argparse.Action):
def __init__(self, option_strings, dest, const=None, default=None, def __init__(self, option_strings, dest, const=None, default=None,
required=False, help=None, metavar=None): required=False, help=None, metavar=None):
super().__init__(option_strings=option_strings, super().__init__(option_strings=option_strings,
...@@ -45,6 +92,31 @@ def parse_args(args) -> argparse.Namespace: ...@@ -45,6 +92,31 @@ def parse_args(args) -> argparse.Namespace:
" 2017 ISO C++ standard." " 2017 ISO C++ standard."
) )
parser.add_argument(
"--c_out",
type=Path,
metavar="OUT_DIR",
dest="impl_output_dir",
help="The directory where C/C++ implementation files should be generated.",
default=Path.cwd()
)
parser.add_argument(
"--h_out",
type=Path,
metavar="OUT_DIR",
dest="header_output_dir",
help="The directory where C/C++ header files should be generated.",
default=Path.cwd()
)
parser.add_argument(
"--copyright-file",
type=Path,
default=None,
help="Use the text in COPYRIGHT_FILE as the copyright header of all generated files."
)
parser.add_argument( parser.add_argument(
"--color-diagnostics", "--color-diagnostics",
choices=["never", "always", "auto"], choices=["never", "always", "auto"],
...@@ -54,10 +126,29 @@ def parse_args(args) -> argparse.Namespace: ...@@ -54,10 +126,29 @@ def parse_args(args) -> argparse.Namespace:
"always, or auto (the default)." "always, or auto (the default)."
) )
parser.add_argument(
"--debug",
"-d",
action="store_const",
dest="loglevel",
const=logging.DEBUG,
default=logging.WARNING,
help=argparse.SUPPRESS
)
parser.add_argument(
"--verbose",
"-v",
action=_SetVerbosity,
dest="loglevel",
default=logging.INFO,
help="Increase verbosity"
)
parser.add_argument( parser.add_argument(
"--version", "--version",
"-V", "-V",
action=PrintVersion, action=_PrintVersion,
help="Display rpcc version information." help="Display rpcc version information."
) )
......
# Copyright 2021-2022, Barcelona Supercomputing Center (BSC), Spain
#
# This software was partially supported by the EuroHPC-funded project ADMIRE
# (Project ID: 956748, https://www.admire-eurohpc.eu).
#
# This file is part of rpcc.
#
# rpcc is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
#
# rpcc is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License along with rpcc. If not, see
# <https://www.gnu.org/licenses/>.
#
# SPDX-License-Identifier: GPL-3.0-or-later
from typing import Any from typing import Any
from rich.console import Console as RichConsole from rich.console import Console as RichConsole
......
# Copyright 2021-2022, Barcelona Supercomputing Center (BSC), Spain
#
# This software was partially supported by the EuroHPC-funded project ADMIRE
# (Project ID: 956748, https://www.admire-eurohpc.eu).
#
# This file is part of rpcc.
#
# rpcc is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
#
# rpcc is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License along with rpcc. If not, see
# <https://www.gnu.org/licenses/>.
#
# SPDX-License-Identifier: GPL-3.0-or-later
from rich.highlighter import RegexHighlighter from rich.highlighter import RegexHighlighter
......
# Copyright 2021-2022, Barcelona Supercomputing Center (BSC), Spain
#
# This software was partially supported by the EuroHPC-funded project ADMIRE
# (Project ID: 956748, https://www.admire-eurohpc.eu).
#
# This file is part of rpcc.
#
# rpcc is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
#
# rpcc is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License along with rpcc. If not, see
# <https://www.gnu.org/licenses/>.
#
# SPDX-License-Identifier: GPL-3.0-or-later
from rich.theme import Theme from rich.theme import Theme
# the colors that should be applied to each part of a diagnostic message # the colors that should be applied to each part of a diagnostic message
......
# Copyright 2021-2022, Barcelona Supercomputing Center (BSC), Spain
#
# This software was partially supported by the EuroHPC-funded project ADMIRE
# (Project ID: 956748, https://www.admire-eurohpc.eu).
#
# This file is part of rpcc.
#
# rpcc is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
#
# rpcc is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License along with rpcc. If not, see
# <https://www.gnu.org/licenses/>.
#
# SPDX-License-Identifier: GPL-3.0-or-later
from typing import Collection from typing import Collection
from rpcc.meta import FileLocation from rpcc.meta import FilePosition
class ConfigurationError(ValueError): class ConfigurationError(ValueError):
...@@ -12,20 +30,20 @@ def assert_config(value, options: Collection, msg="Got %r, expected one of %s"): ...@@ -12,20 +30,20 @@ def assert_config(value, options: Collection, msg="Got %r, expected one of %s"):
raise ConfigurationError(msg % (value, options)) raise ConfigurationError(msg % (value, options))
class RpccError(SyntaxError): class RpccSyntaxError(SyntaxError):
label = "syntax error" label = "syntax error"
def __init__(self, location: FileLocation) -> None: def __init__(self, location: FilePosition) -> None:
self.location = location self.location = location
def __str__(self): def __str__(self):
return f"{self.location.filename}:{self.location.line}:{self.location.column}: error: {self.label}" return f"{self.location.filename}:{self.location.line}:{self.location.column}: error: {self.label}"
class UnexpectedToken(RpccError): class UnexpectedToken(RpccSyntaxError):
label = "unexpected token" label = "unexpected token"
def __init__(self, location: FileLocation, token, expected, terminals): def __init__(self, location: FilePosition, token, expected, terminals):
super().__init__(location) super().__init__(location)
self.token = token self.token = token
self.expected_tokens = expected self.expected_tokens = expected
...@@ -49,18 +67,19 @@ class UnexpectedToken(RpccError): ...@@ -49,18 +67,19 @@ class UnexpectedToken(RpccError):
f"{self._format_expected(self.expected_tokens, self.terminals)}") f"{self._format_expected(self.expected_tokens, self.terminals)}")
class UnexpectedCharacters(RpccError): class UnexpectedCharacters(RpccSyntaxError):
label = "unexpected character" label = "unexpected character"
def __str__(self):
return (super().__str__() + "\n" +
get_context(self.location))
class UnexpectedEOF(RpccError): class UnexpectedEOF(RpccSyntaxError):
label = "unexpected End-of-File." label = "unexpected End-of-File."
def __init__(self):
pass
class MissingRpcDefinition(RpccError): class MissingRpcDefinition(RpccSyntaxError):
label = "missing rpc definition" label = "missing rpc definition"
def __str__(self): def __str__(self):
...@@ -68,7 +87,7 @@ class MissingRpcDefinition(RpccError): ...@@ -68,7 +87,7 @@ class MissingRpcDefinition(RpccError):
get_context(self.location)) get_context(self.location))
class MissingRpcName(RpccError): class MissingRpcName(RpccSyntaxError):
label = "missing rpc name" label = "missing rpc name"
def __str__(self): def __str__(self):
...@@ -76,7 +95,7 @@ class MissingRpcName(RpccError): ...@@ -76,7 +95,7 @@ class MissingRpcName(RpccError):
get_context(self.location)) get_context(self.location))
class EmptyRpcDefinition(RpccError): class EmptyRpcDefinition(RpccSyntaxError):
label = "rpc definition is empty" label = "rpc definition is empty"
def __str__(self): def __str__(self):
...@@ -84,11 +103,11 @@ class EmptyRpcDefinition(RpccError): ...@@ -84,11 +103,11 @@ class EmptyRpcDefinition(RpccError):
get_context(self.location)) get_context(self.location))
class RpcRedefinition(RpccError): class RpcRedefinition(RpccSyntaxError):
label = "redefinition of rpc" label = "redefinition of rpc"
prev_label = "previously defined here" prev_label = "previously defined here"
def __init__(self, location: FileLocation, name: str, prev_definition: FileLocation): def __init__(self, location: FilePosition, name: str, prev_definition: FilePosition):
super().__init__(location) super().__init__(location)
self.name = name self.name = name
self.prev = prev_definition self.prev = prev_definition
...@@ -100,7 +119,7 @@ class RpcRedefinition(RpccError): ...@@ -100,7 +119,7 @@ class RpcRedefinition(RpccError):
get_context(self.prev)) get_context(self.prev))
def get_context(location: FileLocation, span: int = 40) -> str: def get_context(location: FilePosition, span: int = 40) -> str:
"""Returns a pretty string pinpointing the error in the text, """Returns a pretty string pinpointing the error in the text,
with span amount of context characters around it. with span amount of context characters around it.
""" """
......
start : [package] rpc_list
package : "package" DOTTED_NAME EOS
rpc_list : ( rpc )+
rpc : "rpc" rpc_name "{" [rpc_id] [include_if] (rpc_args [rpc_retvals] | [rpc_args] rpc_retvals) "}" EOS
rpc_id : "id:" INT EOS
?include_if : "include_if:" (defined | not_defined) EOS
defined : "compiler_defines" "(" NAME ")" -> ifdef
not_defined : "compiler_not_defines" "(" NAME ")" -> ifndef
rpc_name : NAME
rpc_args : "arguments" "{" ( var | )+ "}" EOS
rpc_retvals : "returns" "{" ( var | )+ "}" EOS
var : type NAME EOS
type : "bool" -> bool
| "double" -> double
| "float" -> float
| "int8" -> int8
| "int16" -> int16
| "int32" -> int32
| "int64" -> int64
| "uint8" -> uint8
| "uint16" -> uint16
| "uint32" -> uint32
| "uint64" -> uint64
| "csize" -> csize
| "string" -> string
| "exposed_buffer" -> exposed_buffer
DOTTED_NAME : NAME ( "." NAME)*
COMMENT : /#+[^\n]*/
EOS : ";"
%import common.CNAME -> NAME
%import common.WS
%import common.INT
%ignore WS
%ignore COMMENT
%ignore ";"
import string # Copyright 2021-2022, Barcelona Supercomputing Center (BSC), Spain
from typing import List #
# This software was partially supported by the EuroHPC-funded project ADMIRE
import lark # (Project ID: 956748, https://www.admire-eurohpc.eu).
#
# This file is part of rpcc.
#
# rpcc is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
#
# rpcc is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License along with rpcc. If not, see
# <https://www.gnu.org/licenses/>.
#
# SPDX-License-Identifier: GPL-3.0-or-later
import itertools
from abc import ABC, abstractmethod
from typing import List, Any, Optional
class Argument: class Argument:
"""A remote procedure argument. """A remote procedure argument.
Parameters: Parameters:
argname: a string with the argument's name. id: a string with the argument's name.
typename: a string with the argument's type. typeinfo: an object instance with the argument's type information.
Example: Example:
>>> Argument("foobar", "int") >>> Argument("foobar", Integer(32, True))
Argument(...) Argument(...)
""" """
argname: str id: str
typename: str typeinfo: Any
def __init__(self, argname: str, typename: str) -> None: def __init__(self, id: str, typeinfo: Any) -> None:
self.argname = argname self.id = id
self.typename = typename self.typeinfo = typeinfo
def __repr__(self) -> str: def __repr__(self) -> str:
return f"Argument(argname='{self.argname}', typename='{self.typename}')" return f"Argument(id='{self.id}', typeinfo='{self.typeinfo}')"
class ReturnVariable: class ReturnValue:
"""A remote procedure return variable. """A remote procedure return value.
Parameters: Parameters:
varname: a string with the return variable's name. id: a string with the return variable's name.
typename: a string with the return variable's type. typeinfo: an object instance with the argument's type information.
Example: Example:
>>> ReturnVariable("barbaz", "int") >>> ReturnValue("barbaz", Integer(32, True))
ReturnVariable(...) ReturnValue(...)
""" """
def __init__(self, varname: str, typename: str) -> None: id: str
self.varname = varname typeinfo: Any
self.typename = typename
def __init__(self, id: str, typeinfo: Any) -> None:
self.id = id
self.typeinfo = typeinfo
def __repr__(self) -> str: def __repr__(self) -> str:
return f"ReturnVariable(argname='{self.varname}', typename='{self.typename}')" return f"ReturnVariable(id='{self.id}', typeinfo='{self.typeinfo}')"
class ArgumentList:
"""A list of RPC arguments."""
args: List[Argument]
def __init__(self, args: List[Argument]):
self.args = args
def __iter__(self):
for arg in self.args:
yield arg
def __len__(self):
return len(self.args)
class ReturnValueList:
"""A list of RPC return values."""
retvals: List[ReturnValue]
def __init__(self, retvals: List[ReturnValue]):
self.retvals = retvals
def __iter__(self):
for retval in self.retvals:
yield retval
def __len__(self):
return len(self.retvals)
class ConditionalDefinition(ABC):
@property
@abstractmethod
def start_keyword(self):
raise NotImplemented
@property
@abstractmethod
def end_keyword(self):
raise NotImplemented
@property
@abstractmethod
def symbol(self):
raise NotImplemented
@symbol.setter
@abstractmethod
def symbol(self, value):
raise NotImplemented
class RemoteProcedure: class RemoteProcedure:
...@@ -52,44 +126,62 @@ class RemoteProcedure: ...@@ -52,44 +126,62 @@ class RemoteProcedure:
Parameters: Parameters:
name: a string with the remote procedure's name. name: a string with the remote procedure's name.
args: a list of Arguments containing the remote procedure's arguments. args: an ArgumentList containing the remote procedure's arguments.
retval: a ReturnVariable containing the remote procedure's return variable. retvals: a ReturnValueList containing the remote procedure's return variables.
Example: Example:
>>> RemoteProcedure("send_message", [Argument("message", "string")], ReturnVariable("retval", "uint32")) >>> RemoteProcedure("send_message",
>>> ArgumentList([Argument("message", "string")],
>>> ReturnValueList([ReturnValue("retval", "uint32")])))
RemoteProcedure(...) RemoteProcedure(...)
""" """
def __init__(self, name: str, args: List[Argument], retval: ReturnVariable) -> None: id: int
self.id = 42 name: str
args: ArgumentList
retvals: ReturnValueList
include_if_expr: Optional[ConditionalDefinition]
id_iter = itertools.count()
def __init__(self, id: int, name: str, args: ArgumentList, retvals: ReturnValueList,
include_if_expr: Optional[ConditionalDefinition]) -> None:
self.id = id
self.name = name self.name = name
self.args = args self.args = args
self.retval = retval self.retvals = retvals
self.include_if_expr = include_if_expr
def __repr__(self) -> str: def __repr__(self) -> str:
return f"RemoteProcedure(name='{self.name}', args={self.args}, retval={self.retval})" return f"RemoteProcedure(id={self.id}, name='{self.name}', args={self.args}, retvals={self.retvals})"
@staticmethod
def generate_id(margo_compatible: bool = False) -> int:
if not margo_compatible:
return next(RemoteProcedure.id_iter)
raise NotImplemented
# TODO: generate a margo compatible id using the rpc name
class IR:
"""The Intermediate Representation for our source-to-source compiler. class IRTree:
"""The Intermediate Representation Tree for our source-to-source compiler.
Parameters: Parameters:
remote_procedures: a list of the remote procedures defined in the iput file. rpcs: a list of the remote procedures defined in the input file.
Example: Example:
>>> IR([ >>> IRTree([
>>> RemoteProcedure("send_message", [Argument("message", "string")], ReturnVariable("retval", "uint32")) >>> RemoteProcedure("send_message",
>>> RemoteProcedure("shutdown", [], ReturnVariable("retval", "uint32")) >>> ArgumentList([Argument("message", "string")]),
>>> ReturnValueList([ReturnValue("retval", "uint32"))])
>>> RemoteProcedure("shutdown", ArgumentList([]), ReturnValueList([ReturnValue("retval", "uint32")]))
>>> ] >>> ]
IR(...) Tree(...)
""" """
remote_procedures: List[RemoteProcedure] package: Optional[str]
tree = lark.Tree rpcs: List[RemoteProcedure]
def __init__(self, remote_procedures: List[RemoteProcedure]):
self.remote_procedures = remote_procedures
def __iter__(self): def __init__(self, package: Optional[str], rpcs: List[RemoteProcedure]):
for rpc in self.remote_procedures: self.package = package
yield rpc self.rpcs = rpcs
# Copyright 2021-2022, Barcelona Supercomputing Center (BSC), Spain
#
# This software was partially supported by the EuroHPC-funded project ADMIRE
# (Project ID: 956748, https://www.admire-eurohpc.eu).
#
# This file is part of rpcc.
#
# rpcc is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
#
# rpcc is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License along with rpcc. If not, see
# <https://www.gnu.org/licenses/>.
#
# SPDX-License-Identifier: GPL-3.0-or-later
import pathlib import pathlib
class FileLocation: class FilePosition:
"""An object representing a specific location in the parsed text""" """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: def __init__(self, filename: pathlib.Path, text: str, line: int, column: int, pos_in_stream: int) -> None:
......
# Copyright 2021-2022, Barcelona Supercomputing Center (BSC), Spain
#
# This software was partially supported by the EuroHPC-funded project ADMIRE
# (Project ID: 956748, https://www.admire-eurohpc.eu).
#
# This file is part of rpcc.
#
# rpcc is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
#
# rpcc is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License along with rpcc. If not, see
# <https://www.gnu.org/licenses/>.
#
# SPDX-License-Identifier: GPL-3.0-or-later
import pathlib import pathlib
from typing import Tuple
from lark import Lark, Tree from lark import Lark, Tree
import lark.exceptions import lark.exceptions
from loguru import logger from loguru import logger
...@@ -7,30 +27,7 @@ from rpcc.exceptions import ( ...@@ -7,30 +27,7 @@ from rpcc.exceptions import (
EmptyRpcDefinition, MissingRpcDefinition, MissingRpcName, UnexpectedToken, EmptyRpcDefinition, MissingRpcDefinition, MissingRpcName, UnexpectedToken,
UnexpectedCharacters, UnexpectedEOF UnexpectedCharacters, UnexpectedEOF
) )
from rpcc.meta import FileLocation from rpcc.meta import FilePosition
GRAMMAR = r"""
start : ( rpc )+
rpc : "rpc" rpc_name "{" (rpc_args rpc_return | rpc_args | rpc_return) "}"
rpc_name : NAME
rpc_args : "arguments" "{" ( var | )+ "}"
rpc_return : "returns" "{" ( var | ) "}"
var : type NAME
type : "double" -> double
| "float" -> float
| "int32" -> int32
| "uint32" -> uint32
| "string" -> string
| "exposed_buffer" -> exposed_buffer
COMMENT : /#+[^\n]*/
EOS : ";"+
%import common.CNAME -> NAME
%import common.WS
%ignore WS
%ignore COMMENT
%ignore ";"
"""
INVALID_INPUT_EXAMPLES = { INVALID_INPUT_EXAMPLES = {
EmptyRpcDefinition: ['rpc foo {}', EmptyRpcDefinition: ['rpc foo {}',
...@@ -51,45 +48,41 @@ class Parser: ...@@ -51,45 +48,41 @@ class Parser:
"""The main parser class""" """The main parser class"""
def __init__(self) -> None: def __init__(self) -> None:
self.input_file = None self.parser = Lark.open_from_package("rpcc", "grammars/rpcc.lark", propagate_positions=True, parser='lalr')
self.text = None
self.parser = Lark(GRAMMAR, propagate_positions=True, parser='lalr')
def parse(self, input_file: pathlib.Path) -> Tree: def parse(self, input_file: pathlib.Path) -> Tuple[Tree, str]:
"""Parse an input file. """Parse an input file.
:param input_file: A `pathlib.Path` containing the path to the RPC description file to parse. :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. :return: An AST tree with a representation of the parsed text.
""" """
self.input_file = input_file
try: try:
file = open(self.input_file, "r", encoding="utf-8") file = open(input_file, "r", encoding="utf-8")
except (FileNotFoundError, EnvironmentError) as err: except (FileNotFoundError, EnvironmentError) as err:
logger.error(f"Error parsing file: {err}") logger.error(f"Error parsing file: {err}")
raise raise
else: else:
with file: with file:
self.text = file.read() text = file.read()
try: try:
return self.parser.parse(self.text) return self.parser.parse(text), text
except lark.exceptions.UnexpectedInput as u: except lark.exceptions.UnexpectedInput as u:
location = FileLocation(self.input_file, self.text, u.line, u.column, u.pos_in_stream) location = FilePosition(input_file, text, u.line, u.column, u.pos_in_stream)
exc_class = u.match_examples(self.parser.parse, INVALID_INPUT_EXAMPLES, use_accepts=True) exc_class = u.match_examples(self.parser.parse, INVALID_INPUT_EXAMPLES, use_accepts=True)
if exc_class: if exc_class:
raise exc_class(location) raise exc_class(location) from None
if isinstance(u, lark.exceptions.UnexpectedToken): if isinstance(u, lark.exceptions.UnexpectedToken):
raise UnexpectedToken(location, u.token, u.expected, self.parser.terminals) raise UnexpectedToken(location, u.token, u.expected, self.parser.terminals) from None
if isinstance(u, lark.exceptions.UnexpectedCharacters): if isinstance(u, lark.exceptions.UnexpectedCharacters):
raise UnexpectedCharacters(location) raise UnexpectedCharacters(location) from None
if isinstance(u, lark.exceptions.UnexpectedEOF): if isinstance(u, lark.exceptions.UnexpectedEOF):
raise UnexpectedEOF() raise UnexpectedEOF(location) from None
raise u raise u
# Copyright 2021-2022, Barcelona Supercomputing Center (BSC), Spain
#
# This software was partially supported by the EuroHPC-funded project ADMIRE
# (Project ID: 956748, https://www.admire-eurohpc.eu).
#
# This file is part of rpcc.
#
# rpcc is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
#
# rpcc is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License along with rpcc. If not, see
# <https://www.gnu.org/licenses/>.
#
# SPDX-License-Identifier: GPL-3.0-or-later
import pathlib import pathlib
from rpcc.transformers import FileTransformer, FileTransformerFactory from rpcc.transformers import FileTransformer, FileTransformerFactory
class Rpcc: class Rpcc:
input_file: pathlib.Path """The main class for the DSL compiler."""
output_language: str
transformer: FileTransformer transformer: FileTransformer
def __init__(self, input_file: pathlib.Path, output_language: str): def __init__(self, input_file: pathlib.Path, copyright_file: pathlib.Path, output_language: str,
self.input_file = input_file header_outdir: pathlib.Path, impl_outdir: pathlib.Path) -> None:
self.output_language = output_language self.transformer = FileTransformerFactory.create_transformer(
self.transformer = FileTransformerFactory().create_transformer(output_language) output_language,
input_file,
copyright_file,
header_outdir,
impl_outdir)
def transform(self): def transform(self) -> None:
self.transformer.transform(self.input_file) self.transformer.transform()
# Copyright 2021-2022, Barcelona Supercomputing Center (BSC), Spain
#
# This software was partially supported by the EuroHPC-funded project ADMIRE
# (Project ID: 956748, https://www.admire-eurohpc.eu).
#
# This file is part of rpcc.
#
# rpcc is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
#
# rpcc is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License along with rpcc. If not, see
# <https://www.gnu.org/licenses/>.
#
# SPDX-License-Identifier: GPL-3.0-or-later
from .base import FileTransformer from .base import FileTransformer
from .factory import FileTransformerFactory from .factory import FileTransformerFactory
import pathlib # Copyright 2021-2022, Barcelona Supercomputing Center (BSC), Spain
#
# This software was partially supported by the EuroHPC-funded project ADMIRE
# (Project ID: 956748, https://www.admire-eurohpc.eu).
#
# This file is part of rpcc.
#
# rpcc is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
#
# rpcc is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License along with rpcc. If not, see
# <https://www.gnu.org/licenses/>.
#
# SPDX-License-Identifier: GPL-3.0-or-later
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
class FileTransformer(ABC): class FileTransformer(ABC):
"""FileTransformer interface""" """The FileTransformer interface."""
@abstractmethod @abstractmethod
def transform(self, input_file: pathlib.Path): def transform(self):
return NotImplemented return NotImplemented
# Copyright 2021-2022, Barcelona Supercomputing Center (BSC), Spain
#
# This software was partially supported by the EuroHPC-funded project ADMIRE
# (Project ID: 956748, https://www.admire-eurohpc.eu).
#
# This file is part of rpcc.
#
# rpcc is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
#
# rpcc is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License along with rpcc. If not, see
# <https://www.gnu.org/licenses/>.
#
# SPDX-License-Identifier: GPL-3.0-or-later
from .transformer import Cxx17FileTransformer from .transformer import Cxx17FileTransformer
This diff is collapsed.
from string import Template
HEADER_TEMPLATE = Template("""\
#infdef ${header_guard_name}
#define ${header_guard_name}
// Generated by rpcc. DO NOT EDIT!
// source: ${input_file}
""")
FOOTER_TEMPLATE = Template("""\
#undef HG_GEN_PROC_NAME
#endif // ${header_guard_name}
""")
PRC_PROLOG_TEMPLATE = Template("""\
//==============================================================================
// definitions for example_rpcs::${rpc_name}
namespace hermes { namespace detail {
// Generate Mercury types and serialization functions (field names match
// those defined in ${rpc_name}::input and ${rpc_name}::output). These
// definitions are internal and should not be used directly. Classes
// ${rpc_name}::input and ${rpc_name}::output are provided for public use.
MERCURY_GEN_PROC(${rpc_name}_in_t,
((hg_const_string_t) (message)))
MERCURY_GEN_PROC(${rpc_name}_out_t,
((int32_t) (retval)))
}} // namespace hermes::detail
""")
RPC_BODY_TEMPLATE = Template("""\
struct ${rpc_name} {
// forward declarations of public input/output types for this RPC
class input;
class output;
// traits used so that the engine knows what to do with the RPC
using self_type = ${rpc_name};
using handle_type = hermes::rpc_handle<self_type>;
using input_type = input;
using output_type = output;
using mercury_input_type = hermes::detail::${rpc_name}_in_t;
using mercury_output_type = hermes::detail::${rpc_name}_out_t;
// RPC public identifier"
constexpr static const uint16_t public_id = ${rpc_id};
// RPC internal Mercury identifier
constexpr static const uint16_t mercury_id = public_id;
// RPC name
constexpr static const auto name = "${rpc_name}";
// requires response?
constexpr static const auto requires_response = true;
};
""")
# Copyright 2021-2022, Barcelona Supercomputing Center (BSC), Spain
#
# This software was partially supported by the EuroHPC-funded project ADMIRE
# (Project ID: 956748, https://www.admire-eurohpc.eu).
#
# This file is part of rpcc.
#
# rpcc is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
#
# rpcc is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License along with rpcc. If not, see
# <https://www.gnu.org/licenses/>.
#
# SPDX-License-Identifier: GPL-3.0-or-later
import pathlib import pathlib
from typing import List, Tuple from typing import List, Tuple, Optional, Dict
import lark import lark
from lark import v_args from lark import v_args, Discard
from loguru import logger
import rpcc.parser import rpcc.parser
from rpcc.console import console from rpcc.exceptions import RpcRedefinition
from rpcc.exceptions import RpcRedefinition, RpccError from rpcc.transformers.cxx17.formatter import FileFormatter
from rpcc.meta import FileLocation from rpcc.meta import FilePosition
from rpcc.parser import Parser from rpcc.parser import Parser
from rpcc.ir import RemoteProcedure, Argument, ReturnVariable, IR from rpcc.ir import RemoteProcedure, Argument, ReturnValue, IRTree, ArgumentList, ReturnValueList, ConditionalDefinition
from rpcc.transformers import FileTransformer from rpcc.transformers import FileTransformer
from .types import Bool, Double, Float, Integer, String, ExposedBuffer, CSize
_cxx_type_map = {
"double": "double",
"float": "float",
"int32": "int32_t",
"uint32": "uint32_t",
"string": "std::string",
"exposed_buffer": "hermes::exposed_memory"
}
class _RpcInfo:
pos_in_text: FilePosition
class _Transformer(lark.Transformer): def __init__(self, pos_in_text: FilePosition):
self.pos_in_text = pos_in_text
def __init__(self, parser: Parser):
super().__init__()
self.rpcs = dict()
self.parser = parser
# variable names can be coverted directly into strings class _CxxCompilerDefines(ConditionalDefinition):
NAME = str _symbol: str
# variables can be converted directly into tuples def __init__(self, symbol):
var = tuple self._symbol = symbol
def start(self, children) -> IR: @property
"""Transform the AST tree generated by Lark into an `IR` instance, that we can use to generate the output files def start_keyword(self):
""" return "#ifdef"
return IR(children)
def rpc_args(self, children: List[Tuple[str, str]]) -> List[Argument]: @property
"""Transform a list of tuples returned by Lark into a list of `rpc.Arguments`. The tuples are implicitly def end_keyword(self):
constructed by Lark by transforming all the `var` nodes in the AST that have this particular `rpc_args` node return "#endif"
as a parent.
:param children: A list of tuples describing the rpc arguments. @property
:return: A list of `rpc.Arguments` def symbol(self):
return self._symbol
@symbol.setter
def symbol(self, value):
self._symbol = value
class _CxxCompilerNotDefines(ConditionalDefinition):
_symbol: str
def __init__(self, symbol):
self._symbol = symbol
@property
def start_keyword(self):
return "#ifndef"
@property
def end_keyword(self):
return "#endif"
@property
def symbol(self):
return self._symbol
@symbol.setter
def symbol(self, value):
self._symbol = value
class _AstTransformer(lark.Transformer):
input_file: pathlib.Path
text: str
rpc_info: Dict[str, _RpcInfo]
def __init__(self, input_file: pathlib.Path, text: str):
super().__init__()
self.input_file = input_file
self.text = text
self.rpc_info = dict()
@v_args(inline=True)
def start(self, package: Optional[str], rpcs: List[RemoteProcedure]) -> IRTree:
"""Transform the AST tree generated by Lark into an `IR` instance, that we can use to generate the output files
""" """
return [Argument(varname, typename) for (typename, varname) in children]
return IRTree(package, rpcs)
@v_args(inline=True) @v_args(inline=True)
def rpc_return(self, t: Tuple[str, str]) -> ReturnVariable: def package(self, name: str) -> str:
"""Transform a tuple returned by Lark into a `ReturnVariable`. The tuple is implictly constructed by Lark by return name
transforming the `var` node under this particular `rpc_return` node.
:param t: A tuple describing the rpc return value # rpc_list can be converted directly into list
:return: A `rpc.ReturnVariable` rpc_list = list
"""
(typename, varname) = t
return ReturnVariable(varname, typename)
@v_args(inline=True, meta=True) @v_args(inline=True, meta=True)
def rpc(self, meta: lark.tree.Meta, name: str, args: List[Argument] = None, def rpc(self, meta: lark.tree.Meta, name: str, id: Optional[int], include_if_expr: Optional[ConditionalDefinition],
retval: ReturnVariable = None) -> RemoteProcedure: args: Optional[ArgumentList], retvals: Optional[ReturnValueList]) -> RemoteProcedure:
"""Transform a `rpc` node from the AST into a `RemoteProcedure` object from a name, a list of `Argument`s and a """Transform a `rpc` node from the AST into a `RemoteProcedure` object from a name, a list of `Argument`s and a
`ReturnVariable`. `ReturnVariable`.
:param meta: Metainformation about the current Token such as line, column, start_pos, etc. :param meta: Metainformation about the current Token such as line, column, start_pos, etc.
:param name: A name for the remote procedure. :param name: A name for the remote procedure.
:param args: A list of `Argument` describing the input arguments for the remote procedure. :param id: An optional numeric id the remote procedure.
:param retval: A `ReturnValue` describing the remote procedure's return value. :param args: An `ArgumentList` describing the input arguments for the remote procedure. `ArgumentList` may be
empty if the input file defined an empty `arguments` clause, or `None` if not defined.
:param retvals: A `ReturnValueList` describing the remote procedure's return values. `ReturnValueList` may be
empty if the input file defined an empty `returns` clause, or `None` if not defined.
:param include_if_expr: A C/C++ preprocessor expression specifying when this rpc should be included in
compilation.
:return: A `RemoteProcedure` object describing the remote procedure. :return: A `RemoteProcedure` object describing the remote procedure.
""" """
location = FileLocation(self.parser.input_file, self.parser.text, meta.line, meta.column, meta.start_pos) if not id:
if name in self.rpcs: id = RemoteProcedure.generate_id(margo_compatible=False)
prev_location = self.rpcs[name]
raise RpcRedefinition(location, name, prev_location) if not args:
args = ArgumentList([])
if not retvals:
retvals = ReturnValueList([])
pos = FilePosition(self.input_file, self.text, meta.line, meta.column, meta.start_pos)
if name in self.rpc_info:
prev_pos = self.rpc_info[name].pos_in_text
raise RpcRedefinition(pos, name, prev_pos)
else: else:
self.rpcs[name] = location self.rpc_info[name] = _RpcInfo(pos)
return RemoteProcedure(name, args, retval) return RemoteProcedure(id, name, args, retvals, include_if_expr)
@v_args(inline=True) @v_args(inline=True)
def rpc_name(self, name: str) -> str: def rpc_name(self, name: str) -> str:
...@@ -93,41 +157,141 @@ class _Transformer(lark.Transformer): ...@@ -93,41 +157,141 @@ class _Transformer(lark.Transformer):
""" """
return name return name
def double(self, _) -> str: @v_args(inline=True)
return _cxx_type_map["double"] def rpc_id(self, id: int) -> int:
"""Transform a `rpc_id` node from the AST into its corresponding id. The id is extracted from the AST
nodes' value.
:param id: An integer value from the `rpc_id` node.
:return: An id for the remote procedure.
"""
return id
@v_args(inline=True)
def include_if(self, expr) -> ConditionalDefinition:
return expr
@v_args(inline=True)
def ifdef(self, symbol) -> ConditionalDefinition:
return _CxxCompilerDefines(symbol)
@v_args(inline=True)
def ifndef(self, symbol) -> ConditionalDefinition:
return _CxxCompilerNotDefines(symbol)
def rpc_args(self, children: List[Tuple[str, str]]) -> ArgumentList:
"""Transform a list of tuples returned by Lark into a list of `rpc.Arguments`. The tuples are implicitly
constructed by Lark by transforming all the `var` nodes in the AST that have this particular `rpc_args` node
as a parent.
:param children: A list of tuples describing the rpc arguments.
:return: A list of `rpc.Arguments`
"""
return ArgumentList([Argument(id, typeinfo) for (typeinfo, id) in children])
def rpc_retvals(self, children: List[Tuple[str, str]]) -> ReturnValueList:
"""Transform a tuple returned by Lark into a `ReturnVariable`. The tuple is implictly constructed by Lark by
transforming the `var` node under this particular `rpc_return` node.
:param children: A list of tuples describing the rpc return values.
:return: A list of `rpc.ReturnVariables`
"""
return ReturnValueList([ReturnValue(id, typeinfo) for (typeinfo, id) in children])
# variables can be converted directly into tuples
var = tuple
def bool(self, _) -> Bool:
return Bool()
def double(self, _) -> Double:
return Double()
def float(self, _) -> Float:
return Float()
def int8(self, _) -> Integer:
return Integer(8, False)
def int16(self, _) -> Integer:
return Integer(16, False)
def int32(self, _) -> Integer:
return Integer(32, False)
def int64(self, _) -> Integer:
return Integer(64, False)
def uint8(self, _) -> Integer:
return Integer(8, True)
def float(self, _) -> str: def uint16(self, _) -> Integer:
return _cxx_type_map["float"] return Integer(16, True)
def int32(self, _) -> str: def uint32(self, _) -> Integer:
return _cxx_type_map["int32"] return Integer(32, True)
def uint32(self, _) -> str: def uint64(self, _) -> Integer:
return _cxx_type_map["uint32"] return Integer(64, True)
def string(self, _) -> str: def csize(self, _) -> CSize:
return _cxx_type_map["string"] return CSize()
def exposed_buffer(self, _) -> str: def string(self, _) -> String:
return _cxx_type_map["exposed_buffer"] return String()
def exposed_buffer(self, _) -> ExposedBuffer:
return ExposedBuffer()
# variable names can be coverted directly into strings
NAME = str
DOTTED_NAME = str
def EOS(self, children):
return Discard
class Cxx17FileTransformer(FileTransformer): class Cxx17FileTransformer(FileTransformer):
input_file: pathlib.Path
copyright_file: pathlib.Path
header_outdir: pathlib.Path
impl_outdir: pathlib.Path
parser: rpcc.parser.Parser parser: rpcc.parser.Parser
transformer: _Transformer ast_transformer: _AstTransformer
def __init__(self): def _get_output_paths(self) -> Tuple[pathlib.Path, pathlib.Path]:
prefix = self.input_file.stem.replace('.', '_')
return (pathlib.Path(self.header_outdir, f"{prefix}-rpcc.hpp"),
pathlib.Path(self.impl_outdir, f"{prefix}-rpcc.cpp"))
def __init__(self, input_file: pathlib.Path, copyright_file: pathlib.Path, header_outdir: pathlib.Path,
impl_outdir: pathlib.Path):
self.input_file = input_file
self.copyright_file = copyright_file
self.header_outdir = header_outdir
self.impl_outdir = impl_outdir
self.parser = Parser() self.parser = Parser()
self.transformer = _Transformer(self.parser)
def transform(self, input_file: pathlib.Path): def transform(self) -> None:
ast = self.parser.parse(input_file) ast, text = self.parser.parse(self.input_file)
try: try:
ir = self.transformer.transform(ast) ir = _AstTransformer(self.input_file, text).transform(ast)
except lark.exceptions.VisitError as e: except lark.exceptions.VisitError as e:
raise e.orig_exc raise e.orig_exc
for n in ir: header_file, impl_file = self._get_output_paths()
console.print(n) header_text, impl_text = FileFormatter(self.input_file, self.copyright_file, header_file, impl_file).format(ir)
try:
with open(header_file, "w") as f:
f.write(header_text)
except OSError as e:
raise OSError(f"Error writing header file:\n {e}") from None
try:
with open(impl_file, "w") as f:
f.write(impl_text)
except OSError as e:
raise OSError(f"Error writing implementation file:\n {e}") from None
# Copyright 2021-2022, Barcelona Supercomputing Center (BSC), Spain
#
# This software was partially supported by the EuroHPC-funded project ADMIRE
# (Project ID: 956748, https://www.admire-eurohpc.eu).
#
# This file is part of rpcc.
#
# rpcc is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
#
# rpcc is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License along with rpcc. If not, see
# <https://www.gnu.org/licenses/>.
#
# SPDX-License-Identifier: GPL-3.0-or-later
from abc import ABCMeta, abstractmethod
from typing import Optional, Dict, Type, Callable
class NameSpace:
id: str
def __init__(self, id='') -> None:
self.id = id
def __str__(self) -> str:
return self.id
def __repr__(self) -> str:
return f"NameSpace(id='{self.id}')"
class CxxType(metaclass=ABCMeta):
@abstractmethod
def is_fundamental(self) -> bool:
raise NotImplemented
@abstractmethod
def member_operator(self) -> str:
raise NotImplemented
@abstractmethod
def hg_type(self) -> str:
raise NotImplemented
class FundamentalType(CxxType, metaclass=ABCMeta):
def is_fundamental(self) -> bool:
return True
def member_operator(self) -> str:
raise ValueError("C++ fundamental types don't have member operators!")
def hg_type(self) -> str:
raise NotImplemented
def __repr__(self) -> str:
return f"{self.__class__.__name__}()"
class CompoundType(CxxType, metaclass=ABCMeta):
classname: str
namespace: Optional[NameSpace]
def __init__(self, classname: str, namespace: Optional[NameSpace] = NameSpace()) -> None:
self.classname = classname
self.namespace = namespace
def is_fundamental(self) -> bool:
return False
def member_operator(self) -> str:
return "."
def hg_type(self) -> str:
raise NotImplemented
def __str__(self) -> str:
return '::'.join(filter(None, [str(self.namespace), self.classname]))
def __repr__(self) -> str:
return f"{self.__class__.__name__}(classname='{self.classname}', namespace={self.namespace.__repr__()})"
class Void(FundamentalType):
def __str__(self) -> str:
return "void"
def hg_type(self) -> str:
return "void"
class Bool(FundamentalType):
def __str__(self) -> str:
return "bool"
def hg_type(self) -> str:
return "hg_bool_t"
class Integer(FundamentalType):
width: int
unsigned: bool
def __init__(self, width: int, unsigned: bool) -> None:
self.width = width
self.unsigned = unsigned
def hg_type(self) -> str:
return f"hg_{self}"
def __str__(self) -> str:
prefix = "u" if self.unsigned else ""
return f"{prefix}int{self.width}_t"
def __repr__(self) -> str:
return f"{self.__class__.__name__}(width={self.width}, unsigned={self.unsigned})"
class CSize(FundamentalType):
def hg_type(self) -> str:
return "hg_size_t"
def __str__(self) -> str:
return "size_t"
class Float(FundamentalType):
def hg_type(self) -> str:
return str(self)
def __str__(self) -> str:
return "float"
class Double(FundamentalType):
def hg_type(self) -> str:
return str(self)
def __str__(self) -> str:
return "double"
class Object(CompoundType):
"""A generic object."""
pass
class String(CompoundType):
def __init__(self) -> None:
super().__init__("string", NameSpace("std"))
def hg_type(self) -> str:
return "hg_const_string_t"
class ExposedBuffer(CompoundType):
def __init__(self) -> None:
super().__init__("exposed_memory", NameSpace("hermes"))
def hg_type(self) -> str:
return "hg_bulk_t"
class Pointer(CompoundType):
to_type: CxxType
def __init__(self, to_type: CxxType) -> None:
self.to_type = to_type
def member_operator(self) -> str:
return "->"
def __str__(self) -> str:
return f"{self.to_type} *"
class ReferenceType(CompoundType):
to_type: CxxType
def __init__(self, to_type: CxxType) -> None:
self.to_type = to_type
def __str__(self) -> str:
return f"{self.to_type}&"
class Identifier:
id: str
typeinfo: CxxType
parent: Optional["Identifier"]
def __init__(self, id: str, typeinfo: CxxType, parent: Optional["Identifier"] = None) -> None:
self.id = id
self.typeinfo = typeinfo
self.parent = parent
def member_operator(self) -> str:
return self.typeinfo.member_operator()
def as_declarator(self) -> str:
return f"{self.typeinfo} {self.id}"
def as_member_of(self) -> str:
if not self.parent:
return str(self.id)
return f"{self.parent}{self.parent.member_operator()}{self.id}"
def as_hg_conversion(self) -> str:
valid_hg_conversions: Dict[Type[CxxType], Callable[[Identifier], str]] = {
String: lambda a: f"{a}.c_str()",
ExposedBuffer: lambda a: f"hg_bulk_t({a})",
}
if self.typeinfo.is_fundamental():
return self.id
if type(self.typeinfo) in valid_hg_conversions:
return valid_hg_conversions[type(self.typeinfo)](self)
raise ValueError(f"No known conversion for compound type '{self.typeinfo}'")
def __str__(self) -> str:
return self.id
def __repr__(self) -> str:
return f"{self.__class__.__name__}(" \
f"id='{self.id}', typeinfo={self.typeinfo.__repr__()}, parent={self.parent.__repr__() or 'None'})"
class LValueReference(Identifier):
def __init__(self, id: str, typeinfo: CxxType, parent: Optional["Identifier"] = None) -> None:
super().__init__(id, typeinfo, parent)
def as_declarator(self) -> str:
return f"{self.typeinfo}& {self.id}"
class ConstLValueReference(Identifier):
def __init__(self, id: str, typeinfo: CxxType, parent: Optional["Identifier"] = None) -> None:
super().__init__(id, typeinfo, parent)
def as_declarator(self) -> str:
return f"const {self.typeinfo}& {self.id}"
class RValueReference(Identifier):
def __init__(self, id: str, typeinfo: CxxType, parent: Optional["Identifier"] = None) -> None:
super().__init__(id, typeinfo, parent)
def as_declarator(self) -> str:
return f"{self.typeinfo}&& {self.id}"