Пример #1
0
 def find_obj(parent, name):
     if parent is not current_obj and \
             self.scope_redirection_logic is not None:
         from textx.scoping import Postponed
         res = self.scope_redirection_logic(parent)
         assert res is not None, \
             "scope_redirection_logic must not return None"
         if type(res) is Postponed:
             return res
         for m in res:
             return_value = find_obj(m, name)
             if return_value is not None:
                 return return_value
     for attr in [a for a in parent.__dict__ if
                  not a.startswith('__') and not
                  a.startswith('_tx_') and not
                  callable(getattr(parent, a))]:
         obj = getattr(parent, attr)
         if isinstance(obj, (list, tuple)):
             for innerobj in obj:
                 if _hasattr(innerobj, "name") \
                         and _getattr(innerobj, "name") == name:
                     return innerobj
         else:
             if _hasattr(obj, "name") \
                     and _getattr(obj, "name") == name:
                 return obj
     return None
Пример #2
0
def get_model(obj):
    """
    Finds model root element for the given object.
    """
    p = obj
    while _hasattr(p, 'parent'):
        p = _getattr(p, 'parent')
    return p
Пример #3
0
    def _load_referenced_models(self, model, encoding):
        from textx.model import get_children
        visited = []
        for obj in get_children(
                lambda x: hasattr(x, "importURI") and x not in visited, model):
            add_to_local_models = True
            if self.importURI_to_scope_name is not None:
                obj.name = self.importURI_to_scope_name(obj)
                # print("setting name to {}".format(obj.name))
            if _hasattr(obj, "name"):
                if _getattr(obj, "name") is not None \
                        and _getattr(obj, "name") != "":
                    add_to_local_models = not self.importAs

            visited.append(obj)
            if self.search_path is not None:
                # search_path based i/o:
                my_search_path = \
                    [dirname(model._tx_filename)] + self.search_path
                loaded_model = \
                    model._tx_model_repository.load_model_using_search_path(
                        self.importURI_converter(obj.importURI), model=model,
                        search_path=my_search_path, encoding=encoding,
                        add_to_local_models=add_to_local_models)
                obj._tx_loaded_models = [loaded_model]

            else:
                # globing based i/o:
                basedir = dirname(model._tx_filename)
                filename_pattern = abspath(
                    join(basedir, self.importURI_converter(obj.importURI)))

                obj._tx_loaded_models = \
                    model._tx_model_repository.load_models_using_filepattern(
                        filename_pattern, model=model,
                        glob_args=self.glob_args, encoding=encoding,
                        add_to_local_models=add_to_local_models)
Пример #4
0
    def follow(elem):

        if id(elem) in map(lambda x: id(x), collected):
            return

        # Use meta-model to search for all contained child elements.
        cls = elem.__class__

        if hasattr(cls, '_tx_attrs') and decider(elem):
            collected.append(elem)

        if hasattr(cls, '_tx_attrs'):
            for attr_name, attr in cls._tx_attrs.items():
                # Follow only attributes with containment semantics
                if attr.cont:
                    if attr.mult in (MULT_ONE, MULT_OPTIONAL):
                        new_elem = _getattr(elem, attr_name)
                        if new_elem:
                            follow(new_elem)
                    else:
                        new_elem_list = _getattr(elem, attr_name)
                        if new_elem_list:
                            for new_elem in new_elem_list:
                                follow(new_elem)
Пример #5
0
def get_parent_of_type(typ, obj):
    """
    Finds first object up the parent chain of the given type.
    If no parent of the given type exists None is returned.

    Args:
        typ(str or python class): The type of the model object we are
            looking for.
        obj (model object): Python model object which is the start of the
            search process.
    """
    if type(typ) is not text:
        typ = typ.__name__

    while _hasattr(obj, 'parent'):
        obj = _getattr(obj, 'parent')
        if obj.__class__.__name__ == typ:
            return obj
Пример #6
0
        def _find_referenced_obj(p, name, cls):
            """
            Helper function:
            Search the fully qualified name starting at relative container p.
            If no result is found, the search is continued from p.parent until
            the model root is reached.

            Args:
                p: parent object
                name: name to be found

            Returns:
                None or the found object
            """
            ret = _find_obj_fqn(p, name, cls)
            if ret:
                return ret
            while _hasattr(p, "parent"):
                p = _getattr(p, "parent")
                ret = _find_obj_fqn(p, name, cls)
                if ret:
                    return ret
