├── .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 | --------------------------------------------------------------------------------