blockly-solidity / src / blocks / customblocks.js
customblocks.js
Raw
import * as Blockly from "blockly/core"

/**
 * @fileoverview Helper functions for generating Solidity for blocks.
 * @author jeanmarc.leroux@google.com (Jean-Marc Le Roux)
 * @author rekmarks@icloud.com  (Erik Marks)
 */
//  'use strict';

//  goog.provide('Blockly.Solidity');

//  goog.require('Blockly.Generator');


/**
 * Solidity code generator.
 * @type {!Blockly.Generator}
 */
Blockly.Solidity = new Blockly.Generator('Solidity');

Blockly.Solidity.LABEL_GROUP_STATE = "state";
Blockly.Solidity.LABEL_GROUP_PARAMETER = "parameter";
Blockly.Solidity.LABEL_GROUP_VARIABLE = "variable";
Blockly.Solidity.LABEL_GROUP_METHOD = "method";
Blockly.Solidity.UNDEFINED_NAME = "__UNDEFINED__";

/**
 * 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.Solidity.addReservedWords(
  'Blockly,' +  // In case JS is evaled in the current window.
  'abstract, after, case, catch, default, final, in, inline, let, match,' +
  'null, of, relocatable, static, switch, try, type, typeof' +
  "function,true,false,bool,int8,int,uint8,",
  "uint,address,bytes1,contract,constructor,uint8,",
  "override,virtual,indexed,anonymous,immutable,constant,payable,view,pure,",
  "public,private,external,internal,abi,bytes,block,gasleft,msg,tx,assert,require,",
  "revert,blockhash,keccak256,sha256,ripemd160,ecrecover,addmod,mulmod,this,",
  "super,selfdestruct,type"
);

/**
 * Order of operation ENUMs.
 * https://solidity.readthedocs.io/en/develop/miscellaneous.html#order
 * TODO: can this be further minimized?
 */
Blockly.Solidity.ORDER_ATOMIC = 0;           // 0 "" ...
// TODO: postfix increment/decrement
// https://stackoverflow.com/questions/7031326/what-is-the-difference-between-prefix-and-postfix-operators#7031409
Blockly.Solidity.ORDER_NEW = 1.1;            // new
Blockly.Solidity.ORDER_MEMBER = 1.2;         // . []
Blockly.Solidity.ORDER_FUNCTION_CALL = 1.3;  // ()
Blockly.Solidity.ORDER_PARENTHESES = 1.4;    // ()
// TODO: "Parentheses" ought to be 1.X
Blockly.Solidity.ORDER_INCREMENT = 2.1;      // ++ (prefix)
Blockly.Solidity.ORDER_DECREMENT = 2.2;      // -- (prefix)
Blockly.Solidity.ORDER_UNARY_PLUS = 2.3;     // +
Blockly.Solidity.ORDER_UNARY_NEGATION = 2.4; // -
Blockly.Solidity.ORDER_TYPEOF = 2.5;         // typeof
Blockly.Solidity.ORDER_VOID = 2.6;           // void
Blockly.Solidity.ORDER_DELETE = 2.7;         // delete
Blockly.Solidity.ORDER_LOGICAL_NOT = 2.8;    // !
Blockly.Solidity.ORDER_BITWISE_NOT = 2.9;    // ~
Blockly.Solidity.ORDER_EXPONENTATION = 3;    // **
Blockly.Solidity.ORDER_MULTIPLICATION = 4.1; // *
Blockly.Solidity.ORDER_DIVISION = 4.2;       // /
Blockly.Solidity.ORDER_MODULUS = 4.3;        // %
Blockly.Solidity.ORDER_SUBTRACTION = 5.1;    // -
Blockly.Solidity.ORDER_ADDITION = 5.2;       // +
Blockly.Solidity.ORDER_BITWISE_SHIFT = 6;    // << >> >>>
Blockly.Solidity.ORDER_BITWISE_AND = 7;      // &
Blockly.Solidity.ORDER_BITWISE_XOR = 8;      // ^
Blockly.Solidity.ORDER_BITWISE_OR = 9;       // |
Blockly.Solidity.ORDER_RELATIONAL = 10       // < <= > >=
Blockly.Solidity.ORDER_EQUALITY = 11;        // == != === !==
Blockly.Solidity.ORDER_LOGICAL_AND = 12;     // &&
Blockly.Solidity.ORDER_LOGICAL_OR = 13;      // ||
Blockly.Solidity.ORDER_CONDITIONAL = 14;     // ?:
Blockly.Solidity.ORDER_ASSIGNMENT = 15;      // = += -= *= /= %= <<= >>= ...
Blockly.Solidity.ORDER_COMMA = 16;           // ,
Blockly.Solidity.ORDER_NONE = 99;            // (...)

