Skip to main content

ironwork_rt/
bms.rs

1//! The mapset model BMS map source describes: each map's fields with their positions, attributes
2//! and initial data, as SEND MAP and RECEIVE MAP use it.
3
4#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5pub enum Mode {
6    In,
7    Out,
8    InOut,
9}
10
11#[derive(Clone, Debug, PartialEq, Eq)]
12pub enum Initial {
13    Text(String),
14    Bytes(Vec<u8>),
15}
16
17#[derive(Clone, Copy, Debug, PartialEq, Eq)]
18pub enum Protection {
19    Askip,
20    Prot,
21    Unprot,
22}
23
24#[derive(Clone, Copy, Debug, PartialEq, Eq)]
25pub enum Intensity {
26    Norm,
27    Brt,
28    Drk,
29}
30
31#[derive(Clone, Copy, Debug, PartialEq, Eq)]
32pub struct Attrb {
33    pub protection: Protection,
34    pub numeric: bool,
35    pub intensity: Intensity,
36    pub detectable: bool,
37    pub cursor: bool,
38    pub fset: bool,
39}
40
41#[derive(Clone, Debug, PartialEq, Eq)]
42pub struct Field {
43    /// Upper-cased label; None for an unlabelled field.
44    pub name: Option<String>,
45    /// 1-based position of the attribute byte within the map.
46    pub line: u16,
47    pub column: u16,
48    pub length: u16,
49    pub attrb: Attrb,
50    /// INITIAL or GINIT text, or XINIT's bytes.
51    pub initial: Option<Initial>,
52    pub picin: Option<String>,
53    pub picout: Option<String>,
54    pub occurs: u16,
55    pub group: Option<String>,
56    pub justify_right: bool,
57    pub fill_zero: bool,
58    pub color: Option<String>,
59    pub hilight: Option<String>,
60}
61
62#[derive(Clone, Debug, PartialEq, Eq)]
63pub struct Map {
64    pub name: String,
65    pub lines: u16,
66    pub columns: u16,
67    pub line: u16,
68    pub column: u16,
69    pub ctrl: Vec<String>,
70    pub tioapfx: bool,
71    pub dsatts: Vec<String>,
72    pub fields: Vec<Field>,
73}
74
75#[derive(Clone, Debug, PartialEq, Eq)]
76pub struct Mapset {
77    pub name: String,
78    pub mode: Mode,
79    pub ctrl: Vec<String>,
80    pub maps: Vec<Map>,
81}
82
83/// The extended attributes in symbolic-map order: name, field-name suffix, and whether EXTATT=YES implies it.
84pub const EXTENDED: &[(&str, char, bool)] = &[
85    ("COLOR", 'C', true),
86    ("PS", 'P', true),
87    ("HILIGHT", 'H', true),
88    ("VALIDN", 'V', true),
89    ("OUTLINE", 'U', false),
90    ("SOSI", 'M', false),
91    ("TRANSP", 'T', false),
92];
93
94/// The bytes a PICIN or PICOUT picture occupies: S and V take none.
95pub fn picture_size(pic: &str) -> u32 {
96    let chars: Vec<char> = pic.chars().collect();
97    let (mut size, mut i) = (0, 0);
98    while i < chars.len() {
99        let c = chars[i].to_ascii_uppercase();
100        i += 1;
101        let mut repeat = 1;
102        if chars.get(i) == Some(&'(')
103            && let Some(close) = chars[i..].iter().position(|&c| c == ')')
104        {
105            repeat = chars[i + 1..i + close].iter().collect::<String>().parse().unwrap_or(1);
106            i += close + 1;
107        }
108        if !matches!(c, 'S' | 'V') {
109            size += repeat;
110        }
111    }
112    size
113}
114
115/// Where one occurrence of a named field lies in a map's symbolic structure: the offsets of its L
116/// and F/A bytes and extended attributes (for the first member of a group, or a lone field), and
117/// of its data. They are the offsets the symbolic map declares, which SEND MAP and RECEIVE MAP use.
118#[derive(Clone, Copy, Debug, PartialEq, Eq)]
119pub struct Slot {
120    pub field: usize,
121    pub occurrence: u16,
122    pub control: Option<usize>,
123    pub extended: usize,
124    pub data: usize,
125    pub size: usize,
126}
127
128impl Slot {
129    /// The L halfword, then the F/A byte, when the slot has them.
130    pub fn length_at(&self) -> Option<usize> {
131        self.control
132    }
133
134    pub fn attribute_at(&self) -> Option<usize> {
135        self.control.map(|c| c + 2)
136    }
137}
138
139/// The DSATTS attributes a map's symbolic structure carries, in their order.
140pub fn extended_attributes(map: &Map) -> Vec<&'static str> {
141    EXTENDED.iter().filter(|(a, _, _)| map.dsatts.iter().any(|d| d == a)).map(|(a, _, _)| *a).collect()
142}
143
144/// Every named field's slots, in structure order, sized for the input side (PICIN) or the output
145/// side (PICOUT).
146pub fn slots(map: &Map, input: bool) -> Vec<Slot> {
147    let k = extended_attributes(map).len();
148    let mut at = if map.tioapfx { 12 } else { 0 };
149    let mut out = Vec::new();
150    let mut group: Option<&str> = None;
151    for (i, f) in map.fields.iter().enumerate().filter(|(_, f)| f.name.is_some()) {
152        let lead = match &f.group {
153            Some(g) => {
154                let first = group != Some(g.as_str());
155                group = Some(g);
156                first
157            }
158            None => {
159                group = None;
160                true
161            }
162        };
163        let picture = if input { &f.picin } else { &f.picout };
164        let size = picture.as_deref().map_or(usize::from(f.length), |p| picture_size(p) as usize);
165        let copies = if f.group.is_none() { f.occurs.max(1) } else { 1 };
166        for occurrence in 0..copies {
167            let control = lead.then_some(at);
168            if lead {
169                at += 3 + k;
170            }
171            out.push(Slot { field: i, occurrence, control, extended: at - k, data: at, size });
172            at += size;
173        }
174    }
175    out
176}