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::{AssignItem,
8 AbendText, Block, Code, 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)]
59pub struct SourceFile {
60 pub root: u32,
61 pub path: String,
62 pub sha256: [u8; 32],
63 pub bytes: u64,
64}
65
66codec_struct!(SourceFile { root, path, sha256, bytes } check source_file_valid);
67
68fn source_file_valid(file: &SourceFile) -> Result<(), String> {
71 let relative = !file.path.contains('\\') && file.path.split('/').all(|part| !matches!(part, "" | "." | ".."));
72 if relative { Ok(()) } else { Err(format!("source file {:?} is not a path within its library", file.path)) }
73}
74
75#[derive(Clone, Debug, PartialEq, Eq)]
79pub struct LoadedModule {
80 pub directory: Vec<DirectoryEntry>,
81 pub programs: Vec<Program>,
82 pub mapsets: Vec<Mapset>,
83 pub files: Vec<Vec<Option<SourceFile>>>,
84}
85
86struct Parts<'a> {
88 id: &'a SymId,
89 options: &'a ProgramOptions,
90 initial: &'a bool,
91 recursive: &'a bool,
92 storage: &'a Storage,
93 items: &'a Vec<Item>,
94 paragraphs: &'a Vec<Paragraph>,
95 procedure_start: &'a ParaId,
96 ranges: &'a Vec<Range>,
97 blocks: &'a Vec<Block>,
98 places: &'a Vec<Place>,
99 exprs: &'a Vec<Expr>,
100 conds: &'a Vec<Cond>,
101 consts: &'a Vec<Const>,
102 plans: &'a Plans,
103 services: &'a Services,
104 sql: &'a Vec<SqlEntry>,
105 abends: &'a Vec<AbendText>,
106 edits: &'a Vec<Edit>,
107 symbols: &'a Vec<String>,
108 debug: &'a Debug,
109}
110
111impl<'a> Parts<'a> {
112 fn of(program: &'a Program) -> Self {
113 let Program {
114 id, options, initial, recursive, storage, items, paragraphs, procedure_start, ranges, blocks, places,
115 exprs, conds, consts, plans, services, sql, abends, edits, symbols, debug,
116 } = program;
117 Self {
118 id, options, initial, recursive, storage, items, paragraphs, procedure_start, ranges, blocks, places,
119 exprs, conds, consts, plans, services, sql, abends, edits, symbols, debug,
120 }
121 }
122
123 fn encode_lir(&self, w: &mut Writer) {
124 self.id.encode(w);
125 self.initial.encode(w);
126 self.recursive.encode(w);
127 self.paragraphs.encode(w);
128 self.procedure_start.encode(w);
129 self.ranges.encode(w);
130 self.blocks.encode(w);
131 self.places.encode(w);
132 self.exprs.encode(w);
133 self.conds.encode(w);
134 self.consts.encode(w);
135 self.plans.encode(w);
136 self.services.encode(w);
137 self.abends.encode(w);
138 self.symbols.encode(w);
139 }
140}
141
142pub type LayoutRecord = (Storage, Vec<Item>, Vec<Edit>);
144
145#[derive(Clone, Debug, PartialEq, Eq)]
147pub struct LirRecord {
148 pub id: SymId,
149 pub initial: bool,
150 pub recursive: bool,
151 pub paragraphs: Vec<Paragraph>,
152 pub procedure_start: ParaId,
153 pub ranges: Vec<Range>,
154 pub blocks: Vec<Block>,
155 pub places: Vec<Place>,
156 pub exprs: Vec<Expr>,
157 pub conds: Vec<Cond>,
158 pub consts: Vec<Const>,
159 pub plans: Plans,
160 pub services: Services,
161 pub abends: Vec<AbendText>,
162 pub symbols: Vec<String>,
163}
164
165impl LirRecord {
166 pub fn code(&self) -> Code<'_> {
167 Code {
168 id: self.id,
169 initial: self.initial,
170 recursive: self.recursive,
171 paragraphs: &self.paragraphs,
172 procedure_start: self.procedure_start,
173 ranges: &self.ranges,
174 blocks: &self.blocks,
175 places: &self.places,
176 exprs: &self.exprs,
177 conds: &self.conds,
178 consts: &self.consts,
179 plans: &self.plans,
180 services: &self.services,
181 abends: &self.abends,
182 symbols: &self.symbols,
183 }
184 }
185}
186
187impl Decode for LirRecord {
188 fn decode(r: &mut Reader<'_>) -> Result<Self, ModuleError> {
189 Ok(Self {
190 id: Decode::decode(r)?,
191 initial: Decode::decode(r)?,
192 recursive: Decode::decode(r)?,
193 paragraphs: Decode::decode(r)?,
194 procedure_start: Decode::decode(r)?,
195 ranges: Decode::decode(r)?,
196 blocks: Decode::decode(r)?,
197 places: Decode::decode(r)?,
198 exprs: Decode::decode(r)?,
199 conds: Decode::decode(r)?,
200 consts: Decode::decode(r)?,
201 plans: Decode::decode(r)?,
202 services: Decode::decode(r)?,
203 abends: Decode::decode(r)?,
204 symbols: Decode::decode(r)?,
205 })
206 }
207}
208
209fn per_program(w: &mut Writer, programs: &[Program], record: impl Fn(&Parts<'_>, &mut Writer)) {
211 w.count(programs.len());
212 for program in programs {
213 record(&Parts::of(program), w);
214 }
215}
216
217fn encode_module(programs: &[Program], directory: &[DirectoryEntry], mapsets: &[Mapset], files: &[Vec<Option<SourceFile>>]) -> Vec<u8> {
218 let mut m = ModuleWriter::new();
219 m.section(Section::DIRECTORY, |w| {
220 w.count(directory.len());
221 for entry in directory {
222 entry.encode(w);
223 }
224 });
225 m.section(Section::OPTIONS, |w| per_program(w, programs, |p, w| p.options.encode(w)));
226 m.section(Section::LAYOUT, |w| {
227 per_program(w, programs, |p, w| {
228 p.storage.encode(w);
229 p.items.encode(w);
230 p.edits.encode(w);
231 });
232 });
233 m.section(Section::LIR, |w| {
234 per_program(w, programs, |p, w| p.encode_lir(w));
235 let assigned = assign_items(programs);
236 if !assigned.is_empty() {
237 assigned.encode(w);
238 }
239 });
240 m.section(Section::SQL, |w| per_program(w, programs, |p, w| p.sql.encode(w)));
241 m.section(Section::BMS, |w| {
242 w.count(mapsets.len());
243 for mapset in mapsets {
244 mapset.encode(w);
245 }
246 });
247 m.section(Section::DEBUG, |w| {
248 w.count(programs.len());
249 for (program, files) in programs.iter().zip(files) {
250 Parts::of(program).debug.encode(w);
251 files.encode(w);
252 }
253 });
254 m.finish()
255}
256
257fn assign_items(programs: &[Program]) -> Vec<(u32, u32, AssignItem)> {
261 let mut out = Vec::new();
262 for (n, program) in programs.iter().enumerate() {
263 for (k, file) in program.services.files.iter().enumerate() {
264 if let Some(item) = file.assign_item {
265 out.push((n as u32, k as u32, item));
266 }
267 }
268 }
269 out
270}
271
272fn lir_records(module: &Module<'_>, strings: &StringTable, expected: usize) -> Result<Vec<LirRecord>, ModuleError> {
274 let mut r = module.reader(Section::LIR, strings)?;
275 let at = r.position();
276 let count = r.count()?;
277 if count != expected {
278 return Err(r.malformed(at, format!("{count} records for {expected} programs")));
279 }
280 let mut bodies = Vec::with_capacity(count);
281 for _ in 0..count {
282 bodies.push(LirRecord::decode(&mut r)?);
283 }
284 if r.remaining() > 0 {
285 let at = r.position();
286 for (n, k, item) in Vec::<(u32, u32, AssignItem)>::decode(&mut r)? {
287 let body = bodies.get_mut(n as usize).ok_or_else(|| r.malformed(at, format!("an assign item for program {n} of {count}")))?;
288 let places = body.places.len();
289 let file = body.services.files.get_mut(k as usize).ok_or_else(|| r.malformed(at, format!("an assign item for file {k} of program {n}")))?;
290 if item.place as usize >= places {
291 return Err(r.malformed(at, format!("an assign item's place {} of {places}", item.place)));
292 }
293 file.assign_item = Some(item);
294 }
295 }
296 r.finish()?;
297 Ok(bodies)
298}
299
300fn unrecorded(programs: &[Program]) -> Vec<Vec<Option<SourceFile>>> {
302 programs.iter().map(|p| vec![None; p.debug.sources.len()]).collect()
303}
304
305pub fn write(programs: &[Program]) -> Vec<u8> {
308 let directory: Vec<_> = programs.iter().map(DirectoryEntry::top_level).collect();
309 encode_module(programs, &directory, &[], &unrecorded(programs))
310}
311
312pub fn write_with(programs: &[Program], directory: &[DirectoryEntry], mapsets: &[Mapset]) -> Result<Vec<u8>, ModuleError> {
315 let files = unrecorded(programs);
316 write_module(&LoadedModule { directory: directory.to_vec(), programs: programs.to_vec(), mapsets: mapsets.to_vec(), files })
317}
318
319pub fn write_module(module: &LoadedModule) -> Result<Vec<u8>, ModuleError> {
322 let LoadedModule { directory, programs, mapsets, files } = module;
323 check_directory(directory, programs)?;
324 for program in programs {
325 crate::lir::program_valid(program).map_err(|reason| bad("LIR", reason))?;
326 }
327 check_mapsets(mapsets).map_err(|reason| bad(Section::BMS.name, reason))?;
328 check_files(files, programs).map_err(|reason| bad(Section::DEBUG.name, reason))?;
329 Ok(encode_module(programs, directory, mapsets, files))
330}
331
332fn check_files(files: &[Vec<Option<SourceFile>>], programs: &[Program]) -> Result<(), String> {
334 if files.len() != programs.len() {
335 return Err(format!("files for {} programs of {}", files.len(), programs.len()));
336 }
337 for (ordinal, (files, program)) in files.iter().zip(programs).enumerate() {
338 if files.len() != program.debug.sources.len() {
339 return Err(format!("program {ordinal} records {} files for {} sources", files.len(), program.debug.sources.len()));
340 }
341 files.iter().flatten().try_for_each(source_file_valid)?;
342 }
343 Ok(())
344}
345
346fn check_mapsets(mapsets: &[Mapset]) -> Result<(), String> {
348 match mapsets.windows(2).find(|pair| pair[0].name >= pair[1].name) {
349 Some(pair) => Err(format!("mapset {} follows mapset {}", pair[1].name, pair[0].name)),
350 None => Ok(()),
351 }
352}
353
354fn bad(section: &'static str, reason: impl Into<String>) -> ModuleError {
355 ModuleError::Malformed { section, offset: 0, reason: reason.into() }
356}
357
358fn check_directory(directory: &[DirectoryEntry], programs: &[Program]) -> Result<(), ModuleError> {
359 let name = Section::DIRECTORY.name;
360 if directory.len() != programs.len() {
361 return Err(bad(name, format!("{} entries for {} programs", directory.len(), programs.len())));
362 }
363 for (ordinal, (entry, program)) in directory.iter().zip(programs).enumerate() {
364 let symbol = program.symbols.get(program.id as usize);
365 if symbol != Some(&entry.id) {
366 return Err(bad(name, format!("program {ordinal} is {symbol:?} in its symbols, {:?} in the directory", entry.id)));
367 }
368 if entry.parent.is_some_and(|p| p as usize >= ordinal) {
369 return Err(bad(name, format!("program {ordinal} has parent {}, which does not precede it", entry.parent.unwrap_or(0))));
370 }
371 if let Some((entry_name, _)) = entry.entries.iter().find(|(_, para)| *para as usize >= program.paragraphs.len()) {
372 return Err(bad(name, format!("program {ordinal} ENTRY {entry_name} names a paragraph it lacks")));
373 }
374 }
375 Ok(())
376}
377
378fn records<T: Decode>(module: &Module<'_>, strings: &StringTable, section: Section, expected: usize) -> Result<Vec<T>, ModuleError> {
380 let mut r = module.reader(section, strings)?;
381 let at = r.position();
382 let count = r.count()?;
383 if count != expected {
384 return Err(r.malformed(at, format!("{count} records for {expected} programs")));
385 }
386 let mut out = Vec::with_capacity(count);
387 for _ in 0..count {
388 out.push(T::decode(&mut r)?);
389 }
390 r.finish()?;
391 Ok(out)
392}
393
394pub fn read(bytes: &[u8]) -> Result<LoadedModule, ModuleError> {
396 let module = Module::read(bytes)?;
397 let strings = module.strings()?;
398 let mut r = module.reader(Section::DIRECTORY, &strings)?;
399 let directory = Vec::<DirectoryEntry>::decode(&mut r)?;
400 r.finish()?;
401 let count = directory.len();
402
403 let options = records::<ProgramOptions>(&module, &strings, Section::OPTIONS, count)?;
404 let layouts = records::<LayoutRecord>(&module, &strings, Section::LAYOUT, count)?;
405 let bodies = lir_records(&module, &strings, count)?;
406 let sql = records::<Vec<SqlEntry>>(&module, &strings, Section::SQL, count)?;
407 let (debug, files): (Vec<Debug>, Vec<Vec<Option<SourceFile>>>) = records::<(Debug, Vec<Option<SourceFile>>)>(&module, &strings, Section::DEBUG, count)?.into_iter().unzip();
408
409 let mut r = module.reader(Section::BMS, &strings)?;
410 let mapsets = Vec::<Mapset>::decode(&mut r)?;
411 r.finish()?;
412 check_mapsets(&mapsets).map_err(|reason| bad(Section::BMS.name, reason))?;
413
414 let parts = options.into_iter().zip(layouts).zip(bodies).zip(sql).zip(debug);
415 let mut programs = Vec::with_capacity(count);
416 for ((((options, (storage, items, edits)), body), sql), debug) in parts {
417 let LirRecord {
418 id, initial, recursive, paragraphs, procedure_start, ranges, blocks, places, exprs, conds, consts, plans,
419 services, abends, symbols,
420 } = body;
421 let program = Program {
422 id, options, initial, recursive, storage, items, paragraphs, procedure_start, ranges, blocks, places, exprs,
423 conds, consts, plans, services, sql, abends, edits, symbols, debug,
424 };
425 crate::lir::program_valid(&program).map_err(|reason| bad(Section::SQL.name, reason))?;
426 programs.push(program);
427 }
428 check_directory(&directory, &programs)?;
429 check_files(&files, &programs).map_err(|reason| bad(Section::DEBUG.name, reason))?;
430 Ok(LoadedModule { directory, programs, mapsets, files })
431}