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

Last change on this file since 965 was 965, checked in by jemian, 11 years ago

refs #8, section titles in extended comments now handled properly

  • 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: 10.0 KB
Line 
1#!/usr/bin/env python
2
3########### SVN repository information ###################
4# $Date: 2012-06-23 06:56:38 +0000 (Sat, 23 Jun 2012) $
5# $Author: jemian $
6# $Revision: 965 $
7# $HeadURL: specdomain/src/specdomain/sphinxcontrib/specmacrofileparser.py $
8# $Id: specmacrofileparser.py 965 2012-06-23 06:56:38Z 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        Since 2002, SPEC has allowed for triple-quoted
88        strings as extended comments.  Few, if any, have used them.
89        Assume all extended comments contain ReST formatted comments,
90        *including initial section titles or transitions*.
91        The first and simplest thing to do is to read the .mac file and only extract
92        all the extended comments and add them as nodes to the current document.
93       
94        An additional step would be to parse for:
95        * def
96        * cdef
97        * rdef
98        * global    (done)
99        * local    (done)
100        * constant    (done)
101        * array
102        * ...
103       
104    '''
105
106    # consider using:  docutils.statemachine here
107    states = (                  # assume SPEC def macros cannot be nested
108        'global',               # the level that provides the SPEC command prompt
109        'extended comment',     # inside a multiline extended comment
110        'def macro',            # inside a multiline def macro definition
111        'rdef macro',           # inside a multiline rdef macro definition
112        'cdef macro',           # inside a multiline cdef macro definition
113    )
114
115    def __init__(self, macrofile):
116        '''
117        Constructor
118        '''
119        self.buf = None
120        self.findings = []
121        self.filename = None
122        self.read(macrofile)
123        self.parse_macro_file()
124   
125    def read(self, filename):
126        """
127        load the SPEC macro source code file into an internal buffer
128       
129        :param str filename: name (with optional path) of SPEC macro file
130            (The path is relative to the ``.rst`` document.)
131        """
132        if not os.path.exists(filename):
133            raise RuntimeError, "file not found: " + filename
134        self.filename = filename
135        self.buf = open(filename, 'r').read()
136   
137    def parse_macro_file(self):
138        """
139        parse the internal buffer
140        """
141        line_number = 0
142        state = 'global'
143        state_stack = []
144        for line in self.buf.split('\n'):
145            if state not in self.states:
146                raise RuntimeError, "unexpected parser state: " + state
147            line_number += 1
148            if state == 'global':
149
150                m = self._match(lgc_variable_sig_re, line)
151                if m is not None:           # local, global, or constant variable declaration
152                    objtype, args = lgc_variable_sig_re.match(line).groups()
153                    pos = args.find('#')
154                    if pos > -1:
155                        args = args[:pos]
156                    m['objtype'] = objtype
157                    m['start_line'] = m['end_line'] = line_number
158                    del m['start'], m['end'], m['line']
159                    if objtype == 'constant':
160                        var, _ = args.split()
161                        m['text'] = var
162                        self.findings.append(dict(m))
163                    else:
164                        # TODO: consider not indexing "global" inside a def
165                        # TODO: consider not indexing "local" at global level
166                        for var in args.split():
167                            m['text'] = var
168                            self.findings.append(dict(m))
169                            # TODO: to what is this local?  (remember the def it belongs to)
170                    continue
171
172                # test if one-line extended comment
173                m = self._match(extended_comment_block_sig_re, line)
174                if m is not None:
175                    del m['start'], m['end'], m['line']
176                    m['objtype'] = 'extended comment'
177                    m['start_line'] = m['end_line'] = line_number
178                    m['text'] = m['text'].strip()
179                    self.findings.append(dict(m))
180                    continue
181               
182                # test if start of multiline extended comment
183                m = self._match(extended_comment_start_sig_re, line)
184                if m is not None:
185                    text = m['line'][m['end']:]
186                    del m['start'], m['end'], m['line']
187                    m['objtype'] = 'extended comment'
188                    m['start_line'] = line_number
189                    ec = dict(m)    # container for extended comment data
190                    ec['text'] = [text]
191                    state_stack.append(state)
192                    state = 'extended comment'
193                    continue
194
195            elif state == 'extended comment':
196                # test if end of multiline extended comment
197                m = self._match(extended_comment_end_sig_re, line)
198                if m is not None:
199                    text = m['line'][:m['start']]
200                    ec['text'].append(text)
201                    ec['text'] = '\n'.join(ec['text'])
202                    ec['end_line'] = line_number
203                    self.findings.append(dict(ec))
204                    state = state_stack.pop()
205                    del ec
206                else:
207                    # multiline extended comment continues
208                    ec['text'].append(line)
209                continue
210   
211    def _match(self, regexp, line):
212        m = regexp.search(line)
213        if m is None:
214            return None
215        d = {
216            'start': m.start(1),
217            'end':   m.end(1),
218            'text':  m.group(1),
219            'line':  line,
220            'filename':  self.filename,
221        }
222        return d
223
224    def __str__(self):
225        s = []
226        for r in self.findings:
227            s.append( '' )
228            t = '%s %s %d %d %s' % ('.. ' + '*'*20, 
229                                    r['objtype'], 
230                                    r['start_line'], 
231                                    r['end_line'], 
232                                    '*'*20)
233            s.append( t )
234            s.append( '' )
235            s.append( r['text'] )
236        return '\n'.join(s)
237
238    def ReST(self):
239        """create the ReStructured Text from what has been found"""
240        s = []
241        for r in self.findings:
242            if r['objtype'] == 'extended comment':
243                s.append( '' )
244                s.append( '.. %s %s %d %d' % (self.filename, 
245                                              r['objtype'], 
246                                              r['start_line'], 
247                                              r['end_line']) )
248                s.append( '' )
249                s.append(r['text'])
250            # TODO: other objtypes
251        return '\n'.join(s)
252
253
254if __name__ == '__main__':
255    p = SpecMacrofileParser('../test/test-battery.mac')
256    #print p.ReST()
257    print p
258    p = SpecMacrofileParser('../test/cdef-examples.mac')
259    #print p.ReST()
Note: See TracBrowser for help on using the repository browser.