gtsam/wrap/gtwrap/interface_parser/classes.py

368 lines
11 KiB
Python

"""
GTSAM Copyright 2010-2020, Georgia Tech Research Corporation,
Atlanta, Georgia 30332-0415
All Rights Reserved
See LICENSE for the license information
Parser classes and rules for parsing C++ classes.
Author: Duy Nguyen Ta, Fan Jiang, Matthew Sklar, Varun Agrawal, and Frank Dellaert
"""
from typing import Any, Iterable, List, Union
from pyparsing import ZeroOrMore # type: ignore
from pyparsing import Literal, Optional, Word, alphas
from .enum import Enum
from .function import ArgumentList, ReturnType
from .template import Template
from .tokens import (CLASS, COLON, CONST, DUNDER, IDENT, LBRACE, LPAREN,
OPERATOR, RBRACE, RPAREN, SEMI_COLON, STATIC, VIRTUAL)
from .type import TemplatedType, Typename
from .utils import collect_namespaces
from .variable import Variable
class Method:
"""
Rule to parse a method in a class.
E.g.
```
class Hello {
void sayHello() const;
};
```
"""
rule = (
Optional(Template.rule("template")) #
+ ReturnType.rule("return_type") #
+ IDENT("name") #
+ LPAREN #
+ ArgumentList.rule("args_list") #
+ RPAREN #
+ Optional(CONST("is_const")) #
+ SEMI_COLON # BR
).setParseAction(lambda t: Method(t.template, t.name, t.return_type, t.
args_list, t.is_const))
def __init__(self,
template: Union[Template, Any],
name: str,
return_type: ReturnType,
args: ArgumentList,
is_const: str,
parent: Union["Class", Any] = ''):
self.template = template
self.name = name
self.return_type = return_type
self.args = args
self.is_const = is_const
self.parent = parent
def to_cpp(self) -> str:
"""Generate the C++ code for wrapping."""
return self.name
def __repr__(self) -> str:
return "Method: {} {} {}({}){}".format(
self.template,
self.return_type,
self.name,
self.args,
self.is_const,
)
class StaticMethod:
"""
Rule to parse all the static methods in a class.
E.g.
```
class Hello {
static void changeGreeting();
};
```
"""
rule = (
Optional(Template.rule("template")) #
+ STATIC #
+ ReturnType.rule("return_type") #
+ IDENT("name") #
+ LPAREN #
+ ArgumentList.rule("args_list") #
+ RPAREN #
+ SEMI_COLON # BR
).setParseAction(
lambda t: StaticMethod(t.name, t.return_type, t.args_list, t.template))
def __init__(self,
name: str,
return_type: ReturnType,
args: ArgumentList,
template: Union[Template, Any] = None,
parent: Union["Class", Any] = ''):
self.name = name
self.return_type = return_type
self.args = args
self.template = template
self.parent = parent
def __repr__(self) -> str:
return "static {} {}{}".format(self.return_type, self.name, self.args)
def to_cpp(self) -> str:
"""Generate the C++ code for wrapping."""
return self.name
class Constructor:
"""
Rule to parse the class constructor.
Can have 0 or more arguments.
"""
rule = (
Optional(Template.rule("template")) #
+ IDENT("name") #
+ LPAREN #
+ ArgumentList.rule("args_list") #
+ RPAREN #
+ SEMI_COLON # BR
).setParseAction(lambda t: Constructor(t.name, t.args_list, t.template))
def __init__(self,
name: str,
args: ArgumentList,
template: Union[Template, Any],
parent: Union["Class", Any] = ''):
self.name = name
self.args = args
self.template = template
self.parent = parent
def __repr__(self) -> str:
return "Constructor: {}{}".format(self.name, self.args)
class Operator:
"""
Rule for parsing operator overloads.
E.g.
```
class Overload {
Vector2 operator+(const Vector2 &v) const;
};
"""
rule = (
ReturnType.rule("return_type") #
+ Literal("operator")("name") #
+ OPERATOR("operator") #
+ LPAREN #
+ ArgumentList.rule("args_list") #
+ RPAREN #
+ CONST("is_const") #
+ SEMI_COLON # BR
).setParseAction(lambda t: Operator(t.name, t.operator, t.return_type, t.
args_list, t.is_const))
def __init__(self,
name: str,
operator: str,
return_type: ReturnType,
args: ArgumentList,
is_const: str,
parent: Union["Class", Any] = ''):
self.name = name
self.operator = operator
self.return_type = return_type
self.args = args
self.is_const = is_const
self.is_unary = len(args) == 0
self.parent = parent
# Check for valid unary operators
if self.is_unary and self.operator not in ('+', '-'):
raise ValueError("Invalid unary operator {} used for {}".format(
self.operator, self))
# Check that number of arguments are either 0 or 1
assert 0 <= len(args) < 2, \
"Operator overload should be at most 1 argument, " \
"{} arguments provided".format(len(args))
# Check to ensure arg and return type are the same.
if len(args) == 1 and self.operator not in ("()", "[]"):
assert args.list()[0].ctype.typename.name == return_type.type1.typename.name, \
"Mixed type overloading not supported. Both arg and return type must be the same."
def __repr__(self) -> str:
return "Operator: {}{}{}({}) {}".format(
self.return_type,
self.name,
self.operator,
self.args,
self.is_const,
)
class DunderMethod:
"""Special Python double-underscore (dunder) methods, e.g. __iter__, __contains__"""
rule = (
DUNDER #
+ (Word(alphas))("name") #
+ DUNDER #
+ LPAREN #
+ ArgumentList.rule("args_list") #
+ RPAREN #
+ SEMI_COLON # BR
).setParseAction(lambda t: DunderMethod(t.name, t.args_list))
def __init__(self, name: str, args: ArgumentList):
self.name = name
self.args = args
def __repr__(self) -> str:
return f"DunderMethod: __{self.name}__({self.args})"
class Class:
"""
Rule to parse a class defined in the interface file.
E.g.
```
class Hello {
...
};
```
"""
class Members:
"""
Rule for all the members within a class.
"""
rule = ZeroOrMore(DunderMethod.rule #
^ Constructor.rule #
^ Method.rule #
^ StaticMethod.rule #
^ Variable.rule #
^ Operator.rule #
^ Enum.rule #
).setParseAction(lambda t: Class.Members(t.asList()))
def __init__(self, members: List[Union[Constructor, Method,
StaticMethod, Variable,
Operator, Enum, DunderMethod]]):
self.ctors = []
self.methods = []
self.dunder_methods = []
self.static_methods = []
self.properties = []
self.operators = []
self.enums: List[Enum] = []
for m in members:
if isinstance(m, Constructor):
self.ctors.append(m)
elif isinstance(m, Method):
self.methods.append(m)
elif isinstance(m, StaticMethod):
self.static_methods.append(m)
elif isinstance(m, DunderMethod):
self.dunder_methods.append(m)
elif isinstance(m, Variable):
self.properties.append(m)
elif isinstance(m, Operator):
self.operators.append(m)
elif isinstance(m, Enum):
self.enums.append(m)
_parent = COLON + (TemplatedType.rule ^ Typename.rule)("parent_class")
rule = (
Optional(Template.rule("template")) #
+ Optional(VIRTUAL("is_virtual")) #
+ CLASS #
+ IDENT("name") #
+ Optional(_parent) #
+ LBRACE #
+ Members.rule("members") #
+ RBRACE #
+ SEMI_COLON # BR
).setParseAction(lambda t: Class(
t.template, t.is_virtual, t.name, t.parent_class, t.members.ctors, t.
members.methods, t.members.static_methods, t.members.dunder_methods, t.
members.properties, t.members.operators, t.members.enums))
def __init__(
self,
template: Union[Template, None],
is_virtual: str,
name: str,
parent_class: list,
ctors: List[Constructor],
methods: List[Method],
static_methods: List[StaticMethod],
dunder_methods: List[DunderMethod],
properties: List[Variable],
operators: List[Operator],
enums: List[Enum],
parent: Any = '',
):
self.template = template
self.is_virtual = is_virtual
self.name = name
if parent_class:
# If it is in an iterable, extract the parent class.
if isinstance(parent_class, Iterable):
parent_class = parent_class[0] # type: ignore
# If the base class is a TemplatedType,
# we want the instantiated Typename
if isinstance(parent_class, TemplatedType):
pass # Note: this must get handled in InstantiatedClass
self.parent_class = parent_class
else:
self.parent_class = '' # type: ignore
self.ctors = ctors
self.methods = methods
self.static_methods = static_methods
self.dunder_methods = dunder_methods
self.properties = properties
self.operators = operators
self.enums = enums
self.parent = parent
# Make sure ctors' names and class name are the same.
for ctor in self.ctors:
if ctor.name != self.name:
raise ValueError("Error in constructor name! {} != {}".format(
ctor.name, self.name))
for ctor in self.ctors:
ctor.parent = self
for method in self.methods:
method.parent = self
for static_method in self.static_methods:
static_method.parent = self
for dunder_method in self.dunder_methods:
dunder_method.parent = self
for _property in self.properties:
_property.parent = self
def namespaces(self) -> list:
"""Get the namespaces which this class is nested under as a list."""
return collect_namespaces(self)
def __repr__(self):
return "Class: {self.name}".format(self=self)