@portll/cobolwork 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +89 -26
- package/STABILITY.md +76 -0
- package/bin/cobolwork.mjs +56 -14
- package/lib/baseline.mjs +9 -0
- package/lib/bms.mjs +21 -7
- package/lib/build.mjs +133 -53
- package/lib/capabilities.mjs +13 -11
- package/lib/cics-commands.mjs +501 -9
- package/lib/compliance.mjs +11 -0
- package/lib/consequence.mjs +17 -0
- package/lib/control-reuse.mjs +113 -0
- package/lib/control-workers.mjs +161 -0
- package/lib/control.mjs +77 -4
- package/lib/dataflow.mjs +402 -152
- package/lib/db2/cursor.mjs +15 -0
- package/lib/db2/read.mjs +170 -0
- package/lib/db2/rules.mjs +77 -0
- package/lib/db2/stmt/alter.mjs +473 -0
- package/lib/db2/stmt/grant.mjs +125 -0
- package/lib/db2/stmt/index.mjs +141 -0
- package/lib/db2/stmt/misc.mjs +325 -0
- package/lib/db2/stmt/routine.mjs +564 -0
- package/lib/db2/stmt/storage.mjs +146 -0
- package/lib/db2/stmt/table.mjs +540 -0
- package/lib/db2/stmt/view.mjs +146 -0
- package/lib/diff.mjs +17 -5
- package/lib/equivalence.mjs +44 -5
- package/lib/evidence/cli.mjs +26 -7
- package/lib/evidence/record.mjs +16 -6
- package/lib/evidence/seal.mjs +17 -0
- package/lib/evidence/store.mjs +44 -15
- package/lib/evidence/timestamp.mjs +83 -0
- package/lib/evidence/verify.mjs +120 -42
- package/lib/exec-reading.mjs +51 -0
- package/lib/execution.mjs +3 -2
- package/lib/explain.mjs +2 -0
- package/lib/exploitability.mjs +11 -2
- package/lib/hlasm/asm/data.mjs +128 -0
- package/lib/hlasm/asm/listing.mjs +43 -0
- package/lib/hlasm/asm/output.mjs +45 -0
- package/lib/hlasm/asm/sections.mjs +161 -0
- package/lib/hlasm/asm/symbols.mjs +78 -0
- package/lib/hlasm/exec.mjs +27 -0
- package/lib/hlasm/expr.mjs +167 -0
- package/lib/hlasm/instr.mjs +73 -0
- package/lib/hlasm/locate.mjs +333 -0
- package/lib/hlasm/macro/authorization.mjs +146 -0
- package/lib/hlasm/macro/datasets.mjs +107 -0
- package/lib/hlasm/macro/io.mjs +162 -0
- package/lib/hlasm/macro/le.mjs +53 -0
- package/lib/hlasm/macro/linkage.mjs +143 -0
- package/lib/hlasm/macro/operator.mjs +77 -0
- package/lib/hlasm/macro/program.mjs +184 -0
- package/lib/hlasm/macro/recovery.mjs +85 -0
- package/lib/hlasm/macro/storage.mjs +167 -0
- package/lib/hlasm/macro/structured.mjs +131 -0
- package/lib/hlasm/model.mjs +97 -0
- package/lib/hlasm/mvs38.mjs +47 -0
- package/lib/hlasm/operands.mjs +59 -0
- package/lib/hlasm/optable.mjs +61 -0
- package/lib/hlasm/read.mjs +130 -0
- package/lib/hlasm.mjs +44 -7
- package/lib/ims/dli.mjs +37 -0
- package/lib/ims/macro/dbd.mjs +299 -0
- package/lib/ims/macro/psb.mjs +286 -0
- package/lib/ims/model.mjs +149 -0
- package/lib/ims/operands.mjs +23 -0
- package/lib/ims/read.mjs +37 -0
- package/lib/ims/rules.mjs +135 -0
- package/lib/inventory.mjs +10 -7
- package/lib/ironwork-ids.mjs +24 -0
- package/lib/ironwork.mjs +20 -13
- package/lib/kernel/pds-archive.mjs +256 -0
- package/lib/kernel/registry.mjs +32 -23
- package/lib/kernel/shared-pass.mjs +163 -0
- package/lib/kernel/source-tree.mjs +104 -36
- package/lib/kernel/version-key.mjs +17 -0
- package/lib/layout.mjs +26 -31
- package/lib/options.mjs +26 -4
- package/lib/parser.mjs +136 -17
- package/lib/pli/cursor.mjs +15 -0
- package/lib/pli/expr.mjs +101 -0
- package/lib/pli/include.mjs +82 -0
- package/lib/pli/layout.mjs +125 -0
- package/lib/pli/lex.mjs +198 -0
- package/lib/pli/program.mjs +280 -0
- package/lib/pli/rules/based.mjs +95 -0
- package/lib/pli/rules/conditions.mjs +68 -0
- package/lib/pli/rules/entry.mjs +130 -0
- package/lib/pli/rules/index.mjs +24 -0
- package/lib/pli/rules/preprocessor.mjs +55 -0
- package/lib/pli/statements.mjs +130 -0
- package/lib/pli/stmt/alloc.mjs +45 -0
- package/lib/pli/stmt/assignment.mjs +56 -0
- package/lib/pli/stmt/call.mjs +104 -0
- package/lib/pli/stmt/conditions.mjs +94 -0
- package/lib/pli/stmt/control.mjs +219 -0
- package/lib/pli/stmt/declare.mjs +149 -0
- package/lib/pli/stmt/exec.mjs +55 -0
- package/lib/pli/stmt/io.mjs +239 -0
- package/lib/pli/stmt/misc.mjs +4 -0
- package/lib/pli/stmt/preprocessor.mjs +242 -0
- package/lib/pli/stmt/procedure.mjs +258 -0
- package/lib/pli/stmt/stream.mjs +283 -0
- package/lib/pli/storage.mjs +129 -0
- package/lib/policy.mjs +6 -0
- package/lib/precompile-check.mjs +124 -0
- package/lib/precompile-cics.mjs +8 -4
- package/lib/reach.mjs +11 -2
- package/lib/revision.json +1 -1
- package/lib/sarif.mjs +44 -3
- package/lib/scan.mjs +28 -12
- package/lib/sets/abend.mjs +82 -8
- package/lib/sets/build.mjs +43 -37
- package/lib/sets/cics.mjs +20 -38
- package/lib/sets/compile.mjs +46 -18
- package/lib/sets/copybook.mjs +5 -3
- package/lib/sets/crypto.mjs +5 -3
- package/lib/sets/ddl.mjs +36 -0
- package/lib/sets/flow.mjs +32 -8
- package/lib/sets/hidden.mjs +5 -3
- package/lib/sets/hlasm.mjs +124 -8
- package/lib/sets/ims.mjs +139 -0
- package/lib/sets/log.mjs +11 -9
- package/lib/sets/opaque.mjs +27 -7
- package/lib/sets/pli.mjs +40 -0
- package/lib/sets/priv.mjs +5 -3
- package/lib/sets/recon.mjs +5 -3
- package/lib/sets/secrets.mjs +112 -0
- package/lib/sets/semantics.mjs +8 -3
- package/lib/sets/web.mjs +39 -23
- package/lib/site.mjs +10 -0
- package/lib/sources.mjs +80 -20
- package/lib/statement-cursor.mjs +67 -0
- package/lib/verify.mjs +3 -2
- package/lib/version.mjs +6 -0
- package/package.json +3 -2
- package/rules/compliance-cobit2019.json +520 -5
- package/rules/compliance-dora.json +509 -5
- package/rules/compliance-ffiec.json +505 -1
- package/rules/compliance-nist80053.json +557 -1
- package/rules/gitleaks-mainframe.toml +46 -15
- package/rules/hlasm-instructions.json +2396 -0
- package/rules/hlasm-optables.json +8024 -0
- package/schema/cobolwork-baseline.schema.json +36 -0
- package/schema/cobolwork-build-provenance.schema.json +187 -0
- package/schema/cobolwork-build.schema.json +382 -0
- package/schema/cobolwork-capabilities.schema.json +239 -0
- package/schema/cobolwork-diff.schema.json +217 -0
- package/schema/cobolwork-evidence.schema.json +161 -0
- package/schema/cobolwork-execution.schema.json +53 -0
- package/schema/cobolwork-explain.schema.json +360 -0
- package/schema/cobolwork-finding.schema.json +465 -0
- package/schema/cobolwork-flow.schema.json +465 -0
- package/schema/cobolwork-gate.schema.json +211 -0
- package/schema/cobolwork-inventory.schema.json +206 -0
- package/schema/cobolwork-parse.schema.json +105 -0
- package/schema/cobolwork-reach.schema.json +74 -0
- package/schema/cobolwork-report.schema.json +559 -0
- package/schema/cobolwork-witness.schema.json +107 -0
- package/schema/cobolwork.baseline.schema.json +101 -0
- package/schema/cobolwork.policy.schema.json +7 -0
- package/schema/cobolwork.site.schema.json +116 -0
|
@@ -0,0 +1,161 @@
|
|
|
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
|
+
// An unnamed dummy section, like an unnamed COM, is defined when the name entry is omitted.
|
|
133
|
+
// https://www.ibm.com/docs/en/hla-and-tf/1.6.0?topic=sections-unnamed-section
|
|
134
|
+
DSECT: (st) => noOperand('DSECT', st, false),
|
|
135
|
+
RSECT: (st) => noOperand('RSECT', st, false),
|
|
136
|
+
COM: (st) => noOperand('COM', st, false),
|
|
137
|
+
START: (st) => {
|
|
138
|
+
const ops = splitOperands(st.field);
|
|
139
|
+
if (ops.length > 1) throw new HlasmSyntax('START takes at most one operand');
|
|
140
|
+
const origin = ops.length ? parseExpression(ops[0].trim()) : null;
|
|
141
|
+
return { kind: 'START', name: st.name || null, origin };
|
|
142
|
+
},
|
|
143
|
+
LOCTR: (st) => {
|
|
144
|
+
if (!st.name) throw new HlasmSyntax('LOCTR requires a name');
|
|
145
|
+
if (st.field.trim()) throw new HlasmSyntax('LOCTR takes no operand');
|
|
146
|
+
return { kind: 'LOCTR', name: st.name };
|
|
147
|
+
},
|
|
148
|
+
ENTRY: (st) => nameList('ENTRY', st),
|
|
149
|
+
EXTRN: (st) => nameList('EXTRN', st),
|
|
150
|
+
WXTRN: (st) => nameList('WXTRN', st),
|
|
151
|
+
AMODE: (st) => mode('AMODE', st, AMODE_VALUES),
|
|
152
|
+
RMODE: (st) => mode('RMODE', st, RMODE_VALUES),
|
|
153
|
+
ALIAS: alias,
|
|
154
|
+
XATTR: xattr,
|
|
155
|
+
CATTR: cattr,
|
|
156
|
+
DXD: dxd,
|
|
157
|
+
CXD: (st) => {
|
|
158
|
+
if (st.field.trim()) throw new HlasmSyntax('CXD takes no operand');
|
|
159
|
+
return { kind: 'CXD', name: st.name || null };
|
|
160
|
+
},
|
|
161
|
+
};
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// Machine instructions, matched against the operand syntax rules/hlasm-instructions.json gives each
|
|
3
|
+
// mnemonic: the operand count, and each operand an expression or an address of the written shape.
|
|
4
|
+
// One matcher for every format, since IBM's operand syntax already says what each operand is.
|
|
5
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
6
|
+
import { HlasmSyntax, splitOperands } from './operands.mjs';
|
|
7
|
+
import { parseExpression, parseAddress } from './expr.mjs';
|
|
8
|
+
|
|
9
|
+
const TABLE = new URL('../../rules/hlasm-instructions.json', import.meta.url);
|
|
10
|
+
const rows = existsSync(TABLE) ? JSON.parse(readFileSync(TABLE, 'utf8')).instructions : [];
|
|
11
|
+
export const INSTRUCTIONS = new Map(rows.map((r) => [r.mnemonic, r]));
|
|
12
|
+
|
|
13
|
+
// "R1,D2(X2,B2)[,M3]" as operand shapes: an address names how many parts its parentheses may hold.
|
|
14
|
+
const shapes = new Map();
|
|
15
|
+
function shapeOf(pattern) {
|
|
16
|
+
if (shapes.has(pattern)) return shapes.get(pattern);
|
|
17
|
+
const out = [];
|
|
18
|
+
let optional = false;
|
|
19
|
+
for (let part of splitOperands(pattern.replace(/\[,?/g, ',[').replace(/\]/g, '').replace(/^,/, ''))) {
|
|
20
|
+
if (part.startsWith('[')) { optional = true; part = part.slice(1); }
|
|
21
|
+
const address = /^(D\d*)\(([^)]*)\)$/.exec(part);
|
|
22
|
+
out.push({ name: part, optional, parts: address ? address[2].split(',').length : 0 });
|
|
23
|
+
}
|
|
24
|
+
shapes.set(pattern, out);
|
|
25
|
+
return out;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const formatFamily = (format) => String(format || '').replace(/-[a-z]$/, '');
|
|
29
|
+
|
|
30
|
+
// An explicit length is assembled into a length field: 8 bits for L, 4 bits for L1 and L2, each
|
|
31
|
+
// holding the length less one, so 0 to 256 and 0 to 16 bytes. Checked when the length is a
|
|
32
|
+
// constant; a symbol's value needs the symbol table.
|
|
33
|
+
const LENGTH_FIELD = { L: 256, L1: 16, L2: 16 };
|
|
34
|
+
function constant(e) {
|
|
35
|
+
if (!e) return null;
|
|
36
|
+
if (e.t === 'num') return e.v;
|
|
37
|
+
if (e.t === 'sdt' && e.type === 'X') return /^[0-9A-F]+$/i.test(e.text) ? parseInt(e.text, 16) : null;
|
|
38
|
+
if (e.t === 'sdt' && e.type === 'B') return /^[01]+$/.test(e.text) ? parseInt(e.text, 2) : null;
|
|
39
|
+
if (e.t === 'paren') return constant(e.e);
|
|
40
|
+
if (e.t === 'unary') { const v = constant(e.e); return v === null ? null : e.op === '-' ? -v : v; }
|
|
41
|
+
if (e.t === 'bin') {
|
|
42
|
+
const l = constant(e.l);
|
|
43
|
+
const r = constant(e.r);
|
|
44
|
+
if (l === null || r === null) return null;
|
|
45
|
+
return { '+': l + r, '-': l - r, '*': l * r, '/': r ? Math.trunc(l / r) : 0 }[e.op] ?? null;
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function parseInstruction(st, row) {
|
|
51
|
+
const shape = shapeOf(row.operands || '');
|
|
52
|
+
// An instruction with no operands takes whatever follows it as remarks: SAM24 Back to AMODE 24.
|
|
53
|
+
if (!shape.length) return { kind: 'INSTRUCTION', mnemonic: st.operation, format: row.format, length: row.length, operands: [] };
|
|
54
|
+
const given = splitOperands(st.field || '');
|
|
55
|
+
const required = shape.filter((s) => !s.optional).length;
|
|
56
|
+
if (given.length < required || given.length > shape.length) {
|
|
57
|
+
throw new HlasmSyntax(`${st.operation} takes ${required === shape.length ? required : `${required} to ${shape.length}`} operands (${row.operands}), not ${given.length}`);
|
|
58
|
+
}
|
|
59
|
+
const operands = given.map((text, i) => {
|
|
60
|
+
const s = shape[i];
|
|
61
|
+
if (!text) throw new HlasmSyntax(`operand ${i + 1} of ${st.operation} (${s.name}) is empty`);
|
|
62
|
+
if (!s.parts) return { operand: s.name, e: parseExpression(text) };
|
|
63
|
+
const a = parseAddress(text);
|
|
64
|
+
if (a.parts && a.parts.length > s.parts) throw new HlasmSyntax(`operand ${i + 1} of ${st.operation} (${s.name}) has ${a.parts.length} parts in parentheses, not at most ${s.parts}`);
|
|
65
|
+
const field = /^D\d*\((L\d?),/.exec(s.name)?.[1];
|
|
66
|
+
const length = field && a.parts ? constant(a.parts[0]) : null;
|
|
67
|
+
if (length !== null && (length < 0 || length > LENGTH_FIELD[field])) {
|
|
68
|
+
throw new HlasmSyntax(`operand ${i + 1} of ${st.operation} has length ${length}, and ${field} holds 0 to ${LENGTH_FIELD[field]}`);
|
|
69
|
+
}
|
|
70
|
+
return { operand: s.name, ...a };
|
|
71
|
+
});
|
|
72
|
+
return { kind: 'INSTRUCTION', mnemonic: st.operation, format: row.format, length: row.length, operands };
|
|
73
|
+
}
|