2024-02-04 12:09:24 -05:00
|
|
|
use std::collections::{BTreeMap, HashSet};
|
|
|
|
|
|
|
|
#[derive(Clone, Copy)]
|
2024-02-04 13:43:56 -05:00
|
|
|
/// Flag indicating whether special effects variants should be used.
|
2024-02-04 12:09:24 -05:00
|
|
|
pub enum SfxMode {
|
|
|
|
Off,
|
|
|
|
On,
|
|
|
|
}
|
|
|
|
|
2024-01-31 15:49:41 -05:00
|
|
|
struct Translation<'a> {
|
|
|
|
find: &'a str,
|
|
|
|
replacement: &'a str,
|
|
|
|
}
|
|
|
|
|
2024-02-04 12:09:24 -05:00
|
|
|
#[derive(Clone)]
|
2024-02-04 13:43:56 -05:00
|
|
|
/// Holds the set of delimiters that should be removed from the MML to select a variant.
|
2024-02-04 12:09:24 -05:00
|
|
|
pub struct Variant<'v> {
|
|
|
|
keep_delim: &'v str,
|
|
|
|
remove_delims: HashSet<&'v str>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'v> Variant<'v> {
|
2024-02-04 13:43:56 -05:00
|
|
|
/// Get the set of character delimiters to remove from MML for this variant.
|
2024-02-04 12:09:24 -05:00
|
|
|
pub fn ignores(&self) -> &HashSet<&str> {
|
|
|
|
&self.remove_delims
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-04 13:43:56 -05:00
|
|
|
/// The list of all variants defined in an MML file.
|
2024-02-04 12:09:24 -05:00
|
|
|
pub struct VariantList<'v> {
|
|
|
|
all_delims: HashSet<&'v str>,
|
|
|
|
variants: BTreeMap<&'v str, Variant<'v>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'v> VariantList<'v> {
|
|
|
|
fn new() -> Self {
|
|
|
|
VariantList {
|
|
|
|
all_delims: HashSet::new(),
|
|
|
|
variants: BTreeMap::new(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn finish(mut self) -> Self {
|
|
|
|
for (_, v) in self.variants.iter_mut() {
|
|
|
|
v.remove_delims = self.all_delims.clone();
|
|
|
|
v.remove_delims.remove(v.keep_delim);
|
|
|
|
}
|
|
|
|
|
|
|
|
if self.variants.is_empty() {
|
|
|
|
self.variants.insert(
|
|
|
|
"_default_",
|
|
|
|
Variant {
|
|
|
|
keep_delim: "",
|
|
|
|
remove_delims: self.all_delims.clone(),
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2024-02-04 13:43:56 -05:00
|
|
|
/// Get a specific variant by name, which exists in the MML file.
|
2024-02-04 12:09:24 -05:00
|
|
|
pub fn get(&self, name: &str) -> Option<&Variant> {
|
|
|
|
self.variants.get(name)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-04 13:43:56 -05:00
|
|
|
/// 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.
|
2024-01-31 15:49:41 -05:00
|
|
|
pub fn replace_chars(input: &str) -> String {
|
|
|
|
let trs: Vec<Translation<'_>> = input
|
|
|
|
.lines()
|
|
|
|
.filter_map(|line| {
|
2024-02-04 08:53:10 -05:00
|
|
|
line.strip_prefix("#REPLACE")
|
2024-02-04 13:43:56 -05:00
|
|
|
.and_then(|l| l.trim().rsplit_once(char::is_whitespace))
|
2024-01-31 15:49:41 -05:00
|
|
|
.and_then(|(first, second)| {
|
|
|
|
let length = std::cmp::min(first.len(), second.len());
|
|
|
|
Some(Translation {
|
|
|
|
find: &first[0..length],
|
|
|
|
replacement: &second[0..length],
|
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
let mut new = input.to_string();
|
|
|
|
for tr in trs {
|
|
|
|
new = new.replace(tr.find, tr.replacement);
|
|
|
|
}
|
|
|
|
|
|
|
|
new
|
|
|
|
}
|
2024-02-04 12:09:24 -05:00
|
|
|
|
2024-02-04 13:43:56 -05:00
|
|
|
/// 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.
|
2024-02-04 12:09:24 -05:00
|
|
|
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") {
|
2024-02-04 13:43:56 -05:00
|
|
|
if let Some((first, second)) = l.trim().rsplit_once(char::is_whitespace) {
|
2024-02-04 12:09:24 -05:00
|
|
|
use SfxMode::*;
|
|
|
|
match sfx_mode {
|
|
|
|
Off => state.all_delims.insert(first),
|
|
|
|
On => state.all_delims.insert(second),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
} else if let Some(l) = line.strip_prefix("#VARIANT") {
|
|
|
|
let (first, second) = {
|
|
|
|
let rest = l.trim();
|
|
|
|
if let Some((f, s)) = rest.rsplit_once(' ') {
|
|
|
|
(f, s)
|
|
|
|
} else {
|
|
|
|
(rest, "_default_")
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
state.all_delims.insert(first);
|
|
|
|
|
|
|
|
if state.variants.len() == 0 && second != "_default_" {
|
|
|
|
state.variants.insert(
|
|
|
|
"_default_",
|
|
|
|
Variant {
|
|
|
|
keep_delim: first,
|
|
|
|
remove_delims: HashSet::new(),
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
|
|
|
state.variants.insert(
|
|
|
|
second,
|
|
|
|
Variant {
|
|
|
|
keep_delim: first,
|
|
|
|
remove_delims: HashSet::new(),
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
state
|
|
|
|
})
|
|
|
|
.finish()
|
|
|
|
}
|