/**
 * List of outer-inner pairings that do NOT require parentheses.
 * @type {!Array.<!Array.<number>>}
 */
Blockly.Solidity.ORDER_OVERRIDES = [
  // (foo()).bar -> foo().bar
  // (foo())[0] -> foo()[0]
  [Blockly.Solidity.ORDER_FUNCTION_CALL, Blockly.Solidity.ORDER_MEMBER],
  // (foo())() -> foo()()
  [Blockly.Solidity.ORDER_FUNCTION_CALL, Blockly.Solidity.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.Solidity.ORDER_MEMBER, Blockly.Solidity.ORDER_MEMBER],
  // (foo.bar)() -> foo.bar()
  // (foo[0])() -> foo[0]()
  [Blockly.Solidity.ORDER_MEMBER, Blockly.Solidity.ORDER_FUNCTION_CALL],

  // !(!foo) -> !!foo
  [Blockly.Solidity.ORDER_LOGICAL_NOT, Blockly.Solidity.ORDER_LOGICAL_NOT],
  // a * (b * c) -> a * b * c
  [Blockly.Solidity.ORDER_MULTIPLICATION, Blockly.Solidity.ORDER_MULTIPLICATION],
  // a + (b + c) -> a + b + c
  [Blockly.Solidity.ORDER_ADDITION, Blockly.Solidity.ORDER_ADDITION],
  // a && (b && c) -> a && b && c
  [Blockly.Solidity.ORDER_LOGICAL_AND, Blockly.Solidity.ORDER_LOGICAL_AND],
  // a || (b || c) -> a || b || c
  [Blockly.Solidity.ORDER_LOGICAL_OR, Blockly.Solidity.ORDER_LOGICAL_OR],
  // ((foo[0])) -> foo[0]
  // ((foo.bar)) -> foo.bar
  [Blockly.Solidity.ORDER_MEMBER, Blockly.Solidity.ORDER_PARENTHESES],
  // (foo()) -> foo()
  [Blockly.Solidity.ORDER_FUNCTION_CALL, Blockly.Solidity.ORDER_PARENTHESES]
];

/**
 * Initialise the database of variable names.
 * @param {!Blockly.Workspace} workspace Workspace to generate code from.
 */
Blockly.Solidity.init = function (workspace) {
  // Create a dictionary of definitions to be printed before the code.
  Blockly.Solidity.definitions_ = Object.create(null);
  // Create a dictionary mapping desired function names in definitions_
  // to actual function names (to avoid collisions with user functions).
  Blockly.Solidity.functionNames_ = Object.create(null);

  if (!Blockly.Solidity.variableDB_) {
    Blockly.Solidity.variableDB_ =
      new Blockly.Names(Blockly.Solidity.RESERVED_WORDS_);
  } else {
    Blockly.Solidity.variableDB_.reset();
  }

  // var defvars = [];
  // var variables = workspace.getAllVariables();
  // if (variables.length) {
  //   for (var i = 0; i < variables.length; i++) {
  //     defvars[i] = Blockly.Solidity.variableDB_.getName(variables[i].name,
  //         Blockly.Variables.NAME_TYPE);
  //   }
  //   Blockly.Solidity.definitions_['variables'] =
  //       'int ' + defvars.join(', ') + ';';
  // }
};

/**
 * Prepend the generated code with the variable definitions.
 * @param {string} code Generated code.
 * @return {string} Completed code.
 */
