""" 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 Iterable, List, Union from pyparsing import Literal, Optional, ZeroOrMore from .enum import Enum from .function import ArgumentList, ReturnType from .template import Template from .tokens import (CLASS, COLON, CONST, 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: str, name: str, return_type: ReturnType, args: ArgumentList, is_const: str, parent: Union[str, "Class"] = ''): self.template = template self.name = name self.return_type = return_type self.args = args self.is_const = is_const self.parent = parent 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 = ( 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)) def __init__(self, name: str, return_type: ReturnType, args: ArgumentList, parent: Union[str, "Class"] = ''): self.name = name self.return_type = return_type self.args = args 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 = ( IDENT("name") # + LPAREN # + ArgumentList.rule("args_list") # + RPAREN # + SEMI_COLON # BR ).setParseAction(lambda t: Constructor(t.name, t.args_list)) def __init__(self, name: str, args: ArgumentList, parent: Union["Class", str] = ''): self.name = name self.args = args self.parent = parent def __repr__(self) -> str: return "Constructor: {}".format(self.name) 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[str, "Class"] = ''): 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 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(Constructor.rule # ^ StaticMethod.rule # ^ Method.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]]): self.ctors = [] self.methods = [] self.static_methods = [] self.properties = [] self.operators = [] self.enums = [] 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, 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.properties, t.members.operators, t.members.enums )) def __init__( self, template: Template, is_virtual: str, name: str, parent_class: list, ctors: List[Constructor], methods: List[Method], static_methods: List[StaticMethod], properties: List[Variable], operators: List[Operator], enums: List[Enum], parent: str = '', ): 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] # If the base class is a TemplatedType, # we want the instantiated Typename if isinstance(parent_class, TemplatedType): parent_class = parent_class.typename self.parent_class = parent_class else: self.parent_class = '' self.ctors = ctors self.methods = methods self.static_methods = static_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 _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)