Exemple #1
0
        /// <summary>
        /// Update the generator code.
        /// </summary>
        /// <param name="block"> Rendered block in preview workspace.</param>
        public static void updateGenerator(Blockly.Block block)
        {
            var language      = ((HTMLSelectElement)Document.GetElementById("language")).Value;
            var generatorStub = FactoryUtils.getGeneratorStub(block, language);

            FactoryUtils.injectCode(generatorStub, "generatorPre");
        }
Exemple #2
0
        /// <summary>
        /// Check to see if more than one input has this name.
        /// Highly inefficient (On^2), but n is small.
        /// </summary>
        /// <param name="referenceBlock">Block to check.</param>
        public static void inputNameCheck(Blockly.Block referenceBlock)
        {
            if (referenceBlock.workspace == null)
            {
                // Block has been deleted.
                return;
            }
            var name   = referenceBlock.getFieldValue("INPUTNAME").ToLowerCase();
            var count  = 0;
            var blocks = referenceBlock.workspace.getAllBlocks();

            for (var i = 0; i < blocks.Length; i++)
            {
                var block     = blocks[i];
                var otherName = block.getFieldValue("INPUTNAME");
                if (!block.disabled && !block.getInheritedDisabled() &&
                    otherName != null && otherName.ToLowerCase() == name)
                {
                    count++;
                }
            }
            var msg = (count > 1) ?
                      "There are " + count + " input blocks\n with this name." : null;

            referenceBlock.setWarningText(msg);
        }
Exemple #3
0
 /// <summary>
 /// Checks if a block has a variable field. Blocks with variable fields cannot
 /// be shadow blocks.
 /// </summary>
 /// <param name="block"> The block to check if a variable field exists.</param>
 /// <returns>True if the block has a variable field, false otherwise.</returns>
 public static bool hasVariableField(Blockly.Block block)
 {
     if (block == null)
     {
         return(false);
     }
     return(block.getVars().Length > 0);
 }
Exemple #4
0
 /// <summary>
 /// Checks if a block is a procedures block. If procedures block names are
 /// ever updated or expanded, this function should be updated as well (no
 /// other known markers for procedure blocks beyond name).
 /// </summary>
 /// <param name="block"> The block to check.</param>
 /// <returns>True if the block is a procedure block, false otherwise.</returns>
 public static bool isProcedureBlock(Blockly.Block block)
 {
     return(block != null &&
            (block.type == "procedures_defnoreturn" ||
             block.type == "procedures_defreturn" ||
             block.type == "procedures_callnoreturn" ||
             block.type == "procedures_callreturn" ||
             block.type == "procedures_ifreturn"));
 }
Exemple #5
0
        /// <summary>
        /// Get block definition code for the current block.
        /// </summary>
        /// <param  name="blockType"></param> Type of block.
        /// <param name="rootBlock"> RootBlock from main workspace in which
        /// user uses Block Factory Blocks to create a custom block.</param>
        /// <param  name="format">"JSON" or "JavaScript".</param>
        /// <param name="workspace"> Where the root block lives.</param>
        /// <returns>Block definition.</returns>
        public static string getBlockDefinition(string blockType, Blockly.Block rootBlock, string format, Blockly.Workspace workspace)
        {
            string code = null;

            blockType = blockType.Replace(new Regex(@"\W", RegexOptions.Multiline), "_").Replace(new Regex(@"^(\d)"), "_\\1");
            switch (format)
            {
            case "JSON":
                code = FactoryUtils.formatJson_(blockType, rootBlock);
                break;

            case "JavaScript":
                code = FactoryUtils.formatJavaScript_(blockType, rootBlock, workspace);
                break;
            }
            return(code);
        }
