nimbus_fml/backends/swift/gen_structs/
object.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
5use askama::Template;
6use std::fmt::Display;
7
8use crate::backends::{
9    CodeDeclaration, CodeOracle, CodeType, LiteralRenderer, TypeIdentifier, VariablesType,
10};
11use crate::intermediate_representation::{FeatureManifest, Literal, ObjectDef};
12
13use super::filters;
14
15use super::common::{self, code_type};
16
17pub struct ObjectRuntime;
18
19impl CodeDeclaration for ObjectRuntime {}
20
21pub struct ObjectCodeType {
22    id: String,
23}
24
25impl ObjectCodeType {
26    pub fn new(id: String) -> Self {
27        Self { id }
28    }
29}
30
31impl CodeType for ObjectCodeType {
32    fn type_label(&self, _oracle: &dyn CodeOracle) -> String {
33        common::class_name(&self.id)
34    }
35
36    fn property_getter(
37        &self,
38        oracle: &dyn CodeOracle,
39        vars: &dyn Display,
40        prop: &dyn Display,
41        default: &dyn Display,
42    ) -> String {
43        code_type::property_getter(self, oracle, vars, prop, default)
44    }
45
46    fn value_getter(
47        &self,
48        oracle: &dyn CodeOracle,
49        vars: &dyn Display,
50        prop: &dyn Display,
51    ) -> String {
52        code_type::value_getter(self, oracle, vars, prop)
53    }
54
55    fn value_mapper(&self, oracle: &dyn CodeOracle) -> Option<String> {
56        let transform = self.create_transform(oracle)?;
57        Some(format!("map({})", transform))
58    }
59
60    /// The language specific expression that gets a value of the `prop` from the `vars` object.
61    ///
62    /// The name of the type as it's represented in the `Variables` object.
63    /// The string return may be used to combine with an identifier, e.g. a `Variables` method name.
64    fn variables_type(&self, _oracle: &dyn CodeOracle) -> VariablesType {
65        VariablesType::Variables
66    }
67
68    fn create_transform(&self, oracle: &dyn CodeOracle) -> Option<String> {
69        Some(format!("{}.create", self.type_label(oracle)))
70    }
71
72    fn merge_transform(&self, oracle: &dyn CodeOracle) -> Option<String> {
73        Some(format!("{}.mergeWith", self.type_label(oracle)))
74    }
75
76    fn value_merger(&self, _oracle: &dyn CodeOracle, default: &dyn Display) -> Option<String> {
77        Some(format!("_mergeWith({})", default))
78    }
79
80    fn literal(
81        &self,
82        oracle: &dyn CodeOracle,
83        ctx: &dyn Display,
84        renderer: &dyn LiteralRenderer,
85        literal: &Literal,
86    ) -> String {
87        renderer.literal(
88            oracle,
89            &TypeIdentifier::Object(self.id.clone()),
90            literal,
91            ctx,
92        )
93    }
94}
95
96#[derive(Template)]
97#[template(syntax = "swift", escape = "none", path = "ObjectTemplate.swift")]
98pub(crate) struct ObjectCodeDeclaration {
99    inner: ObjectDef,
100    fm: FeatureManifest,
101}
102
103impl ObjectCodeDeclaration {
104    pub fn new(fm: &FeatureManifest, inner: &ObjectDef) -> Self {
105        Self {
106            fm: fm.clone(),
107            inner: inner.clone(),
108        }
109    }
110    pub fn inner(&self) -> ObjectDef {
111        self.inner.clone()
112    }
113}
114
115impl CodeDeclaration for ObjectCodeDeclaration {
116    fn definition_code(&self, _oracle: &dyn CodeOracle) -> Option<String> {
117        Some(self.render().unwrap())
118    }
119}
120
121impl LiteralRenderer for ObjectCodeDeclaration {
122    fn literal(
123        &self,
124        oracle: &dyn CodeOracle,
125        typ: &TypeIdentifier,
126        value: &Literal,
127        ctx: &dyn Display,
128    ) -> String {
129        object_literal(&self.fm, &self, oracle, typ, value, ctx)
130    }
131}
132
133pub(crate) fn object_literal(
134    fm: &FeatureManifest,
135    renderer: &dyn LiteralRenderer,
136    oracle: &dyn CodeOracle,
137    typ: &TypeIdentifier,
138    value: &Literal,
139    ctx: &dyn Display,
140) -> String {
141    let id = if let TypeIdentifier::Object(id) = typ {
142        id
143    } else {
144        return oracle.find(typ).literal(oracle, ctx, renderer, value);
145    };
146    let literal_map = if let Literal::Object(map) = value {
147        map
148    } else {
149        unreachable!(
150            "An JSON object is expected for {} object literal",
151            oracle.find(typ).type_label(oracle)
152        )
153    };
154
155    let def = fm.find_object(id).unwrap();
156    let args: Vec<String> = def
157        .props()
158        .iter()
159        .filter_map(|prop_def| {
160            literal_map.get(&prop_def.name()).map(|v| {
161                format!(
162                    "{var_name}: {var_value}",
163                    var_name = common::var_name(&prop_def.name()),
164                    var_value = oracle.find(&prop_def.typ).literal(oracle, ctx, renderer, v)
165                )
166            })
167        })
168        .collect();
169
170    format!(
171        "{typelabel}({args})",
172        typelabel = oracle.find(typ).type_label(oracle),
173        args = args.join(", ")
174    )
175}
176
177#[cfg(test)]
178mod unit_tests {
179    use serde_json::json;
180
181    use crate::{backends::TypeIdentifier, intermediate_representation::Literal};
182
183    use super::*;
184
185    struct TestCodeOracle;
186    impl CodeOracle for TestCodeOracle {
187        fn find(&self, _type_: &TypeIdentifier) -> Box<dyn CodeType> {
188            unreachable!()
189        }
190    }
191
192    struct TestRenderer;
193    impl LiteralRenderer for TestRenderer {
194        fn literal(
195            &self,
196            _oracle: &dyn CodeOracle,
197            typ: &TypeIdentifier,
198            _value: &Literal,
199            _ctx: &dyn Display,
200        ) -> String {
201            if let TypeIdentifier::Object(nm) = typ {
202                format!("{}()", nm)
203            } else {
204                unreachable!()
205            }
206        }
207    }
208
209    fn oracle() -> Box<dyn CodeOracle> {
210        Box::new(TestCodeOracle) as Box<dyn CodeOracle>
211    }
212
213    fn code_type(name: &str) -> Box<dyn CodeType> {
214        Box::new(ObjectCodeType::new(name.to_string())) as Box<dyn CodeType>
215    }
216
217    fn getter_with_fallback(
218        ct: &dyn CodeType,
219        vars: &dyn Display,
220        prop: &dyn Display,
221        def: &dyn Display,
222    ) -> String {
223        let oracle = &*oracle();
224        ct.property_getter(oracle, vars, prop, def)
225    }
226
227    #[test]
228    fn test_type_label() {
229        let ct = code_type("AnObject");
230        let oracle = &*oracle();
231        assert_eq!("AnObject".to_string(), ct.type_label(oracle))
232    }
233
234    #[test]
235    fn test_literal() {
236        let ct = code_type("AnObject");
237        let oracle = &*oracle();
238        let finder = &TestRenderer;
239        let ctx = String::from("ctx");
240        assert_eq!(
241            "AnObject()".to_string(),
242            ct.literal(oracle, &ctx, finder, &json!({}))
243        );
244    }
245
246    #[test]
247    fn test_get_value() {
248        let ct = code_type("AnObject");
249        let oracle = &*oracle();
250
251        assert_eq!(
252            r#"v.getVariables("the-property")"#.to_string(),
253            ct.value_getter(oracle, &"v", &"the-property")
254        );
255    }
256
257    #[test]
258    fn test_getter_with_fallback() {
259        let ct = code_type("AnObject");
260        assert_eq!(
261            r#"vars.getVariables("the-property")?.map(AnObject.create)._mergeWith(default) ?? default"#.to_string(),
262            getter_with_fallback(&*ct, &"vars", &"the-property", &"default")
263        );
264    }
265}