-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathembedjs.scala
189 lines (162 loc) · 6.6 KB
/
embedjs.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
package org.scala_lang.virtualized.js
import org.scala_lang.virtualized.{CoreDefs, CoreExps}
/**
* JavaScript-specific definitions.
*/
trait JSDefsExps extends CoreDefs {
// definitions, or statements -- their sequencing will be captured when they are converted to Exp's by toAtom
case class IfThenElse[T](c: Exp[Boolean], a: Block[T], b: Block[T]) extends Def[T]
case class VarInit[T](x: Exp[T]) extends Def[T]
case class VarAssign[T](v: Exp[T], x: Exp[T]) extends Def[Unit]
/**
* The representation of a binary operation: we'll only use >= here
*/
case class BinaryOp[T, U](x: Exp[T], op: String, y: Exp[T]) extends Def[U]
/**
* Any T for which there is an implicit Ordering[T] will be converted into a BinaryOps[T],
* so that >= appears to be available on these T's -- calling these operations will
* yield a BinaryOp statement that represents the operation.
*/
trait BinaryOps[T] { val self: Exp[T]
def >=(y: Exp[T]) = BinaryOp[T, Boolean](self, ">=", y)
}
implicit def orderingOps[T: Ordering](x: Exp[T]) = new BinaryOps[T]{ val self = x }
// Obj and Select are Exp's: used on their own (as a statement) in a DSL program,
// they will not generate any code (since toAtom is not called, and thus they are not registered in the current scope)
// TODO: turn them into Def's, but it will work fine as long as they are used as part of Def's
case class Select[T, U](tgt: Exp[U], field: String) extends Exp[T] {
override def refStr: String = tgt.refStr +"."+ field
}
case class Obj[T](fields: Map[String, Exp[_]]) extends Exp[T]
case class Global[T](name: String) extends Exp[T]
case class Invoke[T,U](tgt: Exp[U], name: String, args: List[Exp[_]]) extends Def[T]
}
trait EmbedJS extends JSDefsExps {
// note the return types! toAtom will be used to turn the Def's into Exp's
def __ifThenElse[T](cond: Exp[Boolean], thenp: Exp[T], elsep: Exp[T]): Exp[T] = IfThenElse(cond, reifyBlock(thenp), reifyBlock(elsep))
def __newVar[T](x: Exp[T]): Exp[T] = VarInit(x)
def __assign[T](lhs: Exp[T], rhs: Exp[T]): Exp[Unit] = VarAssign(lhs, rhs)
// marker to trigger __new reification
class JSObj extends Struct
def __new[T](args: (String, Boolean, Exp[T] => Exp[_])*): Exp[T] = new Obj(args map {case (n, mut, rhs) => (n, rhs(null))} toMap)
implicit def selectOps(self: Exp[_ <: JSObj]) = new {
def selectDynamic[T](n: String): Exp[T] = Select(self, n)
}
class DOM
def document: Exp[DOM] = Global[DOM]("document")
def infix_getElementById(x: Exp[DOM], id: Exp[String]): Exp[DOM] = Invoke[DOM,DOM](x, "getElementById", List(id))
def infix_innerHTML(x: Exp[DOM]): Exp[String] = Select(x, "innerHTML")
def infix_foobar(x: Exp[DOM]): Exp[String] = Select(x, "innerHTML")
}
/**
* An example embedded JavaScript program, for which code will be generated by running Test.
* To run in Eclipse, right-click on "Test" below, and select Run As > Scala Application
*/
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"
document.getElementById("drink").innerHTML = allowedDrink
}
}
Example.emitFun("prog")(Example.prog) // output to console
val html = {
<html>
<head>
<title>Scala to JavaScript</title>
<script type="text/javascript">
{ scala.xml.Unparsed(Example.captureOutput(Example.emitFun("prog")(Example.prog))) }
</script>
</head>
<body onLoad="prog();">
<h1>Kim drinks:</h1>
<div id="drink">dunno</div>
</body>
</html>
}
scala.xml.XML.save("test.html", html)
}
/* emitted code: {
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
} */
/**
* Rudimentary JavaScript code generation
*
* render Block, Def, and Expr as JavaScript code (printing to the console)
*/
trait JSCodeGen extends JSDefsExps {
var nesting = 0
var indent = true
def emitValDef[T](s: Sym[T], rhs: String, more: Boolean = false) = {
emitPlain("var " + s.refStr + " = " + rhs, more)
}
def emitPlain(s: String, more: Boolean = false) = {
if (indent) print(" " * (nesting * 2))
if (more) print(s) else println(s)
indent = !more
}
def emitFun[T](name: String)(a: => Exp[T]) = {
emitPlain("function " + name + "() ", true)
emitBlock(reifyBlock(a))
}
def emitBlock[T](a: Block[T], more: Boolean = false, s: Sym[T] = null) = a match {
case Block(stms, e) =>
emitPlain("{", false); nesting += 1
stms foreach { case t: ScopeEntry[t] => emitNode[t](t.sym, t.rhs) }
if(s == null) emitPlain(e.refStr)
else emitPlain(s.refStr + " = " + e.refStr)
nesting -= 1; emitPlain("}", more)
}
def captureOutput(block: =>Unit): String = {
val bstream = new java.io.ByteArrayOutputStream
Console.withOut(new java.io.PrintStream(bstream))(block)
bstream.toString
}
def emitNode[T](s: Sym[T], d: Def[T]): Unit = d match {
case IfThenElse(c,a,b) =>
emitPlain("var " + s.refStr)
emitPlain("if (", true); emitExpr(c); emitPlain(") ", true)
emitBlock(a, true, s)
emitPlain(" else ", true)
emitBlock(b, false, s)
case VarInit(x) =>
emitPlain("var " + s.refStr + " = ", true); emitExpr(x); emitPlain("")
case VarAssign(v, x) =>
emitValDef(s, "(" + v.refStr + " = ", true); emitExpr(x); emitPlain(")")
case BinaryOp(x, op, y) =>
emitValDef(s, "", true); emitExpr(x); emitPlain(" "+ op +" ", true); emitExpr(y); emitPlain("")
case Invoke(x, m, args) =>
emitValDef(s, x.refStr + "." + m + "(", true)
args.init.foreach { e => emitExpr(e); emitPlain(",", true) }
emitExpr(args.last)
//args.map(e => emitExpr(e)).mkString(",") + ")")
emitPlain(")")
}
override def quoteExp[T](x: Exp[T]): String = x match {
case Global(name) => name
case _ => super.quoteExp(x)
}
def emitExpr[T](expr: Exp[T]): Unit = expr match {
case s@Sym(_) => emitPlain(s.refStr, true)
case c@Const(_) => emitPlain(c.refStr, true);
case Global(name) => emitPlain(name, true)
case Select(tgt, field) => emitExpr(tgt); emitPlain("."+field, true)
case Obj(fields) =>
emitPlain("{", true)
if(fields nonEmpty) {
fields.head match { case (n, v) => emitPlain("\""+ n +"\" : ", true); emitExpr(v)}
fields.tail foreach {case (n, v) => emitPlain(",", true); emitPlain("\""+ n +"\" : ", true); emitExpr(v)}
}
emitPlain("}", true)
}
}