@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.
Files changed (140) hide show
  1. package/README.md +49 -18
  2. package/STABILITY.md +76 -0
  3. package/bin/cobolwork.mjs +30 -7
  4. package/lib/bms.mjs +21 -7
  5. package/lib/build.mjs +108 -53
  6. package/lib/capabilities.mjs +9 -10
  7. package/lib/cics-commands.mjs +501 -9
  8. package/lib/compliance.mjs +11 -0
  9. package/lib/consequence.mjs +13 -0
  10. package/lib/control-workers.mjs +161 -0
  11. package/lib/control.mjs +52 -3
  12. package/lib/dataflow.mjs +176 -125
  13. package/lib/db2/cursor.mjs +15 -0
  14. package/lib/db2/read.mjs +170 -0
  15. package/lib/db2/rules.mjs +77 -0
  16. package/lib/db2/stmt/alter.mjs +473 -0
  17. package/lib/db2/stmt/grant.mjs +125 -0
  18. package/lib/db2/stmt/index.mjs +141 -0
  19. package/lib/db2/stmt/misc.mjs +325 -0
  20. package/lib/db2/stmt/routine.mjs +564 -0
  21. package/lib/db2/stmt/storage.mjs +146 -0
  22. package/lib/db2/stmt/table.mjs +540 -0
  23. package/lib/db2/stmt/view.mjs +146 -0
  24. package/lib/diff.mjs +17 -5
  25. package/lib/evidence/cli.mjs +14 -3
  26. package/lib/evidence/record.mjs +1 -1
  27. package/lib/evidence/store.mjs +44 -31
  28. package/lib/exec-reading.mjs +51 -0
  29. package/lib/execution.mjs +3 -2
  30. package/lib/explain.mjs +2 -0
  31. package/lib/exploitability.mjs +11 -2
  32. package/lib/hlasm/asm/data.mjs +13 -1
  33. package/lib/hlasm/asm/sections.mjs +3 -1
  34. package/lib/hlasm/instr.mjs +25 -0
  35. package/lib/hlasm/macro/authorization.mjs +24 -4
  36. package/lib/hlasm/macro/datasets.mjs +30 -17
  37. package/lib/hlasm/macro/io.mjs +68 -45
  38. package/lib/hlasm/macro/le.mjs +2 -2
  39. package/lib/hlasm/macro/operator.mjs +32 -21
  40. package/lib/hlasm/macro/program.mjs +126 -84
  41. package/lib/hlasm/macro/recovery.mjs +10 -5
  42. package/lib/hlasm/macro/storage.mjs +64 -14
  43. package/lib/hlasm/macro/structured.mjs +1 -1
  44. package/lib/hlasm/model.mjs +35 -10
  45. package/lib/hlasm/mvs38.mjs +47 -0
  46. package/lib/hlasm/operands.mjs +10 -0
  47. package/lib/hlasm/optable.mjs +61 -0
  48. package/lib/hlasm/read.mjs +22 -8
  49. package/lib/hlasm.mjs +44 -7
  50. package/lib/ims/dli.mjs +37 -0
  51. package/lib/ims/macro/dbd.mjs +299 -0
  52. package/lib/ims/macro/psb.mjs +286 -0
  53. package/lib/ims/model.mjs +149 -0
  54. package/lib/ims/operands.mjs +23 -0
  55. package/lib/ims/read.mjs +37 -0
  56. package/lib/ims/rules.mjs +135 -0
  57. package/lib/ironwork.mjs +17 -13
  58. package/lib/kernel/pds-archive.mjs +256 -0
  59. package/lib/kernel/registry.mjs +14 -8
  60. package/lib/kernel/shared-pass.mjs +34 -9
  61. package/lib/kernel/source-tree.mjs +104 -36
  62. package/lib/kernel/version-key.mjs +17 -0
  63. package/lib/layout.mjs +26 -31
  64. package/lib/parser.mjs +136 -17
  65. package/lib/pli/cursor.mjs +15 -0
  66. package/lib/pli/expr.mjs +101 -0
  67. package/lib/pli/include.mjs +82 -0
  68. package/lib/pli/layout.mjs +125 -0
  69. package/lib/pli/lex.mjs +198 -0
  70. package/lib/pli/program.mjs +280 -0
  71. package/lib/pli/rules/based.mjs +95 -0
  72. package/lib/pli/rules/conditions.mjs +68 -0
  73. package/lib/pli/rules/entry.mjs +130 -0
  74. package/lib/pli/rules/index.mjs +24 -0
  75. package/lib/pli/rules/preprocessor.mjs +55 -0
  76. package/lib/pli/statements.mjs +130 -0
  77. package/lib/pli/stmt/alloc.mjs +45 -0
  78. package/lib/pli/stmt/assignment.mjs +56 -0
  79. package/lib/pli/stmt/call.mjs +104 -0
  80. package/lib/pli/stmt/conditions.mjs +94 -0
  81. package/lib/pli/stmt/control.mjs +219 -0
  82. package/lib/pli/stmt/declare.mjs +149 -0
  83. package/lib/pli/stmt/exec.mjs +55 -0
  84. package/lib/pli/stmt/io.mjs +239 -0
  85. package/lib/pli/stmt/misc.mjs +4 -0
  86. package/lib/pli/stmt/preprocessor.mjs +242 -0
  87. package/lib/pli/stmt/procedure.mjs +258 -0
  88. package/lib/pli/stmt/stream.mjs +283 -0
  89. package/lib/pli/storage.mjs +129 -0
  90. package/lib/precompile-check.mjs +124 -0
  91. package/lib/precompile-cics.mjs +7 -3
  92. package/lib/reach.mjs +11 -2
  93. package/lib/revision.json +1 -1
  94. package/lib/sarif.mjs +41 -3
  95. package/lib/scan.mjs +7 -1
  96. package/lib/sets/abend.mjs +16 -6
  97. package/lib/sets/cics.mjs +15 -35
  98. package/lib/sets/compile.mjs +41 -15
  99. package/lib/sets/crypto.mjs +5 -3
  100. package/lib/sets/ddl.mjs +36 -0
  101. package/lib/sets/flow.mjs +18 -1
  102. package/lib/sets/hidden.mjs +5 -3
  103. package/lib/sets/hlasm.mjs +72 -12
  104. package/lib/sets/ims.mjs +139 -0
  105. package/lib/sets/log.mjs +6 -6
  106. package/lib/sets/opaque.mjs +27 -7
  107. package/lib/sets/pli.mjs +40 -0
  108. package/lib/sets/recon.mjs +5 -3
  109. package/lib/sets/secrets.mjs +5 -3
  110. package/lib/sets/semantics.mjs +3 -0
  111. package/lib/sets/web.mjs +36 -22
  112. package/lib/site.mjs +10 -0
  113. package/lib/sources.mjs +80 -20
  114. package/lib/statement-cursor.mjs +67 -0
  115. package/lib/verify.mjs +3 -2
  116. package/lib/version.mjs +6 -0
  117. package/package.json +3 -2
  118. package/rules/compliance-cobit2019.json +432 -1
  119. package/rules/compliance-dora.json +414 -1
  120. package/rules/compliance-ffiec.json +414 -1
  121. package/rules/compliance-nist80053.json +466 -1
  122. package/rules/hlasm-optables.json +8024 -0
  123. package/schema/cobolwork-baseline.schema.json +36 -0
  124. package/schema/cobolwork-build-provenance.schema.json +187 -0
  125. package/schema/cobolwork-build.schema.json +382 -0
  126. package/schema/cobolwork-capabilities.schema.json +239 -0
  127. package/schema/cobolwork-diff.schema.json +217 -0
  128. package/schema/cobolwork-evidence.schema.json +161 -0
  129. package/schema/cobolwork-execution.schema.json +53 -0
  130. package/schema/cobolwork-explain.schema.json +360 -0
  131. package/schema/cobolwork-finding.schema.json +465 -0
  132. package/schema/cobolwork-flow.schema.json +465 -0
  133. package/schema/cobolwork-gate.schema.json +211 -0
  134. package/schema/cobolwork-inventory.schema.json +206 -0
  135. package/schema/cobolwork-parse.schema.json +105 -0
  136. package/schema/cobolwork-reach.schema.json +74 -0
  137. package/schema/cobolwork-report.schema.json +559 -0
  138. package/schema/cobolwork-witness.schema.json +107 -0
  139. package/schema/cobolwork.baseline.schema.json +101 -0
  140. package/schema/cobolwork.site.schema.json +116 -0
