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.
11 from string
import Template
14 from util
import build_utils
16 # List of C++ types that are compatible with the Java code generated by this
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']
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
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
42 return self
.class_name_override
or self
.original_enum_name
46 self
._AssignEntryIndices
()
50 assert self
.class_name
51 assert self
.enum_package
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
))
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()):
61 for key
, value
in self
.entries
.iteritems():
63 self
.entries
[key
] = prev_enum_value
+ 1
64 elif value
in self
.entries
:
65 self
.entries
[key
] = self
.entries
[value
]
68 self
.entries
[key
] = int(value
)
70 raise Exception('Could not interpret integer from enum value "%s" '
71 'for key %s.' % (value
, key
))
72 prev_enum_value
= self
.entries
[key
]
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()]):
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)
91 entries
[stripped_key
] = stripped_value
93 self
.entries
= entries
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'
100 known_keys
= [class_name_override_key
, enum_package_key
, prefix_to_strip_key
]
103 self
._directives
= {}
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
112 return len(self
._directives
) == 0
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
)
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]*)\)$')
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*$')
144 def __init__(self
, lines
, path
=None):
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
153 def _ApplyGeneratorDirectives(self
):
154 self
._generator
_directives
.UpdateDefinition(self
._current
_definition
)
155 self
._generator
_directives
= DirectiveSet()
157 def ParseDefinitions(self
):
158 for line
in self
._lines
:
159 self
._ParseLine
(line
)
160 return self
._enum
_definitions
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
)
168 self
._ParseEnumLine
(line
)
170 def _ParseEnumLine(self
, line
):
171 if HeaderParser
.single_line_comment_re
.match(line
):
173 if HeaderParser
.multi_line_comment_start_re
.match(line
):
174 raise Exception('Multi-line comments in enums are not supported in ' +
176 enum_end
= HeaderParser
.enum_end_re
.match(line
)
177 enum_entry
= HeaderParser
.enum_line_re
.match(line
)
179 self
._ApplyGeneratorDirectives
()
180 self
._current
_definition
.Finalize()
181 self
._enum
_definitions
.append(self
._current
_definition
)
182 self
._in
_enum
= False
184 enum_key
= enum_entry
.groups()[0]
185 enum_value
= enum_entry
.groups()[2]
186 self
._current
_definition
.AppendEntry(enum_key
, enum_value
)
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
))
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
)
204 raise Exception('Malformed multi-line directive declaration in ' +
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
))
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
])
222 if self
._generator
_directives
.empty
:
224 self
._current
_definition
= EnumDefinition(
225 original_enum_name
=enum_start
.groups()[1],
226 fixed_type
=enum_start
.groups()[3])
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
:])
235 def DoGenerate(output_dir
, source_paths
, print_output_only
=False):
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"?' %
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
)
255 def DoParseHeaderFile(path
):
256 with
open(path
) as f
:
257 return HeaderParser(f
.readlines(), path
).ParseDefinitions()
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.
266 // This file is autogenerated by
273 public class ${CLASS_NAME} {
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():
285 enum_entries_string
.append(enum_template
.substitute(values
))
286 enum_entries_string
= '\n'.join(enum_entries_string
)
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
,
295 return template
.substitute(values
)
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
))
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
))
312 usage
= 'usage: %prog [options] output_dir input_file(s)...'
313 parser
= optparse
.OptionParser(usage
=usage
)
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 '
319 parser
.add_option('--print_output_only', help='Only print output paths.',
321 parser
.add_option('--verbose', help='Print more information.',
324 options
, args
= parser
.parse_args(argv
)
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
)
330 if options
.assert_files_list
:
331 AssertFilesList(output_paths
, options
.assert_files_list
)
334 print 'Output paths:'
335 print '\n'.join(output_paths
)
337 return ' '.join(output_paths
)
339 if __name__
== '__main__':