@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,149 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// PL/I DECLARE statements.
|
|
3
|
+
import { cursor, split, PliSyntax } from '../cursor.mjs';
|
|
4
|
+
import { parseExpression, parseReference } from '../expr.mjs';
|
|
5
|
+
|
|
6
|
+
const ABBREV = new Map([
|
|
7
|
+
['CHAR', 'CHARACTER'], ['BIN', 'BINARY'], ['DEC', 'DECIMAL'], ['PIC', 'PICTURE'],
|
|
8
|
+
['PTR', 'POINTER'], ['VAR', 'VARYING'], ['VARZ', 'VARYINGZ'], ['NONVAR', 'NONVARYING'],
|
|
9
|
+
['INIT', 'INITIAL'], ['EXT', 'EXTERNAL'], ['INT', 'INTERNAL'], ['AUTO', 'AUTOMATIC'],
|
|
10
|
+
['CTL', 'CONTROLLED'], ['DEF', 'DEFINED'], ['POS', 'POSITION'], ['ENV', 'ENVIRONMENT'],
|
|
11
|
+
['WCHAR', 'WIDECHAR'], ['COND', 'CONDITION'], ['PARM', 'PARAMETER'],
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
const KNOWN = new Set([
|
|
15
|
+
'CHARACTER', 'BIT', 'GRAPHIC', 'WIDECHAR', 'VARYING', 'VARYINGZ', 'NONVARYING',
|
|
16
|
+
'FIXED', 'FLOAT', 'BINARY', 'DECIMAL', 'PRECISION', 'SIGNED', 'UNSIGNED', 'REAL',
|
|
17
|
+
'COMPLEX', 'PICTURE', 'POINTER', 'OFFSET', 'HANDLE', 'AREA', 'ENTRY', 'RETURNS',
|
|
18
|
+
'OPTIONS', 'FILE', 'STREAM', 'RECORD', 'INPUT', 'OUTPUT', 'UPDATE', 'SEQUENTIAL',
|
|
19
|
+
'DIRECT', 'KEYED', 'ENVIRONMENT', 'PRINT', 'LABEL', 'FORMAT', 'CONDITION', 'BUILTIN',
|
|
20
|
+
'GENERIC', 'EXTERNAL', 'INTERNAL', 'STATIC', 'AUTOMATIC', 'BASED', 'CONTROLLED',
|
|
21
|
+
'DEFINED', 'POSITION', 'PARAMETER', 'ALIGNED', 'UNALIGNED', 'INITIAL', 'VALUE',
|
|
22
|
+
'LIKE', 'UNION', 'CONNECTED', 'NONCONNECTED', 'ASSIGNABLE', 'NONASSIGNABLE',
|
|
23
|
+
'NORMAL', 'ABNORMAL', 'BYADDR', 'BYVALUE', 'OPTIONAL', 'ORDINAL', 'TYPE', 'DIMACROSS',
|
|
24
|
+
'BIGENDIAN', 'LITTLEENDIAN', 'HEXADEC', 'IEEE', 'NATIVE', 'NONNATIVE', 'LIMITED',
|
|
25
|
+
'VARIABLE', 'IRREDUCIBLE', 'REDUCIBLE', 'RESERVED', 'INONLY', 'OUTONLY', 'INOUT',
|
|
26
|
+
'TASK', 'EVENT', 'BACKWARDS', 'BUFFERED', 'UNBUFFERED', 'TRANSIENT', 'EXCLUSIVE',
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
function expandAttr(u) {
|
|
30
|
+
return ABBREV.get(u) || u;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function parseAttribute(c, unknown) {
|
|
34
|
+
const w = c.peek();
|
|
35
|
+
if (!w || w.t !== 'word') c.fail('an attribute');
|
|
36
|
+
c.next();
|
|
37
|
+
const name = expandAttr(w.u);
|
|
38
|
+
let args = null;
|
|
39
|
+
let picture;
|
|
40
|
+
if (name === 'PICTURE' && c.peek() && c.peek().t === 'lit') return { name, args, picture: c.next().v };
|
|
41
|
+
if (c.isOp('(')) {
|
|
42
|
+
const inner = c.group();
|
|
43
|
+
if (name === 'PICTURE' && inner.length === 1 && inner[0].t === 'lit') {
|
|
44
|
+
picture = inner[0].v;
|
|
45
|
+
} else {
|
|
46
|
+
args = inner;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (!KNOWN.has(name)) unknown.push(name);
|
|
50
|
+
return { name, args, picture };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function parseDims(c) {
|
|
54
|
+
const dims = [];
|
|
55
|
+
while (c.isOp('(')) {
|
|
56
|
+
dims.push(...c.items());
|
|
57
|
+
}
|
|
58
|
+
return dims;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function parseItem(c, unknown, line) {
|
|
62
|
+
let level = null;
|
|
63
|
+
if (c.peek() && c.peek().t === 'num' && /^\d+$/.test(c.peek().v)) {
|
|
64
|
+
level = parseInt(c.next().v, 10);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
let name = null;
|
|
68
|
+
let factored = null;
|
|
69
|
+
let dims = [];
|
|
70
|
+
|
|
71
|
+
if (c.isOp('(')) {
|
|
72
|
+
const inner = c.group();
|
|
73
|
+
const parts = split(inner, ',');
|
|
74
|
+
factored = [];
|
|
75
|
+
for (const p of parts) {
|
|
76
|
+
const sc = cursor(p);
|
|
77
|
+
const sub = parseItem(sc, unknown, line);
|
|
78
|
+
if (!sc.done()) sc.fail('end of factored member');
|
|
79
|
+
factored.push(sub);
|
|
80
|
+
}
|
|
81
|
+
} else if (c.isOp('*')) {
|
|
82
|
+
c.next();
|
|
83
|
+
name = '*';
|
|
84
|
+
} else {
|
|
85
|
+
const w = c.peek();
|
|
86
|
+
if (!w || w.t !== 'word') c.fail('a name');
|
|
87
|
+
c.next();
|
|
88
|
+
name = w.u;
|
|
89
|
+
dims = parseDims(c);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const attributes = [];
|
|
93
|
+
while (!c.done()) {
|
|
94
|
+
const t = c.peek();
|
|
95
|
+
if (t.t === 'word') {
|
|
96
|
+
attributes.push(parseAttribute(c, unknown));
|
|
97
|
+
} else {
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return { level, name, factored, dims, attributes, line };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// A factored list gives one item per member, each with its own attributes and the shared ones.
|
|
106
|
+
function flatten(item, names) {
|
|
107
|
+
if (!item.factored) return [{ ...item, factored: names }];
|
|
108
|
+
const all = item.factored.flatMap((m) => (m.factored ? m.factored.map((x) => x.name) : [m.name]));
|
|
109
|
+
return item.factored.flatMap((m) => flatten({ ...m, level: m.level ?? item.level, attributes: [...m.attributes, ...item.attributes] }, all));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const isInclude = (p) => p.length === 3 && p[0].t === 'op' && p[0].v === '%' && p[1].t === 'word' && p[1].u === 'INCLUDE' && p[2].t === 'word';
|
|
113
|
+
|
|
114
|
+
// A %INCLUDE where an item belongs supplies the rest of the declaration from a member, which an
|
|
115
|
+
// unexpanded read records by name; a member that is itself the middle of a structure ends at the
|
|
116
|
+
// comma its includer continues from.
|
|
117
|
+
function parseItems(c, unknown, line, { fragment = false } = {}) {
|
|
118
|
+
const items = [];
|
|
119
|
+
const includes = [];
|
|
120
|
+
const parts = split(c.drain(), ',');
|
|
121
|
+
parts.forEach((p, k) => {
|
|
122
|
+
const last = k === parts.length - 1;
|
|
123
|
+
if (isInclude(p) && last) { includes.push(p[2].u); return; }
|
|
124
|
+
if (!p.length && last && fragment && k > 0) return;
|
|
125
|
+
const sc = cursor(p);
|
|
126
|
+
const item = parseItem(sc, unknown, line);
|
|
127
|
+
if (!sc.done()) sc.fail('end of item');
|
|
128
|
+
items.push(...flatten(item, null));
|
|
129
|
+
});
|
|
130
|
+
return { items, includes };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function parseDeclare(c, stmt, ctx) {
|
|
134
|
+
c.word(['DECLARE', 'DCL']);
|
|
135
|
+
const unknown = [];
|
|
136
|
+
const { items, includes } = parseItems(c, unknown, stmt.line);
|
|
137
|
+
return { kind: 'DECLARE', items, includes, unknownAttributes: unknown };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function parseDeclareFragment(c, stmt, ctx) {
|
|
141
|
+
const unknown = [];
|
|
142
|
+
const { items, includes } = parseItems(c, unknown, stmt.line, { fragment: true });
|
|
143
|
+
return { kind: 'DECLARE_FRAGMENT', items, includes, unknownAttributes: unknown };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export const parsers = {
|
|
147
|
+
DECLARE: parseDeclare,
|
|
148
|
+
DECLARE_FRAGMENT: parseDeclareFragment,
|
|
149
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// PL/I EXEC SQL, EXEC CICS and EXEC DLI statements.
|
|
3
|
+
import { hostVariablesIn } from '../../embedded-sql.mjs';
|
|
4
|
+
import { CICS_COMMANDS, CICS_EVERY_COMMAND } from '../../cics-commands.mjs';
|
|
5
|
+
import { cicsCommand } from '../../precompile-cics.mjs';
|
|
6
|
+
|
|
7
|
+
function toSqlTok(t) {
|
|
8
|
+
if (t.t === 'word') return { t: 'word', u: t.u };
|
|
9
|
+
if (t.t === 'op' && t.v === ':') return { t: 'op', v: ':' };
|
|
10
|
+
if (t.t === 'op' && t.v === '.') return { t: 'period', joined: true };
|
|
11
|
+
if (t.t === 'lit') return { t: 'lit', v: t.v };
|
|
12
|
+
if (t.t === 'num') return { t: 'word', u: t.v };
|
|
13
|
+
return { t: 'word', u: t.v };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function parseSql(c) {
|
|
17
|
+
const rest = c.drain();
|
|
18
|
+
const sqlToks = rest.map(toSqlTok);
|
|
19
|
+
const verb = sqlToks.find((t) => t.t === 'word')?.u;
|
|
20
|
+
const hostVars = hostVariablesIn(sqlToks).map((h) => ({ host: h.host, written: h.written }));
|
|
21
|
+
return { verb, hostVars };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// The options of an EXEC CICS command as words with their argument tokens, the command resolved by
|
|
25
|
+
// the same table the COBOL translator uses, and each option's direction from it.
|
|
26
|
+
function parseCics(c) {
|
|
27
|
+
const options = [];
|
|
28
|
+
while (!c.done()) {
|
|
29
|
+
const w = c.expectWord();
|
|
30
|
+
options.push({ name: w.u, args: c.isOp('(') ? c.group() : null });
|
|
31
|
+
}
|
|
32
|
+
const name = cicsCommand(options.map((o) => ({ word: o.name })));
|
|
33
|
+
const table = name ? CICS_COMMANDS[name].options : {};
|
|
34
|
+
for (const o of options.slice(1)) o.direction = table[o.name] || CICS_EVERY_COMMAND[o.name] || null;
|
|
35
|
+
return { command: name ? name.split(' ') : options.slice(0, 1).map((o) => o.name), known: !!name, options: options.slice(1) };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const parsers = {
|
|
39
|
+
EXEC(c, stmt, ctx) {
|
|
40
|
+
c.expectWord('EXEC');
|
|
41
|
+
const procTok = c.peek();
|
|
42
|
+
if (!procTok || procTok.t !== 'word') c.fail('a processor word after EXEC');
|
|
43
|
+
const processor = procTok.u;
|
|
44
|
+
c.next();
|
|
45
|
+
const node = { kind: 'EXEC', processor, toks: [] };
|
|
46
|
+
if (processor === 'SQL') {
|
|
47
|
+
node.sql = parseSql(c);
|
|
48
|
+
} else if (processor === 'CICS') {
|
|
49
|
+
node.cics = parseCics(c);
|
|
50
|
+
} else {
|
|
51
|
+
node.toks = c.drain();
|
|
52
|
+
}
|
|
53
|
+
return node;
|
|
54
|
+
},
|
|
55
|
+
};
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// Parsers for PL/I OPEN, CLOSE, READ, WRITE, REWRITE, DELETE, LOCATE and UNLOCK statements.
|
|
3
|
+
|
|
4
|
+
import { cursor } from '../cursor.mjs';
|
|
5
|
+
import { parseExpression, parseReference } from '../expr.mjs';
|
|
6
|
+
|
|
7
|
+
const OPEN_OPTS = new Set([
|
|
8
|
+
'STREAM', 'RECORD', 'INPUT', 'OUTPUT', 'UPDATE', 'SEQUENTIAL', 'DIRECT', 'TRANSIENT',
|
|
9
|
+
'BUFFERED', 'UNBUFFERED', 'KEYED', 'PRINT', 'BACKWARDS', 'EXCLUSIVE',
|
|
10
|
+
'TITLE', 'LINESIZE', 'PAGESIZE', 'ENVIRONMENT', 'ENV'
|
|
11
|
+
]);
|
|
12
|
+
|
|
13
|
+
const REF_OPTS = new Set(['INTO', 'FROM', 'SET', 'KEYTO', 'EVENT']);
|
|
14
|
+
const EXPR_OPTS = new Set(['KEY', 'KEYFROM', 'IGNORE', 'TITLE', 'LINESIZE', 'PAGESIZE']);
|
|
15
|
+
|
|
16
|
+
function parseFileRef(c) {
|
|
17
|
+
c.expectWord('FILE');
|
|
18
|
+
c.expectOp('(');
|
|
19
|
+
const ref = parseReference(c);
|
|
20
|
+
c.expectOp(')');
|
|
21
|
+
return ref;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function parseOpenFile(c) {
|
|
25
|
+
const file = parseFileRef(c);
|
|
26
|
+
const options = {};
|
|
27
|
+
|
|
28
|
+
while (!c.done()) {
|
|
29
|
+
if (c.isOp(',')) {
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (c.isWord()) {
|
|
34
|
+
const word = c.next();
|
|
35
|
+
const u = word.u;
|
|
36
|
+
|
|
37
|
+
if (OPEN_OPTS.has(u)) {
|
|
38
|
+
if (c.isOp('(')) {
|
|
39
|
+
const inner = c.group();
|
|
40
|
+
const s = cursor(inner);
|
|
41
|
+
if (u === 'ENVIRONMENT' || u === 'ENV') {
|
|
42
|
+
options[u] = inner;
|
|
43
|
+
} else {
|
|
44
|
+
const e = parseExpression(s);
|
|
45
|
+
if (!s.done()) s.fail('the end of the option argument');
|
|
46
|
+
options[u] = e;
|
|
47
|
+
}
|
|
48
|
+
} else {
|
|
49
|
+
options[u] = true;
|
|
50
|
+
}
|
|
51
|
+
} else {
|
|
52
|
+
if (c.isOp('(')) {
|
|
53
|
+
const inner = c.group();
|
|
54
|
+
options[u] = inner;
|
|
55
|
+
} else {
|
|
56
|
+
options[u] = true;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
} else {
|
|
60
|
+
c.fail('an OPEN option');
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return { file, options };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function parseOpen(c, stmt, ctx) {
|
|
68
|
+
c.expectWord('OPEN');
|
|
69
|
+
const files = [];
|
|
70
|
+
files.push(parseOpenFile(c));
|
|
71
|
+
|
|
72
|
+
while (c.isOp(',')) {
|
|
73
|
+
c.next();
|
|
74
|
+
files.push(parseOpenFile(c));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return { kind: 'OPEN', files };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function parseCloseFile(c) {
|
|
81
|
+
const file = parseFileRef(c);
|
|
82
|
+
const options = {};
|
|
83
|
+
|
|
84
|
+
while (!c.done()) {
|
|
85
|
+
if (c.isOp(',')) {
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (c.isWord('ENVIRONMENT') || c.isWord('ENV')) {
|
|
90
|
+
const u = c.next().u;
|
|
91
|
+
if (c.isOp('(')) {
|
|
92
|
+
const inner = c.group();
|
|
93
|
+
options[u] = inner;
|
|
94
|
+
} else {
|
|
95
|
+
options[u] = true;
|
|
96
|
+
}
|
|
97
|
+
} else {
|
|
98
|
+
c.fail('a CLOSE option');
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return { file, options };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function parseClose(c, stmt, ctx) {
|
|
106
|
+
c.expectWord('CLOSE');
|
|
107
|
+
const files = [];
|
|
108
|
+
|
|
109
|
+
if (c.isWord('FILE') && c.isOp('(', 1)) {
|
|
110
|
+
c.next();
|
|
111
|
+
c.next();
|
|
112
|
+
if (c.isOp('*')) {
|
|
113
|
+
c.next();
|
|
114
|
+
c.expectOp(')');
|
|
115
|
+
files.push({ file: { t: 'star', toks: [] }, options: {} });
|
|
116
|
+
} else {
|
|
117
|
+
const ref = parseReference(c);
|
|
118
|
+
c.expectOp(')');
|
|
119
|
+
files.push({ file: ref, options: {} });
|
|
120
|
+
}
|
|
121
|
+
} else {
|
|
122
|
+
files.push(parseCloseFile(c));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
while (c.isOp(',')) {
|
|
126
|
+
c.next();
|
|
127
|
+
files.push(parseCloseFile(c));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return { kind: 'CLOSE', files };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function parseRecordIO(c, stmt, ctx, kind) {
|
|
134
|
+
c.expectWord(kind);
|
|
135
|
+
|
|
136
|
+
const file = parseFileRef(c);
|
|
137
|
+
const options = {};
|
|
138
|
+
let target = null;
|
|
139
|
+
|
|
140
|
+
while (!c.done()) {
|
|
141
|
+
if (c.isWord()) {
|
|
142
|
+
const word = c.next();
|
|
143
|
+
const u = word.u;
|
|
144
|
+
|
|
145
|
+
if (REF_OPTS.has(u)) {
|
|
146
|
+
c.expectOp('(');
|
|
147
|
+
const ref = parseReference(c);
|
|
148
|
+
c.expectOp(')');
|
|
149
|
+
options[u] = ref;
|
|
150
|
+
if (u === 'INTO' || u === 'FROM') target = ref;
|
|
151
|
+
} else if (EXPR_OPTS.has(u)) {
|
|
152
|
+
const inner = c.group();
|
|
153
|
+
const s = cursor(inner);
|
|
154
|
+
const e = parseExpression(s);
|
|
155
|
+
if (!s.done()) s.fail('the end of the option argument');
|
|
156
|
+
options[u] = e;
|
|
157
|
+
} else if (u === 'NOLOCK') {
|
|
158
|
+
options[u] = true;
|
|
159
|
+
} else {
|
|
160
|
+
if (c.isOp('(')) {
|
|
161
|
+
const inner = c.group();
|
|
162
|
+
options[u] = inner;
|
|
163
|
+
} else {
|
|
164
|
+
options[u] = true;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
} else {
|
|
168
|
+
c.fail(`an ${kind} option`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return { kind, file, target, options };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function parseLocate(c, stmt, ctx) {
|
|
176
|
+
c.expectWord('LOCATE');
|
|
177
|
+
const target = parseReference(c);
|
|
178
|
+
const file = parseFileRef(c);
|
|
179
|
+
const options = {};
|
|
180
|
+
|
|
181
|
+
while (!c.done()) {
|
|
182
|
+
if (c.isWord()) {
|
|
183
|
+
const word = c.next();
|
|
184
|
+
const u = word.u;
|
|
185
|
+
|
|
186
|
+
if (u === 'SET') {
|
|
187
|
+
c.expectOp('(');
|
|
188
|
+
const ref = parseReference(c);
|
|
189
|
+
c.expectOp(')');
|
|
190
|
+
options[u] = ref;
|
|
191
|
+
} else if (u === 'KEYFROM') {
|
|
192
|
+
const inner = c.group();
|
|
193
|
+
const s = cursor(inner);
|
|
194
|
+
const e = parseExpression(s);
|
|
195
|
+
if (!s.done()) s.fail('the end of the option argument');
|
|
196
|
+
options[u] = e;
|
|
197
|
+
} else {
|
|
198
|
+
if (c.isOp('(')) {
|
|
199
|
+
const inner = c.group();
|
|
200
|
+
options[u] = inner;
|
|
201
|
+
} else {
|
|
202
|
+
options[u] = true;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
} else {
|
|
206
|
+
c.fail('a LOCATE option');
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return { kind: 'LOCATE', file, target, options };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function parseUnlock(c, stmt, ctx) {
|
|
214
|
+
c.expectWord('UNLOCK');
|
|
215
|
+
const file = parseFileRef(c);
|
|
216
|
+
const options = {};
|
|
217
|
+
|
|
218
|
+
if (c.isWord('KEY')) {
|
|
219
|
+
c.next();
|
|
220
|
+
const inner = c.group();
|
|
221
|
+
const s = cursor(inner);
|
|
222
|
+
const e = parseExpression(s);
|
|
223
|
+
if (!s.done()) s.fail('the end of the option argument');
|
|
224
|
+
options['KEY'] = e;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return { kind: 'UNLOCK', file, target: null, options };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export const parsers = {
|
|
231
|
+
OPEN: parseOpen,
|
|
232
|
+
CLOSE: parseClose,
|
|
233
|
+
READ: (c, s, ctx) => parseRecordIO(c, s, ctx, 'READ'),
|
|
234
|
+
WRITE: (c, s, ctx) => parseRecordIO(c, s, ctx, 'WRITE'),
|
|
235
|
+
REWRITE: (c, s, ctx) => parseRecordIO(c, s, ctx, 'REWRITE'),
|
|
236
|
+
DELETE: (c, s, ctx) => parseRecordIO(c, s, ctx, 'DELETE'),
|
|
237
|
+
LOCATE: parseLocate,
|
|
238
|
+
UNLOCK: parseUnlock
|
|
239
|
+
};
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// Parser for PL/I preprocessor statements (kind PREPROCESSOR), which begin with the op '%'.
|
|
3
|
+
import { cursor } from '../cursor.mjs';
|
|
4
|
+
import { parseExpression, parseReference } from '../expr.mjs';
|
|
5
|
+
|
|
6
|
+
export const parsers = {
|
|
7
|
+
PREPROCESSOR: (c, stmt, ctx) => {
|
|
8
|
+
const node = { kind: 'PREPROCESSOR', units: [] };
|
|
9
|
+
if (!c.isOp('%')) c.fail("'%'");
|
|
10
|
+
c.next();
|
|
11
|
+
|
|
12
|
+
const labelTok = c.peek();
|
|
13
|
+
if (labelTok && labelTok.t === 'word' && c.isOp(':', 1)) {
|
|
14
|
+
c.next();
|
|
15
|
+
c.next();
|
|
16
|
+
node.label = labelTok.u;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (c.done()) { node.directive = 'NULL'; return node; }
|
|
20
|
+
const dirTok = c.peek();
|
|
21
|
+
if (!dirTok || dirTok.t !== 'word') c.fail('a directive word');
|
|
22
|
+
// %X = expression assigns a preprocessor variable.
|
|
23
|
+
if (c.isOp('=', 1)) {
|
|
24
|
+
c.next();
|
|
25
|
+
c.next();
|
|
26
|
+
return { ...node, directive: 'ASSIGN', names: [dirTok.u], value: parseExpression(c) };
|
|
27
|
+
}
|
|
28
|
+
c.next();
|
|
29
|
+
node.directive = dirTok.u;
|
|
30
|
+
|
|
31
|
+
switch (node.directive) {
|
|
32
|
+
case 'INCLUDE':
|
|
33
|
+
case 'XINCLUDE': {
|
|
34
|
+
// A member is a name, a quoted name, or ddname(member) naming the library it is read from.
|
|
35
|
+
node.members = [];
|
|
36
|
+
for (;;) {
|
|
37
|
+
const t = c.next();
|
|
38
|
+
if (t && t.t === 'lit') node.members.push({ name: t.v, library: null });
|
|
39
|
+
else if (t && t.t === 'word' && c.isOp('(')) {
|
|
40
|
+
const s2 = cursor(c.group());
|
|
41
|
+
const m = s2.next();
|
|
42
|
+
if (!m || (m.t !== 'word' && m.t !== 'lit') || !s2.done()) c.fail('a member name in the library reference');
|
|
43
|
+
node.members.push({ name: m.u ?? m.v, library: t.u });
|
|
44
|
+
} else if (t && t.t === 'word') node.members.push({ name: t.u, library: null });
|
|
45
|
+
else c.fail('a member name');
|
|
46
|
+
if (!c.op(',')) break;
|
|
47
|
+
}
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
case 'DCL':
|
|
51
|
+
case 'DECLARE': {
|
|
52
|
+
node.names = [];
|
|
53
|
+
while (!c.done()) {
|
|
54
|
+
if (c.isOp(',')) c.next();
|
|
55
|
+
const nameTok = c.expectWord(undefined);
|
|
56
|
+
node.names.push(nameTok.u);
|
|
57
|
+
while (!c.done()) {
|
|
58
|
+
const t = c.peek();
|
|
59
|
+
if (t.t === 'word') {
|
|
60
|
+
c.next();
|
|
61
|
+
} else if (t.t === 'op' && t.v === '(') {
|
|
62
|
+
c.group();
|
|
63
|
+
} else if (t.t === 'op' && t.v === ',') {
|
|
64
|
+
break;
|
|
65
|
+
} else {
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
case 'IF': {
|
|
73
|
+
const expr = parseExpression(c, { stopOps: ['%'] });
|
|
74
|
+
node.value = expr;
|
|
75
|
+
if (c.isOp('%') && c.isWord('THEN', 1)) {
|
|
76
|
+
c.next();
|
|
77
|
+
c.next();
|
|
78
|
+
const unitToks = c.drain();
|
|
79
|
+
if (unitToks.length) {
|
|
80
|
+
const sub = ctx.subStatement(stmt, unitToks);
|
|
81
|
+
const res = ctx.parseStatement(sub);
|
|
82
|
+
node.units.push(res);
|
|
83
|
+
}
|
|
84
|
+
} else {
|
|
85
|
+
c.fail('%THEN');
|
|
86
|
+
}
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
case 'ELSE': {
|
|
90
|
+
const unitToks = c.drain();
|
|
91
|
+
if (unitToks.length) {
|
|
92
|
+
const sub = ctx.subStatement(stmt, unitToks);
|
|
93
|
+
const res = ctx.parseStatement(sub);
|
|
94
|
+
node.units.push(res);
|
|
95
|
+
}
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
case 'DO':
|
|
99
|
+
case 'END':
|
|
100
|
+
case 'PAGE':
|
|
101
|
+
case 'NOPRINT':
|
|
102
|
+
case 'PUSH':
|
|
103
|
+
case 'POP':
|
|
104
|
+
case 'PRINT':
|
|
105
|
+
case 'ITERATE':
|
|
106
|
+
case 'LEAVE':
|
|
107
|
+
case 'INSCAN':
|
|
108
|
+
case 'XINSCAN':
|
|
109
|
+
case 'PROCESS': {
|
|
110
|
+
if (node.directive === 'PROCESS') {
|
|
111
|
+
while (!c.done()) c.next();
|
|
112
|
+
}
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
case 'SKIP': {
|
|
116
|
+
if (c.isOp('(')) {
|
|
117
|
+
c.group();
|
|
118
|
+
}
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
case 'NOTE': {
|
|
122
|
+
if (c.isOp('(')) {
|
|
123
|
+
c.group();
|
|
124
|
+
}
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
case 'ACTIVATE':
|
|
128
|
+
case 'ACT':
|
|
129
|
+
case 'DEACTIVATE':
|
|
130
|
+
case 'DEACT': {
|
|
131
|
+
node.names = [];
|
|
132
|
+
while (!c.done()) {
|
|
133
|
+
if (c.isWord()) {
|
|
134
|
+
const t = c.next();
|
|
135
|
+
if (t.u === 'RESCAN' || t.u === 'NORESCAN') {
|
|
136
|
+
node.rescan = t.u === 'RESCAN';
|
|
137
|
+
} else {
|
|
138
|
+
node.names.push(t.u);
|
|
139
|
+
}
|
|
140
|
+
} else if (c.isOp(',')) {
|
|
141
|
+
c.next();
|
|
142
|
+
} else {
|
|
143
|
+
c.fail('a name or RESCAN/NORESCAN');
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
case 'GO':
|
|
149
|
+
case 'GOTO': {
|
|
150
|
+
if (c.isWord('TO')) c.next();
|
|
151
|
+
const label = c.expectWord(undefined);
|
|
152
|
+
node.label = label.u;
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
case 'RETURN': {
|
|
156
|
+
if (c.isOp('(')) {
|
|
157
|
+
const inner = c.group();
|
|
158
|
+
const s = cursor(inner);
|
|
159
|
+
node.value = parseExpression(s);
|
|
160
|
+
if (!s.done()) s.fail('the end of the argument');
|
|
161
|
+
}
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
case 'REPLACE': {
|
|
165
|
+
const name = c.expectWord(undefined);
|
|
166
|
+
node.names = [name.u];
|
|
167
|
+
if (!c.isWord('BY')) c.fail('BY');
|
|
168
|
+
c.next();
|
|
169
|
+
node.value = parseExpression(c);
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
case 'PROCEDURE':
|
|
173
|
+
case 'PROC': {
|
|
174
|
+
if (c.isOp('(')) {
|
|
175
|
+
const params = c.items();
|
|
176
|
+
node.params = params.map(p => {
|
|
177
|
+
const s = cursor(p);
|
|
178
|
+
const ref = parseReference(s);
|
|
179
|
+
if (!s.done()) s.fail('the end of the parameter');
|
|
180
|
+
return ref;
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
while (!c.done()) {
|
|
184
|
+
const t = c.peek();
|
|
185
|
+
if (t.t === 'word') {
|
|
186
|
+
if (t.u === 'STATEMENT') {
|
|
187
|
+
c.next();
|
|
188
|
+
node.statement = true;
|
|
189
|
+
} else if (t.u === 'RETURNS') {
|
|
190
|
+
c.next();
|
|
191
|
+
c.expectOp('(');
|
|
192
|
+
const retTok = c.expectWord(undefined);
|
|
193
|
+
c.expectOp(')');
|
|
194
|
+
node.returns = retTok.u;
|
|
195
|
+
} else {
|
|
196
|
+
c.next();
|
|
197
|
+
}
|
|
198
|
+
} else if (t.t === 'op' && t.v === '(') {
|
|
199
|
+
c.group();
|
|
200
|
+
} else {
|
|
201
|
+
c.fail('STATEMENT or RETURNS');
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
case 'SELECT':
|
|
207
|
+
case 'WHEN':
|
|
208
|
+
case 'OTHERWISE': {
|
|
209
|
+
if (node.directive === 'WHEN') {
|
|
210
|
+
if (c.isOp('(')) {
|
|
211
|
+
const inner = c.group();
|
|
212
|
+
const s = cursor(inner);
|
|
213
|
+
node.value = parseExpression(s);
|
|
214
|
+
if (!s.done()) s.fail('the end of the condition');
|
|
215
|
+
}
|
|
216
|
+
const unitToks = c.drain();
|
|
217
|
+
if (unitToks.length) {
|
|
218
|
+
const sub = ctx.subStatement(stmt, unitToks);
|
|
219
|
+
const res = ctx.parseStatement(sub);
|
|
220
|
+
node.units.push(res);
|
|
221
|
+
}
|
|
222
|
+
} else if (node.directive === 'OTHERWISE') {
|
|
223
|
+
const unitToks = c.drain();
|
|
224
|
+
if (unitToks.length) {
|
|
225
|
+
const sub = ctx.subStatement(stmt, unitToks);
|
|
226
|
+
const res = ctx.parseStatement(sub);
|
|
227
|
+
node.units.push(res);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
break;
|
|
231
|
+
}
|
|
232
|
+
default: {
|
|
233
|
+
const rest = c.drain();
|
|
234
|
+
node.toks = rest;
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (!c.done()) c.fail('the end of the statement');
|
|
240
|
+
return node;
|
|
241
|
+
}
|
|
242
|
+
};
|