Skip to main content

ironwork_rt/lir/
file.rs

1//! Files (lir.md §9.4): each file's SELECT and FD, and each file statement with its phrases. The
2//! statements stay `rt::files` calls; these name the places, plans and procedures the walker finds
3//! by name on each execution.
4
5use super::{IntExpr, MovePlan, Operand, PlaceId, RangeId, SenderCheck, StorePlan, SymId};
6use crate::files::Format;
7use crate::vocab::{Closing, OpenMode, Pos};
8use crate::module::ModuleError;
9use crate::module::codec::{Decode, Encode, Reader, Writer};
10use crate::{codec_enum, codec_struct};
11
12/// A file as SELECT and FD declare it. `format` is how its records are held when its DD does not
13/// say; `status` is FILE STATUS with the MOVE its two characters take.
14#[derive(Clone, Debug, PartialEq, Eq)]
15pub struct FileDesc {
16    pub name: SymId,
17    /// The DD name ASSIGN gives.
18    pub assign: SymId,
19    pub organization: Organization,
20    pub access: Access,
21    pub optional: bool,
22    pub format: Format,
23    /// The shortest and longest variable-length record a READ takes without a record length
24    /// conflict (status 04), as the VLR option measures them (Programming Guide SC27-8714-03,
25    /// pp. 422-424).
26    pub read_lengths: (u32, u32),
27    /// No RECORDING MODE V, and its smallest record as long as its largest.
28    pub fixed: bool,
29    /// The RECORD clause's smallest record.
30    pub record_min: Option<u32>,
31    pub depending: Option<RecordDepending>,
32    pub status: Option<(PlaceId, MovePlan)>,
33    /// An indexed file's keys, as spans of its record area.
34    pub keys: Option<IndexKeys>,
35    pub relative: Option<RelativeKey>,
36    pub linage: Option<Linage>,
37    /// A print file's control character.
38    pub carriage: Option<Carriage>,
39    /// Described by SD.
40    pub sort: bool,
41    /// Its own EXCEPTION/ERROR procedure, which comes before one for its open mode.
42    pub error: Option<RangeId>,
43    /// The data item ASSIGN names, whose value is the DD name at each OPEN (`--compliance
44    /// extended`).
45    pub assign_item: Option<AssignItem>,
46}
47
48/// ASSIGN's data item, and where the SELECT is, which the input trace records the value at.
49#[derive(Clone, Copy, Debug, PartialEq, Eq)]
50pub struct AssignItem {
51    pub place: PlaceId,
52    pub select: Pos,
53}
54
55#[derive(Clone, Copy, Debug, PartialEq, Eq)]
56pub enum Organization {
57    Sequential,
58    LineSequential,
59    Indexed,
60    Relative,
61}
62
63#[derive(Clone, Copy, Debug, PartialEq, Eq)]
64pub enum Access {
65    Sequential,
66    Random,
67    Dynamic,
68}
69
70/// RECORD IS VARYING DEPENDING ON ([`crate::fileio::Depending`]): the item a successful READ or
71/// RETURN stores the record's length in and WRITE, REWRITE and RELEASE take it from, read as an
72/// integer and stored as `set_integer` stores; and the shortest and longest record the clause allows.
73#[derive(Clone, Copy, Debug, PartialEq, Eq)]
74pub struct RecordDepending {
75    pub item: PlaceId,
76    pub lengths: (u32, u32),
77}
78
79/// Bytes of a record: from `offset` in the file's record area, `len` long.
80#[derive(Clone, Copy, Debug, PartialEq, Eq)]
81pub struct RecordSpan {
82    pub offset: u32,
83    pub len: u32,
84}
85
86/// RECORD KEY, then each ALTERNATE RECORD KEY with WITH DUPLICATES; a key of reference numbers
87/// them from 0 in this order.
88#[derive(Clone, Debug, PartialEq, Eq)]
89pub struct IndexKeys {
90    pub prime: RecordSpan,
91    pub alternates: Vec<(RecordSpan, bool)>,
92    /// Micro Focus's split keys under `--compliance extended`: a key's number (0 the prime key) and
93    /// the pieces it joins, in order, its span above the first's offset and their summed length.
94    /// The LIR section carries them after its records (load-module.md §5.2).
95    pub split: Vec<(u8, Vec<RecordSpan>)>,
96}
97
98/// The RELATIVE KEY: read as `value`, stored by `store` when a sequential READ or WRITE sets it,
99/// and `digits` the integer digits that bound the record numbers it can hold (None: no bound).
100#[derive(Clone, Debug, PartialEq, Eq)]
101pub struct RelativeKey {
102    pub place: PlaceId,
103    pub value: IntExpr,
104    pub store: StorePlan,
105    pub digits: Option<u32>,
106}
107
108/// LINAGE: each value evaluated, in this order, whenever the page's geometry is taken (at OPEN
109/// OUTPUT or EXTEND and at each new page), and LINAGE-COUNTER with its store.
110#[derive(Clone, Debug, PartialEq, Eq)]
111pub struct Linage {
112    pub lines: IntExpr,
113    pub footing: Option<IntExpr>,
114    pub top: Option<IntExpr>,
115    pub bottom: Option<IntExpr>,
116    pub counter: Option<(PlaceId, StorePlan)>,
117}
118
119/// `machine`: machine codes rather than ASA characters; `reserved`: the character is the record's
120/// own first byte (NOADV) rather than one added before it.
121#[derive(Clone, Copy, Debug, PartialEq, Eq)]
122pub struct Carriage {
123    pub machine: bool,
124    pub reserved: bool,
125}
126
127/// One file statement on one file. `phrase` is AT END or INVALID KEY and its NOT phrase, as
128/// written; `end_of_page` WRITE's END-OF-PAGE and NOT END-OF-PAGE. With neither the op returns
129/// Next; otherwise it returns Arm(1) for the ON phrase, Arm(2) for NOT ON, Arm(3) for END-OF-PAGE,
130/// Arm(4) for NOT END-OF-PAGE, and Arm(0) when no phrase written runs, and a `Select` of
131/// [`FileOp::arms`] blocks follows it.
132#[derive(Clone, Debug, PartialEq, Eq)]
133pub struct FileOp {
134    pub file: u16,
135    pub verb: FileVerb,
136    pub phrase: Option<Phrase>,
137    pub end_of_page: Option<Phrase>,
138}
139
140impl FileOp {
141    /// The blocks the `Select` after the op has: none, 3, or 5 with END-OF-PAGE.
142    pub fn arms(&self) -> usize {
143        match (self.phrase, self.end_of_page) {
144            (None, None) => 0,
145            (_, None) => 3,
146            (_, Some(_)) => 5,
147        }
148    }
149}
150
151#[derive(Clone, Copy, Debug, PartialEq, Eq)]
152pub struct Phrase {
153    pub on: bool,
154    pub not_on: bool,
155}
156
157#[derive(Clone, Debug, PartialEq, Eq)]
158pub enum FileVerb {
159    Open(OpenMode),
160    Close,
161    CloseWith(Closing),
162    /// `sequential`: READ NEXT or PREVIOUS, or a file read in sequence, whose phrase is AT END;
163    /// otherwise the phrase is INVALID KEY and `key` the key of reference of an indexed file.
164    Read { sequential: bool, previous: bool, into: Option<(PlaceId, MovePlan)>, key: u8 },
165    /// `record` is located after FROM has moved into it.
166    Write { record: PlaceId, from: Option<FromMove>, advancing: Option<Advance> },
167    Rewrite { record: PlaceId, from: Option<FromMove> },
168    Delete,
169    Start { rel: StartRel, key: StartKey },
170    /// Micro Focus's and GnuCOBOL's DELETE FILE under `--compliance extended`.
171    DeleteFile,
172}
173
174/// WRITE, REWRITE or RELEASE FROM: `to` is the record as a receiving item, and `check` NUMCHECK's
175/// test of `from` before it is read.
176#[derive(Clone, Copy, Debug, PartialEq, Eq)]
177pub struct FromMove {
178    pub from: Operand,
179    pub to: PlaceId,
180    pub plan: MovePlan,
181    pub check: SenderCheck,
182}
183
184/// WRITE's ADVANCING phrase. A count below zero moves as zero; PAGE is channel 1, or the next
185/// page of a LINAGE file.
186#[derive(Clone, Debug, PartialEq, Eq)]
187pub enum Advance {
188    Lines { before: bool, count: IntExpr },
189    Page { before: bool },
190    /// A mnemonic-name of SPECIAL-NAMES, as the movement its environment-name gives.
191    Mnemonic { before: bool, space: Spacing },
192}
193
194#[derive(Clone, Copy, Debug, PartialEq, Eq)]
195pub enum Spacing {
196    /// CSP: no spacing.
197    Lines(u64),
198    Channel(u8),
199    /// AFP-5A: page mode data.
200    PageMode,
201}
202
203/// START's relation: KEY =, KEY >, and KEY NOT < or >=; and under `--compliance extended` KEY <,
204/// and KEY NOT > or <=, which position at the last record they find.
205#[derive(Clone, Copy, Debug, PartialEq, Eq)]
206pub enum StartRel {
207    Equal,
208    Greater,
209    NotLess,
210    Less,
211    NotGreater,
212}
213
214#[derive(Clone, Debug, PartialEq, Eq)]
215pub enum StartKey {
216    /// An indexed file without KEY: the prime key's bytes in the record area.
217    Prime,
218    /// KEY names key `key`, or a leading part of it: the bytes of `span`.
219    Named { key: u8, span: RecordSpan },
220    /// A relative file's KEY, read as an integer.
221    Relative(IntExpr),
222    /// A relative file without KEY: its RELATIVE KEY.
223    RelativeKey,
224}
225
226// A FileDesc's encoding is 0.5's, without `assign_item`: the LIR section's end carries the files
227// that have one (load-module.md §3.4), which keeps the change additive (§8.1).
228impl Encode for FileDesc {
229    fn encode(&self, w: &mut Writer) {
230        let FileDesc { name, assign, organization, access, optional, format, read_lengths, fixed, record_min, depending, status, keys, relative, linage, carriage, sort, error, assign_item: _ } = self;
231        name.encode(w);
232        assign.encode(w);
233        organization.encode(w);
234        access.encode(w);
235        optional.encode(w);
236        format.encode(w);
237        read_lengths.encode(w);
238        fixed.encode(w);
239        record_min.encode(w);
240        depending.encode(w);
241        status.encode(w);
242        keys.encode(w);
243        relative.encode(w);
244        linage.encode(w);
245        carriage.encode(w);
246        sort.encode(w);
247        error.encode(w);
248    }
249}
250
251impl Decode for FileDesc {
252    fn decode(r: &mut Reader<'_>) -> Result<Self, ModuleError> {
253        let at = r.position();
254        let file = FileDesc {
255            name: Decode::decode(r)?,
256            assign: Decode::decode(r)?,
257            organization: Decode::decode(r)?,
258            access: Decode::decode(r)?,
259            optional: Decode::decode(r)?,
260            format: Decode::decode(r)?,
261            read_lengths: Decode::decode(r)?,
262            fixed: Decode::decode(r)?,
263            record_min: Decode::decode(r)?,
264            depending: Decode::decode(r)?,
265            status: Decode::decode(r)?,
266            keys: Decode::decode(r)?,
267            relative: Decode::decode(r)?,
268            linage: Decode::decode(r)?,
269            carriage: Decode::decode(r)?,
270            sort: Decode::decode(r)?,
271            error: Decode::decode(r)?,
272            assign_item: None,
273        };
274        file_valid(&file).map_err(|reason| r.malformed(at, reason))?;
275        Ok(file)
276    }
277}
278codec_struct!(AssignItem { place, select });
279codec_struct!(RecordDepending { item, lengths });
280codec_enum!(Organization { Sequential = 0, LineSequential = 1, Indexed = 2, Relative = 3 });
281codec_enum!(Access { Sequential = 0, Random = 1, Dynamic = 2 });
282codec_struct!(RecordSpan { offset, len });
283codec_struct!(IndexKeys { prime, alternates } default { split } check crate::module::codec::unchecked);
284codec_struct!(RelativeKey { place, value, store, digits });
285codec_struct!(Linage { lines, footing, top, bottom, counter });
286codec_struct!(Carriage { machine, reserved });
287codec_struct!(FileOp { file, verb, phrase, end_of_page });
288codec_struct!(Phrase { on, not_on });
289codec_enum!(FileVerb {
290    Open(mode) = 0,
291    Close = 1,
292    Read { sequential, previous, into, key } = 2,
293    Write { record, from, advancing } = 3,
294    Rewrite { record, from } = 4,
295    Delete = 5,
296    Start { rel, key } = 6,
297    CloseWith(closing) = 7,
298    DeleteFile = 8,
299});
300codec_struct!(FromMove { from, to, plan, check });
301codec_enum!(Advance { Lines { before, count } = 0, Page { before } = 1, Mnemonic { before, space } = 2 });
302codec_enum!(Spacing { Lines(n) = 0, Channel(c) = 1, PageMode = 2 });
303codec_enum!(StartRel { Equal = 0, Greater = 1, NotLess = 2, Less = 3, NotGreater = 4 });
304codec_enum!(StartKey { Prime = 0, Named { key, span } = 1, Relative(value) = 2, RelativeKey = 3 });
305
306/// Only an indexed file has keys.
307fn file_valid(file: &FileDesc) -> Result<(), String> {
308    if file.keys.is_some() == (file.organization == Organization::Indexed) {
309        Ok(())
310    } else {
311        Err("keys on a file that is not indexed, or an indexed file without them".into())
312    }
313}