1use super::codec::{Decode, Encode, Reader, Writer};
4use super::{Module, ModuleError, ModuleWriter, Section, StringTable};
5use numeric::Assumed;
6use crate::bms::Mapset;
7use crate::codec_struct;
8use crate::lir::{AssignItem,
9 AbendText, Block, Code, Cond, Const, Debug, Edit, Expr, Item, ParaId, Paragraph, Place, Plans, Program, ProgramOptions, Range,
10 Services, SqlEntry, Storage, SymId, TableRange,
11};
12
13#[derive(Clone, Debug, PartialEq, Eq)]
15pub struct DirectoryEntry {
16 pub id: String,
18 pub external: Option<String>,
21 pub parent: Option<u32>,
23 pub common: bool,
24 pub entries: Vec<(String, ParaId)>,
26 pub params: Vec<bool>,
28 pub returning: bool,
29 pub dynamic: bool,
31}
32
33codec_struct!(DirectoryEntry { id, external, parent, common, entries, params, returning, dynamic });
34
35impl DirectoryEntry {
36 pub fn top_level(program: &Program) -> Self {
38 Self {
39 id: program.symbols.get(program.id as usize).cloned().unwrap_or_default(),
40 external: None,
41 parent: None,
42 common: false,
43 entries: Vec::new(),
44 params: vec![false; program.storage.using.len()],
45 returning: program.storage.returning.is_some(),
46 dynamic: true,
47 }
48 }
49
50 pub fn load_name(&self) -> &str {
52 self.external.as_deref().unwrap_or(&self.id)
53 }
54}
55
56#[derive(Clone, Debug, PartialEq, Eq)]
60pub struct SourceFile {
61 pub root: u32,
62 pub path: String,
63 pub sha256: [u8; 32],
64 pub bytes: u64,
65}
66
67codec_struct!(SourceFile { root, path, sha256, bytes } check source_file_valid);
68
69fn source_file_valid(file: &SourceFile) -> Result<(), String> {
72 let relative = !file.path.contains('\\') && file.path.split('/').all(|part| !matches!(part, "" | "." | ".."));
73 if relative { Ok(()) } else { Err(format!("source file {:?} is not a path within its library", file.path)) }
74}
75
76#[derive(Clone, Debug, PartialEq, Eq)]
80pub struct LoadedModule {
81 pub directory: Vec<DirectoryEntry>,
82 pub programs: Vec<Program>,
83 pub mapsets: Vec<Mapset>,
84 pub files: Vec<Vec<Option<SourceFile>>>,
85}
86
87struct Parts<'a> {
89 id: &'a SymId,
90 options: &'a ProgramOptions,
91 initial: &'a bool,
92 recursive: &'a bool,
93 storage: &'a Storage,
94 items: &'a Vec<Item>,
95 paragraphs: &'a Vec<Paragraph>,
96 procedure_start: &'a ParaId,
97 ranges: &'a Vec<Range>,
98 blocks: &'a Vec<Block>,
99 places: &'a Vec<Place>,
100 exprs: &'a Vec<Expr>,
101 conds: &'a Vec<Cond>,
102 consts: &'a Vec<Const>,
103 plans: &'a Plans,
104 services: &'a Services,
105 sql: &'a Vec<SqlEntry>,
106 abends: &'a Vec<AbendText>,
107 edits: &'a Vec<Edit>,
108 symbols: &'a Vec<String>,
109 debug: &'a Debug,
110}
111
112impl<'a> Parts<'a> {
113 fn of(program: &'a Program) -> Self {
114 let Program {
115 id, options, initial, recursive, storage, items, paragraphs, procedure_start, ranges, blocks, places,
116 exprs, conds, consts, plans, services, sql, abends, edits, symbols, debug,
117 } = program;
118 Self {
119 id, options, initial, recursive, storage, items, paragraphs, procedure_start, ranges, blocks, places,
120 exprs, conds, consts, plans, services, sql, abends, edits, symbols, debug,
121 }
122 }
123
124 fn encode_lir(&self, w: &mut Writer) {
125 self.id.encode(w);
126 self.initial.encode(w);
127 self.recursive.encode(w);
128 self.paragraphs.encode(w);
129 self.procedure_start.encode(w);
130 self.ranges.encode(w);
131 self.blocks.encode(w);
132 self.places.encode(w);
133 self.exprs.encode(w);
134 self.conds.encode(w);
135 self.consts.encode(w);
136 self.plans.encode(w);
137 self.services.encode(w);
138 self.abends.encode(w);
139 self.symbols.encode(w);
140 }
141}
142
143pub type LayoutRecord = (Storage, Vec<Item>, Vec<Edit>);
145
146#[derive(Clone, Debug, PartialEq, Eq)]
148pub struct LirRecord {
149 pub id: SymId,
150 pub initial: bool,
151 pub recursive: bool,
152 pub paragraphs: Vec<Paragraph>,
153 pub procedure_start: ParaId,
154 pub ranges: Vec<Range>,
155 pub blocks: Vec<Block>,
156 pub places: Vec<Place>,
157 pub exprs: Vec<Expr>,
158 pub conds: Vec<Cond>,
159 pub consts: Vec<Const>,
160 pub plans: Plans,
161 pub services: Services,
162 pub abends: Vec<AbendText>,
163 pub symbols: Vec<String>,
164}
165
166impl LirRecord {
167 pub fn code(&self) -> Code<'_> {
168 Code {
169 id: self.id,
170 initial: self.initial,
171 recursive: self.recursive,
172 paragraphs: &self.paragraphs,
173 procedure_start: self.procedure_start,
174 ranges: &self.ranges,
175 blocks: &self.blocks,
176 places: &self.places,
177 exprs: &self.exprs,
178 conds: &self.conds,
179 consts: &self.consts,
180 plans: &self.plans,
181 services: &self.services,
182 abends: &self.abends,
183 symbols: &self.symbols,
184 }
185 }
186}
187
188impl Decode for LirRecord {
189 fn decode(r: &mut Reader<'_>) -> Result<Self, ModuleError> {
190 Ok(Self {
191 id: Decode::decode(r)?,
192 initial: Decode::decode(r)?,
193 recursive: Decode::decode(r)?,
194 paragraphs: Decode::decode(r)?,
195 procedure_start: Decode::decode(r)?,
196 ranges: Decode::decode(r)?,
197 blocks: Decode::decode(r)?,
198 places: Decode::decode(r)?,
199 exprs: Decode::decode(r)?,
200 conds: Decode::decode(r)?,
201 consts: Decode::decode(r)?,
202 plans: Decode::decode(r)?,
203 services: Decode::decode(r)?,
204 abends: Decode::decode(r)?,
205 symbols: Decode::decode(r)?,
206 })
207 }
208}
209
210fn per_program(w: &mut Writer, programs: &[Program], record: impl Fn(&Parts<'_>, &mut Writer)) {
212 w.count(programs.len());
213 for program in programs {
214 record(&Parts::of(program), w);
215 }
216}
217
218fn encode_module(programs: &[Program], directory: &[DirectoryEntry], mapsets: &[Mapset], files: &[Vec<Option<SourceFile>>]) -> Vec<u8> {
219 let mut m = ModuleWriter::new();
220 m.section(Section::DIRECTORY, |w| {
221 w.count(directory.len());
222 for entry in directory {
223 entry.encode(w);
224 }
225 });
226 m.section(Section::OPTIONS, |w| {
227 per_program(w, programs, |p, w| p.options.encode(w));
228 let assumed = assumed_options(programs);
229 if !assumed.is_empty() {
230 assumed.encode(w);
231 }
232 });
233 m.section(Section::LAYOUT, |w| {
234 per_program(w, programs, |p, w| {
235 p.storage.encode(w);
236 p.items.encode(w);
237 p.edits.encode(w);
238 });
239 });
240 m.section(Section::LIR, |w| {
241 per_program(w, programs, |p, w| p.encode_lir(w));
242 let (assigned, ranges) = (assign_items(programs), table_ranges(programs));
243 if !assigned.is_empty() || !ranges.is_empty() {
244 assigned.encode(w);
245 }
246 if !ranges.is_empty() {
247 ranges.encode(w);
248 }
249 });
250 m.section(Section::SQL, |w| per_program(w, programs, |p, w| p.sql.encode(w)));
251 m.section(Section::BMS, |w| {
252 w.count(mapsets.len());
253 for mapset in mapsets {
254 mapset.encode(w);
255 }
256 });
257 m.section(Section::DEBUG, |w| {
258 w.count(programs.len());
259 for (program, files) in programs.iter().zip(files) {
260 Parts::of(program).debug.encode(w);
261 files.encode(w);
262 }
263 });
264 m.finish()
265}
266
267fn assumed_options(programs: &[Program]) -> Vec<(u32, Assumed)> {
270 programs.iter().enumerate().filter(|(_, p)| p.options.options.assumed != Assumed::default()).map(|(n, p)| (n as u32, p.options.options.assumed)).collect()
271}
272
273pub struct OptionRecords(pub Vec<ProgramOptions>);
275
276impl Decode for OptionRecords {
277 fn decode(r: &mut Reader<'_>) -> Result<Self, ModuleError> {
278 let mut options = Vec::<ProgramOptions>::decode(r)?;
279 if r.remaining() > 0 {
280 let at = r.position();
281 for (n, assumed) in Vec::<(u32, Assumed)>::decode(r)? {
282 let count = options.len();
283 let program = options.get_mut(n as usize).ok_or_else(|| r.malformed(at, format!("--assume choices for program {n} of {count}")))?;
284 program.options.assumed = assumed;
285 }
286 }
287 Ok(Self(options))
288 }
289}
290
291fn option_records(module: &Module<'_>, strings: &StringTable, expected: usize) -> Result<Vec<ProgramOptions>, ModuleError> {
292 let mut r = module.reader(Section::OPTIONS, strings)?;
293 let at = r.position();
294 let OptionRecords(options) = OptionRecords::decode(&mut r)?;
295 if options.len() != expected {
296 return Err(r.malformed(at, format!("{} records for {expected} programs", options.len())));
297 }
298 r.finish()?;
299 Ok(options)
300}
301
302fn assign_items(programs: &[Program]) -> Vec<(u32, u32, AssignItem)> {
306 let mut out = Vec::new();
307 for (n, program) in programs.iter().enumerate() {
308 for (k, file) in program.services.files.iter().enumerate() {
309 if let Some(item) = file.assign_item {
310 out.push((n as u32, k as u32, item));
311 }
312 }
313 }
314 out
315}
316
317fn table_ranges(programs: &[Program]) -> Vec<(u32, u32, TableRange)> {
320 let mut out = Vec::new();
321 for (n, program) in programs.iter().enumerate() {
322 for (k, place) in program.places.iter().enumerate() {
323 if let Some(range) = place.table {
324 out.push((n as u32, k as u32, range));
325 }
326 }
327 }
328 out
329}
330
331pub struct LirRecords(pub Vec<LirRecord>);
334
335impl Decode for LirRecords {
336 fn decode(r: &mut Reader<'_>) -> Result<Self, ModuleError> {
337 let mut bodies = Vec::<LirRecord>::decode(r)?;
338 if r.remaining() > 0 {
339 let at = r.position();
340 let count = bodies.len();
341 for (n, k, item) in Vec::<(u32, u32, AssignItem)>::decode(r)? {
342 let body = bodies.get_mut(n as usize).ok_or_else(|| r.malformed(at, format!("an assign item for program {n} of {count}")))?;
343 let places = body.places.len();
344 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}")))?;
345 if item.place as usize >= places {
346 return Err(r.malformed(at, format!("an assign item's place {} of {places}", item.place)));
347 }
348 file.assign_item = Some(item);
349 }
350 }
351 if r.remaining() > 0 {
352 let at = r.position();
353 let count = bodies.len();
354 for (n, k, range) in Vec::<(u32, u32, TableRange)>::decode(r)? {
355 let body = bodies.get_mut(n as usize).ok_or_else(|| r.malformed(at, format!("a table range for program {n} of {count}")))?;
356 let places = body.places.len();
357 let place = body.places.get_mut(k as usize).ok_or_else(|| r.malformed(at, format!("a table range for place {k} of {places} in program {n}")))?;
358 place.table = Some(range);
359 }
360 }
361 Ok(Self(bodies))
362 }
363}
364
365fn lir_records(module: &Module<'_>, strings: &StringTable, expected: usize) -> Result<Vec<LirRecord>, ModuleError> {
366 let mut r = module.reader(Section::LIR, strings)?;
367 let at = r.position();
368 let LirRecords(bodies) = LirRecords::decode(&mut r)?;
369 if bodies.len() != expected {
370 return Err(r.malformed(at, format!("{} records for {expected} programs", bodies.len())));
371 }
372 r.finish()?;
373 Ok(bodies)
374}
375
376fn unrecorded(programs: &[Program]) -> Vec<Vec<Option<SourceFile>>> {
378 programs.iter().map(|p| vec![None; p.debug.sources.len()]).collect()
379}
380
381pub fn write(programs: &[Program]) -> Vec<u8> {
384 let directory: Vec<_> = programs.iter().map(DirectoryEntry::top_level).collect();
385 encode_module(programs, &directory, &[], &unrecorded(programs))
386}
387
388pub fn write_with(programs: &[Program], directory: &[DirectoryEntry], mapsets: &[Mapset]) -> Result<Vec<u8>, ModuleError> {
391 let files = unrecorded(programs);
392 write_module(&LoadedModule { directory: directory.to_vec(), programs: programs.to_vec(), mapsets: mapsets.to_vec(), files })
393}
394
395pub fn write_module(module: &LoadedModule) -> Result<Vec<u8>, ModuleError> {
398 let LoadedModule { directory, programs, mapsets, files } = module;
399 check_directory(directory, programs)?;
400 for program in programs {
401 crate::lir::program_valid(program).map_err(|reason| bad("LIR", reason))?;
402 }
403 check_mapsets(mapsets).map_err(|reason| bad(Section::BMS.name, reason))?;
404 check_files(files, programs).map_err(|reason| bad(Section::DEBUG.name, reason))?;
405 Ok(encode_module(programs, directory, mapsets, files))
406}
407
408fn check_files(files: &[Vec<Option<SourceFile>>], programs: &[Program]) -> Result<(), String> {
410 if files.len() != programs.len() {
411 return Err(format!("files for {} programs of {}", files.len(), programs.len()));
412 }
413 for (ordinal, (files, program)) in files.iter().zip(programs).enumerate() {
414 if files.len() != program.debug.sources.len() {
415 return Err(format!("program {ordinal} records {} files for {} sources", files.len(), program.debug.sources.len()));
416 }
417 files.iter().flatten().try_for_each(source_file_valid)?;
418 }
419 Ok(())
420}
421
422fn check_mapsets(mapsets: &[Mapset]) -> Result<(), String> {
424 match mapsets.windows(2).find(|pair| pair[0].name >= pair[1].name) {
425 Some(pair) => Err(format!("mapset {} follows mapset {}", pair[1].name, pair[0].name)),
426 None => Ok(()),
427 }
428}
429
430fn bad(section: &'static str, reason: impl Into<String>) -> ModuleError {
431 ModuleError::Malformed { section, offset: 0, reason: reason.into() }
432}
433
434fn check_directory(directory: &[DirectoryEntry], programs: &[Program]) -> Result<(), ModuleError> {
435 let name = Section::DIRECTORY.name;
436 if directory.len() != programs.len() {
437 return Err(bad(name, format!("{} entries for {} programs", directory.len(), programs.len())));
438 }
439 for (ordinal, (entry, program)) in directory.iter().zip(programs).enumerate() {
440 let symbol = program.symbols.get(program.id as usize);
441 if symbol != Some(&entry.id) {
442 return Err(bad(name, format!("program {ordinal} is {symbol:?} in its symbols, {:?} in the directory", entry.id)));
443 }
444 if entry.parent.is_some_and(|p| p as usize >= ordinal) {
445 return Err(bad(name, format!("program {ordinal} has parent {}, which does not precede it", entry.parent.unwrap_or(0))));
446 }
447 if let Some((entry_name, _)) = entry.entries.iter().find(|(_, para)| *para as usize >= program.paragraphs.len()) {
448 return Err(bad(name, format!("program {ordinal} ENTRY {entry_name} names a paragraph it lacks")));
449 }
450 }
451 Ok(())
452}
453
454fn records<T: Decode>(module: &Module<'_>, strings: &StringTable, section: Section, expected: usize) -> Result<Vec<T>, ModuleError> {
456 let mut r = module.reader(section, strings)?;
457 let at = r.position();
458 let count = r.count()?;
459 if count != expected {
460 return Err(r.malformed(at, format!("{count} records for {expected} programs")));
461 }
462 let mut out = Vec::with_capacity(count);
463 for _ in 0..count {
464 out.push(T::decode(&mut r)?);
465 }
466 r.finish()?;
467 Ok(out)
468}
469
470pub fn read(bytes: &[u8]) -> Result<LoadedModule, ModuleError> {
472 let module = Module::read(bytes)?;
473 let strings = module.strings()?;
474 let mut r = module.reader(Section::DIRECTORY, &strings)?;
475 let directory = Vec::<DirectoryEntry>::decode(&mut r)?;
476 r.finish()?;
477 let count = directory.len();
478
479 let options = option_records(&module, &strings, count)?;
480 let layouts = records::<LayoutRecord>(&module, &strings, Section::LAYOUT, count)?;
481 let bodies = lir_records(&module, &strings, count)?;
482 let sql = records::<Vec<SqlEntry>>(&module, &strings, Section::SQL, count)?;
483 let (debug, files): (Vec<Debug>, Vec<Vec<Option<SourceFile>>>) = records::<(Debug, Vec<Option<SourceFile>>)>(&module, &strings, Section::DEBUG, count)?.into_iter().unzip();
484
485 let mut r = module.reader(Section::BMS, &strings)?;
486 let mapsets = Vec::<Mapset>::decode(&mut r)?;
487 r.finish()?;
488 check_mapsets(&mapsets).map_err(|reason| bad(Section::BMS.name, reason))?;
489
490 let parts = options.into_iter().zip(layouts).zip(bodies).zip(sql).zip(debug);
491 let mut programs = Vec::with_capacity(count);
492 for ((((options, (storage, items, edits)), body), sql), debug) in parts {
493 let LirRecord {
494 id, initial, recursive, paragraphs, procedure_start, ranges, blocks, places, exprs, conds, consts, plans,
495 services, abends, symbols,
496 } = body;
497 let program = Program {
498 id, options, initial, recursive, storage, items, paragraphs, procedure_start, ranges, blocks, places, exprs,
499 conds, consts, plans, services, sql, abends, edits, symbols, debug,
500 };
501 crate::lir::program_valid(&program).map_err(|reason| bad(Section::SQL.name, reason))?;
502 programs.push(program);
503 }
504 check_directory(&directory, &programs)?;
505 check_files(&files, &programs).map_err(|reason| bad(Section::DEBUG.name, reason))?;
506 Ok(LoadedModule { directory, programs, mapsets, files })
507}