# coro.exs -rw-r--r-- 1.6 KiB View raw
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
defmodule Coro do
  @doc "Create a new coroutine"
  @spec create((pid -> any())) :: pid
  def create(f) do
    IO.puts "create"
    coro_driver = fn ->
      # Block until we are resumed
      receive do
        {:resume, resumer} -> 
            IO.puts("Resumed for the first time")
            f.(resumer) 
        other -> raise "Invalid initial resume"
      end
    end
    spawn(coro_driver)
  end

  @doc "Yield to the thing that resumed this coro"
  @spec yield(pid, any) :: nil
  def yield(resumer, value) do
    # Having to pass the resumer explicitly is wonkity but it's a start
    IO.puts "yield"
    send(resumer, {:yield, self(), value})
    # Block waiting for resumption
    receive do
      {:resume, resumer} -> IO.puts("wossname")
      other -> raise "Invalid resume"
    end 
  end

  @doc "Resume the given coro"
  @spec resume(pid) :: any
  def resume(coro) do
    IO.puts "resume"
    # We need to monitor a process to detect if it quits without yielding to us,
    # see https://www.erlang.org/doc/reference_manual/processes#monitors
    :erlang.monitor(:process, coro)
    send(coro, {:resume, self()})
    receive do
      # The coro yielded to us
      {:yield, pid, value} -> 
        IO.puts("Coro #{inspect pid} yielded to us with value #{inspect value}")
      # The coro exited without yielding
      {:DOWN, ref, _process, _pid2, reason} ->
        IO.puts "Coro exited normally #{inspect reason}"
        :erlang.demonitor(ref)
      other -> raise "aw heck: #{inspect other}"
    end
  end
end

x = Coro.create(
  fn resumer -> 
    IO.puts "Hello world from inside a coro" 
    Coro.yield(resumer, :hi)
    3
  end)
Coro.resume(x)
Coro.resume(x)