Blockly.Solidity.finish = function (code) {
  // Convert the definitions dictionary into a list.
  var definitions = [];
  for (var name in Blockly.Solidity.definitions_) {
    definitions.push(Blockly.Solidity.definitions_[name]);
  }
  // Clean up temporary data.
  delete Blockly.Solidity.definitions_;
  delete Blockly.Solidity.functionNames_;
  Blockly.Solidity.variableDB_.reset();
  return definitions.join('\n\n') + '\n\n\n' + code;
};

/**
 * 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.Solidity.scrubNakedValue = function (line) {
  return line + '\n';
};

/**
 * Encode a string as a properly escaped Solidity string, complete with
 * quotes.
 * @param {string} string Text to encode.
 * @return {string} Solidity string.
 * @private
 */
Blockly.Solidity.quote_ = function (string) {
  // Can't use goog.string.quote since Google's style guide recommends
  // JS string literals use single quotes.
  string = string.replace(/\\/g, '\\\\')
    .replace(/\n/g, '\\\n')
    .replace(/'/g, '\\\'');
  return '\'' + string + '\'';
};

/**
 * Common tasks for generating Solidity 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 Solidity code created for this block.
 * @return {string} Solidity code with comments and subsequent blocks added.
 * @private
 */
Blockly.Solidity.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 ? Blockly.utils.wrap(comment, Blockly.Solidity.COMMENT_WRAP - 3) : '';
    if (comment) {
      if (block.getProcedureDef) {
        // Use a comment block for function comments.
        commentCode += '/**\n' +
          Blockly.Solidity.prefixLines(comment + '\n', ' * ') +
          ' */\n';
      } else {
        commentCode += Blockly.Solidity.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.Solidity.allNestedComments(childBlock);
          if (comment) {
            commentCode += Blockly.Solidity.prefixLines(comment, '// ');
          }
        }
      }
    }
  }
  var nextBlock = block.nextConnection && block.nextConnection.targetBlock();
  var nextCode = Blockly.Solidity.blockToCode(nextBlock);
  return commentCode + code + nextCode;
};

/**
 * Gets a property and adjusts the value while taking into account indexing.
 * @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.
 * @param {number=} opt_order The highest order acting on this value.
 * @return {string|number}
 */
Blockly.Solidity.getAdjusted = function (block, atId, opt_delta, opt_negate,
  opt_order) {
  var delta = opt_delta || 0;
  var order = opt_order || Blockly.Solidity.ORDER_NONE;
  if (block.workspace.options.oneBasedIndex) {
    delta--;
  }
  var defaultAtIndex = block.workspace.options.oneBasedIndex ? '1' : '0';
  if (delta > 0) {
    var at = Blockly.Solidity.valueToCode(block, atId,
      Blockly.Solidity.ORDER_ADDITION) || defaultAtIndex;
  } else if (delta < 0) {
    var at = Blockly.Solidity.valueToCode(block, atId,
      Blockly.Solidity.ORDER_SUBTRACTION) || defaultAtIndex;
  } else if (opt_negate) {
    var at = Blockly.Solidity.valueToCode(block, atId,
      Blockly.Solidity.ORDER_UNARY_NEGATION) || defaultAtIndex;
  } else {
    var at = Blockly.Solidity.valueToCode(block, atId, order) ||
      defaultAtIndex;
  }

  if (Blockly.isNumber(at)) {
    // If the index is a naked number, adjust it right now.
    at = parseFloat(at) + delta;
    if (opt_negate) {
      at = -at;
    }
  } else {
    // If the index is dynamic, adjust it in code.
    if (delta > 0) {
      at = at + ' + ' + delta;
      var innerOrder = Blockly.Solidity.ORDER_ADDITION;
    } else if (delta < 0) {
      at = at + ' - ' + -delta;
      var innerOrder = Blockly.Solidity.ORDER_SUBTRACTION;
    }
    if (opt_negate) {
      if (delta) {
        at = '-(' + at + ')';
      } else {
        at = '-' + at;
      }
      var innerOrder = Blockly.Solidity.ORDER_UNARY_NEGATION;
    }
    innerOrder = Math.floor(innerOrder);
    order = Math.floor(order);
    if (innerOrder && order >= innerOrder) {
      at = '(' + at + ')';
    }
  }
  return at;
};