Exemple #6
0
        /// <summary>
        /// Fetch the type(s) defined in the given input.
        /// </summary>
        /// <param name="block"> Block with input.</param>
        /// <param  name="name">Name of the input.</param>
        /// <returns>List of types.</returns>
        private static JsArray <string> getTypesFrom_(Blockly.Block block, string name)
        {
            var typeBlock = block.getInputTargetBlock(name);
            JsArray <string> types;

            if (typeBlock == null || typeBlock.disabled)
            {
                types = new JsArray <string>();
            }
            else if (typeBlock.type == "type_other")
            {
                types = new JsArray <string> {
                    FactoryUtils.escapeString(typeBlock.getFieldValue("TYPE"))
                };
            }
            else if (typeBlock.type == "type_group")
            {
                types = new JsArray <string>();
                for (var n = 0; n < ((TypeGroup)typeBlock).typeCount_; n++)
                {
                    types = types.Concat(FactoryUtils.getTypesFrom_(typeBlock, "TYPE" + n));
                }
                // Remove duplicates.
                var hash = new Dictionary <string, object>();
                for (var n = types.Length - 1; n >= 0; n--)
                {
                    if (hash.ContainsKey(types[n]))
                    {
                        types.Splice(n, 1);
                    }
                    hash[types[n]] = true;
                }
            }
            else
            {
                var fi = typeBlock.GetType().GetField("valueType", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public);
                types = new JsArray <string> {
                    FactoryUtils.escapeString((string)fi.GetValue(null))
                };
            }
            return(types);
        }
Exemple #7
0
        /// <summary>
        /// Fetch the type(s) defined in the given input.
        /// Format as a string for appending to the generated code.
        /// </summary>
        /// <param  name="block">Block with input.</param>
        /// <param name="name"> Name of the input.</param>
        /// <returns>String defining the types.</returns>
        public static string getOptTypesFrom(Blockly.Block block, string name)
        {
            var types = FactoryUtils.getTypesFrom_(block, name);

            if (types.Length == 0)
            {
                return(null);
            }
            else if (types.IndexOf("null") != -1)
            {
                return("null");
            }
            else if (types.Length == 1)
            {
                return(types[0]);
            }
            else
            {
                return("[" + types.Join(", ") + "]");
            }
        }
