@portll/cobolwork 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/README.md +53 -5
  2. package/bin/cobolwork.mjs +34 -9
  3. package/lib/baseline.mjs +9 -0
  4. package/lib/bms.mjs +8 -0
  5. package/lib/build.mjs +33 -8
  6. package/lib/capabilities.mjs +6 -3
  7. package/lib/consequence.mjs +17 -0
  8. package/lib/constants.mjs +60 -0
  9. package/lib/control-reuse.mjs +113 -0
  10. package/lib/control.mjs +86 -18
  11. package/lib/csd.mjs +6 -1
  12. package/lib/dataflow.mjs +343 -95
  13. package/lib/equivalence.mjs +44 -5
  14. package/lib/evidence/cli.mjs +13 -5
  15. package/lib/evidence/record.mjs +16 -6
  16. package/lib/evidence/seal.mjs +17 -0
  17. package/lib/evidence/store.mjs +30 -14
  18. package/lib/evidence/timestamp.mjs +83 -0
  19. package/lib/evidence/verify.mjs +120 -42
  20. package/lib/hlasm/asm/data.mjs +116 -0
  21. package/lib/hlasm/asm/listing.mjs +43 -0
  22. package/lib/hlasm/asm/output.mjs +45 -0
  23. package/lib/hlasm/asm/sections.mjs +159 -0
  24. package/lib/hlasm/asm/symbols.mjs +78 -0
  25. package/lib/hlasm/exec.mjs +27 -0
  26. package/lib/hlasm/expr.mjs +167 -0
  27. package/lib/hlasm/instr.mjs +48 -0
  28. package/lib/hlasm/locate.mjs +333 -0
  29. package/lib/hlasm/macro/authorization.mjs +126 -0
  30. package/lib/hlasm/macro/datasets.mjs +94 -0
  31. package/lib/hlasm/macro/io.mjs +139 -0
  32. package/lib/hlasm/macro/le.mjs +53 -0
  33. package/lib/hlasm/macro/linkage.mjs +143 -0
  34. package/lib/hlasm/macro/operator.mjs +66 -0
  35. package/lib/hlasm/macro/program.mjs +142 -0
  36. package/lib/hlasm/macro/recovery.mjs +80 -0
  37. package/lib/hlasm/macro/storage.mjs +117 -0
  38. package/lib/hlasm/macro/structured.mjs +131 -0
  39. package/lib/hlasm/model.mjs +72 -0
  40. package/lib/hlasm/operands.mjs +49 -0
  41. package/lib/hlasm/read.mjs +116 -0
  42. package/lib/hlasm.mjs +79 -0
  43. package/lib/inventory.mjs +10 -7
  44. package/lib/ironwork-ids.mjs +24 -0
  45. package/lib/ironwork.mjs +4 -1
  46. package/lib/kernel/registry.mjs +24 -17
  47. package/lib/kernel/shared-pass.mjs +138 -0
  48. package/lib/options.mjs +26 -4
  49. package/lib/policy.mjs +6 -0
  50. package/lib/precompile-cics.mjs +1 -1
  51. package/lib/revision.json +1 -1
  52. package/lib/sarif.mjs +3 -0
  53. package/lib/scan.mjs +21 -11
  54. package/lib/sets/abend.mjs +115 -13
  55. package/lib/sets/build.mjs +43 -37
  56. package/lib/sets/cics.mjs +5 -3
  57. package/lib/sets/compile.mjs +5 -3
  58. package/lib/sets/copybook.mjs +5 -3
  59. package/lib/sets/crypto.mjs +110 -0
  60. package/lib/sets/flow.mjs +32 -7
  61. package/lib/sets/hlasm.mjs +177 -0
  62. package/lib/sets/jcl.mjs +15 -1
  63. package/lib/sets/log.mjs +5 -3
  64. package/lib/sets/priv.mjs +5 -3
  65. package/lib/sets/secrets.mjs +110 -0
  66. package/lib/sets/semantics.mjs +5 -3
  67. package/lib/sets/web.mjs +104 -7
  68. package/lib/sources.mjs +12 -2
  69. package/package.json +1 -1
  70. package/rules/compliance-cobit2019.json +216 -0
  71. package/rules/compliance-dora.json +234 -0
  72. package/rules/compliance-ffiec.json +234 -0
  73. package/rules/compliance-nist80053.json +234 -0
  74. package/rules/gitleaks-mainframe.toml +61 -16
  75. package/rules/hlasm-instructions.json +2396 -0
  76. package/rules/hlasm-operations.json +451 -0
  77. package/rules/icsf-services.json +1178 -0
  78. package/schema/cobolwork.policy.schema.json +7 -0
