@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.
- package/README.md +49 -18
- package/STABILITY.md +76 -0
- package/bin/cobolwork.mjs +30 -7
- package/lib/bms.mjs +21 -7
- package/lib/build.mjs +108 -53
- package/lib/capabilities.mjs +9 -10
- package/lib/cics-commands.mjs +501 -9
- package/lib/compliance.mjs +11 -0
- package/lib/consequence.mjs +13 -0
- package/lib/control-workers.mjs +161 -0
- package/lib/control.mjs +52 -3
- package/lib/dataflow.mjs +176 -125
- 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/evidence/cli.mjs +14 -3
- package/lib/evidence/record.mjs +1 -1
- package/lib/evidence/store.mjs +44 -31
- 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 +13 -1
- package/lib/hlasm/asm/sections.mjs +3 -1
- package/lib/hlasm/instr.mjs +25 -0
- package/lib/hlasm/macro/authorization.mjs +24 -4
- package/lib/hlasm/macro/datasets.mjs +30 -17
- package/lib/hlasm/macro/io.mjs +68 -45
- package/lib/hlasm/macro/le.mjs +2 -2
- package/lib/hlasm/macro/operator.mjs +32 -21
- package/lib/hlasm/macro/program.mjs +126 -84
- package/lib/hlasm/macro/recovery.mjs +10 -5
- package/lib/hlasm/macro/storage.mjs +64 -14
- package/lib/hlasm/macro/structured.mjs +1 -1
- package/lib/hlasm/model.mjs +35 -10
- package/lib/hlasm/mvs38.mjs +47 -0
- package/lib/hlasm/operands.mjs +10 -0
- package/lib/hlasm/optable.mjs +61 -0
- package/lib/hlasm/read.mjs +22 -8
- 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/ironwork.mjs +17 -13
- package/lib/kernel/pds-archive.mjs +256 -0
- package/lib/kernel/registry.mjs +14 -8
- package/lib/kernel/shared-pass.mjs +34 -9
- package/lib/kernel/source-tree.mjs +104 -36
- package/lib/kernel/version-key.mjs +17 -0
- package/lib/layout.mjs +26 -31
- 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/precompile-check.mjs +124 -0
- package/lib/precompile-cics.mjs +7 -3
- package/lib/reach.mjs +11 -2
- package/lib/revision.json +1 -1
- package/lib/sarif.mjs +41 -3
- package/lib/scan.mjs +7 -1
- package/lib/sets/abend.mjs +16 -6
- package/lib/sets/cics.mjs +15 -35
- package/lib/sets/compile.mjs +41 -15
- package/lib/sets/crypto.mjs +5 -3
- package/lib/sets/ddl.mjs +36 -0
- package/lib/sets/flow.mjs +18 -1
- package/lib/sets/hidden.mjs +5 -3
- package/lib/sets/hlasm.mjs +72 -12
- package/lib/sets/ims.mjs +139 -0
- package/lib/sets/log.mjs +6 -6
- package/lib/sets/opaque.mjs +27 -7
- package/lib/sets/pli.mjs +40 -0
- package/lib/sets/recon.mjs +5 -3
- package/lib/sets/secrets.mjs +5 -3
- package/lib/sets/semantics.mjs +3 -0
- package/lib/sets/web.mjs +36 -22
- 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 +432 -1
- package/rules/compliance-dora.json +414 -1
- package/rules/compliance-ffiec.json +414 -1
- package/rules/compliance-nist80053.json +466 -1
- 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.site.schema.json +116 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// Which kind each PL/I statement is, and its parse by that kind's parser. PL/I reserves no words, so
|
|
3
|
+
// a statement is an assignment whenever it reads as references followed by '=', and a keyword
|
|
4
|
+
// statement only otherwise: `IF = 1;` assigns to a variable named IF.
|
|
5
|
+
import { cursor, PliSyntax } from './cursor.mjs';
|
|
6
|
+
import { parsers as declare } from './stmt/declare.mjs';
|
|
7
|
+
import { parsers as procedure } from './stmt/procedure.mjs';
|
|
8
|
+
import { parsers as control } from './stmt/control.mjs';
|
|
9
|
+
import { parsers as call } from './stmt/call.mjs';
|
|
10
|
+
import { parsers as assignment } from './stmt/assignment.mjs';
|
|
11
|
+
import { parsers as io } from './stmt/io.mjs';
|
|
12
|
+
import { parsers as stream } from './stmt/stream.mjs';
|
|
13
|
+
import { parsers as conditions } from './stmt/conditions.mjs';
|
|
14
|
+
import { parsers as alloc } from './stmt/alloc.mjs';
|
|
15
|
+
import { parsers as exec } from './stmt/exec.mjs';
|
|
16
|
+
import { parsers as preprocessor } from './stmt/preprocessor.mjs';
|
|
17
|
+
import { parsers as misc } from './stmt/misc.mjs';
|
|
18
|
+
|
|
19
|
+
export const PARSERS = Object.freeze({ ...declare, ...procedure, ...control, ...call, ...assignment, ...io, ...stream, ...conditions, ...alloc, ...exec, ...preprocessor, ...misc });
|
|
20
|
+
|
|
21
|
+
const KEYWORDS = new Map(Object.entries({
|
|
22
|
+
DECLARE: 'DECLARE', DCL: 'DECLARE', DEFAULT: 'DEFAULT', DFT: 'DEFAULT', DEFINE: 'DEFINE',
|
|
23
|
+
PROCEDURE: 'PROCEDURE', PROC: 'PROCEDURE', ENTRY: 'ENTRY', PACKAGE: 'PACKAGE', BEGIN: 'BEGIN', END: 'END',
|
|
24
|
+
DO: 'DO', IF: 'IF', ELSE: 'ELSE', SELECT: 'SELECT', WHEN: 'WHEN', OTHERWISE: 'OTHERWISE', OTHER: 'OTHERWISE',
|
|
25
|
+
LEAVE: 'LEAVE', ITERATE: 'ITERATE', GO: 'GOTO', GOTO: 'GOTO', RETURN: 'RETURN', STOP: 'STOP', EXIT: 'EXIT',
|
|
26
|
+
CALL: 'CALL', FETCH: 'FETCH', RELEASE: 'RELEASE',
|
|
27
|
+
OPEN: 'OPEN', CLOSE: 'CLOSE', READ: 'READ', WRITE: 'WRITE', REWRITE: 'REWRITE', DELETE: 'DELETE', LOCATE: 'LOCATE',
|
|
28
|
+
UNLOCK: 'UNLOCK', GET: 'GET', PUT: 'PUT', DISPLAY: 'DISPLAY', FORMAT: 'FORMAT', FLUSH: 'FLUSH',
|
|
29
|
+
ON: 'ON', SIGNAL: 'SIGNAL', REVERT: 'REVERT', RESIGNAL: 'RESIGNAL',
|
|
30
|
+
ALLOCATE: 'ALLOCATE', ALLOC: 'ALLOCATE', FREE: 'FREE',
|
|
31
|
+
EXEC: 'EXEC', EXECUTE: 'EXEC',
|
|
32
|
+
WAIT: 'WAIT', DELAY: 'DELAY', ATTACH: 'ATTACH', DETACH: 'DETACH', ASSERT: 'ASSERT', QUALIFY: 'QUALIFY',
|
|
33
|
+
}));
|
|
34
|
+
|
|
35
|
+
const ASSIGN_OPS = new Set(['=', '+=', '-=', '*=', '/=', '|=', '&=', '||=', '**=']);
|
|
36
|
+
|
|
37
|
+
// Where a reference starting at `i` ends: name, then any mix of (subscripts), '.' name and
|
|
38
|
+
// '->' or '=>' name. -1 when `i` does not start one.
|
|
39
|
+
function referenceEnd(toks, i) {
|
|
40
|
+
if (!toks[i] || toks[i].t !== 'word') return -1;
|
|
41
|
+
let k = i + 1;
|
|
42
|
+
for (;;) {
|
|
43
|
+
const t = toks[k];
|
|
44
|
+
if (t && t.t === 'op' && t.v === '(') {
|
|
45
|
+
let depth = 0;
|
|
46
|
+
for (; k < toks.length; k++) {
|
|
47
|
+
if (toks[k].t !== 'op') continue;
|
|
48
|
+
if (toks[k].v === '(') depth++;
|
|
49
|
+
else if (toks[k].v === ')' && --depth === 0) break;
|
|
50
|
+
}
|
|
51
|
+
if (k >= toks.length) return -1;
|
|
52
|
+
k++;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (t && t.t === 'op' && (t.v === '.' || t.v === '->' || t.v === '=>') && toks[k + 1] && toks[k + 1].t === 'word') { k += 2; continue; }
|
|
56
|
+
return k;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function isAssignment(toks) {
|
|
61
|
+
let i = 0;
|
|
62
|
+
for (;;) {
|
|
63
|
+
const end = referenceEnd(toks, i);
|
|
64
|
+
if (end < 0) return false;
|
|
65
|
+
const t = toks[end];
|
|
66
|
+
if (t && t.t === 'op' && ASSIGN_OPS.has(t.v)) return true;
|
|
67
|
+
if (t && t.t === 'op' && t.v === ',') { i = end + 1; continue; }
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function thenFollows(toks) {
|
|
73
|
+
let depth = 0;
|
|
74
|
+
for (const t of toks.slice(1)) {
|
|
75
|
+
if (t.t === 'op' && t.v === '(') depth++;
|
|
76
|
+
else if (t.t === 'op' && t.v === ')') depth--;
|
|
77
|
+
else if (depth === 0 && t.t === 'word' && t.u === 'THEN') return true;
|
|
78
|
+
}
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function classify(stmt) {
|
|
83
|
+
const toks = stmt.toks;
|
|
84
|
+
if (!toks.length) return 'NULL';
|
|
85
|
+
if (toks[0].t === 'op' && toks[0].v === '%') return 'PREPROCESSOR';
|
|
86
|
+
// PL/I reserves no words, so IF (X) = 0 could assign to an array IF; a THEN at depth 0 makes it
|
|
87
|
+
// the IF statement, as the compiler reads it.
|
|
88
|
+
if (toks[0].t === 'word' && toks[0].u === 'IF' && thenFollows(toks)) return 'IF';
|
|
89
|
+
if (isAssignment(toks)) return 'ASSIGNMENT';
|
|
90
|
+
// An %INCLUDE member holding the middle of a structure starts at a level number.
|
|
91
|
+
if (toks[0].t === 'num' && /^\d+$/.test(toks[0].v) && toks[1] && toks[1].t === 'word') return 'DECLARE_FRAGMENT';
|
|
92
|
+
if (toks[0].t !== 'word') return 'UNKNOWN';
|
|
93
|
+
return KEYWORDS.get(toks[0].u) || 'UNKNOWN';
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// A statement built from tokens inside another, such as the unit of an IF, ON or WHEN, with its own
|
|
97
|
+
// prefixes taken off as the splitter takes them off a statement of its own.
|
|
98
|
+
export function subStatement(parent, toks) {
|
|
99
|
+
const labels = [];
|
|
100
|
+
let i = 0;
|
|
101
|
+
while (toks[i] && toks[i].t === 'word' && toks[i + 1] && toks[i + 1].t === 'op' && toks[i + 1].v === ':') { labels.push({ name: toks[i].u, line: toks[i].line }); i += 2; }
|
|
102
|
+
const first = toks[i] || toks[0];
|
|
103
|
+
return { labels, conditions: [], toks: toks.slice(i), line: first ? first.line : parent.line, endLine: parent.endLine, ...(parent.file ? { file: parent.file } : {}) };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const RANK = { parsed: 0, unbuilt: 1, unparsed: 2, unknown: 3 };
|
|
107
|
+
|
|
108
|
+
// { kind, status, node?, reason? }. status is parsed (every token accounted for, and every nested
|
|
109
|
+
// unit parsed), unbuilt (the kind has no parser yet), unparsed (the parser refused it or left tokens)
|
|
110
|
+
// or unknown (no kind fits). A parser receives a cursor on the statement's tokens after its prefixes
|
|
111
|
+
// and returns a node; a nested statement it parses through ctx.parseStatement goes in node.units.
|
|
112
|
+
export function parseStatement(stmt) {
|
|
113
|
+
const kind = classify(stmt);
|
|
114
|
+
if (kind === 'NULL') return { kind, status: 'parsed', node: { kind } };
|
|
115
|
+
if (kind === 'UNKNOWN') return { kind, status: 'unknown', reason: 'no statement kind fits' };
|
|
116
|
+
const parser = PARSERS[kind];
|
|
117
|
+
if (!parser) return { kind, status: 'unbuilt' };
|
|
118
|
+
const c = cursor(stmt.toks);
|
|
119
|
+
let node;
|
|
120
|
+
try {
|
|
121
|
+
node = parser(c, stmt, { parseStatement, subStatement });
|
|
122
|
+
} catch (e) {
|
|
123
|
+
if (e instanceof PliSyntax) return { kind, status: 'unparsed', reason: e.message, line: e.line ?? stmt.line };
|
|
124
|
+
return { kind, status: 'unparsed', reason: `parser error: ${e && e.message}`, crash: true, line: stmt.line };
|
|
125
|
+
}
|
|
126
|
+
if (!c.done()) return { kind, status: 'unparsed', reason: `tokens left at '${c.peek().v ?? c.peek().t}'`, line: c.peek().line, node };
|
|
127
|
+
let status = 'parsed';
|
|
128
|
+
for (const u of (node && node.units) || []) if (RANK[u.status] > RANK[status]) status = u.status;
|
|
129
|
+
return status === 'parsed' ? { kind, status, node } : { kind, status, node, reason: 'a nested statement is not parsed' };
|
|
130
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// PL/I ALLOCATE and FREE: each names one or more controlled or based variables, an ALLOCATE with
|
|
3
|
+
// the area it takes storage IN, the locator it SETs and any attributes it gives a controlled one.
|
|
4
|
+
|
|
5
|
+
import { parseReference } from '../expr.mjs';
|
|
6
|
+
|
|
7
|
+
const ALLOCATE_ATTRIBUTES = new Set(['CHARACTER', 'CHAR', 'BIT', 'GRAPHIC', 'WIDECHAR', 'AREA', 'INITIAL', 'INIT', 'DIMENSION', 'DIM']);
|
|
8
|
+
|
|
9
|
+
function readParenthesised(c, read) {
|
|
10
|
+
c.expectOp('(');
|
|
11
|
+
const value = read(c);
|
|
12
|
+
c.expectOp(')');
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function readAllocateItem(c) {
|
|
17
|
+
const level = c.peek() && c.peek().t === 'num' ? Number(c.next().v) : null;
|
|
18
|
+
const item = { level, variable: parseReference(c), in: null, set: null, attributes: [] };
|
|
19
|
+
while (!c.done() && !c.isOp(',')) {
|
|
20
|
+
if (c.isWord('IN')) { c.next(); item.in = readParenthesised(c, parseReference); }
|
|
21
|
+
else if (c.isWord('SET')) { c.next(); item.set = readParenthesised(c, parseReference); }
|
|
22
|
+
else if (c.isWord([...ALLOCATE_ATTRIBUTES])) {
|
|
23
|
+
const name = c.next().u;
|
|
24
|
+
item.attributes.push({ name, args: c.isOp('(') ? c.group() : null });
|
|
25
|
+
} else c.fail('IN, SET, an attribute or a comma');
|
|
26
|
+
}
|
|
27
|
+
return item;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function readFreeItem(c) {
|
|
31
|
+
const item = { variable: parseReference(c), in: null };
|
|
32
|
+
if (c.isWord('IN')) { c.next(); item.in = readParenthesised(c, parseReference); }
|
|
33
|
+
return item;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function readList(c, read) {
|
|
37
|
+
const items = [read(c)];
|
|
38
|
+
while (c.isOp(',')) { c.next(); items.push(read(c)); }
|
|
39
|
+
return items;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const parsers = {
|
|
43
|
+
ALLOCATE: (c) => { c.expectWord(['ALLOCATE', 'ALLOC']); return { kind: 'ALLOCATE', items: readList(c, readAllocateItem) }; },
|
|
44
|
+
FREE: (c) => { c.expectWord('FREE'); return { kind: 'FREE', items: readList(c, readFreeItem) }; },
|
|
45
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// PL/I assignment statements.
|
|
3
|
+
|
|
4
|
+
import { parseExpression, parseReference } from '../expr.mjs';
|
|
5
|
+
|
|
6
|
+
const ASSIGN_OPS = new Set(['=', '+=', '-=', '*=', '/=', '|=', '&=', '||=', '**=']);
|
|
7
|
+
|
|
8
|
+
function parseAssignment(c, stmt, ctx) {
|
|
9
|
+
const targets = [];
|
|
10
|
+
for (;;) {
|
|
11
|
+
const ref = parseReference(c);
|
|
12
|
+
targets.push(ref);
|
|
13
|
+
if (!c.isOp(',')) break;
|
|
14
|
+
c.next();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const opTok = c.peek();
|
|
18
|
+
if (!opTok || opTok.t !== 'op' || !ASSIGN_OPS.has(opTok.v)) {
|
|
19
|
+
c.fail('an assignment operator');
|
|
20
|
+
}
|
|
21
|
+
const op = c.next().v;
|
|
22
|
+
|
|
23
|
+
const value = parseExpression(c);
|
|
24
|
+
|
|
25
|
+
let byName = false;
|
|
26
|
+
if (c.isOp(',')) {
|
|
27
|
+
c.next();
|
|
28
|
+
if (c.isWord('BY')) {
|
|
29
|
+
c.next();
|
|
30
|
+
if (c.isWord('NAME')) {
|
|
31
|
+
c.next();
|
|
32
|
+
byName = true;
|
|
33
|
+
} else {
|
|
34
|
+
c.fail("'NAME'");
|
|
35
|
+
}
|
|
36
|
+
} else {
|
|
37
|
+
c.fail("'BY'");
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!c.done()) {
|
|
42
|
+
c.fail('the end of the statement');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
kind: 'ASSIGNMENT',
|
|
47
|
+
targets,
|
|
48
|
+
op,
|
|
49
|
+
value,
|
|
50
|
+
byName,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export const parsers = {
|
|
55
|
+
ASSIGNMENT: parseAssignment,
|
|
56
|
+
};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// PL/I CALL, FETCH and RELEASE statements.
|
|
3
|
+
|
|
4
|
+
import { cursor } from '../cursor.mjs';
|
|
5
|
+
import { parseExpression, parseReference } from '../expr.mjs';
|
|
6
|
+
|
|
7
|
+
function parseCall(c, stmt, ctx) {
|
|
8
|
+
c.expectWord('CALL');
|
|
9
|
+
const callee = parseReference(c);
|
|
10
|
+
const args = callee.args.length ? callee.args[callee.args.length - 1] : [];
|
|
11
|
+
const options = [];
|
|
12
|
+
|
|
13
|
+
while (!c.done()) {
|
|
14
|
+
if (c.isWord('TASK')) {
|
|
15
|
+
c.next();
|
|
16
|
+
const inner = c.group();
|
|
17
|
+
const s = cursor(inner);
|
|
18
|
+
const e = parseExpression(s);
|
|
19
|
+
if (!s.done()) s.fail('the end of the argument');
|
|
20
|
+
options.push({ name: 'TASK', args: [e] });
|
|
21
|
+
} else if (c.isWord('EVENT')) {
|
|
22
|
+
c.next();
|
|
23
|
+
const inner = c.group();
|
|
24
|
+
const s = cursor(inner);
|
|
25
|
+
const e = parseExpression(s);
|
|
26
|
+
if (!s.done()) s.fail('the end of the argument');
|
|
27
|
+
options.push({ name: 'EVENT', args: [e] });
|
|
28
|
+
} else if (c.isWord('PRIORITY')) {
|
|
29
|
+
c.next();
|
|
30
|
+
const inner = c.group();
|
|
31
|
+
const s = cursor(inner);
|
|
32
|
+
const e = parseExpression(s);
|
|
33
|
+
if (!s.done()) s.fail('the end of the argument');
|
|
34
|
+
options.push({ name: 'PRIORITY', args: [e] });
|
|
35
|
+
} else {
|
|
36
|
+
c.fail('end of statement or TASK, EVENT, or PRIORITY');
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return { kind: 'CALL', callee, name: callee.path.join('.'), args, options };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function parseFetch(c, stmt, ctx) {
|
|
44
|
+
c.expectWord('FETCH');
|
|
45
|
+
const entries = [];
|
|
46
|
+
|
|
47
|
+
while (true) {
|
|
48
|
+
const name = c.expectWord(undefined).u;
|
|
49
|
+
let set = null;
|
|
50
|
+
let title = null;
|
|
51
|
+
|
|
52
|
+
// SET and TITLE may both appear, in either order.
|
|
53
|
+
while (c.isWord(['SET', 'TITLE']) && c.isOp('(', 1)) {
|
|
54
|
+
const option = c.next().u;
|
|
55
|
+
const s = cursor(c.group());
|
|
56
|
+
if (option === 'SET') set = parseReference(s);
|
|
57
|
+
else title = parseExpression(s);
|
|
58
|
+
if (!s.done()) s.fail('the end of the argument');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
entries.push({ name, set, title });
|
|
62
|
+
|
|
63
|
+
if (c.isOp(',')) {
|
|
64
|
+
c.next();
|
|
65
|
+
} else {
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (!c.done()) c.fail('end of statement');
|
|
71
|
+
|
|
72
|
+
return { kind: 'FETCH', entries };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function parseRelease(c, stmt, ctx) {
|
|
76
|
+
c.expectWord('RELEASE');
|
|
77
|
+
const entries = [];
|
|
78
|
+
|
|
79
|
+
if (c.isOp('*')) {
|
|
80
|
+
c.next();
|
|
81
|
+
entries.push('*');
|
|
82
|
+
} else {
|
|
83
|
+
while (true) {
|
|
84
|
+
const name = c.expectWord(undefined).u;
|
|
85
|
+
entries.push(name);
|
|
86
|
+
|
|
87
|
+
if (c.isOp(',')) {
|
|
88
|
+
c.next();
|
|
89
|
+
} else {
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (!c.done()) c.fail('end of statement');
|
|
96
|
+
|
|
97
|
+
return { kind: 'RELEASE', entries };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export const parsers = {
|
|
101
|
+
CALL: parseCall,
|
|
102
|
+
FETCH: parseFetch,
|
|
103
|
+
RELEASE: parseRelease,
|
|
104
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// Parsers for ON, SIGNAL, REVERT and RESIGNAL statements.
|
|
3
|
+
|
|
4
|
+
const CONDITION_NAMES = new Set([
|
|
5
|
+
'ENDFILE', 'ENDPAGE', 'KEY', 'RECORD', 'TRANSMIT', 'UNDEFINEDFILE', 'UNDF',
|
|
6
|
+
'NAME', 'CONDITION', 'COND',
|
|
7
|
+
'ERROR', 'FINISH', 'CONVERSION', 'CONV', 'FIXEDOVERFLOW', 'FOFL',
|
|
8
|
+
'OVERFLOW', 'OFL', 'UNDERFLOW', 'UFL', 'ZERODIVIDE', 'ZDIV',
|
|
9
|
+
'SIZE', 'STRINGRANGE', 'STRG', 'STRINGSIZE', 'STRZ',
|
|
10
|
+
'SUBSCRIPTRANGE', 'SUBRG', 'AREA', 'STORAGE', 'ATTENTION', 'ATTN',
|
|
11
|
+
'INVALIDOP', 'ANYCONDITION', 'ANYCOND'
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
function parseCondition(c) {
|
|
15
|
+
const nameTok = c.next();
|
|
16
|
+
if (!nameTok || nameTok.t !== 'word' || !CONDITION_NAMES.has(nameTok.u)) {
|
|
17
|
+
c.fail('a condition name');
|
|
18
|
+
}
|
|
19
|
+
const name = nameTok.u;
|
|
20
|
+
let arg = null;
|
|
21
|
+
if (c.isOp('(')) {
|
|
22
|
+
const inner = c.group();
|
|
23
|
+
arg = inner;
|
|
24
|
+
}
|
|
25
|
+
return { name, arg };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function parseConditions(c) {
|
|
29
|
+
const conditions = [];
|
|
30
|
+
const first = parseCondition(c);
|
|
31
|
+
conditions.push(first);
|
|
32
|
+
|
|
33
|
+
while (c.isOp(',')) {
|
|
34
|
+
c.next();
|
|
35
|
+
const next = parseCondition(c);
|
|
36
|
+
conditions.push(next);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return conditions;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function parseOn(c, stmt, ctx) {
|
|
43
|
+
c.expectWord('ON');
|
|
44
|
+
|
|
45
|
+
const conditions = parseConditions(c);
|
|
46
|
+
|
|
47
|
+
let snap = false;
|
|
48
|
+
if (c.isWord('SNAP')) {
|
|
49
|
+
c.next();
|
|
50
|
+
snap = true;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let system = false;
|
|
54
|
+
if (c.isWord('SYSTEM')) {
|
|
55
|
+
c.next();
|
|
56
|
+
system = true;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const units = [];
|
|
60
|
+
if (!system) {
|
|
61
|
+
const rest = c.drain();
|
|
62
|
+
if (rest.length > 0) {
|
|
63
|
+
const sub = ctx.subStatement(stmt, rest);
|
|
64
|
+
const result = ctx.parseStatement(sub);
|
|
65
|
+
units.push(result);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return { kind: 'ON', conditions, snap, system, units };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function parseSignal(c, stmt, ctx) {
|
|
73
|
+
c.expectWord('SIGNAL');
|
|
74
|
+
const condition = parseCondition(c);
|
|
75
|
+
return { kind: 'SIGNAL', condition, units: [] };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function parseRevert(c, stmt, ctx) {
|
|
79
|
+
c.expectWord('REVERT');
|
|
80
|
+
const condition = parseCondition(c);
|
|
81
|
+
return { kind: 'REVERT', condition, units: [] };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function parseResignal(c, stmt, ctx) {
|
|
85
|
+
c.expectWord('RESIGNAL');
|
|
86
|
+
return { kind: 'RESIGNAL', units: [] };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export const parsers = {
|
|
90
|
+
ON: parseOn,
|
|
91
|
+
SIGNAL: parseSignal,
|
|
92
|
+
REVERT: parseRevert,
|
|
93
|
+
RESIGNAL: parseResignal
|
|
94
|
+
};
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// PL/I DO, IF, ELSE, SELECT, WHEN, OTHERWISE, LEAVE, ITERATE, GO TO, RETURN, STOP and EXIT statements.
|
|
3
|
+
|
|
4
|
+
import { cursor } from '../cursor.mjs';
|
|
5
|
+
import { parseExpression, parseReference } from '../expr.mjs';
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
function parseUnit(c, stmt, ctx, node) {
|
|
9
|
+
const rest = c.drain();
|
|
10
|
+
if (!rest.length) {
|
|
11
|
+
node.units = [];
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
const sub = ctx.subStatement(stmt, rest);
|
|
15
|
+
node.units = [ctx.parseStatement(sub)];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function parseDo(c, stmt, ctx) {
|
|
19
|
+
c.expectWord('DO');
|
|
20
|
+
const node = { kind: 'DO', form: 'group', control: null, specs: [], while: null, until: null };
|
|
21
|
+
|
|
22
|
+
if (c.done()) return node;
|
|
23
|
+
|
|
24
|
+
// LOOP, FOREVER, WHILE and UNTIL are words only where a variable of that name could not be:
|
|
25
|
+
// followed by '=', each is the control variable of an iterative DO.
|
|
26
|
+
if (c.isWord('LOOP') && !c.isOp('=', 1)) {
|
|
27
|
+
c.next();
|
|
28
|
+
node.form = 'loop';
|
|
29
|
+
if (!c.done()) c.fail('end of DO LOOP');
|
|
30
|
+
return node;
|
|
31
|
+
}
|
|
32
|
+
if (c.isWord('FOREVER') && !c.isOp('=', 1)) {
|
|
33
|
+
c.next();
|
|
34
|
+
node.form = 'loop';
|
|
35
|
+
if (!c.done()) c.fail('end of DO FOREVER');
|
|
36
|
+
return node;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (c.isWord('WHILE') && !c.isOp('=', 1)) {
|
|
40
|
+
c.next();
|
|
41
|
+
node.form = 'while';
|
|
42
|
+
node.while = parseExpression(c, { stopWords: ['UNTIL'] });
|
|
43
|
+
if (c.isWord('UNTIL') && !c.isOp('=', 1)) {
|
|
44
|
+
c.next();
|
|
45
|
+
node.until = parseExpression(c);
|
|
46
|
+
}
|
|
47
|
+
if (!c.done()) c.fail('end of DO WHILE');
|
|
48
|
+
return node;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (c.isWord('UNTIL') && !c.isOp('=', 1)) {
|
|
52
|
+
c.next();
|
|
53
|
+
node.form = 'while';
|
|
54
|
+
node.until = parseExpression(c, { stopWords: ['WHILE'] });
|
|
55
|
+
if (c.isWord('WHILE')) {
|
|
56
|
+
c.next();
|
|
57
|
+
node.while = parseExpression(c);
|
|
58
|
+
}
|
|
59
|
+
if (!c.done()) c.fail('end of DO UNTIL');
|
|
60
|
+
return node;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
node.form = 'iterative';
|
|
64
|
+
node.control = parseReference(c);
|
|
65
|
+
c.expectOp('=');
|
|
66
|
+
|
|
67
|
+
while (!c.done()) {
|
|
68
|
+
const spec = { from: null, to: null, by: null, repeat: null, while: null, until: null };
|
|
69
|
+
node.specs.push(spec);
|
|
70
|
+
|
|
71
|
+
spec.from = parseExpression(c);
|
|
72
|
+
if (c.isWord('REPEAT')) {
|
|
73
|
+
c.next();
|
|
74
|
+
spec.repeat = parseExpression(c);
|
|
75
|
+
} else {
|
|
76
|
+
// TO and BY may come in either order, each at most once.
|
|
77
|
+
for (let k = 0; k < 2; k++) {
|
|
78
|
+
if (!spec.to && c.isWord('TO')) { c.next(); spec.to = parseExpression(c); }
|
|
79
|
+
else if (!spec.by && c.isWord('BY')) { c.next(); spec.by = parseExpression(c); }
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (c.isWord('WHILE')) {
|
|
84
|
+
c.next();
|
|
85
|
+
spec.while = parseExpression(c, { stopWords: ['UNTIL'] });
|
|
86
|
+
}
|
|
87
|
+
if (c.isWord('UNTIL') && !c.isOp('=', 1)) {
|
|
88
|
+
c.next();
|
|
89
|
+
spec.until = parseExpression(c);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (c.isOp(',')) {
|
|
93
|
+
c.next();
|
|
94
|
+
} else if (!c.done()) {
|
|
95
|
+
c.fail('end of DO iterative');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return node;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function parseIf(c, stmt, ctx) {
|
|
103
|
+
c.expectWord('IF');
|
|
104
|
+
const node = { kind: 'IF', cond: parseExpression(c, { stopWords: ['THEN'] }), units: [] };
|
|
105
|
+
c.expectWord('THEN');
|
|
106
|
+
parseUnit(c, stmt, ctx, node);
|
|
107
|
+
return node;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function parseElse(c, stmt, ctx) {
|
|
111
|
+
c.expectWord('ELSE');
|
|
112
|
+
const node = { kind: 'ELSE', units: [] };
|
|
113
|
+
parseUnit(c, stmt, ctx, node);
|
|
114
|
+
return node;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function parseSelect(c, stmt, ctx) {
|
|
118
|
+
c.expectWord('SELECT');
|
|
119
|
+
const node = { kind: 'SELECT', subject: null };
|
|
120
|
+
if (c.isOp('(')) {
|
|
121
|
+
const inner = c.group();
|
|
122
|
+
const s = cursor(inner);
|
|
123
|
+
node.subject = parseExpression(s);
|
|
124
|
+
if (!s.done()) s.fail('end of SELECT subject');
|
|
125
|
+
}
|
|
126
|
+
if (!c.done()) c.fail('end of SELECT');
|
|
127
|
+
return node;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function parseWhen(c, stmt, ctx) {
|
|
131
|
+
c.expectWord('WHEN');
|
|
132
|
+
const node = { kind: 'WHEN', values: [], units: [] };
|
|
133
|
+
if (c.isWord(['ANY', 'ALL'])) {
|
|
134
|
+
c.next();
|
|
135
|
+
}
|
|
136
|
+
const items = c.items();
|
|
137
|
+
for (const toks of items) {
|
|
138
|
+
const s = cursor(toks);
|
|
139
|
+
node.values.push(parseExpression(s));
|
|
140
|
+
if (!s.done()) s.fail('end of WHEN value');
|
|
141
|
+
}
|
|
142
|
+
parseUnit(c, stmt, ctx, node);
|
|
143
|
+
return node;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function parseOtherwise(c, stmt, ctx) {
|
|
147
|
+
c.word(['OTHERWISE', 'OTHER']);
|
|
148
|
+
const node = { kind: 'OTHERWISE', units: [] };
|
|
149
|
+
parseUnit(c, stmt, ctx, node);
|
|
150
|
+
return node;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function parseLeave(c, stmt, ctx) {
|
|
154
|
+
c.expectWord('LEAVE');
|
|
155
|
+
const node = { kind: 'LEAVE', label: null };
|
|
156
|
+
if (!c.done()) {
|
|
157
|
+
node.label = parseReference(c);
|
|
158
|
+
}
|
|
159
|
+
if (!c.done()) c.fail('end of LEAVE');
|
|
160
|
+
return node;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function parseIterate(c, stmt, ctx) {
|
|
164
|
+
c.expectWord('ITERATE');
|
|
165
|
+
const node = { kind: 'ITERATE', label: null };
|
|
166
|
+
if (!c.done()) {
|
|
167
|
+
node.label = parseReference(c);
|
|
168
|
+
}
|
|
169
|
+
if (!c.done()) c.fail('end of ITERATE');
|
|
170
|
+
return node;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function parseGoto(c, stmt, ctx) {
|
|
174
|
+
c.word(['GO', 'GOTO']);
|
|
175
|
+
if (c.isWord('TO')) c.next();
|
|
176
|
+
const node = { kind: 'GOTO', target: parseReference(c) };
|
|
177
|
+
if (!c.done()) c.fail('end of GO TO');
|
|
178
|
+
return node;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function parseReturn(c, stmt, ctx) {
|
|
182
|
+
c.expectWord('RETURN');
|
|
183
|
+
const node = { kind: 'RETURN', value: null };
|
|
184
|
+
if (c.isOp('(')) {
|
|
185
|
+
const inner = c.group();
|
|
186
|
+
const s = cursor(inner);
|
|
187
|
+
node.value = parseExpression(s);
|
|
188
|
+
if (!s.done()) s.fail('end of RETURN value');
|
|
189
|
+
}
|
|
190
|
+
if (!c.done()) c.fail('end of RETURN');
|
|
191
|
+
return node;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function parseStop(c, stmt, ctx) {
|
|
195
|
+
c.expectWord('STOP');
|
|
196
|
+
if (!c.done()) c.fail('end of STOP');
|
|
197
|
+
return { kind: 'STOP' };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function parseExit(c, stmt, ctx) {
|
|
201
|
+
c.expectWord('EXIT');
|
|
202
|
+
if (!c.done()) c.fail('end of EXIT');
|
|
203
|
+
return { kind: 'EXIT' };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export const parsers = {
|
|
207
|
+
DO: parseDo,
|
|
208
|
+
IF: parseIf,
|
|
209
|
+
ELSE: parseElse,
|
|
210
|
+
SELECT: parseSelect,
|
|
211
|
+
WHEN: parseWhen,
|
|
212
|
+
OTHERWISE: parseOtherwise,
|
|
213
|
+
LEAVE: parseLeave,
|
|
214
|
+
ITERATE: parseIterate,
|
|
215
|
+
GOTO: parseGoto,
|
|
216
|
+
RETURN: parseReturn,
|
|
217
|
+
STOP: parseStop,
|
|
218
|
+
EXIT: parseExit,
|
|
219
|
+
};
|