Skip to main content

cp_ast_core/projection/
projection_impl.rs

1use super::api::ProjectionAPI;
2use super::types::{
3    AvailableAction, CandidateKind, CompletenessSummary, NodeDetail, NodeEditProjection,
4    NotEditableReason, ProjectedNode, SlotEntry,
5};
6use crate::constraint::{ArithOp, Constraint, ExpectedType, Expression};
7use crate::operation::AstEngine;
8use crate::structure::{NodeId, NodeKind, Reference};
9
10impl ProjectionAPI for AstEngine {
11    fn nodes(&self) -> Vec<ProjectedNode> {
12        let mut result = Vec::new();
13        let root_id = self.structure.root();
14        let mut stack = Vec::new();
15
16        if let Some(root) = self.structure.get(root_id) {
17            if is_hidden_root(root.kind()) {
18                for &child_id in children_of(root.kind()).iter().rev() {
19                    stack.push((child_id, 0));
20                }
21            } else {
22                stack.push((root_id, 0));
23            }
24        }
25
26        while let Some((node_id, depth)) = stack.pop() {
27            if let Some(node) = self.structure.get(node_id) {
28                let label = make_label(node.kind());
29                let is_hole = matches!(node.kind(), NodeKind::Hole { .. });
30
31                result.push(ProjectedNode {
32                    id: node_id,
33                    label,
34                    depth,
35                    is_hole,
36                    edit: node_edit_projection(self, node_id),
37                });
38
39                // Add children in reverse order for DFS (stack is LIFO)
40                let children = children_of(node.kind());
41                for &child_id in children.iter().rev() {
42                    stack.push((child_id, depth + 1));
43                }
44            }
45        }
46
47        result
48    }
49
50    fn children(&self, node: NodeId) -> Vec<SlotEntry> {
51        if let Some(node) = self.structure.get(node) {
52            slot_entries_for(node.kind())
53        } else {
54            Vec::new()
55        }
56    }
57
58    fn inspect(&self, node: NodeId) -> Option<NodeDetail> {
59        let node = self.structure.get(node)?;
60        let kind_label = make_label(node.kind());
61
62        let constraint_ids = self.constraints.for_node(node.id());
63        let constraints = constraint_ids
64            .iter()
65            .filter_map(|&id| self.constraints.get(id))
66            .map(format_constraint)
67            .collect();
68
69        Some(NodeDetail {
70            id: node.id(),
71            kind_label,
72            constraints,
73        })
74    }
75
76    fn hole_candidates(&self, hole: NodeId) -> Vec<CandidateKind> {
77        if let Some(node) = self.structure.get(hole) {
78            if matches!(node.kind(), NodeKind::Hole { .. }) {
79                vec![
80                    CandidateKind::IntroduceScalar {
81                        suggested_names: vec!["N".into(), "M".into(), "K".into()],
82                    },
83                    CandidateKind::IntroduceArray {
84                        suggested_names: vec!["A".into(), "B".into()],
85                    },
86                    CandidateKind::IntroduceMatrix,
87                    CandidateKind::IntroduceSection,
88                ]
89            } else {
90                Vec::new()
91            }
92        } else {
93            Vec::new()
94        }
95    }
96
97    fn available_actions(&self) -> Vec<AvailableAction> {
98        let mut actions = Vec::new();
99
100        for node in self.structure.iter() {
101            let node_id = node.id();
102
103            match node.kind() {
104                NodeKind::Hole { .. } => {
105                    actions.push(AvailableAction {
106                        target: node_id,
107                        description: "Fill hole".to_owned(),
108                    });
109                }
110                _ => {
111                    // Non-hole, non-root nodes can potentially be replaced or removed
112                    if node_id != self.structure.root() {
113                        actions.push(AvailableAction {
114                            target: node_id,
115                            description: "Replace node".to_owned(),
116                        });
117
118                        // Only allow removal if no constraints
119                        let constraints = self.constraints.for_node(node_id);
120                        if constraints.is_empty() {
121                            actions.push(AvailableAction {
122                                target: node_id,
123                                description: "Remove from parent".to_owned(),
124                            });
125                        }
126                    }
127                }
128            }
129        }
130
131        actions
132    }
133
134    fn why_not_editable(&self, node: NodeId) -> Option<NotEditableReason> {
135        if node == self.structure.root() {
136            return Some(NotEditableReason::IsRoot);
137        }
138
139        let constraint_ids = self.constraints.for_node(node);
140        if !constraint_ids.is_empty() {
141            return Some(NotEditableReason::HasDependents {
142                dependents: vec![node], // Simplified - just return the node itself
143            });
144        }
145
146        None
147    }
148
149    fn completeness(&self) -> CompletenessSummary {
150        let mut total_nodes: usize = 0;
151        let mut total_holes: usize = 0;
152
153        for node in self.structure.iter() {
154            total_nodes += 1;
155            if matches!(node.kind(), NodeKind::Hole { .. }) {
156                total_holes += 1;
157            }
158        }
159
160        let filled_slots = total_nodes.saturating_sub(total_holes);
161        let unsatisfied_constraints = 0; // Future work - constraint satisfaction checking
162        let is_complete = total_holes == 0;
163
164        CompletenessSummary {
165            total_holes,
166            filled_slots,
167            unsatisfied_constraints,
168            is_complete,
169        }
170    }
171}
172
173fn node_edit_projection(engine: &AstEngine, node_id: NodeId) -> Option<NodeEditProjection> {
174    let node = engine.structure.get(node_id)?;
175    let value_type = expected_type(engine, node_id)
176        .map_or("number", |typ| match typ {
177            ExpectedType::Int => "number",
178            ExpectedType::Str => "string",
179            ExpectedType::Char => "char",
180        })
181        .to_owned();
182
183    match node.kind() {
184        NodeKind::Scalar { name } => Some(NodeEditProjection {
185            kind: "scalar".to_owned(),
186            name: name.as_str().to_owned(),
187            value_type,
188            length_expr: None,
189            allowed_kinds: vec!["scalar".to_owned(), "array".to_owned()],
190            allowed_types: vec!["number".to_owned(), "char".to_owned(), "string".to_owned()],
191        }),
192        NodeKind::Array { name, length } => Some(NodeEditProjection {
193            kind: "array".to_owned(),
194            name: name.as_str().to_owned(),
195            value_type,
196            length_expr: Some(format_expr_with_names(length, engine)),
197            allowed_kinds: vec!["scalar".to_owned(), "array".to_owned()],
198            allowed_types: vec!["number".to_owned(), "char".to_owned(), "string".to_owned()],
199        }),
200        _ => None,
201    }
202}
203
204fn expected_type(engine: &AstEngine, node_id: NodeId) -> Option<ExpectedType> {
205    let ids = engine.constraints.for_node(node_id);
206    for cid in &ids {
207        if let Some(Constraint::TypeDecl { expected, .. }) = engine.constraints.get(*cid) {
208            return Some(expected.clone());
209        }
210    }
211    None
212}
213
214fn format_expr_with_names(expr: &Expression, engine: &AstEngine) -> String {
215    match expr {
216        Expression::Lit(n) => n.to_string(),
217        Expression::Var(r) => ref_to_name(r, engine),
218        Expression::BinOp { op, lhs, rhs } => {
219            let symbol = match op {
220                ArithOp::Add => "+",
221                ArithOp::Sub => "-",
222                ArithOp::Mul => "*",
223                ArithOp::Div => "/",
224            };
225            format!(
226                "{}{}{}",
227                format_expr_with_names(lhs, engine),
228                symbol,
229                format_expr_with_names(rhs, engine)
230            )
231        }
232        Expression::Pow { base, exp } => {
233            format!(
234                "{}^{}",
235                format_expr_with_names(base, engine),
236                format_expr_with_names(exp, engine)
237            )
238        }
239        Expression::FnCall { name, args } => {
240            let arg_strs: Vec<_> = args
241                .iter()
242                .map(|arg| format_expr_with_names(arg, engine))
243                .collect();
244            format!("{}({})", name.as_str(), arg_strs.join(","))
245        }
246    }
247}
248
249fn ref_to_name(reference: &Reference, engine: &AstEngine) -> String {
250    match reference {
251        Reference::VariableRef(node_id) => engine.structure.get(*node_id).map_or_else(
252            || format!("?node({node_id:?})"),
253            |node| match node.kind() {
254                NodeKind::Scalar { name }
255                | NodeKind::Array { name, .. }
256                | NodeKind::Matrix { name, .. } => name.as_str().to_owned(),
257                other => format!("{other:?}"),
258            },
259        ),
260        Reference::Unresolved(ident) => ident.as_str().to_owned(),
261        Reference::IndexedRef { .. } => format!("{reference:?}"),
262    }
263}
264
265/// Generate display label for a node kind.
266pub(super) fn make_label(kind: &NodeKind) -> String {
267    match kind {
268        NodeKind::Scalar { name } => name.as_str().to_owned(),
269        NodeKind::Array { name, .. } => format!("{}[]", name.as_str()),
270        NodeKind::Matrix { name, .. } => format!("{}[][]", name.as_str()),
271        NodeKind::Tuple { .. } => "Tuple".to_owned(),
272        NodeKind::Repeat { .. } => "Repeat".to_owned(),
273        NodeKind::Section { .. } => "Section".to_owned(),
274        NodeKind::Sequence { .. } => "Sequence".to_owned(),
275        NodeKind::Choice { .. } => "Choice".to_owned(),
276        NodeKind::Hole { .. } => "[ ]".to_owned(),
277    }
278}
279
280fn is_hidden_root(kind: &NodeKind) -> bool {
281    matches!(kind, NodeKind::Sequence { .. })
282}
283
284/// Get child node IDs for DFS traversal.
285fn children_of(kind: &NodeKind) -> Vec<NodeId> {
286    match kind {
287        NodeKind::Sequence { children } => children.clone(),
288        NodeKind::Section { header, body } => {
289            header.iter().copied().chain(body.iter().copied()).collect()
290        }
291        NodeKind::Repeat { body, .. } => body.clone(),
292        NodeKind::Tuple { elements } => elements.clone(),
293        NodeKind::Choice { variants, .. } => variants
294            .iter()
295            .flat_map(|(_, v)| v.iter())
296            .copied()
297            .collect(),
298        _ => Vec::new(),
299    }
300}
301
302/// Get named slot entries for a node.
303fn slot_entries_for(kind: &NodeKind) -> Vec<SlotEntry> {
304    match kind {
305        NodeKind::Sequence { children } => children
306            .iter()
307            .map(|&child| SlotEntry {
308                name: "children".to_owned(),
309                child,
310            })
311            .collect(),
312        NodeKind::Section { header, body } => {
313            let mut entries = Vec::new();
314            if let Some(header_id) = header {
315                entries.push(SlotEntry {
316                    name: "header".to_owned(),
317                    child: *header_id,
318                });
319            }
320            for &body_id in body {
321                entries.push(SlotEntry {
322                    name: "body".to_owned(),
323                    child: body_id,
324                });
325            }
326            entries
327        }
328        NodeKind::Repeat { body, .. } => body
329            .iter()
330            .map(|&child| SlotEntry {
331                name: "body".to_owned(),
332                child,
333            })
334            .collect(),
335        NodeKind::Tuple { elements } => elements
336            .iter()
337            .map(|&child| SlotEntry {
338                name: "elements".to_owned(),
339                child,
340            })
341            .collect(),
342        NodeKind::Choice { variants, .. } => variants
343            .iter()
344            .flat_map(|(_, v)| v.iter())
345            .map(|&child| SlotEntry {
346                name: "variant".to_owned(),
347                child,
348            })
349            .collect(),
350        _ => Vec::new(),
351    }
352}
353
354/// Format a constraint as a human-readable string.
355fn format_constraint(c: &Constraint) -> String {
356    match c {
357        Constraint::Range { lower, upper, .. } => {
358            format!("Range: {} to {}", format_expr(lower), format_expr(upper))
359        }
360        Constraint::TypeDecl { expected, .. } => format!("Type: {expected:?}"),
361        Constraint::LengthRelation { length, .. } => format!("Length: {}", format_expr(length)),
362        Constraint::Relation { lhs, op, rhs } => {
363            format!(
364                "Relation: {} {:?} {}",
365                format_expr(lhs),
366                op,
367                format_expr(rhs)
368            )
369        }
370        Constraint::Distinct { unit, .. } => format!("Distinct: {unit:?}"),
371        Constraint::Property { tag, .. } => format!("Property: {tag:?}"),
372        Constraint::SumBound { upper, .. } => format!("SumBound: {}", format_expr(upper)),
373        Constraint::Sorted { order, .. } => format!("Sorted: {order:?}"),
374        Constraint::Guarantee { description, .. } => format!("Guarantee: {description}"),
375        Constraint::CharSet { charset, .. } => format!("CharSet: {charset:?}"),
376        Constraint::StringLength { min, max, .. } => {
377            format!("StringLength: {} to {}", format_expr(min), format_expr(max))
378        }
379        Constraint::RenderHint { hint, .. } => format!("RenderHint: {hint:?}"),
380    }
381}
382
383/// Format an expression as a simple string.
384fn format_expr(e: &Expression) -> String {
385    match e {
386        Expression::Lit(n) => n.to_string(),
387        Expression::Var(r) => format!("{r:?}"),
388        Expression::BinOp { op, lhs, rhs } => {
389            format!("({} {:?} {})", format_expr(lhs), op, format_expr(rhs))
390        }
391        Expression::Pow { base, exp } => {
392            format!("({} ^ {})", format_expr(base), format_expr(exp))
393        }
394        Expression::FnCall { name, args } => {
395            let arg_strs: Vec<_> = args.iter().map(format_expr).collect();
396            format!("{}({})", name.as_str(), arg_strs.join(", "))
397        }
398    }
399}