Exemple #8
0
        /// <summary>
        /// Get the generator code for a given block.
        /// </summary>
        /// <param name="block"> Rendered block in preview workspace.</param>
        /// <param name="generatorLanguage">"JavaScript", "Python", "PHP", "Lua",
        /// "Dart".</param>
        /// <returns>Generator code for multiple blocks.</returns>
        public static string getGeneratorStub(Blockly.Block block, string generatorLanguage)
        {
            var makeVar = new Func <string, string, string>((root, name) => {
                name = name.ToLowerCase().Replace(new Regex(@"\W", RegexOptions.Multiline), "_");
                return("  var " + root + "_" + name);
            });
            // The makevar function lives in the original update generator.
            var language = generatorLanguage;
            var code     = new JsArray <string>();

            code.Push("Blockly." + language + "[\"" + block.type +
                      "\"] = (block) => {");

            // Generate getters for any fields or inputs.
            for (var i = 0; i < block.inputList.Length; i++)
            {
                string name;
                var    input = block.inputList[i];
                for (var j = 0; j < input.fieldRow.Length; j++)
                {
                    var field = input.fieldRow[j];
                    name = field.name;
                    if (String.IsNullOrEmpty(name))
                    {
                        continue;
                    }
                    if (typeof(Blockly.FieldVariable).IsInstanceOfType(field))
                    {
                        // Subclass of Blockly.FieldDropdown, must test first.
                        code.Push(makeVar("variable", name) +
                                  " = Blockly." + language +
                                  ".variableDB_.getName(block.getFieldValue(\"" + name +
                                  "\"), Blockly.Variables.NAME_TYPE);");
                    }
                    else if (typeof(Blockly.FieldAngle).IsInstanceOfType(field))
                    {
                        // Subclass of Blockly.FieldTextInput, must test first.
                        code.Push(makeVar("angle", name) +
                                  " = block.getFieldValue(\"" + name + "\");");
                    }
                    else if (typeof(Blockly.FieldDate).IsInstanceOfType(field))
                    {
                        // Blockly.FieldDate may not be compiled into Blockly.
                        code.Push(makeVar("date", name) +
                                  " = block.getFieldValue(\"" + name + "\");");
                    }
                    else if (typeof(Blockly.FieldColour).IsInstanceOfType(field))
                    {
                        code.Push(makeVar("colour", name) +
                                  " = block.getFieldValue(\"" + name + "\");");
                    }
                    else if (typeof(Blockly.FieldCheckbox).IsInstanceOfType(field))
                    {
                        code.Push(makeVar("checkbox", name) +
                                  " = block.getFieldValue(\"" + name + "\") == \"TRUE\";");
                    }
                    else if (typeof(Blockly.FieldDropdown).IsInstanceOfType(field))
                    {
                        code.Push(makeVar("dropdown", name) +
                                  " = block.getFieldValue(\"" + name + "\");");
                    }
                    else if (typeof(Blockly.FieldNumber).IsInstanceOfType(field))
                    {
                        code.Push(makeVar("number", name) +
                                  " = block.getFieldValue(\"" + name + "\");");
                    }
                    else if (typeof(Blockly.FieldTextInput).IsInstanceOfType(field))
                    {
                        code.Push(makeVar("text", name) +
                                  " = block.getFieldValue(\"" + name + "\");");
                    }
                }
                name = input.name;
                if (!String.IsNullOrEmpty(name))
                {
                    if (input.type == Blockly.Core.INPUT_VALUE)
                    {
                        code.Push(makeVar("value", name) +
                                  " = Blockly." + language + ".valueToCode(block, \"" + name +
                                  "\", Blockly." + language + ".ORDER_ATOMIC);");
                    }
                    else if (input.type == Blockly.Core.NEXT_STATEMENT)
                    {
                        code.Push(makeVar("statements", name) +
                                  " = Blockly." + language + ".statementToCode(block, \"" +
                                  name + "\");");
                    }
                }
            }
            // Most languages end lines with a semicolon.  Python does not.
            var lineEnd = new Dictionary <string, string> {
                { "JavaScript", ";" },
                { "Python", "" },
                { "PHP", ";" },
                { "Dart", ";" }
            };

            code.Push("  // TODO: Assemble " + language + " into code variable.");
            if (block.outputConnection != null)
            {
                code.Push("  var code = \"...\";");
                code.Push("  // TODO: Change ORDER_NONE to the correct strength.");
                code.Push("  return [code, Blockly." + language + ".ORDER_NONE];");
            }
            else
            {
                code.Push("  var code = \"...\" + (lineEnd[language] || \"\") + \"\\n\";");
                code.Push("  return code;");
            }
            code.Push("};");

            return(code.Join("\n"));
        }
