From a6b39c07d9d03955382a0e930b363c6e6fd4b942 Mon Sep 17 00:00:00 2001 From: Nathan Binkert Date: Sun, 16 Aug 2009 13:40:03 -0700 Subject: [PATCH] code_formatter: Add a python class for writing code generator templates --- src/python/SConscript | 1 + src/python/m5/util/__init__.py | 1 + src/python/m5/util/code_formatter.py | 311 +++++++++++++++++++++++++++ 3 files changed, 313 insertions(+) create mode 100644 src/python/m5/util/code_formatter.py diff --git a/src/python/SConscript b/src/python/SConscript index c36f03695..bb892f376 100644 --- a/src/python/SConscript +++ b/src/python/SConscript @@ -53,6 +53,7 @@ PySource('m5', 'm5/ticks.py') PySource('m5', 'm5/trace.py') PySource('m5.util', 'm5/util/__init__.py') PySource('m5.util', 'm5/util/attrdict.py') +PySource('m5.util', 'm5/util/code_formatter.py') PySource('m5.util', 'm5/util/grammar.py') PySource('m5.util', 'm5/util/jobfile.py') PySource('m5.util', 'm5/util/misc.py') diff --git a/src/python/m5/util/__init__.py b/src/python/m5/util/__init__.py index 48e8111bd..3930c8b6f 100644 --- a/src/python/m5/util/__init__.py +++ b/src/python/m5/util/__init__.py @@ -27,6 +27,7 @@ # Authors: Nathan Binkert from attrdict import attrdict, optiondict +from code_formatter import code_formatter from misc import * from multidict import multidict from orderdict import orderdict diff --git a/src/python/m5/util/code_formatter.py b/src/python/m5/util/code_formatter.py new file mode 100644 index 000000000..919a6423b --- /dev/null +++ b/src/python/m5/util/code_formatter.py @@ -0,0 +1,311 @@ +# Copyright (c) 2006-2009 Nathan Binkert +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer; +# redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution; +# neither the name of the copyright holders nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import inspect +import os +import re +import string + +class lookup(object): + def __init__(self, formatter, frame, *args, **kwargs): + self.frame = frame + self.formatter = formatter + self.dict = self.formatter._dict + self.args = args + self.kwargs = kwargs + self.locals = {} + + def __setitem__(self, item, val): + self.locals[item] = val + + def __getitem__(self, item): + if item in self.locals: + return self.locals[item] + + if item in self.kwargs: + return self.kwargs[item] + + if item == '__file__': + return self.frame.f_code.co_filename + + if item == '__line__': + return self.frame.f_lineno + + if item in self.dict: + return self.dict[item] + + if self.formatter.locals or self.formatter.globals: + if self.formatter.locals and item in self.frame.f_locals: + return self.frame.f_locals[item] + + if self.formatter.globals and item in self.frame.f_globals: + return self.frame.f_globals[item] + + if item in __builtins__: + return __builtins__[item] + + try: + item = int(item) + return self.args[item] + except ValueError: + pass + raise IndexError, "Could not find '%s'" % item + +class code_formatter_meta(type): + pattern = r""" + (?: + %(delim)s(?P%(delim)s) | # escaped delimiter + ^(?P[ ]*)%(delim)s(?P%(ident)s)$ | # lone identifier + %(delim)s(?P%(ident)s) | # identifier + %(delim)s%(lb)s(?P%(ident)s)%(rb)s | # braced identifier + %(delim)s(?P%(pos)s) | # positional parameter + %(delim)s%(lb)s(?P%(pos)s)%(rb)s | # braced positional + %(delim)s%(ldb)s(?P.*?)%(rdb)s | # double braced expression + %(delim)s(?P) # ill-formed delimiter exprs + ) + """ + def __init__(cls, name, bases, dct): + super(code_formatter_meta, cls).__init__(name, bases, dct) + if 'pattern' in dct: + pat = cls.pattern + else: + # tuple expansion to ensure strings are proper length + lb,rb = cls.braced + lb1,lb2,rb2,rb1 = cls.double_braced + pat = code_formatter_meta.pattern % { + 'delim' : re.escape(cls.delim), + 'ident' : cls.ident, + 'pos' : cls.pos, + 'lb' : re.escape(lb), + 'rb' : re.escape(rb), + 'ldb' : re.escape(lb1+lb2), + 'rdb' : re.escape(rb2+rb1), + } + cls.pattern = re.compile(pat, re.VERBOSE | re.DOTALL | re.MULTILINE) + +class code_formatter(object): + __metaclass__ = code_formatter_meta + + delim = r'$' + ident = r'[_A-z]\w*' + pos = r'[0-9]+' + braced = r'{}' + double_braced = r'{{}}' + + globals = True + locals = True + fix_newlines = True + def __init__(self, *args, **kwargs): + self._data = [] + self._dict = {} + self._indent_level = 0 + self._indent_spaces = 4 + self.globals = kwargs.pop('globals',type(self).globals) + self.locals = kwargs.pop('locals', type(self).locals) + self._fix_newlines = \ + kwargs.pop('fix_newlines', type(self).fix_newlines) + + if args: + self.__call__(args) + + def indent(self): + self._indent_level += self._indent_spaces + + def dedent(self): + assert self._indent_level >= self._indent_spaces + self._indent_level -= self._indent_spaces + + def fix(self, status): + previous = self._fix_newlines + self._fix_newlines = status + return previous + + def nofix(self): + previous = self._fix_newlines + self._fix_newlines = False + return previous + + def clear(): + self._data = [] + + def write(self, *args): + f = file(os.path.join(*args), "w") + for data in self._data: + f.write(data) + f.close() + + def __str__(self): + data = string.join(self._data, '') + self._data = [ data ] + return data + + def __getitem__(self, item): + return self._dict[item] + + def __setitem__(self, item, value): + self._dict[item] = value + + def __delitem__(self, item): + del self._dict[item] + + def __contains__(self, item): + return item in self._dict + + def __iadd__(self, data): + self.append(data) + + def append(self, data): + if isinstance(data, code_formatter): + self._data.extend(data._data) + else: + self._append(str(data)) + + def _append(self, data): + if not self._fix_newlines: + self._data.append(data) + return + + initial_newline = not self._data or self._data[-1] == '\n' + for line in data.splitlines(): + if line: + if self._indent_level: + self._data.append(' ' * self._indent_level) + self._data.append(line) + + if line or not initial_newline: + self._data.append('\n') + + initial_newline = False + + def insert_newline(self): + self._data.append('\n') + + def __call__(self, format, *args, **kwargs): + frame = inspect.currentframe().f_back + + l = lookup(self, frame, *args, **kwargs) + def convert(match): + ident = match.group('lone') + # check for a lone identifier + if ident: + indent = match.group('indent') # must be spaces + lone = '%s' % (l[ident], ) + + def indent_lines(gen): + for line in gen: + yield indent + yield line + return ''.join(indent_lines(lone.splitlines(True))) + + # check for an identifier, braced or not + ident = match.group('ident') or match.group('b_ident') + if ident is not None: + return '%s' % (l[ident], ) + + # check for a positional parameter, braced or not + pos = match.group('pos') or match.group('b_pos') + if pos is not None: + pos = int(pos) + if pos > len(args): + raise ValueError \ + ('Positional parameter #%d not found in pattern' % pos, + code_formatter.pattern) + return '%s' % (args[int(pos)], ) + + # check for a double braced expression + eval_expr = match.group('eval') + if eval_expr is not None: + result = eval(eval_expr, {}, l) + return '%s' % (result, ) + + # check for an escaped delimiter + if match.group('escaped') is not None: + return '$' + + # At this point, we have to match invalid + if match.group('invalid') is None: + # didn't match invalid! + raise ValueError('Unrecognized named group in pattern', + code_formatter.pattern) + + i = match.start('invalid') + if i == 0: + colno = 1 + lineno = 1 + else: + lines = format[:i].splitlines(True) + colno = i - reduce(lambda x,y: x+y, (len(z) for z in lines)) + lineno = len(lines) + + raise ValueError('Invalid format string: line %d, col %d' % + (lineno, colno)) + + d = code_formatter.pattern.sub(convert, format) + self._append(d) + +__all__ = [ "code_formatter" ] + +if __name__ == '__main__': + from code_formatter import code_formatter + f = code_formatter() + + class Foo(dict): + def __init__(self, **kwargs): + self.update(kwargs) + def __getattr__(self, attr): + return self[attr] + + x = "this is a test" + l = [ [Foo(x=[Foo(y=9)])] ] + + y = code_formatter() + y(''' +{ + this_is_a_test(); +} +''') + f(' $y') + f('''$__file__:$__line__ +{''') + f("${{', '.join(str(x) for x in xrange(4))}}") + f('${x}') + f('$x') + f.indent() + for i in xrange(5): + f('$x') + f('$i') + f('$0', "zero") + f('$1 $0', "zero", "one") + f('${0}', "he went") + f('${0}asdf', "he went") + f.dedent() + + f(''' + ${{l[0][0]["x"][0].y}} +} +''', 1, 9) + + print f, -- 2.30.2