├── .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 | [![Build Status](https://secure.travis-ci.org/nau/jscala.png)](http://travis-ci.org/nau/jscala) 7 | 8 | [![Gitter](https://badges.gitter.im/nau/jscala.svg)](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 | 37 | Sorry, this example cannot be run because your browser does not support the <canvas> element 38 | 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,c'+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 | --------------------------------------------------------------------------------