Exemple #9
0
        /// <summary>
        /// Returns field strings and any config.
        /// </summary>
        /// <param  name="block"></param> Input block.
        /// <returns></returns> Array of static text and field configs.
        private static JsArray <object> getFieldsJson_(Blockly.Block block)
        {
            var fields = new JsArray <object>();

            while (block != null)
            {
                if (!block.disabled && !block.getInheritedDisabled())
                {
                    switch (block.type)
                    {
                    case "field_static":
                        // Result: "hello"
                        fields.Push(block.getFieldValue("TEXT"));
                        break;

                    case "field_input":
                        fields.Push(new {
                            type = block.type,
                            name = block.getFieldValue("FIELDNAME"),
                            text = block.getFieldValue("TEXT")
                        });
                        break;

                    case "field_number":
                        var obj = new Dictionary <string, object>();
                        obj["type"]  = block.type;
                        obj["name"]  = block.getFieldValue("FIELDNAME");
                        obj["value"] = Script.ParseFloat(block.getFieldValue("VALUE"));
                        var min = Script.ParseFloat(block.getFieldValue("MIN"));
                        if (min > Double.NegativeInfinity)
                        {
                            obj["min"] = min;
                        }
                        var max = Script.ParseFloat(block.getFieldValue("MAX"));
                        if (max < Double.PositiveInfinity)
                        {
                            obj["max"] = max;
                        }
                        var precision = block.getFieldValue("PRECISION");
                        if (!String.IsNullOrEmpty(precision))
                        {
                            obj["precision"] = Script.ParseFloat(precision);
                        }
                        fields.Push(obj);
                        break;

                    case "field_angle":
                        fields.Push(new {
                            type  = block.type,
                            name  = block.getFieldValue("FIELDNAME"),
                            angle = Convert.ToDouble(block.getFieldValue("ANGLE"))
                        });
                        break;

                    case "field_checkbox":
                        fields.Push(new {
                            type     = block.type,
                            name     = block.getFieldValue("FIELDNAME"),
                            @checked = block.getFieldValue("CHECKED") == "TRUE"
                        });
                        break;

                    case "field_colour":
                        fields.Push(new {
                            type   = block.type,
                            name   = block.getFieldValue("FIELDNAME"),
                            colour = block.getFieldValue("COLOUR")
                        });
                        break;

                    case "field_date":
                        fields.Push(new {
                            type = block.type,
                            name = block.getFieldValue("FIELDNAME"),
                            date = block.getFieldValue("DATE")
                        });
                        break;

                    case "field_variable":
                        fields.Push(new {
                            type     = block.type,
                            name     = block.getFieldValue("FIELDNAME"),
                            variable = block.getFieldValue("TEXT") ?? null
                        });
                        break;

                    case "field_dropdown":
                        var count   = ((FieldDropdown)block).optionCount_;
                        var options = new JsArray <DropdownItemInfo>(count);
                        for (var i = 0; i < count; i++)
                        {
                            options[i] = new DropdownItemInfo(
                                block.getFieldValue("USER" + i),
                                block.getFieldValue("CPU" + i)
                                );
                        }
                        if (options.Length >= 0)
                        {
                            fields.Push(new {
                                type    = block.type,
                                name    = block.getFieldValue("FIELDNAME"),
                                options = options
                            });
                        }
                        break;

                    case "field_image":
                        fields.Push(new {
                            type   = block.type,
                            src    = block.getFieldValue("SRC"),
                            width  = Convert.ToDouble(block.getFieldValue("WIDTH")),
                            height = Convert.ToDouble(block.getFieldValue("HEIGHT")),
                            alt    = block.getFieldValue("ALT")
                        });
                        break;
                    }
                }
                block = block.nextConnection == null ? null : block.nextConnection.targetBlock();
            }
            return(fields);
        }
