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)
def __init__(self, referenced_unit, base_unit, abstract_expr=None): super(IsReferencedFrom, self).__init__( 'Is_Referenced', 'Is_Referenced_From', T.BoolType, [construct(referenced_unit, T.AnalysisUnitType), construct(base_unit, T.AnalysisUnitType)], abstract_expr=self ) PropertyDef.get().set_uses_envs()
def __init__(self, referenced_env, base_env, abstract_expr=None): super().__init__( 'Is_Visible', 'Is_Visible_From', T.Bool, [construct(referenced_env, T.LexicalEnv), construct(base_env, T.LexicalEnv)], abstract_expr=abstract_expr ) PropertyDef.get().set_uses_envs()
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)
def get_value(self, logic_var): """ Extract the value out of a logic variable. The returned type is always the root entity type. If the variable is not defined, return a null entity. :param AbstractExpression logic_var: The logic var from which we want to extract the value. """ from langkit.expressions import If PropertyDef.get()._gets_logic_var_value = True rtype = T.root_node.entity logic_var_expr = construct(logic_var, T.LogicVarType) logic_var_ref = logic_var_expr.create_result_var('Logic_Var_Value') return If.Expr(cond=CallExpr('Is_Logic_Var_Defined', 'Eq_Node.Refs.Is_Defined', T.BoolType, [logic_var_expr]), then=CallExpr('Eq_Solution', 'Eq_Node.Refs.Get_Value', rtype, [logic_var_ref]), else_then=NullExpr(T.root_node.entity), rtype=rtype, abstract_expr=self)
def __init__(self, expr, dest_type, do_raise=False, result_var=None): """ :type expr: ResolvedExpression :type dest_type: ASTNode :type do_raise: bool :param ResolvedExpr result_var: If provided, the cast will use it to store the cast result. Otherwise, a dedicated variable is created for this. """ self.do_raise = do_raise self.expr = expr self.static_type = dest_type p = PropertyDef.get() self.expr_var = p.vars.create('Cast_Expr', self.expr.type) self.result_var = (result_var or p.vars.create('Cast_Result', dest_type)) assert self.result_var.type == dest_type, ( 'Cast temporaries must have exactly the cast type: {} expected' ' but got {} instead'.format( dest_type.name().camel, self.result_var.type.name().camel ) ) super(Cast.Expr, self).__init__()
def construct(self): # Accept as a prefix all types that can have a null value expr = construct( self.expr, lambda cls: cls.null_allowed, '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 then_expr, default_expr = expr_or_null( then_expr, self.default_val, 'Then expression', "function's return type" ) return Then.Expr(expr, construct(self.var_expr), then_expr, default_expr, then_scope)
def __init__(self, element_var, index_var, collection, expr, iter_scope, filter=None, concat=False, take_while=None): """ :type element_var: VarExpr :type index_var: None|VarExpr :type collection: ResolvedExpression :type expr: ResolvedExpression :type iter_scope: langkit.expressions.base.LocalVars.Scope :type filter: ResolvedExpression :type concat: bool :type take_while: ResolvedExpression """ self.take_while = take_while self.element_var = element_var self.index_var = index_var self.collection = collection self.expr = expr self.filter = filter self.concat = concat self.iter_scope = iter_scope element_type = (self.expr.type.element_type() if self.concat else self.expr.type) self.static_type = element_type.array_type() self.static_type.add_to_context() self.array_var = PropertyDef.get().vars.create_scopeless( 'Map', self.type ) iter_scope.parent.add(self.array_var) super(Map.Expr, self).__init__()
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 __init__(self, unit_expr): super(AnalysisUnitRoot, self).__init__() self.static_type = T.root_node self.unit_expr = unit_expr self.prefix_var = PropertyDef.get().vars.create( 'Unit', self.unit_expr.type)
def construct(self) -> ResolvedExpression: # Make sure this expression is allowed in the current expression # context. current_prop = PropertyDef.get() check_source_language( current_prop.lazy_field, "Dynamic lexical environment creation can only happen inside a" " lazy field initializer" ) # Sanitize the resolver: make sure we have a property reference, then # make sure it has the expected signature. resolver = resolve_property(self.resolver).root_property resolver.require_untyped_wrapper() expected_rtype = T.inner_env_assoc.array check_source_language( resolver.type.matches(expected_rtype), '"resolver" must return an array of {} (got {})' .format(expected_rtype.element_type.dsl_name, resolver.type.dsl_name) ) check_source_language(not resolver.arguments, '"resolver" cannot accept arguments') # Should this environment has a transitive parent? transitive_parent = construct(self.transitive_parent, T.Bool) return self.Expr(resolver, transitive_parent, abstract_expr=self)
def __init__(self, expr, astnode, do_raise=False, result_var=None): """ :type expr: ResolvedExpression :type astnode: ASTNode :type do_raise: bool :param ResolvedExpr result_var: If provided, the cast will use it to store the cast result. Otherwise, a dedicated variable is created for this. """ self.do_raise = do_raise self.expr = expr self.static_type = astnode p = PropertyDef.get() self.expr_var = p.vars.create('Cast_Expr', self.expr.type) self.result_var = (result_var or p.vars.create('Cast_Result', astnode)) assert self.result_var.type == astnode, ( 'Cast temporaries must have exactly the cast type: {} expected' ' but got {} instead'.format( astnode.name().camel, self.result_var.type.name().camel ) ) super(Cast.Expr, self).__init__()
def __init__(self, env_expr, key_expr, lookup_kind_expr, categories, sequential_from=None, only_first=False, abstract_expr=None): self.env_expr = env_expr self.key_expr = key_expr self.lookup_kind_expr = lookup_kind_expr self.sequential_from = sequential_from self.categories = categories self.static_type = ( T.root_node.entity if only_first else T.root_node.entity.array ) self.only_first = only_first super().__init__('Env_Get_Result', abstract_expr=abstract_expr) PropertyDef.get().set_uses_envs()
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 __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 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)
def __init__(self, env_expr, to_eval_expr): self.to_eval_expr = to_eval_expr self.env_expr = env_expr # Declare a variable that will hold the value of the # bound environment. self.static_type = self.to_eval_expr.type self.env_var = PropertyDef.get().vars.create("New_Env", LexicalEnvType) super(EnvBindExpr, self).__init__()
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 __init__(self, domain, logic_var_expr): self.domain = domain ":type: ResolvedExpression" self.logic_var_expr = logic_var_expr ":type: ResolvedExpression" self.res_var = PropertyDef.get().vars.create("Var", EquationType) super(DomainExpr, self).__init__()
def has_ambient_env(self): """ Return whether ambient environment value is available. If there is one, this is either the implicit environment argument for the current property, or the currently bound environment (using eval_in_env). :rtype: bool """ return PropertyDef.get().has_implicit_env or self.is_bound
def construct(self): """ :rtype: StructExpr """ if issubclass(self.struct_type, ASTNode): check_source_language( not self.struct_type.is_list_type, 'List node synthetization is not supported for now' ) check_source_language( PropertyDef.get().memoized, 'Node synthetization can only happen inside a memoized' ' property' ) # Make sure the provided set of fields matches the one the struct # needs. def error_if_not_empty(name_set, message): check_source_language(not name_set, ('{}: {}'.format( message, ', '.join(name for name in name_set) ))) # Create a dict of field names to fields in the struct type required_fields = { f._name.lower: f for f in self.struct_type.get_abstract_fields() if isinstance(f, (Field, UserField)) and not isinstance(f, BuiltinField) } error_if_not_empty( set(required_fields) - set(self.field_values.keys()), 'Values are missing for {} fields'.format( self.struct_type.name().camel ) ) error_if_not_empty( set(self.field_values.keys()) - set(required_fields), 'Extraneous fields for {}'.format(self.struct_type.name().camel) ) # At this stage, we know that the user has only provided fields that # are valid for the struct type. provided_fields = {required_fields[name].name: construct( value, required_fields[name].type, 'Wrong type for field {}: expected {{expected}}, ' 'got {{expr_type}}'.format(name) ) for name, value in self.field_values.items()} expr_cls = (New.NodeExpr if self.struct_type.is_ast_node() else New.StructExpr) return expr_cls(self.struct_type, provided_fields)
def __init__(self, expr): """ :type expr: ResolvedExpression """ self.expr = expr self.expr.type.array_type().add_to_context() self.static_type = self.expr.type.array_type() self.array_var = PropertyDef.get().vars.create('Singleton', self.type) super(CollectionSingleton.Expr, self).__init__()
def __init__(self, env_expr, key_expr, recursive_expr, sequential=False, sequential_from=None, only_first=False, abstract_expr=None): self.env_expr = env_expr self.key_expr = key_expr self.recursive_expr = recursive_expr self.sequential = sequential self.sequential_from = sequential_from self.static_type = (T.root_node.entity if only_first else T.root_node.entity.array) self.only_first = only_first super(EnvGet.Expr, self).__init__('Env_Get_Result', abstract_expr=abstract_expr) PropertyDef.get().set_uses_envs()
def __init__(self, expr): """ :type expr: ResolvedExpression """ self.expr = expr self.expr.type.array_type().add_to_context() self.static_type = self.expr.type.array_type() self.array_var = PropertyDef.get().vars.create( 'Singleton', self.type) super(CollectionSingleton.Expr, self).__init__()
def make_as_entity(node_expr, entity_info=None, null_check=True, abstract_expr=None): """ Helper for as_entity. Takes a resolved expression instead of an abstract one. :param ResolvedExpression node_expr: The AST node expression to wrap as an entity. :param ResolvedExpression|None entity_info: Expression to use as the entity information. If provided, its type must be T.entity_info. Otherwise, the ambient entity info is used. """ from langkit.expressions import If, IsNull, New entity_type = node_expr.type.entity # If we use the ambient entity info, make the current property an entity # one. if entity_info is None: p = PropertyDef.get() p.set_uses_entity_info() entity_info = construct(p.entity_info_arg) # Expression tree sharing is forbidden, so if we need to reference the # result of the input node expression multiple times, create a variable to # hold the input node. node_ref = (node_expr.create_result_var('Node_For_Entity') if null_check else node_expr) entity_expr = New.StructExpr( entity_type, { names.Name('El'): node_ref, names.Name('Info'): entity_info }, result_var_name=names.Name.from_lower('as_entity'), ) result = If.Expr( IsNull.construct_static(node_expr), NullExpr(entity_type), entity_expr, entity_type, abstract_expr=abstract_expr) if null_check else entity_expr result.abstract_expr = abstract_expr return result
def __init__(self, cond, then, else_then, rtype): """ :param ResolvedExpression cond: A boolean expression. :param ResolvedExpression then: If "cond" is evaluated to true, this part is returned. :param ResolvedExpression else_then: If "cond" is evaluated to false, this part is returned. :param langkit.compiled_types.CompiledType rtype: Type parameter. The type that is returned by then and else_then. """ self.cond = cond self.then = then self.else_then = else_then self.static_type = rtype self.result_var = PropertyDef.get().vars.create('Result', rtype) super(If.Expr, self).__init__()
def __init__(self, receiver_expr, node_data, arguments, implicit_deref=False): """ :param ResolvedExpression receiver_expr: The receiver of the field access. :param langkit.compiled_types.AbstracNodeData node_data: The accessed property or field. :param list[ResolvedExpression] arguments: If non-empty, this field access will actually be a primitive call. :param bool implicit_deref: Whether the receiver is an env element, and we want to access a field or property of the stored node. """ self.receiver_expr = receiver_expr self.node_data = node_data self.static_type = self.node_data.type self.arguments = arguments self.simple_field_access = False self.implicit_deref = implicit_deref # After EnvSpec.create_properties has been run, expressions in # environment specifications only allow field accesses. These are # not evaluated in a property context, so they cannot create local # variables. # # TODO: in this context, it would still be useful to emit a null # check so that we raise a special exception instead of a # Storage_Error. p = PropertyDef.get() if p: self.prefix_var = p.vars.create('Pfx', self.receiver_expr.type) else: self.simple_field_access = True # Create a variable for all field accesses in properties. This is # needed because the property will return an owning reference, so # we need it to be attached to the scope. In other cases, this can # make debugging easier. super(FieldAccess.Expr, self).__init__( 'Field_Access_Result' if p else None )
def __init__(self, kind, collection, expr, list_element_var, element_var, index_var, iter_scope): """ :param str kind: Kind for this quantifier expression. 'all' will check that all items in "collection" fullfill "expr" while 'any' will check that at least one of them does. :param ResolvedExpression expr: Expression to evaluate for each item in "collection". :param ResolvedExpression collection: Collection on which this map operation works. :param ResolvedExpression expr: A boolean expression to evaluate on the collection's items. :param list_element_var: When the collection is an AST list, variable that holds the element we are currently processing during the iteration, typed as root grammar type. None otherwise. :type: ResolvedExpression|None :param element_var: Variable to use in "expr". :type element_var: ResolvedExpression :param index_var: Index variable to use in "expr". :type index_var: None|ResolvedExpression :param iter_scope: Scope for local variables internal to the iteration. :type iter_scope: langkit.expressions.base.LocalVars.Scope """ self.kind = kind self.collection = collection self.expr = expr self.list_element_var = list_element_var self.element_var = element_var self.index_var = index_var self.iter_scope = iter_scope self.result_var = PropertyDef.get().vars.create_scopeless( 'Quantifier_Result', BoolType) iter_scope.parent.add(self.result_var) super(Quantifier.Expr, self).__init__()
def as_entity(self, node): """ Wrap `node` into an entity. This uses environment rebindings from the context. """ p = PropertyDef.get() check_source_language(p, "as_entity has to be used in a property") check_source_language( p._uses_entity_info is not False, 'This property has been explicitly tagged as not using entity info, so' ' .as_entity is invalid here') # We want to keep original type of node, so no downcast node_expr = construct(node, T.root_node, downcast=False) ret = make_as_entity(node_expr, abstract_expr=self) ret.create_result_var('Ent') return ret
def solve(self, equation): """ Call ``solve`` on the given `equation` and return whether any solution was found or not. The solutions are not returned, instead, logic variables are bound to their values in the current solution. .. todo:: For the moment, since properties returning equations will reconstruct them everytime, there is no way to get the second solution if there is one. Also you cannot do that manually either since a property exposing equations cannot be public at the moment. :param AbstractExpression equation: The equation to solve. """ PropertyDef.get()._solves_equation = True return CallExpr('Solve_Success', 'Solve_Wrapper', T.BoolType, [construct(equation, T.EquationType), construct(Self, T.root_node)], abstract_expr=self)
def __init__(self, list_element_var, element_var, index_var, collection, expr, iter_scope, filter=None, concat=False, take_while=None): """ :type list_element_var: VarExpr|None :type element_var: VarExpr :type index_var: None|VarExpr :type collection: ResolvedExpression :type expr: ResolvedExpression :type iter_scope: langkit.expressions.base.LocalVars.Scope :type filter: ResolvedExpression :type concat: bool :type take_while: ResolvedExpression """ self.take_while = take_while self.list_element_var = list_element_var self.element_var = element_var self.index_var = index_var self.collection = collection self.expr = expr self.filter = filter self.concat = concat self.iter_scope = iter_scope element_type = (self.expr.type.element_type() if self.concat else self.expr.type) self.static_type = element_type.array_type() self.static_type.add_to_context() self.array_var = PropertyDef.get().vars.create_scopeless( 'Map', self.type) iter_scope.parent.add(self.array_var) super(Map.Expr, self).__init__()
def __init__(self, receiver_expr, node_data, arguments): """ :param ResolvedExpression receiver_expr: The receiver of the field access. :param langkit.compiled_types.AbstracNodeData node_data: The accessed property or field. :param list[ResolvedExpression] arguments: If non-empty, this field access will actually be a primitive call. """ self.receiver_expr = receiver_expr self.node_data = node_data self.static_type = self.node_data.type self.arguments = arguments self.simple_field_access = False # After EnvSpec.create_properties has been run, expressions in # environment specifications only allow field accesses. These are # not evaluated in a property context, so they cannot create local # variables. # # TODO: in this context, it would still be useful to emit a null # check so that we raise a special exception instead of a # Storage_Error. p = PropertyDef.get() if p: self.prefix_var = p.vars.create('Pfx', self.receiver_expr.type) else: self.simple_field_access = True # Create a variable for all field accesses in properties. This is # needed because the property will return an owning reference, so # we need it to be attached to the scope. In other cases, this can # make debugging easier. super(FieldAccess.Expr, self).__init__( 'Field_Access_Result' if p else None )
def __init__(self, kind, collection, expr, element_var, index_var, iter_scope): """ :param str kind: Kind for this quantifier expression. 'all' will check that all items in "collection" fullfill "expr" while 'any' will check that at least one of them does. :param ResolvedExpression expr: Expression to evaluate for each item in "collection". :param ResolvedExpression collection: Collection on which this map operation works. :param ResolvedExpression expr: A boolean expression to evaluate on the collection's items. :param element_var: Variable to use in "expr". :type element_var: ResolvedExpression :param index_var: Index variable to use in "expr". :type index_var: None|ResolvedExpression :param iter_scope: Scope for local variables internal to the iteration. :type iter_scope: langkit.expressions.base.LocalVars.Scope """ self.kind = kind self.collection = collection self.expr = expr self.element_var = element_var self.index_var = index_var self.iter_scope = iter_scope self.result_var = PropertyDef.get().vars.create_scopeless( 'Quantifier_Result', BoolType ) iter_scope.parent.add(self.result_var) super(Quantifier.Expr, self).__init__()
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)
def construct_common(self) -> CollectionExpression.ConstructCommonResult: """ Construct and return the expressions commonly needed by collection expression subclasses. """ assert self.element_var is not None current_scope = PropertyDef.get_scope() # Because of the discrepancy between the storage type in list nodes # (always root nodes) and the element type that user code deals with # (non-root list elements and/or entities), we may need to introduce # variables and initializing expressions. This is what the code below # does. # First, build the collection expression. From the result, we can # deduce the type of the user element variable. collection_expr = construct(self.collection) # If the collection is actually an entity, unwrap the bare list node # and save the entity info for later. 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 ) ) # Now that potential entity types are unwrapped, we can look for its # element type. elt_type = collection_expr.type.element_type if with_entities: elt_type = elt_type.entity self.element_var.set_type(elt_type) user_element_var = construct(self.element_var) # List of element variables, and the associated initialization # expressions (when applicable). # # Start with the only element variable that exists at this point: the # one that the user code for each iteration uses directly. When # relevant, each step in the code below creates a new variable N and # initialize variable N-1 from it. element_vars: List[InitializedVar] = [InitializedVar(user_element_var)] # Node lists contain bare nodes: if the user code deals with entities, # create a variable to hold a bare node and initialize the user # variable using it. if with_entities: entity_var = element_vars[-1] node_var = AbstractVariable( names.Name('Bare') + self.element_var._name, type=elt_type.element_type ) entity_var.init_expr = make_as_entity( construct(node_var), entity_info=entity_info ) element_vars.append(InitializedVar(construct(node_var))) # Node lists contain root nodes: if the user code deals with non-root # nodes, create a variable to hold the root bare node and initialize # the non-root node using it. if ( collection_expr.type.is_list_type and not collection_expr.type.is_root_node ): typed_elt_var = element_vars[-1] untyped_elt_var = AbstractVariable( names.Name('Untyped') + self.element_var._name, type=get_context().root_grammar_class ) typed_elt_var.init_expr = UncheckedCastExpr( construct(untyped_elt_var), typed_elt_var.var.type ) element_vars.append(InitializedVar(construct(untyped_elt_var))) # Keep track of the ultimate "codegen" element variable. Unlike all # other iteration variable, it is the only one that will be defined by # the "for" loop in Ada (the other ones must be declared as regular # local variables). codegen_element_var = element_vars[-1].var # Create a scope to contain the code that runs during an iteration and # lower the iteration expression. with current_scope.new_child() as inner_scope: inner_expr = construct(self.expr) # Build the list of all iteration variables iter_vars = list(element_vars) index_var = None if self.index_var: index_var = construct(self.index_var) iter_vars.append(InitializedVar(index_var)) # Create local variables for all iteration variables that need it for v in iter_vars: if v.var != codegen_element_var: v.var.abstract_var.create_local_variable(inner_scope) return self.ConstructCommonResult( collection_expr, codegen_element_var, user_element_var, index_var, iter_vars, inner_expr, inner_scope, )
def construct(self): """ Construct a resolved expression for this. :rtype: ResolvedExpression """ # Add the variables created for this expression to the current scope scope = PropertyDef.get_scope() for _, v, _ in self.matchers: scope.add(v.local_var) matched_expr = construct(self.matched_expr) check_source_language(issubclass(matched_expr.type, ASTNode), 'Match expressions can only work on AST nodes') # Yes, the assertion below is what we just checked above, but unlike # check_source_language, assert_type provides type information to # PyCharm's static analyzer. matched_type = assert_type(matched_expr.type, ASTNode) constructed_matchers = [] # Check (i.e. raise an error if no true) the set of matchers is valid: # * all matchers must target allowed types, i.e. input type subclasses; for t, v, e in self.matchers: if t is not None: check_source_language( t.matches(matched_expr.type), 'Cannot match {} (input type is {})'.format( t.name().camel, matched_expr.type.name().camel ) ) else: # The default matcher (if any) matches the most general type, # which is the input type. v.set_type(matched_expr.type) constructed_matchers.append((construct(v), construct(e))) # * all possible input types must have at least one matcher. Also warn # if some matchers are unreachable. self._check_match_coverage(matched_type) # Compute the return type as the unification of all branches _, expr = constructed_matchers[-1] rtype = expr.type for _, expr in constructed_matchers: check_source_language( expr.type.matches(rtype), "Wrong type for match expression : " "{}, expected {} or sub/supertype".format( expr.type.name().camel, rtype.name().camel ) ) rtype = expr.type.unify(rtype) # This is the expression execution will reach if we have a bug in our # code (i.e. if matchers did not cover all cases). result = UnreachableExpr(rtype) # Wrap this "failing" expression with all the cases to match in the # appropriate order, so that in the end the first matchers are tested # first. for match_var, expr in reversed(constructed_matchers): casted = Cast.Expr(matched_expr, match_var.type, result_var=match_var) guard = Not.make_expr( Eq.make_expr(casted, LiteralExpr('null', casted.type)) ) if expr.type != rtype: # We already checked that type matches, so only way this is # true is if expr.type is an ASTNode type derived from # rtype. In that case, we need an explicity upcast. expr = Cast.Expr(expr, rtype) result = If.Expr(guard, expr, result, rtype) return result
def construct(self): """ Construct a resolved expression for this. :rtype: ResolvedExpression """ # Add the variables created for this expression to the current scope scope = PropertyDef.get_scope() for _, var, _ in self.matchers: scope.add(var.local_var) matched_expr = construct(self.matched_expr) check_source_language(issubclass(matched_expr.type, ASTNode) or matched_expr.type.is_env_element_type, 'Match expressions can only work on AST nodes ' 'or env elements') # Create a local variable so that in the generated code, we don't have # to re-compute the prefix for each type check. matched_abstract_var = AbstractVariable( names.Name('Match_Prefix'), type=matched_expr.type, create_local=True ) PropertyDef.get_scope().add(matched_abstract_var.local_var) matched_var = construct(matched_abstract_var) constructed_matchers = [] # Check (i.e. raise an error if no true) the set of matchers is valid: # * all matchers must target allowed types, i.e. input type subclasses; for typ, var, expr in self.matchers: if typ is not None: check_source_language( typ.matches(matched_expr.type), 'Cannot match {} (input type is {})'.format( typ.name().camel, matched_expr.type.name().camel ) ) else: # The default matcher (if any) matches the most general type, # which is the input type. var.set_type(matched_expr.type) constructed_matchers.append((construct(var), construct(expr))) # * all possible input types must have at least one matcher. Also warn # if some matchers are unreachable. self._check_match_coverage(matched_expr.type) # Compute the return type as the unification of all branches _, expr = constructed_matchers[-1] rtype = expr.type for _, expr in constructed_matchers: check_source_language( expr.type.matches(rtype), "Wrong type for match result" " expression: got {} but expected {} or sub/supertype".format( expr.type.name().camel, rtype.name().camel ) ) rtype = expr.type.unify(rtype) # This is the expression execution will reach if we have a bug in our # code (i.e. if matchers did not cover all cases). result = UnreachableExpr(rtype) # Wrap this "failing" expression with all the cases to match in the # appropriate order, so that in the end the first matchers are tested # first. for match_var, expr in reversed(constructed_matchers): casted = Cast.Expr(matched_var, match_var.type, result_var=match_var) guard = Not.make_expr( Eq.make_expr( casted, LiteralExpr(casted.type.nullexpr(), casted.type) ) ) if expr.type != rtype: # We already checked that type matches, so only way this is # true is if expr.type is an ASTNode type derived from # rtype. In that case, we need an explicity upcast. expr = Cast.Expr(expr, rtype) result = If.Expr(guard, expr, result, rtype) return Let.Expr( [matched_var], [matched_expr], BindingScope(result, [construct(var) for _, var, _ in self.matchers]) )
def __init__(self, astnode, assocs): p = PropertyDef.get() self.result_var = p.vars.create('New_Node', astnode) super(New.NodeExpr, self).__init__(astnode, assocs)