source: specdomain/src/specdomain/test/parser.py @ 957

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

refs #8, now parses global, local, and constant declarations, need to parse macro definitions next

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