Exemple #10
0
        /// <summary>
        /// Returns field strings and any config.
        /// </summary>
        /// <param  name="block">Input block.</param>
        /// <returns>Field strings.</returns>
        private static JsArray <string> getFieldsJs_(Blockly.Block block)
        {
            var fields = new JsArray <string>();

            while (block != null)
            {
                if (!block.disabled && !block.getInheritedDisabled())
                {
                    switch (block.type)
                    {
                    case "field_static":
                        // Result: "hello"
                        fields.Push(FactoryUtils.escapeString(block.getFieldValue("TEXT")));
                        break;

                    case "field_input":
                        // Result: new Blockly.FieldTextInput("Hello"), "GREET"
                        fields.Push("new Blockly.FieldTextInput(" +
                                    FactoryUtils.escapeString(block.getFieldValue("TEXT")) + "), " +
                                    FactoryUtils.escapeString(block.getFieldValue("FIELDNAME")));
                        break;

                    case "field_number":
                        // Result: new Blockly.FieldNumber(10, 0, 100, 1), "NUMBER"
                        var args = new JsArray <double> {
                            Script.ParseFloat(block.getFieldValue("VALUE")),
                            Script.ParseFloat(block.getFieldValue("MIN")),
                            Script.ParseFloat(block.getFieldValue("MAX")),
                            Script.ParseFloat(block.getFieldValue("PRECISION"))
                        };
                        // Remove any trailing arguments that aren't needed.
                        if (args[3] == 0)
                        {
                            args.Pop();
                            if (args[2] == Double.PositiveInfinity)
                            {
                                args.Pop();
                                if (args[1] == Double.NegativeInfinity)
                                {
                                    args.Pop();
                                }
                            }
                        }
                        fields.Push("new Blockly.FieldNumber(" + args.Join(", ") + "), " +
                                    FactoryUtils.escapeString(block.getFieldValue("FIELDNAME")));
                        break;

                    case "field_angle":
                        // Result: new Blockly.FieldAngle(90), "ANGLE"
                        fields.Push("new Blockly.FieldAngle(" +
                                    Script.ParseFloat(block.getFieldValue("ANGLE")) + "), " +
                                    FactoryUtils.escapeString(block.getFieldValue("FIELDNAME")));
                        break;

                    case "field_checkbox":
                        // Result: new Blockly.FieldCheckbox("TRUE"), "CHECK"
                        fields.Push("new Blockly.FieldCheckbox(" +
                                    FactoryUtils.escapeString(block.getFieldValue("CHECKED")) +
                                    "), " +
                                    FactoryUtils.escapeString(block.getFieldValue("FIELDNAME")));
                        break;

                    case "field_colour":
                        // Result: new Blockly.FieldColour("#ff0000"), "COLOUR"
                        fields.Push("new Blockly.FieldColour(" +
                                    FactoryUtils.escapeString(block.getFieldValue("COLOUR")) +
                                    "), " +
                                    FactoryUtils.escapeString(block.getFieldValue("FIELDNAME")));
                        break;

                    case "field_date":
                        // Result: new Blockly.FieldDate("2015-02-04"), "DATE"
                        fields.Push("new Blockly.FieldDate(" +
                                    FactoryUtils.escapeString(block.getFieldValue("DATE")) + "), " +
                                    FactoryUtils.escapeString(block.getFieldValue("FIELDNAME")));
                        break;

                    case "field_variable":
                        // Result: new Blockly.FieldVariable("item"), "VAR"
                        var varname
                            = FactoryUtils.escapeString(block.getFieldValue("TEXT") ?? null);
                        fields.Push("new Blockly.FieldVariable(" + varname + "), " +
                                    FactoryUtils.escapeString(block.getFieldValue("FIELDNAME")));
                        break;

                    case "field_dropdown":
                        // Result:
                        // new Blockly.FieldDropdown([["yes", "1"], ["no", "0"]]), "TOGGLE"
                        var options = new JsArray <string>();
                        for (var i = 0; i < ((FieldDropdown)block).optionCount_; i++)
                        {
                            options[i] = "[" +
                                         FactoryUtils.escapeString(block.getFieldValue("USER" + i)) +
                                         ", " +
                                         FactoryUtils.escapeString(block.getFieldValue("CPU" + i)) + "]";
                        }
                        if (options.Length > 0)
                        {
                            fields.Push("new Blockly.FieldDropdown([" +
                                        options.Join(", ") + "]), " +
                                        FactoryUtils.escapeString(block.getFieldValue("FIELDNAME")));
                        }
                        break;

                    case "field_image":
                        // Result: new Blockly.FieldImage("http://...", 80, 60)
                        var src    = FactoryUtils.escapeString(block.getFieldValue("SRC"));
                        var width  = Convert.ToDouble(block.getFieldValue("WIDTH"));
                        var height = Convert.ToDouble(block.getFieldValue("HEIGHT"));
                        var alt    = FactoryUtils.escapeString(block.getFieldValue("ALT"));
                        fields.Push("new Blockly.FieldImage(" +
                                    src + ", " + width + ", " + height + ", " + alt + ")");
                        break;
                    }
                }
                block = block.nextConnection == null ? null : block.nextConnection.targetBlock();
            }
            return(fields);
        }
