diff --git a/src/mfvi.rs b/src/mfvi.rs index 919371b..b1b554f 100644 --- a/src/mfvi.rs +++ b/src/mfvi.rs @@ -1,6 +1,7 @@ use std::collections::{BTreeMap, HashSet}; #[derive(Clone, Copy)] +/// Flag indicating whether special effects variants should be used. pub enum SfxMode { Off, On, @@ -12,17 +13,20 @@ struct Translation<'a> { } #[derive(Clone)] +/// Holds the set of delimiters that should be removed from the MML to select a variant. pub struct Variant<'v> { keep_delim: &'v str, remove_delims: HashSet<&'v str>, } impl<'v> Variant<'v> { + /// Get the set of character delimiters to remove from MML for this variant. pub fn ignores(&self) -> &HashSet<&str> { &self.remove_delims } } +/// The list of all variants defined in an MML file. pub struct VariantList<'v> { all_delims: HashSet<&'v str>, variants: BTreeMap<&'v str, Variant<'v>>, @@ -55,17 +59,26 @@ impl<'v> VariantList<'v> { self } + /// Get a specific variant by name, which exists in the MML file. pub fn get(&self, name: &str) -> Option<&Variant> { self.variants.get(name) } } +/// Processes "#REPLACE" macros in MML. +/// +/// Macro syntax: +/// #REPLACE c1 c2 +/// +/// "c1" and "c2" are meant to be single characters, but `replace_chars` finds any two strings of +/// non-whitespace. These strings are substituted in-place, so the longer string is truncated to +/// the length of the shorter. pub fn replace_chars(input: &str) -> String { let trs: Vec> = input .lines() .filter_map(|line| { line.strip_prefix("#REPLACE") - .and_then(|l| l.trim().rsplit_once(' ')) + .and_then(|l| l.trim().rsplit_once(char::is_whitespace)) .and_then(|(first, second)| { let length = std::cmp::min(first.len(), second.len()); Some(Translation { @@ -84,12 +97,22 @@ pub fn replace_chars(input: &str) -> String { new } +/// Processes "#SFXV" and "#VARIANT" macros in MML. +/// +/// Macro syntax: +/// #SFXV o i +/// #VARIANT c name +/// +/// "o", "i", and "c" are single characters that act as markup delimiters around the variant codes. +/// For #SFXV, if special effects mode is off, the "o" variants are used, otherwise "i". The "name" +/// for one #VARIANT is optional, and its absence indicates the default variant. If all variants in +/// the input are named, the first variant will be the default. pub fn get_variants<'a>(input: &'a str, sfx_mode: SfxMode) -> VariantList<'a> { input .lines() .fold(VariantList::new(), |mut state, line| { if let Some(l) = line.strip_prefix("#SFXV") { - if let Some((first, second)) = l.trim().rsplit_once(' ') { + if let Some((first, second)) = l.trim().rsplit_once(char::is_whitespace) { use SfxMode::*; match sfx_mode { Off => state.all_delims.insert(first),