1use 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, ScreenPlan, ScreenPosition, 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, Native};
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#[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#[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
112const 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 shared: BTreeMap<&'a str, Vec<usize>>,
125 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
135fn 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 fn name(&self, id: SymId) -> String {
208 self.symbol(id).map_or_else(|| format!("symbol{id}?"), word)
209 }
210
211 fn string(&self, id: SymId) -> String {
213 self.symbol(id).map_or_else(|| format!("symbol{id}?"), |s| format!("{s:?}"))
214 }
215
216 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 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 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 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 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 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 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 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 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 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 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), native_word(native)),
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 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::DisplayError(id) => format!("Error{}", self.display(*id)),
725 Op::ArgumentNumber(value) => format!("ArgumentNumber <- {}", self.int(value)),
726 Op::Environment { display, value } => format!("Environment{} {}", if *value { "Value" } else { "Name" }, self.display(*display)),
727 Op::ScreenDisplay { display, screen } => format!("Screen{} {}", self.display(*display), self.screen(screen)),
728 Op::ScreenAccept { inputs, handled } => {
729 let inputs = inputs.iter().map(|i| {
730 let flags = format!("{}{}", if i.update { " update" } else { "" }, if i.secure { " secure" } else { "" });
731 format!("{} <- {} [{}{flags}]", self.place(i.target), self.place(i.field), self.screen_position(&i.at))
732 });
733 format!("ScreenAccept {}{}", join(inputs, ", "), if *handled { " handled" } else { "" })
734 }
735 Op::Accept { target, from, plan } => format!("Accept {} <- {}{}", self.place(*target), accept_from(*from), self.moved(plan, SenderCheck::None)),
736 Op::File(id) => self.file_op(*id),
737 Op::Call(id) => self.call(*id),
738 Op::Cancel(name) => format!("Cancel {}", self.operand(name)),
739 Op::Sort(id) => self.sort(*id),
740 Op::Release(id) => self.release(*id),
741 Op::Return(id) => self.return_op(*id),
742 Op::Report(r) => self.report_op(r),
743 Op::Invoke(id) => self.invoke(*id),
744 Op::Cics(id) => self.cics(*id),
745 Op::Sql(ordinal) => self.sql(*ordinal),
746 Op::Alter { para, to } => format!("Alter {} to {}", self.para(*para), self.para(*to)),
747 Op::EnterSegment(priority) => format!("EnterSegment {priority}"),
748 Op::DebugLine(line) => format!("DebugLine {line}"),
749 Op::DebugAlter { range, name, contents } => format!("DebugAlter {} name {} contents {}", self.range(*range), self.literal(*name), self.literal(*contents)),
750 Op::Markup(id) => self.markup(*id),
751 }
752 }
753
754 fn arith(&self, id: ArithId) -> String {
755 let Some(ArithPlan { dmax, arith, prepass, steps, remainder, handled, per_receiver, inner_dmax }) = self.c.plans.arith.get(id as usize) else {
756 return format!("Arith arith{id}?");
757 };
758 let mut steps: Vec<String> = steps.iter().map(|s| self.arith_step(s)).collect();
759 if let Some(r) = remainder {
760 steps.push(format!(
761 "remainder {} <- {} / {} [{}, quotient scale {}]",
762 self.place(r.target),
763 self.term(r.dividend),
764 self.term(r.divisor),
765 self.store(&r.store),
766 r.quotient_scale
767 ));
768 }
769 let mut how = vec![format!("dmax {dmax}")];
770 if inner_dmax != dmax {
771 how.push(format!("inner dmax {inner_dmax}"));
772 }
773 match arith {
774 Arith::Compat => {}
775 Arith::Extend => how.push("arith extend".to_owned()),
776 }
777 if !prepass.is_empty() {
778 how.push(format!("prepass {}", self.places(prepass)));
779 }
780 how.extend(yes(*per_receiver, "per receiver"));
781 how.extend(yes(*handled, "size error"));
782 format!("Arith {}{}", steps.join("; "), attrs('{', how))
783 }
784
785 fn arith_step(&self, s: &ArithStep) -> String {
786 let mut how = vec![self.store(&s.store)];
787 how.extend(yes(s.rounded, "rounded"));
788 match s.mode {
789 Mode::Fixed => {}
790 Mode::Float(p) => how.push(format!("float {}", precision(p))),
791 }
792 if !s.probe.is_empty() {
793 how.push(format!("probe {}", self.places(&s.probe)));
794 }
795 format!("{} <- {}{}", self.place(s.target), self.expr(s.expr), attrs('[', how))
796 }
797
798 fn screen_position(&self, at: &ScreenPosition) -> String {
799 match at {
800 ScreenPosition::Cursor => "cursor".to_owned(),
801 ScreenPosition::Combined(at) => format!("at {}", self.int(at)),
802 ScreenPosition::LineColumn { line, column } => {
803 format!("line {} column {}", line.as_ref().map_or("cursor".to_owned(), |l| self.int(l)), column.as_ref().map_or("1".to_owned(), |c| self.int(c)))
804 }
805 }
806 }
807
808 fn screen(&self, s: &ScreenPlan) -> String {
809 let at = self.screen_position(&s.at);
810 let flags = [(s.blank_screen, " blank-screen"), (s.blank_line, " blank-line"), (s.erase_eol, " erase-eol"), (s.erase_eos, " erase-eos"), (s.update, " update"), (s.secure, " secure")];
811 format!("[{at}{}]", flags.iter().filter(|(on, _)| *on).map(|(_, name)| *name).collect::<String>())
812 }
813
814 fn display(&self, id: DisplayId) -> String {
815 let Some(d) = self.c.plans.display.get(id as usize) else { return format!("Display display{id}?") };
816 let items = d.items.iter().map(|item| match item {
817 DisplayItem::Bytes(p) => self.place(*p),
818 DisplayItem::National(p) => format!("{} [national]", self.place(*p)),
819 DisplayItem::Digits { place, digits, signed } => format!("{} [digits {digits}{}]", self.place(*place), if *signed { " signed" } else { "" }),
820 DisplayItem::Refused { place, abend } => format!("{} [refused {}]", self.place(*place), abend_ref(*abend)),
821 DisplayItem::Text(text) => self.literal(*text),
822 DisplayItem::Value(o) => self.operand(o),
823 });
824 format!("Display {}{}", join(items, ", "), attrs('{', yes(d.no_advancing, "no advancing").into_iter().collect()))
825 }
826
827 fn inspect(&self, id: InspectId) -> String {
828 let Some(i) = self.c.plans.inspect.get(id as usize) else { return format!("Inspect inspect{id}?") };
829 let mut text = format!(
830 "Inspect {}",
831 match &i.target {
832 Inspected::Item(p) => self.place(*p),
833 Inspected::Value(o) => self.operand(o),
834 }
835 );
836 for phrase in &i.tallying {
837 text += &format!(" tallying {}", self.inspect_phrase(phrase));
838 }
839 for phrase in &i.replacing {
840 text += &format!(" replacing {}", self.inspect_phrase(phrase));
841 }
842 if let Some(c) = &i.converting {
843 text += &match &c.table {
844 ConvertTable::Built(pairs) => {
845 let (from, to): (Vec<u8>, Vec<u8>) = pairs.iter().copied().unzip();
846 format!(" converting {} to {} {{built}}", self.bytes(&from), self.bytes(&to))
847 }
848 ConvertTable::Operands { from, to } => format!(" converting {} to {}", self.chars(from), self.chars(to)),
849 };
850 text += &self.bounds(&c.bounds);
851 }
852 text
853 }
854
855 fn inspect_phrase(&self, phrase: &InspectPhrase) -> String {
856 let mut text = String::new();
857 if let Some((p, step)) = &phrase.counter {
858 text += &format!("{} [{}] for ", self.place(*p), self.step(step));
859 }
860 text += match phrase.mode {
861 InspectMode::Characters => "CHARACTERS",
862 InspectMode::All => "ALL",
863 InspectMode::Leading => "LEADING",
864 InspectMode::Trailing => "TRAILING",
865 InspectMode::First => "FIRST",
866 };
867 if let Some(pattern) = &phrase.pattern {
868 text += &format!(" {}", self.chars(pattern));
869 }
870 match &phrase.by {
871 None => {}
872 Some(Replacement::Chars(c)) => text += &format!(" by {}", self.chars(c)),
873 Some(Replacement::Fill(b)) => text += &format!(" by fill {}", hex("X", &[*b])),
874 }
875 text + &self.bounds(&phrase.bounds)
876 }
877
878 fn bounds(&self, bounds: &[Bound]) -> String {
879 join(bounds.iter().map(|b| format!(" {} {}", if b.after { "after" } else { "before" }, self.chars(&b.value))), "")
880 }
881
882 fn string_op(&self, id: StringId) -> String {
883 let Some(s) = self.c.plans.string.get(id as usize) else { return format!("String string{id}?") };
884 let sources = s.sources.iter().map(|source| {
885 let delimiter = source.delimiter.as_ref().map_or_else(|| "size".to_owned(), |d| self.chars(d));
886 format!("{} delimited by {delimiter}", self.chars(&source.chars))
887 });
888 let pointer = s.pointer.as_ref().map_or_else(String::new, |p| format!(" pointer {}", self.stored(p)));
889 format!("String {} <- {}{pointer}", self.place(s.into), join(sources, ", "))
890 }
891
892 fn unstring(&self, id: UnstringId) -> String {
893 let Some(u) = self.c.plans.unstring.get(id as usize) else { return format!("Unstring unstring{id}?") };
894 let mut text = format!("Unstring {}", self.place(u.source));
895 if !u.delimiters.is_empty() {
896 let delimiters = u.delimiters.iter().map(|(all, c)| format!("{}{}", if *all { "ALL " } else { "" }, self.chars(c)));
897 text += &format!(" delimited by {}", join(delimiters, ", "));
898 }
899 let into = u.into.iter().map(|i| {
900 let mut field = format!("{} [{}]", self.place(i.target), self.move_plan(&i.plan));
901 if let Some(d) = &i.delimiter {
902 field += &format!(" delimiter in {} [found {}, none {}]", self.place(d.target), self.move_plan(&d.found), self.move_plan(&d.none));
903 }
904 if let Some(c) = &i.count {
905 field += &format!(" count in {}", self.stored(c));
906 }
907 field
908 });
909 text += &format!(" into {}", join(into, ", "));
910 if let Some(p) = &u.pointer {
911 text += &format!(" pointer {}", self.stored(p));
912 }
913 if let Some((p, step)) = &u.tallying {
914 text += &format!(" tallying {} [{}]", self.place(*p), self.step(step));
915 }
916 text
917 }
918
919 fn search_all(&self, id: SearchAllId) -> String {
920 let Some(s) = self.c.plans.search_all.get(id as usize) else { return format!("SearchAll search-all{id}?") };
921 let keys = s.keys.iter().map(|k| {
922 format!("{} = {} [{}{}]", self.comparand(&k.key), self.comparand(&k.value), compare(k.how), if k.ascending { "" } else { ", descending" })
923 });
924 format!("SearchAll {} [{}] within {} when {}", self.place(s.index), self.store(&s.store), self.count(&s.count), join(keys, " and "))
925 }
926
927 fn file_op(&self, id: FileOpId) -> String {
928 let Some(op) = self.c.services.file_ops.get(id as usize) else { return format!("File file-op{id}?") };
929 let file = self.file(op.file);
930 let (text, at_end) = match &op.verb {
931 FileVerb::Open(mode) => (format!("OPEN {} {file}", open_mode(*mode)), false),
932 FileVerb::Close => (format!("CLOSE {file}"), false),
933 FileVerb::CloseWith(closing) => (format!("CLOSE {file} {}", closing_text(*closing)), false),
934 FileVerb::Read { sequential, previous, into, key } => {
935 let mut text = format!("READ {file}");
936 text += &if *sequential { " sequential".to_owned() } else { format!(" key {key}") };
937 if *previous {
938 text += " previous";
939 }
940 if let Some((p, plan)) = into {
941 text += &format!(" into {}{}", self.place(*p), self.moved(plan, SenderCheck::None));
942 }
943 (text, *sequential)
944 }
945 FileVerb::Write { record, from, advancing } => {
946 let mut text = format!("WRITE {}{}", self.place(*record), self.sender(from.as_ref()));
947 if let Some(a) = advancing {
948 text += &format!(" advancing {}", self.advance(a));
949 }
950 (text, false)
951 }
952 FileVerb::Rewrite { record, from } => (format!("REWRITE {}{}", self.place(*record), self.sender(from.as_ref())), false),
953 FileVerb::Delete => (format!("DELETE {file}"), false),
954 FileVerb::DeleteFile => (format!("DELETE FILE {file}"), false),
955 FileVerb::Start { rel, key } => {
956 let rel = match rel {
957 StartRel::Equal => "=",
958 StartRel::Greater => ">",
959 StartRel::NotLess => ">=",
960 StartRel::Less => "<",
961 StartRel::NotGreater => "<=",
962 };
963 let key = match key {
964 StartKey::Prime => "prime".to_owned(),
965 StartKey::Named { key, span } => format!("{key} +{} len {}", span.offset, span.len),
966 StartKey::Relative(value) => format!("relative {}", self.int(value)),
967 StartKey::RelativeKey => "relative key".to_owned(),
968 };
969 (format!("START {file} key {rel} {key}"), false)
970 }
971 };
972 let (on, not_on) = if at_end { ("at end", "not at end") } else { ("invalid key", "not invalid key") };
973 let mut phrases = phrase_words(op.phrase, on, not_on);
974 phrases.extend(phrase_words(op.end_of_page, "end-of-page", "not end-of-page"));
975 format!("File {text}{}", attrs('{', phrases))
976 }
977
978 fn sender(&self, from: Option<&FromMove>) -> String {
979 from.map_or_else(String::new, |m| format!(" <- {}{}", self.operand(&m.from), self.moved(&m.plan, m.check)))
980 }
981
982 fn advance(&self, a: &Advance) -> String {
983 let side = |before: bool| if before { "before" } else { "after" };
984 match a {
985 Advance::Lines { before, count } => format!("{} {} lines", side(*before), self.int(count)),
986 Advance::Page { before } => format!("{} page", side(*before)),
987 Advance::Mnemonic { before, space } => format!(
988 "{} {}",
989 side(*before),
990 match space {
991 Spacing::Lines(n) => format!("{n} lines"),
992 Spacing::Channel(c) => format!("channel {c}"),
993 Spacing::PageMode => "page mode".to_owned(),
994 }
995 ),
996 }
997 }
998
999 fn call(&self, id: CallId) -> String {
1000 let Some(c) = self.c.services.calls.get(id as usize) else { return format!("Call call{id}?") };
1001 let target = match &c.target {
1002 CallTarget::Named { name, le: None } => self.literal(*name),
1003 CallTarget::Named { name, le: Some(service) } => {
1004 let service = format!("{service:?}").to_ascii_uppercase();
1005 format!("{} or {service}", self.literal(*name))
1006 }
1007 CallTarget::Dynamic(o) => self.operand(o),
1008 CallTarget::Pointer(p) => format!("pointer {}", self.place(*p)),
1009 CallTarget::Entry(p) => format!("entry {}", self.place(*p)),
1010 };
1011 let mut text = format!("Call {target}");
1012 if !c.args.is_empty() {
1013 let args = c.args.iter().map(|a| match a {
1014 CallArg::Reference(p) => self.place(*p),
1015 CallArg::Content(chars) => format!("content {}", self.chars(chars)),
1016 CallArg::Value(o) => format!("value {}", self.operand(o)),
1017 CallArg::Omitted => "omitted".to_owned(),
1018 });
1019 text += &format!(" using {}", join(args, ", "));
1020 }
1021 if let Some(r) = c.returning {
1022 text += &format!(" returning {}", self.place(r));
1023 }
1024 text + &attrs('{', [yes(c.on_exception, "on exception"), yes(c.not_on_exception, "not on exception")].into_iter().flatten().collect())
1025 }
1026
1027 fn sort_keys(&self, keys: &SortKeys) -> String {
1028 let named = keys.keys.iter().map(|k| {
1029 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));
1030 format!("{} {item}{}", if k.ascending { "ascending" } else { "descending" }, if k.collated { " collated" } else { "" })
1031 });
1032 format!("keys {}{}", join(named, ", "), if keys.collating.is_some() { " {collating sequence}" } else { "" })
1033 }
1034
1035 fn sort(&self, id: SortId) -> String {
1036 match self.c.services.sorts.get(id as usize) {
1037 None => format!("Sort sort{id}?"),
1038 Some(SortPlan::File(s)) => {
1039 let io = |io: &Option<SortIo>, files: &str, procedure: &str| match io {
1040 None => format!("{procedure} none"),
1041 Some(SortIo::Files(list)) => format!("{files} {}", join(list.iter().map(|&k| self.file(k)), " ")),
1042 Some(SortIo::Procedure(r)) => format!("{procedure} procedure {}", self.range(*r)),
1043 };
1044 format!(
1045 "Sort {} {} {} {} {}{}",
1046 if s.merge { "merge" } else { "file" },
1047 self.file(s.sd),
1048 self.sort_keys(&s.keys),
1049 io(&s.input, "using", "input"),
1050 io(&s.output, "giving", "output"),
1051 attrs('{', vec![format!("sort-return {}", self.place(s.sort_return)), format!("sort-control {}", self.place(s.sort_control))])
1052 )
1053 }
1054 Some(SortPlan::Table(t)) => {
1055 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))
1056 }
1057 }
1058 }
1059
1060 fn release(&self, id: ReleaseId) -> String {
1061 let Some(r) = self.c.services.releases.get(id as usize) else { return format!("Release release{id}?") };
1062 let file = r.file.map_or_else(|| self.name(r.name), |k| self.file(k));
1063 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))]))
1064 }
1065
1066 fn return_op(&self, id: ReturnId) -> String {
1067 let Some(r) = self.c.services.returns.get(id as usize) else { return format!("Return return{id}?") };
1068 let file = r.file.map_or_else(|| self.name(r.name), |k| self.file(k));
1069 let into = r.into.as_ref().map_or_else(String::new, |(p, plan)| format!(" into {}{}", self.place(*p), self.moved(plan, SenderCheck::None)));
1070 format!("Return {file}{into}{}", attrs('{', vec![format!("sort-return {}", self.place(r.sort_return))]))
1071 }
1072
1073 fn report_name(&self, report: u32) -> String {
1074 self.c.services.report.reports.get(report as usize).map_or_else(|| format!("report{report}?"), |r| word(&r.name))
1075 }
1076
1077 fn report_op(&self, op: &ReportOp) -> String {
1078 match *op {
1079 ReportOp::Initiate(r) => format!("Report Initiate {}", self.report_name(r)),
1080 ReportOp::Generate { report, detail } => {
1081 let group = detail.map_or_else(String::new, |d| {
1082 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());
1083 format!(" detail {}", name.map_or_else(|| format!("group{d}"), word))
1084 });
1085 format!("Report Generate {}{group}", self.report_name(report))
1086 }
1087 ReportOp::Terminate(r) => format!("Report Terminate {}", self.report_name(r)),
1088 ReportOp::Suppress => "Report Suppress".to_owned(),
1089 }
1090 }
1091
1092 fn invoke(&self, id: InvokeId) -> String {
1093 let Some(i) = self.c.services.invokes.get(id as usize) else { return format!("Invoke invoke{id}?") };
1094 let receiver = match &i.receiver {
1095 Receiver::SelfRef => "SELF".to_owned(),
1096 Receiver::Super => "SUPER".to_owned(),
1097 Receiver::Class { name, external } if name == external => format!("class {}", self.name(*name)),
1098 Receiver::Class { name, external } => format!("class {} external {}", self.name(*name), self.string(*external)),
1099 Receiver::Object(p) => self.place(*p),
1100 };
1101 let method = match &i.method {
1102 MethodName::New => "NEW".to_owned(),
1103 MethodName::Named(s) => self.literal(*s),
1104 MethodName::Dynamic(p) => self.place(*p),
1105 };
1106 let mut text = format!("Invoke {receiver} {method}");
1107 if !i.args.is_empty() {
1108 text += &format!(" using {}", join(i.args.iter().map(|(o, sig)| format!("{} as {}", self.operand(o), self.string(*sig))), ", "));
1109 }
1110 if let Some((p, sig)) = &i.returning {
1111 text += &format!(" returning {} as {}", self.place(*p), self.string(*sig));
1112 }
1113 text + &attrs('{', [yes(i.on_exception, "on exception"), yes(i.not_on_exception, "not on exception")].into_iter().flatten().collect())
1114 }
1115
1116 fn cics(&self, id: CicsId) -> String {
1117 let Some(written) = self.c.services.cics.get(id as usize) else { return format!("Cics cics{id}?") };
1118 let Ok(CicsCommand { name: _, command, resp, sinks }) = written.clone().map(&mut Names(self));
1119 let mut words = vec![command.name().to_owned()];
1120 self.cics_options(&command, &mut words);
1121 option(&mut words, "RESP", &resp.resp);
1122 option(&mut words, "RESP2", &resp.resp2);
1123 flag(&mut words, "NOHANDLE", resp.nohandle);
1124 let mut how = Vec::new();
1125 if self.symbol(written.name) != Some(command.name()) {
1126 how.push(format!("written {}", self.string(written.name)));
1127 }
1128 if !sinks.is_empty() {
1129 how.push(format!("sinks {}", join(sinks.iter().map(|(p, s)| format!("{p} {}", s.kind())), " ")));
1130 }
1131 format!("Cics {}{}", words.join(" "), attrs('{', how))
1132 }
1133
1134 fn sql(&self, ordinal: SqlId) -> String {
1135 let Some(e) = (ordinal as usize).checked_sub(1).and_then(|k| self.sql.get(k)) else { return format!("Sql {ordinal}") };
1136 let text = if self.symbol(e.text).is_some_and(|t| !t.is_empty()) { self.string(e.text) } else { self.name(e.verb) };
1137 let hosts = |key: &str, list: &[super::HostPlace]| if list.is_empty() { String::new() } else { format!(" {key} ({})", join(list.iter().map(|h| self.host(h)), ", ")) };
1138 let statement = match &e.statement {
1139 SqlStatement::Query { inputs, into } => format!("query{}{}", hosts("inputs", inputs), hosts("into", into)),
1140 SqlStatement::Change { delete, inputs, current_of } => {
1141 let current = current_of.map_or_else(String::new, |c| format!(" current of {}", self.name(c)));
1142 format!("{}{}{current}", if *delete { "delete" } else { "change" }, hosts("inputs", inputs))
1143 }
1144 SqlStatement::Open { cursor, inputs } => format!("open {}{}", self.name(*cursor), hosts("inputs", inputs)),
1145 SqlStatement::Fetch { cursor, into } => format!("fetch {}{}", self.name(*cursor), hosts("into", into)),
1146 SqlStatement::Close { cursor } => format!("close {}", self.name(*cursor)),
1147 SqlStatement::Commit => "commit".to_owned(),
1148 SqlStatement::Rollback => "rollback".to_owned(),
1149 SqlStatement::Declaration => "declaration".to_owned(),
1150 SqlStatement::Unsupported(what) => format!("unsupported {}", self.string(*what)),
1151 SqlStatement::Connect { what, location } => format!("connect {}{}", self.string(*what), hosts("location", location)),
1152 SqlStatement::Prepare { name, source } => format!("prepare {}{}", self.name(*name), hosts("from", source)),
1153 SqlStatement::ExecuteImmediate { source } => format!("execute immediate{}", hosts("from", source)),
1154 SqlStatement::Execute { name, inputs } => format!("execute {}{}", self.name(*name), hosts("using", inputs)),
1155 SqlStatement::OpenPrepared { cursor, statement, inputs } => format!("open {} for {}{}", self.name(*cursor), self.name(*statement), hosts("using", inputs)),
1156 SqlStatement::Describe { name, descriptor, names } => format!("describe {} into {} {names:?}", self.name(*name), self.place(*descriptor)),
1157 SqlStatement::PrepareInto { name, source, descriptor, names } => format!("prepare {} into {} {names:?}{}", self.name(*name), self.place(*descriptor), hosts("from", source)),
1158 SqlStatement::ExecuteDescriptor { name, descriptor } => format!("execute {} using descriptor {}", self.name(*name), self.place(*descriptor)),
1159 SqlStatement::OpenDescriptor { cursor, statement, descriptor } => format!("open {} for {} using descriptor {}", self.name(*cursor), self.name(*statement), self.place(*descriptor)),
1160 SqlStatement::FetchDescriptor { cursor, descriptor } => format!("fetch {} using descriptor {}", self.name(*cursor), self.place(*descriptor)),
1161 SqlStatement::FetchRowset { cursor, rows, into, enabled } => {
1162 let disabled = if *enabled { "" } else { " (no rowset positioning)" };
1163 format!("fetch rowset {}{disabled}{}{}", self.name(*cursor), self.rows(rows), self.arrays("into", into))
1164 }
1165 SqlStatement::InsertRows { inputs, rows, atomic } => format!("insert rows{}{}{}", if *atomic { " atomic" } else { " not atomic" }, self.rows(rows), self.arrays("inputs", inputs)),
1166 SqlStatement::Call { procedure, args } => format!("call {}{}", self.name(*procedure), hosts("arguments", args)),
1167 };
1168 format!("Sql {ordinal} {text} {statement}{}", attrs('{', yes(e.with_hold, "with hold").into_iter().collect()))
1169 }
1170
1171 fn rows(&self, rows: &super::RowCount) -> String {
1172 match rows {
1173 super::RowCount::Implicit => String::new(),
1174 super::RowCount::Constant(n) => format!(" for {n} rows"),
1175 super::RowCount::Host(h) => format!(" for ({}) rows", self.host(h)),
1176 }
1177 }
1178
1179 fn arrays(&self, key: &str, list: &[super::HostArray]) -> String {
1180 if list.is_empty() {
1181 return String::new();
1182 }
1183 let array = |a: &super::HostArray| match a.array {
1184 Some(d) => format!("{} array {} by {} indicator by {}", self.host(&a.place), d.count, d.stride, d.indicator_stride),
1185 None => self.host(&a.place),
1186 };
1187 format!(" {key} ({})", join(list.iter().map(array), ", "))
1188 }
1189
1190 fn host(&self, h: &super::HostPlace) -> String {
1191 let mut text = self.place(h.var);
1192 if let Some((offset, len)) = h.member {
1193 text += &format!(" member +{offset} len {len}");
1194 }
1195 text += &match &h.ty {
1196 Ok(ty) => format!(" [{}]", host_type(ty)),
1197 Err(a) => format!(" [refused {}]", abend_ref(*a)),
1198 };
1199 if let Some((p, offset)) = h.indicator {
1200 text += &format!(" indicator {} +{offset}", self.place(p));
1201 }
1202 text
1203 }
1204
1205 fn ccsid(&self, c: &Ccsid) -> Option<String> {
1206 match c {
1207 Ccsid::Unnamed => None,
1208 Ccsid::CodePage => Some("encoding codepage".to_owned()),
1209 Ccsid::Operand(o) => Some(format!("encoding {}", self.operand(o))),
1210 }
1211 }
1212
1213 fn markup(&self, id: MarkupId) -> String {
1214 let Some(m) = self.c.services.markup.get(id as usize) else { return format!("Markup markup{id}?") };
1215 let subscripts = |s: &[IntExpr]| (!s.is_empty()).then(|| format!("subscripts ({})", join(s.iter().map(|e| self.int(e)), ", ")));
1216 let (verb, words) = match m {
1217 Markup::JsonGenerate(g) => {
1218 let name = g.name.map_or_else(|| "name omitted".to_owned(), |n| format!("name {}", self.raw(n)));
1219 let mut words = vec![format!("{} <- {}", self.place(g.receiver), self.place(g.from))];
1220 words.extend(subscripts(&g.subscripts));
1221 words.push(name);
1222 words.extend(self.ccsid(&g.encoding));
1223 words.extend(g.count.as_ref().map(|c| format!("count {}", self.stored(c))));
1224 words.push(format!("code {}", self.stored(&g.code)));
1225 ("JSON GENERATE", words)
1226 }
1227 Markup::XmlGenerate(g) => {
1228 let mut words = vec![format!("{} <- {}", self.place(g.receiver), self.place(g.from))];
1229 words.extend(subscripts(&g.subscripts));
1230 words.extend(self.ccsid(&g.encoding));
1231 words.extend(g.namespace.as_ref().map(|o| format!("namespace {}", self.operand(o))));
1232 words.extend(g.prefix.as_ref().map(|o| format!("prefix {}", self.operand(o))));
1233 words.extend(yes(g.declaration, "declaration"));
1234 words.extend(yes(g.suppressing, "suppress"));
1235 words.extend(g.count.as_ref().map(|c| format!("count {}", self.stored(c))));
1236 words.push(format!("code {}", self.stored(&g.code)));
1237 ("XML GENERATE", words)
1238 }
1239 Markup::XmlParse(p) => {
1240 let mut words = vec![self.place(p.document)];
1241 words.extend(p.encoding.as_ref().map(|o| format!("encoding {}", self.operand(o))));
1242 words.extend(yes(p.national, "returning national"));
1243 words.push(format!("procedure {}", self.range(p.procedure)));
1244 words.push(format!("event {}", self.place(p.event)));
1245 words.push(format!("code {}", self.stored(&p.code)));
1246 words.push(format!("information {}", self.stored(&p.information)));
1247 words.push(format!("code-value {}", self.int(&p.code_value)));
1248 ("XML PARSE", words)
1249 }
1250 Markup::JsonParse(p) => {
1251 let mut words = vec![format!("{} <- {}", self.place(p.into), self.place(p.source))];
1252 words.extend(subscripts(&p.subscripts));
1253 words.extend(self.ccsid(&p.encoding));
1254 words.extend(yes(p.ignore_all, "ignoring null for all"));
1255 words.push(format!("code {}", self.stored(&p.code)));
1256 words.push(format!("status {}", self.stored(&p.status)));
1257 ("JSON PARSE", words)
1258 }
1259 };
1260 let (on, not_on) = m.phrases();
1261 let phrases = [yes(on, "on exception"), yes(not_on, "not on exception")].into_iter().flatten().collect();
1262 format!("Markup m{id} {verb} {}{}", words.join(" "), attrs('{', phrases))
1263 }
1264
1265 fn tables(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1266 for (k, p) in self.c.places.iter().enumerate() {
1267 writeln!(f, "{}", self.place_row(k, p))?;
1268 }
1269 for (k, c) in self.c.consts.iter().enumerate() {
1270 writeln!(f, "const c{k} {}", self.constant(c))?;
1271 }
1272 for (k, a) in self.c.abends.iter().enumerate() {
1273 writeln!(f, "abend a{k} {} {}{}", a.code, self.string(a.message), a.at.map_or_else(String::new, |at| self.at(at)))?;
1274 }
1275 let s = self.c.services;
1276 for (k, d) in s.files.iter().enumerate() {
1277 writeln!(f, "file {k} {}", self.file_row(d))?;
1278 }
1279 for (k, e) in s.entries.iter().enumerate() {
1280 let using = join(e.using.iter().map(|&r| self.linkage(r)), " ");
1281 writeln!(f, "entry {k} {} paragraph {} block {} using ({using})", self.name(e.name), self.para(e.paragraph), label(e.block))?;
1282 }
1283 for (k, plan) in self.c.plans.init.iter().enumerate() {
1284 for field in &plan.fields {
1285 let value = match &field.value {
1286 InitValue::Default(v) => figurative(*v).to_owned(),
1287 InitValue::Value(c) => self.konst(*c),
1288 InitValue::Replacing(o) => format!("replacing {}", self.operand(o)),
1289 };
1290 let scaling = if field.scaling == 0 { String::new() } else { format!(" scaling {}", field.scaling) };
1291 writeln!(f, "init {k} +{} len {} <- {value}{}{scaling}", field.offset, field.len, self.moved(&field.store, SenderCheck::None))?;
1292 }
1293 }
1294 for (field, p, ty) in &s.sqlca.fields {
1295 writeln!(f, "sqlca {} {} [{}]", sqlca_field(*field), self.place(*p), host_type(ty))?;
1296 }
1297 let modes = s.declaratives.modes.iter().zip(MODES).filter_map(|(r, mode)| r.map(|r| format!("{mode} {}", self.range(r))));
1298 for mode in modes {
1299 writeln!(f, "declaratives {mode}")?;
1300 }
1301 if let Some((offset, len)) = s.declaratives.debug_item {
1302 writeln!(f, "declaratives debug-item program+{offset} len {len}")?;
1303 }
1304 self.scope(f)?;
1305 if let Some(d) = &s.function {
1306 writeln!(f, "function params ({}) returning {}", self.places(&d.params), self.place(d.returning))?;
1307 }
1308 self.reports(f)?;
1309 for (k, m) in s.markup.iter().enumerate() {
1310 self.markup_nodes(f, k, m)?;
1311 }
1312 if let Some(class) = &s.class {
1313 self.class(f, class)?;
1314 }
1315 Ok(())
1316 }
1317
1318 fn place_row(&self, k: usize, p: &Place) -> String {
1319 let mut text = format!("place p{k} {} {}+{} len {} {}", self.reference(p, &[]), base(p.base), p.offset, p.len, kind(&p.kind));
1320 if p.scaling != 0 {
1321 text += &format!(" scaling {}", p.scaling);
1322 }
1323 for o in &p.moved {
1324 text += &format!(" moved {}", self.odo(o));
1325 }
1326 for s in &p.subscripts {
1327 text += &format!(" stride {}", s.stride);
1328 }
1329 if let Some(t) = p.table {
1330 text += &format!(" table check {}+{}", t.displacement, t.extent);
1331 }
1332 for o in &p.odo {
1333 text += &format!(" odo {}", self.odo(o));
1334 }
1335 if p.refmod.as_ref().is_some_and(|r| r.check) {
1336 text += " refmod check";
1337 }
1338 match p.numcheck.lax {
1339 None => {}
1340 Some(LaxRedefinition::Signed) => text += " numcheck lax signed",
1341 Some(LaxRedefinition::LeadingSpaces(n)) => text += &format!(" numcheck lax {n} leading spaces"),
1342 }
1343 if p.numcheck.removed {
1344 text += " numcheck removed";
1345 }
1346 text + &self.at(p.at)
1347 }
1348
1349 fn file_row(&self, d: &FileDesc) -> String {
1350 let organization = match d.organization {
1351 Organization::Sequential => "sequential",
1352 Organization::LineSequential => "line-sequential",
1353 Organization::Indexed => "indexed",
1354 Organization::Relative => "relative",
1355 };
1356 let access = match d.access {
1357 Access::Sequential => "sequential",
1358 Access::Random => "random",
1359 Access::Dynamic => "dynamic",
1360 };
1361 let format = match d.format {
1362 Format::Fixed => "fixed",
1363 Format::Variable => "variable",
1364 Format::Text => "text",
1365 };
1366 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);
1367 let flags = [yes(d.optional, "optional"), yes(d.fixed, "fixed-length"), yes(d.sort, "sort")];
1368 text += &join(flags.into_iter().flatten().map(|s| format!(" {s}")), "");
1369 if let Some(min) = d.record_min {
1370 text += &format!(" record-min {min}");
1371 }
1372 if let Some(dep) = &d.depending {
1373 text += &format!(" depending {} from {} to {}", self.place(dep.item), dep.lengths.0, dep.lengths.1);
1374 }
1375 if let Some((p, plan)) = &d.status {
1376 text += &format!(" status {}{}", self.place(*p), self.moved(plan, SenderCheck::None));
1377 }
1378 if let Some(keys) = &d.keys {
1379 text += &format!(" key +{} len {}", keys.prime.offset, keys.prime.len);
1380 for (span, duplicates) in &keys.alternates {
1381 text += &format!(" alternate +{} len {}{}", span.offset, span.len, if *duplicates { " duplicates" } else { "" });
1382 }
1383 for (key, pieces) in &keys.split {
1384 let pieces: Vec<String> = pieces.iter().map(|p| format!("+{} len {}", p.offset, p.len)).collect();
1385 text += &format!(" split {key} = {}", pieces.join(", "));
1386 }
1387 }
1388 if let Some(r) = &d.relative {
1389 let digits = r.digits.map_or_else(String::new, |n| format!(" digits {n}"));
1390 text += &format!(" relative-key {} [{}] value {}{digits}", self.place(r.place), self.store(&r.store), self.int(&r.value));
1391 }
1392 if let Some(l) = &d.linage {
1393 text += &format!(" linage {}", self.int(&l.lines));
1394 for (key, value) in [("footing", &l.footing), ("top", &l.top), ("bottom", &l.bottom)] {
1395 if let Some(v) = value {
1396 text += &format!(" {key} {}", self.int(v));
1397 }
1398 }
1399 if let Some(c) = &l.counter {
1400 text += &format!(" counter {}", self.stored(c));
1401 }
1402 }
1403 if let Some(c) = d.carriage {
1404 text += &format!(" carriage {}{}", if c.machine { "machine" } else { "asa" }, if c.reserved { " reserved" } else { "" });
1405 }
1406 if let Some(r) = d.error {
1407 text += &format!(" error {}", self.range(r));
1408 }
1409 if let Some(a) = d.assign_item {
1410 text += &format!(" assign-item {} @{}", self.place(a.place), a.select);
1411 }
1412 text
1413 }
1414
1415 fn scope(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1416 let s = &self.c.services.scope;
1417 for &c in &s.containers {
1418 writeln!(f, "scope container {}", self.name(c))?;
1419 }
1420 for &c in &s.callable {
1421 writeln!(f, "scope callable {}", self.name(c))?;
1422 }
1423 for &c in &s.hidden {
1424 writeln!(f, "scope hidden {}", self.name(c))?;
1425 }
1426 for (record, binding) in &s.records {
1427 let binding = match binding {
1428 Binding::External { name, size } => format!("external {} size {size}", self.name(*name)),
1429 Binding::ExternalFile(k) => format!("external file {}", self.file(*k)),
1430 Binding::Global { program, section, name } => format!("global {} of {} in {}", self.name(*name), scope_section(*section), self.name(*program)),
1431 };
1432 writeln!(f, "scope record {} {binding}", self.linkage(*record))?;
1433 }
1434 for shared in &s.files {
1435 let from = match (shared.external, shared.declared_in) {
1436 (true, _) => "external".to_owned(),
1437 (false, Some(p)) => format!("global in {}", self.name(p)),
1438 (false, None) => "global".to_owned(),
1439 };
1440 writeln!(f, "scope file {} {from}", self.file(shared.file))?;
1441 }
1442 for (file, record) in &s.areas {
1443 writeln!(f, "scope area {} {}", self.file(*file), self.linkage(*record))?;
1444 }
1445 for g in &s.globals {
1446 let at = match g.at {
1447 GlobalAt::Program(offset) => format!("program+{offset}"),
1448 GlobalAt::Local(offset) => format!("local+{offset}"),
1449 GlobalAt::Linkage(record) => self.linkage(record),
1450 };
1451 writeln!(f, "scope global {} of {} at {at}", self.name(g.name), scope_section(g.section))?;
1452 }
1453 for (file, range) in &s.global_files {
1454 writeln!(f, "scope global-error {} {}", self.file(*file), self.range(*range))?;
1455 }
1456 for (mode, r) in MODES.iter().zip(&s.global_modes) {
1457 if let Some(r) = r {
1458 writeln!(f, "scope global-error {mode} {}", self.range(*r))?;
1459 }
1460 }
1461 Ok(())
1462 }
1463
1464 fn reports(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1465 let w = &self.c.services.report;
1466 if let Some(p) = w.print_switch {
1467 writeln!(f, "report print-switch {}", self.place_at(p))?;
1468 }
1469 for (k, r) in w.reports.iter().enumerate() {
1470 let mut text = format!("report {k} {} file {} width {}", word(&r.name), self.file_at(r.file), r.width);
1471 if let Some(c) = r.code {
1472 text += &format!(" code {}", self.konst(c));
1473 }
1474 if let Some(p) = r.page {
1475 text += &format!(" page limit {} heading {} first-detail {} last-detail {} footing {}", p.limit, p.heading, p.first_detail, p.last_detail, p.footing);
1476 }
1477 text += &format!(" page-counter {} line-counter {} state {}", self.place_at(r.page_counter), self.place_at(r.line_counter), self.place_at(r.state));
1478 let headings = [("report-heading", r.report_heading), ("page-heading", r.page_heading), ("page-footing", r.page_footing), ("report-footing", r.report_footing)];
1479 for (key, group) in headings {
1480 if let Some(g) = group {
1481 text += &format!(" {key} group {g}");
1482 }
1483 }
1484 for (key, list) in [("control-headings", &r.control_headings), ("control-footings", &r.control_footings)] {
1485 if list.iter().any(Option::is_some) {
1486 text += &format!(" {key} ({})", join(list.iter().map(|g| g.map_or_else(|| "-".to_owned(), |g| g.to_string())), " "));
1487 }
1488 }
1489 if let Some(n) = r.first_detail_written {
1490 text += &format!(" first-detail-written {n}");
1491 }
1492 writeln!(f, "{text}")?;
1493 for (c, control) in r.controls.iter().enumerate() {
1494 writeln!(f, "report {k} control {c} {} saved +{} len {}", self.place(control.reference), control.saved, control.len)?;
1495 }
1496 for (g, group) in r.groups.iter().enumerate() {
1497 let name = group.name.as_deref().map_or_else(|| "-".to_owned(), word);
1498 let mut text = format!("report {k} group {g} {name} {} level {}", group_kind(group.kind), group.level);
1499 if let Some(n) = group.next_group {
1500 text += &format!(" next-group {}", next_group(n));
1501 }
1502 if let Some(i) = group.indicate {
1503 text += &format!(" indicate +{i}");
1504 }
1505 if let Some(r) = group.declarative {
1506 text += &format!(" declarative {}", self.range(r));
1507 }
1508 if !group.totals.is_empty() {
1509 text += &format!(" totals ({})", join(group.totals.iter().map(|t| format!("sum{t}")), " "));
1510 }
1511 writeln!(f, "{text}")?;
1512 for (l, line) in group.lines.iter().enumerate() {
1513 writeln!(f, "report {k} group {g} line {l} {}", line_number(line.number))?;
1514 for field in &line.fields {
1515 writeln!(f, "report {k} group {g} line {l} {}", self.report_field(field))?;
1516 }
1517 }
1518 for field in &group.unprinted {
1519 writeln!(f, "report {k} group {g} unprinted {}", self.report_field(field))?;
1520 }
1521 for (key, list) in [("cross", &group.cross), ("roll", &group.rolls)] {
1522 for (sum, origin) in list {
1523 writeln!(f, "report {k} group {g} {key} sum{sum} <- {}", self.origin(origin))?;
1524 }
1525 }
1526 }
1527 for (s, sum) in r.sums.iter().enumerate() {
1528 let reset = sum.reset.map_or_else(String::new, |l| format!(" reset on control {l}"));
1529 writeln!(f, "report {k} sum{s} {}{reset}", self.place_at(sum.total))?;
1530 }
1531 for t in &r.subtotals {
1532 let adding = match &t.adding {
1533 Adding::EveryGenerate => "every generate".to_owned(),
1534 Adding::Upon(groups) => format!("upon groups ({})", join(groups.iter().map(usize::to_string), " ")),
1535 Adding::Correlated(groups) => format!("correlated groups ({})", join(groups.iter().map(usize::to_string), " ")),
1536 };
1537 writeln!(f, "report {k} subtotal sum{} <- {} {adding}", t.sum, self.comparand(&t.operand))?;
1538 }
1539 }
1540 Ok(())
1541 }
1542
1543 fn place_at(&self, place: usize) -> String {
1544 u32::try_from(place).map_or_else(|_| format!("p{place}?"), |p| self.place(p))
1545 }
1546
1547 fn report_field(&self, field: &Field) -> String {
1548 let content = match &field.content {
1549 FieldContent::Source(x) => format!("source {}", self.comparand(x)),
1550 FieldContent::Value(v) => format!("value {}", self.konst(*v)),
1551 FieldContent::Sum(s) => format!("sum{s}"),
1552 FieldContent::Program => "program".to_owned(),
1553 };
1554 let flags = [yes(field.group_indicate, "group indicate"), yes(field.blank_when_zero, "blank-when-zero"), yes(field.rounded, "rounded")];
1555 format!("column {} {} <- {content}{} @{}", field.column, self.place_at(field.item), attrs('{', flags.into_iter().flatten().collect()), self.position(&field.pos))
1556 }
1557
1558 fn origin(&self, origin: &Origin) -> String {
1559 match origin {
1560 Origin::Source(x) => self.comparand(x),
1561 Origin::Value(v) => self.konst(*v),
1562 Origin::Total(s) => format!("sum{s}"),
1563 }
1564 }
1565
1566 fn markup_nodes(&self, f: &mut fmt::Formatter<'_>, k: usize, m: &Markup) -> fmt::Result {
1567 let rows: Vec<String> = match m {
1568 Markup::JsonGenerate(g) => g.nodes.iter().map(|n| self.json_node(n)).collect(),
1569 Markup::XmlGenerate(g) => g.nodes.iter().map(|n| self.xml_node(n)).collect(),
1570 Markup::XmlParse(_) => Vec::new(),
1571 Markup::JsonParse(p) => p.nodes.iter().map(|n| self.parse_node(n)).collect(),
1572 };
1573 for (n, row) in rows.iter().enumerate() {
1574 writeln!(f, "markup m{k} node {n} {row}")?;
1575 }
1576 Ok(())
1577 }
1578
1579 fn node(&self, name: String, offset: u32, moved: &[Odo], len: u32, kind_of: &Kind, occurs: Option<&Count>) -> String {
1581 let mut text = format!("{name} +{offset} len {len} {}", kind(kind_of));
1582 if let Some(c) = occurs {
1583 text += &format!(" occurs {}", self.count(c));
1584 }
1585 for o in moved {
1586 text += &format!(" moved {}", self.odo(o));
1587 }
1588 text
1589 }
1590
1591 fn marker(&self, m: &Marker) -> String {
1592 match m {
1593 Marker::Byte(Some(b)) => format!("byte {}", hex("X", &[*b])),
1594 Marker::Byte(None) => "byte none".to_owned(),
1595 Marker::Condition(c) => format!("condition ({})", self.cond(*c)),
1596 Marker::Refused(a) => format!("refused {}", abend_ref(*a)),
1597 }
1598 }
1599
1600 fn convert(c: &Convert) -> String {
1601 match c {
1602 Convert::Chars { justified } => if *justified { "chars justified" } else { "chars" }.to_owned(),
1603 Convert::National => "national".to_owned(),
1604 Convert::Dbcs => "dbcs".to_owned(),
1605 Convert::Float(p) => format!("float {}", precision(*p)),
1606 Convert::Fixed { integers } => format!("fixed {integers} integers"),
1607 Convert::Scaled { integers, scaling } => format!("fixed {integers} integers scaled {scaling}"),
1608 Convert::Refused(a) => format!("refused {}", abend_ref(*a)),
1609 }
1610 }
1611
1612 fn suppress(list: &[Figurative]) -> String {
1613 if list.is_empty() { String::new() } else { format!(" suppress {}", join(list.iter().map(|f| figurative(*f).to_owned()), " ")) }
1614 }
1615
1616 fn json_node(&self, n: &JsonNode) -> String {
1617 let mut text = self.node(self.raw(n.name), n.offset, &n.moved, n.len, &n.kind, n.occurs.as_ref());
1618 if let Some((place, marker)) = &n.indicator {
1619 let place = place.map_or_else(|a| format!("refused {}", abend_ref(a)), |p| self.place(p));
1620 text += &format!(" indicator {place} {}", self.marker(marker));
1621 }
1622 if let Some(null) = n.null {
1623 text += &format!(" null {}", figurative(null));
1624 }
1625 text + &match &n.value {
1626 JsonValue::Object { members, eligible } => format!(" object ({}){}", members_list(members), if *eligible { " eligible" } else { "" }),
1627 JsonValue::Leaf(JsonLeaf { suppress, boolean, convert }) => {
1628 let boolean = boolean.as_ref().map_or_else(String::new, |m| format!(" boolean {}", self.marker(m)));
1629 format!(" leaf{}{boolean} {}", Self::suppress(suppress), Self::convert(convert))
1630 }
1631 }
1632 }
1633
1634 fn xml_node(&self, n: &XmlNode) -> String {
1635 let text = self.node(self.string(n.name), n.offset, &n.moved, n.len, &n.kind, n.occurs.as_ref());
1636 text + &match &n.value {
1637 XmlValue::Element { members } => format!(" element ({})", members_list(members)),
1638 XmlValue::Members { members } => format!(" members ({})", members_list(members)),
1639 XmlValue::Leaf { form, suppress, convert } => {
1640 let form = match form {
1641 XmlForm::Attribute => "attribute",
1642 XmlForm::Element => "element",
1643 XmlForm::Content => "content",
1644 };
1645 format!(" leaf {form}{} {}", Self::suppress(suppress), Self::convert(convert))
1646 }
1647 }
1648 }
1649
1650 fn set_to(&self, s: &SetTo) -> String {
1651 match s {
1652 SetTo::Nothing => "nothing".to_owned(),
1653 SetTo::Move { place, value, plan } => format!("{} <- {}{}", self.place(*place), self.konst(*value), self.moved(plan, SenderCheck::None)),
1654 SetTo::Refused(a) => format!("refused {}", abend_ref(*a)),
1655 }
1656 }
1657
1658 fn flag(&self, flag: &Flag) -> String {
1659 match flag {
1660 Flag::Set { on, off } => format!("set on {} off {}", self.set_to(on), self.set_to(off)),
1661 Flag::Literals { on, off } => format!(
1662 "literals on {}{} off {}{}",
1663 self.konst(on.0),
1664 self.moved(&on.1, SenderCheck::None),
1665 self.konst(off.0),
1666 self.moved(&off.1, SenderCheck::None)
1667 ),
1668 }
1669 }
1670
1671 fn parse_node(&self, n: &ParseNode) -> String {
1672 let name = match n.name {
1673 Named::Exactly(s) => self.string(s),
1674 Named::Folded(s) => format!("folded {}", self.name(s)),
1675 Named::Omitted => "omitted".to_owned(),
1676 };
1677 let mut text = self.node(name, n.offset, &n.moved, n.len, &n.kind, n.occurs.as_ref());
1678 if n.ignored {
1679 text += " ignoring null";
1680 }
1681 if let Some(Indicator { place, flag }) = &n.indicator {
1682 let place = match place {
1683 None => "-".to_owned(),
1684 Some(Ok(p)) => self.place(*p),
1685 Some(Err(a)) => format!("refused {}", abend_ref(*a)),
1686 };
1687 text += &format!(" indicator {place} {}", self.flag(flag));
1688 }
1689 if let Some((null, plan)) = &n.null {
1690 text += &format!(" null {}{}", figurative(*null), self.moved(plan, SenderCheck::None));
1691 }
1692 text + &match &n.value {
1693 ParseValue::Object { members } => format!(" object ({})", members_list(members)),
1694 ParseValue::Suppressed => " suppressed".to_owned(),
1695 ParseValue::Leaf(ParseLeaf { boolean, text, number }) => {
1696 let mut leaf = " leaf".to_owned();
1697 if let Some(b) = boolean {
1698 leaf += &format!(" boolean {}", self.flag(b));
1699 }
1700 if let Some(plan) = text {
1701 leaf += &format!(" text{}", self.moved(plan, SenderCheck::None));
1702 }
1703 leaf + &match number {
1704 NumberInto::Float(plan) => format!(" number float{}", self.moved(plan, SenderCheck::None)),
1705 NumberInto::Store(store) => format!(" number [{}]", self.store(store)),
1706 NumberInto::Edited(plan) => format!(" number edited{}", self.moved(plan, SenderCheck::None)),
1707 NumberInto::Digits => " number digits".to_owned(),
1708 NumberInto::Incompatible => " number incompatible".to_owned(),
1709 NumberInto::StoreScaled { store, scaling } => format!(" number [{}] scaled {scaling}", self.store(store)),
1710 NumberInto::EditedScaled { plan, scaling } => format!(" number edited{} scaled {scaling}", self.moved(plan, SenderCheck::None)),
1711 }
1712 }
1713 }
1714 }
1715
1716 fn class(&self, f: &mut fmt::Formatter<'_>, class: &Class) -> fmt::Result {
1718 writeln!(f, "class {} inherits {}", self.string(class.external), self.string(class.parent))?;
1719 for (key, part) in [("factory", &class.factory), ("object", &class.object)] {
1720 if let Some(part) = part {
1721 writeln!(f, "class {key} data records ({})", join(part.records.iter().map(|r| format!("+{r}")), " "))?;
1722 write!(f, "{}", Listing::of(&part.data))?;
1723 }
1724 }
1725 for (k, m) in class.methods.iter().enumerate() {
1726 let params = join(m.params.iter().map(|&s| self.string(s)), " ");
1727 let returns = m.returns.map_or_else(String::new, |s| format!(" returns {}", self.string(s)));
1728 let factory = if m.factory { " factory" } else { "" };
1729 writeln!(f, "class method {k} {}{factory} params ({params}){returns} own-records {}", self.name(m.name), m.own_records)?;
1730 write!(f, "{}", Listing::of(&m.code))?;
1731 }
1732 Ok(())
1733 }
1734}
1735
1736struct Names<'p, 'a>(&'p Printer<'a>);
1738
1739impl Handles<PlaceId, Operand, SymId> for Names<'_, '_> {
1740 type Place = String;
1741 type Value = String;
1742 type Text = String;
1743 type Error = Infallible;
1744
1745 fn place(&mut self, place: PlaceId) -> Result<String, Infallible> {
1746 Ok(self.0.place(place))
1747 }
1748
1749 fn value(&mut self, value: Operand) -> Result<String, Infallible> {
1750 Ok(self.0.operand(&value))
1751 }
1752
1753 fn text(&mut self, text: SymId) -> Result<String, Infallible> {
1754 Ok(self.0.name(text))
1755 }
1756}
1757
1758type CicsOpt = Option<Datum<String, String, String>>;
1759
1760fn option(words: &mut Vec<String>, key: &str, value: &CicsOpt) {
1761 match value {
1762 None => {}
1763 Some(Datum::Place(v) | Datum::Value(v) | Datum::Text(v)) => words.push(format!("{key}({v})")),
1764 Some(Datum::Bare) => words.push(key.to_owned()),
1765 }
1766}
1767
1768fn flag(words: &mut Vec<String>, key: &str, on: bool) {
1769 if on {
1770 words.push(key.to_owned());
1771 }
1772}
1773
1774impl Printer<'_> {
1775 fn cics_options(&self, command: &Cics<String, String, String>, w: &mut Vec<String>) {
1777 match command {
1778 Cics::File { verb: _, file, options: o } => {
1779 option(w, "FILE", file);
1780 for (key, value) in [("RIDFLD", &o.ridfld), ("KEYLENGTH", &o.keylength), ("REQID", &o.reqid), ("FROM", &o.from), ("NUMREC", &o.numrec)] {
1781 option(w, key, value);
1782 }
1783 for (key, value) in [("INTO", &o.record.into), ("SET", &o.record.set), ("LENGTH", &o.record.length)] {
1784 option(w, key, value);
1785 }
1786 for (key, on) in [("GENERIC", o.generic), ("RRN", o.rrn), ("GTEQ", o.gteq), ("EQUAL", o.equal), ("UPDATE", o.update)] {
1787 flag(w, key, on);
1788 }
1789 }
1790 Cics::Return { transid, commarea, length, channel, immediate } => {
1791 for (key, value) in [("TRANSID", transid), ("COMMAREA", commarea), ("LENGTH", length), ("CHANNEL", channel)] {
1792 option(w, key, value);
1793 }
1794 flag(w, "IMMEDIATE", *immediate);
1795 }
1796 Cics::Link(t) | Cics::Xctl(t) => {
1797 for (key, value) in [("PROGRAM", &t.program), ("COMMAREA", &t.commarea), ("LENGTH", &t.length)] {
1798 option(w, key, value);
1799 }
1800 }
1801 Cics::Abend { abcode, cancel } => {
1802 option(w, "ABCODE", abcode);
1803 flag(w, "CANCEL", *cancel);
1804 }
1805 Cics::HandleCondition(labels) => {
1806 w.extend(labels.iter().map(|(c, label)| label.map_or_else(|| c.name().to_owned(), |p| format!("{}({})", c.name(), self.para(p)))));
1807 }
1808 Cics::IgnoreCondition(conditions) => w.extend(conditions.iter().map(|c| c.name().to_owned())),
1809 Cics::PushHandle | Cics::PopHandle | Cics::HandleAid | Cics::Freemain | Cics::Enq | Cics::Deq | Cics::Delay | Cics::Unsupported => {}
1810 Cics::HandleAbend { program, label, reset } => {
1811 option(w, "PROGRAM", program);
1812 if let Some(p) = label {
1813 w.push(format!("LABEL({})", self.para(*p)));
1814 }
1815 flag(w, "RESET", *reset);
1816 }
1817 Cics::SendMap { map, mapset, from, maponly, dataonly, cursor, control } => {
1818 for (key, value) in [("MAP", map), ("MAPSET", mapset), ("FROM", from), ("CURSOR", cursor)] {
1819 option(w, key, value);
1820 }
1821 flag(w, "MAPONLY", *maponly);
1822 flag(w, "DATAONLY", *dataonly);
1823 control_flags(w, control);
1824 }
1825 Cics::ReceiveMap { map, mapset, into, set } => {
1826 for (key, value) in [("MAP", map), ("MAPSET", mapset), ("INTO", into), ("SET", set)] {
1827 option(w, key, value);
1828 }
1829 }
1830 Cics::SendControl { cursor, control } => {
1831 option(w, "CURSOR", cursor);
1832 control_flags(w, control);
1833 }
1834 Cics::Receive(r) => {
1835 for (key, value) in [("INTO", &r.into), ("SET", &r.set), ("LENGTH", &r.length)] {
1836 option(w, key, value);
1837 }
1838 }
1839 Cics::Asktime { abstime } => option(w, "ABSTIME", abstime),
1840 Cics::Formattime { abstime, datesep, timesep, outputs } => {
1841 for (key, value) in [("ABSTIME", abstime), ("DATESEP", datesep), ("TIMESEP", timesep)] {
1842 option(w, key, value);
1843 }
1844 for (key, value) in outputs {
1845 option(w, key, &Some(value.clone()));
1846 }
1847 }
1848 Cics::Assign(a) => {
1849 let options = [
1850 ("APPLID", &a.applid),
1851 ("SYSID", &a.sysid),
1852 ("USERID", &a.userid),
1853 ("NETNAME", &a.netname),
1854 ("FACILITY", &a.facility),
1855 ("STARTCODE", &a.startcode),
1856 ("ABCODE", &a.abcode),
1857 ("PROGRAM", &a.program),
1858 ("CWALENG", &a.cwaleng),
1859 ("TWALENG", &a.twaleng),
1860 ];
1861 for (key, value) in options {
1862 option(w, key, value);
1863 }
1864 }
1865 Cics::Getmain { flength, length, initimg, set } => {
1866 for (key, value) in [("FLENGTH", flength), ("LENGTH", length), ("INITIMG", initimg), ("SET", set)] {
1867 option(w, key, value);
1868 }
1869 }
1870 Cics::Syncpoint { rollback } => flag(w, "ROLLBACK", *rollback),
1871 Cics::Address { eib, commarea, cwa, twa } => {
1872 for (key, value) in [("EIB", eib), ("COMMAREA", commarea), ("CWA", cwa), ("TWA", twa)] {
1873 option(w, key, value);
1874 }
1875 }
1876 Cics::SendText { from, length } => {
1877 option(w, "FROM", from);
1878 option(w, "LENGTH", length);
1879 }
1880 Cics::WriteOperator { text, textlength } => {
1881 option(w, "TEXT", text);
1882 option(w, "TEXTLENGTH", textlength);
1883 }
1884 Cics::WriteqTs { queue, from, length, rewrite, item, numitems } => {
1885 for (key, value) in [("QUEUE", queue), ("FROM", from), ("LENGTH", length), ("ITEM", item), ("NUMITEMS", numitems)] {
1886 option(w, key, value);
1887 }
1888 flag(w, "REWRITE", *rewrite);
1889 }
1890 Cics::ReadqTs { queue, next, item, numitems, record } => {
1891 option(w, "QUEUE", queue);
1892 flag(w, "NEXT", *next);
1893 for (key, value) in [("ITEM", item), ("NUMITEMS", numitems), ("INTO", &record.into), ("SET", &record.set), ("LENGTH", &record.length)] {
1894 option(w, key, value);
1895 }
1896 }
1897 Cics::DeleteqTs { queue } | Cics::DeleteqTd { queue } => option(w, "QUEUE", queue),
1898 Cics::WriteqTd { queue, from, length } => {
1899 for (key, value) in [("QUEUE", queue), ("FROM", from), ("LENGTH", length)] {
1900 option(w, key, value);
1901 }
1902 }
1903 Cics::ReadqTd { queue, record } => {
1904 for (key, value) in [("QUEUE", queue), ("INTO", &record.into), ("SET", &record.set), ("LENGTH", &record.length)] {
1905 option(w, key, value);
1906 }
1907 }
1908 Cics::Refused(why) => w.push(format!("{why:?}")),
1909 }
1910 }
1911}
1912
1913fn control_flags(w: &mut Vec<String>, c: &crate::cics::Control) {
1914 for (key, on) in [("ERASE", c.erase), ("FREEKB", c.freekb), ("ALARM", c.alarm), ("FRSET", c.frset)] {
1915 flag(w, key, on);
1916 }
1917}
1918
1919fn word(s: &str) -> String {
1921 if !s.is_empty() && s.chars().all(|c| !c.is_control() && !c.is_whitespace()) { s.to_owned() } else { format!("{s:?}") }
1922}
1923
1924fn quote(s: &str) -> String {
1927 if s.chars().any(char::is_control) { format!("{s:?}") } else { format!("'{}'", s.replace('\'', "''")) }
1928}
1929
1930fn hex(prefix: &str, bytes: &[u8]) -> String {
1931 format!("{prefix}'{}'", join(bytes.iter().map(|b| format!("{b:02X}")), ""))
1932}
1933
1934fn national(units: &[u8]) -> String {
1936 let (pairs, odd) = units.as_chunks::<2>();
1937 let text = odd
1938 .is_empty()
1939 .then(|| char::decode_utf16(pairs.iter().map(|&u| u16::from_be_bytes(u))).collect::<Result<String, _>>().ok())
1940 .flatten()
1941 .filter(|t| !t.chars().any(char::is_control));
1942 text.map_or_else(|| hex("NX", units), |t| format!("N{}", quote(&t)))
1943}
1944
1945fn decimal(n: &Fixed) -> String {
1947 const MOST: u32 = 100;
1948 if n.places.dec > MOST {
1949 return format!("{:?}", n.magnitude);
1950 }
1951 let mut digits = Vec::new();
1952 let mut rest = n.magnitude;
1953 while !rest.is_zero() {
1954 let (q, r) = rest.div_rem(U256::from_u128(10));
1955 digits.push(char::from(b'0' + r.to_u128().and_then(|d| u8::try_from(d).ok()).unwrap_or(0)));
1956 rest = q;
1957 }
1958 let dec = n.places.dec as usize;
1959 while digits.len() <= dec {
1960 digits.push('0');
1961 }
1962 digits.reverse();
1963 let (int, frac): (String, String) = (digits[..digits.len() - dec].iter().collect(), digits[digits.len() - dec..].iter().collect());
1964 let sign = if n.negative { "-" } else { "" };
1965 if frac.is_empty() { format!("{sign}{int}") } else { format!("{sign}{int}.{frac}") }
1966}
1967
1968fn native_word(native: Native) -> &'static str {
1969 match native {
1970 Native::No => "",
1971 Native::Comp5 => " native",
1972 Native::BinaryChar => " binary-char",
1973 Native::CompX => " comp-x",
1974 Native::Comp5Bytes => " native-bytes",
1975 }
1976}
1977
1978fn pic(digits: u32, scale: u32, signed: bool) -> String {
1979 let int = digits.saturating_sub(scale);
1980 let mut text = if signed { "S".to_owned() } else { String::new() };
1981 if int > 0 || scale == 0 {
1982 text += &format!("9({int})");
1983 }
1984 if scale > 0 {
1985 text += &format!("V9({scale})");
1986 }
1987 text
1988}
1989
1990fn sign_clause(sign: Option<SignClause>) -> String {
1991 sign.map_or_else(String::new, |s| {
1992 let position = match s.position {
1993 SignPosition::Leading => "leading",
1994 SignPosition::Trailing => "trailing",
1995 };
1996 format!(" sign {position}{}", if s.separate { " separate" } else { "" })
1997 })
1998}
1999
2000fn kind(k: &Kind) -> String {
2001 match *k {
2002 Kind::Group => "group".to_owned(),
2003 Kind::Alnum { justified } => if justified { "alnum justified" } else { "alnum" }.to_owned(),
2004 Kind::National => "national".to_owned(),
2005 Kind::Dbcs { justified, edit } => format!("dbcs{}{}", if justified { " justified" } else { "" }, edit.map_or(String::new(), |e| format!(" edit {e}"))),
2006 Kind::Zoned { digits, scale, signed, sign } => format!("zoned {}{}", pic(digits, scale, signed), sign_clause(sign)),
2007 Kind::Packed { digits, scale, signed } => format!("packed {}", pic(digits, scale, signed)),
2008 Kind::Binary { digits, scale, signed, native } => format!("binary {}{}", pic(digits, scale, signed), native_word(native)),
2009 Kind::Float(p) => format!("float {}", precision(p)),
2010 Kind::NumericEdited { edit, digits, scale, blank_when_zero } => {
2011 format!("numeric-edited edit {edit} {}{}", pic(digits, scale, false), if blank_when_zero { " blank-when-zero" } else { "" })
2012 }
2013 Kind::AlnumEdited { edit } => format!("alnum-edited edit {edit}"),
2014 Kind::Pointer => "pointer".to_owned(),
2015 Kind::Index => "index".to_owned(),
2016 Kind::ObjectReference => "object-reference".to_owned(),
2017 Kind::ProgramPointer => "program-pointer".to_owned(),
2018 }
2019}
2020
2021fn base(b: Base) -> String {
2022 match b {
2023 Base::Program => "program".to_owned(),
2024 Base::Local => "local".to_owned(),
2025 Base::Linkage(n) => format!("linkage{n}"),
2026 Base::ReturnCode => "return-code".to_owned(),
2027 Base::Eib => "eib".to_owned(),
2028 Base::SelfRef => "self".to_owned(),
2029 Base::JniEnv => "jnienv".to_owned(),
2030 Base::Xml(r) => match r {
2031 XmlRegister::Text => "xml-text",
2032 XmlRegister::NText => "xml-ntext",
2033 XmlRegister::Namespace => "xml-namespace",
2034 XmlRegister::NNamespace => "xml-nnamespace",
2035 XmlRegister::Prefix => "xml-namespace-prefix",
2036 XmlRegister::NPrefix => "xml-nnamespace-prefix",
2037 }
2038 .to_owned(),
2039 }
2040}
2041
2042fn precision(p: Precision) -> &'static str {
2043 match p {
2044 Precision::Short => "short",
2045 Precision::Long => "long",
2046 Precision::Extended => "extended",
2047 }
2048}
2049
2050fn figurative(f: Figurative) -> &'static str {
2051 match f {
2052 Figurative::Zero => "ZERO",
2053 Figurative::Space => "SPACE",
2054 Figurative::HighValue => "HIGH-VALUE",
2055 Figurative::LowValue => "LOW-VALUE",
2056 Figurative::Quote => "QUOTE",
2057 Figurative::Null => "NULL",
2058 }
2059}
2060
2061fn binop(op: BinOp) -> &'static str {
2062 match op {
2063 BinOp::Add => "+",
2064 BinOp::Sub => "-",
2065 BinOp::Mul => "*",
2066 BinOp::Div => "/",
2067 BinOp::Pow => "**",
2068 }
2069}
2070
2071fn relop(op: RelOp) -> &'static str {
2072 match op {
2073 RelOp::Eq => "=",
2074 RelOp::Ne => "<>",
2075 RelOp::Lt => "<",
2076 RelOp::Le => "<=",
2077 RelOp::Gt => ">",
2078 RelOp::Ge => ">=",
2079 }
2080}
2081
2082fn compare(c: Compare) -> String {
2083 match c {
2084 Compare::PackedPfd => "packed-pfd".to_owned(),
2085 Compare::Address => "address".to_owned(),
2086 Compare::Float => "float".to_owned(),
2087 Compare::Fixed => "fixed".to_owned(),
2088 Compare::National => "national".to_owned(),
2089 Compare::Dbcs => "dbcs".to_owned(),
2090 Compare::Alphanumeric => "alnum".to_owned(),
2091 Compare::Refused(a) => format!("refused {}", abend_ref(a)),
2092 Compare::References => "references".to_owned(),
2093 Compare::ZonedBytes { zoned_first } => format!("zoned-bytes {}", if zoned_first { "left" } else { "right" }),
2094 }
2095}
2096
2097fn byte_class(c: super::ByteClass) -> String {
2098 use super::ByteClass;
2099 let name = match c {
2100 ByteClass::Packed { signed: true } => "numeric [packed signed]",
2101 ByteClass::Packed { signed: false } => "numeric [packed]",
2102 ByteClass::Zoned { signed: true } => "numeric [zoned signed]",
2103 ByteClass::Zoned { signed: false } => "numeric [zoned]",
2104 ByteClass::Digits => "numeric [digits]",
2105 ByteClass::Alphabetic => "alphabetic",
2106 ByteClass::AlphabeticLower => "alphabetic-lower",
2107 ByteClass::AlphabeticUpper => "alphabetic-upper",
2108 ByteClass::Dbcs => "dbcs",
2109 ByteClass::Kanji => "kanji",
2110 ByteClass::Set { bits } => return format!("class [{}]", (0..=255u8).filter(|&b| bits[usize::from(b / 8)] >> (b % 8) & 1 == 1).map(|b| format!("{b:02X}")).collect::<Vec<_>>().join(" ")),
2111 };
2112 name.to_owned()
2113}
2114
2115fn sign_test(t: SignTest) -> &'static str {
2116 match t {
2117 SignTest::Positive => "positive",
2118 SignTest::Negative => "negative",
2119 SignTest::Zero => "zero",
2120 }
2121}
2122
2123fn sql_test(t: SqlTest) -> &'static str {
2124 match t {
2125 SqlTest::Error => "error",
2126 SqlTest::NotFound => "not found",
2127 SqlTest::Warning => "warning",
2128 }
2129}
2130
2131fn ending(e: Ending) -> &'static str {
2132 match e {
2133 Ending::Goback => "goback",
2134 Ending::StopRun => "stop-run",
2135 Ending::EndOfProgram => "end-of-program",
2136 }
2137}
2138
2139fn range_kind(k: RangeKind) -> &'static str {
2140 match k {
2141 RangeKind::Perform => "perform",
2142 RangeKind::SortProcedure => "sort-procedure",
2143 RangeKind::UseBeforeReporting => "use-before-reporting",
2144 RangeKind::UseProcedure => "use-procedure",
2145 RangeKind::Debugging => "debugging",
2146 RangeKind::Processing => "processing",
2147 }
2148}
2149
2150fn image(i: Image) -> String {
2151 match i {
2152 Image::Bytes => String::new(),
2153 Image::All => " all".to_owned(),
2154 Image::Figurative => " figurative".to_owned(),
2155 Image::Digits { digits } => format!(" digits {digits}"),
2156 Image::Stored => " stored".to_owned(),
2157 }
2158}
2159
2160fn national_from(n: NationalFrom) -> &'static str {
2161 match n {
2162 NationalFrom::Units => "units",
2163 NationalFrom::Decoded => "decoded",
2164 NationalFrom::Figurative => "figurative",
2165 NationalFrom::Dbcs => "dbcs",
2166 }
2167}
2168
2169fn numeric_from(n: NumericFrom) -> String {
2170 match n {
2171 NumericFrom::Value => "value".to_owned(),
2172 NumericFrom::PackedCopy => "packed-copy".to_owned(),
2173 NumericFrom::Float => "float".to_owned(),
2174 NumericFrom::Zero => "zero".to_owned(),
2175 NumericFrom::Fill => "fill".to_owned(),
2176 NumericFrom::Zoned => "zoned".to_owned(),
2177 NumericFrom::DeEdit { edit, digits, scale } => format!("de-edit edit {edit} {}", pic(digits, scale, false)),
2178 }
2179}
2180
2181fn float_from(f: FloatFrom) -> &'static str {
2182 match f {
2183 FloatFrom::Float => "float",
2184 FloatFrom::Fixed => "fixed",
2185 FloatFrom::Zero => "zero",
2186 }
2187}
2188
2189fn accept_from(a: AcceptFrom) -> &'static str {
2190 match a {
2191 AcceptFrom::Sysin => "SYSIN",
2192 AcceptFrom::Date { four_digit_year: false } => "DATE",
2193 AcceptFrom::Date { four_digit_year: true } => "DATE YYYYMMDD",
2194 AcceptFrom::Day { four_digit_year: false } => "DAY",
2195 AcceptFrom::Day { four_digit_year: true } => "DAY YYYYDDD",
2196 AcceptFrom::DayOfWeek => "DAY-OF-WEEK",
2197 AcceptFrom::Time => "TIME",
2198 AcceptFrom::CommandLine => "COMMAND-LINE",
2199 AcceptFrom::ArgumentNumber => "ARGUMENT-NUMBER",
2200 AcceptFrom::ArgumentValue => "ARGUMENT-VALUE",
2201 AcceptFrom::EnvironmentValue => "ENVIRONMENT-VALUE",
2202 }
2203}
2204
2205fn open_mode(m: OpenMode) -> &'static str {
2206 match m {
2207 OpenMode::Input => "INPUT",
2208 OpenMode::Output => "OUTPUT",
2209 OpenMode::Extend => "EXTEND",
2210 OpenMode::InputOutput => "I-O",
2211 }
2212}
2213
2214fn closing_text(c: Closing) -> &'static str {
2215 match c {
2216 Closing::Volume => "volume",
2217 Closing::NoRewind => "no rewind",
2218 Closing::Lock => "lock",
2219 }
2220}
2221
2222fn phrase_words(p: Option<Phrase>, on: &str, not_on: &str) -> Vec<String> {
2223 p.map_or_else(Vec::new, |p| [yes(p.on, on), yes(p.not_on, not_on)].into_iter().flatten().collect())
2224}
2225
2226fn host_type(t: &HostType) -> String {
2227 let unsigned = |signed: bool| if signed { "" } else { " unsigned" };
2228 match t {
2229 HostType::SmallInt { signed } => format!("smallint{}", unsigned(*signed)),
2230 HostType::Integer { signed } => format!("integer{}", unsigned(*signed)),
2231 HostType::BigInt { signed } => format!("bigint{}", unsigned(*signed)),
2232 HostType::Decimal { digits, scale, signed } => format!("decimal({digits},{scale}){}", unsigned(*signed)),
2233 HostType::Zoned { digits, scale, signed, sign } => format!("zoned({digits},{scale}){}{}", unsigned(*signed), sign_clause(*sign)),
2234 HostType::Real => "real".to_owned(),
2235 HostType::Double => "double".to_owned(),
2236 HostType::Char(n) => format!("char({n})"),
2237 HostType::VarChar(n) => format!("varchar({n})"),
2238 HostType::Graphic(n) => format!("graphic({n})"),
2239 HostType::VarGraphic(n) => format!("vargraphic({n})"),
2240 HostType::Structure(members) => format!("structure of {}", members.len()),
2241 }
2242}
2243
2244fn sqlca_field(f: SqlcaField) -> String {
2245 match f {
2246 SqlcaField::CaId => "SQLCAID".to_owned(),
2247 SqlcaField::CaBc => "SQLCABC".to_owned(),
2248 SqlcaField::Code => "SQLCODE".to_owned(),
2249 SqlcaField::ErrMl => "SQLERRML".to_owned(),
2250 SqlcaField::ErrMc => "SQLERRMC".to_owned(),
2251 SqlcaField::ErrP => "SQLERRP".to_owned(),
2252 SqlcaField::State => "SQLSTATE".to_owned(),
2253 SqlcaField::ErrD(n) => format!("SQLERRD({n})"),
2254 SqlcaField::Warn(n) => format!("SQLWARN{}", if n == 10 { "A".to_owned() } else { n.to_string() }),
2255 }
2256}
2257
2258fn scope_section(s: Section) -> &'static str {
2259 match s {
2260 Section::WorkingStorage => "working-storage",
2261 Section::LocalStorage => "local-storage",
2262 Section::Linkage => "linkage",
2263 Section::File => "file",
2264 }
2265}
2266
2267fn group_kind(k: GroupKind) -> &'static str {
2268 match k {
2269 GroupKind::ReportHeading => "report-heading",
2270 GroupKind::PageHeading => "page-heading",
2271 GroupKind::ControlHeading => "control-heading",
2272 GroupKind::Detail => "detail",
2273 GroupKind::ControlFooting => "control-footing",
2274 GroupKind::PageFooting => "page-footing",
2275 GroupKind::ReportFooting => "report-footing",
2276 }
2277}
2278
2279fn next_group(n: NextGroup) -> String {
2280 match n {
2281 NextGroup::Line(l) => format!("line {l}"),
2282 NextGroup::Plus(l) => format!("plus {l}"),
2283 NextGroup::NextPage => "next page".to_owned(),
2284 }
2285}
2286
2287fn line_number(n: LineNumber) -> String {
2288 match n {
2289 LineNumber::Line(l) => format!("at {l}"),
2290 LineNumber::Plus(l) => format!("plus {l}"),
2291 LineNumber::NextPage(None) => "next page".to_owned(),
2292 LineNumber::NextPage(Some(l)) => format!("next page at {l}"),
2293 }
2294}
2295
2296fn members_list(members: &[u32]) -> String {
2297 join(members.iter().map(u32::to_string), " ")
2298}
2299
2300const MODES: [&str; 4] = ["input", "output", "i-o", "extend"];
2302
2303#[cfg(test)]
2304mod tests {
2305 use super::*;
2306 use numeric::precision::Places;
2307
2308 #[test]
2309 fn a_number_prints_with_every_decimal_place_it_holds() {
2310 let n = |value: i128, int: u32, dec: u32| decimal(&Fixed::new(value, Places::new(int, dec)));
2311 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"]);
2312 }
2313
2314 #[test]
2315 fn text_prints_on_one_line_whatever_it_holds() {
2316 assert_eq!(quote("IT'S"), "'IT''S'");
2317 assert_eq!(quote("A\nB"), "\"A\\nB\"");
2318 assert_eq!(word("WS-A"), "WS-A");
2319 assert_eq!(word("A B"), "\"A B\"");
2320 assert_eq!(national(&[0, 0x41, 0, 0x42]), "N'AB'");
2321 assert_eq!(national(&[0xD8, 0x00]), "NX'D800'");
2322 assert_eq!(pic(9, 2, true), "S9(7)V9(2)");
2323 assert_eq!(pic(2, 2, false), "V9(2)");
2324 assert_eq!(pic(4, 0, false), "9(4)");
2325 }
2326
2327 #[test]
2328 fn tables_that_refer_to_each_other_in_a_cycle_print_and_stop() {
2329 let (plans, services) = (Plans::default(), Services::default());
2330 let exprs = [Expr::Bin(0, BinOp::Add, 0)];
2331 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 }];
2332 let blocks = [Block { ops: vec![Op::Display(4)], end: Terminator::Branch { cond: 0, then: 0, otherwise: 1 } }];
2333 let code = Code {
2334 id: 0,
2335 initial: false,
2336 recursive: false,
2337 paragraphs: &[],
2338 procedure_start: 0,
2339 ranges: &[],
2340 blocks: &blocks,
2341 places: &[],
2342 exprs: &exprs,
2343 conds: &conds,
2344 consts: &[],
2345 plans: &plans,
2346 services: &services,
2347 abends: &[],
2348 symbols: &[],
2349 };
2350 let text = Listing { code, items: &[], debug: None, sql: &[], ccsid: None }.to_string();
2351 let lines: Vec<&str> = text.lines().collect();
2352 assert_eq!(lines[..3], ["program symbol0?", "b0:", " Display display4?"]);
2353 assert!(lines[3].starts_with(" Branch (((((") && lines[3].contains("...") && lines[3].ends_with(" = c9? [fixed]) b0 else b1"), "{text}");
2354 }
2355}