예제 #1
0
class CollectionExpression(AbstractExpression):
    """
    Base class to provide common code for abstract expressions working on
    collections.
    """

    _counter = count()

    class ConstructCommonResult:
        """
        Holder for the result of the "construct_common" method.
        """
        def __init__(self, collection_expr, element_vars, index_var,
                     inner_expr, inner_scope):
            self.collection_expr = collection_expr
            """
            Resolved expression corresponding to the collection on which the
            iteration is done.

            :type: ResolvedExpression
            """

            self.element_vars = element_vars
            """
            List of "element" iteration variables, and their initialization
            expression, if any.

            we need a list of "element" iteration variables for code generation
            purposes. For instance, assuming we iterate on an entity that is an
            AST list, we need 3 variables::

              * one that contains the node whose type is the root one (AST
                lists contain only root nodes in the generated code);

              * one that contains the node that is casted to the proper type;

              * one that wraps this casted node as an entity.

            The first variable is the one used to expand iteration expressions
            (see the "user_element_var" property.  This is the one that must
            have a source name. The other ones are mere code generation
            temporaries.

            The last variable is the one that is used as the actual iteration
            variable in the generated code. This is the only one that will not
            require explicit initialization.

            :type: list[(ResolvedExpression, ResolvedExpression|None)]
            """

            self.index_var = index_var
            """
            The index variable as a resolved expression, if required.

            :type: ResolvedExpression|None
            """

            self.inner_expr = inner_expr
            """
            Resolved expression to be evaluated for each collection item.

            :type: ResolvedExpression
            """

            self.inner_scope = inner_scope
            """
            Local variable scope for the body of the iteration.

            :type: langkit.expressions.base.LocalVars.Scope
            """

    def __init__(self, collection, expr):
        """
        :param AbstractExpression collection: Collection on which this map
            operation works.

        :param expr: Function that takes the induction variable and returns an
            expression to evaluate for each item in "collection". If the
            function takes two parameters, the first one will also be the
            an induction variable for the iteration index.
        :type collection: AbstractExpression
        """
        super().__init__()
        self.collection = collection
        self.expr_fn = expr
        self.expr = None
        self.element_var = None
        self.requires_index = False
        self.index_var = None

    def initialize(self,
                   expr: AbstractExpression,
                   element_var: AbstractVariable,
                   index_var: Optional[AbstractVariable] = None) -> None:
        """
        Initialize this expression using already expanded sub-expressions.

        This is useful when building expressions with item sub-expressions
        outside of our Python DSL.
        """
        self.expr = expr
        self.element_var = element_var
        self.requires_index = index_var is not None
        self.index_var = index_var

    def do_prepare(self):
        # When this expression does not come from our Python DSL (see the
        # initialize method above), the sub-expression is ready to use: do not
        # try to expand the function.
        if self.expr is not None:
            return

        argspec = inspect.getargspec(self.expr_fn)

        check_multiple([
            (len(argspec.args)
             in (1, 2), 'Invalid collection iteration lambda: only one '
             'or two parameters expected'),
            (not argspec.varargs and not argspec.keywords,
             'Invalid collection iteration lambda: no *args or **kwargs'),
            (not argspec.defaults,
             'Invalid collection iteration lambda: No default values allowed '
             'for arguments')
        ])

        if len(argspec.args) == 2:
            self.requires_index = True
            index_var_pos = 0
            item_var_pos = 1
        else:
            self.requires_index = False
            index_var_pos = None
            item_var_pos = 0

        # Get the name of the loop variable from the DSL. But don't when we
        # have a "default" one, such as for using the ".filter" combinator. In
        # this case, it's up to ".filter"'s special implementation to get the
        # name from the filter function.
        source_name = (None if self.expr_fn == collection_expr_identity else
                       names.Name.from_lower(argspec.args[item_var_pos]))

        self.element_var = AbstractVariable(names.Name("Item_{}".format(
            next(CollectionExpression._counter))),
                                            source_name=source_name)
        if self.requires_index:
            self.index_var = AbstractVariable(
                names.Name('I'),
                type=T.Int,
                source_name=names.Name.from_lower(argspec.args[index_var_pos]))
            expr = self.expr_fn(self.index_var, self.element_var)
        else:
            expr = self.expr_fn(self.element_var)
        self.expr = unsugar(expr)

    def construct_common(self):
        """
        Construct and return the expressions commonly needed by collection
        expression subclasses.

        :rtype: CollectionExpression.ConstructCommonResult
        """
        current_scope = PropertyDef.get_scope()

        # First, build the collection expression. From the result, we can
        # deduce the type of the element variable.
        collection_expr = construct(self.collection)
        with_entities = collection_expr.type.is_entity_type
        if with_entities:
            saved_entity_coll_expr, collection_expr, entity_info = (
                collection_expr.destructure_entity())
            collection_expr = SequenceExpr(saved_entity_coll_expr,
                                           collection_expr)

        check_source_language(
            collection_expr.type.is_collection,
            'Cannot iterate on {}, which is not a collection'.format(
                collection_expr.type.dsl_name))

        elt_type = collection_expr.type.element_type
        if with_entities:
            elt_type = elt_type.entity
        self.element_var.set_type(elt_type)

        # List of "element" iteration variables
        elt_vars = [construct(self.element_var)]

        # List of initializing expressions for them
        elt_var_inits = []

        if with_entities:
            entity_var = elt_vars[-1]
            node_var = AbstractVariable(names.Name('Bare') +
                                        self.element_var._name,
                                        type=elt_type.element_type)
            elt_var_inits.append(
                make_as_entity(construct(node_var), entity_info=entity_info))
            elt_vars.append(construct(node_var))

        # If we are iterating over an AST list, then we get root grammar typed
        # values. We need to convert them to the more specific type to get the
        # rest of the expression machinery work.
        if collection_expr.type.is_list_type:
            typed_elt_var = elt_vars[-1]
            untyped_elt_var = AbstractVariable(
                names.Name('Untyped') + self.element_var._name,
                type=get_context().root_grammar_class)
            # Initialize the former last variable with a cast from the new last
            # variable and push the new last variable.
            elt_var_inits.append(
                UncheckedCastExpr(construct(untyped_elt_var),
                                  typed_elt_var.type))
            elt_vars.append(construct(untyped_elt_var))

        # Only then we can build the inner expression
        with current_scope.new_child() as inner_scope:
            inner_expr = construct(self.expr)

        if with_entities:
            entity_var.abstract_var.create_local_variable(inner_scope)
        if collection_expr.type.is_list_type:
            typed_elt_var.abstract_var.create_local_variable(inner_scope)

        if self.index_var:
            self.index_var.add_to_scope(inner_scope)

        elt_var_inits.append(None)

        return self.ConstructCommonResult(
            collection_expr, funcy.lzip(elt_vars, elt_var_inits),
            construct(self.index_var) if self.index_var else None, inner_expr,
            inner_scope)
