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
18 ENUM_FIXED_TYPE_WHITELIST
= ['char', 'unsigned char',
19 'short', 'unsigned short',
20 'int', 'int8_t', 'int16_t', 'int32_t', 'uint8_t', 'uint16_t']
22 class EnumDefinition(object):
23 def __init__(self
, original_enum_name
=None, class_name_override
=None,
24 enum_package
=None, entries
=None, fixed_type
=None):
25 self
.original_enum_name
= original_enum_name
26 self
.class_name_override
= class_name_override
27 self
.enum_package
= enum_package
28 self
.entries
= collections
.OrderedDict(entries
or [])
29 self
.prefix_to_strip
= None
30 self
.fixed_type
= fixed_type
32 def AppendEntry(self
, key
, value
):
33 if key
in self
.entries
:
34 raise Exception('Multiple definitions of key %s found.' % key
)
35 self
.entries
[key
] = value
39 return self
.class_name_override
or self
.original_enum_name
43 self
._AssignEntryIndices
()
47 assert self
.class_name
48 assert self
.enum_package
50 if self
.fixed_type
and self
.fixed_type
not in ENUM_FIXED_TYPE_WHITELIST
:
51 raise Exception('Fixed type %s for enum %s not whitelisted.' %
52 (self
.fixed_type
, self
.class_name
))
54 def _AssignEntryIndices(self
):
55 # Enums, if given no value, are given the value of the previous enum + 1.
56 if not all(self
.entries
.values()):
58 for key
, value
in self
.entries
.iteritems():
60 self
.entries
[key
] = prev_enum_value
+ 1
61 elif value
in self
.entries
:
62 self
.entries
[key
] = self
.entries
[value
]
65 self
.entries
[key
] = int(value
)
67 raise Exception('Could not interpret integer from enum value "%s" '
68 'for key %s.' % (value
, key
))
69 prev_enum_value
= self
.entries
[key
]
72 def _StripPrefix(self
):
73 prefix_to_strip
= self
.prefix_to_strip
74 if not prefix_to_strip
:
75 prefix_to_strip
= self
.original_enum_name
76 prefix_to_strip
= re
.sub('(?!^)([A-Z]+)', r
'_\1', prefix_to_strip
).upper()
77 prefix_to_strip
+= '_'
78 if not all([w
.startswith(prefix_to_strip
) for w
in self
.entries
.keys()]):
81 entries
= collections
.OrderedDict()
82 for (k
, v
) in self
.entries
.iteritems():
83 stripped_key
= k
.replace(prefix_to_strip
, '', 1)
84 if isinstance(v
, basestring
):
85 stripped_value
= v
.replace(prefix_to_strip
, '', 1)
88 entries
[stripped_key
] = stripped_value
90 self
.entries
= entries
92 class DirectiveSet(object):
93 class_name_override_key
= 'CLASS_NAME_OVERRIDE'
94 enum_package_key
= 'ENUM_PACKAGE'
95 prefix_to_strip_key
= 'PREFIX_TO_STRIP'
97 known_keys
= [class_name_override_key
, enum_package_key
, prefix_to_strip_key
]
100 self
._directives
= {}
102 def Update(self
, key
, value
):
103 if key
not in DirectiveSet
.known_keys
:
104 raise Exception("Unknown directive: " + key
)
105 self
._directives
[key
] = value
109 return len(self
._directives
) == 0
111 def UpdateDefinition(self
, definition
):
112 definition
.class_name_override
= self
._directives
.get(
113 DirectiveSet
.class_name_override_key
, '')
114 definition
.enum_package
= self
._directives
.get(
115 DirectiveSet
.enum_package_key
)
116 definition
.prefix_to_strip
= self
._directives
.get(
117 DirectiveSet
.prefix_to_strip_key
)
120 class HeaderParser(object):
121 single_line_comment_re
= re
.compile(r
'\s*//')
122 multi_line_comment_start_re
= re
.compile(r
'\s*/\*')
123 enum_line_re
= re
.compile(r
'^\s*(\w+)(\s*\=\s*([^,\n]+))?,?')
124 enum_end_re
= re
.compile(r
'^\s*}\s*;\.*$')
125 generator_directive_re
= re
.compile(
126 r
'^\s*//\s+GENERATED_JAVA_(\w+)\s*:\s*([\.\w]+)$')
127 multi_line_generator_directive_start_re
= re
.compile(
128 r
'^\s*//\s+GENERATED_JAVA_(\w+)\s*:\s*\(([\.\w]*)$')
129 multi_line_directive_continuation_re
= re
.compile(
130 r
'^\s*//\s+([\.\w]+)$')
131 multi_line_directive_end_re
= re
.compile(
132 r
'^\s*//\s+([\.\w]*)\)$')
134 optional_class_or_struct_re
= r
'(class|struct)?'
135 enum_name_re
= r
'(\w+)'
136 optional_fixed_type_re
= r
'(\:\s*(\w+\s*\w+?))?'
137 enum_start_re
= re
.compile(r
'^\s*enum\s+' + optional_class_or_struct_re
+
138 '\s*' + enum_name_re
+ '\s*' + optional_fixed_type_re
+ '\s*{\s*$')
140 def __init__(self
, lines
, path
=None):
143 self
._enum
_definitions
= []
144 self
._in
_enum
= False
145 self
._current
_definition
= None
146 self
._generator
_directives
= DirectiveSet()
147 self
._multi
_line
_generator
_directive
= None
149 def _ApplyGeneratorDirectives(self
):
150 self
._generator
_directives
.UpdateDefinition(self
._current
_definition
)
151 self
._generator
_directives
= DirectiveSet()
153 def ParseDefinitions(self
):
154 for line
in self
._lines
:
155 self
._ParseLine
(line
)
156 return self
._enum
_definitions
158 def _ParseLine(self
, line
):
159 if self
._multi
_line
_generator
_directive
:
160 self
._ParseMultiLineDirectiveLine
(line
)
161 elif not self
._in
_enum
:
162 self
._ParseRegularLine
(line
)
164 self
._ParseEnumLine
(line
)
166 def _ParseEnumLine(self
, line
):
167 if HeaderParser
.single_line_comment_re
.match(line
):
169 if HeaderParser
.multi_line_comment_start_re
.match(line
):
170 raise Exception('Multi-line comments in enums are not supported in ' +
172 enum_end
= HeaderParser
.enum_end_re
.match(line
)
173 enum_entry
= HeaderParser
.enum_line_re
.match(line
)
175 self
._ApplyGeneratorDirectives
()
176 self
._current
_definition
.Finalize()
177 self
._enum
_definitions
.append(self
._current
_definition
)
178 self
._in
_enum
= False
180 enum_key
= enum_entry
.groups()[0]
181 enum_value
= enum_entry
.groups()[2]
182 self
._current
_definition
.AppendEntry(enum_key
, enum_value
)
184 def _ParseMultiLineDirectiveLine(self
, line
):
185 multi_line_directive_continuation
= (
186 HeaderParser
.multi_line_directive_continuation_re
.match(line
))
187 multi_line_directive_end
= (
188 HeaderParser
.multi_line_directive_end_re
.match(line
))
190 if multi_line_directive_continuation
:
191 value_cont
= multi_line_directive_continuation
.groups()[0]
192 self
._multi
_line
_generator
_directive
[1].append(value_cont
)
193 elif multi_line_directive_end
:
194 directive_name
= self
._multi
_line
_generator
_directive
[0]
195 directive_value
= "".join(self
._multi
_line
_generator
_directive
[1])
196 directive_value
+= multi_line_directive_end
.groups()[0]
197 self
._multi
_line
_generator
_directive
= None
198 self
._generator
_directives
.Update(directive_name
, directive_value
)
200 raise Exception('Malformed multi-line directive declaration in ' +
203 def _ParseRegularLine(self
, line
):
204 enum_start
= HeaderParser
.enum_start_re
.match(line
)
205 generator_directive
= HeaderParser
.generator_directive_re
.match(line
)
206 multi_line_generator_directive_start
= (
207 HeaderParser
.multi_line_generator_directive_start_re
.match(line
))
209 if generator_directive
:
210 directive_name
= generator_directive
.groups()[0]
211 directive_value
= generator_directive
.groups()[1]
212 self
._generator
_directives
.Update(directive_name
, directive_value
)
213 elif multi_line_generator_directive_start
:
214 directive_name
= multi_line_generator_directive_start
.groups()[0]
215 directive_value
= multi_line_generator_directive_start
.groups()[1]
216 self
._multi
_line
_generator
_directive
= (directive_name
, [directive_value
])
218 if self
._generator
_directives
.empty
:
220 self
._current
_definition
= EnumDefinition(
221 original_enum_name
=enum_start
.groups()[1],
222 fixed_type
=enum_start
.groups()[3])
226 script_components
= os
.path
.abspath(sys
.argv
[0]).split(os
.path
.sep
)
227 build_index
= script_components
.index('build')
228 return os
.sep
.join(script_components
[build_index
:])
231 def DoGenerate(output_dir
, source_paths
, print_output_only
=False):
233 for source_path
in source_paths
:
234 enum_definitions
= DoParseHeaderFile(source_path
)
235 if not enum_definitions
:
236 raise Exception('No enums found in %s\n'
237 'Did you forget prefixing enums with '
238 '"// GENERATED_JAVA_ENUM_PACKAGE: foo"?' %
240 for enum_definition
in enum_definitions
:
241 package_path
= enum_definition
.enum_package
.replace('.', os
.path
.sep
)
242 file_name
= enum_definition
.class_name
+ '.java'
243 output_path
= os
.path
.join(output_dir
, package_path
, file_name
)
244 output_paths
.append(output_path
)
245 if not print_output_only
:
246 build_utils
.MakeDirectory(os
.path
.dirname(output_path
))
247 DoWriteOutput(source_path
, output_path
, enum_definition
)
251 def DoParseHeaderFile(path
):
252 with
open(path
) as f
:
253 return HeaderParser(f
.readlines(), path
).ParseDefinitions()
256 def GenerateOutput(source_path
, enum_definition
):
257 template
= Template("""
258 // Copyright 2014 The Chromium Authors. All rights reserved.
259 // Use of this source code is governed by a BSD-style license that can be
260 // found in the LICENSE file.
262 // This file is autogenerated by
269 public class ${CLASS_NAME} {
274 enum_template
= Template(' public static final int ${NAME} = ${VALUE};')
275 enum_entries_string
= []
276 for enum_name
, enum_value
in enum_definition
.entries
.iteritems():
281 enum_entries_string
.append(enum_template
.substitute(values
))
282 enum_entries_string
= '\n'.join(enum_entries_string
)
285 'CLASS_NAME': enum_definition
.class_name
,
286 'ENUM_ENTRIES': enum_entries_string
,
287 'PACKAGE': enum_definition
.enum_package
,
288 'SCRIPT_NAME': GetScriptName(),
289 'SOURCE_PATH': source_path
,
291 return template
.substitute(values
)
294 def DoWriteOutput(source_path
, output_path
, enum_definition
):
295 with
open(output_path
, 'w') as out_file
:
296 out_file
.write(GenerateOutput(source_path
, enum_definition
))
298 def AssertFilesList(output_paths
, assert_files_list
):
299 actual
= set(output_paths
)
300 expected
= set(assert_files_list
)
301 if not actual
== expected
:
302 need_to_add
= list(actual
- expected
)
303 need_to_remove
= list(expected
- actual
)
304 raise Exception('Output files list does not match expectations. Please '
305 'add %s and remove %s.' % (need_to_add
, need_to_remove
))
308 usage
= 'usage: %prog [options] output_dir input_file(s)...'
309 parser
= optparse
.OptionParser(usage
=usage
)
311 parser
.add_option('--assert_file', action
="append", default
=[],
312 dest
="assert_files_list", help='Assert that the given '
313 'file is an output. There can be multiple occurrences of '
315 parser
.add_option('--print_output_only', help='Only print output paths.',
317 parser
.add_option('--verbose', help='Print more information.',
320 options
, args
= parser
.parse_args(argv
)
322 parser
.error('Need to specify output directory and at least one input file')
323 output_paths
= DoGenerate(args
[0], args
[1:],
324 print_output_only
=options
.print_output_only
)
326 if options
.assert_files_list
:
327 AssertFilesList(output_paths
, options
.assert_files_list
)
330 print 'Output paths:'
331 print '\n'.join(output_paths
)
333 return ' '.join(output_paths
)
335 if __name__
== '__main__':