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