/** * @license * Visual Blocks Language * * Copyright 2012 Google Inc. * https://developers.google.com/blockly/ * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * @fileoverview Helper functions for generating Python for blocks. * @author fraser@google.com (Neil Fraser) */ 'use strict'; goog.provide('Blockly.Python'); goog.require('Blockly.Generator'); //Blockly.Python.StaticTyping = new Blockly.StaticTyping(); /** * Python code generator. * @type {!Blockly.Generator} */ //Blockly.Python = new Blockly.Generator('Python'); /** * List of illegal variable names. * This is not intended to be a security feature. Blockly is 100% client-side, * so bypassing this list is trivial. This is intended to prevent users from * accidentally clobbering a built-in object or function. * @private */ Blockly.Python.addReservedWords( // import keyword // print ','.join(keyword.kwlist) // http://docs.python.org/reference/lexical_analysis.html#keywords 'and,as,assert,break,class,continue,def,del,elif,else,except,exec,' + 'finally,for,from,global,if,import,in,is,lambda,not,or,pass,print,raise,' + 'return,try,while,with,yield,' + //http://docs.python.org/library/constants.html 'True,False,None,NotImplemented,Ellipsis,__debug__,quit,exit,copyright,license,credits,' + // Reserved libraries //'crime,stocks,earthquakes,books,weather,plt,math,'+ // http://docs.python.org/library/functions.html '' //'abs,divmod,input,open,staticmethod,all,enumerate,int,ord,str,any,eval,isinstance,pow,sum,basestring,execfile,issubclass,print,super,bin,file,iter,property,tuple,bool,filter,len,range,type,bytearray,float,list,raw_input,unichr,callable,format,locals,reduce,unicode,chr,frozenset,long,reload,vars,classmethod,getattr,map,repr,xrange,cmp,globals,max,reversed,zip,compile,hasattr,memoryview,round,__import__,complex,hash,min,set,apply,delattr,help,next,setattr,buffer,dict,hex,object,slice,coerce,dir,id,oct,sorted,intern' ); /** * Order of operation ENUMs. * http://docs.python.org/reference/expressions.html#summary */ Blockly.Python.ORDER_ATOMIC = 0; // 0 "" ... Blockly.Python.ORDER_COLLECTION = 1; // tuples, lists, dictionaries Blockly.Python.ORDER_STRING_CONVERSION = 1; // `expression...` Blockly.Python.ORDER_MEMBER = 2.1; // . [] Blockly.Python.ORDER_FUNCTION_CALL = 2.2; // () Blockly.Python.ORDER_EXPONENTIATION = 3; // ** Blockly.Python.ORDER_UNARY_SIGN = 4; // + - Blockly.Python.ORDER_BITWISE_NOT = 4; // ~ Blockly.Python.ORDER_MULTIPLICATIVE = 5; // * / // % Blockly.Python.ORDER_ADDITIVE = 6; // + - Blockly.Python.ORDER_BITWISE_SHIFT = 7; // << >> Blockly.Python.ORDER_BITWISE_AND = 8; // & Blockly.Python.ORDER_BITWISE_XOR = 9; // ^ Blockly.Python.ORDER_BITWISE_OR = 10; // | Blockly.Python.ORDER_RELATIONAL = 11; // in, not in, is, is not, // <, <=, >, >=, <>, !=, == Blockly.Python.ORDER_LOGICAL_NOT = 12; // not Blockly.Python.ORDER_LOGICAL_AND = 13; // and Blockly.Python.ORDER_LOGICAL_OR = 14; // or Blockly.Python.ORDER_CONDITIONAL = 15; // if else Blockly.Python.ORDER_LAMBDA = 16; // lambda Blockly.Python.ORDER_NONE = 99; // (...) /** * Empty loops or conditionals are not allowed in Python. */ Blockly.Python.PASS = ' pass\n'; /* * Allow for switching between one and zero based indexing for lists and text, * one based by default. */ Blockly.Python.ONE_BASED_INDEXING = false; /** * List of outer-inner pairings that do NOT require parentheses. * @type {!Array.>} */ Blockly.Python.ORDER_OVERRIDES = [ // (foo()).bar -> foo().bar // (foo())[0] -> foo()[0] [Blockly.Python.ORDER_FUNCTION_CALL, Blockly.Python.ORDER_MEMBER], // (foo())() -> foo()() [Blockly.Python.ORDER_FUNCTION_CALL, Blockly.Python.ORDER_FUNCTION_CALL], // (foo.bar).baz -> foo.bar.baz // (foo.bar)[0] -> foo.bar[0] // (foo[0]).bar -> foo[0].bar // (foo[0])[1] -> foo[0][1] [Blockly.Python.ORDER_MEMBER, Blockly.Python.ORDER_MEMBER], // (foo.bar)() -> foo.bar() // (foo[0])() -> foo[0]() [Blockly.Python.ORDER_MEMBER, Blockly.Python.ORDER_FUNCTION_CALL], // not (not foo) -> not not foo [Blockly.Python.ORDER_LOGICAL_NOT, Blockly.Python.ORDER_LOGICAL_NOT], // a and (b and c) -> a and b and c [Blockly.Python.ORDER_LOGICAL_AND, Blockly.Python.ORDER_LOGICAL_AND], // a or (b or c) -> a or b or c [Blockly.Python.ORDER_LOGICAL_OR, Blockly.Python.ORDER_LOGICAL_OR] ]; /** * Initialise the database of variable names. * @param {!Blockly.Workspace} workspace Workspace to generate code from. */ Blockly.Python.init = function (workspace) { /** * Empty loops or conditionals are not allowed in Python. */ Blockly.Python.PASS = this.INDENT + 'pass\n'; // Create a dictionary of definitions to be printed before the code. Blockly.Python.definitions_ = Object.create(null); // Create a dictionary mapping desired function names in definitions_ // to actual function names (to avoid collisions with user functions). Blockly.Python.functionNames_ = Object.create(null); if (!Blockly.Python.variableDB_) { Blockly.Python.variableDB_ = new Blockly.Names(Blockly.Python.RESERVED_WORDS_); } else { Blockly.Python.variableDB_.reset(); } // Removed, because we shouldn't teach students to do this. /* var defvars = []; var variables = workspace.variableList; for (var i = 0; i < variables.length; i++) { defvars[i] = Blockly.Python.variableDB_.getName(variables[i], Blockly.Variables.NAME_TYPE) + ' = None'; } Blockly.Python.definitions_['variables'] = defvars.join('\n');*/ }; /** * Prepend the generated code with the variable definitions. * @param {string} code Generated code. * @return {string} Completed code. */ Blockly.Python.finish = function (code) { // Convert the definitions dictionary into a list. var imports = []; var definitions = []; for (var name in Blockly.Python.definitions_) { var def = Blockly.Python.definitions_[name]; if (def.match(/^(from\s+\S+\s+)?import\s+\S+/)) { imports.push(def); } else { definitions.push(def); } } // Clean up temporary data. delete Blockly.Python.definitions_; delete Blockly.Python.functionNames_; Blockly.Python.variableDB_.reset(); var allDefs = imports.join('\n') + '\n\n' + definitions.join('\n\n'); return allDefs.replace(/\n\n+/g, '\n\n').replace(/\n*$/, '\n\n\n') + code; }; /** * Naked values are top-level blocks with outputs that aren't plugged into * anything. * @param {string} line Line of generated code. * @return {string} Legal line of code. */ Blockly.Python.scrubNakedValue = function (line) { return line + '\n'; }; /** * Encode a string as a properly escaped Python string, complete with quotes. * @param {string} string Text to encode. * @return {string} Python string. * @private */ Blockly.Python.quote_ = function (string) { // Can't use goog.string.quote since % must also be escaped. string = string.replace(/\\/g, '\\\\') .replace(/\n/g, '\\\n'); if (string.indexOf('"') > -1 && string.indexOf('"') == -1) { return '\'' + string + '\''; } else if (string.indexOf('"') == -1 && string.indexOf('"') > -1) { return '"' + string + '"'; } else { string = string.replace(/"/g, '\\\"'); return '"' + string + '"'; } }; /** * Common tasks for generating Python from blocks. * Handles comments for the specified block and any connected value blocks. * Calls any statements following this block. * @param {!Blockly.Block} block The current block. * @param {string} code The Python code created for this block. * @return {string} Python code with comments and subsequent blocks added. * @private */ Blockly.Python.scrub_ = function (block, code) { var commentCode = ''; // Only collect comments for blocks that aren't inline. if (!block.outputConnection || !block.outputConnection.targetConnection) { // Collect comment for this block. var comment = block.getCommentText(); comment = Blockly.utils.wrap(comment, Blockly.Python.COMMENT_WRAP - 3); if (comment) { if (block.getProcedureDef) { // Use a comment block for function comments. //TODO: fixme //commentCode += '"""' + comment + '\n"""\n'; } else { commentCode += Blockly.Python.prefixLines(comment + '\n', '# '); } } // Collect comments for all value arguments. // Don't collect comments for nested statements. for (var i = 0; i < block.inputList.length; i++) { if (block.inputList[i].type == Blockly.INPUT_VALUE) { var childBlock = block.inputList[i].connection.targetBlock(); if (childBlock) { var comment = Blockly.Python.allNestedComments(childBlock); if (comment) { commentCode += Blockly.Python.prefixLines(comment, '# '); } } } } } var nextBlock = block.nextConnection && block.nextConnection.targetBlock(); var nextCode = Blockly.Python.blockToCode(nextBlock); return commentCode + code + nextCode; }; /** * Gets a property and adjusts the value, taking into account indexing, and * casts to an integer. * @param {!Blockly.Block} block The block. * @param {string} atId The property ID of the element to get. * @param {number=} opt_delta Value to add. * @param {boolean=} opt_negate Whether to negate the value. * @return {string|number} */ Blockly.Python.getAdjustedInt = function (block, atId, opt_delta, opt_negate) { var delta = opt_delta || 0; if (block.workspace.options.oneBasedIndex) { delta--; } var defaultAtIndex = block.workspace.options.oneBasedIndex ? '1' : '0'; var atOrder = delta ? Blockly.Python.ORDER_ADDITIVE : Blockly.Python.ORDER_NONE; var at = Blockly.Python.valueToCode(block, atId, atOrder) || defaultAtIndex; if (Blockly.isNumber(at)) { // If the index is a naked number, adjust it right now. at = parseInt(at, 10) + delta; if (opt_negate) { at = -at; } } else { // If the index is dynamic, adjust it in code. if (delta > 0) { at = 'int(' + at + ' + ' + delta + ')'; } else if (delta < 0) { at = 'int(' + at + ' - ' + -delta + ')'; } else { at = 'int(' + at + ')'; } if (opt_negate) { at = '-' + at; } } return at; }; /** * A list of types tasks that the pins can be assigned. Used to track usage and * warn if the same pin has been assigned to more than one task. */ Blockly.Python.PinTypes = { INPUT: 'INPUT', OUTPUT: 'OUTPUT', PWM: 'PWM', SERVO: 'SERVO', STEPPER: 'STEPPER', SERIAL: 'SERIAL', I2C: 'I2C/TWI', SPI: 'SPI', // extra setup FASTLED: 'FASTLED', DHT: 'DHT' }; /** * Arduino generator short name for * Blockly.Generator.prototype.FUNCTION_NAME_PLACEHOLDER_ * @type {!string} */ Blockly.Python.DEF_FUNC_NAME = Blockly.Python.FUNCTION_NAME_PLACEHOLDER_; /** * Initialises the database of global definitions, the setup function, function * names, and variable names. * @param {Blockly.Workspace} workspace Workspace to generate code from. */ Blockly.Python.init = function (workspace) { // Create a dictionary of definitions to be printed at the top of the sketch Blockly.Python.includes_ = Object.create(null); // Create a dictionary of global definitions to be printed after variables Blockly.Python.definitions_ = Object.create(null); // Create a dictionary of variables Blockly.Python.variables_ = Object.create(null); // Create a dictionary of functions from the code generator Blockly.Python.codeFunctions_ = Object.create(null); // Create a dictionary of functions created by the user Blockly.Python.userFunctions_ = Object.create(null); // Create a dictionary mapping desired function names in definitions_ // to actual function names (to avoid collisions with user functions) Blockly.Python.functionNames_ = Object.create(null); // Create a dictionary of setups to be printed in the setup() function Blockly.Python.setups_ = Object.create(null); // Create a dictionary of pins to check if their use conflicts Blockly.Python.pins_ = Object.create(null); if (!Blockly.Python.variableDB_) { Blockly.Python.variableDB_ = new Blockly.Names(Blockly.Python.RESERVED_WORDS_); } else { Blockly.Python.variableDB_.reset(); } // var defvars = []; // var variables = workspace.variableList; // for (var i = 0; i < variables.length; i++) { // defvars[i] = Blockly.Python.variableDB_.getName(variables[i], // Blockly.Variables.NAME_TYPE) + ' = None'; // } //Blockly.Python.definitions_['variables'] = defvars.join('\n'); // Iterate through to capture all blocks types and set the function arguments // var varsWithTypes = Blockly.Python.StaticTyping.collectVarsWithTypes(workspace); // Blockly.Python.StaticTyping.setProcedureArgs(workspace, varsWithTypes); // // Set variable declarations with their Arduino type in the defines dictionary // for (var varName in varsWithTypes) { // if (Blockly.Python.getArduinoType_(varsWithTypes[varName]) != "array") { // // console.log(Blockly.Python.getArduinoType_(varsWithTypes[varName])); // Blockly.Python.addVariable(varName, // Blockly.Python.getArduinoType_(varsWithTypes[varName]) + ' ' + // Blockly.Python.variableDB_.getName(varName, Blockly.Variables.NAME_TYPE) + ';'); // } // } }; // /** // * Prepare all generated code to be placed in the sketch specific locations. // * @param {string} code Generated main program (loop function) code. // * @return {string} Completed sketch code. // */ Blockly.Python.finish = function (code) { // Convert the includes, definitions, and functions dictionaries into lists var includes = [], definitions = [], variables = [], functions = []; for (var name in Blockly.Python.includes_) { includes.push(Blockly.Python.includes_[name]); } if (includes.length) { includes.push('\n'); } for (var name in Blockly.Python.variables_) { variables.push(Blockly.Python.variables_[name]); } if (variables.length) { variables.push('\n'); } for (var name in Blockly.Python.definitions_) { var def = Blockly.Python.definitions_[name]; if (def.match(/^(from\s+\S+\s+)?import\s+\S+/)) { includes.push(def); } else { definitions.push(def); } } if (definitions.length) { definitions.push('\n'); } for (var name in Blockly.Python.codeFunctions_) { functions.push(Blockly.Python.codeFunctions_[name]); } for (var name in Blockly.Python.userFunctions_) { functions.push(Blockly.Python.userFunctions_[name]); } if (functions.length) { functions.push('\n'); } // userSetupCode added at the end of the setup function without leading spaces var setups = [''], userSetupCode = ''; if (Blockly.Python.setups_['userSetupCode'] !== undefined) { userSetupCode = '\n' + Blockly.Python.setups_['userSetupCode']; delete Blockly.Python.setups_['userSetupCode']; } for (var name in Blockly.Python.setups_) { setups.push(Blockly.Python.setups_[name]); } if (userSetupCode) { setups.push(userSetupCode); } // Clean up temporary data delete Blockly.Python.includes_; delete Blockly.Python.definitions_; delete Blockly.Python.variables_; delete Blockly.Python.codeFunctions_; delete Blockly.Python.userFunctions_; delete Blockly.Python.functionNames_; delete Blockly.Python.setups_; delete Blockly.Python.pins_; Blockly.Python.variableDB_.reset(); var allDefs = includes.join('\n') + '\n\n' + definitions.join('\n') + '\n\n' + functions.join('\n') + '\n\n' + variables.join('\n') + '\n\n'; var setup = setups.join('\n') + '\n\n'; var loop = code + '\n\n'; // functions.join('\n').replace(/\n\n+/g, '\n\n') + return allDefs.replace(/\n\n+/g, '\n\n') + setup.replace(/\n\n+/g, '\n\n') + loop.replace(/\n\n+/g, '\n\n'); }; /** * Adds a string of "include" code to be added to the sketch. * Once a include is added it will not get overwritten with new code. * @param {!string} includeTag Identifier for this include code. * @param {!string} code Code to be included at the very top of the sketch. */ Blockly.Python.addInclude = function (includeTag, code) { if (Blockly.Python.includes_[includeTag] === undefined) { Blockly.Python.includes_[includeTag] = code; } }; /** * Adds a string of code to be declared globally to the sketch. * Once it is added it will not get overwritten with new code. * @param {!string} declarationTag Identifier for this declaration code. * @param {!string} code Code to be added below the includes. */ Blockly.Python.addDeclaration = function (declarationTag, code) { if (Blockly.Python.definitions_[declarationTag] === undefined) { Blockly.Python.definitions_[declarationTag] = code; } }; /** * Adds a string of code to declare a variable globally to the sketch. * Only if overwrite option is set to true it will overwrite whatever * value the identifier held before. * @param {!string} varName The name of the variable to declare. * @param {!string} code Code to be added for the declaration. * @param {boolean=} overwrite Flag to ignore previously set value. * @return {!boolean} Indicates if the declaration overwrote a previous one. */ Blockly.Python.addVariable = function (varName, code, overwrite) { var overwritten = false; if (overwrite || (Blockly.Python.variableDB_.dbReverse_[varName] === undefined)) { Blockly.Python.variables_[varName] = code; Blockly.mainWorkspace.createVariable(varName) // Blockly.Python.variableDB_.dbReverse_[varName] = true; // Blockly.Python.variableDB_.db_[varName + "_VARIABLE"] = varName; overwritten = true; } return overwritten; }; /** * Adds a string of code into the Arduino setup() function. It takes an * identifier to not repeat the same kind of initialisation code from several * blocks. If overwrite option is set to true it will overwrite whatever * value the identifier held before. * @param {!string} setupTag Identifier for the type of set up code. * @param {!string} code Code to be included in the setup() function. * @param {boolean=} overwrite Flag to ignore previously set value. * @return {!boolean} Indicates if the new setup code overwrote a previous one. */ Blockly.Python.addSetup = function (setupTag, code, overwrite) { var overwritten = false; if (overwrite || (Blockly.Python.setups_[setupTag] === undefined)) { Blockly.Python.setups_[setupTag] = code; overwritten = true; } return overwritten; }; /** * Adds a string of code as a function. It takes an identifier (meant to be the * function name) to only keep a single copy even if multiple blocks might * request this function to be created. * A function (and its code) will only be added on first request. * @param {!string} preferedName Identifier for the function. * @param {!string} code Code to be included in the setup() function. * @return {!string} A unique function name based on input name. */ Blockly.Python.addFunction = function (preferedName, code) { if (Blockly.Python.codeFunctions_[preferedName] === undefined) { var uniqueName = Blockly.Python.variableDB_.getDistinctName( preferedName, Blockly.Generator.NAME_TYPE); Blockly.Python.codeFunctions_[preferedName] = code.replace(Blockly.Python.DEF_FUNC_NAME, uniqueName); Blockly.Python.functionNames_[preferedName] = uniqueName; } return Blockly.Python.functionNames_[preferedName]; }; /** * Description. * @param {!Blockly.Block} block Description. * @param {!string} pin Description. * @param {!string} pinType Description. * @param {!string} warningTag Description. */ Blockly.Python.reservePin = function (block, pin, pinType, warningTag) { if (Blockly.Python.pins_[pin] !== undefined) { if (Blockly.Python.pins_[pin] != pinType) { block.setWarningText(Blockly.Msg.ARD_PIN_WARN1.replace('%1', pin) .replace('%2', warningTag).replace('%3', pinType) .replace('%4', Blockly.Python.pins_[pin]), warningTag); } else { block.setWarningText(null, warningTag); } } else { Blockly.Python.pins_[pin] = pinType; block.setWarningText(null, warningTag); } }; /** * Naked values are top-level blocks with outputs that aren't plugged into * anything. A trailing semicolon is needed to make this legal. * @param {string} line Line of generated code. * @return {string} Legal line of code. */ // Blockly.Python.scrubNakedValue = function(line) { // return line + ';\n'; // }; /** * Encode a string as a properly escaped Arduino string, complete with quotes. * @param {string} string Text to encode. * @return {string} Arduino string. * @private */ // Blockly.Python.quote_ = function(string) { // // TODO: This is a quick hack. Replace with goog.string.quote // string = string.replace(/\\/g, '\\\\') // .replace(/\n/g, '\\\n') // .replace(/\$/g, '\\$') // .replace(/'/g, '\\\''); // return '\"' + string + '\"'; // }; /** * Common tasks for generating Arduino from blocks. * Handles comments for the specified block and any connected value blocks. * Calls any statements following this block. * @param {!Blockly.Block} block The current block. * @param {string} code The Arduino code created for this block. * @return {string} Arduino code with comments and subsequent blocks added. * @this {Blockly.CodeGenerator} * @private */ // Blockly.Python.scrub_ = function(block, code) { // if (code === null) { return ''; } // Block has handled code generation itself // var commentCode = ''; // // Only collect comments for blocks that aren't inline // if (!block.outputConnection || !block.outputConnection.targetConnection) { // // Collect comment for this block. // var comment = block.getCommentText(); // if (comment) { // commentCode += this.prefixLines(comment, '// ') + '\n'; // } // // Collect comments for all value arguments // // Don't collect comments for nested statements // for (var x = 0; x < block.inputList.length; x++) { // if (block.inputList[x].type == Blockly.INPUT_VALUE) { // var childBlock = block.inputList[x].connection.targetBlock(); // if (childBlock) { // var comment = this.allNestedComments(childBlock); // if (comment) { // commentCode += this.prefixLines(comment, '// '); // } // } // } // } // } // var nextBlock = block.nextConnection && block.nextConnection.targetBlock(); // var nextCode = this.blockToCode(nextBlock); // return commentCode + code + nextCode; // }; /** * Generates Arduino Types from a Blockly Type. * @param {!Blockly.Type} typeBlockly The Blockly type to be converted. * @return {string} Arduino type for the respective Blockly input type, in a * string format. * @private */ Blockly.Python.getArduinoType_ = function (typeBlockly) { switch (typeBlockly.typeId) { case Blockly.Types.SHORT_NUMBER.typeId: return 'short'; case Blockly.Types.NUMBER.typeId: return 'int'; case Blockly.Types.LARGE_NUMBER.typeId: return 'long'; case Blockly.Types.DECIMAL.typeId: return 'float'; case Blockly.Types.TEXT.typeId: return 'String'; case Blockly.Types.CHARACTER.typeId: return 'char'; case Blockly.Types.BOOLEAN.typeId: return 'boolean'; case Blockly.Types.NULL.typeId: return 'void'; case Blockly.Types.UNDEF.typeId: return 'undefined'; case Blockly.Types.CHILD_BLOCK_MISSING.typeId: // If no block connected default to int, change for easier debugging //return 'ChildBlockMissing'; return 'int'; case Blockly.Types.ARRAY.typeId: return 'array'; default: return 'Invalid Blockly Type'; } }; /** Used for not-yet-implemented block code generators */ Blockly.Python.noGeneratorCodeInline = function () { return ['', Blockly.Python.ORDER_ATOMIC]; }; /** Used for not-yet-implemented block code generators */ Blockly.Python.noGeneratorCodeLine = function () { return ''; };