| # Copyright (c) 2022 Arm Limited |
| # All rights reserved. |
| # |
| # The license below extends only to copyright in the software and shall |
| # not be construed as granting a license to any other intellectual |
| # property including but not limited to intellectual property relating |
| # to a hardware implementation of the functionality of the software |
| # licensed hereunder. You may use the software subject to the license |
| # terms below provided that you ensure that this notice is replicated |
| # unmodified and in its entirety in all distributions of the software, |
| # modified or unmodified, in source code or in binary form. |
| # |
| # Copyright (c) 2006-2009 Nathan Binkert <nate@binkert.org> |
| # 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. |
| |
| try: |
| import builtins |
| except ImportError: |
| # Python 2 fallback |
| import __builtin__ as builtins |
| import inspect |
| import os |
| import re |
| |
| |
| 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 self.formatter.locals and item in self.frame.f_locals: |
| return self.frame.f_locals[item] |
| |
| if item in self.dict: |
| return self.dict[item] |
| |
| if self.formatter.globals and item in self.frame.f_globals: |
| return self.frame.f_globals[item] |
| |
| if item in builtins.__dict__: |
| return builtins.__dict__[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<escaped>%(delim)s) | # escaped delimiter |
| ^(?P<indent>[ ]*)%(delim)s(?P<lone>%(ident)s)$ | # lone identifier |
| %(delim)s(?P<ident>%(ident)s) | # identifier |
| %(delim)s%(lb)s(?P<b_ident>%(ident)s)%(rb)s | # braced identifier |
| %(delim)s(?P<pos>%(pos)s) | # positional parameter |
| %(delim)s%(lb)s(?P<b_pos>%(pos)s)%(rb)s | # braced positional |
| %(delim)s%(ldb)s(?P<eval>.*?)%(rdb)s | # double braced expression |
| %(delim)s(?P<invalid>) # 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, count=1): |
| self._indent_level += self._indent_spaces * count |
| |
| def dedent(self, count=1): |
| assert self._indent_level >= (self._indent_spaces * count) |
| self._indent_level -= self._indent_spaces * count |
| |
| 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 = open(os.path.join(*args), "w") |
| name, extension = os.path.splitext(f.name) |
| |
| # Add a comment to inform which file generated the generated file |
| # to make it easier to backtrack and modify generated code |
| frame = inspect.currentframe().f_back |
| if re.match(r"^\.(cc|hh|c|h)$", extension) is not None: |
| f.write( |
| f"""/** |
| * DO NOT EDIT THIS FILE! |
| * File automatically generated by |
| * {frame.f_code.co_filename}:{frame.f_lineno} |
| */ |
| |
| """ |
| ) |
| elif re.match(r"^\.py$", extension) is not None: |
| f.write( |
| f"""# |
| # DO NOT EDIT THIS FILE! |
| # File automatically generated by |
| # {frame.f_code.co_filename}:{frame.f_lineno} |
| # |
| |
| """ |
| ) |
| elif re.match(r"^\.html$", extension) is not None: |
| f.write( |
| f"""<!-- |
| DO NOT EDIT THIS FILE! |
| File automatically generated by |
| {frame.f_code.co_filename}:{frame.f_lineno} |
| --> |
| |
| """ |
| ) |
| |
| for data in self._data: |
| f.write(data) |
| f.close() |
| |
| def __str__(self): |
| data = "".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 __call__(self, *args, **kwargs): |
| if not args: |
| self._data.append("\n") |
| return |
| |
| format = args[0] |
| args = args[1:] |
| |
| 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 - sum(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 range(4))}}") |
| f("${x}") |
| f("$x") |
| f.indent() |
| for i in range(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, end=" ") |