2015-03-15 18 views
8

Tôi đang cố gắng để tạo ra một chương trình mà chấp nhận đầu vào người dùng như cách tương tự như iex hoặc erl không (ví dụ. Khi nhấn phép phím để di chuyển lịch sử trước đó).Bất kỳ hỗ trợ ràng buộc dòng đọc cho elixir?

Nếu tiêu chuẩn IO.gets được sử dụng như sau,

IO.gets "user> " 

Giao diện điều khiển kết thúc với những sản phẩm sau khi up-cho phép được nhấn.

user> ^[[A^[[A 

Có bất kỳ chức năng/thư viện có khả năng readline mà có thể được sử dụng bên trong mã elixir?


Những gì tôi đã điều tra cho đến nay là,

  • Một số ngôn ngữ có tính ràng buộc hỗ trợ cho các thư viện readline, nhưng tôi đã không thể tìm thấy khả năng tương xứng cho elixir.
  • iex thực hiện dường như ủy quyền khả năng này cho erl (/lib/iex/history.ex dường như chỉ quản lý danh sách lịch sử), nhưng tôi chưa thể tìm ra chức năng tương ứng ở phía bên erlang.
  • Tôi đã thử erl_ddll.load, nhưng không đi xa hơn từ những điều sau đây.

trên 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' 

Tôi đang trên OSX và cài đặt libreadline qua homebrew, và tôi có thể tìm libreadline.dylib trong /usr/lib.


[Ghi chú thêm về mục đích]

tôi đã thử nghiệm trên như sau (mal) với elixir, mà là một repl lisp thực hiện bằng các ngôn ngữ khác nhau (nhưng không phải với elixir/erlang).

https://github.com/kanaka/mal

Phần của bước đang thực hiện repl với lịch sử, và một số ngôn ngữ đang sử dụng thư viện readline ràng buộc nếu có không phải là một người bản xứ.

[Một chút nữa cập nhật - 2015/3/22]

Tôi đã cố gắng tiếp cận với NIF (như tương tự như encurses) để sử dụng thư viện readline. Tôi có thể làm cho nó phần nào làm việc trên erlang (erl), nhưng bị mắc kẹt ở bên elixir. Khi đọc đầu vào từ thư viện C (readline hoặc chỉ scanf thuần túy), "mix run -e" hoặc "iex" có vẻ có vẻ hơi lạ (bỏ qua hoặc bỏ qua một số đầu vào), nhưng không thể tìm ra lý do. encurses dường như hành xử tương tự.

Sau đây là thử nghiệm của tôi.

https://github.com/parroty/ereadline

https://github.com/parroty/readline

Tôi có thể xảy ra với cách tiếp cận tổng quát hơn như rlwrap.

+2

có thể trùng lặp của [Làm thế nào để lưu/đăng nhập đầu ra của vỏ iex để có được lịch sử lệnh liên tục?] (Http://stackoverflow.com/questions/22426716/how-to-save-log-the-output- of-the-iex-shell-to-get-persistent-command-history) –

+2

Tôi không biết về bất kỳ tích hợp readline nào nhưng sẽ ncurses giúp đỡ với những gì bạn muốn đạt được? https://github.com/jzellner/encurses –

+0

Cảm ơn bạn đã tham khảo. Tôi đã tìm kiếm lịch sử thực hiện bên ngoài của iex (tôi đã cập nhật nền trong câu hỏi). Tuy nhiên, một số kỹ thuật có thể được áp dụng. Tôi sẽ kiểm tra chúng thêm. Tôi sẽ kiểm tra ncurses quá. – parroty

Trả lời

6

Tuyên bố từ chối trách nhiệm: Tôi không có nghĩa là chuyên gia về chủ đề của mã vỏ sò của Erlang của ban giám khảo để thực hiện đặt giá thầu của một người. Việc sửa chữa hoặc giải thích cho câu trả lời này rất được hoan nghênh. Đây cũng là một quá trình học tập cho tôi.

tl; dr: erliex dựa vào sự hợp tác giữa mô-đun edlin và cổng tty_sl để triển khai các tính năng giống như Readline của chúng. Bạn cũng có thể sử dụng chúng, mặc dù tài liệu chính thức để làm như vậy là thiếu, để nói rằng ít nhất.


Vỏ của Erlang (khi Elixir được tạo) phức tạp hơn một chút so với REPL điển hình. Không giống như một REPL điển hình, nó không chỉ lặp qua đầu vào và đánh giá nó; nó thực sự được xây dựng giống như một ứng dụng OTP điển hình, với cây giám sát tất cả các con đường xuống.

This article by the author of Learn You Some Erlang for Great Good! đi sâu hơn vào kiến ​​trúc của toàn bộ vỏ Erlang. Để tóm tắt:

  • Một tài xế TTY nền tảng cụ thể đi vào sử dụng để user_drv
  • user_drv hai giọt vào chế độ quản lý vỏ (nếu nó nhận ^G hoặc ^C) hoặc qua đầu vào cho hiện lựa chọn group (có thể có nhiều group s và do đó shell s; khi bạn nhấn ^G, bạn có tùy chọn tạo thêm group s, chuyển sang hiện group s, vv)
  • group làm việc với edlin để đặt cùng một dòng mã để đánh giá; khi nó có một dòng, mà nó gửi tới shell
  • shell làm việc đánh giá thực tế, sau đó gửi kết quả đến group, gửi kết quả đến user_drv
  • Nếu group gửi điều cần user_drv là nhóm hoạt động, sau đó user_drv chuyển nó vào trình điều khiển TTY (và do đó cho người dùng); khác, nó bị tắt tiếng

Phần của quá trình này có liên quan đến câu hỏi là edlin, là một triển khai Erlang về chức năng giống như Readline. edlin là, không may, không phải tài liệu đặc biệt cũng như xa như tôi có thể nói, nhưng ý chính của việc sử dụng nó trong Elixir (dựa trên những gì tôi có thể lượm lặt ra khỏi lib/kernel/src/group.erl) như sau:

  • Gọi :edlin.init trong quá trình đang sử dụng edlin (điều này thiết lập "bộ đệm giết", theo ý nghĩa Emacs của "vòng giết")
  • Khi bạn đã sẵn sàng đọc một dòng, hãy gọi {:more_chars,continuation,requests} = :edlin.start(prompt), trong đó prompt là danh sách đại diện bạn đoán nó - lời nhắc dòng lệnh của trình bao của bạn
  • Xử lý requests, phải ở dạng [{:put_chars,:unicode,prompt}]; trong vỏ Erlang, "xử lý" có nghĩa là "gửi cho user_drv được in", nhưng trong trường hợp của bạn này có thể khác nhau

Tại thời điểm này, các vòng lặp khởi động. Trong mỗi lần lặp lại, bạn sẽ gọi số :edlin.edit_line(characters,continuation) (trong đó characters là danh sách ký tự, tức là từ đầu vào của người dùng).Mỗi cuộc gọi sẽ cung cấp cho bạn một trong các bộ sưu tập sau đây:

  • {:done,line,rest,requests}: edlin gặp phải dòng mới và được xử lý xong dòng của bạn; vào thời điểm này, bạn có thể làm bất cứ điều gì bạn muốn với line (dòng) và rest (tất cả các ký tự sau dòng của bạn)
  • {:more_chars,continuation,requests}: edlin cần thêm ký tự; gọi :edlin.edit_line(characters,continuation)
  • {:blink,continuation,requests}: Tôi không chắc chắn 100% đây, nhưng tôi nghĩ rằng điều này đã làm với edlin nhân vật nổi bật (như khi con trỏ nhảy đến một phù hợp với ( nếu bạn gõ ))
  • {:undefined,character,rest,continuation,requests}: Không phải 100% chắc chắn ở đây, một trong hai, nhưng tôi nghĩ rằng nó đã làm với việc xử lý những thứ như lịch sử lệnh

trong mọi trường hợp, requests sẽ là một danh sách các hàng tương ứng với hướng dẫn user_drv, thường cho những thứ như viết chữ, di chuyển con trỏ, v.v.


Tiếp theo là câu hỏi về xử lý TTY. user_drv.erl thực hiện điều này với một cái gì đó gọi là tty_sl, là một cổng Erlang (có nghĩa là, một chương trình bên ngoài được thiết kế để hoạt động như một quy trình Erlang) với các phiên bản khác nhau cho Windows và Unix. Thủ tục cơ bản (một lần nữa, Elixirified):

  • Xác định như sau (chúng tôi sẽ cần nó sau này):

    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 
    
  • Gọi port = Port.open {:spawn,'tty_sl -c -e'} (-e cho "echo", -c cho bất cứ điều gì "canon " có nghĩa); có một chút kiểm tra lỗi trong bước này cho user_drv, rõ ràng để nó có thể bắt đầu cũ hơn user thay thế (trong đó - mỗi bài viết được liên kết ở trên - dường như là phiên bản cũ hơn của vỏ Erlang)

  • Quay lên quá trình edlin • nhập mô tả ở trên và lưu nó trong, nói, shell (user_drv làm thứ một bó hơn ở đây để thiết lập nhiều group s)
  • Bắt đầu vòng lặp để xử lý yêu cầu từ shell

Sau đó bạn, trong vòng lặp:

  • Nhận request (thực sự là một danh sách các request s mỗi ở trên)
  • Chuyển đổi mỗi request để cái gì đó tty_sl hiểu:

    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 
    
  • Gửi command đến TTY:

    result = case command do 
        {:requests,requests} -> 
        # Handle more requests 
        {:command,_} = command -> 
        send port, command 
        :ok 
        {command,reply} -> 
        send port, command 
        reply 
        _ -> 
        :ok 
    end 
    

Bạn cũng sẽ cần nhận nội dung từ TTY.Trong trường hợp user_drv, TTY sẽ gửi thư đến cùng một quy trình user_drv làm các quy trình group. Trong mọi trường hợp, bạn sẽ muốn để xử lý một số thông báo bổ sung bên cạnh những yêu cầu gửi qua group bởi edlin:

  • {port,{:data,bytes}}: Chuyển đổi bytes để nhân vật và gửi cho shell của bạn. Vì chúng tôi đang ở Elixir, chúng tôi thậm chí không cần phải thực hiện chuyển đổi.
  • {port,:eof}: Thỏa thuận tương tự; gửi :eof để shell của bạn
  • {port,:ok}: Không chắc chắn 100% về vấn đề này từ user_drv, nhưng tôi tin rằng nó đã làm với lệnh :put_chars_sync, cho rằng các mã trong user_drv để xử lý giao dịch thông điệp này với một Reply biến và rằng chỉ tty_sl lệnh liên quan đến một câu trả lời là :put_chars_sync

phần còn lại của các thông điệp xử lý user_drv liên quan đến các cây giám sát (cụ thể là: xử lý quá trình thoát cho cả tty_sl và khác nhau group s).


Tất nhiên, có thể là một câu trả lời đơn giản hơn nhiều để tất cả điều này: chỉ cần sử dụng user_drv và tạo cho mình một mới shell. Điều này có thể được thực hiện (tôi nghĩ, không chắc chắn 100% ở đây) với một cái gì đó dọc theo dòng user_drv_pid = :user_drv.start('tty_sl -c -e', {MyShell,:start}). Điều này có vẻ là cách hoạt động của iex (xem IEx.CLI.start/0).

+0

Wow - một câu trả lời rất chi tiết, với điều mà tôi mong đợi những người elixir đã dễ dàng giải quyết một thời gian dài trước đây, theo mặc định. Nó giống như là họ không sử dụng iex tương tác. – shevy

Các vấn đề liên quan