@abaplint/transpiler 2.13.39 → 2.13.41
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/build/src/chunk.d.ts +26 -1
- package/build/src/chunk.js +83 -37
- package/build/src/expressions/reduce_body.d.ts +3 -0
- package/build/src/expressions/reduce_body.js +82 -8
- package/build/src/expressions/switch_body.js +3 -0
- package/build/src/expressions/value_body.js +8 -4
- package/build/src/handlers/handle_fugr.js +4 -1
- package/build/src/statements/clear.js +6 -9
- package/build/src/statements/concatenate.js +3 -0
- package/build/src/statements/loop.js +13 -1
- package/build/src/statements/return.js +3 -3
- package/build/src/structures/constants.js +5 -3
- package/build/src/structures/data.js +1 -1
- package/build/src/structures/do.js +2 -1
- package/build/src/structures/function_module.js +7 -7
- package/build/src/structures/loop.js +1 -0
- package/build/src/structures/select.js +2 -1
- package/build/src/structures/while.js +2 -1
- package/build/src/traversal.d.ts +3 -0
- package/build/src/traversal.js +11 -2
- package/package.json +2 -2
package/build/src/chunk.d.ts
CHANGED
|
@@ -2,17 +2,42 @@ import * as sourceMap from "source-map";
|
|
|
2
2
|
import * as abaplint from "@abaplint/core";
|
|
3
3
|
export declare class Chunk {
|
|
4
4
|
private raw;
|
|
5
|
+
private lineCount;
|
|
6
|
+
private lastLineLength;
|
|
5
7
|
mappings: sourceMap.Mapping[];
|
|
6
8
|
constructor(str?: string);
|
|
7
9
|
join(chunks: Chunk[], str?: string): Chunk;
|
|
8
10
|
appendChunk(append: Chunk): Chunk;
|
|
11
|
+
private originalPosition;
|
|
9
12
|
append(input: string, pos: abaplint.Position | abaplint.INode | abaplint.Token, traversal: {
|
|
10
13
|
getFilename(): string;
|
|
11
14
|
}): Chunk;
|
|
15
|
+
/**
|
|
16
|
+
* Baseline fallback so statements whose transpiler emitted no mappings still
|
|
17
|
+
* resolve to their ABAP source. Adds a single mapping from the start of this
|
|
18
|
+
* chunk (generated line 1, column 0) to `pos`. No-op if the chunk is empty or
|
|
19
|
+
* already carries mappings, so it never overrides finer-grained mappings.
|
|
20
|
+
*/
|
|
21
|
+
ensureStartMapping(pos: abaplint.Position | abaplint.INode | abaplint.Token, traversal: {
|
|
22
|
+
getFilename(): string;
|
|
23
|
+
}): Chunk;
|
|
12
24
|
appendString(input: string): this;
|
|
13
25
|
stripLastNewline(): void;
|
|
14
26
|
getCode(): string;
|
|
15
27
|
toString(): string;
|
|
16
28
|
runIndentationLogic(ignoreSourceMap?: boolean): this;
|
|
17
|
-
|
|
29
|
+
/**
|
|
30
|
+
* @param generatedFilename name written to the "file" field of the map
|
|
31
|
+
* @param options.generatedLineOffset number of lines prepended to the generated
|
|
32
|
+
* output after this chunk was built (e.g. a runtime import line); every mapping
|
|
33
|
+
* is shifted down by this amount so the map stays aligned with the file on disk
|
|
34
|
+
* @param options.sourcePaths maps a mapping "source" (the bare abap filename) to
|
|
35
|
+
* the path that should appear in the map, avoiding fragile post-hoc string edits
|
|
36
|
+
*/
|
|
37
|
+
getMap(generatedFilename: string, options?: {
|
|
38
|
+
generatedLineOffset?: number;
|
|
39
|
+
sourcePaths?: {
|
|
40
|
+
[filename: string]: string;
|
|
41
|
+
};
|
|
42
|
+
}): string;
|
|
18
43
|
}
|
package/build/src/chunk.js
CHANGED
|
@@ -48,9 +48,14 @@ abaplint:
|
|
|
48
48
|
// Keeps track of source maps as generated code is added
|
|
49
49
|
class Chunk {
|
|
50
50
|
raw;
|
|
51
|
+
// tracked incrementally so appends never re-scan the whole buffer
|
|
52
|
+
lineCount;
|
|
53
|
+
lastLineLength;
|
|
51
54
|
mappings = [];
|
|
52
55
|
constructor(str) {
|
|
53
56
|
this.raw = "";
|
|
57
|
+
this.lineCount = 1;
|
|
58
|
+
this.lastLineLength = 0;
|
|
54
59
|
this.mappings = [];
|
|
55
60
|
if (str) {
|
|
56
61
|
this.appendString(str);
|
|
@@ -69,63 +74,85 @@ class Chunk {
|
|
|
69
74
|
if (append.getCode() === "") {
|
|
70
75
|
return this;
|
|
71
76
|
}
|
|
72
|
-
const lines = this.raw.split("\n");
|
|
73
|
-
const lineCount = lines.length;
|
|
74
|
-
const lastLine = lines[lines.length - 1];
|
|
75
77
|
for (const m of append.mappings) {
|
|
76
|
-
//
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
78
|
+
// deep copy so the appended chunk's mappings are never mutated,
|
|
79
|
+
// otherwise appending the same chunk twice double-shifts its positions
|
|
80
|
+
const add = {
|
|
81
|
+
source: m.source,
|
|
82
|
+
name: m.name,
|
|
83
|
+
generated: { line: m.generated.line, column: m.generated.column },
|
|
84
|
+
original: { line: m.original.line, column: m.original.column },
|
|
85
|
+
};
|
|
86
|
+
// original stays the same, but adjust the generated positions:
|
|
87
|
+
// the appended content begins at the end of the current buffer, so its
|
|
88
|
+
// first line continues the current last line, and every line moves down
|
|
89
|
+
if (add.generated.line === 1) {
|
|
90
|
+
add.generated.column += this.lastLineLength;
|
|
83
91
|
}
|
|
92
|
+
add.generated.line += this.lineCount - 1;
|
|
84
93
|
this.mappings.push(add);
|
|
85
94
|
}
|
|
86
|
-
this.
|
|
87
|
-
|
|
95
|
+
return this.appendString(append.getCode());
|
|
96
|
+
}
|
|
97
|
+
originalPosition(pos) {
|
|
98
|
+
if (pos instanceof abaplint.Position || pos instanceof abaplint.Token) {
|
|
99
|
+
return { line: pos.getRow(), column: pos.getCol() - 1 };
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
return { line: pos.getFirstToken().getRow(), column: pos.getFirstToken().getCol() - 1 };
|
|
103
|
+
}
|
|
88
104
|
}
|
|
89
105
|
append(input, pos, traversal) {
|
|
90
106
|
if (input === "") {
|
|
91
107
|
return this;
|
|
92
108
|
}
|
|
93
109
|
if (pos && input !== "\n") {
|
|
94
|
-
const lines = this.raw.split("\n");
|
|
95
|
-
const lastLine = lines[lines.length - 1];
|
|
96
|
-
let originalLine = 0;
|
|
97
|
-
let originalColumn = 0;
|
|
98
|
-
if (pos instanceof abaplint.Position || pos instanceof abaplint.Token) {
|
|
99
|
-
originalLine = pos.getRow();
|
|
100
|
-
originalColumn = pos.getCol() - 1;
|
|
101
|
-
}
|
|
102
|
-
else {
|
|
103
|
-
originalLine = pos.getFirstToken().getRow();
|
|
104
|
-
originalColumn = pos.getFirstToken().getCol() - 1;
|
|
105
|
-
}
|
|
106
110
|
this.mappings.push({
|
|
107
111
|
source: traversal.getFilename(),
|
|
108
112
|
generated: {
|
|
109
|
-
line:
|
|
110
|
-
column:
|
|
111
|
-
},
|
|
112
|
-
original: {
|
|
113
|
-
line: originalLine,
|
|
114
|
-
column: originalColumn,
|
|
113
|
+
line: this.lineCount,
|
|
114
|
+
column: this.lastLineLength,
|
|
115
115
|
},
|
|
116
|
+
original: this.originalPosition(pos),
|
|
116
117
|
});
|
|
117
118
|
}
|
|
118
|
-
this.
|
|
119
|
+
return this.appendString(input);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Baseline fallback so statements whose transpiler emitted no mappings still
|
|
123
|
+
* resolve to their ABAP source. Adds a single mapping from the start of this
|
|
124
|
+
* chunk (generated line 1, column 0) to `pos`. No-op if the chunk is empty or
|
|
125
|
+
* already carries mappings, so it never overrides finer-grained mappings.
|
|
126
|
+
*/
|
|
127
|
+
ensureStartMapping(pos, traversal) {
|
|
128
|
+
if (this.raw === "" || this.mappings.length > 0) {
|
|
129
|
+
return this;
|
|
130
|
+
}
|
|
131
|
+
this.mappings.push({
|
|
132
|
+
source: traversal.getFilename(),
|
|
133
|
+
generated: { line: 1, column: 0 },
|
|
134
|
+
original: this.originalPosition(pos),
|
|
135
|
+
});
|
|
119
136
|
return this;
|
|
120
137
|
}
|
|
121
138
|
appendString(input) {
|
|
122
139
|
this.raw += input;
|
|
140
|
+
const lastNewline = input.lastIndexOf("\n");
|
|
141
|
+
if (lastNewline < 0) {
|
|
142
|
+
this.lastLineLength += input.length;
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
this.lineCount += input.split("\n").length - 1;
|
|
146
|
+
this.lastLineLength = input.length - lastNewline - 1;
|
|
147
|
+
}
|
|
123
148
|
return this;
|
|
124
149
|
}
|
|
125
150
|
stripLastNewline() {
|
|
126
151
|
// note: this will not change the source map
|
|
127
152
|
if (this.raw.endsWith("\n")) {
|
|
128
153
|
this.raw = this.raw.substring(0, this.raw.length - 1);
|
|
154
|
+
this.lineCount--;
|
|
155
|
+
this.lastLineLength = this.raw.length - this.raw.lastIndexOf("\n") - 1;
|
|
129
156
|
}
|
|
130
157
|
}
|
|
131
158
|
getCode() {
|
|
@@ -145,16 +172,18 @@ class Chunk {
|
|
|
145
172
|
if (l.startsWith("}")) {
|
|
146
173
|
i = i - 1;
|
|
147
174
|
}
|
|
148
|
-
|
|
149
|
-
|
|
175
|
+
// clamp so unbalanced braces never produce a negative indent/shift
|
|
176
|
+
const indent = i > 0 ? i * 2 : 0;
|
|
177
|
+
if (indent > 0) {
|
|
178
|
+
output.push(" ".repeat(indent) + l);
|
|
150
179
|
}
|
|
151
180
|
else {
|
|
152
181
|
output.push(l);
|
|
153
182
|
}
|
|
154
|
-
// fix maps
|
|
183
|
+
// fix maps: shift columns by the indentation actually applied to this line
|
|
155
184
|
for (const m of this.mappings) {
|
|
156
185
|
if (m.generated.line === line) {
|
|
157
|
-
m.generated.column +=
|
|
186
|
+
m.generated.column += indent;
|
|
158
187
|
}
|
|
159
188
|
}
|
|
160
189
|
if (l.endsWith(" {")) {
|
|
@@ -163,11 +192,28 @@ class Chunk {
|
|
|
163
192
|
line++;
|
|
164
193
|
}
|
|
165
194
|
this.raw = output.join("\n");
|
|
195
|
+
// line structure is unchanged, but the last line may have been indented
|
|
196
|
+
this.lastLineLength = output[output.length - 1].length;
|
|
166
197
|
return this;
|
|
167
198
|
}
|
|
168
|
-
|
|
199
|
+
/**
|
|
200
|
+
* @param generatedFilename name written to the "file" field of the map
|
|
201
|
+
* @param options.generatedLineOffset number of lines prepended to the generated
|
|
202
|
+
* output after this chunk was built (e.g. a runtime import line); every mapping
|
|
203
|
+
* is shifted down by this amount so the map stays aligned with the file on disk
|
|
204
|
+
* @param options.sourcePaths maps a mapping "source" (the bare abap filename) to
|
|
205
|
+
* the path that should appear in the map, avoiding fragile post-hoc string edits
|
|
206
|
+
*/
|
|
207
|
+
getMap(generatedFilename, options) {
|
|
208
|
+
const offset = options?.generatedLineOffset ?? 0;
|
|
209
|
+
const sourcePaths = options?.sourcePaths ?? {};
|
|
169
210
|
const sourceMapGenerator = new sourceMap.SourceMapGenerator();
|
|
170
|
-
this.mappings.forEach(m => sourceMapGenerator.addMapping(
|
|
211
|
+
this.mappings.forEach(m => sourceMapGenerator.addMapping({
|
|
212
|
+
source: sourcePaths[m.source] ?? m.source,
|
|
213
|
+
name: m.name,
|
|
214
|
+
original: m.original,
|
|
215
|
+
generated: { line: m.generated.line + offset, column: m.generated.column },
|
|
216
|
+
}));
|
|
171
217
|
const json = sourceMapGenerator.toJSON();
|
|
172
218
|
json.file = generatedFilename;
|
|
173
219
|
json.sourceRoot = "";
|
|
@@ -3,4 +3,7 @@ import { Traversal } from "../traversal";
|
|
|
3
3
|
import { Chunk } from "../chunk";
|
|
4
4
|
export declare class ReduceBodyTranspiler {
|
|
5
5
|
transpile(typ: Nodes.ExpressionNode, body: Nodes.ExpressionNode, traversal: Traversal): Chunk;
|
|
6
|
+
private transpileIndex;
|
|
7
|
+
private declareInit;
|
|
8
|
+
private transpileNext;
|
|
6
9
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ReduceBodyTranspiler = void 0;
|
|
4
4
|
const core_1 = require("@abaplint/core");
|
|
5
|
+
const traversal_1 = require("../traversal");
|
|
5
6
|
const chunk_1 = require("../chunk");
|
|
6
7
|
const transpile_types_1 = require("../transpile_types");
|
|
7
8
|
const target_1 = require("./target");
|
|
@@ -21,10 +22,14 @@ class ReduceBodyTranspiler {
|
|
|
21
22
|
else if (forExpressions.length > 1) {
|
|
22
23
|
throw new Error("ReduceBodyTranspiler, multiple FOR not supported, " + body.concatTokens());
|
|
23
24
|
}
|
|
24
|
-
|
|
25
|
+
const loopExpression = forExpression.findDirectExpression(core_1.Expressions.InlineLoopDefinition);
|
|
26
|
+
if (loopExpression === undefined) {
|
|
27
|
+
// index based FOR, eg. "FOR i = 1 WHILE i <= 5"
|
|
28
|
+
return this.transpileIndex(body, forExpression, traversal);
|
|
29
|
+
}
|
|
30
|
+
else if (["THEN", "UNTIL", "WHILE", "FROM", "TO", "GROUPS"].some(token => forExpression.findDirectTokenByText(token))) {
|
|
25
31
|
throw new Error("ValueBody FOR todo, " + body.concatTokens());
|
|
26
32
|
}
|
|
27
|
-
const loopExpression = forExpression.findDirectExpression(core_1.Expressions.InlineLoopDefinition);
|
|
28
33
|
const loopSource = traversal.traverse(loopExpression?.findDirectExpression(core_1.Expressions.Source)).getCode();
|
|
29
34
|
const loopVariable = traversal.traverse(loopExpression?.findDirectExpression(core_1.Expressions.TargetField)
|
|
30
35
|
|| loopExpression?.findDirectExpression(core_1.Expressions.TargetFieldSymbol)).getCode();
|
|
@@ -42,6 +47,75 @@ class ReduceBodyTranspiler {
|
|
|
42
47
|
const returnId = UniqueIdentifier.get();
|
|
43
48
|
ret.appendString(`const ${returnId} = ${target};\n`);
|
|
44
49
|
*/
|
|
50
|
+
const returnField = this.declareInit(body, traversal, ret);
|
|
51
|
+
ret.appendString(`for await (const ${loopVariable} of abap.statements.loop(${loopSource}${loopWhere})) {\n`);
|
|
52
|
+
ret.appendString(this.transpileNext(body, traversal));
|
|
53
|
+
ret.appendString(`}\n`);
|
|
54
|
+
ret.appendString(`return ${returnField};\n`);
|
|
55
|
+
ret.appendString("})())");
|
|
56
|
+
return ret;
|
|
57
|
+
}
|
|
58
|
+
transpileIndex(body, forExpression, traversal) {
|
|
59
|
+
if (["FROM", "TO", "GROUPS"].some(token => forExpression.findDirectTokenByText(token))) {
|
|
60
|
+
throw new Error("ValueBody FOR todo, " + body.concatTokens());
|
|
61
|
+
}
|
|
62
|
+
const counter = forExpression.findDirectExpression(core_1.Expressions.InlineFieldDefinition);
|
|
63
|
+
if (counter === undefined) {
|
|
64
|
+
throw new Error("ValueBody FOR todo, " + body.concatTokens());
|
|
65
|
+
}
|
|
66
|
+
const cond = forExpression.findDirectExpression(core_1.Expressions.Cond);
|
|
67
|
+
if (cond === undefined) {
|
|
68
|
+
throw new Error("ValueBody FOR missing condition, " + body.concatTokens());
|
|
69
|
+
}
|
|
70
|
+
const hasUntil = forExpression.findDirectTokenByText("UNTIL") !== undefined;
|
|
71
|
+
const hasWhile = forExpression.findDirectTokenByText("WHILE") !== undefined;
|
|
72
|
+
if ((hasUntil ? 1 : 0) + (hasWhile ? 1 : 0) !== 1) {
|
|
73
|
+
throw new Error("ValueBody FOR todo, condition, " + body.concatTokens());
|
|
74
|
+
}
|
|
75
|
+
const fieldName = counter.findDirectExpression(core_1.Expressions.Field)?.concatTokens().toLowerCase();
|
|
76
|
+
if (fieldName === undefined) {
|
|
77
|
+
throw new Error("ValueBody FOR todo, inline field, " + body.concatTokens());
|
|
78
|
+
}
|
|
79
|
+
const scope = traversal.findCurrentScopeByToken(counter.getFirstToken());
|
|
80
|
+
const variable = scope?.findVariable(fieldName);
|
|
81
|
+
if (variable === undefined) {
|
|
82
|
+
throw new Error("ValueBody FOR todo, variable, " + body.concatTokens());
|
|
83
|
+
}
|
|
84
|
+
const counterName = traversal_1.Traversal.prefixVariable(fieldName);
|
|
85
|
+
const startSource = counter.findDirectExpression(core_1.Expressions.Source);
|
|
86
|
+
if (startSource === undefined) {
|
|
87
|
+
throw new Error("ValueBody FOR missing initial value, " + body.concatTokens());
|
|
88
|
+
}
|
|
89
|
+
const start = traversal.traverse(startSource).getCode();
|
|
90
|
+
const thenExpr = forExpression.findExpressionAfterToken("THEN");
|
|
91
|
+
let incrementExpression = "";
|
|
92
|
+
if (thenExpr && thenExpr instanceof core_1.Nodes.ExpressionNode) {
|
|
93
|
+
incrementExpression = traversal.traverse(thenExpr).getCode();
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
incrementExpression = `abap.operators.add(${counterName}, new abap.types.Integer().set(1))`;
|
|
97
|
+
}
|
|
98
|
+
const condCode = traversal.traverse(cond).getCode();
|
|
99
|
+
const ret = new chunk_1.Chunk();
|
|
100
|
+
ret.appendString("(await (async () => {\n");
|
|
101
|
+
const returnField = this.declareInit(body, traversal, ret);
|
|
102
|
+
ret.appendString(transpile_types_1.TranspileTypes.declare(variable) + `\n`);
|
|
103
|
+
ret.appendString(`${counterName}.set(${start});\n`);
|
|
104
|
+
ret.appendString(`while (true) {\n`);
|
|
105
|
+
if (hasWhile) {
|
|
106
|
+
ret.appendString(`if (!(${condCode})) {\nbreak;\n}\n`);
|
|
107
|
+
}
|
|
108
|
+
ret.appendString(this.transpileNext(body, traversal));
|
|
109
|
+
ret.appendString(`${counterName}.set(${incrementExpression});\n`);
|
|
110
|
+
if (hasUntil) {
|
|
111
|
+
ret.appendString(`if (${condCode}) {\nbreak;\n}\n`);
|
|
112
|
+
}
|
|
113
|
+
ret.appendString(`}\n`);
|
|
114
|
+
ret.appendString(`return ${returnField};\n`);
|
|
115
|
+
ret.appendString("})())");
|
|
116
|
+
return ret;
|
|
117
|
+
}
|
|
118
|
+
declareInit(body, traversal, ret) {
|
|
45
119
|
let returnField = "";
|
|
46
120
|
for (const init of body.findDirectExpressions(core_1.Expressions.InlineFieldDefinition)) {
|
|
47
121
|
const fieldName = init.findDirectExpression(core_1.Expressions.Field).concatTokens().toLowerCase();
|
|
@@ -53,18 +127,18 @@ class ReduceBodyTranspiler {
|
|
|
53
127
|
}
|
|
54
128
|
ret.appendString(transpile_types_1.TranspileTypes.declare(variable) + `\n`);
|
|
55
129
|
}
|
|
56
|
-
|
|
130
|
+
return returnField;
|
|
131
|
+
}
|
|
132
|
+
transpileNext(body, traversal) {
|
|
133
|
+
let ret = "";
|
|
57
134
|
for (const nextChild of body.findDirectExpression(core_1.Expressions.ReduceNext)?.getChildren() || []) {
|
|
58
135
|
if (nextChild.get() instanceof core_1.Expressions.SimpleTarget && nextChild instanceof core_1.Nodes.ExpressionNode) {
|
|
59
|
-
ret
|
|
136
|
+
ret += new target_1.TargetTranspiler().transpile(nextChild, traversal).getCode() + ".set(";
|
|
60
137
|
}
|
|
61
138
|
else if (nextChild.get() instanceof core_1.Expressions.Source && nextChild instanceof core_1.Nodes.ExpressionNode) {
|
|
62
|
-
ret
|
|
139
|
+
ret += traversal.traverse(nextChild).getCode() + ");\n";
|
|
63
140
|
}
|
|
64
141
|
}
|
|
65
|
-
ret.appendString(`}\n`);
|
|
66
|
-
ret.appendString(`return ${returnField};\n`);
|
|
67
|
-
ret.appendString("})())");
|
|
68
142
|
return ret;
|
|
69
143
|
}
|
|
70
144
|
}
|
|
@@ -32,6 +32,9 @@ class SwitchBodyTranspiler {
|
|
|
32
32
|
else if (c.concatTokens() === "THEN") {
|
|
33
33
|
mode = "THEN";
|
|
34
34
|
}
|
|
35
|
+
else if (c.concatTokens() === "ELSE") {
|
|
36
|
+
mode = "";
|
|
37
|
+
}
|
|
35
38
|
}
|
|
36
39
|
else if (mode === "WHEN" && c instanceof core_1.Nodes.ExpressionNode) {
|
|
37
40
|
currentWhenThen.whenOr.push(c);
|
|
@@ -24,6 +24,7 @@ class ValueBodyTranspiler {
|
|
|
24
24
|
}
|
|
25
25
|
let post = "";
|
|
26
26
|
let extraFields = "";
|
|
27
|
+
let baseCode = undefined;
|
|
27
28
|
const hasLines = body.findDirectExpression(core_1.Expressions.ValueBodyLine) !== undefined;
|
|
28
29
|
const children = body.getChildren();
|
|
29
30
|
for (let i = 0; i < children.length; i++) {
|
|
@@ -39,7 +40,8 @@ class ValueBodyTranspiler {
|
|
|
39
40
|
}
|
|
40
41
|
else if (child.get() instanceof core_1.Expressions.ValueBase && child instanceof core_1.Nodes.ExpressionNode) {
|
|
41
42
|
const source = traversal.traverse(child.findDirectExpression(core_1.Expressions.Source));
|
|
42
|
-
|
|
43
|
+
baseCode = source.getCode() + ".clone()";
|
|
44
|
+
ret = new chunk_1.Chunk().appendString(baseCode);
|
|
43
45
|
}
|
|
44
46
|
else if (child.get() instanceof core_1.Expressions.ValueBodyLine && child instanceof core_1.Nodes.ExpressionNode) {
|
|
45
47
|
if (!(context instanceof core_1.BasicTypes.TableType)) {
|
|
@@ -66,7 +68,7 @@ class ValueBodyTranspiler {
|
|
|
66
68
|
}
|
|
67
69
|
}
|
|
68
70
|
i = idx - 1;
|
|
69
|
-
const result = this.buildForChain(forNodes, typ, traversal, body);
|
|
71
|
+
const result = this.buildForChain(forNodes, typ, traversal, body, baseCode);
|
|
70
72
|
ret = result.chunk;
|
|
71
73
|
post = result.post;
|
|
72
74
|
}
|
|
@@ -77,6 +79,8 @@ class ValueBodyTranspiler {
|
|
|
77
79
|
const pre = `(await (async () => { try { return `;
|
|
78
80
|
ret = new chunk_1.Chunk().appendString(pre + ret.getCode());
|
|
79
81
|
post += `; } catch (error) { if (abap.isLineNotFound(error)) { return ${deflt}; } throw error; } })())`;
|
|
82
|
+
// the default value Source is part of this DEFAULT handling, stop processing further children
|
|
83
|
+
break;
|
|
80
84
|
}
|
|
81
85
|
else if (child instanceof core_1.Nodes.TokenNode && child.getFirstToken().getStr().toUpperCase() === "OPTIONAL") {
|
|
82
86
|
// note: this is last in the body, so its okay to prepend and postpend
|
|
@@ -90,8 +94,8 @@ class ValueBodyTranspiler {
|
|
|
90
94
|
}
|
|
91
95
|
return ret.appendString(post);
|
|
92
96
|
}
|
|
93
|
-
buildForChain(forNodes, typ, traversal, body) {
|
|
94
|
-
const val = new type_name_or_infer_1.TypeNameOrInfer().transpile(typ, traversal).getCode();
|
|
97
|
+
buildForChain(forNodes, typ, traversal, body, baseCode) {
|
|
98
|
+
const val = baseCode ?? new type_name_or_infer_1.TypeNameOrInfer().transpile(typ, traversal).getCode();
|
|
95
99
|
const chunk = new chunk_1.Chunk();
|
|
96
100
|
const preLoopDecls = [];
|
|
97
101
|
const descriptors = [];
|
|
@@ -62,8 +62,11 @@ class HandleFUGR {
|
|
|
62
62
|
const contents = new traversal_1.Traversal(spaghetti, file, obj, reg, this.options).traverse(rearranged);
|
|
63
63
|
chunk.appendChunk(contents);
|
|
64
64
|
chunk.stripLastNewline();
|
|
65
|
-
chunk.runIndentationLogic(this.options?.ignoreSourceMap);
|
|
66
65
|
}
|
|
66
|
+
// indentation must run once over the accumulated chunk: running it per
|
|
67
|
+
// appended file re-indents the earlier files, drifting the brace counter
|
|
68
|
+
// and shifting their mapping columns multiple times
|
|
69
|
+
chunk.runIndentationLogic(this.options?.ignoreSourceMap);
|
|
67
70
|
chunk.appendString("\n}");
|
|
68
71
|
const output = {
|
|
69
72
|
object: {
|
|
@@ -39,17 +39,14 @@ const chunk_1 = require("../chunk");
|
|
|
39
39
|
class ClearTranspiler {
|
|
40
40
|
transpile(node, traversal) {
|
|
41
41
|
const target = traversal.traverse(node.findDirectExpression(abaplint.Expressions.Target));
|
|
42
|
+
// Reference implementation for the source-map convention:
|
|
43
|
+
// - keep the target's own expression-level mappings by appending its Chunk
|
|
44
|
+
// (do NOT flatten with target.getCode(), that discards the mappings)
|
|
45
|
+
// - map the trailing generated syntax to the statement's last token, not
|
|
46
|
+
// getEnd() which points one column past the statement
|
|
42
47
|
const ret = new chunk_1.Chunk();
|
|
43
|
-
/*
|
|
44
48
|
ret.appendChunk(target);
|
|
45
|
-
ret.append(".clear();", node.getLastToken()
|
|
46
|
-
*/
|
|
47
|
-
ret.append(target.getCode() + ".clear();", node.getLastToken().getEnd(), traversal);
|
|
48
|
-
// ret.append(target.getCode() + ".clear();", node, traversal);
|
|
49
|
-
/*
|
|
50
|
-
ret.append(target.getCode(), node.getFirstToken().getStart(), traversal);
|
|
51
|
-
ret.append(".clear();", node.getLastToken().getEnd(), traversal);
|
|
52
|
-
*/
|
|
49
|
+
ret.append(".clear();", node.getLastToken(), traversal);
|
|
53
50
|
return ret;
|
|
54
51
|
}
|
|
55
52
|
}
|
|
@@ -53,6 +53,9 @@ class ConcatenateTranspiler {
|
|
|
53
53
|
if (concat.includes(" RESPECTING BLANKS")) {
|
|
54
54
|
extra += ", respectingBlanks: true";
|
|
55
55
|
}
|
|
56
|
+
if (concat.includes(" IN BYTE MODE")) {
|
|
57
|
+
extra += ", byteMode: true";
|
|
58
|
+
}
|
|
56
59
|
const target = traversal.traverse(node.findDirectExpression(abaplint.Expressions.Target));
|
|
57
60
|
return new chunk_1.Chunk()
|
|
58
61
|
.append("abap.statements.concatenate({source: [", node, traversal)
|
|
@@ -65,8 +65,11 @@ class LoopTranspiler {
|
|
|
65
65
|
const source = traversal.traverse(loopSource).getCode();
|
|
66
66
|
this.unique = unique_identifier_1.UniqueIdentifier.get();
|
|
67
67
|
let target = "";
|
|
68
|
+
// the abap expression the target-assignment line should map back to
|
|
69
|
+
let targetNode = undefined;
|
|
68
70
|
const into = this.determineInto(node);
|
|
69
71
|
if (into && this.skipInto !== true) {
|
|
72
|
+
targetNode = into;
|
|
70
73
|
const concat = node.concatTokens().toUpperCase();
|
|
71
74
|
const t = traversal.traverse(into).getCode();
|
|
72
75
|
const scope = traversal.findCurrentScopeByToken(node.getFirstToken());
|
|
@@ -86,6 +89,7 @@ class LoopTranspiler {
|
|
|
86
89
|
else if (this.skipInto !== true) {
|
|
87
90
|
const assigning = node.findFirstExpression(abaplint.Expressions.FSTarget)?.findFirstExpression(abaplint.Expressions.FieldSymbol);
|
|
88
91
|
if (assigning) {
|
|
92
|
+
targetNode = assigning;
|
|
89
93
|
target = traversal.traverse(assigning).getCode() + ".assign(" + this.unique + ");";
|
|
90
94
|
}
|
|
91
95
|
}
|
|
@@ -166,7 +170,15 @@ class LoopTranspiler {
|
|
|
166
170
|
if (extra.length > 0) {
|
|
167
171
|
concat = ",{" + extra.join(",") + "}";
|
|
168
172
|
}
|
|
169
|
-
|
|
173
|
+
// map the loop head to the statement start and the target-assignment line
|
|
174
|
+
// (foo.set(unique)) back to the INTO/ASSIGNING target expression
|
|
175
|
+
const ret = new chunk_1.Chunk();
|
|
176
|
+
ret.append(`for await (const ${this.unique} of abap.statements.loop(${source}${concat})) {`, node, traversal);
|
|
177
|
+
ret.appendString("\n");
|
|
178
|
+
if (target !== "") {
|
|
179
|
+
ret.append(target, targetNode ?? node, traversal);
|
|
180
|
+
}
|
|
181
|
+
return ret;
|
|
170
182
|
}
|
|
171
183
|
}
|
|
172
184
|
exports.LoopTranspiler = LoopTranspiler;
|
|
@@ -37,7 +37,6 @@ exports.ReturnTranspiler = void 0;
|
|
|
37
37
|
const abaplint = __importStar(require("@abaplint/core"));
|
|
38
38
|
const traversal_1 = require("../traversal");
|
|
39
39
|
const chunk_1 = require("../chunk");
|
|
40
|
-
const unique_identifier_1 = require("../unique_identifier");
|
|
41
40
|
class ReturnTranspiler {
|
|
42
41
|
transpile(node, traversal) {
|
|
43
42
|
const source = node.findDirectExpression(abaplint.Expressions.Source);
|
|
@@ -51,8 +50,9 @@ class ReturnTranspiler {
|
|
|
51
50
|
}
|
|
52
51
|
}
|
|
53
52
|
let pre = "";
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
const syIndexBackup = traversal.findCurrentDoOrWhileIndexBackup(node);
|
|
54
|
+
if (syIndexBackup !== undefined) {
|
|
55
|
+
pre = `abap.builtin.sy.get().index.set(${syIndexBackup});\n`;
|
|
56
56
|
}
|
|
57
57
|
if (scope?.getIdentifier().stype === abaplint.ScopeType.Method
|
|
58
58
|
&& scope?.getIdentifier().sname.toLowerCase() === "constructor") {
|
|
@@ -47,9 +47,11 @@ class ConstantsTranspiler {
|
|
|
47
47
|
if (name === undefined) {
|
|
48
48
|
throw "ConstantsTranspilerName";
|
|
49
49
|
}
|
|
50
|
-
|
|
51
|
-
ret
|
|
52
|
-
|
|
50
|
+
const ret = new chunk_1.Chunk();
|
|
51
|
+
ret.appendChunk(new statements_1.DataTranspiler().transpile(begin, traversal).ensureStartMapping(begin, traversal));
|
|
52
|
+
ret.appendString("\n");
|
|
53
|
+
ret.appendString(ConstantsTranspiler.handleValues(name, node, traversal));
|
|
54
|
+
return ret;
|
|
53
55
|
}
|
|
54
56
|
static handleValues(prefix, node, traversal) {
|
|
55
57
|
let ret = "";
|
|
@@ -44,7 +44,7 @@ class DataTranspiler {
|
|
|
44
44
|
return new chunk_1.Chunk("");
|
|
45
45
|
}
|
|
46
46
|
const topName = begin.findDirectExpression(abaplint.Expressions.DefinitionName)?.concatTokens().toLowerCase();
|
|
47
|
-
const chunk = new statements_1.DataTranspiler().transpile(begin, traversal);
|
|
47
|
+
const chunk = new statements_1.DataTranspiler().transpile(begin, traversal).ensureStartMapping(begin, traversal);
|
|
48
48
|
for (const d of node.findDirectStatements(abaplint.Statements.Data)) {
|
|
49
49
|
const subName = d.findFirstExpression(abaplint.Expressions.DefinitionName)?.concatTokens().toLowerCase();
|
|
50
50
|
if (subName && topName) {
|
|
@@ -44,7 +44,8 @@ class DoTranspiler {
|
|
|
44
44
|
const syIndexBackup = unique_identifier_1.UniqueIdentifier.getIndexBackup();
|
|
45
45
|
for (const c of node.getChildren()) {
|
|
46
46
|
if (c instanceof abaplint.Nodes.StatementNode && c.get() instanceof abaplint.Statements.Do) {
|
|
47
|
-
|
|
47
|
+
traversal.registerDoOrWhileIndexBackup(c, syIndexBackup);
|
|
48
|
+
ret.appendChunk(new statements_1.DoTranspiler(syIndexBackup).transpile(c, traversal).ensureStartMapping(c, traversal));
|
|
48
49
|
ret.appendString("\n");
|
|
49
50
|
}
|
|
50
51
|
else if (c instanceof abaplint.Nodes.StatementNode && c.get() instanceof abaplint.Statements.EndDo) {
|
|
@@ -41,7 +41,7 @@ const transpile_types_1 = require("../transpile_types");
|
|
|
41
41
|
const unique_identifier_1 = require("../unique_identifier");
|
|
42
42
|
class FunctionModuleTranspiler {
|
|
43
43
|
transpile(node, traversal) {
|
|
44
|
-
|
|
44
|
+
const chunk = new chunk_1.Chunk();
|
|
45
45
|
let name = "";
|
|
46
46
|
for (const c of node.getChildren()) {
|
|
47
47
|
if (c.get() instanceof abaplint.Statements.FunctionModule && c instanceof abaplint.Nodes.StatementNode) {
|
|
@@ -49,19 +49,19 @@ class FunctionModuleTranspiler {
|
|
|
49
49
|
if (name === undefined) {
|
|
50
50
|
name = "FunctionModuleTranspilerNameNotFound";
|
|
51
51
|
}
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
chunk.append(`async function ${traversal_1.Traversal.escapeNamespace(name)}(INPUT) {\n`, c, traversal);
|
|
53
|
+
chunk.appendString(this.findSignature(traversal, name, c));
|
|
54
54
|
}
|
|
55
55
|
else if (c.get() instanceof abaplint.Statements.EndFunction) {
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
chunk.append("}\n", c, traversal);
|
|
57
|
+
chunk.appendString(`abap.FunctionModules['${name.toUpperCase()}'] = ${traversal_1.Traversal.escapeNamespace(name)};\n`);
|
|
58
58
|
}
|
|
59
59
|
else {
|
|
60
|
-
|
|
60
|
+
chunk.appendChunk(traversal.traverse(c));
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
unique_identifier_1.UniqueIdentifier.resetIndexBackup();
|
|
64
|
-
return
|
|
64
|
+
return chunk;
|
|
65
65
|
}
|
|
66
66
|
//////////////////////
|
|
67
67
|
findSignature(traversal, name, node) {
|
|
@@ -94,6 +94,7 @@ class LoopTranspiler {
|
|
|
94
94
|
ret.appendString(`let ${tabix} = undefined;\n`);
|
|
95
95
|
}
|
|
96
96
|
const loop = new statements_1.LoopTranspiler();
|
|
97
|
+
// LoopStatementTranspiler maps its own head, so no ensureStartMapping needed here
|
|
97
98
|
ret.appendChunk(loop.transpile(c, traversal));
|
|
98
99
|
ret.appendString("\n");
|
|
99
100
|
if (hasAt === true) {
|
|
@@ -60,7 +60,8 @@ class SelectTranspiler {
|
|
|
60
60
|
const targetName = unique_identifier_1.UniqueIdentifier.get();
|
|
61
61
|
const loopName = unique_identifier_1.UniqueIdentifier.get();
|
|
62
62
|
ret.appendString(`let ${targetName} = new abap.types.Table(abap.DDIC["${from}"].type());\n`);
|
|
63
|
-
|
|
63
|
+
const selectHead = new select_1.SelectTranspiler().transpile(selectStatement, traversal, targetName);
|
|
64
|
+
ret.appendChunk(selectHead.ensureStartMapping(selectStatement, traversal));
|
|
64
65
|
// todo: optimize, it should do real streaming?
|
|
65
66
|
const packageSize = selectStatement.findFirstExpression(abaplint.Expressions.SQLPackageSize)
|
|
66
67
|
?.findFirstExpression(abaplint.Expressions.SQLSource);
|
|
@@ -44,7 +44,8 @@ class WhileTranspiler {
|
|
|
44
44
|
const syIndexBackup = unique_identifier_1.UniqueIdentifier.getIndexBackup();
|
|
45
45
|
for (const c of node.getChildren()) {
|
|
46
46
|
if (c instanceof abaplint.Nodes.StatementNode && c.get() instanceof abaplint.Statements.While) {
|
|
47
|
-
|
|
47
|
+
traversal.registerDoOrWhileIndexBackup(c, syIndexBackup);
|
|
48
|
+
ret.appendChunk(new statements_1.WhileTranspiler(syIndexBackup).transpile(c, traversal).ensureStartMapping(c, traversal));
|
|
48
49
|
ret.appendString("\n");
|
|
49
50
|
}
|
|
50
51
|
else if (c instanceof abaplint.Nodes.StatementNode && c.get() instanceof abaplint.Statements.EndWhile) {
|
package/build/src/traversal.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export declare class Traversal {
|
|
|
6
6
|
private readonly file;
|
|
7
7
|
private readonly obj;
|
|
8
8
|
private sqlInferredType;
|
|
9
|
+
private readonly doOrWhileIndexBackups;
|
|
9
10
|
readonly reg: abaplint.IRegistry;
|
|
10
11
|
readonly options: ITranspilerOptions | undefined;
|
|
11
12
|
constructor(spaghetti: abaplint.ISpaghettiScope, file: abaplint.ABAPFile, obj: abaplint.ABAPObject, reg: abaplint.IRegistry, options?: ITranspilerOptions);
|
|
@@ -58,6 +59,8 @@ export declare class Traversal {
|
|
|
58
59
|
determineType(node: abaplint.Nodes.ExpressionNode | abaplint.Nodes.StatementNode, scope: abaplint.ISpaghettiScopeNode | undefined): abaplint.AbstractType | undefined;
|
|
59
60
|
isInsideLoop(node: abaplint.Nodes.StatementNode): boolean;
|
|
60
61
|
isInsideDoOrWhile(node: abaplint.Nodes.StatementNode): boolean;
|
|
62
|
+
registerDoOrWhileIndexBackup(node: abaplint.Nodes.StatementNode, name: string): void;
|
|
63
|
+
findCurrentDoOrWhileIndexBackup(node: abaplint.Nodes.StatementNode): string | undefined;
|
|
61
64
|
registerClassOrInterface(def: abaplint.IClassDefinition | abaplint.IInterfaceDefinition | undefined): string;
|
|
62
65
|
setValues(identifier: abaplint.TypedIdentifier, name: string): string;
|
|
63
66
|
static setValues(identifier: abaplint.TypedIdentifier, name: string): string;
|
package/build/src/traversal.js
CHANGED
|
@@ -47,6 +47,7 @@ class Traversal {
|
|
|
47
47
|
file;
|
|
48
48
|
obj;
|
|
49
49
|
sqlInferredType;
|
|
50
|
+
doOrWhileIndexBackups = new Map();
|
|
50
51
|
reg;
|
|
51
52
|
options;
|
|
52
53
|
constructor(spaghetti, file, obj, reg, options) {
|
|
@@ -779,12 +780,18 @@ this.INTERNAL_ID = abap.internalIdCounter++;\n`;
|
|
|
779
780
|
return stack.length > 0;
|
|
780
781
|
}
|
|
781
782
|
isInsideDoOrWhile(node) {
|
|
783
|
+
return this.findCurrentDoOrWhileIndexBackup(node) !== undefined;
|
|
784
|
+
}
|
|
785
|
+
registerDoOrWhileIndexBackup(node, name) {
|
|
786
|
+
this.doOrWhileIndexBackups.set(node, name);
|
|
787
|
+
}
|
|
788
|
+
findCurrentDoOrWhileIndexBackup(node) {
|
|
782
789
|
const stack = [];
|
|
783
790
|
for (const statement of this.getFile().getStatements()) {
|
|
784
791
|
const get = statement.get();
|
|
785
792
|
if (get instanceof abaplint.Statements.While
|
|
786
793
|
|| get instanceof abaplint.Statements.Do) {
|
|
787
|
-
stack.push(statement);
|
|
794
|
+
stack.push(this.doOrWhileIndexBackups.get(statement));
|
|
788
795
|
}
|
|
789
796
|
else if (get instanceof abaplint.Statements.EndWhile
|
|
790
797
|
|| get instanceof abaplint.Statements.EndDo) {
|
|
@@ -794,7 +801,7 @@ this.INTERNAL_ID = abap.internalIdCounter++;\n`;
|
|
|
794
801
|
break;
|
|
795
802
|
}
|
|
796
803
|
}
|
|
797
|
-
return stack.length
|
|
804
|
+
return stack[stack.length - 1];
|
|
798
805
|
}
|
|
799
806
|
registerClassOrInterface(def) {
|
|
800
807
|
if (def === undefined) {
|
|
@@ -907,6 +914,8 @@ this.INTERNAL_ID = abap.internalIdCounter++;\n`;
|
|
|
907
914
|
if (list[search]) {
|
|
908
915
|
const transpiler = new list[search]();
|
|
909
916
|
const chunk = transpiler.transpile(node, this);
|
|
917
|
+
// baseline: statements that emitted no mappings still resolve to their source
|
|
918
|
+
chunk.ensureStartMapping(node, this);
|
|
910
919
|
chunk.appendString("\n");
|
|
911
920
|
return chunk;
|
|
912
921
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abaplint/transpiler",
|
|
3
|
-
"version": "2.13.
|
|
3
|
+
"version": "2.13.41",
|
|
4
4
|
"description": "Transpiler",
|
|
5
5
|
"main": "build/src/index.js",
|
|
6
6
|
"typings": "build/src/index.d.ts",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"author": "abaplint",
|
|
30
30
|
"license": "MIT",
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@abaplint/core": "^2.
|
|
32
|
+
"@abaplint/core": "^2.120.2",
|
|
33
33
|
"source-map": "^0.7.6"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|