Exemple #11
0
        /// <summary>
        /// Update the language code as JavaScript.
        /// </summary>
        /// <param name="blockType"> Name of block.</param>
        /// <param name="rootBlock"> Factory_base block.</param>
        /// <param name="workspace"></param> Where the root block lives.
        /// <returns>Generated language code.</returns>
        private static string formatJavaScript_(string blockType, Blockly.Block rootBlock, Blockly.Workspace workspace)
        {
            var code = new JsArray <string>();

            code.Push("Blockly.Core.Blocks[\"" + blockType + "\"] = {");
            code.Push("  init: () => {");
            // Generate inputs.
            var TYPES = new Dictionary <string, string>()
            {
                { "input_value", "appendValueInput" },
                { "input_statement", "appendStatementInput" },
                { "input_dummy", "appendDummyInput" }
            };
            var contentsBlock = rootBlock.getInputTargetBlock("INPUTS");

            while (contentsBlock != null)
            {
                if (!contentsBlock.disabled && !contentsBlock.getInheritedDisabled())
                {
                    var name = "";
                    // Dummy inputs don't have names.  Other inputs do.
                    if (contentsBlock.type != "input_dummy")
                    {
                        name =
                            FactoryUtils.escapeString(contentsBlock.getFieldValue("INPUTNAME"));
                    }
                    code.Push("    this." + TYPES[contentsBlock.type] + "(" + name + ")");
                    var check = FactoryUtils.getOptTypesFrom(contentsBlock, "TYPE");
                    if (!String.IsNullOrEmpty(check))
                    {
                        code.Push("        .setCheck(" + check + ")");
                    }
                    var align = contentsBlock.getFieldValue("ALIGN");
                    if (align != "LEFT")
                    {
                        code.Push("        .setAlign(Blockly.ALIGN_" + align + ")");
                    }
                    var fields = FactoryUtils.getFieldsJs_(
                        contentsBlock.getInputTargetBlock("FIELDS"));
                    for (var i = 0; i < fields.Length; i++)
                    {
                        code.Push("        .appendField(" + fields[i] + ")");
                    }
                    // Add semicolon to last line to finish the statement.
                    code[code.Length - 1] += ";";
                }
                contentsBlock = contentsBlock.nextConnection == null ? null :
                                contentsBlock.nextConnection.targetBlock();
            }
            // Generate inline/external switch.
            if (rootBlock.getFieldValue("INLINE") == "EXT")
            {
                code.Push("    this.setInputsInline(false);");
            }
            else if (rootBlock.getFieldValue("INLINE") == "INT")
            {
                code.Push("    this.setInputsInline(true);");
            }
            // Generate output, or next/previous connections.
            switch (rootBlock.getFieldValue("CONNECTIONS"))
            {
            case "LEFT":
                code.Push(FactoryUtils.connectionLineJs_("setOutput", "OUTPUTTYPE", workspace));
                break;

            case "BOTH":
                code.Push(
                    FactoryUtils.connectionLineJs_("setPreviousStatement", "TOPTYPE", workspace));
                code.Push(
                    FactoryUtils.connectionLineJs_("setNextStatement", "BOTTOMTYPE", workspace));
                break;

            case "TOP":
                code.Push(
                    FactoryUtils.connectionLineJs_("setPreviousStatement", "TOPTYPE", workspace));
                break;

            case "BOTTOM":
                code.Push(
                    FactoryUtils.connectionLineJs_("setNextStatement", "BOTTOMTYPE", workspace));
                break;
            }
            // Generate colour.
            var colourBlock = rootBlock.getInputTargetBlock("COLOUR");

            if (colourBlock != null && !colourBlock.disabled)
            {
                var hue = Script.ParseFloat(colourBlock.getFieldValue("HUE"));
                if (!Double.IsNaN(hue))
                {
                    code.Push("    this.setColour(" + hue + ");");
                }
            }
            code.Push("    this.setTooltip(\"\");");
            code.Push("    this.setHelpUrl(\"http://www.example.com/\");");
            code.Push("  }");
            code.Push("};");
            return(code.Join("\n"));
        }
