BlockIt
|
00001 ## 00002 # @file blocks.py 00003 # @brief Contains the basic block class. 00004 # 00005 #............................................................................ 00006 # Copyright (c) 2009,2010,2011 David Car, david.car7@gmail.com 00007 # 00008 # This program is free software; you can redistribute it and/or modify it under 00009 # the terms of the GNU General Public License as published by the Free Software 00010 # Foundation; either version 2 of the License, or (at your option) any later 00011 # version. 00012 # 00013 # This program is distributed in the hope that it will be useful, but WITHOUT 00014 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 00015 # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 00016 # 00017 # You should have received a copy of the GNU General Public License along with 00018 # this program; if not, write to the Free Software Foundation, Inc., 59 Temple 00019 # Place, Suite 330, Boston, MA 02111-1307 USA 00020 #.......................................................................... 00021 from funcs import * 00022 from tables import SymbolTable 00023 from sets import ASet 00024 from parsers import pyp 00025 from time import time as sys_time 00026 import os 00027 import re 00028 import hashlib 00029 import copy 00030 from math import log 00031 from math import ceil 00032 00033 class Block(object): 00034 """This is the base block class. 00035 """ 00036 00037 #................................................................................ 00038 # pyparsing grammars 00039 #................................................................................ 00040 GRAMMAR = None 00041 SENTINEL = None 00042 CHILDTOKEN = None 00043 BLOCKTYPE='Block' 00044 #................................................................................ 00045 00046 def __init__(self, blockType, name, parent=None, childToken=None): 00047 self._parent = parent 00048 self._code = [] 00049 self._children = SymbolTable() 00050 self._name = name 00051 self._blockType = blockType 00052 self._childDeps = ASet() 00053 self._ownDeps = ASet() 00054 self._childToken = childToken 00055 self._depList = [] 00056 self._depIgnore = [] 00057 self._state = None 00058 self._fromCache = False 00059 self._timeStamp = sys_time() 00060 00061 #................................................................................ 00062 # pyparsing grammars 00063 #................................................................................ 00064 jc = self._children.joinCharacter() 00065 if self.SENTINEL is None: 00066 self.SENTINEL = \ 00067 pyp.StringStart() +\ 00068 pyp.CaselessKeyword('end') +\ 00069 pyp.CaselessKeyword(self._blockType) +\ 00070 pyp.CaselessKeyword(self._name) 00071 00072 if childToken is None: 00073 self.CHILDTOKEN = \ 00074 pyp.StringStart() +\ 00075 pyp.Suppress(pyp.Literal('#')) +\ 00076 pyp.Suppress(pyp.CaselessKeyword('Child:')) +\ 00077 pyp.Word(pyp.alphas, pyp.alphanums+'_~<>=()'+jc)('symbol') 00078 00079 def getTimeStamp( self ): 00080 """ Return the time stamp of the block. 00081 """ 00082 return self._timeStamp 00083 00084 def addLine( self, line, n=None ): 00085 '''Rudimentary method to add a line to the code listing of the block. 00086 00087 ''' 00088 self._code.append( line ) 00089 00090 def addDeps( self, sym, propagate=True ): 00091 '''Add an internal dependency and propagate up the tree. Do not add 00092 dependencies that are resolved by any of your children. This is 00093 checked by consulting the child nodes to see if they contain the 00094 dependency. Call the addChildDeps() on the parent. The dependency is 00095 given as a symbol name, not a set. 00096 00097 ''' 00098 if sym in self._depIgnore: return 00099 if self.contains(sym): return 00100 self._ownDeps.add(sym) 00101 if propagate and self._parent: self._parent.addChildDeps(sym) 00102 00103 def addChildDeps( self, sym, propagate=True, skipList=None ): 00104 '''Add the dependencies from a child block to the _childDeps set. 00105 00106 ''' 00107 if sym in self._depIgnore: return 00108 if self.contains( sym, skipList ): return 00109 self._childDeps.add( sym ) 00110 if propagate and self._parent: 00111 self._parent.addChildDeps( sym, propagate, skipList ) 00112 00113 def shareDeps( self, propagate=True ): 00114 '''Propagate the dependencies to the parent. If propagate is True, 00115 then continue up the tree. 00116 00117 ''' 00118 parent = self._parent 00119 if parent: 00120 for sym in self.dependencies(): 00121 parent.addChildDeps( sym, propagate, skipList=( self, ) ) 00122 00123 def contains( self, sym, skipList=None ): 00124 '''Search to see if block has symbol. 00125 00126 ''' 00127 if not self.isTemplated(): 00128 # if self.resolveSymbol( sym ) in self._children: return True 00129 if sym in self._children: return True 00130 if skipList is not None: 00131 _children = [ x for x in self._children.values() 00132 if x not in skipList ] 00133 else: 00134 _children = self._children.values() 00135 for child in _children: 00136 if child.contains( sym, skipList ): return True 00137 return False 00138 00139 def addChild( self, blk, token=None, before=False, regen=True ): 00140 '''Add a block as a child of this block. If a token string is given, then add 00141 this child as a token before/after this string in the code, else, 00142 just append it to the code at the end. 00143 00144 Inputs 00145 ------ 00146 blk : child block to add 00147 token : token string to use for adding line after/before 00148 before : boolean. If true, add child line before given token; else after. 00149 00150 ''' 00151 addChildDeps = self.addChildDeps 00152 00153 self._children.register( blk ) 00154 blk.setParent( self, regen ) 00155 blk.ignoreDependencies( self._depIgnore ) 00156 blk.removeDependencies( self._depIgnore ) 00157 00158 #................................................................. 00159 # Check if this new child resolves any dependencies 00160 #................................................................. 00161 for sym in list( self._ownDeps ): 00162 if blk.contains( sym ): 00163 self._ownDeps.discard( sym ) 00164 00165 for sym in list( self._childDeps ): 00166 if blk.contains( sym ): 00167 self._childDeps.discard( sym ) 00168 00169 #............................................................ 00170 # Now add the child dependencies to the parent 00171 #............................................................ 00172 for sym in blk.dependencies(): 00173 addChildDeps( sym ) 00174 00175 #............................................................ 00176 # Add the child line after/before token 00177 #............................................................ 00178 if token: 00179 try: 00180 n = self._code.index( token ) + 1 00181 except: 00182 n = None 00183 else: 00184 n = None 00185 00186 self.addLine( self.childString( blk.name() ), n ) 00187 00188 def mergeChildren( self, tbl ): 00189 '''Merge a symbol table with children into the block and add their 00190 dependencies. Does nothing to the underlying code base though. 00191 Useful only in certain circumstances. 00192 00193 ''' 00194 addChildDeps = self.addChildDeps 00195 for blk in tbl.values(): 00196 self._children.register( blk ) 00197 for sym in blk.dependencies(): 00198 addChildDeps( dep ) 00199 00200 def setParent(self, blk, regen=True): 00201 '''Set the parent block for this block. If this block has a parent, 00202 remove it from that blocks children. 00203 00204 ''' 00205 if self._parent is blk: return 00206 00207 if self._parent: 00208 self._parent.popChild( self.name(), regen ) 00209 00210 self._parent = blk 00211 00212 # def setChild(self, blk): 00213 # '''Add a child block to this block, but does not touch the code like the 00214 # addChild(...) method does. It simply puts the child into this blocks 00215 # local symbolTable (_children). 00216 00217 # ''' 00218 # self._children.register(blk) 00219 00220 def popChild(self, name, regen=True): 00221 '''Remove a child from this block. 00222 00223 ''' 00224 self._code.remove( self.childString( name ) ) 00225 child = self._children.pop( name ) 00226 if regen: self.regenChildDeps() 00227 return child 00228 00229 def regenChildDeps( self ): 00230 '''Clear the child dependency list and regenerate it from the children. 00231 00232 ''' 00233 self._childDeps.clear() 00234 for blk in self._children.values(): 00235 for dep in blk.dependencies(): 00236 self.addChildDeps( dep, propagate=False ) 00237 00238 if self._parent: self._parent.regenChildDeps() 00239 00240 def baseName( self ): 00241 '''Return the base, i.e. non-instantiated, non-mangled, name of the block. 00242 00243 ''' 00244 return self._name 00245 00246 @staticmethod 00247 def childString( name ): 00248 '''Create a child string token. 00249 00250 ''' 00251 return '# Child: %s%s' %( name, os.linesep ) 00252 00253 def name( self ): 00254 '''Return the name of the block. 00255 00256 ''' 00257 return self._name 00258 00259 def blockType(self): 00260 return self._blockType 00261 00262 def sentinel(self, line): 00263 if self.SENTINEL is None: return False 00264 if self.SENTINEL.searchString(line): 00265 self.addLine(line) 00266 return True 00267 return False 00268 00269 def children(self): 00270 return self._children 00271 00272 def dependencies(self): 00273 return self._ownDeps.union(self._childDeps) 00274 00275 def clearChildDependencies( self ): 00276 self._childDeps.clear() 00277 00278 def clearOwnDependencies( self ): 00279 self._ownDeps.clear() 00280 00281 def clearAllDependencies( self ): 00282 self.clearChildDependencies() 00283 self.clearOwnDependencies() 00284 00285 def renameDependencies( self, nameMap ): 00286 '''Rename an existing dependency in the dependency list and propagate 00287 to the children. 00288 00289 ''' 00290 if isinstance( nameMap, list ): 00291 for oldName,newName in nameMap: 00292 self._ownDeps.discard( oldName ) 00293 self._ownDeps.add( newName ) 00294 if oldName in self._childDeps: 00295 self._childDeps.discard( oldName ) 00296 self._childDeps.add( newName ) 00297 00298 for child in self._children: 00299 child.renameDependency( nameMap ) 00300 else: 00301 oldName, newName = nameMap 00302 self._ownDeps.discard( oldName ) 00303 self._ownDeps.add( newName ) 00304 if oldName in self._childDeps: 00305 self._childDeps.discard( oldName ) 00306 self._childDeps.add( newName ) 00307 00308 for child in self._children: 00309 child.renameDependency( oldName, newName ) 00310 00311 def removeDependencies( self, depList ): 00312 '''Remove a list of token dependencies from both dependencies lists 00313 and traverse down the tree recursively to the children and do the 00314 same. 00315 ''' 00316 for dep in depList: 00317 self._ownDeps.discard( dep ) 00318 self._childDeps.discard( dep ) 00319 for child in self._children.values(): 00320 child.removeDependencies( depList ) 00321 00322 def ignoreDependencies( self, depList ): 00323 '''A list of dependencies that should be ignored. 00324 00325 ''' 00326 self._depIgnore = copy.copy( depList ) 00327 for child in self._children.values(): 00328 child.ignoreDependencies( depList ) 00329 00330 def getIgnoreDependencies( self ): 00331 return copy.copy( self._depIgnore ) 00332 00333 def parent(self): 00334 return self._parent 00335 00336 def hasChild(self, blk): 00337 return (blk.name() in self._children) 00338 00339 def root(self): 00340 '''Return the root of the block tree starting at this block. 00341 00342 ''' 00343 if self._parent: 00344 return self._parent.root() 00345 return self 00346 00347 def upTo(self, blkType): 00348 '''Travel up the tree and return the first blkType found in this 00349 blocks lineage. 00350 00351 ''' 00352 if isinstance(self, blkType): return self 00353 if self._parent: 00354 return self._parent.upTo(blkType) 00355 return None 00356 00357 def objects(self): 00358 return self._objects 00359 00360 def code(self, lineNumbers=False): 00361 '''Show the code strings with or without line numbers. The default is 00362 no line numbers. 00363 00364 ''' 00365 block = '' 00366 _srcLength = int(ceil(log(len(self._code)+1)/log(10))) 00367 if lineNumbers: 00368 for n,line in enumerate(self._code): 00369 block += "%s %s%s"%(str(n).zfill(_srcLength+1),line.rstrip(), os.linesep) 00370 else: 00371 for line in self._code: 00372 block += line.rstrip()+os.linesep 00373 00374 return block 00375 00376 def codeList(self): 00377 return self._code 00378 00379 def show(self): 00380 '''Show the block unrolled. 00381 00382 ''' 00383 block = '' 00384 for line in self._code: 00385 child = self.CHILDTOKEN.searchString(line) 00386 if child: 00387 sym = child[0].symbol 00388 _blk = self._children.get(sym, None) 00389 if _blk is None: 00390 print 'Child %s has disappeared from block %s'%(sym, self.name()) 00391 line = '' 00392 else: 00393 line = _blk.show() 00394 block += line 00395 return block 00396 00397 def hash(self): 00398 '''Return a hashed value of this block if it does not already have a 00399 _hash internal value. Uses the mangled version for the hash. 00400 00401 ''' 00402 hsh = getattr(self, '_hash', None) 00403 if hsh is None: 00404 self._hash = hashlib.sha256(self.show(True)).hexdigest() 00405 return self._hash 00406 00407 def __getstate__(self, _dd=None): 00408 '''Builds a dictionary tree of the block for pickling. 00409 00410 ''' 00411 if _dd is None: _dd = self.__dict__.copy() 00412 _dd.pop('_children', None) 00413 _dd.pop('_parent', None) 00414 _dd.pop('SENTINEL', None) 00415 _dd.pop('CHILDTOKEN', None) 00416 _dd['_children'] = [] 00417 00418 for child in self.children().values(): 00419 _dd['_children'].append( child.__getstate__() ) 00420 return _dd 00421 00422 def __setstate__(self, tree): 00423 '''Unpack the dictionary tree. 00424 00425 ''' 00426 g = globals() 00427 self.__dict__.update( tree ) 00428 _children = self._children 00429 self._children = SymbolTable() 00430 00431 for node in _children: 00432 _blockType = node[ '_blockType' ] 00433 _params = node[ '_parameters' ] 00434 child = g[ _blockType ]( _params ) 00435 child.__setstate__( node ) 00436 child.setParent( self ) 00437 self._children.register( child ) 00438 00439 def unserialize(self, tree): 00440 self.__setstate__(tree) 00441 00442 def serialize(self, _dd=None): 00443 '''Builds a dictionary tree of the block. 00444 00445 ''' 00446 return self.__getattr__(_dd) 00447 00448 def toFile(self, name): 00449 '''Write the block to a special file. This will write everything 00450 necessary to recreate the block and all its children with the fromFile 00451 class method. 00452 00453 Input 00454 ----- 00455 name : block fileName to write it to 00456 00457 ''' 00458 00459 # Save the internal dictionary 00460 import pickle 00461 00462 tree = self.getDictTree() 00463 hsh = getattr('_hash',None) 00464 00465 output = open(name+'.pkl','wb') 00466 00467 # Dump the hash value and the dictionary tree 00468 pickle.dump((self.hash(), tree), output) 00469 output.close() 00470 00471 def lineArtGraph(self, tree=None): 00472 '''Display a line art graph for this block or the provided block tree. 00473 00474 ''' 00475 def _lineArtGraph(dt, tb=0): 00476 parent = dt 00477 if tb == 0: 00478 print parent['_blockType']+': '+parent['_name'] 00479 else: 00480 print (tb-1)*(5*' ')+'|___ '+parent['_blockType']+': '+parent['_name'] 00481 00482 for child in parent[ '_children' ]: 00483 # print tb*' '+'|' 00484 _lineArtGraph(child, tb+1) 00485 00486 _tree = tree or self.__getstate__() 00487 _lineArtGraph(_tree) 00488 00489 def findAll(self, blks, depth=-1): 00490 '''Find all types given in the blks tuple in your descendants to the 00491 given depth. If depth is < 0 , then recurse all the way to the 00492 leaves. 00493 00494 ''' 00495 #............................................................ 00496 def _findAll(myChildren, blks, matches, depth): 00497 00498 if depth == 0: return 00499 _depth = depth-1 00500 00501 for child in myChildren.values(): 00502 if isinstance(child, blks): 00503 matches.append(child) 00504 _findAll(child.children(), blks, matches, _depth) 00505 #............................................................ 00506 00507 matches = [] 00508 _findAll(self.children(), blks, matches, depth) 00509 return matches 00510 00511 def getFromCache(self): 00512 return self._fromCache 00513 00514 def setFromCache(self, value): 00515 if isinstance(value, bool): 00516 self._fromCache = value 00517 if not value and self._parent: self._parent.fromCache = value 00518 else: 00519 print "fromCache must be a boolean type" 00520 00521 #................................................................................ 00522 # Virtual Methods 00523 #................................................................................ 00524 def resolveSymbol(self, sym): 00525 raise NotImplementedError('''Virtual method 'resolveSymbol' in Block 00526 class''') 00527 00528 def instantiate(self, obj): 00529 raise NotImplementedError("Virtual method 'instantiate' in Block class ") 00530 00531 def getState(self): 00532 raise NotImplementedError("Virtual method 'getState' Block class") 00533 00534 def setState(self, subs): 00535 raise NotImplementedError("Virtual function 'setState' in Block class") 00536 00537 fromCache = property(getFromCache, setFromCache) 00538