def visit_call_Aggregate_initial(self, node: ast.Call):
        '''
        - (const, acc lambda): the accumulator is set to the value, and then the lambda is called to
                        update it on every single element. This is called `agg_initial`
        '''
        agg_lambda = node.args[1]
        init_val = self.get_rep(node.args[0])

        # Get the sequence we are calling against and the accumulator
        seq = self.as_sequence(node.func.value)
        accumulator, accumulator_scope = self.create_accumulator(
            seq, initial_value=init_val, acc_type=init_val.cpp_type())

        # Now do the accumulation. This happens at the current iterator scope.
        sv = seq.sequence_value()
        if isinstance(sv, crep.cpp_sequence):
            self._gc.set_scope(sv.iterator_value().scope()[-1])
        else:
            self._gc.set_scope(sv.scope())
        call = ast.Call(
            func=agg_lambda,
            args=[accumulator.as_ast(),
                  seq.sequence_value().as_ast()])
        self._gc.add_statement(
            statement.set_var(accumulator, self.get_rep(call)))

        # Finally, since this is a terminal, we need to pop off the top.
        self._gc.set_scope(accumulator_scope)

        # Cache the results in our result in case we are skipping nodes in the AST.
        node.rep = accumulator
        self._result = accumulator
    def visit_Call_Aggregate_only(self, node: ast.Call):
        '''
        - (acc lambda): the accumulator is set to the first element, and the lambda is called to
                        update it after that. This is called `agg_only`.
        '''
        agg_lambda = node.args[0]

        # Get the sequence we are calling against and the accumulator
        if not isinstance(node.func, ast.Attribute):
            raise BaseException("Wrong type of function")
        seq = self.as_sequence(node.func.value)
        accumulator, accumulator_scope = self.create_accumulator(seq)

        # We have to do a simple if statement here so that the first time through we can set the
        # accumulator, and the second time we can add to it.

        is_first_iter = crep.cpp_variable(unique_name("is_first"),
                                          self._gc.current_scope(),
                                          cpp_type=ctyp.terminal('bool'),
                                          initial_value=crep.cpp_value(
                                              'true', self._gc.current_scope(),
                                              ctyp.terminal('bool')))
        accumulator_scope.declare_variable(is_first_iter)

        # Set the scope where we will be doing the accumulation
        sv = seq.sequence_value()
        if isinstance(sv, crep.cpp_sequence):
            self._gc.set_scope(sv.iterator_value().scope()[-1])
        else:
            self._gc.set_scope(sv.scope())

        # Code up if statement to select out the first element.
        if_first = statement.iftest(is_first_iter)
        self._gc.add_statement(if_first)
        self._gc.add_statement(
            statement.set_var(
                is_first_iter,
                crep.cpp_value("false", self._gc.current_scope(),
                               ctyp.terminal('bool'))))

        # Set the accumulator
        self._gc.add_statement(
            statement.set_var(accumulator, seq.sequence_value()))
        self._gc.pop_scope()

        # Now do the if statement and make the call to calculate the accumulation.
        self._gc.add_statement(statement.elsephrase())
        call = ast.Call(
            func=agg_lambda,
            args=[accumulator.as_ast(),
                  seq.sequence_value().as_ast()])
        self._gc.add_statement(
            statement.set_var(accumulator, self.get_rep(call)))

        # Finally, since this is a terminal, we need to pop off the top.
        self._gc.set_scope(accumulator_scope)

        # Cache the results in our result in case we are skipping nodes in the AST.
        node.rep = accumulator
        self._result = accumulator
Example #3
0
    def call_Select(self, node: ast.Call, args: List[ast.arg]):
        'Transform the iterable from one form to another'

        assert len(args) == 2
        source = args[0]
        selection = cast(ast.Lambda, args[1])

        # Make sure we are in a loop
        seq = self.as_sequence(source)

        # Simulate this as a "call"
        selection = lambda_unwrap(selection)
        c = ast.Call(func=selection, args=[seq.sequence_value().as_ast()])
        new_sequence_value = self.get_rep(c)

        # We need to build a new sequence.
        rep = crep.cpp_sequence(new_sequence_value, seq.iterator_value(), self._gc.current_scope())

        node.rep = rep  # type: ignore
        self._result = rep
        return rep
Example #4
0
 def visit_Call(self, call_node: ast.Call):
     r'''
     Very limited call forwarding.
     '''
     # What kind of a call is this?
     if isinstance(call_node.func, ast.Lambda):
         self.visit_Call_Lambda(call_node)
     elif isinstance(call_node.func, ast.Attribute):
         self.visit_Call_Member(call_node)
     elif isinstance(call_node.func, cpp_ast.CPPCodeValue):
         self._result = cpp_ast.process_ast_node(self, self._gc, call_node)
     elif isinstance(call_node.func, FunctionAST):
         self._result = self.visit_function_ast(call_node)
     else:
         # Perhaps a method call we can normalize?
         r = FuncADLNodeVisitor.visit_Call(self, call_node)
         if r is None and not hasattr(call_node, 'rep'):
             raise Exception("Do not know how to call '{0}'".format(ast.dump(call_node.func, annotate_fields=False)))
         if r is not None:
             self._result = r
     call_node.rep = self._result  # type: ignore
