PageRenderTime 46ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/TemaLib/tema/modelutils/rextendedrules.py

https://github.com/jaaskel9/tema-tg
Python | 497 lines | 454 code | 2 blank | 41 comment | 0 complexity | 56c4e9e65790e7c2eca2747e50aa3069 MD5 | raw file
  1. #!/usr/bin/env python
  2. # coding: iso-8859-1
  3. # Copyright (c) 2006-2010 Tampere University of Technology
  4. #
  5. # Permission is hereby granted, free of charge, to any person obtaining
  6. # a copy of this software and associated documentation files (the
  7. # "Software"), to deal in the Software without restriction, including
  8. # without limitation the rights to use, copy, modify, merge, publish,
  9. # distribute, sublicense, and/or sell copies of the Software, and to
  10. # permit persons to whom the Software is furnished to do so, subject to
  11. # the following conditions:
  12. #
  13. # The above copyright notice and this permission notice shall be
  14. # included in all copies or substantial portions of the Software.
  15. #
  16. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  17. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  18. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  19. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  20. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  21. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  22. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  23. """
  24. tema.rextendedrules 0.3b
  25. This program takes extended rules file with regular expressions as
  26. input and gives extended rules as output. The output can be redirected
  27. to tvt.extendedrules program.
  28. Usage:
  29. tema.rextendedrules [options] <inputfile> [outputfile]
  30. Options:
  31. --help Print this help.
  32. --re-help Show help on regular expressions.
  33. --force Overwrite output file if it exists.
  34. --Wall Print warnings if rows of input file
  35. could not be parsed (otherwise the rows
  36. are silently skipped).
  37. If input (output) file name is replaced by '-', standard input
  38. (output) is used instead.
  39. Copyright (C)
  40. 2004-2005 by VARG research group at Tampere University of Technology,
  41. author Antti Kervinen, teams@cs.tut.fi.
  42. """
  43. ##
  44. # 0.21b-> 0.3b
  45. # changes in behavior:
  46. # - does not print a rule if there is an (lsts,action) pair where
  47. # action is not in the alphabet of the lsts
  48. # - multiple lsts synchronisations are checked in the end
  49. # after broken rules (see previous change) are removed
  50. #
  51. #
  52. # 0.2b -> 0.21b
  53. # new features:
  54. # - $E(expression)E is replaced by the value of /expression/
  55. # evaluated in Python.
  56. #
  57. # 0.11 -> 0.2b
  58. # new features:
  59. # - ALL and OPT keywords for introducing rules with
  60. # variable number of participant processes.
  61. # - process ids can be given as arrays
  62. # - empty and comment rows are be copied to output
  63. #
  64. #
  65. # 0.1 -> 0.11
  66. # bug fixes: handling tau, prints now all process names,
  67. # can be run with only one argument (default output is stdout)
  68. import tema.lsts.lsts as lsts
  69. import sys
  70. import re
  71. import copy
  72. import os
  73. RULEROW=0
  74. COMMENTROW=1
  75. class RextendedrulesError(Exception): pass
  76. def error(s,exitval=1):
  77. print >>sys.stderr,"rextendedrules: %s" % s
  78. sys.exit(exitval)
  79. ### TVT-like routine for argument handling
  80. def parse_args(argv):
  81. try:
  82. # parse (and remove) options
  83. if len(argv)==1 or "--help" in argv:
  84. print __doc__
  85. sys.exit(0)
  86. if "--re-help" in argv:
  87. print re.__doc__
  88. sys.exit(0)
  89. overwriteoutput=(0,1)["--force" in argv]
  90. if overwriteoutput: argv.remove("--force")
  91. parsewarnings=(0,1)["--Wall" in argv]
  92. if parsewarnings: argv.remove("--Wall")
  93. # parse input and output files
  94. if len(argv)==2: argv.append("-") # default output is stdout
  95. if len(argv)!=3: raise Exception("Illegal arguments. Try --help.")
  96. inputfile=argv[1]
  97. if argv[2]=="-": outputfile=argv[2]
  98. elif not overwriteoutput and os.path.isfile(argv[2]):
  99. raise Exception("'%s' already exists. Use --force to overwrite." % argv[2])
  100. else: outputfile=argv[2]
  101. except SystemExit, num:
  102. sys.exit(num)
  103. except Exception, e:
  104. error(e)
  105. return (parsewarnings,inputfile,outputfile)
  106. ### end of argument routine
  107. class Rextfile_parser:
  108. def __init__(self):
  109. self.__procrowre = re.compile('\s*([^=\s]+)\s*=\s*"([^"]+)"')
  110. self.__procarray = re.compile('\[([0-9]+)\s*\.\.\s*([0-9]+)\]')
  111. self.__rulerowre = re.compile('^\s*([A-Z]*\(.*\))\s*->\s*"([^"]+)"\s*$')
  112. # single syncronisating action spec on the left of ->
  113. self.__syncactre = re.compile('([A-Z]*)\(\s*([^,\s]*)\s*,\s*"([^"]+)"\s*\)')
  114. # comment row
  115. self.__commentrow = re.compile('^(\s*(#.*|))$')
  116. # regexps are in ${regex} and replace numbers $=nn.
  117. self.__regex = re.compile('\$\{([^}]+)\}')
  118. # evalexps are in $(evalex), they are evaluated when all
  119. # regexps have been expanded, unless they are inside a regular
  120. # expression. In the latter case they are evaluated just
  121. # before the regular expression is expanded
  122. self.__evalex = re.compile('\$E\(((?!\)E).*)\)E')
  123. def commentrow(self,s):
  124. m = re.match(self.__commentrow,s)
  125. if m:
  126. return m.group(1)
  127. return None
  128. def processrow(self,s):
  129. m = re.match(self.__procrowre,s)
  130. if m:
  131. lsts_key = m.group(1)
  132. lsts_filename = m.group(2)
  133. return lsts_key,lsts_filename
  134. return None
  135. def rulerow(self,s):
  136. m = re.match(self.__rulerowre,s)
  137. if m:
  138. leftspecs=re.findall(self.__syncactre,m.group(1))
  139. rightspec=m.group(2)
  140. if len(leftspecs)>0:
  141. return leftspecs,rightspec
  142. return None
  143. def expand_procarray(self,s):
  144. names=[s]
  145. for m in self.__procarray.finditer(s):
  146. grouplen=len(m.group(0))
  147. fill,newnames='='*grouplen,[]
  148. for i in xrange(int(m.group(1)),int(m.group(2))+1):
  149. # in 2.4 python: ns=str(i).rjust(grouplen,'=')
  150. ns=('%s%s' % (fill,i))[-grouplen:]
  151. for n in names:
  152. newnames.append(n[:m.start()]+ns+n[m.end():])
  153. names=newnames
  154. return [s.replace('=','') for s in names]
  155. def pop_next_regex(self,s):
  156. m = self.__regex.search(s)
  157. if m:
  158. return s[:m.start(1)-2],m.group(1),s[m.end(1)+1:]
  159. return s,None,""
  160. def replace_next(self,rule,repl,match_rhs=1):
  161. try:
  162. replp=re.compile('\$\='+str(rule[2])+'\.')
  163. except Exception, e:
  164. print rule
  165. print e
  166. try:
  167. for triplet_index,triplet in enumerate(rule[0]):
  168. rule[0][triplet_index]=(rule[0][triplet_index][0],
  169. replp.sub(repl,triplet[1]),
  170. replp.sub(repl,triplet[2]))
  171. if match_rhs: rule[1]=replp.sub(repl,rule[1])
  172. except Exception, e:
  173. print rule
  174. print e
  175. rule[2]+=1
  176. return rule
  177. def expand_evalexps(self,s):
  178. ee = self.__evalex # evaluation expression pattern $E(...)E
  179. try:
  180. if ee.findall(s)==[]:
  181. return s
  182. else:
  183. return ee.sub('%s',s) % \
  184. tuple( [eval(expr) for expr in ee.findall(s)] )
  185. except Exception, e:
  186. raise RextendedrulesError("invalid Python evaluation in '%s'%s(%s)%s" % (s,os.linesep,e,os.linesep))
  187. # sys.stderr.write("ERROR: tvt.rextendedrules: invalid Python evaluation in '%s'\n(%s)\n" % (s,e))
  188. # sys.exit(1)
  189. def rextendedrules(working_dir, parse_warnings,input_fileobj,output_fileobj):
  190. parser=Rextfile_parser()
  191. filenames={}
  192. alphabets={}
  193. lstsnumber={}
  194. rules=[]
  195. # rules = [
  196. # [ [ (quant,lstskey, actionname), ..., (quant,key_n, name_n) ],
  197. # result,
  198. # next_expanded_re_number,
  199. # ruletype ::= RULEROW | COMMENTROW,
  200. # row_number_in_input_file,
  201. # dict: participant -> number of appearances in the row,
  202. # ],
  203. # similar rule row 2
  204. # ...
  205. # ]
  206. # Parse rextended rules
  207. for rowindex,l in enumerate(input_fileobj):
  208. row=parser.processrow(l)
  209. if row:
  210. for processname in parser.expand_procarray(row[0]):
  211. filenames[processname]=row[1]
  212. lstsnumber[processname]=len(filenames)
  213. continue
  214. row=parser.rulerow(l)
  215. if row:
  216. rules.append([row[0],row[1],1,RULEROW,rowindex+1,{}])
  217. continue
  218. row=parser.commentrow(l)
  219. if row:
  220. rules.append([row,0,0,COMMENTROW,rowindex+1])
  221. continue
  222. if parse_warnings:
  223. sys.stderr.write("WARNING: rextendedrules: could not parse row %s:%s\t'%s'%s" % (rowindex+1,os.linesep,l.strip(),os.linesep))
  224. # Get actionnames sections of mentioned lsts files
  225. for key in filenames:
  226. r=lsts.reader()
  227. try:
  228. r.read(open(os.path.join(working_dir,filenames[key]),'r'))
  229. alphabets[key]=r.get_actionnames()
  230. except Exception, e:
  231. raise RextendedrulesError("(in file '%s'): %s%s" % (filenames[key],e,os.linesep))
  232. # sys.stderr.write("ERROR: tvt.rextendedrules (in file '%s'): %s\n" % (filenames[key],e))
  233. # import traceback
  234. # traceback.print_exc(file=sys.stderr)
  235. # sys.exit(1)
  236. del r # no need for reader anymore.
  237. ### Print LSTS files and their numbers
  238. lsts_id_by_num={}
  239. for k in lstsnumber: lsts_id_by_num[lstsnumber[k]]=k
  240. for n in xrange(1,max(lsts_id_by_num.keys())+1):
  241. output_fileobj.write('%s="%s"%s' % (n,filenames[lsts_id_by_num[n]],os.linesep))
  242. # Handle the rules in the original order. The rules list will be
  243. # handled as a stack, so we have to reorder it.
  244. rules=rules[::-1]
  245. def create_regexp(s,rownumber):
  246. # return None, if s has no regexps, otherwise
  247. # builds and returns a new regular expression
  248. prefix,regex,suffix=parser.pop_next_regex(s)
  249. if not regex:
  250. return None
  251. newregex=""
  252. while regex:
  253. newregex+=re.escape(prefix)+"("+parser.expand_evalexps(regex)+")"
  254. prefix,regex,suffix=parser.pop_next_regex(suffix)
  255. newregex+=re.escape(prefix)
  256. try:
  257. retval=re.compile(newregex)
  258. return retval
  259. except:
  260. raise RextendedrulesError("in row %s invalid regular expression: '%s'%s" % (rownumber,s,os.linesep))
  261. # sys.stderr.write("ERROR: tvt.rextendedrules: in row %s invalid regular expression: '%s'\n" % (rownumber,s))
  262. # sys.exit(1)
  263. while rules:
  264. # 0. Take a rule
  265. r=rules.pop()
  266. if r[3]==COMMENTROW:
  267. output_fileobj.write("%s%s" % (r[0],os.linesep))
  268. continue
  269. # assume: r[3]==RULEROW
  270. # quantifier ::= empty | ALL | OPT
  271. for left_index,left_contents in enumerate(r[0]):
  272. quantifier,lstskey,actionname=left_contents
  273. # 1. If quantifier of a participant is ALL,
  274. # extend it to many participants
  275. if quantifier=='ALL':
  276. newre=create_regexp(lstskey,r[4])
  277. if newre:
  278. cr=copy.deepcopy(r)
  279. cr[0]=[]
  280. for k in alphabets.keys():
  281. m=newre.match(k)
  282. if m:
  283. appearcount=r[5].get(k,0)
  284. if appearcount!=0: continue # skip already participating lstss
  285. r[5][k]=1
  286. cr[0].append( ('OPT',k,actionname) )
  287. for g in m.groups():
  288. parser.replace_next(cr,g,match_rhs=0)
  289. cr[2]=cr[2]-len(m.groups())
  290. cr[0]=r[0][:left_index] + cr[0] + r[0][left_index+1:]
  291. rules.append(cr)
  292. break
  293. else:
  294. # no regexp but quantifier == ALL
  295. # handling is equal to OPT.
  296. r[0][left_index]=('OPT',lstskey,actionname)
  297. quantifier='OPT'
  298. # no need to break here
  299. # endif --- after this quantifier != ALL
  300. # 2. Search for regular expressions in lstskey part of a
  301. # action specification in the left hand side of the rule
  302. newre=create_regexp(lstskey,r[4])
  303. if newre:
  304. matchcount=0
  305. for k in alphabets.keys():
  306. m=newre.match(k)
  307. if m:
  308. matchcount+=1
  309. # 2.1 If quantifier=='' or quantifier=='OPT',
  310. # create a new rule for each lstskey that matched
  311. # to the regexp. In the new rule, replace every
  312. # '$=n.' by the string that matched the rexexp.
  313. # part. Add all the resulting rules to the rules
  314. # stack.
  315. cr=copy.deepcopy(r)
  316. cr[5][k]=cr[5].get(k,0)+1
  317. if quantifier=='OPT':
  318. if cr[5][k]>1:
  319. cr[0]=cr[0][:left_index]+cr[0][left_index+1:]
  320. break # silently ignore participant
  321. cr[0]=[ (quantifier,k,actionname) ]
  322. for g in m.groups():
  323. parser.replace_next(cr,g,match_rhs=0)
  324. cr[0]=r[0][:left_index] + cr[0] + r[0][left_index+1:]
  325. cr[2]=cr[2]-len(m.groups)
  326. elif quantifier=='':
  327. if cr[5][k]>1:
  328. #sys.stderr.write("ERROR: tvt.rextendedrules: in row %s LSTS '%s' synchronised more than once.\n" % (r[4],k))
  329. #sys.exit(1)
  330. pass # hope that the problem goes away before last check
  331. cr[0][left_index]=(quantifier,k,actionname)
  332. for g in m.groups():
  333. parser.replace_next(cr,g)
  334. rules.append(cr)
  335. break
  336. if quantifier=='OPT' and (not lstskey in alphabets and matchcount==0):
  337. r[0]=r[0][:left_index]+r[0][left_index+1:]
  338. rules.append(r)
  339. break
  340. # 3. Similarly to 2., handle the actionname.
  341. # lstskey should be valid (it did not contain regexp).
  342. try:
  343. actionlist=alphabets[lstskey]
  344. except KeyError:
  345. if quantifier=='OPT':
  346. r[0]=r[0][:left_index]+r[0][left_index+1:]
  347. rules.append(r)
  348. break
  349. raise RextendedrulesError("in row %s unknown LSTS '%s'%s " % (r[4],lstskey,os.linesep))
  350. # sys.stderr.write("ERROR: tvt.rextendedrules: in row %s unknown LSTS '%s'\n " % (r[4],lstskey))
  351. # sys.exit(1)
  352. matchcount=0
  353. newre=create_regexp(actionname,r[4])
  354. if newre:
  355. for a in actionlist:
  356. m=newre.match(a)
  357. if m:
  358. matchcount+=1
  359. cr=copy.deepcopy(r)
  360. if quantifier=='OPT':
  361. cr[0]=[ ('',lstskey,a) ]
  362. for g in m.groups():
  363. parser.replace_next(cr,g,match_rhs=0)
  364. cr[0]=r[0][:left_index]+cr[0]+r[0][left_index+1:]
  365. if quantifier=='':
  366. cr[0][left_index]=('',lstskey,a)
  367. for g in m.groups():
  368. parser.replace_next(cr,g)
  369. rules.append(cr)
  370. break
  371. if quantifier=='OPT':
  372. # no need to quantify anymore: if no match then remove participant
  373. if (not newre and not actionname in alphabets[lstskey]):
  374. r[0]=r[0][:left_index]+r[0][left_index+1:]
  375. elif actionname in alphabets[lstskey]:
  376. r[0][left_index]=('',lstskey,actionname)
  377. rules.append(r)
  378. break
  379. else: # for-loop was not breaked out => rule does not contain regexps
  380. # it is ready to be printed
  381. rulestr=""
  382. lstsappearances={}
  383. failed=0
  384. for quantifier,lstskey,actionname in r[0]:
  385. if quantifier!='': print 'hell with',r
  386. if actionname in alphabets[lstskey]:
  387. thislsts=lstsnumber[parser.expand_evalexps(lstskey)]
  388. rulestr+='(%s,"%s") ' % (thislsts,parser.expand_evalexps(actionname))
  389. lstsappearances[thislsts]=lstsappearances.get(thislsts,0)+1
  390. else:
  391. failed=1
  392. if not failed: # check that no LSTS appears in any line more than once
  393. if [ v for v in lstsappearances.values() if v>1 ]:
  394. raise RextendedrulesError("the same LSTS synchronized more than once in rule:%s '%s' (row %s)" % (os.linesep,rulestr,r[4]))
  395. # sys.stderr.write("ERROR: tvt.rextendedrules: the same LSTS synchronized more than once in rule:\n '%s' (row %s)" % (rulestr,r[4]))
  396. # sys.exit(1)
  397. if not failed:
  398. output_fileobj.write('%s-> %s%s' % (rulestr,('"'+parser.expand_evalexps(r[1])+'"',0)[r[1].upper()=="TAU"],os.linesep))
  399. if __name__ == "__main__":
  400. parse_warnings,input_filename,output_filename = parse_args(sys.argv)
  401. infile = None
  402. outfile = None
  403. try:
  404. try:
  405. if input_filename == "-":
  406. infile = sys.stdin
  407. else:
  408. infile = open(input_filename,'r')
  409. if output_filename == "-":
  410. outfile = sys.stdout
  411. else:
  412. outfile = open(output_filename,'w')
  413. rextendedrules(os.getcwd(), parse_warnings, infile, outfile )
  414. except RextendedrulesError,e:
  415. error(e)
  416. except KeyboardInterrupt,e:
  417. sys.exit(1)
  418. except:
  419. import traceback
  420. traceback.print_exc(file=sys.stderr)
  421. sys.exit(1)
  422. finally:
  423. if outfile and output_filename != "-":
  424. outfile.close()
  425. if infile and input_filename != "-":
  426. infile.close()