Commits on Source (2)
# rpcc
A compiler for Mercury/Margo rpcs
\ No newline at end of file
**rpcc** is a Python command line tool that allows developers to easily define and work with remote procedure calls (
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
import sys
import lark.exceptions
# 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 sys
from rpcc import Rpcc
from rpcc.arguments import parse_args
from rpcc.exceptions import RpccError
from rpcc.exceptions import RpccSyntaxError
from rpcc.console import console
from loguru import logger
def main(args=None):
......@@ -15,13 +31,24 @@ def main(args=None):
args = sys.argv[1:]
args = parse_args(args)
# configure console
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:
rpcc.transform()
except RpccError as e:
console.eprint(e)
Rpcc(
args.rpc_proto_file,
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 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 logging
import sys
from pathlib import Path
from rpcc.version import __version__ as rpcc_version
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,
required=False, help=None, metavar=None):
super().__init__(option_strings=option_strings,
......@@ -45,6 +92,31 @@ def parse_args(args) -> argparse.Namespace:
" 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(
"--color-diagnostics",
choices=["never", "always", "auto"],
......@@ -54,10 +126,29 @@ def parse_args(args) -> argparse.Namespace:
"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(
"--version",
"-V",
action=PrintVersion,
action=_PrintVersion,
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 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
......
# 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
# 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 rpcc.meta import FileLocation
from rpcc.meta import FilePosition
class ConfigurationError(ValueError):
......@@ -12,20 +30,20 @@ def assert_config(value, options: Collection, msg="Got %r, expected one of %s"):
raise ConfigurationError(msg % (value, options))
class RpccError(SyntaxError):
class RpccSyntaxError(SyntaxError):
label = "syntax error"
def __init__(self, location: FileLocation) -> None:
def __init__(self, location: FilePosition) -> None:
self.location = location
def __str__(self):
return f"{self.location.filename}:{self.location.line}:{self.location.column}: error: {self.label}"
class UnexpectedToken(RpccError):
class UnexpectedToken(RpccSyntaxError):
label = "unexpected token"
def __init__(self, location: FileLocation, token, expected, terminals):
def __init__(self, location: FilePosition, token, expected, terminals):
super().__init__(location)
self.token = token
self.expected_tokens = expected
......@@ -49,18 +67,19 @@ class UnexpectedToken(RpccError):
f"{self._format_expected(self.expected_tokens, self.terminals)}")
class UnexpectedCharacters(RpccError):
class UnexpectedCharacters(RpccSyntaxError):
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."
def __init__(self):
pass
class MissingRpcDefinition(RpccError):
class MissingRpcDefinition(RpccSyntaxError):
label = "missing rpc definition"
def __str__(self):
......@@ -68,7 +87,7 @@ class MissingRpcDefinition(RpccError):
get_context(self.location))
class MissingRpcName(RpccError):
class MissingRpcName(RpccSyntaxError):
label = "missing rpc name"
def __str__(self):
......@@ -76,7 +95,7 @@ class MissingRpcName(RpccError):
get_context(self.location))
class EmptyRpcDefinition(RpccError):
class EmptyRpcDefinition(RpccSyntaxError):
label = "rpc definition is empty"
def __str__(self):
......@@ -84,11 +103,11 @@ class EmptyRpcDefinition(RpccError):
get_context(self.location))
class RpcRedefinition(RpccError):
class RpcRedefinition(RpccSyntaxError):
label = "redefinition of rpc"
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)
self.name = name
self.prev = prev_definition
......@@ -100,7 +119,7 @@ class RpcRedefinition(RpccError):
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,
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
from typing import List
import lark
# 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 itertools
from abc import ABC, abstractmethod
from typing import List, Any, Optional
class Argument:
"""A remote procedure argument.
Parameters:
argname: a string with the argument's name.
typename: a string with the argument's type.
id: a string with the argument's name.
typeinfo: an object instance with the argument's type information.
Example:
>>> Argument("foobar", "int")
>>> Argument("foobar", Integer(32, True))
Argument(...)
"""
argname: str
typename: str
id: str
typeinfo: Any
def __init__(self, argname: str, typename: str) -> None:
self.argname = argname
self.typename = typename
def __init__(self, id: str, typeinfo: Any) -> None:
self.id = id
self.typeinfo = typeinfo
def __repr__(self) -> str:
return f"Argument(argname='{self.argname}', typename='{self.typename}')"
return f"Argument(id='{self.id}', typeinfo='{self.typeinfo}')"
class ReturnVariable:
"""A remote procedure return variable.
class ReturnValue:
"""A remote procedure return value.
Parameters:
varname: a string with the return variable's name.
typename: a string with the return variable's type.
id: a string with the return variable's name.
typeinfo: an object instance with the argument's type information.
Example:
>>> ReturnVariable("barbaz", "int")
ReturnVariable(...)
>>> ReturnValue("barbaz", Integer(32, True))
ReturnValue(...)
"""
def __init__(self, varname: str, typename: str) -> None:
self.varname = varname
self.typename = typename
id: str
typeinfo: Any
def __init__(self, id: str, typeinfo: Any) -> None:
self.id = id
self.typeinfo = typeinfo
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:
......@@ -52,44 +126,62 @@ class RemoteProcedure:
Parameters:
name: a string with the remote procedure's name.
args: a list of Arguments containing the remote procedure's arguments.
retval: a ReturnVariable containing the remote procedure's return variable.
args: an ArgumentList containing the remote procedure's arguments.
retvals: a ReturnValueList containing the remote procedure's return variables.
Example:
>>> RemoteProcedure("send_message", [Argument("message", "string")], ReturnVariable("retval", "uint32"))
>>> RemoteProcedure("send_message",
>>> ArgumentList([Argument("message", "string")],
>>> ReturnValueList([ReturnValue("retval", "uint32")])))
RemoteProcedure(...)
"""
def __init__(self, name: str, args: List[Argument], retval: ReturnVariable) -> None:
self.id = 42
id: int
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.args = args
self.retval = retval
self.retvals = retvals
self.include_if_expr = include_if_expr
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:
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:
>>> IR([
>>> RemoteProcedure("send_message", [Argument("message", "string")], ReturnVariable("retval", "uint32"))
>>> RemoteProcedure("shutdown", [], ReturnVariable("retval", "uint32"))
>>> IRTree([
>>> RemoteProcedure("send_message",
>>> ArgumentList([Argument("message", "string")]),
>>> ReturnValueList([ReturnValue("retval", "uint32"))])
>>> RemoteProcedure("shutdown", ArgumentList([]), ReturnValueList([ReturnValue("retval", "uint32")]))
>>> ]
IR(...)
Tree(...)
"""
remote_procedures: List[RemoteProcedure]
tree = lark.Tree
def __init__(self, remote_procedures: List[RemoteProcedure]):
self.remote_procedures = remote_procedures
package: Optional[str]
rpcs: List[RemoteProcedure]
def __iter__(self):
for rpc in self.remote_procedures:
yield rpc
def __init__(self, package: Optional[str], rpcs: List[RemoteProcedure]):
self.package = package
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
class FileLocation:
class FilePosition:
"""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:
......
# 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
from typing import Tuple
from lark import Lark, Tree
import lark.exceptions
from loguru import logger
......@@ -7,30 +27,7 @@ from rpcc.exceptions import (
EmptyRpcDefinition, MissingRpcDefinition, MissingRpcName, UnexpectedToken,
UnexpectedCharacters, UnexpectedEOF
)
from rpcc.meta import FileLocation
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 ";"
"""
from rpcc.meta import FilePosition
INVALID_INPUT_EXAMPLES = {
EmptyRpcDefinition: ['rpc foo {}',
......@@ -51,45 +48,41 @@ class Parser:
"""The main parser class"""
def __init__(self) -> None:
self.input_file = None
self.text = None
self.parser = Lark(GRAMMAR, propagate_positions=True, parser='lalr')
self.parser = Lark.open_from_package("rpcc", "grammars/rpcc.lark", 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.
: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.
"""
self.input_file = input_file
try:
file = open(self.input_file, "r", encoding="utf-8")
file = open(input_file, "r", encoding="utf-8")
except (FileNotFoundError, EnvironmentError) as err:
logger.error(f"Error parsing file: {err}")
raise
else:
with file:
self.text = file.read()
text = file.read()
try:
return self.parser.parse(self.text)
return self.parser.parse(text), text
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)
if exc_class:
raise exc_class(location)
raise exc_class(location) from None
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):
raise UnexpectedCharacters(location)
raise UnexpectedCharacters(location) from None
if isinstance(u, lark.exceptions.UnexpectedEOF):
raise UnexpectedEOF()
raise UnexpectedEOF(location) from None
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
from rpcc.transformers import FileTransformer, FileTransformerFactory
class Rpcc:
input_file: pathlib.Path
output_language: str
"""The main class for the DSL compiler."""
transformer: FileTransformer
def __init__(self, input_file: pathlib.Path, output_language: str):
self.input_file = input_file
self.output_language = output_language
self.transformer = FileTransformerFactory().create_transformer(output_language)
def __init__(self, input_file: pathlib.Path, copyright_file: pathlib.Path, output_language: str,
header_outdir: pathlib.Path, impl_outdir: pathlib.Path) -> None:
self.transformer = FileTransformerFactory.create_transformer(
output_language,
input_file,
copyright_file,
header_outdir,
impl_outdir)
def transform(self):
self.transformer.transform(self.input_file)
def transform(self) -> None:
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 .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
class FileTransformer(ABC):
"""FileTransformer interface"""
"""The FileTransformer interface."""
@abstractmethod
def transform(self, input_file: pathlib.Path):
def transform(self):
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
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
from typing import List, Tuple
from typing import List, Tuple, Optional, Dict
import lark
from lark import v_args
from lark import v_args, Discard
from loguru import logger
import rpcc.parser
from rpcc.console import console
from rpcc.exceptions import RpcRedefinition, RpccError
from rpcc.meta import FileLocation
from rpcc.exceptions import RpcRedefinition
from rpcc.transformers.cxx17.formatter import FileFormatter
from rpcc.meta import FilePosition
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 .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
NAME = str
class _CxxCompilerDefines(ConditionalDefinition):
_symbol: str
# variables can be converted directly into tuples
var = tuple
def __init__(self, symbol):
self._symbol = symbol
def start(self, children) -> IR:
"""Transform the AST tree generated by Lark into an `IR` instance, that we can use to generate the output files
"""
return IR(children)
@property
def start_keyword(self):
return "#ifdef"
def rpc_args(self, children: List[Tuple[str, str]]) -> List[Argument]:
"""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.
@property
def end_keyword(self):
return "#endif"
:param children: A list of tuples describing the rpc arguments.
:return: A list of `rpc.Arguments`
@property
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)
def rpc_return(self, t: Tuple[str, str]) -> ReturnVariable:
"""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.
def package(self, name: str) -> str:
return name
:param t: A tuple describing the rpc return value
:return: A `rpc.ReturnVariable`
"""
(typename, varname) = t
return ReturnVariable(varname, typename)
# rpc_list can be converted directly into list
rpc_list = list
@v_args(inline=True, meta=True)
def rpc(self, meta: lark.tree.Meta, name: str, args: List[Argument] = None,
retval: ReturnVariable = None) -> RemoteProcedure:
def rpc(self, meta: lark.tree.Meta, name: str, id: Optional[int], include_if_expr: Optional[ConditionalDefinition],
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
`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.
:param id: An optional numeric id the remote procedure.
: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.
"""
location = FileLocation(self.parser.input_file, self.parser.text, meta.line, meta.column, meta.start_pos)
if name in self.rpcs:
prev_location = self.rpcs[name]
raise RpcRedefinition(location, name, prev_location)
if not id:
id = RemoteProcedure.generate_id(margo_compatible=False)
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:
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)
def rpc_name(self, name: str) -> str:
......@@ -93,41 +157,141 @@ class _Transformer(lark.Transformer):
"""
return name
def double(self, _) -> str:
return _cxx_type_map["double"]
@v_args(inline=True)
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:
return _cxx_type_map["float"]
def uint16(self, _) -> Integer:
return Integer(16, True)
def int32(self, _) -> str:
return _cxx_type_map["int32"]
def uint32(self, _) -> Integer:
return Integer(32, True)
def uint32(self, _) -> str:
return _cxx_type_map["uint32"]
def uint64(self, _) -> Integer:
return Integer(64, True)
def string(self, _) -> str:
return _cxx_type_map["string"]
def csize(self, _) -> CSize:
return CSize()
def exposed_buffer(self, _) -> str:
return _cxx_type_map["exposed_buffer"]
def string(self, _) -> String:
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):
input_file: pathlib.Path
copyright_file: pathlib.Path
header_outdir: pathlib.Path
impl_outdir: pathlib.Path
parser: rpcc.parser.Parser
transformer: _Transformer
def __init__(self):
ast_transformer: _AstTransformer
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.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:
ir = self.transformer.transform(ast)
ir = _AstTransformer(self.input_file, text).transform(ast)
except lark.exceptions.VisitError as e:
raise e.orig_exc
for n in ir:
console.print(n)
header_file, impl_file = self._get_output_paths()
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}"