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!(op_expr, "input-history must have 3 parameters: key-type(virtual|real), key, key-recency");
182                }
183
184                let input_type = match l[1]
185                    .atom(s.vars())
186                    .ok_or_else(|| anyhow_expr!(&l[1], "key-type must be virtual|real"))?
187                {
188                    "real" => InputType::Real,
189                    "fake" | "virtual" => InputType::Virtual,
190                    _ => bail_expr!(&l[1], "key-type must be virtual|real"),
191                };
192                let input = match input_type {
193                    InputType::Real => {
194                        let key = l[2].atom(s.vars()).ok_or_else(|| {
195                            anyhow_expr!(&l[2], "input key name must not be a list")
196                        })?;
197                        u16::from(
198                            str_to_oscode(key)
199                                .ok_or_else(|| anyhow_expr!(&l[2], "invalid input key name"))?,
200                        )
201                    }
202                    InputType::Virtual => parse_vkey_coord(&l[2], s)?.y,
203                };
204                let key_recency = parse_u8_with_range(&l[3], s, "key-recency", 1, 8)? - 1;
205                let (op1, op2) =
206                    OpCode::new_historical_input((input_type.to_row(), input), key_recency);
207                ops.extend(&[op1, op2]);
208                Ok(())
209            }
210            AllowedListOps::KeyTiming => {
211                if l.len() != 4 {
212                    bail_expr!(
213                        op_expr,
214                        "key-timing must have 3 parameters: key-recency, lt|gt|less-than|greater-than, milliseconds (0-65535)"
215                    );
216                }
217                let nth_key = parse_u8_with_range(&l[1], s, "key-recency", 1, 8)? - 1;
218                let ticks_since = parse_u16(&l[3], s, "milliseconds")?;
219                match l[2].atom(s.vars()).ok_or_else(|| {
220                    anyhow_expr!(
221                        &l[2],
222                        "key-timing 2nd parameter must be one of: lt|gt|less-than|greater-than"
223                    )
224                })? {
225                    "less-than" | "lt" => {
226                        ops.push(OpCode::new_ticks_since_lt(nth_key, ticks_since));
227                    }
228                    "greater-than" | "gt" => {
229                        ops.push(OpCode::new_ticks_since_gt(nth_key, ticks_since));
230                    }
231                    _ => {
232                        bail_expr!(
233                            &l[2],
234                            "key-timing 2nd parameter must be one of: lt|gt|less-than|greater-than"
235                        );
236                    }
237                };
238                s.switch_max_key_timing
239                    .set(std::cmp::max(s.switch_max_key_timing.get(), ticks_since));
240                Ok(())
241            }
242            AllowedListOps::Layer | AllowedListOps::BaseLayer => {
243                if l.len() != 2 {
244                    bail_expr!(
245                        op_expr,
246                        "{} must have 1 parameter: layer-name",
247                        match op {
248                            AllowedListOps::Layer => "layer",
249                            AllowedListOps::BaseLayer => "base-layer",
250                            _ => unreachable!(),
251                        }
252                    );
253                }
254                let layer = l[1]
255                    .atom(s.vars())
256                    .and_then(|atom| s.layer_idxs.get(atom))
257                    .map(|idx| {
258                        assert!(*idx < MAX_LAYERS);
259                        *idx as u16
260                    })
261                    .ok_or_else(|| anyhow_expr!(&l[1], "not a known layer name"))?;
262                let (op1, op2) = match op {
263                    AllowedListOps::Layer => OpCode::new_layer(layer),
264                    AllowedListOps::BaseLayer => OpCode::new_base_layer(layer),
265                    _ => unreachable!(),
266                };
267                ops.extend(&[op1, op2]);
268                Ok(())
269            }
270            AllowedListOps::Or | AllowedListOps::And | AllowedListOps::Not => {
271                let op = match op {
272                    AllowedListOps::Or => BooleanOperator::Or,
273                    AllowedListOps::And => BooleanOperator::And,
274                    AllowedListOps::Not => BooleanOperator::Not,
275                    _ => unreachable!(),
276                };
277                // insert a placeholder for now, don't know the end index yet.
278                let placeholder_index = ops.len() as u16;
279                ops.push(OpCode::new_bool(op, placeholder_index));
280                for op in l.iter().skip(1) {
281                    parse_switch_case_bool(depth + 1, op, ops, s)?;
282                }
283                if ops.len() > usize::from(MAX_OPCODE_LEN) {
284                    bail_expr!(op_expr, "switch logic length has been exceeded");
285                }
286                ops[placeholder_index as usize] = OpCode::new_bool(op, ops.len() as u16);
287                Ok(())
288            }
289        }
290    }
291}