Package PyDSTool :: Module parseUtils
[hide private]
[frames] | no frames]

Source Code for Module PyDSTool.parseUtils

   1  """
 
   2      Parser utilities.
 
   3  
 
   4      Robert Clewley, September 2005.
 
   5  
 
   6      Includes AST code by Pearu Peterson and Ryan Gutenkunst,
 
   7        modified by R. Clewley.
 
   8  """ 
   9  
 
  10  # IMPORTS
 
  11  from __future__ import division 
  12  from errors import * 
  13  from common import * 
  14  import re 
  15  import math, random 
  16  from numpy import alltrue, sometrue 
  17  import numpy as np 
  18  from copy import copy, deepcopy 
  19  import parser, symbol, token 
  20  
 
  21  # --------------------------------------------------------------------------
 
  22  # GLOBAL BEHAVIOUR CONTROLS -- leave both as True for use with PyDSTool.
 
  23  # switch to change output from using 'x**y' (DO_POW=False)
 
  24  # to 'pow(x,y)' (DO_POW=TRUE)
 
  25  DO_POW=True 
  26  # switch to activate treatment of integer constants as decimals in expressions
 
  27  # and simplifications of expressions.
 
  28  DO_DEC=True 
  29  
 
  30  # ------------------------------------------------------------------------
 
  31  ### Protected names
 
  32  
 
  33  protected_scipynames = ['sign', 'mod'] 
  34  specialfns = ['airy', 'airye', 'ai_zeros', 'bi_zeros', 'ellipj',
 
  35              'ellipk', 'ellipkinc', 'ellipe', 'ellipeinc', 'jn',
 
  36              'jv', 'jve', 'yn', 'yv', 'yve', 'kn', 'kv', 'kve',
 
  37              'iv', 'ive', 'hankel1', 'hankel1e', 'hankel2',
 
  38              'hankel2e', 'lmbda', 'jnjnp_zeros', 'jnyn_zeros',
 
  39              'jn_zeros', 'jnp_zeros', 'yn_zeros', 'ynp_zeros',
 
  40              'y0_zeros', 'y1_zeros', 'y1p_zeros', 'j0', 'j1',
 
  41              'y0', 'y1', 'i0', 'i0e', 'i1', 'i1e', 'k0', 'k0e',
 
  42              'k1', 'k1e', 'itj0y0', 'it2j0y0', 'iti0k0', 'it2i0k0',
 
  43              'besselpoly', 'jvp', 'yvp', 'kvp', 'ivp', 'h1vp',
 
  44              'h2vp', 'sph_jn', 'sph_yn', 'sph_jnyn', 'sph_in',
 
  45              'sph_kn', 'sph_inkn', 'riccati_jn', 'riccati_yn',
 
  46              'struve', 'modstruve', 'itstruve0', 'it2struve0',
 
  47              'itmodstruve0', 'bdtr', 'bdtrc', 'bdtri', 'btdtr',
 
  48              'btdtri', 'fdtr', 'fdtrc', 'fdtri', 'gdtr', 'gdtrc',
 
  49              'gdtria', 'nbdtr', 'nbdtrc', 'nbdtri', 'pdtr', 'pdtrc',
 
  50              'pdtri', 'stdtr', 'stdtridf', 'stdtrit', 'chdtr', 'chdtrc',
 
  51              'chdtri', 'ndtr', 'ndtri', 'smirnov', 'smirnovi',
 
  52              'kolmogorov', 'kolmogi', 'tklmbda', 'gamma',
 
  53              'gammaln', 'gammainc', 'gammaincinv', 'gammaincc',
 
  54              'gammainccinv', 'beta', 'betaln', 'betainc',
 
  55              'betaincinv', 'psi',
 
  56              'digamma', 'rgamma', 'polygamma', 'erf', 'erfc',
 
  57              'erfinv', 'erfcinv', 'erf_zeros', 'fresnel',
 
  58              'fresnel_zeros', 'fresnelc_zeros', 'fresnels_zeros',
 
  59              'modfresnelp', 'modfresnelm', 'lpn', 'lqn', 'lpmn',
 
  60              'lqmn', 'lpmv', 'sph_harm', 'legendre', 'chebyt',
 
  61              'chebyu', 'chebyc', 'chebys', 'jacobi', 'laguerre',
 
  62              'genlaguerre', 'hermite', 'hermitenorm', 'gegenbauer',
 
  63              'sh_legendre', 'sh_chebyt', 'sh_chebyu', 'sh_jacobi',
 
  64              'hyp2f1', 'hyp1f1', 'hyperu', 'hyp0f1', 'hyp2f0',
 
  65              'hyp1f2', 'hyp3f0', 'pbdv', 'pbvv', 'pbwa', 'pbdv_seq',
 
  66              'pbvv_seq', 'pbdn_seq', 'mathieu_a', 'mathieu_b',
 
  67              'mathieu_even_coef', 'mathieu_odd_coef', 'mathieu_cem',
 
  68              'mathieu_sem', 'mathieu_modcem1', 'mathieu_modcem2',
 
  69              'mathieu_modsem1', 'mathieu_modsem2', 'pro_ang1',
 
  70              'pro_rad1', 'pro_rad2', 'obl_ang1', 'obl_rad1',
 
  71              'obl_rad2', 'pro_cv', 'obl_cv', 'pro_cv_seq',
 
  72              'obl_cv_seq', 'pro_ang1_cv', 'pro_rad1_cv',
 
  73              'pro_rad2_cv', 'obl_ang1_cv', 'obl_rad1_cv',
 
  74              'obl_rad2_cv', 'kelvin', 'kelvin_zeros', 'ber',
 
  75              'bei', 'berp', 'beip', 'ker', 'kei', 'kerp', 'keip',
 
  76              'ber_zeros', 'bei_zeros', 'berp_zeros', 'beip_zeros',
 
  77              'ker_zeros', 'kei_zeros', 'kerp_zeros', 'keip_zeros',
 
  78              'expn', 'exp1', 'expi', 'wofz', 'dawsn', 'shichi',
 
  79              'sici', 'spence', 'zeta', 'zetac', 'cbrt', 'exp10',
 
  80              'exp2', 'radian', 'cosdg', 'sindg', 'tandg', 'cotdg',
 
  81              'log1p', 'expm1', 'cosm1', 'round'] 
  82  protected_specialfns = ['special_'+s for s in specialfns] 
  83  protected_mathnames = filter(lambda s: not s.startswith('__'), \
 
  84                                   dir(math)) 
  85  protected_randomnames = filter(lambda s: not s.startswith('_'), \
 
  86                                   dir(random))  # yes, just single _ 
  87  # We add internal default auxiliary function names for use by
 
  88  # functional specifications.
 
  89  builtin_auxnames = ['globalindepvar', 'initcond', 'heav', 'if',
 
  90                      'getindex', 'getbound'] 
  91  
 
  92  protected_macronames = ['for', 'if', 'max', 'min', 'sum'] 
  93  
 
  94  reserved_keywords = ['and', 'not', 'or', 'del', 'for', 'if', 'is', 'raise',
 
  95                  'assert', 'elif', 'from', 'lambda', 'return', 'break', 'else',
 
  96                  'global', 'try', 'class', 'except', 'while',
 
  97                  'continue', 'exec', 'import', 'pass', 'yield', 'def',
 
  98                  'finally', 'in', 'print', 'as', 'None'] 
  99  
 
 100  convert_power_reserved_keywords = ['del', 'for', 'if', 'is', 'raise',
 
 101                  'assert', 'elif', 'from', 'lambda', 'return', 'break', 'else',
 
 102                  'global', 'try', 'class', 'except', 'while',
 
 103                  'continue', 'exec', 'import', 'pass', 'yield', 'def',
 
 104                  'finally', 'in', 'print', 'as', 'None'] 
 105  
 
 106  # 'abs' is defined in python core, so doesn't appear in math
 
 107  protected_allnames = protected_mathnames + protected_scipynames \
 
 108                      + protected_specialfns + protected_randomnames \
 
 109                      + builtin_auxnames + protected_macronames \
 
 110                      + ['abs', 'pow', 'min', 'max', 'sum'] 
 111  
 
 112  # signature lengths for builtin auxiliary functions and macros, for
 
 113  # use by ModelSpec in eval() method (to create correct-signatured temporary
 
 114  # functions).
 
 115  builtinFnSigInfo = {'globalindepvar': 1, 'initcond': 1, 'heav': 1, 'getindex': 1,
 
 116                      'if': 3, 'for': 4, 'getbound': 2, 'max': 1, 'min': 1} 
 117  
 
 118  # ------------------------------------------------------------------------
 
 119  
 
 120  # EXPORTS
 
 121  _functions = ['readArgs', 'findEndBrace', 'makeParList', 'joinStrs',
 
 122                'parseMatrixStrToDictStr', 'joinAsStrs', 'replaceSep',
 
 123                'wrapArgInCall', 'addArgToCalls', 'findNumTailPos',
 
 124                'isToken', 'isNameToken', 'isNumericToken', 'count_sep',
 
 125                'isHierarchicalName', 'replaceSepInv', 'replaceSepListInv',
 
 126                'replaceSepList', 'convertPowers', 'mapNames',
 
 127                'replaceCallsWithDummies', 'isIntegerToken', 'proper_match',
 
 128                'remove_indices_from_range'] 
 129  
 
 130  _objects = ['protected_auxnamesDB', 'protected_allnames', 'protected_macronames',
 
 131              'protected_mathnames', 'protected_randomnames', 'builtin_auxnames',
 
 132              'protected_scipynames', 'protected_specialfns', 'builtinFnSigInfo'] 
 133  
 
 134  _classes = ['symbolMapClass', 'parserObject', 'auxfnDBclass'] 
 135  
 
 136  _constants = ['name_chars_RE', 'num_chars', 'ZEROS', 'ONES', 'NAMESEP',
 
 137                '_indentstr'] 
 138  
 
 139  _symbfuncs = ['simplify', 'simplify_str', 'ensurebare', 'ensureparen',
 
 140               'trysimple', 'ensuredecimalconst', 'doneg', 'dosub', 'doadd',
 
 141               'dodiv', 'domul', 'dopower', 'splitastLR', 'ast2string', 'string2ast',
 
 142               'sym2name', 'ast2shortlist', 'splitargs', 'mapPowStr',
 
 143               'toPowSyntax', 'ensureparen_div'] 
 144  
 
 145  _symbconsts = ['syms'] 
 146  
 
 147  __all__ = _functions + _classes + _objects + _constants + _symbfuncs + _symbconsts 
 148  
 
 149  #-----------------------------------------------------------------------------
 
 150  
 
 151  ## constants for parsing
 
 152  name_chars_RE = re.compile('\w') 
 153  alphabet_chars_RE = re.compile('[a-zA-Z0-9]')   # without the '_' 
 154  num_chars = map(lambda i: str(i), range(10)) 
 155  
 
 156  if DO_POW: 
 157      POW_STR = 'pow(%s,%s)' 
 158  else: 
 159      POW_STR = '%s**%s' 
 160  
 
 161  if DO_DEC: 
 162      ZEROS = ['0','0.0','0.','(0)','(0.0)','(0.)'] 
 163      ONES = ['1','1.0','1.','(1)','(1.0)','(1.)'] 
 164      TENS = ['10', '10.0', '10.','(10)','(10.0)','(10.)'] 
 165  else: 
 166      ZEROS = ['0'] 
 167      ONES = ['1'] 
 168      TENS = ['10'] 
 169  
 
 170  # separator for compound, hierarchical names
 
 171  NAMESEP = '.' 
 172  
 
 173  # for python indentation in automatically generated code, use 4 spaces
 
 174  _indentstr = "    " 
 175  
 
 176  
 
 177  # ----------------------------------------------------------------------------
 
 178  # This section: code by Pearu Peterson, adapted by Ryan Gutenkunst
 
 179  #   and Robert Clewley.
 
 180  
 
 181  syms=token.tok_name 
 182  for s in symbol.sym_name.keys(): 
 183      syms[s]=symbol.sym_name[s] 
 184  
 
 185  
 
