pub const BASE36_RADIX: usize = 36;
pub const ALLOWED_SPECIAL_CHARS: &str = "*#$!%:?=;_";
pub const OPERATOR_SPECIAL_CHARS: &str = "*#$!%:?=;";
pub fn is_allowed(g: char) -> bool {
let gl = g.to_ascii_lowercase();
gl == '.' || gl.is_ascii_alphanumeric() || ALLOWED_SPECIAL_CHARS.contains(gl)
}
pub fn is_operator(g: char) -> bool {
let gl = g.to_ascii_lowercase();
gl.is_ascii_alphabetic() || OPERATOR_SPECIAL_CHARS.contains(gl)
}
pub fn value_of(g: char) -> usize {
g.to_digit(36).unwrap_or(0) as usize
}
pub fn key_of(val: usize, uppercase: bool) -> char {
let c = std::char::from_digit((val % BASE36_RADIX) as u32, BASE36_RADIX as u32).unwrap_or('0');
if uppercase { c.to_ascii_uppercase() } else { c }
}
pub fn operator_name(gl: char) -> &'static str {
match gl {
'a' => "add",
'b' => "subtract",
'c' => "clock",
'd' => "delay",
'e' => "east",
'f' => "if",
'g' => "generator",
'h' => "halt",
'i' => "increment",
'j' => "jumper",
'k' => "konkat",
'l' => "lesser",
'm' => "multiply",
'n' => "north",
'o' => "read",
'p' => "push",
'q' => "query",
'r' => "random",
's' => "south",
't' => "track",
'u' => "uclid",
'v' => "variable",
'w' => "west",
'x' => "write",
'y' => "jymper",
'z' => "lerp",
'*' => "bang",
'#' => "comment",
':' => "midi",
'%' => "mono",
'!' => "cc",
'?' => "pb",
'=' => "osc",
';' => "udp",
'$' => "self",
_ => "unknown",
}
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
#[test]
fn test_operator_name_edge_cases() {
assert_eq!(operator_name('\0'), "unknown");
assert_eq!(operator_name(' '), "unknown");
assert_eq!(operator_name('A'), "unknown");
assert_eq!(operator_name('5'), "unknown");
assert_eq!(operator_name('ё'), "unknown");
assert_eq!(operator_name('🍁'), "unknown");
}
#[test]
fn test_is_allowed() {
assert!(is_allowed('.'));
assert!(is_allowed('A'));
assert!(is_allowed('a'));
assert!(is_allowed('0'));
assert!(is_allowed('*'));
assert!(is_allowed('#'));
assert!(is_allowed('_'));
assert!(!is_allowed(' '));
assert!(!is_allowed('-'));
assert!(!is_allowed('\t'));
}
#[test]
fn test_is_operator() {
assert!(is_operator('A'));
assert!(is_operator('a'));
assert!(is_operator('*'));
assert!(is_operator('#'));
assert!(!is_operator('.'));
assert!(!is_operator('5'));
assert!(!is_operator('_'));
}
#[test]
fn test_base36_roundtrip() {
for val in 0..36usize {
let g = key_of(val, false);
assert_eq!(value_of(g), val);
}
}
proptest! {
#[test]
fn prop_operator_name_never_panics(c in any::<char>()) {
let _ = operator_name(c);
}
#[test]
fn prop_is_allowed_never_panics(c in any::<char>()) {
let _ = is_allowed(c);
}
#[test]
fn prop_value_of_and_key_of_roundtrip(val in any::<usize>()) {
let ch_lower = key_of(val, false);
assert_eq!(value_of(ch_lower), val % 36);
let ch_upper = key_of(val, true);
assert_eq!(value_of(ch_upper), val % 36);
}
}
}