@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
@@ -0,0 +1,258 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // Parsers for PL/I PROCEDURE, ENTRY, PACKAGE, BEGIN, and END statements.
3
+
4
+ const PROC_OPTIONS = new Set([
5
+ 'RECURSIVE', 'NONRECURSIVE', 'REORDER', 'ORDER', 'EXTERNAL', 'EXT',
6
+ 'CHARGRAPHIC', 'NOCHARGRAPHIC', 'REDUCIBLE', 'IRREDUCIBLE'
7
+ ]);
8
+
9
+ const BEGIN_OPTIONS = new Set([
10
+ 'ORDER', 'REORDER', 'CHARGRAPHIC', 'NOCHARGRAPHIC'
11
+ ]);
12
+
13
+ function parseNameList(c, failMsg) {
14
+ const items = c.items();
15
+ const names = [];
16
+ for (const item of items) {
17
+ if (item.length === 1 && item[0].t === 'op' && item[0].v === '*') {
18
+ names.push('*');
19
+ } else if (item.length === 1 && item[0].t === 'word') {
20
+ names.push(item[0].u);
21
+ } else {
22
+ c.fail(failMsg);
23
+ }
24
+ }
25
+ return names;
26
+ }
27
+
28
+ function parseOptionsList(c) {
29
+ const items = c.items();
30
+ const options = [];
31
+ for (const item of items) {
32
+ if (item.length === 0) continue;
33
+ const first = item[0];
34
+ if (first.t !== 'word') {
35
+ c.fail('an option name');
36
+ }
37
+ const opt = { name: first.u, args: null };
38
+ let i = 1;
39
+ if (i < item.length && item[i].t === 'op' && item[i].v === '(') {
40
+ const start = i;
41
+ let depth = 0;
42
+ for (; i < item.length; i++) {
43
+ if (item[i].t === 'op' && item[i].v === '(') depth++;
44
+ else if (item[i].t === 'op' && item[i].v === ')') {
45
+ depth--;
46
+ if (depth === 0) { i++; break; }
47
+ }
48
+ }
49
+ if (depth !== 0) {
50
+ c.fail("matching ')'");
51
+ }
52
+ opt.args = item.slice(start + 1, i - 1);
53
+ }
54
+ if (i < item.length) {
55
+ c.fail('end of option');
56
+ }
57
+ options.push(opt);
58
+ }
59
+ return options;
60
+ }
61
+
62
+ function parseProcedure(c, stmt) {
63
+ c.expectWord(['PROCEDURE', 'PROC']);
64
+ const name = stmt.labels.length ? stmt.labels[stmt.labels.length - 1].name : null;
65
+ const node = {
66
+ kind: 'PROCEDURE',
67
+ name,
68
+ params: [],
69
+ options: [],
70
+ returns: null,
71
+ recursive: false,
72
+ external: null,
73
+ order: null
74
+ };
75
+
76
+ if (c.isOp('(')) {
77
+ const items = c.items();
78
+ for (const item of items) {
79
+ if (item.length === 1 && item[0].t === 'word') {
80
+ node.params.push(item[0].u);
81
+ } else {
82
+ c.fail('a parameter name');
83
+ }
84
+ }
85
+ }
86
+
87
+ while (!c.done()) {
88
+ if (c.isWord('OPTIONS')) {
89
+ c.next();
90
+ if (!c.isOp('(')) c.fail("'('");
91
+ node.options.push(...parseOptionsList(c));
92
+ } else if (c.isWord('RETURNS')) {
93
+ c.next();
94
+ if (!c.isOp('(')) c.fail("'('");
95
+ node.returns = c.group();
96
+ } else if (c.isWord(['RECURSIVE', 'NONRECURSIVE'])) {
97
+ const t = c.next();
98
+ node.recursive = t.u === 'RECURSIVE';
99
+ } else if (c.isWord(['ORDER', 'REORDER'])) {
100
+ const t = c.next();
101
+ node.order = t.u;
102
+ } else if (c.isWord(['EXTERNAL', 'EXT'])) {
103
+ c.next();
104
+ if (c.isOp('(')) {
105
+ const inner = c.group();
106
+ if (inner.length === 1 && inner[0].t === 'lit') {
107
+ node.external = inner[0].v;
108
+ } else if (inner.length === 1 && inner[0].t === 'word') {
109
+ node.external = inner[0].u;
110
+ } else {
111
+ c.fail("a name or literal");
112
+ }
113
+ } else {
114
+ node.external = true;
115
+ }
116
+ } else if (c.isWord(['CHARGRAPHIC', 'NOCHARGRAPHIC', 'REDUCIBLE', 'IRREDUCIBLE'])) {
117
+ const t = c.next();
118
+ node.options.push({ name: t.u, args: null });
119
+ } else {
120
+ c.fail('a PROCEDURE option');
121
+ }
122
+ }
123
+
124
+ return node;
125
+ }
126
+
127
+ function parseEntry(c, stmt) {
128
+ c.expectWord('ENTRY');
129
+ const name = stmt.labels.length ? stmt.labels[stmt.labels.length - 1].name : null;
130
+ const node = {
131
+ kind: 'ENTRY',
132
+ name,
133
+ params: [],
134
+ options: [],
135
+ returns: null,
136
+ recursive: false,
137
+ external: null,
138
+ order: null
139
+ };
140
+
141
+ if (c.isOp('(')) {
142
+ const items = c.items();
143
+ for (const item of items) {
144
+ if (item.length === 1 && item[0].t === 'word') {
145
+ node.params.push(item[0].u);
146
+ } else {
147
+ c.fail('a parameter name');
148
+ }
149
+ }
150
+ }
151
+
152
+ while (!c.done()) {
153
+ if (c.isWord('RETURNS')) {
154
+ c.next();
155
+ if (!c.isOp('(')) c.fail("'('");
156
+ node.returns = c.group();
157
+ } else if (c.isWord('OPTIONS')) {
158
+ c.next();
159
+ if (!c.isOp('(')) c.fail("'('");
160
+ node.options.push(...parseOptionsList(c));
161
+ } else if (c.isWord(['EXTERNAL', 'EXT'])) {
162
+ c.next();
163
+ if (c.isOp('(')) {
164
+ const inner = c.group();
165
+ if (inner.length === 1 && inner[0].t === 'lit') {
166
+ node.external = inner[0].v;
167
+ } else if (inner.length === 1 && inner[0].t === 'word') {
168
+ node.external = inner[0].u;
169
+ } else {
170
+ c.fail("a name or literal");
171
+ }
172
+ } else {
173
+ node.external = true;
174
+ }
175
+ } else {
176
+ c.fail('an ENTRY option');
177
+ }
178
+ }
179
+
180
+ return node;
181
+ }
182
+
183
+ function parsePackage(c, stmt) {
184
+ c.expectWord('PACKAGE');
185
+ const name = stmt.labels.length ? stmt.labels[stmt.labels.length - 1].name : null;
186
+ const node = {
187
+ kind: 'PACKAGE',
188
+ name,
189
+ exports: null,
190
+ reserves: null,
191
+ options: []
192
+ };
193
+
194
+ while (!c.done()) {
195
+ if (c.isWord('EXPORTS')) {
196
+ c.next();
197
+ if (!c.isOp('(')) c.fail("'('");
198
+ node.exports = parseNameList(c, 'an export name');
199
+ } else if (c.isWord('RESERVES')) {
200
+ c.next();
201
+ if (!c.isOp('(')) c.fail("'('");
202
+ node.reserves = parseNameList(c, 'a reserve name');
203
+ } else if (c.isWord('OPTIONS')) {
204
+ c.next();
205
+ if (!c.isOp('(')) c.fail("'('");
206
+ node.options.push(...parseOptionsList(c));
207
+ } else {
208
+ c.fail('a PACKAGE option');
209
+ }
210
+ }
211
+
212
+ return node;
213
+ }
214
+
215
+ function parseBegin(c, stmt) {
216
+ c.expectWord('BEGIN');
217
+ const node = {
218
+ kind: 'BEGIN',
219
+ options: []
220
+ };
221
+
222
+ while (!c.done()) {
223
+ if (c.isWord(['OPTIONS', 'ORDER', 'REORDER', 'CHARGRAPHIC', 'NOCHARGRAPHIC'])) {
224
+ const t = c.next();
225
+ if (t.u === 'OPTIONS') {
226
+ if (!c.isOp('(')) c.fail("'('");
227
+ node.options.push(...parseOptionsList(c));
228
+ } else {
229
+ node.options.push({ name: t.u, args: null });
230
+ }
231
+ } else {
232
+ c.fail('a BEGIN option');
233
+ }
234
+ }
235
+
236
+ return node;
237
+ }
238
+
239
+ function parseEnd(c, stmt) {
240
+ c.expectWord('END');
241
+ let label = null;
242
+ if (!c.done()) {
243
+ if (c.isWord()) {
244
+ label = c.next().u;
245
+ } else {
246
+ c.fail('a label or end of statement');
247
+ }
248
+ }
249
+ return { kind: 'END', label };
250
+ }
251
+
252
+ export const parsers = {
253
+ PROCEDURE: parseProcedure,
254
+ ENTRY: parseEntry,
255
+ PACKAGE: parsePackage,
256
+ BEGIN: parseBegin,
257
+ END: parseEnd
258
+ };
@@ -0,0 +1,283 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // Parsers for PL/I stream I/O statements: GET, PUT, DISPLAY, FORMAT, and FLUSH.
3
+ import { cursor, split } from '../cursor.mjs';
4
+ import { parseExpression, parseReference } from '../expr.mjs';
5
+
6
+ function parseFormatItem(c) {
7
+ const start = c.pos;
8
+ let factor = null;
9
+ // A parenthesised group followed by another item is an iteration factor, (N) F(5,2); one followed
10
+ // by ',' or the end is a nested format list, (A, X(2)).
11
+ if (c.isOp('(')) {
12
+ const inner = c.group();
13
+ const next = c.peek();
14
+ if (next && (next.t === 'word' || (next.t === 'op' && next.v === '('))) {
15
+ const s = cursor(inner);
16
+ factor = parseExpression(s);
17
+ if (!s.done()) s.fail('end of iteration factor');
18
+ } else {
19
+ c.pos = start;
20
+ }
21
+ } else if (c.peek() && c.peek().t === 'num') {
22
+ factor = c.next();
23
+ }
24
+
25
+ if (c.isOp('(')) {
26
+ const inner = c.group();
27
+ const s = cursor(inner);
28
+ const items = [];
29
+ while (!s.done()) {
30
+ const item = parseFormatItem(s);
31
+ items.push(item);
32
+ if (!s.done()) {
33
+ if (s.isOp(',')) s.next();
34
+ else s.fail("',' or ')'");
35
+ }
36
+ }
37
+ const toks = c.slice(start);
38
+ return { factor, items, toks };
39
+ }
40
+
41
+ const t = c.peek();
42
+ if (!t) c.fail('a format item');
43
+ if (t.t === 'word') {
44
+ c.next();
45
+ if (c.isOp('(')) c.group();
46
+ else if (t.u === 'P' && c.peek() && c.peek().t === 'lit') c.next();
47
+ } else if (t.t === 'lit') {
48
+ c.next();
49
+ } else {
50
+ c.fail('a format item');
51
+ }
52
+ return { factor, items: null, toks: c.slice(start) };
53
+ }
54
+
55
+ function parseFormatList(c) {
56
+ const inner = c.group();
57
+ const s = cursor(inner);
58
+ const items = [];
59
+ while (!s.done()) {
60
+ const item = parseFormatItem(s);
61
+ items.push(item);
62
+ if (!s.done()) {
63
+ if (s.isOp(',')) s.next();
64
+ else s.fail("',' or ')'");
65
+ }
66
+ }
67
+ return items;
68
+ }
69
+
70
+ // A repetitive specification, (X(I) DO I = 1 TO N), is an item whose parentheses hold a DO at depth one.
71
+ function isRepetitive(toks) {
72
+ if (!toks.length || toks[0].t !== 'op' || toks[0].v !== '(') return false;
73
+ let depth = 0;
74
+ for (const t of toks) {
75
+ if (t.t === 'op' && t.v === '(') depth++;
76
+ else if (t.t === 'op' && t.v === ')') depth--;
77
+ else if (depth === 1 && t.t === 'word' && t.u === 'DO') return true;
78
+ }
79
+ return false;
80
+ }
81
+
82
+ function parseDataList(c) {
83
+ const items = [];
84
+ for (const toks of split(c.group(), ',')) {
85
+ if (!toks.length) c.fail('a data item');
86
+ if (isRepetitive(toks)) { items.push({ t: 'repetitive', toks }); continue; }
87
+ const s = cursor(toks);
88
+ items.push(parseExpression(s));
89
+ if (!s.done()) s.fail("',' or ')'");
90
+ }
91
+ return items;
92
+ }
93
+
94
+ function parsePut(c, stmt, ctx) {
95
+ c.expectWord('PUT');
96
+ const node = { kind: 'PUT', file: null, string: null, page: false, line: null, skip: null, mode: null, data: [], formats: [] };
97
+
98
+ while (!c.done()) {
99
+ if (c.isWord('FILE')) {
100
+ c.next();
101
+ c.expectOp('(');
102
+ node.file = parseReference(c);
103
+ c.expectOp(')');
104
+ } else if (c.isWord('STRING')) {
105
+ c.next();
106
+ c.expectOp('(');
107
+ node.string = parseExpression(c);
108
+ c.expectOp(')');
109
+ } else if (c.isWord('PAGE')) {
110
+ c.next();
111
+ node.page = true;
112
+ } else if (c.isWord('LINE')) {
113
+ c.next();
114
+ c.expectOp('(');
115
+ node.line = parseExpression(c);
116
+ c.expectOp(')');
117
+ } else if (c.isWord('SKIP')) {
118
+ c.next();
119
+ if (c.isOp('(')) {
120
+ c.next();
121
+ node.skip = parseExpression(c);
122
+ c.expectOp(')');
123
+ } else {
124
+ node.skip = true;
125
+ }
126
+ } else if (c.isWord('LIST')) {
127
+ c.next();
128
+ node.mode = 'LIST';
129
+ if (c.isOp('(')) {
130
+ node.data = parseDataList(c);
131
+ }
132
+ } else if (c.isWord('EDIT')) {
133
+ c.next();
134
+ node.mode = 'EDIT';
135
+ // EDIT takes one or more (data list) (format list) pairs.
136
+ do {
137
+ node.data.push(...parseDataList(c));
138
+ node.formats.push(parseFormatList(c));
139
+ } while (c.isOp('('));
140
+ } else if (c.isWord('DATA')) {
141
+ c.next();
142
+ node.mode = 'DATA';
143
+ if (c.isOp('(')) {
144
+ node.data = parseDataList(c);
145
+ }
146
+ } else {
147
+ c.fail('PUT option');
148
+ }
149
+ }
150
+ return node;
151
+ }
152
+
153
+ function parseGet(c, stmt, ctx) {
154
+ c.expectWord('GET');
155
+ const node = { kind: 'GET', file: null, string: null, page: false, line: null, skip: null, copy: null, mode: null, data: [], formats: [] };
156
+
157
+ while (!c.done()) {
158
+ if (c.isWord('FILE')) {
159
+ c.next();
160
+ c.expectOp('(');
161
+ node.file = parseReference(c);
162
+ c.expectOp(')');
163
+ } else if (c.isWord('STRING')) {
164
+ c.next();
165
+ c.expectOp('(');
166
+ node.string = parseExpression(c);
167
+ c.expectOp(')');
168
+ } else if (c.isWord('PAGE')) {
169
+ c.next();
170
+ node.page = true;
171
+ } else if (c.isWord('LINE')) {
172
+ c.next();
173
+ c.expectOp('(');
174
+ node.line = parseExpression(c);
175
+ c.expectOp(')');
176
+ } else if (c.isWord('SKIP')) {
177
+ c.next();
178
+ if (c.isOp('(')) {
179
+ c.next();
180
+ node.skip = parseExpression(c);
181
+ c.expectOp(')');
182
+ } else {
183
+ node.skip = true;
184
+ }
185
+ } else if (c.isWord('COPY')) {
186
+ c.next();
187
+ if (c.isOp('(')) {
188
+ c.next();
189
+ node.copy = parseReference(c);
190
+ c.expectOp(')');
191
+ } else {
192
+ node.copy = true;
193
+ }
194
+ } else if (c.isWord('LIST')) {
195
+ c.next();
196
+ node.mode = 'LIST';
197
+ if (c.isOp('(')) {
198
+ node.data = parseDataList(c);
199
+ }
200
+ } else if (c.isWord('EDIT')) {
201
+ c.next();
202
+ node.mode = 'EDIT';
203
+ // EDIT takes one or more (data list) (format list) pairs.
204
+ do {
205
+ node.data.push(...parseDataList(c));
206
+ node.formats.push(parseFormatList(c));
207
+ } while (c.isOp('('));
208
+ } else if (c.isWord('DATA')) {
209
+ c.next();
210
+ node.mode = 'DATA';
211
+ if (c.isOp('(')) {
212
+ node.data = parseDataList(c);
213
+ }
214
+ } else {
215
+ c.fail('GET option');
216
+ }
217
+ }
218
+ return node;
219
+ }
220
+
221
+ function parseDisplay(c, stmt, ctx) {
222
+ c.expectWord('DISPLAY');
223
+ c.expectOp('(');
224
+ const value = parseExpression(c);
225
+ c.expectOp(')');
226
+ const node = { kind: 'DISPLAY', value, reply: null, event: null, routcde: null, desc: null };
227
+
228
+ while (!c.done()) {
229
+ if (c.isWord('REPLY')) {
230
+ c.next();
231
+ c.expectOp('(');
232
+ node.reply = parseReference(c);
233
+ c.expectOp(')');
234
+ } else if (c.isWord('EVENT')) {
235
+ c.next();
236
+ c.expectOp('(');
237
+ node.event = parseReference(c);
238
+ c.expectOp(')');
239
+ } else if (c.isWord('ROUTCDE')) {
240
+ c.next();
241
+ c.expectOp('(');
242
+ node.routcde = parseDataList(c);
243
+ c.expectOp(')');
244
+ } else if (c.isWord('DESC')) {
245
+ c.next();
246
+ c.expectOp('(');
247
+ node.desc = parseDataList(c);
248
+ c.expectOp(')');
249
+ } else {
250
+ c.fail('DISPLAY option');
251
+ }
252
+ }
253
+ return node;
254
+ }
255
+
256
+ function parseFormat(c, stmt, ctx) {
257
+ c.expectWord('FORMAT');
258
+ const items = parseFormatList(c);
259
+ return { kind: 'FORMAT', items };
260
+ }
261
+
262
+ function parseFlush(c, stmt, ctx) {
263
+ c.expectWord('FLUSH');
264
+ c.expectWord('FILE');
265
+ c.expectOp('(');
266
+ let file;
267
+ if (c.isOp('*')) {
268
+ c.next();
269
+ file = '*';
270
+ } else {
271
+ file = parseReference(c);
272
+ }
273
+ c.expectOp(')');
274
+ return { kind: 'FLUSH', file };
275
+ }
276
+
277
+ export const parsers = {
278
+ PUT: parsePut,
279
+ GET: parseGet,
280
+ DISPLAY: parseDisplay,
281
+ FORMAT: parseFormat,
282
+ FLUSH: parseFlush,
283
+ };
@@ -0,0 +1,129 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // The storage a PL/I element takes and the boundary it needs, from its DECLARE attributes, as the
3
+ // Enterprise PL/I Language Reference's alignment table gives them (LP(32), OFFSETSIZE(4)). Sizes
4
+ // and alignments are in bits so unaligned bit strings pack.
5
+
6
+ const STRING_TYPES = new Set(['CHARACTER', 'BIT', 'GRAPHIC', 'WIDECHAR', 'UCHAR']);
7
+ const UNALIGNED_BY_DEFAULT = new Set([...STRING_TYPES, 'PICTURE']);
8
+ const ARITHMETIC = new Set(['FIXED', 'FLOAT', 'BINARY', 'DECIMAL', 'PRECISION', 'SIGNED', 'UNSIGNED', 'REAL', 'COMPLEX']);
9
+ const UNIT = { CHARACTER: 8, BIT: 1, GRAPHIC: 16, WIDECHAR: 16, UCHAR: 32 };
10
+ // Program-control data: bytes and alignment in bytes.
11
+ const CONTROL = { POINTER: [4, 4], HANDLE: [4, 4], OFFSET: [4, 4], FILE: [4, 4], ENTRY: [8, 4], LABEL: [8, 4], FORMAT: [8, 4], TASK: [16, 4] };
12
+
13
+ // Names that occupy no storage of their own.
14
+ const NO_STORAGE = new Set(['BUILTIN', 'GENERIC', 'CONDITION']);
15
+ // File description attributes declare a file even when FILE itself is not written.
16
+ const FILE_DESCRIPTION = new Set(['STREAM', 'RECORD', 'INPUT', 'OUTPUT', 'UPDATE', 'SEQUENTIAL', 'DIRECT', 'TRANSIENT', 'KEYED', 'PRINT', 'ENVIRONMENT', 'BUFFERED', 'UNBUFFERED', 'BACKWARDS', 'EXCLUSIVE']);
17
+ // Attributes that say nothing about the data type, so a name carrying only these takes the default.
18
+ const NOT_TYPE = new Set(['STATIC', 'AUTOMATIC', 'BASED', 'CONTROLLED', 'DEFINED', 'POSITION', 'PARAMETER', 'EXTERNAL', 'INTERNAL', 'INITIAL', 'ALIGNED', 'UNALIGNED', 'NORMAL', 'ABNORMAL', 'ASSIGNABLE', 'NONASSIGNABLE', 'CONNECTED', 'NONCONNECTED', 'BYADDR', 'BYVALUE', 'OPTIONAL', 'INONLY', 'OUTONLY', 'INOUT', 'RESERVED', 'VALUE']);
19
+
20
+ const plainNumber = (toks) => (toks && toks.length === 1 && toks[0].t === 'num' && /^\d+$/.test(toks[0].v) ? Number(toks[0].v) : null);
21
+
22
+ function precisionOf(args) {
23
+ if (!args || !args.length) return null;
24
+ const parts = [[]];
25
+ for (const t of args) { if (t.t === 'op' && t.v === ',') parts.push([]); else parts[parts.length - 1].push(t); }
26
+ const signed = (p) => (p.length === 2 && p[0].t === 'op' && p[0].v === '-' ? -plainNumber([p[1]]) : plainNumber(p));
27
+ return { p: plainNumber(parts[0]), q: parts[1] ? signed(parts[1]) : 0 };
28
+ }
29
+
30
+ // Bytes a PICTURE occupies: every picture character except V, K and the F scaling factor, a
31
+ // repetition factor (n) multiplying the character after it, CR and DB two characters each.
32
+ export function pictureBytes(pic) {
33
+ const s = pic.toUpperCase().replace(/F\(\s*[+-]?\d+\s*\)/g, '');
34
+ let n = 0;
35
+ for (const m of s.matchAll(/\((\d+)\)(CR|DB|.)|(CR|DB)|([^VK])|[VK]/g)) {
36
+ if (m[2]) n += Number(m[1]) * (m[2].length === 2 ? 2 : m[2] === 'V' || m[2] === 'K' ? 0 : 1);
37
+ else if (m[3]) n += 2;
38
+ else if (m[4]) n += 1;
39
+ }
40
+ return n;
41
+ }
42
+
43
+ // { type, bits, align (bits), aligned, varying, base, scale, precision, known } for one element.
44
+ // bits is null when an extent is not a constant (CHAR(*), CHAR(N), REFER). align is what the
45
+ // element needs once ALIGNED or UNALIGNED is settled; `inherited` is the containing structure's
46
+ // explicit choice, used when the element makes none of its own.
47
+ export function storageOf(attributes, { inherited = null, name = null } = {}) {
48
+ const has = (n) => attributes.find((a) => a.name === n);
49
+ if (attributes.some((a) => NO_STORAGE.has(a.name))) return { type: attributes.find((a) => NO_STORAGE.has(a.name)).name, bits: 0, align: 8, aligned: null, known: true, storage: false };
50
+ if (has('TYPE')) return { type: 'TYPE', typeName: has('TYPE').args?.[0]?.u ?? null, bits: null, align: 8, aligned: null, known: false };
51
+ if (!has('FILE') && attributes.some((a) => FILE_DESCRIPTION.has(a.name))) attributes = [...attributes, { name: 'FILE', args: null }];
52
+ // RULES(IBM): a name with no data attribute is FIXED BINARY(15) when it starts with I to N, else
53
+ // FLOAT DECIMAL(6).
54
+ if (name && attributes.every((a) => NOT_TYPE.has(a.name))) {
55
+ const implicit = /^[I-N]/.test(name) ? [{ name: 'FIXED', args: null }, { name: 'BINARY', args: null }] : [{ name: 'FLOAT', args: null }, { name: 'DECIMAL', args: null }];
56
+ return { ...storageOf([...attributes, ...implicit], { inherited }), implicit: true };
57
+ }
58
+ const explicit = has('ALIGNED') ? true : has('UNALIGNED') ? false : null;
59
+ const alignedN = has('ALIGNED') && plainNumber(has('ALIGNED').args);
60
+ let type = null;
61
+ let bits = null;
62
+ let aligned;
63
+ let alignAligned = 8;
64
+ let alignUnaligned = 8;
65
+ const out = {};
66
+
67
+ const stringType = attributes.find((a) => STRING_TYPES.has(a.name));
68
+ const varying = has('VARYING') ? 'VARYING' : has('VARYINGZ') ? 'VARYINGZ' : has('VARYING4') ? 'VARYING4' : null;
69
+ if (stringType) {
70
+ type = stringType.name;
71
+ const n = stringType.args ? plainNumber(stringType.args) : 1;
72
+ const unit = UNIT[type];
73
+ aligned = explicit ?? inherited ?? !UNALIGNED_BY_DEFAULT.has(type);
74
+ if (n != null) {
75
+ if (type === 'BIT' && !varying) bits = aligned ? Math.ceil(n / 8) * 8 : n;
76
+ else if (type === 'BIT') bits = (varying === 'VARYING4' ? 32 : 16) + (aligned ? Math.ceil(n / 8) * 8 : n);
77
+ else bits = n * unit + (varying === 'VARYING' ? 16 : varying === 'VARYING4' ? 32 : varying === 'VARYINGZ' ? (type === 'CHARACTER' || type === 'UCHAR' ? 8 : 16) : 0);
78
+ }
79
+ alignAligned = varying === 'VARYING' ? 16 : varying === 'VARYING4' ? 32 : 8;
80
+ alignUnaligned = type === 'BIT' && !varying ? 1 : 8;
81
+ Object.assign(out, { length: n, varying });
82
+ } else if (has('PICTURE')) {
83
+ type = 'PICTURE';
84
+ aligned = explicit ?? inherited ?? false;
85
+ bits = has('PICTURE').picture != null ? pictureBytes(has('PICTURE').picture) * 8 : null;
86
+ out.picture = has('PICTURE').picture ?? null;
87
+ } else if (has('AREA')) {
88
+ type = 'AREA';
89
+ aligned = true;
90
+ const n = has('AREA').args ? plainNumber(has('AREA').args) : 1000;
91
+ bits = n == null ? null : (16 + n) * 8;
92
+ alignAligned = alignUnaligned = 64;
93
+ } else if (Object.keys(CONTROL).some((k) => has(k))) {
94
+ type = Object.keys(CONTROL).find((k) => has(k));
95
+ aligned = explicit ?? inherited ?? true;
96
+ const limited = type === 'ENTRY' && has('LIMITED');
97
+ const [bytes, align] = limited ? [4, 4] : CONTROL[type];
98
+ bits = bytes * 8;
99
+ alignAligned = align * 8;
100
+ } else if (attributes.some((a) => ARITHMETIC.has(a.name))) {
101
+ const last = (names) => [...attributes].reverse().find((a) => names.includes(a.name) && a.args);
102
+ const scale = has('FIXED') ? 'FIXED' : 'FLOAT';
103
+ const base = has('BINARY') ? 'BINARY' : 'DECIMAL';
104
+ const prec = precisionOf(last(['FIXED', 'FLOAT', 'BINARY', 'DECIMAL', 'PRECISION'])?.args);
105
+ const unsigned = !!has('UNSIGNED');
106
+ const p = prec?.p ?? (scale === 'FIXED' ? (base === 'BINARY' ? 15 : 5) : base === 'BINARY' ? 21 : 6);
107
+ type = `${scale} ${base}`;
108
+ aligned = explicit ?? inherited ?? true;
109
+ Object.assign(out, { scale, base, precision: p, fraction: prec?.q ?? 0, signed: !unsigned });
110
+ if (prec && prec.p == null) bits = null;
111
+ else if (scale === 'FIXED' && base === 'DECIMAL') { bits = Math.ceil((p + 1) / 2) * 8; alignAligned = 8; }
112
+ else if (scale === 'FIXED') {
113
+ const q = unsigned ? p - 1 : p;
114
+ const bytes = q <= 7 ? 1 : q <= 15 ? 2 : q <= 31 ? 4 : 8;
115
+ bits = bytes * 8;
116
+ alignAligned = bytes * 8;
117
+ } else {
118
+ const short = base === 'BINARY' ? p <= 21 : p <= 6;
119
+ const long = base === 'BINARY' ? p <= 53 : p <= 16;
120
+ bits = short ? 32 : long ? 64 : 128;
121
+ alignAligned = short ? 32 : 64;
122
+ }
123
+ if (has('COMPLEX') && bits != null) bits *= 2;
124
+ } else {
125
+ return { type: null, bits: null, align: 8, aligned: explicit ?? inherited ?? null, known: false };
126
+ }
127
+ const align = alignedN ? alignedN * 8 : aligned ? alignAligned : alignUnaligned;
128
+ return { type, bits, align, aligned, known: bits != null, ...out };
129
+ }