blockly-solidity / src / generator / generator.js
generator.js
Raw
/**
 * @license
 *
 * Copyright 2019 Google LLC
 *
 * 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 Define generation methods for custom blocks.
 * @author samelh@google.com (Sam El-Husseini)
 */

// More on generating code:
// https://developers.google.com/blockly/guides/create-custom-blocks/generating-code

import Blockly from "blockly";

export const Solidity = new Blockly.Generator("Solidity");

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

const objectUtils = Blockly.utils.object;
const stringUtils = Blockly.utils.string;

Solidity.isInitialized = false;

//incomplete list
Solidity.addReservedWords(
  "after,alias,apply,auto,byte,case,copyof,default," +
  "define,final,implements,in,inline,let,macro,match," +
  "mutable,null,of,partial,promise,reference,relocatable,sealed,",
  "sizeof,static,supports,switch,typedef,typeof,var",
  "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"
);
Solidity.ORDER_ATOMIC = 0; // 0 "" ...
Solidity.ORDER_NEW = 1.1; // new
Solidity.ORDER_MEMBER = 1.2; // . []
Solidity.ORDER_FUNCTION_CALL = 2; // ()
Solidity.ORDER_INCREMENT = 3; // ++
Solidity.ORDER_DECREMENT = 3; // --
Solidity.ORDER_BITWISE_NOT = 4.1; // ~
Solidity.ORDER_UNARY_PLUS = 4.2; // +
Solidity.ORDER_UNARY_NEGATION = 4.3; // -
Solidity.ORDER_LOGICAL_NOT = 4.4; // !
Solidity.ORDER_TYPEOF = 4.5; // typeof
Solidity.ORDER_VOID = 4.6; // void
Solidity.ORDER_DELETE = 4.7; // delete
Solidity.ORDER_DIVISION = 5.1; // /
Solidity.ORDER_MULTIPLICATION = 5.2; // *
Solidity.ORDER_MODULUS = 5.3; // %
Solidity.ORDER_SUBTRACTION = 6.1; // -
Solidity.ORDER_ADDITION = 6.2; // +
Solidity.ORDER_BITWISE_SHIFT = 7; // << >> >>>
Solidity.ORDER_RELATIONAL = 8; // < <= > >=
Solidity.ORDER_IN = 8; // in
Solidity.ORDER_INSTANCEOF = 8; // instanceof
Solidity.ORDER_EQUALITY = 9; // == != === !==
Solidity.ORDER_BITWISE_AND = 10; // &
Solidity.ORDER_BITWISE_XOR = 11; // ^
Solidity.ORDER_BITWISE_OR = 12; // |
Solidity.ORDER_LOGICAL_AND = 13; // &&
Solidity.ORDER_LOGICAL_OR = 14; // ||
Solidity.ORDER_CONDITIONAL = 15; // ?:
Solidity.ORDER_ASSIGNMENT = 16; // = += -= *= /= %= <<= >>= ...
Solidity.ORDER_COMMA = 17; // ,
Solidity.ORDER_NONE = 99; // (...)

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

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

/**
 * Whether the init method has been called.
 * @type {?boolean}
 */
Solidity.isInitialized = false;

/**
 * Initialise the database of variable names.
 * @param {!Workspace} workspace Workspace to generate code from.
 */
Solidity.init = function (workspace) {
  // Call Blockly.Generator's init.
  Object.getPrototypeOf(this).init.call(this);

  if (!this.nameDB_) {
    this.nameDB_ = new Blockly.Names(this.RESERVED_WORDS_);
  } else {
    this.nameDB_.reset();
  }
  this.nameDB_.setVariableMap(workspace.getVariableMap());
  this.nameDB_.populateVariables(workspace);
  this.nameDB_.populateProcedures(workspace);

  this.isInitialized = true;
};

/**
 * Prepend the generated code with the variable definitions.
 * @param {string} code Generated code.
 * @return {string} Completed code.
 */
Solidity.finish = function (code) {
  // Convert the definitions dictionary into a list.
  const definitions = objectUtils.values(this.definitions_);
  // Call Blockly.Generator's finish.
  code = Object.getPrototypeOf(this).finish.call(this, code);
  this.isInitialized = false;

  this.nameDB_.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.
 */
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.
 * @protected
 */
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 + "'";
};

/**
 * Encode a string as a properly escaped multiline Solidity string, complete
 * with quotes.
 * @param {string} string Text to encode.
 * @return {string} Solidity string.
 * @protected
 */
Solidity.multiline_quote_ = function (string) {
  // Can't use goog.string.quote since Google's style guide recommends
  // JS string literals use single quotes.
  const lines = string.split(/\n/g).map(this.quote_);
  return lines.join(" + '\\n' +\n");
};

/**
 * 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 {!Block} block The current block.
 * @param {string} code The Solidity code created for this block.
 * @param {boolean=} opt_thisOnly True to generate code for only this statement.
 * @return {string} Solidity code with comments and subsequent blocks added.
 * @protected
 */
