Irgendeine Readline Binding-Unterstützung für Elixier?

8

Ich versuche, ein Programm zu erstellen, das Benutzereingaben auf ähnliche Weise akzeptiert wie iex oder erl (zB beim Drücken der Tasten erlauben, in früheren Historien zu navigieren).

Wenn der Standard IO.gets wie folgt verwendet wird,

%Vor%

Die Konsole endet mit den folgenden Schritten, wenn Sie auf "Zulassen" drücken.

%Vor%

Gibt es irgendeine Funktion / Bibliothek mit Readline-Fähigkeit, die innerhalb des Elixier-Codes verwendet werden kann?

Was ich bisher untersucht habe, ist

  • Einige Sprachen haben eine bindende Unterstützung für Readline-Bibliotheken, aber ich war nicht in der Lage, entsprechende Fähigkeiten für Elixir zu finden.
  • iex implementation scheint diese Fähigkeit auf erl zu delegieren (/lib/iex/history.ex scheint nur die Liste der Historien zu verwalten), aber ich war nicht in der Lage, die entsprechende Funktionalität in erlang side herauszufinden.
  • Ich habe erl_ddll.load ausprobiert, bin aber nicht weiter gegangen.

auf iex,

%Vor%

Ich bin auf OSX und habe Libreadline über Homebrew installiert, und ich kann libreadline.dylib in /usr/lib finden.

[Zusätzliche Anmerkungen zum Zweck]

Ich habe mit elixir experimentiert, was ein Lisp repl ist, das in verschiedenen Sprachen implementiert ist (aber nicht mit elixir / erlang).

Ссылка

Der Teil des Schritts implementiert repl mit history, und einige Sprachen verwenden readline binding libraries, wenn es keine native gibt.

[Ein wenig mehr Update - 2015/3/22]

Ich habe versucht mit NIF-Ansatz (ähnlich wie Enkurs), Readline-Bibliothek zu verwenden. Ich könnte es mit Erlang (Erl) etwas arbeiten lassen, aber auf Elixierseite stecken. Beim Lesen von Eingaben aus C-Bibliotheken (readline oder einfach scanf) scheint "mix run -e" oder "iex" ein wenig seltsam zu sein (überspringt oder ignoriert einige Eingaben), konnte aber den Grund nicht herausfinden. Encurses scheint sich ähnlich zu verhalten.

Das Folgende war meine Prüfungen.

Ссылка

Ссылка

Ich gehe vielleicht mit einem allgemeineren Ansatz wie rlwrap.

    
parroty 15.03.2015, 02:29
quelle

1 Antwort

6

Disclaimer: Ich bin auf keinen Fall ein Experte auf dem Gebiet der Jury, die Erlangs Shell-Code manipuliert, um seine Gebote zu erfüllen. Korrekturen oder Klarstellungen zu dieser Antwort sind sehr willkommen. Das war auch ein Lernprozess für mich.

tl; dr: erl und iex verlassen sich auf die Zusammenarbeit zwischen dem edlin -Modul und dem tty_sl -Port, um ihre Readline-ähnlichen Merkmale zu implementieren. Sie können diese auch verwenden, obwohl die offizielle Dokumentation dafür fehlt, um es gelinde auszudrücken.

Erlangs Shell (auf der Elixirs gebaut ist) ist ziemlich viel komplexer als eine typische REPL. Im Gegensatz zu einer typischen REPL wird nicht nur die Eingabe wiederholt und ausgewertet. Es ist eigentlich wie eine typische OTP-Anwendung aufgebaut, mit Supervisor-Bäumen ganz unten.

Dieser Artikel des Autors von Lerne etwas Erlang für Great Gut! geht ausführlicher auf die Architektur der gesamten Erlang-Shell ein. Zusammenfassend:

  • Ein plattformspezifischer TTY-Treiber übergibt Benutzereingaben an user_drv
  • user_drv fällt entweder in den Shell-Management-Modus (wenn er ^G oder ^C empfängt) oder übergibt die Eingabe an die aktuell ausgewählte group (es können mehrere group s und daher shell s sein; wenn Sie Drücken Sie ^G , Sie haben die Möglichkeit, mehr group s zu erstellen, zu bestehenden group s zu wechseln, etc.)
  • group arbeitet mit edlin , um eine Codezeile für die Auswertung zusammenzustellen; Sobald es eine Zeile hat, wird es an shell gesendet
  • shell führt die eigentliche Auswertung durch und sendet das Ergebnis an group , wodurch das Ergebnis an user_drv gesendet wird.
  • Wenn die group sendende Objekte an user_drv die aktive Gruppe ist, gibt user_drv sie an den TTY-Treiber (und somit an den Benutzer) weiter; sonst ist es gedämpft

