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