@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
|
@@ -1,142 +1,184 @@
|
|
|
1
1
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
2
|
// Parsers for z/OS program-management macros: LINK, XCTL, LOAD, ATTACH.
|
|
3
|
-
import { HlasmSyntax, NAME } from '../operands.mjs';
|
|
3
|
+
import { HlasmSyntax, NAME, remoteForm, splitOperands } from '../operands.mjs';
|
|
4
|
+
import { parseAddress } from '../expr.mjs';
|
|
5
|
+
import { MVS38_HIARCHY, MVS38_KEYWORDS, mvs38Form } from '../mvs38.mjs';
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
+
// An RX-type or A-type address, a register in parentheses, a literal or an absolute expression, as
|
|
8
|
+
// the macros' operands are written: anything the address reader takes.
|
|
9
|
+
const isAddress = (text) => {
|
|
10
|
+
const t = String(text).trim();
|
|
11
|
+
if (t === '') return false;
|
|
12
|
+
try { parseAddress(t); return true; } catch { return false; }
|
|
13
|
+
};
|
|
7
14
|
const YES_NO = /^(?:YES|NO)$/i;
|
|
8
15
|
|
|
16
|
+
// STAI= and ESTAI= of ATTACH: an exit address, or (exit addr,parm addr).
|
|
17
|
+
const isExit = (text) => {
|
|
18
|
+
const t = String(text).trim();
|
|
19
|
+
if (isAddress(t)) return true;
|
|
20
|
+
if (!t.startsWith('(') || !t.endsWith(')')) return false;
|
|
21
|
+
const parts = splitOperands(t.slice(1, -1));
|
|
22
|
+
return parts.length <= 2 && parts.every(isAddress);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// The list form (SF=L) and the execute form (MF=(E,addr), SF=(E,addr)) of LINK and XCTL (z/OS 3.1
|
|
26
|
+
// MVS Programming: Assembler Services Reference, Volume 2). The list form may leave the entry point
|
|
27
|
+
// to the execute form, and an execute form with SF=(E,addr) may take it from the list.
|
|
28
|
+
function forms(kw, macro) {
|
|
29
|
+
const sf = kw.has('SF') ? remoteForm(kw.get('SF'), 'SF') : null;
|
|
30
|
+
const mf = kw.has('MF') ? remoteForm(kw.get('MF'), 'MF') : null;
|
|
31
|
+
if (mf && mf.form !== 'E') throw new HlasmSyntax(`${macro} takes MF only as (E,addr)`);
|
|
32
|
+
if (sf && sf.form === 'L' && (mf || kw.has('PARAM'))) throw new HlasmSyntax(`the list form of ${macro} takes no MF or PARAM`);
|
|
33
|
+
const out = {};
|
|
34
|
+
if (sf) out.sf = sf.form === 'L' ? 'L' : { form: 'E', list: sf.list };
|
|
35
|
+
if (mf) out.mf = { form: 'E', list: mf.list };
|
|
36
|
+
return { out, entryOptional: Boolean(sf) };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// PARAM=(addr,addr,...): an execute form may leave an entry empty, to keep the list form's.
|
|
40
|
+
// A list form (SF=L) may write a keyword with no value, reserving its field for the execute form.
|
|
41
|
+
function written(keywords) {
|
|
42
|
+
if (!/^L$/i.test(String(keywords.get('SF') || '').trim())) return keywords;
|
|
43
|
+
return new Map([...keywords].filter(([, v]) => String(v).trim() !== ''));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function parseParam(raw, execute = false) {
|
|
47
|
+
const p = raw.trim();
|
|
48
|
+
if (p.startsWith('(') && p.endsWith(')')) {
|
|
49
|
+
const inner = p.slice(1, -1).trim();
|
|
50
|
+
if (inner !== '') {
|
|
51
|
+
for (const part of splitOperands(inner).map(s => s.trim())) {
|
|
52
|
+
if (part === '' ? !execute : !isAddress(part)) throw new HlasmSyntax('invalid PARAM address');
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
} else if (!isAddress(p)) {
|
|
56
|
+
throw new HlasmSyntax('invalid PARAM address');
|
|
57
|
+
}
|
|
58
|
+
return p;
|
|
59
|
+
}
|
|
60
|
+
|
|
9
61
|
function parseLink(st, ops) {
|
|
10
|
-
const kw = ops.keywords;
|
|
11
|
-
const allowed = new Set(['EP', 'EPLOC', 'DE', 'DCB', 'PARAM', 'VL', 'ID', 'ERRET', 'LSEARCH']);
|
|
62
|
+
const kw = written(ops.keywords);
|
|
63
|
+
const allowed = new Set(['EP', 'EPLOC', 'DE', 'DCB', 'PARAM', 'VL', 'ID', 'ERRET', 'LSEARCH', 'SF', 'MF']);
|
|
12
64
|
for (const k of kw.keys()) if (!allowed.has(k)) throw new HlasmSyntax(`unknown keyword ${k}`);
|
|
13
|
-
if (ops.positional.
|
|
65
|
+
if (ops.positional.some((p) => p.trim() !== '')) throw new HlasmSyntax('unexpected positional operand');
|
|
66
|
+
const { out: form, entryOptional } = forms(kw, 'LINK');
|
|
14
67
|
const epKeys = ['EP', 'EPLOC', 'DE'].filter(k => kw.has(k));
|
|
15
68
|
if (epKeys.length > 1) throw new HlasmSyntax('EP, EPLOC, and DE are mutually exclusive');
|
|
16
|
-
if (epKeys.length === 0) throw new HlasmSyntax('missing EP, EPLOC, or DE');
|
|
69
|
+
if (epKeys.length === 0 && !entryOptional) throw new HlasmSyntax('missing EP, EPLOC, or DE');
|
|
17
70
|
const ep = kw.has('EP') ? kw.get('EP').trim().toUpperCase() : null;
|
|
18
71
|
const eploc = kw.has('EPLOC') ? kw.get('EPLOC') : null;
|
|
19
72
|
const de = kw.has('DE') ? kw.get('DE') : null;
|
|
20
73
|
if (ep && !NAME.test(ep)) throw new HlasmSyntax('invalid EP name');
|
|
21
|
-
if (eploc && !
|
|
22
|
-
if (de && !
|
|
23
|
-
if (kw.has('DCB') && !
|
|
24
|
-
if (kw.has('ERRET') && !
|
|
74
|
+
if (eploc && !isAddress(eploc.trim())) throw new HlasmSyntax('invalid EPLOC address');
|
|
75
|
+
if (de && !isAddress(de.trim())) throw new HlasmSyntax('invalid DE address');
|
|
76
|
+
if (kw.has('DCB') && !isAddress(kw.get('DCB').trim())) throw new HlasmSyntax('invalid DCB address');
|
|
77
|
+
if (kw.has('ERRET') && !isAddress(kw.get('ERRET').trim())) throw new HlasmSyntax('invalid ERRET address');
|
|
25
78
|
if (kw.has('LSEARCH') && !YES_NO.test(kw.get('LSEARCH').trim())) throw new HlasmSyntax('invalid LSEARCH value');
|
|
26
79
|
if (kw.has('VL') && !/^\d+$/.test(kw.get('VL').trim())) throw new HlasmSyntax('invalid VL value');
|
|
27
80
|
if (kw.has('ID') && !/^\d+$/.test(kw.get('ID').trim())) throw new HlasmSyntax('invalid ID value');
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const p = kw.get('PARAM').trim();
|
|
31
|
-
if (p.startsWith('(') && p.endsWith(')')) {
|
|
32
|
-
const inner = p.slice(1, -1).trim();
|
|
33
|
-
if (inner !== '') {
|
|
34
|
-
const parts = inner.split(',').map(s => s.trim());
|
|
35
|
-
for (const part of parts) {
|
|
36
|
-
if (!ADDR.test(part)) throw new HlasmSyntax('invalid PARAM address');
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
} else if (!ADDR.test(p)) {
|
|
40
|
-
throw new HlasmSyntax('invalid PARAM address');
|
|
41
|
-
}
|
|
42
|
-
param = p;
|
|
43
|
-
}
|
|
44
|
-
return { kind: 'LINK', ep, eploc, de, dcb: kw.has('DCB') ? kw.get('DCB') : null, param, keywords: Object.fromEntries(kw) };
|
|
81
|
+
const param = kw.has('PARAM') ? parseParam(kw.get('PARAM'), Boolean(form.mf)) : null;
|
|
82
|
+
return { kind: 'LINK', ep, eploc, de, dcb: kw.has('DCB') ? kw.get('DCB') : null, param, ...form, keywords: Object.fromEntries(kw) };
|
|
45
83
|
}
|
|
46
84
|
|
|
47
85
|
function parseXctl(st, ops) {
|
|
48
|
-
const kw = ops.keywords;
|
|
49
|
-
const allowed = new Set(['EP', 'EPLOC', 'DE', 'DCB', 'LSEARCH']);
|
|
86
|
+
const kw = written(ops.keywords);
|
|
87
|
+
const allowed = new Set(['EP', 'EPLOC', 'DE', 'DCB', 'LSEARCH', 'SF', 'MF', 'PARAM', 'VL']);
|
|
50
88
|
for (const k of kw.keys()) if (!allowed.has(k)) throw new HlasmSyntax(`unknown keyword ${k}`);
|
|
89
|
+
const { out: form, entryOptional } = forms(kw, 'XCTL');
|
|
90
|
+
// XCTL builds a parameter list only in its execute form, at MF=(E,addr).
|
|
91
|
+
if ((kw.has('PARAM') || kw.has('VL')) && !form.mf) throw new HlasmSyntax('XCTL takes PARAM and VL only with MF=(E,addr)');
|
|
92
|
+
if (kw.has('VL') && !/^\d+$/.test(kw.get('VL').trim())) throw new HlasmSyntax('invalid VL value');
|
|
51
93
|
// The registers to restore before control passes: (reg) or (reg1,reg2), numbers or EQU symbols.
|
|
52
94
|
if (ops.positional.length > 1) throw new HlasmSyntax('too many positional operands');
|
|
53
95
|
if (ops.positional.length === 1 && ops.positional[0].trim() !== '' && !/^\(\s*[A-Z0-9$#@_]+\s*(,\s*[A-Z0-9$#@_]+\s*)?\)$/i.test(ops.positional[0].trim())) throw new HlasmSyntax('invalid register list');
|
|
54
96
|
const epKeys = ['EP', 'EPLOC', 'DE'].filter(k => kw.has(k));
|
|
55
97
|
if (epKeys.length > 1) throw new HlasmSyntax('EP, EPLOC, and DE are mutually exclusive');
|
|
56
|
-
if (epKeys.length === 0) throw new HlasmSyntax('missing EP, EPLOC, or DE');
|
|
98
|
+
if (epKeys.length === 0 && !entryOptional) throw new HlasmSyntax('missing EP, EPLOC, or DE');
|
|
57
99
|
const ep = kw.has('EP') ? kw.get('EP').trim().toUpperCase() : null;
|
|
58
100
|
const eploc = kw.has('EPLOC') ? kw.get('EPLOC') : null;
|
|
59
101
|
const de = kw.has('DE') ? kw.get('DE') : null;
|
|
60
102
|
if (ep && !NAME.test(ep)) throw new HlasmSyntax('invalid EP name');
|
|
61
|
-
if (eploc && !
|
|
62
|
-
if (de && !
|
|
63
|
-
if (kw.has('DCB') && !
|
|
103
|
+
if (eploc && !isAddress(eploc.trim())) throw new HlasmSyntax('invalid EPLOC address');
|
|
104
|
+
if (de && !isAddress(de.trim())) throw new HlasmSyntax('invalid DE address');
|
|
105
|
+
if (kw.has('DCB') && !isAddress(kw.get('DCB').trim())) throw new HlasmSyntax('invalid DCB address');
|
|
64
106
|
if (kw.has('LSEARCH') && !YES_NO.test(kw.get('LSEARCH').trim())) throw new HlasmSyntax('invalid LSEARCH value');
|
|
65
|
-
|
|
107
|
+
const param = kw.has('PARAM') ? parseParam(kw.get('PARAM'), true) : null;
|
|
108
|
+
return { kind: 'XCTL', ep, eploc, de, dcb: kw.has('DCB') ? kw.get('DCB') : null, ...(param ? { param } : {}), ...form, keywords: Object.fromEntries(kw) };
|
|
66
109
|
}
|
|
67
110
|
|
|
68
111
|
function parseLoad(st, ops) {
|
|
69
|
-
const kw = ops.keywords;
|
|
70
|
-
|
|
112
|
+
const kw = written(ops.keywords);
|
|
113
|
+
// The standard, list and execute forms, authorized keywords included (z/OS 3.1 MVS Programming:
|
|
114
|
+
// Authorized Assembler Services Reference, Volume 3, LOAD).
|
|
115
|
+
const allowed = new Set(['EP', 'EPLOC', 'DE', 'DCB', 'ERRET', 'LSEARCH', 'LOADPT', 'LOADPT64', 'EXTINFO', 'RELATED',
|
|
116
|
+
'ADDR', 'ADDR64', 'ADRNAPF', 'ADRNAPF64', 'ARCHLVL', 'EOM', 'FETCHOPT', 'GLOBAL', 'PLISTVER', 'RMODE', 'SF']);
|
|
71
117
|
for (const k of kw.keys()) if (!allowed.has(k)) throw new HlasmSyntax(`unknown keyword ${k}`);
|
|
72
|
-
if (ops.positional.
|
|
118
|
+
if (ops.positional.some((p) => p.trim() !== '')) throw new HlasmSyntax('unexpected positional operand');
|
|
119
|
+
const { out: form, entryOptional } = forms(kw, 'LOAD');
|
|
73
120
|
const epKeys = ['EP', 'EPLOC', 'DE'].filter(k => kw.has(k));
|
|
74
121
|
if (epKeys.length > 1) throw new HlasmSyntax('EP, EPLOC, and DE are mutually exclusive');
|
|
75
|
-
if (epKeys.length === 0) throw new HlasmSyntax('missing EP, EPLOC, or DE');
|
|
122
|
+
if (epKeys.length === 0 && !entryOptional) throw new HlasmSyntax('missing EP, EPLOC, or DE');
|
|
76
123
|
const ep = kw.has('EP') ? kw.get('EP').trim().toUpperCase() : null;
|
|
77
124
|
const eploc = kw.has('EPLOC') ? kw.get('EPLOC') : null;
|
|
78
125
|
const de = kw.has('DE') ? kw.get('DE') : null;
|
|
79
126
|
if (ep && !NAME.test(ep)) throw new HlasmSyntax('invalid EP name');
|
|
80
|
-
if (eploc && !
|
|
81
|
-
if (de && !
|
|
82
|
-
if (kw.has('DCB') && !
|
|
83
|
-
if (kw.has('ERRET') && !
|
|
127
|
+
if (eploc && !isAddress(eploc.trim())) throw new HlasmSyntax('invalid EPLOC address');
|
|
128
|
+
if (de && !isAddress(de.trim())) throw new HlasmSyntax('invalid DE address');
|
|
129
|
+
if (kw.has('DCB') && !isAddress(kw.get('DCB').trim())) throw new HlasmSyntax('invalid DCB address');
|
|
130
|
+
if (kw.has('ERRET') && !isAddress(kw.get('ERRET').trim())) throw new HlasmSyntax('invalid ERRET address');
|
|
84
131
|
if (kw.has('LSEARCH') && !YES_NO.test(kw.get('LSEARCH').trim())) throw new HlasmSyntax('invalid LSEARCH value');
|
|
85
|
-
if (kw.has('LOADPT') && !
|
|
86
|
-
if (kw.has('LOADPT64') && !
|
|
87
|
-
if (kw.has('EXTINFO') && !
|
|
88
|
-
return { kind: 'LOAD', ep, eploc, de, dcb: kw.has('DCB') ? kw.get('DCB') : null, keywords: Object.fromEntries(kw) };
|
|
132
|
+
if (kw.has('LOADPT') && !isAddress(kw.get('LOADPT').trim())) throw new HlasmSyntax('invalid LOADPT address');
|
|
133
|
+
if (kw.has('LOADPT64') && !isAddress(kw.get('LOADPT64').trim())) throw new HlasmSyntax('invalid LOADPT64 address');
|
|
134
|
+
if (kw.has('EXTINFO') && !isAddress(kw.get('EXTINFO').trim())) throw new HlasmSyntax('invalid EXTINFO address');
|
|
135
|
+
return { kind: 'LOAD', ep, eploc, de, dcb: kw.has('DCB') ? kw.get('DCB') : null, ...form, keywords: Object.fromEntries(kw) };
|
|
89
136
|
}
|
|
90
137
|
|
|
91
|
-
function parseAttach(st, ops) {
|
|
92
|
-
const kw = ops.keywords;
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
138
|
+
function parseAttach(st, ops, opts) {
|
|
139
|
+
const kw = written(ops.keywords);
|
|
140
|
+
// The standard, list and execute forms, authorized keywords included (z/OS 3.1 MVS Programming:
|
|
141
|
+
// Authorized Assembler Services Reference, Volume 1, ATTACH and ATTACHX).
|
|
142
|
+
const allowed = new Set(['EP', 'EPLOC', 'DE', 'DCB', 'LPMOD', 'DPMOD', 'PARAM', 'VL', 'ECB', 'ETXR', 'GSPV', 'GSPL', 'SHSPV', 'SHSPL', 'SZERO', 'TASKLIB', 'STAI', 'ESTAI', 'PURGE', 'ASYNCH', 'TERM', 'ALCOPY', 'RELATED',
|
|
143
|
+
'ADDRENV', 'ASCENV', 'BYADDRESS', 'DISP', 'JSTCB', 'KEY', 'MF', 'NSHSPL', 'NSHSPV', 'PKM', 'PLIST4', 'PLIST8', 'PLIST8ARALETS', 'PLISTARALETS', 'RSAPF', 'SDWALOC31', 'SF', 'SM', 'SVAREA', 'TCB', 'TID']);
|
|
144
|
+
const legacy = {};
|
|
145
|
+
for (const k of kw.keys()) {
|
|
146
|
+
if (MVS38_KEYWORDS.ATTACH.has(k)) mvs38Form(opts, legacy, 'ATTACH', `${k}=${kw.get(k).trim()}`);
|
|
147
|
+
else if (!allowed.has(k)) throw new HlasmSyntax(`unknown keyword ${k}`);
|
|
148
|
+
}
|
|
149
|
+
if (kw.has('HIARCHY') && !MVS38_HIARCHY.test(kw.get('HIARCHY').trim())) throw new HlasmSyntax('invalid HIARCHY value');
|
|
150
|
+
if (kw.has('JSCB') && !isAddress(kw.get('JSCB').trim())) throw new HlasmSyntax('invalid JSCB address');
|
|
151
|
+
if (ops.positional.some((p) => p.trim() !== '')) throw new HlasmSyntax('unexpected positional operand');
|
|
152
|
+
const { out: form, entryOptional } = forms(kw, 'ATTACH');
|
|
96
153
|
const epKeys = ['EP', 'EPLOC', 'DE'].filter(k => kw.has(k));
|
|
97
154
|
if (epKeys.length > 1) throw new HlasmSyntax('EP, EPLOC, and DE are mutually exclusive');
|
|
98
|
-
if (epKeys.length === 0) throw new HlasmSyntax('missing EP, EPLOC, or DE');
|
|
155
|
+
if (epKeys.length === 0 && !entryOptional) throw new HlasmSyntax('missing EP, EPLOC, or DE');
|
|
99
156
|
const ep = kw.has('EP') ? kw.get('EP').trim().toUpperCase() : null;
|
|
100
157
|
const eploc = kw.has('EPLOC') ? kw.get('EPLOC') : null;
|
|
101
158
|
const de = kw.has('DE') ? kw.get('DE') : null;
|
|
102
159
|
if (ep && !NAME.test(ep)) throw new HlasmSyntax('invalid EP name');
|
|
103
|
-
if (eploc && !
|
|
104
|
-
if (de && !
|
|
105
|
-
if (kw.has('DCB') && !
|
|
106
|
-
if (kw.has('LPMOD') && !
|
|
107
|
-
if (kw.has('DPMOD') && !
|
|
160
|
+
if (eploc && !isAddress(eploc.trim())) throw new HlasmSyntax('invalid EPLOC address');
|
|
161
|
+
if (de && !isAddress(de.trim())) throw new HlasmSyntax('invalid DE address');
|
|
162
|
+
if (kw.has('DCB') && !isAddress(kw.get('DCB').trim())) throw new HlasmSyntax('invalid DCB address');
|
|
163
|
+
if (kw.has('LPMOD') && !isAddress(kw.get('LPMOD').trim())) throw new HlasmSyntax('invalid LPMOD value');
|
|
164
|
+
if (kw.has('DPMOD') && !isAddress(kw.get('DPMOD').trim())) throw new HlasmSyntax('invalid DPMOD value');
|
|
108
165
|
if (kw.has('VL') && !/^\d+$/.test(kw.get('VL').trim())) throw new HlasmSyntax('invalid VL value');
|
|
109
|
-
if (kw.has('ECB') && !
|
|
110
|
-
if (kw.has('ETXR') && !
|
|
111
|
-
if (kw.has('GSPV') && !
|
|
112
|
-
if (kw.has('GSPL') && !
|
|
113
|
-
if (kw.has('SHSPV') && !
|
|
114
|
-
if (kw.has('SHSPL') && !
|
|
166
|
+
if (kw.has('ECB') && !isAddress(kw.get('ECB').trim())) throw new HlasmSyntax('invalid ECB address');
|
|
167
|
+
if (kw.has('ETXR') && !isAddress(kw.get('ETXR').trim())) throw new HlasmSyntax('invalid ETXR address');
|
|
168
|
+
if (kw.has('GSPV') && !isAddress(kw.get('GSPV').trim())) throw new HlasmSyntax('invalid GSPV value');
|
|
169
|
+
if (kw.has('GSPL') && !isAddress(kw.get('GSPL').trim())) throw new HlasmSyntax('invalid GSPL address');
|
|
170
|
+
if (kw.has('SHSPV') && !isAddress(kw.get('SHSPV').trim())) throw new HlasmSyntax('invalid SHSPV value');
|
|
171
|
+
if (kw.has('SHSPL') && !isAddress(kw.get('SHSPL').trim())) throw new HlasmSyntax('invalid SHSPL address');
|
|
115
172
|
if (kw.has('SZERO') && !YES_NO.test(kw.get('SZERO').trim())) throw new HlasmSyntax('invalid SZERO value');
|
|
116
|
-
if (kw.has('TASKLIB') && !
|
|
117
|
-
if (kw.has('STAI') && !
|
|
118
|
-
if (kw.has('ESTAI') && !
|
|
173
|
+
if (kw.has('TASKLIB') && !isAddress(kw.get('TASKLIB').trim())) throw new HlasmSyntax('invalid TASKLIB address');
|
|
174
|
+
if (kw.has('STAI') && !isExit(kw.get('STAI'))) throw new HlasmSyntax('invalid STAI address');
|
|
175
|
+
if (kw.has('ESTAI') && !isExit(kw.get('ESTAI'))) throw new HlasmSyntax('invalid ESTAI address');
|
|
119
176
|
if (kw.has('PURGE') && !/^(?:QUIESCE|NONE|HALT)$/i.test(kw.get('PURGE').trim())) throw new HlasmSyntax('invalid PURGE value');
|
|
120
177
|
if (kw.has('ASYNCH') && !YES_NO.test(kw.get('ASYNCH').trim())) throw new HlasmSyntax('invalid ASYNCH value');
|
|
121
178
|
if (kw.has('TERM') && !YES_NO.test(kw.get('TERM').trim())) throw new HlasmSyntax('invalid TERM value');
|
|
122
179
|
if (kw.has('ALCOPY') && !YES_NO.test(kw.get('ALCOPY').trim())) throw new HlasmSyntax('invalid ALCOPY value');
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
const p = kw.get('PARAM').trim();
|
|
126
|
-
if (p.startsWith('(') && p.endsWith(')')) {
|
|
127
|
-
const inner = p.slice(1, -1).trim();
|
|
128
|
-
if (inner !== '') {
|
|
129
|
-
const parts = inner.split(',').map(s => s.trim());
|
|
130
|
-
for (const part of parts) {
|
|
131
|
-
if (!ADDR.test(part)) throw new HlasmSyntax('invalid PARAM address');
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
} else if (!ADDR.test(p)) {
|
|
135
|
-
throw new HlasmSyntax('invalid PARAM address');
|
|
136
|
-
}
|
|
137
|
-
param = p;
|
|
138
|
-
}
|
|
139
|
-
return { kind: 'ATTACH', ep, eploc, de, dcb: kw.has('DCB') ? kw.get('DCB') : null, param, keywords: Object.fromEntries(kw) };
|
|
180
|
+
const param = kw.has('PARAM') ? parseParam(kw.get('PARAM'), Boolean(form.mf)) : null;
|
|
181
|
+
return { kind: 'ATTACH', ...legacy, ep, eploc, de, dcb: kw.has('DCB') ? kw.get('DCB') : null, param, ...form, keywords: Object.fromEntries(kw) };
|
|
140
182
|
}
|
|
141
183
|
|
|
142
184
|
export const parsers = Object.freeze({ LINK: parseLink, XCTL: parseXctl, LOAD: parseLoad, ATTACH: parseAttach });
|
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
// Parsers for the z/OS recovery and dump macros: ESTAE, ESPIE, ABEND, SNAP.
|
|
3
3
|
import { HlasmSyntax } from '../operands.mjs';
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
// The standard, list and execute forms, authorized keywords included (z/OS 3.1 MVS Programming:
|
|
6
|
+
// Authorized Assembler Services Reference, Volume 2, ESTAE and ESTAEX).
|
|
7
|
+
const ESTAE_KW = new Set(['PARAM', 'XCTL', 'PURGE', 'ASYNCH', 'TERM', 'ASCENV', 'RECORD', 'RELATED', 'MF',
|
|
8
|
+
'AMODE64', 'BRANCH', 'CANCEL', 'KEY', 'SDWALOC31', 'SPIEOVERRIDE', 'SVEAREA', 'TKNPASS', 'TOKEN']);
|
|
6
9
|
const ESPIE_KW = new Set(['PKM', 'MF']);
|
|
7
10
|
const ABEND_KW = new Set(['REASON', 'DUMPOPT', 'DUMPOPX', 'MF']);
|
|
8
11
|
const SNAP_KW = new Set(['DCB', 'TCB', 'ID', 'SDATA', 'PDATA', 'STORAGE', 'LIST', 'STRHDR', 'SUBPLST', 'DSPSTOR', 'MF']);
|
|
@@ -16,13 +19,15 @@ function checkKw(kw, allowed, op) {
|
|
|
16
19
|
function parseEstae(st, ops) {
|
|
17
20
|
const { positional, keywords } = ops;
|
|
18
21
|
checkKw(keywords, ESTAE_KW, 'ESTAE');
|
|
19
|
-
|
|
22
|
+
// The list and execute forms (MF=) may leave the exit and the CT or OV to the other form.
|
|
23
|
+
const remote = keywords.has('MF');
|
|
24
|
+
if ((positional.length < 1 && !remote) || positional.length > 3) {
|
|
20
25
|
throw new HlasmSyntax('ESTAE: expected 1 to 3 positional operands');
|
|
21
26
|
}
|
|
22
|
-
const exit = positional[0];
|
|
27
|
+
const exit = positional[0] ?? '';
|
|
23
28
|
for (let i = 1; i < positional.length; i++) {
|
|
24
|
-
const p = positional[i].toUpperCase();
|
|
25
|
-
if (p !== 'CT' && p !== 'OV') {
|
|
29
|
+
const p = positional[i].trim().toUpperCase();
|
|
30
|
+
if (p !== 'CT' && p !== 'OV' && !(remote && p === '')) {
|
|
26
31
|
throw new HlasmSyntax(`ESTAE: unexpected positional operand ${positional[i]}`);
|
|
27
32
|
}
|
|
28
33
|
}
|
|
@@ -1,9 +1,41 @@
|
|
|
1
1
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
2
|
// Parser for z/OS virtual storage macros: STORAGE, GETMAIN, and FREEMAIN.
|
|
3
|
-
import { HlasmSyntax } from '../operands.mjs';
|
|
3
|
+
import { HlasmSyntax, remoteForm } from '../operands.mjs';
|
|
4
|
+
import { MVS38_HIARCHY, MVS38_KEYWORDS, MVS38_STORAGE_MODES, mvs38Form } from '../mvs38.mjs';
|
|
4
5
|
|
|
5
6
|
const GETMAIN_MODES = new Set(['R', 'RU', 'RC', 'VU', 'VC', 'EU', 'EC', 'LU', 'LC', 'VRC', 'VRU']);
|
|
6
7
|
const FREEMAIN_MODES = new Set(['R', 'RU', 'RC', 'VU', 'VC', 'V', 'EU', 'EC', 'E', 'LU', 'LC', 'L']);
|
|
8
|
+
// The request types that have list and execute forms (MF=L, MF=(E,addr)), and those that take
|
|
9
|
+
// BRANCH=(YES,GLOBAL) (z/OS 3.1 MVS Programming: Assembler Services Reference, Volume 1, and
|
|
10
|
+
// Authorized Assembler Services Reference, Volume 2, GETMAIN and FREEMAIN). The request type is
|
|
11
|
+
// optional in both forms: the execute form takes it from the list it names.
|
|
12
|
+
const GETMAIN_LIST_MODES = new Set(['LC', 'LU', 'VC', 'VU', 'EC', 'EU']);
|
|
13
|
+
const FREEMAIN_LIST_MODES = new Set(['LC', 'LU', 'L', 'VC', 'VU', 'V', 'EC', 'EU', 'E']);
|
|
14
|
+
const GETMAIN_GLOBAL_MODES = new Set(['RC', 'RU', 'VRC', 'VRU']);
|
|
15
|
+
const FREEMAIN_GLOBAL_MODES = new Set(['RC', 'RU']);
|
|
16
|
+
const OWNERS = new Set(['HOME', 'PRIMARY', 'SECONDARY', 'SYSTEM']);
|
|
17
|
+
|
|
18
|
+
// MF=L or MF=(E,addr), and BRANCH=YES or (YES,GLOBAL), each checked against the request type.
|
|
19
|
+
function remoteAndBranch(macro, mode, keywords, listModes, globalModes) {
|
|
20
|
+
const out = {};
|
|
21
|
+
if (keywords.has('MF')) {
|
|
22
|
+
const { form, list } = remoteForm(keywords.get('MF'), 'MF');
|
|
23
|
+
if (mode && !listModes.has(mode)) throw new HlasmSyntax(`${macro} ${mode} has no list or execute form`);
|
|
24
|
+
out.mf = form === 'L' ? 'L' : { form: 'E', list };
|
|
25
|
+
}
|
|
26
|
+
if (keywords.has('BRANCH')) {
|
|
27
|
+
const v = keywords.get('BRANCH').replace(/\s+/g, '').toUpperCase();
|
|
28
|
+
if (v === '(YES,GLOBAL)') {
|
|
29
|
+
if (!globalModes.has(mode)) throw new HlasmSyntax(`${macro} takes BRANCH=(YES,GLOBAL) only with ${[...globalModes].join(', ')}`);
|
|
30
|
+
out.branch = 'GLOBAL';
|
|
31
|
+
} else if (v === 'YES' || v === '(YES)') {
|
|
32
|
+
out.branch = 'YES';
|
|
33
|
+
} else {
|
|
34
|
+
throw new HlasmSyntax(`${macro} BRANCH= value ${keywords.get('BRANCH')} is not YES or (YES,GLOBAL)`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
7
39
|
|
|
8
40
|
function getKeyword(keywords, key) {
|
|
9
41
|
const val = keywords.get(key);
|
|
@@ -42,25 +74,35 @@ function parseStorage(st, ops) {
|
|
|
42
74
|
};
|
|
43
75
|
}
|
|
44
76
|
|
|
45
|
-
function parseGetmain(st, ops) {
|
|
77
|
+
function parseGetmain(st, ops, opts) {
|
|
46
78
|
const { positional, keywords } = ops;
|
|
47
|
-
|
|
79
|
+
const mode = (positional[0] ?? '').trim().toUpperCase() || null;
|
|
80
|
+
if (!mode && !keywords.has('MF')) {
|
|
48
81
|
throw new HlasmSyntax('GETMAIN requires a request type');
|
|
49
82
|
}
|
|
50
|
-
const
|
|
51
|
-
if (
|
|
83
|
+
const legacy = {};
|
|
84
|
+
if (mode && MVS38_STORAGE_MODES.has(mode)) {
|
|
85
|
+
mvs38Form(opts, legacy, 'GETMAIN', mode);
|
|
86
|
+
} else if (mode && !GETMAIN_MODES.has(mode)) {
|
|
52
87
|
throw new HlasmSyntax(`GETMAIN request type must be one of ${[...GETMAIN_MODES].join(', ')}, got ${positional[0]}`);
|
|
53
88
|
}
|
|
54
|
-
if (positional.
|
|
89
|
+
if (positional.slice(1).some((p) => p.trim() !== '')) {
|
|
55
90
|
throw new HlasmSyntax(`GETMAIN does not accept extra positional operands: ${positional[1]}`);
|
|
56
91
|
}
|
|
57
92
|
|
|
58
|
-
const allowed = new Set(['LA', 'LV', 'A', 'SP', 'BNDRY', 'CONTBDY', 'STARTBDY', 'KEY', 'LOC', 'INADDR', 'CHECKZERO', 'RELATED']);
|
|
93
|
+
const allowed = new Set(['LA', 'LV', 'A', 'SP', 'BNDRY', 'CONTBDY', 'STARTBDY', 'KEY', 'LOC', 'INADDR', 'CHECKZERO', 'RELATED', 'MF', 'BRANCH', 'OWNER']);
|
|
59
94
|
for (const key of keywords.keys()) {
|
|
60
|
-
if (
|
|
95
|
+
if (MVS38_KEYWORDS.GETMAIN.has(key)) {
|
|
96
|
+
mvs38Form(opts, legacy, 'GETMAIN', `${key}=${keywords.get(key).trim()}`);
|
|
97
|
+
if (!MVS38_HIARCHY.test(keywords.get(key).trim())) throw new HlasmSyntax('GETMAIN HIARCHY must be 0 or 1');
|
|
98
|
+
} else if (!allowed.has(key)) {
|
|
61
99
|
throw new HlasmSyntax(`GETMAIN does not accept keyword ${key}`);
|
|
62
100
|
}
|
|
63
101
|
}
|
|
102
|
+
if (keywords.has('OWNER') && !OWNERS.has(keywords.get('OWNER').trim().toUpperCase())) {
|
|
103
|
+
throw new HlasmSyntax(`GETMAIN OWNER must be one of ${[...OWNERS].join(', ')}`);
|
|
104
|
+
}
|
|
105
|
+
const extra = remoteAndBranch('GETMAIN', mode, keywords, GETMAIN_LIST_MODES, GETMAIN_GLOBAL_MODES);
|
|
64
106
|
|
|
65
107
|
const length = getKeyword(keywords, 'LV') ?? getKeyword(keywords, 'LA');
|
|
66
108
|
const address = getKeyword(keywords, 'A');
|
|
@@ -68,33 +110,39 @@ function parseGetmain(st, ops) {
|
|
|
68
110
|
|
|
69
111
|
return {
|
|
70
112
|
kind: 'GETMAIN',
|
|
113
|
+
...legacy,
|
|
71
114
|
mode,
|
|
72
115
|
length,
|
|
73
116
|
address,
|
|
74
117
|
subpool,
|
|
118
|
+
...extra,
|
|
75
119
|
keywords: Object.fromEntries(keywords)
|
|
76
120
|
};
|
|
77
121
|
}
|
|
78
122
|
|
|
79
|
-
function parseFreemain(st, ops) {
|
|
123
|
+
function parseFreemain(st, ops, opts) {
|
|
80
124
|
const { positional, keywords } = ops;
|
|
81
|
-
|
|
125
|
+
const mode = (positional[0] ?? '').trim().toUpperCase() || null;
|
|
126
|
+
if (!mode && !keywords.has('MF')) {
|
|
82
127
|
throw new HlasmSyntax('FREEMAIN requires a request type');
|
|
83
128
|
}
|
|
84
|
-
const
|
|
85
|
-
if (
|
|
129
|
+
const legacy = {};
|
|
130
|
+
if (mode && MVS38_STORAGE_MODES.has(mode)) {
|
|
131
|
+
mvs38Form(opts, legacy, 'FREEMAIN', mode);
|
|
132
|
+
} else if (mode && !FREEMAIN_MODES.has(mode)) {
|
|
86
133
|
throw new HlasmSyntax(`FREEMAIN request type must be one of ${[...FREEMAIN_MODES].join(', ')}, got ${positional[0]}`);
|
|
87
134
|
}
|
|
88
|
-
if (positional.
|
|
135
|
+
if (positional.slice(1).some((p) => p.trim() !== '')) {
|
|
89
136
|
throw new HlasmSyntax(`FREEMAIN does not accept extra positional operands: ${positional[1]}`);
|
|
90
137
|
}
|
|
91
138
|
|
|
92
|
-
const allowed = new Set(['LA', 'LV', 'A', 'SP', 'KEY', 'RELATED']);
|
|
139
|
+
const allowed = new Set(['LA', 'LV', 'A', 'SP', 'KEY', 'RELATED', 'MF', 'BRANCH']);
|
|
93
140
|
for (const key of keywords.keys()) {
|
|
94
141
|
if (!allowed.has(key)) {
|
|
95
142
|
throw new HlasmSyntax(`FREEMAIN does not accept keyword ${key}`);
|
|
96
143
|
}
|
|
97
144
|
}
|
|
145
|
+
const extra = remoteAndBranch('FREEMAIN', mode, keywords, FREEMAIN_LIST_MODES, FREEMAIN_GLOBAL_MODES);
|
|
98
146
|
|
|
99
147
|
const length = getKeyword(keywords, 'LV') ?? getKeyword(keywords, 'LA');
|
|
100
148
|
const address = getKeyword(keywords, 'A');
|
|
@@ -102,10 +150,12 @@ function parseFreemain(st, ops) {
|
|
|
102
150
|
|
|
103
151
|
return {
|
|
104
152
|
kind: 'FREEMAIN',
|
|
153
|
+
...legacy,
|
|
105
154
|
mode,
|
|
106
155
|
length,
|
|
107
156
|
address,
|
|
108
157
|
subpool,
|
|
158
|
+
...extra,
|
|
109
159
|
keywords: Object.fromEntries(keywords)
|
|
110
160
|
};
|
|
111
161
|
}
|
|
@@ -110,7 +110,7 @@ function parseWHEN(st, ops) {
|
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
function parseNoOperand(st, ops) {
|
|
113
|
-
if (ops.positional.
|
|
113
|
+
if (ops.positional.some((p) => p.trim() !== '')) throw new HlasmSyntax(`${st.operation} does not accept positional operands`);
|
|
114
114
|
return { kind: st.operation, keywords: Object.fromEntries(ops.keywords) };
|
|
115
115
|
}
|
|
116
116
|
|
package/lib/hlasm/model.mjs
CHANGED
|
@@ -14,7 +14,8 @@ const FIXED_LENGTH = { '&SYSDATE': 8, '&SYSDATC': 8, '&SYSTIME': 5 };
|
|
|
14
14
|
// &NAME, with an optional subscript (&A(1), &A(&I+1)) and an optional period that joins it to what
|
|
15
15
|
// follows. && is one ampersand, not a variable symbol. Inside a quoted string a variable becomes as
|
|
16
16
|
// many X as its value has characters when that is fixed, and one X otherwise, counted as unsized.
|
|
17
|
-
|
|
17
|
+
// Outside one it becomes what `standIn` gives for the nth such variable.
|
|
18
|
+
function substitute(text, standIn = (n) => `VAR${n}`) {
|
|
18
19
|
let out = '';
|
|
19
20
|
let quoted = false;
|
|
20
21
|
let n = 0;
|
|
@@ -35,20 +36,44 @@ function substitute(text) {
|
|
|
35
36
|
i--;
|
|
36
37
|
const fixed = FIXED_LENGTH[m[0].toUpperCase()];
|
|
37
38
|
if (quoted && !fixed) unsized++;
|
|
38
|
-
out += quoted ? 'X'.repeat(fixed || 1) :
|
|
39
|
+
out += quoted ? 'X'.repeat(fixed || 1) : standIn(++n);
|
|
39
40
|
}
|
|
40
|
-
return { text: out, unsized };
|
|
41
|
+
return { text: out, unsized, variables: n };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// A variable outside quotes stands in for a symbol. When no statement reads that way, one variable
|
|
45
|
+
// at a time stands in for a quoted string (DC C&MSG, LA &R,=C&LIST), a number (DC &T.F'0') or an
|
|
46
|
+
// option (PRINT &GEN, AMODE &MODE), as a caller's value may be, and then all of them do; past
|
|
47
|
+
// eight variables only the uniform readings are tried.
|
|
48
|
+
const SYMBOL = (n) => `VAR${n}`;
|
|
49
|
+
const VALUES = ["'X'", '1', 'ON', 'ANY'];
|
|
50
|
+
function* standIns(variables) {
|
|
51
|
+
yield SYMBOL;
|
|
52
|
+
if (variables <= 8) {
|
|
53
|
+
for (let j = 1; j <= variables; j++) for (const value of VALUES) yield (n) => (n === j ? value : SYMBOL(n));
|
|
54
|
+
}
|
|
55
|
+
for (const value of VALUES) yield () => value;
|
|
41
56
|
}
|
|
42
57
|
|
|
43
58
|
function read(kind, st) {
|
|
44
59
|
if (/&[A-Z$#@_]/i.test(st.operation)) throw new HlasmSyntax(`the operation ${st.operation} is decided when the macro is expanded`);
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
60
|
+
// A name built from variable symbols (&NAME, KFBR&SYSNDX) stands in as an ordinary symbol, as the
|
|
61
|
+
// operands do; one still holding an ampersand would send the statement back here.
|
|
62
|
+
const named = st.name && !st.name.startsWith('.') ? substitute(st.name).text : null;
|
|
63
|
+
const name = named && !named.includes('&') ? named : null;
|
|
64
|
+
const { variables } = substitute(st.field || '');
|
|
65
|
+
let first = null;
|
|
66
|
+
for (const standIn of standIns(variables)) {
|
|
67
|
+
const { text, unsized } = substitute(st.field || '', standIn);
|
|
68
|
+
const res = parseHlasmStatement({ ...st, name, field: text, inMacro: false, prototype: false });
|
|
69
|
+
// exact: the symbol reading, with no quoted string holding a variable of unknown length, so the
|
|
70
|
+
// node's lengths are the source's.
|
|
71
|
+
if (res.status === 'parsed') return { kind, as: res.kind, node: res.node, exact: standIn === SYMBOL && unsized === 0 };
|
|
72
|
+
first ||= res;
|
|
73
|
+
if (!variables) break;
|
|
74
|
+
}
|
|
75
|
+
if (first.status === 'unbuilt') throw new HlasmSyntax(`no parser yet for ${first.kind}`);
|
|
76
|
+
throw new HlasmSyntax(first.reason || `${first.kind} ${first.status}`);
|
|
52
77
|
}
|
|
53
78
|
|
|
54
79
|
function prototype(st) {
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// Operands the MVS 3.8 system macros take that z/OS 3.1's documentation does not list, with the
|
|
3
|
+
// meaning those macros give them (MVS 3.8j SYS1.MACLIB, public domain: MODESET, GETMAIN, FREEMAIN,
|
|
4
|
+
// ATTACH, DCB). Read by default; parseHlasmStatement(st, { mvs38: false }) refuses them.
|
|
5
|
+
import { HlasmSyntax } from './operands.mjs';
|
|
6
|
+
|
|
7
|
+
// MODESET EXTKEY= names and the PSW key each sets.
|
|
8
|
+
export const MVS38_EXTKEYS = new Map([
|
|
9
|
+
['SUPR', 0], ['RSM', 0], ['VSM', 0], ['SRM', 0],
|
|
10
|
+
['SCHED', 1], ['JES', 1], ['HASP', 1],
|
|
11
|
+
['DATAMGT', 5],
|
|
12
|
+
['TCAM', 6], ['VTAM', 6],
|
|
13
|
+
]);
|
|
14
|
+
|
|
15
|
+
// GETMAIN and FREEMAIN P: a request for a subpool, register 0 holding its number in the high byte.
|
|
16
|
+
export const MVS38_STORAGE_MODES = new Set(['P']);
|
|
17
|
+
|
|
18
|
+
// HIARCHY= chose a storage hierarchy (0 or 1); MVS/XA removed hierarchies.
|
|
19
|
+
export const MVS38_HIARCHY = /^[01]$/;
|
|
20
|
+
|
|
21
|
+
export const MVS38_KEYWORDS = {
|
|
22
|
+
GETMAIN: new Set(['HIARCHY']),
|
|
23
|
+
ATTACH: new Set(['HIARCHY', 'JSCB']),
|
|
24
|
+
DCB: new Set(['PGFX', 'AERR']),
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const mvs38Enabled = (opts) => opts?.mvs38 !== false;
|
|
28
|
+
|
|
29
|
+
export const systemKeyExtkeys = (opts) => (mvs38Enabled(opts) ? [...MVS38_EXTKEYS].filter(([, key]) => key > 0) : []);
|
|
30
|
+
|
|
31
|
+
export const keyZeroExtkeys = (opts) => (mvs38Enabled(opts) ? [...MVS38_EXTKEYS].filter(([, key]) => key === 0).map(([name]) => name) : []);
|
|
32
|
+
|
|
33
|
+
// Marks an MVS 3.8 form on the node it is read into, or refuses it when the option is off.
|
|
34
|
+
export function mvs38Form(opts, node, macro, form) {
|
|
35
|
+
if (!mvs38Enabled(opts)) throw new HlasmSyntax(`${macro}: ${form} is an MVS 3.8 form`);
|
|
36
|
+
(node.mvs38 ||= []).push(form);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// BTAM's READ and WRITE types (IHBRDWRT); BTAM is not in z/OS. READ T alone is its execute form.
|
|
40
|
+
export const MVS38_BTAM_TYPES = {
|
|
41
|
+
READ: new Set(['T', 'TB', 'TI', 'TT', 'TV', 'TP', 'TS', 'TQ', 'TC', 'TE', 'TM', 'TIR', 'TTR', 'TVR', 'TPR', 'TTA', 'TTL', 'TPL', 'TIQ', 'TRV', 'TCW', 'TBR', 'TMP', 'TBP', 'TQM']),
|
|
42
|
+
WRITE: new Set(['TB', 'TI', 'TT', 'TV', 'TA', 'TS', 'TN', 'TR', 'TL', 'TD', 'TQ', 'TW', 'TC', 'TIR', 'TTR', 'TVR', 'TLR', 'TIO', 'TIV', 'TCO', 'TTV', 'TUS', 'TIX', 'TVO', 'TTX', 'TSR', 'TIE', 'TTE', 'TRM', 'TSA', 'TSF', 'TIVX', 'TTVX', 'TIXR', 'TTXR']),
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// VTAM's basic-mode READ and WRITE, given RPL=, and the RPL fields they may set; z/OS
|
|
46
|
+
// Communications Server keeps SEND and RECEIVE and has no READ or WRITE.
|
|
47
|
+
export const MVS38_VTAM_RPL_KEYWORDS = new Set(['RPL', 'ACB', 'AREA', 'AREALEN', 'RECLEN', 'AAREA', 'AAREALN', 'ARECLEN', 'ECB', 'EXIT', 'OPTCD', 'ARG', 'BRANCH']);
|
package/lib/hlasm/operands.mjs
CHANGED
|
@@ -47,3 +47,13 @@ export function macroOperands(field) {
|
|
|
47
47
|
}
|
|
48
48
|
return { positional, keywords };
|
|
49
49
|
}
|
|
50
|
+
|
|
51
|
+
// A macro's list form (MF=L, SF=L) builds only its parameter list; its execute form ((E,addr))
|
|
52
|
+
// runs the service on a list built elsewhere, at addr.
|
|
53
|
+
export function remoteForm(value, keyword) {
|
|
54
|
+
const t = String(value).trim();
|
|
55
|
+
if (/^L$/i.test(t)) return { form: 'L', list: null };
|
|
56
|
+
const m = /^\(\s*E\s*,\s*(\S.*?)\s*\)$/i.exec(t);
|
|
57
|
+
if (m) return { form: 'E', list: m[1] };
|
|
58
|
+
throw new HlasmSyntax(`${keyword}= value ${t} is not L or (E,addr)`);
|
|
59
|
+
}
|