1use super::codec::{Decode, Encode, Reader, Writer};
4use super::{Module, ModuleError, ModuleWriter, Section, StringTable};
5use numeric::Assumed;
6use numeric::governs::{Facts, Statement, Usage};
7use crate::bms::Mapset;
8use crate::codec_struct;
9use crate::lir::{AssignItem,
10 AbendText, Block, Code, Cond, Const, Debug, Edit, Expr, Item, ParaId, Paragraph, Place, Plans, Program, ProgramOptions, Range,
11 RecordSpan, Services, SqlEntry, Storage, SymId, TableRange,
12};
13
14#[derive(Clone, Debug, PartialEq, Eq)]
16pub struct DirectoryEntry {
17 pub id: String,
19 pub external: Option<String>,
22 pub parent: Option<u32>,
24 pub common: bool,
25 pub entries: Vec<(String, ParaId)>,
27 pub params: Vec<bool>,
29 pub returning: bool,
30 pub dynamic: bool,
32}
33
34codec_struct!(DirectoryEntry { id, external, parent, common, entries, params, returning, dynamic });
35
36impl DirectoryEntry {
37 pub fn top_level(program: &Program) -> Self {
39 Self {
40 id: program.symbols.get(program.id as usize).cloned().unwrap_or_default(),
41 external: None,
42 parent: None,
43 common: false,
44 entries: Vec::new(),
45 params: vec![false; program.storage.using.len()],
46 returning: program.storage.returning.is_some(),
47 dynamic: true,
48 }
49 }
50
51 pub fn load_name(&self) -> &str {
53 self.external.as_deref().unwrap_or(&self.id)
54 }
55}
56
57#[derive(Clone, Debug, PartialEq, Eq)]
61pub struct SourceFile {
62 pub root: u32,
63 pub path: String,
64 pub sha256: [u8; 32],
65 pub bytes: u64,
66}
67
68codec_struct!(SourceFile { root, path, sha256, bytes } check source_file_valid);
69
70fn source_file_valid(file: &SourceFile) -> Result<(), String> {
73 let relative = !file.path.contains('\\') && file.path.split('/').all(|part| !matches!(part, "" | "." | ".."));
74 if relative { Ok(()) } else { Err(format!("source file {:?} is not a path within its library", file.path)) }
75}
76
77#[derive(Clone, Debug, PartialEq, Eq)]
82pub struct LoadedModule {
83 pub directory: Vec<DirectoryEntry>,
84 pub programs: Vec<Program>,
85 pub mapsets: Vec<Mapset>,
86 pub files: Vec<Vec<Option<SourceFile>>>,
87 pub facts: Vec<Option<Facts>>,
88}
89
90pub const FACTS: Section = Section { id: super::EXTENSIONS, name: "FACTS" };
92
93type FactsRecord = Option<(Vec<String>, Vec<String>)>;
95
96fn facts_record(facts: &Option<Facts>) -> FactsRecord {
97 facts.map(|f| (f.statements().map(|s| s.name().to_owned()).collect(), f.usages().map(|u| u.name().to_owned()).collect()))
98}
99
100pub fn held_facts(recorded: Option<Facts>, program: &Program) -> Facts {
103 let mut facts = recorded.unwrap_or_else(Facts::every_construct);
104 let options = &program.options;
105 facts.union(Facts::of_options(&options.options, options.ssrange, !options.cards.is_empty()));
106 facts
107}
108
109fn record_facts(record: FactsRecord) -> Option<Facts> {
111 record.map(|(statements, usages)| {
112 let mut facts = Facts::default();
113 statements.iter().filter_map(|s| Statement::named(s)).for_each(|s| facts.statement(s));
114 usages.iter().filter_map(|u| Usage::named(u)).for_each(|u| facts.usage(u));
115 facts
116 })
117}
118
119struct Parts<'a> {
121 id: &'a SymId,
122 options: &'a ProgramOptions,
123 initial: &'a bool,
124 recursive: &'a bool,
125 storage: &'a Storage,
126 items: &'a Vec<Item>,
127 paragraphs: &'a Vec<Paragraph>,
128 procedure_start: &'a ParaId,
129 ranges: &'a Vec<Range>,
130 blocks: &'a Vec<Block>,
131 places: &'a Vec<Place>,
132 exprs: &'a Vec<Expr>,
133 conds: &'a Vec<Cond>,
134 consts: &'a Vec<Const>,
135 plans: &'a Plans,
136 services: &'a Services,
137 sql: &'a Vec<SqlEntry>,
138 abends: &'a Vec<AbendText>,
139 edits: &'a Vec<Edit>,
140 symbols: &'a Vec<String>,
141 debug: &'a Debug,
142}
143
144impl<'a> Parts<'a> {
145 fn of(program: &'a Program) -> Self {
146 let Program {
147 id, options, initial, recursive, storage, items, paragraphs, procedure_start, ranges, blocks, places,
148 exprs, conds, consts, plans, services, sql, abends, edits, symbols, debug,
149 } = program;
150 Self {
151 id, options, initial, recursive, storage, items, paragraphs, procedure_start, ranges, blocks, places,
152 exprs, conds, consts, plans, services, sql, abends, edits, symbols, debug,
153 }
154 }
155
156 fn encode_lir(&self, w: &mut Writer) {
157 self.id.encode(w);
158 self.initial.encode(w);
159 self.recursive.encode(w);
160 self.paragraphs.encode(w);
161 self.procedure_start.encode(w);
162 self.ranges.encode(w);
163 self.blocks.encode(w);
164 self.places.encode(w);
165 self.exprs.encode(w);
166 self.conds.encode(w);
167 self.consts.encode(w);
168 self.plans.encode(w);
169 self.services.encode(w);
170 self.abends.encode(w);
171 self.symbols.encode(w);
172 }
173}
174
175pub type LayoutRecord = (Storage, Vec<Item>, Vec<Edit>);
177
178#[derive(Clone, Debug, PartialEq, Eq)]
180pub struct LirRecord {
181 pub id: SymId,
182 pub initial: bool,
183 pub recursive: bool,
184 pub paragraphs: Vec<Paragraph>,
185 pub procedure_start: ParaId,
186 pub ranges: Vec<Range>,
187 pub blocks: Vec<Block>,
188 pub places: Vec<Place>,
189 pub exprs: Vec<Expr>,
190 pub conds: Vec<Cond>,
191 pub consts: Vec<Const>,
192 pub plans: Plans,
193 pub services: Services,
194 pub abends: Vec<AbendText>,
195 pub symbols: Vec<String>,
196}
197
198impl LirRecord {
199 pub fn code(&self) -> Code<'_> {
200 Code {
201 id: self.id,
202 initial: self.initial,
203 recursive: self.recursive,
204 paragraphs: &self.paragraphs,
205 procedure_start: self.procedure_start,
206 ranges: &self.ranges,
207 blocks: &self.blocks,
208 places: &self.places,
209 exprs: &self.exprs,
210 conds: &self.conds,
211 consts: &self.consts,
212 plans: &self.plans,
213 services: &self.services,
214 abends: &self.abends,
215 symbols: &self.symbols,
216 }
217 }
218}
219
220impl Decode for LirRecord {
221 fn decode(r: &mut Reader<'_>) -> Result<Self, ModuleError> {
222 Ok(Self {
223 id: Decode::decode(r)?,
224 initial: Decode::decode(r)?,
225 recursive: Decode::decode(r)?,
226 paragraphs: Decode::decode(r)?,
227 procedure_start: Decode::decode(r)?,
228 ranges: Decode::decode(r)?,
229 blocks: Decode::decode(r)?,
230 places: Decode::decode(r)?,
231 exprs: Decode::decode(r)?,
232 conds: Decode::decode(r)?,
233 consts: Decode::decode(r)?,
234 plans: Decode::decode(r)?,
235 services: Decode::decode(r)?,
236 abends: Decode::decode(r)?,
237 symbols: Decode::decode(r)?,
238 })
239 }
240}
241
242fn per_program(w: &mut Writer, programs: &[Program], record: impl Fn(&Parts<'_>, &mut Writer)) {
244 w.count(programs.len());
245 for program in programs {
246 record(&Parts::of(program), w);
247 }
248}
249
250fn encode_module(programs: &[Program], directory: &[DirectoryEntry], mapsets: &[Mapset], files: &[Vec<Option<SourceFile>>], facts: &[Option<Facts>]) -> Vec<u8> {
251 let mut m = ModuleWriter::new();
252 m.section(Section::DIRECTORY, |w| {
253 w.count(directory.len());
254 for entry in directory {
255 entry.encode(w);
256 }
257 });
258 m.section(Section::OPTIONS, |w| {
259 per_program(w, programs, |p, w| p.options.encode(w));
260 let assumed = assumed_options(programs);
261 if !assumed.is_empty() {
262 assumed.encode(w);
263 }
264 });
265 m.section(Section::LAYOUT, |w| {
266 per_program(w, programs, |p, w| {
267 p.storage.encode(w);
268 p.items.encode(w);
269 p.edits.encode(w);
270 });
271 });
272 m.section(Section::LIR, |w| {
273 per_program(w, programs, |p, w| p.encode_lir(w));
274 let (assigned, ranges, split) = (assign_items(programs), table_ranges(programs), split_keys(programs));
275 if !assigned.is_empty() || !ranges.is_empty() || !split.is_empty() {
276 assigned.encode(w);
277 }
278 if !ranges.is_empty() || !split.is_empty() {
279 ranges.encode(w);
280 }
281 if !split.is_empty() {
282 split.encode(w);
283 }
284 });
285 m.section(Section::SQL, |w| per_program(w, programs, |p, w| p.sql.encode(w)));
286 m.section(Section::BMS, |w| {
287 w.count(mapsets.len());
288 for mapset in mapsets {
289 mapset.encode(w);
290 }
291 });
292 m.section(Section::DEBUG, |w| {
293 w.count(programs.len());
294 for (program, files) in programs.iter().zip(files) {
295 Parts::of(program).debug.encode(w);
296 files.encode(w);
297 }
298 });
299 if facts.iter().any(Option::is_some) {
300 m.extension(FACTS.id, |w| {
301 w.count(facts.len());
302 for program in facts {
303 facts_record(program).encode(w);
304 }
305 });
306 }
307 m.finish()
308}
309
310fn assumed_options(programs: &[Program]) -> Vec<(u32, Assumed)> {
313 programs.iter().enumerate().filter(|(_, p)| p.options.options.assumed != Assumed::default()).map(|(n, p)| (n as u32, p.options.options.assumed)).collect()
314}
315
316pub struct OptionRecords(pub Vec<ProgramOptions>);
318
319impl Decode for OptionRecords {
320 fn decode(r: &mut Reader<'_>) -> Result<Self, ModuleError> {
321 let mut options = Vec::<ProgramOptions>::decode(r)?;
322 if r.remaining() > 0 {
323 let at = r.position();
324 for (n, assumed) in Vec::<(u32, Assumed)>::decode(r)? {
325 let count = options.len();
326 let program = options.get_mut(n as usize).ok_or_else(|| r.malformed(at, format!("--assume choices for program {n} of {count}")))?;
327 program.options.assumed = assumed;
328 }
329 }
330 Ok(Self(options))
331 }
332}
333
334fn option_records(module: &Module<'_>, strings: &StringTable, expected: usize) -> Result<Vec<ProgramOptions>, ModuleError> {
335 let mut r = module.reader(Section::OPTIONS, strings)?;
336 let at = r.position();
337 let OptionRecords(options) = OptionRecords::decode(&mut r)?;
338 if options.len() != expected {
339 return Err(r.malformed(at, format!("{} records for {expected} programs", options.len())));
340 }
341 r.finish()?;
342 Ok(options)
343}
344
345fn assign_items(programs: &[Program]) -> Vec<(u32, u32, AssignItem)> {
349 let mut out = Vec::new();
350 for (n, program) in programs.iter().enumerate() {
351 for (k, file) in program.services.files.iter().enumerate() {
352 if let Some(item) = file.assign_item {
353 out.push((n as u32, k as u32, item));
354 }
355 }
356 }
357 out
358}
359
360fn table_ranges(programs: &[Program]) -> Vec<(u32, u32, TableRange)> {
363 let mut out = Vec::new();
364 for (n, program) in programs.iter().enumerate() {
365 for (k, place) in program.places.iter().enumerate() {
366 if let Some(range) = place.table {
367 out.push((n as u32, k as u32, range));
368 }
369 }
370 }
371 out
372}
373
374fn split_keys(programs: &[Program]) -> Vec<(u32, u32, u8, Vec<RecordSpan>)> {
377 let mut out = Vec::new();
378 for (n, program) in programs.iter().enumerate() {
379 for (k, file) in program.services.files.iter().enumerate() {
380 for (key, pieces) in file.keys.iter().flat_map(|keys| &keys.split) {
381 out.push((n as u32, k as u32, *key, pieces.clone()));
382 }
383 }
384 }
385 out
386}
387
388pub struct LirRecords(pub Vec<LirRecord>);
391
392impl Decode for LirRecords {
393 fn decode(r: &mut Reader<'_>) -> Result<Self, ModuleError> {
394 let mut bodies = Vec::<LirRecord>::decode(r)?;
395 if r.remaining() > 0 {
396 let at = r.position();
397 let count = bodies.len();
398 for (n, k, item) in Vec::<(u32, u32, AssignItem)>::decode(r)? {
399 let body = bodies.get_mut(n as usize).ok_or_else(|| r.malformed(at, format!("an assign item for program {n} of {count}")))?;
400 let places = body.places.len();
401 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}")))?;
402 if item.place as usize >= places {
403 return Err(r.malformed(at, format!("an assign item's place {} of {places}", item.place)));
404 }
405 file.assign_item = Some(item);
406 }
407 }
408 if r.remaining() > 0 {
409 let at = r.position();
410 let count = bodies.len();
411 for (n, k, range) in Vec::<(u32, u32, TableRange)>::decode(r)? {
412 let body = bodies.get_mut(n as usize).ok_or_else(|| r.malformed(at, format!("a table range for program {n} of {count}")))?;
413 let places = body.places.len();
414 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}")))?;
415 place.table = Some(range);
416 }
417 }
418 if r.remaining() > 0 {
419 let at = r.position();
420 let count = bodies.len();
421 for (n, k, key, pieces) in Vec::<(u32, u32, u8, Vec<RecordSpan>)>::decode(r)? {
422 let body = bodies.get_mut(n as usize).ok_or_else(|| r.malformed(at, format!("a split key for program {n} of {count}")))?;
423 let file = body.services.files.get_mut(k as usize).ok_or_else(|| r.malformed(at, format!("a split key for file {k} of program {n}")))?;
424 let keys = file.keys.as_mut().ok_or_else(|| r.malformed(at, format!("a split key for file {k} of program {n}, which has no keys")))?;
425 if usize::from(key) > keys.alternates.len() || pieces.is_empty() {
426 return Err(r.malformed(at, format!("split key {key} of file {k} of program {n}")));
427 }
428 keys.split.push((key, pieces));
429 }
430 }
431 Ok(Self(bodies))
432 }
433}
434
435fn lir_records(module: &Module<'_>, strings: &StringTable, expected: usize) -> Result<Vec<LirRecord>, ModuleError> {
436 let mut r = module.reader(Section::LIR, strings)?;
437 let at = r.position();
438 let LirRecords(bodies) = LirRecords::decode(&mut r)?;
439 if bodies.len() != expected {
440 return Err(r.malformed(at, format!("{} records for {expected} programs", bodies.len())));
441 }
442 r.finish()?;
443 Ok(bodies)
444}
445
446fn unrecorded(programs: &[Program]) -> Vec<Vec<Option<SourceFile>>> {
448 programs.iter().map(|p| vec![None; p.debug.sources.len()]).collect()
449}
450
451pub fn write(programs: &[Program]) -> Vec<u8> {
454 let directory: Vec<_> = programs.iter().map(DirectoryEntry::top_level).collect();
455 encode_module(programs, &directory, &[], &unrecorded(programs), &vec![None; programs.len()])
456}
457
458pub fn write_with(programs: &[Program], directory: &[DirectoryEntry], mapsets: &[Mapset]) -> Result<Vec<u8>, ModuleError> {
461 let files = unrecorded(programs);
462 write_module(&LoadedModule { directory: directory.to_vec(), programs: programs.to_vec(), mapsets: mapsets.to_vec(), files, facts: vec![None; programs.len()] })
463}
464
465pub fn write_module(module: &LoadedModule) -> Result<Vec<u8>, ModuleError> {
468 let LoadedModule { directory, programs, mapsets, files, facts } = module;
469 check_directory(directory, programs)?;
470 if facts.len() != programs.len() {
471 return Err(bad(FACTS.name, format!("facts for {} programs of {}", facts.len(), programs.len())));
472 }
473 for program in programs {
474 crate::lir::program_valid(program).map_err(|reason| bad("LIR", reason))?;
475 }
476 check_mapsets(mapsets).map_err(|reason| bad(Section::BMS.name, reason))?;
477 check_files(files, programs).map_err(|reason| bad(Section::DEBUG.name, reason))?;
478 Ok(encode_module(programs, directory, mapsets, files, facts))
479}
480
481fn check_files(files: &[Vec<Option<SourceFile>>], programs: &[Program]) -> Result<(), String> {
483 if files.len() != programs.len() {
484 return Err(format!("files for {} programs of {}", files.len(), programs.len()));
485 }
486 for (ordinal, (files, program)) in files.iter().zip(programs).enumerate() {
487 if files.len() != program.debug.sources.len() {
488 return Err(format!("program {ordinal} records {} files for {} sources", files.len(), program.debug.sources.len()));
489 }
490 files.iter().flatten().try_for_each(source_file_valid)?;
491 }
492 Ok(())
493}
494
495fn check_mapsets(mapsets: &[Mapset]) -> Result<(), String> {
497 match mapsets.windows(2).find(|pair| pair[0].name >= pair[1].name) {
498 Some(pair) => Err(format!("mapset {} follows mapset {}", pair[1].name, pair[0].name)),
499 None => Ok(()),
500 }
501}
502
503fn bad(section: &'static str, reason: impl Into<String>) -> ModuleError {
504 ModuleError::Malformed { section, offset: 0, reason: reason.into() }
505}
506
507fn check_directory(directory: &[DirectoryEntry], programs: &[Program]) -> Result<(), ModuleError> {
508 let name = Section::DIRECTORY.name;
509 if directory.len() != programs.len() {
510 return Err(bad(name, format!("{} entries for {} programs", directory.len(), programs.len())));
511 }
512 for (ordinal, (entry, program)) in directory.iter().zip(programs).enumerate() {
513 let symbol = program.symbols.get(program.id as usize);
514 if symbol != Some(&entry.id) {
515 return Err(bad(name, format!("program {ordinal} is {symbol:?} in its symbols, {:?} in the directory", entry.id)));
516 }
517 if entry.parent.is_some_and(|p| p as usize >= ordinal) {
518 return Err(bad(name, format!("program {ordinal} has parent {}, which does not precede it", entry.parent.unwrap_or(0))));
519 }
520 if let Some((entry_name, _)) = entry.entries.iter().find(|(_, para)| *para as usize >= program.paragraphs.len()) {
521 return Err(bad(name, format!("program {ordinal} ENTRY {entry_name} names a paragraph it lacks")));
522 }
523 }
524 Ok(())
525}
526
527fn records<T: Decode>(module: &Module<'_>, strings: &StringTable, section: Section, expected: usize) -> Result<Vec<T>, ModuleError> {
529 let mut r = module.reader(section, strings)?;
530 let at = r.position();
531 let count = r.count()?;
532 if count != expected {
533 return Err(r.malformed(at, format!("{count} records for {expected} programs")));
534 }
535 let mut out = Vec::with_capacity(count);
536 for _ in 0..count {
537 out.push(T::decode(&mut r)?);
538 }
539 r.finish()?;
540 Ok(out)
541}
542
543pub fn read(bytes: &[u8]) -> Result<LoadedModule, ModuleError> {
545 let module = Module::read(bytes)?;
546 let strings = module.strings()?;
547 let mut r = module.reader(Section::DIRECTORY, &strings)?;
548 let directory = Vec::<DirectoryEntry>::decode(&mut r)?;
549 r.finish()?;
550 let count = directory.len();
551
552 let options = option_records(&module, &strings, count)?;
553 let layouts = records::<LayoutRecord>(&module, &strings, Section::LAYOUT, count)?;
554 let bodies = lir_records(&module, &strings, count)?;
555 let sql = records::<Vec<SqlEntry>>(&module, &strings, Section::SQL, count)?;
556 let (debug, files): (Vec<Debug>, Vec<Vec<Option<SourceFile>>>) = records::<(Debug, Vec<Option<SourceFile>>)>(&module, &strings, Section::DEBUG, count)?.into_iter().unzip();
557
558 let mut r = module.reader(Section::BMS, &strings)?;
559 let mapsets = Vec::<Mapset>::decode(&mut r)?;
560 r.finish()?;
561 check_mapsets(&mapsets).map_err(|reason| bad(Section::BMS.name, reason))?;
562
563 let parts = options.into_iter().zip(layouts).zip(bodies).zip(sql).zip(debug);
564 let mut programs = Vec::with_capacity(count);
565 for ((((options, (storage, items, edits)), body), sql), debug) in parts {
566 let LirRecord {
567 id, initial, recursive, paragraphs, procedure_start, ranges, blocks, places, exprs, conds, consts, plans,
568 services, abends, symbols,
569 } = body;
570 let program = Program {
571 id, options, initial, recursive, storage, items, paragraphs, procedure_start, ranges, blocks, places, exprs,
572 conds, consts, plans, services, sql, abends, edits, symbols, debug,
573 };
574 crate::lir::program_valid(&program).map_err(|reason| bad(Section::SQL.name, reason))?;
575 programs.push(program);
576 }
577 check_directory(&directory, &programs)?;
578 check_files(&files, &programs).map_err(|reason| bad(Section::DEBUG.name, reason))?;
579 let facts = match module.sections().iter().any(|e| e.id == FACTS.id) {
580 true => records::<FactsRecord>(&module, &strings, FACTS, count)?.into_iter().map(record_facts).collect(),
581 false => vec![None; count],
582 };
583 Ok(LoadedModule { directory, programs, mapsets, files, facts })
584}