use syntax::Error;
use syntax::ast::{ClassClause, Literal};
use zarch::ebcdic::CodePage;
pub(crate) fn sets(clauses: &[ClassClause], page: &CodePage, errors: &mut Vec<Error>) -> Vec<(String, [u8; 32])> {
clauses
.iter()
.map(|clause| {
let mut bits = [0u8; 32];
for (first, last) in &clause.members {
match members(first, last.as_ref(), page) {
Ok(bytes) => bytes.into_iter().for_each(|b| bits[usize::from(b / 8)] |= 1 << (b % 8)),
Err(m) => errors.push(syntax::messages::IWC0005.at(clause.pos, format!("CLASS {}: {m}", clause.name))),
}
}
(clause.name.clone(), bits)
})
.collect()
}
fn members(first: &Literal, last: Option<&Literal>, page: &CodePage) -> Result<Vec<u8>, String> {
let from = bytes(first, page)?;
let Some(last) = last else { return Ok(from) };
let to = bytes(last, page)?;
if matches!(first, Literal::Number(_)) != matches!(last, Literal::Number(_)) {
return Err("the literals of a THROUGH phrase are both numeric or both alphanumeric".into());
}
match (from.as_slice(), to.as_slice()) {
(&[a], &[b]) => Ok((a.min(b)..=a.max(b)).collect()),
_ => Err("an alphanumeric literal of a THROUGH phrase is one character".into()),
}
}
fn bytes(literal: &Literal, page: &CodePage) -> Result<Vec<u8>, String> {
match literal {
Literal::Alnum(text) if !text.is_empty() => page.encode(text).map_err(|e| format!("{text:?}: {e}")),
Literal::Hex(bytes) if !bytes.is_empty() => Ok(bytes.clone()),
Literal::Number(n) => match n.parse::<u16>() {
Ok(ordinal @ 1..=256) if n.bytes().all(|b| b.is_ascii_digit()) => Ok(vec![(ordinal - 1) as u8]),
_ => Err(format!("{n} is not an ordinal number from 1 to 256")),
},
_ => Err("the literals are alphanumeric characters or ordinal numbers".into()),
}
}