123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624 |
- '''This file is part of AToMPM - A Tool for Multi-Paradigm Modelling
- Copyright 2011 by the AToMPM team and licensed under the LGPL
- See COPYING.lesser and README.md in the root of this project for full details'''
- import re, json, uuid, itertools
- from dcal import DesignerCodeAbstractionLayer
- from utils import Utilities as utils
- from tconstants import TConstants as TC
- from pytcore.core.himesis import Himesis, HConstants as HC
- from pytcore.core.himesis import HimesisPreConditionPatternLHS
- from pytcore.core.himesis import HimesisPreConditionPatternNAC
- from pytcore.core.himesis import HimesisPostConditionPattern
- '''
- implements an atompm-to-himesis model compiler and an
- atompm-to-himesis rule compiler
-
- _username the user's username, needed to access files from user's
- folder
- _aswid the user's backend's aswid, needed to support httpreqs to
- backend from rule code
- _defaultDCL the user's preferred designer code language
- _subtypes a mapping from metamodel types to their subtypes
- _connectorTypes a set of known 'connector' fulltypes
- _knownMMs a set of known metamodels (i.e., metamodels for which we've
- computed subtype and connectorType information)
- _loadedMMs a set of currently loaded (on the asworker) metamodels
- _mmTypeData a mapping of fulltypes to default attributes
- _dcal an instance of the DesignerCodeAbstractionLayer
- _compiledRules a mapping between rule filenames and compiled data
- *runtime configuration flags*
- RC__looseSubtypingMM (see _computeSubtypes() for details) '''
- class ModelAndRuleCompiler :
- def __init__(self,username,aswid,defaultDCL,mtwid) :
- self._username = username
- self._aswid = aswid
- self._defaultDCL = defaultDCL
- self._subtypes = {}
- self._connectorTypes = set()
- self._knownMMs = set()
- self._loadedMMs = set()
- self._mmTypeData = {}
- self._mtwid = mtwid;
- self._dcal = DesignerCodeAbstractionLayer(username,aswid,mtwid)
- self._compiledRules = {}
- self.RC__looseSubtypingMM = None
-
- '''
- add a node to himesis graph 'hg' that reflects the contents of asworker
- node 'n' with id 'id' '''
- def addNode(self,hg,n,id) :
- newNodeIndex = \
- hg.add_node(n['$type'],n['$type'] in self._connectorTypes)
- hg.vs[newNodeIndex]['$atompmId'] = str(id)
- for attr in n :
- attr = str(attr)
- if attr.startswith('$') :
- hg.vs[newNodeIndex][attr] = n[attr]
- elif attr.startswith('____'):
- hg.vs[newNodeIndex][attr[2:]] = n[attr]['value']
- elif 'value' in n[attr]:
- hg.vs[newNodeIndex][attr] = n[attr]['value']
- return newNodeIndex
-
-
- '''
- compile an atompm json model into a pytcore himesis graph
- 0. compute and store the relevant metamodels' subtypes and connector
- types if we haven't already done so
- 1. initialise a Himesis/HimesisPreConditionPatternLHS/... graph 'hg'
- 2. in the special case where compileModel() is called as a result of a
- user loading a model (i.e., from ptcal.loadModel(), detectable because
- himesisBaseClass==Himesis), we also (re)set self._loadedMMs to match
- the given model's loaded metamodels
- 3. if input model is empty, return 'hg'
- 4. for each node in input model, create appropriate node in 'hg'
- 5. for each edge in input model, create appropriate edge in 'hg'
- 6. return 'hg'
- NOTE: igraph.add_vertices() crashes when attribute names are unicode
- strings (i.e., u'...')... this is why we convert attr names to
- ascii via 'attr = str(attr)' '''
- def compileModel(self,m,mmsData=None,name=None,himesisBaseClass=Himesis) :
- if m.__class__ != {}.__class__ :
- m = json.loads(m)
- if mmsData != None :
- if mmsData.__class__ != {}.__class__ :
- mmsData = json.loads(mmsData)
- for mm in mmsData :
- if mm not in self._knownMMs :
- self.parseMetamodel(mm,mmsData[mm])
- else :
- for mm in m['metamodels'] :
- if mm not in self._knownMMs :
- mmData = utils.fread(
- '/users/%s/%s.metamodel'%(self._username,mm))
- self.parseMetamodel(mm,mmData)
-
- hg = himesisBaseClass(name)
- hg[HC.METAMODELS] = set(m['metamodels'])
- hg[HC.MISSING_METAMODELS] = \
- lambda : hg[HC.METAMODELS] - self._loadedMMs
- hg[HC.GUID] = uuid.uuid4()
-
- if himesisBaseClass == Himesis :
- self._loadedMMs = hg[HC.METAMODELS].copy()
-
- if len(m['nodes']) == 0 :
- return hg
- atompmIds2himesisIndices = {}
- for id in m['nodes'] :
- atompmIds2himesisIndices[id] = self.addNode(hg,m['nodes'][id],id)
- for e in m['edges'] :
- hg.add_edges(
- [(atompmIds2himesisIndices[str(e['src'])],
- atompmIds2himesisIndices[str(e['dest'])])])
- return hg
- '''
- compile one rule (this function defines a number of inner functions, see
- bottom for actual logic) given its atompm model 'r' and its filename
- 'fname'
- 1. compile LHS, NACs and RHS into himesis graphs
- 2. add remaining properties to compiled patterns (e.g., LHS.NACs) '''
- def compileRule(self,r,fname) :
- if fname in self._compiledRules :
- return self._compiledRules[fname]
- elif r == None :
- r = utils.fread('/users/%s/%s'%(self._username,fname))
- '''
- compile a pattern (e.g. LHS) into a himesis graph
- 1. retrieve atompm model(s) of pattern(s) contents
- 2. validate __pLabels
- 3. foreach pattern>contents pair from step 1
- a) rename metamodels from (.*).pattern to just $1
- b) foreach node in contents
- i. rename type from (.*).pattern/__p(.*) to $1/$2
- ii. rename __pLabel to HC.MT_LABEL (and delete __pLabel attr)
- iii. rename __pMatchSubtypes to HC.MT_SUBTYPE_MATCH, if any (and
- delete __pMatchSubtypes)
- (the model is now ready to be fed to compileModel)
- c) compile contents into himesis graph, 'hg'
- d) wrap attribute and pattern action/condition code into python
- functions
- NOTE: to complete pattern compilation, certain properties still need to
- be added (e.g., pointer to NACs in LHS) '''
- def compilePattern(patternType,himesisBaseClass) :
- p2pcm = getPatternContents(patternType)
- ''' hergin :: motif-integration :: modify start '''
- if p2pcm==None or len(p2pcm)==0 or len(p2pcm.keys()) == 0 :
- ''' hergin :: motif-integration :: modify end '''
- return []
- res = validateLabels(p2pcm)
- if '$err' in res :
- return res
- hgs = []
- for p,pcm in p2pcm.iteritems() :
- mms = []
- for mm in pcm['metamodels'] :
- if re.search('.pattern$',mm):
- mms.append(mm[:-len('.pattern')])
- elif re.search('.ramified$',mm):
- mms.append(mm[:-len('.ramified')])
- pcm['metamodels'] = mms
-
- for id in pcm['nodes'] :
- n = pcm['nodes'][id]
- matches = re.match('(.*)\.pattern/__p(.*)',n['$type']) or re.match('(.*)\.ramified/__p(.*)',n['$type'])
- n['$type'] = matches.group(1)+'/'+matches.group(2)
- n[HC.MT_LABEL] = n['__pLabel']['value']
- del n['__pLabel']
- ''' hergin :: motif-integration start '''
- if '__pPivotIn' in n:
- if n['__pPivotIn']['value']:
- n[HC.MT_PIVOT_IN] = n['__pPivotIn']['value']
- del n['__pPivotIn']
- if '__pPivotOut' in n:
- if n['__pPivotOut']['value']:
- n[HC.MT_PIVOT_OUT] = n['__pPivotOut']['value']
- del n['__pPivotOut']
- ''' hergin :: motif-integration end '''
- if 'value' in n['__pMatchSubtypes'] :
- n[HC.MT_SUBTYPE_MATCH] = n['__pMatchSubtypes']['value']
- del n['__pMatchSubtypes']
- hg = self.compileModel(
- pcm,
- name=fname+'_'+patternType,
- himesisBaseClass=himesisBaseClass)
- if patternType == 'LHS' or patternType == 'NAC' :
- wrapAttributeDesignerCode(hg,'attrCondition')
- hg[HC.MT_CONSTRAINT] = r['nodes'][p]['Condition']['value']
- wrapPatternConditionDesignerCode(hg)
- for v in hg.vs :
- v[HC.MT_DIRTY] = False
- v[HC.MT_SUBTYPES] = (self._subtypes[v[HC.FULLTYPE]] if v[HC.FULLTYPE] in self._subtypes else [])
- elif patternType == 'RHS' :
- wrapAttributeDesignerCode(hg,'attrAction')
- hg[HC.MT_ACTION] = r['nodes'][p]['Action']['value']
- wrapPatternActionDesignerCode(hg)
- elif patternType == 'RHSImport' :
- def wrapImportedModelAttribute(val) :
- return lambda arg1,arg2 : val
- for v in hg.vs :
- for attr,val in v.attributes().iteritems() :
- if Himesis.is_RAM_attribute(attr) and val != None :
- v[attr] = wrapImportedModelAttribute(val)
- hg[HC.MT_ACTION] = ''
- wrapPatternActionDesignerCode(hg)
- hgs.append(hg)
- return hgs
- '''
- read in and slightly alter a model such that it can pass as a
- traditional RHS's contents... this functions return format is
- this identical to that of getPatternContents(..)
- 1. find the RHSImport node
- 2. read in the model it refers to
- 3. update each of that model's nodes s.t.
- a) they have valid __pLabels
- b) they have empty __matchSubtypes
- c) they have pattern types
- 4. update each of that model's metamodels s.t. that become pattern
- metamodels '''
- def getImportedModelAsPatternContents() :
- pc = {}
- for id in r['nodes'] :
- if re.search('/RHSImport$',r['nodes'][id]['$type']) :
- pc[id] = utils.fread(
- '/users/%s/%s'%(self._username,
- r['nodes'][id]['filename']['value']))
- for nid in pc[id]['nodes'] :
- n = pc[id]['nodes'][nid]
- n['__pLabel'] = {'type':'string','value':'$IM_'+str(nid)}
- n['__pMatchSubtypes'] = {}
- matches = re.match('(.*)/(.*)',n['$type'])
- n['$type'] = matches.group(1)+'.pattern/__p'+matches.group(2)
- pc[id]['metamodels'] = \
- map(lambda mm : mm+'.pattern', pc[id]['metamodels'])
- return pc
-
- '''
- return a dict of the form {...,id:contents,...} where 'id' describes a
- node of pattern-type 'pt' (e.g., LHS) and 'contents' is an atompm model
- that contains only 'id''s contents
- 0. if 'pt' is not LHS, NAC, RHS, outsource operation
- 1. identify all nodes of type 'pt'
- 2. return {} if no matches
- 3. map each of step 1's results to every single node that is
- [transitively] connected to it (except its PatternContents links)...
- this effectively maps patterns to their contents
- 4. based on results from step 3, map each match from step 1 to an
- atompm model that contains only the pattern's contents
- 5. return map from step 4
-
- NOTE:: the outNeighbors() and getConnectedNodes() inner functions are
- translated from identical javascript functions in
- mmmk.compileToIconDefinitionMetamodel
- '''
- def getPatternContents(pt) :
- if pt == 'RHSImport' :
- return getImportedModelAsPatternContents()
- def outNeighbors(source) :
- return map(lambda x: str(x['dest']),
- filter(lambda y : y['src'] == source, r['edges']))
- def getConnectedNodes(container,contents) :
- _contents = set()
- for n in outNeighbors(container) :
- if not n in contents :
- _contents.add(n)
- if len(_contents) == 0 :
- return contents
-
- contents = contents | _contents
- return set(utils.flatten(
- map(lambda x: getConnectedNodes(x,contents),_contents)))
- pc = {}
- for id in r['nodes'] :
- if re.search('/'+pt+'$',r['nodes'][id]['$type']) :
- pc[id] = []
- if len(pc) == 0 :
- return {}
- for p in pc :
- pc[p] = filter(
- lambda x: \
- r['nodes'][x]['$type'] != TC.RULEMM+'/PatternContents', \
- getConnectedNodes(p,set()))
- m = {'nodes':{},'edges':[],'metamodels':[]}
- mms = []
- for id in pc[p] :
- m['nodes'][id] = r['nodes'][id]
- mms.append( utils.getMetamodel(r['nodes'][id]['$type']) )
- m['metamodels'] = list(set(mms))
- m['edges'] = \
- filter(
- lambda e : e['src'] in m['nodes'], r['edges'])
- pc[p] = m
-
- return pc
- '''
- ensure none of the nodes specified in the provided list of {patternId:
- patternContentsModel} have empty or duplicate __pLabels... return error
- if any '''
- def validateLabels(p2pcm) :
- for p,pcm in p2pcm.iteritems() :
- for id in pcm['nodes'] :
- if '__pLabel' not in pcm['nodes'][id] :
- return {'$err':'missing __pLabel attribute'}
- l = pcm['nodes'][id]['__pLabel']['value']
- if l == '' :
- return {'$err':'empty __pLabel'}
- elif len(
- filter(
- lambda x: pcm['nodes'][x]['__pLabel']['value'] == l,
- pcm['nodes']) ) > 1 :
- return {'$err':'duplicate __pLabel :: '+l}
- return {}
- '''
- store a function that evaluates designer-specified javascript attribute
- action/condition code as the value of every RAM attribute (i.e., of
- every non-Himesis attribute or atompm $ attribute)
-
- NOTE: a little quirk of igraph is that all vertices have all attributes
- (e.g., v1['a']=5, v2['b']=5
- >> v1:{'a':5,'b':None}, v2:{'a':None,'b':6})... thus, we make
- sure to only wrap 'real' attributes to avoid 'false' attributes
- becoming non-None '''
- def wrapAttributeDesignerCode(hg,type) :
- '''
- return a python function that will properly execute designer-
- specified javascript action/condition code '''
- def wrap(code,pLabel,attr) :
- def evalAttrCode(pLabel2graphIndexMap,graph):
- if code == '' :
- if type == 'attrCondition':
- return True
- else :
- return
- ex = {}
- try :
- self._dcal.configure(
- self._dcal.identifyLanguage(code) or self._defaultDCL,
- graph,
- type,
- pLabel2graphIndexMap,
- ex,
- pLabel,
- attr)
- return self._dcal.eval(code)
- except Exception as e :
- if '$err' in ex :
- raise RuntimeError(ex['$err'])
- else :
- raise RuntimeError(\
- 'unexpected error encountered while evaluating '+
- type+' :: '+str(e))
- return evalAttrCode
- for v in hg.vs :
- for attr,code in v.attributes().iteritems() :
- if Himesis.is_RAM_attribute(attr) and code != None :
- v[attr] = wrap(code,v[HC.MT_LABEL],attr)
- '''
- store a function that evaluates designer-specified javascript pattern
- action code as the value of pattern[MT_ACTION] '''
- def wrapPatternActionDesignerCode(hg) :
- def wrap(code) :
- def evalPatternCode(pLabel2graphIndexMap,graph):
- if code == '' :
- return []
- journal = []
- ex = {}
- try :
- self._dcal.configure(
- self._dcal.identifyLanguage(code) or self._defaultDCL,
- graph,
- 'patternAction',
- pLabel2graphIndexMap,
- ex,
- journal=journal)
- self._dcal.eval(code)
- return journal
- except Exception as e :
- if '$err' in ex :
- raise RuntimeError(ex['$err'])
- else :
- raise RuntimeError(\
- 'unexpected error encountered while evaluating '+
- 'pattern action code :: '+str(e))
- return evalPatternCode
- hg[HC.MT_ACTION] = wrap(hg[HC.MT_ACTION])
- '''
- store a function that evaluates designer-specified javascript pattern
- condition code as the value of pattern[MT_CONSTRAINT] '''
- def wrapPatternConditionDesignerCode(hg) :
- def wrap(code) :
- def evalPatternCode(pLabel2graphIndexMap,graph) :
- if code == '' :
- return True
- ex = {}
- try :
- self._dcal.configure(
- self._dcal.identifyLanguage(code) or self._defaultDCL,
- graph,
- 'patternCondition',
- pLabel2graphIndexMap,
- ex)
- return self._dcal.eval(code)
- except Exception as e :
- if '$err' in ex :
- raise RuntimeError(ex['$err'])
- else :
- raise RuntimeError(\
- 'unexpected error encountered while evaluating '+
- 'pattern condition code :: '+str(e))
- return evalPatternCode
- hg[HC.MT_CONSTRAINT] = wrap(hg[HC.MT_CONSTRAINT])
- lhs = compilePattern('LHS',HimesisPreConditionPatternLHS)
- if lhs.__class__ == {}.__class__ :
- raise ValueError(fname+' LHS compilation failed on :: '+lhs['$err'])
- nacs = compilePattern('NAC',HimesisPreConditionPatternNAC)
- if nacs.__class__ == {}.__class__ :
- raise ValueError(fname+' NAC compilation failed on :: '+nacs['$err'])
- rhs = compilePattern('RHS',HimesisPostConditionPattern) or \
- compilePattern('RHSImport',HimesisPostConditionPattern)
- if rhs.__class__ == {}.__class__ :
- raise ValueError(fname+' RHS compilation failed on :: '+rhs['$err'])
- #lhs[0].NACs = nacs
- for nac in nacs :
- nac.LHS = lhs[0]
- nac.bridge = nac.compute_bridge()
-
- #lhs[0].NACs = nacs
-
- lhs[0].addNACs(nacs)
-
- ''' hergin :: motif-integration start '''
- ''' check condition for RHS for query rule '''
- if len(rhs)>0:
- rhs[0].pre = lhs[0]
- if lhs[0].vcount() > 0 :
- rhs[0].pre_labels = lhs[0].vs[HC.MT_LABEL]
- else :
- rhs[0].pre_labels = []
-
- self._compiledRules[fname] = {'lhs':lhs[0],'rhs':rhs[0]}
- else:
- self._compiledRules[fname] = {'lhs':lhs[0]}
- ''' hergin :: motif-integration end '''
- return self._compiledRules[fname]
-
- '''
- remember the types stored in the 'connectorTypes' property of the passed
- metamodel '''
- def _computeConnectorTypes(self,mm,mmData) :
- for ct in mmData['connectorTypes'].keys() :
- self._connectorTypes.add(mm+'/'+ct)
- '''
- remember the information required to initialize nodes from any of mm's
- types to their default values '''
- def _computeMMTypeData(self,mm,mmData) :
- for type in mmData['types'] :
- fulltype = mm+'/'+type
- self._mmTypeData[fulltype] = {}
- for attr in mmData['types'][type] :
- # if there is no default, provide an empty string
- try:
- self._mmTypeData[fulltype][attr['name']] = attr['default']
- except KeyError:
- self._mmTypeData[fulltype][attr['name']] = ""
- '''
- using the 'types2parentTypes' property of the passed metamodel, construct
- and save a mapping of metamodel types to their subtypes
-
- NOTE:: to EASE SEMANTICS SHARING (paramount when working with semantic
- templates), we introduce the 'RC__looseSubtypingMM' compiler
- flag... it is used to allow mapToBaseFormalism semantics to be
- seamlessly used by "looseSubtyping" DSLs... see example below:
- 1 mapToBaseFormalism rule:
- match BasicState subtypes, produce BasicState
- 2 base formalism subtypes:
- SimpleStateChart/BasicState subtypes = []
- 3 loose subtyping formalism subtypes:
- MyDSL/BasicState subtypes = [AAA, BBB]
- 4 result
- SimpleStateChart/BasicState subtypes =
- [MyDSL/BasicState, MyDSL/AAA, MyDSL/BBB]
- >> rule can now match entities from MyDSL '''
- def _computeSubtypes(self,mm,mmData) :
- t2pt = mmData['types2parentTypes']
- types = t2pt.keys()
- parents = set(itertools.chain.from_iterable(t2pt.values()))
- children = filter(lambda t: t2pt[t] != [], types)
- for type in types :
- fulltype = mm+'/'+type
- if fulltype not in self._subtypes :
- self._subtypes[fulltype] = []
- if type in parents :
- for c in children :
- if type in t2pt[c] :
- self._subtypes[fulltype].append(mm+'/'+c)
- if self.RC__looseSubtypingMM and \
- self.RC__looseSubtypingMM+'/'+type in self._subtypes :
- self._subtypes[fulltype].append(self.RC__looseSubtypingMM+'/'+type)
- self._subtypes[fulltype].extend(
- self._subtypes[self.RC__looseSubtypingMM+'/'+type])
-
- '''
- forget all compiled rules '''
- def forgetCompiledRules(self) :
- self._compiledRules = {}
-
- '''
- return a reference to self._mmTypeData '''
- def getMMTypeData(self) :
- return self._mmTypeData
- '''
- compute and store the specified metamodel's default attributes, subtypes
- and connector types
- 1. if we already know 'mm' (e.g., user may be re-loading it or a newer
- version of it),
- a) clear known subtypes for that 'mm' in-place... we do this clearing
- in-place (i.e., del L[:] vs. L = []) because compiled pattern nodes
- have pointers to entries in self._subtypes... this implies that to
- avoid having to recompile rules when we alter self._subtypes,
- alterations made to existing entries need to preserve pointers
- b) clear known connector types for that 'mm'
- 2. do the deed
- 3. if 'loadMM' is specified (i.e., this function is called as a result of
- a user LOADMM changelog, not as a result of encountering an unknown
- metamodel while compiling a rule '''
- def parseMetamodel(self,mm,mmData,loadMM=False) :
- if mm in self._knownMMs :
- for type in mmData['types'] :
- fulltype = mm+'/'+type
- if fulltype in self._subtypes :
- self._subtypes[fulltype][:] = []
- if fulltype in self._connectorTypes :
- self._connectorTypes.remove(fulltype)
- self._computeSubtypes(mm,mmData)
- self._computeConnectorTypes(mm,mmData)
- self._computeMMTypeData(mm,mmData)
- self._knownMMs.add(mm)
- if loadMM :
- self._loadedMMs.add(mm)
-
- '''
- remove a metamodel from the list of currently loaded (on the asworker)
- metamodels '''
- def unloadMetamodel(self,mm):
- if mm in self._loadedMMs:
- self._loadedMMs.remove(mm)
|