Esempio n. 1
0
def add_import(node: Module,
               module: str,
               cls: str,
               try_to_import: bool = True) -> None:
    """
    Adds "from ... import ..." to the beginning of the script.

    Parameters
    ----------
    node
    module
    cls

    Returns
    -------

    """
    class AddImportTransformer(NodeTransformer):
        def __init__(self, module: str, cls: str) -> None:
            self.done = False
            self.module = module
            self.cls = cls

        def visit_ImportFrom(self, node: ImportFrom) -> ImportFrom:
            if node.module == self.module:

                for aliass in node.names:
                    if aliass.name == self.cls:
                        self.done = True
                        break
                else:
                    node.names.append(alias(name=self.cls, asname=None))
                    self.done = True

            return node

    if try_to_import:

        try:
            imported_mod = importlib.import_module(module)
        except ModuleNotFoundError as e:
            raise SourceException(e)

        try:
            getattr(imported_mod, cls)
        except AttributeError as e:
            raise SourceException(e)

    tr = AddImportTransformer(module, cls)
    node = tr.visit(node)

    if not tr.done:
        node.body.insert(
            0,
            ImportFrom(module=module,
                       names=[alias(name=cls, asname=None)],
                       level=0))
Esempio n. 2
0
        def visit_FunctionDef(self, node: FunctionDef) -> None:

            if node.name == 'main':

                for item in node.body:

                    if isinstance(item, Assign):

                        assert isinstance(item.targets[0], Name)

                        if item.targets[0].id == name:

                            # TODO assert for item.value
                            if item.value.func.id != cls:  # type: ignore
                                raise SourceException(
                                    "Name '{}' already used for instance of '{}'!"
                                    .format(
                                        name,
                                        item.value.func.id))  # type: ignore

                            self.found = True
                            # TODO update arguments?

            if not self.found:
                self.generic_visit(node)
Esempio n. 3
0
def main_loop(tree: Module) -> While:
    main = find_function("main", tree)

    for node in main.body:
        if isinstance(
                node, While
        ):  # TODO more specific condition (test for True argument)
            return node

    raise SourceException("Main loop not found.")
Esempio n. 4
0
def add_logic_to_loop(tree: Module, scene: Scene, project: Project) -> None:

    loop = main_loop_body(tree)

    try:
        actions_cache, first_action_id, last_action_id = get_actions_cache(
            project)
    except ProjectException as e:
        raise SourceException(e)

    if first_action_id is None:
        raise SourceException("'start' action not found.")

    if last_action_id is None:
        raise SourceException("'end' action not found.")

    next_action_id = first_action_id

    while True:

        act = actions_cache[next_action_id]

        if len(loop) == 1 and isinstance(loop[0], Pass):
            # pass is not necessary now
            loop.clear()

        ac_obj, ac_type = act.type.split('/')

        # for scene objects, convert ID to name
        try:
            ac_obj = scene.object(ac_obj).name
        except Arcor2Exception:
            pass

        append_method_call(loop, fix_object_name(ac_obj), ac_type,
                           [get_name_attr("res", clean(act.name))], [])

        if act.id == last_action_id:
            break

        next_action_id = act.outputs[0].default
Esempio n. 5
0
def check_object_type(object_type_source: str, type_name: str) -> None:
    """
    Checks whether the object type source is a valid one.
    :param object_type_source:
    :return:
    """
    try:
        type_def = hlp.type_def_from_source(object_type_source, type_name,
                                            Generic)
    except hlp.TypeDefException as e:
        raise SourceException(e)

    meta_from_def(type_def, False)
    object_actions(TYPE_TO_PLUGIN, type_def, object_type_source)
Esempio n. 6
0
def check_service_type(service_type_source: str, type_name: str) -> None:
    """
    Checks whether the service type source is a valid one.
    :param service_type_source: Source code.
    :param type_name: Class name.
    :return:
    """
    try:
        type_def = hlp.type_def_from_source(service_type_source, type_name,
                                            Service)
    except hlp.TypeDefException as e:
        raise SourceException(e)

    # meta_from_def(type_def)  # calls API which is probably undesirable for check
    object_actions(TYPE_TO_PLUGIN, type_def, service_type_source)
