我试图创造出接受用户输入的类似的方式,程序iex或erl做(例如:按允许键来浏览过去的历史时).
如果标准IO.gets使用如下,
IO.gets "user> "
Run Code Online (Sandbox Code Playgroud)
按下向上允许时,控制台最终会出现以下情况.
user> ^[[A^[[A
Run Code Online (Sandbox Code Playgroud)
是否有任何函数/库具有可在elixir代码中使用的readline功能?
到目前为止我调查的是,
iex实现似乎将此功能委托给erl(/lib/iex/history.ex似乎只是管理历史列表),但我无法在erlang方面找到相应的功能.erl_ddll.load,但未能从以下方面走得更远.在iex上,
iex(4)> :erl_ddll.load('/usr/lib', 'libreadline')
{:error, {:open_error, -10}}
iex(5)> :erl_ddll.format_error({:open_error, -10})
'dlopen(/usr/lib/libreadline.so, 2): image not found'
Run Code Online (Sandbox Code Playgroud)
我在OSX,并通过自制安装libreadline,我可以找到libreadline.dylib在/usr/lib.
[关于目的的附加说明]
我正在使用elixir进行以下(mal)实验,这是一个用各种语言实现的lisp repl(但不是用elixir/erlang实现).
该步骤的一部分是使用历史记录实现repl,并且如果不是本地语言,则某些语言正在使用readline绑定库.
[更新一点 - 2015/3/22]
我尝试使用NIF方法(与encurses类似)来使用readline库.我可以在erlang(erl)上做一些工作,但是坚持使用elixir方面.当从C库(readline或只是普通scanf)读取输入时,"mix run -e"或"iex"似乎表现得有些奇怪(跳过或忽略某些输入),但无法找出原因.encurses似乎行为相似.
以下是我的试验.
https://github.com/parroty/ereadline
https://github.com/parroty/readline
我可能会采用像rlwrap这样的更通用的方法.
Yel*_*ple 10
免责声明:我绝不是关于陪审团操纵Erlang的shell代码进行竞标的专家.对此答案的更正或澄清非常受欢迎.这对我来说也是一个学习过程.
tl; dr:erl并iex依靠edlin模块和tty_sl端口之间的合作来实现类似Readline的功能.您也可以使用这些,但至少可以说缺少这样做的官方文档.
Erlang的shell(Elixir的构建版本)比典型的REPL复杂得多.与典型的REPL不同,它不仅仅是循环输入和评估它; 它实际上就像典型的OTP应用程序一样构建,监控树一直向下.
本文由作者为您学习一些Erlang for Great Good!进一步详细介绍了整个Erlang shell的架构.总结一下:
user_drvuser_drv要么进入shell管理模式(如果它接收^G或^C)或将输入传递给当前选择的group(可能有多个groups,因此shells;当你按下时^G,你可以选择创建更多的groups,切换到现有的groups等. )group与... edlin合作编写一行代码进行评估; 一旦它有一条线,它就会发送给它shellshell进行实际评估,然后将结果发送给group,将结果发送给user_drvgroup发送的东西user_drv是活动组,则将其user_drv传递给TTY驱动程序(从而传递给用户); 否则,它是静音的此过程中与该问题相关的部分是edlin,类似Readline的Erlang实现. edlin遗憾的是,据我所知,没有特别好的记录,但在Elixir中使用它的要点(根据我能够收集的内容lib/kernel/src/group.erl)如下:
:edlin.init正在使用的进程edlin(这会设置一个"杀死缓冲区",在Emacs意义上的"杀死环"){:more_chars,continuation,requests} = :edlin.start(prompt),在哪里prompt是一个charlist表示 - 你猜对了 - 你的shell的命令行提示符requests,应该是[{:put_chars,:unicode,prompt}]; 在Erlang shell中,"handle"表示"发送到user_drv打印",但在您的情况下,这可能会有所不同此时,循环开始.在每次迭代中,您将调用:edlin.edit_line(characters,continuation)(其中characters是字符列表,即来自用户输入).每次调用都会给你一个以下元组:
{:done,line,rest,requests}:edlin遇到换行符并处理好你的行; 在这一点上,你可以做任何你想做的事line(你的线)和rest(你的线后面的所有字符){:more_chars,continuation,requests}:edlin需要更多人物; 呼叫:edlin.edit_line(characters,continuation){:blink,continuation,requests}:我不是100%肯定在这里,但我认为这与edlin突出显示字符有关(比如当(你输入时光标跳转到匹配)){:undefined,character,rest,continuation,requests}:这里也不是100%肯定,但我认为它与处理命令历史等事情有关在所有情况下,requests都将是一个对应于指令的元组列表user_drv,通常用于写字符,移动光标等.
接下来是处理TTY的问题. 这是一个user_drv.erl叫做的东西tty_sl,它是一个Erlang端口(也就是说,一个设计成像Erlang进程的外部程序),具有不同的Windows和Unix版本.基本程序(再次,Elixirified):
定义以下内容(稍后我们将需要它):
def put_int16(num, tail) do # we need this in a bit
use Bitwise # because macros
[num |> bsr(8) |> band(255), num |> band(255) | tail]
end
Run Code Online (Sandbox Code Playgroud)呼叫port = Port.open {:spawn,'tty_sl -c -e'}(-e对于"回声",-c对于"佳能"的意思); 在这一步中有更多的错误检查user_drv,显然是因为它可以启动旧版本user(根据上面链接的文章 - 似乎是Erlang shell的旧版本)
edlin上述-使用的过程,它在存储,也就是说,shell(user_drv在这里做了一大堆更多的东西,以设置多个groupS)shell然后,在循环中:
request(真的是request每个上面的s 列表)将每个转换request为tty_sl理解的东西:
command = case request do
{:put_chars,:unicode,chars} -> # OP_PUTC
{:command, [0|:unicode.characters_to_binary(chars,:utf8)]}
{:move_rel,count} -> # OP_MOVE
{:command, [1|put_int16(count, [])]}
{:insert_chars,:unicode,chars} -> # OP_INSC
{:command, [2|:unicode.characters_to_binary(chars,:utf8)]}
{:delete_chars,count} -> # OP_DELC
{:command, [3|put_int16(count, [])]}
:beep -> # OP_BEEP
{:command, [4]}
{:put_chars_sync,:unicode,chars,reply} -> # OP_PUTC_SYNC
{{:command, [5|:unicode.characters_to_binary(chars,:utf8)]}, reply}
else ->
else
end
Run Code Online (Sandbox Code Playgroud)发送command到TTY:
result = case command do
{:requests,requests} ->
# Handle more requests
{:command,_} = command ->
send port, command
:ok
{command,reply} ->
send port, command
reply
_ ->
:ok
end
Run Code Online (Sandbox Code Playgroud)您还需要从TTY接收内容.在这种情况下user_drv,TTY将消息发送到与user_drv进程相同的group进程.在任何情况下,除了通过以下group方式发送的请求之外,您还需要处理一些其他消息edlin:
{port,{:data,bytes}}:转换bytes为字符并发送到您的shell.由于我们在Elixir-land,我们甚至可能不需要进行转换.{port,:eof}:类似的交易; 发送:eof到你的shell{port,:ok}:不是100%肯定这个user_drv,但我相信它与:put_chars_sync命令有关,因为user_drv处理此消息的代码处理Reply变量并且tty_sl涉及回复的唯一命令是:put_chars_sync处理的其余消息user_drv属于监督树(即:处理过程退出两者tty_sl和各种groups).
当然,对于所有这些,可能有一个更简单的答案:只需使用user_drv并创建一个新的shell.这可以做到(我认为;不是100%肯定在这里)user_drv_pid = :user_drv.start('tty_sl -c -e', {MyShell,:start}).这似乎是iex有效的(见IEx.CLI.start/0).
| 归档时间: |
|
| 查看次数: |
1365 次 |
| 最近记录: |