@abaplint/transpiler
Version:
292 lines • 15.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ValueBodyTranspiler = void 0;
const core_1 = require("@abaplint/core");
const traversal_1 = require("../traversal");
const chunk_1 = require("../chunk");
const type_name_or_infer_1 = require("./type_name_or_infer");
const value_body_line_1 = require("./value_body_line");
const field_assignment_1 = require("./field_assignment");
const statements_1 = require("../statements");
const source_field_symbol_1 = require("./source_field_symbol");
const transpile_types_1 = require("../transpile_types");
const let_1 = require("./let");
class ValueBodyTranspiler {
transpile(typ, body, traversal) {
if (!(typ.get() instanceof core_1.Expressions.TypeNameOrInfer)) {
throw new Error("ValueBodyTranspiler, Expected TypeNameOrInfer");
}
let ret = new chunk_1.Chunk().appendString(new type_name_or_infer_1.TypeNameOrInfer().transpile(typ, traversal).getCode());
const context = new type_name_or_infer_1.TypeNameOrInfer().findType(typ, traversal);
if (context instanceof core_1.BasicTypes.VoidType || context instanceof core_1.BasicTypes.UnknownType) {
// compile option is runtime error, or it failed during the validation step
return new chunk_1.Chunk(transpile_types_1.TranspileTypes.toType(context));
}
let post = "";
let extraFields = "";
const hasLines = body.findDirectExpression(core_1.Expressions.ValueBodyLine) !== undefined;
const children = body.getChildren();
for (let i = 0; i < children.length; i++) {
const child = children[i];
if (child.get() instanceof core_1.Expressions.FieldAssignment && child instanceof core_1.Nodes.ExpressionNode) {
const transpiled = new field_assignment_1.FieldAssignmentTranspiler().transpile(child, traversal, context).getCode();
if (hasLines === false) {
ret.appendString(transpiled);
}
else {
extraFields += transpiled;
}
}
else if (child.get() instanceof core_1.Expressions.ValueBase && child instanceof core_1.Nodes.ExpressionNode) {
const source = traversal.traverse(child.findDirectExpression(core_1.Expressions.Source));
ret = new chunk_1.Chunk().appendString(source.getCode() + ".clone()");
}
else if (child.get() instanceof core_1.Expressions.ValueBodyLine && child instanceof core_1.Nodes.ExpressionNode) {
if (!(context instanceof core_1.BasicTypes.TableType)) {
throw new Error("ValueBodyTranspiler, Expected BasicTypes, " + body.concatTokens());
}
const rowType = context.getRowType();
ret.appendString(new value_body_line_1.ValueBodyLineTranspiler().transpile(rowType, child, traversal, extraFields).getCode());
}
else if (child.get() instanceof core_1.Expressions.Source && child instanceof core_1.Nodes.ExpressionNode) {
const source = traversal.traverse(child);
ret.appendString(".set(" + source.getCode() + ".clone())");
}
else if (child.get() instanceof core_1.Expressions.For && child instanceof core_1.Nodes.ExpressionNode) {
const forNodes = [];
let idx = i;
while (idx < children.length) {
const candidate = children[idx];
if (candidate.get() instanceof core_1.Expressions.For && candidate instanceof core_1.Nodes.ExpressionNode) {
forNodes.push(candidate);
idx++;
}
else {
break;
}
}
i = idx - 1;
const result = this.buildForChain(forNodes, typ, traversal, body);
ret = result.chunk;
post = result.post;
}
else if (child instanceof core_1.Nodes.TokenNode && child.getFirstToken().getStr().toUpperCase() === "DEFAULT") {
// note: this is last in the body, so its okay to prepend and postpend
const sources = body.findDirectExpressions(core_1.Expressions.Source);
const deflt = traversal.traverse(sources[1]).getCode();
const pre = `(await (async () => { try { return `;
ret = new chunk_1.Chunk().appendString(pre + ret.getCode());
post += `; } catch (error) { if (abap.isLineNotFound(error)) { return ${deflt}; } throw error; } })())`;
}
else if (child instanceof core_1.Nodes.TokenNode && child.getFirstToken().getStr().toUpperCase() === "OPTIONAL") {
// note: this is last in the body, so its okay to prepend and postpend
const pre = `(await (async () => { try { return `;
ret = new chunk_1.Chunk().appendString(pre + ret.getCode());
post += `; } catch (error) { if (abap.isLineNotFound(error)) { return ${transpile_types_1.TranspileTypes.toType(context)}; } throw error; } })())`;
}
else {
throw new Error("ValueBodyTranspiler, unknown " + child.get().constructor.name + " \"" + child.concatTokens()) + "\"";
}
}
return ret.appendString(post);
}
buildForChain(forNodes, typ, traversal, body) {
const val = new type_name_or_infer_1.TypeNameOrInfer().transpile(typ, traversal).getCode();
const chunk = new chunk_1.Chunk();
const preLoopDecls = [];
const descriptors = [];
const levelIndents = [];
let uniqueCounter = 1;
for (const child of forNodes) {
const loop = child.findDirectExpression(core_1.Expressions.InlineLoopDefinition);
if (loop) {
if (["THEN", "UNTIL", "WHILE", "FROM", "TO", "GROUPS"].some(token => child.findDirectTokenByText(token))) {
throw new Error("ValueBody FOR todo, " + body.concatTokens());
}
let loopWhere = "";
const whereNode = child.findDirectExpression(core_1.Expressions.ComponentCond);
if (whereNode) {
const where = traversal.traverse(whereNode).getCode();
loopWhere = `, {"where": async ` + where + `}`;
}
const base = loop.findDirectExpression(core_1.Expressions.ValueBase);
if (base) {
throw new Error("ValueBody FOR todo, base, " + body.concatTokens());
}
let targetDeclare = "";
let targetAction = "";
const fs = loop.findDirectExpression(core_1.Expressions.TargetFieldSymbol);
const uniqueName = `unique${uniqueCounter++}`;
if (fs) {
targetDeclare = new statements_1.FieldSymbolTranspiler().transpile(fs, traversal).getCode();
const targetName = new source_field_symbol_1.SourceFieldSymbolTranspiler().transpile(fs, traversal).getCode();
targetAction = `${targetName}.assign(${uniqueName});`;
}
else {
const field = traversal.traverse(loop.findDirectExpression(core_1.Expressions.TargetField));
if (field === undefined) {
throw new Error("ValueBody FOR empty field todo, " + body.concatTokens());
}
targetAction = `const ${field.getCode()} = ${uniqueName}.clone();`;
}
const llet = child.findDirectExpression(core_1.Expressions.Let);
if (llet) {
targetAction += new let_1.LetTranspiler().transpile(llet, traversal).getCode();
}
const preBodyStatements = [];
if (targetAction !== "") {
preBodyStatements.push(targetAction);
}
const source = traversal.traverse(loop.findDirectExpression(core_1.Expressions.Source)).getCode();
if (targetDeclare !== "") {
preLoopDecls.push(targetDeclare);
}
const descriptor = {
beforeLoop: [],
open: `for await (const ${uniqueName} of abap.statements.loop(${source}${loopWhere})) {`,
preBody: preBodyStatements,
postBody: [],
close: "}",
};
const loopTokens = loop.concatTokens().toUpperCase();
const indexTarget = loopTokens.includes("INDEX INTO") ? loop.findExpressionAfterToken("INTO") : undefined;
if (indexTarget && indexTarget instanceof core_1.Nodes.ExpressionNode) {
const indexCode = traversal.traverse(indexTarget).getCode();
const counterName = `unique${uniqueCounter++}`;
descriptor.beforeLoop.push(`let ${counterName} = 1;`);
descriptor.preBody.push(`const ${indexCode} = new abap.types.Integer().set(${counterName});`);
descriptor.postBody.push(`${counterName}++;`);
}
descriptors.push(descriptor);
}
else {
const counter = child.findDirectExpression(core_1.Expressions.InlineFieldDefinition);
if (counter === undefined) {
throw new Error("ValueBody FOR todo, " + body.concatTokens());
}
if (["GROUPS", "FROM", "TO"].some(token => child.findDirectTokenByText(token))) {
throw new Error("ValueBody FOR todo, " + body.concatTokens());
}
if (child.findDirectExpression(core_1.Expressions.ComponentCond)) {
throw new Error("ValueBody FOR todo, component cond, " + body.concatTokens());
}
const cond = child.findDirectExpression(core_1.Expressions.Cond);
if (cond === undefined) {
throw new Error("ValueBody FOR missing condition, " + body.concatTokens());
}
const hasUntil = child.findDirectTokenByText("UNTIL") !== undefined;
const hasWhile = child.findDirectTokenByText("WHILE") !== undefined;
if ((hasUntil ? 1 : 0) + (hasWhile ? 1 : 0) !== 1) {
throw new Error("ValueBody FOR todo, condition, " + body.concatTokens());
}
const fieldExpr = counter.findDirectExpression(core_1.Expressions.Field);
const fieldName = fieldExpr?.concatTokens().toLowerCase();
if (fieldName === undefined) {
throw new Error("ValueBody FOR todo, inline field, " + body.concatTokens());
}
const scope = traversal.findCurrentScopeByToken(counter.getFirstToken());
const variable = scope?.findVariable(fieldName);
if (variable === undefined) {
throw new Error("ValueBody FOR todo, variable, " + body.concatTokens());
}
const declare = transpile_types_1.TranspileTypes.declare(variable);
const counterName = traversal_1.Traversal.prefixVariable(fieldName);
const startSource = counter.findDirectExpression(core_1.Expressions.Source);
if (startSource === undefined) {
throw new Error("ValueBody FOR missing initial value, " + body.concatTokens());
}
const start = traversal.traverse(startSource).getCode();
const thenExpr = child.findExpressionAfterToken("THEN");
let incrementExpression = "";
if (thenExpr && thenExpr instanceof core_1.Nodes.ExpressionNode) {
incrementExpression = traversal.traverse(thenExpr).getCode();
}
else {
incrementExpression = `abap.operators.add(${counterName}, new abap.types.Integer().set(1))`;
}
const incrementLine = `${counterName}.set(${incrementExpression});`;
const llet = child.findDirectExpression(core_1.Expressions.Let);
let letCode = "";
if (llet) {
letCode = new let_1.LetTranspiler().transpile(llet, traversal).getCode();
}
const condCode = traversal.traverse(cond).getCode();
const preCheck = hasWhile ? `if (!(${condCode})) {\n break;\n}` : "";
const postLoop = [];
postLoop.push(incrementLine);
if (hasUntil) {
postLoop.push(`if (${condCode}) {\n break;\n}`);
}
descriptors.push({
beforeLoop: [declare, `${counterName}.set(${start});`],
open: "while (true) {",
preBody: [preCheck, letCode].filter(s => s !== ""),
postBody: postLoop,
close: "}",
});
}
}
if (descriptors.length === 0) {
throw new Error("ValueBodyTranspiler FOR internal error");
}
chunk.appendString("await (async () => {\n");
this.appendBlocks(chunk, preLoopDecls, "");
chunk.appendString(`const VAL = ${val};\n`);
let indent = "";
for (const desc of descriptors) {
this.appendBlocks(chunk, desc.beforeLoop, indent);
chunk.appendString(indent + desc.open + "\n");
indent += " ";
levelIndents.push(indent);
this.appendBlocks(chunk, desc.preBody, indent);
}
chunk.appendString(indent + "VAL");
let post = ";\n";
for (let i = descriptors.length - 1; i >= 0; i--) {
const desc = descriptors[i];
const currentIndent = levelIndents[i] ?? "";
post += this.blocksToString(desc.postBody, currentIndent);
const parentIndent = currentIndent.substring(0, Math.max(0, currentIndent.length - 2));
post += parentIndent + desc.close + "\n";
}
post += "return VAL;\n})()";
return { chunk, post };
}
appendBlocks(chunk, blocks, indent) {
for (const block of blocks) {
this.appendBlock(chunk, block, indent);
}
}
appendBlock(chunk, block, indent) {
if (block === "") {
return;
}
const lines = block.split("\n");
for (const line of lines) {
const clean = line.replace(/\r/g, "");
if (clean.trim() === "") {
continue;
}
chunk.appendString(indent + clean + "\n");
}
}
blocksToString(blocks, indent) {
let ret = "";
for (const block of blocks) {
if (block === "") {
continue;
}
const lines = block.split("\n");
for (const line of lines) {
const clean = line.replace(/\r/g, "");
if (clean.trim() === "") {
continue;
}
ret += indent + clean + "\n";
}
}
return ret;
}
}
exports.ValueBodyTranspiler = ValueBodyTranspiler;
//# sourceMappingURL=value_body.js.map