| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright 2016 The Chromium Authors. All rights reserved. | 2 # Copyright 2016 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 import math | 6 import math |
| 7 import sys | 7 import sys |
| 8 | 8 |
| 9 import json5_generator | 9 import json5_generator |
| 10 import template_expander | 10 import template_expander |
| 11 import make_style_builder | 11 import make_style_builder |
| 12 | 12 |
| 13 from name_utilities import ( | 13 from name_utilities import ( |
| 14 enum_for_css_keyword, enum_type_name, enum_value_name, class_member_name, me
thod_name, | 14 enum_for_css_keyword, enum_type_name, enum_value_name, class_member_name, me
thod_name, |
| 15 join_name | 15 class_name, join_name |
| 16 ) | 16 ) |
| 17 from collections import OrderedDict | 17 from collections import defaultdict, OrderedDict |
| 18 from itertools import chain |
| 19 |
| 20 # TODO(shend): Improve documentation and add docstrings. |
| 21 |
| 22 |
| 23 def _flatten_list(x): |
| 24 """Flattens a list of lists into a single list.""" |
| 25 return list(chain.from_iterable(x)) |
| 26 |
| 27 |
| 28 def _num_32_bit_words_for_bit_fields(bit_fields): |
| 29 """Gets the number of 32 bit unsigned integers needed store a list of bit fi
elds.""" |
| 30 num_buckets, cur_bucket = 0, 0 |
| 31 for field in bit_fields: |
| 32 if field.size + cur_bucket > 32: |
| 33 num_buckets += 1 |
| 34 cur_bucket = 0 |
| 35 cur_bucket += field.size |
| 36 return num_buckets + (cur_bucket > 0) |
| 37 |
| 38 |
| 39 class Group(object): |
| 40 """Represents a group of fields stored together in a class. |
| 41 |
| 42 Attributes: |
| 43 name: The name of the group as a string. |
| 44 subgroups: List of Group instances that are stored as subgroups under th
is group. |
| 45 fields: List of Field instances stored directly under this group. |
| 46 """ |
| 47 def __init__(self, name, subgroups, fields): |
| 48 self.name = name |
| 49 self.subgroups = subgroups |
| 50 self.fields = fields |
| 51 self.type_name = class_name(join_name('style', name, 'data')) |
| 52 self.member_name = class_member_name(join_name(name, 'data')) |
| 53 self.num_32_bit_words_for_bit_fields = _num_32_bit_words_for_bit_fields( |
| 54 field for field in fields if field.is_bit_field |
| 55 ) |
| 56 |
| 57 # Recursively get all the fields in the subgroups as well |
| 58 self.all_fields = _flatten_list(subgroup.all_fields for subgroup in subg
roups) + fields |
| 18 | 59 |
| 19 | 60 |
| 20 class Field(object): | 61 class Field(object): |
| 21 """ | 62 """ |
| 22 The generated ComputedStyle object is made up of a series of Fields. | 63 The generated ComputedStyle object is made up of a series of Fields. |
| 23 Each Field has a name, size, type, etc, and a bunch of attributes to | 64 Each Field has a name, size, type, etc, and a bunch of attributes to |
| 24 determine which methods it will be used in. | 65 determine which methods it will be used in. |
| 25 | 66 |
| 26 A Field also has enough information to use any storage type in C++, such as | 67 A Field also has enough information to use any storage type in C++, such as |
| 27 regular member variables, or more complex storage like vectors or hashmaps. | 68 regular member variables, or more complex storage like vectors or hashmaps. |
| 28 Almost all properties will have at least one Field, often more than one. | 69 Almost all properties will have at least one Field, often more than one. |
| 29 | 70 |
| 30 Most attributes in this class correspond to parameters in CSSProperties.json
5. | 71 Most attributes in this class correspond to parameters in CSSProperties.json
5. |
| 31 See that file for a more detailed explanation of each attribute. | 72 See that file for a more detailed explanation of each attribute. |
| 32 | 73 |
| 33 Attributes: | 74 Attributes: |
| 34 field_role: The semantic role of the field. Can be: | 75 field_role: The semantic role of the field. Can be: |
| 35 - 'property': for fields that store CSS properties | 76 - 'property': for fields that store CSS properties |
| 36 - 'inherited_flag': for single-bit flags that store whether a proper
ty is | 77 - 'inherited_flag': for single-bit flags that store whether a proper
ty is |
| 37 inherited by this style or set explicitly | 78 inherited by this style or set explicitly |
| 38 - 'nonproperty': for fields that are not CSS properties | 79 - 'nonproperty': for fields that are not CSS properties |
| 39 name_for_methods: String used to form the names of getters and setters. | 80 name_for_methods: String used to form the names of getters and setters. |
| 40 Should be in upper camel case. | 81 Should be in upper camel case. |
| 41 property_name: Name of the property that the field is part of. | 82 property_name: Name of the property that the field is part of. |
| 42 type_name: Name of the C++ type exposed by the generated interface (e.g.
EClear, int). | 83 type_name: Name of the C++ type exposed by the generated interface (e.g.
EClear, int). |
| 43 field_template: Determines the interface generated for the field. Can be
one of: | 84 field_template: Determines the interface generated for the field. Can be
one of: |
| 44 keyword, flag, or monotonic_flag. | 85 keyword, flag, or monotonic_flag. |
| 86 field_group: The name of the group that this field is inside. |
| 45 size: Number of bits needed for storage. | 87 size: Number of bits needed for storage. |
| 46 default_value: Default value for this field when it is first initialized
. | 88 default_value: Default value for this field when it is first initialized
. |
| 47 """ | 89 """ |
| 48 | 90 |
| 49 def __init__(self, field_role, name_for_methods, property_name, type_name, | 91 def __init__(self, field_role, name_for_methods, property_name, type_name, |
| 50 field_template, size, default_value, getter_method_name, setter
_method_name, | 92 field_template, field_group, size, default_value, |
| 51 initial_method_name, **kwargs): | 93 getter_method_name, setter_method_name, initial_method_name, **
kwargs): |
| 52 """Creates a new field.""" | 94 """Creates a new field.""" |
| 53 self.name = class_member_name(name_for_methods) | 95 self.name = class_member_name(name_for_methods) |
| 54 self.property_name = property_name | 96 self.property_name = property_name |
| 55 self.type_name = type_name | 97 self.type_name = type_name |
| 56 self.field_template = field_template | 98 self.field_template = field_template |
| 99 self.group_name = field_group |
| 100 self.group_member_name = class_member_name(join_name(field_group, 'data'
)) if field_group else None |
| 57 self.size = size | 101 self.size = size |
| 58 self.default_value = default_value | 102 self.default_value = default_value |
| 59 | 103 |
| 60 # Field role: one of these must be true | 104 # Field role: one of these must be true |
| 61 self.is_property = field_role == 'property' | 105 self.is_property = field_role == 'property' |
| 62 self.is_inherited_flag = field_role == 'inherited_flag' | 106 self.is_inherited_flag = field_role == 'inherited_flag' |
| 63 self.is_nonproperty = field_role == 'nonproperty' | 107 self.is_nonproperty = field_role == 'nonproperty' |
| 64 assert (self.is_property, self.is_inherited_flag, self.is_nonproperty).c
ount(True) == 1, \ | 108 assert (self.is_property, self.is_inherited_flag, self.is_nonproperty).c
ount(True) == 1, \ |
| 65 'Field role has to be exactly one of: property, inherited_flag, nonp
roperty' | 109 'Field role has to be exactly one of: property, inherited_flag, nonp
roperty' |
| 66 | 110 |
| (...skipping 24 matching lines...) Expand all Loading... |
| 91 """ | 135 """ |
| 92 Get a list of paths that need to be included for ComputedStyleBase. | 136 Get a list of paths that need to be included for ComputedStyleBase. |
| 93 """ | 137 """ |
| 94 include_paths = set() | 138 include_paths = set() |
| 95 for property_ in properties: | 139 for property_ in properties: |
| 96 if property_['field_type_path'] is not None: | 140 if property_['field_type_path'] is not None: |
| 97 include_paths.add(property_['field_type_path'] + '.h') | 141 include_paths.add(property_['field_type_path'] + '.h') |
| 98 return list(sorted(include_paths)) | 142 return list(sorted(include_paths)) |
| 99 | 143 |
| 100 | 144 |
| 145 def _group_fields(fields): |
| 146 """Groups a list of fields by their group_name and returns the root group.""
" |
| 147 groups = defaultdict(list) |
| 148 for field in fields: |
| 149 groups[field.group_name].append(field) |
| 150 |
| 151 no_group = groups.pop(None) |
| 152 subgroups = [Group(group_name, [], _reorder_fields(fields)) for group_name,
fields in groups.items()] |
| 153 return Group('', subgroups=subgroups, fields=_reorder_fields(no_group)) |
| 154 |
| 155 |
| 101 def _create_enums(properties): | 156 def _create_enums(properties): |
| 102 """ | 157 """ |
| 103 Returns an OrderedDict of enums to be generated, enum name -> [list of enum
values] | 158 Returns an OrderedDict of enums to be generated, enum name -> [list of enum
values] |
| 104 """ | 159 """ |
| 105 enums = {} | 160 enums = {} |
| 106 for property_ in properties: | 161 for property_ in properties: |
| 107 # Only generate enums for keyword properties that use the default field_
type_path. | 162 # Only generate enums for keyword properties that use the default field_
type_path. |
| 108 if property_['field_template'] == 'keyword' and property_['field_type_pa
th'] is None: | 163 if property_['field_template'] == 'keyword' and property_['field_type_pa
th'] is None: |
| 109 enum_name = property_['type_name'] | 164 enum_name = property_['type_name'] |
| 110 enum_values = [enum_value_name(k) for k in property_['keywords']] | 165 enum_values = [enum_value_name(k) for k in property_['keywords']] |
| (...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 158 size = 1 | 213 size = 1 |
| 159 | 214 |
| 160 return Field( | 215 return Field( |
| 161 field_role, | 216 field_role, |
| 162 name_for_methods, | 217 name_for_methods, |
| 163 property_name=property_['name'], | 218 property_name=property_['name'], |
| 164 inherited=property_['inherited'], | 219 inherited=property_['inherited'], |
| 165 independent=property_['independent'], | 220 independent=property_['independent'], |
| 166 type_name=type_name, | 221 type_name=type_name, |
| 167 field_template=property_['field_template'], | 222 field_template=property_['field_template'], |
| 223 field_group=property_['field_group'], |
| 168 size=size, | 224 size=size, |
| 169 default_value=default_value, | 225 default_value=default_value, |
| 170 getter_method_name=property_['getter'], | 226 getter_method_name=property_['getter'], |
| 171 setter_method_name=property_['setter'], | 227 setter_method_name=property_['setter'], |
| 172 initial_method_name=property_['initial'], | 228 initial_method_name=property_['initial'], |
| 173 ) | 229 ) |
| 174 | 230 |
| 175 | 231 |
| 176 def _create_inherited_flag_field(property_): | 232 def _create_inherited_flag_field(property_): |
| 177 """ | 233 """ |
| 178 Create the field used for an inheritance fast path from an independent CSS p
roperty, | 234 Create the field used for an inheritance fast path from an independent CSS p
roperty, |
| 179 and return the Field object. | 235 and return the Field object. |
| 180 """ | 236 """ |
| 181 name_for_methods = join_name(property_['name_for_methods'], 'is inherited') | 237 name_for_methods = join_name(property_['name_for_methods'], 'is inherited') |
| 182 return Field( | 238 return Field( |
| 183 'inherited_flag', | 239 'inherited_flag', |
| 184 name_for_methods, | 240 name_for_methods, |
| 185 property_name=property_['name'], | 241 property_name=property_['name'], |
| 186 type_name='bool', | 242 type_name='bool', |
| 187 field_template='primitive', | 243 field_template='primitive', |
| 244 field_group=property_['field_group'], |
| 188 size=1, | 245 size=1, |
| 189 default_value='true', | 246 default_value='true', |
| 190 getter_method_name=method_name(name_for_methods), | 247 getter_method_name=method_name(name_for_methods), |
| 191 setter_method_name=method_name(join_name('set', name_for_methods)), | 248 setter_method_name=method_name(join_name('set', name_for_methods)), |
| 192 initial_method_name=method_name(join_name('initial', name_for_methods)), | 249 initial_method_name=method_name(join_name('initial', name_for_methods)), |
| 193 ) | 250 ) |
| 194 | 251 |
| 195 | 252 |
| 196 def _create_fields(properties): | 253 def _create_fields(properties): |
| 197 """ | 254 """ |
| 198 Create ComputedStyle fields from properties or nonproperties and return a li
st of Field objects. | 255 Create ComputedStyle fields from properties or nonproperties and return a li
st of Field objects. |
| 199 """ | 256 """ |
| 200 fields = [] | 257 fields = [] |
| 201 for property_ in properties: | 258 for property_ in properties: |
| 202 # Only generate properties that have a field template | 259 # Only generate properties that have a field template |
| 203 if property_['field_template'] is not None: | 260 if property_['field_template'] is not None: |
| 204 # If the property is independent, add the single-bit sized isInherit
ed flag | 261 # If the property is independent, add the single-bit sized isInherit
ed flag |
| 205 # to the list of Fields as well. | 262 # to the list of Fields as well. |
| 206 if property_['independent']: | 263 if property_['independent']: |
| 207 fields.append(_create_inherited_flag_field(property_)) | 264 fields.append(_create_inherited_flag_field(property_)) |
| 208 | 265 |
| 209 # TODO(shend): Get rid of the property/nonproperty field roles. | 266 # TODO(shend): Get rid of the property/nonproperty field roles. |
| 267 # If the field has_custom_compare_and_copy, then it does not appear
in |
| 268 # ComputedStyle::operator== and ComputedStyle::CopyNonInheritedFromC
ached. |
| 210 field_role = 'nonproperty' if property_['has_custom_compare_and_copy
'] else 'property' | 269 field_role = 'nonproperty' if property_['has_custom_compare_and_copy
'] else 'property' |
| 211 fields.append(_create_field(field_role, property_)) | 270 fields.append(_create_field(field_role, property_)) |
| 212 | 271 |
| 213 return fields | 272 return fields |
| 214 | 273 |
| 215 | 274 |
| 216 def _pack_fields(fields): | 275 def _reorder_fields(fields): |
| 217 """ | 276 """ |
| 218 Group a list of fields into buckets to minimise padding. | 277 Returns a list of fields ordered to minimise padding. |
| 219 Returns a list of buckets, where each bucket is a list of Field objects. | |
| 220 """ | 278 """ |
| 279 # Separate out bit fields from non bit fields |
| 280 bit_fields = [field for field in fields if field.is_bit_field] |
| 281 non_bit_fields = [field for field in fields if not field.is_bit_field] |
| 282 |
| 221 # Since fields cannot cross word boundaries, in order to minimize | 283 # Since fields cannot cross word boundaries, in order to minimize |
| 222 # padding, group fields into buckets so that as many buckets as possible | 284 # padding, group fields into buckets so that as many buckets as possible |
| 223 # are exactly 32 bits. Although this greedy approach may not always | 285 # are exactly 32 bits. Although this greedy approach may not always |
| 224 # produce the optimal solution, we add a static_assert to the code to | 286 # produce the optimal solution, we add a static_assert to the code to |
| 225 # ensure ComputedStyleBase results in the expected size. If that | 287 # ensure ComputedStyleBase results in the expected size. If that |
| 226 # static_assert fails, this code is falling into the small number of | 288 # static_assert fails, this code is falling into the small number of |
| 227 # cases that are suboptimal, and may need to be rethought. | 289 # cases that are suboptimal, and may need to be rethought. |
| 228 # For more details on packing bit fields to reduce padding, see: | 290 # For more details on packing bit fields to reduce padding, see: |
| 229 # http://www.catb.org/esr/structure-packing/#_bitfields | 291 # http://www.catb.org/esr/structure-packing/#_bitfields |
| 230 field_buckets = [] | 292 field_buckets = [] |
| 231 # Consider fields in descending order of size to reduce fragmentation | 293 # Consider fields in descending order of size to reduce fragmentation |
| 232 # when they are selected. Ties broken in alphabetical order by name. | 294 # when they are selected. Ties broken in alphabetical order by name. |
| 233 for field in sorted(fields, key=lambda f: (-f.size, f.name)): | 295 for field in sorted(bit_fields, key=lambda f: (-f.size, f.name)): |
| 234 added_to_bucket = False | 296 added_to_bucket = False |
| 235 # Go through each bucket and add this field if it will not increase | 297 # Go through each bucket and add this field if it will not increase |
| 236 # the bucket's size to larger than 32 bits. Otherwise, make a new | 298 # the bucket's size to larger than 32 bits. Otherwise, make a new |
| 237 # bucket containing only this field. | 299 # bucket containing only this field. |
| 238 for bucket in field_buckets: | 300 for bucket in field_buckets: |
| 239 if sum(f.size for f in bucket) + field.size <= 32: | 301 if sum(f.size for f in bucket) + field.size <= 32: |
| 240 bucket.append(field) | 302 bucket.append(field) |
| 241 added_to_bucket = True | 303 added_to_bucket = True |
| 242 break | 304 break |
| 243 if not added_to_bucket: | 305 if not added_to_bucket: |
| 244 field_buckets.append([field]) | 306 field_buckets.append([field]) |
| 245 | 307 |
| 246 return field_buckets | 308 # Non bit fields go first, then the bit fields. |
| 309 return list(non_bit_fields) + _flatten_list(field_buckets) |
| 247 | 310 |
| 248 | 311 |
| 249 class ComputedStyleBaseWriter(make_style_builder.StyleBuilderWriter): | 312 class ComputedStyleBaseWriter(make_style_builder.StyleBuilderWriter): |
| 250 def __init__(self, json5_file_paths): | 313 def __init__(self, json5_file_paths): |
| 251 # Read CSS properties | 314 # Read CSS properties |
| 252 super(ComputedStyleBaseWriter, self).__init__([json5_file_paths[0]]) | 315 super(ComputedStyleBaseWriter, self).__init__([json5_file_paths[0]]) |
| 253 | 316 |
| 254 # Ignore shorthand properties | 317 # Ignore shorthand properties |
| 255 for property_ in self._properties.values(): | 318 for property_ in self._properties.values(): |
| 256 if property_['field_template'] is not None: | 319 if property_['field_template'] is not None: |
| (...skipping 21 matching lines...) Expand all Loading... |
| 278 | 341 |
| 279 # Override the type name when field_type_path is specified | 342 # Override the type name when field_type_path is specified |
| 280 for property_ in all_properties: | 343 for property_ in all_properties: |
| 281 if property_['field_type_path']: | 344 if property_['field_type_path']: |
| 282 property_['type_name'] = property_['field_type_path'].split('/')
[-1] | 345 property_['type_name'] = property_['field_type_path'].split('/')
[-1] |
| 283 | 346 |
| 284 self._generated_enums = _create_enums(all_properties) | 347 self._generated_enums = _create_enums(all_properties) |
| 285 | 348 |
| 286 all_fields = _create_fields(all_properties) | 349 all_fields = _create_fields(all_properties) |
| 287 | 350 |
| 288 # Separate the normal fields from the bit fields | 351 # Organise fields into a tree structure where the root group |
| 289 bit_fields = [field for field in all_fields if field.is_bit_field] | 352 # is ComputedStyleBase. |
| 290 normal_fields = [field for field in all_fields if not field.is_bit_field
] | 353 self._root_group = _group_fields(all_fields) |
| 291 | |
| 292 # Pack bit fields into buckets | |
| 293 field_buckets = _pack_fields(bit_fields) | |
| 294 | |
| 295 # The expected size of ComputedStyleBase is equivalent to as many words | |
| 296 # as the total number of buckets. | |
| 297 self._expected_bit_field_bytes = len(field_buckets) | |
| 298 | |
| 299 # The most optimal size of ComputedStyleBase is the total sum of all the | |
| 300 # field sizes, rounded up to the nearest word. If this produces the | |
| 301 # incorrect value, either the packing algorithm is not optimal or there | |
| 302 # is no way to pack the fields such that excess padding space is not | |
| 303 # added. | |
| 304 # If this fails, increase extra_padding_bytes by 1, but be aware that | |
| 305 # this also increases ComputedStyleBase by 1 word. | |
| 306 # We should be able to bring extra_padding_bytes back to 0 from time to | |
| 307 # time. | |
| 308 extra_padding_bytes = 0 | |
| 309 optimal_bit_field_bytes = int(math.ceil(sum(f.size for f in bit_fields)
/ 32.0)) | |
| 310 real_bit_field_bytes = optimal_bit_field_bytes + extra_padding_bytes | |
| 311 assert self._expected_bit_field_bytes == real_bit_field_bytes, \ | |
| 312 ('The field packing algorithm produced %s bytes, optimal is %s bytes
' % | |
| 313 (self._expected_bit_field_bytes, real_bit_field_bytes)) | |
| 314 | |
| 315 # Normal fields go first, then the bit fields. | |
| 316 self._fields = list(normal_fields) | |
| 317 | |
| 318 # Order the fields so fields in each bucket are adjacent. | |
| 319 for bucket in field_buckets: | |
| 320 for field in bucket: | |
| 321 self._fields.append(field) | |
| 322 | 354 |
| 323 self._include_paths = _get_include_paths(all_properties) | 355 self._include_paths = _get_include_paths(all_properties) |
| 324 self._outputs = { | 356 self._outputs = { |
| 325 'ComputedStyleBase.h': self.generate_base_computed_style_h, | 357 'ComputedStyleBase.h': self.generate_base_computed_style_h, |
| 326 'ComputedStyleBase.cpp': self.generate_base_computed_style_cpp, | 358 'ComputedStyleBase.cpp': self.generate_base_computed_style_cpp, |
| 327 'ComputedStyleBaseConstants.h': self.generate_base_computed_style_co
nstants, | 359 'ComputedStyleBaseConstants.h': self.generate_base_computed_style_co
nstants, |
| 328 } | 360 } |
| 329 | 361 |
| 330 @template_expander.use_jinja('ComputedStyleBase.h.tmpl') | 362 @template_expander.use_jinja('ComputedStyleBase.h.tmpl') |
| 331 def generate_base_computed_style_h(self): | 363 def generate_base_computed_style_h(self): |
| 332 return { | 364 return { |
| 333 'properties': self._properties, | 365 'properties': self._properties, |
| 334 'enums': self._generated_enums, | 366 'enums': self._generated_enums, |
| 335 'include_paths': self._include_paths, | 367 'include_paths': self._include_paths, |
| 336 'fields': self._fields, | 368 'computed_style': self._root_group, |
| 337 } | 369 } |
| 338 | 370 |
| 339 @template_expander.use_jinja('ComputedStyleBase.cpp.tmpl') | 371 @template_expander.use_jinja('ComputedStyleBase.cpp.tmpl') |
| 340 def generate_base_computed_style_cpp(self): | 372 def generate_base_computed_style_cpp(self): |
| 341 return { | 373 return { |
| 342 'properties': self._properties, | 374 'properties': self._properties, |
| 343 'enums': self._generated_enums, | 375 'enums': self._generated_enums, |
| 344 'fields': self._fields, | 376 'computed_style': self._root_group, |
| 345 'expected_bit_field_bytes': self._expected_bit_field_bytes, | |
| 346 } | 377 } |
| 347 | 378 |
| 348 @template_expander.use_jinja('ComputedStyleBaseConstants.h.tmpl') | 379 @template_expander.use_jinja('ComputedStyleBaseConstants.h.tmpl') |
| 349 def generate_base_computed_style_constants(self): | 380 def generate_base_computed_style_constants(self): |
| 350 return { | 381 return { |
| 351 'properties': self._properties, | 382 'properties': self._properties, |
| 352 'enums': self._generated_enums, | 383 'enums': self._generated_enums, |
| 353 'fields': self._fields, | |
| 354 } | 384 } |
| 355 | 385 |
| 356 if __name__ == '__main__': | 386 if __name__ == '__main__': |
| 357 json5_generator.Maker(ComputedStyleBaseWriter).main() | 387 json5_generator.Maker(ComputedStyleBaseWriter).main() |
| OLD | NEW |