@portll/cobolwork 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. package/README.md +89 -26
  2. package/STABILITY.md +76 -0
  3. package/bin/cobolwork.mjs +56 -14
  4. package/lib/baseline.mjs +9 -0
  5. package/lib/bms.mjs +21 -7
  6. package/lib/build.mjs +133 -53
  7. package/lib/capabilities.mjs +13 -11
  8. package/lib/cics-commands.mjs +501 -9
  9. package/lib/compliance.mjs +11 -0
  10. package/lib/consequence.mjs +17 -0
  11. package/lib/control-reuse.mjs +113 -0
  12. package/lib/control-workers.mjs +161 -0
  13. package/lib/control.mjs +77 -4
  14. package/lib/dataflow.mjs +402 -152
  15. package/lib/db2/cursor.mjs +15 -0
  16. package/lib/db2/read.mjs +170 -0
  17. package/lib/db2/rules.mjs +77 -0
  18. package/lib/db2/stmt/alter.mjs +473 -0
  19. package/lib/db2/stmt/grant.mjs +125 -0
  20. package/lib/db2/stmt/index.mjs +141 -0
  21. package/lib/db2/stmt/misc.mjs +325 -0
  22. package/lib/db2/stmt/routine.mjs +564 -0
  23. package/lib/db2/stmt/storage.mjs +146 -0
  24. package/lib/db2/stmt/table.mjs +540 -0
  25. package/lib/db2/stmt/view.mjs +146 -0
  26. package/lib/diff.mjs +17 -5
  27. package/lib/equivalence.mjs +44 -5
  28. package/lib/evidence/cli.mjs +26 -7
  29. package/lib/evidence/record.mjs +16 -6
  30. package/lib/evidence/seal.mjs +17 -0
  31. package/lib/evidence/store.mjs +44 -15
  32. package/lib/evidence/timestamp.mjs +83 -0
  33. package/lib/evidence/verify.mjs +120 -42
  34. package/lib/exec-reading.mjs +51 -0
  35. package/lib/execution.mjs +3 -2
  36. package/lib/explain.mjs +2 -0
  37. package/lib/exploitability.mjs +11 -2
  38. package/lib/hlasm/asm/data.mjs +128 -0
  39. package/lib/hlasm/asm/listing.mjs +43 -0
  40. package/lib/hlasm/asm/output.mjs +45 -0
  41. package/lib/hlasm/asm/sections.mjs +161 -0
  42. package/lib/hlasm/asm/symbols.mjs +78 -0
  43. package/lib/hlasm/exec.mjs +27 -0
  44. package/lib/hlasm/expr.mjs +167 -0
  45. package/lib/hlasm/instr.mjs +73 -0
  46. package/lib/hlasm/locate.mjs +333 -0
  47. package/lib/hlasm/macro/authorization.mjs +146 -0
  48. package/lib/hlasm/macro/datasets.mjs +107 -0
  49. package/lib/hlasm/macro/io.mjs +162 -0
  50. package/lib/hlasm/macro/le.mjs +53 -0
  51. package/lib/hlasm/macro/linkage.mjs +143 -0
  52. package/lib/hlasm/macro/operator.mjs +77 -0
  53. package/lib/hlasm/macro/program.mjs +184 -0
  54. package/lib/hlasm/macro/recovery.mjs +85 -0
  55. package/lib/hlasm/macro/storage.mjs +167 -0
  56. package/lib/hlasm/macro/structured.mjs +131 -0
  57. package/lib/hlasm/model.mjs +97 -0
  58. package/lib/hlasm/mvs38.mjs +47 -0
  59. package/lib/hlasm/operands.mjs +59 -0
  60. package/lib/hlasm/optable.mjs +61 -0
  61. package/lib/hlasm/read.mjs +130 -0
  62. package/lib/hlasm.mjs +44 -7
  63. package/lib/ims/dli.mjs +37 -0
  64. package/lib/ims/macro/dbd.mjs +299 -0
  65. package/lib/ims/macro/psb.mjs +286 -0
  66. package/lib/ims/model.mjs +149 -0
  67. package/lib/ims/operands.mjs +23 -0
  68. package/lib/ims/read.mjs +37 -0
  69. package/lib/ims/rules.mjs +135 -0
  70. package/lib/inventory.mjs +10 -7
  71. package/lib/ironwork-ids.mjs +24 -0
  72. package/lib/ironwork.mjs +20 -13
  73. package/lib/kernel/pds-archive.mjs +256 -0
  74. package/lib/kernel/registry.mjs +32 -23
  75. package/lib/kernel/shared-pass.mjs +163 -0
  76. package/lib/kernel/source-tree.mjs +104 -36
  77. package/lib/kernel/version-key.mjs +17 -0
  78. package/lib/layout.mjs +26 -31
  79. package/lib/options.mjs +26 -4
  80. package/lib/parser.mjs +136 -17
  81. package/lib/pli/cursor.mjs +15 -0
  82. package/lib/pli/expr.mjs +101 -0
  83. package/lib/pli/include.mjs +82 -0
  84. package/lib/pli/layout.mjs +125 -0
  85. package/lib/pli/lex.mjs +198 -0
  86. package/lib/pli/program.mjs +280 -0
  87. package/lib/pli/rules/based.mjs +95 -0
  88. package/lib/pli/rules/conditions.mjs +68 -0
  89. package/lib/pli/rules/entry.mjs +130 -0
  90. package/lib/pli/rules/index.mjs +24 -0
  91. package/lib/pli/rules/preprocessor.mjs +55 -0
  92. package/lib/pli/statements.mjs +130 -0
  93. package/lib/pli/stmt/alloc.mjs +45 -0
  94. package/lib/pli/stmt/assignment.mjs +56 -0
  95. package/lib/pli/stmt/call.mjs +104 -0
  96. package/lib/pli/stmt/conditions.mjs +94 -0
  97. package/lib/pli/stmt/control.mjs +219 -0
  98. package/lib/pli/stmt/declare.mjs +149 -0
  99. package/lib/pli/stmt/exec.mjs +55 -0
  100. package/lib/pli/stmt/io.mjs +239 -0
  101. package/lib/pli/stmt/misc.mjs +4 -0
  102. package/lib/pli/stmt/preprocessor.mjs +242 -0
  103. package/lib/pli/stmt/procedure.mjs +258 -0
  104. package/lib/pli/stmt/stream.mjs +283 -0
  105. package/lib/pli/storage.mjs +129 -0
  106. package/lib/policy.mjs +6 -0
  107. package/lib/precompile-check.mjs +124 -0
  108. package/lib/precompile-cics.mjs +8 -4
  109. package/lib/reach.mjs +11 -2
  110. package/lib/revision.json +1 -1
  111. package/lib/sarif.mjs +44 -3
  112. package/lib/scan.mjs +28 -12
  113. package/lib/sets/abend.mjs +82 -8
  114. package/lib/sets/build.mjs +43 -37
  115. package/lib/sets/cics.mjs +20 -38
  116. package/lib/sets/compile.mjs +46 -18
  117. package/lib/sets/copybook.mjs +5 -3
  118. package/lib/sets/crypto.mjs +5 -3
  119. package/lib/sets/ddl.mjs +36 -0
  120. package/lib/sets/flow.mjs +32 -8
  121. package/lib/sets/hidden.mjs +5 -3
  122. package/lib/sets/hlasm.mjs +124 -8
  123. package/lib/sets/ims.mjs +139 -0
  124. package/lib/sets/log.mjs +11 -9
  125. package/lib/sets/opaque.mjs +27 -7
  126. package/lib/sets/pli.mjs +40 -0
  127. package/lib/sets/priv.mjs +5 -3
  128. package/lib/sets/recon.mjs +5 -3
  129. package/lib/sets/secrets.mjs +112 -0
  130. package/lib/sets/semantics.mjs +8 -3
  131. package/lib/sets/web.mjs +39 -23
  132. package/lib/site.mjs +10 -0
  133. package/lib/sources.mjs +80 -20
  134. package/lib/statement-cursor.mjs +67 -0
  135. package/lib/verify.mjs +3 -2
  136. package/lib/version.mjs +6 -0
  137. package/package.json +3 -2
  138. package/rules/compliance-cobit2019.json +520 -5
  139. package/rules/compliance-dora.json +509 -5
  140. package/rules/compliance-ffiec.json +505 -1
  141. package/rules/compliance-nist80053.json +557 -1
  142. package/rules/gitleaks-mainframe.toml +46 -15
  143. package/rules/hlasm-instructions.json +2396 -0
  144. package/rules/hlasm-optables.json +8024 -0
  145. package/schema/cobolwork-baseline.schema.json +36 -0
  146. package/schema/cobolwork-build-provenance.schema.json +187 -0
  147. package/schema/cobolwork-build.schema.json +382 -0
  148. package/schema/cobolwork-capabilities.schema.json +239 -0
  149. package/schema/cobolwork-diff.schema.json +217 -0
  150. package/schema/cobolwork-evidence.schema.json +161 -0
  151. package/schema/cobolwork-execution.schema.json +53 -0
  152. package/schema/cobolwork-explain.schema.json +360 -0
  153. package/schema/cobolwork-finding.schema.json +465 -0
  154. package/schema/cobolwork-flow.schema.json +465 -0
  155. package/schema/cobolwork-gate.schema.json +211 -0
  156. package/schema/cobolwork-inventory.schema.json +206 -0
  157. package/schema/cobolwork-parse.schema.json +105 -0
  158. package/schema/cobolwork-reach.schema.json +74 -0
  159. package/schema/cobolwork-report.schema.json +559 -0
  160. package/schema/cobolwork-witness.schema.json +107 -0
  161. package/schema/cobolwork.baseline.schema.json +101 -0
  162. package/schema/cobolwork.policy.schema.json +7 -0
  163. package/schema/cobolwork.site.schema.json +116 -0
