summaryrefslogtreecommitdiffstats
path: root/toolkit/components/glean/build_scripts/glean_parser_ext/cpp.py
blob: 003ae4997cf801d8dab5fc3030333469de01b0a5 (plain)
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
# -*- coding: utf-8 -*-

# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

"""
Outputter to generate C++ code for metrics.
"""

import json

import jinja2
from glean_parser import metrics, util
from util import generate_metric_ids, generate_ping_ids, get_metrics


def cpp_datatypes_filter(value):
    """
    A Jinja2 filter that renders C++ literals.

    Based on Python's JSONEncoder, but overrides:
      - lists to array literals {}
      - strings to "value"
    """

    class CppEncoder(json.JSONEncoder):
        def iterencode(self, value):
            if isinstance(value, list):
                yield "{"
                first = True
                for subvalue in list(value):
                    if not first:
                        yield ", "
                    yield from self.iterencode(subvalue)
                    first = False
                yield "}"
            elif isinstance(value, str):
                yield '"' + value + '"'
            else:
                yield from super().iterencode(value)

    return "".join(CppEncoder().iterencode(value))


def type_name(obj):
    """
    Returns the C++ type to use for a given metric object.
    """

    if getattr(obj, "labeled", False):
        class_name = util.Camelize(obj.type[8:])  # strips "labeled_" off the front.
        label_enum = "DynamicLabel"
        if obj.labels and len(obj.labels):
            label_enum = f"{util.Camelize(obj.name)}Label"
        return f"Labeled<impl::{class_name}Metric, {label_enum}>"
    generate_enums = getattr(obj, "_generate_enums", [])  # Extra Keys? Reasons?
    if len(generate_enums):
        for name, _ in generate_enums:
            if not len(getattr(obj, name)) and isinstance(obj, metrics.Event):
                return util.Camelize(obj.type) + "Metric<NoExtraKeys>"
            else:
                # we always use the `extra` suffix,
                # because we only expose the new event API
                suffix = "Extra"
                return "{}Metric<{}>".format(
                    util.Camelize(obj.type), util.Camelize(obj.name) + suffix
                )
    return util.Camelize(obj.type) + "Metric"


def extra_type_name(typ: str) -> str:
    """
    Returns the corresponding Rust type for event's extra key types.
    """

    if typ == "boolean":
        return "bool"
    elif typ == "string":
        return "nsCString"
    elif typ == "quantity":
        return "uint32_t"
    else:
        return "UNSUPPORTED"


def output_cpp(objs, output_fd, options={}):
    """
    Given a tree of objects, output C++ code to the file-like object `output_fd`.

    :param objs: A tree of objects (metrics and pings) as returned from
    `parser.parse_objects`.
    :param output_fd: Writeable file to write the output to.
    :param options: options dictionary.
    """

    # Monkeypatch util.snake_case for the templates to use
    util.snake_case = lambda value: value.replace(".", "_").replace("-", "_")
    # Monkeypatch util.get_jinja2_template to find templates nearby

    def get_local_template(template_name, filters=()):
        env = jinja2.Environment(
            loader=jinja2.PackageLoader("cpp", "templates"),
            trim_blocks=True,
            lstrip_blocks=True,
        )
        env.filters["camelize"] = util.camelize
        env.filters["Camelize"] = util.Camelize
        for filter_name, filter_func in filters:
            env.filters[filter_name] = filter_func
        return env.get_template(template_name)

    util.get_jinja2_template = get_local_template
    get_metric_id = generate_metric_ids(objs)
    get_ping_id = generate_ping_ids(objs)

    if "pings" in objs:
        template_filename = "cpp_pings.jinja2"
        if objs.get("tags"):
            del objs["tags"]
    else:
        template_filename = "cpp.jinja2"
        objs = get_metrics(objs)

    template = util.get_jinja2_template(
        template_filename,
        filters=(
            ("cpp", cpp_datatypes_filter),
            ("snake_case", util.snake_case),
            ("type_name", type_name),
            ("extra_type_name", extra_type_name),
            ("metric_id", get_metric_id),
            ("ping_id", get_ping_id),
            ("Camelize", util.Camelize),
        ),
    )

    output_fd.write(template.render(all_objs=objs))
    output_fd.write("\n")