@portll/cobolwork 0.4.0 → 0.6.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.
- package/README.md +53 -5
- package/bin/cobolwork.mjs +34 -9
- package/lib/baseline.mjs +9 -0
- package/lib/bms.mjs +8 -0
- package/lib/build.mjs +33 -8
- package/lib/capabilities.mjs +6 -3
- package/lib/consequence.mjs +17 -0
- package/lib/constants.mjs +60 -0
- package/lib/control-reuse.mjs +113 -0
- package/lib/control.mjs +86 -18
- package/lib/csd.mjs +6 -1
- package/lib/dataflow.mjs +343 -95
- package/lib/equivalence.mjs +44 -5
- package/lib/evidence/cli.mjs +13 -5
- package/lib/evidence/record.mjs +16 -6
- package/lib/evidence/seal.mjs +17 -0
- package/lib/evidence/store.mjs +30 -14
- package/lib/evidence/timestamp.mjs +83 -0
- package/lib/evidence/verify.mjs +120 -42
- package/lib/hlasm/asm/data.mjs +116 -0
- package/lib/hlasm/asm/listing.mjs +43 -0
- package/lib/hlasm/asm/output.mjs +45 -0
- package/lib/hlasm/asm/sections.mjs +159 -0
- package/lib/hlasm/asm/symbols.mjs +78 -0
- package/lib/hlasm/exec.mjs +27 -0
- package/lib/hlasm/expr.mjs +167 -0
- package/lib/hlasm/instr.mjs +48 -0
- package/lib/hlasm/locate.mjs +333 -0
- package/lib/hlasm/macro/authorization.mjs +126 -0
- package/lib/hlasm/macro/datasets.mjs +94 -0
- package/lib/hlasm/macro/io.mjs +139 -0
- package/lib/hlasm/macro/le.mjs +53 -0
- package/lib/hlasm/macro/linkage.mjs +143 -0
- package/lib/hlasm/macro/operator.mjs +66 -0
- package/lib/hlasm/macro/program.mjs +142 -0
- package/lib/hlasm/macro/recovery.mjs +80 -0
- package/lib/hlasm/macro/storage.mjs +117 -0
- package/lib/hlasm/macro/structured.mjs +131 -0
- package/lib/hlasm/model.mjs +72 -0
- package/lib/hlasm/operands.mjs +49 -0
- package/lib/hlasm/read.mjs +116 -0
- package/lib/hlasm.mjs +79 -0
- package/lib/inventory.mjs +10 -7
- package/lib/ironwork-ids.mjs +24 -0
- package/lib/ironwork.mjs +4 -1
- package/lib/kernel/registry.mjs +24 -17
- package/lib/kernel/shared-pass.mjs +138 -0
- package/lib/options.mjs +26 -4
- package/lib/policy.mjs +6 -0
- package/lib/precompile-cics.mjs +1 -1
- package/lib/revision.json +1 -1
- package/lib/sarif.mjs +3 -0
- package/lib/scan.mjs +21 -11
- package/lib/sets/abend.mjs +115 -13
- package/lib/sets/build.mjs +43 -37
- package/lib/sets/cics.mjs +5 -3
- package/lib/sets/compile.mjs +5 -3
- package/lib/sets/copybook.mjs +5 -3
- package/lib/sets/crypto.mjs +110 -0
- package/lib/sets/flow.mjs +32 -7
- package/lib/sets/hlasm.mjs +177 -0
- package/lib/sets/jcl.mjs +15 -1
- package/lib/sets/log.mjs +5 -3
- package/lib/sets/priv.mjs +5 -3
- package/lib/sets/secrets.mjs +110 -0
- package/lib/sets/semantics.mjs +5 -3
- package/lib/sets/web.mjs +104 -7
- package/lib/sources.mjs +12 -2
- package/package.json +1 -1
- package/rules/compliance-cobit2019.json +216 -0
- package/rules/compliance-dora.json +234 -0
- package/rules/compliance-ffiec.json +234 -0
- package/rules/compliance-nist80053.json +234 -0
- package/rules/gitleaks-mainframe.toml +61 -16
- package/rules/hlasm-instructions.json +2396 -0
- package/rules/hlasm-operations.json +451 -0
- package/rules/icsf-services.json +1178 -0
- package/schema/cobolwork.policy.schema.json +7 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// Machine instructions, matched against the operand syntax rules/hlasm-instructions.json gives each
|
|
3
|
+
// mnemonic: the operand count, and each operand an expression or an address of the written shape.
|
|
4
|
+
// One matcher for every format, since IBM's operand syntax already says what each operand is.
|
|
5
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
6
|
+
import { HlasmSyntax, splitOperands } from './operands.mjs';
|
|
7
|
+
import { parseExpression, parseAddress } from './expr.mjs';
|
|
8
|
+
|
|
9
|
+
const TABLE = new URL('../../rules/hlasm-instructions.json', import.meta.url);
|
|
10
|
+
const rows = existsSync(TABLE) ? JSON.parse(readFileSync(TABLE, 'utf8')).instructions : [];
|
|
11
|
+
export const INSTRUCTIONS = new Map(rows.map((r) => [r.mnemonic, r]));
|
|
12
|
+
|
|
13
|
+
// "R1,D2(X2,B2)[,M3]" as operand shapes: an address names how many parts its parentheses may hold.
|
|
14
|
+
const shapes = new Map();
|
|
15
|
+
function shapeOf(pattern) {
|
|
16
|
+
if (shapes.has(pattern)) return shapes.get(pattern);
|
|
17
|
+
const out = [];
|
|
18
|
+
let optional = false;
|
|
19
|
+
for (let part of splitOperands(pattern.replace(/\[,?/g, ',[').replace(/\]/g, '').replace(/^,/, ''))) {
|
|
20
|
+
if (part.startsWith('[')) { optional = true; part = part.slice(1); }
|
|
21
|
+
const address = /^(D\d*)\(([^)]*)\)$/.exec(part);
|
|
22
|
+
out.push({ name: part, optional, parts: address ? address[2].split(',').length : 0 });
|
|
23
|
+
}
|
|
24
|
+
shapes.set(pattern, out);
|
|
25
|
+
return out;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const formatFamily = (format) => String(format || '').replace(/-[a-z]$/, '');
|
|
29
|
+
|
|
30
|
+
export function parseInstruction(st, row) {
|
|
31
|
+
const shape = shapeOf(row.operands || '');
|
|
32
|
+
// An instruction with no operands takes whatever follows it as remarks: SAM24 Back to AMODE 24.
|
|
33
|
+
if (!shape.length) return { kind: 'INSTRUCTION', mnemonic: st.operation, format: row.format, length: row.length, operands: [] };
|
|
34
|
+
const given = splitOperands(st.field || '');
|
|
35
|
+
const required = shape.filter((s) => !s.optional).length;
|
|
36
|
+
if (given.length < required || given.length > shape.length) {
|
|
37
|
+
throw new HlasmSyntax(`${st.operation} takes ${required === shape.length ? required : `${required} to ${shape.length}`} operands (${row.operands}), not ${given.length}`);
|
|
38
|
+
}
|
|
39
|
+
const operands = given.map((text, i) => {
|
|
40
|
+
const s = shape[i];
|
|
41
|
+
if (!text) throw new HlasmSyntax(`operand ${i + 1} of ${st.operation} (${s.name}) is empty`);
|
|
42
|
+
if (!s.parts) return { operand: s.name, e: parseExpression(text) };
|
|
43
|
+
const a = parseAddress(text);
|
|
44
|
+
if (a.parts && a.parts.length > s.parts) throw new HlasmSyntax(`operand ${i + 1} of ${st.operation} (${s.name}) has ${a.parts.length} parts in parentheses, not at most ${s.parts}`);
|
|
45
|
+
return { operand: s.name, ...a };
|
|
46
|
+
});
|
|
47
|
+
return { kind: 'INSTRUCTION', mnemonic: st.operation, format: row.format, length: row.length, operands };
|
|
48
|
+
}
|
|
@@ -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,126 @@
|
|
|
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 } from '../operands.mjs';
|
|
4
|
+
|
|
5
|
+
const MODESET_EXTKEYS = new Set(['ZERO', 'TCB', 'RBT1', 'RBT234', 'KEY2', 'KEY3', 'KEY4', 'KEY7']);
|
|
6
|
+
const MODESET_KEYS = new Set(['ZERO', 'NZERO']);
|
|
7
|
+
const MODESET_MODES = new Set(['PROB', 'SUP']);
|
|
8
|
+
const MODESET_KNOWN = new Set(['EXTKEY', 'KEYADDR', 'KEYREG', 'SAVEKEY', 'WORKREG', 'RELATED', 'KEY', 'MODE']);
|
|
9
|
+
|
|
10
|
+
const TESTAUTH_KNOWN = new Set(['FCTN', 'STATE', 'KEY', 'RBLEVEL', 'BRANCH']);
|
|
11
|
+
const TESTAUTH_STATES = new Set(['YES', 'NO']);
|
|
12
|
+
|
|
13
|
+
const RACROUTE_REQUESTS = new Set(['AUTH', 'FASTAUTH', 'DEFINE', 'DIRAUTH', 'EXTRACT', 'LIST', 'SIGNON', 'STAT', 'TOKENBLD', 'TOKENMAP', 'TOKENXTR', 'VERIFY', 'VERIFYX', 'AUDIT']);
|
|
14
|
+
|
|
15
|
+
function parseModeset(st, ops) {
|
|
16
|
+
const { keywords } = ops;
|
|
17
|
+
const node = { kind: 'MODESET', key: null, mode: null, extkey: null, keywords: {} };
|
|
18
|
+
|
|
19
|
+
for (const [k, v] of keywords) {
|
|
20
|
+
if (!MODESET_KNOWN.has(k)) {
|
|
21
|
+
throw new HlasmSyntax(`MODESET: unknown keyword ${k}`);
|
|
22
|
+
}
|
|
23
|
+
node.keywords[k] = v;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (keywords.has('EXTKEY')) {
|
|
27
|
+
const v = keywords.get('EXTKEY').trim().toUpperCase();
|
|
28
|
+
if (!MODESET_EXTKEYS.has(v)) {
|
|
29
|
+
throw new HlasmSyntax(`MODESET: invalid EXTKEY value ${v}`);
|
|
30
|
+
}
|
|
31
|
+
node.extkey = v;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (keywords.has('KEY')) {
|
|
35
|
+
const v = keywords.get('KEY').trim().toUpperCase();
|
|
36
|
+
if (!MODESET_KEYS.has(v)) {
|
|
37
|
+
throw new HlasmSyntax(`MODESET: invalid KEY value ${v}`);
|
|
38
|
+
}
|
|
39
|
+
node.key = v;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (keywords.has('MODE')) {
|
|
43
|
+
const v = keywords.get('MODE').trim().toUpperCase();
|
|
44
|
+
if (!MODESET_MODES.has(v)) {
|
|
45
|
+
throw new HlasmSyntax(`MODESET: invalid MODE value ${v}`);
|
|
46
|
+
}
|
|
47
|
+
node.mode = v;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return node;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function parseTestauth(st, ops) {
|
|
54
|
+
const { keywords } = ops;
|
|
55
|
+
const node = { kind: 'TESTAUTH', keywords: {} };
|
|
56
|
+
|
|
57
|
+
for (const [k, v] of keywords) {
|
|
58
|
+
if (!TESTAUTH_KNOWN.has(k)) {
|
|
59
|
+
throw new HlasmSyntax(`TESTAUTH: unknown keyword ${k}`);
|
|
60
|
+
}
|
|
61
|
+
node.keywords[k] = v;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (keywords.has('STATE')) {
|
|
65
|
+
const v = keywords.get('STATE').trim().toUpperCase();
|
|
66
|
+
if (!TESTAUTH_STATES.has(v)) {
|
|
67
|
+
throw new HlasmSyntax(`TESTAUTH: invalid STATE value ${v}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (keywords.has('KEY')) {
|
|
72
|
+
const v = keywords.get('KEY').trim().toUpperCase();
|
|
73
|
+
if (!TESTAUTH_STATES.has(v)) {
|
|
74
|
+
throw new HlasmSyntax(`TESTAUTH: invalid KEY value ${v}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (keywords.has('RBLEVEL')) {
|
|
79
|
+
const v = keywords.get('RBLEVEL').trim();
|
|
80
|
+
if (v !== '1' && v !== '2') {
|
|
81
|
+
throw new HlasmSyntax(`TESTAUTH: invalid RBLEVEL value ${v}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (keywords.has('BRANCH')) {
|
|
86
|
+
const v = keywords.get('BRANCH').trim().toUpperCase();
|
|
87
|
+
if (!TESTAUTH_STATES.has(v)) {
|
|
88
|
+
throw new HlasmSyntax(`TESTAUTH: invalid BRANCH value ${v}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return node;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function parseRacroute(st, ops) {
|
|
96
|
+
const { keywords } = ops;
|
|
97
|
+
const node = { kind: 'RACROUTE', request: null, class: null, entity: null, attr: null, keywords: {} };
|
|
98
|
+
|
|
99
|
+
const req = keywords.get('REQUEST');
|
|
100
|
+
if (!req) {
|
|
101
|
+
throw new HlasmSyntax('RACROUTE: REQUEST is required');
|
|
102
|
+
}
|
|
103
|
+
const reqVal = req.trim().toUpperCase();
|
|
104
|
+
if (!RACROUTE_REQUESTS.has(reqVal)) {
|
|
105
|
+
throw new HlasmSyntax(`RACROUTE: invalid REQUEST value ${reqVal}`);
|
|
106
|
+
}
|
|
107
|
+
node.request = reqVal;
|
|
108
|
+
|
|
109
|
+
for (const [k, v] of keywords) {
|
|
110
|
+
if (!NAME.test(k)) {
|
|
111
|
+
throw new HlasmSyntax(`RACROUTE: invalid keyword name ${k}`);
|
|
112
|
+
}
|
|
113
|
+
node.keywords[k] = v;
|
|
114
|
+
if (k === 'CLASS') node.class = v;
|
|
115
|
+
if (k === 'ENTITY') node.entity = v;
|
|
116
|
+
if (k === 'ATTR') node.attr = v;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return node;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export const parsers = {
|
|
123
|
+
MODESET: parseModeset,
|
|
124
|
+
TESTAUTH: parseTestauth,
|
|
125
|
+
RACROUTE: parseRacroute
|
|
126
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
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, splitOperands } from '../operands.mjs';
|
|
4
|
+
import { parseAddress } from '../expr.mjs';
|
|
5
|
+
|
|
6
|
+
const OPEN_OPTS = new Set(['INPUT', 'OUTPUT', 'UPDAT', 'EXTEND', 'OUTIN', 'OUTINX', 'INOUT', 'RDBACK', 'DISP', 'LEAVE', 'REREAD']);
|
|
7
|
+
const CLOSE_OPTS = new Set(['REREAD', 'LEAVE', 'REWIND', 'FREE', 'DISP']);
|
|
8
|
+
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']);
|
|
9
|
+
|
|
10
|
+
// (dcb,(options),dcb,(options),...): each DCB address followed by its options, written in parentheses,
|
|
11
|
+
// as one bare word or omitted. A single DCB may be written without the outer parentheses.
|
|
12
|
+
function parseDcbList(raw, validOpts) {
|
|
13
|
+
const text = raw.trim();
|
|
14
|
+
const items = text.startsWith('(') && text.endsWith(')') ? splitOperands(text.slice(1, -1)) : [text];
|
|
15
|
+
const dcbs = [];
|
|
16
|
+
for (let i = 0; i < items.length; i++) {
|
|
17
|
+
const dcbRaw = items[i].trim();
|
|
18
|
+
if (!dcbRaw) throw new HlasmSyntax('empty dcb address in list');
|
|
19
|
+
let dcb;
|
|
20
|
+
try { dcb = parseAddress(dcbRaw); } catch { throw new HlasmSyntax(`invalid dcb address: ${dcbRaw}`); }
|
|
21
|
+
const next = (items[i + 1] ?? '').trim();
|
|
22
|
+
const word = next.toUpperCase();
|
|
23
|
+
let options = [];
|
|
24
|
+
if (next.startsWith('(') && next.endsWith(')')) options = splitOperands(next.slice(1, -1)).map((o) => o.trim().toUpperCase()).filter(Boolean);
|
|
25
|
+
else if (validOpts.has(word)) options = [word];
|
|
26
|
+
else if (next !== '' || i + 1 >= items.length) { dcbs.push({ dcb, options }); continue; }
|
|
27
|
+
for (const o of options) if (!validOpts.has(o)) throw new HlasmSyntax(`unknown option: ${o}`);
|
|
28
|
+
dcbs.push({ dcb, options });
|
|
29
|
+
i++;
|
|
30
|
+
}
|
|
31
|
+
return dcbs;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const parsers = {
|
|
35
|
+
OPEN(st, ops) {
|
|
36
|
+
if (ops.positional.length !== 1) throw new HlasmSyntax('OPEN requires exactly one positional operand');
|
|
37
|
+
const dcbs = parseDcbList(ops.positional[0], OPEN_OPTS);
|
|
38
|
+
const keywords = {};
|
|
39
|
+
for (const [k, v] of ops.keywords) {
|
|
40
|
+
if (k === 'TYPE') {
|
|
41
|
+
if (v.toUpperCase() !== 'J') throw new HlasmSyntax('OPEN TYPE must be J');
|
|
42
|
+
keywords.TYPE = v;
|
|
43
|
+
} else if (k === 'MODE') {
|
|
44
|
+
if (v !== '24' && v !== '31') throw new HlasmSyntax('OPEN MODE must be 24 or 31');
|
|
45
|
+
keywords.MODE = v;
|
|
46
|
+
} else if (k === 'MF') {
|
|
47
|
+
keywords.MF = v;
|
|
48
|
+
} else {
|
|
49
|
+
throw new HlasmSyntax(`unknown keyword for OPEN: ${k}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return { kind: 'OPEN', dcbs, keywords };
|
|
53
|
+
},
|
|
54
|
+
CLOSE(st, ops) {
|
|
55
|
+
if (ops.positional.length !== 1) throw new HlasmSyntax('CLOSE requires exactly one positional operand');
|
|
56
|
+
const dcbs = parseDcbList(ops.positional[0], CLOSE_OPTS);
|
|
57
|
+
const keywords = {};
|
|
58
|
+
for (const [k, v] of ops.keywords) {
|
|
59
|
+
if (k === 'TYPE') {
|
|
60
|
+
if (v.toUpperCase() !== 'T') throw new HlasmSyntax('CLOSE TYPE must be T');
|
|
61
|
+
keywords.TYPE = v;
|
|
62
|
+
} else if (k === 'MODE') {
|
|
63
|
+
if (v !== '24' && v !== '31') throw new HlasmSyntax('CLOSE MODE must be 24 or 31');
|
|
64
|
+
keywords.MODE = v;
|
|
65
|
+
} else if (k === 'MF') {
|
|
66
|
+
keywords.MF = v;
|
|
67
|
+
} else {
|
|
68
|
+
throw new HlasmSyntax(`unknown keyword for CLOSE: ${k}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return { kind: 'CLOSE', dcbs, keywords };
|
|
72
|
+
},
|
|
73
|
+
DCB(st, ops) {
|
|
74
|
+
if (ops.positional.length > 0) throw new HlasmSyntax('DCB takes no positional operands');
|
|
75
|
+
const keywords = {};
|
|
76
|
+
let ddname = null;
|
|
77
|
+
let dsorg = null;
|
|
78
|
+
let macrf = null;
|
|
79
|
+
let recfm = null;
|
|
80
|
+
let lrecl = null;
|
|
81
|
+
let blksize = null;
|
|
82
|
+
for (const [k, v] of ops.keywords) {
|
|
83
|
+
if (!DCB_KEYWORDS.has(k)) throw new HlasmSyntax(`unknown keyword for DCB: ${k}`);
|
|
84
|
+
keywords[k] = v;
|
|
85
|
+
if (k === 'DDNAME') ddname = v.toUpperCase();
|
|
86
|
+
else if (k === 'DSORG') dsorg = v;
|
|
87
|
+
else if (k === 'MACRF') macrf = v;
|
|
88
|
+
else if (k === 'RECFM') recfm = v;
|
|
89
|
+
else if (k === 'LRECL') lrecl = v;
|
|
90
|
+
else if (k === 'BLKSIZE') blksize = v;
|
|
91
|
+
}
|
|
92
|
+
return { kind: 'DCB', name: st.name, ddname, dsorg, macrf, recfm, lrecl, blksize, keywords };
|
|
93
|
+
}
|
|
94
|
+
};
|