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
def get_model(obj): """ Finds model root element for the given object. """ p = obj while _hasattr(p, 'parent'): p = _getattr(p, 'parent') return p
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)
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)
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
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
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
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
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)
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
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