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