Пример #7
0
    def __call__(self, obj, attr, obj_ref):
        """
        the default scope provider

        Args:
            obj: unused (used for multi_metamodel_support)
            attr: unused
            obj_ref: the cross reference to be resolved

        Returns:
            the resolved reference or None
        """
        from textx.const import RULE_COMMON, RULE_ABSTRACT
        from textx.model import ObjCrossRef
        from textx.scoping.tools import get_parser

        if obj_ref is None:
            return None  # an error! (see model.py: resolve_refs (TODO check)

        assert type(obj_ref) is ObjCrossRef, type(obj_ref)

        if get_parser(obj).debug:
            get_parser(obj).dprint("Resolving obj crossref: {}:{}"
                                   .format(obj_ref.cls, obj_ref.obj_name))

        def _inner_resolve_link_rule_ref(cls, obj_name):
            """
            Depth-first resolving of link rule reference.
            """
            if cls._tx_type is RULE_ABSTRACT:
                for inherited in cls._tx_inh_by:
                    result = _inner_resolve_link_rule_ref(inherited,
                                                          obj_name)
                    if result:
                        return result
            elif cls._tx_type == RULE_COMMON:
                # TODO make this code exchangable
                # allow to know the current attribute (model location for
                # namespace) and to navigate through the whole model...
                # OR (with another scope provider) to make custom lookups in
                # the model
                #
                # Scopeprovider
                # - needs: .current reference (in the model)
                #          .the model (?)
                # - provides: the resolved object or None
                if id(cls) in get_parser(obj)._instances:
                    objs = get_parser(obj)._instances[id(cls)]
                    return objs.get(obj_name)

        if self.multi_metamodel_support:
            from textx import get_model, get_children
            from textx import textx_isinstance
            result_lst = get_children(
                lambda x:
                _hasattr(x, "name") and _getattr(x, "name") == obj_ref.obj_name
                and textx_isinstance(x, obj_ref.cls), get_model(obj))
            if len(result_lst) == 1:
                result = result_lst[0]
            elif len(result_lst) > 1:
                line, col = get_parser(obj).pos_to_linecol(obj_ref.position)
                raise TextXSemanticError(
                    "name {} is not unique.".format(obj_ref.obj_name),
                    line=line, col=col, filename=get_model(obj)._tx_filename)
            else:
                result = None
        else:
            result = _inner_resolve_link_rule_ref(obj_ref.cls,
                                                  obj_ref.obj_name)
        if result:
            return result

        return None  # error handled outside
Пример #8
0
def resolve_model_path(obj,
                       dot_separated_name,
                       follow_named_element_in_lists=False):
    """
    Get a model object based on a model-path starting from some
    model object. It can be used in the same way you would
    navigate through a normal instance of a model object, except:
     - "parent(TYPE)" can be used to navigate to the parent of an
       element repeatedly, until a certain type is reached (see
       unittest).
     - lists of named objects (e.g. lists of named packages) can be
       traversed, as if the named objects were part of the model
       grammar (see unittest: Syntax,
       "name_of_model_list.name_of_named_obj_in_list").
     - None/Postponed values are intercepted and lead to an overall
       return value None/Postponed.
    A use case for this function is, when a model path needs to be
    stored and executed on a previously unknown object and/or the
    Postpone/None-logic is required.

    Args:
        obj: the current object
        dot_separated_name: the attribute name "a.b.c.d" starting from obj
           Note: the attribute "parent(TYPE)" is a shortcut to jump to the
           parent of type "TYPE" (exact match of type name).
        follow_named_element_in_lists: follow named elements in list if True
           override_unresolved_lists: try to follow unresolved lists, if True

    Returns:
        the object if found, or Postponed() if some postponed
        refs are found on the path / or obj is not found
    """
    from textx.scoping import Postponed
    names = dot_separated_name.split(".")
    match = re.match(r'parent\((\w+)\)', names[0])

    if obj is None or type(obj) is Postponed:
        return obj
    elif type(obj) is list:
        if follow_named_element_in_lists:
            next_obj = get_named_obj_in_list(obj, names[0])
        else:
            from textx.exceptions import TextXError
            raise TextXError(
                "unexpected: got list in path for get_referenced_object")
    elif match:
        next_obj = obj
        desired_parent_typename = match.group(1)
        next_obj = get_recursive_parent_with_typename(next_obj,
                                                      desired_parent_typename)
        if type(next_obj) is Postponed:
            return next_obj
        elif next_obj is not None:
            return resolve_model_path(next_obj, ".".join(names[1:]),
                                      follow_named_element_in_lists)
        else:
            return None
    else:
        next_obj = _getattr(obj, names[0])
        if needs_to_be_resolved(obj, names[0]):
            return Postponed()
        elif next_obj is None:
            return None
    if len(names) > 1:
        return resolve_model_path(next_obj, ".".join(names[1:]),
                                  follow_named_element_in_lists)
    return next_obj
