/// <summary> /// Run a REPL shell only. /// </summary> void RunRepl() { // Print version information if (Conf.UsePowerlines) { ANSI.WriteLine( Powerline() .Segment($"IoX {Versions.IoxVersion} ", fg: DarkCyan, bg: White) .Segment($" Iodine {Versions.IodineVersion} {Versions.IodineBuildDate}", fg: White, bg: DarkCyan) .ToAnsiString()); } else { ANSI.WriteLine($"IOx {Versions.IoxVersion} (Iodine {Versions.IodineVersion} {Versions.IodineBuildDate})\n"); } while (!shouldExit) { // Run a single REP iteration RunIteration(); // Save history Hinter.SaveHistory(); } }
/// <summary> /// Pretty format the specified object. /// </summary> /// <returns>The format.</returns> /// <param name="obj">Object.</param> public static string Format(IodineObject obj) { // Get the type of the object var type = obj.GetType(); // Exceptions are special snowflakes if (obj.Base?.GetType().Name == "IodineException") { type = obj.Base.GetType(); } // Try getting the formatting color ANSIColor color; ColorScheme.TryGetValue(type, out color); // Test if the object has a custom formatter if (Formatters.ContainsKey(type)) { // Invoke the formatter return(ANSI.Sanitize(Formatters [type] (obj, color?.fg ?? string.Empty, $"{ANSI.ESC}[0m"))); } // Oops, there's no custom formatter for that object Logger.Warn($"No formatter for type '{type.Name}'"); return(ANSI.Sanitize($"{color ?? White}{obj}")); }
public static void PrintHelp(bool exit, bool error = false) { ANSI.WriteLine(USAGE.Trim()); if (exit) { Environment.Exit(error ? 1 : 0); } }
public static void HandleSyntaxException(Shell instance, Iodine.Compiler.SyntaxException e) { // Iterate over syntax errors foreach (var err in e.ErrorLog.Errors) { // Test if the error came from somewhere else if (!string.IsNullOrEmpty(err.Location.File)) { // Get the name of the file that caused the error var path = System.IO.Path.GetFileNameWithoutExtension(err.Location.File); // Print the error location and description ANSI.WriteLine($"Error at {White}{path}{Default} ({err.Location.Line + 1}:{err.Location.Column}): {err.Text}"); continue; } // Test if the error has an associated token if (err.HasToken) { // Get the length of the associated token var token_length = err.Token.Value.Length; // Print user input for later highlighting var line = instance.BufferedInput.Split('\n').ElementAtOrDefault(err.Location.Line)?.Trim(); if (!string.IsNullOrEmpty(line)) { // Highlight the error var errHighlight = line.Substring(err.Location.Column, line [err.Location.Column] == '"' ? token_length + 2 : token_length); var errUnderline = string.Empty.PadLeft(token_length, '^').PadLeft(err.Location.Column); Console.Write(line); Console.CursorLeft = err.Location.Column; ANSI.WriteLine($"{DarkRed.bg}{White}{errHighlight}"); ANSI.WriteLine($"{White}{errUnderline}"); } } else { // Print user input for later highlighting var line = instance.BufferedInput.Split('\n').ElementAtOrDefault(err.Location.Line)?.Trim(); if (!string.IsNullOrEmpty(line)) { // Highlight the error var startPosition = Math.Max(0, err.Location.Column - 1); Console.Write(line); Console.CursorLeft = startPosition; ANSI.WriteLine($"{DarkRed.bg}{White}{line [Math.Min (line.Length, startPosition)]}"); ANSI.WriteLine($"{White}{"^".PadLeft (err.Location.Column, ' ')}"); } } // Print the error location and description ANSI.WriteLine($"\n{White}SyntaxError{(err.Location.Line != 0 ? $" at line {err.Location.Line + 1}" : string.Empty)}: {Red}{err.Text}"); } }
/// <summary> /// Enter the REPL. /// </summary> public void Run() { // Apply Iodine context options Iodine.Engine.Context.ShouldCache = !Conf.SuppressCache; Iodine.Engine.Context.ShouldOptimize = !Conf.SuppressOptimizer; // Check 'help' if (Conf.Help) { Configuration.PrintHelp(exit: true, error: false); } // Check 'version' else if (Conf.Version) { ANSI.WriteLine($"IOx {Versions.IoxFullVersion} (Iodine {Versions.IodineFullVersion} {Versions.IodineBuildDate})"); Environment.Exit(0); } // Check 'check' else if (Conf.Check) { RunCheck(); Environment.Exit(0); } // Check 'repl' else if (Conf.Repl) { RunFile(repl: true); Environment.Exit(0); } // Check file else if (!string.IsNullOrEmpty(Conf.File)) { RunFile(repl: false); Environment.Exit(0); } // No subcommands or files were specified else { // Enter the REPL shell RunRepl(); } }
public static void HandleModuleNotFoundException(Shell instance, Iodine.Runtime.ModuleNotFoundException e) { // Print the name of the missing module ANSI.WriteLine($"Unable to find module '{White}{e.Name}{ANSI.ESC}[0m'"); // Iterate over the search paths foreach (var searchPath in e.SearchPath) { // Create a relative URI of the search path var workingPathUri = new Uri(Environment.CurrentDirectory); var searchPathUri = new Uri(System.IO.Path.GetFullPath(searchPath), UriKind.RelativeOrAbsolute); var relativePathUri = workingPathUri.MakeRelativeUri(searchPathUri); // Print the relative search path ANSI.WriteLine($"- ./{relativePathUri}"); } }
/// <summary> /// Run a file, and optionally a REPL shell afterwards. /// </summary> /// <param name="repl">If set to <c>true</c> repl.</param> void RunFile(bool repl) { // Get the file var content = string.Empty; try { content = File.ReadAllText(Conf.File); } catch (FileNotFoundException ex) { ANSI.WriteLine(ex.Message); Environment.Exit(1); } // Compile the module Iodine.Engine.Context.ShouldCache = false; var module = WrapIodineOperation(this, () => Iodine.CompileSource(content)); if (module == null) { return; } // Invoke the module var result = WrapIodineOperation(this, () => Iodine.InvokeModule(module)); if (result == null) { return; } // Test if the REPL shell should be entered if (repl) { // Enter REPL shell RunRepl(); } }
/// <summary> /// Run a syntax check only. /// </summary> void RunCheck() { // Get the file var content = string.Empty; try { content = File.ReadAllText(Conf.File); } catch (FileNotFoundException ex) { ANSI.WriteLine(ex.Message); Environment.Exit(1); } // Compile the module Iodine.Engine.Context.ShouldCache = false; try { Iodine.CompileSource(content); ANSI.WriteLine("OK - No syntax errors."); } catch (Iodine.Compiler.SyntaxException e) { ANSI.WriteLine($"ERR - Syntax errors occurred.\n"); IodineExceptionHandler.HandleSyntaxException(this, e); } catch (Exception e) { ANSI.WriteLine($"ERR - {e.Message}"); } }
/// <summary> /// Initializes a new instance of the <see cref="T:iox.Shell"/> class. /// </summary> public Shell(Configuration conf) { Conf = conf; // Get assembly version AssemblyVersion = ReflectionHelper.GetVersion(); // Define builtin exit function var iodineHookExit = new BuiltinMethodCallback((vm, self, arguments) => { shouldExit = true; return(IodineNull.Instance); }, null); iodineHookExit.SetAttribute("__doc__", new IodineString("Exits the iox REPL shell.")); // Define builtin help function var iodineHookHelp = new BuiltinMethodCallback((vm, self, arguments) => { // Test argument count if (arguments.Length == 0) { ANSI.WriteLine("Please pass an object to the help function!"); return(IodineNull.Instance); } // Get the target object var target = arguments [0]; // Test __doc__ attribute existence if (!target.HasAttribute("__doc__")) { ANSI.WriteLine($"The specified {White}{target.TypeDef.Name}{Default} does not provide any documentation :("); return(IodineNull.Instance); } // Get attribute var attr = target.GetAttribute(vm, "__doc__"); // Get documentation var doc = ( attr as IodineString ?? (attr as IodineProperty)?.Get(vm) as IodineString ?? (attr as InternalIodineProperty)?.Get(vm) as IodineString ); // Write documentation if (doc != null) { PrettyPrint.WriteLine(doc); } return(IodineNull.Instance); }, null); iodineHookHelp.SetAttribute("__doc__", new IodineString("Prints the documentation for the specified object.")); // Setup Iodine context Iodine = new IodineContext(); Iodine.ExposeGlobal("exit", iodineHookExit); Iodine.ExposeGlobal("help", iodineHookHelp); Iodine.AddSearchPaths("./iodine/modules"); if (Environment.GetEnvironmentVariable("IODINE_MODULES") != null) { Iodine.AddSearchPaths(Environment.GetEnvironmentVariable("IODINE_MODULES")); } if (Environment.GetEnvironmentVariable("IODINE_HOME") != null) { Iodine.AddSearchPaths(Path.Combine(Environment.GetEnvironmentVariable("IODINE_HOME"), "modules")); } // Set prompt Prompt = new Prompt("λ"); if (Conf.UsePowerlines) { Prompt = new Prompt(Powerline().Segment($"IoX {AssemblyVersion.ToString (2)} ", fg: DarkCyan, bg: White).ToAnsiString()); } // Set hinter Hinter = new HinterWrapper(this); }
string ReadUserInput() { // Declare variables var accum = new StringBuilder(); string lastLine = string.Empty; bool editingFinished = false; bool correctIndent = false; bool correctIndentExtra = false; // special unindent for matched grouping int foldCount = 0; int foldBracketDiff = 0; int foldBraceDiff = 0; int foldParenDiff = 0; int line = 1; int indent = 0; // Define getFoldCount function var getFoldCount = new Func <string, int> (str => { var lexer = new Iodine.Compiler.Tokenizer( new Iodine.Compiler.ErrorSink(), new Iodine.Compiler.SourceReader(str, "__anonymous__") ); IEnumerable <Iodine.Compiler.Token> tokens; try { tokens = lexer.Scan(); } catch { foldBracketDiff = 0; foldParenDiff = 0; foldBraceDiff = 0; return(0); } foldBracketDiff = ( tokens.Count(t => t.Class == global::Iodine.Compiler.TokenClass.OpenBracket) - tokens.Count(t => t.Class == global::Iodine.Compiler.TokenClass.CloseBracket) ); foldBraceDiff = ( tokens.Count(t => t.Class == global::Iodine.Compiler.TokenClass.OpenBrace) - tokens.Count(t => t.Class == global::Iodine.Compiler.TokenClass.CloseBrace) ); foldParenDiff = ( tokens.Count(t => t.Class == global::Iodine.Compiler.TokenClass.OpenParan) - tokens.Count(t => t.Class == global::Iodine.Compiler.TokenClass.CloseParan) ); return(foldBracketDiff + foldBraceDiff + foldParenDiff); }); // Define getPrompt function var getPrompt = new Func <int, string> (lineNum => { if (Conf.UsePowerlines) { var powerline = Powerline() .Segment($"IoX {AssemblyVersion.ToString (2)} ", fg: DarkCyan, bg: White) .Segment($"{lineNum}", fg: White, bg: DarkCyan) .ToAnsiString(); return(powerline); } return($"{lineNum} λ"); }); // Define rewriteIndent function var rewriteIndent = new Action(() => { // Save cursor state var currentCursorTop = Console.CursorTop; var targetCursorTop = Math.Max(0, Console.CursorTop - 1); // Rewrite last line Console.CursorTop = targetCursorTop; Console.CursorLeft = 0; Console.Write("".PadRight(Console.WindowWidth)); Console.CursorTop = targetCursorTop; Console.CursorLeft = 0; Prompt.Push(getPrompt(Math.Max(0, line - 1))); ANSI.Write(Prompt.ToString()); Prompt.Pop(); Console.Write(string.Empty.PadLeft((correctIndentExtra ? Math.Max(0, indent - 1) : indent) * 2)); Console.Write(lastLine); // Restore cursor state Console.CursorTop = currentCursorTop; Console.CursorLeft = 0; }); // Read more lines while (!editingFinished) { // Test if this is the first line if (line == 1) { // Duplicate prompt Prompt.Dup(); } else { // Push new prompt to visually indicate multi-line editing Prompt.Push(getPrompt(line)); } // Test if the indentation of the previous line should be rewritten if (correctIndent) { // Rewrite line rewriteIndent(); // Do not correct the indentation again correctIndent = false; correctIndentExtra = false; } // Test if the prompt of the previous line should be rewritten if (line == 2) { // Save cursor state var currentCursorTop = Console.CursorTop; var targetCursorTop = Math.Max(0, Console.CursorTop - 1); // Rewrite last line Console.CursorTop = targetCursorTop; Console.CursorLeft = 0; Console.Write("".PadRight(Console.WindowWidth)); Console.CursorTop = targetCursorTop; Console.CursorLeft = 0; Prompt.Push(getPrompt(1)); ANSI.Write(Prompt.ToString()); Prompt.Pop(); Console.Write(lastLine); // Restore cursor state Console.CursorTop = currentCursorTop; Console.CursorLeft = 0; } // Modify prompt Prompt.Push($"{Prompt.ToString ().Trim ()}{string.Empty.PadLeft (indent * 2)}"); // Read line lastLine = Hinter.Edit(Prompt.ToString()) ?? string.Empty; // Restore prompt Prompt.Pop(); //lastLine = Hinter.ReadHintedLine ( // hintSource: Iodine.Engine.Context.Globals.Concat (Iodine.Engine.Context.InteractiveLocals), // hintField: attr => attr.Key, // hintColor: Gray //).Trim (); // lastLine = Console.ReadLine ().Trim (); // Update state var localFoldCount = getFoldCount(lastLine); foldCount += localFoldCount; // Auto-indent based on local fold count if (localFoldCount > 0) { indent += 1; } else if (localFoldCount < 0) { correctIndent = true; indent = Math.Max(0, indent - 1); } // Auto-indent based on total fold count if (foldCount == 0) { indent = 0; } // Auto-indent based on matched close-open grouping operators if (lastLine.Count(c => new [] { '{', '}', '[', ']', '(', ')' }.Contains(c)) >= 2 && lastLine.IndexOfAny(new [] { '}', ']', ')' }) < lastLine.IndexOfAny(new [] { '{', '[', '(' })) { correctIndentExtra = true; correctIndent = true; } editingFinished = foldCount == 0; line += 1; // Test for negative (unfixable) grouping mismatch if (foldCount < 0) { // Clear buffer accum.Clear(); // Output error ANSI.WriteLine($"{Red}Mismatched bracket, brace, or parenthesis group!"); editingFinished = true; } else { // Append line to buffer accum.AppendLine(lastLine); } // Restore prompt Prompt.Pop(); } // Rewrite indent if fold count is 0 if (correctIndent) { rewriteIndent(); } // Return buffer return(accum.ToString()); }
/// <summary> /// Pretty print the specified object. /// </summary> /// <param name="obj">Object.</param> public static void WriteLine(IodineObject obj) { ANSI.WriteLine(Format(obj)); }
/// <summary> /// Convert the powerline to an ANSI string. /// </summary> /// <returns>The ANSI string.</returns> public string ToAnsiString() => ANSI.Sanitize($"{Builder}{lastbg.fg}{ANSI.ESC}[49m{PLColor}");
public static string ReadHintedLine <T, TResult> (IEnumerable <T> hintSource, Func <T, TResult> hintField, ANSIColor hintColor, string inputRegex = ".*") { ConsoleKeyInfo input; var editComplete = true; var accum = string.Empty; var lastWord = string.Empty; var userInput = string.Empty; var suggestion = string.Empty; var initialCursorTop = Console.CursorTop; var initialCursorLeft = Console.CursorLeft; var lastInitialCursorTop = Console.CursorTop; var lastInitialCursorLeft = Console.CursorLeft; var localInitialCursorTop = Console.CursorTop; var localInitialCursorLeft = Console.CursorLeft; #if DEBUG var __DEBUG_TRD = new System.Threading.Thread(() => { while (true) { Console.Title = ($"ACCUM: {accum} | USRIN: {userInput}"); System.Threading.Thread.Sleep(100); } }); __DEBUG_TRD.Start(); #endif // Read next key while (ConsoleKey.Enter != (input = Console.ReadKey(intercept: true)).Key) { // Prepare state if (editComplete) { lastWord = string.Empty; userInput = string.Empty; suggestion = string.Empty; editComplete = false; lastInitialCursorTop = localInitialCursorTop; lastInitialCursorLeft = localInitialCursorLeft; localInitialCursorTop = Console.CursorTop; localInitialCursorLeft = Console.CursorLeft; } // Handle backspace if (input.Key == ConsoleKey.Backspace) { if (userInput.Any()) { userInput = userInput.Any() ? userInput.Remove(userInput.Length - 1, 1) : string.Empty; } else { accum = accum.Remove(Math.Max(0, accum.Length - 1), accum.Length > 0 ? 1 : 0); editComplete = true; // Clear line Console.SetCursorPosition(initialCursorLeft, initialCursorTop); Console.Write(string.Empty.PadLeft(Console.WindowWidth - initialCursorLeft)); Console.SetCursorPosition(initialCursorLeft, initialCursorTop); // Write finished line Console.Write(accum); continue; } } // Handle member access else if (input.Key == ConsoleKey.OemPeriod) { editComplete = true; userInput += input.KeyChar; } // Handle space else if (input.Key == ConsoleKey.Spacebar) { editComplete = true; userInput += input.KeyChar; } // Handle tab (accept suggestion) else if (input.Key == ConsoleKey.Tab) { editComplete = true; userInput = suggestion ?? userInput; } // Test if keychar is not alphanumeric else if (!char.IsLetterOrDigit(input.KeyChar)) { editComplete = true; userInput += input.KeyChar; } // Test if keychar matches input regex else if (Regex.IsMatch(input.KeyChar.ToString(), inputRegex)) { Console.CursorLeft++; userInput += input.KeyChar; } // Get the suggestion suggestion = ( hintSource.Select(item => hintField(item).ToString()) .FirstOrDefault( item => ( item.Length > userInput.Length && item.Substring(0, userInput.Length) == userInput ) ) ); // Get line lastWord = suggestion ?? userInput; // Clear line Console.SetCursorPosition(localInitialCursorLeft, localInitialCursorTop); Console.Write(string.Empty.PadLeft(Console.WindowWidth - localInitialCursorLeft)); Console.SetCursorPosition(localInitialCursorLeft, localInitialCursorTop); // Write user input ANSI.Write($"{(suggestion != null ? White : Default)}{userInput}"); // Write suggestion if (userInput.Any()) { ANSI.Write($"{hintColor}{lastWord.Substring (userInput.Length, lastWord.Length - userInput.Length)}"); if (editComplete) { // Clear line Console.SetCursorPosition(initialCursorLeft, initialCursorTop); Console.Write(string.Empty.PadLeft(Console.WindowWidth - initialCursorLeft)); Console.SetCursorPosition(initialCursorLeft, initialCursorTop); // Write finished line accum += lastWord; Console.Write(accum); } } continue; } if (!editComplete) { accum += userInput; } // Clear line Console.SetCursorPosition(initialCursorLeft, initialCursorTop); Console.Write(string.Empty.PadLeft(Console.WindowWidth - initialCursorLeft)); Console.SetCursorPosition(initialCursorLeft, initialCursorTop); // Write finished line Console.WriteLine(accum); #if DEBUG __DEBUG_TRD.Abort(); #endif // Return read line return(accum); }