186 -def mapPowStr(t, p='**'):
187 """Input an expression of the form 'pow(x,y)'. Outputs an expression of the 188 form x**y, x^y, or pow(x,y) and where x and y have also been processed to the 189 target power syntax. 190 Written by R. Clewley""" 191 ll = splitargs(ast2string(t[2])[1:-1]) 192 if p=='**': 193 lpart = dopower(ensureparen(ast2string(toDoubleStarSyntax(string2ast(ll[0]))),1), 194 ensureparen(ast2string(toDoubleStarSyntax(string2ast(ll[1]))),1), 195 '%s**%s') 196 if len(t) > 3: 197 res = ensureparen(ast2string(toDoubleStarSyntax(['power',string2ast('__LPART__')]+t[3:]))) 198 return res.replace('__LPART__', lpart) 199 else: 200 return ensureparen(lpart,1) 201 elif p=='^': 202 lpart = dopower(ensureparen(ast2string(toCircumflexSyntax(string2ast(ll[0]))),1), 203 ensureparen(ast2string(toCircumflexSyntax(string2ast(ll[1]))),1), 204 '%s^%s') 205 if len(t) > 3: 206 res = ensureparen(ast2string(toCircumflexSyntax(['power',string2ast('__LPART__')]+t[3:])),1) 207 return res.replace('__LPART__', lpart) 208 else: 209 return ensureparen(lpart,1) 210 elif p=='pow': 211 lpart = dopower(ensurebare(ast2string(toPowSyntax(string2ast(ll[0])))), 212 ensurebare(ast2string(toPowSyntax(string2ast(ll[1])))), 213 'pow(%s,%s)') 214 if len(t) > 3: 215 res = ensurebare(ast2string(toPowSyntax(['power',string2ast('__LPART__')]+t[3:]))) 216 return res.replace('__LPART__', lpart) 217 else: 218 return ensureparen(lpart,1) 219 else: 220 raise ValueError("Invalid power operator")
221 222
223 -def toCircumflexSyntax(t):
224 # R. Clewley 225 if isinstance(t[0], str): 226 if t[0] == 'power': 227 if t[2][0] == 'DOUBLESTAR': 228 return string2ast(ensureparen(dopower(ast2string(toCircumflexSyntax(t[1])), 229 ast2string(toCircumflexSyntax(t[3])), 230 '%s^%s'),1)) 231 if t[1] == ['NAME', 'pow']: 232 return string2ast(ensureparen(mapPowStr(t,'^'),1)) 233 o = [] 234 for i in t: 235 if isinstance(i,list): 236 if type(i[0]) == str and i[0].islower(): 237 o.append(toCircumflexSyntax(i)) 238 else: 239 o.append(i) 240 else: 241 o.append(i) 242 return o
243 244
245 -def toDoubleStarSyntax(t):
246 # R. Clewley 247 if isinstance(t[0], str): 248 if t[0] == 'xor_expr' and t[2][0]=='CIRCUMFLEX': 249 # ^ syntax has complex binding rules in python parser's AST! 250 # trick - easy to convert to ** first. then, using a bit of a hack 251 # convert to string and back to AST so that proper AST for ** 252 # is formed. 253 tc = copy(t) 254 tc[0] = 'power' 255 tc[2] = ['DOUBLESTAR', '**'] 256 return toDoubleStarSyntax(string2ast(ast2string(tc))) # yes, i mean this 257 if t[0] == 'power' and t[1] == ['NAME', 'pow']: 258 return string2ast(ensureparen(mapPowStr(t,'**'),1)) 259 o = [] 260 for i in t: 261 if isinstance(i,list): 262 if type(i[0]) == str and i[0].islower(): 263 o.append(toDoubleStarSyntax(i)) 264 else: 265 o.append(i) 266 else: 267 o.append(i) 268 return o
269 270
271 -def toPowSyntax(t):
272 # R. Clewley 273 if isinstance(t[0],str): 274 if t[0] == 'power': 275 try: 276 if t[2][0]=='DOUBLESTAR': 277 try: 278 return string2ast(dopower(ensurebare(ast2string(toPowSyntax(t[1]))), 279 ensurebare(ast2string(toPowSyntax(t[3]))), 280 'pow(%s,%s)')) 281 except IndexError: 282 # there's a pow statement already here, not a ** 283 # so ignore 284 return t 285 elif t[1][1] == 'pow': 286 return string2ast(ensureparen(mapPowStr(t,'pow'),1)) 287 elif len(t)>3 and t[3][0]=='DOUBLESTAR': 288 try: 289 return string2ast(dopower(ensurebare(ast2string(toPowSyntax(t[1:3]))), 290 ensurebare(ast2string(toPowSyntax(t[4]))), 291 'pow(%s,%s)')) 292 except IndexError: 293 # there's a pow statement already here, not a ** 294 # so ignore 295 return t 296 except: 297 print t 298 print ast2string(t) 299 raise 300 elif t[0] == 'xor_expr' and t[2][0]=='CIRCUMFLEX': 301 # ^ syntax has complex binding rules in python parser's AST! 302 # trick - easy to convert to ** first. then, using a bit of a hack 303 # convert to string and back to AST so that proper AST for ** 304 # is formed. 305 tc = copy(t) 306 tc[0] = 'power' 307 tc[2] = ['DOUBLESTAR', '**'] 308 return toPowSyntax(string2ast(ast2string(tc))) # yes, i mean this 309 o = [] 310 for i in t: 311 if isinstance(i,list): 312 if type(i[0]) == str and i[0].islower(): 313 o.append(toPowSyntax(i)) 314 else: 315 o.append(i) 316 else: 317 o.append(i) 318 return o
319 320
321 -def temp_macro_names(s):
322 t = s 323 for m in convert_power_reserved_keywords: 324 t = t.replace(m, '__'+m+'__') 325 return t
326
327 -def temp_macro_names_inv(s):
328 t = s 329 for m in convert_power_reserved_keywords: 330 t = t.replace('__'+m+'__', m) 331 return t
332 333
334 -def convertPowers(s, target="pow"):
335 """convertPowers takes a string argument and maps all occurrences 336 of power sub-expressions into the chosen target syntax. That option 337 is one of "**", "^", or "pow".""" 338 # temp_macro_names switches python reserved keywords to adapted names 339 # that won't make the python parser used in string2ast raise a syntax error 340 # ... but only invoke this relatively expensive function in the unlikely 341 # event a clash occurs. 342 # R. Clewley 343 if target=="**": 344 try: 345 return ast2string(toDoubleStarSyntax(string2ast(s))) 346 except SyntaxError: 347 s = temp_macro_names(s) 348 return temp_macro_names_inv(ast2string(toDoubleStarSyntax(string2ast(s)))) 349 elif target=="^": 350 try: 351 return ast2string(ensureints(toCircumflexSyntax(string2ast(s)))) 352 except SyntaxError: 353 s = temp_macro_names(s) 354 return temp_macro_names_inv(ast2string(ensureints(toCircumflexSyntax(string2ast(s))))) 355 elif target=="pow": 356 try: 357 return ast2string(toPowSyntax(string2ast(s))) 358 except SyntaxError: 359 s = temp_macro_names(s) 360 return temp_macro_names_inv(ast2string(toPowSyntax(string2ast(s)))) 361 else: 362 raise ValueError("Invalid target syntax")
363 364
365 -def ensureints(t):
366 """Ensure that any floating point constants appearing in t that are 367 round numbers get converted to integer representations.""" 368 if type(t)==str: 369 return ast2string(ensureints(string2ast(t))) 370 o = [] 371 for i in t: 372 if type(i) == list: 373 if type(i[0]) == str and i[0].islower(): 374 o.append(ensureints(i)) 375 elif i[0]=='NUMBER': 376 # CAPS for constants 377 o.append(string2ast(trysimple(ast2string(i)))) 378 else: 379 o.append(i) 380 else: 381 o.append(i) 382 return o
383 384
385 -def splitargs(da, lbraces=['('], rbraces=[')']):
386 """Function to split string-delimited arguments in a string without 387 being fooled by those that occur in function calls. 388 Written by Pearu Peterson. Adapted by Rob Clewley to accept different 389 braces.""" 390 if alltrue([da.find(lbrace)<0 for lbrace in lbraces]): 391 return da.split(',') 392 ll=[];o='';ii=0 393 for i in da: 394 if i==',' and ii==0: 395 ll.append(o) 396 o='' 397 else: 398 if i in lbraces: ii=ii+1 399 if i in rbraces: ii=ii-1 400 o=o+i 401 ll.append(o) 402 return ll
403
404 -def ast2shortlist(t):
405 if type(t) is parser.ASTType: return ast2shortlist(t.tolist()) 406 if not isinstance(t, list): return t 407 if t[1] == '': return None 408 if not isinstance(t[1], list): return t 409 if len(t) == 2 and isinstance(t[1], list): 410 return ast2shortlist(t[1]) 411 o=[] 412 for tt in map(ast2shortlist, t[1:]): 413 if tt is not None: 414 o.append(tt) 415 if len(o)==1: return o[0] 416 return [t[0]]+o
417
418 -def sym2name(t):
419 if type(t) is parser.ASTType: return sym2name(t.tolist()) 420 if not isinstance(t, list): return t 421 return [syms[t[0]]]+map(sym2name,t[1:])
422
423 -def string2ast(t):
424 return sym2name(ast2shortlist(parser.expr(t)))
425
426 -def ast2string(t):
427 #if isinstance(t, str): return t 428 if type(t) is parser.ASTType: return ast2string(t.tolist()) 429 if not isinstance(t, list): return None 430 if not isinstance(t[1], list): return t[1] 431 o='' 432 for tt in map(ast2string,t): 433 if isinstance(tt, str): 434 o=o+tt 435 return o
436
437 -def splitastLR(t):
438 lft=t[1] 439 rt=t[3:] 440 if len(rt)>1: 441 rt=[t[0]]+rt 442 else: 443 rt=rt[0] 444 return lft,rt
445
446 -def dopower(l,r,pow_str=POW_STR):
447 if r in ZEROS: return '1' 448 if l in ZEROS: return '0' 449 if l in ONES: return '1' 450 if r in ONES: return l 451 if pow_str=='%s**%s': 452 return trysimple('%s**%s'%(ensureparen(l),ensureparen(r))) 453 elif pow_str == '%s^%s': 454 return trysimple('%s^%s'%(ensuredecimalconst(ensureparen(l)),ensuredecimalconst(ensureparen(r)))) 455 elif pow_str == 'pow(%s,%s)': 456 return trysimple('pow(%s,%s)'%(l,r)) #trysimple('pow(%s,%s)'%(ensurebare(l),ensurebare(r))) 457 else: 458 raise ValueError("Invalid target power syntax")
459
460 -def domul(l,r):
461 if l in ZEROS or r in ZEROS: return '0' 462 if l in ONES: return r 463 if r in ONES: return l 464 if l in ['-'+o for o in ONES]: return doneg(r) 465 if r in ['-'+o for o in ONES]: return doneg(l) 466 lft = string2ast(l) 467 rt = string2ast(r) 468 lft_neg = lft[0] == 'factor' and lft[1][0]=='MINUS' 469 rt_neg = rt[0] == 'factor' and rt[1][0]=='MINUS' 470 if lft_neg: 471 new_l = l[1:] 472 else: 473 new_l = l 474 if rt_neg: 475 new_r = r[1:] 476 else: 477 new_r = r 478 if lft_neg and rt_neg or not (lft_neg or rt_neg): 479 return trysimple('%s*%s'%(ensureparen(new_l,ismul=1), 480 ensureparen(new_r,ismul=1))) 481 else: 482 return trysimple('-%s*%s'%(ensureparen(new_l,ismul=1), 483 ensureparen(new_r,ismul=1)))
484
485 -def dodiv(l,r):
486 if r in ZEROS: raise ValueError("Division by zero in expression") 487 if l in ZEROS: return '0' 488 if r in ONES: return l 489 ## if l in ['-'+o for o in ONES]: return doneg(dodiv('1',r)) 490 if r in ['-'+o for o in ONES]: return doneg(l) 491 if r==l: return '1' 492 lft = string2ast(l) 493 rt = string2ast(r) 494 lft_neg = lft[0] == 'factor' and lft[1][0]=='MINUS' 495 rt_neg = rt[0] == 'factor' and rt[1][0]=='MINUS' 496 if lft_neg: 497 new_l = l[1:] 498 else: 499 new_l = l 500 if rt_neg: 501 new_r = r[1:] 502 else: 503 new_r = r 504 if lft_neg and rt_neg or not (lft_neg or rt_neg): 505 return trysimple('%s/%s'%(ensureparen(ensuredecimalconst(new_l),ismul=1,do_decimal=DO_DEC), 506 ensureparen(ensuredecimalconst(new_r),1,do_decimal=DO_DEC)), 507 do_decimal=DO_DEC) 508 else: 509 return trysimple('-%s/%s'%(ensureparen(ensuredecimalconst(new_l),ismul=1,do_decimal=DO_DEC), 510 ensureparen(ensuredecimalconst(new_r),1,do_decimal=DO_DEC)), 511 do_decimal=DO_DEC)
512
513 -def doadd(l,r):
514 if l in ZEROS and r in ZEROS: return '0' 515 if l in ZEROS: return r 516 if r in ZEROS: return l 517 if l==r: return trysimple(domul('2',l)) 518 if r[0]=='-': return trysimple('%s%s'%(l,r)) 519 return trysimple('%s+%s'%(l,r))
520
521 -def dosub(l,r):
522 if l in ZEROS and r in ZEROS: return '0' 523 if l in ZEROS: return doneg(r) 524 if r in ZEROS: return l 525 if l==r: return '0' 526 if r[0]=='-': return ensureparen(trysimple('%s+%s'%(l,doneg(r)))) 527 return trysimple('%s-%s'%(l,r))
528
529 -def doneg(l):
530 if l in ZEROS: return '0' 531 ## if l[0]=='-': return l[1:] 532 t=string2ast(l) 533 ## print "doneg called with %s"%l, "\n", t, "\n" 534 if t[0]=='atom' and t[1][0]=='LPAR' and t[-1][0]=='RPAR': 535 return ensureparen(doneg(ast2string(t[2]))) 536 if t[0]=='arith_expr': 537 # Propagate -ve sign into the sum 538 o=doneg(ast2string(t[1])) 539 aexpr = t[2:] 540 for i in range(0,len(aexpr),2): 541 if aexpr[i][0]=='PLUS': 542 o = dosub(o, ast2string(aexpr[i+1])) 543 else: 544 o = doadd(o, ast2string(aexpr[i+1])) 545 return o 546 if t[0]=='term': 547 # Propagate -ve sign onto the first term 548 tc = copy(t) 549 if t[1][0]=='factor' and t[1][1][0]=='MINUS': 550 tc[1] = t[1][2] 551 else: 552 tc[1] = string2ast(doneg(ast2string(t[1]))) 553 return ast2string(tc) 554 if t[0]=='factor' and t[1][0] == 'MINUS': 555 return ast2string(t[2]) 556 return trysimple('-%s'%l)
557
558 -def ensuredecimalconst(t):
559 return trysimple(t,do_decimal=DO_DEC)
560
561 -def trysimple(t,do_decimal=False):
562 try: 563 t_e = eval(t, {}, {}) 564 add_point = do_decimal and t_e != 0 and DO_DEC 565 if type(t_e) == int and add_point: 566 t = repr(t_e)+".0" 567 elif type(t_e) == float and int(t_e)==t_e: 568 # explicitly use repr here in case t string was e.g. '2e7' 569 # which evals to a float, but the string itself represents an int 570 if add_point: 571 t = repr(t_e) 572 else: 573 t = repr(int(t_e)) 574 else: 575 t = repr(t_e) 576 except: 577 pass 578 return t
579
580 -def ensureparen_div(tt):
581 if tt[0] == 'term' and tt[2][0] == 'SLASH' and len(tt[3:])>1: 582 return ['term', 583 string2ast(ensureparen(ast2string(tt[1])+'/'+ast2string(tt[3]), 584 1))] + tt[4:] 585 else: 586 return tt
587
588 -def ensureparen(t,flag=0,ismul=0,do_decimal=False):
589 t=trysimple(t, do_decimal) 590 tt=string2ast(t) 591 if t[0]=='-': 592 if tt[0] == 'factor': # or tt[0] == 'term' and ismul: 593 # single number doesn't need braces 594 return t 595 else: 596 ## print "0: ", t, "->", '(%s)'%t 597 return '(%s)'%t 598 if tt[0]=='arith_expr': 599 ## print "1: ", t, "->", '(%s)'%t 600 return '(%s)'%t 601 if flag>0: 602 if tt[0] == 'term': 603 return '(%s)'%t 604 elif tt[0] == 'power': 605 if tt[1] == ['NAME', 'pow']: 606 return t 607 else: 608 # ** case 609 for x in tt[1:]: 610 if x[0] == 'arith_expr': 611 return '(%s)'%t 612 elif tt[0] == 'xor_expr': # added xor_expr for ^ powers 613 if len(tt)>3: 614 for x in tt[1:]: 615 if x[0] == 'arith_expr': 616 return '(%s)'%t 617 else: 618 return t 619 ## if t[0]=='(' and t[-1]==')' and t[1:-1].find('(') < t[1:-1].find(')'): 620 ## # e.g. (1+x) doesn't need another set of braces 621 ## print "2: ", t, "->", t 622 ## return t 623 ## else: 624 ## # e.g. (1+x)-(3-y) does need braces 625 ## print "3: ", t, "->", '(%s)'%t 626 ## return '(%s)'%t 627 return t
628
629 -def ensurebare(t):
630 """Ensure no braces in string expression (where possible). 631 Written by Robert Clewley""" 632 t=trysimple(t) 633 try: 634 if t[0]=='(' and t[-1]==')': 635 if t[1:-1].find('(') < t[1:-1].find(')'): 636 return t[1:-1] 637 else: 638 # false positive, e.g. (1+x)-(3-y) 639 return t 640 else: 641 return t 642 except IndexError: 643 return t
644
645 -def collect_numbers(t):
646 """Re-arrange 'term' or 'arith_expr' expressions to combine numbers. 647 Numbers go first unless in a sum the numeric term is negative and not all 648 the remaining terms are negative.""" 649 # number may be prefixed by a 'factor' 650 ## print "collect called with %s"%ast2string(t), "\n", t, "\n" 651 if t[0] == 'arith_expr': 652 args = [simplify(a) for a in t[1::2]] 653 numargs = len(args) 654 if args[0][0] == 'factor' and args[0][1][0] == 'MINUS': 655 ops = [-1] 656 # remove negative sign from term as we've recorded the sign for the sum 657 args[0] = args[0][2] 658 else: 659 ops = [1] 660 for op_t in t[2::2]: 661 if op_t[0]=='PLUS': 662 ops.append(1) 663 else: 664 ops.append(-1) 665 num_ixs = [] 666 oth_ixs = [] 667 for i, a in enumerate(args): 668 if a[0]=='NUMBER': 669 num_ixs.append(i) 670 else: 671 oth_ixs.append(i) 672 res_num = '0' 673 # enter numbers first 674 for nix in num_ixs: 675 if ops[nix] > 0: 676 res_num=doadd(res_num,ast2string(simplify(args[nix]))) 677 else: 678 res_num=dosub(res_num,ast2string(simplify(args[nix]))) 679 # follow by other terms 680 res_oth = '0' 681 for oix in oth_ixs: 682 if ops[oix] > 0: 683 res_oth=doadd(res_oth,ast2string(simplify(args[oix]))) 684 else: 685 res_oth=dosub(res_oth,ast2string(simplify(args[oix]))) 686 if res_num[0] == '-' and res_oth[0] != '-': 687 # switch order 688 return string2ast(doadd(res_oth,res_num)) 689 else: 690 return string2ast(doadd(res_num,res_oth)) 691 elif t[0] == 'term': 692 args = [simplify(a) for a in t[1::2]] 693 numargs = len(args) 694 ops = [1] 695 for op_t in t[2::2]: 696 if op_t[0]=='STAR': 697 ops.append(1) 698 else: 699 ops.append(-1) 700 num_ixs = [] 701 oth_ixs = [] 702 for i, a in enumerate(args): 703 if a[0]=='NUMBER': 704 num_ixs.append(i) 705 else: 706 oth_ixs.append(i) 707 res_numerator = '1' 708 res_denominator = '1' 709 # enter numbers first 710 for nix in num_ixs: 711 if ops[nix] > 0: 712 res_numerator=domul(res_numerator,ast2string(simplify(args[nix]))) 713 else: 714 res_denominator=domul(res_denominator,ast2string(simplify(args[nix]))) 715 # follow by other terms 716 for oix in oth_ixs: 717 if ops[oix] > 0: 718 res_numerator=domul(res_numerator,ast2string(simplify(args[oix]))) 719 else: 720 res_denominator=domul(res_denominator,ast2string(simplify(args[oix]))) 721 return string2ast(dodiv(res_numerator,res_denominator)) 722 else: 723 return t
724
725 -def simplify_str(s):
726 return s
727 ## """String output version of simplify""" 728 ## t=string2ast(s) 729 ## if isinstance(t, list) and len(t) == 1: 730 ## return ast2string(simplify(t[0])) 731 ## if t[0]=='NUMBER': 732 ## if 'e' in s: 733 ## epos=s.find('e') 734 ## man=s[:epos] 735 ## ex=s[epos:] 736 ## else: 737 ## ex='' 738 ## man=s 739 ## if man[-1] == '.': 740 ## return man+'0'+ex 741 ## else: 742 ## return s 743 ## if t[0]=='NAME': 744 ## return s 745 ## if t[0]=='factor': 746 ## return doneg(ensureparen(ast2string(simplify(t[2:][0])))) 747 ## if t[0]=='arith_expr': 748 ## return ast2string(collect_numbers(t)) 749 ## if t[0]=='term': 750 ## return ast2string(collect_numbers(ensureparen_div(t))) 751 ## if t[0]=='power': # covers math functions like sin, cos, and log10 752 ## if t[2][0]=='trailer': 753 ## if len(t)>3: 754 ## term1 = simplify(t[:3]) 755 ## if term1[0] in ['NUMBER', 'NAME']: #'power' 756 ## formatstr='%s' 757 ## else: 758 ## formatstr='(%s)' 759 ## return ast2string([t[0],string2ast(formatstr%ast2string(term1)), 760 ## simplify(t[3:])]) 761 ## elif len(t)==3 and t[1][1]=='pow': 762 ## # 'pow' syntax case 763 ## terms = t[2][2][1::2] 764 ## return dopower(ast2string(simplify(terms[0])), 765 ## ast2string(simplify(terms[1]))) 766 ## # else ignore and carry on 767 ## ts=[];o=[]; 768 ## for i in t[1:]: 769 ## if i[0]=='DOUBLESTAR': 770 ## if len(o)==1: o=o[0] 771 ## ts.append(o); 772 ## o=[] 773 ## else: o.append(simplify(i)) 774 ## if len(o)==1: o=o[0] 775 ## ts.append(o) 776 ## if t[2][0]=='DOUBLESTAR': 777 ## st,lft,rt=map(ast2string,[t,ts[0],ts[1]]) 778 ## return dopower(simplify_str(lft),simplify_str(rt)) 779 ## if t[2][0]=='trailer': 780 ## return ast2string(simplify(ts[0])) 781 ## if t[0] in ['arglist','testlist']: 782 ## o=[] 783 ## for i in t[1::2]: 784 ## o.append(ast2string(simplify(i))) 785 ## return ','.join(o) 786 ## if t[0]=='atom': 787 #### if t[1][0]=='LPAR' and t[-1][0]=='RPAR': 788 #### return ast2string(simplify(t[2:-1])) 789 #### else: 790 ## return ensureparen(ast2string(simplify(t[2:-1]))) 791 ## if t[1][0]=='trailer': # t=[[NAME,f],[trailer,[(],[ll],[)]]] 792 ## # just simplify arguments to functions 793 ## return ast2string([['NAME',t[0][1]], ['trailer',['LPAR','('], 794 ## simplify(t[1][2]),['RPAR',')']]]) 795 ## return s 796 797
798 -def simplify(t):
799 return t
800 ## """Attempt to simplify symbolic expression string. 801 ## Adapted by R. Clewley from original DiffStr() code by Pearu Peterson and Ryan Gutenkunst. 802 ## 803 ## Essentially the same format as DiffStr() except we just move down the 804 ## syntax tree calling appropriate 'do' functions on the parts to 805 ## simplify them.""" 806 ## if isinstance(t, list) and len(t) == 1: 807 ## return simplify(t[0]) 808 ## if t[0]=='NUMBER': 809 ## if 'e' in t[1]: 810 ## epos=t[1].find('e') 811 ## man=t[1][:epos] 812 ## ex=t[1][epos:] 813 ## else: 814 ## man=t[1] 815 ## ex='' 816 ## if man[-1] == '.': 817 ## return ['NUMBER', man+'0'+ex] 818 ## else: 819 ## return t 820 ## if t[0]=='NAME': 821 ## return t 822 ## if t[0]=='factor': 823 ## return string2ast(doneg(ensureparen(ast2string(simplify(t[2:][0]))))) 824 ## if t[0]=='arith_expr': 825 ## return collect_numbers(t) 826 ## if t[0]=='term': 827 ## return collect_numbers(ensureparen_div(t)) 828 ## if t[0]=='xor_expr' and t[2]=='CIRCUMFLEX': # covers alternative power syntax 829 ## alt = copy(t) 830 ## alt[0] = 'power'; alt[2]=='DOUBLESTAR' 831 ## return toCircumflexSyntax(simplify(alt)) 832 ## if t[0]=='power': # covers math functions like sin, cos and log10 833 ## if t[2][0]=='trailer': 834 ## if len(t)>3: 835 ## term1 = simplify(t[:3]) 836 ## if term1[0] in ['NUMBER', 'NAME']: # 'power' 837 ## formatstr='%s' 838 ## else: 839 ## formatstr='(%s)' 840 ## return [t[0],string2ast(formatstr%ast2string(term1)),simplify(t[3:])] 841 ## elif len(t)==3 and t[1][1]=='pow': 842 ## # 'pow' syntax case 843 ## terms = t[2][2][1::2] 844 ## return string2ast(dopower(ast2string(simplify(terms[0])), 845 ## ast2string(simplify(terms[1])))) 846 ## # else ignore and carry on 847 ## ts=[];o=[]; 848 ## for i in t[1:]: 849 ## if i[0]=='DOUBLESTAR': 850 ## if len(o)==1: o=o[0] 851 ## ts.append(o); 852 ## o=[] 853 ## else: o.append(simplify(i)) 854 ## if len(o)==1: o=o[0] 855 ## ts.append(o) 856 ## if t[2][0]=='DOUBLESTAR': 857 ## st,lft,rt=map(ast2string,[t,ts[0],ts[1]]) 858 ## return string2ast(dopower(simplify_str(lft),simplify_str(rt))) 859 ## if t[2][0]=='trailer': 860 ## return simplify(ts[0]) 861 ## if t[0] in ['arglist','testlist']: 862 ## o=[] 863 ## for i in t[1::2]: 864 ## o.append(ast2string(simplify(i))) 865 ## return string2ast(','.join(o)) 866 ## if t[0]=='atom': 867 #### if t[1][0]=='LPAR' and t[-1][0]=='RPAR': 868 #### return simplify(t[2:-1]) 869 #### else: 870 ## return string2ast(ensureparen(ast2string(simplify(t[2:-1])))) 871 ## if t[1][0]=='trailer': # t=[[NAME,f],[trailer,[(],[ll],[)]]] 872 ## # just simplify arguments to functions 873 ## return [['NAME',t[0][1]], ['trailer',['LPAR','('], 874 ## simplify(t[1][2]),['RPAR',')']]] 875 ## return t 876 877 878 879 # ---------------------------------------------------------------------------- 880
881 -class symbolMapClass(object):
882 """Abstract class for hassle-free symbol re-mappings."""
883 - def __init__(self, symbolMap=None):
884 if isinstance(symbolMap, symbolMapClass): 885 self.lookupDict = copy(symbolMap.lookupDict) 886 elif symbolMap is None: 887 self.lookupDict = {} 888 else: 889 self.lookupDict = copy(symbolMap)
890
891 - def __call__(self, arg):
892 if isinstance(arg, str): 893 if arg in self.lookupDict: 894 return self.lookupDict[arg] 895 else: 896 try: 897 po = parserObject(arg, False) 898 except: 899 # cannot do anything to it! 900 return arg 901 else: 902 if len(po.tokenized) <= 1: 903 # don't recurse, we have a single token or whitespace/CR/LF 904 return arg 905 else: 906 return "".join(mapNames(self,po.tokenized)) 907 elif hasattr(arg, 'mapNames'): 908 # Quantity or QuantSpec 909 res = copy(arg) 910 res.mapNames(self) 911 return res 912 elif hasattr(arg, 'coordnames'): 913 # treat as point or pointset -- just transform the coordnames 914 # ensure return type is the same 915 res = copy(arg) 916 try: 917 res.mapNames(self) 918 except AttributeError: 919 raise TypeError("symbolMapClass does not know how to " 920 "process this type of argument") 921 return res 922 elif hasattr(arg, 'iteritems'): 923 # ensure return type is the same 924 try: 925 res = copy(arg) 926 except TypeError: 927 # not copyable, so no need to worry 928 res = arg 929 try: 930 for k, v in arg.iteritems(): 931 new_k = self.__call__(k) 932 new_v = self.__call__(v) 933 res[new_k] = new_v 934 # delete unprocessed entry in res, from copy of arg 935 if k != new_k: 936 del res[k] 937 except TypeError: 938 # probably not a mutable type - we know what to do with a tuple 939 if isinstance(arg, tuple): 940 return tuple([self.__getitem__(v) for v in arg]) 941 else: 942 return arg 943 except: 944 raise TypeError("symbolMapClass does not know how to " 945 "process this type of argument") 946 return res 947 else: 948 # assume arg is iterable and mutable (list, array, etc.) 949 # ensure return type is the same 950 try: 951 res = copy(arg) 952 except TypeError: 953 # not copyable, so no need to worry 954 res = arg 955 try: 956 for i, v in enumerate(arg): 957 # overwrite unprocessed entry in res, from copy of arg 958 res[i] = self(v) 959 except TypeError: 960 # probably not a mutable type - we know what to do with a tuple 961 if isinstance(arg, tuple): 962 return tuple([self(v) for v in arg]) 963 else: 964 try: 965 return self.__getitem__(res) 966 except: 967 raise TypeError("symbolMapClass does not know how to " 968 "process this type of argument (%s)"%str(type(arg))) 969 except: 970 try: 971 return self.__getitem__(res) 972 except: 973 raise TypeError("symbolMapClass does not know how to " 974 "process this type of argument (%s)"%str(type(arg))) 975 else: 976 return res
977
978 - def __eq__(self, other):
979 try: 980 return self.lookupDict == other.lookupDict 981 except AttributeError: 982 return False
983
984 - def __neq__(self, other):
985 return not self.__eq__(other)
986
987 - def __copy__(self):
988 return symbolMapClass(deepcopy(self.lookupDict))
989
990 - def __setitem__(self, symbol, mappedsymbol):
991 self.lookupDict[symbol] = mappedsymbol
992
993 - def __getitem__(self, symbol):
994 try: 995 return self.lookupDict[symbol] 996 except (KeyError, TypeError): 997 return symbol
998
999 - def __delitem__(self, symbol):
1000 del self.lookupDict[symbol]
1001
1002 - def __contains__(self, symbol):
1003 return self.lookupDict.__contains__(symbol)
1004
1005 - def keys(self):
1006 return self.lookupDict.keys()
1007
1008 - def values(self):
1009 return self.lookupDict.values()
1010
1011 - def items(self):
1012 return self.lookupDict.items()
1013
1014 - def iterkeys(self):
1015 return self.lookupDict.iterkeys()
1016
1017 - def itervalues(self):
1018 return self.lookupDict.itervalues()
1019
1020 - def iteritems(self):
1021 return self.lookupDict.iteritems()
1022
1023 - def inverse(self):
1024 return symbolMapClass(dict(map(lambda (k,v): (v,k), 1025 self.lookupDict.iteritems())))
1026
1027 - def update(self, amap):
1028 try: 1029 # perhaps passed a symbolMapClass object 1030 self.lookupDict.update(amap.lookupDict) 1031 except AttributeError: 1032 # was passed a dict 1033 self.lookupDict.update(amap)
1034
1035 - def reorder(self):
1036 """Return numpy array of indices that can be used to re-order 1037 a list of values that have been sorted by this symbol map object, 1038 so that the list becomes ordered according to the alphabetical 1039 order of the map's keys. 1040 """ 1041 # sorted by keys 1042 keys, vals = sortedDictLists(self.lookupDict, byvalue=False) 1043 return np.argsort(vals)
1044 1045
1046 - def __len__(self):
1047 return len(self.lookupDict)
1048
1049 - def copy(self):
1050 return symbolMapClass(self)
1051
1052 - def __repr__(self):
1053 return "Symbol mapping"
1054 1055 __str__ = __repr__ 1056
1057 - def has_key(self, k):
1058 return k in self.lookupDict
1059 1060
1061 -class auxfnDBclass(object):
1062 """Auxiliary function database, for use by parsers."""
1063 - def __init__(self):
1064 self.auxnames = {}
1065
1066 - def addAuxFn(self, auxfnName, parserObj):
1067 if parserObj not in self.auxnames: 1068 self.auxnames[parserObj] = auxfnName 1069 else: 1070 raise ValueError("Parser object " + parserObj.name + " already " 1071 "exists in auxiliary function database")
1072
1073 - def __repr__(self):
1074 return "ModelSpec internal helper class: auxfnDBclass object"
1075 1076 __str__ = __repr__ 1077
1078 - def __call__(self, parserObj=None):
1079 if parserObj is None: 1080 # return all auxiliary functions known 1081 return self.auxnames.values() 1082 else: 1083 try: 1084 return [self.auxnames[parserObj]] 1085 except KeyError: 1086 return []
1087
1088 - def removeAuxFn(self, auxfnName):
1089 flagdelete = None 1090 for k, v in self.auxnames.iteritems(): 1091 if v == auxfnName: 1092 flagdelete = k 1093 break 1094 if flagdelete is not None: 1095 del self.auxnames[k]
1096
1097 - def clear(self, parserObj):
1098 if parserObj in self.auxnames: 1099 del self.auxnames[parserObj]
1100
1101 - def clearall(self):
1102 self.auxnames = {}
1103 1104 1105 # only need one of these per session 1106 global protected_auxnamesDB 1107 protected_auxnamesDB = auxfnDBclass() 1108 1109
1110 -class parserObject(object):
1111 """Alphanumeric symbol (pseudo-)parser for mathematical expressions. 1112 1113 An AST is not properly implemented -- rather, we tokenize, 1114 identify free symbols, and apply a small number of syntactic rule checks. 1115 The target language parser is relied upon for full syntax checking. 1116 """ 1117
1118 - def __init__(self, specStr, includeProtected=True, 1119 treatMultiRefs=False, ignoreTokens=[], 1120 preserveSpace=False):
1121 # freeSymbols does not include protected names, and so forth. 1122 # freeSymbols only contains unrecognized alphanumeric symbols. 1123 self.usedSymbols = [] 1124 self.freeSymbols = [] 1125 self.preserveSpace = preserveSpace 1126 # token by token list of the specStr 1127 self.tokenized = [] 1128 if type(specStr) is str: 1129 self.specStr = specStr 1130 else: 1131 print "Found type", type(specStr), ": ", specStr 1132 raise TypeError("specStr must be a string") 1133 self.treatMultiRefs = treatMultiRefs 1134 # record init options in case want to reset 1135 self.ignoreTokens = copy(ignoreTokens) 1136 self.includeProtected = includeProtected 1137 # process specStr with empty symbol map to create 1138 # self.freeSymbols and self.usedSymbols 1139 self.parse(ignoreTokens, None, includeProtected, reset=True)
1140 1141
1142 - def isCompound(self, ops=['+', '-', '*', '/']):
1143 """Function to verify whether an expression is 'compound', 1144 in the sense that it has an operator at the root of its syntax 1145 parse tree (i.e. not inside braces).""" 1146 result = False 1147 nested = 0 1148 if len(self.tokenized) > 2: 1149 stage = 0 1150 for s in self.tokenized: 1151 if stage == 0: 1152 if s in self.usedSymbols and nested == 0: 1153 stage = 1 1154 elif s == ')': 1155 stage = 1 1156 nested = max([0, nested-1]) 1157 elif s == '(': 1158 nested += 1 1159 elif stage == 1: 1160 if s in ops and nested == 0: 1161 stage = 2 1162 elif s == '(': 1163 nested += 1 1164 elif s == ')': 1165 nested = max([0, nested-1]) 1166 elif nested == 0: 1167 stage = 0 1168 elif stage == 2: 1169 if s in self.usedSymbols and nested == 0: 1170 stage = 3 1171 elif s == '(': 1172 stage = 3 1173 nested += 1 1174 elif s == ')': 1175 nested = max([0, nested-1]) 1176 elif nested == 0: 1177 stage = 0 1178 if stage == 3: 1179 result = True 1180 break 1181 return result
1182
1183 - def __call__(self, specialtoks=None, symbolMap=None, includeProtected=True):
1184 if specialtoks is None: 1185 if self.ignoreTokens is not None: 1186 specialtoks = self.ignoreTokens 1187 else: 1188 specialtoks = [] 1189 if self.tokenized == []: 1190 return self.parse(specialtoks, symbolMap, includeProtected) 1191 else: 1192 if symbolMap is None: 1193 return "".join(self.tokenized) 1194 else: 1195 return "".join(symbolMap(self.tokenized))
1196
1197 - def find(self, token):
1198 """Find all occurrences of the given token in the expression, returning a list 1199 of indices (empty if not present). 1200 """ 1201 if self.tokenized == []: 1202 self.parse([]) 1203 return [i for i, t in enumerate(self.tokenized) if t == token]
1204
1205 - def parse(self, specialtoks, symbolMap=None, includeProtected=True, 1206 reset=False):
1207 if reset: 1208 self.usedSymbols = [] 1209 self.freeSymbols = [] 1210 if symbolMap is None: 1211 # dummy identity function 1212 symbolMap = lambda x: x 1213 specialtokens = specialtoks + ['('] + self.usedSymbols 1214 if includeProtected: 1215 specialtokens.extend(protected_allnames) 1216 protected_auxnames = protected_auxnamesDB(self) 1217 else: 1218 protected_auxnames = [] 1219 if self.treatMultiRefs: 1220 specialtokens.append('[') 1221 dohierarchical = '.' not in specialtokens 1222 allnames = specialtokens + protected_auxnames 1223 specstr = self.specStr # eases notation 1224 returnstr = "" 1225 if specstr == "": 1226 # Hack for empty specs to pass without losing their last characters 1227 specstr = " " 1228 elif specstr[-1] != ')': 1229 # temporary hack because strings not ending in ) lose their last 1230 # character! 1231 # Problem could be with line "if scount < speclen - 1:" below 1232 # ... should it be <= ? 1233 specstr += " " 1234 scount = 0 1235 speclen = len(specstr) 1236 # temp holders for used and free symbols 1237 used = copy(self.usedSymbols) 1238 free = copy(self.freeSymbols) 1239 tokenized = [] 1240 # current token being built is: s 1241 # current character being processed is: stemp 1242 s = '' 1243 foundtoken = False 1244 while scount < speclen: 1245 stemp = specstr[scount] 1246 ## DEBUGGING PRINT STATEMENTS 1247 ## print "\n*********************************************" 1248 ## print specstr[:scount] 1249 ## print stemp 1250 ## print s 1251 ## print tokenized 1252 scount += 1 1253 if name_chars_RE.match(stemp) is None: 1254 # then stemp is a non-alphanumeric char 1255 if s not in ['', ' ', '\n', '\t']: 1256 # checking allnames catches var names etc. that are valid 1257 # in auxiliary functions but are not special tokens 1258 # and must be left alone 1259 if s in allnames: 1260 snew = symbolMap(s) 1261 tokenized.append(snew) 1262 if snew not in used: 1263 used.append(snew) 1264 returnstr += snew 1265 else: 1266 # isdecimal cases: 1267 # s = number followed by a '.' 1268 # s = number with 'e' followed by +/- 1269 # (just a number dealt with elsewhere) 1270 isnumtok = isNumericToken(s) 1271 issimpledec = stemp == '.' and isnumtok 1272 isexpdec = s[-1] in ['e','E'] and s[0] not in ['e','E']\ 1273 and stemp in ['-','+'] and isnumtok 1274 isdecimal = issimpledec or isexpdec 1275 ishierarchicalname = stemp == '.' and isNameToken(s) 1276 if isdecimal or (ishierarchicalname and dohierarchical): 1277 # continue building token 1278 s += stemp 1279 continue 1280 else: 1281 # We have found a complete token 1282 snew = symbolMap(s) 1283 if s[0] not in num_chars + ['+','-'] \ 1284 and snew not in free: 1285 free.append(snew) 1286 tokenized.append(snew) 1287 if snew not in used: 1288 used.append(snew) 1289 returnstr += snew 1290 if stemp in ['+', '-']: 1291 # may be start of a unary number 1292 try: 1293 next_stemp = specstr[scount] 1294 except IndexError: 1295 tokenized.append(stemp) 1296 returnstr += stemp 1297 s = '' 1298 continue 1299 if (tokenized==[] or tokenized[-1]=='(') and \ 1300 next_stemp in num_chars + ['.']: 1301 # continue to build token 1302 s += stemp 1303 continue 1304 elif len(tokenized)>0 and tokenized[-1] == '+': 1305 # process double sign 1306 if stemp == '-': 1307 tokenized[-1] = '-' 1308 returnstr = returnstr[:-1] + '-' 1309 # else do nothing -- keep just one '+' 1310 s = '' 1311 continue 1312 elif len(tokenized)>0 and tokenized[-1] == '-': 1313 # process double sign 1314 if stemp == '-': 1315 tokenized[-1] = '+' 1316 returnstr = returnstr[:-1] + '+' 1317 # else do nothing -- keep just one '-' 1318 s = '' 1319 continue 1320 else: 1321 tokenized.append(stemp) 1322 returnstr += stemp 1323 s = '' 1324 continue 1325 elif stemp in ['`', '!', '@', '#', '$', '{', 1326 '}', "\\"]: 1327 if stemp in specialtokens: 1328 tokenized.append(stemp) 1329 returnstr += stemp 1330 s = '' 1331 continue 1332 else: 1333 ## if stemp == '^': 1334 ## raise ValueError('Symbol ^ is not allowed. ' 1335 ## 'Please use the pow() call') 1336 ## else: 1337 print "Problem with string '%s'"%specstr 1338 raise ValueError('Symbol %s is illegal. '%stemp) 1339 elif stemp == '[': 1340 # self.treatMultiRefs == False and '[' in specialtokens 1341 # means it was probably in the ignoreToken list in __init__ 1342 if self.treatMultiRefs and len(tokenized)>0 \ 1343 and (tokenized[-1].isalnum() or \ 1344 ('[' in specialtokens and not ( \ 1345 isVectorClause(specstr[scount-1:]) or \ 1346 len(tokenized)>1 and tokenized[-2] in \ 1347 ('max', 'min', 'max_', 'min_')))): 1348 # then this is probably an actual multiRef 1349 s = '[' 1350 elif '[' in specialtokens: 1351 returnstr += '[' 1352 # s already to tokenized in this case 1353 tokenized.append('[') # was just '[' 1354 s = '' 1355 continue 1356 else: 1357 raise ValueError("Syntax error: Square braces not to " 1358 "be used outside of multiple Quantity" 1359 " definitions and references") 1360 # only use next clause if want to process function call 1361 # arguments specially 1362 # elif stemp == '(': 1363 # returnstr += s 1364 # s = stemp 1365 else: 1366 if stemp == "*": 1367 if len(returnstr)>1 and returnstr[-1] == "*": 1368 # check for ** case 1369 if tokenized[-1] == '*': 1370 tokenized[-1] = '**' 1371 else: 1372 tokenized.append('**') 1373 s = '' 1374 returnstr += stemp 1375 continue # avoids returnstr += stemp below 1376 ## if "**" in specialtokens: 1377 ## if tokenized[-1] == '*': 1378 ## tokenized[-1] = '**' 1379 ## else: 1380 ## tokenized.append('**') 1381 ## s = '' 1382 ## returnstr += "**" 1383 ## continue # avoids returnstr += stemp below 1384 ## else: 1385 ## raise ValueError('Operator ** is not allowed. ' 1386 ## 'Please use the pow() call') 1387 else: 1388 # just a single * 1389 tokenized.append('*') 1390 elif stemp == "=": 1391 if len(returnstr)>1: 1392 # check for >= and <= cases 1393 if returnstr[-1] == ">": 1394 if tokenized[-1] == '>': 1395 tokenized[-1] = '>=' 1396 else: 1397 tokenized.append('>=') 1398 s = '' 1399 returnstr += stemp 1400 continue # avoids returnstr += stemp below 1401 elif returnstr[-1] == "<": 1402 if tokenized[-1] == '<': 1403 tokenized[-1] = '<=' 1404 else: 1405 tokenized[-1] = '<=' 1406 s = '' 1407 returnstr += stemp 1408 continue # avoids returnstr += stemp below 1409 else: 1410 tokenized.append('=') 1411 else: 1412 tokenized.append('=') 1413 elif stemp in [" ","\t","\n"]: 1414 if self.preserveSpace: tokenized.append(stemp) 1415 else: 1416 tokenized.append(stemp) 1417 s = '' 1418 returnstr += stemp 1419 continue 1420 else: 1421 s += stemp 1422 if s in specialtokens: 1423 # only use next clause if want to process function call 1424 # arguments specially 1425 # if s == '(': 1426 # foundtoken = False # so that don't enter next if statement 1427 # tokenized.append(s) 1428 # returnstr += s 1429 # s = '' 1430 if s == '[' and self.treatMultiRefs and len(tokenized)>0 \ 1431 and (tokenized[-1].isalnum() or \ 1432 ('[' in specialtokens and not isVectorClause(specstr[scount-1:]))): 1433 # then this is probably an actual multiRef ... 1434 # will treat as multiRef if there's an alphanumeric 1435 # token directly preceding '[', e.g. z[i,0,1] 1436 # or if commas are not inside, 1437 # otherwise would catch vector usage, e.g. [[x,y],[a,b]] 1438 foundtoken = False # don't go into next clause 1439 # copy the square-bracketed clause to the output 1440 # verbatim, and add whole thing as a token. 1441 try: 1442 rbpos = specstr[scount:].index(']') 1443 except ValueError: 1444 raise ValueError("Mismatch [ and ] in spec") 1445 # expr includes both brackets 1446 expr = specstr[scount-1:scount+rbpos+1] 1447 # find index name in this expression. this should 1448 # be the only free symbol, otherwise syntax error. 1449 temp = parserObject(expr[1:-1], 1450 includeProtected=False) 1451 if len(temp.freeSymbols) == 1: 1452 free.extend(temp.freeSymbols) 1453 # not sure whether should add to usedSymbols 1454 # for consistency with freeSymbols, or leave 1455 # it out for consistency with tokenized 1456 # used.append(temp.freeSymbols) 1457 else: 1458 raise ValueError("Invalid index clause in " 1459 "multiple quantity reference -- " 1460 "multiple index names used in [...]") 1461 # start next parsing iteration after the ']' 1462 scount += rbpos+1 1463 # treat whole expression as one symbol 1464 returnstr += expr 1465 tokenized.append(expr) 1466 used.append(expr) 1467 s = '' 1468 else: 1469 if scount < speclen - 1: 1470 if name_chars_RE.match(specstr[scount]) is None: 1471 foundtoken = True 1472 else: 1473 if s[-1] in ['e','E'] and s[0] not in ['e','E'] and \ 1474 name_chars_RE.match(specstr[scount]).group() \ 1475 in num_chars+['-','+']: 1476 # not expecting an arithmetic symbol or space 1477 # ... we *are* expecting a numeric 1478 foundtoken = True 1479 else: 1480 foundtoken = True 1481 if foundtoken: 1482 if includeProtected: 1483 if s == 'for': 1484 # check next char is '(' 1485 if specstr[scount] != '(': 1486 print "Next char found:", specstr[scount] 1487 raise ValueError("Invalid 'for' macro syntax") 1488 # find next ')' (for statement should contain no 1489 # braces itself) 1490 try: 1491 rbpos = specstr[scount:].index(')') 1492 except ValueError: 1493 raise ValueError("Mismatch ( and ) in 'for' " 1494 "macro") 1495 # expr includes both brackets 1496 expr = specstr[scount:scount+rbpos+1] 1497 # find index name in this expression. this should 1498 # be the only free symbol, otherwise syntax error. 1499 temp = parserObject(expr, includeProtected=False) 1500 macrotests = [len(temp.tokenized) == 7, 1501 temp.tokenized[2] == temp.tokenized[4] == ',', 1502 temp.tokenized[5] in ['+','*']] 1503 if not alltrue(macrotests): 1504 print "specstr was: ", specstr 1505 print "tokens: ", temp.tokenized 1506 print "test results: ", macrotests 1507 raise ValueError("Invalid sub-clause in " 1508 "'for' macro") 1509 # start next parsing iteration after the ')' 1510 scount += rbpos+1 1511 # keep contents of braces as one symbol 1512 returnstr += s+expr 1513 tokenized.extend([s,expr]) 1514 if s not in used: 1515 used.append(s) 1516 if expr not in used: 1517 used.append(expr) 1518 elif s == 'abs': 1519 snew = symbolMap(s) 1520 returnstr += snew 1521 tokenized.append(snew) 1522 if snew not in used: 1523 used.append(snew) 1524 elif s in protected_scipynames + protected_specialfns: 1525 snew = symbolMap(s) 1526 returnstr += snew 1527 tokenized.append(snew) 1528 if snew not in used: 1529 used.append(snew) 1530 elif s in protected_mathnames: 1531 if s in ['e','E']: 1532 # special case where e is either = exp(0) 1533 # as a constant or it's an exponent in 1.0e-4 1534 if len(returnstr)>0: 1535 if returnstr[-1] not in num_chars+['.']: 1536 snew = symbolMap(s.lower()) 1537 returnstr += snew 1538 tokenized.append(snew) 1539 if snew not in used: 1540 used.append(snew) 1541 else: 1542 returnstr += s 1543 else: 1544 snew = symbolMap(s.lower()) 1545 returnstr += snew 1546 tokenized.append(snew) 1547 if snew not in used: 1548 used.append(snew) 1549 else: 1550 snew = symbolMap(s) 1551 returnstr += snew 1552 tokenized.append(snew) 1553 if snew not in used: 1554 used.append(snew) 1555 elif s in protected_randomnames: 1556 snew = symbolMap(s) 1557 returnstr += snew 1558 tokenized.append(snew) 1559 if snew not in used: 1560 used.append(snew) 1561 elif s in protected_auxnames: 1562 snew = symbolMap(s) 1563 returnstr += snew 1564 tokenized.append(snew) 1565 else: 1566 # s is e.g. a declared argument to an aux fn but 1567 # only want to ensure it is present. no action to 1568 # take. 1569 snew = symbolMap(s) 1570 tokenized.append(snew) 1571 returnstr += snew 1572 else: 1573 # not includeProtected names case, so just map 1574 # symbol 1575 snew = symbolMap(s) 1576 tokenized.append(snew) 1577 returnstr += snew 1578 # reset for next iteration 1579 s = '' 1580 foundtoken = False 1581 # end of scount while loop 1582 if reset: 1583 # hack to remove any string literals 1584 actual_free = [sym for sym in free if sym in tokenized] 1585 for sym in [sym for sym in free if sym not in actual_free]: 1586 is_literal = False 1587 for tok in tokenized: 1588 # does symbol appear in quotes inside token? 1589 # if so, then it's a literal and not a free symbol 1590 if ('"' in tok or "'" in tok) and sym in tok: 1591 start_ix = tok.index(sym) 1592 end_ix = start_ix + len(sym) - 1 1593 if len(tok) > end_ix and start_ix > 0: 1594 # then symbol is embedded inside the string 1595 doub = tok[start_ix-1] == tok[end_ix+1] == '"' 1596 sing = tok[start_ix-1] == tok[end_ix+1] == "'" 1597 is_literal = doub or sing 1598 if not is_literal: 1599 actual_free.append(sym) 1600 self.usedSymbols = used 1601 self.freeSymbols = actual_free 1602 self.specStr = returnstr 1603 self.tokenized = tokenized 1604 # strip extraneous whitespace 1605 return returnstr.strip()
1606 1607 1608 #----------------------------------------------------------------------------- 1609 1610
1611 -def isToken(s, treatMultiRefs=False):
1612 # token must be alphanumeric string (with no punctuation, operators, etc.) 1613 if not isinstance(s, str): 1614 return False 1615 try: 1616 temp = parserObject(s, includeProtected=False, 1617 treatMultiRefs=treatMultiRefs) 1618 except ValueError: 1619 return False 1620 if treatMultiRefs and s.find('[')>0: 1621 lenval = 2 1622 else: 1623 lenval = 1 1624 return not temp.isCompound() and len(temp.usedSymbols) == lenval \ 1625 and len(temp.freeSymbols) == lenval \ 1626 and len(temp.tokenized) == lenval
1627 1628
1629 -def isNameToken(s, treatMultiRefs=False):
1630 return isToken(s, treatMultiRefs=treatMultiRefs) \ 1631 and s[0] not in num_chars and not (len(s)==1 and s=="_")
1632
1633 -def isIntegerToken(arg):
1634 return alltrue([t in '0123456789' for t in arg])
1635
1636 -def isNumericToken(arg):
1637 # supports unary + / - at front, and checks for usage of exponentials 1638 # (using 'E' or 'e') 1639 try: 1640 s = arg.lower() 1641 except AttributeError: 1642 return False 1643 try: 1644 if s[0] in ['+','-']: 1645 s_rest = s[1:] 1646 else: 1647 s_rest = s 1648 except IndexError: 1649 return False 1650 pts = s.count('.') 1651 exps = s.count('e') 1652 pm = s_rest.count('+') + s_rest.count('-') 1653 if pts > 1 or exps > 1 or pm > 1: 1654 return False 1655 if exps == 1: 1656 exp_pos = s.find('e') 1657 pre_exp = s[:exp_pos] 1658 # must be numbers before and after the 'e' 1659 if not sometrue([n in num_chars for n in pre_exp]): 1660 return False 1661 if s[-1]=='e': 1662 # no chars after 'e'! 1663 return False 1664 if not sometrue([n in num_chars for n in s[exp_pos:]]): 1665 return False 1666 # check that any additional +/- occurs directly after 'e' 1667 if pm == 1: 1668 pm_pos = max([s_rest.find('+'), s_rest.find('-')]) 1669 if s_rest[pm_pos-1] != 'e': 1670 return False 1671 e_rest = s_rest[pm_pos+1:] # safe due to previous check 1672 else: 1673 e_rest = s[exp_pos+1:] 1674 # only remaining chars in s after e and possible +/- are numbers 1675 if '.' in e_rest: 1676 return False 1677 # cannot use additional +/- if not using exponent 1678 if pm == 1 and exps == 0: 1679 return False 1680 return alltrue([n in num_chars + ['.', 'e', '+', '-'] for n in s_rest])
1681 1682
1683 -def findNumTailPos(s):
1684 """Find position of numeric tail in alphanumeric string. 1685 1686 e.g. findNumTailPos('abc678') = 3""" 1687 try: 1688 l = len(s) 1689 if l > 1: 1690 if s[-1] not in num_chars or s[0] in num_chars: 1691 raise ValueError("Argument must be an alphanumeric string " 1692 "starting with a letter and ending in a number") 1693 for i in range(1, l+1): 1694 if s[-i] not in num_chars: 1695 return l-i+1 1696 else: 1697 raise ValueError("Argument must be alphanumeric string starting " 1698 "with a letter and ending in a number") 1699 except TypeError: 1700 raise ValueError("Argument must be alphanumeric string starting " 1701 "with a letter and ending in a number")
1702 1703
1704 -def isHierarchicalName(s, sep=NAMESEP, treatMultiRefs=False):
1705 s_split = s.split(sep) 1706 return len(s_split) > 1 and alltrue([isNameToken(t, treatMultiRefs) \ 1707 for t in s_split])
1708
1709 -def isVectorClause(s):
1710 brace = findEndBrace(s, '[',']') 1711 if s[0] == '[' and isinstance(brace, int): 1712 return ',' in s[1:brace]
1713 1714
1715 -def replaceSepList(speclist):
1716 return [replaceSep(spec) for spec in speclist]
1717
1718 -def replaceSepListInv(speclist):
1719 return [replaceSepInv(spec) for spec in speclist]
1720
1721 -def replaceSepInv(spec):
1722 """Invert default name separator replacement.""" 1723 return replaceSep(spec, "_", NAMESEP)
1724 1725
1726 -def replaceSep(spec, sourcesep=NAMESEP, targetsep="_"):
1727 """Replace hierarchy separator character with another and return spec 1728 string. e.g. "." -> "_" 1729 Only replaces the character between name tokens, not between numbers.""" 1730 1731 for char in sourcesep: 1732 if alphabet_chars_RE.match(char) is not None: 1733 raise ValueError("Source separator must be non-alphanumeric") 1734 for char in targetsep: 1735 if alphabet_chars_RE.match(char) is not None: 1736 raise ValueError("Target separator must be non-alphanumeric") 1737 if isinstance(spec, str): 1738 return replaceSepStr(spec, sourcesep, targetsep) 1739 else: 1740 # safe way to get string definition from either Variable or QuantSpec 1741 return replaceSepStr(str(spec()), sourcesep, targetsep)
1742 1743
1744 -def mapNames(themap, target):
1745 """Map names in <target> argument using the symbolMapClass 1746 object <themap>, returning a renamed version of the target. 1747 N.B. Only maps the keys of a dictionary type""" 1748 try: 1749 themap.lookupDict 1750 except AttributeError: 1751 t = repr(type(themap)) 1752 raise TypeError("Map argument must be of type symbolMapClass, not %s"%t) 1753 if hasattr(target, 'mapNames'): 1754 ct = copy(target) 1755 ct.mapNames(themap) # these methods work in place 1756 return ct 1757 elif isinstance(target, list): 1758 return themap(target) 1759 elif isinstance(target, tuple): 1760 return tuple(themap(target)) 1761 elif hasattr(target, 'iteritems'): 1762 o = {} 1763 for k, v in target.iteritems(): 1764 o[themap(k)] = v 1765 return o 1766 elif isinstance(target, str): 1767 return themap(target) 1768 elif target is None: 1769 return None 1770 else: 1771 raise TypeError("Invalid target type %s"%repr(type(target)))
1772 1773 1774 ## internal functions for replaceSep
1775 -def replaceSepQSpec(spec, sourcesep, targetsep):
1776 # currently unused because many QuantSpec's with hierarchical names 1777 # are not properly tokenized! They may have the tokenized form 1778 # ['a_parent','.','a_child', <etc.>] rather than 1779 # ['a_parent.a_child', <etc.>] 1780 outstr = "" 1781 # search for pattern <nameToken><sourcechar><nameToken> 1782 try: 1783 for t in spec[:]: 1784 if isHierarchicalName(t, sourcesep): 1785 outstr += t.replace(sourcesep, targetsep) 1786 else: 1787 # just return original token 1788 outstr += t 1789 except AttributeError: 1790 raise ValueError("Invalid QuantSpec passed to replaceSep()") 1791 return outstr
1792 1793
1794 -def replaceSepStr(spec, sourcesep, targetsep):
1795 # spec is a string (e.g. 'leaf.v'), and p.tokenized contains the 1796 # spec split by the separator (e.g. ['leaf', '.', 'v']) if the separator 1797 # is not "_", otherwise it will be retained as part of the name 1798 outstr = "" 1799 treatMultiRefs = '[' in spec and ']' in spec 1800 p = parserObject(spec, treatMultiRefs=treatMultiRefs, 1801 includeProtected=False) 1802 # search for pattern <nameToken><sourcechar><nameToken> 1803 # state 0: not in pattern 1804 # state 1: found nameToken 1805 # state 2: found nameToken then sourcechar 1806 # state 3: found complete pattern 1807 state = 0 1808 for t in p.tokenized: 1809 if isNameToken(t): 1810 if sourcesep in t: 1811 # in case sourcesep == '_' 1812 tsplit = t.split(sourcesep) 1813 if alltrue([isNameToken(ts) for ts in tsplit]): 1814 outstr += targetsep.join(tsplit) 1815 else: 1816 outstr += t 1817 else: 1818 if state == 0: 1819 state = 1 1820 outstr += t 1821 elif state == 1: 1822 state = 0 1823 outstr += t 1824 else: 1825 # state == 2 1826 state = 1 # in case another separator follows 1827 outstr += targetsep + t 1828 continue 1829 elif t == sourcesep: 1830 if state == 1: 1831 state = 2 1832 # delay output until checked next token 1833 else: 1834 # not part of pattern, so reset 1835 state = 0 1836 outstr += t 1837 else: 1838 state = 0 1839 outstr += t 1840 return outstr
1841 1842
1843 -def joinStrs(strlist):
1844 """Join a list of strings into a single string (in order).""" 1845 1846 return ''.join(strlist)
1847 1848
1849 -def joinAsStrs(objlist,sep=""):
1850 """Join a list of objects in their string representational form.""" 1851 1852 retstr = '' 1853 for o in objlist: 1854 if type(o) is str: 1855 retstr += o + sep 1856 else: 1857 retstr += str(o) + sep 1858 avoidend = len(sep) 1859 if avoidend > 0: 1860 return retstr[:-avoidend] 1861 else: 1862 return retstr
1863 1864
1865 -def count_sep(specstr, sep=','):
1866 """Count number of specified separators (default = ',') in given string, 1867 avoiding occurrences of the separator inside nested braces""" 1868 1869 num_seps = 0 1870 brace_depth = 0 1871 for s in specstr: 1872 if s == sep and brace_depth == 0: 1873 num_seps += 1 1874 elif s == '(': 1875 brace_depth += 1 1876 elif s == ')': 1877 brace_depth -= 1 1878 return num_seps
1879 1880
1881 -def parseMatrixStrToDictStr(specstr, specvars, m=0):
1882 """Convert string representation of m-by-n matrix into a single 1883 string, assuming a nested comma-delimited list representation in 1884 the input, and outputting a dictionary of the sub-lists, indexed 1885 by the ordered list of names specvars (specified as an 1886 argument).""" 1887 1888 specdict = {} 1889 # matrix is n by m 1890 n = len(specvars) 1891 if n == 0: 1892 raise ValueError("parseMatrixStrToDictStr: specvars was empty") 1893 if m == 0: 1894 # assume square matrix 1895 m = len(specvars) 1896 # strip leading and trailing whitespace 1897 spectemp1 = specstr.strip() 1898 assert spectemp1[0] == '[' and spectemp1[-1] == ']', \ 1899 ("Matrix must be supplied as a Python matrix, using [ and ] syntax") 1900 # strip first [ and last ] and then all whitespace and \n 1901 spectemp2 = spectemp1[1:-1].replace(' ','').replace('\n','') 1902 splitdone = False 1903 entrycount = 0 1904 startpos = 0 1905 try: 1906 while not splitdone: 1907 nextrbrace = findEndBrace(spectemp2[startpos:], 1908 '[', ']') + startpos 1909 if nextrbrace is None: 1910 raise ValueError("Mismatched braces before end of string") 1911 specdict[specvars[entrycount]] = \ 1912 spectemp2[startpos:nextrbrace+1] 1913 entrycount += 1 1914 if entrycount < n: 1915 nextcomma = spectemp2.find(',', nextrbrace) 1916 if nextcomma > 0: 1917 nextlbrace = spectemp2.find('[', nextcomma) 1918 if nextlbrace > 0: 1919 startpos = nextlbrace 1920 else: 1921 raise ValueError("Not enough comma-delimited entries") 1922 else: 1923 raise ValueError("Not enough comma-delimited entries") 1924 else: 1925 splitdone = True 1926 except: 1927 print "Error in matrix specification" 1928 raise 1929 return specdict
1930 1931
1932 -def readArgs(argstr, lbchar='(', rbchar=')'):
1933 """Parse arguments out of string beginning and ending with braces 1934 (default: round brace). 1935 1936 Returns a triple: [success_boolean, list of arguments, number of args]""" 1937 bracetest = argstr[0] == lbchar and argstr[-1] == rbchar 1938 rest = argstr[1:-1].replace(" ","") 1939 pieces = [] 1940 while True: 1941 if '(' in rest: 1942 lix = rest.index('(') 1943 rix = findEndBrace(rest[lix:]) + lix 1944 new = rest[:lix].split(",") 1945 if len(pieces) > 0: 1946 pieces[-1] = pieces[-1] + new[0] 1947 pieces.extend(new[1:]) 1948 else: 1949 pieces.extend(new) 1950 if len(pieces) > 0: 1951 pieces[-1] = pieces[-1] + rest[lix:rix+1] 1952 else: 1953 pieces.append(rest[lix:rix+1]) 1954 rest = rest[rix+1:] 1955 else: 1956 new = rest.split(",") 1957 if len(pieces) > 0: 1958 pieces[-1] = pieces[-1] + new[0] 1959 pieces.extend(new[1:]) 1960 else: 1961 pieces.extend(new) 1962 # quit while loop 1963 break 1964 return [bracetest, pieces, len(argstr)]
1965 1966
1967 -def findEndBrace(s, lbchar='(', rbchar=')'):
1968 """Find position in string (or list of strings), s, at which final matching 1969 brace occurs (if at all). If not found, returns None. 1970 1971 s[0] must be the left brace character. Default left and right braces are 1972 '(' and ')'. Change them with the optional second and third arguments. 1973 """ 1974 pos = 0 1975 assert s[0] == lbchar, 'string argument must begin with left brace' 1976 stemp = s 1977 leftbrace_count = 0 1978 notDone = True 1979 while len(stemp) > 0 and notDone: 1980 # for compatibility with s being a list, use index method 1981 try: 1982 left_pos = stemp.index(lbchar) 1983 except ValueError: 1984 left_pos = -1 1985 try: 1986 right_pos = stemp.index(rbchar) 1987 except ValueError: 1988 right_pos = -1 1989 if left_pos >= 0: 1990 if left_pos < right_pos: 1991 if left_pos >= 0: 1992 leftbrace_count += 1 1993 pos += left_pos+1 1994 stemp = s[pos:] 1995 else: 1996 # no left braces found. next brace is right. 1997 if leftbrace_count > 0: 1998 leftbrace_count -= 1 1999 pos += right_pos+1 2000 stemp = s[pos:] 2001 else: 2002 # right brace found first 2003 leftbrace_count -= 1 2004 pos += right_pos+1 2005 stemp = s[pos:] 2006 else: 2007 if right_pos >= 0: 2008 # right brace found first 2009 leftbrace_count -= 1 2010 pos += right_pos+1 2011 stemp = s[pos:] 2012 else: 2013 # neither were found (both == -1) 2014 raise ValueError('End of string found before closing brace') 2015 if leftbrace_count == 0: 2016 notDone = False 2017 # adjust for 2018 pos -= 1 2019 if leftbrace_count == 0: 2020 return pos 2021 else: 2022 return None
2023 2024
2025 -def makeParList(objlist, prefix=''):
2026 """wrap objlist into a comma separated string of str(objects)""" 2027 parlist = ', '.join(map(lambda i: prefix+str(i), objlist)) 2028 return parlist
2029 2030
2031 -def wrapArgInCall(source, callfn, wrapL, wrapR=None, argnums=[0], 2032 notFirst=False):
2033 """Add delimiters to single argument in function call.""" 2034 done = False 2035 output = "" 2036 currpos = 0 2037 first_occurrence = True 2038 if wrapR is None: 2039 # if no specific wrapR is specified, just use wrapL 2040 # e.g. for symmetric delimiters such as quotes 2041 wrapR = wrapL 2042 assert isinstance(wrapL, str) and isinstance(wrapR, str), \ 2043 "Supplied delimiters must be strings" 2044 while not done: 2045 # find callfn in source 2046 findposlist = [source[currpos:].find(callfn+'(')] 2047 try: 2048 findpos = min(filter(lambda x:x>=0,findposlist))+currpos 2049 except ValueError: 2050 done = True 2051 if not done: 2052 # find start and end braces 2053 startbrace = source[findpos:].find('(')+findpos 2054 endbrace = findEndBrace(source[startbrace:])+startbrace 2055 output += source[currpos:startbrace+1] 2056 # if more than one argument present, apply wrapping to specified 2057 # arguments 2058 currpos = startbrace+1 2059 numargs = source[startbrace+1:endbrace].count(',')+1 2060 if max(argnums) >= numargs: 2061 raise ValueError("Specified argument number out of range") 2062 if numargs > 1: 2063 for argix in range(numargs-1): 2064 nextcomma = source[currpos:endbrace].find(',') 2065 argstr = source[currpos:currpos + nextcomma] 2066 # get rid of leading or tailing whitespace 2067 argstr = argstr.strip() 2068 if argix in argnums: 2069 if first_occurrence and notFirst: 2070 output += source[currpos:currpos + nextcomma + 1] 2071 else: 2072 output += wrapL + argstr + wrapR + ',' 2073 first_occurrence = False 2074 else: 2075 output += source[currpos:currpos + nextcomma + 1] 2076 currpos += nextcomma + 1 2077 if numargs-1 in argnums: 2078 if first_occurrence and notFirst: 2079 # just include last argument as it was if this is 2080 # the first occurrence 2081 output += source[currpos:endbrace+1] 2082 else: 2083 # last argument needs to wrapped too 2084 argstr = source[currpos:endbrace] 2085 # get rid of leading or tailing whitespace 2086 argstr = argstr.strip() 2087 output += wrapL + argstr + wrapR + ')' 2088 first_occurrence = False 2089 else: 2090 # just include last argument as it was 2091 output += source[currpos:endbrace+1] 2092 else: 2093 if argnums[0] != 0: 2094 raise ValueError("Specified argument number out of range") 2095 if first_occurrence and notFirst: 2096 output += source[currpos:endbrace+1] 2097 else: 2098 argstr = source[currpos:endbrace] 2099 # get rid of leading or tailing whitespace 2100 argstr = argstr.strip() 2101 output += wrapL + argstr + wrapR + ')' 2102 first_occurrence = False 2103 currpos = endbrace+1 2104 else: 2105 output += source[currpos:] 2106 return output
2107 2108 2109 ##def replaceCallsWithDummies(source, callfns, used_dummies=None, notFirst=False): 2110 ## """Replace all function calls in source with dummy names, 2111 ## for the functions listed in callfns. Returns a pair (new_source, d) 2112 ## where d is a dict mapping the dummy names used to the function calls.""" 2113 ## # This function used to work on lists of callfns directly, but I can't 2114 ## # see why it stopped working. So I just added this recursing part at 2115 ## # the front to reduce the problem to a singleton function name each time. 2116 ## print "\nEntered with ", source, callfns, used_dummies 2117 ## if used_dummies is None: 2118 ## used_dummies = 0 2119 ## if isinstance(callfns, list): 2120 ## if len(callfns) > 1: 2121 ## res = source 2122 ## dummies = {} 2123 ## #remaining_fns = callfns[:] 2124 ## for f in callfns: 2125 ## #remaining_fns.remove(f) 2126 ## new_res, d = replaceCallsWithDummies(res, [f], used_dummies, notFirst) 2127 ## res = new_res 2128 ## if d != {}: 2129 ## dummies.update(d) 2130 ## used_dummies = max( (used_dummies, max(d.keys())) ) 2131 ## if dummies != {}: # and remaining_fns != []: 2132 ## new_dummies = dummies.copy() 2133 ## for k, v in dummies.items(): 2134 ## new_v, new_d = replaceCallsWithDummies(v, callfns, 2135 ## used_dummies, notFirst=True) 2136 ## new_dummies[k] = new_v 2137 ## if new_d != {}: 2138 ## new_dummies.update(new_d) 2139 ## used_dummies = max( (used_dummies, max(new_dummies.keys())) ) 2140 ## dummies.update(new_dummies) 2141 ## return res, dummies 2142 ## else: 2143 ## raise TypeError("Invalid list of function names") 2144 ## done = False 2145 ## dummies = {} 2146 ## output = "" 2147 ## currpos = 0 2148 ## doneFirst = False 2149 ## while not done: 2150 ## # find any callfns in source (now just always a singleton) 2151 ## findposlist_candidates = [source[currpos:].find(fname+'(') for fname in callfns] 2152 ## findposlist = [] 2153 ## for candidate_pos in findposlist_candidates: 2154 ## # remove any that are actually only full funcname matches to longer strings 2155 ## # that happen to have that funcname at the end: e.g. functions ['if','f'] 2156 ## # and find a match 'f(' in the string 'if(' 2157 ## if currpos+candidate_pos-1 >= 0: 2158 ## if not isNameToken(source[currpos+candidate_pos-1]): 2159 ## findposlist.append(candidate_pos) 2160 ## else: 2161 ## # no earlier character in source, so must be OK 2162 ## findposlist.append(candidate_pos) 2163 ## if len(findposlist) > 0 and notFirst: 2164 ## findposlist = findposlist[1:] 2165 ## try: 2166 ## findpos = min(filter(lambda x:x>=0, findposlist))+currpos 2167 ## except ValueError: 2168 ## done = True 2169 ## if not done: 2170 ## # find start and end braces 2171 ## startbrace = source[findpos:].find('(')+findpos 2172 ## endbrace = findEndBrace(source[startbrace:])+startbrace 2173 ## sub_source = source[startbrace+1:endbrace] 2174 ## embedded_calls = [sub_source.find(fname+'(') for fname in callfns] 2175 ## try: 2176 ## subpositions = filter(lambda x:x>0, embedded_calls) 2177 ## if subpositions == []: 2178 ## filtered_sub_source = sub_source 2179 ## new_d = {} 2180 ## else: 2181 ## filtered_sub_source, new_d = \ 2182 ## replaceCallsWithDummies(sub_source, 2183 ## callfns, used_dummies) 2184 ## except ValueError: 2185 ## pass 2186 ## else: 2187 ## if new_d != {}: 2188 ## dummies.update(new_d) 2189 ## used_dummies = max( (used_dummies, max(dummies.keys())) ) 2190 ## used_dummies += 1 2191 ## dummies[used_dummies] = source[findpos:startbrace+1] + \ 2192 ## filtered_sub_source + ')' 2193 ## output += source[currpos:findpos] + '__dummy%i__' % used_dummies 2194 ## currpos = endbrace+1 2195 ## else: 2196 ## output += source[currpos:] 2197 ## return output, dummies 2198 2199
2200 -def replaceCallsWithDummies(source, callfns, used_dummies=None, notFirst=False):
2201 """Replace all function calls in source with dummy names, 2202 for the functions listed in callfns. Returns a pair (new_source, d) 2203 where d is a dict mapping the dummy names used to the function calls. 2204 """ 2205 # This function used to work on lists of callfns directly, but I can't 2206 # see why it stopped working. So I just added this recursing part at 2207 # the front to reduce the problem to a singleton function name each time. 2208 if used_dummies is None: 2209 used_dummies = 0 2210 done = False 2211 dummies = {} 2212 output = "" 2213 currpos = 0 2214 doneFirst = False 2215 while not done: 2216 # find any callfns in source (now just always a singleton) 2217 findposlist_candidates = [source[currpos:].find(fname+'(') for fname in callfns] 2218 findposlist = [] 2219 for candidate_pos in findposlist_candidates: 2220 # remove any that are actually only full funcname matches to longer strings 2221 # that happen to have that funcname at the end: e.g. functions ['if','f'] 2222 # and find a match 'f(' in the string 'if(' 2223 if currpos+candidate_pos-1 >= 0: 2224 if not isNameToken(source[currpos+candidate_pos-1]): 2225 findposlist.append(candidate_pos) 2226 else: 2227 # no earlier character in source, so must be OK 2228 findposlist.append(candidate_pos) 2229 findposlist = [ix for ix in findposlist if ix >= 0] 2230 findposlist.sort() 2231 if not doneFirst and notFirst and len(findposlist) > 0: 2232 findposlist = findposlist[1:] 2233 doneFirst = True 2234 try: 2235 findpos = findposlist[0]+currpos 2236 except IndexError: 2237 done = True 2238 if not done: 2239 # find start and end braces 2240 startbrace = source[findpos:].find('(')+findpos 2241 endbrace = findEndBrace(source[startbrace:])+startbrace 2242 sub_source = source[startbrace+1:endbrace] 2243 embedded_calls = [sub_source.find(fname+'(') for fname in callfns] 2244 try: 2245 subpositions = filter(lambda x:x>0, embedded_calls) 2246 if subpositions == []: 2247 filtered_sub_source = sub_source 2248 new_d = {} 2249 else: 2250 filtered_sub_source, new_d = \ 2251 replaceCallsWithDummies(sub_source, 2252 callfns, used_dummies) 2253 except ValueError: 2254 pass 2255 else: 2256 if new_d != {}: 2257 dummies.update(new_d) 2258 used_dummies = max( (used_dummies, max(dummies.keys())) ) 2259 used_dummies += 1 2260 dummies[used_dummies] = source[findpos:startbrace+1] + \ 2261 filtered_sub_source + ')' 2262 output += source[currpos:findpos] + '__dummy%i__' % used_dummies 2263 currpos = endbrace+1 2264 else: 2265 output += source[currpos:] 2266 if dummies != {}: 2267 new_dummies = dummies.copy() 2268 for k, v in dummies.items(): 2269 new_v, new_d = replaceCallsWithDummies(v, callfns, 2270 used_dummies, notFirst=True) 2271 new_dummies[k] = new_v 2272 if new_d != {}: 2273 new_dummies.update(new_d) 2274 used_dummies = max( (used_dummies, max(new_dummies.keys())) ) 2275 dummies.update(new_dummies) 2276 return output, dummies
2277 2278
2279 -def addArgToCalls(source, callfns, arg, notFirst=''):
2280 """Add an argument to calls in source, to the functions listed in callfns. 2281 """ 2282 # This function used to work on lists of callfns directly, but I can't 2283 # see why it stopped working. So I just added this recursing part at 2284 # the front to reduce the problem to a singleton function name each time. 2285 if isinstance(callfns, list): 2286 if len(callfns) > 1: 2287 res = source 2288 for f in callfns: 2289 res = addArgToCalls(res, [f], arg, notFirst) 2290 return res 2291 else: 2292 raise TypeError("Invalid list of function names") 2293 done = False 2294 output = "" 2295 currpos = 0 2296 while not done: 2297 # find any callfns in source (now just always a singleton) 2298 findposlist_candidates = [source[currpos:].find(fname+'(') for fname in callfns] 2299 findposlist = [] 2300 for candidate_pos in findposlist_candidates: 2301 # remove any that are actually only full funcname matches to longer strings 2302 # that happen to have that funcname at the end: e.g. functions ['if','f'] 2303 # and find a match 'f(' in the string 'if(' 2304 if currpos+candidate_pos-1 >= 0: 2305 if not isNameToken(source[currpos+candidate_pos-1]): 2306 findposlist.append(candidate_pos) 2307 # remove so that findpos except clause doesn't get confused 2308 findposlist_candidates.remove(candidate_pos) 2309 else: 2310 # no earlier character in source, so must be OK 2311 findposlist.append(candidate_pos) 2312 # remove so that findpos except clause doesn't get confused 2313 findposlist_candidates.remove(candidate_pos) 2314 try: 2315 findpos = min(filter(lambda x:x>=0,findposlist))+currpos 2316 except ValueError: 2317 # findposlist is empty 2318 done = True 2319 # commented this stuff out from before this function only ever 2320 # dealt with a singleton callfns list - probably the source of 2321 # the original bug! 2322 ## if currpos < len(source) and len(findposlist_candidates) > 0 and \ 2323 ## findposlist_candidates[0] >= 0: 2324 ## currpos += findposlist_candidates[0] + 2 2325 ## output += source[:findposlist_candidates[0]+2] 2326 ## continue 2327 ## else: 2328 ## done = True 2329 if not done: 2330 # find start and end braces 2331 startbrace = source[findpos:].find('(')+findpos 2332 endbrace = findEndBrace(source[startbrace:])+startbrace 2333 sub_source = source[startbrace+1:endbrace] 2334 embedded_calls = [sub_source.find(fname+'(') for fname in callfns] 2335 try: 2336 subpositions = filter(lambda x:x>0,embedded_calls) 2337 if subpositions == []: 2338 filtered_sub_source = sub_source 2339 else: 2340 filtered_sub_source = addArgToCalls(sub_source,callfns,arg,notFirst) 2341 except ValueError: 2342 pass 2343 # add unchanged part to output 2344 # insert arg before end brace 2345 if currpos==0 and callfns == [notFirst]: 2346 notFirst = '' 2347 addStr = '' 2348 else: 2349 if filtered_sub_source == '': 2350 addStr = arg 2351 else: 2352 addStr = ', ' + arg 2353 output += source[currpos:startbrace+1] + filtered_sub_source \ 2354 + addStr + ')' 2355 currpos = endbrace+1 2356 else: 2357 output += source[currpos:] 2358 return output
2359 2360
2361 -def proper_match(specstr, term):
2362 """Determine whether string argument 'term' appears one or more times in 2363 string argument 'specstr' as a proper symbol (not just as part of a longer 2364 symbol string or a number). 2365 """ 2366 ix = 0 2367 term_len = len(term) 2368 while ix < len(specstr) and term_len > 0: 2369 found_ix = specstr[ix:].find(term) 2370 pos = found_ix + ix 2371 if found_ix > -1: 2372 try: 2373 if specstr[pos + term_len] not in [')', '+', '-', '/', '*', ' ', 2374 ']', ',', '<', '>', '=', '&', '^']: 2375 # then term continues with additional name characters: 2376 # no match after all 2377 ix = pos + term_len 2378 continue 2379 except IndexError: 2380 # no other chars remaining, so doesn't matter 2381 pass 2382 if isNumericToken(term): 2383 # have to be careful that we don't replace part 2384 # of another number, e.g. origterm == '0' 2385 # with repterm == 'abc', and the numeric literal '130' 2386 # appears in specstr. The danger is that we'd end up 2387 # with new specstr = '13abc' 2388 if specstr[pos-1] in num_chars + ['.', 'e'] or \ 2389 specstr[pos + term_len] in num_chars + ['.', 'e']: 2390 ix = pos + term_len 2391 continue 2392 return True 2393 else: 2394 break 2395 return False
2396 2397
2398 -def remove_indices_from_range(ixs, max_ix):
2399 """From the indices 0:max_ix+1, remove the individual 2400 index values in ixs. 2401 Returns the remaining ranges of indices and singletons. 2402 """ 2403 ranges = [] 2404 i0 = 0 2405 for ix in ixs: 2406 i1 = ix - 1 2407 if i1 < i0: 2408 i0 = ix + 1 2409 elif i1 == i0: 2410 ranges.append([i0]) 2411 i0 = ix + 1 2412 else: 2413 ranges.append([i0,i1+1]) 2414 i0 = ix + 1 2415 if i0 < max_ix: 2416 ranges.append([i0, max_ix+1]) 2417 elif i0 == max_ix: 2418 ranges.append([i0]) 2419 return ranges
2420