@portll/cobolwork 0.6.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.
- package/README.md +49 -18
- package/STABILITY.md +76 -0
- package/bin/cobolwork.mjs +30 -7
- package/lib/bms.mjs +21 -7
- package/lib/build.mjs +108 -53
- package/lib/capabilities.mjs +9 -10
- package/lib/cics-commands.mjs +501 -9
- package/lib/compliance.mjs +11 -0
- package/lib/consequence.mjs +13 -0
- package/lib/control-workers.mjs +161 -0
- package/lib/control.mjs +52 -3
- package/lib/dataflow.mjs +176 -125
- package/lib/db2/cursor.mjs +15 -0
- package/lib/db2/read.mjs +170 -0
- package/lib/db2/rules.mjs +77 -0
- package/lib/db2/stmt/alter.mjs +473 -0
- package/lib/db2/stmt/grant.mjs +125 -0
- package/lib/db2/stmt/index.mjs +141 -0
- package/lib/db2/stmt/misc.mjs +325 -0
- package/lib/db2/stmt/routine.mjs +564 -0
- package/lib/db2/stmt/storage.mjs +146 -0
- package/lib/db2/stmt/table.mjs +540 -0
- package/lib/db2/stmt/view.mjs +146 -0
- package/lib/diff.mjs +17 -5
- package/lib/evidence/cli.mjs +14 -3
- package/lib/evidence/record.mjs +1 -1
- package/lib/evidence/store.mjs +44 -31
- package/lib/exec-reading.mjs +51 -0
- package/lib/execution.mjs +3 -2
- package/lib/explain.mjs +2 -0
- package/lib/exploitability.mjs +11 -2
- package/lib/hlasm/asm/data.mjs +13 -1
- package/lib/hlasm/asm/sections.mjs +3 -1
- package/lib/hlasm/instr.mjs +25 -0
- package/lib/hlasm/macro/authorization.mjs +24 -4
- package/lib/hlasm/macro/datasets.mjs +30 -17
- package/lib/hlasm/macro/io.mjs +68 -45
- package/lib/hlasm/macro/le.mjs +2 -2
- package/lib/hlasm/macro/operator.mjs +32 -21
- package/lib/hlasm/macro/program.mjs +126 -84
- package/lib/hlasm/macro/recovery.mjs +10 -5
- package/lib/hlasm/macro/storage.mjs +64 -14
- package/lib/hlasm/macro/structured.mjs +1 -1
- package/lib/hlasm/model.mjs +35 -10
- package/lib/hlasm/mvs38.mjs +47 -0
- package/lib/hlasm/operands.mjs +10 -0
- package/lib/hlasm/optable.mjs +61 -0
- package/lib/hlasm/read.mjs +22 -8
- package/lib/hlasm.mjs +44 -7
- package/lib/ims/dli.mjs +37 -0
- package/lib/ims/macro/dbd.mjs +299 -0
- package/lib/ims/macro/psb.mjs +286 -0
- package/lib/ims/model.mjs +149 -0
- package/lib/ims/operands.mjs +23 -0
- package/lib/ims/read.mjs +37 -0
- package/lib/ims/rules.mjs +135 -0
- package/lib/ironwork.mjs +17 -13
- package/lib/kernel/pds-archive.mjs +256 -0
- package/lib/kernel/registry.mjs +14 -8
- package/lib/kernel/shared-pass.mjs +34 -9
- package/lib/kernel/source-tree.mjs +104 -36
- package/lib/kernel/version-key.mjs +17 -0
- package/lib/layout.mjs +26 -31
- package/lib/parser.mjs +136 -17
- package/lib/pli/cursor.mjs +15 -0
- package/lib/pli/expr.mjs +101 -0
- package/lib/pli/include.mjs +82 -0
- package/lib/pli/layout.mjs +125 -0
- package/lib/pli/lex.mjs +198 -0
- package/lib/pli/program.mjs +280 -0
- package/lib/pli/rules/based.mjs +95 -0
- package/lib/pli/rules/conditions.mjs +68 -0
- package/lib/pli/rules/entry.mjs +130 -0
- package/lib/pli/rules/index.mjs +24 -0
- package/lib/pli/rules/preprocessor.mjs +55 -0
- package/lib/pli/statements.mjs +130 -0
- package/lib/pli/stmt/alloc.mjs +45 -0
- package/lib/pli/stmt/assignment.mjs +56 -0
- package/lib/pli/stmt/call.mjs +104 -0
- package/lib/pli/stmt/conditions.mjs +94 -0
- package/lib/pli/stmt/control.mjs +219 -0
- package/lib/pli/stmt/declare.mjs +149 -0
- package/lib/pli/stmt/exec.mjs +55 -0
- package/lib/pli/stmt/io.mjs +239 -0
- package/lib/pli/stmt/misc.mjs +4 -0
- package/lib/pli/stmt/preprocessor.mjs +242 -0
- package/lib/pli/stmt/procedure.mjs +258 -0
- package/lib/pli/stmt/stream.mjs +283 -0
- package/lib/pli/storage.mjs +129 -0
- package/lib/precompile-check.mjs +124 -0
- package/lib/precompile-cics.mjs +7 -3
- package/lib/reach.mjs +11 -2
- package/lib/revision.json +1 -1
- package/lib/sarif.mjs +41 -3
- package/lib/scan.mjs +7 -1
- package/lib/sets/abend.mjs +16 -6
- package/lib/sets/cics.mjs +15 -35
- package/lib/sets/compile.mjs +41 -15
- package/lib/sets/crypto.mjs +5 -3
- package/lib/sets/ddl.mjs +36 -0
- package/lib/sets/flow.mjs +18 -1
- package/lib/sets/hidden.mjs +5 -3
- package/lib/sets/hlasm.mjs +72 -12
- package/lib/sets/ims.mjs +139 -0
- package/lib/sets/log.mjs +6 -6
- package/lib/sets/opaque.mjs +27 -7
- package/lib/sets/pli.mjs +40 -0
- package/lib/sets/recon.mjs +5 -3
- package/lib/sets/secrets.mjs +5 -3
- package/lib/sets/semantics.mjs +3 -0
- package/lib/sets/web.mjs +36 -22
- package/lib/site.mjs +10 -0
- package/lib/sources.mjs +80 -20
- package/lib/statement-cursor.mjs +67 -0
- package/lib/verify.mjs +3 -2
- package/lib/version.mjs +6 -0
- package/package.json +3 -2
- package/rules/compliance-cobit2019.json +432 -1
- package/rules/compliance-dora.json +414 -1
- package/rules/compliance-ffiec.json +414 -1
- package/rules/compliance-nist80053.json +466 -1
- package/rules/hlasm-optables.json +8024 -0
- package/schema/cobolwork-baseline.schema.json +36 -0
- package/schema/cobolwork-build-provenance.schema.json +187 -0
- package/schema/cobolwork-build.schema.json +382 -0
- package/schema/cobolwork-capabilities.schema.json +239 -0
- package/schema/cobolwork-diff.schema.json +217 -0
- package/schema/cobolwork-evidence.schema.json +161 -0
- package/schema/cobolwork-execution.schema.json +53 -0
- package/schema/cobolwork-explain.schema.json +360 -0
- package/schema/cobolwork-finding.schema.json +465 -0
- package/schema/cobolwork-flow.schema.json +465 -0
- package/schema/cobolwork-gate.schema.json +211 -0
- package/schema/cobolwork-inventory.schema.json +206 -0
- package/schema/cobolwork-parse.schema.json +105 -0
- package/schema/cobolwork-reach.schema.json +74 -0
- package/schema/cobolwork-report.schema.json +559 -0
- package/schema/cobolwork-witness.schema.json +107 -0
- package/schema/cobolwork.baseline.schema.json +101 -0
- package/schema/cobolwork.site.schema.json +116 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// HLASM's OPTABLE option: which operation code table the assembler reads machine instructions from.
|
|
3
|
+
// A mnemonic outside the chosen table is no instruction, so a macro of that name is called instead.
|
|
4
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
5
|
+
|
|
6
|
+
const TABLE = new URL('../../rules/hlasm-optables.json', import.meta.url);
|
|
7
|
+
const T = existsSync(TABLE) ? JSON.parse(readFileSync(TABLE, 'utf8')) : { levels: [], synonyms: {}, machines: {}, mnemonics: {} };
|
|
8
|
+
const RANK = new Map(T.levels.map((level, i) => [level, i]));
|
|
9
|
+
|
|
10
|
+
// An OPTABLE or MACHINE suboption as written, to the table's level; null when it names none.
|
|
11
|
+
export function optableLevel(name, { machine = false } = {}) {
|
|
12
|
+
const n = String(name || '').trim().toUpperCase();
|
|
13
|
+
if (machine) return T.machines[n] ?? null;
|
|
14
|
+
if (n === 'UNI') return 'UNI';
|
|
15
|
+
return RANK.has(n) ? n : T.synonyms[n] ?? null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// "UNI,DOS-370": UNI, a level, a level and later (X-), or a range (X-Y).
|
|
19
|
+
function covers(ranges, level) {
|
|
20
|
+
return ranges.split(',').some((part) => {
|
|
21
|
+
if (part === level) return true;
|
|
22
|
+
if (level === 'UNI') return false;
|
|
23
|
+
const m = /^([^-]+)-([^-]*)$/.exec(part);
|
|
24
|
+
if (!m || !RANK.has(m[1])) return false;
|
|
25
|
+
const at = RANK.get(level);
|
|
26
|
+
return at >= RANK.get(m[1]) && (m[2] === '' || at <= RANK.get(m[2]));
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Whether the table at this level (UNI when none is given) holds the mnemonic; one the table does not
|
|
31
|
+
// list is taken as held.
|
|
32
|
+
const excluded = new Map();
|
|
33
|
+
export function inOptable(mnemonic, level) {
|
|
34
|
+
const at = level ?? 'UNI';
|
|
35
|
+
if (!excluded.has(at)) excluded.set(at, new Set(Object.entries(T.mnemonics).filter(([, ranges]) => !ranges.some((r) => covers(r, at))).map(([m]) => m)));
|
|
36
|
+
return !excluded.get(at).has(mnemonic);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// OPTABLE or MACHINE in an option string (PARM, *PROCESS), to a level; OVERRIDE(...) says it is forced.
|
|
40
|
+
export function optableIn(options) {
|
|
41
|
+
const text = String(options || '').toUpperCase();
|
|
42
|
+
const override = /OVERRIDE\(([^)]*(?:\([^)]*\)[^)]*)*)\)/.exec(text)?.[1] ?? '';
|
|
43
|
+
const read = (s) => {
|
|
44
|
+
const o = /\bOPTABLE\(\s*([A-Z0-9]+)/.exec(s);
|
|
45
|
+
if (o) return optableLevel(o[1]);
|
|
46
|
+
const m = /\bMACHINE\(\s*([A-Z0-9-]+)/.exec(s);
|
|
47
|
+
return m ? optableLevel(m[1], { machine: true }) : null;
|
|
48
|
+
};
|
|
49
|
+
const forced = read(override);
|
|
50
|
+
return forced ? { level: forced, override: true } : read(text) ? { level: read(text), override: false } : null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// The *PROCESS statements that open a source file name its options.
|
|
54
|
+
export function processOptions(text) {
|
|
55
|
+
const out = [];
|
|
56
|
+
for (const line of String(text).replace(/\r\n?/g, '\n').split('\n')) {
|
|
57
|
+
if (!/^\*PROCESS\s/i.test(line)) break;
|
|
58
|
+
out.push(line.slice(8, 71).trim());
|
|
59
|
+
}
|
|
60
|
+
return out.join(',');
|
|
61
|
+
}
|
package/lib/hlasm/read.mjs
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { foldStatements } from '../bms.mjs';
|
|
8
8
|
import { HlasmSyntax, NAME, macroOperands } from './operands.mjs';
|
|
9
9
|
import { INSTRUCTIONS, parseInstruction, formatFamily } from './instr.mjs';
|
|
10
|
+
import { inOptable } from './optable.mjs';
|
|
10
11
|
import { parsers as data } from './asm/data.mjs';
|
|
11
12
|
import { parsers as sections } from './asm/sections.mjs';
|
|
12
13
|
import { parsers as symbols } from './asm/symbols.mjs';
|
|
@@ -50,7 +51,9 @@ export const PARSERS = Object.freeze({ ...data, ...sections, ...symbols, ...list
|
|
|
50
51
|
export const MACRO_PARSERS = Object.freeze({ ...program, ...linkage, ...storage, ...authorization, ...operator, ...datasets, ...io, ...recovery, ...le, ...structured });
|
|
51
52
|
|
|
52
53
|
// Statements other than comments, each marked inMacro when it is part of a macro definition, the
|
|
53
|
-
// prototype (the statement after MACRO) marked as such
|
|
54
|
+
// prototype (the statement after MACRO) marked as such, and sourceMacro when its operation names a
|
|
55
|
+
// macro the file has already defined: a source macro with the name of an instruction or a library
|
|
56
|
+
// macro overrides it (https://www.ibm.com/docs/en/hla-and-tf/1.6.0?topic=definitions-macro-instruction-prototype).
|
|
54
57
|
// EXEC CICS and EXEC SQL are written for their translators, with blanks between the words: their
|
|
55
58
|
// field is the cards' text after EXEC, columns 16 to 71 of each continuation, not the operands up
|
|
56
59
|
// to the first blank as an assembler statement's are.
|
|
@@ -58,20 +61,28 @@ function execField(phys, st) {
|
|
|
58
61
|
return st.lines.map((n, i) => (i === 0 ? phys[n - 1].slice(0, 71).replace(/^\S*\s+EXEC\b/i, '') : phys[n - 1].slice(15, 71))).join(' ').replace(/\s+/g, ' ').trim();
|
|
59
62
|
}
|
|
60
63
|
|
|
61
|
-
|
|
62
|
-
|
|
64
|
+
// libraryMacros: the macros the repository's own library defines. One with the name of a macro this
|
|
65
|
+
// reader knows is the repository's, found ahead of the system's on the macro search; an instruction
|
|
66
|
+
// is never looked for in a library.
|
|
67
|
+
export function readHlasmStatements(text, { libraryMacros = null } = {}) {
|
|
68
|
+
const { statements, diags } = foldStatements(text, { batch: true });
|
|
63
69
|
const phys = String(text).replace(/\r\n?/g, '\n').split('\n');
|
|
64
70
|
const out = [];
|
|
65
71
|
let depth = 0;
|
|
66
72
|
let prototypeNext = false;
|
|
73
|
+
const defined = new Set();
|
|
67
74
|
for (const s of statements) {
|
|
68
|
-
if (s.kind === 'comment') continue;
|
|
75
|
+
if (s.kind === 'comment' || s.kind === 'not-assembler') continue;
|
|
69
76
|
const st = { ...s, inMacro: depth > 0, prototype: false };
|
|
70
77
|
if (s.kind === 'statement' && s.operation === 'EXEC') st.field = execField(phys, s);
|
|
71
78
|
if (s.kind === 'statement') {
|
|
72
79
|
if (s.operation === 'MACRO') { depth++; prototypeNext = true; st.inMacro = true; }
|
|
73
|
-
else if (prototypeNext) { st.prototype = true; prototypeNext = false; }
|
|
80
|
+
else if (prototypeNext) { st.prototype = true; prototypeNext = false; if (s.operation) defined.add(s.operation.toUpperCase()); }
|
|
81
|
+
else if (s.operation && defined.has(s.operation.toUpperCase())) st.sourceMacro = true;
|
|
82
|
+
else if (s.operation && libraryMacros?.has(s.operation.toUpperCase()) && NAMED_MACROS.has(s.operation.toUpperCase())) st.libraryMacro = true;
|
|
74
83
|
if (s.operation === 'MEND') depth = Math.max(0, depth - 1);
|
|
84
|
+
// Each program of a batch is its own assembly: its source macros are its own.
|
|
85
|
+
if (s.operation === 'END' && depth === 0) defined.clear();
|
|
75
86
|
}
|
|
76
87
|
out.push(st);
|
|
77
88
|
}
|
|
@@ -90,19 +101,22 @@ function run(kind, parser, st, ...args) {
|
|
|
90
101
|
|
|
91
102
|
// { kind, status, node?, reason?, crash? }: parsed, unbuilt (no parser for the kind yet), unparsed
|
|
92
103
|
// (the parser refused it) or unknown (not an operation HLASM has).
|
|
93
|
-
export function parseHlasmStatement(st) {
|
|
104
|
+
export function parseHlasmStatement(st, opts = {}) {
|
|
94
105
|
if (st.kind !== 'statement') return { kind: 'UNREADABLE', status: 'unknown', reason: 'no operation field' };
|
|
106
|
+
if (st.continues === 'comment') return { kind: 'CONTINUED', status: 'unparsed', reason: 'the comment above runs into column 72, so the assembler reads this line as part of it' };
|
|
107
|
+
if (st.continues === 'statement') return { kind: 'CONTINUED', status: 'unparsed', reason: 'the statement above runs into column 72 and this line does not leave columns 1 to 15 blank' };
|
|
95
108
|
const op = st.operation;
|
|
96
109
|
if (op === 'MACRO' || op === 'MEND') return { kind: 'MACRO DEFINITION', status: 'parsed', node: { kind: op } };
|
|
97
110
|
if (CONDITIONAL.has(op)) return { kind: 'CONDITIONAL', status: 'unbuilt' };
|
|
98
111
|
if (st.prototype) return run('PROTOTYPE', model.PROTOTYPE, st);
|
|
99
112
|
if (st.inMacro) return run('MODEL', model.MODEL, st);
|
|
100
113
|
if (/&[A-Z$#@_]/i.test(`${st.name || ''} ${op} ${st.field || ''}`.replace(/&&/g, ''))) return run('SUBSTITUTED', model.SUBSTITUTED, st);
|
|
114
|
+
if (st.sourceMacro || st.libraryMacro) return run('MACRO CALL', (s) => ({ kind: 'MACRO CALL', macro: op, ...(st.sourceMacro ? { source: true } : { library: true }), ...macroOperands(s.field || '') }), st);
|
|
101
115
|
// A lone comma is the convention for no operands with remarks after it (CSECT , remark); an
|
|
102
116
|
// instruction that takes no operands reads whatever follows it as remarks.
|
|
103
117
|
if (ASSEMBLER.has(op)) return run(op, PARSERS[op], st.field === ',' || NO_OPERANDS.has(op) ? { ...st, field: '' } : st);
|
|
104
118
|
const row = INSTRUCTIONS.get(op);
|
|
105
|
-
if (row) return run(formatFamily(row.format), parseInstruction, st, row);
|
|
119
|
+
if (row && inOptable(op, opts.optable)) return run(formatFamily(row.format), parseInstruction, st, row);
|
|
106
120
|
if (op === 'EXEC') {
|
|
107
121
|
const kind = `EXEC ${(/^\S+/.exec(st.field || '') || ['?'])[0].toUpperCase()}`;
|
|
108
122
|
return run(kind, exec[kind], st);
|
|
@@ -110,7 +124,7 @@ export function parseHlasmStatement(st) {
|
|
|
110
124
|
if (!NAME.test(op)) return { kind: 'UNKNOWN', status: 'unknown', reason: `${op} is not an operation name` };
|
|
111
125
|
if (NAMED_MACROS.has(op)) {
|
|
112
126
|
const parser = MACRO_PARSERS[op];
|
|
113
|
-
return run(op, parser && ((s) => parser(s, macroOperands(s.field === ',' ? '' : s.field || ''))), st);
|
|
127
|
+
return run(op, parser && ((s) => parser(s, macroOperands(s.field === ',' ? '' : s.field || ''), opts)), st);
|
|
114
128
|
}
|
|
115
129
|
return run('MACRO CALL', (s) => ({ kind: 'MACRO CALL', macro: op, ...macroOperands(s.field || '') }), st);
|
|
116
130
|
}
|
package/lib/hlasm.mjs
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { readFileSync } from 'node:fs';
|
|
8
8
|
import { foldStatements } from './bms.mjs';
|
|
9
9
|
import { parseOperands, splitOperands } from './cards.mjs';
|
|
10
|
+
import { keyZeroExtkeys, systemKeyExtkeys } from './hlasm/mvs38.mjs';
|
|
10
11
|
|
|
11
12
|
const TABLE = JSON.parse(readFileSync(new URL('../rules/hlasm-operations.json', import.meta.url), 'utf8'));
|
|
12
13
|
export const OPERATIONS = new Map(TABLE.operations.map((o) => [o.name, o]));
|
|
@@ -31,31 +32,64 @@ const IMS = new Set(['DBD', 'DBDGEN', 'SEGM', 'PSBGEN', 'PCB', 'SENSEG']);
|
|
|
31
32
|
|
|
32
33
|
// MODESET changes the PSW key or the state; these operand values make it key zero or supervisor
|
|
33
34
|
// state, and the opposite values (KEY=NZERO, MODE=PROB) return from it. EXTKEY is the inline form,
|
|
34
|
-
// which sets only the PSW key.
|
|
35
|
+
// which sets only the PSW key; MVS 3.8's names for key zero count when its forms are read.
|
|
35
36
|
const STATE_CHANGE = { KEY: ['ZERO'], MODE: ['SUP'], EXTKEY: ['ZERO'] };
|
|
36
37
|
|
|
37
38
|
const nameList = (field) => splitOperands(field || '').map((s) => s.trim().toUpperCase()).filter((s) => /^[A-Z$#@_][A-Z0-9$#@_]*$/.test(s));
|
|
38
39
|
|
|
39
|
-
function stateChange(field) {
|
|
40
|
+
function stateChange(field, opts) {
|
|
40
41
|
let ops;
|
|
41
42
|
try { ops = parseOperands(field || ''); } catch { return null; }
|
|
42
|
-
for (const [key,
|
|
43
|
+
for (const [key, zero] of Object.entries(STATE_CHANGE)) {
|
|
44
|
+
const values = key === 'EXTKEY' ? [...zero, ...keyZeroExtkeys(opts)] : zero;
|
|
43
45
|
const v = ops.keywords.get(key);
|
|
44
46
|
if (v && values.includes(String(v).trim().toUpperCase())) return `${key}=${String(v).trim().toUpperCase()}`;
|
|
45
47
|
}
|
|
46
48
|
return null;
|
|
47
49
|
}
|
|
48
50
|
|
|
51
|
+
// EXTKEY= naming a system key other than zero: z/OS's KEY2, KEY3, KEY4 and KEY7, and MVS 3.8's
|
|
52
|
+
// names for keys 1 to 7 when its forms are read. TCB, RBT1 and RBT234 take a key decided at run time.
|
|
53
|
+
const SYSTEM_EXTKEYS = [['KEY2', 2], ['KEY3', 3], ['KEY4', 4], ['KEY7', 7]];
|
|
54
|
+
|
|
55
|
+
function systemKeyChange(field, opts) {
|
|
56
|
+
let ops;
|
|
57
|
+
try { ops = parseOperands(field || ''); } catch { return null; }
|
|
58
|
+
const v = String(ops.keywords.get('EXTKEY') || '').trim().toUpperCase();
|
|
59
|
+
const key = new Map([...SYSTEM_EXTKEYS, ...systemKeyExtkeys(opts)]).get(v);
|
|
60
|
+
return key === undefined ? null : { written: `EXTKEY=${v}`, key };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// A disassembler's listing: each line's object code, its characters between asterisks and its offset
|
|
64
|
+
// to the right of the source, as in `BR R15 07FF *..* 00004`.
|
|
65
|
+
const DISASSEMBLED = /\s[0-9A-F]{2,16}(?: [0-9A-F]{2,16}){0,2}\s+\*.{1,16}\*\s+[0-9A-F]{5,8}\s*$/;
|
|
66
|
+
|
|
67
|
+
// English function words, which no statement's name, operation or first operand holds.
|
|
68
|
+
const FUNCTION_WORDS = new Set(['THE', 'OF', 'AND', 'TO', 'IS', 'IN', 'FOR', 'THIS', 'THAT', 'WITH', 'ARE', 'BE', 'IT', 'AN', 'WILL', 'CAN',
|
|
69
|
+
'YOU', 'WHICH', 'FROM', 'ON', 'AS', 'BY', 'NOT', 'IF', 'WHEN', 'HAS', 'HAVE', 'WAS', 'ALL', 'ANY', 'ONE', 'MAY', 'YOUR', 'THESE', 'THEY',
|
|
70
|
+
'THERE', 'SHOULD', 'MUST', 'ALSO', 'ONLY']);
|
|
71
|
+
|
|
72
|
+
// A manual, article or README saved as assembler source: its statements, macro definitions included,
|
|
73
|
+
// read as English, and the reader refuses many of those in open code. On the CBT, MVS 3.8j and GitHub
|
|
74
|
+
// corpora both thresholds together held for 25 documents and no program.
|
|
75
|
+
export function isDocument(statements, open, refused) {
|
|
76
|
+
if (open < 5) return false;
|
|
77
|
+
const english = statements.filter((s) => [s.name, s.operation, (s.field || '').split(/[\s,]/)[0]].some((w) => w && FUNCTION_WORDS.has(w.toUpperCase()))).length;
|
|
78
|
+
return english >= 0.15 * statements.length && refused >= 0.2 * open;
|
|
79
|
+
}
|
|
80
|
+
|
|
49
81
|
// What the file is, and in it: each operation the table names with where it is written; the
|
|
50
82
|
// sections and entry points it defines, which a CALL or a job step can name; and the names it
|
|
51
83
|
// leaves to the binder.
|
|
52
|
-
export function readHlasm(text) {
|
|
53
|
-
const { statements, diags } = foldStatements(text);
|
|
84
|
+
export function readHlasm(text, opts = {}) {
|
|
85
|
+
const { statements, diags } = foldStatements(text, { batch: true });
|
|
54
86
|
const ops = statements.filter((s) => s.kind === 'statement');
|
|
55
87
|
const has = (set) => ops.some((s) => set.has(s.operation));
|
|
56
88
|
const weak = new Set(ops.filter((s) => WEAK.has(s.operation)).map((s) => s.operation)).size;
|
|
57
89
|
const assembler = !GNU.test(text) && (ops.some(strong) || weak >= 2);
|
|
58
|
-
const
|
|
90
|
+
const lines = String(text).replace(/\r\n?/g, '\n').split('\n');
|
|
91
|
+
const listing = ops.length >= 5 && ops.filter((s) => DISASSEMBLED.test(lines[s.line - 1].slice(0, 80))).length * 2 >= ops.length;
|
|
92
|
+
const kind = has(BMS) ? 'bms' : has(IMS) ? 'ims' : !assembler ? 'unrecognised' : listing ? 'listing' : 'hlasm';
|
|
59
93
|
const out = { kind, statements: ops.length, operations: [], defines: [], external: [], diags };
|
|
60
94
|
if (kind !== 'hlasm') return out;
|
|
61
95
|
|
|
@@ -72,7 +106,10 @@ export function readHlasm(text) {
|
|
|
72
106
|
const op = OPERATIONS.get(s.operation);
|
|
73
107
|
if (!op || op.class === 'section' || op.class === 'linkage') continue;
|
|
74
108
|
const use = { name: op.name, class: op.class, line: s.line, inMacro, field: s.field };
|
|
75
|
-
if (op.name === 'MODESET')
|
|
109
|
+
if (op.name === 'MODESET') {
|
|
110
|
+
use.stateChange = stateChange(s.field, opts);
|
|
111
|
+
if (!use.stateChange) use.systemKey = systemKeyChange(s.field, opts);
|
|
112
|
+
}
|
|
76
113
|
out.operations.push(use);
|
|
77
114
|
}
|
|
78
115
|
return out;
|
package/lib/ims/dli.mjs
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// The DL/I call interface as a program issues it: CALL 'CBLTDLI' or 'AIBTDLI' USING [count,] function,
|
|
3
|
+
// PCB or AIB, I/O area [, SSA ...], and EXEC DLI. Each database function needs a processing option on
|
|
4
|
+
// the PCB it runs against, the PROCOPT of its PSBGEN PCB or SENSEG: a get needs G, ISRT I, REPL R
|
|
5
|
+
// and DLET D. A get call fills the I/O area with the segment it retrieves.
|
|
6
|
+
|
|
7
|
+
export const DLI_ROUTINE = /^(CBLTDLI|AIBTDLI)$/i;
|
|
8
|
+
|
|
9
|
+
export const DLI_FUNCTIONS = Object.freeze({
|
|
10
|
+
GU: 'G', GN: 'G', GNP: 'G', GHU: 'G', GHN: 'G', GHNP: 'G',
|
|
11
|
+
ISRT: 'I', REPL: 'R', DLET: 'D',
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
export const isGet = (fn) => DLI_FUNCTIONS[fn] === 'G';
|
|
15
|
+
|
|
16
|
+
// Calls that are not database calls: scheduling, termination, checkpoint, restart, rollback, sync,
|
|
17
|
+
// logging, statistics and the system service calls. They need no processing option.
|
|
18
|
+
export const DLI_SERVICES = new Set(['PCB', 'TERM', 'CHKP', 'XRST', 'ROLB', 'ROLL', 'ROLS', 'SETS', 'SETU', 'SYNC', 'INIT',
|
|
19
|
+
'INQY', 'GSCD', 'LOG', 'STAT', 'APSB', 'DPSB', 'GMSG', 'ICMD', 'RCMD', 'SCHD', 'AUTH', 'CHNG', 'PURG', 'CMD', 'GCMD', 'ICAL']);
|
|
20
|
+
|
|
21
|
+
// The PSB a DFSRRC00 step's PARM names: region type, program, then PSB, which defaults to the program.
|
|
22
|
+
export function psbOfParm(parm) {
|
|
23
|
+
const [, program, psb] = String(parm || '').replace(/^[('\s]+|[)'\s]+$/g, '').split(',').map((x) => x.trim().toUpperCase());
|
|
24
|
+
return program ? { program, psb: psb || program } : null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// The function code of a CALL to a DL/I routine and the argument that holds it: the first argument,
|
|
28
|
+
// or the second when the first is a parameter count. `valueOf(arg)` gives the literal or the VALUE
|
|
29
|
+
// of the item an argument names.
|
|
30
|
+
export function dliFunction(using, valueOf) {
|
|
31
|
+
for (let index = 0; index < Math.min(2, using.length); index++) {
|
|
32
|
+
const v = valueOf(using[index]);
|
|
33
|
+
const fn = v == null ? null : String(v).trim().toUpperCase();
|
|
34
|
+
if (fn && Object.hasOwn(DLI_FUNCTIONS, fn)) return { fn, index };
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// Parsers for the IMS DBD macros: DBD, DATASET, AREA, SEGM, FIELD, LCHILD, XDFLD, DBDGEN.
|
|
3
|
+
import { ImsSyntax, sublist, integer, onlyKeywords } from '../operands.mjs';
|
|
4
|
+
|
|
5
|
+
const ACCESS_TYPES = new Set(['HDAM', 'HIDAM', 'HISAM', 'HSAM', 'SHISAM', 'SHSAM', 'PHDAM', 'PHIDAM', 'PSINDEX', 'INDEX', 'LOGICAL', 'DEDB', 'MSDB', 'GSAM']);
|
|
6
|
+
const ACCESS_METHODS = new Set(['VSAM', 'OSAM', 'BSAM']);
|
|
7
|
+
const FIELD_TYPES = new Set(['C', 'X', 'P', 'Z', 'H', 'F', 'G']);
|
|
8
|
+
|
|
9
|
+
function parseDBD(st, ops) {
|
|
10
|
+
const name = ops.keywords.get('NAME');
|
|
11
|
+
if (!name) throw new ImsSyntax('DBD requires NAME');
|
|
12
|
+
const accessRaw = ops.keywords.get('ACCESS');
|
|
13
|
+
let access = null;
|
|
14
|
+
if (accessRaw) {
|
|
15
|
+
const items = sublist(accessRaw);
|
|
16
|
+
if (!items || items.length < 1 || items.length > 3) throw new ImsSyntax('DBD ACCESS must be a type or (type, method[,prot])');
|
|
17
|
+
const type = items[0].toUpperCase();
|
|
18
|
+
if (!ACCESS_TYPES.has(type)) throw new ImsSyntax(`DBD ACCESS type ${type} is not recognised`);
|
|
19
|
+
let method = null;
|
|
20
|
+
if (items.length >= 2) {
|
|
21
|
+
method = items[1].toUpperCase();
|
|
22
|
+
if (!ACCESS_METHODS.has(method)) throw new ImsSyntax(`DBD ACCESS method ${method} is not recognised`);
|
|
23
|
+
}
|
|
24
|
+
access = { type, method };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let passwd = null;
|
|
28
|
+
const passwdRaw = ops.keywords.get('PASSWD');
|
|
29
|
+
if (passwdRaw) {
|
|
30
|
+
const p = passwdRaw.toUpperCase();
|
|
31
|
+
if (p !== 'YES' && p !== 'NO') throw new ImsSyntax('DBD PASSWD must be YES or NO');
|
|
32
|
+
passwd = p;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let rmname = null;
|
|
36
|
+
const rmnameRaw = ops.keywords.get('RMNAME');
|
|
37
|
+
if (rmnameRaw) {
|
|
38
|
+
const items = sublist(rmnameRaw);
|
|
39
|
+
if (!items || items.length < 1 || items.length > 4) throw new ImsSyntax('DBD RMNAME must be (module[,anchors[,rbn[,bytes]]])');
|
|
40
|
+
rmname = items;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const keywords = {};
|
|
44
|
+
for (const [k, v] of ops.keywords) keywords[k] = v;
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
kind: 'DBD',
|
|
48
|
+
name,
|
|
49
|
+
access,
|
|
50
|
+
passwd,
|
|
51
|
+
rmname,
|
|
52
|
+
keywords
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function parseDATASET(st, ops) {
|
|
57
|
+
const keywords = {};
|
|
58
|
+
for (const [k, v] of ops.keywords) keywords[k] = v;
|
|
59
|
+
|
|
60
|
+
const dd1 = ops.keywords.get('DD1');
|
|
61
|
+
const dd2 = ops.keywords.get('DD2');
|
|
62
|
+
const ovflw = ops.keywords.get('OVFLW');
|
|
63
|
+
const device = ops.keywords.get('DEVICE');
|
|
64
|
+
const sizeRaw = ops.keywords.get('SIZE');
|
|
65
|
+
let size = null;
|
|
66
|
+
if (sizeRaw) {
|
|
67
|
+
const items = sublist(sizeRaw);
|
|
68
|
+
if (items) size = items;
|
|
69
|
+
else size = [sizeRaw];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
kind: 'DATASET',
|
|
74
|
+
label: st.name,
|
|
75
|
+
dd1,
|
|
76
|
+
dd2,
|
|
77
|
+
ovflw,
|
|
78
|
+
device,
|
|
79
|
+
size,
|
|
80
|
+
keywords
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function parseAREA(st, ops) {
|
|
85
|
+
const keywords = {};
|
|
86
|
+
for (const [k, v] of ops.keywords) keywords[k] = v;
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
kind: 'AREA',
|
|
90
|
+
keywords
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function parseSEGM(st, ops) {
|
|
95
|
+
const name = ops.keywords.get('NAME');
|
|
96
|
+
if (!name) throw new ImsSyntax('SEGM requires NAME');
|
|
97
|
+
|
|
98
|
+
const parentRaw = ops.keywords.get('PARENT');
|
|
99
|
+
let parent = null;
|
|
100
|
+
if (parentRaw) {
|
|
101
|
+
const p = parentRaw.trim();
|
|
102
|
+
if (p === '0') {
|
|
103
|
+
parent = null;
|
|
104
|
+
} else {
|
|
105
|
+
const items = sublist(p);
|
|
106
|
+
if (items && items.length === 1) {
|
|
107
|
+
parent = items[0];
|
|
108
|
+
} else {
|
|
109
|
+
parent = p;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const bytesRaw = ops.keywords.get('BYTES');
|
|
115
|
+
let bytes = null;
|
|
116
|
+
if (bytesRaw) {
|
|
117
|
+
const items = sublist(bytesRaw);
|
|
118
|
+
if (items) {
|
|
119
|
+
if (items.length === 1) {
|
|
120
|
+
const n = integer(items[0]);
|
|
121
|
+
if (n === null) throw new ImsSyntax('SEGM BYTES must be an integer or (max,min)');
|
|
122
|
+
bytes = { max: n, min: n };
|
|
123
|
+
} else if (items.length === 2) {
|
|
124
|
+
const max = integer(items[0]);
|
|
125
|
+
const min = integer(items[1]);
|
|
126
|
+
if (max === null || min === null) throw new ImsSyntax('SEGM BYTES (max,min) must be integers');
|
|
127
|
+
bytes = { max, min };
|
|
128
|
+
} else {
|
|
129
|
+
throw new ImsSyntax('SEGM BYTES must be an integer or (max,min)');
|
|
130
|
+
}
|
|
131
|
+
} else {
|
|
132
|
+
const n = integer(bytesRaw);
|
|
133
|
+
if (n === null) throw new ImsSyntax('SEGM BYTES must be an integer or (max,min)');
|
|
134
|
+
bytes = { max: n, min: n };
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const rulesRaw = ops.keywords.get('RULES');
|
|
139
|
+
let rules = null;
|
|
140
|
+
if (rulesRaw) {
|
|
141
|
+
rules = sublist(rulesRaw);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const pointerRaw = ops.keywords.get('POINTER');
|
|
145
|
+
let pointer = null;
|
|
146
|
+
if (pointerRaw) {
|
|
147
|
+
const items = sublist(pointerRaw);
|
|
148
|
+
if (items) pointer = items;
|
|
149
|
+
else pointer = pointerRaw;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const keywords = {};
|
|
153
|
+
for (const [k, v] of ops.keywords) keywords[k] = v;
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
kind: 'SEGM',
|
|
157
|
+
name,
|
|
158
|
+
parent,
|
|
159
|
+
bytes,
|
|
160
|
+
rules,
|
|
161
|
+
pointer,
|
|
162
|
+
keywords
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function parseFIELD(st, ops) {
|
|
167
|
+
const nameRaw = ops.keywords.get('NAME');
|
|
168
|
+
if (!nameRaw) throw new ImsSyntax('FIELD requires NAME');
|
|
169
|
+
|
|
170
|
+
const items = sublist(nameRaw);
|
|
171
|
+
let name, seq = false, unique = null;
|
|
172
|
+
if (items) {
|
|
173
|
+
name = items[0];
|
|
174
|
+
if (items.length >= 2) {
|
|
175
|
+
const seqFlag = items[1].toUpperCase();
|
|
176
|
+
if (seqFlag === 'SEQ') seq = true;
|
|
177
|
+
else throw new ImsSyntax('FIELD NAME sublist second element must be SEQ');
|
|
178
|
+
}
|
|
179
|
+
if (items.length >= 3) {
|
|
180
|
+
const uFlag = items[2].toUpperCase();
|
|
181
|
+
if (uFlag === 'U') unique = true;
|
|
182
|
+
else if (uFlag === 'M') unique = false;
|
|
183
|
+
else throw new ImsSyntax('FIELD NAME sublist third element must be U or M');
|
|
184
|
+
}
|
|
185
|
+
} else {
|
|
186
|
+
name = nameRaw;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const bytesRaw = ops.keywords.get('BYTES');
|
|
190
|
+
let bytes = null;
|
|
191
|
+
if (bytesRaw) {
|
|
192
|
+
const n = integer(bytesRaw);
|
|
193
|
+
if (n === null) throw new ImsSyntax('FIELD BYTES must be an integer');
|
|
194
|
+
bytes = n;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const startRaw = ops.keywords.get('START');
|
|
198
|
+
let start = null;
|
|
199
|
+
if (startRaw) {
|
|
200
|
+
const n = integer(startRaw);
|
|
201
|
+
if (n === null) throw new ImsSyntax('FIELD START must be an integer');
|
|
202
|
+
start = n;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const typeRaw = ops.keywords.get('TYPE');
|
|
206
|
+
let type = 'C';
|
|
207
|
+
if (typeRaw) {
|
|
208
|
+
const t = typeRaw.toUpperCase();
|
|
209
|
+
if (!FIELD_TYPES.has(t)) throw new ImsSyntax(`FIELD TYPE ${t} is not recognised`);
|
|
210
|
+
type = t;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const keywords = {};
|
|
214
|
+
for (const [k, v] of ops.keywords) keywords[k] = v;
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
kind: 'FIELD',
|
|
218
|
+
name,
|
|
219
|
+
seq,
|
|
220
|
+
unique,
|
|
221
|
+
bytes,
|
|
222
|
+
start,
|
|
223
|
+
type,
|
|
224
|
+
keywords
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function parseLCHILD(st, ops) {
|
|
229
|
+
const nameRaw = ops.keywords.get('NAME');
|
|
230
|
+
if (!nameRaw) throw new ImsSyntax('LCHILD requires NAME');
|
|
231
|
+
|
|
232
|
+
const items = sublist(nameRaw);
|
|
233
|
+
let name, dbd = null;
|
|
234
|
+
if (items) {
|
|
235
|
+
name = items[0];
|
|
236
|
+
if (items.length >= 2) dbd = items[1];
|
|
237
|
+
} else {
|
|
238
|
+
name = nameRaw;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const keywords = {};
|
|
242
|
+
for (const [k, v] of ops.keywords) keywords[k] = v;
|
|
243
|
+
|
|
244
|
+
return {
|
|
245
|
+
kind: 'LCHILD',
|
|
246
|
+
name,
|
|
247
|
+
dbd,
|
|
248
|
+
keywords
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function parseXDFLD(st, ops) {
|
|
253
|
+
const keywords = {};
|
|
254
|
+
for (const [k, v] of ops.keywords) keywords[k] = v;
|
|
255
|
+
|
|
256
|
+
return {
|
|
257
|
+
kind: 'XDFLD',
|
|
258
|
+
keywords
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function parseDBDGEN(st, ops) {
|
|
263
|
+
const keywords = {};
|
|
264
|
+
for (const [k, v] of ops.keywords) keywords[k] = v;
|
|
265
|
+
|
|
266
|
+
return {
|
|
267
|
+
kind: 'DBDGEN',
|
|
268
|
+
keywords
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const KEYWORDS = {
|
|
273
|
+
DBD: ['NAME', 'ACCESS', 'RMNAME', 'PASSWD', 'VERSION', 'DATXEXIT', 'EXIT', 'PSNAME', 'GSROOT', 'ENCODING', 'FPINDEX'],
|
|
274
|
+
DATASET: ['DD1', 'DD2', 'OVFLW', 'DEVICE', 'BLOCK', 'SIZE', 'SCAN', 'FRSPC', 'RECORD', 'RECFM', 'MODEL', 'SEARCHA', 'REL'],
|
|
275
|
+
AREA: ['DD1', 'MODEL', 'SIZE', 'UOW', 'ROOT', 'REMOTE'],
|
|
276
|
+
SEGM: ['NAME', 'PARENT', 'BYTES', 'FREQ', 'POINTER', 'PTR', 'RULES', 'COMPRTN', 'EXIT', 'SOURCE', 'SSPTR', 'TYPE', 'DSGROUP'],
|
|
277
|
+
FIELD: ['NAME', 'BYTES', 'START', 'TYPE'],
|
|
278
|
+
LCHILD: ['NAME', 'POINTER', 'PTR', 'PAIR', 'RULES', 'INDEX', 'RKSIZE'],
|
|
279
|
+
XDFLD: ['NAME', 'SEGMENT', 'SRCH', 'SUBSEQ', 'DDATA', 'NULLVAL', 'CONST', 'EXTRTN'],
|
|
280
|
+
DBDGEN: [],
|
|
281
|
+
};
|
|
282
|
+
// Operands that end in a comma were meant to go on, but the next card was not read as their
|
|
283
|
+
// continuation (no column-72 mark), so the operands that follow never reached the parser.
|
|
284
|
+
const checked = (macro, parse) => (st, ops) => {
|
|
285
|
+
if (/,\s*$/.test(st.field)) throw new ImsSyntax(`${macro} operands end in a comma: the operands meant to follow were not read as a continuation`);
|
|
286
|
+
onlyKeywords(ops, macro, KEYWORDS[macro]);
|
|
287
|
+
return parse(st, ops);
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
export const parsers = Object.freeze({
|
|
291
|
+
DBD: checked('DBD', parseDBD),
|
|
292
|
+
DATASET: checked('DATASET', parseDATASET),
|
|
293
|
+
AREA: checked('AREA', parseAREA),
|
|
294
|
+
SEGM: checked('SEGM', parseSEGM),
|
|
295
|
+
FIELD: checked('FIELD', parseFIELD),
|
|
296
|
+
LCHILD: checked('LCHILD', parseLCHILD),
|
|
297
|
+
XDFLD: checked('XDFLD', parseXDFLD),
|
|
298
|
+
DBDGEN: checked('DBDGEN', parseDBDGEN)
|
|
299
|
+
});
|