#!/usr/bin/env python3 # # Copyright (C) 2012 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Generates default implementations of operator<< for enum types.""" import codecs import re import sys _ENUM_START_RE = re.compile( r'\benum\b\s+(class\s+)?(\S+)\s+:?.*\{(\s+// private)?') _ENUM_VALUE_RE = re.compile(r'([A-Za-z0-9_]+)(.*)') _ENUM_END_RE = re.compile(r'^\s*\};$') _ENUMS = {} _NAMESPACES = {} _ENUM_CLASSES = {} def Confused(filename, line_number, line): sys.stderr.write('%s:%d: confused by:\n%s\n' % (filename, line_number, line)) raise Exception("giving up!") sys.exit(1) def ProcessFile(filename): lines = codecs.open(filename, 'r', 'utf8', 'replace').read().split('\n') class EnumLines: def __init__(self, ns, ec): self.namespaces = ns self.enclosing_classes = ec self.lines = [] def generate_enum_lines(l): line_number = 0 enum_lines = None namespaces = [] enclosing_classes = [] for raw_line in l: line_number += 1 if enum_lines is None: # Is this the start of a new enum? m = _ENUM_START_RE.search(raw_line) if m: # Yes, so create new line list. enum_lines = EnumLines(namespaces[:], enclosing_classes[:]) enum_lines.lines.append((raw_line, line_number)) continue # Is this the start or end of a namespace? m = re.search(r'^namespace (\S+) \{', raw_line) if m: namespaces.append(m.group(1)) continue m = re.search(r'^\}\s+// namespace', raw_line) if m: namespaces = namespaces[0:len(namespaces) - 1] continue # Is this the start or end of an enclosing class or struct? m = re.search( r'^\s*(?:class|struct)(?: MANAGED)?(?: PACKED\([0-9]\))? (\S+).* \{', raw_line) if m: enclosing_classes.append(m.group(1)) continue # End of class/struct -- be careful not to match "do { ... } while" constructs by accident m = re.search(r'^\s*\}(\s+)?(while)?(.+)?;', raw_line) if m and not m.group(2): enclosing_classes = enclosing_classes[0:len(enclosing_classes) - 1] continue continue # Is this the end of the current enum? m = _ENUM_END_RE.search(raw_line) if m: if enum_lines is None: Confused(filename, line_number, raw_line) yield enum_lines enum_lines = None continue # Append the line enum_lines.lines.append((raw_line, line_number)) for enum_lines in generate_enum_lines(lines): m = _ENUM_START_RE.search(enum_lines.lines[0][0]) if m.group(3) is not None: # Skip private enums. continue # Add an empty entry to _ENUMS for this enum. is_enum_class = m.group(1) is not None enum_name = m.group(2) if len(enum_lines.enclosing_classes) > 0: enum_name = '::'.join(enum_lines.enclosing_classes) + '::' + enum_name _ENUMS[enum_name] = [] _NAMESPACES[enum_name] = '::'.join(enum_lines.namespaces) _ENUM_CLASSES[enum_name] = is_enum_class def generate_non_empty_line(lines): for raw_line, line_number in lines: # Strip // comments. line = re.sub(r'//.*', '', raw_line) # Strip whitespace. line = line.strip() # Skip blank lines. if len(line) == 0: continue # The only useful thing in comments is the <> syntax for # overriding the default enum value names. Pull that out... enum_text = None m_comment = re.search(r'// <<(.*?)>>', raw_line) if m_comment: enum_text = m_comment.group(1) yield (line, enum_text, raw_line, line_number) for line, enum_text, raw_line, line_number in generate_non_empty_line(enum_lines.lines[1:]): # Since we know we're in an enum type, and we're not looking at a comment # or a blank line, this line should be the next enum value... m = _ENUM_VALUE_RE.search(line) if not m: Confused(filename, line_number, raw_line) enum_value = m.group(1) # By default, we turn "kSomeValue" into "SomeValue". if enum_text is None: enum_text = enum_value if enum_text.startswith('k'): enum_text = enum_text[1:] # Check that we understand the line (and hopefully do not parse incorrectly), or should # filter. rest = m.group(2).strip() # With "kSomeValue = kOtherValue," we take the original and skip later synonyms. # TODO: check that the rhs is actually an existing value. if rest.startswith('= k'): continue # Remove trailing comma. if rest.endswith(','): rest = rest[:-1] # We now expect rest to be empty, or an assignment to an "expression." if len(rest): # We want to lose the expression "= [exp]". As we do not have a real C parser, just # assume anything without a comma is valid. m_exp = re.match('= [^,]+$', rest) if m_exp is None: sys.stderr.write('%s\n' % (rest)) Confused(filename, line_number, raw_line) # If the enum is scoped, we must prefix enum value with enum name (which is already prefixed # by enclosing classes). if is_enum_class: enum_value = enum_name + '::' + enum_value else: if len(enum_lines.enclosing_classes) > 0: enum_value = '::'.join(enum_lines.enclosing_classes) + '::' + enum_value _ENUMS[enum_name].append((enum_value, enum_text)) def main(): local_path = sys.argv[1] header_files = [] for header_file in sys.argv[2:]: header_files.append(header_file) ProcessFile(header_file) print('#include ') print('') for header_file in header_files: header_file = header_file.replace(local_path + '/', '') print('#include "%s"' % header_file) print('') for enum_name in _ENUMS: print('// This was automatically generated by art/tools/generate_operator_out.py --- do not edit!') namespaces = _NAMESPACES[enum_name].split('::') for namespace in namespaces: print('namespace %s {' % namespace) print( 'std::ostream& operator<<(std::ostream& os, %s rhs) {' % enum_name) print(' switch (rhs) {') for (enum_value, enum_text) in _ENUMS[enum_name]: print(' case %s: os << "%s"; break;' % (enum_value, enum_text)) if not _ENUM_CLASSES[enum_name]: print( ' default: os << "%s[" << static_cast(rhs) << "]"; break;' % enum_name) print(' }') print(' return os;') print('}') for namespace in reversed(namespaces): print('} // namespace %s' % namespace) print('') sys.exit(0) if __name__ == '__main__': main()