├── .gitignore
├── .jvmopts
├── .travis.yml
├── LICENSE
├── README.md
├── build.sbt
├── jscala-annots
└── src
│ └── main
│ └── scala
│ └── org
│ └── jscala
│ └── JavaScript.scala
├── jscala-examples
├── javascript-tetris
│ ├── LICENSE
│ ├── README.md
│ ├── index.html
│ ├── stats.js
│ └── texture.jpg
└── src
│ ├── main
│ └── scala
│ │ └── org
│ │ └── jscalaexample
│ │ ├── AES.scala
│ │ ├── JScalaExample.scala
│ │ └── Tetris.scala
│ └── test
│ └── scala
│ └── org
│ └── jscalaexample
│ ├── AesTest.scala
│ ├── JavascriptPrinterTest.scala
│ └── ScalaToJsConverterTest.scala
├── jscala
└── src
│ └── main
│ └── scala
│ └── org
│ └── jscala
│ ├── BasisConverter.scala
│ ├── CollectionConverter.scala
│ ├── JavascriptPrinter.scala
│ ├── JsGlobal.scala
│ ├── MacroHelpers.scala
│ ├── ScalaToJsConverter.scala
│ ├── SyntaxConverter.scala
│ ├── model.scala
│ └── package.scala
└── project
└── build.properties
/.gitignore:
--------------------------------------------------------------------------------
1 | *.class
2 | *.log
3 |
4 | # sbt specific
5 | dist/*
6 | target/
7 | lib_managed/
8 | src_managed/
9 | project/boot/
10 | project/plugins/project/
11 |
12 | # Scala-IDE specific
13 | .scala_dependencies
14 | .project
15 | .classpath
16 | .cache
17 | .cache-main
18 | .settings/
19 |
20 | .idea/
21 | .idea_modules/
--------------------------------------------------------------------------------
/.jvmopts:
--------------------------------------------------------------------------------
1 | -Xms512M
2 | -Xmx4096M
3 | -Xss2M
4 | -XX:MaxMetaspaceSize=1024M
5 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: scala
2 | scala:
3 | - "2.13.2"
4 | jdk:
5 | - oraclejdk11
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2013 Alexander Nemish.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | JScala
2 | ======
3 |
4 | Scala macro that produces JavaScript from Scala code. Let it be type safe!
5 |
6 | [](http://travis-ci.org/nau/jscala)
7 |
8 | [](https://gitter.im/nau/jscala?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
9 |
10 | Supported Features:
11 | ===================
12 | * Variable definitions, basic unary and binary operations
13 | * Named and anonymous functions
14 | * Scala Arrays/Seq as JavaScript Array literals
15 | * Scala Map and anonymous classes as JavaScript object
16 | * if, while, for..in and for statements
17 | * Scala if as an expression (e.g. val a = if (true) 1 else 2)
18 | * Scala match as JavaScript switch
19 | * Basic Scala class/trait definition to JavaScript object definition translation
20 | * Global JavaScript functions (parseInt etc)
21 | * Basic Browser objects (window, history, location etc)
22 | * Basic HTML DOM objects (Document, Element, Attribute, Node, NodeList etc)
23 | * Raw JavaScript inclusion
24 | * Values and function call injections from your Scala code
25 | * Generated JavaScript eval using Java ScriptEngine
26 | * Pretty printing and compression using YUI compressor
27 | * Basic @Javascript macro annotation support
28 | * Basic toJson/fromJson macros
29 | * @Typescripted annotation
30 |
31 | Examples
32 | ========
33 |
34 | This Scala code has no meaning but shows basic ideas:
35 |
36 | ```scala
37 | val replacement = "text"
38 | val js = javascript {
39 | window.setTimeout(() => {
40 | val r = new RegExp("d.*", "g")
41 | class Point(val x: Int, val y: Int)
42 | val point = new Point(1, 2)
43 | def func(i: String) = r.exec(i)
44 | val list = document.getElementById("myList2")
45 | val map = collection.mutable.Map[String, String]()
46 | if (typeof(map) == "string") {
47 | for (idx <- 0 until list.attributes.length) {
48 | val attr = list.attributes.item(idx).asInstanceOf[Attribute]
49 | map(attr.name) = func(attr.textContent).as[String]
50 | }
51 | } else {
52 | val obj = new {
53 | val field = 1
54 | def func2(i: Int) = "string"
55 | }
56 | val links = Array("https://github.com/nau/scala")
57 | for (link <- links) {
58 | include("var raw = 'JavaScript'")
59 | console.log(link + obj.func2(obj.field) + point.x)
60 | }
61 | window.location.href = links(0).replace("scala", "jscala")
62 | }
63 | }, 1000)
64 | }
65 | println(js.asString)
66 | ```
67 |
68 | It will print
69 |
70 | ```javascript
71 | window.setTimeout(function () {
72 | var r = new RegExp("d.*", "g");
73 | function Point(x, y) {
74 | this.x = x;
75 | this.y = y;
76 | };
77 | var point = new Point(1, 2);
78 | function func(i) {
79 | return r.exec(i);
80 | };
81 | var list = document.getElementById("myList2");
82 | var map = {};
83 | if (typeof(map) == "string") for (var idx = 0; idx < list.attributes.length; ++idx) {
84 | var attr = list.attributes.item(idx);
85 | map[attr.name] = func(attr.textContent);
86 | } else {
87 | var obj = {
88 | field: 1,
89 | func2: function (i) {
90 | return "string";
91 | }
92 | };
93 | var links = ["https://github.com/nau/scala"];
94 | for (var linkIdx = 0, link = links[linkIdx]; linkIdx < links.length; link = links[++linkIdx]) {
95 | var raw = 'JavaScript';
96 | console.log((link + obj.func2(obj.field)) + point.x);
97 | };
98 | window.location.href = links[0].replace("scala", "jscala");
99 | };
100 | }, 1000)
101 | ```
102 |
103 | How To Use
104 | ==========
105 |
106 | In your build.sbt add
107 |
108 | scalaVersion := "2.13.2"
109 |
110 | libraryDependencies += "org.jscala" %% "jscala-macros" % "0.5"
111 |
112 | If you want to try the latest snapshot:
113 |
114 | scalaVersion := "2.13.2"
115 |
116 | resolvers += Resolver.sonatypeRepo("snapshots")
117 |
118 | libraryDependencies += "org.jscala" %% "jscala-macros" % "0.6-SNAPSHOT"
119 |
120 | In your code
121 |
122 | ```scala
123 | import org.jscala._
124 | val js = javascript { ... }
125 | println(js.asString)
126 | println(js.compress)
127 | println(js.eval())
128 | ```
129 |
130 | That's it!
131 |
132 | How To Try Macro Annotations
133 | ============================
134 | In your build.sbt add
135 |
136 | scalaVersion := "2.13.2"
137 |
138 | libraryDependencies += "org.jscala" %% "jscala-macros" % "0.5"
139 |
140 | libraryDependencies += "org.jscala" %% "jscala-annots" % "0.5"
141 |
142 | In your code
143 |
144 | ```scala
145 | import org.jscala._
146 | @Javascript class User(val name: String, val id: Int)
147 | @Javascript(json = false) class Greeter {
148 | def hello(u: User) {
149 | print("Hello, " + u.name + "\n")
150 | }
151 | }
152 | // Run on JVM
153 | val u1 = new User("Alex", 1)
154 | val greeter = new Greeter()
155 | greeter.hello(u1) // prints "Hello, Alex"
156 | val json = u1.js.json.asString
157 | val main = javascript {
158 | val u = new User("nau", 2)
159 | val u1Json = eval("(" + inject(json) + ")").as[User] // read User from json string generated above
160 | val t = new Greeter()
161 | t.hello(u)
162 | t.hello(u1Json)
163 | }
164 | val js = User.jscala.javascript ++ Greeter.jscala.javascript ++ main // join classes definitions with main code
165 | js.eval() // run using Rhino
166 | println(js.asString) // prints resulting JavaScript
167 | ```
168 |
169 | Run it and you'll get
170 |
171 | ```javascript
172 | Hello, Alex
173 |
174 | Hello, nau
175 | Hello, Alex
176 | {
177 | function User(name, id) {
178 | this.name = name;
179 | this.id = id;
180 | };
181 | function Greeter() {
182 | this.hello = function (u) {
183 | print(("Hello, " + u.name) + "\n");
184 | };
185 | };
186 | var u = new User("nau", 2);
187 | var u1Json = eval(("(" + "{\n \"name\": \"Alex\",\n \"id\": 1\n}") + ")");
188 | var t = new Greeter();
189 | t.hello(u);
190 | t.hello(u1Json);
191 | }
192 | ```
193 |
194 | See AES example:
195 |
196 | https://github.com/nau/jscala/blob/master/jscala-examples/src/main/scala/org/jscalaexample/AES.scala
197 |
198 | It's AES Scala implementation which is used for both Scala and JavaScript encryption/decryption.
199 |
200 |
201 | How To Build And Play Some Tetris
202 | =================================
203 |
204 | Make sure you have at least -Xmx750Mb for your sbt.
205 | Don't know why but it takes up to 700Mb to compile _jscala-macros_ project.
206 |
207 | In sbt shell run `tetris` task.
208 | It will compile and generate _tetris.js_ file in _jscala-examples/javascript-tetris_ and open Tetris game in your browser.
209 | Tetris is fully written in Scala and translates to JavaScript mostly literally.
210 |
211 | Tetris sources are here: [jscala-examples/src/main/scala/org/jscalaexample/Tetris.scala](https://github.com/nau/jscala/blob/master/jscala-examples/src/main/scala/org/jscalaexample/Tetris.scala)
212 |
213 | Planned Features
214 | ================
215 |
216 | * Language support improvements
217 | * Web frameworks support: Play, Lift
218 |
219 | Feedback
220 | ========
221 |
222 | Any feedback is very welcome!
223 |
224 | You can use [JScala mailing list](https://groups.google.com/forum/#!forum/jscala-user) if you have any questions.
225 |
226 | Or simply ask me on Twitter: [@atlanter](https://twitter.com/atlanter)
227 |
--------------------------------------------------------------------------------
/build.sbt:
--------------------------------------------------------------------------------
1 | import java.awt.Desktop
2 |
3 | lazy val ossSnapshots = "Sonatype OSS Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots/"
4 | lazy val ossStaging = "Sonatype OSS Staging" at "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
5 | lazy val buildSettings = Seq(
6 | organization := "org.jscala",
7 | version := "0.6-SNAPSHOT",
8 | crossScalaVersions := Seq("2.11.12", "2.12.11", "2.13.2"),
9 | scalaVersion := "2.13.2",
10 | resolvers += Resolver.sonatypeRepo("snapshots"),
11 | // usePgpKeyHex("2B6E37353BE8BF8ED89B858DBC5373CC0297421A"),
12 | publishTo := { Some( if (version.value.trim endsWith "SNAPSHOT") ossSnapshots else ossStaging)},
13 | publishMavenStyle := true,
14 | publishArtifact in Test := false,
15 | pomIncludeRepository := (_ => false),
16 | pomExtra := extraPom,
17 | scalacOptions ++= Seq(
18 | "-deprecation",
19 | "-feature",
20 | "-unchecked",
21 | // "-Ystatistics",
22 | // "-verbose",
23 | "-language:_"
24 | ),
25 | Compile / scalacOptions ++= {
26 | CrossVersion.partialVersion(scalaVersion.value) match {
27 | case Some((2, n)) if n >= 13 => "-Ymacro-annotations" :: Nil
28 | case _ => Nil
29 | }
30 | },
31 | libraryDependencies ++= {
32 | CrossVersion.partialVersion(scalaVersion.value) match {
33 | case Some((2, n)) if n >= 13 => Nil
34 | case _ => compilerPlugin("org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.full) :: Nil
35 | }
36 | }
37 | )
38 |
39 |
40 | def extraPom =
41 | http://jscala.org
42 |
43 |
44 | MIT
45 | http://opensource.org/licenses/MIT
46 | repo
47 |
48 |
49 |
50 | git@github.com:nau/jscala.git
51 | scm:git:git@github.com:nau/jscala.git
52 |
53 |
54 |
55 | nau
56 | Alexander Nemish
57 | http://github.com/nau
58 |
59 |
60 |
61 | val tetris = taskKey[Unit]("Translates tetris Scala code to Javascript and runs the game")
62 |
63 | lazy val root = (project in file(".")).settings(buildSettings: _*).settings(
64 | name := "jscala",
65 | crossScalaVersions := Nil
66 | ) aggregate(jscala, jscalaAnnots, examples)
67 |
68 | lazy val jscala = (project in file("jscala")).settings(buildSettings:_*).settings(
69 | name := "jscala-macros",
70 | libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value % "provided",
71 | libraryDependencies += "org.scala-js" % "scalajs-library_2.11" % "0.6.15",
72 | libraryDependencies += "com.yahoo.platform.yui" % "yuicompressor" % "2.4.8" % "provided"
73 | )
74 |
75 | lazy val jscalaAnnots = (project in file("jscala-annots")).settings(buildSettings: _*).settings(
76 | name := "jscala-annots",
77 | libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value % "provided"
78 | ).dependsOn(jscala)
79 |
80 | lazy val examples: Project = (project in file("jscala-examples")).settings(buildSettings: _*).settings(
81 | name := "jscala-examples",
82 | tetris := {
83 | runner.value.run(
84 | "org.jscalaexample.Tetris",
85 | Attributed.data((Runtime / fullClasspath).value), Seq((baseDirectory.value / "javascript-tetris" / "tetris.js").toString),
86 | streams.value.log
87 | )
88 | Desktop.getDesktop.browse(baseDirectory.value / "javascript-tetris" / "index.html" toURI)
89 | },
90 | libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.8" % "test",
91 | libraryDependencies += "org.scalacheck" %% "scalacheck" % "1.14.3" % "test",
92 | libraryDependencies += "org.scala-js" % "scalajs-dom_sjs0.6_2.11" % "0.9.1",
93 | libraryDependencies += "be.doeraene" % "scalajs-jquery_sjs0.6_2.11" % "0.9.1",
94 | libraryDependencies += "com.typesafe.play" %% "play-json" % "2.7.4",
95 | libraryDependencies += "com.yahoo.platform.yui" % "yuicompressor" % "2.4.8"
96 | ).dependsOn(jscalaAnnots)
97 |
--------------------------------------------------------------------------------
/jscala-annots/src/main/scala/org/jscala/JavaScript.scala:
--------------------------------------------------------------------------------
1 | package org.jscala
2 |
3 | import scala.annotation.StaticAnnotation
4 | import scala.language.experimental.macros
5 | import scala.language.implicitConversions
6 | import scala.reflect.macros.whitebox
7 |
8 | object MacroAnnotations {
9 |
10 | class JavascriptAnnotation [C <: whitebox.Context](val c: C, debug: Boolean) {
11 | import c.universe._
12 | def transform(annottees: c.Expr[Any]*): c.Expr[Any] = {
13 | val inputs = annottees.map(_.tree).toList
14 | val expandees = inputs match {
15 | case (cd@ClassDef(mods, name, tparams, tpl@Template(parents, sf, body))) :: comp =>
16 | val javascriptMacro = if (debug) TermName("javascriptDebug") else TermName("javascript")
17 | val jsDef = if (mods hasFlag Flag.CASE) EmptyTree
18 | else DefDef(Modifiers(), TermName("javascript"), List(), List(), Ident(TypeName("JsAst")),
19 | Apply(Select(Select(Ident(TermName("org")), TermName("jscala")), javascriptMacro), List(Block(
20 | List(ClassDef(mods, name, List(), Template(parents, noSelfType, body))), Literal(Constant(()))))))
21 | val cd1 = ClassDef(mods, name, tparams, Template(parents, sf, body))
22 | val companion = comp match {
23 | case Nil => q"""object ${name.toTermName} { object jscala {
24 | $jsDef
25 | }
26 | }"""
27 | case List(co@ModuleDef(mods, name, Template(p, t, impl))) => ModuleDef(mods, name, Template(p, t, impl :+ jsDef))
28 | }
29 |
30 | List(cd1, companion)
31 | case _ => c.abort(c.enclosingPosition, "Javascript annotation is only supported on class/trait definitions")
32 | }
33 | if (debug) println(s"Expandees: $expandees")
34 | c.Expr[Any](Block(expandees, Literal(Constant(()))))
35 | }
36 | }
37 |
38 | def annotationImpl(c: whitebox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
39 |
40 | var debug = false
41 | /*
42 | c.macroApplication match {
43 | case Apply(Select(Apply(Select(New(Ident(js)), termNames.CONSTRUCTOR), args), _), _) if js.decodedName.toString == "Javascript" =>
44 | args match {
45 | case List(Literal(Constant(dbg: Boolean))) => debug = dbg
46 | case _ =>
47 | args.foreach {
48 | case AssignOrNamedArg(Ident(dbg), Literal(Constant(v: Boolean))) if dbg.decodedName.toString == "debug" => debug = v
49 | case _ =>
50 | }
51 | }
52 | case _ => c.warning(c.enclosingPosition, "Can't parse @Javascript annotation arguments")
53 | }
54 | */
55 |
56 | new JavascriptAnnotation[c.type](c, debug).transform(annottees:_*)
57 | }
58 | }
59 |
60 | class Javascript(val debug: Boolean = false) extends StaticAnnotation {
61 | def macroTransform(annottees: Any*): Any = macro MacroAnnotations.annotationImpl
62 | }
63 |
64 | class Transient extends StaticAnnotation
--------------------------------------------------------------------------------
/jscala-examples/javascript-tetris/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2011, 2012, 2013 Jake Gordon and contributors
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
20 |
21 |
--------------------------------------------------------------------------------
/jscala-examples/javascript-tetris/README.md:
--------------------------------------------------------------------------------
1 | Javascript Tetris
2 | =================
3 |
4 | An HTML5 Tetris Game
5 |
6 | * [play the game](http://codeincomplete.com/projects/tetris/)
7 | * read a [blog article](http://codeincomplete.com/posts/2011/10/10/javascript_tetris/)
8 | * view the [source](https://github.com/jakesgordon/javascript-tetris)
9 |
10 | >> _*SUPPORTED BROWSERS*: Chrome, Firefox, Safari, Opera and IE9+_
11 |
12 | FUTURE
13 | ======
14 |
15 | * menu
16 | * animation and fx
17 | * levels
18 | * high scores
19 | * touch support
20 | * music and sound fx
21 |
22 |
23 | License
24 | =======
25 |
26 | [MIT](http://en.wikipedia.org/wiki/MIT_License) license.
27 |
28 |
29 |
--------------------------------------------------------------------------------
/jscala-examples/javascript-tetris/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Javascript Tetris
5 |
25 |
26 |
27 |
28 |
29 |
30 |
36 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/jscala-examples/javascript-tetris/stats.js:
--------------------------------------------------------------------------------
1 | // stats.js r6 - http://github.com/mrdoob/stats.js
2 | var Stats=function(){function s(a,g,d){var f,c,e;for(c=0;c<30;c++)for(f=0;f<73;f++)e=(f+c*74)*4,a[e]=a[e+4],a[e+1]=a[e+5],a[e+2]=a[e+6];for(c=0;c<30;c++)e=(73+c*74)*4,cFPS';k.appendChild(d);a=document.createElement("canvas");a.width=74;a.height=30;a.style.display="block";a.style.marginLeft="3px";k.appendChild(a);
5 | m=a.getContext("2d");m.fillStyle="rgb("+b.fps.bg.r+","+b.fps.bg.g+","+b.fps.bg.b+")";m.fillRect(0,0,a.width,a.height);y=m.getImageData(0,0,a.width,a.height);f=document.createElement("div");f.style.backgroundColor="rgb("+Math.floor(b.ms.bg.r/2)+","+Math.floor(b.ms.bg.g/2)+","+Math.floor(b.ms.bg.b/2)+")";f.style.padding="2px 0px 3px 0px";f.style.display="none";g.appendChild(f);c=document.createElement("div");c.style.fontFamily="Helvetica, Arial, sans-serif";c.style.textAlign="left";c.style.fontSize=
6 | "9px";c.style.color="rgb("+b.ms.fg.r+","+b.ms.fg.g+","+b.ms.fg.b+")";c.style.margin="0px 0px 1px 3px";c.innerHTML='MS';f.appendChild(c);a=document.createElement("canvas");a.width=74;a.height=30;a.style.display="block";a.style.marginLeft="3px";f.appendChild(a);o=a.getContext("2d");o.fillStyle="rgb("+b.ms.bg.r+","+b.ms.bg.g+","+b.ms.bg.b+")";o.fillRect(0,0,a.width,a.height);B=o.getImageData(0,0,a.width,a.height);try{performance&&performance.memory&&performance.memory.totalJSHeapSize&&
7 | (t=3)}catch(G){}h=document.createElement("div");h.style.backgroundColor="rgb("+Math.floor(b.mb.bg.r/2)+","+Math.floor(b.mb.bg.g/2)+","+Math.floor(b.mb.bg.b/2)+")";h.style.padding="2px 0px 3px 0px";h.style.display="none";g.appendChild(h);i=document.createElement("div");i.style.fontFamily="Helvetica, Arial, sans-serif";i.style.textAlign="left";i.style.fontSize="9px";i.style.color="rgb("+b.mb.fg.r+","+b.mb.fg.g+","+b.mb.fg.b+")";i.style.margin="0px 0px 1px 3px";i.innerHTML='MB';
8 | h.appendChild(i);a=document.createElement("canvas");a.width=74;a.height=30;a.style.display="block";a.style.marginLeft="3px";h.appendChild(a);q=a.getContext("2d");q.fillStyle="#301010";q.fillRect(0,0,a.width,a.height);E=q.getImageData(0,0,a.width,a.height);return{domElement:g,update:function(){u++;j=(new Date).getTime();n=j-F;z=Math.min(z,n);A=Math.max(A,n);s(B.data,Math.min(30,30-n/200*30),"ms");c.innerHTML=''+n+" MS ("+z+"-"+A+")";o.putImageData(B,0,0);F=j;if(j>
9 | v+1E3){l=Math.round(u*1E3/(j-v));w=Math.min(w,l);x=Math.max(x,l);s(y.data,Math.min(30,30-l/100*30),"fps");d.innerHTML=''+l+" FPS ("+w+"-"+x+")";m.putImageData(y,0,0);if(t==3)p=performance.memory.usedJSHeapSize*9.54E-7,C=Math.min(C,p),D=Math.max(D,p),s(E.data,Math.min(30,30-p/2),"mb"),i.innerHTML=''+Math.round(p)+" MB ("+Math.round(C)+"-"+Math.round(D)+")",q.putImageData(E,0,0);v=j;u=0}}}};
10 |
11 |
--------------------------------------------------------------------------------
/jscala-examples/javascript-tetris/texture.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nau/jscala/309fe3a9bf0e7978f3fffc9fc15143edafc4b20f/jscala-examples/javascript-tetris/texture.jpg
--------------------------------------------------------------------------------
/jscala-examples/src/main/scala/org/jscalaexample/AES.scala:
--------------------------------------------------------------------------------
1 | package org.jscalaexample
2 |
3 | import org.jscala._
4 |
5 | /**
6 | * @author Alexander Nemish
7 | */
8 | object AesExample {
9 |
10 | def main(args: Array[String]): Unit = {
11 | val data = Array(22, 13, 8, 123456789)
12 | println("Input data " + data.mkString(","))
13 | val key = Array(1, 1, 1, 1)
14 | val aes = new Aes(key)
15 | val encrypted = aes.crypt(data, false)
16 | println("Encrypted Scala " + encrypted.mkString(","))
17 | val decrypted = aes.crypt(encrypted, true)
18 | println("Decrypted Scala " + decrypted.mkString(","))
19 | val main = javascript {
20 | val d = inject(data)
21 | val k = inject(key)
22 | val aes = new Aes(k)
23 | val encrypted = aes.crypt(d, false)
24 | print("Encrypted JS " + encrypted + "\n")
25 | val decrypted = aes.crypt(encrypted, true)
26 | print("Decrypted JS " + decrypted + "\n")
27 | }
28 | val js = Aes.jscala.javascript ++ main
29 | js.eval()
30 | }
31 | }
32 |
33 | @Javascript
34 | class Aes(val key: Array[Int]) {
35 | val encTable = Array(new Array[Int](256), new Array[Int](256), new Array[Int](256), new Array[Int](256), new Array[Int](256))
36 | val decTable = Array(new Array[Int](256), new Array[Int](256), new Array[Int](256), new Array[Int](256), new Array[Int](256))
37 | var keys = Array(new Array[Int](44), new Array[Int](44))
38 |
39 | init()
40 |
41 | private def precompute(): Unit = {
42 | val sbox = encTable(4)
43 | val sboxInv = decTable(4)
44 | val d = new Array[Int](256)
45 | val th = new Array[Int](256)
46 | var s = 0
47 | var tEnc = 0
48 | var tDec = 0
49 |
50 | // Compute double and third tables
51 | for (i <- 0 until 256) {
52 | d(i) = i << 1 ^ (i >> 7) * 283
53 | th(d(i) ^ i) = i
54 | }
55 |
56 | var x = 0
57 | var x2 = 0
58 | var x4 = 0
59 | var x8 = 0
60 | var xInv = 0
61 |
62 | while (sbox(x) == undefined || sbox(x) == 0) {
63 | // Compute sbox
64 | s = xInv ^ xInv << 1 ^ xInv << 2 ^ xInv << 3 ^ xInv << 4
65 | s = s >> 8 ^ s & 255 ^ 99
66 | sbox(x) = s
67 | sboxInv(s) = x
68 |
69 | // Compute MixColumns
70 | x2 = d(x)
71 | x4 = d(x2)
72 | x8 = d(x4)
73 | tDec = x8 * 0x1010101 ^ x4 * 0x10001 ^ x2 * 0x101 ^ x * 0x1010100
74 | tEnc = d(s) * 0x101 ^ s * 0x1010100
75 |
76 | for (i <- 0 until 4) {
77 | tEnc = tEnc << 24 ^ tEnc >>> 8
78 | encTable(i)(x) = tEnc
79 | tDec = tDec << 24 ^ tDec >>> 8
80 | decTable(i)(s) = tDec
81 | }
82 |
83 | x = x ^ (if (x2 != 0 && x2 != undefined) x2 else 1)
84 | xInv = if (th(xInv) != 0 && th(xInv) != undefined) th(xInv) else 1
85 | }
86 | }
87 |
88 | private def init(): Unit = {
89 | precompute()
90 |
91 | var tmp = 0
92 | val encKey = new Array[Int](44)
93 | val decKey = new Array[Int](44)
94 | val sbox = this.encTable(4)
95 | val keyLen = key.length
96 | var rcon = 1
97 |
98 | if (keyLen != 4 && keyLen != 6 && keyLen != 8) {
99 | return
100 | }
101 |
102 | for (l <- 0 until 4) encKey(l) = key(l)
103 | this.keys = Array(encKey, decKey)
104 |
105 | // schedule encryption keys
106 | var i = keyLen
107 | while (i < 4 * keyLen + 28) {
108 | tmp = encKey(i - 1)
109 |
110 | // apply sbox
111 | if (i % keyLen == 0 || (keyLen == 8 && i % keyLen == 4)) {
112 | tmp = sbox(tmp >>> 24) << 24 ^ sbox(tmp >> 16 & 255) << 16 ^ sbox(tmp >> 8 & 255) << 8 ^ sbox(tmp & 255)
113 |
114 | // shift rows and add rcon
115 | if (i % keyLen == 0) {
116 | tmp = tmp << 8 ^ tmp >>> 24 ^ rcon << 24
117 | rcon = rcon << 1 ^ (rcon >> 7) * 283
118 | }
119 | }
120 |
121 | encKey(i) = encKey(i - keyLen) ^ tmp
122 | i += 1
123 | }
124 | // schedule decryption keys
125 | var j = 0
126 | while (i != 0) {
127 | tmp = encKey(if ((j & 3) != 0) i else i - 4)
128 | if (i <= 4 || j < 4) {
129 | decKey(j) = tmp
130 | } else {
131 | decKey(j) = decTable(0)(sbox(tmp >>> 24)) ^
132 | decTable(1)(sbox(tmp >> 16 & 255)) ^
133 | decTable(2)(sbox(tmp >> 8 & 255)) ^
134 | decTable(3)(sbox(tmp & 255))
135 | }
136 | j += 1
137 | i -= 1
138 | }
139 | }
140 |
141 | def crypt(input: Array[Int], dir: Boolean): Array[Int] = {
142 | if (input.length != 4) {
143 | return Array(0, 0, 0, 0)
144 | }
145 |
146 | val key = this.keys(if (dir) 1 else 0)
147 | // state variables a,b,c,d are loaded with pre-whitened data
148 | var a = input(0) ^ key(0)
149 | var b = input(if (dir) 3 else 1) ^ key(1)
150 | var c = input(2) ^ key(2)
151 | var d = input(if (dir) 1 else 3) ^ key(3)
152 |
153 | val nInnerRounds = key.length / 4 - 2
154 | var kIndex = 4
155 | val out = Array(0, 0, 0, 0)
156 |
157 | val table = if (dir) decTable else encTable
158 |
159 | // load up the tables
160 | val t0 = table(0)
161 | val t1 = table(1)
162 | val t2 = table(2)
163 | val t3 = table(3)
164 | val sbox = table(4)
165 |
166 | // Inner rounds. Cribbed from OpenSSL.
167 | var a2 = 0
168 | var b2 = 0
169 | var c2 = 0
170 | for (i <- 0 until nInnerRounds) {
171 | a2 = t0(a >>> 24) ^ t1(b >> 16 & 255) ^ t2(c >> 8 & 255) ^ t3(d & 255) ^ key(kIndex)
172 | b2 = t0(b >>> 24) ^ t1(c >> 16 & 255) ^ t2(d >> 8 & 255) ^ t3(a & 255) ^ key(kIndex + 1)
173 | c2 = t0(c >>> 24) ^ t1(d >> 16 & 255) ^ t2(a >> 8 & 255) ^ t3(b & 255) ^ key(kIndex + 2)
174 | d = t0(d >>> 24) ^ t1(a >> 16 & 255) ^ t2(b >> 8 & 255) ^ t3(c & 255) ^ key(kIndex + 3)
175 | kIndex += 4
176 | a = a2
177 | b = b2
178 | c = c2
179 | }
180 |
181 | // Last round.
182 | for (i <- 0 until 4) {
183 | out(if (dir) 3 & (-i) else i) =
184 | sbox(a >>> 24) << 24 ^
185 | sbox(b >> 16 & 255) << 16 ^
186 | sbox(c >> 8 & 255) << 8 ^
187 | sbox(d & 255) ^
188 | key(kIndex)
189 | kIndex += 1
190 | a2 = a; a = b; b = c; c = d; d = a2
191 | }
192 |
193 | out
194 | }
195 | }
--------------------------------------------------------------------------------
/jscala-examples/src/main/scala/org/jscalaexample/JScalaExample.scala:
--------------------------------------------------------------------------------
1 | package org.jscalaexample
2 |
3 | import org.jscala._
4 | import scala.util.Random
5 | import org.scalajs.dom._
6 |
7 | case class User(name: String, id: Int)
8 |
9 | object JScalaExample {
10 | def domManipulations(): Unit = {
11 | val html = javascript {
12 | val node = document.getElementById("myList2").lastChild
13 | document.getElementById("myList1").appendChild(node)
14 | val buttons = document.getElementsByTagName("link")
15 | for (idx <- 0 until buttons.length) {
16 | console.log(buttons.item(idx).attributes)
17 | }
18 | }.asString
19 | println(html)
20 | }
21 |
22 | def hello(): Unit = {
23 | val ast = javascript {
24 | def main(args: Array[String]): Unit = {
25 | val language = if (args.length == 0) "EN" else args(0)
26 | val res = language match {
27 | case "EN" => "Hello!"
28 | case "FR" => "Salut!"
29 | case "IT" => "Ciao!"
30 | case _ => s"Sorry, I can't greet you in $language yet"
31 | }
32 | print(res)
33 | }
34 | }
35 | println(ast.asString)
36 | }
37 |
38 | def browserStuff(): Unit = {
39 | val js = javascript {
40 | window.location.href = "http://jscala.org"
41 | window.open("https://github.com")
42 | window.history.back()
43 | }
44 | println(js.asString)
45 | }
46 |
47 | def shortExample(): Unit = {
48 | val scalaValue = "https://github.com/nau/jscala"
49 | def rand() = Random.nextInt(5).toJs
50 | val $ = new JsDynamic {}
51 |
52 | val js = javascript {
53 | window.setTimeout(() => {
54 | val links = Array("https://github.com/nau/scala")
55 | include("var raw = 'JavaScript'")
56 | for (link <- links) {
57 | $("#id").append(s"$link
")
58 | }
59 | for (i <- 0 to rand().as[Int]) print(inject(scalaValue))
60 | }, 1000)
61 | }
62 | println(js.asString)
63 | }
64 |
65 | def complexExample(): Unit = {
66 | val js = javascript {
67 | window.setTimeout(() => {
68 | val r = new RegExp("d.*", "g")
69 | class Point(val x: Int, val y: Int)
70 | val point = new Point(1, 2)
71 | def func(i: String) = r.exec(i)
72 | val list = document.getElementById("myList2")
73 | val map = collection.mutable.Map[String, String]()
74 | if (typeof(map) == "string") {
75 | for (idx <- 0 until list.attributes.length) {
76 | val attr = list.attributes.item(idx)
77 | map(attr.name) = func(attr.textContent)
78 | }
79 | } else {
80 | val obj = new {
81 | val field = 1
82 | def func2(i: Int) = "string"
83 | }
84 | val links = Array("https://github.com/nau/scala")
85 | for (link <- links) {
86 | include("var raw = 'JavaScript'")
87 | console.log(link + obj.func2(obj.field) + point.x)
88 | }
89 | window.location.href = links(0).replace("scala", "jscala")
90 | }
91 | }, 1000)
92 | }
93 | println(js.asString)
94 | }
95 |
96 | def ajaxExample(): Unit = {
97 | val $ = new JsDynamic {}
98 | def ajaxCall(pageId: Int) = javascript {
99 | $.get("ajax/" + pageId, (data: String) => $("#someId").html(data))
100 | }
101 | def genAjaxCall(pageId: Int) = javascript {
102 | ajaxCall(pageId)
103 | }
104 |
105 | println(genAjaxCall(123).asString)
106 | }
107 |
108 |
109 | import play.api.libs.json._
110 | def readmeExample(): Unit = {
111 | implicit val userJson = Json.format[User]
112 | @Javascript class Greeter {
113 | def hello(u: User): Unit = {
114 | print(s"Hello, ${u.name} \n")
115 | }
116 | }
117 | // Run on JVM
118 | val u1 = User("Alex", 1)
119 | val greeter = new Greeter()
120 | greeter.hello(u1) // prints "Hello, Alex"
121 | val json = Json.stringify(Json.toJson(u1))
122 | val main = javascript {
123 | val u = User(id = 2, name = "nau")
124 | // read User from json string generated above
125 | val u1Json = eval(s"(${include(json)})").as[User]
126 | val t = new Greeter()
127 | t.hello(u)
128 | t.hello(u1Json)
129 | }
130 | // join classes definitions with main code
131 | val js = Greeter.jscala.javascript ++ main
132 | js.eval() // run using Rhino
133 | println(js.asString) // prints resulting JavaScript
134 | }
135 |
136 |
137 | def astManipulation(): Unit = {
138 | val vardef = JsVarDef("test", "Test".toJs).block
139 | val print = JsCall(JsIdent("print"), JsIdent("test") :: Nil)
140 | val ast = vardef ++ print
141 | ast.eval()
142 | println(ast.asString)
143 | /* prints
144 | Test
145 | {
146 | var test = "Test";
147 | print(test);
148 | }
149 | */
150 | }
151 |
152 | def main(args: Array[String]): Unit = {
153 | domManipulations()
154 | browserStuff()
155 | complexExample()
156 | hello()
157 | shortExample()
158 | readmeExample()
159 | astManipulation()
160 | }
161 | }
162 |
163 |
--------------------------------------------------------------------------------
/jscala-examples/src/main/scala/org/jscalaexample/Tetris.scala:
--------------------------------------------------------------------------------
1 | package org.jscalaexample
2 |
3 | import org.jscala._
4 | import org.jscala.JArray
5 | import org.scalajs.dom._
6 | import java.io.{File, FileWriter}
7 |
8 | import org.scalajs.dom.raw.HTMLElement
9 |
10 | /**
11 | * Javascript Tetris code originally taken from https://github.com/jakesgordon/javascript-tetris/
12 | * and rewritten in Scala
13 | */
14 | object Tetris {
15 | def tetris = {
16 | class Canvas(var width: Int, var height: Int, var clientWidth: Int, var clientHeight: Int) extends JsDynamic
17 | class Stats extends JsDynamic
18 | javascript {
19 | //-------------------------------------------------------------------------
20 | // base helper methods
21 | //-------------------------------------------------------------------------
22 |
23 | def get(id: String) = document.getElementById(id).as[HTMLElement]
24 | def hide(id: String): Unit = { get(id).style.visibility = "hidden"; }
25 | def show(id: String): Unit = { get(id).style.visibility = null; }
26 | def html(id: String, html: String): Unit = { get(id).innerHTML = html; }
27 |
28 | def timestamp() = { new Date().getTime() }
29 | def random(min: Int, max: Int) = { (min + (Math.random() * (max - min))); }
30 | def randomChoice(choices: Array[Int]) = choices(Math.round(random(0, choices.length-1)).as[Int])
31 |
32 | /*if (!window.requestAnimationFrame) {
33 | val func = (callback: () => Unit, element: Canvas) => {
34 | window.setTimeout(callback, 1000 / 60)
35 | }
36 | window.requestAnimationFrame = window.webkitRequestAnimationFrame ||
37 | window.mozRequestAnimationFrame ||
38 | window.oRequestAnimationFrame ||
39 | window.msRequestAnimationFrame || func
40 | }*/
41 |
42 | //-------------------------------------------------------------------------
43 | // game constants
44 | //-------------------------------------------------------------------------
45 |
46 | val KEY = new { val ESC = 27; val SPACE = 32; val LEFT = 37; val UP = 38; val RIGHT = 39; val DOWN = 40 }
47 | val DIR = new { val UP = 0; val RIGHT = 1; val DOWN = 2; val LEFT = 3; val MIN = 0; val MAX = 3 }
48 | val stats = new Stats()
49 | val canvas = get("canvas").as[Canvas]
50 | val ctx = canvas.getContext("2d")
51 | val ucanvas = get("upcoming").as[Canvas]
52 | val uctx = ucanvas.getContext("2d")
53 | val speed = new { val start = 0.6; val decrement = 0.005; val min = 0.1 } // how long before piece drops by 1 row (seconds)
54 | val nx = 10 // width of tetris court (in blocks)
55 | val ny = 20 // height of tetris court (in blocks)
56 | val nu = 5 // width/height of upcoming preview (in blocks)
57 |
58 | //-------------------------------------------------------------------------
59 | // game variables (initialized during reset)
60 | //-------------------------------------------------------------------------
61 |
62 | var dx = 0
63 | var dy = 0 // pixel size of a single tetris block
64 | var blocks = JArray[JArray[Block]]() // 2 dimensional array (nx*ny) representing tetris court - either empty block or occupied by a "piece"
65 | var actions = JArray[Int]() // queue of user actions (inputs)
66 | var playing = false // true|false - game is in progress
67 | var dt: Long = 0 // time since starting this game
68 | var current: Piece = null // the current piece
69 | var next: Piece = null // the next piece
70 | var score = 0 // the current score
71 | var vscore = 0 // the currently displayed score (it catches up to score in small chunks - like a spinning slot machine)
72 | var rows = 0 // number of completed rows in the current game
73 | var step = 0 // how long before current piece drops by 1 row
74 |
75 | //-------------------------------------------------------------------------
76 | // tetris pieces
77 | //
78 | // blocks: each element represents a rotation of the piece (0, 90, 180, 270)
79 | // each element is a 16 bit integer where the 16 bits represent
80 | // a 4x4 set of blocks, e.g. j.blocks[0] = 0x44C0
81 | //
82 | // 0100 = 0x4 << 3 = 0x4000
83 | // 0100 = 0x4 << 2 = 0x0400
84 | // 1100 = 0xC << 1 = 0x00C0
85 | // 0000 = 0x0 << 0 = 0x0000
86 | // ------
87 | // 0x44C0
88 | //
89 | //-------------------------------------------------------------------------
90 |
91 | case class Block(id: String, size: Int, blocks: Seq[Int], color: String)
92 | class Piece(val `type`: Block, var dir: Int, var x: Int, var y: Int )
93 |
94 | val i = Block("i", 4, Seq(0x0F00, 0x2222, 0x00F0, 0x4444), "cyan")
95 | val j = Block("j", 3, Seq(0x44C0, 0x8E00, 0x6440, 0x0E20), "blue" )
96 | val l = Block("l", 3, Seq(0x4460, 0x0E80, 0xC440, 0x2E00), "orange" )
97 | val o = Block("o", 2, Seq(0xCC00, 0xCC00, 0xCC00, 0xCC00), "yellow" )
98 | val s = Block("s", 3, Seq(0x06C0, 0x8C40, 0x6C00, 0x4620), "green" )
99 | val t = Block("t", 3, Seq(0x0E40, 0x4C40, 0x4E00, 0x4640), "purple" )
100 | val z = Block("z", 3, Seq(0x0C60, 0x4C80, 0xC600, 0x2640), "red" )
101 |
102 | //------------------------------------------------
103 | // do the bit manipulation and iterate through each
104 | // occupied block (x,y) for a given piece
105 | //------------------------------------------------
106 | def eachblock(`type`: Block, x: Int, y: Int, dir: Int, fn: (Int, Int) => Unit): Unit = {
107 | var row = 0
108 | var col = 0
109 | val blocks = `type`.blocks(dir)
110 | var bit = 0x8000
111 | while (bit > 0) {
112 | if ((blocks & bit) != 0) {
113 | fn(x + col, y + row)
114 | }
115 | col += 1
116 | if (col == 4) {
117 | col = 0
118 | row += 1
119 | }
120 | bit = bit >> 1
121 | }
122 | }
123 |
124 | //-----------------------------------------------------
125 | // check if a piece can fit into a position in the grid
126 | //-----------------------------------------------------
127 | def occupied(`type`: Block, x: Int, y: Int, dir: Int) = {
128 | var result = false
129 | eachblock(`type`, x, y, dir, (x, y) => {
130 | if ((x < 0) || (x >= nx) || (y < 0) || (y >= ny) || (getBlock(x,y) != null)) result = true
131 | })
132 | result
133 | }
134 |
135 | def unoccupied(`type`: Block, x: Int, y: Int, dir: Int) = !occupied(`type`, x, y, dir)
136 |
137 | //-----------------------------------------
138 | // start with 4 instances of each piece and
139 | // pick randomly until the "bag is empty"
140 | //-----------------------------------------
141 | var pieces = JArray[Block]()
142 | def randomPiece() = {
143 | if (pieces.length == 0)
144 | pieces = JArray(i,i,i,i,j,j,j,j,l,l,l,l,o,o,o,o,s,s,s,s,t,t,t,t,z,z,z,z)
145 | val `type` = pieces.splice(random(0, pieces.length-1).as[Int], 1)(0)
146 | new Piece(`type`, DIR.UP, Math.round(random(0, nx - `type`.size)).as[Int], 0)
147 | }
148 |
149 | //-------------------------------------------------------------------------
150 | // GAME LOOP
151 | //-------------------------------------------------------------------------
152 |
153 | def run(): Unit = {
154 | showStats()
155 | addEvents() // attach keydown and resize events
156 | var last = timestamp()
157 | var now = timestamp()
158 | def frame(): Unit = {
159 | now = timestamp()
160 | update(Math.min(1, (now - last) / 1000.0).as[Int]) // using requestAnimationFrame have to be able to handle large delta"s caused when it "hibernates" in a background or non-visible tab
161 | draw()
162 | stats.update()
163 | last = now
164 | window.as[JsDynamic].requestAnimationFrame(frame _, canvas)
165 | }
166 | resize(null) // setup all our sizing information
167 | reset() // reset the per-game variables
168 | frame() // start the first frame
169 | }
170 |
171 | def showStats(): Unit = {
172 | stats.domElement.id = "stats"
173 | get("menu").appendChild(stats.domElement.as[Node])
174 | }
175 |
176 | def addEvents(): Unit = {
177 | document.addEventListener("keydown", keydown _, false)
178 | window.addEventListener("resize", resize _, false)
179 | }
180 |
181 | def resize(ev: JsDynamic): Unit = {
182 | canvas.width = canvas.clientWidth // set canvas logical size equal to its physical size
183 | canvas.height = canvas.clientHeight // (ditto)
184 | ucanvas.width = ucanvas.clientWidth
185 | ucanvas.height = ucanvas.clientHeight
186 | dx = canvas.width / nx // pixel size of a single tetris block
187 | dy = canvas.height / ny // (ditto)
188 | invalidate()
189 | invalidateNext()
190 | }
191 |
192 | def keydown(ev: JsDynamic): Unit = {
193 | var handled = false
194 | if (playing) {
195 | ev.keyCode.as[Int] match {
196 | case KEY.LEFT => actions.push(DIR.LEFT); handled = true
197 | case KEY.RIGHT => actions.push(DIR.RIGHT); handled = true
198 | case KEY.UP => actions.push(DIR.UP); handled = true
199 | case KEY.DOWN => actions.push(DIR.DOWN); handled = true
200 | case KEY.ESC => lose(); handled = true
201 | }
202 | }
203 | else if (ev.keyCode == KEY.SPACE) {
204 | play()
205 | handled = true
206 | }
207 | if (handled)
208 | ev.preventDefault() // prevent arrow keys from scrolling the page (supported in IE9+ and all other browsers)
209 | }
210 |
211 | //-------------------------------------------------------------------------
212 | // GAME LOGIC
213 | //-------------------------------------------------------------------------
214 |
215 | def play() = { hide("start"); reset(); playing = true; }
216 | def lose() = { show("start"); setVisualScore(-1); playing = false; }
217 |
218 | def setVisualScore(n: Int) = {
219 | val s = if (n != -1) n else score
220 | vscore = s
221 | invalidateScore()
222 | }
223 | def setScore(n: Int) = { score = n; setVisualScore(n); }
224 | def addScore(n: Int) = { score = score + n; }
225 | def clearScore() = { setScore(0) }
226 | def clearRows() = { setRows(0) }
227 | def setRows(n: Int) = { rows = n; step = Math.max(speed.min, speed.start - (speed.decrement*rows)).as[Int]; invalidateRows(); }
228 | def addRows(n: Int) = { setRows(rows + n); }
229 | def getBlock(x: Int, y: Int) = {
230 | val b = if (blocks(x) != null) blocks(x)(y) else null
231 | b
232 | }
233 | def setBlock(x: Int, y: Int, `type`: Block) = {
234 | val r = if (blocks(x) != null) blocks(x) else JArray[Block]()
235 | blocks(x) = r
236 | blocks(x)(y) = `type`
237 | invalidate()
238 | }
239 | def clearBlocks(): Unit = { blocks = JArray(); invalidate(); }
240 | def clearActions() = { actions = JArray() }
241 | def setCurrentPiece(piece: Piece) = {
242 | val n = if (piece != null) piece else randomPiece()
243 | current = n
244 | invalidate()
245 | }
246 | def setNextPiece(piece: Piece = null) = {
247 | val n = if (piece != null) piece else randomPiece()
248 | next = n
249 | invalidateNext()
250 | }
251 |
252 | def reset(): Unit = {
253 | dt = 0
254 | clearActions()
255 | clearBlocks()
256 | clearRows()
257 | clearScore()
258 | setCurrentPiece(next)
259 | setNextPiece()
260 | }
261 |
262 | def update(idt: Int): Unit = {
263 | if (playing) {
264 | if (vscore < score)
265 | setVisualScore(vscore + 1)
266 | handle(actions.shift())
267 | dt = dt + idt
268 | if (dt > step) {
269 | dt = dt - step
270 | drop()
271 | }
272 | }
273 | }
274 |
275 | def handle(action: Int): Unit = {
276 | action match {
277 | case DIR.LEFT => move(DIR.LEFT)
278 | case DIR.RIGHT => move(DIR.RIGHT)
279 | case DIR.UP => rotate()
280 | case DIR.DOWN => drop()
281 | }
282 | }
283 |
284 | def move(dir: Int): Boolean = {
285 | var x = current.x
286 | var y = current.y
287 | dir match {
288 | case DIR.RIGHT => x = x + 1
289 | case DIR.LEFT => x = x - 1
290 | case DIR.DOWN => y = y + 1
291 | }
292 | if (unoccupied(current.`type`, x, y, current.dir)) {
293 | current.x = x
294 | current.y = y
295 | invalidate()
296 | return true
297 | }
298 | else {
299 | return false
300 | }
301 | }
302 |
303 | def rotate(): Unit = {
304 | val newdir = if (current.dir == DIR.MAX) DIR.MIN else current.dir + 1
305 | if (unoccupied(current.`type`, current.x, current.y, newdir)) {
306 | current.dir = newdir
307 | invalidate()
308 | }
309 | }
310 |
311 | def drop(): Unit = {
312 | if (!move(DIR.DOWN)) {
313 | addScore(10)
314 | dropPiece()
315 | removeLines()
316 | setCurrentPiece(next)
317 | setNextPiece(randomPiece())
318 | clearActions()
319 | if (occupied(current.`type`, current.x, current.y, current.dir)) {
320 | lose()
321 | }
322 | }
323 | }
324 |
325 | def dropPiece(): Unit = {
326 | eachblock(current.`type`, current.x, current.y, current.dir, (x, y) => {
327 | setBlock(x, y, current.`type`)
328 | })
329 | }
330 |
331 | def removeLines(): Unit = {
332 | var x = 0
333 | var y = ny
334 | var complete = false
335 | var n = 0
336 | while (y > 0) {
337 | complete = true
338 | x = 0
339 | while (x < nx) {
340 | if (!(getBlock(x, y).as[Boolean]))
341 | complete = false
342 | x += 1
343 | }
344 | if (complete) {
345 | removeLine(y)
346 | y = y + 1 // recheck same line
347 | n += 1
348 | }
349 | y -= 1
350 | }
351 | if (n > 0) {
352 | addRows(n)
353 | addScore(100*Math.pow(2,n-1).as[Int]) // 1: 100, 2: 200, 3: 400, 4: 800
354 | }
355 | }
356 |
357 | def removeLine(n: Int): Unit = {
358 | var x = 0
359 | var y = n
360 | while (y >= 0) {
361 | x = 0
362 | while (x < nx) {
363 | val b = if (y == 0) null else getBlock(x, y-1)
364 | setBlock(x, y, b)
365 | x += 1
366 | }
367 | y -= 1
368 | }
369 | }
370 |
371 | //-------------------------------------------------------------------------
372 | // RENDERING
373 | //-------------------------------------------------------------------------
374 |
375 | class Invalid(var court: Boolean, var next: Boolean, var score: Boolean, var rows: Boolean)
376 | val invalid = new Invalid(false, false, false, false)
377 |
378 | def invalidate(): Unit = { invalid.court = true }
379 | def invalidateNext(): Unit = { invalid.next = true }
380 | def invalidateScore(): Unit = { invalid.score = true }
381 | def invalidateRows(): Unit = { invalid.rows = true }
382 |
383 | def draw(): Unit = {
384 | ctx.save()
385 | ctx.lineWidth = 1
386 | ctx.translate(0.5, 0.5) // for crisp 1px black lines
387 | drawCourt()
388 | drawNext()
389 | drawScore()
390 | drawRows()
391 | ctx.restore()
392 | }
393 |
394 | def drawCourt(): Unit = {
395 | if (invalid.court) {
396 | ctx.clearRect(0, 0, canvas.width, canvas.height)
397 | if (playing)
398 | drawPiece(ctx, current.`type`, current.x, current.y, current.dir)
399 | var x = 0
400 | var y = 0
401 | var block: Block = null
402 | while (y < ny) {
403 | x = 0
404 | while (x < nx) {
405 | block = getBlock(x,y)
406 | if (block != null)
407 | drawBlock(ctx, x, y, block.color)
408 | x += 1
409 | }
410 | y += 1
411 | }
412 | ctx.strokeRect(0, 0, nx*dx - 1, ny*dy - 1) // court boundary
413 | invalid.court = false
414 | }
415 | }
416 |
417 | def drawNext(): Unit = {
418 | if (invalid.next) {
419 | val padding = (nu - next.`type`.size) / 2 // half-arsed attempt at centering next piece display
420 | uctx.save()
421 | uctx.translate(0.5, 0.5)
422 | uctx.clearRect(0, 0, nu*dx, nu*dy)
423 | drawPiece(uctx, next.`type`, padding, padding, next.dir)
424 | uctx.strokeStyle = "black"
425 | uctx.strokeRect(0, 0, nu*dx - 1, nu*dy - 1)
426 | uctx.restore()
427 | invalid.next = false
428 | }
429 | }
430 |
431 | def drawScore(): Unit = {
432 | if (invalid.score) {
433 | val text: JString = "00000" + vscore
434 | html("score", text.slice(-5).toString())
435 | invalid.score = false
436 | }
437 | }
438 |
439 | def drawRows(): Unit = {
440 | if (invalid.rows) {
441 | html("rows", rows.toString())
442 | invalid.rows = false
443 | }
444 | }
445 |
446 | def drawPiece(ctx: JsDynamic, `type`: Block, x: Int, y: Int, dir: Int): Unit = {
447 | eachblock(`type`, x, y, dir, (x, y) => drawBlock(ctx, x, y, `type`.color))
448 | }
449 |
450 | def drawBlock(ctx: JsDynamic, x: Int, y: Int, color: String): Unit = {
451 | ctx.fillStyle = color
452 | ctx.fillRect(x*dx, y*dy, dx, dy)
453 | ctx.strokeRect(x*dx, y*dy, dx, dy)
454 | }
455 | //-------------------------------------------------------------------------
456 | // FINALLY, lets run the game
457 | //-------------------------------------------------------------------------
458 |
459 | run()
460 | }
461 | }
462 |
463 | def main(args: Array[String]): Unit = {
464 | val path = new File(args(0))
465 | val ast = tetris
466 | val fw = new FileWriter(path)
467 | fw.write(ast.asString)
468 | fw.close()
469 | }
470 | }
471 |
--------------------------------------------------------------------------------
/jscala-examples/src/test/scala/org/jscalaexample/AesTest.scala:
--------------------------------------------------------------------------------
1 | package org.jscalaexample
2 |
3 | import jdk.nashorn.api.scripting.ScriptObjectMirror
4 | import org.jscala._
5 | import org.scalatest.{FunSuite, Matchers}
6 | import org.scalatest.prop.{Checkers, PropertyChecks}
7 |
8 | import collection.JavaConverters._
9 |
10 | class AesTest extends FunSuite with Matchers with PropertyChecks with Checkers {
11 | test("test") {
12 | val key = Array(1, 1, 1, 1)
13 | forAll((d: Array[Int]) => whenever(d.size >= 4) {
14 | val data = d.take(4)
15 | val aes = new Aes(key)
16 | val encrypted = aes.crypt(data, false)
17 | val decrypted = aes.crypt(encrypted, true)
18 | val main = javascript {
19 | val d = inject(data)
20 | val k = inject(key)
21 | val aes = new Aes(k)
22 | val encrypted = aes.crypt(d, false)
23 | aes.crypt(encrypted, true)
24 | }
25 | val js = Aes.jscala.javascript ++ main
26 | val jsDecrypted = js.eval().asInstanceOf[ScriptObjectMirror].values().asScala.toList
27 | data should be(decrypted)
28 | data.toList should be(jsDecrypted)
29 | })
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/jscala-examples/src/test/scala/org/jscalaexample/JavascriptPrinterTest.scala:
--------------------------------------------------------------------------------
1 | package org.jscalaexample
2 |
3 | import org.scalatest.FunSuite
4 | import org.jscala._
5 | import org.jscala.{javascript=>js}
6 | import org.scalajs.dom.console
7 |
8 | class JavascriptPrinterTest extends FunSuite {
9 | test("String escaping") {
10 | val ast = js {
11 | val quote1 = """© "something""""
12 | val quote2 = "© \"something\""
13 | val multiline1 = "a\nb\tc"
14 | val multiline2 =
15 | """a
16 | b
17 | c"""
18 | }
19 | assert(ast.asString ===
20 | """{
21 | | var quote1 = "© \"something\"";
22 | | var quote2 = "© \"something\"";
23 | | var multiline1 = "a\nb\tc";
24 | | var multiline2 = "a\n b\n c";
25 | |}""".stripMargin)
26 | ast.eval()
27 | }
28 |
29 | test("Printer") {
30 | val ast = js {
31 | val a = Array("1", "2", "3")
32 | for (i <- a) console.log(i)
33 | }
34 | assert(ast.asString === """{
35 | | var a = ["1", "2", "3"];
36 | | for (var iColl = a, iIdx = 0, i = iColl[iIdx]; iIdx < iColl.length; i = iColl[++iIdx]) console.log(i);
37 | |}""".stripMargin)
38 | }
39 |
40 | test("IIFE") {
41 | val ast = js {
42 | (() => {
43 | val a = 1
44 | console.log(a)
45 | })()
46 | }
47 | assert(ast.asString === """(function () {
48 | | var a = 1;
49 | | console.log(a);
50 | | })()""".stripMargin)
51 | }
52 |
53 | test("YUI Compressor") {
54 | val ast = js {
55 | val a = Array("1", "2", "3")
56 | for (i <- a) console.log(i)
57 | }
58 | assert(ast.compress === """var a=["1","2","3"];
59 | |for(var iColl=a,iIdx=0,i=iColl[iIdx];
60 | |iIdx 3) && (Math.PI < 4)) Math.PI else Math.E }
67 | assert(ast.asString === "((Math.PI > 3) && (Math.PI < 4)) ? Math.PI : Math.E")
68 | }
69 |
70 | test("Switches") {
71 | val ast = js {
72 | val a = 1 match {
73 | case 1 | 2 => 1
74 | }
75 | }
76 | assert(ast.asString ===
77 | """{
78 | | var a;
79 | | switch (1) {
80 | | case 1:
81 | | case 2:
82 | | a = 1;
83 | | break;
84 | | };
85 | |}""".stripMargin)
86 | }
87 |
88 | test("foreach") {
89 |
90 | assert(js(Array(1, 2) foreach { i => print(i) }).asString ===
91 | """for (var iColl = [1, 2], iIdx = 0, i = iColl[iIdx]; iIdx < iColl.length; i = iColl[++iIdx]) print(i)""")
92 |
93 | assert(js(Array[Int](1, 2) foreach { i => print(i) }).asString ===
94 | """for (var iColl = [1, 2], iIdx = 0, i = iColl[iIdx]; iIdx < iColl.length; i = iColl[++iIdx]) print(i)""")
95 |
96 | assert(js(Array("1", "2") foreach { i => print(i) }).asString ===
97 | """for (var iColl = ["1", "2"], iIdx = 0, i = iColl[iIdx]; iIdx < iColl.length; i = iColl[++iIdx]) print(i)""")
98 |
99 | assert(js(Seq(1, 2) foreach { i => print(i) }).asString ===
100 | """for (var iColl = [1, 2], iIdx = 0, i = iColl[iIdx]; iIdx < iColl.length; i = iColl[++iIdx]) print(i)""")
101 |
102 | assert(js(List(1, 2) foreach { i => print(i) }).asString ===
103 | """for (var iColl = [1, 2], iIdx = 0, i = iColl[iIdx]; iIdx < iColl.length; i = iColl[++iIdx]) print(i)""")
104 |
105 | assert(js(List("1", "2") foreach { i => print(i) }).asString ===
106 | """for (var iColl = ["1", "2"], iIdx = 0, i = iColl[iIdx]; iIdx < iColl.length; i = iColl[++iIdx]) print(i)""")
107 |
108 | assert(js(List[java.lang.String]("1", "2") foreach { i => print(i) }).asString ===
109 | """for (var iColl = ["1", "2"], iIdx = 0, i = iColl[iIdx]; iIdx < iColl.length; i = iColl[++iIdx]) print(i)""")
110 |
111 | val ast = js {
112 | val a = Array(1, 2)
113 | val b = Seq(1, 2)
114 | val c = List(1, 2)
115 | for (i <- a) print(i)
116 | for (i <- b) print(i)
117 | for (i <- c) print(i)
118 | }
119 | assert(ast.asString ===
120 | """{
121 | | var a = [1, 2];
122 | | var b = [1, 2];
123 | | var c = [1, 2];
124 | | for (var iColl = a, iIdx = 0, i = iColl[iIdx]; iIdx < iColl.length; i = iColl[++iIdx]) print(i);
125 | | for (var iColl = b, iIdx = 0, i = iColl[iIdx]; iIdx < iColl.length; i = iColl[++iIdx]) print(i);
126 | | for (var iColl = c, iIdx = 0, i = iColl[iIdx]; iIdx < iColl.length; i = iColl[++iIdx]) print(i);
127 | |}""".stripMargin)
128 | }
129 |
130 | test("callback func") {
131 | case class User(name: String)
132 | case class Project(title: String, users: Seq[User])
133 |
134 | assert(js({ (p: Project) =>
135 | if (p.title == "title")
136 | for (u <- p.users) print(u.name)
137 | }).asString === """function (p) {
138 | | if (p.title == "title") for (var uColl = p.users, uIdx = 0, u = uColl[uIdx]; uIdx < uColl.length; u = uColl[++uIdx]) print(u.name);
139 | | }""".stripMargin)
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/jscala-examples/src/test/scala/org/jscalaexample/ScalaToJsConverterTest.scala:
--------------------------------------------------------------------------------
1 | package org.jscalaexample
2 |
3 | import org.scalatest.FunSuite
4 | import org.jscala._
5 | import org.jscala.{javascript => js}
6 | import org.scalajs.dom.console
7 | import scala.collection.mutable.ArrayBuffer
8 |
9 | class ScalaToJsConverterTest extends FunSuite {
10 | test("Literals") {
11 | assert(js(1) === 1.toJs)
12 | assert(js(-5.1) === (-5.1).toJs)
13 | assert(js('c') === "c".toJs)
14 | assert(js("str") === "str".toJs)
15 | assert(js(true) === true.toJs)
16 | assert(js(()) === JsUnit)
17 | }
18 |
19 | test("Unary operators") {
20 | val a = 0
21 | val b = false
22 | assert(js(+a) === JsUnOp("+", JsIdent("a")))
23 | assert(js(-a) === JsUnOp("-", JsIdent("a")))
24 | assert(js(!b) === JsUnOp("!", JsIdent("b")))
25 | }
26 |
27 | test("Binary operators") {
28 | val a = 0
29 | val b = 1
30 | val f = 2.0
31 | val c = true
32 | val d = false
33 | var s = 0
34 | val ja = JsIdent("a")
35 | val jb = JsIdent("b")
36 | val jc = JsIdent("c")
37 | val jd = JsIdent("d")
38 | val jf = JsIdent("f")
39 | assert(js(a * b) === JsBinOp("*", ja, jb))
40 | assert(js(b / f) === JsBinOp("/", jb, jf))
41 | assert(js(a / b) === JsBinOp("|", JsBinOp("/", ja, jb), 0.toJs))
42 | assert(js(a % b) === JsBinOp("%", ja, jb))
43 | assert(js(a + b) === JsBinOp("+", ja, jb))
44 | assert(js(a - b) === JsBinOp("-", ja, jb))
45 | assert(js(a << b) === JsBinOp("<<", ja, jb))
46 | assert(js(a >> b) === JsBinOp(">>", ja, jb))
47 | assert(js(a >>> b) === JsBinOp(">>>", ja, jb))
48 | assert(js(a < b) === JsBinOp("<", ja, jb))
49 | assert(js(a > b) === JsBinOp(">", ja, jb))
50 | assert(js(a <= b) === JsBinOp("<=", ja, jb))
51 | assert(js(a >= b) === JsBinOp(">=", ja, jb))
52 | assert(js(a == b) === JsBinOp("==", ja, jb))
53 | assert(js(a != b) === JsBinOp("!=", ja, jb))
54 | assert(js(a & b) === JsBinOp("&", ja, jb))
55 | assert(js(a | b) === JsBinOp("|", ja, jb))
56 | assert(js(c && d) === JsBinOp("&&", jc, jd))
57 | assert(js(c || d) === JsBinOp("||", jc, jd))
58 | assert(js{s = a + b} === JsBinOp("=", JsIdent("s"), JsBinOp("+", ja, jb)))
59 | val lhs = JsBinOp("!=", JsBinOp("/", JsBinOp("+", ja, JsBinOp("*", jb, ja)), jf), jb)
60 | val expected = JsBinOp("&&", lhs, JsBinOp("==", jc, jb))
61 | assert(js(((a + b * a) / f != b) && c == b) === expected)
62 | }
63 |
64 | test("Simple expressions") {
65 | val local = 1
66 | def func1() = 1
67 | val obj = new {
68 | val field = 1
69 | def func2(i: Int) = "string"
70 | }
71 | def func3(f: Int => String) = f(1)
72 | assert(js(local) === JsIdent("local"))
73 | assert(js(func1()) === JsCall(JsIdent("func1"), Nil))
74 | val ast = js {
75 | val obj = new {
76 | val field = 1
77 | def func1(i: Int) = field
78 | def func2(i: Int) = "string"
79 | }
80 | }
81 | val fields = List("field" -> 1.toJs, "func1" -> JsAnonFunDecl(List("i"), JsReturn(JsSelect(JsIdent("this"), "field")).block), "func2" -> JsAnonFunDecl(List("i"), JsReturn("string".toJs).block))
82 | assert(ast === JsVarDef("obj", JsAnonObjDecl(fields)).block)
83 | assert(js(obj.func2(1)) === JsCall(JsSelect(JsIdent("obj"), "func2"), List(1.toJs)))
84 | val lambda = JsAnonFunDecl(List("i"), JsReturn(JsCall(JsSelect(JsIdent("i"), "toString"), Nil)).block)
85 | assert(js(func3(i => i.toString)) === JsCall(JsIdent("func3"), List(lambda)))
86 |
87 | val ifExprAst = js {
88 | var a = if (inject(local) > 1) 2 else 3
89 | val b = if (inject(local) > 1) {
90 | a += 1
91 | a
92 | } else {
93 | a += 2
94 | a
95 | }
96 | a + b
97 | }
98 | assert(ifExprAst.eval() === 10)
99 | val matchExprAst = js {
100 | val a = inject(local) match {
101 | case 1 | 2 => "one or two"
102 | case 3 => "three"
103 | case _ => "other"
104 | }
105 | a
106 | }
107 | assert(matchExprAst.eval() === "one or two")
108 | val ternaryAst = js {
109 | val a = if (Math.PI > 3) 3 else 4
110 | def f(i: Int) = i
111 | f(if (Math.PI > 3) 3 else 4) + a
112 | }
113 | assert(ternaryAst.eval() === 6)
114 | }
115 |
116 | test("Closures") {
117 | val ast = js {
118 | class A {
119 | def foo(f: Int => String) = f(1)
120 | foo(_.toString)
121 | def bar(i: Int) = i.toString
122 | foo(bar)
123 | }
124 | }
125 | }
126 |
127 | test("Simple statements") {
128 | var a = 0
129 | val b = 1
130 | val c = true
131 | val d = false
132 | var s = 0
133 | val ja = JsIdent("a")
134 | val jb = JsIdent("b")
135 | val jc = JsIdent("c")
136 | val jd = JsIdent("d")
137 | val jss = JsIdent("s")
138 | assert(js { val a = 0 } === JsVarDef("a", 0.toJs).block)
139 | assert(js { val a = b + 2 } === JsVarDef("a", JsBinOp("+", JsIdent("b"), 2.toJs)).block)
140 | assert(js { if (a > 0) b } === JsIf(JsBinOp(">", ja, 0.toJs), jb, None))
141 | val stmt1 = JsCall(JsSelect(JsIdent("console"), "log"), List("".toJs))
142 | assert(js { if (a > 0) console.log("") else a } === JsIf(JsBinOp(">", ja, 0.toJs), stmt1, Some(ja)))
143 | val whileBody = JsCall(JsSelect(JsIdent("Math"), "random"), List())
144 | assert(js { while (a > 0) Math.random() } === JsWhile(JsBinOp(">", ja, 0.toJs), whileBody))
145 | val body = JsBlock(List(JsBinOp("=", jss, JsBinOp("+", jss, ja)), JsBinOp("=", ja, JsBinOp("+", ja, 1.toJs))))
146 | val expected = JsWhile(JsBinOp(">", ja, 0.toJs), body)
147 | assert(js {
148 | while (a > 0) {
149 | s = s + a
150 | a = a + 1
151 | } } === expected)
152 |
153 | assert(js { def func1(): Unit = {} } === JsFunDecl("func1", Nil, JsBlock(Nil)).block)
154 | assert(js { def func2 = 5 } === JsFunDecl("func2", Nil, JsBlock(List(JsReturn(5.toJs)))).block)
155 | assert(js { def func3() = 5 } === JsFunDecl("func3", Nil, JsBlock(List(JsReturn(5.toJs)))).block)
156 | assert(js { def func4(a: String) = 5 } === JsFunDecl("func4", List("a"), JsBlock(List(JsReturn(5.toJs)))).block)
157 | val ifElse = Some(JsBlock(List(JsVarDef("c", JsBinOp("*", jb, 2.0.toJs)), JsUnOp("-", jc))))
158 | val jsIf = JsIf(JsBinOp(">", ja, 2.toJs), JsBinOp("*", JsBinOp("+", ja, jb), 2.toJs), ifElse)
159 | val bodyfunc5 = JsBlock(List(JsVarDef("b", 5.0.toJs), jsIf))
160 | val expectedFunc5 = JsFunDecl("func5", List("a"), bodyfunc5).block
161 | assert(js {
162 | def func5(a: Int) = {
163 | val b = 5.0
164 | if (a > 2)
165 | (a + b) * 2
166 | else {
167 | val c = b * 2.0
168 | -c
169 | }
170 | }
171 | } === expectedFunc5)
172 |
173 | val els = Some(JsCall(JsSelect(JsIdent("console"), "log"), List("a".toJs)))
174 | val func6Body = JsIf(JsBinOp(">", JsSelect(ja, "length"), 2.toJs), JsReturn(JsUnit), els).block
175 | assert(js { def func6(a: String): Unit = {
176 | if (a.length > 2) return else {
177 | console.log("a")
178 | }
179 | } } === JsFunDecl("func6",List("a"),func6Body).block)
180 | }
181 |
182 | test("Val access") {
183 | assert(javascript("String".jstr.length) === JsSelect(JsString("String"), "length"))
184 | }
185 |
186 | test("for to/until") {
187 | val ast = js {
188 | val a = Array(1, 2)
189 | for (i <- a(0) until a.length) print(a(i))
190 | for (i <- 0 to a.length) print(a(i))
191 | }
192 | val stmt = JsCall(JsIdent("print"), List(JsAccess(JsIdent("a"), JsIdent("i"))))
193 | val jsForUntil = JsFor(List(JsVarDef("i", JsAccess(JsIdent("a"), 0.toJs))), JsBinOp("<", JsIdent("i"), JsSelect(JsIdent("a"), "length")), List(JsUnOp("++", JsIdent("i"))), stmt)
194 | val jsForTo = JsFor(List(JsVarDef("i", 0.toJs)), JsBinOp("<=", JsIdent("i"), JsSelect(JsIdent("a"), "length")), List(JsUnOp("++", JsIdent("i"))), stmt)
195 | assert(ast === JsBlock(List(JsVarDef("a", JsArray(List(1.toJs, 2.toJs))), jsForUntil, jsForTo)))
196 | }
197 |
198 | test("for in") {
199 | val js = javascript {
200 | val a = Array(1, 2)
201 | val b = Seq("1", "2")
202 | val c = Map("1" -> 1, "2" -> 2)
203 | val d = new { val a = 1 }
204 | forIn(a) { i => print(i) }
205 | forIn(b) { i => print(i) }
206 | forIn(c) { i => print(i) }
207 | forIn(d) { i => print(i) }
208 | }
209 | /// FIXME: use === instead when scalatest for 2.11 is ready
210 | assert(js === JsBlock(List(
211 | JsVarDef("a", JsArray(List(1.toJs, 2.toJs))),
212 | JsVarDef("b", JsArray(List("1".toJs, "2".toJs))),
213 | JsVarDef("c", JsAnonObjDecl(List("1" -> 1.toJs, "2" -> 2.toJs))),
214 | JsVarDef("d", JsAnonObjDecl(List("a" -> 1.toJs))),
215 | JsForIn(JsIdent("i"), JsIdent("a"), JsCall(JsIdent("print"), List(JsIdent("i")))),
216 | JsForIn(JsIdent("i"), JsIdent("b"), JsCall(JsIdent("print"), List(JsIdent("i")))),
217 | JsForIn(JsIdent("i"), JsIdent("c"), JsCall(JsIdent("print"), List(JsIdent("i")))),
218 | JsForIn(JsIdent("i"), JsIdent("d"), JsCall(JsIdent("print"), List(JsIdent("i"))))
219 | )))
220 | }
221 |
222 | test("String operations") {
223 | val ast = js {
224 | val a = new JString("str")
225 | a.replace("s", "a").slice(1, 2)
226 | }
227 | val call = JsCall(JsSelect(JsCall(JsSelect(JsIdent("a"), "replace"), List("s".toJs, "a".toJs)), "slice"), List(1.toJs, 2.toJs))
228 | assert(ast === JsBlock(List(JsVarDef("a","str".toJs), call)))
229 | val ast1 = js("String".length)
230 | assert(ast1 === JsSelect(JsString("String"), "length"))
231 | }
232 |
233 | test("String interpolations") {
234 | val ast = js {
235 | val a = 1
236 | s"string"
237 | s"$a test"
238 | s"a = $a and string is ${a.toString}"
239 | }
240 | val oldRes = JsBinOp("+", JsBinOp("+", JsBinOp("+", "a = ".toJs, JsIdent("a")), " and string is ".toJs), JsCall(JsSelect(JsIdent("a"), "toString"), Nil))
241 | val res = if (util.Properties.versionNumberString.startsWith("2.13")) oldRes else JsBinOp("+", oldRes, "".toJs)
242 | assert(ast === JsVarDef("a", 1.toJs) ++
243 | "string".toJs ++
244 | JsBinOp("+", JsBinOp("+", "".toJs, JsIdent("a")), " test".toJs) ++ res
245 | )
246 |
247 | // JsBlock(List(JsVarDef(a,JsNum(1.0,false)), JsString(string), JsBinOp(+,JsBinOp(+,JsString(),JsIdent(a)),JsString( test)), JsBinOp(+,JsBinOp(+,JsBinOp(+, JsString(a = ),JsIdent(a)),JsString( and string is )),JsCall(JsSelect(JsIdent(a),toString),List()))))
248 | // JsBlock(List(JsVarDef(a,JsNum(1.0,false)), JsString(string), JsBinOp(+,JsBinOp(+,JsString(),JsIdent(a)),JsString( test)), JsBinOp(+,JsBinOp(+,JsBinOp(+,JsBinOp(+,JsString(a = ),JsIdent(a)),JsString( and string is )),JsCall(JsSelect(JsIdent(a),toString),List())),JsString())))
249 | }
250 |
251 | test("Arrays") {
252 | assert(js(Tuple1(1)) === JsArray(List(1.toJs)))
253 | assert(js((1, 2)) === JsArray(List(1.toJs, 2.toJs)))
254 | assert(js(("1", "2")) === JsArray(List("1".toJs, "2".toJs)))
255 | assert(js((1, 2, 3)) === JsArray(List(1.toJs, 2.toJs, 3.toJs)))
256 | assert(js((1, 2, 3, 4)) === JsArray(List(1.toJs, 2.toJs, 3.toJs, 4.toJs)))
257 |
258 | assert(js(Array(1, 2)) === JsArray(List(1.toJs, 2.toJs)))
259 | val arint = js {
260 | val a = new Array[Int](25)
261 | a(0) = 1
262 | a(0)
263 | }
264 | assert(arint.eval() === 1)
265 | assert(js(Array("1", "2")) === JsArray(List("1".toJs, "2".toJs)))
266 | val call = JsCall(JsSelect(JsIdent("console"), "log"), List(JsIdent("i")))
267 | val init = List(JsVarDef("iColl", JsIdent("a")), JsVarDef("iIdx", 0.toJs), JsVarDef("i", JsAccess(JsIdent("iColl"), JsIdent("iIdx"))))
268 | val check = JsBinOp("<", JsIdent("iIdx"), JsSelect(JsIdent("iColl"), "length"))
269 | val update = List(JsBinOp("=", JsIdent("i"), JsAccess(JsIdent("iColl"), JsUnOp("++", JsIdent("iIdx")))))
270 | assert(js{
271 | val a = Array("1", "2")
272 | for (i <- a) console.log(i)
273 |
274 | } === JsBlock(List(JsVarDef("a", JsArray(List("1".toJs, "2".toJs))), JsFor(init, check, update, call))))
275 | val call1 = JsCall(JsSelect(JsIdent("a"), "pop"), Nil)
276 | assert(js {
277 | val a = JArray("1", "2")
278 | a.pop()
279 | } === JsBlock(List(JsVarDef("a", JsArray(List("1".toJs, "2".toJs))), call1)))
280 |
281 | val assign = JsBinOp("=", JsAccess(JsIdent("a"), 0.toJs), JsAccess(JsIdent("a"), 1.toJs))
282 | val init1 = List(JsVarDef("iIdx", 0.toJs), JsVarDef("i", JsAccess(JsIdent("a"), JsIdent("iIdx"))))
283 | val check1 = JsBinOp("<", JsIdent("iIdx"), JsSelect(JsIdent("a"), "length"))
284 | val update1 = List(JsBinOp("=", JsIdent("i"), JsAccess(JsIdent("a"), JsUnOp("++", JsIdent("iIdx")))))
285 | val forStmt = JsFor(init1, check1, update1, JsCall(JsIdent("print"), List(JsIdent("i"))))
286 | assert(js {
287 | val a = JArray(1, 2)
288 | for (i <- a) print(i)
289 | a(0) = a(1)
290 | a.pop()
291 | } === JsBlock(List(JsVarDef("a", JsArray(List(1.toJs, 2.toJs))), forStmt, assign, call1)))
292 |
293 | val ast = js {
294 | val a = Seq("1", "2")
295 | val b = ArrayBuffer(1, 2)
296 | parseInt(a(0)) + b(1)
297 | }
298 | assert(ast.eval() === 3)
299 | }
300 |
301 | test("Math") {
302 | val ast = js(Math.abs(-1))
303 | assert(ast === JsCall(JsSelect(JsIdent("Math"), "abs"), List((-1.0).toJs)))
304 | }
305 |
306 | test("Global functions") {
307 | val ast = js(escape("asdf"))
308 | assert(ast === JsCall(JsIdent("escape"), List("asdf".toJs)))
309 | val ast1 = js(typeof("asdf"))
310 | assert(ast1 === JsCall(JsIdent("typeof"), List("asdf".toJs)))
311 | val ast2 = js {
312 | val a = include("[1, 2]").asInstanceOf[JArray[Int]]
313 | if (a.length > 1) {
314 | include("console.log(a[1])")
315 | }
316 | }
317 | val jsIf = JsIf(JsBinOp(">", JsSelect(JsIdent("a"), "length"), 1.toJs), JsRaw("console.log(a[1])"), None)
318 | assert(ast2 === JsBlock(List(JsVarDef("a", JsRaw("[1, 2]")), jsIf)))
319 | object L {
320 | class A
321 | def f(x: Int): Int = ???
322 | }
323 | val ast3 = js { L.f(3) }
324 | assert(ast3 === JsCall(JsIdent("f"), List(3.toJs)))
325 | val ast4 = js { new L.A() }
326 | assert(ast4 === JsNew(JsCall(JsIdent("A"), Nil)))
327 | }
328 |
329 | test("instanceof operator") {
330 | assert(js("".instanceof[String]) === JsBinOp("instanceof", "".toJs, JsIdent("String")))
331 | assert(js("".instanceof[Int]) === JsBinOp("instanceof", "".toJs, JsIdent("Number")))
332 | class Test(val a: String)
333 | assert(js("".instanceof[Test]) === JsBinOp("instanceof", "".toJs, JsIdent("Test")))
334 | // instanceof(tpname) form
335 | assert(js("".instanceof("String")) === JsBinOp("instanceof", "".toJs, JsIdent("String")))
336 | }
337 |
338 | test("delete operator") {
339 | val ast = js {
340 | val a = Map("field1" -> 1, "field2" -> 2)
341 | delete(a("field1"))
342 | a("field1") == undefined
343 | }
344 | assert(ast.eval() === true)
345 | val ast2 = js {
346 | val a = Array(1, 2)
347 | delete(a(1))
348 | a(1) == undefined
349 | }
350 | assert(ast2.eval() === true)
351 | }
352 |
353 | test("include") {
354 | val ast = javascript { include(javascript(1 + 2)) }
355 | assert(ast.eval() === 3.0)
356 | }
357 |
358 | test("RegExp") {
359 | val ast = js {
360 | val a = new RegExp("d.*", "g")
361 | a.exec("asdf").toString()
362 | }
363 | val call = JsCall(JsSelect(JsCall(JsSelect(JsIdent("a"), "exec"), List("asdf".toJs)), "toString"), Nil)
364 | assert(ast === JsBlock(List(JsVarDef("a",JsNew(JsCall(JsIdent("RegExp"),List("d.*".toJs, "g".toJs)))), call)))
365 | }
366 |
367 | test("Date") {
368 | val ast = js {
369 | val a = new Date()
370 | a.getDay().toString()
371 | }
372 | val call = JsCall(JsSelect(JsCall(JsSelect(JsIdent("a"), "getDay"), Nil), "toString"), Nil)
373 | assert(ast === JsBlock(List(JsVarDef("a", JsNew(JsCall(JsIdent("Date"), Nil))), call)))
374 | }
375 |
376 | test("Maps") {
377 | import collection.mutable
378 | // Immutable Map
379 | val ast = js {
380 | val a = Map("field" -> JArray(1, 2), ("field2", JArray(1)), "field3" -> JArray[Int]())
381 | a("field").pop().toString()
382 | }
383 | val call1 = JsCall(JsSelect(JsCall(JsSelect(JsAccess(JsIdent("a"), "field".toJs), "pop"), Nil), "toString"), Nil)
384 | val fields = List("field" -> JsArray(List(1.toJs, 2.toJs)), "field2" -> JsArray(List(1.toJs)), "field3" -> JsArray(Nil))
385 | assert(ast === JsBlock(List(JsVarDef("a", JsAnonObjDecl(fields)), call1)))
386 |
387 | // Mutable Map
388 | val ast1 = js {
389 | val a = mutable.Map("field" -> JArray(1, 2), ("field2", JArray(1)), "field3" → JArray[Int]())
390 | a("field") = a("field2")
391 | }
392 | val stmt = JsBinOp("=", JsAccess(JsIdent("a"), "field".toJs), JsAccess(JsIdent("a"), "field2".toJs))
393 | assert(ast1 === JsBlock(List(JsVarDef("a", JsAnonObjDecl(fields)), stmt)))
394 |
395 | // case class
396 | val ast2 = javascript {
397 | case class Test(n: String, id: Int)
398 | val t = Test("nau", 2)
399 | t.n
400 | }
401 | assert(JavascriptPrinter.simplify(ast2) === JsBlock(List(JsVarDef("t", JsAnonObjDecl(List(("n", "nau".toJs), ("id", 2.toJs)))), JsSelect(JsIdent("t"), "n"))))
402 | }
403 |
404 | test("Switch declaration") {
405 | val ast = javascript {
406 | val a: Any = "2"
407 | a match {
408 | case 1 | 2 => "1"
409 | case "2" => "2"
410 | case true => "true"
411 | case _ => "3"
412 | }
413 | }
414 | assert(ast.eval() === "2")
415 | }
416 |
417 | test("Try/catch/finally") {
418 | val ast = javascript {
419 | try { 1 } catch {
420 | case e: Exception => 2
421 | }
422 | }
423 | assert(ast.eval() === 1.0)
424 | val ast1 = javascript { try { 1 } finally { print(2) }}
425 | assert(ast1 === JsTry(1.toJs, None, Some(JsCall(JsIdent("print"), List(2.toJs)))))
426 | val ast2 = javascript {
427 | try { 1 } catch {
428 | case e: Exception => 2
429 | } finally { print(3) }
430 | }
431 | assert(ast2 === JsTry(1.toJs, Some(JsCatch(JsIdent("e"), 2.toJs)), Some(JsCall(JsIdent("print"), List(3.toJs)))))
432 | }
433 |
434 | test("Throw") {
435 | val ast = javascript(throw new IndexOutOfBoundsException())
436 | /// FIXME: use === instead when scalatest for 2.11 is ready
437 | assert(ast == JsThrow(JsNew(JsCall(JsSelect(JsIdent("package"), "IndexOutOfBoundsException"), Nil))))
438 | }
439 |
440 | test("Object declaration") {
441 | val ast = javascript {
442 | class A(val arg1: String, var arg2: Int, arg3: Array[String]) {
443 | val field = 1
444 | var count = arg2
445 | count = 5
446 | def func1(i: Int) = field
447 | def func2(i: Int) = arg3(i)
448 | def func3() = arg3
449 | def func4() = func3()(0)
450 | def func5() = count
451 | }
452 | val a = new A("a", 1, Array("111", "222"))
453 | a.arg1 + a.func2(a.arg2) + a.field + a.func4() + a.func5()
454 | }
455 | assert(ast.eval() === "a22211115")
456 | }
457 |
458 | test("Lazy injection") {
459 | val x = 15
460 | val y = "hehe"
461 | val bool = true
462 | def f() = "1"
463 | val map = Map("a" -> JsString("b"))
464 | val map1 = Map("a" -> "c")
465 | val map2 = Map("a" -> true)
466 | val seq = List(1, 2)
467 | case class My(a: String)
468 | implicit def zzz: JsSerializer[My] = new JsSerializer[My] { def apply(a: My) = JsString(a.a) }
469 | val z = My("my")
470 | val ls = Seq(1, 2, 3).map(_.toJs)
471 | def genJsAst() = "gen".toJs
472 | val ast = javascript {
473 | val a = inject(x)
474 | val b = inject(y)
475 | val c = inject(z)
476 | val d = inject(f _)
477 | val e = inject(ls)
478 | val j = inject(bool)
479 | val k = inject(map)
480 | val l = inject(map1)
481 | val m = inject(map2)
482 | val n = inject(seq)
483 | val o = inject(JsNull)
484 | val gen = genJsAst()
485 | a.as[String] + b + c + d + e.toString() + gen + j + k("a") + l("a") + m("a") + n(0) + o
486 | }
487 | assert(ast.eval() === "15hehemy11,2,3gentruebctrue1null")
488 | }
489 |
490 | test("() => JsAst injection") {
491 | var t = 1
492 | def handle: Int => Unit = (i: Int) => { t = i }
493 | def ajax(f: Int => Unit) = {
494 | f(2)
495 | javascript { (y: Int) => y.toString() }
496 | }
497 |
498 | val js = javascript {
499 | val x = 10
500 | val s = ajax(handle)(x)
501 | "JavaScript " + s
502 | }
503 | js.asString
504 | assert(t === 2)
505 | assert(js.eval() === "JavaScript 10")
506 | }
507 |
508 | test("Implicit conversions") {
509 | val ast = javascript {
510 | val a: JString = "asdf"
511 | val b: JArray[Int] = Array(1, 2)
512 | val c: JArray[String] = ArrayBuffer("1", "2")
513 | def f(s: String, b: Array[Int], c: Array[String]) = s
514 | f(a, b, c)
515 | }
516 | assert(ast.eval() === "asdf" )
517 | }
518 |
519 | test("JsDynamic") {
520 | val $ = new JsDynamic {}
521 | class XmlHttpRequest(s: String) extends JsDynamic
522 | val ast = javascript {
523 | val a = new XmlHttpRequest("request")
524 | a.foo("reply")
525 | $("button", this).click(() => $("p").css("color", "red"))
526 | $.field = $.select
527 | }
528 | val request = JsVarDef("a",JsNew(JsCall(JsIdent("XmlHttpRequest"),List("request".toJs))))
529 | val foo = JsCall(JsSelect(JsIdent("a"),"foo"),List("reply".toJs))
530 | val returnExpr = JsCall(JsSelect(JsCall(JsSelect(JsIdent("$"), "apply"), List("p".toJs)), "css"), List("color".toJs, "red".toJs))
531 | val anonFunDecl = JsAnonFunDecl(List(), JsReturn(returnExpr).block)
532 | val call = JsCall(JsSelect(JsCall(JsSelect(JsIdent("$"), "apply"), List("button".toJs, JsIdent("this"))), "click"), List(anonFunDecl))
533 | val update = JsBinOp("=", JsSelect(JsIdent("$"), "field"), JsSelect(JsIdent("$"), "select"))
534 | assert(ast === JsBlock(List(request, foo, call, update)))
535 | }
536 |
537 | test("Traits") {
538 | val ast = javascript {
539 | trait A {
540 | def f1() = 1
541 | def abstr(i: Int): String
542 | val self = this
543 | }
544 | trait B
545 | trait C extends B
546 | class D(p: String) extends A with C {
547 | def abstr(i: Int) = i.toString()
548 | }
549 | def bar(a: A) = a.abstr(a.self.f1())
550 | val a = new D("Test")
551 | bar(a)
552 | }
553 | // println(ast.asString)
554 | assert(ast.eval() === "1")
555 | }
556 |
557 | test("Bug #23") {
558 | def m1 = javascript { "string" }
559 | def m2() = javascript { "string" }
560 |
561 | val ast = javascript {
562 | m1.as[String] + m2()
563 | }
564 | assert(ast.eval() === "stringstring")
565 | }
566 | }
567 |
--------------------------------------------------------------------------------
/jscala/src/main/scala/org/jscala/BasisConverter.scala:
--------------------------------------------------------------------------------
1 | package org.jscala
2 |
3 | import scala.reflect.macros.blackbox
4 |
5 | /**
6 | * Author: Alexander Nemish
7 | * Date: 10/25/13
8 | * Time: 10:50 PM
9 | */
10 | trait BasisConverter[C <: blackbox.Context] extends MacroHelpers[C] {
11 | import c.universe._
12 |
13 | class DieException(val msg: String, val t: Tree) extends RuntimeException
14 |
15 | protected def die(msg: String): ToExpr[Nothing] = new ToExpr[Nothing] {
16 |
17 | def isDefinedAt(x: Tree): Boolean = true
18 |
19 | def apply(v1: Tree): Nothing = throw new DieException(msg, v1)
20 | }
21 |
22 | protected def funParams(args: List[Tree]): Tree = {
23 | val filteredDefaults = args collect {
24 | case arg@Select(_, n) if n.decodedName.toString.contains("$default$") => None
25 | case arg@Ident(n) if n.decodedName.toString.contains("$default$") => None
26 | case Typed(exp, _) => Some(jsExprOrDie(exp))
27 | case arg => Some(jsExprOrDie(arg))
28 | }
29 | listToExpr(filteredDefaults.flatten)
30 | }
31 |
32 | protected val unaryOps = Seq("+", "-", "!")
33 | protected val encodedUnaryOpsMap = unaryOps.map(op => TermName(s"unary_$op").encodedName -> op).toMap
34 | protected val binOps = Seq("*", "/", "%", "+", "-", "<<", ">>", ">>>",
35 | "<", ">", "<=", ">=",
36 | "==", "!=", "&", "|", "^", "&&", "||")
37 | protected val encodedBinOpsMap = binOps.map(op => TermName(op).encodedName -> op).toMap
38 |
39 | protected lazy val jsString: PFT[String] = {
40 | case Literal(Constant(value: Char)) => value.toString
41 | case Literal(Constant(value: String)) => value
42 | }
43 | protected lazy val jsStringLit: ToExpr[JsString] = jsString.andThen(s => q"org.jscala.JsString($s)")
44 |
45 | protected lazy val jsNumLit: ToExpr[JsNum] = {
46 | case Literal(Constant(value: Byte)) => q"org.jscala.JsNum($value, isFloat = false)"
47 | case Literal(Constant(value: Short)) => q"org.jscala.JsNum($value, isFloat = false)"
48 | case Literal(Constant(value: Int)) => q"org.jscala.JsNum($value, isFloat = false)"
49 | case Literal(Constant(value: Long)) => q"org.jscala.JsNum($value, isFloat = false)"
50 | case Literal(Constant(value: Float)) => q"org.jscala.JsNum($value, isFloat = true)"
51 | case Literal(Constant(value: Double)) => q"org.jscala.JsNum($value, isFloat = true)"
52 | }
53 | protected lazy val jsBoolLit: ToExpr[JsBool] = {
54 | case Literal(Constant(value: Boolean)) => q"org.jscala.JsBool($value)"
55 | }
56 | protected object jsUnitLit extends PartialFunction[Tree, Tree] {
57 | def apply(v1: Tree) = q"org.jscala.JsUnit"
58 | def isDefinedAt(x: Tree) = isUnit(x)
59 | }
60 | protected object jsNullLit extends PartialFunction[Tree, Tree] {
61 | def apply(v1: Tree) = q"org.jscala.JsNull"
62 | def isDefinedAt(x: Tree) = isNull(x)
63 | }
64 |
65 | protected val jsLit: ToExpr[JsLit] = {
66 | jsStringLit orElse jsNumLit orElse jsBoolLit orElse jsNullLit orElse jsUnitLit
67 | }
68 |
69 | protected lazy val jsThis: ToExpr[JsIdent] = {
70 | case This(name) => q"""org.jscala.JsIdent("this")"""
71 | }
72 |
73 | protected lazy val jsIdent: ToExpr[JsIdent] = {
74 | case id@Ident(TermName(name)) if id.symbol.isMethod && id.symbol.asMethod.returnType <:< typeOf[JsAst] => q"org.jscala.JsLazy(() => $id)"
75 | case Ident(Name(name)) => q"org.jscala.JsIdent($name)"
76 | }
77 |
78 | protected lazy val jsJStringExpr: ToExpr[JsExpr] = {
79 | case Apply(Select(New(Select(Select(Ident(Name("org")), Name("jscala")), Name("JString"))), _), List(Literal(Constant(str: String)))) =>
80 | q"org.jscala.JsString($str)"
81 | }
82 |
83 | protected lazy val jsSelect: ToExpr[JsExpr] = {
84 | case Select(Select(Select(Ident(Name("scala")), Name("scalajs")), Name("js")), Name(name)) => q"""org.jscala.JsIdent($name)"""
85 | // case q"scala.scalajs.js.$name" => q"""org.jscala.JsIdent($name)"""
86 | case q"org.scalajs.dom.`package`.${TermName(name)}" => q"""org.jscala.JsIdent($name)"""
87 | // org.jscala.package.$ident => $ident
88 | case Select(Select(Select(Ident(Name("org")), Name("jscala")), Name("package")), Name(name)) =>
89 | q"org.jscala.JsIdent($name)"
90 | // org.jscala.$ident => $ident
91 | case Select(Select(Ident(Name("org")), Name("jscala")), Name(name)) =>
92 | q"org.jscala.JsIdent($name)"
93 | // objectname.$ident => $ident
94 | case s@Select(q@Ident(_), name) if q.symbol.isModule => jsExprOrDie(Ident(name))
95 | case Select(q, name) =>
96 | q"org.jscala.JsSelect(${jsExprOrDie(q)}, ${name.decodedName.toString})"
97 | }
98 |
99 | protected lazy val jsBlock: ToExpr[JsBlock] = {
100 | case Block(stmts, expr) =>
101 | val stmtTrees = if (expr.equalsStructure(q"()")) stmts else stmts :+ expr
102 | val ss = listToExpr(stmtTrees map jsStmtOrDie)
103 | q"org.jscala.JsBlock($ss)"
104 | }
105 |
106 | protected lazy val jsUnaryOp: ToExpr[JsUnOp] = {
107 | case q"$q.$n" if encodedUnaryOpsMap.contains(n) =>
108 | val op = encodedUnaryOpsMap(n)
109 | q"org.jscala.JsUnOp($op, ${jsExprOrDie(q)})"
110 | }
111 |
112 | protected lazy val jsBinOp: ToExpr[JsBinOp] = {
113 | case q"$q.$n($rhs)" if encodedBinOpsMap.contains(n) =>
114 | val op = encodedBinOpsMap(n)
115 | val opExpr = q"$op"
116 | val qExpr = jsExprOrDie(q)
117 | val rhsExpr = jsExprOrDie(rhs)
118 | // generate correct whole number devision JavaScript if a and b are [Byte,Short,Int,Long]: a/b|0
119 | if (op == "/" && q.isNum && rhs.isNum)
120 | q"""org.jscala.JsBinOp("|", org.jscala.JsBinOp($opExpr, $qExpr, $rhsExpr), org.jscala.JsNum(0, false))"""
121 | else q"org.jscala.JsBinOp($opExpr, $qExpr, $rhsExpr)"
122 | case Assign(lhs, rhs) =>q"""org.jscala.JsBinOp("=", ${jsExprOrDie(lhs)}, ${jsExprOrDie(rhs)})"""
123 | }
124 |
125 | protected lazy val jsTupleExpr: PFT[(Tree, Tree)] = {
126 | case Apply(TypeApply(Select(Apply(TypeApply(path, _), List(lhs)), arrow), _), List(rhs))
127 | if path.isArrow && arrow.isArrow => lhs -> rhs
128 | case Apply(TypeApply(path, _), List(lhs, rhs)) if path.is("scala.Tuple2.apply") => lhs -> rhs
129 | }
130 |
131 | protected lazy val jsStringInterpolation: ToExpr[JsExpr] = {
132 | case q"scala.StringContext.apply(..$args).s(..$exprs)" =>
133 | val at = args.map(a => q"org.jscala.JsString($a)")
134 | val es = exprs.map(e => jsExprOrDie(e))
135 | val ls = es.zip(at.tail).flatMap { case (e, a) => List(e, a) }
136 | val r = ls.foldLeft(at.head){case (r, a) => q"""org.jscala.JsBinOp("+", $r, $a)"""}
137 | r
138 | }
139 |
140 | protected lazy val jsStringHelpersExpr: ToExpr[JsExpr] = {
141 | case q"$str.length()" if str.tpe.widen =:= typeOf[String] =>
142 | q"""org.jscala.JsSelect(${jsExprOrDie(str)}, "length")"""
143 | }
144 |
145 |
146 | protected lazy val jsNewExpr: ToExpr[JsExpr] = {
147 | case Apply(Select(New(Ident(ident)), _), args) =>
148 | val params = funParams(args)
149 | q"org.jscala.JsNew(org.jscala.JsCall(org.jscala.JsIdent(${ident.decodedName.toString}), $params))"
150 | case Apply(Select(New(path), _), args) =>
151 | val params = funParams(args)
152 | q"org.jscala.JsNew(org.jscala.JsCall(${jsExprOrDie(path)}, $params))"
153 | }
154 |
155 | protected lazy val jsCallExpr: ToExpr[JsExpr] = {
156 | case Apply(Select(lhs, name), List(rhs)) if name.decodedName.toString.endsWith("_=") =>
157 | q"""org.jscala.JsBinOp("=", org.jscala.JsSelect(${jsExprOrDie(lhs)}, ${name.decodedName.toString.dropRight(2)}), ${jsExprOrDie(rhs)})"""
158 | case Apply(Apply(Select(sel, Name("applyDynamic")), List(Literal(Constant(name: String)))), args) =>
159 | val callee = q"org.jscala.JsSelect(${jsExprOrDie(sel)}, $name)"
160 | val params = listToExpr(args.map(jsExprOrDie))
161 | q"org.jscala.JsCall($callee, $params)"
162 | case Apply(Apply(Select(sel, Name("updateDynamic")), List(Literal(Constant(name: String)))), List(arg)) =>
163 | val callee = q"org.jscala.JsSelect(${jsExprOrDie(sel)}, $name)"
164 | q"""org.jscala.JsBinOp("=", $callee, ${jsExprOrDie(arg)})"""
165 | case Apply(Select(sel, Name("selectDynamic")), List(Literal(Constant(name: String)))) =>
166 | q"org.jscala.JsSelect(${jsExprOrDie(sel)}, $name)"
167 | case app@Apply(fun, args) if app.tpe <:< typeOf[JsAst] =>
168 | val expr = c.Expr[JsAst](app)
169 | q"org.jscala.JsLazy(() => $expr)"
170 | case Apply(TypeApply(fun, _), args) =>
171 | val callee = jsExprOrDie apply fun
172 | val params = funParams(args)
173 | q"org.jscala.JsCall($callee, $params)"
174 | case Apply(fun, args) =>
175 | val callee = jsExprOrDie apply fun
176 | val params = funParams(args)
177 | q"org.jscala.JsCall($callee, $params)"
178 | }
179 |
180 | protected def jsExpr: ToExpr[JsExpr]
181 |
182 | protected val jsExprOrDie: ToExpr[JsExpr] = jsExpr orElse die("Unsupported syntax")
183 |
184 | protected def jsStmt: ToExpr[JsStmt]
185 |
186 | protected val jsStmtOrDie: ToExpr[JsStmt] = jsStmt orElse die("Unsupported syntax")
187 | }
188 |
--------------------------------------------------------------------------------
/jscala/src/main/scala/org/jscala/CollectionConverter.scala:
--------------------------------------------------------------------------------
1 | package org.jscala
2 |
3 | import scala.reflect.macros.blackbox
4 |
5 | /**
6 | * Author: Alexander Nemish
7 | */
8 | trait CollectionConverter[C <: blackbox.Context] extends BasisConverter[C] {
9 | import c.universe._
10 |
11 | private lazy val jsSeqExpr: ToExpr[JsExpr] = {
12 | case t if t.tpe.baseClasses.contains(seqSym) => jsExpr(t)
13 | }
14 |
15 | protected lazy val jsIterableExpr: ToExpr[JsExpr] = jsSeqExpr orElse jsArrayIdentOrExpr
16 |
17 | private lazy val jsArrayIdentOrExpr: ToExpr[JsExpr] = jsArrayIdent orElse jsArrayExpr
18 |
19 | private lazy val jsArrayIdent: ToExpr[JsIdent] = {
20 | case i @ Ident(name) if i.tpe.baseClasses.contains(arraySym) => q"org.jscala.JsIdent(${name.decodedName.toString})"
21 | }
22 |
23 | protected lazy val jsArrayExpr: ToExpr[JsExpr] = {
24 | // Array creation
25 | case Apply(TypeApply(path, _), args) if path.is("org.jscala.JArray.apply") =>
26 | val params = listToExpr(args map jsExprOrDie)
27 | q"org.jscala.JsArray($params)"
28 | case TypeApply(path, args) if path.is("scala.Array.apply") =>
29 | val params = listToExpr(args map jsExprOrDie)
30 | q"org.jscala.JsArray($params)"
31 | case Apply(Apply(TypeApply(path, _), args), _) if path.is("scala.Array.apply") =>
32 | val params = listToExpr(args map jsExprOrDie)
33 | q"org.jscala.JsArray($params)"
34 | case TypeApply(path, args) if path.is("scala.Array.apply") =>
35 | val params = listToExpr(args map jsExprOrDie)
36 | q"org.jscala.JsArray($params)"
37 | case Apply(path, args) if path.is("scala.Array.apply") =>
38 | val params = listToExpr(args map jsExprOrDie)
39 | q"org.jscala.JsArray($params)"
40 | case Apply(Ident(Name("Array")), args) =>
41 | val params = listToExpr(args map jsExprOrDie)
42 | q"org.jscala.JsArray($params)"
43 | case Apply(Select(New(AppliedTypeTree(Ident(TypeName("Array")), _)), ctor), List(Literal(Constant(_)))) if ctor == termNames.CONSTRUCTOR =>
44 | q"org.jscala.JsArray(Nil)"
45 | // new Array[Int](256)
46 | case Apply(Select(a@New(t@TypeTree()), ctor), List(Literal(Constant(_))))
47 | if ctor == termNames.CONSTRUCTOR && t.original.isInstanceOf[AppliedTypeTree @unchecked] && t.original.asInstanceOf[AppliedTypeTree].tpt.equalsStructure(Select(Ident(TermName("scala")), TypeName("Array"))) =>
48 | q"org.jscala.JsArray(Nil)"
49 | case Apply(Select(a@New(t@TypeTree()), ctor), List(Literal(Constant(_)))) =>
50 | q"org.jscala.JsArray(Nil)"
51 | case Apply(TypeApply(Select(path, Name("apply")), _), args) if path.tpe.baseClasses.contains(seqFactorySym) =>
52 | val params = listToExpr(args map jsExprOrDie)
53 | q"org.jscala.JsArray($params)"
54 | // Array access
55 | case Apply(Select(path, Name("apply")), List(idx)) if isArray(path) =>
56 | q"org.jscala.JsAccess(${jsExprOrDie(path)}, ${jsExprOrDie(idx)})"
57 | // Array update
58 | case Apply(Select(path, Name("update")), List(key, value)) if isArray(path) =>
59 | q"""org.jscala.JsBinOp("=", org.jscala.JsAccess(${jsExprOrDie(path)}, ${jsExprOrDie(key)}), ${jsExprOrDie(value)})"""
60 | // arrayOps
61 | case Apply(TypeApply(path, _), List(body)) if path.is("scala.Predef.refArrayOps") => jsArrayIdentOrExpr(body)
62 | case Apply(Select(path, Name(ops)), List(body)) if path.is("scala.Predef") && ops.endsWith("ArrayOps") => jsArrayIdentOrExpr(body)
63 |
64 | // Tuples
65 | case Apply(TypeApply(Select(Select(Ident(Name("scala")), Name(tuple)), Name("apply")), _), args) if tuple.contains("Tuple") =>
66 | val params = listToExpr(args map jsExprOrDie)
67 | q"org.jscala.JsArray($params)"
68 | }
69 |
70 | private def genMap(args: List[Tree]) = {
71 | val fields = for (arg <- args) yield {
72 | val (lhs, rhs) = jsTupleExpr(arg)
73 | jsString.applyOrElse(lhs, (t: Tree) => c.abort(arg.pos, "Map key type can only be String")) -> jsExprOrDie(rhs)
74 | }
75 | val params = listToExpr(fields.map { case (n, v) => q"($n, $v)" })
76 | q"org.jscala.JsAnonObjDecl($params)"
77 | }
78 |
79 |
80 | protected lazy val jsMapExpr: ToExpr[JsExpr] = {
81 | case Apply(TypeApply(Select(path, Name("apply")), _), args) if path.tpe.baseClasses.contains(mapFactorySym) =>
82 | genMap(args)
83 | case Apply(Select(path, Name("apply")), List(index)) if path.tpe.baseClasses.contains(mapSym) =>
84 | q"org.jscala.JsAccess(${jsExprOrDie(path)}, ${jsExprOrDie(index)})"
85 | case Apply(Select(path, Name("update")), List(key, value)) if path.tpe.baseClasses.contains(mapSym) =>
86 | q"""org.jscala.JsBinOp("=", org.jscala.JsAccess(${jsExprOrDie(path)}, ${jsExprOrDie(key)}), ${jsExprOrDie(value)})"""
87 | }
88 |
89 | protected lazy val jsForStmt: ToExpr[JsStmt] = {
90 | /*
91 | for (index <- from until untilExpr) body
92 | for (index <- from to untilExpr) body
93 | */
94 | case Apply(TypeApply(Select(Apply(Select(Apply(fn, List(from)), n@Name("until"|"to")), List(endExpr)), Name("foreach")), _),
95 | List(Function(List(ValDef(_, Name(index), _, _)), body))) if fn.is("scala.Predef.intWrapper") =>
96 | val forBody = jsStmtOrDie(body)
97 | val fromExpr = jsExprOrDie(from)
98 | val init = q"org.jscala.JsVarDef($index, $fromExpr)"
99 | val check = if (n.decodedName.toString == "until")
100 | q"""org.jscala.JsBinOp("<", org.jscala.JsIdent($index), ${jsExprOrDie(endExpr)})"""
101 | else q"""org.jscala.JsBinOp("<=", org.jscala.JsIdent($index), ${jsExprOrDie(endExpr)})"""
102 | val update = q"""org.jscala.JsUnOp("++", org.jscala.JsIdent($index))"""
103 | q"org.jscala.JsFor(List($init), $check, List($update), $forBody)"
104 | /*
105 | val coll = Seq(1, 2)
106 | for (ident <- coll) body
107 | */
108 | case Apply(TypeApply(Select(Apply(TypeApply(Select(path, Name(n)), _), List(Ident(Name(coll)))), Name("foreach")), _), List(Function(List(ValDef(_, Name(ident), _, _)), body)))
109 | if path.is("org.jscala.package") && n.startsWith("implicit") => forStmt(ident, coll, body)
110 | /*
111 | forIn(ident, coll) body
112 | */
113 | case app@Apply(Apply(TypeApply(path, _), List(coll)), List(Function(List(ValDef(_, Name(ident), _, _)), body))) if path.is("org.jscala.package.forIn") =>
114 | q"org.jscala.JsForIn(org.jscala.JsIdent($ident), ${jsExprOrDie(coll)}, ${jsStmtOrDie(body)})"
115 |
116 | /*
117 | coll.foreach(item)
118 | */
119 | case Apply(TypeApply(Select(collTree, Name("foreach")), _), List(Function(List(ValDef(_, Name(ident), _, _)), body))) if jsIterableExpr.isDefinedAt(collTree) =>
120 | val collExpr = jsIterableExpr(collTree)
121 | val coll = s"${ident}Coll"
122 | val idx = s"${ident}Idx"
123 | val seq = q"org.jscala.JsIdent($coll)"
124 | val len = q"""org.jscala.JsSelect($seq, "length")"""
125 | val init = q"List(org.jscala.JsVarDef($coll, $collExpr), org.jscala.JsVarDef($idx, org.jscala.JsNum(0, false)), org.jscala.JsVarDef($ident, org.jscala.JsAccess($seq, org.jscala.JsIdent($idx))))"
126 | val check = q"""org.jscala.JsBinOp("<", org.jscala.JsIdent($idx), $len)"""
127 | val update = q"""org.jscala.JsBinOp("=", org.jscala.JsIdent($ident), org.jscala.JsAccess(org.jscala.JsIdent($coll), org.jscala.JsUnOp("++", org.jscala.JsIdent($idx))))"""
128 | val forBody = jsStmtOrDie(body)
129 | q"org.jscala.JsFor($init, $check, List($update), $forBody)"
130 | }
131 |
132 | private def forStmt(ident: String, coll: String, body: Tree) = {
133 | val idx = s"${ident}Idx"
134 | val seq = q"org.jscala.JsIdent($coll)"
135 | val len = q"""org.jscala.JsSelect($seq, "length")"""
136 | val init = q"List(org.jscala.JsVarDef($idx, org.jscala.JsNum(0, false)), org.jscala.JsVarDef($ident, org.jscala.JsAccess($seq, org.jscala.JsIdent($idx))))"
137 | val check = q"""org.jscala.JsBinOp("<", org.jscala.JsIdent($idx), $len)"""
138 | val update = q"""org.jscala.JsBinOp("=", org.jscala.JsIdent($ident), org.jscala.JsAccess(org.jscala.JsIdent($coll), org.jscala.JsUnOp("++", org.jscala.JsIdent($idx))))"""
139 | val forBody = jsStmtOrDie(body)
140 | q"org.jscala.JsFor($init, $check, List($update), $forBody)"
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/jscala/src/main/scala/org/jscala/JavascriptPrinter.scala:
--------------------------------------------------------------------------------
1 | package org.jscala
2 |
3 | object JavascriptPrinter {
4 | private[this] val substitutions = Map("\\\"".r -> "\\\\\"", "\\n".r -> "\\\\n", "\\r".r -> "\\\\r", "\\t".r -> "\\\\t")
5 | def simplify(ast: JsAst): JsAst = ast match {
6 | case JsBlock(stmts) => JsBlock(stmts.filter(_ != JsUnit))
7 | case JsCase(const, JsBlock(List(stmt))) => JsCase(const, stmt)
8 | case JsDefault(JsBlock(List(stmt))) => JsDefault(stmt)
9 | case t => t
10 | }
11 |
12 | def print(ast: JsAst, indent: Int): String = {
13 | def ind(c: Int = 0) = " " * (indent + c)
14 | def p(ast: JsAst) = print(ast, indent)
15 | def p2(ast: JsAst) = ind(2) + p3(ast)
16 | def p3(ast: JsAst) = print(ast, indent + 2)
17 | def p4(ast: JsAst) = ind() + p(ast)
18 | def s(ast: JsAst) = ast match {
19 | case _: JsLit => p(ast)
20 | case _: JsIdent => p(ast)
21 | case _: JsCall => p(ast)
22 | case s => s"(${p(ast)})"
23 | }
24 | def !< = "{\n"
25 | def !> = "\n" + ind() + "}"
26 | simplify(ast) match {
27 | case JsLazy(f) => p(f())
28 | case JsNull => "null"
29 | case JsBool(value) => value.toString
30 | case JsString(value) => "\"" + substitutions.foldLeft(value){case (v, (r, s)) => r.replaceAllIn(v, s)} + "\""
31 | case JsNum(value, true) => value.toString
32 | case JsNum(value, false) => value.toLong.toString
33 | case JsArray(values) => values.map(p).mkString("[", ", ", "]")
34 | case JsIdent(value) => value
35 | case JsRaw(value) => value
36 | case JsAccess(qual, key) => s"${p(qual)}[${p(key)}]"
37 | case JsSelect(qual, "apply") => p(qual)
38 | case JsSelect(qual, name) => s"${p(qual)}.$name"
39 | case JsUnOp(operator, operand) => operator + s(operand)
40 | case JsBinOp("=", lhs, rhs) => s"${p(lhs)} = ${p(rhs)}"
41 | case JsBinOp(operator, lhs: JsBinOp, rhs: JsBinOp) => s"${s(lhs)} $operator ${s(rhs)}"
42 | case JsBinOp(operator, lhs: JsBinOp, rhs) => s"${s(lhs)} $operator ${p(rhs)}"
43 | case JsBinOp(operator, lhs, rhs: JsTernary) => s"${s(lhs)} $operator ${s(rhs)}"
44 | case JsBinOp(operator, lhs, rhs: JsBinOp) => s"${p(lhs)} $operator ${s(rhs)}"
45 | case JsBinOp(operator, lhs, rhs) => s"${p(lhs)} $operator ${p(rhs)}"
46 | case JsNew(call) => s"new ${p(call)}"
47 | case expr@JsCall(JsSelect(callee: JsLazy[_], "apply"), params) => s"""(${p(callee)})(${params.map(p(_)).mkString(", ")})"""
48 | case JsCall(JsSelect(callee: JsAnonFunDecl, "apply"), params) => s"""(${p(callee)})(${params.map(p(_)).mkString(", ")})"""
49 | case JsCall(callee, params) => s"""${p(callee)}(${params.map(p(_)).mkString(", ")})"""
50 | case JsBlock(Nil) => "{}"
51 | case JsBlock(stmts) => !< + stmts.map(p2(_) + ";\n").mkString + ind() + "}"
52 | case JsTernary(cond, thenp, elsep) => s"${s(cond)} ? ${p(thenp)} : ${p(elsep)}"
53 | case JsIf(cond, expr: JsExpr, Some(els: JsExpr)) => s"${s(cond)} ? ${p(expr)} : ${p(els)}"
54 | case JsIf(cond, thenp, elsep) => s"if (${p(cond)}) ${p(thenp)}" + elsep.map(e => s" else ${p(e)}").getOrElse("")
55 | case JsSwitch(expr, cases, default) => s"switch (${p(expr)}) " +
56 | !< + cases.map(p2).mkString("\n") + default.map(d => "\n" + p2(d)).getOrElse("") + !>
57 | case JsCase(consts, body) => consts.map(c => s"case ${p(c)}:\n").mkString(ind()) + p2(body) + ";\n" + ind(2) + "break;"
58 | case JsDefault(body) => "default:\n" + p2(body) + ";\n" + ind(2) + "break;"
59 | case JsWhile(cond, body) => s"while (${p(cond)}) ${p(body)}"
60 | case JsTry(body, cat, fin) =>
61 | val (b, c, f) = (p(body), cat.map(p2).getOrElse(""), fin.map(f => s"finally {${p2(f)}\n}").getOrElse(""))
62 | s"try { $b \n} $c \n $f"
63 | case JsCatch(JsIdent(ident), body) => s"catch($ident) {\n${p2(body)}\n}"
64 | case JsFor(init, check, update, body) =>
65 | val in = init.collect { case JsVarDef(ident, init) => s"$ident = ${p(init)}" }.mkString("var ", ", ", "")
66 | val upd = update.map(p).mkString(", ")
67 | s"for ($in; ${p(check)}; $upd) ${p(body)}"
68 | case JsForIn(JsIdent(ident), coll, body) => s"for (var $ident in ${p(coll)}) ${p(body)}"
69 | case JsVarDef(ident, init) => s"var $ident" + (init match {
70 | case JsUnit => ""
71 | case init => " = " + p(init)
72 | })
73 | case JsFunDecl(ident, params, body) => s"""function $ident(${params.mkString(", ")}) ${p(body)}"""
74 | case JsAnonFunDecl(params, body) => s"""function (${params.mkString(", ")}) ${p3(body)}"""
75 | case JsAnonObjDecl(fields) =>
76 | if (fields.isEmpty) "{}" else fields.map{ case (k, v) => ind(2) + s""""$k": ${p(v)}"""}.mkString(!<, ",\n", !>)
77 | case JsObjDecl(name, JsFunDecl(_, params, JsBlock(stmts)), fields) =>
78 | val fs = for ((n, v) <- fields) yield ind(2) + s"this.$n = ${p(v)};"
79 | val body = fs ++ stmts.map(s => ind(2) + p(s)) mkString "\n"
80 | s"""function $name(${params.mkString(", ")}) {\n$body\n${ind()}}"""
81 | case JsReturn(jsExpr) => s"return ${p(jsExpr)}"
82 | case JsUnit => ""
83 | case JsStmts(stmts) => p(stmts.head) + ";\n" + stmts.tail.map(p4(_)).mkString(";\n")
84 | }
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/jscala/src/main/scala/org/jscala/JsGlobal.scala:
--------------------------------------------------------------------------------
1 | package org.jscala
2 |
3 | import language.dynamics
4 | import scala.util.matching.Regex
5 | import java.lang
6 |
7 | class JString(s: String) {
8 | val length = 0
9 | def charAt(i: Int) = ""
10 | def charCodeAt(i: Int) = 0
11 | def indexOf(item: String): Int = 0
12 | def lastIndexOf(item: String): Int = 0
13 | def `match`(regexp: Regex): JArray[JString] = JArray()
14 | def replace(searchvalue: Regex, newvalue: String) = this
15 | def replace(searchvalue: String, newvalue: String) = this
16 | def search(searchvalue: String) = 0
17 | def search(searchvalue: Regex) = 0
18 | def slice(start: Int, end: Int = 0) = 0
19 | def split(separator: Int, end: Int = 0) = 0
20 | }
21 |
22 | case class JArray[A](args: A*) {
23 | val length = 0
24 | def apply(idx: Int): A = null.asInstanceOf[A]
25 | def update(idx: Int, value: A): Unit = {}
26 | def concat(arrays: JArray[A]*): JArray[A] = this
27 | def indexOf(item: A): Int = 0
28 | def lastIndexOf(item: A): Int = 0
29 | def join(): JString = null
30 | def push(items: A*) = this
31 | def pop(): A = null.asInstanceOf[A]
32 | def reverse() = this
33 | def shift(): A = null.asInstanceOf[A]
34 | def unshift(items: A): Int = 0
35 | def slice(start: Int, end: Int) = this
36 | def sort(f: (A, A) => Int = (_, _) => 0) = this
37 | def splice(index: Int, howmany: Int, items: A*) = this
38 | def valueOf() = this
39 | }
40 |
41 | object Math {
42 | val E = lang.Math.E
43 | val LN2 = lang.Math.E
44 | val LN10 = lang.Math.E
45 | val LOG2E = lang.Math.E
46 | val LOG10E = lang.Math.E
47 | val PI = lang.Math.PI
48 | val SQRT1_2 = lang.Math.PI
49 | val SQRT2 = lang.Math.PI
50 | def abs(x: Double) = lang.Math.abs(x)
51 | def acos(x: Double) = lang.Math.acos(x)
52 | def asin(x: Double) = lang.Math.asin(x)
53 | def atan(x: Double) = lang.Math.atan(x)
54 | def atan2(y: Double, x: Double) = lang.Math.atan2(y, x)
55 | def ceil(x: Double) = lang.Math.ceil(x)
56 | def cos(x: Double) = lang.Math.cos(x)
57 | def exp(x: Double) = lang.Math.exp(x)
58 | def floor(x: Double) = lang.Math.floor(x)
59 | def log(x: Double) = lang.Math.log(x)
60 | def max(x1: Double, x2: Double, xs: Double*) = lang.Math.max(x1, x2)
61 | def min(x1: Double, x2: Double, xs: Double*) = lang.Math.min(x1, x2)
62 | def pow(x: Double, y: Double) = lang.Math.pow(x, y)
63 | def random() = lang.Math.random()
64 | def round(x: Double) = lang.Math.round(x)
65 | def sin(x: Double) = lang.Math.sin(x)
66 | def sqrt(x: Double) = lang.Math.sqrt(x)
67 | def tan(x: Double) = lang.Math.tan(x)
68 | }
69 |
70 | class RegExp(pattern: String, modifiers: String = "") {
71 | val global = false
72 | val ignoreCase = false
73 | val lastIndex = 1
74 | val multiline = false
75 | val source = pattern
76 | def compile(pattern: String, modifiers: String = ""): Unit = {}
77 | def exec(str: String): JString = null
78 | def test(str: String) = false
79 | }
80 |
81 | class Date {
82 | def getDate() = 0
83 |
84 | // Returns the day of the month (from 1-31)
85 | def getDay() = 0
86 |
87 | // Returns the day of the week (from 0-6)
88 | def getFullYear() = 0
89 |
90 | // Returns the year (four digits)
91 | def getHours() = 0
92 |
93 | // Returns the hour (from 0-23)
94 | def getMilliseconds() = 0
95 |
96 | // Returns the milliseconds (from 0-999)
97 | def getMinutes() = 0
98 |
99 | // Returns the minutes (from 0-59)
100 | def getMonth() = 0
101 |
102 | // Returns the month (from 0-11)
103 | def getSeconds() = 0
104 |
105 | // Returns the seconds (from 0-59)
106 | def getTime() = 0
107 |
108 | // Returns the number of milliseconds since midnight Jan 1, 1970
109 | def getTimezoneOffset() = 0
110 |
111 | // Returns the time difference between UTC time and local time, in minutes
112 | def getUTCDate() = 0
113 |
114 | // Returns the day of the month, according to universal time (from 1-31)
115 | def getUTCDay() = 0
116 |
117 | // Returns the day of the week, according to universal time (from 0-6)
118 | def getUTCFullYear() = 0
119 |
120 | // Returns the year, according to universal time (four digits)
121 | def getUTCHours() = 0
122 |
123 | // Returns the hour, according to universal time (from 0-23)
124 | def getUTCMilliseconds() = 0
125 |
126 | // Returns the milliseconds, according to universal time (from 0-999)
127 | def getUTCMinutes() = 0
128 |
129 | // Returns the minutes, according to universal time (from 0-59)
130 | def getUTCMonth() = 0
131 |
132 | // Returns the month, according to universal time (from 0-11)
133 | def getUTCSeconds() = 0
134 |
135 | // Returns the seconds, according to universal time (from 0-59)
136 | def getYear() = 0
137 |
138 | // Deprecated. Use the def getFullYear() = 0 // method instead
139 | def parse(date: String) = 0
140 |
141 | // Parses a date string and returns the number of milliseconds since midnight of January 1, 1970
142 | def setDate(v: Int): Unit = {}
143 |
144 | // Sets the day of the month of a date object
145 | def setFullYear(v: Int): Unit = {}
146 |
147 | // Sets the year (four digits) of a date object
148 | def setHours(v: Int): Unit = {}
149 |
150 | // Sets the hour of a date object
151 | def setMilliseconds(v: Int): Unit = {}
152 |
153 | // Sets the milliseconds of a date object
154 | def setMinutes(v: Int): Unit = {}
155 |
156 | // Set the minutes of a date object
157 | def setMonth(v: Int): Unit = {}
158 |
159 | // Sets the month of a date object
160 | def setSeconds(v: Int): Unit = {}
161 |
162 | // Sets the seconds of a date object
163 | def setTime(v: Int): Unit = {}
164 |
165 | // Sets a date and time by adding or subtracting a specified number of milliseconds to/from midnight January 1, 1970
166 | def setUTCDate(v: Int): Unit = {}
167 |
168 | // Sets the day of the month of a date object, according to universal time
169 | def setUTCFullYear(v: Int): Unit = {}
170 |
171 | // Sets the year of a date object, according to universal time (four digits)
172 | def setUTCHours(v: Int): Unit = {}
173 |
174 | // Sets the hour of a date object, according to universal time
175 | def setUTCMilliseconds(v: Int): Unit = {}
176 |
177 | // Sets the milliseconds of a date object, according to universal time
178 | def setUTCMinutes(v: Int): Unit = {}
179 |
180 | // Set the minutes of a date object, according to universal time
181 | def setUTCMonth(v: Int): Unit = {}
182 |
183 | // Sets the month of a date object, according to universal time
184 | def setUTCSeconds(v: Int): Unit = {}
185 |
186 | // Set the seconds of a date object, according to universal time
187 | def setYear(v: Int): Unit = {}
188 |
189 | // Deprecated. Use the def setFullYear() = 0 // method instead
190 | def toDateString() = 0
191 |
192 | // Converts the date portion of a Date object into a readable string
193 | def toGMTString() = ""
194 |
195 | // Deprecated. Use the toUTCString // method instead
196 | def toISOString() = ""
197 |
198 | // Returns the date as a string, using the ISO standard
199 | def toJSON() = ""
200 |
201 | // Returns the date as a string, formatted as a JSON date
202 | def toLocaleDateString() = ""
203 |
204 | // Returns the date portion of a Date object as a string, using locale conventions
205 | def toLocaleTimeString() = ""
206 |
207 | // Returns the time portion of a Date object as a string, using locale conventions
208 | def toLocaleString() = ""
209 |
210 | // Converts a Date object to a string
211 | def toTimeString() = ""
212 |
213 | // Converts the time portion of a Date object to a string
214 | def toUTCString() = ""
215 |
216 | // Converts a Date object to a string, according to universal time
217 | def UTC() = ""
218 |
219 | def valueOf() = 0
220 | }
221 |
222 | trait JsDynamic extends Dynamic {
223 | def unary_! : Boolean = true
224 | def ||(a: Any): JsDynamic = this
225 | def &&(a: Any): JsDynamic = this
226 | def +(a: Any): JsDynamic = this
227 | def -(a: Any): JsDynamic = this
228 | def *(a: Any): JsDynamic = this
229 | def /(a: Any): JsDynamic = this
230 | def apply(a: Any*): JsDynamic = this
231 | def applyDynamic(name: String)(args: Any*): JsDynamic = this
232 | def selectDynamic(name: String): JsDynamic = this
233 | def updateDynamic(name: String)(arg: Any): JsDynamic = this
234 | }
235 |
--------------------------------------------------------------------------------
/jscala/src/main/scala/org/jscala/MacroHelpers.scala:
--------------------------------------------------------------------------------
1 | package org.jscala
2 |
3 | import scala.reflect.macros.blackbox
4 |
5 | /**
6 | * Author: Alexander Nemish
7 | * Date: 10/25/13
8 | * Time: 10:35 PM
9 | */
10 | trait MacroHelpers[C <: blackbox.Context] {
11 | val c: C
12 |
13 | import c.universe._
14 |
15 | type PFT[A] = PartialFunction[Tree, A]
16 | type ToExpr[A] = PFT[Tree]
17 |
18 | implicit class TreeHelper(tree: Tree) {
19 | def is(p: String): Boolean = tree.equalsStructure(select(p)) || tree.equalsStructure(select(p, s => This(TypeName(s))))
20 |
21 | lazy val isArrow: Boolean = is("scala.Predef.ArrowAssoc")
22 |
23 | /*2.11.x*/
24 | def raw: String = showRaw(tree)
25 |
26 | def isNum: Boolean = tree.tpe.widen weak_<:< typeOf[Long]
27 |
28 | def isCaseClass: Boolean = tree.tpe.typeSymbol.isClass && tree.tpe.typeSymbol.asClass.isCaseClass
29 |
30 | def caseMembers: List[c.universe.MethodSymbol] = tree.tpe.members.sorted.collect {
31 | case m: MethodSymbol if m.isCaseAccessor => m
32 | }
33 | }
34 |
35 | implicit class NameHelper(name: Name) {
36 | def isArrow: Boolean = name.decodedName.toString == "->" || name.decodedName.toString == "→"
37 | }
38 |
39 | object Name {
40 | def unapply(name: Name): Option[String] = Some(name.decodedName.toString)
41 | }
42 |
43 | protected lazy val seqFactorySym = if (util.Properties.versionNumberString.startsWith("2.13"))
44 | c.mirror.staticClass("scala.collection.SeqFactory")
45 | else c.mirror.staticClass("scala.collection.generic.SeqFactory")
46 | protected lazy val mapFactorySym = if (util.Properties.versionNumberString.startsWith("2.13"))
47 | c.mirror.staticClass("scala.collection.MapFactory")
48 | else c.mirror.staticClass("scala.collection.generic.MapFactory")
49 | protected lazy val arraySym = c.mirror.staticClass("scala.Array")
50 | protected lazy val jarraySym = c.mirror.staticClass("org.jscala.JArray")
51 | protected lazy val seqSym = c.mirror.staticClass("scala.collection.Seq")
52 | protected lazy val traversableSym = c.mirror.staticClass("scala.collection.Traversable")
53 | protected lazy val mapSym = c.mirror.staticClass("scala.collection.Map")
54 | protected lazy val setSym = c.mirror.staticClass("scala.collection.Set")
55 | protected lazy val functionTypes = List(typeOf[Function1[_,_]], typeOf[Function2[_, _,_]], typeOf[Function3[_,_,_,_]], typeOf[Function4[_,_,_,_,_]])
56 |
57 |
58 | protected def select(p: String, init: String => Tree = s => Ident(TermName(s))): Tree = {
59 | p.split("\\.").foldLeft(EmptyTree) {
60 | case (EmptyTree, el) => init(el)
61 | case (t, el) => Select(t, TermName(el))
62 | }
63 | }
64 |
65 | protected def isUnit(tree: Tree) = tree.equalsStructure(q"()")
66 |
67 | protected def isNull(tree: Tree) = tree.equalsStructure(q"null")
68 |
69 | protected def isArray(path: c.Tree) =
70 | path.tpe.typeSymbol == definitions.ArrayClass || path.tpe.typeSymbol == jarraySym || path.tpe.baseClasses.contains(seqSym)
71 |
72 | protected def listToExpr(exprs: List[Tree]): Tree = q"List(..$exprs)"
73 |
74 | protected def mapToExpr(m: Map[String, Tree]): Tree = {
75 | val args: List[Tree] = m.map { case (k, v) => q"$k -> $v" }.toList
76 | q"Map(..$args)"
77 | }
78 |
79 | def prn(t: Tree): Unit = {
80 | println(s"Tpe: ${t.tpe}, S: ${t.symbol}, STS: " + (if (t.symbol ne null) t.symbol.typeSignature.toString else "null"))
81 | }
82 |
83 | def tpe(t: Tree) = t.tpe.widen
84 | }
85 |
--------------------------------------------------------------------------------
/jscala/src/main/scala/org/jscala/ScalaToJsConverter.scala:
--------------------------------------------------------------------------------
1 | package org.jscala
2 |
3 | import scala.language.experimental.macros
4 | import scala.language.implicitConversions
5 | import scala.reflect.internal.Flags
6 | import scala.reflect.macros.blackbox
7 |
8 | class ScalaToJsConverter[C <: blackbox.Context](val c: C, debug: Boolean) extends SyntaxConverter[C] with CollectionConverter[C] {
9 | import c.universe._
10 |
11 | private val traits = collection.mutable.HashMap[String, List[Tree]]()
12 | private val ints = Set("Byte", "Short", "Int", "Long", "Float", "Double")
13 |
14 | def convert(tree: Tree): Tree = {
15 | if (debug) println(tree)
16 | if (debug) println(tree.raw)
17 |
18 | val expr = try jsAst apply tree catch {
19 | case ex: DieException =>
20 | val stack = if (debug) Seq("Raw tree:", ex.t.raw) ++ Thread.currentThread.getStackTrace mkString "\n" else ""
21 | c.abort(ex.t.pos, s"${ex.msg}: ${ex.t}. $stack")
22 | }
23 |
24 | val resultTree = if (!tree.tpe.=:=(typeOf[Nothing]) && functionTypes.exists(tree.tpe.<:<)) {
25 | q"$expr.asInstanceOf[org.jscala.JsAst with ${tree.tpe}]"
26 | } else expr
27 |
28 | resultTree
29 | }
30 |
31 | private lazy val jsCaseClassApply: ToExpr[JsAnonObjDecl] = {
32 | case tree@Apply(Select(path, TermName("apply")), args) if tree.isCaseClass && !tree.tpe.typeSymbol.fullName.startsWith("org.jscala.") =>
33 | val params = listToExpr(tree.caseMembers.zip(args).map { case (m, a) => q"(${m.name.decodedName.toString}, ${jsExprOrDie(a)})" })
34 | q"org.jscala.JsAnonObjDecl($params)"
35 | }
36 |
37 | private def typeConverter(tpe: Tree): String = {
38 | val or = tpe.asInstanceOf[TypeTree].original
39 | val ident = or match {
40 | case Select(Select(This(TypeName("scala")), Name("Predef")), Name("String")) => "String"
41 | case Select(Ident(Name("scala")), Name(int)) if ints.contains(int) => "Number"
42 | case Select(q, Name(nm)) => nm
43 | case Ident(Name(nm)) => nm
44 | case t => c.abort(c.enclosingPosition, s"Unknown type: ${show(t)}: ${showRaw(t)}")
45 | }
46 | ident
47 | }
48 |
49 | private lazy val jsGlobalFuncsExpr: ToExpr[JsExpr] = {
50 | case Select(Apply(path, List(arg)), Name("jstr")) => jsExprOrDie(arg)
51 | case Apply(Select(Apply(jsAnyOps, List(expr)), Name("instanceof")), List(Literal(Constant(tpname: String)))) if jsAnyOps.is("org.jscala.package.JsAnyOps") =>
52 | q"""org.jscala.JsBinOp("instanceof", ${jsExprOrDie(expr)}, org.jscala.JsIdent($tpname))"""
53 | case TypeApply(Select(Apply(jsAnyOps, List(expr)), Name("instanceof")), List(tpe)) if jsAnyOps.is("org.jscala.package.JsAnyOps") =>
54 | q"""org.jscala.JsBinOp("instanceof", ${jsExprOrDie(expr)}, org.jscala.JsIdent(${typeConverter(tpe)}))"""
55 | case TypeApply(Select(Apply(jsAnyOps, List(expr)), Name("as")), _) if jsAnyOps.is("org.jscala.package.JsAnyOps") =>
56 | jsExprOrDie(expr)
57 | case Select(expr, Name("toDouble")) => jsExprOrDie(expr)
58 | case TypeApply(Select(expr, Name("asInstanceOf")), _) =>
59 | jsExprOrDie(expr)
60 | case q"js.this.Any.${TermName(func)}[..$tpts](...$exprss)" if func.startsWith("from") || func.startsWith("to") =>
61 | jsExprOrDie(exprss.head.head)
62 | case Apply(TypeApply(Select(path, Name(n)), _), List(expr)) if path.is("org.jscala.package") && n.startsWith("implicit") =>
63 | jsExprOrDie(expr)
64 | case Apply(Select(path, Name(n)), List(expr)) if path.is("org.jscala.package") && n.startsWith("implicit") =>
65 | jsExprOrDie(expr)
66 | case Apply(path, List(Literal(Constant(js: String)))) if path.is("org.jscala.package.include") =>
67 | q"org.jscala.JsRaw($js)"
68 | case app@Apply(path, List(ident)) if path.is("org.jscala.package.include") =>
69 | q"org.jscala.JsLazy(() => ${jsAst(ident)})"
70 | case app@Apply(Apply(TypeApply(path, _), List(ident)), List(jss)) if path.is("org.jscala.package.inject") =>
71 | val call = c.Expr[JsExpr](Apply(jss, List(ident)))
72 | q"org.jscala.JsLazy(() => $call)"
73 | case Apply(Select(path, fn), args) if path.is("org.jscala.package") =>
74 | val params = funParams(args)
75 | q"org.jscala.JsCall(org.jscala.JsIdent(${fn.decodedName.toString}), $params)"
76 | }
77 |
78 | private def eligibleDef(f: DefDef) = {
79 | f.name != termNames.CONSTRUCTOR && f.name.decodedName.toString != "$init$" && !f.mods.hasFlag(Flags.ACCESSOR.toLong.asInstanceOf[FlagSet] | Flag.DEFERRED)
80 | }
81 |
82 | private lazy val objectFields: PFT[(String, Tree)] = {
83 | case f@DefDef(mods, n, _, argss, _, body) if eligibleDef(f) =>
84 | n.decodedName.toString -> jsExprOrDie(Function(argss.flatten, body))
85 | case ValDef(mods, n, _, rhs) if !rhs.equalsStructure(EmptyTree)
86 | && !mods.hasFlag(Flags.PARAMACCESSOR.toLong.asInstanceOf[FlagSet] | Flag.ABSTRACT) => n.decodedName.toString.trim -> jsExprOrDie(rhs)
87 | }
88 |
89 | private lazy val jsClassDecl: ToExpr[JsStmt] = {
90 | case cd@ClassDef(mods, clsName, _, Template(_, _, body)) if mods.hasFlag(Flag.TRAIT) =>
91 | // Remember trait AST to embed its definitions in concrete class for simplicity
92 | traits(cd.symbol.fullName) = body
93 | q"org.jscala.JsUnit"
94 | case cd@ClassDef(mods, clsName, _, Template(_, _, body)) if mods.hasFlag(Flag.CASE) =>
95 | q"org.jscala.JsUnit"
96 | case ModuleDef(_, name, _) =>
97 | q"org.jscala.JsUnit"
98 | case cd@ClassDef(_, clsName, _, t@Template(base, _, body)) =>
99 | val ctor = body.collect {
100 | case f@DefDef(mods, n, _, argss, _, Block(stats, _)) if n == termNames.CONSTRUCTOR =>
101 | val a = argss.flatten.map(v => v.name.decodedName.toString)
102 | a
103 | }
104 | if (ctor.size != 1) c.abort(c.enclosingPosition, "Only single primary constructor is currently supported. Sorry.")
105 | val init = ctor.head.map(f => f -> q"org.jscala.JsIdent($f)")
106 | val inherited = t.tpe.baseClasses.map(_.fullName).flatMap {bc => traits.get(bc).toList }
107 | val bigBody = inherited.foldRight(List[Tree]())(_ ::: _) ::: body
108 | val defs = bigBody.collect(objectFields)
109 | val fields = init ::: defs
110 | val fs = listToExpr(fields.map { case (n, v) => q"($n, $v)" })
111 | val args = listToExpr(ctor.head.map(arg => q"$arg"))
112 | val ctorBody = listToExpr(bigBody.collect {
113 | case _: ValDef => None
114 | case _: DefDef => None
115 | case stmt => Some(stmt)
116 | }.flatten.map(jsStmtOrDie))
117 | val ctorFuncDecl = q"org.jscala.JsFunDecl(${clsName.decodedName.toString}, $args, org.jscala.JsBlock($ctorBody))"
118 | q"org.jscala.JsObjDecl(${clsName.decodedName.toString}, $ctorFuncDecl, $fs)"
119 | }
120 |
121 | private lazy val jsAnonObjDecl: ToExpr[JsAnonObjDecl] = {
122 | case Block(List(ClassDef(_, clsName, _, Template(_, _, body))), _/* Constructor call */) =>
123 | val defs = body.collect(objectFields)
124 | val params = listToExpr(defs.map { case (n, v) => q"($n, $v)" })
125 | q"org.jscala.JsAnonObjDecl($params)"
126 | }
127 |
128 | /** Case class creation, using named arguments in wrong order
129 | * {
130 | * val x$2: Int = 2;
131 | * val x$3: String = "nau";
132 | * User.apply(x$3, x$2)
133 | * }
134 | */
135 | private lazy val jsCaseClassNamedArgs: ToExpr[JsExpr] = {
136 | case Block(valdefs, ap@Apply(expr, params)) if jsCaseClassApply.isDefinedAt(ap) =>
137 | val args = valdefs.map { case ValDef(_, TermName(n), _, init) => n -> init }.toMap
138 | val orderedParams = params.map {
139 | case Ident(TermName(i)) => args(i)
140 | case other => other
141 | }
142 | val tree1 = c.typecheck(Apply(expr, orderedParams))
143 | jsCaseClassApply(tree1)
144 | }
145 |
146 | private lazy val jsAsdf: ToExpr[JsExpr] = {
147 | case Typed(e, _) => jsExpr(e)
148 | }
149 |
150 | protected lazy val jsExpr: ToExpr[JsExpr] = Seq(
151 | jsAsdf,
152 | jsLit,
153 | jsUnaryOp,
154 | jsBinOp,
155 | jsGlobalFuncsExpr,
156 | jsStringHelpersExpr,
157 | jsStringInterpolation,
158 | jsJStringExpr,
159 | jsArrayExpr,
160 | jsNewExpr,
161 | jsCaseClassApply,
162 | jsMapExpr,
163 | jsCallExpr,
164 | jsAnonFunDecl,
165 | jsSelect,
166 | jsIdent,
167 | jsThis,
168 | jsAnonObjDecl,
169 | jsThrowExpr,
170 | jsTernaryExpr,
171 | jsCaseClassNamedArgs
172 | ) reduceLeft( _ orElse _)
173 |
174 | protected lazy val jsStmt: ToExpr[JsStmt] = Seq(
175 | jsBlock,
176 | jsVarDefStmt,
177 | jsIfStmt,
178 | jsSwitch,
179 | jsWhileStmt,
180 | jsTry,
181 | jsForStmt,
182 | jsFunDecl,
183 | jsClassDecl,
184 | jsReturn1,
185 | jsExpr
186 | ) reduceLeft (_ orElse _)
187 |
188 | private lazy val jsAst: ToExpr[JsAst] = jsStmtOrDie
189 | }
190 |
--------------------------------------------------------------------------------
/jscala/src/main/scala/org/jscala/SyntaxConverter.scala:
--------------------------------------------------------------------------------
1 | package org.jscala
2 |
3 | import scala.reflect.macros.blackbox
4 |
5 | /**
6 | * Author: Alexander Nemish
7 | */
8 | trait SyntaxConverter[C <: blackbox.Context] extends BasisConverter[C] {
9 | import c.universe._
10 |
11 | protected lazy val jsReturn1: ToExpr[JsStmt] = {
12 | case Return(expr) => q"org.jscala.JsReturn(${jsExprOrDie(expr)})"
13 | }
14 |
15 | protected lazy val jsReturn: ToExpr[JsStmt] = jsReturnStmt orElse jsStmtOrDie
16 |
17 | protected lazy val jsReturnStmt: ToExpr[JsReturn] = jsExpr andThen (jsExpr => q"org.jscala.JsReturn($jsExpr)")
18 |
19 | protected lazy val jsIfStmt: ToExpr[JsIf] = {
20 | case q"if ($cond) $thenp else $elsep" =>
21 | val condJsExpr = jsExprOrDie(cond)
22 | val thenJsExpr = jsStmtOrDie(thenp)
23 | val elseJsStmt = if (isUnit(elsep)) q"None" else q"Some(${jsStmtOrDie(elsep)})"
24 | q"org.jscala.JsIf($condJsExpr, $thenJsExpr, $elseJsStmt)"
25 | }
26 |
27 | protected lazy val jsTernaryExpr: ToExpr[JsTernary] = {
28 | case q"if ($cond) $thenp else $elsep" if !thenp.tpe.=:=(typeOf[Unit]) && !isUnit(elsep) && jsExpr.isDefinedAt(thenp) && jsExpr.isDefinedAt(elsep) =>
29 | val condJsExpr = jsExprOrDie(cond)
30 | val thenJsExpr = jsExprOrDie(thenp)
31 | val elseExpr = jsExprOrDie(elsep)
32 | q"org.jscala.JsTernary($condJsExpr, $thenJsExpr, $elseExpr)"
33 | }
34 |
35 | protected lazy val jsWhileStmt: ToExpr[JsWhile] = {
36 | case q"while ($cond) $body" =>
37 | val condJsExpr = jsExprOrDie(cond)
38 | val bodyJsStmt = jsStmtOrDie(body)
39 | q"org.jscala.JsWhile($condJsExpr, $bodyJsStmt)"
40 | }
41 |
42 | private def addAssign(tree: Tree, name: Name) = tree match {
43 | case Block(stats, expr) => Block(stats :+ Assign(Ident(name), expr), q"()")
44 | case expr => Block(Assign(Ident(name), expr) :: Nil, q"()")
45 | }
46 |
47 | protected lazy val jsIfExpr: PartialFunction[(Name, Tree), Tree] = {
48 | case (name, If(cond, thenp, elsep)) =>
49 | val condJsExpr = jsExprOrDie(cond)
50 | val thenJsExpr = jsStmtOrDie(addAssign(thenp, name))
51 | val elseJsExpr = jsStmtOrDie(addAssign(elsep, name))
52 | q"org.jscala.JsIf($condJsExpr, $thenJsExpr, Some($elseJsExpr))"
53 | }
54 |
55 | protected lazy val jsMatchExpr: PartialFunction[(Name, Tree), Tree] = {
56 | case (name, Match(expr, cases)) => jsSwitchGen(expr, cases, body => addAssign(body, name))
57 | }
58 |
59 | protected lazy val jsVarDefStmt: ToExpr[JsStmt] = {
60 | case ValDef(_, name, _, rhs) =>
61 | val identifier = name.decodedName.toString
62 | if (jsTernaryExpr.isDefinedAt(rhs)) {
63 | q"org.jscala.JsVarDef($identifier, ${jsTernaryExpr(rhs)})"
64 | } else {
65 | val funcs = Seq(jsIfExpr, jsMatchExpr).reduceLeft(_ orElse _) andThen { expr =>
66 | q"org.jscala.JsStmts(List(org.jscala.JsVarDef($identifier, org.jscala.JsUnit), $expr))"
67 | }
68 | val x = name -> rhs
69 | funcs.applyOrElse(x, (t: (TermName, Tree)) => {
70 | q"org.jscala.JsVarDef($identifier, ${jsExprOrDie(rhs)})"
71 | })
72 | }
73 | }
74 |
75 | protected lazy val jsFunBody: ToExpr[JsBlock] = {
76 | case lit@Literal(_) =>
77 | val body = if (isUnit(lit)) Nil else List(jsReturnStmt(lit))
78 | q"org.jscala.JsBlock(${listToExpr(body)})"
79 | case b@Block(stmts, expr) =>
80 | val lastExpr = if (isUnit(expr)) Nil
81 | else if (expr.tpe =:= typeOf[Unit]) List(jsStmtOrDie(expr))
82 | else List(jsReturn(expr))
83 | val ss = listToExpr(stmts.map(jsStmtOrDie) ::: lastExpr)
84 | q"org.jscala.JsBlock($ss)"
85 | case rhs =>
86 | if (rhs.tpe =:= typeOf[Unit]) q"org.jscala.JsBlock(List(${jsStmtOrDie(rhs)}))"
87 | else q"org.jscala.JsBlock(List(${jsReturn(rhs)}))"
88 | }
89 |
90 | protected lazy val jsFunDecl: ToExpr[JsFunDecl] = {
91 | case DefDef(_, name, _, vparamss, _, rhs) =>
92 | val ident = name.decodedName.toString
93 | val a = vparamss.headOption.map(vp => vp.map(v => q"${v.name.decodedName.toString}")).getOrElse(Nil)
94 | val params = listToExpr(a)
95 | val body = jsFunBody(rhs)
96 | q"org.jscala.JsFunDecl($ident, $params, $body)"
97 | }
98 |
99 | protected lazy val jsAnonFunDecl: ToExpr[JsAnonFunDecl] = {
100 | case Block(Nil, Function(vparams, rhs)) =>
101 | val params = listToExpr(vparams.map(v => q"${v.name.decodedName.toString}"))
102 | val body = jsFunBody(rhs)
103 | q"org.jscala.JsAnonFunDecl($params, $body)"
104 | case Function(vparams, rhs) =>
105 | val params = listToExpr(vparams.map(v => q"${v.name.decodedName.toString}"))
106 | val body = jsFunBody(rhs)
107 | q"org.jscala.JsAnonFunDecl($params, $body)"
108 | }
109 |
110 | protected lazy val jsTry: ToExpr[JsTry] = {
111 | case q"try $body catch { case ..$catchBlock } finally $finBody" =>
112 | // case Try(body, catchBlock, finBody) =>
113 | val ctch = catchBlock match {
114 | case Nil => q"None"
115 | case List(CaseDef(Bind(pat, _), EmptyTree, catchBody)) =>
116 | q"Some(org.jscala.JsCatch(org.jscala.JsIdent(${pat.decodedName.toString}), ${jsStmtOrDie(catchBody)}))"
117 | }
118 | val fin = if (finBody.equalsStructure(EmptyTree)) q"None"
119 | else q"Some(${jsStmtOrDie(finBody)})"
120 | q"org.jscala.JsTry(${jsStmtOrDie(body)}, $ctch, $fin)"
121 | }
122 |
123 | protected lazy val jsThrowExpr: ToExpr[JsThrow] = {
124 | case Throw(expr) => q"org.jscala.JsThrow(${jsExprOrDie(expr)})"
125 | }
126 |
127 | private def jsSwitchGen(expr: Tree, cases: List[CaseDef], f: Tree => Tree) = {
128 | val cs = cases collect {
129 | case CaseDef(const@Literal(Constant(_)), EmptyTree, body) => q"org.jscala.JsCase(List(${jsLit(const)}), ${jsStmtOrDie(f(body))})"
130 | case CaseDef(sel@Select(path, n), EmptyTree, body) =>
131 | q"org.jscala.JsCase(List(${jsExprOrDie(sel)}), ${jsStmtOrDie(f(body))})"
132 | case CaseDef(Alternative(xs), EmptyTree, body) =>
133 | val stmt = jsStmtOrDie(f(body))
134 | val consts = listToExpr(xs map(c => jsLit(c)))
135 | q"org.jscala.JsCase($consts, $stmt)"
136 | }
137 | val df = (cases collect {
138 | case CaseDef(Ident(termNames.WILDCARD), EmptyTree, body) => q"Some(org.jscala.JsDefault(${jsStmtOrDie(f(body))}))"
139 | }).headOption.getOrElse(q"None")
140 | val css = listToExpr(cs)
141 | q"org.jscala.JsSwitch(${jsExprOrDie(expr)}, $css, $df)"
142 | }
143 |
144 | protected lazy val jsSwitch: ToExpr[JsSwitch] = {
145 | case Match(expr, cases) => jsSwitchGen(expr, cases, t => t)
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/jscala/src/main/scala/org/jscala/model.scala:
--------------------------------------------------------------------------------
1 | package org.jscala
2 |
3 |
4 | sealed trait JsAst {
5 | def block: JsBlock
6 | def join(a: JsAst): JsAst
7 | final def ++(a: JsAst): JsAst = join(a)
8 | }
9 |
10 | sealed trait JsStmt extends JsAst {
11 | def block = JsBlock(List(this))
12 | def join(a: JsAst) = (this, a) match {
13 | case (JsBlock(lhs), JsBlock(rhs)) => JsBlock(lhs ::: rhs)
14 | case (JsStmts(lhs), JsBlock(rhs)) => JsBlock(lhs ::: rhs)
15 | case (JsBlock(lhs), JsStmts(rhs)) => JsBlock(lhs ::: rhs)
16 | case (JsStmts(lhs), JsStmts(rhs)) => JsStmts(lhs ::: rhs)
17 | case (JsBlock(lhs), s: JsStmt) => JsBlock(lhs :+ s)
18 | case (s: JsStmt, JsBlock(rhs)) => JsBlock(s :: rhs)
19 | case (JsStmts(lhs), s: JsStmt) => JsStmts(lhs :+ s)
20 | case (s: JsStmt, JsStmts(rhs)) => JsStmts(s :: rhs)
21 | case (lhs: JsStmt, rhs: JsStmt) => JsBlock(List(lhs, rhs))
22 | }
23 | }
24 | sealed trait JsExpr extends JsStmt
25 | sealed trait JsLit extends JsExpr
26 |
27 | case class JsBool(value: Boolean) extends JsLit
28 | case class JsString(value: String) extends JsLit
29 | case class JsNum(value: Double, isFloat: Boolean) extends JsLit
30 | case class JsArray(values: List[JsExpr]) extends JsLit
31 | case object JsUnit extends JsLit
32 | case object JsNull extends JsLit
33 |
34 | case class JsLazy[A <: JsAst](ast: () => A) extends JsExpr
35 | case class JsIdent(ident: String) extends JsExpr
36 | case class JsRaw(js: String) extends JsExpr
37 | case class JsAccess(qualifier: JsExpr, key: JsExpr) extends JsExpr
38 | case class JsSelect(qualifier: JsExpr, name: String) extends JsExpr
39 | case class JsUnOp(operator: String, operand: JsExpr) extends JsExpr
40 | case class JsBinOp(operator: String, lhs: JsExpr, rhs: JsExpr) extends JsExpr
41 | case class JsCall(callee: JsExpr, params: List[JsExpr]) extends JsExpr
42 | case class JsNew(ctor: JsCall) extends JsExpr
43 | case class JsThrow(expr: JsExpr) extends JsExpr
44 | case class JsAnonFunDecl(params: List[String], body: JsStmt) extends JsExpr
45 | case class JsAnonObjDecl(fields: List[(String, JsExpr)]) extends JsExpr
46 | case class JsTernary(cond: JsExpr, `then`: JsExpr, `else`: JsExpr) extends JsExpr
47 |
48 | case class JsBlock(stmts: List[JsStmt]) extends JsStmt
49 | case class JsTry(body: JsStmt, cat: Option[JsCatch], fin: Option[JsStmt]) extends JsStmt
50 | case class JsCatch(ident: JsIdent, body: JsStmt) extends JsStmt
51 | case class JsIf(cond: JsExpr, `then`: JsStmt, `else`: Option[JsStmt]) extends JsStmt
52 | case class JsWhile(cond: JsExpr, body: JsStmt) extends JsStmt
53 | case class JsFor(init: List[JsStmt], check: JsExpr, update: List[JsStmt], body: JsStmt) extends JsStmt
54 | case class JsForIn(ident: JsIdent, coll: JsExpr, body: JsStmt) extends JsStmt
55 | sealed trait JsSwitchable extends JsStmt
56 | case class JsCase(const: List[JsExpr], body: JsStmt) extends JsSwitchable
57 | case class JsDefault(body: JsStmt) extends JsSwitchable
58 | case class JsSwitch(expr: JsExpr, cases: List[JsCase], default: Option[JsDefault]) extends JsStmt
59 | case class JsVarDef(ident: String, init: JsExpr) extends JsStmt
60 | case class JsFunDecl(ident: String, params: List[String], body: JsStmt) extends JsStmt
61 | case class JsObjDecl(name: String, constructor: JsFunDecl, fields: List[(String, JsExpr)]) extends JsStmt
62 | case class JsReturn(jsExpr: JsExpr) extends JsStmt
63 | case class JsStmts(stmts: List[JsStmt]) extends JsStmt
--------------------------------------------------------------------------------
/jscala/src/main/scala/org/jscala/package.scala:
--------------------------------------------------------------------------------
1 | package org
2 |
3 | import javax.script.{ScriptEngine, ScriptEngineManager}
4 | import com.yahoo.platform.yui.compressor.JavaScriptCompressor
5 | import java.io.{StringReader, StringWriter}
6 |
7 | import org.mozilla.javascript.ErrorReporter
8 |
9 | import scala.reflect.macros.whitebox
10 |
11 | package object jscala {
12 | import language.experimental.macros
13 | import language.implicitConversions
14 | import scala.reflect.macros.Context
15 |
16 | private lazy val engine: ScriptEngine = {
17 | val factory = new ScriptEngineManager(null)
18 | factory.getEngineByName("JavaScript")
19 | }
20 |
21 | implicit class JsAstOps(ast: JsAst) {
22 |
23 | def asString: String = JavascriptPrinter.print(ast, 0)
24 | def eval(): AnyRef = engine.eval(asString)
25 | def compress: String = {
26 | val compressor = new JavaScriptCompressor(new StringReader(asString), new ErrorReporter {
27 | def warning(p1: String, p2: String, p3: Int, p4: String, p5: Int): Unit = {
28 | println(s"Warn $p1 $p2, ${p3.toString} $p4 ${p5.toString}")
29 | }
30 |
31 | def error(p1: String, p2: String, p3: Int, p4: String, p5: Int): Unit = {
32 | println(s"Error $p1 $p2, ${p3.toString} $p4 ${p5.toString}")
33 | }
34 |
35 | def runtimeError(p1: String, p2: String, p3: Int, p4: String, p5: Int) = {
36 | println(s"Runtime $p1 $p2, ${p3.toString} $p4 ${p5.toString}")
37 | ???
38 | }
39 | })
40 | val buf = new StringWriter
41 | compressor.compress(buf, 1, true, false, false, false)
42 | buf.toString
43 | }
44 | }
45 |
46 | implicit class JsAnyOps(a: Any) {
47 | def as[A]: A = a.asInstanceOf[A]
48 | def instanceof[A]: Boolean = sys.error("Can't be used on JVM side")
49 | def instanceof(name: String): Boolean = sys.error("Can't be used on JVM side")
50 | }
51 |
52 | implicit def implicitString2JString(s: String): JString = new JString(s)
53 | implicit class implicitRichString(s: String) {
54 | def jstr: JString = new JString(s)
55 | }
56 | implicit def implicitJString2String(s: JString): String = ""
57 | implicit def implicitArray2JArray[A](s: Array[A]): JArray[A] = ???
58 | implicit def implicitJArray2Array[A](s: JArray[A]): Array[A] = ???
59 | implicit def implicitSeq2JArray[A](s: collection.Seq[A]): JArray[A] = ???
60 | implicit def implicitJArray2Seq[A](s: JArray[A]): Seq[A] = ???
61 |
62 | trait JsSerializer[-A] {
63 | def apply(a: A): JsExpr
64 | }
65 | implicit object jsExprJsSerializer extends JsSerializer[JsExpr] { def apply(a: JsExpr): JsExpr = a }
66 | implicit object boolJsSerializer extends JsSerializer[Boolean] { def apply(a: Boolean): JsExpr = JsBool(a) }
67 | implicit object byteJsSerializer extends JsSerializer[Byte] { def apply(a: Byte): JsExpr = JsNum(a, false) }
68 | implicit object shortJsSerializer extends JsSerializer[Short] { def apply(a: Short): JsExpr = JsNum(a, false) }
69 | implicit object intJsSerializer extends JsSerializer[Int] { def apply(a: Int): JsExpr = JsNum(a, false) }
70 | implicit object longJsSerializer extends JsSerializer[Long] { def apply(a: Long): JsExpr = JsNum(a.toDouble, false) }
71 | implicit object floatJsSerializer extends JsSerializer[Float] { def apply(a: Float): JsExpr = JsNum(a, true) }
72 | implicit object doubleJsSerializer extends JsSerializer[Double] { def apply(a: Double): JsExpr = JsNum(a, true) }
73 | implicit object stringJsSerializer extends JsSerializer[String] { def apply(a: String): JsExpr = JsString(a) }
74 | implicit def arrJsSerializer[A](implicit ev: JsSerializer[A]): JsSerializer[Array[A]] =
75 | new JsSerializer[Array[A]] {
76 | def apply(a: Array[A]): JsExpr = JsArray(a.map(ev.apply).toList)
77 | }
78 |
79 | implicit def seqJsSerializer[A](implicit ev: JsSerializer[A]): JsSerializer[collection.Seq[A]] =
80 | new JsSerializer[collection.Seq[A]] {
81 | def apply(a: collection.Seq[A]): JsExpr = JsArray(a.map(ev.apply).toList)
82 | }
83 |
84 | implicit def mapJsSerializer[A](implicit ev: JsSerializer[A]): JsSerializer[collection.Map[String, A]] =
85 | new JsSerializer[collection.Map[String, A]] {
86 | def apply(a: collection.Map[String, A]): JsExpr = JsAnonObjDecl(a.map{ case (k, v) => (k, ev.apply(v)) }.toList)
87 | }
88 |
89 | implicit def funcJsSerializer[A](implicit ev: JsSerializer[A]): JsSerializer[() => A] =
90 | new JsSerializer[() => A] {
91 | def apply(a: () => A): JsExpr = ev.apply(a())
92 | }
93 |
94 | implicit class ToJsExpr[A](a: A)(implicit ev: JsSerializer[A]) {
95 | def toJs: JsExpr = ev.apply(a)
96 | }
97 |
98 | // Javascript top-level functions/constants
99 | val Infinity = Double.PositiveInfinity
100 | val NaN = Double.NaN
101 | val undefined: AnyRef = null
102 |
103 | // Javascript Global Functions
104 | def decodeURI(uri: String): JString = null
105 | def decodeURIComponent(uri: String): JString = null
106 | def encodeURI(uri: String): JString = null
107 | def encodeURIComponent(uri: String): JString = null
108 | def escape(str: String): JString = null
109 | def unescape(str: String): JString = null
110 | def eval(str: String): AnyRef = null
111 | def isFinite(x: Any): Boolean = false
112 | def isNaN(x: Any): Boolean = false
113 | def parseFloat(str: String): Double = str.toDouble
114 | def parseInt(str: String, base: Int = 10): Int = java.lang.Integer.parseInt(str, base)
115 | def typeof(x: Any): String = ""
116 | def include(js: String): String = ""
117 | def print(x: Any): Unit = {
118 | System.out.println(x)
119 | }
120 | def delete(x: Any): Boolean = ???
121 |
122 | /**
123 | * Scala/JavaScript implementation of for..in
124 | *
125 | * {{{
126 | * val coll = Seq("a", "b")
127 | * forIn(coll)(ch => print(ch))
128 | * }}}
129 | * translates to
130 | * var coll = ["a", "b"];
131 | * for (var ch in coll) print(ch);
132 | */
133 | def forIn[A](coll: Seq[A])(f: Int => Unit): Unit = {
134 | var idx = 0
135 | val len = coll.length
136 | while (idx < len) {
137 | f(idx)
138 | idx += 1
139 | }
140 | }
141 |
142 | /**
143 | * Scala/JavaScript implementation of for..in
144 | *
145 | * {{{
146 | * val coll = Map("a" -> 1, "b" -> 2)
147 | * forIn(coll)(ch => print(ch))
148 | * }}}
149 | * translates to
150 | * var coll = {"a": 1, "b": 2};
151 | * for (var ch in coll) print(ch);
152 | */
153 | def forIn[A, B](map: Map[A, B])(f: A => Unit): Unit = {
154 | map.keysIterator.foreach(k => f(k))
155 | }
156 |
157 | /**
158 | * Scala/JavaScript implementation of for..in
159 | *
160 | * {{{
161 | * val obj = new { val a = 1 }
162 | * forIn(obj)(ch => print(ch))
163 | * }}}
164 | * translates to
165 | * var obj = {"a": 1};
166 | * for (var ch in obj) print(ch);
167 | * @note Doesn't work in Scala!
168 | */
169 | def forIn[A, B](obj: AnyRef)(f: String => Unit): Unit = ???
170 |
171 | def include(a: JsAst): Nothing = ???
172 | /**
173 | * Injects a value into generated JavaScript using JsSerializer
174 | */
175 | def inject[A](a: A)(implicit jss: JsSerializer[A]): A = a
176 |
177 | /**
178 | * Macro that generates JavaScript AST representation of its argument
179 | */
180 | def javascript(expr: Any): JsAst = macro Macros.javascriptImpl
181 | /**
182 | * Macro that generates JavaScript String representation of its argument
183 | */
184 | def javascriptString(expr: Any): String = macro Macros.javascriptStringImpl
185 | def javascriptDebug(expr: Any): JsAst = macro Macros.javascriptDebugImpl
186 |
187 | object Macros {
188 | def javascriptImpl(c: whitebox.Context)(expr: c.Expr[Any]): c.Expr[JsAst] = {
189 | val parser = new ScalaToJsConverter[c.type](c, debug = false)
190 | c.Expr(parser.convert(expr.tree))
191 | }
192 | def javascriptStringImpl(c: whitebox.Context)(expr: c.Expr[Any]): c.Expr[String] = {
193 | import c.universe._
194 | val parser = new ScalaToJsConverter[c.type](c, debug = false)
195 | val jsAst = parser.convert(expr.tree)
196 | val str = c.eval(c.Expr[String](q"new JsAstOps($jsAst).asString"))
197 | c.Expr[String](q"$str")
198 | }
199 |
200 | def javascriptDebugImpl(c: whitebox.Context)(expr: c.Expr[Any]): c.Expr[JsAst] = {
201 | val parser = new ScalaToJsConverter[c.type](c, debug = true)
202 | c.Expr(parser.convert(expr.tree))
203 | }
204 | }
205 | }
206 |
207 |
208 |
--------------------------------------------------------------------------------
/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version=1.3.8
2 |
3 |
--------------------------------------------------------------------------------