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