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}
93
94/// The RELATIVE KEY: read as `value`, stored by `store` when a sequential READ or WRITE sets it,
95/// and `digits` the integer digits that bound the record numbers it can hold (None: no bound).
96#[derive(Clone, Debug, PartialEq, Eq)]
97pub struct RelativeKey {
98    pub place: PlaceId,
99    pub value: IntExpr,
100    pub store: StorePlan,
101    pub digits: Option<u32>,
102}
103
104/// LINAGE: each value evaluated, in this order, whenever the page's geometry is taken (at OPEN
105/// OUTPUT or EXTEND and at each new page), and LINAGE-COUNTER with its store.
106#[derive(Clone, Debug, PartialEq, Eq)]
107pub struct Linage {
108    pub lines: IntExpr,
109    pub footing: Option<IntExpr>,
110    pub top: Option<IntExpr>,
111    pub bottom: Option<IntExpr>,
112    pub counter: Option<(PlaceId, StorePlan)>,
113}
114
115/// `machine`: machine codes rather than ASA characters; `reserved`: the character is the record's
116/// own first byte (NOADV) rather than one added before it.
117#[derive(Clone, Copy, Debug, PartialEq, Eq)]
118pub struct Carriage {
119    pub machine: bool,
120    pub reserved: bool,
121}
122
123/// One file statement on one file. `phrase` is AT END or INVALID KEY and its NOT phrase, as
124/// written; `end_of_page` WRITE's END-OF-PAGE and NOT END-OF-PAGE. With neither the op returns
125/// Next; otherwise it returns Arm(1) for the ON phrase, Arm(2) for NOT ON, Arm(3) for END-OF-PAGE,
126/// Arm(4) for NOT END-OF-PAGE, and Arm(0) when no phrase written runs, and a `Select` of
127/// [`FileOp::arms`] blocks follows it.
128#[derive(Clone, Debug, PartialEq, Eq)]
129pub struct FileOp {
130    pub file: u16,
131    pub verb: FileVerb,
132    pub phrase: Option<Phrase>,
133    pub end_of_page: Option<Phrase>,
134}
135
136impl FileOp {
137    /// The blocks the `Select` after the op has: none, 3, or 5 with END-OF-PAGE.
138    pub fn arms(&self) -> usize {
139        match (self.phrase, self.end_of_page) {
140            (None, None) => 0,
141            (_, None) => 3,
142            (_, Some(_)) => 5,
143        }
144    }
145}
146
147#[derive(Clone, Copy, Debug, PartialEq, Eq)]
148pub struct Phrase {
149    pub on: bool,
150    pub not_on: bool,
151}
152
153#[derive(Clone, Debug, PartialEq, Eq)]
154pub enum FileVerb {
155    Open(OpenMode),
156    Close,
157    CloseWith(Closing),
158    /// `sequential`: READ NEXT or PREVIOUS, or a file read in sequence, whose phrase is AT END;
159    /// otherwise the phrase is INVALID KEY and `key` the key of reference of an indexed file.
160    Read { sequential: bool, previous: bool, into: Option<(PlaceId, MovePlan)>, key: u8 },
161    /// `record` is located after FROM has moved into it.
162    Write { record: PlaceId, from: Option<FromMove>, advancing: Option<Advance> },
163    Rewrite { record: PlaceId, from: Option<FromMove> },
164    Delete,
165    Start { rel: StartRel, key: StartKey },
166}
167
168/// WRITE, REWRITE or RELEASE FROM: `to` is the record as a receiving item, and `check` NUMCHECK's
169/// test of `from` before it is read.
170#[derive(Clone, Copy, Debug, PartialEq, Eq)]
171pub struct FromMove {
172    pub from: Operand,
173    pub to: PlaceId,
174    pub plan: MovePlan,
175    pub check: SenderCheck,
176}
177
178/// WRITE's ADVANCING phrase. A count below zero moves as zero; PAGE is channel 1, or the next
179/// page of a LINAGE file.
180#[derive(Clone, Debug, PartialEq, Eq)]
181pub enum Advance {
182    Lines { before: bool, count: IntExpr },
183    Page { before: bool },
184    /// A mnemonic-name of SPECIAL-NAMES, as the movement its environment-name gives.
185    Mnemonic { before: bool, space: Spacing },
186}
187
188#[derive(Clone, Copy, Debug, PartialEq, Eq)]
189pub enum Spacing {
190    /// CSP: no spacing.
191    Lines(u64),
192    Channel(u8),
193    /// AFP-5A: page mode data.
194    PageMode,
195}
196
197/// START's relation: KEY =, KEY >, and KEY NOT < or >=.
198#[derive(Clone, Copy, Debug, PartialEq, Eq)]
199pub enum StartRel {
200    Equal,
201    Greater,
202    NotLess,
203}
204
205#[derive(Clone, Debug, PartialEq, Eq)]
206pub enum StartKey {
207    /// An indexed file without KEY: the prime key's bytes in the record area.
208    Prime,
209    /// KEY names key `key`, or a leading part of it: the bytes of `span`.
210    Named { key: u8, span: RecordSpan },
211    /// A relative file's KEY, read as an integer.
212    Relative(IntExpr),
213    /// A relative file without KEY: its RELATIVE KEY.
214    RelativeKey,
215}
216
217// A FileDesc's encoding is 0.5's, without `assign_item`: the LIR section's end carries the files
218// that have one (load-module.md §3.4), which keeps the change additive (§8.1).
219impl Encode for FileDesc {
220    fn encode(&self, w: &mut Writer) {
221        let FileDesc { name, assign, organization, access, optional, format, read_lengths, fixed, record_min, depending, status, keys, relative, linage, carriage, sort, error, assign_item: _ } = self;
222        name.encode(w);
223        assign.encode(w);
224        organization.encode(w);
225        access.encode(w);
226        optional.encode(w);
227        format.encode(w);
228        read_lengths.encode(w);
229        fixed.encode(w);
230        record_min.encode(w);
231        depending.encode(w);
232        status.encode(w);
233        keys.encode(w);
234        relative.encode(w);
235        linage.encode(w);
236        carriage.encode(w);
237        sort.encode(w);
238        error.encode(w);
239    }
240}
241
242impl Decode for FileDesc {
243    fn decode(r: &mut Reader<'_>) -> Result<Self, ModuleError> {
244        let at = r.position();
245        let file = FileDesc {
246            name: Decode::decode(r)?,
247            assign: Decode::decode(r)?,
248            organization: Decode::decode(r)?,
249            access: Decode::decode(r)?,
250            optional: Decode::decode(r)?,
251            format: Decode::decode(r)?,
252            read_lengths: Decode::decode(r)?,
253            fixed: Decode::decode(r)?,
254            record_min: Decode::decode(r)?,
255            depending: Decode::decode(r)?,
256            status: Decode::decode(r)?,
257            keys: Decode::decode(r)?,
258            relative: Decode::decode(r)?,
259            linage: Decode::decode(r)?,
260            carriage: Decode::decode(r)?,
261            sort: Decode::decode(r)?,
262            error: Decode::decode(r)?,
263            assign_item: None,
264        };
265        file_valid(&file).map_err(|reason| r.malformed(at, reason))?;
266        Ok(file)
267    }
268}
269codec_struct!(AssignItem { place, select });
270codec_struct!(RecordDepending { item, lengths });
271codec_enum!(Organization { Sequential = 0, LineSequential = 1, Indexed = 2, Relative = 3 });
272codec_enum!(Access { Sequential = 0, Random = 1, Dynamic = 2 });
273codec_struct!(RecordSpan { offset, len });
274codec_struct!(IndexKeys { prime, alternates });
275codec_struct!(RelativeKey { place, value, store, digits });
276codec_struct!(Linage { lines, footing, top, bottom, counter });
277codec_struct!(Carriage { machine, reserved });
278codec_struct!(FileOp { file, verb, phrase, end_of_page });
279codec_struct!(Phrase { on, not_on });
280codec_enum!(FileVerb {
281    Open(mode) = 0,
282    Close = 1,
283    Read { sequential, previous, into, key } = 2,
284    Write { record, from, advancing } = 3,
285    Rewrite { record, from } = 4,
286    Delete = 5,
287    Start { rel, key } = 6,
288    CloseWith(closing) = 7,
289});
290codec_struct!(FromMove { from, to, plan, check });
291codec_enum!(Advance { Lines { before, count } = 0, Page { before } = 1, Mnemonic { before, space } = 2 });
292codec_enum!(Spacing { Lines(n) = 0, Channel(c) = 1, PageMode = 2 });
293codec_enum!(StartRel { Equal = 0, Greater = 1, NotLess = 2 });
294codec_enum!(StartKey { Prime = 0, Named { key, span } = 1, Relative(value) = 2, RelativeKey = 3 });
295
296/// Only an indexed file has keys.
297fn file_valid(file: &FileDesc) -> Result<(), String> {
298    if file.keys.is_some() == (file.organization == Organization::Indexed) {
299        Ok(())
300    } else {
301        Err("keys on a file that is not indexed, or an indexed file without them".into())
302    }
303}