1use chumsky::Parser as ChumskyParser;
2
3use crate::parser::{any_quoted_string, TokenError, TokenSource};
4
5#[derive(Debug, Default, Clone)]
7pub struct EntityOutput<'src> {
8 pub output_name: &'src str,
9 pub target: &'src str,
10 pub input: &'src str,
11 pub parameter: &'src str,
12 pub delay: f32,
13 pub times_to_fire: i32,
14}
15
16impl<'src> EntityOutput<'src> {
17 pub fn parse_output_string(output_name: &'src str, value: &'src str) -> Result<Self, String> {
20 let mut parts = value.split(',').map(|split| split.trim());
21
22 let (target, input, parameter, delay, times_to_fire) = match (
23 parts.next(),
24 parts.next(),
25 parts.next(),
26 parts.next(),
27 parts.next(),
28 ) {
29 (Some(a), Some(b), Some(c), Some(d), Some(e)) => {
30 let delay = d
31 .parse::<f32>()
32 .map_err(|e| format!("invalid delay '{}': {}", d, e))?;
33 let times_to_fire = e
34 .parse::<i32>()
35 .map_err(|e| format!("invalid times_to_fire '{}': {}", e, e))?;
36 (a, b, c, delay, times_to_fire)
37 }
38 _ => return Err("expected at least 5 comma-separated values".into()),
39 };
40
41 Ok(EntityOutput::<'src> {
42 output_name,
43 target,
44 input,
45 parameter,
46 delay,
47 times_to_fire,
48 })
49 }
50}
51
52pub(crate) fn parse_output_entry<'src, I>(
55) -> impl ChumskyParser<'src, I, EntityOutput<'src>, TokenError<'src>>
56where
57 I: TokenSource<'src>,
58{
59 use chumsky::error::Rich;
60
61 any_quoted_string()
62 .then(any_quoted_string())
63 .try_map(|(output_name, value_str), span| {
64 EntityOutput::parse_output_string(output_name, value_str)
65 .map_err(|err_msg| Rich::custom(span, format!("Invalid output: {}", err_msg)))
66 })
67}
68
69#[cfg(test)]
70mod tests {
71 use super::*;
72 use crate::util::lex;
73
74 #[test]
75 fn test_parse_output_string_complete() {
76 let result = EntityOutput::parse_output_string("OnIn", "motor*,TurnOn,,0,-1");
77 assert!(result.is_ok());
78
79 let output = result.unwrap();
80 assert_eq!(output.output_name, "OnIn");
81 assert_eq!(output.target, "motor*");
82 assert_eq!(output.input, "TurnOn");
83 assert_eq!(output.parameter, "");
84 assert_eq!(output.delay, 0.0);
85 assert_eq!(output.times_to_fire, -1);
86 }
87
88 #[test]
89 fn test_parse_output_string_with_parameter() {
90 let result = EntityOutput::parse_output_string("OnStartTouch", "door1,Open,fast,0.5,1");
91 assert!(result.is_ok());
92
93 let output = result.unwrap();
94 assert_eq!(output.output_name, "OnStartTouch");
95 assert_eq!(output.target, "door1");
96 assert_eq!(output.input, "Open");
97 assert_eq!(output.parameter, "fast");
98 assert_eq!(output.delay, 0.5);
99 assert_eq!(output.times_to_fire, 1);
100 }
101
102 #[test]
103 fn test_parse_output_string_invalid_format() {
104 let result = EntityOutput::parse_output_string("OnUse", "target,input,param");
105 assert!(result.is_err());
106 }
107
108 #[test]
109 fn test_parse_output_string_invalid_delay() {
110 let result = EntityOutput::parse_output_string("OnUse", "target,input,,not_a_number,-1");
111 assert!(result.is_err());
112 }
113
114 #[test]
115 fn test_parse_output_string_invalid_times() {
116 let result = EntityOutput::parse_output_string("OnUse", "target,input,,0,not_a_number");
117 assert!(result.is_err());
118 }
119
120 #[test]
121 fn test_parse_output_entry() {
122 let input = r#""OnIn" "motor*,TurnOn,,0,-1""#;
123 let stream = lex(input);
124
125 let parser = parse_output_entry();
126 let result = parser.parse(stream).into_result();
127
128 assert!(result.is_ok(), "Parsing failed: {:?}", result.err());
129 let output = result.unwrap();
130 assert_eq!(output.output_name, "OnIn");
131 assert_eq!(output.target, "motor*");
132 assert_eq!(output.input, "TurnOn");
133 assert_eq!(output.delay, 0.0);
134 assert_eq!(output.times_to_fire, -1);
135 }
136
137 #[test]
138 fn test_parse_output_entry_with_spaces() {
139 let input = r#""OnOut" "motor*, TurnOff, , 0.5, 1""#;
140 let stream = lex(input);
141
142 let parser = parse_output_entry();
143 let result = parser.parse(stream).into_result();
144
145 assert!(result.is_ok(), "Parsing failed: {:?}", result.err());
146 let output = result.unwrap();
147 assert_eq!(output.output_name, "OnOut");
148 assert_eq!(output.target, "motor*");
149 assert_eq!(output.input, "TurnOff");
150 assert_eq!(output.parameter, "");
151 assert_eq!(output.delay, 0.5);
152 assert_eq!(output.times_to_fire, 1);
153 }
154}