1use log::error;
2
3use crate::{Lexer, Token, TokenType};
4
5#[derive(PartialEq)]
6pub struct Message {
7 pub prefix: Option<String>,
8 pub command: String,
9 pub params: Vec<String>,
10}
11
12pub struct Parser<'a> {
13 lexer: Lexer<'a>,
14}
15
16impl<'a> Parser<'a> {
17 pub fn new(message: &'a str) -> Self {
18 let lexer = Lexer::new(message);
19
20 Parser { lexer }
21 }
22
23 pub fn parse_message(&mut self) -> Message {
24 let mut token = self.lexer.next_token();
25
26 let prefix = self.parse_prefix(&token);
28 token = if prefix.is_some() {
29 self.lexer.next_token()
31 } else {
32 token
33 };
34
35 let command = self.parse_command(&token);
36
37 let params = self.parse_params();
38
39 Message {
40 prefix,
41 command,
42 params,
43 }
44 }
45
46 fn parse_prefix(&mut self, token: &Token) -> Option<String> {
47 if let TokenType::Colon = token.token_type {
48 let prefix_token = self.lexer.next_token();
49 if prefix_token.token_type != TokenType::Word {
50 error!(
51 "parse_prefix: Expected prefix after ':', got {}",
52 prefix_token.literal
53 );
54 panic!("Expected prefix after ':'");
55 }
56
57 let space_token = self.lexer.next_token();
58 if space_token.token_type != TokenType::Space {
59 error!(
60 "parse_prefix: Expected space after prefix, got {}",
61 space_token.literal
62 );
63 panic!("Expected space after prefix");
64 }
65
66 return Some(prefix_token.literal.clone());
67 }
68 None
69 }
70
71 fn parse_command(&mut self, token: &Token) -> String {
72 if token.token_type != TokenType::Word {
73 error!(
74 "parse_command: Expected command token, got {}",
75 token.literal
76 );
77 panic!("Expected command token");
78 } else if !Self::is_only_based_on_letters(&token.literal)
79 && !Self::is_three_digit_number(&token.literal)
80 {
81 error!(
82 "parse_command: Command must be letters or 3 digits, got {}",
83 token.literal
84 );
85 panic!("Command must consist of letters only or a number with three digits.");
86 } else {
87 token.literal.clone()
88 }
89 }
90
91 fn parse_params(&mut self) -> Vec<String> {
92 let mut params = Vec::new();
93 let mut token = self.lexer.next_token();
94
95 if token.token_type == TokenType::CrLf || token.token_type == TokenType::EOF {
96 return Vec::new();
97 }
98
99 while token.token_type == TokenType::Space {
100 let mut param_token = self.lexer.next_token();
101
102 while param_token.token_type == TokenType::Space {
104 param_token = self.lexer.next_token();
105 }
106
107 match param_token.token_type {
108 TokenType::Word => {
109 if param_token.literal.starts_with(':') {
110 let mut trailing = param_token.literal[1..].to_string();
111
112 loop {
113 let next = self.lexer.next_token();
114 match next.token_type {
115 TokenType::CrLf | TokenType::EOF => break,
116 _ => trailing.push_str(&next.literal),
117 }
118 }
119
120 params.push(trailing);
121 return params;
122 } else {
123 params.push(param_token.literal);
124 }
125 }
126 TokenType::CrLf | TokenType::EOF => return params,
127 _ => {
128 panic!("Expected parameter token")
129 }
130 }
131
132 token = self.lexer.next_token();
133 if token.token_type == TokenType::CrLf || token.token_type == TokenType::EOF {
134 return params;
135 }
136 }
137
138 if token.token_type != TokenType::CrLf && token.token_type != TokenType::EOF {
139 error!(
140 "parse_params: Expected new line or end of file, got {}",
141 token.literal
142 );
143 panic!("Expected new line or end of file");
144 }
145
146 params
147 }
148
149 fn is_only_based_on_letters(value: &str) -> bool {
150 value.chars().all(|c| c.is_ascii_alphabetic())
151 }
152
153 fn is_three_digit_number(value: &str) -> bool {
154 if value.len() != 3 {
155 return false;
156 }
157
158 value.chars().all(|c| c.is_ascii_digit())
159 }
160}
161
162#[cfg(test)]
163mod tests {
164 use super::*;
165
166 #[test]
167 fn test_extracting_prefix_from_message() {
168 let message = ":copper.libera.chat NOTICE * :*** Checking Ident\r\n";
169 let mut parser = Parser::new(message);
170
171 let parsed_message = parser.parse_message();
172
173 assert_eq!(
174 Some("copper.libera.chat".to_string()),
175 parsed_message.prefix
176 );
177 }
178
179 #[test]
180 fn test_message_without_prefix() {
181 let message = "NOTICE * :*** Checking Ident\r\n";
182 let mut parser = Parser::new(message);
183
184 let parsed_message = parser.parse_message();
185
186 assert_eq!(None, parsed_message.prefix);
187 }
188
189 #[test]
190 fn test_extracting_notice_command() {
191 let message = ":copper.libera.chat NOTICE * :*** Checking Ident\r\n";
192 let mut parser = Parser::new(message);
193
194 let parsed_message = parser.parse_message();
195
196 assert_eq!("NOTICE", parsed_message.command);
197 }
198
199 #[test]
200 fn test_invalid_command_throws_exception() {
201 let message = ":copper.libera.chat N0T1C3 * :*** Checking Ident\r\n";
202 let mut parser = Parser::new(message);
203
204 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
205 parser.parse_message();
206 }));
207
208 assert!(result.is_err());
209 }
210
211 #[test]
212 fn test_numeric_command() {
213 let message = ":copper.libera.chat 001 copper :Welcome to the IRC server\r\n";
214 let mut parser = Parser::new(message);
215
216 let parsed_message = parser.parse_message();
217
218 assert_eq!("001", parsed_message.command);
219 }
220
221 #[test]
222 fn parse_simple_message() {
223 let message = "foo bar baz asdf";
224 let mut parser = Parser::new(message);
225
226 let parsed_message = parser.parse_message();
227
228 assert_eq!(None, parsed_message.prefix);
229 assert_eq!("foo", parsed_message.command);
230 assert_eq!(
231 vec!["bar".to_string(), "baz".to_string(), "asdf".to_string()],
232 parsed_message.params
233 );
234 }
235
236 #[test]
237 fn parse_message_with_prefix() {
238 let message = ":coolguy foo bar baz asdf";
239 let mut parser = Parser::new(message);
240
241 let parsed_message = parser.parse_message();
242
243 assert_eq!(Some("coolguy".to_string()), parsed_message.prefix);
244 assert_eq!("foo", parsed_message.command);
245 assert_eq!(
246 vec!["bar".to_string(), "baz".to_string(), "asdf".to_string()],
247 parsed_message.params
248 );
249 }
250
251 #[test]
252 fn parse_message_with_trailing_param() {
253 let message = "foo bar baz :asdf quux";
254 let mut parser = Parser::new(message);
255
256 let parsed_message = parser.parse_message();
257
258 assert_eq!(None, parsed_message.prefix);
259 assert_eq!("foo", parsed_message.command);
260 assert_eq!(
261 vec![
262 "bar".to_string(),
263 "baz".to_string(),
264 "asdf quux".to_string()
265 ],
266 parsed_message.params
267 );
268 }
269
270 #[test]
271 fn parse_message_with_empty_trailing_param() {
272 let message = "foo bar baz :";
273 let mut parser = Parser::new(message);
274
275 let parsed_message = parser.parse_message();
276
277 assert_eq!(None, parsed_message.prefix);
278 assert_eq!("foo", parsed_message.command);
279 assert_eq!(
280 vec!["bar".to_string(), "baz".to_string(), "".to_string()],
281 parsed_message.params
282 );
283 }
284
285 #[test]
286 fn parse_message_with_colon_trailing_param() {
287 let message = "foo bar baz ::asdf";
288 let mut parser = Parser::new(message);
289
290 let parsed_message = parser.parse_message();
291
292 assert_eq!(None, parsed_message.prefix);
293 assert_eq!("foo", parsed_message.command);
294 assert_eq!(
295 vec!["bar".to_string(), "baz".to_string(), ":asdf".to_string()],
296 parsed_message.params
297 );
298 }
299
300 #[test]
301 fn parse_message_with_prefix_and_trailing() {
302 let message = ":coolguy foo bar baz :asdf quux";
303 let mut parser = Parser::new(message);
304
305 let parsed_message = parser.parse_message();
306
307 assert_eq!(Some("coolguy".to_string()), parsed_message.prefix);
308 assert_eq!("foo", parsed_message.command);
309 assert_eq!(
310 vec![
311 "bar".to_string(),
312 "baz".to_string(),
313 "asdf quux".to_string()
314 ],
315 parsed_message.params
316 );
317 }
318
319 #[test]
320 fn parse_message_with_prefix_and_spacey_trailing() {
321 let message = ":coolguy foo bar baz : asdf quux ";
322 let mut parser = Parser::new(message);
323
324 let parsed_message = parser.parse_message();
325
326 assert_eq!(Some("coolguy".to_string()), parsed_message.prefix);
327 assert_eq!("foo", parsed_message.command);
328 assert_eq!(
329 vec![
330 "bar".to_string(),
331 "baz".to_string(),
332 " asdf quux ".to_string()
333 ],
334 parsed_message.params
335 );
336 }
337
338 #[test]
339 fn parse_message_privmsg_trailing() {
340 let message = ":coolguy PRIVMSG bar :lol :) ";
341 let mut parser = Parser::new(message);
342
343 let parsed_message = parser.parse_message();
344
345 assert_eq!(Some("coolguy".to_string()), parsed_message.prefix);
346 assert_eq!("PRIVMSG", parsed_message.command);
347 assert_eq!(
348 vec!["bar".to_string(), "lol :) ".to_string()],
349 parsed_message.params
350 );
351 }
352
353 #[test]
354 fn parse_message_with_prefix_and_empty_trailing() {
355 let message = ":coolguy foo bar baz :";
356 let mut parser = Parser::new(message);
357
358 let parsed_message = parser.parse_message();
359
360 assert_eq!(Some("coolguy".to_string()), parsed_message.prefix);
361 assert_eq!("foo", parsed_message.command);
362 assert_eq!(
363 vec!["bar".to_string(), "baz".to_string(), "".to_string()],
364 parsed_message.params
365 );
366 }
367
368 #[test]
369 fn parse_message_with_prefix_and_blank_trailing() {
370 let message = ":coolguy foo bar baz : ";
371 let mut parser = Parser::new(message);
372
373 let parsed_message = parser.parse_message();
374
375 assert_eq!(Some("coolguy".to_string()), parsed_message.prefix);
376 assert_eq!("foo", parsed_message.command);
377 assert_eq!(
378 vec!["bar".to_string(), "baz".to_string(), " ".to_string()],
379 parsed_message.params
380 );
381 }
382
383 #[test]
384 fn parse_message_join_with_last_param() {
385 let message = ":src JOIN #chan";
386 let mut parser = Parser::new(message);
387
388 let parsed_message = parser.parse_message();
389
390 assert_eq!(Some("src".to_string()), parsed_message.prefix);
391 assert_eq!("JOIN", parsed_message.command);
392 assert_eq!(vec!["#chan".to_string()], parsed_message.params);
393 }
394
395 #[test]
396 fn parse_message_join_with_trailing_last_param() {
397 let message = ":src JOIN :#chan";
398 let mut parser = Parser::new(message);
399
400 let parsed_message = parser.parse_message();
401
402 assert_eq!(Some("src".to_string()), parsed_message.prefix);
403 assert_eq!("JOIN", parsed_message.command);
404 assert_eq!(vec!["#chan".to_string()], parsed_message.params);
405 }
406
407 #[test]
408 fn parse_message_away_without_param() {
409 let message = ":src AWAY";
410 let mut parser = Parser::new(message);
411
412 let parsed_message = parser.parse_message();
413
414 assert_eq!(Some("src".to_string()), parsed_message.prefix);
415 assert_eq!("AWAY", parsed_message.command);
416 assert_eq!(Vec::<String>::new(), parsed_message.params);
417 }
418
419 #[test]
420 fn parse_message_away_without_param_with_space() {
421 let message = ":src AWAY ";
422 let mut parser = Parser::new(message);
423
424 let parsed_message = parser.parse_message();
425
426 assert_eq!(Some("src".to_string()), parsed_message.prefix);
427 assert_eq!("AWAY", parsed_message.command);
428 assert_eq!(Vec::<String>::new(), parsed_message.params);
429 }
430
431 #[test]
432 fn parse_message_tab_not_space() {
433 let message = ":cool\tguy foo bar baz";
434 let mut parser = Parser::new(message);
435
436 let parsed_message = parser.parse_message();
437
438 assert_eq!(Some("cool\tguy".to_string()), parsed_message.prefix);
439 assert_eq!("foo", parsed_message.command);
440 assert_eq!(
441 vec!["bar".to_string(), "baz".to_string()],
442 parsed_message.params
443 );
444 }
445
446 #[test]
447 fn parse_message_with_prefix_and_control_codes_1() {
448 let message = ":coolguy!ag@net\x035w\x03ork.admin PRIVMSG foo :bar baz";
449 let mut parser = Parser::new(message);
450
451 let parsed_message = parser.parse_message();
452
453 assert_eq!(
454 Some("coolguy!ag@net\x035w\x03ork.admin".to_string()),
455 parsed_message.prefix
456 );
457 assert_eq!("PRIVMSG", parsed_message.command);
458 assert_eq!(
459 vec!["foo".to_string(), "bar baz".to_string()],
460 parsed_message.params
461 );
462 }
463
464 #[test]
465 fn parse_message_source_with_control_codes_2() {
466 let message = ":coolguy!~ag@n\x02et\x0305w\x0fork.admin PRIVMSG foo :bar baz";
467 let mut parser = Parser::new(message);
468
469 let parsed_message = parser.parse_message();
470
471 assert_eq!(
472 Some("coolguy!~ag@n\x02et\x0305w\x0fork.admin".to_string()),
473 parsed_message.prefix
474 );
475 assert_eq!("PRIVMSG", parsed_message.command);
476 assert_eq!(
477 vec!["foo".to_string(), "bar baz".to_string()],
478 parsed_message.params
479 );
480 }
481
482 #[test]
483 fn parse_message_with_trailing_params() {
484 let message = ":irc.example.com COMMAND param1 param2 :param3 param3";
485 let mut parser = Parser::new(message);
486
487 let parsed_message = parser.parse_message();
488
489 assert_eq!(Some("irc.example.com".to_string()), parsed_message.prefix);
490 assert_eq!("COMMAND", parsed_message.command);
491 assert_eq!(
492 vec![
493 "param1".to_string(),
494 "param2".to_string(),
495 "param3 param3".to_string()
496 ],
497 parsed_message.params
498 );
499 }
500
501 #[test]
502 fn parse_message_command_only() {
503 let message = "COMMAND";
504 let mut parser = Parser::new(message);
505
506 let parsed_message = parser.parse_message();
507
508 assert_eq!(None, parsed_message.prefix);
509 assert_eq!("COMMAND", parsed_message.command);
510 assert_eq!(Vec::<String>::new(), parsed_message.params);
511 }
512
513 #[test]
514 fn parse_message_with_broken_unreal_erroneous_nick() {
515 let message = ":gravel.mozilla.org 432 #momo :Erroneous Nickname: Illegal characters";
516 let mut parser = Parser::new(message);
517
518 let parsed_message = parser.parse_message();
519
520 assert_eq!(
521 Some("gravel.mozilla.org".to_string()),
522 parsed_message.prefix
523 );
524 assert_eq!("432", parsed_message.command);
525 assert_eq!(
526 vec![
527 "#momo".to_string(),
528 "Erroneous Nickname: Illegal characters".to_string()
529 ],
530 parsed_message.params
531 );
532 }
533
534 #[test]
535 fn parse_message_with_broken_unreal_mode_plus_n() {
536 let message = ":gravel.mozilla.org MODE #tckk +n ";
537 let mut parser = Parser::new(message);
538
539 let parsed_message = parser.parse_message();
540
541 assert_eq!(
542 Some("gravel.mozilla.org".to_string()),
543 parsed_message.prefix
544 );
545 assert_eq!("MODE", parsed_message.command);
546 assert_eq!(
547 vec!["#tckk".to_string(), "+n".to_string()],
548 parsed_message.params
549 );
550 }
551
552 #[test]
553 fn parse_message_with_broken_unreal_mode_plus_o() {
554 let message = ":services.esper.net MODE #foo-bar +o foobar ";
555 let mut parser = Parser::new(message);
556
557 let parsed_message = parser.parse_message();
558
559 assert_eq!(
560 Some("services.esper.net".to_string()),
561 parsed_message.prefix
562 );
563 assert_eq!("MODE", parsed_message.command);
564 assert_eq!(
565 vec![
566 "#foo-bar".to_string(),
567 "+o".to_string(),
568 "foobar".to_string()
569 ],
570 parsed_message.params
571 );
572 }
573
574 #[test]
575 fn parse_message_mode_trailing_plus_i() {
576 let message = ":SomeOp MODE #channel :+i";
577 let mut parser = Parser::new(message);
578
579 let parsed_message = parser.parse_message();
580
581 assert_eq!(Some("SomeOp".to_string()), parsed_message.prefix);
582 assert_eq!("MODE", parsed_message.command);
583 assert_eq!(
584 vec!["#channel".to_string(), "+i".to_string()],
585 parsed_message.params
586 );
587 }
588
589 #[test]
590 fn parse_message_mode_trailing_user() {
591 let message = ":SomeOp MODE #channel +oo SomeUser :AnotherUser";
592 let mut parser = Parser::new(message);
593
594 let parsed_message = parser.parse_message();
595
596 assert_eq!(Some("SomeOp".to_string()), parsed_message.prefix);
597 assert_eq!("MODE", parsed_message.command);
598 assert_eq!(
599 vec![
600 "#channel".to_string(),
601 "+oo".to_string(),
602 "SomeUser".to_string(),
603 "AnotherUser".to_string()
604 ],
605 parsed_message.params
606 );
607 }
608}