HipHop is a DSL dedicated to Web orchestration. HipHop programs are expressed as AST which are evaluated on demand by a HipHop Reactive Machine. HipHop is provided as a Hop library which has to be included in your source files through the library directive, within the Hop module clause:

(module …
   (library hiphop)
   ; other module options
   …)

Syntax

HipHop DSL constructors are suffixed by the & (ampersand) character, to distinguish them from Hop constructs. HipHop use the same prefix, parenthesized as Lisp and Scheme language families. For instance, to pop-up the text "Hello, World!" in HipHop, one can write:

(atom& (alert "Hello, World!"))

The atom& HipHop primitive wraps Hop code to be executed when HipHop decides so. The primitive execution takes no time from the HipHop point of view, as such the HipHop execution waits for the Hop code to terminate.

Definitions

HipHop ASTs are Hop values. As such, they can be computed by Hop code. Hop functions can return HipHop code, enabling the programmer to reuse code:

(define (pop-up& text)
   (atom& (alert text)))

(seq&
   (pop-up& "Hello")
   (pop-up& "World"))

The first code block defines a Hop function named pop-up& returning a HipHop atom& instruction, parameterized by the text argument. We voluntary choose to suffix our function name with an ampersand as this function returns HipHop code.

The second block starts a sequence of two HipHop instructions, using the function defined above. The code is equivalent as repeating twice the (atom& (alert …)) snippet in a seq& block.

Reactive Machines

HipHop instructions forms programs, and such programs are executed by reactive machines. Machines are responsible for isolating HipHop environment from real-world, Hop, environment by taking snapshots of it. Machine executions are driven by Hop programs.

Machines are Hop object that must be instantiated with a HipHop program. Here we create a HipHop machine, bound to the Hop variable M running the "Hello World" example above.

(define M (instantiate::HipHopMachine
             (program (pop-up& "Hello, World!"))))
(hiphop-react! M)

The hiphop-react! function starts a reaction of the HipHop machine, computing its new state, taking into account the current state and the current environment. The call to the hiphop-react! function may be triggered by some event occurring on the web page.

Here we attach an event handler to the <body> element. Each time the element is clicked, the machine will run a reaction:

(add-event-listener! document.body "click"
   (lambda () (hiphop-react! M)))

HipHop Events

Events are the primary mean to drive HipHop executions. HipHop programs react to events presence or absence, and to their actual value. Events may carry values that can be exchanged with Hop. HipHop is really about handling events, which the Web is full of.

Events have a status (present or absent) at each machine reaction. The presence can be set either within HipHop (the event is said to be emitted during the reaction) or input by Hop, before starting a new reaction.

As machines, HipHop Events are Hop object instances. Pure (value-less) events are instances of the HipHopEventPure class.

(define my-event (instantiate::HipHopEventPure))

With a global machine M, we can input the event my-event each time a button is pressed.

(<BUTTON>
   :onclick (lambda () (hiphop-input-and-react! M my-event))
   "click me!")

HipHop knows about events. To pop-up out "Hello, World!" each time the button is pressed, we need to write a program that wait for my-event to occur before our atom&:

(every& (now& my-event)
   (pop-up& "You clicked me!"))

The every& instruction repeats its body (pop-up& …) each time the condition (now& my-event) is true, i.e. each reaction where my-event is present.

The construction now& takes an event as argument and returns a true is the event is present, and false otherwise. Those Boolean values may be combined using Hop's Boolean operators.

(if& (and (now& high-temperature) (not (now& window-is-opened)))
     (atom& (go-open-the-window)))

HipHop programs are easy to read. The above snippet tells that we want to open the window if the window was not opened before, and the temperature is too high.

Events have memory. Analogous to now&, HipHop provided pre& that reports if an event was present in the previous reaction. For instance, we want to turn off the air conditioner when a window is opened:

(if& (and (now& window-is-opened) (not (pre& window-is-opened)))
     (atom& (turn-off-air-conditioner!)))

To access events' values, HipHop provides both val& and preval& to respectively access the event's value in the current reaction or in the previous reaction.

Output events

Emitted events are outputs of the reactive machine. Event listener can be attached to output events to trigger Hop code execution when a reaction ends. Using output events to start specific actions instead of using atom& directly (as we did above) may be necessary in situations where we need to start a long running computation.

Let's assume we want to start the air conditioner when both the indoor and outdoor temperatures are greater than 25°C, we can use the following program:

(define indoor-temp (instantiate::HipHopEventValued (name "in")))
(define outdoor-temp (instantiate::HipHopEventValued (name "out")))
(define start-ac (instantiate::HipHopEventPure))
(define threshold 25)
(define machine
   (instantiate::HipHopMachine
      (program
         (if& (and (> (val& indoor-temp) threshold)
                   (> (val& outdoor-temp) threshold))
              (emit& start-ac)))))

(add-event-listener! indoor-sensor "temp"
   (lambda (event) (hiphop-input-and-react! machine indoor-temp event.value)))
