Merge pull request #3662 from henricm/fix-windowsbase-on-win
[mono.git] / mcs / class / corlib / System / TermInfoDriver.cs
index 55583f98e41b82175d49f24954d4843506809f74..d849b03f8d66dbb4a2106a95baaa950e044ef286 100644 (file)
@@ -5,8 +5,9 @@
 //     Gonzalo Paniagua Javier (gonzalo@ximian.com)
 //
 // (C) 2005,2006 Novell, Inc (http://www.novell.com)
+// Copyright (c) Microsoft.
+// Copyright 2014 Xamarin Inc
 //
-
 //
 // Permission is hereby granted, free of charge, to any person obtaining
 // a copy of this software and associated documentation files (the
 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 //
-#if (NET_2_0||BOOTSTRAP_NET_2_0) && !NET_2_1
+// This code contains the ParameterizedStrings implementation from .NET's
+// Core System.Console:
+// https://github.com/dotnet/corefx
+// src/System.Console/src/System/ConsolePal.Unix.cs
+//
+#if !MOBILE
+
+//
+// Defining this writes the output to console.log
 //#define DEBUG
+
 using System.Collections;
 using System.IO;
 using System.Text;
@@ -43,7 +53,7 @@ namespace System {
                static int terminal_size;
                
                //static uint flag = 0xdeadbeef;
-               static string [] locations = { "/etc/terminfo", "/usr/share/terminfo", "/usr/lib/terminfo" };
+               readonly static string [] locations = { "/usr/share/terminfo", "/etc/terminfo", "/usr/lib/terminfo", "/lib/terminfo" };
 
                TermInfoReader reader;
                int cursorLeft;
@@ -58,7 +68,6 @@ namespace System {
                string term;
                StreamReader stdin;
                CStreamWriter stdout;
-               internal byte verase;
 
                int windowWidth;
                int windowHeight;
@@ -73,48 +82,57 @@ namespace System {
                string keypadXmit, keypadLocal;
                bool controlCAsInput;
                bool inited;
+               object initLock = new object ();
                bool initKeys;
                string origPair;
                string origColors;
                string cursorAddress;
                ConsoleColor fgcolor = ConsoleColor.White;
                ConsoleColor bgcolor = ConsoleColor.Black;
-               bool color16 = false; // terminal supports 16 colors
-               string setlfgcolor;
-               string setlbgcolor;
                string setfgcolor;
                string setbgcolor;
+               int maxColors;
                bool noGetPosition;
                Hashtable keymap;
                ByteMatcher rootmap;
-               bool home_1_1; // if true, we have to add 1 to x and y when using cursorAddress
                int rl_startx = -1, rl_starty = -1;
+               byte [] control_characters; // Indexed by ControlCharacters.XXXXXX
 #if DEBUG
                StreamWriter logger;
 #endif
 
+               static string TryTermInfoDir (string dir, string term )
+               {
+                       string path = String.Format ("{0}/{1:x}/{2}", dir, (int)(term [0]), term);
+                       if (File.Exists (path))
+                               return path;
+                               
+                       path = Path.Combine (dir, term.Substring (0, 1), term);
+                       if (File.Exists (path))
+                               return path;
+                       return null;
+               }
+
                static string SearchTerminfo (string term)
                {
                        if (term == null || term == String.Empty)
                                return null;
 
-                       // Ignore TERMINFO and TERMINFO_DIRS by now
-                       //string terminfo = Environment.GetEnvironmentVariable ("TERMINFO");
-                       //string terminfoDirs = Environment.GetEnvironmentVariable ("TERMINFO_DIRS");
-
+                       string path;
+                       string terminfo = Environment.GetEnvironmentVariable ("TERMINFO");
+                       if (terminfo != null && Directory.Exists (terminfo)){
+                               path = TryTermInfoDir (terminfo, term);
+                               if (path != null)
+                                       return path;
+                       }
+                                   
                        foreach (string dir in locations) {
                                if (!Directory.Exists (dir))
                                        continue;
 
-                               string path = Path.Combine (dir, term.Substring (0, 1));
-                               if (!Directory.Exists (dir))
-                                       continue;
-
-                               path = Path.Combine (path, term);
-                               if (!File.Exists (path))
-                                       continue;
-
-                               return path;
+                               path = TryTermInfoDir (dir, term);
+                               if (path != null)
+                                       return path;
                        }
 
                        return null;
@@ -141,16 +159,16 @@ namespace System {
 #endif
                        this.term = term;
 
-                       if (term == "xterm") {
-                               reader = new TermInfoReader (term, KnownTerminals.xterm);
-                               color16 = true;
-                       } else if (term == "linux") {
-                               reader = new TermInfoReader (term, KnownTerminals.linux);
-                               color16 = true;
-                       } else {
-                               string filename = SearchTerminfo (term);
-                               if (filename != null)
-                                       reader = new TermInfoReader (term, filename);
+                       string filename = SearchTerminfo (term);
+                       if (filename != null)
+                               reader = new TermInfoReader (term, filename);
+                       else {
+                               // fallbacks
+                               if (term == "xterm") {
+                                       reader = new TermInfoReader (term, KnownTerminals.xterm);
+                               } else if (term == "linux") {
+                                       reader = new TermInfoReader (term, KnownTerminals.linux);
+                               }
                        }
 
                        if (reader == null)
@@ -158,7 +176,7 @@ namespace System {
 
                        if (!(Console.stdout is CStreamWriter)) {
                                // Application set its own stdout, we need a reference to the real stdout
-                               stdout = new CStreamWriter (Console.OpenStandardOutput (0), Console.OutputEncoding);
+                               stdout = new CStreamWriter (Console.OpenStandardOutput (0), Console.OutputEncoding, false);
                                ((StreamWriter) stdout).AutoFlush = true;
                        } else {
                                stdout = (CStreamWriter) Console.stdout;
@@ -174,154 +192,82 @@ namespace System {
                        if (inited)
                                return;
 
-                       /* This should not happen any more, since it is checked for in Console */
-                       if (!ConsoleDriver.IsConsole)
-                               throw new IOException ("Not a tty.");
-
-                       inited = true;
-
-                       ConsoleDriver.SetEcho (false);
-
-                       string endString = null;
-                       keypadXmit = reader.Get (TermInfoStrings.KeypadXmit);
-                       keypadLocal = reader.Get (TermInfoStrings.KeypadLocal);
-                       if (keypadXmit != null) {
-                               WriteConsole (keypadXmit); // Needed to get the arrows working
-                               if (keypadLocal != null)
-                                       endString += keypadLocal;
-                       }
-
-                       origPair = reader.Get (TermInfoStrings.OrigPair);
-                       origColors = reader.Get (TermInfoStrings.OrigColors);
-                       setfgcolor = MangleParameters (reader.Get (TermInfoStrings.SetAForeground));
-                       setbgcolor = MangleParameters (reader.Get (TermInfoStrings.SetABackground));
-                       // lighter fg colours are 90 -> 97 rather than 30 -> 37
-                       setlfgcolor = color16 ? setfgcolor.Replace ("[3", "[9") : setfgcolor;
-                       // lighter bg colours are 100 -> 107 rather than 40 -> 47
-                       setlbgcolor = color16 ? setbgcolor.Replace ("[4", "[10") : setbgcolor;
-                       string resetColors = (origColors == null) ? origPair : origColors;
-                       if (resetColors != null)
-                               endString += resetColors;
-
-                       byte vsusp;
-                       byte intr;
-                       unsafe {
-                               if (!ConsoleDriver.TtySetup (keypadXmit, endString, out verase, out vsusp, out intr, out native_terminal_size))
-                                       throw new IOException ("Error initializing terminal.");
-                       }
-
-                       stdin = new StreamReader (Console.OpenStandardInput (0), Console.InputEncoding);
-                       clear = reader.Get (TermInfoStrings.ClearScreen);
-                       bell = reader.Get (TermInfoStrings.Bell);
-                       if (clear == null) {
-                               clear = reader.Get (TermInfoStrings.CursorHome);
-                               clear += reader.Get (TermInfoStrings.ClrEos);
-                       }
-
-                       csrVisible = reader.Get (TermInfoStrings.CursorNormal);
-                       if (csrVisible == null)
-                               csrVisible = reader.Get (TermInfoStrings.CursorVisible);
-
-                       csrInvisible = reader.Get (TermInfoStrings.CursorInvisible);
-                       if (term == "cygwin" || term == "linux" || (term != null && term.StartsWith ("xterm")) ||
-                               term == "rxvt" || term == "dtterm") {
-                               titleFormat = "\x1b]0;{0}\x7"; // icon + window title
-                       } else if (term == "iris-ansi") {
-                               titleFormat = "\x1bP1.y{0}\x1b\\"; // not tested
-                       } else if (term == "sun-cmd") {
-                               titleFormat = "\x1b]l{0}\x1b\\"; // not tested
-                       }
-
-                       cursorAddress = reader.Get (TermInfoStrings.CursorAddress);
-                       if (cursorAddress != null) {
-                               string result = cursorAddress.Replace ("%i", String.Empty);
-                               home_1_1 = (cursorAddress != result);
-                               cursorAddress = MangleParameters (result);
-                       }
-
-                       GetCursorPosition ();
+                       lock (initLock){
+                               if (inited)
+                                       return;
+                               inited = true;
+                               
+                               /* This should not happen any more, since it is checked for in Console */
+                               if (!ConsoleDriver.IsConsole)
+                                       throw new IOException ("Not a tty.");
+                               
+                               ConsoleDriver.SetEcho (false);
+                               
+                               string endString = null;
+                               keypadXmit = reader.Get (TermInfoStrings.KeypadXmit);
+                               keypadLocal = reader.Get (TermInfoStrings.KeypadLocal);
+                               if (keypadXmit != null) {
+                                       WriteConsole (keypadXmit); // Needed to get the arrows working
+                                       if (keypadLocal != null)
+                                               endString += keypadLocal;
+                               }
+                               
+                               origPair = reader.Get (TermInfoStrings.OrigPair);
+                               origColors = reader.Get (TermInfoStrings.OrigColors);
+                               setfgcolor = reader.Get (TermInfoStrings.SetAForeground);
+                               setbgcolor = reader.Get (TermInfoStrings.SetABackground);
+                               maxColors = reader.Get (TermInfoNumbers.MaxColors);
+                               maxColors = Math.Max (Math.Min (maxColors, 16), 1);
+                               
+                               string resetColors = (origColors == null) ? origPair : origColors;
+                               if (resetColors != null)
+                                       endString += resetColors;
+                               
+                               unsafe {
+                                       if (!ConsoleDriver.TtySetup (keypadXmit, endString, out control_characters, out native_terminal_size)){
+                                               control_characters = new byte [17];
+                                               native_terminal_size = null;
+                                               //throw new IOException ("Error initializing terminal.");
+                                       }
+                               }
+                               
+                               stdin = new StreamReader (Console.OpenStandardInput (0), Console.InputEncoding);
+                               clear = reader.Get (TermInfoStrings.ClearScreen);
+                               bell = reader.Get (TermInfoStrings.Bell);
+                               if (clear == null) {
+                                       clear = reader.Get (TermInfoStrings.CursorHome);
+                                       clear += reader.Get (TermInfoStrings.ClrEos);
+                               }
+                               
+                               csrVisible = reader.Get (TermInfoStrings.CursorNormal);
+                               if (csrVisible == null)
+                                       csrVisible = reader.Get (TermInfoStrings.CursorVisible);
+                               
+                               csrInvisible = reader.Get (TermInfoStrings.CursorInvisible);
+                               if (term == "cygwin" || term == "linux" || (term != null && term.StartsWith ("xterm")) ||
+                                   term == "rxvt" || term == "dtterm") {
+                                       titleFormat = "\x1b]0;{0}\x7"; // icon + window title
+                               } else if (term == "iris-ansi") {
+                                       titleFormat = "\x1bP1.y{0}\x1b\\"; // not tested
+                               } else if (term == "sun-cmd") {
+                                       titleFormat = "\x1b]l{0}\x1b\\"; // not tested
+                               }
+                               
+                               cursorAddress = reader.Get (TermInfoStrings.CursorAddress);
+                               
+                               GetCursorPosition ();
 #if DEBUG
-                       logger.WriteLine ("noGetPosition: {0} left: {1} top: {2}", noGetPosition, cursorLeft, cursorTop);
-                       logger.Flush ();
+                               logger.WriteLine ("noGetPosition: {0} left: {1} top: {2}", noGetPosition, cursorLeft, cursorTop);
+                               logger.Flush ();
 #endif
-                       if (noGetPosition) {
-                               WriteConsole (clear);
-                               cursorLeft = 0;
-                               cursorTop = 0;
+                               if (noGetPosition) {
+                                       WriteConsole (clear);
+                                       cursorLeft = 0;
+                                       cursorTop = 0;
+                               }
                        }
                }
 
-               static string MangleParameters (string str)
-               {
-                       if (str == null)
-                               return null;
-
-                       str = str.Replace ("{", "{{");
-                       str = str.Replace ("}", "}}");
-                       str = str.Replace ("%p1%d", "{0}");
-                       return str.Replace ("%p2%d", "{1}");
-               }
-
-               static int TranslateColor (ConsoleColor desired, out bool light)
-               {
-                       switch (desired) {
-                       // Dark colours
-                       case ConsoleColor.Black:
-                               light = false;
-                               return 0;
-                       case ConsoleColor.DarkRed:
-                               light = false;
-                               return 1;
-                       case ConsoleColor.DarkGreen:
-                               light = false;
-                               return 2;
-                       case ConsoleColor.DarkYellow:
-                               light = false;
-                               return 3;
-                       case ConsoleColor.DarkBlue:
-                               light = false;
-                               return 4;
-                       case ConsoleColor.DarkMagenta:
-                               light = false;
-                               return 5;
-                       case ConsoleColor.DarkCyan:
-                               light = false;
-                               return 6;
-                       case ConsoleColor.Gray:
-                               light = false;
-                               return 7;
-                       // Light colours
-                       case ConsoleColor.DarkGray:
-                               light = true;
-                               return 0;
-                       case ConsoleColor.Red:
-                               light = true;
-                               return 1;
-                       case ConsoleColor.Green:
-                               light = true;
-                               return 2;
-                       case ConsoleColor.Yellow:
-                               light = true;
-                               return 3;
-                       case ConsoleColor.Blue:
-                               light = true;
-                               return 4;
-                       case ConsoleColor.Magenta:
-                               light = true;
-                               return 5;
-                       case ConsoleColor.Cyan:
-                               light = true;
-                               return 6;
-                       case ConsoleColor.White:
-                               light = true;
-                               return 7;
-                       }
-
-                       light = false;
-
-                       return 0;
-               }
-
                void IncrementX ()
                {
                        cursorLeft++;
@@ -356,8 +302,10 @@ namespace System {
                                break;
                        case ConsoleKey.Tab:
                                int n = 8 - (cursorLeft % 8);
-                               for (int i = 0; i < n; i++)
+                               for (int i = 0; i < n; i++){
                                        IncrementX ();
+                               }
+                               WriteConsole ("\t");
                                break;
                        case ConsoleKey.Clear:
                                WriteConsole (clear);
@@ -413,6 +361,45 @@ namespace System {
                        return IsSpecialKey (CreateKeyInfoFromInt (c, false));
                }
 
+               /// <summary>
+               /// The values of the ConsoleColor enums unfortunately don't map to the 
+               /// corresponding ANSI values.  We need to do the mapping manually.
+               /// See http://en.wikipedia.org/wiki/ANSI_escape_code#Colors
+               /// </summary>
+               private static readonly int[] _consoleColorToAnsiCode = new int[]
+               {
+                       // Dark/Normal colors
+                       0, // Black,
+                       4, // DarkBlue,
+                       2, // DarkGreen,
+                       6, // DarkCyan,
+                       1, // DarkRed,
+                       5, // DarkMagenta,
+                       3, // DarkYellow,
+                       7, // Gray,
+       
+                       // Bright colors
+                       8,  // DarkGray,
+                       12, // Blue,
+                       10, // Green,
+                       14, // Cyan,
+                       9,  // Red,
+                       13, // Magenta,
+                       11, // Yellow,
+                       15  // White
+               };
+
+               void ChangeColor (string format, ConsoleColor color)
+               {
+                       int ccValue = (int)color;
+                       if ((ccValue & ~0xF) != 0)
+                               throw new ArgumentException("Invalid Console Color");
+
+                       int ansiCode = _consoleColorToAnsiCode[ccValue] % maxColors;
+
+                       WriteConsole (ParameterizedStrings.Evaluate (format, ansiCode));
+               }
+               
                public ConsoleColor BackgroundColor {
                        get {
                                if (!inited) {
@@ -425,16 +412,8 @@ namespace System {
                                if (!inited) {
                                        Init ();
                                }
-
+                               ChangeColor (setbgcolor, value);
                                bgcolor = value;
-
-                               bool light;
-                               int colour = TranslateColor (value, out light);
-
-                               if (light)
-                                       WriteConsole (String.Format (setlbgcolor, colour));
-                               else
-                                       WriteConsole (String.Format (setbgcolor, colour));
                        }
                }
 
@@ -450,30 +429,31 @@ namespace System {
                                if (!inited) {
                                        Init ();
                                }
-
+                               ChangeColor (setfgcolor, value);
                                fgcolor = value;
-
-                               bool light;
-                               int colour = TranslateColor (value, out light);
-
-                               if (light)
-                                       WriteConsole (String.Format (setlfgcolor, colour));
-                               else
-                                       WriteConsole (String.Format (setfgcolor, colour));
                        }
                }
 
                void GetCursorPosition ()
                {
                        int row = 0, col = 0;
+                       int b;
 
+                       // First, get any data in the input buffer.  Merely reduces the likelyhood of getting an error
+                       int inqueue = ConsoleDriver.InternalKeyAvailable (0);
+                       while (inqueue-- > 0){
+                               b = stdin.Read ();
+                               AddToBuffer (b);
+                       }
+
+                       // Then try to probe for the cursor coordinates
                        WriteConsole ("\x1b[6n");
                        if (ConsoleDriver.InternalKeyAvailable (1000) <= 0) {
                                noGetPosition = true;
                                return;
                        }
 
-                       int b = stdin.Read ();
+                       b = stdin.Read ();
                        while (b != '\x1b') {
                                AddToBuffer (b);
                                if (ConsoleDriver.InternalKeyAvailable (100) <= 0)
@@ -527,6 +507,7 @@ namespace System {
                                        Init ();
                                }
 
+                               CheckWindowDimensions ();
                                return bufferHeight;
                        }
                        set {
@@ -544,6 +525,7 @@ namespace System {
                                        Init ();
                                }
 
+                               CheckWindowDimensions ();
                                return bufferWidth;
                        }
                        set {
@@ -560,8 +542,7 @@ namespace System {
                                if (!inited) {
                                        Init ();
                                }
-
-                               throw new NotSupportedException ();
+                               return false;
                        }
                }
 
@@ -659,7 +640,7 @@ namespace System {
                                        Init ();
                                }
 
-                               throw new NotSupportedException ();
+                               return false;
                        }
                }
 
@@ -701,12 +682,14 @@ namespace System {
                        }
                }
 
+               //
+               // Requries that caller calls Init () if not !inited.
+               //
                unsafe void CheckWindowDimensions ()
                {
-                       if (terminal_size == *native_terminal_size)
+                       if (native_terminal_size == null || terminal_size == *native_terminal_size)
                                return;
 
-                       terminal_size = *native_terminal_size;
                        if (*native_terminal_size == -1){
                                int c = reader.Get (TermInfoNumbers.Columns);
                                if (c != 0)
@@ -716,6 +699,7 @@ namespace System {
                                if (c != 0)
                                        windowHeight = c;
                        } else {
+                               terminal_size = *native_terminal_size;
                                windowWidth = terminal_size >> 16;
                                windowHeight = terminal_size & 0xffff;
                        }
@@ -854,23 +838,45 @@ namespace System {
                        bool shift = false;
                        bool ctrl = false;
 
-                       if (n == 10) {
+                       switch (n){
+                       case 10:
                                key = ConsoleKey.Enter;
-                       } else if (n == 8 || n == 9 || n == 12 || n == 13 || n == 19) {
+                               break;
+                       case 0x20:
+                               key = ConsoleKey.Spacebar;
+                               break;
+                       case 45:
+                               key = ConsoleKey.Subtract;
+                               break;
+                       case 43:
+                               key = ConsoleKey.Add;
+                               break;
+                       case 47:
+                               key = ConsoleKey.Divide;
+                               break;
+                       case 42:
+                               key = ConsoleKey.Multiply;
+                               break;
+                       case 8: case 9: case 12: case 13: case 19:
                                /* Values in ConsoleKey */
-                       } else if (n >= 1 && n <= 26) {
-                               // For Ctrl-a to Ctrl-z.
-                               ctrl = true;
-                               key = ConsoleKey.A + n - 1;
-                       } else if (n == 27) {
+                               break;
+                       case 27:
                                key = ConsoleKey.Escape;
-                       } else if (n >= 'a' && n <= 'z') {
-                               key = ConsoleKey.A - 'a' + n;
-                       } else if (n >= 'A' && n <= 'Z') {
-                               shift = true;
-                       } else if (n >= '0' && n <= '9') {
-                       } else {
-                               key = 0;
+                               break;
+                               
+                       default:
+                               if (n >= 1 && n <= 26) {
+                                       // For Ctrl-a to Ctrl-z.
+                                       ctrl = true;
+                                       key = ConsoleKey.A + n - 1;
+                               } else if (n >= 'a' && n <= 'z') {
+                                       key = ConsoleKey.A - 'a' + n;
+                               } else if (n >= 'A' && n <= 'Z') {
+                                       shift = true;
+                               } else if (n >= '0' && n <= '9') {
+                               } else
+                                       key = 0;
+                               break;
                        }
 
                        return new ConsoleKeyInfo (c, key, shift, alt, ctrl);
@@ -931,11 +937,11 @@ namespace System {
                                                do {
                                                        AddToBuffer (stdin.Read ());
                                                } while (ConsoleDriver.InternalKeyAvailable (0) > 0);
-                                       } else if (stdin.Peek () != -1) {
+                                       } else if (stdin.DataAvailable ()) {
                                                do {
                                                        AddToBuffer (stdin.Read ());
-                                               } while (stdin.Peek () != -1);
-                                       } else {                                                
+                                               } while (stdin.DataAvailable ());
+                                       } else {
                                                if ((o = GetKeyFromBuffer (false)) != null)
                                                        break;
 
@@ -959,7 +965,7 @@ namespace System {
                bool InputPending ()
                {
                        // check if we've got pending input we can read immediately
-                       return readpos < writepos || stdin.Peek () != -1;
+                       return readpos < writepos || stdin.DataAvailable ();
                }
 
                char [] echobuf = null;
@@ -1112,11 +1118,15 @@ namespace System {
 
                        rl_startx = cursorLeft;
                        rl_starty = cursorTop;
+                       char eof = (char) control_characters [ControlCharacters.EOF];
 
                        do {
                                key = ReadKeyInternal (out fresh);
                                echo = echo || fresh;
                                c = key.KeyChar;
+                               // EOF -> Ctrl-D (EOT) pressed.
+                               if (c == eof && c != 0 && builder.Length == 0)
+                                       return null;
 
                                if (key.Key != ConsoleKey.Enter) {
                                        if (key.Key != ConsoleKey.Backspace) {
@@ -1167,9 +1177,7 @@ namespace System {
                                Init ();
                        }
 
-                       if (bufferWidth == 0)
-                               CheckWindowDimensions ();
-
+                       CheckWindowDimensions ();
                        if (left < 0 || left >= bufferWidth)
                                throw new ArgumentOutOfRangeException ("left", "Value must be positive and below the buffer width.");
 
@@ -1181,8 +1189,7 @@ namespace System {
                        if (cursorAddress == null)
                                throw new NotSupportedException ("This terminal does not suport setting the cursor position.");
 
-                       int one = (home_1_1 ? 1 : 0);
-                       WriteConsole (String.Format (cursorAddress, top + one, left + one));
+                       WriteConsole (ParameterizedStrings.Evaluate (cursorAddress, top, left));
                        cursorLeft = left;
                        cursorTop = top;
                }
@@ -1211,9 +1218,10 @@ namespace System {
                void CreateKeyMap ()
                {
                        keymap = new Hashtable ();
+                       
                        keymap [TermInfoStrings.KeyBackspace] = new ConsoleKeyInfo ('\0', ConsoleKey.Backspace, false, false, false);
                        keymap [TermInfoStrings.KeyClear] = new ConsoleKeyInfo ('\0', ConsoleKey.Clear, false, false, false);
-                       // Delete character...
+                       // Delete character...
                        keymap [TermInfoStrings.KeyDown] = new ConsoleKeyInfo ('\0', ConsoleKey.DownArrow, false, false, false);
                        keymap [TermInfoStrings.KeyF1] = new ConsoleKeyInfo ('\0', ConsoleKey.F1, false, false, false);
                        keymap [TermInfoStrings.KeyF10] = new ConsoleKeyInfo ('\0', ConsoleKey.F10, false, false, false);
@@ -1226,7 +1234,6 @@ namespace System {
                        keymap [TermInfoStrings.KeyF8] = new ConsoleKeyInfo ('\0', ConsoleKey.F8, false, false, false);
                        keymap [TermInfoStrings.KeyF9] = new ConsoleKeyInfo ('\0', ConsoleKey.F9, false, false, false);
                        keymap [TermInfoStrings.KeyHome] = new ConsoleKeyInfo ('\0', ConsoleKey.Home, false, false, false);
-
                        keymap [TermInfoStrings.KeyLeft] = new ConsoleKeyInfo ('\0', ConsoleKey.LeftArrow, false, false, false);
                        keymap [TermInfoStrings.KeyLl] = new ConsoleKeyInfo ('\0', ConsoleKey.NumPad1, false, false, false);
                        keymap [TermInfoStrings.KeyNpage] = new ConsoleKeyInfo ('\0', ConsoleKey.PageDown, false, false, false);
@@ -1271,8 +1278,6 @@ namespace System {
                        keymap [TermInfoStrings.KeyF22] = new ConsoleKeyInfo ('\0', ConsoleKey.F22, false, false, false);
                        keymap [TermInfoStrings.KeyF23] = new ConsoleKeyInfo ('\0', ConsoleKey.F23, false, false, false);
                        keymap [TermInfoStrings.KeyF24] = new ConsoleKeyInfo ('\0', ConsoleKey.F24, false, false, false);
-
-
                        // These were previously missing:
                        keymap [TermInfoStrings.KeyDc] = new ConsoleKeyInfo ('\0', ConsoleKey.Delete, false, false, false);
                        keymap [TermInfoStrings.KeyIc] = new ConsoleKeyInfo ('\0', ConsoleKey.Insert, false, false, false);
@@ -1285,70 +1290,79 @@ namespace System {
 
                        CreateKeyMap ();
                        rootmap = new ByteMatcher ();
-                       AddStringMapping (TermInfoStrings.KeyBackspace);
-                       AddStringMapping (TermInfoStrings.KeyClear);
-                       AddStringMapping (TermInfoStrings.KeyDown);
-                       AddStringMapping (TermInfoStrings.KeyF1);
-                       AddStringMapping (TermInfoStrings.KeyF10);
-                       AddStringMapping (TermInfoStrings.KeyF2);
-                       AddStringMapping (TermInfoStrings.KeyF3);
-                       AddStringMapping (TermInfoStrings.KeyF4);
-                       AddStringMapping (TermInfoStrings.KeyF5);
-                       AddStringMapping (TermInfoStrings.KeyF6);
-                       AddStringMapping (TermInfoStrings.KeyF7);
-                       AddStringMapping (TermInfoStrings.KeyF8);
-                       AddStringMapping (TermInfoStrings.KeyF9);
-                       AddStringMapping (TermInfoStrings.KeyHome);
-                       AddStringMapping (TermInfoStrings.KeyLeft);
-                       AddStringMapping (TermInfoStrings.KeyLl);
-                       AddStringMapping (TermInfoStrings.KeyNpage);
-                       AddStringMapping (TermInfoStrings.KeyPpage);
-                       AddStringMapping (TermInfoStrings.KeyRight);
-                       AddStringMapping (TermInfoStrings.KeySf);
-                       AddStringMapping (TermInfoStrings.KeySr);
-                       AddStringMapping (TermInfoStrings.KeyUp);
-                       AddStringMapping (TermInfoStrings.KeyA1);
-                       AddStringMapping (TermInfoStrings.KeyA3);
-                       AddStringMapping (TermInfoStrings.KeyB2);
-                       AddStringMapping (TermInfoStrings.KeyC1);
-                       AddStringMapping (TermInfoStrings.KeyC3);
-                       AddStringMapping (TermInfoStrings.KeyBtab);
-                       AddStringMapping (TermInfoStrings.KeyBeg);
-                       AddStringMapping (TermInfoStrings.KeyCopy);
-                       AddStringMapping (TermInfoStrings.KeyEnd);
-                       AddStringMapping (TermInfoStrings.KeyEnter);
-                       AddStringMapping (TermInfoStrings.KeyHelp);
-                       AddStringMapping (TermInfoStrings.KeyPrint);
-                       AddStringMapping (TermInfoStrings.KeyUndo);
-                       AddStringMapping (TermInfoStrings.KeySbeg);
-                       AddStringMapping (TermInfoStrings.KeyScopy);
-                       AddStringMapping (TermInfoStrings.KeySdc);
-                       AddStringMapping (TermInfoStrings.KeyShelp);
-                       AddStringMapping (TermInfoStrings.KeyShome);
-                       AddStringMapping (TermInfoStrings.KeySleft);
-                       AddStringMapping (TermInfoStrings.KeySprint);
-                       AddStringMapping (TermInfoStrings.KeySright);
-                       AddStringMapping (TermInfoStrings.KeySundo);
-                       AddStringMapping (TermInfoStrings.KeyF11);
-                       AddStringMapping (TermInfoStrings.KeyF12);
-                       AddStringMapping (TermInfoStrings.KeyF13);
-                       AddStringMapping (TermInfoStrings.KeyF14);
-                       AddStringMapping (TermInfoStrings.KeyF15);
-                       AddStringMapping (TermInfoStrings.KeyF16);
-                       AddStringMapping (TermInfoStrings.KeyF17);
-                       AddStringMapping (TermInfoStrings.KeyF18);
-                       AddStringMapping (TermInfoStrings.KeyF19);
-                       AddStringMapping (TermInfoStrings.KeyF20);
-                       AddStringMapping (TermInfoStrings.KeyF21);
-                       AddStringMapping (TermInfoStrings.KeyF22);
-                       AddStringMapping (TermInfoStrings.KeyF23);
-                       AddStringMapping (TermInfoStrings.KeyF24);
-
-                       // These were missing
-                       AddStringMapping (TermInfoStrings.KeyDc);
-                       AddStringMapping (TermInfoStrings.KeyIc);
-
-                       rootmap.AddMapping (TermInfoStrings.KeyBackspace, new byte [] { verase });
+
+                       //
+                       // The keys that we know about and use
+                       //
+                       var UsedKeys = new [] {
+                               TermInfoStrings.KeyBackspace,
+                               TermInfoStrings.KeyClear,
+                               TermInfoStrings.KeyDown,
+                               TermInfoStrings.KeyF1,
+                               TermInfoStrings.KeyF10,
+                               TermInfoStrings.KeyF2,
+                               TermInfoStrings.KeyF3,
+                               TermInfoStrings.KeyF4,
+                               TermInfoStrings.KeyF5,
+                               TermInfoStrings.KeyF6,
+                               TermInfoStrings.KeyF7,
+                               TermInfoStrings.KeyF8,
+                               TermInfoStrings.KeyF9,
+                               TermInfoStrings.KeyHome,
+                               TermInfoStrings.KeyLeft,
+                               TermInfoStrings.KeyLl,
+                               TermInfoStrings.KeyNpage,
+                               TermInfoStrings.KeyPpage,
+                               TermInfoStrings.KeyRight,
+                               TermInfoStrings.KeySf,
+                               TermInfoStrings.KeySr,
+                               TermInfoStrings.KeyUp,
+                               TermInfoStrings.KeyA1,
+                               TermInfoStrings.KeyA3,
+                               TermInfoStrings.KeyB2,
+                               TermInfoStrings.KeyC1,
+                               TermInfoStrings.KeyC3,
+                               TermInfoStrings.KeyBtab,
+                               TermInfoStrings.KeyBeg,
+                               TermInfoStrings.KeyCopy,
+                               TermInfoStrings.KeyEnd,
+                               TermInfoStrings.KeyEnter,
+                               TermInfoStrings.KeyHelp,
+                               TermInfoStrings.KeyPrint,
+                               TermInfoStrings.KeyUndo,
+                               TermInfoStrings.KeySbeg,
+                               TermInfoStrings.KeyScopy,
+                               TermInfoStrings.KeySdc,
+                               TermInfoStrings.KeyShelp,
+                               TermInfoStrings.KeyShome,
+                               TermInfoStrings.KeySleft,
+                               TermInfoStrings.KeySprint,
+                               TermInfoStrings.KeySright,
+                               TermInfoStrings.KeySundo,
+                               TermInfoStrings.KeyF11,
+                               TermInfoStrings.KeyF12,
+                               TermInfoStrings.KeyF13,
+                               TermInfoStrings.KeyF14,
+                               TermInfoStrings.KeyF15,
+                               TermInfoStrings.KeyF16,
+                               TermInfoStrings.KeyF17,
+                               TermInfoStrings.KeyF18,
+                               TermInfoStrings.KeyF19,
+                               TermInfoStrings.KeyF20,
+                               TermInfoStrings.KeyF21,
+                               TermInfoStrings.KeyF22,
+                               TermInfoStrings.KeyF23,
+                               TermInfoStrings.KeyF24,
+
+                               // These were missing
+                               TermInfoStrings.KeyDc,
+                               TermInfoStrings.KeyIc
+                       };
+                       
+                       foreach (TermInfoStrings tis in UsedKeys)
+                               AddStringMapping (tis);
+                       
+                       rootmap.AddMapping (TermInfoStrings.KeyBackspace, new byte [] { control_characters [ControlCharacters.Erase] });
                        rootmap.Sort ();
                        initKeys = true;
                }
@@ -1363,6 +1377,477 @@ namespace System {
                }
        }
 
+       /// <summary>Provides support for evaluating parameterized terminfo database format strings.</summary>
+       internal static class ParameterizedStrings
+       {
+                /// <summary>A cached stack to use to avoid allocating a new stack object for every evaluation.</summary>
+                [ThreadStatic]
+                private static LowLevelStack _cachedStack;
+
+                /// <summary>Evaluates a terminfo formatting string, using the supplied arguments.</summary>
+                /// <param name="format">The format string.</param>
+                /// <param name="args">The arguments to the format string.</param>
+                /// <returns>The formatted string.</returns>
+                public static string Evaluate(string format, params FormatParam[] args)
+               {
+                       if (format == null)
+                               throw new ArgumentNullException("format");
+                       if (args == null)
+                               throw new ArgumentNullException("args");
+
+                       // Initialize the stack to use for processing.
+                       LowLevelStack stack = _cachedStack;
+                       if (stack == null)
+                               _cachedStack = stack = new LowLevelStack();
+                       else
+                               stack.Clear();
+
+                       // "dynamic" and "static" variables are much less often used (the "dynamic" and "static"
+                       // terminology appears to just refer to two different collections rather than to any semantic
+                       // meaning).  As such, we'll only initialize them if we really need them.
+                       FormatParam[] dynamicVars = null, staticVars = null;
+                       
+                       int pos = 0;
+                       return EvaluateInternal(format, ref pos, args, stack, ref dynamicVars, ref staticVars);
+                       
+                       // EvaluateInternal may throw IndexOutOfRangeException and InvalidOperationException
+                       // if the format string is malformed or if it's inconsistent with the parameters provided.
+               }
+               
+                /// <summary>Evaluates a terminfo formatting string, using the supplied arguments and processing data structures.</summary>
+                /// <param name="format">The format string.</param>
+                /// <param name="pos">The position in <paramref name="format"/> to start processing.</param>
+                /// <param name="args">The arguments to the format string.</param>
+                /// <param name="stack">The stack to use as the format string is evaluated.</param>
+                /// <param name="dynamicVars">A lazily-initialized collection of variables.</param>
+                /// <param name="staticVars">A lazily-initialized collection of variables.</param>
+                /// <returns>
+                /// The formatted string; this may be empty if the evaluation didn't yield any output.
+                /// The evaluation stack will have a 1 at the top if all processing was completed at invoked level
+                /// of recursion, and a 0 at the top if we're still inside of a conditional that requires more processing.
+                /// </returns>
+                private static string EvaluateInternal(
+                       string format, ref int pos, FormatParam[] args, LowLevelStack stack,
+                       ref FormatParam[] dynamicVars, ref FormatParam[] staticVars)
+               {
+                       // Create a StringBuilder to store the output of this processing.  We use the format's length as an 
+                       // approximation of an upper-bound for how large the output will be, though with parameter processing,
+                       // this is just an estimate, sometimes way over, sometimes under.
+                       StringBuilder output = new StringBuilder(format.Length);
+
+                       // Format strings support conditionals, including the equivalent of "if ... then ..." and
+                       // "if ... then ... else ...", as well as "if ... then ... else ... then ..."
+                       // and so on, where an else clause can not only be evaluated for string output but also
+                       // as a conditional used to determine whether to evaluate a subsequent then clause.
+                       // We use recursion to process these subsequent parts, and we track whether we're processing
+                       // at the same level of the initial if clause (or whether we're nested).
+                       bool sawIfConditional = false;
+
+                       // Process each character in the format string, starting from the position passed in.
+                       for (; pos < format.Length; pos++){
+                               // '%' is the escape character for a special sequence to be evaluated.
+                               // Anything else just gets pushed to output.
+                               if (format[pos] != '%') {
+                                       output.Append(format[pos]);
+                                       continue;
+                               }
+                               // We have a special parameter sequence to process.  Now we need
+                               // to look at what comes after the '%'.
+                               ++pos;
+                               switch (format[pos]) {
+                               // Output appending operations
+                               case '%': // Output the escaped '%'
+                                       output.Append('%');
+                                       break;
+                               case 'c': // Pop the stack and output it as a char
+                                       output.Append((char)stack.Pop().Int32);
+                                       break;
+                               case 's': // Pop the stack and output it as a string
+                                       output.Append(stack.Pop().String);
+                                       break;
+                               case 'd': // Pop the stack and output it as an integer
+                                       output.Append(stack.Pop().Int32);
+                                       break;
+                               case 'o':
+                               case 'X':
+                               case 'x':
+                               case ':':
+                               case '0':
+                               case '1':
+                               case '2':
+                               case '3':
+                               case '4':
+                               case '5':
+                               case '6':
+                               case '7':
+                               case '8':
+                               case '9':
+                                       // printf strings of the format "%[[:]flags][width[.precision]][doxXs]" are allowed
+                                       // (with a ':' used in front of flags to help differentiate from binary operations, as flags can
+                                       // include '-' and '+').  While above we've special-cased common usage (e.g. %d, %s),
+                                       // for more complicated expressions we delegate to printf.
+                                       int printfEnd = pos;
+                                       for (; printfEnd < format.Length; printfEnd++) // find the end of the printf format string
+                                       {
+                                               char ec = format[printfEnd];
+                                               if (ec == 'd' || ec == 'o' || ec == 'x' || ec == 'X' || ec == 's')
+                                               {
+                                                       break;
+                                               }
+                                       }
+                                       if (printfEnd >= format.Length)
+                                               throw new InvalidOperationException("Terminfo database contains invalid values");
+                                       string printfFormat = format.Substring(pos - 1, printfEnd - pos + 2); // extract the format string
+                                       if (printfFormat.Length > 1 && printfFormat[1] == ':')
+                                               printfFormat = printfFormat.Remove(1, 1);
+                                       output.Append(FormatPrintF(printfFormat, stack.Pop().Object)); // do the printf formatting and append its output
+                                       break;
+
+                                       // Stack pushing operations
+                               case 'p': // Push the specified parameter (1-based) onto the stack
+                                       pos++;
+                                       stack.Push(args[format[pos] - '1']);
+                                       break;
+                               case 'l': // Pop a string and push its length
+                                       stack.Push(stack.Pop().String.Length);
+                                       break;
+                               case '{': // Push integer literal, enclosed between braces
+                                       pos++;
+                                       int intLit = 0;
+                                       while (format[pos] != '}')
+                                       {
+                                               intLit = (intLit * 10) + (format[pos] - '0');
+                                               pos++;
+                                       }
+                                       stack.Push(intLit);
+                                       break;
+                               case '\'': // Push literal character, enclosed between single quotes
+                                       stack.Push((int)format[pos + 1]);
+                                       pos += 2;
+                                       break;
+
+                                       // Storing and retrieving "static" and "dynamic" variables
+                               case 'P': // Pop a value and store it into either static or dynamic variables based on whether the a-z variable is capitalized
+                                       pos++;
+                                       int setIndex;
+                                       FormatParam[] targetVars = GetDynamicOrStaticVariables(format[pos], ref dynamicVars, ref staticVars, out setIndex);
+                                       targetVars[setIndex] = stack.Pop();
+                                       break;
+                               case 'g': // Push a static or dynamic variable; which is based on whether the a-z variable is capitalized
+                                       pos++;
+                                       int getIndex;
+                                       FormatParam[] sourceVars = GetDynamicOrStaticVariables(format[pos], ref dynamicVars, ref staticVars, out getIndex);
+                                       stack.Push(sourceVars[getIndex]);
+                                       break;
+
+                                       // Binary operations
+                               case '+':
+                               case '-':
+                               case '*':
+                               case '/':
+                               case 'm':
+                               case '^': // arithmetic
+                               case '&':
+                               case '|':                                         // bitwise
+                               case '=':
+                               case '>':
+                               case '<':                               // comparison
+                               case 'A':
+                               case 'O':                                         // logical
+                                       int second = stack.Pop().Int32; // it's a stack... the second value was pushed last
+                                       int first = stack.Pop().Int32;
+                                       int res;
+                                       switch (format[pos]) {
+                                       case '+':
+                                               res = first + second;
+                                               break;
+                                       case '-':
+                                               res = first - second;
+                                               break;
+                                       case '*':
+                                               res = first * second;
+                                               break;
+                                       case '/':
+                                               res = first / second;
+                                               break;
+                                       case 'm':
+                                               res = first % second;
+                                               break;
+                                       case '^':
+                                               res = first ^ second;
+                                               break;
+                                       case '&':
+                                               res = first & second;
+                                               break;
+                                       case '|':
+                                               res = first | second;
+                                               break;
+                                       case '=':
+                                               res = AsInt(first == second);
+                                               break;
+                                       case '>':
+                                               res = AsInt(first > second);
+                                               break;
+                                       case '<':
+                                               res = AsInt(first < second);
+                                               break;
+                                       case 'A':
+                                               res = AsInt(AsBool(first) && AsBool(second));
+                                               break;
+                                       case 'O':
+                                               res = AsInt(AsBool(first) || AsBool(second));
+                                               break;
+                                       default:
+                                               res = 0;
+                                               break;
+                                       }
+                                       stack.Push(res);
+                                       break;
+
+                                       // Unary operations
+                               case '!':
+                               case '~':
+                                       int value = stack.Pop().Int32;
+                                       stack.Push(
+                                               format[pos] == '!' ? AsInt(!AsBool(value)) :
+                                               ~value);
+                                       break;
+
+                                       // Augment first two parameters by 1
+                               case 'i':
+                                       args[0] = 1 + args[0].Int32;
+                                       args[1] = 1 + args[1].Int32;
+                                       break;
+
+                                       // Conditional of the form %? if-part %t then-part %e else-part %;
+                                       // The "%e else-part" is optional.
+                               case '?':
+                                       sawIfConditional = true;
+                                       break;
+                               case 't':
+                                       // We hit the end of the if-part and are about to start the then-part.
+                                       // The if-part left its result on the stack; pop and evaluate.
+                                       bool conditionalResult = AsBool(stack.Pop().Int32);
+
+                                       // Regardless of whether it's true, run the then-part to get past it.
+                                       // If the conditional was true, output the then results.
+                                       pos++;
+                                       string thenResult = EvaluateInternal(format, ref pos, args, stack, ref dynamicVars, ref staticVars);
+                                       if (conditionalResult)
+                                       {
+                                               output.Append(thenResult);
+                                       }
+
+                                       // We're past the then; the top of the stack should now be a Boolean
+                                       // indicating whether this conditional has more to be processed (an else clause).
+                                       if (!AsBool(stack.Pop().Int32))
+                                       {
+                                               // Process the else clause, and if the conditional was false, output the else results.
+                                               pos++;
+                                               string elseResult = EvaluateInternal(format, ref pos, args, stack, ref dynamicVars, ref staticVars);
+                                               if (!conditionalResult)
+                                               {
+                                                       output.Append(elseResult);
+                                               }
+                                               // Now we should be done (any subsequent elseif logic will have bene handled in the recursive call).
+                                               if (!AsBool(stack.Pop().Int32))
+                                               {
+                                                       throw new InvalidOperationException("Terminfo database contains invalid values");
+                                               }
+                                       }
+
+                                       // If we're in a nested processing, return to our parent.
+                                       if (!sawIfConditional)
+                                       {
+                                               stack.Push(1);
+                                               return output.ToString();
+                                       }
+                                       // Otherwise, we're done processing the conditional in its entirety.
+                                       sawIfConditional = false;
+                                       break;
+                               case 'e':
+                               case ';':
+                                       // Let our caller know why we're exiting, whether due to the end of the conditional or an else branch.
+                                       stack.Push(AsInt(format[pos] == ';'));
+                                       return output.ToString();
+
+                                       // Anything else is an error
+                               default:
+                                       throw new InvalidOperationException("Terminfo database contains invalid values");
+                               }
+                       }
+                       stack.Push(1);
+                       return output.ToString();
+               }
+               
+                /// <summary>Converts an Int32 to a Boolean, with 0 meaning false and all non-zero values meaning true.</summary>
+                /// <param name="i">The integer value to convert.</param>
+                /// <returns>true if the integer was non-zero; otherwise, false.</returns>
+                static bool AsBool(Int32 i) { return i != 0; }
+
+                /// <summary>Converts a Boolean to an Int32, with true meaning 1 and false meaning 0.</summary>
+                /// <param name="b">The Boolean value to convert.</param>
+                /// <returns>1 if the Boolean is true; otherwise, 0.</returns>
+                static int AsInt(bool b) { return b ? 1 : 0; }
+
+               static string StringFromAsciiBytes(byte[] buffer, int offset, int length)
+               {
+                       // Special-case for empty strings
+                       if (length == 0)
+                               return string.Empty;
+
+                       // new string(sbyte*, ...) doesn't exist in the targeted reference assembly,
+                       // so we first copy to an array of chars, and then create a string from that.
+                       char[] chars = new char[length];
+                       for (int i = 0, j = offset; i < length; i++, j++)
+                               chars[i] = (char)buffer[j];
+                       return new string(chars);
+               }
+
+               [DllImport("libc")]
+               static extern unsafe int snprintf(byte* str, IntPtr size, string format, string arg1);
+
+               [DllImport("libc")]
+               static extern unsafe int snprintf(byte* str, IntPtr size, string format, int arg1);
+               
+                /// <summary>Formats an argument into a printf-style format string.</summary>
+                /// <param name="format">The printf-style format string.</param>
+                /// <param name="arg">The argument to format.  This must be an Int32 or a String.</param>
+                /// <returns>The formatted string.</returns>
+                static unsafe string FormatPrintF(string format, object arg)
+                {
+                       // Determine how much space is needed to store the formatted string.
+                       string stringArg = arg as string;
+                       int neededLength = stringArg != null ?
+                               snprintf(null, IntPtr.Zero, format, stringArg) :
+                               snprintf(null, IntPtr.Zero, format, (int)arg);
+                       if (neededLength == 0)
+                               return string.Empty;
+                       if (neededLength < 0)
+                               throw new InvalidOperationException("The printf operation failed");
+                       
+                       // Allocate the needed space, format into it, and return the data as a string.
+                       byte[] bytes = new byte[neededLength + 1]; // extra byte for the null terminator
+                       fixed (byte* ptr = bytes){
+                               int length = stringArg != null ?
+                                       snprintf(ptr, (IntPtr)bytes.Length, format, stringArg) :
+                                       snprintf(ptr, (IntPtr)bytes.Length, format, (int)arg);
+                               if (length != neededLength)
+                               {
+                                       throw new InvalidOperationException("Invalid printf operation");
+                               }
+                       }
+                       return StringFromAsciiBytes(bytes, 0, neededLength);
+                }
+               
+                /// <summary>Gets the lazily-initialized dynamic or static variables collection, based on the supplied variable name.</summary>
+                /// <param name="c">The name of the variable.</param>
+                /// <param name="dynamicVars">The lazily-initialized dynamic variables collection.</param>
+                /// <param name="staticVars">The lazily-initialized static variables collection.</param>
+                /// <param name="index">The index to use to index into the variables.</param>
+                /// <returns>The variables collection.</returns>
+                private static FormatParam[] GetDynamicOrStaticVariables(
+                       char c, ref FormatParam[] dynamicVars, ref FormatParam[] staticVars, out int index)
+                {
+                       if (c >= 'A' && c <= 'Z'){
+                               index = c - 'A';
+                               return staticVars ?? (staticVars = new FormatParam[26]); // one slot for each letter of alphabet
+                       } else if (c >= 'a' && c <= 'z') {
+                               index = c - 'a';
+                               return dynamicVars ?? (dynamicVars = new FormatParam[26]); // one slot for each letter of alphabet
+                       }
+                       else throw new InvalidOperationException("Terminfo database contains invalid values");
+                }
+
+                /// <summary>
+                /// Represents a parameter to a terminfo formatting string.
+                /// It is a discriminated union of either an integer or a string, 
+                /// with characters represented as integers.
+                /// </summary>
+                public struct FormatParam
+                {
+                       /// <summary>The integer stored in the parameter.</summary>
+                       private readonly int _int32;
+                       /// <summary>The string stored in the parameter.</summary>
+                       private readonly string _string; // null means an Int32 is stored
+                       
+                       /// <summary>Initializes the parameter with an integer value.</summary>
+                       /// <param name="value">The value to be stored in the parameter.</param>
+                       public FormatParam(Int32 value) : this(value, null) { }
+                       
+                       /// <summary>Initializes the parameter with a string value.</summary>
+                       /// <param name="value">The value to be stored in the parameter.</param>
+                       public FormatParam(String value) : this(0, value ?? string.Empty) { }
+                       
+                       /// <summary>Initializes the parameter.</summary>
+                       /// <param name="intValue">The integer value.</param>
+                       /// <param name="stringValue">The string value.</param>
+                       private FormatParam(Int32 intValue, String stringValue)
+                       {
+                               _int32 = intValue;
+                               _string = stringValue;
+                       }
+                       
+                       /// <summary>Implicit converts an integer into a parameter.</summary>
+                       public static implicit operator FormatParam(int value)
+                       {
+                               return new FormatParam(value);
+                       }
+                       
+                       /// <summary>Implicit converts a string into a parameter.</summary>
+                       public static implicit operator FormatParam(string value)
+                       {
+                               return new FormatParam(value);
+                       }
+                       
+                       /// <summary>Gets the integer value of the parameter. If a string was stored, 0 is returned.</summary>
+                       public int Int32 { get { return _int32; } }
+                       
+                       /// <summary>Gets the string value of the parameter.  If an Int32 or a null String were stored, an empty string is returned.</summary>
+                       public string String { get { return _string ?? string.Empty; } }
+                       
+                       /// <summary>Gets the string or the integer value as an object.</summary>
+                       public object Object { get { return _string ?? (object)_int32; } }
+                }
+               
+                /// <summary>Provides a basic stack data structure.</summary>
+                /// <typeparam name="T">Specifies the type of data in the stack.</typeparam>
+                private sealed class LowLevelStack
+               {
+                       private const int DefaultSize = 4;
+                       private FormatParam[] _arr;
+                       private int _count;
+                       
+                       public LowLevelStack() { _arr = new FormatParam[DefaultSize]; }
+                       
+                       public FormatParam Pop()
+                       {
+                               if (_count == 0)
+                                       throw new InvalidOperationException("Terminfo: Invalid Stack");
+
+                               var item = _arr[--_count];
+                               _arr[_count] = default(FormatParam);
+                               return item;
+                       }
+                       
+                       public void Push(FormatParam item)
+                       {
+                               if (_arr.Length == _count){
+                                       var newArr = new FormatParam[_arr.Length * 2];
+                                       Array.Copy(_arr, 0, newArr, 0, _arr.Length);
+                                       _arr = newArr;
+                               }
+                               _arr[_count++] = item;
+                       }
+                       
+                       public void Clear()
+                       {
+                               Array.Clear(_arr, 0, _count);
+                               _count = 0;
+                       }
+                }
+       }
+              
        class ByteMatcher {
                Hashtable map = new Hashtable ();
                Hashtable starts = new Hashtable ();