Exemple #12
0
        /// <summary>
        /// Update the language code as JSON.
        /// </summary>
        /// <param name="blockType"> Name of block.</param>
        /// <param  name="rootBlock">Factory_base block.</param>
        /// <returns>Generanted language code.</returns>
        private static string formatJson_(string blockType, Blockly.Block rootBlock)
        {
            var JS = new Dictionary <string, object>();

            // Type is not used by Blockly, but may be used by a loader.
            JS["type"] = blockType;
            // Generate inputs.
            var message       = new JsArray <string>();
            var args          = new JsArray <object>();
            var contentsBlock = rootBlock.getInputTargetBlock("INPUTS");

            Blockly.Block lastInput = null;
            while (contentsBlock != null)
            {
                if (!contentsBlock.disabled && !contentsBlock.getInheritedDisabled())
                {
                    var fields = FactoryUtils.getFieldsJson_(
                        contentsBlock.getInputTargetBlock("FIELDS"));
                    for (var i = 0; i < fields.Length; i++)
                    {
                        if (fields[i] is string str)
                        {
                            message.Push(str.Replace(new Regex("%", RegexOptions.Multiline), "%%"));
                        }
                        else
                        {
                            args.Push(fields[i]);
                            message.Push("%" + args.Length);
                        }
                    }

                    var input = new Dictionary <string, object>();
                    input.Add("type", contentsBlock.type);
                    // Dummy inputs don't have names.  Other inputs do.
                    if (contentsBlock.type != "input_dummy")
                    {
                        input["name"] = contentsBlock.getFieldValue("INPUTNAME");
                    }
                    var check = JSON.Parse(
                        FactoryUtils.getOptTypesFrom(contentsBlock, "TYPE") ?? "null");
                    if (check != null)
                    {
                        input["check"] = check;
                    }
                    var align = contentsBlock.getFieldValue("ALIGN");
                    if (align != "LEFT")
                    {
                        input["align"] = align;
                    }
                    args.Push(input);
                    message.Push("%" + args.Length);
                    lastInput = contentsBlock;
                }
                contentsBlock = contentsBlock.nextConnection == null ? null :
                                contentsBlock.nextConnection.targetBlock();
            }
            // Remove last input if dummy and not empty.
            if (lastInput != null && lastInput.type == "input_dummy")
            {
                var fields = lastInput.getInputTargetBlock("FIELDS");
                if (fields != null && FactoryUtils.getFieldsJson_(fields).Join("").Trim() != "")
                {
                    var align = lastInput.getFieldValue("ALIGN");
                    if (align != "LEFT")
                    {
                        JS["lastDummyAlign0"] = align;
                    }
                    args.Pop();
                    message.Pop();
                }
            }
            JS["message0"] = message.Join(" ");
            if (args.Length > 0)
            {
                JS["args0"] = args;
            }
            // Generate inline/external switch.
            if (rootBlock.getFieldValue("INLINE") == "EXT")
            {
                JS["inputsInline"] = false;
            }
            else if (rootBlock.getFieldValue("INLINE") == "INT")
            {
                JS["inputsInline"] = true;
            }
            // Generate output, or next/previous connections.
            switch (rootBlock.getFieldValue("CONNECTIONS"))
            {
            case "LEFT":
                JS["output"] =
                    JSON.Parse(
                        FactoryUtils.getOptTypesFrom(rootBlock, "OUTPUTTYPE") ?? "null");
                break;

            case "BOTH":
                JS["previousStatement"] =
                    JSON.Parse(
                        FactoryUtils.getOptTypesFrom(rootBlock, "TOPTYPE") ?? "null");
                JS["nextStatement"] =
                    JSON.Parse(
                        FactoryUtils.getOptTypesFrom(rootBlock, "BOTTOMTYPE") ?? "null");
                break;

            case "TOP":
                JS["previousStatement"] =
                    JSON.Parse(
                        FactoryUtils.getOptTypesFrom(rootBlock, "TOPTYPE") ?? "null");
                break;

            case "BOTTOM":
                JS["nextStatement"] =
                    JSON.Parse(
                        FactoryUtils.getOptTypesFrom(rootBlock, "BOTTOMTYPE") ?? "null");
                break;
            }
            // Generate colour.
            var colourBlock = rootBlock.getInputTargetBlock("COLOUR");

            if (colourBlock != null && !colourBlock.disabled)
            {
                var hue = Script.ParseFloat(colourBlock.getFieldValue("HUE"));
                JS["colour"] = hue;
            }
            JS["tooltip"] = "";
            JS["helpUrl"] = "http://www.example.com/";
            return(JSON.Stringify(JS, null, "  "));
        }