@portll/cobolwork 0.0.1 → 0.2.75
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/LICENSE +661 -0
- package/LICENSING.md +93 -0
- package/NOTICE +9 -0
- package/README.md +323 -3
- package/THIRD-PARTY-NOTICES.md +118 -0
- package/bin/cobolwork.mjs +354 -0
- package/lib/advisories.mjs +133 -0
- package/lib/baseline.mjs +154 -0
- package/lib/bms.mjs +453 -0
- package/lib/build.mjs +402 -0
- package/lib/capabilities.mjs +79 -0
- package/lib/cics-commands.mjs +281 -0
- package/lib/compliance.mjs +81 -0
- package/lib/consequence.mjs +139 -0
- package/lib/control.mjs +1515 -0
- package/lib/csd.mjs +77 -0
- package/lib/dataflow.mjs +1506 -0
- package/lib/diff.mjs +344 -0
- package/lib/explain.mjs +145 -0
- package/lib/gate.mjs +383 -0
- package/lib/index.mjs +6 -0
- package/lib/inventory.mjs +79 -0
- package/lib/jcl.mjs +478 -0
- package/lib/kernel/findings.mjs +94 -0
- package/lib/kernel/identity.mjs +216 -0
- package/lib/kernel/memory.mjs +217 -0
- package/lib/kernel/printable.mjs +6 -0
- package/lib/kernel/registry.mjs +79 -0
- package/lib/kernel/ruleset.mjs +72 -0
- package/lib/kernel/source-tree.mjs +159 -0
- package/lib/kev.mjs +27 -0
- package/lib/options.mjs +512 -0
- package/lib/packs.mjs +148 -0
- package/lib/parser.mjs +2055 -0
- package/lib/policy.mjs +163 -0
- package/lib/precompile-cics.mjs +169 -0
- package/lib/precompile.mjs +544 -0
- package/lib/reach.mjs +122 -0
- package/lib/revision.json +1 -0
- package/lib/revision.mjs +89 -0
- package/lib/sarif.mjs +222 -0
- package/lib/scan.mjs +272 -0
- package/lib/sets/build.mjs +234 -0
- package/lib/sets/cics.mjs +306 -0
- package/lib/sets/compile.mjs +187 -0
- package/lib/sets/copybook.mjs +174 -0
- package/lib/sets/flow.mjs +487 -0
- package/lib/sets/hidden.mjs +216 -0
- package/lib/sets/jcl.mjs +440 -0
- package/lib/sets/log.mjs +406 -0
- package/lib/sets/opaque.mjs +102 -0
- package/lib/sets/priv.mjs +322 -0
- package/lib/sets/recon.mjs +267 -0
- package/lib/sets/vendor.mjs +117 -0
- package/lib/sets/web.mjs +327 -0
- package/lib/site.mjs +164 -0
- package/lib/sources.mjs +156 -0
- package/lib/tui/app.mjs +325 -0
- package/lib/tui/keys.mjs +39 -0
- package/lib/tui/model.mjs +96 -0
- package/lib/tui/run.mjs +38 -0
- package/lib/tui/screen.mjs +59 -0
- package/lib/tui/terminal.mjs +46 -0
- package/lib/utilities.mjs +296 -0
- package/lib/version.mjs +15 -0
- package/lib/words.mjs +318 -0
- package/package.json +45 -6
- package/rules/advisories.json +264 -0
- package/rules/compliance-dora.json +2151 -0
- package/rules/compliance-ffiec.json +2134 -0
- package/rules/compliance-nist80053.json +2134 -0
- package/rules/gitleaks-mainframe.toml +57 -0
- package/rules/kev-ids.json +1729 -0
- package/rules/packs/broadcom.json +124 -0
- package/rules/packs/connectdirect.json +116 -0
- package/rules/packs/controlm.json +114 -0
- package/rules/system-layouts.json +28 -0
- package/schema/cobolwork-coverage.schema.json +65 -0
- package/schema/cobolwork.policy.schema.json +54 -0
package/lib/policy.mjs
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// Which findings stop a build, which run-time checks the compiler must generate, and what an
|
|
3
|
+
// incomplete scan means: docs/spec/build-gate.md §4. Two layers - a floor from outside the reviewed
|
|
4
|
+
// tree and the repository's own file - and at every key the stricter of the two applies.
|
|
5
|
+
import { createHash } from 'node:crypto';
|
|
6
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
7
|
+
import { join, resolve } from 'node:path';
|
|
8
|
+
import { ALL_RULES } from './kernel/registry.mjs';
|
|
9
|
+
import { resolvesInside } from './kernel/source-tree.mjs';
|
|
10
|
+
import { printable } from './kernel/printable.mjs';
|
|
11
|
+
import { CLASSES, TIERS, TIER_RANK } from './consequence.mjs';
|
|
12
|
+
|
|
13
|
+
export const POLICY_FILE = 'cobolwork.policy.json';
|
|
14
|
+
export const CHECKS = ['subscript', 'reference-modification', 'numeric-data', 'argument-length'];
|
|
15
|
+
export const COMPILERS = ['enterprise', 'gnucobol'];
|
|
16
|
+
|
|
17
|
+
export const DEFAULT_POLICY = Object.freeze({
|
|
18
|
+
policyVersion: 1,
|
|
19
|
+
block: 'high',
|
|
20
|
+
always: 'crit',
|
|
21
|
+
classes: [...CLASSES],
|
|
22
|
+
rules: {},
|
|
23
|
+
coverage: 'block',
|
|
24
|
+
options: 'warn',
|
|
25
|
+
waivers: { maxDays: 180 },
|
|
26
|
+
checks: ['subscript', 'reference-modification'],
|
|
27
|
+
forbid: { enterprise: [], gnucobol: [] },
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const KEYS = Object.keys(DEFAULT_POLICY);
|
|
31
|
+
const isObject = (v) => v !== null && typeof v === 'object' && !Array.isArray(v);
|
|
32
|
+
const isStrings = (v) => Array.isArray(v) && v.every((x) => typeof x === 'string' && x.trim() !== '');
|
|
33
|
+
|
|
34
|
+
// What is wrong with a policy document, or nothing. A key nobody reads and a rule id nothing
|
|
35
|
+
// declares are both refused: either would be a setting that silently does nothing.
|
|
36
|
+
export function validatePolicy(raw) {
|
|
37
|
+
if (!isObject(raw)) return ['the policy is not a JSON object'];
|
|
38
|
+
const errs = [];
|
|
39
|
+
for (const k of Object.keys(raw)) if (!KEYS.includes(k) && k !== '_comment') errs.push(`${printable(k, 40)} is not a policy key; the keys are ${KEYS.join(', ')}`);
|
|
40
|
+
if (raw.policyVersion !== 1) errs.push('policyVersion must be 1');
|
|
41
|
+
for (const k of ['block', 'always']) if (k in raw && !TIERS.includes(raw[k])) errs.push(`${k} must be one of ${TIERS.join(', ')}`);
|
|
42
|
+
if ('classes' in raw) {
|
|
43
|
+
if (!Array.isArray(raw.classes) || raw.classes.some((c) => !CLASSES.includes(c))) errs.push(`classes must be a list drawn from ${CLASSES.join(', ')}`);
|
|
44
|
+
}
|
|
45
|
+
for (const k of ['coverage', 'options']) if (k in raw && !['block', 'warn'].includes(raw[k])) errs.push(`${k} must be block or warn`);
|
|
46
|
+
if ('rules' in raw) {
|
|
47
|
+
if (!isObject(raw.rules)) errs.push('rules must be an object of rule id to block or warn');
|
|
48
|
+
else for (const [id, v] of Object.entries(raw.rules)) {
|
|
49
|
+
if (!Object.hasOwn(ALL_RULES, id)) errs.push(`rules: ${printable(id, 60)} is not a rule cobolwork declares`);
|
|
50
|
+
else if (!['block', 'warn'].includes(v)) errs.push(`rules: ${id} must be block or warn`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if ('waivers' in raw) {
|
|
54
|
+
const w = raw.waivers;
|
|
55
|
+
if (!isObject(w) || Object.keys(w).some((k) => k !== 'maxDays') || !Number.isInteger(w.maxDays) || w.maxDays < 1) {
|
|
56
|
+
errs.push('waivers must be { "maxDays": <a whole number of days, at least 1> }');
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if ('checks' in raw) {
|
|
60
|
+
if (!isStrings(raw.checks)) errs.push(`checks must be a list drawn from ${CHECKS.join(', ')}`);
|
|
61
|
+
else for (const c of raw.checks) if (!CHECKS.includes(c)) errs.push(`checks: ${printable(c, 40)} is not one of ${CHECKS.join(', ')}`);
|
|
62
|
+
}
|
|
63
|
+
if ('forbid' in raw) {
|
|
64
|
+
if (!isObject(raw.forbid)) errs.push(`forbid must be an object keyed by ${COMPILERS.join(' or ')}`);
|
|
65
|
+
else for (const [c, list] of Object.entries(raw.forbid)) {
|
|
66
|
+
if (!COMPILERS.includes(c)) errs.push(`forbid: ${printable(c, 40)} is not one of ${COMPILERS.join(', ')}`);
|
|
67
|
+
else if (!isStrings(list)) errs.push(`forbid.${c} must be a list of options`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return errs;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// { path, raw, problems }, or null when the file is absent.
|
|
74
|
+
export function readPolicy(path) {
|
|
75
|
+
if (!existsSync(path)) return null;
|
|
76
|
+
let raw;
|
|
77
|
+
try { raw = JSON.parse(readFileSync(path, 'utf8')); } catch (e) {
|
|
78
|
+
return { path, raw: null, problems: [`${path} is not readable as JSON: ${printable(e.message, 120)}`] };
|
|
79
|
+
}
|
|
80
|
+
return { path, raw, problems: validatePolicy(raw).map((p) => `${path}: ${p}`) };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// The repository's own policy, from a tree on disk. A link out of the tree is not followed.
|
|
84
|
+
export function treePolicy(root) {
|
|
85
|
+
const path = join(root, POLICY_FILE);
|
|
86
|
+
if (!existsSync(path)) return null;
|
|
87
|
+
if (!resolvesInside(root, path)) return { path, raw: null, problems: [`${POLICY_FILE} is a link that leads outside the tree`] };
|
|
88
|
+
return readPolicy(path);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// The floor must come from outside the reviewed tree, or the change under review could write it.
|
|
92
|
+
export function floorPolicy(file, repo) {
|
|
93
|
+
const path = resolve(file);
|
|
94
|
+
if (!existsSync(path)) return { path, raw: null, problems: [`--policy ${path}: no such file`] };
|
|
95
|
+
if (resolvesInside(repo, path)) return { path, raw: null, problems: [`--policy ${path} is inside the repository; a floor the change can edit is not a floor`] };
|
|
96
|
+
return readPolicy(path);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const stricterTier = (a, b) => (TIER_RANK[a] <= TIER_RANK[b] ? a : b);
|
|
100
|
+
const union = (a, b) => [...new Set([...a, ...b])].sort();
|
|
101
|
+
// A list key the repository can add to and never shrink below the floor, or the defaults without one.
|
|
102
|
+
const listKey = (key, f, r, floor) => {
|
|
103
|
+
const base = key in f ? f[key] : DEFAULT_POLICY[key];
|
|
104
|
+
if (!(key in r)) return { value: [...base].sort(), by: key in f ? 'floor' : 'default' };
|
|
105
|
+
if (!floor) return { value: [...r[key]].sort(), by: 'repository' };
|
|
106
|
+
return { value: union(base, r[key]), by: key in f ? 'floor+repository' : 'default+repository' };
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// { policy, setBy, ignored }. Without a floor the repository's file stands over the defaults. With
|
|
110
|
+
// one, a repository key applies only where it is at least as strict as the floor's, and each key it
|
|
111
|
+
// could not loosen is named in `ignored`.
|
|
112
|
+
export function combinePolicies({ floor = null, repository = null } = {}) {
|
|
113
|
+
const f = floor || {};
|
|
114
|
+
const r = repository || {};
|
|
115
|
+
const setBy = {};
|
|
116
|
+
const ignored = [];
|
|
117
|
+
const pick = (key, stricter) => {
|
|
118
|
+
const base = key in f ? f[key] : DEFAULT_POLICY[key];
|
|
119
|
+
const baseBy = key in f ? 'floor' : 'default';
|
|
120
|
+
if (!(key in r)) { setBy[key] = baseBy; return base; }
|
|
121
|
+
if (!floor) { setBy[key] = 'repository'; return r[key]; }
|
|
122
|
+
const s = stricter(base, r[key]);
|
|
123
|
+
if (JSON.stringify(s) === JSON.stringify(r[key])) { setBy[key] = 'repository'; return r[key]; }
|
|
124
|
+
ignored.push(key);
|
|
125
|
+
setBy[key] = baseBy;
|
|
126
|
+
return base;
|
|
127
|
+
};
|
|
128
|
+
const checks = listKey('checks', f, r, floor);
|
|
129
|
+
const classes = listKey('classes', f, r, floor);
|
|
130
|
+
const policy = {
|
|
131
|
+
policyVersion: 1,
|
|
132
|
+
block: pick('block', stricterTier),
|
|
133
|
+
always: pick('always', stricterTier),
|
|
134
|
+
classes: classes.value,
|
|
135
|
+
coverage: pick('coverage', (a, b) => (a === 'block' || b === 'block' ? 'block' : 'warn')),
|
|
136
|
+
options: pick('options', (a, b) => (a === 'block' || b === 'block' ? 'block' : 'warn')),
|
|
137
|
+
waivers: pick('waivers', (a, b) => ({ maxDays: Math.min(a.maxDays, b.maxDays) })),
|
|
138
|
+
checks: checks.value,
|
|
139
|
+
forbid: Object.fromEntries(COMPILERS.map((c) => [c, union((f.forbid || {})[c] || [], (r.forbid || {})[c] || [])])),
|
|
140
|
+
rules: {},
|
|
141
|
+
};
|
|
142
|
+
setBy.checks = checks.by;
|
|
143
|
+
setBy.classes = classes.by;
|
|
144
|
+
setBy.forbid = r.forbid ? (f.forbid ? 'floor+repository' : 'repository') : f.forbid ? 'floor' : 'default';
|
|
145
|
+
const fr = f.rules || {};
|
|
146
|
+
const rr = r.rules || {};
|
|
147
|
+
for (const id of [...new Set([...Object.keys(fr), ...Object.keys(rr)])].sort()) {
|
|
148
|
+
// block is always the stricter; the repository's warn stands only where the floor also says warn.
|
|
149
|
+
if (rr[id] === 'block' || !floor || !(id in rr) || fr[id] === 'warn') policy.rules[id] = id in rr ? rr[id] : fr[id];
|
|
150
|
+
else { policy.rules[id] = fr[id] || null; ignored.push(`rules.${id}`); }
|
|
151
|
+
if (policy.rules[id] === null) delete policy.rules[id];
|
|
152
|
+
}
|
|
153
|
+
return { policy, setBy, ignored };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Keys sorted at every level, so one policy has one hash however its file was written.
|
|
157
|
+
export function canonical(value) {
|
|
158
|
+
if (Array.isArray(value)) return `[${value.map(canonical).join(',')}]`;
|
|
159
|
+
if (isObject(value)) return `{${Object.keys(value).sort().map((k) => `${JSON.stringify(k)}:${canonical(value[k])}`).join(',')}}`;
|
|
160
|
+
return JSON.stringify(value);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export const policyHash = (policy) => createHash('sha256').update(canonical(policy)).digest('hex');
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// The EXEC CICS half of lib/precompile.mjs: what one CICS command becomes, and the copybooks a CICS
|
|
3
|
+
// program copies that no repository holds. The commands, the direction each option's data moves and
|
|
4
|
+
// the DFHRESP and DFHVALUE numbers are IBM's, recorded with their pages in provenance/precompile.json.
|
|
5
|
+
import { EIB_LAYOUT } from './words.mjs';
|
|
6
|
+
import { CICS_COMMANDS, CICS_EVERY_COMMAND, DFHRESP, DFHVALUE, DFHAID_NAMES, DFHBMSCA_NAMES } from './cics-commands.mjs';
|
|
7
|
+
|
|
8
|
+
// The number DFHRESP(name) or DFHVALUE(name) stands for, or undefined for a name IBM's tables lack.
|
|
9
|
+
export const builtinValue = (fn, name) => (fn === 'RESP' ? DFHRESP : DFHVALUE)[name];
|
|
10
|
+
|
|
11
|
+
// Whitespace runs outside literals, as one space.
|
|
12
|
+
const squeeze = (text) => text.replace(/('[^']*'|"[^"]*")|\s+/g, (_, lit) => lit || ' ').trim();
|
|
13
|
+
|
|
14
|
+
// The options of an EXEC CICS command in order: each word, with its argument's text when it has one.
|
|
15
|
+
function cicsOptions(text) {
|
|
16
|
+
const out = [];
|
|
17
|
+
let i = 0;
|
|
18
|
+
while (i < text.length) {
|
|
19
|
+
const m = /^[\s,]*([A-Z0-9][A-Z0-9-]*)/i.exec(text.slice(i));
|
|
20
|
+
if (!m) { i++; continue; }
|
|
21
|
+
i += m[0].length;
|
|
22
|
+
const word = m[1].toUpperCase();
|
|
23
|
+
const p = /^\s*\(/.exec(text.slice(i));
|
|
24
|
+
if (!p) { out.push({ word }); continue; }
|
|
25
|
+
i += p[0].length;
|
|
26
|
+
let depth = 1;
|
|
27
|
+
let quote = null;
|
|
28
|
+
let arg = '';
|
|
29
|
+
for (; i < text.length; i++) {
|
|
30
|
+
const ch = text[i];
|
|
31
|
+
if (quote) { if (ch === quote) quote = null; } else if (ch === "'" || ch === '"') quote = ch;
|
|
32
|
+
else if (ch === '(') depth++;
|
|
33
|
+
else if (ch === ')' && --depth === 0) break;
|
|
34
|
+
arg += ch;
|
|
35
|
+
}
|
|
36
|
+
i++;
|
|
37
|
+
out.push({ word, arg: squeeze(arg) });
|
|
38
|
+
}
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Which table entry a command is: the one an option word identifies (SEND MAP by MAP), else the verb
|
|
43
|
+
// alone, else the one IBM makes the default (READQ is READQ TS).
|
|
44
|
+
export function cicsCommand(options) {
|
|
45
|
+
const verb = options[0]?.word;
|
|
46
|
+
const words = new Set(options.slice(1).map((o) => o.word));
|
|
47
|
+
const names = Object.keys(CICS_COMMANDS).filter((n) => n === verb || n.startsWith(`${verb} `));
|
|
48
|
+
return names.find((n) => CICS_COMMANDS[n].identify?.some((w) => words.has(w)))
|
|
49
|
+
|| names.find((n) => n === verb) || names.find((n) => CICS_COMMANDS[n].default) || null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// A literal, a figurative constant or anything computed can only be sent, whatever its option does.
|
|
53
|
+
const LITERAL = /^(?:[A-Z]{0,2}(['"])[\s\S]*\1|[+-]?\d*\.?\d+|(?:ZEROE?S?|SPACES?|LOW-VALUES?|HIGH-VALUES?|QUOTES?|NULLS?|ALL\s.+))$/i;
|
|
54
|
+
const COMPUTED = /^(?:LENGTH\s+OF|FUNCTION)\s/i;
|
|
55
|
+
|
|
56
|
+
// The commands that set or clear how a later condition, attention key or abend is handled.
|
|
57
|
+
const HANDLERS = new Set(['HANDLE CONDITION', 'HANDLE AID', 'HANDLE ABEND', 'IGNORE CONDITION', 'PUSH HANDLE', 'POP HANDLE']);
|
|
58
|
+
|
|
59
|
+
// The EIB halfword a HANDLE's GO TO ... DEPENDING ON reads: 0 when the HANDLE itself runs, so control
|
|
60
|
+
// falls through, and the label's position when CICS later returns there to take the branch.
|
|
61
|
+
export const SELECTOR = 'DFHEIGDI';
|
|
62
|
+
|
|
63
|
+
// What one EXEC CICS block becomes: a CALL naming the command, then each argument by its option's
|
|
64
|
+
// direction, then the EIB, which every command updates. A command the table lacks passes every data
|
|
65
|
+
// name BY REFERENCE, since nothing says what it does not write. A HANDLE with labels is followed by the
|
|
66
|
+
// GO TO ... DEPENDING ON its branch is taken through. `state` carries whether the block is in the
|
|
67
|
+
// procedure division, has the EIB and the selector, and collects the handlers and the counts.
|
|
68
|
+
export function translateCics(block, state) {
|
|
69
|
+
if (!state.procedure) return '';
|
|
70
|
+
const options = cicsOptions(block.text);
|
|
71
|
+
if (!options.length) return 'CONTINUE';
|
|
72
|
+
const name = cicsCommand(options);
|
|
73
|
+
const command = name && CICS_COMMANDS[name];
|
|
74
|
+
const second = options[1];
|
|
75
|
+
const callName = name || [options[0].word, ...(second && second.arg === undefined && second.word !== 'NOHANDLE' ? [second.word] : [])].join(' ');
|
|
76
|
+
if (!command) state.unknown++;
|
|
77
|
+
if (HANDLERS.has(name)) state.handlers.push({ line: block.line + 1, command: name, options: Object.fromEntries(options.slice(name.split(' ').length).map((o) => [o.word, o.arg ?? null])) });
|
|
78
|
+
const sending = [];
|
|
79
|
+
const receiving = [];
|
|
80
|
+
const labels = [];
|
|
81
|
+
const pass = (text, direction) => {
|
|
82
|
+
const list = direction === 'sends' || LITERAL.test(text) || COMPUTED.test(text) ? sending : receiving;
|
|
83
|
+
if (!list.includes(text)) list.push(text);
|
|
84
|
+
};
|
|
85
|
+
for (const o of options.slice(1)) {
|
|
86
|
+
if (!o.arg) continue;
|
|
87
|
+
const direction = command ? (command.options[o.word] || CICS_EVERY_COMMAND[o.word] || command.anyOption) : null;
|
|
88
|
+
if (direction === 'label') { labels.push(o.arg); continue; }
|
|
89
|
+
if (command && !direction) state.undirected++;
|
|
90
|
+
const builtin = /^DFH(RESP|VALUE)\s*\(\s*([A-Z0-9-]+)\s*\)$/i.exec(o.arg);
|
|
91
|
+
const value = builtin ? builtinValue(builtin[1].toUpperCase(), builtin[2].toUpperCase()) : undefined;
|
|
92
|
+
if (builtin && value === undefined) state.unresolved++;
|
|
93
|
+
pass(value === undefined ? o.arg : String(value), builtin ? 'sends' : direction);
|
|
94
|
+
}
|
|
95
|
+
// RECEIVE MAP without INTO writes the map's input record, SEND MAP without FROM reads its output one.
|
|
96
|
+
const written = new Set(options.map((o) => o.word));
|
|
97
|
+
for (const [option, d] of Object.entries(command?.defaults || {})) {
|
|
98
|
+
const map = /^(['"])([A-Z0-9$#@]+)\1$/i.exec(options.find((o) => o.word === d.name)?.arg || '');
|
|
99
|
+
if (map && !written.has(option) && !d.unless.some((w) => written.has(w))) pass(`${map[2].toUpperCase()}${d.suffix}`, command.options[option]);
|
|
100
|
+
}
|
|
101
|
+
const content = sending.filter((t) => !receiving.includes(t));
|
|
102
|
+
if (state.eib) receiving.push('DFHEIBLK');
|
|
103
|
+
const args = [...(content.length ? ['BY CONTENT', ...content] : []), ...(receiving.length ? ['BY REFERENCE', ...receiving] : [])];
|
|
104
|
+
state.translated++;
|
|
105
|
+
const branch = labels.length && state.selector ? [`GO TO ${labels.join(' ')} DEPENDING ON ${SELECTOR}`] : [];
|
|
106
|
+
return [`CALL 'CW-CICS-${callName.split(' ').join('-')}'`, ...(args.length ? ['USING', ...args] : []), ...branch].join(' ');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// The EXEC interface block the translator declares for every program with CICS in it, with the
|
|
110
|
+
// selector a HANDLE's branch reads in the reserved halfword after EIBTRMID.
|
|
111
|
+
export function eibCopybook() {
|
|
112
|
+
const fields = EIB_LAYOUT.flatMap(([n, pic]) => [[n, pic], ...(n === 'EIBTRMID' ? [[SELECTOR, 'S9(4) COMP']] : [])]);
|
|
113
|
+
return [' 01 DFHEIBLK.', ...fields.map(([n, pic]) => ` 05 ${n} PIC ${pic}.`), ''].join('\n');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// DFHAID and DFHBMSCA are IBM's and in no repository; each name is declared as one character, enough
|
|
117
|
+
// for a program that compares EIBAID with one or moves one to an attribute byte.
|
|
118
|
+
export const CONSTANT_COPYBOOKS = { DFHAID: DFHAID_NAMES, DFHBMSCA: DFHBMSCA_NAMES };
|
|
119
|
+
export const constantsCopybook = (names) => `${names.map((n) => ` 01 ${n} PIC X.`).join('\n')}\n`;
|
|
120
|
+
|
|
121
|
+
// A mapset's symbolic map, laid out the way the BMS assembler lays one out for COBOL: the 12-byte
|
|
122
|
+
// prefix when TIOAPFX=YES, then for each named field its length, flag or attribute byte, extended
|
|
123
|
+
// attribute bytes and data, the output record redefining the input. Grouped (GRPNAME) and OCCURS
|
|
124
|
+
// fields are laid out differently, and a mapset holding either is not written. Returns the copybook
|
|
125
|
+
// text and the names it declares, or null.
|
|
126
|
+
const ATTRIBUTE_LETTER = { COLOR: 'C', PS: 'P', HILIGHT: 'H', VALIDN: 'V', OUTLINE: 'U', SOSI: 'M', TRANSP: 'T' };
|
|
127
|
+
export function symbolicMapCopybook(mapset) {
|
|
128
|
+
const out = [];
|
|
129
|
+
const names = [];
|
|
130
|
+
const line = (s) => out.push(` ${s}`);
|
|
131
|
+
const declare = (n, s) => { names.push(n); line(s); };
|
|
132
|
+
// The assembler writes FILLER; each gets a name here, the layout unchanged, so the grade can tell
|
|
133
|
+
// the stand-in's items from the program's.
|
|
134
|
+
let k = 0;
|
|
135
|
+
const filler = (rec) => { const n = `${rec}-F${++k}`; names.push(n); return n; };
|
|
136
|
+
for (const map of mapset.maps) {
|
|
137
|
+
const fields = map.fields.filter((f) => f.name);
|
|
138
|
+
if (fields.some((f) => f.grpname || f.occurs > 1)) return null;
|
|
139
|
+
const { input, output, attributes } = map.symbolic;
|
|
140
|
+
if (!input && !output) continue;
|
|
141
|
+
const prefix = String(map.operands?.get?.('TIOAPFX') ?? mapset.options.tioapfx ?? '').toUpperCase() === 'YES';
|
|
142
|
+
const pic = (f, which) => f[which] || `X(${f.length})`;
|
|
143
|
+
if (input) {
|
|
144
|
+
declare(input, `01 ${input}.`);
|
|
145
|
+
if (prefix) line(` 02 ${filler(input)} PIC X(12).`);
|
|
146
|
+
for (const f of fields) {
|
|
147
|
+
const n = f.name.toUpperCase();
|
|
148
|
+
declare(`${n}L`, ` 02 ${n}L COMP PIC S9(4).`);
|
|
149
|
+
declare(`${n}F`, ` 02 ${n}F PIC X.`);
|
|
150
|
+
line(` 02 ${filler(input)} REDEFINES ${n}F.`);
|
|
151
|
+
declare(`${n}A`, ` 03 ${n}A PIC X.`);
|
|
152
|
+
if (attributes.length) line(` 02 ${filler(input)} PIC X(${attributes.length}).`);
|
|
153
|
+
declare(`${n}I`, ` 02 ${n}I PIC ${pic(f, 'picin')}.`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (output) {
|
|
157
|
+
declare(output, input ? `01 ${output} REDEFINES ${input}.` : `01 ${output}.`);
|
|
158
|
+
if (prefix) line(` 02 ${filler(output)} PIC X(12).`);
|
|
159
|
+
for (const f of fields) {
|
|
160
|
+
const n = f.name.toUpperCase();
|
|
161
|
+
if (input) line(` 02 ${filler(output)} PIC X(3).`);
|
|
162
|
+
else { line(` 02 ${filler(output)} PIC X(2).`); declare(`${n}A`, ` 02 ${n}A PIC X.`); }
|
|
163
|
+
for (const a of attributes) declare(`${n}${ATTRIBUTE_LETTER[a]}`, ` 02 ${n}${ATTRIBUTE_LETTER[a]} PIC X.`);
|
|
164
|
+
declare(`${n}O`, ` 02 ${n}O PIC ${pic(f, 'picout')}.`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return out.length ? { text: `${out.join('\n')}\n`, names } : null;
|
|
169
|
+
}
|