3.3.  site Sites

3.3.1. Fundamentals

Orc sites imported with the site declaration are implemented as Java classes which extend orc.runtime.sites.Site. Here we will summarize the most important features of this and related classes; for a detailed description, refer to the Javadoc.

Sites must implement a single method: callSite(Args args, Token token). The Orc engine calls this method whenever the site is called by the Orc program.

The args argument (of type Args) is used to get the arguments which were passed to the site call by the Orc program. It has the following important methods:

getArg(int n)
returns the value of the (n+1)th argument, as an Object. For example, to get the value of the first argument, call args.getArg(0).
intArg(int n), stringArg(int n), boolArg(int n), etc.
returns the value of the (n+1)th argument cast to the appropriate type. If the argument is not the appropriate type, throws an ArgumentTypeMismatchException.

The token argument (of type Token) is a sort of callback which is used by the site to return a value or signal that it has halted. It has the following important methods:

resume(Object value)
return the value value from the site call. For example, to return the number 5, call token.resume(5). To publish a signal without returning any specific value, call token.resume().
error(TokenException problem)
halt without publishing a value and report an error. This method is an appropriate way to report Java exceptions encountered during a site call. Alternatively, a site can simply throw a checked exception from the callSite method; the engine will call token.error on its behalf. To convert a Java exception to the necessary TokenException type, wrap it in an instance of orc.error.runtime.JavaException.
die()
halt, without publishing a value or reporting an error. For example, the built-in site if uses this method to halt when called with a false argument.

The callSite method must return control to its caller within a short, deterministic amount of time. To implement a site which blocks or waits for some condition, callSite must save the token and return without calling any of the above methods. Then when it is time for the site call to return, call the appropriate method on the token.

3.3.2. More Site Classes

3.3.2.1. EvalSite

Many sites are deterministic, always returning a value immediately when called. Such sites can be implemented easily by extending orc.runtime.sites.EvalSite and implementing the method evaluate(Args args). This method should read the arguments from args as usual, and then return the value to be returned from the site call, or throw a checked exception to indicate an error.

3.3.2.2. ThreadedSite

Some sites need to perform blocking IO or use other Java blocking methods like Object#wait(). This cannot be done directly in the callSite or EvalSite#evaluate methods because that might block the Orc engine. Instead, extend orc.runtime.sites.ThreadedSite and implement the method evaluate(Args args). This method should read the arguments from args as usual, and then return the value to be returned from the site call, or throw a checked exception to indicate an error. Your evaluate method will be automatically run asynchronously in a new thread so that it does not interfere with the Orc engine.

Beware that the Orc engine may be configured to only allow a fixed number of site threads. If too many sites need to use threads simultaneously, some will block until threads become available.

3.3.2.3. DotSite

Recall that the notation x.msg corresponds to calling the site x with the special message value msg. To implement a site which responds to such messages, extend orc.runtime.sites.DotSite.

In your site's constructor, call the method addMember(String message, Object value) once for each message you wish the site to respond to. The call addMember(m, v) instructs the site to return the value v when it is called with the message m.

Any object can be used as a member value, even another site. Anonymous inner classes extending Site are often used as member values which behave like methods of the enclosing DotSite.

3.3.3. Tutorial Example

We will implement a simplified version of Orc's Buffer site, called ExampleBuffer. The goal will be to be able to run this Orc program:

-- Import the site definition
site ExampleBuffer = ExampleBuffer
-- Create a new buffer site
val b = ExampleBuffer()
-- wait for a value in the buffer
b.get()
-- put a value in the buffer
| b.put(3) >> stop
-- Publishes: 3

The first step is to create ExampleBuffer.java. The ExampleBuffer site is actually a discovery site: all it does when called is create and return a reference to a new site (the buffer instance).

public class ExampleBuffer extends EvalSite {
  public Object evaluate(Args args) {
    return new ExampleBufferInstance();
  }
}

Next we must implement ExampleBufferInstance, which defines the behavior of an individual buffer. We can put this class either in its own file or in ExampleBuffer.java, since that is the only class which refers to it directly. How does an instance behave? If we write b.get(), that means that b responds to the message get with a site which we call to get a value from the buffer. So we'll extend DotSite, telling it to respond to the message "get" with a GetMethod site, and likewise for "put". We'll use a linked list to store the actual contents of the buffer.

class ExampleBufferInstance extends DotSite {
  private LinkedList<Object> contents = new LinkedList<Object>();
  public ExampleBufferInstance() {
    addMember("get", new GetMethod());
    addMember("put", new PutMethod());
  }
}

The GetMethod site is implemented as an inner class so it has easy access to the contents of the buffer. In the implementation we first check if there is an item in the buffer. If so, we return it. Otherwise, we must store the token in a queue to be notified when the next item is put in the buffer. We synchronize on the outer object to protect against concurrent modification.

private LinkedList<Token> waiters = new LinkedList<Token>();
private class GetMethod extends Site {
  public void callSite(Args args, Token token) {
    synchronized (ExampleBufferInstance.this) {
      if (!contents.isEmpty()) {
        token.resume(contents.removeFirst());
      } else {
        waiters.add(token);
      } 
    }
  }
}

PutMethod is even simpler since it doesn't need to block. If there is a token waiting for an item, we give it the item. Otherwise we put the item in the buffer. We synchronize on the outer object to protect against concurrent modification. When done we return a dummy "signal" value.

private class PutMethod extends EvalSite {
  public Object evaluate(Args args) {
    Object item = args.getArg(0);
    synchronized (ExampleBufferInstance.this) { 
      if (!waiters.isEmpty()) {
        waiters.removeFirst().resume(item);
      } else {
        contents.add(item);
      } 
    }
    return signal();
  }
}

Putting it all together, here is the entire implementation:

import java.util.LinkedList;

import orc.runtime.sites.EvalSite;
import orc.runtime.sites.DotSite;
import orc.runtime.sites.Site;
import orc.runtime.Token;

public class ExampleBuffer extends EvalSite {
  public Object evaluate(Args args) {
    return new ExampleBufferInstance();
  }
}

class ExampleBufferInstance extends DotSite {
  private LinkedList<Object> contents = new LinkedList<Object>();
  private LinkedList<Token> waiters = new LinkedList<Token>();
    
  private class GetMethod extends Site {
    public void callSite(Args args, Token token) {
      synchronized (ExampleBufferInstance.this) {
        if (!contents.isEmpty()) {
          token.resume(contents.removeFirst());
        } else {
          waiters.add(token);
        } 
      }
    }
  }

  private class PutMethod extends EvalSite {
    public Object evaluate(Args args) {
      Object item = args.getArg(0);
      synchronized (ExampleBufferInstance.this) { 
        if (!waiters.isEmpty()) {
          waiters.removeFirst().resume(item);
        } else {
          contents.add(item);
        } 
      }
      return signal();
    }
  }

  public ExampleBufferInstance() {
    addMember("get", new GetMethod());
    addMember("put", new PutMethod());
  }
}