use crate::Value;
fn split_array(s: &str) -> Vec<String> {
let mut nest = 0;
let mut parts = Vec::new();
let mut part = String::new();
for c in s.chars() {
if c == '[' {
part.push(c);
nest += 1;
} else if c == ']' {
nest -= 1;
part.push(c);
} else if c == ',' && nest == 0 {
parts.push(part.trim().to_string());
part = String::new();
} else {
part.push(c);
}
}
let part = part.trim().to_string();
if !part.is_empty() {
parts.push(part);
}
parts
}
#[derive(Debug, PartialEq, Eq)]
pub enum FromArmaError {
InvalidValue(String),
InvalidPrimitive(String),
InvalidLength {
expected: usize,
actual: usize,
},
MissingBracket(bool),
MissingField(String),
UnknownField(String),
DuplicateField(String),
Custom(String),
}
impl std::fmt::Display for FromArmaError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidValue(s) => write!(f, "invalid value: {s}"),
Self::InvalidPrimitive(s) => write!(f, "error parsing primitive: {s}"),
Self::InvalidLength { expected, actual } => {
write!(f, "expected {expected} elements, got {actual}")
}
Self::MissingBracket(start) => match *start {
true => write!(f, "missing '[' at start of array"),
false => write!(f, "missing ']' at end of array"),
},
Self::MissingField(s) => write!(f, "missing field: {s}"),
Self::UnknownField(s) => write!(f, "unknown field: {s}"),
Self::DuplicateField(s) => write!(f, "duplicate field: {s}"),
Self::Custom(s) => f.write_str(s),
}
}
}
impl FromArmaError {
pub fn custom(msg: impl std::fmt::Display) -> Self {
Self::Custom(msg.to_string())
}
}
pub trait FromArma: Sized {
fn from_arma(s: String) -> Result<Self, FromArmaError>;
}
#[cfg(not(any(test, doc, debug_assertions)))]
impl FromArma for String {
fn from_arma(s: String) -> Result<Self, FromArmaError> {
let Some(s) = s.strip_prefix('"').and_then(|s| s.strip_suffix('"')) else {
return Err(FromArmaError::InvalidPrimitive(String::from(
"missing '\"' at start or end of string",
)));
};
Ok(s.replace("\"\"", "\""))
}
}
#[cfg(any(test, doc, debug_assertions))]
impl FromArma for String {
fn from_arma(s: String) -> Result<Self, FromArmaError> {
let s = s
.strip_prefix('"')
.and_then(|s| s.strip_suffix('"'))
.unwrap_or(&s);
Ok(s.replace("\"\"", "\""))
}
}
macro_rules! impl_from_arma {
($($t:ty),*) => {
$(
impl FromArma for $t {
fn from_arma(s: String) -> Result<Self, FromArmaError> {
let s = s.strip_suffix('"').and_then(|s| s.strip_prefix('"')).unwrap_or(&s);
s.parse::<Self>().map_err(|e| FromArmaError::InvalidPrimitive(e.to_string()))
}
}
)*
};
}
impl_from_arma!(f32, f64, bool, char);
macro_rules! impl_from_arma_number {
($($t:ty),*) => {
$(
impl FromArma for $t {
fn from_arma(s: String) -> Result<Self, FromArmaError> {
let s = s.strip_suffix('"').and_then(|s| s.strip_prefix('"')).unwrap_or(&s);
if s.contains("e") {
let mut parts = s.split('e');
let base = match parts.next().unwrap() {
s if !s.is_empty() => s.parse::<f64>().map_err(|e| FromArmaError::InvalidPrimitive(e.to_string()))?,
_ => return Err(FromArmaError::InvalidPrimitive("invalid number literal".to_string())),
};
let exp = match parts.next().unwrap() {
s if !s.is_empty() => s.parse::<i32>().map_err(|e| FromArmaError::InvalidPrimitive(e.to_string()))?,
_ => return Err(FromArmaError::InvalidPrimitive("invalid number literal".to_string())),
};
return Ok((base * 10.0_f64.powi(exp)) as $t);
}
s.parse::<Self>().map_err(|e| FromArmaError::InvalidPrimitive(e.to_string()))
}
}
)*
};
}
impl_from_arma_number!(i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize);
macro_rules! impl_from_arma_tuple {
{ $c: expr, $($t:ident)* } => {
impl<$($t),*> FromArma for ($($t),*)
where
$($t: FromArma),*
{
fn from_arma(s: String) -> Result<Self, FromArmaError> {
let v: [Value; $c] = FromArma::from_arma(s)?;
let mut iter = v.iter();
Ok((
$($t::from_arma(iter.next().unwrap().to_string())?),*
))
}
}
};
}
impl_from_arma_tuple! { 2, A B }
impl_from_arma_tuple! { 3, A B C }
impl_from_arma_tuple! { 4, A B C D }
impl_from_arma_tuple! { 5, A B C D E }
impl_from_arma_tuple! { 6, A B C D E F }
impl_from_arma_tuple! { 7, A B C D E F G }
impl_from_arma_tuple! { 8, A B C D E F G H }
impl_from_arma_tuple! { 9, A B C D E F G H I }
impl_from_arma_tuple! { 10, A B C D E F G H I J }
impl_from_arma_tuple! { 11, A B C D E F G H I J K }
impl_from_arma_tuple! { 12, A B C D E F G H I J K L }
impl_from_arma_tuple! { 13, A B C D E F G H I J K L M }
impl_from_arma_tuple! { 14, A B C D E F G H I J K L M N }
impl_from_arma_tuple! { 15, A B C D E F G H I J K L M N O }
impl_from_arma_tuple! { 16, A B C D E F G H I J K L M N O P }
impl_from_arma_tuple! { 17, A B C D E F G H I J K L M N O P Q }
impl_from_arma_tuple! { 18, A B C D E F G H I J K L M N O P Q R }
impl_from_arma_tuple! { 19, A B C D E F G H I J K L M N O P Q R S }
impl_from_arma_tuple! { 20, A B C D E F G H I J K L M N O P Q R S T }
impl_from_arma_tuple! { 21, A B C D E F G H I J K L M N O P Q R S T U }
impl_from_arma_tuple! { 22, A B C D E F G H I J K L M N O P Q R S T U V }
impl_from_arma_tuple! { 23, A B C D E F G H I J K L M N O P Q R S T U V W }
impl_from_arma_tuple! { 24, A B C D E F G H I J K L M N O P Q R S T U V W X }
impl_from_arma_tuple! { 25, A B C D E F G H I J K L M N O P Q R S T U V W X Y }
impl_from_arma_tuple! { 26, A B C D E F G H I J K L M N O P Q R S T U V W X Y Z }
impl<T> FromArma for Vec<T>
where
T: FromArma,
{
fn from_arma(s: String) -> Result<Self, FromArmaError> {
let source = s
.strip_prefix('[')
.ok_or(FromArmaError::MissingBracket(true))?
.strip_suffix(']')
.ok_or(FromArmaError::MissingBracket(false))?;
let parts = split_array(source);
parts.iter().try_fold(Self::new(), |mut acc, p| {
acc.push(T::from_arma(p.to_string())?);
Ok(acc)
})
}
}
impl<T, const N: usize> FromArma for [T; N]
where
T: FromArma,
{
fn from_arma(s: String) -> Result<Self, FromArmaError> {
let v: Vec<T> = FromArma::from_arma(s)?;
let len = v.len();
v.try_into().map_err(|_| FromArmaError::InvalidLength {
expected: N,
actual: len,
})
}
}
impl<K, V, S> FromArma for std::collections::HashMap<K, V, S>
where
K: FromArma + Eq + std::hash::Hash,
V: FromArma,
S: std::hash::BuildHasher + Default,
{
fn from_arma(s: String) -> Result<Self, FromArmaError> {
let data: Vec<(K, V)> = FromArma::from_arma(s)?;
let mut ret = Self::default();
for (k, v) in data {
ret.insert(k, v);
}
Ok(ret)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Value;
#[test]
fn parse_tuple_varying_types() {
assert_eq!(
(String::from("hello"), 123),
<(String, i32)>::from_arma(r#"["hello", 123]"#.to_string()).unwrap()
);
assert!(<(String, String)>::from_arma(r#"["hello", 123, "world"]"#.to_string()).is_err());
}
#[test]
fn parse_tuple_size_errors() {
assert_eq!(
<(String, i32)>::from_arma(r#"[]"#.to_string()),
Err(FromArmaError::InvalidLength {
expected: 2,
actual: 0
})
);
assert_eq!(
<(String, i32)>::from_arma(r#"["hello"]"#.to_string()),
Err(FromArmaError::InvalidLength {
expected: 2,
actual: 1
})
);
assert_eq!(
<(String, i32)>::from_arma(r#"["hello", 123, 456]"#.to_string()),
Err(FromArmaError::InvalidLength {
expected: 2,
actual: 3
})
);
}
#[test]
fn parse_tuple_bracket_errors() {
assert_eq!(
<(String, i32)>::from_arma(r#"["hello", 123"#.to_string()),
Err(FromArmaError::MissingBracket(false))
);
assert_eq!(
<(String, i32)>::from_arma(r#""hello", 123"#.to_string()),
Err(FromArmaError::MissingBracket(true))
);
}
#[test]
fn test_tuple_2() {
assert_eq!(
(0, 1),
<(u8, u8)>::from_arma(r#"[0, 1]"#.to_string()).unwrap()
);
}
#[test]
fn test_tuple_3() {
assert_eq!(
(0, 1, 2),
<(u8, u8, u8)>::from_arma(r#"[0, 1, 2]"#.to_string()).unwrap()
);
}
#[test]
fn test_tuple_4() {
assert_eq!(
(0, 1, 2, 3),
<(u8, u8, u8, u8)>::from_arma(r#"[0, 1, 2, 3]"#.to_string()).unwrap()
);
}
#[test]
fn test_tuple_5() {
assert_eq!(
(0, 1, 2, 3, 4),
<(u8, u8, u8, u8, u8)>::from_arma(r#"[0, 1, 2, 3, 4]"#.to_string()).unwrap()
);
}
#[test]
fn test_tuple_6() {
assert_eq!(
(0, 1, 2, 3, 4, 5),
<(u8, u8, u8, u8, u8, u8)>::from_arma(r#"[0, 1, 2, 3, 4, 5]"#.to_string()).unwrap()
);
}
#[test]
fn test_tuple_7() {
assert_eq!(
(0, 1, 2, 3, 4, 5, 6),
<(u8, u8, u8, u8, u8, u8, u8)>::from_arma(r#"[0, 1, 2, 3, 4, 5, 6]"#.to_string())
.unwrap()
);
}
#[test]
fn test_tuple_8() {
assert_eq!(
(0, 1, 2, 3, 4, 5, 6, 7),
<(u8, u8, u8, u8, u8, u8, u8, u8)>::from_arma(
r#"[0, 1, 2, 3, 4, 5, 6, 7]"#.to_string()
)
.unwrap()
);
}
#[test]
fn test_tuple_9() {
assert_eq!(
(0, 1, 2, 3, 4, 5, 6, 7, 8),
<(u8, u8, u8, u8, u8, u8, u8, u8, u8)>::from_arma(
r#"[0, 1, 2, 3, 4, 5, 6, 7, 8]"#.to_string()
)
.unwrap()
);
}
#[test]
fn test_tuple_10() {
assert_eq!(
(0, 1, 2, 3, 4, 5, 6, 7, 8, 9),
<(u8, u8, u8, u8, u8, u8, u8, u8, u8, u8)>::from_arma(
r#"[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]"#.to_string()
)
.unwrap()
);
}
#[test]
fn parse_string() {
assert_eq!(
String::from("hello"),
<String>::from_arma(r#""hello""#.to_string()).unwrap()
);
assert_eq!(
String::from("\"hello\""),
<String>::from_arma(r#"""hello"""#.to_string()).unwrap()
);
assert_eq!(
String::from(r#"hello "john"."#),
<String>::from_arma(r#""hello ""john"".""#.to_string()).unwrap()
);
}
#[test]
fn parse_vec() {
assert_eq!(
vec![String::from("hello"), String::from("bye"),],
<Vec<String>>::from_arma(r#"["hello","bye"]"#.to_string()).unwrap()
);
assert_eq!(
vec![String::from("hello"), String::from("world")],
<Vec<String>>::from_arma(r#"[hello, "world"]"#.to_string()).unwrap()
);
}
#[test]
fn parse_vec_bracket_errors() {
assert_eq!(
<Vec<String>>::from_arma(r#""hello","bye"]"#.to_string()),
Err(FromArmaError::MissingBracket(true))
);
assert_eq!(
<Vec<String>>::from_arma(r#"["hello","bye""#.to_string()),
Err(FromArmaError::MissingBracket(false))
);
}
#[test]
fn parse_vec_tuple() {
assert_eq!(
(vec![(String::from("hello"), 123), (String::from("bye"), 321),]),
<Vec<(String, i32)>>::from_arma(r#"[["hello", 123],["bye", 321]]"#.to_string())
.unwrap()
);
}
#[test]
fn parse_slice() {
assert_eq!(
vec![String::from("hello"), String::from("bye"),],
<[String; 2]>::from_arma(r#"["hello","bye"]"#.to_string()).unwrap()
);
}
#[test]
fn parse_slice_size_errors() {
assert_eq!(
<[String; 2]>::from_arma(r#"[]"#.to_string()),
Err(FromArmaError::InvalidLength {
expected: 2,
actual: 0
})
);
assert_eq!(
<[String; 2]>::from_arma(r#"["hello"]"#.to_string()),
Err(FromArmaError::InvalidLength {
expected: 2,
actual: 1
})
);
assert_eq!(
<[String; 2]>::from_arma(r#"["hello","bye","world"]"#.to_string()),
Err(FromArmaError::InvalidLength {
expected: 2,
actual: 3
})
);
}
#[test]
fn parse_hashmap() {
assert_eq!(
std::collections::HashMap::from([
(String::from("hello"), 123),
(String::from("bye"), 321),
]),
<std::collections::HashMap<String, i32>>::from_arma(
r#"[["hello", 123],["bye",321]]"#.to_string()
)
.unwrap()
);
assert_eq!(
std::collections::HashMap::from([
(String::from("hello"), 123),
(String::from("bye"), 321),
(String::from("hello"), 321),
]),
<std::collections::HashMap<String, i32>>::from_arma(
r#"[["hello", 123],["bye",321],["hello", 321]]"#.to_string()
)
.unwrap()
);
}
#[test]
fn parse_exponential() {
assert_eq!(1.0e-10, <f64>::from_arma(r#"1.0e-10"#.to_string()).unwrap());
assert_eq!(
1_227_700,
<u32>::from_arma(r#"1.2277e+006"#.to_string()).unwrap()
);
}
#[test]
fn parse_exponential_errors() {
assert_eq!(
<f64>::from_arma(r#"e-10"#.to_string()),
Err(FromArmaError::InvalidPrimitive(
"invalid float literal".to_string()
))
);
assert_eq!(
<f64>::from_arma(r#"1.0e"#.to_string()),
Err(FromArmaError::InvalidPrimitive(
"invalid float literal".to_string()
))
);
assert_eq!(
<u32>::from_arma(r#"e-10"#.to_string()),
Err(FromArmaError::InvalidPrimitive(
"invalid number literal".to_string()
))
);
assert_eq!(
<u32>::from_arma(r#"1.0e"#.to_string()),
Err(FromArmaError::InvalidPrimitive(
"invalid number literal".to_string()
))
);
}
#[test]
fn parse_value_tuple() {
assert_eq!(
(
Value::String(String::from("hello")),
Value::String(String::from("world"))
),
<(Value, Value)>::from_arma(r#"["hello", "world"]"#.to_string()).unwrap()
);
}
#[test]
fn parse_value_vec() {
assert_eq!(
vec![
Value::String(String::from("hello")),
Value::String(String::from("world"))
],
<Vec<Value>>::from_arma(r#"["hello", "world"]"#.to_string()).unwrap()
);
}
#[test]
fn parse_float() {
assert_eq!(1.0, <f64>::from_arma(r#"1.0"#.to_string()).unwrap());
assert_eq!(1.0, <f64>::from_arma(r#"1"#.to_string()).unwrap());
assert_eq!(1.0, <f64>::from_arma(r#"1.0e+0"#.to_string()).unwrap());
assert_eq!(-1.0, <f64>::from_arma(r#"-1.0"#.to_string()).unwrap());
assert_eq!(1.0, <f64>::from_arma(r#""1.0""#.to_string()).unwrap());
assert_eq!(1.0, <f64>::from_arma(r#""1""#.to_string()).unwrap());
assert_eq!(1.0, <f64>::from_arma(r#""1.0e+0""#.to_string()).unwrap());
assert_eq!(-1.0, <f64>::from_arma(r#""-1.0""#.to_string()).unwrap());
}
}