<!--
    The PingPong program simulates client-server communication.
    Here the client sends a request to the server that says "ping",
    and the client answers "pong".
-->


<!--
    First let us see a functional version of ping and pong to get
    used to the concept. The client simply calls ping n times, and
    puts "pong" when it returns.
-->

# functping: functional version of ping
# Prints "ping" and returns to pong

def functping = fun (Stdout con -> return()
{
    console!con.Puts("ping")
})

# functpong: functional version of pong
# Uses unfold to call ping n times. As unfold evaluates as needed,
# count is used to evaluate all the terms. The recursive version
# (without unfold) is left as an exercise to the reader.

def functpong = fun (int n, Stdout con -> return()
{
    unfold (i in 1...n:
        ping(con) ->
        console!con.Puts("pong").
    ).count   # do all
})


<!--
    Now to the real thing. This time it is ping that stops after
    it has produced n "ping"s, the client just keeps requesting
    the server until the server says done.

    This example illustrates first-class continuations. However I
    am not sure that I want first-class continuations in dodo, as
    the code can get quite confusing. This is why I did not define
    a type for them.
-->

# ping with first-class continuation
# Note: this is not valid dodo syntax. Comment this part to run.
# Because the parameter of yield1 is a continuation, we use ? as
# type since dodo has no type defined for continuations. Here
# callback() is the equivalent of call/cc in other languages.
# As before, we use unfold to put "ping" n times. The callback
# call sends the control back to the caller while saving the
# current stack, so that the computation can continue when
# ping regains control.

def ping = fun (int n, Stdout con -> yield1(?), done()
{
    fun (-> c(): yield1(c). ) -> callback
    unfold (i in 1...n:
        console!con.Puts("ping") ->
        callback().
    ).count ->
    done()
})

# pong with first-class continuation
# Send a request to ping and receive a handle. Then put "pong"
# and give the control back to ping using the handle. The value
# of the handle is updated for next iteration. When ping is
# done, return.

def pong = fun (int n, Stdout con -> return()
{
    unfold (handle = ping(n, con);;
        console!con.Puts("pong") ->
        handle() ->
        |               # update handle or...
            return()    # done
    ).count   # do all
})


<!--
    The final version uses the yield and resume constructs to
    replace first-class continuations. It is otherwise identical
    to the version with first-class continuations.
-->

# ping with yield
# Use unfold to put n "ping"s, allowing pong to run after each.
# The callback call sends the control back to the caller while
# the yield type saves the current stack, so that the computa-
# tion can continue when ping regains control.

def ping = fun (int n, Stdout con -> callback yield(), done()
{
    unfold (i in 1...n:
        console!con.Puts("ping") ->
        callback().
    ).count ->
    done()
})

# pong with resume
# Send a request to ping and get the hand. Then put "pong"
# and give the control back to ping using resume. When
# ping is done, return.

def pong = fun (int n, Stdout con -> return()
{
    ping(n, con) -> $$handle
    unfold (true:
        console!con.Puts("pong") ->
        resume $$handle ->
        |               # update handle or...
            return().   # done
    ).count   # do all
})

# Call pong with 15 bounces

def Main
{
    Main():
        pong(15, Stdout()).
}