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