def scan_code(self, code: str) -> Sequence[ast.AST]: try: # horast can parse and preserve comments tree = horast.parse(code) except (ValueError, TypeError): # fallback to regular typed ast tree = ast.parse(code) return self.scan_ast(tree)
def transform_code(code: str, xformer: "ASTMigrator") -> str: """Apply a transformer to a given chunk of source code This will parse the code using the AST and find the expressions that are interesting according to xformer. If those are found the resulting statements will be rewritten and merged into the final source code """ line_ends = list(accumulate([len(x) for x in code.splitlines(keepends=True)])) line_starts = [0] + [x for x in line_ends[:-1]] try: tree = horast.parse(code) except Exception as e_horast: # fallback to regular typed ast try: tree = ast.parse(code) except Exception as e: raise CantParseException(str(e), code) matched = list(xformer.scan_ast(tree)) astmonkey.transformers.ParentChildNodeTransformer().visit(tree) def node_to_code_offset(node, use_col_offset=True): return line_starts[node.lineno - 1] + use_col_offset * node.col_offset # Replace the matched patterns in reverse line order for match in sorted( matched, key=lambda node: (node.lineno, node.col_offset), reverse=True ): xformer.transform_match(match) parent_statement = find_parent_statement(match) next_statement = find_next_sibling(parent_statement) code_start = node_to_code_offset(parent_statement) if next_statement: code_end = node_to_code_offset(next_statement, use_col_offset=False) else: code_end = len(code) new_code = horast.unparse(parent_statement) new_code = new_code.strip() code = code[:code_start] + new_code + "\n" + code[code_end:] return code
async def get_robot_meta(robot_type: Union[Type[Robot], Type[RobotService]], source: str) -> None: # TODO use inspect.getsource(robot_type) instead of source parameters # once we will get rid of type_def_from_source / temp. module meta = robot.RobotMeta(robot_type.__name__) meta.features.focus = hasattr(robot_type, "focus") # TODO more sophisticated test? (attr(s) and return value?) tree = parse(source) meta.features.move_to_pose = feature(tree, robot_type, Robot.move_to_pose.__name__) meta.features.move_to_joints = feature(tree, robot_type, Robot.move_to_joints.__name__) meta.features.stop = feature(tree, robot_type, Robot.stop.__name__) if issubclass(robot_type, Robot) and robot_type.urdf_package_path: meta.urdf_package_filename = os.path.split(robot_type.urdf_package_path)[1] glob.ROBOT_META[robot_type.__name__] = meta
def visit_FunctionDef(self, node: FunctionDef) -> FunctionDef: if node.name == 'main': kw = [] if kwargs: for k, v in kwargs.items(): kw.append(keyword(arg=k, value=v)) if kwargs2parse: for k, v in kwargs2parse.items(): kw.append(keyword(arg=k, value=parse(v))) node.body.insert( 0, Assign(targets=[Name(id=name, ctx=Store())], value=Call(func=Name(id=cls, ctx=Load()), args=[], keywords=kw))) return node
def get_logic_from_source(source_code: str, project: Project) -> None: tree = parse(source_code) assert isinstance(tree, Module) try: actions_cache, _, _ = get_actions_cache(project) except ProjectException as e: raise SourceException(e) # objects_cache = get_objects_cache(project, id_to_var=True) found_actions: Set[str] = set() loop = main_loop_body(tree) last_action: Union[None, Action] = None for node_idx, node in enumerate(loop): # simple checks for expected 'syntax' of action calls (e.g. 'robot.move_to(**res.MoveToBoxIN)') if not isinstance(node, Expr) or not isinstance( node.value, Call) or not isinstance(node.value.func, Attribute): raise SourceException("Unexpected content.") try: val = node.value obj_id = val.func.value.id # type: ignore method = val.func.attr # type: ignore except (AttributeError, IndexError) as e: print(e) raise SourceException("Script has unexpected content.") """ Support for both: robot.move_to(res.MoveToBoxIN) # args ...as well as robot.move_to(**res.MoveToBoxIN) # kwargs """ if len(val.args) == 1 and not val.keywords: action_id = val.args[0].attr # type: ignore elif len(val.keywords) == 1 and not val.args: action_id = val.keywords[0].value.attr # type: ignore else: raise SourceException("Unexpected argument(s) to the action.") if action_id in found_actions: raise SourceException(f"Duplicate action: {action_id}.") found_actions.add(action_id) # TODO test if object instance exists # raise GenerateSourceException(f"Unknown object id {obj_id}.") try: action = actions_cache[action_id] except KeyError: raise SourceException(f"Unknown action {action_id}.") at_obj, at_method = action.type.split("/") at_obj = camel_case_to_snake_case( at_obj) # convert obj id into script variable name if at_obj != obj_id or at_method != method: raise SourceException( f"Action type {action.type} does not correspond to source, where it is" f" {obj_id}/{method}.") action.inputs.clear() action.outputs.clear() if node_idx == 0: action.inputs.append(ActionIO(ActionIOEnum.FIRST.value)) else: assert last_action is not None action.inputs.append(ActionIO(last_action.id)) if node_idx > 0: assert last_action is not None actions_cache[last_action.id].outputs.append(ActionIO(action.id)) if node_idx == len(loop) - 1: action.outputs.append(ActionIO(ActionIOEnum.LAST.value)) last_action = action
#the ast module is f*****g useless, because it doesnt include comments #same with inspect, because it only gives the source, not a tree with open("../vm.py") as f: text = f.read() from horast import parse, unparse #print("Parsing...") tree = parse(text) #print(unparse(tree)) #print(tree) """ class Visitor(RecursiveAstVisitor[typed_ast.ast3]): def visit_node(self, node): if not only_localizable or hasattr(node, 'lineno') and hasattr(node, 'col_offset'): print(node) visitor = Visitor() visitor.visit(tree) """ from typed_ast import _ast3 as ast from horast.nodes import Comment node = tree.body[2].body[-1].body[-2] print("| Instruction | Description |") print("|-------------|-------------|") while len(node.orelse) > 0: #print(type(node.orelse[0])) if type(node.orelse[0]) in [ast.Break]:
def object_actions(plugins: Dict[Type, Type[ParameterPlugin]], type_def: Union[Type[Generic], Type[Service]], source: str) -> ObjectActions: ret: ObjectActions = [] tree = horast.parse(source) # ...inspect.ismethod does not work on un-initialized classes for method_name, method_def in inspect.getmembers(type_def, predicate=inspect.isfunction): # TODO check also if the method has 'action' decorator (ast needed) if not hasattr(method_def, "__action__"): continue # action from ancestor, will be copied later (only if the action was not overridden) base_cls_def = type_def.__bases__[0] if hasattr(base_cls_def, method_name) and getattr(base_cls_def, method_name) == method_def: continue meta: ActionMetadata = method_def.__action__ data = ObjectAction(name=method_name, meta=meta) if method_name in type_def.CANCEL_MAPPING: meta.cancellable = True doc = parse_docstring(method_def.__doc__) doc_short = doc["short_description"] if doc_short: data.description = doc_short signature = inspect.signature(method_def) method_tree = find_function(method_name, tree) try: for name, ttype in get_type_hints(method_def).items(): try: param_type = plugins[ttype] except KeyError: for k, v in plugins.items(): if not v.EXACT_TYPE and inspect.isclass(ttype) and issubclass(ttype, k): param_type = v break else: if name == "return": # noqa: E721 # ...just ignore NoneType for returns continue # ignore action with unknown parameter type raise IgnoreActionException(f"Parameter {name} of action {method_name}" f" has unknown type {ttype}.") if name == "return": data.returns = param_type.type_name() continue args = ActionParameterMeta(name=name, type=param_type.type_name()) try: param_type.meta(args, method_def, method_tree) except ParameterPluginException as e: # TODO log exception raise IgnoreActionException(e) if name in type_def.DYNAMIC_PARAMS: args.dynamic_value = True dvp = type_def.DYNAMIC_PARAMS[name][1] if dvp: args.dynamic_value_parents = dvp def_val = signature.parameters[name].default if def_val is not inspect.Parameter.empty: args.default_value = def_val try: args.description = doc["params"][name].strip() except KeyError: pass data.parameters.append(args) except IgnoreActionException as e: data.disabled = True data.problem = str(e) # TODO log exception ret.append(data) return ret
def _publish(project_id: str, package_name: str): with tempfile.TemporaryDirectory() as tmpdirname: try: project = ps.get_project(project_id) scene = ps.get_scene(project.scene_id) project_dir = os.path.join(tmpdirname, "arcor2_project") data_path = os.path.join(project_dir, "data") ot_path = os.path.join(project_dir, "object_types") srv_path = os.path.join(project_dir, "services") os.makedirs(data_path) os.makedirs(ot_path) os.makedirs(srv_path) with open(os.path.join(ot_path, "__init__.py"), "w"): pass with open(os.path.join(srv_path, "__init__.py"), "w"): pass with open(os.path.join(data_path, "project.json"), "w") as project_file: project_file.write(project.to_json()) with open(os.path.join(data_path, "scene.json"), "w") as scene_file: scene_file.write(scene.to_json()) obj_types_with_models: Set[str] = set() for scene_obj in scene.objects: # TODO handle inheritance obj_type = ps.get_object_type(scene_obj.type) if obj_type.model and obj_type.id not in obj_types_with_models: obj_types_with_models.add(obj_type.id) model = ps.get_model(obj_type.model.id, obj_type.model.type) obj_model = ObjectModel(obj_type.model.type, **{model.type().value.lower(): model}) with open(os.path.join(data_path, camel_case_to_snake_case(obj_type.id) + ".json"), "w")\ as model_file: model_file.write(obj_model.to_json()) with open(os.path.join(ot_path, camel_case_to_snake_case(obj_type.id)) + ".py", "w") as obj_file: obj_file.write(obj_type.source) for scene_srv in scene.services: srv = ps.get_service_type(scene_srv.type) with open(os.path.join(srv_path, camel_case_to_snake_case(srv.id)) + ".py", "w") as srv_file: srv_file.write(srv.source) except ps.PersistentStorageException as e: logger.exception("Failed to get something from the project service.") return str(e), 404 try: with open(os.path.join(project_dir, 'script.py'), "w") as script_file: if project.has_logic: script_file.write(program_src(project, scene, built_in_types_names(), True)) else: try: script = ps.get_project_sources(project.id).script # check if it is a valid Python code try: horast.parse(script) except SyntaxError: logger.exception("Failed to parse code of the uploaded script.") return "Invalid code.", 501 script_file.write(script) except ps.PersistentStorageException: logger.info("Script not found on project service, creating one from scratch.") # write script without the main loop script_file.write(program_src(project, scene, built_in_types_names(), False)) with open(os.path.join(project_dir, 'resources.py'), "w") as res: res.write(derived_resources_class(project)) with open(os.path.join(project_dir, 'actions.py'), "w") as act: act.write(global_actions_class(project)) with open(os.path.join(project_dir, 'action_points.py'), "w") as aps: aps.write(global_action_points_class(project)) with open(os.path.join(project_dir, 'package.json'), "w") as pkg: pkg.write(PackageMeta(package_name, datetime.now(tz=timezone.utc)).to_json()) except SourceException as e: logger.exception("Failed to generate script.") return str(e), 501 archive_path = os.path.join(tmpdirname, "arcor2_project") shutil.make_archive(archive_path, 'zip', project_dir) return send_file(archive_path + ".zip", as_attachment=True, cache_timeout=0)
import inspect import sys from horast import parse from arcor2.source.utils import function_implemented module_tree = parse(inspect.getsource(sys.modules[__name__])) def func_1() -> None: """ Test subject :return: """ return None def func_2() -> None: """ Test subject :return: """ raise NotImplementedError("This function is not implemented.") def func_3() -> None: var = 1 + 2 raise NotImplementedError(f"{var}")