Esempio n. 7
0
def find_class_def(name: str, tree: Union[Module, AST]) -> ClassDef:
    class FindClassDef(NodeVisitor):
        def __init__(self) -> None:
            self.cls_def_node: Optional[ClassDef] = None

        def visit_ClassDef(self, node: ClassDef) -> None:
            if node.name == name:
                self.cls_def_node = node
                return

            if not self.cls_def_node:
                self.generic_visit(node)

    ff = FindClassDef()
    ff.visit(tree)

    if ff.cls_def_node is None:
        raise SourceException(f"Class definition {name} not found.")

    return ff.cls_def_node
Esempio n. 8
0
def find_function(name: str, tree: Union[Module, AST]) -> FunctionDef:
    class FindFunction(NodeVisitor):
        def __init__(self) -> None:
            self.function_node: Optional[FunctionDef] = None

        def visit_FunctionDef(self, node: FunctionDef) -> None:
            if node.name == name:
                self.function_node = node
                return

            if not self.function_node:
                self.generic_visit(node)

    ff = FindFunction()
    ff.visit(tree)

    if ff.function_node is None:
        raise SourceException(f"Function {name} not found.")

    return ff.function_node
Esempio n. 9
0
def add_method_call_in_main(tree: Module, instance: str, method: str,
                            args: List, kwargs: List) -> None:
    """
    Places method call after block where instances are created.

    Parameters
    ----------
    tree
    instance
    init
    args
    kwargs

    Returns
    -------

    """

    main_body = find_function("main", tree).body
    last_assign_idx = None

    for body_idx, body_item in enumerate(main_body):

        # TODO check if instance exists!

        if isinstance(body_item, Assign):
            last_assign_idx = body_idx

    if not last_assign_idx:
        raise SourceException()

    # TODO iterate over args/kwargs
    # TODO check actual number of method's arguments (and types?)
    main_body.insert(
        last_assign_idx + 1,
        Expr(value=Call(
            func=get_name_attr(instance, method), args=args, keywords=[])))
Esempio n. 10
0
def parse_def(type_def: type) -> AST:
    try:
        return parse(inspect.getsource(type_def))
    except OSError as e:
        raise SourceException("Failed to get the source code.") from e
Esempio n. 11
0
def parse(source: str) -> AST:

    try:
        return ast.parse(source, feature_version=sys.version_info[0:2])
    except (AssertionError, NotImplementedError, SyntaxError, ValueError) as e:
        raise SourceException("Failed to parse the code.") from e
Esempio n. 12
0
    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)
Esempio n. 13
0
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
Esempio n. 14
0
def add_cls_inst(node: Module,
                 cls: str,
                 name: str,
                 kwargs: Optional[Dict] = None,
                 kwargs2parse: Optional[Dict] = None) -> None:
    class FindImport(NodeVisitor):
        def __init__(self, cls: str) -> None:

            self.found = False
            self.cls = cls

        def visit_ImportFrom(self, node: ImportFrom) -> None:

            for node_alias in node.names:
                if node_alias.name == self.cls:
                    self.found = True

            if not self.found:
                self.generic_visit(node)

    class FindClsInst(NodeVisitor):
        def __init__(self) -> None:

            self.found = False

        def visit_FunctionDef(self, node: FunctionDef) -> None:

            if node.name == 'main':

                for item in node.body:

                    if isinstance(item, Assign):

                        assert isinstance(item.targets[0], Name)

                        if item.targets[0].id == name:

                            # TODO assert for item.value
                            if item.value.func.id != cls:  # type: ignore
                                raise SourceException(
                                    "Name '{}' already used for instance of '{}'!"
                                    .format(
                                        name,
                                        item.value.func.id))  # type: ignore

                            self.found = True
                            # TODO update arguments?

            if not self.found:
                self.generic_visit(node)

    class AddClsInst(NodeTransformer):
        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

    find_import = FindImport(cls)
    find_import.visit(node)

    if not find_import.found:
        raise SourceException("Class '{}' not imported!".format(cls))

    vis = FindClsInst()
    vis.visit(node)

    if not vis.found:
        tr = AddClsInst()
        node = tr.visit(node)