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, StorePlan, SymId};
6use crate::files::Format;
7use crate::vocab::{Closing, OpenMode};
8use crate::{codec_enum, codec_struct};
9
10/// A file as SELECT and FD declare it. `format` is how its records are held when its DD does not
11/// say; `status` is FILE STATUS with the MOVE its two characters take.
12#[derive(Clone, Debug, PartialEq, Eq)]
13pub struct FileDesc {
14    pub name: SymId,
15    /// The DD name ASSIGN gives.
16    pub assign: SymId,
17    pub organization: Organization,
18    pub access: Access,
19    pub optional: bool,
20    pub format: Format,
21    /// The shortest and longest variable-length record a READ takes without a record length
22    /// conflict (status 04), as the VLR option measures them (Programming Guide SC27-8714-03,
23    /// pp. 422-424).
24    pub read_lengths: (u32, u32),
25    pub status: Option<(PlaceId, MovePlan)>,
26    /// An indexed file's keys, as spans of its record area.
27    pub keys: Option<IndexKeys>,
28    pub relative: Option<RelativeKey>,
29    pub linage: Option<Linage>,
30    /// A print file's control character.
31    pub carriage: Option<Carriage>,
32    /// Described by SD.
33    pub sort: bool,
34    /// Its own EXCEPTION/ERROR procedure, which comes before one for its open mode.
35    pub error: Option<RangeId>,
36}
37
38#[derive(Clone, Copy, Debug, PartialEq, Eq)]
39pub enum Organization {
40    Sequential,
41    LineSequential,
42    Indexed,
43    Relative,
44}
45
46#[derive(Clone, Copy, Debug, PartialEq, Eq)]
47pub enum Access {
48    Sequential,
49    Random,
50    Dynamic,
51}
52
53/// Bytes of a record: from `offset` in the file's record area, `len` long.
54#[derive(Clone, Copy, Debug, PartialEq, Eq)]
55pub struct RecordSpan {
56    pub offset: u32,
57    pub len: u32,
58}
59
60/// RECORD KEY, then each ALTERNATE RECORD KEY with WITH DUPLICATES; a key of reference numbers
61/// them from 0 in this order.
62#[derive(Clone, Debug, PartialEq, Eq)]
63pub struct IndexKeys {
64    pub prime: RecordSpan,
65    pub alternates: Vec<(RecordSpan, bool)>,
66}
67
68/// The RELATIVE KEY: read as `value`, stored by `store` when a sequential READ or WRITE sets it,
69/// and `digits` the integer digits that bound the record numbers it can hold (None: no bound).
70#[derive(Clone, Debug, PartialEq, Eq)]
71pub struct RelativeKey {
72    pub place: PlaceId,
73    pub value: IntExpr,
74    pub store: StorePlan,
75    pub digits: Option<u32>,
76}
77
78/// LINAGE: each value evaluated, in this order, whenever the page's geometry is taken (at OPEN
79/// OUTPUT or EXTEND and at each new page), and LINAGE-COUNTER with its store.
80#[derive(Clone, Debug, PartialEq, Eq)]
81pub struct Linage {
82    pub lines: IntExpr,
83    pub footing: Option<IntExpr>,
84    pub top: Option<IntExpr>,
85    pub bottom: Option<IntExpr>,
86    pub counter: Option<(PlaceId, StorePlan)>,
87}
88
89/// `machine`: machine codes rather than ASA characters; `reserved`: the character is the record's
90/// own first byte (NOADV) rather than one added before it.
91#[derive(Clone, Copy, Debug, PartialEq, Eq)]
92pub struct Carriage {
93    pub machine: bool,
94    pub reserved: bool,
95}
96
97/// One file statement on one file. `phrase` is AT END or INVALID KEY and its NOT phrase, as
98/// written; `end_of_page` WRITE's END-OF-PAGE and NOT END-OF-PAGE. With neither the op returns
99/// Next; otherwise it returns Arm(1) for the ON phrase, Arm(2) for NOT ON, Arm(3) for END-OF-PAGE,
100/// Arm(4) for NOT END-OF-PAGE, and Arm(0) when no phrase written runs, and a `Select` of
101/// [`FileOp::arms`] blocks follows it.
102#[derive(Clone, Debug, PartialEq, Eq)]
103pub struct FileOp {
104    pub file: u16,
105    pub verb: FileVerb,
106    pub phrase: Option<Phrase>,
107    pub end_of_page: Option<Phrase>,
108}
109
110impl FileOp {
111    /// The blocks the `Select` after the op has: none, 3, or 5 with END-OF-PAGE.
112    pub fn arms(&self) -> usize {
113        match (self.phrase, self.end_of_page) {
114            (None, None) => 0,
115            (_, None) => 3,
116            (_, Some(_)) => 5,
117        }
118    }
119}
120
121#[derive(Clone, Copy, Debug, PartialEq, Eq)]
122pub struct Phrase {
123    pub on: bool,
124    pub not_on: bool,
125}
126
127#[derive(Clone, Debug, PartialEq, Eq)]
128pub enum FileVerb {
129    Open(OpenMode),
130    Close,
131    CloseWith(Closing),
132    /// `sequential`: READ NEXT or PREVIOUS, or a file read in sequence, whose phrase is AT END;
133    /// otherwise the phrase is INVALID KEY and `key` the key of reference of an indexed file.
134    Read { sequential: bool, previous: bool, into: Option<(PlaceId, MovePlan)>, key: u8 },
135    /// `record` is located after FROM has moved into it.
136    Write { record: PlaceId, from: Option<FromMove>, advancing: Option<Advance> },
137    Rewrite { record: PlaceId, from: Option<FromMove> },
138    Delete,
139    Start { rel: StartRel, key: StartKey },
140}
141
142/// WRITE or REWRITE FROM: `to` is the record as a receiving item.
143#[derive(Clone, Copy, Debug, PartialEq, Eq)]
144pub struct FromMove {
145    pub from: Operand,
146    pub to: PlaceId,
147    pub plan: MovePlan,
148}
149
150/// WRITE's ADVANCING phrase. A count below zero moves as zero; PAGE is channel 1, or the next
151/// page of a LINAGE file.
152#[derive(Clone, Debug, PartialEq, Eq)]
153pub enum Advance {
154    Lines { before: bool, count: IntExpr },
155    Page { before: bool },
156    /// A mnemonic-name of SPECIAL-NAMES, as the movement its environment-name gives.
157    Mnemonic { before: bool, space: Spacing },
158}
159
160#[derive(Clone, Copy, Debug, PartialEq, Eq)]
161pub enum Spacing {
162    /// CSP: no spacing.
163    Lines(u64),
164    Channel(u8),
165    /// AFP-5A: page mode data.
166    PageMode,
167}
168
169/// START's relation: KEY =, KEY >, and KEY NOT < or >=.
170#[derive(Clone, Copy, Debug, PartialEq, Eq)]
171pub enum StartRel {
172    Equal,
173    Greater,
174    NotLess,
175}
176
177#[derive(Clone, Debug, PartialEq, Eq)]
178pub enum StartKey {
179    /// An indexed file without KEY: the prime key's bytes in the record area.
180    Prime,
181    /// KEY names key `key`, or a leading part of it: the bytes of `span`.
182    Named { key: u8, span: RecordSpan },
183    /// A relative file's KEY, read as an integer.
184    Relative(IntExpr),
185    /// A relative file without KEY: its RELATIVE KEY.
186    RelativeKey,
187}
188
189codec_struct!(FileDesc {
190    name, assign, organization, access, optional, format, read_lengths, status, keys, relative, linage, carriage, sort, error,
191} check file_valid);
192codec_enum!(Organization { Sequential = 0, LineSequential = 1, Indexed = 2, Relative = 3 });
193codec_enum!(Access { Sequential = 0, Random = 1, Dynamic = 2 });
194codec_struct!(RecordSpan { offset, len });
195codec_struct!(IndexKeys { prime, alternates });
196codec_struct!(RelativeKey { place, value, store, digits });
197codec_struct!(Linage { lines, footing, top, bottom, counter });
198codec_struct!(Carriage { machine, reserved });
199codec_struct!(FileOp { file, verb, phrase, end_of_page });
200codec_struct!(Phrase { on, not_on });
201codec_enum!(FileVerb {
202    Open(mode) = 0,
203    Close = 1,
204    Read { sequential, previous, into, key } = 2,
205    Write { record, from, advancing } = 3,
206    Rewrite { record, from } = 4,
207    Delete = 5,
208    Start { rel, key } = 6,
209    CloseWith(closing) = 7,
210});
211codec_struct!(FromMove { from, to, plan });
212codec_enum!(Advance { Lines { before, count } = 0, Page { before } = 1, Mnemonic { before, space } = 2 });
213codec_enum!(Spacing { Lines(n) = 0, Channel(c) = 1, PageMode = 2 });
214codec_enum!(StartRel { Equal = 0, Greater = 1, NotLess = 2 });
215codec_enum!(StartKey { Prime = 0, Named { key, span } = 1, Relative(value) = 2, RelativeKey = 3 });
216
217/// Only an indexed file has keys.
218fn file_valid(file: &FileDesc) -> Result<(), String> {
219    if file.keys.is_some() == (file.organization == Organization::Indexed) {
220        Ok(())
221    } else {
222        Err("keys on a file that is not indexed, or an indexed file without them".into())
223    }
224}