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      m = re.compile(r'^\s*\}( .*)?;').search(raw_line)
90      if m:
91        enclosing_classes = enclosing_classes[0:len(enclosing_classes) - 1]
92        continue
93
94      continue
95
96    # Is this the end of the current enum?
97    m = _ENUM_END_RE.search(raw_line)
98    if m:
99      if not in_enum:
100        Confused(filename, line_number, raw_line)
101      in_enum = False
102      continue
103
104    if is_enum_private:
105      continue
106
107    # The only useful thing in comments is the <<alternate text>> syntax for
108    # overriding the default enum value names. Pull that out...
109    enum_text = None
110    m_comment = re.compile(r'// <<(.*?)>>').search(raw_line)
111    if m_comment:
112      enum_text = m_comment.group(1)
113    # ...and then strip // comments.
114    line = re.sub(r'//.*', '', raw_line)
115
116    # Strip whitespace.
117    line = line.strip()
118
119    # Skip blank lines.
120    if len(line) == 0:
121      continue
122
123    # Since we know we're in an enum type, and we're not looking at a comment
124    # or a blank line, this line should be the next enum value...
125    m = _ENUM_VALUE_RE.search(line)
126    if not m:
127      Confused(filename, line_number, raw_line)
128    enum_value = m.group(1)
129
130    # By default, we turn "kSomeValue" into "SomeValue".
131    if enum_text == None:
132      enum_text = enum_value
133      if enum_text.startswith('k'):
134        enum_text = enum_text[1:]
135
136    # Lose literal values because we don't care; turn "= 123, // blah" into ", // blah".
137    rest = m.group(2).strip()
138    m_literal = re.compile(r'= (0x[0-9a-f]+|-?[0-9]+|\'.\')').search(rest)
139    if m_literal:
140      rest = rest[(len(m_literal.group(0))):]
141
142    # With "kSomeValue = kOtherValue," we take the original and skip later synonyms.
143    # TODO: check that the rhs is actually an existing value.
144    if rest.startswith('= k'):
145      continue
146
147    # Remove any trailing comma and whitespace
148    if rest.startswith(','):
149      rest = rest[1:]
150    rest = rest.strip()
151
152    # There shouldn't be anything left.
153    if len(rest):
154      sys.stderr.write('%s\n' % (rest))
155      Confused(filename, line_number, raw_line)
156
157    # If the enum is scoped, we must prefix enum value with enum name (which is already prefixed
158    # by enclosing classes).
159    if is_enum_class:
160      enum_value = enum_name + '::' + enum_value
161    else:
162      if len(enclosing_classes) > 0:
163        enum_value = '::'.join(enclosing_classes) + '::' + enum_value
164
165    _ENUMS[enum_name].append((enum_value, enum_text))
166
167def main():
168  local_path = sys.argv[1]
169  header_files = []
170  for header_file in sys.argv[2:]:
171    header_files.append(header_file)
172    ProcessFile(header_file)
173
174  print('#include <iostream>')
175  print('')
176
177  for header_file in header_files:
178    header_file = header_file.replace(local_path + '/', '')
179    print('#include "%s"' % header_file)
180
181  print('')
182
183  for enum_name in _ENUMS:
184    print('// This was automatically generated by %s --- do not edit!' % sys.argv[0])
185
186    namespaces = _NAMESPACES[enum_name].split('::')
187    for namespace in namespaces:
188      print('namespace %s {' % namespace)
189
190    print('std::ostream& operator<<(std::ostream& os, const %s& rhs) {' % enum_name)
191    print('  switch (rhs) {')
192    for (enum_value, enum_text) in _ENUMS[enum_name]:
193      print('    case %s: os << "%s"; break;' % (enum_value, enum_text))
194    if not _ENUM_CLASSES[enum_name]:
195      print('    default: os << "%s[" << static_cast<int>(rhs) << "]"; break;' % enum_name)
196    print('  }')
197    print('  return os;')
198    print('}')
199
200    for namespace in reversed(namespaces):
201      print('}  // namespace %s' % namespace)
202    print('')
203
204  sys.exit(0)
205
206
207if __name__ == '__main__':
208  main()
209