Last active
July 30, 2021 02:23
-
-
Save louiidev/bebf0e96588a1caa72f0b27524d1f6fc to your computer and use it in GitHub Desktop.
A simple message log parser - To display Text with certain colors
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| use std::{collections::VecDeque}; | |
| use lazy_static::lazy_static; | |
| use regex::Regex; | |
| use crate::render::{BLUE, Color, RED, WHITE, GREEN}; | |
| pub type LogItemValue = (String, Color); | |
| lazy_static! { | |
| static ref PATTERN_WORD: Regex = Regex::new(r"[a-zA-Z0-9,!?\.'][a-zA-Z0-9,!?\.'\s]+").unwrap(); | |
| static ref PATTERN_COLOR: Regex = Regex::new(r"^(RED|GREEN|BLUE|BLACK)\b").unwrap(); | |
| static ref PATTERN_OPEN_BRACKET: Regex = Regex::new(r"^\[").unwrap(); | |
| static ref PATTERN_CLOSE_BRACKET: Regex = Regex::new(r"^\]").unwrap(); | |
| } | |
| #[derive(Debug, PartialEq)] | |
| pub enum LogColors { | |
| Red, | |
| Green, | |
| Blue, | |
| Black, | |
| White, | |
| } | |
| #[derive(Debug, PartialEq)] | |
| pub enum Token { | |
| Color(LogColors), | |
| Text(String), | |
| OpenParen, | |
| CloseParen, | |
| } | |
| fn match_with_pattern<'a, F>( | |
| source: &'a str, | |
| pattern: &Regex, | |
| tokenizer: F, | |
| ) -> Option<(&'a str, Token)> | |
| where | |
| F: Fn(&'a str) -> Token, | |
| { | |
| if let Some(range) = pattern.find(source) { | |
| let matched = &source[range.start()..range.end()]; | |
| let rest = &source[range.end()..]; | |
| Some((rest, tokenizer(matched))) | |
| } else { | |
| None | |
| } | |
| } | |
| fn match_color<'a>(source: &'a str) -> Option<(&'a str, Token)> { | |
| // Our numeric pattern shouldn't ever fail to parse as an f64 | |
| match_with_pattern(source, &PATTERN_COLOR, |v| { | |
| let colour: LogColors = match v { | |
| "RED" => LogColors::Red, | |
| "GREEN" => LogColors::Green, | |
| "BLUE" => LogColors::Blue, | |
| _ => LogColors::White, | |
| }; | |
| Token::Color(colour) | |
| }) | |
| } | |
| fn match_bracket<'a>(source: &'a str) -> Option<(&'a str, Token)> { | |
| match_with_pattern(source, &PATTERN_OPEN_BRACKET, |_| Token::OpenParen) | |
| .or_else(|| match_with_pattern(source, &PATTERN_CLOSE_BRACKET, |_| Token::CloseParen)) | |
| } | |
| fn match_word<'a>(source: &'a str) -> Option<(&'a str, Token)> { | |
| match_with_pattern(source, &PATTERN_WORD, |v| Token::Text(v.to_string())) | |
| } | |
| fn lex(input: &str) -> Vec<Token> { | |
| let mut tokens = Vec::default(); | |
| let mut current_text = input; | |
| loop { | |
| let result = match_color(current_text) | |
| .or_else(|| match_bracket(current_text)) | |
| .or_else(|| match_word(current_text)); | |
| match result { | |
| Some((next, token)) => { | |
| tokens.push(token); | |
| current_text = next; | |
| } | |
| None => break, | |
| } | |
| } | |
| if !current_text.is_empty() { | |
| eprintln!("Found unexpected tokens in lexer: {}", current_text); | |
| } | |
| tokens | |
| } | |
| fn log_color_to_color(log_color: LogColors) -> Color { | |
| match log_color { | |
| LogColors::Red => RED, | |
| LogColors::Blue => BLUE, | |
| LogColors::Green => GREEN, | |
| _ => WHITE, | |
| } | |
| } | |
| fn parse_tokens(tokens: Vec<Token>) -> Vec<LogItemValue> { | |
| let mut log = Vec::default(); | |
| let mut current_tokens: VecDeque<Token> = tokens.into(); | |
| while let Some(token) = current_tokens.pop_front() { | |
| match token { | |
| Token::OpenParen => { | |
| // expect next token to be color | |
| let color_token = current_tokens.pop_front().unwrap(); | |
| // expect next token to be text | |
| let text_token = current_tokens.pop_front().unwrap(); | |
| let color = match color_token { | |
| Token::Color(color) => color, | |
| _ => panic!("TOKEN IS NOT A COLOR"), | |
| }; | |
| let text = match text_token { | |
| Token::Text(text) => text, | |
| _ => panic!("TOKEN IS NOT TEXT"), | |
| }; | |
| log.push((text, log_color_to_color(color))) | |
| } | |
| Token::CloseParen => {} | |
| Token::Text(text) => { | |
| log.push((text, WHITE)); | |
| } | |
| Token::Color(_) => {} | |
| } | |
| } | |
| log | |
| } | |
| #[test] | |
| fn parsing() { | |
| let text = "[BLUE Hello ]World, My [RED name is louis!]"; | |
| let tokens = lex(text); | |
| let expect_tokens = vec![ | |
| Token::OpenParen, | |
| Token::Color(LogColors::Blue), | |
| Token::Text("Hello ".to_string()), | |
| Token::CloseParen, | |
| Token::Text("World, My ".to_string()), | |
| Token::OpenParen, | |
| Token::Color(LogColors::Red), | |
| Token::Text("name is louis!".to_string()), | |
| Token::CloseParen, | |
| ]; | |
| assert_eq!(tokens, expect_tokens); | |
| let log_message = parse_tokens(tokens); | |
| let expect_log = vec![ | |
| ("Hello ".to_string(), BLUE), | |
| ("World, My ".to_string(), WHITE), | |
| ("name is louis!".to_string(), RED), | |
| ]; | |
| assert_eq!(log_message, expect_log) | |
| } | |
| #[test] | |
| fn parsing_normal_text() { | |
| let text = "Hello world my name is louis!"; | |
| let tokens = lex(text); | |
| let expect_tokens = vec![Token::Text("Hello world my name is louis!".to_string())]; | |
| assert_eq!(tokens, expect_tokens); | |
| let log_message = parse_tokens(tokens); | |
| let expect_log = vec![("Hello world my name is louis!".to_string(), WHITE)]; | |
| assert_eq!(log_message, expect_log) | |
| } | |
| #[test] | |
| fn parsing_tree_hit() { | |
| let text = "You hit a [GREEN Tree!]. [RED The Tree isn't happy]"; | |
| let tokens = lex(text); | |
| let expect_tokens = vec![ | |
| Token::Text("You hit a ".to_string()), | |
| Token::OpenParen, | |
| Token::Color(LogColors::Green), | |
| Token::Text("Tree!".to_string()), | |
| Token::CloseParen, | |
| Token::Text(". ".to_string()), | |
| Token::OpenParen, | |
| Token::Color(LogColors::Red), | |
| Token::Text("The Tree isn't happy".to_string()), | |
| Token::CloseParen, | |
| ]; | |
| assert_eq!(tokens, expect_tokens); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment