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

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

Visual Studio 2019 と Bridge.NET でビルドできるよう更新。

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