Skip to main content

crulz/
mangle_ast.rs

1use crate::ast::{ASTNode, CmdEvalArgs, GroupType, LiftAST, VAN};
2use delegate_attr::delegate;
3use itertools::Itertools;
4
5// do NOT "use ASTNode::*;" here, because sometimes we want to "use ASTNodeClass::*;"
6
7pub trait MangleAST: Default {
8    fn to_str(self, escc: char) -> String;
9
10    /// helper for MangleAST::simplify and interp::eval
11    fn get_complexity(&self) -> usize;
12
13    #[inline(always)]
14    fn take(&mut self) -> Self {
15        std::mem::take(self)
16    }
17
18    #[inline]
19    fn simplify_inplace(&mut self) {
20        *self = self.take().simplify();
21    }
22
23    /// this cleanup up the AST, opposite of two lift_ast invocations
24    fn simplify(self) -> Self;
25
26    /// this apply_arguments function applies the 'args' to the AST
27    /// # Return value
28    /// * `Err(idx)`: the first applied index which wasn't present in 'args'
29    fn apply_arguments_inplace(&mut self, args: &CmdEvalArgs) -> Result<(), usize>;
30}
31
32impl MangleAST for ASTNode {
33    fn to_str(self, escc: char) -> String {
34        use ASTNode::*;
35        match self {
36            NullNode => String::new(),
37            Constant(_, x) => x.to_string(),
38            Grouped(gt, elems) => {
39                let inner = elems.to_str(escc);
40                if gt == GroupType::Strict {
41                    format!("({})", inner)
42                } else {
43                    inner
44                }
45            }
46            Argument { indirection, index } => std::iter::repeat('$')
47                .take(indirection + 1)
48                .chain(
49                    index
50                        .as_ref()
51                        .map(usize::to_string)
52                        .iter()
53                        .flat_map(|i| i.chars()),
54                )
55                .collect(),
56            CmdEval(cmd, args) => format!("{}({}{})", escc, cmd.to_str(escc), args.to_str(escc)),
57        }
58    }
59
60    fn get_complexity(&self) -> usize {
61        use ASTNode::*;
62        match &self {
63            NullNode => 0,
64            Constant(_, x) => 1 + x.len(),
65            Argument { indirection, .. } => 3 + indirection,
66            Grouped(gt, x) => {
67                (match *gt {
68                    GroupType::Dissolving => 0,
69                    GroupType::Loose => 1,
70                    GroupType::Strict => 2,
71                }) + x.get_complexity()
72            }
73            CmdEval(cmd, x) => 1 + cmd.get_complexity() + x.get_complexity(),
74        }
75    }
76
77    fn simplify(mut self) -> Self {
78        use ASTNode::*;
79        let mut cplx = self.get_complexity();
80        loop {
81            match &mut self {
82                Grouped(ref mut gt, ref mut x) => {
83                    match x.len() {
84                        0 => {
85                            if *gt != GroupType::Strict {
86                                self = NullNode;
87                            }
88                        }
89                        1 => {
90                            let y = x[0].take().simplify();
91                            if *gt != GroupType::Strict {
92                                self = y;
93                            } else if let Grouped(GroupType::Dissolving, z) = y {
94                                *x = z;
95                            } else {
96                                // swap it back, omit clone
97                                x[0] = y;
98                            }
99                        }
100                        _ => x.simplify_inplace(),
101                    }
102                }
103                CmdEval(ref mut cmd, ref mut args) => {
104                    cmd.simplify_inplace();
105                    args.simplify_inplace();
106                }
107                _ => break,
108            }
109            let new_cplx = self.get_complexity();
110            if new_cplx >= cplx {
111                break;
112            }
113            cplx = new_cplx;
114        }
115        self
116    }
117
118    fn apply_arguments_inplace(&mut self, xargs: &CmdEvalArgs) -> Result<(), usize> {
119        use ASTNode::*;
120        match self {
121            Argument {
122                indirection: 0,
123                index,
124            } => {
125                *self = match *index {
126                    Some(index) => match xargs.0.get(index) {
127                        Some(x) => x.clone(),
128                        None => return Err(index),
129                    },
130                    None => Constant(true, crulst_atom!("$")),
131                };
132            }
133            Argument {
134                ref mut indirection,
135                ..
136            } => *indirection -= 1,
137
138            Grouped(_, ref mut x) => x.apply_arguments_inplace(xargs)?,
139            CmdEval(ref mut cmd, ref mut args) => {
140                cmd.apply_arguments_inplace(xargs)?;
141                args.apply_arguments_inplace(xargs)?;
142            }
143            _ => {}
144        }
145        Ok(())
146    }
147}
148
149impl MangleAST for VAN {
150    fn to_str(self, escc: char) -> String {
151        self.into_iter()
152            .fold(String::new(), |acc, i| acc + &i.to_str(escc))
153    }
154
155    #[inline]
156    fn get_complexity(&self) -> usize {
157        self.iter().map(|i| i.get_complexity()).sum()
158    }
159
160    fn simplify(self) -> Self {
161        #[derive(PartialEq)]
162        enum ASTNodeClass {
163            NullNode,
164            Constant(bool),
165            Grouped(GroupType),
166            Opaque,
167        }
168
169        self.into_iter()
170            .map(|i| i.simplify())
171            .group_by(|i| {
172                use ASTNodeClass::*;
173                match i {
174                    ASTNode::Grouped(gt, x) if x.is_empty() && *gt != GroupType::Strict => NullNode,
175                    ASTNode::Constant(_, x) if x.is_empty() => NullNode,
176                    ASTNode::Constant(s, _) => Constant(*s),
177                    ASTNode::Grouped(s, _) => Grouped(*s),
178                    ASTNode::Argument { .. } | ASTNode::CmdEval(_, _) => Opaque,
179                    _ => NullNode,
180                }
181            })
182            .into_iter()
183            .filter(|(d, _)| *d != ASTNodeClass::NullNode)
184            .flat_map(|(d, i)| {
185                use ASTNode::*;
186                match d {
187                    ASTNodeClass::Constant(x) => Constant(
188                        x,
189                        i.map(|j| {
190                            if let Constant(_, y) = j {
191                                y
192                            } else {
193                                unreachable!()
194                            }
195                        })
196                        .fold(String::new(), |acc, i| acc + &i)
197                        .into(),
198                    )
199                    .lift_ast(),
200                    ASTNodeClass::Grouped(GroupType::Dissolving) => i
201                        .flat_map(|j| {
202                            if let Grouped(_, x) = j {
203                                x
204                            } else {
205                                unreachable!()
206                            }
207                        })
208                        .collect(),
209                    _ => i.collect(),
210                }
211            })
212            .collect()
213    }
214
215    fn apply_arguments_inplace(&mut self, args: &CmdEvalArgs) -> Result<(), usize> {
216        for i in self.iter_mut() {
217            i.apply_arguments_inplace(args)?;
218        }
219        Ok(())
220    }
221}
222
223impl MangleAST for CmdEvalArgs {
224    fn to_str(self, escc: char) -> String {
225        self.0
226            .into_iter()
227            .fold(String::new(), |acc, i| acc + " " + &i.to_str(escc))
228    }
229
230    fn simplify(self) -> Self {
231        self.into_iter()
232            .map(|i| i.simplify())
233            .flat_map(|i| {
234                if let ASTNode::Grouped(GroupType::Dissolving, elems) = i {
235                    elems
236                } else {
237                    i.lift_ast()
238                }
239            })
240            .collect()
241    }
242
243    #[delegate(self.0)]
244    fn get_complexity(&self) -> usize { }
245
246    #[delegate(self.0)]
247    fn apply_arguments_inplace(&mut self, args: &CmdEvalArgs) -> Result<(), usize> { }
248}
249
250pub trait MangleASTExt: MangleAST {
251    fn compact_toplevel(self) -> Self;
252}
253
254impl MangleASTExt for VAN {
255    fn compact_toplevel(self) -> Self {
256        // we are at the top level, wo can inline non-strict groups
257        // and then put all constants heaps into single constants
258        self.into_iter()
259            // 1. inline non-strict groups
260            .flat_map(|i| match i {
261                ASTNode::NullNode => vec![],
262                ASTNode::Grouped(gt, x) if gt != GroupType::Strict => x.compact_toplevel(),
263                _ => vec![i],
264            })
265            // 2. aggressive concat constant-after-constants
266            .peekable()
267            .batching(|it| {
268                let (mut risp, mut rdat) = match it.next()? {
269                    ASTNode::Constant(isp, dat) => (isp, dat.to_string()),
270                    x => return Some(x),
271                };
272                while let Some(ASTNode::Constant(isp, ref dat)) = it.peek() {
273                    risp |= isp;
274                    rdat += &dat;
275                    it.next();
276                }
277                Some(ASTNode::Constant(risp, rdat.into()))
278            })
279            .collect()
280    }
281}
282
283#[cfg(test)]
284mod tests {
285    use super::ASTNode::*;
286    use super::*;
287
288    #[test]
289    fn test_simplify() {
290        let ast = vec![
291            Constant(true, "a".into()),
292            Constant(true, "b".into())
293                .lift_ast()
294                .lift_ast()
295                .lift_ast()
296                .lift_ast(),
297            Constant(true, "c".into()),
298        ]
299        .lift_ast()
300        .lift_ast()
301        .lift_ast();
302        assert_eq!(ast.simplify(), Constant(true, "abc".into()));
303    }
304
305    #[test]
306    fn test_compact_tl() {
307        let ast = vec![
308            Constant(true, "a".into()),
309            Constant(false, "b".into()),
310            Constant(true, "c".into()),
311        ]
312        .compact_toplevel();
313        assert_eq!(ast, vec![Constant(true, "abc".into())]);
314    }
315}