Пример #9
0
    def resolve_one_step(self):
        """
        Resolves model references.
        """
        metamodel = self.parser.metamodel

        current_crossrefs = self.parser._crossrefs
        # print("DEBUG: Current crossrefs #: {}".
        #      format(len(current_crossrefs)))
        new_crossrefs = []
        self.delayed_crossrefs = []
        resolved_crossref_count = 0

        # -------------------------
        # start of resolve-loop
        # -------------------------
        default_scope = DefaultScopeProvider()
        for obj, attr, crossref in current_crossrefs:
            if (get_model(obj) == self.model):
                attr_value = _getattr(obj, attr.name)
                attr_refs = [
                    obj.__class__.__name__ + "." + attr.name, "*." + attr.name,
                    obj.__class__.__name__ + ".*", "*.*"
                ]
                for attr_ref in attr_refs:
                    if attr_ref in metamodel.scope_providers:
                        if self.parser.debug:
                            self.parser.dprint(" FOUND {}".format(attr_ref))
                        resolved = metamodel.scope_providers[attr_ref](
                            obj, attr, crossref)
                        break
                else:
                    resolved = default_scope(obj, attr, crossref)

                # Collect cross-references for textx-tools
                if resolved and not type(resolved) is Postponed:
                    if metamodel.textx_tools_support:
                        self.pos_crossref_list.append(
                            RefRulePosition(
                                name=crossref.obj_name,
                                ref_pos_start=crossref.position,
                                ref_pos_end=crossref.position +
                                len(resolved.name),
                                def_pos_start=resolved._tx_position,
                                def_pos_end=resolved._tx_position_end))

                if not resolved:
                    # As a fall-back search builtins if given
                    if metamodel.builtins:
                        if crossref.obj_name in metamodel.builtins:
                            from textx import textx_isinstance
                            if textx_isinstance(
                                    metamodel.builtins[crossref.obj_name],
                                    crossref.cls):
                                resolved = metamodel.builtins[
                                    crossref.obj_name]

                if not resolved:
                    line, col = self.parser.pos_to_linecol(crossref.position)
                    raise TextXSemanticError(
                        message='Unknown object "{}" of class "{}"'.format(
                            crossref.obj_name, crossref.cls.__name__),
                        line=line,
                        col=col,
                        err_type=UNKNOWN_OBJ_ERROR,
                        expected_obj_cls=crossref.cls,
                        filename=self.model._tx_filename)

                if type(resolved) is Postponed:
                    self.delayed_crossrefs.append((obj, attr, crossref))
                    new_crossrefs.append((obj, attr, crossref))
                else:
                    resolved_crossref_count += 1
                    if attr.mult in [MULT_ONEORMORE, MULT_ZEROORMORE]:
                        attr_value.append(resolved)
                    else:
                        _setattr(obj, attr.name, resolved)
            else:  # crossref not in model
                new_crossrefs.append((obj, attr, crossref))
        # -------------------------
        # end of resolve-loop
        # -------------------------
        # store cross-refs from other models in the parser list (for later
        # processing)
        self.parser._crossrefs = new_crossrefs
        # print("DEBUG: Next crossrefs #: {}".format(len(new_crossrefs)))
        return (resolved_crossref_count, self.delayed_crossrefs)
