OLD | NEW |
| (Empty) |
1 #!/usr/bin/env python | |
2 # | |
3 # Copyright 2014 The Chromium Authors. All rights reserved. | |
4 # Use of this source code is governed by a BSD-style license that can be | |
5 # found in the LICENSE file. | |
6 | |
7 import collections | |
8 import re | |
9 import optparse | |
10 import os | |
11 from string import Template | |
12 import sys | |
13 | |
14 from util import build_utils | |
15 | |
16 # List of C++ types that are compatible with the Java code generated by this | |
17 # script. | |
18 # | |
19 # This script can parse .idl files however, at present it ignores special | |
20 # rules such as [cpp_enum_prefix_override="ax_attr"]. | |
21 ENUM_FIXED_TYPE_WHITELIST = ['char', 'unsigned char', | |
22 'short', 'unsigned short', | |
23 'int', 'int8_t', 'int16_t', 'int32_t', 'uint8_t', 'uint16_t'] | |
24 | |
25 class EnumDefinition(object): | |
26 def __init__(self, original_enum_name=None, class_name_override=None, | |
27 enum_package=None, entries=None, fixed_type=None): | |
28 self.original_enum_name = original_enum_name | |
29 self.class_name_override = class_name_override | |
30 self.enum_package = enum_package | |
31 self.entries = collections.OrderedDict(entries or []) | |
32 self.prefix_to_strip = None | |
33 self.fixed_type = fixed_type | |
34 | |
35 def AppendEntry(self, key, value): | |
36 if key in self.entries: | |
37 raise Exception('Multiple definitions of key %s found.' % key) | |
38 self.entries[key] = value | |
39 | |
40 @property | |
41 def class_name(self): | |
42 return self.class_name_override or self.original_enum_name | |
43 | |
44 def Finalize(self): | |
45 self._Validate() | |
46 self._AssignEntryIndices() | |
47 self._StripPrefix() | |
48 | |
49 def _Validate(self): | |
50 assert self.class_name | |
51 assert self.enum_package | |
52 assert self.entries | |
53 if self.fixed_type and self.fixed_type not in ENUM_FIXED_TYPE_WHITELIST: | |
54 raise Exception('Fixed type %s for enum %s not whitelisted.' % | |
55 (self.fixed_type, self.class_name)) | |
56 | |
57 def _AssignEntryIndices(self): | |
58 # Enums, if given no value, are given the value of the previous enum + 1. | |
59 if not all(self.entries.values()): | |
60 prev_enum_value = -1 | |
61 for key, value in self.entries.iteritems(): | |
62 if not value: | |
63 self.entries[key] = prev_enum_value + 1 | |
64 elif value in self.entries: | |
65 self.entries[key] = self.entries[value] | |
66 else: | |
67 try: | |
68 self.entries[key] = int(value) | |
69 except ValueError: | |
70 raise Exception('Could not interpret integer from enum value "%s" ' | |
71 'for key %s.' % (value, key)) | |
72 prev_enum_value = self.entries[key] | |
73 | |
74 | |
75 def _StripPrefix(self): | |
76 prefix_to_strip = self.prefix_to_strip | |
77 if not prefix_to_strip: | |
78 prefix_to_strip = self.original_enum_name | |
79 prefix_to_strip = re.sub('(?!^)([A-Z]+)', r'_\1', prefix_to_strip).upper() | |
80 prefix_to_strip += '_' | |
81 if not all([w.startswith(prefix_to_strip) for w in self.entries.keys()]): | |
82 prefix_to_strip = '' | |
83 | |
84 entries = collections.OrderedDict() | |
85 for (k, v) in self.entries.iteritems(): | |
86 stripped_key = k.replace(prefix_to_strip, '', 1) | |
87 if isinstance(v, basestring): | |
88 stripped_value = v.replace(prefix_to_strip, '', 1) | |
89 else: | |
90 stripped_value = v | |
91 entries[stripped_key] = stripped_value | |
92 | |
93 self.entries = entries | |
94 | |
95 class DirectiveSet(object): | |
96 class_name_override_key = 'CLASS_NAME_OVERRIDE' | |
97 enum_package_key = 'ENUM_PACKAGE' | |
98 prefix_to_strip_key = 'PREFIX_TO_STRIP' | |
99 | |
100 known_keys = [class_name_override_key, enum_package_key, prefix_to_strip_key] | |
101 | |
102 def __init__(self): | |
103 self._directives = {} | |
104 | |
105 def Update(self, key, value): | |
106 if key not in DirectiveSet.known_keys: | |
107 raise Exception("Unknown directive: " + key) | |
108 self._directives[key] = value | |
109 | |
110 @property | |
111 def empty(self): | |
112 return len(self._directives) == 0 | |
113 | |
114 def UpdateDefinition(self, definition): | |
115 definition.class_name_override = self._directives.get( | |
116 DirectiveSet.class_name_override_key, '') | |
117 definition.enum_package = self._directives.get( | |
118 DirectiveSet.enum_package_key) | |
119 definition.prefix_to_strip = self._directives.get( | |
120 DirectiveSet.prefix_to_strip_key) | |
121 | |
122 | |
123 class HeaderParser(object): | |
124 single_line_comment_re = re.compile(r'\s*//') | |
125 multi_line_comment_start_re = re.compile(r'\s*/\*') | |
126 enum_line_re = re.compile(r'^\s*(\w+)(\s*\=\s*([^,\n]+))?,?') | |
127 enum_end_re = re.compile(r'^\s*}\s*;\.*$') | |
128 generator_directive_re = re.compile( | |
129 r'^\s*//\s+GENERATED_JAVA_(\w+)\s*:\s*([\.\w]+)$') | |
130 multi_line_generator_directive_start_re = re.compile( | |
131 r'^\s*//\s+GENERATED_JAVA_(\w+)\s*:\s*\(([\.\w]*)$') | |
132 multi_line_directive_continuation_re = re.compile( | |
133 r'^\s*//\s+([\.\w]+)$') | |
134 multi_line_directive_end_re = re.compile( | |
135 r'^\s*//\s+([\.\w]*)\)$') | |
136 | |
137 optional_class_or_struct_re = r'(class|struct)?' | |
138 enum_name_re = r'(\w+)' | |
139 optional_fixed_type_re = r'(\:\s*(\w+\s*\w+?))?' | |
140 enum_start_re = re.compile(r'^\s*(?:\[cpp.*\])?\s*enum\s+' + | |
141 optional_class_or_struct_re + '\s*' + enum_name_re + '\s*' + | |
142 optional_fixed_type_re + '\s*{\s*$') | |
143 | |
144 def __init__(self, lines, path=None): | |
145 self._lines = lines | |
146 self._path = path | |
147 self._enum_definitions = [] | |
148 self._in_enum = False | |
149 self._current_definition = None | |
150 self._generator_directives = DirectiveSet() | |
151 self._multi_line_generator_directive = None | |
152 | |
153 def _ApplyGeneratorDirectives(self): | |
154 self._generator_directives.UpdateDefinition(self._current_definition) | |
155 self._generator_directives = DirectiveSet() | |
156 | |
157 def ParseDefinitions(self): | |
158 for line in self._lines: | |
159 self._ParseLine(line) | |
160 return self._enum_definitions | |
161 | |
162 def _ParseLine(self, line): | |
163 if self._multi_line_generator_directive: | |
164 self._ParseMultiLineDirectiveLine(line) | |
165 elif not self._in_enum: | |
166 self._ParseRegularLine(line) | |
167 else: | |
168 self._ParseEnumLine(line) | |
169 | |
170 def _ParseEnumLine(self, line): | |
171 if HeaderParser.single_line_comment_re.match(line): | |
172 return | |
173 if HeaderParser.multi_line_comment_start_re.match(line): | |
174 raise Exception('Multi-line comments in enums are not supported in ' + | |
175 self._path) | |
176 enum_end = HeaderParser.enum_end_re.match(line) | |
177 enum_entry = HeaderParser.enum_line_re.match(line) | |
178 if enum_end: | |
179 self._ApplyGeneratorDirectives() | |
180 self._current_definition.Finalize() | |
181 self._enum_definitions.append(self._current_definition) | |
182 self._in_enum = False | |
183 elif enum_entry: | |
184 enum_key = enum_entry.groups()[0] | |
185 enum_value = enum_entry.groups()[2] | |
186 self._current_definition.AppendEntry(enum_key, enum_value) | |
187 | |
188 def _ParseMultiLineDirectiveLine(self, line): | |
189 multi_line_directive_continuation = ( | |
190 HeaderParser.multi_line_directive_continuation_re.match(line)) | |
191 multi_line_directive_end = ( | |
192 HeaderParser.multi_line_directive_end_re.match(line)) | |
193 | |
194 if multi_line_directive_continuation: | |
195 value_cont = multi_line_directive_continuation.groups()[0] | |
196 self._multi_line_generator_directive[1].append(value_cont) | |
197 elif multi_line_directive_end: | |
198 directive_name = self._multi_line_generator_directive[0] | |
199 directive_value = "".join(self._multi_line_generator_directive[1]) | |
200 directive_value += multi_line_directive_end.groups()[0] | |
201 self._multi_line_generator_directive = None | |
202 self._generator_directives.Update(directive_name, directive_value) | |
203 else: | |
204 raise Exception('Malformed multi-line directive declaration in ' + | |
205 self._path) | |
206 | |
207 def _ParseRegularLine(self, line): | |
208 enum_start = HeaderParser.enum_start_re.match(line) | |
209 generator_directive = HeaderParser.generator_directive_re.match(line) | |
210 multi_line_generator_directive_start = ( | |
211 HeaderParser.multi_line_generator_directive_start_re.match(line)) | |
212 | |
213 if generator_directive: | |
214 directive_name = generator_directive.groups()[0] | |
215 directive_value = generator_directive.groups()[1] | |
216 self._generator_directives.Update(directive_name, directive_value) | |
217 elif multi_line_generator_directive_start: | |
218 directive_name = multi_line_generator_directive_start.groups()[0] | |
219 directive_value = multi_line_generator_directive_start.groups()[1] | |
220 self._multi_line_generator_directive = (directive_name, [directive_value]) | |
221 elif enum_start: | |
222 if self._generator_directives.empty: | |
223 return | |
224 self._current_definition = EnumDefinition( | |
225 original_enum_name=enum_start.groups()[1], | |
226 fixed_type=enum_start.groups()[3]) | |
227 self._in_enum = True | |
228 | |
229 def GetScriptName(): | |
230 script_components = os.path.abspath(sys.argv[0]).split(os.path.sep) | |
231 build_index = script_components.index('build') | |
232 return os.sep.join(script_components[build_index:]) | |
233 | |
234 | |
235 def DoGenerate(output_dir, source_paths, print_output_only=False): | |
236 output_paths = [] | |
237 for source_path in source_paths: | |
238 enum_definitions = DoParseHeaderFile(source_path) | |
239 if not enum_definitions: | |
240 raise Exception('No enums found in %s\n' | |
241 'Did you forget prefixing enums with ' | |
242 '"// GENERATED_JAVA_ENUM_PACKAGE: foo"?' % | |
243 source_path) | |
244 for enum_definition in enum_definitions: | |
245 package_path = enum_definition.enum_package.replace('.', os.path.sep) | |
246 file_name = enum_definition.class_name + '.java' | |
247 output_path = os.path.join(output_dir, package_path, file_name) | |
248 output_paths.append(output_path) | |
249 if not print_output_only: | |
250 build_utils.MakeDirectory(os.path.dirname(output_path)) | |
251 DoWriteOutput(source_path, output_path, enum_definition) | |
252 return output_paths | |
253 | |
254 | |
255 def DoParseHeaderFile(path): | |
256 with open(path) as f: | |
257 return HeaderParser(f.readlines(), path).ParseDefinitions() | |
258 | |
259 | |
260 def GenerateOutput(source_path, enum_definition): | |
261 template = Template(""" | |
262 // Copyright 2014 The Chromium Authors. All rights reserved. | |
263 // Use of this source code is governed by a BSD-style license that can be | |
264 // found in the LICENSE file. | |
265 | |
266 // This file is autogenerated by | |
267 // ${SCRIPT_NAME} | |
268 // From | |
269 // ${SOURCE_PATH} | |
270 | |
271 package ${PACKAGE}; | |
272 | |
273 public class ${CLASS_NAME} { | |
274 ${ENUM_ENTRIES} | |
275 } | |
276 """) | |
277 | |
278 enum_template = Template(' public static final int ${NAME} = ${VALUE};') | |
279 enum_entries_string = [] | |
280 for enum_name, enum_value in enum_definition.entries.iteritems(): | |
281 values = { | |
282 'NAME': enum_name, | |
283 'VALUE': enum_value, | |
284 } | |
285 enum_entries_string.append(enum_template.substitute(values)) | |
286 enum_entries_string = '\n'.join(enum_entries_string) | |
287 | |
288 values = { | |
289 'CLASS_NAME': enum_definition.class_name, | |
290 'ENUM_ENTRIES': enum_entries_string, | |
291 'PACKAGE': enum_definition.enum_package, | |
292 'SCRIPT_NAME': GetScriptName(), | |
293 'SOURCE_PATH': source_path, | |
294 } | |
295 return template.substitute(values) | |
296 | |
297 | |
298 def DoWriteOutput(source_path, output_path, enum_definition): | |
299 with open(output_path, 'w') as out_file: | |
300 out_file.write(GenerateOutput(source_path, enum_definition)) | |
301 | |
302 def AssertFilesList(output_paths, assert_files_list): | |
303 actual = set(output_paths) | |
304 expected = set(assert_files_list) | |
305 if not actual == expected: | |
306 need_to_add = list(actual - expected) | |
307 need_to_remove = list(expected - actual) | |
308 raise Exception('Output files list does not match expectations. Please ' | |
309 'add %s and remove %s.' % (need_to_add, need_to_remove)) | |
310 | |
311 def DoMain(argv): | |
312 usage = 'usage: %prog [options] output_dir input_file(s)...' | |
313 parser = optparse.OptionParser(usage=usage) | |
314 | |
315 parser.add_option('--assert_file', action="append", default=[], | |
316 dest="assert_files_list", help='Assert that the given ' | |
317 'file is an output. There can be multiple occurrences of ' | |
318 'this flag.') | |
319 parser.add_option('--print_output_only', help='Only print output paths.', | |
320 action='store_true') | |
321 parser.add_option('--verbose', help='Print more information.', | |
322 action='store_true') | |
323 | |
324 options, args = parser.parse_args(argv) | |
325 if len(args) < 2: | |
326 parser.error('Need to specify output directory and at least one input file') | |
327 output_paths = DoGenerate(args[0], args[1:], | |
328 print_output_only=options.print_output_only) | |
329 | |
330 if options.assert_files_list: | |
331 AssertFilesList(output_paths, options.assert_files_list) | |
332 | |
333 if options.verbose: | |
334 print 'Output paths:' | |
335 print '\n'.join(output_paths) | |
336 | |
337 return ' '.join(output_paths) | |
338 | |
339 if __name__ == '__main__': | |
340 DoMain(sys.argv[1:]) | |
OLD | NEW |