@@ -0,0 +1,333 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // Where the assembler would put each open-code statement and symbol: location counters per control
3
+ // and dummy section, symbol values, length attributes and types, literal pools, and the external
4
+ // symbols a module defines and needs. Offsets are relative to their section. Nothing after a
5
+ // statement whose length cannot be known (a macro call, a COPY member, a conditional branch, a
6
+ // statement the reader refused) is placed in its section: its location is null and says after which
7
+ // line, since a guessed location would be graded as the assembler's.
8
+ // https://www.ibm.com/docs/en/hla-and-tf/1.6.0?topic=reference-location-counter
9
+ import { readHlasmStatements, parseHlasmStatement } from './read.mjs';
10
+ import { NAME } from './operands.mjs';
11
+
12
+ const ALIGN = { H: 2, Y: 2, S: 2, F: 4, E: 4, A: 4, V: 4, Q: 4, J: 4, R: 4, D: 8, L: 8 };
13
+ const ALIGN_EXT = { FD: 8, AD: 8, VD: 8, QD: 8, JD: 8, RD: 8, LQ: 16, SY: 1, QY: 1 };
14
+ const FIXED = { F: 4, H: 2, E: 4, D: 8, L: 16, A: 4, Y: 2, S: 2, V: 4, Q: 4, J: 4, R: 4 };
15
+ const FIXED_EXT = { FD: 8, AD: 8, VD: 8, QD: 8, JD: 8, RD: 8, SY: 3, QY: 3 };
16
+ const NO_STORAGE = new Set(['USING', 'DROP', 'PRINT', 'TITLE', 'EJECT', 'SPACE', 'CEJECT', 'AMODE', 'RMODE', 'ENTRY',
17
+ 'EXTRN', 'WXTRN', 'PUSH', 'POP', 'MNOTE', 'ICTL', 'ISEQ', 'PUNCH', 'REPRO', 'OPSYN', 'ACONTROL', 'ADATA', 'EXITCTL',
18
+ 'XATTR', 'ALIAS', 'CATTR', 'MACRO DEFINITION', 'PROTOTYPE', 'MODEL']);
19
+ const SECTION = { CSECT: 'CST', RSECT: 'CST', START: 'CST', COM: 'COM', DSECT: 'DST' };
20
+ // Conditional assembly that only declares or sets variables does not change which statements are read.
21
+ const QUIET_CONDITIONAL = new Set(['ANOP', 'SETA', 'SETB', 'SETC', 'LCLA', 'LCLB', 'LCLC', 'GBLA', 'GBLB', 'GBLC', 'ACTR', 'MHELP']);
22
+
23
+ const alignUp = (n, a) => Math.ceil(n / a) * a;
24
+ const typeKey = (o) => `${o.type}${o.ext || ''}`;
25
+
26
+ const EBCDIC = (() => {
27
+ const t = {};
28
+ const rows = [[0x40, ' '], [0x4b, '.'], [0x4c, '<'], [0x4d, '('], [0x4e, '+'], [0x50, '&'], [0x5b, '$'], [0x5c, '*'], [0x5d, ')'], [0x5e, ';'],
29
+ [0x60, '-'], [0x61, '/'], [0x6b, ','], [0x6c, '%'], [0x6d, '_'], [0x6e, '>'], [0x6f, '?'], [0x7a, ':'], [0x7b, '#'], [0x7c, '@'], [0x7d, "'"], [0x7e, '='], [0x7f, '"']];
30
+ for (const [code, ch] of rows) t[ch] = code;
31
+ 'ABCDEFGHI'.split('').forEach((c, i) => { t[c] = 0xc1 + i; t[c.toLowerCase()] = 0x81 + i; });
32
+ 'JKLMNOPQR'.split('').forEach((c, i) => { t[c] = 0xd1 + i; t[c.toLowerCase()] = 0x91 + i; });
33
+ 'STUVWXYZ'.split('').forEach((c, i) => { t[c] = 0xe2 + i; t[c.toLowerCase()] = 0xa2 + i; });
34
+ '0123456789'.split('').forEach((c, i) => { t[c] = 0xf0 + i; });
35
+ return t;
36
+ })();
37
+
38
+ function selfDefining(sdt) {
39
+ const text = sdt.text;
40
+ if (sdt.type === 'X') return /^[0-9A-F]+$/i.test(text) ? parseInt(text, 16) : null;
41
+ if (sdt.type === 'B') return /^[01]+$/.test(text) ? parseInt(text, 2) : null;
42
+ if (sdt.type === 'C' || sdt.type === 'CE') {
43
+ let v = 0;
44
+ for (const ch of text) { if (EBCDIC[ch] === undefined) return null; v = v * 256 + EBCDIC[ch]; }
45
+ return v;
46
+ }
47
+ return null;
48
+ }
49
+
50
+ // The values, one per constant, an operand's nominal field holds, and the implicit length of each.
51
+ function nominalLengths(o) {
52
+ const key = typeKey(o);
53
+ const n = o.nominal;
54
+ if (n && n.expressions) return n.expressions.map(() => FIXED_EXT[key] ?? FIXED[o.type]);
55
+ if (FIXED_EXT[key] || FIXED[o.type]) {
56
+ const count = n && n.quoted !== undefined ? n.quoted.split(',').length : 1;
57
+ return Array(count).fill(FIXED_EXT[key] ?? FIXED[o.type]);
58
+ }
59
+ if (!n) return [1];
60
+ const q = n.quoted;
61
+ if (o.type === 'C') return [o.ext === 'U' ? q.length * 2 : q.length];
62
+ if (o.type === 'X') return q.split(',').map((v) => Math.ceil(v.replace(/\s/g, '').length / 2));
63
+ if (o.type === 'B') return q.split(',').map((v) => Math.ceil(v.replace(/\s/g, '').length / 8));
64
+ if (o.type === 'P') return q.split(',').map((v) => Math.ceil((v.replace(/[^0-9]/g, '').length + 1) / 2));
65
+ if (o.type === 'Z') return q.split(',').map((v) => v.replace(/[^0-9]/g, '').length);
66
+ return null;
67
+ }
68
+
69
+ // COPY members read in where they are copied, each statement naming the member it came from, up to
70
+ // five members deep. copy(name) gives a member's text, or null when the tree does not hold it.
71
+ function expand(statements, copy, depth = 0, file = null) {
72
+ const out = [];
73
+ for (const st of statements) {
74
+ const name = st.kind === 'statement' && st.operation === 'COPY' && !st.inMacro ? (st.field || '').trim().toUpperCase() : null;
75
+ const member = name && copy && depth < 5 && NAME.test(name) ? copy(name) : null;
76
+ out.push({ ...st, file, expanded: member != null });
77
+ if (member != null) out.push(...expand(readHlasmStatements(member).statements, copy, depth + 1, name));
78
+ }
79
+ return out;
80
+ }
81
+
82
+ export function locate(text, { copy = null } = {}) {
83
+ const statements = expand(readHlasmStatements(text).statements, copy);
84
+ const sections = new Map();
85
+ const order = [];
86
+ const symbols = new Map();
87
+ const placed = [];
88
+ const literals = { pending: [], seen: new Map() };
89
+ const esd = { entries: [], externals: [] };
90
+ let current = null;
91
+ let lost = null;
92
+ const unknown = [];
93
+
94
+ const section = (name, type) => {
95
+ if (!sections.has(name)) {
96
+ sections.set(name, { name, type, lc: 0, high: 0, after: null, line: null });
97
+ order.push(name);
98
+ }
99
+ return sections.get(name);
100
+ };
101
+ const here = () => current || (current = section('', 'CST'));
102
+ const advance = (s, to) => { s.lc = to; s.high = Math.max(s.high, to); };
103
+ const markUnknown = (s, line, why) => { if (s && s.after === null) { s.after = line; unknown.push({ line, why }); } };
104
+
105
+ function value(e, s) {
106
+ if (!e) return null;
107
+ switch (e.t) {
108
+ case 'num': return { abs: e.v };
109
+ case 'sdt': { const v = selfDefining(e); return v === null ? null : { abs: v }; }
110
+ case 'loc': return s && s.after === null ? { sec: s.name, off: s.lc } : null;
111
+ case 'paren': return value(e.e, s);
112
+ case 'unary': { const v = value(e.e, s); return v && v.abs !== undefined ? { abs: e.op === '-' ? -v.abs : v.abs } : null; }
113
+ case 'sym': {
114
+ const y = symbols.get(e.name);
115
+ if (!y || y.value === null) return null;
116
+ return y.value;
117
+ }
118
+ case 'attr': {
119
+ if (e.attr !== 'L') return null;
120
+ if (e.of.t === 'loc') return { abs: 1 };
121
+ const y = e.of.t === 'sym' ? symbols.get(e.of.name) : null;
122
+ return y && y.len !== null ? { abs: y.len } : null;
123
+ }
124
+ case 'bin': {
125
+ const l = value(e.l, s);
126
+ const r = value(e.r, s);
127
+ if (!l || !r) return null;
128
+ if (e.op === '+' || e.op === '-') {
129
+ if (l.abs !== undefined && r.abs !== undefined) return { abs: e.op === '+' ? l.abs + r.abs : l.abs - r.abs };
130
+ if (l.sec !== undefined && r.abs !== undefined) return { sec: l.sec, off: e.op === '+' ? l.off + r.abs : l.off - r.abs };
131
+ if (e.op === '+' && l.abs !== undefined && r.sec !== undefined) return { sec: r.sec, off: r.off + l.abs };
132
+ if (e.op === '-' && l.sec !== undefined && r.sec !== undefined && l.sec === r.sec) return { abs: l.off - r.off };
133
+ return null;
134
+ }
135
+ if (l.abs === undefined || r.abs === undefined) return null;
136
+ if (e.op === '*') return { abs: l.abs * r.abs };
137
+ return r.abs === 0 ? { abs: 0 } : { abs: Math.trunc(l.abs / r.abs) };
138
+ }
139
+ default: return null;
140
+ }
141
+ }
142
+ const absolute = (e, s) => { const v = value(e, s); return v && v.abs !== undefined ? v.abs : null; };
143
+
144
+ // Length, alignment and size of one DC or DS operand, or null when one of them cannot be known.
145
+ function constant(o, s) {
146
+ const dup = o.dup ? absolute(o.dup, s) : 1;
147
+ if (dup === null || o.bitLength) return null;
148
+ let lengths;
149
+ let align = 1;
150
+ if (o.length) {
151
+ const n = absolute(o.length, s);
152
+ if (n === null) return null;
153
+ const count = o.nominal ? (o.nominal.expressions ? o.nominal.expressions.length : (['C', 'G'].includes(o.type) ? 1 : o.nominal.quoted.split(',').length)) : 1;
154
+ lengths = Array(count).fill(n);
155
+ } else {
156
+ lengths = nominalLengths(o);
157
+ if (!lengths) return null;
158
+ align = ALIGN_EXT[typeKey(o)] ?? ALIGN[o.type] ?? 1;
159
+ }
160
+ return { align, length: lengths[0], size: dup * lengths.reduce((a, b) => a + b, 0) };
161
+ }
162
+
163
+ function define(st, s, off, len, type, how) {
164
+ if (!st.name || st.name.startsWith('&') || st.name.startsWith('.')) return;
165
+ const name = st.name.toUpperCase();
166
+ const known = s.after === null && off !== null;
167
+ symbols.set(name, { name, line: st.line, file: st.file, section: s.name, value: known ? { sec: s.name, off } : null, len, type, how, after: known ? null : s.after });
168
+ }
169
+
170
+ // An EQU with no length operand takes the length of its value's leftmost term: 1 for *, a
171
+ // self-defining term, L' or a symbol naming an assembler instruction other than DC, DS or CCW.
172
+ // https://www.ibm.com/docs/en/hla-and-tf/1.6.0?topic=instructions-equ-instruction
173
+ function leftmostLength(e) {
174
+ while (e && (e.t === 'bin' || e.t === 'paren' || e.t === 'unary')) e = e.t === 'bin' ? e.l : e.e;
175
+ if (!e) return null;
176
+ if (e.t !== 'sym') return 1;
177
+ const y = symbols.get(e.name);
178
+ return y ? y.len : null;
179
+ }
180
+
181
+ // Each literal a statement names, and whether an instruction names it by relative address (an RI
182
+ // operand), which aligns an odd-length one to a halfword in the pool.
183
+ function literalsIn(node, relative = false, out = []) {
184
+ if (!node || typeof node !== 'object') return out;
185
+ if (node.t === 'lit') out.push({ dc: node.dc, relative });
186
+ if (node.kind === 'INSTRUCTION') { for (const op of node.operands) literalsIn(op, /^RI/.test(op.operand), out); return out; }
187
+ for (const v of Object.values(node)) if (v && typeof v === 'object') literalsIn(v, relative, out);
188
+ return out;
189
+ }
190
+
191
+ function pool(s, line) {
192
+ if (!literals.pending.length) return;
193
+ if (s.after !== null) { literals.pending = []; return; }
194
+ // Five segments in the order literals were written: lengths that are multiples of 16, of 8, of 4,
195
+ // even, then odd. https://www.ibm.com/docs/en/hla-and-tf/1.6.0?topic=instructions-ltorg-instruction
196
+ const segment = (x) => { const i = [16, 8, 4, 2].findIndex((n) => x.size % n === 0); return i < 0 ? 4 : i; };
197
+ const sized = literals.pending.map((lit) => ({ ...lit, ...constant(lit.dc, s) }));
198
+ if (sized.some((x) => x.size === undefined || x.size === null)) { literals.pending = []; markUnknown(s, line, 'literal of unknown length'); return; }
199
+ let at = alignUp(s.lc, 8);
200
+ for (const g of [0, 1, 2, 3, 4]) {
201
+ for (const x of sized.filter((y) => segment(y) === g)) { if (g === 4 && x.relative) at = alignUp(at, 2); at += x.size; }
202
+ }
203
+ advance(s, at);
204
+ literals.pending = [];
205
+ literals.seen = new Map();
206
+ }
207
+
208
+ let first = null;
209
+ for (const st of statements) {
210
+ if (st.inMacro) continue;
211
+ let r = parseHlasmStatement(st);
212
+ // An open-code statement with variable symbols is placed as what it reads as, when its lengths
213
+ // do not depend on a variable's value; its label is a variable and defines nothing here.
214
+ if (r.kind === 'SUBSTITUTED' && r.status === 'parsed' && r.node.exact) r = { kind: r.node.as, status: 'parsed', node: r.node.node };
215
+ const kind = r.kind;
216
+ if (kind === 'UNREADABLE' || kind === 'UNKNOWN') { markUnknown(here(), st.line, `${kind}`); continue; }
217
+ if (kind === 'CONDITIONAL') {
218
+ if (!QUIET_CONDITIONAL.has(st.operation)) { lost = lost ?? st.line; for (const s of sections.values()) markUnknown(s, st.line, st.operation); markUnknown(here(), st.line, st.operation); }
219
+ continue;
220
+ }
221
+ if (SECTION[kind]) {
222
+ const name = (st.name || '').toUpperCase();
223
+ current = section(name, SECTION[kind]);
224
+ if (kind !== 'DSECT' && first === null) first = current;
225
+ if (current.line === null) current.line = st.line;
226
+ if (r.status === 'parsed' && kind === 'START' && r.node.origin) {
227
+ const o = absolute(r.node.origin, current);
228
+ if (o === null) markUnknown(current, st.line, 'START origin'); else advance(current, o);
229
+ }
230
+ if (lost !== null) markUnknown(current, lost, 'conditional assembly');
231
+ placed.push({ line: st.line, file: st.file, section: current.name, loc: current.after === null ? current.lc : null });
232
+ if (!symbols.has(name) && name) symbols.set(name, { name, line: st.line, section: name, value: { sec: name, off: 0 }, len: 1, type: SECTION[kind], how: 'SECTION', after: null });
233
+ continue;
234
+ }
235
+ const s = here();
236
+ const at = () => (s.after === null ? s.lc : null);
237
+ // Statements that take no storage leave the location counter where it is, read or not; a COPY
238
+ // whose member was read in is one, its statements following it.
239
+ if (NO_STORAGE.has(kind) || (kind === 'COPY' && st.expanded) || kind === 'ENTRY' || kind === 'EXTRN' || kind === 'WXTRN' || kind === 'END') {
240
+ placed.push({ line: st.line, file: st.file, section: s.name, loc: at() });
241
+ if (r.status === 'parsed' && kind === 'ENTRY') for (const n of r.node.names || []) esd.entries.push({ name: n, line: st.line });
242
+ if (r.status === 'parsed' && (kind === 'EXTRN' || kind === 'WXTRN')) for (const n of r.node.names || []) esd.externals.push({ name: n, how: kind, line: st.line });
243
+ if (kind === 'END') break;
244
+ continue;
245
+ }
246
+ if (kind === 'LTORG') { placed.push({ line: st.line, file: st.file, section: s.name, loc: at() }); define(st, s, s.lc, 1, 'REL', 'LTORG'); pool(s, st.line); continue; }
247
+ // CALL's macro names its entry in a V-type constant, so the entry is an external the module needs.
248
+ if (kind === 'CALL' && r.status === 'parsed' && r.node.entry) esd.externals.push({ name: r.node.entry.toUpperCase(), how: 'CALL', line: st.line });
249
+ if (r.status !== 'parsed') { placed.push({ line: st.line, file: st.file, section: s.name, loc: null }); markUnknown(s, st.line, `${kind} ${r.status}`); continue; }
250
+ const node = r.node;
251
+ for (const { dc, relative } of literalsIn(node)) {
252
+ const key = JSON.stringify(dc);
253
+ if (!literals.seen.has(key)) { literals.seen.set(key, { dc, relative }); literals.pending.push(literals.seen.get(key)); }
254
+ else if (relative) literals.seen.get(key).relative = true;
255
+ if (dc.type === 'V' && dc.nominal && dc.nominal.expressions) for (const e of dc.nominal.expressions) if (e.t === 'sym') esd.externals.push({ name: e.name, how: 'V', line: st.line });
256
+ }
257
+ if (node.kind === 'INSTRUCTION') {
258
+ const at = alignUp(s.lc, 2);
259
+ placed.push({ line: st.line, file: st.file, section: s.name, loc: s.after === null ? at : null });
260
+ define(st, s, at, node.length, 'REL', 'INSTRUCTION');
261
+ advance(s, at + node.length);
262
+ continue;
263
+ }
264
+ if (kind === 'DC' || kind === 'DS') {
265
+ let at = s.lc;
266
+ let firstAt = null;
267
+ let firstLen = null;
268
+ let known = s.after === null;
269
+ for (const o of node.operands) {
270
+ if (kind === 'DC' && o.type === 'V' && o.nominal && o.nominal.expressions) for (const e of o.nominal.expressions) if (e.t === 'sym') esd.externals.push({ name: e.name, how: 'V', line: st.line });
271
+ const c = known ? constant(o, s) : null;
272
+ if (!c) { known = false; continue; }
273
+ at = alignUp(at, c.align);
274
+ if (firstAt === null) { firstAt = at; firstLen = c.length; }
275
+ at += c.size;
276
+ }
277
+ if (!known) { placed.push({ line: st.line, file: st.file, section: s.name, loc: null }); markUnknown(s, st.line, `${kind} of unknown length`); define(st, s, null, null, 'REL', kind); continue; }
278
+ placed.push({ line: st.line, file: st.file, section: s.name, loc: firstAt });
279
+ define(st, s, firstAt, firstLen, 'REL', kind);
280
+ advance(s, at);
281
+ continue;
282
+ }
283
+ if (kind === 'EQU') {
284
+ const v = value(node.value, s);
285
+ const len = node.length ? absolute(node.length, s) : leftmostLength(node.value);
286
+ const name = (st.name || '').toUpperCase();
287
+ if (name) symbols.set(name, { name, line: st.line, section: v && v.sec !== undefined ? v.sec : null, value: v, len, type: v && v.abs !== undefined ? 'ABS' : 'REL', how: 'EQU', after: v ? null : s.after });
288
+ placed.push({ line: st.line, file: st.file, section: s.name, loc: s.after === null ? s.lc : null });
289
+ continue;
290
+ }
291
+ if (kind === 'ORG') {
292
+ placed.push({ line: st.line, file: st.file, section: s.name, loc: s.after === null ? s.lc : null });
293
+ if (s.after !== null) continue;
294
+ if (!node.value) { advance(s, s.high); s.lc = s.high; continue; }
295
+ const v = value(node.value, s);
296
+ if (!v || v.sec !== s.name) { markUnknown(s, st.line, 'ORG to an unknown location'); continue; }
297
+ let to = v.off;
298
+ if (node.boundary) to = alignUp(to, node.boundary) + (node.offset ? absolute(node.offset, s) ?? 0 : 0);
299
+ s.lc = to;
300
+ s.high = Math.max(s.high, to);
301
+ continue;
302
+ }
303
+ if (kind === 'CNOP') {
304
+ const b = absolute(node.byte, s);
305
+ const w = absolute(node.boundary, s);
306
+ // CNOP pads with two-byte NOPs, so the statement starts at the next halfword.
307
+ placed.push({ line: st.line, file: st.file, section: s.name, loc: s.after === null ? alignUp(s.lc, 2) : null });
308
+ if (b === null || !w) { markUnknown(s, st.line, 'CNOP'); continue; }
309
+ let at = alignUp(s.lc, 2);
310
+ while (at % w !== b) at += 2;
311
+ advance(s, at);
312
+ continue;
313
+ }
314
+ if (kind === 'CCW' || kind === 'CCW0' || kind === 'CCW1') {
315
+ const at = alignUp(s.lc, 8);
316
+ placed.push({ line: st.line, file: st.file, section: s.name, loc: s.after === null ? at : null });
317
+ define(st, s, at, 8, 'REL', 'CCW');
318
+ advance(s, at + 8);
319
+ continue;
320
+ }
321
+ placed.push({ line: st.line, file: st.file, section: s.name, loc: null });
322
+ markUnknown(s, st.line, kind);
323
+ }
324
+ if (literals.pending.length) pool(first || here(), null);
325
+
326
+ return {
327
+ sections: order.map((n) => { const s = sections.get(n); return { name: n, type: s.type, line: s.line, length: s.after === null ? s.high : null, after: s.after }; }),
328
+ symbols: [...symbols.values()].map((y) => ({ name: y.name, line: y.line, file: y.file ?? null, section: y.section, loc: y.value && y.value.sec !== undefined ? y.value.off : y.value ? y.value.abs : null, len: y.len, type: y.type, how: y.how, after: y.after })),
329
+ statements: placed,
330
+ esd,
331
+ unknown,
332
+ };
333
+ }
@@ -0,0 +1,146 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // Parsers for the z/OS authorization macros MODESET, TESTAUTH, and RACROUTE.
3
+ import { HlasmSyntax, NAME, remoteForm } from '../operands.mjs';
4
+ import { MVS38_EXTKEYS, mvs38Form } from '../mvs38.mjs';
5
+
6
+ const MODESET_EXTKEYS = new Set(['ZERO', 'TCB', 'RBT1', 'RBT234', 'KEY2', 'KEY3', 'KEY4', 'KEY7']);
7
+ const MODESET_KEYS = new Set(['ZERO', 'NZERO']);
8
+ const MODESET_MODES = new Set(['PROB', 'SUP']);
9
+ const MODESET_KNOWN = new Set(['EXTKEY', 'KEYADDR', 'KEYREG', 'SAVEKEY', 'WORKREG', 'RELATED', 'KEY', 'MODE', 'MF']);
10
+ // The list and execute forms of the MODESET that generates an SVC (z/OS 3.1 MVS Programming:
11
+ // Authorized Assembler Services Reference, Volume 3): the list form holds KEY and MODE, the execute
12
+ // form names the list.
13
+ const MODESET_LIST_KNOWN = new Set(['KEY', 'MODE', 'RELATED', 'MF']);
14
+ const MODESET_EXECUTE_KNOWN = new Set(['RELATED', 'MF']);
15
+
16
+ const TESTAUTH_KNOWN = new Set(['FCTN', 'STATE', 'KEY', 'RBLEVEL', 'BRANCH']);
17
+ const TESTAUTH_STATES = new Set(['YES', 'NO']);
18
+
19
+ const RACROUTE_REQUESTS = new Set(['AUTH', 'FASTAUTH', 'DEFINE', 'DIRAUTH', 'EXTRACT', 'LIST', 'SIGNON', 'STAT', 'TOKENBLD', 'TOKENMAP', 'TOKENXTR', 'VERIFY', 'VERIFYX', 'AUDIT']);
20
+
21
+ function parseModeset(st, ops, opts) {
22
+ const { keywords } = ops;
23
+ const node = { kind: 'MODESET', key: null, mode: null, extkey: null, keywords: {} };
24
+
25
+ for (const [k, v] of keywords) {
26
+ if (!MODESET_KNOWN.has(k)) {
27
+ throw new HlasmSyntax(`MODESET: unknown keyword ${k}`);
28
+ }
29
+ node.keywords[k] = v;
30
+ }
31
+
32
+ if (keywords.has('MF')) {
33
+ const { form, list } = remoteForm(keywords.get('MF'), 'MF');
34
+ const known = form === 'L' ? MODESET_LIST_KNOWN : MODESET_EXECUTE_KNOWN;
35
+ for (const k of keywords.keys()) {
36
+ if (!known.has(k)) throw new HlasmSyntax(`MODESET: the ${form === 'L' ? 'list' : 'execute'} form does not take ${k}`);
37
+ }
38
+ if (form === 'L' && !keywords.has('KEY') && !keywords.has('MODE')) throw new HlasmSyntax('MODESET: the list form needs KEY or MODE');
39
+ node.mf = form;
40
+ if (list) node.list = list;
41
+ }
42
+
43
+ if (keywords.has('EXTKEY')) {
44
+ const v = keywords.get('EXTKEY').trim().toUpperCase();
45
+ if (MVS38_EXTKEYS.has(v)) {
46
+ mvs38Form(opts, node, 'MODESET', `EXTKEY=${v}`);
47
+ node.pswKey = MVS38_EXTKEYS.get(v);
48
+ } else if (!MODESET_EXTKEYS.has(v)) {
49
+ throw new HlasmSyntax(`MODESET: invalid EXTKEY value ${v}`);
50
+ }
51
+ node.extkey = v;
52
+ }
53
+
54
+ if (keywords.has('KEY')) {
55
+ const v = keywords.get('KEY').trim().toUpperCase();
56
+ if (!MODESET_KEYS.has(v)) {
57
+ throw new HlasmSyntax(`MODESET: invalid KEY value ${v}`);
58
+ }
59
+ node.key = v;
60
+ }
61
+
62
+ if (keywords.has('MODE')) {
63
+ const v = keywords.get('MODE').trim().toUpperCase();
64
+ if (!MODESET_MODES.has(v)) {
65
+ throw new HlasmSyntax(`MODESET: invalid MODE value ${v}`);
66
+ }
67
+ node.mode = v;
68
+ }
69
+
70
+ return node;
71
+ }
72
+
73
+ function parseTestauth(st, ops) {
74
+ const { keywords } = ops;
75
+ const node = { kind: 'TESTAUTH', keywords: {} };
76
+
77
+ for (const [k, v] of keywords) {
78
+ if (!TESTAUTH_KNOWN.has(k)) {
79
+ throw new HlasmSyntax(`TESTAUTH: unknown keyword ${k}`);
80
+ }
81
+ node.keywords[k] = v;
82
+ }
83
+
84
+ if (keywords.has('STATE')) {
85
+ const v = keywords.get('STATE').trim().toUpperCase();
86
+ if (!TESTAUTH_STATES.has(v)) {
87
+ throw new HlasmSyntax(`TESTAUTH: invalid STATE value ${v}`);
88
+ }
89
+ }
90
+
91
+ if (keywords.has('KEY')) {
92
+ const v = keywords.get('KEY').trim().toUpperCase();
93
+ if (!TESTAUTH_STATES.has(v)) {
94
+ throw new HlasmSyntax(`TESTAUTH: invalid KEY value ${v}`);
95
+ }
96
+ }
97
+
98
+ if (keywords.has('RBLEVEL')) {
99
+ const v = keywords.get('RBLEVEL').trim();
100
+ if (v !== '1' && v !== '2') {
101
+ throw new HlasmSyntax(`TESTAUTH: invalid RBLEVEL value ${v}`);
102
+ }
103
+ }
104
+
105
+ if (keywords.has('BRANCH')) {
106
+ const v = keywords.get('BRANCH').trim().toUpperCase();
107
+ if (!TESTAUTH_STATES.has(v)) {
108
+ throw new HlasmSyntax(`TESTAUTH: invalid BRANCH value ${v}`);
109
+ }
110
+ }
111
+
112
+ return node;
113
+ }
114
+
115
+ function parseRacroute(st, ops) {
116
+ const { keywords } = ops;
117
+ const node = { kind: 'RACROUTE', request: null, class: null, entity: null, attr: null, keywords: {} };
118
+
119
+ const req = keywords.get('REQUEST');
120
+ if (!req) {
121
+ throw new HlasmSyntax('RACROUTE: REQUEST is required');
122
+ }
123
+ const reqVal = req.trim().toUpperCase();
124
+ if (!RACROUTE_REQUESTS.has(reqVal)) {
125
+ throw new HlasmSyntax(`RACROUTE: invalid REQUEST value ${reqVal}`);
126
+ }
127
+ node.request = reqVal;
128
+
129
+ for (const [k, v] of keywords) {
130
+ if (!NAME.test(k)) {
131
+ throw new HlasmSyntax(`RACROUTE: invalid keyword name ${k}`);
132
+ }
133
+ node.keywords[k] = v;
134
+ if (k === 'CLASS') node.class = v;
135
+ if (k === 'ENTITY') node.entity = v;
136
+ if (k === 'ATTR') node.attr = v;
137
+ }
138
+
139
+ return node;
140
+ }
141
+
142
+ export const parsers = {
143
+ MODESET: parseModeset,
144
+ TESTAUTH: parseTestauth,
145
+ RACROUTE: parseRacroute
146
+ };
@@ -0,0 +1,107 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // Parser for z/OS data-set macros: OPEN, CLOSE, and DCB.
3
+ import { HlasmSyntax, NAME, remoteForm, splitOperands } from '../operands.mjs';
4
+ import { parseAddress } from '../expr.mjs';
5
+ import { MVS38_KEYWORDS, mvs38Form } from '../mvs38.mjs';
6
+
7
+ const OPEN_OPTS = new Set(['INPUT', 'OUTPUT', 'UPDAT', 'EXTEND', 'OUTIN', 'OUTINX', 'INOUT', 'RDBACK', 'DISP', 'LEAVE', 'REREAD']);
8
+ const CLOSE_OPTS = new Set(['REREAD', 'LEAVE', 'REWIND', 'FREE', 'DISP']);
9
+ const DCB_KEYWORDS = new Set(['BFALN', 'BFTEK', 'BLKSIZE', 'BUFCB', 'BUFL', 'BUFNO', 'BUFOFF', 'DCBE', 'DDNAME', 'DEVD', 'DSORG', 'EODAD', 'EROPT', 'EXLST', 'LRECL', 'MACRF', 'NCP', 'OPTCD', 'RECFM', 'SYNAD', 'KEYLEN', 'LIMCT', 'RKP', 'DEN', 'TRTCH', 'PRTSP', 'STACK', 'FUNC', 'MODE', 'CODE',
10
+ // EXCP (z/OS 3.1 DFSMSdfp Advanced Services, Data Control Block (DCB) Fields).
11
+ 'REPOS', 'EOEA', 'PCIA', 'SIOA', 'CENDA', 'XENDA', 'IMSK', 'PGFIX', 'IOBAD']);
12
+
13
+ // (dcb,(options),dcb,(options),...): each DCB address followed by its options, written in parentheses,
14
+ // as one bare word or omitted. A single DCB may be written without the outer parentheses. The list
15
+ // form (MF=L) and the execute form (MF=(E,addr)) may leave a DCB address empty, to be filled in by
16
+ // the other (z/OS 3.1 DFSMS Macro Instructions for Data Sets, OPEN and CLOSE list and execute forms).
17
+ function parseDcbList(raw, validOpts, emptyAllowed = false) {
18
+ const text = raw.trim();
19
+ const items = text.startsWith('(') && text.endsWith(')') ? splitOperands(text.slice(1, -1)) : [text];
20
+ const dcbs = [];
21
+ for (let i = 0; i < items.length; i++) {
22
+ const dcbRaw = items[i].trim();
23
+ if (!dcbRaw && !emptyAllowed) throw new HlasmSyntax('empty dcb address in list');
24
+ let dcb = null;
25
+ if (dcbRaw) try { dcb = parseAddress(dcbRaw); } catch { throw new HlasmSyntax(`invalid dcb address: ${dcbRaw}`); }
26
+ const next = (items[i + 1] ?? '').trim();
27
+ const word = next.toUpperCase();
28
+ let options = [];
29
+ if (next.startsWith('(') && next.endsWith(')')) options = splitOperands(next.slice(1, -1)).map((o) => o.trim().toUpperCase()).filter(Boolean);
30
+ else if (validOpts.has(word)) options = [word];
31
+ else if (next !== '' || i + 1 >= items.length) { dcbs.push({ dcb, options }); continue; }
32
+ for (const o of options) if (!validOpts.has(o)) throw new HlasmSyntax(`unknown option: ${o}`);
33
+ dcbs.push({ dcb, options });
34
+ i++;
35
+ }
36
+ return dcbs;
37
+ }
38
+
39
+ // The DCB list of OPEN or CLOSE and the form MF= gives it; the execute form may omit the list.
40
+ function dcbOperand(macro, ops, validOpts) {
41
+ const mf = ops.keywords.has('MF') ? remoteForm(ops.keywords.get('MF'), 'MF') : null;
42
+ if (ops.positional.length === 0 && mf?.form === 'E') return { dcbs: [], mf };
43
+ if (ops.positional.length !== 1) throw new HlasmSyntax(`${macro} requires exactly one positional operand`);
44
+ return { dcbs: parseDcbList(ops.positional[0], validOpts, Boolean(mf)), mf };
45
+ }
46
+
47
+ export const parsers = {
48
+ OPEN(st, ops) {
49
+ const { dcbs, mf } = dcbOperand('OPEN', ops, OPEN_OPTS);
50
+ const keywords = {};
51
+ for (const [k, v] of ops.keywords) {
52
+ if (k === 'TYPE') {
53
+ if (v.toUpperCase() !== 'J') throw new HlasmSyntax('OPEN TYPE must be J');
54
+ keywords.TYPE = v;
55
+ } else if (k === 'MODE') {
56
+ if (v !== '24' && v !== '31') throw new HlasmSyntax('OPEN MODE must be 24 or 31');
57
+ keywords.MODE = v;
58
+ } else if (k === 'MF') {
59
+ keywords.MF = v;
60
+ } else {
61
+ throw new HlasmSyntax(`unknown keyword for OPEN: ${k}`);
62
+ }
63
+ }
64
+ return { kind: 'OPEN', dcbs, ...(mf ? { mf: mf.form === 'L' ? 'L' : { form: 'E', list: mf.list } } : {}), keywords };
65
+ },
66
+ CLOSE(st, ops) {
67
+ const { dcbs, mf } = dcbOperand('CLOSE', ops, CLOSE_OPTS);
68
+ const keywords = {};
69
+ for (const [k, v] of ops.keywords) {
70
+ if (k === 'TYPE') {
71
+ if (v.toUpperCase() !== 'T') throw new HlasmSyntax('CLOSE TYPE must be T');
72
+ keywords.TYPE = v;
73
+ } else if (k === 'MODE') {
74
+ if (v !== '24' && v !== '31') throw new HlasmSyntax('CLOSE MODE must be 24 or 31');
75
+ keywords.MODE = v;
76
+ } else if (k === 'MF') {
77
+ keywords.MF = v;
78
+ } else {
79
+ throw new HlasmSyntax(`unknown keyword for CLOSE: ${k}`);
80
+ }
81
+ }
82
+ return { kind: 'CLOSE', dcbs, ...(mf ? { mf: mf.form === 'L' ? 'L' : { form: 'E', list: mf.list } } : {}), keywords };
83
+ },
84
+ DCB(st, ops, opts) {
85
+ if (ops.positional.some((p) => p.trim() !== '')) throw new HlasmSyntax('DCB takes no positional operands');
86
+ const keywords = {};
87
+ let ddname = null;
88
+ let dsorg = null;
89
+ let macrf = null;
90
+ let recfm = null;
91
+ let lrecl = null;
92
+ let blksize = null;
93
+ const legacy = {};
94
+ for (const [k, v] of ops.keywords) {
95
+ if (MVS38_KEYWORDS.DCB.has(k)) mvs38Form(opts, legacy, 'DCB', `${k}=${String(v).trim()}`);
96
+ else if (!DCB_KEYWORDS.has(k)) throw new HlasmSyntax(`unknown keyword for DCB: ${k}`);
97
+ keywords[k] = v;
98
+ if (k === 'DDNAME') ddname = v.toUpperCase();
99
+ else if (k === 'DSORG') dsorg = v;
100
+ else if (k === 'MACRF') macrf = v;
101
+ else if (k === 'RECFM') recfm = v;
102
+ else if (k === 'LRECL') lrecl = v;
103
+ else if (k === 'BLKSIZE') blksize = v;
104
+ }
105
+ return { kind: 'DCB', ...legacy, name: st.name, ddname, dsorg, macrf, recfm, lrecl, blksize, keywords };
106
+ }
107
+ };