Skip to main content

engine_input_producers/
expression_semantics.rs

1use std::collections::BTreeMap;
2
3use crate::{
4    EngineInputV2, ExpressionSemanticsCandidateV0, ExpressionSemanticsCandidatesV0,
5    ExpressionSemanticsCanonicalCandidateBundleV0, ExpressionSemanticsCanonicalProducerSignalV0,
6    ExpressionSemanticsEvaluatorCandidatePayloadV0, ExpressionSemanticsEvaluatorCandidateV0,
7    ExpressionSemanticsEvaluatorCandidatesV0, ExpressionSemanticsFragmentV0,
8    ExpressionSemanticsFragmentsV0, ExpressionSemanticsMatchFragmentV0,
9    ExpressionSemanticsMatchFragmentsV0, ExpressionSemanticsQueryFragmentV0,
10    ExpressionSemanticsQueryFragmentsV0, canonical_selector_count, finite_values_for_facts,
11    map_expression_value_domain_kind, map_reduced_expression_value_domain_derivation,
12    map_reduced_expression_value_domain_provenance_tree, map_selector_certainty,
13    map_selector_certainty_shape_kind, map_selector_certainty_shape_label, map_value_certainty,
14    map_value_certainty_shape_kind, map_value_certainty_shape_label, resolve_selector_names,
15};
16
17struct ExpressionSemanticsInputRows {
18    query_fragments: Vec<ExpressionSemanticsQueryFragmentV0>,
19    fragments: Vec<ExpressionSemanticsFragmentV0>,
20    match_fragments: Vec<ExpressionSemanticsMatchFragmentV0>,
21    candidates: Vec<ExpressionSemanticsCandidateV0>,
22    evaluator_candidates: Vec<ExpressionSemanticsEvaluatorCandidateV0>,
23}
24
25fn collect_expression_semantics_input_rows(input: &EngineInputV2) -> ExpressionSemanticsInputRows {
26    let mut expression_index = BTreeMap::new();
27    let mut style_index = BTreeMap::new();
28    let mut query_fragments = Vec::new();
29
30    for source in &input.sources {
31        for expression in &source.document.class_expressions {
32            expression_index.insert(expression.id.clone(), expression);
33            query_fragments.push(ExpressionSemanticsQueryFragmentV0 {
34                query_id: expression.id.clone(),
35                expression_id: expression.id.clone(),
36                expression_kind: expression.kind.clone(),
37                style_file_path: expression.scss_module_path.clone(),
38            });
39        }
40    }
41
42    for style in &input.styles {
43        style_index.insert(style.file_path.clone(), style);
44    }
45
46    let mut fragments = Vec::new();
47    let mut match_fragments = Vec::new();
48    let mut candidates = Vec::new();
49    let mut evaluator_candidates = Vec::new();
50
51    for entry in &input.type_facts {
52        let Some(expression) = expression_index.get(&entry.expression_id) else {
53            continue;
54        };
55        let Some(style) = style_index.get(&expression.scss_module_path) else {
56            continue;
57        };
58
59        let selector_names = resolve_selector_names(style, &entry.facts);
60        let finite_values = finite_values_for_facts(&entry.facts);
61        let candidate_names = finite_values
62            .clone()
63            .unwrap_or_else(|| selector_names.clone());
64        let selector_certainty = map_selector_certainty(
65            &entry.facts,
66            selector_names.len(),
67            canonical_selector_count(style),
68        );
69        let value_certainty = map_value_certainty(&entry.facts);
70        let selector_certainty_shape_label = map_selector_certainty_shape_label(
71            &entry.facts,
72            selector_names.len(),
73            canonical_selector_count(style),
74        );
75        let selector_certainty_shape_kind = map_selector_certainty_shape_kind(
76            &entry.facts,
77            selector_names.len(),
78            canonical_selector_count(style),
79        );
80        let value_certainty_shape_kind = map_value_certainty_shape_kind(&entry.facts);
81        let value_certainty_shape_label = map_value_certainty_shape_label(&entry.facts);
82
83        fragments.push(ExpressionSemanticsFragmentV0 {
84            query_id: entry.expression_id.clone(),
85            expression_id: entry.expression_id.clone(),
86            expression_kind: expression.kind.clone(),
87            style_file_path: expression.scss_module_path.clone(),
88            value_domain_kind: map_expression_value_domain_kind(&entry.facts),
89            value_constraint_kind: entry.facts.constraint_kind.clone(),
90            value_prefix: entry.facts.prefix.clone(),
91            value_suffix: entry.facts.suffix.clone(),
92            value_min_len: entry.facts.min_len,
93            value_max_len: entry.facts.max_len,
94            value_char_must: entry.facts.char_must.clone(),
95            value_char_may: entry.facts.char_may.clone(),
96            value_may_include_other_chars: entry.facts.may_include_other_chars,
97        });
98
99        match_fragments.push(ExpressionSemanticsMatchFragmentV0 {
100            query_id: entry.expression_id.clone(),
101            expression_id: entry.expression_id.clone(),
102            style_file_path: expression.scss_module_path.clone(),
103            selector_names: selector_names.clone(),
104            candidate_names: candidate_names.clone(),
105            finite_values: finite_values.clone(),
106        });
107
108        let candidate = ExpressionSemanticsCandidateV0 {
109            query_id: entry.expression_id.clone(),
110            expression_id: entry.expression_id.clone(),
111            expression_kind: expression.kind.clone(),
112            style_file_path: expression.scss_module_path.clone(),
113            selector_names,
114            candidate_names,
115            finite_values,
116            value_domain_kind: map_expression_value_domain_kind(&entry.facts),
117            selector_certainty,
118            value_certainty,
119            selector_certainty_shape_kind,
120            selector_certainty_shape_label,
121            value_certainty_shape_kind,
122            value_certainty_shape_label,
123            selector_constraint_kind: entry.facts.constraint_kind.clone(),
124            value_certainty_constraint_kind: entry.facts.constraint_kind.clone(),
125            value_constraint_kind: entry.facts.constraint_kind.clone(),
126            value_prefix: entry.facts.prefix.clone(),
127            value_suffix: entry.facts.suffix.clone(),
128            value_min_len: entry.facts.min_len,
129            value_max_len: entry.facts.max_len,
130            value_char_must: entry.facts.char_must.clone(),
131            value_char_may: entry.facts.char_may.clone(),
132            value_may_include_other_chars: entry.facts.may_include_other_chars,
133        };
134
135        candidates.push(candidate.clone());
136
137        evaluator_candidates.push(ExpressionSemanticsEvaluatorCandidateV0 {
138            kind: "expression-semantics",
139            file_path: entry.file_path.clone(),
140            query_id: entry.expression_id.clone(),
141            payload: ExpressionSemanticsEvaluatorCandidatePayloadV0 {
142                expression_id: entry.expression_id.clone(),
143                expression_kind: candidate.expression_kind.clone(),
144                style_file_path: candidate.style_file_path.clone(),
145                selector_names: candidate.selector_names.clone(),
146                candidate_names: candidate.candidate_names.clone(),
147                finite_values: candidate.finite_values.clone(),
148                value_domain_kind: candidate.value_domain_kind.clone(),
149                selector_certainty: candidate.selector_certainty.clone(),
150                value_certainty: candidate.value_certainty.clone(),
151                selector_certainty_shape_kind: candidate.selector_certainty_shape_kind.clone(),
152                selector_certainty_shape_label: candidate.selector_certainty_shape_label.clone(),
153                value_certainty_shape_kind: candidate.value_certainty_shape_kind.clone(),
154                value_certainty_shape_label: candidate.value_certainty_shape_label.clone(),
155                selector_constraint_kind: candidate.selector_constraint_kind.clone(),
156                value_certainty_constraint_kind: candidate.value_certainty_constraint_kind.clone(),
157                value_constraint_kind: candidate.value_constraint_kind.clone(),
158                value_prefix: candidate.value_prefix.clone(),
159                value_suffix: candidate.value_suffix.clone(),
160                value_min_len: candidate.value_min_len,
161                value_max_len: candidate.value_max_len,
162                value_char_must: candidate.value_char_must.clone(),
163                value_char_may: candidate.value_char_may.clone(),
164                value_may_include_other_chars: candidate.value_may_include_other_chars,
165                value_domain_derivation: map_reduced_expression_value_domain_derivation(
166                    &entry.facts,
167                ),
168                value_domain_provenance_tree: map_reduced_expression_value_domain_provenance_tree(
169                    &entry.facts,
170                ),
171            },
172        });
173    }
174
175    query_fragments.sort_by(|a, b| a.query_id.cmp(&b.query_id));
176    fragments.sort_by(|a, b| a.query_id.cmp(&b.query_id));
177    match_fragments.sort_by(|a, b| a.query_id.cmp(&b.query_id));
178    candidates.sort_by(|a, b| a.query_id.cmp(&b.query_id));
179    evaluator_candidates.sort_by(|a, b| a.query_id.cmp(&b.query_id));
180
181    ExpressionSemanticsInputRows {
182        query_fragments,
183        fragments,
184        match_fragments,
185        candidates,
186        evaluator_candidates,
187    }
188}
189
190pub fn summarize_expression_semantics_canonical_candidate_bundle_input(
191    input: &EngineInputV2,
192) -> ExpressionSemanticsCanonicalCandidateBundleV0 {
193    let rows = collect_expression_semantics_input_rows(input);
194
195    ExpressionSemanticsCanonicalCandidateBundleV0 {
196        schema_version: "0",
197        input_version: input.version.clone(),
198        query_fragments: rows.query_fragments,
199        fragments: rows.fragments,
200        match_fragments: rows.match_fragments,
201        candidates: rows.candidates,
202    }
203}
204
205pub fn summarize_expression_semantics_candidates_input(
206    input: &EngineInputV2,
207) -> ExpressionSemanticsCandidatesV0 {
208    let rows = collect_expression_semantics_input_rows(input);
209
210    ExpressionSemanticsCandidatesV0 {
211        schema_version: "0",
212        input_version: input.version.clone(),
213        candidates: rows.candidates,
214    }
215}
216
217pub fn summarize_expression_semantics_evaluator_candidates_input(
218    input: &EngineInputV2,
219) -> ExpressionSemanticsEvaluatorCandidatesV0 {
220    let rows = collect_expression_semantics_input_rows(input);
221
222    ExpressionSemanticsEvaluatorCandidatesV0 {
223        schema_version: "0",
224        input_version: input.version.clone(),
225        results: rows.evaluator_candidates,
226    }
227}
228
229pub fn summarize_expression_semantics_canonical_producer_signal_input(
230    input: &EngineInputV2,
231) -> ExpressionSemanticsCanonicalProducerSignalV0 {
232    let rows = collect_expression_semantics_input_rows(input);
233    let input_version = input.version.clone();
234
235    ExpressionSemanticsCanonicalProducerSignalV0 {
236        schema_version: "0",
237        input_version: input_version.clone(),
238        canonical_bundle: ExpressionSemanticsCanonicalCandidateBundleV0 {
239            schema_version: "0",
240            input_version: input_version.clone(),
241            query_fragments: rows.query_fragments.clone(),
242            fragments: rows.fragments.clone(),
243            match_fragments: rows.match_fragments.clone(),
244            candidates: rows.candidates.clone(),
245        },
246        evaluator_candidates: ExpressionSemanticsEvaluatorCandidatesV0 {
247            schema_version: "0",
248            input_version,
249            results: rows.evaluator_candidates,
250        },
251    }
252}
253
254pub fn summarize_expression_semantics_fragments_input(
255    input: &EngineInputV2,
256) -> ExpressionSemanticsFragmentsV0 {
257    let rows = collect_expression_semantics_input_rows(input);
258
259    ExpressionSemanticsFragmentsV0 {
260        schema_version: "0",
261        input_version: input.version.clone(),
262        fragments: rows.fragments,
263    }
264}
265
266pub fn summarize_expression_semantics_query_fragments_input(
267    input: &EngineInputV2,
268) -> ExpressionSemanticsQueryFragmentsV0 {
269    let rows = collect_expression_semantics_input_rows(input);
270
271    ExpressionSemanticsQueryFragmentsV0 {
272        schema_version: "0",
273        input_version: input.version.clone(),
274        fragments: rows.query_fragments,
275    }
276}
277
278pub fn summarize_expression_semantics_match_fragments_input(
279    input: &EngineInputV2,
280) -> ExpressionSemanticsMatchFragmentsV0 {
281    let rows = collect_expression_semantics_input_rows(input);
282
283    ExpressionSemanticsMatchFragmentsV0 {
284        schema_version: "0",
285        input_version: input.version.clone(),
286        fragments: rows.match_fragments,
287    }
288}
289
290#[cfg(test)]
291mod tests {
292    use super::{
293        summarize_expression_semantics_candidates_input,
294        summarize_expression_semantics_canonical_candidate_bundle_input,
295        summarize_expression_semantics_canonical_producer_signal_input,
296        summarize_expression_semantics_evaluator_candidates_input,
297        summarize_expression_semantics_fragments_input,
298        summarize_expression_semantics_match_fragments_input,
299        summarize_expression_semantics_query_fragments_input,
300    };
301    use crate::test_support::sample_input;
302
303    #[test]
304    fn builds_expression_semantics_fragment_from_type_fact() {
305        let summary = summarize_expression_semantics_fragments_input(&sample_input());
306
307        assert_eq!(summary.fragments.len(), 2);
308        let fragment = &summary.fragments[0];
309        assert_eq!(fragment.query_id, "expr-1");
310        assert_eq!(fragment.expression_id, "expr-1");
311        assert_eq!(fragment.expression_kind, "symbolRef");
312        assert_eq!(fragment.style_file_path, "/tmp/App.module.scss");
313        assert_eq!(fragment.value_domain_kind, "constrained");
314        assert_eq!(
315            fragment.value_constraint_kind.as_deref(),
316            Some("prefixSuffix")
317        );
318        assert_eq!(fragment.value_prefix.as_deref(), Some("btn-"));
319        assert_eq!(fragment.value_suffix.as_deref(), Some("-active"));
320        assert_eq!(fragment.value_min_len, Some(10));
321
322        let second = &summary.fragments[1];
323        assert_eq!(second.query_id, "expr-2");
324        assert_eq!(second.value_domain_kind, "finiteSet");
325    }
326
327    #[test]
328    fn builds_expression_semantics_query_fragments_from_input() {
329        let summary = summarize_expression_semantics_query_fragments_input(&sample_input());
330
331        assert_eq!(summary.fragments.len(), 2);
332        let first = &summary.fragments[0];
333        assert_eq!(first.query_id, "expr-1");
334        assert_eq!(first.expression_id, "expr-1");
335        assert_eq!(first.expression_kind, "symbolRef");
336        assert_eq!(first.style_file_path, "/tmp/App.module.scss");
337
338        let second = &summary.fragments[1];
339        assert_eq!(second.query_id, "expr-2");
340        assert_eq!(second.expression_kind, "styleAccess");
341        assert_eq!(second.style_file_path, "/tmp/Card.module.scss");
342    }
343
344    #[test]
345    fn builds_expression_semantics_match_fragments_from_input() {
346        let summary = summarize_expression_semantics_match_fragments_input(&sample_input());
347
348        assert_eq!(summary.fragments.len(), 2);
349        let first = &summary.fragments[0];
350        assert_eq!(first.query_id, "expr-1");
351        assert_eq!(first.expression_id, "expr-1");
352        assert_eq!(first.style_file_path, "/tmp/App.module.scss");
353        assert_eq!(first.selector_names, vec!["btn-active".to_string()]);
354        assert_eq!(first.candidate_names, vec!["btn-active".to_string()]);
355        assert!(first.finite_values.is_none());
356
357        let second = &summary.fragments[1];
358        assert_eq!(second.query_id, "expr-2");
359        assert_eq!(second.selector_names, vec!["card-header".to_string()]);
360        assert_eq!(
361            second.candidate_names,
362            vec!["card-header".to_string(), "card-body".to_string()]
363        );
364        assert_eq!(
365            second.finite_values,
366            Some(vec!["card-header".to_string(), "card-body".to_string()])
367        );
368    }
369
370    #[test]
371    fn builds_expression_semantics_candidates_from_input() {
372        let summary = summarize_expression_semantics_candidates_input(&sample_input());
373
374        assert_eq!(summary.candidates.len(), 2);
375        let first = &summary.candidates[0];
376        assert_eq!(first.query_id, "expr-1");
377        assert_eq!(first.expression_kind, "symbolRef");
378        assert_eq!(first.style_file_path, "/tmp/App.module.scss");
379        assert_eq!(first.selector_names, vec!["btn-active".to_string()]);
380        assert_eq!(first.candidate_names, vec!["btn-active".to_string()]);
381        assert_eq!(first.value_domain_kind, "constrained");
382        assert_eq!(first.selector_certainty, "inferred");
383        assert_eq!(first.value_certainty.as_deref(), Some("inferred"));
384        assert_eq!(first.selector_certainty_shape_kind, "constrained");
385        assert_eq!(
386            first.selector_certainty_shape_label,
387            "constrained edge selector set (1)"
388        );
389        assert_eq!(first.value_certainty_shape_kind, "constrained");
390        assert_eq!(
391            first.value_certainty_shape_label,
392            "constrained prefix `btn-` + suffix `-active`"
393        );
394        assert_eq!(
395            first.selector_constraint_kind.as_deref(),
396            Some("prefixSuffix")
397        );
398        assert_eq!(
399            first.value_certainty_constraint_kind.as_deref(),
400            Some("prefixSuffix")
401        );
402        assert_eq!(first.value_constraint_kind.as_deref(), Some("prefixSuffix"));
403        assert_eq!(first.value_prefix.as_deref(), Some("btn-"));
404        assert_eq!(first.value_suffix.as_deref(), Some("-active"));
405
406        let second = &summary.candidates[1];
407        assert_eq!(second.query_id, "expr-2");
408        assert_eq!(second.expression_kind, "styleAccess");
409        assert_eq!(second.selector_names, vec!["card-header".to_string()]);
410        assert_eq!(
411            second.finite_values,
412            Some(vec!["card-header".to_string(), "card-body".to_string()])
413        );
414        assert_eq!(second.value_domain_kind, "finiteSet");
415        assert_eq!(second.selector_certainty, "inferred");
416        assert_eq!(second.value_certainty.as_deref(), Some("inferred"));
417        assert_eq!(second.selector_certainty_shape_kind, "boundedFinite");
418        assert_eq!(
419            second.selector_certainty_shape_label,
420            "bounded selector set (1)"
421        );
422        assert_eq!(second.value_certainty_shape_kind, "boundedFinite");
423        assert_eq!(second.value_certainty_shape_label, "bounded finite (2)");
424    }
425
426    #[test]
427    fn builds_expression_semantics_canonical_candidate_bundle() {
428        let bundle =
429            summarize_expression_semantics_canonical_candidate_bundle_input(&sample_input());
430
431        assert_eq!(bundle.query_fragments.len(), 2);
432        assert_eq!(bundle.fragments.len(), 2);
433        assert_eq!(bundle.match_fragments.len(), 2);
434        assert_eq!(bundle.candidates.len(), 2);
435        assert_eq!(bundle.query_fragments[0].query_id, "expr-1");
436        assert_eq!(bundle.candidates[0].query_id, "expr-1");
437    }
438
439    #[test]
440    fn builds_expression_semantics_evaluator_candidates() {
441        let summary = summarize_expression_semantics_evaluator_candidates_input(&sample_input());
442
443        assert_eq!(summary.results.len(), 2);
444        let first = &summary.results[0];
445        assert_eq!(first.kind, "expression-semantics");
446        assert_eq!(first.file_path, "/tmp/App.tsx");
447        assert_eq!(first.query_id, "expr-1");
448        assert_eq!(first.payload.expression_kind, "symbolRef");
449        assert_eq!(first.payload.value_domain_kind, "constrained");
450        assert_eq!(
451            first.payload.value_constraint_kind.as_deref(),
452            Some("prefixSuffix")
453        );
454        assert_eq!(
455            first.payload.value_domain_derivation.product,
456            "omena-abstract-value.reduced-class-value-derivation"
457        );
458        assert_eq!(
459            first.payload.value_domain_derivation.reduced_kind,
460            "prefixSuffix"
461        );
462        assert_eq!(
463            first.payload.value_domain_provenance_tree.product,
464            "omena-abstract-value.provenance-tree"
465        );
466        assert_eq!(
467            first.payload.value_domain_provenance_tree.root.operation,
468            "constraintDomain"
469        );
470    }
471
472    #[test]
473    fn builds_expression_semantics_canonical_producer_signal() {
474        let summary =
475            summarize_expression_semantics_canonical_producer_signal_input(&sample_input());
476
477        assert_eq!(summary.canonical_bundle.candidates.len(), 2);
478        assert_eq!(summary.evaluator_candidates.results.len(), 2);
479        assert_eq!(summary.evaluator_candidates.results[0].query_id, "expr-1");
480    }
481}