Blockly.Solidity.updateWorkspaceNameFields = function (workspace) {
  var blocks = workspace.getAllBlocks();
  for (var i = 0; i < blocks.length; ++i) {
    var nameField = blocks[i].getVariableNameSelectField
      ? blocks[i].getVariableNameSelectField()
      : null;
    var group = blocks[i].getVariableLabelGroup
      ? blocks[i].getVariableLabelGroup()
      : null;

    if (!!nameField && !!group) {
      var vars = Blockly.Solidity.getVariablesInScope(blocks[i], group);
      var options = vars.map(function (v) {
        return [Blockly.Solidity.getVariableName(v), v.id_];
      });

      var selectedOption = nameField.getValue();

      console.log("selected option => ", selectedOption);
      if (options.length != 0) {
        var wasUndefined = nameField.menuGenerator_[0][1]
          == Blockly.Solidity.UNDEFINED_NAME;

        nameField.menuGenerator_ = options;
        if (wasUndefined) {
          nameField.setValue(options[0][1]);
        } else {
          nameField.setValue(selectedOption);
          // The text input does not redraw/update itself after we call "setValue",
          // so we set the text manually.
          // nameField.setText(
          //   options.filter(function (o) { return o[1] == selectedOption })[0][0]
          // );
        }
      }
    }
  }
};

Blockly.Solidity.updateWorkspaceTypes = function (workspace, nameFieldName, valueFieldName) {
  var blocks = workspace.getAllBlocks();
  var vars = workspace.getAllVariables();

  for (var i = 0; i < blocks.length; ++i) {
    var stateNameField = blocks[i].getField(nameFieldName);

    if (!stateNameField) {
      continue;
    }

    var variableId = blocks[i].getFieldValue(nameFieldName);
    var variable = workspace.getVariableById(variableId);

    if (!variable) {
      return;
    }

    if (blocks[i].inputList[0] && blocks[i].inputList[0].name == valueFieldName) {
      switch (variable.type) {
        case 'TYPE_BOOL':
          blocks[i].inputList[0].setCheck("Boolean");
          break;
        case 'TYPE_INT':
          blocks[i].inputList[0].setCheck("Number");
          break;
        case 'TYPE_UINT':
          blocks[i].inputList[0].setCheck("Number");
          break;
        case 'TYPE_STRING':
          blocks[i].inputList[0].setCheck("String");
          break;
        case 'TYPE_ADDRESS':
          blocks[i].inputList[0].setCheck("String");
          break;
        default:
      }
    }
    // TODO: update the output type
  }
};

Blockly.Solidity.updateWorkspaceStateTypes = function (workspace) {
  Blockly.Solidity.updateWorkspaceTypes(
    workspace,
    'STATE_NAME',
    'STATE_VALUE'
  );
};

Blockly.Solidity.updateWorkspaceParameterTypes = function (workspace) {
  Blockly.Solidity.updateWorkspaceTypes(
    workspace,
    'PARAM_NAME',
    'PARAM_VALUE'
  );
};

Blockly.Solidity.createVariable = function (workspace, group, type, name, scope, id) {
  var variable = workspace.createVariable(name, type, id);

  variable.group = group;
  variable.scope = scope;

  Blockly.Solidity.setVariableName(variable, name);

  return variable;
};

Blockly.Solidity.getVariableById = function (workspace, id) {
  return workspace.getVariableById(id);
};

Blockly.Solidity.getVariableByName = function (workspace, name) {
  return Blockly.Solidity.getAllVariables(workspace)
    .filter(function (v) { return Blockly.Solidity.getVariableName(v) == name; })
  [0];
};

Blockly.Solidity.getVariableByNameAndScope = function (name, scope, group = null) {
  return Blockly.Solidity.getVariablesInScope(scope, group)
    .filter(function (v) { return Blockly.Solidity.getVariableName(v) == name; })
  [0];
};

