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_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 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 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_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 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 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 process_ast_node(visitor, gc, call_node: ast.Call): r'''Inject the proper code into the output stream to deal with this C++ code. We expect this to be run on the back-end of the system. visitor - The node visitor that is converting the code into C++ gc - the generated code object that we fill with actual code call_node - a Call ast node, with func being a CPPCodeValue. Result: representation - A value that represents the output ''' # We write everything into a new scope to prevent conflicts. So we have to declare the result ahead of time. cpp_ast_node = cast(CPPCodeValue, call_node.func) result_rep = cpp_ast_node.result_rep(gc.current_scope()) gc.declare_variable(result_rep) # Include files for i in cpp_ast_node.include_files: gc.add_include(i) # Build the dictionary for replacement for the object we are calling # against, if any. repl_list = [] if cpp_ast_node.replacement_instance_obj is not None: repl_list += [ (cpp_ast_node.replacement_instance_obj[0], visitor.resolve_id( cpp_ast_node.replacement_instance_obj[1]).rep.as_cpp()) ] # Process the arguments that are getting passed to the function for arg, dest in zip(cpp_ast_node.args, call_node.args): rep = visitor.get_rep(dest) repl_list += [(arg, rep.as_cpp())] # Emit the statements. blk = statements.block() visitor._gc.add_statement(blk) for s in cpp_ast_node.running_code: l_s = s for src, dest in repl_list: l_s = l_s.replace(src, str(dest)) blk.add_statement(statements.arbitrary_statement(l_s)) # Set the result and close the scope assert cpp_ast_node.result is not None blk.add_statement( statements.set_var( result_rep, cpp_value(cpp_ast_node.result, gc.current_scope(), result_rep.cpp_type()))) gc.pop_scope() return result_rep
def visit_Subscript(self, node): 'Index into an array. Check types, as tuple indexing can be very bad for us' v = self.get_rep(node.value) if not isinstance(v, crep.cpp_collection): raise Exception("Do not know how to take the index of type '{0}'".format(v.cpp_type())) index = self.get_rep(node.slice) node.rep = crep.cpp_value("{0}.at({1})".format(v.as_cpp(), index.as_cpp()), self._gc.current_scope(), cpp_type=v.get_element_type()) # type: ignore self._result = node.rep
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_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 visit_UnaryOp(self, node: ast.UnaryOp): if type(node.op) not in _known_unary_operators: raise Exception(f"Do not know how to translate Unary operator {ast.dump(node.op)}!") operand = self.get_rep(node.operand) s = operand.scope() r = crep.cpp_value(f"({_known_unary_operators[type(node.op)]}({operand.as_cpp()}))", s, operand.cpp_type()) node.rep = r # type: ignore self._result = r
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 visit_function_ast(self, call_node): 'Drop-in replacement for a function' # Get the arguments cpp_func = call_node.func arg_reps = [self.get_rep_value(a) for a in call_node.args] # Code up a call r = crep.cpp_value('{0}({1})'.format(cpp_func.cpp_name, ','.join(a.as_cpp() for a in arg_reps)), self._gc.current_scope(), cpp_type=cpp_func.cpp_return_type) # Include files and return the resulting expression for i in cpp_func.include_files: self._gc.add_include(i) call_node.rep = r return r
def make_sequence_from_collection(self, rep): ''' Take a collection and produce a sequence. Eventually this should likely be some sort of plug-in architecture. But for now, we will just assume everything looks like a vector. When it comes time for a new type, this is where it should go. ''' element_type = rep.cpp_type().element_type() iterator_value = crep.cpp_value(unique_name("i_obj"), None, element_type) # type: ignore l_statement = statement.loop(iterator_value, crep.dereference_var(rep)) # type: ignore self._gc.add_statement(l_statement) iterator_value.reset_scope(self._gc.current_scope()) # For a new sequence like this the sequence and iterator value are the same return crep.cpp_sequence(iterator_value, iterator_value, self._gc.current_scope())
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 visit_Call_Member(self, call_node): 'Method call on an object' # Visit everything down a level. self.generic_visit(call_node) # figure out what we are calling against, and the # method name we are going to be calling against. calling_against = self.get_rep(call_node.func.value) function_name = call_node.func.attr if not isinstance(calling_against, crep.cpp_value): # We didn't use get_rep_value above because now we can make a better error message. raise Exception("Do not know how to call '{0}' on '{1}'".format(function_name, type(calling_against).__name__)) # We support member calls that directly translate only. Here, for example, this is only for # obj.pt() or similar. The translation is direct. c_stub = calling_against.as_cpp() + ("->" if calling_against.is_pointer() else ".") result_type = determine_type_mf(calling_against.cpp_type(), function_name) self._result = crep.cpp_value(c_stub + function_name + "()", calling_against.scope(), result_type)
def visit_Num(self, node): node.rep = crep.cpp_value(node.n, self._gc.current_scope(), guess_type_from_number(node.n)) self._result = node.rep
def _create_accumulator(self, seq: crep.cpp_sequence, acc_type: ctyp.terminal, initial_value=None): 'Helper to create an accumulator for the Aggregate function' accumulator_type = acc_type # When we implement other types of aggregate, this code will need to # be back in. # if accumulator_type is None: # sv = seq.sequence_value() # if not isinstance(sv, crep.cpp_value): # raise Exception("Do not know how to accumulate a sequence!") # accumulator_type = sv.cpp_type() if not check_accumulator_type(accumulator_type): raise ValueError(f"Aggregate over a sequence of type '{str(accumulator_type)}' is not supported.") # Getting the scope level right is tricky. If this is a straight sequence of items, then we want the sequence level. # But if this is a sequence of sequences, we are aggregating over the sequence itself. So we need to do it one level # up from where the iterator is running on the interior sequence. seq_val = seq.sequence_value() if isinstance(seq_val, crep.cpp_sequence): accumulator_scope = seq_val.iterator_value().scope()[-1] else: accumulator_scope = seq.iterator_value().scope()[-1] accumulator = crep.cpp_variable(unique_name("aggResult"), accumulator_scope, accumulator_type, initial_value=initial_value if initial_value is not None else crep.cpp_value(accumulator_type.default_value(), self._gc.current_scope(), accumulator_type)) accumulator_scope.declare_variable(accumulator) return accumulator, accumulator_scope
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