Solidity.scrub_ = function (block, code, opt_thisOnly) {
  let commentCode = "";
  // Only collect comments for blocks that aren't inline.
  if (!block.outputConnection || !block.outputConnection.targetConnection) {
    // Collect comment for this block.
    let comment = block.getCommentText();
    if (comment) {
      comment = stringUtils.wrap(comment, this.COMMENT_WRAP - 3);
      commentCode += this.prefixLines(comment + "\n", "/// ");
    }
    /// Collect comments for all value arguments.
    /// Don't collect comments for nested statements.
    for (let i = 0; i < block.inputList.length; i++) {
      if (block.inputList[i].type === Blockly.inputTypes.VALUE) {
        const childBlock = block.inputList[i].connection.targetBlock();
        if (childBlock) {
          comment = this.allNestedComments(childBlock);
          if (comment) {
            commentCode += this.prefixLines(comment, "/// ");
          }
        }
      }
    }
  }
  const nextBlock = block.nextConnection && block.nextConnection.targetBlock();
  const nextCode = opt_thisOnly ? "" : this.blockToCode(nextBlock);

  return commentCode + code + nextCode;
};

/**
 * Gets a property and adjusts the value while taking into account indexing.
 * @param {!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}
 */
Solidity.getAdjusted = function (
  block,
  atId,
  opt_delta,
  opt_negate,
  opt_order
) {
  let delta = opt_delta || 0;
  let order = opt_order || this.ORDER_NONE;
  if (block.workspace.options.oneBasedIndex) {
    delta--;
  }
  const defaultAtIndex = block.workspace.options.oneBasedIndex ? "1" : "0";

  let innerOrder;
  let outerOrder = order;
  if (delta > 0) {
    outerOrder = this.ORDER_ADDITION;
    innerOrder = this.ORDER_ADDITION;
  } else if (delta < 0) {
    outerOrder = this.ORDER_SUBTRACTION;
    innerOrder = this.ORDER_SUBTRACTION;
  } else if (opt_negate) {
    outerOrder = this.ORDER_UNARY_NEGATION;
    innerOrder = this.ORDER_UNARY_NEGATION;
  }

  let at = this.valueToCode(block, atId, outerOrder) || defaultAtIndex;

  if (stringUtils.isNumber(at)) {
    // If the index is a naked number, adjust it right now.
    at = Number(at) + delta;
    if (opt_negate) {
      at = -at;
    }
  } else {
    // If the index is dynamic, adjust it in code.
    if (delta > 0) {
      at = at + " + " + delta;
    } else if (delta < 0) {
      at = at + " - " + -delta;
    }
    if (opt_negate) {
      if (delta) {
        at = "-(" + at + ")";
      } else {
        at = "-" + at;
      }
    }
    innerOrder = Math.floor(innerOrder);
    order = Math.floor(order);
    if (innerOrder && order >= innerOrder) {
      at = "(" + at + ")";
    }
  }
  return at;
};

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 = Solidity.getVariablesInScope(blocks[i], group);
      var options = vars.map(function (v) {
        return [Solidity.getVariableName(v), v.id_];
      });

      var selectedOption = nameField.getValue();

      console.log("selected option => ", selectedOption);
      if (options.length != 0) {
        var wasUndefined =
          nameField.menuGenerator_[0][1] == 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]
          // );
        }
      }
    }
  }
};

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
  }
};

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

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

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

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

  Solidity.setVariableName(variable, name);

  return variable;
};

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

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

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

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

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

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;
};

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

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

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

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

Solidity["contract"] = function (block) {
  var states = Solidity.statementToCode(block, "STATES");
  if (states.length > 0) {
    states += "\n";
  }
  var ctor = Solidity.statementToCode(block, "CTOR");
  var methods = Solidity.statementToCode(block, "METHODS");

  // trim newline before ultimate closing curly brace
  if (methods.length > 0) {
    methods = methods.slice(0, -2);
  } else if (ctor.length > 0) {
    ctor = ctor.slice(0, -2);
  }

  var code =
    "//SPDX-License-Identifier: MIT\n\n" +
    "pragma solidity ^0.8.17;\n\n" +
    "contract " +
    block.getFieldValue("NAME") +
    " {\n\n" +
    states +
    ctor +
    methods +
    "}\n";

  return code;
};

Solidity["contract_state"] = function (block) {
  var name = block.getFieldValue("NAME");
  var value = Solidity.valueToCode(block, "VALUE", Solidity.ORDER_ASSIGNMENT);
  var type = block.getFieldValue("TYPE");
  var types = {
    TYPE_BOOL: "bool",
    TYPE_INT: "int",
    TYPE_UINT: "uint",
    TYPE_STRING: "string",
    TYPE_ADDRESS: "address",
  };
  var defaultValue = {
    TYPE_BOOL: "false",
    TYPE_INT: "0",
    TYPE_UINT: "0",
    TYPE_STRING: '""',
    TYPE_ADDRESS: "0x0000000000000000000000000000000000000000",
  };

  if (value === "") {
    value = defaultValue[type];
  }

  return types[type] + " " + name + " = " + value + ";\n";
};

Solidity["contract_state_get"] = function (block) {
  var variableId = block.getFieldValue("STATE_NAME");
  var variable = block.workspace.getVariableById(variableId);

  if (!variable) {
    return "";
  }

  return [Solidity.getVariableName(variable), Solidity.ORDER_ATOMIC];
};

