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)
class Then(AbstractExpression): """ Evaluate and return the result of `then_fn` if `expr` is not null. Otherwise, evaluate and return the result of `default_val`. For instance, to evaluate the property of a node or return false when this node is null:: node.then(lambda n: n.my_property, False) """ class Expr(ComputingExpr): pretty_name = 'Then' def __init__(self, expr, var_expr, then_expr, default_expr, then_scope, abstract_expr=None): self.expr = expr self.var_expr = var_expr self.then_expr = then_expr self.default_expr = default_expr self.then_scope = then_scope self.static_type = self.then_expr.type super(Then.Expr, self).__init__('Result_Var', abstract_expr=abstract_expr) def _render_pre(self): return render('properties/then_ada', then=self) @property def subexprs(self): return { '0-prefix': self.expr, '1-then': self.then_expr, '2-default': self.default_expr } def _bindings(self): return [self.var_expr] def __repr__(self): return '<Then.Expr>' @staticmethod def create_from_exprs(base, then_expr, var_expr): """ Create a Then expression without going through a lambda. Used internally to constructs then expressions for the underscore operator. """ ret = Then(base, None) ret.then_expr = then_expr ret.var_expr = var_expr return ret def __init__(self, expr, then_fn, default_val=None): """ :param AbstractExpression expr: The expression to use as a source for the then. Must be of a pointer type. :param (AbstractExpression) -> AbstractExpression then_fn: The function describing the expression to compute if expr is not null. :param AbstractExpression default_val: The expression to use as fallback if expr is null. """ super(Then, self).__init__() self.expr = expr self.then_fn = then_fn self.default_val = default_val self.var_expr = self.then_expr = None self.underscore_then = False def do_prepare(self): # If this Then was created using create_from exprs, there is no lambda # expansion to do. if self.then_expr: return argspec = inspect.getargspec(self.then_fn) check_source_language( len(argspec.args) == 1 and not argspec.varargs and not argspec.keywords and not argspec.defaults, 'Invalid lambda for Then expression: exactly one parameter is' ' required, without a default value') self.var_expr = AbstractVariable(names.Name("Var_Expr"), create_local=True, source_name=names.Name( argspec.args[0])) self.then_expr = unsugar(self.then_fn(self.var_expr)) def construct(self): # Accept as a prefix: # # * any pointer, since it can be checked against "null"; # * any StructType, since structs are nullable; # * any LexicalEnvType, which has EmptyEnv as a null value. expr = construct( self.expr, lambda cls: (cls.is_ptr or cls.is_struct_type or cls.is_lexical_env_type), 'Invalid prefix type for .then: {expr_type}') self.var_expr.set_type(expr.type) # Create a then-expr specific scope to restrict the span of the "then" # variable in the debugger. with PropertyDef.get_scope().new_child() as then_scope: then_scope.add(self.var_expr.local_var) then_expr = construct(self.then_expr) var_expr = construct(self.var_expr) then_expr = BindingScope(then_expr, [var_expr], scope=then_scope) # Affect default value to the fallback expression if self.default_val is None: check_source_language( then_expr.type.null_allowed or then_expr.type is T.BoolType, "Then expression should have a default value provided," " in cases where the provided function's return type (here" " {}) does not have a default null value".format( then_expr.type.dsl_name)) default_expr = construct(No(then_expr.type)) else: default_expr = construct(self.default_val, then_expr.type) return Then.Expr(expr, construct(self.var_expr), then_expr, default_expr, then_scope) def __repr__(self): return "<Then {}: {} {}>".format(self.expr, self.var_expr, self.then_expr)
class Then(AbstractExpression): """ Expression for the then boolean combinator that works as follows:: expression.then( lambda expr_result: arbitrary_expression, default_expression ) This property code will evaluate the arbitrary expression if expression evaluates to a not null result, and will evaluate the default_expression otherwise. """ class Expr(ResolvedExpression): def __init__(self, expr, var_expr, then_expr, default_expr): self.expr = expr self.var_expr = var_expr self.then_expr = then_expr self.default_expr = default_expr self.static_type = self.then_expr.type self.result_var = PropertyDef.get().vars.create("Result_Var", self.type) super(Then.Expr, self).__init__() def _render_pre(self): return render('properties/then_ada', then=self) def _render_expr(self): return self.result_var.name.camel_with_underscores def __repr__(self): return '<Then.Expr>' def __init__(self, expr, then_fn, default_val=None): """ :param AbstractExpression expr: The expression to use as a source for the then. Must be of a pointer type. :param (AbstractExpression) -> AbstractExpression then_fn: The function describing the expression to compute if expr is not null. :param AbstractExpression default_val: The expression to use as fallback if expr is null. """ super(Then, self).__init__() self.expr = expr self.then_fn = then_fn self.default_val = default_val self.var_expr = self.then_expr = None def do_prepare(self): self.var_expr = AbstractVariable(names.Name("Var_Expr"), create_local=True) self.then_expr = self.then_fn(self.var_expr) def construct(self): # Add var_expr to the scope for this Then expression PropertyDef.get_scope().add(self.var_expr.local_var) # Accept as a prefix: # * any pointer, since it can be checked against "null"; # * any Struct, since its "Is_Null" field can be checked. expr = construct(self.expr, lambda cls: cls.is_ptr or issubclass(cls, Struct)) self.var_expr.set_type(expr.type) then_expr = construct(self.then_expr) # Affect default value to the fallback expression. For the moment, # only booleans and structs are handled. if self.default_val is None: if then_expr.type.matches(BoolType): default_expr = construct(False) elif issubclass(then_expr.type, Struct): default_expr = construct(No( # Because we're doing issubclass instead of isinstance, # PyCharm do not understand that then_exp.type is a Struct, # so the following is necessary not to have warnings. assert_type(then_expr.type, Struct) )) elif then_expr.type.matches(LexicalEnvType): default_expr = construct(EmptyEnv) else: # The following is not actually used but PyCharm's typer # requires it. default_expr = None check_source_language( False, "Then expression should have a default value provided, " "in cases where the provided function's return type is " "not Bool, here {}".format(then_expr.type.name().camel) ) else: default_expr = construct(self.default_val, then_expr.type) return Then.Expr(expr, construct(self.var_expr), then_expr, default_expr)
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)
class Then(AbstractExpression): """ Expression for the then boolean combinator that works as follows:: expression.then( lambda expr_result: arbitrary_expression, default_expression ) This property code will evaluate the arbitrary expression if expression evaluates to a not null result, and will evaluate the default_expression otherwise. """ class Expr(ResolvedExpression): pretty_name = 'Then' def __init__(self, expr, var_expr, then_expr, default_expr): self.expr = expr self.var_expr = var_expr self.then_expr = then_expr self.default_expr = default_expr self.static_type = self.then_expr.type self.result_var = PropertyDef.get().vars.create( "Result_Var", self.type) super(Then.Expr, self).__init__() def _render_pre(self): return render('properties/then_ada', then=self) def _render_expr(self): return self.result_var.name.camel_with_underscores @property def subexprs(self): return { '0-prefix': self.expr, '1-then': self.then_expr, '2-default': self.default_expr } def _bindings(self): return [self.var_expr] def __repr__(self): return '<Then.Expr>' @staticmethod def create_from_exprs(base, then_expr, var_expr): """ Create a Then expression without going through a lambda. Used internally to constructs then expressions for the underscore operator. """ ret = Then(base, None) ret.then_expr = then_expr ret.var_expr = var_expr return ret def __init__(self, expr, then_fn, default_val=None): """ :param AbstractExpression expr: The expression to use as a source for the then. Must be of a pointer type. :param (AbstractExpression) -> AbstractExpression then_fn: The function describing the expression to compute if expr is not null. :param AbstractExpression default_val: The expression to use as fallback if expr is null. """ super(Then, self).__init__() self.expr = expr self.then_fn = then_fn self.default_val = default_val self.var_expr = self.then_expr = None self.underscore_then = False def do_prepare(self): # If this Then was created using create_from exprs, there is no lambda # expansion to do. if self.then_expr: return argspec = inspect.getargspec(self.then_fn) check_source_language( len(argspec.args) == 1 and not argspec.varargs and not argspec.keywords and not argspec.defaults, 'Invalid lambda for Then expression: exactly one parameter is' ' required, without a default value') self.var_expr = AbstractVariable(names.Name("Var_Expr"), create_local=True, source_name=names.Name( argspec.args[0])) self.then_expr = self.then_fn(self.var_expr) def construct(self): # Add var_expr to the scope for this Then expression PropertyDef.get_scope().add(self.var_expr.local_var) # Accept as a prefix: # * any pointer, since it can be checked against "null"; # * any Struct, since its "Is_Null" field can be checked. expr = construct(self.expr, lambda cls: cls.is_ptr or issubclass(cls, Struct)) self.var_expr.set_type(expr.type) then_expr = construct(self.then_expr) # Affect default value to the fallback expression. For the moment, # only booleans and structs are handled. if self.default_val is None: if then_expr.type.matches(BoolType): default_expr = construct(False) elif issubclass(then_expr.type, Struct): default_expr = construct( No( # Because we're doing issubclass instead of isinstance, # PyCharm do not understand that then_exp.type is a Struct, # so the following is necessary not to have warnings. assert_type(then_expr.type, Struct))) elif then_expr.type.matches(LexicalEnvType): default_expr = construct(EmptyEnv) elif then_expr.type.matches(Symbol): default_expr = LiteralExpr(Symbol.nullexpr(), Symbol) else: # The following is not actually used but PyCharm's typer # requires it. default_expr = None check_source_language( False, "Then expression should have a default value provided, " "in cases where the provided function's return type is " "not Bool, here {}".format(then_expr.type.name().camel)) else: default_expr = construct(self.default_val, then_expr.type) return Then.Expr(expr, construct(self.var_expr), then_expr, default_expr) def __repr__(self): return "<Then {}: {} {}>".format(self.expr, self.var_expr, self.then_expr)
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.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))), ) if self.requires_index: self.index_var = AbstractVariable( names.Name('I'), type=LongType, create_local=True ) expr = self.expr_fn(self.index_var, self.element_var) else: expr = self.expr_fn(self.element_var) self.expr = assert_type(expr, AbstractExpression) 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. The element variable as a resolved expression. 4. The index variable as a resolved expression. 5. The inner scope for the iteration. :rtype: (ResolvedExpression, ResolvedExpression, 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' ) with PropertyDef.get_scope().new_child() as iter_scope: if self.index_var: PropertyDef.get_scope().add(self.index_var.local_var) self.element_var.set_type(collection_expr.type.element_type()) return (collection_expr, construct(self.expr), construct(self.element_var), construct(self.index_var) if self.index_var else None, iter_scope)