melody_compiler 0.20.0

The Melody language compiler
Documentation
use super::types::pest::Rule;
use crate::errors::CompilerError;
use crate::types::Result;
use once_cell::sync::Lazy;
use pest::iterators::Pair;
use std::collections::HashSet;

pub fn first_inner(pair: Pair<'_, Rule>) -> Result<Pair<'_, Rule>> {
    let last = pair.into_inner().next().ok_or(CompilerError::MissingNode)?;

    Ok(last)
}

pub fn last_inner(pair: Pair<'_, Rule>) -> Result<Pair<'_, Rule>> {
    let last = pair.into_inner().next_back().ok_or(CompilerError::MissingNode)?;

    Ok(last)
}

pub fn first_last_inner_str(pair: Pair<'_, Rule>) -> Result<(&str, &str)> {
    let pairs: Vec<Pair<'_, Rule>> = pair.into_inner().collect();
    Ok((
        pairs.first().ok_or(CompilerError::MissingNode)?.as_str(),
        pairs.last().ok_or(CompilerError::MissingNode)?.as_str(),
    ))
}

pub fn nth_inner(pair: Pair<'_, Rule>, n: usize) -> Option<Pair<'_, Rule>> {
    pair.into_inner().nth(n)
}

pub fn to_char(value: &str) -> Result<char> {
    let char = value.chars().next().ok_or(CompilerError::MissingNode)?;

    Ok(char)
}

pub fn alphabetic_first_char(value: &str) -> Result<bool> {
    Ok(to_char(value)?.is_alphabetic())
}

pub fn unquote_escape_raw(pair: &Pair<'_, Rule>) -> String {
    let pair_str = pair.as_str();
    pair_str[1..pair_str.len() - 1].replace(r"\`", "`")
}

pub fn unquote_escape_literal(pair: &Pair<'_, Rule>) -> String {
    let raw_literal = pair.as_str();
    let quote_type = raw_literal.chars().next().unwrap_or('"');
    let pair_str = escape_chars(raw_literal);
    let literal = pair_str[1..pair_str.len() - 1].to_owned();

    match quote_type {
        '"' => literal.replace(r#"\""#, r#"""#),
        '\'' => literal.replace(r"\'", r"'"),
        _ => unreachable!(),
    }
}

static RESERVED_CHARS: Lazy<HashSet<char>> =
    Lazy::new(|| HashSet::from(['[', ']', '(', ')', '{', '}', '*', '+', '?', '|', '^', '$', '.']));

fn escape_chars(source: &str) -> String {
    let mut escaped_source = String::new();
    for char in source.chars() {
        if RESERVED_CHARS.contains(&char) {
            let escaped_char = format!("\\{char}");
            escaped_source.push_str(&escaped_char);
        } else {
            escaped_source.push_str(&String::from(char));
        }
    }
    escaped_source
}