(add-event-listener! outdoor-sensor "temp"
   (lambda (event) (hiphop-input-and-react! machine outdoor-temp event.value)))
(add-event-listener! machine start-ac
   (lambda (e) (really-start-the-air-conditioner!)))

Line 1 to 3, we create the HipHop events we will use in the program. Line 4 defines a Hop variable. Line 5, we define the machine, specifying a simple program that will emit the event start-ac (line 10) when both events indoor-temp (line 8) and outdoor-temp (line 9) have a value strictly greater than threshold.

Line 12-13 and 14-15, we register two events handlers to our temperature sensors. When either one of the two sensor have new data, we start a reaction of the machine with the sensor's temperature. Last, line 16-17, we register an event listener to execute really-start-the-air-conditioner procedure when the start-ac event is actually emitted in the HipHop machine.

This section describes all HipHop statements provided by the standard library.

Grammar

In the sequel, the square bracket [,] characters groups arguments. We use the traditional regexp quantifiers ?,*,+ to denote respectively optional, possibly empty and non-empty argument lists.

Here is the list of HipHop expressions and statements:

stmt ::=
    core-stmt
  | derived-stmt

core-stmt ::=
    (nothing&)
  | (emit& event host-expr*)
  | (atom& host-expr)
  | (pause&)
  | (if& host-expr stmt stmt?)
  | (seq& stmt+)
  | (loop& stmt+)
  | (par& stmt+)
  | (genpar& (binding+) stmt+)
  | (dyngenpar& (binding+) :when await-expr stmt+)
  | (suspend& [:immediate? bool]? await-expr stmt+)
  | (trap& identifier stmt+)
  | (exit& identifier)
  | (local& (binding+) stmt+)

derived-stmt ::=
    (halt&)
  | (sustain& event host-expr*)
  | (cond& clause* (else stmt+)?)
  | (await& [:immediate? bool]? await-expr stmt*)
  | (abort& [:immediate? bool]? await-expr handler? stmt+)
  | (until& [:immediate? bool]? await-expr handler? stmt+)
  | (loop-each& [:immediate? bool]? await-expr stmt+)
  | (every& [:immediate? bool]? await-expr stmt+)

host-expr ::=
    (now& event)
  | (pre& event)
  | (val& event hop-expr*)
  | (preval& event hop-expr*)
  | hop-expression
  | (host-expr+)

binding ::=
    (identifier host-expr)

clause ::=
    (host-expr stmt+)

await-clause ::=
    (await-expr stmt+)

handler ::=
    (handler& stmt)

await-expr ::=
    host-expr
  | (times& host-expr host-expr)

Core Statements

The nothing& statement does nothing and terminates instantaneously. It is the HipHop no-op.

The emit& statement emits its event with values determined by the rest of its argument. It terminates instantaneously.

The atom& statement calls Hop to executes its argument as a Hop expression; it is instantaneous, which means that its Hop argument execution time should be kept negligible in practice.

The pause& statement delays execution by one reaction. When executed, it pauses for the reaction and terminates at the next reaction.

The if& statement instantaneously evaluates its test. If the result is true, it immediately starts its second HipHop argument and behaves as it from then on; otherwise, it immediately starts its third argument. Notice that these arguments can be arbitrary instantaneous or delayed HipHop statements. Termination of the if& statement is instantaneously triggered by termination of the selected branch.

The seq& statement executes its arguments in order: the first HipHop statement starts immediately when the sequence starts; when it terminates, be it immediately or in a delayed way, the second argument is immediately started, etc. For instance, (seq& (emit& A) (emit& B)) immediately emits A and B, which are seen as simultaneous within the reaction, while (seq& (emit& A) (pause&) (emit& B)) emits A and B in two successive reactions.

The loop& statement is a loop-forever, equivalent to the infinite sequential repetition of its argument statements, themselves implicitly evaluated in sequence. For instance, (loop& (pause&) (emit& A)) waits for the next reaction and then keeps emitting A at each reaction. It could be infinitely expanded into (seq& (pause&) (emit& A) (pause&) (emit& A) ...). A loop& can only be exited from within using the exit& statement below. It can also be killed from outside by statements such as until& and abort&+

The par& statement starts its arguments concurrently and terminates at the reaction where the last of them terminates. Therefore, (par& (await& A) (await B)) terminates when both A and B have been received. Remember that all arms of a par& statement see all statuses and values of all (visible) events in exactly the same way.

The genpar& and dyngenpar& statements create dynamic programs. They are described separately in the dynamicity section.

The suspend& statement immediately starts its body. At all next instants where its body is alive, it suspends the execution of its body for the reaction when its condition is true. The suspend& statements terminates if its body is executed and terminates. For instance:

(suspend& (now& A)
   (loop&
      (emit& B)
      (pause&)))

emits B at first instant and at all subsequent instants where A is absent.

The trap& statement defines a named exit point for its body.