Solidity["contract_state_set"] = function (block) {
  // Variable setter.
  var argument0 =
    Solidity.valueToCode(block, "STATE_VALUE", Solidity.ORDER_ASSIGNMENT) ||
    "0";
  var variableId = block.getFieldValue("STATE_NAME");
  var variable = block.workspace.getVariableById(variableId);

  if (!variable) {
    return "";
  }

  return Solidity.getVariableName(variable) + " = " + argument0 + ";\n";
};

Solidity["controls_if"] = function (block) {
  // If/elseif/else condition.
  var n = 0;
  var code = "",
    branchCode,
    conditionCode;
  do {
    conditionCode =
      Solidity.valueToCode(block, "IF" + n, Solidity.ORDER_NONE) || "false";
    branchCode = Solidity.statementToCode(block, "DO" + n);
    code +=
      (n > 0 ? " else " : "") +
      "if (" +
      conditionCode +
      ") {\n" +
      branchCode +
      "}";

    ++n;
  } while (block.getInput("IF" + n));

  if (block.getInput("ELSE")) {
    branchCode = Solidity.statementToCode(block, "ELSE");
    code += " else {\n" + branchCode + "}";
  }
  return code + "\n";
};

Solidity["controls_ifelse"] = Solidity["controls_if"];

Solidity["logic_compare"] = function (block) {
  // Comparison operator.
  var OPERATORS = {
    EQ: "==",
    NEQ: "!=",
    LT: "<",
    LTE: "<=",
    GT: ">",
    GTE: ">=",
  };
  var operator = OPERATORS[block.getFieldValue("OP")];
  var order =
    operator == "==" || operator == "!="
      ? Solidity.ORDER_EQUALITY
      : Solidity.ORDER_RELATIONAL;
  var argument0 = Solidity.valueToCode(block, "A", order) || "0";
  var argument1 = Solidity.valueToCode(block, "B", order) || "0";
  var code = argument0 + " " + operator + " " + argument1;
  return [code, order];
};

Solidity["logic_operation"] = function (block) {
  // Operations 'and', 'or'.
  var operator = block.getFieldValue("OP") == "AND" ? "&&" : "||";
  var order =
    operator == "&&" ? Solidity.ORDER_LOGICAL_AND : Solidity.ORDER_LOGICAL_OR;
  var argument0 = Solidity.valueToCode(block, "A", order);
  var argument1 = Solidity.valueToCode(block, "B", order);
  if (!argument0 && !argument1) {
    // If there are no arguments, then the return value is false.
    argument0 = "false";
    argument1 = "false";
  } else {
    // Single missing arguments have no effect on the return value.
    var defaultArgument = operator == "&&" ? "true" : "false";
    if (!argument0) {
      argument0 = defaultArgument;
    }
    if (!argument1) {
      argument1 = defaultArgument;
    }
  }
  var code = argument0 + " " + operator + " " + argument1;
  return [code, order];
};

Solidity["logic_negate"] = function (block) {
  // Negation.
  var order = Solidity.ORDER_LOGICAL_NOT;
  var argument0 = Solidity.valueToCode(block, "BOOL", order) || "true";
  var code = "!" + argument0;
  return [code, order];
};

Solidity["logic_boolean"] = function (block) {
  // Boolean values true and false.
  var code = block.getFieldValue("BOOL") == "TRUE" ? "true" : "false";
  return [code, Solidity.ORDER_ATOMIC];
};

Solidity["logic_null"] = function (block) {
  // Null data type.
  return ["null", Solidity.ORDER_ATOMIC];
};

Solidity["logic_ternary"] = function (block) {
  // Ternary operator.
  var value_if =
    Solidity.valueToCode(block, "IF", Solidity.ORDER_CONDITIONAL) || "false";
  var value_then =
    Solidity.valueToCode(block, "THEN", Solidity.ORDER_CONDITIONAL) || "null";
  var value_else =
    Solidity.valueToCode(block, "ELSE", Solidity.ORDER_CONDITIONAL) || "null";
  var code = value_if + " ? " + value_then + " : " + value_else;
  return [code, Solidity.ORDER_CONDITIONAL];
};

Solidity["math_number"] = function (block) {
  // Numeric value.
  var code = parseFloat(block.getFieldValue("NUM"));
  return [code, Solidity.ORDER_ATOMIC];
};

Solidity["math_arithmetic"] = function (block) {
  // Basic arithmetic operators, and power.
  var OPERATORS = {
    ADD: [" + ", Solidity.ORDER_ADDITION],
    MINUS: [" - ", Solidity.ORDER_SUBTRACTION],
    MULTIPLY: [" * ", Solidity.ORDER_MULTIPLICATION],
    DIVIDE: [" / ", Solidity.ORDER_DIVISION],
    POWER: [" ** ", Solidity.ORDER_EXPONENTATION],
  };
  var tuple = OPERATORS[block.getFieldValue("OP")];
  var operator = tuple[0];
  var order = tuple[1];
  var argument0 = Solidity.valueToCode(block, "A", order) || "0";
  var argument1 = Solidity.valueToCode(block, "B", order) || "0";
  var code = argument0 + operator + argument1;
  return [code, order];
};

