def visit_match(self, match: Expr): """For matches, we wrap the entire expression in a thunk because it is easiest to implement them using if statements. For each clause, we generate a function that checks if the pattern matches. If yes, we call a function that assigns the variables appropriately and invokes the clause body.""" data, defs = self.visit(match.data) data_var = self.generate_var_name("_match_data") # must ensure the data clause is executed exactly once thunk_body = [Assign([Name(data_var, Store())], data)] for clause in match.clauses: check_expr = self.create_match_check(clause.lhs, Name(data_var, Load())) body_def, body_name = self.create_match_clause_body( clause.lhs, clause.rhs) defs.append(body_def) # equiv: if check(data): return body(data) thunk_body.append( ast.If(check_expr, [ Return( self.create_call(body_name, [Name(data_var, Load())])) ], [])) # finally if nothing matches we have a failed assert (should never happen) thunk_body.append( ast.Assert(NameConstant(False), Str("Match was not exhaustive"))) thunk_name = self.generate_function_name("_match_thunk") thunk_def = self.create_def(thunk_name, [], defs + thunk_body) return (self.create_call(thunk_name, []), [thunk_def])
def set_value(value, kind=None): """ Creates a Constant or a Str depending on Python version in use :param value: AST node :type value: ```Any``` :param kind: AST node :type kind: ```Optional[Any]``` :return: Probably a string, but could be any constant value :rtype: ```Union[Constant, Str, NameConstant]``` """ if ( value is not None and isinstance(value, str) and len(value) > 2 and value[0] + value[-1] in frozenset(('""', "''")) ): value = value[1:-1] return ( Constant(kind=kind, value=value, constant_value=None, string=None) if PY_GTE_3_8 else ( Str(s=value, constant_value=None, string=None) if isinstance(value, str) else Num(n=value, constant_value=None, string=None) if not isinstance(value, bool) and isinstance(value, (int, float, complex)) else NameConstant(value=value, constant_value=None, string=None) ) )
def visit_call(self, call: Expr): """For calls, we must distinguish between ordinary functions, operators, and constructor calls.""" func = call.op fields, field_defs = self.convert_fields(call.args) if isinstance(func, tvm.ir.Op): raise Exception( 'Operators should have been lowered and eliminated') if isinstance(func, relay.Constructor): # produce a constructor value return (self.create_call('ConstructorValue', [ ast.Num(func.tag), ast.List(fields, Load()), NameConstant(None) ]), field_defs) # lowered operator: generate a call to a function that gets the PackedFunc # from TVM's registry if isinstance( func, Function) and func.attrs and func.attrs.Primitive.value == 1: op_call_def, op_call = self.create_op_call(func, call.args, fields) return (op_call, field_defs + [op_call_def]) # ordinary function converted_func, defs = self.visit(func) defs += field_defs return (ast.Call(converted_func, fields, []), defs)
def _getblockattr(name, lineno, col): """calls getattr(name, '__xonsh_block__', False).""" return xonsh_call('getattr', args=[ Name(id=name, ctx=Load(), lineno=lineno, col_offset=col), Str(s='__xonsh_block__', lineno=lineno, col_offset=col), NameConstant(value=False, lineno=lineno, col_offset=col)], lineno=lineno, col=col)
def update_values(self, values: dict): self._updated = True self.check_populated() for name, value in values.items(): statement = NameConstant(value) if statement not in self._statement_to_thunk: self._statement_to_thunk[statement] = ValueThunk(value) self._set_thunk(name, self._statement_to_thunk[statement])
def program_src(type_defs: TypesDict, project: CProject, scene: CScene, add_logic: bool = True) -> str: tree = empty_script_tree(project.id, add_main_loop=add_logic) # get object instances from resources object main = find_function("main", tree) last_assign = find_last_assign(main) for obj in scene.objects: add_import(tree, "object_types." + humps.depascalize(obj.type), obj.type, try_to_import=False) last_assign += 1 main.body.insert(last_assign, object_instance_from_res(obj.name, obj.id, obj.type)) # TODO temporary solution - should be (probably) handled by plugin(s) from arcor2 import json # TODO should we put there even unused parameters? for param in project.parameters: val = json.loads(param.value) aval: Optional[expr] = None if isinstance(val, bool): # subclass of int aval = NameConstant(value=val, kind=None) elif isinstance(val, (int, float)): aval = Num(n=val, kind=None) elif isinstance(val, str): aval = Str(s=val, kind="") if not aval: raise Arcor2Exception( f"Unsupported project parameter type ({param.type}) or value ({val})." ) last_assign += 1 main.body.insert( last_assign, Assign( # TODO use rather AnnAssign? targets=[Name(id=param.name, ctx=Store())], value=aval, type_comment=None), ) if add_logic: add_logic_to_loop(type_defs, tree, scene, project) return SCRIPT_HEADER + tree_to_str(tree)
def parse_numpy_array(self, arr): """Given a Numpy array, produces an appropriate Python array or numerical literal representing its contents.""" parse_single = lambda i: NameConstant(i) if isinstance(i, bool) else Num(i) if arr.ndim == 0: return parse_single(arr.item()) if arr.ndim == 1: return ast.List([parse_single(i.item()) for i in arr], Load()) elts = [] for row in arr: elts.append(self.parse_numpy_array(row)) return ast.List(elts, Load())
def create_match_check(self, pattern: Pattern, data): """Given an ADT match pattern and a (Python) expression pointing to an ADT value, this generates a Python expression that checks if the ADT value matches the given pattern (returning True or False).""" # wildcard or var match everything if isinstance(pattern, (relay.PatternWildcard, relay.PatternVar)): return NameConstant(True) conds = [] if isinstance(pattern, relay.PatternConstructor): # constructor patterns check whether the constructors match # and also the matches of any nested patterns # equiv: (arg.tag == patern_constructor.tag) conds.append( ast.Compare( ast.Attribute(data, "tag", Load()), [ast.Eq()], [ast.Num(pattern.constructor.tag)], ) ) assert isinstance(pattern, (relay.PatternConstructor, relay.PatternTuple)) # now check for any nested patterns for i in range(len(pattern.patterns)): nested_pat = pattern.patterns[i] # can safely skip var or wildcard patterns: they will # never cause a check to fail if not isinstance(nested_pat, relay.PatternConstructor): continue # index into the value corresponding to the subpattern field_index = ast.Subscript( ast.Attribute(data, "fields", Load()), ast.Index(Num(i)), Load() ) conds.append(self.create_match_check(nested_pat, field_index)) # if we do not need to check nested pattern, just return the single check if len(conds) == 1: return conds[0] # otherwise AND together any nested checks return ast.BoolOp(ast.And(), conds)
def visit_For(self, node): # For(target=Name(id='x', ctx=Store()), iter=List(elts=[], ctx=Load()), # body=[Pass()], orelse=[]) # For(target=Tuple(elts=[Name(id='band', ctx=Store()), Name(id='color', ctx=Store())], ctx=Store()), # iter=Tuple(elts=[...], ctx=Load()), # body=[Pass()], orelse=[]) self.bind(node.target) self.generic_visit(node) # if iter is Command, set _out=Capture # so this works as expected: # for line in ls(): ... # For(target=Name(id='line', ctx=Store()), # iter=Call(func=Call(func=Name(id='Command', ctx=Load()), ... if is_executable(node.iter): update_keyword( node.iter, keyword(arg='_out', value=Name(id='Capture', ctx=Load()))) update_keyword(node.iter, keyword(arg='_bg', value=NameConstant(value=True))) return node
def new_object_type(parent: ObjectTypeMeta, child: ObjectTypeMeta) -> AST: assert parent.type == child.base tree = Module(body=[], type_ignores=[]) if parent.type in built_in_types_names(): import_from = arcor2.object_types.abstract.__name__ else: import_from = f".{humps.depascalize(parent.type)}" tree.body.append( ImportFrom(module=import_from, names=[alias(name=parent.type, asname=None)], level=0)) c = ClassDef( name=child.type, bases=[get_name(parent.type)], keywords=[], body=[ Assign( targets=[Name(id="_ABSTRACT", ctx=Store())], value=NameConstant(value=False, kind=None), type_comment=None, ) ], decorator_list=[], ) # TODO add docstring with description (if provided) c.body.append(Pass()) tree.body.append(c) return tree
def visit_FunctionDef(self, node: ast.FunctionDef): if len(node.body)>0 and isinstance(node.body[0],Expr) and isinstance(node.body[0].value,Str) and node.body[0].value.s == 'dbg_ignore': oldHHRC = self.hotHasReturnCheck oldBV = self.hot self.hotHasReturnCheck = False self.hot = None self.generic_visit(node) self.hotHasReturnCheck = oldHHRC self.hot = oldBV return node # print("visiting",node.name) frozone = len(self.funcNames) self.funcNames.append(node.name)#+str(node.lineno) if hasattr(node,'definedforclass'): self.classowners.append(node.definedforclass) else: self.classowners.append(None) self.scopes.append(0) self.funcparams.append([k.arg for k in node.args.args]) hasEnter = False hasExit = False fpad = 2 if len(node.body)>0: if self.isEnterFunc(node.body[0]): hasEnter=True elif self.isExitFunc(node.body[0],node.args.args): hasExit=True else: fpad-=1 else: fpad-=1 if len(node.body)>1: if self.isEnterFunc(node.body[1]): hasEnter=True elif self.isExitFunc(node.body[1],node.args.args): hasExit=True else: fpad-=1 else: fpad-=1 oldHHRC = self.hotHasReturnCheck oldBV = self.hot self.hotHasReturnCheck = hasExit self.hot = frozone self.generic_visit(node) if len(self.exitpatterns.get(node.name,[])) > len(node.args.args): print("Exit pattern for function ",node.name," has too many parameters.") assert False shobb = [] for z in range(len(node.args.args) if hasExit else len(self.exitpatterns.get(node.name,[]))): # print("Assign2: ",str(frozone)) shobb.append(Assign( targets=[Name(id=node.args.args[z].arg+'_dbg_str_var_'+str(frozone), ctx=Store())], value=Name(id=node.args.args[z].arg,ctx=Load()) )) if hasExit: expattern = [k.arg for k in node.args.args] # sin.insert(1,Expr(value=Call(func=Name(id='_dbgExit', ctx=Load()), args=[Name(id=pn+'_dbg_str_var_'+str(self.hot),ctx=Load()) for pn in expattern]+[Name(id='_dbg_ret_var', ctx=Load())], keywords=[]))) node.body.append(Expr(value=Call(func=Name(id='_dbgExit', ctx=Load()), args=[Name(id=pn+'_dbg_str_var_'+str(self.hot),ctx=Load()) for pn in expattern]+[NameConstant(value=None)], keywords=[]))) if node.name in self.exitpatterns: expattern = self.exitpatterns[node.name] if len(node.args.args)<len(expattern) or expattern != [k.arg for k in node.args.args][:len(expattern)]: print("You defined an exit pattern, "+node.name+", and then you define a function with different first N parameters from it.") assert False node.body.append(Expr(value=Call(func=Name(id='_dbgExit_'+node.name, ctx=Load()), args=[Name(id=pn+'_dbg_str_var_'+str(self.hot),ctx=Load()) for pn in expattern]+[NameConstant(value=None)], keywords=[]))) track_frames = False freebody = node.body[fpad:]+[] if track_frames: freebody = [With( items=[ withitem( context_expr=Call(func=Attribute(value=Name(id='madscience_debugger', ctx=Load()), attr='push_context', ctx=Load()), args=[Num(n=frozone)], keywords=[]), optional_vars=Name(id='madscience_debug_context', ctx=Store()) ) ], body=node.body[fpad:]+[] )] node.body = shobb + node.body[:fpad] + freebody if track_frames: self.visitblock(node.body[-1].body,"func") else: self.visitblock(node.body[-1],"func") # if node.name=="verify": # print("verify mutated",node.lineno) # node.body.insert(0,Expr(value=Call(func=Name(id='print', ctx=Load()), args=[Str(s='function was knocked on '+str(node.lineno))], keywords=[]))) # node.body[-1].body.insert(0,Expr(value=Call(func=Name(id='print', ctx=Load()), args=[Str(s='function was visited '+str(node.lineno))], keywords=[]))) # print("mutated",node.name) # print(self.enterpatterns,frozone,node.name) if hasEnter: node.body.insert(fpad+len(shobb),Expr(value=Call(func=Name(id='_dbgEnter', ctx=Load()), args=[], keywords=[]))) if node.name in self.enterpatterns: # print("enter pattern added.") expattern = self.enterpatterns[node.name] if len(node.args.args)<len(expattern) or expattern != [k.arg for k in node.args.args][:len(expattern)]: print("You defined an enter pattern, "+node.name+", and then you define a function with different first N parameters from it.") assert False node.body.insert(fpad+len(shobb),Expr(value=Call(func=Name(id='_dbgEnter_'+node.name, ctx=Load()), args=[Name(id=pn,ctx=Load()) for pn in expattern], keywords=[]))) ast.fix_missing_locations(node) if self.isTestFunc(node): # self.generic_visit(node) sin = [ node, Expr(value=Call(func=Name(id='_dbgTest', ctx=Load()), args=[], keywords=[])) ] ast.copy_location(sin[1], node) ast.fix_missing_locations(sin[1]) return sin self.absorbEnterPattern(node) self.absorbExitPattern(node) # print() # print(ast.dump(node)) self.hotHasReturnCheck = oldHHRC self.hot = oldBV return node
def empty_script_tree(project_id: str, add_main_loop: bool = True) -> Module: """Creates barebones of the script (empty 'main' function). Returns ------- """ main_body: List[stmt] = [ Assign( targets=[Name(id="aps", ctx=Store())], value=Call(func=Name(id="ActionPoints", ctx=Load()), args=[Name(id="res", ctx=Load())], keywords=[]), type_comment=None, ) ] if add_main_loop: main_body.append( While(test=NameConstant(value=True, kind=None), body=[Pass()], orelse=[])) else: """put there "pass" in order to make code valid even if there is no other statement (e.g. no object from resources)""" main_body.append(Pass()) # TODO helper function for try ... except tree = Module( body=[ FunctionDef( name="main", args=arguments( args=[ arg(arg="res", annotation=Name(id=RES_CLS, ctx=Load()), type_comment=None) ], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[], ), body=main_body, decorator_list=[], returns=NameConstant(value=None, kind=None), type_comment=None, ), If( test=Compare(left=Name(id="__name__", ctx=Load()), ops=[Eq()], comparators=[Str(s="__main__", kind="")]), body=[ Try( body=[ With( items=[ withitem( context_expr=Call( func=Name(id=RES_CLS, ctx=Load()), args=[Str(s=project_id, kind="")], keywords=[], ), optional_vars=Name(id="res", ctx=Store()), ) ], body=[ Expr(value=Call( func=Name(id="main", ctx=Load()), args=[Name(id="res", ctx=Load())], keywords=[], )) ], type_comment=None, ) ], handlers=[ ExceptHandler( type=Name(id=Exception.__name__, ctx=Load()), name="e", body=[ Expr(value=Call( func=Name(id=arcor2.exceptions.runtime. print_exception.__name__, ctx=Load()), args=[Name(id="e", ctx=Load())], keywords=[], )) ], ) ], orelse=[], finalbody=[], ) ], orelse=[], ), ], type_ignores=[], ) add_import(tree, arcor2.exceptions.runtime.__name__, arcor2.exceptions.runtime.print_exception.__name__) add_import(tree, RES_MODULE, RES_CLS, try_to_import=False) add_import(tree, "action_points", "ActionPoints", try_to_import=False) return tree
def _add_logic(container: Container, current_action: Action, super_container: Optional[Container] = None) -> None: # more paths could lead to the same action, so it might be already added # ...this is easier than searching the tree if current_action.id in added_actions: logger.debug( f"Action {current_action.name} already added, skipping.") return inputs, outputs = project.action_io(current_action.id) logger.debug( f"Adding action {current_action.name}, with {len(inputs)} input(s) and {len(outputs)} output(s)." ) act = current_action.parse_type() ac_obj = scene.object(act.obj_id).name args: List[AST] = [] # TODO make sure that the order of parameters is correct / re-order for param in current_action.parameters: if param.type == ActionParameter.TypeEnum.LINK: parsed_link = param.parse_link() parent_action = project.action(parsed_link.action_id) # TODO add support for tuples assert len(parent_action.flow(FlowTypes.DEFAULT).outputs ) == 1, "Only one result is supported atm." assert parsed_link.output_index == 0 res_name = parent_action.flow(FlowTypes.DEFAULT).outputs[0] # make sure that the result already exists if parent_action.id not in added_actions: raise SourceException( f"Action {current_action.name} attempts to use result {res_name} " f"of subsequent action {parent_action.name}.") args.append(Name(id=res_name, ctx=Load())) elif param.type == ActionParameter.TypeEnum.PROJECT_PARAMETER: args.append( Name(id=project.parameter(param.str_from_value()).name, ctx=Load())) else: plugin = plugin_from_type_name(param.type) args.append( plugin.parameter_ast(type_defs, scene, project, current_action.id, param.name)) list_of_imp_tup = plugin.need_to_be_imported( type_defs, scene, project, current_action.id, param.name) if list_of_imp_tup: # TODO what if there are two same names? for imp_tup in list_of_imp_tup: add_import(tree, imp_tup.module_name, imp_tup.class_name, try_to_import=False) add_method_call( container.body, ac_obj, act.action_type, args, [keyword(arg="an", value=Str(s=current_action.name, kind=""))], current_action.flow(FlowTypes.DEFAULT).outputs, ) added_actions.add(current_action.id) if not outputs: raise SourceException( f"Action {current_action.name} has no outputs.") elif len(outputs) == 1: output = outputs[0] if output.end == output.END: # TODO this is just temporary (while there is while loop), should be rather Return() container.body.append(Continue()) return seq_act = project.action(output.end) seq_act_inputs, _ = project.action_io(seq_act.id) if len(seq_act_inputs ) > 1: # the action belongs to a different block if seq_act.id in added_actions: return logger.debug( f"Action {seq_act.name} going to be added to super_container." ) # test if this is the correct super_container -> count distance (number of blocks) to the START blocks_to_start: Dict[str, int] = {} for inp in seq_act_inputs: parsed_start = inp.parse_start() pact = project.action(parsed_start.start_action_id) blocks_to_start[pact.id] = _blocks_to_start(pact) winner = min(blocks_to_start, key=blocks_to_start.get ) # type: ignore # TODO what is wrong with it? # TODO if blocks_to_start is cached somewhere, the second part of the condition is useless # it might happen that there are two different ways with the same distance if winner == current_action.id or all( value == list(blocks_to_start.values())[0] for value in blocks_to_start.values()): assert super_container is not None _add_logic(super_container, seq_act) return logger.debug(f"Sequential action: {seq_act.name}") _add_logic(container, seq_act, super_container) else: root_if: Optional[If] = None # action has more outputs - each output should have condition for idx, output in enumerate(outputs): if not output.condition: raise SourceException("Missing condition.") # TODO use parameter plugin (action metadata will be needed - to get the return types) # TODO support for other countable types # ...this will only work for booleans from arcor2 import json condition_value = json.loads(output.condition.value) comp = NameConstant(value=condition_value, kind=None) what = output.condition.parse_what() output_name = project.action(what.action_id).flow( what.flow_name).outputs[what.output_index] cond = If( test=Compare(left=Name(id=output_name, ctx=Load()), ops=[Eq()], comparators=[comp]), body=[], orelse=[], ) if idx == 0: root_if = cond container.body.append(root_if) logger.debug(f"Adding branch for: {condition_value}") else: assert isinstance(root_if, If) root_if.orelse.append(cond) if output.end == output.END: cond.body.append( Continue()) # TODO should be rather return continue _add_logic(cond, project.action(output.end), container)
def parse_single(i): return NameConstant(i) if isinstance(i, bool) else Num(i)
def name_constant(value, loc=None): return cl(NameConstant(value), loc)
def parameter_ast(cls, type_defs: TypesDict, scene: CScene, project: CProject, action_id: str, parameter_id: str) -> NameConstant: return NameConstant(value=cls.parameter_execution_value( type_defs, scene, project, action_id, parameter_id), kind=None)