Skip to main content

presolve_compiler/
form_ir.rs

1//! I12 instance-qualified projection of canonical Form plans.
2use crate::{
3    ComponentInstanceId, ComponentInstancePlan, FieldId, FormEntity, FormFieldDirtySlotId,
4    FormFieldEntity, FormFieldTouchedSlotId, FormFieldValidationSlotId, FormFieldValueSlotId,
5    FormId, FormInstanceId, FormSubmissionStateSlotId, FormValidationAggregateSlotId,
6};
7use std::collections::BTreeMap;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum FormIrOperation {
11    InitializeFormInstance,
12    InitializeFieldValue,
13    InitializeFieldDirty,
14    InitializeFieldTouched,
15    InitializeFieldValidation,
16    ReadControlChannel,
17    NormalizeControlValue,
18    WriteFieldValue,
19    WriteControlChannel,
20    ComputeDirty,
21    MarkTouched,
22    EvaluateValidationRule,
23    StoreValidationResult,
24    ComputeFormValidity,
25    SerializeForm,
26    BeginSubmission,
27    InvokeSubmissionAction,
28    CompleteSubmission,
29    FailSubmission,
30    ResetField,
31    ResetForm,
32    ClearValidation,
33}
34
35#[derive(Debug, Clone, PartialEq, Eq)]
36pub struct FormRuntimeStorage {
37    pub value: BTreeMap<FieldId, FormFieldValueSlotId>,
38    pub dirty: BTreeMap<FieldId, FormFieldDirtySlotId>,
39    pub touched: BTreeMap<FieldId, FormFieldTouchedSlotId>,
40    pub validation: BTreeMap<FieldId, FormFieldValidationSlotId>,
41    pub aggregate: FormValidationAggregateSlotId,
42    pub submission: FormSubmissionStateSlotId,
43}
44#[derive(Debug, Clone, PartialEq, Eq)]
45pub struct FormInstanceIr {
46    pub id: FormInstanceId,
47    pub form: FormId,
48    pub component_instance: ComponentInstanceId,
49    pub storage: FormRuntimeStorage,
50    pub initialize: Vec<FormIrOperation>,
51    pub input: BTreeMap<FieldId, Vec<FormIrOperation>>,
52    pub blur: BTreeMap<FieldId, Vec<FormIrOperation>>,
53    pub reset: Vec<FormIrOperation>,
54}
55#[derive(Debug, Clone, PartialEq, Eq, Default)]
56pub struct FormIrReport {
57    pub instances: BTreeMap<FormInstanceId, FormInstanceIr>,
58}
59
60#[must_use]
61#[allow(clippy::too_many_lines)]
62pub fn lower_form_ir(
63    instances: &ComponentInstancePlan,
64    forms: &BTreeMap<FormId, FormEntity>,
65    fields: &BTreeMap<FieldId, FormFieldEntity>,
66) -> FormIrReport {
67    let mut result = BTreeMap::new();
68    for component in instances.instances.values() {
69        for form in forms
70            .values()
71            .filter(|form| form.owner.entity_id() == Some(&component.component))
72        {
73            let id = FormInstanceId::for_component_instance(&component.id, &form.id);
74            let mut form_fields = fields
75                .values()
76                .filter(|field| field.owner_form == form.id)
77                .collect::<Vec<_>>();
78            form_fields
79                .sort_by(|a, b| (a.declaration_order, &a.id).cmp(&(b.declaration_order, &b.id)));
80            let storage = FormRuntimeStorage {
81                value: form_fields
82                    .iter()
83                    .map(|f| {
84                        (
85                            f.id.clone(),
86                            FormFieldValueSlotId::for_instance(&id, f.id.as_str()),
87                        )
88                    })
89                    .collect(),
90                dirty: form_fields
91                    .iter()
92                    .map(|f| {
93                        (
94                            f.id.clone(),
95                            FormFieldDirtySlotId::for_instance(&id, f.id.as_str()),
96                        )
97                    })
98                    .collect(),
99                touched: form_fields
100                    .iter()
101                    .map(|f| {
102                        (
103                            f.id.clone(),
104                            FormFieldTouchedSlotId::for_instance(&id, f.id.as_str()),
105                        )
106                    })
107                    .collect(),
108                validation: form_fields
109                    .iter()
110                    .map(|f| {
111                        (
112                            f.id.clone(),
113                            FormFieldValidationSlotId::for_instance(&id, f.id.as_str()),
114                        )
115                    })
116                    .collect(),
117                aggregate: FormValidationAggregateSlotId::for_instance(&id, "aggregate"),
118                submission: FormSubmissionStateSlotId::for_instance(&id, "submission"),
119            };
120            let input = form_fields
121                .iter()
122                .map(|f| {
123                    (
124                        f.id.clone(),
125                        vec![
126                            FormIrOperation::ReadControlChannel,
127                            FormIrOperation::NormalizeControlValue,
128                            FormIrOperation::WriteFieldValue,
129                            FormIrOperation::ComputeDirty,
130                            FormIrOperation::EvaluateValidationRule,
131                            FormIrOperation::StoreValidationResult,
132                            FormIrOperation::ComputeFormValidity,
133                            FormIrOperation::WriteControlChannel,
134                        ],
135                    )
136                })
137                .collect();
138            let blur = form_fields
139                .iter()
140                .map(|f| {
141                    (
142                        f.id.clone(),
143                        vec![
144                            FormIrOperation::MarkTouched,
145                            FormIrOperation::EvaluateValidationRule,
146                            FormIrOperation::ComputeFormValidity,
147                        ],
148                    )
149                })
150                .collect();
151            result.insert(
152                id.clone(),
153                FormInstanceIr {
154                    id,
155                    form: form.id.clone(),
156                    component_instance: component.id.clone(),
157                    storage,
158                    initialize: vec![FormIrOperation::InitializeFormInstance],
159                    input,
160                    blur,
161                    reset: vec![
162                        FormIrOperation::ResetField,
163                        FormIrOperation::ClearValidation,
164                        FormIrOperation::ResetForm,
165                    ],
166                },
167            );
168        }
169    }
170    FormIrReport { instances: result }
171}
172
173#[cfg(test)]
174mod tests {
175    #[test]
176    fn repeats_form_storage_per_component_instance() {
177        let parsed = presolve_parser::parse_file(
178            "src/X.tsx",
179            r#"@component("x") class X { @form() form!: Form; @field(this.form) value=""; render(){return <input field={this.value}/>;} }"#,
180        );
181        let model = crate::build_application_semantic_model(&parsed);
182        assert_eq!(
183            model.form_ir.instances.len(),
184            model.component_instance_plan.instances.len()
185        );
186        assert!(model
187            .form_ir
188            .instances
189            .values()
190            .all(|instance| !instance.storage.value.is_empty()));
191    }
192}