Skip to main content

ironwork_compile/
collating.rs

1//! Collating sequences: the order an ALPHABET clause gives the single-byte characters. PROGRAM
2//! COLLATING SEQUENCE makes one the program's, for its alphanumeric comparisons, HIGH-VALUE,
3//! LOW-VALUE, CHAR and ORD; the COLLATING SEQUENCE phrase makes one a SORT's or MERGE's.
4
5use numeric::Quote;
6use syntax::ast::{Alphabet, AlphabetEntry, Environment, Figurative, Literal};
7use zarch::ebcdic::{self, CodePage, Collation};
8
9pub struct Sequence {
10    collation: Collation,
11    /// The first character given each position, lowest position first.
12    ordinals: Vec<u8>,
13    pub high_value: u8,
14    pub low_value: u8,
15    /// QUOTE's character, which APOST/QUOTE chooses.
16    pub quote: Quote,
17}
18
19impl Sequence {
20    pub fn native() -> Self {
21        Self { collation: Collation::Native, ordinals: (0..=255).collect(), high_value: ebcdic::HIGH_VALUE, low_value: ebcdic::LOW_VALUE, quote: Quote::default() }
22    }
23
24    /// The program's sequence: its PROGRAM COLLATING SEQUENCE, else EBCDIC.
25    pub fn program(environment: &Environment, page: &CodePage, quote: Quote) -> Result<Self, String> {
26        match &environment.collating_sequence {
27            Some(name) => Self::named(environment, name, page, quote).map_err(|m| format!("PROGRAM COLLATING SEQUENCE {name}: {m}")),
28            None => Ok(Self { quote, ..Self::native() }),
29        }
30    }
31
32    pub fn named(environment: &Environment, name: &str, page: &CodePage, quote: Quote) -> Result<Self, String> {
33        let (_, alphabet) = environment.alphabets.iter().find(|(n, _)| n == name).ok_or("not an alphabet-name of SPECIAL-NAMES")?;
34        Self::of(alphabet, page, quote)
35    }
36
37    /// STANDARD-1 and STANDARD-2 are 7-bit ASCII's order: ASCII_COLLATION in numeric::assumptions.
38    pub fn of(alphabet: &Alphabet, page: &CodePage, quote: Quote) -> Result<Self, String> {
39        let sequence = match alphabet {
40            Alphabet::Ebcdic | Alphabet::Native => Self::native(),
41            Alphabet::Standard1 | Alphabet::Standard2 => {
42                Self::from_positions((0..0x80u8).filter_map(|c| page.encode_char(c as char)).map(|b| vec![b]).collect())
43            }
44            Alphabet::Literal(entries) => Self::from_positions(literal_positions(entries, page, quote)?),
45        };
46        Ok(Self { quote, ..sequence })
47    }
48
49    /// Positions given explicitly, lowest first, each holding characters that collate equal; every
50    /// other character follows them in EBCDIC order, in a position of its own. HIGH-VALUE is the
51    /// last character of the highest position and LOW-VALUE the first of the lowest (SC27-8713-03,
52    /// pp. 128-129).
53    fn from_positions(mut positions: Vec<Vec<u8>>) -> Self {
54        positions.retain(|p| !p.is_empty());
55        let mut given = [false; 256];
56        positions.iter().flatten().for_each(|&b| given[b as usize] = true);
57        positions.extend((0..=255u8).filter(|&b| !given[b as usize]).map(|b| vec![b]));
58        let mut weights = Box::new([0u16; 256]);
59        for (w, chars) in positions.iter().enumerate() {
60            for &b in chars {
61                weights[b as usize] = w as u16;
62            }
63        }
64        let low_value = positions.first().and_then(|p| p.first()).copied().unwrap_or(ebcdic::LOW_VALUE);
65        let high_value = positions.last().and_then(|p| p.last()).copied().unwrap_or(ebcdic::HIGH_VALUE);
66        Self { collation: Collation::Weights(weights), ordinals: positions.iter().map(|p| p[0]).collect(), high_value, low_value, quote: Quote::default() }
67    }
68
69    /// A figurative constant's character: HIGH-VALUE and LOW-VALUE are this sequence's highest and
70    /// lowest.
71    pub fn figurative(&self, f: Figurative) -> u8 {
72        match f {
73            Figurative::HighValue => self.high_value,
74            Figurative::LowValue => self.low_value,
75            other => native_figurative(other, self.quote),
76        }
77    }
78
79    pub fn collation(&self) -> &Collation {
80        &self.collation
81    }
82
83    pub fn is_native(&self) -> bool {
84        matches!(self.collation, Collation::Native)
85    }
86
87    /// Each character's position, from 0: what a sort key collates by.
88    pub fn positions(&self) -> [u8; 256] {
89        std::array::from_fn(|b| self.collation.weight(b as u8) as u8)
90    }
91
92    /// FUNCTION ORD: the character's position, from 1.
93    pub fn ordinal(&self, byte: u8) -> u16 {
94        self.collation.weight(byte) + 1
95    }
96
97    /// FUNCTION CHAR: the character at a position, from 1; the first given it when several share it.
98    pub fn character(&self, ordinal: i64) -> Option<u8> {
99        usize::try_from(ordinal).ok().and_then(|n| n.checked_sub(1)).and_then(|i| self.ordinals.get(i)).copied()
100    }
101
102    /// How many positions there are: the most FUNCTION CHAR takes.
103    pub fn count(&self) -> usize {
104        self.ordinals.len()
105    }
106}
107
108fn literal_positions(entries: &[AlphabetEntry], page: &CodePage, quote: Quote) -> Result<Vec<Vec<u8>>, String> {
109    let mut positions: Vec<Vec<u8>> = Vec::new();
110    for entry in entries {
111        match entry {
112            AlphabetEntry::Literal(l) => positions.extend(characters(l, page, quote)?.into_iter().map(|b| vec![b])),
113            AlphabetEntry::Through(first, last) => {
114                let (a, b) = (single(first, page, quote)?, single(last, page, quote)?);
115                if a <= b {
116                    positions.extend((a..=b).map(|c| vec![c]));
117                } else {
118                    positions.extend((b..=a).rev().map(|c| vec![c]));
119                }
120            }
121            AlphabetEntry::Also(literals) => positions.push(literals.iter().map(|l| single(l, page, quote)).collect::<Result<_, _>>()?),
122        }
123    }
124    let mut seen = [false; 256];
125    for &b in positions.iter().flatten() {
126        if std::mem::replace(&mut seen[b as usize], true) {
127            return Err(format!("the character X'{b:02X}' is given more than one position"));
128        }
129    }
130    Ok(positions)
131}
132
133/// The characters an ALPHABET literal gives: its own; for a number, the character at that
134/// position of EBCDIC; for a figurative constant, its EBCDIC character (ALPHABET_LITERALS in
135/// numeric::assumptions).
136fn characters(literal: &Literal, page: &CodePage, quote: Quote) -> Result<Vec<u8>, String> {
137    Ok(match literal {
138        Literal::Alnum(s) => page.encode(s).map_err(|e| e.to_string())?,
139        Literal::Hex(b) => b.clone(),
140        Literal::Number(n) => match n.parse::<u16>() {
141            Ok(k @ 1..=256) if n.bytes().all(|c| c.is_ascii_digit()) => vec![(k - 1) as u8],
142            _ => return Err(format!("{n} is not an ordinal position from 1 to 256")),
143        },
144        Literal::Figurative(Figurative::Null) => return Err("NULL cannot be in an ALPHABET clause".into()),
145        Literal::Figurative(f) => vec![native_figurative(*f, quote)],
146        Literal::National(_) => return Err("a national literal cannot be in an ALPHABET clause".into()),
147        Literal::Dbcs(_) => return Err("a DBCS literal cannot be in an ALPHABET clause".into()),
148        Literal::All(_) => return Err("ALL cannot be in an ALPHABET clause".into()),
149    })
150}
151
152fn native_figurative(f: Figurative, quote: Quote) -> u8 {
153    match f {
154        Figurative::Space => ebcdic::SPACE,
155        Figurative::Zero => ebcdic::ZERO,
156        Figurative::Quote => quote.byte(),
157        Figurative::HighValue => ebcdic::HIGH_VALUE,
158        Figurative::LowValue => ebcdic::LOW_VALUE,
159        Figurative::Null => 0,
160    }
161}
162
163fn single(literal: &Literal, page: &CodePage, quote: Quote) -> Result<u8, String> {
164    match characters(literal, page, quote)?.as_slice() {
165        [b] => Ok(*b),
166        _ => Err("a literal of THROUGH or ALSO must be one character".into()),
167    }
168}