@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,130 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// HLASM source read statement by statement: the cards fold as the assembler folds them (lib/bms.mjs),
|
|
3
|
+
// and each statement goes to the parser for what its operation is. A machine instruction is matched
|
|
4
|
+
// against IBM's operand syntax, an assembler instruction and a system macro each have their own
|
|
5
|
+
// parser, and any other macro call is split into its operands. Conditional assembly and macro
|
|
6
|
+
// definitions are counted and not evaluated: nothing here expands a macro.
|
|
7
|
+
import { foldStatements } from '../bms.mjs';
|
|
8
|
+
import { HlasmSyntax, NAME, macroOperands } from './operands.mjs';
|
|
9
|
+
import { INSTRUCTIONS, parseInstruction, formatFamily } from './instr.mjs';
|
|
10
|
+
import { inOptable } from './optable.mjs';
|
|
11
|
+
import { parsers as data } from './asm/data.mjs';
|
|
12
|
+
import { parsers as sections } from './asm/sections.mjs';
|
|
13
|
+
import { parsers as symbols } from './asm/symbols.mjs';
|
|
14
|
+
import { parsers as listing } from './asm/listing.mjs';
|
|
15
|
+
import { parsers as output } from './asm/output.mjs';
|
|
16
|
+
import { parsers as program } from './macro/program.mjs';
|
|
17
|
+
import { parsers as linkage } from './macro/linkage.mjs';
|
|
18
|
+
import { parsers as storage } from './macro/storage.mjs';
|
|
19
|
+
import { parsers as authorization } from './macro/authorization.mjs';
|
|
20
|
+
import { parsers as operator } from './macro/operator.mjs';
|
|
21
|
+
import { parsers as datasets } from './macro/datasets.mjs';
|
|
22
|
+
import { parsers as io } from './macro/io.mjs';
|
|
23
|
+
import { parsers as recovery } from './macro/recovery.mjs';
|
|
24
|
+
import { parsers as le } from './macro/le.mjs';
|
|
25
|
+
import { parsers as structured } from './macro/structured.mjs';
|
|
26
|
+
import { parsers as exec } from './exec.mjs';
|
|
27
|
+
import { parsers as model } from './model.mjs';
|
|
28
|
+
|
|
29
|
+
// The assembler instructions of HLASM V1R6 (Language Reference SC26-4940-09, chapters 5 and 9).
|
|
30
|
+
export const ASSEMBLER = new Set(['ACONTROL', 'ADATA', 'AINSERT', 'ALIAS', 'AMODE', 'CATTR', 'CCW', 'CCW0', 'CCW1',
|
|
31
|
+
'CEJECT', 'CNOP', 'COM', 'COPY', 'CSECT', 'CXD', 'DC', 'DROP', 'DS', 'DSECT', 'DXD', 'EJECT', 'END', 'ENTRY', 'EQU',
|
|
32
|
+
'EXITCTL', 'EXTRN', 'ICTL', 'ISEQ', 'LOCTR', 'LTORG', 'MNOTE', 'OPSYN', 'ORG', 'POP', 'PRINT', 'PUNCH', 'PUSH',
|
|
33
|
+
'REPRO', 'RMODE', 'RSECT', 'SPACE', 'START', 'TITLE', 'USING', 'WXTRN', 'XATTR']);
|
|
34
|
+
export const CONDITIONAL = new Set(['ACTR', 'AEJECT', 'AGO', 'AGOB', 'AIF', 'AIFB', 'ANOP', 'AREAD', 'ASPACE',
|
|
35
|
+
'GBLA', 'GBLB', 'GBLC', 'LCLA', 'LCLB', 'LCLC', 'MEXIT', 'MHELP', 'SETA', 'SETAF', 'SETB', 'SETC', 'SETCF']);
|
|
36
|
+
// The z/OS macros the rules read, each parsed against the keywords IBM lists for it.
|
|
37
|
+
export const SYSTEM_MACROS = new Set(['LINK', 'XCTL', 'LOAD', 'ATTACH', 'CALL', 'SAVE', 'RETURN', 'MODESET',
|
|
38
|
+
'STORAGE', 'GETMAIN', 'FREEMAIN', 'RACROUTE', 'TESTAUTH', 'WTO', 'WTOR', 'OPEN', 'CLOSE', 'DCB', 'GET', 'PUT',
|
|
39
|
+
'READ', 'WRITE', 'ESTAE', 'ESPIE', 'ABEND', 'SNAP']);
|
|
40
|
+
// Language Environment's prolog, epilog and mapping macros, and the HLASM Toolkit's structured
|
|
41
|
+
// programming macros, each with its own parser.
|
|
42
|
+
export const LE_MACROS = new Set(['CEEENTRY', 'CEETERM', 'CEEPPA', 'CEECAA', 'CEEDSA']);
|
|
43
|
+
export const TOOLKIT_MACROS = new Set(['IF', 'ELSE', 'ELSEIF', 'ENDIF', 'DO', 'ENDDO', 'DOEXIT', 'ASMLEAVE', 'ITERATE',
|
|
44
|
+
'SELECT', 'WHEN', 'NEXTWHEN', 'OTHRWISE', 'ENDSEL', 'STRTSRCH', 'EXITIF', 'ORELSE', 'ENDLOOP', 'ENDSRCH',
|
|
45
|
+
'CASENTRY', 'CASE', 'ENDCASE', 'ASMMREL']);
|
|
46
|
+
export const NAMED_MACROS = new Set([...SYSTEM_MACROS, ...LE_MACROS, ...TOOLKIT_MACROS]);
|
|
47
|
+
|
|
48
|
+
const NO_OPERANDS = new Set(['CSECT', 'DSECT', 'RSECT', 'COM', 'LOCTR', 'LTORG', 'EJECT', 'REPRO', 'CXD']);
|
|
49
|
+
|
|
50
|
+
export const PARSERS = Object.freeze({ ...data, ...sections, ...symbols, ...listing, ...output });
|
|
51
|
+
export const MACRO_PARSERS = Object.freeze({ ...program, ...linkage, ...storage, ...authorization, ...operator, ...datasets, ...io, ...recovery, ...le, ...structured });
|
|
52
|
+
|
|
53
|
+
// Statements other than comments, each marked inMacro when it is part of a macro definition, the
|
|
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).
|
|
57
|
+
// EXEC CICS and EXEC SQL are written for their translators, with blanks between the words: their
|
|
58
|
+
// field is the cards' text after EXEC, columns 16 to 71 of each continuation, not the operands up
|
|
59
|
+
// to the first blank as an assembler statement's are.
|
|
60
|
+
function execField(phys, st) {
|
|
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();
|
|
62
|
+
}
|
|
63
|
+
|
|
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 });
|
|
69
|
+
const phys = String(text).replace(/\r\n?/g, '\n').split('\n');
|
|
70
|
+
const out = [];
|
|
71
|
+
let depth = 0;
|
|
72
|
+
let prototypeNext = false;
|
|
73
|
+
const defined = new Set();
|
|
74
|
+
for (const s of statements) {
|
|
75
|
+
if (s.kind === 'comment' || s.kind === 'not-assembler') continue;
|
|
76
|
+
const st = { ...s, inMacro: depth > 0, prototype: false };
|
|
77
|
+
if (s.kind === 'statement' && s.operation === 'EXEC') st.field = execField(phys, s);
|
|
78
|
+
if (s.kind === 'statement') {
|
|
79
|
+
if (s.operation === 'MACRO') { depth++; prototypeNext = true; st.inMacro = true; }
|
|
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;
|
|
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();
|
|
86
|
+
}
|
|
87
|
+
out.push(st);
|
|
88
|
+
}
|
|
89
|
+
return { statements: out, diags };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function run(kind, parser, st, ...args) {
|
|
93
|
+
if (!parser) return { kind, status: 'unbuilt' };
|
|
94
|
+
try {
|
|
95
|
+
return { kind, status: 'parsed', node: parser(st, ...args) };
|
|
96
|
+
} catch (e) {
|
|
97
|
+
if (e instanceof HlasmSyntax) return { kind, status: 'unparsed', reason: e.message };
|
|
98
|
+
return { kind, status: 'unparsed', reason: `parser error: ${e && e.message}`, crash: true };
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// { kind, status, node?, reason?, crash? }: parsed, unbuilt (no parser for the kind yet), unparsed
|
|
103
|
+
// (the parser refused it) or unknown (not an operation HLASM has).
|
|
104
|
+
export function parseHlasmStatement(st, opts = {}) {
|
|
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' };
|
|
108
|
+
const op = st.operation;
|
|
109
|
+
if (op === 'MACRO' || op === 'MEND') return { kind: 'MACRO DEFINITION', status: 'parsed', node: { kind: op } };
|
|
110
|
+
if (CONDITIONAL.has(op)) return { kind: 'CONDITIONAL', status: 'unbuilt' };
|
|
111
|
+
if (st.prototype) return run('PROTOTYPE', model.PROTOTYPE, st);
|
|
112
|
+
if (st.inMacro) return run('MODEL', model.MODEL, st);
|
|
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);
|
|
115
|
+
// A lone comma is the convention for no operands with remarks after it (CSECT , remark); an
|
|
116
|
+
// instruction that takes no operands reads whatever follows it as remarks.
|
|
117
|
+
if (ASSEMBLER.has(op)) return run(op, PARSERS[op], st.field === ',' || NO_OPERANDS.has(op) ? { ...st, field: '' } : st);
|
|
118
|
+
const row = INSTRUCTIONS.get(op);
|
|
119
|
+
if (row && inOptable(op, opts.optable)) return run(formatFamily(row.format), parseInstruction, st, row);
|
|
120
|
+
if (op === 'EXEC') {
|
|
121
|
+
const kind = `EXEC ${(/^\S+/.exec(st.field || '') || ['?'])[0].toUpperCase()}`;
|
|
122
|
+
return run(kind, exec[kind], st);
|
|
123
|
+
}
|
|
124
|
+
if (!NAME.test(op)) return { kind: 'UNKNOWN', status: 'unknown', reason: `${op} is not an operation name` };
|
|
125
|
+
if (NAMED_MACROS.has(op)) {
|
|
126
|
+
const parser = MACRO_PARSERS[op];
|
|
127
|
+
return run(op, parser && ((s) => parser(s, macroOperands(s.field === ',' ? '' : s.field || ''), opts)), st);
|
|
128
|
+
}
|
|
129
|
+
return run('MACRO CALL', (s) => ({ kind: 'MACRO CALL', macro: op, ...macroOperands(s.field || '') }), st);
|
|
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,
|
|
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
|
|
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')
|
|
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;
|
package/lib/ims/dli.mjs
ADDED
|
@@ -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
|
+
});
|