PageRenderTime 22ms CodeModel.GetById 8ms app.highlight 9ms RepoModel.GetById 1ms app.codeStats 0ms

/Microsoft.Scripting/Hosting/Shell/CommandLine.cs

https://bitbucket.org/stefanrusek/xronos
C# | 453 lines | 295 code | 68 blank | 90 comment | 58 complexity | f13ed03d1cef40b3b8e2a362aaff82b8 MD5 | raw file
  1/* ****************************************************************************
  2 *
  3 * Copyright (c) Microsoft Corporation. 
  4 *
  5 * This source code is subject to terms and conditions of the Microsoft Public License. A 
  6 * copy of the license can be found in the License.html file at the root of this distribution. If 
  7 * you cannot locate the  Microsoft Public License, please send an email to 
  8 * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound 
  9 * by the terms of the Microsoft Public License.
 10 *
 11 * You must not remove this notice, or any other, from this software.
 12 *
 13 *
 14 * ***************************************************************************/
 15
 16#if CODEPLEX_40
 17using System;
 18#else
 19using System; using Microsoft;
 20#endif
 21using System.Collections.Generic;
 22using System.IO;
 23using System.Text;
 24using System.Threading;
 25using Microsoft.Scripting.Hosting.Providers;
 26using Microsoft.Scripting.Runtime;
 27using Microsoft.Scripting.Utils;
 28
 29namespace Microsoft.Scripting.Hosting.Shell {
 30    /// <summary>
 31    /// Command line hosting service.
 32    /// </summary>
 33    public class CommandLine {
 34        private LanguageContext _language;
 35        private IConsole _console;
 36        private ConsoleOptions _options;
 37        private ScriptScope _scope;
 38        private ScriptEngine _engine;
 39        private ICommandDispatcher _commandDispatcher;
 40        private int? _terminatingExitCode;
 41
 42        protected IConsole Console { get { return _console; } }
 43        protected ConsoleOptions Options { get { return _options; } }
 44        protected ScriptEngine Engine { get { return _engine; } }
 45        public ScriptScope ScriptScope { get { return _scope; } protected set { _scope = value; } }
 46
 47        /// <summary>
 48        /// Scope is not remotable, and this only works in the same AppDomain.
 49        /// </summary>
 50        protected Scope Scope { 
 51            get {
 52                if (_scope == null) {
 53                    return null;
 54                }
 55                return _scope.Scope; 
 56            }
 57            set {
 58                _scope = new ScriptScope(_engine, value);
 59            }
 60        }
 61        
 62        protected LanguageContext Language {
 63            get {
 64                // LanguageContext is not remotable, and this only works in the same AppDomain.
 65                if (_language == null) {
 66                    _language = HostingHelpers.GetLanguageContext(_engine);
 67                }
 68                return _language;
 69            }
 70        }
 71
 72        protected virtual string Prompt { get { return ">>> "; } }
 73        public virtual string PromptContinuation { get { return "... "; } }
 74        protected virtual string Logo { get { return null; } }
 75
 76        public CommandLine() {
 77        }
 78
 79        protected virtual void Initialize() {
 80            if (_commandDispatcher == null) {
 81                _commandDispatcher = CreateCommandDispatcher();
 82            }
 83        }
 84
 85        protected virtual Scope CreateScope() {
 86            return new Scope();
 87        }
 88
 89        protected virtual ICommandDispatcher CreateCommandDispatcher() {
 90            return new SimpleCommandDispatcher();
 91        }
 92
 93        public virtual void Terminate(int exitCode) {
 94            // The default implementation just sets a flag. Derived types can support better termination
 95            _terminatingExitCode = exitCode;
 96        }
 97
 98        /// <summary>
 99        /// Executes the comand line - depending upon the options provided we will
100        /// either run a single file, a single command, or enter the interactive loop.
101        /// </summary>
102        public int Run(ScriptEngine engine, IConsole console, ConsoleOptions options) {
103            ContractUtils.RequiresNotNull(engine, "engine");
104            ContractUtils.RequiresNotNull(console, "console");
105            ContractUtils.RequiresNotNull(options, "options");
106
107            _engine = engine;
108            _options = options;
109            _console = console;
110
111            Initialize();
112
113            try {
114                return Run();
115
116#if !SILVERLIGHT // ThreadAbortException.ExceptionState
117            } catch (System.Threading.ThreadAbortException tae) {
118                if (tae.ExceptionState is KeyboardInterruptException) {
119                    Thread.ResetAbort();
120                    return -1;
121                } else {
122                    throw;
123                }
124#endif
125            } finally {
126                Shutdown();
127                _engine = null;
128                _options = null;
129                _console = null;
130            }
131        }
132
133        /// <summary>
134        /// Runs the command line.  Languages can override this to provide custom behavior other than:
135        ///     1. Running a single command
136        ///     2. Running a file
137        ///     3. Entering the interactive console loop.
138        /// </summary>
139        /// <returns></returns>
140        protected virtual int Run() {
141            int result;
142
143            if (_options.Command != null) {
144                result = RunCommand(_options.Command);
145            } else if (_options.FileName != null) {
146                result = RunFile(_options.FileName);
147            } else {
148                return RunInteractive();
149            }
150
151            if (_options.Introspection) {
152                return RunInteractiveLoop();
153            }
154
155            return result;
156        }
157
158        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
159        protected virtual void Shutdown() {
160            try {
161                _engine.Runtime.Shutdown();
162            } catch (Exception e) {
163                UnhandledException(e);
164            }
165        }
166
167        protected virtual int RunFile(string fileName) {
168            return RunFile(_engine.CreateScriptSourceFromFile(fileName));
169        }
170
171        protected virtual int RunCommand(string command) {
172            return RunFile(_engine.CreateScriptSourceFromString(command, SourceCodeKind.Statements));
173        }
174
175        /// <summary>
176        /// Runs the specified filename
177        /// </summary>
178        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
179        protected virtual int RunFile(ScriptSource source) {
180            int result = 1;
181
182            if (Options.HandleExceptions) {
183                try {
184                    result = source.ExecuteProgram();
185                } catch (Exception e) {
186                    UnhandledException(e);
187                }
188            } else {
189                result = source.ExecuteProgram();
190            }
191
192            return result;
193        }
194
195        protected void PrintLogo() {
196            if (Logo != null) {
197                _console.Write(Logo, Style.Out);
198            }
199        }
200
201        #region Interactivity
202
203        /// <summary>
204        /// Starts the interactive loop.  Performs any initialization necessary before
205        /// starting the loop and then calls RunInteractiveLoop to start the loop.
206        /// 
207        /// Returns the exit code when the interactive loop is completed.
208        /// </summary>
209        protected virtual int RunInteractive() {
210            PrintLogo();
211            return RunInteractiveLoop();
212        }
213
214        /// <summary>
215        /// Runs the interactive loop.  Repeatedly parse and run interactive actions
216        /// until an exit code is received.  If any exceptions are unhandled displays
217        /// them to the console
218        /// </summary>
219        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
220        protected int RunInteractiveLoop() {
221            if (_scope == null) {
222                _scope = _engine.CreateScope();
223            }
224
225#if !SILVERLIGHT // Remote console
226            string remoteRuntimeChannel = _options.RemoteRuntimeChannel;
227            if (remoteRuntimeChannel != null) {
228                // Publish the ScriptScope so that the host can use it
229                Remote.RemoteRuntimeServer.StartServer(remoteRuntimeChannel, _scope);
230                return 0;
231            }
232#endif
233            int? res = null;
234
235            do {
236                if (Options.HandleExceptions) {
237                    try {
238                        res = TryInteractiveAction();
239#if SILVERLIGHT 
240                    } catch (ExitProcessException e) {
241                        res = e.ExitCode;
242#endif
243                    }  catch (Exception e) {
244                        if (CommandLine.IsFatalException(e)) {
245                            // Some exceptions are too dangerous to try to catch
246                            throw;
247                        }
248
249                        // There should be no unhandled exceptions in the interactive session
250                        // We catch all (most) exceptions here, and just display it,
251                        // and keep on going
252                        UnhandledException(e);
253                    }
254                } else {
255                    res = TryInteractiveAction();
256                }
257
258            } while (res == null);
259
260            return res.Value;
261        }
262
263        internal static bool IsFatalException(Exception e) {
264            ThreadAbortException tae = e as ThreadAbortException;
265            if (tae != null) {
266#if SILVERLIGHT // ThreadAbortException.ExceptionState
267                return true;
268#else
269                if ((tae.ExceptionState as KeyboardInterruptException) == null) {
270                    return true;
271                }
272#endif
273            }
274            return false;
275        }
276
277        protected virtual void UnhandledException(Exception e) {
278            ExceptionOperations exceptionOperations = _engine.GetService<ExceptionOperations>();
279            _console.WriteLine(exceptionOperations.FormatException(e), Style.Error);
280        }
281
282        /// <summary>
283        /// Attempts to run a single interaction and handle any language-specific
284        /// exceptions.  Base classes can override this and call the base implementation
285        /// surrounded with their own exception handling.
286        /// 
287        /// Returns null if successful and execution should continue, or an exit code.
288        /// </summary>
289        protected virtual int? TryInteractiveAction() {
290            int? result = null;
291
292            try {
293                result = RunOneInteraction();
294#if SILVERLIGHT // ThreadAbortException.ExceptionState
295            } catch (ThreadAbortException) {
296#else
297            } catch (ThreadAbortException tae) {
298                KeyboardInterruptException pki = tae.ExceptionState as KeyboardInterruptException;
299                if (pki != null) {
300                    UnhandledException(tae);
301                    Thread.ResetAbort();
302                } else {
303                    throw;
304                }
305#endif
306            }
307
308            return result;
309        }
310
311        /// <summary>
312        /// Parses a single interactive command or a set of statements and executes it.  
313        /// 
314        /// Returns null if successful and execution should continue, or the appropiate exit code.
315        /// 
316        /// We check if the code read is an interactive command or statements is by checking for NewLine
317        /// If the code contains NewLine, it's a set of statements (most probably from SendToConsole)
318        /// If the code does not contain a NewLine, it's an interactive command typed by the user at the prompt
319        /// </summary>
320        private int? RunOneInteraction() {
321            bool continueInteraction;
322            string s = ReadStatement(out continueInteraction);
323
324            if (continueInteraction == false) {
325                return (_terminatingExitCode == null) ? 0 : _terminatingExitCode;
326            }
327
328            if (String.IsNullOrEmpty(s)) {
329                // Is it an empty line?
330                _console.Write(String.Empty, Style.Out);
331                return null;
332            }
333
334            ExecuteCommand(_engine.CreateScriptSourceFromString(s, SourceCodeKind.InteractiveCode));
335            return null;
336        }
337
338        protected object ExecuteCommand(ScriptSource source) {
339            ErrorListener errorListener = new ErrorSinkProxyListener(ErrorSink);
340            CompiledCode compiledCode = source.Compile(_engine.GetCompilerOptions(_scope), errorListener);
341            return _commandDispatcher.Execute(compiledCode, _scope);
342        }
343
344        protected virtual ErrorSink ErrorSink {
345            get { return ErrorSink.Default; }
346        }
347
348        /// <summary>
349        /// Private helper function to see if we should treat the current input as a blank link.
350        /// 
351        /// We do this if we only have auto-indent text.
352        /// </summary>
353        private static bool TreatAsBlankLine(string line, int autoIndentSize) {
354            if (line.Length == 0) return true;
355            if (autoIndentSize != 0 && line.Trim().Length == 0 && line.Length == autoIndentSize) {
356                return true;
357            }
358
359            return false;
360        }
361
362        /// <summary>
363        /// Read a statement, which can potentially be a multiple-line statement suite (like a class declaration).
364        /// </summary>
365        /// <param name="continueInteraction">Should the console session continue, or did the user indicate 
366        /// that it should be terminated?</param>
367        /// <returns>Expression to evaluate. null for empty input</returns>
368        protected string ReadStatement(out bool continueInteraction) {
369            StringBuilder b = new StringBuilder();
370            int autoIndentSize = 0;
371
372            _console.Write(Prompt, Style.Prompt);
373
374            while (true) {
375                string line = ReadLine(autoIndentSize);
376                continueInteraction = true;
377
378                if (line == null || (_terminatingExitCode != null)) {
379                    continueInteraction = false;
380                    return null;
381                }
382
383                bool allowIncompleteStatement = TreatAsBlankLine(line, autoIndentSize);
384                b.Append(line);
385                // Note that this does not use Environment.NewLine because some languages (eg. Python) only
386                // recognize \n as a line terminator.
387                b.Append("\n");
388
389                string code = b.ToString();
390
391                ScriptSource command = _engine.CreateScriptSourceFromString(code, SourceCodeKind.InteractiveCode);
392                ScriptCodeParseResult props = command.GetCodeProperties(_engine.GetCompilerOptions(_scope));
393
394                if (SourceCodePropertiesUtils.IsCompleteOrInvalid(props, allowIncompleteStatement)) {
395                    return props != ScriptCodeParseResult.Empty ? code : null;
396                }
397
398                if (_options.AutoIndent && _options.AutoIndentSize != 0) {
399                    autoIndentSize = GetNextAutoIndentSize(code);
400                }
401
402                // Keep on reading input
403                _console.Write(PromptContinuation, Style.Prompt);
404            }
405        }
406
407        /// <summary>
408        /// Gets the next level for auto-indentation
409        /// </summary>
410        protected virtual int GetNextAutoIndentSize(string text) {
411            return 0;
412        }
413
414        protected virtual string ReadLine(int autoIndentSize) {
415            return _console.ReadLine(autoIndentSize);
416        }
417
418        internal protected virtual TextWriter GetOutputWriter(bool isErrorOutput) {
419            return isErrorOutput ? System.Console.Error : System.Console.Out;
420        }
421
422        //private static DynamicSite<object, IList<string>>  _memberCompletionSite =
423        //    new DynamicSite<object, IList<string>>(OldDoOperationAction.Make(Operators.GetMemberNames));
424
425        public IList<string> GetMemberNames(string code) {
426            object value = _engine.CreateScriptSourceFromString(code, SourceCodeKind.Expression).Execute(_scope);
427            return _engine.Operations.GetMemberNames(value);
428            // TODO: why doesn't this work ???
429            //return _memberCompletionSite.Invoke(new CodeContext(_scope, _engine), value);
430        }
431
432        public virtual IList<string> GetGlobals(string name) {
433            List<string> res = new List<string>();
434            foreach (SymbolId scopeName in _scope.Scope.Keys) {
435                string strName = SymbolTable.IdToString(scopeName);
436                if (strName.StartsWith(name)) {
437                    res.Add(strName);
438                }
439            }
440
441            return res;
442        }
443
444        #endregion
445
446        class SimpleCommandDispatcher : ICommandDispatcher {
447            public object Execute(CompiledCode compiledCode, ScriptScope scope) {
448                return compiledCode.Execute(scope);
449            }
450        }
451    }
452
453}