Skip to content

Instantly share code, notes, and snippets.

@taylor
Forked from mururu/string_io.ex
Created February 6, 2014 02:13
Show Gist options
  • Select an option

  • Save taylor/8837248 to your computer and use it in GitHub Desktop.

Select an option

Save taylor/8837248 to your computer and use it in GitHub Desktop.

Revisions

  1. Yuki Ito created this gist Feb 6, 2014.
    186 changes: 186 additions & 0 deletions string_io.ex
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,186 @@
    defmodule StringIO do
    def new(string) when is_binary(string) do
    spawn_link(fn -> string_io_process(string) end)
    end

    def string_io_process(string) do
    loop(:infinity, String.to_char_list!(string))
    end

    def loop(wait, buf) do
    receive do
    { :io_request, from, reply_as, req } ->
    p = :erlang.process_flag(:priority, :normal)
    buf = io_request(from, reply_as, req, buf)
    :erlang.process_flag(:priority, p)
    loop(wait, buf)
    :stop ->
    receive after: (2 -> :ok)
    :erlang.process_flag(:priority, :low)
    loop(0, buf)
    _ ->
    loop(0, buf)
    end
    end

    defp io_request(from, reply_as, req, buf) do
    { reply, buf1 } = io_request(req, buf)
    io_reply(from, reply_as, reply)
    buf1
    end

    defp io_reply(from, reply_as, reply) do
    send from, { :io_reply, reply_as, reply }
    end

    defp io_request({ :put_chars, chars }, buf) do
    { :ok, [chars|buf] }
    end

    defp io_request({ :put_chars, m, f, as }, buf) do
    chars = apply(m, f, as)
    { :ok, [chars|buf] }
    end

    defp io_request({ :put_chars, _enc, chars }, buf) do
    io_request({ :put_chars, chars }, buf)
    end

    defp io_request({ :put_chars, _enc, mod, func, args }, buf) do
    io_request({ :put_chars, mod, func, args }, buf)
    end

    defp io_request({ :get_chars, _enc, prompt, n }, buf) when n >= 0 do
    io_request({ :get_chars, prompt, n }, buf)
    end

    defp io_request({ :get_chars, _prompt, n }, buf) when n >= 0 do
    get_chars(n, buf)
    end

    defp io_request({ :get_line, _enc, prompt }, buf) do
    io_request({ :get_line, prompt }, buf)
    end

    defp io_request({ :get_line, _prompt }, buf) do
    get_line(buf)
    end

    defp io_request({ :get_until, _encoding, prompt, mod, fun, args}, buf) do
    io_request({ :get_until, prompt, mod, fun, args}, buf)
    end

    defp io_request({ :get_until, _prompt, mod, fun, args }, buf) do
    get_until(mod, fun, args, buf)
    end

    defp io_request({ :setopts, _opts }, buf) do
    { :ok, buf }
    end

    defp io_request(:getopts, buf) do
    { { :error, :enotsup }, buf }
    end

    defp io_request({ :get_geometry, :columns }, buf) do
    { { :error, :enotsup }, buf }
    end

    defp io_request({ :get_geometry, :rows }, buf) do
    { { :error, :enotsup }, buf }
    end

    defp io_request({ :requests, reqs }, buf) do
    io_requests(reqs, { :ok, buf })
    end

    defp io_request(_, buf) do
    { { :error, :request }, buf }
    end

    defp io_requests([r|rs], { :ok, buf }) do
    io_requests(rs, io_request(r, buf))
    end

    defp io_requests(_, result) do
    result
    end

    defp get_line(buf) do
    case buf do
    [] ->
    { :eof, [] }
    _ ->
    { line, rest } = Enum.split_while(buf, fn(char) -> char != ?\n end)
    case rest do
    [] ->
    { String.from_char_list!(line), [] }
    [_|t] ->
    { String.from_char_list!(line) <> "\n", t }
    end
    end
    end

    defp get_chars(n, buf) do
    case buf do
    [] ->
    { :eof, [] }
    _ ->
    { chars, rest } = Enum.split(buf, n)
    { String.from_char_list!(chars), rest }
    end
    end

    defp get_until(mod, fun, args, buf) do
    do_get_until(buf, mod, fun, args)
    end

    defp do_get_until([], mod, fun, args, continuation \\ [])

    defp do_get_until([], mod, fun, args, continuation) do
    case apply(mod, fun, [continuation, :eof | args]) do
    { :done, result, rest_chars } ->
    { result, rest_chars }
    { :more, next_continuation } ->
    do_get_until([], mod, fun, args, next_continuation)
    end
    end

    defp do_get_until(input, mod, fun, args, continuation) do
    { line, rest } = Enum.split_while(input, fn(char) -> char != ?\n end)

    case rest do
    [] ->
    case apply(mod, fun, [continuation, line | args]) do
    { :done, result, rest_chars } ->
    { result, rest_chars }
    { :more, next_continuation } ->
    do_get_until([], mod, fun, args, next_continuation)
    end
    [_|t] ->
    case apply(mod, fun, [continuation, line ++ '\n' | args]) do
    { :done, result, rest_chars } ->
    { result, rest_chars ++ t }
    { :more, next_continuation } ->
    do_get_until(t, mod, fun, args, next_continuation)
    end
    end
    end
    end

    ExUnit.start

    defmodule StringIOTest do
    use ExUnit.Case

    test "the truth" do
    f = StringIO.new "abc\ndef\n"
    assert IO.read(f, :line) == "abc\n"
    assert IO.read(f, :line) == "def\n"
    assert IO.read(f, :line) == :eof

    assert IO.write(f, "hello\n") == :ok
    assert IO.read(f, :line) == "hello\n"
    assert IO.read(f, :line) == :eof
    end
    end