@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,116 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // DC and DS operands, and literals, which are DC operands after an equals sign: duplication factor,
3
+ // type and type extension, program type, length, scale and exponent modifiers, and nominal value.
4
+ // https://www.ibm.com/docs/en/hla-and-tf/1.6.0?topic=statements-dc-instruction
5
+ import { HlasmSyntax, splitOperands } from '../operands.mjs';
6
+ import { readExpression } from '../expr.mjs';
7
+
8
+ const EXTENSIONS = {
9
+ C: 'AEU', G: '', X: '', B: '', F: 'D', H: '', E: 'HBD', D: 'HBD', L: 'HBDQ', P: '', Z: '',
10
+ A: 'D', Y: '', S: 'Y', V: 'D', Q: 'DY', J: 'D', R: 'D',
11
+ };
12
+ const ADDRESS_TYPES = new Set(['A', 'Y', 'S', 'V', 'Q', 'J', 'R']);
13
+
14
+ // A parenthesised expression or a decimal number, as a duplication factor or a modifier is written.
15
+ function amount(text, pos) {
16
+ if (text[pos] === '(') {
17
+ const { e, end } = readExpression(text, pos + 1);
18
+ if (text[end] !== ')') throw new HlasmSyntax(`expected ) at column ${end + 1} of '${text}'`);
19
+ return { value: { t: 'paren', e }, end: end + 1 };
20
+ }
21
+ const m = /^\d+/.exec(text.slice(pos));
22
+ if (!m) return null;
23
+ return { value: { t: 'num', v: Number(m[0]) }, end: pos + m[0].length };
24
+ }
25
+
26
+ function quotedNominal(text, pos) {
27
+ let out = '';
28
+ for (let i = pos + 1; i < text.length; i++) {
29
+ if (text[i] === "'") {
30
+ if (text[i + 1] === "'") { out += "'"; i++; continue; }
31
+ return { value: out, end: i + 1 };
32
+ }
33
+ if (text[i] === '&' && text[i + 1] === '&') i++;
34
+ out += text[i];
35
+ }
36
+ throw new HlasmSyntax(`a nominal value is not closed in '${text}'`);
37
+ }
38
+
39
+ function addressNominal(text, pos) {
40
+ const items = [];
41
+ let at = pos + 1;
42
+ for (;;) {
43
+ const { e, end } = readExpression(text, at);
44
+ at = end;
45
+ if (text[at] === '(') {
46
+ const close = text.indexOf(')', at);
47
+ if (close < 0) throw new HlasmSyntax(`a base register is not closed in '${text}'`);
48
+ items.push({ t: 'address', disp: e, base: text.slice(at + 1, close) });
49
+ at = close + 1;
50
+ } else items.push(e);
51
+ if (text[at] === ',') { at++; continue; }
52
+ if (text[at] === ')') return { value: items, end: at + 1 };
53
+ throw new HlasmSyntax(`expected , or ) at column ${at + 1} of '${text}'`);
54
+ }
55
+ }
56
+
57
+ // One DC operand starting at pos: the operand, and where it ended. A literal needs a nominal value
58
+ // and a duplication factor that is not zero.
59
+ export function readDataOperand(text, pos = 0, { literal = false, nominalRequired = literal } = {}) {
60
+ const operand = { dup: null, type: null, ext: null, program: null, length: null, bitLength: false, scale: null, exponent: null, nominal: null };
61
+ let at = pos;
62
+ const dup = amount(text, at);
63
+ if (dup) { operand.dup = dup.value; at = dup.end; }
64
+ const type = (text[at] || '').toUpperCase();
65
+ if (!(type in EXTENSIONS)) throw new HlasmSyntax(`'${text.slice(pos)}' has no constant type a DC operand can have`);
66
+ operand.type = type;
67
+ at++;
68
+ const ext = (text[at] || '').toUpperCase();
69
+ if (ext && EXTENSIONS[type].includes(ext)) { operand.ext = ext; at++; }
70
+ if ((text[at] || '').toUpperCase() === 'P' && text[at + 1] === '(') {
71
+ const p = amount(text, at + 1);
72
+ operand.program = p.value;
73
+ at = p.end;
74
+ }
75
+ for (const [letter, key] of [['L', 'length'], ['S', 'scale'], ['E', 'exponent']]) {
76
+ if ((text[at] || '').toUpperCase() !== letter) continue;
77
+ let from = at + 1;
78
+ if (key === 'length' && text[from] === '.') { operand.bitLength = true; from++; }
79
+ const sign = key !== 'length' && (text[from] === '-' || text[from] === '+') ? text[from++] : '';
80
+ const m = amount(text, from);
81
+ if (!m) throw new HlasmSyntax(`the ${key} modifier in '${text}' has no value`);
82
+ operand[key] = sign === '-' ? { t: 'unary', op: '-', e: m.value } : m.value;
83
+ at = m.end;
84
+ }
85
+ if (text[at] === "'") {
86
+ if (ADDRESS_TYPES.has(type)) throw new HlasmSyntax(`a ${type}-type constant takes its value in parentheses, not quotes, in '${text}'`);
87
+ const n = quotedNominal(text, at);
88
+ operand.nominal = { quoted: n.value };
89
+ at = n.end;
90
+ } else if (text[at] === '(') {
91
+ if (!ADDRESS_TYPES.has(type)) throw new HlasmSyntax(`a ${type}-type constant takes its value in quotes, not parentheses, in '${text}'`);
92
+ const n = addressNominal(text, at);
93
+ operand.nominal = { expressions: n.value };
94
+ at = n.end;
95
+ }
96
+ // A DC operand may omit its nominal value only when its duplication factor is zero.
97
+ const zeroDup = operand.dup && operand.dup.t === 'num' && operand.dup.v === 0;
98
+ if (nominalRequired && !operand.nominal && !(zeroDup && !literal)) throw new HlasmSyntax(`'${text.slice(pos, at)}' has no nominal value`);
99
+ if (literal && operand.dup && operand.dup.t === 'num' && operand.dup.v === 0) throw new HlasmSyntax('a literal cannot have a duplication factor of zero');
100
+ return { operand, end: at };
101
+ }
102
+
103
+ function operands(st, nominalRequired) {
104
+ const list = splitOperands(st.field || '');
105
+ if (!list.length) throw new HlasmSyntax(`${st.operation} has no operand`);
106
+ return list.map((text) => {
107
+ const { operand, end } = readDataOperand(text, 0, { nominalRequired });
108
+ if (end !== text.length) throw new HlasmSyntax(`unexpected '${text.slice(end)}' after the operand '${text.slice(0, end)}'`);
109
+ return operand;
110
+ });
111
+ }
112
+
113
+ export const parsers = {
114
+ DC: (st) => ({ kind: 'DC', operands: operands(st, true) }),
115
+ DS: (st) => ({ kind: 'DS', operands: operands(st, false) }),
116
+ };
@@ -0,0 +1,43 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // Parsers for HLASM listing instructions: PRINT, TITLE, EJECT, SPACE, CEJECT.
3
+
4
+ import { HlasmSyntax, splitOperands } from '../operands.mjs';
5
+ import { parseExpression } from '../expr.mjs';
6
+
7
+ const PRINT_OPTS = new Set(['ON', 'OFF', 'GEN', 'NOGEN', 'DATA', 'NODATA', 'MCALL', 'NOMCALL', 'MSOURCE', 'NOMSOURCE', 'UHEAD', 'NOUHEAD', 'NOPRINT']);
8
+
9
+ export const parsers = {
10
+ PRINT(st) {
11
+ const ops = splitOperands(st.field);
12
+ if (ops.length === 0) throw new HlasmSyntax('PRINT requires at least one option');
13
+ const options = ops.map(o => o.trim().toUpperCase());
14
+ for (const opt of options) {
15
+ if (!PRINT_OPTS.has(opt)) throw new HlasmSyntax(`unknown PRINT option: ${opt}`);
16
+ }
17
+ return { kind: 'PRINT', options };
18
+ },
19
+ TITLE(st) {
20
+ const ops = splitOperands(st.field);
21
+ if (ops.length !== 1) throw new HlasmSyntax('TITLE requires exactly one operand');
22
+ const raw = ops[0].trim();
23
+ if (!raw.startsWith("'") || !raw.endsWith("'") || raw.length < 2) throw new HlasmSyntax('TITLE operand must be a quoted string');
24
+ const text = raw.slice(1, -1).replace(/''/g, "'").replace(/&&/g, "&");
25
+ return { kind: 'TITLE', name: st.name, text };
26
+ },
27
+ EJECT(st) {
28
+ if (st.field.trim() !== '') throw new HlasmSyntax('EJECT takes no operand');
29
+ return { kind: 'EJECT', value: null };
30
+ },
31
+ SPACE(st) {
32
+ const ops = splitOperands(st.field);
33
+ if (ops.length === 0) return { kind: 'SPACE', value: null };
34
+ if (ops.length > 1) throw new HlasmSyntax('SPACE takes at most one operand');
35
+ return { kind: 'SPACE', value: parseExpression(ops[0].trim()) };
36
+ },
37
+ CEJECT(st) {
38
+ const ops = splitOperands(st.field);
39
+ if (ops.length === 0) return { kind: 'CEJECT', value: null };
40
+ if (ops.length > 1) throw new HlasmSyntax('CEJECT takes at most one operand');
41
+ return { kind: 'CEJECT', value: parseExpression(ops[0].trim()) };
42
+ }
43
+ };
@@ -0,0 +1,45 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // Assembler instructions that write messages, copy source in or shape the input records: MNOTE,
3
+ // COPY, PUNCH, REPRO, AINSERT, ICTL and ISEQ.
4
+ // https://www.ibm.com/docs/en/hla-and-tf/1.6.0?topic=instructions-mnote-instruction
5
+ import { HlasmSyntax, NAME, splitOperands } from '../operands.mjs';
6
+ import { parseExpression } from '../expr.mjs';
7
+
8
+ // A quoted string's text, '' read as one quote and && as one ampersand.
9
+ function quoted(text, what) {
10
+ const m = /^'((?:[^']|'')*)'$/.exec(text.trim());
11
+ if (!m) throw new HlasmSyntax(`${what} needs a quoted string, not ${text}`);
12
+ return m[1].replace(/''/g, "'").replace(/&&/g, '&');
13
+ }
14
+
15
+ const numbers = (st, min, max) => {
16
+ const ops = splitOperands(st.field || '');
17
+ if (ops.length < min || ops.length > max || ops.some((o) => !/^\d+$/.test(o.trim()))) throw new HlasmSyntax(`${st.operation} takes ${min} to ${max} decimal numbers, not ${st.field || 'none'}`);
18
+ return { kind: st.operation, values: ops.map(Number) };
19
+ };
20
+
21
+ export const parsers = {
22
+ // [severity,]'message': severity is an absolute expression, *, or empty before the comma.
23
+ MNOTE: (st) => {
24
+ const ops = splitOperands(st.field || '');
25
+ if (ops.length < 1 || ops.length > 2) throw new HlasmSyntax(`MNOTE takes a message and an optional severity, not ${ops.length} operands`);
26
+ const [sev, msg] = ops.length === 2 ? ops : [null, ops[0]];
27
+ const severity = sev === null || sev.trim() === '' ? null : sev.trim() === '*' ? '*' : parseExpression(sev.trim());
28
+ return { kind: 'MNOTE', severity, text: quoted(msg, 'MNOTE') };
29
+ },
30
+ COPY: (st) => {
31
+ const member = (st.field || '').trim();
32
+ if (!NAME.test(member)) throw new HlasmSyntax(`COPY names a member, not ${member || 'nothing'}`);
33
+ return { kind: 'COPY', member: member.toUpperCase() };
34
+ },
35
+ PUNCH: (st) => ({ kind: 'PUNCH', text: quoted(st.field || '', 'PUNCH') }),
36
+ REPRO: () => ({ kind: 'REPRO' }),
37
+ // 'record'[,FRONT|BACK]: a record put into the assembler's input stream.
38
+ AINSERT: (st) => {
39
+ const ops = splitOperands(st.field || '');
40
+ if (ops.length !== 2 || !/^(FRONT|BACK)$/i.test(ops[1].trim())) throw new HlasmSyntax('AINSERT takes a quoted record and FRONT or BACK');
41
+ return { kind: 'AINSERT', text: quoted(ops[0], 'AINSERT'), where: ops[1].trim().toUpperCase() };
42
+ },
43
+ ICTL: (st) => numbers(st, 1, 3),
44
+ ISEQ: (st) => (st.field ? numbers(st, 2, 2) : { kind: 'ISEQ', values: [] }),
45
+ };
@@ -0,0 +1,159 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // Parsers for HLASM section and linkage instructions: CSECT, DSECT, RSECT, START, COM, LOCTR, ENTRY,
3
+ // EXTRN, WXTRN, AMODE, RMODE, ALIAS, XATTR, CATTR, DXD, CXD.
4
+ import { HlasmSyntax, NAME, splitOperands } from '../operands.mjs';
5
+ import { parseExpression } from '../expr.mjs';
6
+
7
+ const AMODE_VALUES = new Set(['24', '31', '64', 'ANY', 'ANY31', 'ANY64']);
8
+ const RMODE_VALUES = new Set(['24', '31', '64', 'ANY']);
9
+ const XATTR_VALUES = {
10
+ ATTRIBUTES: null,
11
+ LINKAGE: new Set(['OS', 'XPLINK']),
12
+ PSECT: null,
13
+ REFERENCE: new Set(['DIRECT', 'INDIRECT', 'DATA', 'CODE']),
14
+ SCOPE: new Set(['SECTION', 'MODULE', 'LIBRARY', 'IMPORT', 'EXPORT']),
15
+ };
16
+ const CATTR_VALUES = {
17
+ ALIGN: null,
18
+ DEFLOAD: true,
19
+ EXECUTABLE: true,
20
+ FILL: null,
21
+ NOLOAD: true,
22
+ NOTEXECUTABLE: true,
23
+ NOTREUSABLE: true,
24
+ PART: null,
25
+ PRIORITY: null,
26
+ READONLY: true,
27
+ REFRESH: true,
28
+ RENT: true,
29
+ REUSABLE: true,
30
+ RMODE: new Set(['24', '31', 'ANY', '64']),
31
+ };
32
+
33
+ function noOperand(kind, st, requireName) {
34
+ if (st.field.trim()) throw new HlasmSyntax(`${kind} takes no operand`);
35
+ if (requireName && !st.name) throw new HlasmSyntax(`${kind} requires a name`);
36
+ return { kind, name: st.name || null };
37
+ }
38
+
39
+ function nameList(kind, st) {
40
+ const ops = splitOperands(st.field);
41
+ if (!ops.length) throw new HlasmSyntax(`${kind} requires at least one operand`);
42
+ const names = [];
43
+ const parts = [];
44
+ for (const op of ops) {
45
+ const t = op.trim();
46
+ if (!t) throw new HlasmSyntax(`${kind} has an empty operand`);
47
+ const m = /^PART\(([\s\S]*)\)$/i.exec(t);
48
+ if (m) {
49
+ for (const p of m[1].split(',')) {
50
+ const n = p.trim();
51
+ if (!NAME.test(n)) throw new HlasmSyntax(`${kind} PART has an invalid name '${n}'`);
52
+ parts.push(n.toUpperCase());
53
+ }
54
+ } else if (NAME.test(t)) {
55
+ names.push(t.toUpperCase());
56
+ } else {
57
+ throw new HlasmSyntax(`${kind} operand '${t}' is not a name or PART(...)`);
58
+ }
59
+ }
60
+ return { kind, names, parts };
61
+ }
62
+
63
+ function mode(kind, st, values) {
64
+ const ops = splitOperands(st.field);
65
+ if (ops.length !== 1) throw new HlasmSyntax(`${kind} takes exactly one operand`);
66
+ const v = ops[0].trim().toUpperCase();
67
+ if (!values.has(v)) throw new HlasmSyntax(`${kind} operand '${v}' is not a valid mode`);
68
+ return { kind, name: st.name || null, mode: v };
69
+ }
70
+
71
+ function alias(st) {
72
+ const ops = splitOperands(st.field);
73
+ if (ops.length !== 1) throw new HlasmSyntax('ALIAS takes exactly one operand');
74
+ if (!st.name) throw new HlasmSyntax('ALIAS requires a name');
75
+ const t = ops[0].trim();
76
+ const m = /^([CX])'((?:[^']|'')*)'$/i.exec(t);
77
+ if (!m) throw new HlasmSyntax('ALIAS operand must be C\'string\' or X\'hex\'');
78
+ return { kind: 'ALIAS', name: st.name, alias: { type: m[1].toUpperCase(), text: m[2] } };
79
+ }
80
+
81
+ function xattr(st) {
82
+ if (!st.name) throw new HlasmSyntax('XATTR requires a name');
83
+ const ops = splitOperands(st.field);
84
+ if (!ops.length) throw new HlasmSyntax('XATTR requires at least one operand');
85
+ const attributes = {};
86
+ for (const op of ops) {
87
+ const t = op.trim();
88
+ const m = /^([A-Z]+)(?:\(([\s\S]*)\))?$/i.exec(t);
89
+ if (!m) throw new HlasmSyntax(`XATTR operand '${t}' is not a keyword`);
90
+ const key = m[1].toUpperCase();
91
+ if (!(key in XATTR_VALUES)) throw new HlasmSyntax(`XATTR keyword '${key}' is not recognised`);
92
+ const val = m[2] !== undefined ? m[2].trim() : null;
93
+ if (val !== null) {
94
+ const allowed = XATTR_VALUES[key];
95
+ if (allowed && !allowed.has(val.toUpperCase())) throw new HlasmSyntax(`XATTR ${key} value '${val}' is not valid`);
96
+ }
97
+ attributes[key] = val !== null ? val : true;
98
+ }
99
+ return { kind: 'XATTR', name: st.name, attributes };
100
+ }
101
+
102
+ function cattr(st) {
103
+ if (!st.name) throw new HlasmSyntax('CATTR requires a name');
104
+ const ops = splitOperands(st.field);
105
+ if (!ops.length) throw new HlasmSyntax('CATTR requires at least one operand');
106
+ const attributes = {};
107
+ for (const op of ops) {
108
+ const t = op.trim();
109
+ const m = /^([A-Z]+)(?:\(([\s\S]*)\))?$/i.exec(t);
110
+ if (!m) throw new HlasmSyntax(`CATTR operand '${t}' is not a keyword`);
111
+ const key = m[1].toUpperCase();
112
+ if (!(key in CATTR_VALUES)) throw new HlasmSyntax(`CATTR keyword '${key}' is not recognised`);
113
+ const val = m[2] !== undefined ? m[2].trim() : null;
114
+ if (val !== null) {
115
+ const allowed = CATTR_VALUES[key];
116
+ if (allowed instanceof Set && !allowed.has(val.toUpperCase())) throw new HlasmSyntax(`CATTR ${key} value '${val}' is not valid`);
117
+ }
118
+ attributes[key] = val !== null ? val : true;
119
+ }
120
+ return { kind: 'CATTR', name: st.name, attributes };
121
+ }
122
+
123
+ function dxd(st) {
124
+ if (!st.name) throw new HlasmSyntax('DXD requires a name');
125
+ const ops = splitOperands(st.field);
126
+ if (ops.length !== 1) throw new HlasmSyntax('DXD takes exactly one operand');
127
+ return { kind: 'DXD', name: st.name, operand: ops[0].trim() };
128
+ }
129
+
130
+ export const parsers = {
131
+ CSECT: (st) => noOperand('CSECT', st, false),
132
+ DSECT: (st) => noOperand('DSECT', st, true),
133
+ RSECT: (st) => noOperand('RSECT', st, false),
134
+ COM: (st) => noOperand('COM', st, false),
135
+ START: (st) => {
136
+ const ops = splitOperands(st.field);
137
+ if (ops.length > 1) throw new HlasmSyntax('START takes at most one operand');
138
+ const origin = ops.length ? parseExpression(ops[0].trim()) : null;
139
+ return { kind: 'START', name: st.name || null, origin };
140
+ },
141
+ LOCTR: (st) => {
142
+ if (!st.name) throw new HlasmSyntax('LOCTR requires a name');
143
+ if (st.field.trim()) throw new HlasmSyntax('LOCTR takes no operand');
144
+ return { kind: 'LOCTR', name: st.name };
145
+ },
146
+ ENTRY: (st) => nameList('ENTRY', st),
147
+ EXTRN: (st) => nameList('EXTRN', st),
148
+ WXTRN: (st) => nameList('WXTRN', st),
149
+ AMODE: (st) => mode('AMODE', st, AMODE_VALUES),
150
+ RMODE: (st) => mode('RMODE', st, RMODE_VALUES),
151
+ ALIAS: alias,
152
+ XATTR: xattr,
153
+ CATTR: cattr,
154
+ DXD: dxd,
155
+ CXD: (st) => {
156
+ if (st.field.trim()) throw new HlasmSyntax('CXD takes no operand');
157
+ return { kind: 'CXD', name: st.name || null };
158
+ },
159
+ };
@@ -0,0 +1,78 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // The assembler instructions that define symbols and move or read the location counter: EQU, ORG,
3
+ // CNOP, LTORG, USING, DROP, OPSYN, END, PUSH and POP.
4
+ // https://www.ibm.com/docs/en/hla-and-tf/1.6.0?topic=instructions-equ-instruction
5
+ import { HlasmSyntax, NAME, splitOperands } from '../operands.mjs';
6
+ import { parseExpression } from '../expr.mjs';
7
+
8
+ const ASSEMBLER_TYPES = new Set(['AR', 'CR', 'CR32', 'CR64', 'FPR', 'GR', 'GR32', 'GR64', 'VR']);
9
+ const STACKED = new Set(['PRINT', 'USING', 'ACONTROL', 'NOPRINT']);
10
+ const optional = (text) => (text ? parseExpression(text) : null);
11
+
12
+ function equ(st) {
13
+ if (!st.name) throw new HlasmSyntax('EQU needs a name');
14
+ const ops = splitOperands(st.field || '');
15
+ if (!ops.length || !ops[0]) throw new HlasmSyntax('EQU needs a value');
16
+ if (ops.length > 5) throw new HlasmSyntax(`EQU takes at most five operands, not ${ops.length}`);
17
+ const assemblerType = ops[4] ? ops[4].toUpperCase() : null;
18
+ if (assemblerType && !ASSEMBLER_TYPES.has(assemblerType)) throw new HlasmSyntax(`${ops[4]} is not an assembler type`);
19
+ return { kind: 'EQU', name: st.name, value: parseExpression(ops[0]), length: optional(ops[1]), type: optional(ops[2]), programType: optional(ops[3]), assemblerType };
20
+ }
21
+
22
+ function org(st) {
23
+ const ops = splitOperands(st.field || '');
24
+ if (!ops.length) return { kind: 'ORG', value: null, boundary: null, offset: null };
25
+ if (ops.length > 3) throw new HlasmSyntax(`ORG takes at most three operands, not ${ops.length}`);
26
+ const boundary = ops[1] ? Number(ops[1]) : null;
27
+ if (ops[1] && !(Number.isInteger(boundary) && boundary >= 2 && boundary <= 4096 && (boundary & (boundary - 1)) === 0)) throw new HlasmSyntax(`ORG boundary ${ops[1]} is not a power of 2 from 2 to 4096`);
28
+ return { kind: 'ORG', value: ops[0] ? parseExpression(ops[0]) : null, boundary, offset: optional(ops[2]) };
29
+ }
30
+
31
+ function using(st) {
32
+ const ops = splitOperands(st.field || '');
33
+ if (ops.length < 2) throw new HlasmSyntax('USING needs a base and a register or address');
34
+ const range = /^\(([\s\S]*)\)$/.exec(ops[0]);
35
+ const parts = range ? splitOperands(range[1]) : null;
36
+ if (parts && parts.length !== 2) throw new HlasmSyntax(`USING range ${ops[0]} needs a base and an end`);
37
+ return {
38
+ kind: 'USING', label: st.name || null,
39
+ base: parseExpression(parts ? parts[0] : ops[0]), end: parts ? parseExpression(parts[1]) : null,
40
+ registers: ops.slice(1).map((o) => parseExpression(o)),
41
+ };
42
+ }
43
+
44
+ function names(kind, allowed) {
45
+ return (st) => {
46
+ const ops = splitOperands(st.field || '').map((o) => o.toUpperCase());
47
+ for (const o of ops) if (!allowed.has(o)) throw new HlasmSyntax(`${kind} does not take ${o}`);
48
+ if (!ops.length) throw new HlasmSyntax(`${kind} needs an operand`);
49
+ return { kind, what: ops };
50
+ };
51
+ }
52
+
53
+ export const parsers = {
54
+ EQU: equ,
55
+ ORG: org,
56
+ USING: using,
57
+ DROP: (st) => ({ kind: 'DROP', operands: splitOperands(st.field || '').map((o) => parseExpression(o)) }),
58
+ LTORG: (st) => { if (st.field) throw new HlasmSyntax('LTORG takes no operand'); return { kind: 'LTORG', name: st.name || null }; },
59
+ CNOP: (st) => {
60
+ const ops = splitOperands(st.field || '');
61
+ if (ops.length !== 2) throw new HlasmSyntax(`CNOP takes two operands, not ${ops.length}`);
62
+ return { kind: 'CNOP', byte: parseExpression(ops[0]), boundary: parseExpression(ops[1]) };
63
+ },
64
+ OPSYN: (st) => {
65
+ if (!st.name) throw new HlasmSyntax('OPSYN needs a name');
66
+ const ops = splitOperands(st.field || '');
67
+ if (ops.length > 1 || (ops[0] && !NAME.test(ops[0]))) throw new HlasmSyntax(`OPSYN takes one operation name, not ${st.field}`);
68
+ return { kind: 'OPSYN', name: st.name, operation: ops[0] ? ops[0].toUpperCase() : null };
69
+ },
70
+ // The second operand names the language translator, in parentheses; it is kept as written.
71
+ END: (st) => {
72
+ const ops = splitOperands(st.field || '');
73
+ if (ops.length > 2) throw new HlasmSyntax(`END takes at most two operands, not ${ops.length}`);
74
+ return { kind: 'END', entry: ops[0] ? parseExpression(ops[0]) : null, language: ops[1] || null };
75
+ },
76
+ PUSH: names('PUSH', STACKED),
77
+ POP: names('POP', STACKED),
78
+ };
@@ -0,0 +1,27 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // EXEC CICS, EXEC SQL and EXEC DLI written in assembler, read by the same code that reads them in
3
+ // COBOL: the CICS command table and option reader, and the SQL host-variable reader.
4
+ import { HlasmSyntax } from './operands.mjs';
5
+ import { cicsOptions, cicsCommand } from '../precompile-cics.mjs';
6
+ import { hostVariableRoles } from '../embedded-sql.mjs';
7
+
8
+ const after = (st, word) => (st.field || '').replace(new RegExp(`^${word}\\b\\s*`, 'i'), '');
9
+
10
+ export const parsers = {
11
+ 'EXEC CICS': (st) => {
12
+ const options = cicsOptions(after(st, 'CICS'));
13
+ const command = cicsCommand(options);
14
+ if (!command) throw new HlasmSyntax(`${options[0]?.word || 'nothing'} is not a CICS command the command table holds`);
15
+ return { kind: 'EXEC CICS', command, options };
16
+ },
17
+ 'EXEC SQL': (st) => {
18
+ const sql = after(st, 'SQL');
19
+ if (!sql) throw new HlasmSyntax('EXEC SQL holds no statement');
20
+ return { kind: 'EXEC SQL', sql, hostVariables: hostVariableRoles(sql) };
21
+ },
22
+ 'EXEC DLI': (st) => {
23
+ const options = cicsOptions(after(st, 'DLI'));
24
+ if (!options.length) throw new HlasmSyntax('EXEC DLI holds no command');
25
+ return { kind: 'EXEC DLI', command: options[0].word, options };
26
+ },
27
+ };
@@ -0,0 +1,167 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // HLASM expressions, read and not evaluated: terms (symbols, *, self-defining terms, literals,
3
+ // attribute references, variable symbols) joined by + - * / with the usual precedence. An address
4
+ // operand is an expression with an optional parenthesised index or length and base after it.
5
+ // https://www.ibm.com/docs/en/hla-and-tf/1.6.0?topic=structure-terms-literals-expressions
6
+ import { HlasmSyntax, ATTRIBUTES } from './operands.mjs';
7
+ import { readDataOperand } from './asm/data.mjs';
8
+
9
+ const NAME_START = /[A-Z$#@_]/i;
10
+ const NAME_CHAR = /[A-Z0-9$#@_]/i;
11
+ const SELF_DEFINING = new Set(['X', 'B', 'C', 'CA', 'CE', 'CU', 'G']);
12
+
13
+ class Cursor {
14
+ constructor(text, pos = 0) { this.text = text; this.pos = pos; }
15
+ peek(n = 0) { return this.text[this.pos + n]; }
16
+ fail(what) { throw new HlasmSyntax(`${what} at column ${this.pos + 1} of '${this.text}'`); }
17
+ name() {
18
+ const start = this.pos;
19
+ if (!NAME_START.test(this.peek() || '')) return null;
20
+ while (NAME_CHAR.test(this.peek() || '')) this.pos++;
21
+ if (this.pos - start > 63) this.fail('a name longer than 63 characters');
22
+ return this.text.slice(start, this.pos).toUpperCase();
23
+ }
24
+ quoted() {
25
+ if (this.peek() !== "'") this.fail('expected a quote');
26
+ let out = '';
27
+ for (this.pos++; this.pos < this.text.length; this.pos++) {
28
+ const c = this.text[this.pos];
29
+ if (c === "'") {
30
+ if (this.peek(1) === "'") { out += "'"; this.pos++; continue; }
31
+ this.pos++;
32
+ return out;
33
+ }
34
+ if (c === '&' && this.peek(1) === '&') this.pos++;
35
+ out += c;
36
+ }
37
+ return this.fail('a quoted string is not closed');
38
+ }
39
+ }
40
+
41
+ function variable(c) {
42
+ const start = c.pos;
43
+ c.pos++;
44
+ if (!c.name()) c.fail('expected a variable symbol name after &');
45
+ if (c.peek() === '(') {
46
+ c.pos++;
47
+ expression(c);
48
+ while (c.peek() === ',') { c.pos++; expression(c); }
49
+ if (c.peek() !== ')') c.fail('expected ) after a variable symbol subscript');
50
+ c.pos++;
51
+ }
52
+ return { t: 'var', text: c.text.slice(start, c.pos).toUpperCase() };
53
+ }
54
+
55
+ function term(c) {
56
+ const ch = c.peek();
57
+ if (ch === undefined) c.fail('expected a term');
58
+ if (ch === '(') {
59
+ c.pos++;
60
+ const inner = expression(c);
61
+ if (c.peek() !== ')') c.fail('expected )');
62
+ c.pos++;
63
+ return { t: 'paren', e: inner };
64
+ }
65
+ if (ch === '*') { c.pos++; return { t: 'loc' }; }
66
+ if (ch === '=') {
67
+ const { operand, end } = readDataOperand(c.text, c.pos + 1, { literal: true });
68
+ c.pos = end;
69
+ return { t: 'lit', dc: operand };
70
+ }
71
+ if (ch === '&') return variable(c);
72
+ if (/[0-9]/.test(ch)) {
73
+ const start = c.pos;
74
+ while (/[0-9]/.test(c.peek() || '')) c.pos++;
75
+ return { t: 'num', v: Number(c.text.slice(start, c.pos)) };
76
+ }
77
+ const start = c.pos;
78
+ const name = c.name();
79
+ if (!name) c.fail(`unexpected '${ch}'`);
80
+ if (c.peek() === "'") {
81
+ if (name.length === 1 && ATTRIBUTES.includes(name) && /[A-Z$#@_&*=]/i.test(c.peek(1) || '')) {
82
+ c.pos++;
83
+ const of = c.peek() === '*' ? (c.pos++, { t: 'loc' }) : c.peek() === '&' ? variable(c) : c.peek() === '=' ? term(c) : { t: 'sym', name: c.name() };
84
+ return { t: 'attr', attr: name, of };
85
+ }
86
+ if (SELF_DEFINING.has(name)) return { t: 'sdt', type: name, text: c.quoted() };
87
+ c.pos = start;
88
+ c.fail(`'${name}' followed by a quote is neither a self-defining term nor an attribute reference`);
89
+ }
90
+ if (c.peek() === '.' && NAME_START.test(c.peek(1) || '')) {
91
+ c.pos++;
92
+ return { t: 'sym', qualifier: name, name: c.name() };
93
+ }
94
+ return { t: 'sym', name };
95
+ }
96
+
97
+ function unary(c) {
98
+ if (c.peek() === '+' || c.peek() === '-') {
99
+ const op = c.text[c.pos++];
100
+ return { t: 'unary', op, e: unary(c) };
101
+ }
102
+ return term(c);
103
+ }
104
+
105
+ function product(c) {
106
+ let left = unary(c);
107
+ while (c.peek() === '*' || c.peek() === '/') {
108
+ const op = c.text[c.pos++];
109
+ left = { t: 'bin', op, l: left, r: unary(c) };
110
+ }
111
+ return left;
112
+ }
113
+
114
+ function expression(c) {
115
+ let left = product(c);
116
+ while (c.peek() === '+' || c.peek() === '-') {
117
+ const op = c.text[c.pos++];
118
+ left = { t: 'bin', op, l: left, r: product(c) };
119
+ }
120
+ return left;
121
+ }
122
+
123
+ // An expression starting at pos: the tree, and where it ended.
124
+ export function readExpression(text, pos = 0) {
125
+ const c = new Cursor(text, pos);
126
+ const e = expression(c);
127
+ return { e, end: c.pos };
128
+ }
129
+
130
+ export function parseExpression(text) {
131
+ const { e, end } = readExpression(text);
132
+ if (end !== text.length) throw new HlasmSyntax(`unexpected '${text.slice(end)}' after an expression in '${text}'`);
133
+ return e;
134
+ }
135
+
136
+ // D(X,B), D(,B), D(X), D(L,B) or an implicit address: the displacement or address expression, and
137
+ // the parenthesised parts written after it, an omitted one as null.
138
+ export function parseAddress(text) {
139
+ const { e, end } = readExpression(text);
140
+ if (end === text.length) return { disp: e, parts: null };
141
+ if (text[end] !== '(' || !text.endsWith(')')) throw new HlasmSyntax(`unexpected '${text.slice(end)}' in the address '${text}'`);
142
+ const inner = text.slice(end + 1, -1);
143
+ const parts = [];
144
+ let at = 0;
145
+ for (;;) {
146
+ if (inner[at] === ',' || at === inner.length) parts.push(null);
147
+ else {
148
+ const r = readExpression(inner, at);
149
+ parts.push(r.e);
150
+ at = r.end;
151
+ }
152
+ if (at === inner.length) break;
153
+ if (inner[at] !== ',') throw new HlasmSyntax(`unexpected '${inner.slice(at)}' in the address '${text}'`);
154
+ at++;
155
+ }
156
+ return { disp: e, parts };
157
+ }
158
+
159
+ // The symbols an expression names, unqualified, for the locator and the rules.
160
+ export function symbolsOf(e, out = []) {
161
+ if (!e) return out;
162
+ if (e.t === 'sym') out.push(e.name);
163
+ else if (e.t === 'attr') symbolsOf(e.of, out);
164
+ else if (e.t === 'paren' || e.t === 'unary') symbolsOf(e.e, out);
165
+ else if (e.t === 'bin') { symbolsOf(e.l, out); symbolsOf(e.r, out); }
166
+ return out;
167
+ }