slamshuffle/src/mfvi.rs

156 lines
4.6 KiB
Rust
Raw Normal View History

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()
}