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;
}