/** * @license Licensed under the Apache License, Version 2.0 (the "License"): * http://www.apache.org/licenses/LICENSE-2.0 */ /** * @fileoverview Object that defines static objects and methods to assign * Blockly types to Blockly blocks. These can then be converted to language * specific types in each language generator. */ 'use strict'; goog.provide('Blockly.StaticTyping'); goog.require('Blockly.Block'); goog.require('Blockly.Type'); goog.require('Blockly.Types'); goog.require('Blockly.Workspace'); goog.require('goog.asserts'); /** * Class for Static Typing. * @constructor */ Blockly.StaticTyping = function() { this.varTypeDict = Object.create(null); this.pendingVarTypeDict = Object.create(null); }; /** * Navigates through all the statement blocks, collecting all variables and * their type into an associative array with the variable names as the keys and * the type as the values. * @param {Blockly.Workspace} workspace Blockly Workspace to collect variables. * @return {Object{ String: Blockly.Type, } Associative array with the variable * names as the keys and the type as the values. */ Blockly.StaticTyping.prototype.collectVarsWithTypes = function(workspace) { this.varTypeDict = Object.create(null); this.pendingVarTypeDict = Object.create(null); var blocks = Blockly.StaticTyping.getAllStatementsOrdered(workspace); for (var i = 0; i < blocks.length; i++) { //blocks[i].select(); // for step debugging, highlights block in workspace // Each statement block iterates through its input children collecting vars var blockVarAndTypes = Blockly.StaticTyping.getBlockVars(blocks[i]); for (var j = 0; j < blockVarAndTypes.length; j++) { var variableName = blockVarAndTypes[j][0]; var variableType = blockVarAndTypes[j][1]; // If the type comes from a variable, so it's not directly defined, it // returns an Array if (goog.isArray(variableType)) { if (this.varTypeDict[variableType[1]]) { variableType = this.varTypeDict[variableType[1]]; } else { // Dependant variable undefined, add this var to the pending list if (!goog.isArray(this.pendingVarTypeDict[variableType[1]])) { this.pendingVarTypeDict[variableType[1]] = [variableName]; } else { this.pendingVarTypeDict[variableType[1]].push(variableName); } variableType = Blockly.Types.UNDEF; } } this.assignTypeToVars(blocks[i], variableName, variableType); } } return this.varTypeDict; }; /** * Navigates through each top level block in the workspace to collect all * statement blocks, ordered from top left. * @param {Blockly.Workspace} workspace Blockly Workspace to collect blocks. * @return {Array} Array containing all workspace statement * blocks. */ Blockly.StaticTyping.getAllStatementsOrdered = function(workspace) { if (!workspace.getTopBlocks) { throw 'Not a valid workspace: ' + workspace; } /** * Navigates through each continuous block to collect all statement blocks. * Function required to use recursion for block input statements. * @param {Blockly.Block} startBlock Block to start iterating from.. * @return {Array} Array containing all continuous statement * blocks. */ var getAllContinuousStatements = function(startBlock) { var block = startBlock; var nextBlock = null; var connections = null; var blockNextConnection = null; var blocks = []; do { //block.select(); // for step debugging, highlights block in workspace blocks.push(block); blockNextConnection = block.nextConnection; connections = block.getConnections_(); block = null; for (var j = 0; j < connections.length; j++) { if (connections[j].type == Blockly.NEXT_STATEMENT) { nextBlock = connections[j].targetBlock(); if (nextBlock) { // If it is the next connection select it and move to the next block if (connections[j] === blockNextConnection) { block = nextBlock; } else { // Recursion as block children can have their own input statements blocks = blocks.concat(getAllContinuousStatements(nextBlock)); } } } } } while (block); return blocks; }; var allStatementBlocks = []; var topBlocks = workspace.getTopBlocks(true); for (var i = 0; i < topBlocks.length; i++) { allStatementBlocks = allStatementBlocks.concat( getAllContinuousStatements(topBlocks[i])); } return allStatementBlocks; }; /** * Retrieves the input argument block variables with their set type. * @param {Blockly.Block} block Block to retrieve variables from. * @return {Array>} Two dimensional array with the * block variable as the first item pair and variable type as the second. */ Blockly.StaticTyping.getBlockVars = function(block) { var blockVarAndTypes = []; var getVars = block.getVars; if (getVars) { var blockVariables = getVars.call(block); // Iterate through the variables used in this block for (var i = 0; i < blockVariables.length; i++) { var varName = blockVariables[i]; var getVarType = block.getVarType; if (getVarType) { var varType = getVarType.call(block, varName); blockVarAndTypes.push([varName, varType]); } else { blockVarAndTypes.push([varName, Blockly.Types.NULL]); } } } // else: !(block.getVars), block does not define variables, so do nothing return blockVarAndTypes; }; /** * Manages the associative array of variables with their type. * @param {Blockly.Block} block Blockly providing the variable to manage. * @param {string} varName Name of the variable to manage. * @param {Blockly.Type} varType Type assigned by current block. */ Blockly.StaticTyping.prototype.assignTypeToVars = function(block, varName, varType) { switch (this.varTypeDict[varName]) { // First time variable is encountered, or previously undefined case undefined: case Blockly.Types.UNDEF: this.varTypeDict[varName] = varType; if ((varType != Blockly.Types.UNDEF) && (this.pendingVarTypeDict[varName] !== undefined)) { for (var i = 0; i < this.pendingVarTypeDict[varName].length; i++) { this.assignTypeToVars( block, this.pendingVarTypeDict[varName][i], varType); } } break; // Variable with valid type already registered default: this.setBlockTypeWarning( block, varType, varName, this.varTypeDict[varName]); break; } }; /** * When a block uses a variable this function can compare its type with the * variable type and set a warning if they are not the same or compatible. * @param {!Blockly.Block} block The block to manage its warning. * @param {!Blockly.Type} blockType The type of this block. * @param {!string} varName The variable name. */ Blockly.StaticTyping.prototype.setBlockTypeWarning = function(block, blockType, varName) { var warningLabel = 'varType'; if ((blockType == Blockly.Types.CHILD_BLOCK_MISSING) || (this.varTypeDict[varName] == Blockly.Types.CHILD_BLOCK_MISSING)) { // User still has to attach a block to this variable or its first // declaration, so for now do not display any warning block.setWarningText(null, warningLabel); } else if ((this.varTypeDict[varName] !== blockType) && (blockType !== Blockly.Types.UNDEF)) { block.setWarningText('The variable ' + varName + ' has been first ' + 'assigned to the "' + this.varTypeDict[varName].typeName + '" type\n' + 'and this block tries to assign the type "' + blockType.typeName + '"!', warningLabel); } else { block.setWarningText(null, warningLabel); } }; /** * Iterates through the list of top level blocks and sets the function arguments * types. * @param {Blockly.Workspace} workspace Blockly Workspace to collect variables. */ Blockly.StaticTyping.prototype.setProcedureArgs = function(workspace) { var blocks = workspace.getTopBlocks(); for (var i = 0, length_ = blocks.length; i < length_; i++) { var setArgsType = blocks[i].setArgsType; if (setArgsType) { setArgsType.call(blocks[i], this.varTypeDict); } } };