Blockly.Solidity.deleteVariableById = function (workspace, id) {
  Blockly.Solidity.deleteVariableByName(
    workspace,
    Blockly.Solidity.getVariableById(workspace, id).name
  );
}

Blockly.Solidity.deleteVariableByName = function (workspace, name) {
  return workspace.deleteVariable(name);
};

Blockly.Solidity.variableIsInScope = function (variable, scope) {
  while (!!scope && scope.id != variable.scope.id) {
    var type = scope.type;
    do {
      scope = scope.getParent();
    } while (scope && type == scope.type)
  }

  return !!scope;
};

Blockly.Solidity.setVariableName = function (variable, name) {
  variable.name = '_scope("' + variable.scope.id + '")_' + name;
}

Blockly.Solidity.getVariableName = function (variable) {
  return variable.name.replace('_scope("' + variable.scope.id + '")_', '');
}

Blockly.Solidity.getAllVariables = function (workspace) {
  return workspace.getAllVariables();
};

Blockly.Solidity.getVariablesInScope = function (block, group = null) {
  return Blockly.Solidity.getAllVariables(block.workspace)
    .filter(function (v) { return Blockly.Solidity.variableIsInScope(v, block); })
    .filter(function (v) { return !group || v.group == group });
};


Blockly.Extensions.register("declare_typed_variable", function () {
  var block = this;

  if (!this.getVariableNameField) {
    throw "missing getVariableNameField method";
  }

  if (!this.getVariableType) {
    throw "missing getVariableType method";
  }

  if (!this.getVariableGroup) {
    throw "missing getVariableGroup method";
  }

  if (!this.getVariableScope) {
    throw "missing getVariableScope method";
  }

  this.declareOrUpdateVariable = function (name, force = false) {
    var oldName = this.getVariableNameField().getValue();

    if (!this.getParent()) {
      return oldName;
    }

    if (!force && (!this.getParent() || oldName == name)) {
      return oldName;
    }

    var group = this.getVariableGroup();
    var scope = this.getVariableScope();
    var type = this.getVariableType();

    if (!Blockly.Solidity.getVariableByNameAndScope(name, scope, group)) {
      newName = name;
    } else {
      var count = 2;
      var newName = name + count;
      while (
        Blockly.Solidity.getVariableByNameAndScope(newName, scope, group)
      ) {
        count++;
        newName = name + count;
      }
    }

    var variable = Blockly.Solidity.getVariableById(this.workspace, this.id);
    if (!variable) {
      Blockly.Solidity.createVariable(
        this.workspace,
        group,
        type,
        newName,
        scope,
        this.id
      );
    } else {
      variable.name = newName;
    }

    if (force) {
      this.getVariableNameField().setValue(newName);
    }

    Blockly.Solidity.updateWorkspaceNameFields(this.workspace);

    return newName;
  };

  this.getVariableNameField().setValidator(function (name) {
    return block.declareOrUpdateVariable(name);
  });

  var onchange = null;
  // if (goog.isFunction(this.onchange)) {
  //   onchange = this.onchange;
  // }

  this.setOnChange(function (event) {
    Blockly.Solidity.updateWorkspaceNameFields(this.workspace);
    Blockly.Solidity.updateWorkspaceStateTypes(this.workspace);
    Blockly.Solidity.updateWorkspaceParameterTypes(this.workspace);

    if (event.blockId != this.id) {
      return;
    }

    if (event.type == "move" && !!event.oldParentId) {
      if (!!Blockly.Solidity.getVariableById(this.workspace, this.id)) {
        this.workspace.deleteVariableById(this.id)
      }
    }
    if (event.type == "move" && !!event.newParentId) {
      if (!this.workspace.getVariableById(this.id)) {
        this.declareOrUpdateVariable(
          this.getVariableNameField().getValue(),
          true
        );
      }
    }
    if (event.element == "field" && event.name == "TYPE") {
      var variable = this.workspace.getVariableById(this.id);

      variable.type = this.getVariableType();
      Blockly.Solidity.updateWorkspaceStateTypes(this.workspace);
    }

    if (!!onchange) {
      onchange.call(block, event);
    }
  });
});

