source: src/mpy.py @ 5

Last change on this file since 5 was 5, checked in by franck, 14 years ago

mpy includes file when it is in brackets (i.e. [file]) only if file has at least 1 extension.
[vci.ports] good
[vci.mpy.ports] good
[vciports] bad

  • Property svn:executable set to *
File size: 21.0 KB
Line 
1#!/usr/bin/env python
2# -------------------------------------------------------------------------------------------------
3# Macroprocessor vith PYthon script inclusion
4#
5# Written by Franck Wajsburt at LIP6 2009
6#
7# This program is free software; you can redistribute it and/or
8# modify it under the terms of the GNU General Public License as
9# published by the Free Software Foundation; either version 2 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful, but
13# WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15# General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program; if not, write to the Free Software
19# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20# -------------------------------------------------------------------------------------------------
21# requires doctutils package to run rst2html
22#
23# TODO step by step execution
24# TODO write a manual
25# TODO add unitary tests
26# TODO add [ and { despecialization
27# -------------------------------------------------------------------------------------------------
28_date = '2011-01-05 14:30'
29_version = '1.08'
30_authors = 'Franck Wajsburt'
31_organization = '(UPMC/LIP6/SOC)'
32__doc__ = '''\
33===========================================================================
34Macroprocessor vith PYthon script inclusion
35 - Authors:      '''+_authors+'''
36 - Date:         '''+_date+'''
37 - Version:      '''+_version+'''
38 - Organisation: '''+_organization+'''
39==========================================================================='''
40
41import os, sys, pdb, math, re
42from optparse import OptionParser
43from datetime import datetime
44
45# -------------------------------------------------------------------------------------------------
46# os and log utilities
47# -------------------------------------------------------------------------------------------------
48
49def _mpyhash(s):
50   return "%08x" % (hash(s) & 0xffffffff)
51
52def _exit(error):
53   if (error <> ''):
54      print >> sys.stderr, "\n*** mpy:", error
55   print >> sys.stderr, "*** preprocessing aborted\n"
56   exit(1)
57
58def _mpyfindfile(fname, mpyidir):
59   "try to find fname in any directory (or subdir) of mpyidir list"
60   "if fname is 'stdin' then just returns 'stdin' else returns real filename"
61   "if fname does not exist then returns ''"
62   if fname == 'stdin':
63      return fname
64   else:
65      for path in mpyidir:
66         for root, dirs, files  in os.walk(path):
67            for f in files:
68               if f == fname: # fname found
69                  return os.path.join(root,f)
70      return ''
71
72def _mpyopen(fname = '', mpyidir = [], realfname = ''):
73   "two behaviors:"
74   " 1. try to open realfname but if realfname is '' then 2."
75   " 2. try to open fname in any directory OR SUBDIR of mpyidir list1"
76   "returns tuple (string_with_file_content, real_file_name)"
77   "if fname is 'stdin' then open sys.stdin file"
78   if realfname == '':
79      realfname = _mpyfindfile(fname,mpyidir)
80   if realfname == '':
81      _exit("unable to find file %s" % fname)
82   if realfname == 'stdin':
83      return sys.stdin, fname
84   else:
85      try: mpyin = open(realfname,'r')
86      except IOError, e: _exit(e) # but cannot be open
87      return mpyin, realfname
88
89def _mpytree(mess):
90   print >> mpyglobals['mpytree'], mess
91   mpyglobals['mpytree'].flush()
92
93def _mpylog(mess):
94   global mpyglobals
95   print >> mpyglobals['mpylog'], mess
96   mpyglobals['mpylog'].flush()
97   if mpyglobals['mpyverbose']:
98      print >> sys.stderr, mess
99
100def _dictlog(dic, exclude=[]):
101   for key,val in dic.items():
102      for exc in exclude:
103         if key[0:len(exc)] == exc: 
104            break
105      else: 
106         _mpylog(' :%s: %s' % (key, str(val)))
107
108# -------------------------------------------------------------------------------------------------
109# macro expander
110# -------------------------------------------------------------------------------------------------
111
112def _mpy( mpyins  = []    # ['infile1',  ...] 
113        , mpytxt  = ''    # 'python code'
114        , mpyout  = ''    # 'outfile'
115        , mpydict = {} ): # global dictionary
116
117   # to allow _mpy call in inclusion
118   exec 'from mpy import mpyglobals, mpygen, mpyexp' in mpydict
119
120   # get arguments from the global dictionary
121   global mpyglobals
122   mpylog     = mpyglobals['mpylog']     # log file
123   mpylist    = mpyglobals['mpylist']    # file list log file
124   mpydebug   = mpyglobals['mpydebug']   # True|False
125   mpyverbose = mpyglobals['mpyverbose'] # True|False
126   mpywdir    = mpyglobals['mpywdir']    # working directory
127   mpyidir    = mpyglobals['mpyidir']    # list of input directory
128   mpyoutlist = mpyglobals['mpyoutlist'] # list of opened output
129   mpytabwidth= mpyglobals['mpytabwidth']# number of char of a tabulation character
130   
131   # this is necessary in order to write in mpyout file with just print
132   if mpyout not in ('', 'stdout'):
133      stdout = sys.stdout
134      try: 
135         sys.stdout = open( mpywdir+'/'+mpyout, "w")
136         mpyoutlist.append(mpywdir+'/'+mpyout)
137         mpylist.write(mpywdir+'/'+mpyout+'\n')
138      except IOError, e: _exit(e)
139
140   opened_mpyin = [] # list of opened mpyin (include)
141
142   # default values, a way to define the variable type
143   mpyin = sys.stdin # mpyin file
144   mpyfname = ''     # real filename of mpyin file
145   mpylineno = 1     # line number in mpyin file
146   mpycharno = 0     # character number in mpyin file
147   mpylength = 0     # number of character of mpyin file
148   level = 0;        # level > 0 in python inclusion
149   mode = ''         # '' outside python section, 'eval' | 'code' inside
150   buf = ''          # buffer for python section
151
152   # the first item in mpyins is a buffer to exec
153   mpyins = [mpytxt] + mpyins
154
155   # for all files in mpyins list (first name is a buffer) {
156   nbloop = 0
157   for fname in mpyins:
158
159      if nbloop == 0:
160         # the fist item of mpyins is a buffer
161         mpyin, mpyfname = None, 'buffer'
162         mpybuf = fname
163         if mpybuf != '':
164            lbuf = len(mpybuf)
165            if lbuf > 31: lbuf = 31
166            if mpydebug == True:
167               _mpylog("\n**Expand to file** %s **from text** `%s...`" 
168                      % (mpyoutlist[-1], mpybuf[0:lbuf]))
169            if mpydebug == True: 
170               _dictlog(mpydict, exclude=['_','mpy'])
171      else:
172         # try to open fnam then initialize all associated counters
173         mpyin, mpyfname = _mpyopen(fname,mpyidir)
174         mpybuf = mpyin.read(-1)   # all chars of mpyin file
175         if __name__ == '__main__':
176            _mpylog('\nLog\n'+'-'*40) 
177           
178         _mpylog("\n**Generate to file** %s **from file** %s" 
179                % (mpyoutlist[-1], mpyfname))
180         if nbloop == 1 and mpytxt == '': 
181            _dictlog(mpydict, exclude=['_','mpy'])
182
183      mpylineno = 1              # line number in mpyin file
184      mpycharno = 0              # character number in mpyin file
185      mpylinecharno = 0          # character number in current line
186      startlineno = 0            # first ligne of python inclusion
187      mpylength = len(mpybuf)    # number of character of mpyin file
188      level = 0;                 # level > 0 in python inclusion
189      mode = ''                  # '' because outside python section at first
190      buf = ''                   # buffer for python section
191      instr = 0                  # in string boolean (0 outside, 1 inside)
192      erasespace = 0             # nb space to erase for current line
193      nbspacetab = 0             # nb space to erase for current expands
194
195      # fname may include files, when a file ends, it is maybe an
196      # include file and then we must continue to process.
197      # This while exits with a break statement
198      # while the hierarchical file continues {
199      while True:
200
201         # foreach char in current mpybuf {
202         while mpycharno < mpylength:
203
204            # get the current char
205            c = mpybuf[mpycharno]
206            mpycharno += 1
207
208            # in order to get line number of error
209            if c == '\n':
210               mpylinecharno = 0
211               mpylineno += 1
212            else:
213               mpylinecharno += 1
214
215            # debug mode merges python section with result file
216            if mpydebug == True :
217               sys.stdout.write(c)
218
219            # outside python section
220            if level == 0:
221               startlineno = mpylineno
222               if c == '[':
223                  level += 1
224                  mode = 'eval'
225               elif c == '{':
226                  level += 1
227                  mode = 'code'
228               else:
229                  if mpydebug == False: # in debug mode c is already written
230                     sys.stdout.write(c)
231
232            # inside python section
233            else:
234
235               # tabulation of expansion
236               if erasespace != 0:
237                  if c == ' ' :
238                     erasespace -= 1
239                     continue
240                  if c == '\t' :
241                     erasespace -= mpytabwidth
242                     continue
243                  else:
244                     if c == '\n':
245                        erasespace = nbspacetab
246                        buf += c
247                        continue
248                     else:
249                        _exit ( "%s[%d]: %d spaces expected not %c" 
250                              % (mpyfname, mpylineno, nbspacetab, c))
251               
252               # we are inside a string is it a newline ?
253               if instr == 1 and c == '\n':
254                  erasespace = nbspacetab
255
256               # is it a string delimitor ?
257               if buf[-2:]+c == "'''" :
258                  instr = 1 - instr
259                  if instr == 1:
260                     nbspacetab = mpylinecharno-3
261
262               # count [ and {
263               if c == '[' :
264                  if mode == 'eval':
265                     level += 1
266                  buf += c
267               elif c == '{':
268                  if mode == 'code':
269                     level += 1
270                  buf += c
271
272               # test for evaluation
273               elif c == ']':
274                  if level == 0:
275                     if startlineno == mpylineno:
276                        _exit ( "%s[%d]: unexpected ']'" % (mpyfname, mpylineno))
277                     else:
278                        _exit ( "%s[%d-%d]: unexpected ']'" % (mpyfname, startlineno, mpylineno))
279                  elif mode == 'code':
280                     buf += c
281                  else: # mode eval
282                     level -= 1
283                     if level != 0:
284                        buf += c
285                     else:
286                        # test if buf is a file name, if yes includes file
287                        # therefore pushes the current file and opens the new
288
289                        if re.match("\w*(\.\w*)+",buf):
290                           incname = _mpyfindfile(buf,mpyidir)
291                           if incname != '':
292                              if mpydebug == True:
293                                 _mpylog("\nInclude  to %s from %s" % (mpyout, incname))
294
295                              # push known parameters of the current opened file
296                              opened_mpyin.append((mpyin, mpybuf, mpyfname,
297                                                mpylineno, mpycharno, mpylength))
298
299                              # old values required for test of recursivity just below
300                              oldmpyfname = mpyfname
301                              oldmpylineno = mpylineno
302
303                              # open the include file
304                              mpyin, mpyfname = _mpyopen(realfname = incname)
305                              mpybuf = mpyin.read(-1)
306                              mpylineno = 1
307                              mpycharno = 0
308                              mpylength = len(mpybuf)
309                              level = 0
310                              buf = mode = ''
311
312                              # test recursive include
313                              for f in opened_mpyin[:-1]:
314                                 if mpyfname in f:
315                                    _exit ("%s[%d]: forbidden recursive inclusion on %s"
316                                           % (oldmpyfname, oldmpylineno, mpyfname))
317
318                        # just evaluate buf
319                        else:
320                           try: print eval(buf, mpydict),
321                           except Exception, e:
322                              if startlineno == mpylineno:
323                                 _exit ( "%s[%d]: %s" % (mpyfname, mpylineno, e))
324                              else:
325                                 _exit ( "%s[%d-%d]: %s" % (mpyfname, startlineno, mpylineno, e))
326                           buf = mode = ''
327
328               # test for execute           
329               elif c == '}':
330                 
331                  if level == 0:
332                     if startlineno == mpylineno:
333                        _exit ( "%s[%d]: unexpected '}'" % (mpyfname, mpylineno))
334                     else:
335                        _exit ( "%s[%d-%d]: unexpected '}'" % (mpyfname, startlineno, mpylineno))
336                  elif mode == 'eval':
337                     buf += c
338                  else: # mode code
339                     level -= 1
340                     if level != 0:
341                        buf += c
342                     else:
343                        try:
344                           mpydict
345                           # print >> sys.stderr, buf
346                           exec(buf, mpydict)
347                           # pdb.run(buf, mpydict)
348                        except Exception, e:
349                           if startlineno == mpylineno:
350                              _exit ( "%s[%d]: %s" 
351                                     % (mpyfname, mpylineno, e))
352                           else:
353                              _exit ( "%s[%d-%d]: %s" 
354                                    % (mpyfname, startlineno, mpylineno, e))
355                        buf = mode = ''
356               else:
357                  buf += c
358
359         # } end while mpycharno < mpylength:
360
361         if mpyfname not in ['stdin', 'buffer']:
362            mpyin.close()
363
364         if level <> 0:
365            if mode == 'eval':
366               _exit ("%s[%d]: expected ']'" % (mpyfname,mpylineno))
367            else:
368               _exit ("%s[%d]: expected '}'" % (mpyfname,mpylineno))
369
370         # pops included file if there is one, else breaks while
371         try:
372            mpyin, mpybuf, mpyfname, mpylineno, mpycharno, mpylength = opened_mpyin.pop()
373            level = 0
374            buf = mode = ''
375
376         except: break
377
378      # } end while True:
379
380      # because the first fname is actually a buffer
381      nbloop += 1
382
383   # } end for fname in mpyins:
384
385   # close and redirect sys.stdout
386   if mpyout not in ('', 'stdout'):
387      sys.stdout.close()
388      sys.stdout = stdout
389      mpyoutlist.pop()
390
391# -------------------------------------------------------------------------------------------------
392
393def mpygen(mpyin, param):
394   "call the macro processor on file mpyin with its own dictionaty, produce a newfile"
395   global mpyglobals
396
397   # check parameters
398   if type(mpyin) <> str:
399      _exit('first argument of mpygen: %s is not a valid filename' % str(mpyin))
400   if type(param) <> dict:
401      _exit('second argument of mpygen: %s is not a valid dictionary' % str(param))
402
403   # test input filename syntax
404   mpyout = os.path.basename(mpyin).split('.')
405   if mpyout[-2] <> 'mpy':
406      _exit('filename %s has not a valid form (anyroot.mpy.anyext)' % str(mpyin))
407
408   # generate output filename
409   hashnumber = _mpyhash(`param`)
410   mpyout_base = '.'.join(mpyout[0:-2])
411   mpyout = mpyout_base+'_'+hashnumber+'.'+mpyout[-1]
412   mpymodelname = mpyout_base+'_'+hashnumber
413   param['mpymodelname'] = mpymodelname
414
415   # call the macro processor
416   _mpytree('%s* %s <- %s\n' % ('  '*mpyglobals['mpylevelgen'], mpyout, mpyin))
417   mpyglobals['mpylevelgen'] += 1
418   _mpy(mpyins = [mpyin], mpydict = param, mpyout = mpyout)
419   mpyglobals['mpylevelgen'] -= 1
420   _mpytree('')
421
422   # return the computed model name
423   return mpymodelname
424
425# -------------------------------------------------------------------------------------------------
426
427def mpyexp(txt, param):
428   "call the macro processor on a buffer of chars with its own dictionary"
429   if type(txt) <> str:
430      _exit('argument of mpyexp: must be a string not %s' % type(txt))
431   _mpy(mpytxt = txt, mpydict = param)
432
433# -------------------------------------------------------------------------------------------------
434
435def log2(x):
436    return int(math.ceil(math.log(x,2)))
437
438# -------------------------------------------------------------------------------------------------
439# define globals dictionnary
440# -------------------------------------------------------------------------------------------------
441
442_parser = OptionParser( 
443            usage = "%prog [options] [infiles...]",
444            version = "version "+_version, 
445            description = "mpy opens all infiles list (default stdin) to macro-process " 
446                          "them and writes the result in OFILE (default stdout). "
447                          "Infiles are searched in any dir or subdir of PATHS (default "
448                          "MPYIDIR environment variable)")
449
450_parser.set_defaults( verbose = False , debug = False , txt = '', tab = 8 
451                    , wdir = '.' , idir = '' , output = 'stdout')
452
453_parser.add_option("-v", "--verbose", action = "store_true", dest = "verbose"
454                  , help = "trace processing step")
455_parser.add_option("-d", "--debug", action = "store_true", dest = "debug" 
456                  , help = "do not remove python inclusions")
457_parser.add_option("-t", "--txt", dest = "txt" 
458                  , help = "text (chars) to macro process")
459_parser.add_option("-b", "--tab", dest = "tab" 
460                  , help = "tabulation width [default: %default]")
461_parser.add_option("-w", "--wdir", dest = "wdir"
462                  , help = "define the working directory [default: %default]")
463_parser.add_option("-o", "--output", dest = "ofile", default = 'stdout'
464                  , help = "define output filename [default: %default] ")
465_parser.add_option("-n", "--name", dest = "name", default = 'mpy'
466                  , help = "define instance name, basename for logs [default: 'mpy']")
467_parser.add_option("-i", "--idir", dest = "idir" 
468                  , help = "list of directories (separated with colon ':') "
469                           "where to find input files "
470                           "(default %prog use MPYPATH environment variable) "
471                           "%prog begins to search in the working directory (-w option) "
472                           "then in all the directories or subdirectories of the path")
473
474(_options, _mpyins) = _parser.parse_args()
475
476# if no path dirctories are given in command line, getenv MPYIDIR
477if _options.idir == '':
478   try: _options.idir = os.environ['MPYPATH']
479   except: pass
480
481# gets the input files
482if len(_mpyins) == 0: 
483   _mpyins = ['stdin']
484
485# open log file
486if not os.path.isdir(_options.wdir): 
487   _exit('%s is not a working directory' % _options.wdir)
488try: 
489   _mpymetaname = os.path.join(_options.wdir,_options.name+"_meta.log")
490   _mpytreename = os.path.join(_options.wdir,_options.name+"_tree.log")
491   _mpylistname = os.path.join(_options.wdir,_options.name+".list")
492   _mpymetafile = open(_mpymetaname,"a")
493   _mpytreefile = open(_mpytreename,"a")
494   _mpylistfile = open(_mpylistname,"a")
495   _logname = os.path.join(_options.wdir, _options.name+".log")
496   _htmlname = os.path.join(_options.wdir,_options.name+".html")
497except IOError, e: 
498   _exit(e) 
499
500mpyglobals = dict( mpyverbose = _options.verbose # True|False
501                 , mpydebug = _options.debug # True|False
502                 , mpywdir = _options.wdir # working directory
503                 , mpyidir = [_options.wdir]+_options.idir.split(':') # split pathes in a list
504                 , mpylevelgen = 1              # level of generation (tree deep)
505                 , mpylog  = _mpymetafile
506                 , mpytree = _mpytreefile
507                 , mpylist = _mpylistfile
508                 , mpytabwidth = _options.tab
509                 , mpyoutlist = [_options.ofile])
510
511# -------------------------------------------------------------------------------------------------
512# main function
513# -------------------------------------------------------------------------------------------------
514
515if __name__ == '__main__':
516   _mpytree('''
517========================================================================
518MPY: Macroprocessor with PYthon script inclusion (V'''+_version+''')
519========================================================================
520Generation date '''+datetime.now().strftime("%A, %d. %B %Y %I:%M%p")+'''
521~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
522''')
523   _mpytree( '\nTree Generation\n'+'-'*40+'\n\n')
524   _mpytree( '* %s <- %s\n' % (_options.ofile, _mpyins[0]))
525   _mpy(mpytxt = _options.txt, mpyout = _options.ofile, mpyins = _mpyins, mpydict = {})
526   try:
527      os.system('cat '+_mpytreename+' '+_mpymetaname+' > '+_logname)
528      os.remove(_mpytreename)
529      os.remove(_mpymetaname)
530      os.system('rst2html '+_logname+' '+_htmlname+' 2> /dev/null')
531   except:
532      pass
Note: See TracBrowser for help on using the repository browser.