/** * @license * Visual Blocks Editor * * Copyright 2012 Google Inc. * https://developers.google.com/blockly/ * * 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 Utility functions for generating executable code from * Blockly code. * @author fraser@google.com (Neil Fraser) */ using System; using System.Linq; using System.Collections.Generic; using System.Reflection; using System.Runtime.InteropServices; using Bridge; using Bridge.Text.RegularExpressions; namespace WebMrbc { [ComVisible(true)] public abstract class Generator { public string name_; /// /// Class for a code generator that translates the blocks into a language. /// /// Language name of this generator. public Generator(string name) { this.name_ = name; this.FUNCTION_NAME_PLACEHOLDER_REGEXP_ = new Regex(this.FUNCTION_NAME_PLACEHOLDER_, "g"); } /// // Category to separate generated function names from variables and procedures. /// public static string NAME_TYPE = "generated_function"; /// /// Arbitrary code to inject into locations that risk causing infinite loops. /// Any instances of '%1' will be replaced by the block ID that failed. /// E.g. ' checkTimeout(%1);\n' /// public string INFINITE_LOOP_TRAP = null; /// /// Arbitrary code to inject before every statement. /// Any instances of '%1' will be replaced by the block ID of the statement. /// E.g. 'highlight(%1);\n' /// public string STATEMENT_PREFIX = null; /// /// The method of indenting. Defaults to two spaces, but language generators /// may override this to increase indent or change to tabs. /// public string INDENT = " "; /// /// Maximum length for a comment before wrapping. Does not account for /// indenting level. /// public int COMMENT_WRAP = 60; /// /// List of outer-inner pairings that do NOT require parentheses. /// public int[][] ORDER_OVERRIDES = new int[0][]; public abstract void init(Workspace workspace); public abstract string finish(node[] code); public abstract node[] scrubNakedValue(node[] line); public abstract node[] scrub_(Block block, node[] code); /// /// Generate code for all blocks in the workspace to the specified language. /// /// workspace Workspace to generate code from. /// Generated code. public string workspaceToCode(Workspace workspace) { if (workspace == null) { // Backwards compatibility from before there could be multiple workspaces. App.WriteLine("No workspace specified in workspaceToCode call. Guessing."); workspace = Blockly.getMainWorkspace(); } this.init(workspace); var codes = workspaceToNodes(workspace); return this.finish(codes); } public node[] workspaceToNodes(Workspace workspace) { var nodes = new node[0]; var blocks = workspace.getTopBlocks(true); foreach (var block in blocks) { var line = this.blockToCode(block); if (line != null) { if (block.outputConnection != null/*&& this.scrubNakedValue*/) { // This block is a naked value. Ask the language's code generator if // it wants to append a semicolon, or something. line = this.scrubNakedValue(line); } nodes = (node[])nodes.Concat(line); } } return nodes; } // The following are some helpful functions which can be used by multiple // languages. /// /// Prepend a common prefix onto each line of code. /// /// The lines of code. /// The common prefix. /// The prefixed lines of code. public string prefixLines(string text, string prefix) { return prefix + text.Replace(new Regex("(?!\n$)\n"), "\n" + prefix); } /// /// Recursively spider a tree of blocks, returning all their comments. /// /// The block from which to start spidering. /// Concatenated list of comments. public string allNestedComments(Block block) { var comments = new string[0]; var blocks = block.getDescendants(); for (var i = 0; i < blocks.Length; i++) { var comment = blocks[i].getCommentText(); if (comment != null) { comments.Push(comment); } } // Append an empty string to create a trailing line break when joined. if (comments.Length != 0) { comments.Push(""); } return String.Join("\n", comments); } /// /// Generate code for the specified block (and attached blocks). /// /// The block to generate code for. /// For statement blocks, the generated code. /// For value blocks, an array containing the generated code and an /// operator order value. Returns '' if block is null. /// public node[] blockToCode(Block block) { if (block == null) { return null; } if (block.disabled) { // Skip past this block if it is disabled. return this.blockToCode(block.getNextBlock()); } var func = (dynamic)this[block.type]; if (func == null) return null; var code = (node)func.call(this, block); if (code == null) { // Block has handled code generation itself. return null; } var result = new node[0]; if (code.GetType() == typeof(node)) { do { var c = (node)code.car; c.block_id = block.id; result.Push(c); } while ((code = code.cdr as node) != null); } else { code.block_id = block.id; result.Push(code); } return this.scrub_(block, result); } /// /// Generate code representing the specified value input. /// /// The block containing the input. /// The name of the input. /// /// Generated code or '' if no blocks are connected or the /// specified input does not exist. public node valueToCode(Block block, string name) { var targetBlock = block.getInputTargetBlock(name); if (targetBlock == null) { return null; } var code = this.blockToCode(targetBlock); if(code == null) { return null; } else if (code.Length == 1) { return code[0]; } else { throw new Exception(); } } /// /// Generate code representing the statement. Indent the code. /// /// The block containing the input. /// The name of the input. /// Generated code or '' if no blocks are connected. public begin_node statementToCode(Block block, string name) { var targetBlock = block.getInputTargetBlock(name); var code = this.blockToCode(targetBlock); // Value blocks must return code and order of operations info. // Statement blocks must only return code. //goog.asserts.assertString(code, "Expecting code from statement block \"%s\".", // targetBlock != null ? targetBlock.type : ""); if (code == null) code = new node[0]; return new begin_node((IMrbParser)this, code); } /// /// Comma-separated list of reserved words. /// public static string RESERVED_WORDS_ = ""; /// /// Add one or more words to the list of reserved words for this language. /// /// Comma-separated list of words to add to the list. /// No spaces. Duplicates are ok. public static void addReservedWords(string words) { RESERVED_WORDS_ += words + ","; } /// /// This is used as a placeholder in functions defined using /// Blockly.Generator.provideFunction_. It must not be legal code that could /// legitimately appear in a function definition (or comment), and it must /// not confuse the regular expression parser. /// public string FUNCTION_NAME_PLACEHOLDER_ = "{leCUI8hutHZI4480Dc}"; public Regex FUNCTION_NAME_PLACEHOLDER_REGEXP_; } }