from qt import QObject,PYSIGNAL import math import compiler class SpreadSheet(QObject): _cells = {} _compiledcells={} tools = {} _cache = None _lastval={} #Reverse dependencies: if cellA is in _deps[cellB], then cellA depends on cellB _deps={} #What cells I am evaluating right now _eving=[] def __init__(self,parent): global obj QObject.__init__(self,parent) for name in dir(math): pass self.tools[name]=eval('math.'+name) def getformula(self, key): if key in self._cells: return self._cells[key] else: return '' def reCalculate(self,key): #recalculates all the dependencies of the key if key in self._deps: for dep in self._deps[key]: self.emit(PYSIGNAL("cellChanged"),(dep,self[dep])) self.reCalculate(dep) def __setitem__(self, key, formula): if formula.strip()=='': #Empty formula ## print "empty formula for", key if key in self._cells: del self._cells[key] del self._compiledcells[key] if key in self._deps: del self._deps[key] else: self._cells[key] = formula self._compiledcells[key] = compiler.compile(formula,"Formula for %s"%key,'eval') if key not in self._deps: self._deps[key]=[] #Since this a new formula, it doesn't yet depend on #any other cells. The dependencies will be #calculated when it's evaluated for k in self._deps: if key in self._deps[k]: self._deps[k].remove(key) def __getitem__(self, key ): ## print "getitem",key,self._eving #Dependency tree if key in self._eving: #Evaluating a cell in a loop ## print "Loop1" ## print self._deps,key self._eving=[] raise "Loop1" #whatever I am evaluating is a dependency #for the last key I was evaluating if len(self._eving)>0: if self._eving[-1] not in self._deps[key]: #But only once ## print "adding dep in %s for %s",self._eving[-1],key self._deps[key].append(self._eving[-1]) self._eving.append(key) bCache = self._cache is None if bCache: self._cache = {} while True: try: try: f=self._cells[key] if f.strip()=='': rv='' else: rv = eval(self._compiledcells[key], self.tools, self._cache) except KeyError: #key not in _cells rv='' break except NameError, ne: name = ne.args[0][6:-16] # Extract name from NameError if name in self._cells: self._cache[name] = self[name] elif name[0]=='_' and name[1:] in self._cells: self._cache[name] = self[name[1:]] elif isKey(name): #Default value is '' self[name]='' self._cache[name] = self[name] else: self._eving=[] raise if bCache: self._cache = None if self._lastval.has_key(key): if self._lastval[key] <> rv: self.emit(PYSIGNAL("cellChanged"),(key,rv)) ## print key,"changed from",self._lastval[key],'to',rv self._lastval[key]=rv self._eving.remove(key) ## print self._deps ## print "end eval of ",key,self._eving return rv def isKey(key): if (key[0].isalpha() and key[1:].isdigit()) or (key[0:1].isalpha() and key[2:].isdigit()): return True return False def coordKey(x,y): if x< 26: key=chr(97+x) else: key=chr(97+int(x/26))+chr(97+x%26) key=key+str(y+1) return key def keyCoord(key): if key[1].isalpha(): x=(ord(key[0])-97)*26+ord(key[1])-97 y=int(key[2:])-1 else: x=ord(key[0])-97 y=int(key[1:])-1 return (x,y) try: import psyco psyco.bind(SpreadSheet.__getitem__) except: pass if __name__ == "__main__": from math import sin, pi from pprint import pprint ss = SpreadSheet() ss.tools.update({'sin':sin, 'pi':pi}) ss.tools.update({'__builtins__':None}) ss['a1'] = '5' ss['a2'] = 'a1*6' ss['a3'] = 'a2*7' print "a3: ", ss['a3'] del ss.tools['__builtins__'] ss['b1'] = 'sin(pi/4)' print "b1: ", ss['b1'] print "b1 formula: ", ss.getformula('b1')