use chumsky::{error::Rich, Parser as ChumskyParser};
use crate::parser::{any_quoted_string, quoted_string, TokenError, TokenSource};
#[derive(Debug, Default, Clone, PartialEq)]
pub struct TextureAxis {
pub x: f32,
pub y: f32,
pub z: f32,
pub shift: f32,
pub scale: f32,
}
fn parse_texture_vector_str(numbers_str: &str) -> Result<(f32, f32, f32, f32), String> {
let mut parts = numbers_str.split_whitespace();
if let (Some(x_str), Some(y_str), Some(z_str), Some(shift_str)) =
(parts.next(), parts.next(), parts.next(), parts.next())
{
let x = x_str
.parse::<f32>()
.map_err(|e| format!("invalid x '{}': {}", x_str, e))?;
let y = y_str
.parse::<f32>()
.map_err(|e| format!("invalid y '{}': {}", y_str, e))?;
let z = z_str
.parse::<f32>()
.map_err(|e| format!("invalid z '{}': {}", z_str, e))?;
let shift = shift_str
.parse::<f32>()
.map_err(|e| format!("invalid shift '{}': {}", shift_str, e))?;
return Ok((x, y, z, shift));
} else {
return Err("expected multiple of 3 numbers".into());
}
}
pub(crate) fn key_value_texture_axis<'src, I>(
key: &'static str,
) -> impl ChumskyParser<'src, I, TextureAxis, TokenError<'src>>
where
I: TokenSource<'src>,
{
quoted_string(key) .ignore_then(any_quoted_string()) .try_map(move |value_str, span| {
let mut remainder = value_str.trim();
let vector_part_str: &str;
if let Some(open_idx) = remainder.find('[') {
remainder = &remainder[open_idx + 1..];
} else {
return Err(Rich::custom(
span,
"Missing opening bracket '[' for texture axis vector".to_string(),
));
}
if let Some(close_idx) = remainder.find(']') {
vector_part_str = &remainder[..close_idx];
remainder = &remainder[close_idx + 1..];
} else {
return Err(Rich::custom(
span,
"Missing closing bracket ']' for texture axis vector".to_string(),
));
}
let (x, y, z, shift) = match parse_texture_vector_str(vector_part_str) {
Ok(v) => v,
Err(err_msg) => {
return Err(Rich::custom(
span,
format!("Invalid texture vector: {err_msg} (in '{vector_part_str}')",),
));
}
};
let scale_str = remainder.trim();
if scale_str.is_empty() {
return Err(Rich::custom(
span,
"Missing scale value after texture vector".to_string(),
));
}
let scale = match scale_str.parse::<f32>() {
Ok(s) => s,
Err(e) => {
return Err(Rich::custom(
span,
format!("Invalid scale value '{scale_str}': {e}"),
));
}
};
Ok(TextureAxis {
x,
y,
z,
shift,
scale,
})
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::util::lex;
#[test]
fn test_parse_valid_uaxis() {
let stream = lex(r#""uaxis" "[1 0 0 0] 0.25""#); let parser = key_value_texture_axis("uaxis");
let result = parser.parse(stream).into_result();
assert!(result.is_ok(), "Parsing failed: {:?}", result.err());
let axis = result.unwrap();
assert_eq!(
axis,
TextureAxis {
x: 1.0,
y: 0.0,
z: 0.0,
shift: 0.0,
scale: 0.25
}
);
}
#[test]
fn test_parse_valid_vaxis_with_shift() {
let stream = lex(r#""vaxis" "[0 -1 0 128] 0.5""#);
let parser = key_value_texture_axis("vaxis");
let result = parser.parse(stream).into_result();
assert!(result.is_ok(), "Parsing failed: {:?}", result.err());
let axis = result.unwrap();
assert_eq!(
axis,
TextureAxis {
x: 0.0,
y: -1.0,
z: 0.0,
shift: 128.0,
scale: 0.5
}
);
}
#[test]
fn test_parse_texture_axis_missing_bracket_open() {
let stream = lex(r#""uaxis" "1 0 0 0] 0.25""#);
let parser = key_value_texture_axis("uaxis");
let result = parser.parse(stream).into_result();
assert!(result.is_err());
}
#[test]
fn test_parse_texture_axis_missing_bracket_close() {
let stream = lex(r#""uaxis" "[1 0 0 0 0.25""#);
let parser = key_value_texture_axis("uaxis");
let result = parser.parse(stream).into_result();
assert!(result.is_err());
}
#[test]
fn test_parse_texture_axis_malformed_vector_numbers() {
let stream = lex(r#""uaxis" "[1 0 oops 0] 0.25""#);
let parser = key_value_texture_axis("uaxis");
let result = parser.parse(stream).into_result();
assert!(result.is_err());
}
#[test]
fn test_parse_texture_axis_too_few_vector_numbers() {
let stream = lex(r#""uaxis" "[1 0 0] 0.25""#);
let parser = key_value_texture_axis("uaxis");
let result = parser.parse(stream).into_result();
assert!(result.is_err());
}
#[test]
fn test_parse_texture_axis_missing_scale() {
let stream = lex(r#""uaxis" "[1 0 0 0]""#); let parser = key_value_texture_axis("uaxis");
let result = parser.parse(stream).into_result();
assert!(result.is_err());
}
#[test]
fn test_parse_texture_axis_malformed_scale() {
let stream = lex(r#""uaxis" "[1 0 0 0] scale_text""#);
let parser = key_value_texture_axis("uaxis");
let result = parser.parse(stream).into_result();
assert!(result.is_err());
}
#[test]
fn test_parse_texture_axis_extra_garbage_after_scale() {
let stream = lex(r#""uaxis" "[1 0 0 0] 0.25 garbage""#);
let parser = key_value_texture_axis("uaxis");
let result = parser.parse(stream).into_result();
assert!(result.is_err()); }
}