Blockly.defineBlocksWithJsonArray([
  {
    type: "contract",
    message0: "smart contract %1",
    args0: [
      {
        type: "field_input",
        name: "NAME",
        check: "String",
        text: "MyContract",
      },
    ],
    message1: "states %1",
    args1: [
      {
        type: "input_statement",
        name: "STATES",
        check: ["contract_state"],
        align: "RIGHT",
      },
    ],
    message2: "constructor %1",
    args2: [
      {
        type: "input_statement",
        name: "CTOR",
        check: ["contract_ctor"],
        align: "RIGHT",
      },
    ],
    message3: "methods %1",
    args3: [
      {
        type: "input_statement",
        name: "METHODS",
        check: ["contract_method"],
        align: "RIGHT",
      },
    ],
    colour: 160,
    tooltip: "Declares a new smart contract.",
  },
]);

Blockly.Blocks["contract_state"] = {
  init: function () {
    var nameField = new Blockly.FieldTextInput("s");
    this.appendDummyInput()
      .appendField(
        new Blockly.FieldDropdown([
          ["bool", "TYPE_BOOL"],
          ["int", "TYPE_INT"],
          ["uint", "TYPE_UINT"],
          ["string", "TYPE_STRING"],
          ["address", "TYPE_ADDRESS"],
        ]),
        "TYPE"
      )
      .appendField(nameField, "NAME");
    this.setPreviousStatement(true, "contract_state");
    this.setNextStatement(true, "contract_state");
    this.setColour(195);
    this.contextMenu = false;

    this._stateNameInitialized = false;

    this.getVariableNameField = function () {
      return nameField;
    };
    this.getVariableType = function () {
      return this.getFieldValue("TYPE");
    };
    this.getVariableGroup = function () {
      return Blockly.Solidity.LABEL_GROUP_STATE;
    };
    this.getVariableScope = function () {
      var scope = this.getParent();
      while (!!scope && scope.type != "contract") {
        scope = scope.getParent();
      }
      return scope;
    };

    Blockly.Extensions.apply("declare_typed_variable", this, false);
  },
};

Blockly.Blocks["contract_state_get"] = {
  init: function () {
    this.appendDummyInput().appendField(
      new Blockly.FieldDropdown([
        ["select state...", Blockly.Solidity.UNDEFINED_NAME],
      ]),
      "STATE_NAME"
    );
    this.setOutput(true, null);
    this.setColour(195);

    this.getVariableNameSelectField = function () {
      return this.getField("STATE_NAME");
    };
    this.getVariableLabelGroup = function () {
      return Blockly.Solidity.LABEL_GROUP_STATE;
    };
  },
};

Blockly.Blocks["contract_state_set"] = {
  init: function () {
    this.appendValueInput("STATE_VALUE")
      .appendField("set")
      .appendField(
        new Blockly.FieldDropdown(
          [["select state...", Blockly.Solidity.UNDEFINED_NAME]],
          this.validate
        ),
        "STATE_NAME"
      )
      .appendField("to");
    this.setPreviousStatement(true, null);
    this.setNextStatement(true, null);
    this.setColour(195);

    this.getVariableNameSelectField = function () {
      return this.getField("STATE_NAME");
    };
    this.getVariableLabelGroup = function () {
      return Blockly.Solidity.LABEL_GROUP_STATE;
    };
  },

  validate: function (stateNameVariableId) {
    var workspace = this.sourceBlock_.workspace;
    // FIXME: dirty hack to make sure updateWorkspaceStateTypes is called right after validate
    setTimeout(function () {
      Blockly.Solidity.updateWorkspaceStateTypes(workspace);
    }, 1);
    return stateNameVariableId;
  },
};