Solidity["math_single"] = function (block) {
  // Math operators with single operand.
  var operator = block.getFieldValue("OP");
  var code;
  var arg;
  if (operator == "NEG") {
    // Negation is a special case given its different operator precedence.
    arg =
      Solidity.valueToCode(block, "NUM", Solidity.ORDER_UNARY_NEGATION) || "0";
    if (arg[0] == "-") {
      // --3 is not legal in JS.
      arg = " " + arg;
    }
    code = "-" + arg;
    return [code, Solidity.ORDER_UNARY_NEGATION];
  }
  if (operator == "SIN" || operator == "COS" || operator == "TAN") {
    arg = Solidity.valueToCode(block, "NUM", Solidity.ORDER_DIVISION) || "0";
  } else {
    arg = Solidity.valueToCode(block, "NUM", Solidity.ORDER_NONE) || "0";
  }
  // First, handle cases which generate values that don't need parentheses
  // wrapping the code.
  switch (operator) {
    case "ABS":
      code = "Math.abs(" + arg + ")";
      break;
    case "ROOT":
      code = "Math.sqrt(" + arg + ")";
      break;
    case "LN":
      code = "Math.log(" + arg + ")";
      break;
    case "EXP":
      code = "Math.exp(" + arg + ")";
      break;
    case "POW10":
      code = "Math.pow(10," + arg + ")";
      break;
    case "ROUND":
      code = "Math.round(" + arg + ")";
      break;
    case "ROUNDUP":
      code = "Math.ceil(" + arg + ")";
      break;
    case "ROUNDDOWN":
      code = "Math.floor(" + arg + ")";
      break;
    case "SIN":
      code = "Math.sin(" + arg + " / 180 * Math.PI)";
      break;
    case "COS":
      code = "Math.cos(" + arg + " / 180 * Math.PI)";
      break;
    case "TAN":
      code = "Math.tan(" + arg + " / 180 * Math.PI)";
      break;
  }
  if (code) {
    return [code, Solidity.ORDER_FUNCTION_CALL];
  }
  // Second, handle cases which generate values that may need parentheses
  // wrapping the code.
  switch (operator) {
    case "LOG10":
      code = "Math.log(" + arg + ") / Math.log(10)";
      break;
    case "ASIN":
      code = "Math.asin(" + arg + ") / Math.PI * 180";
      break;
    case "ACOS":
      code = "Math.acos(" + arg + ") / Math.PI * 180";
      break;
    case "ATAN":
      code = "Math.atan(" + arg + ") / Math.PI * 180";
      break;
    default:
      throw "Unknown math operator: " + operator;
  }
  return [code, Solidity.ORDER_DIVISION];
};

Solidity["math_constant"] = function (block) {
  // Constants: PI, E, the Golden Ratio, sqrt(2), 1/sqrt(2), INFINITY.
  var CONSTANTS = {
    PI: ["Math.PI", Solidity.ORDER_MEMBER],
    E: ["Math.E", Solidity.ORDER_MEMBER],
    GOLDEN_RATIO: ["(1 + Math.sqrt(5)) / 2", Solidity.ORDER_DIVISION],
    SQRT2: ["Math.SQRT2", Solidity.ORDER_MEMBER],
    SQRT1_2: ["Math.SQRT1_2", Solidity.ORDER_MEMBER],
    INFINITY: ["Infinity", Solidity.ORDER_ATOMIC],
  };
  return CONSTANTS[block.getFieldValue("CONSTANT")];
};

Solidity["math_number_property"] = function (block) {
  // Check if a number is even, odd, prime, whole, positive, or negative
  // or if it is divisible by certain number. Returns true or false.
  var number_to_check =
    Solidity.valueToCode(block, "NUMBER_TO_CHECK", Solidity.ORDER_MODULUS) ||
    "0";
  var dropdown_property = block.getFieldValue("PROPERTY");
  var code;
  if (dropdown_property == "PRIME") {
    // Prime is a special case as it is not a one-liner test.
    var functionName = Solidity.provideFunction_("mathIsPrime", [
      "function " + Solidity.FUNCTION_NAME_PLACEHOLDER_ + "(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, or not whole.",
      "  // And false if n is divisible by 2 or 3.",
      "  if (isNaN(n) || n <= 1 || n % 1 != 0 || n % 2 == 0 ||" +
      " n % 3 == 0) {",
      "    return false;",
      "  }",
      "  // Check all the numbers of form 6k +/- 1, up to sqrt(n).",
      "  for (var x = 6; x <= Math.sqrt(n) + 1; x += 6) {",
      "    if (n % (x - 1) == 0 || n % (x + 1) == 0) {",
      "      return false;",
      "    }",
      "  }",
      "  return true;",
      "}",
    ]);
    code = functionName + "(" + number_to_check + ")";
    return [code, Solidity.ORDER_FUNCTION_CALL];
  }
  switch (dropdown_property) {
    case "EVEN":
      code = number_to_check + " % 2 == 0";
      break;
    case "ODD":
      code = number_to_check + " % 2 == 1";
      break;
    case "WHOLE":
      code = number_to_check + " % 1 == 0";
      break;
    case "POSITIVE":
      code = number_to_check + " > 0";
      break;
    case "NEGATIVE":
      code = number_to_check + " < 0";
      break;
    case "DIVISIBLE_BY":
      var divisor =
        Solidity.valueToCode(block, "DIVISOR", Solidity.ORDER_MODULUS) || "0";
      code = number_to_check + " % " + divisor + " == 0";
      break;
  }
  return [code, Solidity.ORDER_EQUALITY];
};

