1use crate::picture::{self, Category, Sym};
6use syntax::ast::{DataEntry, Literal, SignClause, Usage};
7use syntax::{Error, Pos};
8use zarch::hfp::Precision;
9
10#[derive(Clone, Copy, Debug, PartialEq, Eq)]
11pub enum Kind {
12 Group,
13 Alnum { justified: bool },
14 National,
15 Zoned { digits: u32, scale: u32, signed: bool, sign: Option<SignClause> },
16 Packed { digits: u32, scale: u32, signed: bool },
17 Binary { digits: u32, scale: u32, signed: bool, native: bool },
18 Float(Precision),
19 NumericEdited { edit: u32, digits: u32, scale: u32, blank_when_zero: bool },
21 AlnumEdited { edit: u32 },
22 Pointer,
24 Index,
26}
27
28impl Kind {
29 pub fn is_numeric(self) -> bool {
30 matches!(self, Kind::Zoned { .. } | Kind::Packed { .. } | Kind::Binary { .. } | Kind::Float(_) | Kind::Index)
31 }
32
33 pub fn digits_scale(self) -> Option<(u32, u32)> {
35 match self {
36 Kind::Zoned { digits, scale, .. }
37 | Kind::Packed { digits, scale, .. }
38 | Kind::Binary { digits, scale, .. }
39 | Kind::NumericEdited { digits, scale, .. } => Some((digits, scale)),
40 Kind::Index => Some((9, 0)),
41 _ => None,
42 }
43 }
44}
45
46#[derive(Clone, Debug)]
47pub struct Item {
48 pub name: Option<String>,
49 pub level: u8,
50 pub parent: Option<usize>,
51 pub children: Vec<usize>,
52 pub offset: u32,
53 pub size: u32,
55 pub occurs: u32,
56 pub table: bool,
58 pub depending_on: Option<syntax::ast::Ref>,
60 pub odo: Option<usize>,
62 pub index_names: Vec<String>,
64 pub keys: Vec<(bool, syntax::ast::Ref)>,
65 pub local: bool,
67 pub kind: Kind,
68 pub value: Option<Literal>,
69 pub dims: Vec<(u32, u32)>,
71 pub redefines: Option<String>,
72 pub file: Option<u16>,
74 pub linkage: Option<u16>,
77 pub pos: Pos,
78}
79
80#[derive(Clone, Debug)]
81pub struct Condition {
82 pub name: String,
83 pub item: usize,
84 pub values: Vec<(Literal, Option<Literal>)>,
85}
86
87#[derive(Clone, Copy, Debug, PartialEq, Eq)]
88pub enum Resolved {
89 Item(usize),
90 Condition(usize),
91}
92
93pub struct Layout {
94 pub items: Vec<Item>,
95 pub conditions: Vec<Condition>,
96 pub edits: Vec<Vec<Sym>>,
98 pub file_areas: Vec<(u32, u32)>,
100 pub linkage_roots: Vec<usize>,
102 pub local_size: u32,
104 pub size: u32,
105}
106
107const LEVEL_ALIGNMENT: u32 = 8;
108pub const MAX_STORAGE: u32 = 128 << 20;
110
111pub fn build(entries: &[DataEntry], files: &[(&[DataEntry], Option<u32>)], linkage: &[DataEntry], local: &[DataEntry]) -> Result<Layout, Error> {
114 let mut items: Vec<Item> = Vec::new();
115 let mut usages: Vec<Option<Usage>> = Vec::new();
116 let mut conditions = Vec::new();
117 let mut open: Vec<usize> = Vec::new();
118 const LINKAGE: u16 = u16::MAX;
119 const LOCAL: u16 = u16::MAX - 1;
120 let tagged: Vec<(Option<u16>, &DataEntry)> = entries
121 .iter()
122 .map(|e| (None, e))
123 .chain(files.iter().enumerate().flat_map(|(k, (records, _))| records.iter().map(move |e| (Some(k as u16), e))))
124 .chain(linkage.iter().map(|e| (Some(LINKAGE), e)))
125 .chain(local.iter().map(|e| (Some(LOCAL), e)))
126 .collect();
127 let mut group = None;
128 let mut linkage_roots = Vec::new();
129 for &(region, e) in &tagged {
130 if region != group {
131 open.clear();
132 group = region;
133 }
134 let file = region.filter(|&r| r != LINKAGE && r != LOCAL);
135 let in_linkage = region == Some(LINKAGE);
136
137
138 if e.level == 88 {
139 let item = *open.last().ok_or_else(|| Error::at(e.pos, "a level-88 entry with no item before it"))?;
140 let name = e.name.clone().ok_or_else(|| Error::at(e.pos, "a level-88 entry needs a name"))?;
141 conditions.push(Condition { name, item, values: e.condition_values.clone() });
142 continue;
143 }
144 if e.level == 66 {
145 return Err(Error::at(e.pos, "RENAMES (level 66) is not supported yet"));
146 }
147 if !(e.level == 1 || e.level == 77 || (2..=49).contains(&e.level)) {
148 return Err(Error::at(e.pos, format!("level {} is not a data level", e.level)));
149 }
150 if e.sync {
151 return Err(Error::at(e.pos, "SYNCHRONIZED is not supported yet"));
152 }
153 while open.last().is_some_and(|&i| items[i].level >= e.level || items[i].level == 77) {
154 open.pop();
155 }
156 let parent = if e.level == 1 || e.level == 77 { None } else { open.last().copied() };
157 if e.level != 1 && e.level != 77 && parent.is_none() {
158 return Err(Error::at(e.pos, format!("level {} with no group to belong to", e.level)));
159 }
160 let index = items.len();
161 items.push(Item {
162 name: e.name.clone(),
163 level: e.level,
164 parent,
165 children: Vec::new(),
166 offset: 0,
167 size: 0,
168 occurs: e.occurs.unwrap_or(1),
169 table: e.occurs.is_some(),
170 depending_on: e.depending_on.clone(),
171 odo: None,
172 index_names: e.indexed_by.clone(),
173 keys: e.keys.clone(),
174 local: region == Some(LOCAL),
175 kind: Kind::Group,
176 value: e.value.clone().filter(|_| file.is_none() && !in_linkage),
177 dims: Vec::new(),
178 redefines: e.redefines.clone(),
179 file,
180 linkage: if in_linkage {
181 Some(match parent {
182 None => {
183 linkage_roots.push(index);
184 linkage_roots.len() as u16 - 1
185 }
186 Some(p) => items[p].linkage.unwrap_or_default(),
187 })
188 } else {
189 None
190 },
191 pos: e.pos,
192 });
193 if (e.level == 1 || e.level == 77) && e.occurs.is_some() {
194 return Err(Error::at(e.pos, "OCCURS is not allowed at level 01 or 77"));
195 }
196 if e.occurs == Some(0) {
197 return Err(Error::at(e.pos, "OCCURS 0 is not a table"));
198 }
199 let inherited = parent.and_then(|p| usages[p]);
200 usages.push(e.usage.or(inherited));
201 if let Some(p) = parent {
202 items[p].children.push(index);
203 }
204 open.push(index);
205 }
206 let mut edits = Vec::new();
207 for (index, (_, e)) in tagged.iter().filter(|(_, e)| e.level != 88).enumerate() {
208 items[index].kind = kind(e, &items[index], usages[index], &mut edits)?;
209 items[index].size = elementary_size(&items[index], declared_size(e));
210 }
211 for (_, e) in &tagged {
212 for name in &e.indexed_by {
213 items.push(Item {
214 name: Some(name.clone()),
215 level: 77,
216 parent: None,
217 children: Vec::new(),
218 offset: 0,
219 size: 4,
220 occurs: 1,
221 table: false,
222 depending_on: None,
223 odo: None,
224 index_names: Vec::new(),
225 keys: Vec::new(),
226 local: false,
227 kind: Kind::Index,
228 value: None,
229 dims: Vec::new(),
230 redefines: None,
231 file: None,
232 linkage: None,
233 pos: e.pos,
234 });
235 }
236 }
237 let roots: Vec<usize> = (0..items.len()).filter(|&i| items[i].parent.is_none()).collect();
238 for &r in &roots {
239 measure(&mut items, r)?;
240 }
241 let mut local_cursor = 0u32;
242 let local_roots: Vec<usize> = roots.iter().copied().filter(|&r| items[r].local).collect();
243 for r in local_roots {
244 let start = local_cursor.div_ceil(LEVEL_ALIGNMENT) * LEVEL_ALIGNMENT;
245 local_cursor = start + items[r].size;
246 if local_cursor > MAX_STORAGE {
247 return Err(Error::at(items[r].pos, format!("LOCAL-STORAGE exceeds the interpreter's {MAX_STORAGE} bytes")));
248 }
249 place(&mut items, r, start, Vec::new());
250 }
251 let mut cursor = 0u32;
252 let mut root_offsets: Vec<(String, u32)> = Vec::new();
253 let mut file_areas: Vec<Option<(u32, u32)>> = vec![None; files.len()];
254 for &r in &roots {
255 if items[r].local {
256 continue;
257 }
258 if items[r].linkage.is_some() {
259 place(&mut items, r, 0, Vec::new());
260 continue;
261 }
262 if let Some(k) = items[r].file {
263 let k = k as usize;
264 let (start, size) = *file_areas[k].get_or_insert((cursor.div_ceil(LEVEL_ALIGNMENT) * LEVEL_ALIGNMENT, files[k].1.unwrap_or(0)));
265 let size = size.max(items[r].size);
266 file_areas[k] = Some((start, size));
267 cursor = cursor.max(start + size);
268 if cursor > MAX_STORAGE {
269 return Err(Error::at(items[r].pos, format!("storage exceeds the interpreter's {MAX_STORAGE} bytes")));
270 }
271 place(&mut items, r, start, Vec::new());
272 continue;
273 }
274 let offset = match &items[r].redefines {
275 Some(target) => root_offsets.iter().find(|(n, _)| n == target).map(|&(_, o)| o).ok_or_else(|| {
276 Error::at(items[r].pos, format!("REDEFINES {target}: no earlier 01-level item of that name"))
277 })?,
278 None => cursor.div_ceil(LEVEL_ALIGNMENT) * LEVEL_ALIGNMENT,
279 };
280 cursor = cursor.max(offset + items[r].size);
281 if cursor > MAX_STORAGE {
282 return Err(Error::at(items[r].pos, format!("WORKING-STORAGE exceeds the interpreter's {MAX_STORAGE} bytes")));
283 }
284 if let Some(name) = &items[r].name {
285 root_offsets.push((name.clone(), offset));
286 }
287 place(&mut items, r, offset, Vec::new());
288 }
289 let tables: Vec<usize> = (0..items.len()).filter(|&i| items[i].depending_on.is_some()).collect();
290 for t in tables {
291 let (mut child, mut at) = (t, items[t].parent);
292 while let Some(a) = at {
293 let last = items[a].children.iter().rev().find(|&&c| items[c].redefines.is_none()).copied();
294 if last != Some(child) {
295 return Err(Error::at(items[t].pos, "items after an OCCURS DEPENDING ON table in the same record are not supported yet"));
296 }
297 items[a].odo.get_or_insert(t);
298 (child, at) = (a, items[a].parent);
299 }
300 }
301 let mut areas = Vec::new();
302 for (k, area) in file_areas.into_iter().enumerate() {
303 let area = area.unwrap_or_else(|| {
304 let start = cursor.div_ceil(LEVEL_ALIGNMENT) * LEVEL_ALIGNMENT;
305 (start, files[k].1.unwrap_or(0))
306 });
307 cursor = cursor.max(area.0 + area.1);
308 areas.push(area);
309 }
310 Ok(Layout { items, conditions, edits, file_areas: areas, linkage_roots, local_size: local_cursor, size: cursor })
311}
312
313fn kind(e: &DataEntry, item: &Item, usage: Option<Usage>, edits: &mut Vec<Vec<Sym>>) -> Result<Kind, Error> {
314 let err = |m: String| Error::at(e.pos, m);
315 let usage = usage.unwrap_or_default();
316 let elementary = e.picture.is_some() || matches!(usage, Usage::Float1 | Usage::Float2 | Usage::Pointer | Usage::Index) && item.children.is_empty();
317 if !elementary {
318 return if item.children.is_empty() { Err(err("an elementary item needs a PICTURE".into())) } else { Ok(Kind::Group) };
319 }
320 if !item.children.is_empty() {
321 return Err(err("a group item cannot have a PICTURE".into()));
322 }
323 if let Usage::Pointer | Usage::Index = usage {
324 if e.picture.is_some() {
325 return Err(err("POINTER and INDEX items take no PICTURE".into()));
326 }
327 return Ok(if usage == Usage::Pointer { Kind::Pointer } else { Kind::Index });
328 }
329 if let Usage::Float1 | Usage::Float2 = usage {
330 if e.picture.is_some() {
331 return Err(err("COMP-1 and COMP-2 items take no PICTURE".into()));
332 }
333 return Ok(Kind::Float(if usage == Usage::Float1 { Precision::Short } else { Precision::Long }));
334 }
335 let pic = picture::analyse(e.picture.as_deref().unwrap()).map_err(err)?;
336 if e.blank_when_zero && pic.category != Category::NumericEdited {
337 return Err(err("BLANK WHEN ZERO is supported on numeric-edited items only, so far".into()));
338 }
339 let k = match (pic.category, usage) {
340 (Category::NumericEdited, Usage::Display) => {
341 edits.push(pic.edit.clone().unwrap_or_default());
342 Kind::NumericEdited { edit: edits.len() as u32 - 1, digits: pic.digits, scale: pic.scale, blank_when_zero: e.blank_when_zero }
343 }
344 (Category::AlphanumericEdited, Usage::Display) => {
345 edits.push(pic.edit.clone().unwrap_or_default());
346 Kind::AlnumEdited { edit: edits.len() as u32 - 1 }
347 }
348 (Category::Numeric, Usage::Display) => Kind::Zoned { digits: pic.digits, scale: pic.scale, signed: pic.signed, sign: e.sign },
349 (Category::Numeric, Usage::Packed) => Kind::Packed { digits: pic.digits, scale: pic.scale, signed: pic.signed },
350 (Category::Numeric, Usage::Binary | Usage::NativeBinary) if pic.digits <= 18 => {
351 Kind::Binary { digits: pic.digits, scale: pic.scale, signed: pic.signed, native: usage == Usage::NativeBinary }
352 }
353 (Category::Numeric, Usage::Binary | Usage::NativeBinary) => return Err(err("a binary item holds at most 18 digits".into())),
354 (Category::Alphanumeric, Usage::Display) => Kind::Alnum { justified: e.justified },
355 (Category::National, Usage::Display | Usage::National) => Kind::National,
356 (category, usage) => return Err(err(format!("a {category:?} PICTURE with USAGE {usage:?} is not supported yet"))),
357 };
358 if let Kind::Zoned { sign: Some(_), signed: false, .. } = k {
359 return Err(err("a SIGN clause needs an S in the PICTURE".into()));
360 }
361 Ok(k)
362}
363
364fn elementary_size(item: &Item, e_size: Option<u32>) -> u32 {
365 match item.kind {
366 Kind::Group => 0,
367 Kind::Alnum { .. } | Kind::NumericEdited { .. } | Kind::AlnumEdited { .. } => e_size.unwrap_or(0),
368 Kind::National => 2 * e_size.unwrap_or(0),
369 Kind::Zoned { digits, sign, .. } => digits + sign.is_some_and(|s| s.separate) as u32,
370 Kind::Packed { digits, .. } => digits / 2 + 1,
371 Kind::Binary { digits, .. } => match digits {
372 0..=4 => 2,
373 5..=9 => 4,
374 _ => 8,
375 },
376 Kind::Float(p) => p.bytes() as u32,
377 Kind::Pointer | Kind::Index => 4,
378 }
379}
380
381fn measure(items: &mut [Item], index: usize) -> Result<(), Error> {
383 if items[index].kind != Kind::Group {
384 return Ok(());
385 }
386 let children = items[index].children.clone();
387 let (mut cursor, mut extent) = (0u32, 0u32);
388 let mut placed: Vec<(Option<String>, u32)> = Vec::new();
389 for c in children {
390 measure(items, c)?;
391 let offset = match items[c].redefines.clone() {
392 Some(target) => placed.iter().rev().find(|(n, _)| n.as_deref() == Some(target.as_str())).map(|&(_, o)| o).ok_or_else(|| {
393 Error::at(items[c].pos, format!("REDEFINES {target}: no earlier item of that name at this level"))
394 })?,
395 None => cursor,
396 };
397 let too_large = || Error::at(items[c].pos, format!("an item larger than the interpreter's {MAX_STORAGE} bytes"));
398 let span = items[c].size.checked_mul(items[c].occurs).filter(|&s| s <= MAX_STORAGE).ok_or_else(too_large)?;
399 if items[c].redefines.is_none() {
400 cursor = cursor.checked_add(span).filter(|&s| s <= MAX_STORAGE).ok_or_else(too_large)?;
401 }
402 extent = extent.max(offset + span);
403 items[c].offset = offset;
404 placed.push((items[c].name.clone(), offset));
405 }
406 items[index].size = cursor.max(extent);
407 Ok(())
408}
409
410fn place(items: &mut [Item], index: usize, offset: u32, mut dims: Vec<(u32, u32)>) {
411 items[index].offset = offset;
412 if items[index].table {
413 dims.push((items[index].size, items[index].occurs));
414 }
415 items[index].dims = dims.clone();
416 for c in items[index].children.clone() {
417 let relative = items[c].offset;
418 place(items, c, offset + relative, dims.clone());
419 }
420}
421
422impl Layout {
423 pub fn resolve(&self, name: &str, qualifiers: &[String], pos: Pos) -> Result<Resolved, Error> {
424 let within = |mut at: Option<usize>| {
425 let mut wanted = qualifiers.iter();
426 let mut next = wanted.next();
427 while let (Some(q), Some(i)) = (next, at) {
428 if self.items[i].name.as_deref() == Some(q.as_str()) {
429 next = wanted.next();
430 }
431 at = self.items[i].parent;
432 }
433 next.is_none()
434 };
435 let mut found: Vec<Resolved> = self
436 .items
437 .iter()
438 .enumerate()
439 .filter(|(_, it)| it.name.as_deref() == Some(name) && within(it.parent))
440 .map(|(i, _)| Resolved::Item(i))
441 .collect();
442 found.extend(
443 self.conditions.iter().enumerate().filter(|(_, c)| c.name == name && within(Some(c.item))).map(|(i, _)| Resolved::Condition(i)),
444 );
445 match found.as_slice() {
446 [one] => Ok(*one),
447 [] => Err(Error::at(pos, format!("{name} is not defined"))),
448 _ => Err(Error::at(pos, format!("{name} is ambiguous; qualify it with OF or IN"))),
449 }
450 }
451}
452
453fn declared_size(e: &DataEntry) -> Option<u32> {
454 e.picture.as_deref().and_then(|p| picture::analyse(p).ok()).map(|p| p.size)
455}