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,
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
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. 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.
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:
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.
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:
:edlin.init
im Prozess auf, der edlin
verwendet (dies richtet einen "Kill-Puffer" ein, im Sinne von Emacs "Tötungs-Ring") {:more_chars,continuation,requests} = :edlin.start(prompt)
auf, wobei prompt
eine charlist ist, die - Ihrer Meinung nach - die Befehlszeilenaufforderung Ihrer Shell darstellt 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) {: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 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)
edlin
-benutzenden Prozess auf und speichere ihn, sagen wir, shell
( user_drv
macht hier eine Menge mehr Zeug, um mehrere group
s einzurichten) shell
zu bearbeiten.
Dann in der Schleife:
request
(wirklich eine Liste von request
s oben) Konvertiere jedes request
in etwas, das tty_sl
versteht:
Sende command
an das TTY:
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
:
{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
).
Tags und Links elixir