Solidity["math_change"] = function (block) {
  // Add to a variable in place.
  var argument0 =
    Solidity.valueToCode(block, "DELTA", Solidity.ORDER_ADDITION) || "0";
  var varName = Solidity.variableDB_.getName(
    block.getFieldValue("VAR"),
    Blockly.Variables.NAME_TYPE
  );
  return (
    varName +
    " = (typeof " +
    varName +
    " == 'number' ? " +
    varName +
    " : 0) + " +
    argument0 +
    ";\n"
  );
};

// Rounding functions have a single operand.
Solidity["math_round"] = Solidity["math_single"];
// Trigonometry functions have a single operand.
Solidity["math_trig"] = Solidity["math_single"];

Solidity["math_on_list"] = function (block) {
  // Math functions for lists.
  var func = block.getFieldValue("OP");
  var list, code;
  switch (func) {
    case "SUM":
      list = Solidity.valueToCode(block, "LIST", Solidity.ORDER_MEMBER) || "[]";
      code = list + ".reduce(function(x, y) {return x + y;})";
      break;
    case "MIN":
      list = Solidity.valueToCode(block, "LIST", Solidity.ORDER_COMMA) || "[]";
      code = "Math.min.apply(null, " + list + ")";
      break;
    case "MAX":
      list = Solidity.valueToCode(block, "LIST", Solidity.ORDER_COMMA) || "[]";
      code = "Math.max.apply(null, " + list + ")";
      break;
    case "AVERAGE":
      // mathMean([null,null,1,3]) == 2.0.
      var functionName = Solidity.provideFunction_("mathMean", [
        "function " + Solidity.FUNCTION_NAME_PLACEHOLDER_ + "(myList) {",
        "  return myList.reduce(function(x, y) {return x + y;}) / " +
        "myList.length;",
        "}",
      ]);
      list = Solidity.valueToCode(block, "LIST", Solidity.ORDER_NONE) || "[]";
      code = functionName + "(" + list + ")";
      break;
    case "MEDIAN":
      // mathMedian([null,null,1,3]) == 2.0.
      var functionName = Solidity.provideFunction_("mathMedian", [
        "function " + Solidity.FUNCTION_NAME_PLACEHOLDER_ + "(myList) {",
        "  var localList = myList.filter(function (x) " +
        "{return typeof x == 'number';});",
        "  if (!localList.length) return null;",
        "  localList.sort(function(a, b) {return b - a;});",
        "  if (localList.length % 2 == 0) {",
        "    return (localList[localList.length / 2 - 1] + " +
        "localList[localList.length / 2]) / 2;",
        "  } else {",
        "    return localList[(localList.length - 1) / 2];",
        "  }",
        "}",
      ]);
      list = Solidity.valueToCode(block, "LIST", Solidity.ORDER_NONE) || "[]";
      code = functionName + "(" + list + ")";
      break;
    case "MODE":
      // As a list of numbers can contain more than one mode,
      // the returned result is provided as an array.
      // Mode of [3, 'x', 'x', 1, 1, 2, '3'] -> ['x', 1].
      var functionName = Solidity.provideFunction_("mathModes", [
        "function " + Solidity.FUNCTION_NAME_PLACEHOLDER_ + "(values) {",
        "  var modes = [];",
        "  var counts = [];",
        "  var maxCount = 0;",
        "  for (var i = 0; i < values.length; i++) {",
        "    var value = values[i];",
        "    var found = false;",
        "    var thisCount;",
        "    for (var j = 0; j < counts.length; j++) {",
        "      if (counts[j][0] === value) {",
        "        thisCount = ++counts[j][1];",
        "        found = true;",
        "        break;",
        "      }",
        "    }",
        "    if (!found) {",
        "      counts.push([value, 1]);",
        "      thisCount = 1;",
        "    }",
        "    maxCount = Math.max(thisCount, maxCount);",
        "  }",
        "  for (var j = 0; j < counts.length; j++) {",
        "    if (counts[j][1] == maxCount) {",
        "        modes.push(counts[j][0]);",
        "    }",
        "  }",
        "  return modes;",
        "}",
      ]);
      list = Solidity.valueToCode(block, "LIST", Solidity.ORDER_NONE) || "[]";
      code = functionName + "(" + list + ")";
      break;
    case "STD_DEV":
      var functionName = Solidity.provideFunction_("mathStandardDeviation", [
        "function " + Solidity.FUNCTION_NAME_PLACEHOLDER_ + "(numbers) {",
        "  var n = numbers.length;",
        "  if (!n) return null;",
        "  var mean = numbers.reduce(function(x, y) {return x + y;}) / n;",
        "  var variance = 0;",
        "  for (var j = 0; j < n; j++) {",
        "    variance += Math.pow(numbers[j] - mean, 2);",
        "  }",
        "  variance = variance / n;",
        "  return Math.sqrt(variance);",
        "}",
      ]);
      list = Solidity.valueToCode(block, "LIST", Solidity.ORDER_NONE) || "[]";
      code = functionName + "(" + list + ")";
      break;
    case "RANDOM":
      var functionName = Solidity.provideFunction_("mathRandomList", [
        "function " + Solidity.FUNCTION_NAME_PLACEHOLDER_ + "(list) {",
        "  var x = Math.floor(Math.random() * list.length);",
        "  return list[x];",
        "}",
      ]);
      list = Solidity.valueToCode(block, "LIST", Solidity.ORDER_NONE) || "[]";
      code = functionName + "(" + list + ")";
      break;
    default:
      throw "Unknown operator: " + func;
  }
  return [code, Solidity.ORDER_FUNCTION_CALL];
};

