static_typing.js 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. /**
  2. * @license Licensed under the Apache License, Version 2.0 (the "License"):
  3. * http://www.apache.org/licenses/LICENSE-2.0
  4. */
  5. /**
  6. * @fileoverview Object that defines static objects and methods to assign
  7. * Blockly types to Blockly blocks. These can then be converted to language
  8. * specific types in each language generator.
  9. */
  10. 'use strict';
  11. goog.provide('Blockly.StaticTyping');
  12. goog.require('Blockly.Block');
  13. goog.require('Blockly.Type');
  14. goog.require('Blockly.Types');
  15. goog.require('Blockly.Workspace');
  16. goog.require('goog.asserts');
  17. /**
  18. * Class for Static Typing.
  19. * @constructor
  20. */
  21. Blockly.StaticTyping = function() {
  22. this.varTypeDict = Object.create(null);
  23. this.pendingVarTypeDict = Object.create(null);
  24. };
  25. /**
  26. * Navigates through all the statement blocks, collecting all variables and
  27. * their type into an associative array with the variable names as the keys and
  28. * the type as the values.
  29. * @param {Blockly.Workspace} workspace Blockly Workspace to collect variables.
  30. * @return {Object{ String: Blockly.Type, } Associative array with the variable
  31. * names as the keys and the type as the values.
  32. */
  33. Blockly.StaticTyping.prototype.collectVarsWithTypes = function(workspace) {
  34. this.varTypeDict = Object.create(null);
  35. this.pendingVarTypeDict = Object.create(null);
  36. var blocks = Blockly.StaticTyping.getAllStatementsOrdered(workspace);
  37. for (var i = 0; i < blocks.length; i++) {
  38. //blocks[i].select(); // for step debugging, highlights block in workspace
  39. // Each statement block iterates through its input children collecting vars
  40. var blockVarAndTypes = Blockly.StaticTyping.getBlockVars(blocks[i]);
  41. for (var j = 0; j < blockVarAndTypes.length; j++) {
  42. var variableName = blockVarAndTypes[j][0];
  43. var variableType = blockVarAndTypes[j][1];
  44. // If the type comes from a variable, so it's not directly defined, it
  45. // returns an Array<String(block type), String(source variable name)>
  46. if (goog.isArray(variableType)) {
  47. if (this.varTypeDict[variableType[1]]) {
  48. variableType = this.varTypeDict[variableType[1]];
  49. } else {
  50. // Dependant variable undefined, add this var to the pending list
  51. if (!goog.isArray(this.pendingVarTypeDict[variableType[1]])) {
  52. this.pendingVarTypeDict[variableType[1]] = [variableName];
  53. } else {
  54. this.pendingVarTypeDict[variableType[1]].push(variableName);
  55. }
  56. variableType = Blockly.Types.UNDEF;
  57. }
  58. }
  59. this.assignTypeToVars(blocks[i], variableName, variableType);
  60. }
  61. }
  62. return this.varTypeDict;
  63. };
  64. /**
  65. * Navigates through each top level block in the workspace to collect all
  66. * statement blocks, ordered from top left.
  67. * @param {Blockly.Workspace} workspace Blockly Workspace to collect blocks.
  68. * @return {Array<Blockly.Block>} Array containing all workspace statement
  69. * blocks.
  70. */
  71. Blockly.StaticTyping.getAllStatementsOrdered = function(workspace) {
  72. if (!workspace.getTopBlocks) {
  73. throw 'Not a valid workspace: ' + workspace;
  74. }
  75. /**
  76. * Navigates through each continuous block to collect all statement blocks.
  77. * Function required to use recursion for block input statements.
  78. * @param {Blockly.Block} startBlock Block to start iterating from..
  79. * @return {Array<Blockly.Block>} Array containing all continuous statement
  80. * blocks.
  81. */
  82. var getAllContinuousStatements = function(startBlock) {
  83. var block = startBlock;
  84. var nextBlock = null;
  85. var connections = null;
  86. var blockNextConnection = null;
  87. var blocks = [];
  88. do {
  89. //block.select(); // for step debugging, highlights block in workspace
  90. blocks.push(block);
  91. blockNextConnection = block.nextConnection;
  92. connections = block.getConnections_();
  93. block = null;
  94. for (var j = 0; j < connections.length; j++) {
  95. if (connections[j].type == Blockly.NEXT_STATEMENT) {
  96. nextBlock = connections[j].targetBlock();
  97. if (nextBlock) {
  98. // If it is the next connection select it and move to the next block
  99. if (connections[j] === blockNextConnection) {
  100. block = nextBlock;
  101. } else {
  102. // Recursion as block children can have their own input statements
  103. blocks = blocks.concat(getAllContinuousStatements(nextBlock));
  104. }
  105. }
  106. }
  107. }
  108. } while (block);
  109. return blocks;
  110. };
  111. var allStatementBlocks = [];
  112. var topBlocks = workspace.getTopBlocks(true);
  113. for (var i = 0; i < topBlocks.length; i++) {
  114. allStatementBlocks = allStatementBlocks.concat(
  115. getAllContinuousStatements(topBlocks[i]));
  116. }
  117. return allStatementBlocks;
  118. };
  119. /**
  120. * Retrieves the input argument block variables with their set type.
  121. * @param {Blockly.Block} block Block to retrieve variables from.
  122. * @return {Array<Array<String, Blockly.Type>>} Two dimensional array with the
  123. * block variable as the first item pair and variable type as the second.
  124. */
  125. Blockly.StaticTyping.getBlockVars = function(block) {
  126. var blockVarAndTypes = [];
  127. var getVars = block.getVars;
  128. if (getVars) {
  129. var blockVariables = getVars.call(block);
  130. // Iterate through the variables used in this block
  131. for (var i = 0; i < blockVariables.length; i++) {
  132. var varName = blockVariables[i];
  133. var getVarType = block.getVarType;
  134. if (getVarType) {
  135. var varType = getVarType.call(block, varName);
  136. blockVarAndTypes.push([varName, varType]);
  137. } else {
  138. blockVarAndTypes.push([varName, Blockly.Types.NULL]);
  139. }
  140. }
  141. } // else: !(block.getVars), block does not define variables, so do nothing
  142. return blockVarAndTypes;
  143. };
  144. /**
  145. * Manages the associative array of variables with their type.
  146. * @param {Blockly.Block} block Blockly providing the variable to manage.
  147. * @param {string} varName Name of the variable to manage.
  148. * @param {Blockly.Type} varType Type assigned by current block.
  149. */
  150. Blockly.StaticTyping.prototype.assignTypeToVars =
  151. function(block, varName, varType) {
  152. switch (this.varTypeDict[varName]) {
  153. // First time variable is encountered, or previously undefined
  154. case undefined:
  155. case Blockly.Types.UNDEF:
  156. this.varTypeDict[varName] = varType;
  157. if ((varType != Blockly.Types.UNDEF) &&
  158. (this.pendingVarTypeDict[varName] !== undefined)) {
  159. for (var i = 0; i < this.pendingVarTypeDict[varName].length; i++) {
  160. this.assignTypeToVars(
  161. block, this.pendingVarTypeDict[varName][i], varType);
  162. }
  163. }
  164. break;
  165. // Variable with valid type already registered
  166. default:
  167. this.setBlockTypeWarning(
  168. block, varType, varName, this.varTypeDict[varName]);
  169. break;
  170. }
  171. };
  172. /**
  173. * When a block uses a variable this function can compare its type with the
  174. * variable type and set a warning if they are not the same or compatible.
  175. * @param {!Blockly.Block} block The block to manage its warning.
  176. * @param {!Blockly.Type} blockType The type of this block.
  177. * @param {!string} varName The variable name.
  178. */
  179. Blockly.StaticTyping.prototype.setBlockTypeWarning =
  180. function(block, blockType, varName) {
  181. var warningLabel = 'varType';
  182. if ((blockType == Blockly.Types.CHILD_BLOCK_MISSING) ||
  183. (this.varTypeDict[varName] == Blockly.Types.CHILD_BLOCK_MISSING)) {
  184. // User still has to attach a block to this variable or its first
  185. // declaration, so for now do not display any warning
  186. block.setWarningText(null, warningLabel);
  187. } else if ((this.varTypeDict[varName] !== blockType) &&
  188. (blockType !== Blockly.Types.UNDEF)) {
  189. block.setWarningText('The variable ' + varName + ' has been first ' +
  190. 'assigned to the "' + this.varTypeDict[varName].typeName + '" type\n' +
  191. 'and this block tries to assign the type "' + blockType.typeName + '"!',
  192. warningLabel);
  193. } else {
  194. block.setWarningText(null, warningLabel);
  195. }
  196. };
  197. /**
  198. * Iterates through the list of top level blocks and sets the function arguments
  199. * types.
  200. * @param {Blockly.Workspace} workspace Blockly Workspace to collect variables.
  201. */
  202. Blockly.StaticTyping.prototype.setProcedureArgs = function(workspace) {
  203. var blocks = workspace.getTopBlocks();
  204. for (var i = 0, length_ = blocks.length; i < length_; i++) {
  205. var setArgsType = blocks[i].setArgsType;
  206. if (setArgsType) {
  207. setArgsType.call(blocks[i], this.varTypeDict);
  208. }
  209. }
  210. };