source: specdomain/src/specdomain/sphinxcontrib/specmacrofileparser.py @ 961

Last change on this file since 961 was 961, checked in by jemian, 10 years ago

refs #8

  • Property svn:eol-style set to native
  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision URL Header
File size: 9.2 KB
Line 
1#!/usr/bin/env python
2
3########### SVN repository information ###################
4# $Date: 2012-06-21 21:39:57 +0000 (Thu, 21 Jun 2012) $
5# $Author: jemian $
6# $Revision: 961 $
7# $HeadURL: specdomain/src/specdomain/sphinxcontrib/specmacrofileparser.py $
8# $Id: specmacrofileparser.py 961 2012-06-21 21:39:57Z jemian $
9########### SVN repository information ###################
10
11
12"""
13Construct a SPEC macro source code file parser for
14use by the specdomain for Sphinx.
15
16:copyright: Copyright 2012 by BCDA, Advanced Photon Source, Argonne National Laboratory
17:license: ANL Open Source License, see LICENSE for details.
18"""
19
20import os
21import re
22
23
24#   http://www.txt2re.com/index-python.php3
25#  http://regexpal.com/
26
27string_start                = r'^'
28string_end                  = r'$'
29match_all                   = r'.*'
30non_greedy_filler           = match_all + r'?'
31non_greedy_whitespace       = r'\s*?'
32double_quote_string_match   = r'("' + non_greedy_filler + r'")'
33prog_name_match             = r'([a-z_]\w*)'
34word_match                  = r'((?:[a-z_]\w*))'
35cdef_match                  = r'(cdef)'
36extended_comment_marker     = r'\"{3}'
37extended_comment_match      = r'(' + extended_comment_marker + r')'
38
39macro_sig_re = re.compile(
40                               r'''^ ([a-zA-Z_]\w*)         # macro name
41                               ''', re.VERBOSE)
42
43func_sig_re = re.compile(word_match + r'\('
44                      + r'(' + match_all + r')' 
45                      + r'\)', 
46                      re.IGNORECASE|re.DOTALL)
47
48cdef_name_sig_re = re.compile(double_quote_string_match, 
49                                   re.IGNORECASE|re.DOTALL)
50
51
52extended_comment_flag_sig_re = re.compile(extended_comment_marker, 
53                                               re.IGNORECASE|re.DOTALL)
54extended_comment_start_sig_re = re.compile(string_start
55                                                + non_greedy_whitespace
56                                                + extended_comment_match, 
57                                                re.IGNORECASE|re.VERBOSE)
58extended_comment_end_sig_re = re.compile(non_greedy_whitespace
59                                                + extended_comment_match
60                                                + non_greedy_whitespace
61                                                + r'#' + non_greedy_filler
62                                                + string_end,
63                                                re.IGNORECASE|re.VERBOSE)
64extended_comment_block_sig_re = re.compile(string_start
65                                                + non_greedy_whitespace
66                                                + extended_comment_marker
67                                                + r'(' + non_greedy_filler + r')'
68                                                + extended_comment_marker
69                                                + non_greedy_filler
70                                                + string_end, 
71                                                re.IGNORECASE|re.DOTALL|re.MULTILINE)
72lgc_variable_sig_re = re.compile(string_start
73                                    + non_greedy_whitespace
74                                    + r'(local|global|constant)'
75                                    + r'((?:\s*@?[\w.eE+-]+\[?\]?)*)'
76                                    + non_greedy_whitespace
77                                    + r'#' + non_greedy_filler
78                                    + string_end, 
79                                    re.VERBOSE)
80
81
82class SpecMacrofileParser:
83    '''
84    Parse a SPEC macro file for macro definitions,
85    variable declarations, and extended comments.
86    '''
87
88    states = (                  # assume SPEC def macros cannot be nested
89        'global',               # the level that provides the SPEC command prompt
90        'extended comment',     # inside a multiline extended comment
91        'def macro',            # inside a multiline def macro definition
92        'rdef macro',           # inside a multiline rdef macro definition
93        'cdef macro',           # inside a multiline cdef macro definition
94    )
95
96    def __init__(self, macrofile):
97        '''
98        Constructor
99        '''
100        self.buf = None
101        self.findings = []
102        self.filename = None
103        self.read(macrofile)
104        self.parse_macro_file()
105   
106    def read(self, filename):
107        """
108        load the SPEC macro source code file into an internal buffer
109       
110        :param str filename: name (with optional path) of SPEC macro file
111            (The path is relative to the ``.rst`` document.)
112        """
113        if not os.path.exists(filename):
114            raise RuntimeError, "file not found: " + filename
115        self.filename = filename
116        self.buf = open(filename, 'r').read()
117   
118    def parse_macro_file(self):
119        """
120        parse the internal buffer
121        """
122        line_number = 0
123        state = 'global'
124        state_stack = []
125        for line in self.buf.split('\n'):
126            if state not in self.states:
127                raise RuntimeError, "unexpected parser state: " + state
128            line_number += 1
129            if state == 'global':
130
131                m = self._match(lgc_variable_sig_re, line)
132                if m is not None:           # local, global, or constant variable declaration
133                    objtype, args = lgc_variable_sig_re.match(line).groups()
134                    pos = args.find('#')
135                    if pos > -1:
136                        args = args[:pos]
137                    m['objtype'] = objtype
138                    m['start_line'] = m['end_line'] = line_number
139                    del m['start'], m['end'], m['line']
140                    if objtype == 'constant':
141                        var, _ = args.split()
142                        m['text'] = var
143                        self.findings.append(dict(m))
144                    else:
145                        # TODO: consider not indexing "global" inside a def
146                        # TODO: consider not indexing "local" at global level
147                        for var in args.split():
148                            m['text'] = var
149                            self.findings.append(dict(m))
150                            # TODO: to what is this local?  (remember the def it belongs to)
151                    continue
152
153                # test if one-line extended comment
154                m = self._match(extended_comment_block_sig_re, line)
155                if m is not None:
156                    del m['start'], m['end'], m['line']
157                    m['objtype'] = 'extended comment'
158                    m['start_line'] = m['end_line'] = line_number
159                    self.findings.append(dict(m))
160                    continue
161               
162                # test if start of multiline extended comment
163                m = self._match(extended_comment_start_sig_re, line)
164                if m is not None:
165                    text = m['line'][m['end']:]
166                    del m['start'], m['end'], m['line']
167                    m['objtype'] = 'extended comment'
168                    m['start_line'] = line_number
169                    ec = dict(m)    # container for extended comment data
170                    ec['text'] = [text]
171                    state_stack.append(state)
172                    state = 'extended comment'
173                    continue
174
175            elif state == 'extended comment':
176                # test if end of multiline extended comment
177                m = self._match(extended_comment_end_sig_re, line)
178                if m is not None:
179                    text = m['line'][:m['start']]
180                    ec['text'].append(text)
181                    ec['text'] = '\n'.join(ec['text'])
182                    ec['end_line'] = line_number
183                    self.findings.append(dict(ec))
184                    state = state_stack.pop()
185                    del ec
186                else:
187                    # multiline extended comment continues
188                    ec['text'].append(line)
189                continue
190   
191    def _match(self, regexp, line):
192        m = regexp.search(line)
193        if m is None:
194            return None
195        d = {
196            'start': m.start(1),
197            'end':   m.end(1),
198            'text':  m.group(1),
199            'line':  line,
200            'filename':  self.filename,
201        }
202        return d
203
204    def __str__(self):
205        s = []
206        for r in self.findings:
207            s.append( '' )
208            t = '%s %s %d %d %s' % ('*'*20, 
209                                    r['objtype'], 
210                                    r['start_line'], 
211                                    r['end_line'], 
212                                    '*'*20)
213            s.append( t )
214            s.append( r['text'] )
215        return '\n'.join(s)
216
217    def ReST(self):
218        """create the ReStructured Text from what has been found"""
219        s = []
220        for r in self.findings:
221            if r['objtype'] == 'extended comment':
222                s.append( '' )
223                s.append( '.. %s %s %d %d' % (self.filename, 
224                                              r['objtype'], 
225                                              r['start_line'], 
226                                              r['end_line']) )
227                s.append( '' )
228                s.append(r['text'])
229            # TODO: other objtypes
230        return '\n'.join(s)
231
232
233if __name__ == '__main__':
234    p = SpecMacrofileParser('../test/test-battery.mac')
235    #print p.ReST()
236    print p
237    p = SpecMacrofileParser('../test/cdef-examples.mac')
238    #print p.ReST()
Note: See TracBrowser for help on using the repository browser.