C#: step0_repl using de Icaza's getline.cs
authorJoel Martin <github@martintribe.org>
Mon, 7 Apr 2014 00:23:28 +0000 (19:23 -0500)
committerJoel Martin <github@martintribe.org>
Mon, 7 Apr 2014 00:23:28 +0000 (19:23 -0500)
.gitignore
cs/Makefile [new file with mode: 0644]
cs/getline.cs [new file with mode: 0644]
cs/step0_repl.cs [new file with mode: 0644]
docs/TODO

index 2d3759a..76ce4fd 100644 (file)
@@ -17,6 +17,8 @@ c/step7_quote
 c/step8_macros
 c/step9_interop
 c/stepA_more
+cs/*.exe
+cs/*.dll
 clojure/target
 clojure/.lein-repl-history
 java/target/
diff --git a/cs/Makefile b/cs/Makefile
new file mode 100644 (file)
index 0000000..c03e6c9
--- /dev/null
@@ -0,0 +1,46 @@
+#####################
+
+TESTS =
+
+SOURCES = readline.h readline.c types.h types.c \
+          reader.h reader.c printer.h printer.c \
+          env.c core.h core.c interop.h interop.c \
+          stepA_more.c
+
+#####################
+
+SRCS = step0_repl.cs
+OBJS = $(SRCS:%.cs=%.exe)
+BINS = $(OBJS:%.o=%)
+OTHER_OBJS = getline.dll
+
+#####################
+
+all: $(BINS) mal.exe
+
+mal.exe: $(word $(words $(OBJS)),$(OBJS))
+       cp $< $@
+
+$(OTHER_OBJS): %.dll: %.cs
+       mcs -target:library -out:$@ $+
+
+$(OBJS): %.exe: %.cs  $(OTHER_OBJS)
+       mcs $(foreach lib,$(OTHER_OBJS),-r:$(lib)) $(@:%.exe=%.cs)
+
+#$(patsubst %.o,%,$(filter step%,$(OBJS))): $(OTHER_OBJS)
+#$(BINS): %: %.o
+#      gcc $+ -o $@ $(LDFLAGS)
+
+clean:
+       rm -f $(OBJS) $(OTHER_OBJS) mal.exe
+
+.PHONY: stats tests $(TESTS)
+
+stats: $(SOURCES)
+       @wc $^
+
+tests: $(TESTS)
+
+$(TESTS):
+       @echo "Running $@"; \
+       ./$@ || exit 1; \
diff --git a/cs/getline.cs b/cs/getline.cs
new file mode 100644 (file)
index 0000000..b6dcf07
--- /dev/null
@@ -0,0 +1,1086 @@
+//
+// getline.cs: A command line editor
+//
+// Authors:
+//   Miguel de Icaza (miguel@novell.com)
+//
+// Copyright 2008 Novell, Inc.
+//
+// Dual-licensed under the terms of the MIT X11 license or the
+// Apache License 2.0
+//
+// USE -define:DEMO to build this as a standalone file and test it
+//
+// TODO:
+//    Enter an error (a = 1);  Notice how the prompt is in the wrong line
+//             This is caused by Stderr not being tracked by System.Console.
+//    Completion support
+//    Why is Thread.Interrupt not working?   Currently I resort to Abort which is too much.
+//
+// Limitations in System.Console:
+//    Console needs SIGWINCH support of some sort
+//    Console needs a way of updating its position after things have been written
+//    behind its back (P/Invoke puts for example).
+//    System.Console needs to get the DELETE character, and report accordingly.
+//
+
+using System;
+using System.Text;
+using System.IO;
+using System.Threading;
+using System.Reflection;
+
+namespace Mono.Terminal {
+
+       public class LineEditor {
+
+               public class Completion {
+                       public string [] Result;
+                       public string Prefix;
+
+                       public Completion (string prefix, string [] result)
+                       {
+                               Prefix = prefix;
+                               Result = result;
+                       }
+               }
+               
+               public delegate Completion AutoCompleteHandler (string text, int pos);
+               
+               //static StreamWriter log;
+               
+               // The text being edited.
+               StringBuilder text;
+
+               // The text as it is rendered (replaces (char)1 with ^A on display for example).
+               StringBuilder rendered_text;
+
+               // The prompt specified, and the prompt shown to the user.
+               string prompt;
+               string shown_prompt;
+               
+               // The current cursor position, indexes into "text", for an index
+               // into rendered_text, use TextToRenderPos
+               int cursor;
+
+               // The row where we started displaying data.
+               int home_row;
+
+               // The maximum length that has been displayed on the screen
+               int max_rendered;
+
+               // If we are done editing, this breaks the interactive loop
+               bool done = false;
+
+               // The thread where the Editing started taking place
+               Thread edit_thread;
+
+               // Our object that tracks history
+               History history;
+
+               // The contents of the kill buffer (cut/paste in Emacs parlance)
+               string kill_buffer = "";
+
+               // The string being searched for
+               string search;
+               string last_search;
+
+               // whether we are searching (-1= reverse; 0 = no; 1 = forward)
+               int searching;
+
+               // The position where we found the match.
+               int match_at;
+               
+               // Used to implement the Kill semantics (multiple Alt-Ds accumulate)
+               KeyHandler last_handler;
+               
+               delegate void KeyHandler ();
+               
+               struct Handler {
+                       public ConsoleKeyInfo CKI;
+                       public KeyHandler KeyHandler;
+
+                       public Handler (ConsoleKey key, KeyHandler h)
+                       {
+                               CKI = new ConsoleKeyInfo ((char) 0, key, false, false, false);
+                               KeyHandler = h;
+                       }
+
+                       public Handler (char c, KeyHandler h)
+                       {
+                               KeyHandler = h;
+                               // Use the "Zoom" as a flag that we only have a character.
+                               CKI = new ConsoleKeyInfo (c, ConsoleKey.Zoom, false, false, false);
+                       }
+
+                       public Handler (ConsoleKeyInfo cki, KeyHandler h)
+                       {
+                               CKI = cki;
+                               KeyHandler = h;
+                       }
+                       
+                       public static Handler Control (char c, KeyHandler h)
+                       {
+                               return new Handler ((char) (c - 'A' + 1), h);
+                       }
+
+                       public static Handler Alt (char c, ConsoleKey k, KeyHandler h)
+                       {
+                               ConsoleKeyInfo cki = new ConsoleKeyInfo ((char) c, k, false, true, false);
+                               return new Handler (cki, h);
+                       }
+               }
+
+               /// <summary>
+               ///   Invoked when the user requests auto-completion using the tab character
+               /// </summary>
+               /// <remarks>
+               ///    The result is null for no values found, an array with a single
+               ///    string, in that case the string should be the text to be inserted
+               ///    for example if the word at pos is "T", the result for a completion
+               ///    of "ToString" should be "oString", not "ToString".
+               ///
+               ///    When there are multiple results, the result should be the full
+               ///    text
+               /// </remarks>
+               public AutoCompleteHandler AutoCompleteEvent;
+               
+               static Handler [] handlers;
+
+               public LineEditor (string name) : this (name, 10) { }
+               
+               public LineEditor (string name, int histsize)
+               {
+                       handlers = new Handler [] {
+                               new Handler (ConsoleKey.Home,       CmdHome),
+                               new Handler (ConsoleKey.End,        CmdEnd),
+                               new Handler (ConsoleKey.LeftArrow,  CmdLeft),
+                               new Handler (ConsoleKey.RightArrow, CmdRight),
+                               new Handler (ConsoleKey.UpArrow,    CmdHistoryPrev),
+                               new Handler (ConsoleKey.DownArrow,  CmdHistoryNext),
+                               new Handler (ConsoleKey.Enter,      CmdDone),
+                               new Handler (ConsoleKey.Backspace,  CmdBackspace),
+                               new Handler (ConsoleKey.Delete,     CmdDeleteChar),
+                               new Handler (ConsoleKey.Tab,        CmdTabOrComplete),
+                               
+                               // Emacs keys
+                               Handler.Control ('A', CmdHome),
+                               Handler.Control ('E', CmdEnd),
+                               Handler.Control ('B', CmdLeft),
+                               Handler.Control ('F', CmdRight),
+                               Handler.Control ('P', CmdHistoryPrev),
+                               Handler.Control ('N', CmdHistoryNext),
+                               Handler.Control ('K', CmdKillToEOF),
+                               Handler.Control ('Y', CmdYank),
+                               Handler.Control ('D', CmdDeleteChar),
+                               Handler.Control ('L', CmdRefresh),
+                               Handler.Control ('R', CmdReverseSearch),
+                               Handler.Control ('G', delegate {} ),
+                               Handler.Alt ('B', ConsoleKey.B, CmdBackwardWord),
+                               Handler.Alt ('F', ConsoleKey.F, CmdForwardWord),
+                               
+                               Handler.Alt ('D', ConsoleKey.D, CmdDeleteWord),
+                               Handler.Alt ((char) 8, ConsoleKey.Backspace, CmdDeleteBackword),
+                               
+                               // DEBUG
+                               //Handler.Control ('T', CmdDebug),
+
+                               // quote
+                               Handler.Control ('Q', delegate { HandleChar (Console.ReadKey (true).KeyChar); })
+                       };
+
+                       rendered_text = new StringBuilder ();
+                       text = new StringBuilder ();
+
+                       history = new History (name, histsize);
+                       
+                       //if (File.Exists ("log"))File.Delete ("log");
+                       //log = File.CreateText ("log"); 
+               }
+
+               void CmdDebug ()
+               {
+                       history.Dump ();
+                       Console.WriteLine ();
+                       Render ();
+               }
+
+               void Render ()
+               {
+                       Console.Write (shown_prompt);
+                       Console.Write (rendered_text);
+
+                       int max = System.Math.Max (rendered_text.Length + shown_prompt.Length, max_rendered);
+                       
+                       for (int i = rendered_text.Length + shown_prompt.Length; i < max_rendered; i++)
+                               Console.Write (' ');
+                       max_rendered = shown_prompt.Length + rendered_text.Length;
+
+                       // Write one more to ensure that we always wrap around properly if we are at the
+                       // end of a line.
+                       Console.Write (' ');
+
+                       UpdateHomeRow (max);
+               }
+
+               void UpdateHomeRow (int screenpos)
+               {
+                       int lines = 1 + (screenpos / Console.WindowWidth);
+
+                       home_row = Console.CursorTop - (lines - 1);
+                       if (home_row < 0)
+                               home_row = 0;
+               }
+               
+
+               void RenderFrom (int pos)
+               {
+                       int rpos = TextToRenderPos (pos);
+                       int i;
+                       
+                       for (i = rpos; i < rendered_text.Length; i++)
+                               Console.Write (rendered_text [i]);
+
+                       if ((shown_prompt.Length + rendered_text.Length) > max_rendered)
+                               max_rendered = shown_prompt.Length + rendered_text.Length;
+                       else {
+                               int max_extra = max_rendered - shown_prompt.Length;
+                               for (; i < max_extra; i++)
+                                       Console.Write (' ');
+                       }
+               }
+
+               void ComputeRendered ()
+               {
+                       rendered_text.Length = 0;
+
+                       for (int i = 0; i < text.Length; i++){
+                               int c = (int) text [i];
+                               if (c < 26){
+                                       if (c == '\t')
+                                               rendered_text.Append ("    ");
+                                       else {
+                                               rendered_text.Append ('^');
+                                               rendered_text.Append ((char) (c + (int) 'A' - 1));
+                                       }
+                               } else
+                                       rendered_text.Append ((char)c);
+                       }
+               }
+
+               int TextToRenderPos (int pos)
+               {
+                       int p = 0;
+
+                       for (int i = 0; i < pos; i++){
+                               int c;
+
+                               c = (int) text [i];
+                               
+                               if (c < 26){
+                                       if (c == 9)
+                                               p += 4;
+                                       else
+                                               p += 2;
+                               } else
+                                       p++;
+                       }
+
+                       return p;
+               }
+
+               int TextToScreenPos (int pos)
+               {
+                       return shown_prompt.Length + TextToRenderPos (pos);
+               }
+               
+               string Prompt {
+                       get { return prompt; }
+                       set { prompt = value; }
+               }
+
+               int LineCount {
+                       get {
+                               return (shown_prompt.Length + rendered_text.Length)/Console.WindowWidth;
+                       }
+               }
+               
+               void ForceCursor (int newpos)
+               {
+                       cursor = newpos;
+
+                       int actual_pos = shown_prompt.Length + TextToRenderPos (cursor);
+                       int row = home_row + (actual_pos/Console.WindowWidth);
+                       int col = actual_pos % Console.WindowWidth;
+
+                       if (row >= Console.BufferHeight)
+                               row = Console.BufferHeight-1;
+                       Console.SetCursorPosition (col, row);
+                       
+                       //log.WriteLine ("Going to cursor={0} row={1} col={2} actual={3} prompt={4} ttr={5} old={6}", newpos, row, col, actual_pos, prompt.Length, TextToRenderPos (cursor), cursor);
+                       //log.Flush ();
+               }
+
+               void UpdateCursor (int newpos)
+               {
+                       if (cursor == newpos)
+                               return;
+
+                       ForceCursor (newpos);
+               }
+
+               void InsertChar (char c)
+               {
+                       int prev_lines = LineCount;
+                       text = text.Insert (cursor, c);
+                       ComputeRendered ();
+                       if (prev_lines != LineCount){
+
+                               Console.SetCursorPosition (0, home_row);
+                               Render ();
+                               ForceCursor (++cursor);
+                       } else {
+                               RenderFrom (cursor);
+                               ForceCursor (++cursor);
+                               UpdateHomeRow (TextToScreenPos (cursor));
+                       }
+               }
+
+               //
+               // Commands
+               //
+               void CmdDone ()
+               {
+                       done = true;
+               }
+
+               void CmdTabOrComplete ()
+               {
+                       bool complete = false;
+
+                       if (AutoCompleteEvent != null){
+                               if (TabAtStartCompletes)
+                                       complete = true;
+                               else {
+                                       for (int i = 0; i < cursor; i++){
+                                               if (!Char.IsWhiteSpace (text [i])){
+                                                       complete = true;
+                                                       break;
+                                               }
+                                       }
+                               }
+
+                               if (complete){
+                                       Completion completion = AutoCompleteEvent (text.ToString (), cursor);
+                                       string [] completions = completion.Result;
+                                       if (completions == null)
+                                               return;
+                                       
+                                       int ncompletions = completions.Length;
+                                       if (ncompletions == 0)
+                                               return;
+                                       
+                                       if (completions.Length == 1){
+                                               InsertTextAtCursor (completions [0]);
+                                       } else {
+                                               int last = -1;
+                                               
+                                               for (int p = 0; p < completions [0].Length; p++){
+                                                       char c = completions [0][p];
+
+
+                                                       for (int i = 1; i < ncompletions; i++){
+                                                               if (completions [i].Length < p)
+                                                                       goto mismatch;
+                                                       
+                                                               if (completions [i][p] != c){
+                                                                       goto mismatch;
+                                                               }
+                                                       }
+                                                       last = p;
+                                               }
+                                       mismatch:
+                                               if (last != -1){
+                                                       InsertTextAtCursor (completions [0].Substring (0, last+1));
+                                               }
+                                               Console.WriteLine ();
+                                               foreach (string s in completions){
+                                                       Console.Write (completion.Prefix);
+                                                       Console.Write (s);
+                                                       Console.Write (' ');
+                                               }
+                                               Console.WriteLine ();
+                                               Render ();
+                                               ForceCursor (cursor);
+                                       }
+                               } else
+                                       HandleChar ('\t');
+                       } else
+                               HandleChar ('t');
+               }
+               
+               void CmdHome ()
+               {
+                       UpdateCursor (0);
+               }
+
+               void CmdEnd ()
+               {
+                       UpdateCursor (text.Length);
+               }
+               
+               void CmdLeft ()
+               {
+                       if (cursor == 0)
+                               return;
+
+                       UpdateCursor (cursor-1);
+               }
+
+               void CmdBackwardWord ()
+               {
+                       int p = WordBackward (cursor);
+                       if (p == -1)
+                               return;
+                       UpdateCursor (p);
+               }
+
+               void CmdForwardWord ()
+               {
+                       int p = WordForward (cursor);
+                       if (p == -1)
+                               return;
+                       UpdateCursor (p);
+               }
+
+               void CmdRight ()
+               {
+                       if (cursor == text.Length)
+                               return;
+
+                       UpdateCursor (cursor+1);
+               }
+
+               void RenderAfter (int p)
+               {
+                       ForceCursor (p);
+                       RenderFrom (p);
+                       ForceCursor (cursor);
+               }
+               
+               void CmdBackspace ()
+               {
+                       if (cursor == 0)
+                               return;
+
+                       text.Remove (--cursor, 1);
+                       ComputeRendered ();
+                       RenderAfter (cursor);
+               }
+
+               void CmdDeleteChar ()
+               {
+                       // If there is no input, this behaves like EOF
+                       if (text.Length == 0){
+                               done = true;
+                               text = null;
+                               Console.WriteLine ();
+                               return;
+                       }
+                       
+                       if (cursor == text.Length)
+                               return;
+                       text.Remove (cursor, 1);
+                       ComputeRendered ();
+                       RenderAfter (cursor);
+               }
+
+               int WordForward (int p)
+               {
+                       if (p >= text.Length)
+                               return -1;
+
+                       int i = p;
+                       if (Char.IsPunctuation (text [p]) || Char.IsSymbol (text [p]) || Char.IsWhiteSpace (text[p])){
+                               for (; i < text.Length; i++){
+                                       if (Char.IsLetterOrDigit (text [i]))
+                                           break;
+                               }
+                               for (; i < text.Length; i++){
+                                       if (!Char.IsLetterOrDigit (text [i]))
+                                           break;
+                               }
+                       } else {
+                               for (; i < text.Length; i++){
+                                       if (!Char.IsLetterOrDigit (text [i]))
+                                           break;
+                               }
+                       }
+                       if (i != p)
+                               return i;
+                       return -1;
+               }
+
+               int WordBackward (int p)
+               {
+                       if (p == 0)
+                               return -1;
+
+                       int i = p-1;
+                       if (i == 0)
+                               return 0;
+                       
+                       if (Char.IsPunctuation (text [i]) || Char.IsSymbol (text [i]) || Char.IsWhiteSpace (text[i])){
+                               for (; i >= 0; i--){
+                                       if (Char.IsLetterOrDigit (text [i]))
+                                               break;
+                               }
+                               for (; i >= 0; i--){
+                                       if (!Char.IsLetterOrDigit (text[i]))
+                                               break;
+                               }
+                       } else {
+                               for (; i >= 0; i--){
+                                       if (!Char.IsLetterOrDigit (text [i]))
+                                               break;
+                               }
+                       }
+                       i++;
+                       
+                       if (i != p)
+                               return i;
+
+                       return -1;
+               }
+               
+               void CmdDeleteWord ()
+               {
+                       int pos = WordForward (cursor);
+
+                       if (pos == -1)
+                               return;
+
+                       string k = text.ToString (cursor, pos-cursor);
+                       
+                       if (last_handler == CmdDeleteWord)
+                               kill_buffer = kill_buffer + k;
+                       else
+                               kill_buffer = k;
+                       
+                       text.Remove (cursor, pos-cursor);
+                       ComputeRendered ();
+                       RenderAfter (cursor);
+               }
+               
+               void CmdDeleteBackword ()
+               {
+                       int pos = WordBackward (cursor);
+                       if (pos == -1)
+                               return;
+
+                       string k = text.ToString (pos, cursor-pos);
+                       
+                       if (last_handler == CmdDeleteBackword)
+                               kill_buffer = k + kill_buffer;
+                       else
+                               kill_buffer = k;
+                       
+                       text.Remove (pos, cursor-pos);
+                       ComputeRendered ();
+                       RenderAfter (pos);
+               }
+               
+               //
+               // Adds the current line to the history if needed
+               //
+               void HistoryUpdateLine ()
+               {
+                       history.Update (text.ToString ());
+               }
+               
+               void CmdHistoryPrev ()
+               {
+                       if (!history.PreviousAvailable ())
+                               return;
+
+                       HistoryUpdateLine ();
+                       
+                       SetText (history.Previous ());
+               }
+
+               void CmdHistoryNext ()
+               {
+                       if (!history.NextAvailable())
+                               return;
+
+                       history.Update (text.ToString ());
+                       SetText (history.Next ());
+                       
+               }
+
+               void CmdKillToEOF ()
+               {
+                       kill_buffer = text.ToString (cursor, text.Length-cursor);
+                       text.Length = cursor;
+                       ComputeRendered ();
+                       RenderAfter (cursor);
+               }
+
+               void CmdYank ()
+               {
+                       InsertTextAtCursor (kill_buffer);
+               }
+
+               void InsertTextAtCursor (string str)
+               {
+                       int prev_lines = LineCount;
+                       text.Insert (cursor, str);
+                       ComputeRendered ();
+                       if (prev_lines != LineCount){
+                               Console.SetCursorPosition (0, home_row);
+                               Render ();
+                               cursor += str.Length;
+                               ForceCursor (cursor);
+                       } else {
+                               RenderFrom (cursor);
+                               cursor += str.Length;
+                               ForceCursor (cursor);
+                               UpdateHomeRow (TextToScreenPos (cursor));
+                       }
+               }
+               
+               void SetSearchPrompt (string s)
+               {
+                       SetPrompt ("(reverse-i-search)`" + s + "': ");
+               }
+
+               void ReverseSearch ()
+               {
+                       int p;
+
+                       if (cursor == text.Length){
+                               // The cursor is at the end of the string
+                               
+                               p = text.ToString ().LastIndexOf (search);
+                               if (p != -1){
+                                       match_at = p;
+                                       cursor = p;
+                                       ForceCursor (cursor);
+                                       return;
+                               }
+                       } else {
+                               // The cursor is somewhere in the middle of the string
+                               int start = (cursor == match_at) ? cursor - 1 : cursor;
+                               if (start != -1){
+                                       p = text.ToString ().LastIndexOf (search, start);
+                                       if (p != -1){
+                                               match_at = p;
+                                               cursor = p;
+                                               ForceCursor (cursor);
+                                               return;
+                                       }
+                               }
+                       }
+
+                       // Need to search backwards in history
+                       HistoryUpdateLine ();
+                       string s = history.SearchBackward (search);
+                       if (s != null){
+                               match_at = -1;
+                               SetText (s);
+                               ReverseSearch ();
+                       }
+               }
+               
+               void CmdReverseSearch ()
+               {
+                       if (searching == 0){
+                               match_at = -1;
+                               last_search = search;
+                               searching = -1;
+                               search = "";
+                               SetSearchPrompt ("");
+                       } else {
+                               if (search == ""){
+                                       if (last_search != "" && last_search != null){
+                                               search = last_search;
+                                               SetSearchPrompt (search);
+
+                                               ReverseSearch ();
+                                       }
+                                       return;
+                               }
+                               ReverseSearch ();
+                       } 
+               }
+
+               void SearchAppend (char c)
+               {
+                       search = search + c;
+                       SetSearchPrompt (search);
+
+                       //
+                       // If the new typed data still matches the current text, stay here
+                       //
+                       if (cursor < text.Length){
+                               string r = text.ToString (cursor, text.Length - cursor);
+                               if (r.StartsWith (search))
+                                       return;
+                       }
+
+                       ReverseSearch ();
+               }
+               
+               void CmdRefresh ()
+               {
+                       Console.Clear ();
+                       max_rendered = 0;
+                       Render ();
+                       ForceCursor (cursor);
+               }
+
+               void InterruptEdit (object sender, ConsoleCancelEventArgs a)
+               {
+                       // Do not abort our program:
+                       a.Cancel = true;
+
+                       // Interrupt the editor
+                       edit_thread.Abort();
+               }
+
+               void HandleChar (char c)
+               {
+                       if (searching != 0)
+                               SearchAppend (c);
+                       else
+                               InsertChar (c);
+               }
+
+               void EditLoop ()
+               {
+                       ConsoleKeyInfo cki;
+
+                       while (!done){
+                               ConsoleModifiers mod;
+                               
+                               cki = Console.ReadKey (true);
+                               if (cki.Key == ConsoleKey.Escape){
+                                       cki = Console.ReadKey (true);
+
+                                       mod = ConsoleModifiers.Alt;
+                               } else
+                                       mod = cki.Modifiers;
+                               
+                               bool handled = false;
+
+                               foreach (Handler handler in handlers){
+                                       ConsoleKeyInfo t = handler.CKI;
+
+                                       if (t.Key == cki.Key && t.Modifiers == mod){
+                                               handled = true;
+                                               handler.KeyHandler ();
+                                               last_handler = handler.KeyHandler;
+                                               break;
+                                       } else if (t.KeyChar == cki.KeyChar && t.Key == ConsoleKey.Zoom){
+                                               handled = true;
+                                               handler.KeyHandler ();
+                                               last_handler = handler.KeyHandler;
+                                               break;
+                                       }
+                               }
+                               if (handled){
+                                       if (searching != 0){
+                                               if (last_handler != CmdReverseSearch){
+                                                       searching = 0;
+                                                       SetPrompt (prompt);
+                                               }
+                                       }
+                                       continue;
+                               }
+
+                               if (cki.KeyChar != (char) 0)
+                                       HandleChar (cki.KeyChar);
+                       } 
+               }
+
+               void InitText (string initial)
+               {
+                       text = new StringBuilder (initial);
+                       ComputeRendered ();
+                       cursor = text.Length;
+                       Render ();
+                       ForceCursor (cursor);
+               }
+
+               void SetText (string newtext)
+               {
+                       Console.SetCursorPosition (0, home_row);
+                       InitText (newtext);
+               }
+
+               void SetPrompt (string newprompt)
+               {
+                       shown_prompt = newprompt;
+                       Console.SetCursorPosition (0, home_row);
+                       Render ();
+                       ForceCursor (cursor);
+               }
+               
+               public string Edit (string prompt, string initial)
+               {
+                       edit_thread = Thread.CurrentThread;
+                       searching = 0;
+                       Console.CancelKeyPress += InterruptEdit;
+                       
+                       done = false;
+                       history.CursorToEnd ();
+                       max_rendered = 0;
+                       
+                       Prompt = prompt;
+                       shown_prompt = prompt;
+                       InitText (initial);
+                       history.Append (initial);
+
+                       do {
+                               try {
+                                       EditLoop ();
+                               } catch (ThreadAbortException){
+                                       searching = 0;
+                                       Thread.ResetAbort ();
+                                       Console.WriteLine ();
+                                       SetPrompt (prompt);
+                                       SetText ("");
+                               }
+                       } while (!done);
+                       Console.WriteLine ();
+                       
+                       Console.CancelKeyPress -= InterruptEdit;
+
+                       if (text == null){
+                               history.Close ();
+                               return null;
+                       }
+
+                       string result = text.ToString ();
+                       if (result != "")
+                               history.Accept (result);
+                       else
+                               history.RemoveLast ();
+
+                       return result;
+               }
+               
+               public void SaveHistory ()
+               {
+                       if (history != null) {
+                               history.Close ();
+                       }
+               }
+
+               public bool TabAtStartCompletes { get; set; }
+                       
+               //
+               // Emulates the bash-like behavior, where edits done to the
+               // history are recorded
+               //
+               class History {
+                       string [] history;
+                       int head, tail;
+                       int cursor, count;
+                       string histfile;
+                       
+                       public History (string app, int size)
+                       {
+                               if (size < 1)
+                                       throw new ArgumentException ("size");
+
+                               if (app != null){
+                                       string dir = Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData);
+                                       //Console.WriteLine (dir);
+                                       if (!Directory.Exists (dir)){
+                                               try {
+                                                       Directory.CreateDirectory (dir);
+                                               } catch {
+                                                       app = null;
+                                               }
+                                       }
+                                       if (app != null)
+                                               histfile = Path.Combine (dir, app) + ".history";
+                               }
+                               
+                               history = new string [size];
+                               head = tail = cursor = 0;
+
+                               if (File.Exists (histfile)){
+                                       using (StreamReader sr = File.OpenText (histfile)){
+                                               string line;
+                                               
+                                               while ((line = sr.ReadLine ()) != null){
+                                                       if (line != "")
+                                                               Append (line);
+                                               }
+                                       }
+                               }
+                       }
+
+                       public void Close ()
+                       {
+                               if (histfile == null)
+                                       return;
+
+                               try {
+                                       using (StreamWriter sw = File.CreateText (histfile)){
+                                               int start = (count == history.Length) ? head : tail;
+                                               for (int i = start; i < start+count; i++){
+                                                       int p = i % history.Length;
+                                                       sw.WriteLine (history [p]);
+                                               }
+                                       }
+                               } catch {
+                                       // ignore
+                               }
+                       }
+                       
+                       //
+                       // Appends a value to the history
+                       //
+                       public void Append (string s)
+                       {
+                               //Console.WriteLine ("APPENDING {0} head={1} tail={2}", s, head, tail);
+                               history [head] = s;
+                               head = (head+1) % history.Length;
+                               if (head == tail)
+                                       tail = (tail+1 % history.Length);
+                               if (count != history.Length)
+                                       count++;
+                               //Console.WriteLine ("DONE: head={1} tail={2}", s, head, tail);
+                       }
+
+                       //
+                       // Updates the current cursor location with the string,
+                       // to support editing of history items.   For the current
+                       // line to participate, an Append must be done before.
+                       //
+                       public void Update (string s)
+                       {
+                               history [cursor] = s;
+                       }
+
+                       public void RemoveLast ()
+                       {
+                               head = head-1;
+                               if (head < 0)
+                                       head = history.Length-1;
+                       }
+                       
+                       public void Accept (string s)
+                       {
+                               int t = head-1;
+                               if (t < 0)
+                                       t = history.Length-1;
+                               
+                               history [t] = s;
+                       }
+                       
+                       public bool PreviousAvailable ()
+                       {
+                               //Console.WriteLine ("h={0} t={1} cursor={2}", head, tail, cursor);
+                               if (count == 0)
+                                       return false;
+                               int next = cursor-1;
+                               if (next < 0)
+                                       next = count-1;
+
+                               if (next == head)
+                                       return false;
+
+                               return true;
+                       }
+
+                       public bool NextAvailable ()
+                       {
+                               if (count == 0)
+                                       return false;
+                               int next = (cursor + 1) % history.Length;
+                               if (next == head)
+                                       return false;
+                               return true;
+                       }
+                       
+                       
+                       //
+                       // Returns: a string with the previous line contents, or
+                       // nul if there is no data in the history to move to.
+                       //
+                       public string Previous ()
+                       {
+                               if (!PreviousAvailable ())
+                                       return null;
+
+                               cursor--;
+                               if (cursor < 0)
+                                       cursor = history.Length - 1;
+
+                               return history [cursor];
+                       }
+
+                       public string Next ()
+                       {
+                               if (!NextAvailable ())
+                                       return null;
+
+                               cursor = (cursor + 1) % history.Length;
+                               return history [cursor];
+                       }
+
+                       public void CursorToEnd ()
+                       {
+                               if (head == tail)
+                                       return;
+
+                               cursor = head;
+                       }
+
+                       public void Dump ()
+                       {
+                               Console.WriteLine ("Head={0} Tail={1} Cursor={2} count={3}", head, tail, cursor, count);
+                               for (int i = 0; i < history.Length;i++){
+                                       Console.WriteLine (" {0} {1}: {2}", i == cursor ? "==>" : "   ", i, history[i]);
+                               }
+                               //log.Flush ();
+                       }
+
+                       public string SearchBackward (string term)
+                       {
+                               for (int i = 0; i < count; i++){
+                                       int slot = cursor-i-1;
+                                       if (slot < 0)
+                                               slot = history.Length+slot;
+                                       if (slot >= history.Length)
+                                               slot = 0;
+                                       if (history [slot] != null && history [slot].IndexOf (term) != -1){
+                                               cursor = slot;
+                                               return history [slot];
+                                       }
+                               }
+
+                               return null;
+                       }
+                       
+               }
+       }
+
+#if DEMO
+       class Demo {
+               static void Main ()
+               {
+                       LineEditor le = new LineEditor ("foo");
+                       string s;
+                       
+                       while ((s = le.Edit ("shell> ", "")) != null){
+                               Console.WriteLine ("----> [{0}]", s);
+                       }
+               }
+       }
+#endif
+}
diff --git a/cs/step0_repl.cs b/cs/step0_repl.cs
new file mode 100644 (file)
index 0000000..665aab6
--- /dev/null
@@ -0,0 +1,44 @@
+using System;
+using System.IO;
+using Mono.Terminal;
+
+namespace Mal {
+    class step0_repl {
+        // read
+        static string READ(string str) {
+            return str;
+        }
+
+        // eval
+        static string EVAL(string ast, string env) {
+            return ast;
+        }
+
+        // print
+        static string PRINT(string exp) {
+            return exp;
+        }
+
+        // REPL
+        static string RE(string env, string str) {
+            return EVAL(READ(str), env);
+        }
+
+        static void Main(string[] args) {
+            string prompt = "user> ";
+            LineEditor lineedit = new LineEditor("Mal");
+
+            while (true) {
+                string line;
+                try {
+                    line = lineedit.Edit(prompt, "");
+                    if (line == null) { break; }
+                } catch (IOException e) {
+                    Console.WriteLine("IOException: " + e.Message);
+                    break;
+                }
+                Console.WriteLine(PRINT(RE(null, line)));
+            }
+        }
+    }
+}
index fb87766..6ff581c 100644 (file)
--- a/docs/TODO
+++ b/docs/TODO
@@ -19,6 +19,15 @@ Bash:
 C:
     - come up with better way to do 20 vararg code
 
+C#:
+    - http://msdn.microsoft.com/en-us/library/ms228362.aspx
+    - http://www.tutorialspoint.com/csharp
+    - Readline:
+        - http://stackoverflow.com/questions/2024170/is-there-a-net-library-similar-to-gnu-readline
+        - http://tirania.org/blog/archive/2008/Aug-26.html
+        - https://github.com/deveel/deveelrl
+
+
 Clojure:
 
 Java: