code_formatter.py revision 6502
16502Snate@binkert.org# Copyright (c) 2006-2009 Nathan Binkert <nate@binkert.org>
26502Snate@binkert.org# All rights reserved.
36502Snate@binkert.org#
46502Snate@binkert.org# Redistribution and use in source and binary forms, with or without
56502Snate@binkert.org# modification, are permitted provided that the following conditions are
66502Snate@binkert.org# met: redistributions of source code must retain the above copyright
76502Snate@binkert.org# notice, this list of conditions and the following disclaimer;
86502Snate@binkert.org# redistributions in binary form must reproduce the above copyright
96502Snate@binkert.org# notice, this list of conditions and the following disclaimer in the
106502Snate@binkert.org# documentation and/or other materials provided with the distribution;
116502Snate@binkert.org# neither the name of the copyright holders nor the names of its
126502Snate@binkert.org# contributors may be used to endorse or promote products derived from
136502Snate@binkert.org# this software without specific prior written permission.
146502Snate@binkert.org#
156502Snate@binkert.org# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
166502Snate@binkert.org# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
176502Snate@binkert.org# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
186502Snate@binkert.org# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
196502Snate@binkert.org# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
206502Snate@binkert.org# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
216502Snate@binkert.org# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
226502Snate@binkert.org# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
236502Snate@binkert.org# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
246502Snate@binkert.org# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
256502Snate@binkert.org# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
266502Snate@binkert.org
276502Snate@binkert.orgimport inspect
286502Snate@binkert.orgimport os
296502Snate@binkert.orgimport re
306502Snate@binkert.orgimport string
316502Snate@binkert.org
326502Snate@binkert.orgclass lookup(object):
336502Snate@binkert.org    def __init__(self, formatter, frame, *args, **kwargs):
346502Snate@binkert.org        self.frame = frame
356502Snate@binkert.org        self.formatter = formatter
366502Snate@binkert.org        self.dict = self.formatter._dict
376502Snate@binkert.org        self.args = args
386502Snate@binkert.org        self.kwargs = kwargs
396502Snate@binkert.org        self.locals = {}
406502Snate@binkert.org
416502Snate@binkert.org    def __setitem__(self, item, val):
426502Snate@binkert.org        self.locals[item] = val
436502Snate@binkert.org
446502Snate@binkert.org    def __getitem__(self, item):
456502Snate@binkert.org        if item in self.locals:
466502Snate@binkert.org            return self.locals[item]
476502Snate@binkert.org
486502Snate@binkert.org        if item in self.kwargs:
496502Snate@binkert.org            return self.kwargs[item]
506502Snate@binkert.org
516502Snate@binkert.org        if item == '__file__':
526502Snate@binkert.org            return self.frame.f_code.co_filename
536502Snate@binkert.org
546502Snate@binkert.org        if item == '__line__':
556502Snate@binkert.org            return self.frame.f_lineno
566502Snate@binkert.org
576502Snate@binkert.org        if item in self.dict:
586502Snate@binkert.org            return self.dict[item]
596502Snate@binkert.org
606502Snate@binkert.org        if self.formatter.locals or self.formatter.globals:
616502Snate@binkert.org            if self.formatter.locals and item in self.frame.f_locals:
626502Snate@binkert.org                return self.frame.f_locals[item]
636502Snate@binkert.org
646502Snate@binkert.org            if self.formatter.globals and item in self.frame.f_globals:
656502Snate@binkert.org                return self.frame.f_globals[item]
666502Snate@binkert.org
676502Snate@binkert.org        if item in __builtins__:
686502Snate@binkert.org            return __builtins__[item]
696502Snate@binkert.org
706502Snate@binkert.org        try:
716502Snate@binkert.org            item = int(item)
726502Snate@binkert.org            return self.args[item]
736502Snate@binkert.org        except ValueError:
746502Snate@binkert.org            pass
756502Snate@binkert.org        raise IndexError, "Could not find '%s'" % item
766502Snate@binkert.org
776502Snate@binkert.orgclass code_formatter_meta(type):
786502Snate@binkert.org    pattern = r"""
796502Snate@binkert.org    (?:
806502Snate@binkert.org      %(delim)s(?P<escaped>%(delim)s)              | # escaped delimiter
816502Snate@binkert.org      ^(?P<indent>[ ]*)%(delim)s(?P<lone>%(ident)s)$ | # lone identifier
826502Snate@binkert.org      %(delim)s(?P<ident>%(ident)s)                | # identifier
836502Snate@binkert.org      %(delim)s%(lb)s(?P<b_ident>%(ident)s)%(rb)s  | # braced identifier
846502Snate@binkert.org      %(delim)s(?P<pos>%(pos)s)                    | # positional parameter
856502Snate@binkert.org      %(delim)s%(lb)s(?P<b_pos>%(pos)s)%(rb)s      | # braced positional
866502Snate@binkert.org      %(delim)s%(ldb)s(?P<eval>.*?)%(rdb)s         | # double braced expression
876502Snate@binkert.org      %(delim)s(?P<invalid>)                       # ill-formed delimiter exprs
886502Snate@binkert.org    )
896502Snate@binkert.org    """
906502Snate@binkert.org    def __init__(cls, name, bases, dct):
916502Snate@binkert.org        super(code_formatter_meta, cls).__init__(name, bases, dct)
926502Snate@binkert.org        if 'pattern' in dct:
936502Snate@binkert.org            pat = cls.pattern
946502Snate@binkert.org        else:
956502Snate@binkert.org            # tuple expansion to ensure strings are proper length
966502Snate@binkert.org            lb,rb = cls.braced
976502Snate@binkert.org            lb1,lb2,rb2,rb1 = cls.double_braced
986502Snate@binkert.org            pat = code_formatter_meta.pattern % {
996502Snate@binkert.org                'delim' : re.escape(cls.delim),
1006502Snate@binkert.org                'ident' : cls.ident,
1016502Snate@binkert.org                'pos' : cls.pos,
1026502Snate@binkert.org                'lb' : re.escape(lb),
1036502Snate@binkert.org                'rb' : re.escape(rb),
1046502Snate@binkert.org                'ldb' : re.escape(lb1+lb2),
1056502Snate@binkert.org                'rdb' : re.escape(rb2+rb1),
1066502Snate@binkert.org                }
1076502Snate@binkert.org        cls.pattern = re.compile(pat, re.VERBOSE | re.DOTALL | re.MULTILINE)
1086502Snate@binkert.org
1096502Snate@binkert.orgclass code_formatter(object):
1106502Snate@binkert.org    __metaclass__ = code_formatter_meta
1116502Snate@binkert.org
1126502Snate@binkert.org    delim = r'$'
1136502Snate@binkert.org    ident = r'[_A-z]\w*'
1146502Snate@binkert.org    pos = r'[0-9]+'
1156502Snate@binkert.org    braced = r'{}'
1166502Snate@binkert.org    double_braced = r'{{}}'
1176502Snate@binkert.org
1186502Snate@binkert.org    globals = True
1196502Snate@binkert.org    locals = True
1206502Snate@binkert.org    fix_newlines = True
1216502Snate@binkert.org    def __init__(self, *args, **kwargs):
1226502Snate@binkert.org        self._data = []
1236502Snate@binkert.org        self._dict = {}
1246502Snate@binkert.org        self._indent_level = 0
1256502Snate@binkert.org        self._indent_spaces = 4
1266502Snate@binkert.org        self.globals = kwargs.pop('globals',type(self).globals)
1276502Snate@binkert.org        self.locals = kwargs.pop('locals', type(self).locals)
1286502Snate@binkert.org        self._fix_newlines = \
1296502Snate@binkert.org                kwargs.pop('fix_newlines', type(self).fix_newlines)
1306502Snate@binkert.org
1316502Snate@binkert.org        if args:
1326502Snate@binkert.org            self.__call__(args)
1336502Snate@binkert.org
1346502Snate@binkert.org    def indent(self):
1356502Snate@binkert.org        self._indent_level += self._indent_spaces
1366502Snate@binkert.org
1376502Snate@binkert.org    def dedent(self):
1386502Snate@binkert.org        assert self._indent_level >= self._indent_spaces
1396502Snate@binkert.org        self._indent_level -= self._indent_spaces
1406502Snate@binkert.org
1416502Snate@binkert.org    def fix(self, status):
1426502Snate@binkert.org        previous = self._fix_newlines
1436502Snate@binkert.org        self._fix_newlines = status
1446502Snate@binkert.org        return previous
1456502Snate@binkert.org
1466502Snate@binkert.org    def nofix(self):
1476502Snate@binkert.org        previous = self._fix_newlines
1486502Snate@binkert.org        self._fix_newlines = False
1496502Snate@binkert.org        return previous
1506502Snate@binkert.org
1516502Snate@binkert.org    def clear():
1526502Snate@binkert.org        self._data = []
1536502Snate@binkert.org
1546502Snate@binkert.org    def write(self, *args):
1556502Snate@binkert.org        f = file(os.path.join(*args), "w")
1566502Snate@binkert.org        for data in self._data:
1576502Snate@binkert.org            f.write(data)
1586502Snate@binkert.org        f.close()
1596502Snate@binkert.org
1606502Snate@binkert.org    def __str__(self):
1616502Snate@binkert.org        data = string.join(self._data, '')
1626502Snate@binkert.org        self._data = [ data ]
1636502Snate@binkert.org        return data
1646502Snate@binkert.org
1656502Snate@binkert.org    def __getitem__(self, item):
1666502Snate@binkert.org        return self._dict[item]
1676502Snate@binkert.org
1686502Snate@binkert.org    def __setitem__(self, item, value):
1696502Snate@binkert.org        self._dict[item] = value
1706502Snate@binkert.org
1716502Snate@binkert.org    def __delitem__(self, item):
1726502Snate@binkert.org        del self._dict[item]
1736502Snate@binkert.org
1746502Snate@binkert.org    def __contains__(self, item):
1756502Snate@binkert.org        return item in self._dict
1766502Snate@binkert.org
1776502Snate@binkert.org    def __iadd__(self, data):
1786502Snate@binkert.org        self.append(data)
1796502Snate@binkert.org
1806502Snate@binkert.org    def append(self, data):
1816502Snate@binkert.org        if isinstance(data, code_formatter):
1826502Snate@binkert.org            self._data.extend(data._data)
1836502Snate@binkert.org        else:
1846502Snate@binkert.org            self._append(str(data))
1856502Snate@binkert.org
1866502Snate@binkert.org    def _append(self, data):
1876502Snate@binkert.org        if not self._fix_newlines:
1886502Snate@binkert.org            self._data.append(data)
1896502Snate@binkert.org            return
1906502Snate@binkert.org
1916502Snate@binkert.org        initial_newline = not self._data or self._data[-1] == '\n'
1926502Snate@binkert.org        for line in data.splitlines():
1936502Snate@binkert.org            if line:
1946502Snate@binkert.org                if self._indent_level:
1956502Snate@binkert.org                    self._data.append(' ' * self._indent_level)
1966502Snate@binkert.org                self._data.append(line)
1976502Snate@binkert.org
1986502Snate@binkert.org            if line or not initial_newline:
1996502Snate@binkert.org                self._data.append('\n')
2006502Snate@binkert.org
2016502Snate@binkert.org            initial_newline = False
2026502Snate@binkert.org
2036502Snate@binkert.org    def insert_newline(self):
2046502Snate@binkert.org        self._data.append('\n')
2056502Snate@binkert.org
2066502Snate@binkert.org    def __call__(self, format, *args, **kwargs):
2076502Snate@binkert.org        frame = inspect.currentframe().f_back
2086502Snate@binkert.org
2096502Snate@binkert.org        l = lookup(self, frame, *args, **kwargs)
2106502Snate@binkert.org        def convert(match):
2116502Snate@binkert.org            ident = match.group('lone')
2126502Snate@binkert.org            # check for a lone identifier
2136502Snate@binkert.org            if ident:
2146502Snate@binkert.org                indent = match.group('indent') # must be spaces
2156502Snate@binkert.org                lone = '%s' % (l[ident], )
2166502Snate@binkert.org
2176502Snate@binkert.org                def indent_lines(gen):
2186502Snate@binkert.org                    for line in gen:
2196502Snate@binkert.org                        yield indent
2206502Snate@binkert.org                        yield line
2216502Snate@binkert.org                return ''.join(indent_lines(lone.splitlines(True)))
2226502Snate@binkert.org
2236502Snate@binkert.org            # check for an identifier, braced or not
2246502Snate@binkert.org            ident = match.group('ident') or match.group('b_ident')
2256502Snate@binkert.org            if ident is not None:
2266502Snate@binkert.org                return '%s' % (l[ident], )
2276502Snate@binkert.org
2286502Snate@binkert.org            # check for a positional parameter, braced or not
2296502Snate@binkert.org            pos = match.group('pos') or match.group('b_pos')
2306502Snate@binkert.org            if pos is not None:
2316502Snate@binkert.org                pos = int(pos)
2326502Snate@binkert.org                if pos > len(args):
2336502Snate@binkert.org                    raise ValueError \
2346502Snate@binkert.org                        ('Positional parameter #%d not found in pattern' % pos,
2356502Snate@binkert.org                         code_formatter.pattern)
2366502Snate@binkert.org                return '%s' % (args[int(pos)], )
2376502Snate@binkert.org
2386502Snate@binkert.org            # check for a double braced expression
2396502Snate@binkert.org            eval_expr = match.group('eval')
2406502Snate@binkert.org            if eval_expr is not None:
2416502Snate@binkert.org                result = eval(eval_expr, {}, l)
2426502Snate@binkert.org                return '%s' % (result, )
2436502Snate@binkert.org
2446502Snate@binkert.org            # check for an escaped delimiter
2456502Snate@binkert.org            if match.group('escaped') is not None:
2466502Snate@binkert.org                return '$'
2476502Snate@binkert.org
2486502Snate@binkert.org            # At this point, we have to match invalid
2496502Snate@binkert.org            if match.group('invalid') is None:
2506502Snate@binkert.org                # didn't match invalid!
2516502Snate@binkert.org                raise ValueError('Unrecognized named group in pattern',
2526502Snate@binkert.org                                 code_formatter.pattern)
2536502Snate@binkert.org
2546502Snate@binkert.org            i = match.start('invalid')
2556502Snate@binkert.org            if i == 0:
2566502Snate@binkert.org                colno = 1
2576502Snate@binkert.org                lineno = 1
2586502Snate@binkert.org            else:
2596502Snate@binkert.org                lines = format[:i].splitlines(True)
2606502Snate@binkert.org                colno = i - reduce(lambda x,y: x+y, (len(z) for z in lines))
2616502Snate@binkert.org                lineno = len(lines)
2626502Snate@binkert.org
2636502Snate@binkert.org                raise ValueError('Invalid format string: line %d, col %d' %
2646502Snate@binkert.org                                 (lineno, colno))
2656502Snate@binkert.org
2666502Snate@binkert.org        d = code_formatter.pattern.sub(convert, format)
2676502Snate@binkert.org        self._append(d)
2686502Snate@binkert.org
2696502Snate@binkert.org__all__ = [ "code_formatter" ]
2706502Snate@binkert.org
2716502Snate@binkert.orgif __name__ == '__main__':
2726502Snate@binkert.org    from code_formatter import code_formatter
2736502Snate@binkert.org    f = code_formatter()
2746502Snate@binkert.org
2756502Snate@binkert.org    class Foo(dict):
2766502Snate@binkert.org        def __init__(self, **kwargs):
2776502Snate@binkert.org            self.update(kwargs)
2786502Snate@binkert.org        def __getattr__(self, attr):
2796502Snate@binkert.org            return self[attr]
2806502Snate@binkert.org
2816502Snate@binkert.org    x = "this is a test"
2826502Snate@binkert.org    l = [ [Foo(x=[Foo(y=9)])] ]
2836502Snate@binkert.org
2846502Snate@binkert.org    y = code_formatter()
2856502Snate@binkert.org    y('''
2866502Snate@binkert.org{
2876502Snate@binkert.org    this_is_a_test();
2886502Snate@binkert.org}
2896502Snate@binkert.org''')
2906502Snate@binkert.org    f('    $y')
2916502Snate@binkert.org    f('''$__file__:$__line__
2926502Snate@binkert.org{''')
2936502Snate@binkert.org    f("${{', '.join(str(x) for x in xrange(4))}}")
2946502Snate@binkert.org    f('${x}')
2956502Snate@binkert.org    f('$x')
2966502Snate@binkert.org    f.indent()
2976502Snate@binkert.org    for i in xrange(5):
2986502Snate@binkert.org        f('$x')
2996502Snate@binkert.org        f('$i')
3006502Snate@binkert.org        f('$0', "zero")
3016502Snate@binkert.org        f('$1 $0', "zero", "one")
3026502Snate@binkert.org        f('${0}', "he went")
3036502Snate@binkert.org        f('${0}asdf', "he went")
3046502Snate@binkert.org    f.dedent()
3056502Snate@binkert.org
3066502Snate@binkert.org    f('''
3076502Snate@binkert.org    ${{l[0][0]["x"][0].y}}
3086502Snate@binkert.org}
3096502Snate@binkert.org''', 1, 9)
3106502Snate@binkert.org
3116502Snate@binkert.org    print f,
312