@abaplint/transpiler 2.13.96 → 2.13.98

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.
@@ -170,7 +170,8 @@ class Chunk {
170
170
  }
171
171
  const indentationByLine = this.mappings.length > 0 ? [] : undefined;
172
172
  for (const l of this.raw.split("\n")) {
173
- if (l.startsWith("}")) {
173
+ // trimStart, a pre-indented closing brace must still close its level
174
+ if (l.trimStart().startsWith("}")) {
174
175
  i = i - 1;
175
176
  }
176
177
  // clamp so unbalanced braces never produce a negative indent/shift
@@ -31,22 +31,18 @@ class ReduceBodyTranspiler {
31
31
  for (const declaration of declarations) {
32
32
  ret.appendString(declaration + "\n");
33
33
  }
34
- let indent = "";
35
- const levelIndents = [];
34
+ // no manual indentation, Chunk.runIndentationLogic indents by brace depth,
35
+ // pre-indented closing braces would not be recognized and drift the indentation
36
36
  for (const descriptor of descriptors) {
37
- this.appendBlocks(ret, descriptor.beforeLoop, indent);
38
- ret.appendString(indent + descriptor.open + "\n");
39
- indent += " ";
40
- levelIndents.push(indent);
41
- this.appendBlocks(ret, descriptor.preBody, indent);
37
+ this.appendBlocks(ret, descriptor.beforeLoop);
38
+ ret.appendString(descriptor.open + "\n");
39
+ this.appendBlocks(ret, descriptor.preBody);
42
40
  }
43
- this.appendBlock(ret, this.transpileNext(body, traversal), indent);
41
+ this.appendBlock(ret, this.transpileNext(body, traversal));
44
42
  for (let i = descriptors.length - 1; i >= 0; i--) {
45
43
  const descriptor = descriptors[i];
46
- const currentIndent = levelIndents[i];
47
- this.appendBlocks(ret, descriptor.postBody, currentIndent);
48
- indent = currentIndent.substring(0, Math.max(0, currentIndent.length - 2));
49
- ret.appendString(indent + descriptor.close + "\n");
44
+ this.appendBlocks(ret, descriptor.postBody);
45
+ ret.appendString(descriptor.close + "\n");
50
46
  }
51
47
  ret.appendString(`return ${returnField};\n`);
52
48
  ret.appendString("})())");
@@ -266,15 +262,15 @@ class ReduceBodyTranspiler {
266
262
  }
267
263
  return ret;
268
264
  }
269
- appendBlocks(ret, blocks, indent) {
265
+ appendBlocks(ret, blocks) {
270
266
  for (const block of blocks) {
271
- this.appendBlock(ret, block, indent);
267
+ this.appendBlock(ret, block);
272
268
  }
273
269
  }
274
- appendBlock(ret, block, indent) {
270
+ appendBlock(ret, block) {
275
271
  for (const line of block.split("\n")) {
276
272
  if (line.trim() !== "") {
277
- ret.appendString(indent + line.replace(/\r/g, "") + "\n");
273
+ ret.appendString(line.replace(/\r/g, "") + "\n");
278
274
  }
279
275
  }
280
276
  }
@@ -4,7 +4,9 @@ import { IExpressionTranspiler } from "./_expression_transpiler";
4
4
  import { Traversal } from "../traversal";
5
5
  export declare class SourceTranspiler implements IExpressionTranspiler {
6
6
  private readonly addGet;
7
- constructor(addGet?: boolean);
7
+ private readonly int8;
8
+ /** int8: the calculation type is int8, each operation gets its left operand as an int8 */
9
+ constructor(addGet?: boolean, int8?: boolean);
8
10
  private concatOperands;
9
11
  transpile(node: Nodes.ExpressionNode, traversal: Traversal, context?: AbstractType): Chunk;
10
12
  }
@@ -9,11 +9,15 @@ const corresponding_body_1 = require("./corresponding_body");
9
9
  const filter_body_1 = require("./filter_body");
10
10
  const reduce_body_1 = require("./reduce_body");
11
11
  const transpile_types_1 = require("../transpile_types");
12
+ const let_1 = require("./let");
12
13
  const value_body_1 = require("./value_body");
13
14
  class SourceTranspiler {
14
15
  addGet;
15
- constructor(addGet = false) {
16
+ int8;
17
+ /** int8: the calculation type is int8, each operation gets its left operand as an int8 */
18
+ constructor(addGet = false, int8 = false) {
16
19
  this.addGet = addGet;
20
+ this.int8 = int8;
17
21
  }
18
22
  // the operands of a chain of & / && (the rearranger leaves them as a binary
19
23
  // tree of Source nodes); an empty list when the node is not such a chain
@@ -81,6 +85,8 @@ class SourceTranspiler {
81
85
  break;
82
86
  }
83
87
  }
88
+ // int8: whether the operand left of the next operator is already an int8
89
+ let leftIsInt8 = false;
84
90
  for (let i = startIdx; i < children.length; i++) {
85
91
  const c = children[i];
86
92
  const isLast = i === children.length - 1;
@@ -98,7 +104,18 @@ class SourceTranspiler {
98
104
  ret.appendChunk(traversal.traverse(c));
99
105
  }
100
106
  else if (c.get() instanceof core_1.Expressions.ArithOperator) {
101
- ret = new chunk_1.Chunk().appendChunk(traversal.traverse(c)).appendString("(").appendChunk(ret).appendString(",");
107
+ // "/" rounds in calculation type int8, the runtime's divide() does not know the target
108
+ const operator = this.int8 === true && c.concatTokens().trim() === "/"
109
+ ? new chunk_1.Chunk().append("abap.operators.divideInt8", c, traversal)
110
+ : traversal.traverse(c);
111
+ if (this.int8 === true && leftIsInt8 === false) {
112
+ ret = new chunk_1.Chunk().appendChunk(operator).appendString("(new abap.types.Integer8().set(").appendChunk(ret).appendString("),");
113
+ }
114
+ else {
115
+ ret = new chunk_1.Chunk().appendChunk(operator).appendString("(").appendChunk(ret).appendString(",");
116
+ }
117
+ // the result of an operation with an int8 operand is an int8
118
+ leftIsInt8 = true;
102
119
  post.appendString(")");
103
120
  if (this.addGet) {
104
121
  post.append(".get()", c, traversal);
@@ -115,7 +132,8 @@ class SourceTranspiler {
115
132
  }
116
133
  }
117
134
  else if (c.get() instanceof core_1.Expressions.Source) {
118
- ret.appendChunk(new SourceTranspiler(this.addGet).transpile(c, traversal));
135
+ ret.appendChunk(new SourceTranspiler(this.addGet, this.int8).transpile(c, traversal));
136
+ leftIsInt8 = c.findDirectExpression(core_1.Expressions.ArithOperator) !== undefined;
119
137
  }
120
138
  else if (c.get() instanceof core_1.Expressions.Arrow) {
121
139
  ret = new chunk_1.Chunk().appendString("(").appendChunk(ret).appendString(").get().");
@@ -151,13 +169,19 @@ class SourceTranspiler {
151
169
  }
152
170
  ret = new chunk_1.Chunk().appendString(new _1.TypeNameOrInfer().transpile(typ, traversal).getCode());
153
171
  ret.appendString(".set(");
154
- // todo: handle LET
155
172
  const context = new _1.TypeNameOrInfer().findType(typ, traversal);
156
- ret.appendString(new SourceTranspiler().transpile(c.getFirstChild(), traversal, context).getCode());
173
+ const convSource = c.findDirectExpression(core_1.Expressions.Source);
174
+ ret.appendString(new SourceTranspiler().transpile(convSource, traversal, context).getCode());
157
175
  ret.appendString(")");
158
176
  if (this.addGet) {
159
177
  ret.appendString(".get()");
160
178
  }
179
+ const convLet = c.findDirectExpression(core_1.Expressions.Let);
180
+ if (convLet) {
181
+ // the LET bindings are declared in a scope of their own, before the value is built
182
+ const letCode = new let_1.LetTranspiler().transpile(convLet, traversal).getCode();
183
+ ret = new chunk_1.Chunk().appendString(`(await (async () => { ${letCode} return ${ret.getCode()}; })())`);
184
+ }
161
185
  }
162
186
  else if (c.get() instanceof core_1.Expressions.ValueBody) {
163
187
  continue;
@@ -30,6 +30,7 @@ class ValueBodyTranspiler {
30
30
  const outerLetCode = outerLet === undefined
31
31
  ? undefined
32
32
  : new let_1.LetTranspiler().transpile(outerLet, traversal).getCode();
33
+ let hasFor = false;
33
34
  const children = body.getChildren();
34
35
  for (let i = 0; i < children.length; i++) {
35
36
  const child = children[i];
@@ -72,6 +73,7 @@ class ValueBodyTranspiler {
72
73
  }
73
74
  }
74
75
  i = idx - 1;
76
+ hasFor = true;
75
77
  const result = this.buildForChain(forNodes, typ, traversal, body, baseCode, outerLetCode);
76
78
  ret = result.chunk;
77
79
  post = result.post;
@@ -99,14 +101,18 @@ class ValueBodyTranspiler {
99
101
  throw new Error("ValueBodyTranspiler, unknown " + child.get().constructor.name + " \"" + child.concatTokens()) + "\"";
100
102
  }
101
103
  }
102
- return ret.appendString(post);
104
+ ret.appendString(post);
105
+ if (outerLetCode !== undefined && hasFor === false) {
106
+ // without FOR the LET bindings are declared here, in a scope of their own, before the value is built
107
+ ret = new chunk_1.Chunk().appendString(`(await (async () => { ${outerLetCode} return ${ret.getCode()}; })())`);
108
+ }
109
+ return ret;
103
110
  }
104
111
  buildForChain(forNodes, typ, traversal, body, baseCode, outerLetCode) {
105
112
  const val = baseCode ?? new type_name_or_infer_1.TypeNameOrInfer().transpile(typ, traversal).getCode();
106
113
  const chunk = new chunk_1.Chunk();
107
114
  const preLoopDecls = outerLetCode === undefined ? [] : [outerLetCode];
108
115
  const descriptors = [];
109
- const levelIndents = [];
110
116
  let uniqueCounter = 1;
111
117
  for (const child of forNodes) {
112
118
  const loop = child.findDirectExpression(core_1.Expressions.InlineLoopDefinition);
@@ -222,11 +228,11 @@ class ValueBodyTranspiler {
222
228
  letCode = new let_1.LetTranspiler().transpile(llet, traversal).getCode();
223
229
  }
224
230
  const condCode = traversal.traverse(cond).getCode();
225
- const preCheck = hasWhile ? `if (!(${condCode})) {\n break;\n}` : "";
231
+ const preCheck = hasWhile ? `if (!(${condCode})) {\nbreak;\n}` : "";
226
232
  const postLoop = [];
227
233
  postLoop.push(incrementLine);
228
234
  if (hasUntil) {
229
- postLoop.push(`if (${condCode}) {\n break;\n}`);
235
+ postLoop.push(`if (${condCode}) {\nbreak;\n}`);
230
236
  }
231
237
  descriptors.push({
232
238
  beforeLoop: [declare, `${counterName}.set(${start});`],
@@ -241,34 +247,31 @@ class ValueBodyTranspiler {
241
247
  throw new Error("ValueBodyTranspiler FOR internal error");
242
248
  }
243
249
  chunk.appendString("await (async () => {\n");
244
- this.appendBlocks(chunk, preLoopDecls, "");
250
+ this.appendBlocks(chunk, preLoopDecls);
245
251
  chunk.appendString(`const VAL = ${val};\n`);
246
- let indent = "";
252
+ // no manual indentation, Chunk.runIndentationLogic indents by brace depth,
253
+ // pre-indented closing braces would not be recognized and drift the indentation
247
254
  for (const desc of descriptors) {
248
- this.appendBlocks(chunk, desc.beforeLoop, indent);
249
- chunk.appendString(indent + desc.open + "\n");
250
- indent += " ";
251
- levelIndents.push(indent);
252
- this.appendBlocks(chunk, desc.preBody, indent);
255
+ this.appendBlocks(chunk, desc.beforeLoop);
256
+ chunk.appendString(desc.open + "\n");
257
+ this.appendBlocks(chunk, desc.preBody);
253
258
  }
254
- chunk.appendString(indent + "VAL");
259
+ chunk.appendString("VAL");
255
260
  let post = ";\n";
256
261
  for (let i = descriptors.length - 1; i >= 0; i--) {
257
262
  const desc = descriptors[i];
258
- const currentIndent = levelIndents[i] ?? "";
259
- post += this.blocksToString(desc.postBody, currentIndent);
260
- const parentIndent = currentIndent.substring(0, Math.max(0, currentIndent.length - 2));
261
- post += parentIndent + desc.close + "\n";
263
+ post += this.blocksToString(desc.postBody);
264
+ post += desc.close + "\n";
262
265
  }
263
266
  post += "return VAL;\n})()";
264
267
  return { chunk, post };
265
268
  }
266
- appendBlocks(chunk, blocks, indent) {
269
+ appendBlocks(chunk, blocks) {
267
270
  for (const block of blocks) {
268
- this.appendBlock(chunk, block, indent);
271
+ this.appendBlock(chunk, block);
269
272
  }
270
273
  }
271
- appendBlock(chunk, block, indent) {
274
+ appendBlock(chunk, block) {
272
275
  if (block === "") {
273
276
  return;
274
277
  }
@@ -278,10 +281,10 @@ class ValueBodyTranspiler {
278
281
  if (clean.trim() === "") {
279
282
  continue;
280
283
  }
281
- chunk.appendString(indent + clean + "\n");
284
+ chunk.appendString(clean + "\n");
282
285
  }
283
286
  }
284
- blocksToString(blocks, indent) {
287
+ blocksToString(blocks) {
285
288
  let ret = "";
286
289
  for (const block of blocks) {
287
290
  if (block === "") {
@@ -293,7 +296,7 @@ class ValueBodyTranspiler {
293
296
  if (clean.trim() === "") {
294
297
  continue;
295
298
  }
296
- ret += indent + clean + "\n";
299
+ ret += clean + "\n";
297
300
  }
298
301
  }
299
302
  return ret;
@@ -43,15 +43,16 @@ class AssignTranspiler {
43
43
  if (node?.findFirstExpression(abaplint.Expressions.TableExpression) === undefined) {
44
44
  return code;
45
45
  }
46
+ // not indented, Chunk.runIndentationLogic indents by brace depth
46
47
  return `(await (async () => {
47
- try {
48
- return ${code};
49
- } catch (error) {
50
- if (abap.isLineNotFound(error)) {
51
- return undefined;
52
- }
53
- throw error;
54
- }
48
+ try {
49
+ return ${code};
50
+ } catch (error) {
51
+ if (abap.isLineNotFound(error)) {
52
+ return undefined;
53
+ }
54
+ throw error;
55
+ }
55
56
  })())`;
56
57
  }
57
58
  transpile(node, traversal) {
@@ -145,7 +145,7 @@ class CallTranspiler {
145
145
  post += `\nabap.builtin.sy.get().subrc.set(0);
146
146
  } catch (e) {
147
147
  if (e.classic) {
148
- switch (e.classic.toUpperCase()) {\n`;
148
+ switch (e.classic.toUpperCase()) {\n`;
149
149
  for (const e of node.findAllExpressions(abaplint.Expressions.ParameterException)) {
150
150
  const name = e.getFirstToken().getStr().toUpperCase();
151
151
  // todo, also handle SimpleChain,
@@ -164,9 +164,9 @@ if (e.classic) {
164
164
  post += `case "${name}": abap.builtin.sy.get().subrc.set(${value}); break;\n`;
165
165
  }
166
166
  }
167
- post += ` }
167
+ post += `}
168
168
  } else {
169
- throw e;
169
+ throw e;
170
170
  }
171
171
  }`;
172
172
  return { pre, post };
@@ -65,12 +65,13 @@ class CallFunctionTranspiler {
65
65
  }
66
66
  const calling = node.findExpressionAfterToken("CALLING");
67
67
  const dest = node.findDirectExpression(abaplint.Expressions.Destination)?.findDirectExpression(abaplint.Expressions.Source);
68
- if (dest) {
68
+ const updateTask = node.findDirectTokenByText("UPDATE") !== undefined;
69
+ if (dest && updateTask === false) {
69
70
  const s = new expressions_1.SourceTranspiler(true).transpile(dest, traversal);
70
71
  param = param.replace("{", ",").replace(/}$/, "");
71
72
  ret.appendString(`await abap.statements.callFunction({name:${fmname},destination:${s.getCode()}${param}});`);
72
73
  }
73
- else if (calling) {
74
+ else if (calling && updateTask === false) {
74
75
  param = param.replace("{", ",").replace(/}$/, "");
75
76
  // typically used in combination with STARTING NEW TASK so dont await,
76
77
  ret.appendString(`abap.statements.callFunction({name:${fmname},calling:this.${calling.concatTokens().toLowerCase()}${param}});`);
@@ -80,7 +81,13 @@ class CallFunctionTranspiler {
80
81
  const call = `abap.FunctionModules[${fmname}]`;
81
82
  // eslint-disable-next-line max-len
82
83
  ret.appendString(`if (${call} === undefined) { if (${illegalFunc} === undefined) { throw "CX_SY_DYN_CALL_ILLEGAL_FUNC not found"; } else { throw await new ${illegalFunc}().constructor_({function: new abap.types.String().set(${fmname})});} }\n`);
83
- ret.appendString(`await ${call}(${param});`);
84
+ if (updateTask) {
85
+ param = param.replace("{", ",").replace(/}$/, "");
86
+ ret.appendString(`await abap.statements.callFunction({name:${fmname},updateTask:true${param}});`);
87
+ }
88
+ else {
89
+ ret.appendString(`await ${call}(${param});`);
90
+ }
84
91
  }
85
92
  if (exceptions) {
86
93
  const build = call_1.CallTranspiler.buildExceptions(exceptions);
@@ -83,7 +83,7 @@ class CreateDataTranspiler {
83
83
  }
84
84
  const handle = node.findExpressionAfterToken("HANDLE");
85
85
  if (handle) {
86
- const so = traversal.traverse(node.findDirectExpression(abaplint.Expressions.Source));
86
+ const so = traversal.traverse(handle);
87
87
  const lookup = traversal.lookupClassOrInterface("KERNEL_CREATE_DATA_HANDLE", node.getFirstToken());
88
88
  const call = `await ${lookup}.call({handle: ${so.getCode()}, dref: ${target.getCode()}});`;
89
89
  return new chunk_1.Chunk().append(`if (${lookup} === undefined) throw new Error("CreateData, kernel class missing");\n${call}`, node, traversal);
@@ -3,5 +3,7 @@ import { IStatementTranspiler } from "./_statement_transpiler";
3
3
  import { Traversal } from "../traversal";
4
4
  import { Chunk } from "../chunk";
5
5
  export declare class GenerateSubroutineTranspiler implements IStatementTranspiler {
6
- transpile(_node: abaplint.Nodes.StatementNode, _traversal: Traversal): Chunk;
6
+ transpile(node: abaplint.Nodes.StatementNode, traversal: Traversal): Chunk;
7
+ /** the expression directly after the addition keyword, MESSAGE does not match MESSAGE-ID */
8
+ private findAddition;
7
9
  }
@@ -1,10 +1,80 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  Object.defineProperty(exports, "__esModule", { value: true });
3
36
  exports.GenerateSubroutineTranspiler = void 0;
37
+ const abaplint = __importStar(require("@abaplint/core"));
4
38
  const chunk_1 = require("../chunk");
39
+ // GENERATE SUBROUTINE POOL cannot be supported, there is no compiler at runtime.
40
+ // The statement is refused without an exception: sy-subrc = 8 (documented as "other
41
+ // generation error"), NAME initial, MESSAGE filled, LINE 0 and WORD initial. This is
42
+ // a refusal of its own, not what a system answers; the other additions are left untouched.
5
43
  class GenerateSubroutineTranspiler {
6
- transpile(_node, _traversal) {
7
- return new chunk_1.Chunk(`throw new Error("GenerateSubroutine, not supported, transpiler");`);
44
+ transpile(node, traversal) {
45
+ const ret = new chunk_1.Chunk();
46
+ const name = this.findAddition(node, "NAME");
47
+ if (name) {
48
+ ret.appendString(traversal.traverse(name).getCode() + ".clear();\n");
49
+ }
50
+ const message = this.findAddition(node, "MESSAGE");
51
+ if (message) {
52
+ ret.appendString(traversal.traverse(message).getCode() + `.set("GENERATE SUBROUTINE POOL is not supported");\n`);
53
+ }
54
+ const line = this.findAddition(node, "LINE");
55
+ if (line) {
56
+ ret.appendString(traversal.traverse(line).getCode() + ".set(0);\n");
57
+ }
58
+ const word = this.findAddition(node, "WORD");
59
+ if (word) {
60
+ ret.appendString(traversal.traverse(word).getCode() + ".clear();\n");
61
+ }
62
+ ret.append("abap.builtin.sy.get().subrc.set(8);", node, traversal);
63
+ return ret;
64
+ }
65
+ /** the expression directly after the addition keyword, MESSAGE does not match MESSAGE-ID */
66
+ findAddition(node, keyword) {
67
+ const children = node.getChildren();
68
+ for (let i = 0; i < children.length - 1; i++) {
69
+ const child = children[i];
70
+ const next = children[i + 1];
71
+ if (child instanceof abaplint.Nodes.TokenNode
72
+ && child.getFirstToken().getStr().toUpperCase() === keyword
73
+ && next instanceof abaplint.Nodes.ExpressionNode) {
74
+ return next;
75
+ }
76
+ }
77
+ return undefined;
8
78
  }
9
79
  }
10
80
  exports.GenerateSubroutineTranspiler = GenerateSubroutineTranspiler;
@@ -4,4 +4,6 @@ import { Traversal } from "../traversal";
4
4
  import { Chunk } from "../chunk";
5
5
  export declare class MoveTranspiler implements IStatementTranspiler {
6
6
  transpile(node: abaplint.Nodes.StatementNode, traversal: Traversal): Chunk;
7
+ private isInt8Calculation;
8
+ private integerOperands;
7
9
  }
@@ -36,6 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.MoveTranspiler = void 0;
37
37
  const abaplint = __importStar(require("@abaplint/core"));
38
38
  const chunk_1 = require("../chunk");
39
+ const source_1 = require("../expressions/source");
39
40
  class MoveTranspiler {
40
41
  transpile(node, traversal) {
41
42
  const sourceExpression = node.findDirectExpression(abaplint.Expressions.Source);
@@ -55,7 +56,9 @@ class MoveTranspiler {
55
56
  ret.appendString(target + `.set(abap.alphaIn(${tSource.getCode()}, ${target}, ${target}));`);
56
57
  return ret;
57
58
  }
58
- let source = traversal.traverse(sourceExpression);
59
+ let source = this.isInt8Calculation(node, sourceExpression, targetExpressions.length, traversal)
60
+ ? new source_1.SourceTranspiler(false, true).transpile(sourceExpression, traversal)
61
+ : traversal.traverse(sourceExpression);
59
62
  const second = node.getChildren()[1]?.concatTokens();
60
63
  switch (second) {
61
64
  case "?=":
@@ -117,6 +120,72 @@ class MoveTranspiler {
117
120
  }
118
121
  return ret;
119
122
  }
123
+ // The calculation type of an arithmetic expression comes from its operands and the target
124
+ // field, the type with the largest range wins. With operands of type i or int8 it is int8
125
+ // when the target or an operand is int8: i * i into int8 is exact up to 2^62, and "/"
126
+ // rounds. The runtime decides by the operands alone and does not know the target, so here
127
+ // the operands are converted to int8 before the calculation. Only for an i or int8 target
128
+ // and operands whose type is known to be i or int8, and not for **, which counts as f
129
+ isInt8Calculation(node, source, targets, traversal) {
130
+ if (source === undefined
131
+ || targets !== 1
132
+ || source.findFirstExpression(abaplint.Expressions.ArithOperator) === undefined) {
133
+ return false;
134
+ }
135
+ const scope = traversal.findCurrentScopeByToken(node.getFirstToken());
136
+ const target = traversal.determineType(node, scope);
137
+ const found = { int8: target instanceof abaplint.BasicTypes.Integer8Type };
138
+ if (found.int8 === false && !(target instanceof abaplint.BasicTypes.IntegerType)) {
139
+ return false;
140
+ }
141
+ return this.integerOperands(source, scope, found) && found.int8;
142
+ }
143
+ integerOperands(node, scope, found) {
144
+ for (const c of node.getChildren()) {
145
+ if (c instanceof abaplint.Nodes.TokenNode) {
146
+ if (["(", ")", "-", "+"].includes(c.getFirstToken().getStr()) === false) {
147
+ return false;
148
+ }
149
+ }
150
+ else if (c.get() instanceof abaplint.Expressions.Source) {
151
+ if (this.integerOperands(c, scope, found) === false) {
152
+ return false;
153
+ }
154
+ }
155
+ else if (c.get() instanceof abaplint.Expressions.ArithOperator) {
156
+ if (["+", "-", "*", "/", "DIV", "MOD"].includes(c.concatTokens().toUpperCase()) === false) {
157
+ return false;
158
+ }
159
+ }
160
+ else if (c.get() instanceof abaplint.Expressions.Constant) {
161
+ const integer = c.findDirectExpression(abaplint.Expressions.Integer);
162
+ if (integer === undefined) {
163
+ return false;
164
+ }
165
+ else if (Math.abs(parseInt(integer.concatTokens().replace(/ /g, ""), 10)) > 2147483647) {
166
+ // the transpiler makes an int8 of a literal outside the range of i
167
+ found.int8 = true;
168
+ }
169
+ }
170
+ else if (c.get() instanceof abaplint.Expressions.FieldChain) {
171
+ const children = c.getChildren();
172
+ if (children.length !== 1) {
173
+ return false;
174
+ }
175
+ const type = scope?.findVariable(children[0].concatTokens())?.getType();
176
+ if (!(type instanceof abaplint.BasicTypes.IntegerType) && !(type instanceof abaplint.BasicTypes.Integer8Type)) {
177
+ return false;
178
+ }
179
+ if (type instanceof abaplint.BasicTypes.Integer8Type) {
180
+ found.int8 = true;
181
+ }
182
+ }
183
+ else {
184
+ return false;
185
+ }
186
+ }
187
+ return true;
188
+ }
120
189
  }
121
190
  exports.MoveTranspiler = MoveTranspiler;
122
191
  //# sourceMappingURL=move.js.map
@@ -44,16 +44,18 @@ class PerformTranspiler {
44
44
  return new chunk_1.Chunk(`throw new Error("PerformTranspiler FormName not found");`);
45
45
  }
46
46
  else if (node.concatTokens().toUpperCase().includes(" IN PROGRAM ")) {
47
- // todo: throw exception if not found?
48
47
  const expression = node.findExpressionAfterToken("PROGRAM");
49
48
  let ref = "";
49
+ let program = "";
50
50
  if (expression?.get() instanceof abaplint.Expressions.Dynamic) {
51
51
  const name = expression.getChildren()[1].concatTokens() + ".get().trimEnd()";
52
52
  ref = `abap.Forms['PROG-' + ${name} + '-${formName.concatTokens().toUpperCase()}']`;
53
+ program = `'PROG-' + ${name} + '-'`;
53
54
  }
54
55
  else {
55
56
  const progName = expression?.concatTokens().toUpperCase();
56
57
  ref = `abap.Forms['PROG-${progName}-${formName.concatTokens().toUpperCase()}']`;
58
+ program = `'PROG-${progName}-'`;
57
59
  }
58
60
  const params = [];
59
61
  // hacky hack
@@ -65,6 +67,16 @@ class PerformTranspiler {
65
67
  if (node.concatTokens().toUpperCase().includes(" IF FOUND")) {
66
68
  call = `if (${ref} !== undefined) { ${call} }`;
67
69
  }
70
+ else {
71
+ // the form is missing from a program whose forms are registered: CX_SY_DYN_CALL_ILLEGAL_FORM,
72
+ // no form of the program is registered: CX_SY_PROGRAM_NOT_FOUND
73
+ const raise = (name) => {
74
+ const cls = traversal.lookupClassOrInterface(`'${name}'`, node.getFirstToken(), true);
75
+ return `if (${cls} === undefined) { throw "${name} not found"; } else { throw await new ${cls}().constructor_(); }`;
76
+ };
77
+ const loaded = `Object.keys(abap.Forms).some(k => k.startsWith(${program}))`;
78
+ call = `if (${ref} === undefined) { if (${loaded}) { ${raise("CX_SY_DYN_CALL_ILLEGAL_FORM")} } else { ${raise("CX_SY_PROGRAM_NOT_FOUND")} } }\n` + call;
79
+ }
68
80
  return new chunk_1.Chunk(call);
69
81
  }
70
82
  else {
@@ -184,24 +184,25 @@ class SelectTranspiler {
184
184
  // DB key alone is wrong for projections (INTO CORRESPONDING FIELDS
185
185
  // without the key fields crashed on the missing component names)
186
186
  const by = `Object.keys(${target}.getRowType().get())`;
187
+ // generated code is not indented, Chunk.runIndentationLogic indents by brace depth
187
188
  const dedup = `if (!(${target} instanceof abap.types.HashedTable) && ${target}.getOptions()?.primaryKey?.type !== "SORTED") {
188
- abap.statements.sort(${target}, {by: ${by}.map(k => { return {component: k}; })});
189
- await abap.statements.deleteInternal(${target}, {adjacent: true, allFields: true});
190
- }`;
189
+ abap.statements.sort(${target}, {by: ${by}.map(k => { return {component: k}; })});
190
+ await abap.statements.deleteInternal(${target}, {adjacent: true, allFields: true});
191
+ }`;
191
192
  const at = where.startsWith("WHERE ") ? select.indexOf(where) : -1;
192
193
  // UP TO with ORDER BY keeps the first n rows in that order; the blocks
193
194
  // trim after the de-duplicating sort, so that pair stays row by row
194
195
  if (concat.startsWith("SELECT SINGLE ") || at < 0 || (upTo && orderBy)) {
195
196
  const code = `if (${faeTranspiled}.array().length === 0) {
196
- await abap.statements.select(${target}, {select: "${selectEmpty.trim()}"${extra}});
197
+ await abap.statements.select(${target}, {select: "${selectEmpty.trim()}"${extra}});
197
198
  } else {
198
- const ${unique2} = ${faeTranspiled}.array();
199
- ${target}.clear();
200
- for await (const ${unique} of ${unique2}) {
201
- await abap.statements.select(${target}, {select: "${select.trim()}"${extra}}, {appending: true});
202
- }
203
- ${dedup}
204
- abap.builtin.sy.get().dbcnt.set(${target}.getArrayLength());
199
+ const ${unique2} = ${faeTranspiled}.array();
200
+ ${target}.clear();
201
+ for await (const ${unique} of ${unique2}) {
202
+ await abap.statements.select(${target}, {select: "${select.trim()}"${extra}}, {appending: true});
203
+ }
204
+ ${dedup}
205
+ abap.builtin.sy.get().dbcnt.set(${target}.getArrayLength());
205
206
  }`;
206
207
  return new chunk_1.Chunk().append(code, node, traversal);
207
208
  }
@@ -229,29 +230,29 @@ class SelectTranspiler {
229
230
  const unique3 = unique_identifier_1.UniqueIdentifier.get();
230
231
  const unique4 = unique_identifier_1.UniqueIdentifier.get();
231
232
  const code = `if (${faeTranspiled}.array().length === 0) {
232
- await abap.statements.select(${target}, {select: "${selectEmpty.trim()}"${extra}});
233
+ await abap.statements.select(${target}, {select: "${selectEmpty.trim()}"${extra}});
234
+ } else {
235
+ const ${unique2} = ${faeTranspiled}.array();
236
+ ${target}.clear();
237
+ const ${unique3} = (${unique}) => "(${condition})";
238
+ for (let ${unique4} = 0; ${unique4} < ${unique2}.length; ${unique4} += 50) {
239
+ const ${unique4}where = ${unique2}.slice(${unique4}, ${unique4} + 50).map(${unique3}).join(" OR ");
240
+ await abap.statements.select(${target}, {select: "${head}WHERE " + ${unique4}where + " ${tail.trim()}"${extra}}, {appending: true});
241
+ }
242
+ ${dedup}
243
+ const ${unique4}max = ${upToCode};
244
+ if (${unique4}max > 0 && ${target}.getArrayLength() > ${unique4}max) {
245
+ if (${target} instanceof abap.types.HashedTable) {
246
+ for (const ${unique4}row of ${target}.array().slice(${unique4}max)) {
247
+ await abap.statements.deleteInternal(${target}, {fromValue: ${unique4}row});
248
+ }
233
249
  } else {
234
- const ${unique2} = ${faeTranspiled}.array();
235
- ${target}.clear();
236
- const ${unique3} = (${unique}) => "(${condition})";
237
- for (let ${unique4} = 0; ${unique4} < ${unique2}.length; ${unique4} += 50) {
238
- const ${unique4}where = ${unique2}.slice(${unique4}, ${unique4} + 50).map(${unique3}).join(" OR ");
239
- await abap.statements.select(${target}, {select: "${head}WHERE " + ${unique4}where + " ${tail.trim()}"${extra}}, {appending: true});
240
- }
241
- ${dedup}
242
- const ${unique4}max = ${upToCode};
243
- if (${unique4}max > 0 && ${target}.getArrayLength() > ${unique4}max) {
244
- if (${target} instanceof abap.types.HashedTable) {
245
- for (const ${unique4}row of ${target}.array().slice(${unique4}max)) {
246
- await abap.statements.deleteInternal(${target}, {fromValue: ${unique4}row});
247
- }
248
- } else {
249
- while (${target}.getArrayLength() > ${unique4}max) {
250
- ${target}.deleteIndex(${target}.getArrayLength() - 1);
251
- }
252
- }
253
- }
254
- abap.builtin.sy.get().dbcnt.set(${target}.getArrayLength());
250
+ while (${target}.getArrayLength() > ${unique4}max) {
251
+ ${target}.deleteIndex(${target}.getArrayLength() - 1);
252
+ }
253
+ }
254
+ }
255
+ abap.builtin.sy.get().dbcnt.set(${target}.getArrayLength());
255
256
  }`;
256
257
  return new chunk_1.Chunk().append(code, node, traversal);
257
258
  }
@@ -4,5 +4,7 @@ import { Traversal } from "../traversal";
4
4
  import { Chunk } from "../chunk";
5
5
  export declare class SortTranspiler implements IStatementTranspiler {
6
6
  transpile(node: abaplint.Nodes.StatementNode, traversal: Traversal): Chunk;
7
+ private isDynamicName;
8
+ private dynamicName;
7
9
  private findNextText;
8
10
  }
@@ -40,7 +40,8 @@ const expressions_1 = require("../expressions");
40
40
  class SortTranspiler {
41
41
  transpile(node, traversal) {
42
42
  const concat = node.concatTokens().toUpperCase();
43
- const components = node.findDirectExpressions(abaplint.Expressions.ComponentChain);
43
+ const components = node.getChildren().filter(c => c instanceof abaplint.Nodes.ExpressionNode
44
+ && (c.get() instanceof abaplint.Expressions.ComponentChain || this.isDynamicName(c, traversal)));
44
45
  const options = [];
45
46
  if (concat.includes(" BY ") === false) {
46
47
  const descending = node.findDirectTokenByText("DESCENDING") !== undefined;
@@ -52,12 +53,14 @@ class SortTranspiler {
52
53
  const by = [];
53
54
  for (const c of components) {
54
55
  const next = this.findNextText(c, node);
55
- const concat = expressions_1.ComponentChainTranspiler.concat(c, traversal);
56
+ const component = c.get() instanceof abaplint.Expressions.Dynamic
57
+ ? this.dynamicName(c, traversal)
58
+ : `"${expressions_1.ComponentChainTranspiler.concat(c, traversal)}"`;
56
59
  if (next === "DESCENDING") {
57
- by.push(`{component: "${concat}", descending: true}`);
60
+ by.push(`{component: ${component}, descending: true}`);
58
61
  }
59
62
  else {
60
- by.push(`{component: "${concat}"}`);
63
+ by.push(`{component: ${component}}`);
61
64
  }
62
65
  }
63
66
  options.push(`by: [${by.join(",")}]`);
@@ -65,6 +68,32 @@ class SortTranspiler {
65
68
  const target = traversal.traverse(node.findDirectExpression(abaplint.Expressions.Target)).getCode();
66
69
  return new chunk_1.Chunk().append("abap.statements.sort(" + target + ",{" + options.join(",") + "});", node, traversal);
67
70
  }
71
+ // "BY (name)" with the component name in a literal or a character-like field; a sort order table
72
+ // (abap_sortorder_tab) is not handled here
73
+ isDynamicName(c, traversal) {
74
+ if (!(c.get() instanceof abaplint.Expressions.Dynamic)) {
75
+ return false;
76
+ }
77
+ if (c.findFirstExpression(abaplint.Expressions.ConstantString)) {
78
+ return true;
79
+ }
80
+ const field = c.findFirstExpression(abaplint.Expressions.FieldChain);
81
+ if (field === undefined) {
82
+ return false;
83
+ }
84
+ const token = field.getFirstToken();
85
+ const type = traversal.findCurrentScopeByToken(token)?.findVariable(token.getStr())?.getType();
86
+ return !(type instanceof abaplint.BasicTypes.TableType);
87
+ }
88
+ dynamicName(c, traversal) {
89
+ const constant = c.findFirstExpression(abaplint.Expressions.ConstantString);
90
+ if (constant) {
91
+ const str = constant.getFirstToken().getStr();
92
+ return JSON.stringify(str.substring(1, str.length - 1).toLowerCase().trimEnd());
93
+ }
94
+ const field = c.findFirstExpression(abaplint.Expressions.FieldChain);
95
+ return new expressions_1.FieldChainTranspiler(true).transpile(field, traversal).getCode() + ".toLowerCase().trimEnd()";
96
+ }
68
97
  findNextText(c, parent) {
69
98
  const children = parent.getChildren();
70
99
  for (let i = 0; i < children.length; i++) {
@@ -52,17 +52,18 @@ class ClassDefinitionTranspiler {
52
52
  }
53
53
  else {
54
54
  // its an abstract class with only abstract methods
55
+ // not indented, Chunk.runIndentationLogic indents by brace depth
55
56
  return new chunk_1.Chunk(`
56
57
  class ${className?.toLowerCase()} {
57
- static INTERNAL_TYPE = 'CLAS';
58
- static IMPLEMENTED_INTERFACES = [];
59
- static INTERNAL_NAME = 'ABSTRACT_CLASS_INTERNAL_NAME';
60
- static ATTRIBUTES = {};
61
- async constructor_() {
62
- this.me = new abap.types.ABAPObject();
63
- this.me.set(this);
64
- return this;
65
- }
58
+ static INTERNAL_TYPE = 'CLAS';
59
+ static IMPLEMENTED_INTERFACES = [];
60
+ static INTERNAL_NAME = 'ABSTRACT_CLASS_INTERNAL_NAME';
61
+ static ATTRIBUTES = {};
62
+ async constructor_() {
63
+ this.me = new abap.types.ABAPObject();
64
+ this.me.set(this);
65
+ return this;
66
+ }
66
67
  }`);
67
68
  }
68
69
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abaplint/transpiler",
3
- "version": "2.13.96",
3
+ "version": "2.13.98",
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.120.64",
32
+ "@abaplint/core": "^2.120.66",
33
33
  "source-map": "^0.8.0"
34
34
  },
35
35
  "devDependencies": {