The exit& statement provokes immediate termination of the corresponding trap& statement, as well as immediate termination of all active statements within the trap& body, which do normally receive the control at that instant.

The local& statement declares local events in the first argument list. Their scope is the body, which is the implicitly seq& sequential list of the remaining HipHop arguments. They are not visible from Hop. A local& statement terminates when its body does.

Derived Statements

The halt& statement pauses forever.

The sustain& statement keeps emitting its event at each reaction.

The cond& statement extends if& as usual in Scheme.

The await& statement waits for its expression to become true and terminates.

The abort& statement instantaneously kills its body when its condition becomes true, not passing the control to its body in this reaction; this is what we call strong abortion.

The until& statement instantaneously kills its body when its condition becomes true, but only at the end of the reaction, passing the control to its body for the last time at that reaction as for an exited trap&; this is what we call weak abortion.

The loop-each& statement immediately starts its body, and then strongly kills it and restarts it immediately whenever its condition becomes true.

The every& statement is similar but starts by waiting for the condition instead of immediately starting its body.

Dynamic Statements

HipHop introduces dynamic primitives to generate programs depending on previous computations. Using Hop, one can already generate programs from statically computed lists, assuming fetch& is HipHop code that fetch some data using an URL:

(define program&
   (par& (map (lambda (url) (fetch& url))
              (list "http://hop.inria.fr" "http://fr.wikipedia.org"))))

which is equivalent to the following code:

(define program&
   (par& (fetch& "http://hop.inria.fr")
         (fetch& "http://fr.wikipedia.org")))

The above idiom works fine with Hop lists, which are known at program elaboration. However, the list may be computed as a HipHop run-time value (i.e. an event value. In such a situation, we need a specific statement to dynamically create parallelism during run-time. This is the purpose of genpar&. Assuming with have an urls event generated by Hop (for instance, a user input box), we could re-write the fetch example as follows:

(define program&
   (seq&
      (emit& urls (list "http://hop.inria.fr" "http://fr.wikipedia.org"))
      (genpar& ((url (val& urls)))
         (fetch& url))))

The above snippet is actually equivalent to the two previous ones, but it is important to notice that in the first two snippets, the par& statement statically has two branches, while in the third snippet, it has zero branch. The two branches will be dynamically appended to the (hidden) par& statement when the genpar& statement is executed. Once the genpar& branches are appended, the statement is said to be expanded.

The expansion of a genpar& only occurs once. Once expanded it behaves exactly as if a plain par& has been used in place of genpar&.

Incremental Parallelism

We also introduced dyngenpar& to provide a continuously expanding genpar&-like behavior. For instance, we want to fetch URLs at each reaction were new URLs are obtained (maybe from a remote service), while the previously started fetch& programs may not have finished yet. (Using every& and genpar& is not suitable, as every& would kill unfinished programs):

(define (program& urls::HipHopEvent)
   (dyngenpar& ((url (val& urls))) :when (now& urls)
      (fetch& url)))

The dyngenpar& statement's syntax is similar to that of genpar&; we add a :when attribute which tells HipHop when to compute new expansions.

Hooks

Hooks provide user-defined actions that are triggered by the HipHop run-time. Hooks can be attached to any HipHop standard (library-provided) constructor.

A hook is a piece of code that can be attached through a dedicated attribute. The name of the attribute specify the kind of event that will trigger the hook execution. Here is an example for a abort&:

(let& ((http (make-client-socket "hop.inria.fr" 80)))
   (abort& :when (now& cancel)
     :onkill (socket-close http)
     (use-data-from-socket& http)))

Using let&, we create a socket to hop.inria.fr when the code is executed, then run a HipHop program to get the data from the socket. However, we enable the user to cancel the request through the cancel event (e.g. emitted when the user clicks a big red button). On abortion, the abort& body is killed, triggering the execution of the (socket-close …) code.

Here is the list of supported hooks:

  • :onstart: code to be executed when the associated statement is executed for the first time.
  • :onpause: code to be executed when the associated statement (or its body) pauses (see pause&).
  • :onresume: code to be executed when the associated statement is resumed (i.e. is has been previously paused.
  • :onterminate: code to be executed when the associated statement terminates normally.
  • :onexit: code to be executed when the associated statement (or its body) exits (see exit&).
  • :onkill: code to be executed when the associated statement is killed or aborted.
  • :onsuspend: code to be executed when the associated statement is suspended by a parent suspend& node.

Hooks implementation follows the DOM API. HipHop implements add-event-listener! with the above events on HipHop statements:

(define code
   (loop&
      (atom& :id "hello" (alert "Hello, World!"))
      (pause&)))

(hiphop-add-event-listener!
   (hiphop-get-element-by-id code "hello")
   "start" (lambda () (alert "Goodbye, World!")))

Note: if you define your own HipHop forms, you have to add support for hooks. WIP: a special form define-hiphop (in the manner of define-tag in Hop) is getting introduced, which may automatically add support for hooks in user-defined HipHop constructs.