1#!/usr/bin/env 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 19import codecs 20import os 21import re 22import string 23import sys 24 25 26_ENUM_START_RE = re.compile(r'\benum\b\s+(class\s+)?(\S+)\s+:?.*\{(\s+// private)?') 27_ENUM_VALUE_RE = re.compile(r'([A-Za-z0-9_]+)(.*)') 28_ENUM_END_RE = re.compile(r'^\s*\};$') 29_ENUMS = {} 30_NAMESPACES = {} 31_ENUM_CLASSES = {} 32 33def Confused(filename, line_number, line): 34 sys.stderr.write('%s:%d: confused by:\n%s\n' % (filename, line_number, line)) 35 raise Exception("giving up!") 36 sys.exit(1) 37 38 39def ProcessFile(filename): 40 lines = codecs.open(filename, 'r', 'utf8', 'replace').read().split('\n') 41 in_enum = False 42 is_enum_private = False 43 is_enum_class = False 44 line_number = 0 45 46 47 namespaces = [] 48 enclosing_classes = [] 49 50 for raw_line in lines: 51 line_number += 1 52 53 if not in_enum: 54 # Is this the start of a new enum? 55 m = _ENUM_START_RE.search(raw_line) 56 if m: 57 # Yes, so add an empty entry to _ENUMS for this enum. 58 59 # Except when it's private 60 if m.group(3) is not None: 61 is_enum_private = True 62 else: 63 is_enum_private = False 64 is_enum_class = m.group(1) is not None 65 enum_name = m.group(2) 66 if len(enclosing_classes) > 0: 67 enum_name = '::'.join(enclosing_classes) + '::' + enum_name 68 _ENUMS[enum_name] = [] 69 _NAMESPACES[enum_name] = '::'.join(namespaces) 70 _ENUM_CLASSES[enum_name] = is_enum_class 71 in_enum = True 72 continue 73 74 # Is this the start or end of a namespace? 75 m = re.compile(r'^namespace (\S+) \{').search(raw_line) 76 if m: 77 namespaces.append(m.group(1)) 78 continue 79 m = re.compile(r'^\}\s+// namespace').search(raw_line) 80 if m: 81 namespaces = namespaces[0:len(namespaces) - 1] 82 continue 83 84 # Is this the start or end of an enclosing class or struct? 85 m = re.compile(r'^\s*(?:class|struct)(?: MANAGED)?(?: PACKED\([0-9]\))? (\S+).* \{').search(raw_line) 86 if m: 87 enclosing_classes.append(m.group(1)) 88 continue 89 90 # End of class/struct -- be careful not to match "do { ... } while" constructs by accident 91 m = re.compile(r'^\s*\}(\s+)?(while)?(.+)?;').search(raw_line) 92 if m and not m.group(2): 93 enclosing_classes = enclosing_classes[0:len(enclosing_classes) - 1] 94 continue 95 96 continue 97 98 # Is this the end of the current enum? 99 m = _ENUM_END_RE.search(raw_line) 100 if m: 101 if not in_enum: 102 Confused(filename, line_number, raw_line) 103 in_enum = False 104 continue 105 106 if is_enum_private: 107 continue 108 109 # The only useful thing in comments is the <<alternate text>> syntax for 110 # overriding the default enum value names. Pull that out... 111 enum_text = None 112 m_comment = re.compile(r'// <<(.*?)>>').search(raw_line) 113 if m_comment: 114 enum_text = m_comment.group(1) 115 # ...and then strip // comments. 116 line = re.sub(r'//.*', '', raw_line) 117 118 # Strip whitespace. 119 line = line.strip() 120 121 # Skip blank lines. 122 if len(line) == 0: 123 continue 124 125 # Since we know we're in an enum type, and we're not looking at a comment 126 # or a blank line, this line should be the next enum value... 127 m = _ENUM_VALUE_RE.search(line) 128 if not m: 129 Confused(filename, line_number, raw_line) 130 enum_value = m.group(1) 131 132 # By default, we turn "kSomeValue" into "SomeValue". 133 if enum_text == None: 134 enum_text = enum_value 135 if enum_text.startswith('k'): 136 enum_text = enum_text[1:] 137 138 # Lose literal values because we don't care; turn "= 123, // blah" into ", // blah". 139 rest = m.group(2).strip() 140 m_literal = re.compile(r'= (0x[0-9a-f]+|-?[0-9]+|\'.\')').search(rest) 141 if m_literal: 142 rest = rest[(len(m_literal.group(0))):] 143 144 # With "kSomeValue = kOtherValue," we take the original and skip later synonyms. 145 # TODO: check that the rhs is actually an existing value. 146 if rest.startswith('= k'): 147 continue 148 149 # Remove any trailing comma and whitespace 150 if rest.startswith(','): 151 rest = rest[1:] 152 rest = rest.strip() 153 154 # There shouldn't be anything left. 155 if len(rest): 156 sys.stderr.write('%s\n' % (rest)) 157 Confused(filename, line_number, raw_line) 158 159 # If the enum is scoped, we must prefix enum value with enum name (which is already prefixed 160 # by enclosing classes). 161 if is_enum_class: 162 enum_value = enum_name + '::' + enum_value 163 else: 164 if len(enclosing_classes) > 0: 165 enum_value = '::'.join(enclosing_classes) + '::' + enum_value 166 167 _ENUMS[enum_name].append((enum_value, enum_text)) 168 169def main(): 170 local_path = sys.argv[1] 171 header_files = [] 172 for header_file in sys.argv[2:]: 173 header_files.append(header_file) 174 ProcessFile(header_file) 175 176 print('#include <iostream>') 177 print('') 178 179 for header_file in header_files: 180 header_file = header_file.replace(local_path + '/', '') 181 print('#include "%s"' % header_file) 182 183 print('') 184 185 for enum_name in _ENUMS: 186 print('// This was automatically generated by %s --- do not edit!' % sys.argv[0]) 187 188 namespaces = _NAMESPACES[enum_name].split('::') 189 for namespace in namespaces: 190 print('namespace %s {' % namespace) 191 192 print('std::ostream& operator<<(std::ostream& os, const %s& rhs) {' % enum_name) 193 print(' switch (rhs) {') 194 for (enum_value, enum_text) in _ENUMS[enum_name]: 195 print(' case %s: os << "%s"; break;' % (enum_value, enum_text)) 196 if not _ENUM_CLASSES[enum_name]: 197 print(' default: os << "%s[" << static_cast<int>(rhs) << "]"; break;' % enum_name) 198 print(' }') 199 print(' return os;') 200 print('}') 201 202 for namespace in reversed(namespaces): 203 print('} // namespace %s' % namespace) 204 print('') 205 206 sys.exit(0) 207 208 209if __name__ == '__main__': 210 main() 211