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