use super::sexpr::*;
use super::*;
use crate::{anyhow_expr, bail, bail_expr};
pub fn parse_switch(ac_params: &[SExpr], s: &ParserState) -> Result<&'static KanataAction> {
const ERR_STR: &str =
"switch expects triples of params: <key match> <action> <break|fallthrough>";
let mut cases = vec![];
let mut params = ac_params.iter();
loop {
let Some(key_match) = params.next() else {
break;
};
let Some(action) = params.next() else {
bail!("{ERR_STR}\nMissing <action> and <break|fallthrough> for the final triple");
};
let Some(break_or_fallthrough_expr) = params.next() else {
bail!("{ERR_STR}\nMissing <break|fallthrough> for the final triple");
};
let Some(key_match) = key_match.list(s.vars()) else {
bail_expr!(key_match, "{ERR_STR}\n<key match> must be a list")
};
let mut ops = vec![];
for op in key_match.iter() {
parse_switch_case_bool(1, op, &mut ops, s)?;
}
let action = parse_action(action, s)?;
let Some(break_or_fallthrough) = break_or_fallthrough_expr.atom(s.vars()) else {
bail_expr!(
break_or_fallthrough_expr,
"{ERR_STR}\nthis must be one of: break, fallthrough"
);
};
let break_or_fallthrough = match break_or_fallthrough {
"break" => BreakOrFallthrough::Break,
"fallthrough" => BreakOrFallthrough::Fallthrough,
_ => bail_expr!(
break_or_fallthrough_expr,
"{ERR_STR}\nthis must be one of: break, fallthrough"
),
};
cases.push((s.a.sref_vec(ops), action, break_or_fallthrough));
}
Ok(s.a.sref(Action::Switch(s.a.sref(Switch {
cases: s.a.sref_vec(cases),
}))))
}
pub fn parse_switch_case_bool(
depth: u8,
op_expr: &SExpr,
ops: &mut Vec<OpCode>,
s: &ParserState,
) -> Result<()> {
if ops.len() > MAX_OPCODE_LEN as usize {
bail_expr!(
op_expr,
"maximum key match size of {MAX_OPCODE_LEN} items is exceeded"
);
}
if usize::from(depth) > MAX_BOOL_EXPR_DEPTH {
bail_expr!(
op_expr,
"maximum key match expression depth {MAX_BOOL_EXPR_DEPTH} is exceeded"
);
}
if let Some(a) = op_expr.atom(s.vars()) {
let osc = str_to_oscode(a).ok_or_else(|| anyhow_expr!(op_expr, "invalid key name"))?;
ops.push(OpCode::new_key(osc.into()));
Ok(())
} else {
let l = op_expr
.list(s.vars())
.expect("must be a list, checked atom");
if l.is_empty() {
bail_expr!(op_expr, "key match cannot contain empty lists inside");
}
#[derive(PartialEq)]
enum AllowedListOps {
Or,
And,
Not,
KeyHistory,
KeyTiming,
Input,
InputHistory,
}
#[derive(Copy, Clone)]
enum InputType {
Real,
Virtual,
}
impl InputType {
fn to_row(self) -> u8 {
match self {
InputType::Real => 0,
InputType::Virtual => 1,
}
}
}
let op = l[0]
.atom(s.vars())
.and_then(|s| match s {
"or" => Some(AllowedListOps::Or),
"and" => Some(AllowedListOps::And),
"not" => Some(AllowedListOps::Not),
"key-history" => Some(AllowedListOps::KeyHistory),
"key-timing" => Some(AllowedListOps::KeyTiming),
"input" => Some(AllowedListOps::Input),
"input-history" => Some(AllowedListOps::InputHistory),
_ => None,
})
.ok_or_else(|| {
anyhow_expr!(
op_expr,
"lists inside key match must begin with one of: or | and | not | key-history"
)
})?;
match op {
AllowedListOps::KeyHistory => {
if l.len() != 3 {
bail_expr!(
op_expr,
"key-history must have 2 parameters: key, key-recency"
);
}
let osc = l[1]
.atom(s.vars())
.and_then(str_to_oscode)
.ok_or_else(|| anyhow_expr!(op_expr, "invalid key name"))?;
let key_recency = parse_u8_with_range(&l[2], s, "key-recency", 1, 8)? - 1;
ops.push(OpCode::new_key_history(osc.into(), key_recency));
Ok(())
}
AllowedListOps::Input => {
if l.len() != 3 {
bail_expr!(
op_expr,
"input must have 2 parameters: key-type(virtual|real), key"
);
}
let input_type = match l[1]
.atom(s.vars())
.ok_or_else(|| anyhow_expr!(op_expr, "key-type must be virtual|real"))?
{
"real" => InputType::Real,
"fake" | "virtual" => InputType::Virtual,
_ => bail_expr!(op_expr, "key-type must be virtual|real"),
};
let input = l[2]
.atom(s.vars())
.ok_or_else(|| anyhow_expr!(op_expr, "input key name must not be a list"))?;
let input = match input_type {
InputType::Real => u16::from(
str_to_oscode(input)
.ok_or_else(|| anyhow_expr!(op_expr, "invalid input key name"))?,
),
InputType::Virtual => {
let vk = s.virtual_keys.get(input).ok_or_else(|| {
anyhow_expr!(op_expr, "virtual key name is not defined")
})?;
assert!(vk.0 < usize::from(KEY_MAX));
vk.0 as u16
}
};
let (op1, op2) = OpCode::new_active_input((input_type.to_row(), input));
ops.extend(&[op1, op2]);
Ok(())
}
AllowedListOps::InputHistory => {
if l.len() != 4 {
bail_expr!(op_expr, "input-history must have 3 parameters: key-type(virtual|real), key, key-recency");
}
let input_type = match l[1]
.atom(s.vars())
.ok_or_else(|| anyhow_expr!(op_expr, "key-type must be virtual|real"))?
{
"real" => InputType::Real,
"fake" | "virtual" => InputType::Virtual,
_ => bail_expr!(op_expr, "key-type must be virtual|real"),
};
let input = l[2]
.atom(s.vars())
.ok_or_else(|| anyhow_expr!(op_expr, "input key name must not be a list"))?;
let input = match input_type {
InputType::Real => u16::from(
str_to_oscode(input)
.ok_or_else(|| anyhow_expr!(op_expr, "invalid input key name"))?,
),
InputType::Virtual => {
let vk = s.virtual_keys.get(input).ok_or_else(|| {
anyhow_expr!(op_expr, "virtual key name is not defined")
})?;
assert!(vk.0 < usize::from(KEY_MAX));
vk.0 as u16
}
};
let key_recency = parse_u8_with_range(&l[3], s, "key-recency", 1, 8)? - 1;
let (op1, op2) =
OpCode::new_historical_input((input_type.to_row(), input), key_recency);
ops.extend(&[op1, op2]);
Ok(())
}
AllowedListOps::KeyTiming => {
if l.len() != 4 {
bail_expr!(
op_expr,
"key-timing must have 3 parameters: key-recency, lt|gt|less-than|greater-than, milliseconds (0-65535)"
);
}
let nth_key = parse_u8_with_range(&l[1], s, "key-recency", 1, 8)? - 1;
let ticks_since = parse_u16(&l[3], s, "milliseconds")?;
match l[2].atom(s.vars()).ok_or_else(|| {
anyhow_expr!(
&l[2],
"key-timing 2nd parameter must be one of: lt|gt|less-than|greater-than"
)
})? {
"less-than" | "lt" => {
ops.push(OpCode::new_ticks_since_lt(nth_key, ticks_since));
}
"greater-than" | "gt" => {
ops.push(OpCode::new_ticks_since_gt(nth_key, ticks_since));
}
_ => {
bail_expr!(
&l[2],
"key-timing 2nd parameter must be one of: lt|gt|less-than|greater-than"
);
}
};
s.switch_max_key_timing
.set(std::cmp::max(s.switch_max_key_timing.get(), ticks_since));
Ok(())
}
AllowedListOps::Or | AllowedListOps::And | AllowedListOps::Not => {
let op = match op {
AllowedListOps::Or => BooleanOperator::Or,
AllowedListOps::And => BooleanOperator::And,
AllowedListOps::Not => BooleanOperator::Not,
_ => unreachable!(),
};
let placeholder_index = ops.len() as u16;
ops.push(OpCode::new_bool(op, placeholder_index));
for op in l.iter().skip(1) {
parse_switch_case_bool(depth + 1, op, ops, s)?;
}
if ops.len() > usize::from(MAX_OPCODE_LEN) {
bail_expr!(op_expr, "switch logic length has been exceeded");
}
ops[placeholder_index as usize] = OpCode::new_bool(op, ops.len() as u16);
Ok(())
}
}
}
}