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