function getCallStatement()

in pxtcompiler/emitter/decompiler.ts [1942:2269]


        function getCallStatement(node: ts.CallExpression, asExpression: boolean): StatementNode | ExpressionNode {
            const info: DecompilerCallInfo = pxtc.pxtInfo(node).callInfo
            const attributes = attrs(info);

            if (info.qName == "Math.pow") {
                const r = mkExpr("math_arithmetic", node);
                r.inputs = [
                    mkValue("A", getOutputBlock(node.arguments[0]), numberType),
                    mkValue("B", getOutputBlock(node.arguments[1]), numberType)
                ];
                r.fields = [getField("OP", "POWER")];
                return r;
            }
            else if (pxt.Util.startsWith(info.qName, "Math.")) {
                const op = info.qName.substring(5);
                if (isSupportedMathFunction(op)) {
                    let r: ExpressionNode;

                    if (isRoundingFunction(op)) {
                        r = mkExpr("math_js_round", node);
                    } else {
                        r = mkExpr("math_js_op", node);
                        let opType: string;
                        if (isUnaryMathFunction(op)) opType = "unary";
                        else if (isInfixMathFunction(op)) opType = "infix";
                        else opType = "binary";

                        r.mutation = { "op-type": opType };
                    }

                    r.inputs = info.args.map((arg, index) => mkValue("ARG" + index, getOutputBlock(arg), "math_number"))
                    r.fields = [getField("OP", op)];

                    return r;
                }
            }

            if (attributes.blockId === pxtc.PAUSE_UNTIL_TYPE) {
                const r = mkStmt(pxtc.PAUSE_UNTIL_TYPE, node);
                const lambda = node.arguments[0] as (ts.FunctionExpression | ts.ArrowFunction);

                let condition: ts.Node;

                if (lambda.body.kind === SK.Block) {
                    // We already checked to make sure the body is a single return statement
                    condition = ((lambda.body as ts.Block).statements[0] as ts.ReturnStatement).expression;
                }
                else {
                    condition = lambda.body;
                }

                r.inputs = [mkValue("PREDICATE", getOutputBlock(condition), "logic_boolean")];
                return r;
            }

            if (!attributes.blockId || !attributes.block) {
                const builtin = pxt.blocks.builtinFunctionInfo[info.qName];
                if (!builtin) {
                    const name = getVariableName(node.expression as ts.Identifier);
                    if (env.declaredFunctions[name]) {
                        let r: StatementNode;

                        let isStatement = true;

                        if (info.isExpression) {
                            const [parent] = getParent(node);
                            isStatement = parent && parent.kind === SK.ExpressionStatement;
                        }

                        r = mkStmt(isStatement ? "function_call" : "function_call_output", node);
                        if (info.args.length) {
                            r.mutationChildren = [];
                            r.inputs = [];
                            env.declaredFunctions[name].parameters.forEach((p, i) => {
                                const paramName = p.name.getText();
                                const argId = env.functionParamIds[name][paramName];
                                r.mutationChildren.push({
                                    nodeName: "arg",
                                    attributes: {
                                        name: paramName,
                                        type: p.type.getText(),
                                        id: argId
                                    }
                                });
                                const argBlock = getOutputBlock(info.args[i]);
                                const value = mkValue(argId, argBlock);
                                r.inputs.push(value);
                            });
                        }
                        r.mutation = { name };
                        return r;
                    }
                    else {
                        return getTypeScriptStatementBlock(node);
                    }
                }
                attributes.blockId = builtin.blockId;
            }

            if (attributes.imageLiteral) {
                return getImageLiteralStatement(node, info);
            }

            if (ts.isFunctionLike(info.decl)) {
                // const decl = info.decl as FunctionLikeDeclaration;
                // if (decl.parameters && decl.parameters.length === 1 && ts.isRestParameter(decl.parameters[0])) {
                //     openCallExpressionBlockWithRestParameter(node, info);
                //     return;
                // }
            }

            const args = paramList(info, env.blocks);
            const api = env.blocks.apis.byQName[info.decompilerBlockAlias || info.qName];
            const comp = pxt.blocks.compileInfo(api);

            const r = asExpression ? mkExpr(attributes.blockId, node)
                : mkStmt(attributes.blockId, node);

            const addInput = (v: ValueNode) => (r.inputs || (r.inputs = [])).push(v);
            const addField = (f: FieldNode) => (r.fields || (r.fields = [])).push(f);

            if (info.qName == "Math.max") {
                addField({
                    kind: "field",
                    name: "op",
                    value: "max"
                });
            }

            let optionalCount = 0;
            args.forEach((arg, i) => {
                let e = arg.value;
                let shadowMutation: pxt.Map<string>;
                const param = arg.param;
                const paramInfo = arg.info;

                const paramComp = comp.parameters[comp.thisParameter ? i - 1 : i];
                const paramRange = paramComp && paramComp.range;
                if (paramRange) {
                    const min = paramRange['min'];
                    const max = paramRange['max'];
                    shadowMutation = { 'min': min.toString(), 'max': max.toString() };
                }

                if (i === 0 && attributes.defaultInstance) {
                    if (e.getText() === attributes.defaultInstance) {
                        return;
                    }
                    else {
                        r.mutation = { "showing": "true" };
                    }
                }

                if (attributes.mutatePropertyEnum && i === info.args.length - 2) {
                    // Implicit in the blocks
                    return;
                }

                if (param && param.isOptional) {
                    ++optionalCount;
                }

                let shadowBlockInfo: SymbolInfo;
                if (param && param.shadowBlockId) {
                    shadowBlockInfo = blocksInfo.blocksById[param.shadowBlockId];
                }

                if (e.kind === SK.CallExpression) {
                    // Many enums have shim wrappers that need to be unwrapped if used
                    // in a parameter that is of an enum type. By default, enum parameters
                    // are dropdown fields (not value inputs) so we want to decompile the
                    // inner enum value as a field and not the shim block as a value
                    const shimCall: pxtc.CallInfo = pxtc.pxtInfo(e).callInfo;
                    const shimAttrs: CommentAttrs = shimCall && attrs(shimCall);
                    if (shimAttrs && shimAttrs.shim === "TD_ID" && paramInfo.isEnum) {
                        e = unwrapNode(shimCall.args[0]) as ts.Expression;
                    }
                }

                if (param && paramInfo && paramInfo.isEnum && e.kind === SK.Identifier) {
                    addField(getField(U.htmlEscape(param.definitionName), pxtInfo(e).commentAttrs.enumIdentity));
                    return;
                }

                if (param && param.fieldOptions && param.fieldOptions[DecompileParamKeys.DecompileArgumentAsString]) {
                    addField(getField(U.htmlEscape(param.definitionName), Util.htmlEscape(e.getText())));
                    return;
                }

                switch (e.kind) {
                    case SK.FunctionExpression:
                    case SK.ArrowFunction:
                        let expBody = (e as ArrowFunction | FunctionExpression).body;
                        const m = getDestructuringMutation(e as ArrowFunction);
                        let mustPopLocalScope = false;
                        if (m) {
                            r.mutation = m;
                        }
                        else {
                            let arrow = e as ArrowFunction;
                            const sym = blocksInfo.blocksById[attributes.blockId];
                            const paramDesc = sym.parameters[comp.thisParameter ? i - 1 : i];
                            const addDraggableInput = (arg: PropertyDesc, varName: string) => {
                                if (attributes.draggableParameters === "reporter") {
                                    addInput(mkDraggableReporterValue("HANDLER_DRAG_PARAM_" + arg.name, varName, arg.type));
                                } else {
                                    addInput(getDraggableVariableBlock("HANDLER_DRAG_PARAM_" + arg.name, varName));
                                }
                            };
                            if (arrow.parameters.length) {
                                if (attributes.optionalVariableArgs) {
                                    r.mutation = {
                                        "numargs": arrow.parameters.length.toString()
                                    };
                                    arrow.parameters.forEach((parameter, i) => {
                                        r.mutation["arg" + i] = (parameter.name as ts.Identifier).text;
                                    });
                                }
                                else {
                                    arrow.parameters.forEach((parameter, i) => {
                                        const arg = paramDesc.handlerParameters[i];
                                        if (attributes.draggableParameters) {
                                            addDraggableInput(arg, (parameter.name as ts.Identifier).text);
                                        }
                                        else {
                                            addField(getField("HANDLER_" + arg.name, (parameter.name as ts.Identifier).text));
                                        }
                                    });

                                }
                            }
                            if (attributes.draggableParameters) {
                                if (arrow.parameters.length < paramDesc.handlerParameters.length) {
                                    for (let i = arrow.parameters.length; i < paramDesc.handlerParameters.length; i++) {
                                        const arg = paramDesc.handlerParameters[i];
                                        addDraggableInput(arg, arg.name);
                                    }
                                }
                                if (attributes.draggableParameters === "reporter") {
                                    // Push the parameter descriptions onto the local scope stack
                                    // so the getStatementBlock() below knows that these parameters
                                    // should be decompiled as reporters instead of variables.
                                    env.localReporters.push(paramDesc.handlerParameters);
                                    mustPopLocalScope = true;
                                }
                            }
                        }
                        const statement = getStatementBlock(e);
                        (r.handlers || (r.handlers = [])).push({ name: "HANDLER", statement });

                        if (mustPopLocalScope) {
                            env.localReporters.pop();
                        }
                        break;
                    case SK.PropertyAccessExpression:
                        const callInfo = pxtc.pxtInfo(e).callInfo as pxtc.CallInfo;
                        const aName = U.htmlEscape(param.definitionName);
                        const argAttrs = attrs(callInfo);

                        if (shadowBlockInfo && shadowBlockInfo.attributes.shim === "TD_ID") {
                            addInput(mkValue(aName, getPropertyAccessExpression(e as PropertyAccessExpression, false, param.shadowBlockId), param.shadowBlockId, shadowMutation));
                        }
                        else if (paramInfo && paramInfo.isEnum || callInfo && (argAttrs.fixedInstance || argAttrs.blockIdentity === info.qName)) {
                            addField(getField(aName, (getPropertyAccessExpression(e as PropertyAccessExpression, true) as TextNode).value))
                        }
                        else {
                            addInput(getValue(aName, e, param.shadowBlockId, shadowMutation))
                        }
                        break;
                    case SK.BinaryExpression:
                        if (param && param.shadowOptions && param.shadowOptions.toString) {
                            const be = e as BinaryExpression;
                            if (be.operatorToken.kind === SK.PlusToken && isEmptyStringNode(be.left)) {
                                addInput(getValue(U.htmlEscape(param.definitionName), be.right, param.shadowBlockId || "text"));
                                break;
                            }
                        }
                        addInput(getValue(U.htmlEscape(param.definitionName), e, param.shadowBlockId, shadowMutation))
                        break;
                    default:
                        let v: ValueNode;
                        const vName = U.htmlEscape(param.definitionName);
                        let defaultV = true;

                        if (info.qName == "Math.random") {
                            v = mkValue(vName, getMathRandomArgumentExpresion(e), numberType, shadowMutation);
                            defaultV = false;
                        } else if (isLiteralNode(e)) {
                            // Remove quotes on strings
                            const fieldText = param.fieldEditor == 'text' ? (e as ts.StringLiteral).text : e.getText();
                            const isFieldBlock = param.shadowBlockId && !isLiteralBlockType(param.shadowBlockId);

                            if (decompileLiterals(param) && param.fieldOptions['onParentBlock']) {
                                addField(getField(vName, fieldText));
                                return;
                            }
                            else if (isFieldBlock) {
                                const field = fieldBlockInfo(param.shadowBlockId);
                                if (field && decompileLiterals(field)) {
                                    const fieldBlock = getFieldBlock(param.shadowBlockId, field.definitionName, fieldText, true);
                                    if (param.shadowOptions) {
                                        fieldBlock.mutation = { "customfield": Util.htmlEscape(JSON.stringify(param.shadowOptions)) };
                                    }
                                    v = mkValue(vName, fieldBlock, param.shadowBlockId, shadowMutation);
                                    defaultV = false;
                                }
                            }
                        }
                        else if (e.kind === SK.TaggedTemplateExpression && param.fieldOptions && param.fieldOptions[DecompileParamKeys.TaggedTemplate]) {
                            addField(getField(vName, Util.htmlEscape(e.getText())));
                            return;
                        }
                        if (defaultV) {
                            v = getValue(vName, e, param.shadowBlockId, shadowMutation);
                        }

                        addInput(v);
                        break;
                }
            });

            if (optionalCount) {
                if (!r.mutation) r.mutation = {};
                r.mutation["_expanded"] = optionalCount.toString();
            }

            return r;
        }