Example #5
0
    def visit_call_Aggregate_initial(self, node: ast.Call, args: List[ast.AST]):
        '''
        - (const, acc lambda): the accumulator is set to the value, and then the lambda is called to
                        update it on every single element. This is called `agg_initial`
        '''
        raw_seq = node.args[0]
        init_val = self.get_rep(node.args[1])
        agg_lambda = node.args[2]
        assert isinstance(agg_lambda, ast.Lambda)

        # Get the sequence we are calling against and the accumulator
        seq = self.as_sequence(raw_seq)
        accumulator, accumulator_scope = self._create_accumulator(seq, initial_value=init_val, acc_type=init_val.cpp_type())

        # Now do the accumulation. This happens at the current iterator scope.
        sv = seq.sequence_value()
        if isinstance(sv, crep.cpp_sequence):
            self._gc.set_scope(sv.iterator_value().scope()[-1])
        else:
            self._gc.set_scope(sv.scope())
        call = ast.Call(func=agg_lambda, args=[accumulator.as_ast(), seq.sequence_value().as_ast()])
        update_lambda = self.get_rep(call)

        # Check the accumulator value still hols out. Since we need the accumulator previously,
        # this will allow us to patch things up. This isn't perfect, but it will do.
        if update_lambda.cpp_type().type != init_val.cpp_type().type:
            best_type = most_accurate_type([init_val.cpp_type(), update_lambda.cpp_type()])
            accumulator.update_type(best_type)

        self._gc.add_statement(statement.set_var(accumulator, update_lambda))

        # Finally, since this is a terminal, we need to pop off the top.
        self._gc.set_scope(accumulator_scope)

        # Cache the results in our result in case we are skipping nodes in the AST.
        node.rep = accumulator  # type: ignore
        self._result = accumulator
Example #6
0
    def call_ResultTTree(self, node: ast.Call, args: List[ast.AST]):
        '''This AST means we are taking an iterable and converting it to a ROOT file.
        '''
        # Unpack the variables.
        assert len(args) == 4
        source = args[0]
        column_names = _extract_column_names(args[1])
        tree_name = ast.literal_eval(args[2])
        assert isinstance(tree_name, str)
        # root_filename = args[3]

        # Get the representations for each variable. We expect some sort of structure
        # for the variables - or perhaps a single variable.
        self.generic_visit(source)
        v_rep_not_norm = self.as_sequence(source)

        # What we have is a sequence of the data values we want to fill. The iterator at play
        # here is the scope we want to use to run our Fill() calls to the TTree.
        scope_fill = v_rep_not_norm.iterator_value().scope()

        # Clean the data up so it is uniform and the next bit can proceed smoothly.
        # If we don't have a tuple of data to log, turn it into a tuple.
        seq_values = v_rep_not_norm.sequence_value()
        if not isinstance(seq_values, crep.cpp_tuple):
            seq_values = crep.cpp_tuple((v_rep_not_norm.sequence_value(),), scope_fill)

        # Make sure the number of items is the same as the number of columns specified.
        if len(seq_values.values()) != len(column_names):
            raise Exception("Number of columns ({0}) is not the same as labels ({1}) in TTree creation".format(len(seq_values.values()), len(column_names)))

        # Next, look at each on in turn to decide if it is a vector or a simple variable.
        # Create a variable that we will fill for each one.
        var_names = [(name, crep.cpp_variable(unique_name(name, is_class_var=True), self._gc.current_scope(), cpp_type=get_ttree_type(rep)))
                     for name, rep in zip(column_names, seq_values.values())]

        # For each incoming variable, we need to declare something we are going to write.
        for cv in var_names:
            self._gc.declare_class_variable(cv[1])

        # Next, emit the booking code
        self._gc.add_book_statement(statement.book_ttree(tree_name, var_names))

        # Note that the output file and tree are what we are going to return.
        # The output filename is fixed - the hose code in AnalysisBase has that hard coded.
        # To allow it to be different we have to modify that template too, and pass the
        # information there. If more than one tree is written, the current code would
        # lead to a bug.
        node.rep = rh.cpp_ttree_rep("ANALYSIS.root", tree_name, self._gc.current_scope())  # type: ignore

        # For each varable we need to save, cache it or push it back, depending.
        # Make sure that it happens at the proper scope, where what we are after is defined!
        s_orig = self._gc.current_scope()
        for e_rep, e_name in zip(seq_values.values(), var_names):
            scope_fill = self.code_fill_ttree(e_rep, e_name[1], scope_fill)

        # The fill statement. This should happen at the scope where the tuple was defined.
        # The scope where this should be done is a bit tricky (note the update above):
        # - If a sequence, you want it where the sequence iterator is defined - or outside that scope
        # - If a value, you want it at the level where the value is set.
        self._gc.set_scope(scope_fill)
        self._gc.add_statement(statement.ttree_fill(tree_name))
        for e in zip(seq_values.values(), var_names):
            if rep_is_collection(e[0]):
                self._gc.add_statement(statement.container_clear(e[1][1]))

        # And we are a terminal, so pop off the block.
        self._gc.set_scope(s_orig)
        self._gc.pop_scope()
        return node.rep  # type: ignore