Skip to main content

ironwork_rt/lir/
print.rs

1//! The LIR as text (lir.md ยง13): a program's generated code as `ironwork dump` prints it, for any
2//! tool that shows it. The same program prints the same bytes; nothing reads the text back.
3
4use super::{
5    AbendId, AbendText, Access, Advance, Argument, ArithId, ArithPlan, ArithStep, Base, Binding, Block, BlockId, Bound, CallArg, CallId, CallTarget, Ccsid,
6    Chars, CicsId, Class, Comparand, Compare, Cond, CondId, Const, ConstId, Convert, ConvertTable, Count, Debug, DebugId, DisplayId, DisplayItem,
7    Expr, ExprId, FileDesc, FileOpId, FileVerb, Flag, FloatFrom, FromMove, FunctionId, GlobalAt, Image, Indicator, InitValue, InspectId,
8    InspectPhrase, Inspected, IntExpr, InvokeId, Item, JsonLeaf, JsonNode, JsonValue, Marker, MarkupId, MethodName, Mode, MovePlan, Named,
9    NationalFrom, NumberInto, NumericFrom, Odo, Op, Operand, Organization, ParaId, Paragraph, ParseLeaf, ParseNode, ParseValue, Phrase, Place,
10    PlaceId, Plans, Program, Range, RangeId, RangeKind, Receiver, Replacement, ReportOp, ReturnId, SearchAllId, Section, SenderCheck, Services,
11    SetTo, SignTest, SortIo, SortKeys, SortPlan, Spacing, SqlEntry, SqlStatement, SqlTest, SqlcaField, StartKey, StartRel, StepPlan,
12    StorePlan, StringId, SymId, Terminator, TrimSide, UnstringId, UpDown, UserArgument, UserFunctionId, XmlForm, XmlNode, XmlRegister,
13    XmlValue,
14};
15use super::{Markup, ReleaseId, SortId, SqlId};
16use crate::abend::Ending;
17use crate::cics::{Cics, CicsCommand, Datum, Handles};
18use crate::files::Format;
19use crate::report::{Adding, Field, FieldContent, GroupKind, LineNumber, NextGroup, Origin};
20use crate::sql::HostType;
21use crate::storage::Kind;
22use crate::store::LaxRedefinition;
23use crate::vocab::{AcceptFrom, BinOp, Closing, Figurative, InspectMode, OpenMode, Pos, RelOp, SignClause, SignPosition};
24use numeric::Arith;
25use numeric::precision::Fixed;
26use std::cell::Cell;
27use std::collections::BTreeMap;
28use std::convert::Infallible;
29use std::fmt;
30use zarch::ebcdic::CodePage;
31use zarch::hfp::Precision;
32use zarch::wide::U256;
33
34/// What a program's LIR section holds, borrowed: what the printer prints.
35#[derive(Clone, Copy)]
36pub struct Code<'a> {
37    pub id: SymId,
38    pub initial: bool,
39    pub recursive: bool,
40    pub paragraphs: &'a [Paragraph],
41    pub procedure_start: ParaId,
42    pub ranges: &'a [Range],
43    pub blocks: &'a [Block],
44    pub places: &'a [Place],
45    pub exprs: &'a [Expr],
46    pub conds: &'a [Cond],
47    pub consts: &'a [Const],
48    pub plans: &'a Plans,
49    pub services: &'a Services,
50    pub abends: &'a [AbendText],
51    pub symbols: &'a [String],
52}
53
54/// A program's code as text. The other sections name what the code refers to: `items` qualifies a
55/// data name several items share, `debug` gives source positions, `sql` the EXEC SQL statements and
56/// `ccsid` the code page literals are shown in. Each may be left empty, and is then not shown.
57#[derive(Clone, Copy)]
58pub struct Listing<'a> {
59    pub code: Code<'a>,
60    pub items: &'a [Item],
61    pub debug: Option<&'a Debug>,
62    pub sql: &'a [SqlEntry],
63    pub ccsid: Option<u16>,
64}
65
66impl Program {
67    pub fn code(&self) -> Code<'_> {
68        Code {
69            id: self.id,
70            initial: self.initial,
71            recursive: self.recursive,
72            paragraphs: &self.paragraphs,
73            procedure_start: self.procedure_start,
74            ranges: &self.ranges,
75            blocks: &self.blocks,
76            places: &self.places,
77            exprs: &self.exprs,
78            conds: &self.conds,
79            consts: &self.consts,
80            plans: &self.plans,
81            services: &self.services,
82            abends: &self.abends,
83            symbols: &self.symbols,
84        }
85    }
86}
87
88impl<'a> Listing<'a> {
89    pub fn of(program: &'a Program) -> Self {
90        Listing {
91            code: program.code(),
92            items: &program.items,
93            debug: Some(&program.debug),
94            sql: &program.sql,
95            ccsid: Some(program.options.options.codepage),
96        }
97    }
98}
99
100impl fmt::Display for Listing<'_> {
101    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102        Printer::new(self).program(f)
103    }
104}
105
106impl fmt::Display for Program {
107    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108        Listing::of(self).fmt(f)
109    }
110}
111
112/// How deep one reference may nest, and how many references it may print, before the rest is
113/// "...": a module is untrusted, and its tables may refer to each other in a cycle.
114const DEPTH: u32 = 256;
115const BUDGET: u32 = 100_000;
116
117struct Printer<'a> {
118    c: Code<'a>,
119    items: &'a [Item],
120    debug: Option<&'a Debug>,
121    sql: &'a [SqlEntry],
122    page: Option<&'static CodePage>,
123    /// Each data name more than one item has, with those items.
124    shared: BTreeMap<&'a str, Vec<usize>>,
125    /// Each paragraph name more than one paragraph has.
126    repeated: BTreeMap<&'a str, usize>,
127    depth: Cell<u32>,
128    spent: Cell<u32>,
129}
130
131fn join(parts: impl IntoIterator<Item = String>, separator: &str) -> String {
132    parts.into_iter().collect::<Vec<_>>().join(separator)
133}
134
135/// ` [a, b]` or ` {a, b}`, nothing when there is nothing to say.
136fn attrs(open: char, parts: Vec<String>) -> String {
137    if parts.is_empty() {
138        return String::new();
139    }
140    let close = if open == '[' { ']' } else { '}' };
141    format!(" {open}{}{close}", parts.join(", "))
142}
143
144fn yes(on: bool, text: &str) -> Option<String> {
145    on.then(|| text.to_owned())
146}
147
148fn label(id: BlockId) -> String {
149    format!("b{id}")
150}
151
152fn abend_ref(id: AbendId) -> String {
153    format!("a{id}")
154}
155
156impl<'a> Printer<'a> {
157    fn new(l: &Listing<'a>) -> Self {
158        let symbol = |id: SymId| l.code.symbols.get(id as usize).map(String::as_str);
159        let mut shared: BTreeMap<&'a str, Vec<usize>> = BTreeMap::new();
160        for (k, item) in l.items.iter().enumerate() {
161            if let Some(name) = item.name.and_then(symbol) {
162                shared.entry(name).or_default().push(k);
163            }
164        }
165        shared.retain(|_, items| items.len() > 1);
166        let mut repeated: BTreeMap<&'a str, usize> = BTreeMap::new();
167        for p in l.code.paragraphs {
168            if let Some(name) = symbol(p.name) {
169                *repeated.entry(name).or_default() += 1;
170            }
171        }
172        repeated.retain(|_, n| *n > 1);
173        Printer {
174            c: l.code,
175            items: l.items,
176            debug: l.debug,
177            sql: l.sql,
178            page: l.ccsid.and_then(CodePage::by_ccsid),
179            shared,
180            repeated,
181            depth: Cell::new(0),
182            spent: Cell::new(0),
183        }
184    }
185
186    fn nested(&self, render: impl FnOnce() -> String) -> String {
187        let depth = self.depth.get();
188        if depth == 0 {
189            self.spent.set(0);
190        }
191        let spent = self.spent.get();
192        if depth >= DEPTH || spent >= BUDGET {
193            return "...".to_owned();
194        }
195        self.depth.set(depth + 1);
196        self.spent.set(spent + 1);
197        let text = render();
198        self.depth.set(depth);
199        text
200    }
201
202    fn symbol(&self, id: SymId) -> Option<&'a str> {
203        self.c.symbols.get(id as usize).map(String::as_str)
204    }
205
206    /// A data, paragraph, file or program name as a word.
207    fn name(&self, id: SymId) -> String {
208        self.symbol(id).map_or_else(|| format!("symbol{id}?"), word)
209    }
210
211    /// Text that is not a name: a message, a statement, a signature.
212    fn string(&self, id: SymId) -> String {
213        self.symbol(id).map_or_else(|| format!("symbol{id}?"), |s| format!("{s:?}"))
214    }
215
216    /// Text that is quoted already, as a JSON string is.
217    fn raw(&self, id: SymId) -> String {
218        self.symbol(id).map_or_else(|| format!("symbol{id}?"), |s| if s.chars().any(char::is_control) { format!("{s:?}") } else { s.to_owned() })
219    }
220
221    /// A literal's text, as COBOL writes it.
222    fn literal(&self, id: SymId) -> String {
223        self.symbol(id).map_or_else(|| format!("symbol{id}?"), quote)
224    }
225
226    fn bytes(&self, bytes: &[u8]) -> String {
227        let decoded = self.page.and_then(|page| {
228            bytes
229                .iter()
230                .map(|&b| {
231                    let ch = page.decode_byte(b);
232                    (!ch.is_control() && page.encode_char(ch) == Some(b)).then_some(ch)
233                })
234                .collect::<Option<String>>()
235        });
236        decoded.map_or_else(|| hex("X", bytes), |text| quote(&text))
237    }
238
239    fn position(&self, pos: &Pos) -> String {
240        if pos.file == 0 {
241            return format!("{}:{}", pos.line, pos.col);
242        }
243        let file = self.debug.and_then(|d| d.sources.get(usize::from(pos.file))).map_or_else(|| format!("file{}", pos.file), |&s| self.name(s));
244        format!("{file}:{}:{}", pos.line, pos.col)
245    }
246
247    fn pos(&self, at: DebugId) -> Option<String> {
248        self.debug.and_then(|d| d.positions.get(at as usize)).map(|p| self.position(p))
249    }
250
251    fn at(&self, at: DebugId) -> String {
252        self.pos(at).map_or_else(String::new, |p| format!("  @{p}"))
253    }
254
255    fn para(&self, id: ParaId) -> String {
256        let Some(p) = self.c.paragraphs.get(id as usize) else { return format!("paragraph{id}?") };
257        let name = self.name(p.name);
258        if !self.symbol(p.name).is_some_and(|n| self.repeated.contains_key(n)) {
259            return name;
260        }
261        let section = (0..id).rev().map(|s| (s, &self.c.paragraphs[s as usize])).find(|(_, s)| s.is_section && s.section_end >= id);
262        match section {
263            Some((_, s)) if !self.symbol(s.name).is_some_and(|n| self.repeated.contains_key(n)) => format!("{name} OF {}", self.name(s.name)),
264            _ => format!("{name} (paragraph {id})"),
265        }
266    }
267
268    /// The paragraph control falls into, or "end" past the last.
269    fn next_para(&self, id: ParaId) -> String {
270        if id as usize == self.c.paragraphs.len() { "end".to_owned() } else { self.para(id) }
271    }
272
273    fn range(&self, id: RangeId) -> String {
274        match self.c.ranges.get(id as usize) {
275            None => format!("r{id}?"),
276            Some(r) if r.first == r.last => format!("r{id} {}", self.para(r.first)),
277            Some(r) => format!("r{id} {} thru {}", self.para(r.first), self.para(r.last)),
278        }
279    }
280
281    fn file(&self, file: u16) -> String {
282        self.c.services.files.get(usize::from(file)).map_or_else(|| format!("file{file}?"), |d| self.name(d.name))
283    }
284
285    fn file_at(&self, file: usize) -> String {
286        u16::try_from(file).map_or_else(|_| format!("file{file}?"), |f| self.file(f))
287    }
288
289    /// LINKAGE record `n` by its 01 or 77's name.
290    fn linkage(&self, n: u16) -> String {
291        let record = self.items.iter().find(|i| i.linkage == Some(n) && i.parent.is_none()).and_then(|i| i.name);
292        record.map_or_else(|| format!("linkage{n}"), |s| self.name(s))
293    }
294
295    /// A place as a reference is written: its name, qualified when other items share it, then
296    /// its subscripts and reference modification.
297    fn place(&self, id: PlaceId) -> String {
298        self.nested(|| match self.c.places.get(id as usize) {
299            None => format!("p{id}?"),
300            Some(p) => self.reference(p, &[]),
301        })
302    }
303
304    fn places(&self, ids: &[PlaceId]) -> String {
305        join(ids.iter().map(|&p| self.place(p)), " ")
306    }
307
308    /// `all` holds the subscripts written ALL, by position.
309    fn reference(&self, p: &Place, all: &[u32]) -> String {
310        let mut text = self.data_name(p);
311        if !p.subscripts.is_empty() {
312            let subscripts = p.subscripts.iter().enumerate().map(|(k, s)| if all.contains(&(k as u32)) { "ALL".to_owned() } else { self.int(&s.value) });
313            text += &format!("({})", join(subscripts, ", "));
314        }
315        if let Some(r) = &p.refmod {
316            text += &format!("({}:{})", self.int(&r.start), r.length.as_ref().map_or_else(String::new, |l| self.int(l)));
317        }
318        text
319    }
320
321    fn data_name(&self, p: &Place) -> String {
322        let name = self.name(p.name);
323        let Some(same) = self.symbol(p.name).and_then(|n| self.shared.get(n)) else { return name };
324        let held = |i: &Item| match p.base {
325            Base::Program => !i.local && i.linkage.is_none(),
326            Base::Local => i.local,
327            Base::Linkage(n) => i.linkage == Some(n),
328            Base::ReturnCode | Base::Eib | Base::SelfRef | Base::JniEnv | Base::Xml(_) => false,
329        };
330        let Some(&own) = same.iter().find(|&&k| self.items[k].offset == p.offset && held(&self.items[k])) else { return name };
331        let mut others: Vec<usize> = same.iter().copied().filter(|&k| k != own).collect();
332        let mut qualifiers: Vec<&str> = Vec::new();
333        for ancestor in self.ancestors(own) {
334            if others.is_empty() {
335                break;
336            }
337            let Some(q) = self.items[ancestor].name.and_then(|s| self.symbol(s)) else { continue };
338            qualifiers.push(q);
339            others.retain(|&k| self.qualifies(k, &qualifiers));
340        }
341        qualifiers.iter().fold(name, |text, q| format!("{text} OF {}", word(q)))
342    }
343
344    /// An item's ancestors, innermost first; a chain that loops stops.
345    fn ancestors(&self, k: usize) -> Vec<usize> {
346        let mut out = Vec::new();
347        let mut next = self.items.get(k).and_then(|i| i.parent);
348        while let Some(p) = next.map(|p| p as usize).filter(|&p| p < self.items.len() && out.len() < self.items.len()) {
349            out.push(p);
350            next = self.items[p].parent;
351        }
352        out
353    }
354
355    /// Item `k` has ancestors named by `qualifiers`, in order.
356    fn qualifies(&self, k: usize, qualifiers: &[&str]) -> bool {
357        let mut wanted = qualifiers.iter().peekable();
358        for a in self.ancestors(k) {
359            if wanted.peek().is_some_and(|&&q| self.items[a].name.and_then(|s| self.symbol(s)) == Some(q)) {
360                wanted.next();
361            }
362        }
363        wanted.peek().is_none()
364    }
365
366    fn konst(&self, id: ConstId) -> String {
367        match self.c.consts.get(id as usize) {
368            None => format!("c{id}?"),
369            Some(c) => self.constant(c),
370        }
371    }
372
373    fn constant(&self, c: &Const) -> String {
374        match c {
375            Const::Bytes(b) => self.bytes(b),
376            Const::National(units) => national(units),
377            Const::Number(n) => decimal(n),
378            Const::Figurative(f) => figurative(*f).to_owned(),
379            Const::All(b) => format!("ALL {}", self.bytes(b)),
380            Const::AllNational(units) => format!("ALL {}", national(units)),
381            Const::Dbcs(b) => hex("GX", b),
382            Const::Refused(a) => format!("refused {}", abend_ref(*a)),
383        }
384    }
385
386    fn operand(&self, o: &Operand) -> String {
387        match *o {
388            Operand::Load(p) => self.place(p),
389            Operand::Const(c) => self.konst(c),
390            Operand::LengthOf(p) => format!("LENGTH OF {}", self.place(p)),
391            Operand::AddressOf(p) => format!("ADDRESS OF {}", self.place(p)),
392            Operand::Function(f) => self.function(f),
393            Operand::UserFunction(u) => self.user_function(u),
394        }
395    }
396
397    fn function(&self, id: FunctionId) -> String {
398        self.nested(|| {
399            let Some(f) = self.c.plans.function.get(id as usize) else { return format!("function{id}?") };
400            let args = f.args.iter().map(|a| match a {
401                Argument::Value(v) => self.comparand(v),
402                Argument::All { element, all } => match self.c.places.get(*element as usize) {
403                    None => format!("p{element}?"),
404                    Some(p) => self.reference(p, &all.iter().map(|(k, _)| *k).collect::<Vec<_>>()),
405                },
406            });
407            let side = match f.side {
408                None => "",
409                Some(TrimSide::Leading) => " LEADING",
410                Some(TrimSide::Trailing) => " TRAILING",
411            };
412            let mut text = format!("FUNCTION {}({}{side})", f.func.name(), join(args, ", "));
413            if let Some(r) = &f.refmod {
414                text += &format!("({}:{})", self.int(&r.start), r.length.as_ref().map_or_else(String::new, |l| self.int(l)));
415            }
416            let how = [f.integer.as_ref().map(|i| format!("integer {}", self.int(i))), f.arity.map(|a| format!("arity {}", abend_ref(a)))];
417            text + &attrs('{', how.into_iter().flatten().collect())
418        })
419    }
420
421    fn user_function(&self, id: UserFunctionId) -> String {
422        self.nested(|| {
423            let Some(u) = self.c.services.user_functions.get(id as usize) else { return format!("user-function{id}?") };
424            let args = u.args.iter().map(|a| match a {
425                UserArgument::Reference(p) => self.place(*p),
426                UserArgument::Value(v) => format!("value {}", self.comparand(v)),
427            });
428            let mut text = format!("FUNCTION {}({})", self.name(u.name), join(args, ", "));
429            if let Some(r) = &u.refmod {
430                text += &format!("({}:{})", self.int(&r.start), r.length.as_ref().map_or_else(String::new, |l| self.int(l)));
431            }
432            let external = (u.external != u.name).then(|| format!("external {}", self.name(u.external)));
433            text + &attrs('{', external.into_iter().collect())
434        })
435    }
436
437    fn expr(&self, id: ExprId) -> String {
438        self.nested(|| match self.c.exprs.get(id as usize) {
439            None => format!("e{id}?"),
440            Some(Expr::Operand(o)) => self.operand(o),
441            Some(Expr::Neg(e)) => format!("-{}", self.term(*e)),
442            Some(Expr::Bin(a, op, b)) => format!("{} {} {}", self.term(*a), binop(*op), self.term(*b)),
443            Some(Expr::Pow(a, n)) => {
444                let exponent = self.int(n);
445                let compound = matches!(n, IntExpr::Fixed { expr, .. } if matches!(self.c.exprs.get(*expr as usize), Some(Expr::Bin(..) | Expr::Pow(..))));
446                if compound { format!("{} ** ({exponent})", self.term(*a)) } else { format!("{} ** {exponent}", self.term(*a)) }
447            }
448        })
449    }
450
451    /// An operand of an operator, in parentheses when it is an operation itself.
452    fn term(&self, id: ExprId) -> String {
453        match self.c.exprs.get(id as usize) {
454            Some(Expr::Bin(..) | Expr::Pow(..)) => format!("({})", self.expr(id)),
455            _ => self.expr(id),
456        }
457    }
458
459    fn int(&self, e: &IntExpr) -> String {
460        match e {
461            IntExpr::Const(n) => n.to_string(),
462            IntExpr::Item(p) => self.place(*p),
463            IntExpr::Fixed { expr, dmax, prepass } => {
464                let how = self.how(*dmax, &Mode::Fixed, prepass);
465                if how.is_empty() { self.expr(*expr) } else { self.term(*expr) + &attrs('{', how) }
466            }
467            IntExpr::Walk(k) => format!("walk{k}"),
468        }
469    }
470
471    /// How an expression is evaluated where that is not plain: dmax, floating point, and the
472    /// places located before it.
473    fn how(&self, dmax: u32, mode: &Mode, prepass: &[PlaceId]) -> Vec<String> {
474        let mut how = Vec::new();
475        if dmax != 0 {
476            how.push(format!("dmax {dmax}"));
477        }
478        match mode {
479            Mode::Fixed => {}
480            Mode::Float(p) => how.push(format!("float {}", precision(*p))),
481        }
482        if !prepass.is_empty() {
483            how.push(format!("prepass {}", self.places(prepass)));
484        }
485        how
486    }
487
488    fn comparand(&self, c: &Comparand) -> String {
489        match c {
490            Comparand::Operand(o) => self.operand(o),
491            Comparand::Expr { expr, dmax, mode, prepass } => self.term(*expr) + &attrs('{', self.how(*dmax, mode, prepass)),
492        }
493    }
494
495    fn cond(&self, id: CondId) -> String {
496        self.nested(|| match self.c.conds.get(id as usize) {
497            None => format!("k{id}?"),
498            Some(Cond::Rel { a, op, b, how }) => format!("{} {} {} [{}]", self.comparand(a), relop(*op), self.comparand(b), compare(*how)),
499            Some(Cond::Class { place, test }) => format!("{} is {}", self.place(*place), byte_class(*test)),
500            Some(Cond::Sign { value, test }) => format!("{} is {}", self.comparand(value), sign_test(*test)),
501            Some(Cond::Name { subject, values, how }) => {
502                let values = values.iter().map(|(low, high)| match high {
503                    None => self.konst(*low),
504                    Some(h) => format!("{} thru {}", self.konst(*low), self.konst(*h)),
505                });
506                format!("{} in ({}) [{}]", self.place(*subject), join(values, ", "), compare(*how))
507            }
508            Some(Cond::Not(c)) => format!("not ({})", self.cond(*c)),
509            Some(Cond::And(a, b)) => format!("{} and {}", self.logical(*a), self.logical(*b)),
510            Some(Cond::Or(a, b)) => format!("{} or {}", self.logical(*a), self.logical(*b)),
511            Some(Cond::Counter(t)) => format!("t{t} > 0"),
512            Some(Cond::InTable { index, count }) => format!("{} within {}", self.place(*index), self.count(count)),
513            Some(Cond::Sql(test)) => format!("sql {}", sql_test(*test)),
514        })
515    }
516
517    /// A side of AND or OR, in parentheses when it is AND or OR itself.
518    fn logical(&self, id: CondId) -> String {
519        match self.c.conds.get(id as usize) {
520            Some(Cond::And(..) | Cond::Or(..)) => format!("({})", self.cond(id)),
521            _ => self.cond(id),
522        }
523    }
524
525    fn count(&self, c: &Count) -> String {
526        match c {
527            Count::Fixed(n) => n.to_string(),
528            Count::Odo(o) => self.odo(o),
529            Count::Temp(t) => format!("t{t}"),
530        }
531    }
532
533    fn odo(&self, o: &Odo) -> String {
534        format!("({} max {} element {}{})", self.int(&o.object), o.max, o.element, if o.check { " check" } else { "" })
535    }
536
537    fn chars(&self, c: &Chars) -> String {
538        match c {
539            Chars::Literal(b) => self.bytes(b),
540            Chars::Place(p) => self.place(*p),
541            Chars::Value(o) => self.operand(o),
542        }
543    }
544
545    fn store(&self, s: &StorePlan) -> String {
546        match *s {
547            StorePlan::Zoned { digits, scale, signed, sign } => format!("zoned {}{}", pic(digits, scale, signed), sign_clause(sign)),
548            StorePlan::Packed { digits, scale, signed } => format!("packed {}", pic(digits, scale, signed)),
549            StorePlan::Binary { digits, scale, signed, native, name: _ } => format!("binary {}{}", pic(digits, scale, signed), if native { " native" } else { "" }),
550            StorePlan::NumericEdited { edit, digits, scale, blank_when_zero } => {
551                format!("numeric-edited edit {edit} {}{}", pic(digits, scale, false), if blank_when_zero { " blank-when-zero" } else { "" })
552            }
553            StorePlan::Float(p) => format!("float {}", precision(p)),
554            StorePlan::Index => "index".to_owned(),
555            StorePlan::Refused(a) => format!("refused {}", abend_ref(a)),
556        }
557    }
558
559    fn step(&self, s: &StepPlan) -> String {
560        format!("{}, dmax {}", self.store(&s.store), s.dmax)
561    }
562
563    fn move_plan(&self, m: &MovePlan) -> String {
564        match *m {
565            MovePlan::Alnum { image: i, justified } => format!("alnum{}{}", image(i), if justified { " justified" } else { "" }),
566            MovePlan::AlnumEdited { image: i, edit, positions } => format!("alnum-edited edit {edit} positions {positions}{}", image(i)),
567            MovePlan::National(from) => format!("national {}", national_from(from)),
568            MovePlan::Dbcs { justified, edit } => {
569                format!("dbcs{}{}", if justified { " justified" } else { "" }, edit.map_or(String::new(), |e| format!(" edit {e}")))
570            }
571            MovePlan::Numeric { from, store } => format!("numeric {} to {}", numeric_from(from), self.store(&store)),
572            MovePlan::Float { from, precision: p } => format!("float {} to {}", float_from(from), precision(p)),
573            MovePlan::Address => "address".to_owned(),
574            MovePlan::Index => "index".to_owned(),
575            MovePlan::Refused(a) => format!("refused {}", abend_ref(a)),
576        }
577    }
578
579    /// ` [plan]` and NUMCHECK's test of the sender.
580    fn moved(&self, plan: &MovePlan, check: SenderCheck) -> String {
581        let check = match check {
582            SenderCheck::None => None,
583            SenderCheck::Item => Some("check item".to_owned()),
584            SenderCheck::Integer => Some("check integer".to_owned()),
585        };
586        attrs('[', std::iter::once(self.move_plan(plan)).chain(check).collect())
587    }
588
589    fn stored(&self, (place, store): &(PlaceId, StorePlan)) -> String {
590        format!("{} [{}]", self.place(*place), self.store(store))
591    }
592
593    fn program(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
594        let start = (!self.c.paragraphs.is_empty()).then(|| format!("start {}", self.para(self.c.procedure_start)));
595        let flags = [start, yes(self.c.initial, "initial"), yes(self.c.recursive, "recursive")];
596        writeln!(f, "program {}{}", self.name(self.c.id), join(flags.into_iter().flatten().map(|s| format!(" {s}")), ""))?;
597        for (k, r) in self.c.ranges.iter().enumerate() {
598            let span = if r.first == r.last { self.para(r.first) } else { format!("{} thru {}", self.para(r.first), self.para(r.last)) };
599            writeln!(f, "range r{k} {} {span}", range_kind(r.kind))?;
600        }
601        self.code(f)?;
602        self.tables(f)
603    }
604
605    fn code(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
606        let mut entries: BTreeMap<BlockId, Vec<ParaId>> = BTreeMap::new();
607        for (k, p) in self.c.paragraphs.iter().enumerate() {
608            entries.entry(p.entry).or_default().push(k as ParaId);
609        }
610        for (b, block) in self.c.blocks.iter().enumerate() {
611            for &p in entries.get(&(b as BlockId)).into_iter().flatten() {
612                writeln!(f, "{}", self.paragraph(p, false))?;
613            }
614            writeln!(f, "{}:", label(b as BlockId))?;
615            let ats = self.debug.and_then(|d| d.ops.get(b));
616            let starts = self.debug.and_then(|d| d.statements.get(b)).map_or(&[][..], Vec::as_slice);
617            let mut statement = None;
618            let starting = |f: &mut fmt::Formatter<'_>, k: usize, statement: &mut Option<String>| -> fmt::Result {
619                for (_, at) in starts.iter().filter(|(i, _)| *i as usize == k) {
620                    let pos = self.pos(*at);
621                    writeln!(f, "    statement {}", pos.as_deref().unwrap_or("?"))?;
622                    *statement = pos;
623                }
624                Ok(())
625            };
626            let suffix = |k: usize, statement: &Option<String>| match ats.and_then(|a| a.get(k)).and_then(|&at| self.pos(at)) {
627                Some(pos) if Some(&pos) != statement.as_ref() => format!("  @{pos}"),
628                _ => String::new(),
629            };
630            for (k, op) in block.ops.iter().enumerate() {
631                starting(f, k, &mut statement)?;
632                writeln!(f, "    {}{}", self.op(op), suffix(k, &statement))?;
633            }
634            let k = block.ops.len();
635            starting(f, k, &mut statement)?;
636            let at = match block.end {
637                Terminator::Jump(_) | Terminator::Select(_) => String::new(),
638                _ => suffix(k, &statement),
639            };
640            writeln!(f, "    {}{at}", self.terminator(&block.end))?;
641        }
642        for (&b, paragraphs) in entries.range(self.c.blocks.len() as BlockId..) {
643            for &p in paragraphs {
644                writeln!(f, "{} entry {}?", self.paragraph(p, true), label(b))?;
645            }
646        }
647        Ok(())
648    }
649
650    fn paragraph(&self, k: ParaId, bare: bool) -> String {
651        let p = &self.c.paragraphs[k as usize];
652        let mut text = format!("paragraph {k} {}", self.name(p.name));
653        if p.is_section {
654            text += &format!(" section thru {}", self.para(p.section_end));
655        }
656        if p.priority != 0 {
657            text += &format!(" priority {}", p.priority);
658        }
659        if let Some(a) = p.abandoned {
660            text += &format!(" abandoned {}", abend_ref(a));
661        }
662        if bare { text } else { text + &self.at(p.at) }
663    }
664
665    fn terminator(&self, t: &Terminator) -> String {
666        match t {
667            Terminator::Jump(b) => format!("Jump {}", label(*b)),
668            Terminator::Branch { cond, then, otherwise } => format!("Branch ({}) {} else {}", self.cond(*cond), label(*then), label(*otherwise)),
669            Terminator::Select(arms) => format!("Select {}", join(arms.iter().map(|&b| label(b)), " ")),
670            Terminator::ParagraphEnd { next } => format!("ParagraphEnd next {}", self.next_para(*next)),
671            Terminator::GoTo(p) => format!("GoTo {}", self.para(*p)),
672            Terminator::Switch { value, targets, otherwise } => {
673                format!("Switch {} {} else {}", self.int(value), join(targets.iter().map(|&p| self.para(p)), " "), label(*otherwise))
674            }
675            Terminator::PerformEnter { range, ret, resume } => {
676                let resume = resume.map_or_else(String::new, |r| format!(" resume {} {}", self.para(r.para), label(r.block)));
677                format!("PerformEnter {} -> {}{resume}", self.range(*range), label(*ret))
678            }
679            Terminator::ExitProgram { next } => format!("ExitProgram next {}", label(*next)),
680            Terminator::End(e) => format!("End {}", ending(*e)),
681            Terminator::Abend(a) => match self.c.abends.get(*a as usize) {
682                Some(text) => format!("Abend {} {} {}", abend_ref(*a), text.code, self.string(text.message)),
683                None => format!("Abend {}?", abend_ref(*a)),
684            },
685            Terminator::AlteredGoTo { para, otherwise } => format!("AlteredGoTo {} else {}", self.para(*para), label(*otherwise)),
686            Terminator::Debug { range, name, next } => format!("Debug {} name {} next {}", self.range(*range), self.literal(*name), label(*next)),
687        }
688    }
689
690    fn op(&self, op: &Op) -> String {
691        match op {
692            Op::Move { from, to, plan, check } => format!("Move {} <- {}{}", self.place(*to), self.operand(from), self.moved(plan, *check)),
693            Op::Set { from, to, plan } => format!("Set {} <- {}{}", self.place(*to), self.operand(from), self.moved(plan, SenderCheck::None)),
694            Op::Initialize { target, plan } => format!("Initialize {} init {plan}", self.place(*target)),
695            Op::Arith(id) => self.arith(*id),
696            Op::SetAddress { records, address } => format!("SetAddress {} <- {}", join(records.iter().map(|&r| self.linkage(r)), " "), self.operand(address)),
697            Op::SetUpDown { by, down, targets } => {
698                let targets = targets.iter().map(|(p, how)| {
699                    let how = match how {
700                        UpDown::Pointer => "pointer".to_owned(),
701                        UpDown::Number(step) => self.step(step),
702                        UpDown::Refused(a) => format!("refused {}", abend_ref(*a)),
703                    };
704                    format!("{} [{how}]", self.place(*p))
705                });
706                format!("SetUpDown {} {} by {}", join(targets, ", "), if *down { "down" } else { "up" }, self.int(by))
707            }
708            Op::SetEntry { entry, targets } => format!("SetEntry {} <- {}", self.places(targets), self.operand(entry)),
709            Op::Step { var, by, plan, prepass } => {
710                let prepass = (!prepass.is_empty()).then(|| format!("prepass {}", self.places(prepass)));
711                format!("Step {} by {} [{}]{}", self.place(*var), self.expr(*by), self.step(plan), attrs('{', prepass.into_iter().collect()))
712            }
713            Op::SetInt { target, value } => format!("SetInt {} <- {}", self.place(*target), self.int(value)),
714            Op::Inspect(id) => self.inspect(*id),
715            Op::String(id) => self.string_op(*id),
716            Op::Unstring(id) => self.unstring(*id),
717            Op::SearchAll(id) => self.search_all(*id),
718            Op::Nest => "Nest".to_owned(),
719            Op::Unnest(n) => format!("Unnest {n}"),
720            Op::SetTemp(t, value) => format!("SetTemp t{t} <- {}", self.int(value)),
721            Op::DecTemp(t) => format!("DecTemp t{t}"),
722            Op::SetCount(t, odo) => format!("SetCount t{t} <- {}", self.odo(odo)),
723            Op::Display(id) => self.display(*id),
724            Op::Accept { target, from, plan } => format!("Accept {} <- {}{}", self.place(*target), accept_from(*from), self.moved(plan, SenderCheck::None)),
725            Op::File(id) => self.file_op(*id),
726            Op::Call(id) => self.call(*id),
727            Op::Cancel(name) => format!("Cancel {}", self.operand(name)),
728            Op::Sort(id) => self.sort(*id),
729            Op::Release(id) => self.release(*id),
730            Op::Return(id) => self.return_op(*id),
731            Op::Report(r) => self.report_op(r),
732            Op::Invoke(id) => self.invoke(*id),
733            Op::Cics(id) => self.cics(*id),
734            Op::Sql(ordinal) => self.sql(*ordinal),
735            Op::Alter { para, to } => format!("Alter {} to {}", self.para(*para), self.para(*to)),
736            Op::EnterSegment(priority) => format!("EnterSegment {priority}"),
737            Op::DebugLine(line) => format!("DebugLine {line}"),
738            Op::DebugAlter { range, name, contents } => format!("DebugAlter {} name {} contents {}", self.range(*range), self.literal(*name), self.literal(*contents)),
739            Op::Markup(id) => self.markup(*id),
740        }
741    }
742
743    fn arith(&self, id: ArithId) -> String {
744        let Some(ArithPlan { dmax, arith, prepass, steps, remainder, handled, per_receiver, inner_dmax }) = self.c.plans.arith.get(id as usize) else {
745            return format!("Arith arith{id}?");
746        };
747        let mut steps: Vec<String> = steps.iter().map(|s| self.arith_step(s)).collect();
748        if let Some(r) = remainder {
749            steps.push(format!(
750                "remainder {} <- {} / {} [{}, quotient scale {}]",
751                self.place(r.target),
752                self.term(r.dividend),
753                self.term(r.divisor),
754                self.store(&r.store),
755                r.quotient_scale
756            ));
757        }
758        let mut how = vec![format!("dmax {dmax}")];
759        if inner_dmax != dmax {
760            how.push(format!("inner dmax {inner_dmax}"));
761        }
762        match arith {
763            Arith::Compat => {}
764            Arith::Extend => how.push("arith extend".to_owned()),
765        }
766        if !prepass.is_empty() {
767            how.push(format!("prepass {}", self.places(prepass)));
768        }
769        how.extend(yes(*per_receiver, "per receiver"));
770        how.extend(yes(*handled, "size error"));
771        format!("Arith {}{}", steps.join("; "), attrs('{', how))
772    }
773
774    fn arith_step(&self, s: &ArithStep) -> String {
775        let mut how = vec![self.store(&s.store)];
776        how.extend(yes(s.rounded, "rounded"));
777        match s.mode {
778            Mode::Fixed => {}
779            Mode::Float(p) => how.push(format!("float {}", precision(p))),
780        }
781        if !s.probe.is_empty() {
782            how.push(format!("probe {}", self.places(&s.probe)));
783        }
784        format!("{} <- {}{}", self.place(s.target), self.expr(s.expr), attrs('[', how))
785    }
786
787    fn display(&self, id: DisplayId) -> String {
788        let Some(d) = self.c.plans.display.get(id as usize) else { return format!("Display display{id}?") };
789        let items = d.items.iter().map(|item| match item {
790            DisplayItem::Bytes(p) => self.place(*p),
791            DisplayItem::National(p) => format!("{} [national]", self.place(*p)),
792            DisplayItem::Digits { place, digits, signed } => format!("{} [digits {digits}{}]", self.place(*place), if *signed { " signed" } else { "" }),
793            DisplayItem::Refused { place, abend } => format!("{} [refused {}]", self.place(*place), abend_ref(*abend)),
794            DisplayItem::Text(text) => self.literal(*text),
795            DisplayItem::Value(o) => self.operand(o),
796        });
797        format!("Display {}{}", join(items, ", "), attrs('{', yes(d.no_advancing, "no advancing").into_iter().collect()))
798    }
799
800    fn inspect(&self, id: InspectId) -> String {
801        let Some(i) = self.c.plans.inspect.get(id as usize) else { return format!("Inspect inspect{id}?") };
802        let mut text = format!(
803            "Inspect {}",
804            match &i.target {
805                Inspected::Item(p) => self.place(*p),
806                Inspected::Value(o) => self.operand(o),
807            }
808        );
809        for phrase in &i.tallying {
810            text += &format!(" tallying {}", self.inspect_phrase(phrase));
811        }
812        for phrase in &i.replacing {
813            text += &format!(" replacing {}", self.inspect_phrase(phrase));
814        }
815        if let Some(c) = &i.converting {
816            text += &match &c.table {
817                ConvertTable::Built(pairs) => {
818                    let (from, to): (Vec<u8>, Vec<u8>) = pairs.iter().copied().unzip();
819                    format!(" converting {} to {} {{built}}", self.bytes(&from), self.bytes(&to))
820                }
821                ConvertTable::Operands { from, to } => format!(" converting {} to {}", self.chars(from), self.chars(to)),
822            };
823            text += &self.bounds(&c.bounds);
824        }
825        text
826    }
827
828    fn inspect_phrase(&self, phrase: &InspectPhrase) -> String {
829        let mut text = String::new();
830        if let Some((p, step)) = &phrase.counter {
831            text += &format!("{} [{}] for ", self.place(*p), self.step(step));
832        }
833        text += match phrase.mode {
834            InspectMode::Characters => "CHARACTERS",
835            InspectMode::All => "ALL",
836            InspectMode::Leading => "LEADING",
837            InspectMode::First => "FIRST",
838        };
839        if let Some(pattern) = &phrase.pattern {
840            text += &format!(" {}", self.chars(pattern));
841        }
842        match &phrase.by {
843            None => {}
844            Some(Replacement::Chars(c)) => text += &format!(" by {}", self.chars(c)),
845            Some(Replacement::Fill(b)) => text += &format!(" by fill {}", hex("X", &[*b])),
846        }
847        text + &self.bounds(&phrase.bounds)
848    }
849
850    fn bounds(&self, bounds: &[Bound]) -> String {
851        join(bounds.iter().map(|b| format!(" {} {}", if b.after { "after" } else { "before" }, self.chars(&b.value))), "")
852    }
853
854    fn string_op(&self, id: StringId) -> String {
855        let Some(s) = self.c.plans.string.get(id as usize) else { return format!("String string{id}?") };
856        let sources = s.sources.iter().map(|source| {
857            let delimiter = source.delimiter.as_ref().map_or_else(|| "size".to_owned(), |d| self.chars(d));
858            format!("{} delimited by {delimiter}", self.chars(&source.chars))
859        });
860        let pointer = s.pointer.as_ref().map_or_else(String::new, |p| format!(" pointer {}", self.stored(p)));
861        format!("String {} <- {}{pointer}", self.place(s.into), join(sources, ", "))
862    }
863
864    fn unstring(&self, id: UnstringId) -> String {
865        let Some(u) = self.c.plans.unstring.get(id as usize) else { return format!("Unstring unstring{id}?") };
866        let mut text = format!("Unstring {}", self.place(u.source));
867        if !u.delimiters.is_empty() {
868            let delimiters = u.delimiters.iter().map(|(all, c)| format!("{}{}", if *all { "ALL " } else { "" }, self.chars(c)));
869            text += &format!(" delimited by {}", join(delimiters, ", "));
870        }
871        let into = u.into.iter().map(|i| {
872            let mut field = format!("{} [{}]", self.place(i.target), self.move_plan(&i.plan));
873            if let Some(d) = &i.delimiter {
874                field += &format!(" delimiter in {} [found {}, none {}]", self.place(d.target), self.move_plan(&d.found), self.move_plan(&d.none));
875            }
876            if let Some(c) = &i.count {
877                field += &format!(" count in {}", self.stored(c));
878            }
879            field
880        });
881        text += &format!(" into {}", join(into, ", "));
882        if let Some(p) = &u.pointer {
883            text += &format!(" pointer {}", self.stored(p));
884        }
885        if let Some((p, step)) = &u.tallying {
886            text += &format!(" tallying {} [{}]", self.place(*p), self.step(step));
887        }
888        text
889    }
890
891    fn search_all(&self, id: SearchAllId) -> String {
892        let Some(s) = self.c.plans.search_all.get(id as usize) else { return format!("SearchAll search-all{id}?") };
893        let keys = s.keys.iter().map(|k| {
894            format!("{} = {} [{}{}]", self.comparand(&k.key), self.comparand(&k.value), compare(k.how), if k.ascending { "" } else { ", descending" })
895        });
896        format!("SearchAll {} [{}] within {} when {}", self.place(s.index), self.store(&s.store), self.count(&s.count), join(keys, " and "))
897    }
898
899    fn file_op(&self, id: FileOpId) -> String {
900        let Some(op) = self.c.services.file_ops.get(id as usize) else { return format!("File file-op{id}?") };
901        let file = self.file(op.file);
902        let (text, at_end) = match &op.verb {
903            FileVerb::Open(mode) => (format!("OPEN {} {file}", open_mode(*mode)), false),
904            FileVerb::Close => (format!("CLOSE {file}"), false),
905            FileVerb::CloseWith(closing) => (format!("CLOSE {file} {}", closing_text(*closing)), false),
906            FileVerb::Read { sequential, previous, into, key } => {
907                let mut text = format!("READ {file}");
908                text += &if *sequential { " sequential".to_owned() } else { format!(" key {key}") };
909                if *previous {
910                    text += " previous";
911                }
912                if let Some((p, plan)) = into {
913                    text += &format!(" into {}{}", self.place(*p), self.moved(plan, SenderCheck::None));
914                }
915                (text, *sequential)
916            }
917            FileVerb::Write { record, from, advancing } => {
918                let mut text = format!("WRITE {}{}", self.place(*record), self.sender(from.as_ref()));
919                if let Some(a) = advancing {
920                    text += &format!(" advancing {}", self.advance(a));
921                }
922                (text, false)
923            }
924            FileVerb::Rewrite { record, from } => (format!("REWRITE {}{}", self.place(*record), self.sender(from.as_ref())), false),
925            FileVerb::Delete => (format!("DELETE {file}"), false),
926            FileVerb::Start { rel, key } => {
927                let rel = match rel {
928                    StartRel::Equal => "=",
929                    StartRel::Greater => ">",
930                    StartRel::NotLess => ">=",
931                };
932                let key = match key {
933                    StartKey::Prime => "prime".to_owned(),
934                    StartKey::Named { key, span } => format!("{key} +{} len {}", span.offset, span.len),
935                    StartKey::Relative(value) => format!("relative {}", self.int(value)),
936                    StartKey::RelativeKey => "relative key".to_owned(),
937                };
938                (format!("START {file} key {rel} {key}"), false)
939            }
940        };
941        let (on, not_on) = if at_end { ("at end", "not at end") } else { ("invalid key", "not invalid key") };
942        let mut phrases = phrase_words(op.phrase, on, not_on);
943        phrases.extend(phrase_words(op.end_of_page, "end-of-page", "not end-of-page"));
944        format!("File {text}{}", attrs('{', phrases))
945    }
946
947    fn sender(&self, from: Option<&FromMove>) -> String {
948        from.map_or_else(String::new, |m| format!(" <- {}{}", self.operand(&m.from), self.moved(&m.plan, m.check)))
949    }
950
951    fn advance(&self, a: &Advance) -> String {
952        let side = |before: bool| if before { "before" } else { "after" };
953        match a {
954            Advance::Lines { before, count } => format!("{} {} lines", side(*before), self.int(count)),
955            Advance::Page { before } => format!("{} page", side(*before)),
956            Advance::Mnemonic { before, space } => format!(
957                "{} {}",
958                side(*before),
959                match space {
960                    Spacing::Lines(n) => format!("{n} lines"),
961                    Spacing::Channel(c) => format!("channel {c}"),
962                    Spacing::PageMode => "page mode".to_owned(),
963                }
964            ),
965        }
966    }
967
968    fn call(&self, id: CallId) -> String {
969        let Some(c) = self.c.services.calls.get(id as usize) else { return format!("Call call{id}?") };
970        let target = match &c.target {
971            CallTarget::Named { name, le: None } => self.literal(*name),
972            CallTarget::Named { name, le: Some(service) } => {
973                let service = format!("{service:?}").to_ascii_uppercase();
974                format!("{} or {service}", self.literal(*name))
975            }
976            CallTarget::Dynamic(o) => self.operand(o),
977            CallTarget::Pointer(p) => format!("pointer {}", self.place(*p)),
978            CallTarget::Entry(p) => format!("entry {}", self.place(*p)),
979        };
980        let mut text = format!("Call {target}");
981        if !c.args.is_empty() {
982            let args = c.args.iter().map(|a| match a {
983                CallArg::Reference(p) => self.place(*p),
984                CallArg::Content(chars) => format!("content {}", self.chars(chars)),
985                CallArg::Value(o) => format!("value {}", self.operand(o)),
986                CallArg::Omitted => "omitted".to_owned(),
987            });
988            text += &format!(" using {}", join(args, ", "));
989        }
990        if let Some(r) = c.returning {
991            text += &format!(" returning {}", self.place(r));
992        }
993        text + &attrs('{', [yes(c.on_exception, "on exception"), yes(c.not_on_exception, "not on exception")].into_iter().flatten().collect())
994    }
995
996    fn sort_keys(&self, keys: &SortKeys) -> String {
997        let named = keys.keys.iter().map(|k| {
998            let item = self.items.get(k.item as usize).and_then(|i| i.name).map_or_else(|| format!("+{} len {}", k.offset, k.len), |s| self.name(s));
999            format!("{} {item}{}", if k.ascending { "ascending" } else { "descending" }, if k.collated { " collated" } else { "" })
1000        });
1001        format!("keys {}{}", join(named, ", "), if keys.collating.is_some() { " {collating sequence}" } else { "" })
1002    }
1003
1004    fn sort(&self, id: SortId) -> String {
1005        match self.c.services.sorts.get(id as usize) {
1006            None => format!("Sort sort{id}?"),
1007            Some(SortPlan::File(s)) => {
1008                let io = |io: &Option<SortIo>, files: &str, procedure: &str| match io {
1009                    None => format!("{procedure} none"),
1010                    Some(SortIo::Files(list)) => format!("{files} {}", join(list.iter().map(|&k| self.file(k)), " ")),
1011                    Some(SortIo::Procedure(r)) => format!("{procedure} procedure {}", self.range(*r)),
1012                };
1013                format!(
1014                    "Sort {} {} {} {} {}{}",
1015                    if s.merge { "merge" } else { "file" },
1016                    self.file(s.sd),
1017                    self.sort_keys(&s.keys),
1018                    io(&s.input, "using", "input"),
1019                    io(&s.output, "giving", "output"),
1020                    attrs('{', vec![format!("sort-return {}", self.place(s.sort_return)), format!("sort-control {}", self.place(s.sort_control))])
1021                )
1022            }
1023            Some(SortPlan::Table(t)) => {
1024                format!("Sort table {} first {} within {} stride {} {}", self.name(t.name), self.place(t.first), self.count(&t.count), t.stride, self.sort_keys(&t.keys))
1025            }
1026        }
1027    }
1028
1029    fn release(&self, id: ReleaseId) -> String {
1030        let Some(r) = self.c.services.releases.get(id as usize) else { return format!("Release release{id}?") };
1031        let file = r.file.map_or_else(|| self.name(r.name), |k| self.file(k));
1032        format!("Release {}{}{}", self.place(r.record), self.sender(r.from.as_ref()), attrs('{', vec![format!("file {file}"), format!("sort-return {}", self.place(r.sort_return))]))
1033    }
1034
1035    fn return_op(&self, id: ReturnId) -> String {
1036        let Some(r) = self.c.services.returns.get(id as usize) else { return format!("Return return{id}?") };
1037        let file = r.file.map_or_else(|| self.name(r.name), |k| self.file(k));
1038        let into = r.into.as_ref().map_or_else(String::new, |(p, plan)| format!(" into {}{}", self.place(*p), self.moved(plan, SenderCheck::None)));
1039        format!("Return {file}{into}{}", attrs('{', vec![format!("sort-return {}", self.place(r.sort_return))]))
1040    }
1041
1042    fn report_name(&self, report: u32) -> String {
1043        self.c.services.report.reports.get(report as usize).map_or_else(|| format!("report{report}?"), |r| word(&r.name))
1044    }
1045
1046    fn report_op(&self, op: &ReportOp) -> String {
1047        match *op {
1048            ReportOp::Initiate(r) => format!("Report Initiate {}", self.report_name(r)),
1049            ReportOp::Generate { report, detail } => {
1050                let group = detail.map_or_else(String::new, |d| {
1051                    let name = self.c.services.report.reports.get(report as usize).and_then(|r| r.groups.get(d as usize)).and_then(|g| g.name.as_deref());
1052                    format!(" detail {}", name.map_or_else(|| format!("group{d}"), word))
1053                });
1054                format!("Report Generate {}{group}", self.report_name(report))
1055            }
1056            ReportOp::Terminate(r) => format!("Report Terminate {}", self.report_name(r)),
1057            ReportOp::Suppress => "Report Suppress".to_owned(),
1058        }
1059    }
1060
1061    fn invoke(&self, id: InvokeId) -> String {
1062        let Some(i) = self.c.services.invokes.get(id as usize) else { return format!("Invoke invoke{id}?") };
1063        let receiver = match &i.receiver {
1064            Receiver::SelfRef => "SELF".to_owned(),
1065            Receiver::Super => "SUPER".to_owned(),
1066            Receiver::Class { name, external } if name == external => format!("class {}", self.name(*name)),
1067            Receiver::Class { name, external } => format!("class {} external {}", self.name(*name), self.string(*external)),
1068            Receiver::Object(p) => self.place(*p),
1069        };
1070        let method = match &i.method {
1071            MethodName::New => "NEW".to_owned(),
1072            MethodName::Named(s) => self.literal(*s),
1073            MethodName::Dynamic(p) => self.place(*p),
1074        };
1075        let mut text = format!("Invoke {receiver} {method}");
1076        if !i.args.is_empty() {
1077            text += &format!(" using {}", join(i.args.iter().map(|(o, sig)| format!("{} as {}", self.operand(o), self.string(*sig))), ", "));
1078        }
1079        if let Some((p, sig)) = &i.returning {
1080            text += &format!(" returning {} as {}", self.place(*p), self.string(*sig));
1081        }
1082        text + &attrs('{', [yes(i.on_exception, "on exception"), yes(i.not_on_exception, "not on exception")].into_iter().flatten().collect())
1083    }
1084
1085    fn cics(&self, id: CicsId) -> String {
1086        let Some(written) = self.c.services.cics.get(id as usize) else { return format!("Cics cics{id}?") };
1087        let Ok(CicsCommand { name: _, command, resp, sinks }) = written.clone().map(&mut Names(self));
1088        let mut words = vec![command.name().to_owned()];
1089        self.cics_options(&command, &mut words);
1090        option(&mut words, "RESP", &resp.resp);
1091        option(&mut words, "RESP2", &resp.resp2);
1092        flag(&mut words, "NOHANDLE", resp.nohandle);
1093        let mut how = Vec::new();
1094        if self.symbol(written.name) != Some(command.name()) {
1095            how.push(format!("written {}", self.string(written.name)));
1096        }
1097        if !sinks.is_empty() {
1098            how.push(format!("sinks {}", join(sinks.iter().map(|(p, s)| format!("{p} {}", s.kind())), " ")));
1099        }
1100        format!("Cics {}{}", words.join(" "), attrs('{', how))
1101    }
1102
1103    fn sql(&self, ordinal: SqlId) -> String {
1104        let Some(e) = (ordinal as usize).checked_sub(1).and_then(|k| self.sql.get(k)) else { return format!("Sql {ordinal}") };
1105        let text = if self.symbol(e.text).is_some_and(|t| !t.is_empty()) { self.string(e.text) } else { self.name(e.verb) };
1106        let hosts = |key: &str, list: &[super::HostPlace]| if list.is_empty() { String::new() } else { format!(" {key} ({})", join(list.iter().map(|h| self.host(h)), ", ")) };
1107        let statement = match &e.statement {
1108            SqlStatement::Query { inputs, into } => format!("query{}{}", hosts("inputs", inputs), hosts("into", into)),
1109            SqlStatement::Change { delete, inputs, current_of } => {
1110                let current = current_of.map_or_else(String::new, |c| format!(" current of {}", self.name(c)));
1111                format!("{}{}{current}", if *delete { "delete" } else { "change" }, hosts("inputs", inputs))
1112            }
1113            SqlStatement::Open { cursor, inputs } => format!("open {}{}", self.name(*cursor), hosts("inputs", inputs)),
1114            SqlStatement::Fetch { cursor, into } => format!("fetch {}{}", self.name(*cursor), hosts("into", into)),
1115            SqlStatement::Close { cursor } => format!("close {}", self.name(*cursor)),
1116            SqlStatement::Commit => "commit".to_owned(),
1117            SqlStatement::Rollback => "rollback".to_owned(),
1118            SqlStatement::Declaration => "declaration".to_owned(),
1119            SqlStatement::Unsupported(what) => format!("unsupported {}", self.string(*what)),
1120            SqlStatement::Connect { what, location } => format!("connect {}{}", self.string(*what), hosts("location", location)),
1121            SqlStatement::Prepare { name, source } => format!("prepare {}{}", self.name(*name), hosts("from", source)),
1122            SqlStatement::ExecuteImmediate { source } => format!("execute immediate{}", hosts("from", source)),
1123            SqlStatement::Execute { name, inputs } => format!("execute {}{}", self.name(*name), hosts("using", inputs)),
1124            SqlStatement::OpenPrepared { cursor, statement, inputs } => format!("open {} for {}{}", self.name(*cursor), self.name(*statement), hosts("using", inputs)),
1125        };
1126        format!("Sql {ordinal} {text} {statement}{}", attrs('{', yes(e.with_hold, "with hold").into_iter().collect()))
1127    }
1128
1129    fn host(&self, h: &super::HostPlace) -> String {
1130        let mut text = self.place(h.var);
1131        if let Some((offset, len)) = h.member {
1132            text += &format!(" member +{offset} len {len}");
1133        }
1134        text += &match &h.ty {
1135            Ok(ty) => format!(" [{}]", host_type(ty)),
1136            Err(a) => format!(" [refused {}]", abend_ref(*a)),
1137        };
1138        if let Some((p, offset)) = h.indicator {
1139            text += &format!(" indicator {} +{offset}", self.place(p));
1140        }
1141        text
1142    }
1143
1144    fn ccsid(&self, c: &Ccsid) -> Option<String> {
1145        match c {
1146            Ccsid::Unnamed => None,
1147            Ccsid::CodePage => Some("encoding codepage".to_owned()),
1148            Ccsid::Operand(o) => Some(format!("encoding {}", self.operand(o))),
1149        }
1150    }
1151
1152    fn markup(&self, id: MarkupId) -> String {
1153        let Some(m) = self.c.services.markup.get(id as usize) else { return format!("Markup markup{id}?") };
1154        let subscripts = |s: &[IntExpr]| (!s.is_empty()).then(|| format!("subscripts ({})", join(s.iter().map(|e| self.int(e)), ", ")));
1155        let (verb, words) = match m {
1156            Markup::JsonGenerate(g) => {
1157                let name = g.name.map_or_else(|| "name omitted".to_owned(), |n| format!("name {}", self.raw(n)));
1158                let mut words = vec![format!("{} <- {}", self.place(g.receiver), self.place(g.from))];
1159                words.extend(subscripts(&g.subscripts));
1160                words.push(name);
1161                words.extend(self.ccsid(&g.encoding));
1162                words.extend(g.count.as_ref().map(|c| format!("count {}", self.stored(c))));
1163                words.push(format!("code {}", self.stored(&g.code)));
1164                ("JSON GENERATE", words)
1165            }
1166            Markup::XmlGenerate(g) => {
1167                let mut words = vec![format!("{} <- {}", self.place(g.receiver), self.place(g.from))];
1168                words.extend(subscripts(&g.subscripts));
1169                words.extend(self.ccsid(&g.encoding));
1170                words.extend(g.namespace.as_ref().map(|o| format!("namespace {}", self.operand(o))));
1171                words.extend(g.prefix.as_ref().map(|o| format!("prefix {}", self.operand(o))));
1172                words.extend(yes(g.declaration, "declaration"));
1173                words.extend(yes(g.suppressing, "suppress"));
1174                words.extend(g.count.as_ref().map(|c| format!("count {}", self.stored(c))));
1175                words.push(format!("code {}", self.stored(&g.code)));
1176                ("XML GENERATE", words)
1177            }
1178            Markup::XmlParse(p) => {
1179                let mut words = vec![self.place(p.document)];
1180                words.extend(p.encoding.as_ref().map(|o| format!("encoding {}", self.operand(o))));
1181                words.extend(yes(p.national, "returning national"));
1182                words.push(format!("procedure {}", self.range(p.procedure)));
1183                words.push(format!("event {}", self.place(p.event)));
1184                words.push(format!("code {}", self.stored(&p.code)));
1185                words.push(format!("information {}", self.stored(&p.information)));
1186                words.push(format!("code-value {}", self.int(&p.code_value)));
1187                ("XML PARSE", words)
1188            }
1189            Markup::JsonParse(p) => {
1190                let mut words = vec![format!("{} <- {}", self.place(p.into), self.place(p.source))];
1191                words.extend(subscripts(&p.subscripts));
1192                words.extend(self.ccsid(&p.encoding));
1193                words.extend(yes(p.ignore_all, "ignoring null for all"));
1194                words.push(format!("code {}", self.stored(&p.code)));
1195                words.push(format!("status {}", self.stored(&p.status)));
1196                ("JSON PARSE", words)
1197            }
1198        };
1199        let (on, not_on) = m.phrases();
1200        let phrases = [yes(on, "on exception"), yes(not_on, "not on exception")].into_iter().flatten().collect();
1201        format!("Markup m{id} {verb} {}{}", words.join(" "), attrs('{', phrases))
1202    }
1203
1204    fn tables(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1205        for (k, p) in self.c.places.iter().enumerate() {
1206            writeln!(f, "{}", self.place_row(k, p))?;
1207        }
1208        for (k, c) in self.c.consts.iter().enumerate() {
1209            writeln!(f, "const c{k} {}", self.constant(c))?;
1210        }
1211        for (k, a) in self.c.abends.iter().enumerate() {
1212            writeln!(f, "abend a{k} {} {}{}", a.code, self.string(a.message), a.at.map_or_else(String::new, |at| self.at(at)))?;
1213        }
1214        let s = self.c.services;
1215        for (k, d) in s.files.iter().enumerate() {
1216            writeln!(f, "file {k} {}", self.file_row(d))?;
1217        }
1218        for (k, e) in s.entries.iter().enumerate() {
1219            let using = join(e.using.iter().map(|&r| self.linkage(r)), " ");
1220            writeln!(f, "entry {k} {} paragraph {} block {} using ({using})", self.name(e.name), self.para(e.paragraph), label(e.block))?;
1221        }
1222        for (k, plan) in self.c.plans.init.iter().enumerate() {
1223            for field in &plan.fields {
1224                let value = match &field.value {
1225                    InitValue::Default(v) => figurative(*v).to_owned(),
1226                    InitValue::Value(c) => self.konst(*c),
1227                    InitValue::Replacing(o) => format!("replacing {}", self.operand(o)),
1228                };
1229                let scaling = if field.scaling == 0 { String::new() } else { format!(" scaling {}", field.scaling) };
1230                writeln!(f, "init {k} +{} len {} <- {value}{}{scaling}", field.offset, field.len, self.moved(&field.store, SenderCheck::None))?;
1231            }
1232        }
1233        for (field, p, ty) in &s.sqlca.fields {
1234            writeln!(f, "sqlca {} {} [{}]", sqlca_field(*field), self.place(*p), host_type(ty))?;
1235        }
1236        let modes = s.declaratives.modes.iter().zip(MODES).filter_map(|(r, mode)| r.map(|r| format!("{mode} {}", self.range(r))));
1237        for mode in modes {
1238            writeln!(f, "declaratives {mode}")?;
1239        }
1240        if let Some((offset, len)) = s.declaratives.debug_item {
1241            writeln!(f, "declaratives debug-item program+{offset} len {len}")?;
1242        }
1243        self.scope(f)?;
1244        if let Some(d) = &s.function {
1245            writeln!(f, "function params ({}) returning {}", self.places(&d.params), self.place(d.returning))?;
1246        }
1247        self.reports(f)?;
1248        for (k, m) in s.markup.iter().enumerate() {
1249            self.markup_nodes(f, k, m)?;
1250        }
1251        if let Some(class) = &s.class {
1252            self.class(f, class)?;
1253        }
1254        Ok(())
1255    }
1256
1257    fn place_row(&self, k: usize, p: &Place) -> String {
1258        let mut text = format!("place p{k} {} {}+{} len {} {}", self.reference(p, &[]), base(p.base), p.offset, p.len, kind(&p.kind));
1259        if p.scaling != 0 {
1260            text += &format!(" scaling {}", p.scaling);
1261        }
1262        for o in &p.moved {
1263            text += &format!(" moved {}", self.odo(o));
1264        }
1265        for s in &p.subscripts {
1266            text += &format!(" stride {}", s.stride);
1267            if let Some(n) = s.check {
1268                text += &format!(" check {n}");
1269            }
1270        }
1271        for o in &p.odo {
1272            text += &format!(" odo {}", self.odo(o));
1273        }
1274        if p.refmod.as_ref().is_some_and(|r| r.check) {
1275            text += " refmod check";
1276        }
1277        match p.numcheck.lax {
1278            None => {}
1279            Some(LaxRedefinition::Signed) => text += " numcheck lax signed",
1280            Some(LaxRedefinition::LeadingSpaces(n)) => text += &format!(" numcheck lax {n} leading spaces"),
1281        }
1282        if p.numcheck.removed {
1283            text += " numcheck removed";
1284        }
1285        text + &self.at(p.at)
1286    }
1287
1288    fn file_row(&self, d: &FileDesc) -> String {
1289        let organization = match d.organization {
1290            Organization::Sequential => "sequential",
1291            Organization::LineSequential => "line-sequential",
1292            Organization::Indexed => "indexed",
1293            Organization::Relative => "relative",
1294        };
1295        let access = match d.access {
1296            Access::Sequential => "sequential",
1297            Access::Random => "random",
1298            Access::Dynamic => "dynamic",
1299        };
1300        let format = match d.format {
1301            Format::Fixed => "fixed",
1302            Format::Variable => "variable",
1303            Format::Text => "text",
1304        };
1305        let mut text = format!("{} assign {} {organization} access {access} format {format} read {} to {}", self.name(d.name), self.name(d.assign), d.read_lengths.0, d.read_lengths.1);
1306        let flags = [yes(d.optional, "optional"), yes(d.fixed, "fixed-length"), yes(d.sort, "sort")];
1307        text += &join(flags.into_iter().flatten().map(|s| format!(" {s}")), "");
1308        if let Some(min) = d.record_min {
1309            text += &format!(" record-min {min}");
1310        }
1311        if let Some(dep) = &d.depending {
1312            text += &format!(" depending {} from {} to {}", self.place(dep.item), dep.lengths.0, dep.lengths.1);
1313        }
1314        if let Some((p, plan)) = &d.status {
1315            text += &format!(" status {}{}", self.place(*p), self.moved(plan, SenderCheck::None));
1316        }
1317        if let Some(keys) = &d.keys {
1318            text += &format!(" key +{} len {}", keys.prime.offset, keys.prime.len);
1319            for (span, duplicates) in &keys.alternates {
1320                text += &format!(" alternate +{} len {}{}", span.offset, span.len, if *duplicates { " duplicates" } else { "" });
1321            }
1322        }
1323        if let Some(r) = &d.relative {
1324            let digits = r.digits.map_or_else(String::new, |n| format!(" digits {n}"));
1325            text += &format!(" relative-key {} [{}] value {}{digits}", self.place(r.place), self.store(&r.store), self.int(&r.value));
1326        }
1327        if let Some(l) = &d.linage {
1328            text += &format!(" linage {}", self.int(&l.lines));
1329            for (key, value) in [("footing", &l.footing), ("top", &l.top), ("bottom", &l.bottom)] {
1330                if let Some(v) = value {
1331                    text += &format!(" {key} {}", self.int(v));
1332                }
1333            }
1334            if let Some(c) = &l.counter {
1335                text += &format!(" counter {}", self.stored(c));
1336            }
1337        }
1338        if let Some(c) = d.carriage {
1339            text += &format!(" carriage {}{}", if c.machine { "machine" } else { "asa" }, if c.reserved { " reserved" } else { "" });
1340        }
1341        if let Some(r) = d.error {
1342            text += &format!(" error {}", self.range(r));
1343        }
1344        if let Some(a) = d.assign_item {
1345            text += &format!(" assign-item {} @{}", self.place(a.place), a.select);
1346        }
1347        text
1348    }
1349
1350    fn scope(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1351        let s = &self.c.services.scope;
1352        for &c in &s.containers {
1353            writeln!(f, "scope container {}", self.name(c))?;
1354        }
1355        for (record, binding) in &s.records {
1356            let binding = match binding {
1357                Binding::External { name, size } => format!("external {} size {size}", self.name(*name)),
1358                Binding::ExternalFile(k) => format!("external file {}", self.file(*k)),
1359                Binding::Global { program, section, name } => format!("global {} of {} in {}", self.name(*name), scope_section(*section), self.name(*program)),
1360            };
1361            writeln!(f, "scope record {} {binding}", self.linkage(*record))?;
1362        }
1363        for shared in &s.files {
1364            let from = match (shared.external, shared.declared_in) {
1365                (true, _) => "external".to_owned(),
1366                (false, Some(p)) => format!("global in {}", self.name(p)),
1367                (false, None) => "global".to_owned(),
1368            };
1369            writeln!(f, "scope file {} {from}", self.file(shared.file))?;
1370        }
1371        for (file, record) in &s.areas {
1372            writeln!(f, "scope area {} {}", self.file(*file), self.linkage(*record))?;
1373        }
1374        for g in &s.globals {
1375            let at = match g.at {
1376                GlobalAt::Program(offset) => format!("program+{offset}"),
1377                GlobalAt::Local(offset) => format!("local+{offset}"),
1378                GlobalAt::Linkage(record) => self.linkage(record),
1379            };
1380            writeln!(f, "scope global {} of {} at {at}", self.name(g.name), scope_section(g.section))?;
1381        }
1382        for (file, range) in &s.global_files {
1383            writeln!(f, "scope global-error {} {}", self.file(*file), self.range(*range))?;
1384        }
1385        for (mode, r) in MODES.iter().zip(&s.global_modes) {
1386            if let Some(r) = r {
1387                writeln!(f, "scope global-error {mode} {}", self.range(*r))?;
1388            }
1389        }
1390        Ok(())
1391    }
1392
1393    fn reports(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1394        let w = &self.c.services.report;
1395        if let Some(p) = w.print_switch {
1396            writeln!(f, "report print-switch {}", self.place_at(p))?;
1397        }
1398        for (k, r) in w.reports.iter().enumerate() {
1399            let mut text = format!("report {k} {} file {} width {}", word(&r.name), self.file_at(r.file), r.width);
1400            if let Some(c) = r.code {
1401                text += &format!(" code {}", self.konst(c));
1402            }
1403            if let Some(p) = r.page {
1404                text += &format!(" page limit {} heading {} first-detail {} last-detail {} footing {}", p.limit, p.heading, p.first_detail, p.last_detail, p.footing);
1405            }
1406            text += &format!(" page-counter {} line-counter {} state {}", self.place_at(r.page_counter), self.place_at(r.line_counter), self.place_at(r.state));
1407            let headings = [("report-heading", r.report_heading), ("page-heading", r.page_heading), ("page-footing", r.page_footing), ("report-footing", r.report_footing)];
1408            for (key, group) in headings {
1409                if let Some(g) = group {
1410                    text += &format!(" {key} group {g}");
1411                }
1412            }
1413            for (key, list) in [("control-headings", &r.control_headings), ("control-footings", &r.control_footings)] {
1414                if list.iter().any(Option::is_some) {
1415                    text += &format!(" {key} ({})", join(list.iter().map(|g| g.map_or_else(|| "-".to_owned(), |g| g.to_string())), " "));
1416                }
1417            }
1418            if let Some(n) = r.first_detail_written {
1419                text += &format!(" first-detail-written {n}");
1420            }
1421            writeln!(f, "{text}")?;
1422            for (c, control) in r.controls.iter().enumerate() {
1423                writeln!(f, "report {k} control {c} {} saved +{} len {}", self.place(control.reference), control.saved, control.len)?;
1424            }
1425            for (g, group) in r.groups.iter().enumerate() {
1426                let name = group.name.as_deref().map_or_else(|| "-".to_owned(), word);
1427                let mut text = format!("report {k} group {g} {name} {} level {}", group_kind(group.kind), group.level);
1428                if let Some(n) = group.next_group {
1429                    text += &format!(" next-group {}", next_group(n));
1430                }
1431                if let Some(i) = group.indicate {
1432                    text += &format!(" indicate +{i}");
1433                }
1434                if let Some(r) = group.declarative {
1435                    text += &format!(" declarative {}", self.range(r));
1436                }
1437                if !group.totals.is_empty() {
1438                    text += &format!(" totals ({})", join(group.totals.iter().map(|t| format!("sum{t}")), " "));
1439                }
1440                writeln!(f, "{text}")?;
1441                for (l, line) in group.lines.iter().enumerate() {
1442                    writeln!(f, "report {k} group {g} line {l} {}", line_number(line.number))?;
1443                    for field in &line.fields {
1444                        writeln!(f, "report {k} group {g} line {l} {}", self.report_field(field))?;
1445                    }
1446                }
1447                for field in &group.unprinted {
1448                    writeln!(f, "report {k} group {g} unprinted {}", self.report_field(field))?;
1449                }
1450                for (key, list) in [("cross", &group.cross), ("roll", &group.rolls)] {
1451                    for (sum, origin) in list {
1452                        writeln!(f, "report {k} group {g} {key} sum{sum} <- {}", self.origin(origin))?;
1453                    }
1454                }
1455            }
1456            for (s, sum) in r.sums.iter().enumerate() {
1457                let reset = sum.reset.map_or_else(String::new, |l| format!(" reset on control {l}"));
1458                writeln!(f, "report {k} sum{s} {}{reset}", self.place_at(sum.total))?;
1459            }
1460            for t in &r.subtotals {
1461                let adding = match &t.adding {
1462                    Adding::EveryGenerate => "every generate".to_owned(),
1463                    Adding::Upon(groups) => format!("upon groups ({})", join(groups.iter().map(usize::to_string), " ")),
1464                    Adding::Correlated(groups) => format!("correlated groups ({})", join(groups.iter().map(usize::to_string), " ")),
1465                };
1466                writeln!(f, "report {k} subtotal sum{} <- {} {adding}", t.sum, self.comparand(&t.operand))?;
1467            }
1468        }
1469        Ok(())
1470    }
1471
1472    fn place_at(&self, place: usize) -> String {
1473        u32::try_from(place).map_or_else(|_| format!("p{place}?"), |p| self.place(p))
1474    }
1475
1476    fn report_field(&self, field: &Field) -> String {
1477        let content = match &field.content {
1478            FieldContent::Source(x) => format!("source {}", self.comparand(x)),
1479            FieldContent::Value(v) => format!("value {}", self.konst(*v)),
1480            FieldContent::Sum(s) => format!("sum{s}"),
1481            FieldContent::Program => "program".to_owned(),
1482        };
1483        let flags = [yes(field.group_indicate, "group indicate"), yes(field.blank_when_zero, "blank-when-zero"), yes(field.rounded, "rounded")];
1484        format!("column {} {} <- {content}{}  @{}", field.column, self.place_at(field.item), attrs('{', flags.into_iter().flatten().collect()), self.position(&field.pos))
1485    }
1486
1487    fn origin(&self, origin: &Origin) -> String {
1488        match origin {
1489            Origin::Source(x) => self.comparand(x),
1490            Origin::Value(v) => self.konst(*v),
1491            Origin::Total(s) => format!("sum{s}"),
1492        }
1493    }
1494
1495    fn markup_nodes(&self, f: &mut fmt::Formatter<'_>, k: usize, m: &Markup) -> fmt::Result {
1496        let rows: Vec<String> = match m {
1497            Markup::JsonGenerate(g) => g.nodes.iter().map(|n| self.json_node(n)).collect(),
1498            Markup::XmlGenerate(g) => g.nodes.iter().map(|n| self.xml_node(n)).collect(),
1499            Markup::XmlParse(_) => Vec::new(),
1500            Markup::JsonParse(p) => p.nodes.iter().map(|n| self.parse_node(n)).collect(),
1501        };
1502        for (n, row) in rows.iter().enumerate() {
1503            writeln!(f, "markup m{k} node {n} {row}")?;
1504        }
1505        Ok(())
1506    }
1507
1508    /// What every node of a markup tree has: where it is, its kind, and how often it occurs.
1509    fn node(&self, name: String, offset: u32, moved: &[Odo], len: u32, kind_of: &Kind, occurs: Option<&Count>) -> String {
1510        let mut text = format!("{name} +{offset} len {len} {}", kind(kind_of));
1511        if let Some(c) = occurs {
1512            text += &format!(" occurs {}", self.count(c));
1513        }
1514        for o in moved {
1515            text += &format!(" moved {}", self.odo(o));
1516        }
1517        text
1518    }
1519
1520    fn marker(&self, m: &Marker) -> String {
1521        match m {
1522            Marker::Byte(Some(b)) => format!("byte {}", hex("X", &[*b])),
1523            Marker::Byte(None) => "byte none".to_owned(),
1524            Marker::Condition(c) => format!("condition ({})", self.cond(*c)),
1525            Marker::Refused(a) => format!("refused {}", abend_ref(*a)),
1526        }
1527    }
1528
1529    fn convert(c: &Convert) -> String {
1530        match c {
1531            Convert::Chars { justified } => if *justified { "chars justified" } else { "chars" }.to_owned(),
1532            Convert::National => "national".to_owned(),
1533            Convert::Dbcs => "dbcs".to_owned(),
1534            Convert::Float(p) => format!("float {}", precision(*p)),
1535            Convert::Fixed { integers } => format!("fixed {integers} integers"),
1536            Convert::Scaled { integers, scaling } => format!("fixed {integers} integers scaled {scaling}"),
1537            Convert::Refused(a) => format!("refused {}", abend_ref(*a)),
1538        }
1539    }
1540
1541    fn suppress(list: &[Figurative]) -> String {
1542        if list.is_empty() { String::new() } else { format!(" suppress {}", join(list.iter().map(|f| figurative(*f).to_owned()), " ")) }
1543    }
1544
1545    fn json_node(&self, n: &JsonNode) -> String {
1546        let mut text = self.node(self.raw(n.name), n.offset, &n.moved, n.len, &n.kind, n.occurs.as_ref());
1547        if let Some((place, marker)) = &n.indicator {
1548            let place = place.map_or_else(|a| format!("refused {}", abend_ref(a)), |p| self.place(p));
1549            text += &format!(" indicator {place} {}", self.marker(marker));
1550        }
1551        if let Some(null) = n.null {
1552            text += &format!(" null {}", figurative(null));
1553        }
1554        text + &match &n.value {
1555            JsonValue::Object { members, eligible } => format!(" object ({}){}", members_list(members), if *eligible { " eligible" } else { "" }),
1556            JsonValue::Leaf(JsonLeaf { suppress, boolean, convert }) => {
1557                let boolean = boolean.as_ref().map_or_else(String::new, |m| format!(" boolean {}", self.marker(m)));
1558                format!(" leaf{}{boolean} {}", Self::suppress(suppress), Self::convert(convert))
1559            }
1560        }
1561    }
1562
1563    fn xml_node(&self, n: &XmlNode) -> String {
1564        let text = self.node(self.string(n.name), n.offset, &n.moved, n.len, &n.kind, n.occurs.as_ref());
1565        text + &match &n.value {
1566            XmlValue::Element { members } => format!(" element ({})", members_list(members)),
1567            XmlValue::Members { members } => format!(" members ({})", members_list(members)),
1568            XmlValue::Leaf { form, suppress, convert } => {
1569                let form = match form {
1570                    XmlForm::Attribute => "attribute",
1571                    XmlForm::Element => "element",
1572                    XmlForm::Content => "content",
1573                };
1574                format!(" leaf {form}{} {}", Self::suppress(suppress), Self::convert(convert))
1575            }
1576        }
1577    }
1578
1579    fn set_to(&self, s: &SetTo) -> String {
1580        match s {
1581            SetTo::Nothing => "nothing".to_owned(),
1582            SetTo::Move { place, value, plan } => format!("{} <- {}{}", self.place(*place), self.konst(*value), self.moved(plan, SenderCheck::None)),
1583            SetTo::Refused(a) => format!("refused {}", abend_ref(*a)),
1584        }
1585    }
1586
1587    fn flag(&self, flag: &Flag) -> String {
1588        match flag {
1589            Flag::Set { on, off } => format!("set on {} off {}", self.set_to(on), self.set_to(off)),
1590            Flag::Literals { on, off } => format!(
1591                "literals on {}{} off {}{}",
1592                self.konst(on.0),
1593                self.moved(&on.1, SenderCheck::None),
1594                self.konst(off.0),
1595                self.moved(&off.1, SenderCheck::None)
1596            ),
1597        }
1598    }
1599
1600    fn parse_node(&self, n: &ParseNode) -> String {
1601        let name = match n.name {
1602            Named::Exactly(s) => self.string(s),
1603            Named::Folded(s) => format!("folded {}", self.name(s)),
1604            Named::Omitted => "omitted".to_owned(),
1605        };
1606        let mut text = self.node(name, n.offset, &n.moved, n.len, &n.kind, n.occurs.as_ref());
1607        if n.ignored {
1608            text += " ignoring null";
1609        }
1610        if let Some(Indicator { place, flag }) = &n.indicator {
1611            let place = match place {
1612                None => "-".to_owned(),
1613                Some(Ok(p)) => self.place(*p),
1614                Some(Err(a)) => format!("refused {}", abend_ref(*a)),
1615            };
1616            text += &format!(" indicator {place} {}", self.flag(flag));
1617        }
1618        if let Some((null, plan)) = &n.null {
1619            text += &format!(" null {}{}", figurative(*null), self.moved(plan, SenderCheck::None));
1620        }
1621        text + &match &n.value {
1622            ParseValue::Object { members } => format!(" object ({})", members_list(members)),
1623            ParseValue::Suppressed => " suppressed".to_owned(),
1624            ParseValue::Leaf(ParseLeaf { boolean, text, number }) => {
1625                let mut leaf = " leaf".to_owned();
1626                if let Some(b) = boolean {
1627                    leaf += &format!(" boolean {}", self.flag(b));
1628                }
1629                if let Some(plan) = text {
1630                    leaf += &format!(" text{}", self.moved(plan, SenderCheck::None));
1631                }
1632                leaf + &match number {
1633                    NumberInto::Float(plan) => format!(" number float{}", self.moved(plan, SenderCheck::None)),
1634                    NumberInto::Store(store) => format!(" number [{}]", self.store(store)),
1635                    NumberInto::Edited(plan) => format!(" number edited{}", self.moved(plan, SenderCheck::None)),
1636                    NumberInto::Digits => " number digits".to_owned(),
1637                    NumberInto::Incompatible => " number incompatible".to_owned(),
1638                    NumberInto::StoreScaled { store, scaling } => format!(" number [{}] scaled {scaling}", self.store(store)),
1639                    NumberInto::EditedScaled { plan, scaling } => format!(" number edited{} scaled {scaling}", self.moved(plan, SenderCheck::None)),
1640                }
1641            }
1642        }
1643    }
1644
1645    /// A class's data and methods are programs of their own, each printed after its heading.
1646    fn class(&self, f: &mut fmt::Formatter<'_>, class: &Class) -> fmt::Result {
1647        writeln!(f, "class {} inherits {}", self.string(class.external), self.string(class.parent))?;
1648        for (key, part) in [("factory", &class.factory), ("object", &class.object)] {
1649            if let Some(part) = part {
1650                writeln!(f, "class {key} data records ({})", join(part.records.iter().map(|r| format!("+{r}")), " "))?;
1651                write!(f, "{}", Listing::of(&part.data))?;
1652            }
1653        }
1654        for (k, m) in class.methods.iter().enumerate() {
1655            let params = join(m.params.iter().map(|&s| self.string(s)), " ");
1656            let returns = m.returns.map_or_else(String::new, |s| format!(" returns {}", self.string(s)));
1657            let factory = if m.factory { " factory" } else { "" };
1658            writeln!(f, "class method {k} {}{factory} params ({params}){returns} own-records {}", self.name(m.name), m.own_records)?;
1659            write!(f, "{}", Listing::of(&m.code))?;
1660        }
1661        Ok(())
1662    }
1663}
1664
1665/// Maps a CICS command's handles to their text.
1666struct Names<'p, 'a>(&'p Printer<'a>);
1667
1668impl Handles<PlaceId, Operand, SymId> for Names<'_, '_> {
1669    type Place = String;
1670    type Value = String;
1671    type Text = String;
1672    type Error = Infallible;
1673
1674    fn place(&mut self, place: PlaceId) -> Result<String, Infallible> {
1675        Ok(self.0.place(place))
1676    }
1677
1678    fn value(&mut self, value: Operand) -> Result<String, Infallible> {
1679        Ok(self.0.operand(&value))
1680    }
1681
1682    fn text(&mut self, text: SymId) -> Result<String, Infallible> {
1683        Ok(self.0.name(text))
1684    }
1685}
1686
1687type CicsOpt = Option<Datum<String, String, String>>;
1688
1689fn option(words: &mut Vec<String>, key: &str, value: &CicsOpt) {
1690    match value {
1691        None => {}
1692        Some(Datum::Place(v) | Datum::Value(v) | Datum::Text(v)) => words.push(format!("{key}({v})")),
1693        Some(Datum::Bare) => words.push(key.to_owned()),
1694    }
1695}
1696
1697fn flag(words: &mut Vec<String>, key: &str, on: bool) {
1698    if on {
1699        words.push(key.to_owned());
1700    }
1701}
1702
1703impl Printer<'_> {
1704    /// A command's options as EXEC CICS writes them, in the order of its fields.
1705    fn cics_options(&self, command: &Cics<String, String, String>, w: &mut Vec<String>) {
1706        match command {
1707            Cics::File { verb: _, file, options: o } => {
1708                option(w, "FILE", file);
1709                for (key, value) in [("RIDFLD", &o.ridfld), ("KEYLENGTH", &o.keylength), ("REQID", &o.reqid), ("FROM", &o.from), ("NUMREC", &o.numrec)] {
1710                    option(w, key, value);
1711                }
1712                for (key, value) in [("INTO", &o.record.into), ("SET", &o.record.set), ("LENGTH", &o.record.length)] {
1713                    option(w, key, value);
1714                }
1715                for (key, on) in [("GENERIC", o.generic), ("RRN", o.rrn), ("GTEQ", o.gteq), ("EQUAL", o.equal), ("UPDATE", o.update)] {
1716                    flag(w, key, on);
1717                }
1718            }
1719            Cics::Return { transid, commarea, length, channel, immediate } => {
1720                for (key, value) in [("TRANSID", transid), ("COMMAREA", commarea), ("LENGTH", length), ("CHANNEL", channel)] {
1721                    option(w, key, value);
1722                }
1723                flag(w, "IMMEDIATE", *immediate);
1724            }
1725            Cics::Link(t) | Cics::Xctl(t) => {
1726                for (key, value) in [("PROGRAM", &t.program), ("COMMAREA", &t.commarea), ("LENGTH", &t.length)] {
1727                    option(w, key, value);
1728                }
1729            }
1730            Cics::Abend { abcode, cancel } => {
1731                option(w, "ABCODE", abcode);
1732                flag(w, "CANCEL", *cancel);
1733            }
1734            Cics::HandleCondition(labels) => {
1735                w.extend(labels.iter().map(|(c, label)| label.map_or_else(|| c.name().to_owned(), |p| format!("{}({})", c.name(), self.para(p)))));
1736            }
1737            Cics::IgnoreCondition(conditions) => w.extend(conditions.iter().map(|c| c.name().to_owned())),
1738            Cics::PushHandle | Cics::PopHandle | Cics::HandleAid | Cics::Freemain | Cics::Enq | Cics::Deq | Cics::Delay | Cics::Unsupported => {}
1739            Cics::HandleAbend { program, label, reset } => {
1740                option(w, "PROGRAM", program);
1741                if let Some(p) = label {
1742                    w.push(format!("LABEL({})", self.para(*p)));
1743                }
1744                flag(w, "RESET", *reset);
1745            }
1746            Cics::SendMap { map, mapset, from, maponly, dataonly, cursor, control } => {
1747                for (key, value) in [("MAP", map), ("MAPSET", mapset), ("FROM", from), ("CURSOR", cursor)] {
1748                    option(w, key, value);
1749                }
1750                flag(w, "MAPONLY", *maponly);
1751                flag(w, "DATAONLY", *dataonly);
1752                control_flags(w, control);
1753            }
1754            Cics::ReceiveMap { map, mapset, into, set } => {
1755                for (key, value) in [("MAP", map), ("MAPSET", mapset), ("INTO", into), ("SET", set)] {
1756                    option(w, key, value);
1757                }
1758            }
1759            Cics::SendControl { cursor, control } => {
1760                option(w, "CURSOR", cursor);
1761                control_flags(w, control);
1762            }
1763            Cics::Receive(r) => {
1764                for (key, value) in [("INTO", &r.into), ("SET", &r.set), ("LENGTH", &r.length)] {
1765                    option(w, key, value);
1766                }
1767            }
1768            Cics::Asktime { abstime } => option(w, "ABSTIME", abstime),
1769            Cics::Formattime { abstime, datesep, timesep, outputs } => {
1770                for (key, value) in [("ABSTIME", abstime), ("DATESEP", datesep), ("TIMESEP", timesep)] {
1771                    option(w, key, value);
1772                }
1773                for (key, value) in outputs {
1774                    option(w, key, &Some(value.clone()));
1775                }
1776            }
1777            Cics::Assign(a) => {
1778                let options = [
1779                    ("APPLID", &a.applid),
1780                    ("SYSID", &a.sysid),
1781                    ("USERID", &a.userid),
1782                    ("NETNAME", &a.netname),
1783                    ("FACILITY", &a.facility),
1784                    ("STARTCODE", &a.startcode),
1785                    ("ABCODE", &a.abcode),
1786                    ("PROGRAM", &a.program),
1787                    ("CWALENG", &a.cwaleng),
1788                    ("TWALENG", &a.twaleng),
1789                ];
1790                for (key, value) in options {
1791                    option(w, key, value);
1792                }
1793            }
1794            Cics::Getmain { flength, length, initimg, set } => {
1795                for (key, value) in [("FLENGTH", flength), ("LENGTH", length), ("INITIMG", initimg), ("SET", set)] {
1796                    option(w, key, value);
1797                }
1798            }
1799            Cics::Syncpoint { rollback } => flag(w, "ROLLBACK", *rollback),
1800            Cics::Address { eib, commarea, cwa, twa } => {
1801                for (key, value) in [("EIB", eib), ("COMMAREA", commarea), ("CWA", cwa), ("TWA", twa)] {
1802                    option(w, key, value);
1803                }
1804            }
1805            Cics::SendText { from, length } => {
1806                option(w, "FROM", from);
1807                option(w, "LENGTH", length);
1808            }
1809            Cics::WriteOperator { text, textlength } => {
1810                option(w, "TEXT", text);
1811                option(w, "TEXTLENGTH", textlength);
1812            }
1813            Cics::WriteqTs { queue, from, length, rewrite, item, numitems } => {
1814                for (key, value) in [("QUEUE", queue), ("FROM", from), ("LENGTH", length), ("ITEM", item), ("NUMITEMS", numitems)] {
1815                    option(w, key, value);
1816                }
1817                flag(w, "REWRITE", *rewrite);
1818            }
1819            Cics::ReadqTs { queue, next, item, numitems, record } => {
1820                option(w, "QUEUE", queue);
1821                flag(w, "NEXT", *next);
1822                for (key, value) in [("ITEM", item), ("NUMITEMS", numitems), ("INTO", &record.into), ("SET", &record.set), ("LENGTH", &record.length)] {
1823                    option(w, key, value);
1824                }
1825            }
1826            Cics::DeleteqTs { queue } | Cics::DeleteqTd { queue } => option(w, "QUEUE", queue),
1827            Cics::WriteqTd { queue, from, length } => {
1828                for (key, value) in [("QUEUE", queue), ("FROM", from), ("LENGTH", length)] {
1829                    option(w, key, value);
1830                }
1831            }
1832            Cics::ReadqTd { queue, record } => {
1833                for (key, value) in [("QUEUE", queue), ("INTO", &record.into), ("SET", &record.set), ("LENGTH", &record.length)] {
1834                    option(w, key, value);
1835                }
1836            }
1837            Cics::Refused(why) => w.push(format!("{why:?}")),
1838        }
1839    }
1840}
1841
1842fn control_flags(w: &mut Vec<String>, c: &crate::cics::Control) {
1843    for (key, on) in [("ERASE", c.erase), ("FREEKB", c.freekb), ("ALARM", c.alarm), ("FRSET", c.frset)] {
1844        flag(w, key, on);
1845    }
1846}
1847
1848/// A name as it is, or quoted and escaped when it is not one word of printable characters.
1849fn word(s: &str) -> String {
1850    if !s.is_empty() && s.chars().all(|c| !c.is_control() && !c.is_whitespace()) { s.to_owned() } else { format!("{s:?}") }
1851}
1852
1853/// A literal in quotes, a quote doubled within it, as COBOL writes one; escaped when it holds a
1854/// control character.
1855fn quote(s: &str) -> String {
1856    if s.chars().any(char::is_control) { format!("{s:?}") } else { format!("'{}'", s.replace('\'', "''")) }
1857}
1858
1859fn hex(prefix: &str, bytes: &[u8]) -> String {
1860    format!("{prefix}'{}'", join(bytes.iter().map(|b| format!("{b:02X}")), ""))
1861}
1862
1863/// UTF-16 units, big-endian, as N'...'; NX'...' when they are not printable text.
1864fn national(units: &[u8]) -> String {
1865    let (pairs, odd) = units.as_chunks::<2>();
1866    let text = odd
1867        .is_empty()
1868        .then(|| char::decode_utf16(pairs.iter().map(|&u| u16::from_be_bytes(u))).collect::<Result<String, _>>().ok())
1869        .flatten()
1870        .filter(|t| !t.chars().any(char::is_control));
1871    text.map_or_else(|| hex("NX", units), |t| format!("N{}", quote(&t)))
1872}
1873
1874/// A number with all its decimal places.
1875fn decimal(n: &Fixed) -> String {
1876    const MOST: u32 = 100;
1877    if n.places.dec > MOST {
1878        return format!("{:?}", n.magnitude);
1879    }
1880    let mut digits = Vec::new();
1881    let mut rest = n.magnitude;
1882    while !rest.is_zero() {
1883        let (q, r) = rest.div_rem(U256::from_u128(10));
1884        digits.push(char::from(b'0' + r.to_u128().and_then(|d| u8::try_from(d).ok()).unwrap_or(0)));
1885        rest = q;
1886    }
1887    let dec = n.places.dec as usize;
1888    while digits.len() <= dec {
1889        digits.push('0');
1890    }
1891    digits.reverse();
1892    let (int, frac): (String, String) = (digits[..digits.len() - dec].iter().collect(), digits[digits.len() - dec..].iter().collect());
1893    let sign = if n.negative { "-" } else { "" };
1894    if frac.is_empty() { format!("{sign}{int}") } else { format!("{sign}{int}.{frac}") }
1895}
1896
1897fn pic(digits: u32, scale: u32, signed: bool) -> String {
1898    let int = digits.saturating_sub(scale);
1899    let mut text = if signed { "S".to_owned() } else { String::new() };
1900    if int > 0 || scale == 0 {
1901        text += &format!("9({int})");
1902    }
1903    if scale > 0 {
1904        text += &format!("V9({scale})");
1905    }
1906    text
1907}
1908
1909fn sign_clause(sign: Option<SignClause>) -> String {
1910    sign.map_or_else(String::new, |s| {
1911        let position = match s.position {
1912            SignPosition::Leading => "leading",
1913            SignPosition::Trailing => "trailing",
1914        };
1915        format!(" sign {position}{}", if s.separate { " separate" } else { "" })
1916    })
1917}
1918
1919fn kind(k: &Kind) -> String {
1920    match *k {
1921        Kind::Group => "group".to_owned(),
1922        Kind::Alnum { justified } => if justified { "alnum justified" } else { "alnum" }.to_owned(),
1923        Kind::National => "national".to_owned(),
1924        Kind::Dbcs { justified, edit } => format!("dbcs{}{}", if justified { " justified" } else { "" }, edit.map_or(String::new(), |e| format!(" edit {e}"))),
1925        Kind::Zoned { digits, scale, signed, sign } => format!("zoned {}{}", pic(digits, scale, signed), sign_clause(sign)),
1926        Kind::Packed { digits, scale, signed } => format!("packed {}", pic(digits, scale, signed)),
1927        Kind::Binary { digits, scale, signed, native } => format!("binary {}{}", pic(digits, scale, signed), if native { " native" } else { "" }),
1928        Kind::Float(p) => format!("float {}", precision(p)),
1929        Kind::NumericEdited { edit, digits, scale, blank_when_zero } => {
1930            format!("numeric-edited edit {edit} {}{}", pic(digits, scale, false), if blank_when_zero { " blank-when-zero" } else { "" })
1931        }
1932        Kind::AlnumEdited { edit } => format!("alnum-edited edit {edit}"),
1933        Kind::Pointer => "pointer".to_owned(),
1934        Kind::Index => "index".to_owned(),
1935        Kind::ObjectReference => "object-reference".to_owned(),
1936        Kind::ProgramPointer => "program-pointer".to_owned(),
1937    }
1938}
1939
1940fn base(b: Base) -> String {
1941    match b {
1942        Base::Program => "program".to_owned(),
1943        Base::Local => "local".to_owned(),
1944        Base::Linkage(n) => format!("linkage{n}"),
1945        Base::ReturnCode => "return-code".to_owned(),
1946        Base::Eib => "eib".to_owned(),
1947        Base::SelfRef => "self".to_owned(),
1948        Base::JniEnv => "jnienv".to_owned(),
1949        Base::Xml(r) => match r {
1950            XmlRegister::Text => "xml-text",
1951            XmlRegister::NText => "xml-ntext",
1952            XmlRegister::Namespace => "xml-namespace",
1953            XmlRegister::NNamespace => "xml-nnamespace",
1954            XmlRegister::Prefix => "xml-namespace-prefix",
1955            XmlRegister::NPrefix => "xml-nnamespace-prefix",
1956        }
1957        .to_owned(),
1958    }
1959}
1960
1961fn precision(p: Precision) -> &'static str {
1962    match p {
1963        Precision::Short => "short",
1964        Precision::Long => "long",
1965        Precision::Extended => "extended",
1966    }
1967}
1968
1969fn figurative(f: Figurative) -> &'static str {
1970    match f {
1971        Figurative::Zero => "ZERO",
1972        Figurative::Space => "SPACE",
1973        Figurative::HighValue => "HIGH-VALUE",
1974        Figurative::LowValue => "LOW-VALUE",
1975        Figurative::Quote => "QUOTE",
1976        Figurative::Null => "NULL",
1977    }
1978}
1979
1980fn binop(op: BinOp) -> &'static str {
1981    match op {
1982        BinOp::Add => "+",
1983        BinOp::Sub => "-",
1984        BinOp::Mul => "*",
1985        BinOp::Div => "/",
1986        BinOp::Pow => "**",
1987    }
1988}
1989
1990fn relop(op: RelOp) -> &'static str {
1991    match op {
1992        RelOp::Eq => "=",
1993        RelOp::Ne => "<>",
1994        RelOp::Lt => "<",
1995        RelOp::Le => "<=",
1996        RelOp::Gt => ">",
1997        RelOp::Ge => ">=",
1998    }
1999}
2000
2001fn compare(c: Compare) -> String {
2002    match c {
2003        Compare::PackedPfd => "packed-pfd".to_owned(),
2004        Compare::Address => "address".to_owned(),
2005        Compare::Float => "float".to_owned(),
2006        Compare::Fixed => "fixed".to_owned(),
2007        Compare::National => "national".to_owned(),
2008        Compare::Dbcs => "dbcs".to_owned(),
2009        Compare::Alphanumeric => "alnum".to_owned(),
2010        Compare::Refused(a) => format!("refused {}", abend_ref(a)),
2011        Compare::References => "references".to_owned(),
2012        Compare::ZonedBytes { zoned_first } => format!("zoned-bytes {}", if zoned_first { "left" } else { "right" }),
2013    }
2014}
2015
2016fn byte_class(c: super::ByteClass) -> &'static str {
2017    use super::ByteClass;
2018    match c {
2019        ByteClass::Packed { signed: true } => "numeric [packed signed]",
2020        ByteClass::Packed { signed: false } => "numeric [packed]",
2021        ByteClass::Zoned { signed: true } => "numeric [zoned signed]",
2022        ByteClass::Zoned { signed: false } => "numeric [zoned]",
2023        ByteClass::Digits => "numeric [digits]",
2024        ByteClass::Alphabetic => "alphabetic",
2025        ByteClass::AlphabeticLower => "alphabetic-lower",
2026        ByteClass::AlphabeticUpper => "alphabetic-upper",
2027        ByteClass::Dbcs => "dbcs",
2028        ByteClass::Kanji => "kanji",
2029    }
2030}
2031
2032fn sign_test(t: SignTest) -> &'static str {
2033    match t {
2034        SignTest::Positive => "positive",
2035        SignTest::Negative => "negative",
2036        SignTest::Zero => "zero",
2037    }
2038}
2039
2040fn sql_test(t: SqlTest) -> &'static str {
2041    match t {
2042        SqlTest::Error => "error",
2043        SqlTest::NotFound => "not found",
2044        SqlTest::Warning => "warning",
2045    }
2046}
2047
2048fn ending(e: Ending) -> &'static str {
2049    match e {
2050        Ending::Goback => "goback",
2051        Ending::StopRun => "stop-run",
2052        Ending::EndOfProgram => "end-of-program",
2053    }
2054}
2055
2056fn range_kind(k: RangeKind) -> &'static str {
2057    match k {
2058        RangeKind::Perform => "perform",
2059        RangeKind::SortProcedure => "sort-procedure",
2060        RangeKind::UseBeforeReporting => "use-before-reporting",
2061        RangeKind::UseProcedure => "use-procedure",
2062        RangeKind::Debugging => "debugging",
2063        RangeKind::Processing => "processing",
2064    }
2065}
2066
2067fn image(i: Image) -> String {
2068    match i {
2069        Image::Bytes => String::new(),
2070        Image::All => " all".to_owned(),
2071        Image::Figurative => " figurative".to_owned(),
2072        Image::Digits { digits } => format!(" digits {digits}"),
2073        Image::Stored => " stored".to_owned(),
2074    }
2075}
2076
2077fn national_from(n: NationalFrom) -> &'static str {
2078    match n {
2079        NationalFrom::Units => "units",
2080        NationalFrom::Decoded => "decoded",
2081        NationalFrom::Figurative => "figurative",
2082        NationalFrom::Dbcs => "dbcs",
2083    }
2084}
2085
2086fn numeric_from(n: NumericFrom) -> String {
2087    match n {
2088        NumericFrom::Value => "value".to_owned(),
2089        NumericFrom::PackedCopy => "packed-copy".to_owned(),
2090        NumericFrom::Float => "float".to_owned(),
2091        NumericFrom::Zero => "zero".to_owned(),
2092        NumericFrom::Fill => "fill".to_owned(),
2093        NumericFrom::Zoned => "zoned".to_owned(),
2094        NumericFrom::DeEdit { edit, digits, scale } => format!("de-edit edit {edit} {}", pic(digits, scale, false)),
2095    }
2096}
2097
2098fn float_from(f: FloatFrom) -> &'static str {
2099    match f {
2100        FloatFrom::Float => "float",
2101        FloatFrom::Fixed => "fixed",
2102        FloatFrom::Zero => "zero",
2103    }
2104}
2105
2106fn accept_from(a: AcceptFrom) -> &'static str {
2107    match a {
2108        AcceptFrom::Sysin => "SYSIN",
2109        AcceptFrom::Date { four_digit_year: false } => "DATE",
2110        AcceptFrom::Date { four_digit_year: true } => "DATE YYYYMMDD",
2111        AcceptFrom::Day { four_digit_year: false } => "DAY",
2112        AcceptFrom::Day { four_digit_year: true } => "DAY YYYYDDD",
2113        AcceptFrom::DayOfWeek => "DAY-OF-WEEK",
2114        AcceptFrom::Time => "TIME",
2115    }
2116}
2117
2118fn open_mode(m: OpenMode) -> &'static str {
2119    match m {
2120        OpenMode::Input => "INPUT",
2121        OpenMode::Output => "OUTPUT",
2122        OpenMode::Extend => "EXTEND",
2123        OpenMode::InputOutput => "I-O",
2124    }
2125}
2126
2127fn closing_text(c: Closing) -> &'static str {
2128    match c {
2129        Closing::Volume => "volume",
2130        Closing::NoRewind => "no rewind",
2131        Closing::Lock => "lock",
2132    }
2133}
2134
2135fn phrase_words(p: Option<Phrase>, on: &str, not_on: &str) -> Vec<String> {
2136    p.map_or_else(Vec::new, |p| [yes(p.on, on), yes(p.not_on, not_on)].into_iter().flatten().collect())
2137}
2138
2139fn host_type(t: &HostType) -> String {
2140    let unsigned = |signed: bool| if signed { "" } else { " unsigned" };
2141    match t {
2142        HostType::SmallInt { signed } => format!("smallint{}", unsigned(*signed)),
2143        HostType::Integer { signed } => format!("integer{}", unsigned(*signed)),
2144        HostType::BigInt { signed } => format!("bigint{}", unsigned(*signed)),
2145        HostType::Decimal { digits, scale, signed } => format!("decimal({digits},{scale}){}", unsigned(*signed)),
2146        HostType::Zoned { digits, scale, signed, sign } => format!("zoned({digits},{scale}){}{}", unsigned(*signed), sign_clause(*sign)),
2147        HostType::Real => "real".to_owned(),
2148        HostType::Double => "double".to_owned(),
2149        HostType::Char(n) => format!("char({n})"),
2150        HostType::VarChar(n) => format!("varchar({n})"),
2151        HostType::Graphic(n) => format!("graphic({n})"),
2152        HostType::VarGraphic(n) => format!("vargraphic({n})"),
2153        HostType::Structure(members) => format!("structure of {}", members.len()),
2154    }
2155}
2156
2157fn sqlca_field(f: SqlcaField) -> String {
2158    match f {
2159        SqlcaField::CaId => "SQLCAID".to_owned(),
2160        SqlcaField::CaBc => "SQLCABC".to_owned(),
2161        SqlcaField::Code => "SQLCODE".to_owned(),
2162        SqlcaField::ErrMl => "SQLERRML".to_owned(),
2163        SqlcaField::ErrMc => "SQLERRMC".to_owned(),
2164        SqlcaField::ErrP => "SQLERRP".to_owned(),
2165        SqlcaField::State => "SQLSTATE".to_owned(),
2166        SqlcaField::ErrD(n) => format!("SQLERRD({n})"),
2167        SqlcaField::Warn(n) => format!("SQLWARN{}", if n == 10 { "A".to_owned() } else { n.to_string() }),
2168    }
2169}
2170
2171fn scope_section(s: Section) -> &'static str {
2172    match s {
2173        Section::WorkingStorage => "working-storage",
2174        Section::LocalStorage => "local-storage",
2175        Section::Linkage => "linkage",
2176        Section::File => "file",
2177    }
2178}
2179
2180fn group_kind(k: GroupKind) -> &'static str {
2181    match k {
2182        GroupKind::ReportHeading => "report-heading",
2183        GroupKind::PageHeading => "page-heading",
2184        GroupKind::ControlHeading => "control-heading",
2185        GroupKind::Detail => "detail",
2186        GroupKind::ControlFooting => "control-footing",
2187        GroupKind::PageFooting => "page-footing",
2188        GroupKind::ReportFooting => "report-footing",
2189    }
2190}
2191
2192fn next_group(n: NextGroup) -> String {
2193    match n {
2194        NextGroup::Line(l) => format!("line {l}"),
2195        NextGroup::Plus(l) => format!("plus {l}"),
2196        NextGroup::NextPage => "next page".to_owned(),
2197    }
2198}
2199
2200fn line_number(n: LineNumber) -> String {
2201    match n {
2202        LineNumber::Line(l) => format!("at {l}"),
2203        LineNumber::Plus(l) => format!("plus {l}"),
2204        LineNumber::NextPage(None) => "next page".to_owned(),
2205        LineNumber::NextPage(Some(l)) => format!("next page at {l}"),
2206    }
2207}
2208
2209fn members_list(members: &[u32]) -> String {
2210    join(members.iter().map(u32::to_string), " ")
2211}
2212
2213/// The open modes `Declaratives.modes` and `Scope.global_modes` are in.
2214const MODES: [&str; 4] = ["input", "output", "i-o", "extend"];
2215
2216#[cfg(test)]
2217mod tests {
2218    use super::*;
2219    use numeric::precision::Places;
2220
2221    #[test]
2222    fn a_number_prints_with_every_decimal_place_it_holds() {
2223        let n = |value: i128, int: u32, dec: u32| decimal(&Fixed::new(value, Places::new(int, dec)));
2224        assert_eq!([n(125, 3, 1), n(-5, 1, 2), n(0, 1, 0), n(1250, 3, 2), n(7, 1, 0)], ["12.5", "-0.05", "0", "12.50", "7"]);
2225    }
2226
2227    #[test]
2228    fn text_prints_on_one_line_whatever_it_holds() {
2229        assert_eq!(quote("IT'S"), "'IT''S'");
2230        assert_eq!(quote("A\nB"), "\"A\\nB\"");
2231        assert_eq!(word("WS-A"), "WS-A");
2232        assert_eq!(word("A B"), "\"A B\"");
2233        assert_eq!(national(&[0, 0x41, 0, 0x42]), "N'AB'");
2234        assert_eq!(national(&[0xD8, 0x00]), "NX'D800'");
2235        assert_eq!(pic(9, 2, true), "S9(7)V9(2)");
2236        assert_eq!(pic(2, 2, false), "V9(2)");
2237        assert_eq!(pic(4, 0, false), "9(4)");
2238    }
2239
2240    #[test]
2241    fn tables_that_refer_to_each_other_in_a_cycle_print_and_stop() {
2242        let (plans, services) = (Plans::default(), Services::default());
2243        let exprs = [Expr::Bin(0, BinOp::Add, 0)];
2244        let conds = [Cond::Rel { a: Comparand::Expr { expr: 0, dmax: 0, mode: Mode::Fixed, prepass: Vec::new() }, op: RelOp::Eq, b: Comparand::Operand(Operand::Const(9)), how: Compare::Fixed }];
2245        let blocks = [Block { ops: vec![Op::Display(4)], end: Terminator::Branch { cond: 0, then: 0, otherwise: 1 } }];
2246        let code = Code {
2247            id: 0,
2248            initial: false,
2249            recursive: false,
2250            paragraphs: &[],
2251            procedure_start: 0,
2252            ranges: &[],
2253            blocks: &blocks,
2254            places: &[],
2255            exprs: &exprs,
2256            conds: &conds,
2257            consts: &[],
2258            plans: &plans,
2259            services: &services,
2260            abends: &[],
2261            symbols: &[],
2262        };
2263        let text = Listing { code, items: &[], debug: None, sql: &[], ccsid: None }.to_string();
2264        let lines: Vec<&str> = text.lines().collect();
2265        assert_eq!(lines[..3], ["program symbol0?", "b0:", "    Display display4?"]);
2266        assert!(lines[3].starts_with("    Branch (((((") && lines[3].contains("...") && lines[3].ends_with(" = c9? [fixed]) b0 else b1"), "{text}");
2267    }
2268}