Solidity["math_modulo"] = function (block) {
  // Remainder computation.
  var argument0 =
    Solidity.valueToCode(block, "DIVIDEND", Solidity.ORDER_MODULUS) || "0";
  var argument1 =
    Solidity.valueToCode(block, "DIVISOR", Solidity.ORDER_MODULUS) || "0";
  var code = argument0 + " % " + argument1;
  return [code, Solidity.ORDER_MODULUS];
};

Solidity["math_constrain"] = function (block) {
  // Constrain a number between two limits.
  var argument0 =
    Solidity.valueToCode(block, "VALUE", Solidity.ORDER_COMMA) || "0";
  var argument1 =
    Solidity.valueToCode(block, "LOW", Solidity.ORDER_COMMA) || "0";
  var argument2 =
    Solidity.valueToCode(block, "HIGH", Solidity.ORDER_COMMA) || "Infinity";
  var code =
    "Math.min(Math.max(" +
    argument0 +
    ", " +
    argument1 +
    "), " +
    argument2 +
    ")";
  return [code, Solidity.ORDER_FUNCTION_CALL];
};

Solidity["math_random_int"] = function (block) {
  // Random integer between [X] and [Y].
  var argument0 =
    Solidity.valueToCode(block, "FROM", Solidity.ORDER_COMMA) || "0";
  var argument1 =
    Solidity.valueToCode(block, "TO", Solidity.ORDER_COMMA) || "0";
  var functionName = Solidity.provideFunction_("mathRandomInt", [
    "function " + Solidity.FUNCTION_NAME_PLACEHOLDER_ + "(a, b) {",
    "  if (a > b) {",
    "    // Swap a and b to ensure a is smaller.",
    "    var c = a;",
    "    a = b;",
    "    b = c;",
    "  }",
    "  return Math.floor(Math.random() * (b - a + 1) + a);",
    "}",
  ]);
  var code = functionName + "(" + argument0 + ", " + argument1 + ")";
  return [code, Solidity.ORDER_FUNCTION_CALL];
};

Solidity["math_random_float"] = function (block) {
  // Random fraction between 0 and 1.
  return ["Math.random()", Solidity.ORDER_FUNCTION_CALL];
};

Solidity["contract_method"] = function (block) {
  var params = Solidity.statementToCode(block, "PARAMS").trim();
  var branch = Solidity.statementToCode(block, "STACK");
  var code =
    "function " +
    block.getFieldValue("NAME") +
    "(" +
    params +
    ") {\n" +
    branch +
    "\n}\n\n";

  return code;
};

Solidity["contract_ctor"] = function (block) {
  var parent = block.getSurroundParent();

  if (!parent) {
    return "";
  }

  var params = Solidity.statementToCode(block, "PARAMS").trim();
  var branch = Solidity.statementToCode(block, "STACK");
  var code = "constructor " + "(" + params + ") {\n" + branch + "}\n\n";

  return code;
};

Solidity["contract_method_parameter"] = function (block) {
  var name = block.getFieldValue("NAME");
  var nextBlock = block.getNextBlock();
  var sep = nextBlock && nextBlock.type == block.type ? ", " : "";
  var types = {
    TYPE_BOOL: "bool",
    TYPE_INT: "int",
    TYPE_UINT: "uint",
    TYPE_STRING: "string",
    TYPE_ADDRESS: "address",
  };

  return types[block.getFieldValue("TYPE")] + " " + name + sep;
};

Solidity["contract_method_parameter_get"] = function (block) {
  var variableId = block.getFieldValue("PARAM_NAME");
  var variable = block.workspace.getVariableById(variableId);

  if (!variable) {
    return "";
  }

  return [Solidity.getVariableName(variable), Solidity.ORDER_ATOMIC];
};

Solidity["contract_intrinsic_sha3"] = function (block) {
  var argument0 =
    Solidity.valueToCode(block, "VALUE", Solidity.ORDER_ASSIGNMENT) || "0";

  return ["sha3(" + argument0 + ")", Solidity.ORDER_ATOMIC];
};

Solidity["contract_method_call"] = function (block) {
  var variableId = block.getFieldValue("METHOD_NAME");
  var variable = block.workspace.getVariableById(variableId);

  if (!variable) {
    return "";
  }

  return Solidity.getVariableName(variable) + "();\n";
};

Solidity["variables_get"] = function (block) {
  // Variable getter.
  var code = Solidity.variableDB_.getName(
    block.getFieldValue("VAR"),
    Blockly.Variables.NAME_TYPE
  );
  return [code, Solidity.ORDER_ATOMIC];
};