Пример #10
0
    def call_obj_processors(metamodel,
                            model_obj,
                            metaclass_of_grammar_rule=None):
        """
        Depth-first model object processing.
        """
        try:
            if metaclass_of_grammar_rule is None:
                metaclass_of_grammar_rule = \
                    metamodel[model_obj.__class__.__name__]
        except KeyError:
            raise TextXSemanticError('Unknown meta-class "{}".'.format(
                model.obj.__class__.__name__))

        if metaclass_of_grammar_rule._tx_type is RULE_MATCH:
            # Object processors for match rules are already called
            # in `process_match`
            return

        many = [MULT_ONEORMORE, MULT_ZEROORMORE]

        # return value of obj_processor
        return_value_grammar = None
        return_value_current = None

        # enter recursive visit of attributes only, if the class of the
        # object being processed is a meta class of the current meta model
        if model_obj.__class__.__name__ in metamodel:
            current_metaclass_of_obj = metamodel[model_obj.__class__.__name__]

            for metaattr in current_metaclass_of_obj._tx_attrs.values():
                # If attribute is base type or containment reference go down
                if metaattr.cont:
                    attr = _getattr(model_obj, metaattr.name)
                    if attr:
                        if metaattr.mult in many:
                            for idx, obj in enumerate(attr):
                                if obj:
                                    result = call_obj_processors(
                                        metamodel, obj, metaattr.cls)
                                    if result is not None:
                                        attr[idx] = result
                        else:
                            result = call_obj_processors(
                                metamodel, attr, metaattr.cls)
                            if result is not None:
                                _setattr(model_obj, metaattr.name, result)

            # call obj_proc of the current meta_class if type == RULE_ABSTRACT
            if current_metaclass_of_obj._tx_fqn !=\
                    metaclass_of_grammar_rule._tx_fqn:
                assert RULE_ABSTRACT == metaclass_of_grammar_rule._tx_type
                obj_processor_current = metamodel.obj_processors.get(
                    current_metaclass_of_obj.__name__, None)
                if obj_processor_current:
                    return_value_current = obj_processor_current(model_obj)

        # call obj_proc of rule found in grammar
        obj_processor_grammar = metamodel.obj_processors.get(
            metaclass_of_grammar_rule.__name__, None)
        if obj_processor_grammar:
            return_value_grammar = obj_processor_grammar(model_obj)

        # both obj_processors are called, if two different processors
        # are defined for the object metaclass and the grammar metaclass
        # (can happen with type==RULE_ABSTRACT):
        # e.g.
        #   Base: Special1|Special2;
        #   RuleCurrentlyChecked: att_to_be_checked=[Base]
        # with object processors defined for Base, Special1, and Special2.
        #
        # Both processors are called, but for the return value the
        # obj_processor corresponding to the object (e.g. of type Special1)
        # dominates over the obj_processor of the grammar rule (Base).
        #
        # The order they are called is: first object (e.g., Special1), then
        # the grammar based metaclass object processor (e.g., Base).
        if return_value_current is not None:
            return return_value_current
        else:
            return return_value_grammar  # may be None