@@ -1,23 +1,28 @@
1
1
  // SPDX-License-Identifier: AGPL-3.0-or-later
2
2
  // Parser for z/OS data-set macros: OPEN, CLOSE, and DCB.
3
- import { HlasmSyntax, NAME, splitOperands } from '../operands.mjs';
3
+ import { HlasmSyntax, NAME, remoteForm, splitOperands } from '../operands.mjs';
4
4
  import { parseAddress } from '../expr.mjs';
5
+ import { MVS38_KEYWORDS, mvs38Form } from '../mvs38.mjs';
5
6
 
6
7
  const OPEN_OPTS = new Set(['INPUT', 'OUTPUT', 'UPDAT', 'EXTEND', 'OUTIN', 'OUTINX', 'INOUT', 'RDBACK', 'DISP', 'LEAVE', 'REREAD']);
7
8
  const CLOSE_OPTS = new Set(['REREAD', 'LEAVE', 'REWIND', 'FREE', 'DISP']);
8
- const DCB_KEYWORDS = new Set(['BFALN', 'BFTEK', 'BLKSIZE', 'BUFCB', 'BUFL', 'BUFNO', 'BUFOFF', 'DCBE', 'DDNAME', 'DEVD', 'DSORG', 'EODAD', 'EROPT', 'EXLST', 'LRECL', 'MACRF', 'NCP', 'OPTCD', 'RECFM', 'SYNAD', 'KEYLEN', 'LIMCT', 'RKP', 'DEN', 'TRTCH', 'PRTSP', 'STACK', 'FUNC', 'MODE', 'CODE']);
9
+ const DCB_KEYWORDS = new Set(['BFALN', 'BFTEK', 'BLKSIZE', 'BUFCB', 'BUFL', 'BUFNO', 'BUFOFF', 'DCBE', 'DDNAME', 'DEVD', 'DSORG', 'EODAD', 'EROPT', 'EXLST', 'LRECL', 'MACRF', 'NCP', 'OPTCD', 'RECFM', 'SYNAD', 'KEYLEN', 'LIMCT', 'RKP', 'DEN', 'TRTCH', 'PRTSP', 'STACK', 'FUNC', 'MODE', 'CODE',
10
+ // EXCP (z/OS 3.1 DFSMSdfp Advanced Services, Data Control Block (DCB) Fields).
11
+ 'REPOS', 'EOEA', 'PCIA', 'SIOA', 'CENDA', 'XENDA', 'IMSK', 'PGFIX', 'IOBAD']);
9
12
 