Solidity["variables_set"] = function (block) {
  // Variable setter.
  var argument0 =
    Solidity.valueToCode(block, "VALUE", Solidity.ORDER_ASSIGNMENT) || "0";
  var varName = Solidity.variableDB_.getName(
    block.getFieldValue("VAR"),
    Blockly.Variables.NAME_TYPE
  );
  return varName + " = " + argument0 + ";\n";
};

// Blockly.JavaScript["test_react_field"] = function (block) {
//     return "console.log('custom block');\n"
// }

// Blockly.JavaScript["test_react_date_field"] = function (block) {
//     return "console.log(" + block.getField("DATE").getText() + ");\n"
// }

// // Solidity["contract"] = function (block) {
// //     var states = Solidity.statementToCode(block, "STATES");
// //     if (states.length > 0) {
// //       states += "\n";
// //     }
// //     var ctor = Solidity.statementToCode(block, "CTOR");
// //     var methods = Solidity.statementToCode(block, "METHODS");

// //     // trim newline before ultimate closing curly brace
// //     if (methods.length > 0) {
// //       methods = methods.slice(0, -2);
// //     } else if (ctor.length > 0) {
// //       ctor = ctor.slice(0, -2);
// //     }

// //     var code =
// //       "//SPDX-License-Identifier: MIT\n\n" +
// //       "pragma solidity ^0.8.17;\n\n" +
// //       "contract " +
// //       block.getFieldValue("NAME") +
// //       " {\n\n" +
// //       states +
// //       ctor +
// //       methods +
// //       "}\n";

// //     return code;
// // };

// /** Global variables **/
// // Transaction variables - Subcategory
// Blockly.JavaScript["transaction_constiables"] = function (block) {
//     const dropdown_transaction = block.getFieldValue("transaction")
//     const value_transaction =
//         Blockly.JavaScript.valueToCode(block, "transaction", Blockly.JavaScript.ORDER_ATOMIC) ||
//         dropdown_transaction
//     const code = value_transaction
//     return [code, Blockly.JavaScript.ORDER_NONE]
// }

// // Block variables - Subcategory
// Blockly.JavaScript["block_constiables"] = function (block) {
//     const dropdown_transaction = block.getFieldValue("block")
//     const value_transaction =
//         Blockly.JavaScript.valueToCode(block, "block", Blockly.JavaScript.ORDER_ATOMIC) ||
//         dropdown_transaction
//     const code = value_transaction
//     return [code, Blockly.JavaScript.ORDER_NONE]
// }

// /* State variables */
// // Keywords - Subcategory
// Blockly.JavaScript["keywords"] = function (block) {
//     const dropdown_transaction = block.getFieldValue("keywords")
//     const value_transaction =
//         Blockly.JavaScript.valueToCode(block, "keywords", Blockly.JavaScript.ORDER_ATOMIC) ||
//         dropdown_transaction
//     const code = value_transaction
//     return [code, Blockly.JavaScript.ORDER_NONE]
// }

// Blockly.JavaScript["visibility"] = function (block) {
//     const dropdown_transaction = block.getFieldValue("visibility")
//     const value_transaction =
//         Blockly.JavaScript.valueToCode(block, "visibility", Blockly.JavaScript.ORDER_ATOMIC) ||
//         dropdown_transaction
//     const code = value_transaction
//     return [code, Blockly.JavaScript.ORDER_NONE]
// }

// // Value Types - Subcategory

// Blockly.JavaScript["uint"] = function (block) {
//     const dropdown_transaction = block.getFieldValue("uint")
//     const value_transaction =
//         Blockly.JavaScript.valueToCode(block, "uint", Blockly.JavaScript.ORDER_ATOMIC) ||
//         dropdown_transaction
//     const code = value_transaction
//     return [code, Blockly.JavaScript.ORDER_NONE]
// }

// Blockly.JavaScript["int"] = function (block) {
//     const dropdown_transaction = block.getFieldValue("int")
//     const value_transaction =
//         Blockly.JavaScript.valueToCode(block, "int", Blockly.JavaScript.ORDER_ATOMIC) ||
//         dropdown_transaction
//     const code = value_transaction
//     return [code, Blockly.JavaScript.ORDER_NONE]
// }

// Blockly.JavaScript["bytes"] = function (block) {
//     const dropdown_transaction = block.getFieldValue("bytes")
//     const value_transaction =
//         Blockly.JavaScript.valueToCode(block, "bytes", Blockly.JavaScript.ORDER_ATOMIC) ||
//         dropdown_transaction
//     const code = value_transaction
//     return [code, Blockly.JavaScript.ORDER_NONE]
// }

// Blockly.JavaScript["string"] = function (block) {
//     const dropdown_transaction = block.getFieldValue("string")
//     const value_transaction =
//         Blockly.JavaScript.valueToCode(block, "string", Blockly.JavaScript.ORDER_ATOMIC) ||
//         dropdown_transaction
//     const code = value_transaction
//     return [code, Blockly.JavaScript.ORDER_NONE]
// }

// Blockly.JavaScript["boolean"] = function (block) {
//     const dropdown_transaction = block.getFieldValue("bool")
//     const value_transaction =
//         Blockly.JavaScript.valueToCode(block, "bool", Blockly.JavaScript.ORDER_ATOMIC) ||
//         dropdown_transaction
//     const code = value_transaction
//     return [code, Blockly.JavaScript.ORDER_NONE]
// }