예제 #2
0
class CollectionExpression(AbstractExpression):
    """
    Base class to provide common code for abstract expressions working on
    collections.
    """

    _counter = count()

    def __init__(self, collection, expr):
        """
        :param AbstractExpression collection: Collection on which this map
            operation works.

        :param expr: Function that takes the induction variable and returns an
            expression to evaluate for each item in "collection". If the
            function takes two parameters, the first one will also be the
            an induction variable for the iteration index.
        :type collection: AbstractExpression
        """
        super(CollectionExpression, self).__init__()
        self.collection = collection
        self.expr_fn = expr
        self.expr = None
        self.list_element_var = None
        self.element_var = None
        self.requires_index = False
        self.index_var = None

    def do_prepare(self):
        argspec = inspect.getargspec(self.expr_fn)

        check_multiple([
            (len(argspec.args)
             in (1, 2), 'Invalid collection iteration lambda: only one '
             'or two parameters expected'),
            (not argspec.varargs and not argspec.keywords,
             'Invalid collection iteration lambda: no *args or **kwargs'),
            (not argspec.defaults,
             'Invalid collection iteration lambda: No default values allowed '
             'for arguments')
        ])

        self.requires_index = len(argspec.args) == 2
        self.element_var = AbstractVariable(
            names.Name("Item_{}".format(next(CollectionExpression._counter))),
            source_name=names.Name.from_lower(argspec.args[0]))
        if self.requires_index:
            self.index_var = AbstractVariable(
                names.Name('I'),
                type=LongType,
                create_local=True,
                source_name=names.Name.from_lower(argspec.args[1]))
            expr = self.expr_fn(self.index_var, self.element_var)
        else:
            expr = self.expr_fn(self.element_var)
        self.expr = unsugar(expr)

    def construct_common(self):
        """
        Construct the expressions commonly needed by collection expression
        subclasses, and return them as a tuple constituted of:

        1. The resolved collection expression.
        2. The resolved expression function passed to CollectionExpression's
           constructor.
        3. If the collection is an AST list, the iteration variable, whose type
           is the root grammar type. None otherwise.
        4. The element variable as a resolved expression. In the case of an AST
           list collection, this is just 3. converted to the specific type.
        5. The index variable as a resolved expression.
        6. The inner scope for the iteration.

        :rtype: (ResolvedExpression,
                 ResolvedExpression,
                 ResolvedExpression|None,
                 ResolvedExpression,
                 ResolvedExpression,
                 langkit.expressions.base.LocalVars.Scope)
        """
        collection_expr = construct(
            self.collection, lambda t: t.is_collection(),
            'Map cannot iterate on {expr_type}, which is not a collection')
        self.element_var.set_type(collection_expr.type.element_type())

        current_scope = PropertyDef.get_scope()

        # If we are iterating over an AST list, then we get root grammar typed
        # values. We need to convert them to the more specific type to get the
        # rest of the expression machinery work. For this, create a new
        # variable.
        if collection_expr.type.is_list_type:
            self.list_element_var = AbstractVariable(
                names.Name("List_Item_{}".format(
                    next(CollectionExpression._counter))),
                type=get_context().root_grammar_class)
            self.element_var.add_to_scope(current_scope)

        with current_scope.new_child() as iter_scope:
            if self.index_var:
                PropertyDef.get_scope().add(self.index_var.local_var)

            return (collection_expr, construct(self.expr), (construct(
                self.list_element_var) if self.list_element_var else None),
                    construct(self.element_var),
                    construct(self.index_var) if self.index_var else None,
                    iter_scope)