nimbus_fml/backends/mod.rs
1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5//! # Backend traits
6//!
7//! This module provides a number of traits useful for implementing a backend for FML structs.
8//!
9//! A [CodeType] is needed for each type that is referred to in the feature definition (i.e. every [TypeRef]
10//! instance should have corresponding `CodeType` instance). Helper code for types might include managing how merging/overriding of
11//! defaults occur.
12//!
13//! A [CodeDeclaration] is needed for each type that is declared in the manifest file: i.e. an Object classes, Enum classes and Feature classes.
14//! This has access to intermediate structs of the [crate::intermediate_representation::FeatureManifest] so may want to do some additional lookups to help rendering.
15//!
16//! `CodeDeclaration`s provide the target language's version of the type defined in the feature manifest. For objects and features, this would
17//! be objects that have properties corresponding to the FML variables. For enums, this would mean the Enum class definition. In all cases, this will
18//! likely be attached to an [askama::Template].
19//!
20//! `CodeDeclaration`s can also be used to conditionally include code: e.g. only include the CallbackInterfaceRuntime
21//! if the user has used at least one callback interface.
22//!
23//! Each backend has a wrapper template for each file it needs to generate. This should collect the `CodeDeclaration`s that
24//! the backend and `FeatureManifest` between them specify and use them to stitch together a file in the target language.
25//!
26//! The [CodeOracle] provides methods to map the `TypeRef` values found in the `FeatureManifest` to the `CodeType`s specified
27//! by the backend.
28//!
29//! Each backend will have its own `filter` module, which is used by the askama templates used in all `CodeType`s and `CodeDeclaration`s.
30//! This filter provides methods to generate expressions and identifiers in the target language. These are all forwarded to the oracle.
31
32use std::fmt::Display;
33
34use crate::intermediate_representation::Literal;
35use crate::intermediate_representation::TypeRef;
36
37pub type TypeIdentifier = TypeRef;
38
39/// An object to look up a foreign language code specific renderer for a given type used.
40/// Every [TypeRef] referred to in the [crate::intermediate_representation::FeatureManifest] should map to a corresponding
41/// `CodeType`.
42///
43/// The mapping may be opaque, but the oracle always knows the answer.
44pub trait CodeOracle {
45 fn find(&self, type_: &TypeIdentifier) -> Box<dyn CodeType>;
46}
47
48/// A Trait to emit foreign language code to handle referenced types.
49/// A type which is specified in the FML (i.e. a type that a variable declares itself of)
50/// will have a `CodeDeclaration` as well, but for types used e.g. primitive types, Strings, etc
51/// only a `CodeType` is needed.
52///
53/// This includes generating an literal of the type from the right type of JSON and
54/// expressions to get a property from the JSON backed `Variables` object.
55pub trait CodeType {
56 /// The language specific label used to reference this type. This will be used in
57 /// method signatures and property declarations.
58 fn type_label(&self, oracle: &dyn CodeOracle) -> String;
59
60 /// The language specific expression that gets a value of the `prop` from the `vars` object,
61 /// and fallbacks to the `default` value.
62 ///
63 /// /// All the propertis follow this general pattern:
64 ///
65 /// ```kt
66 /// variables?.{{ value_getter }}
67 /// ?.{{ value_mapper }}
68 /// ?.{{ value_merger }}
69 /// ?: {{ default_fallback}}
70 /// ```
71 ///
72 /// In the case of structural types and objects, `value_mapper` and `value_merger`
73 /// become mutually recursive to generate quite complicated properties.
74 ///
75 fn property_getter(
76 &self,
77 oracle: &dyn CodeOracle,
78 vars: &dyn Display,
79 prop: &dyn Display,
80 default: &dyn Display,
81 ) -> String;
82
83 /// The expression needed to get a value out of a `Variables` objectm with the `prop` key.
84 ///
85 /// This will almost certainly use the `variables_type` method to determine which method to use.
86 /// e.g. `vars?.getString("prop")`
87 ///
88 /// The `value_mapper` will be used to transform this value into the required value.
89 fn value_getter(
90 &self,
91 oracle: &dyn CodeOracle,
92 vars: &dyn Display,
93 prop: &dyn Display,
94 ) -> String;
95
96 /// The method call here will use the `create_transform` to transform the value coming out of
97 /// the `Variables` object into the desired type.
98 ///
99 /// e.g. a string will need to be transformed into an enum, so the value mapper in Kotlin will be
100 /// `let(Enum::enumValue)`.
101 ///
102 /// If the value is `None`, then no mapper is used.
103 fn value_mapper(&self, _oracle: &dyn CodeOracle) -> Option<String> {
104 None
105 }
106
107 /// The method call to merge the value with the defaults.
108 ///
109 /// This may use the `merge_transform`.
110 ///
111 /// If this returns `None`, no merging happens, and implicit `null` replacement happens.
112 fn value_merger(&self, _oracle: &dyn CodeOracle, _default: &dyn Display) -> Option<String> {
113 None
114 }
115
116 /// The name of the type as it's represented in the `Variables` object.
117 /// The string return may be used to combine with an identifier, e.g. a `Variables` method name.
118 fn variables_type(&self, _oracle: &dyn CodeOracle) -> VariablesType;
119
120 /// A function handle that is capable of turning the variables type to the TypeRef type.
121 fn create_transform(&self, _oracle: &dyn CodeOracle) -> Option<String> {
122 None
123 }
124
125 /// A function handle that is capable of merging two instances of the same class. By default, this is None.
126 fn merge_transform(&self, _oracle: &dyn CodeOracle) -> Option<String> {
127 None
128 }
129
130 // The foreign language type for how default values are stored in the `Defaults` object.
131 // This is usually the same as the type_label itself, but occasionally— e.g. for bundled resources—
132 // this will be different.
133 // If it is different, then a `defaults_mapper` is needed to map between the `defaults_type` and the
134 // `type_label` type.
135 fn defaults_type(&self, oracle: &dyn CodeOracle) -> String {
136 self.type_label(oracle)
137 }
138
139 fn defaults_mapper(
140 &self,
141 _oracle: &dyn CodeOracle,
142 _value: &dyn Display,
143 _vars: &dyn Display,
144 ) -> Option<String> {
145 None
146 }
147
148 fn preference_getter(
149 &self,
150 _oracle: &dyn CodeOracle,
151 _prefs: &dyn Display,
152 _pref_key: &dyn Display,
153 ) -> Option<String> {
154 None
155 }
156
157 /// Call from the template
158 fn as_json(&self, oracle: &dyn CodeOracle, prop: &dyn Display) -> String {
159 self.as_json_transform(oracle, prop)
160 .unwrap_or_else(|| prop.to_string())
161 }
162
163 /// Implement these in different code types, and call recursively from different code types.
164 fn as_json_transform(&self, _oracle: &dyn CodeOracle, _prop: &dyn Display) -> Option<String> {
165 None
166 }
167
168 /// A representation of the given literal for this type.
169 /// N.B. `Literal` is aliased from `serde_json::Value`.
170 fn literal(
171 &self,
172 oracle: &dyn CodeOracle,
173 ctx: &dyn Display,
174 renderer: &dyn LiteralRenderer,
175 literal: &Literal,
176 ) -> String;
177
178 fn is_resource_id(&self, _literal: &Literal) -> bool {
179 false
180 }
181
182 /// Optional helper code to make this type work.
183 /// This might include functions to patch a default value with another.
184 #[allow(dead_code)]
185 fn helper_code(&self, _oracle: &dyn CodeOracle) -> Option<String> {
186 None
187 }
188
189 /// A list of imports that are needed if this type is in use.
190 /// Classes are imported exactly once.
191 fn imports(&self, _oracle: &dyn CodeOracle) -> Option<Vec<String>> {
192 None
193 }
194}
195
196pub trait LiteralRenderer {
197 fn literal(
198 &self,
199 _oracle: &dyn CodeOracle,
200 _typ: &TypeIdentifier,
201 value: &Literal,
202 ctx: &dyn Display,
203 ) -> String;
204}
205
206impl<T, C> LiteralRenderer for T
207where
208 T: std::ops::Deref<Target = C>,
209 C: LiteralRenderer,
210{
211 fn literal(
212 &self,
213 oracle: &dyn CodeOracle,
214 typ: &TypeIdentifier,
215 value: &Literal,
216 ctx: &dyn Display,
217 ) -> String {
218 self.deref().literal(oracle, typ, value, ctx)
219 }
220}
221
222/// A trait that is able to render a declaration about a particular member declared in
223/// the `FeatureManifest`.
224/// Like `CodeType`, it can render declaration code and imports.
225/// All methods are optional, and there is no requirement that the trait be used for a particular
226/// member. Thus, it can also be useful for conditionally rendering code.
227pub trait CodeDeclaration {
228 /// A list of imports that are needed if this type is in use.
229 /// Classes are imported exactly once.
230 fn imports(&self, _oracle: &dyn CodeOracle) -> Option<Vec<String>> {
231 None
232 }
233
234 /// Code (one or more statements) that is run on start-up of the library,
235 /// but before the client code has access to it.
236 fn initialization_code(&self, _oracle: &dyn CodeOracle) -> Option<String> {
237 None
238 }
239
240 /// Code which represents this member. e.g. the foreign language class definition for
241 /// a given Object type.
242 fn definition_code(&self, _oracle: &dyn CodeOracle) -> Option<String> {
243 None
244 }
245}
246
247/// The generated code is running against hand written code to give type safe, error free access to JSON.
248/// This is the `Variables` object. This enum gives the underlying types that the `Variables` object supports.
249pub enum VariablesType {
250 Bool,
251 Image,
252 Int,
253 String,
254 Text,
255 Variables,
256}
257
258/// The Variables objects use a naming convention to name its methods. e.g. `getBool`, `getBoolList`, `getBoolMap`.
259/// In part this is to make generating code easier.
260/// This is the mapping from type to identifier part that corresponds to its type.
261impl Display for VariablesType {
262 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
263 let nm = match self {
264 VariablesType::Bool => "Bool",
265 VariablesType::Image => "Image",
266 VariablesType::Int => "Int",
267 VariablesType::String => "String",
268 VariablesType::Text => "Text",
269 VariablesType::Variables => "Variables",
270 };
271 f.write_str(nm)
272 }
273}
274
275pub(crate) mod experimenter_manifest;
276pub(crate) mod frontend_manifest;
277pub(crate) mod info;
278pub(crate) mod kotlin;
279pub(crate) mod swift;