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