// Blockly.JavaScript["bool_value"] = function (block) {
//     const dropdown_transaction = block.getFieldValue("bool_value")
//     const value_transaction =
//         Blockly.JavaScript.valueToCode(block, "bool_value", Blockly.JavaScript.ORDER_ATOMIC) ||
//         dropdown_transaction
//     const code = value_transaction
//     return [code, Blockly.JavaScript.ORDER_NONE]
// }

// Blockly.JavaScript["address"] = function (block) {
//     const dropdown_transaction = block.getFieldValue("address")
//     const value_transaction =
//         Blockly.JavaScript.valueToCode(block, "address", Blockly.JavaScript.ORDER_ATOMIC) ||
//         dropdown_transaction
//     const code = value_transaction
//     return [code, Blockly.JavaScript.ORDER_NONE]
// }

// Blockly.JavaScript["uintStateVariable"] = function (block) {
//     const dropdown_uint = block.getFieldValue("uint")
//     const dropdown_visibility = block.getFieldValue("visibility")
//     const text_constiable_name = block.getFieldValue("variable_name")
//     const text_name = block.getFieldValue("NAME")
//     const code = `${dropdown_uint} ${dropdown_visibility} ${text_constiable_name} = ${text_name} \n`
//     return [code, Blockly.JavaScript.ORDER_NONE]
// }

// // Reference Types - Subcategory
// Blockly.JavaScript["mapping"] = function (block) {
//     const key =
//         Blockly.JavaScript.valueToCode(block, "KEY", Blockly.JavaScript.ORDER_ATOMIC) || null

//     const value =
//         Blockly.JavaScript.valueToCode(block, "VALUE", Blockly.JavaScript.ORDER_ATOMIC) || null
//     const code = `mapping(${key} => ${value})`
//     return [code, Blockly.JavaScript.ORDER_NONE]
// }

// Blockly.JavaScript["fixed_sized_arrays"] = function (block) {
//     const key =
//         Blockly.JavaScript.valueToCode(block, "KEY", Blockly.JavaScript.ORDER_ATOMIC) || null

//     const size =
//         Blockly.JavaScript.valueToCode(block, "SIZE", Blockly.JavaScript.ORDER_ATOMIC) || null
//     const code = `${key}[${size}]`
//     return [code, Blockly.JavaScript.ORDER_NONE]
// }

// Blockly.JavaScript["dynamic_arrays"] = function (block) {
//     const key =
//         Blockly.JavaScript.valueToCode(block, "KEY", Blockly.JavaScript.ORDER_ATOMIC) || null
//     const code = `${key}[]`
//     return [code, Blockly.JavaScript.ORDER_NONE]
// }

// /* Functions  Category*/
// // Normal Functions - Subcategory
// Blockly.JavaScript["function_name"] = function (block) {
//     const function_name =
//         block.getFieldValue("function_name") || ""
//     const code = `function ${function_name}()`
//     return [code, Blockly.JavaScript.ORDER_NONE]
// }

// Blockly.JavaScript["function_visibility"] = function (block) {
//     const dropdown_transaction = block.getFieldValue("function_visibility")
//     const value_transaction =
//         Blockly.JavaScript.valueToCode(block, "function_visibility", Blockly.JavaScript.ORDER_ATOMIC) ||
//         dropdown_transaction
//     const code = value_transaction
//     return [code, Blockly.JavaScript.ORDER_NONE]
// }

// Blockly.JavaScript["payable"] = function (block) {
//     const function_name =
//         block.getFieldValue("payable") || ""
//     const code = function_name
//     return [code, Blockly.JavaScript.ORDER_NONE]
// }

// Blockly.JavaScript["function_state_mutability"] = function (block) {
//     const dropdown_transaction = block.getFieldValue("state_mutability")
//     const value_transaction =
//         Blockly.JavaScript.valueToCode(block, "state_mutability", Blockly.JavaScript.ORDER_ATOMIC) ||
//         dropdown_transaction
//     const code = value_transaction
//     return [code, Blockly.JavaScript.ORDER_NONE]
// }

// // Special Functions - Subcategory

// // Local Variables Category

// // Handling Errors
// Blockly.JavaScript["assert"] = function (block) {
//     const value_name =
//         Blockly.JavaScript.valueToCode(block, "assert", Blockly.JavaScript.ORDER_ATOMIC) ||
//         "assert"
//     const code = value_name
//     return [code, Blockly.JavaScript.ORDER_NONE]
// }

// Blockly.JavaScript["require"] = function (block) {
//     const value_require =
//         Blockly.JavaScript.valueToCode(block, "require", Blockly.JavaScript.ORDER_ATOMIC) ||
//         "require"
//     const code = value_require
//     return [code, Blockly.JavaScript.ORDER_ATOMIC]
// }

// Blockly.JavaScript["revert"] = function (block) {
//     const value_revert =
//         Blockly.JavaScript.valueToCode(block, "revert", Blockly.JavaScript.ORDER_ATOMIC) ||
//         "revert"
//     const code = value_revert
//     return [code, Blockly.JavaScript.ORDER_ATOMIC]
// }