nimbus_fml/backends/
mod.rs

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
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
/* 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/. */

//! # Backend traits
//!
//! This module provides a number of traits useful for implementing a backend for FML structs.
//!
//! A [CodeType] is needed for each type that is referred to in the feature definition (i.e. every [TypeRef]
//! instance should have corresponding `CodeType` instance). Helper code for types might include managing how merging/overriding of
//! defaults occur.
//!
//! A [CodeDeclaration] is needed for each type that is declared in the manifest file: i.e. an Object classes, Enum classes and Feature classes.
//! This has access to intermediate structs of the [crate::intermediate_representation::FeatureManifest] so may want to do some additional lookups to help rendering.
//!
//! `CodeDeclaration`s provide the target language's version of the type defined in the feature manifest. For objects and features, this would
//! be objects that have properties corresponding to the FML variables. For enums, this would mean the Enum class definition. In all cases, this will
//! likely be attached to an [askama::Template].
//!
//! `CodeDeclaration`s can also be used to conditionally include code: e.g. only include the CallbackInterfaceRuntime
//! if the user has used at least one callback interface.
//!
//! Each backend has a wrapper template for each file it needs to generate. This should collect the `CodeDeclaration`s that
//! the backend and `FeatureManifest` between them specify and use them to stitch together a file in the target language.
//!
//! The [CodeOracle] provides methods to map the `TypeRef` values found in the `FeatureManifest` to the `CodeType`s specified
//! by the backend.
//!
//! Each backend will have its own `filter` module, which is used by the askama templates used in all `CodeType`s and `CodeDeclaration`s.
//! This filter provides methods to generate expressions and identifiers in the target language. These are all forwarded to the oracle.

use std::fmt::Display;

use crate::intermediate_representation::Literal;
use crate::intermediate_representation::TypeRef;

pub type TypeIdentifier = TypeRef;

/// An object to look up a foreign language code specific renderer for a given type used.
/// Every [TypeRef] referred to in the [crate::intermediate_representation::FeatureManifest] should map to a corresponding
/// `CodeType`.
///
/// The mapping may be opaque, but the oracle always knows the answer.
pub trait CodeOracle {
    fn find(&self, type_: &TypeIdentifier) -> Box<dyn CodeType>;
}

/// A Trait to emit foreign language code to handle referenced types.
/// A type which is specified in the FML (i.e. a type that a variable declares itself of)
/// will have a `CodeDeclaration` as well, but for types used e.g. primitive types, Strings, etc
/// only a `CodeType` is needed.
///
/// This includes generating an literal of the type from the right type of JSON and
/// expressions to get a property from the JSON backed `Variables` object.
pub trait CodeType {
    /// The language specific label used to reference this type. This will be used in
    /// method signatures and property declarations.
    fn type_label(&self, oracle: &dyn CodeOracle) -> String;

    /// The language specific expression that gets a value of the `prop` from the `vars` object,
    /// and fallbacks to the `default` value.
    ///
    /// /// All the propertis follow this general pattern:
    ///
    /// ```kt
    /// variables?.{{ value_getter }}
    ///         ?.{{ value_mapper }}
    ///         ?.{{ value_merger }}
    ///         ?: {{ default_fallback}}
    /// ```
    ///
    /// In the case of structural types and objects, `value_mapper` and `value_merger`
    /// become mutually recursive to generate quite complicated properties.
    ///
    fn property_getter(
        &self,
        oracle: &dyn CodeOracle,
        vars: &dyn Display,
        prop: &dyn Display,
        default: &dyn Display,
    ) -> String;

    /// The expression needed to get a value out of a `Variables` objectm with the `prop` key.
    ///
    /// This will almost certainly use the `variables_type` method to determine which method to use.
    /// e.g. `vars?.getString("prop")`
    ///
    /// The `value_mapper` will be used to transform this value into the required value.
    fn value_getter(
        &self,
        oracle: &dyn CodeOracle,
        vars: &dyn Display,
        prop: &dyn Display,
    ) -> String;

    /// The method call here will use the `create_transform` to transform the value coming out of
    /// the `Variables` object into the desired type.
    ///
    /// e.g. a string will need to be transformed into an enum, so the value mapper in Kotlin will be
    /// `let(Enum::enumValue)`.
    ///
    /// If the value is `None`, then no mapper is used.
    fn value_mapper(&self, _oracle: &dyn CodeOracle) -> Option<String> {
        None
    }

    /// The method call to merge the value with the defaults.
    ///
    /// This may use the `merge_transform`.
    ///
    /// If this returns `None`, no merging happens, and implicit `null` replacement happens.
    fn value_merger(&self, _oracle: &dyn CodeOracle, _default: &dyn Display) -> Option<String> {
        None
    }

    /// The name of the type as it's represented in the `Variables` object.
    /// The string return may be used to combine with an identifier, e.g. a `Variables` method name.
    fn variables_type(&self, _oracle: &dyn CodeOracle) -> VariablesType;