Blockly.Blocks["contract_method_parameter"] = {
  init: function () {
    var nameField = new Blockly.FieldTextInput("p");
    this.appendDummyInput()
      .appendField(
        new Blockly.FieldDropdown([
          ["bool", "TYPE_BOOL"],
          ["int", "TYPE_INT"],
          ["uint", "TYPE_UINT"],
          ["string", "TYPE_STRING"],
          ["address", "TYPE_ADDRESS"],
        ]),
        "TYPE"
      )
      .appendField(nameField, "NAME");
    this.setPreviousStatement(true, "contract_method_parameter");
    this.setNextStatement(true, "contract_method_parameter");
    this.setColour(320);
    this.contextMenu = false;

    this.getVariableNameField = function () {
      return nameField;
    };
    this.getVariableType = function () {
      return this.getFieldValue("TYPE");
    };
    this.getVariableGroup = function () {
      return Blockly.Solidity.LABEL_GROUP_PARAMETER;
    };
    this.getVariableScope = function () {
      var scope = this.getParent();
      while (
        !!scope &&
        scope.type != "contract_method" &&
        scope.type != "contract_ctor"
      ) {
        scope = scope.getParent();
      }
      return scope;
    };

    Blockly.Extensions.apply("declare_typed_variable", this, false);
  },
};

Blockly.Blocks["contract_method_parameter_get"] = {
  init: function () {
    this.appendDummyInput().appendField(
      new Blockly.FieldDropdown([
        ["select param...", Blockly.Solidity.UNDEFINED_NAME],
      ]),
      "PARAM_NAME"
    );
    this.setOutput(true, null);
    this.setColour(320);

    this.getVariableNameSelectField = function () {
      return this.getField("PARAM_NAME");
    };
    this.getVariableLabelGroup = function () {
      return Blockly.Solidity.LABEL_GROUP_PARAMETER;
    };
  },
};

Blockly.Blocks["contract_method"] = {
  init: function () {
    this.jsonInit({
      message0: "method %1",
      args0: [
        {
          type: "field_input",
          name: "NAME",
          text: "myMethod",
        },
      ],
      message1: "parameters %1",
      args1: [
        {
          type: "input_statement",
          name: "PARAMS",
          check: ["contract_method_parameter"],
          align: "RIGHT",
        },
      ],
      message2: "code %1",
      args2: [
        {
          type: "input_statement",
          name: "STACK",
          align: "RIGHT",
        },
      ],
      previousStatement: "contract_method",
      nextStatement: "contract_method",
      colour: 290,
      tooltip: "",
      helpUrl: "",
    });

    this.getVariableNameField = function () {
      return this.getField("NAME");
    };
    this.getVariableType = function () {
      return "void";
    };
    this.getVariableGroup = function () {
      return Blockly.Solidity.LABEL_GROUP_METHOD;
    };
    this.getVariableScope = function () {
      var scope = this.getParent();
      while (!!scope && scope.type != "contract") {
        scope = scope.getParent();
      }
      return scope;
    };

    Blockly.Extensions.apply("declare_typed_variable", this, false);
  },
};

Blockly.Blocks['contract_method_call'] = {
  init: function () {
    this.appendDummyInput()
      .appendField('call method')
      .appendField(
        new Blockly.FieldDropdown(
          [["select method...", Blockly.Solidity.UNDEFINED_NAME]]
        ),
        "METHOD_NAME"
      );
    this.setPreviousStatement(true, null);
    this.setNextStatement(true, null);
    // this.setOutput(true, null);
    this.setColour(320);
    this.getVariableNameSelectField = function () { return this.getField('METHOD_NAME'); };
    this.getVariableLabelGroup = function () { return Blockly.Solidity.LABEL_GROUP_METHOD };

    this.setOnChange(function (event) {
      if (event.blockId != this.id) {
        return;
      }

      if (event.element == 'field' && event.name == 'METHOD_NAME') {
        var methodId = this.getFieldValue('METHOD_NAME');
        var methodBlock = this.workspace.getBlockById(methodId);

        var block = methodBlock;
        // Remove the old input (so that you don't have inputs stack repeatedly)
        for (let i = 0; i < this.params_.length; i++) {
          if (this.getInput(`${i}`)) {
            this.removeInput(`${i}`);
          }
        }
        this.params_ = []
        do {
          block = block.getChildren()
            .filter(function (c) { return c.type == 'contract_method_parameter' })[0];

          if (block) {
            this.params_.push(block);
          }
        } while (block)
        this.updateShape_()
        // FIXME: add/remove inputs according to the method params
      }
    });
  },
  params_: [],
  saveExtraState: function () {
    return {
      'params': this.params_,
    };
  },
  loadExtraState: function (state) {
    this.params_ = state['params'];
    // This is a helper function which adds or removes inputs from the block.
    this.updateShape_();
  },
  updateShape_: function () {
    for (let i = 0; i < this.params_.length; i++) {
      this.appendValueInput(`${i}`)
        .appendField(`${this.params_[i].getVariableType()} ${this.params_[i].getVariableNameField().value_}`)
      // FIXME: Modify the code in this loop so that we pass the name of the each parameter in order
      // instead of set each time. 
    }
  }

};

