1use numeric::Quote;
6use syntax::ast::{Alphabet, AlphabetEntry, Environment, Figurative, Literal};
7use syntax::messages::Refused;
8use zarch::ebcdic::{self, CodePage, Collation};
9
10pub struct Sequence {
11 collation: Collation,
12 ordinals: Vec<u8>,
14 pub high_value: u8,
15 pub low_value: u8,
16 pub quote: Quote,
18}
19
20impl Sequence {
21 pub fn native() -> Self {
22 Self { collation: Collation::Native, ordinals: (0..=255).collect(), high_value: ebcdic::HIGH_VALUE, low_value: ebcdic::LOW_VALUE, quote: Quote::default() }
23 }
24
25 pub fn program(environment: &Environment, page: &CodePage, quote: Quote) -> Result<Self, Refused> {
27 match &environment.collating_sequence {
28 Some(name) => Self::named(environment, name, page, quote).map_err(|(message, m)| (message, format!("PROGRAM COLLATING SEQUENCE {name}: {m}"))),
29 None => Ok(Self { quote, ..Self::native() }),
30 }
31 }
32
33 pub fn named(environment: &Environment, name: &str, page: &CodePage, quote: Quote) -> Result<Self, Refused> {
34 let (_, alphabet) = environment.alphabets.iter().find(|(n, _)| n == name).ok_or_else(|| (syntax::messages::IWC0279, "not an alphabet-name of SPECIAL-NAMES".to_owned()))?;
35 Self::of(alphabet, page, quote)
36 }
37
38 pub fn of(alphabet: &Alphabet, page: &CodePage, quote: Quote) -> Result<Self, Refused> {
40 let sequence = match alphabet {
41 Alphabet::Ebcdic | Alphabet::Native => Self::native(),
42 Alphabet::Standard1 | Alphabet::Standard2 => {
43 Self::from_positions((0..0x80u8).filter_map(|c| page.encode_char(c as char)).map(|b| vec![b]).collect())
44 }
45 Alphabet::Literal(entries) => Self::from_positions(literal_positions(entries, page, quote)?),
46 };
47 Ok(Self { quote, ..sequence })
48 }
49
50 fn from_positions(mut positions: Vec<Vec<u8>>) -> Self {
55 positions.retain(|p| !p.is_empty());
56 let mut given = [false; 256];
57 positions.iter().flatten().for_each(|&b| given[b as usize] = true);
58 positions.extend((0..=255u8).filter(|&b| !given[b as usize]).map(|b| vec![b]));
59 let mut weights = Box::new([0u16; 256]);
60 for (w, chars) in positions.iter().enumerate() {
61 for &b in chars {
62 weights[b as usize] = w as u16;
63 }
64 }
65 let low_value = positions.first().and_then(|p| p.first()).copied().unwrap_or(ebcdic::LOW_VALUE);
66 let high_value = positions.last().and_then(|p| p.last()).copied().unwrap_or(ebcdic::HIGH_VALUE);
67 Self { collation: Collation::Weights(weights), ordinals: positions.iter().map(|p| p[0]).collect(), high_value, low_value, quote: Quote::default() }
68 }
69
70 pub fn figurative(&self, f: Figurative) -> u8 {
73 match f {
74 Figurative::HighValue => self.high_value,
75 Figurative::LowValue => self.low_value,
76 other => native_figurative(other, self.quote),
77 }
78 }
79
80 pub fn collation(&self) -> &Collation {
81 &self.collation
82 }
83
84 pub fn is_native(&self) -> bool {
85 matches!(self.collation, Collation::Native)
86 }
87
88 pub fn positions(&self) -> [u8; 256] {
90 std::array::from_fn(|b| self.collation.weight(b as u8) as u8)
91 }
92
93 pub fn ordinal(&self, byte: u8) -> u16 {
95 self.collation.weight(byte) + 1
96 }
97
98 pub fn character(&self, ordinal: i64) -> Option<u8> {
100 usize::try_from(ordinal).ok().and_then(|n| n.checked_sub(1)).and_then(|i| self.ordinals.get(i)).copied()
101 }
102
103 pub fn count(&self) -> usize {
105 self.ordinals.len()
106 }
107}
108
109fn literal_positions(entries: &[AlphabetEntry], page: &CodePage, quote: Quote) -> Result<Vec<Vec<u8>>, Refused> {
110 let mut positions: Vec<Vec<u8>> = Vec::new();
111 for entry in entries {
112 match entry {
113 AlphabetEntry::Literal(l) => positions.extend(characters(l, page, quote)?.into_iter().map(|b| vec![b])),
114 AlphabetEntry::Through(first, last) => {
115 let (a, b) = (single(first, page, quote)?, single(last, page, quote)?);
116 if a <= b {
117 positions.extend((a..=b).map(|c| vec![c]));
118 } else {
119 positions.extend((b..=a).rev().map(|c| vec![c]));
120 }
121 }
122 AlphabetEntry::Also(literals) => positions.push(literals.iter().map(|l| single(l, page, quote)).collect::<Result<_, _>>()?),
123 }
124 }
125 let mut seen = [false; 256];
126 for &b in positions.iter().flatten() {
127 if std::mem::replace(&mut seen[b as usize], true) {
128 return Err((syntax::messages::IWC0280, format!("the character X'{b:02X}' is given more than one position")));
129 }
130 }
131 Ok(positions)
132}
133
134fn characters(literal: &Literal, page: &CodePage, quote: Quote) -> Result<Vec<u8>, Refused> {
138 Ok(match literal {
139 Literal::Alnum(s) => page.encode(s).map_err(|e| (syntax::messages::IWC0291, e.to_string()))?,
140 Literal::Hex(b) => b.clone(),
141 Literal::Number(n) => match n.parse::<u16>() {
142 Ok(k @ 1..=256) if n.bytes().all(|c| c.is_ascii_digit()) => vec![(k - 1) as u8],
143 _ => return Err((syntax::messages::IWC0281, format!("{n} is not an ordinal position from 1 to 256"))),
144 },
145 Literal::Figurative(Figurative::Null) => return Err((syntax::messages::IWC0282, "NULL cannot be in an ALPHABET clause".into())),
146 Literal::Figurative(f) => vec![native_figurative(*f, quote)],
147 Literal::National(_) => return Err((syntax::messages::IWC0283, "a national literal cannot be in an ALPHABET clause".into())),
148 Literal::Dbcs(_) => return Err((syntax::messages::IWC0284, "a DBCS literal cannot be in an ALPHABET clause".into())),
149 Literal::All(_) => return Err((syntax::messages::IWC0285, "ALL cannot be in an ALPHABET clause".into())),
150 })
151}
152
153fn native_figurative(f: Figurative, quote: Quote) -> u8 {
154 match f {
155 Figurative::Space => ebcdic::SPACE,
156 Figurative::Zero => ebcdic::ZERO,
157 Figurative::Quote => quote.byte(),
158 Figurative::HighValue => ebcdic::HIGH_VALUE,
159 Figurative::LowValue => ebcdic::LOW_VALUE,
160 Figurative::Null => 0,
161 }
162}
163
164fn single(literal: &Literal, page: &CodePage, quote: Quote) -> Result<u8, Refused> {
165 match characters(literal, page, quote)?.as_slice() {
166 [b] => Ok(*b),
167 _ => Err((syntax::messages::IWC0286, "a literal of THROUGH or ALSO must be one character".into())),
168 }
169}