chromium/third_party/blink/renderer/build/scripts/core/css/make_css_value_id_mappings.py

#!/usr/bin/env python
# Copyright 2017 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

from blinkbuild.name_style_converter import NameStyleConverter
import json5_generator
import template_expander
import make_style_builder
import keyword_utils
from name_utilities import enum_key_for_css_keyword


def _find_continuous_segment(numbers):
    """Find the longest continuous segment in a list of numbers.
    For example:
        input:
                1, 2, 3, 4, 5, 6
               22,70,23,24,25,26
        output:
            number_list_sorted:
                1, 3, 4, 5, 6, 2
               22,23,24,25,26,70
            segments:
                0, 1, 5, 6
            which means there are 3 segment with start and end indices
            on the number_list_sorted to be: (0, 1), (1, 5), (5, 6)

    Args:
        numbers: List of pairs of number

    Returns:
        segment: a list containing the indices of the segment start point
            and end point.
        number_list: sorted by the first element version of the input.

    """
    segments = [0]
    number_list_sorted = sorted(numbers, key=lambda elem: elem[0])
    for i in range(len(number_list_sorted) - 1):
        # continuous segment is a segment which the number in pair is 1 unit
        # more than the previous pair
        if (number_list_sorted[i + 1][0] - number_list_sorted[i][0] != 1 or
                number_list_sorted[i + 1][1] - number_list_sorted[i][1] != 1):
            segments.append(i + 1)
    segments.append(len(number_list_sorted))
    return segments, number_list_sorted


def _find_largest_segment(segments):
    """Find the largest segment given a list of start and end
    indices of segments

    Args:
        segments: a list of start and end indices

    Returns:
        longest_segment: the start and end indices of the longest segment

    """
    segment_list = zip(segments[:-1], segments[1:])
    return max(segment_list, key=lambda x: x[1] - x[0])


def _find_enum_longest_continuous_segment(property_,
                                          name_to_position_dictionary):
    """Find the longest continuous segment in the list of keywords
    Finding the continuous segment will allows us to do the subtraction
    between keywords so that the distance between 2 keywords in this
    enum is equal to the distance of corresponding keywords in another
    enum.

    Step 1:
        Convert keyword enums into number.
        Sort and find all continuous segment in the list of enums.

    Step 2:
        Get the longest segment.

    Step 3:
        Compose a list of keyword enums and their respective numbers
        in the sorted order.

    Step 4:
        Build the switch case statements of other enums not in the
        segment. Enums in the segment will be computed in default clause.
    """
    property_enum_order = range(len(property_.keywords))
    css_enum_order = [
        name_to_position_dictionary[x] for x in property_.keywords
    ]
    enum_pair_list = zip(css_enum_order, property_enum_order)
    enum_segment, enum_pair_list = _find_continuous_segment(enum_pair_list)
    longest_segment = _find_largest_segment(enum_segment)

    enum_tuple_list = []
    for x in enum_pair_list:
        keyword = NameStyleConverter(property_.keywords[x[1]])
        enum_tuple_list.append((enum_key_for_css_keyword(keyword), x[1], x[0]))
    return enum_tuple_list, enum_segment, longest_segment


class CSSValueIDMappingsWriter(make_style_builder.StyleBuilderWriter):
    def __init__(self, json5_file_paths, output_dir):
        super(CSSValueIDMappingsWriter, self).__init__(json5_file_paths,
                                                       output_dir)
        self._outputs = {
            'css_value_id_mappings_generated.h':
            self.generate_css_value_mappings,
        }
        self.css_values_dictionary_file = json5_file_paths[3]
        css_properties = self.css_properties.longhands
        # We sort the enum values based on each value's position in
        # the keywords as listed in css_properties.json5. This will ensure that if there is a continuous
        # segment in css_properties.json5 matching the segment in this enum then
        # the generated enum will have the same order and continuity as
        # css_properties.json5 and we can get the longest continuous segment.
        # Thereby reduce the switch case statement to the minimum.
        css_properties = keyword_utils.sort_keyword_properties_by_canonical_order(
            css_properties, json5_file_paths[3], self.default_parameters)

    @template_expander.use_jinja(
        'core/css/templates/css_value_id_mappings_generated.h.tmpl')
    def generate_css_value_mappings(self):
        mappings = {}
        include_paths = set()
        css_values_dictionary = json5_generator.Json5File.load_from_files(
            [self.css_values_dictionary_file],
            default_parameters=self.default_parameters).name_dictionaries
        name_to_position_dictionary = dict(
            zip([x['name'].original for x in css_values_dictionary],
                range(len(css_values_dictionary))))

        for property_ in self.css_properties.properties_including_aliases:
            include_paths.update(property_.include_paths)
            if property_.field_template in ('multi_keyword', 'bitset_keyword'):
                mappings[property_.type_name] = {
                    'default_value':
                    property_.default_value,
                    'mapping':
                    [enum_key_for_css_keyword(k) for k in property_.keywords],
                }
            elif property_.field_template == 'keyword':
                enum_pair_list, enum_segment, p_segment = _find_enum_longest_continuous_segment(
                    property_, name_to_position_dictionary)
                mappings[property_.type_name] = {
                    'default_value': property_.default_value,
                    'mapping': enum_pair_list,
                    'segment': enum_segment,
                    'longest_segment_length': p_segment[1] - p_segment[0],
                    'start_segment': enum_pair_list[p_segment[0]],
                    'end_segment': enum_pair_list[p_segment[1] - 1],
                }

        return {
            'include_paths': list(sorted(include_paths)),
            'input_files': self._input_files,
            'mappings': mappings,
        }


if __name__ == '__main__':
    json5_generator.Maker(CSSValueIDMappingsWriter).main()