10
13
  // (dcb,(options),dcb,(options),...): each DCB address followed by its options, written in parentheses,
11
- // as one bare word or omitted. A single DCB may be written without the outer parentheses.
12
- function parseDcbList(raw, validOpts) {
14
+ // as one bare word or omitted. A single DCB may be written without the outer parentheses. The list
15
+ // form (MF=L) and the execute form (MF=(E,addr)) may leave a DCB address empty, to be filled in by
16
+ // the other (z/OS 3.1 DFSMS Macro Instructions for Data Sets, OPEN and CLOSE list and execute forms).
17
+ function parseDcbList(raw, validOpts, emptyAllowed = false) {
13
18
  const text = raw.trim();
14
19
  const items = text.startsWith('(') && text.endsWith(')') ? splitOperands(text.slice(1, -1)) : [text];
15
20
  const dcbs = [];
16
21
  for (let i = 0; i < items.length; i++) {
17
22
  const dcbRaw = items[i].trim();
18
- if (!dcbRaw) throw new HlasmSyntax('empty dcb address in list');
19
- let dcb;
20
- try { dcb = parseAddress(dcbRaw); } catch { throw new HlasmSyntax(`invalid dcb address: ${dcbRaw}`); }
23
+ if (!dcbRaw && !emptyAllowed) throw new HlasmSyntax('empty dcb address in list');
24
+ let dcb = null;
25
+ if (dcbRaw) try { dcb = parseAddress(dcbRaw); } catch { throw new HlasmSyntax(`invalid dcb address: ${dcbRaw}`); }
21
26
  const next = (items[i + 1] ?? '').trim();
22
27
  const word = next.toUpperCase();
23
28
  let options = [];
@@ -31,10 +36,17 @@ function parseDcbList(raw, validOpts) {
31
36
  return dcbs;
32
37
  }
33
38
 
39
+ // The DCB list of OPEN or CLOSE and the form MF= gives it; the execute form may omit the list.
40
+ function dcbOperand(macro, ops, validOpts) {
41
+ const mf = ops.keywords.has('MF') ? remoteForm(ops.keywords.get('MF'), 'MF') : null;
42
+ if (ops.positional.length === 0 && mf?.form === 'E') return { dcbs: [], mf };
43
+ if (ops.positional.length !== 1) throw new HlasmSyntax(`${macro} requires exactly one positional operand`);
44
+ return { dcbs: parseDcbList(ops.positional[0], validOpts, Boolean(mf)), mf };
45
+ }
46
+
34
47
  export const parsers = {
35
48
  OPEN(st, ops) {
36
- if (ops.positional.length !== 1) throw new HlasmSyntax('OPEN requires exactly one positional operand');
37
- const dcbs = parseDcbList(ops.positional[0], OPEN_OPTS);
49
+ const { dcbs, mf } = dcbOperand('OPEN', ops, OPEN_OPTS);
38
50
  const keywords = {};
39
51
  for (const [k, v] of ops.keywords) {
40
52
  if (k === 'TYPE') {
@@ -49,11 +61,10 @@ export const parsers = {
49
61
  throw new HlasmSyntax(`unknown keyword for OPEN: ${k}`);
50
62
  }
51
63
  }
52
- return { kind: 'OPEN', dcbs, keywords };
64
+ return { kind: 'OPEN', dcbs, ...(mf ? { mf: mf.form === 'L' ? 'L' : { form: 'E', list: mf.list } } : {}), keywords };
53
65
  },
54
66
  CLOSE(st, ops) {
55
- if (ops.positional.length !== 1) throw new HlasmSyntax('CLOSE requires exactly one positional operand');
56
- const dcbs = parseDcbList(ops.positional[0], CLOSE_OPTS);
67
+ const { dcbs, mf } = dcbOperand('CLOSE', ops, CLOSE_OPTS);
57
68
  const keywords = {};
58
69
  for (const [k, v] of ops.keywords) {
59
70
  if (k === 'TYPE') {
@@ -68,10 +79,10 @@ export const parsers = {
68
79
  throw new HlasmSyntax(`unknown keyword for CLOSE: ${k}`);
69
80
  }
70
81
  }
71
- return { kind: 'CLOSE', dcbs, keywords };
82
+ return { kind: 'CLOSE', dcbs, ...(mf ? { mf: mf.form === 'L' ? 'L' : { form: 'E', list: mf.list } } : {}), keywords };
72
83
  },
73
- DCB(st, ops) {
74
- if (ops.positional.length > 0) throw new HlasmSyntax('DCB takes no positional operands');
84
+ DCB(st, ops, opts) {
85
+ if (ops.positional.some((p) => p.trim() !== '')) throw new HlasmSyntax('DCB takes no positional operands');
75
86
  const keywords = {};
76
87
  let ddname = null;
77
88
  let dsorg = null;
@@ -79,8 +90,10 @@ export const parsers = {
79
90
  let recfm = null;
80
91
  let lrecl = null;
81
92
  let blksize = null;
93
+ const legacy = {};
82
94
  for (const [k, v] of ops.keywords) {
83
- if (!DCB_KEYWORDS.has(k)) throw new HlasmSyntax(`unknown keyword for DCB: ${k}`);
95
+ if (MVS38_KEYWORDS.DCB.has(k)) mvs38Form(opts, legacy, 'DCB', `${k}=${String(v).trim()}`);
96
+ else if (!DCB_KEYWORDS.has(k)) throw new HlasmSyntax(`unknown keyword for DCB: ${k}`);
84
97
  keywords[k] = v;
85
98
  if (k === 'DDNAME') ddname = v.toUpperCase();
86
99
  else if (k === 'DSORG') dsorg = v;
@@ -89,6 +102,6 @@ export const parsers = {
89
102
  else if (k === 'LRECL') lrecl = v;
90
103
  else if (k === 'BLKSIZE') blksize = v;
91
104
  }
92
- return { kind: 'DCB', name: st.name, ddname, dsorg, macrf, recfm, lrecl, blksize, keywords };
105
+ return { kind: 'DCB', ...legacy, name: st.name, ddname, dsorg, macrf, recfm, lrecl, blksize, keywords };
93
106
  }
94
107
  };
@@ -2,6 +2,7 @@
2
2
  // Parsers for the z/OS record macros GET, PUT, READ, and WRITE in their standard forms.
3
3
  import { HlasmSyntax, NAME } from '../operands.mjs';
4
4
  import { parseExpression, parseAddress } from '../expr.mjs';
5
+ import { MVS38_BTAM_TYPES, MVS38_VTAM_RPL_KEYWORDS, mvs38Form } from '../mvs38.mjs';
5
6
 
6
7
  const REG = /^\(\d{1,2}\)$/;
7
8
  const MF_VAL = /^(L|\((E|M),\s*[A-Z$#@_][A-Z0-9$#@_]*\))$/i;
@@ -36,7 +37,17 @@ function parseMf(text) {
36
37
  return t;
37
38
  }
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
+
39
49
  function parseGet(st, ops) {
50
+ if (ops.keywords.has('RPL')) return vsamRequest('GET', ops);
40
51
  const { positional, keywords } = ops;
41
52
  if (positional.length > 2) throw new HlasmSyntax('GET takes at most 2 positional operands');
42
53
  if (positional.length === 0) throw new HlasmSyntax('GET requires a dcb address');
@@ -59,6 +70,7 @@ function parseGet(st, ops) {
59
70
  }
60
71
 
61
72
  function parsePut(st, ops) {
73
+ if (ops.keywords.has('RPL')) return vsamRequest('PUT', ops);
62
74
  const { positional, keywords } = ops;
63
75
  if (positional.length > 2) throw new HlasmSyntax('PUT takes at most 2 positional operands');
64
76
  if (positional.length === 0) throw new HlasmSyntax('PUT requires a dcb address');
@@ -77,60 +89,71 @@ function parsePut(st, ops) {
77
89
  return node;
78
90
  }
79
91
 
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;
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) };
105
116
  }
106
117
 
107
- function parseWrite(st, ops) {
118
+ function blockRequest(macro, st, ops, opts) {
108
119
  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
-
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`);
112
134
  const decb = positional[0].trim();
113
- if (!NAME.test(decb)) throw new HlasmSyntax('WRITE decb name is not a valid symbol');
114
-
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
+ }
115
138
  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;
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);
129
149
  }
130
- }
150
+ });
131
151
  return node;
132
152
  }
133
153
 
154
+ const parseRead = (st, ops, opts) => blockRequest('READ', st, ops, opts);
155
+ const parseWrite = (st, ops, opts) => blockRequest('WRITE', st, ops, opts);
156
+
134
157
  export const parsers = {
135
158
  GET: parseGet,
136
159
  PUT: parsePut,
@@ -17,13 +17,13 @@ function checkKeywords(ops, allowed, op) {
17
17
  }
18
18
 
19
19
  function parseCEEENTRY(st, ops) {
20
- if (ops.positional.length > 0) throw new HlasmSyntax('CEEENTRY takes no positional operands');
20
+ if (ops.positional.some((p) => p.trim() !== '')) throw new HlasmSyntax('CEEENTRY takes no positional operands');
21
21
  const keywords = checkKeywords(ops, CEEENTRY_KW, 'CEEENTRY');
22
22
  return { kind: 'CEEENTRY', name: st.name, main: keywords.MAIN || null, ppa: keywords.PPA || null, keywords };
23
23
  }
24
24
 
25
25
  function parseCEETERM(st, ops) {
26
- if (ops.positional.length > 0) throw new HlasmSyntax('CEETERM takes no positional operands');
26
+ if (ops.positional.some((p) => p.trim() !== '')) throw new HlasmSyntax('CEETERM takes no positional operands');
27
27
  const keywords = checkKeywords(ops, CEETERM_KW, 'CEETERM');
28
28
  return { kind: 'CEETERM', rc: keywords.RC || null, keywords };
29
29
  }
@@ -2,8 +2,13 @@
2
2
  // Parser for the z/OS operator-communication macros WTO and WTOR in their standard form.
3
3
  import { HlasmSyntax } from '../operands.mjs';
4
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']);
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('(');
7
12
 
8
13
  function parseWTO(st, ops) {
9
14
  const { positional, keywords } = ops;
@@ -12,23 +17,24 @@ function parseWTO(st, ops) {
12
17
  if (!WTO_KW.has(k)) throw new HlasmSyntax(`WTO: unknown keyword ${k}`);
13
18
  kw[k] = v;
14
19
  }
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
- }
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');
23
27
  }
24
- if (positional.length > 1) {
25
- throw new HlasmSyntax(`WTO: too many positional operands (${positional.length})`);
28
+ if (lines.length > 1 && !lines.every((l) => l.startsWith('('))) {
29
+ throw new HlasmSyntax(`WTO: too many positional operands (${lines.length})`);
26
30
  }
27
- return { kind: 'WTO', text, keywords: kw };
31
+ return { kind: 'WTO', text: lines.length ? lines.join(',') : null, keywords: kw };
28
32
  }
29
33
 
30
34
  function parseWTOR(st, ops) {
31
- const { positional, keywords } = ops;
35
+ const { keywords } = ops;
36
+ const positional = [...ops.positional];
37
+ while (positional.length > 4 && positional.at(-1).trim() === '') positional.pop();
32
38
  const kw = {};
33
39
  for (const [k, v] of keywords) {
34
40
  if (!WTOR_KW.has(k)) throw new HlasmSyntax(`WTOR: unknown keyword ${k}`);
@@ -39,20 +45,25 @@ function parseWTOR(st, ops) {
39
45
  let replyLength = null;
40
46
  let ecb = null;
41
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);
42
53
  if (positional.length === 0) {
43
- if (!keywords.has('TEXT')) {
54
+ if (!keywords.has('TEXT') && !execute) {
44
55
  throw new HlasmSyntax('WTOR: no positional operands and no TEXT= keyword');
45
56
  }
46
- } else if (positional.length === 4) {
57
+ } else if (positional.length === 4 || (remote && positional.length <= 4)) {
47
58
  const msg = positional[0].trim();
48
- if (msg.startsWith("'") || msg.startsWith('(')) {
59
+ if (isMessage(msg)) {
49
60
  text = msg;
50
- } else {
61
+ } else if (!(msg === '' && (execute || keywords.has('TEXT')))) {
51
62
  throw new HlasmSyntax('WTOR: first positional operand must be a quoted string or a parenthesised list');
52
63
  }
53
- reply = positional[1];
54
- replyLength = positional[2];
55
- ecb = positional[3];
64
+ reply = positional[1] ?? null;
65
+ replyLength = positional[2] ?? null;
66
+ ecb = positional[3] ?? null;
56
67
  } else {
57
68
  throw new HlasmSyntax(`WTOR: expected 4 positional operands, got ${positional.length}`);
58
69
  }