Skip to content

Embedding JavaScript

Adriaan Moors edited this page Oct 10, 2011 · 12 revisions

Embedding JavaScript

This tutorial shows how to deal with statement-based languages, and more specifically how to capture the sequencing of statements.

To illustrate this, we'll show how to embed JavaScript programs like:

var kim = {"name" : "kim","age" : 20}
kim.age = 21
if (x1.age >= 21) {
  var allowedDrink = "beer"
} else {
  var allowedDrink = "milk"
}
allowedDrink

By the end of this tutorial, you'll know how the following embedding relates to the above JavaScript program.

object Test extends App  {
  object Example extends EmbedJS with JSCodeGen { def prog = {
    var kim = new JSObj { val name = "kim"; val age = 20 }
    kim.age = 21
    var allowedDrink = if (kim.age >= 21) "beer" else "milk"
    allowedDrink
  }}

  Example.emitBlock(Example.prog)
}

The full source for this tutorial is available on GitHub.

Statements

Virtualized Scala does not provide support for reifying (or virtualising) the sequencing of statements. Instead, we'll use mutable state and Scala's run-time semantics to capture the sequencing of statements in embedded domain programs.

To see how this works, let's consider the following fragment of our example.

var kim = ...
kim.age = 21

The virtualizing Scala compiler rewrites this to:

val kim = __newVar(...)
__assign(selectOps(kim).selectDynamic("age"), liftInt(21))

(See the reference for the details.)

These methods are defined as follows (see the full source for details):

def __newVar[T](x: Exp[T]): Exp[T] = VarInit(x)
def __assign[T](lhs: Exp[T], rhs: Exp[T]): Exp[Unit] = VarAssign(lhs, rhs)

What you don't see here is that VarInit and VarAssign are not expressions (Exp[T]); they are statements (Def[T])! The infrastructure for dealing with statements provides the crucial missing ingredient, `toAtom`: an implicit conversion from Exp[T] to Def[T]. Let's first make the conversion explicit:

def __newVar[T](x: Exp[T]): Exp[T] = toAtom(VarInit(x))
def __assign[T](lhs: Exp[T], rhs: Exp[T]): Exp[Unit] = toAtom(VarAssign(lhs, rhs))

The crucial insight is that the order in which these toAtom's are executed corresponds to the order in which the statements occur in the embedded JavaScript program above. This tells us all we need to know about sequencing of statements in the embedded program!

To drive the point home, inlining __newVar and __assign peels off the last layer of syntactic sugar and indirection from our initial fragment:

val kim = toAtom(VarInit(...))
toAtom(VarAssign(selectOps(kim).selectDynamic("age"), liftInt(21)))

Running this Scala program creates an accurate representation of the embedded JavaScript program, as the DSL implementation keeps track of the current scope of the domain program, and toAtom populates this scope in the order in which it is called. On each invocation, toAtom creates a fresh symbol and enters it into the current scope. The symbol links the new entry in the current scope to the original expression.

For more details, see the implementation of CoreDefs.

The remainder of the embedding uses the same mechanisms as the SQL embedding.

The generated code looks as follows:

{
  var x1 = {"name" : "kim","age" : 20}
  var x2 = (x1.age = 21)
  if (x1.age >= 21) {
    var x3 = "beer"
  } else {
    var x3 = "milk"
  }
  var x4 = x3
  x4
}

The full source completes the example by generating a html page so you can easily run the generated code in your browser.