use std::collections::HashMap;
use std::sync::OnceLock;
const TABLE: &str = include_str!("../data/reserved-words.tsv");
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Category {
Reserved,
StandardOnly,
Potential,
}
fn table() -> &'static HashMap<&'static str, Category> {
static WORDS: OnceLock<HashMap<&'static str, Category>> = OnceLock::new();
WORDS.get_or_init(|| {
TABLE
.lines()
.filter(|l| !l.is_empty() && !l.starts_with('#'))
.map(|l| match l.split_once('\t') {
Some((word, "reserved")) => (word, Category::Reserved),
Some((word, "standard-only")) => (word, Category::StandardOnly),
Some((word, "potential")) => (word, Category::Potential),
_ => panic!("reserved-words.tsv: {l} is not a word and its category"),
})
.collect()
})
}
pub fn category(word: &str) -> Option<Category> {
table().get(word.to_ascii_uppercase().as_str()).copied()
}
pub fn is_reserved(word: &str) -> bool {
category(word) == Some(Category::Reserved)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn each_word_has_its_column() {
let count = |c| table().values().filter(|&&v| v == c).count();
assert_eq!((count(Category::Reserved), count(Category::StandardOnly), count(Category::Potential)), (401, 52, 62));
assert!(is_reserved("COUNT") && is_reserved("start") && is_reserved("INDEX"));
assert_eq!((category("CONTROL"), category("ACTIVE-CLASS"), category("WS-COUNT")), (Some(Category::StandardOnly), Some(Category::Potential), None));
assert!(!is_reserved("CONTROL") && !is_reserved("ACTIVE-CLASS") && !is_reserved("WS-COUNT"));
}
#[test]
fn the_vendored_table_is_cobolworks() {
let Ok(dir) = std::env::var("IRONWORK_COBOLWORK_DIR") else { return };
let theirs = std::fs::read_to_string(std::path::Path::new(&dir).join("provenance/reserved-words.tsv")).expect("cobolwork's table");
assert!(theirs.replace('\r', "") == TABLE.replace('\r', ""), "crates/rt/data/reserved-words.tsv differs from cobolwork's: run tools/sync-reserved-words.sh");
}
}