pxt-core
Version:
Microsoft MakeCode provides Blocks / JavaScript / Python tools and editors
1,040 lines • 661 kB
JavaScript
///<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