1use crate::picture::{self, Category, Sym};
7use numeric::Qualify;
8use syntax::ast::{DataEntry, Environment, FileDecl, Literal, Organization, Ref, Usage};
9use syntax::{Error, Pos};
10use zarch::hfp::Precision;
11
12pub use rt::storage::Kind;
13
14#[derive(Clone, Debug)]
15pub struct Item {
16 pub name: Option<String>,
17 pub level: u8,
18 pub parent: Option<usize>,
19 pub children: Vec<usize>,
20 pub offset: u32,
21 pub size: u32,
23 pub occurs: u32,
24 pub occurs_min: u32,
26 pub table: bool,
28 pub depending_on: Option<syntax::ast::Ref>,
30 pub odo: Vec<usize>,
33 pub moved_by: Vec<usize>,
37 pub followed: bool,
39 pub index_names: Vec<String>,
41 pub keys: Vec<(bool, syntax::ast::Ref)>,
42 pub local: bool,
44 pub kind: Kind,
45 pub value: Option<Literal>,
46 pub dims: Vec<(u32, u32)>,
48 pub redefines: Option<String>,
49 pub file: Option<u16>,
51 pub linkage: Option<u16>,
54 pub object_class: Option<String>,
56 pub scaling: u32,
59 pub alphabetic: bool,
61 pub pos: Pos,
62}
63
64#[derive(Clone, Debug)]
65pub struct Condition {
66 pub name: String,
67 pub item: usize,
68 pub values: Vec<(Literal, Option<Literal>)>,
69 pub false_value: Option<Literal>,
71}
72
73#[derive(Clone, Copy, Debug, PartialEq, Eq)]
74pub enum Resolved {
75 Item(usize),
76 Condition(usize),
77}
78
79#[derive(Clone, Debug, PartialEq, Eq)]
82pub enum Binding {
83 Argument,
85 External { name: String, size: u32 },
88 ExternalFile(u16),
90 Global { program: String, record: String, section: Section },
92}
93
94#[derive(Clone, Debug, PartialEq, Eq)]
96pub enum Section {
97 WorkingStorage,
98 LocalStorage,
99 Linkage,
100 File(String),
102}
103
104pub struct Layout {
105 pub items: Vec<Item>,
106 pub conditions: Vec<Condition>,
107 pub edits: Vec<Vec<Sym>>,
109 pub currencies: Vec<String>,
111 pub file_areas: Vec<(u32, u32)>,
113 pub record_lengths: Vec<Option<(u32, u32)>>,
117 pub linkage_roots: Vec<usize>,
120 pub bindings: Vec<Binding>,
121 pub depths: Vec<u8>,
123 pub bound_areas: Vec<Option<u16>>,
126 pub local_size: u32,
128 pub size: u32,
129 pub file_names: Vec<String>,
131 pub linage_counters: Vec<Option<usize>>,
133 pub qualify: Qualify,
135 pub parmcheck: Option<(u32, u32)>,
138 pub numcheck: crate::numcheck::NumcheckFacts,
139}
140
141const LEVEL_ALIGNMENT: u32 = 8;
142pub const MAX_STORAGE: u32 = 128 << 20;
144
145#[allow(clippy::too_many_arguments)]
153pub fn build(
154 entries: &[DataEntry],
155 files: &[(&[DataEntry], Option<u32>)],
156 shared: &[usize],
157 linkage: &[DataEntry],
158 local: &[DataEntry],
159 notation: picture::Notation,
160 qualify: Qualify,
161 parmcheck: Option<(usize, u32)>,
162) -> Result<Layout, Error> {
163 let mut items: Vec<Item> = Vec::new();
164 let mut usages: Vec<Option<Usage>> = Vec::new();
165 let mut synchronized: Vec<bool> = Vec::new();
166 let mut conditions = Vec::new();
167 let mut open: Vec<usize> = Vec::new();
168 let mut renames: Vec<(usize, &DataEntry)> = Vec::new();
169 const LINKAGE: u16 = u16::MAX;
170 const LOCAL: u16 = u16::MAX - 1;
171 let tagged: Vec<(Option<u16>, &DataEntry)> = entries
172 .iter()
173 .map(|e| (None, e))
174 .chain(files.iter().enumerate().flat_map(|(k, (records, _))| records.iter().map(move |e| (Some(k as u16), e))))
175 .chain(linkage.iter().map(|e| (Some(LINKAGE), e)))
176 .chain(local.iter().map(|e| (Some(LOCAL), e)))
177 .collect();
178 let mut group = None;
179 let mut linkage_roots = Vec::new();
180 let mut after_renames = false;
181 let bound = bound_records(&tagged);
182 let mut bound_roots = Vec::new();
183 for (&(region, e), &bound) in tagged.iter().zip(&bound) {
184 if region != group {
185 open.clear();
186 group = region;
187 after_renames = false;
188 }
189 let file = region.filter(|&r| r != LINKAGE && r != LOCAL);
190 let in_linkage = region == Some(LINKAGE);
191 if e.occurs.is_some() && matches!(e.level, 1 | 66 | 77 | 88) {
192 return Err(Error::at(e.pos, format!("OCCURS at level {:02}: Enterprise COBOL takes OCCURS only at levels 02 to 49", e.level)));
193 }
194 if e.level == 88 {
195 if after_renames {
196 return Err(Error::at(e.pos, "a level-88 entry after a level-66 entry: a RENAMES item cannot be a conditional variable"));
197 }
198 let item = *open.last().ok_or_else(|| Error::at(e.pos, "a level-88 entry with no item before it"))?;
199 let name = e.name.clone().ok_or_else(|| Error::at(e.pos, "a level-88 entry needs a name"))?;
200 conditions.push(Condition { name, item, values: e.condition_values.clone(), false_value: e.false_value.clone() });
201 continue;
202 }
203 if e.renames.is_some() != (e.level == 66) {
204 return Err(Error::at(e.pos, "RENAMES goes with level 66, and level 66 with RENAMES"));
205 }
206 if e.level == 66 {
207 let record = open.first().copied().filter(|&r| items[r].level == 1).ok_or_else(|| Error::at(e.pos, "a level-66 entry must follow the entries of a level-01 record"))?;
208 if e.name.is_none() || e.picture.is_some() || e.usage.is_some() || e.value.is_some() || e.redefines.is_some() || e.sync || e.sign.is_some() {
209 return Err(Error::at(e.pos, "a level-66 entry has a name and a RENAMES clause, and nothing else"));
210 }
211 renames.push((items.len(), e));
212 items.push(Item {
213 name: e.name.clone(),
214 level: 66,
215 parent: Some(record),
216 children: Vec::new(),
217 offset: 0,
218 size: 0,
219 occurs: 1,
220 occurs_min: 1,
221 table: false,
222 depending_on: None,
223 odo: Vec::new(),
224 moved_by: Vec::new(),
225 followed: false,
226 index_names: Vec::new(),
227 keys: Vec::new(),
228 local: items[record].local,
229 kind: Kind::Group,
230 value: None,
231 dims: Vec::new(),
232 redefines: None,
233 file,
234 linkage: items[record].linkage,
235 object_class: None,
236 scaling: 0,
237 alphabetic: false,
238 pos: e.pos,
239 });
240 usages.push(None);
241 synchronized.push(false);
242 after_renames = true;
243 continue;
244 }
245 if !(e.level == 1 || e.level == 77 || (2..=49).contains(&e.level)) {
246 return Err(Error::at(e.pos, format!("level {} is not a data level", e.level)));
247 }
248 if after_renames && e.level != 1 && e.level != 77 {
249 return Err(Error::at(e.pos, format!("level {:02} after a level-66 entry: a record's RENAMES entries follow its last entry", e.level)));
250 }
251 after_renames = false;
252 while open.last().is_some_and(|&i| items[i].level >= e.level || items[i].level == 77) {
253 open.pop();
254 }
255 let parent = if e.level == 1 || e.level == 77 { None } else { open.last().copied() };
256 if e.level != 1 && e.level != 77 && parent.is_none() {
257 return Err(Error::at(e.pos, format!("level {} with no group to belong to", e.level)));
258 }
259 let index = items.len();
260 items.push(Item {
261 name: e.name.clone(),
262 level: e.level,
263 parent,
264 children: Vec::new(),
265 offset: 0,
266 size: 0,
267 occurs: e.occurs.unwrap_or(1),
268 occurs_min: e.occurs_min.or(e.occurs).unwrap_or(1),
269 table: e.occurs.is_some(),
270 depending_on: e.depending_on.clone(),
271 odo: Vec::new(),
272 moved_by: Vec::new(),
273 followed: false,
274 index_names: e.indexed_by.clone(),
275 keys: e.keys.clone(),
276 local: region == Some(LOCAL),
277 kind: Kind::Group,
278 value: e.value.clone().filter(|_| file.is_none() && !in_linkage),
279 dims: Vec::new(),
280 redefines: e.redefines.clone(),
281 file,
282 linkage: if in_linkage {
283 Some(match parent {
284 None => {
285 linkage_roots.push(index);
286 linkage_roots.len() as u16 - 1
287 }
288 Some(p) => items[p].linkage.unwrap_or_default(),
289 })
290 } else if bound {
291 if parent.is_none() {
292 bound_roots.push(index);
293 }
294 Some(0)
295 } else {
296 None
297 },
298 object_class: e.object_class.clone(),
299 scaling: 0,
300 alphabetic: false,
301 pos: e.pos,
302 });
303 if e.occurs == Some(0) {
304 return Err(Error::at(e.pos, "OCCURS 0 is not a table"));
305 }
306 let inherited = parent.and_then(|p| usages[p]);
307 usages.push(e.usage.or(inherited));
308 synchronized.push(e.sync || parent.is_some_and(|p| synchronized[p]));
309 if let Some(p) = parent {
310 items[p].children.push(index);
311 }
312 open.push(index);
313 }
314 let (mut edits, mut currencies) = (Vec::new(), Vec::new());
315 let mut aligns = vec![1u32; items.len()];
316 for (index, (_, e)) in tagged.iter().filter(|(_, e)| e.level != 88).enumerate() {
317 if e.level == 66 {
318 continue;
319 }
320 let pic = e.picture.as_deref().map(|p| picture::analyse_with(p, notation).map_err(|m| Error::at(e.pos, m))).transpose()?;
321 items[index].kind = kind(e, &items[index], usages[index], pic.as_ref(), &mut edits)?;
322 if currencies.len() < edits.len() {
323 currencies.push(pic.as_ref().and_then(|p| p.currency.clone()).unwrap_or_default());
324 }
325 items[index].scaling = pic.as_ref().map_or(0, |p| p.scaling);
326 items[index].alphabetic = matches!(items[index].kind, Kind::Alnum { .. }) && e.picture.as_deref().is_some_and(crate::corresponding::is_alphabetic);
327 items[index].size = elementary_size(&items[index], pic.as_ref().map(|p| p.size));
328 if synchronized[index] && items[index].kind != Kind::Group {
329 aligns[index] = alignment(items[index].kind);
330 }
331 }
332 for (_, e) in &tagged {
333 for name in &e.indexed_by {
334 items.push(Item {
335 name: Some(name.clone()),
336 level: 77,
337 parent: None,
338 children: Vec::new(),
339 offset: 0,
340 size: 4,
341 occurs: 1,
342 occurs_min: 1,
343 table: false,
344 depending_on: None,
345 odo: Vec::new(),
346 moved_by: Vec::new(),
347 followed: false,
348 index_names: Vec::new(),
349 keys: Vec::new(),
350 local: false,
351 kind: Kind::Index,
352 value: None,
353 dims: Vec::new(),
354 redefines: None,
355 file: None,
356 linkage: None,
357 object_class: None,
358 scaling: 0,
359 alphabetic: false,
360 pos: e.pos,
361 });
362 }
363 }
364 aligns.resize(items.len(), 1);
365 let arguments = linkage_roots.len();
366 for i in 0..items.len() {
367 let mut r = i;
368 while let Some(p) = items[r].parent {
369 r = p;
370 }
371 if let Some(b) = bound_roots.iter().position(|&x| x == r) {
372 items[i].linkage = Some((arguments + b) as u16);
373 }
374 }
375 linkage_roots.extend(&bound_roots);
376 let roots: Vec<usize> = (0..items.len()).filter(|&i| items[i].parent.is_none()).collect();
377 for &r in &roots {
378 measure(&mut items, &aligns, r, 0)?;
379 }
380 let mut local_cursor = 0u32;
381 let local_roots: Vec<usize> = roots.iter().copied().filter(|&r| items[r].local).collect();
382 for r in local_roots {
383 let start = local_cursor.div_ceil(LEVEL_ALIGNMENT) * LEVEL_ALIGNMENT;
384 local_cursor = start + items[r].size;
385 if local_cursor > MAX_STORAGE {
386 return Err(Error::at(items[r].pos, format!("LOCAL-STORAGE exceeds the interpreter's {MAX_STORAGE} bytes")));
387 }
388 place(&mut items, r, start, Vec::new());
389 }
390 let owner = |k: usize| shared.get(k).copied().filter(|&g| g < files.len()).unwrap_or(k);
391 let mut own: Vec<u32> = files.iter().map(|f| f.1.unwrap_or(0)).collect();
392 for &r in &roots {
393 if let Some(k) = items[r].file {
394 own[k as usize] = own[k as usize].max(items[r].size);
395 }
396 }
397 let mut area_size = vec![0u32; files.len()];
398 for (k, &size) in own.iter().enumerate() {
399 area_size[owner(k)] = area_size[owner(k)].max(size);
400 }
401 let mut cursor = 0u32;
402 let mut root_offsets: Vec<(String, u32)> = Vec::new();
403 let mut area_starts: Vec<Option<u32>> = vec![None; files.len()];
404 let mut pending = parmcheck.map(|(declared, bytes)| (entries[..declared.min(entries.len())].iter().filter(|e| e.level != 88).count(), bytes));
405 let mut buffer = None;
406 for &r in &roots {
407 if items[r].local {
408 continue;
409 }
410 if items[r].linkage.is_some() {
411 place(&mut items, r, 0, Vec::new());
412 continue;
413 }
414 if let Some((own, bytes)) = pending
415 && r >= own
416 {
417 buffer = Some((cursor, bytes));
418 cursor += bytes;
419 pending = None;
420 }
421 if let Some(k) = items[r].file {
422 let g = owner(k as usize);
423 let start = *area_starts[g].get_or_insert(cursor.div_ceil(LEVEL_ALIGNMENT) * LEVEL_ALIGNMENT);
424 cursor = cursor.max(start + area_size[g]);
425 if cursor > MAX_STORAGE {
426 return Err(Error::at(items[r].pos, format!("storage exceeds the interpreter's {MAX_STORAGE} bytes")));
427 }
428 place(&mut items, r, start, Vec::new());
429 continue;
430 }
431 let offset = match &items[r].redefines {
432 Some(target) => root_offsets.iter().find(|(n, _)| n == target).map(|&(_, o)| o).ok_or_else(|| {
433 Error::at(items[r].pos, format!("REDEFINES {target}: no earlier 01-level item of that name"))
434 })?,
435 None => cursor.div_ceil(LEVEL_ALIGNMENT) * LEVEL_ALIGNMENT,
436 };
437 cursor = cursor.max(offset + items[r].size);
438 if cursor > MAX_STORAGE {
439 return Err(Error::at(items[r].pos, format!("WORKING-STORAGE exceeds the interpreter's {MAX_STORAGE} bytes")));
440 }
441 if let Some(name) = &items[r].name {
442 root_offsets.push((name.clone(), offset));
443 }
444 place(&mut items, r, offset, Vec::new());
445 }
446 if let Some((_, bytes)) = pending {
447 buffer = Some((cursor, bytes));
448 cursor += bytes;
449 }
450 let tables: Vec<usize> = (0..items.len()).filter(|&i| items[i].depending_on.is_some()).collect();
451 for &t in &tables {
452 let (mut child, mut at, mut nested) = (t, items[t].parent, false);
453 let (mut chain, mut followers) = (vec![t], Vec::new());
454 while let Some(a) = at {
455 nested |= child != t && items[child].depending_on.is_some();
456 if !nested {
457 items[a].odo.push(t);
458 }
459 let end = items[child].offset + items[child].size * items[child].occurs;
460 let after = items[a].children.iter().skip_while(|&&c| c != child).skip(1).copied().filter(|&c| items[c].offset >= end);
461 let before = followers.len();
462 followers.extend(after);
463 if followers.len() > before {
464 chain.iter().for_each(|&c| items[c].followed = true);
465 }
466 chain.push(a);
467 (child, at) = (a, items[a].parent);
468 }
469 let holds_another = tables.iter().any(|&u| u != t && ancestors(&items, u).any(|p| p == t));
470 if !followers.is_empty() && (items[t].dims.len() > 1 || holds_another) {
471 return Err(Error::at(items[t].pos, "items after an OCCURS DEPENDING ON table in the same record are not supported yet"));
472 }
473 while let Some(f) = followers.pop() {
474 items[f].moved_by.push(t);
475 followers.extend(items[f].children.iter().copied());
476 }
477 }
478 for (index, e) in renames {
479 rename(&mut items, index, e, qualify)?;
480 }
481 let mut bound_areas = vec![None; files.len()];
482 let mut bindings = vec![Binding::Argument; arguments];
483 for &r in &bound_roots {
484 let binding = match items[r].file {
485 Some(k) => {
486 bound_areas[k as usize].get_or_insert(items[r].linkage.unwrap_or_default());
487 Binding::ExternalFile(k)
488 }
489 None => {
490 let name = items[r].redefines.clone().or_else(|| items[r].name.clone()).unwrap_or_default();
491 let size = bound_roots.iter().find(|&&t| items[t].name.as_deref() == Some(name.as_str())).map_or(items[r].size, |&t| items[t].size);
492 Binding::External { name, size }
493 }
494 };
495 bindings.push(binding);
496 }
497 let mut areas = Vec::new();
498 for (k, &size) in own.iter().enumerate() {
499 if bound_areas[k].is_some() {
500 areas.push((0, area_size[owner(k)]));
501 continue;
502 }
503 let g = owner(k);
504 let start = match area_starts[g] {
505 Some(start) => start,
506 None => {
507 let start = cursor.div_ceil(LEVEL_ALIGNMENT) * LEVEL_ALIGNMENT;
508 area_starts[g] = Some(start);
509 cursor = cursor.max(start + area_size[g]);
510 start
511 }
512 };
513 areas.push((start, size));
514 }
515 let mut record_lengths: Vec<Option<(u32, u32)>> = vec![None; files.len()];
516 for &r in &roots {
517 let Some(k) = items[r].file else { continue };
518 let fewer: u32 = items[r]
519 .odo
520 .iter()
521 .map(|&t| {
522 let t = &items[t];
523 let outer: u32 = t.dims[..t.dims.len().saturating_sub(1)].iter().map(|&(_, n)| n).product();
524 t.occurs.saturating_sub(t.occurs_min) * t.size * outer
525 })
526 .sum();
527 let (least, most) = (items[r].size.saturating_sub(fewer), items[r].size);
528 let lengths = &mut record_lengths[k as usize];
529 *lengths = Some(lengths.map_or((least, most), |(l, m)| (l.min(least), m.max(most))));
530 }
531 Ok(Layout {
532 items,
533 conditions,
534 edits,
535 currencies,
536 file_areas: areas,
537 record_lengths,
538 linkage_roots,
539 depths: vec![0; bindings.len()],
540 bindings,
541 bound_areas,
542 local_size: local_cursor,
543 size: cursor,
544 file_names: Vec::new(),
545 linage_counters: Vec::new(),
546 qualify,
547 parmcheck: buffer,
548 numcheck: Default::default(),
549 })
550}
551
552
553fn names_from(items: &[Item], start: Option<usize>) -> impl Iterator<Item = &str> {
557 std::iter::successors(start, |&p| items[p].parent).filter_map(|p| items[p].name.as_deref())
558}
559
560fn bound_records(tagged: &[(Option<u16>, &DataEntry)]) -> Vec<bool> {
564 let mut externals: Vec<&str> = Vec::new();
565 let mut current = false;
566 let mut out = Vec::with_capacity(tagged.len());
567 for &(region, e) in tagged {
568 if matches!(e.level, 1 | 77) {
569 current = match region {
570 None => e.external || e.redefines.as_deref().is_some_and(|t| externals.contains(&t)),
571 Some(r) => r < u16::MAX - 1 && e.external,
572 };
573 if current && region.is_none() && e.external {
574 externals.extend(e.name.as_deref());
575 }
576 }
577 out.push(current);
578 }
579 out
580}
581
582fn rename(items: &mut [Item], index: usize, e: &DataEntry, qualify: Qualify) -> Result<(), Error> {
586 let Some((first, last)) = &e.renames else { return Ok(()) };
587 let record = items[index].parent.unwrap_or(index);
588 let find = |r: &Ref| -> Result<usize, Error> {
589 let err = |m: String| Err(Error::at(r.pos, format!("RENAMES {}: {m}", r.name)));
590 if !r.subscripts.is_empty() || r.refmod.is_some() {
591 return err("a renamed item is named without subscripts or reference modification".into());
592 }
593 let in_record = |mut at: usize| {
594 let mut wanted = r.qualifiers.iter().peekable();
595 while let Some(p) = items[at].parent {
596 if wanted.peek().is_some_and(|q| items[p].name.as_deref() == Some(q.as_str())) {
597 wanted.next();
598 }
599 at = p;
600 }
601 at == record && wanted.next().is_none()
602 };
603 let mut found: Vec<usize> = (0..items.len()).filter(|&i| i != record && items[i].level != 66 && items[i].name.as_deref() == Some(r.name.as_str()) && in_record(i)).collect();
604 if found.len() > 1 && qualify == Qualify::Extend {
605 let complete: Vec<usize> = found.iter().copied().filter(|&i| names_from(items, items[i].parent).eq(r.qualifiers.iter().map(String::as_str))).collect();
606 if complete.len() == 1 {
607 found = complete;
608 }
609 }
610 let &[t] = found.as_slice() else {
611 let record_name = items[record].name.clone().unwrap_or_default();
612 return err(if found.is_empty() { format!("no item of that name below {record_name}, other than a level-66 entry") } else { "ambiguous; qualify it with OF or IN".into() });
613 };
614 if !items[t].dims.is_empty() {
615 return err("a renamed item must not have OCCURS, nor belong to a group that has it".into());
616 }
617 Ok(t)
618 };
619 if first.name == items[record].name.clone().unwrap_or_default() && first.qualifiers.is_empty() {
620 return Err(Error::at(first.pos, format!("RENAMES {}: a level-66 entry cannot rename a level-01 record", first.name)));
621 }
622 let a = find(first)?;
623 let Some(last) = last else {
624 let (offset, size, kind, scaling, odo, moved_by, followed) =
625 (items[a].offset, items[a].size, items[a].kind, items[a].scaling, items[a].odo.clone(), items[a].moved_by.clone(), items[a].followed);
626 let it = &mut items[index];
627 (it.offset, it.size, it.kind, it.scaling, it.odo, it.moved_by, it.followed) = (offset, size, kind, scaling, odo, moved_by, followed);
628 return Ok(());
629 };
630 let b = find(last)?;
631 let end = |i: usize| items[i].offset + items[i].size;
632 let mut up = items[b].parent;
633 while let Some(p) = up {
634 if p == a {
635 return Err(Error::at(last.pos, format!("RENAMES {} THRU {}: the last item cannot be within the first", first.name, last.name)));
636 }
637 up = items[p].parent;
638 }
639 if a == b || items[b].offset < items[a].offset || end(b) < end(a) {
640 return Err(Error::at(last.pos, format!("RENAMES {} THRU {}: the last item must start and end no earlier than the first", first.name, last.name)));
641 }
642 let root = |mut i: usize| {
643 while let Some(p) = items[i].parent {
644 i = p;
645 }
646 i
647 };
648 if let Some(t) = (0..items.len()).find(|&i| items[i].depending_on.is_some() && root(i) == record && items[i].offset >= items[a].offset && items[i].offset < end(b)) {
649 return Err(Error::at(items[t].pos, format!("RENAMES {} THRU {}: no OCCURS DEPENDING ON between them", first.name, last.name)));
650 }
651 let (offset, size, moved_by) = (items[a].offset, end(b) - items[a].offset, items[a].moved_by.clone());
652 let it = &mut items[index];
653 (it.offset, it.size, it.kind, it.moved_by) = (offset, size, Kind::Group, moved_by);
654 Ok(())
655}
656
657fn ancestors(items: &[Item], i: usize) -> impl Iterator<Item = usize> + '_ {
659 std::iter::successors(items[i].parent, |&p| items[p].parent)
660}
661
662fn alignment(kind: Kind) -> u32 {
665 match kind {
666 Kind::Binary { digits: 0..=4, .. } => 2,
667 Kind::Binary { .. } | Kind::Float(Precision::Short) | Kind::Pointer | Kind::Index | Kind::ObjectReference | Kind::ProgramPointer => 4,
668 Kind::Float(_) => 8,
669 _ => 1,
670 }
671}
672
673fn kind(e: &DataEntry, item: &Item, usage: Option<Usage>, pic: Option<&picture::Picture>, edits: &mut Vec<Vec<Sym>>) -> Result<Kind, Error> {
674 let err = |m: String| Error::at(e.pos, m);
675 let usage = usage.unwrap_or_default();
676 let handle = matches!(usage, Usage::ObjectReference | Usage::ProgramPointer);
677 let elementary = e.picture.is_some() || (handle || matches!(usage, Usage::Float1 | Usage::Float2 | Usage::Pointer | Usage::Index)) && item.children.is_empty();
678 if !elementary {
679 return if item.children.is_empty() { Err(err("an elementary item needs a PICTURE".into())) } else { Ok(Kind::Group) };
680 }
681 if !item.children.is_empty() {
682 return Err(err("a group item cannot have a PICTURE".into()));
683 }
684 if handle {
685 if e.picture.is_some() || e.value.as_ref().is_some_and(|v| *v != Literal::Figurative(syntax::ast::Figurative::Null)) {
686 return Err(err("an object reference, function-pointer or procedure-pointer takes no PICTURE and only VALUE NULL".into()));
687 }
688 return Ok(if usage == Usage::ObjectReference { Kind::ObjectReference } else { Kind::ProgramPointer });
689 }
690 if let Usage::Pointer | Usage::Index = usage {
691 if e.picture.is_some() {
692 return Err(err("POINTER and INDEX items take no PICTURE".into()));
693 }
694 return Ok(if usage == Usage::Pointer { Kind::Pointer } else { Kind::Index });
695 }
696 if let Usage::Float1 | Usage::Float2 = usage {
697 if e.picture.is_some() {
698 return Err(err("COMP-1 and COMP-2 items take no PICTURE".into()));
699 }
700 return Ok(Kind::Float(if usage == Usage::Float1 { Precision::Short } else { Precision::Long }));
701 }
702 let Some(pic) = pic else { return Err(err("an elementary item needs a PICTURE".into())) };
703 let blank_numeric;
704 let pic = match pic.category {
705 Category::Numeric if e.blank_when_zero && usage == Usage::Display => {
706 blank_numeric = picture::blank_when_zero(pic).map_err(err)?;
707 &blank_numeric
708 }
709 Category::NumericEdited => pic,
710 Category::Numeric if e.blank_when_zero && usage == Usage::National => return Err(err("BLANK WHEN ZERO on a USAGE NATIONAL item is not supported yet".into())),
711 _ if e.blank_when_zero => return Err(err("BLANK WHEN ZERO needs a numeric or numeric-edited item of USAGE DISPLAY or NATIONAL".into())),
712 _ => pic,
713 };
714 let k = match (pic.category, usage) {
715 (Category::NumericEdited, Usage::Display) => {
716 edits.push(pic.edit.clone().unwrap_or_default());
717 Kind::NumericEdited { edit: edits.len() as u32 - 1, digits: pic.digits, scale: pic.scale, blank_when_zero: e.blank_when_zero }
718 }
719 (Category::AlphanumericEdited, Usage::Display) => {
720 edits.push(pic.edit.clone().unwrap_or_default());
721 Kind::AlnumEdited { edit: edits.len() as u32 - 1 }
722 }
723 (Category::Numeric, Usage::Display) => Kind::Zoned { digits: pic.digits, scale: pic.scale, signed: pic.signed, sign: e.sign },
724 (Category::Numeric, Usage::Packed) => Kind::Packed { digits: pic.digits, scale: pic.scale, signed: pic.signed },
725 (Category::Numeric, Usage::Binary | Usage::NativeBinary) if pic.digits <= 18 => {
726 Kind::Binary { digits: pic.digits, scale: pic.scale, signed: pic.signed, native: usage == Usage::NativeBinary }
727 }
728 (Category::Numeric, Usage::Binary | Usage::NativeBinary) => return Err(err("a binary item holds at most 18 digits".into())),
729 (Category::Alphanumeric, Usage::Display) => Kind::Alnum { justified: e.justified },
730 (Category::National, Usage::Display | Usage::National) => Kind::National,
731 (category, usage) => return Err(err(format!("a {category:?} PICTURE with USAGE {usage:?} is not supported yet"))),
732 };
733 if let Kind::Zoned { sign: Some(_), signed: false, .. } = k {
734 return Err(err("a SIGN clause needs an S in the PICTURE".into()));
735 }
736 Ok(k)
737}
738
739fn elementary_size(item: &Item, e_size: Option<u32>) -> u32 {
740 match item.kind {
741 Kind::Group => 0,
742 Kind::Alnum { .. } | Kind::NumericEdited { .. } | Kind::AlnumEdited { .. } => e_size.unwrap_or(0),
743 Kind::National => 2 * e_size.unwrap_or(0),
744 Kind::Zoned { digits, sign, .. } => digits + sign.is_some_and(|s| s.separate) as u32,
745 Kind::Packed { digits, .. } => digits / 2 + 1,
746 Kind::Binary { digits, .. } => match digits {
747 0..=4 => 2,
748 5..=9 => 4,
749 _ => 8,
750 },
751 Kind::Float(p) => p.bytes() as u32,
752 Kind::Pointer | Kind::Index | Kind::ObjectReference | Kind::ProgramPointer => 4,
753 }
754}
755
756fn measure(items: &mut [Item], aligns: &[u32], index: usize, base: u32) -> Result<(), Error> {
760 if items[index].kind != Kind::Group {
761 return Ok(());
762 }
763 let children = items[index].children.clone();
764 let (mut cursor, mut extent) = (0u32, 0u32);
765 let mut placed: Vec<(Option<String>, u32)> = Vec::new();
766 let mut previous: Option<usize> = None;
767 for c in children {
768 let redefined = match items[c].redefines.clone() {
769 Some(target) => Some(placed.iter().rev().find(|(n, _)| n.as_deref() == Some(target.as_str())).map(|&(_, o)| o).ok_or_else(|| {
770 Error::at(items[c].pos, format!("REDEFINES {target}: no earlier item of that name at this level"))
771 })?),
772 None => None,
773 };
774 let start = redefined.unwrap_or(cursor);
775 let m = first_alignment(items, aligns, c);
776 let slack = (m - (base + start) % m) % m;
777 if slack > 0 && redefined.is_some() {
778 return Err(Error::at(items[c].pos, format!("a SYNCHRONIZED item at the start of a REDEFINES would need {slack} slack bytes: the redefined item must be on a {m}-byte boundary")));
779 }
780 if slack > 0 {
781 give_slack(items, previous, cursor, slack);
782 }
783 let offset = start + slack;
784 measure(items, aligns, c, base + offset)?;
785 if items[c].table {
786 let m = widest_alignment(items, aligns, c);
787 items[c].size = items[c].size.div_ceil(m) * m;
788 }
789 let too_large = || Error::at(items[c].pos, format!("an item larger than the interpreter's {MAX_STORAGE} bytes"));
790 let span = items[c].size.checked_mul(items[c].occurs).filter(|&s| s <= MAX_STORAGE).ok_or_else(too_large)?;
791 if redefined.is_none() {
792 cursor = offset.checked_add(span).filter(|&s| s <= MAX_STORAGE).ok_or_else(too_large)?;
793 }
794 extent = extent.max(offset + span);
795 items[c].offset = offset;
796 placed.push((items[c].name.clone(), offset));
797 previous = Some(c);
798 }
799 items[index].size = cursor.max(extent);
800 Ok(())
801}
802
803fn first_alignment(items: &[Item], aligns: &[u32], index: usize) -> u32 {
805 match items[index].children.first() {
806 Some(&c) if items[index].kind == Kind::Group => first_alignment(items, aligns, c),
807 _ => aligns[index],
808 }
809}
810
811fn widest_alignment(items: &[Item], aligns: &[u32], index: usize) -> u32 {
814 items[index].children.iter().map(|&c| widest_alignment(items, aligns, c)).fold(aligns[index], u32::max)
815}
816
817fn give_slack(items: &mut [Item], previous: Option<usize>, cursor: u32, slack: u32) {
822 let Some(mut g) = previous else { return };
823 let mut end = cursor;
824 while items[g].kind == Kind::Group && !items[g].table && items[g].redefines.is_none() && items[g].offset + items[g].size == end {
825 end = items[g].size;
826 items[g].size += slack;
827 match items[g].children.last() {
828 Some(&c) => g = c,
829 None => return,
830 }
831 }
832}
833
834fn place(items: &mut [Item], index: usize, offset: u32, mut dims: Vec<(u32, u32)>) {
835 items[index].offset = offset;
836 if items[index].table {
837 dims.push((items[index].size, items[index].occurs));
838 }
839 items[index].dims = dims.clone();
840 for c in items[index].children.clone() {
841 let relative = items[c].offset;
842 place(items, c, offset + relative, dims.clone());
843 }
844}
845
846impl Layout {
847 pub fn category(&self, i: usize) -> Option<syntax::ast::DataCategory> {
851 use syntax::ast::DataCategory;
852 let item = &self.items[i];
853 Some(match item.kind {
854 Kind::Alnum { .. } if item.alphabetic => DataCategory::Alphabetic,
855 Kind::Alnum { .. } => DataCategory::Alphanumeric,
856 Kind::AlnumEdited { .. } => DataCategory::AlphanumericEdited,
857 Kind::National => DataCategory::National,
858 Kind::NumericEdited { .. } => DataCategory::NumericEdited,
859 Kind::Zoned { .. } | Kind::Packed { .. } | Kind::Binary { .. } | Kind::Float(_) => DataCategory::Numeric,
860 Kind::Group | Kind::Pointer | Kind::Index | Kind::ObjectReference | Kind::ProgramPointer => return None,
861 })
862 }
863
864 pub fn length_of_ref<'r>(&self, r: &'r Ref) -> std::borrow::Cow<'r, Ref> {
867 use syntax::ast::{Expr, Operand};
868 match self.resolve(&r.name, &r.qualifiers, r.pos) {
869 Ok(Resolved::Item(i)) if r.subscripts.is_empty() && !self.items[i].dims.is_empty() => {
870 let one = Expr::Operand(Operand::Literal(Literal::Number("1".into())));
871 std::borrow::Cow::Owned(Ref { subscripts: vec![one; self.items[i].dims.len()], ..r.clone() })
872 }
873 _ => std::borrow::Cow::Borrowed(r),
874 }
875 }
876
877 pub fn initialize_receivers(&self, i: usize, filler: bool) -> Vec<(usize, u32)> {
881 let mut out = Vec::new();
882 self.receivers_under(i, 0, filler, &mut out);
883 out
884 }
885
886 fn receivers_under(&self, i: usize, offset: u32, filler: bool, out: &mut Vec<(usize, u32)>) {
887 let item = &self.items[i];
888 match item.kind {
889 Kind::Index | Kind::ObjectReference | Kind::ProgramPointer => {}
890 Kind::Group => {
891 for &c in &item.children {
892 let child = &self.items[c];
893 if child.redefines.is_some() || child.name.is_none() && child.kind != Kind::Group && !filler {
894 continue;
895 }
896 for k in 0..child.occurs {
897 self.receivers_under(c, offset + (child.offset - item.offset) + k * child.size, filler, out);
898 }
899 }
900 }
901 _ => out.push((i, offset)),
902 }
903 }
904
905 pub fn name_files(&mut self, files: &[FileDecl], linage_counters: Vec<Option<usize>>) {
907 self.file_names = files.iter().map(|f| f.name.clone()).collect();
908 self.linage_counters = linage_counters;
909 }
910
911 fn depth(&self, r: Resolved) -> u8 {
913 let mut i = match r {
914 Resolved::Item(i) => i,
915 Resolved::Condition(c) => self.conditions[c].item,
916 };
917 while let Some(p) = self.items[i].parent {
918 i = p;
919 }
920 self.items[i].linkage.and_then(|l| self.depths.get(l as usize)).copied().unwrap_or_default()
921 }
922
923 pub fn is_argument(&self, ordinal: usize) -> bool {
925 self.bindings.get(ordinal).is_none_or(|b| *b == Binding::Argument)
926 }
927
928 fn file_qualifying(&self, mut i: usize) -> Option<&str> {
931 while let Some(p) = self.items[i].parent {
932 i = p;
933 }
934 let k = self.items[i].file.map(usize::from).or_else(|| self.linage_counters.iter().position(|&c| c == Some(i)))?;
935 self.file_names.get(k).map(String::as_str)
936 }
937
938 fn complete(&self, candidate: Resolved, qualifiers: &[String]) -> bool {
942 let (start, own) = match candidate {
943 Resolved::Item(i) => (self.items[i].parent, i),
944 Resolved::Condition(c) => (Some(self.conditions[c].item), self.conditions[c].item),
945 };
946 let given = || qualifiers.iter().map(String::as_str);
947 given().eq(names_from(&self.items, start)) || given().eq(names_from(&self.items, start).chain(self.file_qualifying(own)))
948 }
949
950 pub fn resolve(&self, name: &str, qualifiers: &[String], pos: Pos) -> Result<Resolved, Error> {
956 let within = |mut at: Option<usize>, own: usize| {
957 let mut wanted = qualifiers.iter();
958 let mut next = wanted.next();
959 while let (Some(q), Some(i)) = (next, at) {
960 if self.items[i].name.as_deref() == Some(q.as_str()) {
961 next = wanted.next();
962 }
963 at = self.items[i].parent;
964 }
965 match next {
966 None => true,
967 Some(file) => wanted.next().is_none() && self.file_qualifying(own) == Some(file.as_str()),
968 }
969 };
970 let mut found: Vec<Resolved> = self
971 .items
972 .iter()
973 .enumerate()
974 .filter(|(i, it)| it.name.as_deref() == Some(name) && within(it.parent, *i))
975 .map(|(i, _)| Resolved::Item(i))
976 .collect();
977 found.extend(
978 self.conditions.iter().enumerate().filter(|(_, c)| c.name == name && within(Some(c.item), c.item)).map(|(i, _)| Resolved::Condition(i)),
979 );
980 if found.len() > 1 {
981 let nearest = found.iter().map(|&r| self.depth(r)).min().unwrap_or_default();
982 found.retain(|&r| self.depth(r) == nearest);
983 }
984 if found.len() > 1 && self.qualify == Qualify::Extend {
985 let complete: Vec<Resolved> = found.iter().copied().filter(|&r| self.complete(r, qualifiers)).collect();
986 if let [one] = complete.as_slice() {
987 return Ok(*one);
988 }
989 }
990 match found.as_slice() {
991 [one] => Ok(*one),
992 [] => Err(Error::at(pos, format!("{name} is not defined"))),
993 _ => Err(Error::at(pos, format!("{name} is ambiguous; qualify it with OF or IN"))),
994 }
995 }
996}
997
998pub fn record_area_owners(files: &[FileDecl], environment: &Environment) -> Result<Vec<usize>, Error> {
1002 let mut owner: Vec<usize> = (0..files.len()).collect();
1003 let root = |owner: &[usize], mut k: usize| {
1004 while owner[k] != k {
1005 k = owner[k];
1006 }
1007 k
1008 };
1009 let clauses = environment.same_record_areas.iter().map(|c| ("SAME RECORD AREA", c)).chain(environment.same_areas.iter().map(|c| ("SAME AREA", c)));
1010 for (clause, names) in clauses {
1011 let mut members = Vec::new();
1012 for name in names {
1013 let k = files.iter().position(|f| f.name == *name).ok_or_else(|| Error::at(Pos::default(), format!("{clause} names {name}, which is not a file")))?;
1014 if clause == "SAME RECORD AREA" || matches!(files[k].organization, Organization::Indexed | Organization::Relative) {
1015 members.push(k);
1016 }
1017 }
1018 for pair in members.windows(2) {
1019 let (a, b) = (root(&owner, pair[0]), root(&owner, pair[1]));
1020 owner[a.max(b)] = a.min(b);
1021 }
1022 }
1023 Ok((0..files.len()).map(|k| root(&owner, k)).collect())
1024}