Skip to content
bmistree edited this page Jun 12, 2013 · 11 revisions

In this example, we alternate execution between two sides of a connection, incrementing a number each time.

Waldo file

Below is the overall Waldo file. Don't worry, we'll step through each line in a second. At this point, we're just showing the full file to give you a sense of the overall structure of a Waldo file and to give you a feel for the Waldo basic syntax.

PingPong
Endpoint Ping;
Endpoint Pong;

Sequences {
    PingPongSeq: Ping.start_ping -> Pong.perform_pong -> Ping.next_ping -> Pong.final_pong;
}

Sequence PingPongSeq (Number start_num) returns Number final_num {
    Ping.start_ping {
       start_num += 1;
    }
    Pong.perform_pong {
       start_num += 1;
    }
    Ping.next_ping {
       start_num += 1;
    }
    Pong.final_pong {
       start_num += 1;
       final_num = start_num;
    }
}

Ping
{
     Public Function ping_seq(Number to_ping_with) returns Number
     {
        return PingPongSeq(to_ping_with);
     }
}

Pong
{}

Broken down

File header

The first few lines of any Waldo file specify the name of the protocol that you are writing as well as the name of the endpoints in the file.

PingPong
Endpoint Ping;
Endpoint Pong;

In this example, our protocol, named PingPong, is composed of two endpoints, Ping and Pong. An endpoint is one side of a protocol, encapsulating all of that side's logic and state. A Waldo file can have 1 or 2 Endpoints specified. But cannot have more or less. Later parts of the protocol file actually define the logic and state that each endpoint hold as well as how they interact to perform actions.

Sequences

Sequences in Waldo are linearly ordered blocks that switch execution between each endpoint in a protocol. The sequences declaration immediately follows the file header:

Sequences {
    PingPongSeq: Ping.start_ping -> Pong.perform_pong -> Ping.next_ping -> Pong.final_pong;
}

In this case, we've declared that our protocol will have a single sequence, PingPongSeq. When the seuqence is invoked (more later), PingPongSeq starts by executing the sequence block start_ping on endpoint Ping. Following the execution of this block, PingPongSeq will automatically execute perform_pong on endpoint Pong, then next_ping on Ping, and, finally, final_pong on Pong. Sequence declarations must be linearly ordered and must alternate between endpoints. If you wanted to have additional sequences in your protocol, you would declare them in a similar way after the first. For instance, to define a PongPingSeq sequence in addition to the PingPongSeq:

Sequences {
    PingPongSeq: Ping.start_ping -> Pong.perform_pong -> Ping.next_ping -> Pong.final_pong;
    PongPingSeq: Pong.start_pong -> Ping.perform_ping -> Pong.next_pong -> Pong.final_ping;
}

Following sequence declarations, comes definitions of each sequence. In our example, we only have a single sequence, PingPongSeq, which is defined by:

Sequence PingPongSeq (Number start_num) returns Number final_num {
    Ping.start_ping {
       start_num += 1;
    }
    Pong.perform_ping {
       start_num += 1;
    }
    Ping.next_ping {
       start_num += 1;
    }
    Pong.final_pong {
       start_num += 1;
       final_num = start_num;
    }
}

Notice that the sequence takes in a single argument, start_num, with type Number and returns a final_num, also with type Number. (For a list of Waldo's types, see the type system page.)

A sequence definition is decomposed into multiple sequence blocks, in this example, Ping.start_ping, Pong.perform_ping, Ping.next_ping, and Pong.final_pong. Notice that both the order and names of these blocks mirror the sequence declaration. Sequence blocks specify where code is run. For instance, the block Ping.start_ping runs on the host that the endpoint Ping is instantiated on. Conversely, the block Pong.perform_ping runs on the host that the endpoint Pong is instantiated on. Notice that in this example, both endpoints have access to the variable start_num (as well as final_num). Arguments passed into functions as well as return types are sequence global, meaning that either endpoint in a connection can write and read to them. Using sequence global variables, each endpoint can exchange information.

Endpoint definitions

Recall that the top of the protocol file declared two endpoints, Ping and Pong. Sequences specify endpoint logic applied when communicating between endpoints. All other endpoint logic and data are specified in each endpoint's definition. The following code defines each endpoints' additional methods and data:

Ping
{
     Public Function ping_seq(Number to_ping_with) returns Number
     {
        return PingPongSeq(to_ping_with);
     }
}

Pong
{}

As you can see, Pong has no additional methods or data. However, Ping, supports one additional method, ping_seq. The type signature for ping_seq says that it takes in a single Number and returns a Number. All that ping_seq does is invoke the sequence we declared above.

It may seem strange to so trivially wrap PingPongSeq. The reason we wrap the sequence call is because non-Waldo code can only call an endpoint object's Public methods.

Run it

Compiling

Copy the code above into a single file. From the Waldo base directory, call bin/wcompile.py [filename] on it. This should produce a file, emitted.py, that contains definitions and declarations for both the Ping and Pong endpoint in it. You can use each separately.

Invoking from foreign code

Waldo compiles down to Python. So (at this time), any program that uses emitted Waldo code should be written in Python. Here are two example files, one to instantiate a Ping endpoint and one to instantiate a Pong endpoint. This example assumes that you have already compiled your PingPongProtocol to the file emitted.py.

Instantiate Pong:

from lib import Waldo
from emitted import Pong
import time

def pong_connected(endpoint):
    print '\nPong endpoint is connected!\n'

Waldo.tcp_accept(
    Pong, 'localhost',6767, connected_callback=pong_connected)

time.sleep(5)

Instantiate Ping:

from lib import Waldo
from emitted import Ping
import time

ping = Waldo.tcp_connect(Ping, 'localhost', 6767)

print ('\nThis is result of ping seq: ' + str(ping.ping_seq(0)))

Note that these examples presuppose that the lib/ folder of Waldo is on your sys path. If it is not, you may have to "point" to it by changing the line:

from lib import Waldo

Let's look more closely at the file that instantiates Pong. The line

Waldo.tcp_accept(
    Pong, 'localhost',6767, connected_callback=pong_connected)

binds to TCP port 6767 listening for connections. When it receives a connection, it creates a Pong endpoint and executes the callback function pong_connected, passing the newly created Pong endpoint to it.

Finally, we call time.sleep(5) to wait five seconds for incoming connections. After this period, we end the program.

Correspondingly, the file that instantiates a Ping endpoint connects to the TCP port 6767 and creates an endpoint. We can then call that endpoint's public methods.