├── .gitattributes
├── .gitignore
├── LICENSE-MIT.md
├── Package
└── csharp-interpreter-unity-3d-plugin_code.unitypackage
├── README.md
└── Source
├── .gitignore
└── Assets
└── Plugins
└── CSI
├── CSharpInterpreter.cs
├── CSharpInterpreter_Include.txt
├── interpreter.cs
└── prepro.cs
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Apply native OS line-endings on checkout of these files...
2 | *.boo text
3 | *.c text
4 | *.cginc text
5 | *.config text
6 | *.contentproj text
7 | *.cpp text
8 | *.cs text
9 | *.css text
10 | *.dae text
11 | *.DAE text
12 | *.dtd text
13 | *.fx text
14 | *.glsl text
15 | *.h text
16 | *.htm text
17 | *.html text
18 | *.inc text
19 | *.ini text
20 | *.js text
21 | *.JSFL text
22 | *.jsfl text
23 | *.json text
24 | *.log text
25 | *.md text
26 | *.mel text
27 | *.php text
28 | *.shader text
29 | *.txt text
30 | *.TXT text
31 | *.xaml text
32 | *.xml text
33 | *.xsd text
34 | .gitattributes text
35 | .gitignore text
36 | COPYING text
37 | INSTALL* text
38 | KEYS* text
39 | LICENSE* text
40 | NEWS* text
41 | NOTICE* text
42 | README* text
43 | TODO* text
44 | WHATSNEW* text
45 |
46 | # Apply Unix-style LF line-endings on checkout of these files...
47 | *.meta text eol=lf
48 | *.sh text eol=lf
49 | *.vspscc text eol=lf
50 | .htaccess text eol=lf
51 |
52 | # Apply Windows/DOS-style CR-LF line-endings on checkout of these files...
53 | *.bat text eol=crlf
54 | *.cmd text eol=crlf
55 | *.csproj text eol=crlf
56 | *.sln text eol=crlf
57 | *.user text eol=crlf
58 | *.vcproj text eol=crlf
59 |
60 | # No end-of-line conversions are applied (i.e., "-text -diff") to these files...
61 | *.7z binary
62 | *.ai binary
63 | *.anim binary
64 | *.apk binary
65 | *.asset binary
66 | *.bin binary
67 | *.bmp binary
68 | *.BMP binary
69 | *.com binary
70 | *.COM binary
71 | *.controller binary
72 | *.cubemap binary
73 | *.dex binary
74 | *.dll binary
75 | *.DLL binary
76 | *.dylib binary
77 | *.eps binary
78 | *.exe binary
79 | *.EXE binary
80 | *.exr binary
81 | *.fbx binary
82 | *.FBX binary
83 | *.fla binary
84 | *.flare binary
85 | *.flv binary
86 | *.gif binary
87 | *.guiskin binary
88 | *.gz binary
89 | *.ht binary
90 | *.ico binary
91 | *.jpeg binary
92 | *.jpg binary
93 | *.keystore binary
94 | *.mask binary
95 | *.mat binary
96 | *.mb binary
97 | *.mp3 binary
98 | *.mp4 binary
99 | *.mpg binary
100 | *.ogg binary
101 | *.PCX binary
102 | *.pcx binary
103 | *.pdb binary
104 | *.pdf binary
105 | *.physicMaterial binary
106 | *.physicmaterial binary
107 | *.png binary
108 | *.prefab binary
109 | *.ps binary
110 | *.psd binary
111 | *.qt binary
112 | *.so binary
113 | *.swf binary
114 | *.tga binary
115 | *.tif binary
116 | *.tiff binary
117 | *.ttf binary
118 | *.TTF binary
119 | *.unity binary
120 | *.unitypackage binary
121 | *.unityPackage binary
122 | *.wav binary
123 | *.wmv binary
124 | *.zip binary
125 | *.ZIP binary
126 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .svn
2 |
--------------------------------------------------------------------------------
/LICENSE-MIT.md:
--------------------------------------------------------------------------------
1 | The MIT/X11 License
2 | ===================
3 |
4 | Copyright (c) 2008-2012 Tiaan Geldenhuys
5 |
6 | Permission is hereby granted, free of charge, to any person
7 | obtaining a copy of this software and associated documentation
8 | files (the "Software"), to deal in the Software without
9 | restriction, including without limitation the rights to use,
10 | copy, modify, merge, publish, distribute, sublicense, and/or
11 | sell copies of the Software, and to permit persons to whom the
12 | Software is furnished to do so, subject to the following
13 | conditions:
14 |
15 | The above copyright notice and this permission notice shall be
16 | included in all copies or substantial portions of the Software.
17 |
18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
20 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
22 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
23 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
24 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
25 | OTHER DEALINGS IN THE SOFTWARE.
26 |
--------------------------------------------------------------------------------
/Package/csharp-interpreter-unity-3d-plugin_code.unitypackage:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/keyworq/CSharp-Interpreter-for-Unity-3D/689b53a58e0557b7219654927bf84aad34212cd2/Package/csharp-interpreter-unity-3d-plugin_code.unitypackage
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | C# Interpreter Console for Unity 3D
3 | ===================================
4 |
5 | CSI for Unity 3D is a simple C# code interpreter that executes inside
6 | the [Unity 3D](http://unity3d.com/) runtime environment. It can be used
7 | during game-play to evaluate and influence the state of game objects and
8 | components while using the familiar C# syntax and .NET/Mono Framework.
9 | The code is released as Open Source and the project is available under
10 | the [MIT/X11 License](http://opensource.org/licenses/mit-license.php).
11 |
12 | Documentation
13 | -------------
14 |
15 | A more detailed description of the C# Interpreter Console for Unity 3D is
16 | available on the project's home page at:
17 |
18 | http://blog.tiaan.com/link/2010/03/15/csharp-interpreter-unity-plugin-console-debugger
19 |
20 | The documentation includes information about installation, usage, the runtime
21 | user-interface, the syntax used by the interpreter, configuration options,
22 | and so on.
23 |
24 | Source Code
25 | -----------
26 |
27 | The open-source files are available as a Unity package and in raw code format
28 | at [GitHub](https://github.com/keyword/CSharp-Interpreter-for-Unity-3D/)
29 | as well as from the project's
30 | [home page](http://blog.tiaan.com/link/2010/03/15/csharp-interpreter-unity-plugin-console-debugger).
31 |
--------------------------------------------------------------------------------
/Source/.gitignore:
--------------------------------------------------------------------------------
1 | .svn
2 | /*.csproj
3 | /*.pidb
4 | /*.sln
5 | /*.suo
6 | /*.unityproj
7 | /*.user
8 | /*.userprefs
9 | /[Ll]ibrary/
10 | /[Oo]bj/
11 | /[Tt]emp/
12 |
--------------------------------------------------------------------------------
/Source/Assets/Plugins/CSI/CSharpInterpreter.cs:
--------------------------------------------------------------------------------
1 | //-----------------------------------------------------------------------
2 | //
3 | // CSI: A simple C# interpreter
4 | //
5 | //
6 | // Copyright (c) 2010 Tiaan Geldenhuys
7 | //
8 | // Permission is hereby granted, free of charge, to any person
9 | // obtaining a copy of this software and associated documentation
10 | // files (the "Software"), to deal in the Software without
11 | // restriction, including without limitation the rights to use,
12 | // copy, modify, merge, publish, distribute, sublicense, and/or
13 | // sell copies of the Software, and to permit persons to whom the
14 | // Software is furnished to do so, subject to the following
15 | // conditions:
16 | //
17 | // The above copyright notice and this permission notice shall be
18 | // included in all copies or substantial portions of the Software.
19 | //
20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
22 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
24 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
25 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
27 | // OTHER DEALINGS IN THE SOFTWARE.
28 | //
29 | //-----------------------------------------------------------------------
30 | using System;
31 | using System.Collections.Generic;
32 | using System.IO;
33 | using System.Reflection;
34 | using System.Text;
35 | using UnityEngine;
36 |
37 | ///
38 | /// Implements a hosting environment for the C# Interpreter as a Component that can be attached to a GameObject in Unity 3D.
39 | ///
40 | ////[ExecuteInEditMode]
41 | public sealed class CSharpInterpreter : MonoBehaviour, CSI.IConsole
42 | {
43 | public const string Version = "0.8.24.5";
44 |
45 | private const string PromptStart = ">>>";
46 | private const string PromptExtra = "...";
47 | private const string PromptBlank = "----";
48 | private const string InputTextBoxName = "CsiInputTextBox";
49 | private const string EmptyCacheSlot = "{7ewyutPloEyVoQ0lPYfsWw}"; // Cannot use null, since Unity resets it to empty during rebuilds
50 |
51 | public string includeFile;
52 | public UnityEngine.Object includeAsset;
53 | public UnityEngine.Object queuedAsset;
54 | public int maxHistorySize;
55 | public int maxOutputSize;
56 | public bool showInteractiveGUI;
57 | public bool showOutputText;
58 | public bool showOutputAsEditorSelection;
59 | public bool showTooltipText;
60 | public float leftMargin;
61 | public float topMargin;
62 | public float rightMargin;
63 | public float bottomMargin;
64 | public int toolboxWidth;
65 | public float splitterFraction;
66 | public int maxOutputLineWidth;
67 | public int maxOutputLineCount;
68 |
69 | private int currentHistoryIndex;
70 | private string promptText, inputText, outputText;
71 | private Vector2 inputScrollPosition, outputScrollPosition;
72 | private StringBuilder outputStringBuilder;
73 | private static CSI.Interpreter csharpEngine;
74 | private CSI.Interpreter.InputHandler inputHandler;
75 | private Assembly unityEditorAssembly;
76 | private List inputTextCache;
77 | private List inputTextHistory;
78 |
79 | ///
80 | /// Gets the C# interpreter instance that is currently active.
81 | ///
82 | /// The current CSI instance.
83 | public static CSharpInterpreter Current
84 | {
85 | get
86 | {
87 | return (CSI.Interpreter.Console as CSharpInterpreter);
88 | }
89 | }
90 |
91 | public bool IsEditorAvailable()
92 | {
93 | return (this.unityEditorAssembly != null);
94 | }
95 |
96 | private static string GetDefaultIncludeFilename()
97 | {
98 | string filename;
99 | try
100 | {
101 | filename =
102 | new System.Diagnostics.StackTrace(true).GetFrame(0).GetFileName();
103 | filename = Path.Combine(
104 | Path.GetDirectoryName(filename),
105 | Path.GetFileNameWithoutExtension(filename) + "_Include.txt");
106 | if ((!File.Exists(filename)) ||
107 | string.IsNullOrEmpty(Application.dataPath))
108 | {
109 | return null;
110 | }
111 | }
112 | catch
113 | {
114 | return null;
115 | }
116 |
117 | filename = filename.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
118 | string dataPath = Application.dataPath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
119 | if ((filename.StartsWith(dataPath, StringComparison.OrdinalIgnoreCase)))
120 | {
121 | filename = filename.Substring(dataPath.Length).TrimStart(Path.DirectorySeparatorChar);
122 | }
123 |
124 | return filename;
125 | }
126 |
127 | //public CSharpInterpreter()
128 | private void Awake()
129 | {
130 | this.Reset();
131 | }
132 |
133 | ///
134 | /// Performs one-time initialization of this instance; called by Unity.
135 | ///
136 | private void Start()
137 | {
138 | this.promptText = string.Empty;
139 | this.inputText = string.Empty;
140 | this.outputText = string.Empty;
141 | this.outputScrollPosition = Vector2.zero;
142 | this.inputScrollPosition = Vector2.zero;
143 |
144 | this.Reinitialize();
145 | }
146 |
147 | private void Reset()
148 | {
149 | this.includeFile = GetDefaultIncludeFilename();
150 | this.includeAsset = null;
151 | this.queuedAsset = null;
152 | this.maxHistorySize = 100;
153 | this.maxOutputSize = 15250; // Seems to be okay to avoid errors like these from the Debug inspector: "Optimized GUI Block text buffer too large. Not appending further text."
154 | this.showInteractiveGUI = true;
155 | this.showOutputText = true;
156 | this.showOutputAsEditorSelection = true;
157 | this.showTooltipText = true;
158 | this.leftMargin = float.NaN;
159 | this.topMargin = float.NaN;
160 | this.rightMargin = float.NaN;
161 | this.bottomMargin = float.NaN;
162 | this.toolboxWidth = 45;
163 | this.splitterFraction = 75f;
164 | this.maxOutputLineWidth = 32000;
165 | this.maxOutputLineCount = 80;
166 | this.currentHistoryIndex = (this.inputTextHistory == null) ? 0 :
167 | Math.Max(0, this.inputTextHistory.Count - 1);
168 | }
169 |
170 | ///
171 | /// Performs initialization of this instance, which can be called at startup or in play mode when the Unity Editor rebuilds scripts.
172 | ///
173 | private bool Reinitialize()
174 | {
175 | if ((csharpEngine != null /* Has an interpreter */) &&
176 | (CSI.Interpreter.Console != null /* Has a console */) &&
177 | (!object.ReferenceEquals(CSI.Interpreter.Console, this) /* Console is another object */))
178 | {
179 | UnityEngine.Object otherUnityObject = null;
180 | if ((!(CSI.Interpreter.Console is UnityEngine.Object) /* Not a Unity object */) ||
181 | ((bool)(otherUnityObject = CSI.Interpreter.Console as UnityEngine.Object) /* Another live Unity object */))
182 | {
183 | this.enabled = false;
184 | if (otherUnityObject)
185 | {
186 | Debug.LogWarning(
187 | "Only one C# Interpreter may be created per scene; " +
188 | "use the one on the object named: " + otherUnityObject.name, this);
189 | if (otherUnityObject is Behaviour)
190 | {
191 | ((Behaviour)otherUnityObject).enabled = true;
192 | }
193 | }
194 | else
195 | {
196 | Debug.LogWarning(
197 | "Only one C# Interpreter console may be created!", this);
198 | }
199 |
200 | return false; // Not initialized
201 | }
202 | }
203 |
204 | this.EnforceParameterLimits();
205 | if (string.IsNullOrEmpty(this.outputText))
206 | {
207 | this.outputText = "CSI Simple C# Interpreter v." + Version + " from Tiaan.com in CLR v." + Environment.Version.ToString() + " on Unity v." + Application.unityVersion;
208 | }
209 | else
210 | {
211 | this.outputText += string.Format("{0}[CSI reloaded and reset some data @ {1}]", Environment.NewLine, DateTime.Now.ToLongTimeString());
212 | }
213 |
214 | this.outputStringBuilder = new StringBuilder(this.outputText + Environment.NewLine);
215 | this.outputScrollPosition.y = Mathf.Infinity;
216 |
217 | if (this.inputTextCache == null)
218 | {
219 | this.inputTextCache = new List(this.maxHistorySize + 1);
220 | }
221 |
222 | if (this.inputTextHistory == null)
223 | {
224 | this.inputTextHistory = new List(this.maxHistorySize + 1);
225 | this.inputTextCache.Clear();
226 | }
227 |
228 | this.currentHistoryIndex = Math.Max(0, this.inputTextHistory.Count - 1);
229 |
230 | InitializeCompilerForUnity3D.RunOnce();
231 | csharpEngine = new CSI.Interpreter();
232 | csharpEngine.OnGetUnknownItem += this.OnGetUnknownItem;
233 |
234 | CSI.Interpreter.Console = this;
235 | string libraryPath = null;
236 | if (Application.isEditor)
237 | {
238 | // For Editor: Seach project's "Library\ScriptAssemblies" directory
239 | libraryPath = Path.GetDirectoryName(
240 | csharpEngine.FullExecutablePath());
241 | }
242 | else
243 | {
244 | #if UNITY_2_6
245 | // Unity 2.6.1 Player: Seach "_Data"
246 | libraryPath = Application.dataPath;
247 | #else // i.e., 3.0 and greater
248 | // Players of Unity 3.0.0 and 3.1.0: Seach "_Data\Managed"
249 | try
250 | {
251 | libraryPath = Path.GetDirectoryName(GetFullPathOfAssembly(typeof(int).Assembly));
252 | }
253 | catch
254 | {
255 | libraryPath = Path.Combine(Application.dataPath ?? string.Empty, "Managed");
256 | }
257 | #endif
258 | }
259 |
260 | try
261 | {
262 | // Add DLLs from the project's "Library\ScriptAssemblies" directory
263 | if (!string.IsNullOrEmpty(libraryPath))
264 | {
265 | foreach (string reference in
266 | #if UNITY_2_6
267 | Directory.GetFiles(libraryPath, "Assembly - *.dll"))
268 | #else // i.e., 3.0 and greater
269 | Directory.GetFiles(libraryPath, "Assembly-*.dll"))
270 | #endif
271 | {
272 | // When using Unity 2.6.1 convention:
273 | // * "Assembly - CSharp.dll"
274 | // * "Assembly - CSharp - Editor.dll"
275 | // * "Assembly - CSharp - first pass.dll"
276 | // * "Assembly - UnityScript - first pass.dll"
277 | // When using Unity 3.0.0 and 3.1.0 convention:
278 | // * "Assembly-CSharp.dll"
279 | // * "Assembly-CSharp-firstpass.dll"
280 | // * "Assembly-UnityScript-firstpass.dll"
281 | csharpEngine.AddReference(reference);
282 | }
283 | }
284 |
285 | string includeFile = this.includeFile;
286 | if (!string.IsNullOrEmpty(includeFile))
287 | {
288 | string cachedFilename = includeFile;
289 | includeFile = ResolveFilename(includeFile);
290 | if (!csharpEngine.ReadIncludeFile(includeFile))
291 | {
292 | ForceWarning(
293 | "CSI include-file not loaded (" + cachedFilename + ")", this);
294 | }
295 | }
296 |
297 | string includeAssetName;
298 | string includeCode = GetAssetText(this.includeAsset, out includeAssetName);
299 | if ((!string.IsNullOrEmpty(includeCode)) &&
300 | (!csharpEngine.ReadIncludeCode(includeCode)))
301 | {
302 | ForceWarning(
303 | "CSI include-asset not loaded: " + (includeAssetName ?? string.Empty), this);
304 | }
305 |
306 | Assembly unityEngineAssembly = null;
307 | string fullAssemblyPath = GetFullPathOfAssembly(
308 | typeof(UnityEngine.GameObject).Assembly);
309 | if (File.Exists(fullAssemblyPath))
310 | {
311 | // Adds "UnityEngine.dll", or rather "UnityEngine-Debug.dll", for the
312 | // Editor, which is located in the "...\Unity\Editor\Data\lib" directory.
313 | // However, this does not work for the Standalone Windows Player, which
314 | // uses the same mechanism for UnityEngine as for UnityEditor (below).
315 | unityEngineAssembly = typeof(UnityEngine.GameObject).Assembly;
316 | }
317 |
318 | // Add the Unity Editor's assembly only when available
319 | this.unityEditorAssembly = null;
320 | foreach (Assembly assembly in
321 | AppDomain.CurrentDomain.GetAssemblies())
322 | {
323 | if (this.unityEditorAssembly == null)
324 | {
325 | try
326 | {
327 | if ((assembly.FullName.StartsWith("UnityEditor,", StringComparison.OrdinalIgnoreCase)) &&
328 | (assembly.GetType("UnityEditor.EditorApplication") != null))
329 | {
330 | this.unityEditorAssembly = assembly;
331 | }
332 | }
333 | catch
334 | {
335 | // Skip problematic assemblies
336 | }
337 | }
338 |
339 | if (unityEngineAssembly == null)
340 | {
341 | try
342 | {
343 | if (((assembly.FullName.StartsWith("UnityEngine,", StringComparison.OrdinalIgnoreCase)) ||
344 | (assembly.FullName.StartsWith("UnityEngine-Debug,", StringComparison.OrdinalIgnoreCase))) &&
345 | (assembly.GetType("UnityEngine.GameObject") != null))
346 | {
347 | unityEngineAssembly = assembly;
348 | }
349 | }
350 | catch
351 | {
352 | // Skip problematic assemblies
353 | }
354 | }
355 |
356 | if ((this.unityEditorAssembly != null) &&
357 | (unityEngineAssembly != null))
358 | {
359 | break;
360 | }
361 | }
362 |
363 | if (unityEngineAssembly != null)
364 | {
365 | // Include "UnityEngine.dll" or "UnityEngine-Debug.dll"
366 | string filename = GetFullPathOfAssembly(unityEngineAssembly);
367 | #if !UNITY_2_6 // i.e., 3.0 and greater
368 | if (!File.Exists(filename))
369 | {
370 | try
371 | {
372 | filename = GetFullPathOfAssembly(typeof(int).Assembly);
373 | filename = Path.Combine(
374 | Path.GetDirectoryName(filename) ?? string.Empty,
375 | "UnityEngine.dll");
376 | }
377 | catch
378 | {
379 | filename = null;
380 | }
381 | }
382 | #endif
383 |
384 | if (File.Exists(filename))
385 | {
386 | csharpEngine.AddReference(filename);
387 | csharpEngine.AddNamespace("UnityEngine");
388 | }
389 | else
390 | {
391 | unityEngineAssembly = null;
392 | }
393 | }
394 |
395 | if (unityEngineAssembly == null)
396 | {
397 | ForceWarning("UnityEngine is not referenced!");
398 | }
399 |
400 | if (this.unityEditorAssembly != null)
401 | {
402 | // Include "UnityEditor.dll"
403 | string filename =
404 | GetFullPathOfAssembly(this.unityEditorAssembly);
405 | if (File.Exists(filename))
406 | {
407 | csharpEngine.AddReference(filename);
408 | csharpEngine.AddNamespace("UnityEditor");
409 | }
410 | else
411 | {
412 | this.unityEditorAssembly = null;
413 | }
414 | }
415 |
416 | if ((this.unityEditorAssembly == null)
417 | && Application.isEditor)
418 | {
419 | Debug.LogWarning("UnityEditor is not referenced!");
420 | }
421 |
422 | this.PromptForInput(PromptStart);
423 | this.AddGlobal("csi", this);
424 | return true; // Initialized successfully
425 | }
426 | catch (IOException exception)
427 | {
428 | // Probably running in the web player without required rights
429 | Debug.LogError(
430 | "CSI failed to initialize (web player not supported): " + exception.Message, this);
431 | return false;
432 | }
433 | }
434 |
435 | private static void ForceWarning(string message)
436 | {
437 | ForceWarning(message, null /* context */);
438 | }
439 |
440 | private static void ForceWarning(string message, UnityEngine.Object context)
441 | {
442 | if (Application.isEditor)
443 | {
444 | if (context == null)
445 | {
446 | Debug.LogWarning(message);
447 | }
448 | else
449 | {
450 | Debug.LogWarning(message, context);
451 | }
452 | }
453 | else
454 | {
455 | CSI.Utils.Print("Warning: " + message);
456 | }
457 | }
458 |
459 | private static string GetFullPathOfAssembly(Assembly assembly)
460 | {
461 | if (assembly == null)
462 | {
463 | return null;
464 | }
465 |
466 | string codeBase = assembly.CodeBase;
467 | if (string.IsNullOrEmpty(codeBase))
468 | {
469 | return null;
470 | }
471 |
472 | string filename = new Uri(codeBase).LocalPath;
473 | if (!File.Exists(filename))
474 | {
475 | string tempName = assembly.FullName ?? string.Empty;
476 | int index = tempName.IndexOf(',');
477 | if (index > 0)
478 | {
479 | tempName = Path.Combine(
480 | Path.GetDirectoryName(filename) ?? string.Empty,
481 | Path.ChangeExtension(tempName.Substring(0, index), ".dll"));
482 | if (File.Exists(tempName))
483 | {
484 | filename = tempName;
485 | }
486 | }
487 | }
488 |
489 | return filename;
490 | }
491 |
492 | private static string GetAssetText(object asset, out string assetName)
493 | {
494 | assetName = null;
495 | string includeCode = null;
496 | if (asset != null)
497 | {
498 | // Handle any Unity object (dead or alive)
499 | if (asset is UnityEngine.Object)
500 | {
501 | UnityEngine.Object unityObject;
502 | if ((bool)(unityObject = asset as UnityEngine.Object))
503 | {
504 | // Handle live Unity objects
505 | assetName = unityObject.name;
506 | if (unityObject is TextAsset)
507 | {
508 | includeCode = ((TextAsset)unityObject).text;
509 | }
510 | }
511 | }
512 | else if (asset is string)
513 | {
514 | includeCode = (string)asset;
515 | }
516 | }
517 |
518 | return includeCode;
519 | }
520 |
521 | private void EnforceParameterLimits()
522 | {
523 | // Auto-size the window layout area, if needed...
524 | if (float.IsNaN(this.leftMargin))
525 | {
526 | this.leftMargin = 800f / Screen.width;
527 | }
528 |
529 | if (float.IsNaN(this.topMargin))
530 | {
531 | this.topMargin = 800f / Screen.height;
532 | }
533 |
534 | if (float.IsNaN(this.rightMargin))
535 | {
536 | this.rightMargin = 6500f / Screen.width;
537 | }
538 |
539 | if (float.IsNaN(this.bottomMargin))
540 | {
541 | this.bottomMargin = 800f / Screen.height;
542 | }
543 |
544 | // Clip parameters within bounds...
545 | if (this.maxHistorySize < 1)
546 | {
547 | this.maxHistorySize = 1;
548 | }
549 | else if (this.maxHistorySize > 9999)
550 | {
551 | this.maxHistorySize = 9999;
552 | }
553 |
554 | if (this.maxOutputSize < 2048)
555 | {
556 | this.maxOutputSize = 2048;
557 | }
558 | else if (this.maxOutputSize > 16380)
559 | {
560 | // Almost 16KB seems to be the upper limit of a Unity TextArea
561 | this.maxOutputSize = 16380;
562 | }
563 |
564 | if (this.splitterFraction < 15f)
565 | {
566 | this.splitterFraction = 15f;
567 | }
568 | else if (this.splitterFraction > 77.5f)
569 | {
570 | this.splitterFraction = 77.5f;
571 | }
572 |
573 | if (this.toolboxWidth < 35)
574 | {
575 | this.toolboxWidth = 35;
576 | }
577 |
578 | if (this.maxOutputLineWidth < 20)
579 | {
580 | this.maxOutputLineWidth = 20;
581 | }
582 |
583 | if (this.maxOutputLineCount < 3)
584 | {
585 | this.maxOutputLineCount = 3;
586 | }
587 | }
588 |
589 | private object OnGetUnknownItem(object key)
590 | {
591 | if (key is string)
592 | {
593 | string stringKey = (string)key;
594 | try
595 | {
596 | GameObject gameObject = GameObject.Find(stringKey);
597 | if (gameObject)
598 | {
599 | return gameObject;
600 | }
601 | }
602 | catch
603 | {
604 | // Interpret any error as not finding the item
605 | }
606 |
607 | try
608 | {
609 | GameObject[] gameObjects =
610 | GameObject.FindGameObjectsWithTag(stringKey);
611 | if ((gameObjects != null) &&
612 | (gameObjects.Length > 0))
613 | {
614 | return gameObjects;
615 | }
616 | }
617 | catch
618 | {
619 | // Interpret any error as not finding the items
620 | }
621 |
622 | try
623 | {
624 | Type type = CSI.Utils.GetType(stringKey);
625 | if (type != null)
626 | {
627 | key = type;
628 | }
629 | }
630 | catch
631 | {
632 | // Ignore
633 | }
634 | }
635 |
636 | if (key is Type)
637 | {
638 | Type typeKey = (Type)key;
639 | try
640 | {
641 | UnityEngine.Object[] objects =
642 | UnityEngine.Object.FindObjectsOfType(typeKey);
643 | if ((objects != null) &&
644 | (objects.Length > 0))
645 | {
646 | return objects;
647 | }
648 | }
649 | catch
650 | {
651 | // Interpret any error as not finding the item
652 | }
653 | }
654 |
655 | return null;
656 | }
657 |
658 | private static string ResolveFilename(string filename)
659 | {
660 | try
661 | {
662 | if (string.IsNullOrEmpty(filename))
663 | {
664 | return filename;
665 | }
666 |
667 | if (File.Exists(filename))
668 | {
669 | return Path.GetFullPath(filename);
670 | }
671 |
672 | filename = Path.Combine(Application.dataPath ?? string.Empty, filename);
673 | if (File.Exists(filename))
674 | {
675 | return filename;
676 | }
677 | }
678 | catch (IOException)
679 | {
680 | // Probably running in the web player without required rights
681 | }
682 |
683 | return null;
684 | }
685 |
686 | ///
687 | /// Adds the specified global variable to the interpreter environment.
688 | ///
689 | public object AddGlobal(string name, object value)
690 | {
691 | csharpEngine.SetValue(name, value);
692 | return value;
693 | }
694 |
695 | public bool HasGlobal(string name)
696 | {
697 | return csharpEngine.VarTable.ContainsKey(name);
698 | }
699 |
700 | public bool RemoveGlobal(string name)
701 | {
702 | if (this.HasGlobal(name))
703 | {
704 | csharpEngine.VarTable.Remove(name);
705 | return true;
706 | }
707 |
708 | return false;
709 | }
710 |
711 | public void ClearOutput()
712 | {
713 | string outputText;
714 | this.ClearOutput(out outputText);
715 | }
716 |
717 | public void ClearOutput(out string outputText)
718 | {
719 | outputText = this.outputText;
720 | this.outputText = string.Empty;
721 | this.outputStringBuilder.Length = 0;
722 | }
723 |
724 | public string GetOutput()
725 | {
726 | return this.outputText;
727 | }
728 |
729 | public string[] GetHistory()
730 | {
731 | return this.inputTextHistory.ToArray();
732 | }
733 |
734 | public void ClearHistory()
735 | {
736 | string[] history;
737 | this.ClearHistory(out history);
738 | }
739 |
740 | public void ClearHistory(out string[] history)
741 | {
742 | history = this.GetHistory();
743 | this.inputTextHistory.Clear();
744 | this.inputTextCache.Clear();
745 | this.currentHistoryIndex = 0;
746 | }
747 |
748 | public object GetLastExecuteResult()
749 | {
750 | if ((csharpEngine != null) &&
751 | (csharpEngine.returnsValue))
752 | {
753 | return csharpEngine.VarTable["_"];
754 | }
755 |
756 | return null;
757 | }
758 |
759 | ///
760 | /// Execute the specified code in the interpreter environment.
761 | ///
762 | public bool ExecuteCode(string inputText)
763 | {
764 | try
765 | {
766 | if (csharpEngine.ProcessLine(inputText))
767 | {
768 | this.PromptForInput((csharpEngine.BlockLevel > 0) ? PromptExtra : PromptStart);
769 |
770 | if (this.showOutputAsEditorSelection &&
771 | this.IsEditorAvailable())
772 | {
773 | try
774 | {
775 | object result = this.GetLastExecuteResult();
776 | if (result != null)
777 | {
778 | this.Select(result);
779 | }
780 | }
781 | catch
782 | {
783 | // Ignore error during result selection
784 | }
785 | }
786 |
787 | return true;
788 | }
789 | }
790 | catch (Exception exception)
791 | {
792 | CSI.Interpreter.Console.Write("ERROR: " + exception.Message);
793 | ////CSI.Interpreter.Console.Write("ERROR: " + exception.ToString());
794 | CSI.Interpreter.Console.Write(Environment.NewLine);
795 | this.PromptForInput(PromptStart);
796 | }
797 |
798 | return false;
799 | }
800 |
801 | ///
802 | /// Execute the specified file in the interpreter environment.
803 | ///
804 | public bool ExecuteFile(string inputFilename)
805 | {
806 | inputFilename = ResolveFilename(inputFilename);
807 | if (File.Exists(inputFilename))
808 | {
809 | string inputText = File.ReadAllText(inputFilename);
810 | return this.ExecuteCode(inputText);
811 | }
812 |
813 | return false;
814 | }
815 |
816 | public bool Select(object obj)
817 | {
818 | if ((obj == null) ||
819 | (!this.IsEditorAvailable()))
820 | {
821 | return false;
822 | }
823 |
824 | if (obj is GameObject)
825 | {
826 | return this.Select((GameObject)obj);
827 | }
828 |
829 | if (obj is Transform)
830 | {
831 | return this.Select((Transform)obj);
832 | }
833 |
834 | if (obj is IEnumerable)
835 | {
836 | return this.Select((IEnumerable)obj);
837 | }
838 |
839 | if (obj is UnityEngine.Object)
840 | {
841 | return this.Select((UnityEngine.Object)obj);
842 | }
843 |
844 | return false;
845 | }
846 |
847 | public bool Select(GameObject gameObject)
848 | {
849 | return this.Select("activeGameObject", gameObject);
850 | }
851 |
852 | public bool Select(Transform transform)
853 | {
854 | return this.Select("activeTransform", transform);
855 | }
856 |
857 | public delegate void Action();
858 |
859 | ///
860 | /// Implements a helper method that can be used to execute a statement and hide the result from the interpreter output windows.
861 | ///
862 | /// The action delegate to be executed.
863 | public void Invoke(Action action)
864 | {
865 | if (action != null)
866 | {
867 | action();
868 | }
869 | }
870 |
871 | public bool Select(UnityEngine.Object unityObject)
872 | {
873 | return this.Select("activeObject", unityObject);
874 | }
875 |
876 | public bool Select(IEnumerable unityObjects)
877 | {
878 | if ((unityObjects == null) ||
879 | (!this.IsEditorAvailable()))
880 | {
881 | return false;
882 | }
883 |
884 | List objects =
885 | new List(unityObjects);
886 | if (objects.Count <= 0)
887 | {
888 | return false;
889 | }
890 |
891 | if ((objects.Count == 1) &&
892 | this.Select((object)objects[0]))
893 | {
894 | return true;
895 | }
896 |
897 | // For Component objects, select the containing game-object
898 | // instead, so that the items would be highlighted in the editor
899 | Component component;
900 | GameObject gameObject;
901 | for (int index = objects.Count - 1; index >= 0; index--)
902 | {
903 | component = objects[index] as Component;
904 | if (!component)
905 | {
906 | continue;
907 | }
908 |
909 | gameObject = component.gameObject;
910 | if (!gameObject)
911 | {
912 | continue;
913 | }
914 |
915 | objects[index] = gameObject;
916 | }
917 |
918 | return this.Select("objects", objects.ToArray());
919 | }
920 |
921 | private bool Select(
922 | string selectionPropertyName, object selectionPropertyValue)
923 | {
924 | if ((selectionPropertyValue == null) ||
925 | (!this.IsEditorAvailable()))
926 | {
927 | return false;
928 | }
929 |
930 | return this.SetUnityEditorProperty(
931 | "UnityEditor.Selection",
932 | selectionPropertyName,
933 | selectionPropertyValue);
934 | }
935 |
936 | private bool SetUnityEditorProperty(
937 | string objectTypeName, string propertyName, object propertyValue)
938 | {
939 | const object ObjectInstance = null; // Static property
940 | if (this.unityEditorAssembly != null)
941 | {
942 | Type type = this.unityEditorAssembly.GetType(objectTypeName);
943 | if (type != null)
944 | {
945 | PropertyInfo property = type.GetProperty(propertyName);
946 | if (property != null)
947 | {
948 | property.SetValue(ObjectInstance, propertyValue, null);
949 | return true;
950 | }
951 | }
952 | }
953 |
954 | return false;
955 | }
956 |
957 | ///
958 | /// Perform the update logic; called by Unity on every frame.
959 | ///
960 | private void Update()
961 | {
962 | // If any code is queued for execution, run it
963 | UnityEngine.Object queuedAsset = this.queuedAsset;
964 | if (queuedAsset != null)
965 | {
966 | this.queuedAsset = null;
967 | string queuedAssetName;
968 | string queuedCode = GetAssetText(queuedAsset, out queuedAssetName);
969 | if ((!string.IsNullOrEmpty(queuedCode)) ||
970 | (!string.IsNullOrEmpty(queuedAssetName)))
971 | {
972 | if (!string.IsNullOrEmpty(queuedCode))
973 | {
974 | this.ExecuteCode(queuedCode);
975 | this.outputScrollPosition.y = Mathf.Infinity;
976 | }
977 | else if (!string.IsNullOrEmpty(queuedAssetName))
978 | {
979 | Debug.LogWarning(
980 | "CSI queued-asset not executed: " + queuedAssetName, this);
981 | }
982 | }
983 | }
984 | }
985 |
986 | ///
987 | /// Draws the GUI and execute its interaction logic; called by Unity on a frequent basis.
988 | ///
989 | private void OnGUI()
990 | {
991 | if ((csharpEngine == null) ||
992 | (!object.ReferenceEquals(CSI.Interpreter.Console, this)))
993 | {
994 | // Detect and re-initialize after a rebuild
995 | // or when the component has been re-enabled
996 | if (!this.Reinitialize())
997 | {
998 | return; // Cannot have multiple active consoles
999 | }
1000 | }
1001 |
1002 | // Process keyboard input
1003 | this.EnforceParameterLimits();
1004 | Event currentEvent = Event.current;
1005 | if ((currentEvent.isKey) &&
1006 | (!currentEvent.control) &&
1007 | (!currentEvent.shift))
1008 | {
1009 | bool isKeyDown = (currentEvent.type == EventType.KeyDown);
1010 | if (currentEvent.alt)
1011 | {
1012 | if (currentEvent.keyCode == KeyCode.F2)
1013 | {
1014 | if (isKeyDown)
1015 | {
1016 | // For Alt+F2, toggle whether the GUI gets displayed
1017 | this.showInteractiveGUI = !this.showInteractiveGUI;
1018 | }
1019 |
1020 | currentEvent.Use();
1021 | }
1022 | }
1023 |
1024 | if (GUI.GetNameOfFocusedControl() == InputTextBoxName)
1025 | {
1026 | if (currentEvent.alt)
1027 | {
1028 | if (currentEvent.keyCode == KeyCode.F1)
1029 | {
1030 | if (isKeyDown)
1031 | {
1032 | // For Alt+F1, display metadata of the last result
1033 | this.OnMetaRequest();
1034 | }
1035 |
1036 | currentEvent.Use();
1037 | }
1038 | }
1039 | else
1040 | {
1041 | while (this.inputTextHistory.Count <= this.currentHistoryIndex)
1042 | {
1043 | this.inputTextHistory.Add(string.Empty);
1044 | }
1045 |
1046 | while (this.inputTextCache.Count <= this.currentHistoryIndex)
1047 | {
1048 | this.inputTextCache.Add(EmptyCacheSlot);
1049 | }
1050 |
1051 | if ((currentEvent.keyCode == KeyCode.UpArrow) ||
1052 | (currentEvent.keyCode == KeyCode.DownArrow) ||
1053 | (currentEvent.keyCode == KeyCode.Escape))
1054 | {
1055 | // Navigate the input history
1056 | // NOTE: Holding down Caps Lock would bypass navigation
1057 | if (!currentEvent.capsLock)
1058 | {
1059 | if (isKeyDown)
1060 | {
1061 | KeyCode keyCode = currentEvent.keyCode;
1062 | bool? useTrueForOlderOrFalseForNewerOrNullForUndo =
1063 | (keyCode == KeyCode.UpArrow) ? true :
1064 | (keyCode == KeyCode.DownArrow) ? (bool?)false :
1065 | null;
1066 | this.OnNavigateHistory(
1067 | useTrueForOlderOrFalseForNewerOrNullForUndo);
1068 | }
1069 |
1070 | currentEvent.Use();
1071 | }
1072 | }
1073 | else if ((this.inputHandler != null) &&
1074 | (currentEvent.keyCode == KeyCode.Return))
1075 | {
1076 | // Handle the enter key; process the input text
1077 | currentEvent.Use();
1078 | if (isKeyDown)
1079 | {
1080 | this.OnExecuteInput();
1081 | }
1082 | }
1083 | }
1084 | }
1085 | }
1086 |
1087 | // Draw the GUI
1088 | try
1089 | {
1090 | this.OnDrawGUI();
1091 | }
1092 | catch (ArgumentException exception)
1093 | {
1094 | // Ignore known exceptions that can happen during shutdown
1095 | if (!exception.Message.Contains("repaint"))
1096 | {
1097 | throw; // Rethrow unknow exceptions
1098 | }
1099 | }
1100 |
1101 | // Prevent extra whitespace at the start of the edit box
1102 | if (GUI.changed)
1103 | {
1104 | this.inputText = this.inputText.TrimStart();
1105 | }
1106 | }
1107 |
1108 | ///
1109 | /// Called when the history needs ot be navigated (e.g., when the up-arrow, down-arrow or escape key is pressed).
1110 | ///
1111 | /// Specify how to navigate the history: true for older history, false for newer history, or null for to undo some history editing or navigation.
1112 | private void OnNavigateHistory(
1113 | bool? useTrueForOlderOrFalseForNewerOrNullForUndo)
1114 | {
1115 | // Save the current input text into its slot in the cache
1116 | this.inputTextCache[this.currentHistoryIndex] = this.inputText;
1117 | if (useTrueForOlderOrFalseForNewerOrNullForUndo.HasValue)
1118 | {
1119 | if (useTrueForOlderOrFalseForNewerOrNullForUndo.Value)
1120 | {
1121 | if (--this.currentHistoryIndex < 0)
1122 | {
1123 | this.currentHistoryIndex += this.inputTextHistory.Count;
1124 | }
1125 | }
1126 | else
1127 | {
1128 | if (++this.currentHistoryIndex >= this.inputTextHistory.Count)
1129 | {
1130 | this.currentHistoryIndex -= this.inputTextHistory.Count;
1131 | }
1132 | }
1133 | }
1134 | else
1135 | {
1136 | // For the escape, "undo" in steps depending on the current state
1137 | if (!string.Equals(
1138 | this.inputText,
1139 | this.inputTextHistory[this.currentHistoryIndex],
1140 | StringComparison.Ordinal))
1141 | {
1142 | // Revert the text to the unmodified version
1143 | this.inputTextCache[this.currentHistoryIndex] = EmptyCacheSlot;
1144 | }
1145 | else
1146 | {
1147 | // Go to the latest history item
1148 | this.currentHistoryIndex = this.inputTextHistory.Count - 1;
1149 | }
1150 | }
1151 |
1152 | // Load the current input text from the historic slot
1153 | this.inputText = this.inputTextCache[this.currentHistoryIndex];
1154 | if (this.inputText == EmptyCacheSlot)
1155 | {
1156 | this.inputText = this.inputTextHistory[this.currentHistoryIndex];
1157 | }
1158 | }
1159 |
1160 | ///
1161 | /// Called when the GUI needs to be drawn.
1162 | ///
1163 | private void OnDrawGUI()
1164 | {
1165 | // Draw the GUI
1166 | const int SpacePixelCount = 4;
1167 | bool showAutoSelectToggle = this.IsEditorAvailable();
1168 | bool showBlankPrompt = string.IsNullOrEmpty(this.promptText) || (this.inputHandler == null);
1169 | float splitHeight = (float)Screen.height * (1f - ((Mathf.Min(0f, this.topMargin) + Mathf.Min(0f, this.bottomMargin)) / 100f));
1170 | Rect areaRect = new Rect(
1171 | Screen.width * (this.leftMargin / 100f),
1172 | Screen.height * (this.topMargin / 100f),
1173 | (float)Screen.width * (1f - ((this.leftMargin + this.rightMargin) / 100f)),
1174 | (float)Screen.height * (1f - ((this.topMargin + this.bottomMargin) / 100f)));
1175 | GUILayout.BeginArea(areaRect);
1176 | GUILayout.BeginVertical(
1177 | GUILayout.MinHeight(60f),
1178 | GUILayout.MaxHeight((splitHeight * (this.splitterFraction / 100f)) - (SpacePixelCount >> 1)),
1179 | GUILayout.Width(areaRect.width));
1180 | GUILayout.FlexibleSpace();
1181 | this.outputScrollPosition =
1182 | GUILayout.BeginScrollView(this.outputScrollPosition);
1183 | if (this.showInteractiveGUI && this.showOutputText)
1184 | {
1185 | // Ignore changes to text to make it read-only
1186 | GUILayout.TextArea(this.outputText, this.outputText.Length);
1187 | }
1188 |
1189 | GUILayout.EndScrollView();
1190 | GUILayout.EndVertical();
1191 | GUILayout.Space(SpacePixelCount);
1192 | GUILayout.BeginHorizontal(
1193 | GUILayout.MinHeight(Mathf.Max(90f, ((splitHeight * ((100f - this.splitterFraction) / 100f)) - (SpacePixelCount >> 1)))),
1194 | GUILayout.Width(areaRect.width));
1195 | GUILayout.BeginVertical();
1196 | GUILayout.BeginHorizontal();
1197 | GUILayout.FlexibleSpace();
1198 | this.showInteractiveGUI = GUILayout.Toggle(this.showInteractiveGUI, new GUIContent(showBlankPrompt ? PromptBlank : this.promptText, (this.showInteractiveGUI ? "Click to hide the C# interpreter GUI" : "Click to show the C# interpreter GUI")), "Button");
1199 | GUILayout.EndHorizontal();
1200 | if (this.showInteractiveGUI)
1201 | {
1202 | GUILayout.BeginHorizontal();
1203 | GUILayout.FlexibleSpace();
1204 | if (GUILayout.Button(new GUIContent(string.Empty, "Click to clear the output text window"), "Toggle"))
1205 | {
1206 | this.ClearOutput();
1207 | }
1208 |
1209 | this.showOutputText = GUILayout.Toggle(this.showOutputText, new GUIContent(string.Empty, (this.showOutputText ? "Click to hide the output text window" : "Click to show the output text window")));
1210 | GUILayout.EndHorizontal();
1211 | if (showAutoSelectToggle)
1212 | {
1213 | GUILayout.BeginHorizontal();
1214 | GUILayout.FlexibleSpace();
1215 | this.showOutputAsEditorSelection = GUILayout.Toggle(this.showOutputAsEditorSelection, new GUIContent(string.Empty, (this.showOutputAsEditorSelection ? "Click to disable automatic selection of results in the editor" : "Click to enable automatic selection of results in the editor")));
1216 | GUILayout.EndHorizontal();
1217 | }
1218 |
1219 | GUILayout.BeginHorizontal();
1220 | GUILayout.FlexibleSpace();
1221 | this.showTooltipText = GUILayout.Toggle(this.showTooltipText, new GUIContent(string.Empty, (this.showTooltipText ? "Click to hide the tooltip text bar" : "Click to display the tooltip text bar")));
1222 | GUILayout.EndHorizontal();
1223 | }
1224 |
1225 | GUILayout.FlexibleSpace();
1226 | GUILayout.EndVertical();
1227 | GUILayout.BeginVertical();
1228 | this.inputScrollPosition = GUILayout.BeginScrollView(
1229 | this.inputScrollPosition,
1230 | GUILayout.MaxWidth(areaRect.width - this.toolboxWidth));
1231 | if (this.showInteractiveGUI)
1232 | {
1233 | GUI.SetNextControlName(InputTextBoxName);
1234 | this.inputText = GUILayout.TextArea(this.inputText);
1235 | }
1236 |
1237 | GUILayout.EndScrollView();
1238 | if (this.showInteractiveGUI && this.showTooltipText)
1239 | {
1240 | GUILayout.Label(GUI.tooltip, "TextField");
1241 | }
1242 |
1243 | GUILayout.FlexibleSpace();
1244 | GUILayout.EndVertical();
1245 | GUILayout.EndHorizontal();
1246 | GUILayout.EndArea();
1247 | }
1248 |
1249 | ///
1250 | /// Called when the input text need to be executed (e.g., when Enter is pressed).
1251 | ///
1252 | private void OnExecuteInput()
1253 | {
1254 | string inputText = this.inputText.Trim();
1255 | this.inputText = string.Empty;
1256 | if (inputText == string.Empty)
1257 | {
1258 | // Repeat the previous command, if any
1259 | for (int index = this.inputTextHistory.Count - 1; index >= 0; index--)
1260 | {
1261 | if (!string.IsNullOrEmpty(this.inputTextHistory[index]))
1262 | {
1263 | this.ExecuteCode(this.inputTextHistory[index]);
1264 | this.outputScrollPosition.y = Mathf.Infinity;
1265 | break;
1266 | }
1267 | }
1268 |
1269 | return;
1270 | }
1271 |
1272 | // Move the text from the input to output console
1273 | CSI.Interpreter.Console.Write(
1274 | this.promptText + " " + inputText + Environment.NewLine);
1275 | this.inputScrollPosition = Vector2.zero;
1276 | this.outputScrollPosition.y = Mathf.Infinity;
1277 |
1278 | // Update the input history
1279 | this.inputTextCache.Clear();
1280 | if ((this.inputTextHistory.Count > 0) ||
1281 | (this.inputTextHistory[this.inputTextHistory.Count - 1] == string.Empty))
1282 | {
1283 | this.inputTextHistory[this.inputTextHistory.Count - 1] = inputText;
1284 | }
1285 | else
1286 | {
1287 | this.inputTextHistory.Add(inputText);
1288 | }
1289 |
1290 | if (this.inputTextHistory.Count > this.maxHistorySize)
1291 | {
1292 | this.inputTextHistory.RemoveRange(
1293 | 0, (this.inputTextHistory.Count - this.maxHistorySize));
1294 | }
1295 | else if ((this.inputTextHistory.Count > 0) &&
1296 | (this.inputTextHistory[0] == string.Empty))
1297 | {
1298 | this.inputTextHistory.RemoveAt(0);
1299 | }
1300 |
1301 | this.currentHistoryIndex = this.inputTextHistory.Count;
1302 |
1303 | // Notify the async-handler of the keyboard input
1304 | CSI.Interpreter.InputHandler inputHandler = this.inputHandler;
1305 | this.inputHandler = null;
1306 | inputHandler(inputText);
1307 | }
1308 |
1309 | ///
1310 | /// Called when metadata should be displayed (e.g., when Alt+F1 is pressed).
1311 | ///
1312 | private bool OnMetaRequest()
1313 | {
1314 | object lastResult = this.GetLastExecuteResult();
1315 | if (lastResult == null)
1316 | {
1317 | return false;
1318 | }
1319 |
1320 | string inputText = this.inputText.Trim();
1321 | string memberNameFilter;
1322 | int seachTextStartIndex;
1323 | if (string.IsNullOrEmpty(inputText))
1324 | {
1325 | memberNameFilter = null;
1326 | seachTextStartIndex = -1;
1327 | }
1328 | else
1329 | {
1330 | for (seachTextStartIndex = inputText.Length - 1;
1331 | seachTextStartIndex >= 0;
1332 | seachTextStartIndex--)
1333 | {
1334 | if ((!char.IsLetterOrDigit(inputText[seachTextStartIndex])) &&
1335 | (inputText[seachTextStartIndex] != '_'))
1336 | {
1337 | break;
1338 | }
1339 | }
1340 |
1341 | seachTextStartIndex = seachTextStartIndex + 1;
1342 | memberNameFilter = inputText.Substring(seachTextStartIndex);
1343 | }
1344 |
1345 | string[] memberNames = CSI.Utils.GetMeta(lastResult, memberNameFilter);
1346 | if ((memberNames == null) ||
1347 | (memberNames.Length <= 0))
1348 | {
1349 | return false;
1350 | }
1351 |
1352 | CSI.IConsole console = CSI.Interpreter.Console;
1353 | string separationLine = new string('-', Math.Min(35, (1 + (console.GetLineWidth() >> 1)))) + Environment.NewLine;
1354 | string memberNameForDetail = null;
1355 | if (memberNames.Length == 1)
1356 | {
1357 | // Incorporate the single found entry's name into the
1358 | // input text, which provides some kind of auto-completion
1359 | memberNameForDetail = memberNames[0];
1360 | if (seachTextStartIndex < 0)
1361 | {
1362 | inputText += memberNameForDetail;
1363 | }
1364 | else
1365 | {
1366 | inputText = inputText.Substring(0, seachTextStartIndex) +
1367 | memberNameForDetail;
1368 | }
1369 |
1370 | this.inputText = inputText;
1371 | }
1372 | else
1373 | {
1374 | // Look for the longest substring shared among results
1375 | int matchTextStopIndex = 0;
1376 | while (true)
1377 | {
1378 | if (matchTextStopIndex >= memberNames[0].Length)
1379 | {
1380 | break;
1381 | }
1382 |
1383 | char matchChar = memberNames[0][matchTextStopIndex];
1384 | bool isDone = false;
1385 | for (int memberIndex = memberNames.Length - 1; memberIndex > 0; memberIndex--)
1386 | {
1387 | if ((matchTextStopIndex >= memberNames[memberIndex].Length) ||
1388 | (matchChar != memberNames[memberIndex][matchTextStopIndex]))
1389 | {
1390 | if (matchTextStopIndex == memberNames[memberIndex].Length)
1391 | {
1392 | memberNameForDetail = memberNames[memberIndex];
1393 | }
1394 |
1395 | isDone = true;
1396 | break;
1397 | }
1398 | }
1399 |
1400 | if (isDone)
1401 | {
1402 | break;
1403 | }
1404 |
1405 | matchTextStopIndex++;
1406 | }
1407 |
1408 | matchTextStopIndex--;
1409 | if ((matchTextStopIndex >= 0) &&
1410 | ((seachTextStartIndex < 0) ||
1411 | (matchTextStopIndex >= (inputText.Length - seachTextStartIndex))))
1412 | {
1413 | string commonText = memberNames[0].Substring(0, matchTextStopIndex + 1);
1414 | if (seachTextStartIndex < 0)
1415 | {
1416 | inputText += commonText;
1417 | }
1418 | else
1419 | {
1420 | inputText =
1421 | inputText.Substring(0, seachTextStartIndex) + commonText;
1422 | }
1423 |
1424 | if (!string.Equals(
1425 | commonText,
1426 | memberNameForDetail,
1427 | StringComparison.Ordinal))
1428 | {
1429 | memberNameForDetail = null;
1430 | }
1431 |
1432 | this.inputText = inputText;
1433 | }
1434 | else
1435 | {
1436 | memberNameForDetail = null;
1437 | }
1438 |
1439 | console.Write(separationLine);
1440 | foreach (string memberName in memberNames)
1441 | {
1442 | console.Write(memberName);
1443 | console.Write(" ");
1444 | }
1445 |
1446 | console.Write(Environment.NewLine);
1447 | }
1448 |
1449 | if (!string.IsNullOrEmpty(memberNameForDetail))
1450 | {
1451 | console.Write(separationLine);
1452 | try
1453 | {
1454 | CSI.Utils.MInfo(lastResult, memberNameForDetail);
1455 | }
1456 | catch
1457 | {
1458 | // Ignore exceptions while trying to auto-complete
1459 | }
1460 | }
1461 |
1462 | this.outputScrollPosition.y = Mathf.Infinity;
1463 | return true;
1464 | }
1465 |
1466 | private void PromptForInput(string prompt)
1467 | {
1468 | this.promptText = prompt;
1469 | CSI.Interpreter.Console.ReadLineAsync(this.ExecuteCode);
1470 | }
1471 |
1472 | #region IConsole Members
1473 |
1474 | void CSI.IConsole.ReadLineAsync(CSI.Interpreter.InputHandler callback)
1475 | {
1476 | CSI.Interpreter.InputHandler inputHandler = this.inputHandler;
1477 | this.inputHandler = callback; // Register new callback
1478 | if (inputHandler != null)
1479 | {
1480 | inputHandler(null); // Cancel previous handler
1481 | }
1482 | }
1483 |
1484 | string CSI.IConsole.Write(string s)
1485 | {
1486 | this.outputStringBuilder.Append(s);
1487 | this.EnforceParameterLimits();
1488 | if (this.outputStringBuilder.Length > this.maxOutputSize)
1489 | {
1490 | this.outputText = this.outputStringBuilder.ToString();
1491 | this.outputStringBuilder.Remove(0, (this.outputStringBuilder.Length - (this.maxOutputSize >> 1)));
1492 | }
1493 |
1494 | this.outputText = this.outputStringBuilder.ToString().TrimEnd();
1495 | return s;
1496 | }
1497 |
1498 | int CSI.IConsole.GetLineWidth()
1499 | {
1500 | return this.maxOutputLineWidth;
1501 | }
1502 |
1503 | int CSI.IConsole.GetMaxLines()
1504 | {
1505 | return this.maxOutputLineCount;
1506 | }
1507 |
1508 | #endregion
1509 |
1510 | ///
1511 | /// Performs initialization of the C# compiler.
1512 | ///
1513 | ///
1514 | /// Most of this method's code is a hack as a workaround for the wrong GAC path and compiler search logic that is baked into Unity.
1515 | ///
1516 | private static class InitializeCompilerForUnity3D
1517 | {
1518 | private static bool didRunOnce;
1519 |
1520 | public static void RunOnce()
1521 | {
1522 | if (didRunOnce)
1523 | {
1524 | return;
1525 | }
1526 |
1527 | try
1528 | {
1529 | didRunOnce = true;
1530 | Type monoCompilerType = null;
1531 | foreach (Type type in
1532 | typeof(Microsoft.CSharp.CSharpCodeProvider).Assembly.GetTypes())
1533 | {
1534 | if (type.FullName == "Mono.CSharp.CSharpCodeCompiler")
1535 | {
1536 | monoCompilerType = type;
1537 | break;
1538 | }
1539 | }
1540 |
1541 | if (monoCompilerType == null)
1542 | {
1543 | Debug.LogWarning(
1544 | "The C# compiler may not yet work on this version of " +
1545 | "Unity! Please provide feedback about test results.");
1546 | return;
1547 | }
1548 |
1549 | if (Path.DirectorySeparatorChar != '\\')
1550 | {
1551 | Debug.LogWarning(
1552 | "The C# compiler may not yet work on this operating " +
1553 | "system! Please provide feedback about test results.");
1554 | return;
1555 | }
1556 |
1557 | // This begins a hack to bypass the static constructor of
1558 | // Mono.CSharp.CSharpCodeCompiler and initialize that data
1559 | // type in an alternative way for Unity 3D (v.2.6.1); it
1560 | // attempts to locate the Mono and MCS executables correctly
1561 | const BindingFlags StaticNonPublicBindingFlags =
1562 | BindingFlags.NonPublic | BindingFlags.Static;
1563 | const BindingFlags StaticPublicBindingFlags =
1564 | BindingFlags.Public | BindingFlags.Static;
1565 | const string EnvVarMonoPath = "MONO_PATH";
1566 | const string EnvVarCsiCompPath = "CSI_COMPILER_PATH";
1567 | #if UNITY_2_6
1568 | const string CompilerDirectoryName = "MonoCompiler.framework";
1569 | const string RuntimeDirectoryName = CompilerDirectoryName;
1570 | #else // i.e., 3.0 and greater
1571 | const string CompilerDirectoryName = "Mono/lib/mono/2.0";
1572 | const string RuntimeDirectoryName = "Mono/bin";
1573 | #endif
1574 | const string DataRootProgramGuessPath = "Unity/Editor/Data/";
1575 | const string CompilerProgramGuessPath = DataRootProgramGuessPath + CompilerDirectoryName;
1576 | const string RuntimeProgramGuessPath = DataRootProgramGuessPath + RuntimeDirectoryName;
1577 | string mcsPath, monoPath;
1578 | string envValMonoPath =
1579 | Environment.GetEnvironmentVariable(EnvVarMonoPath);
1580 | string[] envSplitMonoPath =
1581 | string.IsNullOrEmpty(envValMonoPath) ? new string[0] :
1582 | envValMonoPath.Split(Path.PathSeparator);
1583 | FieldInfo mcsPathField = monoCompilerType.GetField(
1584 | "windowsMcsPath", StaticNonPublicBindingFlags);
1585 | FieldInfo monoPathField = monoCompilerType.GetField(
1586 | "windowsMonoPath", StaticNonPublicBindingFlags);
1587 | FieldInfo directorySeparatorCharField = typeof(Path).
1588 | GetField("DirectorySeparatorChar", StaticPublicBindingFlags);
1589 | if ((mcsPathField == null) ||
1590 | (monoPathField == null) ||
1591 | (directorySeparatorCharField == null))
1592 | {
1593 | Debug.LogWarning(
1594 | "The C# compiler may not yet work on this version " +
1595 | "of Mono, since some of the expected fields were not " +
1596 | "found! Please provide feedback about test results.");
1597 | return;
1598 | }
1599 |
1600 | // To bypass the problematic initialization
1601 | // code, pretend we're running of Unix
1602 | directorySeparatorCharField.SetValue(null, '/');
1603 | try
1604 | {
1605 | // Now access a static member to ensure that
1606 | // the static constructor has been executed
1607 | mcsPath = (string)mcsPathField.GetValue(null);
1608 | monoPath = (string)monoPathField.GetValue(null);
1609 | }
1610 | finally
1611 | {
1612 | // Restore the bypass mechanism made earlier
1613 | directorySeparatorCharField.SetValue(null, '\\');
1614 | }
1615 |
1616 | // Attempt to locate the MCS and Mono executables for Unity
1617 | if ((!File.Exists(mcsPath)) ||
1618 | (!File.Exists(monoPath)))
1619 | {
1620 | string compilerPath =
1621 | GetFullPathOfAssembly(Assembly.GetEntryAssembly()) ?? // Unity 2.6.1
1622 | GetFullPathOfAssembly(typeof(System.Uri).Assembly); // Unity 3.x
1623 | string runtimePath = compilerPath;
1624 | if (!string.IsNullOrEmpty(compilerPath))
1625 | {
1626 | compilerPath = Path.GetFullPath(compilerPath);
1627 | do
1628 | {
1629 | compilerPath = Path.GetDirectoryName(compilerPath);
1630 | if (compilerPath == null)
1631 | {
1632 | break;
1633 | }
1634 |
1635 | if (Directory.Exists(Path.Combine(
1636 | compilerPath, CompilerDirectoryName)))
1637 | {
1638 | compilerPath = Path.Combine(
1639 | compilerPath, CompilerDirectoryName);
1640 | break;
1641 | }
1642 | }
1643 | while (Directory.Exists(compilerPath));
1644 | }
1645 |
1646 | if (string.Equals(
1647 | CompilerDirectoryName,
1648 | RuntimeDirectoryName,
1649 | StringComparison.Ordinal))
1650 | {
1651 | runtimePath = null;
1652 | }
1653 | else if (!string.IsNullOrEmpty(runtimePath))
1654 | {
1655 | runtimePath = Path.GetFullPath(runtimePath);
1656 | do
1657 | {
1658 | runtimePath = Path.GetDirectoryName(runtimePath);
1659 | if (runtimePath == null)
1660 | {
1661 | break;
1662 | }
1663 |
1664 | if (Directory.Exists(Path.Combine(
1665 | runtimePath, RuntimeDirectoryName)))
1666 | {
1667 | runtimePath = Path.Combine(
1668 | runtimePath, RuntimeDirectoryName);
1669 | break;
1670 | }
1671 | }
1672 | while (Directory.Exists(runtimePath));
1673 | }
1674 |
1675 | string envValCsiCompPath =
1676 | Environment.GetEnvironmentVariable(EnvVarCsiCompPath);
1677 | string[] envSplitCsiCompPath =
1678 | string.IsNullOrEmpty(envValCsiCompPath) ? new string[0] :
1679 | envValCsiCompPath.Split(Path.PathSeparator);
1680 | List searchPaths = new List();
1681 | searchPaths.Add(Directory.GetCurrentDirectory());
1682 | searchPaths.AddRange(envSplitCsiCompPath);
1683 | searchPaths.Add(compilerPath);
1684 | searchPaths.Add(runtimePath);
1685 | searchPaths.AddRange(envSplitMonoPath);
1686 | foreach (string programFilesRoot in new string[]
1687 | {
1688 | Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles),
1689 | Environment.GetFolderPath(Environment.SpecialFolder.CommonProgramFiles),
1690 | Environment.GetEnvironmentVariable("ProgramFiles"),
1691 | Environment.GetEnvironmentVariable("ProgramFiles(x86)")
1692 | })
1693 | {
1694 | if (!string.IsNullOrEmpty(programFilesRoot))
1695 | {
1696 | searchPaths.Add(Path.Combine(
1697 | programFilesRoot, CompilerProgramGuessPath));
1698 | searchPaths.Add(Path.Combine(
1699 | programFilesRoot, RuntimeProgramGuessPath));
1700 | }
1701 | }
1702 |
1703 | if (!File.Exists(mcsPath))
1704 | {
1705 | mcsPath = SearchForFullPath("gmcs", searchPaths, ".exe", ".bat"); // Must be EXE for Unity 3.x
1706 | }
1707 |
1708 | if (!File.Exists(monoPath))
1709 | {
1710 | monoPath = SearchForFullPath("mono", searchPaths, ".bat", ".exe");
1711 | }
1712 |
1713 | if ((!File.Exists(mcsPath)) ||
1714 | (!File.Exists(monoPath)))
1715 | {
1716 | // Attempt to revert to calling the bypassed static constructor
1717 | ConstructorInfo staticConstructor =
1718 | monoCompilerType.TypeInitializer;
1719 | if (staticConstructor == null)
1720 | {
1721 | Debug.LogWarning(
1722 | "The C# compiler may not yet work on " +
1723 | "this version of Mono, since some of " +
1724 | "the paths are still missing! Please " +
1725 | "provide feedback about test results.");
1726 | }
1727 | else
1728 | {
1729 | staticConstructor.Invoke(null, null);
1730 | string alternativePath =
1731 | (string)mcsPathField.GetValue(null);
1732 | if (File.Exists(alternativePath))
1733 | {
1734 | mcsPath = alternativePath;
1735 | }
1736 |
1737 | alternativePath =
1738 | (string)monoPathField.GetValue(null);
1739 | if (File.Exists(monoPath))
1740 | {
1741 | monoPath = alternativePath;
1742 | }
1743 | }
1744 | }
1745 |
1746 | // Keep any valid paths that were located
1747 | if (File.Exists(mcsPath))
1748 | {
1749 | mcsPathField.SetValue(null, mcsPath);
1750 | }
1751 |
1752 | if (File.Exists(monoPath))
1753 | {
1754 | monoPathField.SetValue(null, monoPath);
1755 | }
1756 | }
1757 |
1758 | #if UNITY_2_6
1759 | // Ensure that the Mono-path environment-variable exists,
1760 | // since the C# compiler needs this to find mscorlib.dll.
1761 | // NOTE: Since Unity 3.0.0, this is apparently no longer required.
1762 | if ((envSplitMonoPath.Length <= 0) ||
1763 | (/* single path that doesn't exist */ (envSplitMonoPath.Length == 1) &&
1764 | (!Directory.Exists(envSplitMonoPath[0]))))
1765 | {
1766 | if (File.Exists(mcsPath))
1767 | {
1768 | envValMonoPath = Path.GetDirectoryName(mcsPath);
1769 | }
1770 |
1771 | if ((!Directory.Exists(envValMonoPath)) &&
1772 | File.Exists(monoPath))
1773 | {
1774 | envValMonoPath = Path.GetDirectoryName(monoPath);
1775 | }
1776 |
1777 | if (!Directory.Exists(envValMonoPath))
1778 | {
1779 | envValMonoPath = Path.GetDirectoryName(
1780 | GetFullPathOfAssembly(typeof(int).Assembly));
1781 | }
1782 |
1783 | if (Directory.Exists(envValMonoPath))
1784 | {
1785 | Environment.SetEnvironmentVariable(
1786 | EnvVarMonoPath, envValMonoPath);
1787 | }
1788 | }
1789 | #endif
1790 | }
1791 | catch (IOException)
1792 | {
1793 | // Probably running in the web player without required rights
1794 | }
1795 | }
1796 |
1797 | private static string SearchForFullPath(
1798 | string fileNameWithoutExtension,
1799 | IEnumerable searchPaths,
1800 | params string[] fileExtensions)
1801 | {
1802 | if (searchPaths == null)
1803 | {
1804 | return null;
1805 | }
1806 |
1807 | string applicationPath;
1808 | foreach (string searchPath in searchPaths)
1809 | {
1810 | if (string.IsNullOrEmpty(searchPath))
1811 | {
1812 | continue;
1813 | }
1814 |
1815 | applicationPath = Path.Combine(
1816 | searchPath, fileNameWithoutExtension);
1817 | if ((fileExtensions == null) ||
1818 | (fileExtensions.Length <= 0))
1819 | {
1820 | if (File.Exists(applicationPath))
1821 | {
1822 | return applicationPath;
1823 | }
1824 | }
1825 | else
1826 | {
1827 | foreach (string fileExtension in fileExtensions)
1828 | {
1829 | applicationPath = Path.ChangeExtension(
1830 | applicationPath, fileExtension);
1831 | if (File.Exists(applicationPath))
1832 | {
1833 | return applicationPath;
1834 | }
1835 | }
1836 | }
1837 | }
1838 |
1839 | return null;
1840 | }
1841 | }
1842 | }
1843 |
--------------------------------------------------------------------------------
/Source/Assets/Plugins/CSI/CSharpInterpreter_Include.txt:
--------------------------------------------------------------------------------
1 | /r System.Core.dll
2 | // /r System.Xml.dll
3 | // /r System.Data.dll
4 | // /n System.Linq
5 | /n System.Collections.Generic
6 | /n System.IO
7 | /n System.Text.RegularExpressions
8 | /n System.Text
9 | /n System.Reflection
10 | // /n System.Diagnostics
11 | #def FOR(i,n) for(int i = 0; i < (n); i++)
12 | #def print(x) Print(x)
13 | #def P(x) Print(x)
14 | #def PL(x) Printl(x)
15 | #def sin Math.Sin
16 | #def cos Math.Cos
17 | #def M(klass) MInfo(typeof(klass),null)
18 | #def MI(method) MInfo(null, #method)
19 | #def clear CSharpInterpreter.Current.ClearOutput()
20 | #def hide CSharpInterpreter.Current.Invoke(delegate () { CSharpInterpreter.Current.showInteractiveGUI = false; })
21 | #def exit CSharpInterpreter.Current.Invoke(delegate () { CSharpInterpreter.Current.enabled = false; })
22 | #def quit UnityEngine.Application.Quit()
23 | #def pwd(x) Directory.GetCurrentDirectory()
24 |
--------------------------------------------------------------------------------
/Source/Assets/Plugins/CSI/interpreter.cs:
--------------------------------------------------------------------------------
1 | //-----------------------------------------------------------------------
2 | //
3 | // CSI: A simple C# interpreter
4 | //
5 | //
6 | // Copyright (c) 2008-2010 Tiaan Geldenhuys
7 | // Copyright (c) 2005 Steve Donovan
8 | //
9 | // Permission is hereby granted, free of charge, to any person
10 | // obtaining a copy of this software and associated documentation
11 | // files (the "Software"), to deal in the Software without
12 | // restriction, including without limitation the rights to use,
13 | // copy, modify, merge, publish, distribute, sublicense, and/or
14 | // sell copies of the Software, and to permit persons to whom the
15 | // Software is furnished to do so, subject to the following
16 | // conditions:
17 | //
18 | // The above copyright notice and this permission notice shall be
19 | // included in all copies or substantial portions of the Software.
20 | //
21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
23 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
25 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
26 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
27 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
28 | // OTHER DEALINGS IN THE SOFTWARE.
29 | //
30 | //-----------------------------------------------------------------------
31 | namespace CSI
32 | {
33 | using System;
34 | using System.CodeDom.Compiler;
35 | using System.Collections;
36 | using System.IO;
37 | using System.Reflection;
38 | using System.Text;
39 | using System.Text.RegularExpressions;
40 | using Microsoft.CSharp;
41 |
42 | public interface IConsole
43 | {
44 | void ReadLineAsync(Interpreter.InputHandler callback);
45 | string Write(string s);
46 | int GetLineWidth();
47 | int GetMaxLines();
48 | }
49 |
50 | public class Utils
51 | {
52 | static Type lastClass = null;
53 | public static Interpreter interpreter;
54 |
55 | // It's possible to load scripts from within the interpreter.
56 | public static void Include(string file)
57 | {
58 | interpreter.ReadIncludeFile(file);
59 | }
60 | private static System.Collections.Generic.Dictionary typeDictionary =
61 | new System.Collections.Generic.Dictionary(StringComparer.Ordinal);
62 | private static object typeDictionaryLock = new object();
63 |
64 | public static Type GetType(string typeName)
65 | {
66 | Type type;
67 | lock (typeDictionaryLock)
68 | {
69 | if (typeDictionary.TryGetValue(typeName, out type))
70 | return type;
71 | }
72 |
73 | type = Type.GetType(typeName);
74 | if (type == null)
75 | {
76 | foreach (System.Reflection.Assembly assembly in System.AppDomain.CurrentDomain.GetAssemblies())
77 | {
78 | try
79 | {
80 | type = assembly.GetType(typeName);
81 | if (type != null)
82 | {
83 | break;
84 | }
85 | }
86 | catch
87 | {
88 | // Skip problematic assemblies
89 | }
90 | }
91 |
92 | if (type == null)
93 | {
94 | string fullTypeName;
95 | foreach (string nameSpace in interpreter.GetNamespaces())
96 | {
97 | fullTypeName = nameSpace + "." + typeName;
98 | foreach (System.Reflection.Assembly assembly in System.AppDomain.CurrentDomain.GetAssemblies())
99 | {
100 | try
101 | {
102 | type = assembly.GetType(fullTypeName);
103 | if (type != null)
104 | {
105 | break;
106 | }
107 | }
108 | catch
109 | {
110 | // Skip problematic assemblies
111 | }
112 | }
113 |
114 | if (type != null)
115 | {
116 | break;
117 | }
118 | }
119 | }
120 | }
121 |
122 | // Cache the lookup result to speeds up subsequent lookups
123 | // NOTE: Failed lookups are also cached by inserting null values,
124 | // which prevents additional lengthy repeats of the process
125 | lock (typeDictionaryLock)
126 | {
127 | if (typeDictionary.ContainsKey(typeName))
128 | {
129 | // Compensate for a possible race condition
130 | if (typeDictionary[typeName] != null)
131 | {
132 | type = typeDictionary[typeName];
133 | }
134 | else if (type != null)
135 | {
136 | typeDictionary[typeName] = type;
137 | }
138 | }
139 | else
140 | {
141 | typeDictionary.Add(typeName, type);
142 | }
143 | }
144 |
145 | return type;
146 | }
147 |
148 | public static void Meta(object objectOrStaticType)
149 | {
150 | Meta(objectOrStaticType, null /* memberNameFilter */);
151 | }
152 |
153 | public static void Meta(object objectOrStaticType, string memberNameFilter)
154 | {
155 | string[] memberNames = GetMeta(objectOrStaticType, memberNameFilter);
156 | if ((memberNames == null) ||
157 | (memberNames.Length <= 0))
158 | {
159 | return;
160 | }
161 |
162 | foreach (string memberName in memberNames)
163 | {
164 | Write(memberName);
165 | Write(" ");
166 | }
167 |
168 | Write(Environment.NewLine);
169 | }
170 |
171 | public static string[] GetMeta(object objectOrStaticType)
172 | {
173 | return GetMeta(objectOrStaticType, null /* memberNameFilter */);
174 | }
175 |
176 | public static string[] GetMeta(object objectOrStaticType, string memberNameFilter)
177 | {
178 | if (objectOrStaticType == null)
179 | {
180 | objectOrStaticType = lastClass;
181 | if (objectOrStaticType == null)
182 | {
183 | return null;
184 | }
185 | }
186 |
187 | Type type;
188 | MemberInfo[] members;
189 | if (objectOrStaticType is Type)
190 | {
191 | // Query static members
192 | type = (Type)objectOrStaticType;
193 | members = type.GetMembers(BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy);
194 | }
195 | else
196 | {
197 | // Query instance members
198 | type = objectOrStaticType.GetType();
199 | members = type.GetMembers(BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy);
200 | }
201 |
202 | if ((members == null) || (members.Length <= 0))
203 | {
204 | return null;
205 | }
206 |
207 | // Sort alphabetically -- without LINQ :(
208 | lastClass = type;
209 | string[] memberNames = new string[members.Length];
210 | for (int index = members.Length - 1; index >= 0; index--)
211 | {
212 | MemberInfo member = members[index];
213 | if (member is MethodInfo)
214 | {
215 | MethodInfo methodInfo = (MethodInfo)member;
216 | if (((methodInfo.Attributes & MethodAttributes.SpecialName) == MethodAttributes.SpecialName) &&
217 | (member.Name.StartsWith("get_") || member.Name.StartsWith("set_")))
218 | {
219 | memberNames[index] = member.Name.Substring(4);
220 | }
221 | else
222 | {
223 | memberNames[index] = member.Name;
224 | }
225 | }
226 | else
227 | {
228 | memberNames[index] = member.Name;
229 | }
230 | }
231 |
232 | Array.Sort(memberNames, members, StringComparer.OrdinalIgnoreCase);
233 |
234 | Regex filterRegex;
235 | if (string.IsNullOrEmpty(memberNameFilter))
236 | {
237 | filterRegex = null;
238 | }
239 | else
240 | {
241 | filterRegex = new Regex(memberNameFilter, RegexOptions.IgnoreCase);
242 | }
243 |
244 | string lastName = null;
245 | System.Collections.Generic.List resultMemberNames =
246 | new System.Collections.Generic.List();
247 | for (int index = 0; index < members.Length; index++)
248 | {
249 | string name = memberNames[index];
250 | MemberInfo member = members[index];
251 | if (string.Equals(lastName, name, StringComparison.Ordinal) ||
252 | ((filterRegex != null) && (!filterRegex.IsMatch(name)) && (!filterRegex.IsMatch(member.Name))))
253 | {
254 | continue;
255 | }
256 |
257 | lastName = name;
258 | resultMemberNames.Add(name);
259 | }
260 |
261 | return (resultMemberNames.Count <= 0) ? null : resultMemberNames.ToArray();
262 | }
263 |
264 | public static void MInfo(object ctype, string mname)
265 | {
266 | Type t;
267 | if (ctype == null)
268 | {
269 | if (lastClass != null)
270 | ctype = lastClass;
271 | else
272 | return;
273 | }
274 | if (ctype is String)
275 | {
276 | string cname = (string)ctype;
277 | if (cname.Length < 7 || cname.Substring(0, 7) != "System.")
278 | cname = "System." + cname;
279 | t = GetType(cname);
280 | if (t == null) throw (new Exception("is not a type"));
281 | }
282 | else
283 | if (!(ctype is Type))
284 | t = ctype.GetType();
285 | else
286 | t = (Type)ctype;
287 | lastClass = t;
288 | try
289 | {
290 | string lastName = "";
291 | int k = 0;
292 | foreach (MethodInfo mi in t.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy))
293 | {
294 | if (mname == null)
295 | {
296 | if (mi.Name != lastName && mi.Name.IndexOf('_') == -1)
297 | {
298 | lastName = mi.Name;
299 | Write(lastName);
300 | if (++k == 5)
301 | {
302 | Print();
303 | k = 0;
304 | }
305 | else
306 | Write(" ");
307 | }
308 | }
309 | else
310 | {
311 | if (mi.Name == mname)
312 | {
313 | string proto = mi.ToString();
314 | proto = proto.Replace("System.", "");
315 | if (mi.IsStatic)
316 | proto = "static " + proto;
317 | if (mi.IsVirtual)
318 | proto = "virtual " + proto;
319 | Print(proto);
320 | }
321 | }
322 | }
323 | if (k > 0)
324 | Print();
325 | }
326 | catch (Exception e)
327 | {
328 | Print("Error: " + ctype, e.Message);
329 | }
330 | }
331 |
332 | // This is a smart version of Printl, which tries to keep to a reasonable
333 | // line width, and won't go on forever. Also, strings and chars are quoted.
334 | public static void Dumpl(IEnumerable c)
335 | {
336 | Write("{");
337 | int nlines = 0;
338 | StringBuilder sb = new StringBuilder();
339 | int screenWidth = GetLineWidth();
340 | int maxLines = GetMaxLines() - 1;
341 | bool isFirstItem = true;
342 | foreach (object o in c)
343 | {
344 | if (isFirstItem)
345 | {
346 | isFirstItem = false;
347 | }
348 | else
349 | {
350 | sb.Append(',');
351 | }
352 | string s;
353 | if (o != null)
354 | {
355 | s = o.ToString();
356 | if (o is string) s = "\"" + s + "\"";
357 | else
358 | if (o is char) s = "\'" + s + "\'";
359 | }
360 | else
361 | s = "";
362 | if (sb.Length + s.Length >= screenWidth)
363 | {
364 | if (sb.Length > 0)
365 | {
366 | Write(sb.ToString());
367 | Write("\n");
368 | sb.Length = 0;
369 | }
370 | if (++nlines > maxLines)
371 | {
372 | sb.Append(".....");
373 | break;
374 | }
375 | }
376 | sb.Append(s);
377 | }
378 | Write(sb.ToString() + "}\n");
379 | }
380 |
381 | public static void Printl(IEnumerable c)
382 | {
383 | foreach (object o in c)
384 | {
385 | if (o != null) Write(o.ToString());
386 | else Write("");
387 | Write(" ");
388 | }
389 | Write("\n");
390 | }
391 |
392 | // a very convenient function for quick output ('Print' is easier to type than 'Console.WriteLine')
393 | public static void Print(params object[] obj)
394 | {
395 | Printl(obj);
396 | }
397 |
398 | public static void ReadLineAsync(Interpreter.InputHandler callback)
399 | {
400 | Interpreter.Console.ReadLineAsync(callback);
401 | }
402 |
403 | public static void Write(string s)
404 | {
405 | Interpreter.Console.Write(s);
406 | }
407 |
408 | public static int GetLineWidth()
409 | {
410 | return Interpreter.Console.GetLineWidth();
411 | }
412 |
413 | public static int GetMaxLines()
414 | {
415 | return Interpreter.Console.GetMaxLines();
416 | }
417 | }
418 |
419 | public class CodeChunk : Utils
420 | {
421 | public static bool DumpingValue = true;
422 |
423 | // the generated assemblies will have to override this method
424 | public virtual void Go(Hashtable V)
425 | {
426 | }
427 |
428 | // here's the template used to generate the assemblies
429 | public const string Template =
430 | @"$USES$
431 | class CsiChunk : CodeChunk {
432 | public override void Go(Hashtable V) {
433 | $BODY$;
434 | }
435 | }";
436 |
437 | public static void Instantiate(Assembly a, Interpreter interp)
438 | {
439 | Hashtable table = interp.VarTable;
440 | try
441 | {
442 | CodeChunk chunk = (CodeChunk)a.CreateInstance("CsiChunk");
443 | chunk.Go(table);
444 | // we display the type and value of expressions. The variable $_ is
445 | // always set, which is useful if you want to save the result of the last
446 | // calculation.
447 | if (interp.returnsValue && DumpingValue)
448 | {
449 | string stype;
450 | object val = table["_"];
451 | if (val == null)
452 | {
453 | stype = null;
454 | }
455 | else
456 | {
457 | Type type = val.GetType();
458 | stype = interp.GetPublicRuntimeTypeName(type, true);
459 | stype = "(" + (stype ?? interp.GetTypeName(type, true)) + ")";
460 | }
461 | if (val == null)
462 | {
463 | Print("null");
464 | }
465 | else
466 | if (val is string)
467 | {
468 | Print(stype, "'" + val + "'");
469 | }
470 | else
471 | if (val is IEnumerable)
472 | {
473 | Print(stype);
474 | Dumpl((IEnumerable)val);
475 | }
476 | else
477 | Print(stype, val);
478 | }
479 | }
480 | catch (Exception ex)
481 | {
482 | Print(ex.GetType() + " was thrown: " + ex.Message);
483 | }
484 | }
485 | }
486 |
487 | public class CsiFunctionContext : Utils
488 | {
489 | public Hashtable V;
490 |
491 | public const string Template =
492 | @"$USES$
493 | public class $CLASS$ : CsiFunctionContext {
494 | public $BODY$
495 | }";
496 |
497 | public static void Instantiate(Assembly a, Hashtable table, string className, string funName)
498 | {
499 | try
500 | {
501 | CsiFunctionContext dll = (CsiFunctionContext)a.CreateInstance(className);
502 | dll.V = table;
503 | table[className] = dll;
504 | }
505 | catch (Exception ex)
506 | {
507 | Print(ex.GetType() + " was thrown: " + ex.Message);
508 | }
509 | }
510 | }
511 |
512 | public class Interpreter
513 | {
514 | public delegate bool InputHandler(string str);
515 | readonly Hashtable varTable;
516 | public delegate object GetUnknownItem(object key);
517 | public event GetUnknownItem OnGetUnknownItem;
518 |
519 | private class HashtableWithItemGetterHook : Hashtable
520 | {
521 | readonly Interpreter interpreter;
522 | public HashtableWithItemGetterHook(Interpreter interpreter)
523 | {
524 | this.interpreter = interpreter;
525 | }
526 |
527 | public override object this[object key]
528 | {
529 | get
530 | {
531 | if (this.ContainsKey(key))
532 | {
533 | return base[key];
534 | }
535 |
536 | if (this.interpreter.OnGetUnknownItem == null)
537 | {
538 | return null;
539 | }
540 |
541 | return this.interpreter.OnGetUnknownItem(key);
542 | }
543 |
544 | set
545 | {
546 | base[key] = value;
547 | }
548 | }
549 | }
550 | string namespaceString = "";
551 | ArrayList referenceList = new ArrayList();
552 | CSharpCodeProvider prov;
553 | ICodeGenerator gen;
554 | bool mustDeclare = false;
555 | bool showCode = false;
556 | StringBuilder sb = new StringBuilder();
557 | int bcount = 0;
558 | public bool returnsValue;
559 | public static IConsole Console;
560 | static string[] keywords = { "for", "foreach", "while", "using", "if", "switch", "do" };
561 | enum CHash { Expression, Assignment, Function, Class };
562 |
563 | MacroSubstitutor macro = new MacroSubstitutor();
564 |
565 | public Interpreter()
566 | {
567 | this.varTable = new HashtableWithItemGetterHook(this);
568 | AddNamespace("System");
569 | AddNamespace("System.Collections");
570 | AddReference("System.dll"); // Also works on Mono
571 | SetValue("interpreter", this);
572 | SetValue("_", this);
573 | Utils.interpreter = this;
574 | string fullExecutablePath = FullExecutablePath();
575 | if (File.Exists(fullExecutablePath))
576 | {
577 | AddReference(fullExecutablePath);
578 | }
579 |
580 | string localNamespace = typeof(Interpreter).Namespace;
581 | if (!string.IsNullOrEmpty(localNamespace))
582 | {
583 | AddNamespace(localNamespace);
584 | }
585 |
586 | ConstructorInfo constructorInfo = typeof(CSharpCodeProvider).GetConstructor(
587 | new Type[] { typeof(System.Collections.Generic.Dictionary) });
588 | if (constructorInfo == null)
589 | {
590 | prov = new CSharpCodeProvider();
591 | }
592 | else
593 | {
594 | System.Collections.Generic.Dictionary providerOptions =
595 | new System.Collections.Generic.Dictionary();
596 | providerOptions.Add(
597 | "CompilerVersion",
598 | (Environment.Version < new Version("4.0.0.0")) ? "v3.5" : "v4.0");
599 | prov = (CSharpCodeProvider)constructorInfo.Invoke(new object[] { providerOptions });
600 | }
601 |
602 | using (StringWriter stringWriter = new StringWriter())
603 | {
604 | gen = prov.CreateGenerator(stringWriter);
605 | }
606 | }
607 |
608 | // abosolute path of our executable, so it can always be found!
609 | public string FullExecutablePath()
610 | {
611 | Assembly thisAssembly = Assembly.GetAssembly(typeof(Interpreter));
612 | return new Uri(thisAssembly.CodeBase).LocalPath;
613 | }
614 |
615 | // the default .csi file is now found with the executable
616 | public string DefaultIncludeFile()
617 | {
618 | return Path.ChangeExtension(FullExecutablePath(), ".csi");
619 | }
620 |
621 | public bool ReadIncludeFile(string file)
622 | {
623 | if (File.Exists(file))
624 | {
625 | CodeChunk.DumpingValue = false;
626 | using (TextReader tr = File.OpenText(file))
627 | {
628 | while (ProcessLine(tr.ReadLine()))
629 | ;
630 | }
631 | CodeChunk.DumpingValue = true;
632 | return true;
633 | }
634 |
635 | return false;
636 | }
637 |
638 | public bool ReadIncludeCode(string code)
639 | {
640 | if (string.IsNullOrEmpty(code))
641 | {
642 | return false;
643 | }
644 |
645 | CodeChunk.DumpingValue = false;
646 | try
647 | {
648 | using (TextReader tr = new StringReader(code))
649 | {
650 | while (ProcessLine(tr.ReadLine()))
651 | ;
652 | }
653 | }
654 | finally
655 | {
656 | CodeChunk.DumpingValue = true;
657 | }
658 |
659 | return true;
660 | }
661 |
662 | public void SetValue(string name, object val)
663 | {
664 | varTable[name] = val;
665 | }
666 |
667 | static readonly Regex usingDirectiveRegex = new Regex(@"^\s*using\s+(?[a-zA-Z_][a-zA-Z0-9_\.]*)\s*;?\s*$");
668 |
669 | public bool ProcessLine(string line)
670 | {
671 | // Statements inside braces will be compiled together
672 | if (line == null)
673 | return false;
674 | if (line == "")
675 | return true;
676 | if ((line[0] == '/') && ((line.Length < 2) || (line[1] != '*'))) // Let comment segments through: "/*"
677 | {
678 | if ((line.Length < 2) || (line[1] != '/')) // Ignore comment lines: "//"
679 | ProcessCommand(line);
680 | return true;
681 | }
682 | Match usingMatch = usingDirectiveRegex.Match(line);
683 | if (usingMatch.Success)
684 | {
685 | AddNamespace(usingMatch.Groups["namespace"].Value);
686 | return true;
687 | }
688 | sb.Append(line);
689 | // ignore {} inside strings! Otherwise keep track of our block level
690 | bool insideQuote = false;
691 | for (int i = 0; i < line.Length; i++)
692 | {
693 | if (line[i] == '\"')
694 | insideQuote = !insideQuote;
695 | if (!insideQuote)
696 | {
697 | if (line[i] == '{') bcount++;
698 | else
699 | if (line[i] == '}') bcount--;
700 | }
701 | }
702 | if (bcount == 0)
703 | {
704 | string code = sb.ToString();
705 | sb = new StringBuilder();
706 | if (code != "")
707 | ExecuteLine(code);
708 | }
709 | return true;
710 | }
711 |
712 | static Regex cmdSplit = new Regex(@"(\w+)($|\s+.+)");
713 | static Regex spaces = new Regex(@"\s+");
714 |
715 | void ProcessCommand(string line)
716 | {
717 | Match m = cmdSplit.Match(line);
718 | string cmd = m.Groups[1].ToString();
719 | string arg = m.Groups[2].ToString().TrimStart(null);
720 | switch (cmd)
721 | {
722 | case "n":
723 | AddNamespace(arg);
724 | break;
725 | case "r":
726 | AddReference(arg);
727 | break;
728 | case "v":
729 | foreach (string v in varTable.Keys)
730 | Utils.Print(v + " = " + varTable[v]);
731 | break;
732 | case "dcl":
733 | MustDeclare = !MustDeclare;
734 | break;
735 | case "code": // show code sent to compiler!
736 | showCode = !showCode;
737 | break;
738 | default:
739 | // a macro may be used as a command; the line is split up and
740 | // and supplied as arguments.
741 | // For macros taking one argument, the whole line is supplied.
742 | MacroEntry me = macro.Lookup(cmd);
743 | if (me != null && me.Parms != null)
744 | {
745 | string[] parms;
746 | if (me.Parms.Length > 1)
747 | parms = spaces.Split(arg);
748 | else
749 | parms = new string[] { arg };
750 | string s = macro.ReplaceParms(me, parms);
751 | ExecuteLine(s);
752 | }
753 | else
754 | Utils.Print("unrecognized command, or bad macro");
755 | break;
756 | }
757 | }
758 |
759 | // the actual dynamic type of an object may not be publically available
760 | // (e.g. Type.GetMethods() returns an array of RuntimeMethodInfo)
761 | // so we look for the first public base class.
762 | static Type GetPublicRuntimeType(Type symType)
763 | {
764 | if (symType == null)
765 | {
766 | return symType;
767 | }
768 | // Find try to find a public class-type
769 | Type pubType = symType;
770 | while ((pubType != null) && (!pubType.IsPublic) && (!pubType.IsNestedPublic))
771 | {
772 | pubType = pubType.BaseType;
773 | }
774 | bool isObject = (pubType == typeof(object));
775 | if (isObject)
776 | {
777 | // Rather try to find a more specific interface-type
778 | // instead, although we remember that this is an object and
779 | // revert back to that type if no public interface is found
780 | pubType = null;
781 | }
782 | if (pubType == null)
783 | {
784 | // As a last resort, try to find a public interface-type
785 | System.Collections.Generic.List interfaceTypes =
786 | new System.Collections.Generic.List();
787 | int interfaceIndex = 0;
788 | while ((pubType == null) &&
789 | (symType != null) &&
790 | (symType != typeof(object)))
791 | {
792 | foreach (Type interfaceType in symType.GetInterfaces())
793 | {
794 | if (interfaceTypes.Contains(interfaceType))
795 | {
796 | continue;
797 | }
798 | interfaceTypes.Add(interfaceType);
799 | }
800 | symType = symType.BaseType;
801 | while (interfaceIndex < interfaceTypes.Count)
802 | {
803 | Type interfaceType = interfaceTypes[interfaceIndex++];
804 | if (interfaceType.IsPublic || interfaceType.IsNestedPublic)
805 | {
806 | pubType = interfaceType;
807 | break;
808 | }
809 | interfaceType = interfaceType.BaseType;
810 | if ((interfaceType == null) ||
811 | (interfaceType == typeof(object)) ||
812 | (interfaceTypes.Contains(interfaceType)))
813 | {
814 | continue;
815 | }
816 | interfaceTypes.Add(interfaceType);
817 | }
818 | }
819 | }
820 | return pubType ?? ((isObject) ? typeof(object) : null);
821 | }
822 |
823 | internal string GetPublicRuntimeTypeName(Type symType, bool useSimplifiedNamespaces)
824 | {
825 | return GetTypeName(GetPublicRuntimeType(symType), useSimplifiedNamespaces);
826 | }
827 |
828 | internal string GetTypeName(Type symType, bool useSimplifiedNamespaces)
829 | {
830 | if (symType == null)
831 | {
832 | return null;
833 | }
834 | if ((symType.IsGenericType) &&
835 | (symType.Namespace == "System") &&
836 | (symType.GetGenericArguments().Length == 1))
837 | {
838 | if (symType == typeof(bool?))
839 | {
840 | return "bool?";
841 | }
842 | if (symType == typeof(sbyte?))
843 | {
844 | return "sbyte?";
845 | }
846 | if (symType == typeof(byte?))
847 | {
848 | return "byte?";
849 | }
850 | if (symType == typeof(char?))
851 | {
852 | return "char?";
853 | }
854 | if (symType == typeof(short?))
855 | {
856 | return "short?";
857 | }
858 | if (symType == typeof(ushort?))
859 | {
860 | return "ushort?";
861 | }
862 | if (symType == typeof(int?))
863 | {
864 | return "int?";
865 | }
866 | if (symType == typeof(uint?))
867 | {
868 | return "uint?";
869 | }
870 | if (symType == typeof(long?))
871 | {
872 | return "long?";
873 | }
874 | if (symType == typeof(ulong?))
875 | {
876 | return "ulong?";
877 | }
878 | if (symType == typeof(float?))
879 | {
880 | return "float?";
881 | }
882 | if (symType == typeof(double?))
883 | {
884 | return "double?";
885 | }
886 | if (symType == typeof(decimal?))
887 | {
888 | return "decimal?";
889 | }
890 | if ((symType == typeof(Guid?)) ||
891 | (symType == typeof(DateTime?)))
892 | {
893 | return GetTypeName(symType.GetGenericArguments()[0],
894 | useSimplifiedNamespaces) + "?";
895 | }
896 | }
897 | string symTypeName;
898 | using (StringWriter stringWriter = new StringWriter())
899 | {
900 | gen.GenerateCodeFromExpression(new System.CodeDom.
901 | CodeTypeReferenceExpression(symType), stringWriter, null);
902 | symTypeName = stringWriter.ToString();
903 | }
904 | if (useSimplifiedNamespaces)
905 | {
906 | foreach (string namespacePrefix in new string[]
907 | {
908 | "System.Collections.Generic.",
909 | "System.Collections.",
910 | "System."
911 | })
912 | {
913 | symTypeName = symTypeName.Replace(namespacePrefix, "");
914 | }
915 | }
916 | return symTypeName;
917 | }
918 |
919 | static Regex quoteRegex = new Regex(@"^""|[^\\]""");
920 | static Regex dollarWord = new Regex(@"\$(?:\w+|\{[^\{\}\r\n\t\f\v""]+\})");
921 | static Regex dollarAssignment = new Regex(@"\$(?:\w+|\{[^\{\}\r\n\t\f\v""]+\})\s*=[^=]");
922 | static Regex plainWord = new Regex(@"\b[a-zA-Z_]\w*");
923 | static Regex plainAssignment = new Regex(@"\b[a-zA-Z_]\w*\s*=[^=]");
924 | static Regex assignment = dollarAssignment;
925 | static Regex wordPattern = dollarWord;
926 |
927 | // 'session variables' like $x will be replaced by ((LastType)V["x"]) where
928 | // LastType is the current type associated with the last value of V["x"].
929 | // The 'MustDeclare' mode; session variables don't need '$', but they must be
930 | // previously declared using var; declarations must look like this 'var = '.
931 | string MassageInput(string s, out bool wasAssignment)
932 | {
933 | // process the words in reverse order when looking for assignments!
934 | MatchCollection words = wordPattern.Matches(s);
935 | Match[] wordArray = new Match[words.Count];
936 | words.CopyTo(wordArray, 0);
937 | Array.Reverse(wordArray);
938 | wasAssignment = false;
939 | bool varDeclaration = false;
940 | for (int i = 0; i < wordArray.Length; i++)
941 | {
942 | Match m = wordArray[i];
943 | // exclude matches found inside strings
944 | if (m.Index > 0 && (quoteRegex.Matches(s.Substring(0, m.Index)).Count % 2) != 0 && (quoteRegex.Matches(s.Substring(m.Index)).Count % 2) != 0)
945 | continue;
946 | string sym = m.Value;
947 | if (!mustDeclare) // strip the '$'
948 | sym = sym.EndsWith("}") ? sym.Substring(2, sym.Length - 3) : sym.Substring(1);
949 | else
950 | { // either it's a declaration, or the var was previously declared.
951 | if (sym == "var")
952 | continue;
953 | // are we preceded by 'var'? If so, this is a declaration
954 | if (i + 1 < wordArray.Length && wordArray[i + 1].Value == "var")
955 | varDeclaration = true;
956 | else if (varTable[sym] == null)
957 | continue;
958 | }
959 | string symRef = "V[\"" + sym + "\"]"; // will index our hashtable
960 | // are we followed by an assignment operator?
961 | Match lhs = assignment.Match(s, m.Index);
962 | wasAssignment = lhs.Success && lhs.Index == m.Index;
963 | object symVal = varTable[sym];
964 | string symTypeName = (symVal == null) ? null : GetPublicRuntimeTypeName(symVal.GetType(), false);
965 | // unless we're on the LHS, try to strongly type this variable reference.
966 | if ((!string.IsNullOrEmpty(symTypeName)) && (!wasAssignment))
967 | {
968 | symRef = "((" + symTypeName + ")" + symRef + ")";
969 | }
970 | s = wordPattern.Replace(s, symRef, 1, m.Index);
971 | }
972 | if (varDeclaration)
973 | s = s.Replace("var ", "");
974 | return s;
975 | }
976 |
977 | static Regex funDef = new Regex(@"^\s*[a-zA-Z]\w*\s+([a-zA-Z]\w*)\s*\(.*\)\s*{");
978 | static int nextAssembly = 1;
979 |
980 | void ExecuteLine(string codeStr)
981 | {
982 | // at this point we either have a line to be immediately compiled and evaluated,
983 | // or a function definition.
984 | CHash type = CHash.Expression;
985 | string className = null, assemblyName = null, funName = null;
986 | Match funMatch = funDef.Match(codeStr);
987 | if (funMatch.Success)
988 | type = CHash.Function;
989 | if (type == CHash.Function)
990 | {
991 | funName = funMatch.Groups[1].ToString();
992 | macro.RemoveMacro(funName);
993 | className = "Csi" + nextAssembly++;
994 | assemblyName = className + ".dll";
995 | codeStr = codeStr.Insert(funMatch.Groups[1].Index, "_");
996 | }
997 | codeStr = macro.ProcessLine(codeStr);
998 | if (codeStr == "") // may have been a prepro statement!
999 | return;
1000 | bool wasAssignment;
1001 | codeStr = MassageInput(codeStr, out wasAssignment);
1002 | if (wasAssignment)
1003 | type = CHash.Assignment;
1004 | CompilerResults cr = CompileLine(codeStr.TrimStart(), type, assemblyName, className);
1005 | if (cr != null)
1006 | {
1007 | Assembly ass = cr.CompiledAssembly;
1008 | if (type != CHash.Function)
1009 | CodeChunk.Instantiate(ass, this);
1010 | else
1011 | {
1012 | CsiFunctionContext.Instantiate(ass, varTable, className, funName);
1013 | string prefix = mustDeclare ? "" : "$";
1014 | macro.AddMacro(funName, prefix + className + "._" + funName, null);
1015 | AddReference(Path.GetFullPath(assemblyName));
1016 | }
1017 | }
1018 | }
1019 |
1020 | CompilerResults CompileTemplate(CompilerParameters cp, string codeStr, CHash type, string className)
1021 | {
1022 | if (showCode)
1023 | Utils.Print("code:", codeStr);
1024 | string finalSource = CodeChunk.Template;
1025 | if (type == CHash.Function)
1026 | finalSource = CsiFunctionContext.Template;
1027 | finalSource = finalSource.Replace("$USES$", namespaceString);
1028 | finalSource = finalSource.Replace("$BODY$", codeStr);
1029 | if (type == CHash.Function)
1030 | finalSource = finalSource.Replace("$CLASS$", className);
1031 | return prov.CompileAssemblyFromSource(cp, finalSource);
1032 | }
1033 |
1034 | static Regex beginWord = new Regex(@"^\w+");
1035 |
1036 | string firstToken(string s)
1037 | {
1038 | Match m = beginWord.Match(s);
1039 | return m.ToString();
1040 | }
1041 |
1042 | bool word_within(string s, string[] strs)
1043 | {
1044 | return Array.IndexOf(strs, s) != -1;
1045 | }
1046 |
1047 | CompilerResults CompileLine(string codeStr, CHash type, string assemblyName, string className)
1048 | {
1049 | CompilerParameters cp = new CompilerParameters();
1050 | if (type == CHash.Function)
1051 | cp.OutputAssembly = assemblyName;
1052 | else
1053 | cp.GenerateInMemory = true;
1054 |
1055 | foreach (string r in referenceList)
1056 | {
1057 | #if DEBUG
1058 | if (!System.Diagnostics.Debugger.IsAttached)
1059 | Utils.Print(r);
1060 | #endif
1061 | cp.ReferencedAssemblies.Add(r);
1062 | }
1063 |
1064 | string exprStr = codeStr;
1065 | returnsValue = false;
1066 | if (type == CHash.Expression)
1067 | {
1068 | if (codeStr[0] != '{' && !word_within(firstToken(codeStr), keywords))
1069 | {
1070 | returnsValue = true;
1071 | exprStr = "V[\"_\"] = " + codeStr;
1072 | }
1073 | }
1074 | CompilerResults cr = CompileTemplate(cp, exprStr, type, className);
1075 | if (cr.Errors.HasErrors)
1076 | {
1077 | if (returnsValue)
1078 | {
1079 | // we assumed that this expression did return a value; we were wrong.
1080 | // Try it again, without assignment to $_
1081 | returnsValue = false;
1082 | cp.OutputAssembly = null; // Reset value, which is needed for Mono to work
1083 | CompilerResults cr2 = CompileTemplate(cp, codeStr, CHash.Expression, "");
1084 | if (!cr2.Errors.HasErrors)
1085 | return cr2;
1086 | try
1087 | {
1088 | bool firstErrorIsTypeConversion = false;
1089 | foreach (CompilerError err in cr.Errors)
1090 | {
1091 | // Check for "Cannot implicitly convert type `void' to `object'"
1092 | if (string.Equals("CS0029", err.ErrorNumber, StringComparison.OrdinalIgnoreCase)
1093 | && (!string.IsNullOrEmpty(err.ErrorText))
1094 | && (err.ErrorText.IndexOf("void", 0, StringComparison.OrdinalIgnoreCase) >= 0))
1095 | {
1096 | firstErrorIsTypeConversion = true;
1097 | break;
1098 | }
1099 | }
1100 |
1101 | bool secondErrorIsTooCommon = false;
1102 | foreach (CompilerError err in cr2.Errors)
1103 | {
1104 | // Check for "Only assignment, call, increment, decrement, and new object expressions can be used as a statement"
1105 | if (string.Equals("CS0201", err.ErrorNumber, StringComparison.OrdinalIgnoreCase))
1106 | {
1107 | secondErrorIsTooCommon = true;
1108 | break;
1109 | }
1110 | }
1111 |
1112 | // Usually show the second error, unless it is not very
1113 | // informative and the first error is unlikely to have
1114 | // been caused by our editing of the expression string
1115 | if ((!secondErrorIsTooCommon) || (firstErrorIsTypeConversion))
1116 | {
1117 | cr = cr2;
1118 | }
1119 | }
1120 | catch
1121 | {
1122 | // Assume that most recent error is mostly appropriate
1123 | cr = cr2;
1124 | }
1125 | }
1126 | ShowErrors(cr, codeStr);
1127 | return null;
1128 | }
1129 | else
1130 | return cr;
1131 | }
1132 |
1133 | System.Collections.Generic.List namespaces =
1134 | new System.Collections.Generic.List();
1135 |
1136 | public string[] GetNamespaces()
1137 | {
1138 | return this.namespaces.ToArray();
1139 | }
1140 |
1141 | public bool AddNamespace(string ns)
1142 | {
1143 | foreach (string nameSpace in this.namespaces)
1144 | {
1145 | if (string.Equals(ns, nameSpace, StringComparison.Ordinal))
1146 | {
1147 | return false;
1148 | }
1149 | }
1150 |
1151 | this.namespaces.Add(ns);
1152 | namespaceString = namespaceString + "using " + ns + ";\n";
1153 | return true;
1154 | }
1155 |
1156 | public void AddReference(string r)
1157 | {
1158 | referenceList.Add(r);
1159 | }
1160 |
1161 | void ShowErrors(CompilerResults cr, string codeStr)
1162 | {
1163 | StringBuilder sbErr;
1164 | sbErr = new StringBuilder("Compiling string: ");
1165 | sbErr.AppendFormat("'{0}'\n\n", codeStr);
1166 | foreach (CompilerError err in cr.Errors)
1167 | {
1168 | sbErr.AppendFormat(
1169 | "{0}{1}\n",
1170 | (err.ErrorText ?? string.Empty).Trim(),
1171 | (string.IsNullOrEmpty(err.ErrorNumber) ? string.Empty : (" [" + err.ErrorNumber + "]")));
1172 | }
1173 | Utils.Print(sbErr.ToString());
1174 | }
1175 |
1176 | public Hashtable VarTable
1177 | {
1178 | get { return varTable; }
1179 | }
1180 |
1181 | public int BlockLevel
1182 | {
1183 | get { return bcount; }
1184 | }
1185 |
1186 | public bool MustDeclare
1187 | {
1188 | get { return mustDeclare; }
1189 | set
1190 | {
1191 | mustDeclare = value;
1192 | wordPattern = mustDeclare ? plainWord : dollarWord;
1193 | assignment = mustDeclare ? plainAssignment : dollarAssignment;
1194 | }
1195 | }
1196 | }
1197 | }
1198 |
--------------------------------------------------------------------------------
/Source/Assets/Plugins/CSI/prepro.cs:
--------------------------------------------------------------------------------
1 | //-----------------------------------------------------------------------
2 | //
3 | // CSI: A simple C# interpreter
4 | //
5 | //
6 | // Copyright (c) 2008-2010 Tiaan Geldenhuys
7 | // Copyright (c) 2005 Steve Donovan
8 | //
9 | // Permission is hereby granted, free of charge, to any person
10 | // obtaining a copy of this software and associated documentation
11 | // files (the "Software"), to deal in the Software without
12 | // restriction, including without limitation the rights to use,
13 | // copy, modify, merge, publish, distribute, sublicense, and/or
14 | // sell copies of the Software, and to permit persons to whom the
15 | // Software is furnished to do so, subject to the following
16 | // conditions:
17 | //
18 | // The above copyright notice and this permission notice shall be
19 | // included in all copies or substantial portions of the Software.
20 | //
21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
23 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
25 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
26 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
27 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
28 | // OTHER DEALINGS IN THE SOFTWARE.
29 | //
30 | //-----------------------------------------------------------------------
31 | namespace CSI
32 | {
33 | using System;
34 | using System.Collections;
35 | using System.Text.RegularExpressions;
36 |
37 | public class MacroEntry
38 | {
39 | public string Subst;
40 | public string[] Parms;
41 | }
42 |
43 | public class MacroSubstitutor
44 | {
45 | Hashtable macroTable = new Hashtable();
46 |
47 | public void AddMacro(string name, string subst, string[] parms)
48 | {
49 | MacroEntry me = new MacroEntry();
50 | me.Subst = subst;
51 | me.Parms = parms;
52 | macroTable[name] = me;
53 | }
54 |
55 | public void RemoveMacro(string name)
56 | {
57 | macroTable[name] = null;
58 | }
59 |
60 | public MacroEntry Lookup(string s)
61 | {
62 | return (MacroEntry)macroTable[s];
63 | }
64 |
65 | static Regex iden = new Regex(@"\b[a-zA-Z_]\w*");
66 |
67 | public string ReplaceParms(MacroEntry me, string[] actual_parms)
68 | {
69 | Match m;
70 | int istart = 0;
71 | string subst = me.Subst;
72 | while ((m = iden.Match(subst, istart)).Success)
73 | {
74 | int idx = Array.IndexOf(me.Parms, m.Value);
75 | int len = m.Length;
76 | if (idx != -1)
77 | {
78 | string actual = actual_parms[idx];
79 | // A _single_ # before a token means the 'stringizing' operator
80 | if (m.Index > 0 && subst[m.Index - 1] == '#')
81 | {
82 | // whereas ## means 'token-pasting'! #s will be removed later!
83 | if (!(m.Index > 1 && subst[m.Index - 2] == '#'))
84 | actual = '\"' + actual + '\"';
85 | }
86 | subst = iden.Replace(subst, actual, 1, istart);
87 | len = actual.Length;
88 | }
89 | istart = m.Index + len;
90 | }
91 | subst = subst.Replace("#", "");
92 | return subst;
93 | }
94 |
95 | public string Substitute(string str)
96 | {
97 | Match m;
98 | int istart = 0;
99 | while ((m = iden.Match(str, istart)).Success)
100 | {
101 | MacroEntry me = (MacroEntry)macroTable[m.Value];
102 | if (me != null)
103 | {
104 | string subst = me.Subst;
105 | if (me.Parms != null)
106 | {
107 | int i = m.Index + m.Length; // points to char just beyond match
108 | while (i < str.Length && str[i] != '(')
109 | i++;
110 | i++; // just past '('
111 | int parenDepth = 1;
112 | string[] actuals = new string[me.Parms.Length];
113 | int idx = 0, isi = i;
114 | while (parenDepth > 0 && i < str.Length)
115 | {
116 | char ch = str[i];
117 | if (parenDepth == 1 && (ch == ',' || ch == ')'))
118 | {
119 | actuals[idx] = str.Substring(isi, i - isi);
120 | idx++;
121 | isi = i + 1; // past ',' or ')'
122 | }
123 | // understands commas within braces or square brackets (e.g. 'matrix' indexing)
124 | if (ch == '(' || ch == '{' || ch == '[') parenDepth++;
125 | else
126 | if (ch == ')' || ch == '}' || ch == ']') parenDepth--;
127 | i++;
128 | }
129 | if (parenDepth != 0)
130 | {
131 | return "**Badly formed macro call**";
132 | }
133 | subst = ReplaceParms(me, actuals);
134 | istart = m.Index;
135 | str = str.Remove(istart, i - istart);
136 | str = str.Insert(istart, subst);
137 | }
138 | else
139 | {
140 | str = iden.Replace(str, subst, 1, istart);
141 | }
142 | }
143 | else
144 | istart = m.Index + m.Length;
145 | }
146 | return str;
147 | }
148 |
149 | static Regex define = new Regex(@"#def (\w+)($|\s+|\(.+\)\s+)(.+)");
150 |
151 | public string ProcessLine(string line)
152 | {
153 | Match m = define.Match(line);
154 | if (m.Success)
155 | {
156 | string[] parms = null;
157 | string sym = m.Groups[1].ToString();
158 | string subst = m.Groups[3].ToString();
159 | string arg = m.Groups[2].ToString();
160 | if (arg != "")
161 | {
162 | arg = arg.ToString();
163 | if (arg[0] == '(')
164 | {
165 | arg = arg.TrimEnd(null);
166 | arg = arg.Substring(1, arg.Length - 2);
167 | parms = arg.Split(new char[] { ',' });
168 | }
169 | }
170 | AddMacro(sym, subst, parms);
171 | return "";
172 | }
173 | else
174 | return Substitute(line);
175 | }
176 | }
177 | }
178 |
--------------------------------------------------------------------------------