import * as Blockly from "blockly/core"; /** * @license Licensed under the Apache License, Version 2.0 (the "License"): * http://www.apache.org/licenses/LICENSE-2.0 */ /** * @fileoverview Generating Arduino code for the Math blocks. * * TODO: Math on list needs lists to be implemented. * math_constant and math_change needs to be tested in compiler. */ /** * Generator for a numeric value (X). * Arduino code: loop { X } * @param {!Blockly.Block} block Block to generate the code from. * @return {array} Completed code with order of operation. */ Blockly.Arduino["math_number"] = function (block) { // Numeric value. var code = parseFloat(block.getFieldValue("NUM")); if (code === Infinity) { code = "INFINITY"; } else if (code === -Infinity) { code = "-INFINITY"; } return [code, Blockly.Arduino.ORDER_ATOMIC]; }; /** * Generator for a basic arithmetic operators (X and Y) and power function * (X ^ Y). * Arduino code: loop { X operator Y } * @param {!Blockly.Block} block Block to generate the code from. * @return {array} Completed code with order of operation. */ Blockly.Arduino["math_arithmetic"] = function (block) { var OPERATORS = { ADD: [" + ", Blockly.Arduino.ORDER_ADDITIVE], MINUS: [" - ", Blockly.Arduino.ORDER_ADDITIVE], MULTIPLY: [" * ", Blockly.Arduino.ORDER_MULTIPLICATIVE], DIVIDE: [" / ", Blockly.Arduino.ORDER_MULTIPLICATIVE], POWER: [null, Blockly.Arduino.ORDER_NONE], // Handle power separately. }; var tuple = OPERATORS[block.getFieldValue("OP")]; var operator = tuple[0]; var order = tuple[1]; var argument0 = Blockly.Arduino.valueToCode(block, "A", order) || "0"; var argument1 = Blockly.Arduino.valueToCode(block, "B", order) || "0"; var code; // Power in C++ requires a special case since it has no operator. if (!operator) { code = "Math.pow(" + argument0 + ", " + argument1 + ")"; return [code, Blockly.Arduino.ORDER_UNARY_POSTFIX]; } code = argument0 + operator + argument1; return [code, order]; }; /** * Generator for math operators that contain a single operand (X). * Arduino code: loop { operator(X) } * @param {!Blockly.Block} block Block to generate the code from. * @return {array} Completed code with order of operation. */ Blockly.Arduino["math_single"] = function (block) { var operator = block.getFieldValue("OP"); var code; var arg; if (operator === "NEG") { // Negation is a special case given its different operator precedents. arg = Blockly.Arduino.valueToCode( block, "NUM", Blockly.Arduino.ORDER_UNARY_PREFIX ) || "0"; if (arg[0] === "-") { // --3 is not legal in C++ in this context. arg = " " + arg; } code = "-" + arg; return [code, Blockly.Arduino.ORDER_UNARY_PREFIX]; } if (operator === "ABS" || operator.substring(0, 5) === "ROUND") { arg = Blockly.Arduino.valueToCode( block, "NUM", Blockly.Arduino.ORDER_UNARY_POSTFIX ) || "0"; } else if (operator === "SIN" || operator === "COS" || operator === "TAN") { arg = Blockly.Arduino.valueToCode( block, "NUM", Blockly.Arduino.ORDER_MULTIPLICATIVE ) || "0"; } else { arg = Blockly.Arduino.valueToCode(block, "NUM", Blockly.Arduino.ORDER_NONE) || "0"; } // First, handle cases which generate values that don't need parentheses. switch (operator) { case "ABS": code = "abs(" + arg + ")"; break; case "ROOT": code = "sqrt(" + arg + ")"; break; case "LN": code = "log(" + arg + ")"; break; case "EXP": code = "exp(" + arg + ")"; break; case "POW10": code = "pow(10," + arg + ")"; break; case "ROUND": code = "round(" + arg + ")"; break; case "ROUNDUP": code = "ceil(" + arg + ")"; break; case "ROUNDDOWN": code = "floor(" + arg + ")"; break; case "SIN": code = "sin(" + arg + " / 180 * Math.PI)"; break; case "COS": code = "cos(" + arg + " / 180 * Math.PI)"; break; case "TAN": code = "tan(" + arg + " / 180 * Math.PI)"; break; default: break; } if (code) { return [code, Blockly.Arduino.ORDER_UNARY_POSTFIX]; } // Second, handle cases which generate values that may need parentheses. switch (operator) { case "LOG10": code = "log(" + arg + ") / log(10)"; break; case "ASIN": code = "asin(" + arg + ") / M_PI * 180"; break; case "ACOS": code = "acos(" + arg + ") / M_PI * 180"; break; case "ATAN": code = "atan(" + arg + ") / M_PI * 180"; break; default: throw new Error("Unknown math operator: " + operator); } return [code, Blockly.Arduino.ORDER_MULTIPLICATIVE]; }; /** * Generator for math constants (PI, E, the Golden Ratio, sqrt(2), 1/sqrt(2), * INFINITY). * Arduino code: loop { constant } * TODO: Might need to include "#define _USE_MATH_DEFINES" * The arduino header file already includes math.h * @param {!Blockly.Block} block Block to generate the code from. * @return {string} Completed code. */ Blockly.Arduino["math_constant"] = function (block) { var CONSTANTS = { PI: ["M_PI", Blockly.Arduino.ORDER_UNARY_POSTFIX], E: ["M_E", Blockly.Arduino.ORDER_UNARY_POSTFIX], GOLDEN_RATIO: ["(1 + sqrt(5)) / 2", Blockly.Arduino.ORDER_MULTIPLICATIVE], SQRT2: ["M_SQRT2", Blockly.Arduino.ORDER_UNARY_POSTFIX], SQRT1_2: ["M_SQRT1_2", Blockly.Arduino.ORDER_UNARY_POSTFIX], INFINITY: ["INFINITY", Blockly.Arduino.ORDER_ATOMIC], }; return CONSTANTS[block.getFieldValue("CONSTANT")]; }; /** * Generator for math checks: if a number is even, odd, prime, whole, positive, * negative, or if it is divisible by certain number. Returns true or false. * Arduino code: complex code, can create external functions. * @param {!Blockly.Block} block Block to generate the code from. * @return {array} Completed code with order of operation. */ Blockly.Arduino["math_number_property"] = function (block) { var number_to_check = Blockly.Arduino.valueToCode( block, "NUMBER_TO_CHECK", Blockly.Arduino.ORDER_MULTIPLICATIVE ) || "0"; var dropdown_property = block.getFieldValue("PROPERTY"); var code; if (dropdown_property === "PRIME") { var func = [ "boolean " + Blockly.Arduino.DEF_FUNC_NAME + "(int n) {", " // https://en.wikipedia.org/wiki/Primality_test#Naive_methods", " if (n == 2 || n == 3) {", " return true;", " }", " // False if n is NaN, negative, is 1.", " // And false if n is divisible by 2 or 3.", " if (isnan(n) || (n <= 1) || (n == 1) || (n % 2 == 0) || " + "(n % 3 == 0)) {", " return false;", " }", " // Check all the numbers of form 6k +/- 1, up to sqrt(n).", " for (int x = 6; x <= sqrt(n) + 1; x += 6) {", " if (n % (x - 1) == 0 || n % (x + 1) == 0) {", " return false;", " }", " }", " return true;", "}", ]; var funcName = Blockly.Arduino.addFunction("mathIsPrime", func.join("\n")); Blockly.Arduino.addInclude("math", "#include "); code = funcName + "(" + number_to_check + ")"; return [code, Blockly.Arduino.ORDER_UNARY_POSTFIX]; } switch (dropdown_property) { case "EVEN": code = number_to_check + " % 2 == 0"; break; case "ODD": code = number_to_check + " % 2 == 1"; break; case "WHOLE": Blockly.Arduino.addInclude("math", "#include "); code = "(floor(" + number_to_check + ") == " + number_to_check + ")"; break; case "POSITIVE": code = number_to_check + " > 0"; break; case "NEGATIVE": code = number_to_check + " < 0"; break; case "DIVISIBLE_BY": var divisor = Blockly.Arduino.valueToCode( block, "DIVISOR", Blockly.Arduino.ORDER_MULTIPLICATIVE ) || "0"; code = number_to_check + " % " + divisor + " == 0"; break; default: break; } return [code, Blockly.Arduino.ORDER_EQUALITY]; }; /** * Generator to add (Y) to a variable (X). * If variable X has not been declared before this block it will be declared as * a (not initialised) global int, however globals are 0 initialised in C/C++. * Arduino code: loop { X += Y; } * @param {!Blockly.Block} block Block to generate the code from. * @return {array} Completed code with order of operation. */ Blockly.Arduino["math_change"] = function (block) { var argument0 = Blockly.Arduino.valueToCode( block, "DELTA", Blockly.Arduino.ORDER_ADDITIVE ) || "0"; var id = block.getFieldValue("VAR") const varName = Blockly.Variables.getVariable( Blockly.getMainWorkspace(), id ).name; // var varName = Blockly.Arduino.nameDB_.getName( // block.getFieldValue("VAR"), // Blockly.Variables.NAME_TYPE // ); return varName + " += " + argument0 + ";\n"; }; /** Rounding functions have a single operand. */ Blockly.Arduino["math_round"] = Blockly.Arduino["math_single"]; /** Trigonometry functions have a single operand. */ Blockly.Arduino["math_trig"] = Blockly.Arduino["math_single"]; /** * Generator for the math function to a list. * Arduino code: ??? * TODO: List have to be implemented first. Removed from toolbox for now. * @param {!Blockly.Block} block Block to generate the code from. * @return {array} Completed code with order of operation. */ Blockly.Arduino["math_on_list"] = Blockly.Arduino.noGeneratorCodeInline; /** * Generator for the math modulo function (calculates remainder of X/Y). * Arduino code: loop { X % Y } * @param {!Blockly.Block} block Block to generate the code from. * @return {array} Completed code with order of operation. */ Blockly.Arduino["math_modulo"] = function (block) { var argument0 = Blockly.Arduino.valueToCode( block, "DIVIDEND", Blockly.Arduino.ORDER_MULTIPLICATIVE ) || "0"; var argument1 = Blockly.Arduino.valueToCode( block, "DIVISOR", Blockly.Arduino.ORDER_MULTIPLICATIVE ) || "0"; var code = argument0 + " % " + argument1; return [code, Blockly.Arduino.ORDER_MULTIPLICATIVE]; }; /** * Generator for clipping a number(X) between two limits (Y and Z). * Arduino code: loop { (X < Y ? Y : ( X > Z ? Z : X)) } * @param {!Blockly.Block} block Block to generate the code from. * @return {array} Completed code with order of operation. */ Blockly.Arduino["math_constrain"] = function (block) { // Constrain a number between two limits. var argument0 = Blockly.Arduino.valueToCode(block, "VALUE", Blockly.Arduino.ORDER_NONE) || "0"; var argument1 = Blockly.Arduino.valueToCode(block, "LOW", Blockly.Arduino.ORDER_NONE) || "0"; var argument2 = Blockly.Arduino.valueToCode(block, "HIGH", Blockly.Arduino.ORDER_NONE) || "0"; var code = "(" + argument0 + " < " + argument1 + " ? " + argument1 + " : ( " + argument0 + " > " + argument2 + " ? " + argument2 + " : " + argument0 + "))"; return [code, Blockly.Arduino.ORDER_UNARY_POSTFIX]; }; /** * Generator for a random integer between two numbers (X and Y). * Arduino code: loop { math_random_int(X, Y); } * and an aditional math_random_int function * @param {!Blockly.Block} block Block to generate the code from. * @return {array} Completed code with order of operation. */ Blockly.Arduino["math_random_int"] = function (block) { var argument0 = Blockly.Arduino.valueToCode(block, "FROM", Blockly.Arduino.ORDER_NONE) || "0"; var argument1 = Blockly.Arduino.valueToCode(block, "TO", Blockly.Arduino.ORDER_NONE) || "0"; Blockly.Arduino.setupCode_["init_rand"] = "randomSeed(analogRead(0));"; Blockly.Arduino.functionNames_[ "math_random_int" ] = `int mathRandomInt (int min, int max) {\n if (min > max) { int temp = min; min = max; max = temp; } return min + (rand() % (max - min + 1)); } `; var code = `mathRandomInt(${argument0},${argument1});`; return [code, Blockly.Arduino.ORDER_ATOMIC]; }; /** * Generator for a random float from 0 to 1. * Arduino code: loop { (rand() / RAND_MAX) } * @param {!Blockly.Block} block Block to generate the code from. * @return {string} Completed code. */ Blockly.Arduino["math_random_float"] = function (block) { return ["(rand() / RAND_MAX)", Blockly.Arduino.ORDER_UNARY_POSTFIX]; };