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;