Skip to content

Instantly share code, notes, and snippets.

@louiidev
Last active July 30, 2021 02:23
Show Gist options
  • Save louiidev/bebf0e96588a1caa72f0b27524d1f6fc to your computer and use it in GitHub Desktop.
Save louiidev/bebf0e96588a1caa72f0b27524d1f6fc to your computer and use it in GitHub Desktop.
A simple message log parser - To display Text with certain colors
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