Skip to main content

ironwork_compile/
layout.rs

1//! WORKING-STORAGE as IBM lays it out: sizes by USAGE and PICTURE, REDEFINES sharing storage,
2//! OCCURS repeating it, SYNCHRONIZED slack bytes before an item and after each occurrence, and
3//! level-66 RENAMES over what is laid out. See [`numeric::assumptions::WORKING_STORAGE_LAYOUT`]
4//! for where each 01 level starts.
5
6use crate::picture::{self, Category, Sym};
7use numeric::{Native, Qualify};
8use syntax::ast::{DataEntry, Environment, FileDecl, Literal, Organization, Ref, SignClause, Usage};
9use syntax::messages::{IWC0001, IWC0002, Message};
10use syntax::{Error, Pos};
11use std::collections::HashMap;
12use zarch::hfp::Precision;
13
14pub use rt::storage::Kind;
15
16#[derive(Clone, Debug)]
17pub struct Item {
18    pub name: Option<String>,
19    pub level: u8,
20    pub parent: Option<usize>,
21    pub children: Vec<usize>,
22    pub offset: u32,
23    /// One occurrence.
24    pub size: u32,
25    pub occurs: u32,
26    /// The fewest occurrences: OCCURS DEPENDING ON's minimum, else `occurs`.
27    pub occurs_min: u32,
28    /// Declared with OCCURS, so references to it take a subscript.
29    pub table: bool,
30    /// OCCURS ... DEPENDING ON: the item holding the current number of occurrences.
31    pub depending_on: Option<syntax::ast::Ref>,
32    /// The OCCURS DEPENDING ON tables within this group, other than one within another of them: the
33    /// occurrences past each one's current count leave its length out.
34    pub odo: Vec<usize>,
35    /// The OCCURS DEPENDING ON tables ahead of this item in its record: the occurrences past each
36    /// one's current count move it back (a variably located item, Language Reference SC27-8713-03,
37    /// p. 206).
38    pub moved_by: Vec<usize>,
39    /// Whether an item after this one in its record moves with an OCCURS DEPENDING ON table in it.
40    pub followed: bool,
41    /// A table's INDEXED BY names, and its ASCENDING/DESCENDING keys.
42    pub index_names: Vec<String>,
43    pub keys: Vec<(bool, syntax::ast::Ref)>,
44    /// A LOCAL-STORAGE item: its offset is from the activation's local storage.
45    pub local: bool,
46    pub kind: Kind,
47    pub value: Option<Literal>,
48    /// Stride and count of each OCCURS on the item and its ancestors, outermost first.
49    pub dims: Vec<(u32, u32)>,
50    pub redefines: Option<String>,
51    /// The file whose record area holds the item, for a FILE SECTION record.
52    pub file: Option<u16>,
53    /// The LINKAGE record the item belongs to, by its position among LINKAGE 01 and 77 items; its
54    /// offset is from that record's start, wherever the caller's argument puts it.
55    pub linkage: Option<u16>,
56    /// The class-name of a typed object reference.
57    pub object_class: Option<String>,
58    /// PICTURE scaling positions P right of the digits: the item's value is its digits times ten
59    /// to this power.
60    pub scaling: u32,
61    /// Of category alphabetic: a PICTURE of the symbol A alone.
62    pub alphabetic: bool,
63    pub pos: Pos,
64}
65
66#[derive(Clone, Debug)]
67pub struct Condition {
68    pub name: String,
69    pub item: usize,
70    pub values: Vec<(Literal, Option<Literal>)>,
71    /// The WHEN SET TO FALSE value.
72    pub false_value: Option<Literal>,
73}
74
75#[derive(Clone, Copy, Debug, PartialEq, Eq)]
76pub enum Resolved {
77    Item(usize),
78    Condition(usize),
79}
80
81/// Where a record addressed as a LINKAGE record is. Every other record is in the program's own
82/// storage (Language Reference SC27-8713-03, pp. 63-66).
83#[derive(Clone, Debug, PartialEq, Eq)]
84pub enum Binding {
85    /// A LINKAGE record: an argument, SET ADDRESS OF or the runtime gives it its address.
86    Argument,
87    /// An EXTERNAL data record, or a record redefining one: the run unit's record of this name
88    /// and size.
89    External { name: String, size: u32 },
90    /// A record of file k, an EXTERNAL file: the run unit's record area of that file-name.
91    ExternalFile(u16),
92    /// A GLOBAL record of a program containing this one, by its PROGRAM-ID and the record's name.
93    Global { program: String, record: String, section: Section },
94}
95
96/// Where a GLOBAL record is in the program that declares it.
97#[derive(Clone, Debug, PartialEq, Eq)]
98pub enum Section {
99    WorkingStorage,
100    LocalStorage,
101    Linkage,
102    /// The record area of the file of this name.
103    File(String),
104}
105
106pub struct Layout {
107    pub items: Vec<Item>,
108    pub conditions: Vec<Condition>,
109    /// The items and the condition-names of each name, in declaration order: the candidates
110    /// [`Layout::resolve`] qualifies.
111    named_items: HashMap<String, Vec<usize>>,
112    named_conditions: HashMap<String, Vec<usize>>,
113    /// The positions of each edited PICTURE.
114    pub edits: Vec<Vec<Sym>>,
115    /// The currency sign value each edited PICTURE's currency symbol stands for, empty without one.
116    pub currencies: Vec<String>,
117    /// Offset and size of each file's record area, in declaration order.
118    pub file_areas: Vec<(u32, u32)>,
119    /// The least and greatest length of each file's level-01 records, an OCCURS DEPENDING ON table
120    /// counted at its fewest and at its most occurrences (Language Reference SC27-8713-03, p. 188);
121    /// None for a file with none.
122    pub record_lengths: Vec<Option<(u32, u32)>>,
123    /// The item of each LINKAGE record, in order: the LINKAGE SECTION's, then the records whose
124    /// storage is elsewhere, as `bindings` says.
125    pub linkage_roots: Vec<usize>,
126    pub bindings: Vec<Binding>,
127    /// How many programs out the program declaring each LINKAGE record is: 0 for its own.
128    pub depths: Vec<u8>,
129    /// For each file whose record area is not in the program's storage, the LINKAGE record
130    /// bound to that area.
131    pub bound_areas: Vec<Option<u16>>,
132    /// Bytes of LOCAL-STORAGE each activation gets.
133    pub local_size: u32,
134    pub size: u32,
135    /// Each FD and SD name, in declaration order: the highest qualifier of its records.
136    pub file_names: Vec<String>,
137    /// The LINAGE-COUNTER item of each file whose FD has LINAGE, which its file-name qualifies.
138    pub linage_counters: Vec<Option<usize>>,
139    /// The QUALIFY option [`Layout::resolve`] follows.
140    pub qualify: Qualify,
141    /// PARMCHECK's buffer: its offset, at the end of the WORKING-STORAGE the program declares, and
142    /// its length (assumption [`numeric::assumptions::PARMCHECK_BUFFER`]).
143    pub parmcheck: Option<(u32, u32)>,
144    pub numcheck: crate::numcheck::NumcheckFacts,
145    /// Each SPECIAL-NAMES class-name with its characters, one bit per byte value.
146    pub classes: Vec<(String, [u8; 32])>,
147}
148
149const LEVEL_ALIGNMENT: u32 = 8;
150/// The interpreter's ceiling on WORKING-STORAGE, well under Enterprise COBOL's own.
151pub const MAX_STORAGE: u32 = 128 << 20;
152
153/// Lays out WORKING-STORAGE, then each file's record area, which all its 01 records share and
154/// which is at least `record_max` bytes. Files whose `shared` entry names the same file share one
155/// area, as large as the largest of them (see [`record_area_owners`]). `notation` is what
156/// SPECIAL-NAMES changes in the PICTUREs; `qualify` how RENAMES and later references resolve.
157/// Under PARMCHECK, `parmcheck` is how many of `entries` the program declares itself, the special
158/// registers the compiler adds following them, and the bytes of the buffer that goes between
159/// (Programming Guide SC27-8714-03, p. 397).
160#[allow(clippy::too_many_arguments)]
161pub fn build(
162    entries: &[DataEntry],
163    files: &[(&[DataEntry], Option<u32>)],
164    shared: &[usize],
165    linkage: &[DataEntry],
166    own_linkage: usize,
167    local: &[DataEntry],
168    notation: picture::Notation,
169    qualify: Qualify,
170    parmcheck: Option<(usize, u32)>,
171) -> Result<Layout, Error> {
172    let mut items: Vec<Item> = Vec::new();
173    let mut usages: Vec<Option<Usage>> = Vec::new();
174    let mut signs: Vec<Option<SignClause>> = Vec::new();
175    let mut synchronized: Vec<bool> = Vec::new();
176    let mut conditions = Vec::new();
177    let mut open: Vec<usize> = Vec::new();
178    let mut renames: Vec<(usize, &DataEntry)> = Vec::new();
179    const LINKAGE: u16 = u16::MAX;
180    const LOCAL: u16 = u16::MAX - 1;
181    let tagged: Vec<(Option<u16>, &DataEntry)> = entries
182        .iter()
183        .map(|e| (None, e))
184        .chain(files.iter().enumerate().flat_map(|(k, (records, _))| records.iter().map(move |e| (Some(k as u16), e))))
185        .chain(linkage.iter().map(|e| (Some(LINKAGE), e)))
186        .chain(local.iter().map(|e| (Some(LOCAL), e)))
187        .collect();
188    let mut group = None;
189    let mut linkage_roots = Vec::new();
190    let mut after_renames = false;
191    let bound = bound_records(&tagged);
192    // The LINKAGE entries after the program's own are the GLOBAL records of the programs containing
193    // it; an index of one of their tables is the declaring program's item, bound as the record is.
194    let inherited: Vec<bool> = std::iter::repeat_n(false, tagged.len() - linkage.len() - local.len()).chain((0..linkage.len()).map(|i| i >= own_linkage)).chain(std::iter::repeat_n(false, local.len())).collect();
195    let mut bound_roots = Vec::new();
196    for (&(region, e), &bound) in tagged.iter().zip(&bound) {
197        if region != group {
198            open.clear();
199            group = region;
200            after_renames = false;
201        }
202        let file = region.filter(|&r| r != LINKAGE && r != LOCAL);
203        let in_linkage = region == Some(LINKAGE);
204        if e.occurs.is_some() && matches!(e.level, 1 | 66 | 77 | 88) {
205            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)));
206        }
207        if e.level == 88 {
208            if after_renames {
209                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"));
210            }
211            let item = *open.last().ok_or_else(|| syntax::messages::IWC0029.at(e.pos, "a level-88 entry with no item before it"))?;
212            let name = e.name.clone().ok_or_else(|| syntax::messages::IWC0030.at(e.pos, "a level-88 entry needs a name"))?;
213            conditions.push(Condition { name, item, values: e.condition_values.clone(), false_value: e.false_value.clone() });
214            continue;
215        }
216        if e.renames.is_some() != (e.level == 66) {
217            return Err(syntax::messages::IWC0031.at(e.pos, "RENAMES goes with level 66, and level 66 with RENAMES"));
218        }
219        if e.level == 66 {
220            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"))?;
221            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() {
222                return Err(syntax::messages::IWC0033.at(e.pos, "a level-66 entry has a name and a RENAMES clause, and nothing else"));
223            }
224            renames.push((items.len(), e));
225            items.push(Item {
226                name: e.name.clone(),
227                level: 66,
228                parent: Some(record),
229                children: Vec::new(),
230                offset: 0,
231                size: 0,
232                occurs: 1,
233                occurs_min: 1,
234                table: false,
235                depending_on: None,
236                odo: Vec::new(),
237                moved_by: Vec::new(),
238                followed: false,
239                index_names: Vec::new(),
240                keys: Vec::new(),
241                local: items[record].local,
242                kind: Kind::Group,
243                value: None,
244                dims: Vec::new(),
245                redefines: None,
246                file,
247                linkage: items[record].linkage,
248                object_class: None,
249                scaling: 0,
250                alphabetic: false,
251                pos: e.pos,
252            });
253            usages.push(None);
254            signs.push(None);
255            synchronized.push(false);
256            after_renames = true;
257            continue;
258        }
259        if !(e.level == 1 || e.level == 77 || (2..=49).contains(&e.level)) {
260            return Err(syntax::messages::IWC0034.at(e.pos, format!("level {} is not a data level", e.level)));
261        }
262        if after_renames && e.level != 1 && e.level != 77 {
263            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)));
264        }
265        after_renames = false;
266        while open.last().is_some_and(|&i| items[i].level >= e.level || items[i].level == 77) {
267            open.pop();
268        }
269        let parent = if e.level == 1 || e.level == 77 { None } else { open.last().copied() };
270        if e.level != 1 && e.level != 77 && parent.is_none() {
271            return Err(syntax::messages::IWC0036.at(e.pos, format!("level {} with no group to belong to", e.level)));
272        }
273        let index = items.len();
274        items.push(Item {
275            name: e.name.clone(),
276            level: e.level,
277            parent,
278            children: Vec::new(),
279            offset: 0,
280            size: 0,
281            occurs: e.occurs.unwrap_or(1),
282            occurs_min: e.occurs_min.or(e.occurs).unwrap_or(1),
283            table: e.occurs.is_some(),
284            depending_on: e.depending_on.clone(),
285            odo: Vec::new(),
286            moved_by: Vec::new(),
287            followed: false,
288            index_names: e.indexed_by.clone(),
289            keys: e.keys.clone(),
290            local: region == Some(LOCAL),
291            kind: Kind::Group,
292            value: e.value.clone().filter(|_| file.is_none() && !in_linkage),
293            dims: Vec::new(),
294            redefines: e.redefines.clone(),
295            file,
296            linkage: if in_linkage {
297                Some(match (parent, &e.redefines) {
298                    (None, Some(target)) => linkage_roots.iter().position(|&r: &usize| items[r].name.as_ref() == Some(target)).ok_or_else(|| {
299                        syntax::messages::IWC0037.at(e.pos, format!("REDEFINES {target}: no earlier 01-level item of that name"))
300                    })? as u16,
301                    (None, None) => {
302                        linkage_roots.push(index);
303                        linkage_roots.len() as u16 - 1
304                    }
305                    (Some(p), _) => items[p].linkage.unwrap_or_default(),
306                })
307            } else if bound {
308                if parent.is_none() {
309                    bound_roots.push(index);
310                }
311                Some(0)
312            } else {
313                None
314            },
315            object_class: e.object_class.clone(),
316            scaling: 0,
317            alphabetic: false,
318            pos: e.pos,
319        });
320        if e.occurs == Some(0) {
321            return Err(syntax::messages::IWC0038.at(e.pos, "OCCURS 0 is not a table"));
322        }
323        let inherited = parent.and_then(|p| usages[p]);
324        usages.push(e.usage.or(inherited));
325        signs.push(e.sign.or(parent.and_then(|p| signs[p])));
326        synchronized.push(e.sync || parent.is_some_and(|p| synchronized[p]));
327        if let Some(p) = parent {
328            items[p].children.push(index);
329        }
330        open.push(index);
331    }
332    let (mut edits, mut currencies) = (Vec::new(), Vec::new());
333    let mut aligns = vec![1u32; items.len()];
334    for (index, (_, e)) in tagged.iter().filter(|(_, e)| e.level != 88).enumerate() {
335        if e.level == 66 {
336            continue;
337        }
338        let pic = e.picture.as_deref().map(|p| picture::analyse_with(p, notation).map_err(|(message, m)| message.at(e.pos, m))).transpose()?;
339        items[index].kind = kind(e, &items[index], usages[index], signs[index], pic.as_ref(), &mut edits)?;
340        if currencies.len() < edits.len() {
341            currencies.push(pic.as_ref().and_then(|p| p.currency.clone()).unwrap_or_default());
342        }
343        items[index].scaling = pic.as_ref().map_or(0, |p| p.scaling);
344        items[index].alphabetic = matches!(items[index].kind, Kind::Alnum { .. }) && e.picture.as_deref().is_some_and(crate::corresponding::is_alphabetic);
345        items[index].size = elementary_size(&items[index], pic.as_ref().map(|p| p.size));
346        if synchronized[index] && items[index].kind != Kind::Group {
347            aligns[index] = alignment(items[index].kind);
348        }
349    }
350    for ((_, e), &inherited) in tagged.iter().zip(&inherited) {
351        for name in &e.indexed_by {
352            let index = items.len();
353            items.push(Item {
354                name: Some(name.clone()),
355                level: 77,
356                parent: None,
357                children: Vec::new(),
358                offset: 0,
359                size: 4,
360                occurs: 1,
361                occurs_min: 1,
362                table: false,
363                depending_on: None,
364                odo: Vec::new(),
365                moved_by: Vec::new(),
366                followed: false,
367                index_names: Vec::new(),
368                keys: Vec::new(),
369                local: false,
370                kind: Kind::Index,
371                value: None,
372                dims: Vec::new(),
373                redefines: None,
374                file: None,
375                linkage: None,
376                object_class: None,
377                scaling: 0,
378                alphabetic: false,
379                pos: e.pos,
380            });
381            if inherited {
382                items[index].linkage = Some(linkage_roots.len() as u16);
383                linkage_roots.push(index);
384            }
385        }
386    }
387    aligns.resize(items.len(), 1);
388    let arguments = linkage_roots.len();
389    for i in 0..items.len() {
390        let mut r = i;
391        while let Some(p) = items[r].parent {
392            r = p;
393        }
394        if let Some(b) = bound_roots.iter().position(|&x| x == r) {
395            items[i].linkage = Some((arguments + b) as u16);
396        }
397    }
398    linkage_roots.extend(&bound_roots);
399    let roots: Vec<usize> = (0..items.len()).filter(|&i| items[i].parent.is_none()).collect();
400    for &r in &roots {
401        measure(&mut items, &aligns, r, 0)?;
402    }
403    let mut local_cursor = 0u32;
404    let local_roots: Vec<usize> = roots.iter().copied().filter(|&r| items[r].local).collect();
405    for r in local_roots {
406        let start = local_cursor.div_ceil(LEVEL_ALIGNMENT) * LEVEL_ALIGNMENT;
407        local_cursor = start + items[r].size;
408        if local_cursor > MAX_STORAGE {
409            return Err(syntax::messages::IWL0003.at(items[r].pos, format!("LOCAL-STORAGE exceeds the interpreter's {MAX_STORAGE} bytes")));
410        }
411        place(&mut items, r, start, Vec::new());
412    }
413    let owner = |k: usize| shared.get(k).copied().filter(|&g| g < files.len()).unwrap_or(k);
414    let mut own: Vec<u32> = files.iter().map(|f| f.1.unwrap_or(0)).collect();
415    for &r in &roots {
416        if let Some(k) = items[r].file {
417            own[k as usize] = own[k as usize].max(items[r].size);
418        }
419    }
420    let mut area_size = vec![0u32; files.len()];
421    for (k, &size) in own.iter().enumerate() {
422        area_size[owner(k)] = area_size[owner(k)].max(size);
423    }
424    let mut cursor = 0u32;
425    let mut root_offsets: Vec<(String, u32)> = Vec::new();
426    let mut area_starts: Vec<Option<u32>> = vec![None; files.len()];
427    let mut pending = parmcheck.map(|(declared, bytes)| (entries[..declared.min(entries.len())].iter().filter(|e| e.level != 88).count(), bytes));
428    let mut buffer = None;
429    for &r in &roots {
430        if items[r].local {
431            continue;
432        }
433        if items[r].linkage.is_some() {
434            place(&mut items, r, 0, Vec::new());
435            continue;
436        }
437        if let Some((own, bytes)) = pending
438            && r >= own
439        {
440            buffer = Some((cursor, bytes));
441            cursor += bytes;
442            pending = None;
443        }
444        if let Some(k) = items[r].file {
445            let g = owner(k as usize);
446            let start = *area_starts[g].get_or_insert(cursor.div_ceil(LEVEL_ALIGNMENT) * LEVEL_ALIGNMENT);
447            cursor = cursor.max(start + area_size[g]);
448            if cursor > MAX_STORAGE {
449                return Err(syntax::messages::IWL0004.at(items[r].pos, format!("storage exceeds the interpreter's {MAX_STORAGE} bytes")));
450            }
451            place(&mut items, r, start, Vec::new());
452            continue;
453        }
454        let offset = match &items[r].redefines {
455            Some(target) => root_offsets.iter().find(|(n, _)| n == target).map(|&(_, o)| o).ok_or_else(|| {
456                syntax::messages::IWC0037.at(items[r].pos, format!("REDEFINES {target}: no earlier 01-level item of that name"))
457            })?,
458            None => cursor.div_ceil(LEVEL_ALIGNMENT) * LEVEL_ALIGNMENT,
459        };
460        cursor = cursor.max(offset + items[r].size);
461        if cursor > MAX_STORAGE {
462            return Err(syntax::messages::IWL0005.at(items[r].pos, format!("WORKING-STORAGE exceeds the interpreter's {MAX_STORAGE} bytes")));
463        }
464        if let Some(name) = &items[r].name {
465            root_offsets.push((name.clone(), offset));
466        }
467        place(&mut items, r, offset, Vec::new());
468    }
469    if let Some((_, bytes)) = pending {
470        buffer = Some((cursor, bytes));
471        cursor += bytes;
472    }
473    let tables: Vec<usize> = (0..items.len()).filter(|&i| items[i].depending_on.is_some()).collect();
474    for &t in &tables {
475        let (mut child, mut at, mut nested) = (t, items[t].parent, false);
476        let (mut chain, mut followers) = (vec![t], Vec::new());
477        while let Some(a) = at {
478            nested |= child != t && items[child].depending_on.is_some();
479            if !nested {
480                items[a].odo.push(t);
481            }
482            let end = items[child].offset + items[child].size * items[child].occurs;
483            let after = items[a].children.iter().skip_while(|&&c| c != child).skip(1).copied().filter(|&c| items[c].offset >= end);
484            let before = followers.len();
485            followers.extend(after);
486            if followers.len() > before {
487                chain.iter().for_each(|&c| items[c].followed = true);
488            }
489            chain.push(a);
490            (child, at) = (a, items[a].parent);
491        }
492        let holds_another = tables.iter().any(|&u| u != t && ancestors(&items, u).any(|p| p == t));
493        if !followers.is_empty() && (items[t].dims.len() > 1 || holds_another) {
494            return Err(syntax::messages::IWR0012.at(items[t].pos, "items after an OCCURS DEPENDING ON table in the same record are not supported yet"));
495        }
496        while let Some(f) = followers.pop() {
497            items[f].moved_by.push(t);
498            followers.extend(items[f].children.iter().copied());
499        }
500    }
501    for (index, e) in renames {
502        rename(&mut items, index, e, qualify)?;
503    }
504    let mut bound_areas = vec![None; files.len()];
505    let mut bindings = vec![Binding::Argument; arguments];
506    for &r in &bound_roots {
507        let binding = match items[r].file {
508            Some(k) => {
509                bound_areas[k as usize].get_or_insert(items[r].linkage.unwrap_or_default());
510                Binding::ExternalFile(k)
511            }
512            None => {
513                let name = items[r].redefines.clone().or_else(|| items[r].name.clone()).unwrap_or_default();
514                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);
515                Binding::External { name, size }
516            }
517        };
518        bindings.push(binding);
519    }
520    let mut areas = Vec::new();
521    for (k, &size) in own.iter().enumerate() {
522        if bound_areas[k].is_some() {
523            areas.push((0, area_size[owner(k)]));
524            continue;
525        }
526        let g = owner(k);
527        let start = match area_starts[g] {
528            Some(start) => start,
529            None => {
530                let start = cursor.div_ceil(LEVEL_ALIGNMENT) * LEVEL_ALIGNMENT;
531                area_starts[g] = Some(start);
532                cursor = cursor.max(start + area_size[g]);
533                start
534            }
535        };
536        areas.push((start, size));
537    }
538    let mut record_lengths: Vec<Option<(u32, u32)>> = vec![None; files.len()];
539    for &r in &roots {
540        let Some(k) = items[r].file else { continue };
541        let fewer: u32 = items[r]
542            .odo
543            .iter()
544            .map(|&t| {
545                let t = &items[t];
546                let outer: u32 = t.dims[..t.dims.len().saturating_sub(1)].iter().map(|&(_, n)| n).product();
547                t.occurs.saturating_sub(t.occurs_min) * t.size * outer
548            })
549            .sum();
550        let (least, most) = (items[r].size.saturating_sub(fewer), items[r].size);
551        let lengths = &mut record_lengths[k as usize];
552        *lengths = Some(lengths.map_or((least, most), |(l, m)| (l.min(least), m.max(most))));
553    }
554    Ok(Layout {
555        named_items: by_name(items.iter().map(|i| i.name.as_deref())),
556        named_conditions: by_name(conditions.iter().map(|c| Some(c.name.as_str()))),
557        items,
558        conditions,
559        edits,
560        currencies,
561        file_areas: areas,
562        record_lengths,
563        linkage_roots,
564        depths: vec![0; bindings.len()],
565        bindings,
566        bound_areas,
567        local_size: local_cursor,
568        size: cursor,
569        file_names: Vec::new(),
570        linage_counters: Vec::new(),
571        qualify,
572        parmcheck: buffer,
573        numcheck: Default::default(),
574        classes: Vec::new(),
575    })
576}
577
578
579/// The index of each entry under its name, ascending; an unnamed entry is under none.
580fn by_name<'a>(names: impl Iterator<Item = Option<&'a str>>) -> HashMap<String, Vec<usize>> {
581    let mut index: HashMap<String, Vec<usize>> = HashMap::new();
582    for (k, name) in names.enumerate() {
583        if let Some(name) = name {
584            index.entry(name.to_owned()).or_default().push(k);
585        }
586    }
587    index
588}
589
590/// The names of item `start` and each group above it, nearest first: the hierarchy of names that
591/// qualifies an item `start` holds or a condition-name of `start`. FILLER and unnamed items give
592/// none.
593fn names_from(items: &[Item], start: Option<usize>) -> impl Iterator<Item = &str> {
594    std::iter::successors(start, |&p| items[p].parent).filter_map(|p| items[p].name.as_deref())
595}
596
597/// Which entries belong to records whose storage the run unit holds: an EXTERNAL record of
598/// WORKING-STORAGE or of an EXTERNAL file, or a WORKING-STORAGE record redefining an EXTERNAL one
599/// (Language Reference SC27-8713-03, p. 197).
600fn bound_records(tagged: &[(Option<u16>, &DataEntry)]) -> Vec<bool> {
601    let mut externals: Vec<&str> = Vec::new();
602    let mut current = false;
603    let mut out = Vec::with_capacity(tagged.len());
604    for &(region, e) in tagged {
605        if matches!(e.level, 1 | 77) {
606            current = match region {
607                None => e.external || e.redefines.as_deref().is_some_and(|t| externals.contains(&t)),
608                Some(r) => r < u16::MAX - 1 && e.external,
609            };
610            if current && region.is_none() && e.external {
611                externals.extend(e.name.as_deref());
612            }
613        }
614        out.push(current);
615    }
616    out
617}
618
619/// Gives level-66 entry `index` the storage and attributes of what it renames (Language Reference
620/// SC27-8713-03, pp. 228-229): one item as that item is, or from the start of the first item
621/// through the end of the last as an alphanumeric group.
622fn rename(items: &mut [Item], index: usize, e: &DataEntry, qualify: Qualify) -> Result<(), Error> {
623    let Some((first, last)) = &e.renames else { return Ok(()) };
624    let record = items[index].parent.unwrap_or(index);
625    let find = |r: &Ref| -> Result<usize, Error> {
626        let err = |m: String| Err(syntax::messages::IWC0039.at(r.pos, format!("RENAMES {}: {m}", r.name)));
627        if !r.subscripts.is_empty() || r.refmod.is_some() {
628            return err("a renamed item is named without subscripts or reference modification".into());
629        }
630        let in_record = |mut at: usize| {
631            let mut wanted = r.qualifiers.iter().peekable();
632            while let Some(p) = items[at].parent {
633                if wanted.peek().is_some_and(|q| items[p].name.as_deref() == Some(q.as_str())) {
634                    wanted.next();
635                }
636                at = p;
637            }
638            at == record && wanted.next().is_none()
639        };
640        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();
641        if found.len() > 1 && qualify == Qualify::Extend {
642            let complete: Vec<usize> = found.iter().copied().filter(|&i| names_from(items, items[i].parent).eq(r.qualifiers.iter().map(String::as_str))).collect();
643            if complete.len() == 1 {
644                found = complete;
645            }
646        }
647        let &[t] = found.as_slice() else {
648            let record_name = items[record].name.clone().unwrap_or_default();
649            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() });
650        };
651        if !items[t].dims.is_empty() {
652            return err("a renamed item must not have OCCURS, nor belong to a group that has it".into());
653        }
654        Ok(t)
655    };
656    if first.name == items[record].name.clone().unwrap_or_default() && first.qualifiers.is_empty() {
657        return Err(syntax::messages::IWC0040.at(first.pos, format!("RENAMES {}: a level-66 entry cannot rename a level-01 record", first.name)));
658    }
659    let a = find(first)?;
660    let Some(last) = last else {
661        let (offset, size, kind, scaling, odo, moved_by, followed) =
662            (items[a].offset, items[a].size, items[a].kind, items[a].scaling, items[a].odo.clone(), items[a].moved_by.clone(), items[a].followed);
663        let it = &mut items[index];
664        (it.offset, it.size, it.kind, it.scaling, it.odo, it.moved_by, it.followed) = (offset, size, kind, scaling, odo, moved_by, followed);
665        return Ok(());
666    };
667    let b = find(last)?;
668    let end = |i: usize| items[i].offset + items[i].size;
669    let mut up = items[b].parent;
670    while let Some(p) = up {
671        if p == a {
672            return Err(syntax::messages::IWC0041.at(last.pos, format!("RENAMES {} THRU {}: the last item cannot be within the first", first.name, last.name)));
673        }
674        up = items[p].parent;
675    }
676    if a == b || items[b].offset < items[a].offset || end(b) < end(a) {
677        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)));
678    }
679    let root = |mut i: usize| {
680        while let Some(p) = items[i].parent {
681            i = p;
682        }
683        i
684    };
685    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)) {
686        return Err(syntax::messages::IWC0043.at(items[t].pos, format!("RENAMES {} THRU {}: no OCCURS DEPENDING ON between them", first.name, last.name)));
687    }
688    let (offset, size, moved_by) = (items[a].offset, end(b) - items[a].offset, items[a].moved_by.clone());
689    let it = &mut items[index];
690    (it.offset, it.size, it.kind, it.moved_by) = (offset, size, Kind::Group, moved_by);
691    Ok(())
692}
693
694/// The groups item `i` belongs to, nearest first.
695fn ancestors(items: &[Item], i: usize) -> impl Iterator<Item = usize> + '_ {
696    std::iter::successors(items[i].parent, |&p| items[p].parent)
697}
698
699/// The boundary a SYNCHRONIZED item of this kind is aligned on (Language Reference SC27-8713-03,
700/// pp. 232-233); 1 for the kinds SYNCHRONIZED leaves where they are.
701fn alignment(kind: Kind) -> u32 {
702    match kind {
703        Kind::Binary { native: Native::BinaryChar, .. } => 1,
704        Kind::Binary { digits, signed, native: native @ (Native::CompX | Native::Comp5Bytes), .. } => match (numeric::binary::Binary { digits: digits as u8, signed, native }).bytes() {
705            bytes @ (2 | 4) => bytes as u32,
706            8 => 4,
707            _ => 1,
708        },
709        Kind::Binary { digits: 0..=4, .. } => 2,
710        Kind::Binary { .. } | Kind::Float(Precision::Short) | Kind::Pointer | Kind::Index | Kind::ObjectReference | Kind::ProgramPointer => 4,
711        Kind::Float(_) => 8,
712        _ => 1,
713    }
714}
715
716/// An elementary item's kind. `sign` is its own SIGN clause or the nearest group's above it, which
717/// applies to a signed zoned item alone (Language Reference SC27-8713-03, p. 231).
718fn kind(e: &DataEntry, item: &Item, usage: Option<Usage>, sign: Option<SignClause>, pic: Option<&picture::Picture>, edits: &mut Vec<Vec<Sym>>) -> Result<Kind, Error> {
719    let err = |message: Message, m: String| message.at(e.pos, m);
720    let usage = usage.unwrap_or_default();
721    let handle = matches!(usage, Usage::ObjectReference | Usage::ProgramPointer);
722    let elementary = e.picture.is_some() || (handle || matches!(usage, Usage::Float1 | Usage::Float2 | Usage::Pointer | Usage::Index | Usage::BinaryChar { .. })) && item.children.is_empty();
723    if !elementary {
724        return if item.children.is_empty() { Err(err(syntax::messages::IWC0235, "an elementary item needs a PICTURE".into())) } else { Ok(Kind::Group) };
725    }
726    if !item.children.is_empty() {
727        return Err(err(syntax::messages::IWC0236, "a group item cannot have a PICTURE".into()));
728    }
729    if handle {
730        if e.picture.is_some() || e.value.as_ref().is_some_and(|v| *v != Literal::Figurative(syntax::ast::Figurative::Null)) {
731            return Err(err(syntax::messages::IWC0237, "an object reference, function-pointer or procedure-pointer takes no PICTURE and only VALUE NULL".into()));
732        }
733        return Ok(if usage == Usage::ObjectReference { Kind::ObjectReference } else { Kind::ProgramPointer });
734    }
735    if let Usage::Pointer | Usage::Index = usage {
736        if e.picture.is_some() {
737            return Err(err(syntax::messages::IWC0238, "POINTER and INDEX items take no PICTURE".into()));
738        }
739        return Ok(if usage == Usage::Pointer { Kind::Pointer } else { Kind::Index });
740    }
741    if let Usage::Float1 | Usage::Float2 = usage {
742        if e.picture.is_some() {
743            return Err(err(syntax::messages::IWC0239, "COMP-1 and COMP-2 items take no PICTURE".into()));
744        }
745        return Ok(Kind::Float(if usage == Usage::Float1 { Precision::Short } else { Precision::Long }));
746    }
747    if let Usage::BinaryChar { signed } = usage {
748        if e.picture.is_some() {
749            return Err(err(syntax::messages::IWC0294, "BINARY-CHAR takes no PICTURE".into()));
750        }
751        return Ok(Kind::Binary { digits: 3, scale: 0, signed, native: Native::BinaryChar });
752    }
753    let Some(pic) = pic else { return Err(err(syntax::messages::IWC0235, "an elementary item needs a PICTURE".into())) };
754    let blank_numeric;
755    let pic = match pic.category {
756        Category::Numeric if e.blank_when_zero && usage == Usage::Display => {
757            blank_numeric = picture::blank_when_zero(pic).map_err(|(message, m)| message.at(e.pos, m))?;
758            &blank_numeric
759        }
760        Category::NumericEdited => pic,
761        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())),
762        _ 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())),
763        _ => pic,
764    };
765    let k = match (pic.category, usage) {
766        (Category::NumericEdited, Usage::Display) => {
767            edits.push(pic.edit.clone().unwrap_or_default());
768            Kind::NumericEdited { edit: edits.len() as u32 - 1, digits: pic.digits, scale: pic.scale, blank_when_zero: e.blank_when_zero }
769        }
770        (Category::AlphanumericEdited, Usage::Display) => {
771            edits.push(pic.edit.clone().unwrap_or_default());
772            Kind::AlnumEdited { edit: edits.len() as u32 - 1 }
773        }
774        (Category::Numeric, Usage::Display) => Kind::Zoned { digits: pic.digits, scale: pic.scale, signed: pic.signed, sign: sign.filter(|_| pic.signed) },
775        (Category::Numeric, Usage::Packed) => Kind::Packed { digits: pic.digits, scale: pic.scale, signed: pic.signed },
776        (Category::Numeric, Usage::Binary | Usage::NativeBinary | Usage::CompX) if pic.digits <= 18 => {
777            let native = match usage {
778                Usage::NativeBinary => Native::Comp5,
779                Usage::CompX => Native::CompX,
780                _ => Native::No,
781            };
782            Kind::Binary { digits: pic.digits, scale: pic.scale, signed: pic.signed, native }
783        }
784        (Category::Numeric, Usage::Binary | Usage::NativeBinary | Usage::CompX) => return Err(err(syntax::messages::IWC0241, "a binary item holds at most 18 digits".into())),
785        (Category::Alphanumeric, Usage::CompX | Usage::NativeBinary) if pic.size <= 8 => {
786            let native = if usage == Usage::CompX { Native::CompX } else { Native::Comp5Bytes };
787            Kind::Binary { digits: u32::from(numeric::binary::alphanumeric_digits(pic.size)), scale: 0, signed: false, native }
788        }
789        (Category::Alphanumeric, Usage::CompX | Usage::NativeBinary) => {
790            let usage = if usage == Usage::CompX { "COMP-X" } else { "COMP-5" };
791            return Err(err(syntax::messages::IWC0303, format!("PIC X({}) {usage}: {} bytes of binary, and ironwork's binary items hold at most eight", pic.size, pic.size)));
792        }
793        (Category::Alphanumeric, Usage::Display) => Kind::Alnum { justified: e.justified },
794        (Category::Dbcs | Category::National, Usage::Dbcs) => {
795            if pic.edit.is_some() && e.justified {
796                return Err(err(syntax::messages::IWC0242, "JUSTIFIED cannot be given for a DBCS item whose PICTURE has B".into()));
797            }
798            let edit = pic.edit.clone().map(|syms| {
799                edits.push(syms);
800                edits.len() as u32 - 1
801            });
802            Kind::Dbcs { justified: e.justified, edit }
803        }
804        (Category::Dbcs, _) => return Err(err(syntax::messages::IWC0243, "a PICTURE with G needs USAGE DISPLAY-1 (Language Reference SC27-8713-03, p. 214)".into())),
805        (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())),
806        (Category::National, Usage::Display | Usage::National) => Kind::National,
807        (category, usage) => return Err(err(syntax::messages::IWR0056, format!("a {category:?} PICTURE with USAGE {usage:?} is not supported yet"))),
808    };
809    if e.sign.is_some() && matches!(k, Kind::Zoned { signed: false, .. }) {
810        return Err(err(syntax::messages::IWC0244, "a SIGN clause needs an S in the PICTURE".into()));
811    }
812    Ok(k)
813}
814
815fn elementary_size(item: &Item, e_size: Option<u32>) -> u32 {
816    match item.kind {
817        Kind::Group => 0,
818        Kind::Alnum { .. } | Kind::NumericEdited { .. } | Kind::AlnumEdited { .. } => e_size.unwrap_or(0),
819        Kind::National | Kind::Dbcs { .. } => 2 * e_size.unwrap_or(0),
820        Kind::Zoned { digits, sign, .. } => digits + sign.is_some_and(|s| s.separate) as u32,
821        Kind::Packed { digits, .. } => digits / 2 + 1,
822        Kind::Binary { digits, signed, native, .. } => numeric::binary::Binary { digits: digits as u8, signed, native }.bytes() as u32,
823        Kind::Float(p) => p.bytes() as u32,
824        Kind::Pointer | Kind::Index | Kind::ObjectReference | Kind::ProgramPointer => 4,
825    }
826}
827
828/// Sizes the item and its descendants; children's offsets are relative to their parent here.
829/// `base` is where the item starts in its record, the 01 level on a doubleword, which is what a
830/// SYNCHRONIZED item's boundary is reckoned from (Language Reference SC27-8713-03, pp. 233-235).
831fn measure(items: &mut [Item], aligns: &[u32], index: usize, base: u32) -> Result<(), Error> {
832    if items[index].kind != Kind::Group {
833        return Ok(());
834    }
835    let children = items[index].children.clone();
836    let (mut cursor, mut extent) = (0u32, 0u32);
837    let mut placed: Vec<(Option<String>, u32)> = Vec::new();
838    let mut previous: Option<usize> = None;
839    for c in children {
840        let redefined = match items[c].redefines.clone() {
841            Some(target) => Some(placed.iter().rev().find(|(n, _)| n.as_deref() == Some(target.as_str())).map(|&(_, o)| o).ok_or_else(|| {
842                syntax::messages::IWC0044.at(items[c].pos, format!("REDEFINES {target}: no earlier item of that name at this level"))
843            })?),
844            None => None,
845        };
846        let start = redefined.unwrap_or(cursor);
847        let m = first_alignment(items, aligns, c);
848        let slack = (m - (base + start) % m) % m;
849        if slack > 0 && redefined.is_some() {
850            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")));
851        }
852        if slack > 0 {
853            give_slack(items, previous, cursor, slack);
854        }
855        let offset = start + slack;
856        measure(items, aligns, c, base + offset)?;
857        if items[c].table {
858            let m = widest_alignment(items, aligns, c);
859            items[c].size = items[c].size.div_ceil(m) * m;
860        }
861        let too_large = || syntax::messages::IWL0006.at(items[c].pos, format!("an item larger than the interpreter's {MAX_STORAGE} bytes"));
862        let span = items[c].size.checked_mul(items[c].occurs).filter(|&s| s <= MAX_STORAGE).ok_or_else(too_large)?;
863        if redefined.is_none() {
864            cursor = offset.checked_add(span).filter(|&s| s <= MAX_STORAGE).ok_or_else(too_large)?;
865        }
866        extent = extent.max(offset + span);
867        items[c].offset = offset;
868        placed.push((items[c].name.clone(), offset));
869        previous = Some(c);
870    }
871    items[index].size = cursor.max(extent);
872    Ok(())
873}
874
875/// The boundary the first elementary item within `index` is aligned on.
876fn first_alignment(items: &[Item], aligns: &[u32], index: usize) -> u32 {
877    match items[index].children.first() {
878        Some(&c) if items[index].kind == Kind::Group => first_alignment(items, aligns, c),
879        _ => aligns[index],
880    }
881}
882
883/// The widest boundary of any elementary item within `index`, to which each occurrence of a table
884/// is padded so that every occurrence aligns as the first does.
885fn widest_alignment(items: &[Item], aligns: &[u32], index: usize) -> u32 {
886    items[index].children.iter().map(|&c| widest_alignment(items, aligns, c)).fold(aligns[index], u32::max)
887}
888
889/// Slack bytes before a SYNCHRONIZED item belong at the level of the elementary item before it
890/// (Language Reference SC27-8713-03, p. 234): so they lengthen the group sibling that ends where
891/// they start, and that group's last group, down to the elementary item's own group. A table or
892/// a redefinition keeps its length and leaves them to the group it is in.
893fn give_slack(items: &mut [Item], previous: Option<usize>, cursor: u32, slack: u32) {
894    let Some(mut g) = previous else { return };
895    let mut end = cursor;
896    while items[g].kind == Kind::Group && !items[g].table && items[g].redefines.is_none() && items[g].offset + items[g].size == end {
897        end = items[g].size;
898        items[g].size += slack;
899        match items[g].children.last() {
900            Some(&c) => g = c,
901            None => return,
902        }
903    }
904}
905
906fn place(items: &mut [Item], index: usize, offset: u32, mut dims: Vec<(u32, u32)>) {
907    items[index].offset = offset;
908    if items[index].table {
909        dims.push((items[index].size, items[index].occurs));
910    }
911    items[index].dims = dims.clone();
912    for c in items[index].children.clone() {
913        let relative = items[c].offset;
914        place(items, c, offset + relative, dims.clone());
915    }
916}
917
918impl Item {
919    /// The index a SEARCH of this table steps: VARYING's index-name when it is one of the table's
920    /// own, else the table's first index-name, else VARYING's item (Language Reference
921    /// SC27-8713-03, p. 437).
922    pub fn search_index(&self, varying: Option<&Ref>, pos: Pos) -> Option<Ref> {
923        match (varying.filter(|v| self.index_names.contains(&v.name)), self.index_names.first()) {
924            (Some(own), _) => Some(own.clone()),
925            (None, Some(name)) => Some(Ref { name: name.clone(), qualifiers: Vec::new(), subscripts: Vec::new(), refmod: None, pos }),
926            (None, None) => varying.cloned(),
927        }
928    }
929}
930
931impl Layout {
932    /// Where item `index`, at its first occurrence, lies within its outermost table, and that
933    /// table's bytes with every OCCURS at its maximum: the region SSRANGE checks a reference's
934    /// address against (Programming Guide SC27-8714-03, p. 411). None for an item in no table.
935    pub fn table_range(&self, index: usize) -> Option<(u32, u32)> {
936        let &(stride, count) = self.items[index].dims.first()?;
937        let mut table = index;
938        while let Some(p) = self.items[table].parent.filter(|&p| !self.items[p].dims.is_empty()) {
939            table = p;
940        }
941        Some((self.items[index].offset - self.items[table].offset, stride * count))
942    }
943
944    /// The characters of class-name `name`, one bit per byte value.
945    pub fn class(&self, name: &str) -> Option<[u8; 32]> {
946        self.classes.iter().find(|(n, _)| n == name).map(|&(_, bits)| bits)
947    }
948
949    /// An elementary item's category as INITIALIZE's phrases name it, a floating-point item's as
950    /// NUMERIC (assumption [`numeric::assumptions::INITIALIZE_FLOAT_NUMERIC`]); None for a group
951    /// and for a pointer, index or object reference.
952    pub fn category(&self, i: usize) -> Option<syntax::ast::DataCategory> {
953        use syntax::ast::DataCategory;
954        let item = &self.items[i];
955        Some(match item.kind {
956            Kind::Alnum { .. } if item.alphabetic => DataCategory::Alphabetic,
957            Kind::Alnum { .. } => DataCategory::Alphanumeric,
958            Kind::AlnumEdited { .. } => DataCategory::AlphanumericEdited,
959            Kind::National => DataCategory::National,
960            Kind::Dbcs { .. } => DataCategory::Dbcs,
961            Kind::NumericEdited { .. } => DataCategory::NumericEdited,
962            Kind::Zoned { .. } | Kind::Packed { .. } | Kind::Binary { .. } | Kind::Float(_) => DataCategory::Numeric,
963            Kind::Group | Kind::Pointer | Kind::Index | Kind::ObjectReference | Kind::ProgramPointer => return None,
964        })
965    }
966
967    /// The category of the elementary item reference modification makes of `item` of `kind`, or of
968    /// a special register of `kind` when there is no item: national when `kind` is, alphabetic for
969    /// an alphabetic item, and alphanumeric otherwise (Language Reference SC27-8713-03, p. 76;
970    /// assumption [`numeric::assumptions::INITIALIZE_REFERENCE_MODIFIED`]).
971    pub fn refmod_category(&self, item: Option<usize>, kind: Kind) -> syntax::ast::DataCategory {
972        use syntax::ast::DataCategory;
973        match item.and_then(|i| self.category(i)) {
974            Some(DataCategory::Alphabetic) => DataCategory::Alphabetic,
975            _ if kind == Kind::National => DataCategory::National,
976            _ => DataCategory::Alphanumeric,
977        }
978    }
979
980    /// LENGTH OF a table element written without subscripts gives one occurrence's length (Language
981    /// Reference SC27-8713-03, p. 23): the reference with each subscript 1.
982    pub fn length_of_ref<'r>(&self, r: &'r Ref) -> std::borrow::Cow<'r, Ref> {
983        use syntax::ast::{Expr, Operand};
984        match self.resolve(&r.name, &r.qualifiers, r.pos) {
985            Ok(Resolved::Item(i)) if r.subscripts.is_empty() && !self.items[i].dims.is_empty() => {
986                let one = Expr::Operand(Operand::Literal(Literal::Number("1".into())));
987                std::borrow::Cow::Owned(Ref { subscripts: vec![one; self.items[i].dims.len()], ..r.clone() })
988            }
989            _ => std::borrow::Cow::Borrowed(r),
990        }
991    }
992
993    /// The elementary items INITIALIZE of item `i` may move to, each occurrence with its offset from
994    /// `i`'s start (Language Reference SC27-8713-03, p. 352, rules 1a and 1b): none under a
995    /// REDEFINES, no FILLER unless `filler`, and no index, object reference or program pointer.
996    pub fn initialize_receivers(&self, i: usize, filler: bool) -> Vec<(usize, u32)> {
997        let mut out = Vec::new();
998        self.receivers_under(i, 0, filler, &mut out);
999        out
1000    }
1001
1002    fn receivers_under(&self, i: usize, offset: u32, filler: bool, out: &mut Vec<(usize, u32)>) {
1003        let item = &self.items[i];
1004        match item.kind {
1005            Kind::Index | Kind::ObjectReference | Kind::ProgramPointer => {}
1006            Kind::Group => {
1007                for &c in &item.children {
1008                    let child = &self.items[c];
1009                    if child.redefines.is_some() || child.name.is_none() && child.kind != Kind::Group && !filler {
1010                        continue;
1011                    }
1012                    for k in 0..child.occurs {
1013                        self.receivers_under(c, offset + (child.offset - item.offset) + k * child.size, filler, out);
1014                    }
1015                }
1016            }
1017            _ => out.push((i, offset)),
1018        }
1019    }
1020
1021    /// Names the files, so that a file-name qualifies its records and its LINAGE-COUNTER.
1022    pub fn name_files(&mut self, files: &[FileDecl], linage_counters: Vec<Option<usize>>) {
1023        self.file_names = files.iter().map(|f| f.name.clone()).collect();
1024        self.linage_counters = linage_counters;
1025    }
1026
1027    /// How many programs out the program declaring a name is: 0 for this program's own.
1028    fn depth(&self, r: Resolved) -> u8 {
1029        let mut i = match r {
1030            Resolved::Item(i) => i,
1031            Resolved::Condition(c) => self.conditions[c].item,
1032        };
1033        while let Some(p) = self.items[i].parent {
1034            i = p;
1035        }
1036        self.items[i].linkage.and_then(|l| self.depths.get(l as usize)).copied().unwrap_or_default()
1037    }
1038
1039    /// Whether LINKAGE record `ordinal` is one an argument or SET ADDRESS OF addresses.
1040    pub fn is_argument(&self, ordinal: usize) -> bool {
1041        self.bindings.get(ordinal).is_none_or(|b| *b == Binding::Argument)
1042    }
1043
1044    /// The file whose name qualifies item `i`: the file of its record, or the one it is the
1045    /// LINAGE-COUNTER of.
1046    fn file_qualifying(&self, mut i: usize) -> Option<&str> {
1047        while let Some(p) = self.items[i].parent {
1048            i = p;
1049        }
1050        let k = self.items[i].file.map(usize::from).or_else(|| self.linage_counters.iter().position(|&c| c == Some(i)))?;
1051        self.file_names.get(k).map(String::as_str)
1052    }
1053
1054    /// Whether `qualifiers` are the complete set of a candidate: every name of its hierarchy, the
1055    /// file-name of its FD or SD allowed last but not needed (assumption
1056    /// [`numeric::assumptions::COMPLETE_SET_OF_QUALIFIERS`]).
1057    fn complete(&self, candidate: Resolved, qualifiers: &[String]) -> bool {
1058        let (start, own) = match candidate {
1059            Resolved::Item(i) => (self.items[i].parent, i),
1060            Resolved::Condition(c) => (Some(self.conditions[c].item), self.conditions[c].item),
1061        };
1062        let given = || qualifiers.iter().map(String::as_str);
1063        given().eq(names_from(&self.items, start)) || given().eq(names_from(&self.items, start).chain(self.file_qualifying(own)))
1064    }
1065
1066    /// A data item, condition-name or LINAGE-COUNTER named with its qualifiers, the last of which
1067    /// may be the file-name of an FD or SD (Language Reference SC27-8713-03, pp. 69-70). Under
1068    /// QUALIFY(EXTEND) a reference the standard's rules find ambiguous names the one candidate it
1069    /// gives a complete set of qualifiers, if only one (Programming Guide SC27-8714-03, p. 400;
1070    /// Language Reference SC27-8713-03, pp. 67-68).
1071    pub fn resolve(&self, name: &str, qualifiers: &[String], pos: Pos) -> Result<Resolved, Error> {
1072        let within = |mut at: Option<usize>, own: usize| {
1073            let mut wanted = qualifiers.iter();
1074            let mut next = wanted.next();
1075            while let (Some(q), Some(i)) = (next, at) {
1076                if self.items[i].name.as_deref() == Some(q.as_str()) {
1077                    next = wanted.next();
1078                }
1079                at = self.items[i].parent;
1080            }
1081            match next {
1082                None => true,
1083                Some(file) => wanted.next().is_none() && self.file_qualifying(own) == Some(file.as_str()),
1084            }
1085        };
1086        fn candidates<'a>(named: &'a HashMap<String, Vec<usize>>, name: &str) -> &'a [usize] {
1087            named.get(name).map_or(&[], Vec::as_slice)
1088        }
1089        let mut found: Vec<Resolved> = candidates(&self.named_items, name).iter().filter(|&&i| within(self.items[i].parent, i)).map(|&i| Resolved::Item(i)).collect();
1090        found.extend(candidates(&self.named_conditions, name).iter().filter(|&&c| within(Some(self.conditions[c].item), self.conditions[c].item)).map(|&c| Resolved::Condition(c)));
1091        if found.len() > 1 {
1092            let nearest = found.iter().map(|&r| self.depth(r)).min().unwrap_or_default();
1093            found.retain(|&r| self.depth(r) == nearest);
1094        }
1095        if found.len() > 1 && self.qualify == Qualify::Extend {
1096            let complete: Vec<Resolved> = found.iter().copied().filter(|&r| self.complete(r, qualifiers)).collect();
1097            if let [one] = complete.as_slice() {
1098                return Ok(*one);
1099            }
1100        }
1101        match found.as_slice() {
1102            [one] => Ok(*one),
1103            [] => Err(IWC0001.at(pos, format!("{name} is not defined"))),
1104            _ => Err(IWC0002.at(pos, format!("{name} is ambiguous; qualify it with OF or IN"))),
1105        }
1106    }
1107}
1108
1109/// The file whose record area each file uses: its own, or the first file of its SAME RECORD AREA
1110/// clause. SAME AREA shares the record area of the VSAM (indexed and relative) files it names and
1111/// is documentation for the rest: SAME_AREA_VSAM in numeric::assumptions.
1112pub fn record_area_owners(files: &[FileDecl], environment: &Environment) -> Result<Vec<usize>, Error> {
1113    let mut owner: Vec<usize> = (0..files.len()).collect();
1114    let root = |owner: &[usize], mut k: usize| {
1115        while owner[k] != k {
1116            k = owner[k];
1117        }
1118        k
1119    };
1120    let clauses = environment.same_record_areas.iter().map(|c| ("SAME RECORD AREA", c)).chain(environment.same_areas.iter().map(|c| ("SAME AREA", c)));
1121    for (clause, names) in clauses {
1122        let mut members = Vec::new();
1123        for name in names {
1124            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")))?;
1125            if clause == "SAME RECORD AREA" || matches!(files[k].organization, Organization::Indexed | Organization::Relative) {
1126                members.push(k);
1127            }
1128        }
1129        for pair in members.windows(2) {
1130            let (a, b) = (root(&owner, pair[0]), root(&owner, pair[1]));
1131            owner[a.max(b)] = a.min(b);
1132        }
1133    }
1134    Ok((0..files.len()).map(|k| root(&owner, k)).collect())
1135}