chatter-macros 0.1.0

Proc macro for parsing chatter
Documentation
use std::collections::HashMap;

use crate::UNEXPECTED_ERROR;

use pest::{iterators::Pair, Parser};
use pest_derive::Parser;

pub(crate) struct Choice {
    pub text: String,
    pub tags: Vec<String>,
    pub goto: Option<String>,
}

pub(crate) enum Chat {
    Line {
        text: String,
        tags: Vec<String>,
        goto: Option<String>,
    },
    Choices(Vec<Choice>),
}

#[derive(Parser)]
#[grammar = "chatter.pest"]
struct ChatterParser;

pub(crate) fn parse(input: &str) -> (Vec<Chat>, HashMap<String, usize>) {
    let chatter = ChatterParser::parse(Rule::chatter, input).unwrap();

    let mut chat = Vec::new();
    let mut branches = HashMap::new();

    for pair in chatter {
        match pair.as_rule() {
            Rule::line => chat.push(parse_line(pair)),
            Rule::choices => chat.push(parse_choices(pair)),
            Rule::branch => {
                let branch = pair.into_inner().next().unwrap().as_str().to_string();
                branches.insert(branch, chat.len());
            }
            Rule::goto => {
                let branch = pair.into_inner().next().unwrap().as_str().to_string();

                let last_chat = chat.last_mut().unwrap();
                match last_chat {
                    Chat::Line {
                        text: _,
                        tags: _,
                        goto,
                    } => {
                        if goto.is_none() {
                            *goto = Some(branch.clone())
                        }
                    }
                    Chat::Choices(choices) => {
                        for choice in choices {
                            if choice.goto.is_none() {
                                choice.goto = Some(branch.clone())
                            }
                        }
                    }
                }
            }
            Rule::EOI => break,
            _ => {
                panic!("{}", UNEXPECTED_ERROR)
            }
        }
    }

    (chat, branches)
}

fn parse_choices(input: Pair<Rule>) -> Chat {
    let mut choices = Vec::new();

    for pair in input.into_inner() {
        choices.push(parse_choice(pair));
    }

    Chat::Choices(choices)
}

fn parse_choice(choice: Pair<Rule>) -> Choice {
    let mut text = None;
    let mut tags = Vec::new();
    let mut goto = None;

    for pair in choice.into_inner() {
        match pair.as_rule() {
            Rule::goto => goto = Some(pair.into_inner().next().unwrap().as_str().to_string()),
            Rule::tags => {
                for tag in pair.into_inner() {
                    tags.push(tag.as_str().to_string())
                }
            }
            Rule::text => text = Some(pair.as_str().to_string()),
            _ => panic!("{}", UNEXPECTED_ERROR),
        }
    }

    match text {
        Some(text) => Choice { text, tags, goto },
        None => panic!("{}", UNEXPECTED_ERROR),
    }
}

fn parse_line(line: Pair<Rule>) -> Chat {
    let mut text = None;
    let mut tags = Vec::new();
    let mut goto = None;

    for pair in line.into_inner() {
        match pair.as_rule() {
            Rule::goto => goto = Some(pair.into_inner().next().unwrap().as_str().to_string()),
            Rule::tags => {
                for tag in pair.into_inner() {
                    tags.push(tag.as_str().to_string())
                }
            }
            Rule::text => text = Some(pair.as_str().to_string()),
            _ => panic!("{}", UNEXPECTED_ERROR),
        }
    }

    match text {
        Some(text) => Chat::Line { text, tags, goto },
        None => panic!("{}", UNEXPECTED_ERROR),
    }
}