@@ -0,0 +1,139 @@
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
+
6
+ const REG = /^\(\d{1,2}\)$/;
7
+ const MF_VAL = /^(L|\((E|M),\s*[A-Z$#@_][A-Z0-9$#@_]*\))$/i;
8
+
9
+ function parseAddr(text, label) {
10
+ const t = text.trim();
11
+ if (t === '') throw new HlasmSyntax(`${label} is missing`);
12
+ if (REG.test(t)) return t;
13
+ try {
14
+ parseAddress(t);
15
+ } catch (e) {
16
+ throw new HlasmSyntax(`${label} is not a valid address: ${e.message}`);
17
+ }
18
+ return t;
19
+ }
20
+
21
+ function parseLen(text, label) {
22
+ const t = text.trim();
23
+ if (t === '') throw new HlasmSyntax(`${label} is missing`);
24
+ if (t === "'S'") return t;
25
+ try {
26
+ parseExpression(t);
27
+ } catch (e) {
28
+ throw new HlasmSyntax(`${label} is not a valid length: ${e.message}`);
29
+ }
30
+ return t;
31
+ }
32
+
33
+ function parseMf(text) {
34
+ const t = text.trim();
35
+ if (!MF_VAL.test(t)) throw new HlasmSyntax(`MF= value ${t} is not L, (E,addr) or (M,addr)`);
36
+ return t;
37
+ }
38
+
39
+ function parseGet(st, ops) {
40
+ const { positional, keywords } = ops;
41
+ if (positional.length > 2) throw new HlasmSyntax('GET takes at most 2 positional operands');
42
+ if (positional.length === 0) throw new HlasmSyntax('GET requires a dcb address');
43
+
44
+ const dcb = parseAddr(positional[0], 'dcb address');
45
+ const area = positional.length > 1 ? parseAddr(positional[1], 'area address') : null;
46
+
47
+ const node = { kind: 'GET', dcb, area };
48
+ const allowed = new Set(['TYPE', 'MF']);
49
+ for (const [k, v] of keywords) {
50
+ if (!allowed.has(k)) throw new HlasmSyntax(`unknown keyword ${k} in GET`);
51
+ if (k === 'TYPE') {
52
+ if (v.trim().toUpperCase() !== 'P') throw new HlasmSyntax('GET TYPE= must be P');
53
+ node.type = v;
54
+ } else if (k === 'MF') {
55
+ node.mf = parseMf(v);
56
+ }
57
+ }
58
+ return node;
59
+ }
60
+
61
+ function parsePut(st, ops) {
62
+ const { positional, keywords } = ops;
63
+ if (positional.length > 2) throw new HlasmSyntax('PUT takes at most 2 positional operands');
64
+ if (positional.length === 0) throw new HlasmSyntax('PUT requires a dcb address');
65
+
66
+ const dcb = parseAddr(positional[0], 'dcb address');
67
+ const area = positional.length > 1 ? parseAddr(positional[1], 'area address') : null;
68
+
69
+ const node = { kind: 'PUT', dcb, area };
70
+ const allowed = new Set(['MF']);
71
+ for (const [k, v] of keywords) {
72
+ if (!allowed.has(k)) throw new HlasmSyntax(`unknown keyword ${k} in PUT`);
73
+ if (k === 'MF') {
74
+ node.mf = parseMf(v);
75
+ }
76
+ }
77
+ return node;
78
+ }
79
+
80
+ function parseRead(st, ops) {
81
+ const { positional, keywords } = ops;
82
+ if (positional.length < 4) throw new HlasmSyntax('READ requires decb, type, dcb, and area');
83
+ if (positional.length > 5) throw new HlasmSyntax('READ takes at most 5 positional operands');
84
+
85
+ const decb = positional[0].trim();
86
+ if (!NAME.test(decb)) throw new HlasmSyntax('READ decb name is not a valid symbol');
87
+
88
+ const type = positional[1].trim().toUpperCase();
89
+ if (!['SF', 'SB', 'SF64', 'SF64P'].includes(type)) throw new HlasmSyntax(`READ type ${type} is not SF, SB, SF64, or SF64P`);
90
+
91
+ const dcb = parseAddr(positional[2], 'dcb address');
92
+ const area = parseAddr(positional[3], 'area address');
93
+ const length = positional.length > 4 ? parseLen(positional[4], 'length') : null;
94
+
95
+ const node = { kind: 'READ', decb, type, dcb, area, length, keywords: {} };
96
+ const allowed = new Set(['MF']);
97
+ for (const [k, v] of keywords) {
98
+ if (!allowed.has(k)) throw new HlasmSyntax(`unknown keyword ${k} in READ`);
99
+ if (k === 'MF') {
100
+ node.mf = parseMf(v);
101
+ node.keywords.MF = v;
102
+ }
103
+ }
104
+ return node;
105
+ }
106
+
107
+ function parseWrite(st, ops) {
108
+ const { positional, keywords } = ops;
109
+ if (positional.length < 4) throw new HlasmSyntax('WRITE requires decb, type, dcb, and area');
110
+ if (positional.length > 5) throw new HlasmSyntax('WRITE takes at most 5 positional operands');
111
+
112
+ const decb = positional[0].trim();
113
+ if (!NAME.test(decb)) throw new HlasmSyntax('WRITE decb name is not a valid symbol');
114
+
115
+ const type = positional[1].trim().toUpperCase();
116
+ if (!['SF', 'SF64', 'SF64P'].includes(type)) throw new HlasmSyntax(`WRITE type ${type} is not SF, SF64, or SF64P`);
117
+
118
+ const dcb = parseAddr(positional[2], 'dcb address');
119
+ const area = parseAddr(positional[3], 'area address');
120
+ const length = positional.length > 4 ? parseLen(positional[4], 'length') : null;
121
+
122
+ const node = { kind: 'WRITE', decb, type, dcb, area, length, keywords: {} };
123
+ const allowed = new Set(['MF']);
124
+ for (const [k, v] of keywords) {
125
+ if (!allowed.has(k)) throw new HlasmSyntax(`unknown keyword ${k} in WRITE`);
126
+ if (k === 'MF') {
127
+ node.mf = parseMf(v);
128
+ node.keywords.MF = v;
129
+ }
130
+ }
131
+ return node;
132
+ }
133
+
134
+ export const parsers = {
135
+ GET: parseGet,
136
+ PUT: parsePut,
137
+ READ: parseRead,
138
+ WRITE: parseWrite
139
+ };
@@ -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.length > 0) 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.length > 0) 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,66 @@
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
+ const WTO_KW = new Set(['TEXT', 'ROUTCDE', 'DESC', 'MCSFLAG', 'MSGTYP', 'CART', 'CONSID', 'CONSNAME', 'KEY', 'TOKEN', 'MF']);
6
+ const WTOR_KW = new Set(['TEXT', 'ROUTCDE', 'MCSFLAG', 'DESC', 'MSGTYP', 'RPLYISUR', 'CART', 'CONSID', 'CONSNAME', 'KEY', 'TOKEN', 'MF']);
7
+
8
+ function parseWTO(st, ops) {
9
+ const { positional, keywords } = ops;
10
+ const kw = {};
11
+ for (const [k, v] of keywords) {
12
+ if (!WTO_KW.has(k)) throw new HlasmSyntax(`WTO: unknown keyword ${k}`);
13
+ kw[k] = v;
14
+ }
15
+ let text = null;
16
+ if (positional.length > 0) {
17
+ const first = positional[0].trim();
18
+ if (first.startsWith("'") || first.startsWith('(')) {
19
+ text = first;
20
+ } else {
21
+ throw new HlasmSyntax('WTO: first positional operand must be a quoted string or a parenthesised list');
22
+ }
23
+ }
24
+ if (positional.length > 1) {
25
+ throw new HlasmSyntax(`WTO: too many positional operands (${positional.length})`);
26
+ }
27
+ return { kind: 'WTO', text, keywords: kw };
28
+ }
29
+
30
+ function parseWTOR(st, ops) {
31
+ const { positional, keywords } = ops;
32
+ const kw = {};
33
+ for (const [k, v] of keywords) {
34
+ if (!WTOR_KW.has(k)) throw new HlasmSyntax(`WTOR: unknown keyword ${k}`);
35
+ kw[k] = v;
36
+ }
37
+ let text = null;
38
+ let reply = null;
39
+ let replyLength = null;
40
+ let ecb = null;
41
+
42
+ if (positional.length === 0) {
43
+ if (!keywords.has('TEXT')) {
44
+ throw new HlasmSyntax('WTOR: no positional operands and no TEXT= keyword');
45
+ }
46
+ } else if (positional.length === 4) {
47
+ const msg = positional[0].trim();
48
+ if (msg.startsWith("'") || msg.startsWith('(')) {
49
+ text = msg;
50
+ } else {
51
+ throw new HlasmSyntax('WTOR: first positional operand must be a quoted string or a parenthesised list');
52
+ }
53
+ reply = positional[1];
54
+ replyLength = positional[2];
55
+ ecb = positional[3];
56
+ } else {
57
+ throw new HlasmSyntax(`WTOR: expected 4 positional operands, got ${positional.length}`);
58
+ }
59
+
60
+ return { kind: 'WTOR', text, reply, replyLength, ecb, keywords: kw };
61
+ }
62
+
63
+ export const parsers = {
64
+ WTO: parseWTO,
65
+ WTOR: parseWTOR
66
+ };
@@ -0,0 +1,142 @@
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 } from '../operands.mjs';
4
+
5
+ const ADDR = /^(?:\(\d{1,2}\)|[A-Z$#@_][A-Z0-9$#@_]*|[\w$#@_]+(?:\s*[-+*/]\s*[\w$#@_]+)*)$/i;
6
+ const NUM_OR_ADDR = /^(?:\d+|\(\d{1,2}\)|[A-Z$#@_][A-Z0-9$#@_]*|[\w$#@_]+(?:\s*[-+*/]\s*[\w$#@_]+)*)$/i;
7
+ const YES_NO = /^(?:YES|NO)$/i;
8
+
9
+ function parseLink(st, ops) {
10
+ const kw = ops.keywords;
11
+ const allowed = new Set(['EP', 'EPLOC', 'DE', 'DCB', 'PARAM', 'VL', 'ID', 'ERRET', 'LSEARCH']);
12
+ for (const k of kw.keys()) if (!allowed.has(k)) throw new HlasmSyntax(`unknown keyword ${k}`);
13
+ if (ops.positional.length > 0) throw new HlasmSyntax('unexpected positional operand');
14
+ const epKeys = ['EP', 'EPLOC', 'DE'].filter(k => kw.has(k));
15
+ 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');
17
+ const ep = kw.has('EP') ? kw.get('EP').trim().toUpperCase() : null;
18
+ const eploc = kw.has('EPLOC') ? kw.get('EPLOC') : null;
19
+ const de = kw.has('DE') ? kw.get('DE') : null;
20
+ if (ep && !NAME.test(ep)) throw new HlasmSyntax('invalid EP name');
21
+ if (eploc && !ADDR.test(eploc.trim())) throw new HlasmSyntax('invalid EPLOC address');
22
+ if (de && !ADDR.test(de.trim())) throw new HlasmSyntax('invalid DE address');
23
+ if (kw.has('DCB') && !ADDR.test(kw.get('DCB').trim())) throw new HlasmSyntax('invalid DCB address');
24
+ if (kw.has('ERRET') && !ADDR.test(kw.get('ERRET').trim())) throw new HlasmSyntax('invalid ERRET address');
25
+ if (kw.has('LSEARCH') && !YES_NO.test(kw.get('LSEARCH').trim())) throw new HlasmSyntax('invalid LSEARCH value');
26
+ if (kw.has('VL') && !/^\d+$/.test(kw.get('VL').trim())) throw new HlasmSyntax('invalid VL value');
27
+ if (kw.has('ID') && !/^\d+$/.test(kw.get('ID').trim())) throw new HlasmSyntax('invalid ID value');
28
+ let param = null;
29
+ if (kw.has('PARAM')) {
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) };
45
+ }
46
+
47
+ function parseXctl(st, ops) {
48
+ const kw = ops.keywords;
49
+ const allowed = new Set(['EP', 'EPLOC', 'DE', 'DCB', 'LSEARCH']);
50
+ for (const k of kw.keys()) if (!allowed.has(k)) throw new HlasmSyntax(`unknown keyword ${k}`);
51
+ // The registers to restore before control passes: (reg) or (reg1,reg2), numbers or EQU symbols.
52
+ if (ops.positional.length > 1) throw new HlasmSyntax('too many positional operands');
53
+ 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
+ const epKeys = ['EP', 'EPLOC', 'DE'].filter(k => kw.has(k));
55
+ 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');
57
+ const ep = kw.has('EP') ? kw.get('EP').trim().toUpperCase() : null;
58
+ const eploc = kw.has('EPLOC') ? kw.get('EPLOC') : null;
59
+ const de = kw.has('DE') ? kw.get('DE') : null;
60
+ if (ep && !NAME.test(ep)) throw new HlasmSyntax('invalid EP name');
61
+ if (eploc && !ADDR.test(eploc.trim())) throw new HlasmSyntax('invalid EPLOC address');
62
+ if (de && !ADDR.test(de.trim())) throw new HlasmSyntax('invalid DE address');
63
+ if (kw.has('DCB') && !ADDR.test(kw.get('DCB').trim())) throw new HlasmSyntax('invalid DCB address');
64
+ if (kw.has('LSEARCH') && !YES_NO.test(kw.get('LSEARCH').trim())) throw new HlasmSyntax('invalid LSEARCH value');
65
+ return { kind: 'XCTL', ep, eploc, de, dcb: kw.has('DCB') ? kw.get('DCB') : null, keywords: Object.fromEntries(kw) };
66
+ }
67
+
68
+ function parseLoad(st, ops) {
69
+ const kw = ops.keywords;
70
+ const allowed = new Set(['EP', 'EPLOC', 'DE', 'DCB', 'ERRET', 'LSEARCH', 'LOADPT', 'LOADPT64', 'EXTINFO', 'RELATED']);
71
+ for (const k of kw.keys()) if (!allowed.has(k)) throw new HlasmSyntax(`unknown keyword ${k}`);
72
+ if (ops.positional.length > 0) throw new HlasmSyntax('unexpected positional operand');
73
+ const epKeys = ['EP', 'EPLOC', 'DE'].filter(k => kw.has(k));
74
+ 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');
76
+ const ep = kw.has('EP') ? kw.get('EP').trim().toUpperCase() : null;
77
+ const eploc = kw.has('EPLOC') ? kw.get('EPLOC') : null;
78
+ const de = kw.has('DE') ? kw.get('DE') : null;
79
+ if (ep && !NAME.test(ep)) throw new HlasmSyntax('invalid EP name');
80
+ if (eploc && !ADDR.test(eploc.trim())) throw new HlasmSyntax('invalid EPLOC address');
81
+ if (de && !ADDR.test(de.trim())) throw new HlasmSyntax('invalid DE address');
82
+ if (kw.has('DCB') && !ADDR.test(kw.get('DCB').trim())) throw new HlasmSyntax('invalid DCB address');
83
+ if (kw.has('ERRET') && !ADDR.test(kw.get('ERRET').trim())) throw new HlasmSyntax('invalid ERRET address');
84
+ if (kw.has('LSEARCH') && !YES_NO.test(kw.get('LSEARCH').trim())) throw new HlasmSyntax('invalid LSEARCH value');
85
+ if (kw.has('LOADPT') && !ADDR.test(kw.get('LOADPT').trim())) throw new HlasmSyntax('invalid LOADPT address');
86
+ if (kw.has('LOADPT64') && !ADDR.test(kw.get('LOADPT64').trim())) throw new HlasmSyntax('invalid LOADPT64 address');
87
+ if (kw.has('EXTINFO') && !ADDR.test(kw.get('EXTINFO').trim())) throw new HlasmSyntax('invalid EXTINFO address');
88
+ return { kind: 'LOAD', ep, eploc, de, dcb: kw.has('DCB') ? kw.get('DCB') : null, keywords: Object.fromEntries(kw) };
89
+ }
90
+
91
+ function parseAttach(st, ops) {
92
+ const kw = ops.keywords;
93
+ 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']);
94
+ for (const k of kw.keys()) if (!allowed.has(k)) throw new HlasmSyntax(`unknown keyword ${k}`);
95
+ if (ops.positional.length > 0) throw new HlasmSyntax('unexpected positional operand');
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) 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 && !ADDR.test(eploc.trim())) throw new HlasmSyntax('invalid EPLOC address');
104
+ if (de && !ADDR.test(de.trim())) throw new HlasmSyntax('invalid DE address');
105
+ if (kw.has('DCB') && !ADDR.test(kw.get('DCB').trim())) throw new HlasmSyntax('invalid DCB address');
106
+ if (kw.has('LPMOD') && !NUM_OR_ADDR.test(kw.get('LPMOD').trim())) throw new HlasmSyntax('invalid LPMOD value');
107
+ if (kw.has('DPMOD') && !NUM_OR_ADDR.test(kw.get('DPMOD').trim())) throw new HlasmSyntax('invalid DPMOD value');
108
+ if (kw.has('VL') && !/^\d+$/.test(kw.get('VL').trim())) throw new HlasmSyntax('invalid VL value');
109
+ if (kw.has('ECB') && !ADDR.test(kw.get('ECB').trim())) throw new HlasmSyntax('invalid ECB address');
110
+ if (kw.has('ETXR') && !ADDR.test(kw.get('ETXR').trim())) throw new HlasmSyntax('invalid ETXR address');
111
+ if (kw.has('GSPV') && !NUM_OR_ADDR.test(kw.get('GSPV').trim())) throw new HlasmSyntax('invalid GSPV value');
112
+ if (kw.has('GSPL') && !ADDR.test(kw.get('GSPL').trim())) throw new HlasmSyntax('invalid GSPL address');
113
+ if (kw.has('SHSPV') && !NUM_OR_ADDR.test(kw.get('SHSPV').trim())) throw new HlasmSyntax('invalid SHSPV value');
114
+ if (kw.has('SHSPL') && !ADDR.test(kw.get('SHSPL').trim())) throw new HlasmSyntax('invalid SHSPL address');
115
+ if (kw.has('SZERO') && !YES_NO.test(kw.get('SZERO').trim())) throw new HlasmSyntax('invalid SZERO value');
116
+ if (kw.has('TASKLIB') && !ADDR.test(kw.get('TASKLIB').trim())) throw new HlasmSyntax('invalid TASKLIB address');
117
+ if (kw.has('STAI') && !ADDR.test(kw.get('STAI').trim())) throw new HlasmSyntax('invalid STAI address');
118
+ if (kw.has('ESTAI') && !ADDR.test(kw.get('ESTAI').trim())) throw new HlasmSyntax('invalid ESTAI address');
119
+ if (kw.has('PURGE') && !/^(?:QUIESCE|NONE|HALT)$/i.test(kw.get('PURGE').trim())) throw new HlasmSyntax('invalid PURGE value');
120
+ if (kw.has('ASYNCH') && !YES_NO.test(kw.get('ASYNCH').trim())) throw new HlasmSyntax('invalid ASYNCH value');
121
+ if (kw.has('TERM') && !YES_NO.test(kw.get('TERM').trim())) throw new HlasmSyntax('invalid TERM value');
122
+ if (kw.has('ALCOPY') && !YES_NO.test(kw.get('ALCOPY').trim())) throw new HlasmSyntax('invalid ALCOPY value');
123
+ let param = null;
124
+ if (kw.has('PARAM')) {
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) };
140
+ }
141
+
142
+ export const parsers = Object.freeze({ LINK: parseLink, XCTL: parseXctl, LOAD: parseLoad, ATTACH: parseAttach });
@@ -0,0 +1,80 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // Parsers for the z/OS recovery and dump macros: ESTAE, ESPIE, ABEND, SNAP.
3
+ import { HlasmSyntax } from '../operands.mjs';
4
+
5
+ const ESTAE_KW = new Set(['PARAM', 'XCTL', 'PURGE', 'ASYNCH', 'TERM', 'ASCENV', 'RECORD', 'RELATED', 'MF']);
6
+ const ESPIE_KW = new Set(['PKM', 'MF']);
7
+ const ABEND_KW = new Set(['REASON', 'DUMPOPT', 'DUMPOPX', 'MF']);
8
+ const SNAP_KW = new Set(['DCB', 'TCB', 'ID', 'SDATA', 'PDATA', 'STORAGE', 'LIST', 'STRHDR', 'SUBPLST', 'DSPSTOR', 'MF']);
9
+
10
+ function checkKw(kw, allowed, op) {
11
+ for (const k of kw.keys()) {
12
+ if (!allowed.has(k)) throw new HlasmSyntax(`${op}: unknown keyword ${k}`);
13
+ }
14
+ }
15
+
16
+ function parseEstae(st, ops) {
17
+ const { positional, keywords } = ops;
18
+ checkKw(keywords, ESTAE_KW, 'ESTAE');
19
+ if (positional.length < 1 || positional.length > 3) {
20
+ throw new HlasmSyntax('ESTAE: expected 1 to 3 positional operands');
21
+ }
22
+ const exit = positional[0];
23
+ for (let i = 1; i < positional.length; i++) {
24
+ const p = positional[i].toUpperCase();
25
+ if (p !== 'CT' && p !== 'OV') {
26
+ throw new HlasmSyntax(`ESTAE: unexpected positional operand ${positional[i]}`);
27
+ }
28
+ }
29
+ return { kind: 'ESTAE', exit, keywords: Object.fromEntries(keywords) };
30
+ }
31
+
32
+ function parseEspie(st, ops) {
33
+ const { positional, keywords } = ops;
34
+ checkKw(keywords, ESPIE_KW, 'ESPIE');
35
+ if (positional.length < 1) throw new HlasmSyntax('ESPIE: missing action');
36
+ const action = positional[0].toUpperCase();
37
+ if (action !== 'SET' && action !== 'RESET' && action !== 'TEST') {
38
+ throw new HlasmSyntax(`ESPIE: invalid action ${positional[0]}`);
39
+ }
40
+ const rest = positional.slice(1);
41
+ if (action === 'SET') {
42
+ if (rest.length > 2) throw new HlasmSyntax('ESPIE SET: too many operands');
43
+ } else if (rest.length > 0) {
44
+ throw new HlasmSyntax(`ESPIE ${action}: no operands expected`);
45
+ }
46
+ return { kind: 'ESPIE', action, operands: rest, keywords: Object.fromEntries(keywords) };
47
+ }
48
+
49
+ function parseAbend(st, ops) {
50
+ const { positional, keywords } = ops;
51
+ checkKw(keywords, ABEND_KW, 'ABEND');
52
+ if (positional.length < 1 || positional.length > 4) {
53
+ throw new HlasmSyntax('ABEND: expected 1 to 4 positional operands');
54
+ }
55
+ const code = positional[0];
56
+ let dump = false, step = false;
57
+ for (let i = 1; i < positional.length; i++) {
58
+ const p = positional[i].toUpperCase();
59
+ if (p === '') continue;
60
+ if (p === 'DUMP') dump = true;
61
+ else if (p === 'STEP') step = true;
62
+ else if (p === 'USER' || p === 'SYSTEM') continue;
63
+ else throw new HlasmSyntax(`ABEND: unexpected positional operand ${positional[i]}`);
64
+ }
65
+ return { kind: 'ABEND', code, dump, step, keywords: Object.fromEntries(keywords) };
66
+ }
67
+
68
+ function parseSnap(st, ops) {
69
+ const { keywords } = ops;
70
+ checkKw(keywords, SNAP_KW, 'SNAP');
71
+ const dcb = keywords.has('DCB') ? keywords.get('DCB') : null;
72
+ return { kind: 'SNAP', dcb, keywords: Object.fromEntries(keywords) };
73
+ }
74
+
75
+ export const parsers = {
76
+ ESTAE: parseEstae,
77
+ ESPIE: parseEspie,
78
+ ABEND: parseAbend,
79
+ SNAP: parseSnap
80
+ };