source: EcnlProtoTool/trunk/webapp/webmrbc/Procedures.cs@ 270

Last change on this file since 270 was 270, checked in by coas-nagasima, 7 years ago

mruby版ECNLプロトタイピング・ツールを追加

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
  • Property svn:mime-type set to text/x-csharp
File size: 11.2 KB
Line 
1/**
2 * @license
3 * Visual Blocks Editor
4 *
5 * Copyright 2012 Google Inc.
6 * https://developers.google.com/blockly/
7 *
8 * Licensed under the Apache License, Version 2.0 (the "License");
9 * you may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 */
20
21/**
22 * @fileoverview Utility functions for handling procedures.
23 * @author fraser@google.com (Neil Fraser)
24 */
25using System;
26using System.Collections.Generic;
27using System.Runtime.InteropServices;
28using Bridge;
29using Bridge.Html5;
30using Bridge.Text.RegularExpressions;
31using WebMrbc;
32
33namespace WebMrbc
34{
35 public class Procedures
36 {
37 /// <summary>
38 /// Category to separate procedure names from variables and generated functions.
39 /// </summary>
40 public string NAME_TYPE = "PROCEDURE";
41
42 /**
43 * Common HSV hue for all blocks in this category.
44 */
45 public int HUE = 290;
46
47 /// <summary>
48 /// Find all user-created procedure definitions in a workspace.
49 /// </summary>
50 /// <param name="root">Root workspace.</param>
51 /// <returns>Pair of arrays, the first contains procedures without return variables, the
52 /// second with. Each procedure is defined by a three-element list of name, parameter
53 /// list, and return value boolean.</returns>
54 public Tuple<string, string[], bool>[][] allProcedures(Workspace root)
55 {
56 var blocks = root.getAllBlocks();
57 var proceduresReturn = new Tuple<string, string[], bool>[0];
58 var proceduresNoReturn = new Tuple<string, string[], bool>[0];
59 for (var i = 0; i < blocks.Length; i++) {
60 ProceduresDefBlock block = (dynamic)blocks[i];
61 if ((block.type == ProceduresDefnoreturnBlock.type_name)
62 || (block.type == ProceduresDefreturnBlock.type_name)) {
63 var tuple = block.getProcedureDef();
64 if (tuple != null) {
65 if (tuple.Item3) {
66 proceduresReturn.Push(tuple);
67 }
68 else {
69 proceduresNoReturn.Push(tuple);
70 }
71 }
72 }
73 }
74 proceduresNoReturn.Sort(procTupleComparator_);
75 proceduresReturn.Sort(procTupleComparator_);
76 return new Tuple<string, string[], bool>[][] { proceduresNoReturn, proceduresReturn };
77 }
78
79 /// <summary>
80 /// Comparison function for case-insensitive sorting of the first element of
81 /// a tuple.
82 /// </summary>
83 /// <param name="ta">First tuple.</param>
84 /// <param name="tb">Second tuple.</param>
85 /// <returns>-1, 0, or 1 to signify greater than, equality, or less than.</returns>
86 public int procTupleComparator_(Tuple<string, string[], bool> ta, Tuple<string, string[], bool> tb)
87 {
88 return ta.Item1.ToLower().LocaleCompare(tb.Item1.ToLower());
89 }
90
91 /// <summary>
92 /// Ensure two identically-named procedures don't exist.
93 /// </summary>
94 /// <param name="name">Proposed procedure name.</param>
95 /// <param name="block">Block to disambiguate.</param>
96 /// <returns>Non-colliding name.</returns>
97 public static string findLegalName(string name, Block block)
98 {
99 if (block.isInFlyout) {
100 // Flyouts can have multiple procedures called 'do something'.
101 return name;
102 }
103 while (!isLegalName_(name, block.workspace, block)) {
104 // Collision with another procedure.
105 var r = new Regex(@"^(.*?)(\d+)$").Exec(name);
106 if (r == null) {
107 name += "2";
108 }
109 else {
110 name = r[1] + (Bridge.Script.ParseInt(r[2], 10) + 1);
111 }
112 }
113 return name;
114 }
115
116 /// <summary>
117 /// Does this procedure have a legal name? Illegal names include names of
118 /// procedures already defined.
119 /// </summary>
120 /// <param name="name">The questionable name.</param>
121 /// <param name="workspace">The workspace to scan for collisions.</param>
122 /// <param name="opt_exclude">Optional block to exclude from
123 /// comparisons (one doesn't want to collide with oneself).</param>
124 /// <returns>True if the name is legal.</returns>
125 public static bool isLegalName_(string name, Workspace workspace, Block opt_exclude = null)
126 {
127 var blocks = workspace.getAllBlocks();
128 // Iterate through every block and check the name.
129 for (var i = 0; i < blocks.Length; i++) {
130 if (blocks[i] == opt_exclude) {
131 continue;
132 }
133 ProceduresDefBlock block = (dynamic)blocks[i];
134 if ((block.type == ProceduresDefnoreturnBlock.type_name)
135 || (block.type == ProceduresDefreturnBlock.type_name)) {
136 var procName = block.getProcedureDef();
137 if (Blockly.Names.equals(procName.Item1, name)) {
138 return false;
139 }
140 }
141 }
142 return true;
143 }
144
145 /// <summary>
146 /// Rename a procedure. Called by the editable field.
147 /// </summary>
148 /// <param name="field"></param>
149 /// <param name="name">The proposed new name.</param>
150 /// <returns>The accepted name.</returns>
151 public string rename(Field field, string name)
152 {
153 // Strip leading and trailing whitespace. Beyond this, all names are legal.
154 name = name.Replace(new Regex(@"^[\s\xa0] +|[\s\xa0] +$"), "");
155
156 // Ensure two identically-named procedures don't exist.
157 var legalName = findLegalName(name, field.sourceBlock_);
158 var oldName = field.text_;
159 if (oldName != name && oldName != legalName) {
160 // Rename any callers.
161 var blocks = field.sourceBlock_.workspace.getAllBlocks();
162 for (var i = 0; i < blocks.Length; i++) {
163 ProceduresCallBlock block = (dynamic)blocks[i];
164 if ((block.type == ProceduresCallnoreturnBlock.type_name)
165 || (block.type == ProceduresCallreturnBlock.type_name)) {
166 block.renameProcedure(oldName, legalName);
167 }
168 }
169 }
170 return legalName;
171 }
172
173 /// <summary>
174 /// Construct the blocks required by the flyout for the procedure category.
175 /// </summary>
176 /// <param name="workspace">The workspace contianing procedures.</param>
177 /// <returns>Array of XML block elements.</returns>
178 public Element[] flyoutCategory(Workspace workspace)
179 {
180 var xmlList = new Element[0];
181 if (Script.Get(Blockly.Blocks, ProceduresDefnoreturnBlock.type_name) != null) {
182 // <block type="procedures_defnoreturn" gap="16"></block>
183 var block = goog.dom.createDom("block");
184 block.SetAttribute("type", ProceduresDefnoreturnBlock.type_name);
185 block.SetAttribute("gap", "16");
186 xmlList.Push(block);
187 }
188 if (Script.Get(Blockly.Blocks, ProceduresDefreturnBlock.type_name) != null) {
189 // <block type="procedures_defreturn" gap="16"></block>
190 var block = goog.dom.createDom("block");
191 block.SetAttribute("type", ProceduresDefreturnBlock.type_name);
192 block.SetAttribute("gap", "16");
193 xmlList.Push(block);
194 }
195 if (Script.Get(Blockly.Blocks, ProceduresIfreturnBlock.type_name) != null) {
196 // <block type="procedures_ifreturn" gap="16"></block>
197 var block = goog.dom.createDom("block");
198 block.SetAttribute("type", ProceduresIfreturnBlock.type_name);
199 block.SetAttribute("gap", "16");
200 xmlList.Push(block);
201 }
202 if (xmlList.Length != 0) {
203 // Add slightly larger gap between system blocks and user calls.
204 xmlList[xmlList.Length - 1].SetAttribute("gap", "24");
205 }
206
207 var populateProcedures = new Action<Tuple<string, string[], bool>[], string>((procedureList, templateName) => {
208 for (var i = 0; i < procedureList.Length; i++) {
209 var name = procedureList[i].Item1;
210 var args = procedureList[i].Item2;
211 // <block type="procedures_callnoreturn" gap="16">
212 // <mutation name="do something">
213 // <arg name="x"></arg>
214 // </mutation>
215 // </block>
216 var block = goog.dom.createDom("block");
217 block.SetAttribute("type", templateName);
218 block.SetAttribute("gap", "16");
219 var mutation = goog.dom.createDom("mutation");
220 mutation.SetAttribute("name", name);
221 block.AppendChild(mutation);
222 for (var j = 0; j < args.Length; j++) {
223 var arg = goog.dom.createDom("arg");
224 arg.SetAttribute("name", args[j]);
225 mutation.AppendChild(arg);
226 }
227 xmlList.Push(block);
228 }
229 });
230
231 var tuple = allProcedures(workspace);
232 populateProcedures(tuple[0], ProceduresCallnoreturnBlock.type_name);
233 populateProcedures(tuple[1], ProceduresCallreturnBlock.type_name);
234 return xmlList;
235 }
236
237 /// <summary>
238 /// Find all the callers of a named procedure.
239 /// </summary>
240 /// <param name="name">Name of procedure.</param>
241 /// <param name="workspace">The workspace to find callers in.</param>
242 /// <returns>Array of caller blocks.</returns>
243 public ProceduresCallBlock[] getCallers(string name, Workspace workspace)
244 {
245 var callers = new ProceduresCallBlock[0];
246 var blocks = workspace.getAllBlocks();
247 // Iterate through every block and check the name.
248 for (var i = 0; i < blocks.Length; i++) {
249 ProceduresCallBlock block = (dynamic)blocks[i];
250 if ((block.type == ProceduresCallnoreturnBlock.type_name)
251 || (block.type == ProceduresCallreturnBlock.type_name)) {
252 var procName = block.getProcedureCall();
253 // Procedure name may be null if the block is only half-built.
254 if (procName != null && Blockly.Names.equals(procName, name)) {
255 callers.Push(block);
256 }
257 }
258 }
259 return callers;
260 }
261
262 /// <summary>
263 /// When a procedure definition changes its parameters, find and edit all its
264 /// callers.
265 /// </summary>
266 /// <param name="defBlock">Procedure definition block.</param>
267 public void mutateCallers(ProceduresDefBlock defBlock)
268 {
269 var oldRecordUndo = Blockly.Events.recordUndo;
270 var name = defBlock.getProcedureDef().Item1;
271 var xmlElement = defBlock.mutationToDom(true);
272 var callers = getCallers(name, defBlock.workspace);
273 foreach (var caller in callers) {
274 var oldMutationDom = caller.mutationToDom();
275 var oldMutation = oldMutationDom != null ? Blockly.Xml.domToText(oldMutationDom) : null;
276 caller.domToMutation(xmlElement);
277 var newMutationDom = caller.mutationToDom();
278 var newMutation = newMutationDom != null ? Blockly.Xml.domToText(newMutationDom) : null;
279 if (oldMutation != newMutation) {
280 // Fire a mutation on every caller block. But don't record this as an
281 // undo action since it is deterministically tied to the procedure's
282 // definition mutation.
283 Blockly.Events.recordUndo = false;
284 Blockly.Events.fire(new Change(
285 caller, "mutation", null, oldMutation, newMutation));
286 Blockly.Events.recordUndo = oldRecordUndo;
287 }
288 }
289 }
290
291 /// <summary>
292 /// Find the definition block for the named procedure.
293 /// </summary>
294 /// <param name="name">Name of procedure.</param>
295 /// <param name="workspace">The workspace to search.</param>
296 /// <returns>The procedure definition block, or null not found.</returns>
297 public Block getDefinition(string name, Workspace workspace)
298 {
299 // Assume that a procedure definition is a top block.
300 var blocks = workspace.getTopBlocks(false);
301 for (var i = 0; i < blocks.Length; i++) {
302 ProceduresDefBlock block = (dynamic)blocks[i];
303 if ((block.type == ProceduresDefnoreturnBlock.type_name)
304 || (block.type == ProceduresDefreturnBlock.type_name)) {
305 var tuple = block.getProcedureDef();
306 if (tuple != null && Blockly.Names.equals(tuple.Item1, name)) {
307 return block;
308 }
309 }
310 }
311 return null;
312 }
313 }
314}
Note: See TracBrowser for help on using the repository browser.