1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223
|
# Copyright 2025 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from dataclasses import dataclass
from enum import Enum
from typing import Optional
class AggregationKind(Enum):
"""Allowed aggregation types for data that uses "aggregate" declaration."""
NONE = "none"
ARRAY = "array"
MAP = "map"
@dataclass
class AggregationDetails:
"""Aggregation rules, if specified by the processed JSON file."""
kind: AggregationKind
name: Optional[str]
export_items: bool
elements: dict[str, str]
map_key_type: Optional[str]
def GetSortedArrayElements(self) -> list[str]:
"""Returns sorted list of names of all elements."""
return sorted(self.elements.keys())
def GetSortedMapElements(self) -> list[tuple[str, str]]:
"""Returns sorted mapping of all elements, including aliases."""
keys = sorted(self.elements.keys())
return [(key, self.elements[key]) for key in keys]
def GetAggregationDetails(description) -> AggregationDetails:
"""Extracts aggregation details from a JSON structure.
This function processes a JSON data object to determine its aggregation
properties. Aggregation details are specified within an optional "aggregation"
descriptor in the JSON input. If the descriptor is missing, no aggregation is
performed.
**Aggregation Descriptor Structure:**
- `type` (str): Defines the aggregation type. Options include:
- `"none"`: No aggregation (default/implied if descriptor is missing).
- `"array"`: Uses `std::span<Type>` for aggregation.
- `"map"`: Uses `base::fixed_flat_map<std::string_view, Type>` for
aggregation.
- `name` (str): The name assigned to the generated array or map.
- `export_items` (bool, optional): Whether aggregated items should be
exported (defaults to `true`).
- `map_aliases` (dict[str, str]) - if the aggregation `type` is set to
`"map"`, this field allows specifying additional aliases - elements
pointing to already defined structures.
- `map_key_type` (str) - the type representing the map key. Must be a
constexpr-constructible from const char[].
**Default Behavior:**
- If the `aggregation` descriptor is missing, the function defaults to:
- `type = "none"`
- `export_items = True`
- The `name` field is only relevant when aggregation is `array` or `map`.
**Parameters:**
description (dict): input JSON data file (not schema).
**Returns:**
AggregationDetails populated with relevant fields. This is always
returned, even if the data object does not include aggregation descriptor.
"""
aggregation = description.get('aggregation', {})
kind = AggregationKind(aggregation.get('type', 'none'))
name = aggregation.get('name', None)
export_items = aggregation.get('export_items', True)
map_aliases = aggregation.get('map_aliases', {})
map_key_type = None
if kind != AggregationKind.NONE and not name:
raise Exception("Aggregation container needs a `name`.")
elements = {}
for element in description.get('elements', {}).keys():
elements.update({element: element})
confirmed_aliases = {}
if kind == AggregationKind.MAP:
map_key_type = aggregation.get('map_key_type', 'std::string_view')
for alias, element in map_aliases.items():
# Confirmation check for duplicate entries.
# Note: we do not need to verify duplicate aliases, because `map_aliases`
# is already a dict - all keys should be unique.
if elements.get(alias, None):
raise Exception(f"Alias `{alias}` already defined as element.")
# Detect that alias does not point to a valid element.
if not elements.get(element, None):
raise Exception(f"Aliased element `{element}` does not exist.")
confirmed_aliases.update({alias: element})
elements.update(confirmed_aliases)
return AggregationDetails(kind, name, export_items, elements, map_key_type)
def GenerateCCAggregation(type_name: str,
aggregation: AggregationDetails) -> Optional[str]:
"""
Generates C++ aggregation code based on the aggregation kind.
Parameters:
type_name (str): The type name to be used in the aggregation.
aggregation (AggregationDetails): The aggregation details.
Returns:
Optional[str]: The generated C++ aggregation code if applicable, otherwise None.
"""
if aggregation.kind == AggregationKind.ARRAY:
return _GenerateCCArray(type_name, aggregation)
if aggregation.kind == AggregationKind.MAP:
return _GenerateCCMap(type_name, aggregation)
return None
def _GenerateCCArray(type_name: str, aggregation: AggregationDetails) -> str:
"""
Generates C++ code for an array aggregation.
Parameters:
type_name (str): The type name to be used in the aggregation.
aggregation (AggregationDetails): The aggregation details.
Returns:
str: The generated C++ array aggregation code.
"""
res = f'\nconst auto {aggregation.name} =\n'
res += f' std::array<const {type_name}*, {len(aggregation.elements)}>'
res += '({{\n'
for element_name in aggregation.elements.values():
res += f' &{element_name},\n'
res += '}});\n'
return res
def _GenerateCCMap(type_name: str, aggregation: AggregationDetails) -> str:
"""
Generates C++ code for a map aggregation.
Parameters:
type_name (str): The type name to be used in the aggregation.
aggregation (AggregationDetails): The aggregation details.
Returns:
str: The generated C++ map aggregation code.
"""
key_type = aggregation.map_key_type
res = f'\nconst auto {aggregation.name} =\n'
res += f' base::MakeFixedFlatMap<{key_type}, const {type_name}*>'
res += '({\n'
for (alias_name, element_name) in aggregation.GetSortedMapElements():
res += f' {{{key_type}("{alias_name}"), &{element_name}}},\n'
res += '});\n'
return res
def GenerateHHAggregation(type_name: str,
aggregation: AggregationDetails) -> Optional[str]:
"""
Generates header file aggregation code based on the aggregation kind.
Parameters:
type_name (str): The type name to be used in the aggregation.
aggregation (AggregationDetails): The aggregation details.
Returns:
Optional[str]: The generated header file aggregation code if applicable, otherwise None.
"""
if aggregation.kind == AggregationKind.ARRAY:
return _GenerateHHArray(type_name, aggregation)
if aggregation.kind == AggregationKind.MAP:
return _GenerateHHMap(type_name, aggregation)
return None
def _GenerateHHArray(type_name: str, aggregation: AggregationDetails) -> str:
"""
Generates header file code for an array aggregation.
Parameters:
type_name (str): The type name to be used in the aggregation.
aggregation (AggregationDetails): The aggregation details.
Returns:
str: The generated header file array aggregation declaration.
"""
res = '\nextern const '
res += f'std::array<const {type_name}*, {len(aggregation.elements)}> '
res += f'{aggregation.name};\n'
return res
def _GenerateHHMap(type_name: str, aggregation: AggregationDetails) -> str:
"""
Generates header file code for a map aggregation.
Parameters:
type_name (str): The type name to be used in the aggregation.
aggregation (AggregationDetails): The aggregation details.
Returns:
str: The generated header file map aggregation declaration.
"""
res = '\nextern const '
res += f'base::fixed_flat_map<{aggregation.map_key_type}, '
res += f'const {type_name}*, {len(aggregation.GetSortedMapElements())}> '
res += f'{aggregation.name};\n'
return res
|