Home | History | Annotate | Download | only in tools
      1 #!/usr/bin/python
      2 #
      3 # Copyright (C) 2012 The Android Open Source Project
      4 #
      5 # Licensed under the Apache License, Version 2.0 (the "License");
      6 # you may not use this file except in compliance with the License.
      7 # You may obtain a copy of the License at
      8 #
      9 #      http://www.apache.org/licenses/LICENSE-2.0
     10 #
     11 # Unless required by applicable law or agreed to in writing, software
     12 # distributed under the License is distributed on an "AS IS" BASIS,
     13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14 # See the License for the specific language governing permissions and
     15 # limitations under the License.
     16 
     17 """Generates default implementations of operator<< for enum types."""
     18 
     19 import codecs
     20 import os
     21 import re
     22 import string
     23 import sys
     24 
     25 
     26 _ENUM_START_RE = re.compile(r'\benum\b\s+(\S+)\s+\{')
     27 _ENUM_VALUE_RE = re.compile(r'([A-Za-z0-9_]+)(.*)')
     28 _ENUM_END_RE = re.compile(r'^\s*\};$')
     29 _ENUMS = {}
     30 _NAMESPACES = {}
     31 
     32 def Confused(filename, line_number, line):
     33   sys.stderr.write('%s:%d: confused by:\n%s\n' % (filename, line_number, line))
     34   raise Exception("giving up!")
     35   sys.exit(1)
     36 
     37 
     38 def ProcessFile(filename):
     39   lines = codecs.open(filename, 'r', 'utf8', 'replace').read().split('\n')
     40   in_enum = False
     41   line_number = 0
     42 
     43   namespaces = []
     44   enclosing_classes = []
     45 
     46   for raw_line in lines:
     47     line_number += 1
     48 
     49     if not in_enum:
     50       # Is this the start of a new enum?
     51       m = _ENUM_START_RE.search(raw_line)
     52       if m:
     53         # Yes, so add an empty entry to _ENUMS for this enum.
     54         enum_name = m.group(1)
     55         if len(enclosing_classes) > 0:
     56           enum_name = '::'.join(enclosing_classes) + '::' + enum_name
     57         _ENUMS[enum_name] = []
     58         _NAMESPACES[enum_name] = '::'.join(namespaces)
     59         in_enum = True
     60         continue
     61 
     62       # Is this the start or end of a namespace?
     63       m = re.compile(r'^namespace (\S+) \{').search(raw_line)
     64       if m:
     65         namespaces.append(m.group(1))
     66         continue
     67       m = re.compile(r'^\}\s+// namespace').search(raw_line)
     68       if m:
     69         namespaces = namespaces[0:len(namespaces) - 1]
     70         continue
     71 
     72       # Is this the start or end of an enclosing class or struct?
     73       m = re.compile(r'^(?:class|struct)(?: MANAGED)? (\S+).* \{').search(raw_line)
     74       if m:
     75         enclosing_classes.append(m.group(1))
     76         continue
     77       m = re.compile(r'^\};').search(raw_line)
     78       if m:
     79         enclosing_classes = enclosing_classes[0:len(enclosing_classes) - 1]
     80         continue
     81 
     82       continue
     83 
     84     # Is this the end of the current enum?
     85     m = _ENUM_END_RE.search(raw_line)
     86     if m:
     87       if not in_enum:
     88         Confused(filename, line_number, raw_line)
     89       in_enum = False
     90       continue
     91 
     92     # The only useful thing in comments is the <<alternate text>> syntax for
     93     # overriding the default enum value names. Pull that out...
     94     enum_text = None
     95     m_comment = re.compile(r'// <<(.*?)>>').search(raw_line)
     96     if m_comment:
     97       enum_text = m_comment.group(1)
     98     # ...and then strip // comments.
     99     line = re.sub(r'//.*', '', raw_line)
    100 
    101     # Strip whitespace.
    102     line = line.strip()
    103 
    104     # Skip blank lines.
    105     if len(line) == 0:
    106       continue
    107 
    108     # Since we know we're in an enum type, and we're not looking at a comment
    109     # or a blank line, this line should be the next enum value...
    110     m = _ENUM_VALUE_RE.search(line)
    111     if not m:
    112       Confused(filename, line_number, raw_line)
    113     enum_value = m.group(1)
    114 
    115     # By default, we turn "kSomeValue" into "SomeValue".
    116     if enum_text == None:
    117       enum_text = enum_value
    118       if enum_text.startswith('k'):
    119         enum_text = enum_text[1:]
    120 
    121     # Lose literal values because we don't care; turn "= 123, // blah" into ", // blah".
    122     rest = m.group(2).strip()
    123     m_literal = re.compile(r'= (0x[0-9a-f]+|-?[0-9]+|\'.\')').search(rest)
    124     if m_literal:
    125       rest = rest[(len(m_literal.group(0))):]
    126 
    127     # With "kSomeValue = kOtherValue," we take the original and skip later synonyms.
    128     # TODO: check that the rhs is actually an existing value.
    129     if rest.startswith('= k'):
    130       continue
    131 
    132     # Remove any trailing comma and whitespace
    133     if rest.startswith(','):
    134       rest = rest[1:]
    135     rest = rest.strip()
    136 
    137     # There shouldn't be anything left.
    138     if len(rest):
    139       Confused(filename, line_number, raw_line)
    140 
    141     if len(enclosing_classes) > 0:
    142       enum_value = '::'.join(enclosing_classes) + '::' + enum_value
    143 
    144     _ENUMS[enum_name].append((enum_value, enum_text))
    145 
    146 def main():
    147   local_path = sys.argv[1]
    148   header_files = []
    149   for header_file in sys.argv[2:]:
    150     header_files.append(header_file)
    151     ProcessFile(header_file)
    152 
    153   print '#include <iostream>'
    154   print
    155 
    156   for header_file in header_files:
    157     header_file = header_file.replace(local_path + '/', '')
    158     print '#include "%s"' % header_file
    159 
    160   print
    161 
    162   for enum_name in _ENUMS:
    163     print '// This was automatically generated by %s --- do not edit!' % sys.argv[0]
    164 
    165     namespaces = _NAMESPACES[enum_name].split('::')
    166     for namespace in namespaces:
    167       print 'namespace %s {' % namespace
    168 
    169     print 'std::ostream& operator<<(std::ostream& os, const %s& rhs) {' % enum_name
    170     print '  switch (rhs) {'
    171     for (enum_value, enum_text) in _ENUMS[enum_name]:
    172       print '    case %s: os << "%s"; break;' % (enum_value, enum_text)
    173     print '    default: os << "%s[" << static_cast<int>(rhs) << "]"; break;' % enum_name
    174     print '  }'
    175     print '  return os;'
    176     print '}'
    177 
    178     for namespace in reversed(namespaces):
    179       print '}  // namespace %s' % namespace
    180     print
    181 
    182   sys.exit(0)
    183 
    184 
    185 if __name__ == '__main__':
    186   main()
    187