@portll/cobolwork 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +89 -26
- package/STABILITY.md +76 -0
- package/bin/cobolwork.mjs +56 -14
- package/lib/baseline.mjs +9 -0
- package/lib/bms.mjs +21 -7
- package/lib/build.mjs +133 -53
- package/lib/capabilities.mjs +13 -11
- package/lib/cics-commands.mjs +501 -9
- package/lib/compliance.mjs +11 -0
- package/lib/consequence.mjs +17 -0
- package/lib/control-reuse.mjs +113 -0
- package/lib/control-workers.mjs +161 -0
- package/lib/control.mjs +77 -4
- package/lib/dataflow.mjs +402 -152
- 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/equivalence.mjs +44 -5
- package/lib/evidence/cli.mjs +26 -7
- package/lib/evidence/record.mjs +16 -6
- package/lib/evidence/seal.mjs +17 -0
- package/lib/evidence/store.mjs +44 -15
- package/lib/evidence/timestamp.mjs +83 -0
- package/lib/evidence/verify.mjs +120 -42
- 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 +128 -0
- package/lib/hlasm/asm/listing.mjs +43 -0
- package/lib/hlasm/asm/output.mjs +45 -0
- package/lib/hlasm/asm/sections.mjs +161 -0
- package/lib/hlasm/asm/symbols.mjs +78 -0
- package/lib/hlasm/exec.mjs +27 -0
- package/lib/hlasm/expr.mjs +167 -0
- package/lib/hlasm/instr.mjs +73 -0
- package/lib/hlasm/locate.mjs +333 -0
- package/lib/hlasm/macro/authorization.mjs +146 -0
- package/lib/hlasm/macro/datasets.mjs +107 -0
- package/lib/hlasm/macro/io.mjs +162 -0
- package/lib/hlasm/macro/le.mjs +53 -0
- package/lib/hlasm/macro/linkage.mjs +143 -0
- package/lib/hlasm/macro/operator.mjs +77 -0
- package/lib/hlasm/macro/program.mjs +184 -0
- package/lib/hlasm/macro/recovery.mjs +85 -0
- package/lib/hlasm/macro/storage.mjs +167 -0
- package/lib/hlasm/macro/structured.mjs +131 -0
- package/lib/hlasm/model.mjs +97 -0
- package/lib/hlasm/mvs38.mjs +47 -0
- package/lib/hlasm/operands.mjs +59 -0
- package/lib/hlasm/optable.mjs +61 -0
- package/lib/hlasm/read.mjs +130 -0
- 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/inventory.mjs +10 -7
- package/lib/ironwork-ids.mjs +24 -0
- package/lib/ironwork.mjs +20 -13
- package/lib/kernel/pds-archive.mjs +256 -0
- package/lib/kernel/registry.mjs +32 -23
- package/lib/kernel/shared-pass.mjs +163 -0
- package/lib/kernel/source-tree.mjs +104 -36
- package/lib/kernel/version-key.mjs +17 -0
- package/lib/layout.mjs +26 -31
- package/lib/options.mjs +26 -4
- 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/policy.mjs +6 -0
- package/lib/precompile-check.mjs +124 -0
- package/lib/precompile-cics.mjs +8 -4
- package/lib/reach.mjs +11 -2
- package/lib/revision.json +1 -1
- package/lib/sarif.mjs +44 -3
- package/lib/scan.mjs +28 -12
- package/lib/sets/abend.mjs +82 -8
- package/lib/sets/build.mjs +43 -37
- package/lib/sets/cics.mjs +20 -38
- package/lib/sets/compile.mjs +46 -18
- package/lib/sets/copybook.mjs +5 -3
- package/lib/sets/crypto.mjs +5 -3
- package/lib/sets/ddl.mjs +36 -0
- package/lib/sets/flow.mjs +32 -8
- package/lib/sets/hidden.mjs +5 -3
- package/lib/sets/hlasm.mjs +124 -8
- package/lib/sets/ims.mjs +139 -0
- package/lib/sets/log.mjs +11 -9
- package/lib/sets/opaque.mjs +27 -7
- package/lib/sets/pli.mjs +40 -0
- package/lib/sets/priv.mjs +5 -3
- package/lib/sets/recon.mjs +5 -3
- package/lib/sets/secrets.mjs +112 -0
- package/lib/sets/semantics.mjs +8 -3
- package/lib/sets/web.mjs +39 -23
- 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 +520 -5
- package/rules/compliance-dora.json +509 -5
- package/rules/compliance-ffiec.json +505 -1
- package/rules/compliance-nist80053.json +557 -1
- package/rules/gitleaks-mainframe.toml +46 -15
- package/rules/hlasm-instructions.json +2396 -0
- 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.policy.schema.json +7 -0
- package/schema/cobolwork.site.schema.json +116 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// Parsers for the z/OS record macros GET, PUT, READ, and WRITE in their standard forms.
|
|
3
|
+
import { HlasmSyntax, NAME } from '../operands.mjs';
|
|
4
|
+
import { parseExpression, parseAddress } from '../expr.mjs';
|
|
5
|
+
import { MVS38_BTAM_TYPES, MVS38_VTAM_RPL_KEYWORDS, mvs38Form } from '../mvs38.mjs';
|
|
6
|
+
|
|
7
|
+
const REG = /^\(\d{1,2}\)$/;
|
|
8
|
+
const MF_VAL = /^(L|\((E|M),\s*[A-Z$#@_][A-Z0-9$#@_]*\))$/i;
|
|
9
|
+
|
|
10
|
+
function parseAddr(text, label) {
|
|
11
|
+
const t = text.trim();
|
|
12
|
+
if (t === '') throw new HlasmSyntax(`${label} is missing`);
|
|
13
|
+
if (REG.test(t)) return t;
|
|
14
|
+
try {
|
|
15
|
+
parseAddress(t);
|
|
16
|
+
} catch (e) {
|
|
17
|
+
throw new HlasmSyntax(`${label} is not a valid address: ${e.message}`);
|
|
18
|
+
}
|
|
19
|
+
return t;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function parseLen(text, label) {
|
|
23
|
+
const t = text.trim();
|
|
24
|
+
if (t === '') throw new HlasmSyntax(`${label} is missing`);
|
|
25
|
+
if (t === "'S'") return t;
|
|
26
|
+
try {
|
|
27
|
+
parseExpression(t);
|
|
28
|
+
} catch (e) {
|
|
29
|
+
throw new HlasmSyntax(`${label} is not a valid length: ${e.message}`);
|
|
30
|
+
}
|
|
31
|
+
return t;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function parseMf(text) {
|
|
35
|
+
const t = text.trim();
|
|
36
|
+
if (!MF_VAL.test(t)) throw new HlasmSyntax(`MF= value ${t} is not L, (E,addr) or (M,addr)`);
|
|
37
|
+
return t;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// VSAM's GET and PUT name only a request parameter list (z/OS 3.1 DFSMS Macro Instructions for
|
|
41
|
+
// Data Sets, GET and PUT for VSAM): RPL=address, register notation from 1 to 12 included.
|
|
42
|
+
function vsamRequest(macro, ops) {
|
|
43
|
+
const { positional, keywords } = ops;
|
|
44
|
+
if (positional.some((p) => p.trim() !== '')) throw new HlasmSyntax(`${macro} RPL= takes no positional operands`);
|
|
45
|
+
for (const k of keywords.keys()) if (k !== 'RPL') throw new HlasmSyntax(`unknown keyword ${k} in ${macro} RPL=`);
|
|
46
|
+
return { kind: macro, rpl: parseAddr(keywords.get('RPL'), 'RPL address') };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function parseGet(st, ops) {
|
|
50
|
+
if (ops.keywords.has('RPL')) return vsamRequest('GET', ops);
|
|
51
|
+
const { positional, keywords } = ops;
|
|
52
|
+
if (positional.length > 2) throw new HlasmSyntax('GET takes at most 2 positional operands');
|
|
53
|
+
if (positional.length === 0) throw new HlasmSyntax('GET requires a dcb address');
|
|
54
|
+
|
|
55
|
+
const dcb = parseAddr(positional[0], 'dcb address');
|
|
56
|
+
const area = positional.length > 1 ? parseAddr(positional[1], 'area address') : null;
|
|
57
|
+
|
|
58
|
+
const node = { kind: 'GET', dcb, area };
|
|
59
|
+
const allowed = new Set(['TYPE', 'MF']);
|
|
60
|
+
for (const [k, v] of keywords) {
|
|
61
|
+
if (!allowed.has(k)) throw new HlasmSyntax(`unknown keyword ${k} in GET`);
|
|
62
|
+
if (k === 'TYPE') {
|
|
63
|
+
if (v.trim().toUpperCase() !== 'P') throw new HlasmSyntax('GET TYPE= must be P');
|
|
64
|
+
node.type = v;
|
|
65
|
+
} else if (k === 'MF') {
|
|
66
|
+
node.mf = parseMf(v);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return node;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function parsePut(st, ops) {
|
|
73
|
+
if (ops.keywords.has('RPL')) return vsamRequest('PUT', ops);
|
|
74
|
+
const { positional, keywords } = ops;
|
|
75
|
+
if (positional.length > 2) throw new HlasmSyntax('PUT takes at most 2 positional operands');
|
|
76
|
+
if (positional.length === 0) throw new HlasmSyntax('PUT requires a dcb address');
|
|
77
|
+
|
|
78
|
+
const dcb = parseAddr(positional[0], 'dcb address');
|
|
79
|
+
const area = positional.length > 1 ? parseAddr(positional[1], 'area address') : null;
|
|
80
|
+
|
|
81
|
+
const node = { kind: 'PUT', dcb, area };
|
|
82
|
+
const allowed = new Set(['MF']);
|
|
83
|
+
for (const [k, v] of keywords) {
|
|
84
|
+
if (!allowed.has(k)) throw new HlasmSyntax(`unknown keyword ${k} in PUT`);
|
|
85
|
+
if (k === 'MF') {
|
|
86
|
+
node.mf = parseMf(v);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return node;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// READ and WRITE name a data event control block, a type, then by position the dcb, area, length,
|
|
93
|
+
// key and block addresses and a next address (z/OS 3.1 DFSMS Macro Instructions for Data Sets: BSAM
|
|
94
|
+
// and BPAM, BDAM, BISAM, and BSAM creating a direct data set). The standard form names the DECB and
|
|
95
|
+
// needs the dcb and area; the list form (MF=L) may leave any operand after the type to the execute
|
|
96
|
+
// form, and the execute form (MF=E) names an existing DECB by address and may omit them too.
|
|
97
|
+
const BLOCK_TYPES = {
|
|
98
|
+
READ: /^(?:SF|SB|SF64P?|K|KU|D[IK][FX]?(?:R|RU)?)$/,
|
|
99
|
+
WRITE: /^(?:SF|SFR|SD|SZ|SF64P?|K|KN|DAF?|D[IK][FX]?)$/,
|
|
100
|
+
};
|
|
101
|
+
const BLOCK_FIELDS = ['dcb', 'area', 'length', 'key', 'block', 'next'];
|
|
102
|
+
const REGISTER = /^\(\s*[A-Z0-9$#@_]+\s*\)$/i;
|
|
103
|
+
|
|
104
|
+
function blockField(field, raw) {
|
|
105
|
+
if (raw === "'S'" || (field === 'key' && raw === '0') || REGISTER.test(raw)) return raw;
|
|
106
|
+
return field === 'length' ? parseLen(raw, 'length') : parseAddr(raw, `${field} address`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// VTAM's basic-mode READ and WRITE: RPL= and the request parameter list fields, no positional operands.
|
|
110
|
+
function vtamBasicRequest(macro, ops, opts) {
|
|
111
|
+
const node = { kind: macro };
|
|
112
|
+
mvs38Form(opts, node, macro, 'RPL=');
|
|
113
|
+
if (ops.positional.some((p) => p.trim() !== '')) throw new HlasmSyntax(`${macro} RPL= takes no positional operands`);
|
|
114
|
+
for (const k of ops.keywords.keys()) if (!MVS38_VTAM_RPL_KEYWORDS.has(k)) throw new HlasmSyntax(`unknown keyword ${k} in ${macro} RPL=`);
|
|
115
|
+
return { ...node, rpl: parseAddr(ops.keywords.get('RPL'), 'RPL address'), keywords: Object.fromEntries(ops.keywords) };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function blockRequest(macro, st, ops, opts) {
|
|
119
|
+
const { positional, keywords } = ops;
|
|
120
|
+
if (keywords.has('RPL')) return vtamBasicRequest(macro, ops, opts);
|
|
121
|
+
for (const k of keywords.keys()) if (k !== 'MF') throw new HlasmSyntax(`unknown keyword ${k} in ${macro}`);
|
|
122
|
+
const legacy = {};
|
|
123
|
+
let mf = keywords.has('MF') ? keywords.get('MF').trim().toUpperCase() : null;
|
|
124
|
+
let list = null;
|
|
125
|
+
const remote = mf && /^\(\s*E\s*,(.+)\)$/.exec(mf);
|
|
126
|
+
if (remote) {
|
|
127
|
+
mvs38Form(opts, legacy, macro, 'MF=(E,addr)');
|
|
128
|
+
list = parseAddr(remote[1], 'MF list address');
|
|
129
|
+
mf = 'E';
|
|
130
|
+
}
|
|
131
|
+
if (mf !== null && mf !== 'L' && mf !== 'E') throw new HlasmSyntax(`${macro} MF= value ${mf} is not L or E`);
|
|
132
|
+
if (positional.length < 2) throw new HlasmSyntax(`${macro} requires a decb and a type`);
|
|
133
|
+
if (positional.length > 2 + BLOCK_FIELDS.length) throw new HlasmSyntax(`${macro} takes at most ${2 + BLOCK_FIELDS.length} positional operands`);
|
|
134
|
+
const decb = positional[0].trim();
|
|
135
|
+
if (mf === 'E' ? decb === '' || !(REGISTER.test(decb) || parseAddr(decb, 'decb address')) : !NAME.test(decb)) {
|
|
136
|
+
throw new HlasmSyntax(`${macro} decb name is not a valid symbol`);
|
|
137
|
+
}
|
|
138
|
+
const type = positional[1].trim().toUpperCase();
|
|
139
|
+
if (MVS38_BTAM_TYPES[macro].has(type) && (type !== 'T' || mf === 'E')) mvs38Form(opts, legacy, macro, `type ${type}`);
|
|
140
|
+
else if (!BLOCK_TYPES[macro].test(type)) throw new HlasmSyntax(`${macro} type ${type} is not one the access methods define`);
|
|
141
|
+
const node = { kind: macro, ...legacy, decb, type, mf, ...(list ? { list } : {}), keywords: Object.fromEntries(keywords) };
|
|
142
|
+
BLOCK_FIELDS.forEach((field, i) => {
|
|
143
|
+
const raw = (positional[i + 2] ?? '').trim();
|
|
144
|
+
if (raw === '') {
|
|
145
|
+
if (!mf && (field === 'dcb' || field === 'area')) throw new HlasmSyntax(`${field} address is missing`);
|
|
146
|
+
node[field] = null;
|
|
147
|
+
} else {
|
|
148
|
+
node[field] = blockField(field, raw);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
return node;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const parseRead = (st, ops, opts) => blockRequest('READ', st, ops, opts);
|
|
155
|
+
const parseWrite = (st, ops, opts) => blockRequest('WRITE', st, ops, opts);
|
|
156
|
+
|
|
157
|
+
export const parsers = {
|
|
158
|
+
GET: parseGet,
|
|
159
|
+
PUT: parsePut,
|
|
160
|
+
READ: parseRead,
|
|
161
|
+
WRITE: parseWrite
|
|
162
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// Parsers for the Language Environment prolog, epilog and mapping macros.
|
|
3
|
+
|
|
4
|
+
import { HlasmSyntax } from '../operands.mjs';
|
|
5
|
+
|
|
6
|
+
const CEEENTRY_KW = new Set(['PPA', 'AUTO', 'NAB', 'MAIN', 'ENCLAVE', 'EXECOPS', 'PARMREG', 'EXPORT', 'BASE', 'PLIST', 'RMODE', 'AMODE', 'STKPROT']);
|
|
7
|
+
const CEETERM_KW = new Set(['RC', 'MODIFIER', 'MF']);
|
|
8
|
+
const CEEPPA_KW = new Set(['LIBRARY', 'PPA2', 'EXTPROC', 'TSTAMP', 'PEP', 'INSTOP', 'EXITDSA', 'OWNEXM', 'EPNAME', 'VER', 'REL', 'MOD', 'DSA', 'SERVICE', 'VRSMASK', 'VRSLOCR', 'STKPROT']);
|
|
9
|
+
|
|
10
|
+
function checkKeywords(ops, allowed, op) {
|
|
11
|
+
const keywords = {};
|
|
12
|
+
for (const [k, v] of ops.keywords) {
|
|
13
|
+
if (!allowed.has(k)) throw new HlasmSyntax(`${op} does not take keyword ${k}`);
|
|
14
|
+
keywords[k] = v;
|
|
15
|
+
}
|
|
16
|
+
return keywords;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function parseCEEENTRY(st, ops) {
|
|
20
|
+
if (ops.positional.some((p) => p.trim() !== '')) throw new HlasmSyntax('CEEENTRY takes no positional operands');
|
|
21
|
+
const keywords = checkKeywords(ops, CEEENTRY_KW, 'CEEENTRY');
|
|
22
|
+
return { kind: 'CEEENTRY', name: st.name, main: keywords.MAIN || null, ppa: keywords.PPA || null, keywords };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function parseCEETERM(st, ops) {
|
|
26
|
+
if (ops.positional.some((p) => p.trim() !== '')) throw new HlasmSyntax('CEETERM takes no positional operands');
|
|
27
|
+
const keywords = checkKeywords(ops, CEETERM_KW, 'CEETERM');
|
|
28
|
+
return { kind: 'CEETERM', rc: keywords.RC || null, keywords };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function parseCEEPPA(st, ops) {
|
|
32
|
+
if (ops.positional.length > 1) throw new HlasmSyntax('CEEPPA takes no positional operands');
|
|
33
|
+
const keywords = checkKeywords(ops, CEEPPA_KW, 'CEEPPA');
|
|
34
|
+
return { kind: 'CEEPPA', name: st.name, keywords };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function parseCEECAA(st, ops) {
|
|
38
|
+
if (ops.positional.length > 1 || ops.keywords.size > 0) throw new HlasmSyntax('CEECAA takes no operands');
|
|
39
|
+
return { kind: 'CEECAA', keywords: {} };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function parseCEEDSA(st, ops) {
|
|
43
|
+
if (ops.positional.length > 1 || ops.keywords.size > 0) throw new HlasmSyntax('CEEDSA takes no operands');
|
|
44
|
+
return { kind: 'CEEDSA', keywords: {} };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const parsers = {
|
|
48
|
+
CEEENTRY: parseCEEENTRY,
|
|
49
|
+
CEETERM: parseCEETERM,
|
|
50
|
+
CEEPPA: parseCEEPPA,
|
|
51
|
+
CEECAA: parseCEECAA,
|
|
52
|
+
CEEDSA: parseCEEDSA
|
|
53
|
+
};
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// Parsers for the z/OS linkage macros CALL, SAVE, and RETURN.
|
|
3
|
+
import { HlasmSyntax, NAME } from '../operands.mjs';
|
|
4
|
+
|
|
5
|
+
const CALL_KW = new Set(['ID', 'LINKINST', 'PLIST4', 'PLIST8', 'PLIST8ARALETS', 'PARAM64', 'MF']);
|
|
6
|
+
const RETURN_KW = new Set(['RC', 'RETREGS']);
|
|
7
|
+
|
|
8
|
+
function parseCall(st, ops) {
|
|
9
|
+
const { positional, keywords } = ops;
|
|
10
|
+
if (positional.length < 1 || positional.length > 3) {
|
|
11
|
+
throw new HlasmSyntax(`CALL expects 1 to 3 positional operands, got ${positional.length}`);
|
|
12
|
+
}
|
|
13
|
+
const entryRaw = positional[0].trim();
|
|
14
|
+
let entry = null;
|
|
15
|
+
let register = null;
|
|
16
|
+
const regMatch = /^\((\d+)\)$/.exec(entryRaw);
|
|
17
|
+
// The list form (MF=L) builds only the parameter list, so it names no entry.
|
|
18
|
+
const listForm = /^L$/i.test(String(keywords.get('MF') || '').trim());
|
|
19
|
+
if (regMatch) {
|
|
20
|
+
register = entryRaw;
|
|
21
|
+
} else if (NAME.test(entryRaw)) {
|
|
22
|
+
entry = entryRaw;
|
|
23
|
+
} else if (!(entryRaw === '' && listForm)) {
|
|
24
|
+
throw new HlasmSyntax(`CALL entry operand '${entryRaw}' is not a valid name or register`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let params = [];
|
|
28
|
+
if (positional.length >= 2) {
|
|
29
|
+
const paramsRaw = positional[1].trim();
|
|
30
|
+
if (paramsRaw.startsWith('(') && paramsRaw.endsWith(')')) {
|
|
31
|
+
const inner = paramsRaw.slice(1, -1).trim();
|
|
32
|
+
if (inner) {
|
|
33
|
+
params = inner.split(',').map(s => s.trim());
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const node = { kind: 'CALL', entry, register, params, vl: false, keywords: {} };
|
|
39
|
+
if (positional.length === 3) {
|
|
40
|
+
const vlRaw = positional[2].trim().toUpperCase();
|
|
41
|
+
if (vlRaw !== 'VL') {
|
|
42
|
+
throw new HlasmSyntax('CALL third positional operand must be VL');
|
|
43
|
+
}
|
|
44
|
+
node.vl = true;
|
|
45
|
+
node.keywords.VL = positional[2];
|
|
46
|
+
}
|
|
47
|
+
for (const [k, v] of keywords) {
|
|
48
|
+
if (k === 'VL') {
|
|
49
|
+
node.vl = true;
|
|
50
|
+
node.keywords[k] = v;
|
|
51
|
+
} else if (CALL_KW.has(k)) {
|
|
52
|
+
node.keywords[k] = v;
|
|
53
|
+
} else {
|
|
54
|
+
throw new HlasmSyntax(`CALL does not accept keyword ${k}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return node;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function parseSave(st, ops) {
|
|
61
|
+
const { positional, keywords } = ops;
|
|
62
|
+
if (keywords.size > 0) {
|
|
63
|
+
throw new HlasmSyntax('SAVE does not accept keywords');
|
|
64
|
+
}
|
|
65
|
+
if (positional.length < 1 || positional.length > 3) {
|
|
66
|
+
throw new HlasmSyntax(`SAVE expects 1 to 3 positional operands, got ${positional.length}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const regRaw = positional[0].trim();
|
|
70
|
+
if (!regRaw.startsWith('(') || !regRaw.endsWith(')')) {
|
|
71
|
+
throw new HlasmSyntax('SAVE first operand must be a parenthesized register list');
|
|
72
|
+
}
|
|
73
|
+
const inner = regRaw.slice(1, -1).trim();
|
|
74
|
+
const registers = inner ? inner.split(',').map(s => s.trim()) : [];
|
|
75
|
+
|
|
76
|
+
let t = false;
|
|
77
|
+
let id = null;
|
|
78
|
+
let idx = 1;
|
|
79
|
+
// The second slot is T or empty: SAVE (14,12),,* omits T and names the identifier.
|
|
80
|
+
if (idx < positional.length) {
|
|
81
|
+
const next = positional[idx].trim().toUpperCase();
|
|
82
|
+
if (next === 'T' || next === '') {
|
|
83
|
+
t = next === 'T';
|
|
84
|
+
idx++;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (idx < positional.length) {
|
|
88
|
+
id = positional[idx].trim();
|
|
89
|
+
idx++;
|
|
90
|
+
}
|
|
91
|
+
if (idx < positional.length) {
|
|
92
|
+
throw new HlasmSyntax(`SAVE has too many positional operands`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return { kind: 'SAVE', registers, t, id };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function parseReturn(st, ops) {
|
|
99
|
+
const { positional, keywords } = ops;
|
|
100
|
+
if (positional.length > 2) {
|
|
101
|
+
throw new HlasmSyntax(`RETURN expects at most 2 positional operands, got ${positional.length}`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
let registers = [];
|
|
105
|
+
let t = false;
|
|
106
|
+
if (positional.length > 0) {
|
|
107
|
+
const regRaw = positional[0].trim();
|
|
108
|
+
if (regRaw.startsWith('(') && regRaw.endsWith(')')) {
|
|
109
|
+
const inner = regRaw.slice(1, -1).trim();
|
|
110
|
+
if (inner) {
|
|
111
|
+
registers = inner.split(',').map(s => s.trim());
|
|
112
|
+
}
|
|
113
|
+
} else {
|
|
114
|
+
throw new HlasmSyntax('RETURN first operand must be a parenthesized register list');
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (positional.length > 1) {
|
|
118
|
+
const next = positional[1].trim().toUpperCase();
|
|
119
|
+
if (next === 'T') {
|
|
120
|
+
t = true;
|
|
121
|
+
} else if (next !== '') {
|
|
122
|
+
throw new HlasmSyntax('RETURN second positional operand must be T or empty');
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const node = { kind: 'RETURN', registers, t, rc: null, keywords: {} };
|
|
127
|
+
for (const [k, v] of keywords) {
|
|
128
|
+
if (!RETURN_KW.has(k)) {
|
|
129
|
+
throw new HlasmSyntax(`RETURN does not accept keyword ${k}`);
|
|
130
|
+
}
|
|
131
|
+
if (k === 'RC') {
|
|
132
|
+
node.rc = v;
|
|
133
|
+
}
|
|
134
|
+
node.keywords[k] = v;
|
|
135
|
+
}
|
|
136
|
+
return node;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export const parsers = {
|
|
140
|
+
CALL: parseCall,
|
|
141
|
+
SAVE: parseSave,
|
|
142
|
+
RETURN: parseReturn
|
|
143
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// Parser for the z/OS operator-communication macros WTO and WTOR in their standard form.
|
|
3
|
+
import { HlasmSyntax } from '../operands.mjs';
|
|
4
|
+
|
|
5
|
+
// The standard, list and execute forms, authorized keywords included (z/OS 3.1 MVS Programming:
|
|
6
|
+
// Authorized Assembler Services Reference, Volume 4, WTO and WTOR).
|
|
7
|
+
const WTO_KW = new Set(['TEXT', 'ROUTCDE', 'DESC', 'MCSFLAG', 'MSGTYP', 'CART', 'CONSID', 'CONSNAME', 'KEY', 'TOKEN', 'MF',
|
|
8
|
+
'AREAID', 'CONNECT', 'JOBID', 'JOBNAME', 'LINKAGE', 'SYNCH', 'SYSNAME', 'WSPARM']);
|
|
9
|
+
const WTOR_KW = new Set(['TEXT', 'ROUTCDE', 'MCSFLAG', 'DESC', 'MSGTYP', 'RPLYISUR', 'CART', 'CONSID', 'CONSNAME', 'KEY', 'TOKEN', 'MF',
|
|
10
|
+
'JOBID', 'JOBNAME', 'LINKAGE', 'SYNCH', 'SYSNAME']);
|
|
11
|
+
const isMessage = (text) => text.startsWith("'") || text.startsWith('(');
|
|
12
|
+
|
|
13
|
+
function parseWTO(st, ops) {
|
|
14
|
+
const { positional, keywords } = ops;
|
|
15
|
+
const kw = {};
|
|
16
|
+
for (const [k, v] of keywords) {
|
|
17
|
+
if (!WTO_KW.has(k)) throw new HlasmSyntax(`WTO: unknown keyword ${k}`);
|
|
18
|
+
kw[k] = v;
|
|
19
|
+
}
|
|
20
|
+
// A message is one quoted string, or the lines of a multiple-line message, each ('text',type);
|
|
21
|
+
// the execute form (MF=(E,addr)) may leave it to the list form.
|
|
22
|
+
const lines = positional.map((p) => p.trim());
|
|
23
|
+
const execute = /^\(\s*E\s*,/i.test(String(keywords.get('MF') || ''));
|
|
24
|
+
if (lines.length === 1 && lines[0] === '' && execute) return { kind: 'WTO', text: null, keywords: kw };
|
|
25
|
+
for (const line of lines) {
|
|
26
|
+
if (!isMessage(line)) throw new HlasmSyntax('WTO: first positional operand must be a quoted string or a parenthesised list');
|
|
27
|
+
}
|
|
28
|
+
if (lines.length > 1 && !lines.every((l) => l.startsWith('('))) {
|
|
29
|
+
throw new HlasmSyntax(`WTO: too many positional operands (${lines.length})`);
|
|
30
|
+
}
|
|
31
|
+
return { kind: 'WTO', text: lines.length ? lines.join(',') : null, keywords: kw };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function parseWTOR(st, ops) {
|
|
35
|
+
const { keywords } = ops;
|
|
36
|
+
const positional = [...ops.positional];
|
|
37
|
+
while (positional.length > 4 && positional.at(-1).trim() === '') positional.pop();
|
|
38
|
+
const kw = {};
|
|
39
|
+
for (const [k, v] of keywords) {
|
|
40
|
+
if (!WTOR_KW.has(k)) throw new HlasmSyntax(`WTOR: unknown keyword ${k}`);
|
|
41
|
+
kw[k] = v;
|
|
42
|
+
}
|
|
43
|
+
let text = null;
|
|
44
|
+
let reply = null;
|
|
45
|
+
let replyLength = null;
|
|
46
|
+
let ecb = null;
|
|
47
|
+
|
|
48
|
+
// The list form (MF=L) may leave the reply, its length and the ECB to the execute form, and the
|
|
49
|
+
// execute form (MF=(E,addr)) any operand to the list form.
|
|
50
|
+
const mf = String(keywords.get('MF') || '').trim();
|
|
51
|
+
const remote = mf !== '';
|
|
52
|
+
const execute = /^\(\s*E\s*,/i.test(mf);
|
|
53
|
+
if (positional.length === 0) {
|
|
54
|
+
if (!keywords.has('TEXT') && !execute) {
|
|
55
|
+
throw new HlasmSyntax('WTOR: no positional operands and no TEXT= keyword');
|
|
56
|
+
}
|
|
57
|
+
} else if (positional.length === 4 || (remote && positional.length <= 4)) {
|
|
58
|
+
const msg = positional[0].trim();
|
|
59
|
+
if (isMessage(msg)) {
|
|
60
|
+
text = msg;
|
|
61
|
+
} else if (!(msg === '' && (execute || keywords.has('TEXT')))) {
|
|
62
|
+
throw new HlasmSyntax('WTOR: first positional operand must be a quoted string or a parenthesised list');
|
|
63
|
+
}
|
|
64
|
+
reply = positional[1] ?? null;
|
|
65
|
+
replyLength = positional[2] ?? null;
|
|
66
|
+
ecb = positional[3] ?? null;
|
|
67
|
+
} else {
|
|
68
|
+
throw new HlasmSyntax(`WTOR: expected 4 positional operands, got ${positional.length}`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return { kind: 'WTOR', text, reply, replyLength, ecb, keywords: kw };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export const parsers = {
|
|
75
|
+
WTO: parseWTO,
|
|
76
|
+
WTOR: parseWTOR
|
|
77
|
+
};
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// Parsers for z/OS program-management macros: LINK, XCTL, LOAD, ATTACH.
|
|
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';
|
|
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
|
+
};
|
|
14
|
+
const YES_NO = /^(?:YES|NO)$/i;
|
|
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
|
+
|
|
61
|
+
function parseLink(st, ops) {
|
|
62
|
+
const kw = written(ops.keywords);
|
|
63
|
+
const allowed = new Set(['EP', 'EPLOC', 'DE', 'DCB', 'PARAM', 'VL', 'ID', 'ERRET', 'LSEARCH', 'SF', 'MF']);
|
|
64
|
+
for (const k of kw.keys()) if (!allowed.has(k)) throw new HlasmSyntax(`unknown keyword ${k}`);
|
|
65
|
+
if (ops.positional.some((p) => p.trim() !== '')) throw new HlasmSyntax('unexpected positional operand');
|
|
66
|
+
const { out: form, entryOptional } = forms(kw, 'LINK');
|
|
67
|
+
const epKeys = ['EP', 'EPLOC', 'DE'].filter(k => kw.has(k));
|
|
68
|
+
if (epKeys.length > 1) throw new HlasmSyntax('EP, EPLOC, and DE are mutually exclusive');
|
|
69
|
+
if (epKeys.length === 0 && !entryOptional) throw new HlasmSyntax('missing EP, EPLOC, or DE');
|
|
70
|
+
const ep = kw.has('EP') ? kw.get('EP').trim().toUpperCase() : null;
|
|
71
|
+
const eploc = kw.has('EPLOC') ? kw.get('EPLOC') : null;
|
|
72
|
+
const de = kw.has('DE') ? kw.get('DE') : null;
|
|
73
|
+
if (ep && !NAME.test(ep)) throw new HlasmSyntax('invalid EP name');
|
|
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');
|
|
78
|
+
if (kw.has('LSEARCH') && !YES_NO.test(kw.get('LSEARCH').trim())) throw new HlasmSyntax('invalid LSEARCH value');
|
|
79
|
+
if (kw.has('VL') && !/^\d+$/.test(kw.get('VL').trim())) throw new HlasmSyntax('invalid VL value');
|
|
80
|
+
if (kw.has('ID') && !/^\d+$/.test(kw.get('ID').trim())) throw new HlasmSyntax('invalid ID value');
|
|
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) };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function parseXctl(st, ops) {
|
|
86
|
+
const kw = written(ops.keywords);
|
|
87
|
+
const allowed = new Set(['EP', 'EPLOC', 'DE', 'DCB', 'LSEARCH', 'SF', 'MF', 'PARAM', 'VL']);
|
|
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');
|
|
93
|
+
// The registers to restore before control passes: (reg) or (reg1,reg2), numbers or EQU symbols.
|
|
94
|
+
if (ops.positional.length > 1) throw new HlasmSyntax('too many positional operands');
|
|
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');
|
|
96
|
+
const epKeys = ['EP', 'EPLOC', 'DE'].filter(k => kw.has(k));
|
|
97
|
+
if (epKeys.length > 1) throw new HlasmSyntax('EP, EPLOC, and DE are mutually exclusive');
|
|
98
|
+
if (epKeys.length === 0 && !entryOptional) throw new HlasmSyntax('missing EP, EPLOC, or DE');
|
|
99
|
+
const ep = kw.has('EP') ? kw.get('EP').trim().toUpperCase() : null;
|
|
100
|
+
const eploc = kw.has('EPLOC') ? kw.get('EPLOC') : null;
|
|
101
|
+
const de = kw.has('DE') ? kw.get('DE') : null;
|
|
102
|
+
if (ep && !NAME.test(ep)) throw new HlasmSyntax('invalid EP name');
|
|
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');
|
|
106
|
+
if (kw.has('LSEARCH') && !YES_NO.test(kw.get('LSEARCH').trim())) throw new HlasmSyntax('invalid LSEARCH value');
|
|
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) };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function parseLoad(st, ops) {
|
|
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']);
|
|
117
|
+
for (const k of kw.keys()) if (!allowed.has(k)) throw new HlasmSyntax(`unknown keyword ${k}`);
|
|
118
|
+
if (ops.positional.some((p) => p.trim() !== '')) throw new HlasmSyntax('unexpected positional operand');
|
|
119
|
+
const { out: form, entryOptional } = forms(kw, 'LOAD');
|
|
120
|
+
const epKeys = ['EP', 'EPLOC', 'DE'].filter(k => kw.has(k));
|
|
121
|
+
if (epKeys.length > 1) throw new HlasmSyntax('EP, EPLOC, and DE are mutually exclusive');
|
|
122
|
+
if (epKeys.length === 0 && !entryOptional) throw new HlasmSyntax('missing EP, EPLOC, or DE');
|
|
123
|
+
const ep = kw.has('EP') ? kw.get('EP').trim().toUpperCase() : null;
|
|
124
|
+
const eploc = kw.has('EPLOC') ? kw.get('EPLOC') : null;
|
|
125
|
+
const de = kw.has('DE') ? kw.get('DE') : null;
|
|
126
|
+
if (ep && !NAME.test(ep)) throw new HlasmSyntax('invalid EP name');
|
|
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');
|
|
131
|
+
if (kw.has('LSEARCH') && !YES_NO.test(kw.get('LSEARCH').trim())) throw new HlasmSyntax('invalid LSEARCH value');
|
|
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) };
|
|
136
|
+
}
|
|
137
|
+
|
|
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');
|
|
153
|
+
const epKeys = ['EP', 'EPLOC', 'DE'].filter(k => kw.has(k));
|
|
154
|
+
if (epKeys.length > 1) throw new HlasmSyntax('EP, EPLOC, and DE are mutually exclusive');
|
|
155
|
+
if (epKeys.length === 0 && !entryOptional) throw new HlasmSyntax('missing EP, EPLOC, or DE');
|
|
156
|
+
const ep = kw.has('EP') ? kw.get('EP').trim().toUpperCase() : null;
|
|
157
|
+
const eploc = kw.has('EPLOC') ? kw.get('EPLOC') : null;
|
|
158
|
+
const de = kw.has('DE') ? kw.get('DE') : null;
|
|
159
|
+
if (ep && !NAME.test(ep)) throw new HlasmSyntax('invalid EP name');
|
|
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');
|
|
165
|
+
if (kw.has('VL') && !/^\d+$/.test(kw.get('VL').trim())) throw new HlasmSyntax('invalid VL value');
|
|
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');
|
|
172
|
+
if (kw.has('SZERO') && !YES_NO.test(kw.get('SZERO').trim())) throw new HlasmSyntax('invalid SZERO value');
|
|
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');
|
|
176
|
+
if (kw.has('PURGE') && !/^(?:QUIESCE|NONE|HALT)$/i.test(kw.get('PURGE').trim())) throw new HlasmSyntax('invalid PURGE value');
|
|
177
|
+
if (kw.has('ASYNCH') && !YES_NO.test(kw.get('ASYNCH').trim())) throw new HlasmSyntax('invalid ASYNCH value');
|
|
178
|
+
if (kw.has('TERM') && !YES_NO.test(kw.get('TERM').trim())) throw new HlasmSyntax('invalid TERM value');
|
|
179
|
+
if (kw.has('ALCOPY') && !YES_NO.test(kw.get('ALCOPY').trim())) throw new HlasmSyntax('invalid ALCOPY value');
|
|
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) };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export const parsers = Object.freeze({ LINK: parseLink, XCTL: parseXctl, LOAD: parseLoad, ATTACH: parseAttach });
|