/**  ERROR HANDLING */
// Assert
Blockly.Blocks["assert"] = {
  init: function () {
    this.appendValueInput("NAME").setCheck(null).appendField("Assert")
    this.setInputsInline(false)
    this.setOutput(true, null)
    this.setColour(185)
    this.setTooltip("")
    this.setHelpUrl("")
  },
}

// Require
Blockly.Blocks["require"] = {
  init: function () {
    this.appendValueInput("require").setCheck(null).appendField("require")
    this.setInputsInline(false)
    this.setOutput(true, null)
    this.setColour(285)
    this.setTooltip("")
    this.setHelpUrl("")
  },
}

// Revert
Blockly.Blocks["revert"] = {
  init: function () {
    this.appendValueInput("revert").setCheck(null).appendField("revert")
    this.setInputsInline(false)
    this.setOutput(true, null)
    this.setColour(210)
    this.setTooltip("")
    this.setHelpUrl("")
  },
}

Blockly.defineBlocksWithJsonArray([
  {
    type: "contract_ctor",
    message0: "constructor",
    message1: "parameters %1",
    args1: [
      {
        type: "input_statement",
        name: "PARAMS",
        check: "contract_method_parameter",
        align: "RIGHT",
      },
    ],
    message2: "code %1",
    args2: [
      {
        type: "input_statement",
        name: "STACK",
        align: "RIGHT",
      },
    ],
    previousStatement: ["contract_ctor"],
    colour: 290,
    tooltip: "",
    helpUrl: "",
  },
]);

Blockly.defineBlocksWithJsonArray([
  {
    type: "contract_intrinsic_sha3",
    message0: "sha3 %1",
    args0: [
      {
        type: "input_value",
        name: "VALUE",
      },
    ],
    output: null,
    colour: 60,
    tooltip: "",
    helpUrl: "",
  },
]);

Blockly.Blocks["controls_for"] = {
  init: function () {
    this.jsonInit({
      message0: "%{BKY_CONTROLS_FOR_TITLE}",
      args0: [
        {
          type: "field_input",
          name: "VAR",
          text: "i",
        },
        {
          type: "input_value",
          name: "FROM",
          check: "Number",
          align: "RIGHT",
        },
        {
          type: "input_value",
          name: "TO",
          check: "Number",
          align: "RIGHT",
        },
        {
          type: "input_value",
          name: "BY",
          check: "Number",
          align: "RIGHT",
        },
      ],
      message1: "%{BKY_CONTROLS_REPEAT_INPUT_DO} %1",
      args1: [
        {
          type: "input_statement",
          name: "DO",
        },
      ],
      inputsInline: true,
      previousStatement: null,
      nextStatement: null,
      colour: "%{BKY_LOOPS_HUE}",
      helpUrl: "%{BKY_CONTROLS_FOR_HELPURL}",
    });

    this.getVariableNameField = function () {
      return this.getField("VAR");
    };
    this.getVariableType = function () {
      return "TYPE_UINT";
    };
    this.getVariableGroup = function () {
      return Blockly.Solidity.LABEL_GROUP_VARIABLE;
    };
    this.getVariableScope = function () {
      return this;
    };

    Blockly.Extensions.apply("declare_typed_variable", this, false);
  },
};