Der für die Frage relevante Teil dieses Prozesses ist edlin , was eine Erlang-Implementierung der Readline-ähnlichen Funktionalität ist. edlin ist leider, soweit ich das beurteilen kann, nicht besonders gut dokumentiert, aber der Kern der Verwendung in Elixir (basierend auf dem, was ich aus lib/kernel/src/group.erl ) lautet wie folgt:

  • Rufen Sie :edlin.init im Prozess auf, der edlin verwendet (dies richtet einen "Kill-Puffer" ein, im Sinne von Emacs "Tötungs-Ring")
  • Wenn Sie bereit sind, eine Zeile zu lesen, rufen Sie {:more_chars,continuation,requests} = :edlin.start(prompt) auf, wobei prompt eine charlist ist, die - Ihrer Meinung nach - die Befehlszeilenaufforderung Ihrer Shell darstellt
  • Handle requests , was in Form von [{:put_chars,:unicode,prompt}] sein sollte; in der Erlang-Shell bedeutet "handle" "sende an user_drv , um gedruckt zu werden", aber in Ihrem Fall könnte dies anders sein

An diesem Punkt beginnt die Schleifenbildung. In jeder Iteration rufen Sie :edlin.edit_line(characters,continuation) auf (wobei characters eine Zeichenliste ist, d. H. Aus einer Benutzereingabe). Bei jedem Anruf erhalten Sie eines der folgenden Tupel:

  • {:done,line,rest,requests} : edlin hat eine neue Zeile gefunden und die Verarbeitung Ihrer Zeile abgeschlossen. An dieser Stelle können Sie mit line (Ihre Linie) und rest (alle Zeichen, die Ihrer Linie folgen)
  • machen, was Sie wollen
  • {:more_chars,continuation,requests} : edlin benötigt mehr Zeichen; Anruf :edlin.edit_line(characters,continuation)
  • {:blink,continuation,requests} : Ich bin mir hier nicht 100% ig sicher, aber ich denke, dass dies mit edlin hervorgehobenen Zeichen zu tun hat (zB wenn der Cursor zu einem passenden ( springt, wenn Sie ) eingeben)
  • {:undefined,character,rest,continuation,requests} : Auch hier nicht 100% sicher, aber ich denke, es hat damit zu tun, Dinge wie Kommandohistorie
  • zu behandeln

In allen Fällen wird requests eine Liste von Tupeln sein, die Anweisungen für user_drv entsprechen, normalerweise für Dinge wie das Schreiben von Zeichen, das Bewegen des Cursors usw.

Als nächstes geht es um den Umgang mit dem TTY. user_drv.erl macht das mit etwas namens tty_sl , das ein Erlang-Port ist (dh ein externes Programm, das sich wie ein Erlang-Prozess verhält) mit verschiedenen Versionen für Windows und Unix. Das grundlegende Verfahren (wieder, Elixirified):

  • Definieren Sie Folgendes (wir werden es später brauchen):

    %Vor%
  • Aufruf port = Port.open {:spawn,'tty_sl -c -e'} ( -e für "echo", -c für was auch immer "canon" bedeutet); es gibt ein bisschen mehr Fehlerprüfung in diesem Schritt für user_drv , anscheinend, damit es stattdessen die ältere user starten kann (was - laut dem oben verlinkten Artikel - eine ältere Version der Erlang-Shell zu sein scheint)

  • Dreh den oben beschriebenen edlin -benutzenden Prozess auf und speichere ihn, sagen wir, shell ( user_drv macht hier eine Menge mehr Zeug, um mehrere group s einzurichten)
  • Starten Sie die Schleife, um Anfragen von shell zu bearbeiten.

Dann in der Schleife:

  • Erhalte eine request (wirklich eine Liste von request s oben)
  • Konvertiere jedes request in etwas, das tty_sl versteht:

    %Vor%
  • Sende command an das TTY:

    %Vor%

Sie müssen auch Zeug vom TTY erhalten. Im Falle von user_drv sendet das TTY Nachrichten an den gleichen user_drv Prozess wie die group Prozesse.In jedem Fall sollten Sie neben den Anforderungen, die über group by edlin :

gesendet werden, noch weitere Nachrichten verarbeiten
  • {port,{:data,bytes}} : Konvertiere bytes in Zeichen und sende es an deine Shell. Da wir in Elixir-Land sind, müssen wir nicht einmal die Konvertierung machen.
  • {port,:eof} : Ähnliche Transaktion; Sende das :eof an deine Shell
  • {port,:ok} : Nicht 100% sicher von user_drv , aber ich glaube, dass es mit dem :put_chars_sync -Befehl zu tun hat, da der Code in user_drv , um diese Nachricht zu verarbeiten, mit einer Reply -Variable und dem Der einzige Befehl tty_sl mit einer Antwort ist :put_chars_sync

Der Rest der Nachrichten, die in user_drv behandelt werden, beziehen sich auf den Supervisionsbaum (nämlich: Verarbeitungsprozess-Exits sowohl für tty_sl als auch für die verschiedenen group s).

Natürlich gibt es wahrscheinlich eine viel einfachere Antwort auf all das: Verwenden Sie einfach user_drv und erstellen Sie sich ein neues shell . Dies kann getan werden (ich denke; nicht 100% sicher hier) mit etwas in der Art von user_drv_pid = :user_drv.start('tty_sl -c -e', {MyShell,:start}) . Dies scheint zu sein, wie iex funktioniert (siehe IEx.CLI.start/0 ).

    
YellowApple 12.12.2016 02:31
quelle

Tags und Links