1use super::codec::{Decode, Encode, Reader, Writer};
4use super::{Module, ModuleError, ModuleWriter, Section, StringTable};
5use crate::bms::Mapset;
6use crate::codec_struct;
7use crate::lir::{
8 AbendText, Block, Cond, Const, Debug, Edit, Expr, Item, ParaId, Paragraph, Place, Plans, Program, ProgramOptions, Range,
9 Services, SqlEntry, Storage, SymId,
10};
11
12#[derive(Clone, Debug, PartialEq, Eq)]
14pub struct DirectoryEntry {
15 pub id: String,
17 pub external: Option<String>,
20 pub parent: Option<u32>,
22 pub common: bool,
23 pub entries: Vec<(String, ParaId)>,
25 pub params: Vec<bool>,
27 pub returning: bool,
28 pub dynamic: bool,
30}
31
32codec_struct!(DirectoryEntry { id, external, parent, common, entries, params, returning, dynamic });
33
34impl DirectoryEntry {
35 pub fn top_level(program: &Program) -> Self {
37 Self {
38 id: program.symbols.get(program.id as usize).cloned().unwrap_or_default(),
39 external: None,
40 parent: None,
41 common: false,
42 entries: Vec::new(),
43 params: vec![false; program.storage.using.len()],
44 returning: program.storage.returning.is_some(),
45 dynamic: true,
46 }
47 }
48
49 pub fn load_name(&self) -> &str {
51 self.external.as_deref().unwrap_or(&self.id)
52 }
53}
54
55#[derive(Clone, Debug, PartialEq, Eq)]
57pub struct LoadedModule {
58 pub directory: Vec<DirectoryEntry>,
59 pub programs: Vec<Program>,
60 pub mapsets: Vec<Mapset>,
61}
62
63struct Parts<'a> {
65 id: &'a SymId,
66 options: &'a ProgramOptions,
67 initial: &'a bool,
68 recursive: &'a bool,
69 storage: &'a Storage,
70 items: &'a Vec<Item>,
71 paragraphs: &'a Vec<Paragraph>,
72 procedure_start: &'a ParaId,
73 ranges: &'a Vec<Range>,
74 blocks: &'a Vec<Block>,
75 places: &'a Vec<Place>,
76 exprs: &'a Vec<Expr>,
77 conds: &'a Vec<Cond>,
78 consts: &'a Vec<Const>,
79 plans: &'a Plans,
80 services: &'a Services,
81 sql: &'a Vec<SqlEntry>,
82 abends: &'a Vec<AbendText>,
83 edits: &'a Vec<Edit>,
84 symbols: &'a Vec<String>,
85 debug: &'a Debug,
86}
87
88impl<'a> Parts<'a> {
89 fn of(program: &'a Program) -> Self {
90 let Program {
91 id, options, initial, recursive, storage, items, paragraphs, procedure_start, ranges, blocks, places,
92 exprs, conds, consts, plans, services, sql, abends, edits, symbols, debug,
93 } = program;
94 Self {
95 id, options, initial, recursive, storage, items, paragraphs, procedure_start, ranges, blocks, places,
96 exprs, conds, consts, plans, services, sql, abends, edits, symbols, debug,
97 }
98 }
99
100 fn encode_lir(&self, w: &mut Writer) {
101 self.id.encode(w);
102 self.initial.encode(w);
103 self.recursive.encode(w);
104 self.paragraphs.encode(w);
105 self.procedure_start.encode(w);
106 self.ranges.encode(w);
107 self.blocks.encode(w);
108 self.places.encode(w);
109 self.exprs.encode(w);
110 self.conds.encode(w);
111 self.consts.encode(w);
112 self.plans.encode(w);
113 self.services.encode(w);
114 self.abends.encode(w);
115 self.symbols.encode(w);
116 }
117}
118
119pub type LayoutRecord = (Storage, Vec<Item>, Vec<Edit>);
121
122#[derive(Clone, Debug, PartialEq, Eq)]
124pub struct LirRecord {
125 pub id: SymId,
126 pub initial: bool,
127 pub recursive: bool,
128 pub paragraphs: Vec<Paragraph>,
129 pub procedure_start: ParaId,
130 pub ranges: Vec<Range>,
131 pub blocks: Vec<Block>,
132 pub places: Vec<Place>,
133 pub exprs: Vec<Expr>,
134 pub conds: Vec<Cond>,
135 pub consts: Vec<Const>,
136 pub plans: Plans,
137 pub services: Services,
138 pub abends: Vec<AbendText>,
139 pub symbols: Vec<String>,
140}
141
142impl Decode for LirRecord {
143 fn decode(r: &mut Reader<'_>) -> Result<Self, ModuleError> {
144 Ok(Self {
145 id: Decode::decode(r)?,
146 initial: Decode::decode(r)?,
147 recursive: Decode::decode(r)?,
148 paragraphs: Decode::decode(r)?,
149 procedure_start: Decode::decode(r)?,
150 ranges: Decode::decode(r)?,
151 blocks: Decode::decode(r)?,
152 places: Decode::decode(r)?,
153 exprs: Decode::decode(r)?,
154 conds: Decode::decode(r)?,
155 consts: Decode::decode(r)?,
156 plans: Decode::decode(r)?,
157 services: Decode::decode(r)?,
158 abends: Decode::decode(r)?,
159 symbols: Decode::decode(r)?,
160 })
161 }
162}
163
164fn per_program(w: &mut Writer, programs: &[Program], record: impl Fn(&Parts<'_>, &mut Writer)) {
166 w.count(programs.len());
167 for program in programs {
168 record(&Parts::of(program), w);
169 }
170}
171
172fn encode_module(programs: &[Program], directory: &[DirectoryEntry], mapsets: &[Mapset]) -> Vec<u8> {
173 let mut m = ModuleWriter::new();
174 m.section(Section::DIRECTORY, |w| {
175 w.count(directory.len());
176 for entry in directory {
177 entry.encode(w);
178 }
179 });
180 m.section(Section::OPTIONS, |w| per_program(w, programs, |p, w| p.options.encode(w)));
181 m.section(Section::LAYOUT, |w| {
182 per_program(w, programs, |p, w| {
183 p.storage.encode(w);
184 p.items.encode(w);
185 p.edits.encode(w);
186 });
187 });
188 m.section(Section::LIR, |w| per_program(w, programs, |p, w| p.encode_lir(w)));
189 m.section(Section::SQL, |w| per_program(w, programs, |p, w| p.sql.encode(w)));
190 m.section(Section::BMS, |w| {
191 w.count(mapsets.len());
192 for mapset in mapsets {
193 mapset.encode(w);
194 }
195 });
196 m.section(Section::DEBUG, |w| per_program(w, programs, |p, w| p.debug.encode(w)));
197 m.finish()
198}
199
200pub fn write(programs: &[Program]) -> Vec<u8> {
203 let directory: Vec<_> = programs.iter().map(DirectoryEntry::top_level).collect();
204 encode_module(programs, &directory, &[])
205}
206
207pub fn write_with(programs: &[Program], directory: &[DirectoryEntry], mapsets: &[Mapset]) -> Result<Vec<u8>, ModuleError> {
210 check_directory(directory, programs)?;
211 for program in programs {
212 crate::lir::program_valid(program).map_err(|reason| bad("LIR", reason))?;
213 }
214 check_mapsets(mapsets).map_err(|reason| bad(Section::BMS.name, reason))?;
215 Ok(encode_module(programs, directory, mapsets))
216}
217
218fn check_mapsets(mapsets: &[Mapset]) -> Result<(), String> {
220 match mapsets.windows(2).find(|pair| pair[0].name >= pair[1].name) {
221 Some(pair) => Err(format!("mapset {} follows mapset {}", pair[1].name, pair[0].name)),
222 None => Ok(()),
223 }
224}
225
226fn bad(section: &'static str, reason: impl Into<String>) -> ModuleError {
227 ModuleError::Malformed { section, offset: 0, reason: reason.into() }
228}
229
230fn check_directory(directory: &[DirectoryEntry], programs: &[Program]) -> Result<(), ModuleError> {
231 let name = Section::DIRECTORY.name;
232 if directory.len() != programs.len() {
233 return Err(bad(name, format!("{} entries for {} programs", directory.len(), programs.len())));
234 }
235 for (ordinal, (entry, program)) in directory.iter().zip(programs).enumerate() {
236 let symbol = program.symbols.get(program.id as usize);
237 if symbol != Some(&entry.id) {
238 return Err(bad(name, format!("program {ordinal} is {symbol:?} in its symbols, {:?} in the directory", entry.id)));
239 }
240 if entry.parent.is_some_and(|p| p as usize >= ordinal) {
241 return Err(bad(name, format!("program {ordinal} has parent {}, which does not precede it", entry.parent.unwrap_or(0))));
242 }
243 if let Some((entry_name, _)) = entry.entries.iter().find(|(_, para)| *para as usize >= program.paragraphs.len()) {
244 return Err(bad(name, format!("program {ordinal} ENTRY {entry_name} names a paragraph it lacks")));
245 }
246 }
247 Ok(())
248}
249
250fn records<T: Decode>(module: &Module<'_>, strings: &StringTable, section: Section, expected: usize) -> Result<Vec<T>, ModuleError> {
252 let mut r = module.reader(section, strings)?;
253 let at = r.position();
254 let count = r.count()?;
255 if count != expected {
256 return Err(r.malformed(at, format!("{count} records for {expected} programs")));
257 }
258 let mut out = Vec::with_capacity(count);
259 for _ in 0..count {
260 out.push(T::decode(&mut r)?);
261 }
262 r.finish()?;
263 Ok(out)
264}
265
266pub fn read(bytes: &[u8]) -> Result<LoadedModule, ModuleError> {
268 let module = Module::read(bytes)?;
269 let strings = module.strings()?;
270 let mut r = module.reader(Section::DIRECTORY, &strings)?;
271 let directory = Vec::<DirectoryEntry>::decode(&mut r)?;
272 r.finish()?;
273 let count = directory.len();
274
275 let options = records::<ProgramOptions>(&module, &strings, Section::OPTIONS, count)?;
276 let layouts = records::<LayoutRecord>(&module, &strings, Section::LAYOUT, count)?;
277 let bodies = records::<LirRecord>(&module, &strings, Section::LIR, count)?;
278 let sql = records::<Vec<SqlEntry>>(&module, &strings, Section::SQL, count)?;
279 let debug = records::<Debug>(&module, &strings, Section::DEBUG, count)?;
280
281 let mut r = module.reader(Section::BMS, &strings)?;
282 let mapsets = Vec::<Mapset>::decode(&mut r)?;
283 r.finish()?;
284 check_mapsets(&mapsets).map_err(|reason| bad(Section::BMS.name, reason))?;
285
286 let parts = options.into_iter().zip(layouts).zip(bodies).zip(sql).zip(debug);
287 let mut programs = Vec::with_capacity(count);
288 for ((((options, (storage, items, edits)), body), sql), debug) in parts {
289 let LirRecord {
290 id, initial, recursive, paragraphs, procedure_start, ranges, blocks, places, exprs, conds, consts, plans,
291 services, abends, symbols,
292 } = body;
293 let program = Program {
294 id, options, initial, recursive, storage, items, paragraphs, procedure_start, ranges, blocks, places, exprs,
295 conds, consts, plans, services, sql, abends, edits, symbols, debug,
296 };
297 crate::lir::program_valid(&program).map_err(|reason| bad(Section::SQL.name, reason))?;
298 programs.push(program);
299 }
300 check_directory(&directory, &programs)?;
301 Ok(LoadedModule { directory, programs, mapsets })
302}