| Home | Trees | Indices | Help |
|
|---|
|
|
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
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
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
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
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
322 t = s
323 for m in convert_power_reserved_keywords:
324 t = t.replace(m, '__'+m+'__')
325 return t
326
328 t = s
329 for m in convert_power_reserved_keywords:
330 t = t.replace('__'+m+'__', m)
331 return t
332
333
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
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
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
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
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
425
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
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
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
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
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
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
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
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
560
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
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
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
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
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
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
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
882 """Abstract class for hassle-free symbol re-mappings."""
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
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
983
985 return not self.__eq__(other)
986
988 return symbolMapClass(deepcopy(self.lookupDict))
989
992
998
1001
1003 return self.lookupDict.__contains__(symbol)
1004
1006 return self.lookupDict.keys()
1007
1009 return self.lookupDict.values()
1010
1012 return self.lookupDict.items()
1013
1015 return self.lookupDict.iterkeys()
1016
1018 return self.lookupDict.itervalues()
1019
1021 return self.lookupDict.iteritems()
1022
1026
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
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
1048
1050 return symbolMapClass(self)
1051
1054
1055 __str__ = __repr__
1056
1059
1060
1062 """Auxiliary function database, for use by parsers."""
1065
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
1075
1076 __str__ = __repr__
1077
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
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
1100
1103
1104
1105 # only need one of these per session
1106 global protected_auxnamesDB
1107 protected_auxnamesDB = auxfnDBclass()
1108
1109
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
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
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
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
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
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
1630 return isToken(s, treatMultiRefs=treatMultiRefs) \
1631 and s[0] not in num_chars and not (len(s)==1 and s=="_")
1632
1635
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
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
1705 s_split = s.split(sep)
1706 return len(s_split) > 1 and alltrue([isNameToken(t, treatMultiRefs) \
1707 for t in s_split])
1708
1710 brace = findEndBrace(s, '[',']')
1711 if s[0] == '[' and isinstance(brace, int):
1712 return ',' in s[1:brace]
1713
1714
1716 return [replaceSep(spec) for spec in speclist]
1717
1719 return [replaceSepInv(spec) for spec in speclist]
1720
1724
1725
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
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
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
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
1844 """Join a list of strings into a single string (in order)."""
1845
1846 return ''.join(strlist)
1847
1848
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
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
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
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
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
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
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
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
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
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
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
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Sun Dec 2 23:44:39 2012 | http://epydoc.sourceforge.net |