Пример #11
0
    def process_node(node):
        if isinstance(node, Terminal):
            from arpeggio import RegExMatch
            if metamodel.use_regexp_group and \
                    isinstance(node.rule, RegExMatch):
                if node.rule.regex.groups == 1:
                    value = node.extra_info.group(1)
                    return metamodel.convert(value, node.rule_name)
                else:
                    return metamodel.convert(node.value, node.rule_name)
            else:
                return metamodel.convert(node.value, node.rule_name)

        assert node.rule.root, \
            "Not a root node: {}".format(node.rule.rule_name)
        # If this node is created by some root rule
        # create metaclass instance.
        inst = None
        if not node.rule_name.startswith('__asgn'):
            # If not assignment
            # Get class
            mclass = node.rule._tx_class

            if mclass._tx_type == RULE_ABSTRACT:
                # If this meta-class is product of abstract rule replace it
                # with matched concrete meta-class down the inheritance tree.
                # Abstract meta-class should never be instantiated.
                if len(node) > 1:
                    try:
                        return process_node(
                            next(n for n in node if type(n) is not Terminal
                                 and n.rule._tx_class is not RULE_MATCH))
                    except StopIteration:
                        # All nodes are match rules, do concatenation
                        return ''.join(text(n) for n in node)
                else:
                    return process_node(node[0])
            elif mclass._tx_type == RULE_MATCH:
                # If this is a product of match rule handle it as a RHS
                # of assignment and return converted python type.
                return process_match(node)

            if parser.debug:
                parser.dprint("CREATING INSTANCE {}".format(node.rule_name))

            # If user class is given
            # use it instead of generic one
            if node.rule_name in metamodel.user_classes:
                user_class = metamodel.user_classes[node.rule_name]

                # Object initialization will be done afterwards
                # At this point we need object to be allocated
                # So that nested object get correct reference
                inst = user_class.__new__(user_class)
                user_class._tx_obj_attrs[id(inst)] = {}

                # Initialize object attributes for user class
                parser.metamodel._init_obj_attrs(inst, user=True)
            else:
                # Generic class will call attributes init
                # from the constructor
                inst = mclass.__new__(mclass)

                # Initialize object attributes
                parser.metamodel._init_obj_attrs(inst)

            # Collect attributes directly on meta-class instance
            obj_attrs = inst

            try:
                inst._tx_position = node.position
                inst._tx_position_end = node.position_end
            except AttributeError:
                # Skip if class doesn't allow to set these attributes
                pass

            # Push real obj. and dummy attr obj on the instance stack
            parser._inst_stack.append((inst, obj_attrs))

            for n in node:
                if parser.debug:
                    parser.dprint("Recursing into {} = '{}'".format(
                        type(n).__name__, text(n)))
                process_node(n)

            parser._inst_stack.pop()

            # If this object is nested add 'parent' reference
            if parser._inst_stack:
                _setattr(obj_attrs, 'parent', parser._inst_stack[-1][0])

            # Special case for 'name' attrib. It is used for cross-referencing
            if _hasattr(inst, 'name') and _getattr(inst, 'name'):
                # Objects of each class are in its own namespace
                if not id(inst.__class__) in parser._instances:
                    parser._instances[id(inst.__class__)] = {}
                parser._instances[id(inst.__class__)][_getattr(inst, 'name')]\
                    = inst

            if parser.debug:
                parser.dprint("LEAVING INSTANCE {}".format(node.rule_name))

        else:
            # Handle assignments
            attr_name = node.rule._attr_name
            op = node.rule_name.split('_')[-1]
            model_obj, obj_attr = parser._inst_stack[-1]
            cls = type(model_obj)
            metaattr = cls._tx_attrs[attr_name]

            if parser.debug:
                parser.dprint('Handling assignment: {} {}...'.format(
                    op, attr_name))

            if op == 'optional':
                _setattr(obj_attr, attr_name, True)

            elif op == 'plain':
                attr_value = _getattr(obj_attr, attr_name)
                if attr_value and type(attr_value) is not list:
                    fmt = "Multiple assignments to attribute {} at {}"
                    raise TextXSemanticError(message=fmt.format(
                        attr_name, parser.pos_to_linecol(node.position)),
                                             err_type=MULT_ASSIGN_ERROR)

                # Convert tree bellow assignment to proper value
                value = process_node(node[0])

                if metaattr.ref and not metaattr.cont:
                    # If this is non-containing reference create ObjCrossRef
                    value = ObjCrossRef(obj_name=value,
                                        cls=metaattr.cls,
                                        position=node[0].position)
                    parser._crossrefs.append((model_obj, metaattr, value))
                    return model_obj

                if type(attr_value) is list:
                    attr_value.append(value)
                else:
                    _setattr(obj_attr, attr_name, value)

            elif op in ['list', 'oneormore', 'zeroormore']:
                for n in node:
                    # If the node is separator skip
                    if n.rule_name != 'sep':
                        # Convert node to proper type
                        # Rule links will be resolved later
                        value = process_node(n)

                        if metaattr.ref and not metaattr.cont:
                            # If this is non-containing reference
                            # create ObjCrossRef

                            value = ObjCrossRef(obj_name=value,
                                                cls=metaattr.cls,
                                                position=n.position)

                            parser._crossrefs.append(
                                (obj_attr, metaattr, value))
                            continue

                        if not _hasattr(obj_attr, attr_name) or \
                                _getattr(obj_attr, attr_name) is None:
                            _setattr(obj_attr, attr_name, [])
                        _getattr(obj_attr, attr_name).append(value)
            else:
                # This shouldn't happen
                assert False

        # Collect rules for textx-tools
        if inst and metamodel.textx_tools_support:
            pos = (inst._tx_position, inst._tx_position_end)
            pos_rule_dict[pos] = inst

        return inst