@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,61 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // HLASM's OPTABLE option: which operation code table the assembler reads machine instructions from.
3
+ // A mnemonic outside the chosen table is no instruction, so a macro of that name is called instead.
4
+ import { readFileSync, existsSync } from 'node:fs';
5
+
6
+ const TABLE = new URL('../../rules/hlasm-optables.json', import.meta.url);
7
+ const T = existsSync(TABLE) ? JSON.parse(readFileSync(TABLE, 'utf8')) : { levels: [], synonyms: {}, machines: {}, mnemonics: {} };
8
+ const RANK = new Map(T.levels.map((level, i) => [level, i]));
9
+
10
+ // An OPTABLE or MACHINE suboption as written, to the table's level; null when it names none.
11
+ export function optableLevel(name, { machine = false } = {}) {
12
+ const n = String(name || '').trim().toUpperCase();
13
+ if (machine) return T.machines[n] ?? null;
14
+ if (n === 'UNI') return 'UNI';
15
+ return RANK.has(n) ? n : T.synonyms[n] ?? null;
16
+ }
17
+
18
+ // "UNI,DOS-370": UNI, a level, a level and later (X-), or a range (X-Y).
19
+ function covers(ranges, level) {
20
+ return ranges.split(',').some((part) => {
21
+ if (part === level) return true;
22
+ if (level === 'UNI') return false;
23
+ const m = /^([^-]+)-([^-]*)$/.exec(part);
24
+ if (!m || !RANK.has(m[1])) return false;
25
+ const at = RANK.get(level);
26
+ return at >= RANK.get(m[1]) && (m[2] === '' || at <= RANK.get(m[2]));
27
+ });
28
+ }
29
+
30
+ // Whether the table at this level (UNI when none is given) holds the mnemonic; one the table does not
31
+ // list is taken as held.
32
+ const excluded = new Map();
33
+ export function inOptable(mnemonic, level) {
34
+ const at = level ?? 'UNI';
35
+ if (!excluded.has(at)) excluded.set(at, new Set(Object.entries(T.mnemonics).filter(([, ranges]) => !ranges.some((r) => covers(r, at))).map(([m]) => m)));
36
+ return !excluded.get(at).has(mnemonic);
37
+ }
38
+
39
+ // OPTABLE or MACHINE in an option string (PARM, *PROCESS), to a level; OVERRIDE(...) says it is forced.
40
+ export function optableIn(options) {
41
+ const text = String(options || '').toUpperCase();
42
+ const override = /OVERRIDE\(([^)]*(?:\([^)]*\)[^)]*)*)\)/.exec(text)?.[1] ?? '';
43
+ const read = (s) => {
44
+ const o = /\bOPTABLE\(\s*([A-Z0-9]+)/.exec(s);
45
+ if (o) return optableLevel(o[1]);
46
+ const m = /\bMACHINE\(\s*([A-Z0-9-]+)/.exec(s);
47
+ return m ? optableLevel(m[1], { machine: true }) : null;
48
+ };
49
+ const forced = read(override);
50
+ return forced ? { level: forced, override: true } : read(text) ? { level: read(text), override: false } : null;
51
+ }
52
+
53
+ // The *PROCESS statements that open a source file name its options.
54
+ export function processOptions(text) {
55
+ const out = [];
56
+ for (const line of String(text).replace(/\r\n?/g, '\n').split('\n')) {
57
+ if (!/^\*PROCESS\s/i.test(line)) break;
58
+ out.push(line.slice(8, 71).trim());
59
+ }
60
+ return out.join(',');
61
+ }
@@ -7,6 +7,7 @@
7
7
  import { foldStatements } from '../bms.mjs';
8
8
  import { HlasmSyntax, NAME, macroOperands } from './operands.mjs';
9
9
  import { INSTRUCTIONS, parseInstruction, formatFamily } from './instr.mjs';
10
+ import { inOptable } from './optable.mjs';
10
11
  import { parsers as data } from './asm/data.mjs';
11
12
  import { parsers as sections } from './asm/sections.mjs';
12
13
  import { parsers as symbols } from './asm/symbols.mjs';
@@ -50,7 +51,9 @@ export const PARSERS = Object.freeze({ ...data, ...sections, ...symbols, ...list
50
51
  export const MACRO_PARSERS = Object.freeze({ ...program, ...linkage, ...storage, ...authorization, ...operator, ...datasets, ...io, ...recovery, ...le, ...structured });
51
52
 
52
53
  // Statements other than comments, each marked inMacro when it is part of a macro definition, the
53
- // prototype (the statement after MACRO) marked as such.
54
+ // prototype (the statement after MACRO) marked as such, and sourceMacro when its operation names a
55
+ // macro the file has already defined: a source macro with the name of an instruction or a library
56
+ // macro overrides it (https://www.ibm.com/docs/en/hla-and-tf/1.6.0?topic=definitions-macro-instruction-prototype).
54
57
  // EXEC CICS and EXEC SQL are written for their translators, with blanks between the words: their
55
58
  // field is the cards' text after EXEC, columns 16 to 71 of each continuation, not the operands up
56
59
  // to the first blank as an assembler statement's are.
@@ -58,20 +61,28 @@ function execField(phys, st) {
58
61
  return st.lines.map((n, i) => (i === 0 ? phys[n - 1].slice(0, 71).replace(/^\S*\s+EXEC\b/i, '') : phys[n - 1].slice(15, 71))).join(' ').replace(/\s+/g, ' ').trim();
59
62
  }
60
63
 
61
- export function readHlasmStatements(text) {
62
- const { statements, diags } = foldStatements(text);
64
+ // libraryMacros: the macros the repository's own library defines. One with the name of a macro this
65
+ // reader knows is the repository's, found ahead of the system's on the macro search; an instruction
66
+ // is never looked for in a library.
67
+ export function readHlasmStatements(text, { libraryMacros = null } = {}) {
68
+ const { statements, diags } = foldStatements(text, { batch: true });
63
69
  const phys = String(text).replace(/\r\n?/g, '\n').split('\n');
64
70
  const out = [];
65
71
  let depth = 0;
66
72
  let prototypeNext = false;
73
+ const defined = new Set();
67
74
  for (const s of statements) {
68
- if (s.kind === 'comment') continue;
75
+ if (s.kind === 'comment' || s.kind === 'not-assembler') continue;
69
76
  const st = { ...s, inMacro: depth > 0, prototype: false };
70
77
  if (s.kind === 'statement' && s.operation === 'EXEC') st.field = execField(phys, s);
71
78
  if (s.kind === 'statement') {
72
79
  if (s.operation === 'MACRO') { depth++; prototypeNext = true; st.inMacro = true; }
73
- else if (prototypeNext) { st.prototype = true; prototypeNext = false; }
80
+ else if (prototypeNext) { st.prototype = true; prototypeNext = false; if (s.operation) defined.add(s.operation.toUpperCase()); }
81
+ else if (s.operation && defined.has(s.operation.toUpperCase())) st.sourceMacro = true;
82
+ else if (s.operation && libraryMacros?.has(s.operation.toUpperCase()) && NAMED_MACROS.has(s.operation.toUpperCase())) st.libraryMacro = true;
74
83
  if (s.operation === 'MEND') depth = Math.max(0, depth - 1);
84
+ // Each program of a batch is its own assembly: its source macros are its own.
85
+ if (s.operation === 'END' && depth === 0) defined.clear();
75
86
  }
76
87
  out.push(st);
77
88
  }
@@ -90,19 +101,22 @@ function run(kind, parser, st, ...args) {
90
101
 
91
102
  // { kind, status, node?, reason?, crash? }: parsed, unbuilt (no parser for the kind yet), unparsed
92
103
  // (the parser refused it) or unknown (not an operation HLASM has).
93
- export function parseHlasmStatement(st) {
104
+ export function parseHlasmStatement(st, opts = {}) {
94
105
  if (st.kind !== 'statement') return { kind: 'UNREADABLE', status: 'unknown', reason: 'no operation field' };
106
+ if (st.continues === 'comment') return { kind: 'CONTINUED', status: 'unparsed', reason: 'the comment above runs into column 72, so the assembler reads this line as part of it' };
107
+ if (st.continues === 'statement') return { kind: 'CONTINUED', status: 'unparsed', reason: 'the statement above runs into column 72 and this line does not leave columns 1 to 15 blank' };
95
108
  const op = st.operation;
96
109
  if (op === 'MACRO' || op === 'MEND') return { kind: 'MACRO DEFINITION', status: 'parsed', node: { kind: op } };
97
110
  if (CONDITIONAL.has(op)) return { kind: 'CONDITIONAL', status: 'unbuilt' };
98
111
  if (st.prototype) return run('PROTOTYPE', model.PROTOTYPE, st);
99
112
  if (st.inMacro) return run('MODEL', model.MODEL, st);
100
113
  if (/&[A-Z$#@_]/i.test(`${st.name || ''} ${op} ${st.field || ''}`.replace(/&&/g, ''))) return run('SUBSTITUTED', model.SUBSTITUTED, st);
114
+ if (st.sourceMacro || st.libraryMacro) return run('MACRO CALL', (s) => ({ kind: 'MACRO CALL', macro: op, ...(st.sourceMacro ? { source: true } : { library: true }), ...macroOperands(s.field || '') }), st);
101
115
  // A lone comma is the convention for no operands with remarks after it (CSECT , remark); an
102
116
  // instruction that takes no operands reads whatever follows it as remarks.
103
117
  if (ASSEMBLER.has(op)) return run(op, PARSERS[op], st.field === ',' || NO_OPERANDS.has(op) ? { ...st, field: '' } : st);
104
118
  const row = INSTRUCTIONS.get(op);
105
- if (row) return run(formatFamily(row.format), parseInstruction, st, row);
119
+ if (row && inOptable(op, opts.optable)) return run(formatFamily(row.format), parseInstruction, st, row);
106
120
  if (op === 'EXEC') {
107
121
  const kind = `EXEC ${(/^\S+/.exec(st.field || '') || ['?'])[0].toUpperCase()}`;
108
122
  return run(kind, exec[kind], st);
@@ -110,7 +124,7 @@ export function parseHlasmStatement(st) {
110
124
  if (!NAME.test(op)) return { kind: 'UNKNOWN', status: 'unknown', reason: `${op} is not an operation name` };
111
125
  if (NAMED_MACROS.has(op)) {
112
126
  const parser = MACRO_PARSERS[op];
113
- return run(op, parser && ((s) => parser(s, macroOperands(s.field === ',' ? '' : s.field || ''))), st);
127
+ return run(op, parser && ((s) => parser(s, macroOperands(s.field === ',' ? '' : s.field || ''), opts)), st);
114
128
  }
115
129
  return run('MACRO CALL', (s) => ({ kind: 'MACRO CALL', macro: op, ...macroOperands(s.field || '') }), st);
116
130
  }
package/lib/hlasm.mjs CHANGED
@@ -7,6 +7,7 @@
7
7
  import { readFileSync } from 'node:fs';
8
8
  import { foldStatements } from './bms.mjs';
9
9
  import { parseOperands, splitOperands } from './cards.mjs';
10
+ import { keyZeroExtkeys, systemKeyExtkeys } from './hlasm/mvs38.mjs';
10
11
 
11
12
  const TABLE = JSON.parse(readFileSync(new URL('../rules/hlasm-operations.json', import.meta.url), 'utf8'));
12
13
  export const OPERATIONS = new Map(TABLE.operations.map((o) => [o.name, o]));
@@ -31,31 +32,64 @@ const IMS = new Set(['DBD', 'DBDGEN', 'SEGM', 'PSBGEN', 'PCB', 'SENSEG']);
31
32
 
32
33
  // MODESET changes the PSW key or the state; these operand values make it key zero or supervisor
33
34
  // state, and the opposite values (KEY=NZERO, MODE=PROB) return from it. EXTKEY is the inline form,
34
- // which sets only the PSW key.
35
+ // which sets only the PSW key; MVS 3.8's names for key zero count when its forms are read.
35
36
  const STATE_CHANGE = { KEY: ['ZERO'], MODE: ['SUP'], EXTKEY: ['ZERO'] };
36
37
 
37
38
  const nameList = (field) => splitOperands(field || '').map((s) => s.trim().toUpperCase()).filter((s) => /^[A-Z$#@_][A-Z0-9$#@_]*$/.test(s));
38
39
 
39
- function stateChange(field) {
40
+ function stateChange(field, opts) {
40
41
  let ops;
41
42
  try { ops = parseOperands(field || ''); } catch { return null; }
42
- for (const [key, values] of Object.entries(STATE_CHANGE)) {
43
+ for (const [key, zero] of Object.entries(STATE_CHANGE)) {
44
+ const values = key === 'EXTKEY' ? [...zero, ...keyZeroExtkeys(opts)] : zero;
43
45
  const v = ops.keywords.get(key);
44
46
  if (v && values.includes(String(v).trim().toUpperCase())) return `${key}=${String(v).trim().toUpperCase()}`;
45
47
  }
46
48
  return null;
47
49
  }
48
50
 
51
+ // EXTKEY= naming a system key other than zero: z/OS's KEY2, KEY3, KEY4 and KEY7, and MVS 3.8's
52
+ // names for keys 1 to 7 when its forms are read. TCB, RBT1 and RBT234 take a key decided at run time.
53
+ const SYSTEM_EXTKEYS = [['KEY2', 2], ['KEY3', 3], ['KEY4', 4], ['KEY7', 7]];
54
+
55
+ function systemKeyChange(field, opts) {
56
+ let ops;
57
+ try { ops = parseOperands(field || ''); } catch { return null; }
58
+ const v = String(ops.keywords.get('EXTKEY') || '').trim().toUpperCase();
59
+ const key = new Map([...SYSTEM_EXTKEYS, ...systemKeyExtkeys(opts)]).get(v);
60
+ return key === undefined ? null : { written: `EXTKEY=${v}`, key };
61
+ }
62
+
63
+ // A disassembler's listing: each line's object code, its characters between asterisks and its offset
64
+ // to the right of the source, as in `BR R15 07FF *..* 00004`.
65
+ const DISASSEMBLED = /\s[0-9A-F]{2,16}(?: [0-9A-F]{2,16}){0,2}\s+\*.{1,16}\*\s+[0-9A-F]{5,8}\s*$/;
66
+
67
+ // English function words, which no statement's name, operation or first operand holds.
68
+ const FUNCTION_WORDS = new Set(['THE', 'OF', 'AND', 'TO', 'IS', 'IN', 'FOR', 'THIS', 'THAT', 'WITH', 'ARE', 'BE', 'IT', 'AN', 'WILL', 'CAN',
69
+ 'YOU', 'WHICH', 'FROM', 'ON', 'AS', 'BY', 'NOT', 'IF', 'WHEN', 'HAS', 'HAVE', 'WAS', 'ALL', 'ANY', 'ONE', 'MAY', 'YOUR', 'THESE', 'THEY',
70
+ 'THERE', 'SHOULD', 'MUST', 'ALSO', 'ONLY']);
71
+
72
+ // A manual, article or README saved as assembler source: its statements, macro definitions included,
73
+ // read as English, and the reader refuses many of those in open code. On the CBT, MVS 3.8j and GitHub
74
+ // corpora both thresholds together held for 25 documents and no program.
75
+ export function isDocument(statements, open, refused) {
76
+ if (open < 5) return false;
77
+ const english = statements.filter((s) => [s.name, s.operation, (s.field || '').split(/[\s,]/)[0]].some((w) => w && FUNCTION_WORDS.has(w.toUpperCase()))).length;
78
+ return english >= 0.15 * statements.length && refused >= 0.2 * open;
79
+ }
80
+
49
81
  // What the file is, and in it: each operation the table names with where it is written; the
50
82
  // sections and entry points it defines, which a CALL or a job step can name; and the names it
51
83
  // leaves to the binder.
52
- export function readHlasm(text) {
53
- const { statements, diags } = foldStatements(text);
84
+ export function readHlasm(text, opts = {}) {
85
+ const { statements, diags } = foldStatements(text, { batch: true });
54
86
  const ops = statements.filter((s) => s.kind === 'statement');
55
87
  const has = (set) => ops.some((s) => set.has(s.operation));
56
88
  const weak = new Set(ops.filter((s) => WEAK.has(s.operation)).map((s) => s.operation)).size;
57
89
  const assembler = !GNU.test(text) && (ops.some(strong) || weak >= 2);
58
- const kind = has(BMS) ? 'bms' : has(IMS) ? 'ims' : assembler ? 'hlasm' : 'unrecognised';
90
+ const lines = String(text).replace(/\r\n?/g, '\n').split('\n');
91
+ const listing = ops.length >= 5 && ops.filter((s) => DISASSEMBLED.test(lines[s.line - 1].slice(0, 80))).length * 2 >= ops.length;
92
+ const kind = has(BMS) ? 'bms' : has(IMS) ? 'ims' : !assembler ? 'unrecognised' : listing ? 'listing' : 'hlasm';
59
93
  const out = { kind, statements: ops.length, operations: [], defines: [], external: [], diags };
60
94
  if (kind !== 'hlasm') return out;
61
95
 
@@ -72,7 +106,10 @@ export function readHlasm(text) {
72
106
  const op = OPERATIONS.get(s.operation);
73
107
  if (!op || op.class === 'section' || op.class === 'linkage') continue;
74
108
  const use = { name: op.name, class: op.class, line: s.line, inMacro, field: s.field };
75
- if (op.name === 'MODESET') use.stateChange = stateChange(s.field);
109
+ if (op.name === 'MODESET') {
110
+ use.stateChange = stateChange(s.field, opts);
111
+ if (!use.stateChange) use.systemKey = systemKeyChange(s.field, opts);
112
+ }
76
113
  out.operations.push(use);
77
114
  }
78
115
  return out;
@@ -0,0 +1,37 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // The DL/I call interface as a program issues it: CALL 'CBLTDLI' or 'AIBTDLI' USING [count,] function,
3
+ // PCB or AIB, I/O area [, SSA ...], and EXEC DLI. Each database function needs a processing option on
4
+ // the PCB it runs against, the PROCOPT of its PSBGEN PCB or SENSEG: a get needs G, ISRT I, REPL R
5
+ // and DLET D. A get call fills the I/O area with the segment it retrieves.
6
+
7
+ export const DLI_ROUTINE = /^(CBLTDLI|AIBTDLI)$/i;
8
+
9
+ export const DLI_FUNCTIONS = Object.freeze({
10
+ GU: 'G', GN: 'G', GNP: 'G', GHU: 'G', GHN: 'G', GHNP: 'G',
11
+ ISRT: 'I', REPL: 'R', DLET: 'D',
12
+ });
13
+
14
+ export const isGet = (fn) => DLI_FUNCTIONS[fn] === 'G';
15
+
16
+ // Calls that are not database calls: scheduling, termination, checkpoint, restart, rollback, sync,
17
+ // logging, statistics and the system service calls. They need no processing option.
18
+ export const DLI_SERVICES = new Set(['PCB', 'TERM', 'CHKP', 'XRST', 'ROLB', 'ROLL', 'ROLS', 'SETS', 'SETU', 'SYNC', 'INIT',
19
+ 'INQY', 'GSCD', 'LOG', 'STAT', 'APSB', 'DPSB', 'GMSG', 'ICMD', 'RCMD', 'SCHD', 'AUTH', 'CHNG', 'PURG', 'CMD', 'GCMD', 'ICAL']);
20
+
21
+ // The PSB a DFSRRC00 step's PARM names: region type, program, then PSB, which defaults to the program.
22
+ export function psbOfParm(parm) {
23
+ const [, program, psb] = String(parm || '').replace(/^[('\s]+|[)'\s]+$/g, '').split(',').map((x) => x.trim().toUpperCase());
24
+ return program ? { program, psb: psb || program } : null;
25
+ }
26
+
27
+ // The function code of a CALL to a DL/I routine and the argument that holds it: the first argument,
28
+ // or the second when the first is a parameter count. `valueOf(arg)` gives the literal or the VALUE
29
+ // of the item an argument names.
30
+ export function dliFunction(using, valueOf) {
31
+ for (let index = 0; index < Math.min(2, using.length); index++) {
32
+ const v = valueOf(using[index]);
33
+ const fn = v == null ? null : String(v).trim().toUpperCase();
34
+ if (fn && Object.hasOwn(DLI_FUNCTIONS, fn)) return { fn, index };
35
+ }
36
+ return null;
37
+ }
@@ -0,0 +1,299 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // Parsers for the IMS DBD macros: DBD, DATASET, AREA, SEGM, FIELD, LCHILD, XDFLD, DBDGEN.
3
+ import { ImsSyntax, sublist, integer, onlyKeywords } from '../operands.mjs';
4
+
5
+ const ACCESS_TYPES = new Set(['HDAM', 'HIDAM', 'HISAM', 'HSAM', 'SHISAM', 'SHSAM', 'PHDAM', 'PHIDAM', 'PSINDEX', 'INDEX', 'LOGICAL', 'DEDB', 'MSDB', 'GSAM']);
6
+ const ACCESS_METHODS = new Set(['VSAM', 'OSAM', 'BSAM']);
7
+ const FIELD_TYPES = new Set(['C', 'X', 'P', 'Z', 'H', 'F', 'G']);
8
+
9
+ function parseDBD(st, ops) {
10
+ const name = ops.keywords.get('NAME');
11
+ if (!name) throw new ImsSyntax('DBD requires NAME');
12
+ const accessRaw = ops.keywords.get('ACCESS');
13
+ let access = null;
14
+ if (accessRaw) {
15
+ const items = sublist(accessRaw);
16
+ if (!items || items.length < 1 || items.length > 3) throw new ImsSyntax('DBD ACCESS must be a type or (type, method[,prot])');
17
+ const type = items[0].toUpperCase();
18
+ if (!ACCESS_TYPES.has(type)) throw new ImsSyntax(`DBD ACCESS type ${type} is not recognised`);
19
+ let method = null;
20
+ if (items.length >= 2) {
21
+ method = items[1].toUpperCase();
22
+ if (!ACCESS_METHODS.has(method)) throw new ImsSyntax(`DBD ACCESS method ${method} is not recognised`);
23
+ }
24
+ access = { type, method };
25
+ }
26
+
27
+ let passwd = null;
28
+ const passwdRaw = ops.keywords.get('PASSWD');
29
+ if (passwdRaw) {
30
+ const p = passwdRaw.toUpperCase();
31
+ if (p !== 'YES' && p !== 'NO') throw new ImsSyntax('DBD PASSWD must be YES or NO');
32
+ passwd = p;
33
+ }
34
+
35
+ let rmname = null;
36
+ const rmnameRaw = ops.keywords.get('RMNAME');
37
+ if (rmnameRaw) {
38
+ const items = sublist(rmnameRaw);
39
+ if (!items || items.length < 1 || items.length > 4) throw new ImsSyntax('DBD RMNAME must be (module[,anchors[,rbn[,bytes]]])');
40
+ rmname = items;
41
+ }
42
+
43
+ const keywords = {};
44
+ for (const [k, v] of ops.keywords) keywords[k] = v;
45
+
46
+ return {
47
+ kind: 'DBD',
48
+ name,
49
+ access,
50
+ passwd,
51
+ rmname,
52
+ keywords
53
+ };
54
+ }
55
+
56
+ function parseDATASET(st, ops) {
57
+ const keywords = {};
58
+ for (const [k, v] of ops.keywords) keywords[k] = v;
59
+
60
+ const dd1 = ops.keywords.get('DD1');
61
+ const dd2 = ops.keywords.get('DD2');
62
+ const ovflw = ops.keywords.get('OVFLW');
63
+ const device = ops.keywords.get('DEVICE');
64
+ const sizeRaw = ops.keywords.get('SIZE');
65
+ let size = null;
66
+ if (sizeRaw) {
67
+ const items = sublist(sizeRaw);
68
+ if (items) size = items;
69
+ else size = [sizeRaw];
70
+ }
71
+
72
+ return {
73
+ kind: 'DATASET',
74
+ label: st.name,
75
+ dd1,
76
+ dd2,
77
+ ovflw,
78
+ device,
79
+ size,
80
+ keywords
81
+ };
82
+ }
83
+
84
+ function parseAREA(st, ops) {
85
+ const keywords = {};
86
+ for (const [k, v] of ops.keywords) keywords[k] = v;
87
+
88
+ return {
89
+ kind: 'AREA',
90
+ keywords
91
+ };
92
+ }
93
+
94
+ function parseSEGM(st, ops) {
95
+ const name = ops.keywords.get('NAME');
96
+ if (!name) throw new ImsSyntax('SEGM requires NAME');
97
+
98
+ const parentRaw = ops.keywords.get('PARENT');
99
+ let parent = null;
100
+ if (parentRaw) {
101
+ const p = parentRaw.trim();
102
+ if (p === '0') {
103
+ parent = null;
104
+ } else {
105
+ const items = sublist(p);
106
+ if (items && items.length === 1) {
107
+ parent = items[0];
108
+ } else {
109
+ parent = p;
110
+ }
111
+ }
112
+ }
113
+
114
+ const bytesRaw = ops.keywords.get('BYTES');
115
+ let bytes = null;
116
+ if (bytesRaw) {
117
+ const items = sublist(bytesRaw);
118
+ if (items) {
119
+ if (items.length === 1) {
120
+ const n = integer(items[0]);
121
+ if (n === null) throw new ImsSyntax('SEGM BYTES must be an integer or (max,min)');
122
+ bytes = { max: n, min: n };
123
+ } else if (items.length === 2) {
124
+ const max = integer(items[0]);
125
+ const min = integer(items[1]);
126
+ if (max === null || min === null) throw new ImsSyntax('SEGM BYTES (max,min) must be integers');
127
+ bytes = { max, min };
128
+ } else {
129
+ throw new ImsSyntax('SEGM BYTES must be an integer or (max,min)');
130
+ }
131
+ } else {
132
+ const n = integer(bytesRaw);
133
+ if (n === null) throw new ImsSyntax('SEGM BYTES must be an integer or (max,min)');
134
+ bytes = { max: n, min: n };
135
+ }
136
+ }
137
+
138
+ const rulesRaw = ops.keywords.get('RULES');
139
+ let rules = null;
140
+ if (rulesRaw) {
141
+ rules = sublist(rulesRaw);
142
+ }
143
+
144
+ const pointerRaw = ops.keywords.get('POINTER');
145
+ let pointer = null;
146
+ if (pointerRaw) {
147
+ const items = sublist(pointerRaw);
148
+ if (items) pointer = items;
149
+ else pointer = pointerRaw;
150
+ }
151
+
152
+ const keywords = {};
153
+ for (const [k, v] of ops.keywords) keywords[k] = v;
154
+
155
+ return {
156
+ kind: 'SEGM',
157
+ name,
158
+ parent,
159
+ bytes,
160
+ rules,
161
+ pointer,
162
+ keywords
163
+ };
164
+ }
165
+
166
+ function parseFIELD(st, ops) {
167
+ const nameRaw = ops.keywords.get('NAME');
168
+ if (!nameRaw) throw new ImsSyntax('FIELD requires NAME');
169
+
170
+ const items = sublist(nameRaw);
171
+ let name, seq = false, unique = null;
172
+ if (items) {
173
+ name = items[0];
174
+ if (items.length >= 2) {
175
+ const seqFlag = items[1].toUpperCase();
176
+ if (seqFlag === 'SEQ') seq = true;
177
+ else throw new ImsSyntax('FIELD NAME sublist second element must be SEQ');
178
+ }
179
+ if (items.length >= 3) {
180
+ const uFlag = items[2].toUpperCase();
181
+ if (uFlag === 'U') unique = true;
182
+ else if (uFlag === 'M') unique = false;
183
+ else throw new ImsSyntax('FIELD NAME sublist third element must be U or M');
184
+ }
185
+ } else {
186
+ name = nameRaw;
187
+ }
188
+
189
+ const bytesRaw = ops.keywords.get('BYTES');
190
+ let bytes = null;
191
+ if (bytesRaw) {
192
+ const n = integer(bytesRaw);
193
+ if (n === null) throw new ImsSyntax('FIELD BYTES must be an integer');
194
+ bytes = n;
195
+ }
196
+
197
+ const startRaw = ops.keywords.get('START');
198
+ let start = null;
199
+ if (startRaw) {
200
+ const n = integer(startRaw);
201
+ if (n === null) throw new ImsSyntax('FIELD START must be an integer');
202
+ start = n;
203
+ }
204
+
205
+ const typeRaw = ops.keywords.get('TYPE');
206
+ let type = 'C';
207
+ if (typeRaw) {
208
+ const t = typeRaw.toUpperCase();
209
+ if (!FIELD_TYPES.has(t)) throw new ImsSyntax(`FIELD TYPE ${t} is not recognised`);
210
+ type = t;
211
+ }
212
+
213
+ const keywords = {};
214
+ for (const [k, v] of ops.keywords) keywords[k] = v;
215
+
216
+ return {
217
+ kind: 'FIELD',
218
+ name,
219
+ seq,
220
+ unique,
221
+ bytes,
222
+ start,
223
+ type,
224
+ keywords
225
+ };
226
+ }
227
+
228
+ function parseLCHILD(st, ops) {
229
+ const nameRaw = ops.keywords.get('NAME');
230
+ if (!nameRaw) throw new ImsSyntax('LCHILD requires NAME');
231
+
232
+ const items = sublist(nameRaw);
233
+ let name, dbd = null;
234
+ if (items) {
235
+ name = items[0];
236
+ if (items.length >= 2) dbd = items[1];
237
+ } else {
238
+ name = nameRaw;
239
+ }
240
+
241
+ const keywords = {};
242
+ for (const [k, v] of ops.keywords) keywords[k] = v;
243
+
244
+ return {
245
+ kind: 'LCHILD',
246
+ name,
247
+ dbd,
248
+ keywords
249
+ };
250
+ }
251
+
252
+ function parseXDFLD(st, ops) {
253
+ const keywords = {};
254
+ for (const [k, v] of ops.keywords) keywords[k] = v;
255
+
256
+ return {
257
+ kind: 'XDFLD',
258
+ keywords
259
+ };
260
+ }
261
+
262
+ function parseDBDGEN(st, ops) {
263
+ const keywords = {};
264
+ for (const [k, v] of ops.keywords) keywords[k] = v;
265
+
266
+ return {
267
+ kind: 'DBDGEN',
268
+ keywords
269
+ };
270
+ }
271
+
272
+ const KEYWORDS = {
273
+ DBD: ['NAME', 'ACCESS', 'RMNAME', 'PASSWD', 'VERSION', 'DATXEXIT', 'EXIT', 'PSNAME', 'GSROOT', 'ENCODING', 'FPINDEX'],
274
+ DATASET: ['DD1', 'DD2', 'OVFLW', 'DEVICE', 'BLOCK', 'SIZE', 'SCAN', 'FRSPC', 'RECORD', 'RECFM', 'MODEL', 'SEARCHA', 'REL'],
275
+ AREA: ['DD1', 'MODEL', 'SIZE', 'UOW', 'ROOT', 'REMOTE'],
276
+ SEGM: ['NAME', 'PARENT', 'BYTES', 'FREQ', 'POINTER', 'PTR', 'RULES', 'COMPRTN', 'EXIT', 'SOURCE', 'SSPTR', 'TYPE', 'DSGROUP'],
277
+ FIELD: ['NAME', 'BYTES', 'START', 'TYPE'],
278
+ LCHILD: ['NAME', 'POINTER', 'PTR', 'PAIR', 'RULES', 'INDEX', 'RKSIZE'],
279
+ XDFLD: ['NAME', 'SEGMENT', 'SRCH', 'SUBSEQ', 'DDATA', 'NULLVAL', 'CONST', 'EXTRTN'],
280
+ DBDGEN: [],
281
+ };
282
+ // Operands that end in a comma were meant to go on, but the next card was not read as their
283
+ // continuation (no column-72 mark), so the operands that follow never reached the parser.
284
+ const checked = (macro, parse) => (st, ops) => {
285
+ if (/,\s*$/.test(st.field)) throw new ImsSyntax(`${macro} operands end in a comma: the operands meant to follow were not read as a continuation`);
286
+ onlyKeywords(ops, macro, KEYWORDS[macro]);
287
+ return parse(st, ops);
288
+ };
289
+
290
+ export const parsers = Object.freeze({
291
+ DBD: checked('DBD', parseDBD),
292
+ DATASET: checked('DATASET', parseDATASET),
293
+ AREA: checked('AREA', parseAREA),
294
+ SEGM: checked('SEGM', parseSEGM),
295
+ FIELD: checked('FIELD', parseFIELD),
296
+ LCHILD: checked('LCHILD', parseLCHILD),
297
+ XDFLD: checked('XDFLD', parseXDFLD),
298
+ DBDGEN: checked('DBDGEN', parseDBDGEN)
299
+ });