    /// A function handle that is capable of turning the variables type to the TypeRef type.
    fn create_transform(&self, _oracle: &dyn CodeOracle) -> Option<String> {
        None
    }

    /// A function handle that is capable of merging two instances of the same class. By default, this is None.
    fn merge_transform(&self, _oracle: &dyn CodeOracle) -> Option<String> {
        None
    }

    // The foreign language type for how default values are stored in the `Defaults` object.
    // This is usually the same as the type_label itself, but occasionally— e.g. for bundled resources—
    // this will be different.
    // If it is different, then a `defaults_mapper` is needed to map between the `defaults_type` and the
    // `type_label` type.
    fn defaults_type(&self, oracle: &dyn CodeOracle) -> String {
        self.type_label(oracle)
    }

    fn defaults_mapper(
        &self,
        _oracle: &dyn CodeOracle,
        _value: &dyn Display,
        _vars: &dyn Display,
    ) -> Option<String> {
        None
    }

    fn preference_getter(
        &self,
        _oracle: &dyn CodeOracle,
        _prefs: &dyn Display,
        _pref_key: &dyn Display,
    ) -> Option<String> {
        None
    }

    /// Call from the template
    fn as_json(&self, oracle: &dyn CodeOracle, prop: &dyn Display) -> String {
        self.as_json_transform(oracle, prop)
            .unwrap_or_else(|| prop.to_string())
    }

    /// Implement these in different code types, and call recursively from different code types.
    fn as_json_transform(&self, _oracle: &dyn CodeOracle, _prop: &dyn Display) -> Option<String> {
        None
    }

    /// A representation of the given literal for this type.
    /// N.B. `Literal` is aliased from `serde_json::Value`.
    fn literal(
        &self,
        oracle: &dyn CodeOracle,
        ctx: &dyn Display,
        renderer: &dyn LiteralRenderer,
        literal: &Literal,
    ) -> String;

    fn is_resource_id(&self, _literal: &Literal) -> bool {
        false
    }

    /// Optional helper code to make this type work.
    /// This might include functions to patch a default value with another.
    #[allow(dead_code)]
    fn helper_code(&self, _oracle: &dyn CodeOracle) -> Option<String> {
        None
    }

    /// A list of imports that are needed if this type is in use.
    /// Classes are imported exactly once.
    fn imports(&self, _oracle: &dyn CodeOracle) -> Option<Vec<String>> {
        None
    }
}

pub trait LiteralRenderer {
    fn literal(
        &self,
        _oracle: &dyn CodeOracle,
        _typ: &TypeIdentifier,
        value: &Literal,
        ctx: &dyn Display,
    ) -> String;
}

impl<T, C> LiteralRenderer for T
where
    T: std::ops::Deref<Target = C>,
    C: LiteralRenderer,
{
    fn literal(
        &self,
        oracle: &dyn CodeOracle,
        typ: &TypeIdentifier,
        value: &Literal,
        ctx: &dyn Display,
    ) -> String {
        self.deref().literal(oracle, typ, value, ctx)
    }
}

/// A trait that is able to render a declaration about a particular member declared in
/// the `FeatureManifest`.
/// Like `CodeType`, it can render declaration code and imports.
/// All methods are optional, and there is no requirement that the trait be used for a particular
/// member. Thus, it can also be useful for conditionally rendering code.
pub trait CodeDeclaration {
    /// A list of imports that are needed if this type is in use.
    /// Classes are imported exactly once.
    fn imports(&self, _oracle: &dyn CodeOracle) -> Option<Vec<String>> {
        None
    }

    /// Code (one or more statements) that is run on start-up of the library,
    /// but before the client code has access to it.
    fn initialization_code(&self, _oracle: &dyn CodeOracle) -> Option<String> {
        None
    }

    /// Code which represents this member. e.g. the foreign language class definition for
    /// a given Object type.
    fn definition_code(&self, _oracle: &dyn CodeOracle) -> Option<String> {
        None
    }
}

/// The generated code is running against hand written code to give type safe, error free access to JSON.
/// This is the `Variables` object. This enum gives the underlying types that the `Variables` object supports.
pub enum VariablesType {
    Bool,
    Image,
    Int,
    String,
    Text,
    Variables,
}

/// The Variables objects use a naming convention to name its methods. e.g. `getBool`, `getBoolList`, `getBoolMap`.
/// In part this is to make generating code easier.
/// This is the mapping from type to identifier part that corresponds to its type.
impl Display for VariablesType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let nm = match self {
            VariablesType::Bool => "Bool",
            VariablesType::Image => "Image",
            VariablesType::Int => "Int",
            VariablesType::String => "String",
            VariablesType::Text => "Text",
            VariablesType::Variables => "Variables",
        };
        f.write_str(nm)
    }
}

pub(crate) mod experimenter_manifest;
pub(crate) mod frontend_manifest;
pub(crate) mod info;
pub(crate) mod kotlin;
pub(crate) mod swift;