kanata_parser/cfg/
switch.rs

1use super::sexpr::*;
2use super::*;
3use crate::{anyhow_expr, bail, bail_expr};
4
5pub fn parse_switch(ac_params: &[SExpr], s: &ParserState) -> Result<&'static KanataAction> {
6    const ERR_STR: &str =
7        "switch expects triples of params: <key match> <action> <break|fallthrough>";
8
9    let mut cases = vec![];
10
11    let mut params = ac_params.iter();
12    loop {
13        let Some(key_match) = params.next() else {
14            break;
15        };
16        let Some(action) = params.next() else {
17            bail!("{ERR_STR}\nMissing <action> and <break|fallthrough> for the final triple");
18        };
19        let Some(break_or_fallthrough_expr) = params.next() else {
20            bail!("{ERR_STR}\nMissing <break|fallthrough> for the final triple");
21        };
22
23        let Some(key_match) = key_match.list(s.vars()) else {
24            bail_expr!(key_match, "{ERR_STR}\n<key match> must be a list")
25        };
26        let mut ops = vec![];
27        for op in key_match.iter() {
28            parse_switch_case_bool(1, op, &mut ops, s)?;
29        }
30
31        let action = parse_action(action, s)?;
32
33        let Some(break_or_fallthrough) = break_or_fallthrough_expr.atom(s.vars()) else {
34            bail_expr!(
35                break_or_fallthrough_expr,
36                "{ERR_STR}\nthis must be one of: break, fallthrough"
37            );
38        };
39        let break_or_fallthrough = match break_or_fallthrough {
40            "break" => BreakOrFallthrough::Break,
41            "fallthrough" => BreakOrFallthrough::Fallthrough,
42            _ => bail_expr!(
43                break_or_fallthrough_expr,
44                "{ERR_STR}\nthis must be one of: break, fallthrough"
45            ),
46        };
47        cases.push((s.a.sref_vec(ops), action, break_or_fallthrough));
48    }
49    Ok(s.a.sref(Action::Switch(s.a.sref(Switch {
50        cases: s.a.sref_vec(cases),
51    }))))
52}
53
54pub fn parse_switch_case_bool(
55    depth: u8,
56    op_expr: &SExpr,
57    ops: &mut Vec<OpCode>,
58    s: &ParserState,
59) -> Result<()> {
60    if ops.len() > MAX_OPCODE_LEN as usize {
61        bail_expr!(
62            op_expr,
63            "maximum key match size of {MAX_OPCODE_LEN} items is exceeded"
64        );
65    }
66    if usize::from(depth) > MAX_BOOL_EXPR_DEPTH {
67        bail_expr!(
68            op_expr,
69            "maximum key match expression depth {MAX_BOOL_EXPR_DEPTH} is exceeded"
70        );
71    }
72    if let Some(a) = op_expr.atom(s.vars()) {
73        let osc = str_to_oscode(a).ok_or_else(|| anyhow_expr!(op_expr, "invalid key name"))?;
74        ops.push(OpCode::new_key(osc.into()));
75        Ok(())
76    } else {
77        let l = op_expr
78            .list(s.vars())
79            .expect("must be a list, checked atom");
80        if l.is_empty() {
81            bail_expr!(op_expr, "switch logic cannot contain empty lists inside");
82        }
83        #[derive(PartialEq)]
84        enum AllowedListOps {
85            Or,
86            And,
87            Not,
88            KeyHistory,
89            KeyTiming,
90            Input,
91            InputHistory,
92            Layer,
93            BaseLayer,
94        }
95        #[derive(Copy, Clone)]
96        enum InputType {
97            Real,
98            Virtual,
99        }
100        impl InputType {
101            fn to_row(self) -> u8 {
102                match self {
103                    InputType::Real => 0,
104                    InputType::Virtual => 1,
105                }
106            }
107        }
108        let op = l[0]
109            .atom(s.vars())
110            .and_then(|s| match s {
111                "or" => Some(AllowedListOps::Or),
112                "and" => Some(AllowedListOps::And),
113                "not" => Some(AllowedListOps::Not),
114                "key-history" => Some(AllowedListOps::KeyHistory),
115                "key-timing" => Some(AllowedListOps::KeyTiming),
116                "input" => Some(AllowedListOps::Input),
117                "input-history" => Some(AllowedListOps::InputHistory),
118                "layer" => Some(AllowedListOps::Layer),
119                "base-layer" => Some(AllowedListOps::BaseLayer),
120                _ => None,
121            })
122            .ok_or_else(|| {
123                anyhow_expr!(
124                    op_expr,
125                    "lists inside switch logic must begin with one of:\n\
126                    or | and | not | key-history | key-timing\n\
127                    | input | input-history | layer | base-layer",
128                )
129            })?;
130
131        match op {
132            AllowedListOps::KeyHistory => {
133                if l.len() != 3 {
134                    bail_expr!(
135                        op_expr,
136                        "key-history must have 2 parameters: key, key-recency"
137                    );
138                }
139                let osc = l[1]
140                    .atom(s.vars())
141                    .and_then(str_to_oscode)
142                    .ok_or_else(|| anyhow_expr!(&l[1], "invalid key name"))?;
143                let key_recency = parse_u8_with_range(&l[2], s, "key-recency", 1, 8)? - 1;
144                ops.push(OpCode::new_key_history(osc.into(), key_recency));
145                Ok(())
146            }
147            AllowedListOps::Input => {
148                if l.len() != 3 {
149                    bail_expr!(
150                        op_expr,
151                        "input must have 2 parameters: key-type(virtual|real), key"
152                    );
153                }
154
155                let input_type = match l[1]
156                    .atom(s.vars())
157                    .ok_or_else(|| anyhow_expr!(&l[1], "key-type must be virtual|real"))?
158                {
159                    "real" => InputType::Real,
160                    "fake" | "virtual" => InputType::Virtual,
161                    _ => bail_expr!(op_expr, "key-type must be virtual|real"),
162                };
163                let input = match input_type {
164                    InputType::Real => {
165                        let key = l[2].atom(s.vars()).ok_or_else(|| {
166                            anyhow_expr!(&l[2], "input key name must not be a list")
167                        })?;
168                        u16::from(
169                            str_to_oscode(key)
170                                .ok_or_else(|| anyhow_expr!(&l[2], "invalid input key name"))?,
171                        )
172                    }
173                    InputType::Virtual => parse_vkey_coord(&l[2], s)?.y,
174                };
175                let (op1, op2) = OpCode::new_active_input((input_type.to_row(), input));
176                ops.extend(&[op1, op2]);
177                Ok(())
178            }
179            AllowedListOps::InputHistory => {
180                if l.len() != 4 {
181                    bail_expr!(
182                        op_expr,
183                        "input-history must have 3 parameters: key-type(virtual|real), key, key-recency"
184                    );
185                }
186
187                let input_type = match l[1]
188                    .atom(s.vars())
189                    .ok_or_else(|| anyhow_expr!(&l[1], "key-type must be virtual|real"))?
190                {
191                    "real" => InputType::Real,
192                    "fake" | "virtual" => InputType::Virtual,
193                    _ => bail_expr!(&l[1], "key-type must be virtual|real"),
194                };
195                let input = match input_type {
196                    InputType::Real => {
197                        let key = l[2].atom(s.vars()).ok_or_else(|| {
198                            anyhow_expr!(&l[2], "input key name must not be a list")
199                        })?;
200                        u16::from(
201                            str_to_oscode(key)
202                                .ok_or_else(|| anyhow_expr!(&l[2], "invalid input key name"))?,
203                        )
204                    }
205                    InputType::Virtual => parse_vkey_coord(&l[2], s)?.y,
206                };
207                let key_recency = parse_u8_with_range(&l[3], s, "key-recency", 1, 8)? - 1;
208                let (op1, op2) =
209                    OpCode::new_historical_input((input_type.to_row(), input), key_recency);
210                ops.extend(&[op1, op2]);
211                Ok(())
212            }
213            AllowedListOps::KeyTiming => {
214                if l.len() != 4 {
215                    bail_expr!(
216                        op_expr,
217                        "key-timing must have 3 parameters: key-recency, lt|gt|less-than|greater-than, milliseconds (0-65535)"
218                    );
219                }
220                let nth_key = parse_u8_with_range(&l[1], s, "key-recency", 1, 8)? - 1;
221                let ticks_since = parse_u16(&l[3], s, "milliseconds")?;
222                match l[2].atom(s.vars()).ok_or_else(|| {
223                    anyhow_expr!(
224                        &l[2],
225                        "key-timing 2nd parameter must be one of: lt|gt|less-than|greater-than"
226                    )
227                })? {
228                    "less-than" | "lt" => {
229                        ops.push(OpCode::new_ticks_since_lt(nth_key, ticks_since));
230                    }
231                    "greater-than" | "gt" => {
232                        ops.push(OpCode::new_ticks_since_gt(nth_key, ticks_since));
233                    }
234                    _ => {
235                        bail_expr!(
236                            &l[2],
237                            "key-timing 2nd parameter must be one of: lt|gt|less-than|greater-than"
238                        );
239                    }
240                };
241                s.switch_max_key_timing
242                    .set(std::cmp::max(s.switch_max_key_timing.get(), ticks_since));
243                Ok(())
244            }
245            AllowedListOps::Layer | AllowedListOps::BaseLayer => {
246                if l.len() != 2 {
247                    bail_expr!(
248                        op_expr,
249                        "{} must have 1 parameter: layer-name",
250                        match op {
251                            AllowedListOps::Layer => "layer",
252                            AllowedListOps::BaseLayer => "base-layer",
253                            _ => unreachable!(),
254                        }
255                    );
256                }
257                let layer = l[1]
258                    .atom(s.vars())
259                    .and_then(|atom| s.layer_idxs.get(atom))
260                    .map(|idx| {
261                        assert!(*idx < MAX_LAYERS);
262                        *idx as u16
263                    })
264                    .ok_or_else(|| anyhow_expr!(&l[1], "not a known layer name"))?;
265                let (op1, op2) = match op {
266                    AllowedListOps::Layer => OpCode::new_layer(layer),
267                    AllowedListOps::BaseLayer => OpCode::new_base_layer(layer),
268                    _ => unreachable!(),
269                };
270                ops.extend(&[op1, op2]);
271                Ok(())
272            }
273            AllowedListOps::Or | AllowedListOps::And | AllowedListOps::Not => {
274                let op = match op {
275                    AllowedListOps::Or => BooleanOperator::Or,
276                    AllowedListOps::And => BooleanOperator::And,
277                    AllowedListOps::Not => BooleanOperator::Not,
278                    _ => unreachable!(),
279                };
280                // insert a placeholder for now, don't know the end index yet.
281                let placeholder_index = ops.len() as u16;
282                ops.push(OpCode::new_bool(op, placeholder_index));
283                for op in l.iter().skip(1) {
284                    parse_switch_case_bool(depth + 1, op, ops, s)?;
285                }
286                if ops.len() > usize::from(MAX_OPCODE_LEN) {
287                    bail_expr!(op_expr, "switch logic length has been exceeded");
288                }
289                ops[placeholder_index as usize] = OpCode::new_bool(op, ops.len() as u16);
290                Ok(())
291            }
292        }
293    }
294}