UNPKG

pxt-core

Version:

Microsoft MakeCode provides Blocks / JavaScript / Python tools and editors

1,040 lines • 661 kB
///<reference path='../localtypings/pxtblockly.d.ts'/> /// <reference path="../built/pxtlib.d.ts" /> let iface; var pxt; (function (pxt) { var blocks; (function (blocks_1) { function workerOpAsync(op, arg) { return pxt.worker.getWorker(pxt.webConfig.workerjs).opAsync(op, arg); } blocks_1.workerOpAsync = workerOpAsync; let placeholders = {}; const MAX_COMMENT_LINE_LENGTH = 50; /////////////////////////////////////////////////////////////////////////////// // Miscellaneous utility functions /////////////////////////////////////////////////////////////////////////////// // Mutate [a1] in place and append to it the elements from [a2]. function append(a1, a2) { a1.push.apply(a1, a2); } // A few wrappers for basic Block operations that throw errors when compilation // is not possible. (The outer code catches these and highlights the relevant // block.) // Internal error (in our code). Compilation shouldn't proceed. function assert(x) { if (!x) throw new Error("Assertion failure"); } function throwBlockError(msg, block) { let e = new Error(msg); e.block = block; throw e; } /////////////////////////////////////////////////////////////////////////////// // Types // // We slap a very simple type system on top of Blockly. This is needed to ensure // we generate valid TouchDevelop code (otherwise compilation from TD to C++ // would not work). /////////////////////////////////////////////////////////////////////////////// // There are several layers of abstraction for the type system. // - Block are annotated with a string return type, and a string type for their // input blocks (see blocks-custom.js). We use that as the reference semantics // for the blocks. // - In this "type system", we use the enum Type. Using an enum rules out more // mistakes. // - When emitting code, we target the "TouchDevelop types". // // Type inference / checking is done as follows. First, we try to assign a type // to all variables. We do this by examining all variable assignments and // figuring out the type from the right-hand side. There's a fixpoint computation // (see [mkEnv]). Then, we propagate down the expected type when doing code // generation; when generating code for a variable dereference, if the expected // type doesn't match the inferred type, it's an error. If the type was // undetermined as of yet, the type of the variable becomes the expected type. class Point { constructor(link, type, parentType, childType, isArrayType) { this.link = link; this.type = type; this.parentType = parentType; this.childType = childType; this.isArrayType = isArrayType; } } blocks_1.Point = Point; let BlockDeclarationType; (function (BlockDeclarationType) { BlockDeclarationType[BlockDeclarationType["None"] = 0] = "None"; BlockDeclarationType[BlockDeclarationType["Argument"] = 1] = "Argument"; BlockDeclarationType[BlockDeclarationType["Assigned"] = 2] = "Assigned"; BlockDeclarationType[BlockDeclarationType["Implicit"] = 3] = "Implicit"; })(BlockDeclarationType = blocks_1.BlockDeclarationType || (blocks_1.BlockDeclarationType = {})); function find(p) { if (p.link) return find(p.link); return p; } function union(p1, p2) { let _p1 = find(p1); let _p2 = find(p2); assert(_p1.link == null && _p2.link == null); if (_p1 == _p2) return; if (_p1.childType && _p2.childType) { const ct = _p1.childType; _p1.childType = null; union(ct, _p2.childType); } else if (_p1.childType && !_p2.childType) { _p2.childType = _p1.childType; } if (_p1.parentType && _p2.parentType) { const pt = _p1.parentType; _p1.parentType = null; union(pt, _p2.parentType); } else if (_p1.parentType && !_p2.parentType && !_p2.type) { _p2.parentType = _p1.parentType; } let t = unify(_p1.type, _p2.type); p1.link = _p2; _p1.link = _p2; _p1.isArrayType = _p2.isArrayType; p1.type = null; p2.type = t; } // Ground types. function mkPoint(t, isArrayType = false) { return new Point(null, t, null, null, isArrayType); } const pNumber = mkPoint("number"); const pBoolean = mkPoint("boolean"); const pString = mkPoint("string"); const pUnit = mkPoint("void"); function ground(t) { if (!t) return mkPoint(t); switch (t.toLowerCase()) { case "number": return pNumber; case "boolean": return pBoolean; case "string": return pString; case "void": return pUnit; default: // Unification variable. return mkPoint(t); } } /////////////////////////////////////////////////////////////////////////////// // Type inference // // Expressions are now directly compiled as a tree. This requires knowing, for // each property ref, the right value for its [parent] property. /////////////////////////////////////////////////////////////////////////////// // Infers the expected type of an expression by looking at the untranslated // block and figuring out, from the look of it, what type of expression it // holds. function returnType(e, b) { assert(b != null); if (isPlaceholderBlock(b)) { if (!b.p) b.p = mkPoint(null); return find(b.p); } if (b.type == "variables_get") return find(lookup(e, b, b.getField("VAR").getText()).type); if (b.type == "function_call_output") { return getReturnTypeOfFunctionCall(e, b); } if (!b.outputConnection) { return ground(pUnit.type); } const check = b.outputConnection.check_ && b.outputConnection.check_.length ? b.outputConnection.check_[0] : "T"; if (check === "Array") { if (b.outputConnection.check_.length > 1) { // HACK: The real type is stored as the second check return ground(b.outputConnection.check_[1]); } // lists_create_with and argument_reporter_array both hit this. // For lists_create_with, we can safely infer the type from the // first input that has a return type. // For argument_reporter_array just return any[] for now let tp; if (b.type == "lists_create_with") { if (b.inputList && b.inputList.length) { for (const input of b.inputList) { if (input.connection && input.connection.targetBlock()) { let t = find(returnType(e, input.connection.targetBlock())); if (t) { if (t.parentType) { return t.parentType; } tp = t.type ? ground(t.type + "[]") : mkPoint(null); genericLink(tp, t); break; } } } } } else if (b.type == "argument_reporter_array") { if (!tp) { tp = lookup(e, b, b.getFieldValue("VALUE")).type; } } if (tp) tp.isArrayType = true; return tp || mkPoint(null, true); } else if (check === "T") { const func = e.stdCallTable[b.type]; const isArrayGet = b.type === "lists_index_get"; if (isArrayGet || func && func.comp.thisParameter) { let parentInput; if (isArrayGet) { parentInput = b.inputList.find(i => i.name === "LIST"); } else { parentInput = b.inputList.find(i => i.name === func.comp.thisParameter.definitionName); } if (parentInput.connection && parentInput.connection.targetBlock()) { const parentType = returnType(e, parentInput.connection.targetBlock()); if (parentType.childType) { return parentType.childType; } const p = isArrayType(parentType.type) && parentType.type !== "Array" ? mkPoint(parentType.type.substr(0, parentType.type.length - 2)) : mkPoint(null); genericLink(parentType, p); return p; } } return mkPoint(null); } return ground(check); } function returnTypeWithInheritance(e, b) { var _a, _b; if (!((_b = (_a = b.outputConnection) === null || _a === void 0 ? void 0 : _a.check_) === null || _b === void 0 ? void 0 : _b.length) || b.outputConnection.check_[0] === "Array" || b.outputConnection.check_[0] === "T") { return [returnType(e, b)]; } return b.outputConnection.check_.map(t => ground(t)); } function getReturnTypeOfFunction(e, name) { if (!e.userFunctionReturnValues[name]) { const definition = Blockly.Functions.getDefinition(name, e.workspace); let res = mkPoint("void"); if (isFunctionRecursive(definition, true)) { res = mkPoint("any"); } else { const returnTypes = []; for (const child of definition.getDescendants(false)) { if (child.type === "function_return") { attachPlaceholderIf(e, child, "RETURN_VALUE"); returnTypes.push(returnType(e, getInputTargetBlock(child, "RETURN_VALUE"))); } } if (returnTypes.length) { try { const unified = mkPoint(null); for (const point of returnTypes) { union(unified, point); } res = unified; } catch (err) { e.diagnostics.push({ blockId: definition.id, message: pxt.Util.lf("Function '{0}' has an invalid return type", name) }); res = mkPoint("any"); } } } e.userFunctionReturnValues[name] = res; } return e.userFunctionReturnValues[name]; } function getReturnTypeOfFunctionCall(e, call) { const name = call.getField("function_name").getText(); return getReturnTypeOfFunction(e, name); } // Basic type unification routine; easy, because there's no structural types. // FIXME: Generics are not supported function unify(t1, t2) { if (t1 == null || t1 === "Array" && isArrayType(t2)) return t2; else if (t2 == null || t2 === "Array" && isArrayType(t1)) return t1; else if (t1 == t2) return t1; else throw new Error("cannot mix " + t1 + " with " + t2); } function isArrayType(type) { return type && (type.indexOf("[]") !== -1 || type == "Array"); } function mkPlaceholderBlock(e, parent, type) { // XXX define a proper placeholder block type return { type: "placeholder", p: mkPoint(type || null), workspace: e.workspace, parentBlock_: parent }; } function attachPlaceholderIf(e, b, n, type) { // Ugly hack to keep track of the type we want there. const target = b.getInputTargetBlock(n); if (!target) { if (!placeholders[b.id]) { placeholders[b.id] = {}; } if (!placeholders[b.id][n]) { placeholders[b.id][n] = mkPlaceholderBlock(e, b, type); } } else if (target.type === pxtc.TS_OUTPUT_TYPE && !(target.p)) { target.p = mkPoint(null); } } function getLoopVariableField(b) { return (b.type == "pxt_controls_for" || b.type == "pxt_controls_for_of") ? getInputTargetBlock(b, "VAR") : b; } function getInputTargetBlock(b, n) { const res = b.getInputTargetBlock(n); if (!res) { return placeholders[b.id] && placeholders[b.id][n]; } else { return res; } } function removeAllPlaceholders() { placeholders = {}; } // Unify the *return* type of the parameter [n] of block [b] with point [p]. function unionParam(e, b, n, p) { attachPlaceholderIf(e, b, n); try { union(returnType(e, getInputTargetBlock(b, n)), p); } catch (e) { // TypeScript should catch this error and bubble it up } } function infer(allBlocks, e, w) { if (allBlocks) allBlocks.filter(b => b.isEnabled()).forEach((b) => { try { switch (b.type) { case "math_op2": unionParam(e, b, "x", ground(pNumber.type)); unionParam(e, b, "y", ground(pNumber.type)); break; case "math_op3": unionParam(e, b, "x", ground(pNumber.type)); break; case "math_arithmetic": case "logic_compare": switch (b.getFieldValue("OP")) { case "ADD": case "MINUS": case "MULTIPLY": case "DIVIDE": case "LT": case "LTE": case "GT": case "GTE": case "POWER": unionParam(e, b, "A", ground(pNumber.type)); unionParam(e, b, "B", ground(pNumber.type)); break; case "AND": case "OR": attachPlaceholderIf(e, b, "A", pBoolean.type); attachPlaceholderIf(e, b, "B", pBoolean.type); break; case "EQ": case "NEQ": attachPlaceholderIf(e, b, "A"); attachPlaceholderIf(e, b, "B"); let p1 = returnType(e, getInputTargetBlock(b, "A")); let p2 = returnType(e, getInputTargetBlock(b, "B")); try { union(p1, p2); } catch (e) { // TypeScript should catch this error and bubble it up } break; } break; case "logic_operation": attachPlaceholderIf(e, b, "A", pBoolean.type); attachPlaceholderIf(e, b, "B", pBoolean.type); break; case "logic_negate": attachPlaceholderIf(e, b, "BOOL", pBoolean.type); break; case "controls_if": for (let i = 0; i <= b.elseifCount_; ++i) attachPlaceholderIf(e, b, "IF" + i, pBoolean.type); break; case "pxt_controls_for": case "controls_simple_for": unionParam(e, b, "TO", ground(pNumber.type)); break; case "pxt_controls_for_of": case "controls_for_of": const listTp = returnType(e, getInputTargetBlock(b, "LIST")); const elementTp = lookup(e, b, getLoopVariableField(b).getField("VAR").getText()).type; genericLink(listTp, elementTp); break; case "variables_set": case "variables_change": let p1 = lookup(e, b, b.getField("VAR").getText()).type; attachPlaceholderIf(e, b, "VALUE"); let rhs = getInputTargetBlock(b, "VALUE"); if (rhs) { // Get the inheritance chain for this type and check to see if the existing // type shows up in it somewhere let tr = returnTypeWithInheritance(e, rhs); const t1 = find(p1); if (t1.type && tr.slice(1).some(p => p.type === t1.type)) { // If it does, we want to take the most narrow type (which will always be in 0) p1.link = find(tr[0]); } else { try { union(p1, tr[0]); } catch (e) { // TypeScript should catch this error and bubble it up } } } break; case "controls_repeat_ext": unionParam(e, b, "TIMES", ground(pNumber.type)); break; case "device_while": attachPlaceholderIf(e, b, "COND", pBoolean.type); break; case "lists_index_get": unionParam(e, b, "LIST", ground("Array")); unionParam(e, b, "INDEX", ground(pNumber.type)); const listType = returnType(e, getInputTargetBlock(b, "LIST")); const ret = returnType(e, b); genericLink(listType, ret); break; case "lists_index_set": unionParam(e, b, "LIST", ground("Array")); attachPlaceholderIf(e, b, "VALUE"); handleGenericType(b, "LIST"); unionParam(e, b, "INDEX", ground(pNumber.type)); break; case 'function_definition': getReturnTypeOfFunction(e, b.getField("function_name").getText()); break; case 'function_call': case 'function_call_output': b.getArguments().forEach(arg => { unionParam(e, b, arg.id, ground(arg.type)); }); break; case pxtc.TS_RETURN_STATEMENT_TYPE: attachPlaceholderIf(e, b, "RETURN_VALUE"); break; case pxtc.PAUSE_UNTIL_TYPE: unionParam(e, b, "PREDICATE", pBoolean); break; default: if (b.type in e.stdCallTable) { const call = e.stdCallTable[b.type]; if (call.attrs.shim === "ENUM_GET" || call.attrs.shim === "KIND_GET") return; visibleParams(call, countOptionals(b, call)).forEach((p, i) => { const isInstance = call.isExtensionMethod && i === 0; if (p.definitionName && !b.getFieldValue(p.definitionName)) { let i = b.inputList.find((i) => i.name == p.definitionName); if (i && i.connection && i.connection.check_) { if (isInstance && connectionCheck(i) === "Array") { let gen = handleGenericType(b, p.definitionName); if (gen) { return; } } // All of our injected blocks have single output checks, but the builtin // blockly ones like string.length and array.length might have multiple for (let j = 0; j < i.connection.check_.length; j++) { try { let t = i.connection.check_[j]; unionParam(e, b, p.definitionName, ground(t)); break; } catch (e) { // Ignore type checking errors in the blocks... } } } } }); } } } catch (err) { const be = err.block || b; be.setWarningText(err + ""); e.errors.push(be); } }); // Last pass: if some variable has no type (because it was never used or // assigned to), just unify it with int... e.allVariables.forEach((v) => { if (getConcreteType(v.type).type == null) { if (!v.isFunctionParameter) { union(v.type, ground(v.type.isArrayType ? "number[]" : pNumber.type)); } else if (v.type.isArrayType) { v.type.type = "any[]"; } } }); function connectionCheck(i) { return i.name ? i.connection && i.connection.check_ && i.connection.check_.length ? i.connection.check_[0] : "T" : undefined; } function handleGenericType(b, name) { let genericArgs = b.inputList.filter((input) => connectionCheck(input) === "T"); if (genericArgs.length) { const gen = getInputTargetBlock(b, genericArgs[0].name); if (gen) { const arg = returnType(e, gen); const arrayType = arg.type ? ground(returnType(e, gen).type + "[]") : ground(null); genericLink(arrayType, arg); unionParam(e, b, name, arrayType); return true; } } return false; } } function genericLink(parent, child) { const p = find(parent); const c = find(child); if (p.childType) { union(p.childType, c); } else if (!p.type) { p.childType = c; } if (c.parentType) { union(c.parentType, p); } else if (!c.type) { c.parentType = p; } if (isArrayType(p.type)) p.isArrayType = true; } function getConcreteType(point, found = []) { const t = find(point); if (found.indexOf(t) === -1) { found.push(t); if (!t.type || t.type === "Array") { if (t.parentType) { const parent = getConcreteType(t.parentType, found); if (parent.type && parent.type !== "Array") { if (isArrayType(parent.type)) { t.type = parent.type.substr(0, parent.type.length - 2); } else { t.type = parent.type; } return t; } } if (t.childType) { const child = getConcreteType(t.childType, found); if (child.type) { t.type = child.type + "[]"; return t; } } } } return t; } /////////////////////////////////////////////////////////////////////////////// // Expressions // // Expressions are now directly compiled as a tree. This requires knowing, for // each property ref, the right value for its [parent] property. /////////////////////////////////////////////////////////////////////////////// function extractNumber(b) { let v = b.getFieldValue(b.type === "math_number_minmax" ? "SLIDER" : "NUM"); const parsed = parseFloat(v); checkNumber(parsed, b); return parsed; } function checkNumber(n, b) { if (!isFinite(n) || isNaN(n)) { throwBlockError(lf("Number entered is either too large or too small"), b); } } function extractTsExpression(e, b, comments) { return blocks_1.mkText(b.getFieldValue("EXPRESSION").trim()); } function compileNumber(e, b, comments) { return blocks_1.H.mkNumberLiteral(extractNumber(b)); } function isNumericLiteral(e, b) { if (!b) return false; if (b.type === "math_number" || b.type === "math_integer" || b.type === "math_number_minmax" || b.type === "math_whole_number") { return true; } const blockInfo = e.stdCallTable[b.type]; if (!blockInfo) return false; const { comp } = blockInfo; if (blockInfo.attrs.shim === "TD_ID" && comp.parameters.length === 1) { const fieldValue = b.getFieldValue(comp.parameters[0].definitionName); if (fieldValue) { return !isNaN(parseInt(fieldValue)); } else { return isNumericLiteral(e, getInputTargetBlock(b, comp.parameters[0].definitionName)); } } return false; } function isLiteral(e, b) { return isNumericLiteral(e, b) || b.type === "logic_boolean" || b.type === "text"; } let opToTok = { "ADD": "+", "MINUS": "-", "MULTIPLY": "*", "DIVIDE": "/", "LT": "<", "LTE": "<=", "GT": ">", "GTE": ">=", "AND": "&&", "OR": "||", "EQ": "==", "NEQ": "!=", "POWER": "**" }; function isComparisonOp(op) { return ["LT", "LTE", "GT", "GTE", "EQ", "NEQ"].indexOf(op) !== -1; } function compileArithmetic(e, b, comments) { let bOp = b.getFieldValue("OP"); let left = getInputTargetBlock(b, "A"); let right = getInputTargetBlock(b, "B"); let args = [compileExpression(e, left, comments), compileExpression(e, right, comments)]; // Special handling for the case of comparing two literals (e.g. 0 === 5). TypeScript // throws an error if we don't first cast to any if (isComparisonOp(bOp) && isLiteral(e, left) && isLiteral(e, right)) { if (blocks_1.flattenNode([args[0]]).output !== blocks_1.flattenNode([args[1]]).output) { args = args.map(arg => blocks_1.H.mkParenthesizedExpression(blocks_1.mkGroup([arg, blocks_1.mkText(" as any")]))); } } let t = returnType(e, left).type; if (t == pString.type) { if (bOp == "EQ") return blocks_1.H.mkSimpleCall("==", args); else if (bOp == "NEQ") return blocks_1.H.mkSimpleCall("!=", args); } else if (t == pBoolean.type) return blocks_1.H.mkSimpleCall(opToTok[bOp], args); // Compilation of math operators. assert(bOp in opToTok); return blocks_1.H.mkSimpleCall(opToTok[bOp], args); } function compileModulo(e, b, comments) { let left = getInputTargetBlock(b, "DIVIDEND"); let right = getInputTargetBlock(b, "DIVISOR"); let args = [compileExpression(e, left, comments), compileExpression(e, right, comments)]; return blocks_1.H.mkSimpleCall("%", args); } function compileMathOp2(e, b, comments) { let op = b.getFieldValue("op"); let x = compileExpression(e, getInputTargetBlock(b, "x"), comments); let y = compileExpression(e, getInputTargetBlock(b, "y"), comments); return blocks_1.H.mathCall(op, [x, y]); } function compileMathOp3(e, b, comments) { let x = compileExpression(e, getInputTargetBlock(b, "x"), comments); return blocks_1.H.mathCall("abs", [x]); } function compileText(e, b, comments) { return blocks_1.H.mkStringLiteral(b.getFieldValue("TEXT")); } function compileTextJoin(e, b, comments) { let last; let i = 0; while (true) { const val = getInputTargetBlock(b, "ADD" + i); i++; if (!val) { if (i < b.inputList.length) { continue; } else { break; } } const compiled = compileExpression(e, val, comments); if (!last) { if (val.type.indexOf("text") === 0) { last = compiled; } else { // If we don't start with a string, then the TS won't match // the implied semantics of the blocks last = blocks_1.H.mkSimpleCall("+", [blocks_1.H.mkStringLiteral(""), compiled]); } } else { last = blocks_1.H.mkSimpleCall("+", [last, compiled]); } } if (!last) { return blocks_1.H.mkStringLiteral(""); } return last; } function compileBoolean(e, b, comments) { return blocks_1.H.mkBooleanLiteral(b.getFieldValue("BOOL") == "TRUE"); } function compileNot(e, b, comments) { let expr = compileExpression(e, getInputTargetBlock(b, "BOOL"), comments); return blocks_1.mkPrefix("!", [blocks_1.H.mkParenthesizedExpression(expr)]); } function compileCreateList(e, b, comments) { // collect argument let args = b.inputList.map(input => input.connection && input.connection.targetBlock() ? compileExpression(e, input.connection.targetBlock(), comments) : undefined) .filter(e => !!e); return blocks_1.H.mkArrayLiteral(args, !b.getInputsInline()); } function compileListGet(e, b, comments) { const listBlock = getInputTargetBlock(b, "LIST"); const listExpr = compileExpression(e, listBlock, comments); const index = compileExpression(e, getInputTargetBlock(b, "INDEX"), comments); const res = blocks_1.mkGroup([listExpr, blocks_1.mkText("["), index, blocks_1.mkText("]")]); return res; } function compileListSet(e, b, comments) { const listBlock = getInputTargetBlock(b, "LIST"); const listExpr = compileExpression(e, listBlock, comments); const index = compileExpression(e, getInputTargetBlock(b, "INDEX"), comments); const value = compileExpression(e, getInputTargetBlock(b, "VALUE"), comments); const res = blocks_1.mkGroup([listExpr, blocks_1.mkText("["), index, blocks_1.mkText("] = "), value]); return listBlock.type === "lists_create_with" ? prefixWithSemicolon(res) : res; } function compileMathJsOp(e, b, comments) { const op = b.getFieldValue("OP"); const args = [compileExpression(e, getInputTargetBlock(b, "ARG0"), comments)]; if (b.getInput("ARG1")) { args.push(compileExpression(e, getInputTargetBlock(b, "ARG1"), comments)); } return blocks_1.H.mathCall(op, args); } function compileFunctionDefinition(e, b, comments) { const name = escapeVarName(b.getField("function_name").getText(), e, true); const stmts = getInputTargetBlock(b, "STACK"); const argsDeclaration = b.getArguments().map(a => { if (a.type == "Array") { const binding = lookup(e, b, a.name); const declaredType = getConcreteType(binding.type); const paramType = ((declaredType === null || declaredType === void 0 ? void 0 : declaredType.type) && declaredType.type !== "Array") ? declaredType.type : "any[]"; return `${escapeVarName(a.name, e)}: ${paramType}`; } return `${escapeVarName(a.name, e)}: ${a.type}`; }); const isRecursive = isFunctionRecursive(b, false); return [ blocks_1.mkText(`function ${name} (${argsDeclaration.join(", ")})${isRecursive ? ": any" : ""}`), compileStatements(e, stmts) ]; } function compileProcedure(e, b, comments) { const name = escapeVarName(b.getFieldValue("NAME"), e, true); const stmts = getInputTargetBlock(b, "STACK"); return [ blocks_1.mkText("function " + name + "() "), compileStatements(e, stmts) ]; } function compileProcedureCall(e, b, comments) { const name = escapeVarName(b.getFieldValue("NAME"), e, true); return blocks_1.mkStmt(blocks_1.mkText(name + "()")); } function compileFunctionCall(e, b, comments, statement) { const name = escapeVarName(b.getField("function_name").getText(), e, true); const externalInputs = !b.getInputsInline(); const args = b.getArguments().map(a => { return { actualName: a.name, definitionName: a.id }; }); const compiledArgs = args.map(a => compileArgument(e, b, a, comments)); const res = blocks_1.H.stdCall(name, compiledArgs, externalInputs); if (statement) { return blocks_1.mkStmt(res); } return res; } function compileReturnStatement(e, b, comments) { const expression = getInputTargetBlock(b, "RETURN_VALUE"); if (expression && expression.type != "placeholder") { return blocks_1.mkStmt(blocks_1.mkText("return "), compileExpression(e, expression, comments)); } else { return blocks_1.mkStmt(blocks_1.mkText("return")); } } function compileArgumentReporter(e, b, comments) { const name = escapeVarName(b.getFieldValue("VALUE"), e); return blocks_1.mkText(name); } function compileWorkspaceComment(c) { const content = c.getContent(); return blocks_1.Helpers.mkMultiComment(content.trim()); } function defaultValueForType(t) { if (t.type == null) { union(t, ground(pNumber.type)); t = find(t); } if (isArrayType(t.type) || t.isArrayType) { return blocks_1.mkText("[]"); } switch (t.type) { case "boolean": return blocks_1.H.mkBooleanLiteral(false); case "number": return blocks_1.H.mkNumberLiteral(0); case "string": return blocks_1.H.mkStringLiteral(""); default: return blocks_1.mkText("null"); } } // [t] is the expected type; we assume that we never null block children // (because placeholder blocks have been inserted by the type-checking phase // whenever a block was actually missing). function compileExpression(e, b, comments) { assert(b != null); e.stats[b.type] = (e.stats[b.type] || 0) + 1; maybeAddComment(b, comments); let expr; if (b.type == "placeholder" || !(b.isEnabled && b.isEnabled())) { const ret = find(returnType(e, b)); if (ret.type === "Array") { // FIXME: Can't use default type here because TS complains about // the array having an implicit any type. However, forcing this // to be a number array may cause type issues. Also, potential semicolon // issues if we ever have a block where the array is not the first argument... let isExpression = b.parentBlock_.type === "lists_index_get"; if (!isExpression) { const call = e.stdCallTable[b.parentBlock_.type]; isExpression = call && call.isExpression; } const arrayNode = blocks_1.mkText("[0]"); expr = isExpression ? arrayNode : prefixWithSemicolon(arrayNode); } else { expr = defaultValueForType(returnType(e, b)); } } else switch (b.type) { case "math_number": case "math_integer": case "math_whole_number": expr = compileNumber(e, b, comments); break; case "math_number_minmax": expr = compileNumber(e, b, comments); break; case "math_op2": expr = compileMathOp2(e, b, comments); break; case "math_op3": expr = compileMathOp3(e, b, comments); break; case "math_arithmetic": case "logic_compare": case "logic_operation": expr = compileArithmetic(e, b, comments); break; case "math_modulo": expr = compileModulo(e, b, comments); break; case "logic_boolean": expr = compileBoolean(e, b, comments); break; case "logic_negate": expr = compileNot(e, b, comments); break; case "variables_get": expr = compileVariableGet(e, b); break; case "text": expr = compileText(e, b, comments); break; case "text_join": expr = compileTextJoin(e, b, comments); break; case "lists_create_with": expr = compileCreateList(e, b, comments); break; case "lists_index_get": expr = compileListGet(e, b, comments); break; case "lists_index_set": expr = compileListSet(e, b, comments); break; case "math_js_op": case "math_js_round": expr = compileMathJsOp(e, b, comments); break; case pxtc.TS_OUTPUT_TYPE: expr = extractTsExpression(e, b, comments); break; case "argument_reporter_boolean": case "argument_reporter_number": case "argument_reporter_string": case "argument_reporter_array": case "argument_reporter_custom": expr = compileArgumentReporter(e, b, comments); break; case "function_call_output": expr = compileFunctionCall(e, b, comments, false); break; default: let call = e.stdCallTable[b.type]; if (call) { if (call.imageLiteral) expr = compileImage(e, b, call.imageLiteral, call.imageLiteralColumns, call.imageLiteralRows, call.namespace, call.f, visibleParams(call, countOptionals(b, call)).map(ar => compileArgument(e, b, ar, comments))); else expr = compileStdCall(e, b, call, comments); } else { pxt.reportError("blocks", "unable to compile expression", { "details": b.type }); expr = defaultValueForType(returnType(e, b)); } break; } expr.id = b.id; return expr; } blocks_1.compileExpression = compileExpression; function lookup(e, b, name) { return getVarInfo(name, e.idToScope[b.id]); } function emptyEnv(w, options) { return { workspace: w, options, stdCallTable: {}, userFunctionReturnValues: {}, diagnostics: [], errors: [], renames: { oldToNew: {}, takenNames: {}, oldToNewFunctions: {} }, stats: {}, enums: [], kinds: [], idToScope: {}, blockDeclarations: {}, allVariables: [], blocksInfo: null }; } ; /////////////////////////////////////////////////////////////////////////////// // Statements /////////////////////////////////////////////////////////////////////////////// function compileControlsIf(e, b, comments) { let stmts = []; // Notice the <= (if there's no else-if, we still compile the primary if). for (let i = 0; i <= b.elseifCount_; ++i) { let cond = compileExpression(e, getInputTargetBlock(b, "IF" + i), comments); let thenBranch = compileStatements(e, getInputTargetBlock(b, "DO" + i)); let startNode = blocks_1.mkText("if ("); if (i > 0) { startNode = blocks_1.mkText("else if ("); startNode.glueToBlock = blocks_1.GlueMode.WithSpace; } append(stmts, [ startNode, cond, blocks_1.mkText(")"), thenBranch ]); } if (b.elseCount_) { let elseNode = blocks_1.mkText("else"); elseNode.glueToBlock = blocks_1.GlueMode.WithSpace; append(stmts, [ elseNode, compileStatements(e, getInputTargetBlock(b, "ELSE")) ]); } return stmts; } function compileControlsFor(e, b, comments) { let bTo = getInputTargetBlock(b, "TO"); let bDo = getInputTargetBlock(b, "DO"); let bBy = getInputTargetBlock(b, "BY"); let bFrom = getInputTargetBlock(b, "FROM"); let incOne = !bBy || (bBy.type.match(/^math_number/) && extractNumber(bBy) == 1); let binding = lookup(e, b, getLoopVariableField(b).getField("VAR").getText()); return [ blocks_1.mkText("for (let " + binding.escapedName + " = "), bFrom ? compileExpression(e, bFrom, comments) : blocks_1.mkText("0"), blocks_1.mkText("; "), blocks_1.mkInfix(blocks_1.mkText(binding.escapedName), "<=", compileExpression(e, bTo, comments)), blocks_1.mkText("; "), incOne ? blocks_1.mkText(binding.escapedName + "++") : blocks_1.mkInfix(blocks_1.mkText(binding.escapedName), "+=", compileExpression(e, bBy, comments)), blocks_1.mkText(")"), compileStatements(e, bDo) ]; } function compileControlsRepeat(e, b, comments) { let bound = compileExpression(e, getInputTargetBlock(b, "TIMES"), comments); let body = compileStatements(e, getInputTargetBlock(b, "DO")); let valid = (x) => !lookup(e, b, x); let name = "index"; // Start at 2 because index0 and index1 are bad names for (let i = 2; !valid(name); i++) name = "index" + i; return [ blocks_1.mkText("for (let " + name + " = 0; "), blocks_1.mkInfix(blocks_1.mkText(name), "<", bound), blocks_1.mkText("; " + name + "++)"), body ]; } function compileWhile(e, b, comments) { let cond = compileExpression(e, getInputTargetBlock(b, "COND"), comments); let body = compileStatements(e, getInputTargetBlock(b, "DO")); return [ blocks_1.mkText("while ("), cond, blocks_1.mkText(")"), body ]; } function compileControlsForOf(e, b, comments) { let b