def test_expression_pointer_decl(): e2 = crep.cpp_value("dude", top_level_scope(), ctyp.terminal("int")) assert False == e2.is_pointer() e3 = crep.cpp_value("dude", top_level_scope(), ctyp.terminal("int", is_pointer=True)) assert True == e3.is_pointer()
def test_sequence_type(): tc = gc_scope_top_level() s_value = crep.cpp_value('0.0', tc, ctyp.terminal('int', False)) i_value = crep.cpp_value('1.0', tc, ctyp.terminal('object', False)) seq = crep.cpp_sequence(s_value, i_value, tc) assert seq.sequence_value().cpp_type().type == 'int'
def test_variable_type_update(): tc = gc_scope_top_level() expr = "a" ctype = ctyp.terminal('int', False) v = crep.cpp_variable(expr, tc, ctype) v.update_type(ctyp.terminal('float', False)) assert v.cpp_type().type == 'float'
def test_variable_type__with_initial_update(): tc = gc_scope_top_level() expr = "a" c_type = ctyp.terminal('int', False) c_init = crep.cpp_value('0.0', tc, ctyp.terminal('int', False)) v = crep.cpp_variable(expr, tc, c_type, c_init) v.update_type(ctyp.terminal('float', False)) assert v.cpp_type().type == 'float' assert v.initial_value().cpp_type().type == 'float'
def test_as_root_as_single_column(): q = query_ast_visitor() node = ast.parse('1/1') value_obj = crep.cpp_value('i', gc_scope_top_level(), ctyp.terminal('int')) sequence = crep.cpp_sequence( value_obj, crep.cpp_value('i', gc_scope_top_level(), ctyp.terminal('int')), gc_scope_top_level()) node.rep = sequence # type: ignore as_root = q.get_as_ROOT(node) assert isinstance(as_root, rh.cpp_ttree_rep)
def test_deepest_scope_equal(): g = generated_code() s1 = statement.iftest("true") s2 = statement.set_var("v1", "true") g.add_statement(s1) scope_1 = g.current_scope() v1 = crep.cpp_value("v1", scope_1, ctyp.terminal('int')) v2 = crep.cpp_value("v2", scope_1, ctyp.terminal('int')) assert v1 == deepest_scope(v1, v2) assert v2 == deepest_scope(v2, v1)
def determine_type_mf(parent_type, function_name): ''' Determine the return type of the member function. Do our best to make an intelligent case when we can. parent_type: the type of the parent function_name: the name of the function we are calling ''' # If we don't know the type... if parent_type is None: raise RuntimeError("Internal Error: Trying to call member function for a type we do not know!") # If we are doing one of the normal "terminals", then we can just bomb. This should not happen! rtn_type = ctyp.method_type_info(str(parent_type), function_name) if rtn_type is not None: return rtn_type # We didn't know it. Lets make a guess, and error out if we are clearly making a mistake. base_types = ['double', 'float', 'int'] s_parent_type = str(parent_type) if s_parent_type in base_types: raise xAODTranslationError(f'Unable to call method {function_name} on type {str(parent_type)}.') # Ok - we give up. Return a double. logging.getLogger(__name__).warning(f"Warning: assumping that the method '{str(s_parent_type)}.{function_name}(...)' has return type 'double'. Use cpp_types.add_method_type_info to suppress (or correct) this warning.") return ctyp.terminal('double')
def visit_BoolOp(self, node): '''A bool op like And or Or on a set of values This is a bit more complex than just "anding" things as we want to make sure to short-circuit the evaluation if we need to. ''' # The result of this test result = crep.cpp_variable(unique_name('bool_op'), self._gc.current_scope(), cpp_type='bool') self._gc.declare_variable(result) # How we check and short-circuit depends on if we are doing and or or. check_expr = result.as_cpp() if type(node.op) == ast.And else '!{0}'.format(result.as_cpp()) check = crep.cpp_value(check_expr, self._gc.current_scope(), cpp_type=ctyp.terminal('bool')) first = True scope = self._gc.current_scope() for v in node.values: if not first: self._gc.add_statement(statement.iftest(check)) rep_v = self.get_rep(v) self._gc.add_statement(statement.set_var(result, rep_v)) if not first: self._gc.set_scope(scope) first = False # Cache result variable so those above us have something to use. self._result = result node.rep = result
def getAttributeFloatAst(call_node: ast.Call): r''' Return an attribute on one of the xAOD objects. ''' # Get the name of the moment out if len(call_node.args) != 1: raise Exception( "Calling getAttributeFloat - only one argument is allowed") if not isinstance(call_node.args[0], ast.Str): raise Exception( "Calling getAttributeFloat - only acceptable argument is a string") r = cpp_ast.CPPCodeValue() # We don't need include files for this - just quick access r.args = [ 'moment_name', ] r.replacement_instance_obj = ('obj_j', call_node.func.value.id ) # type: ignore r.running_code += [ 'float result = obj_j->getAttribute<float>(moment_name);' ] r.result = 'result' r.result_rep = lambda sc: crep.cpp_variable( unique_name("jet_attrib"), scope=sc, cpp_type=ctyp.terminal('float')) # Replace it as the function that is going to get called. call_node.func = r # type: ignore return call_node
def test_as_root_as_dict(): q = query_ast_visitor() node = ast.parse('1/1') dict_obj = crep.cpp_dict( { ast.Constant(value='hi'): crep.cpp_value('i', gc_scope_top_level(), ctyp.terminal('int')) }, gc_scope_top_level()) sequence = crep.cpp_sequence( dict_obj, # type: ignore crep.cpp_value('i', gc_scope_top_level(), ctyp.terminal('int')), gc_scope_top_level()) node.rep = sequence # type: ignore as_root = q.get_as_ROOT(node) assert isinstance(as_root, rh.cpp_ttree_rep)
def test_cpp_value_as_str(): 'Make sure we can generate a str from a value - this will be important for errors' v1 = crep.cpp_value('dude', top_level_scope(), ctyp.terminal('int')) assert 'dude' in str(v1) v2 = crep.cpp_value('dude', top_level_scope(), None) assert 'dude' in str(v2)
def getAttributeVectorFloatAst(call_node: ast.Call): r''' Return a cpp ast accessing a vector of doubles for an xAOD attribute ''' # Get the name of the moment out if len(call_node.args) != 1: raise Exception( "Calling getAttributeVectorFloat - only one argument is allowed") if not isinstance(call_node.args[0], ast.Str): raise Exception( "Calling getAttributeVectorFloat - only acceptable argument is a string" ) r = cpp_ast.CPPCodeValue() r.include_files += ['vector'] r.args = [ 'moment_name', ] r.replacement_instance_obj = ('obj_j', call_node.func.value.id ) # type: ignore r.running_code += [ 'auto result = obj_j->getAttribute<std::vector<double>>(moment_name);' ] r.result = 'result' r.result_rep = lambda sc: crep.cpp_collection( unique_name("jet_vec_attrib_"), scope=sc, collection_type=ctyp.collection(ctyp.terminal('double') )) # type: ignore # Replace it as the function that is going to get called. call_node.func = r # type: ignore return call_node
def test_variable_pointer(): 'Make sure is_pointer can deal with a non-type correctly' v1 = crep.cpp_value('dude', top_level_scope(), ctyp.terminal('int')) v2 = crep.cpp_value('dude', top_level_scope(), None) assert v1.cpp_type().type == 'int' with pytest.raises(RuntimeError) as e: v2.cpp_type()
def visit_BinOp(self, node): 'An in-line add' if type(node.op) not in _known_binary_operators: raise Exception(f"Do not know how to translate Binary operator {ast.dump(node.op)}!") left = self.get_rep(node.left) right = self.get_rep(node.right) best_type = most_accurate_type([left.cpp_type(), right.cpp_type()]) if type(node.op) is ast.Div: best_type = ctyp.terminal('double', False) s = deepest_scope(left, right).scope() r = crep.cpp_value(f"({left.as_cpp()}{_known_binary_operators[type(node.op)]}{right.as_cpp()})", s, best_type) # Cache the result to push it back further up. node.rep = r self._result = r
def call_First(self, node: ast.AST, args: List[ast.AST]) -> Any: 'We are in a sequence. Take the first element of the sequence and use that for future things.' # Unpack the source here assert len(args) == 1 source = args[0] # Make sure we are in a loop. seq = self.as_sequence(source) # The First terminal works by protecting the code with a if (first_time) {} block. # We need to declare the first_time variable outside the block where the thing we are # looping over here is defined. This is a little tricky, so we delegate to another method. loop_scope = seq.iterator_value().scope() outside_block_scope = loop_scope[-1] # Define the variable to track this outside that block. is_first = crep.cpp_variable(unique_name('is_first'), outside_block_scope, cpp_type=ctyp.terminal('bool'), initial_value=crep.cpp_value('true', self._gc.current_scope(), ctyp.terminal('bool'))) outside_block_scope.declare_variable(is_first) # Now, as long as is_first is true, we can execute things inside this statement. # The trick is putting the if statement in the right place. We need to locate it just one level # below where we defined the scope above. s = statement.iftest(is_first) s.add_statement(statement.set_var(is_first, crep.cpp_value('false', top_level_scope(), cpp_type=ctyp.terminal('bool')))) 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()) self._gc.add_statement(s) # If we just found the first sequence in a sequence, return that. # Otherwise return a new version of the value. first_value = sv if isinstance(sv, crep.cpp_sequence) else sv.copy_with_new_scope(self._gc.current_scope()) node.rep = first_value # type: ignore self._result = first_value
def test_accurate_type_int_and_float(): t1 = ctyp.terminal('int', False) t2 = ctyp.terminal('float', False) r = most_accurate_type([t1, t2]) assert r._type == 'float'
def test_accurate_type_two_int(): t1 = ctyp.terminal('int', False) t2 = ctyp.terminal('int', False) r = most_accurate_type([t1, t2]) assert r._type == 'int'
def test_can_call_prodVtx(): ctyp.add_method_type_info("xAOD::TruthParticle", "prodVtx", ctyp.terminal('xAODTruth::TruthVertex', is_pointer=True)) dataset_for_testing("file://root.root") \ .Select("lambda e: e.TruthParticles('TruthParticles').Select(lambda t: t.prodVtx().x()).Sum()") \ .value()
def guess_type_from_number(n): if int(n) == n: return ctyp.terminal("int") return ctyp.terminal("double")
def visit_IfExp(self, node): r''' We'd like to be able to use the "?" operator in C++, but the problem is lazy evaluation. It could be when we look at one or the other item, a bunch of prep work has to be done - and that will show up in separate statements. So we have to use if/then/else with a result value. ''' # The result we'll store everything in. result = crep.cpp_variable(unique_name("if_else_result"), self._gc.current_scope(), cpp_type=ctyp.terminal("double")) self._gc.declare_variable(result) # We always have to evaluate the test. current_scope = self._gc.current_scope() test_expr = self.get_rep(node.test) self._gc.add_statement(statement.iftest(test_expr)) if_scope = self._gc.current_scope() # Next, we do the true and false if statement. self._gc.add_statement(statement.set_var(result, self.get_rep(node.body))) self._gc.set_scope(if_scope) self._gc.pop_scope() self._gc.add_statement(statement.elsephrase()) self._gc.add_statement(statement.set_var(result, self.get_rep(node.orelse))) self._gc.set_scope(current_scope) # Done, the result is the rep of this node! node.rep = result self._result = result
def test_int_pointer(): t_int = ctyp.terminal('int') assert False == t_int.is_pointer()
def visit_Str(self, node): node.rep = crep.cpp_value('"{0}"'.format(node.s), self._gc.current_scope(), ctyp.terminal("string")) self._result = node.rep
def test_method_type_found(): ctyp.add_method_type_info("bogus", "pt", ctyp.terminal('double')) assert 'double' == str(ctyp.method_type_info("bogus", "pt"))
def element_type(self): 'Return the type of the elements in the collection' return ctyp.terminal(self._element_name, is_pointer=True)
'ANA_CHECK (evtStore()->retrieve(result, collection_name));'] r.result = 'result' is_collection = info['is_collection'] if 'is_collection' in info else True if is_collection: r.result_rep = lambda scope: crep.cpp_collection(unique_name(info['function_name'].lower()), scope=scope, collection_type=info['container_type']) # type: ignore else: r.result_rep = lambda scope: crep.cpp_variable(unique_name(info['function_name'].lower()), scope=scope, cpp_type=info['container_type']) # Replace it as the function that is going to get called. call_node.func = r return call_node # Config everything. def create_higher_order_function(info): 'Creates a higher-order function because python scoping is broken' return lambda call_node: getCollection(info, call_node) for info in collections: cpp_ast.method_names[info['function_name']] = create_higher_order_function(info) # Configure some info about the types. ctyp.add_method_type_info("xAOD::TruthParticle", "prodVtx", ctyp.terminal('xAODTruth::TruthVertex', is_pointer=True)) ctyp.add_method_type_info("xAOD::TruthParticle", "decayVtx", ctyp.terminal('xAODTruth::TruthVertex', is_pointer=True)) ctyp.add_method_type_info("xAOD::TruthParticle", "parent", ctyp.terminal('xAOD::TruthParticle', is_pointer=True)) ctyp.add_method_type_info("xAOD::TruthParticle", "child", ctyp.terminal('xAOD::TruthParticle', is_pointer=True))
def test_accurate_type_float_and_double(): t1 = ctyp.terminal('double', False) t2 = ctyp.terminal('float', False) r = most_accurate_type([t1, t2]) assert r._type == 'double'
def test_accurate_type_single_int(): t = ctyp.terminal('int', False) r = most_accurate_type([t]) assert r._type == 'int'
def visit_Compare(self, node): 'A compare between two things. Python supports more than that, but not implemented yet.' if len(node.ops) != 1: raise Exception("Do not support 1 < a < 10 comparisons yet!") left = self.get_rep(node.left) right = self.get_rep(node.comparators[0]) r = crep.cpp_value('({0}{1}{2})'.format(left.as_cpp(), compare_operations[type(node.ops[0])], right.as_cpp()), self._gc.current_scope(), ctyp.terminal("bool")) node.rep = r self._result = r
def __init__(self, filename, treename, scope): cpp_value.__init__(self, unique_name("ttree_rep"), scope, ctyp.terminal("ttreetfile")) self.filename = filename self.treename = treename
def test_terminal_type(): t = ctyp.terminal('double', False) assert t.type == 'double'