Skip to main content

ironwork_rt/lir/
markup.rs

1//! JSON GENERATE, JSON PARSE, XML GENERATE and XML PARSE (lir.md ยง9.13): each statement's
2//! phrases, and the tree of items its walk reaches with every choice the walker makes by name or
3//! kind made once. Trees nest by index: a node's members come after it in its node table.
4
5use super::{AbendId, CondId, ConstId, Count, IntExpr, MovePlan, Operand, PlaceId, RangeId, StorePlan, SymId};
6use crate::storage::Kind;
7use crate::vocab::Figurative;
8use crate::{codec_enum, codec_struct};
9use zarch::hfp::Precision;
10
11#[derive(Clone, Debug, PartialEq, Eq)]
12pub enum Markup {
13    JsonGenerate(JsonGenerate),
14    XmlGenerate(XmlGenerate),
15    XmlParse(XmlParse),
16    JsonParse(JsonParse),
17}
18
19impl Markup {
20    /// ON EXCEPTION and NOT ON EXCEPTION as written: with either, the op returns Arm(1) for an
21    /// exception and Arm(0) otherwise, for a `Select` of two blocks.
22    pub fn phrases(&self) -> (bool, bool) {
23        match self {
24            Markup::JsonGenerate(g) => (g.on_exception, g.not_on_exception),
25            Markup::XmlGenerate(g) => (g.on_exception, g.not_on_exception),
26            Markup::XmlParse(p) => (p.on_exception, p.not_on_exception),
27            Markup::JsonParse(p) => (p.on_exception, p.not_on_exception),
28        }
29    }
30}
31
32/// The CCSID a statement's document is in: none written, the program's CODEPAGE (JSON's ENCODING
33/// FROM CODEPAGE), or an operand read as the statement starts.
34#[derive(Clone, Copy, Debug, PartialEq, Eq)]
35pub enum Ccsid {
36    Unnamed,
37    CodePage,
38    Operand(Operand),
39}
40
41/// An elementary item's value as JSON and XML GENERATE write it.
42#[derive(Clone, Copy, Debug, PartialEq, Eq)]
43pub enum Convert {
44    /// Alphanumeric and edited items, and a group with no members: its bytes decoded in the
45    /// program's code page, trimmed of trailing spaces, or of leading ones when `justified`.
46    Chars { justified: bool },
47    /// UTF-16 units, trimmed of trailing spaces.
48    National,
49    /// COMP-1 (8 decimals) or COMP-2 (17).
50    Float(Precision),
51    /// Zoned, packed, binary and index items: the value, read as its kind reads it, with at least
52    /// `integers` integer positions before the leading zeros are trimmed.
53    Fixed { integers: u32 },
54    /// A pointer, procedure-pointer or object reference: this IRONWORK abend when it is reached.
55    Refused(AbendId),
56}
57
58/// A USING value that stands for true or null.
59#[derive(Clone, Copy, Debug, PartialEq, Eq)]
60pub enum Marker {
61    /// The first byte equals this literal's first, in the program's code page; never when the
62    /// literal has none.
63    Byte(Option<u8>),
64    /// A condition-name, its conditional variable located with the walk's subscripts.
65    Condition(CondId),
66    /// The IRONWORK abend the walker gives when it tests the marker.
67    Refused(AbendId),
68}
69
70/// JSON GENERATE. In order: FROM is located, its subscripts evaluated, which begin the walk's
71/// subscripts (`IntExpr::Walk`), and the tree walked from `nodes[0]`; then the receiver is located,
72/// the encoding read, the document written, COUNT IN and JSON-CODE stored.
73#[derive(Clone, Debug, PartialEq, Eq)]
74pub struct JsonGenerate {
75    /// FROM, or its table's first element when it names the whole table.
76    pub from: PlaceId,
77    pub subscripts: Vec<IntExpr>,
78    /// `nodes[0]` is FROM, with `occurs` when FROM names a whole table.
79    pub nodes: Vec<JsonNode>,
80    /// The name the document wraps FROM's value in, as a JSON string; None for NAME ... OMITTED.
81    pub name: Option<SymId>,
82    pub receiver: PlaceId,
83    pub encoding: Ccsid,
84    pub count: Option<(PlaceId, StorePlan)>,
85    pub code: (PlaceId, StorePlan),
86    pub on_exception: bool,
87    pub not_on_exception: bool,
88}
89
90/// An item of the tree, `offset` bytes into the occurrence of the node that holds it, `len` its
91/// size; a table's elements are `len` apart, `occurs` of them. `name` is a member's JSON string.
92#[derive(Clone, Debug, PartialEq, Eq)]
93pub struct JsonNode {
94    pub offset: u32,
95    pub len: u32,
96    pub kind: Kind,
97    pub name: SymId,
98    pub occurs: Option<Count>,
99    pub value: JsonValue,
100}
101
102#[derive(Clone, Debug, PartialEq, Eq)]
103pub enum JsonValue {
104    /// A group's members, in order, an unnamed group's among them. With `eligible` (some member
105    /// is not ignored), a group whose members are all left out is left out; a table whose
106    /// elements all are is left out.
107    Object { members: Vec<u32>, eligible: bool },
108    Leaf(JsonLeaf),
109}
110
111/// Tested in this order: `indicator` (null), `null`, `suppress` (left out), `boolean`, `convert`.
112#[derive(Clone, Debug, PartialEq, Eq)]
113pub struct JsonLeaf {
114    /// INDICATING: the indicator, located with the walk's subscripts, whose first byte `Marker`
115    /// tests; or the abend of locating it with too few.
116    pub indicator: Option<(Result<PlaceId, AbendId>, Marker)>,
117    /// CONVERTING ... TO JSON NULL: null when the item equals this figurative constant.
118    pub null: Option<Figurative>,
119    /// SUPPRESS: the item's own WHEN constants, then each EVERY phrase's that selects its kind.
120    pub suppress: Vec<Figurative>,
121    /// CONVERTING ... TO BOOLEAN: true when the marker holds of the item's first byte.
122    pub boolean: Option<Marker>,
123    pub convert: Convert,
124}
125
126/// XML GENERATE. In order: the receiver is located and the encoding read, which may end the
127/// statement with XML-CODE 411, 414 or 415; NAMESPACE is read (416), then NAMESPACE-PREFIX for a
128/// namespace that is not empty (419); FROM is located, its subscripts evaluated, and the tree walked from `nodes[0]`; a national
129/// value in a single-byte document ends it with 420; then the document is written and COUNT IN and
130/// XML-CODE stored.
131#[derive(Clone, Debug, PartialEq, Eq)]
132pub struct XmlGenerate {
133    pub receiver: PlaceId,
134    /// `Unnamed` is UTF-16 for a national receiver and the program's CODEPAGE otherwise.
135    pub encoding: Ccsid,
136    pub namespace: Option<Operand>,
137    pub prefix: Option<Operand>,
138    pub declaration: bool,
139    pub from: PlaceId,
140    pub subscripts: Vec<IntExpr>,
141    /// `nodes[0]` is FROM: its element, which SUPPRESS never leaves out and which takes the
142    /// namespace declaration, or an elementary item's element whatever its form.
143    pub nodes: Vec<XmlNode>,
144    /// SUPPRESS is written, so an element left with no attributes and no content is left out.
145    pub suppressing: bool,
146    pub count: Option<(PlaceId, StorePlan)>,
147    pub code: (PlaceId, StorePlan),
148    pub on_exception: bool,
149    pub not_on_exception: bool,
150}
151
152/// As `JsonNode`; `name` is the element or attribute name as the document writes it.
153#[derive(Clone, Debug, PartialEq, Eq)]
154pub struct XmlNode {
155    pub offset: u32,
156    pub len: u32,
157    pub kind: Kind,
158    pub name: SymId,
159    pub occurs: Option<Count>,
160    pub value: XmlValue,
161}
162
163#[derive(Clone, Debug, PartialEq, Eq)]
164pub enum XmlValue {
165    /// A named group's element: each occurrence of each member, in order.
166    Element { members: Vec<u32> },
167    /// An unnamed group, each occurrence's members joining the element that holds it.
168    Members { members: Vec<u32> },
169    /// `suppress`: the item's own WHEN constants, or when it has none each EVERY phrase's that
170    /// selects its kind and form. A value that is not legal XML goes in hexadecimal, as an element
171    /// named "hex." and the name, and XML-CODE is 417.
172    Leaf { form: XmlForm, suppress: Vec<Figurative>, convert: Convert },
173}
174
175#[derive(Clone, Copy, Debug, PartialEq, Eq)]
176pub enum XmlForm {
177    Attribute,
178    Element,
179    Content,
180}
181
182/// XML PARSE. The document is located, the encoding read (a CCSID ironwork has no page for abends
183/// IRONWORK), and the document located again and decoded; then each event the scanner reports
184/// sets XML-EVENT, XML-CODE and XML-INFORMATION and the fragment registers (`Base::Xml`), and runs
185/// `procedure` through `Procedures::run` with the arrival PERFORM LOOP, at the statement's depth.
186/// A procedure that leaves ends the statement with its step. After it completes, XML-CODE is read:
187/// a warning the scanner reports as an EXCEPTION event (an undeclared prefix) goes on when it is 0,
188/// and that or any other EXCEPTION otherwise ends the parse with the scanner's code;
189/// END-OF-DOCUMENT ends it with 0; at END-OF-INPUT 1 locates the document again for the next
190/// segment and any other value ends the input; after any other event -1 ends the parse with -1.
191/// XML-CODE then takes the result.
192#[derive(Clone, Debug, PartialEq, Eq)]
193pub struct XmlParse {
194    pub document: PlaceId,
195    pub encoding: Option<Operand>,
196    /// RETURNING NATIONAL.
197    pub national: bool,
198    pub procedure: RangeId,
199    pub event: PlaceId,
200    pub code: (PlaceId, StorePlan),
201    pub information: (PlaceId, StorePlan),
202    /// XML-CODE as read after each event.
203    pub code_value: IntExpr,
204    pub on_exception: bool,
205    pub not_on_exception: bool,
206}
207
208/// JSON PARSE. In order: the source is located, the encoding read (ENCODING FROM CODEPAGE of a
209/// national source is JSON-CODE 109 before it is), the text decoded and parsed; then, for a
210/// document that parses, INTO is located and its subscripts evaluated, which begin the walk's, and
211/// each value goes into the item whose name matches it, by MOVE's rules; JSON-CODE and then
212/// JSON-STATUS are stored.
213#[derive(Clone, Debug, PartialEq, Eq)]
214pub struct JsonParse {
215    pub source: PlaceId,
216    pub encoding: Ccsid,
217    /// INTO, or its table's first element when it names the whole table.
218    pub into: PlaceId,
219    pub subscripts: Vec<IntExpr>,
220    /// `nodes[0]` is INTO, with `occurs` when INTO names a whole table. Its name `Omitted` makes
221    /// the document INTO's value; otherwise the document is an object whose members INTO's name
222    /// matches.
223    pub nodes: Vec<ParseNode>,
224    /// IGNORING JSON NULL FOR ALL.
225    pub ignore_all: bool,
226    pub code: (PlaceId, StorePlan),
227    pub status: (PlaceId, StorePlan),
228    pub on_exception: bool,
229    pub not_on_exception: bool,
230}
231
232/// As `JsonNode`. Each item takes a value in this order: a second value at the same item and
233/// offset is a duplicate; `indicator`; then a null goes to `null`'s MOVE, or is ignored, or is a
234/// status; an object fills a group's members; any other value goes to a leaf.
235#[derive(Clone, Debug, PartialEq, Eq)]
236pub struct ParseNode {
237    pub offset: u32,
238    pub len: u32,
239    pub kind: Kind,
240    pub name: Named,
241    pub occurs: Option<Count>,
242    /// IGNORING JSON NULL FOR the item.
243    pub ignored: bool,
244    pub indicator: Option<Indicator>,
245    /// CONVERTING ... FROM JSON NULL: the figurative constant and its MOVE into the item.
246    pub null: Option<(Figurative, MovePlan)>,
247    pub value: ParseValue,
248}
249
250/// How a JSON name matches an item: a NAME literal exactly, a data-name whatever the case of its
251/// letters, or never (NAME ... OMITTED).
252#[derive(Clone, Copy, Debug, PartialEq, Eq)]
253pub enum Named {
254    Exactly(SymId),
255    Folded(SymId),
256    Omitted,
257}
258
259#[derive(Clone, Debug, PartialEq, Eq)]
260pub enum ParseValue {
261    /// A group's members a name can reach, an unnamed group's among them, unnamed tables not.
262    Object { members: Vec<u32> },
263    Leaf(ParseLeaf),
264    /// SUPPRESS: a name reaches it, and it takes nothing.
265    Suppressed,
266}
267
268#[derive(Clone, Debug, PartialEq, Eq)]
269pub struct ParseLeaf {
270    /// CONVERTING ... FROM BOOLEAN: true sets the first value, false the second.
271    pub boolean: Option<Flag>,
272    /// A string's characters into an alphanumeric, alphanumeric-edited or national item: bytes in
273    /// the program's code page, X'3F' for any it lacks, or UTF-16, by this MOVE. None for any
274    /// other item, which takes the number a string of digits spells.
275    pub text: Option<MovePlan>,
276    pub number: NumberInto,
277}
278
279/// How a number goes into a leaf.
280#[derive(Clone, Copy, Debug, PartialEq, Eq)]
281pub enum NumberInto {
282    Float(MovePlan),
283    /// Zoned, packed or binary: stored with a size error a status, not an exception.
284    Store(StorePlan),
285    /// Numeric-edited, by MOVE.
286    Edited(MovePlan),
287    /// An integer only: an alphanumeric or alphanumeric-edited item takes it as MOVE takes an
288    /// unsigned integer of as many digits, a national item its digits as UTF-16.
289    Digits,
290    Incompatible,
291}
292
293/// INDICATING: the indicator IN names, located with the walk's subscripts whatever the flag,
294/// then the flag, on for a null.
295#[derive(Clone, Debug, PartialEq, Eq)]
296pub struct Indicator {
297    pub place: Option<Result<PlaceId, AbendId>>,
298    pub flag: Flag,
299}
300
301/// A USING phrase: what a true or null value sets, then what a false or other value sets.
302#[derive(Clone, Copy, Debug, PartialEq, Eq)]
303pub enum Flag {
304    /// SET condition-name TO TRUE, or TO FALSE.
305    Set { on: SetTo, off: SetTo },
306    /// One of two literals moved into the indicator, or the item.
307    Literals { on: (ConstId, MovePlan), off: (ConstId, MovePlan) },
308}
309
310/// SET of a condition-name, its conditional variable located with the walk's subscripts.
311#[derive(Clone, Copy, Debug, PartialEq, Eq)]
312pub enum SetTo {
313    /// It has no such value.
314    Nothing,
315    Move { place: PlaceId, value: ConstId, plan: MovePlan },
316    /// It is not a condition-name, or the walk has too few subscripts for it.
317    Refused(AbendId),
318}
319
320/// The XML special registers whose length is the current event's fragment's: empty outside a
321/// processing procedure.
322#[derive(Clone, Copy, Debug, PartialEq, Eq)]
323pub enum XmlRegister {
324    Text,
325    NText,
326    Namespace,
327    NNamespace,
328    Prefix,
329    NPrefix,
330}
331
332impl XmlRegister {
333    pub fn national(self) -> bool {
334        matches!(self, XmlRegister::NText | XmlRegister::NNamespace | XmlRegister::NPrefix)
335    }
336}
337
338codec_enum!(Markup { JsonGenerate(g) = 0, XmlGenerate(g) = 1, XmlParse(p) = 2, JsonParse(p) = 3 });
339codec_enum!(Ccsid { Unnamed = 0, CodePage = 1, Operand(o) = 2 });
340codec_enum!(Convert { Chars { justified } = 0, National = 1, Float(precision) = 2, Fixed { integers } = 3, Refused(abend) = 4 });
341codec_enum!(Marker { Byte(b) = 0, Condition(c) = 1, Refused(abend) = 2 });
342codec_struct!(JsonGenerate { from, subscripts, nodes, name, receiver, encoding, count, code, on_exception, not_on_exception } check json_valid);
343codec_struct!(JsonNode { offset, len, kind, name, occurs, value });
344codec_enum!(JsonValue { Object { members, eligible } = 0, Leaf(leaf) = 1 });
345codec_struct!(JsonLeaf { indicator, null, suppress, boolean, convert });
346codec_struct!(XmlGenerate {
347    receiver, encoding, namespace, prefix, declaration, from, subscripts, nodes, suppressing, count, code, on_exception, not_on_exception,
348} check xml_valid);
349codec_struct!(XmlNode { offset, len, kind, name, occurs, value });
350codec_enum!(XmlValue { Element { members } = 0, Members { members } = 1, Leaf { form, suppress, convert } = 2 });
351codec_enum!(XmlForm { Attribute = 0, Element = 1, Content = 2 });
352codec_struct!(XmlParse { document, encoding, national, procedure, event, code, information, code_value, on_exception, not_on_exception });
353codec_struct!(JsonParse { source, encoding, into, subscripts, nodes, ignore_all, code, status, on_exception, not_on_exception } check parse_valid);
354codec_struct!(ParseNode { offset, len, kind, name, occurs, ignored, indicator, null, value });
355codec_enum!(Named { Exactly(name) = 0, Folded(name) = 1, Omitted = 2 });
356codec_enum!(ParseValue { Object { members } = 0, Leaf(leaf) = 1, Suppressed = 2 });
357codec_struct!(ParseLeaf { boolean, text, number });
358codec_enum!(NumberInto { Float(plan) = 0, Store(store) = 1, Edited(plan) = 2, Digits = 3, Incompatible = 4 });
359codec_struct!(Indicator { place, flag });
360codec_enum!(Flag { Set { on, off } = 0, Literals { on, off } = 1 });
361codec_enum!(SetTo { Nothing = 0, Move { place, value, plan } = 1, Refused(abend) = 2 });
362codec_enum!(XmlRegister { Text = 0, NText = 1, Namespace = 2, NNamespace = 3, Prefix = 4, NPrefix = 5 });
363
364/// A tree with a root, whose every member comes after the node that holds it, so no walk loops.
365fn tree_valid<'n>(nodes: usize, members: impl Iterator<Item = (usize, &'n [u32])>) -> Result<(), String> {
366    if nodes == 0 {
367        return Err("a markup tree with no root".into());
368    }
369    for (k, list) in members {
370        if let Some(&m) = list.iter().find(|&&m| m as usize <= k || m as usize >= nodes) {
371            return Err(format!("markup node {k} holds node {m} of {nodes}"));
372        }
373    }
374    Ok(())
375}
376
377fn json_valid(g: &JsonGenerate) -> Result<(), String> {
378    tree_valid(g.nodes.len(), g.nodes.iter().enumerate().filter_map(|(k, n)| match &n.value {
379        JsonValue::Object { members, .. } => Some((k, &members[..])),
380        JsonValue::Leaf(_) => None,
381    }))
382}
383
384fn xml_valid(g: &XmlGenerate) -> Result<(), String> {
385    tree_valid(g.nodes.len(), g.nodes.iter().enumerate().filter_map(|(k, n)| match &n.value {
386        XmlValue::Element { members } | XmlValue::Members { members } => Some((k, &members[..])),
387        XmlValue::Leaf { .. } => None,
388    }))
389}
390
391fn parse_valid(p: &JsonParse) -> Result<(), String> {
392    tree_valid(p.nodes.len(), p.nodes.iter().enumerate().filter_map(|(k, n)| match &n.value {
393        ParseValue::Object { members } => Some((k, &members[..])),
394        _ => None,
395    }))
396}