338 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Python
		
	
	
			
		
		
	
	
			338 lines
		
	
	
		
			10 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 Literal, Optional, ZeroOrMore  # type: ignore
 | 
						|
 | 
						|
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: 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 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  #
 | 
						|
                          ^ 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]]):
 | 
						|
            self.ctors = []
 | 
						|
            self.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, 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: Union[Template, None],
 | 
						|
        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: 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):
 | 
						|
                parent_class = parent_class.typename  # type: ignore
 | 
						|
 | 
						|
            self.parent_class = parent_class
 | 
						|
        else:
 | 
						|
            self.parent_class = ''  # type: ignore
 | 
						|
 | 
						|
        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)
 |