diff options
author | Haoyi Li <haoyi@haoyi-mbp.corp.dropbox.com> | 2014-11-26 00:45:31 -0800 |
---|---|---|
committer | Haoyi Li <haoyi@haoyi-mbp.corp.dropbox.com> | 2014-11-26 00:45:31 -0800 |
commit | 24f31e120f9537faede7a174bb09ee35f64e1ce4 (patch) | |
tree | 06ffc3ecc7847789008352b7e2b7c040dad48907 /examples | |
parent | b89ce9cbf79363f8cab09186a5d7ba94bc0af02a (diff) | |
parent | 2c4b142503bd2d871e6818b5cab8c38627d9e4a0 (diff) | |
download | hands-on-scala-js-24f31e120f9537faede7a174bb09ee35f64e1ce4.tar.gz hands-on-scala-js-24f31e120f9537faede7a174bb09ee35f64e1ce4.tar.bz2 hands-on-scala-js-24f31e120f9537faede7a174bb09ee35f64e1ce4.zip |
Merge commit '2c4b142503bd2d871e6818b5cab8c38627d9e4a0' as 'examples/scala-js'
Diffstat (limited to 'examples')
657 files changed, 86304 insertions, 0 deletions
diff --git a/examples/scala-js/.gitignore b/examples/scala-js/.gitignore new file mode 100644 index 0000000..90977dc --- /dev/null +++ b/examples/scala-js/.gitignore @@ -0,0 +1,10 @@ +target/ +.cache +.classpath +.project +.settings/ +/scalalib/fetchedSources/ +/partest/fetchedSources/ +/cli/pack/ +/.idea/ +/.idea_modules/ diff --git a/examples/scala-js/LICENSE b/examples/scala-js/LICENSE new file mode 100644 index 0000000..79ec7ac --- /dev/null +++ b/examples/scala-js/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2013-2014 EPFL + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the EPFL nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/examples/scala-js/README.md b/examples/scala-js/README.md new file mode 100644 index 0000000..51e65c9 --- /dev/null +++ b/examples/scala-js/README.md @@ -0,0 +1,116 @@ +# Scala.js, a Scala to JavaScript compiler + +Scala.js compiles Scala code to JavaScript, allowing you to write your +Web application entirely in Scala! + +Noteworthy features are: + +* Support all of Scala (including macros!), + modulo [a few semantic differences](http://www.scala-js.org/doc/semantics.html) +* Very good [interoperability with JavaScript code](http://www.scala-js.org/doc/js-interoperability.html). + For example, use jQuery and HTML5 from your Scala.js code, either in a + typed or untyped way. Or create Scala.js objects and call their methods + from JavaScript. +* Integrated with [sbt](http://www.scala-sbt.org/) + (including support for dependency management and incremental compilation) +* Can be used with your favorite IDE for Scala +* Generates [Source Maps](http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/) + for a smooth debugging experience (step through your Scala code from within + your browser supporting source maps) +* Integrates [Google Closure Compiler](https://developers.google.com/closure/compiler/) + for producing minimal code for production. + +## Resources + +* [Website](http://www.scala-js.org/) +* [Mailing list](https://groups.google.com/forum/?fromgroups#!forum/scala-js) + +## Get started + +We provide a +[bootstrapping application](https://github.com/sjrd/scala-js-example-app) +which you can fork to kick off your own project. Its readme provides further +explanations on how to do so. + +## Contribute + +### Compile + +Scala.js uses [sbt](http://www.scala-sbt.org/) for its build process. +To compile your fork, simply run: + + sbt> package + +By default the sbt environment uses Scala 2.11.2. You can switch to any of the +supported versions with, e.g., + + sbt> ++2.10.4 + +### Run the test suite + +Compile and run the Scala.js-specific test suite with + + sbt> testSuite/test + +(you must have run `package` before running the test suite) + +To run the Scala test suite (aka partest), you have to use a 2.11 version, e.g., +2.11.0 or 2.11.1, and run: + + sbt> partestSuite/test + +Beware, this takes a very long time. You may use the `--fastOpt` and +`--fullOpt` switches to run Scala.js DCE or the full Google Closure +Compiler: + + sbt> partestSuite/testOnly -- --fastOpt + +A complete test session from scratch on 2.11.1 would then be + + sbt> ++2.11.1 + sbt> package + sbt> testSuite/test + sbt> partestSuite/test + +### Test the examples + +After having compiled Scala.js, you can compile the example applications with: + + sbt> examples/fullOptJS + +Then, you can "execute" them by opening their respective HTML files in your +favorite browser. Since fully optimizing the JavaScript takes time +(up to ten seconds, depending on your hardware), it is also possible +to only partially optimize JS by doing instead: + + sbt> examples/fastOptJS + +In this case, you have to open the `-fastopt` version of the HTML +files. + +Currently, two examples are provided: + +* `examples/helloworld/helloworld.html`, saying Hello World in four different + ways (using DOM or jQuery, and using the untyped or typed interface to + JavaScript). +* `examples/reversi/reversi.html`, an implementation of a + [Reversi](http://en.wikipedia.org/wiki/Reversi) game. Note that it uses the + HTML5 Canvas element, so it won't work with Internet Explorer 8 or + below. + +If both `fastOptJS` and `fullOptJS` break, you can try and use +`packageJS` which doesn't perform any optimizations (use the `-pack` +version of the HTML files). + +### Use your fork with your own projects + +Simply publish it locally with: + + sbt> publishLocal + sbt> tools/publishLocal + sbt> sbtPlugin/publishLocal + +## License + +Scala.js is distributed under the +[Scala License](http://www.scala-lang.org/license.html). diff --git a/examples/scala-js/TESTING b/examples/scala-js/TESTING new file mode 100644 index 0000000..7d393a4 --- /dev/null +++ b/examples/scala-js/TESTING @@ -0,0 +1,67 @@ +This file contains test cases that should be manually executed. + +## CLI Distribution + +For each major Scala version on a *NIX distro and a Windows distro: + +1. Download packaged Scala from scala-lang.org +2. Build Scala.js CLI distribution (e.g. `./assemble-cli.sh 2.10`) +3. Unpack Scala and Scala.js distro +4. Add `bin/` directories of both distributions to path (`export PATH=$PATH:<scala path>/bin:<scala.js path>/bin`) +5. Create a temporary directory and do: + + mkdir bin + echo 'import scala.scalajs.js.JSApp + object Foo extends JSApp { + + def main() = { + println(s"asdf ${1 + 1}") + new A + } + + class A + }' > foo.scala + scalajsc -d bin foo.scala + + scalajsp bin/Foo$.sjsir + # Verify output + scalajsp bin/Foo\$A.sjsir + # Verify output + + scalajsld -o test.js bin + # Verify output + + echo "Foo().main()" >> test.js + node test.js # Or your favorite thing to run JS + + # Expect "asdf 2" + +## HTML-Runners + +The following HTML-runners/testers must be manually tested: + + examples/helloworld/helloworld-{2.10|2.11}{|-fastopt}.html + examples/reversi/reversi-{2.10|2.11}{|-fastopt}.html + examples/testing/testing-{2.10|2.11}{|-fastopt}.html + test-suite/scalajs-test-suite-{2.10|2.11}{|-fastopt}.html + +## Sourcemaps + +To test source maps, do the following on: + + examples/reversi/reversi-{2.10|2.11}{|-fastopt}.html + +1. Open the respective file in Google Chrome +2. Set a break-point in the HTML launcher on the `new Reversi` statement +3. Step over calls to jQuery into constructor +4. Step into the call to `Array.tabulate` and verify that source maps + to Scala standard library sources work (should point to GitHub) +5. Single step through constructor, until you reach `buildUI()` +6. Step into `buildUI()` + + +## When releasing only + +Once all tests pass, tag the revision and verify that source maps to +Scala.js sources work correctly (should point to GitHub), following +the steps described in the section Sourcemaps. diff --git a/examples/scala-js/ci/check-partest-coverage.sh b/examples/scala-js/ci/check-partest-coverage.sh new file mode 100755 index 0000000..ca35f37 --- /dev/null +++ b/examples/scala-js/ci/check-partest-coverage.sh @@ -0,0 +1,58 @@ +#! /bin/sh + +# This script tests if all Scala partests are classified. Since +# Scala.js does not provide all the Scala functionality (see [1]), we +# have to exclude some partests from testing. Therefore, every partest +# in $TESTDIR has to be in exactly one of the following files located +# in $KNOWDIR: +# - WhitelistedTests.txt: Tests that succeed +# - BlacklistedTests.txt: Tests that fail since they test for behavior +# which is not supported in Scala.js +# - BuglistedTests.txt: Tests that fail due to a bug in Scala.js +# +# [1] http://www.scala-js.org/doc/semantics.html + +# Arguments +if [ $# -le 0 ]; then + echo "Please give full scala version as argument" >&2 + exit 42 +fi + +FULLVER="$1" + +# Config +BASEDIR="`dirname $0`/.." +TESTDIR="$BASEDIR/partest/fetchedSources/$1/test/files" +KNOWDIR="$BASEDIR/partest-suite/src/test/resources/scala/tools/partest/scalajs/$1/" + +# If the classification directory does not exist, this means (by +# definition) that we do not want to or cannot partest this scala +# version. Therefore, everything is OK. +if [ ! -d $KNOWDIR ]; then + exit 0 +fi + +# Temp files +TMP_PREF=`basename $0` +TMP_HAVE_FILE=`mktemp /tmp/${TMP_PREF}_have_XXXXX` || exit 2 +TMP_KNOW_FILE=`mktemp /tmp/${TMP_PREF}_know_XXXXX` || exit 2 + +# Trap removal of tmp files on exit +trap "rm \"$TMP_HAVE_FILE\" \"$TMP_KNOW_FILE\"" EXIT + +# Find all partests +( # Subshell to protect cwd +cd "$TESTDIR" +find "run" "neg" "pos" \ + -mindepth 1 -maxdepth 1 \( -type d -or -name '*.scala' \) \ + | sort >> $TMP_HAVE_FILE +) + +# Find classified partests +( # Subshell to protect cwd +cd "$KNOWDIR" +cat BlacklistedTests.txt BuglistedTests.txt WhitelistedTests.txt \ + | grep -E -v '^#|^\s*$' | sort >> $TMP_KNOW_FILE +) + +diff -U 0 --label 'Classified Tests' $TMP_KNOW_FILE --label 'Existing Tests' $TMP_HAVE_FILE diff --git a/examples/scala-js/ci/checksizes.sh b/examples/scala-js/ci/checksizes.sh new file mode 100755 index 0000000..8e7a508 --- /dev/null +++ b/examples/scala-js/ci/checksizes.sh @@ -0,0 +1,65 @@ +#!/bin/sh + +BASEDIR="`dirname $0`/.." + +FULLVER="$1" + +case $FULLVER in + 2.10.2) + VER=2.10 + ;; + 2.11.2) + VER=2.11 + ;; + 2.11.4) + VER=2.11 + ;; + 2.10.3|2.10.4|2.11.0|2.11.1) + echo "Ignoring checksizes for Scala $FULLVER" + exit 0 + ;; +esac + +REVERSI_PREOPT="$BASEDIR/examples/reversi/target/scala-$VER/reversi-fastopt.js" +REVERSI_OPT="$BASEDIR/examples/reversi/target/scala-$VER/reversi-opt.js" + +REVERSI_PREOPT_SIZE=$(stat '-c%s' "$REVERSI_PREOPT") +REVERSI_OPT_SIZE=$(stat '-c%s' "$REVERSI_OPT") + +gzip "$REVERSI_PREOPT" +gzip "$REVERSI_OPT" + +REVERSI_PREOPT_GZ_SIZE=$(stat '-c%s' "$REVERSI_PREOPT.gz") +REVERSI_OPT_GZ_SIZE=$(stat '-c%s' "$REVERSI_OPT.gz") + +case $FULLVER in + 2.10.2) + REVERSI_PREOPT_EXPECTEDSIZE=1008000 + REVERSI_OPT_EXPECTEDSIZE=161000 + REVERSI_PREOPT_GZ_EXPECTEDSIZE=109000 + REVERSI_OPT_GZ_EXPECTEDSIZE=43000 + ;; + 2.11.2) + REVERSI_PREOPT_EXPECTEDSIZE=943000 + REVERSI_OPT_EXPECTEDSIZE=151000 + REVERSI_PREOPT_GZ_EXPECTEDSIZE=104000 + REVERSI_OPT_GZ_EXPECTEDSIZE=39000 + ;; + 2.11.4) + REVERSI_PREOPT_EXPECTEDSIZE=946000 + REVERSI_OPT_EXPECTEDSIZE=152000 + REVERSI_PREOPT_GZ_EXPECTEDSIZE=104000 + REVERSI_OPT_GZ_EXPECTEDSIZE=40000 + ;; +esac + +echo "Checksizes: Scala version: $FULLVER" +echo "Reversi preopt size = $REVERSI_PREOPT_SIZE (expected $REVERSI_PREOPT_EXPECTEDSIZE)" +echo "Reversi opt size = $REVERSI_OPT_SIZE (expected $REVERSI_OPT_EXPECTEDSIZE)" +echo "Reversi preopt gzip size = $REVERSI_PREOPT_GZ_SIZE (expected $REVERSI_PREOPT_GZ_EXPECTEDSIZE)" +echo "Reversi opt gzip size = $REVERSI_OPT_GZ_SIZE (expected $REVERSI_OPT_GZ_EXPECTEDSIZE)" + +[ "$REVERSI_PREOPT_SIZE" -le "$REVERSI_PREOPT_EXPECTEDSIZE" ] && \ + [ "$REVERSI_OPT_SIZE" -le "$REVERSI_OPT_EXPECTEDSIZE" ] && \ + [ "$REVERSI_PREOPT_GZ_SIZE" -le "$REVERSI_PREOPT_GZ_EXPECTEDSIZE" ] && \ + [ "$REVERSI_OPT_GZ_SIZE" -le "$REVERSI_OPT_GZ_EXPECTEDSIZE" ] diff --git a/examples/scala-js/ci/matrix.xml b/examples/scala-js/ci/matrix.xml new file mode 100644 index 0000000..5c02146 --- /dev/null +++ b/examples/scala-js/ci/matrix.xml @@ -0,0 +1,360 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!DOCTYPE ci [ + <!ELEMENT ci (task*,matrix*)> + <!ELEMENT task (#PCDATA)> + <!ATTLIST task id ID #REQUIRED> + <!ELEMENT matrix (run*)> + <!ATTLIST matrix id ID #REQUIRED> + <!ELEMENT run (v*)> + <!ATTLIST run matrix IDREF #IMPLIED> + <!ATTLIST run task IDREF #IMPLIED> + <!ELEMENT v (#PCDATA)> + <!ATTLIST v n CDATA #REQUIRED> +]> +<ci> + + <task id="main"><![CDATA[ + export JAVA_HOME=$HOME/apps/java-$java + sbt ++$scala package packageDoc && + sbt ++$scala helloworld/run \ + 'set scalaJSStage in Global := FastOptStage' \ + ++$scala helloworld/run \ + 'set scalaJSStage in Global := FullOptStage' \ + ++$scala helloworld/run \ + helloworld/clean && + sbt 'set scalaJSOptimizerOptions in ScalaJSBuild.helloworld ~= (_.withDisableOptimizer(true))' \ + 'set scalaJSStage in Global := FastOptStage' \ + ++$scala helloworld/run \ + helloworld/clean && + sbt 'set inScope(ThisScope in ScalaJSBuild.helloworld)(postLinkJSEnv := PhantomJSEnv().value)' \ + 'set scalaJSStage in Global := FastOptStage' \ + ++$scala helloworld/run \ + 'set scalaJSStage in Global := FullOptStage' \ + ++$scala helloworld/run && + sbt ++$scala testingExample/test \ + 'set scalaJSStage in Global := FastOptStage' \ + ++$scala testingExample/test \ + 'set scalaJSStage in Global := FullOptStage' \ + ++$scala testingExample/test \ + testingExample/clean && + sbt 'set scalaJSOptimizerOptions in ScalaJSBuild.testingExample ~= (_.withDisableOptimizer(true))' \ + 'set scalaJSStage in Global := FastOptStage' \ + ++$scala testingExample/test && + sbt ++$scala "testSuite/testOnly -- -- -trhino -tsource-maps" \ + "noIrCheckTest/testOnly -- -- -trhino -tsource-maps" \ + 'set scalaJSStage in Global := FastOptStage' \ + ++$scala "testSuite/testOnly -- -- -ttypedarray -tnodejs -tsource-maps" \ + "noIrCheckTest/testOnly -- -- -ttypedarray -tnodejs -tsource-maps" \ + 'set scalaJSStage in Global := FullOptStage' \ + ++$scala "testSuite/testOnly -- -- -ttypedarray -tnodejs -tsource-maps" \ + "noIrCheckTest/testOnly -- -- -ttypedarray -tnodejs -tsource-maps" \ + testSuite/clean && + sbt 'set scalaJSOptimizerOptions in ScalaJSBuild.testSuite ~= (_.withDisableOptimizer(true))' \ + 'set scalaJSStage in Global := FastOptStage' \ + ++$scala "testSuite/testOnly -- -- -ttypedarray -tnodejs -tsource-maps" \ + testSuite/clean && + sbt 'set scalaJSSemantics in ScalaJSBuild.testSuite ~= { _.withAsInstanceOfs(scala.scalajs.tools.sem.CheckedBehavior.Compliant).withStrictFloats(true) }' \ + ++$scala "testSuite/testOnly -- -- -trhino -tsource-maps -tcompliant-asinstanceofs -tstrict-floats" \ + 'set scalaJSStage in Global := FastOptStage' \ + ++$scala "testSuite/testOnly -- -- -ttypedarray -tnodejs -tsource-maps -tcompliant-asinstanceofs -tstrict-floats" \ + 'set scalaJSStage in Global := FullOptStage' \ + ++$scala "testSuite/testOnly -- -- -ttypedarray -tnodejs -tsource-maps -tcompliant-asinstanceofs -tstrict-floats" \ + testSuite/clean && + sbt 'set scalaJSSemantics in ScalaJSBuild.testSuite ~= { _.withAsInstanceOfs(scala.scalajs.tools.sem.CheckedBehavior.Compliant).withStrictFloats(true) }' \ + 'set scalaJSOptimizerOptions in ScalaJSBuild.testSuite ~= (_.withDisableOptimizer(true))' \ + 'set scalaJSStage in Global := FastOptStage' \ + ++$scala "testSuite/testOnly -- -- -ttypedarray -tnodejs -tsource-maps -tcompliant-asinstanceofs -tstrict-floats" \ + testSuite/clean && + sbt 'set inScope(ThisScope in ScalaJSBuild.helloworld)(postLinkJSEnv := PhantomJSEnv().value)' \ + 'set scalaJSStage in Global := FastOptStage' \ + ++$scala "testSuite/testOnly -- -- -tphantomjs" \ + 'set scalaJSStage in Global := FullOptStage' \ + ++$scala "testSuite/testOnly -- -- -tphantomjs" \ + testSuite/clean && + sbt 'set scalacOptions in ScalaJSBuild.testSuite += "-Xexperimental"' \ + ++$scala "testSuite/testOnly -- -- -trhino -tsource-maps" \ + 'set scalaJSStage in Global := FastOptStage' \ + ++$scala "testSuite/testOnly -- -- -ttypedarray -tnodejs -tsource-maps" \ + 'set scalaJSStage in Global := FullOptStage' \ + ++$scala "testSuite/testOnly -- -- -ttypedarray -tnodejs -tsource-maps" && + sbt 'set scalaJSStage in Global := FastOptStage' \ + ++$scala "javalibExTestSuite/testOnly -- -- -ttypedarray -tnodejs -tsource-maps" \ + 'set scalaJSStage in Global := FullOptStage' \ + ++$scala "javalibExTestSuite/testOnly -- -- -ttypedarray -tnodejs -tsource-maps" && + sbt ++$scala compiler/test reversi/fastOptJS reversi/fullOptJS && + sbt ++$scala partest/fetchScalaSource && + sh ci/checksizes.sh $scala && + sh ci/check-partest-coverage.sh $scala + ]]></task> + + <task id="bootstrap"><![CDATA[ + export JAVA_HOME=$HOME/apps/java-$java + sbt 'set scalaJSStage in Global := FastOptStage' \ + ++$scala toolsJS/test \ + 'set scalaJSStage in Global := FullOptStage' \ + ++$scala toolsJS/test + ]]></task> + + <task id="tools-cli-stubs"><![CDATA[ + export JAVA_HOME=$HOME/apps/java-$java + sbt ++$scala tools/package tools/test cli/assembly stubs/package + ]]></task> + + <task id="tools-cli-stubs-sbtplugin"><![CDATA[ + export JAVA_HOME=$HOME/apps/java-$java + sbt ++$scala tools/package tools/test cli/assembly stubs/package \ + sbtPlugin/package sbtPlugin/test + ]]></task> + + <task id="partestc"><![CDATA[ + export JAVA_HOME=$HOME/apps/java-$java + sbt ++$scala partest/compile + ]]></task> + + <task id="sbtplugin-test"><![CDATA[ + # Publish Scala.js artifacts locally + sbt ++2.11.2 publishLocal ++2.10.4 tools/publishLocal sbtPlugin/publishLocal + # Go into standalone project and test + cd sbt-plugin-test + sbt noDOM/run withDOM/run test \ + 'set scalaJSStage in Global := FastOptStage' \ + jetty9/run test + ]]></task> + + <task id="partest-noopt"><![CDATA[ + export JAVA_HOME=$HOME/apps/java-$java + sbt ++$scala package "partestSuite/testOnly -- --showDiff" + ]]></task> + + <task id="partest-fastopt"><![CDATA[ + export JAVA_HOME=$HOME/apps/java-$java + sbt ++$scala package "partestSuite/testOnly -- --fastOpt --showDiff" + ]]></task> + + <task id="partest-fullopt"><![CDATA[ + export JAVA_HOME=$HOME/apps/java-$java + sbt ++$scala package "partestSuite/testOnly -- --fullOpt --showDiff" + ]]></task> + + <matrix id="pr"> + <!-- Main test tasks --> + <run task="main"> + <v n="scala">2.10.2</v> + <v n="java">1.6</v> + </run> + <run task="main"> + <v n="scala">2.10.2</v> + <v n="java">1.7</v> + </run> + <run task="main"> + <v n="scala">2.10.2</v> + <v n="java">1.8</v> + </run> + <run task="main"> + <v n="scala">2.11.2</v> + <v n="java">1.6</v> + </run> + <run task="main"> + <v n="scala">2.11.2</v> + <v n="java">1.7</v> + </run> + <run task="main"> + <v n="scala">2.11.2</v> + <v n="java">1.8</v> + </run> + <run task="main"> + <v n="scala">2.11.4</v> + <v n="java">1.7</v> + </run> + + <!-- Bootstrap test tasks --> + <run task="bootstrap"> + <v n="scala">2.10.2</v> + <v n="java">1.6</v> + </run> + <run task="bootstrap"> + <v n="scala">2.10.2</v> + <v n="java">1.7</v> + </run> + <run task="bootstrap"> + <v n="scala">2.10.2</v> + <v n="java">1.8</v> + </run> + <!-- Tools do not compile on JDK6, Scala 2.11.x (see #1235) --> + <run task="bootstrap"> + <v n="scala">2.11.2</v> + <v n="java">1.7</v> + </run> + <run task="bootstrap"> + <v n="scala">2.11.2</v> + <v n="java">1.8</v> + </run> + <!-- Tools do not compile on Scala 2.11.4 (see #1215). --> + + <!-- Tools / CLI / Stubs / sbtPlugin test tasks --> + <run task="tools-cli-stubs-sbtplugin"> + <v n="scala">2.10.4</v> + <v n="java">1.6</v> + </run> + <run task="tools-cli-stubs-sbtplugin"> + <v n="scala">2.10.4</v> + <v n="java">1.7</v> + </run> + <run task="tools-cli-stubs-sbtplugin"> + <v n="scala">2.10.4</v> + <v n="java">1.8</v> + </run> + <!-- Tools do not compile on JDK6, Scala 2.11.x (see #1235) --> + <run task="tools-cli-stubs"> + <v n="scala">2.11.2</v> + <v n="java">1.7</v> + </run> + <run task="tools-cli-stubs"> + <v n="scala">2.11.2</v> + <v n="java">1.8</v> + </run> + <!-- Tools do not compile on Scala 2.11.4 (see #1215). --> + + <!-- Partest compilation test tasks --> + <run task="partestc"> + <v n="scala">2.11.0</v> + <v n="java">1.7</v> + </run> + <run task="partestc"> + <v n="scala">2.11.1</v> + <v n="java">1.7</v> + </run> + <run task="partestc"> + <v n="scala">2.11.2</v> + <v n="java">1.7</v> + </run> + <!-- Partest does not compile on Scala 2.11.4 (see #1215). --> + + <run task="sbtplugin-test" /> + </matrix> + + <matrix id="nightly"> + <run matrix="pr" /> + + <!-- Main test tasks (all remaining Scala versions) --> + <run task="main"> + <v n="scala">2.10.3</v> + <v n="java">1.7</v> + </run> + <run task="main"> + <v n="scala">2.10.4</v> + <v n="java">1.6</v> + </run> + <run task="main"> + <v n="scala">2.10.4</v> + <v n="java">1.7</v> + </run> + <run task="main"> + <v n="scala">2.10.4</v> + <v n="java">1.8</v> + </run> + <run task="main"> + <v n="scala">2.11.0</v> + <v n="java">1.7</v> + </run> + <run task="main"> + <v n="scala">2.11.1</v> + <v n="java">1.7</v> + </run> + <run task="main"> + <v n="scala">2.11.4</v> + <v n="java">1.6</v> + </run> + <run task="main"> + <v n="scala">2.11.4</v> + <v n="java">1.8</v> + </run> + + <!-- Bootstrap test tasks (all remaining Scala versions) --> + <run task="bootstrap"> + <v n="scala">2.10.3</v> + <v n="java">1.7</v> + </run> + <run task="bootstrap"> + <v n="scala">2.10.4</v> + <v n="java">1.6</v> + </run> + <run task="bootstrap"> + <v n="scala">2.10.4</v> + <v n="java">1.7</v> + </run> + <run task="bootstrap"> + <v n="scala">2.10.4</v> + <v n="java">1.8</v> + </run> + <run task="bootstrap"> + <v n="scala">2.11.0</v> + <v n="java">1.7</v> + </run> + <run task="bootstrap"> + <v n="scala">2.11.1</v> + <v n="java">1.7</v> + </run> + + <run task="partest-noopt"> + <v n="scala">2.11.2</v> + <v n="java">1.7</v> + </run> + <run task="partest-fastopt"> + <v n="scala">2.11.2</v> + <v n="java">1.7</v> + </run> + <run task="partest-fullopt"> + <v n="scala">2.11.2</v> + <v n="java">1.7</v> + </run> + </matrix> + + <matrix id="weekly"> + <!-- weekly does not have to run nightly, since they will run at the same time --> + + <run task="partest-noopt"> + <v n="scala">2.11.0</v> + <v n="java">1.7</v> + </run> + <run task="partest-fastopt"> + <v n="scala">2.11.0</v> + <v n="java">1.7</v> + </run> + <run task="partest-fullopt"> + <v n="scala">2.11.0</v> + <v n="java">1.7</v> + </run> + <run task="partest-noopt"> + <v n="scala">2.11.1</v> + <v n="java">1.7</v> + </run> + <run task="partest-fastopt"> + <v n="scala">2.11.1</v> + <v n="java">1.7</v> + </run> + <run task="partest-fullopt"> + <v n="scala">2.11.1</v> + <v n="java">1.7</v> + </run> + <run task="partest-noopt"> + <v n="scala">2.11.2</v> + <v n="java">1.8</v> + </run> + <run task="partest-fastopt"> + <v n="scala">2.11.2</v> + <v n="java">1.8</v> + </run> + <run task="partest-fullopt"> + <v n="scala">2.11.2</v> + <v n="java">1.8</v> + </run> + <!-- + Partest does sometimes not compile on JDK6 (see #1227) we + therefore do not run any JDK6 partests. + + Partest does not compile on Scala 2.11.4 (see #1215). + --> + </matrix> + +</ci> diff --git a/examples/scala-js/ci/scalajs-matrix-build.groovy b/examples/scala-js/ci/scalajs-matrix-build.groovy new file mode 100644 index 0000000..f2a661a --- /dev/null +++ b/examples/scala-js/ci/scalajs-matrix-build.groovy @@ -0,0 +1,57 @@ +out.println("Trying to run matrix ${params.matrix}") + +out.println("Loading ci/matrix.xml") + +def matrixFile = build.properties.moduleRoot.child("ci/matrix.xml") +def fact = javax.xml.parsers.DocumentBuilderFactory.newInstance() +def builder = fact.newDocumentBuilder() + +ciMatrix = builder.parse(matrixFile.read()) + +out.println("Loading relevant definitions") + +buildDefs = [] + +fetchMatrix(params.matrix) + +def fetchMatrix(matrixName) { + def matrix = ciMatrix.getElementById(matrixName) + def list = matrix.getElementsByTagName("run") + for (int i = 0; i < list.getLength(); ++i) { + handleRun(list.item(i)) + } +} + +def handleRun(run) { + def attrs = run.getAttributes() + def matrixAttr = attrs.getNamedItem("matrix") + def taskAttr = attrs.getNamedItem("task") + if (matrixAttr != null) + fetchMatrix(matrixAttr.getValue()) + else if (taskAttr != null) + fetchTask(taskAttr.getValue(), run) +} + +def fetchTask(taskName, runElem) { + def taskElem = ciMatrix.getElementById(taskName) + def taskStr = taskElem.getTextContent() + def fullTaskName = taskName + + runElem.getElementsByTagName("v").each { v -> + def name = v.getAttribute("n") + def value = v.getTextContent() + taskStr = taskStr.replace('$' + name, value) + fullTaskName += " $name=$value" + } + + out.println("Found task: $fullTaskName") + + buildDefs.add({ + build("scalajs-task-worker", + refspec: params.refspec, + sha1: params.sha1, + taskCommand: taskStr) + }) +} + +parallel(buildDefs) diff --git a/examples/scala-js/cli/src/main/resources/scalajsc b/examples/scala-js/cli/src/main/resources/scalajsc new file mode 100755 index 0000000..7fd1100 --- /dev/null +++ b/examples/scala-js/cli/src/main/resources/scalajsc @@ -0,0 +1,16 @@ +#! /bin/sh + +SCALA_BIN_VER="@SCALA_BIN_VER@" +SCALAJS_VER="@SCALAJS_VER@" +SCALA_VER=$(scalac -version 2>&1 | grep -o '[0-9]\.[0-9][0-9]\.[0-9]') + +if [ "$(echo $SCALA_VER | cut -b 1-4)" != "$SCALA_BIN_VER" ]; then + echo "This bundle of Scala.js CLI is for $SCALA_BIN_VER. Your scala version is $SCALA_VER!" >&2 + exit 1 +fi + +BASE="$(dirname $0)/.." +PLUGIN="$BASE/lib/scalajs-compiler_$SCALA_VER-$SCALAJS_VER.jar" +JSLIB="$BASE/lib/scalajs-library_$SCALA_BIN_VER-$SCALAJS_VER.jar" + +scalac -classpath "$JSLIB" "-Xplugin:$PLUGIN" "$@" diff --git a/examples/scala-js/cli/src/main/resources/scalajsc.bat b/examples/scala-js/cli/src/main/resources/scalajsc.bat new file mode 100644 index 0000000..767c5df --- /dev/null +++ b/examples/scala-js/cli/src/main/resources/scalajsc.bat @@ -0,0 +1,14 @@ +@ECHO OFF +set SCALA_BIN_VER=@SCALA_BIN_VER@ +set SCALAJS_VER=@SCALAJS_VER@ + +for /F "tokens=5" %%i in (' scala -version 2^>^&1 1^>nul ') do set SCALA_VER=%%i + +if NOT "%SCALA_VER:~0,4%" == "%SCALA_BIN_VER%" ( + echo "This bundle of Scala.js CLI is for %SCALA_BIN_VER%. Your scala version is %SCALA_VER%!" 1>&2 +) else ( + set PLUGIN=%~dp0\..\lib\scalajs-compiler_%SCALA_VER%-%SCALAJS_VER%.jar + set JSLIB=%~dp0\..\lib\scalajs-library_%SCALA_BIN_VER%-%SCALAJS_VER%.jar + + scalac -classpath "%JSLIB%" "-Xplugin:%PLUGIN%" %* +) diff --git a/examples/scala-js/cli/src/main/resources/scalajsld b/examples/scala-js/cli/src/main/resources/scalajsld new file mode 100755 index 0000000..7732e2a --- /dev/null +++ b/examples/scala-js/cli/src/main/resources/scalajsld @@ -0,0 +1,10 @@ +#! /bin/sh + +SCALA_BIN_VER="@SCALA_BIN_VER@" +SCALAJS_VER="@SCALAJS_VER@" + +BASE="$(dirname $0)/.." +CLILIB="$BASE/lib/scalajs-cli-assembly_$SCALA_BIN_VER-$SCALAJS_VER.jar" +JSLIB="$BASE/lib/scalajs-library_$SCALA_BIN_VER-$SCALAJS_VER.jar" + +scala -classpath "$CLILIB" scala.scalajs.cli.Scalajsld --stdlib "$JSLIB" "$@" diff --git a/examples/scala-js/cli/src/main/resources/scalajsld.bat b/examples/scala-js/cli/src/main/resources/scalajsld.bat new file mode 100644 index 0000000..e915237 --- /dev/null +++ b/examples/scala-js/cli/src/main/resources/scalajsld.bat @@ -0,0 +1,8 @@ +@ECHO OFF +set SCALA_BIN_VER=@SCALA_BIN_VER@ +set SCALAJS_VER=@SCALAJS_VER@ + +set CLILIB="%~dp0\..\lib\scalajs-cli-assembly_%SCALA_BIN_VER%-%SCALAJS_VER%.jar" +set JSLIB="%~dp0\..\lib\scalajs-library_%SCALA_BIN_VER%-%SCALAJS_VER%.jar" + +scala -classpath %CLILIB% scala.scalajs.cli.Scalajsld --stdlib %JSLIB% %* diff --git a/examples/scala-js/cli/src/main/resources/scalajsp b/examples/scala-js/cli/src/main/resources/scalajsp new file mode 100755 index 0000000..e7a6e58 --- /dev/null +++ b/examples/scala-js/cli/src/main/resources/scalajsp @@ -0,0 +1,9 @@ +#! /bin/sh + +SCALA_BIN_VER="@SCALA_BIN_VER@" +SCALAJS_VER="@SCALAJS_VER@" + +BASE="$(dirname $0)/.." +CLILIB="$BASE/lib/scalajs-cli-assembly_$SCALA_BIN_VER-$SCALAJS_VER.jar" + +scala -classpath "$CLILIB" scala.scalajs.cli.Scalajsp "$@" diff --git a/examples/scala-js/cli/src/main/resources/scalajsp.bat b/examples/scala-js/cli/src/main/resources/scalajsp.bat new file mode 100644 index 0000000..dd9745c --- /dev/null +++ b/examples/scala-js/cli/src/main/resources/scalajsp.bat @@ -0,0 +1,7 @@ +@ECHO OFF +set SCALA_BIN_VER=@SCALA_BIN_VER@ +set SCALAJS_VER=@SCALAJS_VER@ + +set CLILIB="%~dp0\..\lib\scalajs-cli-assembly_%SCALA_BIN_VER%-%SCALAJS_VER%.jar" + +scala -classpath %CLILIB% scala.scalajs.cli.Scalajsp %* diff --git a/examples/scala-js/cli/src/main/scala/scala/scalajs/cli/Scalajsld.scala b/examples/scala-js/cli/src/main/scala/scala/scalajs/cli/Scalajsld.scala new file mode 100644 index 0000000..55e61af --- /dev/null +++ b/examples/scala-js/cli/src/main/scala/scala/scalajs/cli/Scalajsld.scala @@ -0,0 +1,180 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js CLI ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.cli + +import scala.scalajs.ir.ScalaJSVersions + +import scala.scalajs.tools.sem._ +import scala.scalajs.tools.io._ +import scala.scalajs.tools.logging._ +import scala.scalajs.tools.classpath._ +import scala.scalajs.tools.classpath.builder._ + +import CheckedBehavior.Compliant + +import scala.scalajs.tools.optimizer.{ + ScalaJSOptimizer, + ScalaJSClosureOptimizer, + ParIncOptimizer +} + +import scala.collection.immutable.Seq + +import java.io.File +import java.net.URI + +object Scalajsld { + + case class Options( + cp: Seq[File] = Seq.empty, + output: File = null, + jsoutput: Option[File] = None, + semantics: Semantics = Semantics.Defaults, + noOpt: Boolean = false, + fullOpt: Boolean = false, + prettyPrint: Boolean = false, + sourceMap: Boolean = false, + relativizeSourceMap: Option[URI] = None, + checkIR: Boolean = false, + stdLib: Option[File] = None, + logLevel: Level = Level.Info) + + def main(args: Array[String]): Unit = { + val parser = new scopt.OptionParser[Options]("scalajsld") { + head("scalajsld", ScalaJSVersions.current) + arg[File]("<value> ...") + .unbounded() + .action { (x, c) => c.copy(cp = c.cp :+ x) } + .text("Entries of Scala.js classpath to link") + opt[File]('o', "output") + .valueName("<file>") + .required() + .action { (x, c) => c.copy(output = x) } + .text("Output file of linker (required)") + opt[File]("jsoutput") + .valueName("<file>") + .abbr("jo") + .action { (x, c) => c.copy(jsoutput = Some(x)) } + .text("Concatenate all JavaScript libary dependencies to this file") + opt[Unit]('f', "fastOpt") + .action { (_, c) => c.copy(noOpt = false, fullOpt = false) } + .text("Optimize code (this is the default)") + opt[Unit]('n', "noOpt") + .action { (_, c) => c.copy(noOpt = true, fullOpt = false) } + .text("Don't optimize code") + opt[Unit]('u', "fullOpt") + .action { (_, c) => c.copy(noOpt = false, fullOpt = true) } + .text("Fully optimize code (uses Google Closure Compiler)") + opt[Unit]('p', "prettyPrint") + .action { (_, c) => c.copy(prettyPrint = true) } + .text("Pretty print full opted code (meaningful with -u)") + opt[Unit]('s', "sourceMap") + .action { (_, c) => c.copy(sourceMap = true) } + .text("Produce a source map for the produced code") + opt[Unit]("compliantAsInstanceOfs") + .action { (_, c) => c.copy(semantics = + c.semantics.withAsInstanceOfs(Compliant)) + } + .text("Use compliant asInstanceOfs") + opt[Unit]('c', "checkIR") + .action { (_, c) => c.copy(checkIR = true) } + .text("Check IR before optimizing") + opt[File]('r', "relativizeSourceMap") + .valueName("<path>") + .action { (x, c) => c.copy(relativizeSourceMap = Some(x.toURI)) } + .text("Relativize source map with respect to given path (meaningful with -s)") + opt[Unit]("noStdlib") + .action { (_, c) => c.copy(stdLib = None) } + .text("Don't automatcially include Scala.js standard library") + opt[File]("stdlib") + .valueName("<scala.js stdlib jar>") + .hidden() + .action { (x, c) => c.copy(stdLib = Some(x)) } + .text("Location of Scala.js standard libarary. This is set by the " + + "runner script and automatically prepended to the classpath. " + + "Use -n to not include it.") + opt[Unit]('d', "debug") + .action { (_, c) => c.copy(logLevel = Level.Debug) } + .text("Debug mode: Show full log") + opt[Unit]('q', "quiet") + .action { (_, c) => c.copy(logLevel = Level.Warn) } + .text("Only show warnings & errors") + opt[Unit]("really-quiet") + .abbr("qq") + .action { (_, c) => c.copy(logLevel = Level.Error) } + .text("Only show errors") + version("version") + .abbr("v") + .text("Show scalajsld version") + help("help") + .abbr("h") + .text("prints this usage text") + + override def showUsageOnError = true + } + + for (options <- parser.parse(args, Options())) { + val cpFiles = options.stdLib.toList ++ options.cp + // Load and resolve classpath + val cp = PartialClasspathBuilder.build(cpFiles).resolve() + + // Write JS dependencies if requested + for (jsout <- options.jsoutput) + IO.concatFiles(WritableFileVirtualJSFile(jsout), cp.jsLibs.map(_.lib)) + + // Link Scala.js code + val outFile = WritableFileVirtualJSFile(options.output) + if (options.fullOpt) + fullOpt(cp, outFile, options) + else + fastOpt(cp, outFile, options) + } + } + + private def fullOpt(cp: IRClasspath, + output: WritableVirtualJSFile, options: Options) = { + import ScalaJSClosureOptimizer._ + + val semantics = options.semantics.optimized + + new ScalaJSClosureOptimizer(semantics).optimizeCP( + newScalaJSOptimizer(semantics), + Inputs(ScalaJSOptimizer.Inputs(cp)), + OutputConfig( + output = output, + wantSourceMap = options.sourceMap, + relativizeSourceMapBase = options.relativizeSourceMap, + checkIR = options.checkIR, + prettyPrint = options.prettyPrint), + newLogger(options)) + } + + private def fastOpt(cp: IRClasspath, + output: WritableVirtualJSFile, options: Options) = { + import ScalaJSOptimizer._ + + newScalaJSOptimizer(options.semantics).optimizeCP( + Inputs(cp), + OutputConfig( + output = output, + wantSourceMap = options.sourceMap, + checkIR = options.checkIR, + disableOptimizer = options.noOpt, + relativizeSourceMapBase = options.relativizeSourceMap), + newLogger(options)) + } + + private def newLogger(options: Options) = + new ScalaConsoleLogger(options.logLevel) + + private def newScalaJSOptimizer(semantics: Semantics) = + new ScalaJSOptimizer(semantics, new ParIncOptimizer(_)) + +} diff --git a/examples/scala-js/cli/src/main/scala/scala/scalajs/cli/Scalajsp.scala b/examples/scala-js/cli/src/main/scala/scala/scalajs/cli/Scalajsp.scala new file mode 100644 index 0000000..0d64b93 --- /dev/null +++ b/examples/scala-js/cli/src/main/scala/scala/scalajs/cli/Scalajsp.scala @@ -0,0 +1,158 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js CLI ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.cli + +import scala.scalajs.ir +import ir.ScalaJSVersions +import ir.Trees.{Tree, ClassDef} +import ir.Printers.{InfoPrinter, IRTreePrinter} + +import scala.scalajs.tools.sem.Semantics +import scala.scalajs.tools.javascript +import javascript.ScalaJSClassEmitter +import javascript.Printers.JSTreePrinter + +import scala.scalajs.tools.io._ +import scala.collection.immutable.Seq + +import java.io.{Console => _, _} +import java.util.zip.{ZipFile, ZipEntry} + +object Scalajsp { + + case class Options( + infos: Boolean = false, + desugar: Boolean = false, + showReflProxy: Boolean = false, + jar: Option[File] = None, + fileNames: Seq[String] = Seq.empty) + + def main(args: Array[String]): Unit = { + val parser = new scopt.OptionParser[Options]("scalajsp") { + head("scalajsp", ScalaJSVersions.current) + arg[String]("<file> ...") + .unbounded() + .action { (x, c) => c.copy(fileNames = c.fileNames :+ x) } + .text("*.sjsir file to display content of") + opt[File]('j', "jar") + .valueName("<jar>") + .action { (x, c) => c.copy(jar = Some(x)) } + .text("Read *.sjsir file(s) from the given JAR.") + opt[Unit]('d', "desugar") + .action { (_, c) => c.copy(desugar = true) } + .text("Desugar JS trees. This yields runnable JavaScript") + opt[Unit]('i', "infos") + .action { (_, c) => c.copy(infos = true) } + .text("Show DCE infos instead of trees") + opt[Unit]('p', "reflProxies") + .action { (_, c) => c.copy(showReflProxy = true) } + .text("Show reflective call proxies") + opt[Unit]('s', "supported") + .action { (_,_) => printSupported(); sys.exit() } + .text("Show supported Scala.js IR versions") + version("version") + .abbr("v") + .text("Show scalajsp version") + help("help") + .abbr("h") + .text("prints this usage text") + + override def showUsageOnError = true + } + + for { + options <- parser.parse(args, Options()) + fileName <- options.fileNames + } { + val vfile = options.jar map { jar => + readFromJar(jar, fileName) + } getOrElse { + readFromFile(fileName) + } + + displayFileContent(vfile, options) + } + } + + def printSupported(): Unit = { + import ScalaJSVersions._ + println(s"Emitted Scala.js IR version is: $binaryEmitted") + println("Supported Scala.js IR versions are") + binarySupported.foreach(v => println(s"* $v")) + } + + def displayFileContent(vfile: VirtualScalaJSIRFile, opts: Options): Unit = { + if (opts.infos) + new InfoPrinter(stdout).printClassInfo(vfile.info) + else { + val outTree = { + if (opts.showReflProxy) vfile.tree + else filterOutReflProxies(vfile.tree) + } + + if (opts.desugar) + new JSTreePrinter(stdout).printTopLevelTree( + new ScalaJSClassEmitter(Semantics.Defaults).genClassDef(outTree)) + else + new IRTreePrinter(stdout).printTopLevelTree(outTree) + } + + stdout.flush() + } + + private def fail(msg: String) = { + Console.err.println(msg) + sys.exit(1) + } + + private def readFromFile(fileName: String) = { + val file = new File(fileName) + + if (!file.exists) + fail(s"No such file: $fileName") + else if (!file.canRead) + fail(s"Unable to read file: $fileName") + else + FileVirtualScalaJSIRFile(file) + } + + private def readFromJar(jar: File, name: String) = { + val jarFile = + try { new ZipFile(jar) } + catch { case _: FileNotFoundException => fail(s"No such JAR: $jar") } + try { + val entry = jarFile.getEntry(name) + if (entry == null) + fail(s"No such file in jar: $name") + else { + val name = jarFile.getName + "#" + entry.getName + val content = + IO.readInputStreamToByteArray(jarFile.getInputStream(entry)) + new MemVirtualSerializedScalaJSIRFile(name).withContent(content) + } + } finally { + jarFile.close() + } + } + + private val stdout = + new BufferedWriter(new OutputStreamWriter(Console.out, "UTF-8")) + + private def filterOutReflProxies(tree: ClassDef): ClassDef = { + import ir.Trees._ + import ir.Definitions.isReflProxyName + val newDefs = tree.defs.filter { + case MethodDef(Ident(name, _), _, _, _) => !isReflProxyName(name) + case _ => true + } + tree.copy(defs = newDefs)(tree.pos) + } + +} diff --git a/examples/scala-js/compiler/src/main/resources/scalac-plugin.xml b/examples/scala-js/compiler/src/main/resources/scalac-plugin.xml new file mode 100644 index 0000000..76ff1b7 --- /dev/null +++ b/examples/scala-js/compiler/src/main/resources/scalac-plugin.xml @@ -0,0 +1,4 @@ +<plugin> + <name>scalajs</name> + <classname>scala.scalajs.compiler.ScalaJSPlugin</classname> +</plugin> diff --git a/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/ClassInfos.scala b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/ClassInfos.scala new file mode 100644 index 0000000..026d664 --- /dev/null +++ b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/ClassInfos.scala @@ -0,0 +1,143 @@ +/* Scala.js compiler + * Copyright 2013 LAMP/EPFL + * @author Sébastien Doeraene + */ + +package scala.scalajs.compiler + +import scala.language.implicitConversions + +import scala.collection.mutable +import scala.tools.nsc._ + +import java.io.{ File, PrintWriter, BufferedOutputStream, FileOutputStream } + +import scala.scalajs.ir +import ir.{Trees => js, Types => jstpe, ClassKind} +import ir.Infos._ + +trait ClassInfos extends SubComponent { self: GenJSCode => + import global._ + import jsAddons._ + + /** Class data that are never eliminated by dce, so we don't need to + * record them. + */ + private val AlwaysPresentClassData = { + import ir.Definitions._ + Set("V", "Z", "C", "B", "S", "I", "J", "F", "D", + ObjectClass, StringClass) + } + + class ClassInfoBuilder(val symbol: ClassSymbol) { + val name = classNameOf(symbol) + val encodedName = encodeClassFullName(symbol) + var isExported: Boolean = false + val ancestorCount = symbol.ancestors.count(!_.isInterface) + val kind = { + if (isStaticModule(symbol)) ClassKind.ModuleClass + else if (symbol.isInterface) ClassKind.Interface + else if (isRawJSType(symbol.tpe)) ClassKind.RawJSType + else if (isHijackedBoxedClass(symbol)) ClassKind.HijackedClass + else if (symbol.isImplClass) ClassKind.TraitImpl + else ClassKind.Class + } + val superClass = + if (kind.isClass || kind == ClassKind.HijackedClass) + encodeClassFullName(symbol.superClass) + else + "" + val ancestors = (symbol :: symbol.ancestors) map encodeClassFullName + + var optimizerHints: OptimizerHints = OptimizerHints.empty + + val methodInfos = mutable.ListBuffer.empty[MethodInfoBuilder] + + def addMethod(encodedName: String, isAbstract: Boolean = false, + isExported: Boolean = false): MethodInfoBuilder = { + val b = new MethodInfoBuilder(encodedName, isAbstract, isExported) + methodInfos += b + b + } + + def result(): ClassInfo = { + ClassInfo(name, encodedName, isExported, ancestorCount, kind, + superClass, ancestors, optimizerHints, + methodInfos.map(_.result()).result()) + } + } + + class MethodInfoBuilder(val encodedName: String, + val isAbstract: Boolean = false, + val isExported: Boolean = false) { + + val calledMethods = mutable.Set.empty[(String, String)] // (tpe, method) + val calledMethodsStatic = mutable.Set.empty[(String, String)] // (class, method) + val instantiatedClasses = mutable.Set.empty[String] + val accessedModules = mutable.Set.empty[String] + val accessedClassData = mutable.Set.empty[String] + var optimizerHints: OptimizerHints = OptimizerHints.empty + + def callsMethod(ownerIdent: js.Ident, method: js.Ident): Unit = + calledMethods += ((patchClassName(ownerIdent.name), method.name)) + + def callsMethod(owner: Symbol, method: js.Ident): Unit = + calledMethods += ((patchClassName(encodeClassFullName(owner)), method.name)) + + def callsMethodStatic(ownerIdent: js.Ident, method: js.Ident): Unit = + calledMethodsStatic += ((patchClassName(ownerIdent.name), method.name)) + + def instantiatesClass(classSym: Symbol): Unit = + instantiatedClasses += patchClassName(encodeClassFullName(classSym)) + + def accessesModule(moduleClassSym: Symbol): Unit = + accessedModules += patchModuleName(encodeModuleFullName(moduleClassSym)) + + def accessesClassData(refType: jstpe.ReferenceType): Unit = { + val className = refType match { + case jstpe.ClassType(name) => name + case jstpe.ArrayType(base, _) => base + } + if (!AlwaysPresentClassData.contains(className)) + accessedClassData += className + } + + def createsAnonFunction(funInfo: ClassInfoBuilder): Unit = { + for (methodInfo <- funInfo.methodInfos) { + calledMethods ++= methodInfo.calledMethods + calledMethodsStatic ++= methodInfo.calledMethodsStatic + instantiatedClasses ++= methodInfo.instantiatedClasses + accessedModules ++= methodInfo.accessedModules + accessedClassData ++= methodInfo.accessedClassData + } + } + + private def patchClassName(name: String): String = name match { + case "jl_String$" => "sjsr_RuntimeString$" + case _ => name + } + + private def patchModuleName(name: String): String = name match { + case "jl_String" => "sjsr_RuntimeString" + case _ => name + } + + def result(): MethodInfo = { + MethodInfo( + encodedName, + isAbstract, + isExported, + calledMethods.toList.groupBy(_._1).mapValues(_.map(_._2)), + calledMethodsStatic.toList.groupBy(_._1).mapValues(_.map(_._2)), + instantiatedClasses.toList, + accessedModules.result.toList, + accessedClassData.result.toList, + optimizerHints + ) + } + } + + private def classNameOf(sym: Symbol): String = + if (needsModuleClassSuffix(sym)) sym.fullName + nme.MODULE_SUFFIX_STRING + else sym.fullName +} diff --git a/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/Compat210Component.scala b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/Compat210Component.scala new file mode 100644 index 0000000..f357337 --- /dev/null +++ b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/Compat210Component.scala @@ -0,0 +1,108 @@ +/* Scala.js compiler + * Copyright 2013 LAMP/EPFL + * @author Sébastien Doeraene + */ + +package scala.scalajs.compiler + +import scala.tools.nsc._ + +/** Hacks to have our source code compatible with 2.10 and 2.11. + * It exposes 2.11 API in a 2.10 compiler. + * + * @author Sébastien Doeraene + */ +trait Compat210Component { + + val global: Global + + import global._ + + // unexpandedName replaces originalName + + implicit final class SymbolCompat(self: Symbol) { + def unexpandedName: Name = self.originalName + def originalName: Name = sys.error("infinite loop in Compat") + + def isLocalToBlock: Boolean = self.isLocal + } + + // enteringPhase/exitingPhase replace beforePhase/afterPhase + + @inline final def enteringPhase[T](ph: Phase)(op: => T): T = { + global.enteringPhase(ph)(op) + } + + @inline final def exitingPhase[T](ph: Phase)(op: => T): T = { + global.exitingPhase(ph)(op) + } + + private implicit final class GlobalCompat( + self: Compat210Component.this.global.type) { + + def enteringPhase[T](ph: Phase)(op: => T): T = self.beforePhase(ph)(op) + def beforePhase[T](ph: Phase)(op: => T): T = sys.error("infinite loop in Compat") + + def exitingPhase[T](ph: Phase)(op: => T): T = self.afterPhase(ph)(op) + def afterPhase[T](ph: Phase)(op: => T): T = sys.error("infinite loop in Compat") + } + + // ErasedValueType has a different encoding + + implicit final class ErasedValueTypeCompat(self: global.ErasedValueType) { + def valueClazz: Symbol = self.original.typeSymbol + def erasedUnderlying: Type = + enteringPhase(currentRun.erasurePhase)( + erasure.erasedValueClassArg(self.original)) + def original: TypeRef = sys.error("infinite loop in Compat") + } + + // repeatedToSingle + + @inline final def repeatedToSingle(t: Type) = + global.definitions.repeatedToSingle(t) + + private implicit final class DefinitionsCompat( + self: Compat210Component.this.global.definitions.type) { + + def repeatedToSingle(t: Type) = t match { + case TypeRef(_, self.RepeatedParamClass, arg :: Nil) => arg + case _ => t + } + + } + + // run.runDefinitions bundles methods and state related to the run + // that were previously in definitions itself + + implicit final class RunCompat(self: Run) { + val runDefinitions: Compat210Component.this.global.definitions.type = + global.definitions + } + + // Mode.FUNmode replaces analyzer.FUNmode + + object Mode { + import Compat210Component.AnalyzerCompat + // No type ascription! Type is different in 2.10 / 2.11 + val FUNmode = analyzer.FUNmode + } +} + +object Compat210Component { + private object LowPriorityMode { + object Mode { + def FUNmode = sys.error("infinite loop in Compat") + } + } + + private implicit final class AnalyzerCompat(self: scala.tools.nsc.typechecker.Analyzer) { + def FUNmode = { + import Compat210Component.LowPriorityMode._ + { + import scala.reflect.internal._ + Mode.FUNmode + } + } + } +} diff --git a/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/GenJSCode.scala b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/GenJSCode.scala new file mode 100644 index 0000000..f9885a0 --- /dev/null +++ b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/GenJSCode.scala @@ -0,0 +1,3911 @@ +/* Scala.js compiler + * Copyright 2013 LAMP/EPFL + * @author Sébastien Doeraene + */ + +package scala.scalajs.compiler + +import scala.language.implicitConversions + +import scala.annotation.switch + +import scala.collection.mutable +import scala.collection.mutable.ListBuffer + +import scala.tools.nsc._ + +import scala.annotation.tailrec + +import scala.scalajs.ir +import ir.{Trees => js, Types => jstpe, ClassKind, Hashers} + +import util.ScopedVar +import ScopedVar.withScopedVars + +/** Generate JavaScript code and output it to disk + * + * @author Sébastien Doeraene + */ +abstract class GenJSCode extends plugins.PluginComponent + with TypeKinds + with JSEncoding + with GenJSExports + with ClassInfos + with GenJSFiles + with Compat210Component { + + val jsAddons: JSGlobalAddons { + val global: GenJSCode.this.global.type + } + + val scalaJSOpts: ScalaJSOptions + + import global._ + import jsAddons._ + import rootMirror._ + import definitions._ + import jsDefinitions._ + import JSTreeExtractors._ + + import treeInfo.hasSynthCaseSymbol + + import platform.isMaybeBoxed + + val phaseName = "jscode" + + /** testing: this will be called when ASTs are generated */ + def generatedJSAST(clDefs: List[js.Tree]): Unit + + /** Implicit conversion from nsc Position to ir.Position. */ + implicit def pos2irPos(pos: Position): ir.Position = { + if (pos == NoPosition) ir.Position.NoPosition + else { + val source = pos2irPosCache.toIRSource(pos.source) + // nsc positions are 1-based but IR positions are 0-based + ir.Position(source, pos.line-1, pos.column-1) + } + } + + private[this] object pos2irPosCache { + import scala.reflect.internal.util._ + + private[this] var lastNscSource: SourceFile = null + private[this] var lastIRSource: ir.Position.SourceFile = null + + def toIRSource(nscSource: SourceFile): ir.Position.SourceFile = { + if (nscSource != lastNscSource) { + lastIRSource = convert(nscSource) + lastNscSource = nscSource + } + lastIRSource + } + + private[this] def convert(nscSource: SourceFile): ir.Position.SourceFile = { + nscSource.file.file match { + case null => + new java.net.URI( + "virtualfile", // Pseudo-Scheme + nscSource.file.path, // Scheme specific part + null // Fragment + ) + case file => + val srcURI = file.toURI + def matches(pat: java.net.URI) = pat.relativize(srcURI) != srcURI + + scalaJSOpts.sourceURIMaps.collectFirst { + case ScalaJSOptions.URIMap(from, to) if matches(from) => + val relURI = from.relativize(srcURI) + to.fold(relURI)(_.resolve(relURI)) + } getOrElse srcURI + } + } + + def clear(): Unit = { + lastNscSource = null + lastIRSource = null + } + } + + /** Materialize implicitly an ir.Position from an implicit nsc Position. */ + implicit def implicitPos2irPos(implicit pos: Position): ir.Position = pos + + override def newPhase(p: Phase) = new JSCodePhase(p) + + private object jsnme { + val arg_outer = newTermName("arg$outer") + val newString = newTermName("newString") + } + + class JSCodePhase(prev: Phase) extends StdPhase(prev) with JSExportsPhase { + + override def name = phaseName + override def description = "Generate JavaScript code from ASTs" + override def erasedTypes = true + + // Some state -------------------------------------------------------------- + + val currentClassSym = new ScopedVar[Symbol] + val currentClassInfoBuilder = new ScopedVar[ClassInfoBuilder] + val currentMethodSym = new ScopedVar[Symbol] + val currentMethodInfoBuilder = new ScopedVar[MethodInfoBuilder] + val methodTailJumpThisSym = new ScopedVar[Symbol](NoSymbol) + val fakeTailJumpParamRepl = new ScopedVar[(Symbol, Symbol)]((NoSymbol, NoSymbol)) + val enclosingLabelDefParams = new ScopedVar(Map.empty[Symbol, List[Symbol]]) + val mutableLocalVars = new ScopedVar[mutable.Set[Symbol]] + val mutatedLocalVars = new ScopedVar[mutable.Set[Symbol]] + val paramAccessorLocals = new ScopedVar(Map.empty[Symbol, js.ParamDef]) + + var isModuleInitialized: Boolean = false // see genApply for super calls + + def currentClassType = encodeClassType(currentClassSym) + + val tryingToGenMethodAsJSFunction = new ScopedVar[Boolean](false) + class CancelGenMethodAsJSFunction(message: String) + extends Throwable(message) with scala.util.control.ControlThrowable + + // Rewriting of anonymous function classes --------------------------------- + + private val translatedAnonFunctions = + mutable.Map.empty[Symbol, + (/*ctor args:*/ List[js.Tree] => /*instance:*/ js.Tree, ClassInfoBuilder)] + private val instantiatedAnonFunctions = + mutable.Set.empty[Symbol] + private val undefinedDefaultParams = + mutable.Set.empty[Symbol] + + // Top-level apply --------------------------------------------------------- + + override def run() { + scalaPrimitives.init() + jsPrimitives.init() + super.run() + } + + /** Generates the Scala.js IR for a compilation unit + * This method iterates over all the class and interface definitions + * found in the compilation unit and emits their IR (.sjsir). + * + * Some classes are never actually emitted: + * - Classes representing primitive types + * - The scala.Array class + * - Implementation classes for raw JS traits + * + * Some classes representing anonymous functions are not actually emitted. + * Instead, a temporary representation of their `apply` method is built + * and recorded, so that it can be inlined as a JavaScript anonymous + * function in the method that instantiates it. + * + * Other ClassDefs are emitted according to their nature: + * * Raw JS type (<: js.Any) -> `genRawJSClassData()` + * * Interface -> `genInterface()` + * * Implementation class -> `genImplClass()` + * * Normal class -> `genClass()` + */ + override def apply(cunit: CompilationUnit) { + try { + val generatedClasses = ListBuffer.empty[(Symbol, js.ClassDef, ClassInfoBuilder)] + + def collectClassDefs(tree: Tree): List[ClassDef] = { + tree match { + case EmptyTree => Nil + case PackageDef(_, stats) => stats flatMap collectClassDefs + case cd: ClassDef => cd :: Nil + } + } + val allClassDefs = collectClassDefs(cunit.body) + + /* First gen and record lambdas for js.FunctionN and js.ThisFunctionN. + * Since they are SAMs, there cannot be dependencies within this set, + * and hence we are sure we can record them before they are used, + * which is critical for these. + */ + val nonRawJSFunctionDefs = allClassDefs filterNot { cd => + if (isRawJSFunctionDef(cd.symbol)) { + genAndRecordRawJSFunctionClass(cd) + true + } else { + false + } + } + + /* Then try to gen and record lambdas for scala.FunctionN. + * These may fail, and sometimes because of dependencies. Since there + * appears to be more forward dependencies than backward dependencies + * (at least for non-nested lambdas, which we cannot translate anyway), + * we process class defs in reverse order here. + */ + val fullClassDefs = (nonRawJSFunctionDefs.reverse filterNot { cd => + cd.symbol.isAnonymousFunction && tryGenAndRecordAnonFunctionClass(cd) + }).reverse + + /* Finally, we emit true code for the remaining class defs. */ + for (cd <- fullClassDefs) { + val sym = cd.symbol + implicit val pos = sym.pos + + /* Do not actually emit code for primitive types nor scala.Array. */ + val isPrimitive = + isPrimitiveValueClass(sym) || (sym == ArrayClass) + + /* Similarly, do not emit code for impl classes of raw JS traits. */ + val isRawJSImplClass = + sym.isImplClass && isRawJSType( + sym.owner.info.decl(sym.name.dropRight(nme.IMPL_CLASS_SUFFIX.length)).tpe) + + if (!isPrimitive && !isRawJSImplClass) { + withScopedVars( + currentClassInfoBuilder := new ClassInfoBuilder(sym.asClass), + currentClassSym := sym + ) { + val tree = if (isRawJSType(sym.tpe)) { + assert(!isRawJSFunctionDef(sym), + s"Raw JS function def should have been recorded: $cd") + genRawJSClassData(cd) + } else if (sym.isInterface) { + genInterface(cd) + } else if (sym.isImplClass) { + genImplClass(cd) + } else { + genClass(cd) + } + generatedClasses += ((sym, tree, currentClassInfoBuilder.get)) + } + } + } + + val clDefs = generatedClasses.map(_._2).toList + generatedJSAST(clDefs) + + for ((sym, tree, infoBuilder) <- generatedClasses) { + genIRFile(cunit, sym, tree, infoBuilder.result()) + } + } finally { + translatedAnonFunctions.clear() + instantiatedAnonFunctions.clear() + undefinedDefaultParams.clear() + pos2irPosCache.clear() + } + } + + // Generate a class -------------------------------------------------------- + + /** Gen the IR ClassDef for a class definition (maybe a module class). + */ + def genClass(cd: ClassDef): js.ClassDef = { + val ClassDef(mods, name, _, impl) = cd + val sym = cd.symbol + implicit val pos = sym.pos + + assert(!sym.isInterface && !sym.isImplClass, + "genClass() must be called only for normal classes: "+sym) + assert(sym.superClass != NoSymbol, sym) + + val classIdent = encodeClassFullNameIdent(sym) + val isHijacked = isHijackedBoxedClass(sym) + + // Optimizer hints + + def isStdLibClassWithAdHocInlineAnnot(sym: Symbol): Boolean = { + val fullName = sym.fullName + (fullName.startsWith("scala.Tuple") && !fullName.endsWith("$")) || + (fullName.startsWith("scala.collection.mutable.ArrayOps$of")) + } + + if (sym.hasAnnotation(InlineAnnotationClass) || + (sym.isAnonymousFunction && !sym.isSubClass(PartialFunctionClass)) || + isStdLibClassWithAdHocInlineAnnot(sym)) + currentClassInfoBuilder.optimizerHints = + currentClassInfoBuilder.optimizerHints.copy(hasInlineAnnot = true) + + // Generate members (constructor + methods) + + val generatedMembers = new ListBuffer[js.Tree] + val exportedSymbols = new ListBuffer[Symbol] + + if (!isHijacked) + generatedMembers ++= genClassFields(cd) + + def gen(tree: Tree): Unit = { + tree match { + case EmptyTree => () + case Template(_, _, body) => body foreach gen + + case ValDef(mods, name, tpt, rhs) => + () // fields are added via genClassFields() + + case dd: DefDef => + val sym = dd.symbol + + val isExport = jsInterop.isExport(sym) + val isNamedExport = isExport && sym.annotations.exists( + _.symbol == JSExportNamedAnnotation) + + if (isNamedExport) + generatedMembers += genNamedExporterDef(dd) + else + generatedMembers ++= genMethod(dd) + + if (isExport) { + // We add symbols that we have to export here. This way we also + // get inherited stuff that is implemented in this class. + exportedSymbols += sym + } + + case _ => abort("Illegal tree in gen of genClass(): " + tree) + } + } + + gen(impl) + + // Create method info builder for exported stuff + val exports = withScopedVars( + currentMethodInfoBuilder := currentClassInfoBuilder.addMethod( + dceExportName + classIdent.name, isExported = true) + ) { + // Generate the exported members + val memberExports = genMemberExports(sym, exportedSymbols.toList) + + // Generate exported constructors or accessors + val exportedConstructorsOrAccessors = + if (isStaticModule(sym)) genModuleAccessorExports(sym) + else genConstructorExports(sym) + if (exportedConstructorsOrAccessors.nonEmpty) + currentClassInfoBuilder.isExported = true + + memberExports ++ exportedConstructorsOrAccessors + } + + // Generate the reflective call proxies (where required) + val reflProxies = + if (isHijacked) Nil + else genReflCallProxies(sym) + + // Hashed definitions of the class + val hashedDefs = + Hashers.hashDefs(generatedMembers.toList ++ exports ++ reflProxies) + + // The complete class definition + val kind = + if (sym.isModuleClass) ClassKind.ModuleClass + else if (isHijacked) ClassKind.HijackedClass + else ClassKind.Class + + val classDefinition = js.ClassDef( + classIdent, + kind, + Some(encodeClassFullNameIdent(sym.superClass)), + sym.ancestors.map(encodeClassFullNameIdent), + hashedDefs) + + classDefinition + } + + // Generate the class data of a raw JS class ------------------------------- + + /** Gen the IR ClassDef for a raw JS class or trait. + */ + def genRawJSClassData(cd: ClassDef): js.ClassDef = { + val sym = cd.symbol + implicit val pos = sym.pos + + // Check that RawJS type is not exported + for (exp <- jsInterop.exportsOf(sym)) + reporter.error(exp.pos, "You may not export a class extending js.Any") + + val classIdent = encodeClassFullNameIdent(sym) + js.ClassDef(classIdent, ClassKind.RawJSType, None, Nil, Nil) + } + + // Generate an interface --------------------------------------------------- + + /** Gen the IR ClassDef for an interface definition. + */ + def genInterface(cd: ClassDef): js.ClassDef = { + val sym = cd.symbol + implicit val pos = sym.pos + + val classIdent = encodeClassFullNameIdent(sym) + + // fill in class info builder + def gen(tree: Tree) { + tree match { + case EmptyTree => () + case Template(_, _, body) => body foreach gen + case dd: DefDef => + currentClassInfoBuilder.addMethod( + encodeMethodName(dd.symbol), isAbstract = true) + case _ => abort("Illegal tree in gen of genInterface(): " + tree) + } + } + gen(cd.impl) + + // Check that interface/trait is not exported + for (exp <- jsInterop.exportsOf(sym)) + reporter.error(exp.pos, "You may not export a trait") + + js.ClassDef(classIdent, ClassKind.Interface, None, + sym.ancestors.map(encodeClassFullNameIdent), Nil) + } + + // Generate an implementation class of a trait ----------------------------- + + /** Gen the IR ClassDef for an implementation class (of a trait). + */ + def genImplClass(cd: ClassDef): js.ClassDef = { + val ClassDef(mods, name, _, impl) = cd + val sym = cd.symbol + implicit val pos = sym.pos + + def gen(tree: Tree): List[js.MethodDef] = { + tree match { + case EmptyTree => Nil + case Template(_, _, body) => body.flatMap(gen) + + case dd: DefDef => + val m = genMethod(dd) + m.toList + + case _ => abort("Illegal tree in gen of genImplClass(): " + tree) + } + } + val generatedMethods = gen(impl) + + js.ClassDef(encodeClassFullNameIdent(sym), ClassKind.TraitImpl, + None, Nil, generatedMethods) + } + + // Generate the fields of a class ------------------------------------------ + + /** Gen definitions for the fields of a class. + * The fields are initialized with the zero of their types. + */ + def genClassFields(cd: ClassDef): List[js.VarDef] = withScopedVars( + currentMethodInfoBuilder := + currentClassInfoBuilder.addMethod("__init__") + ) { + // Non-method term members are fields, except for module members. + (for { + f <- currentClassSym.info.decls + if !f.isMethod && f.isTerm && !f.isModule + } yield { + implicit val pos = f.pos + js.VarDef(encodeFieldSym(f), toIRType(f.tpe), + mutable = f.isMutable, genZeroOf(f.tpe)) + }).toList + } + + // Generate a method ------------------------------------------------------- + + def genMethod(dd: DefDef): Option[js.MethodDef] = withNewLocalNameScope { + genMethodWithInfoBuilder(dd).map(_._1) + } + + /** Gen JS code for a method definition in a class or in an impl class. + * On the JS side, method names are mangled to encode the full signature + * of the Scala method, as described in `JSEncoding`, to support + * overloading. + * + * Some methods are not emitted at all: + * * Primitives, since they are never actually called + * * Abstract methods + * * Constructors of hijacked classes + * * Trivial constructors, which only call their super constructor, with + * the same signature, and the same arguments. The JVM needs these + * constructors, but not JavaScript. Since there are lots of them, we + * take the trouble of recognizing and removing them. + * + * Constructors are emitted by generating their body as a statement, then + * return `this`. + * + * Other (normal) methods are emitted with `genMethodBody()`. + */ + def genMethodWithInfoBuilder( + dd: DefDef): Option[(js.MethodDef, MethodInfoBuilder)] = { + + implicit val pos = dd.pos + val DefDef(mods, name, _, vparamss, _, rhs) = dd + val sym = dd.symbol + + isModuleInitialized = false + + val result = withScopedVars( + currentMethodSym := sym, + methodTailJumpThisSym := NoSymbol, + fakeTailJumpParamRepl := (NoSymbol, NoSymbol), + enclosingLabelDefParams := Map.empty + ) { + assert(vparamss.isEmpty || vparamss.tail.isEmpty, + "Malformed parameter list: " + vparamss) + val params = if (vparamss.isEmpty) Nil else vparamss.head map (_.symbol) + + assert(!sym.owner.isInterface, + "genMethod() must not be called for methods in interfaces: "+sym) + + val methodIdent = encodeMethodSym(sym) + + def createInfoBuilder(isAbstract: Boolean = false) = { + currentClassInfoBuilder.addMethod(methodIdent.name, + isAbstract = isAbstract, + isExported = sym.isClassConstructor && + jsInterop.exportsOf(sym).nonEmpty) + } + + if (scalaPrimitives.isPrimitive(sym)) { + None + } else if (sym.isDeferred) { + createInfoBuilder(isAbstract = true) + None + } else if (isRawJSCtorDefaultParam(sym)) { + None + } else if (isTrivialConstructor(sym, params, rhs)) { + createInfoBuilder().callsMethod(sym.owner.superClass, methodIdent) + None + } else if (sym.isClassConstructor && isHijackedBoxedClass(sym.owner)) { + None + } else { + withScopedVars( + currentMethodInfoBuilder := createInfoBuilder(), + mutableLocalVars := mutable.Set.empty, + mutatedLocalVars := mutable.Set.empty + ) { + def shouldMarkInline = { + sym.hasAnnotation(InlineAnnotationClass) || + sym.name.startsWith(nme.ANON_FUN_NAME) + } + currentMethodInfoBuilder.optimizerHints = + currentMethodInfoBuilder.optimizerHints.copy( + isAccessor = sym.isAccessor, + hasInlineAnnot = shouldMarkInline) + + val methodDef = { + if (sym.isClassConstructor) { + val jsParams = for (param <- params) yield { + implicit val pos = param.pos + js.ParamDef(encodeLocalSym(param), toIRType(param.tpe), + mutable = false) + } + js.MethodDef(methodIdent, jsParams, currentClassType, + js.Block(genStat(rhs), genThis()))(None) + } else { + val resultIRType = toIRType(sym.tpe.resultType) + genMethodDef(methodIdent, params, resultIRType, rhs) + } + } + + val methodDefWithoutUselessVars = { + val unmutatedMutableLocalVars = + (mutableLocalVars -- mutatedLocalVars).toList + val mutatedImmutableLocalVals = + (mutatedLocalVars -- mutableLocalVars).toList + if (unmutatedMutableLocalVars.isEmpty && + mutatedImmutableLocalVals.isEmpty) { + // OK, we're good (common case) + methodDef + } else { + val patches = ( + unmutatedMutableLocalVars.map(encodeLocalSym(_).name -> false) ::: + mutatedImmutableLocalVals.map(encodeLocalSym(_).name -> true) + ).toMap + patchMutableFlagOfLocals(methodDef, patches) + } + } + + Some((methodDefWithoutUselessVars, currentMethodInfoBuilder.get)) + } + } + } + + result + } + + private def isTrivialConstructor(sym: Symbol, params: List[Symbol], + rhs: Tree): Boolean = { + if (!sym.isClassConstructor) { + false + } else { + rhs match { + // Shape of a constructor that only calls super + case Block(List(Apply(fun @ Select(_:Super, _), args)), Literal(_)) => + val callee = fun.symbol + implicit val dummyPos = NoPosition + + // Does the callee have the same signature as sym + if (encodeMethodSym(sym) == encodeMethodSym(callee)) { + // Test whether args are trivial forwarders + assert(args.size == params.size, "Argument count mismatch") + params.zip(args) forall { case (param, arg) => + arg.symbol == param + } + } else { + false + } + + case _ => false + } + } + } + + /** Patches the mutable flags of selected locals in a [[js.MethodDef]]. + * + * @param patches Map from local name to new value of the mutable flags. + * For locals not in the map, the flag is untouched. + */ + private def patchMutableFlagOfLocals(methodDef: js.MethodDef, + patches: Map[String, Boolean]): js.MethodDef = { + + def newMutable(name: String, oldMutable: Boolean): Boolean = + patches.getOrElse(name, oldMutable) + + val js.MethodDef(methodName, params, resultType, body) = methodDef + val newParams = for { + p @ js.ParamDef(name, ptpe, mutable) <- params + } yield { + js.ParamDef(name, ptpe, newMutable(name.name, mutable))(p.pos) + } + val transformer = new ir.Transformers.Transformer { + override def transform(tree: js.Tree, isStat: Boolean): js.Tree = tree match { + case js.VarDef(name, vtpe, mutable, rhs) => + assert(isStat) + super.transform(js.VarDef( + name, vtpe, newMutable(name.name, mutable), rhs)(tree.pos), isStat) + case js.VarRef(name, mutable) => + js.VarRef(name, newMutable(name.name, mutable))(tree.tpe)(tree.pos) + case js.Closure(captureParams, params, body, captureValues) => + js.Closure(captureParams, params, body, + captureValues.map(transformExpr))(tree.pos) + case _ => + super.transform(tree, isStat) + } + } + val newBody = + transformer.transform(body, isStat = resultType == jstpe.NoType) + js.MethodDef(methodName, newParams, resultType, newBody)(None)(methodDef.pos) + } + + /** + * Generates reflective proxy methods for methods in sym + * + * Reflective calls don't depend on the return type, so it's hard to + * generate calls without using runtime reflection to list the methods. We + * generate a method to be used for reflective calls (without return + * type in the name). + * + * There are cases where non-trivial overloads cause ambiguous situations: + * + * {{{ + * object A { + * def foo(x: Option[Int]): String + * def foo(x: Option[String]): Int + * } + * }}} + * + * This is completely legal code, but due to the same erased parameter + * type of the {{{foo}}} overloads, they cannot be disambiguated in a + * reflective call, as the exact return type is unknown at the call site. + * + * Cases like the upper currently fail on the JVM backend at runtime. The + * Scala.js backend uses the following rules for selection (which will + * also cause runtime failures): + * + * - If a proxy with the same signature (method name and parameters) + * exists in the superclass, no proxy is generated (proxy is inherited) + * - If no proxy exists in the superclass, a proxy is generated for the + * first method with matching signatures. + */ + def genReflCallProxies(sym: Symbol): List[js.MethodDef] = { + import scala.reflect.internal.Flags + + // Flags of members we do not want to consider for reflective call proxys + val excludedFlags = ( + Flags.BRIDGE | + Flags.PRIVATE | + Flags.MACRO + ) + + /** Check if two method symbols conform in name and parameter types */ + def weakMatch(s1: Symbol)(s2: Symbol) = { + val p1 = s1.tpe.params + val p2 = s2.tpe.params + s1 == s2 || // Shortcut + s1.name == s2.name && + p1.size == p2.size && + (p1 zip p2).forall { case (s1,s2) => + s1.tpe =:= s2.tpe + } + } + + /** Check if the symbol's owner's superclass has a matching member (and + * therefore an existing proxy). + */ + def superHasProxy(s: Symbol) = { + val alts = sym.superClass.tpe.findMember( + name = s.name, + excludedFlags = excludedFlags, + requiredFlags = Flags.METHOD, + stableOnly = false).alternatives + alts.exists(weakMatch(s) _) + } + + // Query candidate methods + val methods = sym.tpe.findMembers( + excludedFlags = excludedFlags, + requiredFlags = Flags.METHOD) + + val candidates = methods filterNot { s => + s.isConstructor || + superHasProxy(s) || + jsInterop.isExport(s) + } + + val proxies = candidates filter { + c => candidates.find(weakMatch(c) _).get == c + } + + proxies.map(genReflCallProxy _).toList + } + + /** actually generates reflective call proxy for the given method symbol */ + private def genReflCallProxy(sym: Symbol): js.MethodDef = { + implicit val pos = sym.pos + + val proxyIdent = encodeMethodSym(sym, reflProxy = true) + + withNewLocalNameScope { + withScopedVars( + currentMethodInfoBuilder := + currentClassInfoBuilder.addMethod(proxyIdent.name) + ) { + val jsParams = for (param <- sym.tpe.params) yield { + implicit val pos = param.pos + js.ParamDef(encodeLocalSym(param), toIRType(param.tpe), + mutable = false) + } + + val call = genApplyMethod(genThis(), sym.owner, sym, + jsParams.map(_.ref)) + val body = ensureBoxed(call, + enteringPhase(currentRun.posterasurePhase)(sym.tpe.resultType)) + + js.MethodDef(proxyIdent, jsParams, jstpe.AnyType, body)(None) + } + } + } + + /** Generates the MethodDef of a (non-constructor) method + * + * Most normal methods are emitted straightforwardly. If the result + * type is Unit, then the body is emitted as a statement. Otherwise, it is + * emitted as an expression. + * + * The additional complexity of this method handles the transformation of + * a peculiarity of recursive tail calls: the local ValDef that replaces + * `this`. + */ + def genMethodDef(methodIdent: js.Ident, paramsSyms: List[Symbol], + resultIRType: jstpe.Type, tree: Tree): js.MethodDef = { + implicit val pos = tree.pos + + val jsParams = for (param <- paramsSyms) yield { + implicit val pos = param.pos + js.ParamDef(encodeLocalSym(param), toIRType(param.tpe), mutable = false) + } + + val bodyIsStat = resultIRType == jstpe.NoType + + val body = tree match { + case Block( + (thisDef @ ValDef(_, nme.THIS, _, initialThis)) :: otherStats, + rhs) => + // This method has tail jumps + withScopedVars( + (initialThis match { + case This(_) => + Seq(methodTailJumpThisSym := thisDef.symbol, + fakeTailJumpParamRepl := (NoSymbol, NoSymbol)) + case Ident(_) => + Seq(methodTailJumpThisSym := NoSymbol, + fakeTailJumpParamRepl := (thisDef.symbol, initialThis.symbol)) + }): _* + ) { + val innerBody = js.Block(otherStats.map(genStat) :+ ( + if (bodyIsStat) genStat(rhs) + else genExpr(rhs))) + + if (methodTailJumpThisSym.get == NoSymbol) { + innerBody + } else { + if (methodTailJumpThisSym.isMutable) + mutableLocalVars += methodTailJumpThisSym + js.Block( + js.VarDef(encodeLocalSym(methodTailJumpThisSym), + currentClassType, methodTailJumpThisSym.isMutable, + js.This()(currentClassType)), + innerBody) + } + } + + case _ => + if (bodyIsStat) genStat(tree) + else genExpr(tree) + } + + js.MethodDef(methodIdent, jsParams, resultIRType, body)(None) + } + + /** Gen JS code for a tree in statement position (in the IR). + */ + def genStat(tree: Tree): js.Tree = { + exprToStat(genStatOrExpr(tree, isStat = true)) + } + + /** Turn a JavaScript expression of type Unit into a statement */ + def exprToStat(tree: js.Tree): js.Tree = { + /* Any JavaScript expression is also a statement, but at least we get rid + * of some pure expressions that come from our own codegen. + */ + implicit val pos = tree.pos + tree match { + case js.Block(stats :+ expr) => js.Block(stats :+ exprToStat(expr)) + case _:js.Literal | js.This() => js.Skip() + case _ => tree + } + } + + /** Gen JS code for a tree in expression position (in the IR). + */ + def genExpr(tree: Tree): js.Tree = { + val result = genStatOrExpr(tree, isStat = false) + assert(result.tpe != jstpe.NoType, + s"genExpr($tree) returned a tree with type NoType at pos ${tree.pos}") + result + } + + /** Gen JS code for a tree in statement or expression position (in the IR). + * + * This is the main transformation method. Each node of the Scala AST + * is transformed into an equivalent portion of the JS AST. + */ + def genStatOrExpr(tree: Tree, isStat: Boolean): js.Tree = { + implicit val pos = tree.pos + + tree match { + /** LabelDefs (for while and do..while loops) */ + case lblDf: LabelDef => + genLabelDef(lblDf) + + /** Local val or var declaration */ + case ValDef(_, name, _, rhs) => + /* Must have been eliminated by the tail call transform performed + * by genMethodBody(). */ + assert(name != nme.THIS, + s"ValDef(_, nme.THIS, _, _) found at ${tree.pos}") + + val sym = tree.symbol + val rhsTree = + if (rhs == EmptyTree) genZeroOf(sym.tpe) + else genExpr(rhs) + + rhsTree match { + case js.UndefinedParam() => + // This is an intermediate assignment for default params on a + // js.Any. Add the symbol to the corresponding set to inform + // the Ident resolver how to replace it and don't emit the symbol + undefinedDefaultParams += sym + js.Skip() + case _ => + if (sym.isMutable) + mutableLocalVars += sym + js.VarDef(encodeLocalSym(sym), + toIRType(sym.tpe), sym.isMutable, rhsTree) + } + + case If(cond, thenp, elsep) => + js.If(genExpr(cond), genStatOrExpr(thenp, isStat), + genStatOrExpr(elsep, isStat))(toIRType(tree.tpe)) + + case Return(expr) => + js.Return(toIRType(expr.tpe) match { + case jstpe.NoType => js.Block(genStat(expr), js.Undefined()) + case _ => genExpr(expr) + }) + + case t: Try => + genTry(t, isStat) + + case Throw(expr) => + val ex = genExpr(expr) + js.Throw { + if (isMaybeJavaScriptException(expr.tpe)) { + genApplyMethod( + genLoadModule(RuntimePackageModule), + RuntimePackageModule.moduleClass, + Runtime_unwrapJavaScriptException, + List(ex)) + } else { + ex + } + } + + case app: Apply => + genApply(app, isStat) + + case app: ApplyDynamic => + genApplyDynamic(app) + + case This(qual) => + if (tree.symbol == currentClassSym.get) { + genThis() + } else { + assert(tree.symbol.isModuleClass, + "Trying to access the this of another class: " + + "tree.symbol = " + tree.symbol + + ", class symbol = " + currentClassSym.get + + " compilation unit:" + currentUnit) + genLoadModule(tree.symbol) + } + + case Select(qualifier, selector) => + val sym = tree.symbol + if (sym.isModule) { + assert(!sym.isPackageClass, "Cannot use package as value: " + tree) + genLoadModule(sym) + } else if (sym.isStaticMember) { + genStaticMember(sym) + } else if (paramAccessorLocals contains sym) { + paramAccessorLocals(sym).ref + } else { + js.Select(genExpr(qualifier), encodeFieldSym(sym), + mutable = sym.isMutable)(toIRType(sym.tpe)) + } + + case Ident(name) => + val sym = tree.symbol + if (!sym.hasPackageFlag) { + if (sym.isModule) { + assert(!sym.isPackageClass, "Cannot use package as value: " + tree) + genLoadModule(sym) + } else if (undefinedDefaultParams contains sym) { + // This is a default parameter whose assignment was moved to + // a local variable. Put a literal undefined param again + js.UndefinedParam()(toIRType(sym.tpe)) + } else { + js.VarRef(encodeLocalSym(sym), sym.isMutable)(toIRType(sym.tpe)) + } + } else { + sys.error("Cannot use package as value: " + tree) + } + + case Literal(value) => + value.tag match { + case UnitTag => + js.Skip() + case BooleanTag => + js.BooleanLiteral(value.booleanValue) + case ByteTag | ShortTag | CharTag | IntTag => + js.IntLiteral(value.intValue) + case LongTag => + js.LongLiteral(value.longValue) + case FloatTag => + js.FloatLiteral(value.floatValue) + case DoubleTag => + js.DoubleLiteral(value.doubleValue) + case StringTag => + js.StringLiteral(value.stringValue) + case NullTag => + js.Null() + case ClazzTag => + genClassConstant(value.typeValue) + case EnumTag => + genStaticMember(value.symbolValue) + } + + case tree: Block => + genBlock(tree, isStat) + + case Typed(Super(_, _), _) => + genThis() + + case Typed(expr, _) => + genExpr(expr) + + case Assign(lhs, rhs) => + val sym = lhs.symbol + if (sym.isStaticMember) + abort(s"Assignment to static member ${sym.fullName} not supported") + val genLhs = lhs match { + case Select(qualifier, _) => + js.Select(genExpr(qualifier), encodeFieldSym(sym), + mutable = sym.isMutable)(toIRType(sym.tpe)) + case _ => + mutatedLocalVars += sym + js.VarRef(encodeLocalSym(sym), sym.isMutable)(toIRType(sym.tpe)) + } + js.Assign(genLhs, genExpr(rhs)) + + /** Array constructor */ + case av: ArrayValue => + genArrayValue(av) + + /** A Match reaching the backend is supposed to be optimized as a switch */ + case mtch: Match => + genMatch(mtch, isStat) + + /** Anonymous function (only with -Ydelambdafy:method) */ + case fun: Function => + genAnonFunction(fun) + + case EmptyTree => + js.Skip() + + case _ => + abort("Unexpected tree in genExpr: " + + tree + "/" + tree.getClass + " at: " + tree.pos) + } + } // end of GenJSCode.genExpr() + + /** Gen JS this of the current class. + * Normally encoded straightforwardly as a JS this. + * But must be replaced by the tail-jump-this local variable if there + * is one. + */ + private def genThis()(implicit pos: Position): js.Tree = { + if (methodTailJumpThisSym.get != NoSymbol) { + js.VarRef( + encodeLocalSym(methodTailJumpThisSym), + methodTailJumpThisSym.isMutable)(currentClassType) + } else { + if (tryingToGenMethodAsJSFunction) + throw new CancelGenMethodAsJSFunction( + "Trying to generate `this` inside the body") + js.This()(currentClassType) + } + } + + /** Gen JS code for LabelDef + * The only LabelDefs that can reach here are the desugaring of + * while and do..while loops. All other LabelDefs (for tail calls or + * matches) are caught upstream and transformed in ad hoc ways. + * + * So here we recognize all the possible forms of trees that can result + * of while or do..while loops, and we reconstruct the loop for emission + * to JS. + */ + def genLabelDef(tree: LabelDef): js.Tree = { + implicit val pos = tree.pos + val sym = tree.symbol + + tree match { + // while (cond) { body } + case LabelDef(lname, Nil, + If(cond, + Block(bodyStats, Apply(target @ Ident(lname2), Nil)), + Literal(_))) if (target.symbol == sym) => + js.While(genExpr(cond), js.Block(bodyStats map genStat)) + + // while (cond) { body }; result + case LabelDef(lname, Nil, + Block(List( + If(cond, + Block(bodyStats, Apply(target @ Ident(lname2), Nil)), + Literal(_))), + result)) if (target.symbol == sym) => + js.Block( + js.While(genExpr(cond), js.Block(bodyStats map genStat)), + genExpr(result)) + + // while (true) { body } + case LabelDef(lname, Nil, + Block(bodyStats, + Apply(target @ Ident(lname2), Nil))) if (target.symbol == sym) => + js.While(js.BooleanLiteral(true), js.Block(bodyStats map genStat)) + + // while (false) { body } + case LabelDef(lname, Nil, Literal(Constant(()))) => + js.Skip() + + // do { body } while (cond) + case LabelDef(lname, Nil, + Block(bodyStats, + If(cond, + Apply(target @ Ident(lname2), Nil), + Literal(_)))) if (target.symbol == sym) => + js.DoWhile(js.Block(bodyStats map genStat), genExpr(cond)) + + // do { body } while (cond); result + case LabelDef(lname, Nil, + Block( + bodyStats :+ + If(cond, + Apply(target @ Ident(lname2), Nil), + Literal(_)), + result)) if (target.symbol == sym) => + js.Block( + js.DoWhile(js.Block(bodyStats map genStat), genExpr(cond)), + genExpr(result)) + + /* Arbitrary other label - we can jump to it from inside it. + * This is typically for the label-defs implementing tail-calls. + * It can also handle other weird LabelDefs generated by some compiler + * plugins (see for example #1148). + */ + case LabelDef(labelName, labelParams, rhs) => + val labelParamSyms = labelParams.map(_.symbol) map { + s => if (s == fakeTailJumpParamRepl._1) fakeTailJumpParamRepl._2 else s + } + + withScopedVars( + enclosingLabelDefParams := + enclosingLabelDefParams.get + (tree.symbol -> labelParamSyms) + ) { + val bodyType = toIRType(tree.tpe) + val labelIdent = encodeLabelSym(tree.symbol) + val blockLabelIdent = freshLocalIdent() + + js.Labeled(blockLabelIdent, bodyType, { + js.While(js.BooleanLiteral(true), { + if (bodyType == jstpe.NoType) + js.Block(genStat(rhs), js.Return(js.Undefined(), Some(blockLabelIdent))) + else + js.Return(genExpr(rhs), Some(blockLabelIdent)) + }, Some(labelIdent)) + }) + } + } + } + + /** Gen JS code for a try..catch or try..finally block + * + * try..finally blocks are compiled straightforwardly to try..finally + * blocks of JS. + * + * try..catch blocks are a bit more subtle, as JS does not have + * type-based selection of exceptions to catch. We thus encode explicitly + * the type tests, like in: + * + * try { ... } + * catch (e) { + * if (e.isInstanceOf[IOException]) { ... } + * else if (e.isInstanceOf[Exception]) { ... } + * else { + * throw e; // default, re-throw + * } + * } + */ + def genTry(tree: Try, isStat: Boolean): js.Tree = { + implicit val pos = tree.pos + val Try(block, catches, finalizer) = tree + + val blockAST = genStatOrExpr(block, isStat) + + val exceptIdent = freshLocalIdent("e") + val origExceptVar = js.VarRef(exceptIdent, mutable = false)(jstpe.AnyType) + + val resultType = toIRType(tree.tpe) + + val handlerAST = { + if (catches.isEmpty) { + js.EmptyTree + } else { + val mightCatchJavaScriptException = catches.exists { caseDef => + caseDef.pat match { + case Typed(Ident(nme.WILDCARD), tpt) => + isMaybeJavaScriptException(tpt.tpe) + case Ident(nme.WILDCARD) => + true + case pat @ Bind(_, _) => + isMaybeJavaScriptException(pat.symbol.tpe) + } + } + + val (exceptValDef, exceptVar) = if (mightCatchJavaScriptException) { + val valDef = js.VarDef(freshLocalIdent("e"), + encodeClassType(ThrowableClass), mutable = false, { + genApplyMethod( + genLoadModule(RuntimePackageModule), + RuntimePackageModule.moduleClass, + Runtime_wrapJavaScriptException, + List(origExceptVar)) + }) + (valDef, valDef.ref) + } else { + (js.Skip(), origExceptVar) + } + + val elseHandler: js.Tree = js.Throw(origExceptVar) + + val handler0 = catches.foldRight(elseHandler) { (caseDef, elsep) => + implicit val pos = caseDef.pos + val CaseDef(pat, _, body) = caseDef + + // Extract exception type and variable + val (tpe, boundVar) = (pat match { + case Typed(Ident(nme.WILDCARD), tpt) => + (tpt.tpe, None) + case Ident(nme.WILDCARD) => + (ThrowableClass.tpe, None) + case Bind(_, _) => + (pat.symbol.tpe, Some(encodeLocalSym(pat.symbol))) + }) + + // Generate the body that must be executed if the exception matches + val bodyWithBoundVar = (boundVar match { + case None => + genStatOrExpr(body, isStat) + case Some(bv) => + val castException = genAsInstanceOf(exceptVar, tpe) + js.Block( + js.VarDef(bv, toIRType(tpe), mutable = false, castException), + genStatOrExpr(body, isStat)) + }) + + // Generate the test + if (tpe == ThrowableClass.tpe) { + bodyWithBoundVar + } else { + val cond = genIsInstanceOf(exceptVar, tpe) + js.If(cond, bodyWithBoundVar, elsep)(resultType) + } + } + + js.Block( + exceptValDef, + handler0) + } + } + + val finalizerAST = genStat(finalizer) match { + case js.Skip() => js.EmptyTree + case ast => ast + } + + if (handlerAST == js.EmptyTree && finalizerAST == js.EmptyTree) blockAST + else js.Try(blockAST, exceptIdent, handlerAST, finalizerAST)(resultType) + } + + /** Gen JS code for an Apply node (method call) + * + * There's a whole bunch of varieties of Apply nodes: regular method + * calls, super calls, constructor calls, isInstanceOf/asInstanceOf, + * primitives, JS calls, etc. They are further dispatched in here. + */ + def genApply(tree: Apply, isStat: Boolean): js.Tree = { + implicit val pos = tree.pos + val Apply(fun, args) = tree + + fun match { + case TypeApply(_, _) => + genApplyTypeApply(tree) + + case Select(Super(_, _), _) => + genSuperCall(tree) + + case Select(New(_), nme.CONSTRUCTOR) => + genApplyNew(tree) + + case _ => + val sym = fun.symbol + + if (sym.isLabel) { + genLabelApply(tree) + } else if (scalaPrimitives.isPrimitive(sym)) { + genPrimitiveOp(tree, isStat) + } else if (currentRun.runDefinitions.isBox(sym)) { + // Box a primitive value (cannot be Unit) + val arg = args.head + makePrimitiveBox(genExpr(arg), arg.tpe) + } else if (currentRun.runDefinitions.isUnbox(sym)) { + // Unbox a primitive value (cannot be Unit) + val arg = args.head + makePrimitiveUnbox(genExpr(arg), tree.tpe) + } else { + genNormalApply(tree, isStat) + } + } + } + + /** Gen an Apply with a TypeApply method. + * Only isInstanceOf and asInstanceOf keep their type argument until the + * backend. + */ + private def genApplyTypeApply(tree: Apply): js.Tree = { + implicit val pos = tree.pos + val Apply(TypeApply(fun @ Select(obj, _), targs), _) = tree + val sym = fun.symbol + + val cast = sym match { + case Object_isInstanceOf => false + case Object_asInstanceOf => true + case _ => + abort("Unexpected type application " + fun + + "[sym: " + sym.fullName + "]" + " in: " + tree) + } + + val to = targs.head.tpe + val l = toTypeKind(obj.tpe) + val r = toTypeKind(to) + val source = genExpr(obj) + + if (l.isValueType && r.isValueType) { + if (cast) + genConversion(l, r, source) + else + js.BooleanLiteral(l == r) + } else if (l.isValueType) { + val result = if (cast) { + val ctor = ClassCastExceptionClass.info.member( + nme.CONSTRUCTOR).suchThat(_.tpe.params.isEmpty) + js.Throw(genNew(ClassCastExceptionClass, ctor, Nil)) + } else { + js.BooleanLiteral(false) + } + js.Block(source, result) // eval and discard source + } else if (r.isValueType) { + assert(!cast, s"Unexpected asInstanceOf from ref type to value type") + genIsInstanceOf(source, boxedClass(to.typeSymbol).tpe) + } else { + if (cast) + genAsInstanceOf(source, to) + else + genIsInstanceOf(source, to) + } + } + + /** Gen JS code for a super call, of the form Class.super[mix].fun(args). + * + * This does not include calls defined in mixin traits, as these are + * already desugared by the 'mixin' phase. Only calls to super classes + * remain. + * Since a class has exactly one direct superclass, and calling a method + * two classes above the current one is invalid, the `mix` item is + * irrelevant. + */ + private def genSuperCall(tree: Apply): js.Tree = { + implicit val pos = tree.pos + val Apply(fun @ Select(sup @ Super(_, mix), _), args) = tree + val sym = fun.symbol + + if (sym == Object_getClass) { + // The only primitive that is also callable as super call + js.GetClass(genThis()) + } else { + val superCall = genStaticApplyMethod( + genThis()(sup.pos), sym, genActualArgs(sym, args)) + + // Initialize the module instance just after the super constructor call. + if (isStaticModule(currentClassSym) && !isModuleInitialized && + currentMethodSym.isClassConstructor) { + isModuleInitialized = true + val thisType = jstpe.ClassType(encodeClassFullName(currentClassSym)) + val initModule = js.StoreModule(thisType, js.This()(thisType)) + js.Block(superCall, initModule, js.This()(thisType)) + } else { + superCall + } + } + } + + /** Gen JS code for a constructor call (new). + * Further refined into: + * * new String(...) + * * new of a hijacked boxed class + * * new of an anonymous function class that was recorded as JS function + * * new of a raw JS class + * * new Array + * * regular new + */ + private def genApplyNew(tree: Apply): js.Tree = { + implicit val pos = tree.pos + val Apply(fun @ Select(New(tpt), nme.CONSTRUCTOR), args) = tree + val ctor = fun.symbol + val tpe = tpt.tpe + + assert(ctor.isClassConstructor, + "'new' call to non-constructor: " + ctor.name) + + if (isStringType(tpe)) { + genNewString(tree) + } else if (isHijackedBoxedClass(tpe.typeSymbol)) { + genNewHijackedBoxedClass(tpe.typeSymbol, ctor, args map genExpr) + } else if (translatedAnonFunctions contains tpe.typeSymbol) { + val (functionMaker, funInfo) = translatedAnonFunctions(tpe.typeSymbol) + currentMethodInfoBuilder.createsAnonFunction(funInfo) + functionMaker(args map genExpr) + } else if (isRawJSType(tpe)) { + genPrimitiveJSNew(tree) + } else { + toTypeKind(tpe) match { + case arr @ ARRAY(elem) => + genNewArray(arr.toIRType, args map genExpr) + case rt @ REFERENCE(cls) => + genNew(cls, ctor, genActualArgs(ctor, args)) + case generatedType => + abort(s"Non reference type cannot be instantiated: $generatedType") + } + } + } + + /** Gen jump to a label. + * Most label-applys are caught upstream (while and do..while loops, + * jumps to next case of a pattern match), but some are still handled here: + * * Jumps to enclosing label-defs, including tail-recursive calls + * * Jump to the end of a pattern match + */ + private def genLabelApply(tree: Apply): js.Tree = { + implicit val pos = tree.pos + val Apply(fun, args) = tree + val sym = fun.symbol + + if (enclosingLabelDefParams.contains(sym)) { + genEnclosingLabelApply(tree) + } else if (sym.name.toString() startsWith "matchEnd") { + /* Jump the to the end-label of a pattern match + * Such labels have exactly one argument, which is the result of + * the pattern match (of type BoxedUnit if the match is in statement + * position). We simply `return` the argument as the result of the + * labeled block surrounding the match. + */ + js.Return(genExpr(args.head), Some(encodeLabelSym(sym))) + } else { + /* No other label apply should ever happen. If it does, then we + * have missed a pattern of LabelDef/LabelApply and some new + * translation must be found for it. + */ + abort("Found unknown label apply at "+tree.pos+": "+tree) + } + } + + /** Gen a label-apply to an enclosing label def. + * + * This is typically used for tail-recursive calls. + * + * Basically this is compiled into + * continue labelDefIdent; + * but arguments need to be updated beforehand. + * + * Since the rhs for the new value of an argument can depend on the value + * of another argument (and since deciding if it is indeed the case is + * impossible in general), new values are computed in temporary variables + * first, then copied to the actual variables representing the argument. + * + * Trivial assignments (arg1 = arg1) are eliminated. + * + * If, after elimination of trivial assignments, only one assignment + * remains, then we do not use a temporary variable for this one. + */ + private def genEnclosingLabelApply(tree: Apply): js.Tree = { + implicit val pos = tree.pos + val Apply(fun, args) = tree + val sym = fun.symbol + + // Prepare quadruplets of (formalArg, irType, tempVar, actualArg) + // Do not include trivial assignments (when actualArg == formalArg) + val formalArgs = enclosingLabelDefParams(sym) + val actualArgs = args map genExpr + val quadruplets = { + for { + (formalArgSym, actualArg) <- formalArgs zip actualArgs + formalArg = encodeLocalSym(formalArgSym) + if (actualArg match { + case js.VarRef(`formalArg`, _) => false + case _ => true + }) + } yield { + mutatedLocalVars += formalArgSym + val tpe = toIRType(formalArgSym.tpe) + (js.VarRef(formalArg, formalArgSym.isMutable)(tpe), tpe, + freshLocalIdent("temp$" + formalArg.name), + actualArg) + } + } + + // The actual jump (continue labelDefIdent;) + val jump = js.Continue(Some(encodeLabelSym(sym))) + + quadruplets match { + case Nil => jump + + case (formalArg, argType, _, actualArg) :: Nil => + js.Block( + js.Assign(formalArg, actualArg), + jump) + + case _ => + val tempAssignments = + for ((_, argType, tempArg, actualArg) <- quadruplets) + yield js.VarDef(tempArg, argType, mutable = false, actualArg) + val trueAssignments = + for ((formalArg, argType, tempArg, _) <- quadruplets) + yield js.Assign( + formalArg, + js.VarRef(tempArg, mutable = false)(argType)) + js.Block(tempAssignments ++ trueAssignments :+ jump) + } + } + + /** Gen a "normal" apply (to a true method). + * + * But even these are further refined into: + * * Methods of java.lang.String, which are redirected to the + * RuntimeString trait implementation. + * * Calls to methods of raw JS types (Scala.js -> JS bridge) + * * Calls to methods in impl classes of traits. + * * Regular method call + */ + private def genNormalApply(tree: Apply, isStat: Boolean): js.Tree = { + implicit val pos = tree.pos + val Apply(fun @ Select(receiver, _), args) = tree + val sym = fun.symbol + + def isStringMethodFromObject: Boolean = sym.name match { + case nme.toString_ | nme.equals_ | nme.hashCode_ => true + case _ => false + } + + if (sym.owner == StringClass && !isStringMethodFromObject) { + genStringCall(tree) + } else if (isRawJSType(receiver.tpe) && sym.owner != ObjectClass) { + genPrimitiveJSCall(tree, isStat) + } else if (foreignIsImplClass(sym.owner)) { + genTraitImplApply(sym, args map genExpr) + } else if (isRawJSCtorDefaultParam(sym)) { + js.UndefinedParam()(toIRType(sym.tpe.resultType)) + } else if (sym.isClassConstructor) { + /* See #66: we have to emit a static call to avoid calling a + * constructor with the same signature in a subclass */ + genStaticApplyMethod(genExpr(receiver), sym, genActualArgs(sym, args)) + } else { + genApplyMethod(genExpr(receiver), receiver.tpe, sym, genActualArgs(sym, args)) + } + } + + def genStaticApplyMethod(receiver: js.Tree, method: Symbol, + arguments: List[js.Tree])(implicit pos: Position): js.Tree = { + val classIdent = encodeClassFullNameIdent(method.owner) + val methodIdent = encodeMethodSym(method) + currentMethodInfoBuilder.callsMethodStatic(classIdent, methodIdent) + js.StaticApply(receiver, jstpe.ClassType(classIdent.name), methodIdent, + arguments)(toIRType(method.tpe.resultType)) + } + + def genTraitImplApply(method: Symbol, arguments: List[js.Tree])( + implicit pos: Position): js.Tree = { + val implIdent = encodeClassFullNameIdent(method.owner) + val methodIdent = encodeMethodSym(method) + genTraitImplApply(implIdent, methodIdent, arguments, + toIRType(method.tpe.resultType)) + } + + def genTraitImplApply(implIdent: js.Ident, methodIdent: js.Ident, + arguments: List[js.Tree], resultType: jstpe.Type)( + implicit pos: Position): js.Tree = { + currentMethodInfoBuilder.callsMethod(implIdent, methodIdent) + js.TraitImplApply(jstpe.ClassType(implIdent.name), methodIdent, + arguments)(resultType) + } + + /** Gen JS code for a conversion between primitive value types */ + def genConversion(from: TypeKind, to: TypeKind, value: js.Tree)( + implicit pos: Position): js.Tree = { + def int0 = js.IntLiteral(0) + def int1 = js.IntLiteral(1) + def long0 = js.LongLiteral(0L) + def long1 = js.LongLiteral(1L) + def float0 = js.FloatLiteral(0.0f) + def float1 = js.FloatLiteral(1.0f) + + (from, to) match { + case (INT(_), BOOL) => js.BinaryOp(js.BinaryOp.Num_!=, value, int0) + case (LONG, BOOL) => js.BinaryOp(js.BinaryOp.Long_!=, value, long0) + case (FLOAT(_), BOOL) => js.BinaryOp(js.BinaryOp.Num_!=, value, float0) + + case (BOOL, INT(_)) => js.If(value, int1, int0 )(jstpe.IntType) + case (BOOL, LONG) => js.If(value, long1, long0 )(jstpe.LongType) + case (BOOL, FLOAT(_)) => js.If(value, float1, float0)(jstpe.FloatType) + + case _ => value + } + } + + /** Gen JS code for an isInstanceOf test (for reference types only) */ + def genIsInstanceOf(value: js.Tree, to: Type)( + implicit pos: Position): js.Tree = { + + def genTypeOfTest(typeString: String) = { + js.BinaryOp(js.BinaryOp.===, + js.UnaryOp(js.UnaryOp.typeof, value), + js.StringLiteral(typeString)) + } + + if (isRawJSType(to)) { + to.typeSymbol match { + case JSNumberClass => genTypeOfTest("number") + case JSStringClass => genTypeOfTest("string") + case JSBooleanClass => genTypeOfTest("boolean") + case JSUndefinedClass => genTypeOfTest("undefined") + case sym if sym.isTrait => + reporter.error(pos, + s"isInstanceOf[${sym.fullName}] not supported because it is a raw JS trait") + js.BooleanLiteral(true) + case sym => + js.BinaryOp(js.BinaryOp.instanceof, value, genGlobalJSObject(sym)) + } + } else { + val refType = toReferenceType(to) + currentMethodInfoBuilder.accessesClassData(refType) + js.IsInstanceOf(value, refType) + } + } + + /** Gen JS code for an asInstanceOf cast (for reference types only) */ + def genAsInstanceOf(value: js.Tree, to: Type)( + implicit pos: Position): js.Tree = { + + def default: js.Tree = { + val refType = toReferenceType(to) + currentMethodInfoBuilder.accessesClassData(refType) + js.AsInstanceOf(value, refType) + } + + if (isRawJSType(to)) { + // asInstanceOf on JavaScript is completely erased + value + } else if (FunctionClass.seq contains to.typeSymbol) { + /* Don't hide a JSFunctionToScala inside a useless cast, otherwise + * the optimization avoiding double-wrapping in genApply() will not + * be able to kick in. + */ + value match { + case JSFunctionToScala(fun, _) => value + case _ => default + } + } else { + default + } + } + + /** Gen JS code for a call to a Scala method. + * This also registers that the given method is called by the current + * method in the method info builder. + */ + def genApplyMethod(receiver: js.Tree, receiverType: Type, + methodSym: Symbol, arguments: List[js.Tree])( + implicit pos: Position): js.Tree = { + genApplyMethod(receiver, receiverType.typeSymbol, methodSym, arguments) + } + + /** Gen JS code for a call to a Scala method. + * This also registers that the given method is called by the current + * method in the method info builder. + */ + def genApplyMethod(receiver: js.Tree, receiverTypeSym: Symbol, + methodSym: Symbol, arguments: List[js.Tree])( + implicit pos: Position): js.Tree = { + genApplyMethod(receiver, receiverTypeSym, + encodeMethodSym(methodSym), arguments, + toIRType(methodSym.tpe.resultType)) + } + + /** Gen JS code for a call to a Scala method. + * This also registers that the given method is called by the current + * method in the method info builder. + */ + def genApplyMethod(receiver: js.Tree, receiverType: Type, + methodIdent: js.Ident, arguments: List[js.Tree], resultType: jstpe.Type)( + implicit pos: Position): js.Tree = { + genApplyMethod(receiver, receiverType.typeSymbol, methodIdent, + arguments, resultType) + } + + /** Gen JS code for a call to a Scala method. + * This also registers that the given method is called by the current + * method in the method info builder. + */ + def genApplyMethod(receiver: js.Tree, receiverTypeSym: Symbol, + methodIdent: js.Ident, arguments: List[js.Tree], resultType: jstpe.Type)( + implicit pos: Position): js.Tree = { + currentMethodInfoBuilder.callsMethod(receiverTypeSym, methodIdent) + js.Apply(receiver, methodIdent, arguments)(resultType) + } + + /** Gen JS code for a call to a Scala class constructor. + * + * This also registers that the given class is instantiated by the current + * method, and that the given constructor is called, in the method info + * builder. + */ + def genNew(clazz: Symbol, ctor: Symbol, arguments: List[js.Tree])( + implicit pos: Position): js.Tree = { + if (clazz.isAnonymousFunction) + instantiatedAnonFunctions += clazz + assert(!isRawJSFunctionDef(clazz), + s"Trying to instantiate a raw JS function def $clazz") + val ctorIdent = encodeMethodSym(ctor) + currentMethodInfoBuilder.instantiatesClass(clazz) + currentMethodInfoBuilder.callsMethod(clazz, ctorIdent) + js.New(jstpe.ClassType(encodeClassFullName(clazz)), + ctorIdent, arguments) + } + + /** Gen JS code for a call to a constructor of a hijacked boxed class. + * All of these have 2 constructors: one with the primitive + * value, which is erased, and one with a String, which is + * equivalent to BoxedClass.valueOf(arg). + */ + private def genNewHijackedBoxedClass(clazz: Symbol, ctor: Symbol, + arguments: List[js.Tree])(implicit pos: Position): js.Tree = { + assert(arguments.size == 1) + if (isStringType(ctor.tpe.params.head.tpe)) { + // BoxedClass.valueOf(arg) + val companion = clazz.companionModule.moduleClass + val valueOf = getMemberMethod(companion, nme.valueOf) suchThat { s => + s.tpe.params.size == 1 && isStringType(s.tpe.params.head.tpe) + } + genApplyMethod(genLoadModule(companion), companion, valueOf, arguments) + } else { + // erased + arguments.head + } + } + + /** Gen JS code for creating a new Array: new Array[T](length) + * For multidimensional arrays (dimensions > 1), the arguments can + * specify up to `dimensions` lengths for the first dimensions of the + * array. + */ + def genNewArray(arrayType: jstpe.ArrayType, arguments: List[js.Tree])( + implicit pos: Position): js.Tree = { + assert(arguments.length <= arrayType.dimensions, + "too many arguments for array constructor: found " + arguments.length + + " but array has only " + arrayType.dimensions + " dimension(s)") + + currentMethodInfoBuilder.accessesClassData(arrayType) + js.NewArray(arrayType, arguments) + } + + /** Gen JS code for an array literal. + */ + def genArrayValue(tree: Tree): js.Tree = { + implicit val pos = tree.pos + val ArrayValue(tpt @ TypeTree(), elems) = tree + + val arrType = toReferenceType(tree.tpe).asInstanceOf[jstpe.ArrayType] + currentMethodInfoBuilder.accessesClassData(arrType) + js.ArrayValue(arrType, elems map genExpr) + } + + /** Gen JS code for a Match, i.e., a switch-able pattern match + * Eventually, this is compiled into a JS switch construct. But because + * we can be in expression position, and a JS switch cannot be given a + * meaning in expression position, we emit a JS "match" construct (which + * does not need the `break`s in each case. `JSDesugaring` will transform + * that in a switch. + * + * Some caveat here. It may happen that there is a guard in here, despite + * the fact that switches cannot have guards (in the JVM nor in JS). + * The JVM backend emits a jump to the default clause when a guard is not + * fulfilled. We cannot do that. Instead, currently we duplicate the body + * of the default case in the else branch of the guard test. + */ + def genMatch(tree: Tree, isStat: Boolean): js.Tree = { + implicit val pos = tree.pos + val Match(selector, cases) = tree + + val expr = genExpr(selector) + val resultType = toIRType(tree.tpe) + + val List(defaultBody0) = for { + CaseDef(Ident(nme.WILDCARD), EmptyTree, body) <- cases + } yield body + + val (defaultBody, defaultLabelSym) = defaultBody0 match { + case LabelDef(_, Nil, rhs) if hasSynthCaseSymbol(defaultBody0) => + (rhs, defaultBody0.symbol) + case _ => + (defaultBody0, NoSymbol) + } + + val genDefaultBody = genStatOrExpr(defaultBody, isStat) + + var clauses: List[(List[js.Literal], js.Tree)] = Nil + var elseClause: js.Tree = js.EmptyTree + + for (caze @ CaseDef(pat, guard, body) <- cases) { + assert(guard == EmptyTree) + + def genBody() = body match { + // Yes, this will duplicate the default body in the output + case If(cond, thenp, app @ Apply(_, Nil)) + if app.symbol == defaultLabelSym => + js.If(genExpr(cond), genStatOrExpr(thenp, isStat), genDefaultBody)( + resultType)(body.pos) + case If(cond, thenp, Block(List(app @ Apply(_, Nil)), _)) + if app.symbol == defaultLabelSym => + js.If(genExpr(cond), genStatOrExpr(thenp, isStat), genDefaultBody)( + resultType)(body.pos) + + case _ => + genStatOrExpr(body, isStat) + } + + def genLiteral(lit: Literal): js.Literal = + genExpr(lit).asInstanceOf[js.Literal] + + pat match { + case lit: Literal => + clauses = (List(genLiteral(lit)), genBody()) :: clauses + case Ident(nme.WILDCARD) => + elseClause = genDefaultBody + case Alternative(alts) => + val genAlts = { + alts map { + case lit: Literal => genLiteral(lit) + case _ => + abort("Invalid case in alternative in switch-like pattern match: " + + tree + " at: " + tree.pos) + } + } + clauses = (genAlts, genBody()) :: clauses + case _ => + abort("Invalid case statement in switch-like pattern match: " + + tree + " at: " + (tree.pos)) + } + } + + js.Match(expr, clauses.reverse, elseClause)(resultType) + } + + private def genBlock(tree: Block, isStat: Boolean): js.Tree = { + implicit val pos = tree.pos + val Block(stats, expr) = tree + + /** Predicate satisfied by LabelDefs produced by the pattern matcher */ + def isCaseLabelDef(tree: Tree) = + tree.isInstanceOf[LabelDef] && hasSynthCaseSymbol(tree) + + def translateMatch(expr: LabelDef) = { + /* Block that appeared as the result of a translated match + * Such blocks are recognized by having at least one element that is + * a so-called case-label-def. + * The method `genTranslatedMatch()` takes care of compiling the + * actual match. + * + * The assumption is once we encounter a case, the remainder of the + * block will consist of cases. + * The prologue may be empty, usually it is the valdef that stores + * the scrut. + */ + val (prologue, cases) = stats.span(s => !isCaseLabelDef(s)) + assert(cases.forall(isCaseLabelDef), + "Assumption on the form of translated matches broken: " + tree) + + val genPrologue = prologue map genStat + val translatedMatch = + genTranslatedMatch(cases.map(_.asInstanceOf[LabelDef]), expr) + + js.Block(genPrologue :+ translatedMatch) + } + + expr match { + case expr: LabelDef if isCaseLabelDef(expr) => + translateMatch(expr) + + // Sometimes the pattern matcher casts its final result + case Apply(TypeApply(Select(expr: LabelDef, nme.asInstanceOf_Ob), _), _) + if isCaseLabelDef(expr) => + translateMatch(expr) + + case _ => + assert(!stats.exists(isCaseLabelDef), "Found stats with case label " + + s"def in non-match block at ${tree.pos}: $tree") + + /* Normal block */ + val statements = stats map genStat + val expression = genStatOrExpr(expr, isStat) + js.Block(statements :+ expression) + } + } + + /** Gen JS code for a translated match + * + * This implementation relies heavily on the patterns of trees emitted + * by the current pattern match phase (as of Scala 2.10). + * + * The trees output by the pattern matcher are assumed to follow these + * rules: + * * Each case LabelDef (in `cases`) must not take any argument. + * * The last one must be a catch-all (case _ =>) that never falls through. + * * Jumps to the `matchEnd` are allowed anywhere in the body of the + * corresponding case label-defs, but not outside. + * * Jumps to case label-defs are restricted to jumping to the very next + * case, and only in positions denoted by <jump> in: + * <case-body> ::= + * If(_, <case-body>, <case-body>) + * | Block(_, <case-body>) + * | <jump> + * | _ + * These restrictions, together with the fact that we are in statement + * position (thanks to the above transformation), mean that they can be + * simply replaced by `skip`. + * + * To implement jumps to `matchEnd`, which have one argument which is the + * result of the match, we enclose all the cases in one big labeled block. + * Jumps are then compiled as `return`s out of the block. + */ + def genTranslatedMatch(cases: List[LabelDef], + matchEnd: LabelDef)(implicit pos: Position): js.Tree = { + + val nextCaseSyms = (cases.tail map (_.symbol)) :+ NoSymbol + + val translatedCases = for { + (LabelDef(_, Nil, rhs), nextCaseSym) <- cases zip nextCaseSyms + } yield { + def genCaseBody(tree: Tree): js.Tree = { + implicit val pos = tree.pos + tree match { + case If(cond, thenp, elsep) => + js.If(genExpr(cond), genCaseBody(thenp), genCaseBody(elsep))( + jstpe.NoType) + + case Block(stats, expr) => + js.Block((stats map genStat) :+ genCaseBody(expr)) + + case Apply(_, Nil) if tree.symbol == nextCaseSym => + js.Skip() + + case _ => + genStat(tree) + } + } + + genCaseBody(rhs) + } + + js.Labeled(encodeLabelSym(matchEnd.symbol), toIRType(matchEnd.tpe), + js.Block(translatedCases)) + } + + /** Gen JS code for a primitive method call */ + private def genPrimitiveOp(tree: Apply, isStat: Boolean): js.Tree = { + import scalaPrimitives._ + + implicit val pos = tree.pos + + val sym = tree.symbol + val Apply(fun @ Select(receiver, _), args) = tree + + val code = scalaPrimitives.getPrimitive(sym, receiver.tpe) + + if (isArithmeticOp(code) || isLogicalOp(code) || isComparisonOp(code)) + genSimpleOp(tree, receiver :: args, code) + else if (code == scalaPrimitives.CONCAT) + genStringConcat(tree, receiver, args) + else if (code == HASH) + genScalaHash(tree, receiver) + else if (isArrayOp(code)) + genArrayOp(tree, code) + else if (code == SYNCHRONIZED) + genSynchronized(tree, isStat) + else if (isCoercion(code)) + genCoercion(tree, receiver, code) + else if (jsPrimitives.isJavaScriptPrimitive(code)) + genJSPrimitive(tree, receiver, args, code) + else + abort("Unknown primitive operation: " + sym.fullName + "(" + + fun.symbol.simpleName + ") " + " at: " + (tree.pos)) + } + + /** Gen JS code for a simple operation (arithmetic, logical, or comparison) */ + private def genSimpleOp(tree: Apply, args: List[Tree], code: Int): js.Tree = { + import scalaPrimitives._ + + implicit val pos = tree.pos + + def isLongOp(ltpe: Type, rtpe: Type) = + (isLongType(ltpe) || isLongType(rtpe)) && + !(toTypeKind(ltpe).isInstanceOf[FLOAT] || + toTypeKind(rtpe).isInstanceOf[FLOAT] || + isStringType(ltpe) || isStringType(rtpe)) + + val sources = args map genExpr + + val resultType = toIRType(tree.tpe) + + sources match { + // Unary operation + case List(source) => + (code match { + case POS => + source + case NEG => + (resultType: @unchecked) match { + case jstpe.IntType => + js.BinaryOp(js.BinaryOp.Int_-, js.IntLiteral(0), source) + case jstpe.LongType => + js.BinaryOp(js.BinaryOp.Long_-, js.LongLiteral(0), source) + case jstpe.FloatType => + js.BinaryOp(js.BinaryOp.Float_-, js.FloatLiteral(0.0f), source) + case jstpe.DoubleType => + js.BinaryOp(js.BinaryOp.Double_-, js.DoubleLiteral(0), source) + } + case NOT => + (resultType: @unchecked) match { + case jstpe.IntType => + js.BinaryOp(js.BinaryOp.Int_^, js.IntLiteral(-1), source) + case jstpe.LongType => + js.BinaryOp(js.BinaryOp.Long_^, js.LongLiteral(-1), source) + } + case ZNOT => + js.UnaryOp(js.UnaryOp.Boolean_!, source) + case _ => + abort("Unknown unary operation code: " + code) + }) + + // Binary operation on Longs + case List(lsrc, rsrc) if isLongOp(args(0).tpe, args(1).tpe) => + def toLong(tree: js.Tree, tpe: Type) = + if (isLongType(tpe)) tree + else js.UnaryOp(js.UnaryOp.IntToLong, tree) + + def toInt(tree: js.Tree, tpe: Type) = + if (isLongType(tpe)) js.UnaryOp(js.UnaryOp.LongToInt, rsrc) + else tree + + val ltree = toLong(lsrc, args(0).tpe) + def rtree = toLong(rsrc, args(1).tpe) + def rtreeInt = toInt(rsrc, args(1).tpe) + + import js.BinaryOp._ + (code: @switch) match { + case ADD => js.BinaryOp(Long_+, ltree, rtree) + case SUB => js.BinaryOp(Long_-, ltree, rtree) + case MUL => js.BinaryOp(Long_*, ltree, rtree) + case DIV => js.BinaryOp(Long_/, ltree, rtree) + case MOD => js.BinaryOp(Long_%, ltree, rtree) + case OR => js.BinaryOp(Long_|, ltree, rtree) + case XOR => js.BinaryOp(Long_^, ltree, rtree) + case AND => js.BinaryOp(Long_&, ltree, rtree) + case LSL => js.BinaryOp(Long_<<, ltree, rtreeInt) + case LSR => js.BinaryOp(Long_>>>, ltree, rtreeInt) + case ASR => js.BinaryOp(Long_>>, ltree, rtreeInt) + case EQ => js.BinaryOp(Long_==, ltree, rtree) + case NE => js.BinaryOp(Long_!=, ltree, rtree) + case LT => js.BinaryOp(Long_<, ltree, rtree) + case LE => js.BinaryOp(Long_<=, ltree, rtree) + case GT => js.BinaryOp(Long_>, ltree, rtree) + case GE => js.BinaryOp(Long_>=, ltree, rtree) + case _ => + abort("Unknown binary operation code: " + code) + } + + // Binary operation + case List(lsrc_in, rsrc_in) => + def convertArg(tree: js.Tree, tpe: Type) = { + val kind = toTypeKind(tpe) + + // If we end up with a long, target must be float or double + val fromLong = + if (kind == LongKind) js.UnaryOp(js.UnaryOp.LongToDouble, tree) + else tree + + if (resultType != jstpe.FloatType) fromLong + else if (kind == FloatKind) fromLong + else js.UnaryOp(js.UnaryOp.DoubleToFloat, fromLong) + } + + val lsrc = convertArg(lsrc_in, args(0).tpe) + val rsrc = convertArg(rsrc_in, args(1).tpe) + + def genEquality(eqeq: Boolean, not: Boolean) = { + val typeKind = toTypeKind(args(0).tpe) + typeKind match { + case INT(_) | LONG | FLOAT(_) => + /* Note that LONG happens when a fromLong() had to do something, + * which means we're effectively in the FLOAT case. */ + js.BinaryOp(if (not) js.BinaryOp.Num_!= else js.BinaryOp.Num_==, lsrc, rsrc) + case BOOL => + js.BinaryOp(if (not) js.BinaryOp.Boolean_!= else js.BinaryOp.Boolean_==, lsrc, rsrc) + case REFERENCE(_) => + if (eqeq && + // don't call equals if we have a literal null at either side + !lsrc.isInstanceOf[js.Null] && + !rsrc.isInstanceOf[js.Null]) { + val body = genEqEqPrimitive(args(0).tpe, args(1).tpe, lsrc, rsrc) + if (not) js.UnaryOp(js.UnaryOp.Boolean_!, body) else body + } else { + js.BinaryOp(if (not) js.BinaryOp.!== else js.BinaryOp.===, lsrc, rsrc) + } + case _ => + // Arrays, Null, Nothing do not have an equals() method. + js.BinaryOp(if (not) js.BinaryOp.!== else js.BinaryOp.===, lsrc, rsrc) + } + } + + (code: @switch) match { + case EQ => genEquality(eqeq = true, not = false) + case NE => genEquality(eqeq = true, not = true) + case ID => genEquality(eqeq = false, not = false) + case NI => genEquality(eqeq = false, not = true) + + case ZOR => js.If(lsrc, js.BooleanLiteral(true), rsrc)(jstpe.BooleanType) + case ZAND => js.If(lsrc, rsrc, js.BooleanLiteral(false))(jstpe.BooleanType) + + case _ => + import js.BinaryOp._ + val op = (resultType: @unchecked) match { + case jstpe.IntType => + (code: @switch) match { + case ADD => Int_+ + case SUB => Int_- + case MUL => Int_* + case DIV => Int_/ + case MOD => Int_% + case OR => Int_| + case AND => Int_& + case XOR => Int_^ + case LSL => Int_<< + case LSR => Int_>>> + case ASR => Int_>> + } + case jstpe.FloatType => + (code: @switch) match { + case ADD => Float_+ + case SUB => Float_- + case MUL => Float_* + case DIV => Float_/ + case MOD => Float_% + } + case jstpe.DoubleType => + (code: @switch) match { + case ADD => Double_+ + case SUB => Double_- + case MUL => Double_* + case DIV => Double_/ + case MOD => Double_% + } + case jstpe.BooleanType => + (code: @switch) match { + case LT => Num_< + case LE => Num_<= + case GT => Num_> + case GE => Num_>= + case OR => Boolean_| + case AND => Boolean_& + case XOR => Boolean_!= + } + } + js.BinaryOp(op, lsrc, rsrc) + } + + case _ => + abort("Too many arguments for primitive function: " + tree) + } + } + + /** Gen JS code for a call to Any.== */ + def genEqEqPrimitive(ltpe: Type, rtpe: Type, lsrc: js.Tree, rsrc: js.Tree)( + implicit pos: Position): js.Tree = { + /* True if the equality comparison is between values that require the + * use of the rich equality comparator + * (scala.runtime.BoxesRunTime.equals). + * This is the case when either side of the comparison might have a + * run-time type subtype of java.lang.Number or java.lang.Character, + * **which includes when either is a raw JS type**. + * When it is statically known that both sides are equal and subtypes of + * Number or Character, not using the rich equality is possible (their + * own equals method will do ok.) + */ + val mustUseAnyComparator: Boolean = isRawJSType(ltpe) || isRawJSType(rtpe) || { + val areSameFinals = ltpe.isFinalType && rtpe.isFinalType && (ltpe =:= rtpe) + !areSameFinals && isMaybeBoxed(ltpe.typeSymbol) && isMaybeBoxed(rtpe.typeSymbol) + } + + if (mustUseAnyComparator) { + val equalsMethod: Symbol = { + val ptfm = platform.asInstanceOf[backend.JavaPlatform with ThisPlatform] // 2.10 compat + if (ltpe <:< BoxedNumberClass.tpe) { + if (rtpe <:< BoxedNumberClass.tpe) ptfm.externalEqualsNumNum + else if (rtpe <:< BoxedCharacterClass.tpe) ptfm.externalEqualsNumChar + else ptfm.externalEqualsNumObject + } else ptfm.externalEquals + } + val moduleClass = equalsMethod.owner + val instance = genLoadModule(moduleClass) + genApplyMethod(instance, moduleClass, equalsMethod, List(lsrc, rsrc)) + } else { + // if (lsrc eq null) rsrc eq null else lsrc.equals(rsrc) + if (isStringType(ltpe)) { + // String.equals(that) === (this eq that) + js.BinaryOp(js.BinaryOp.===, lsrc, rsrc) + } else { + /* This requires to evaluate both operands in local values first. + * The optimizer will eliminate them if possible. + */ + val ltemp = js.VarDef(freshLocalIdent(), lsrc.tpe, mutable = false, lsrc) + val rtemp = js.VarDef(freshLocalIdent(), rsrc.tpe, mutable = false, rsrc) + js.Block( + ltemp, + rtemp, + js.If(js.BinaryOp(js.BinaryOp.===, ltemp.ref, js.Null()), + js.BinaryOp(js.BinaryOp.===, rtemp.ref, js.Null()), + genApplyMethod(ltemp.ref, ltpe, Object_equals, List(rtemp.ref)))( + jstpe.BooleanType)) + } + } + } + + /** Gen JS code for string concatenation. + */ + private def genStringConcat(tree: Apply, receiver: Tree, + args: List[Tree]): js.Tree = { + implicit val pos = tree.pos + + /* Primitive number types such as scala.Int have a + * def +(s: String): String + * method, which is why we have to box the lhs sometimes. + * Otherwise, both lhs and rhs are already reference types (Any of String) + * so boxing is not necessary (in particular, rhs is never a primitive). + */ + assert(!isPrimitiveValueType(receiver.tpe) || isStringType(args.head.tpe)) + assert(!isPrimitiveValueType(args.head.tpe)) + + val rhs = genExpr(args.head) + + val lhs = { + val lhs0 = genExpr(receiver) + // Box the receiver if it is a primitive value + if (!isPrimitiveValueType(receiver.tpe)) lhs0 + else makePrimitiveBox(lhs0, receiver.tpe) + } + + js.BinaryOp(js.BinaryOp.String_+, lhs, rhs) + } + + /** Gen JS code for a call to Any.## */ + private def genScalaHash(tree: Apply, receiver: Tree): js.Tree = { + implicit val pos = tree.pos + + val instance = genLoadModule(ScalaRunTimeModule) + val arguments = List(genExpr(receiver)) + val sym = getMember(ScalaRunTimeModule, nme.hash_) + + genApplyMethod(instance, ScalaRunTimeModule.moduleClass, sym, arguments) + } + + /** Gen JS code for an array operation (get, set or length) */ + private def genArrayOp(tree: Tree, code: Int): js.Tree = { + import scalaPrimitives._ + + implicit val pos = tree.pos + + val Apply(Select(arrayObj, _), args) = tree + val arrayValue = genExpr(arrayObj) + val arguments = args map genExpr + + def genSelect() = { + val elemIRType = + toTypeKind(arrayObj.tpe).asInstanceOf[ARRAY].elem.toIRType + js.ArraySelect(arrayValue, arguments(0))(elemIRType) + } + + if (scalaPrimitives.isArrayGet(code)) { + // get an item of the array + assert(args.length == 1, + s"Array get requires 1 argument, found ${args.length} in $tree") + genSelect() + } else if (scalaPrimitives.isArraySet(code)) { + // set an item of the array + assert(args.length == 2, + s"Array set requires 2 arguments, found ${args.length} in $tree") + js.Assign(genSelect(), arguments(1)) + } else { + // length of the array + js.ArrayLength(arrayValue) + } + } + + /** Gen JS code for a call to AnyRef.synchronized */ + private def genSynchronized(tree: Apply, isStat: Boolean): js.Tree = { + /* JavaScript is single-threaded, so we can drop the + * synchronization altogether. + */ + val Apply(Select(receiver, _), List(arg)) = tree + val newReceiver = genExpr(receiver) + val newArg = genStatOrExpr(arg, isStat) + newReceiver match { + case js.This() => + // common case for which there is no side-effect nor NPE + newArg + case _ => + implicit val pos = tree.pos + val NPECtor = getMemberMethod(NullPointerExceptionClass, + nme.CONSTRUCTOR).suchThat(_.tpe.params.isEmpty) + js.Block( + js.If(js.BinaryOp(js.BinaryOp.===, newReceiver, js.Null()), + js.Throw(genNew(NullPointerExceptionClass, NPECtor, Nil)), + js.Skip())(jstpe.NoType), + newArg) + } + } + + /** Gen JS code for a coercion */ + private def genCoercion(tree: Apply, receiver: Tree, code: Int): js.Tree = { + import scalaPrimitives._ + + implicit val pos = tree.pos + + val source = genExpr(receiver) + + def source2int = (code: @switch) match { + case F2C | D2C | F2B | D2B | F2S | D2S | F2I | D2I => + js.UnaryOp(js.UnaryOp.DoubleToInt, source) + case L2C | L2B | L2S | L2I => + js.UnaryOp(js.UnaryOp.LongToInt, source) + case _ => + source + } + + (code: @switch) match { + // To Char, need to crop at unsigned 16-bit + case B2C | S2C | I2C | L2C | F2C | D2C => + js.BinaryOp(js.BinaryOp.Int_&, source2int, js.IntLiteral(0xffff)) + + // To Byte, need to crop at signed 8-bit + case C2B | S2B | I2B | L2B | F2B | D2B => + // note: & 0xff would not work because of negative values + js.BinaryOp(js.BinaryOp.Int_>>, + js.BinaryOp(js.BinaryOp.Int_<<, source2int, js.IntLiteral(24)), + js.IntLiteral(24)) + + // To Short, need to crop at signed 16-bit + case C2S | I2S | L2S | F2S | D2S => + // note: & 0xffff would not work because of negative values + js.BinaryOp(js.BinaryOp.Int_>>, + js.BinaryOp(js.BinaryOp.Int_<<, source2int, js.IntLiteral(16)), + js.IntLiteral(16)) + + // To Int, need to crop at signed 32-bit + case L2I | F2I | D2I => + source2int + + // Any int to Long + case C2L | B2L | S2L | I2L => + js.UnaryOp(js.UnaryOp.IntToLong, source) + + // Any double to Long + case F2L | D2L => + js.UnaryOp(js.UnaryOp.DoubleToLong, source) + + // Long to Double + case L2D => + js.UnaryOp(js.UnaryOp.LongToDouble, source) + + // Any int, or Double, to Float + case C2F | B2F | S2F | I2F | D2F => + js.UnaryOp(js.UnaryOp.DoubleToFloat, source) + + // Long to Float === Long to Double to Float + case L2F => + js.UnaryOp(js.UnaryOp.DoubleToFloat, + js.UnaryOp(js.UnaryOp.LongToDouble, source)) + + // Identities and IR upcasts + case C2C | B2B | S2S | I2I | L2L | F2F | D2D | + C2I | C2D | + B2S | B2I | B2D | + S2I | S2D | + I2D | + F2D => + source + } + } + + /** Gen JS code for an ApplyDynamic + * ApplyDynamic nodes appear as the result of calls to methods of a + * structural type. + * + * Most unfortunately, earlier phases of the compiler assume too much + * about the backend, namely, they believe arguments and the result must + * be boxed, and do the boxing themselves. This decision should be left + * to the backend, but it's not, so we have to undo these boxes. + * Note that this applies to parameter types only. The return type is boxed + * anyway since we do not know it's exact type. + * + * This then generates a call to the reflective call proxy for the given + * arguments. + */ + private def genApplyDynamic(tree: ApplyDynamic): js.Tree = { + implicit val pos = tree.pos + + val sym = tree.symbol + val params = sym.tpe.params + + /** check if the method we are invoking is eq or ne. they cannot be + * overridden since they are final. If this is true, we only emit a + * `===` or `!==`. + */ + val isEqOrNeq = (sym.name == nme.eq || sym.name == nme.ne) && + params.size == 1 && params.head.tpe.typeSymbol == ObjectClass + + /** check if the method we are invoking conforms to a method on + * scala.Array. If this is the case, we check that case specially at + * runtime to avoid having reflective call proxies on scala.Array. + * (Also, note that the element type of Array#update is not erased and + * therefore the method name mangling would turn out wrong) + * + * Note that we cannot check if the expected return type is correct, + * since this type information is already erased. + */ + def isArrayLikeOp = { + sym.name == nme.update && + params.size == 2 && params.head.tpe.typeSymbol == IntClass || + sym.name == nme.apply && + params.size == 1 && params.head.tpe.typeSymbol == IntClass || + sym.name == nme.length && + params.size == 0 || + sym.name == nme.clone_ && + params.size == 0 + } + + /** + * Tests whether one of our reflective "boxes" for primitive types + * implements the particular method. If this is the case + * (result != NoSymbol), we generate a runtime instance check if we are + * dealing with the appropriate primitive type. + */ + def matchingSymIn(clazz: Symbol) = clazz.tpe.member(sym.name).suchThat { s => + val sParams = s.tpe.params + !s.isBridge && + params.size == sParams.size && + (params zip sParams).forall { case (s1,s2) => + s1.tpe =:= s2.tpe + } + } + + val ApplyDynamic(receiver, args) = tree + + if (isEqOrNeq) { + // Just emit a boxed equality check + val jsThis = genExpr(receiver) + val jsThat = genExpr(args.head) + val op = if (sym.name == nme.eq) js.BinaryOp.=== else js.BinaryOp.!== + ensureBoxed(js.BinaryOp(op, jsThis, jsThat), BooleanClass.tpe) + } else { + // Create a fully-fledged reflective call + val receiverType = toIRType(receiver.tpe) + val callTrgIdent = freshLocalIdent() + val callTrgVarDef = + js.VarDef(callTrgIdent, receiverType, mutable = false, genExpr(receiver)) + val callTrg = js.VarRef(callTrgIdent, mutable = false)(receiverType) + + val arguments = args zip sym.tpe.params map { case (arg, param) => + /* No need for enteringPosterasure, because value classes are not + * supported as parameters of methods in structural types. + * We could do it for safety and future-proofing anyway, except that + * I am weary of calling enteringPosterasure for a reflective method + * symbol. + * + * Note also that this will typically unbox a primitive value that + * has just been boxed, or will .asInstanceOf[T] an expression which + * is already of type T. But the optimizer will get rid of that, and + * reflective calls are not numerous, so we don't complicate the + * compiler to eliminate them early. + */ + fromAny(genExpr(arg), param.tpe) + } + + val proxyIdent = encodeMethodSym(sym, reflProxy = true) + var callStatement: js.Tree = + genApplyMethod(callTrg, receiver.tpe, proxyIdent, arguments, + jstpe.AnyType) + + if (isArrayLikeOp) { + def genRTCall(method: Symbol, args: js.Tree*) = + genApplyMethod(genLoadModule(ScalaRunTimeModule), + ScalaRunTimeModule.moduleClass, method, args.toList) + val isArrayTree = + genRTCall(ScalaRunTime_isArray, callTrg, js.IntLiteral(1)) + callStatement = js.If(isArrayTree, { + sym.name match { + case nme.update => + js.Block( + genRTCall(currentRun.runDefinitions.arrayUpdateMethod, + callTrg, arguments(0), arguments(1)), + js.Undefined()) // Boxed Unit + case nme.apply => + genRTCall(currentRun.runDefinitions.arrayApplyMethod, callTrg, + arguments(0)) + case nme.length => + genRTCall(currentRun.runDefinitions.arrayLengthMethod, callTrg) + case nme.clone_ => + genApplyMethod(callTrg, receiver.tpe, Object_clone, arguments) + } + }, { + callStatement + })(jstpe.AnyType) + } + + for { + (primTypeOf, reflBoxClass) <- Seq( + ("string", StringClass), + ("number", NumberReflectiveCallClass), + ("boolean", BooleanReflectiveCallClass) + ) + implMethodSym = matchingSymIn(reflBoxClass) + if implMethodSym != NoSymbol && implMethodSym.isPublic + } { + callStatement = js.If( + js.BinaryOp(js.BinaryOp.===, + js.UnaryOp(js.UnaryOp.typeof, callTrg), + js.StringLiteral(primTypeOf)), { + if (implMethodSym.owner == ObjectClass) { + // If the method is defined on Object, we can call it normally. + genApplyMethod(callTrg, receiver.tpe, implMethodSym, arguments) + } else { + if (primTypeOf == "string") { + val (rtModuleClass, methodIdent) = + encodeRTStringMethodSym(implMethodSym) + val retTpe = implMethodSym.tpe.resultType + val castCallTrg = fromAny(callTrg, StringClass.toTypeConstructor) + val rawApply = genApplyMethod( + genLoadModule(rtModuleClass), + rtModuleClass, + methodIdent, + castCallTrg :: arguments, + toIRType(retTpe)) + // Box the result of the implementing method if required + if (isPrimitiveValueType(retTpe)) + makePrimitiveBox(rawApply, retTpe) + else + rawApply + } else { + val (reflBoxClassPatched, callTrg1) = { + def isIntOrLongKind(kind: TypeKind) = kind match { + case _:INT | LONG => true + case _ => false + } + if (primTypeOf == "number" && + toTypeKind(implMethodSym.tpe.resultType) == DoubleKind && + isIntOrLongKind(toTypeKind(sym.tpe.resultType))) { + // This must be an Int, and not a Double + (IntegerReflectiveCallClass, + js.AsInstanceOf(callTrg, + toReferenceType(BoxedIntClass.toTypeConstructor))) + } else { + (reflBoxClass, callTrg) + } + } + val castCallTrg = + fromAny(callTrg1, + reflBoxClassPatched.primaryConstructor.tpe.params.head.tpe) + val reflBox = genNew(reflBoxClassPatched, + reflBoxClassPatched.primaryConstructor, List(castCallTrg)) + genApplyMethod( + reflBox, + reflBoxClassPatched, + proxyIdent, + arguments, + jstpe.AnyType) + } + } + }, { // else + callStatement + })(jstpe.AnyType) + } + + js.Block(callTrgVarDef, callStatement) + } + } + + /** Ensures that the value of the given tree is boxed. + * @param expr Tree to be boxed if needed. + * @param tpeEnteringPosterasure The type of `expr` as it was entering + * the posterasure phase. + */ + def ensureBoxed(expr: js.Tree, tpeEnteringPosterasure: Type)( + implicit pos: Position): js.Tree = { + + tpeEnteringPosterasure match { + case tpe if isPrimitiveValueType(tpe) => + makePrimitiveBox(expr, tpe) + + case tpe: ErasedValueType => + val boxedClass = tpe.valueClazz + val ctor = boxedClass.primaryConstructor + genNew(boxedClass, ctor, List(expr)) + + case _ => + expr + } + } + + /** Extracts a value typed as Any to the given type after posterasure. + * @param expr Tree to be extracted. + * @param tpeEnteringPosterasure The type of `expr` as it was entering + * the posterasure phase. + */ + def fromAny(expr: js.Tree, tpeEnteringPosterasure: Type)( + implicit pos: Position): js.Tree = { + + tpeEnteringPosterasure match { + case tpe if isPrimitiveValueType(tpe) => + makePrimitiveUnbox(expr, tpe) + + case tpe: ErasedValueType => + val boxedClass = tpe.valueClazz + val unboxMethod = boxedClass.derivedValueClassUnbox + val content = genApplyMethod( + genAsInstanceOf(expr, tpe), + boxedClass, unboxMethod, Nil) + if (unboxMethod.tpe.resultType <:< tpe.erasedUnderlying) + content + else + fromAny(content, tpe.erasedUnderlying) + + case tpe => + genAsInstanceOf(expr, tpe) + } + } + + /** Gen a boxing operation (tpe is the primitive type) */ + def makePrimitiveBox(expr: js.Tree, tpe: Type)( + implicit pos: Position): js.Tree = { + toTypeKind(tpe) match { + case VOID => // must be handled at least for JS interop + js.Block(expr, js.Undefined()) + case kind: ValueTypeKind => + if (kind == CharKind) { + genApplyMethod( + genLoadModule(BoxesRunTimeClass), + BoxesRunTimeClass, + BoxesRunTime_boxToCharacter, + List(expr)) + } else { + expr // box is identity for all non-Char types + } + case _ => + abort(s"makePrimitiveBox requires a primitive type, found $tpe at $pos") + } + } + + /** Gen an unboxing operation (tpe is the primitive type) */ + def makePrimitiveUnbox(expr: js.Tree, tpe: Type)( + implicit pos: Position): js.Tree = { + toTypeKind(tpe) match { + case VOID => // must be handled at least for JS interop + expr + case kind: ValueTypeKind => + if (kind == CharKind) { + genApplyMethod( + genLoadModule(BoxesRunTimeClass), + BoxesRunTimeClass, + BoxesRunTime_unboxToChar, + List(expr)) + } else { + js.Unbox(expr, kind.primitiveCharCode) + } + case _ => + abort(s"makePrimitiveUnbox requires a primitive type, found $tpe at $pos") + } + } + + private def lookupModuleClass(name: String) = { + val module = getModuleIfDefined(name) + if (module == NoSymbol) NoSymbol + else module.moduleClass + } + + lazy val ReflectArrayModuleClass = lookupModuleClass("java.lang.reflect.Array") + lazy val UtilArraysModuleClass = lookupModuleClass("java.util.Arrays") + + /** Gen JS code for a Scala.js-specific primitive method */ + private def genJSPrimitive(tree: Apply, receiver0: Tree, + args: List[Tree], code: Int): js.Tree = { + import jsPrimitives._ + + implicit val pos = tree.pos + + def receiver = genExpr(receiver0) + val genArgArray = genPrimitiveJSArgs(tree.symbol, args) + + lazy val js.JSArrayConstr(genArgs) = genArgArray + + def extractFirstArg() = { + (genArgArray: @unchecked) match { + case js.JSArrayConstr(firstArg :: otherArgs) => + (firstArg, js.JSArrayConstr(otherArgs)) + case js.JSBracketMethodApply( + js.JSArrayConstr(firstArg :: firstPart), concat, otherParts) => + (firstArg, js.JSBracketMethodApply( + js.JSArrayConstr(firstPart), concat, otherParts)) + } + } + + if (code == DYNNEW) { + // js.Dynamic.newInstance(clazz)(actualArgs:_*) + val (jsClass, actualArgArray) = extractFirstArg() + actualArgArray match { + case js.JSArrayConstr(actualArgs) => + js.JSNew(jsClass, actualArgs) + case _ => + genNewJSWithVarargs(jsClass, actualArgArray) + } + } else if (code == DYNAPPLY) { + // js.Dynamic.applyDynamic(methodName)(actualArgs:_*) + val (methodName, actualArgArray) = extractFirstArg() + actualArgArray match { + case js.JSArrayConstr(actualArgs) => + js.JSBracketMethodApply(receiver, methodName, actualArgs) + case _ => + genApplyJSMethodWithVarargs(receiver, methodName, actualArgArray) + } + } else if (code == DYNLITN) { + // We have a call of the form: + // js.Dynamic.literal(name1 = ..., name2 = ...) + // Translate to: + // {"name1": ..., "name2": ... } + extractFirstArg() match { + case (js.StringLiteral("apply"), + js.JSArrayConstr(jse.LitNamed(pairs))) => + js.JSObjectConstr(pairs) + case (js.StringLiteral(name), _) if name != "apply" => + reporter.error(pos, + s"js.Dynamic.literal does not have a method named $name") + js.Undefined() + case _ => + reporter.error(pos, + "js.Dynamic.literal.applyDynamicNamed may not be called directly") + js.Undefined() + } + } else if (code == DYNLIT) { + // We have a call of some other form + // js.Dynamic.literal(...) + // Translate to: + // var obj = {}; + // obj[...] = ...; + // obj + + // Extract first arg to future proof against varargs + extractFirstArg() match { + // case js.Dynamic.literal("name1" -> ..., "name2" -> ...) + case (js.StringLiteral("apply"), + js.JSArrayConstr(jse.LitNamed(pairs))) => + js.JSObjectConstr(pairs) + + // case js.Dynamic.literal(x, y) + case (js.StringLiteral("apply"), js.JSArrayConstr(tups)) => + // Create tmp variable + val resIdent = freshLocalIdent("obj") + val resVarDef = js.VarDef(resIdent, jstpe.AnyType, mutable = false, + js.JSObjectConstr(Nil)) + val res = resVarDef.ref + + // Assign fields + val tuple2Type = encodeClassType(TupleClass(2)) + val assigns = tups flatMap { + // special case for literals + case jse.Tuple2(name, value) => + js.Assign(js.JSBracketSelect(res, name), value) :: Nil + case tupExpr => + val tupIdent = freshLocalIdent("tup") + val tup = js.VarRef(tupIdent, mutable = false)(tuple2Type) + js.VarDef(tupIdent, tuple2Type, mutable = false, tupExpr) :: + js.Assign(js.JSBracketSelect(res, + genApplyMethod(tup, TupleClass(2), js.Ident("$$und1__O"), Nil, jstpe.AnyType)), + genApplyMethod(tup, TupleClass(2), js.Ident("$$und2__O"), Nil, jstpe.AnyType)) :: Nil + } + + js.Block(resVarDef +: assigns :+ res: _*) + + /* Here we would need the case where the varargs are passed in + * as non-literal list: + * js.Dynamic.literal(x: _*) + * However, Scala does not currently support this + */ + + // case where another method is called + case (js.StringLiteral(name), _) if name != "apply" => + reporter.error(pos, + s"js.Dynamic.literal does not have a method named $name") + js.Undefined() + case _ => + reporter.error(pos, + "js.Dynamic.literal.applyDynamic may not be called directly") + js.Undefined() + } + } else if (code == ARR_CREATE) { + // js.Array.create(elements: _*) + genArgArray + } else (genArgs match { + case Nil => + code match { + case GETCLASS => js.GetClass(receiver) + case ENV_INFO => js.JSEnvInfo() + case DEBUGGER => js.Debugger() + case UNDEFVAL => js.Undefined() + case UNITVAL => js.Undefined() + case UNITTYPE => genClassConstant(UnitTpe) + case JS_NATIVE => + reporter.error(pos, "js.native may only be used as stub implementation in facade types") + js.Undefined() + } + + case List(arg) => + + /** Factorization of F2JS and F2JSTHIS. */ + def genFunctionToJSFunction(isThisFunction: Boolean): js.Tree = { + val arity = { + val funName = tree.fun.symbol.name.encoded + assert(funName.startsWith("fromFunction")) + funName.stripPrefix("fromFunction").toInt + } + val inputClass = FunctionClass(arity) + val inputIRType = encodeClassType(inputClass) + val applyMeth = getMemberMethod(inputClass, nme.apply) suchThat { s => + val ps = s.paramss + ps.size == 1 && + ps.head.size == arity && + ps.head.forall(_.tpe.typeSymbol == ObjectClass) + } + val fCaptureParam = js.ParamDef(js.Ident("f"), inputIRType, + mutable = false) + val jsArity = + if (isThisFunction) arity - 1 + else arity + val jsParams = (1 to jsArity).toList map { + x => js.ParamDef(js.Ident("arg"+x), jstpe.AnyType, + mutable = false) + } + js.Closure( + List(fCaptureParam), + jsParams, + genApplyMethod( + fCaptureParam.ref, + inputClass, applyMeth, + if (isThisFunction) + js.This()(jstpe.AnyType) :: jsParams.map(_.ref) + else + jsParams.map(_.ref)), + List(arg)) + } + + code match { + /** Convert a scala.FunctionN f to a js.FunctionN. */ + case F2JS => + arg match { + /* This case will happen every time we have a Scala lambda + * in js.FunctionN position. We remove the JS function to + * Scala function wrapper, instead of adding a Scala function + * to JS function wrapper. + */ + case JSFunctionToScala(fun, arity) => + fun + case _ => + genFunctionToJSFunction(isThisFunction = false) + } + + /** Convert a scala.FunctionN f to a js.ThisFunction{N-1}. */ + case F2JSTHIS => + genFunctionToJSFunction(isThisFunction = true) + + case DYNSELECT => + // js.Dynamic.selectDynamic(arg) + js.JSBracketSelect(receiver, arg) + + case DICT_DEL => + // js.Dictionary.delete(arg) + js.JSDelete(js.JSBracketSelect(receiver, arg)) + + case ISUNDEF => + // js.isUndefined(arg) + js.BinaryOp(js.BinaryOp.===, arg, js.Undefined()) + case TYPEOF => + // js.typeOf(arg) + js.UnaryOp(js.UnaryOp.typeof, arg) + + case OBJPROPS => + // js.Object.properties(arg) + genApplyMethod( + genLoadModule(RuntimePackageModule), + RuntimePackageModule.moduleClass, + Runtime_propertiesOf, + List(arg)) + } + + case List(arg1, arg2) => + code match { + case DYNUPDATE => + // js.Dynamic.updateDynamic(arg1)(arg2) + js.Assign(js.JSBracketSelect(receiver, arg1), arg2) + + case HASPROP => + // js.Object.hasProperty(arg1, arg2) + /* Here we have an issue with evaluation order of arg1 and arg2, + * since the obvious translation is `arg2 in arg1`, but then + * arg2 is evaluated before arg1. Since this is not a commonly + * used operator, we don't try to avoid unnessary temp vars, and + * simply always evaluate arg1 in a temp before doing the `in`. + */ + val temp = freshLocalIdent() + js.Block( + js.VarDef(temp, jstpe.AnyType, mutable = false, arg1), + js.BinaryOp(js.BinaryOp.in, arg2, + js.VarRef(temp, mutable = false)(jstpe.AnyType))) + } + }) + } + + /** Gen JS code for a primitive JS call (to a method of a subclass of js.Any) + * This is the typed Scala.js to JS bridge feature. Basically it boils + * down to calling the method without name mangling. But other aspects + * come into play: + * * Operator methods are translated to JS operators (not method calls) + * * apply is translated as a function call, i.e. o() instead of o.apply() + * * Scala varargs are turned into JS varargs (see genPrimitiveJSArgs()) + * * Getters and parameterless methods are translated as Selects + * * Setters are translated to Assigns of Selects + */ + private def genPrimitiveJSCall(tree: Apply, isStat: Boolean): js.Tree = { + implicit val pos = tree.pos + + val sym = tree.symbol + val Apply(fun @ Select(receiver0, _), args0) = tree + + val funName = sym.unexpandedName.decoded + val receiver = genExpr(receiver0) + val argArray = genPrimitiveJSArgs(sym, args0) + + // valid only for methods that don't have any varargs + lazy val js.JSArrayConstr(args) = argArray + lazy val argc = args.length + + def hasExplicitJSEncoding = + sym.hasAnnotation(JSNameAnnotation) || + sym.hasAnnotation(JSBracketAccessAnnotation) + + val boxedResult = funName match { + case "unary_+" | "unary_-" | "unary_~" | "unary_!" => + assert(argc == 0) + js.JSUnaryOp(funName.substring(funName.length-1), receiver) + + case "+" | "-" | "*" | "/" | "%" | "<<" | ">>" | ">>>" | + "&" | "|" | "^" | "&&" | "||" | "<" | ">" | "<=" | ">=" => + assert(argc == 1) + js.JSBinaryOp(funName, receiver, args.head) + + case "apply" if receiver0.tpe.typeSymbol.isSubClass(JSThisFunctionClass) => + js.JSBracketMethodApply(receiver, js.StringLiteral("call"), args) + + case "apply" if !hasExplicitJSEncoding => + argArray match { + case js.JSArrayConstr(args) => + js.JSFunctionApply(receiver, args) + case _ => + js.JSBracketMethodApply( + receiver, js.StringLiteral("apply"), List(js.Null(), argArray)) + } + + case _ => + def jsFunName = jsNameOf(sym) + + if (sym.hasFlag(reflect.internal.Flags.DEFAULTPARAM)) { + js.UndefinedParam()(toIRType(sym.tpe.resultType)) + } else if (jsInterop.isJSGetter(sym)) { + assert(argc == 0) + js.JSBracketSelect(receiver, js.StringLiteral(jsFunName)) + } else if (jsInterop.isJSSetter(sym)) { + assert(argc == 1) + js.Assign( + js.JSBracketSelect(receiver, + js.StringLiteral(jsFunName.stripSuffix("_="))), + args.head) + } else if (jsInterop.isJSBracketAccess(sym)) { + assert(argArray.isInstanceOf[js.JSArrayConstr] && (argc == 1 || argc == 2), + s"@JSBracketAccess methods should have 1 or 2 non-varargs arguments") + args match { + case List(keyArg) => + js.JSBracketSelect(receiver, keyArg) + case List(keyArg, valueArg) => + js.Assign( + js.JSBracketSelect(receiver, keyArg), + valueArg) + } + } else { + argArray match { + case js.JSArrayConstr(args) => + js.JSBracketMethodApply( + receiver, js.StringLiteral(jsFunName), args) + case _ => + genApplyJSMethodWithVarargs(receiver, + js.StringLiteral(jsFunName), argArray) + } + } + } + + boxedResult match { + case js.UndefinedParam() | js.Assign(_, _) => + boxedResult + case _ if isStat => + boxedResult + case _ => + fromAny(boxedResult, + enteringPhase(currentRun.posterasurePhase)(sym.tpe.resultType)) + } + } + + /** Gen JS code to call a primitive JS method with variadic parameters. */ + private def genApplyJSMethodWithVarargs(receiver: js.Tree, + methodName: js.Tree, argArray: js.Tree)( + implicit pos: Position): js.Tree = { + // We need to evaluate `receiver` only once + val receiverValDef = + js.VarDef(freshLocalIdent(), receiver.tpe, mutable = false, receiver) + js.Block( + receiverValDef, + js.JSBracketMethodApply( + js.JSBracketSelect(receiverValDef.ref, methodName), + js.StringLiteral("apply"), + List(receiverValDef.ref, argArray))) + } + + /** Gen JS code to instantiate a JS class with variadic parameters. */ + private def genNewJSWithVarargs(jsClass: js.Tree, argArray: js.Tree)( + implicit pos: Position): js.Tree = { + genApplyMethod( + genLoadModule(RuntimePackageModule), + RuntimePackageModule.moduleClass, + Runtime_newJSObjectWithVarargs, + List(jsClass, argArray)) + } + + /** Gen JS code for new java.lang.String(...) + * Proxies calls to method newString on object + * scala.scalajs.runtime.RuntimeString with proper arguments + */ + private def genNewString(tree: Apply): js.Tree = { + implicit val pos = tree.pos + val Apply(fun @ Select(_, _), args0) = tree + + val ctor = fun.symbol + val args = args0 map genExpr + + // Filter members of target module for matching member + val compMembers = for { + mem <- RuntimeStringModule.tpe.members + if mem.name == jsnme.newString && ctor.tpe.matches(mem.tpe) + } yield mem + + if (compMembers.isEmpty) { + reporter.error(pos, + s"""Could not find implementation for constructor of java.lang.String + |with type ${ctor.tpe}. Constructors on java.lang.String + |are forwarded to the companion object of + |scala.scalajs.runtime.RuntimeString""".stripMargin) + js.Undefined() + } else { + assert(compMembers.size == 1, + s"""For constructor with type ${ctor.tpe} on java.lang.String, + |found multiple companion module members.""".stripMargin) + + // Emit call to companion object + genApplyMethod( + genLoadModule(RuntimeStringModule), + RuntimeStringModule.moduleClass, + compMembers.head, + args) + } + } + + /** Gen JS code for calling a method on java.lang.String. + * + * Forwards call on java.lang.String to the module + * scala.scalajs.runtime.RuntimeString. + */ + private def genStringCall(tree: Apply): js.Tree = { + implicit val pos = tree.pos + + val sym = tree.symbol + + // Deconstruct tree and create receiver and argument JS expressions + val Apply(Select(receiver0, _), args0) = tree + val receiver = genExpr(receiver0) + val args = args0 map genExpr + + // Emit call to the RuntimeString module + val (rtModuleClass, methodIdent) = encodeRTStringMethodSym(sym) + genApplyMethod( + genLoadModule(rtModuleClass), + rtModuleClass, + methodIdent, + receiver :: args, + toIRType(tree.tpe)) + } + + /** Gen JS code for a new of a raw JS class (subclass of js.Any) */ + private def genPrimitiveJSNew(tree: Apply): js.Tree = { + implicit val pos = tree.pos + + val Apply(fun @ Select(New(tpt), _), args0) = tree + val cls = tpt.tpe.typeSymbol + val ctor = fun.symbol + + genPrimitiveJSArgs(ctor, args0) match { + case js.JSArrayConstr(args) => + if (cls == JSObjectClass && args.isEmpty) js.JSObjectConstr(Nil) + else if (cls == JSArrayClass && args.isEmpty) js.JSArrayConstr(Nil) + else js.JSNew(genPrimitiveJSClass(cls), args) + case argArray => + genNewJSWithVarargs(genPrimitiveJSClass(cls), argArray) + } + } + + /** Gen JS code representing a JS class (subclass of js.Any) */ + private def genPrimitiveJSClass(sym: Symbol)( + implicit pos: Position): js.Tree = { + genGlobalJSObject(sym) + } + + /** Gen JS code representing a JS module (var of the global scope) */ + private def genPrimitiveJSModule(sym: Symbol)( + implicit pos: Position): js.Tree = { + genGlobalJSObject(sym) + } + + /** Gen JS code representing a JS object (class or module) in global scope + */ + private def genGlobalJSObject(sym: Symbol)( + implicit pos: Position): js.Tree = { + jsNameOf(sym).split('.').foldLeft(genLoadGlobal()) { (memo, chunk) => + js.JSBracketSelect(memo, js.StringLiteral(chunk)) + } + } + + /** Gen actual actual arguments to Scala method call. + * Returns a list of the transformed arguments. + * + * This tries to optimized repeated arguments (varargs) by turning them + * into js.WrappedArray instead of Scala wrapped arrays. + */ + private def genActualArgs(sym: Symbol, args: List[Tree])( + implicit pos: Position): List[js.Tree] = { + val wereRepeated = exitingPhase(currentRun.typerPhase) { + sym.tpe.params.map(p => isScalaRepeatedParamType(p.tpe)) + } + + if (wereRepeated.size > args.size) { + // Should not happen, but let's not crash + args.map(genExpr) + } else { + /* Arguments that are in excess compared to the type signature after + * erasure are lambda-lifted arguments. They cannot be repeated, hence + * the extension to `false`. + */ + for ((arg, wasRepeated) <- args.zipAll(wereRepeated, EmptyTree, false)) yield { + if (wasRepeated) { + tryGenRepeatedParamAsJSArray(arg, handleNil = false).fold { + genExpr(arg) + } { argArray => + genNew(WrappedArrayClass, WrappedArray_ctor, List(argArray)) + } + } else { + genExpr(arg) + } + } + } + } + + /** Gen actual actual arguments to a primitive JS call + * This handles repeated arguments (varargs) by turning them into + * JS varargs, i.e., by expanding them into normal arguments. + * + * Returns an only tree which is a JS array of the arguments. In most + * cases, it will be a js.JSArrayConstr with the expanded arguments. It will + * not if a Seq is passed to a varargs argument with the syntax seq: _*. + */ + private def genPrimitiveJSArgs(sym: Symbol, args: List[Tree])( + implicit pos: Position): js.Tree = { + val wereRepeated = exitingPhase(currentRun.typerPhase) { + for { + params <- sym.tpe.paramss + param <- params + } yield isScalaRepeatedParamType(param.tpe) + } + + var reversedParts: List[js.Tree] = Nil + var reversedPartUnderConstruction: List[js.Tree] = Nil + + def closeReversedPartUnderConstruction() = { + if (!reversedPartUnderConstruction.isEmpty) { + val part = reversedPartUnderConstruction.reverse + reversedParts ::= js.JSArrayConstr(part) + reversedPartUnderConstruction = Nil + } + } + + val paramTpes = enteringPhase(currentRun.posterasurePhase) { + for (param <- sym.tpe.params) + yield param.tpe + } + + for (((arg, wasRepeated), tpe) <- (args zip wereRepeated) zip paramTpes) { + if (wasRepeated) { + genPrimitiveJSRepeatedParam(arg) match { + case js.JSArrayConstr(jsArgs) => + reversedPartUnderConstruction = + jsArgs reverse_::: reversedPartUnderConstruction + case jsArgArray => + closeReversedPartUnderConstruction() + reversedParts ::= jsArgArray + } + } else { + val unboxedArg = genExpr(arg) + val boxedArg = unboxedArg match { + case js.UndefinedParam() => unboxedArg + case _ => ensureBoxed(unboxedArg, tpe) + } + reversedPartUnderConstruction ::= boxedArg + } + } + closeReversedPartUnderConstruction() + + // Find js.UndefinedParam at the end of the argument list. No check is + // performed whether they may be there, since they will only be placed + // where default arguments can be anyway + reversedParts = reversedParts match { + case Nil => Nil + case js.JSArrayConstr(params) :: others => + val nparams = + params.reverse.dropWhile(_.isInstanceOf[js.UndefinedParam]).reverse + js.JSArrayConstr(nparams) :: others + case parts => parts + } + + // Find remaining js.UndefinedParam and replace by js.Undefined. This can + // happen with named arguments or when multiple argument lists are present + reversedParts = reversedParts map { + case js.JSArrayConstr(params) => + val nparams = params map { + case js.UndefinedParam() => js.Undefined() + case param => param + } + js.JSArrayConstr(nparams) + case part => part + } + + reversedParts match { + case Nil => js.JSArrayConstr(Nil) + case List(part) => part + case _ => + val partHead :: partTail = reversedParts.reverse + js.JSBracketMethodApply( + partHead, js.StringLiteral("concat"), partTail) + } + } + + /** Gen JS code for a repeated param of a primitive JS method + * In this case `arg` has type Seq[T] for some T, but the result should + * have type js.Array[T]. So this method takes care of the conversion. + * It is specialized for the shapes of tree generated by the desugaring + * of repeated params in Scala, so that these produce a js.JSArrayConstr. + */ + private def genPrimitiveJSRepeatedParam(arg: Tree): js.Tree = { + tryGenRepeatedParamAsJSArray(arg, handleNil = true) getOrElse { + /* Fall back to calling runtime.genTraversableOnce2jsArray + * to perform the conversion. + */ + implicit val pos = arg.pos + genApplyMethod( + genLoadModule(RuntimePackageModule), + RuntimePackageModule.moduleClass, + Runtime_genTraversableOnce2jsArray, + List(genExpr(arg))) + } + } + + /** Try and gen a js.Array for a repeated param (xs: T*). + * It is specialized for the shapes of tree generated by the desugaring + * of repeated params in Scala, so that these produce a js.JSArrayConstr. + * If `arg` does not have the shape of a generated repeated param, this + * method returns `None`. + */ + private def tryGenRepeatedParamAsJSArray(arg: Tree, + handleNil: Boolean): Option[js.Tree] = { + implicit val pos = arg.pos + + // Given a method `def foo(args: T*)` + arg match { + // foo(arg1, arg2, ..., argN) where N > 0 + case MaybeAsInstanceOf(WrapArray( + MaybeAsInstanceOf(ArrayValue(tpt, elems)))) => + /* Value classes in arrays are already boxed, so no need to use + * the type before erasure. + */ + val elemTpe = tpt.tpe + Some(js.JSArrayConstr(elems.map(e => ensureBoxed(genExpr(e), elemTpe)))) + + // foo() + case Select(_, _) if handleNil && arg.symbol == NilModule => + Some(js.JSArrayConstr(Nil)) + + // foo(argSeq:_*) - cannot be optimized + case _ => + None + } + } + + object MaybeAsInstanceOf { + def unapply(tree: Tree): Some[Tree] = tree match { + case Apply(TypeApply(asInstanceOf_? @ Select(base, _), _), _) + if asInstanceOf_?.symbol == Object_asInstanceOf => + Some(base) + case _ => + Some(tree) + } + } + + object WrapArray { + lazy val isWrapArray: Set[Symbol] = Seq( + nme.wrapRefArray, + nme.wrapByteArray, + nme.wrapShortArray, + nme.wrapCharArray, + nme.wrapIntArray, + nme.wrapLongArray, + nme.wrapFloatArray, + nme.wrapDoubleArray, + nme.wrapBooleanArray, + nme.wrapUnitArray, + nme.genericWrapArray).map(getMemberMethod(PredefModule, _)).toSet + + def unapply(tree: Apply): Option[Tree] = tree match { + case Apply(wrapArray_?, List(wrapped)) + if isWrapArray(wrapArray_?.symbol) => + Some(wrapped) + case _ => + None + } + } + + // Synthesizers for raw JS functions --------------------------------------- + + /** Try and gen and record JS code for an anonymous function class. + * + * Returns true if the class could be rewritten that way, false otherwise. + * + * We make the following assumptions on the form of such classes: + * - It is an anonymous function + * - Includes being anonymous, final, and having exactly one constructor + * - It is not a PartialFunction + * - It has no field other than param accessors + * - It has exactly one constructor + * - It has exactly one non-bridge method apply if it is not specialized, + * or a method apply$...$sp and a forwarder apply if it is specialized. + * - As a precaution: it is synthetic + * + * From a class looking like this: + * + * final class <anon>(outer, capture1, ..., captureM) extends AbstractionFunctionN[...] { + * def apply(param1, ..., paramN) = { + * <body> + * } + * } + * new <anon>(o, c1, ..., cM) + * + * we generate a function maker that emits: + * + * lambda<o, c1, ..., cM>[notype]( + * outer, capture1, ..., captureM, param1, ..., paramN) { + * <body> + * } + * + * so that, at instantiation point, we can write: + * + * new AnonFunctionN(functionMaker(this, captured1, ..., capturedM)) + * + * Trickier things apply when the function is specialized. + */ + private def tryGenAndRecordAnonFunctionClass(cd: ClassDef): Boolean = { + implicit val pos = cd.pos + val sym = cd.symbol + assert(sym.isAnonymousFunction, + s"tryGenAndRecordAnonFunctionClass called with non-anonymous function $cd") + + withScopedVars( + currentClassInfoBuilder := new ClassInfoBuilder(sym.asClass), + currentClassSym := sym + ) { + val (functionMakerBase, arity) = + tryGenAndRecordAnonFunctionClassGeneric(cd) { msg => + return false + } + val functionMaker = { capturedArgs: List[js.Tree] => + JSFunctionToScala(functionMakerBase(capturedArgs), arity) + } + + translatedAnonFunctions += + sym -> (functionMaker, currentClassInfoBuilder.get) + } + true + } + + /** Constructor and extractor object for a tree that converts a JavaScript + * function into a Scala function. + */ + private object JSFunctionToScala { + private val AnonFunPrefScala = + "scala.scalajs.runtime.AnonFunction" + private val AnonFunPrefJS = + "sjsr_AnonFunction" + + def apply(jsFunction: js.Tree, arity: Int)( + implicit pos: Position): js.Tree = { + val clsSym = getRequiredClass(AnonFunPrefScala + arity) + val ctor = clsSym.tpe.member(nme.CONSTRUCTOR) + genNew(clsSym, ctor, List(jsFunction)) + } + + def unapply(tree: js.New): Option[(js.Tree, Int)] = tree match { + case js.New(jstpe.ClassType(wrapperName), _, List(fun)) + if wrapperName.startsWith(AnonFunPrefJS) => + val arityStr = wrapperName.substring(AnonFunPrefJS.length) + try { + Some((fun, arityStr.toInt)) + } catch { + case e: NumberFormatException => None + } + + case _ => + None + } + } + + /** Gen and record JS code for a raw JS function class. + * + * This is called when emitting a ClassDef that represents an anonymous + * class extending `js.FunctionN`. These are generated by the SAM + * synthesizer when the target type is a `js.FunctionN`. Since JS + * functions are not classes, we deconstruct the ClassDef, then + * reconstruct it to be a genuine Closure. + * + * Compared to `tryGenAndRecordAnonFunctionClass()`, this function must + * always succeed, because we really cannot afford keeping them as + * anonymous classes. The good news is that it can do so, because the + * body of SAM lambdas is hoisted in the enclosing class. Hence, the + * apply() method is just a forwarder to calling that hoisted method. + * + * From a class looking like this: + * + * final class <anon>(outer, capture1, ..., captureM) extends js.FunctionN[...] { + * def apply(param1, ..., paramN) = { + * outer.lambdaImpl(param1, ..., paramN, capture1, ..., captureM) + * } + * } + * new <anon>(o, c1, ..., cM) + * + * we generate a function maker that emits: + * + * lambda<o, c1, ..., cM>[notype]( + * outer, capture1, ..., captureM, param1, ..., paramN) { + * outer.lambdaImpl(param1, ..., paramN, capture1, ..., captureM) + * } + * + * The function maker is recorded in `translatedAnonFunctions` to be + * fetched later by the translation for New. + */ + def genAndRecordRawJSFunctionClass(cd: ClassDef): Unit = { + val sym = cd.symbol + assert(isRawJSFunctionDef(sym), + s"genAndRecordRawJSFunctionClass called with non-JS function $cd") + + withScopedVars( + currentClassInfoBuilder := new ClassInfoBuilder(sym.asClass), + currentClassSym := sym + ) { + val (functionMaker, _) = + tryGenAndRecordAnonFunctionClassGeneric(cd) { msg => + abort(s"Could not generate raw function maker for JS function: $msg") + } + + translatedAnonFunctions += + sym -> (functionMaker, currentClassInfoBuilder.get) + } + } + + /** Code common to tryGenAndRecordAnonFunctionClass and + * genAndRecordRawJSFunctionClass. + */ + private def tryGenAndRecordAnonFunctionClassGeneric(cd: ClassDef)( + fail: (=> String) => Nothing): (List[js.Tree] => js.Tree, Int) = { + implicit val pos = cd.pos + val sym = cd.symbol + + // First checks + + if (sym.isSubClass(PartialFunctionClass)) + fail(s"Cannot rewrite PartialFunction $cd") + if (instantiatedAnonFunctions contains sym) { + // when the ordering we're given is evil (it happens!) + fail(s"Abort function rewrite because it was already instantiated: $cd") + } + + // First step: find the apply method def, and collect param accessors + + var paramAccessors: List[Symbol] = Nil + var applyDef: DefDef = null + + def gen(tree: Tree): Unit = { + tree match { + case EmptyTree => () + case Template(_, _, body) => body foreach gen + case vd @ ValDef(mods, name, tpt, rhs) => + val fsym = vd.symbol + if (!fsym.isParamAccessor) + fail(s"Found field $fsym which is not a param accessor in anon function $cd") + + if (fsym.isPrivate) { + paramAccessors ::= fsym + } else { + // Uh oh ... an inner something will try to access my fields + fail(s"Found a non-private field $fsym in $cd") + } + case dd: DefDef => + val ddsym = dd.symbol + if (ddsym.isClassConstructor) { + if (!ddsym.isPrimaryConstructor) + fail(s"Non-primary constructor $ddsym in anon function $cd") + } else { + val name = dd.name.toString + if (name == "apply" || (ddsym.isSpecialized && name.startsWith("apply$"))) { + if ((applyDef eq null) || ddsym.isSpecialized) + applyDef = dd + } else { + // Found a method we cannot encode in the rewriting + fail(s"Found a non-apply method $ddsym in $cd") + } + } + case _ => + fail("Illegal tree in gen of genAndRecordAnonFunctionClass(): " + tree) + } + } + gen(cd.impl) + paramAccessors = paramAccessors.reverse // preserve definition order + + if (applyDef eq null) + fail(s"Did not find any apply method in anon function $cd") + + withNewLocalNameScope { + // Second step: build the list of useful constructor parameters + + val ctorParams = sym.primaryConstructor.tpe.params + + if (paramAccessors.size != ctorParams.size && + !(paramAccessors.size == ctorParams.size-1 && + ctorParams.head.unexpandedName == jsnme.arg_outer)) { + fail( + s"Have param accessors $paramAccessors but "+ + s"ctor params $ctorParams in anon function $cd") + } + + val hasUnusedOuterCtorParam = paramAccessors.size != ctorParams.size + val usedCtorParams = + if (hasUnusedOuterCtorParam) ctorParams.tail + else ctorParams + val ctorParamDefs = usedCtorParams map { p => + // in the apply method's context + js.ParamDef(encodeLocalSym(p)(p.pos), toIRType(p.tpe), + mutable = false)(p.pos) + } + + // Third step: emit the body of the apply method def + + val (applyMethod, methodInfoBuilder) = withScopedVars( + paramAccessorLocals := (paramAccessors zip ctorParamDefs).toMap, + tryingToGenMethodAsJSFunction := true + ) { + try { + genMethodWithInfoBuilder(applyDef).getOrElse( + abort(s"Oops, $applyDef did not produce a method")) + } catch { + case e: CancelGenMethodAsJSFunction => + fail(e.getMessage) + } + } + + withScopedVars( + currentMethodInfoBuilder := methodInfoBuilder + ) { + // Fourth step: patch the body to unbox parameters and box result + + val js.MethodDef(_, params, _, body) = applyMethod + val (patchedParams, patchedBody) = + patchFunBodyWithBoxes(applyDef.symbol, params, body) + + // Fifth step: build the function maker + + val isThisFunction = JSThisFunctionClasses.exists(sym isSubClass _) + assert(!isThisFunction || patchedParams.nonEmpty, + s"Empty param list in ThisFunction: $cd") + + val functionMaker = { capturedArgs0: List[js.Tree] => + val capturedArgs = + if (hasUnusedOuterCtorParam) capturedArgs0.tail + else capturedArgs0 + assert(capturedArgs.size == ctorParamDefs.size) + + if (isThisFunction) { + val thisParam :: actualParams = patchedParams + js.Closure( + ctorParamDefs, + actualParams, + js.Block( + js.VarDef(thisParam.name, thisParam.ptpe, mutable = false, + js.This()(thisParam.ptpe)(thisParam.pos))(thisParam.pos), + patchedBody), + capturedArgs) + } else { + js.Closure(ctorParamDefs, patchedParams, patchedBody, capturedArgs) + } + } + + val arity = params.size + + (functionMaker, arity) + } + } + } + + /** Generate JS code for an anonymous function + * + * Anonymous functions survive until the backend only under + * -Ydelambdafy:method + * and when they do, their body is always of the form + * EnclosingClass.this.someMethod(arg1, ..., argN, capture1, ..., captureM) + * where argI are the formal arguments of the lambda, and captureI are + * local variables or the enclosing def. + * + * We translate them by instantiating scala.scalajs.runtime.AnonFunctionN + * with a JS closure: + * + * new ScalaJS.c.sjsr_AnonFunctionN().init___xyz( + * lambda<this, capture1, ..., captureM>( + * _this, capture1, ..., captureM, arg1, ..., argN) { + * _this.someMethod(arg1, ..., argN, capture1, ..., captureM) + * } + * ) + * + * In addition, input params are unboxed before use, and the result of + * someMethod() is boxed back. + */ + private def genAnonFunction(originalFunction: Function): js.Tree = { + implicit val pos = originalFunction.pos + val Function(paramTrees, Apply( + targetTree @ Select(receiver, _), allArgs0)) = originalFunction + + val target = targetTree.symbol + val params = paramTrees.map(_.symbol) + + val allArgs = allArgs0 map genExpr + + val formalArgs = params map { p => + js.ParamDef(encodeLocalSym(p)(p.pos), toIRType(p.tpe), + mutable = false)(p.pos) + } + + val isInImplClass = target.owner.isImplClass + + def makeCaptures(actualCaptures: List[js.Tree]) = { + (actualCaptures map { c => (c: @unchecked) match { + case js.VarRef(ident, _) => + (js.ParamDef(ident, c.tpe, mutable = false)(c.pos), + js.VarRef(ident, false)(c.tpe)(c.pos)) + }}).unzip + } + + val (allFormalCaptures, body, allActualCaptures) = if (!isInImplClass) { + val thisActualCapture = genExpr(receiver) + val thisFormalCapture = js.ParamDef( + freshLocalIdent("this")(receiver.pos), + thisActualCapture.tpe, mutable = false)(receiver.pos) + val thisCaptureArg = thisFormalCapture.ref + val (actualArgs, actualCaptures) = allArgs.splitAt(formalArgs.size) + val (formalCaptures, captureArgs) = makeCaptures(actualCaptures) + val body = genApplyMethod(thisCaptureArg, receiver.tpe, target, + actualArgs ::: captureArgs) + + (thisFormalCapture :: formalCaptures, + body, thisActualCapture :: actualCaptures) + } else { + val (thisActualCapture :: actualArgs, actualCaptures) = + allArgs.splitAt(formalArgs.size+1) + val (thisFormalCapture :: formalCaptures, thisCaptureArg :: captureArgs) = + makeCaptures(thisActualCapture :: actualCaptures) + val body = genTraitImplApply(target, + thisCaptureArg :: actualArgs ::: captureArgs) + + (thisFormalCapture :: formalCaptures, + body, thisActualCapture :: actualCaptures) + } + + val (patchedFormalArgs, patchedBody) = + patchFunBodyWithBoxes(target, formalArgs, body) + val closure = js.Closure( + allFormalCaptures, + patchedFormalArgs, + patchedBody, + allActualCaptures) + + JSFunctionToScala(closure, params.size) + } + + private def patchFunBodyWithBoxes(methodSym: Symbol, + params: List[js.ParamDef], body: js.Tree)( + implicit pos: Position): (List[js.ParamDef], js.Tree) = { + val methodType = enteringPhase(currentRun.posterasurePhase)(methodSym.tpe) + + val (patchedParams, paramsLocal) = (for { + (param, paramSym) <- params zip methodType.params + } yield { + val paramTpe = enteringPhase(currentRun.posterasurePhase)(paramSym.tpe) + val paramName = param.name + val js.Ident(name, origName) = paramName + val newOrigName = origName.getOrElse(name) + val newNameIdent = freshLocalIdent(newOrigName)(paramName.pos) + val patchedParam = js.ParamDef(newNameIdent, jstpe.AnyType, + mutable = false)(param.pos) + val paramLocal = js.VarDef(paramName, param.ptpe, mutable = false, + fromAny(patchedParam.ref, paramTpe)) + (patchedParam, paramLocal) + }).unzip + + val patchedBody = js.Block( + paramsLocal :+ ensureBoxed(body, methodType.resultType)) + + (patchedParams, patchedBody) + } + + // Utilities --------------------------------------------------------------- + + /** Generate a literal "zero" for the requested type */ + def genZeroOf(tpe: Type)(implicit pos: Position): js.Tree = toTypeKind(tpe) match { + case VOID => abort("Cannot call genZeroOf(VOID)") + case BOOL => js.BooleanLiteral(false) + case LONG => js.LongLiteral(0L) + case INT(_) => js.IntLiteral(0) + case FloatKind => js.FloatLiteral(0.0f) + case DoubleKind => js.DoubleLiteral(0.0) + case _ => js.Null() + } + + /** Generate loading of a module value + * Can be given either the module symbol, or its module class symbol. + */ + def genLoadModule(sym0: Symbol)(implicit pos: Position): js.Tree = { + require(sym0.isModuleOrModuleClass, + "genLoadModule called with non-module symbol: " + sym0) + val sym1 = if (sym0.isModule) sym0.moduleClass else sym0 + val sym = // redirect all static methods of String to RuntimeString + if (sym1 == StringModule) RuntimeStringModule.moduleClass + else sym1 + + val isGlobalScope = sym.tpe.typeSymbol isSubClass JSGlobalScopeClass + + if (isGlobalScope) genLoadGlobal() + else if (isRawJSType(sym.tpe)) genPrimitiveJSModule(sym) + else { + if (!foreignIsImplClass(sym)) + currentMethodInfoBuilder.accessesModule(sym) + js.LoadModule(jstpe.ClassType(encodeClassFullName(sym))) + } + } + + /** Gen JS code to load the global scope. */ + private def genLoadGlobal()(implicit pos: Position): js.Tree = + js.JSBracketSelect(js.JSEnvInfo(), js.StringLiteral("global")) + + /** Generate access to a static member */ + private def genStaticMember(sym: Symbol)(implicit pos: Position) = { + /* Actually, there is no static member in Scala.js. If we come here, that + * is because we found the symbol in a Java-emitted .class in the + * classpath. But the corresponding implementation in Scala.js will + * actually be a val in the companion module. + * We cannot use the .class files produced by our reimplementations of + * these classes (in which the symbol would be a Scala accessor) because + * that crashes the rest of scalac (at least for some choice symbols). + * Hence we cheat here. + */ + import scalaPrimitives._ + import jsPrimitives._ + if (isPrimitive(sym)) { + getPrimitive(sym) match { + case UNITVAL => js.Undefined() + case UNITTYPE => genClassConstant(UnitTpe) + } + } else { + val instance = genLoadModule(sym.owner) + val method = encodeStaticMemberSym(sym) + currentMethodInfoBuilder.callsMethod(sym.owner, method) + js.Apply(instance, method, Nil)(toIRType(sym.tpe)) + } + } + + /** Generate a Class[_] value (e.g. coming from classOf[T]) */ + private def genClassConstant(tpe: Type)(implicit pos: Position): js.Tree = { + val refType = toReferenceType(tpe) + currentMethodInfoBuilder.accessesClassData(refType) + js.ClassOf(refType) + } + } + + /** Tests whether the given type represents a raw JavaScript type, + * i.e., whether it extends scala.scalajs.js.Any. + */ + def isRawJSType(tpe: Type): Boolean = + tpe.typeSymbol.annotations.find(_.tpe =:= RawJSTypeAnnot.tpe).isDefined + + /** Test whether `sym` is the symbol of a raw JS function definition */ + private def isRawJSFunctionDef(sym: Symbol): Boolean = + sym.isAnonymousClass && AllJSFunctionClasses.exists(sym isSubClass _) + + private def isRawJSCtorDefaultParam(sym: Symbol) = { + sym.hasFlag(reflect.internal.Flags.DEFAULTPARAM) && + sym.owner.isModuleClass && + isRawJSType(patchedLinkedClassOfClass(sym.owner).tpe) && + nme.defaultGetterToMethod(sym.name) == nme.CONSTRUCTOR + } + + private def patchedLinkedClassOfClass(sym: Symbol): Symbol = { + /* Work around a bug of scalac with linkedClassOfClass where package + * objects are involved (the companion class would somehow exist twice + * in the scope, making an assertion fail in Symbol.suchThat). + * Basically this inlines linkedClassOfClass up to companionClass, + * then replaces the `suchThat` by a `filter` and `head`. + */ + val flatOwnerInfo = { + // inline Symbol.flatOwnerInfo because it is protected + if (sym.needsFlatClasses) + sym.info + sym.owner.rawInfo + } + val result = flatOwnerInfo.decl(sym.name).filter(_ isCoDefinedWith sym) + if (!result.isOverloaded) result + else result.alternatives.head + } + + private def isStringType(tpe: Type): Boolean = + tpe.typeSymbol == StringClass + + private def isLongType(tpe: Type): Boolean = + tpe.typeSymbol == LongClass + + private lazy val BoxedBooleanClass = boxedClass(BooleanClass) + private lazy val BoxedByteClass = boxedClass(ByteClass) + private lazy val BoxedShortClass = boxedClass(ShortClass) + private lazy val BoxedIntClass = boxedClass(IntClass) + private lazy val BoxedLongClass = boxedClass(LongClass) + private lazy val BoxedFloatClass = boxedClass(FloatClass) + private lazy val BoxedDoubleClass = boxedClass(DoubleClass) + + private lazy val NumberClass = requiredClass[java.lang.Number] + + private lazy val HijackedNumberClasses = + Seq(BoxedByteClass, BoxedShortClass, BoxedIntClass, BoxedLongClass, + BoxedFloatClass, BoxedDoubleClass) + private lazy val HijackedBoxedClasses = + Seq(BoxedUnitClass, BoxedBooleanClass) ++ HijackedNumberClasses + + protected lazy val isHijackedBoxedClass: Set[Symbol] = + HijackedBoxedClasses.toSet + + private lazy val InlineAnnotationClass = requiredClass[scala.inline] + + private def isMaybeJavaScriptException(tpe: Type) = + JavaScriptExceptionClass isSubClass tpe.typeSymbol + + /** Get JS name of Symbol if it was specified with JSName annotation, or + * infers a default from the Scala name. */ + def jsNameOf(sym: Symbol): String = + sym.getAnnotation(JSNameAnnotation).flatMap(_.stringArg(0)).getOrElse( + sym.unexpandedName.decoded) + + def isStaticModule(sym: Symbol): Boolean = + sym.isModuleClass && !sym.isImplClass && !sym.isLifted +} diff --git a/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/GenJSExports.scala b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/GenJSExports.scala new file mode 100644 index 0000000..92dc26b --- /dev/null +++ b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/GenJSExports.scala @@ -0,0 +1,751 @@ +/* Scala.js compiler + * Copyright 2013 LAMP/EPFL + * @author Sébastien Doeraene + */ + +package scala.scalajs.compiler + +import scala.collection.mutable + +import scala.tools.nsc._ +import scala.math.PartialOrdering +import scala.reflect.internal.Flags + +import scala.scalajs.ir +import ir.{Trees => js, Types => jstpe} + +import util.ScopedVar +import ScopedVar.withScopedVars + +/** Generation of exports for JavaScript + * + * @author Sébastien Doeraene + */ +trait GenJSExports extends SubComponent { self: GenJSCode => + import global._ + import jsAddons._ + import definitions._ + import jsDefinitions._ + + trait JSExportsPhase { this: JSCodePhase => + + /** + * Generate exporter methods for a class + * @param classSym symbol of class we export for + * @param decldExports symbols exporter methods that have been encountered in + * the class' tree. This is not the same as classSym.info.delcs since + * inherited concrete methods from traits should be in this param, too + */ + def genMemberExports( + classSym: Symbol, + decldExports: List[Symbol]): List[js.Tree] = { + + val newlyDecldExports = decldExports.filterNot { isOverridingExport _ } + val newlyDecldExportNames = + newlyDecldExports.map(_.name.toTermName).toList.distinct + + newlyDecldExportNames map { genMemberExport(classSym, _) } + } + + def genConstructorExports(classSym: Symbol): List[js.ConstructorExportDef] = { + val constructors = classSym.tpe.member(nme.CONSTRUCTOR).alternatives + + // Generate exports from constructors and their annotations + val ctorExports = for { + ctor <- constructors + exp <- jsInterop.exportsOf(ctor) + } yield (exp, ctor) + + val exports = for { + (jsName, specs) <- ctorExports.groupBy(_._1.jsName) // group by exported name + } yield { + val (namedExports, normalExports) = specs.partition(_._1.isNamed) + + val normalCtors = normalExports.map(s => ExportedSymbol(s._2)) + val namedCtors = for { + (exp, ctor) <- namedExports + } yield { + implicit val pos = exp.pos + ExportedBody(List(JSAnyTpe), + genNamedExporterBody(ctor, genFormalArg(1).ref), + nme.CONSTRUCTOR.toString, pos) + } + + val ctors = normalCtors ++ namedCtors + + implicit val pos = ctors.head.pos + + val js.MethodDef(_, args, _, body) = + withNewLocalNameScope(genExportMethod(ctors, jsName)) + + js.ConstructorExportDef(jsName, args, body) + } + + exports.toList + } + + def genModuleAccessorExports(classSym: Symbol): List[js.ModuleExportDef] = { + for { + exp <- jsInterop.exportsOf(classSym) + } yield { + implicit val pos = exp.pos + + if (exp.isNamed) + reporter.error(pos, "You may not use @JSNamedExport on an object") + + js.ModuleExportDef(exp.jsName) + } + } + + /** Generate the exporter proxy for a named export */ + def genNamedExporterDef(dd: DefDef): js.MethodDef = { + implicit val pos = dd.pos + + val sym = dd.symbol + + val Block(Apply(fun, _) :: Nil, _) = dd.rhs + val trgSym = fun.symbol + + val inArg = + js.ParamDef(js.Ident("namedParams"), jstpe.AnyType, mutable = false) + val inArgRef = inArg.ref + + val methodIdent = encodeMethodSym(sym) + + withScopedVars( + currentMethodInfoBuilder := + currentClassInfoBuilder.addMethod(methodIdent.name) + ) { + js.MethodDef(methodIdent, List(inArg), toIRType(sym.tpe.resultType), + genNamedExporterBody(trgSym, inArg.ref))(None) + } + } + + private def genNamedExporterBody(trgSym: Symbol, inArg: js.Tree)( + implicit pos: Position) = { + + if (hasRepeatedParam(trgSym)) { + reporter.error(pos, + "You may not name-export a method with a *-parameter") + } + + val jsArgs = for { + (pSym, index) <- trgSym.info.params.zipWithIndex + } yield { + val rhs = js.JSBracketSelect(inArg, + js.StringLiteral(pSym.name.decoded)) + js.VarDef(js.Ident("namedArg$" + index), jstpe.AnyType, + mutable = false, rhs = rhs) + } + + val jsArgRefs = jsArgs.map(_.ref) + + // Generate JS code to prepare arguments (default getters and unboxes) + val jsArgPrep = genPrepareArgs(jsArgRefs, trgSym) + val jsResult = genResult(trgSym, jsArgPrep.map(_.ref)) + + js.Block(jsArgs ++ jsArgPrep :+ jsResult) + } + + private def genMemberExport(classSym: Symbol, name: TermName): js.Tree = { + val alts = classSym.info.member(name).alternatives + + assert(!alts.isEmpty, + s"Ended up with no alternatives for ${classSym.fullName}::$name. " + + s"Original set was ${alts} with types ${alts.map(_.tpe)}") + + val (jsName, isProp) = jsInterop.jsExportInfo(name) + + // Check if we have a conflicting export of the other kind + val conflicting = + classSym.info.member(jsInterop.scalaExportName(jsName, !isProp)) + + if (conflicting != NoSymbol) { + val kind = if (isProp) "property" else "method" + val alts = conflicting.alternatives + + reporter.error(alts.head.pos, + s"Exported $kind $jsName conflicts with ${alts.head.fullName}") + } + + withNewLocalNameScope { + if (isProp) + genExportProperty(alts, jsName) + else + genExportMethod(alts.map(ExportedSymbol), jsName) + } + } + + private def genExportProperty(alts: List[Symbol], jsName: String) = { + assert(!alts.isEmpty) + implicit val pos = alts.head.pos + + // Separate getters and setters. Somehow isJSGetter doesn't work here. Hence + // we just check the parameter list length. + val (getter, setters) = alts.partition(_.tpe.params.isEmpty) + + // if we have more than one getter, something went horribly wrong + assert(getter.size <= 1, + s"Found more than one getter to export for name ${jsName}.") + + val getTree = + if (getter.isEmpty) js.EmptyTree + else genApplyForSym(getter.head) + + val setTree = + if (setters.isEmpty) js.EmptyTree + else genExportSameArgc(setters.map(ExportedSymbol), 0) // we only have 1 argument + + js.PropertyDef(js.StringLiteral(jsName), getTree, genFormalArg(1), setTree) + } + + /** generates the exporter function (i.e. exporter for non-properties) for + * a given name */ + private def genExportMethod(alts0: List[Exported], jsName: String) = { + assert(alts0.nonEmpty, + "need at least one alternative to generate exporter method") + + implicit val pos = alts0.head.pos + + val alts = { + // toString() is always exported. We might need to add it here + // to get correct overloading. + if (jsName == "toString" && alts0.forall(_.params.nonEmpty)) + ExportedSymbol(Object_toString) :: alts0 + else + alts0 + } + + // Factor out methods with variable argument lists. Note that they can + // only be at the end of the lists as enforced by PrepJSExports + val (varArgMeths, normalMeths) = alts.partition(_.hasRepeatedParam) + + // Highest non-repeated argument count + val maxArgc = ( + // We have argc - 1, since a repeated parameter list may also be empty + // (unlike a normal parameter) + varArgMeths.map(_.params.size - 1) ++ + normalMeths.map(_.params.size) + ).max + + val formalArgs = genFormalArgs(maxArgc) + + // Calculates possible arg counts for normal method + def argCounts(ex: Exported) = ex match { + case ExportedSymbol(sym) => + val params = sym.tpe.params + // Find default param + val dParam = params.indexWhere { _.hasFlag(Flags.DEFAULTPARAM) } + if (dParam == -1) Seq(params.size) + else dParam to params.size + case ex: ExportedBody => + List(ex.params.size) + } + + // Generate tuples (argc, method) + val methodArgCounts = { + // Normal methods + for { + method <- normalMeths + argc <- argCounts(method) + } yield (argc, method) + } ++ { + // Repeated parameter methods + for { + method <- varArgMeths + argc <- method.params.size - 1 to maxArgc + } yield (argc, method) + } + + // Create a map: argCount -> methods (methods may appear multiple times) + val methodByArgCount = + methodArgCounts.groupBy(_._1).mapValues(_.map(_._2).toSet) + + // Create tuples: (methods, argCounts). This will be the cases we generate + val caseDefinitions = + methodByArgCount.groupBy(_._2).mapValues(_.keySet) + + // Verify stuff about caseDefinitions + assert({ + val argcs = caseDefinitions.values.flatten.toList + argcs == argcs.distinct && + argcs.forall(_ <= maxArgc) + }, "every argc should appear only once and be lower than max") + + // Generate a case block for each (methods, argCounts) tuple + val cases = for { + (methods, argcs) <- caseDefinitions + if methods.nonEmpty && argcs.nonEmpty + + // exclude default case we're generating anyways for varargs + if methods != varArgMeths.toSet + + // body of case to disambiguates methods with current count + caseBody = + genExportSameArgc(methods.toList, 0, Some(argcs.min)) + + // argc in reverse order + argcList = argcs.toList.sortBy(- _) + } yield (argcList.map(js.IntLiteral(_)), caseBody) + + val hasVarArg = varArgMeths.nonEmpty + + def defaultCase = { + if (!hasVarArg) + genThrowTypeError() + else + genExportSameArgc(varArgMeths, 0) + } + + val body = { + if (cases.isEmpty) + defaultCase + else if (cases.size == 1 && !hasVarArg) + cases.head._2 + else { + js.Match( + js.Unbox(js.JSBracketSelect( + js.VarRef(js.Ident("arguments"), false)(jstpe.AnyType), + js.StringLiteral("length")), + 'I'), + cases.toList, defaultCase)(jstpe.AnyType) + } + } + + js.MethodDef(js.StringLiteral(jsName), formalArgs, jstpe.AnyType, body)(None) + } + + /** + * Resolve method calls to [[alts]] while assuming they have the same + * parameter count. + * @param alts Alternative methods + * @param paramIndex Index where to start disambiguation + * @param maxArgc only use that many arguments + */ + private def genExportSameArgc(alts: List[Exported], + paramIndex: Int, maxArgc: Option[Int] = None): js.Tree = { + + implicit val pos = alts.head.pos + + if (alts.size == 1) + alts.head.body + else if (maxArgc.exists(_ <= paramIndex) || + !alts.exists(_.params.size > paramIndex)) { + // We reach here in three cases: + // 1. The parameter list has been exhausted + // 2. The optional argument count restriction has triggered + // 3. We only have (more than once) repeated parameters left + // Therefore, we should fail + reporter.error(pos, + s"""Cannot disambiguate overloads for exported method ${alts.head.name} with types + | ${alts.map(_.typeInfo).mkString("\n ")}""".stripMargin) + js.Undefined() + } else { + + val altsByTypeTest = groupByWithoutHashCode(alts) { + case ExportedSymbol(alt) => + // get parameter type while resolving repeated params + val tpe = enteringPhase(currentRun.uncurryPhase) { + val ps = alt.paramss.flatten + if (ps.size <= paramIndex || isRepeated(ps(paramIndex))) { + assert(isRepeated(ps.last)) + repeatedToSingle(ps.last.tpe) + } else { + enteringPhase(currentRun.posterasurePhase) { + ps(paramIndex).tpe + } + } + } + + typeTestForTpe(tpe) + + case ex: ExportedBody => + typeTestForTpe(ex.params(paramIndex)) + } + + if (altsByTypeTest.size == 1) { + // Testing this parameter is not doing any us good + genExportSameArgc(alts, paramIndex+1, maxArgc) + } else { + // Sort them so that, e.g., isInstanceOf[String] + // comes before isInstanceOf[Object] + val sortedAltsByTypeTest = topoSortDistinctsBy( + altsByTypeTest)(_._1)(RTTypeTest.Ordering) + + val defaultCase = genThrowTypeError() + + sortedAltsByTypeTest.foldRight[js.Tree](defaultCase) { (elem, elsep) => + val (typeTest, subAlts) = elem + implicit val pos = subAlts.head.pos + + val param = genFormalArg(paramIndex+1) + val genSubAlts = genExportSameArgc(subAlts, paramIndex+1, maxArgc) + + def hasDefaultParam = subAlts.exists { + case ExportedSymbol(p) => + val params = p.tpe.params + params.size > paramIndex && + params(paramIndex).hasFlag(Flags.DEFAULTPARAM) + case _: ExportedBody => false + } + + val optCond = typeTest match { + case HijackedTypeTest(boxedClassName, _) => + Some(js.IsInstanceOf(param.ref, jstpe.ClassType(boxedClassName))) + + case InstanceOfTypeTest(tpe) => + Some(genIsInstanceOf(param.ref, tpe)) + + case NoTypeTest => + None + } + + optCond.fold[js.Tree] { + genSubAlts // note: elsep is discarded, obviously + } { cond => + val condOrUndef = if (!hasDefaultParam) cond else { + js.If(cond, js.BooleanLiteral(true), + js.BinaryOp(js.BinaryOp.===, param.ref, js.Undefined()))( + jstpe.BooleanType) + } + js.If(condOrUndef, genSubAlts, elsep)(jstpe.AnyType) + } + } + } + } + } + + /** + * Generate a call to the method [[sym]] while using the formalArguments + * and potentially the argument array. Also inserts default parameters if + * required. + */ + private def genApplyForSym(sym: Symbol): js.Tree = { + implicit val pos = sym.pos + + // the (single) type of the repeated parameter if any + val repeatedTpe = enteringPhase(currentRun.uncurryPhase) { + for { + param <- sym.paramss.flatten.lastOption + if isRepeated(param) + } yield repeatedToSingle(param.tpe) + } + + val normalArgc = sym.tpe.params.size - + (if (repeatedTpe.isDefined) 1 else 0) + + // optional repeated parameter list + val jsVarArg = repeatedTpe map { tpe => + // Copy arguments that go to vararg into an array, put it in a wrapper + + val countIdent = freshLocalIdent("count") + val count = js.VarRef(countIdent, mutable = false)(jstpe.IntType) + + val counterIdent = freshLocalIdent("i") + val counter = js.VarRef(counterIdent, mutable = true)(jstpe.IntType) + + val arrayIdent = freshLocalIdent("varargs") + val array = js.VarRef(arrayIdent, mutable = false)(jstpe.AnyType) + + val arguments = js.VarRef(js.Ident("arguments"), + mutable = false)(jstpe.AnyType) + val argLen = js.Unbox( + js.JSBracketSelect(arguments, js.StringLiteral("length")), 'I') + val argOffset = js.IntLiteral(normalArgc) + + val jsArrayCtor = + js.JSBracketSelect( + js.JSBracketSelect(js.JSEnvInfo(), js.StringLiteral("global")), + js.StringLiteral("Array")) + + js.Block( + // var i = 0 + js.VarDef(counterIdent, jstpe.IntType, mutable = true, + rhs = js.IntLiteral(0)), + // val count = arguments.length - <normalArgc> + js.VarDef(countIdent, jstpe.IntType, mutable = false, + rhs = js.BinaryOp(js.BinaryOp.Int_-, argLen, argOffset)), + // val varargs = new Array(count) + js.VarDef(arrayIdent, jstpe.AnyType, mutable = false, + rhs = js.JSNew(jsArrayCtor, List(count))), + // while (i < count) + js.While(js.BinaryOp(js.BinaryOp.Num_<, counter, count), js.Block( + // varargs[i] = arguments[<normalArgc> + i]; + js.Assign( + js.JSBracketSelect(array, counter), + js.JSBracketSelect(arguments, + js.BinaryOp(js.BinaryOp.Int_+, argOffset, counter))), + // i = i + 1 (++i won't work, desugar eliminates it) + js.Assign(counter, js.BinaryOp(js.BinaryOp.Int_+, + counter, js.IntLiteral(1))) + )), + // new WrappedArray(varargs) + genNew(WrappedArrayClass, WrappedArray_ctor, List(array)) + ) + } + + // normal arguments + val jsArgs = genFormalArgs(normalArgc) + val jsArgRefs = jsArgs.map(_.ref) + + // Generate JS code to prepare arguments (default getters and unboxes) + val jsArgPrep = genPrepareArgs(jsArgRefs, sym) + val jsResult = genResult(sym, jsArgPrep.map(_.ref) ++ jsVarArg) + + js.Block(jsArgPrep :+ jsResult) + } + + /** Generate the necessary JavaScript code to prepare the arguments of an + * exported method (unboxing and default parameter handling) + */ + private def genPrepareArgs(jsArgs: List[js.VarRef], sym: Symbol)( + implicit pos: Position): List[js.VarDef] = { + + val result = new mutable.ListBuffer[js.VarDef] + + val funTpe = enteringPhase(currentRun.posterasurePhase)(sym.tpe) + for { + (jsArg, (param, i)) <- jsArgs zip funTpe.params.zipWithIndex + } yield { + // Code to verify the type of the argument (if it is defined) + val verifiedArg = { + val tpePosterasure = + enteringPhase(currentRun.posterasurePhase)(param.tpe) + tpePosterasure match { + case tpe if isPrimitiveValueType(tpe) => + val unboxed = makePrimitiveUnbox(jsArg, tpe) + // Ensure we don't convert null to a primitive value type + js.If(js.BinaryOp(js.BinaryOp.===, jsArg, js.Null()), + genThrowTypeError(s"Found null, expected $tpe"), + unboxed)(unboxed.tpe) + case tpe: ErasedValueType => + val boxedClass = tpe.valueClazz + val unboxMethod = boxedClass.derivedValueClassUnbox + genApplyMethod( + genAsInstanceOf(jsArg, tpe), + boxedClass, unboxMethod, Nil) + case tpe => + genAsInstanceOf(jsArg, tpe) + } + } + + // If argument is undefined and there is a default getter, call it + val verifiedOrDefault = if (param.hasFlag(Flags.DEFAULTPARAM)) { + js.If(js.BinaryOp(js.BinaryOp.===, jsArg, js.Undefined()), { + val trgSym = { + if (sym.isClassConstructor) sym.owner.companionModule.moduleClass + else sym.owner + } + val defaultGetter = trgSym.tpe.member( + nme.defaultGetterName(sym.name, i+1)) + + assert(defaultGetter.exists, + s"need default getter for method ${sym.fullName}") + assert(!defaultGetter.isOverloaded) + + val trgTree = { + if (sym.isClassConstructor) genLoadModule(trgSym) + else js.This()(encodeClassType(trgSym)) + } + + // Pass previous arguments to defaultGetter + genApplyMethod(trgTree, trgSym, defaultGetter, + result.take(defaultGetter.tpe.params.size).toList.map(_.ref)) + }, { + // Otherwise, unbox the argument + verifiedArg + })(verifiedArg.tpe) + } else { + // Otherwise, it is always the unboxed argument + verifiedArg + } + + result += + js.VarDef(js.Ident("prep"+jsArg.ident.name, jsArg.ident.originalName), + verifiedOrDefault.tpe, mutable = false, verifiedOrDefault) + } + + result.toList + } + + /** Generate the final forwarding call to the exported method. + * Attention: This method casts the arguments to the right type. The IR + * checker will not detect if you pass in a wrongly typed argument. + */ + private def genResult(sym: Symbol, + args: List[js.Tree])(implicit pos: Position) = { + val thisType = + if (sym.owner == ObjectClass) jstpe.ClassType(ir.Definitions.ObjectClass) + else encodeClassType(sym.owner) + val call = genApplyMethod(js.This()(thisType), sym.owner, sym, args) + ensureBoxed(call, + enteringPhase(currentRun.posterasurePhase)(sym.tpe.resultType)) + } + + private sealed abstract class Exported { + def pos: Position + def params: List[Type] + def body: js.Tree + def name: String + def typeInfo: String + def hasRepeatedParam: Boolean + } + + private case class ExportedSymbol(sym: Symbol) extends Exported { + def pos: Position = sym.pos + def params: List[Type] = sym.tpe.params.map(_.tpe) + def body: js.Tree = genApplyForSym(sym) + def name: String = sym.name.toString + def typeInfo: String = sym.tpe.toString + def hasRepeatedParam: Boolean = GenJSExports.this.hasRepeatedParam(sym) + } + + private case class ExportedBody(params: List[Type], body: js.Tree, + name: String, pos: Position) extends Exported { + def typeInfo: String = params.mkString("(", ", ", ")") + val hasRepeatedParam: Boolean = false + } + } + + private def isOverridingExport(sym: Symbol): Boolean = { + lazy val osym = sym.nextOverriddenSymbol + sym.isOverridingSymbol && !osym.owner.isInterface + } + + private sealed abstract class RTTypeTest + + private final case class HijackedTypeTest( + boxedClassName: String, rank: Int) extends RTTypeTest + + private final case class InstanceOfTypeTest(tpe: Type) extends RTTypeTest { + override def equals(that: Any): Boolean = { + that match { + case InstanceOfTypeTest(thatTpe) => tpe =:= thatTpe + case _ => false + } + } + } + + private case object NoTypeTest extends RTTypeTest + + private object RTTypeTest { + implicit object Ordering extends PartialOrdering[RTTypeTest] { + override def tryCompare(lhs: RTTypeTest, rhs: RTTypeTest): Option[Int] = { + if (lteq(lhs, rhs)) if (lteq(rhs, lhs)) Some(0) else Some(-1) + else if (lteq(rhs, lhs)) Some(1) else None + } + + override def lteq(lhs: RTTypeTest, rhs: RTTypeTest): Boolean = { + (lhs, rhs) match { + // NoTypeTest is always last + case (_, NoTypeTest) => true + case (NoTypeTest, _) => false + + case (HijackedTypeTest(_, rank1), HijackedTypeTest(_, rank2)) => + rank1 <= rank2 + + case (InstanceOfTypeTest(t1), InstanceOfTypeTest(t2)) => + t1 <:< t2 + + case (_:HijackedTypeTest, _:InstanceOfTypeTest) => true + case (_:InstanceOfTypeTest, _:HijackedTypeTest) => false + } + } + + override def equiv(lhs: RTTypeTest, rhs: RTTypeTest): Boolean = { + lhs == rhs + } + } + } + + // Very simple O(n²) topological sort for elements assumed to be distinct + private def topoSortDistinctsBy[A <: AnyRef, B](coll: List[A])(f: A => B)( + implicit ord: PartialOrdering[B]): List[A] = { + + @scala.annotation.tailrec + def loop(coll: List[A], acc: List[A]): List[A] = { + if (coll.isEmpty) acc + else if (coll.tail.isEmpty) coll.head :: acc + else { + val (lhs, rhs) = coll.span(x => !coll.forall( + y => (x eq y) || !ord.lteq(f(x), f(y)))) + assert(!rhs.isEmpty, s"cycle while ordering $coll") + loop(lhs ::: rhs.tail, rhs.head :: acc) + } + } + + loop(coll, Nil) + } + + private def typeTestForTpe(tpe: Type): RTTypeTest = { + tpe match { + case tpe: ErasedValueType => + InstanceOfTypeTest(tpe.valueClazz.typeConstructor) + + case _ => + import ir.{Definitions => Defs} + (toTypeKind(tpe): @unchecked) match { + case VoidKind => HijackedTypeTest(Defs.BoxedUnitClass, 0) + case BooleanKind => HijackedTypeTest(Defs.BoxedBooleanClass, 1) + case ByteKind => HijackedTypeTest(Defs.BoxedByteClass, 2) + case ShortKind => HijackedTypeTest(Defs.BoxedShortClass, 3) + case IntKind => HijackedTypeTest(Defs.BoxedIntegerClass, 4) + case FloatKind => HijackedTypeTest(Defs.BoxedFloatClass, 5) + case DoubleKind => HijackedTypeTest(Defs.BoxedDoubleClass, 6) + + case CharKind => InstanceOfTypeTest(boxedClass(CharClass).tpe) + case LongKind => InstanceOfTypeTest(boxedClass(LongClass).tpe) + + case REFERENCE(cls) => + if (cls == StringClass) HijackedTypeTest(Defs.StringClass, 7) + else if (cls == ObjectClass) NoTypeTest + else if (isRawJSType(tpe)) { + cls match { + case JSUndefinedClass => HijackedTypeTest(Defs.BoxedUnitClass, 0) + case JSBooleanClass => HijackedTypeTest(Defs.BoxedBooleanClass, 1) + case JSNumberClass => HijackedTypeTest(Defs.BoxedDoubleClass, 6) + case JSStringClass => HijackedTypeTest(Defs.StringClass, 7) + case _ => NoTypeTest + } + } else InstanceOfTypeTest(tpe) + + case ARRAY(_) => InstanceOfTypeTest(tpe) + } + } + } + + // Group-by that does not rely on hashCode(), only equals() - O(n²) + private def groupByWithoutHashCode[A, B]( + coll: List[A])(f: A => B): List[(B, List[A])] = { + + import scala.collection.mutable.ArrayBuffer + val m = new ArrayBuffer[(B, List[A])] + m.sizeHint(coll.length) + + for (elem <- coll) { + val key = f(elem) + val index = m.indexWhere(_._1 == key) + if (index < 0) m += ((key, List(elem))) + else m(index) = (key, elem :: m(index)._2) + } + + m.toList + } + + private def genThrowTypeError(msg: String = "No matching overload")( + implicit pos: Position): js.Tree = { + js.Throw(js.StringLiteral(msg)) + } + + private def genFormalArgs(count: Int)(implicit pos: Position): List[js.ParamDef] = + (1 to count map genFormalArg).toList + + private def genFormalArg(index: Int)(implicit pos: Position): js.ParamDef = + js.ParamDef(js.Ident("arg$" + index), jstpe.AnyType, mutable = false) + + private def hasRepeatedParam(sym: Symbol) = + enteringPhase(currentRun.uncurryPhase) { + sym.paramss.flatten.lastOption.exists(isRepeated _) + } + +} diff --git a/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/GenJSFiles.scala b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/GenJSFiles.scala new file mode 100644 index 0000000..f754e70 --- /dev/null +++ b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/GenJSFiles.scala @@ -0,0 +1,51 @@ +/* Scala.js compiler + * Copyright 2013 LAMP/EPFL + * @author Sébastien Doeraene + */ + +package scala.scalajs.compiler + +import scala.tools.nsc._ +import scala.tools.nsc.io.AbstractFile +import scala.reflect.internal.pickling.PickleBuffer + +import java.io._ + +import scala.scalajs.ir +import ir.Infos._ + +/** Send JS ASTs to files + * + * @author Sébastien Doeraene + */ +trait GenJSFiles extends SubComponent { self: GenJSCode => + import global._ + import jsAddons._ + + def genIRFile(cunit: CompilationUnit, sym: Symbol, tree: ir.Trees.ClassDef, + classInfo: ClassInfo): Unit = { + val outfile = getFileFor(cunit, sym, ".sjsir") + val output = outfile.bufferedOutput + try { + ir.InfoSerializers.serialize(output, classInfo) + ir.Serializers.serialize(output, tree) + } finally { + output.close() + } + } + + private def getFileFor(cunit: CompilationUnit, sym: Symbol, + suffix: String) = { + val baseDir: AbstractFile = + settings.outputDirs.outputDirFor(cunit.source.file) + + val pathParts = sym.fullName.split("[./]") + val dir = (baseDir /: pathParts.init)(_.subdirectoryNamed(_)) + + var filename = pathParts.last + if (sym.isModuleClass && !sym.isImplClass) + filename = filename + nme.MODULE_SUFFIX_STRING + + dir fileNamed (filename + suffix) + } +} diff --git a/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/JSDefinitions.scala b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/JSDefinitions.scala new file mode 100644 index 0000000..b8a483a --- /dev/null +++ b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/JSDefinitions.scala @@ -0,0 +1,128 @@ +/* Scala.js compiler + * Copyright 2013 LAMP/EPFL + * @author Sébastien Doeraene + */ + +package scala.scalajs.compiler + +import scala.tools.nsc._ + +/** Core definitions for Scala.js + * + * @author Sébastien Doeraene + */ +trait JSDefinitions { self: JSGlobalAddons => + import global._ + + object jsDefinitions extends JSDefinitionsClass + + import definitions._ + import rootMirror._ + + class JSDefinitionsClass { + + lazy val ScalaJSJSPackage = getPackage(newTermNameCached("scala.scalajs.js")) // compat 2.10/2.11 + lazy val JSPackage_undefined = getMemberMethod(ScalaJSJSPackage, newTermName("undefined")) + lazy val JSPackage_isUndefined = getMemberMethod(ScalaJSJSPackage, newTermName("isUndefined")) + lazy val JSPackage_typeOf = getMemberMethod(ScalaJSJSPackage, newTermName("typeOf")) + lazy val JSPackage_debugger = getMemberMethod(ScalaJSJSPackage, newTermName("debugger")) + lazy val JSPackage_native = getMemberMethod(ScalaJSJSPackage, newTermName("native")) + + lazy val ScalaJSJSPrimPackage = getPackage(newTermNameCached("scala.scalajs.js.prim")) // compat 2.10/2.11 + + lazy val JSAnyClass = getRequiredClass("scala.scalajs.js.Any") + lazy val JSDynamicClass = getRequiredClass("scala.scalajs.js.Dynamic") + lazy val JSDynamic_selectDynamic = getMemberMethod(JSDynamicClass, newTermName("selectDynamic")) + lazy val JSDynamic_updateDynamic = getMemberMethod(JSDynamicClass, newTermName("updateDynamic")) + lazy val JSDynamic_applyDynamic = getMemberMethod(JSDynamicClass, newTermName("applyDynamic")) + lazy val JSDictionaryClass = getRequiredClass("scala.scalajs.js.Dictionary") + lazy val JSDictionary_delete = getMemberMethod(JSDictionaryClass, newTermName("delete")) + lazy val JSNumberClass = getRequiredClass("scala.scalajs.js.prim.Number") + lazy val JSBooleanClass = getRequiredClass("scala.scalajs.js.prim.Boolean") + lazy val JSStringClass = getRequiredClass("scala.scalajs.js.prim.String") + lazy val JSUndefinedClass = getRequiredClass("scala.scalajs.js.prim.Undefined") + lazy val JSObjectClass = getRequiredClass("scala.scalajs.js.Object") + lazy val JSThisFunctionClass = getRequiredClass("scala.scalajs.js.ThisFunction") + + lazy val JSGlobalScopeClass = getRequiredClass("scala.scalajs.js.GlobalScope") + + lazy val UndefOrClass = getRequiredClass("scala.scalajs.js.UndefOr") + + lazy val JSArrayClass = getRequiredClass("scala.scalajs.js.Array") + lazy val JSArray_apply = getMemberMethod(JSArrayClass, newTermName("apply")) + lazy val JSArray_update = getMemberMethod(JSArrayClass, newTermName("update")) + + lazy val JSFunctionClasses = (0 to 22) map (n => getRequiredClass("scala.scalajs.js.Function"+n)) + lazy val JSThisFunctionClasses = (0 to 21) map (n => getRequiredClass("scala.scalajs.js.ThisFunction"+n)) + lazy val AllJSFunctionClasses = JSFunctionClasses ++ JSThisFunctionClasses + + lazy val RuntimeExceptionClass = requiredClass[RuntimeException] + lazy val JavaScriptExceptionClass = getClassIfDefined("scala.scalajs.js.JavaScriptException") + + lazy val JSNameAnnotation = getRequiredClass("scala.scalajs.js.annotation.JSName") + lazy val JSBracketAccessAnnotation = getRequiredClass("scala.scalajs.js.annotation.JSBracketAccess") + lazy val JSExportAnnotation = getRequiredClass("scala.scalajs.js.annotation.JSExport") + lazy val JSExportDescendentObjectsAnnotation = getRequiredClass("scala.scalajs.js.annotation.JSExportDescendentObjects") + lazy val JSExportDescendentClassesAnnotation = getRequiredClass("scala.scalajs.js.annotation.JSExportDescendentClasses") + lazy val JSExportAllAnnotation = getRequiredClass("scala.scalajs.js.annotation.JSExportAll") + lazy val JSExportNamedAnnotation = getRequiredClass("scala.scalajs.js.annotation.JSExportNamed") + + lazy val JSAnyTpe = JSAnyClass.toTypeConstructor + lazy val JSDynamicTpe = JSDynamicClass.toTypeConstructor + lazy val JSNumberTpe = JSNumberClass.toTypeConstructor + lazy val JSBooleanTpe = JSBooleanClass.toTypeConstructor + lazy val JSStringTpe = JSStringClass.toTypeConstructor + lazy val JSUndefinedTpe = JSUndefinedClass.toTypeConstructor + lazy val JSObjectTpe = JSObjectClass.toTypeConstructor + + lazy val JSGlobalScopeTpe = JSGlobalScopeClass.toTypeConstructor + + lazy val JSFunctionTpes = JSFunctionClasses.map(_.toTypeConstructor) + + lazy val JSAnyModule = JSAnyClass.companionModule + def JSAny_fromFunction(arity: Int) = getMemberMethod(JSAnyModule, newTermName("fromFunction"+arity)) + + lazy val JSDynamicModule = JSDynamicClass.companionModule + lazy val JSDynamic_newInstance = getMemberMethod(JSDynamicModule, newTermName("newInstance")) + lazy val JSDynamicLiteral = getMemberModule(JSDynamicModule, newTermName("literal")) + lazy val JSDynamicLiteral_applyDynamicNamed = getMemberMethod(JSDynamicLiteral, newTermName("applyDynamicNamed")) + lazy val JSDynamicLiteral_applyDynamic = getMemberMethod(JSDynamicLiteral, newTermName("applyDynamic")) + + lazy val JSObjectModule = JSObjectClass.companionModule + lazy val JSObject_hasProperty = getMemberMethod(JSObjectModule, newTermName("hasProperty")) + lazy val JSObject_properties = getMemberMethod(JSObjectModule, newTermName("properties")) + + lazy val JSArrayModule = JSArrayClass.companionModule + lazy val JSArray_create = getMemberMethod(JSArrayModule, newTermName("apply")) + + lazy val JSThisFunctionModule = JSThisFunctionClass.companionModule + def JSThisFunction_fromFunction(arity: Int) = getMemberMethod(JSThisFunctionModule, newTermName("fromFunction"+arity)) + + lazy val RawJSTypeAnnot = getClassIfDefined("scala.scalajs.js.annotation.RawJSType") + + lazy val RuntimeStringModule = getRequiredModule("scala.scalajs.runtime.RuntimeString") + lazy val RuntimeStringModuleClass = RuntimeStringModule.moduleClass + + lazy val BooleanReflectiveCallClass = getRequiredClass("scala.scalajs.runtime.BooleanReflectiveCall") + lazy val NumberReflectiveCallClass = getRequiredClass("scala.scalajs.runtime.NumberReflectiveCall") + lazy val IntegerReflectiveCallClass = getRequiredClass("scala.scalajs.runtime.IntegerReflectiveCall") + + lazy val RuntimePackageModule = getPackageObject("scala.scalajs.runtime") + lazy val Runtime_wrapJavaScriptException = getMemberMethod(RuntimePackageModule, newTermName("wrapJavaScriptException")) + lazy val Runtime_unwrapJavaScriptException = getMemberMethod(RuntimePackageModule, newTermName("unwrapJavaScriptException")) + lazy val Runtime_genTraversableOnce2jsArray = getMemberMethod(RuntimePackageModule, newTermName("genTraversableOnce2jsArray")) + lazy val Runtime_newJSObjectWithVarargs = getMemberMethod(RuntimePackageModule, newTermName("newJSObjectWithVarargs")) + lazy val Runtime_propertiesOf = getMemberMethod(RuntimePackageModule, newTermName("propertiesOf")) + + lazy val WrappedArrayClass = getRequiredClass("scala.scalajs.js.WrappedArray") + lazy val WrappedArray_ctor = WrappedArrayClass.primaryConstructor + + // This is a def, since similar symbols (arrayUpdateMethod, etc.) are in runDefinitions + // (rather than definitions) and we weren't sure if it is safe to make this a lazy val + def ScalaRunTime_isArray = getMemberMethod(ScalaRunTimeModule, newTermName("isArray")).suchThat(_.tpe.params.size == 2) + + lazy val BoxesRunTime_boxToCharacter = getMemberMethod(BoxesRunTimeModule, newTermName("boxToCharacter")) + lazy val BoxesRunTime_unboxToChar = getMemberMethod(BoxesRunTimeModule, newTermName("unboxToChar")) + + } +} diff --git a/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/JSEncoding.scala b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/JSEncoding.scala new file mode 100644 index 0000000..bc7f8be --- /dev/null +++ b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/JSEncoding.scala @@ -0,0 +1,261 @@ +/* Scala.js compiler + * Copyright 2013 LAMP/EPFL + * @author Sébastien Doeraene + */ + +package scala.scalajs.compiler + +import scala.collection.mutable + +import scala.tools.nsc._ + +import scala.scalajs.ir +import ir.{Trees => js, Types => jstpe} + +import util.ScopedVar +import ScopedVar.withScopedVars + +/** Encoding of symbol names for JavaScript + * + * Some issues that this encoding solves: + * * Overloading: encode the full signature in the JS name + * * Same scope for fields and methods of a class + * * Global access to classes and modules (by their full name) + * + * @author Sébastien Doeraene + */ +trait JSEncoding extends SubComponent { self: GenJSCode => + import global._ + import jsAddons._ + + /** Outer separator string (between parameter types) */ + final val OuterSep = "__" + + /** Inner separator character (replace dots in full names) */ + final val InnerSep = "_" + + /** Name given to the local Scala.js environment variable */ + final val ScalaJSEnvironmentName = "ScalaJS" + + /** Name given to all exported stuff of a class for DCE */ + final val dceExportName = "<exported>" + + // Fresh local name generator ---------------------------------------------- + + private val usedLocalNames = new ScopedVar[mutable.Set[String]] + private val localSymbolNames = new ScopedVar[mutable.Map[Symbol, String]] + private val isKeywordOrReserved = + js.isKeyword ++ Seq("arguments", "eval", ScalaJSEnvironmentName) + + def withNewLocalNameScope[A](body: => A): A = + withScopedVars( + usedLocalNames := mutable.Set.empty, + localSymbolNames := mutable.Map.empty + )(body) + + private def freshName(base: String = "x"): String = { + var suffix = 1 + var longName = base + while (usedLocalNames(longName) || isKeywordOrReserved(longName)) { + suffix += 1 + longName = base+"$"+suffix + } + usedLocalNames += longName + longName + } + + def freshLocalIdent()(implicit pos: ir.Position): js.Ident = + js.Ident(freshName(), None) + + def freshLocalIdent(base: String)(implicit pos: ir.Position): js.Ident = + js.Ident(freshName(base), Some(base)) + + private def localSymbolName(sym: Symbol): String = + localSymbolNames.getOrElseUpdate(sym, freshName(sym.name.toString)) + + // Encoding methods ---------------------------------------------------------- + + def encodeLabelSym(sym: Symbol)(implicit pos: Position): js.Ident = { + require(sym.isLabel, "encodeLabelSym called with non-label symbol: " + sym) + js.Ident(localSymbolName(sym), Some(sym.unexpandedName.decoded)) + } + + private lazy val allRefClasses: Set[Symbol] = { + import definitions._ + (Set(ObjectRefClass, VolatileObjectRefClass) ++ + refClass.values ++ volatileRefClass.values) + } + + def encodeFieldSym(sym: Symbol)(implicit pos: Position): js.Ident = { + require(sym.owner.isClass && sym.isTerm && !sym.isMethod && !sym.isModule, + "encodeFieldSym called with non-field symbol: " + sym) + + val name0 = encodeMemberNameInternal(sym) + val name = + if (name0.charAt(name0.length()-1) != ' ') name0 + else name0.substring(0, name0.length()-1) + + /* We have to special-case fields of Ref types (IntRef, ObjectRef, etc.) + * because they are emitted as private by our .scala source files, but + * they are considered public at use site since their symbols come from + * Java-emitted .class files. + */ + val idSuffix = + if (sym.isPrivate || allRefClasses.contains(sym.owner)) + sym.owner.ancestors.count(!_.isInterface).toString + else + "f" + + val encodedName = name + "$" + idSuffix + js.Ident(mangleJSName(encodedName), Some(sym.unexpandedName.decoded)) + } + + def encodeMethodSym(sym: Symbol, reflProxy: Boolean = false) + (implicit pos: Position): js.Ident = { + val (encodedName, paramsString) = encodeMethodNameInternal(sym, reflProxy) + js.Ident(encodedName + paramsString, + Some(sym.unexpandedName.decoded + paramsString)) + } + + def encodeMethodName(sym: Symbol, reflProxy: Boolean = false): String = { + val (encodedName, paramsString) = encodeMethodNameInternal(sym, reflProxy) + encodedName + paramsString + } + + /** Encodes a method symbol of java.lang.String for use in RuntimeString. + * + * This basically means adding an initial parameter of type + * java.lang.String, which is the `this` parameter. + */ + def encodeRTStringMethodSym(sym: Symbol)( + implicit pos: Position): (Symbol, js.Ident) = { + require(sym.isMethod, "encodeMethodSym called with non-method symbol: " + sym) + require(sym.owner == definitions.StringClass) + require(!sym.isClassConstructor && !sym.isPrivate) + + val (encodedName, paramsString) = + encodeMethodNameInternal(sym, inRTClass = true) + val methodIdent = js.Ident(encodedName + paramsString, + Some(sym.unexpandedName.decoded + paramsString)) + + (jsDefinitions.RuntimeStringModuleClass, methodIdent) + } + + private def encodeMethodNameInternal(sym: Symbol, + reflProxy: Boolean = false, + inRTClass: Boolean = false): (String, String) = { + require(sym.isMethod, "encodeMethodSym called with non-method symbol: " + sym) + + def name = encodeMemberNameInternal(sym) + + val encodedName = { + if (sym.isClassConstructor) + "init" + InnerSep + else if (foreignIsImplClass(sym.owner)) + encodeClassFullName(sym.owner) + OuterSep + name + else if (sym.isPrivate) + mangleJSName(name) + OuterSep + "p" + + sym.owner.ancestors.count(!_.isInterface).toString + else + mangleJSName(name) + } + + val paramsString = makeParamsString(sym, reflProxy, inRTClass) + + (encodedName, paramsString) + } + + def encodeStaticMemberSym(sym: Symbol)(implicit pos: Position): js.Ident = { + require(sym.isStaticMember, + "encodeStaticMemberSym called with non-static symbol: " + sym) + js.Ident( + mangleJSName(encodeMemberNameInternal(sym)) + + makeParamsString(List(internalName(sym.tpe))), + Some(sym.unexpandedName.decoded)) + } + + def encodeLocalSym(sym: Symbol)(implicit pos: Position): js.Ident = { + require(!sym.owner.isClass && sym.isTerm && !sym.isMethod && !sym.isModule, + "encodeLocalSym called with non-local symbol: " + sym) + js.Ident(mangleJSName(localSymbolName(sym)), Some(sym.unexpandedName.decoded)) + } + + def foreignIsImplClass(sym: Symbol): Boolean = + sym.isModuleClass && nme.isImplClassName(sym.name) + + def encodeClassType(sym: Symbol): jstpe.Type = { + if (sym == definitions.ObjectClass) jstpe.AnyType + else if (isRawJSType(sym.toTypeConstructor)) jstpe.AnyType + else { + assert(sym != definitions.ArrayClass, + "encodeClassType() cannot be called with ArrayClass") + jstpe.ClassType(encodeClassFullName(sym)) + } + } + + def encodeClassFullNameIdent(sym: Symbol)(implicit pos: Position): js.Ident = { + js.Ident(encodeClassFullName(sym), Some(sym.fullName)) + } + + def encodeModuleFullNameIdent(sym: Symbol)(implicit pos: Position): js.Ident = { + js.Ident(encodeModuleFullName(sym), Some(sym.fullName)) + } + + def encodeClassFullName(sym: Symbol): String = { + ir.Definitions.encodeClassName( + sym.fullName + (if (needsModuleClassSuffix(sym)) "$" else "")) + } + + def needsModuleClassSuffix(sym: Symbol): Boolean = + sym.isModuleClass && !foreignIsImplClass(sym) + + def encodeModuleFullName(sym: Symbol): String = + ir.Definitions.encodeClassName(sym.fullName + "$").dropRight(1) + + private def encodeMemberNameInternal(sym: Symbol): String = + sym.name.toString.replace("_", "$und") + + // Encoding of method signatures + + private def makeParamsString(sym: Symbol, reflProxy: Boolean, + inRTClass: Boolean): String = { + val tpe = sym.tpe + val paramTypeNames = tpe.params map (p => internalName(p.tpe)) + val paramAndResultTypeNames = { + if (sym.isClassConstructor) + paramTypeNames + else if (reflProxy) + paramTypeNames :+ "" + else { + val paramAndResultTypeNames0 = + paramTypeNames :+ internalName(tpe.resultType) + if (!inRTClass) paramAndResultTypeNames0 + else internalName(sym.owner.toTypeConstructor) +: paramAndResultTypeNames0 + } + } + makeParamsString(paramAndResultTypeNames) + } + + private def makeParamsString(paramAndResultTypeNames: List[String]) = + paramAndResultTypeNames.mkString(OuterSep, OuterSep, "") + + /** Computes the internal name for a type. */ + private def internalName(tpe: Type): String = internalName(toTypeKind(tpe)) + + private def internalName(kind: TypeKind): String = kind match { + case VOID => "V" + case kind: ValueTypeKind => kind.primitiveCharCode.toString() + case NOTHING => ir.Definitions.RuntimeNothingClass + case NULL => ir.Definitions.RuntimeNullClass + case REFERENCE(cls) => encodeClassFullName(cls) + case ARRAY(elem) => "A"+internalName(elem) + } + + /** mangles names that are illegal in JavaScript by prepending a $ + * also mangles names that would collide with these mangled names + */ + private def mangleJSName(name: String) = + if (js.isKeyword(name) || name(0).isDigit || name(0) == '$') + "$" + name + else name +} diff --git a/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/JSGlobalAddons.scala b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/JSGlobalAddons.scala new file mode 100644 index 0000000..3621050 --- /dev/null +++ b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/JSGlobalAddons.scala @@ -0,0 +1,244 @@ +/* Scala.js compiler + * Copyright 2013 LAMP/EPFL + * @author Sébastien Doeraene + */ + +package scala.scalajs.compiler + +import scala.tools.nsc._ + +import scala.collection.mutable + +/** Additions to Global meaningful for the JavaScript backend + * + * @author Sébastien Doeraene + */ +trait JSGlobalAddons extends JSDefinitions + with Compat210Component { + val global: Global + + import global._ + import jsDefinitions._ + import definitions._ + + /** JavaScript primitives, used in jscode */ + object jsPrimitives extends JSPrimitives { + val global: JSGlobalAddons.this.global.type = JSGlobalAddons.this.global + val jsAddons: ThisJSGlobalAddons = + JSGlobalAddons.this.asInstanceOf[ThisJSGlobalAddons] + } + + /** global javascript interop related helpers */ + object jsInterop { + import scala.reflect.NameTransformer + import scala.reflect.internal.Flags + + private val exportPrefix = "$js$exported$" + private val methodExportPrefix = exportPrefix + "meth$" + private val propExportPrefix = exportPrefix + "prop$" + + case class ExportInfo(jsName: String, pos: Position, isNamed: Boolean) + + /** retrieves the names a sym should be exported to from its annotations + * + * Note that for accessor symbols, the annotations of the accessed symbol + * are used, rather than the annotations of the accessor itself. + */ + def exportsOf(sym: Symbol): List[ExportInfo] = { + val exports = directExportsOf(sym) ++ inheritedExportsOf(sym) + + // Calculate the distinct exports for this symbol (eliminate double + // occurrences of (name, isNamed) pairs). + val buf = new mutable.ListBuffer[ExportInfo] + val seen = new mutable.HashSet[(String, Boolean)] + for (exp <- exports) { + if (!seen.contains((exp.jsName, exp.isNamed))) { + buf += exp + seen += ((exp.jsName, exp.isNamed)) + } + } + + buf.toList + } + + private def directExportsOf(sym: Symbol): List[ExportInfo] = { + val trgSym = { + // For accessors, look on the val/var def + if (sym.isAccessor) sym.accessed + // For primary class constructors, look on the class itself + else if (sym.isPrimaryConstructor && !sym.owner.isModuleClass) sym.owner + else sym + } + + // Annotations that are directly on the member + val directAnnots = for { + annot <- trgSym.annotations + if annot.symbol == JSExportAnnotation || + annot.symbol == JSExportNamedAnnotation + } yield annot + + // Annotations for this member on the whole unit + val unitAnnots = { + if (sym.isMethod && sym.isPublic && + !sym.isConstructor && !sym.isSynthetic) + sym.owner.annotations.filter(_.symbol == JSExportAllAnnotation) + else + Nil + } + + for { + annot <- directAnnots ++ unitAnnots + } yield { + // Is this a named export or a normal one? + val named = annot.symbol == JSExportNamedAnnotation + + def explicitName = annot.stringArg(0).getOrElse { + reporter.error(annot.pos, + s"The argument to ${annot.symbol.name} must be a literal string") + "dummy" + } + + val name = + if (annot.args.nonEmpty) explicitName + else if (sym.isConstructor) decodedFullName(sym.owner) + else if (sym.isModuleClass) decodedFullName(sym) + else sym.unexpandedName.decoded.stripSuffix("_=") + + // Enforce that methods ending with _= are exported as setters + if (sym.isMethod && !sym.isConstructor && + sym.name.decoded.endsWith("_=") && !isJSSetter(sym)) { + reporter.error(annot.pos, "A method ending in _= will be exported " + + s"as setter. But ${sym.name.decoded} does not have the right " + + "signature to do so (single argument, unit return type).") + } + + // Enforce no __ in name + if (name.contains("__")) { + // Get position for error message + val pos = if (annot.stringArg(0).isDefined) + annot.args.head.pos + else trgSym.pos + + reporter.error(pos, + "An exported name may not contain a double underscore (`__`)") + } + + // Make sure we do not override the default export of toString + if (!sym.isConstructor && name == "toString" && !named && + sym.name != nme.toString_ && sym.tpe.params.isEmpty && + !isJSGetter(sym)) { + reporter.error(annot.pos, "You may not export a zero-argument " + + "method named other than 'toString' under the name 'toString'") + } + + if (named && isJSProperty(sym)) { + reporter.error(annot.pos, + "You may not export a getter or a setter as a named export") + } + + ExportInfo(name, annot.pos, named) + } + } + + private def inheritedExportsOf(sym: Symbol): List[ExportInfo] = { + // The symbol from which we (potentially) inherit exports. It also + // gives the exports their name + val trgSym = { + if (sym.isModuleClass) + sym + else if (sym.isConstructor && sym.isPublic && + sym.owner.isConcreteClass && !sym.owner.isModuleClass) + sym.owner + else NoSymbol + } + + if (trgSym == NoSymbol) { + Nil + } else { + val trgAnnot = + if (sym.isModuleClass) JSExportDescendentObjectsAnnotation + else JSExportDescendentClassesAnnotation + + val forcingSym = + trgSym.ancestors.find(_.annotations.exists(_.symbol == trgAnnot)) + + val name = decodedFullName(trgSym) + + forcingSym.map { fs => + // Enfore no __ in name + if (name.contains("__")) { + // Get all annotation positions for error message + reporter.error(sym.pos, + s"""${trgSym.name} may not have a double underscore (`__`) in its fully qualified + |name, since it is forced to be exported by a @${trgAnnot.name} on ${fs}""".stripMargin) + } + + ExportInfo(name, sym.pos, false) + }.toList + } + } + + /** Just like sym.fullName, but does not encode components */ + private def decodedFullName(sym: Symbol): String = { + if (sym.isRoot || sym.isRootPackage || sym == NoSymbol) sym.name.decoded + else if (sym.owner.isEffectiveRoot) sym.name.decoded + else decodedFullName(sym.effectiveOwner.enclClass) + '.' + sym.name.decoded + } + + /** creates a name for an export specification */ + def scalaExportName(jsName: String, isProp: Boolean): TermName = { + val pref = if (isProp) propExportPrefix else methodExportPrefix + val encname = NameTransformer.encode(jsName) + newTermName(pref + encname) + } + + /** checks if the given symbol is a JSExport */ + def isExport(sym: Symbol): Boolean = + sym.unexpandedName.startsWith(exportPrefix) && + !sym.hasFlag(Flags.DEFAULTPARAM) + + /** retrieves the originally assigned jsName of this export and whether it + * is a property + */ + def jsExportInfo(name: Name): (String, Boolean) = { + def dropPrefix(prefix: String) ={ + if (name.startsWith(prefix)) { + // We can't decode right away due to $ separators + val enc = name.encoded.substring(prefix.length) + Some(NameTransformer.decode(enc)) + } else None + } + + dropPrefix(methodExportPrefix).map((_,false)) orElse + dropPrefix(propExportPrefix).map((_,true)) getOrElse + sys.error("non-exported name passed to jsInfoSpec") + } + + def isJSProperty(sym: Symbol): Boolean = isJSGetter(sym) || isJSSetter(sym) + + /** has this symbol to be translated into a JS getter (both directions)? */ + def isJSGetter(sym: Symbol): Boolean = { + sym.tpe.params.isEmpty && enteringPhase(currentRun.uncurryPhase) { + sym.tpe.isInstanceOf[NullaryMethodType] + } + } + + /** has this symbol to be translated into a JS setter (both directions)? */ + def isJSSetter(sym: Symbol) = { + sym.unexpandedName.decoded.endsWith("_=") && + sym.tpe.resultType.typeSymbol == UnitClass && + enteringPhase(currentRun.uncurryPhase) { + sym.tpe.paramss match { + case List(List(arg)) => !isScalaRepeatedParamType(arg.tpe) + case _ => false + } + } + } + + /** has this symbol to be translated into a JS bracket access (JS to Scala) */ + def isJSBracketAccess(sym: Symbol) = + sym.hasAnnotation(JSBracketAccessAnnotation) + + } + +} diff --git a/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/JSPrimitives.scala b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/JSPrimitives.scala new file mode 100644 index 0000000..b8c20e6 --- /dev/null +++ b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/JSPrimitives.scala @@ -0,0 +1,119 @@ +/* Scala.js compiler + * Copyright 2013 LAMP/EPFL + * @author Sébastien Doeraene + */ + +package scala.scalajs.compiler + +import scala.tools.nsc._ + +import scala.collection.mutable + +/** Extension of ScalaPrimitives for primitives only relevant to the JS backend + * + * @author Sébastie Doeraene + */ +abstract class JSPrimitives { + val global: Global + + type ThisJSGlobalAddons = JSGlobalAddons { + val global: JSPrimitives.this.global.type + } + + val jsAddons: ThisJSGlobalAddons + + import global._ + import jsAddons._ + import definitions._ + import rootMirror._ + import jsDefinitions._ + import scalaPrimitives._ + + val GETCLASS = 301 // Object.getClass() + + val F2JS = 305 // FunctionN to js.FunctionN + val F2JSTHIS = 306 // FunctionN to js.ThisFunction{N-1} + + val DYNNEW = 321 // Instantiate a new JavaScript object + + val DYNSELECT = 330 // js.Dynamic.selectDynamic + val DYNUPDATE = 331 // js.Dynamic.updateDynamic + val DYNAPPLY = 332 // js.Dynamic.applyDynamic + val DYNLITN = 333 // js.Dynamic.literal.applyDynamicNamed + val DYNLIT = 334 // js.Dynamic.literal.applyDynamic + + val DICT_DEL = 335 // js.Dictionary.delete + + val ARR_CREATE = 337 // js.Array.apply (array literal syntax) + + val UNDEFVAL = 342 // js.undefined + val ISUNDEF = 343 // js.isUndefined + val TYPEOF = 344 // typeof x + val DEBUGGER = 345 // js.debugger() + val HASPROP = 346 // js.Object.hasProperty(o, p), equiv to `p in o` in JS + val OBJPROPS = 347 // js.Object.properties(o), equiv to `for (p in o)` in JS + val JS_NATIVE = 348 // js.native. Marker method. Fails if tried to be emitted. + + val UNITVAL = 349 // () value, which is undefined + val UNITTYPE = 350 // BoxedUnit.TYPE (== classOf[Unit]) + + val ENV_INFO = 353 // __ScalaJSEnv via helper + + /** Initialize the map of primitive methods (for GenJSCode) */ + def init(): Unit = initWithPrimitives(addPrimitive) + + /** Init the map of primitive methods for Scala.js (for PrepJSInterop) */ + def initPrepJSPrimitives(): Unit = { + scalaJSPrimitives.clear() + initWithPrimitives(scalaJSPrimitives.put) + } + + /** Only call from PrepJSInterop. In GenJSCode, use + * scalaPrimitives.isPrimitive instead + */ + def isJavaScriptPrimitive(sym: Symbol): Boolean = + scalaJSPrimitives.contains(sym) + + private val scalaJSPrimitives = mutable.Map.empty[Symbol, Int] + + private def initWithPrimitives(addPrimitive: (Symbol, Int) => Unit): Unit = { + addPrimitive(Object_getClass, GETCLASS) + + for (i <- 0 to 22) + addPrimitive(JSAny_fromFunction(i), F2JS) + for (i <- 1 to 22) + addPrimitive(JSThisFunction_fromFunction(i), F2JSTHIS) + + addPrimitive(JSDynamic_newInstance, DYNNEW) + + addPrimitive(JSDynamic_selectDynamic, DYNSELECT) + addPrimitive(JSDynamic_updateDynamic, DYNUPDATE) + addPrimitive(JSDynamic_applyDynamic, DYNAPPLY) + addPrimitive(JSDynamicLiteral_applyDynamicNamed, DYNLITN) + addPrimitive(JSDynamicLiteral_applyDynamic, DYNLIT) + + addPrimitive(JSDictionary_delete, DICT_DEL) + + addPrimitive(JSArray_create, ARR_CREATE) + + val ntModule = getRequiredModule("scala.reflect.NameTransformer") + + addPrimitive(JSPackage_typeOf, TYPEOF) + addPrimitive(JSPackage_debugger, DEBUGGER) + addPrimitive(JSPackage_undefined, UNDEFVAL) + addPrimitive(JSPackage_isUndefined, ISUNDEF) + addPrimitive(JSPackage_native, JS_NATIVE) + + addPrimitive(JSObject_hasProperty, HASPROP) + addPrimitive(JSObject_properties, OBJPROPS) + + addPrimitive(BoxedUnit_UNIT, UNITVAL) + addPrimitive(BoxedUnit_TYPE, UNITTYPE) + + addPrimitive(getMember(RuntimePackageModule, + newTermName("environmentInfo")), ENV_INFO) + } + + def isJavaScriptPrimitive(code: Int) = + code >= 300 && code < 360 +} diff --git a/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/JSTreeExtractors.scala b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/JSTreeExtractors.scala new file mode 100644 index 0000000..a18ad88 --- /dev/null +++ b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/JSTreeExtractors.scala @@ -0,0 +1,66 @@ +/* Scala.js compiler + * Copyright 2013 LAMP/EPFL + * @author Tobias Schlatter + */ + +package scala.scalajs.compiler + +import scala.annotation.tailrec + +import scala.scalajs.ir.Trees._ +import scala.scalajs.ir.Types._ + +/** Useful extractors for JavaScript trees */ +object JSTreeExtractors { + + object jse { + /** + * A literally named sequence (like in a call to applyDynamicNamed) + * + * Example (Scala): method(("name1", x), ("name2", y)) + */ + object LitNamed { + def unapply(exprs: List[Tree]) = unapply0(exprs, Nil) + + @tailrec + private def unapply0( + exprs: List[Tree], + acc: List[(StringLiteral, Tree)] + ): Option[List[(StringLiteral, Tree)]] = exprs match { + case Tuple2(name: StringLiteral, value) :: xs => + unapply0(xs, (name, value) :: acc) + case Nil => Some(acc.reverse) + case _ => None + } + } + + /** + * A literal Tuple2 + * + * Example (Scala): (x, y) + * But also (Scala): x -> y + */ + object Tuple2 { + def unapply(tree: Tree): Option[(Tree, Tree)] = tree match { + // case (x, y) + case New(ClassType("T2"), Ident("init___O__O", _), + List(_1, _2)) => + Some((_1, _2)) + // case x -> y + case Apply( + LoadModule(ClassType("s_Predef$ArrowAssoc$")), + Ident("$$minus$greater$extension__O__O__T2", _), + List( + Apply( + LoadModule(ClassType("s_Predef$")), + Ident("any2ArrowAssoc__O__O", _), + List(_1)), + _2)) => + Some((_1, _2)) + case _ => + None + } + } + } + +} diff --git a/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/PrepJSExports.scala b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/PrepJSExports.scala new file mode 100644 index 0000000..9223061 --- /dev/null +++ b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/PrepJSExports.scala @@ -0,0 +1,251 @@ +/* Scala.js compiler + * Copyright 2013 LAMP/EPFL + * @author Tobias Schlatter + */ + +package scala.scalajs.compiler + +import scala.annotation.tailrec + +import scala.tools.nsc.NoPhase + +/** + * Prepare export generation + * + * Helpers for transformation of @JSExport annotations + */ +trait PrepJSExports { this: PrepJSInterop => + + import global._ + import jsAddons._ + import definitions._ + import jsDefinitions._ + + import scala.reflect.internal.Flags + + /** Whether the given symbol has a visibility that allows exporting */ + def hasLegalExportVisibility(sym: Symbol): Boolean = + sym.isPublic || sym.isProtected && !sym.isProtectedLocal + + def genExportMember(ddef: DefDef): List[Tree] = { + val baseSym = ddef.symbol + val clsSym = baseSym.owner + + val exports = jsInterop.exportsOf(baseSym) + + // Helper function for errors + def err(msg: String) = { reporter.error(exports.head.pos, msg); Nil } + def memType = if (baseSym.isConstructor) "constructor" else "method" + + if (exports.isEmpty) + Nil + else if (!hasLegalExportVisibility(baseSym)) + err(s"You may only export public and protected ${memType}s") + else if (baseSym.isMacro) + err("You may not export a macro") + else if (scalaPrimitives.isPrimitive(baseSym)) + err("You may not export a primitive") + else if (hasIllegalRepeatedParam(baseSym)) + err(s"In an exported $memType, a *-parameter must come last " + + "(through all parameter lists)") + else if (hasIllegalDefaultParam(baseSym)) + err(s"In an exported $memType, all parameters with defaults " + + "must be at the end") + else if (currentRun.uncurryPhase == NoPhase) { + /* When using scaladoc, the uncurry phase does not exist. This makes + * our code down below blow up (see bug #323). So we do not do anything + * more here if the phase does not exist. It's no big deal because we do + * not need exports for scaladoc. + */ + Nil + } else if (baseSym.isConstructor) { + // we can generate constructors entirely in the backend, since they + // do not need inheritance and such. But we want to check their sanity + // here by previous tests and the following ones. + + if (!hasLegalExportVisibility(clsSym)) + err("You may only export public and protected classes") + else if (clsSym.isLocalToBlock) + err("You may not export a local class") + else if (clsSym.isNestedClass) + err("You may not export a nested class. Create an exported factory " + + "method in the outer class to work around this limitation.") + else Nil + + } else { + assert(!baseSym.isBridge) + + // Reset interface flag: Any trait will contain non-empty methods + clsSym.resetFlag(Flags.INTERFACE) + + // Actually generate exporter methods + exports.flatMap { exp => + if (exp.isNamed) + genNamedExport(baseSym, exp.jsName, exp.pos) :: Nil + else + genExportDefs(baseSym, exp.jsName, exp.pos) + } + } + } + + /** generate an exporter for a DefDef including default parameter methods */ + private def genExportDefs(defSym: Symbol, jsName: String, pos: Position) = { + val clsSym = defSym.owner + val scalaName = + jsInterop.scalaExportName(jsName, jsInterop.isJSProperty(defSym)) + + // Create symbol for new method + val expSym = defSym.cloneSymbol + + // Set position of symbol + expSym.pos = pos + + // Alter type for new method (lift return type to Any) + // The return type is lifted, in order to avoid bridge + // construction and to detect methods whose signature only differs + // in the return type. + // Attention: This will cause boxes for primitive value types and value + // classes. However, since we have restricted the return types, we can + // always safely remove these boxes again in the back-end. + if (!defSym.isConstructor) + expSym.setInfo(retToAny(expSym.tpe)) + + // Change name for new method + expSym.name = scalaName + + // Update flags + expSym.setFlag(Flags.SYNTHETIC) + expSym.resetFlag( + Flags.DEFERRED | // We always have a body + Flags.ACCESSOR | // We are never a "direct" accessor + Flags.CASEACCESSOR | // And a fortiori not a case accessor + Flags.LAZY | // We are not a lazy val (even if we export one) + Flags.OVERRIDE // Synthetic methods need not bother with this + ) + + // Remove export annotations + expSym.removeAnnotation(JSExportAnnotation) + expSym.removeAnnotation(JSExportNamedAnnotation) + + // Add symbol to class + clsSym.info.decls.enter(expSym) + + // Construct exporter DefDef tree + val exporter = genProxyDefDef(clsSym, defSym, expSym, pos) + + // Construct exporters for default getters + val defaultGetters = for { + (param, i) <- expSym.paramss.flatten.zipWithIndex + if param.hasFlag(Flags.DEFAULTPARAM) + } yield genExportDefaultGetter(clsSym, defSym, expSym, i + 1, pos) + + exporter :: defaultGetters + } + + /** Generate a dummy DefDef tree for a named export. This tree is captured + * by GenJSCode again to generate the required JavaScript logic. + */ + private def genNamedExport(defSym: Symbol, jsName: String, pos: Position) = { + val clsSym = defSym.owner + val scalaName = jsInterop.scalaExportName(jsName, false) + + // Create symbol for the new exporter method + val expSym = clsSym.newMethodSymbol(scalaName, pos, + Flags.SYNTHETIC | Flags.FINAL) + + // Mark the symbol to be a named export + expSym.addAnnotation(JSExportNamedAnnotation) + + // Create a single parameter of type Any + val param = expSym.newValueParameter(newTermName("namedArgs"), pos) + param.setInfo(AnyTpe) + + // Set method type + expSym.setInfo(MethodType(param :: Nil, AnyClass.tpe)) + + // Register method to parent + clsSym.info.decls.enter(expSym) + + // Placeholder tree + def ph = Ident(Predef_???) + + // Create a call to the forwarded method with ??? as args + val sel: Tree = Select(This(clsSym), defSym) + val call = (sel /: defSym.paramss) { + (fun, params) => Apply(fun, List.fill(params.size)(ph)) + } + + // rhs is a block to prevent boxing of result + typer.typedDefDef(DefDef(expSym, Block(call, ph))) + } + + private def genExportDefaultGetter(clsSym: Symbol, trgMethod: Symbol, + exporter: Symbol, paramPos: Int, pos: Position) = { + + // Get default getter method we'll copy + val trgGetter = + clsSym.tpe.member(nme.defaultGetterName(trgMethod.name, paramPos)) + + assert(trgGetter.exists) + + // Although the following must be true in a correct program, we cannot + // assert, since a graceful failure message is only generated later + if (!trgGetter.isOverloaded) { + val expGetter = trgGetter.cloneSymbol + + expGetter.name = nme.defaultGetterName(exporter.name, paramPos) + expGetter.pos = pos + + clsSym.info.decls.enter(expGetter) + + genProxyDefDef(clsSym, trgGetter, expGetter, pos) + + } else EmptyTree + } + + /** generate a DefDef tree (from [[proxySym]]) that calls [[trgSym]] */ + private def genProxyDefDef(clsSym: Symbol, trgSym: Symbol, + proxySym: Symbol, pos: Position) = atPos(pos) { + + // Helper to ascribe repeated argument lists when calling + def spliceParam(sym: Symbol) = { + if (isRepeated(sym)) + Typed(Ident(sym), Ident(tpnme.WILDCARD_STAR)) + else + Ident(sym) + } + + // Construct proxied function call + val sel: Tree = Select(This(clsSym), trgSym) + val rhs = (sel /: proxySym.paramss) { + (fun,params) => Apply(fun, params map spliceParam) + } + + typer.typedDefDef(DefDef(proxySym, rhs)) + } + + /** changes the return type of the method type tpe to Any. returns new type */ + private def retToAny(tpe: Type): Type = tpe match { + case MethodType(params, result) => MethodType(params, retToAny(result)) + case NullaryMethodType(result) => NullaryMethodType(AnyClass.tpe) + case PolyType(tparams, result) => PolyType(tparams, retToAny(result)) + case _ => AnyClass.tpe + } + + /** checks whether this type has a repeated parameter elsewhere than at the end + * of all the params + */ + private def hasIllegalRepeatedParam(sym: Symbol): Boolean = { + val params = sym.paramss.flatten + params.nonEmpty && params.init.exists(isRepeated _) + } + + /** checks whether there are default parameters not at the end of + * the flattened parameter list + */ + private def hasIllegalDefaultParam(sym: Symbol): Boolean = { + val isDefParam = (_: Symbol).hasFlag(Flags.DEFAULTPARAM) + sym.paramss.flatten.reverse.dropWhile(isDefParam).exists(isDefParam) + } + +} diff --git a/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/PrepJSInterop.scala b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/PrepJSInterop.scala new file mode 100644 index 0000000..437576a --- /dev/null +++ b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/PrepJSInterop.scala @@ -0,0 +1,621 @@ +/* Scala.js compiler + * Copyright 2013 LAMP/EPFL + * @author Tobias Schlatter + */ + +package scala.scalajs.compiler + +import scala.tools.nsc +import nsc._ + +import scala.collection.immutable.ListMap +import scala.collection.mutable + +/** Prepares classes extending js.Any for JavaScript interop + * + * This phase does: + * - Sanity checks for js.Any hierarchy + * - Annotate subclasses of js.Any to be treated specially + * - Rewrite calls to scala.Enumeration.Value (include name string) + * - Create JSExport methods: Dummy methods that are propagated + * through the whole compiler chain to mark exports. This allows + * exports to have the same semantics than methods. + * + * @author Tobias Schlatter + */ +abstract class PrepJSInterop extends plugins.PluginComponent + with PrepJSExports + with transform.Transform + with Compat210Component { + val jsAddons: JSGlobalAddons { + val global: PrepJSInterop.this.global.type + } + + val scalaJSOpts: ScalaJSOptions + + import global._ + import jsAddons._ + import definitions._ + import rootMirror._ + import jsDefinitions._ + + val phaseName = "jsinterop" + + override def newPhase(p: nsc.Phase) = new JSInteropPhase(p) + class JSInteropPhase(prev: nsc.Phase) extends Phase(prev) { + override def name = phaseName + override def description = "Prepare ASTs for JavaScript interop" + override def run(): Unit = { + jsPrimitives.initPrepJSPrimitives() + super.run() + } + } + + override protected def newTransformer(unit: CompilationUnit) = + new JSInteropTransformer(unit) + + private object jsnme { + val hasNext = newTermName("hasNext") + val next = newTermName("next") + val nextName = newTermName("nextName") + val x = newTermName("x") + val Value = newTermName("Value") + val Val = newTermName("Val") + } + + private object jstpnme { + val scala_ = newTypeName("scala") // not defined in 2.10's tpnme + } + + class JSInteropTransformer(unit: CompilationUnit) extends Transformer { + + // Force evaluation of JSDynamicLiteral: Strangely, we are unable to find + // nested objects in the JSCode phase (probably after flatten). + // Therefore we force the symbol of js.Dynamic.literal here in order to + // have access to it in JSCode. + JSDynamicLiteral + + var inJSAnyMod = false + var inJSAnyCls = false + var inScalaCls = false + /** are we inside a subclass of scala.Enumeration */ + var inScalaEnum = false + /** are we inside the implementation of scala.Enumeration? */ + var inEnumImpl = false + + def jsAnyClassOnly = !inJSAnyCls && allowJSAny + def allowImplDef = !inJSAnyCls && !inJSAnyMod + def allowJSAny = !inScalaCls + def inJSAny = inJSAnyMod || inJSAnyCls + + /** DefDefs in class templates that export methods to JavaScript */ + val exporters = mutable.Map.empty[Symbol, mutable.ListBuffer[Tree]] + + override def transform(tree: Tree): Tree = postTransform { tree match { + // Catch special case of ClassDef in ModuleDef + case cldef: ClassDef if jsAnyClassOnly && isJSAny(cldef) => + transformJSAny(cldef) + + // Catch forbidden implDefs + case idef: ImplDef if !allowImplDef => + reporter.error(idef.pos, "Traits, classes and objects extending " + + "js.Any may not have inner traits, classes or objects") + super.transform(tree) + + // Handle js.Anys + case idef: ImplDef if isJSAny(idef) => + transformJSAny(idef) + + // Catch the definition of scala.Enumeration itself + case cldef: ClassDef if cldef.symbol == ScalaEnumClass => + enterEnumImpl { super.transform(cldef) } + + // Catch Scala Enumerations to transform calls to scala.Enumeration.Value + case cldef: ClassDef if isScalaEnum(cldef) => + enterScalaCls { + enterScalaEnum { + super.transform(cldef) + } + } + case idef: ImplDef if isScalaEnum(idef) => + enterScalaEnum { super.transform(idef) } + + // Catch (Scala) ClassDefs to forbid js.Anys + case cldef: ClassDef => + enterScalaCls { super.transform(cldef) } + + // Catch ValorDefDef in js.Any + case vddef: ValOrDefDef if inJSAny => + transformValOrDefDefInRawJSType(vddef) + + // Catch ValDefs in enumerations with simple calls to Value + case ValDef(mods, name, tpt, ScalaEnumValue.NoName(optPar)) if inScalaEnum => + val nrhs = ScalaEnumValName(tree.symbol.owner, tree.symbol, optPar) + treeCopy.ValDef(tree, mods, name, transform(tpt), nrhs) + + // Catch Select on Enumeration.Value we couldn't transform but need to + // we ignore the implementation of scala.Enumeration itself + case ScalaEnumValue.NoName(_) if !inEnumImpl => + reporter.warning(tree.pos, + """Couldn't transform call to Enumeration.Value. + |The resulting program is unlikely to function properly as this + |operation requires reflection.""".stripMargin) + super.transform(tree) + + case ScalaEnumValue.NullName() if !inEnumImpl => + reporter.warning(tree.pos, + """Passing null as name to Enumeration.Value + |requires reflection at runtime. The resulting + |program is unlikely to function properly.""".stripMargin) + super.transform(tree) + + case ScalaEnumVal.NoName(_) if !inEnumImpl => + reporter.warning(tree.pos, + """Calls to the non-string constructors of Enumeration.Val + |require reflection at runtime. The resulting + |program is unlikely to function properly.""".stripMargin) + super.transform(tree) + + case ScalaEnumVal.NullName() if !inEnumImpl => + reporter.warning(tree.pos, + """Passing null as name to a constructor of Enumeration.Val + |requires reflection at runtime. The resulting + |program is unlikely to function properly.""".stripMargin) + super.transform(tree) + + // Catch calls to Predef.classOf[T]. These should NEVER reach this phase + // but unfortunately do. In normal cases, the typer phase replaces these + // calls by a literal constant of the given type. However, when we compile + // the scala library itself and Predef.scala is in the sources, this does + // not happen. + // + // The trees reach this phase under the form: + // + // scala.this.Predef.classOf[T] + // + // If we encounter such a tree, depending on the plugin options, we fail + // here or silently fix those calls. + case TypeApply( + classOfTree @ Select(Select(This(jstpnme.scala_), nme.Predef), nme.classOf), + List(tpeArg)) => + if (scalaJSOpts.fixClassOf) { + // Replace call by literal constant containing type + if (typer.checkClassType(tpeArg)) { + typer.typed { Literal(Constant(tpeArg.tpe.dealias.widen)) } + } else { + reporter.error(tpeArg.pos, s"Type ${tpeArg} is not a class type") + EmptyTree + } + } else { + reporter.error(classOfTree.pos, + """This classOf resulted in an unresolved classOf in the jscode + |phase. This is most likely a bug in the Scala compiler. ScalaJS + |is probably able to work around this bug. Enable the workaround + |by passing the fixClassOf option to the plugin.""".stripMargin) + EmptyTree + } + + // Exporter generation + case ddef: DefDef => + // Generate exporters for this ddef if required + exporters.getOrElseUpdate(ddef.symbol.owner, + mutable.ListBuffer.empty) ++= genExportMember(ddef) + + super.transform(tree) + + // Module export sanity check (export generated in JSCode phase) + case modDef: ModuleDef => + val sym = modDef.symbol + + def condErr(msg: String) = { + for (exp <- jsInterop.exportsOf(sym)) { + reporter.error(exp.pos, msg) + } + } + + if (!hasLegalExportVisibility(sym)) + condErr("You may only export public and protected objects") + else if (sym.isLocalToBlock) + condErr("You may not export a local object") + else if (!sym.owner.hasPackageFlag) + condErr("You may not export a nested object") + + super.transform(modDef) + + // Fix for issue with calls to js.Dynamic.x() + // Rewrite (obj: js.Dynamic).x(...) to obj.applyDynamic("x")(...) + case Select(Select(trg, jsnme.x), nme.apply) if isJSDynamic(trg) => + val newTree = atPos(tree.pos) { + Apply( + Select(super.transform(trg), newTermName("applyDynamic")), + List(Literal(Constant("x"))) + ) + } + typer.typed(newTree, Mode.FUNmode, tree.tpe) + + + // Fix for issue with calls to js.Dynamic.x() + // Rewrite (obj: js.Dynamic).x to obj.selectDynamic("x") + case Select(trg, jsnme.x) if isJSDynamic(trg) => + val newTree = atPos(tree.pos) { + Apply( + Select(super.transform(trg), newTermName("selectDynamic")), + List(Literal(Constant("x"))) + ) + } + typer.typed(newTree, Mode.FUNmode, tree.tpe) + + case _ => super.transform(tree) + } } + + private def postTransform(tree: Tree) = tree match { + case Template(parents, self, body) => + val clsSym = tree.symbol.owner + val exports = exporters.get(clsSym).toIterable.flatten + // Add exports to the template + treeCopy.Template(tree, parents, self, body ++ exports) + + case memDef: MemberDef => + val sym = memDef.symbol + if (sym.isLocalToBlock && !sym.owner.isCaseApplyOrUnapply) { + // We exclude case class apply (and unapply) to work around SI-8826 + for (exp <- jsInterop.exportsOf(sym)) { + val msg = { + val base = "You may not export a local definition" + if (sym.owner.isPrimaryConstructor) + base + ". To export a (case) class field, use the " + + "meta-annotation scala.annotation.meta.field like this: " + + "@(JSExport @field)." + else + base + } + reporter.error(exp.pos, msg) + } + } + memDef + + case _ => tree + } + + /** + * Performs checks and rewrites specific to classes / objects extending + * js.Any + */ + private def transformJSAny(implDef: ImplDef) = { + val sym = implDef.symbol + + lazy val badParent = sym.info.parents.find(t => !(t <:< JSAnyClass.tpe)) + val inScalaJSJSPackage = + sym.enclosingPackage == ScalaJSJSPackage || + sym.enclosingPackage == ScalaJSJSPrimPackage + + implDef match { + // Check that we do not have a case modifier + case _ if implDef.mods.hasFlag(Flag.CASE) => + reporter.error(implDef.pos, "Classes and objects extending " + + "js.Any may not have a case modifier") + + // Check that we do not extends a trait that does not extends js.Any + case _ if !inScalaJSJSPackage && !badParent.isEmpty && + !isJSLambda(sym) => + val badName = badParent.get.typeSymbol.fullName + reporter.error(implDef.pos, s"${sym.nameString} extends ${badName} " + + "which does not extend js.Any.") + + // Check that we are not an anonymous class + case cldef: ClassDef + if cldef.symbol.isAnonymousClass && !isJSLambda(sym) => + reporter.error(implDef.pos, "Anonymous classes may not " + + "extend js.Any") + + // Check if we may have a js.Any here + case cldef: ClassDef if !allowJSAny && !jsAnyClassOnly && + !isJSLambda(sym) => + reporter.error(implDef.pos, "Classes extending js.Any may not be " + + "defined inside a class or trait") + + case _: ModuleDef if !allowJSAny => + reporter.error(implDef.pos, "Objects extending js.Any may not be " + + "defined inside a class or trait") + + case _ if sym.isLocalToBlock && !isJSLambda(sym) => + reporter.error(implDef.pos, "Local classes and objects may not " + + "extend js.Any") + + // Check that this is not a class extending js.GlobalScope + case _: ClassDef if isJSGlobalScope(implDef) && + implDef.symbol != JSGlobalScopeClass => + reporter.error(implDef.pos, "Only objects may extend js.GlobalScope") + + case _ => + // We cannot use sym directly, since the symbol + // of a module is not its type's symbol but the value it declares + val tSym = sym.tpe.typeSymbol + + tSym.setAnnotations(rawJSAnnot :: sym.annotations) + + } + + if (implDef.isInstanceOf[ModuleDef]) + enterJSAnyMod { super.transform(implDef) } + else + enterJSAnyCls { super.transform(implDef) } + } + + /** Verify a ValOrDefDef inside a js.Any */ + private def transformValOrDefDefInRawJSType(tree: ValOrDefDef) = { + val sym = tree.symbol + + val exports = jsInterop.exportsOf(sym) + + if (exports.nonEmpty) { + val memType = if (sym.isConstructor) "constructor" else "method" + reporter.error(exports.head.pos, + s"You may not export a $memType of a subclass of js.Any") + } + + if (isNonJSScalaSetter(sym)) { + // Forbid setters with non-unit return type + reporter.error(tree.pos, "Setters that do not return Unit are " + + "not allowed in types extending js.Any") + } + + if (sym.hasAnnotation(NativeAttr)) { + // Native methods are not allowed + reporter.error(tree.pos, "Methods in a js.Any may not be @native") + } + + for { + annot <- sym.getAnnotation(JSNameAnnotation) + if annot.stringArg(0).isEmpty + } { + reporter.error(annot.pos, + "The argument to JSName must be a literal string") + } + + if (sym.isPrimaryConstructor || sym.isValueParameter || + sym.isParamWithDefault || sym.isAccessor && !sym.isDeferred || + sym.isParamAccessor || sym.isSynthetic || + AllJSFunctionClasses.contains(sym.owner)) { + /* Ignore (i.e. allow) primary ctor, parameters, default parameter + * getters, accessors, param accessors, synthetic methods (to avoid + * double errors with case classes, e.g. generated copy method) and + * js.Functions and js.ThisFunctions (they need abstract methods for SAM + * treatment. + */ + } else if (jsPrimitives.isJavaScriptPrimitive(sym)) { + // Force rhs of a primitive to be `sys.error("stub")` except for the + // js.native primitive which displays an elaborate error message + if (sym != JSPackage_native) { + tree.rhs match { + case Apply(trg, Literal(Constant("stub")) :: Nil) + if trg.symbol == definitions.Sys_error => + case _ => + reporter.error(tree.pos, + "The body of a primitive must be `sys.error(\"stub\")`.") + } + } + } else if (sym.isConstructor) { + // Force secondary ctor to have only a call to the primary ctor inside + tree.rhs match { + case Block(List(Apply(trg, _)), Literal(Constant(()))) + if trg.symbol.isPrimaryConstructor && + trg.symbol.owner == sym.owner => + // everything is fine here + case _ => + reporter.error(tree.pos, "A secondary constructor of a class " + + "extending js.Any may only call the primary constructor") + } + } else { + // Check that the tree's body is either empty or calls js.native + tree.rhs match { + case sel: Select if sel.symbol == JSPackage_native => + case _ => + val pos = if (tree.rhs != EmptyTree) tree.rhs.pos else tree.pos + reporter.warning(pos, "Members of traits, classes and objects " + + "extending js.Any may only contain members that call js.native. " + + "This will be enforced in 1.0.") + } + + if (sym.tpe.resultType.typeSymbol == NothingClass && + tree.tpt.asInstanceOf[TypeTree].original == null) { + // Warn if resultType is Nothing and not ascribed + val name = sym.name.decoded.trim + reporter.warning(tree.pos, s"The type of $name got inferred " + + "as Nothing. To suppress this warning, explicitly ascribe " + + "the type.") + } + } + + super.transform(tree) + } + + private def enterJSAnyCls[T](body: =>T) = { + val old = inJSAnyCls + inJSAnyCls = true + val res = body + inJSAnyCls = old + res + } + + private def enterJSAnyMod[T](body: =>T) = { + val old = inJSAnyMod + inJSAnyMod = true + val res = body + inJSAnyMod = old + res + } + + private def enterScalaCls[T](body: =>T) = { + val old = inScalaCls + inScalaCls = true + val res = body + inScalaCls = old + res + } + + private def enterScalaEnum[T](body: =>T) = { + val old = inScalaEnum + inScalaEnum = true + val res = body + inScalaEnum = old + res + } + + private def enterEnumImpl[T](body: =>T) = { + val old = inEnumImpl + inEnumImpl = true + val res = body + inEnumImpl = old + res + } + + } + + def isJSAny(sym: Symbol): Boolean = + sym.tpe.typeSymbol isSubClass JSAnyClass + + private def isJSAny(implDef: ImplDef): Boolean = isJSAny(implDef.symbol) + + private def isJSGlobalScope(implDef: ImplDef) = + implDef.symbol.tpe.typeSymbol isSubClass JSGlobalScopeClass + + private def isJSLambda(sym: Symbol) = sym.isAnonymousClass && + AllJSFunctionClasses.exists(sym.tpe.typeSymbol isSubClass _) + + private def isScalaEnum(implDef: ImplDef) = + implDef.symbol.tpe.typeSymbol isSubClass ScalaEnumClass + + private def isJSDynamic(tree: Tree) = tree.tpe.typeSymbol == JSDynamicClass + + /** + * is this symbol a setter that has a non-unit return type + * + * these setters don't make sense in JS (in JS, assignment returns + * the assigned value) and are therefore not allowed in facade types + */ + private def isNonJSScalaSetter(sym: Symbol) = nme.isSetterName(sym.name) && { + sym.tpe.paramss match { + case List(List(arg)) => + !isScalaRepeatedParamType(arg.tpe) && + sym.tpe.resultType.typeSymbol != UnitClass + case _ => false + } + } + + trait ScalaEnumFctExtractors { + protected val methSym: Symbol + + protected def resolve(ptpes: Symbol*) = { + val res = methSym suchThat { + _.tpe.params.map(_.tpe.typeSymbol) == ptpes.toList + } + assert(res != NoSymbol) + res + } + + protected val noArg = resolve() + protected val nameArg = resolve(StringClass) + protected val intArg = resolve(IntClass) + protected val fullMeth = resolve(IntClass, StringClass) + + /** + * Extractor object for calls to the targeted symbol that do not have an + * explicit name in the parameters + * + * Extracts: + * - `sel: Select` where sel.symbol is targeted symbol (no arg) + * - Apply(meth, List(param)) where meth.symbol is targeted symbol (i: Int) + */ + object NoName { + def unapply(t: Tree) = t match { + case sel: Select if sel.symbol == noArg => + Some(None) + case Apply(meth, List(param)) if meth.symbol == intArg => + Some(Some(param)) + case _ => + None + } + } + + object NullName { + def unapply(tree: Tree) = tree match { + case Apply(meth, List(Literal(Constant(null)))) => + meth.symbol == nameArg + case Apply(meth, List(_, Literal(Constant(null)))) => + meth.symbol == fullMeth + case _ => false + } + } + + } + + private object ScalaEnumValue extends { + protected val methSym = getMemberMethod(ScalaEnumClass, jsnme.Value) + } with ScalaEnumFctExtractors + + private object ScalaEnumVal extends { + protected val methSym = { + val valSym = getMemberClass(ScalaEnumClass, jsnme.Val) + valSym.tpe.member(nme.CONSTRUCTOR) + } + } with ScalaEnumFctExtractors + + /** + * Construct a call to Enumeration.Value + * @param thisSym ClassSymbol of enclosing class + * @param nameOrig Symbol of ValDef where this call will be placed + * (determines the string passed to Value) + * @param intParam Optional tree with Int passed to Value + * @return Typed tree with appropriate call to Value + */ + private def ScalaEnumValName( + thisSym: Symbol, + nameOrig: Symbol, + intParam: Option[Tree]) = { + + val defaultName = nameOrig.asTerm.getterName.encoded + + + // Construct the following tree + // + // if (nextName != null && nextName.hasNext) + // nextName.next() + // else + // <defaultName> + // + val nextNameTree = Select(This(thisSym), jsnme.nextName) + val nullCompTree = + Apply(Select(nextNameTree, nme.NE), Literal(Constant(null)) :: Nil) + val hasNextTree = Select(nextNameTree, jsnme.hasNext) + val condTree = Apply(Select(nullCompTree, nme.ZAND), hasNextTree :: Nil) + val nameTree = If(condTree, + Apply(Select(nextNameTree, jsnme.next), Nil), + Literal(Constant(defaultName))) + val params = intParam.toList :+ nameTree + + typer.typed { + Apply(Select(This(thisSym), jsnme.Value), params) + } + } + + private def rawJSAnnot = + Annotation(RawJSTypeAnnot.tpe, List.empty, ListMap.empty) + + private lazy val ScalaEnumClass = getRequiredClass("scala.Enumeration") + + /** checks if the primary constructor of the ClassDef `cldef` does not + * take any arguments + */ + private def primCtorNoArg(cldef: ClassDef) = + getPrimCtor(cldef.symbol.tpe).map(_.paramss == List(List())).getOrElse(true) + + /** return the MethodSymbol of the primary constructor of the given type + * if it exists + */ + private def getPrimCtor(tpe: Type) = + tpe.declaration(nme.CONSTRUCTOR).alternatives.collectFirst { + case ctor: MethodSymbol if ctor.isPrimaryConstructor => ctor + } + +} diff --git a/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/ScalaJSOptions.scala b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/ScalaJSOptions.scala new file mode 100644 index 0000000..72912bf --- /dev/null +++ b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/ScalaJSOptions.scala @@ -0,0 +1,30 @@ +/* Scala.js compiler + * Copyright 2013 LAMP/EPFL + * @author Tobias Schlatter + */ + +package scala.scalajs.compiler + +import java.net.URI + +/** This trait allows to query all options to the ScalaJS plugin + * + * Also see the help text in ScalaJSPlugin for information about particular + * options. + */ +trait ScalaJSOptions { + import ScalaJSOptions.URIMap + + /** should calls to Predef.classOf[T] be fixed in the jsinterop phase. + * If false, bad calls to classOf will cause an error. */ + def fixClassOf: Boolean + + /** which source locations in source maps should be relativized (or where + * should they be mapped to)? */ + def sourceURIMaps: List[URIMap] + +} + +object ScalaJSOptions { + case class URIMap(from: URI, to: Option[URI]) +} diff --git a/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/ScalaJSPlugin.scala b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/ScalaJSPlugin.scala new file mode 100644 index 0000000..c3916ab --- /dev/null +++ b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/ScalaJSPlugin.scala @@ -0,0 +1,143 @@ +/* Scala.js compiler + * Copyright 2013 LAMP/EPFL + * @author Sébastien Doeraene + */ + +package scala.scalajs.compiler + +import scala.tools.nsc._ +import scala.tools.nsc.plugins.{ + Plugin => NscPlugin, PluginComponent => NscPluginComponent +} +import scala.collection.{ mutable, immutable } + +import java.net.{ URI, URISyntaxException } + +import scala.scalajs.ir.Trees + +/** Main entry point for the Scala.js compiler plugin + * + * @author Sébastien Doeraene + */ +class ScalaJSPlugin(val global: Global) extends NscPlugin { + import global._ + + val name = "scalajs" + val description = "Compile to JavaScript" + val components = { + if (global.forScaladoc) + List[NscPluginComponent](PrepInteropComponent) + else + List[NscPluginComponent](PrepInteropComponent, GenCodeComponent) + } + + /** Called when the JS ASTs are generated. Override for testing */ + def generatedJSAST(clDefs: List[Trees.Tree]): Unit = {} + + /** Addons for JavaScript platform */ + object jsAddons extends { + val global: ScalaJSPlugin.this.global.type = ScalaJSPlugin.this.global + } with JSGlobalAddons with Compat210Component + + object scalaJSOpts extends ScalaJSOptions { + import ScalaJSOptions.URIMap + var fixClassOf: Boolean = false + lazy val sourceURIMaps: List[URIMap] = { + if (_sourceURIMaps.nonEmpty) + _sourceURIMaps.reverse + else + relSourceMap.toList.map(URIMap(_, absSourceMap)) + } + var _sourceURIMaps: List[URIMap] = Nil + var relSourceMap: Option[URI] = None + var absSourceMap: Option[URI] = None + } + + object PrepInteropComponent extends { + val global: ScalaJSPlugin.this.global.type = ScalaJSPlugin.this.global + val jsAddons: ScalaJSPlugin.this.jsAddons.type = ScalaJSPlugin.this.jsAddons + val scalaJSOpts = ScalaJSPlugin.this.scalaJSOpts + override val runsAfter = List("typer") + override val runsBefore = List("pickle") + } with PrepJSInterop + + object GenCodeComponent extends { + val global: ScalaJSPlugin.this.global.type = ScalaJSPlugin.this.global + val jsAddons: ScalaJSPlugin.this.jsAddons.type = ScalaJSPlugin.this.jsAddons + val scalaJSOpts = ScalaJSPlugin.this.scalaJSOpts + override val runsAfter = List("mixin") + override val runsBefore = List("delambdafy", "cleanup", "terminal") + } with GenJSCode { + def generatedJSAST(clDefs: List[Trees.Tree]) = + ScalaJSPlugin.this.generatedJSAST(clDefs) + } + + override def processOptions(options: List[String], + error: String => Unit): Unit = { + import ScalaJSOptions.URIMap + import scalaJSOpts._ + + for (option <- options) { + if (option == "fixClassOf") { + fixClassOf = true + + } else if (option.startsWith("mapSourceURI:")) { + val uris = option.stripPrefix("mapSourceURI:").split("->") + + if (uris.length != 1 && uris.length != 2) { + error("relocateSourceMap needs one or two URIs as argument.") + } else { + try { + val from = new URI(uris.head) + val to = uris.lift(1).map(str => new URI(str)) + _sourceURIMaps ::= URIMap(from, to) + } catch { + case e: URISyntaxException => + error(s"${e.getInput} is not a valid URI") + } + } + + // The following options are deprecated (how do we show this to the user?) + } else if (option.startsWith("relSourceMap:")) { + val uriStr = option.stripPrefix("relSourceMap:") + try { relSourceMap = Some(new URI(uriStr)) } + catch { + case e: URISyntaxException => error(s"$uriStr is not a valid URI") + } + } else if (option.startsWith("absSourceMap:")) { + val uriStr = option.stripPrefix("absSourceMap:") + try { absSourceMap = Some(new URI(uriStr)) } + catch { + case e: URISyntaxException => error(s"$uriStr is not a valid URI") + } + } else { + error("Option not understood: " + option) + } + } + + // Verify constraints + if (_sourceURIMaps.nonEmpty && relSourceMap.isDefined) + error("You may not use mapSourceURI and relSourceMap together. " + + "Use another mapSourceURI option without second URI.") + else if (_sourceURIMaps.nonEmpty && absSourceMap.isDefined) + error("You may not use mapSourceURI and absSourceMap together. " + + "Use another mapSourceURI option.") + else if (absSourceMap.isDefined && relSourceMap.isEmpty) + error("absSourceMap requires the use of relSourceMap") + } + + override val optionsHelp: Option[String] = Some(s""" + | -P:$name:mapSourceURI:FROM_URI[->TO_URI] + | change the location the source URIs in the emitted IR point to + | - strips away the prefix FROM_URI (if it matches) + | - optionally prefixes the TO_URI, where stripping has been performed + | - any number of occurences are allowed. Processing is done on a first match basis. + | -P:$name:fixClassOf repair calls to Predef.classOf that reach ScalaJS + | WARNING: This is a tremendous hack! Expect ugly errors if you use this option. + |Deprecated options + | -P:$name:relSourceMap:<URI> relativize emitted source maps with <URI> + | -P:$name:absSourceMap:<URI> absolutize emitted source maps with <URI> + | This option requires the use of relSourceMap + """.stripMargin) + +} diff --git a/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/TypeKinds.scala b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/TypeKinds.scala new file mode 100644 index 0000000..774be68 --- /dev/null +++ b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/TypeKinds.scala @@ -0,0 +1,252 @@ +/* Scala.js compiler + * Copyright 2013 LAMP/EPFL + * @author Sébastien Doeraene + */ + +package scala.scalajs.compiler + +import scala.tools.nsc._ + +import scala.scalajs.ir +import ir.{Definitions, Types} + +/** Glue representation of types as seen from the IR but still with a + * reference to the Symbols. + * + * @author Sébastien Doeraene + */ +trait TypeKinds extends SubComponent { this: GenJSCode => + import global._ + import jsAddons._ + import definitions._ + + lazy val ObjectReference = REFERENCE(definitions.ObjectClass) + + lazy val VoidKind = VOID + lazy val BooleanKind = BOOL + lazy val CharKind = INT(CharClass) + lazy val ByteKind = INT(ByteClass) + lazy val ShortKind = INT(ShortClass) + lazy val IntKind = INT(IntClass) + lazy val LongKind = LONG + lazy val FloatKind = FLOAT(FloatClass) + lazy val DoubleKind = FLOAT(DoubleClass) + + /** TypeKinds for Scala primitive types. */ + lazy val primitiveTypeMap: Map[Symbol, TypeKind] = { + import definitions._ + Map( + UnitClass -> VoidKind, + BooleanClass -> BooleanKind, + CharClass -> CharKind, + ByteClass -> ByteKind, + ShortClass -> ShortKind, + IntClass -> IntKind, + LongClass -> LongKind, + FloatClass -> FloatKind, + DoubleClass -> DoubleKind + ) + } + + /** Glue representation of types as seen from the IR but still with a + * reference to the Symbols. + */ + sealed abstract class TypeKind { + def isReferenceType = false + def isArrayType = false + def isValueType = false + + def toIRType: Types.Type + def toReferenceType: Types.ReferenceType + } + + sealed abstract class TypeKindButArray extends TypeKind { + protected def typeSymbol: Symbol + + override def toReferenceType: Types.ClassType = + Types.ClassType(encodeClassFullName(typeSymbol)) + } + + /** The void, for trees that can only appear in statement position. */ + case object VOID extends TypeKindButArray { + protected def typeSymbol = UnitClass + def toIRType: Types.NoType.type = Types.NoType + } + + sealed abstract class ValueTypeKind extends TypeKindButArray { + override def isValueType = true + + val primitiveCharCode: Char = typeSymbol match { + case BooleanClass => 'Z' + case CharClass => 'C' + case ByteClass => 'B' + case ShortClass => 'S' + case IntClass => 'I' + case LongClass => 'J' + case FloatClass => 'F' + case DoubleClass => 'D' + case x => abort("Unknown primitive type: " + x.fullName) + } + } + + /** Integer number (Byte, Short, Char or Int). */ + case class INT private[TypeKinds] (typeSymbol: Symbol) extends ValueTypeKind { + def toIRType: Types.IntType.type = Types.IntType + } + + /** Long */ + case object LONG extends ValueTypeKind { + protected def typeSymbol = definitions.LongClass + def toIRType: Types.LongType.type = Types.LongType + } + + /** Floating-point number (Float or Double). */ + case class FLOAT private[TypeKinds] (typeSymbol: Symbol) extends ValueTypeKind { + def toIRType: Types.Type = + if (typeSymbol == FloatClass) Types.FloatType + else Types.DoubleType + } + + /** Boolean */ + case object BOOL extends ValueTypeKind { + protected def typeSymbol = definitions.BooleanClass + def toIRType: Types.BooleanType.type = Types.BooleanType + } + + /** Nothing */ + case object NOTHING extends TypeKindButArray { + protected def typeSymbol = definitions.NothingClass + def toIRType: Types.NothingType.type = Types.NothingType + override def toReferenceType: Types.ClassType = + Types.ClassType(Definitions.RuntimeNothingClass) + } + + /** Null */ + case object NULL extends TypeKindButArray { + protected def typeSymbol = definitions.NullClass + def toIRType: Types.NullType.type = Types.NullType + override def toReferenceType: Types.ClassType = + Types.ClassType(Definitions.RuntimeNullClass) + } + + /** An object */ + case class REFERENCE private[TypeKinds] (typeSymbol: Symbol) extends TypeKindButArray { + override def toString(): String = "REFERENCE(" + typeSymbol.fullName + ")" + override def isReferenceType = true + + def toIRType: Types.Type = encodeClassType(typeSymbol) + } + + /** An array */ + case class ARRAY private[TypeKinds] (elem: TypeKind) extends TypeKind { + override def toString = "ARRAY[" + elem + "]" + override def isArrayType = true + + def dimensions: Int = elem match { + case a: ARRAY => a.dimensions + 1 + case _ => 1 + } + + override def toIRType: Types.ArrayType = toReferenceType + + override def toReferenceType: Types.ArrayType = { + Types.ArrayType( + elementKind.toReferenceType.className, + dimensions) + } + + /** The ultimate element type of this array. */ + def elementKind: TypeKindButArray = elem match { + case a: ARRAY => a.elementKind + case k: TypeKindButArray => k + } + } + + ////////////////// Conversions ////////////////////////////// + + def toIRType(t: Type): Types.Type = + toTypeKind(t).toIRType + + def toReferenceType(t: Type): Types.ReferenceType = + toTypeKind(t).toReferenceType + + // The following code is a hard copy-and-paste from backend.icode.TypeKinds + + /** Return the TypeKind of the given type + * + * Call to .normalize fixes #3003 (follow type aliases). Otherwise, + * arrayOrClassType below would return ObjectReference. + */ + def toTypeKind(t: Type): TypeKind = t.normalize match { + case ThisType(ArrayClass) => ObjectReference + case ThisType(sym) => newReference(sym) + case SingleType(_, sym) => primitiveOrRefType(sym) + case ConstantType(_) => toTypeKind(t.underlying) + case TypeRef(_, sym, args) => primitiveOrClassType(sym, args) + case ClassInfoType(_, _, ArrayClass) => abort("ClassInfoType to ArrayClass!") + case ClassInfoType(_, _, sym) => primitiveOrRefType(sym) + + // !!! Iulian says types which make no sense after erasure should not reach here, + // which includes the ExistentialType, AnnotatedType, RefinedType. I don't know + // if the first two cases exist because they do or as a defensive measure, but + // at the time I added it, RefinedTypes were indeed reaching here. + // !!! Removed in JavaScript backend because I do not know what to do with lub + //case ExistentialType(_, t) => toTypeKind(t) + // Apparently, this case does occur (see pos/CustomGlobal.scala) + case t: AnnotatedType => toTypeKind(t.underlying) + //case RefinedType(parents, _) => parents map toTypeKind reduceLeft lub + + /* This case is not in scalac. We need it for the test + * run/valueclasses-classtag-existential. I have no idea how icode does + * not fail this test: we do everything the same as icode up to here. + */ + case tpe: ErasedValueType => newReference(tpe.valueClazz) + + // For sure WildcardTypes shouldn't reach here either, but when + // debugging such situations this may come in handy. + // case WildcardType => REFERENCE(ObjectClass) + case norm => abort( + "Unknown type: %s, %s [%s, %s] TypeRef? %s".format( + t, norm, t.getClass, norm.getClass, t.isInstanceOf[TypeRef] + ) + ) + } + + /** Return the type kind of a class, possibly an array type. + */ + private def arrayOrClassType(sym: Symbol, targs: List[Type]) = sym match { + case ArrayClass => ARRAY(toTypeKind(targs.head)) + case _ if sym.isClass => newReference(sym) + case _ => + assert(sym.isType, sym) // it must be compiling Array[a] + ObjectReference + } + + /** Interfaces have to be handled delicately to avoid introducing + * spurious errors, but if we treat them all as AnyRef we lose too + * much information. + */ + private def newReference(sym: Symbol): TypeKind = sym match { + case NothingClass => NOTHING + case NullClass => NULL + case _ => + // Can't call .toInterface (at this phase) or we trip an assertion. + // See PackratParser#grow for a method which fails with an apparent mismatch + // between "object PackratParsers$class" and "trait PackratParsers" + if (sym.isImplClass) { + // pos/spec-List.scala is the sole failure if we don't check for NoSymbol + val traitSym = sym.owner.info.decl(tpnme.interfaceName(sym.name)) + if (traitSym != NoSymbol) + REFERENCE(traitSym) + else + REFERENCE(sym) + } else { + REFERENCE(sym) + } + } + + private def primitiveOrRefType(sym: Symbol) = + primitiveTypeMap.getOrElse(sym, newReference(sym)) + private def primitiveOrClassType(sym: Symbol, targs: List[Type]) = + primitiveTypeMap.getOrElse(sym, arrayOrClassType(sym, targs)) +} diff --git a/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/util/ScopedVar.scala b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/util/ScopedVar.scala new file mode 100644 index 0000000..3924955 --- /dev/null +++ b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/util/ScopedVar.scala @@ -0,0 +1,38 @@ +package scala.scalajs.compiler.util + +import language.implicitConversions + +class ScopedVar[A](init: A) { + import ScopedVar.Assignment + + private var value = init + + def this()(implicit ev: Null <:< A) = this(ev(null)) + + def get: A = value + def :=(newValue: A): Assignment[A] = new Assignment(this, newValue) +} + +object ScopedVar { + class Assignment[T](scVar: ScopedVar[T], value: T) { + private[ScopedVar] def push(): AssignmentStackElement[T] = { + val stack = new AssignmentStackElement(scVar, scVar.value) + scVar.value = value + stack + } + } + + private class AssignmentStackElement[T](scVar: ScopedVar[T], oldValue: T) { + private[ScopedVar] def pop(): Unit = { + scVar.value = oldValue + } + } + + implicit def toValue[T](scVar: ScopedVar[T]): T = scVar.get + + def withScopedVars[T](ass: Assignment[_]*)(body: => T): T = { + val stack = ass.map(_.push()) + try body + finally stack.reverse.foreach(_.pop()) + } +} diff --git a/examples/scala-js/compiler/src/test/scala/scala/scalajs/compiler/test/DiverseErrorsTest.scala b/examples/scala-js/compiler/src/test/scala/scala/scalajs/compiler/test/DiverseErrorsTest.scala new file mode 100644 index 0000000..0fe10f8 --- /dev/null +++ b/examples/scala-js/compiler/src/test/scala/scala/scalajs/compiler/test/DiverseErrorsTest.scala @@ -0,0 +1,31 @@ +package scala.scalajs.compiler.test + +import scala.scalajs.compiler.test.util._ +import org.junit.Test + +class DiverseErrorsTest extends DirectTest with TestHelpers { + + override def preamble = + """import scala.scalajs.js + """ + + @Test + def noIsInstanceOnJSRaw = { + + """ + trait JSRaw extends js.Object + + class A { + val a: AnyRef = "asdf" + def x = a.isInstanceOf[JSRaw] + } + """ hasErrors + """ + |newSource1.scala:7: error: isInstanceOf[JSRaw] not supported because it is a raw JS trait + | def x = a.isInstanceOf[JSRaw] + | ^ + """ + + } + +} diff --git a/examples/scala-js/compiler/src/test/scala/scala/scalajs/compiler/test/EnumerationInteropTest.scala b/examples/scala-js/compiler/src/test/scala/scala/scalajs/compiler/test/EnumerationInteropTest.scala new file mode 100644 index 0000000..e186cf4 --- /dev/null +++ b/examples/scala-js/compiler/src/test/scala/scala/scalajs/compiler/test/EnumerationInteropTest.scala @@ -0,0 +1,135 @@ +package scala.scalajs.compiler.test + +import scala.scalajs.compiler.test.util._ + +import org.junit.Test + +class EnumerationInteropTest extends DirectTest with TestHelpers { + + @Test + def warnIfUnableToTransformValue = { + + """ + class A extends Enumeration { + val a = { + println("oh, oh!") + Value + } + val b = { + println("oh, oh!") + Value(4) + } + } + """ hasWarns + """ + |newSource1.scala:5: warning: Couldn't transform call to Enumeration.Value. + |The resulting program is unlikely to function properly as this + |operation requires reflection. + | Value + | ^ + |newSource1.scala:9: warning: Couldn't transform call to Enumeration.Value. + |The resulting program is unlikely to function properly as this + |operation requires reflection. + | Value(4) + | ^ + """ + + } + + @Test + def warnIfNoNameVal = { + + """ + class A extends Enumeration { + val a = new Val + val b = new Val(10) + } + """ hasWarns + """ + |newSource1.scala:3: warning: Calls to the non-string constructors of Enumeration.Val + |require reflection at runtime. The resulting + |program is unlikely to function properly. + | val a = new Val + | ^ + |newSource1.scala:4: warning: Calls to the non-string constructors of Enumeration.Val + |require reflection at runtime. The resulting + |program is unlikely to function properly. + | val b = new Val(10) + | ^ + """ + + } + + @Test + def warnIfNullValue = { + + """ + class A extends Enumeration { + val a = Value(null) + val b = Value(10, null) + } + """ hasWarns + """ + |newSource1.scala:3: warning: Passing null as name to Enumeration.Value + |requires reflection at runtime. The resulting + |program is unlikely to function properly. + | val a = Value(null) + | ^ + |newSource1.scala:4: warning: Passing null as name to Enumeration.Value + |requires reflection at runtime. The resulting + |program is unlikely to function properly. + | val b = Value(10, null) + | ^ + """ + + } + + @Test + def warnIfNullNewVal = { + + """ + class A extends Enumeration { + val a = new Val(null) + val b = new Val(10, null) + } + """ hasWarns + """ + |newSource1.scala:3: warning: Passing null as name to a constructor of Enumeration.Val + |requires reflection at runtime. The resulting + |program is unlikely to function properly. + | val a = new Val(null) + | ^ + |newSource1.scala:4: warning: Passing null as name to a constructor of Enumeration.Val + |requires reflection at runtime. The resulting + |program is unlikely to function properly. + | val b = new Val(10, null) + | ^ + """ + + } + + @Test + def warnIfExtNoNameVal = { + + """ + class A extends Enumeration { + protected class Val1 extends Val + protected class Val2 extends Val(1) + } + """ warns() // no message checking: position differs in 2.10 and 2.11 + + } + + @Test + def warnIfExtNullNameVal = { + + """ + class A extends Enumeration { + protected class Val1 extends Val(null) + protected class Val2 extends Val(1,null) + } + """ warns() // no message checking: position differs in 2.10 and 2.11 + + } + +} diff --git a/examples/scala-js/compiler/src/test/scala/scala/scalajs/compiler/test/JSDynamicLiteralTest.scala b/examples/scala-js/compiler/src/test/scala/scala/scalajs/compiler/test/JSDynamicLiteralTest.scala new file mode 100644 index 0000000..bc1a1b4 --- /dev/null +++ b/examples/scala-js/compiler/src/test/scala/scala/scalajs/compiler/test/JSDynamicLiteralTest.scala @@ -0,0 +1,102 @@ +package scala.scalajs.compiler.test + +import scala.scalajs.compiler.test.util._ +import org.junit.Test + +class JSDynamicLiteralTest extends DirectTest with TestHelpers { + + override def preamble = + """import scala.scalajs.js.Dynamic.{ literal => lit } + """ + + @Test + def callApplyOnly = { + + // selectDynamic (with any name) + expr""" + lit.helloWorld + """.fails() // Scala error, no string checking due to versions + + // applyDynamicNamed with wrong method name + expr""" + lit.helloWorld(a = "a") + """ hasErrors + """ + |newSource1.scala:3: error: js.Dynamic.literal does not have a method named helloWorld + | lit.helloWorld(a = "a") + | ^ + """ + + // applyDynamic with wrong method name + expr""" + lit.helloWorld("a" -> "a") + """ hasErrors + """ + |newSource1.scala:3: error: js.Dynamic.literal does not have a method named helloWorld + | lit.helloWorld("a" -> "a") + | ^ + """ + + } + + @Test + def goodTypesOnly = { + + // Bad value type (applyDynamic) + """ + class A { + val x = new Object() + def foo = lit("a" -> x) + } + """.fails() + + // Bad key type (applyDynamic) + """ + class A { + val x = Seq() + def foo = lit(x -> "a") + } + """.fails() + + // Bad value type (applyDynamicNamed) + """ + class A { + val x = new Object() + def foo = lit(a = x) + } + """.fails() + + } + + @Test + def noNonLiteralMethodName = { + + // applyDynamicNamed + """ + class A { + val x = "string" + def foo = lit.applyDynamicNamed(x)() + } + """ hasErrors + """ + |newSource1.scala:5: error: js.Dynamic.literal.applyDynamicNamed may not be called directly + | def foo = lit.applyDynamicNamed(x)() + | ^ + """ + + // applyDynamic + """ + class A { + val x = "string" + def foo = lit.applyDynamic(x)() + } + """ hasErrors + """ + |newSource1.scala:5: error: js.Dynamic.literal.applyDynamic may not be called directly + | def foo = lit.applyDynamic(x)() + | ^ + """ + + } + +} diff --git a/examples/scala-js/compiler/src/test/scala/scala/scalajs/compiler/test/JSExportASTTest.scala b/examples/scala-js/compiler/src/test/scala/scala/scalajs/compiler/test/JSExportASTTest.scala new file mode 100644 index 0000000..4a2b1af --- /dev/null +++ b/examples/scala-js/compiler/src/test/scala/scala/scalajs/compiler/test/JSExportASTTest.scala @@ -0,0 +1,38 @@ +package scala.scalajs.compiler.test + +import util._ + +import org.junit.Test +import org.junit.Assert._ + +import scala.scalajs.ir.{Trees => js} + +class JSExportASTTest extends JSASTTest { + + @Test + def inheritExportMethods: Unit = { + + var props = 0 + + """ + import scala.scalajs.js.annotation.JSExport + + class A { + @JSExport + def foo = 1 + } + + class B extends A { + @JSExport + override def foo = 2 + } + """.traverse { + case js.PropertyDef(js.StringLiteral("foo"), _, _, _) => + props += 1 + } + + assertEquals("Only define the property `foo` once", props, 1) + + } + +} diff --git a/examples/scala-js/compiler/src/test/scala/scala/scalajs/compiler/test/JSExportTest.scala b/examples/scala-js/compiler/src/test/scala/scala/scalajs/compiler/test/JSExportTest.scala new file mode 100644 index 0000000..c675420 --- /dev/null +++ b/examples/scala-js/compiler/src/test/scala/scala/scalajs/compiler/test/JSExportTest.scala @@ -0,0 +1,745 @@ +package scala.scalajs.compiler.test + +import scala.scalajs.compiler.test.util._ +import org.junit.Test +import org.junit.Ignore + +class JSExportTest extends DirectTest with TestHelpers { + + override def preamble = + """import scala.scalajs.js.annotation._ + """ + + @Test + def noDoubleUnderscoreExport = { + // Normal exports + """ + class A { + @JSExport(name = "__") + def foo = 1 + + @JSExport + def bar__(x: Int) = x + } + + @JSExport + class B__ + """ hasErrors + """ + |newSource1.scala:4: error: An exported name may not contain a double underscore (`__`) + | @JSExport(name = "__") + | ^ + |newSource1.scala:8: error: An exported name may not contain a double underscore (`__`) + | def bar__(x: Int) = x + | ^ + |newSource1.scala:12: error: An exported name may not contain a double underscore (`__`) + | class B__ + | ^ + """ + + // Inherited exports (objects) + """ + @JSExportDescendentObjects + trait A + + package fo__o { + object B extends A + } + """ hasErrors + """ + |newSource1.scala:7: error: B may not have a double underscore (`__`) in its fully qualified + |name, since it is forced to be exported by a @JSExportDescendentObjects on trait A + | object B extends A + | ^ + """ + + // Inherited exports (classes) + """ + @JSExportDescendentClasses + trait A + + package fo__o { + class B(x: Int) extends A { + def this() = this(1) + private def this(s: String) = this(1) + } + } + """ hasErrors + """ + |newSource1.scala:7: error: B may not have a double underscore (`__`) in its fully qualified + |name, since it is forced to be exported by a @JSExportDescendentClasses on trait A + | class B(x: Int) extends A { + | ^ + |newSource1.scala:8: error: B may not have a double underscore (`__`) in its fully qualified + |name, since it is forced to be exported by a @JSExportDescendentClasses on trait A + | def this() = this(1) + | ^ + """ + } + + @Test + def noConflictingExport = { + """ + class Confl { + @JSExport("value") + def hello = "foo" + + @JSExport("value") + def world = "bar" + } + """ fails() // No error test, Scala version dependent error messages + + """ + class Confl { + class Box[T](val x: T) + + @JSExport + def ub(x: Box[String]): String = x.x + @JSExport + def ub(x: Box[Int]): Int = x.x + } + """ fails() // No error test, Scala version dependent error messages + + """ + class Confl { + @JSExport + def rtType(x: scala.scalajs.js.prim.Number) = x + + @JSExport + def rtType(x: Double) = x + } + """ fails() // Error message depends on Scala version + + """ + class Confl { + @JSExport + def foo(x: Int)(ys: Int*) = x + + @JSExport + def foo(x: Int*) = x + } + """ hasErrors + """ + |newSource1.scala:7: error: Cannot disambiguate overloads for exported method $js$exported$meth$foo with types + | (x: Seq)Object + | (x: Int, ys: Seq)Object + | @JSExport + | ^ + """ + + """ + class Confl { + @JSExport + def foo(x: Int = 1) = x + @JSExport + def foo(x: String*) = x + } + """ hasErrors + """ + |newSource1.scala:4: error: Cannot disambiguate overloads for exported method $js$exported$meth$foo with types + | (x: Int)Object + | (x: Seq)Object + | @JSExport + | ^ + """ + + """ + class Confl { + @JSExport + def foo(x: scala.scalajs.js.prim.Number, y: String)(z: Int = 1) = x + @JSExport + def foo(x: Double, y: String)(z: String*) = x + } + """ fails() // Error message depends on Scala version + + """ + class A { + @JSExport + def a(x: scala.scalajs.js.Any) = 1 + + @JSExport + def a(x: Any) = 2 + } + """ fails() // Error message depends on Scala version + + } + + @Test + def noExportLocal = { + // Local class + """ + class A { + def method = { + @JSExport + class A + } + } + """ hasErrors + """ + |newSource1.scala:5: error: You may not export a local class + | @JSExport + | ^ + """ + + // Local object + """ + class A { + def method = { + @JSExport + object A + } + } + """ hasErrors + """ + |newSource1.scala:5: error: You may not export a local object + | @JSExport + | ^ + """ + + // Local method + """ + class A { + def method = { + @JSExport + def foo = 1 + } + } + """ hasErrors + """ + |newSource1.scala:5: error: You may not export a local definition + | @JSExport + | ^ + """ + + // Local val + """ + class A { + def method = { + @JSExport + val x = 1 + } + } + """ hasErrors + """ + |newSource1.scala:5: error: You may not export a local definition + | @JSExport + | ^ + """ + + // Local var + """ + class A { + def method = { + @JSExport + var x = 1 + } + } + """ hasErrors + """ + |newSource1.scala:5: error: You may not export a local definition + | @JSExport + | ^ + """ + + } + + @Test + def infoExportLocal = { + + """ + class A(@JSExport val x: Int) + """ hasErrors + """ + |newSource1.scala:3: error: You may not export a local definition. To export a (case) class field, use the meta-annotation scala.annotation.meta.field like this: @(JSExport @field). + | class A(@JSExport val x: Int) + | ^ + """ + + } + + @Test + def noMiddleVarArg = { + + """ + class A { + @JSExport + def method(xs: Int*)(ys: String) = 1 + } + """ hasErrors + """ + |newSource1.scala:4: error: In an exported method, a *-parameter must come last (through all parameter lists) + | @JSExport + | ^ + """ + + } + + @Test + def noMiddleDefaultParam = { + + """ + class A { + @JSExport + def method(x: Int = 1)(y: String) = 1 + } + """ hasErrors + """ + |newSource1.scala:4: error: In an exported method, all parameters with defaults must be at the end + | @JSExport + | ^ + """ + + } + + @Test + def noExportTrait = { + + """ + @JSExport + trait Test + """ hasErrors + """ + |newSource1.scala:3: error: You may not export a trait + | @JSExport + | ^ + """ + + } + + @Test + def noExportNonPublicClassOrObject = { + + """ + @JSExport + private class A + + @JSExport + protected[this] class B + """ hasErrors + """ + |newSource1.scala:3: error: You may only export public and protected classes + | @JSExport + | ^ + |newSource1.scala:6: error: You may only export public and protected classes + | @JSExport + | ^ + """ + + """ + @JSExport + private object A + + @JSExport + protected[this] object B + """ hasErrors + """ + |newSource1.scala:3: error: You may only export public and protected objects + | @JSExport + | ^ + |newSource1.scala:6: error: You may only export public and protected objects + | @JSExport + | ^ + """ + + } + + @Test + def noExportNonPublicMember = { + + """ + class A { + @JSExport + private def foo = 1 + + @JSExport + protected[this] def bar = 2 + } + """ hasErrors + """ + |newSource1.scala:4: error: You may only export public and protected methods + | @JSExport + | ^ + |newSource1.scala:7: error: You may only export public and protected methods + | @JSExport + | ^ + """ + + } + + @Test + def noExportNestedClass = { + + """ + class A { + @JSExport + class Nested + } + """ hasErrors + """ + |newSource1.scala:4: error: You may not export a nested class. Create an exported factory method in the outer class to work around this limitation. + | @JSExport + | ^ + """ + + """ + object A { + @JSExport + class Nested + } + """ hasErrors + """ + |newSource1.scala:4: error: You may not export a nested class. Create an exported factory method in the outer class to work around this limitation. + | @JSExport + | ^ + """ + + } + + @Test + def noExportNestedObject = { + + """ + class A { + @JSExport + object Nested + } + """ hasErrors + """ + |newSource1.scala:4: error: You may not export a nested object + | @JSExport + | ^ + """ + + """ + object A { + @JSExport + object Nested + } + """ hasErrors + """ + |newSource1.scala:4: error: You may not export a nested object + | @JSExport + | ^ + """ + + } + + @Test + def noExportJSRaw = { + + """ + import scala.scalajs.js + + @JSExport + object A extends js.Object + """ hasErrors + """ + |newSource1.scala:5: error: You may not export a class extending js.Any + | @JSExport + | ^ + """ + + """ + import scala.scalajs.js + + @JSExport + class A extends js.Object + """ hasErrors + """ + |newSource1.scala:5: error: You may not export a constructor of a subclass of js.Any + | @JSExport + | ^ + """ + + } + + @Test + def noExportJSRawMember = { + + """ + import scala.scalajs.js + + class A extends js.Object { + @JSExport + def foo: Int = js.native + } + """ hasErrors + """ + |newSource1.scala:6: error: You may not export a method of a subclass of js.Any + | @JSExport + | ^ + """ + + } + + @Test + def noBadSetterType = { + + // Bad param list + """ + class A { + @JSExport + def foo_=(x: Int, y: Int) = () + } + """ hasErrors + """ + |newSource1.scala:4: error: A method ending in _= will be exported as setter. But foo_= does not have the right signature to do so (single argument, unit return type). + | @JSExport + | ^ + """ + + // Bad return type + """ + class A { + @JSExport + def foo_=(x: Int) = "string" + } + """ hasErrors + """ + |newSource1.scala:4: error: A method ending in _= will be exported as setter. But foo_= does not have the right signature to do so (single argument, unit return type). + | @JSExport + | ^ + """ + + } + + @Test + def noBadToStringExport = { + + """ + class A { + @JSExport("toString") + def a(): Int = 5 + } + """ hasErrors + """ + |newSource1.scala:4: error: You may not export a zero-argument method named other than 'toString' under the name 'toString' + | @JSExport("toString") + | ^ + """ + + } + + @Test + def noBadNameExportAll = { + + """ + @JSExportAll + class A { + val __f = 1 + def a_= = 2 + } + """ hasErrors + """ + |newSource1.scala:5: error: An exported name may not contain a double underscore (`__`) + | val __f = 1 + | ^ + |newSource1.scala:3: error: A method ending in _= will be exported as setter. But a_= does not have the right signature to do so (single argument, unit return type). + | @JSExportAll + | ^ + """ + + } + + @Test + def noConflictingMethodAndProperty = { + + // Basic case + """ + class A { + @JSExport("a") + def bar() = 2 + + @JSExport("a") + val foo = 1 + } + """ hasErrors + """ + |newSource1.scala:7: error: Exported method a conflicts with A.$js$exported$prop$a + | @JSExport("a") + | ^ + |newSource1.scala:4: error: Exported property a conflicts with A.$js$exported$meth$a + | @JSExport("a") + | ^ + """ + + // Inherited case + """ + class A { + @JSExport("a") + def bar() = 2 + } + + class B extends A { + @JSExport("a") + def foo_=(x: Int): Unit = () + + @JSExport("a") + val foo = 1 + } + """ hasErrors + """ + |newSource1.scala:4: error: Exported property a conflicts with A.$js$exported$meth$a + | @JSExport("a") + | ^ + """ + + } + + @Test + def noOverrideNamedExport = { + + """ + class A { + @JSExportNamed + def foo(x: Int, y: Int) = 1 + } + + class B extends A { + @JSExportNamed + override def foo(x: Int, y: Int) = 2 + } + """ hasErrors + """ + |newSource1.scala:9: error: overriding method $js$exported$meth$foo in class A of type (namedArgs: Any)Any; + | method $js$exported$meth$foo cannot override final member + | @JSExportNamed + | ^ + """ + + } + + @Test + def noConflictNamedExport = { + + // Normal method + """ + class A { + @JSExportNamed + def foo(x: Int, y: Int) = 1 + + @JSExport + def foo(x: scala.scalajs.js.Any) = 2 + } + """ fails() // No error test, Scala version dependent error messages + + // Ctors + """ + class A { + @JSExportNamed + def this(x: Int) = this() + + @JSExport + def this(x: scala.scalajs.js.Any) = this + + @JSExportNamed + def this(x: Long) = this() + } + """ fails() // No error test, Scala version dependent error messages + + } + + @Test + def noNamedExportObject = { + + """ + @JSExportNamed + object A + """ hasErrors + """ + |newSource1.scala:3: error: You may not use @JSNamedExport on an object + | @JSExportNamed + | ^ + """ + + } + + @Test + def noNamedExportVarArg = { + + """ + class A { + @JSExportNamed + def foo(a: Int*) = 1 + } + """ hasErrors + """ + |newSource1.scala:4: error: You may not name-export a method with a *-parameter + | @JSExportNamed + | ^ + """ + + } + + @Test + def noNamedExportProperty = { + + // Getter + """ + class A { + @JSExportNamed + def a = 1 + } + """ hasErrors + """ + |newSource1.scala:4: error: You may not export a getter or a setter as a named export + | @JSExportNamed + | ^ + """ + + + // Setter + """ + class A { + @JSExportNamed + def a_=(x: Int) = () + } + """ hasErrors + """ + |newSource1.scala:4: error: You may not export a getter or a setter as a named export + | @JSExportNamed + | ^ + """ + + } + + @Test + def gracefulDoubleDefaultFail = { + // This used to blow up (i.e. not just fail), because PrepJSExports asked + // for the symbol of the default parameter getter of [[y]], and asserted its + // not overloaded. Since the Scala compiler only fails later on this, the + // assert got triggered and made the compiler crash + """ + class A { + @JSExport + def foo(x: String, y: String = "hello") = x + def foo(x: Int, y: String = "bar") = x + } + """ fails() + } + + @Test + def noNonLiteralExportNames = { + + """ + object A { + val a = "Hello" + final val b = "World" + } + + class B { + @JSExport(A.a) + def foo = 1 + @JSExport(A.b) + def bar = 1 + } + """ hasErrors + """ + |newSource1.scala:9: error: The argument to JSExport must be a literal string + | @JSExport(A.a) + | ^ + """ + + } + +} diff --git a/examples/scala-js/compiler/src/test/scala/scala/scalajs/compiler/test/JSInteropTest.scala b/examples/scala-js/compiler/src/test/scala/scala/scalajs/compiler/test/JSInteropTest.scala new file mode 100644 index 0000000..99c274f --- /dev/null +++ b/examples/scala-js/compiler/src/test/scala/scala/scalajs/compiler/test/JSInteropTest.scala @@ -0,0 +1,350 @@ +package scala.scalajs.compiler.test + +import scala.scalajs.compiler.test.util._ + +import org.junit.Test +import org.junit.Ignore + +class JSInteropTest extends DirectTest with TestHelpers { + + override def preamble = + """import scala.scalajs.js + """ + + @Test + def noInnerClassTraitObject: Unit = { + + val objs = List("class", "trait", "object") + + for { + outer <- objs + inner <- objs + } yield { + s""" + $outer A extends js.Object { + $inner A + } + """ hasErrors + s""" + |newSource1.scala:4: error: Traits, classes and objects extending js.Any may not have inner traits, classes or objects + | $inner A + | ${" " * inner.length}^ + """ + } + + } + + @Test + def noBadSetters = { + + """ + class A extends js.Object { + def foo_=(x: Int): Int = js.native + } + """ hasErrors + """ + |newSource1.scala:4: error: Setters that do not return Unit are not allowed in types extending js.Any + | def foo_=(x: Int): Int = js.native + | ^ + """ + + } + + @Test + def onlyJSRawTraits = { + + """ + trait A + class B extends js.Object with A + """ hasErrors + """ + |newSource1.scala:4: error: B extends A which does not extend js.Any. + | class B extends js.Object with A + | ^ + """ + + """ + trait A + class B extends js.Object with Serializable + """ hasErrors + """ + |newSource1.scala:4: error: B extends scala.Serializable which does not extend js.Any. + | class B extends js.Object with Serializable + | ^ + """ + + } + + @Test + def noAnonymousClass = { + + """ + class A { + val x = new js.Object { + def a: Int = js.native + } + } + """ hasErrors + """ + |newSource1.scala:4: error: Anonymous classes may not extend js.Any + | val x = new js.Object { + | ^ + """ + + } + + @Test + def noCaseClassObject = { + + """ + case class A(x: Int) extends js.Object + """ hasErrors + """ + |newSource1.scala:3: error: Classes and objects extending js.Any may not have a case modifier + | case class A(x: Int) extends js.Object + | ^ + """ + + """ + case object B extends js.Object + """ hasErrors + """ + |newSource1.scala:3: error: Classes and objects extending js.Any may not have a case modifier + | case object B extends js.Object + | ^ + """ + + } + + @Test + def notNested: Unit = { + + val outers = List("class", "trait") + val inners = List("trait", "class", "object") + + for { + outer <- outers + inner <- inners + } yield { + + val errTrg = if (inner == "object") "Objects" else "Classes" + + s""" + $outer A { + $inner Inner extends js.Object + } + """ hasErrors + s""" + |newSource1.scala:4: error: $errTrg extending js.Any may not be defined inside a class or trait + | $inner Inner extends js.Object + | ${" " * inner.length}^ + """ + } + + } + + @Test + def noGlobalScopeClass = { + + """ + class A extends js.GlobalScope + """ hasErrors + """ + |newSource1.scala:3: error: Only objects may extend js.GlobalScope + | class A extends js.GlobalScope + | ^ + """ + + """ + trait A extends js.GlobalScope + """ hasErrors + """ + |newSource1.scala:3: error: Only objects may extend js.GlobalScope + | trait A extends js.GlobalScope + | ^ + """ + + } + + @Test + def noLocalClass = { + + """ + object A { + def a = { + class B extends js.Object + } + } + """ hasErrors + """ + |newSource1.scala:5: error: Local classes and objects may not extend js.Any + | class B extends js.Object + | ^ + """ + + } + + @Test + def noLocalObject = { + + """ + object A { + def a = { + object B extends js.Object + } + } + """ hasErrors + """ + |newSource1.scala:5: error: Local classes and objects may not extend js.Any + | object B extends js.Object + | ^ + """ + + } + + @Test + def noExtendJSAny = { + + """ + class A extends js.Any + """ hasErrors + """ + |newSource1.scala:3: error: illegal inheritance from sealed trait Any + | class A extends js.Any + | ^ + """ + + } + + @Test + def noNativeInJSAny = { + + """ + class A extends js.Object { + @native + def value: Int = js.native + } + """ hasErrors + """ + |newSource1.scala:5: error: Methods in a js.Any may not be @native + | def value: Int = js.native + | ^ + """ + + } + + @Test + def warnJSAnyBody = { + + """ + class A extends js.Object { + def value: Int = ??? + val x: Int = ??? + } + """ hasWarns + """ + |newSource1.scala:4: warning: Members of traits, classes and objects extending js.Any may only contain members that call js.native. This will be enforced in 1.0. + | def value: Int = ??? + | ^ + |newSource1.scala:5: warning: Members of traits, classes and objects extending js.Any may only contain members that call js.native. This will be enforced in 1.0. + | val x: Int = ??? + | ^ + """ + + """ + trait A extends js.Object { + def value: Int + val x: Int + } + """ hasWarns + """ + |newSource1.scala:4: warning: Members of traits, classes and objects extending js.Any may only contain members that call js.native. This will be enforced in 1.0. + | def value: Int + | ^ + |newSource1.scala:5: warning: Members of traits, classes and objects extending js.Any may only contain members that call js.native. This will be enforced in 1.0. + | val x: Int + | ^ + """ + + } + + @Test + def noCallSecondaryCtor = { + + """ + class A(x: Int, y: Int) extends js.Object { + def this(x: Int) = this(x, 5) + def this() = this(7) + } + """ hasErrors + """ + |newSource1.scala:5: error: A secondary constructor of a class extending js.Any may only call the primary constructor + | def this() = this(7) + | ^ + """ + + } + + @Test + def noUseJsNative = { + + """ + class A { + def foo = js.native + } + """ hasErrors + """ + |newSource1.scala:4: error: js.native may only be used as stub implementation in facade types + | def foo = js.native + | ^ + """ + + } + + @Test + def warnNothingRaw = { + + """ + class A extends js.Object { + def foo = js.native + val bar = js.native + } + """ hasWarns + """ + |newSource1.scala:4: warning: The type of foo got inferred as Nothing. To suppress this warning, explicitly ascribe the type. + | def foo = js.native + | ^ + |newSource1.scala:5: warning: The type of bar got inferred as Nothing. To suppress this warning, explicitly ascribe the type. + | val bar = js.native + | ^ + """ + + } + + @Test + def noNonLiteralJSName = { + + """ + import js.annotation.JSName + + object A { + val a = "Hello" + final val b = "World" + } + + class B extends js.Object { + @JSName(A.a) + def foo: Int = js.native + @JSName(A.b) + def bar: Int = js.native + } + """ hasErrors + """ + |newSource1.scala:11: error: The argument to JSName must be a literal string + | @JSName(A.a) + | ^ + """ + + } + +} diff --git a/examples/scala-js/compiler/src/test/scala/scala/scalajs/compiler/test/OptimizationTest.scala b/examples/scala-js/compiler/src/test/scala/scala/scalajs/compiler/test/OptimizationTest.scala new file mode 100644 index 0000000..7f15c7a --- /dev/null +++ b/examples/scala-js/compiler/src/test/scala/scala/scalajs/compiler/test/OptimizationTest.scala @@ -0,0 +1,109 @@ +package scala.scalajs.compiler.test + +import util._ + +import org.junit.Test + +import scala.scalajs.ir.{Trees => js, Types => jstpe} + +class OptimizationTest extends JSASTTest { + + @Test + def unwrapScalaFunWrapper: Unit = { + + // Make sure we do not wrap and unwrap right away + """ + import scala.scalajs.js + + class A { + val jsFun: js.Function = (x: Int) => x * 2 + } + """. + hasNot("runtime.AnonFunction ctor") { + case js.New(jstpe.ClassType("sjsr_AnonFunction1"), _, _) => + } + + // Make sure our wrapper matcher has the right name + """ + import scala.scalajs.js + + class A { + val scalaFun = (x: Int) => x * 2 + val jsFun: js.Function = scalaFun + } + """. + has("runtime.AnonFunction ctor") { + case js.New(jstpe.ClassType("sjsr_AnonFunction1"), _, _) => + } + + /* Make sure js.Array(...) is optimized away completely for several kinds + * of data types. + */ + """ + import scala.scalajs.js + + class VC(val x: Int) extends AnyVal + + class A { + val a = js.Array(5, 7, 9, -3) + val b = js.Array("hello", "world") + val c = js.Array('a', 'b') + val d = js.Array(Nil) + val e = js.Array(new VC(151189)) + } + """. + hasNot("any of the wrapArray methods") { + case js.Apply(_, js.Ident(name, _), _) + if name.startsWith("wrap") && name.endsWith("__scm_WrappedArray") => + } + + /* Make sure varargs are optimized to use js.WrappedArray instead of + * scm.WrappedArray, for various data types. + */ + """ + import scala.scalajs.js + + class VC(val x: Int) extends AnyVal + + class A { + val a = List(5, 7, 9, -3) + val b = List("hello", "world") + val c = List('a', 'b') + val d = List(Nil) + val e = List(new VC(151189)) + } + """. + hasNot("any of the wrapArray methods") { + case js.Apply(_, js.Ident(name, _), _) + if name.startsWith("wrap") && name.endsWith("__scm_WrappedArray") => + } + + // Make sure our wrapper matcher has the right name + """ + import scala.scalajs.js + + class A { + val a: Seq[Int] = new Array[Int](5) + } + """. + has("one of the wrapArray methods") { + case js.Apply(_, js.Ident(name, _), _) + if name.startsWith("wrap") && name.endsWith("__scm_WrappedArray") => + } + + // Verify the optimized emitted code for 'new js.Object' and 'new js.Array' + """ + import scala.scalajs.js + + class A { + val o = new js.Object + val a = new js.Array + } + """. + hasNot("any reference to the global scope") { + case js.JSEnvInfo() => + } + + } + +} diff --git a/examples/scala-js/compiler/src/test/scala/scala/scalajs/compiler/test/PositionTest.scala b/examples/scala-js/compiler/src/test/scala/scala/scalajs/compiler/test/PositionTest.scala new file mode 100644 index 0000000..e25399b --- /dev/null +++ b/examples/scala-js/compiler/src/test/scala/scala/scalajs/compiler/test/PositionTest.scala @@ -0,0 +1,37 @@ +package scala.scalajs.compiler.test + +import util.JSASTTest + +import org.junit.Test +import org.junit.Assert._ + +import scala.reflect.internal.util.BatchSourceFile + +import scala.scalajs.ir.{Trees => js} + +class PositionTest extends JSASTTest { + + @Test + def virtualFilePosition = { + + val name = "<foo with illegal URI chars: %%>" + val source = new BatchSourceFile(name, + """class A { def x = 1 }""") + + var found = false + sourceAST(source) traverse { + case lit: js.IntLiteral => + found = true + assertEquals( + "Scheme of virtual file URI should be `virtualfile'", + "virtualfile", lit.pos.source.getScheme) + assertEquals( + "Scheme specific part of virtual file URI should be its path", + name, lit.pos.source.getSchemeSpecificPart) + } + + assertTrue("Should have IntLiteral tree", found) + + } + +} diff --git a/examples/scala-js/compiler/src/test/scala/scala/scalajs/compiler/test/util/DirectTest.scala b/examples/scala-js/compiler/src/test/scala/scala/scalajs/compiler/test/util/DirectTest.scala new file mode 100644 index 0000000..8289129 --- /dev/null +++ b/examples/scala-js/compiler/src/test/scala/scala/scalajs/compiler/test/util/DirectTest.scala @@ -0,0 +1,70 @@ +package scala.scalajs.compiler.test.util + +import scala.tools.nsc._ +import reporters.{Reporter, ConsoleReporter} +import scala.reflect.internal.util.{ SourceFile, BatchSourceFile } + +import scala.scalajs.compiler.ScalaJSPlugin + +import scala.collection.mutable + +/** This is heavily inspired by scala's partest suite's DirectTest */ +abstract class DirectTest { + + /** these arguments are always added to the args passed to newSettings */ + def extraArgs: List[String] = Nil + + /** create settings objects for test from arg string */ + def newSettings(args: List[String]) = { + val s = new Settings + s processArguments (args, true) + s + } + + def newScalaJSCompiler(args: String*): Global = { + val settings = newSettings( + List( + "-d", testOutputPath, + "-bootclasspath", scalaLibPath, + "-classpath", scalaJSLibPath) ++ + extraArgs ++ args.toList) + + lazy val global: Global = new Global(settings, newReporter(settings)) { + override lazy val plugins = newScalaJSPlugin(global) :: Nil + } + + global + } + + def newScalaJSPlugin(global: Global): ScalaJSPlugin = + new ScalaJSPlugin(global) + + def newReporter(settings: Settings) = new ConsoleReporter(settings) + + def newSources(codes: String*) = codes.toList.zipWithIndex map { + case (src, idx) => new BatchSourceFile(s"newSource${idx + 1}.scala", src) + } + + def withRun[T](global: Global)(f: global.Run => T): T = { + global.reporter.reset() + f(new global.Run) + } + + def compileSources(global: Global)(sources: SourceFile*): Boolean = { + withRun(global)(_ compileSources sources.toList) + !global.reporter.hasErrors + } + + def compileString(global: Global)(sourceCode: String): Boolean = + compileSources(global)(newSources(sourceCode): _*) + + def compileString(sourceCode: String): Boolean = + compileString(defaultGlobal)(sourceCode) + + lazy val defaultGlobal = newScalaJSCompiler() + + def testOutputPath = sys.props("scala.scalajs.compiler.test.output") + def scalaJSLibPath = sys.props("scala.scalajs.compiler.test.scalajslib") + def scalaLibPath = sys.props("scala.scalajs.compiler.test.scalalib") + +} diff --git a/examples/scala-js/compiler/src/test/scala/scala/scalajs/compiler/test/util/JSASTTest.scala b/examples/scala-js/compiler/src/test/scala/scala/scalajs/compiler/test/util/JSASTTest.scala new file mode 100644 index 0000000..d3dfd75 --- /dev/null +++ b/examples/scala-js/compiler/src/test/scala/scala/scalajs/compiler/test/util/JSASTTest.scala @@ -0,0 +1,100 @@ +package scala.scalajs.compiler.test.util + +import language.implicitConversions + +import scala.tools.nsc._ +import scala.reflect.internal.util.SourceFile + +import scala.util.control.ControlThrowable + +import org.junit.Assert._ + +import scala.scalajs.compiler.{ScalaJSPlugin, JSTreeExtractors} +import JSTreeExtractors.jse +import scala.scalajs.ir +import ir.{Trees => js} + +abstract class JSASTTest extends DirectTest { + + private var lastAST: JSAST = _ + + class JSAST(val clDefs: List[js.Tree]) { + type Pat = PartialFunction[js.Tree, Unit] + + class PFTraverser(pf: Pat) extends ir.Traversers.Traverser { + private case object Found extends ControlThrowable + + private[this] var finding = false + + def find: Boolean = { + finding = true + try { + clDefs.map(traverse) + false + } catch { + case Found => true + } + } + + def traverse(): Unit = { + finding = false + clDefs.map(traverse) + } + + override def traverse(tree: js.Tree): Unit = { + if (finding && pf.isDefinedAt(tree)) + throw Found + + if (!finding) + pf.lift(tree) + + super.traverse(tree) + } + } + + def has(trgName: String)(pf: Pat): this.type = { + val tr = new PFTraverser(pf) + assertTrue(s"AST should have $trgName", tr.find) + this + } + + def hasNot(trgName: String)(pf: Pat): this.type = { + val tr = new PFTraverser(pf) + assertFalse(s"AST should not have $trgName", tr.find) + this + } + + def traverse(pf: Pat): this.type = { + val tr = new PFTraverser(pf) + tr.traverse() + this + } + + def show: this.type = { + clDefs foreach println _ + this + } + + } + + implicit def string2ast(str: String): JSAST = stringAST(str) + + override def newScalaJSPlugin(global: Global) = new ScalaJSPlugin(global) { + override def generatedJSAST(cld: List[js.Tree]): Unit = { + lastAST = new JSAST(cld) + } + } + + def stringAST(code: String): JSAST = stringAST(defaultGlobal)(code) + def stringAST(global: Global)(code: String): JSAST = { + compileString(global)(code) + lastAST + } + + def sourceAST(source: SourceFile): JSAST = sourceAST(defaultGlobal)(source) + def sourceAST(global: Global)(source: SourceFile): JSAST = { + compileSources(global)(source) + lastAST + } + +} diff --git a/examples/scala-js/compiler/src/test/scala/scala/scalajs/compiler/test/util/TestHelpers.scala b/examples/scala-js/compiler/src/test/scala/scala/scalajs/compiler/test/util/TestHelpers.scala new file mode 100644 index 0000000..adad89c --- /dev/null +++ b/examples/scala-js/compiler/src/test/scala/scala/scalajs/compiler/test/util/TestHelpers.scala @@ -0,0 +1,68 @@ +package scala.scalajs.compiler.test.util + +import java.io._ +import scala.tools.nsc._ + +import reporters.ConsoleReporter + +import org.junit.Assert._ + +import scala.util.matching.Regex + +trait TestHelpers extends DirectTest { + + private[this] val errBuffer = new CharArrayWriter + + override def newReporter(settings: Settings) = { + val in = new BufferedReader(new StringReader("")) + val out = new PrintWriter(errBuffer) + new ConsoleReporter(settings, in, out) + } + + /** will be prefixed to every code that is compiled. use for imports */ + def preamble = "" + + /** pimps a string to compile it and apply the specified test */ + implicit class CompileTests(val code: String) { + + def hasErrors(expected: String) = { + val reps = repResult { + assertFalse("snippet shouldn't compile", compileString(preamble + code)) + } + assertEquals("should have right errors", + expected.stripMargin.trim, reps.trim) + } + + def hasWarns(expected: String) = { + val reps = repResult { + assertTrue("snippet should compile", compileString(preamble + code)) + } + assertEquals("should have right warnings", + expected.stripMargin.trim, reps.trim) + } + + def fails() = + assertFalse("snippet shouldn't compile", compileString(preamble + code)) + + def warns() = { + val reps = repResult { + assertTrue("snippet should compile", compileString(preamble + code)) + } + assertFalse("should have warnings", reps.isEmpty) + } + + def succeeds() = + assertTrue("snippet should compile", compileString(preamble + code)) + + private def repResult(body: => Unit) = { + errBuffer.reset() + body + errBuffer.toString + } + } + + implicit class CodeWrappers(sc: StringContext) { + def expr() = new CompileTests(s"class A { ${sc.parts.mkString} }") + } + +} diff --git a/examples/scala-js/examples/helloworld/HelloWorld.scala b/examples/scala-js/examples/helloworld/HelloWorld.scala new file mode 100644 index 0000000..fd33060 --- /dev/null +++ b/examples/scala-js/examples/helloworld/HelloWorld.scala @@ -0,0 +1,86 @@ +/* Scala.js example code + * Public domain + * @author Sébastien Doeraene + */ + +package helloworld + +import scala.scalajs.js +import js.annotation.JSName + +object HelloWorld extends js.JSApp { + def main() { + if (!(!js.Dynamic.global.document) && + !(!js.Dynamic.global.document.getElementById("playground"))) { + sayHelloFromDOM() + sayHelloFromTypedDOM() + sayHelloFromJQuery() + sayHelloFromTypedJQuery() + } else { + println("Hello world!") + } + } + + def sayHelloFromDOM() { + val document = js.Dynamic.global.document + val playground = document.getElementById("playground") + + val newP = document.createElement("p") + newP.innerHTML = "Hello world! <i>-- DOM</i>" + playground.appendChild(newP) + } + + def sayHelloFromTypedDOM() { + val document = window.document + val playground = document.getElementById("playground") + + val newP = document.createElement("p") + newP.innerHTML = "Hello world! <i>-- typed DOM</i>" + playground.appendChild(newP) + } + + def sayHelloFromJQuery() { + // val $ is fine too, but not very recommended in Scala code + val jQuery = js.Dynamic.global.jQuery + val newP = jQuery("<p>").html("Hello world! <i>-- jQuery</i>") + newP.appendTo(jQuery("#playground")) + } + + def sayHelloFromTypedJQuery() { + val jQuery = helloworld.JQuery + val newP = jQuery("<p>").html("Hello world! <i>-- typed jQuery</i>") + newP.appendTo(jQuery("#playground")) + } +} + +object window extends js.GlobalScope { + val document: DOMDocument = js.native + + def alert(msg: String): Unit = js.native +} + +trait DOMDocument extends js.Object { + def getElementById(id: String): DOMElement = js.native + def createElement(tag: String): DOMElement = js.native +} + +trait DOMElement extends js.Object { + var innerHTML: String = js.native + + def appendChild(child: DOMElement): Unit = js.native +} + +@JSName("jQuery") +object JQuery extends js.Object { + def apply(selector: String): JQuery = js.native +} + +trait JQuery extends js.Object { + def text(value: String): JQuery = js.native + def text(): String = js.native + + def html(value: String): JQuery = js.native + def html(): String = js.native + + def appendTo(parent: JQuery): JQuery = js.native +} diff --git a/examples/scala-js/examples/helloworld/helloworld-2.10-fastopt.html b/examples/scala-js/examples/helloworld/helloworld-2.10-fastopt.html new file mode 100644 index 0000000..98b2705 --- /dev/null +++ b/examples/scala-js/examples/helloworld/helloworld-2.10-fastopt.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<html> +<head> + <title>Hello world - Scala.js example</title> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> +</head> +<body> + +<div id="playground"> +</div> + +<script type="text/javascript" src="http://code.jquery.com/jquery-1.9.1.min.js"></script> + +<script type="text/javascript" src="./target/scala-2.10/helloworld-fastopt.js"></script> + +<script type="text/javascript" src="./target/scala-2.10/helloworld-launcher.js"></script> + +</body> +</html> diff --git a/examples/scala-js/examples/helloworld/helloworld-2.10.html b/examples/scala-js/examples/helloworld/helloworld-2.10.html new file mode 100644 index 0000000..80b00b9 --- /dev/null +++ b/examples/scala-js/examples/helloworld/helloworld-2.10.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<html> +<head> + <title>Hello world - Scala.js example</title> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> +</head> +<body> + +<div id="playground"> +</div> + +<script type="text/javascript" src="http://code.jquery.com/jquery-1.9.1.min.js"></script> + +<script type="text/javascript" src="./target/scala-2.10/helloworld-opt.js"></script> + +<script type="text/javascript" src="./target/scala-2.10/helloworld-launcher.js"></script> + +</body> +</html> diff --git a/examples/scala-js/examples/helloworld/helloworld-2.11-fastopt.html b/examples/scala-js/examples/helloworld/helloworld-2.11-fastopt.html new file mode 100644 index 0000000..dbf5598 --- /dev/null +++ b/examples/scala-js/examples/helloworld/helloworld-2.11-fastopt.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<html> +<head> + <title>Hello world - Scala.js example</title> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> +</head> +<body> + +<div id="playground"> +</div> + +<script type="text/javascript" src="http://code.jquery.com/jquery-1.9.1.min.js"></script> + +<script type="text/javascript" src="./target/scala-2.11/helloworld-fastopt.js"></script> + +<script type="text/javascript" src="./target/scala-2.11/helloworld-launcher.js"></script> + +</body> +</html> diff --git a/examples/scala-js/examples/helloworld/helloworld-2.11.html b/examples/scala-js/examples/helloworld/helloworld-2.11.html new file mode 100644 index 0000000..9c9a3a1 --- /dev/null +++ b/examples/scala-js/examples/helloworld/helloworld-2.11.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<html> +<head> + <title>Hello world - Scala.js example</title> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> +</head> +<body> + +<div id="playground"> +</div> + +<script type="text/javascript" src="http://code.jquery.com/jquery-1.9.1.min.js"></script> + +<script type="text/javascript" src="./target/scala-2.11/helloworld-opt.js"></script> + +<script type="text/javascript" src="./target/scala-2.11/helloworld-launcher.js"></script> + +</body> +</html> diff --git a/examples/scala-js/examples/helloworld/startup.js b/examples/scala-js/examples/helloworld/startup.js new file mode 100644 index 0000000..f45e4cb --- /dev/null +++ b/examples/scala-js/examples/helloworld/startup.js @@ -0,0 +1,8 @@ +/* Scala.js example code + * Public domain + * Author: Sébastien Doeraene + */ + +$(function() { + ScalaJS.modules.helloworld_HelloWorld().main(); +}); diff --git a/examples/scala-js/examples/reversi/JSTypes.scala b/examples/scala-js/examples/reversi/JSTypes.scala new file mode 100644 index 0000000..cc0e5a4 --- /dev/null +++ b/examples/scala-js/examples/reversi/JSTypes.scala @@ -0,0 +1,87 @@ +/* Scala.js example code + * Public domain + * @author Sébastien Doeraene + */ + +package reversi + +import scala.scalajs.js + +trait Window extends js.Object { + val document: DOMDocument = js.native + + def alert(msg: String): Unit = js.native +} + +trait DOMDocument extends js.Object { + def getElementById(id: String): DOMElement = js.native + def createElement(tag: String): DOMElement = js.native +} + +trait DOMElement extends js.Object { + var innerHTML: String = js.native + + def appendChild(child: DOMElement): Unit = js.native +} + +trait JQueryStatic extends js.Object { + def apply(arg: js.Any): JQuery = js.native + def apply(arg: js.Any, attributes: js.Any): JQuery = js.native +} + +trait JQuery extends js.Object { + def get(index: Int): DOMElement = js.native + + def text(value: String): JQuery = js.native + def text(): String = js.native + + def html(value: String): JQuery = js.native + def html(): String = js.native + + def prop(property: String): js.Any = js.native + def prop(property: String, value: js.Any): JQuery = js.native + + def offset(): JQueryOffset = js.native + + def appendTo(parent: JQuery): JQuery = js.native + def append(children: JQuery): JQuery = js.native + + def addClass(classes: String): JQuery = js.native + def removeClass(classes: String): JQuery = js.native + + def each[U](callback: js.Function2[Int, js.Dynamic, U]): JQuery = js.native + + def click[U](handler: js.Function0[U]): JQuery = js.native + def click[U](handler: js.Function1[JQueryEvent, U]): JQuery = js.native +} + +trait JQueryOffset extends js.Object { + val top: Double = js.native + val left: Double = js.native +} + +trait JQueryEvent extends js.Object { + val pageX: Double = js.native + val pageY: Double = js.native +} + +trait HTMLCanvasElement extends DOMElement { + def getContext(kind: String): js.Any = js.native // depends on the kind +} + +trait CanvasRenderingContext2D extends js.Object { + val canvas: HTMLCanvasElement = js.native + + var fillStyle: String = js.native + var lineWidth: Double = js.native + + def fillRect(x: Double, y: Double, w: Double, h: Double): Unit = js.native + def strokeRect(x: Double, y: Double, w: Double, h: Double): Unit = js.native + + def beginPath(): Unit = js.native + def fill(): Unit = js.native + def stroke(): Unit = js.native + + def arc(x: Double, y: Double, radius: Double, startAngle: Double, + endAngle: Double, anticlockwise: Boolean): Unit = js.native +} diff --git a/examples/scala-js/examples/reversi/Reversi.scala b/examples/scala-js/examples/reversi/Reversi.scala new file mode 100644 index 0000000..b4a34a4 --- /dev/null +++ b/examples/scala-js/examples/reversi/Reversi.scala @@ -0,0 +1,266 @@ +/* Scala.js example code + * Public domain + * @author Sébastien Doeraene + */ + +package reversi + +import scala.annotation.tailrec +import scala.scalajs.js +import scala.scalajs.js.annotation.JSExport + +sealed abstract class OptPlayer + +sealed abstract class Player extends OptPlayer { + val opponent: Player +} + +case object NoPlayer extends OptPlayer + +case object White extends Player { + val opponent = Black +} + +case object Black extends Player { + val opponent = White +} + +@JSExport("Reversi") +class Reversi(jQuery: JQueryStatic, playground: JQuery) { + + // The Model ----------------------------------------------------------------- + + val BoardSize = 8 // size of a Reversi board + + def inBounds(index: Int): Boolean = index >= 0 && index < BoardSize + def inBounds(x: Int, y: Int): Boolean = inBounds(x) && inBounds(y) + + class Square(val x: Int, val y: Int) { + private var _owner: OptPlayer = NoPlayer + + var onOwnerChange: (OptPlayer, OptPlayer) => Unit = (oldP, newP) => () + + def owner = _owner + def owner_=(value: OptPlayer) { + val previous = _owner + if (value != previous) { + _owner = value + onOwnerChange(previous, value) + } + } + + override def toString() = "Square("+x+", "+y+", "+owner+")" + } + + val board = Array.tabulate[Square](BoardSize, BoardSize)(new Square(_, _)) + val allSquares = board.flatten + var currentPlayer: Player = White // Irrelevant, set again in startGame() + + // The GUI ------------------------------------------------------------------- + + val resetButton = createResetButton() + val passButton = createPassButton() + val status = createStatus() + buildUI() + + def createResetButton() = { + jQuery("<input>", js.Dynamic.literal( + `type` = "button", value = "Reset" + )).click(reset _) + } + + def createPassButton() = { + jQuery("<input>", js.Dynamic.literal( + `type` = "button", value = "Pass" + )).click(pass _) + } + + def createStatus() = { + jQuery("<span>") + } + + def buildUI() { + // Some dimensions + val SquareSizePx = 48 + val HalfSquareSizePx = SquareSizePx/2 + val PawnRadiusPx = HalfSquareSizePx-4 + val BoardSizePx = BoardSize*SquareSizePx + 3 + + // Creat the board canvas + val boardCanvas = jQuery( + "<canvas width='"+BoardSizePx+"' height='"+BoardSizePx+"'></canvas>") + val domCanvas = boardCanvas.get(0).asInstanceOf[HTMLCanvasElement] + val context = domCanvas.getContext("2d").asInstanceOf[CanvasRenderingContext2D] + + playground.append(jQuery("<div>").append(boardCanvas)) + + /** Draw the specified square on the board canvas */ + def drawSquare(square: Square) { + val x = square.x * SquareSizePx + val y = square.y * SquareSizePx + + // Background + context.fillStyle = "green" + context.fillRect(x, y, SquareSizePx, SquareSizePx) + + // Border + context.fillStyle = "black" + context.lineWidth = 3 + context.strokeRect(x, y, SquareSizePx, SquareSizePx) + + // Pawn + if (square.owner != NoPlayer) { + context.fillStyle = if (square.owner == White) "white" else "black" + context.beginPath() + context.arc(x+HalfSquareSizePx, y+HalfSquareSizePx, PawnRadiusPx, + 0, 2*Math.PI, true) + context.fill() + } + } + + // Draw squares now, and everytime they change ownership + for (square <- allSquares) { + drawSquare(square) + square.onOwnerChange = { (prevOwner, newOwner) => + drawSquare(square) + } + } + + // Configure clicks on the board + boardCanvas.click { (event: JQueryEvent) => + val offsetX = event.pageX - boardCanvas.offset().left + val offsetY = event.pageY - boardCanvas.offset().top + val x = offsetX.toInt / SquareSizePx + val y = offsetY.toInt / SquareSizePx + + if (inBounds(x, y)) + clickSquare(board(x)(y)) + } + + // Build the status bar + val statusBar = jQuery("<p>") + statusBar.append(resetButton) + statusBar.append(status) + statusBar.append(passButton) + playground.append(statusBar) + } + + // The Game ------------------------------------------------------------------ + + def reset() { + startGame() + } + + @JSExport + def startGame() { + // Set up the board + allSquares foreach (_.owner = NoPlayer) + board(3)(3).owner = White + board(3)(4).owner = Black + board(4)(3).owner = Black + board(4)(4).owner = White + + // White begins + currentPlayer = White + + // Let's go! + startTurn() + } + + def startTurn() { + val (scoreWhite, scoreBlack) = computeScore() + status.text(currentPlayer+"'s turn -- White: "+scoreWhite+ + " -- Black: "+scoreBlack) + + passButton.prop("disabled", true) + + if (!existsValidMove()) { + // Test if the other player can do something + currentPlayer = currentPlayer.opponent + val opponentCanDoSomething = existsValidMove() + currentPlayer = currentPlayer.opponent + + if (opponentCanDoSomething) { + passButton.prop("disabled", false) + } else { + // End of game + val winnerText = + if (scoreWhite > scoreBlack) "White won!" + else if (scoreBlack > scoreWhite) "Black won!" + else "Draw" + status.text("Game finished -- White: "+scoreWhite+ + " -- Black: "+scoreBlack+" -- "+winnerText) + } + } + } + + def clickSquare(square: Square) { + val toFlip = computeFlips(square) + if (!toFlip.isEmpty) { + (square :: toFlip) foreach (_.owner = currentPlayer) + nextTurn() + } + } + + def pass() { + assert(!existsValidMove()) + nextTurn() + } + + def existsValidMove(): Boolean = { + allSquares.exists(isValidMove) + } + + def isValidMove(square: Square): Boolean = { + !computeFlips(square).isEmpty + } + + def computeFlips(square: Square): List[Square] = { + if (square.owner != NoPlayer) Nil + else { + for { + i <- (-1 to 1).toList + j <- -1 to 1 + if i != 0 || j != 0 + flip <- computeFlipsInDirection(square.x, square.y, i, j) + } yield flip + } + } + + def computeFlipsInDirection(x: Int, y: Int, + dirx: Int, diry: Int): List[Square] = { + + val allInDir = allSquaresInDirection(x, y, dirx, diry) + val (toFlip, remaining) = + allInDir.span(_.owner == currentPlayer.opponent) + + val success = remaining.headOption.exists(_.owner == currentPlayer) + if (success) toFlip + else Nil + } + + def allSquaresInDirection(fromx: Int, fromy: Int, + dirx: Int, diry: Int): List[Square] = { + val nextx = fromx + dirx + val nexty = fromy + diry + if (inBounds(nextx, nexty)) + board(nextx)(nexty) :: allSquaresInDirection(nextx, nexty, dirx, diry) + else + Nil + } + + def computeScore(): (Int, Int) = { + allSquares.foldLeft((0, 0)) { case ((white, black), square) => + square.owner match { + case White => (white+1, black) + case Black => (white, black+1) + case NoPlayer => (white, black) + } + } + } + + def nextTurn() { + currentPlayer = currentPlayer.opponent + startTurn() + } +} diff --git a/examples/scala-js/examples/reversi/reversi-2.10-fastopt.html b/examples/scala-js/examples/reversi/reversi-2.10-fastopt.html new file mode 100644 index 0000000..46cd1c7 --- /dev/null +++ b/examples/scala-js/examples/reversi/reversi-2.10-fastopt.html @@ -0,0 +1,30 @@ +<!DOCTYPE html> +<html> +<head> + <title>Reversi - Scala.js example</title> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> +</head> +<body> + +<h1>Reversi - Scala.js example</h1> + +<p>Somewhat inspired by +<a href="http://davidbau.com/reversi/">http://davidbau.com/reversi/</a></p> + +<div id="playground"> +</div> + +<script type="text/javascript" src="http://code.jquery.com/jquery-1.9.1.min.js"></script> + +<script type="text/javascript" src="./target/scala-2.10/reversi-fastopt.js"></script> + +<script type="text/javascript"> +$(function() { + var mainInstance = new Reversi( + jQuery, jQuery("#playground")); + mainInstance.startGame(); +}); +</script> + +</body> +</html> diff --git a/examples/scala-js/examples/reversi/reversi-2.10.html b/examples/scala-js/examples/reversi/reversi-2.10.html new file mode 100644 index 0000000..5f7b696 --- /dev/null +++ b/examples/scala-js/examples/reversi/reversi-2.10.html @@ -0,0 +1,30 @@ +<!DOCTYPE html> +<html> +<head> + <title>Reversi - Scala.js example</title> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> +</head> +<body> + +<h1>Reversi - Scala.js example</h1> + +<p>Somewhat inspired by +<a href="http://davidbau.com/reversi/">http://davidbau.com/reversi/</a></p> + +<div id="playground"> +</div> + +<script type="text/javascript" src="http://code.jquery.com/jquery-1.9.1.min.js"></script> + +<script type="text/javascript" src="./target/scala-2.10/reversi-opt.js"></script> + +<script type="text/javascript"> +$(function() { + var mainInstance = new Reversi( + jQuery, jQuery("#playground")); + mainInstance.startGame(); +}); +</script> + +</body> +</html> diff --git a/examples/scala-js/examples/reversi/reversi-2.11-fastopt.html b/examples/scala-js/examples/reversi/reversi-2.11-fastopt.html new file mode 100644 index 0000000..524e716 --- /dev/null +++ b/examples/scala-js/examples/reversi/reversi-2.11-fastopt.html @@ -0,0 +1,30 @@ +<!DOCTYPE html> +<html> +<head> + <title>Reversi - Scala.js example</title> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> +</head> +<body> + +<h1>Reversi - Scala.js example</h1> + +<p>Somewhat inspired by +<a href="http://davidbau.com/reversi/">http://davidbau.com/reversi/</a></p> + +<div id="playground"> +</div> + +<script type="text/javascript" src="http://code.jquery.com/jquery-1.9.1.min.js"></script> + +<script type="text/javascript" src="./target/scala-2.11/reversi-fastopt.js"></script> + +<script type="text/javascript"> +$(function() { + var mainInstance = new Reversi( + jQuery, jQuery("#playground")); + mainInstance.startGame(); +}); +</script> + +</body> +</html> diff --git a/examples/scala-js/examples/reversi/reversi-2.11.html b/examples/scala-js/examples/reversi/reversi-2.11.html new file mode 100644 index 0000000..b1a6d08 --- /dev/null +++ b/examples/scala-js/examples/reversi/reversi-2.11.html @@ -0,0 +1,30 @@ +<!DOCTYPE html> +<html> +<head> + <title>Reversi - Scala.js example</title> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> +</head> +<body> + +<h1>Reversi - Scala.js example</h1> + +<p>Somewhat inspired by +<a href="http://davidbau.com/reversi/">http://davidbau.com/reversi/</a></p> + +<div id="playground"> +</div> + +<script type="text/javascript" src="http://code.jquery.com/jquery-1.9.1.min.js"></script> + +<script type="text/javascript" src="./target/scala-2.11/reversi-opt.js"></script> + +<script type="text/javascript"> +$(function() { + var mainInstance = new Reversi( + jQuery, jQuery("#playground")); + mainInstance.startGame(); +}); +</script> + +</body> +</html> diff --git a/examples/scala-js/examples/testing/src/main/scala/ElementCreator.scala b/examples/scala-js/examples/testing/src/main/scala/ElementCreator.scala new file mode 100644 index 0000000..ccb3600 --- /dev/null +++ b/examples/scala-js/examples/testing/src/main/scala/ElementCreator.scala @@ -0,0 +1,7 @@ +import scala.scalajs.js.Dynamic.global + +object ElementCreator { + val jQ = global.jQuery + + def create() = jQ("body").append(jQ("<h1>Test</h1>")) +} diff --git a/examples/scala-js/examples/testing/src/test/scala/CollectionTest.scala b/examples/scala-js/examples/testing/src/test/scala/CollectionTest.scala new file mode 100644 index 0000000..a586ca2 --- /dev/null +++ b/examples/scala-js/examples/testing/src/test/scala/CollectionTest.scala @@ -0,0 +1,17 @@ +import scala.scalajs.js +import scala.scalajs.js.Dynamic.global +import scala.scalajs.js.JSConverters._ + +import org.scalajs.jasminetest.JasmineTest + +object CollectionTest extends JasmineTest { + + describe("Array") { + + it("should be able to map and filter integers") { + val array = Array(5, 7, 2, 6, -30, 33, 66, 76, 75, 0) + val result = array.filter(_.toInt % 3 != 0).map(x => x*x) + expect(result.toJSArray).toEqual(js.Array(25, 49, 4, 76*76)) + } + } +} diff --git a/examples/scala-js/examples/testing/src/test/scala/ElementCreatorTest.scala b/examples/scala-js/examples/testing/src/test/scala/ElementCreatorTest.scala new file mode 100644 index 0000000..43f6756 --- /dev/null +++ b/examples/scala-js/examples/testing/src/test/scala/ElementCreatorTest.scala @@ -0,0 +1,24 @@ +import scala.scalajs.js +import scala.scalajs.js.Dynamic.global + +import org.scalajs.jasminetest.JasmineTest + +object ElementCreatorTest extends JasmineTest { + + describe("ElementCreator") { + + it("should be able to create an element in the body") { + // create the element + ElementCreator.create() + + // jquery would make this easier, but I wanted to + // only use pure html in the test itself + val body = global.document.getElementsByTagName("body") + .asInstanceOf[js.Array[js.Dynamic]].head + + // the Scala.js DOM API would make this easier + expect(body.lastChild.tagName.toString == "H1").toBeTruthy + expect(body.lastChild.innerHTML.toString == "Test").toBeTruthy + } + } +} diff --git a/examples/scala-js/examples/testing/testing-2.10-fastopt.html b/examples/scala-js/examples/testing/testing-2.10-fastopt.html new file mode 100644 index 0000000..04ded2f --- /dev/null +++ b/examples/scala-js/examples/testing/testing-2.10-fastopt.html @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<html> +<head> + <title>Testing - Jasmine HTML reporter example</title> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <link rel="stylesheet" type="text/css" href="http://cdn.jsdelivr.net/jasmine/1.3.1/jasmine.css"></link> +</head> +<body> +<script type="text/javascript" src="http://code.jquery.com/jquery-1.9.1.min.js"></script> +<script type="text/javascript" src="http://cdn.jsdelivr.net/jasmine/1.3.1/jasmine.js"></script> +<script type="text/javascript" src="http://cdn.jsdelivr.net/jasmine/1.3.1/jasmine-html.js"></script> +<script type="text/javascript" src="./target/scala-2.10/testing-test-fastopt.js"></script> + +<script type="text/javascript"> +$(function() { + // Load tests + CollectionTest(); + ElementCreatorTest(); + + // Setup and run Jasmine + var jasmineEnv = jasmine.getEnv(); + var htmlReporter = new jasmine.HtmlReporter(); + jasmineEnv.addReporter(htmlReporter); + jasmineEnv.specFilter = function(spec) { + return htmlReporter.specFilter(spec); + }; + jasmineEnv.execute(); +}); +</script> + +</body> +</html> diff --git a/examples/scala-js/examples/testing/testing-2.10.html b/examples/scala-js/examples/testing/testing-2.10.html new file mode 100644 index 0000000..bbb0cfe --- /dev/null +++ b/examples/scala-js/examples/testing/testing-2.10.html @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<html> +<head> + <title>Testing - Jasmine HTML reporter example</title> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <link rel="stylesheet" type="text/css" href="http://cdn.jsdelivr.net/jasmine/1.3.1/jasmine.css"></link> +</head> +<body> +<script type="text/javascript" src="http://code.jquery.com/jquery-1.9.1.min.js"></script> +<script type="text/javascript" src="http://cdn.jsdelivr.net/jasmine/1.3.1/jasmine.js"></script> +<script type="text/javascript" src="http://cdn.jsdelivr.net/jasmine/1.3.1/jasmine-html.js"></script> +<script type="text/javascript" src="./target/scala-2.10/testing-test-opt.js"></script> + +<script type="text/javascript"> +$(function() { + // Load tests + CollectionTest(); + ElementCreatorTest(); + + // Setup and run Jasmine + var jasmineEnv = jasmine.getEnv(); + var htmlReporter = new jasmine.HtmlReporter(); + jasmineEnv.addReporter(htmlReporter); + jasmineEnv.specFilter = function(spec) { + return htmlReporter.specFilter(spec); + }; + jasmineEnv.execute(); +}); +</script> + +</body> +</html> diff --git a/examples/scala-js/examples/testing/testing-2.11-fastopt.html b/examples/scala-js/examples/testing/testing-2.11-fastopt.html new file mode 100644 index 0000000..a87f635 --- /dev/null +++ b/examples/scala-js/examples/testing/testing-2.11-fastopt.html @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<html> +<head> + <title>Testing - Jasmine HTML reporter example</title> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <link rel="stylesheet" type="text/css" href="http://cdn.jsdelivr.net/jasmine/1.3.1/jasmine.css"></link> +</head> +<body> +<script type="text/javascript" src="http://code.jquery.com/jquery-1.9.1.min.js"></script> +<script type="text/javascript" src="http://cdn.jsdelivr.net/jasmine/1.3.1/jasmine.js"></script> +<script type="text/javascript" src="http://cdn.jsdelivr.net/jasmine/1.3.1/jasmine-html.js"></script> +<script type="text/javascript" src="./target/scala-2.11/testing-test-fastopt.js"></script> + +<script type="text/javascript"> +$(function() { + // Load tests + CollectionTest(); + ElementCreatorTest(); + + // Setup and run Jasmine + var jasmineEnv = jasmine.getEnv(); + var htmlReporter = new jasmine.HtmlReporter(); + jasmineEnv.addReporter(htmlReporter); + jasmineEnv.specFilter = function(spec) { + return htmlReporter.specFilter(spec); + }; + jasmineEnv.execute(); +}); +</script> + +</body> +</html> diff --git a/examples/scala-js/examples/testing/testing-2.11.html b/examples/scala-js/examples/testing/testing-2.11.html new file mode 100644 index 0000000..9902c3f --- /dev/null +++ b/examples/scala-js/examples/testing/testing-2.11.html @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<html> +<head> + <title>Testing - Jasmine HTML reporter example</title> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <link rel="stylesheet" type="text/css" href="http://cdn.jsdelivr.net/jasmine/1.3.1/jasmine.css"></link> +</head> +<body> +<script type="text/javascript" src="http://code.jquery.com/jquery-1.9.1.min.js"></script> +<script type="text/javascript" src="http://cdn.jsdelivr.net/jasmine/1.3.1/jasmine.js"></script> +<script type="text/javascript" src="http://cdn.jsdelivr.net/jasmine/1.3.1/jasmine-html.js"></script> +<script type="text/javascript" src="./target/scala-2.11/testing-test-opt.js"></script> + +<script type="text/javascript"> +$(function() { + // Load tests + CollectionTest(); + ElementCreatorTest(); + + // Setup and run Jasmine + var jasmineEnv = jasmine.getEnv(); + var htmlReporter = new jasmine.HtmlReporter(); + jasmineEnv.addReporter(htmlReporter); + jasmineEnv.specFilter = function(spec) { + return htmlReporter.specFilter(spec); + }; + jasmineEnv.execute(); +}); +</script> + +</body> +</html> diff --git a/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/ClassKind.scala b/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/ClassKind.scala new file mode 100644 index 0000000..5092d2c --- /dev/null +++ b/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/ClassKind.scala @@ -0,0 +1,53 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js IR ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.ir + +import scala.annotation.switch + +sealed abstract class ClassKind { + import ClassKind._ + + def isClass = this match { + case Class | ModuleClass => true + case _ => false + } + + def isType = this match { + case TraitImpl => false + case _ => true + } +} + +object ClassKind { + case object Class extends ClassKind + case object ModuleClass extends ClassKind + case object Interface extends ClassKind + case object RawJSType extends ClassKind + case object HijackedClass extends ClassKind + case object TraitImpl extends ClassKind + + private[ir] def toByte(kind: ClassKind): Byte = kind match { + case ClassKind.Class => 1 + case ClassKind.ModuleClass => 2 + case ClassKind.Interface => 3 + case ClassKind.RawJSType => 4 + case ClassKind.HijackedClass => 5 + case ClassKind.TraitImpl => 6 + } + + private[ir] def fromByte(b: Byte): ClassKind = (b: @switch) match { + case 1 => ClassKind.Class + case 2 => ClassKind.ModuleClass + case 3 => ClassKind.Interface + case 4 => ClassKind.RawJSType + case 5 => ClassKind.HijackedClass + case 6 => ClassKind.TraitImpl + } +} diff --git a/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Definitions.scala b/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Definitions.scala new file mode 100644 index 0000000..0762602 --- /dev/null +++ b/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Definitions.scala @@ -0,0 +1,144 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js IR ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.ir + +object Definitions { + val ObjectClass = "O" + val ClassClass = "jl_Class" + + val StringClass = "T" + + val PrimitiveClasses = Set("V", "Z", "C", "B", "S", "I", "J", "F", "D") + + def isPrimitiveClass(encodedName: String): Boolean = + PrimitiveClasses.contains(encodedName) + + val BoxedUnitClass = "sr_BoxedUnit" + val BoxedBooleanClass = "jl_Boolean" + val BoxedCharacterClass = "jl_Character" + val BoxedByteClass = "jl_Byte" + val BoxedShortClass = "jl_Short" + val BoxedIntegerClass = "jl_Integer" + val BoxedLongClass = "jl_Long" + val BoxedFloatClass = "jl_Float" + val BoxedDoubleClass = "jl_Double" + + val CharSequenceClass = "jl_CharSequence" + val SerializableClass = "Ljava_io_Serializable" + val ComparableClass = "jl_Comparable" + val NumberClass = "jl_Number" + + val HijackedBoxedClasses = Set( + BoxedUnitClass, BoxedBooleanClass, BoxedByteClass, BoxedShortClass, + BoxedIntegerClass, BoxedLongClass, BoxedFloatClass, BoxedDoubleClass) + val HijackedClasses = + HijackedBoxedClasses + StringClass + + val AncestorsOfStringClass = Set( + CharSequenceClass, ComparableClass, SerializableClass) + val AncestorsOfHijackedNumberClasses = Set( + NumberClass, ComparableClass, SerializableClass) + val AncestorsOfBoxedBooleanClass = Set( + ComparableClass, SerializableClass) + + val AncestorsOfHijackedClasses = + AncestorsOfStringClass ++ AncestorsOfHijackedNumberClasses ++ + AncestorsOfBoxedBooleanClass + + val RuntimeNullClass = "sr_Null$" + val RuntimeNothingClass = "sr_Nothing$" + + val ThrowableClass = "jl_Throwable" + + /** Encodes a class name. */ + def encodeClassName(fullName: String): String = { + val base = fullName.replace("_", "$und").replace(".", "_") + val encoded = compressedClasses.getOrElse(base, { + compressedPrefixes collectFirst { + case (prefix, compressed) if base.startsWith(prefix) => + compressed + base.substring(prefix.length) + } getOrElse { + "L"+base + } + }) + if (Trees.isKeyword(encoded) || encoded.charAt(0).isDigit || + encoded.charAt(0) == '$') { + "$" + encoded + } else encoded + } + + // !!! Duplicate logic: this code must be in sync with runtime.StackTrace + + /** Decodes a class name encoded with [[encodeClassName]]. */ + def decodeClassName(encodedName: String): String = { + val encoded = + if (encodedName.charAt(0) == '$') encodedName.substring(1) + else encodedName + val base = decompressedClasses.getOrElse(encoded, { + decompressedPrefixes collectFirst { + case (prefix, decompressed) if encoded.startsWith(prefix) => + decompressed + encoded.substring(prefix.length) + } getOrElse { + assert(!encoded.isEmpty && encoded.charAt(0) == 'L', + s"Cannot decode invalid encoded name '$encodedName'") + encoded.substring(1) + } + }) + base.replace("_", ".").replace("$und", "_") + } + + private val compressedClasses: Map[String, String] = Map( + "java_lang_Object" -> "O", + "java_lang_String" -> "T", + "scala_Unit" -> "V", + "scala_Boolean" -> "Z", + "scala_Char" -> "C", + "scala_Byte" -> "B", + "scala_Short" -> "S", + "scala_Int" -> "I", + "scala_Long" -> "J", + "scala_Float" -> "F", + "scala_Double" -> "D" + ) ++ ( + for (index <- 2 to 22) + yield s"scala_Tuple$index" -> ("T"+index) + ) ++ ( + for (index <- 0 to 22) + yield s"scala_Function$index" -> ("F"+index) + ) + + private val decompressedClasses: Map[String, String] = + compressedClasses map { case (a, b) => (b, a) } + + private val compressedPrefixes = Seq( + "scala_scalajs_runtime_" -> "sjsr_", + "scala_scalajs_" -> "sjs_", + "scala_collection_immutable_" -> "sci_", + "scala_collection_mutable_" -> "scm_", + "scala_collection_generic_" -> "scg_", + "scala_collection_" -> "sc_", + "scala_runtime_" -> "sr_", + "scala_" -> "s_", + "java_lang_" -> "jl_", + "java_util_" -> "ju_" + ) + + private val decompressedPrefixes: Seq[(String, String)] = + compressedPrefixes map { case (a, b) => (b, a) } + + /* Common predicates on encoded names */ + + def isConstructorName(name: String): Boolean = + name.startsWith("init___") + + def isReflProxyName(name: String): Boolean = + name.endsWith("__") && !isConstructorName(name) + +} diff --git a/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Hashers.scala b/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Hashers.scala new file mode 100644 index 0000000..168d7c1 --- /dev/null +++ b/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Hashers.scala @@ -0,0 +1,459 @@ +package scala.scalajs.ir + +import java.security.{MessageDigest, DigestOutputStream} +import java.io.{OutputStream, DataOutputStream} +import java.util.Arrays + +import Trees._ +import Types._ +import Tags._ + +object Hashers { + + def hashMethodDef(methodDef: MethodDef): MethodDef = { + if (methodDef.hash.isDefined) methodDef + else { + val hasher = new TreeHasher() + val MethodDef(name, args, resultType, body) = methodDef + + hasher.mixPos(methodDef.pos) + hasher.mixPropertyName(name) + hasher.mixTrees(args) + hasher.mixType(resultType) + hasher.mixTree(body) + + val hash = hasher.finalizeHash() + + MethodDef(name, args, resultType, body)(Some(hash))(methodDef.pos) + } + } + + /** Hash definitions from a ClassDef where applicable */ + def hashDefs(defs: List[Tree]): List[Tree] = defs map { + case methodDef: MethodDef => hashMethodDef(methodDef) + case otherDef => otherDef + } + + /** Hash the definitions in a ClassDef (where applicable) */ + def hashClassDef(classDef: ClassDef): ClassDef = + classDef.copy(defs = hashDefs(classDef.defs))(classDef.pos) + + def hashesEqual(x: TreeHash, y: TreeHash, considerPos: Boolean): Boolean = { + Arrays.equals(x.treeHash, y.treeHash) && + (!considerPos || Arrays.equals(x.posHash, y.posHash)) + } + + private final class TreeHasher { + private def newDigest = MessageDigest.getInstance("SHA-1") + private def newDigestStream(digest: MessageDigest) = { + val out = new OutputStream { + def write(b: Int): Unit = () + } + val digOut = new DigestOutputStream(out, digest) + new DataOutputStream(digOut) + } + + private[this] val treeDigest = newDigest + private[this] val treeStream = newDigestStream(treeDigest) + + private[this] val posDigest = newDigest + private[this] val posStream = newDigestStream(posDigest) + + def finalizeHash(): TreeHash = + new TreeHash(treeDigest.digest(), posDigest.digest()) + + def mixTree(tree: Tree): Unit = { + mixPos(tree.pos) + tree match { + case EmptyTree => + mixTag(TagEmptyTree) + + case VarDef(ident, vtpe, mutable, rhs) => + mixTag(TagVarDef) + mixIdent(ident) + mixType(vtpe) + mixBoolean(mutable) + mixTree(rhs) + + case ParamDef(ident, ptpe, mutable) => + mixTag(TagParamDef) + mixIdent(ident) + mixType(ptpe) + mixBoolean(mutable) + + case Skip() => + mixTag(TagSkip) + + case Block(stats) => + mixTag(TagBlock) + mixTrees(stats) + + case Labeled(label, tpe, body) => + mixTag(TagLabeled) + mixIdent(label) + mixType(tpe) + mixTree(body) + + case Assign(lhs, rhs) => + mixTag(TagAssign) + mixTree(lhs) + mixTree(rhs) + + case Return(expr, label) => + mixTag(TagReturn) + mixTree(expr) + mixOptIdent(label) + + case If(cond, thenp, elsep) => + mixTag(TagIf) + mixTree(cond) + mixTree(thenp) + mixTree(elsep) + mixType(tree.tpe) + + case While(cond, body, label) => + mixTag(TagWhile) + mixTree(cond) + mixTree(body) + mixOptIdent(label) + + case DoWhile(body, cond, label) => + mixTag(TagDoWhile) + mixTree(body) + mixTree(cond) + mixOptIdent(label) + + case Try(block, errVar, handler, finalizer) => + mixTag(TagTry) + mixTree(block) + mixIdent(errVar) + mixTree(handler) + mixTree(finalizer) + mixType(tree.tpe) + + case Throw(expr) => + mixTag(TagThrow) + mixTree(expr) + + case Continue(label) => + mixTag(TagContinue) + mixOptIdent(label) + + case Match(selector, cases, default) => + mixTag(TagMatch) + mixTree(selector) + cases foreach { case (patterns, body) => + mixTrees(patterns) + mixTree(body) + } + mixTree(default) + mixType(tree.tpe) + + case Debugger() => + mixTag(TagDebugger) + + case New(cls, ctor, args) => + mixTag(TagNew) + mixType(cls) + mixIdent(ctor) + mixTrees(args) + + case LoadModule(cls) => + mixTag(TagLoadModule) + mixType(cls) + + case StoreModule(cls, value) => + mixTag(TagStoreModule) + mixType(cls) + mixTree(value) + + case Select(qualifier, item, mutable) => + mixTag(TagSelect) + mixTree(qualifier) + mixIdent(item) + mixBoolean(mutable) + mixType(tree.tpe) + + case Apply(receiver, method, args) => + mixTag(TagApply) + mixTree(receiver) + mixIdent(method) + mixTrees(args) + mixType(tree.tpe) + + case StaticApply(receiver, cls, method, args) => + mixTag(TagStaticApply) + mixTree(receiver) + mixType(cls) + mixIdent(method) + mixTrees(args) + mixType(tree.tpe) + + case TraitImplApply(impl, method, args) => + mixTag(TagTraitImplApply) + mixType(impl) + mixIdent(method) + mixTrees(args) + mixType(tree.tpe) + + case UnaryOp(op, lhs) => + mixTag(TagUnaryOp) + mixInt(op) + mixTree(lhs) + + case BinaryOp(op, lhs, rhs) => + mixTag(TagBinaryOp) + mixInt(op) + mixTree(lhs) + mixTree(rhs) + + case NewArray(tpe, lengths) => + mixTag(TagNewArray) + mixType(tpe) + mixTrees(lengths) + + case ArrayValue(tpe, elems) => + mixTag(TagArrayValue) + mixType(tpe) + mixTrees(elems) + + case ArrayLength(array) => + mixTag(TagArrayLength) + mixTree(array) + + case ArraySelect(array, index) => + mixTag(TagArraySelect) + mixTree(array) + mixTree(index) + mixType(tree.tpe) + + case RecordValue(tpe, elems) => + mixTag(TagRecordValue) + mixType(tpe) + mixTrees(elems) + + case IsInstanceOf(expr, cls) => + mixTag(TagIsInstanceOf) + mixTree(expr) + mixType(cls) + + case AsInstanceOf(expr, cls) => + mixTag(TagAsInstanceOf) + mixTree(expr) + mixType(cls) + + case Unbox(expr, charCode) => + mixTag(TagUnbox) + mixTree(expr) + mixInt(charCode) + + case GetClass(expr) => + mixTag(TagGetClass) + mixTree(expr) + + case CallHelper(helper, args) => + mixTag(TagCallHelper) + mixString(helper) + mixTrees(args) + mixType(tree.tpe) + + case JSNew(ctor, args) => + mixTag(TagJSNew) + mixTree(ctor) + mixTrees(args) + + case JSDotSelect(qualifier, item) => + mixTag(TagJSDotSelect) + mixTree(qualifier) + mixIdent(item) + + case JSBracketSelect(qualifier, item) => + mixTag(TagJSBracketSelect) + mixTree(qualifier) + mixTree(item) + + case JSFunctionApply(fun, args) => + mixTag(TagJSFunctionApply) + mixTree(fun) + mixTrees(args) + + case JSDotMethodApply(receiver, method, args) => + mixTag(TagJSDotMethodApply) + mixTree(receiver) + mixIdent(method) + mixTrees(args) + + case JSBracketMethodApply(receiver, method, args) => + mixTag(TagJSBracketMethodApply) + mixTree(receiver) + mixTree(method) + mixTrees(args) + + case JSDelete(prop) => + mixTag(TagJSDelete) + mixTree(prop) + + case JSUnaryOp(op, lhs) => + mixTag(TagJSUnaryOp) + mixString(op) + mixTree(lhs) + + case JSBinaryOp(op, lhs, rhs) => + mixTag(TagJSBinaryOp) + mixString(op) + mixTree(lhs) + mixTree(rhs) + + case JSArrayConstr(items) => + mixTag(TagJSArrayConstr) + mixTrees(items) + + case JSObjectConstr(fields) => + mixTag(TagJSObjectConstr) + fields foreach { case (pn, value) => + mixPropertyName(pn) + mixTree(value) + } + + case JSEnvInfo() => + mixTag(TagJSEnvInfo) + + case Undefined() => + mixTag(TagUndefined) + + case UndefinedParam() => + mixTag(TagUndefinedParam) + mixType(tree.tpe) + + case Null() => + mixTag(TagNull) + + case BooleanLiteral(value) => + mixTag(TagBooleanLiteral) + mixBoolean(value) + + case IntLiteral(value) => + mixTag(TagIntLiteral) + mixInt(value) + + case LongLiteral(value) => + mixTag(TagLongLiteral) + mixLong(value) + + case FloatLiteral(value) => + mixTag(TagFloatLiteral) + mixFloat(value) + + case DoubleLiteral(value) => + mixTag(TagDoubleLiteral) + mixDouble(value) + + case StringLiteral(value) => + mixTag(TagStringLiteral) + mixString(value) + + case ClassOf(cls) => + mixTag(TagClassOf) + mixType(cls) + + case VarRef(ident, mutable) => + mixTag(TagVarRef) + mixIdent(ident) + mixBoolean(mutable) + mixType(tree.tpe) + + case This() => + mixTag(TagThis) + mixType(tree.tpe) + + case Closure(captureParams, params, body, captureValues) => + mixTag(TagClosure) + mixTrees(captureParams) + mixTrees(params) + mixTree(body) + mixTrees(captureValues) + + case _ => + sys.error(s"Unable to hash tree of class ${tree.getClass}") + + } + } + + def mixTrees(trees: List[Tree]): Unit = + trees.foreach(mixTree) + + def mixType(tpe: Type): Unit = tpe match { + case AnyType => mixTag(TagAnyType) + case NothingType => mixTag(TagNothingType) + case UndefType => mixTag(TagUndefType) + case BooleanType => mixTag(TagBooleanType) + case IntType => mixTag(TagIntType) + case LongType => mixTag(TagLongType) + case FloatType => mixTag(TagFloatType) + case DoubleType => mixTag(TagDoubleType) + case StringType => mixTag(TagStringType) + case NullType => mixTag(TagNullType) + case NoType => mixTag(TagNoType) + + case tpe: ClassType => + mixTag(TagClassType) + mixString(tpe.className) + + case tpe: ArrayType => + mixTag(TagArrayType) + mixString(tpe.baseClassName) + mixInt(tpe.dimensions) + + case RecordType(fields) => + mixTag(TagRecordType) + for (RecordType.Field(name, originalName, tpe, mutable) <- fields) { + mixString(name) + originalName.foreach(mixString) + mixType(tpe) + mixBoolean(mutable) + } + } + + def mixIdent(ident: Ident): Unit = { + mixPos(ident.pos) + mixString(ident.name) + ident.originalName.foreach(mixString) + } + + def mixOptIdent(optIdent: Option[Ident]): Unit = optIdent.foreach(mixIdent) + + def mixPropertyName(name: PropertyName): Unit = name match { + case name: Ident => mixIdent(name) + case name: StringLiteral => mixTree(name) + } + + def mixPos(pos: Position): Unit = { + posStream.writeUTF(pos.source.toString) + posStream.writeInt(pos.line) + posStream.writeInt(pos.column) + } + + @inline + private final def mixTag(tag: Int): Unit = mixInt(tag) + + @inline + private final def mixString(str: String): Unit = treeStream.writeUTF(str) + + @inline + private final def mixInt(i: Int): Unit = treeStream.writeInt(i) + + @inline + private final def mixLong(l: Long): Unit = treeStream.writeLong(l) + + @inline + private final def mixBoolean(b: Boolean): Unit = treeStream.writeBoolean(b) + + @inline + private final def mixFloat(f: Float): Unit = treeStream.writeFloat(f) + + @inline + private final def mixDouble(d: Double): Unit = treeStream.writeDouble(d) + + } + +} diff --git a/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/InfoSerializers.scala b/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/InfoSerializers.scala new file mode 100644 index 0000000..dfb520f --- /dev/null +++ b/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/InfoSerializers.scala @@ -0,0 +1,180 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js IR ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.ir + +import java.io._ + +import Infos._ + +object InfoSerializers { + + /** Scala.js IR File Magic Number + * + * CA FE : first part of magic number of Java class files + * 4A 53 : "JS" in ASCII + * + */ + final val IRMagicNumber = 0xCAFE4A53 + + def serialize(stream: OutputStream, classInfo: ClassInfo): Unit = { + new Serializer().serialize(stream, classInfo) + } + + def deserializeRoughInfo(stream: InputStream): RoughClassInfo = { + deserializeVersionRoughInfo(stream)._2 + } + + def deserializeFullInfo(stream: InputStream): ClassInfo = { + deserializeVersionFullInfo(stream)._2 + } + + def deserializeVersionRoughInfo(stream: InputStream): (String, RoughClassInfo) = { + new Deserializer(stream).deserializeRough() + } + + def deserializeVersionFullInfo(stream: InputStream): (String, ClassInfo) = { + new Deserializer(stream).deserializeFull() + } + + private final class Serializer { + def serialize(stream: OutputStream, classInfo: ClassInfo): Unit = { + val s = new DataOutputStream(stream) + + def writeSeq[A](seq: Seq[A])(writeElem: A => Unit): Unit = { + s.writeInt(seq.size) + seq.foreach(writeElem) + } + + def writeStrings(seq: Seq[String]): Unit = + writeSeq(seq)(s.writeUTF(_)) + + // Write the Scala.js IR magic number + s.writeInt(IRMagicNumber) + + // Write the Scala.js Version + s.writeUTF(ScalaJSVersions.binaryEmitted) + + import classInfo._ + s.writeUTF(name) + s.writeUTF(encodedName) + s.writeBoolean(isExported) + s.writeInt(ancestorCount) + s.writeByte(ClassKind.toByte(kind)) + s.writeUTF(superClass) + writeStrings(ancestors) + s.writeInt(optimizerHints.bits) + + def writeMethodInfo(methodInfo: MethodInfo): Unit = { + import methodInfo._ + s.writeUTF(encodedName) + s.writeBoolean(isAbstract) + s.writeBoolean(isExported) + writeSeq(calledMethods.toSeq) { + case (caller, callees) => s.writeUTF(caller); writeStrings(callees) + } + writeSeq(calledMethodsStatic.toSeq) { + case (caller, callees) => s.writeUTF(caller); writeStrings(callees) + } + writeStrings(instantiatedClasses) + writeStrings(accessedModules) + writeStrings(accessedClassData) + s.writeInt(optimizerHints.bits) + } + + writeSeq(methods)(writeMethodInfo(_)) + + s.flush() + } + } + + private final class Deserializer(stream: InputStream) { + private[this] val input = new DataInputStream(stream) + + def readList[A](readElem: => A): List[A] = + List.fill(input.readInt())(readElem) + + def readStrings(): List[String] = + readList(input.readUTF()) + + def deserializeRough(): (String, RoughClassInfo) = { + val version = readHeader() + + import input._ + val name = readUTF() + val encodedName = readUTF() + val isExported = readBoolean() + val ancestorCount = readInt() + val info = RoughClassInfo(name, encodedName, isExported, ancestorCount) + + (version, info) + } + + def deserializeFull(): (String, ClassInfo) = { + val version = readHeader() + + import input._ + + val name = readUTF() + val encodedName = readUTF() + val isExported = readBoolean() + val ancestorCount = readInt() + val kind = ClassKind.fromByte(readByte()) + val superClass = readUTF() + val ancestors = readList(readUTF()) + + val optimizerHints = + if (version == "0.5.0" || version == "0.5.2") OptimizerHints.empty + else new OptimizerHints(readInt()) + + def readMethod(): MethodInfo = { + val encodedName = readUTF() + val isAbstract = readBoolean() + val isExported = readBoolean() + val calledMethods = readList(readUTF() -> readStrings()).toMap + val calledMethodsStatic = readList(readUTF() -> readStrings()).toMap + val instantiatedClasses = readStrings() + val accessedModules = readStrings() + val accessedClassData = readStrings() + val optimizerHints = new OptimizerHints(readInt()) + MethodInfo(encodedName, isAbstract, isExported, + calledMethods, calledMethodsStatic, + instantiatedClasses, accessedModules, accessedClassData, + optimizerHints) + } + + val methods = readList(readMethod()) + + val info = ClassInfo(name, encodedName, isExported, ancestorCount, kind, + superClass, ancestors, optimizerHints, methods) + + (version, info) + } + + /** Reads the Scala.js IR header and verifies the version compatibility. + * Returns the emitted binary version. + */ + def readHeader(): String = { + // Check magic number + if (input.readInt() != IRMagicNumber) + throw new IOException("Not a Scala.js IR file") + + // Check that we support this version of the IR + val version = input.readUTF() + val supported = ScalaJSVersions.binarySupported + if (!supported.contains(version)) { + throw new IOException( + s"This version ($version) of Scala.js IR is not supported. " + + s"Supported versions are: ${supported.mkString(", ")}") + } + + version + } + } +} diff --git a/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Infos.scala b/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Infos.scala new file mode 100644 index 0000000..66feec2 --- /dev/null +++ b/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Infos.scala @@ -0,0 +1,118 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js IR ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.ir + +object Infos { + + sealed class RoughClassInfo protected ( + val name: String, + val encodedName: String, + val isExported: Boolean, + val ancestorCount: Int + ) + + object RoughClassInfo { + def apply(name: String, encodedName: String, isExported: Boolean, + ancestorCount: Int): RoughClassInfo = { + new RoughClassInfo(name, encodedName, isExported, ancestorCount) + } + } + + final class ClassInfo protected ( + name: String, + encodedName: String, + isExported: Boolean, + ancestorCount: Int, + val kind: ClassKind, + val superClass: String, + val ancestors: List[String], // includes this class + val optimizerHints: OptimizerHints, + val methods: List[MethodInfo] + ) extends RoughClassInfo(name, encodedName, isExported, ancestorCount) + + object ClassInfo { + def apply( + name: String, + encodedName: String, + isExported: Boolean = false, + ancestorCount: Int = 0, + kind: ClassKind = ClassKind.Class, + superClass: String = "", + ancestors: List[String] = Nil, + optimizerHints: OptimizerHints = OptimizerHints.empty, + methods: List[MethodInfo] = Nil): ClassInfo = { + new ClassInfo(name, encodedName, isExported, ancestorCount, + kind, superClass, ancestors, optimizerHints, methods) + } + } + + final class MethodInfo private ( + val encodedName: String, + val isAbstract: Boolean, + val isExported: Boolean, + val calledMethods: Map[String, List[String]], + val calledMethodsStatic: Map[String, List[String]], + val instantiatedClasses: List[String], + val accessedModules: List[String], + val accessedClassData: List[String], + val optimizerHints: OptimizerHints + ) + + object MethodInfo { + def apply( + encodedName: String, + isAbstract: Boolean = false, + isExported: Boolean = false, + calledMethods: Map[String, List[String]] = Map.empty, + calledMethodsStatic: Map[String, List[String]] = Map.empty, + instantiatedClasses: List[String] = Nil, + accessedModules: List[String] = Nil, + accessedClassData: List[String] = Nil, + optimizerHints: OptimizerHints = OptimizerHints.empty): MethodInfo = { + new MethodInfo(encodedName, isAbstract, isExported, calledMethods, + calledMethodsStatic, instantiatedClasses, accessedModules, + accessedClassData, optimizerHints) + } + } + + final class OptimizerHints(val bits: Int) extends AnyVal { + import OptimizerHints._ + + private[scalajs] def isAccessor: Boolean = (bits & AccessorMask) != 0 + private[scalajs] def hasInlineAnnot: Boolean = (bits & InlineAnnotMask) != 0 + + private[scalajs] def copy( + isAccessor: Boolean = this.isAccessor, + hasInlineAnnot: Boolean = this.hasInlineAnnot + ): OptimizerHints = { + var bits: Int = 0 + if (isAccessor) + bits |= AccessorMask + if (hasInlineAnnot) + bits |= InlineAnnotMask + new OptimizerHints(bits) + } + + override def toString(): String = + s"OptimizerHints($bits)" + } + + object OptimizerHints { + private final val AccessorShift = 0 + private final val AccessorMask = 1 << AccessorShift + + private final val InlineAnnotShift = 1 + private final val InlineAnnotMask = 1 << InlineAnnotShift + + final val empty: OptimizerHints = + new OptimizerHints(0) + } + +} diff --git a/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Position.scala b/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Position.scala new file mode 100644 index 0000000..3b6d0a2 --- /dev/null +++ b/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Position.scala @@ -0,0 +1,42 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js IR ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.ir + +final case class Position( + /** Source file. */ + source: Position.SourceFile, + /** Zero-based line number. */ + line: Int, + /** Zero-based column number. */ + column: Int +) { + def show: String = s"$line:$column" + + def isEmpty: Boolean = { + source.getScheme == null && source.getRawAuthority == null && + source.getRawPath == "" && source.getRawQuery == null && + source.getRawFragment == null + } + + def isDefined: Boolean = !isEmpty + + def orElse(that: => Position): Position = if (isDefined) this else that +} + +object Position { + type SourceFile = java.net.URI + + object SourceFile { + def apply(f: java.io.File): SourceFile = f.toURI + def apply(f: String): SourceFile = new java.net.URI(f) + } + + val NoPosition = Position(SourceFile(""), 0, 0) +} diff --git a/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Printers.scala b/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Printers.scala new file mode 100644 index 0000000..6208d5f --- /dev/null +++ b/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Printers.scala @@ -0,0 +1,709 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js IR ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.ir + +import scala.annotation.{switch, tailrec} + +import java.io.Writer + +import Position._ +import Trees._ +import Types._ +import Infos._ +import Utils.escapeJS + +object Printers { + + /** Basically copied from scala.reflect.internal.Printers */ + trait IndentationManager { + protected val out: Writer + + protected var indentMargin = 0 + protected val indentStep = 2 + protected var indentString = " " // 40 + + protected def indent(): Unit = indentMargin += indentStep + protected def undent(): Unit = indentMargin -= indentStep + + protected def println(): Unit = { + out.write('\n') + while (indentMargin > indentString.length()) + indentString += indentString + if (indentMargin > 0) + out.write(indentString, 0, indentMargin) + } + + @tailrec + protected final def printSeq[A](ls: List[A])(printelem: A => Unit)( + printsep: A => Unit): Unit = { + ls match { + case Nil => + case x :: Nil => + printelem(x) + case x :: rest => + printelem(x) + printsep(x) + printSeq(rest)(printelem)(printsep) + } + } + + protected def printColumn(ts: List[Any], start: String, sep: String, + end: String): Unit = { + print(start); indent(); println() + printSeq(ts) { x => + print(x) + } { _ => + print(sep) + println() + } + undent(); println(); print(end) + } + + protected def printRow(ts: List[Any], start: String, sep: String, + end: String): Unit = { + print(start) + printSeq(ts) { x => + print(x) + } { _ => + print(sep) + } + print(end) + } + + protected def printRow(ts: List[Any], sep: String): Unit = + printRow(ts, "", sep, "") + + protected def print(args: Any*): Unit = + args.foreach(printOne) + + protected def printOne(arg: Any): Unit + } + + class IRTreePrinter(protected val out: Writer) extends IndentationManager { + def printTopLevelTree(tree: Tree) { + tree match { + case Skip() => + // do not print anything + case Block(stats) => + for (stat <- stats) + printTopLevelTree(stat) + case _ => + printTree(tree) + println() + } + } + + protected def printBlock(tree: Tree): Unit = { + val trees = tree match { + case Block(trees) => trees + case _ => List(tree) + } + printColumn(trees, "{", ";", "}") + } + + protected def printSig(args: List[ParamDef], resultType: Type): Unit = { + printRow(args, "(", ", ", ")") + if (resultType != NoType) + print(": ", resultType, " = ") + else + print(" ") + } + + protected def printArgs(args: List[Tree]): Unit = { + printRow(args, "(", ", ", ")") + } + + def printTree(tree: Tree): Unit = { + tree match { + case EmptyTree => + print("<empty>") + + // Definitions + + case VarDef(ident, vtpe, mutable, rhs) => + if (mutable) + print("var ") + else + print("val ") + print(ident, ": ", vtpe) + if (rhs != EmptyTree) + print(" = ", rhs) + + case ParamDef(ident, ptpe, mutable) => + if (mutable) + print("var ") + print(ident, ": ", ptpe) + + // Control flow constructs + + case Skip() => + print("/*<skip>*/") + + case tree: Block => + printBlock(tree) + + case Labeled(label, tpe, body) => + print(label) + if (tpe != NoType) + print("[", tpe, "]") + print(": ") + printBlock(body) + + case Assign(lhs, rhs) => + print(lhs, " = ", rhs) + + case Return(expr, label) => + if (label.isEmpty) print("return ", expr) + else print("return(", label.get, ") ", expr) + + case If(cond, BooleanLiteral(true), elsep) => + print(cond, " || ", elsep) + case If(cond, thenp, BooleanLiteral(false)) => + print(cond, " && ", thenp) + + case If(cond, thenp, elsep) => + print("if (", cond, ") ") + printBlock(thenp) + elsep match { + case Skip() => () + case If(_, _, _) => + print(" else ") + printTree(elsep) + case _ => + print(" else ") + printBlock(elsep) + } + + case While(cond, body, label) => + if (label.isDefined) + print(label.get, ": ") + print("while (", cond, ") ") + printBlock(body) + + case DoWhile(body, cond, label) => + if (label.isDefined) + print(label.get, ": ") + print("do ") + printBlock(body) + print(" while (", cond, ")") + + case Try(block, errVar, handler, finalizer) => + print("try ") + printBlock(block) + if (handler != EmptyTree) { + print(" catch (", errVar, ") ") + printBlock(handler) + } + if (finalizer != EmptyTree) { + print(" finally ") + printBlock(finalizer) + } + + case Throw(expr) => + print("throw ", expr) + + case Continue(label) => + if (label.isEmpty) print("continue") + else print("continue ", label.get) + + case Match(selector, cases, default) => + print("match (", selector, ") ") + print("{"); indent + for ((values, body) <- cases) { + println() + printRow(values, "case ", " | ", ":"); indent; println() + printTree(body) + print(";") + undent + } + if (default != EmptyTree) { + println() + print("default:"); indent; println() + printTree(default) + print(";") + undent + } + undent; println(); print("}") + + case Debugger() => + print("debugger") + + // Scala expressions + + case New(cls, ctor, args) => + print("new ", cls, "().", ctor) + printArgs(args) + + case LoadModule(cls) => + print("mod:", cls) + + case StoreModule(cls, value) => + print("mod:", cls, "<-", value) + + case Select(qualifier, item, _) => + print(qualifier, ".", item) + + case Apply(receiver, method, args) => + print(receiver, ".", method) + printArgs(args) + + case StaticApply(receiver, cls, method, args) => + print(receiver, ".", cls, "::", method) + printArgs(args) + + case TraitImplApply(impl, method, args) => + print(impl, "::", method) + printArgs(args) + + case UnaryOp(op, lhs) => + import UnaryOp._ + print("(", (op: @switch) match { + case `typeof` => "typeof" + case Boolean_! => "!" + case IntToLong | DoubleToLong => "(long)" + case DoubleToInt | LongToInt => "(int)" + case DoubleToFloat => "(float)" + case LongToDouble => "(double)" + }, lhs, ")") + + case BinaryOp(BinaryOp.Int_-, IntLiteral(0), rhs) => + print("(-", rhs, ")") + case BinaryOp(BinaryOp.Int_^, IntLiteral(-1), rhs) => + print("(~", rhs, ")") + case BinaryOp(BinaryOp.Long_-, LongLiteral(0L), rhs) => + print("(-", rhs, ")") + case BinaryOp(BinaryOp.Long_^, LongLiteral(-1L), rhs) => + print("(~", rhs, ")") + case BinaryOp(BinaryOp.Float_-, FloatLiteral(0.0f), rhs) => + print("(-", rhs, ")") + case BinaryOp(BinaryOp.Double_-, + IntLiteral(0) | FloatLiteral(0.0f) | DoubleLiteral(0.0), rhs) => + print("(-", rhs, ")") + + case BinaryOp(op, lhs, rhs) => + import BinaryOp._ + print("(", lhs, " ", (op: @switch) match { + case === => "===" + case !== => "!==" + + case String_+ => "+[string]" + + case `in` => "in" + case `instanceof` => "instanceof" + + case Int_+ => "+[int]" + case Int_- => "-[int]" + case Int_* => "*[int]" + case Int_/ => "/[int]" + case Int_% => "%[int]" + + case Int_| => "|" + case Int_& => "&" + case Int_^ => "^" + case Int_<< => "<<" + case Int_>>> => ">>>" + case Int_>> => ">>" + + case Float_+ => "+[float]" + case Float_- => "-[float]" + case Float_* => "*[float]" + case Float_/ => "/[float]" + case Float_% => "%[float]" + + case Double_+ => "+" + case Double_- => "-" + case Double_* => "*" + case Double_/ => "/" + case Double_% => "%" + + case Num_== => "==" + case Num_!= => "!=" + case Num_< => "<" + case Num_<= => "<=" + case Num_> => ">" + case Num_>= => ">=" + + case Long_+ => "+[long]" + case Long_- => "-[long]" + case Long_* => "*[long]" + case Long_/ => "/[long]" + case Long_% => "%[long]" + + case Long_| => "|[long]" + case Long_& => "&[long]" + case Long_^ => "^[long]" + case Long_<< => "<<[long]" + case Long_>>> => ">>>[long]" + case Long_>> => ">>[long]" + + case Long_== => "==[long]" + case Long_!= => "!=[long]" + case Long_< => "<[long]" + case Long_<= => "<=[long]" + case Long_> => ">[long]" + case Long_>= => ">=[long]" + + case Boolean_== => "==[bool]" + case Boolean_!= => "!=[bool]" + case Boolean_| => "|[bool]" + case Boolean_& => "&[bool]" + }, " ", rhs, ")") + + case NewArray(tpe, lengths) => + print("new ", tpe.baseClassName) + for (length <- lengths) + print("[", length, "]") + for (dim <- lengths.size until tpe.dimensions) + print("[]") + + case ArrayValue(tpe, elems) => + print(tpe) + printArgs(elems) + + case ArrayLength(array) => + print(array, ".length") + + case ArraySelect(array, index) => + print(array, "[", index, "]") + + case RecordValue(tpe, elems) => + print("(") + var first = true + for ((field, value) <- tpe.fields zip elems) { + if (first) first = false + else print(", ") + print(field.name, " = ", value) + } + print(")") + + case IsInstanceOf(expr, cls) => + print(expr, ".isInstanceOf[", cls, "]") + + case AsInstanceOf(expr, cls) => + print(expr, ".asInstanceOf[", cls, "]") + + case Unbox(expr, charCode) => + print(expr, ".asInstanceOf[", charCode, "]") + + case GetClass(expr) => + print(expr, ".getClass()") + + case CallHelper(helper, args) => + print(helper) + printArgs(args) + + // JavaScript expressions + + case JSNew(ctor, args) => + def containsOnlySelectsFromAtom(tree: Tree): Boolean = tree match { + case JSDotSelect(qual, _) => containsOnlySelectsFromAtom(qual) + case JSBracketSelect(qual, _) => containsOnlySelectsFromAtom(qual) + case VarRef(_, _) => true + case This() => true + case _ => false // in particular, Apply + } + if (containsOnlySelectsFromAtom(ctor)) + print("new ", ctor) + else + print("new (", ctor, ")") + printArgs(args) + + case JSDotSelect(qualifier, item) => + print(qualifier, ".", item) + + case JSBracketSelect(qualifier, item) => + print(qualifier, "[", item, "]") + + case JSFunctionApply(fun, args) => + fun match { + case _:JSDotSelect | _:JSBracketSelect | _:Select => + print("protect(", fun, ")") + case _ => + print(fun) + } + printArgs(args) + + case JSDotMethodApply(receiver, method, args) => + print(receiver, ".", method) + printArgs(args) + + case JSBracketMethodApply(receiver, method, args) => + print(receiver, "[", method, "]") + printArgs(args) + + case JSDelete(prop) => + print("delete ", prop) + + case JSUnaryOp("typeof", lhs) => + print("typeof(", lhs, ")") + + case JSUnaryOp(op, lhs) => + print("(", op, lhs, ")") + + case JSBinaryOp(op, lhs, rhs) => + print("(", lhs, " ", op, " ", rhs, ")") + + case JSArrayConstr(items) => + printRow(items, "[", ", ", "]") + + case JSObjectConstr(Nil) => + print("{}") + + case JSObjectConstr(fields) => + print("{"); indent; println() + printSeq(fields) { + case (name, value) => print(name, ": ", value) + } { _ => + print(",") + println() + } + undent; println(); print("}") + + case JSEnvInfo() => + print("<envinfo>") + + // Literals + + case Undefined() => + print("(void 0)") + + case UndefinedParam() => + print("<undefined param>") + + case Null() => + print("null") + + case BooleanLiteral(value) => + print(if (value) "true" else "false") + + case IntLiteral(value) => + if (value >= 0) + print(value) + else + print("(", value, ")") + + case FloatLiteral(value) => + if (value == 0.0f && 1.0f / value < 0.0f) + print("(-0)") + else if (value >= 0.0f) + print(value) + else + print("(", value, ")") + + case DoubleLiteral(value) => + if (value == 0.0 && 1.0 / value < 0.0) + print("(-0)") + else if (value >= 0.0) + print(value) + else + print("(", value, ")") + + case StringLiteral(value) => + print("\"", escapeJS(value), "\"") + + case ClassOf(cls) => + print("classOf[", cls, "]") + + // Atomic expressions + + case VarRef(ident, _) => + print(ident) + + case This() => + print("this") + + case Closure(captureParams, params, body, captureValues) => + print("(lambda") + printRow(captureValues, "<", ", ", ">") + printRow(captureParams ++ params, "(", ", ", ") = ") + printBlock(body) + print(")") + + // Classes + + case ClassDef(name, kind, parent, ancestors, defs) => + kind match { + case ClassKind.Class => print("class ") + case ClassKind.ModuleClass => print("module class ") + case ClassKind.Interface => print("interface ") + case ClassKind.RawJSType => print("jstype ") + case ClassKind.HijackedClass => print("hijacked class ") + case ClassKind.TraitImpl => print("trait impl ") + } + print(name) + parent.foreach(print(" extends ", _)) + if (ancestors.nonEmpty) + printRow(ancestors, " ancestors ", ", ", "") + print(" ") + printColumn(defs, "{", "", "}") + println() + + case MethodDef(name, args, resultType, body) => + print(name) + printSig(args, resultType) + printBlock(body) + + case PropertyDef(name, _, _, _) => + // TODO + print(s"<property: $name>") + + case ConstructorExportDef(fullName, args, body) => + print("export \"", escapeJS(fullName), "\"") + printSig(args, NoType) // NoType as trick not to display a type + printBlock(body) + + case ModuleExportDef(fullName) => + print("export \"", escapeJS(fullName), "\"") + + case _ => + print(s"<error, elem of class ${tree.getClass()}>") + } + } + + def printType(tpe: Type): Unit = tpe match { + case AnyType => print("any") + case NothingType => print("nothing") + case UndefType => print("void") + case BooleanType => print("boolean") + case IntType => print("int") + case LongType => print("long") + case FloatType => print("float") + case DoubleType => print("number") + case StringType => print("string") + case NullType => print("null") + case ClassType(className) => print(className) + case NoType => print("<notype>") + + case ArrayType(base, dims) => + print(base) + for (i <- 1 to dims) + print("[]") + + case RecordType(fields) => + print("(") + var first = false + for (RecordType.Field(name, _, tpe, mutable) <- fields) { + if (first) first = false + else print(", ") + if (mutable) + print("var ") + print(name, ": ", tpe) + } + print(")") + } + + protected def printIdent(ident: Ident): Unit = + printString(escapeJS(ident.name)) + + protected def printOne(arg: Any): Unit = arg match { + case tree: Tree => + printTree(tree) + case tpe: Type => + printType(tpe) + case ident: Ident => + printIdent(ident) + case arg => + printString(if (arg == null) "null" else arg.toString) + } + + protected def printString(s: String): Unit = { + out.write(s) + } + + // Make it public + override def println(): Unit = super.println() + + def complete(): Unit = () + } + + class InfoPrinter(protected val out: Writer) extends IndentationManager { + def printClassInfo(classInfo: ClassInfo): Unit = { + import classInfo._ + println("name: ", escapeJS(name)) + println("encodedName: ", escapeJS(encodedName)) + println("isExported: ", isExported) + println("ancestorCount: ", ancestorCount) + println("kind: ", kind) + println("superClass: ", superClass) + + if (ancestors.nonEmpty) { + println("ancestors: ", + ancestors.map(escapeJS).mkString("[", ", ", "]")) + } + + if (optimizerHints != OptimizerHints.empty) + println("optimizerHints: ", optimizerHints) + + print("methods:") + indent(); println() + methods.foreach(printMethodInfo) + undent(); println() + } + + def printMethodInfo(methodInfo: MethodInfo): Unit = { + import methodInfo._ + print(escapeJS(encodedName), ":") + indent(); println() + + if (isAbstract) + println("isAbstract: ", isAbstract) + if (isExported) + println("isExported: ", isExported) + if (calledMethods.nonEmpty) { + print("calledMethods:") + indent(); println() + printSeq(calledMethods.toList) { case (caller, callees) => + print(escapeJS(caller), ": ") + print(callees.map(escapeJS).mkString("[", ", ", "]")) + } { _ => println() } + undent(); println() + } + if (calledMethodsStatic.nonEmpty) { + print("calledMethodsStatic:") + indent(); println() + printSeq(calledMethodsStatic.toList) { case (caller, callees) => + print(escapeJS(caller), ": ") + print(callees.map(escapeJS).mkString("[", ", ", "]")) + } { _ => println() } + undent(); println() + } + if (instantiatedClasses.nonEmpty) { + println("instantiatedClasses: ", + instantiatedClasses.map(escapeJS).mkString("[", ", ", "]")) + } + if (accessedModules.nonEmpty) { + println("accessedModules: ", + accessedModules.map(escapeJS).mkString("[", ", ", "]")) + } + if (accessedClassData.nonEmpty) { + println("accessedClassData: ", + accessedClassData.map(escapeJS).mkString("[", ", ", "]")) + } + if (optimizerHints != OptimizerHints.empty) + println("optimizerHints: ", optimizerHints) + + undent(); println() + } + + private def println(arg1: Any, args: Any*): Unit = { + print((arg1 +: args): _*) + println() + } + + protected def printOne(arg: Any): Unit = arg match { + case classInfo: ClassInfo => printClassInfo(classInfo) + case methodInfo: MethodInfo => printMethodInfo(methodInfo) + case arg => out.write(arg.toString()) + } + + def complete(): Unit = () + } + +} diff --git a/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/ScalaJSVersions.scala b/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/ScalaJSVersions.scala new file mode 100644 index 0000000..2690939 --- /dev/null +++ b/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/ScalaJSVersions.scala @@ -0,0 +1,25 @@ +package scala.scalajs.ir + +object ScalaJSVersions { + + /** the Scala.js version of this build */ + final val current = "0.6.0-SNAPSHOT" + + /** true iff the Scala.js version of this build is a snapshot version. */ + final val currentIsSnapshot = current endsWith "-SNAPSHOT" + + /** Version of binary IR this Scala.js version emits + * + * This should be either of: + * - a prior release version (i.e. "0.5.0", *not* "0.5.0-SNAPSHOT") + * - `current` + */ + final val binaryEmitted = current + + /** Versions whose binary files we can support (used by deserializer) */ + val binarySupported: Set[String] = Set(binaryEmitted) + + // Just to be extra safe + assert(binarySupported contains binaryEmitted) + +} diff --git a/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Serializers.scala b/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Serializers.scala new file mode 100644 index 0000000..04ec5c2 --- /dev/null +++ b/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Serializers.scala @@ -0,0 +1,790 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js IR ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.ir + +import scala.annotation.switch + +import java.io._ +import java.net.URI + +import scala.collection.mutable + +import Position._ +import Trees._ +import Types._ +import Tags._ + +import Utils.JumpBackByteArrayOutputStream + +object Serializers { + def serialize(stream: OutputStream, tree: Tree): Unit = { + new Serializer().serialize(stream, tree) + } + + def deserialize(stream: InputStream, version: String): Tree = { + new Deserializer(stream, version).deserialize() + } + + // true for easier debugging (not for "production", it adds 8 bytes per node) + private final val UseDebugMagic = false + private final val DebugMagic = 0x3fa8ef84 + private final val PosDebugMagic = 0x65f0ec32 + + private object PositionFormat { + /* Positions are serialized incrementally as diffs wrt the last position. + * + * Formats are (the first byte is decomposed in bits): + * + * 1st byte | next bytes | description + * ----------------------------------------- + * ccccccc0 | | Column diff (7-bit signed) + * llllll01 | CC | Line diff (6-bit signed), column (8-bit unsigned) + * ____0011 | LL LL CC | Line diff (16-bit signed), column (8-bit unsigned) + * ____0111 | 12 bytes | File index, line, column (all 32-bit signed) + * 11111111 | | NoPosition (is not compared/stored in last position) + * + * Underscores are irrelevant and must be set to 0. + */ + + final val Format1Mask = 0x01 + final val Format1MaskValue = 0x00 + final val Format1Shift = 1 + + final val Format2Mask = 0x03 + final val Format2MaskValue = 0x01 + final val Format2Shift = 2 + + final val Format3Mask = 0x0f + final val Format3MaskValue = 0x03 + + final val FormatFullMask = 0x0f + final val FormatFullMaskValue = 0x7 + + final val FormatNoPositionValue = -1 + } + + private final class Serializer { + private[this] val bufferUnderlying = new JumpBackByteArrayOutputStream + private[this] val buffer = new DataOutputStream(bufferUnderlying) + + private[this] val files = mutable.ListBuffer.empty[URI] + private[this] val fileIndexMap = mutable.Map.empty[URI, Int] + private def fileToIndex(file: URI): Int = + fileIndexMap.getOrElseUpdate(file, (files += file).size - 1) + + private[this] val strings = mutable.ListBuffer.empty[String] + private[this] val stringIndexMap = mutable.Map.empty[String, Int] + private def stringToIndex(str: String): Int = + stringIndexMap.getOrElseUpdate(str, (strings += str).size - 1) + + private[this] var lastPosition: Position = Position.NoPosition + + def serialize(stream: OutputStream, tree: Tree): Unit = { + // Write tree to buffer and record files and strings + writeTree(tree) + + val s = new DataOutputStream(stream) + + // Emit the files + s.writeInt(files.size) + files.foreach(f => s.writeUTF(f.toString)) + + // Emit the strings + s.writeInt(strings.size) + strings.foreach(s.writeUTF) + + // Paste the buffer + bufferUnderlying.writeTo(s) + + s.flush() + } + + def writeTree(tree: Tree): Unit = { + import buffer._ + writePosition(tree.pos) + tree match { + case EmptyTree => + writeByte(TagEmptyTree) + + case VarDef(ident, vtpe, mutable, rhs) => + writeByte(TagVarDef) + writeIdent(ident); writeType(vtpe); writeBoolean(mutable); writeTree(rhs) + + case ParamDef(ident, ptpe, mutable) => + writeByte(TagParamDef) + writeIdent(ident); writeType(ptpe); writeBoolean(mutable) + + case Skip() => + writeByte(TagSkip) + + case Block(stats) => + writeByte(TagBlock) + writeTrees(stats) + + case Labeled(label, tpe, body) => + writeByte(TagLabeled) + writeIdent(label); writeType(tpe); writeTree(body) + + case Assign(lhs, rhs) => + writeByte(TagAssign) + writeTree(lhs); writeTree(rhs) + + case Return(expr, label) => + writeByte(TagReturn) + writeTree(expr); writeOptIdent(label) + + case If(cond, thenp, elsep) => + writeByte(TagIf) + writeTree(cond); writeTree(thenp); writeTree(elsep) + writeType(tree.tpe) + + case While(cond, body, label) => + writeByte(TagWhile) + writeTree(cond); writeTree(body); writeOptIdent(label) + + case DoWhile(body, cond, label) => + writeByte(TagDoWhile) + writeTree(body); writeTree(cond); writeOptIdent(label) + + case Try(block, errVar, handler, finalizer) => + writeByte(TagTry) + writeTree(block); writeIdent(errVar); writeTree(handler); writeTree(finalizer) + writeType(tree.tpe) + + case Throw(expr) => + writeByte(TagThrow) + writeTree(expr) + + case Continue(label) => + writeByte(TagContinue) + writeOptIdent(label) + + case Match(selector, cases, default) => + writeByte(TagMatch) + writeTree(selector) + writeInt(cases.size) + cases foreach { caze => + writeTrees(caze._1); writeTree(caze._2) + } + writeTree(default) + writeType(tree.tpe) + + case Debugger() => + writeByte(TagDebugger) + + case New(cls, ctor, args) => + writeByte(TagNew) + writeClassType(cls); writeIdent(ctor); writeTrees(args) + + case LoadModule(cls) => + writeByte(TagLoadModule) + writeClassType(cls) + + case StoreModule(cls, value) => + writeByte(TagStoreModule) + writeClassType(cls); writeTree(value) + + case Select(qualifier, item, mutable) => + writeByte(TagSelect) + writeTree(qualifier); writeIdent(item); writeBoolean(mutable) + writeType(tree.tpe) + + case Apply(receiver, method, args) => + writeByte(TagApply) + writeTree(receiver); writeIdent(method); writeTrees(args) + writeType(tree.tpe) + + case StaticApply(receiver, cls, method, args) => + writeByte(TagStaticApply) + writeTree(receiver); writeClassType(cls); writeIdent(method); writeTrees(args) + writeType(tree.tpe) + + case TraitImplApply(impl, method, args) => + writeByte(TagTraitImplApply) + writeClassType(impl); writeIdent(method); writeTrees(args) + writeType(tree.tpe) + + case UnaryOp(op, lhs) => + writeByte(TagUnaryOp) + writeByte(op); writeTree(lhs) + + case BinaryOp(op, lhs, rhs) => + writeByte(TagBinaryOp) + writeByte(op); writeTree(lhs); writeTree(rhs) + + case NewArray(tpe, lengths) => + writeByte(TagNewArray) + writeArrayType(tpe); writeTrees(lengths) + + case ArrayValue(tpe, elems) => + writeByte(TagArrayValue) + writeArrayType(tpe); writeTrees(elems) + + case ArrayLength(array) => + writeByte(TagArrayLength) + writeTree(array) + + case ArraySelect(array, index) => + writeByte(TagArraySelect) + writeTree(array); writeTree(index) + writeType(tree.tpe) + + case RecordValue(tpe, elems) => + writeByte(TagRecordValue) + writeType(tpe); writeTrees(elems) + + case IsInstanceOf(expr, cls) => + writeByte(TagIsInstanceOf) + writeTree(expr); writeReferenceType(cls) + + case AsInstanceOf(expr, cls) => + writeByte(TagAsInstanceOf) + writeTree(expr); writeReferenceType(cls) + + case Unbox(expr, charCode) => + writeByte(TagUnbox) + writeTree(expr); writeByte(charCode.toByte) + + case GetClass(expr) => + writeByte(TagGetClass) + writeTree(expr) + + case CallHelper(helper, args) => + writeByte(TagCallHelper) + writeString(helper); writeTrees(args) + writeType(tree.tpe) + + case JSNew(ctor, args) => + writeByte(TagJSNew) + writeTree(ctor); writeTrees(args) + + case JSDotSelect(qualifier, item) => + writeByte(TagJSDotSelect) + writeTree(qualifier); writeIdent(item) + + case JSBracketSelect(qualifier, item) => + writeByte(TagJSBracketSelect) + writeTree(qualifier); writeTree(item) + + case JSFunctionApply(fun, args) => + writeByte(TagJSFunctionApply) + writeTree(fun); writeTrees(args) + + case JSDotMethodApply(receiver, method, args) => + writeByte(TagJSDotMethodApply) + writeTree(receiver); writeIdent(method); writeTrees(args) + + case JSBracketMethodApply(receiver, method, args) => + writeByte(TagJSBracketMethodApply) + writeTree(receiver); writeTree(method); writeTrees(args) + + case JSDelete(prop) => + writeByte(TagJSDelete) + writeTree(prop) + + case JSUnaryOp(op, lhs) => + writeByte(TagJSUnaryOp) + writeString(op); writeTree(lhs) + + case JSBinaryOp(op, lhs, rhs) => + writeByte(TagJSBinaryOp) + writeString(op); writeTree(lhs); writeTree(rhs) + + case JSArrayConstr(items) => + writeByte(TagJSArrayConstr) + writeTrees(items) + + case JSObjectConstr(fields) => + writeByte(TagJSObjectConstr) + writeInt(fields.size) + fields foreach { field => + writePropertyName(field._1); writeTree(field._2) + } + + case JSEnvInfo() => + writeByte(TagJSEnvInfo) + + // Literals + + case Undefined() => + writeByte(TagUndefined) + + case UndefinedParam() => + writeByte(TagUndefinedParam) + writeType(tree.tpe) + + case Null() => + writeByte(TagNull) + + case BooleanLiteral(value) => + writeByte(TagBooleanLiteral) + writeBoolean(value) + + case IntLiteral(value) => + writeByte(TagIntLiteral) + writeInt(value) + + case LongLiteral(value) => + writeByte(TagLongLiteral) + writeLong(value) + + case FloatLiteral(value) => + writeByte(TagFloatLiteral) + writeFloat(value) + + case DoubleLiteral(value) => + writeByte(TagDoubleLiteral) + writeDouble(value) + + case StringLiteral(value) => + writeByte(TagStringLiteral) + writeString(value) + + case ClassOf(cls) => + writeByte(TagClassOf) + writeReferenceType(cls) + + case VarRef(ident, mutable) => + writeByte(TagVarRef) + writeIdent(ident); writeBoolean(mutable) + writeType(tree.tpe) + + case This() => + writeByte(TagThis) + writeType(tree.tpe) + + case Closure(captureParams, params, body, captureValues) => + writeByte(TagClosure) + writeTrees(captureParams) + writeTrees(params) + writeTree(body) + writeTrees(captureValues) + + case ClassDef(name, kind, parent, ancestors, defs) => + writeByte(TagClassDef) + writeIdent(name) + writeByte(ClassKind.toByte(kind)) + writeOptIdent(parent) + writeIdents(ancestors) + writeTrees(defs) + + case methodDef: MethodDef => + val MethodDef(name, args, resultType, body) = methodDef + + writeByte(TagMethodDef) + writeOptHash(methodDef.hash) + + // Prepare for back-jump and write dummy length + bufferUnderlying.markJump() + writeInt(-1) + + // Write out method def + writePropertyName(name); writeTrees(args); writeType(resultType); writeTree(body) + + // Jump back and write true length + val length = bufferUnderlying.jumpBack() + writeInt(length) + bufferUnderlying.continue() + + case PropertyDef(name, getter, arg, setter) => + writeByte(TagPropertyDef) + writePropertyName(name); writeTree(getter); writeTree(arg); writeTree(setter) + + case ConstructorExportDef(fullName, args, body) => + writeByte(TagConstructorExportDef) + writeString(fullName); writeTrees(args); writeTree(body) + + case ModuleExportDef(fullName) => + writeByte(TagModuleExportDef) + writeString(fullName) + } + if (UseDebugMagic) + writeInt(DebugMagic) + } + + def writeTrees(trees: List[Tree]): Unit = { + buffer.writeInt(trees.size) + trees.foreach(writeTree) + } + + def writeIdent(ident: Ident): Unit = { + writePosition(ident.pos) + writeString(ident.name); writeString(ident.originalName.getOrElse("")) + } + + def writeIdents(idents: List[Ident]): Unit = { + buffer.writeInt(idents.size) + idents.foreach(writeIdent) + } + + def writeOptIdent(optIdent: Option[Ident]): Unit = { + buffer.writeBoolean(optIdent.isDefined) + optIdent.foreach(writeIdent) + } + + def writeType(tpe: Type): Unit = { + tpe match { + case AnyType => buffer.write(TagAnyType) + case NothingType => buffer.write(TagNothingType) + case UndefType => buffer.write(TagUndefType) + case BooleanType => buffer.write(TagBooleanType) + case IntType => buffer.write(TagIntType) + case LongType => buffer.write(TagLongType) + case FloatType => buffer.write(TagFloatType) + case DoubleType => buffer.write(TagDoubleType) + case StringType => buffer.write(TagStringType) + case NullType => buffer.write(TagNullType) + case NoType => buffer.write(TagNoType) + + case tpe: ClassType => + buffer.write(TagClassType) + writeClassType(tpe) + + case tpe: ArrayType => + buffer.write(TagArrayType) + writeArrayType(tpe) + + case RecordType(fields) => + buffer.write(TagRecordType) + buffer.writeInt(fields.size) + for (RecordType.Field(name, originalName, tpe, mutable) <- fields) { + writeString(name) + writeString(originalName.getOrElse("")) + writeType(tpe) + buffer.writeBoolean(mutable) + } + } + } + + def writeClassType(tpe: ClassType): Unit = + writeString(tpe.className) + + def writeArrayType(tpe: ArrayType): Unit = { + writeString(tpe.baseClassName) + buffer.writeInt(tpe.dimensions) + } + + def writeReferenceType(tpe: ReferenceType): Unit = + writeType(tpe) + + def writePropertyName(name: PropertyName): Unit = { + name match { + case name: Ident => buffer.writeBoolean(true); writeIdent(name) + case name: StringLiteral => buffer.writeBoolean(false); writeTree(name) + } + } + + def writePosition(pos: Position): Unit = { + import buffer._ + import PositionFormat._ + + def writeFull(): Unit = { + writeByte(FormatFullMaskValue) + writeInt(fileToIndex(pos.source)) + writeInt(pos.line) + writeInt(pos.column) + } + + if (pos == Position.NoPosition) { + writeByte(FormatNoPositionValue) + } else if (lastPosition == Position.NoPosition || + pos.source != lastPosition.source) { + writeFull() + lastPosition = pos + } else { + val line = pos.line + val column = pos.column + val lineDiff = line - lastPosition.line + val columnDiff = column - lastPosition.column + val columnIsByte = column >= 0 && column < 256 + + if (lineDiff == 0 && columnDiff >= -64 && columnDiff < 64) { + writeByte((columnDiff << Format1Shift) | Format1MaskValue) + } else if (lineDiff >= -32 && lineDiff < 32 && columnIsByte) { + writeByte((lineDiff << Format2Shift) | Format2MaskValue) + writeByte(column) + } else if (lineDiff >= Short.MinValue && lineDiff <= Short.MaxValue && columnIsByte) { + writeByte(Format3MaskValue) + writeShort(lineDiff) + writeByte(column) + } else { + writeFull() + } + + lastPosition = pos + } + + if (UseDebugMagic) + writeInt(PosDebugMagic) + } + + def writeOptHash(optHash: Option[TreeHash]): Unit = { + buffer.writeBoolean(optHash.isDefined) + for (hash <- optHash) { + buffer.write(hash.treeHash) + buffer.write(hash.posHash) + } + } + + def writeString(s: String): Unit = + buffer.writeInt(stringToIndex(s)) + } + + private final class Deserializer(stream: InputStream, sourceVersion: String) { + private[this] val input = new DataInputStream(stream) + + private[this] val files = + Array.fill(input.readInt())(new URI(input.readUTF())) + + private[this] val strings = + Array.fill(input.readInt())(input.readUTF()) + + private[this] var lastPosition: Position = Position.NoPosition + + def deserialize(): Tree = { + readTree() + } + + def readTree(): Tree = { + import input._ + implicit val pos = readPosition() + val tag = readByte() + val result = (tag: @switch) match { + case TagEmptyTree => EmptyTree + + case TagVarDef => VarDef(readIdent(), readType(), readBoolean(), readTree()) + case TagParamDef => ParamDef(readIdent(), readType(), readBoolean()) + + case TagSkip => Skip() + case TagBlock => Block(readTrees()) + case TagLabeled => Labeled(readIdent(), readType(), readTree()) + case TagAssign => Assign(readTree(), readTree()) + case TagReturn => Return(readTree(), readOptIdent()) + case TagIf => If(readTree(), readTree(), readTree())(readType()) + case TagWhile => While(readTree(), readTree(), readOptIdent()) + case TagDoWhile => DoWhile(readTree(), readTree(), readOptIdent()) + case TagTry => Try(readTree(), readIdent(), readTree(), readTree())(readType()) + case TagThrow => Throw(readTree()) + case TagContinue => Continue(readOptIdent()) + case TagMatch => + Match(readTree(), List.fill(readInt()) { + (readTrees().map(_.asInstanceOf[Literal]), readTree()) + }, readTree())(readType()) + case TagDebugger => Debugger() + + case TagNew => New(readClassType(), readIdent(), readTrees()) + case TagLoadModule => LoadModule(readClassType()) + case TagStoreModule => StoreModule(readClassType(), readTree()) + case TagSelect => Select(readTree(), readIdent(), readBoolean())(readType()) + case TagApply => Apply(readTree(), readIdent(), readTrees())(readType()) + case TagStaticApply => StaticApply(readTree(), readClassType(), readIdent(), readTrees())(readType()) + case TagTraitImplApply => TraitImplApply(readClassType(), readIdent(), readTrees())(readType()) + case TagUnaryOp => UnaryOp(readByte(), readTree()) + case TagBinaryOp => BinaryOp(readByte(), readTree(), readTree()) + case TagNewArray => NewArray(readArrayType(), readTrees()) + case TagArrayValue => ArrayValue(readArrayType(), readTrees()) + case TagArrayLength => ArrayLength(readTree()) + case TagArraySelect => ArraySelect(readTree(), readTree())(readType()) + case TagRecordValue => RecordValue(readType().asInstanceOf[RecordType], readTrees()) + case TagIsInstanceOf => IsInstanceOf(readTree(), readReferenceType()) + case TagAsInstanceOf => AsInstanceOf(readTree(), readReferenceType()) + case TagUnbox => Unbox(readTree(), readByte().toChar) + case TagGetClass => GetClass(readTree()) + case TagCallHelper => CallHelper(readString(), readTrees())(readType()) + + case TagJSNew => JSNew(readTree(), readTrees()) + case TagJSDotSelect => JSDotSelect(readTree(), readIdent()) + case TagJSBracketSelect => JSBracketSelect(readTree(), readTree()) + case TagJSFunctionApply => JSFunctionApply(readTree(), readTrees()) + case TagJSDotMethodApply => JSDotMethodApply(readTree(), readIdent(), readTrees()) + case TagJSBracketMethodApply => JSBracketMethodApply(readTree(), readTree(), readTrees()) + case TagJSDelete => JSDelete(readTree()) + case TagJSUnaryOp => JSUnaryOp(readString(), readTree()) + case TagJSBinaryOp => JSBinaryOp(readString(), readTree(), readTree()) + case TagJSArrayConstr => JSArrayConstr(readTrees()) + case TagJSObjectConstr => + JSObjectConstr(List.fill(readInt())((readPropertyName(), readTree()))) + case TagJSEnvInfo => JSEnvInfo() + + case TagUndefined => Undefined() + case TagUndefinedParam => UndefinedParam()(readType()) + case TagNull => Null() + case TagBooleanLiteral => BooleanLiteral(readBoolean()) + case TagIntLiteral => IntLiteral(readInt()) + case TagLongLiteral => LongLiteral(readLong()) + case TagFloatLiteral => FloatLiteral(readFloat()) + case TagDoubleLiteral => DoubleLiteral(readDouble()) + case TagStringLiteral => StringLiteral(readString()) + case TagClassOf => ClassOf(readReferenceType()) + + case TagVarRef => VarRef(readIdent(), readBoolean())(readType()) + case TagThis => This()(readType()) + case TagClosure => + Closure(readParamDefs(), readParamDefs(), readTree(), readTrees()) + + case TagClassDef => + val name = readIdent() + val kind = ClassKind.fromByte(readByte()) + val parent = readOptIdent() + val ancestors = readIdents() + val defs = readTrees() + ClassDef(name, kind, parent, ancestors, defs) + + case TagMethodDef => + val optHash = readOptHash() + // read and discard the length + val len = readInt() + assert(len >= 0) + MethodDef(readPropertyName(), readParamDefs(), readType(), readTree())(optHash) + case TagPropertyDef => + PropertyDef(readPropertyName(), readTree(), + readTree().asInstanceOf[ParamDef], readTree()) + case TagConstructorExportDef => + ConstructorExportDef(readString(), readParamDefs(), readTree()) + case TagModuleExportDef => + ModuleExportDef(readString()) + } + if (UseDebugMagic) { + val magic = readInt() + assert(magic == DebugMagic, + s"Bad magic after reading a ${result.getClass}!") + } + result + } + + def readTrees(): List[Tree] = + List.fill(input.readInt())(readTree()) + + def readParamDefs(): List[ParamDef] = + readTrees().map(_.asInstanceOf[ParamDef]) + + def readIdent(): Ident = { + implicit val pos = readPosition() + val name = readString() + val originalName = readString() + Ident(name, if (originalName.isEmpty) None else Some(originalName)) + } + + def readIdents(): List[Ident] = + List.fill(input.readInt())(readIdent()) + + def readOptIdent(): Option[Ident] = { + if (input.readBoolean()) Some(readIdent()) + else None + } + + def readType(): Type = { + val tag = input.readByte() + (tag: @switch) match { + case TagAnyType => AnyType + case TagNothingType => NothingType + case TagUndefType => UndefType + case TagBooleanType => BooleanType + case TagIntType => IntType + case TagLongType => LongType + case TagFloatType => FloatType + case TagDoubleType => DoubleType + case TagStringType => StringType + case TagNullType => NullType + case TagNoType => NoType + + case TagClassType => readClassType() + case TagArrayType => readArrayType() + + case TagRecordType => + RecordType(List.fill(input.readInt()) { + val name = readString() + val originalName = readString() + val tpe = readType() + val mutable = input.readBoolean() + RecordType.Field(name, + if (originalName.isEmpty) None else Some(originalName), + tpe, mutable) + }) + } + } + + def readClassType(): ClassType = + ClassType(readString()) + + def readArrayType(): ArrayType = + ArrayType(readString(), input.readInt()) + + def readReferenceType(): ReferenceType = + readType().asInstanceOf[ReferenceType] + + def readPropertyName(): PropertyName = { + if (input.readBoolean()) readIdent() + else readTree().asInstanceOf[StringLiteral] + } + + def readPosition(): Position = { + import input._ + import PositionFormat._ + + val first = readByte() + + val result = if (first == FormatNoPositionValue) { + Position.NoPosition + } else { + val result = if ((first & FormatFullMask) == FormatFullMaskValue) { + val file = files(readInt()) + val line = readInt() + val column = readInt() + Position(file, line, column) + } else { + assert(lastPosition != NoPosition, + "Position format error: first position must be full") + if ((first & Format1Mask) == Format1MaskValue) { + val columnDiff = first >> Format1Shift + Position(lastPosition.source, lastPosition.line, + lastPosition.column + columnDiff) + } else if ((first & Format2Mask) == Format2MaskValue) { + val lineDiff = first >> Format2Shift + val column = readByte() & 0xff // unsigned + Position(lastPosition.source, + lastPosition.line + lineDiff, column) + } else { + assert((first & Format3Mask) == Format3MaskValue, + s"Position format error: first byte $first does not match any format") + val lineDiff = readShort() + val column = readByte() & 0xff // unsigned + Position(lastPosition.source, + lastPosition.line + lineDiff, column) + } + } + lastPosition = result + result + } + + if (UseDebugMagic) { + val magic = readInt() + assert(magic == PosDebugMagic, + s"Bad magic after reading position with first byte $first") + } + + result + } + + def readOptHash(): Option[TreeHash] = { + if (input.readBoolean()) { + val treeHash = new Array[Byte](20) + val posHash = new Array[Byte](20) + input.readFully(treeHash) + input.readFully(posHash) + Some(new TreeHash(treeHash, posHash)) + } else None + } + + def readString(): String = { + strings(input.readInt()) + } + } +} diff --git a/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Tags.scala b/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Tags.scala new file mode 100644 index 0000000..a03926c --- /dev/null +++ b/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Tags.scala @@ -0,0 +1,107 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js IR ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.ir + +/** Serialization and hashing tags for trees and types */ +private[ir] object Tags { + + // Tags for Trees + + final val TagEmptyTree = 1 + + final val TagVarDef = TagEmptyTree + 1 + final val TagParamDef = TagVarDef + 1 + + final val TagSkip = TagParamDef + 1 + final val TagBlock = TagSkip + 1 + final val TagLabeled = TagBlock + 1 + final val TagAssign = TagLabeled + 1 + final val TagReturn = TagAssign + 1 + final val TagIf = TagReturn + 1 + final val TagWhile = TagIf + 1 + final val TagDoWhile = TagWhile + 1 + final val TagTry = TagDoWhile + 1 + final val TagThrow = TagTry + 1 + final val TagContinue = TagThrow + 1 + final val TagMatch = TagContinue + 1 + final val TagDebugger = TagMatch + 1 + + final val TagNew = TagDebugger + 1 + final val TagLoadModule = TagNew + 1 + final val TagStoreModule = TagLoadModule + 1 + final val TagSelect = TagStoreModule + 1 + final val TagApply = TagSelect + 1 + final val TagStaticApply = TagApply + 1 + final val TagTraitImplApply = TagStaticApply + 1 + final val TagUnaryOp = TagTraitImplApply + 1 + final val TagBinaryOp = TagUnaryOp + 1 + final val TagNewArray = TagBinaryOp + 1 + final val TagArrayValue = TagNewArray + 1 + final val TagArrayLength = TagArrayValue + 1 + final val TagArraySelect = TagArrayLength + 1 + final val TagRecordValue = TagArraySelect + 1 + final val TagIsInstanceOf = TagRecordValue + 1 + final val TagAsInstanceOf = TagIsInstanceOf + 1 + final val TagUnbox = TagAsInstanceOf + 1 + final val TagGetClass = TagUnbox + 1 + final val TagCallHelper = TagGetClass + 1 + + final val TagJSNew = TagCallHelper + 1 + final val TagJSDotSelect = TagJSNew + 1 + final val TagJSBracketSelect = TagJSDotSelect + 1 + final val TagJSFunctionApply = TagJSBracketSelect + 1 + final val TagJSDotMethodApply = TagJSFunctionApply + 1 + final val TagJSBracketMethodApply = TagJSDotMethodApply + 1 + final val TagJSDelete = TagJSBracketMethodApply + 1 + final val TagJSUnaryOp = TagJSDelete + 1 + final val TagJSBinaryOp = TagJSUnaryOp + 1 + final val TagJSArrayConstr = TagJSBinaryOp + 1 + final val TagJSObjectConstr = TagJSArrayConstr + 1 + final val TagJSEnvInfo = TagJSObjectConstr + 1 + + final val TagUndefined = TagJSEnvInfo + 1 + final val TagUndefinedParam = TagUndefined + 1 + final val TagNull = TagUndefinedParam + 1 + final val TagBooleanLiteral = TagNull + 1 + final val TagIntLiteral = TagBooleanLiteral + 1 + final val TagLongLiteral = TagIntLiteral + 1 + final val TagFloatLiteral = TagLongLiteral + 1 + final val TagDoubleLiteral = TagFloatLiteral + 1 + final val TagStringLiteral = TagDoubleLiteral + 1 + final val TagClassOf = TagStringLiteral + 1 + + final val TagVarRef = TagClassOf + 1 + final val TagThis = TagVarRef + 1 + final val TagClosure = TagThis + 1 + + final val TagClassDef = TagClosure + 1 + final val TagMethodDef = TagClassDef + 1 + final val TagPropertyDef = TagMethodDef + 1 + final val TagConstructorExportDef = TagPropertyDef + 1 + final val TagModuleExportDef = TagConstructorExportDef + 1 + + // Tags for Types + + final val TagAnyType = 1 + final val TagNothingType = TagAnyType + 1 + final val TagUndefType = TagNothingType + 1 + final val TagBooleanType = TagUndefType + 1 + final val TagIntType = TagBooleanType + 1 + final val TagLongType = TagIntType + 1 + final val TagFloatType = TagLongType + 1 + final val TagDoubleType = TagFloatType + 1 + final val TagStringType = TagDoubleType + 1 + final val TagNullType = TagStringType + 1 + final val TagClassType = TagNullType + 1 + final val TagArrayType = TagClassType + 1 + final val TagRecordType = TagArrayType + 1 + final val TagNoType = TagRecordType + 1 + +} diff --git a/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Transformers.scala b/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Transformers.scala new file mode 100644 index 0000000..5e4f40c --- /dev/null +++ b/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Transformers.scala @@ -0,0 +1,218 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js IR ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.ir + +import Trees._ + +object Transformers { + + abstract class Transformer { + final def transformStat(tree: Tree): Tree = + transform(tree, isStat = true) + + final def transformExpr(tree: Tree): Tree = + transform(tree, isStat = false) + + def transform(tree: Tree, isStat: Boolean): Tree = { + implicit val pos = tree.pos + + tree match { + // Definitions + + case VarDef(ident, vtpe, mutable, rhs) => + VarDef(ident, vtpe, mutable, transformExpr(rhs)) + + // Control flow constructs + + case Block(stats :+ expr) => + Block(stats.map(transformStat) :+ transform(expr, isStat)) + + case Labeled(label, tpe, body) => + Labeled(label, tpe, transform(body, isStat)) + + case Assign(lhs, rhs) => + Assign(transformExpr(lhs), transformExpr(rhs)) + + case Return(expr, label) => + Return(transformExpr(expr), label) + + case If(cond, thenp, elsep) => + If(transformExpr(cond), transform(thenp, isStat), + transform(elsep, isStat))(tree.tpe) + + case While(cond, body, label) => + While(transformExpr(cond), transformStat(body), label) + + case DoWhile(body, cond, label) => + DoWhile(transformStat(body), transformExpr(cond), label) + + case Try(block, errVar, handler, finalizer) => + Try(transform(block, isStat), errVar, transform(handler, isStat), + transformStat(finalizer))(tree.tpe) + + case Throw(expr) => + Throw(transformExpr(expr)) + + case Match(selector, cases, default) => + Match(transformExpr(selector), + cases map (c => (c._1, transform(c._2, isStat))), + transform(default, isStat))(tree.tpe) + + // Scala expressions + + case New(cls, ctor, args) => + New(cls, ctor, args map transformExpr) + + case StoreModule(cls, value) => + StoreModule(cls, transformExpr(value)) + + case Select(qualifier, item, mutable) => + Select(transformExpr(qualifier), item, mutable)(tree.tpe) + + case Apply(receiver, method, args) => + Apply(transformExpr(receiver), method, + args map transformExpr)(tree.tpe) + + case StaticApply(receiver, cls, method, args) => + StaticApply(transformExpr(receiver), cls, method, + args map transformExpr)(tree.tpe) + + case TraitImplApply(impl, method, args) => + TraitImplApply(impl, method, args map transformExpr)(tree.tpe) + + case UnaryOp(op, lhs) => + UnaryOp(op, transformExpr(lhs)) + + case BinaryOp(op, lhs, rhs) => + BinaryOp(op, transformExpr(lhs), transformExpr(rhs)) + + case NewArray(tpe, lengths) => + NewArray(tpe, lengths map transformExpr) + + case ArrayValue(tpe, elems) => + ArrayValue(tpe, elems map transformExpr) + + case ArrayLength(array) => + ArrayLength(transformExpr(array)) + + case ArraySelect(array, index) => + ArraySelect(transformExpr(array), transformExpr(index))(tree.tpe) + + case RecordValue(tpe, elems) => + RecordValue(tpe, elems map transformExpr) + + case IsInstanceOf(expr, cls) => + IsInstanceOf(transformExpr(expr), cls) + + case AsInstanceOf(expr, cls) => + AsInstanceOf(transformExpr(expr), cls) + + case Unbox(expr, charCode) => + Unbox(transformExpr(expr), charCode) + + case GetClass(expr) => + GetClass(transformExpr(expr)) + + case CallHelper(helper, args) => + CallHelper(helper, args map transformExpr)(tree.tpe) + + // JavaScript expressions + + case JSNew(ctor, args) => + JSNew(transformExpr(ctor), args map transformExpr) + + case JSDotSelect(qualifier, item) => + JSDotSelect(transformExpr(qualifier), item) + + case JSBracketSelect(qualifier, item) => + JSBracketSelect(transformExpr(qualifier), transformExpr(item)) + + case JSFunctionApply(fun, args) => + JSFunctionApply(transformExpr(fun), args map transformExpr) + + case JSDotMethodApply(receiver, method, args) => + JSDotMethodApply(transformExpr(receiver), method, + args map transformExpr) + + case JSBracketMethodApply(receiver, method, args) => + JSBracketMethodApply(transformExpr(receiver), transformExpr(method), + args map transformExpr) + + case JSDelete(prop) => + JSDelete(transformExpr(prop)) + + case JSUnaryOp(op, lhs) => + JSUnaryOp(op, transformExpr(lhs)) + + case JSBinaryOp(op, lhs, rhs) => + JSBinaryOp(op, transformExpr(lhs), transformExpr(rhs)) + + case JSArrayConstr(items) => + JSArrayConstr(items map transformExpr) + + case JSObjectConstr(fields) => + JSObjectConstr(fields map { + case (name, value) => (name, transformExpr(value)) + }) + + // Atomic expressions + + case Closure(captureParams, params, body, captureValues) => + Closure(captureParams, params, transformExpr(body), + captureValues.map(transformExpr)) + + // Trees that need not be transformed + + case _:Skip | _:Continue | _:LoadModule | _:JSEnvInfo | + _:Literal | _:VarRef | _:This | EmptyTree => + tree + + case _ => + sys.error(s"Invalid tree in transform() of class ${tree.getClass}") + } + } + } + + abstract class ClassTransformer extends Transformer { + def transformClassDef(tree: ClassDef): ClassDef = { + val ClassDef(name, kind, parent, ancestors, defs) = tree + ClassDef(name, kind, parent, ancestors, defs.map(transformDef))(tree.pos) + } + + def transformDef(tree: Tree): Tree = { + implicit val pos = tree.pos + + tree match { + case VarDef(name, vtpe, mutable, rhs) => + VarDef(name, vtpe, mutable, transformExpr(rhs)) + + case MethodDef(name, args, resultType, body) => + MethodDef(name, args, resultType, transformStat(body))(None) + + case PropertyDef(name, getterBody, setterArg, setterBody) => + PropertyDef( + name, + transformStat(getterBody), + setterArg, + transformStat(setterBody)) + + case ConstructorExportDef(fullName, args, body) => + ConstructorExportDef(fullName, args, transformStat(body)) + + case ModuleExportDef(_) => + tree + + case _ => + sys.error(s"Invalid tree in transformDef() of class ${tree.getClass}") + } + } + } + +} diff --git a/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Traversers.scala b/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Traversers.scala new file mode 100644 index 0000000..1b77e5e --- /dev/null +++ b/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Traversers.scala @@ -0,0 +1,197 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js IR ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.ir + +import Trees._ + +object Traversers { + + class Traverser { + def traverse(tree: Tree): Unit = tree match { + // Definitions + + case VarDef(ident, vtpe, mutable, rhs) => + traverse(rhs) + + // Control flow constructs + + case Block(stats) => + stats foreach traverse + + case Labeled(label, tpe, body) => + traverse(body) + + case Assign(lhs, rhs) => + traverse(lhs) + traverse(rhs) + + case Return(expr, label) => + traverse(expr) + + case If(cond, thenp, elsep) => + traverse(cond) + traverse(thenp) + traverse(elsep) + + case While(cond, body, label) => + traverse(cond) + traverse(body) + + case DoWhile(body, cond, label) => + traverse(body) + traverse(cond) + + case Try(block, errVar, handler, finalizer) => + traverse(block) + traverse(handler) + traverse(finalizer) + + case Throw(expr) => + traverse(expr) + + case Match(selector, cases, default) => + traverse(selector) + cases foreach (c => (c._1 map traverse, traverse(c._2))) + traverse(default) + + // Scala expressions + + case New(cls, ctor, args) => + args foreach traverse + + case StoreModule(cls, value) => + traverse(value) + + case Select(qualifier, item, mutable) => + traverse(qualifier) + + case Apply(receiver, method, args) => + traverse(receiver) + args foreach traverse + + case StaticApply(receiver, cls, method, args) => + traverse(receiver) + args foreach traverse + + case TraitImplApply(impl, method, args) => + args foreach traverse + + case UnaryOp(op, lhs) => + traverse(lhs) + + case BinaryOp(op, lhs, rhs) => + traverse(lhs) + traverse(rhs) + + case NewArray(tpe, lengths) => + lengths foreach traverse + + case ArrayValue(tpe, elems) => + elems foreach traverse + + case ArrayLength(array) => + traverse(array) + + case ArraySelect(array, index) => + traverse(array) + traverse(index) + + case RecordValue(tpe, elems) => + elems foreach traverse + + case IsInstanceOf(expr, cls) => + traverse(expr) + + case AsInstanceOf(expr, cls) => + traverse(expr) + + case Unbox(expr, charCode) => + traverse(expr) + + case GetClass(expr) => + traverse(expr) + + case CallHelper(helper, args) => + args foreach traverse + + // JavaScript expressions + + case JSNew(ctor, args) => + traverse(ctor) + args foreach traverse + + case JSDotSelect(qualifier, item) => + traverse(qualifier) + + case JSBracketSelect(qualifier, item) => + traverse(qualifier) + traverse(item) + + case JSFunctionApply(fun, args) => + traverse(fun) + args foreach traverse + + case JSDotMethodApply(receiver, method, args) => + traverse(receiver) + args foreach traverse + + case JSBracketMethodApply(receiver, method, args) => + traverse(receiver) + traverse(method) + args foreach traverse + + case JSDelete(prop) => + traverse(prop) + + case JSUnaryOp(op, lhs) => + traverse(lhs) + + case JSBinaryOp(op, lhs, rhs) => + traverse(lhs) + traverse(rhs) + + case JSArrayConstr(items) => + items foreach traverse + + case JSObjectConstr(fields) => + fields foreach { f => traverse(f._2) } + + // Atomic expressions + + case Closure(captureParams, params, body, captureValues) => + traverse(body) + captureValues.foreach(traverse) + + // Classes + + case ClassDef(name, kind, parent, ancestors, defs) => + defs foreach traverse + + case MethodDef(name, args, resultType, body) => + traverse(body) + + case PropertyDef(name, getterBody, setterArg, setterBody) => + traverse(getterBody) + traverse(setterBody) + + case ConstructorExportDef(fullName, args, body) => + traverse(body) + + // Trees that need not be traversed + + case _:Skip | _:Continue | _:LoadModule | _:JSEnvInfo | + _:Literal | _:VarRef | _:This | _:ModuleExportDef | EmptyTree => + + case _ => + sys.error(s"Invalid tree in traverse() of class ${tree.getClass}") + } + } + +} diff --git a/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Trees.scala b/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Trees.scala new file mode 100644 index 0000000..4f58ece --- /dev/null +++ b/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Trees.scala @@ -0,0 +1,536 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js IR ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.ir + +import scala.annotation.switch + +import Position.NoPosition +import Types._ + +object Trees { + /** AST node of the IR. */ + abstract sealed class Tree { + val pos: Position + val tpe: Type + + def show: String = { + val writer = new java.io.StringWriter + val printer = new Printers.IRTreePrinter(writer) + printer.printTree(this) + writer.toString() + } + } + + case object EmptyTree extends Tree { + val pos = NoPosition + val tpe = NoType + } + + // Identifiers and properties + + sealed trait PropertyName { + def name: String + def pos: Position + } + + case class Ident(name: String, originalName: Option[String])( + implicit val pos: Position) extends PropertyName { + requireValidIdent(name) + } + + object Ident { + def apply(name: String)(implicit pos: Position): Ident = + new Ident(name, Some(name)) + } + + final def isValidIdentifier(name: String): Boolean = { + val c = name.head + (c == '$' || c == '_' || c.isUnicodeIdentifierStart) && + name.tail.forall(c => (c == '$') || c.isUnicodeIdentifierPart) && + !isKeyword(name) + } + + @inline final def requireValidIdent(name: String) { + require(isValidIdentifier(name), s"${name} is not a valid identifier") + } + + final val isKeyword: Set[String] = Set( + // Value keywords + "true", "false", "null", "undefined", + + // Current JavaScript keywords + "break", "case", "catch", "continue", "debugger", "default", "delete", + "do", "else", "finally", "for", "function", "if", "in", "instanceof", + "new", "return", "switch", "this", "throw", "try", "typeof", "var", + "void", "while", "with", + + // Future reserved keywords + "class", "const", "enum", "export", "extends", "import", "super", + + // Future reserved keywords in Strict mode + "implements", "interface", "let", "package", "private", "protected", + "public", "static", "yield", + + // Other reserved keywords found on the Web but not in the spec + "abstract", "boolean", "byte", "char", "double", "final", "float", + "goto", "int", "long", "native", "short", "synchronized", "throws", + "transient", "volatile" + ) + + // Definitions + + case class VarDef(name: Ident, vtpe: Type, mutable: Boolean, rhs: Tree)(implicit val pos: Position) extends Tree { + val tpe = NoType // cannot be in expression position + + def ref(implicit pos: Position): VarRef = + VarRef(name, mutable = mutable)(vtpe) + } + + case class ParamDef(name: Ident, ptpe: Type, mutable: Boolean)(implicit val pos: Position) extends Tree { + val tpe = NoType + + def ref(implicit pos: Position): VarRef = + VarRef(name, mutable = mutable)(ptpe) + } + + // Control flow constructs + + case class Skip()(implicit val pos: Position) extends Tree { + val tpe = NoType // cannot be in expression position + } + + class Block private (val stats: List[Tree])(implicit val pos: Position) extends Tree { + val tpe = stats.last.tpe + + override def toString(): String = + stats.mkString("Block(", ",", ")") + } + + object Block { + def apply(stats: List[Tree])(implicit pos: Position): Tree = { + val flattenedStats = stats flatMap { + case Skip() => Nil + case Block(subStats) => subStats + case other => other :: Nil + } + flattenedStats match { + case Nil => Skip() + case only :: Nil => only + case _ => new Block(flattenedStats) + } + } + + def apply(stats: Tree*)(implicit pos: Position): Tree = + apply(stats.toList) + + def unapply(block: Block): Some[List[Tree]] = Some(block.stats) + } + + case class Labeled(label: Ident, tpe: Type, body: Tree)(implicit val pos: Position) extends Tree + + case class Assign(lhs: Tree, rhs: Tree)(implicit val pos: Position) extends Tree { + require(lhs match { + case _:VarRef | _:Select | _:ArraySelect | + _:JSDotSelect | _:JSBracketSelect => true + case _ => false + }, s"Invalid lhs for Assign: $lhs") + + val tpe = NoType // cannot be in expression position + } + + case class Return(expr: Tree, label: Option[Ident] = None)(implicit val pos: Position) extends Tree { + val tpe = NothingType + } + + case class If(cond: Tree, thenp: Tree, elsep: Tree)(val tpe: Type)(implicit val pos: Position) extends Tree + + case class While(cond: Tree, body: Tree, label: Option[Ident] = None)(implicit val pos: Position) extends Tree { + // cannot be in expression position, unless it is infinite + val tpe = cond match { + case BooleanLiteral(true) => NothingType + case _ => NoType + } + } + + case class DoWhile(body: Tree, cond: Tree, label: Option[Ident] = None)(implicit val pos: Position) extends Tree { + val tpe = NoType // cannot be in expression position + } + + case class Try(block: Tree, errVar: Ident, handler: Tree, finalizer: Tree)(val tpe: Type)(implicit val pos: Position) extends Tree + + case class Throw(expr: Tree)(implicit val pos: Position) extends Tree { + val tpe = NothingType + } + + case class Continue(label: Option[Ident] = None)(implicit val pos: Position) extends Tree { + val tpe = NothingType + } + + /** A break-free switch (without fallthrough behavior). + * Unlike a JavaScript switch, it can be used in expression position. + * It supports alternatives explicitly (hence the List[Tree] in cases), + * whereas in a switch one would use the fallthrough behavior to + * implement alternatives. + * (This is not a pattern matching construct like in Scala.) + */ + case class Match(selector: Tree, cases: List[(List[Literal], Tree)], default: Tree)(val tpe: Type)(implicit val pos: Position) extends Tree + + case class Debugger()(implicit val pos: Position) extends Tree { + val tpe = NoType // cannot be in expression position + } + + // Scala expressions + + case class New(cls: ClassType, ctor: Ident, args: List[Tree])(implicit val pos: Position) extends Tree { + val tpe = cls + } + + case class LoadModule(cls: ClassType)(implicit val pos: Position) extends Tree { + val tpe = cls + } + + case class StoreModule(cls: ClassType, value: Tree)(implicit val pos: Position) extends Tree { + val tpe = NoType // cannot be in expression position + } + + case class Select(qualifier: Tree, item: Ident, mutable: Boolean)(val tpe: Type)(implicit val pos: Position) extends Tree + + case class Apply(receiver: Tree, method: Ident, args: List[Tree])(val tpe: Type)(implicit val pos: Position) extends Tree + + case class StaticApply(receiver: Tree, cls: ClassType, method: Ident, args: List[Tree])(val tpe: Type)(implicit val pos: Position) extends Tree + + case class TraitImplApply(impl: ClassType, method: Ident, args: List[Tree])(val tpe: Type)(implicit val pos: Position) extends Tree + + /** Unary operation (always preserves pureness). */ + case class UnaryOp(op: UnaryOp.Code, lhs: Tree)(implicit val pos: Position) extends Tree { + import UnaryOp._ + val tpe = (op: @switch) match { + case `typeof` => StringType + case LongToInt | DoubleToInt => IntType + case IntToLong | DoubleToLong => LongType + case DoubleToFloat => FloatType + case LongToDouble => DoubleType + case Boolean_! => BooleanType + } + } + + object UnaryOp { + /** Codes are raw Ints to be able to write switch matches on them. */ + type Code = Int + + final val typeof = 1 + + final val Boolean_! = 2 + + final val IntToLong = 3 + final val LongToInt = 4 + final val LongToDouble = 5 + final val DoubleToInt = 6 + final val DoubleToFloat = 7 + final val DoubleToLong = 8 + } + + /** Binary operation (always preserves pureness). */ + case class BinaryOp(op: BinaryOp.Code, lhs: Tree, rhs: Tree)(implicit val pos: Position) extends Tree { + import BinaryOp._ + val tpe = (op: @switch) match { + case === | !== | + `in` | `instanceof` | + Num_== | Num_!= | Num_< | Num_<= | Num_> | Num_>= | + Long_== | Long_!= | Long_< | Long_<= | Long_> | Long_>= | + Boolean_== | Boolean_!= | Boolean_| | Boolean_& => + BooleanType + case String_+ => + StringType + case Int_+ | Int_- | Int_* | Int_/ | Int_% | + Int_| | Int_& | Int_^ | Int_<< | Int_>>> | Int_>> => + IntType + case Float_+ | Float_- | Float_* | Float_/ | Float_% => + FloatType + case Double_+ | Double_- | Double_* | Double_/ | Double_% => + DoubleType + case Long_+ | Long_- | Long_* | Long_/ | Long_% | + Long_| | Long_& | Long_^ | Long_<< | Long_>>> | Long_>> => + LongType + } + } + + object BinaryOp { + /** Codes are raw Ints to be able to write switch matches on them. */ + type Code = Int + + final val === = 1 + final val !== = 2 + + final val String_+ = 3 + + final val in = 4 + final val instanceof = 5 + + final val Int_+ = 6 + final val Int_- = 7 + final val Int_* = 8 + final val Int_/ = 9 + final val Int_% = 10 + + final val Int_| = 11 + final val Int_& = 12 + final val Int_^ = 13 + final val Int_<< = 14 + final val Int_>>> = 15 + final val Int_>> = 16 + + final val Float_+ = 17 + final val Float_- = 18 + final val Float_* = 19 + final val Float_/ = 20 + final val Float_% = 21 + + final val Double_+ = 22 + final val Double_- = 23 + final val Double_* = 24 + final val Double_/ = 25 + final val Double_% = 26 + + final val Num_== = 27 + final val Num_!= = 28 + final val Num_< = 29 + final val Num_<= = 30 + final val Num_> = 31 + final val Num_>= = 32 + + final val Long_+ = 33 + final val Long_- = 34 + final val Long_* = 35 + final val Long_/ = 36 + final val Long_% = 37 + + final val Long_| = 38 + final val Long_& = 39 + final val Long_^ = 40 + final val Long_<< = 41 + final val Long_>>> = 42 + final val Long_>> = 43 + + final val Long_== = 44 + final val Long_!= = 45 + final val Long_< = 46 + final val Long_<= = 47 + final val Long_> = 48 + final val Long_>= = 49 + + final val Boolean_== = 50 + final val Boolean_!= = 51 + final val Boolean_| = 52 + final val Boolean_& = 53 + } + + case class NewArray(tpe: ArrayType, lengths: List[Tree])(implicit val pos: Position) extends Tree { + require(lengths.nonEmpty && lengths.size <= tpe.dimensions) + } + + case class ArrayValue(tpe: ArrayType, elems: List[Tree])(implicit val pos: Position) extends Tree + + case class ArrayLength(array: Tree)(implicit val pos: Position) extends Tree { + val tpe = IntType + } + + case class ArraySelect(array: Tree, index: Tree)(val tpe: Type)(implicit val pos: Position) extends Tree + + case class RecordValue(tpe: RecordType, elems: List[Tree])(implicit val pos: Position) extends Tree + + case class IsInstanceOf(expr: Tree, cls: ReferenceType)(implicit val pos: Position) extends Tree { + val tpe = BooleanType + } + + case class AsInstanceOf(expr: Tree, cls: ReferenceType)(implicit val pos: Position) extends Tree { + val tpe = cls match { + case ClassType(Definitions.RuntimeNullClass) => NullType + case ClassType(Definitions.RuntimeNothingClass) => NothingType + case _ => cls + } + } + + case class Unbox(expr: Tree, charCode: Char)(implicit val pos: Position) extends Tree { + val tpe = (charCode: @switch) match { + case 'Z' => BooleanType + case 'B' | 'S' | 'I' => IntType + case 'J' => LongType + case 'F' => FloatType + case 'D' => DoubleType + } + } + + case class GetClass(expr: Tree)(implicit val pos: Position) extends Tree { + val tpe = ClassType(Definitions.ClassClass) + } + + case class CallHelper(helper: String, args: List[Tree])(val tpe: Type)(implicit val pos: Position) extends Tree + + object CallHelper { + def apply(helper: String, args: Tree*)(tpe: Type)( + implicit pos: Position): CallHelper = { + CallHelper(helper, args.toList)(tpe) + } + } + + // JavaScript expressions + + case class JSNew(ctor: Tree, args: List[Tree])(implicit val pos: Position) extends Tree { + val tpe = AnyType + } + + case class JSDotSelect(qualifier: Tree, item: Ident)(implicit val pos: Position) extends Tree { + val tpe = AnyType + } + + case class JSBracketSelect(qualifier: Tree, item: Tree)(implicit val pos: Position) extends Tree { + val tpe = AnyType + } + + case class JSFunctionApply(fun: Tree, args: List[Tree])(implicit val pos: Position) extends Tree { + val tpe = AnyType + } + + case class JSDotMethodApply(receiver: Tree, method: Ident, args: List[Tree])(implicit val pos: Position) extends Tree { + val tpe = AnyType + } + + case class JSBracketMethodApply(receiver: Tree, method: Tree, args: List[Tree])(implicit val pos: Position) extends Tree { + val tpe = AnyType + } + + case class JSDelete(prop: Tree)(implicit val pos: Position) extends Tree { + require(prop match { + case _:JSDotSelect | _:JSBracketSelect => true + case _ => false + }, s"Invalid prop for JSDelete: $prop") + + val tpe = NoType // cannot be in expression position + } + + /** Unary operation (always preserves pureness). + * + * Operations which do not preserve pureness are not allowed in this tree. + * These are notably ++ and -- + */ + case class JSUnaryOp(op: String, lhs: Tree)(implicit val pos: Position) extends Tree { + val tpe = AnyType + } + + /** Binary operation (always preserves pureness). + * + * Operations which do not preserve pureness are not allowed in this tree. + * These are notably +=, -=, *=, /= and %= + */ + case class JSBinaryOp(op: String, lhs: Tree, rhs: Tree)(implicit val pos: Position) extends Tree { + val tpe = AnyType + } + + case class JSArrayConstr(items: List[Tree])(implicit val pos: Position) extends Tree { + val tpe = AnyType + } + + case class JSObjectConstr(fields: List[(PropertyName, Tree)])(implicit val pos: Position) extends Tree { + val tpe = AnyType + } + + case class JSEnvInfo()(implicit val pos: Position) extends Tree { + val tpe = AnyType + } + + // Literals + + /** Marker for literals. Literals are always pure. */ + sealed trait Literal extends Tree + + case class Undefined()(implicit val pos: Position) extends Literal { + val tpe = UndefType + } + + case class UndefinedParam()(val tpe: Type)(implicit val pos: Position) extends Literal + + case class Null()(implicit val pos: Position) extends Literal { + val tpe = NullType + } + + case class BooleanLiteral(value: Boolean)(implicit val pos: Position) extends Literal { + val tpe = BooleanType + } + + case class IntLiteral(value: Int)(implicit val pos: Position) extends Literal { + val tpe = IntType + } + + case class LongLiteral(value: Long)(implicit val pos: Position) extends Literal { + val tpe = LongType + } + + case class FloatLiteral(value: Float)(implicit val pos: Position) extends Literal { + val tpe = FloatType + } + + case class DoubleLiteral(value: Double)(implicit val pos: Position) extends Literal { + val tpe = DoubleType + } + + case class StringLiteral(value: String)( + implicit val pos: Position) extends Literal with PropertyName { + val tpe = StringType + override def name = value + } + + case class ClassOf(cls: ReferenceType)(implicit val pos: Position) extends Literal { + val tpe = ClassType(Definitions.ClassClass) + } + + // Atomic expressions + + case class VarRef(ident: Ident, mutable: Boolean)(val tpe: Type)(implicit val pos: Position) extends Tree + + case class This()(val tpe: Type)(implicit val pos: Position) extends Tree + + /** Closure with explicit captures. + * The n captures map to the n first formal arguments. + */ + case class Closure(captureParams: List[ParamDef], params: List[ParamDef], + body: Tree, captureValues: List[Tree])(implicit val pos: Position) extends Tree { + val tpe = AnyType + } + + // Classes + + case class ClassDef(name: Ident, kind: ClassKind, parent: Option[Ident], ancestors: List[Ident], defs: List[Tree])(implicit val pos: Position) extends Tree { + val tpe = NoType + } + + case class MethodDef(name: PropertyName, args: List[ParamDef], resultType: Type, body: Tree)( + val hash: Option[TreeHash])(implicit val pos: Position) extends Tree { + val tpe = NoType + } + + case class PropertyDef(name: PropertyName, getterBody: Tree, setterArg: ParamDef, setterBody: Tree)(implicit val pos: Position) extends Tree { + val tpe = NoType + } + + case class ConstructorExportDef(name: String, args: List[ParamDef], body: Tree)(implicit val pos: Position) extends Tree { + val tpe = NoType + } + + case class ModuleExportDef(fullName: String)(implicit val pos: Position) extends Tree { + val tpe = NoType + } + + /** A hash of a tree (usually a MethodDef). Contains two SHA-1 hashes */ + final class TreeHash(val treeHash: Array[Byte], val posHash: Array[Byte]) { + assert(treeHash.length == 20) + assert(posHash.length == 20) + } +} diff --git a/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Types.scala b/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Types.scala new file mode 100644 index 0000000..4af493a --- /dev/null +++ b/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Types.scala @@ -0,0 +1,182 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js IR ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.ir + +import scala.annotation.tailrec + +object Types { + + /** Type of an expression in the IR. */ + abstract sealed class Type { + def show(): String = { + val writer = new java.io.StringWriter + val printer = new Printers.IRTreePrinter(writer) + printer.printType(this) + writer.toString() + } + } + + /** Any type (the top type of this type system). + * A variable of this type can contain any value, including `undefined` + * and `null` and any raw JS value. This type supports a very limited set + * of Scala operations, the ones common to all values. Basically only + * reference equality tests and instance tests. It also supports all + * JavaScript operations, since all Scala objects are also genuine + * JavaScript objects. + * The type java.lang.Object in the back-end maps to [[AnyType]] because it + * can hold raw JS values (not only instances of Scala.js classes). + */ + case object AnyType extends Type + + /** Nothing type (the bottom type of this type system). + * Expressions from which one can never come back are typed as [[Nothing]]. + * For example, `throw` and `return`. + */ + case object NothingType extends Type + + /** The type of `undefined`. */ + case object UndefType extends Type + + /** Boolean type. + * It does not accept `null` nor `undefined`. + */ + case object BooleanType extends Type + + /** 32-bit signed integer type. + * It does not accept `null` nor `undefined`. + */ + case object IntType extends Type + + /** 64-bit signed integer type. + * It does not accept `null` nor `undefined`. + */ + case object LongType extends Type + + /** Float type (32-bit). + * It does not accept `null` nor `undefined`. + */ + case object FloatType extends Type + + /** Double type (64-bit). + * It does not accept `null` nor `undefined`. + */ + case object DoubleType extends Type + + /** String type. + * It does not accept `null` nor `undefined`. + */ + case object StringType extends Type + + /** The type of `null`. + * It does not accept `undefined`. + * The null type is a subtype of all class types and array types. + */ + case object NullType extends Type + + /** Reference types (allowed for classOf[], is/asInstanceOf[]). */ + sealed abstract class ReferenceType extends Type + + /** Class (or interface) type. */ + final case class ClassType(className: String) extends ReferenceType + + /** Array type. */ + final case class ArrayType(baseClassName: String, dimensions: Int) extends ReferenceType + + object ArrayType { + def apply(innerType: ReferenceType): ArrayType = innerType match { + case ClassType(className) => ArrayType(className, 1) + case ArrayType(className, dim) => ArrayType(className, dim + 1) + } + } + + /** Record type. + * Used by the optimizer to inline classes as records with multiple fields. + * They are desugared as several local variables by JSDesugaring. + * Record types cannot cross method boundaries, so they cannot appear as + * the type of fields or parameters, nor as result types of methods. + * The compiler itself never generates record types. + */ + final case class RecordType(fields: List[RecordType.Field]) extends Type { + def findField(name: String): RecordType.Field = + fields.find(_.name == name).get + } + + object RecordType { + final case class Field(name: String, originalName: Option[String], + tpe: Type, mutable: Boolean) + } + + /** No type. */ + case object NoType extends Type + + /** Tests whether a type `lhs` is a subtype of `rhs` (or equal). + * [[NoType]] is never a subtype or supertype of anything (including + * itself). All other types are subtypes of themselves. + * @param isSubclass A function testing whether a class/interface is a + * subclass of another class/interface. + */ + def isSubtype(lhs: Type, rhs: Type)( + isSubclass: (String, String) => Boolean): Boolean = { + import Definitions._ + + (lhs != NoType && rhs != NoType) && { + (lhs == rhs) || + ((lhs, rhs) match { + case (_, AnyType) => true + case (NothingType, _) => true + + case (ClassType(lhsClass), ClassType(rhsClass)) => + isSubclass(lhsClass, rhsClass) + + case (NullType, ClassType(_)) => true + case (NullType, ArrayType(_, _)) => true + + case (UndefType, ClassType(cls)) => + isSubclass(BoxedUnitClass, cls) + case (BooleanType, ClassType(cls)) => + isSubclass(BoxedBooleanClass, cls) + case (IntType, ClassType(cls)) => + isSubclass(BoxedIntegerClass, cls) || + cls == BoxedByteClass || + cls == BoxedShortClass || + cls == BoxedDoubleClass + case (LongType, ClassType(cls)) => + isSubclass(BoxedLongClass, cls) + case (FloatType, ClassType(cls)) => + isSubclass(BoxedFloatClass, cls) || + cls == BoxedDoubleClass + case (DoubleType, ClassType(cls)) => + isSubclass(BoxedDoubleClass, cls) + case (StringType, ClassType(cls)) => + isSubclass(StringClass, cls) + + case (IntType, DoubleType) => true + case (FloatType, DoubleType) => true + + case (ArrayType(lhsBase, lhsDims), ArrayType(rhsBase, rhsDims)) => + if (lhsDims < rhsDims) { + false // because Array[A] </: Array[Array[A]] + } else if (lhsDims > rhsDims) { + rhsBase == ObjectClass // because Array[Array[A]] <: Array[Object] + } else { // lhsDims == rhsDims + // lhsBase must be <: rhsBase + if (isPrimitiveClass(lhsBase) || isPrimitiveClass(rhsBase)) { + lhsBase == rhsBase + } else { + isSubclass(lhsBase, rhsBase) + } + } + + case _ => + false + }) + } + } +} diff --git a/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Utils.scala b/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Utils.scala new file mode 100644 index 0000000..d4769dc --- /dev/null +++ b/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Utils.scala @@ -0,0 +1,110 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js IR ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.ir + +import java.net.URI + +object Utils { + + /** Relativize target URI w.r.t. base URI */ + def relativize(base0: URI, trgt0: URI): URI = { + val base = base0.normalize + val trgt = trgt0.normalize + + if (base.isOpaque || !base.isAbsolute || base.getRawPath == null || + trgt.isOpaque || !trgt.isAbsolute || trgt.getRawPath == null || + base.getScheme != trgt.getScheme || + base.getRawAuthority != trgt.getRawAuthority) + trgt + else { + val trgtCmps = trgt.getRawPath.split('/') + val baseCmps = base.getRawPath.split('/') + + val prefixLen = (trgtCmps zip baseCmps).takeWhile(t => t._1 == t._2).size + + val newPathCmps = + List.fill(baseCmps.size - prefixLen)("..") ++ trgtCmps.drop(prefixLen) + + val newPath = newPathCmps.mkString("/") + + // Relative URI does not have scheme or authority + new URI(null, null, newPath, trgt.getRawQuery, trgt.getRawFragment) + } + } + + /** Adds an empty authority to URIs with the "file" scheme without authority. + * Some browsers don't fetch URIs without authority correctly. + */ + def fixFileURI(uri: URI): URI = + if (uri.getScheme() != "file" || uri.getAuthority() != null) uri + else new URI("file", "", uri.getPath(), uri.getQuery(), uri.getFragment()) + + def escapeJS(str: String): String = { + /* Note that Java and JavaScript happen to use the same encoding for + * Unicode, namely UTF-16, which means that 1 char from Java always equals + * 1 char in JavaScript. */ + val builder = new StringBuilder + str foreach { + case '\\' => builder.append("\\\\") + case '"' => builder.append("\\\"") + case '\u0007' => builder.append("\\a") + case '\u0008' => builder.append("\\b") + case '\u0009' => builder.append("\\t") + case '\u000A' => builder.append("\\n") + case '\u000B' => builder.append("\\v") + case '\u000C' => builder.append("\\f") + case '\u000D' => builder.append("\\r") + case c => + if (c >= 32 && c <= 126) builder.append(c.toChar) // ASCII printable characters + else builder.append(f"\\u$c%04x") + } + builder.result() + } + + /** A ByteArrayOutput stream that allows to jump back to a given + * position and complete some bytes. Methods must be called in the + * following order only: + * - [[markJump]] + * - [[jumpBack]] + * - [[continue]] + */ + private[ir] class JumpBackByteArrayOutputStream + extends java.io.ByteArrayOutputStream { + protected var jumpBackPos: Int = -1 + protected var headPos: Int = -1 + + /** Marks the current location for a jumpback */ + def markJump(): Unit = { + assert(jumpBackPos == -1) + assert(headPos == -1) + jumpBackPos = count + } + + /** Jumps back to the mark. Returns the number of bytes jumped */ + def jumpBack(): Int = { + assert(jumpBackPos >= 0) + assert(headPos == -1) + val jumped = count - jumpBackPos + headPos = count + count = jumpBackPos + jumpBackPos = -1 + jumped + } + + /** Continues to write at the head. */ + def continue(): Unit = { + assert(jumpBackPos == -1) + assert(headPos >= 0) + count = headPos + headPos = -1 + } + } + +} diff --git a/examples/scala-js/jasmine-test-framework/src/main/resources/jasmine-polyfills.js b/examples/scala-js/jasmine-test-framework/src/main/resources/jasmine-polyfills.js new file mode 100644 index 0000000..b1a5d44 --- /dev/null +++ b/examples/scala-js/jasmine-test-framework/src/main/resources/jasmine-polyfills.js @@ -0,0 +1,9 @@ +// jasmine.js requires the following 4 to be defined. +(function() { + var g = (typeof global === "object" && global && global["Object"] === Object) ? global : this; + var stub = function() { console.log("jasmine-polyfill.js stub called"); }; + g.setTimeout = g.setTimeout || stub; + g.clearTimeout = g.clearTimeout || stub; + g.setInterval = g.setInterval || stub; + g.clearInterval = g.clearInterval || stub; +}).call(this); diff --git a/examples/scala-js/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/Jasmine.scala b/examples/scala-js/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/Jasmine.scala new file mode 100644 index 0000000..ca0a63f --- /dev/null +++ b/examples/scala-js/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/Jasmine.scala @@ -0,0 +1,17 @@ +package org.scalajs.jasmine + +import scala.scalajs.js + +object Jasmine extends js.GlobalScope { + def jasmine: JasmineEnv = js.native + def describe(name: String, suite: js.Function0[_]): Unit = js.native + def it(title: String, test: js.Function0[_]): Unit = js.native + def xdescribe(name: String, suite: js.Function0[_]): Unit = js.native + def xit(title: String, test: js.Function0[_]): Unit = js.native + def beforeEach(block: js.Function0[_]): Unit = js.native + def afterEach(block: js.Function0[_]): Unit = js.native + def expect(exp: js.Any): JasmineExpectation = js.native + def runs(block: js.Function0[_]): Unit = js.native + def waits(timeout: Int): Unit = js.native + def waitsFor(block: js.Function0[Boolean], errorMsg: String, timeout: Int): Unit = js.native +} diff --git a/examples/scala-js/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/JasmineEnv.scala b/examples/scala-js/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/JasmineEnv.scala new file mode 100644 index 0000000..b81121e --- /dev/null +++ b/examples/scala-js/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/JasmineEnv.scala @@ -0,0 +1,14 @@ +package org.scalajs.jasmine + +import scala.scalajs.js + +trait JasmineEnv extends js.Object { + def Clock: JasmineEnv.Clock = js.native +} + +object JasmineEnv { + trait Clock extends js.Object { + def tick(time: Double): Unit = js.native + def useMock(): Unit = js.native + } +} diff --git a/examples/scala-js/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/JasmineExpectation.scala b/examples/scala-js/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/JasmineExpectation.scala new file mode 100644 index 0000000..9aad02e --- /dev/null +++ b/examples/scala-js/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/JasmineExpectation.scala @@ -0,0 +1,21 @@ +package org.scalajs.jasmine + +import scala.scalajs.js + +trait JasmineExpectation extends js.Object { + def toBe(exp: js.Any): Unit = js.native + def toEqual(exp: js.Any): Unit = js.native + def toMatch(exp: js.RegExp): Unit = js.native + def toMatch(exp: String): Unit = js.native + def toBeDefined(): Unit = js.native + def toBeUndefined(): Unit = js.native + def toBeNull(): Unit = js.native + def toBeTruthy(): Unit = js.native + def toBeFalsy(): Unit = js.native + def toContain(exp: js.Any): Unit = js.native + def toBeGreaterThan(exp: Double): Unit = js.native + def toBeLessThan(exp: Double): Unit = js.native + def toBeCloseTo(exp: Double, precision: Int = 2): Unit = js.native + def toThrow(): Unit = js.native + val not: JasmineExpectation = js.native +} diff --git a/examples/scala-js/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/Results.scala b/examples/scala-js/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/Results.scala new file mode 100644 index 0000000..aed78b9 --- /dev/null +++ b/examples/scala-js/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/Results.scala @@ -0,0 +1,13 @@ +package org.scalajs.jasmine + +import scala.scalajs.js + +trait Result extends js.Object { + def `type`: String = js.native + val trace: js.Dynamic = js.native +} + +trait ExpectationResult extends Result { + def passed(): Boolean = js.native + val message: String = js.native +} diff --git a/examples/scala-js/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/Spec.scala b/examples/scala-js/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/Spec.scala new file mode 100644 index 0000000..afbfa13 --- /dev/null +++ b/examples/scala-js/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/Spec.scala @@ -0,0 +1,9 @@ +package org.scalajs.jasmine + +import scala.scalajs.js + +trait Spec extends js.Object { + def results(): SpecResults = js.native + val description: String = js.native + val suite: Suite = js.native +} diff --git a/examples/scala-js/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/SpecResults.scala b/examples/scala-js/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/SpecResults.scala new file mode 100644 index 0000000..50f073c --- /dev/null +++ b/examples/scala-js/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/SpecResults.scala @@ -0,0 +1,8 @@ +package org.scalajs.jasmine + +import scala.scalajs.js + +trait SpecResults extends js.Object { + def passed(): Boolean = js.native + def getItems(): js.Array[Result] = js.native +} diff --git a/examples/scala-js/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/Suite.scala b/examples/scala-js/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/Suite.scala new file mode 100644 index 0000000..cd4fb75 --- /dev/null +++ b/examples/scala-js/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/Suite.scala @@ -0,0 +1,8 @@ +package org.scalajs.jasmine + +import scala.scalajs.js + +trait Suite extends js.Object { + def results(): SuiteResults = js.native + val description: String = js.native +} diff --git a/examples/scala-js/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/SuiteResults.scala b/examples/scala-js/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/SuiteResults.scala new file mode 100644 index 0000000..db98acf --- /dev/null +++ b/examples/scala-js/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/SuiteResults.scala @@ -0,0 +1,9 @@ +package org.scalajs.jasmine + +import scala.scalajs.js + +trait SuiteResults extends js.Object { + val passedCount: Int = js.native + val failedCount: Int = js.native + val totalCount: Int = js.native +} diff --git a/examples/scala-js/jasmine-test-framework/src/main/scala/org/scalajs/jasminetest/JasmineTest.scala b/examples/scala-js/jasmine-test-framework/src/main/scala/org/scalajs/jasminetest/JasmineTest.scala new file mode 100644 index 0000000..715d39d --- /dev/null +++ b/examples/scala-js/jasmine-test-framework/src/main/scala/org/scalajs/jasminetest/JasmineTest.scala @@ -0,0 +1,34 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Framework ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package org.scalajs.jasminetest + +import scala.scalajs.js +import scala.scalajs.testbridge._ + +import java.util.regex.Pattern +import org.scalajs.jasmine.Jasmine +import org.scalajs.jasmine.JasmineExpectation + +class JasmineTest extends Test with TestSuiteContext { + def jasmine = Jasmine.jasmine + def describe(name: String)(suite: => Unit): Unit = Jasmine.describe(name, suite _) + def it(title: String)(test: => Unit): Unit = Jasmine.it(title, test _) + def xdescribe(name: String)(suite: => Unit): Unit = Jasmine.xdescribe(name, suite _) + def xit(title: String)(test: => Unit): Unit = Jasmine.xit(title, test _) + def beforeEach(block: => Unit): Unit = Jasmine.beforeEach(block _) + def afterEach(block: => Unit): Unit = Jasmine.afterEach(block _) + def expect(exp: CharSequence): JasmineExpectation = + Jasmine.expect(if (exp == null) null else exp.toString) + def expect(exp: js.Any): JasmineExpectation = Jasmine.expect(exp) + def runs(block: => Unit): Unit = Jasmine.runs(block _) + def waits(timeout: Int): Unit = Jasmine.waits(timeout) + def waitsFor(block: => Boolean, errorMsg: String, timeout: Int): Unit = + Jasmine.waitsFor(block _, errorMsg, timeout) +} diff --git a/examples/scala-js/jasmine-test-framework/src/main/scala/org/scalajs/jasminetest/JasmineTestFramework.scala b/examples/scala-js/jasmine-test-framework/src/main/scala/org/scalajs/jasminetest/JasmineTestFramework.scala new file mode 100644 index 0000000..2686e31 --- /dev/null +++ b/examples/scala-js/jasmine-test-framework/src/main/scala/org/scalajs/jasminetest/JasmineTestFramework.scala @@ -0,0 +1,93 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Framework ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package org.scalajs.jasminetest + +import scala.scalajs.js +import scala.scalajs.js.Dynamic.global +import scala.scalajs.js.JavaScriptException +import scala.scalajs.js.annotation.JSExport + +import scala.scalajs.testbridge._ + +import scala.collection.mutable + +object JasmineTestFramework extends TestFramework { + createStackPropertyOnThrowable() + + private def createStackPropertyOnThrowable(): Unit = { + /* All Jasmine cares about when looking for stack trace data is a field + * `stack` on the error object. Our Throwables do not have a `stack` field + * because they are not subclasses of the JavaScript class Error. + * However, a genuine Error object with the proper (lazy) stack field is + * stored under the property stackdata by StackTrace. + * This code installs a property getter on Throwable that will redirect + * `throwable.stack` to `throwable.stackdata.stack` (when it exists). + */ + + val ThrowablePrototype = js.Object.getPrototypeOf( + (new Throwable).asInstanceOf[js.Object]).asInstanceOf[js.Object] + + js.Object.defineProperty(ThrowablePrototype, "stack", js.Dynamic.literal( + configurable = false, + enumerable = false, + get = { (self: js.Dynamic) => + self.stackdata && self.stackdata.stack + }: js.ThisFunction + ).asInstanceOf[js.PropertyDescriptor]) + } + + private val tags = mutable.Set.empty[String] + + def hasTag(tag: String): Boolean = tags.contains(tag) + + def runTest(testOutput: TestOutput, args: js.Array[String])( + test: js.Function0[Test]): Unit = { + + val jasmine = global.jasmine + val reporter = new JasmineTestReporter(testOutput) + + handleArgs(args, testOutput) + + try { + test() + + val jasmineEnv = jasmine.getEnv() + jasmineEnv.addReporter(reporter.asInstanceOf[js.Any]) + jasmineEnv.updateInterval = 0 + jasmineEnv.execute() + } catch { + case throwable@JavaScriptException(exception) => + testOutput.error("Problem executing code in tests: " + exception, + throwable.getStackTrace()) + } finally { + clearTags() + } + } + + /** Set tags used in tests. Only use when invoking with HTML reporter */ + @JSExport + def setTags(newTags: String*): Unit = { + clearTags() + tags ++= newTags + } + + /** Clear tags used in tests. Only use when invoking with HTML reporter */ + @JSExport + def clearTags(): Unit = tags.clear() + + private def handleArgs(args: js.Array[String], testOutput: TestOutput) = { + for (arg <- args) { + if (arg.startsWith("-t")) + tags += arg.stripPrefix("-t") + else + testOutput.log.warn(s"Jasmine: Discarding argument: $arg") + } + } +} diff --git a/examples/scala-js/jasmine-test-framework/src/main/scala/org/scalajs/jasminetest/JasmineTestReporter.scala b/examples/scala-js/jasmine-test-framework/src/main/scala/org/scalajs/jasminetest/JasmineTestReporter.scala new file mode 100644 index 0000000..79a7c75 --- /dev/null +++ b/examples/scala-js/jasmine-test-framework/src/main/scala/org/scalajs/jasminetest/JasmineTestReporter.scala @@ -0,0 +1,130 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Framework ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package org.scalajs.jasminetest + +import scala.scalajs.js +import scala.scalajs.js.annotation.JSExport + +import scala.scalajs.runtime.StackTrace + +import scala.scalajs.testbridge._ + +import org.scalajs.jasmine.ExpectationResult +import org.scalajs.jasmine.Result +import org.scalajs.jasmine.Spec +import org.scalajs.jasmine.Suite + +/** This class is passed to the actual jasmine framework as a reporter */ +class JasmineTestReporter(testOutput: TestOutput) { + private var currentSuite: Suite = _ + + @JSExport + def reportRunnerStarting(): Unit = { + testOutput.log.info("") + } + + @JSExport + def reportSpecStarting(spec: Spec): Unit = { + if (currentSuite != spec.suite) { + currentSuite = spec.suite + info(currentSuite.description) + } + } + + @JSExport + def reportSpecResults(spec: Spec): Unit = { + val results = spec.results() + val description = spec.description + + if (results.passed) { + testOutput.succeeded(s" $success $description") + } else { + error(s" $failure $description") + + results.getItems foreach displayResult + } + } + + @JSExport + def reportSuiteResults(suite: Suite): Unit = { + var results = suite.results() + + info("") + val title = "Total for suite " + suite.description + val message = + s"${results.totalCount} specs, ${results.failedCount} failure" + + if (results.passedCount != results.totalCount) { + error(title) + errorWithInfoColor(message) + } else { + info(title) + infoWithInfoColor(message) + } + info("") + } + + @JSExport + def reportRunnerResults(): Unit = { + // no need to report + } + + private def info(str: String) = + testOutput.log.info(str) + + private def infoWithInfoColor(str: String) = + info(withColor(testOutput.infoColor, str)) + + private def errorWithInfoColor(str: String) = + error(withColor(testOutput.infoColor, str)) + + private def error(msg: js.Any) = + testOutput.log.error(msg.toString) + + private def withColor(color: testOutput.Color, message: String) = + testOutput.color(message, color) + + private def sanitizeMessage(message: String) = { + val FilePattern = """^(.+?) [^ ]+\.js \(line \d+\)\.*?$""".r + val EvalPattern = """^(.+?) in eval.+\(eval\).+?\(line \d+\).*?$""".r + + message match { + case FilePattern(originalMessage) => originalMessage + case EvalPattern(originalMessage) => originalMessage + case message => message + } + } + + private def failure = withColor(testOutput.errorColor, "x") + private def success = withColor(testOutput.successColor, "+") + + private def displayResult(result: Result) = { + (result.`type`: String) match { + case "log" => + info(s" ${result.toString}") + case "expect" => + val r = result.asInstanceOf[ExpectationResult] + + if (!r.passed()) { + val message = sanitizeMessage(r.message) + val stack = StackTrace.extract(r.trace).takeWhile { stackElem => + (stackElem.getFileName == null || + !stackElem.getFileName.endsWith("jasmine.js")) + } + + if (stack.isEmpty) + testOutput.failure(s" $message") + else + testOutput.error(s" $message", stack) + } + } + } + +} diff --git a/examples/scala-js/jasmine-test-framework/src/main/scala/org/scalajs/jasminetest/TestSuiteContext.scala b/examples/scala-js/jasmine-test-framework/src/main/scala/org/scalajs/jasminetest/TestSuiteContext.scala new file mode 100644 index 0000000..827eefd --- /dev/null +++ b/examples/scala-js/jasmine-test-framework/src/main/scala/org/scalajs/jasminetest/TestSuiteContext.scala @@ -0,0 +1,55 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Framework ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package org.scalajs.jasminetest + +trait TestSuiteContext { + def describe(title: String)(test: => Unit): Unit + def it(title: String)(test: => Unit): Unit + def xdescribe(title: String)(test: => Unit): Unit + def xit(title: String)(test: => Unit): Unit + + def when(tag: String): TestSuiteContext = + if (JasmineTestFramework.hasTag(tag)) this + else new TestSuiteContext.IgnoredContext(this) + + def whenAll(tags: String*): TestSuiteContext = + if (tags.forall(JasmineTestFramework.hasTag)) this + else new TestSuiteContext.IgnoredContext(this) + + def whenAny(tags: String*): TestSuiteContext = + if (tags.exists(JasmineTestFramework.hasTag)) this + else new TestSuiteContext.IgnoredContext(this) + + def unless(tag: String): TestSuiteContext = + if (!JasmineTestFramework.hasTag(tag)) this + else new TestSuiteContext.IgnoredContext(this) + + def unlessAll(tags: String*): TestSuiteContext = + if (!tags.forall(JasmineTestFramework.hasTag)) this + else new TestSuiteContext.IgnoredContext(this) + + def unlessAny(tags: String*): TestSuiteContext = + if (!tags.exists(JasmineTestFramework.hasTag)) this + else new TestSuiteContext.IgnoredContext(this) +} + +object TestSuiteContext { + private class IgnoredContext( + baseContext: TestSuiteContext) extends TestSuiteContext { + def describe(title: String)(test: => Unit): Unit = + baseContext.xdescribe(title)(test) + def it(title: String)(test: => Unit): Unit = + baseContext.xit(title)(test) + def xdescribe(title: String)(test: => Unit): Unit = + baseContext.xdescribe(title)(test) + def xit(title: String)(test: => Unit): Unit = + baseContext.xit(title)(test) + } +} diff --git a/examples/scala-js/javalanglib/src/main/scala/java/lang/Appendable.scala b/examples/scala-js/javalanglib/src/main/scala/java/lang/Appendable.scala new file mode 100644 index 0000000..9cd74ad --- /dev/null +++ b/examples/scala-js/javalanglib/src/main/scala/java/lang/Appendable.scala @@ -0,0 +1,7 @@ +package java.lang + +trait Appendable { + def append(c: Char): Appendable + def append(csq: CharSequence): Appendable + def append(csq: CharSequence, start: Int, end: Int): Appendable +} diff --git a/examples/scala-js/javalanglib/src/main/scala/java/lang/AutoCloseable.scala b/examples/scala-js/javalanglib/src/main/scala/java/lang/AutoCloseable.scala new file mode 100644 index 0000000..21a3d0f --- /dev/null +++ b/examples/scala-js/javalanglib/src/main/scala/java/lang/AutoCloseable.scala @@ -0,0 +1,5 @@ +package java.lang + +trait AutoCloseable { + def close(): Unit +} diff --git a/examples/scala-js/javalanglib/src/main/scala/java/lang/Boolean.scala b/examples/scala-js/javalanglib/src/main/scala/java/lang/Boolean.scala new file mode 100644 index 0000000..94a9967 --- /dev/null +++ b/examples/scala-js/javalanglib/src/main/scala/java/lang/Boolean.scala @@ -0,0 +1,61 @@ +package java.lang + +import scala.scalajs.js + +/* This is a hijacked class. Its instances are primitive booleans. + * Constructors are not emitted. + */ +final class Boolean private () extends Comparable[Boolean] { + + def this(value: scala.Boolean) = this() + def this(v: String) = this() + + @inline def booleanValue(): scala.Boolean = + this.asInstanceOf[scala.Boolean] + + @inline override def equals(that: Any): scala.Boolean = + this eq that.asInstanceOf[AnyRef] + + @inline override def hashCode(): Int = + if (booleanValue) 1231 else 1237 + + @inline override def compareTo(that: Boolean): Int = + Boolean.compare(booleanValue, that.booleanValue) + + @inline override def toString(): String = + Boolean.toString(booleanValue) + +} + +object Boolean { + final val TYPE = classOf[scala.Boolean] + + /* TRUE and FALSE are supposed to be vals. However, they are better + * optimized as defs, because they end up being just the constant true and + * false (since `new Boolean(x)` is a no-op). + * Since vals and defs are binary-compatible (although they're not strictly + * speaking source-compatible, because of stability), we implement them as + * defs. Source-compatibility is not an issue because user code is compiled + * against the JDK .class files anyway. + * Moreover, preserving the identity of TRUE and FALSE is not an issue + * either, since they are primitive booleans in the end. + */ + def TRUE: Boolean = new Boolean(true) + def FALSE: Boolean = new Boolean(false) + + @inline def valueOf(booleanValue: scala.Boolean): Boolean = { + // We don't care about identity, since they end up as primitive booleans + new Boolean(booleanValue) + } + + @inline def valueOf(s: String): Boolean = valueOf(parseBoolean(s)) + + @inline def parseBoolean(s: String): scala.Boolean = + (s != null) && s.equalsIgnoreCase("true") + + @inline def toString(b: scala.Boolean): String = + "" + b + + @inline def compare(x: scala.Boolean, y: scala.Boolean): scala.Int = + if (x == y) 0 else if (x) 1 else -1 +} diff --git a/examples/scala-js/javalanglib/src/main/scala/java/lang/Byte.scala b/examples/scala-js/javalanglib/src/main/scala/java/lang/Byte.scala new file mode 100644 index 0000000..dc0c82f --- /dev/null +++ b/examples/scala-js/javalanglib/src/main/scala/java/lang/Byte.scala @@ -0,0 +1,71 @@ +package java.lang + +import scala.scalajs.js + +/* This is a hijacked class. Its instances are primitive numbers. + * Constructors are not emitted. + */ +final class Byte private () extends Number with Comparable[Byte] { + + def this(value: scala.Byte) = this() + def this(s: String) = this() + + @inline override def byteValue(): scala.Byte = + this.asInstanceOf[scala.Byte] + + @inline override def shortValue(): scala.Short = byteValue.toShort + @inline def intValue(): scala.Int = byteValue.toInt + @inline def longValue(): scala.Long = byteValue.toLong + @inline def floatValue(): scala.Float = byteValue.toFloat + @inline def doubleValue(): scala.Double = byteValue.toDouble + + @inline override def equals(that: Any): scala.Boolean = + this eq that.asInstanceOf[AnyRef] + + @inline override def hashCode(): Int = + byteValue + + @inline override def compareTo(that: Byte): Int = + Byte.compare(byteValue, that.byteValue) + + @inline override def toString(): String = + Byte.toString(byteValue) +} + +object Byte { + final val TYPE = classOf[scala.Byte] + final val SIZE = 8 + + /* MIN_VALUE and MAX_VALUE should be 'final val's. But it is impossible to + * write a proper Byte literal in Scala, that would both considered a Byte + * and a constant expression (optimized as final val). + * Since vals and defs are binary-compatible (although they're not strictly + * speaking source-compatible, because of stability), we implement them as + * defs. Source-compatibility is not an issue because user code is compiled + * against the JDK .class files anyway. + */ + def MIN_VALUE: scala.Byte = -128 + def MAX_VALUE: scala.Byte = 127 + + @inline def valueOf(byteValue: scala.Byte): Byte = new Byte(byteValue) + @inline def valueOf(s: String): Byte = valueOf(parseByte(s)) + + @inline def valueOf(s: String, radix: Int): Byte = + valueOf(parseByte(s, radix)) + + @inline def parseByte(s: String): scala.Byte = parseByte(s, 10) + + def parseByte(s: String, radix: Int): scala.Byte = { + val r = Integer.parseInt(s, radix) + if (r < MIN_VALUE || r > MAX_VALUE) + throw new NumberFormatException(s"""For input string: "$s"""") + else + r.toByte + } + + @inline def toString(b: scala.Byte): String = + "" + b + + @inline def compare(x: scala.Byte, y: scala.Byte): scala.Int = + x - y +} diff --git a/examples/scala-js/javalanglib/src/main/scala/java/lang/CharSequence.scala b/examples/scala-js/javalanglib/src/main/scala/java/lang/CharSequence.scala new file mode 100644 index 0000000..5875a2d --- /dev/null +++ b/examples/scala-js/javalanglib/src/main/scala/java/lang/CharSequence.scala @@ -0,0 +1,8 @@ +package java.lang + +trait CharSequence { + def length(): scala.Int + def charAt(index: scala.Int): scala.Char + def subSequence(start: scala.Int, end: scala.Int): CharSequence + def toString(): String +} diff --git a/examples/scala-js/javalanglib/src/main/scala/java/lang/Character.scala b/examples/scala-js/javalanglib/src/main/scala/java/lang/Character.scala new file mode 100644 index 0000000..1b2b565 --- /dev/null +++ b/examples/scala-js/javalanglib/src/main/scala/java/lang/Character.scala @@ -0,0 +1,289 @@ +package java.lang + +import scala.scalajs.js + +class Character(private val value: scala.Char) extends Comparable[Character] { + + def charValue(): scala.Char = value + + override def equals(that: Any) = + that.isInstanceOf[Character] && (value == that.asInstanceOf[Character].charValue) + + override def compareTo(that: Character): Int = + Character.compare(charValue, that.charValue) + + override def toString(): String = + Character.toString(value) + + override def hashCode(): Int = value.## + + /* + * Methods on scala.Char + * The following methods are only here to properly support reflective calls + * on boxed primitive values. YOU WILL NOT BE ABLE TO USE THESE METHODS, since + * we use the true javalib to lookup symbols, this file contains only + * implementations. + */ + protected def toByte: scala.Byte = value.toByte + protected def toShort: scala.Short = value.toShort + protected def toChar: scala.Char = value.toChar + protected def toInt: scala.Int = value + protected def toLong: scala.Long = value.toLong + protected def toFloat: scala.Float = value.toFloat + protected def toDouble: scala.Double = value.toDouble + + protected def unary_~ : scala.Int = ~value + protected def unary_+ : scala.Int = value + protected def unary_- : scala.Int = -value + + protected def +(x: String): String = value + x + + protected def <<(x: scala.Int): scala.Int = value << x + protected def <<(x: scala.Long): scala.Int = value << x + protected def >>>(x: scala.Int): scala.Int = value >>> x + protected def >>>(x: scala.Long): scala.Int = value >>> x + protected def >>(x: scala.Int): scala.Int = value >> x + protected def >>(x: scala.Long): scala.Int = value >> x + + protected def ==(x: scala.Byte): scala.Boolean = value == x + protected def ==(x: scala.Short): scala.Boolean = value == x + protected def ==(x: scala.Char): scala.Boolean = value == x + protected def ==(x: scala.Int): scala.Boolean = value == x + protected def ==(x: scala.Long): scala.Boolean = value == x + protected def ==(x: scala.Float): scala.Boolean = value == x + protected def ==(x: scala.Double): scala.Boolean = value == x + + protected def !=(x: scala.Byte): scala.Boolean = value != x + protected def !=(x: scala.Short): scala.Boolean = value != x + protected def !=(x: scala.Char): scala.Boolean = value != x + protected def !=(x: scala.Int): scala.Boolean = value != x + protected def !=(x: scala.Long): scala.Boolean = value != x + protected def !=(x: scala.Float): scala.Boolean = value != x + protected def !=(x: scala.Double): scala.Boolean = value != x + + protected def <(x: scala.Byte): scala.Boolean = value < x + protected def <(x: scala.Short): scala.Boolean = value < x + protected def <(x: scala.Char): scala.Boolean = value < x + protected def <(x: scala.Int): scala.Boolean = value < x + protected def <(x: scala.Long): scala.Boolean = value < x + protected def <(x: scala.Float): scala.Boolean = value < x + protected def <(x: scala.Double): scala.Boolean = value < x + + protected def <=(x: scala.Byte): scala.Boolean = value <= x + protected def <=(x: scala.Short): scala.Boolean = value <= x + protected def <=(x: scala.Char): scala.Boolean = value <= x + protected def <=(x: scala.Int): scala.Boolean = value <= x + protected def <=(x: scala.Long): scala.Boolean = value <= x + protected def <=(x: scala.Float): scala.Boolean = value <= x + protected def <=(x: scala.Double): scala.Boolean = value <= x + + protected def >(x: scala.Byte): scala.Boolean = value > x + protected def >(x: scala.Short): scala.Boolean = value > x + protected def >(x: scala.Char): scala.Boolean = value > x + protected def >(x: scala.Int): scala.Boolean = value > x + protected def >(x: scala.Long): scala.Boolean = value > x + protected def >(x: scala.Float): scala.Boolean = value > x + protected def >(x: scala.Double): scala.Boolean = value > x + + protected def >=(x: scala.Byte): scala.Boolean = value >= x + protected def >=(x: scala.Short): scala.Boolean = value >= x + protected def >=(x: scala.Char): scala.Boolean = value >= x + protected def >=(x: scala.Int): scala.Boolean = value >= x + protected def >=(x: scala.Long): scala.Boolean = value >= x + protected def >=(x: scala.Float): scala.Boolean = value >= x + protected def >=(x: scala.Double): scala.Boolean = value >= x + + protected def |(x: scala.Byte): scala.Int = value | x + protected def |(x: scala.Short): scala.Int = value | x + protected def |(x: scala.Char): scala.Int = value | x + protected def |(x: scala.Int): scala.Int = value | x + protected def |(x: scala.Long): scala.Long = value | x + + protected def &(x: scala.Byte): scala.Int = value & x + protected def &(x: scala.Short): scala.Int = value & x + protected def &(x: scala.Char): scala.Int = value & x + protected def &(x: scala.Int): scala.Int = value & x + protected def &(x: scala.Long): scala.Long = value & x + + protected def ^(x: scala.Byte): scala.Int = value ^ x + protected def ^(x: scala.Short): scala.Int = value ^ x + protected def ^(x: scala.Char): scala.Int = value ^ x + protected def ^(x: scala.Int): scala.Int = value ^ x + protected def ^(x: scala.Long): scala.Long = value ^ x + + protected def +(x: scala.Byte): scala.Int = value + x + protected def +(x: scala.Short): scala.Int = value + x + protected def +(x: scala.Char): scala.Int = value + x + protected def +(x: scala.Int): scala.Int = value + x + protected def +(x: scala.Long): scala.Long = value + x + protected def +(x: scala.Float): scala.Float = value + x + protected def +(x: scala.Double): scala.Double = value + x + + protected def -(x: scala.Byte): scala.Int = value - x + protected def -(x: scala.Short): scala.Int = value - x + protected def -(x: scala.Char): scala.Int = value - x + protected def -(x: scala.Int): scala.Int = value - x + protected def -(x: scala.Long): scala.Long = value - x + protected def -(x: scala.Float): scala.Float = value - x + protected def -(x: scala.Double): scala.Double = value - x + + protected def *(x: scala.Byte): scala.Int = value * x + protected def *(x: scala.Short): scala.Int = value * x + protected def *(x: scala.Char): scala.Int = value * x + protected def *(x: scala.Int): scala.Int = value * x + protected def *(x: scala.Long): scala.Long = value * x + protected def *(x: scala.Float): scala.Float = value * x + protected def *(x: scala.Double): scala.Double = value * x + + protected def /(x: scala.Byte): scala.Int = value / x + protected def /(x: scala.Short): scala.Int = value / x + protected def /(x: scala.Char): scala.Int = value / x + protected def /(x: scala.Int): scala.Int = value / x + protected def /(x: scala.Long): scala.Long = value / x + protected def /(x: scala.Float): scala.Float = value / x + protected def /(x: scala.Double): scala.Double = value / x + + protected def %(x: scala.Byte): scala.Int = value % x + protected def %(x: scala.Short): scala.Int = value % x + protected def %(x: scala.Char): scala.Int = value % x + protected def %(x: scala.Int): scala.Int = value % x + protected def %(x: scala.Long): scala.Long = value % x + protected def %(x: scala.Float): scala.Float = value % x + protected def %(x: scala.Double): scala.Double = value % x + +} + +object Character { + final val TYPE = classOf[scala.Char] + final val MIN_VALUE = '\u0000' + final val MAX_VALUE = '\uffff' + final val SIZE = 16 + + def valueOf(charValue: scala.Char) = new Character(charValue) + + /* These are supposed to be final vals of type Byte, but that's not possible. + * So we implement them as def's, which are binary compatible with final vals. + */ + def UPPERCASE_LETTER: scala.Byte = 1 + def LOWERCASE_LETTER: scala.Byte = 2 + def TITLECASE_LETTER: scala.Byte = 3 + def MODIFIER_LETTER: scala.Byte = 4 + def OTHER_LETTER: scala.Byte = 5 + def NON_SPACING_MARK: scala.Byte = 6 + def ENCLOSING_MARK: scala.Byte = 7 + def COMBINING_SPACING_MARK: scala.Byte = 8 + def DECIMAL_DIGIT_NUMBER: scala.Byte = 9 + def LETTER_NUMBER: scala.Byte = 10 + def SURROGATE: scala.Byte = 19 + + final val MIN_RADIX = 2 + final val MAX_RADIX = 36 + + final val MIN_HIGH_SURROGATE = '\uD800' + final val MAX_HIGH_SURROGATE = '\uDBFF' + final val MIN_LOW_SURROGATE = '\uDC00' + final val MAX_LOW_SURROGATE = '\uDFFF' + final val MIN_SURROGATE = MIN_HIGH_SURROGATE + final val MAX_SURROGATE = MAX_LOW_SURROGATE + + final val MIN_CODE_POINT = 0 + final val MAX_CODE_POINT = 0x10ffff + final val MIN_SUPPLEMENTARY_CODE_POINT = 0x10000 + + // Not implemented: + //def getType(ch: scala.Char): scala.Int + //def getType(codePoint: scala.Int): scala.Int + + def digit(c: scala.Char, radix: scala.Int): scala.Int = { + if (radix > MAX_RADIX || radix < MIN_RADIX) + -1 + else if (c >= '0' && c <= '9' && c - '0' < radix) + c - '0' + else if (c >= 'A' && c <= 'Z' && c - 'A' < radix - 10) + c - 'A' + 10 + else if (c >= 'a' && c <= 'z' && c - 'a' < radix - 10) + c - 'a' + 10 + else if (c >= '\uFF21' && c <= '\uFF3A' && + c - '\uFF21' < radix - 10) + c - '\uFF21' + 10 + else if (c >= '\uFF41' && c <= '\uFF5A' && + c - '\uFF41' < radix - 10) + c - '\uFF21' + 10 + else -1 + } + + def isISOControl(c: scala.Char): scala.Boolean = isISOControl(c.toInt) + def isISOControl(codePoint: scala.Int): scala.Boolean = { + (0x00 <= codePoint && codePoint <= 0x1F) || (0x7F <= codePoint && codePoint <= 0x9F) + } + + def isDigit(c: scala.Char): scala.Boolean = c >= '0' && c <= '9' + //def isLetter(c: scala.Char): scala.Boolean + //def isLetterOrDigit(c: scala.Char): scala.Boolean + def isWhitespace(c: scala.Char): scala.Boolean = js.RegExp("^\\s$").test(c.toString) + //def isSpaceChar(c: scala.Char): scala.Boolean + + // --- UTF-16 surrogate pairs handling --- + // See http://en.wikipedia.org/wiki/UTF-16 + + private final val HighSurrogateMask = 0xfc00 // 111111 00 00000000 + private final val HighSurrogateID = 0xd800 // 110110 00 00000000 + private final val LowSurrogateMask = 0xfc00 // 111111 00 00000000 + private final val LowSurrogateID = 0xdc00 // 110111 00 00000000 + private final val SurrogateUsefulPartMask = 0x03ff // 000000 11 11111111 + + @inline def isHighSurrogate(c: scala.Char): scala.Boolean = + (c & HighSurrogateMask) == HighSurrogateID + @inline def isLowSurrogate(c: scala.Char): scala.Boolean = + (c & LowSurrogateMask) == LowSurrogateID + @inline def isSurrogatePair(high: scala.Char, low: scala.Char): scala.Boolean = + isHighSurrogate(high) && isLowSurrogate(low) + + @inline def toCodePoint(high: scala.Char, low: scala.Char): scala.Int = + ((high & SurrogateUsefulPartMask) << 10) + (low & SurrogateUsefulPartMask) + 0x10000 + + // --- End of UTF-16 surrogate pairs handling --- + + def isUnicodeIdentifierStart(c: scala.Char): scala.Boolean = + reUnicodeIdentStart.test(c.toString) + + def isUnicodeIdentifierPart(c: scala.Char): scala.Boolean = + isUnicodeIdentifierStart(c) || isIdentifierIgnorable(c) || + reUnicodeIdentPartExcl.test(c.toString) + + def isIdentifierIgnorable(c: scala.Char): scala.Boolean = + reIdentIgnorable.test(c.toString) + + //def isMirrored(c: scala.Char): scala.Boolean + def isLowerCase(c: scala.Char): scala.Boolean = toLowerCase(c) == c + def isUpperCase(c: scala.Char): scala.Boolean = toUpperCase(c) == c + //def isTitleCase(c: scala.Char): scala.Boolean + //def isJavaIdentifierPart(c: scala.Char): scala.Boolean + + //def getDirectionality(c: scala.Char): scala.Byte + + /* Conversions */ + def toUpperCase(c: scala.Char): scala.Char = c.toString.toUpperCase()(0) + def toLowerCase(c: scala.Char): scala.Char = c.toString.toLowerCase()(0) + //def toTitleCase(c: scala.Char): scala.Char + //def getNumericValue(c: scala.Char): scala.Int + + /* Misc */ + //def reverseBytes(ch: scala.Char): scala.Char + + @inline def toString(c: scala.Char) = js.String.fromCharCode(c.toInt) + + @inline def compare(x: scala.Char, y: scala.Char): scala.Int = + x - y + + // Based on Unicode 7.0.0 + private[this] lazy val reUnicodeIdentStart = + new js.RegExp("""[A-Za-z\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0-\u08B2\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6EF\uA717-\uA71F\uA722-\uA788\uA78B-\uA78E\uA790-\uA7AD\uA7B0\uA7B1\uA7F7-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uA9E0-\uA9E4\uA9E6-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB5F\uAB64\uAB65\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]""") + + private[this] lazy val reUnicodeIdentPartExcl = + new js.RegExp("""[0-9\x5F\u0300-\u036F\u0483-\u0487\u0591-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7\u0610-\u061A\u064B-\u0669\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7\u06E8\u06EA-\u06ED\u06F0-\u06F9\u0711\u0730-\u074A\u07A6-\u07B0\u07C0-\u07C9\u07EB-\u07F3\u0816-\u0819\u081B-\u0823\u0825-\u0827\u0829-\u082D\u0859-\u085B\u08E4-\u0903\u093A-\u093C\u093E-\u094F\u0951-\u0957\u0962\u0963\u0966-\u096F\u0981-\u0983\u09BC\u09BE-\u09C4\u09C7\u09C8\u09CB-\u09CD\u09D7\u09E2\u09E3\u09E6-\u09EF\u0A01-\u0A03\u0A3C\u0A3E-\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A66-\u0A71\u0A75\u0A81-\u0A83\u0ABC\u0ABE-\u0AC5\u0AC7-\u0AC9\u0ACB-\u0ACD\u0AE2\u0AE3\u0AE6-\u0AEF\u0B01-\u0B03\u0B3C\u0B3E-\u0B44\u0B47\u0B48\u0B4B-\u0B4D\u0B56\u0B57\u0B62\u0B63\u0B66-\u0B6F\u0B82\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD7\u0BE6-\u0BEF\u0C00-\u0C03\u0C3E-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C62\u0C63\u0C66-\u0C6F\u0C81-\u0C83\u0CBC\u0CBE-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5\u0CD6\u0CE2\u0CE3\u0CE6-\u0CEF\u0D01-\u0D03\u0D3E-\u0D44\u0D46-\u0D48\u0D4A-\u0D4D\u0D57\u0D62\u0D63\u0D66-\u0D6F\u0D82\u0D83\u0DCA\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DE6-\u0DEF\u0DF2\u0DF3\u0E31\u0E34-\u0E3A\u0E47-\u0E4E\u0E50-\u0E59\u0EB1\u0EB4-\u0EB9\u0EBB\u0EBC\u0EC8-\u0ECD\u0ED0-\u0ED9\u0F18\u0F19\u0F20-\u0F29\u0F35\u0F37\u0F39\u0F3E\u0F3F\u0F71-\u0F84\u0F86\u0F87\u0F8D-\u0F97\u0F99-\u0FBC\u0FC6\u102B-\u103E\u1040-\u1049\u1056-\u1059\u105E-\u1060\u1062-\u1064\u1067-\u106D\u1071-\u1074\u1082-\u108D\u108F-\u109D\u135D-\u135F\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17B4-\u17D3\u17DD\u17E0-\u17E9\u180B-\u180D\u1810-\u1819\u18A9\u1920-\u192B\u1930-\u193B\u1946-\u194F\u19B0-\u19C0\u19C8\u19C9\u19D0-\u19D9\u1A17-\u1A1B\u1A55-\u1A5E\u1A60-\u1A7C\u1A7F-\u1A89\u1A90-\u1A99\u1AB0-\u1ABD\u1B00-\u1B04\u1B34-\u1B44\u1B50-\u1B59\u1B6B-\u1B73\u1B80-\u1B82\u1BA1-\u1BAD\u1BB0-\u1BB9\u1BE6-\u1BF3\u1C24-\u1C37\u1C40-\u1C49\u1C50-\u1C59\u1CD0-\u1CD2\u1CD4-\u1CE8\u1CED\u1CF2-\u1CF4\u1CF8\u1CF9\u1DC0-\u1DF5\u1DFC-\u1DFF\u203F\u2040\u2054\u20D0-\u20DC\u20E1\u20E5-\u20F0\u2CEF-\u2CF1\u2D7F\u2DE0-\u2DFF\u302A-\u302F\u3099\u309A\uA620-\uA629\uA66F\uA674-\uA67D\uA69F\uA6F0\uA6F1\uA802\uA806\uA80B\uA823-\uA827\uA880\uA881\uA8B4-\uA8C4\uA8D0-\uA8D9\uA8E0-\uA8F1\uA900-\uA909\uA926-\uA92D\uA947-\uA953\uA980-\uA983\uA9B3-\uA9C0\uA9D0-\uA9D9\uA9E5\uA9F0-\uA9F9\uAA29-\uAA36\uAA43\uAA4C\uAA4D\uAA50-\uAA59\uAA7B-\uAA7D\uAAB0\uAAB2-\uAAB4\uAAB7\uAAB8\uAABE\uAABF\uAAC1\uAAEB-\uAAEF\uAAF5\uAAF6\uABE3-\uABEA\uABEC\uABED\uABF0-\uABF9\uFB1E\uFE00-\uFE0F\uFE20-\uFE2D\uFE33\uFE34\uFE4D-\uFE4F\uFF10-\uFF19\uFF3F]""") + + private[this] lazy val reIdentIgnorable = + new js.RegExp("""[\0-\x08\x0E-\x1B\x7F-\x9F\xAD\u0600-\u0605\u061C\u06DD\u070F\u180E\u200B-\u200F\u202A-\u202E\u2060-\u2064\u2066-\u206F\uFEFF\uFFF9-\uFFFB]""") + +} diff --git a/examples/scala-js/javalanglib/src/main/scala/java/lang/Class.scala b/examples/scala-js/javalanglib/src/main/scala/java/lang/Class.scala new file mode 100644 index 0000000..e8ff46f --- /dev/null +++ b/examples/scala-js/javalanglib/src/main/scala/java/lang/Class.scala @@ -0,0 +1,83 @@ +package java.lang + +import scala.scalajs.js + +private trait ScalaJSClassData[A] extends js.Object { + val name: String = js.native + val isPrimitive: scala.Boolean = js.native + val isInterface: scala.Boolean = js.native + val isArrayClass: scala.Boolean = js.native + + def isInstance(obj: Object): scala.Boolean = js.native + def getFakeInstance(): Object = js.native + + def getSuperclass(): Class[_ >: A] = js.native + def getComponentType(): Class[_] = js.native + + def newArrayOfThisClass(dimensions: js.Array[Int]): AnyRef = js.native +} + +final class Class[A] private (data: ScalaJSClassData[A]) extends Object { + + override def toString(): String = { + (if (isInterface()) "interface " else + if (isPrimitive()) "" else "class ")+getName() + } + + def isInstance(obj: Object): scala.Boolean = + data.isInstance(obj) + + def isAssignableFrom(that: Class[_]): scala.Boolean = + if (this.isPrimitive || that.isPrimitive) { + /* This differs from the JVM specification to mimic the behavior of + * runtime type tests of primitive numeric types. + */ + (this eq that) || { + if (this eq classOf[scala.Short]) + (that eq classOf[scala.Byte]) + else if (this eq classOf[scala.Int]) + (that eq classOf[scala.Byte]) || (that eq classOf[scala.Short]) + else if (this eq classOf[scala.Float]) + (that eq classOf[scala.Byte]) || (that eq classOf[scala.Short]) || + (that eq classOf[scala.Int]) + else if (this eq classOf[scala.Double]) + (that eq classOf[scala.Byte]) || (that eq classOf[scala.Short]) || + (that eq classOf[scala.Int]) || (that eq classOf[scala.Float]) + else + false + } + } else { + this.isInstance(that.getFakeInstance()) + } + + private def getFakeInstance(): Object = + data.getFakeInstance() + + def isInterface(): scala.Boolean = + data.isInterface + + def isArray(): scala.Boolean = + data.isArrayClass + + def isPrimitive(): scala.Boolean = + data.isPrimitive + + def getName(): String = + data.name + + def getSimpleName(): String = + data.name.split('.').last.split('$').last + + def getSuperclass(): Class[_ >: A] = + data.getSuperclass() + + def getComponentType(): Class[_] = + data.getComponentType() + + def getEnclosingClass(): Class[_] = null + + // java.lang.reflect.Array support + + private[lang] def newArrayOfThisClass(dimensions: js.Array[Int]): AnyRef = + data.newArrayOfThisClass(dimensions) +} diff --git a/examples/scala-js/javalanglib/src/main/scala/java/lang/Cloneable.scala b/examples/scala-js/javalanglib/src/main/scala/java/lang/Cloneable.scala new file mode 100644 index 0000000..4183bf5 --- /dev/null +++ b/examples/scala-js/javalanglib/src/main/scala/java/lang/Cloneable.scala @@ -0,0 +1,3 @@ +package java.lang + +trait Cloneable diff --git a/examples/scala-js/javalanglib/src/main/scala/java/lang/Comparable.scala b/examples/scala-js/javalanglib/src/main/scala/java/lang/Comparable.scala new file mode 100644 index 0000000..8d17c6f --- /dev/null +++ b/examples/scala-js/javalanglib/src/main/scala/java/lang/Comparable.scala @@ -0,0 +1,5 @@ +package java.lang + +trait Comparable[A] { + def compareTo(o: A): scala.Int +} diff --git a/examples/scala-js/javalanglib/src/main/scala/java/lang/Double.scala b/examples/scala-js/javalanglib/src/main/scala/java/lang/Double.scala new file mode 100644 index 0000000..25987ac --- /dev/null +++ b/examples/scala-js/javalanglib/src/main/scala/java/lang/Double.scala @@ -0,0 +1,110 @@ +package java.lang + +import scala.scalajs.js + +/* This is a hijacked class. Its instances are primitive numbers. + * Constructors are not emitted. + */ +final class Double private () extends Number with Comparable[Double] { + + def this(value: scala.Double) = this() + def this(s: String) = this() + + @inline def doubleValue(): scala.Double = + this.asInstanceOf[scala.Double] + + @inline override def byteValue(): scala.Byte = doubleValue.toByte + @inline override def shortValue(): scala.Short = doubleValue.toShort + @inline def intValue(): scala.Int = doubleValue.toInt + @inline def longValue(): scala.Long = doubleValue.toLong + @inline def floatValue(): scala.Float = doubleValue.toFloat + + override def equals(that: Any): scala.Boolean = that match { + case that: Double => + val a = doubleValue + val b = that.doubleValue + (a == b) || (Double.isNaN(a) && Double.isNaN(b)) + case _ => + false + } + + @inline override def hashCode(): Int = + scala.scalajs.runtime.Bits.numberHashCode(doubleValue) + + @inline override def compareTo(that: Double): Int = + Double.compare(doubleValue, that.doubleValue) + + @inline override def toString(): String = + Double.toString(doubleValue) + + @inline def isNaN(): scala.Boolean = + Double.isNaN(doubleValue) + + @inline def isInfinite(): scala.Boolean = + Double.isInfinite(doubleValue) + +} + +object Double { + final val TYPE = classOf[scala.Double] + final val POSITIVE_INFINITY = 1.0 / 0.0 + final val NEGATIVE_INFINITY = 1.0 / -0.0 + final val NaN = 0.0 / 0.0 + final val MAX_VALUE = scala.Double.MaxValue + final val MIN_VALUE = scala.Double.MinPositiveValue + final val MAX_EXPONENT = 1023 + final val MIN_EXPONENT = -1022 + final val SIZE = 64 + + @inline def valueOf(doubleValue: scala.Double): Double = + new Double(doubleValue) + + @inline def valueOf(s: String): Double = valueOf(parseDouble(s)) + + private[this] lazy val doubleStrPat = new js.RegExp("^" + + "[\\x00-\\x20]*" + // optional whitespace + "[+-]?" + // optional sign + "(NaN|Infinity|" + // special cases + "(\\d+\\.?\\d*|" + // literal w/ leading digit + "\\.\\d+)" + // literal w/o leading digit + "([eE][+-]?\\d+)?"+ // optional exponent + ")[fFdD]?" + // optional float / double specifier (ignored) + "[\\x00-\\x20]*" + // optional whitespace + "$") + + def parseDouble(s: String): scala.Double = { + if (doubleStrPat.test(s)) + js.parseFloat(s) + else + throw new NumberFormatException(s"""For input string: "$s"""") + } + + @inline def toString(d: scala.Double): String = + "" + d + + def compare(a: scala.Double, b: scala.Double): scala.Int = { + // NaN must equal itself, and be greater than anything else + if (isNaN(a)) { + if (isNaN(b)) 0 + else 1 + } else if (isNaN(b)) { + -1 + } else { + if (a == b) 0 + else if (a < b) -1 + else 1 + } + } + + @inline def isNaN(v: scala.Double): scala.Boolean = + v != v + + @inline def isInfinite(v: scala.Double): scala.Boolean = + v == POSITIVE_INFINITY || v == NEGATIVE_INFINITY + + @inline def longBitsToDouble(bits: scala.Long): scala.Double = + scala.scalajs.runtime.Bits.longBitsToDouble(bits) + + @inline def doubleToLongBits(value: scala.Double): scala.Long = + scala.scalajs.runtime.Bits.doubleToLongBits(value) +} diff --git a/examples/scala-js/javalanglib/src/main/scala/java/lang/Float.scala b/examples/scala-js/javalanglib/src/main/scala/java/lang/Float.scala new file mode 100644 index 0000000..70cb33e --- /dev/null +++ b/examples/scala-js/javalanglib/src/main/scala/java/lang/Float.scala @@ -0,0 +1,96 @@ +package java.lang + +/* This is a hijacked class. Its instances are primitive numbers. + * Constructors are not emitted. + */ +final class Float private () extends Number with Comparable[Float] { + + def this(value: scala.Float) = this() + def this(s: String) = this() + + @inline def floatValue(): scala.Float = + this.asInstanceOf[scala.Float] + + @inline override def byteValue(): scala.Byte = floatValue.toByte + @inline override def shortValue(): scala.Short = floatValue.toShort + @inline def intValue(): scala.Int = floatValue.toInt + @inline def longValue(): scala.Long = floatValue.toLong + @inline def doubleValue(): scala.Double = floatValue.toDouble + + override def equals(that: Any): scala.Boolean = that match { + case that: Double => // yes, Double + val a = doubleValue + val b = that.doubleValue + (a == b) || (Double.isNaN(a) && Double.isNaN(b)) + case _ => + false + } + + // Uses the hashCode of Doubles. See Bits.numberHashCode for the rationale. + @inline override def hashCode(): Int = + scala.scalajs.runtime.Bits.numberHashCode(doubleValue) + + @inline override def compareTo(that: Float): Int = + Float.compare(floatValue, that.floatValue) + + @inline override def toString(): String = + Float.toString(floatValue) + + @inline def isNaN(): scala.Boolean = + Float.isNaN(floatValue) + + @inline def isInfinite(): scala.Boolean = + Float.isInfinite(floatValue) + +} + +object Float { + final val TYPE = classOf[scala.Float] + final val POSITIVE_INFINITY = 1.0f / 0.0f + final val NEGATIVE_INFINITY = 1.0f / -0.0f + final val NaN = 0.0f / 0.0f + final val MAX_VALUE = scala.Float.MaxValue + final val MIN_VALUE = scala.Float.MinPositiveValue + final val MAX_EXPONENT = 127 + final val MIN_EXPONENT = -126 + final val SIZE = 32 + + @inline def valueOf(floatValue: scala.Float): Float = new Float(floatValue) + + @inline def valueOf(s: String): Float = valueOf(parseFloat(s)) + + @inline def parseFloat(s: String): scala.Float = + Double.parseDouble(s).toFloat + + @inline def toString(f: scala.Float): String = + "" + f + + def compare(a: scala.Float, b: scala.Float): scala.Int = { + // NaN must equal itself, and be greater than anything else + if (isNaN(a)) { + if (isNaN(b)) 0 + else 1 + } else if (isNaN(b)) { + -1 + } else { + if (a == b) 0 + else if (a < b) -1 + else 1 + } + } + + @inline protected def equals(a: scala.Float, b: scala.Float): scala.Boolean = + a == b || (isNaN(a) && isNaN(b)) + + @inline def isNaN(v: scala.Float): scala.Boolean = + v != v + + @inline def isInfinite(v: scala.Float): scala.Boolean = + v == POSITIVE_INFINITY || v == NEGATIVE_INFINITY + + @inline def intBitsToFloat(bits: scala.Int): scala.Float = + scala.scalajs.runtime.Bits.intBitsToFloat(bits) + + @inline def floatToIntBits(value: scala.Float): scala.Int = + scala.scalajs.runtime.Bits.floatToIntBits(value) +} diff --git a/examples/scala-js/javalanglib/src/main/scala/java/lang/InheritableThreadLocal.scala b/examples/scala-js/javalanglib/src/main/scala/java/lang/InheritableThreadLocal.scala new file mode 100644 index 0000000..92ef07c --- /dev/null +++ b/examples/scala-js/javalanglib/src/main/scala/java/lang/InheritableThreadLocal.scala @@ -0,0 +1,5 @@ +package java.lang + +class InheritableThreadLocal[T] extends ThreadLocal[T] { + protected def childValue(parentValue: T): T = parentValue +} diff --git a/examples/scala-js/javalanglib/src/main/scala/java/lang/Integer.scala b/examples/scala-js/javalanglib/src/main/scala/java/lang/Integer.scala new file mode 100644 index 0000000..a002fb7 --- /dev/null +++ b/examples/scala-js/javalanglib/src/main/scala/java/lang/Integer.scala @@ -0,0 +1,129 @@ +package java.lang + +import scala.scalajs.js + +/* This is a hijacked class. Its instances are primitive numbers. + * Constructors are not emitted. + */ +final class Integer private () extends Number with Comparable[Integer] { + + def this(value: scala.Int) = this() + def this(s: String) = this() + + @inline def intValue(): scala.Int = + this.asInstanceOf[scala.Int] + + @inline override def byteValue(): scala.Byte = intValue.toByte + @inline override def shortValue(): scala.Short = intValue.toShort + @inline def longValue(): scala.Long = intValue.toLong + @inline def floatValue(): scala.Float = intValue.toFloat + @inline def doubleValue(): scala.Double = intValue.toDouble + + @inline override def equals(that: Any): scala.Boolean = + this eq that.asInstanceOf[AnyRef] + + @inline override def hashCode(): Int = + intValue + + @inline override def compareTo(that: Integer): Int = + Integer.compare(intValue, that.intValue) + + @inline override def toString(): String = + Integer.toString(intValue) + +} + +object Integer { + final val TYPE = classOf[scala.Int] + final val MIN_VALUE = -2147483648 + final val MAX_VALUE = 2147483647 + final val SIZE = 32 + + @inline def valueOf(intValue: scala.Int): Integer = new Integer(intValue) + @inline def valueOf(s: String): Integer = valueOf(parseInt(s)) + + @inline def valueOf(s: String, radix: Int): Integer = + valueOf(parseInt(s, radix)) + + @inline def parseInt(s: String): scala.Int = parseInt(s, 10) + + def parseInt(s: String, radix: scala.Int): scala.Int = { + def fail = throw new NumberFormatException(s"""For input string: "$s"""") + + if (s == null || s.size == 0 || + radix < Character.MIN_RADIX || + radix > Character.MAX_RADIX) + fail + else { + var i = if (s(0) == '-' || s(0) == '+') 1 else 0 + // JavaDoc says: We need at least one digit + if (s.size <= i) fail + else { + // Check each character for validity + while (i < s.size) { + if (Character.digit(s(i), radix) < 0) fail + i += 1 + } + val res = js.parseInt(s, radix) + + if (js.isNaN(res) || res > MAX_VALUE || res < MIN_VALUE) + fail + else + res.toInt + } + } + } + + @inline def toString(i: scala.Int): String = + "" + i + + @inline def compare(x: scala.Int, y: scala.Int): scala.Int = + if (x == y) 0 else if (x < y) -1 else 1 + + def bitCount(i: scala.Int): scala.Int = { + // See http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel + // The implicit casts to 32-bit ints due to binary ops make this work in JS too + val t1 = i - ((i >> 1) & 0x55555555) + val t2 = (t1 & 0x33333333) + ((t1 >> 2) & 0x33333333) + ((t2 + (t2 >> 4) & 0xF0F0F0F) * 0x1010101) >> 24 + } + + def reverseBytes(i: scala.Int): scala.Int = { + val byte3 = i >>> 24 + val byte2 = (i >>> 8) & 0xFF00 + val byte1 = (i << 8) & 0xFF0000 + val byte0 = (i << 24) + byte0 | byte1 | byte2 | byte3 + } + + def rotateLeft(i: scala.Int, distance: scala.Int): scala.Int = + (i << distance) | (i >>> -distance) + + def rotateRight(i: scala.Int, distance: scala.Int): scala.Int = + (i >>> distance) | (i << -distance) + + @inline def signum(i: scala.Int): scala.Int = + if (i == 0) 0 else if (i < 0) -1 else 1 + + def numberOfLeadingZeros(i: scala.Int): scala.Int = { + // See http://aggregate.org/MAGIC/#Leading%20Zero%20Count + var x = i + x |= (x >>> 1) + x |= (x >>> 2) + x |= (x >>> 4) + x |= (x >>> 8) + x |= (x >>> 16) + 32 - bitCount(x) + } + + def numberOfTrailingZeros(i: scala.Int): scala.Int = + // See http://aggregate.org/MAGIC/#Trailing%20Zero%20Count + bitCount((i & -i) - 1) + + def toBinaryString(i: scala.Int): String = toStringBase(i, 2) + def toHexString(i: scala.Int): String = toStringBase(i, 16) + def toOctalString(i: scala.Int): String = toStringBase(i, 8) + + @inline private[this] def toStringBase(i: scala.Int, base: scala.Int): String = + ((i: js.prim.Number) >>> 0).toString(base) +} diff --git a/examples/scala-js/javalanglib/src/main/scala/java/lang/Long.scala b/examples/scala-js/javalanglib/src/main/scala/java/lang/Long.scala new file mode 100644 index 0000000..beeef32 --- /dev/null +++ b/examples/scala-js/javalanglib/src/main/scala/java/lang/Long.scala @@ -0,0 +1,196 @@ +package java.lang + +import scala.annotation.tailrec + +import scala.scalajs.js + +/* This is a hijacked class. Its instances are the representation of scala.Longs. + * Constructors are not emitted. + */ +final class Long private () extends Number with Comparable[Long] { + def this(value: scala.Long) = this() + def this(s: String) = this() + + @inline def longValue(): scala.Long = + this.asInstanceOf[scala.Long] + + @inline override def byteValue(): scala.Byte = longValue.toByte + @inline override def shortValue(): scala.Short = longValue.toShort + @inline def intValue(): scala.Int = longValue.toInt + @inline def floatValue(): scala.Float = longValue.toFloat + @inline def doubleValue(): scala.Double = longValue.toDouble + + @inline override def equals(that: Any): scala.Boolean = that match { + case that: Long => longValue == that.longValue + case _ => false + } + + @inline override def hashCode(): Int = + (longValue ^ (longValue >>> 32)).toInt + + @inline override def compareTo(that: Long): Int = + Long.compare(longValue, that.longValue) + + @inline override def toString(): String = + Long.toString(longValue) + +} + +object Long { + import scala.scalajs.runtime.RuntimeLong + + final val TYPE = classOf[scala.Long] + final val MIN_VALUE = -9223372036854775808L + final val MAX_VALUE = 9223372036854775807L + final val SIZE = 64 + + @inline def valueOf(longValue: scala.Long): Long = new Long(longValue) + @inline def valueOf(s: String): Long = valueOf(parseLong(s)) + + @inline def valueOf(s: String, radix: Int): Long = + valueOf(parseLong(s, radix)) + + @inline def parseLong(s: String): scala.Long = + parseLong(s, 10) + + def parseLong(s: String, radix: Int): scala.Long = { + def fail() = throw new NumberFormatException(s"""For input string: "$s"""") + + if (s.isEmpty) { + fail() + } else if (s.charAt(0) == '-') { + -parseLong(s.substring(1), radix) + } else { + @inline + @tailrec + def fastPow(base: Int, exp: Int, acc: Int = 1): Int = + if (exp == 0) acc + else if (exp % 2 == 0) fastPow(base*base, exp/2, acc) + else fastPow(base, exp-1, acc*base) + + @inline + @tailrec + def loop(str0: String, acc: scala.Long): scala.Long = if (str0.length > 0) { + val MaxLen = 9 + val cur = (str0: js.prim.String).substring(0, MaxLen): String + val macc = acc * fastPow(radix, cur.length) + val ival = js.parseInt(cur, radix): scala.Double + if (ival.isNaN) + fail() + val cval = ival.toInt.toLong // faster than ival.toLong + loop((str0: js.prim.String).substring(MaxLen), macc + cval) + } else acc + + loop(s, 0L) + } + } + + def toString(l: scala.Long): String = { + if (l == 0L) "0" + // Check for MinValue, because it is not negatable + else if (l == MIN_VALUE) "-9223372036854775808" + else if (l < 0L) "-" + toString(-l) + else { + @tailrec + @inline + def toString0(v: scala.Long, acc: String): String = { + val quot = v / 1000000000L // 9 zeros + val rem = v % 1000000000L + + val digits = rem.toInt.toString + + if (quot == 0L) { + digits + acc + } else { + val padding = "000000000".substring(digits.length) // (9 - digits.length) zeros + toString0(quot, padding + digits + acc) + } + } + + toString0(l, "") + } + } + + @inline def compare(x: scala.Long, y: scala.Long): scala.Int = + if (x == y) 0 else if (x < y) -1 else 1 + + def bitCount(i: scala.Long): scala.Int = { + val lo = i.toInt + val hi = (i >>> 32).toInt + Integer.bitCount(lo) + Integer.bitCount(hi) + } + + def reverseBytes(i: scala.Long): scala.Long = { + val hiReversed = Integer.reverseBytes((i >>> 32).toInt) + val loReversed = Integer.reverseBytes(i.toInt) + (loReversed.toLong << 32) | (hiReversed.toLong & 0xffffffffL) + } + + def rotateLeft(i: scala.Long, distance: scala.Int): scala.Long = + (i << distance) | (i >>> -distance) + + def rotateRight(i: scala.Long, distance: scala.Int): scala.Long = + (i >>> distance) | (i << -distance) + + def signum(i: scala.Long): scala.Long = + if (i < 0L) -1L else if (i == 0L) 0L else 1L + + def numberOfLeadingZeros(l: scala.Long): Int = { + val hi = (l >>> 32).toInt + if (hi != 0) Integer.numberOfLeadingZeros(hi) + else Integer.numberOfLeadingZeros(l.toInt) + 32 + } + + def numberOfTrailingZeros(l: scala.Long): Int = { + val lo = l.toInt + if (lo != 0) Integer.numberOfTrailingZeros(lo) + else Integer.numberOfTrailingZeros((l >>> 32).toInt) + 32 + } + + def toBinaryString(l: scala.Long): String = { + val zeros = "00000000000000000000000000000000" // 32 zeros + @inline def padBinary32(i: Int) = { + val s = Integer.toBinaryString(i) + zeros.substring(s.length) + s + } + + val lo = l.toInt + val hi = (l >>> 32).toInt + + if (hi != 0) Integer.toBinaryString(hi) + padBinary32(lo) + else Integer.toBinaryString(lo) + } + + def toHexString(l: scala.Long): String = { + val zeros = "00000000" // 8 zeros + @inline def padBinary8(i: Int) = { + val s = Integer.toHexString(i) + zeros.substring(s.length) + s + } + + val lo = l.toInt + val hi = (l >>> 32).toInt + + if (hi != 0) Integer.toHexString(hi) + padBinary8(lo) + else Integer.toHexString(lo) + } + + def toOctalString(l: scala.Long): String = { + val zeros = "0000000000" // 10 zeros + @inline def padOctal10(i: Int) = { + val s = Integer.toOctalString(i) + zeros.substring(s.length) + s + } + + val lo = l.toInt + val hi = (l >>> 32).toInt + + val lp = lo & 0x3fffffff + val mp = ((lo >>> 30) + (hi << 2)) & 0x3fffffff + val hp = hi >>> 28 + + if (hp != 0) Integer.toOctalString(hp) + padOctal10(mp) + padOctal10(lp) + else if (mp != 0) Integer.toOctalString(mp) + padOctal10(lp) + else Integer.toOctalString(lp) + } +} diff --git a/examples/scala-js/javalanglib/src/main/scala/java/lang/Math.scala b/examples/scala-js/javalanglib/src/main/scala/java/lang/Math.scala new file mode 100644 index 0000000..c8cd7aa --- /dev/null +++ b/examples/scala-js/javalanglib/src/main/scala/java/lang/Math.scala @@ -0,0 +1,211 @@ +package java +package lang + +import scala.scalajs.js + +object Math { + private lazy val internalRandom = new java.util.Random() + + final val E = 2.718281828459045 + final val PI = 3.141592653589793 + + @inline def abs(a: scala.Int): scala.Int = if (a < 0) -a else a + @inline def abs(a: scala.Long): scala.Long = if (a < 0) -a else a + @inline def abs(a: scala.Float): scala.Float = if (a < 0) -a else a + @inline def abs(a: scala.Double): scala.Double = if (a < 0) -a else a + + @inline def max(a: scala.Int, b: scala.Int): scala.Int = if (a > b) a else b + @inline def max(a: scala.Long, b: scala.Long): scala.Long = if (a > b) a else b + @inline def max(a: scala.Float, b: scala.Float): scala.Float = if (a > b) a else b + @inline def max(a: scala.Double, b: scala.Double): scala.Double = if (a > b) a else b + + @inline def min(a: scala.Int, b: scala.Int): scala.Int = if (a < b) a else b + @inline def min(a: scala.Long, b: scala.Long): scala.Long = if (a < b) a else b + @inline def min(a: scala.Float, b: scala.Float): scala.Float = if (a < b) a else b + @inline def min(a: scala.Double, b: scala.Double): scala.Double = if (a < b) a else b + + @inline def ceil(a: scala.Double): scala.Double = js.Math.ceil(a) + @inline def floor(a: scala.Double): scala.Double = js.Math.floor(a) + + @inline def round(a: scala.Float): scala.Int = js.Math.round(a).toInt + @inline def round(a: scala.Double): scala.Long = js.Math.round(a).toLong + + @inline def sqrt(a: scala.Double): scala.Double = js.Math.sqrt(a) + @inline def pow(a: scala.Double, b: scala.Double): scala.Double = js.Math.pow(a, b) + + @inline def exp(a: scala.Double): scala.Double = js.Math.exp(a) + @inline def log(a: scala.Double): scala.Double = js.Math.log(a) + @inline def log10(a: scala.Double): scala.Double = log(a) / 2.302585092994046 + @inline def log1p(a: scala.Double): scala.Double = log(a + 1) + + @inline def sin(a: scala.Double): scala.Double = js.Math.sin(a) + @inline def cos(a: scala.Double): scala.Double = js.Math.cos(a) + @inline def tan(a: scala.Double): scala.Double = js.Math.tan(a) + @inline def asin(a: scala.Double): scala.Double = js.Math.asin(a) + @inline def acos(a: scala.Double): scala.Double = js.Math.acos(a) + @inline def atan(a: scala.Double): scala.Double = js.Math.atan(a) + @inline def atan2(y: scala.Double, x: scala.Double): scala.Double = js.Math.atan2(y, x) + + def random(): scala.Double = internalRandom.nextDouble() + + @inline def toDegrees(a: scala.Double): scala.Double = a * 180.0 / PI + @inline def toRadians(a: scala.Double): scala.Double = a / 180.0 * PI + + @inline def signum(a: scala.Double): scala.Double = { + if (a > 0) 1.0 + else if (a < 0) -1.0 + else a + } + + @inline def signum(a: scala.Float): scala.Float = { + if (a > 0) 1.0f + else if (a < 0) -1.0f + else a + } + + def cbrt(a: scala.Double): scala.Double = { + if (a == 0 || a.isNaN) + return a + + val sign = if (a < 0.0) -1.0 else 1.0 + val value = sign * a + + //Initial Approximation + var x = 0.0 + var xi = pow(value, 0.3333333333333333) + + //Halley's Method (http://metamerist.com/cbrt/cbrt.htm) + while (abs(x - xi) >= 1E-16) { + x = xi + val x3 = js.Math.pow(x, 3) + val x3Plusa = x3 + value + xi = x * (x3Plusa + value) / (x3Plusa + x3) + } + return sign * xi + } + + def nextUp(a: scala.Double): scala.Double = { + // js implementation of nextUp https://gist.github.com/Yaffle/4654250 + import scala.Double._ + if (a != a || a == PositiveInfinity) + a + else if (a == NegativeInfinity) + -MaxValue + else if (a == MaxValue) + PositiveInfinity + else if (a == 0) + MinValue + else { + def iter(x: scala.Double, xi: scala.Double, n: scala.Double): scala.Double = { + if (Math.abs(xi - x) >= 1E-16) { + val c0 = (xi + x) / 2 + val c = + if (c0 == NegativeInfinity || c0 == PositiveInfinity) + x + (xi - x) / 2 + else + c0 + if (n == c) xi + else if (a < c) iter(x = x, xi = c, n = c) + else iter(x = c, xi = xi, n = c) + } + else xi + } + val d = Math.max(Math.abs(a) * 2E-16, MinValue) + val ad = a + d + val xi0 = + if (ad == PositiveInfinity) MaxValue + else ad + iter(x = a, xi = xi0, n = a) + } + } + + def nextAfter(a: scala.Double, b: scala.Double): scala.Double = { + if (b < a) + -nextUp(-a) + else if (a < b) + nextUp(a) + else if (a != a || b != b) + scala.Double.NaN + else + b + } + + def ulp(a: scala.Double): scala.Double = { + if (abs(a) == scala.Double.PositiveInfinity) + scala.Double.PositiveInfinity + else if (abs(a) == scala.Double.MaxValue) + pow(2, 971) + else + nextAfter(abs(a), scala.Double.MaxValue) - a + } + + def hypot(a: scala.Double, b: scala.Double): scala.Double = { + // http://en.wikipedia.org/wiki/Hypot#Implementation + if (abs(a) == scala.Double.PositiveInfinity || abs(b) == scala.Double.PositiveInfinity) + scala.Double.PositiveInfinity + else if (a.isNaN || b.isNaN) + scala.Double.NaN + else if (a == 0 && b == 0) + 0.0 + else { + //To Avoid Overflow and UnderFlow + // calculate |x| * sqrt(1 - (y/x)^2) instead of sqrt(x^2 + y^2) + val x = abs(a) + val y = abs(b) + val m = max(x, y) + val t = min(x, y) / m + m * sqrt(1 + t * t) + } + } + + def expm1(a: scala.Double): scala.Double = { + // https://github.com/ghewgill/picomath/blob/master/javascript/expm1.js + if (a == 0 || a.isNaN) + a + // Power Series http://en.wikipedia.org/wiki/Power_series + // for small values of a, exp(a) = 1 + a + (a*a)/2 + else if (abs(a) < 1E-5) + a + 0.5 * a * a + else + exp(a) - 1.0 + } + + def sinh(a: scala.Double): scala.Double = { + if (a.isNaN || a == 0.0 || abs(a) == scala.Double.PositiveInfinity) + a + else + (exp(a) - exp(-a)) / 2.0 + } + + def cosh(a: scala.Double): scala.Double = { + if (a.isNaN) + a + else if (a == 0.0) + 1.0 + else if (abs(a) == scala.Double.PositiveInfinity) + scala.Double.PositiveInfinity + else + (exp(a) + exp(-a)) / 2.0 + } + + def tanh(a: scala.Double): scala.Double = { + if (a.isNaN || a == 0.0) + a + else if (abs(a) == scala.Double.PositiveInfinity) + signum(a) + else { + // sinh(a) / cosh(a) = + // 1 - 2 * (exp(-a)/ (exp(-a) + exp (a))) + val expma = exp(-a) + if (expma == scala.Double.PositiveInfinity) //Infinity / Infinity + -1.0 + else { + val expa = exp(a) + val ret = expma / (expa + expma) + 1.0 - (2.0 * ret) + } + } + } + + // TODO The methods not available in the JavaScript Math object +} diff --git a/examples/scala-js/javalanglib/src/main/scala/java/lang/Number.scala b/examples/scala-js/javalanglib/src/main/scala/java/lang/Number.scala new file mode 100644 index 0000000..05ffc7a --- /dev/null +++ b/examples/scala-js/javalanglib/src/main/scala/java/lang/Number.scala @@ -0,0 +1,12 @@ +package java.lang + +import scala.scalajs.js + +abstract class Number extends Object { + def byteValue(): scala.Byte = intValue.toByte + def shortValue(): scala.Short = intValue.toShort + def intValue(): scala.Int + def longValue(): scala.Long + def floatValue(): scala.Float + def doubleValue(): scala.Double +} diff --git a/examples/scala-js/javalanglib/src/main/scala/java/lang/Readable.scala b/examples/scala-js/javalanglib/src/main/scala/java/lang/Readable.scala new file mode 100644 index 0000000..53e5689 --- /dev/null +++ b/examples/scala-js/javalanglib/src/main/scala/java/lang/Readable.scala @@ -0,0 +1,7 @@ +package java.lang + +import java.nio.CharBuffer + +trait Readable { + def read(cb: CharBuffer): Int +} diff --git a/examples/scala-js/javalanglib/src/main/scala/java/lang/Runnable.scala b/examples/scala-js/javalanglib/src/main/scala/java/lang/Runnable.scala new file mode 100644 index 0000000..c98cb41 --- /dev/null +++ b/examples/scala-js/javalanglib/src/main/scala/java/lang/Runnable.scala @@ -0,0 +1,5 @@ +package java.lang + +trait Runnable { + def run(): Unit +} diff --git a/examples/scala-js/javalanglib/src/main/scala/java/lang/Runtime.scala b/examples/scala-js/javalanglib/src/main/scala/java/lang/Runtime.scala new file mode 100644 index 0000000..25aaa9f --- /dev/null +++ b/examples/scala-js/javalanglib/src/main/scala/java/lang/Runtime.scala @@ -0,0 +1,47 @@ +package java.lang + +import scala.scalajs.js + +class Runtime private { + def exit(status: Int): Unit = + halt(status) + + //def addShutdownHook(hook: Thread): Unit + //def removeShutdownHook(hook: Thread): Unit + + def halt(status: Int): Unit = { + val envInfo = scala.scalajs.runtime.environmentInfo + + if (js.typeOf(envInfo.exitFunction) == "function") { + envInfo.exitFunction(status) + throw new IllegalStateException("__ScalaJSEnv.exitFunction returned") + } else { + // We don't have an exit function. Fail + throw new SecurityException("Cannot terminate a JavaScript program. " + + "Define a JavaScript function `__ScalaJSEnv.exitFunction` to " + + "be called on exit.") + } + } + + def availableProcessors(): Int = 1 + //def freeMemory(): scala.Long + //def totalMemory(): scala.Long + //def maxMemory(): scala.Long + + def gc(): Unit = { + // Ignore + } + + //def runFinalization(): Unit + //def traceInstructions(on: scala.Boolean): Unit + //def traceMethodCalls(on: scala.Boolean): Unit + + //def load(filename: String): Unit + //def loadLibrary(filename: String): Unit +} + +object Runtime { + private val currentRuntime = new Runtime + + def getRuntime() = currentRuntime +} diff --git a/examples/scala-js/javalanglib/src/main/scala/java/lang/Short.scala b/examples/scala-js/javalanglib/src/main/scala/java/lang/Short.scala new file mode 100644 index 0000000..135fe12 --- /dev/null +++ b/examples/scala-js/javalanglib/src/main/scala/java/lang/Short.scala @@ -0,0 +1,73 @@ +package java.lang + +/* This is a hijacked class. Its instances are primitive numbers. + * Constructors are not emitted. + */ +final class Short private () extends Number with Comparable[Short] { + + def this(value: scala.Short) = this() + def this(s: String) = this() + + @inline override def shortValue(): scala.Short = + this.asInstanceOf[scala.Short] + + @inline override def byteValue(): scala.Byte = shortValue.toByte + @inline def intValue(): scala.Int = shortValue.toInt + @inline def longValue(): scala.Long = shortValue.toLong + @inline def floatValue(): scala.Float = shortValue.toFloat + @inline def doubleValue(): scala.Double = shortValue.toDouble + + @inline override def equals(that: Any): scala.Boolean = + this eq that.asInstanceOf[AnyRef] + + @inline override def hashCode(): Int = + shortValue + + @inline override def compareTo(that: Short): Int = + Short.compare(shortValue, that.shortValue) + + @inline override def toString(): String = + Short.toString(shortValue) + +} + +object Short { + final val TYPE = classOf[scala.Short] + final val SIZE = 16 + + /* MIN_VALUE and MAX_VALUE should be 'final val's. But it is impossible to + * write a proper Short literal in Scala, that would both considered a Short + * and a constant expression (optimized as final val). + * Since vals and defs are binary-compatible (although they're not strictly + * speaking source-compatible, because of stability), we implement them as + * defs. Source-compatibility is not an issue because user code is compiled + * against the JDK .class files anyway. + */ + def MIN_VALUE: scala.Short = -32768 + def MAX_VALUE: scala.Short = 32767 + + @inline def valueOf(shortValue: scala.Short): Short = new Short(shortValue) + @inline def valueOf(s: String): Short = valueOf(parseShort(s)) + + @inline def valueOf(s: String, radix: Int): Short = + valueOf(parseShort(s, radix)) + + @inline def parseShort(s: String): scala.Short = parseShort(s, 10) + + def parseShort(s: String, radix: Int): scala.Short = { + val r = Integer.parseInt(s, radix) + if (r < MIN_VALUE || r > MAX_VALUE) + throw new NumberFormatException(s"""For input string: "$s"""") + else + r.toShort + } + + @inline def toString(s: scala.Short): String = + "" + s + + @inline def compare(x: scala.Short, y: scala.Short): scala.Int = + x - y + + def reverseBytes(i: scala.Short): scala.Short = + (((i >>> 8) & 0xff) + ((i & 0xff) << 8)).toShort +} diff --git a/examples/scala-js/javalanglib/src/main/scala/java/lang/StackTraceElement.scala b/examples/scala-js/javalanglib/src/main/scala/java/lang/StackTraceElement.scala new file mode 100644 index 0000000..cc87aec --- /dev/null +++ b/examples/scala-js/javalanglib/src/main/scala/java/lang/StackTraceElement.scala @@ -0,0 +1,55 @@ +package java.lang + +import scala.scalajs.js + +final class StackTraceElement(declaringClass: String, methodName: String, + fileName: String, lineNumber: Int) extends AnyRef with java.io.Serializable { + + def getFileName(): String = fileName + def getLineNumber(): Int = lineNumber + def getClassName(): String = declaringClass + def getMethodName(): String = methodName + def isNativeMethod(): scala.Boolean = false + + override def equals(that: Any): scala.Boolean = that match { + case that: StackTraceElement => + (getFileName == that.getFileName) && + (getLineNumber == that.getLineNumber) && + (getClassName == that.getClassName) && + (getMethodName == that.getMethodName) + case _ => + false + } + + override def toString(): String = { + var result = "" + if (declaringClass != "<jscode>") + result += declaringClass + "." + result += methodName + if (fileName eq null) { + if (isNativeMethod) + result += "(Native Method)" + else + result += "(Unknown Source)" + } else { + result += s"($fileName" + if (lineNumber >= 0) { + result += s":$lineNumber" + if (columnNumber >= 0) + result += s":$columnNumber" + } + result += ")" + } + result + } + + override def hashCode(): Int = { + declaringClass.hashCode() ^ methodName.hashCode() + } + + private def columnNumber: Int = { + val rawNum = this.asInstanceOf[js.Dynamic].columnNumber + if (!(!rawNum)) rawNum.asInstanceOf[Int] + else -1 + } +} diff --git a/examples/scala-js/javalanglib/src/main/scala/java/lang/StringBuffer.scala b/examples/scala-js/javalanglib/src/main/scala/java/lang/StringBuffer.scala new file mode 100644 index 0000000..31ee89a --- /dev/null +++ b/examples/scala-js/javalanglib/src/main/scala/java/lang/StringBuffer.scala @@ -0,0 +1,159 @@ +package java.lang + +class StringBuffer(private var content: String) extends CharSequence + with Appendable + with java.io.Serializable { + def this() = this("") + def this(initialCapacity: Int) = this("") + def this(csq: CharSequence) = this(csq.toString) + + def append(s: String): StringBuffer = { + content += { if (s == null) "null" else s } + this + } + + def append(b: scala.Boolean): StringBuffer = append(b.toString()) + def append(c: scala.Char): StringBuffer = append(c.toString()) + + def append(str: Array[scala.Char]): StringBuffer = + append(str, 0, str.length) + + def append(str: Array[scala.Char], offset: Int, len: Int): StringBuffer = { + var i = 0 + while (i < len) { + content += str(i + offset) + i += 1 + } + this + } + + def append(b: scala.Byte): StringBuffer = append(b.toString()) + def append(s: scala.Short): StringBuffer = append(s.toString()) + def append(i: scala.Int): StringBuffer = append(i.toString()) + def append(lng: scala.Long): StringBuffer = append(lng.toString()) + def append(f: scala.Float): StringBuffer = append(f.toString()) + def append(d: scala.Double): StringBuffer = append(d.toString()) + + def append(obj: AnyRef): StringBuffer = { + if (obj == null) append(null: String) + else append(obj.toString()) + } + + def append(csq: CharSequence): StringBuffer = append(csq: AnyRef) + def append(csq: CharSequence, start: Int, end: Int): StringBuffer = { + if (csq == null) append("null", start, end) + else append(csq.subSequence(start, end).toString()) + } + + override def toString() = content + + def length() = content.length() + + def charAt(index: Int) = content.charAt(index) + def codePointAt(index: Int) = content.codePointAt(index) + + def indexOf(str: String) = content.indexOf(str) + def indexOf(str: String, fromIndex: Int) = content.indexOf(str, fromIndex) + + def lastIndexOf(str: String) = content.lastIndexOf(str) + def lastIndexOf(str: String, fromIndex: Int) = content.lastIndexOf(str, fromIndex) + + def subSequence(start: Int, end: Int): CharSequence = substring(start, end) + def substring(start: Int): String = content.substring(start) + def substring(start: Int, end: Int): String = content.substring(start, end) + + def reverse(): StringBuffer = { + content = new StringBuilder(content).reverse().toString() + this + } + + def deleteCharAt(index: Int): StringBuffer = { + if (index < 0 || index >= content.length) + throw new StringIndexOutOfBoundsException("String index out of range: " + index) + content = content.substring(0, index) + content.substring(index+1) + this + } + + /** + * @param start The beginning index, inclusive. + * @param end The ending index, exclusive. + * @param str String that will replace previous contents. + * @return This StringBuilder. + */ + def replace(start: Int, end: Int, str: String): StringBuffer = { + val length = content.length + if (start < 0 || start > end || start >= length) + throw new StringIndexOutOfBoundsException(s"Illegal to replace substring at [$start - $end] in string of length $length") + val realEnd = if (end > length) length else end // java api convention + content = content.substring(0, start) + str + content.substring(realEnd) + this + } + + def setCharAt(index: Int, ch: scala.Char): Unit = { + if (index < 0 || index >= content.length) + throw new IndexOutOfBoundsException("String index out of range: " + index) + content = content.substring(0, index) + ch + content.substring(index + 1) + } + + def setLength(newLength: Int): Unit = { + if (newLength < 0) + throw new IndexOutOfBoundsException("String index out of range: " + newLength) + + val len = length() + if (len == newLength) { + } else if (len < newLength) { + var index = len + while (index < newLength) { + append("\u0000") + index += 1 + } + } else { + content = substring(0, newLength) + } + } + + def insert(index: Int, b: scala.Boolean): StringBuffer = insert(index, b.toString) + def insert(index: Int, b: scala.Byte): StringBuffer = insert(index, b.toString) + def insert(index: Int, s: scala.Short): StringBuffer = insert(index, s.toString) + def insert(index: Int, i: scala.Int): StringBuffer = insert(index, i.toString) + def insert(index: Int, l: scala.Long): StringBuffer = insert(index, l.toString) + def insert(index: Int, f: scala.Float): StringBuffer = insert(index, f.toString) + def insert(index: Int, d: scala.Double): StringBuffer = insert(index, d.toString) + def insert(index: Int, c: scala.Char): StringBuffer = insert(index, c.toString) + def insert(index: Int, csq: CharSequence): StringBuffer = insert(index: Int, csq: AnyRef) + def insert(index: Int, arr: Array[scala.Char]): StringBuffer = insert(index, arr, 0, arr.length) + + def insert(index: Int, ref: AnyRef): StringBuffer = + if (ref == null) + insert(index, null: String) + else + insert(index, ref.toString) + + def insert(index: Int, csq: CharSequence, start: Int, end: Int): StringBuffer = + if (csq == null) + insert(index, "null", start, end) + else + insert(index, csq.subSequence(start, end).toString) + + + def insert(index: Int, arr: Array[scala.Char], offset: Int, len: Int): StringBuffer = { + var str = "" + var i = 0 + while (i < len) { + str += arr(i + offset) + i += 1 + } + insert(index, str) + } + + def insert(index: Int, str: String): StringBuffer = { + val thisLength = length() + if (index < 0 || index > thisLength) + throw new StringIndexOutOfBoundsException(index) + else if (index == thisLength) + append(str) + else + content = content.substring(0, index) + Option(str).getOrElse("null") + content.substring(index) + this + } +} diff --git a/examples/scala-js/javalanglib/src/main/scala/java/lang/StringBuilder.scala b/examples/scala-js/javalanglib/src/main/scala/java/lang/StringBuilder.scala new file mode 100644 index 0000000..e8bd2b7 --- /dev/null +++ b/examples/scala-js/javalanglib/src/main/scala/java/lang/StringBuilder.scala @@ -0,0 +1,178 @@ +package java.lang + +class StringBuilder(private var content: String) extends CharSequence + with Appendable + with java.io.Serializable { + def this() = this("") + def this(initialCapacity: Int) = this("") + def this(csq: CharSequence) = this(csq.toString) + + def append(s: String): StringBuilder = { + content += { if (s == null) "null" else s } + this + } + + def append(b: scala.Boolean): StringBuilder = append(b.toString()) + def append(c: scala.Char): StringBuilder = append(c.toString()) + + def append(str: Array[scala.Char]): StringBuilder = + append(str, 0, str.length) + + def append(str: Array[scala.Char], offset: Int, len: Int): StringBuilder = { + var i = 0 + while (i < len) { + content += str(i + offset) + i += 1 + } + this + } + + def append(b: scala.Byte): StringBuilder = append(b.toString()) + def append(s: scala.Short): StringBuilder = append(s.toString()) + def append(i: scala.Int): StringBuilder = append(i.toString()) + def append(lng: scala.Long): StringBuilder = append(lng.toString()) + def append(f: scala.Float): StringBuilder = append(f.toString()) + def append(d: scala.Double): StringBuilder = append(d.toString()) + + def append(obj: AnyRef): StringBuilder = { + if (obj == null) append(null: String) + else append(obj.toString()) + } + + def append(csq: CharSequence): StringBuilder = append(csq: AnyRef) + def append(csq: CharSequence, start: Int, end: Int): StringBuilder = { + if (csq == null) append("null", start, end) + else append(csq.subSequence(start, end).toString()) + } + + override def toString() = content + + def length() = content.length() + + def charAt(index: Int) = content.charAt(index) + def codePointAt(index: Int) = content.codePointAt(index) + + def indexOf(str: String) = content.indexOf(str) + def indexOf(str: String, fromIndex: Int) = content.indexOf(str, fromIndex) + + def lastIndexOf(str: String) = content.lastIndexOf(str) + def lastIndexOf(str: String, fromIndex: Int) = content.lastIndexOf(str, fromIndex) + + def subSequence(start: Int, end: Int): CharSequence = substring(start, end) + def substring(start: Int): String = content.substring(start) + def substring(start: Int, end: Int): String = content.substring(start, end) + + def reverse(): StringBuilder = { + val original = content + var result = "" + var i = 0 + while (i < original.length) { + val c = original.charAt(i) + if (Character.isHighSurrogate(c) && (i+1 < original.length)) { + val c2 = original.charAt(i+1) + if (Character.isLowSurrogate(c2)) { + result = c.toString + c2.toString + result + i += 2 + } else { + result = c.toString + result + i += 1 + } + } else { + result = c.toString + result + i += 1 + } + } + content = result + this + } + + def deleteCharAt(index: Int): StringBuilder = { + if (index < 0 || index >= content.length) + throw new StringIndexOutOfBoundsException("String index out of range: " + index) + content = content.substring(0, index) + content.substring(index+1) + this + } + + /** + * @param start The beginning index, inclusive. + * @param end The ending index, exclusive. + * @param str String that will replace previous contents. + * @return This StringBuilder. + */ + def replace(start: Int, end: Int, str: String): StringBuilder = { + val length = content.length + if (start < 0 || start > end || start >= length) + throw new StringIndexOutOfBoundsException(s"Illegal to replace substring at [$start - $end] in string of length $length") + val realEnd = if (end > length) length else end // java api convention + content = content.substring(0, start) + str + content.substring(realEnd) + this + } + + def setCharAt(index: Int, ch: scala.Char): Unit = { + if (index < 0 || index >= content.length) + throw new IndexOutOfBoundsException("String index out of range: " + index) + content = content.substring(0, index) + ch + content.substring(index + 1) + } + + def setLength(newLength: Int): Unit = { + if (newLength < 0) + throw new IndexOutOfBoundsException("String index out of range: " + newLength) + + val len = length() + if (len == newLength) { + } else if (len < newLength) { + var index = len + while (index < newLength) { + append("\u0000") + index += 1 + } + } else { + content = substring(0, newLength) + } + } + + def insert(index: Int, b: scala.Boolean): StringBuilder = insert(index, b.toString) + def insert(index: Int, b: scala.Byte): StringBuilder = insert(index, b.toString) + def insert(index: Int, s: scala.Short): StringBuilder = insert(index, s.toString) + def insert(index: Int, i: scala.Int): StringBuilder = insert(index, i.toString) + def insert(index: Int, l: scala.Long): StringBuilder = insert(index, l.toString) + def insert(index: Int, f: scala.Float): StringBuilder = insert(index, f.toString) + def insert(index: Int, d: scala.Double): StringBuilder = insert(index, d.toString) + def insert(index: Int, c: scala.Char): StringBuilder = insert(index, c.toString) + def insert(index: Int, csq: CharSequence): StringBuilder = insert(index: Int, csq: AnyRef) + def insert(index: Int, arr: Array[scala.Char]): StringBuilder = insert(index, arr, 0, arr.length) + + def insert(index: Int, ref: AnyRef): StringBuilder = + if (ref == null) + insert(index, null: String) + else + insert(index, ref.toString) + + def insert(index: Int, csq: CharSequence, start: Int, end: Int): StringBuilder = + if (csq == null) + insert(index, "null", start, end) + else + insert(index, csq.subSequence(start, end).toString) + + + def insert(index: Int, arr: Array[scala.Char], offset: Int, len: Int): StringBuilder = { + var str = "" + var i = 0 + while (i < len) { + str += arr(i + offset) + i += 1 + } + insert(index, str) + } + + def insert(index: Int, str: String): StringBuilder = { + val thisLength = length() + if (index < 0 || index > thisLength) + throw new StringIndexOutOfBoundsException(index) + else if (index == thisLength) + append(str) + else + content = content.substring(0, index) + Option(str).getOrElse("null") + content.substring(index) + this + } +} diff --git a/examples/scala-js/javalanglib/src/main/scala/java/lang/System.scala b/examples/scala-js/javalanglib/src/main/scala/java/lang/System.scala new file mode 100644 index 0000000..6d80eaf --- /dev/null +++ b/examples/scala-js/javalanglib/src/main/scala/java/lang/System.scala @@ -0,0 +1,275 @@ +package java.lang + +import java.io._ + +import scala.scalajs.js +import js.Dynamic.global + +object System { + var out: PrintStream = new JSConsoleBasedPrintStream(isErr = false) + var err: PrintStream = new JSConsoleBasedPrintStream(isErr = true) + var in: InputStream = null + + def currentTimeMillis(): scala.Long = { + (new js.Date).getTime().toLong + } + + private[this] val getHighPrecisionTime: js.Function0[scala.Double] = { + if (!(!global.performance)) { + if (!(!global.performance.now)) { + () => global.performance.now().asInstanceOf[scala.Double] + } else if (!(!(global.performance.webkitNow))) { + () => global.performance.webkitNow().asInstanceOf[scala.Double] + } else { + () => new js.Date().getTime() + } + } else { + () => new js.Date().getTime() + } + } + + def nanoTime(): scala.Long = + (getHighPrecisionTime() * 1000000).toLong + + def arraycopy(src: Object, srcPos: scala.Int, dest: Object, + destPos: scala.Int, length: scala.Int): Unit = { + + import scala.{Boolean, Char, Byte, Short, Int, Long, Float, Double} + + @inline def checkIndices(srcLen: Int, destLen: Int): Unit = { + if (srcPos < 0 || destPos < 0 || length < 0 || + srcPos + length > srcLen || destPos + length > destLen) + throw new ArrayIndexOutOfBoundsException("Array index out of bounds") + } + + def mismatch(): Nothing = + throw new ArrayStoreException("Incompatible array types") + + val forward = (src ne dest) || destPos < srcPos || srcPos + length < destPos + + def copyPrim[@specialized T](src: Array[T], dest: Array[T]): Unit = { + checkIndices(src.length, dest.length) + if (forward) { + var i = 0 + while (i < length) { + dest(i+destPos) = src(i+srcPos) + i += 1 + } + } else { + var i = length-1 + while (i >= 0) { + dest(i+destPos) = src(i+srcPos) + i -= 1 + } + } + } + + def copyRef(src: Array[AnyRef], dest: Array[AnyRef]): Unit = { + checkIndices(src.length, dest.length) + if (forward) { + var i = 0 + while (i < length) { + dest(i+destPos) = src(i+srcPos) + i += 1 + } + } else { + var i = length-1 + while (i >= 0) { + dest(i+destPos) = src(i+srcPos) + i -= 1 + } + } + } + + if (src == null || dest == null) { + throw new NullPointerException() + } else (src match { + case src: Array[AnyRef] => + dest match { + case dest: Array[AnyRef] => copyRef(src, dest) + case _ => mismatch() + } + case src: Array[Boolean] => + dest match { + case dest: Array[Boolean] => copyPrim(src, dest) + case _ => mismatch() + } + case src: Array[Char] => + dest match { + case dest: Array[Char] => copyPrim(src, dest) + case _ => mismatch() + } + case src: Array[Byte] => + dest match { + case dest: Array[Byte] => copyPrim(src, dest) + case _ => mismatch() + } + case src: Array[Short] => + dest match { + case dest: Array[Short] => copyPrim(src, dest) + case _ => mismatch() + } + case src: Array[Int] => + dest match { + case dest: Array[Int] => copyPrim(src, dest) + case _ => mismatch() + } + case src: Array[Long] => + dest match { + case dest: Array[Long] => copyPrim(src, dest) + case _ => mismatch() + } + case src: Array[Float] => + dest match { + case dest: Array[Float] => copyPrim(src, dest) + case _ => mismatch() + } + case src: Array[Double] => + dest match { + case dest: Array[Double] => copyPrim(src, dest) + case _ => mismatch() + } + case _ => + mismatch() + }) + } + + def identityHashCode(x: Object): scala.Int = { + import js.prim + x match { + case null => 0 + case _:prim.Boolean | _:prim.Number | _:prim.String | _:prim.Undefined => + x.hashCode() + case _ => + if (x.getClass == null) { + // This is not a Scala.js object + 42 + } else { + val hash = x.asInstanceOf[js.Dynamic].selectDynamic("$idHashCode$0") + if (!js.isUndefined(hash)) { + hash.asInstanceOf[Int] + } else { + val newHash = IDHashCode.nextIDHashCode() + x.asInstanceOf[js.Dynamic].updateDynamic("$idHashCode$0")(newHash) + newHash + } + } + } + } + + private object IDHashCode { + private var lastIDHashCode: Int = 0 + + def nextIDHashCode(): Int = { + val r = lastIDHashCode + 1 + lastIDHashCode = r + r + } + } + + //def getProperties(): java.util.Properties + //def getProperty(key: String): String + //def getProperty(key: String, default: String): String + //def clearProperty(key: String): String + //def setProperty(key: String, value: String): String + + //def getenv(): java.util.Map[String,String] + //def getenv(name: String): String + + def exit(status: scala.Int) = Runtime.getRuntime().exit(status) + def gc() = Runtime.getRuntime().gc() +} + +private[lang] final class JSConsoleBasedPrintStream(isErr: Boolean) + extends PrintStream(new JSConsoleBasedPrintStream.DummyOutputStream) { + + import JSConsoleBasedPrintStream._ + + /** Whether the buffer is flushed. + * This can be true even if buffer != "" because of line continuations. + * However, the converse is never true, i.e., !flushed => buffer != "". + */ + private var flushed: scala.Boolean = true + private var buffer: String = "" + + override def write(b: Int): Unit = + write(Array(b.toByte), 0, 1) + + override def write(buf: Array[scala.Byte], off: Int, len: Int): Unit = { + /* This does *not* decode buf as a sequence of UTF-8 code units. + * This is not really useful, and would uselessly pull in the UTF-8 decoder + * in all applications that use OutputStreams (not just PrintStreams). + * Instead, we use a trivial ISO-8859-1 decoder in here. + */ + if (off < 0 || len < 0 || len > buf.length - off) + throw new IndexOutOfBoundsException + + var i = 0 + while (i < len) { + print((buf(i + off) & 0xff).toChar) + i += 1 + } + } + + override def print(b: scala.Boolean): Unit = printString(String.valueOf(b)) + override def print(c: scala.Char): Unit = printString(String.valueOf(c)) + override def print(i: scala.Int): Unit = printString(String.valueOf(i)) + override def print(l: scala.Long): Unit = printString(String.valueOf(l)) + override def print(f: scala.Float): Unit = printString(String.valueOf(f)) + override def print(d: scala.Double): Unit = printString(String.valueOf(d)) + override def print(s: Array[scala.Char]): Unit = printString(String.valueOf(s)) + override def print(s: String): Unit = printString(if (s == null) "null" else s) + override def print(obj: AnyRef): Unit = printString(String.valueOf(obj)) + + override def println(): Unit = printString("\n") + + private def printString(s: String): Unit = { + var rest: String = s + while (rest != "") { + val nlPos = rest.indexOf("\n") + if (nlPos < 0) { + buffer += rest + flushed = false + rest = "" + } else { + doWriteLine(buffer + rest.substring(0, nlPos)) + buffer = "" + flushed = true + rest = rest.substring(nlPos+1) + } + } + } + + /** + * Since we cannot write a partial line in JavaScript, we write a whole + * line with continuation symbol at the end and schedule a line continuation + * symbol for the new line if the buffer is flushed. + */ + override def flush(): Unit = if (!flushed) { + doWriteLine(buffer + LineContEnd) + buffer = LineContStart + flushed = true + } + + override def close(): Unit = () + + private def doWriteLine(line: String): Unit = { + if (!(!global.console)) { + if (isErr && !(!global.console.error)) + global.console.error(line) + else + global.console.log(line) + } + } +} + +private[lang] object JSConsoleBasedPrintStream { + private final val LineContEnd: String = "\u21A9" + private final val LineContStart: String = "\u21AA" + + class DummyOutputStream extends OutputStream { + def write(c: Int): Unit = + throw new AssertionError( + "Should not get in JSConsoleBasedPrintStream.DummyOutputStream") + } +} diff --git a/examples/scala-js/javalanglib/src/main/scala/java/lang/Thread.scala b/examples/scala-js/javalanglib/src/main/scala/java/lang/Thread.scala new file mode 100644 index 0000000..e52d7f6 --- /dev/null +++ b/examples/scala-js/javalanglib/src/main/scala/java/lang/Thread.scala @@ -0,0 +1,16 @@ +package java.lang + +/* We need a constructor to create SingleThread in the companion object, but + * we don't want user code doing a 'new Thread()' to link, because that could + * be confusing. + * So we use a binary signature that no Java source file can ever produce. + */ +class Thread private (dummy: Unit) extends Runnable { + def run(): Unit = () +} + +object Thread { + private[this] val SingleThread = new Thread(()) + + def currentThread(): Thread = SingleThread +} diff --git a/examples/scala-js/javalanglib/src/main/scala/java/lang/ThreadLocal.scala b/examples/scala-js/javalanglib/src/main/scala/java/lang/ThreadLocal.scala new file mode 100644 index 0000000..a36a40c --- /dev/null +++ b/examples/scala-js/javalanglib/src/main/scala/java/lang/ThreadLocal.scala @@ -0,0 +1,24 @@ +package java.lang + +class ThreadLocal[T] { + private var hasValue: Boolean = false + private var v: T = _ + + protected def initialValue(): T = null.asInstanceOf[T] + + def get(): T = { + if (!hasValue) + set(initialValue) + v + } + + def set(o: T): Unit = { + v = o + hasValue = true + } + + def remove(): Unit = { + hasValue = false + v = null.asInstanceOf[T] // for gc + } +} diff --git a/examples/scala-js/javalanglib/src/main/scala/java/lang/Throwables.scala b/examples/scala-js/javalanglib/src/main/scala/java/lang/Throwables.scala new file mode 100644 index 0000000..a38fee9 --- /dev/null +++ b/examples/scala-js/javalanglib/src/main/scala/java/lang/Throwables.scala @@ -0,0 +1,363 @@ +package java.lang + +import scala.scalajs.js + +class Throwable(s: String, private var e: Throwable) extends Object with java.io.Serializable { + def this() = this(null, null) + def this(s: String) = this(s, null) + def this(e: Throwable) = this(null, e) + + private[this] var stackTrace: Array[StackTraceElement] = _ + + fillInStackTrace() + + def initCause(cause: Throwable): Throwable = { + e = cause + this + } + + def getMessage(): String = s + def getCause(): Throwable = e + def getLocalizedMessage(): String = getMessage() + + def fillInStackTrace(): Throwable = { + scala.scalajs.runtime.StackTrace.captureState(this) + this + } + + def getStackTrace(): Array[StackTraceElement] = { + if (stackTrace eq null) + stackTrace = scala.scalajs.runtime.StackTrace.extract(this) + stackTrace + } + + def setStackTrace(stackTrace: Array[StackTraceElement]): Unit = { + var i = 0 + while (i < stackTrace.length) { + if (stackTrace(i) eq null) + throw new NullPointerException() + i += 1 + } + + this.stackTrace = stackTrace.clone() + } + + def printStackTrace(): Unit = printStackTrace(System.err) + + def printStackTrace(s: java.io.PrintStream): Unit = + printStackTraceImpl(s.println(_)) + + def printStackTrace(s: java.io.PrintWriter): Unit = + printStackTraceImpl(s.println(_)) + + private[this] def printStackTraceImpl(sprintln: String => Unit): Unit = { + getStackTrace() // will init it if still null + + // Message + sprintln(toString) + + // Trace + if (stackTrace.length != 0) { + var i = 0 + while (i < stackTrace.length) { + sprintln(" at "+stackTrace(i)) + i += 1 + } + } else { + sprintln(" <no stack trace available>") + } + + // Causes + var wCause: Throwable = this + while ((wCause ne wCause.getCause) && (wCause.getCause ne null)) { + val parentTrace = wCause.getStackTrace + wCause = wCause.getCause + val thisTrace = wCause.getStackTrace + + val thisLength = thisTrace.length + val parentLength = parentTrace.length + + sprintln("Caused by: " + wCause.toString) + + if (thisLength != 0) { + /* Count how many frames are shared between this stack trace and the + * parent stack trace, so that we can omit them when printing. + */ + var sameFrameCount: Int = 0 + while (sameFrameCount < thisLength && sameFrameCount < parentLength && + thisTrace(thisLength-sameFrameCount-1) == parentTrace(parentLength-sameFrameCount-1)) { + sameFrameCount += 1 + } + + /* If at least one, decrement so that the first common frame is still + * printed. According to Harmony this is spec'ed and common practice. + */ + if (sameFrameCount > 0) + sameFrameCount -= 1 + + // Print the non-common frames + val lengthToPrint = thisLength - sameFrameCount + var i = 0 + while (i < lengthToPrint) { + sprintln(" at "+thisTrace(i)) + i += 1 + } + + if (sameFrameCount > 0) + sprintln(" ... " + sameFrameCount + " more") + } else { + sprintln(" <no stack trace available>") + } + } + } + + override def toString() = { + val className = getClass.getName + val message = getMessage() + if (message eq null) className + else className + ": " + message + } +} + +class ThreadDeath() extends Error() + + +/* java.lang.*Error.java */ + +class AbstractMethodError(s: String) extends IncompatibleClassChangeError(s) { + def this() = this(null) +} + +class AssertionError private (s: String) extends Error(s) { + def this() = this(null) + def this(o: Object) = this(o.toString) + def this(b: scala.Boolean) = this(b.toString) + def this(c: scala.Char) = this(c.toString) + def this(i: scala.Int) = this(i.toString) + def this(l: scala.Long) = this(l.toString) + def this(f: scala.Float) = this(f.toString) + def this(d: scala.Double) = this(d.toString) +} + +class BootstrapMethodError(s: String, e: Throwable) extends LinkageError(s) { + def this(e: Throwable) = this(null, e) + def this(s: String) = this(s, null) + def this() = this(null, null) +} + +class ClassCircularityError(s: String) extends LinkageError(s) { + def this() = this(null) +} + +class ClassFormatError(s: String) extends LinkageError(s) { + def this() = this(null) +} + +class Error(s: String, e: Throwable) extends Throwable(s, e) { + def this() = this(null, null) + def this(s: String) = this(s, null) + def this(e: Throwable) = this(null, e) +} + +class ExceptionInInitializerError private (s: String, private val e: Throwable) extends LinkageError(s) { + def this(thrown: Throwable) = this(null, thrown) + def this(s: String) = this(s, null) + def this() = this(null, null) + def getException(): Throwable = e + override def getCause(): Throwable = e +} + +class IllegalAccessError(s: String) extends IncompatibleClassChangeError(s) { + def this() = this(null) +} + +class IncompatibleClassChangeError(s: String) extends LinkageError(s) { + def this() = this(null) +} + +class InstantiationError(s: String) extends IncompatibleClassChangeError(s) { + def this() = this(null) +} + +class InternalError(s: String) extends VirtualMachineError(s) { + def this() = this(null) +} + +class LinkageError(s: String) extends Error(s) { + def this() = this(null) +} + +class NoClassDefFoundError(s: String) extends LinkageError(s) { + def this() = this(null) +} + +class NoSuchFieldError(s: String) extends IncompatibleClassChangeError(s) { + def this() = this(null) +} + +class NoSuchMethodError(s: String) extends IncompatibleClassChangeError(s) { + def this() = this(null) +} + +class OutOfMemoryError(s: String) extends VirtualMachineError(s) { + def this() = this(null) +} + +class StackOverflowError(s: String) extends VirtualMachineError(s) { + def this() = this(null) +} + +class UnknownError(s: String) extends VirtualMachineError(s) { + def this() = this(null) +} + +class UnsatisfiedLinkError(s: String) extends LinkageError(s) { + def this() = this(null) +} + +class UnsupportedClassVersionError(s: String) extends ClassFormatError(s) { + def this() = this(null) +} + +class VerifyError(s: String) extends LinkageError(s) { + def this() = this(null) +} + +abstract class VirtualMachineError(s: String) extends Error(s) { + def this() = this(null) +} + + +/* java.lang.*Exception.java */ + +class ArithmeticException(s: String) extends RuntimeException(s) { + def this() = this(null) +} + +class ArrayIndexOutOfBoundsException(s: String) extends IndexOutOfBoundsException(s) { + def this(index: Int) = this("Array index out of range: " + index) + def this() = this(null) +} + +class ArrayStoreException(s: String) extends RuntimeException(s) { + def this() = this(null) +} + +class ClassCastException(s: String) extends RuntimeException(s) { + def this() = this(null) +} + +class ClassNotFoundException(s: String, e: Throwable) extends ReflectiveOperationException(s) { + def this(s: String) = this(s, null) + def this() = this(null, null) + def getException(): Throwable = e + override def getCause(): Throwable = e +} + +class CloneNotSupportedException(s: String) extends Exception(s) { + def this() = this(null) +} + +import scala.language.existentials +class EnumConstantNotPresentException( + e: Class[_ <: Enum[T] forSome { type T <: Enum[T] }], c: String) + extends RuntimeException(e.getName() + "." + c) { + def enumType() = e + def constantName() = c +} + +class Exception(s: String, e: Throwable) extends Throwable(s, e) { + def this(e: Throwable) = this(null, e) + def this(s: String) = this(s, null) + def this() = this(null, null) +} + +class IllegalAccessException(s: String) extends ReflectiveOperationException(s) { + def this() = this(null) +} + +class IllegalArgumentException(s: String, e: Throwable) extends RuntimeException(s, e) { + def this(e: Throwable) = this(null, e) + def this(s: String) = this(s, null) + def this() = this(null, null) +} + +class IllegalMonitorStateException(s: String) extends RuntimeException(s) { + def this() = this(null) +} + +class IllegalStateException(s: String, e: Throwable) extends RuntimeException(s, e) { + def this(e: Throwable) = this(null, e) + def this(s: String) = this(s, null) + def this() = this(null, null) +} + +class IllegalThreadStateException(s: String) extends IllegalArgumentException(s) { + def this() = this(null) +} + +class IndexOutOfBoundsException(s: String) extends RuntimeException(s) { + def this() = this(null) +} + +class InstantiationException(s: String) extends ReflectiveOperationException(s) { + def this() = this(null) +} + +class InterruptedException(s: String) extends Exception(s) { + def this() = this(null) +} + +class NegativeArraySizeException(s: String) extends RuntimeException(s) { + def this() = this(null) +} + +class NoSuchFieldException(s: String) extends ReflectiveOperationException(s) { + def this() = this(null) +} + +class NoSuchMethodException(s: String) extends ReflectiveOperationException(s) { + def this() = this(null) +} + +class NullPointerException(s: String) extends RuntimeException(s) { + def this() = this(null) +} + +class NumberFormatException(s: String) extends IllegalArgumentException(s) { + def this() = this(null) +} + +class ReflectiveOperationException(s: String, e: Throwable) extends Exception(s, e) { + def this(e: Throwable) = this(null, e) + def this(s: String) = this(s, null) + def this() = this(null, null) +} + +class RuntimeException(s: String, e: Throwable) extends Exception(s, e) { + def this(e: Throwable) = this(null, e) + def this(s: String) = this(s, null) + def this() = this(null, null) +} + +class SecurityException(s: String, e: Throwable) extends RuntimeException(s, e) { + def this(e: Throwable) = this(null, e) + def this(s: String) = this(s, null) + def this() = this(null, null) +} + +class StringIndexOutOfBoundsException(s: String) extends IndexOutOfBoundsException(s) { + def this(index: Int) = this("String index out of range: " + index) + def this() = this(null) +} + +class TypeNotPresentException(t: String, e: Throwable) + extends RuntimeException("Type " + t + " not present", e) { + def typeName(): String = t +} + +class UnsupportedOperationException(s: String, e: Throwable) extends RuntimeException(s, e) { + def this() = this(null, null) + def this(s: String) = this(s, null) + def this(e: Throwable) = this(null, e) +} diff --git a/examples/scala-js/javalanglib/src/main/scala/java/lang/Void.scala b/examples/scala-js/javalanglib/src/main/scala/java/lang/Void.scala new file mode 100644 index 0000000..fbe68fb --- /dev/null +++ b/examples/scala-js/javalanglib/src/main/scala/java/lang/Void.scala @@ -0,0 +1,7 @@ +package java.lang + +final class Void private {} + +object Void { + final val TYPE = classOf[scala.Unit] +} diff --git a/examples/scala-js/javalanglib/src/main/scala/java/lang/ref/PhantomReference.scala b/examples/scala-js/javalanglib/src/main/scala/java/lang/ref/PhantomReference.scala new file mode 100644 index 0000000..ecace8a --- /dev/null +++ b/examples/scala-js/javalanglib/src/main/scala/java/lang/ref/PhantomReference.scala @@ -0,0 +1,7 @@ +package java.lang.ref + +class PhantomReference[T >: Null <: AnyRef](referent: T, + queue: ReferenceQueue[_ >: T]) extends Reference[T](null) { + + override def get(): T = null +} diff --git a/examples/scala-js/javalanglib/src/main/scala/java/lang/ref/Reference.scala b/examples/scala-js/javalanglib/src/main/scala/java/lang/ref/Reference.scala new file mode 100644 index 0000000..76909cf --- /dev/null +++ b/examples/scala-js/javalanglib/src/main/scala/java/lang/ref/Reference.scala @@ -0,0 +1,8 @@ +package java.lang.ref + +abstract class Reference[T >: Null <: AnyRef](private[this] var referent: T) { + def get(): T = referent + def clear(): Unit = referent = null + def isEnqueued(): Boolean = false + def enqueue(): Boolean = false +} diff --git a/examples/scala-js/javalanglib/src/main/scala/java/lang/ref/ReferenceQueue.scala b/examples/scala-js/javalanglib/src/main/scala/java/lang/ref/ReferenceQueue.scala new file mode 100644 index 0000000..e9c5110 --- /dev/null +++ b/examples/scala-js/javalanglib/src/main/scala/java/lang/ref/ReferenceQueue.scala @@ -0,0 +1,3 @@ +package java.lang.ref + +class ReferenceQueue[T >: Null <: AnyRef] diff --git a/examples/scala-js/javalanglib/src/main/scala/java/lang/ref/SoftReference.scala b/examples/scala-js/javalanglib/src/main/scala/java/lang/ref/SoftReference.scala new file mode 100644 index 0000000..eb0fdf7 --- /dev/null +++ b/examples/scala-js/javalanglib/src/main/scala/java/lang/ref/SoftReference.scala @@ -0,0 +1,9 @@ +package java.lang.ref + +class SoftReference[T >: Null <: AnyRef](referent: T, + queue: ReferenceQueue[_ >: T]) extends Reference[T](referent) { + + def this(referent: T) = this(referent, null) + + override def get(): T = super.get() +} diff --git a/examples/scala-js/javalanglib/src/main/scala/java/lang/ref/WeakReference.scala b/examples/scala-js/javalanglib/src/main/scala/java/lang/ref/WeakReference.scala new file mode 100644 index 0000000..2a74aa1 --- /dev/null +++ b/examples/scala-js/javalanglib/src/main/scala/java/lang/ref/WeakReference.scala @@ -0,0 +1,7 @@ +package java.lang.ref + +class WeakReference[T >: Null <: AnyRef](referent: T, + queue: ReferenceQueue[_ >: T]) extends Reference[T](referent) { + + def this(referent: T) = this(referent, null) +} diff --git a/examples/scala-js/javalanglib/src/main/scala/java/lang/reflect/Array.scala b/examples/scala-js/javalanglib/src/main/scala/java/lang/reflect/Array.scala new file mode 100644 index 0000000..bc3696e --- /dev/null +++ b/examples/scala-js/javalanglib/src/main/scala/java/lang/reflect/Array.scala @@ -0,0 +1,176 @@ +package java.lang.reflect + +import scala.scalajs.js + +import js.JSConverters._ + +import java.lang.Class + +object Array { + def newInstance(componentType: Class[_], length: Int): AnyRef = + componentType.newArrayOfThisClass(js.Array(length)) + + def newInstance(componentType: Class[_], dimensions: scala.Array[Int]): AnyRef = + componentType.newArrayOfThisClass(dimensions.toJSArray) + + def getLength(array: AnyRef): Int = array match { + // yes, this is kind of stupid, but that's how it is + case array: Array[Object] => array.length + case array: Array[Boolean] => array.length + case array: Array[Char] => array.length + case array: Array[Byte] => array.length + case array: Array[Short] => array.length + case array: Array[Int] => array.length + case array: Array[Long] => array.length + case array: Array[Float] => array.length + case array: Array[Double] => array.length + case _ => throw new IllegalArgumentException("argument type mismatch") + } + + def get(array: AnyRef, index: Int): AnyRef = array match { + case array: Array[Object] => array(index) + case array: Array[Boolean] => new java.lang.Boolean(array(index)) + case array: Array[Char] => new java.lang.Character(array(index)) + case array: Array[Byte] => new java.lang.Byte(array(index)) + case array: Array[Short] => new java.lang.Short(array(index)) + case array: Array[Int] => new java.lang.Integer(array(index)) + case array: Array[Long] => new java.lang.Long(array(index)) + case array: Array[Float] => new java.lang.Float(array(index)) + case array: Array[Double] => new java.lang.Double(array(index)) + case _ => throw new IllegalArgumentException("argument type mismatch") + } + + def getBoolean(array: AnyRef, index: Int): Boolean = array match { + case array: Array[Boolean] => array(index) + case _ => throw new IllegalArgumentException("argument type mismatch") + } + + def getChar(array: AnyRef, index: Int): Char = array match { + case array: Array[Char] => array(index) + case _ => throw new IllegalArgumentException("argument type mismatch") + } + + def getByte(array: AnyRef, index: Int): Byte = array match { + case array: Array[Byte] => array(index) + case _ => throw new IllegalArgumentException("argument type mismatch") + } + + def getShort(array: AnyRef, index: Int): Short = array match { + case array: Array[Short] => array(index) + case array: Array[Byte] => array(index) + case _ => throw new IllegalArgumentException("argument type mismatch") + } + + def getInt(array: AnyRef, index: Int): Int = array match { + case array: Array[Int] => array(index) + case array: Array[Char] => array(index) + case array: Array[Byte] => array(index) + case array: Array[Short] => array(index) + case _ => throw new IllegalArgumentException("argument type mismatch") + } + + def getLong(array: AnyRef, index: Int): Long = array match { + case array: Array[Long] => array(index) + case array: Array[Char] => array(index) + case array: Array[Byte] => array(index) + case array: Array[Short] => array(index) + case array: Array[Int] => array(index) + case _ => throw new IllegalArgumentException("argument type mismatch") + } + + def getFloat(array: AnyRef, index: Int): Float = array match { + case array: Array[Float] => array(index) + case array: Array[Char] => array(index) + case array: Array[Byte] => array(index) + case array: Array[Short] => array(index) + case array: Array[Int] => array(index) + case array: Array[Long] => array(index) + case _ => throw new IllegalArgumentException("argument type mismatch") + } + + def getDouble(array: AnyRef, index: Int): Double = array match { + case array: Array[Double] => array(index) + case array: Array[Char] => array(index) + case array: Array[Byte] => array(index) + case array: Array[Short] => array(index) + case array: Array[Int] => array(index) + case array: Array[Long] => array(index) + case array: Array[Float] => array(index) + case _ => throw new IllegalArgumentException("argument type mismatch") + } + + def set(array: AnyRef, index: Int, value: AnyRef): Unit = array match { + case array: Array[Object] => array(index) = value + case _ => + (value: Any) match { + case value: Boolean => setBoolean(array, index, value) + case value: Char => setChar(array, index, value) + case value: Byte => setByte(array, index, value) + case value: Short => setShort(array, index, value) + case value: Int => setInt(array, index, value) + case value: Long => setLong(array, index, value) + case value: Float => setFloat(array, index, value) + case value: Double => setDouble(array, index, value) + case _ => throw new IllegalArgumentException("argument type mismatch") + } + } + + def setBoolean(array: AnyRef, index: Int, value: Boolean): Unit = array match { + case array: Array[Boolean] => array(index) = value + case _ => throw new IllegalArgumentException("argument type mismatch") + } + + def setChar(array: AnyRef, index: Int, value: Char): Unit = array match { + case array: Array[Char] => array(index) = value + case array: Array[Int] => array(index) = value + case array: Array[Long] => array(index) = value + case array: Array[Float] => array(index) = value + case array: Array[Double] => array(index) = value + case _ => throw new IllegalArgumentException("argument type mismatch") + } + + def setByte(array: AnyRef, index: Int, value: Byte): Unit = array match { + case array: Array[Byte] => array(index) = value + case array: Array[Short] => array(index) = value + case array: Array[Int] => array(index) = value + case array: Array[Long] => array(index) = value + case array: Array[Float] => array(index) = value + case array: Array[Double] => array(index) = value + case _ => throw new IllegalArgumentException("argument type mismatch") + } + + def setShort(array: AnyRef, index: Int, value: Short): Unit = array match { + case array: Array[Short] => array(index) = value + case array: Array[Int] => array(index) = value + case array: Array[Long] => array(index) = value + case array: Array[Float] => array(index) = value + case array: Array[Double] => array(index) = value + case _ => throw new IllegalArgumentException("argument type mismatch") + } + + def setInt(array: AnyRef, index: Int, value: Int): Unit = array match { + case array: Array[Int] => array(index) = value + case array: Array[Long] => array(index) = value + case array: Array[Float] => array(index) = value + case array: Array[Double] => array(index) = value + case _ => throw new IllegalArgumentException("argument type mismatch") + } + + def setLong(array: AnyRef, index: Int, value: Long): Unit = array match { + case array: Array[Long] => array(index) = value + case array: Array[Float] => array(index) = value + case array: Array[Double] => array(index) = value + case _ => throw new IllegalArgumentException("argument type mismatch") + } + + def setFloat(array: AnyRef, index: Int, value: Float): Unit = array match { + case array: Array[Float] => array(index) = value + case array: Array[Double] => array(index) = value + case _ => throw new IllegalArgumentException("argument type mismatch") + } + + def setDouble(array: AnyRef, index: Int, value: Double): Unit = array match { + case array: Array[Double] => array(index) = value + case _ => throw new IllegalArgumentException("argument type mismatch") + } +} diff --git a/examples/scala-js/javalib-ex-test-suite/src/test/scala/scala/scalajs/testsuite/javalibex/DataInputStreamTest.scala b/examples/scala-js/javalib-ex-test-suite/src/test/scala/scala/scalajs/testsuite/javalibex/DataInputStreamTest.scala new file mode 100644 index 0000000..61ec560 --- /dev/null +++ b/examples/scala-js/javalib-ex-test-suite/src/test/scala/scala/scalajs/testsuite/javalibex/DataInputStreamTest.scala @@ -0,0 +1,338 @@ +package scala.scalajs.testsuite.javalibex + +import java.io._ + +import org.scalajs.jasminetest.JasmineTest + +import scala.scalajs.js.typedarray._ +import scala.scalajs.js.JSConverters._ + +object DataInputStreamTest extends JasmineTest { + + def tests(name: String)(inFromBytes: Seq[Byte] => InputStream) { + def newStream(data: Int*) = + new DataInputStream(inFromBytes(data.map(_.toByte))) + + when("typedarray"). + describe(name) { + + it("should provide `readBoolean`") { + val data = Seq(0x00, 0x01, 0xF1, 0x00, 0x01) + val stream = newStream(data: _*) + + for (d <- data) + expect(stream.readBoolean()).toBe(d != 0) + + expect(() => stream.readBoolean()).toThrow // EOF + } + + it("should provide `readByte`") { + val data = Seq(0x00, 0x01, 0xF1, 0x7D, 0x35) + val stream = newStream(data: _*) + + for (d <- data) + expect(stream.readByte()).toBe(d.toByte) + + expect(() => stream.readBoolean()).toThrow // EOF + } + + it("should provide `readChar`") { + val stream = newStream( + 0x00, 0x48, // H + 0x00, 0xF6, // ö + 0x00, 0x6C, // l + 0x00, 0x6C, // l + 0x00, 0xF6, // ö + 0x00, 0x20, // [space] + 0x00, 0x57, // W + 0x01, 0x03, // ă + 0x00, 0x72, // r + 0x02, 0x34, // ȴ + 0x01, 0x11, // đ + 0x56 // dangling + ) + var res = "" + + for (i <- 1 to 11) + res += stream.readChar() + + expect(res).toEqual("Höllö Wărȴđ") + + expect(() => stream.readChar()).toThrow // Dangling + EOF + } + + it("should provide `readDouble`") { + val stream = newStream( + 0x3f, 0xe6, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, + 0x41, 0x15, 0x19, 0x20, 0x45, 0x8d, 0x9b, 0x5f, + 0xc0, 0xab, 0x20, 0x22, 0x75, 0x25, 0x46, 0x0b, + 0x7f, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x7f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xc0, 0x1c, 0x0d, 0xca, 0x65, 0xea, 0x3f, 0xa4, + 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01 // dangling + ) + + expect(stream.readDouble()).toBe(0.7) + expect(stream.readDouble()).toBe(345672.067923) + expect(stream.readDouble()).toBe(-3472.0673) + expect(stream.readDouble().isNaN).toBe(true) + expect(stream.readDouble()).toBe(Double.PositiveInfinity) + expect(stream.readDouble()).toBe(-7.0134674) + expect(stream.readDouble()).toBe(Double.NegativeInfinity) + expect(stream.readDouble()).toBe(0) + expect(() => stream.readDouble()).toThrow + } + + it("should provide `readFloat`") { + val stream = newStream( + 0xbf, 0x80, 0x00, 0x00, + 0x45, 0x8e, 0x9c, 0x83, + 0x80, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x7f, 0xc0, 0x00, 0x00, + 0x7f, 0x80, 0x00, 0x00, + 0xbb, 0x03, 0x12, 0x6f, + 0xff, 0x80, 0x00, 0x00, + 0xff // dangling + ) + + expect(stream.readFloat()).toBeCloseTo(-1.0f, 6) + expect(stream.readFloat()).toBeCloseTo(4563.564f, 6) + expect(stream.readFloat()).toBeCloseTo(-0.0f, 6) + expect(stream.readFloat()).toBeCloseTo(0.0f, 6) + expect(stream.readFloat().isNaN).toBe(true) + expect(stream.readFloat()).toBe(Float.PositiveInfinity) + expect(stream.readFloat()).toBeCloseTo(-0.002f, 6) + expect(stream.readFloat()).toBe(Float.NegativeInfinity) + expect(() => stream.readDouble()).toThrow + } + + it("should provide `readInt`") { + val stream = newStream( + 0x00, 0x00, 0x00, 0x00, + 0x7f, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xfc, + 0x00, 0x00, 0x00, 0x53, + 0x00, 0x00, 0x89, 0xa2, + 0xff, 0xfe, 0x82, 0xfd, + 0x80, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01) + + expect(stream.readInt()).toBe(0) + expect(stream.readInt()).toBe(Int.MaxValue) + expect(stream.readInt()).toBe(-4) + expect(stream.readInt()).toBe(83) + expect(stream.readInt()).toBe(35234) + expect(stream.readInt()).toBe(-97539) + expect(stream.readInt()).toBe(Int.MinValue) + expect(stream.readInt()).toBe(1) + expect(() => stream.readDouble()).toThrow + } + + it("should provide `readLong`") { + val stream = newStream( + 0x00, 0x01, 0xf0, 0xec, 0x59, 0x0c, 0x70, 0x9a, + 0xff, 0xff, 0xff, 0xff, 0xfe, 0x10, 0xd5, 0x5e, + 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x79, 0x24, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2f) + + expect(stream.readLong() == 546372873646234L).toBeTruthy + expect(stream.readLong() == -32451234L).toBeTruthy + expect(stream.readLong() == Long.MaxValue).toBeTruthy + expect(stream.readLong() == 0L).toBeTruthy + expect(stream.readLong() == -1L).toBeTruthy + expect(stream.readLong() == Long.MinValue).toBeTruthy + expect(stream.readLong() == -34524L).toBeTruthy + expect(stream.readLong() == 47L).toBeTruthy + expect(() => stream.readDouble()).toThrow + } + + it("should provide `readShort`") { + val stream = newStream( + 0x01, 0xc5, + 0xff, 0xd5, + 0x7f, 0xff, + 0x18, 0xb0, + 0x00, 0x00, + 0x80, 0x00, + 0xfe, 0xa6, + 0x00, 0x22, + 0x01 // dangling + ) + + expect(stream.readShort()).toBe(453) + expect(stream.readShort()).toBe(-43) + expect(stream.readShort()).toBe(Short.MaxValue) + expect(stream.readShort()).toBe(6320) + expect(stream.readShort()).toBe(0) + expect(stream.readShort()).toBe(Short.MinValue) + expect(stream.readShort()).toBe(-346) + expect(stream.readShort()).toBe(34) + expect(() => stream.readDouble()).toThrow + } + + it("should provide `readUnsignedByte`") { + val data = Seq(0x00, 0x01, 0xF1, 0x7D, 0x35) + val stream = newStream(data: _*) + + for (d <- data) + expect(stream.readUnsignedByte()).toBe(d) + + expect(() => stream.readBoolean()).toThrow // EOF + } + + it("should provide `readUnsignedShort`") { + val stream = newStream( + 0xfe, 0x4c, + 0x00, 0x00, + 0x18, 0xee, + 0x0d, 0xed, + 0x00, 0x2b, + 0x01, 0xce, + 0x01, 0x56, + 0x64, 0x2b, + 0x01 // dangling + ) + + expect(stream.readUnsignedShort()).toBe(65100) + expect(stream.readUnsignedShort()).toBe(0) + expect(stream.readUnsignedShort()).toBe(6382) + expect(stream.readUnsignedShort()).toBe(3565) + expect(stream.readUnsignedShort()).toBe(43) + expect(stream.readUnsignedShort()).toBe(462) + expect(stream.readUnsignedShort()).toBe(342) + expect(stream.readUnsignedShort()).toBe(25643) + expect(() => stream.readDouble()).toThrow + } + + it("should provide `readFully` (1 arg & 3 arg)") { + val stream = newStream(-100 to 99: _*) + val buf = new Array[Byte](50) + + stream.readFully(buf) + expect(buf.toJSArray).toEqual((-100 to -51).toJSArray) + + expect(() => stream.readFully(null)).toThrow + + stream.readFully(buf, 40, 10) + expect(buf.toJSArray).toEqual(((-100 to -61) ++ (-50 to -41)).toJSArray) + + expect(() => stream.readFully(buf, 70, 1)).toThrow + expect(() => stream.readFully(buf, 10, 100)).toThrow + expect(() => stream.readFully(buf, -1, 2)).toThrow + expect(() => stream.readFully(buf, 0, -1)).toThrow + + stream.readFully(buf, 0, 50) + expect(buf.toJSArray).toEqual((-40 to 9).toJSArray) + + stream.readFully(buf, 0, 50) + expect(buf.toJSArray).toEqual((10 to 59).toJSArray) + + expect(() => stream.readFully(buf)).toThrow + } + + it("should provide `readFully` for bursty streams") { + class BurstyStream(length: Int, burst: Int) extends InputStream { + private var i: Int = 0 + def read(): Int = if (i < length) { i += 1; i } else -1 + override def read(buf: Array[Byte], off: Int, reqLen: Int): Int = { + val len = Math.min(Math.min(reqLen, burst), length - i) + if (reqLen == 0) 0 + else if (len == 0) -1 + else { + var j: Int = 0 + while (j < len) { + buf(off+j) = read().toByte + j += 1 + } + len + } + } + } + + val stream = new DataInputStream(new BurstyStream(100, 5)) + val buf = new Array[Byte](50) + + stream.readFully(buf) + expect(buf.toJSArray).toEqual((1 to 50).toJSArray) + + stream.readFully(buf, 40, 10) + expect(buf.toJSArray).toEqual(((1 to 40) ++ (51 to 60)).toJSArray) + + expect(() => stream.readFully(buf)).toThrow + } + + it("should provide `readUTF`") { + val stream = newStream( + 0x00, 0x10, 0x48, 0xc3, 0xb6, 0x6c, 0x6c, 0xc3, + 0xb6, 0x20, 0x57, 0xc4, 0x83, 0x72, 0xc8, 0xb4, + 0xc4, 0x91, 0x00, 0x0d, 0x70, 0x6f, 0x6f, 0x20, + 0x2d, 0x3e, 0x20, 0xed, 0xa0, 0xbd, 0xed, 0xb2, + 0xa9, 0x00, 0x03, 0xe6, 0x84, 0x9b) + + expect(stream.readUTF).toEqual("Höllö Wărȴđ") + expect(stream.readUTF).toEqual("poo -> 💩") + expect(stream.readUTF).toEqual("愛") + + val badStream = newStream(0x00, 0x01, 0xC0, 0x82) + expect(() => badStream.readUTF).toThrow + } + + it("should provide `readLine`") { + val stream = newStream( + "Hello World\nUNIX\nWindows\r\nMac (old)\rStuff".map(_.toInt): _*) + + expect(stream.readLine()).toEqual("Hello World") + expect(stream.readLine()).toEqual("UNIX") + expect(stream.readLine()).toEqual("Windows") + expect(stream.readLine()).toEqual("Mac (old)") + expect(stream.readLine()).toEqual("Stuff") + expect(stream.readLine()).toBe(null) + } + + it("should allow marking even when `readLine` has to push back") { + val stream = newStream( + "Hello World\nUNIX\nWindows\r\nMac (old)\rStuff".map(_.toInt): _*) + + expect(stream.readLine()).toEqual("Hello World") + stream.mark(1000) + expect(stream.readLine()).toEqual("UNIX") + stream.reset() + expect(stream.readLine()).toEqual("UNIX") + expect(stream.readLine()).toEqual("Windows") + expect(stream.readLine()).toEqual("Mac (old)") + stream.mark(1000) + expect(stream.readLine()).toEqual("Stuff") + stream.reset() + expect(stream.readLine()).toEqual("Stuff") + expect(stream.readLine()).toBe(null) + } + + } + + } + + tests("java.io.DataInputStream - generic case")(bytes => + new ByteArrayInputStream(bytes.toArray)) + + tests("java.io.DataInputStream - ArrayBufferInputStream case")(bytes => + new ArrayBufferInputStream(new Int8Array(bytes.toJSArray).buffer)) + + tests("java.io.DataInputStream - partially consumed ArrayBufferInputStream case") { bytes => + val addBytes = Seq[Byte](0, 0, 0, 0) + val allBytes = addBytes ++ bytes + val in = new ArrayBufferInputStream( + new Int8Array(allBytes.toJSArray).buffer) + + for (_ <- addBytes) in.read() + + in + } + +} diff --git a/examples/scala-js/javalib-ex-test-suite/src/test/scala/scala/scalajs/testsuite/javalibex/ZipInputStreamTest.scala b/examples/scala-js/javalib-ex-test-suite/src/test/scala/scala/scalajs/testsuite/javalibex/ZipInputStreamTest.scala new file mode 100644 index 0000000..b2a2fe2 --- /dev/null +++ b/examples/scala-js/javalib-ex-test-suite/src/test/scala/scala/scalajs/testsuite/javalibex/ZipInputStreamTest.scala @@ -0,0 +1,136 @@ +package scala.scalajs.testsuite.javalibex + +import org.scalajs.jasminetest.JasmineTest + +import java.io._ +import java.util.zip._ + +object ZipInputStreamTest extends JasmineTest { + + when("typedarray"). + describe("java.util.zip.ZipInputStream") { + + it("should read zip archives") { + val in = new ZipInputStream(new ByteArrayInputStream(binZip)) + + def expectBinEntry(name: String, data: Seq[Int]): Unit = { + val e = in.getNextEntry() + expect(e.getName()).toEqual(name) + + for (d <- data) + expect(in.read()).toBe(d) + + expect(in.read()).toBe(-1) + } + + def expectStrEntry(name: String, content: String): Unit = { + val e = in.getNextEntry() + expect(e.getName()).toEqual(name) + + val r = new InputStreamReader(in) + + for (c <- content) + expect(r.read().toChar).toBe(c) + + expect(r.read()).toBe(-1) + } + + expectBinEntry("greetings/", Seq()) + expectStrEntry("greetings/en.txt", "Hello World, how are you doing?\n") + expectStrEntry("greetings/es.txt", "¿Hola mundo, cómo estás?\n") + expectStrEntry("greetings/fr.txt", "Bonjour, comment ça va?\n") + expectStrEntry("greetings/ja.txt", "こんにちは、お元気ですか。\n") + expectBinEntry("binary/", Seq()) + expectBinEntry("binary/bytes_0_to_50.bin", 0 to 50) + + expect(in.getNextEntry() == null).toBeTruthy + in.close() + } + + } + + /** A zip archive for testing: + * + * $ zipinfo test.zip + * Archive: test.zip 1304 bytes 7 files + * drwxr-xr-x 3.0 unx 0 bx stor 13-Aug-14 07:42 greetings/ + * -rw-r--r-- 3.0 unx 32 tx stor 13-Aug-14 07:40 greetings/en.txt + * -rw-r--r-- 3.0 unx 28 tx stor 13-Aug-14 07:42 greetings/es.txt + * -rw-r--r-- 3.0 unx 25 tx stor 13-Aug-14 07:41 greetings/fr.txt + * -rw-r--r-- 3.0 unx 40 tx stor 13-Aug-14 07:40 greetings/ja.txt + * drwxr-xr-x 3.0 unx 0 bx stor 13-Aug-14 07:48 binary/ + * -rw-r--r-- 3.0 unx 51 bx stor 13-Aug-14 07:48 binary/bytes_0_to_50.bin + * 7 files, 176 bytes uncompressed, 176 bytes compressed: 0.0% + */ + val binZip = Array[Byte](80, 75, 3, 4, 10, 0, 0, 0, 0, 0, 89, 61, 13, 69, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 28, 0, 103, 114, 101, 101, 116, + 105, 110, 103, 115, 47, 85, 84, 9, 0, 3, -39, -6, -22, 83, -44, -4, -22, + 83, 117, 120, 11, 0, 1, 4, -4, 1, 0, 0, 4, 0, 0, 0, 0, 80, 75, 3, 4, 10, + 0, 2, 0, 0, 0, 13, 61, 13, 69, -125, -110, -96, -74, 32, 0, 0, 0, 32, 0, + 0, 0, 16, 0, 28, 0, 103, 114, 101, 101, 116, 105, 110, 103, 115, 47, 101, + 110, 46, 116, 120, 116, 85, 84, 9, 0, 3, 73, -6, -22, 83, -108, -4, -22, + 83, 117, 120, 11, 0, 1, 4, -4, 1, 0, 0, 4, 0, 0, 0, 0, 72, 101, 108, 108, + 111, 32, 87, 111, 114, 108, 100, 44, 32, 104, 111, 119, 32, 97, 114, 101, + 32, 121, 111, 117, 32, 100, 111, 105, 110, 103, 63, 10, 80, 75, 3, 4, 10, + 0, 2, 0, 0, 0, 89, 61, 13, 69, -27, 99, -56, -20, 28, 0, 0, 0, 28, 0, 0, + 0, 16, 0, 28, 0, 103, 114, 101, 101, 116, 105, 110, 103, 115, 47, 101, + 115, 46, 116, 120, 116, 85, 84, 9, 0, 3, -39, -6, -22, 83, -108, -4, -22, + 83, 117, 120, 11, 0, 1, 4, -4, 1, 0, 0, 4, 0, 0, 0, 0, -62, -65, 72, 111, + 108, 97, 32, 109, 117, 110, 100, 111, 44, 32, 99, -61, -77, 109, 111, 32, + 101, 115, 116, -61, -95, 115, 63, 10, 80, 75, 3, 4, 10, 0, 2, 0, 0, 0, 42, + 61, 13, 69, -2, -58, -42, 30, 25, 0, 0, 0, 25, 0, 0, 0, 16, 0, 28, 0, 103, + 114, 101, 101, 116, 105, 110, 103, 115, 47, 102, 114, 46, 116, 120, 116, + 85, 84, 9, 0, 3, -128, -6, -22, 83, -108, -4, -22, 83, 117, 120, 11, 0, 1, + 4, -4, 1, 0, 0, 4, 0, 0, 0, 0, 66, 111, 110, 106, 111, 117, 114, 44, 32, + 99, 111, 109, 109, 101, 110, 116, 32, -61, -89, 97, 32, 118, 97, 63, 10, + 80, 75, 3, 4, 10, 0, 2, 0, 0, 0, 24, 61, 13, 69, -26, -5, 76, 91, 40, 0, + 0, 0, 40, 0, 0, 0, 16, 0, 28, 0, 103, 114, 101, 101, 116, 105, 110, 103, + 115, 47, 106, 97, 46, 116, 120, 116, 85, 84, 9, 0, 3, 96, -6, -22, 83, + -108, -4, -22, 83, 117, 120, 11, 0, 1, 4, -4, 1, 0, 0, 4, 0, 0, 0, 0, + -29, -127, -109, -29, -126, -109, -29, -127, -85, -29, -127, -95, -29, + -127, -81, -29, -128, -127, -29, -127, -118, -27, -123, -125, -26, -80, + -105, -29, -127, -89, -29, -127, -103, -29, -127, -117, -29, -128, -126, + 10, 80, 75, 3, 4, 10, 0, 0, 0, 0, 0, 6, 62, 13, 69, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 7, 0, 28, 0, 98, 105, 110, 97, 114, 121, 47, 85, 84, 9, 0, + 3, 28, -4, -22, 83, -44, -4, -22, 83, 117, 120, 11, 0, 1, 4, -4, 1, 0, 0, + 4, 0, 0, 0, 0, 80, 75, 3, 4, 10, 0, 2, 0, 0, 0, 3, 62, 13, 69, -7, 93, 98, + 55, 51, 0, 0, 0, 51, 0, 0, 0, 24, 0, 28, 0, 98, 105, 110, 97, 114, 121, + 47, 98, 121, 116, 101, 115, 95, 48, 95, 116, 111, 95, 53, 48, 46, 98, 105, + 110, 85, 84, 9, 0, 3, 22, -4, -22, 83, -108, -4, -22, 83, 117, 120, 11, 0, + 1, 4, -4, 1, 0, 0, 4, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, + 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 80, 75, 1, 2, 30, 3, 10, 0, 0, 0, 0, 0, 89, 61, 13, 69, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 24, 0, 0, 0, 0, 0, 0, 0, 16, 0, -19, + 65, 0, 0, 0, 0, 103, 114, 101, 101, 116, 105, 110, 103, 115, 47, 85, 84, + 5, 0, 3, -39, -6, -22, 83, 117, 120, 11, 0, 1, 4, -4, 1, 0, 0, 4, 0, 0, 0, + 0, 80, 75, 1, 2, 30, 3, 10, 0, 2, 0, 0, 0, 13, 61, 13, 69, -125, -110, + -96, -74, 32, 0, 0, 0, 32, 0, 0, 0, 16, 0, 24, 0, 0, 0, 0, 0, 1, 0, 0, 0, + -92, -127, 68, 0, 0, 0, 103, 114, 101, 101, 116, 105, 110, 103, 115, 47, + 101, 110, 46, 116, 120, 116, 85, 84, 5, 0, 3, 73, -6, -22, 83, 117, 120, + 11, 0, 1, 4, -4, 1, 0, 0, 4, 0, 0, 0, 0, 80, 75, 1, 2, 30, 3, 10, 0, 2, 0, + 0, 0, 89, 61, 13, 69, -27, 99, -56, -20, 28, 0, 0, 0, 28, 0, 0, 0, 16, 0, + 24, 0, 0, 0, 0, 0, 1, 0, 0, 0, -92, -127, -82, 0, 0, 0, 103, 114, 101, + 101, 116, 105, 110, 103, 115, 47, 101, 115, 46, 116, 120, 116, 85, 84, 5, + 0, 3, -39, -6, -22, 83, 117, 120, 11, 0, 1, 4, -4, 1, 0, 0, 4, 0, 0, 0, 0, + 80, 75, 1, 2, 30, 3, 10, 0, 2, 0, 0, 0, 42, 61, 13, 69, -2, -58, -42, 30, + 25, 0, 0, 0, 25, 0, 0, 0, 16, 0, 24, 0, 0, 0, 0, 0, 1, 0, 0, 0, -92, -127, + 20, 1, 0, 0, 103, 114, 101, 101, 116, 105, 110, 103, 115, 47, 102, 114, + 46, 116, 120, 116, 85, 84, 5, 0, 3, -128, -6, -22, 83, 117, 120, 11, 0, 1, + 4, -4, 1, 0, 0, 4, 0, 0, 0, 0, 80, 75, 1, 2, 30, 3, 10, 0, 2, 0, 0, 0, 24, + 61, 13, 69, -26, -5, 76, 91, 40, 0, 0, 0, 40, 0, 0, 0, 16, 0, 24, 0, 0, 0, + 0, 0, 1, 0, 0, 0, -92, -127, 119, 1, 0, 0, 103, 114, 101, 101, 116, 105, + 110, 103, 115, 47, 106, 97, 46, 116, 120, 116, 85, 84, 5, 0, 3, 96, -6, + -22, 83, 117, 120, 11, 0, 1, 4, -4, 1, 0, 0, 4, 0, 0, 0, 0, 80, 75, 1, 2, + 30, 3, 10, 0, 0, 0, 0, 0, 6, 62, 13, 69, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 7, 0, 24, 0, 0, 0, 0, 0, 0, 0, 16, 0, -19, 65, -23, 1, 0, 0, 98, 105, + 110, 97, 114, 121, 47, 85, 84, 5, 0, 3, 28, -4, -22, 83, 117, 120, 11, 0, + 1, 4, -4, 1, 0, 0, 4, 0, 0, 0, 0, 80, 75, 1, 2, 30, 3, 10, 0, 2, 0, 0, 0, + 3, 62, 13, 69, -7, 93, 98, 55, 51, 0, 0, 0, 51, 0, 0, 0, 24, 0, 24, 0, 0, + 0, 0, 0, 0, 0, 0, 0, -92, -127, 42, 2, 0, 0, 98, 105, 110, 97, 114, 121, + 47, 98, 121, 116, 101, 115, 95, 48, 95, 116, 111, 95, 53, 48, 46, 98, 105, + 110, 85, 84, 5, 0, 3, 22, -4, -22, 83, 117, 120, 11, 0, 1, 4, -4, 1, 0, 0, + 4, 0, 0, 0, 0, 80, 75, 5, 6, 0, 0, 0, 0, 7, 0, 7, 0, 83, 2, 0, 0, -81, 2, + 0, 0, 0, 0) + +} diff --git a/examples/scala-js/javalib-ex/src/main/scala/java/io/DataInputStream.scala b/examples/scala-js/javalib-ex/src/main/scala/java/io/DataInputStream.scala new file mode 100644 index 0000000..bd11731 --- /dev/null +++ b/examples/scala-js/javalib-ex/src/main/scala/java/io/DataInputStream.scala @@ -0,0 +1,274 @@ +package java.io + +import scala.scalajs.js.typedarray._ + +class DataInputStream(in: InputStream) extends FilterInputStream(in) + with DataInput { + + private var pushedBack: Int = -1 + private var pushedBackMark: Int = -1 + + // -- ArrayBufferInputStream mode helpers -- + // These variables are used to special case on ArrayBufferInputStreams + // They allow directly accessing the underlying ArrayBuffer rather than + // creating byte arrays first + private val inArrayBufferStream = in match { + case in: ArrayBufferInputStream => in + case _ => null + } + private val hasArrayBuffer = inArrayBufferStream != null + private val bufDataView = { + if (hasArrayBuffer) { + val in = inArrayBufferStream + new DataView(in.buffer, in.offset, in.length) + } else null + } + + private def consumePos(n: Int) = { + val off = if (pushedBack != -1) 1 else 0 + val resultPos = inArrayBufferStream.pos - off + val toSkip = n - off + if (in.skip(toSkip) != toSkip) eof() + resultPos + } + + // -- General InputStream mode helpers -- + // Due to the method readLine, we need to be able to push back a byte (if we + // read a \r and the following byte is NOT a \n). We implement this here. + // We also provide a method to create an ad-hoc data view of the next n bytes + private val convBufLen = 8 + private val convBuf = new ArrayBuffer(convBufLen) + private val convInView = new Int8Array(convBuf) + private val convOutView = new DataView(convBuf) + private def view(len: Int) = { + assert(len <= convBufLen) + var i = 0 + while (i < len) { + val byte = read() + if (byte == -1) eof() + convInView(i) = byte.toByte + i += 1 + } + convOutView + } + + // General Helpers + private def eof() = throw new EOFException() + private def pushBack(v: Int) = { pushedBack = v } + + // Methods on DataInput + def readBoolean(): Boolean = readByte() != 0 + + def readByte(): Byte = { + val res = read() + if (res == -1) eof() + res.toByte + } + + def readChar(): Char = { + if (hasArrayBuffer) + bufDataView.getUint16(consumePos(2)).toChar + else + view(2).getUint16(0).toChar + } + + def readDouble(): Double = { + if (hasArrayBuffer) + bufDataView.getFloat64(consumePos(8)) + else + view(8).getFloat64(0) + } + + def readFloat(): Float = { + if (hasArrayBuffer) + bufDataView.getFloat32(consumePos(4)) + else + view(4).getFloat32(0) + } + + def readFully(b: Array[Byte]): Unit = readFully(b, 0, b.length) + + def readFully(b: Array[Byte], off: Int, len: Int): Unit = { + if (off < 0 || len < 0 || off + len > b.length) + throw new IndexOutOfBoundsException() + + var remaining = len + var offset = off + while (remaining > 0) { + val readCount = read(b, offset, remaining) + if (readCount == -1) eof() + remaining -= readCount + offset += readCount + } + } + + def readInt(): Int = { + if (hasArrayBuffer) + bufDataView.getInt32(consumePos(4)) + else + view(4).getInt32(0) + } + + def readLine(): String = { + var cur = read() + if (cur == -1) null + else { + var res = "" + while (cur != -1 && cur != '\n' && cur != '\r') { + res += cur.toChar + cur = read() + } + if (cur == '\r') { + // Discard a potential \n (from \r\n line endings) + cur = read() + if (cur != '\n') pushBack(cur) + } + res + } + } + + def readLong(): Long = { + val hi = readInt().toLong + val lo = readInt().toLong + (hi << 32) | (lo & 0xFFFFFFFFL) + } + + def readShort(): Short = { + if (hasArrayBuffer) + bufDataView.getInt16(consumePos(2)) + else + view(2).getInt16(0) + } + + def readUnsignedByte(): Int = { + val res = read() + if (res == -1) eof() + res + } + + def readUnsignedShort(): Int = { + if (hasArrayBuffer) + bufDataView.getUint16(consumePos(2)) + else + view(2).getUint16(0) + } + + def readUTF(): String = { + val length = readShort() + var res = "" + var i = 0 + + def badFormat(msg: String) = throw new UTFDataFormatException(msg) + + while (i < length) { + val a = read() + + if (a == -1) + badFormat(s"Unexpected EOF: ${length - i} bytes to go") + + i += 1 + + val char = { + if ((a & 0x80) == 0x00) { // 0xxxxxxx + a.toChar + } else if ((a & 0xE0) == 0xC0 && i < length) { // 110xxxxx + val b = read() + i += 1 + + if (b == -1) + badFormat(f"Expected 2 bytes, found: EOF (init: $a%#02x)") + if ((b & 0xC0) != 0x80) // 10xxxxxx + badFormat(f"Expected 2 bytes, found: $b%#02x (init: $a%#02x)") + + (((a & 0x1F) << 6) | (b & 0x3F)).toChar + } else if ((a & 0xF0) == 0xE0 && i < length - 1) { // 1110xxxx + val b = read() + val c = read() + i += 2 + + if (b == -1) + badFormat(f"Expected 3 bytes, found: EOF (init: $a%#02x)") + + if ((b & 0xC0) != 0x80) // 10xxxxxx + badFormat(f"Expected 3 bytes, found: $b%#02x (init: $a%#02x)") + + if (c == -1) + badFormat(f"Expected 3 bytes, found: $b%#02x, EOF (init: $a%#02x)") + + if ((c & 0xC0) != 0x80) // 10xxxxxx + badFormat( + f"Expected 3 bytes, found: $b%#02x, $c%#02x (init: $a%#02x)") + + (((a & 0x0F) << 12) | ((b & 0x3F) << 6) | (c & 0x3F)).toChar + } else { + val rem = length - i + badFormat(f"Unexpected start of char: $a%#02x ($rem%d bytes to go)") + } + } + + res += char + } + + res + } + + def skipBytes(n: Int): Int = skip(n.toLong).toInt + + // Methods on FilterInputStream. + // Overridden to track pushedBack / pushedBackMark + override def available(): Int = { + if (pushedBack != -1) in.available + 1 + else in.available + } + + override def mark(readlimit: Int): Unit = { + in.mark(readlimit + 1) // we need one more since we might read ahead + pushedBackMark = pushedBack + } + + override def markSupported(): Boolean = in.markSupported() + + override def read(): Int = { + val res = { + if (pushedBack != -1) + pushedBack + else + in.read() + } + + pushedBack = -1 + + res + } + + override def read(b: Array[Byte], off: Int, len: Int): Int = { + if (len == 0) + 0 + else if (pushedBack != -1) { + b(off) = pushedBack.toByte + pushedBack = -1 + 1 + } else { + val count = in.read(b, off, len) + count + } + } + + override def reset(): Unit = { + in.reset() + pushedBack = pushedBackMark + } + + override def skip(n: Long): Long = { + if (n == 0) + 0L + else if (pushedBack != -1) { + pushedBack = -1 + 1L + } else { + val skipped = in.skip(n) + skipped + } + } + +} diff --git a/examples/scala-js/javalib-ex/src/main/scala/java/util/zip/InflaterInputStream.scala b/examples/scala-js/javalib-ex/src/main/scala/java/util/zip/InflaterInputStream.scala new file mode 100644 index 0000000..10aa04e --- /dev/null +++ b/examples/scala-js/javalib-ex/src/main/scala/java/util/zip/InflaterInputStream.scala @@ -0,0 +1,18 @@ +package java.util.zip + +import java.io._ + +class InflaterInputStream(in: InputStream) extends FilterInputStream(in) { + + // Not implemented: + // def this(in: InputStream, inf: Inflater) + // def this(in: InputStream, inf: Inflater, size: Int) + // protected var buf: Array[Byte] + // protected var inf: Inflater + // protected var len: Int + + override def markSupported(): Boolean = false + override def mark(readlimit: Int): Unit = {} + override def reset(): Unit = throw new IOException("Reset not supported") + +} diff --git a/examples/scala-js/javalib-ex/src/main/scala/java/util/zip/ZipEntry.scala b/examples/scala-js/javalib-ex/src/main/scala/java/util/zip/ZipEntry.scala new file mode 100644 index 0000000..89f37a3 --- /dev/null +++ b/examples/scala-js/javalib-ex/src/main/scala/java/util/zip/ZipEntry.scala @@ -0,0 +1,67 @@ +package java.util.zip + +class ZipEntry(private[this] val _name: String) extends Cloneable { + + private[this] var _comment: String = null + private[this] var _csize: Long = -1 + private[this] var _crc: Long = -1 + private[this] var _extra: Array[Byte] = null + private[this] var _method: Int = -1 + private[this] var _size: Long = -1 + private[this] var _time: Long = -1 + + def this(e: ZipEntry) = { + this(e.getName()) + setComment(e.getComment()) + setCompressedSize(e.getCompressedSize()) + setCrc(e.getCrc()) + setExtra(e.getExtra()) + setMethod(e.getMethod()) + setSize(e.getSize()) + setTime(e.getTime()) + } + + override def clone(): Object = { + val result = super.clone() + if (getExtra() != null) + setExtra(getExtra().clone().asInstanceOf[Array[Byte]]) + result + } + + def getComment(): String = _comment + def getCompressedSize(): Long = _csize + def getCrc(): Long = _crc + def getExtra(): Array[Byte] = _extra + def getMethod(): Int = _method + def getName(): String = _name + def getSize(): Long = _size + def getTime(): Long = _time + + // Strangely, the Javalib defines hashCode, but not equals. + override def hashCode(): Int = { + import scala.util.hashing.MurmurHash3._ + + var acc = 0x45322 + acc = mix(acc, getComment.##) + acc = mix(acc, getCompressedSize.##) + acc = mix(acc, getCrc.##) + acc = mix(acc, getExtra.##) + acc = mix(acc, getMethod.##) + acc = mix(acc, getName.##) + acc = mix(acc, getSize.##) + acc = mixLast(acc, getTime.##) + finalizeHash(acc, 8) + } + + def isDirectory(): Boolean = _name.endsWith("/") + + def setComment(comment: String): Unit = { _comment = comment } + def setCompressedSize(csize: Long): Unit = { _csize = csize } + def setCrc(crc: Long): Unit = { _crc = crc } + def setExtra(extra: Array[Byte]): Unit = { _extra = extra } + def setMethod(method: Int): Unit = { _method = method } + def setSize(size: Long): Unit = { _size = size } + def setTime(time: Long): Unit = { _time = time } + override def toString(): String = _name + +} diff --git a/examples/scala-js/javalib-ex/src/main/scala/java/util/zip/ZipInputStream.scala b/examples/scala-js/javalib-ex/src/main/scala/java/util/zip/ZipInputStream.scala new file mode 100644 index 0000000..082f6fc --- /dev/null +++ b/examples/scala-js/javalib-ex/src/main/scala/java/util/zip/ZipInputStream.scala @@ -0,0 +1,87 @@ +package java.util.zip + +import java.io._ + +import scala.scalajs.js +import scala.scalajs.js.typedarray._ + +class ZipInputStream(in: InputStream) extends InflaterInputStream(in) { + + // Not implemented + // - All static constant fields (zip internals) + // - protected def createZipEntry(name: String): ZipEntry + + private[this] val entryIter = { + import js.Dynamic.{global => g} + + val data = in match { + case in: ArrayBufferInputStream => + // Simulate reading all the data + while (in.skip(in.available()) > 0) {} + new Uint8Array(in.buffer, in.offset, in.length) + case _ => + val arr = new js.Array[Int] + var x = in.read() + while (x != -1) { + arr.push(x) + x = in.read() + } + new Uint8Array(arr) + } + + val zip = js.Dynamic.newInstance(g.JSZip)(data) + val entries = zip.files.asInstanceOf[js.Dictionary[js.Dynamic]] + + entries.iterator + } + + private[this] var inner: ArrayBufferInputStream = null + + override def close(): Unit = { + closeEntry() + super.close() + } + + override def available(): Int = { + if (inner == null || inner.available() <= 0) 0 + else 1 + } + + def closeEntry(): Unit = { + if (inner != null) + inner.close() + inner = null + } + + def getNextEntry(): ZipEntry = { + closeEntry() + if (entryIter.hasNext) { + val (name, jsEntry) = entryIter.next() + val res = new ZipEntry(name) + res.setTime(jsEntry.date.asInstanceOf[js.Date].getTime().toLong) + res.setComment(jsEntry.comment.asInstanceOf[String]) + + inner = new ArrayBufferInputStream( + jsEntry.asArrayBuffer().asInstanceOf[ArrayBuffer]) + + res + } else null + } + + override def read(): Int = { + if (inner == null) -1 + else inner.read() + } + + override def read(buf: Array[Byte], off: Int, len: Int): Int = { + if (len == 0) 0 + else if (inner == null) -1 + else inner.read(buf, off, len) + } + + override def skip(n: Long): Long = { + if (inner == null) 0 + else inner.skip(n) + } + +} diff --git a/examples/scala-js/javalib/src/main/scala/java/io/BufferedReader.scala b/examples/scala-js/javalib/src/main/scala/java/io/BufferedReader.scala new file mode 100644 index 0000000..0f06523 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/io/BufferedReader.scala @@ -0,0 +1,145 @@ +package java.io + +class BufferedReader(in: Reader, sz: Int) extends Reader { + + def this(in: Reader) = this(in, 4096) + + private[this] var buf = new Array[Char](sz) + + /** Last valid value in the buffer (exclusive) */ + private[this] var end = 0 + + /** Next position to read from buffer */ + private[this] var pos = 0 + + private[this] var closed = false + + private[this] var validMark = false + + override def close(): Unit = { + closed = true + } + + override def mark(readAheadLimit: Int): Unit = { + ensureOpen() + + val srcBuf = buf + if (buf.size < readAheadLimit) + buf = new Array[Char](readAheadLimit) + + // Move data to beginning of buffer + if (pos != 0 || (buf ne srcBuf)) + System.arraycopy(srcBuf, pos, buf, 0, end - pos) + + // Update internal state + end -= pos + pos = 0 + validMark = true + } + + override def markSupported(): Boolean = true + + override def read(): Int = { + ensureOpen() + + if (prepareRead()) { + val res = buf(pos).toInt + pos += 1 + res + } else -1 + } + + override def read(cbuf: Array[Char], off: Int, len: Int): Int = { + ensureOpen() + + if (off < 0 || len < 0 || len > cbuf.length - off) + throw new IndexOutOfBoundsException + + if (len == 0) 0 + else if (prepareRead()) { + val count = Math.min(len, end - pos) + System.arraycopy(this.buf, pos, cbuf, off, count) + pos += count + count + } else -1 + } + + def readLine(): String = { + ensureOpen() + + var res = "" + + while (prepareRead() && buf(pos) != '\n' && buf(pos) != '\r') { + res += buf(pos) + pos += 1 + } + + if (pos >= end) { + // We have reached the end of the stream (prepareRead() returned false) + if (res == "") null + else res + } else { + // Consume terminator + pos += 1 + + // Check whether we have a \r\n. This may overrun the buffer + // and then push a value back which may unnecessarily invalidate + // the mark. This mimics java behavior + if (buf(pos-1) == '\r' && prepareRead() && buf(pos) == '\n') + pos += 1 // consume '\n' + + res + } + } + + override def ready(): Boolean = { + ensureOpen() + pos < end || in.ready() + } + + override def reset(): Unit = { + ensureOpen() + + if (!validMark) throw new IOException("Mark invalid") + pos = 0 + } + + override def skip(n: Long): Long = { + if (n < 0) throw new IllegalArgumentException("n negative") + else if (pos < end) { + val count = Math.min(n, end - pos).toInt + pos += count + count.toLong + } else { + validMark = false + in.skip(n) + } + } + + /** Prepare the buffer for reading. Returns false if EOF */ + private def prepareRead(): Boolean = + pos < end || fillBuffer() + + /** Tries to fill the buffer. Returns false if EOF */ + private def fillBuffer(): Boolean = { + if (validMark && end < buf.length) { + // we may not do a full re-read, since we'll damage the mark. + val read = in.read(buf, end, buf.length - end) + if (read > 0) // protect from adding -1 + end += read + read > 0 + } else { + // Full re-read + validMark = false + end = in.read(buf) + pos = 0 + end > 0 + } + } + + private def ensureOpen(): Unit = { + if (closed) + throw new IOException("Operation on closed stream") + } + +} diff --git a/examples/scala-js/javalib/src/main/scala/java/io/ByteArrayInputStream.scala b/examples/scala-js/javalib/src/main/scala/java/io/ByteArrayInputStream.scala new file mode 100644 index 0000000..697e07b --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/io/ByteArrayInputStream.scala @@ -0,0 +1,58 @@ +package java.io + +class ByteArrayInputStream( + protected val buf: Array[Byte], + offset: Int, length: Int) extends InputStream { + + protected val count: Int = offset + length + protected var mark: Int = offset + protected var pos: Int = offset + + def this(buf: Array[Byte]) = this(buf, 0, buf.length) + + override def read(): Int = { + if (pos >= count) + -1 + else { + val res = buf(pos) & 0xFF // convert to unsigned int + pos += 1 + res + } + } + + override def read(b: Array[Byte], off: Int, reqLen: Int): Int = { + if (off < 0 || reqLen < 0 || reqLen > b.length - off) + throw new IndexOutOfBoundsException + + val len = Math.min(reqLen, count - pos) + + if (reqLen == 0) + 0 // 0 requested, 0 returned + else if (len == 0) + -1 // nothing to read at all + else { + System.arraycopy(buf, pos, b, off, len) + pos += len + len + } + } + + override def skip(n: Long): Long = { + val k = Math.max(0, Math.min(n, count - pos)) + pos += k.toInt + k.toLong + } + + override def available(): Int = count - pos + + override def markSupported(): Boolean = true + + override def mark(readlimit: Int): Unit = + mark = pos + + override def reset(): Unit = + pos = mark + + override def close(): Unit = () + +} diff --git a/examples/scala-js/javalib/src/main/scala/java/io/ByteArrayOutputStream.scala b/examples/scala-js/javalib/src/main/scala/java/io/ByteArrayOutputStream.scala new file mode 100644 index 0000000..916002d --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/io/ByteArrayOutputStream.scala @@ -0,0 +1,62 @@ +package java.io + +import scala.scalajs.js + +import scala.annotation.tailrec + +class ByteArrayOutputStream(initBufSize: Int) extends OutputStream { + + protected var buf: Array[Byte] = new Array(initBufSize) + protected var count: Int = 0 + + def this() = this(32) + + override def write(b: Int): Unit = { + if (count >= buf.length) + growBuf(1) + + buf(count) = b.toByte + count += 1 + } + + override def write(b: Array[Byte], off: Int, len: Int): Unit = { + if (off < 0 || len < 0 || len > b.length - off) + throw new IndexOutOfBoundsException() + + if (count + len > buf.length) + growBuf(len) + + System.arraycopy(b, off, buf, count, len) + count += len + } + + def writeTo(out: OutputStream): Unit = + out.write(buf, 0, count) + + def reset(): Unit = + count = 0 + + def toByteArray(): Array[Byte] = { + val res = new Array[Byte](count) + System.arraycopy(buf, 0, res, 0, count) + res + } + + def size(): Int = count + + override def toString(): String = + new String(buf, 0, count) + + def toString(charsetName: String): String = + new String(buf, 0, count, charsetName) + + override def close(): Unit = () + + private def growBuf(minIncrement: Int): Unit = { + val newSize = Math.max(count + minIncrement, buf.length * 2) + val newBuf = new Array[Byte](newSize) + System.arraycopy(buf, 0, newBuf, 0, count) + buf = newBuf + } + +} diff --git a/examples/scala-js/javalib/src/main/scala/java/io/Closeable.scala b/examples/scala-js/javalib/src/main/scala/java/io/Closeable.scala new file mode 100644 index 0000000..e572390 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/io/Closeable.scala @@ -0,0 +1,6 @@ +package java.io + +/** Note that Closeable doesn't extend AutoCloseable for Java6 compat */ +trait Closeable { + def close(): Unit +} diff --git a/examples/scala-js/javalib/src/main/scala/java/io/DataInput.scala b/examples/scala-js/javalib/src/main/scala/java/io/DataInput.scala new file mode 100644 index 0000000..37913b4 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/io/DataInput.scala @@ -0,0 +1,19 @@ +package java.io + +trait DataInput { + def readBoolean(): Boolean + def readByte(): Byte + def readChar(): Char + def readDouble(): Double + def readFloat(): Float + def readFully(b: Array[Byte]): Unit + def readFully(b: Array[Byte], off: Int, len: Int): Unit + def readInt(): Int + def readLine(): String + def readLong(): Long + def readShort(): Short + def readUnsignedByte(): Int + def readUnsignedShort(): Int + def readUTF(): String + def skipBytes(n: Int): Int +} diff --git a/examples/scala-js/javalib/src/main/scala/java/io/FilterInputStream.scala b/examples/scala-js/javalib/src/main/scala/java/io/FilterInputStream.scala new file mode 100644 index 0000000..a85b9f6 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/io/FilterInputStream.scala @@ -0,0 +1,24 @@ +package java.io + +class FilterInputStream protected ( + protected val in: InputStream) extends InputStream { + + override def read(): Int = + in.read() + + override def read(b: Array[Byte]): Int = + read(b, 0, b.length) // this is spec! must not do in.read(b) + + override def read(b: Array[Byte], off: Int, len: Int): Int = + in.read(b, off, len) + + override def skip(n: Long): Long = in.skip(n) + + override def available(): Int = in.available() + + override def close(): Unit = in.close() + + override def mark(readlimit: Int): Unit = in.mark(readlimit) + override def markSupported(): Boolean = in.markSupported() + override def reset(): Unit = in.reset() +} diff --git a/examples/scala-js/javalib/src/main/scala/java/io/FilterOutputStream.scala b/examples/scala-js/javalib/src/main/scala/java/io/FilterOutputStream.scala new file mode 100644 index 0000000..299b7b6 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/io/FilterOutputStream.scala @@ -0,0 +1,16 @@ +package java.io + +class FilterOutputStream(protected val out: OutputStream) extends OutputStream { + def write(b: Int): Unit = + out.write(b) + + override def write(b: Array[Byte]): Unit = + write(b, 0, b.length) // this is spec! it must not call out.write(b) + + override def write(b: Array[Byte], off: Int, len: Int): Unit = + super.write(b, off, len) // calls this.write(Int) repeatedly + + override def flush(): Unit = out.flush() + + override def close(): Unit = out.close() +} diff --git a/examples/scala-js/javalib/src/main/scala/java/io/Flushable.scala b/examples/scala-js/javalib/src/main/scala/java/io/Flushable.scala new file mode 100644 index 0000000..2879ad2 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/io/Flushable.scala @@ -0,0 +1,5 @@ +package java.io + +trait Flushable { + def flush(): Unit +} diff --git a/examples/scala-js/javalib/src/main/scala/java/io/InputStream.scala b/examples/scala-js/javalib/src/main/scala/java/io/InputStream.scala new file mode 100644 index 0000000..412d84b --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/io/InputStream.scala @@ -0,0 +1,53 @@ +package java.io + +abstract class InputStream extends Closeable { + def read(): Int + + def read(b: Array[Byte]): Int = read(b, 0, b.length) + + def read(b: Array[Byte], off: Int, len: Int): Int = { + if (off < 0 || len < 0 || len > b.length - off) + throw new IndexOutOfBoundsException + + if (len == 0) 0 + else { + var bytesWritten = 0 + var next = 0 + + while (bytesWritten < len && next != -1) { + next = + if (bytesWritten == 0) read() + else { + try read() + catch { case _: IOException => -1 } + } + if (next != -1) { + b(off + bytesWritten) = next.toByte + bytesWritten += 1 + } + } + + if (bytesWritten <= 0) -1 + else bytesWritten + } + } + + def skip(n: Long): Long = { + var skipped = 0 + while (skipped < n && read() != -1) + skipped += 1 + skipped + } + + def available(): Int = 0 + + def close(): Unit = () + + def mark(readlimit: Int): Unit = () + + def reset(): Unit = + throw new IOException("Reset not supported") + + def markSupported(): Boolean = false + +} diff --git a/examples/scala-js/javalib/src/main/scala/java/io/InputStreamReader.scala b/examples/scala-js/javalib/src/main/scala/java/io/InputStreamReader.scala new file mode 100644 index 0000000..1ef957c --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/io/InputStreamReader.scala @@ -0,0 +1,216 @@ +package java.io + +import scala.annotation.tailrec + +import java.nio._ +import java.nio.charset._ + +class InputStreamReader(private[this] var in: InputStream, + private[this] var decoder: CharsetDecoder) extends Reader { + + private[this] var closed: Boolean = false + + /** Buffer in which to read bytes from the underlying input stream. + * + * Class invariant: contains bytes already read from `in` but not yet + * decoded. + */ + private[this] var inBuf: ByteBuffer = ByteBuffer.allocate(4096) + inBuf.limit(0) + + /** Tells whether the end of the underlying input stream has been reached. + * Class invariant: if true, then `in.read()` has returned -1. + */ + private[this] var endOfInput: Boolean = false + + /** Buffer in which to decode bytes into chars. + * Usually, it is not used, because we try to decode directly to the + * destination array. So as long as we do not really need one, we share + * an empty buffer. + * + * Class invariant: contains chars already decoded but not yet *read* by + * the user of this instance. + */ + private[this] var outBuf: CharBuffer = InputStreamReader.CommonEmptyCharBuffer + + def this(in: InputStream, charset: Charset) = + this(in, + charset.newDecoder + .onMalformedInput(CodingErrorAction.REPLACE) + .onUnmappableCharacter(CodingErrorAction.REPLACE)) + + def this(in: InputStream) = + this(in, Charset.defaultCharset) + + def this(in: InputStream, charsetName: String) = + this(in, Charset.forName(charsetName)) + + def close(): Unit = { + closed = true + in = null + decoder = null + inBuf = null + outBuf = null + } + + def getEncoding(): String = + if (closed) null else decoder.charset.name + + override def read(): Int = { + ensureOpen() + + if (outBuf.hasRemaining) outBuf.get() + else super.read() + } + + def read(cbuf: Array[Char], off: Int, len: Int): Int = { + ensureOpen() + + if (off < 0 || len < 0 || len > cbuf.length - off) + throw new IndexOutOfBoundsException + + if (len == 0) 0 + else if (outBuf.hasRemaining) { + // Reuse chars decoded last time + val available = Math.min(outBuf.remaining, len) + outBuf.get(cbuf, off, available) + available + } else { + // Try and decode directly into the destination array + val directOut = CharBuffer.wrap(cbuf, off, len) + val result = readImpl(directOut) + if (result != InputStreamReader.Overflow) { + result + } else { + /* There's not enough space in the destination array to receive even + * a tiny bit of output from the decoder. We need to decode to the + * outBuf instead. + * This happens typically when the next code point to decode is a + * supplementary character, and the given `len` is 1. + */ + readMoreThroughOutBuf(cbuf, off, len) + } + } + } + + // In a separate method because this is (hopefully) not a common case + private def readMoreThroughOutBuf(cbuf: Array[Char], off: Int, len: Int): Int = { + // Return outBuf to its full capacity + outBuf.limit(outBuf.capacity) + outBuf.position(0) + + @tailrec // but not inline, this is not a common path + def loopWithOutBuf(desiredOutBufSize: Int): Int = { + if (outBuf.capacity < desiredOutBufSize) + outBuf = CharBuffer.allocate(desiredOutBufSize) + val charsRead = readImpl(outBuf) + if (charsRead == InputStreamReader.Overflow) + loopWithOutBuf(desiredOutBufSize*2) + else + charsRead + } + + val charsRead = loopWithOutBuf(2*len) + assert(charsRead != 0) // can be -1, though + outBuf.flip() + + if (charsRead == -1) -1 + else { + val available = Math.min(charsRead, len) + outBuf.get(cbuf, off, available) + available + } + } + + @tailrec + private def readImpl(out: CharBuffer): Int = { + val initPos = out.position + val result = decoder.decode(inBuf, out, endOfInput) + + if (out.position != initPos) { + /* Good, we made progress, so we can return. + * Note that the `result` does not matter. Whether it's an underflow, + * an overflow, or even an error, if we read *something*, we can return + * that. + * The next invocation of read() will cause a new invocation of decode(), + * which will necessarily return the same result (but without advancing + * at all), which will cause one of the following cases to be handled. + */ + out.position - initPos + } else if (result.isUnderflow) { + if (endOfInput) { + assert(!inBuf.hasRemaining, + "CharsetDecoder.decode() should not have returned UNDERFLOW when "+ + "both endOfInput and inBuf.hasRemaining are true. It should have "+ + "returned a MalformedInput error instead.") + // Flush + if (decoder.flush(out).isOverflow) + InputStreamReader.Overflow + else { + // Done + if (out.position == initPos) -1 + else out.position - initPos + } + } else { + // We need to read more from the underlying input stream + if (inBuf.limit == inBuf.capacity) { + inBuf.compact() + if (!inBuf.hasRemaining) { + throw new AssertionError( + "Scala.js implementation restriction: " + + inBuf.capacity + " bytes do not seem to be enough for " + + getEncoding + " to decode a single code point. " + + "Please report this as a bug.") + } + inBuf.limit(inBuf.position) + inBuf.position(0) + } + + /* Note that this stores the new data after the limit of the buffer. + * Further, note that we may read more bytes than strictly necessary, + * according to the specification of InputStreamReader. + */ + val bytesRead = + in.read(inBuf.array, inBuf.limit, inBuf.capacity - inBuf.limit) + + if (bytesRead == -1) + endOfInput = true + else + inBuf.limit(inBuf.limit + bytesRead) + + readImpl(out) + } + } else if (result.isOverflow) { + InputStreamReader.Overflow + } else { + result.throwException() + throw new AssertionError("should not get here") + } + } + + /* In theory, `in.available() > 0` is incorrect. We should return true only + * if there are enough bytes available to read at least one code point. + * However, this is how the JDK behaves, and even the JavaDoc suggests this + * is the expected behavior. + */ + override def ready(): Boolean = + outBuf.hasRemaining || in.available() > 0 + + private def ensureOpen(): Unit = { + if (closed) + throw new IOException("Stream closed") + } + +} + +object InputStreamReader { + private final val Overflow = -2 + + /** Empty CharBuffer shared by all InputStreamReaders as long as they do + * not really need one. + * Since we do not use `mark()`, it is fine to share them, because `mark()` + * is the only piece of mutable state for an empty buffer. Everything else + * is effectively immutable (e.g., position and limit must always be 0). + */ + private val CommonEmptyCharBuffer = CharBuffer.allocate(0) +} diff --git a/examples/scala-js/javalib/src/main/scala/java/io/OutputStream.scala b/examples/scala-js/javalib/src/main/scala/java/io/OutputStream.scala new file mode 100644 index 0000000..729e69b --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/io/OutputStream.scala @@ -0,0 +1,25 @@ +package java.io + +abstract class OutputStream extends Object with Closeable with Flushable { + def write(b: Int): Unit + + def write(b: Array[Byte]): Unit = + write(b, 0, b.length) + + def write(b: Array[Byte], off: Int, len: Int): Unit = { + if (off < 0 || len < 0 || len > b.length - off) + throw new IndexOutOfBoundsException() + + var n = off + val stop = off + len + while (n < stop) { + write(b(n)) + n += 1 + } + } + + def flush(): Unit = () + + def close(): Unit = () + +} diff --git a/examples/scala-js/javalib/src/main/scala/java/io/OutputStreamWriter.scala b/examples/scala-js/javalib/src/main/scala/java/io/OutputStreamWriter.scala new file mode 100644 index 0000000..18c1c57 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/io/OutputStreamWriter.scala @@ -0,0 +1,160 @@ +package java.io + +import scala.annotation.tailrec + +import java.nio._ +import java.nio.charset._ + +class OutputStreamWriter(private[this] var out: OutputStream, + private[this] var enc: CharsetEncoder) extends Writer { + + private[this] var closed: Boolean = false + + /** Incoming buffer: pending Chars that have been written to this instance + * of OutputStreamWriter, but not yet encoded. + * Normally, this should always be at most 1 Char, if it is a high surrogate + * which ended up alone at the end of the input of a write(). + */ + private[this] var inBuf: String = "" + + /** Outgoing buffer: Bytes that have been decoded (from `inBuf`), but not + * yet written to the underlying output stream. + * The valid bytes are between 0 and outBuf.position. + */ + private[this] var outBuf: ByteBuffer = ByteBuffer.allocate(4096) + + def this(out: OutputStream, cs: Charset) = + this(out, + cs.newEncoder + .onMalformedInput(CodingErrorAction.REPLACE) + .onUnmappableCharacter(CodingErrorAction.REPLACE)) + + def this(out: OutputStream) = + this(out, Charset.defaultCharset) + + def this(out: OutputStream, charsetName: String) = + this(out, Charset.forName(charsetName)) + + def getEncoding(): String = + if (closed) null else enc.charset.name + + override def write(c: Int): Unit = + write(c.toChar.toString, 0, 1) + + override def write(cbuf: Array[Char], off: Int, len: Int): Unit = + writeImpl(CharBuffer.wrap(cbuf, off, len)) + + override def write(str: String, off: Int, len: Int): Unit = + writeImpl(CharBuffer.wrap(str, off, len)) + + private def writeImpl(cbuf: CharBuffer): Unit = { + ensureOpen() + + val cbuf1 = if (inBuf != "") { + val fullInput = CharBuffer.wrap(inBuf + cbuf.toString) + inBuf = "" + fullInput + } else cbuf + + @inline + @tailrec + def loopEncode(): Unit = { + val result = enc.encode(cbuf1, outBuf, false) + if (result.isUnderflow) () + else if (result.isOverflow) { + makeRoomInOutBuf() + loopEncode() + } else { + result.throwException() + throw new AssertionError("should not get here") + } + } + + loopEncode() + if (cbuf1.hasRemaining) + inBuf = cbuf1.toString + } + + override def flush(): Unit = { + ensureOpen() + flushBuffer() + out.flush() + } + + override def close(): Unit = if (!closed) { + // Finish up the input + @inline + @tailrec + def loopEncode(): Unit = { + val cbuf = CharBuffer.wrap(inBuf) + val result = enc.encode(cbuf, outBuf, true) + if (result.isUnderflow) { + assert(!cbuf.hasRemaining, + "CharsetEncoder.encode() should not have returned UNDERFLOW when "+ + "both endOfInput and inBuf.hasRemaining are true. It should have "+ + "returned a MalformedInput error instead.") + } else if (result.isOverflow) { + makeRoomInOutBuf() + loopEncode() + } else { + result.throwException() + throw new AssertionError("should not get here") + } + } + + @inline + @tailrec + def loopFlush(): Unit = { + if (enc.flush(outBuf).isOverflow) { + makeRoomInOutBuf() + loopFlush() + } + } + + loopEncode() + loopFlush() + + // Flush before closing + flush() + + // Close the underlying stream + out.close() + + // Clean up all the resources + closed = true + out = null + enc = null + inBuf = null + outBuf = null + } + + private def ensureOpen(): Unit = { + if (closed) + throw new IOException("Closed writer.") + } + + private def makeRoomInOutBuf(): Unit = { + if (outBuf.position != 0) { + flushBuffer() + } else { + // Very unlikely (outBuf.capacity is not enough to encode a single code point) + outBuf.flip() + val newBuf = ByteBuffer.allocate(outBuf.capacity * 2) + newBuf.put(outBuf) + outBuf = newBuf + } + } + + /** Flushes the internal buffer of this writer, but not the underlying + * output stream. + */ + private[io] def flushBuffer(): Unit = { + ensureOpen() + + // Don't use outBuf.flip() first, in case out.write() throws + // Hence, use 0 instead of position, and position instead of limit + out.write(outBuf.array, outBuf.arrayOffset, outBuf.position) + outBuf.clear() + } + +} diff --git a/examples/scala-js/javalib/src/main/scala/java/io/PrintStream.scala b/examples/scala-js/javalib/src/main/scala/java/io/PrintStream.scala new file mode 100644 index 0000000..68fa041 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/io/PrintStream.scala @@ -0,0 +1,218 @@ +package java.io + +import java.nio.charset.Charset +import java.util.Formatter + +class PrintStream private (_out: OutputStream, autoFlush: Boolean, + charset: Charset) + extends FilterOutputStream(_out) with Appendable with Closeable { + + /* The way we handle charsets here is a bit tricky, because we want to + * minimize the area of reachability for normal programs. + * + * First, if nobody uses the constructor taking an explicit encoding, we + * don't want to reach Charset.forName(), which pulls in all of the + * implemented charsets. + * + * Second, most programs will reach PrintStream only because of + * java.lang.System.{out,err}, which are subclasses of PrintStream that do + * not actually need to encode anything: they override all of PrintStream's + * methods to bypass the encoding altogether, and hence don't even need + * the default charset. + * + * This is why we have: + * * A private constructor taking the Charset directly, instead of its name. + * * Which is allowed to be `null`, which stands for the default charset. + * * The default charset is only loaded lazily in the initializer of the + * encoder field. + */ + + def this(out: OutputStream) = + this(out, false, null: Charset) + + def this(out: OutputStream, autoFlush: Boolean) = + this(out, autoFlush, null: Charset) + + def this(out: OutputStream, autoFlush: Boolean, encoding: String) = + this(out, autoFlush, Charset.forName(encoding)) + + /* The following constructors, although implemented, will not link, since + * File, FileOutputStream and BufferedOutputStream are not implemented. + * They're here just in case a third-party library on the classpath + * implements those. + */ + def this(file: File) = + this(new BufferedOutputStream(new FileOutputStream(file))) + def this(file: File, csn: String) = + this(new BufferedOutputStream(new FileOutputStream(file)), false, csn) + def this(fileName: String) = + this(new File(fileName)) + def this(fileName: String, csn: String) = + this(new File(fileName), csn) + + private lazy val encoder = { + val c = + if (charset == null) Charset.defaultCharset + else charset + /* We pass `this` as the output stream for the encoding writer so that + * we can apply auto-flushing. Note that this will flush() more often + * than required by the spec. It appears to be consistent with how the + * JDK behaves. + */ + new OutputStreamWriter(this, c) + } + + private var closing: Boolean = false + private var closed: Boolean = false + private var errorFlag: Boolean = false + + override def flush(): Unit = + ensureOpenAndTrapIOExceptions(out.flush()) + + override def close(): Unit = trapIOExceptions { + if (!closing) { + closing = true + encoder.close() + flush() + closed = true + out.close() + } + } + + def checkError(): Boolean = { + if (closed) { + /* Just check the error flag. + * Common sense would tell us to look at the underlying writer's + * checkError() result too (like we do in the not closed case below). + * But the JDK does not behave like that. So we don't either. + */ + errorFlag + } else { + flush() + /* If the underlying writer is also a PrintStream, we also check its + * checkError() result. This is not clearly specified by the JavaDoc, + * but, experimentally, the JDK seems to behave that way. + */ + errorFlag || (out match { + case out: PrintStream => out.checkError() + case _ => false + }) + } + } + + protected[io] def setError(): Unit = errorFlag = true + protected[io] def clearError(): Unit = errorFlag = false + + /* Note that calling directly the write() methods will happily bypass the + * potential lone high surrogate that is buffered in the underlying + * OutputStreamWriter. This means that the following sequence of operations: + * + * ps.print('\ud83d') // high surrogate of PILE OF POO + * ps.write('a') + * ps.print('\udca9') // low surrogate of PILE OF POO + * + * will result in the following bytes being emitted to the underlying stream: + * + * a\ud83d\udca9 + * + * i.e., first the 'a', then the PILE OF POO. + * + * This is consistent with the behavior of the JDK. + */ + + override def write(b: Int): Unit = { + ensureOpenAndTrapIOExceptions { + out.write(b) + if (autoFlush && b == '\n') + flush() + } + } + + override def write(buf: Array[Byte], off: Int, len: Int): Unit = { + ensureOpenAndTrapIOExceptions { + out.write(buf, off, len) + if (autoFlush) + flush() + } + } + + def print(b: Boolean): Unit = printString(String.valueOf(b)) + def print(c: Char): Unit = printString(String.valueOf(c)) + def print(i: Int): Unit = printString(String.valueOf(i)) + def print(l: Long): Unit = printString(String.valueOf(l)) + def print(f: Float): Unit = printString(String.valueOf(f)) + def print(d: Double): Unit = printString(String.valueOf(d)) + def print(s: String): Unit = printString(if (s == null) "null" else s) + def print(obj: AnyRef): Unit = printString(String.valueOf(obj)) + + private def printString(s: String): Unit = ensureOpenAndTrapIOExceptions { + encoder.write(s) + encoder.flushBuffer() + } + + def print(s: Array[Char]): Unit = ensureOpenAndTrapIOExceptions { + encoder.write(s) + encoder.flushBuffer() + } + + def println(): Unit = ensureOpenAndTrapIOExceptions { + encoder.write('\n') // In Scala.js the line separator is always LF + encoder.flushBuffer() + if (autoFlush) + flush() + } + + def println(b: Boolean): Unit = { print(b); println() } + def println(c: Char): Unit = { print(c); println() } + def println(i: Int): Unit = { print(i); println() } + def println(l: Long): Unit = { print(l); println() } + def println(f: Float): Unit = { print(f); println() } + def println(d: Double): Unit = { print(d); println() } + def println(s: Array[Char]): Unit = { print(s); println() } + def println(s: String): Unit = { print(s); println() } + def println(obj: AnyRef): Unit = { print(obj); println() } + + def printf(fmt: String, args: Array[Object]): PrintStream = + format(fmt, args) + + // Not implemented: + //def printf(l: java.util.Locale, fmt: String, args: Array[Object]): PrintStream = ??? + + def format(fmt: String, args: Array[Object]): PrintStream = { + new Formatter(this).format(fmt, args) + this + } + + // Not implemented: + //def format(l: java.util.Locale, fmt: String, args: Array[Object]): PrintStream = ??? + + def append(csq: CharSequence): PrintStream = { + print(if (csq == null) "null" else csq.toString) + this + } + + def append(csq: CharSequence, start: Int, end: Int): PrintStream = { + val csq1 = if (csq == null) "null" else csq + print(csq1.subSequence(start, end).toString) + this + } + + def append(c: Char): PrintStream = { + print(c) + this + } + + @inline private[this] def trapIOExceptions(body: => Unit): Unit = { + try { + body + } catch { + case _: IOException => setError() + } + } + + @inline private[this] def ensureOpenAndTrapIOExceptions(body: => Unit): Unit = { + if (closed) setError() + else trapIOExceptions(body) + } + +} diff --git a/examples/scala-js/javalib/src/main/scala/java/io/PrintWriter.scala b/examples/scala-js/javalib/src/main/scala/java/io/PrintWriter.scala new file mode 100644 index 0000000..4e693e0 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/io/PrintWriter.scala @@ -0,0 +1,150 @@ +package java.io + +import java.util.Formatter + +class PrintWriter(protected[io] var out: Writer, + autoFlush: Boolean) extends Writer { + + def this(out: Writer) = this(out, false) + + def this(out: OutputStream, autoFlush: Boolean) = + this(new OutputStreamWriter(out), autoFlush) + def this(out: OutputStream) = + this(out, false) + + /* The following constructors, although implemented, will not link, since + * File, FileOutputStream and BufferedOutputStream are not implemented. + * They're here just in case a third-party library on the classpath + * implements those. + */ + def this(file: File) = + this(new BufferedOutputStream(new FileOutputStream(file))) + def this(file: File, csn: String) = + this(new OutputStreamWriter(new BufferedOutputStream( + new FileOutputStream(file)), csn)) + def this(fileName: String) = this(new File(fileName)) + def this(fileName: String, csn: String) = this(new File(fileName), csn) + + private var closed: Boolean = false + private var errorFlag: Boolean = false + + def flush(): Unit = + ensureOpenAndTrapIOExceptions(out.flush()) + + def close(): Unit = trapIOExceptions { + if (!closed) { + flush() + closed = true + out.close() + } + } + + def checkError(): Boolean = { + if (closed) { + /* Just check the error flag. + * Common sense would tell us to look at the underlying writer's + * checkError() result too (like we do in the not closed case below). + * But the JDK does not behave like that. So we don't either. + */ + errorFlag + } else { + flush() + /* If the underlying writer is also a PrintWriter, we also check its + * checkError() result. This is not clearly specified by the JavaDoc, + * but, experimentally, the JDK seems to behave that way. + */ + errorFlag || (out match { + case out: PrintWriter => out.checkError() + case _ => false + }) + } + } + + protected[io] def setError(): Unit = errorFlag = true + protected[io] def clearError(): Unit = errorFlag = false + + override def write(c: Int): Unit = + ensureOpenAndTrapIOExceptions(out.write(c)) + + override def write(buf: Array[Char], off: Int, len: Int): Unit = + ensureOpenAndTrapIOExceptions(out.write(buf, off, len)) + + override def write(buf: Array[Char]): Unit = + ensureOpenAndTrapIOExceptions(out.write(buf)) + + override def write(s: String, off: Int, len: Int): Unit = + ensureOpenAndTrapIOExceptions(out.write(s, off, len)) + + override def write(s: String): Unit = + ensureOpenAndTrapIOExceptions(out.write(s)) + + def print(b: Boolean): Unit = write(String.valueOf(b)) + def print(c: Char): Unit = write(c) + def print(i: Int): Unit = write(String.valueOf(i)) + def print(l: Long): Unit = write(String.valueOf(l)) + def print(f: Float): Unit = write(String.valueOf(f)) + def print(d: Double): Unit = write(String.valueOf(d)) + def print(s: Array[Char]): Unit = write(s) + def print(s: String): Unit = write(if (s == null) "null" else s) + def print(obj: AnyRef): Unit = write(String.valueOf(obj)) + + def println(): Unit = { + write('\n') // In Scala.js the line separator is always LF + if (autoFlush) + flush() + } + + def println(b: Boolean): Unit = { print(b); println() } + def println(c: Char): Unit = { print(c); println() } + def println(i: Int): Unit = { print(i); println() } + def println(l: Long): Unit = { print(l); println() } + def println(f: Float): Unit = { print(f); println() } + def println(d: Double): Unit = { print(d); println() } + def println(s: Array[Char]): Unit = { print(s); println() } + def println(s: String): Unit = { print(s); println() } + def println(obj: AnyRef): Unit = { print(obj); println() } + + def printf(fmt: String, args: Array[Object]): PrintWriter = + format(fmt, args) + + // Not implemented: + //def printf(l: java.util.Locale, fmt: String, args: Array[Object]): PrintWriter = ??? + + def format(fmt: String, args: Array[Object]): PrintWriter = { + new Formatter(this).format(fmt, args) + if (autoFlush) + flush() + this + } + + // Not implemented: + //def format(l: java.util.Locale, fmt: String, args: Array[Object]): PrintWriter = ??? + + override def append(csq: CharSequence): PrintWriter = { + super.append(csq) + this + } + + override def append(csq: CharSequence, start: Int, end: Int): PrintWriter = { + super.append(csq, start, end) + this + } + + override def append(c: Char): PrintWriter = { + super.append(c) + this + } + + @inline private[this] def trapIOExceptions(body: => Unit): Unit = { + try { + body + } catch { + case _: IOException => setError() + } + } + + @inline private[this] def ensureOpenAndTrapIOExceptions(body: => Unit): Unit = { + if (closed) setError() + else trapIOExceptions(body) + } +} diff --git a/examples/scala-js/javalib/src/main/scala/java/io/Reader.scala b/examples/scala-js/javalib/src/main/scala/java/io/Reader.scala new file mode 100644 index 0000000..97be140 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/io/Reader.scala @@ -0,0 +1,60 @@ +package java.io + +import java.nio.CharBuffer + +abstract class Reader private[this] (_lock: Option[Object]) + extends Readable with Closeable { + + protected val lock = _lock.getOrElse(this) + + protected def this(lock: Object) = this(Some(lock)) + protected def this() = this(None) + + def read(target: CharBuffer): Int = { + if (!target.hasRemaining) 0 + else if (target.hasArray) { + val charsRead = read(target.array, + target.position + target.arrayOffset, target.remaining) + if (charsRead != -1) + target.position(target.position + charsRead) + charsRead + } else { + val buf = new Array[Char](target.remaining) + val charsRead = read(buf) + if (charsRead != -1) + target.put(buf, 0, charsRead) + charsRead + } + } + + def read(): Int = { + val buf = new Array[Char](1) + if (read(buf) == -1) -1 + else buf(0).toInt + } + + def read(cbuf: Array[Char]): Int = + read(cbuf, 0, cbuf.length) + + def read(cbuf: Array[Char], off: Int, len: Int): Int + + def skip(n: Long): Long = { + if (n < 0) + throw new IllegalArgumentException("Cannot skip negative amount") + else if (read() == -1) 0 + else 1 + } + + def ready(): Boolean = false + + def markSupported(): Boolean = false + + def mark(readAheadLimit: Int): Unit = + throw new IOException("Mark not supported") + + def reset(): Unit = + throw new IOException("Reset not supported") + + def close(): Unit + +} diff --git a/examples/scala-js/javalib/src/main/scala/java/io/Serializable.scala b/examples/scala-js/javalib/src/main/scala/java/io/Serializable.scala new file mode 100644 index 0000000..01dd228 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/io/Serializable.scala @@ -0,0 +1,3 @@ +package java.io + +trait Serializable diff --git a/examples/scala-js/javalib/src/main/scala/java/io/StringReader.scala b/examples/scala-js/javalib/src/main/scala/java/io/StringReader.scala new file mode 100644 index 0000000..2ca8f90 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/io/StringReader.scala @@ -0,0 +1,69 @@ +package java.io + +class StringReader(s: String) extends Reader { + + private[this] var closed = false + private[this] var pos = 0 + private[this] var mark = 0 + + override def close(): Unit = { + closed = true + } + + override def mark(readAheadLimit: Int): Unit = { + ensureOpen() + + mark = pos + } + + override def markSupported(): Boolean = true + + override def read(): Int = { + ensureOpen() + + if (pos < s.length) { + val res = s.charAt(pos).toInt + pos += 1 + res + } else -1 + } + + override def read(cbuf: Array[Char], off: Int, len: Int): Int = { + ensureOpen() + + if (off < 0 || len < 0 || len > cbuf.length - off) + throw new IndexOutOfBoundsException + + if (len == 0) 0 + else { + val count = Math.min(len, s.length - pos) + var i = 0 + while (i < count) { + cbuf(off + i) = s.charAt(pos + i) + i += 1 + } + pos += count + count + } + } + + override def ready(): Boolean = pos < s.length + + override def reset(): Unit = { + ensureOpen() + pos = mark + } + + override def skip(ns: Long): Long = { + // Apparently, StringReader.skip allows negative skips + val count = Math.max(Math.min(ns, s.length - pos).toInt, -pos) + pos += count + count.toLong + } + + private def ensureOpen(): Unit = { + if (closed) + throw new IOException("Operation on closed stream") + } + +} diff --git a/examples/scala-js/javalib/src/main/scala/java/io/StringWriter.scala b/examples/scala-js/javalib/src/main/scala/java/io/StringWriter.scala new file mode 100644 index 0000000..13eca00 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/io/StringWriter.scala @@ -0,0 +1,44 @@ +package java.io + +class StringWriter extends Writer { + + private[this] val buf = new StringBuffer + + def this(initialSize: Int) = this() + + override def write(c: Int): Unit = + buf.append(c.toChar) + + def write(cbuf: Array[Char], off: Int, len: Int): Unit = + buf.append(cbuf, off, len) + + override def write(str: String): Unit = + buf.append(str) + + override def write(str: String, off: Int, len: Int): Unit = + buf.append(str, off, off + len) // Third param is 'end', not 'len' + + override def append(csq: CharSequence): StringWriter = { + buf.append(csq) + this + } + + override def append(csq: CharSequence, start: Int, end: Int): StringWriter = { + buf.append(csq, start, end) + this + } + + override def append(c: Char): StringWriter = { + buf.append(c) + this + } + + override def toString(): String = buf.toString + + def getBuffer(): StringBuffer = buf + + def flush(): Unit = () + + def close(): Unit = () + +} diff --git a/examples/scala-js/javalib/src/main/scala/java/io/Throwables.scala b/examples/scala-js/javalib/src/main/scala/java/io/Throwables.scala new file mode 100644 index 0000000..c312c4c --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/io/Throwables.scala @@ -0,0 +1,19 @@ +package java.io + +class IOException(s: String, e: Throwable) extends Exception(s, e) { + def this(e: Throwable) = this(null, e) + def this(s: String) = this(s, null) + def this() = this(null, null) +} + +class EOFException(s: String) extends IOException(s) { + def this() = this(null) +} + +class UTFDataFormatException(s: String) extends IOException(s) { + def this() = this(null) +} + +class UnsupportedEncodingException(s: String) extends IOException(s) { + def this() = this(null) +} diff --git a/examples/scala-js/javalib/src/main/scala/java/io/Writer.scala b/examples/scala-js/javalib/src/main/scala/java/io/Writer.scala new file mode 100644 index 0000000..d63b477 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/io/Writer.scala @@ -0,0 +1,45 @@ +package java.io + +abstract class Writer private[this] (_lock: Option[Object]) extends + Appendable with Closeable with Flushable { + + protected val lock = _lock.getOrElse(this) + + protected def this(lock: Object) = this(Some(lock)) + protected def this() = this(None) + + def write(c: Int): Unit = + write(Array(c.toChar)) + + def write(cbuf: Array[Char]): Unit = + write(cbuf, 0, cbuf.length) + + def write(cbuf: Array[Char], off: Int, len: Int): Unit + + def write(str: String): Unit = + write(str.toCharArray) + + def write(str: String, off: Int, len: Int): Unit = + write(str.toCharArray, off, len) + + def append(csq: CharSequence): Writer = { + write(if (csq == null) "null" else csq.toString) + this + } + + def append(csq: CharSequence, start: Int, end: Int): Writer = { + val csq1 = if (csq == null) "null" else csq + write(csq1.subSequence(start, end).toString) + this + } + + def append(c: Char): Writer = { + write(c.toInt) + this + } + + def flush(): Unit + + def close(): Unit + +} diff --git a/examples/scala-js/javalib/src/main/scala/java/net/URI.scala b/examples/scala-js/javalib/src/main/scala/java/net/URI.scala new file mode 100644 index 0000000..c969f55 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/net/URI.scala @@ -0,0 +1,706 @@ +package java.net + +import scala.scalajs.js.RegExp +import scala.scalajs.js +import scala.scalajs.js.{decodeURIComponent => decode} + +import scala.annotation.tailrec + +final class URI(origStr: String) extends Serializable with Comparable[URI] { + + import URI.Fields._ + + /** The fields matched in the regular expression. + * + * This is a local val for the primary constructor. It is a val, + * since we'll set it to null after initializing all fields. + */ + private[this] var _fld = Option(URI.uriRe.exec(origStr)).getOrElse { + throw new URISyntaxException(origStr, "Malformed URI") + } + + private val _isAbsolute = fld(AbsScheme).isDefined + private val _isOpaque = fld(AbsOpaquePart).isDefined + + @inline private def fld(idx: Int): js.UndefOr[String] = _fld(idx) + + @inline private def fld(absIdx: Int, relIdx: Int): js.UndefOr[String] = + if (_isAbsolute) _fld(absIdx) else _fld(relIdx) + + private val _scheme = fld(AbsScheme) + + private val _schemeSpecificPart = { + if (!_isAbsolute) fld(RelSchemeSpecificPart) + else if (_isOpaque) fld(AbsOpaquePart) + else fld(AbsHierPart) + }.get + + private val _authority = fld(AbsAuthority, RelAuthority) + private val _userInfo = fld(AbsUserInfo, RelUserInfo) + private val _host = fld(AbsHost, RelHost) + private val _port = fld(AbsPort, RelPort).fold(-1)(_.toInt) + + private val _path = { + if (_isAbsolute) { + if (_authority.isDefined) fld(AbsNetPath) + else fld(AbsAbsPath) + } else { + if (_authority.isDefined) fld(RelNetPath) + else fld(RelAbsPath) orElse fld(RelRelPath) + } + } + + private val _query = fld(AbsQuery, RelQuery) + private val _fragment = fld(Fragment) + + // End of default ctor. Unset helper field + _fld = null + + def this(scheme: String, ssp: String, fragment: String) = + this(URI.uriStr(scheme, ssp, fragment)) + + def this(scheme: String, userInfo: String, host: String, port: Int, + path: String, query: String, fragment: String) = { + this(URI.uriStr(scheme, userInfo, host, port, path, query, fragment)) + parseServerAuthority() + } + + def this(scheme: String, host: String, path: String, fragment: String) = + this(scheme, null, host, -1, path, null, fragment) + + def this(scheme: String, authority: String, path: String, query: String, + fragment: String) = { + this(URI.uriStr(scheme, authority, path, query, fragment)) + // JavaDoc says to invoke parseServerAuthority() here, but in practice + // it isn't invoked. This makes sense, since you want to be able + // to create URIs with registry-based authorities. + // parseServerAuthority() + } + + /** Compare this URI to another URI while supplying a comparator + * + * This helper is required to account for the semantic differences + * between [[compareTo]] and [[equals]]. ([[equals]] does treat + * URI escapes specially: they are never case-sensitive). + */ + @inline + private def internalCompare(that: URI)(cmp: (String, String) => Int): Int = { + @inline def cmpOpt(x: js.UndefOr[String], y: js.UndefOr[String]): Int = { + if (x == y) 0 + // Undefined components are considered less than defined components + else x.fold(-1)(s1 => y.fold(1)(s2 => cmp(s1, s2))) + } + + if (this._scheme != that._scheme) + this._scheme.fold(-1)(s1 => that._scheme.fold(1)(s1.compareToIgnoreCase)) + else if (this._isOpaque != that._isOpaque) + // A hierarchical URI is less than an opaque URI + if (this._isOpaque) 1 else -1 + else if (_isOpaque) { + val ssp = cmp(this._schemeSpecificPart, that._schemeSpecificPart) + if (ssp != 0) ssp + else cmpOpt(this._fragment, that._fragment) + } else if (this._authority != that._authority) { + if (this._host.isDefined && that._host.isDefined) { + val ui = cmpOpt(this._userInfo, that._userInfo) + if (ui != 0) ui + else { + val hst = this._host.get.compareToIgnoreCase(that._host.get) + if (hst != 0) hst + else if (this._port == that._port) 0 + else if (this._port == -1) -1 + else if (that._port == -1) 1 + else this._port - that._port + } + } else + cmpOpt(this._authority, that._authority) + } else if (this._path != that._path) + cmpOpt(this._path, that._path) + else if (this._query != that._query) + cmpOpt(this._query, that._query) + else + cmpOpt(this._fragment, that._fragment) + } + + def compareTo(that: URI): Int = internalCompare(that)(_.compareTo(_)) + + override def equals(that: Any): Boolean = that match { + case that: URI => internalCompare(that)(URI.escapeAwareCompare) == 0 + case _ => false + } + + def getAuthority(): String = _authority.map(decode).orNull + def getFragment(): String = _fragment.map(decode).orNull + def getHost(): String = _host.orNull + def getPath(): String = _path.map(decode).orNull + def getPort(): Int = _port + def getQuery(): String = _query.map(decode).orNull + def getRawAuthority(): String = _authority.orNull + def getRawFragment(): String = _fragment.orNull + def getRawPath(): String = _path.orNull + def getRawQuery(): String = _query.orNull + def getRawSchemeSpecificPart(): String = _schemeSpecificPart + def getRawUserInfo(): String = _userInfo.orNull + def getScheme(): String = _scheme.orNull + def getSchemeSpecificPart(): String = decode(_schemeSpecificPart) + def getUserInfo(): String = _userInfo.map(decode).orNull + + override def hashCode(): Int = { + import scala.util.hashing.MurmurHash3._ + import URI.normalizeEscapes + + var acc = URI.uriSeed + acc = mix(acc, _scheme.##) // scheme may not contain escapes + acc = mix(acc, normalizeEscapes(_schemeSpecificPart).##) + acc = mixLast(acc, _fragment.map(normalizeEscapes).##) + + finalizeHash(acc, 3) + } + + def isAbsolute(): Boolean = _isAbsolute + def isOpaque(): Boolean = _isOpaque + + def normalize(): URI = if (_isOpaque || _path.isEmpty) this else { + val origPath = _path.get + + // Step 1: Remove all "." segments + // Step 2: Remove ".." segments preceeded by non ".." segment until no + // longer applicable + + /** Checks whether a successive ".." may drop the head of a + * reversed segment list. + */ + def okToDropFrom(resRev: List[String]) = + resRev.nonEmpty && resRev.head != ".." && resRev.head != "" + + @tailrec + def loop(in: List[String], resRev: List[String]): List[String] = in match { + case "." :: Nil => + // convert "." segments at end to an empty segment + // (consider: /a/b/. => /a/b/, not /a/b) + loop(Nil, "" :: resRev) + case ".." :: Nil if okToDropFrom(resRev) => + // prevent a ".." segment at end to change a "dir" into a "file" + // (consider: /a/b/.. => /a/, not /a) + loop(Nil, "" :: resRev.tail) + case "." :: xs => + // remove "." segments + loop(xs, resRev) + case "" :: xs if xs.nonEmpty => + // remove empty segments not at end of path + loop(xs, resRev) + case ".." :: xs if okToDropFrom(resRev) => + // Remove preceeding non-".." segment + loop(xs, resRev.tail) + case x :: xs => + loop(xs, x :: resRev) + case Nil => + resRev.reverse + } + + // Split into segments. -1 since we want empty trailing ones + val segments0 = origPath.split("/", -1).toList + val isAbsPath = segments0.nonEmpty && segments0.head == "" + // Don't inject first empty segment into normalization loop, so we + // won't need to special case it. + val segments1 = if (isAbsPath) segments0.tail else segments0 + val segments2 = loop(segments1, Nil) + + // Step 3: If path is relative and first segment contains ":", prepend "." + // segment (according to JavaDoc). If it is absolute, add empty + // segment again to have leading "/". + val segments3 = { + if (isAbsPath) + "" :: segments2 + else if (segments2.nonEmpty && segments2.head.contains(':')) + "." :: segments2 + else segments2 + } + + val newPath = segments3.mkString("/") + + // Only create new instance if anything changed + if (newPath == origPath) + this + else + new URI(getScheme(), getRawAuthority(), newPath, getQuery(), getFragment()) + } + + def parseServerAuthority(): URI = { + if (_authority.nonEmpty && _host.isEmpty) + throw new URISyntaxException(origStr, "No Host in URI") + else this + } + + def relativize(uri: URI): URI = { + def authoritiesEqual = this._authority.fold(uri._authority.isEmpty) { a1 => + uri._authority.fold(false)(a2 => URI.escapeAwareCompare(a1, a2) == 0) + } + + if (this.isOpaque || uri.isOpaque || + this._scheme != uri._scheme || !authoritiesEqual) uri + else { + val thisN = this.normalize() + val uriN = uri.normalize() + + // Strangely, Java doesn't handle escapes here. So we don't + if (uriN.getRawPath().startsWith(thisN.getRawPath())) { + val newPath = uriN.getRawPath().stripPrefix(thisN.getRawPath()) + + new URI(scheme = null, authority = null, + // never produce an abs path if we relativized + path = newPath.stripPrefix("/"), + query = uri.getQuery(), fragment = uri.getFragment()) + } else uri + } + } + + def resolve(str: String): URI = resolve(URI.create(str)) + + def resolve(uri: URI): URI = { + if (uri.isAbsolute() || this.isOpaque()) uri + else if (uri._scheme.isEmpty && uri._authority.isEmpty && + uri._path.get == "" && uri._query.isEmpty) + // This is a special case for URIs like: "#foo". This allows to + // just change the fragment in the current document. + new URI( + this.getScheme(), + this.getRawAuthority(), + this.getRawPath(), + this.getRawQuery(), + uri.getRawFragment()) + else if (uri._authority.isDefined) + new URI( + this.getScheme(), + uri.getRawAuthority(), + uri.getRawPath(), + uri.getRawQuery(), + uri.getRawFragment()) + else if (uri._path.get.startsWith("/")) + new URI( + this.getScheme(), + this.getRawAuthority(), + uri.getRawPath(), + uri.getRawQuery(), + uri.getRawFragment()) + else { + val basePath = this._path.get + val relPath = uri._path.get + val endIdx = basePath.lastIndexOf('/') + val path = + if (endIdx == -1) relPath + else basePath.substring(0, endIdx+1) + relPath + new URI( + this.getScheme(), + this.getAuthority(), + path, + uri.getRawQuery(), + uri.getRawFragment()).normalize() + } + } + + def toASCIIString(): String = origStr // We allow only ASCII in URIs. + override def toString(): String = origStr + + // Not implemented: + // def toURL(): URL + +} + +object URI { + + def create(str: String): URI = { + try new URI(str) + catch { + case e: URISyntaxException => throw new IllegalArgumentException(e) + } + } + + // IPv4address = 1*digit "." 1*digit "." 1*digit "." 1*digit + private final val ipv4address = "[0-9]{1,3}(?:\\.[0-9]{1,3}){3}" + + private final val ipv6address = { + // http://stackoverflow.com/a/17871737/1149944 + val block = "[0-9a-f]{1,4}" + val lelem = "(?:"+block+":)" + val relem = "(?::"+block+")" + val ipv4 = ipv4address + + "(?:" + + lelem+"{7}"+block+"|"+ // 1:2:3:4:5:6:7:8 + lelem+"{1,7}:|"+ // 1:: 1:2:3:4:5:6:7:: + lelem+"{1,6}"+relem+"|"+ // 1::8 1:2:3:4:5:6::8 1:2:3:4:5:6::8 + lelem+"{1,5}"+relem+"{1,2}|"+ // 1::7:8 1:2:3:4:5::7:8 1:2:3:4:5::8 + lelem+"{1,4}"+relem+"{1,3}|"+ // 1::6:7:8 1:2:3:4::6:7:8 1:2:3:4::8 + lelem+"{1,3}"+relem+"{1,4}|"+ // 1::5:6:7:8 1:2:3::5:6:7:8 1:2:3::8 + lelem+"{1,2}"+relem+"{1,5}|"+ // 1::4:5:6:7:8 1:2::4:5:6:7:8 1:2::8 + lelem +relem+"{1,6}|"+ // 1::3:4:5:6:7:8 1::3:4:5:6:7:8 1::8 + ":(?:"+relem+"{1,7}|:)|" + // ::2:3:4:5:6:7:8 ::2:3:4:5:6:7:8 ::8 :: + lelem+"{6}"+ipv4+"|"+ // 1:2:3:4:5:6:10.0.0.1 + lelem+"{1,5}:"+ipv4+"|"+ // 1::10.0.0.1 1:2:3:4:5::10.0.0.1 + lelem+"{1,4}"+relem+":"+ipv4+"|"+ // 1::6:10.0.0.1 1:2:3:4::6:10.0.0.1 + lelem+"{1,3}"+relem+"{1,2}:"+ipv4+"|"+ // 1::5:6:10.0.0.1 1:2:3::5:6:10.0.0.1 1:2:3::6:10.0.0.1 + lelem+"{1,2}"+relem+"{1,3}:"+ipv4+"|"+ // 1::4:5:6:10.0.0.1 1:2::4:5:6:10.0.0.1 1:2::6:10.0.0.1 + lelem +relem+"{1,4}:"+ipv4+"|"+ // 1::3:4:5:6:10.0.0.1 1::3:4:5:6:10.0.0.1 1::6:10.0.0.1 + "::"+lelem+"{1,5}"+ipv4+ // ::2:3:4:5:10.0.0.1 ::5:10.0.0.1 ::10.0.0.1 + ")(?:%[0-9a-z]+)?" + + // This was part of the original regex, but is too specific to + // IPv6 details. + // fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}| # fe80::7:8%eth0 fe80::7:8%1 (link-local IPv6 addresses with zone index) + // ::(ffff(:0{1,4}){0,1}:){0,1} + // ((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3} + // (25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])| # ::255.255.255.255 ::ffff:255.255.255.255 ::ffff:0:255.255.255.255 (IPv4-mapped IPv6 addresses and IPv4-translated addresses) + // ([0-9a-fA-F]{1,4}:){1,4}: + // ((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3} + // (25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]) # 2001:db8:3:4::192.0.2.33 64:ff9b::192.0.2.33 (IPv4-Embedded IPv6 Address) + } + + private val ipv6Re = new RegExp("^"+ipv6address+"$", "i") + + // URI syntax parser. Based on RFC2396, RFC2732 and adaptations according to + // JavaDoc. + // - http://www.ietf.org/rfc/rfc2396.txt (see Appendix A for complete syntax) + // - http://www.ietf.org/rfc/rfc2732.txt + + private val uriRe = { + // We don't use any interpolators here to allow for constant folding + + /////////////////// + //// Helpers //// + /////////////////// + + // Inlined definitions + // reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | + // "$" | "," | "[" | "]" ; last two added by RFC2732 + // unreserved = alphanum | mark + // mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | + // "(" | ")" + + // escaped = "%" hex hex + val escaped = "%[a-f0-9]{2}" + + // uric = reserved | unreserved | escaped + val uric = "(?:[;/?:@&=+$,\\[\\]a-z0-9-_.!~*'()]|"+escaped+")" + + // pchar = unreserved | escaped | + // ":" | "@" | "&" | "=" | "+" | "$" | "," + val pchar = "(?:[a-z0-9-_.!~*'():@&=+$,]|"+escaped+")" + + /////////////////// + //// Server //// + /////////////////// + + // domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum + val domainlabel = "(?:[a-z0-9]|[a-z0-9][a-z0-9-]*[a-z0-9])" + + // toplabel = alpha | alpha *( alphanum | "-" ) alphanum + val toplabel = "(?:[a-z]|[a-z][a-z0-9-]*[a-z0-9])" + + // hostname = *( domainlabel "." ) toplabel [ "." ] + val hostname = "(?:"+domainlabel+"\\.)*"+toplabel+"\\.?" + + // IPv6reference = "[" IPv6address "]" + val ipv6reference = "\\[(?:"+ipv6address+")\\]" + + // host = hostname | IPv4address | IPv6reference + // ; IPv6reference added by RFC2732 + val host = "("+hostname+"|"+ipv4address+"|"+ipv6reference+")" /*CAPT*/ + + // Inlined definition + // port = *digit + + // hostport = host [ ":" port ] + val hostport = host+"(?::([0-9]*))?" /*CAPT*/ + + // userinfo = *( unreserved | escaped | + // ";" | ":" | "&" | "=" | "+" | "$" | "," ) + val userinfo = "(?:[a-z0-9-_.!~*'();:&=+$,]|"+escaped+")*" + + // server = [ [ userinfo "@" ] hostport ] + val server = "(?:(?:("+userinfo+")@)?"+hostport+")?" /*CAPT*/ + + /////////////////// + //// Authority //// + /////////////////// + + // reg_name = 1*( unreserved | escaped | "$" | "," | + // ";" | ":" | "@" | "&" | "=" | "+" ) + val reg_name = "(?:[a-z0-9-_.!~*'()$,;:@&=+]|"+escaped+")+" + + // authority = server | reg_name + val authority = server+"|"+reg_name + + /////////////////// + //// Paths //// + /////////////////// + + // Inlined definitions + // param = *pchar + + // segment = *pchar *( ";" param ) + val segment = pchar+"*(?:;"+pchar+"*)*" + + // path_segments = segment *( "/" segment ) + val path_segments = segment+"(?:/"+segment+")*" + + // abs_path = "/" path_segments + val abs_path = "/"+path_segments + + // net_path = "//" authority [ abs_path ] + val net_path = "//("+authority+")("+abs_path+")?" /*2CAPT*/ + + // Inlined definition + // Deviation from RCF2396 according to JavaDoc: Allow empty rel_segment + // and hence empty rel_path + // rel_segment = 1*( unreserved | escaped | + // ";" | "@" | "&" | "=" | "+" | "$" | "," ) + + // rel_path = rel_segment [ abs_path ] + val rel_path = "(?:[a-z0-9-_.!~*'();@&=+$,]|"+escaped+")*(?:"+abs_path+")?" + + /////////////////// + /// Query/Frag /// + /////////////////// + + // query = *uric + val query = "("+uric+"*)" /*CAPT*/ + // fragment = *uric + val fragment = "("+uric+"*)" /*CAPT*/ + + /////////////////// + /// Parts /// + /////////////////// + + // hier_part = ( net_path | abs_path ) [ "?" query ] + val hier_part = "(?:"+net_path+"|("+abs_path+"))(?:\\?"+query+")?" /*CAPT*/ + + // Inlined definition + // uric_no_slash = unreserved | escaped | ";" | "?" | ":" | "@" | + // "&" | "=" | "+" | "$" | "," + + // opaque_part = uric_no_slash *uric + val opaque_part = "(?:[a-z0-9-_.!~*'();?:@&=+$,]|"+escaped+")"+uric+"*" + + /////////////////// + /// URIs /// + /////////////////// + + // scheme = alpha *( alpha | digit | "+" | "-" | "." ) + val scheme = "([a-z][a-z0-9+-.]*)" /*CAPT*/ + + // absoluteURI = scheme ":" ( hier_part | opaque_part ) + val absoluteURI = scheme+":(?:("+hier_part+")|("+opaque_part+"))" /*2CAPT*/ + + // relativeURI = ( net_path | abs_path | rel_path ) [ "?" query ] + val relativeURI = /*2CAPT*/ + "(?:"+net_path+"|("+abs_path+")|("+rel_path+"))(?:\\?"+query+")?" + + // URI-reference = [ absoluteURI | relativeURI ] [ "#" fragment ] + val uriRef = "^(?:"+absoluteURI+"|"+relativeURI+")(?:#"+fragment+")?$" + + new RegExp(uriRef, "i") + } + + private object Fields { + final val AbsScheme = 1 + final val AbsHierPart = AbsScheme+1 + final val AbsAuthority = AbsHierPart+1 + final val AbsUserInfo = AbsAuthority+1 + final val AbsHost = AbsUserInfo+1 + final val AbsPort = AbsHost+1 + final val AbsNetPath = AbsPort+1 // abs_path part only + final val AbsAbsPath = AbsNetPath+1 + final val AbsQuery = AbsAbsPath+1 + final val AbsOpaquePart = AbsQuery+1 + final val RelSchemeSpecificPart = 0 // It's the whole string + final val RelAuthority = AbsOpaquePart+1 + final val RelUserInfo = RelAuthority+1 + final val RelHost = RelUserInfo+1 + final val RelPort = RelHost+1 + final val RelNetPath = RelPort+1 // abs_path part only + final val RelAbsPath = RelNetPath+1 + final val RelRelPath = RelAbsPath+1 + final val RelQuery = RelRelPath+1 + final val Fragment = RelQuery+1 + } + + // Helpers for constructors + + private def uriStr(scheme: String, ssp: String, fragment: String): String = { + var resStr = "" + + if (scheme != null) + resStr += scheme + ":" + + if (ssp != null) + resStr += quoteIllegal(ssp) + + if (fragment != null) + resStr += "#" + quoteIllegal(fragment) + + resStr + } + + private def uriStr(scheme: String, userInfo: String, host: String, port: Int, + path: String, query: String, fragment: String): String = { + var resStr = "" + + if (scheme != null) + resStr += scheme + ":" + + if (userInfo != null || host != null || port != -1) + resStr += "//" + + if (userInfo != null) + resStr += quoteUserInfo(userInfo) + "@" + + if (host != null) { + if (URI.ipv6Re.test(host)) + resStr += "[" + host + "]" + else + resStr += host + } + + if (port != -1) + resStr += ":" + port + + if (path != null) + resStr += quotePath(path) + + if (query != null) + resStr += "?" + quoteIllegal(query) + + if (fragment != null) + resStr += "#" + quoteIllegal(fragment) + + resStr + } + + private def uriStr(scheme: String, authority: String, path: String, + query: String, fragment: String) = { + var resStr = "" + + if (scheme != null) + resStr += scheme + ":" + + if (authority != null) + resStr += "//" + quoteAuthority(authority) + + if (path != null) + resStr += quotePath(path) + + if (query != null) + resStr += "?" + quoteIllegal(query) + + if (fragment != null) + resStr += "#" + quoteIllegal(fragment) + + resStr + } + + // Quote helpers + + private val quoteChar: js.Function1[String, String] = { (str: String) => + require(str.length == 1) + + val c = str.head.toInt + + if (c > 127) + throw new URISyntaxException(null, "Only ASCII allowed in URIs") + else + f"%%$c%02x" + } + + /** matches any character not in unreserved, punct, escaped or other */ + private val userInfoQuoteRe = + new RegExp("[^a-z0-9-_.!~*'(),;:$&+=%\\s]|%(?![0-9a-f]{2})", "ig") + + /** Quote any character not in unreserved, punct, escaped or other */ + private def quoteUserInfo(str: String) = + (str: js.prim.String).replace(userInfoQuoteRe, quoteChar) + + /** matches any character not in unreserved, punct, escaped, other or equal + * to '/' or '@' + */ + private val pathQuoteRe = + new RegExp("[^a-z0-9-_.!~*'(),;:$&+=%\\s@/]|%(?![0-9a-f]{2})", "ig") + + /** Quote any character not in unreserved, punct, escaped, other or equal + * to '/' or '@' + */ + private def quotePath(str: String) = + (str: js.prim.String).replace(pathQuoteRe, quoteChar) + + /** matches any character not in unreserved, punct, escaped, other or equal + * to '@', '[' or ']' + * The last two are different to how JavaDoc specifies, but hopefully yield + * the same behavior. (We shouldn't escape [], since they may occur + * in IPv6 addresses, but technically speaking they are in reserved + * due to RFC2732). + */ + private val authorityQuoteRe = + new RegExp("[^a-z0-9-_.!~*'(),;:$&+=%\\s@\\[\\]]|%(?![0-9a-f]{2})", "ig") + + /** Quote any character not in unreserved, punct, escaped, other or equal + * to '@' + */ + private def quoteAuthority(str: String) = + (str: js.prim.String).replace(authorityQuoteRe, quoteChar) + + /** matches any character not in unreserved, reserved, escaped or other */ + private val illegalQuoteRe = + new RegExp("[^a-z0-9-_.!~*'(),;:$&+=?/\\[\\]%\\s]|%(?![0-9a-f]{2})", "ig") + + /** Quote any character not in unreserved, reserved, escaped or other */ + private def quoteIllegal(str: String) = + (str: js.prim.String).replace(illegalQuoteRe, quoteChar) + + /** Case-sensitive comparison that is case-insensitive inside URI + * escapes. Will compare `a%A0` and `a%a0` as equal, but `a%A0` and + * `A%A0` as different. + */ + private def escapeAwareCompare(x: String, y: String): Int = { + @tailrec + def loop(i: Int): Int = { + if (i >= x.length || i >= y.length) + x.length - y.length + else { + val diff = x.charAt(i) - y.charAt(i) + if (diff != 0) diff + else if (x.charAt(i) == '%') { + // we need to do a CI compare for the next two characters + assert(x.length > i + 2, "Invalid escape in URI") + assert(y.length > i + 2, "Invalid escape in URI") + val cmp = + x.substring(i+1, i+3).compareToIgnoreCase(y.substring(i+1, i+3)) + if (cmp != 0) cmp + else loop(i+3) + } else loop(i+1) + } + } + + loop(0) + } + + /** Upper-cases all URI escape sequences in `str`. Used for hashing */ + private def normalizeEscapes(str: String): String = { + var i = 0 + var res = "" + while (i < str.length) { + if (str.charAt(i) == '%') { + assert(str.length > i + 2, "Invalid escape in URI") + res += str.substring(i, i+3).toUpperCase() + i += 3 + } else { + res += str.substring(i, i+1) + i += 1 + } + } + + res + } + + private final val uriSeed = 53722356 + +} diff --git a/examples/scala-js/javalib/src/main/scala/java/net/URISyntaxException.scala b/examples/scala-js/javalib/src/main/scala/java/net/URISyntaxException.scala new file mode 100644 index 0000000..85e0879 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/net/URISyntaxException.scala @@ -0,0 +1,15 @@ +package java.net + +class URISyntaxException( + private val input: String, + private val reason: String, + private val index: Int) extends Exception( + s"$reason in $input at $index") { + + def this(input: String, reason: String) = this(input, reason, -1) + + def getIndex(): Int = index + def getInput(): String = input + def getReason(): String = reason + +} diff --git a/examples/scala-js/javalib/src/main/scala/java/nio/Buffer.scala b/examples/scala-js/javalib/src/main/scala/java/nio/Buffer.scala new file mode 100644 index 0000000..be7ab7f --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/nio/Buffer.scala @@ -0,0 +1,83 @@ +package java.nio + +abstract class Buffer private[nio] (val _capacity: Int) { + private var _limit: Int = capacity + private var _position: Int = 0 + private[nio] var _mark: Int = -1 + + final def capacity(): Int = _capacity + + final def position(): Int = _position + + final def position(newPosition: Int): Buffer = { + if (newPosition < 0 || newPosition > limit()) + throw new IllegalArgumentException + _position = newPosition + if (_mark > newPosition) + _mark = -1 + this + } + + final def limit(): Int = _limit + + final def limit(newLimit: Int): Buffer = { + if (newLimit < 0 || newLimit > capacity()) + throw new IllegalArgumentException + _limit = newLimit + if (_position > newLimit) { + _position = newLimit + if (_mark > newLimit) + _mark = -1 + } + this + } + + final def mark(): Buffer = { + _mark = _position + this + } + + final def reset(): Buffer = { + if (_mark == -1) + throw new InvalidMarkException + _position = _mark + this + } + + final def clear(): Buffer = { + _mark = -1 + _position = 0 + _limit = capacity + this + } + + final def flip(): Buffer = { + _mark = -1 + _limit = _position + _position = 0 + this + } + + final def rewind(): Buffer = { + _mark = -1 + _position = 0 + this + } + + @inline final def remaining(): Int = limit - position + + @inline final def hasRemaining(): Boolean = position != limit + + def isReadOnly(): Boolean + + def hasArray(): Boolean + + def array(): Object + + def arrayOffset(): Int + + def isDirect(): Boolean + + override def toString(): String = + s"${getClass.getName}[pos=$position lim=$limit cap=$capacity]" +} diff --git a/examples/scala-js/javalib/src/main/scala/java/nio/BufferOverflowException.scala b/examples/scala-js/javalib/src/main/scala/java/nio/BufferOverflowException.scala new file mode 100644 index 0000000..03f359e --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/nio/BufferOverflowException.scala @@ -0,0 +1,3 @@ +package java.nio + +class BufferOverflowException extends RuntimeException diff --git a/examples/scala-js/javalib/src/main/scala/java/nio/BufferUnderflowException.scala b/examples/scala-js/javalib/src/main/scala/java/nio/BufferUnderflowException.scala new file mode 100644 index 0000000..e286975 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/nio/BufferUnderflowException.scala @@ -0,0 +1,3 @@ +package java.nio + +class BufferUnderflowException extends RuntimeException diff --git a/examples/scala-js/javalib/src/main/scala/java/nio/ByteBuffer.scala b/examples/scala-js/javalib/src/main/scala/java/nio/ByteBuffer.scala new file mode 100644 index 0000000..b743b39 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/nio/ByteBuffer.scala @@ -0,0 +1,227 @@ +package java.nio + +object ByteBuffer { + private final val HashSeed = -547316498 // "java.nio.ByteBuffer".## + + def allocate(capacity: Int): ByteBuffer = + wrap(new Array[Byte](capacity)) + + def allocateDirect(capacity: Int): ByteBuffer = + allocate(capacity) + + def wrap(array: Array[Byte], offset: Int, length: Int): ByteBuffer = + HeapByteBuffer.wrap(array, 0, array.length, offset, length, false) + + def wrap(array: Array[Byte]): ByteBuffer = + wrap(array, 0, array.length) +} + +abstract class ByteBuffer private[nio] ( + _capacity: Int, private[nio] val _array: Array[Byte], + private[nio] val _arrayOffset: Int) + extends Buffer(_capacity) with Comparable[ByteBuffer] { + + def this(_capacity: Int) = this(_capacity, null, -1) + + private var _order: ByteOrder = ByteOrder.BIG_ENDIAN + + def slice(): ByteBuffer + + def duplicate(): ByteBuffer + + def asReadOnlyBuffer(): ByteBuffer + + def get(): Byte + + def put(b: Byte): ByteBuffer + + def get(index: Int): Byte + + def put(index: Int, b: Byte): ByteBuffer + + def get(dst: Array[Byte], offset: Int, length: Int): ByteBuffer = { + val end = offset + length + + if (offset < 0 || length < 0 || end > dst.length) + throw new IndexOutOfBoundsException + if (remaining < length) + throw new BufferUnderflowException + + var i = offset + while (i != end) { + dst(i) = get() + i += 1 + } + + this + } + + def get(dst: Array[Byte]): ByteBuffer = + get(dst, 0, dst.length) + + def put(src: ByteBuffer): ByteBuffer = { + if (src eq this) + throw new IllegalArgumentException + if (isReadOnly) + throw new ReadOnlyBufferException + if (src.remaining > remaining) + throw new BufferOverflowException + + var n = src.remaining + if (src._array != null) { // even if read-only + val pos = src.position + put(src._array, src._arrayOffset + pos, n) + src.position(pos + n) + } else { + while (n != 0) { + put(src.get()) + n -= 1 + } + } + + this + } + + def put(src: Array[Byte], offset: Int, length: Int): ByteBuffer = { + val end = offset + length + if (offset < 0 || length < 0 || end > src.length) + throw new IndexOutOfBoundsException + if (isReadOnly) + throw new ReadOnlyBufferException + if (remaining < length) + throw new BufferOverflowException + + var i = offset + while (i != end) { + put(src(i)) + i += 1 + } + + this + } + + final def put(src: Array[Byte]): ByteBuffer = + put(src, 0, src.length) + + @inline final def hasArray(): Boolean = _array != null && !isReadOnly + + @inline final def array(): Array[Byte] = { + val a = _array + if (a == null) + throw new UnsupportedOperationException + if (isReadOnly) + throw new ReadOnlyBufferException + a + } + + @inline final def arrayOffset(): Int = { + val o = _arrayOffset + if (o == -1) + throw new UnsupportedOperationException + if (isReadOnly) + throw new ReadOnlyBufferException + o + } + + def compact(): ByteBuffer + + // Not implemented: + //def isDirect(): Boolean + + // toString(): String inherited from Buffer + + override def hashCode(): Int = { + import scala.util.hashing.MurmurHash3._ + val start = position + val end = limit + var h = ByteBuffer.HashSeed + var i = start + while (i != end) { + h = mix(h, get().##) + i += 1 + } + position(start) + finalizeHash(h, end-start) + } + + override def equals(that: Any): Boolean = that match { + case that: ByteBuffer => compareTo(that) == 0 + case _ => false + } + + def compareTo(that: ByteBuffer): Int = { + if (this eq that) { + 0 + } else { + val thisStart = this.position + val thisRemaining = this.remaining + val thatStart = that.position + val thatRemaining = that.remaining + val shortestLength = Math.min(thisRemaining, thatRemaining) + + var i = 0 + while (i != shortestLength) { + val cmp = this.get().compareTo(that.get()) + if (cmp != 0) { + this.position(thisStart) + that.position(thatStart) + return cmp + } + i += 1 + } + + this.position(thisStart) + that.position(thatStart) + thisRemaining.compareTo(thatRemaining) + } + } + + final def order(): ByteOrder = _order + + final def order(bo: ByteOrder): ByteBuffer = { + if (bo == null) + throw new NullPointerException + _order = bo + this + } + + /* Not implemented: + + def getChar(): Char + def putChar(value: Char): ByteBuffer + def getChar(index: Int): Char + def putChar(index: Int, value: Char): ByteBuffer + def asCharBuffer(): CharBuffer + + def getShort(): Short + def putShort(value: Short): ByteBuffer + def getShort(index: Int): Short + def putShort(index: Int, value: Short): ByteBuffer + def asShortBuffer(): ShortBuffer + + def getInt(): Int + def putInt(value: Int): ByteBuffer + def getInt(index: Int): Int + def putInt(index: Int, value: Int): ByteBuffer + def asIntBuffer(): IntBuffer + + def getLong(): Long + def putLong(value: Long): ByteBuffer + def getLong(index: Int): Long + def putLong(index: Int, value: Long): ByteBuffer + def asLongBuffer(): LongBuffer + + def getFloat(): Float + def putFloat(value: Float): ByteBuffer + def getFloat(index: Int): Float + def putFloat(index: Int, value: Float): ByteBuffer + def asFloatBuffer(): FloatBuffer + + def getDouble(): Double + def putDouble(value: Double): ByteBuffer + def getDouble(index: Int): Double + def putDouble(index: Int, value: Double): ByteBuffer + def asDoubleBuffer(): DoubleBuffer + + */ +} diff --git a/examples/scala-js/javalib/src/main/scala/java/nio/ByteOrder.scala b/examples/scala-js/javalib/src/main/scala/java/nio/ByteOrder.scala new file mode 100644 index 0000000..20bac6a --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/nio/ByteOrder.scala @@ -0,0 +1,15 @@ +package java.nio + +final class ByteOrder private (name: String) { + override def toString(): String = name +} + +object ByteOrder { + val BIG_ENDIAN: ByteOrder = new ByteOrder("BIG_ENDIAN") + val LITTLE_ENDIAN: ByteOrder = new ByteOrder("LITTLE_ENDIAN") + + def nativeOrder(): ByteOrder = { + if (scala.scalajs.runtime.Bits.areTypedArraysBigEndian) BIG_ENDIAN + else LITTLE_ENDIAN + } +} diff --git a/examples/scala-js/javalib/src/main/scala/java/nio/CharBuffer.scala b/examples/scala-js/javalib/src/main/scala/java/nio/CharBuffer.scala new file mode 100644 index 0000000..5e74953 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/nio/CharBuffer.scala @@ -0,0 +1,228 @@ +package java.nio + +object CharBuffer { + private final val HashSeed = -182887236 // "java.nio.CharBuffer".## + + def allocate(capacity: Int): CharBuffer = + wrap(new Array[Char](capacity)) + + def wrap(array: Array[Char], offset: Int, length: Int): CharBuffer = + HeapCharBuffer.wrap(array, 0, array.length, offset, length, false) + + def wrap(array: Array[Char]): CharBuffer = + wrap(array, 0, array.length) + + def wrap(csq: CharSequence, start: Int, end: Int): CharBuffer = + StringCharBuffer.wrap(csq, 0, csq.length, start, end) + + def wrap(csq: CharSequence): CharBuffer = + wrap(csq, 0, csq.length) +} + +abstract class CharBuffer private[nio] ( + _capacity: Int, private[nio] val _array: Array[Char], + private[nio] val _arrayOffset: Int) + extends Buffer(_capacity) with Comparable[CharBuffer] + with CharSequence with Appendable with Readable { + + def this(_capacity: Int) = this(_capacity, null, -1) + + def read(target: CharBuffer): Int = { + // Attention: this method must not change this buffer's position + val n = remaining + if (n == 0) -1 + else if (_array != null) { // even if read-only + target.put(_array, _arrayOffset, n) + n + } else { + val savedPos = position + target.put(this) + position(savedPos) + n + } + } + + def slice(): CharBuffer + + def duplicate(): CharBuffer + + def asReadOnlyBuffer(): CharBuffer + + def get(): Char + + def put(c: Char): CharBuffer + + def get(index: Int): Char + + def put(index: Int, c: Char): CharBuffer + + def get(dst: Array[Char], offset: Int, length: Int): CharBuffer = { + val end = offset + length + + if (offset < 0 || length < 0 || end > dst.length) + throw new IndexOutOfBoundsException + if (remaining < length) + throw new BufferUnderflowException + + var i = offset + while (i != end) { + dst(i) = get() + i += 1 + } + + this + } + + def get(dst: Array[Char]): CharBuffer = + get(dst, 0, dst.length) + + def put(src: CharBuffer): CharBuffer = { + if (src eq this) + throw new IllegalArgumentException + if (isReadOnly) + throw new ReadOnlyBufferException + if (src.remaining > remaining) + throw new BufferOverflowException + + var n = src.remaining + if (src._array != null) { // even if read-only + val pos = src.position + put(src._array, src._arrayOffset + pos, n) + src.position(pos + n) + } else { + while (n != 0) { + put(src.get()) + n -= 1 + } + } + + this + } + + def put(src: Array[Char], offset: Int, length: Int): CharBuffer = { + val end = offset + length + if (offset < 0 || length < 0 || end > src.length) + throw new IndexOutOfBoundsException + if (isReadOnly) + throw new ReadOnlyBufferException + if (remaining < length) + throw new BufferOverflowException + + var i = offset + while (i != end) { + put(src(i)) + i += 1 + } + + this + } + + final def put(src: Array[Char]): CharBuffer = + put(src, 0, src.length) + + def put(src: String, start: Int, end: Int): CharBuffer = + put(CharBuffer.wrap(src, start, end)) + + final def put(src: String): CharBuffer = + put(src, 0, src.length) + + @inline final def hasArray(): Boolean = _array != null && !isReadOnly + + @inline final def array(): Array[Char] = { + val a = _array + if (a == null) + throw new UnsupportedOperationException + if (isReadOnly) + throw new ReadOnlyBufferException + a + } + + @inline final def arrayOffset(): Int = { + val o = _arrayOffset + if (o == -1) + throw new UnsupportedOperationException + if (isReadOnly) + throw new ReadOnlyBufferException + o + } + + def compact(): CharBuffer + + // Not implemented: + //def isDirect(): Boolean + + override def hashCode(): Int = { + import scala.util.hashing.MurmurHash3._ + val start = position + val end = limit + var h = CharBuffer.HashSeed + var i = start + while (i != end) { + h = mix(h, get().##) + i += 1 + } + position(start) + finalizeHash(h, end-start) + } + + override def equals(that: Any): Boolean = that match { + case that: CharBuffer => compareTo(that) == 0 + case _ => false + } + + def compareTo(that: CharBuffer): Int = { + if (this eq that) { + 0 + } else { + val thisStart = this.position + val thisRemaining = this.remaining + val thatStart = that.position + val thatRemaining = that.remaining + val shortestLength = Math.min(thisRemaining, thatRemaining) + + var i = 0 + while (i != shortestLength) { + val cmp = this.get().compareTo(that.get()) + if (cmp != 0) { + this.position(thisStart) + that.position(thatStart) + return cmp + } + i += 1 + } + + this.position(thisStart) + that.position(thatStart) + thisRemaining.compareTo(thatRemaining) + } + } + + override def toString(): String = { + if (_array != null) { // even if read-only + new String(_array, position + _arrayOffset, remaining) + } else { + val chars = new Array[Char](remaining) + val savedPos = position + get(chars) + position(savedPos) + new String(chars) + } + } + + final def length(): Int = remaining + + final def charAt(index: Int): Char = get(position + index) + + def subSequence(start: Int, end: Int): CharSequence + + def append(csq: CharSequence): CharBuffer = + put(csq.toString()) + + def append(csq: CharSequence, start: Int, end: Int): CharBuffer = + put(csq.subSequence(start, end).toString()) + + def append(c: Char): CharBuffer = + put(c) + + def order(): ByteOrder +} diff --git a/examples/scala-js/javalib/src/main/scala/java/nio/HeapByteBuffer.scala b/examples/scala-js/javalib/src/main/scala/java/nio/HeapByteBuffer.scala new file mode 100644 index 0000000..ed3fd29 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/nio/HeapByteBuffer.scala @@ -0,0 +1,129 @@ +package java.nio + +private[nio] final class HeapByteBuffer private ( + _capacity: Int, _array0: Array[Byte], _arrayOffset0: Int, + _initialPosition: Int, _initialLimit: Int, _readOnly: Boolean) + extends ByteBuffer(_capacity, _array0, _arrayOffset0) { + + position(_initialPosition) + limit(_initialLimit) + + def isReadOnly(): Boolean = _readOnly + + def isDirect(): Boolean = false + + def slice(): ByteBuffer = { + val cap = remaining + new HeapByteBuffer(cap, _array, _arrayOffset+position, 0, cap, isReadOnly) + } + + def duplicate(): ByteBuffer = { + val result = new HeapByteBuffer(capacity, _array, _arrayOffset, + position, limit, isReadOnly) + result._mark = this._mark + result + } + + def asReadOnlyBuffer(): ByteBuffer = { + val result = new HeapByteBuffer(capacity, _array, _arrayOffset, + position, limit, true) + result._mark = this._mark + result + } + + def get(): Byte = { + if (!hasRemaining) + throw new BufferUnderflowException + val p = position + position(p + 1) + _array(_arrayOffset + p) + } + + def put(b: Byte): ByteBuffer = { + if (isReadOnly) + throw new ReadOnlyBufferException + if (!hasRemaining) + throw new BufferOverflowException + val p = position + _array(_arrayOffset + p) = b + position(p + 1) + this + } + + def get(index: Int): Byte = { + if (index < 0 || index >= limit) + throw new IndexOutOfBoundsException + _array(_arrayOffset + index) + } + + def put(index: Int, b: Byte): ByteBuffer = { + if (isReadOnly) + throw new ReadOnlyBufferException + if (index < 0 || index >= limit) + throw new IndexOutOfBoundsException + _array(_arrayOffset + index) = b + this + } + + override def get(dst: Array[Byte], offset: Int, length: Int): ByteBuffer = { + val end = offset + length + + if (offset < 0 || length < 0 || end > dst.length) + throw new IndexOutOfBoundsException + + val startPos = position + val endPos = startPos + length + if (endPos > limit) + throw new BufferUnderflowException + + System.arraycopy(_array, startPos + _arrayOffset, dst, offset, length) + position(endPos) + + this + } + + override def put(src: Array[Byte], offset: Int, length: Int): ByteBuffer = { + val end = offset + length + if (offset < 0 || length < 0 || end > src.length) + throw new IndexOutOfBoundsException + if (isReadOnly) + throw new ReadOnlyBufferException + + val startPos = position + val endPos = startPos + length + if (endPos > limit) + throw new BufferOverflowException + + System.arraycopy(src, offset, _array, startPos + _arrayOffset, length) + position(endPos) + + this + } + + def compact(): ByteBuffer = { + if (isReadOnly) + throw new ReadOnlyBufferException + + val offset = _arrayOffset + val len = remaining + System.arraycopy(_array, offset + position, _array, offset, len) + _mark = -1 + limit(capacity) + position(len) + this + } +} + +private[nio] object HeapByteBuffer { + private[nio] def wrap(array: Array[Byte], arrayOffset: Int, capacity: Int, + initialPosition: Int, initialLength: Int, + isReadOnly: Boolean): ByteBuffer = { + if (arrayOffset < 0 || capacity < 0 || arrayOffset+capacity > array.length) + throw new IndexOutOfBoundsException + val initialLimit = initialPosition + initialLength + if (initialPosition < 0 || initialLength < 0 || initialLimit > capacity) + throw new IndexOutOfBoundsException + new HeapByteBuffer(capacity, array, arrayOffset, + initialPosition, initialLimit, isReadOnly) + } +} diff --git a/examples/scala-js/javalib/src/main/scala/java/nio/HeapCharBuffer.scala b/examples/scala-js/javalib/src/main/scala/java/nio/HeapCharBuffer.scala new file mode 100644 index 0000000..546c55d --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/nio/HeapCharBuffer.scala @@ -0,0 +1,138 @@ +package java.nio + +private[nio] final class HeapCharBuffer private ( + _capacity: Int, _array0: Array[Char], _arrayOffset0: Int, + _initialPosition: Int, _initialLimit: Int, _readOnly: Boolean) + extends CharBuffer(_capacity, _array0, _arrayOffset0) { + + position(_initialPosition) + limit(_initialLimit) + + def isReadOnly(): Boolean = _readOnly + + def isDirect(): Boolean = false + + def slice(): CharBuffer = { + val cap = remaining + new HeapCharBuffer(cap, _array, _arrayOffset + position, 0, cap, isReadOnly) + } + + def duplicate(): CharBuffer = { + val result = new HeapCharBuffer(capacity, _array, _arrayOffset, + position, limit, isReadOnly) + result._mark = this._mark + result + } + + def asReadOnlyBuffer(): CharBuffer = { + val result = new HeapCharBuffer(capacity, _array, _arrayOffset, + position, limit, true) + result._mark = this._mark + result + } + + def subSequence(start: Int, end: Int): CharBuffer = { + if (start < 0 || end < start || end > remaining) + throw new IndexOutOfBoundsException + new HeapCharBuffer(capacity, _array, _arrayOffset, + position + start, position + end, isReadOnly) + } + + def get(): Char = { + if (!hasRemaining) + throw new BufferUnderflowException + val p = position + position(p + 1) + _array(_arrayOffset + p) + } + + def put(c: Char): CharBuffer = { + if (isReadOnly) + throw new ReadOnlyBufferException + if (!hasRemaining) + throw new BufferOverflowException + val p = position + _array(_arrayOffset + p) = c + position(p + 1) + this + } + + def get(index: Int): Char = { + if (index < 0 || index >= limit) + throw new IndexOutOfBoundsException + _array(_arrayOffset + index) + } + + def put(index: Int, b: Char): CharBuffer = { + if (isReadOnly) + throw new ReadOnlyBufferException + if (index < 0 || index >= limit) + throw new IndexOutOfBoundsException + _array(_arrayOffset + index) = b + this + } + + override def get(dst: Array[Char], offset: Int, length: Int): CharBuffer = { + val end = offset + length + + if (offset < 0 || length < 0 || end > dst.length) + throw new IndexOutOfBoundsException + + val startPos = position + val endPos = startPos + length + if (endPos > limit) + throw new BufferUnderflowException + + System.arraycopy(_array, startPos + _arrayOffset, dst, offset, length) + position(endPos) + + this + } + + override def put(src: Array[Char], offset: Int, length: Int): CharBuffer = { + val end = offset + length + if (offset < 0 || length < 0 || end > src.length) + throw new IndexOutOfBoundsException + if (isReadOnly) + throw new ReadOnlyBufferException + + val startPos = position + val endPos = startPos + length + if (endPos > limit) + throw new BufferOverflowException + + System.arraycopy(src, offset, _array, startPos + _arrayOffset, length) + position(endPos) + + this + } + + def compact(): CharBuffer = { + if (isReadOnly) + throw new ReadOnlyBufferException + + val offset = _arrayOffset + val len = remaining + System.arraycopy(_array, offset + position, _array, offset, len) + _mark = -1 + limit(capacity) + position(len) + this + } + + def order(): ByteOrder = ByteOrder.nativeOrder() +} + +private[nio] object HeapCharBuffer { + private[nio] def wrap(array: Array[Char], arrayOffset: Int, capacity: Int, + initialPosition: Int, initialLength: Int, + isReadOnly: Boolean): CharBuffer = { + if (arrayOffset < 0 || capacity < 0 || arrayOffset+capacity > array.length) + throw new IndexOutOfBoundsException + val initialLimit = initialPosition + initialLength + if (initialPosition < 0 || initialLength < 0 || initialLimit > capacity) + throw new IndexOutOfBoundsException + new HeapCharBuffer(capacity, array, arrayOffset, + initialPosition, initialLimit, isReadOnly) + } +} diff --git a/examples/scala-js/javalib/src/main/scala/java/nio/InvalidMarkException.scala b/examples/scala-js/javalib/src/main/scala/java/nio/InvalidMarkException.scala new file mode 100644 index 0000000..c2d3714 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/nio/InvalidMarkException.scala @@ -0,0 +1,3 @@ +package java.nio + +class InvalidMarkException extends IllegalStateException diff --git a/examples/scala-js/javalib/src/main/scala/java/nio/ReadOnlyBufferException.scala b/examples/scala-js/javalib/src/main/scala/java/nio/ReadOnlyBufferException.scala new file mode 100644 index 0000000..ee0868b --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/nio/ReadOnlyBufferException.scala @@ -0,0 +1,3 @@ +package java.nio + +class ReadOnlyBufferException extends UnsupportedOperationException diff --git a/examples/scala-js/javalib/src/main/scala/java/nio/StringCharBuffer.scala b/examples/scala-js/javalib/src/main/scala/java/nio/StringCharBuffer.scala new file mode 100644 index 0000000..25bc594 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/nio/StringCharBuffer.scala @@ -0,0 +1,102 @@ +package java.nio + +private[nio] final class StringCharBuffer private ( + _capacity: Int, private[this] var _csq: CharSequence, + private[this] var _csqOffset: Int, + _initialPosition: Int, _initialLimit: Int) + extends CharBuffer(_capacity) { + + position(_initialPosition) + limit(_initialLimit) + + def isReadOnly(): Boolean = true + + def isDirect(): Boolean = false + + def slice(): CharBuffer = { + val cap = remaining + new StringCharBuffer(cap, _csq, _csqOffset + position, 0, cap) + } + + def duplicate(): CharBuffer = { + val result = new StringCharBuffer(capacity, _csq, _csqOffset, + position, limit) + result._mark = this._mark + result + } + + def asReadOnlyBuffer(): CharBuffer = duplicate() + + def subSequence(start: Int, end: Int): CharBuffer = { + if (start < 0 || end < start || end > remaining) + throw new IndexOutOfBoundsException + new StringCharBuffer(capacity, _csq, _csqOffset, + position + start, position + end) + } + + def get(): Char = { + if (!hasRemaining) + throw new BufferUnderflowException + val p = position + position(p + 1) + _csq.charAt(_csqOffset + p) + } + + def put(c: Char): CharBuffer = + throw new ReadOnlyBufferException + + def get(index: Int): Char = { + if (index < 0 || index >= limit) + throw new IndexOutOfBoundsException + _csq.charAt(_csqOffset + index) + } + + def put(index: Int, b: Char): CharBuffer = + throw new ReadOnlyBufferException + + override def get(dst: Array[Char], offset: Int, length: Int): CharBuffer = { + val end = offset + length + + if (offset < 0 || length < 0 || end > dst.length) + throw new IndexOutOfBoundsException + + val startPos = position + val endPos = startPos + length + if (endPos > limit) + throw new BufferUnderflowException + + var i = offset + var j = startPos + _csqOffset + while (i != end) { + dst(i) = _csq.charAt(j) + i += 1 + j += 1 + } + position(endPos) + + this + } + + def compact(): CharBuffer = + throw new ReadOnlyBufferException + + override def toString(): String = { + val offset = _csqOffset + _csq.subSequence(position + offset, limit + offset).toString() + } + + def order(): ByteOrder = ByteOrder.nativeOrder() +} + +private[nio] object StringCharBuffer { + private[nio] def wrap(csq: CharSequence, csqOffset: Int, capacity: Int, + initialPosition: Int, initialLength: Int): CharBuffer = { + if (csqOffset < 0 || capacity < 0 || csqOffset+capacity > csq.length) + throw new IndexOutOfBoundsException + val initialLimit = initialPosition + initialLength + if (initialPosition < 0 || initialLength < 0 || initialLimit > capacity) + throw new IndexOutOfBoundsException + new StringCharBuffer(capacity, csq, csqOffset, + initialPosition, initialLimit) + } +} diff --git a/examples/scala-js/javalib/src/main/scala/java/nio/charset/CharacterCodingException.scala b/examples/scala-js/javalib/src/main/scala/java/nio/charset/CharacterCodingException.scala new file mode 100644 index 0000000..8017348 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/nio/charset/CharacterCodingException.scala @@ -0,0 +1,3 @@ +package java.nio.charset + +class CharacterCodingException extends java.io.IOException diff --git a/examples/scala-js/javalib/src/main/scala/java/nio/charset/Charset.scala b/examples/scala-js/javalib/src/main/scala/java/nio/charset/Charset.scala new file mode 100644 index 0000000..6d1af47 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/nio/charset/Charset.scala @@ -0,0 +1,103 @@ +package java.nio.charset + +import java.nio.{ByteBuffer, CharBuffer} + +import scala.scalajs.js + +abstract class Charset protected (canonicalName: String, + aliases: Array[String]) extends AnyRef with Comparable[Charset] { + final def name(): String = canonicalName + + override final def equals(that: Any): Boolean = that match { + case that: Charset => this.name == that.name + case _ => false + } + + override final def toString(): String = name() + + override final def hashCode(): Int = name.## + + override final def compareTo(that: Charset): Int = + name.compareToIgnoreCase(that.name) + + def contains(cs: Charset): Boolean + + def newDecoder(): CharsetDecoder + def newEncoder(): CharsetEncoder + + def canEncode(): Boolean = true + + private lazy val cachedDecoder = { + this.newDecoder() + .onMalformedInput(CodingErrorAction.REPLACE) + .onUnmappableCharacter(CodingErrorAction.REPLACE) + } + + private lazy val cachedEncoder = { + this.newEncoder() + .onMalformedInput(CodingErrorAction.REPLACE) + .onUnmappableCharacter(CodingErrorAction.REPLACE) + } + + final def decode(bb: ByteBuffer): CharBuffer = + cachedDecoder.decode(bb) + + final def encode(cb: CharBuffer): ByteBuffer = + cachedEncoder.encode(cb) + + final def encode(str: String): ByteBuffer = + encode(CharBuffer.wrap(str)) + + def displayName(): String = name +} + +object Charset { + import StandardCharsets._ + + def defaultCharset(): Charset = + UTF_8 + + def forName(charsetName: String): Charset = + CharsetMap.getOrElse(charsetName.toLowerCase, + throw new UnsupportedCharsetException(charsetName)) + + def isSupported(charsetName: String): Boolean = + CharsetMap.contains(charsetName.toLowerCase) + + private lazy val CharsetMap = { + val m = js.Dictionary.empty[Charset] + + // All these lists where obtained by experimentation on the JDK + + for (s <- Seq("iso-8859-1", "iso8859-1", "iso_8859_1", "iso8859_1", + "iso_8859-1", "8859_1", "iso_8859-1:1987", + "latin1", "csisolatin1", "l1", + "ibm-819", "ibm819", "cp819", "819", + "iso-ir-100")) + m(s) = ISO_8859_1 + + for (s <- Seq("us-ascii", "ascii7", "ascii", "csascii", + "default", + "cp367", "ibm367", + "iso646-us", "646", "iso_646.irv:1983", "iso_646.irv:1991", + "ansi_x3.4-1986", "ansi_x3.4-1968", + "iso-ir-6")) + m(s) = US_ASCII + + for (s <- Seq("utf-8", "utf_8", "utf8", "unicode-1-1-utf-8")) + m(s) = UTF_8 + + for (s <- Seq("utf-16be", "utf_16be", "x-utf-16be", + "iso-10646-ucs-2", "unicodebigunmarked")) + m(s) = UTF_16BE + + for (s <- Seq("utf-16le", "utf_16le", "x-utf-16le", + "unicodelittleunmarked")) + m(s) = UTF_16LE + + for (s <- Seq("utf-16", "utf_16", "unicode", "unicodebig")) + m(s) = UTF_16 + + m + } +} diff --git a/examples/scala-js/javalib/src/main/scala/java/nio/charset/CharsetDecoder.scala b/examples/scala-js/javalib/src/main/scala/java/nio/charset/CharsetDecoder.scala new file mode 100644 index 0000000..a3532ba --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/nio/charset/CharsetDecoder.scala @@ -0,0 +1,217 @@ +package java.nio.charset + +import scala.annotation.{switch, tailrec} + +import java.nio._ + +abstract class CharsetDecoder protected (cs: Charset, + _averageCharsPerByte: Float, _maxCharsPerByte: Float) { + + import CharsetDecoder._ + + // Config + + private[this] var _replacement: String = "\uFFFD" + private[this] var _malformedInputAction: CodingErrorAction = + CodingErrorAction.REPORT + private[this] var _unmappableCharacterAction: CodingErrorAction = + CodingErrorAction.REPORT + + // Status + + private[this] var status: Int = INIT + + // Methods + + final def charset(): Charset = cs + + final def replacement(): String = _replacement + + final def replaceWith(newReplacement: String): CharsetDecoder = { + if (newReplacement == null || newReplacement == "") + throw new IllegalArgumentException("Invalid replacement: "+newReplacement) + if (newReplacement.length > maxCharsPerByte) + throw new IllegalArgumentException( + "Replacement string cannot be longer than maxCharsPerByte") + _replacement = newReplacement + implReplaceWith(newReplacement) + this + } + + protected def implReplaceWith(newReplacement: String): Unit = () + + def malformedInputAction(): CodingErrorAction = _malformedInputAction + + final def onMalformedInput(newAction: CodingErrorAction): CharsetDecoder = { + if (newAction == null) + throw new IllegalArgumentException("null CodingErrorAction") + _malformedInputAction = newAction + implOnMalformedInput(newAction) + this + } + + protected def implOnMalformedInput(newAction: CodingErrorAction): Unit = () + + def unmappableCharacterAction(): CodingErrorAction = _unmappableCharacterAction + + final def onUnmappableCharacter(newAction: CodingErrorAction): CharsetDecoder = { + if (newAction == null) + throw new IllegalArgumentException("null CodingErrorAction") + _unmappableCharacterAction = newAction + implOnUnmappableCharacter(newAction) + this + } + + protected def implOnUnmappableCharacter(newAction: CodingErrorAction): Unit = () + + final def averageCharsPerByte(): Float = _averageCharsPerByte + final def maxCharsPerByte(): Float = _maxCharsPerByte + + final def decode(in: ByteBuffer, out: CharBuffer, + endOfInput: Boolean): CoderResult = { + + if (status == FLUSHED || (!endOfInput && status == END)) + throw new IllegalStateException + + status = if (endOfInput) END else ONGOING + + @inline + @tailrec + def loop(): CoderResult = { + val result1 = try { + decodeLoop(in, out) + } catch { + case ex: BufferOverflowException => + throw new CoderMalfunctionError(ex) + case ex: BufferUnderflowException => + throw new CoderMalfunctionError(ex) + } + + val result2 = if (result1.isUnderflow) { + val remaining = in.remaining + if (endOfInput && remaining > 0) + CoderResult.malformedForLength(remaining) + else + result1 + } else { + result1 + } + + if (result2.isUnderflow || result2.isOverflow) { + result2 + } else { + val action = + if (result2.isUnmappable) unmappableCharacterAction + else malformedInputAction + + action match { + case CodingErrorAction.REPLACE => + if (out.remaining < replacement.length) { + CoderResult.OVERFLOW + } else { + out.put(replacement) + in.position(in.position + result2.length) + loop() + } + case CodingErrorAction.REPORT => + result2 + case CodingErrorAction.IGNORE => + in.position(in.position + result2.length) + loop() + } + } + } + + loop() + } + + final def flush(out: CharBuffer): CoderResult = { + (status: @switch) match { + case END => + val result = implFlush(out) + if (result.isUnderflow) + status = FLUSHED + result + case FLUSHED => + CoderResult.UNDERFLOW + case _ => + throw new IllegalStateException + } + } + + protected def implFlush(out: CharBuffer): CoderResult = + CoderResult.UNDERFLOW + + final def reset(): CharsetDecoder = { + status = INIT + implReset() + this + } + + protected def implReset(): Unit = () + + protected def decodeLoop(in: ByteBuffer, out: CharBuffer): CoderResult + + final def decode(in: ByteBuffer): CharBuffer = { + def grow(out: CharBuffer): CharBuffer = { + if (out.capacity == 0) { + CharBuffer.allocate(1) + } else { + val result = CharBuffer.allocate(out.capacity*2) + out.flip() + result.put(out) + result + } + } + + @inline + @tailrec + def loopDecode(out: CharBuffer): CharBuffer = { + val result = decode(in, out, endOfInput = true) + if (result.isUnderflow) { + assert(!in.hasRemaining) + out + } else if (result.isOverflow) { + loopDecode(grow(out)) + } else { + result.throwException() + throw new AssertionError("should not get here") + } + } + + @inline + @tailrec + def loopFlush(out: CharBuffer): CharBuffer = { + val result = flush(out) + if (result.isUnderflow) { + out + } else if (result.isOverflow) { + loopFlush(grow(out)) + } else { + result.throwException() + throw new AssertionError("should not get here") + } + } + + reset() + val initLength = (in.remaining.toDouble * averageCharsPerByte).toInt + val out = loopFlush(loopDecode(CharBuffer.allocate(initLength))) + out.flip() + out + } + + def isAutoDetecting(): Boolean = false + + def isCharsetDetected(): Boolean = + throw new UnsupportedOperationException + + def detectedCharset(): Charset = + throw new UnsupportedOperationException +} + +object CharsetDecoder { + private final val INIT = 1 + private final val ONGOING = 2 + private final val END = 3 + private final val FLUSHED = 4 +} diff --git a/examples/scala-js/javalib/src/main/scala/java/nio/charset/CharsetEncoder.scala b/examples/scala-js/javalib/src/main/scala/java/nio/charset/CharsetEncoder.scala new file mode 100644 index 0000000..37d2296 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/nio/charset/CharsetEncoder.scala @@ -0,0 +1,235 @@ +package java.nio.charset + +import scala.annotation.{switch, tailrec} + +import java.nio._ + +abstract class CharsetEncoder protected (cs: Charset, + _averageBytesPerChar: Float, _maxBytesPerChar: Float, + private[this] var _replacement: Array[Byte]) { + + import CharsetEncoder._ + + protected def this(cs: Charset, _averageBytesPerChar: Float, + _maxBytesPerChar: Float) = + this(cs, _averageBytesPerChar, _averageBytesPerChar, Array('?'.toByte)) + + // Config + + private[this] var _malformedInputAction: CodingErrorAction = + CodingErrorAction.REPORT + private[this] var _unmappableCharacterAction: CodingErrorAction = + CodingErrorAction.REPORT + + // Status + + private[this] var status: Int = INIT + + // Methods + + final def charset(): Charset = cs + + final def replacement(): Array[Byte] = _replacement + + final def replaceWith(newReplacement: Array[Byte]): CharsetEncoder = { + if (newReplacement == null || newReplacement.length == 0 || + newReplacement.length > maxBytesPerChar || + !isLegalReplacement(newReplacement)) + throw new IllegalArgumentException + + _replacement = newReplacement + implReplaceWith(newReplacement) + this + } + + protected def implReplaceWith(newReplacement: Array[Byte]): Unit = () + + def isLegalReplacement(repl: Array[Byte]): Boolean = { + val decoder = charset.newDecoder + val replBuf = ByteBuffer.wrap(repl) + + @inline + @tailrec + def loop(outBufSize: Int): Boolean = { + val result = decoder.decode(replBuf, CharBuffer.allocate(outBufSize), true) + if (result.isOverflow) { + loop(outBufSize * 2) + } else { + !replBuf.hasRemaining + } + } + + loop(2) + } + + def malformedInputAction(): CodingErrorAction = _malformedInputAction + + final def onMalformedInput(newAction: CodingErrorAction): CharsetEncoder = { + if (newAction == null) + throw new IllegalArgumentException("null CodingErrorAction") + _malformedInputAction = newAction + implOnMalformedInput(newAction) + this + } + + protected def implOnMalformedInput(newAction: CodingErrorAction): Unit = () + + def unmappableCharacterAction(): CodingErrorAction = _unmappableCharacterAction + + final def onUnmappableCharacter(newAction: CodingErrorAction): CharsetEncoder = { + if (newAction == null) + throw new IllegalArgumentException("null CodingErrorAction") + _unmappableCharacterAction = newAction + implOnUnmappableCharacter(newAction) + this + } + + protected def implOnUnmappableCharacter(newAction: CodingErrorAction): Unit = () + + final def averageBytesPerChar(): Float = _averageBytesPerChar + final def maxBytesPerChar(): Float = _maxBytesPerChar + + final def encode(in: CharBuffer, out: ByteBuffer, + endOfInput: Boolean): CoderResult = { + + if (status == FLUSHED || (!endOfInput && status == END)) + throw new IllegalStateException + + status = if (endOfInput) END else ONGOING + + @inline + @tailrec + def loop(): CoderResult = { + val result1 = try { + encodeLoop(in, out) + } catch { + case ex: BufferOverflowException => + throw new CoderMalfunctionError(ex) + case ex: BufferUnderflowException => + throw new CoderMalfunctionError(ex) + } + + val result2 = if (result1.isUnderflow) { + val remaining = in.remaining + if (endOfInput && remaining > 0) + CoderResult.malformedForLength(remaining) + else + result1 + } else { + result1 + } + + if (result2.isUnderflow || result2.isOverflow) { + result2 + } else { + val action = + if (result2.isUnmappable) unmappableCharacterAction + else malformedInputAction + + action match { + case CodingErrorAction.REPLACE => + if (out.remaining < replacement.length) { + CoderResult.OVERFLOW + } else { + out.put(replacement) + in.position(in.position + result2.length) + loop() + } + case CodingErrorAction.REPORT => + result2 + case CodingErrorAction.IGNORE => + in.position(in.position + result2.length) + loop() + } + } + } + + loop() + } + + final def flush(out: ByteBuffer): CoderResult = { + (status: @switch) match { + case END => + val result = implFlush(out) + if (result.isUnderflow) + status = FLUSHED + result + case FLUSHED => + CoderResult.UNDERFLOW + case _ => + throw new IllegalStateException + } + } + + protected def implFlush(out: ByteBuffer): CoderResult = + CoderResult.UNDERFLOW + + final def reset(): CharsetEncoder = { + status = INIT + implReset() + this + } + + protected def implReset(): Unit = () + + protected def encodeLoop(arg1: CharBuffer, arg2: ByteBuffer): CoderResult + + final def encode(in: CharBuffer): ByteBuffer = { + def grow(out: ByteBuffer): ByteBuffer = { + if (out.capacity == 0) { + ByteBuffer.allocate(1) + } else { + val result = ByteBuffer.allocate(out.capacity*2) + out.flip() + result.put(out) + result + } + } + + if (in.remaining == 0) { + ByteBuffer.allocate(0) + } else { + @inline + @tailrec + def loopEncode(out: ByteBuffer): ByteBuffer = { + val result = encode(in, out, endOfInput = true) + if (result.isUnderflow) { + assert(!in.hasRemaining) + out + } else if (result.isOverflow) { + loopEncode(grow(out)) + } else { + result.throwException() + throw new AssertionError("should not get here") + } + } + + @inline + @tailrec + def loopFlush(out: ByteBuffer): ByteBuffer = { + val result = flush(out) + if (result.isUnderflow) { + out + } else if (result.isOverflow) { + loopFlush(grow(out)) + } else { + result.throwException() + throw new AssertionError("should not get here") + } + } + + reset() + val initLength = (in.remaining * averageBytesPerChar).toInt + val out = loopFlush(loopEncode(ByteBuffer.allocate(initLength))) + out.flip() + out + } + } +} + +object CharsetEncoder { + private final val INIT = 0 + private final val ONGOING = 1 + private final val END = 2 + private final val FLUSHED = 3 +} diff --git a/examples/scala-js/javalib/src/main/scala/java/nio/charset/CoderMalfunctionError.scala b/examples/scala-js/javalib/src/main/scala/java/nio/charset/CoderMalfunctionError.scala new file mode 100644 index 0000000..33174f3 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/nio/charset/CoderMalfunctionError.scala @@ -0,0 +1,3 @@ +package java.nio.charset + +class CoderMalfunctionError(cause: Exception) extends Error(cause) diff --git a/examples/scala-js/javalib/src/main/scala/java/nio/charset/CoderResult.scala b/examples/scala-js/javalib/src/main/scala/java/nio/charset/CoderResult.scala new file mode 100644 index 0000000..fdc63cc --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/nio/charset/CoderResult.scala @@ -0,0 +1,78 @@ +package java.nio.charset + +import scala.annotation.switch + +import scala.collection.mutable + +import java.nio._ + +class CoderResult private (kind: Int, _length: Int) { + import CoderResult._ + + @inline def isUnderflow(): Boolean = kind == Underflow + @inline def isOverflow(): Boolean = kind == Overflow + @inline def isMalformed(): Boolean = kind == Malformed + @inline def isUnmappable(): Boolean = kind == Unmappable + + @inline def isError(): Boolean = isMalformed || isUnmappable + + @inline def length(): Int = { + val l = _length + if (l < 0) + throw new UnsupportedOperationException + l + } + + def throwException(): Unit = (kind: @switch) match { + case Overflow => throw new BufferOverflowException + case Underflow => throw new BufferUnderflowException + case Malformed => throw new MalformedInputException(_length) + case Unmappable => throw new UnmappableCharacterException(_length) + } +} + +object CoderResult { + private final val Underflow = 0 + private final val Overflow = 1 + private final val Malformed = 2 + private final val Unmappable = 3 + + val OVERFLOW: CoderResult = new CoderResult(Overflow, -1) + val UNDERFLOW: CoderResult = new CoderResult(Underflow, -1) + + private val Malformed1 = new CoderResult(Malformed, 1) + private val Malformed2 = new CoderResult(Malformed, 2) + private val Malformed3 = new CoderResult(Malformed, 3) + private val Malformed4 = new CoderResult(Malformed, 4) + + private val uniqueMalformed = mutable.Map.empty[Int, CoderResult] + + private val Unmappable1 = new CoderResult(Unmappable, 1) + private val Unmappable2 = new CoderResult(Unmappable, 2) + private val Unmappable3 = new CoderResult(Unmappable, 3) + private val Unmappable4 = new CoderResult(Unmappable, 4) + + private val uniqueUnmappable = mutable.Map.empty[Int, CoderResult] + + @inline def malformedForLength(length: Int): CoderResult = (length: @switch) match { + case 1 => Malformed1 + case 2 => Malformed2 + case 3 => Malformed3 + case 4 => Malformed4 + case _ => malformedForLengthImpl(length) + } + + private def malformedForLengthImpl(length: Int): CoderResult = + uniqueMalformed.getOrElseUpdate(length, new CoderResult(Malformed, length)) + + @inline def unmappableForLength(length: Int): CoderResult = (length: @switch) match { + case 1 => Unmappable1 + case 2 => Unmappable2 + case 3 => Unmappable3 + case 4 => Unmappable4 + case _ => unmappableForLengthImpl(length) + } + + private def unmappableForLengthImpl(length: Int): CoderResult = + uniqueUnmappable.getOrElseUpdate(length, new CoderResult(Unmappable, length)) +} diff --git a/examples/scala-js/javalib/src/main/scala/java/nio/charset/CodingErrorAction.scala b/examples/scala-js/javalib/src/main/scala/java/nio/charset/CodingErrorAction.scala new file mode 100644 index 0000000..63b48bb --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/nio/charset/CodingErrorAction.scala @@ -0,0 +1,11 @@ +package java.nio.charset + +class CodingErrorAction private (name: String) { + override def toString(): String = name +} + +object CodingErrorAction { + val IGNORE = new CodingErrorAction("IGNORE") + val REPLACE = new CodingErrorAction("REPLACE") + val REPORT = new CodingErrorAction("REPORT") +} diff --git a/examples/scala-js/javalib/src/main/scala/java/nio/charset/MalformedInputException.scala b/examples/scala-js/javalib/src/main/scala/java/nio/charset/MalformedInputException.scala new file mode 100644 index 0000000..4c91c1b --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/nio/charset/MalformedInputException.scala @@ -0,0 +1,9 @@ +package java.nio.charset + +class MalformedInputException( + inputLength: Int) extends CharacterCodingException { + def getInputLength(): Int = inputLength + + override def getMessage(): String = + "Input length = " + inputLength +} diff --git a/examples/scala-js/javalib/src/main/scala/java/nio/charset/StandardCharsets.scala b/examples/scala-js/javalib/src/main/scala/java/nio/charset/StandardCharsets.scala new file mode 100644 index 0000000..38f3f98 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/nio/charset/StandardCharsets.scala @@ -0,0 +1,14 @@ +package java.nio.charset + +final class StandardCharsets private {} + +object StandardCharsets { + import scala.scalajs.niocharset.{StandardCharsets => SC} + + def ISO_8859_1: Charset = SC.ISO_8859_1 + def US_ASCII: Charset = SC.US_ASCII + def UTF_8: Charset = SC.UTF_8 + def UTF_16BE: Charset = SC.UTF_16BE + def UTF_16LE: Charset = SC.UTF_16LE + def UTF_16: Charset = SC.UTF_16 +} diff --git a/examples/scala-js/javalib/src/main/scala/java/nio/charset/UnmappableCharacterException.scala b/examples/scala-js/javalib/src/main/scala/java/nio/charset/UnmappableCharacterException.scala new file mode 100644 index 0000000..5748f70 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/nio/charset/UnmappableCharacterException.scala @@ -0,0 +1,9 @@ +package java.nio.charset + +class UnmappableCharacterException( + inputLength: Int) extends CharacterCodingException { + def getInputLength(): Int = inputLength + + override def getMessage(): String = + "Input length = " + inputLength +} diff --git a/examples/scala-js/javalib/src/main/scala/java/nio/charset/UnsupportedCharsetException.scala b/examples/scala-js/javalib/src/main/scala/java/nio/charset/UnsupportedCharsetException.scala new file mode 100644 index 0000000..97a7a4e --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/nio/charset/UnsupportedCharsetException.scala @@ -0,0 +1,6 @@ +package java.nio.charset + +class UnsupportedCharsetException( + charsetName: String) extends IllegalArgumentException(charsetName) { + def getCharsetName(): String = charsetName +} diff --git a/examples/scala-js/javalib/src/main/scala/java/util/Arrays.scala b/examples/scala-js/javalib/src/main/scala/java/util/Arrays.scala new file mode 100644 index 0000000..ed9afd1 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/util/Arrays.scala @@ -0,0 +1,401 @@ +package java.util + +import scala.annotation.tailrec + +object Arrays { + def sort[T <: Object](array: Array[Object], comparator: Comparator[T]): Unit = { + scala.util.Sorting.stableSort[Object](array, + (a: Object, b: Object) => + comparator.compare(a.asInstanceOf[T], b.asInstanceOf[T]) < 0) + } + + def fill(a: Array[Boolean], value: Boolean): Unit = + fillImpl(a, value) + + def fill(a: Array[Boolean], fromIndex: Int, toIndex: Int, value: Boolean): Unit = + fillImpl(a, fromIndex, toIndex, value) + + def fill(a: Array[Byte], value: Byte): Unit = + fillImpl(a, value) + + def fill(a: Array[Byte], fromIndex: Int, toIndex: Int, value: Byte): Unit = + fillImpl(a, fromIndex, toIndex, value) + + def fill(a: Array[Char], value: Char): Unit = + fillImpl(a, value) + + def fill(a: Array[Char], fromIndex: Int, toIndex: Int, value: Char): Unit = + fillImpl(a, fromIndex, toIndex, value) + + def fill(a: Array[Short], value: Short): Unit = + fillImpl(a, value) + + def fill(a: Array[Short], fromIndex: Int, toIndex: Int, value: Short): Unit = + fillImpl(a, fromIndex, toIndex, value) + + def fill(a: Array[Int], value: Int): Unit = + fillImpl(a, value) + + def fill(a: Array[Int], fromIndex: Int, toIndex: Int, value: Int): Unit = + fillImpl(a, fromIndex, toIndex, value) + + def fill(a: Array[Long], value: Long): Unit = + fillImpl(a, value) + + def fill(a: Array[Long], fromIndex: Int, toIndex: Int, value: Long): Unit = + fillImpl(a, fromIndex, toIndex, value) + + def fill(a: Array[Float], value: Float): Unit = + fillImpl(a, value) + + def fill(a: Array[Float], fromIndex: Int, toIndex: Int, value: Float): Unit = + fillImpl(a, fromIndex, toIndex, value) + + def fill(a: Array[Double], value: Double): Unit = + fillImpl(a, value) + + def fill(a: Array[Double], fromIndex: Int, toIndex: Int, value: Double): Unit = + fillImpl(a, fromIndex, toIndex, value) + + private def fillImpl[@specialized T](a: Array[T], value: T): Unit = { + var i = 0 + while (i != a.length) { + a(i) = value + i += 1 + } + } + + private def fillImpl[@specialized T](a: Array[T], + fromIndex: Int, toIndex: Int, value: T): Unit = { + if (fromIndex > toIndex) + throw new IllegalArgumentException + if (fromIndex < 0 || toIndex > a.length) + throw new ArrayIndexOutOfBoundsException + + var i = fromIndex + while (i != toIndex) { + a(i) = value + i += 1 + } + } + + def fill(a: Array[AnyRef], value: AnyRef): Unit = { + var i = 0 + while (i < a.length) { + a(i) = value + i += 1 + } + } + + def fill(a: Array[AnyRef], + fromIndex: Int, toIndex: Int, value: AnyRef): Unit = { + if (fromIndex > toIndex) + throw new IllegalArgumentException + if (fromIndex < 0 || toIndex > a.length) + throw new ArrayIndexOutOfBoundsException + + var i = fromIndex + while (i < toIndex) { + a(i) = value + i += 1 + } + } + + @inline private def checkIndexForBinarySearch( + length: Int, start: Int, end: Int): Unit = { + if (start > end) + throw new IllegalArgumentException("fromIndex(" + start + ") > toIndex(" + end + ")") + if (start < 0) + throw new ArrayIndexOutOfBoundsException("Array index out of range: " + start) + if (end > length) + throw new ArrayIndexOutOfBoundsException("Array index out of range: " + end) + } + + def binarySearch(a: Array[Char], key: Char): Int = + binarySearchImpl[Char](a, 0, a.length, key, _ < _) + + def binarySearch(a: Array[Char], + startIndex: Int, endIndex: Int, key: Char): Int = { + checkIndexForBinarySearch(a.length, startIndex, endIndex) + binarySearchImpl[Char](a, startIndex, endIndex, key, _ < _) + } + + def binarySearch(a: Array[Short], key: Short): Int = + binarySearchImpl[Short](a, 0, a.length, key, _ < _) + + def binarySearch(a: Array[Short], + startIndex: Int, endIndex: Int, key: Short): Int = { + checkIndexForBinarySearch(a.length, startIndex, endIndex) + binarySearchImpl[Short](a, startIndex, endIndex, key, _ < _) + } + + def binarySearch(a: Array[Int], key: Int): Int = + binarySearchImpl[Int](a, 0, a.length, key, _ < _) + + def binarySearch(a: Array[Int], + startIndex: Int, endIndex: Int, key: Int): Int = { + checkIndexForBinarySearch(a.length, startIndex, endIndex) + binarySearchImpl[Int](a, startIndex, endIndex, key, _ < _) + } + + def binarySearch(a: Array[Long], key: Long): Int = + binarySearchImpl[Long](a, 0, a.length, key, _ < _) + + def binarySearch(a: Array[Long], + startIndex: Int, endIndex: Int, key: Long): Int = { + checkIndexForBinarySearch(a.length, startIndex, endIndex) + binarySearchImpl[Long](a, startIndex, endIndex, key, _ < _) + } + + def binarySearch(a: Array[Float], key: Float): Int = + binarySearchImpl[Float](a, 0, a.length, key, _ < _) + + def binarySearch(a: Array[Float], + startIndex: Int, endIndex: Int, key: Float): Int = { + checkIndexForBinarySearch(a.length, startIndex, endIndex) + binarySearchImpl[Float](a, startIndex, endIndex, key, _ < _) + } + + def binarySearch(a: Array[Double], key: Double): Int = + binarySearchImpl[Double](a, 0, a.length, key, _ < _) + + def binarySearch(a: Array[Double], + startIndex: Int, endIndex: Int, key: Double): Int = { + checkIndexForBinarySearch(a.length, startIndex, endIndex) + binarySearchImpl[Double](a, startIndex, endIndex, key, _ < _) + } + + @inline + @tailrec + private def binarySearchImpl[@specialized T](a: Array[T], + startIndex: Int, endIndex: Int, key: T, lt: (T, T) => Boolean): Int = { + if (startIndex == endIndex) { + // Not found + -startIndex - 1 + } else { + // Indices are unsigned 31-bit integer, so this does not overflow + val mid = (startIndex + endIndex) >>> 1 + val elem = a(mid) + if (lt(key, elem)) { + binarySearchImpl(a, startIndex, mid, key, lt) + } else if (key == elem) { + // Found + mid + } else { + binarySearchImpl(a, mid + 1, endIndex, key, lt) + } + } + } + + def binarySearch(a: Array[AnyRef], key: AnyRef): Int = + binarySearchImplRef(a, 0, a.length, key) + + def binarySearch(a: Array[AnyRef], + startIndex: Int, endIndex: Int, key: AnyRef): Int = { + checkIndexForBinarySearch(a.length, startIndex, endIndex) + binarySearchImplRef(a, startIndex, endIndex, key) + } + + @inline + @tailrec + def binarySearchImplRef(a: Array[AnyRef], + startIndex: Int, endIndex: Int, key: AnyRef): Int = { + if (startIndex == endIndex) { + // Not found + -startIndex - 1 + } else { + // Indices are unsigned 31-bit integer, so this does not overflow + val mid = (startIndex + endIndex) >>> 1 + val cmp = key.asInstanceOf[Comparable[AnyRef]].compareTo(a(mid)) + if (cmp < 0) { + binarySearchImplRef(a, startIndex, mid, key) + } else if (cmp == 0) { + // Found + mid + } else { + binarySearchImplRef(a, mid + 1, endIndex, key) + } + } + } + + def copyOf(original: Array[Boolean], newLength: Int): Array[Boolean] = + copyOfImpl(original, newLength, new Array(_)) + + def copyOfRange(original: Array[Boolean], start: Int, end: Int): Array[Boolean] = + copyOfRangeImpl(original, start, end, new Array(_)) + + def copyOf(original: Array[Char], newLength: Int): Array[Char] = + copyOfImpl(original, newLength, new Array(_)) + + def copyOfRange(original: Array[Char], start: Int, end: Int): Array[Char] = + copyOfRangeImpl(original, start, end, new Array(_)) + + def copyOf(original: Array[Byte], newLength: Int): Array[Byte] = + copyOfImpl(original, newLength, new Array(_)) + + def copyOfRange(original: Array[Byte], start: Int, end: Int): Array[Byte] = + copyOfRangeImpl(original, start, end, new Array(_)) + + def copyOf(original: Array[Short], newLength: Int): Array[Short] = + copyOfImpl(original, newLength, new Array(_)) + + def copyOfRange(original: Array[Short], start: Int, end: Int): Array[Short] = + copyOfRangeImpl(original, start, end, new Array(_)) + + def copyOf(original: Array[Int], newLength: Int): Array[Int] = + copyOfImpl(original, newLength, new Array(_)) + + def copyOfRange(original: Array[Int], start: Int, end: Int): Array[Int] = + copyOfRangeImpl(original, start, end, new Array(_)) + + def copyOf(original: Array[Long], newLength: Int): Array[Long] = + copyOfImpl(original, newLength, new Array(_)) + + def copyOfRange(original: Array[Long], start: Int, end: Int): Array[Long] = + copyOfRangeImpl(original, start, end, new Array(_)) + + def copyOf(original: Array[Float], newLength: Int): Array[Float] = + copyOfImpl(original, newLength, new Array(_)) + + def copyOfRange(original: Array[Float], start: Int, end: Int): Array[Float] = + copyOfRangeImpl(original, start, end, new Array(_)) + + def copyOf(original: Array[Double], newLength: Int): Array[Double] = + copyOfImpl(original, newLength, new Array(_)) + + def copyOfRange(original: Array[Double], start: Int, end: Int): Array[Double] = + copyOfRangeImpl(original, start, end, new Array(_)) + + @inline private def copyOfImpl[@specialized T](original: Array[T], + newLength: Int, newArray: Int => Array[T]): Array[T] = { + checkArrayLength(newLength) + val copyLength = Math.min(newLength, original.length) + val ret = newArray(newLength) + System.arraycopy(original, 0, ret, 0, copyLength) + ret + } + + @inline private def copyOfRangeImpl[@specialized T](original: Array[T], + start: Int, end: Int, newArray: Int => Array[T]): Array[T] = { + checkIndicesForCopyOfRange(original.length, start, end) + val retLength = end - start + val copyLength = Math.min(retLength, original.length - start) + val ret = newArray(retLength) + System.arraycopy(original, start, ret, 0, copyLength) + ret + } + + def copyOf(original: Array[AnyRef], newLength: Int): Array[AnyRef] = { + checkArrayLength(newLength) + val copyLength = Math.min(newLength, original.length) + val ret = java.lang.reflect.Array.newInstance( + original.getClass.getComponentType, newLength).asInstanceOf[Array[AnyRef]] + System.arraycopy(original, 0, ret, 0, copyLength) + ret + } + + def copyOfRange(original: Array[AnyRef], start: Int, end: Int): Array[AnyRef] = { + checkIndicesForCopyOfRange(original.length, start, end) + val retLength = end - start + val copyLength = Math.min(retLength, original.length - start) + val ret = java.lang.reflect.Array.newInstance( + original.getClass.getComponentType, retLength).asInstanceOf[Array[AnyRef]] + System.arraycopy(original, start, ret, 0, copyLength) + ret + } + + @inline private def checkArrayLength(len: Int): Unit = { + if (len < 0) + throw new NegativeArraySizeException + } + + @inline private def checkIndicesForCopyOfRange( + len: Int, start: Int, end: Int): Unit = { + if (start > end) + throw new IllegalArgumentException(start + " > " + end) + if (start < 0 || start > len) + throw new ArrayIndexOutOfBoundsException + } + + def hashCode(a: Array[Boolean]): Int = { + if (a == null) 0 + else a.foldLeft(1)((acc, x) => 31*acc + new java.lang.Boolean(x).hashCode) + } + + def hashCode(a: Array[Char]): Int = { + if (a == null) 0 + else a.foldLeft(1)((acc, x) => 31*acc + new java.lang.Character(x).hashCode) + } + + def hashCode(a: Array[Byte]): Int = { + if (a == null) 0 + else a.foldLeft(1)((acc, x) => 31*acc + new java.lang.Byte(x).hashCode) + } + + def hashCode(a: Array[Short]): Int = { + if (a == null) 0 + else a.foldLeft(1)((acc, x) => 31*acc + new java.lang.Short(x).hashCode) + } + + def hashCode(a: Array[Int]): Int = { + if (a == null) 0 + else a.foldLeft(1)((acc, x) => 31*acc + new java.lang.Integer(x).hashCode) + } + + def hashCode(a: Array[Long]): Int = { + if (a == null) 0 + else a.foldLeft(1)((acc, x) => 31*acc + new java.lang.Long(x).hashCode) + } + + def hashCode(a: Array[Float]): Int = { + if (a == null) 0 + else a.foldLeft(1)((acc, x) => 31*acc + new java.lang.Float(x).hashCode) + } + + def hashCode(a: Array[Double]): Int = { + if (a == null) 0 + else a.foldLeft(1)((acc, x) => 31*acc + new java.lang.Double(x).hashCode) + } + + def hashCode(a: Array[AnyRef]): Int = { + if (a == null) 0 + else a.foldLeft(1)((acc, x) => 31*acc + (if (x == null) 0 else x.hashCode)) + } + + def equals(a: Array[Boolean], b: Array[Boolean]): Boolean = + (a eq b) || (a != null && b != null && a.length == b.length && + (0 until a.size).forall(i => a(i) == b(i))) + + def equals(a: Array[Char], b: Array[Char]): Boolean = + (a eq b) || (a != null && b != null && a.length == b.length && + (0 until a.size).forall(i => a(i) == b(i))) + + def equals(a: Array[Byte], b: Array[Byte]): Boolean = + (a eq b) || (a != null && b != null && a.length == b.length && + (0 until a.size).forall(i => a(i) == b(i))) + + def equals(a: Array[Short], b: Array[Short]): Boolean = + (a eq b) || (a != null && b != null && a.length == b.length && + (0 until a.size).forall(i => a(i) == b(i))) + + def equals(a: Array[Int], b: Array[Int]): Boolean = + (a eq b) || (a != null && b != null && a.length == b.length && + (0 until a.size).forall(i => a(i) == b(i))) + + def equals(a: Array[Long], b: Array[Long]): Boolean = + (a eq b) || (a != null && b != null && a.length == b.length && + (0 until a.size).forall(i => a(i) == b(i))) + + def equals(a: Array[Float], b: Array[Float]): Boolean = + (a eq b) || (a != null && b != null && a.length == b.length && + (0 until a.size).forall(i => a(i) == b(i))) + + def equals(a: Array[Double], b: Array[Double]): Boolean = + (a eq b) || (a != null && b != null && a.length == b.length && + (0 until a.size).forall(i => a(i) == b(i))) + + def equals(a: Array[AnyRef], b: Array[AnyRef]): Boolean = + (a eq b) || (a != null && b != null && a.length == b.length && + (0 until a.size).forall(i => a(i) == b(i))) + +} diff --git a/examples/scala-js/javalib/src/main/scala/java/util/Comparator.scala b/examples/scala-js/javalib/src/main/scala/java/util/Comparator.scala new file mode 100644 index 0000000..d63c48e --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/util/Comparator.scala @@ -0,0 +1,6 @@ +package java.util + +trait Comparator[A] { + def compare(o1: A, o2: A): Int + def equals(obj: Any): Boolean +} diff --git a/examples/scala-js/javalib/src/main/scala/java/util/Date.scala b/examples/scala-js/javalib/src/main/scala/java/util/Date.scala new file mode 100644 index 0000000..8f4e85f --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/util/Date.scala @@ -0,0 +1,147 @@ +/** + * 2014 Matt Seddon + * This code is donated in full to the scala-js project. + */ + +package java.util + +import scalajs.js + +class Date private (private val date: js.Date) extends Object + with Serializable with Cloneable with Comparable[Date] { + + import Date._ + + def this() = this(new js.Date()) + + @Deprecated + def this(year: Int, month: Int, date: Int, hrs: Int, min: Int, sec: Int) = { + this(new js.Date()) + this.date.setFullYear(1900 + year, month, date) + this.date.setHours(hrs, min, sec, 0) + } + + @Deprecated + def this(year: Int, month: Int, date: Int, hrs: Int, min: Int) = + this(year, month, date, hrs, min, 0) + + @Deprecated + def this(year: Int, month: Int, date: Int) = + this(year, month, date, 0, 0, 0) + + def this(date: Long) = this(new js.Date(date)) + + @Deprecated + def this(date: String) = this(new js.Date(date)) + + def after(when: Date): Boolean = date.getTime() > when.date.getTime() + + def before(when: Date): Boolean = date.getTime() < when.date.getTime() + + override def clone(): Object = new Date(new js.Date(date.getTime())) + + override def compareTo(anotherDate: Date): Int = + date.getTime().compareTo(anotherDate.date.getTime()) + + override def equals(obj: Any): Boolean = obj match { + case d: Date => d.date.getTime() == date.getTime() + case _ => false + } + + override def hashCode(): Int = date.getTime().hashCode() + + @Deprecated + def getDate(): Int = date.getDate() + + @Deprecated + def getDay(): Int = date.getDay() + + @Deprecated + def getHours(): Int = date.getHours() + + @Deprecated + def getMinutes(): Int = date.getMinutes() + + @Deprecated + def getMonth(): Int = date.getMonth() + + @Deprecated + def getSeconds(): Int = date.getSeconds() + + def getTime(): Long = date.getTime().toLong + + @Deprecated + def getTimeZoneOffset(): Int = date.getTimezoneOffset() + + @Deprecated + def getYear(): Int = date.getFullYear() - 1900 + + @Deprecated + def setDate(date: Int): Unit = this.date.setDate(date) + + @Deprecated + def setHours(hours: Int): Unit = date.setHours(hours) + + @Deprecated + def setMinutes(minutes: Int): Unit = date.setMinutes(minutes) + + @Deprecated + def setMonth(month: Int): Unit = date.setMonth(month) + + @Deprecated + def setSeconds(seconds: Int): Unit = date.setSeconds(seconds) + + def setTime(time: Long): Unit = date.setTime(time) + + @Deprecated + def setYear(year: Int): Unit = date.setFullYear(1900 + year) + + @Deprecated + def toGMTString(): String = { + date.getUTCDate() + " " + Months(date.getUTCMonth()) + " " + + date.getUTCFullYear() + " " + pad0(date.getUTCHours()) + ":" + + pad0(date.getUTCMinutes()) + ":" + + pad0(date.getUTCSeconds()) +" GMT" + } + + @Deprecated + def toLocaleString(): String = { + date.getDate() + "-" + Months(date.getMonth()) + "-" + + date.getFullYear() + "-" + pad0(date.getHours()) + ":" + + pad0(date.getMinutes()) + ":" + pad0(date.getSeconds()) + } + + override def toString(): String = { + val offset = -date.getTimezoneOffset() + val sign = if(offset < 0) "-" else "+" + val hours = pad0(Math.abs(offset) / 60) + val mins = pad0(Math.abs(offset) % 60) + Days(date.getDay()) + " "+ Months(date.getMonth()) + " " + + pad0(date.getHours()) + ":" + pad0(date.getMinutes()) + ":" + + pad0(date.getSeconds()) + " GMT" + sign + hours + mins + " " + + date.getFullYear() + } +} + +object Date { + private val Days = Array( + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat") + + private val Months = Array( + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec") + + private def pad0(i: Int): String = { + val str = "" + i + if (str.length < 2) "0" + str else str + } + + @Deprecated + def UTC(year: Int, month: Int, date: Int, + hrs: Int, min: Int, sec: Int): Long = + js.Date.UTC(year + 1900, month, date, hrs, min, sec).toLong + + @Deprecated + def parse(string: String): Long = + new Date(new js.Date(string)).getTime.toLong +} diff --git a/examples/scala-js/javalib/src/main/scala/java/util/Formattable.scala b/examples/scala-js/javalib/src/main/scala/java/util/Formattable.scala new file mode 100644 index 0000000..e651fbb --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/util/Formattable.scala @@ -0,0 +1,5 @@ +package java.util + +trait Formattable { + def formatTo(formatter: Formatter, flags: Int, width: Int, precision: Int): Unit +} diff --git a/examples/scala-js/javalib/src/main/scala/java/util/FormattableFlags.scala b/examples/scala-js/javalib/src/main/scala/java/util/FormattableFlags.scala new file mode 100644 index 0000000..02f5bce --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/util/FormattableFlags.scala @@ -0,0 +1,7 @@ +package java.util + +object FormattableFlags { + final val ALTERNATE = 4 + final val LEFT_JUSTIFY = 1 + final val UPPERCASE = 2 +} diff --git a/examples/scala-js/javalib/src/main/scala/java/util/Formatter.scala b/examples/scala-js/javalib/src/main/scala/java/util/Formatter.scala new file mode 100644 index 0000000..5e0ab22 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/util/Formatter.scala @@ -0,0 +1,273 @@ +package java.util + +import scala.annotation.switch +import scala.scalajs.js + +import java.io._ +import java.lang._ + +final class Formatter(private val dest: Appendable) extends Closeable with Flushable { + import Formatter._ + + var closed = false + + def this() = this(new StringBuilder()) + + def close(): Unit = { + if (!closed) { + dest match { + case cl: Closeable => cl.close() + case _ => + } + } + closed = true + } + + def flush(): Unit = ifNotClosed { + dest match { + case fl: Flushable => fl.flush() + case _ => + } + } + + // Begin implem of format() + + def format(format_in: String, args: Array[AnyRef]): Formatter = ifNotClosed { + import js.Any.fromDouble // to have .toFixed and .toExponential on Doubles + + var fmt: String = format_in + var lastImplicitIndex: Int = 0 + var lastIndex: Int = 0 // required for < flag + + while (!fmt.isEmpty) { + fmt match { + case RegularChunk(matchResult) => + fmt = fmt.substring(matchResult(0).get.length) + dest.append(matchResult(0).get) + + case DoublePercent(_) => + fmt = fmt.substring(2) + dest.append('%') + + case EOLChunk(_) => + fmt = fmt.substring(2) + dest.append('\n') + + case FormattedChunk(matchResult) => + fmt = fmt.substring(matchResult(0).get.length) + + val flags = matchResult(2).get + def hasFlag(flag: String) = flags.indexOf(flag) >= 0 + + val indexStr = matchResult(1).getOrElse("") + val index = if (!indexStr.isEmpty) { + Integer.parseInt(indexStr) + } else if (hasFlag("<")) { + lastIndex + } else { + lastImplicitIndex += 1 + lastImplicitIndex + } + lastIndex = index + if (index <= 0 || index > args.length) + throw new MissingFormatArgumentException(matchResult(5).get) + val arg = args(index-1) + + val widthStr = matchResult(3).getOrElse("") + val hasWidth = !widthStr.isEmpty + val width = + if (hasWidth) Integer.parseInt(widthStr) + else 0 + + val precisionStr = matchResult(4).getOrElse("") + val hasPrecision = !precisionStr.isEmpty + val precision = + if (hasPrecision) Integer.parseInt(precisionStr) + else 0 + + val conversion = matchResult(5).get.charAt(0) + + def intArg: Int = (arg: Any) match { + case arg: Int => arg + case arg: Char => arg.toInt + } + def numberArg: scala.Double = (arg: Any) match { + case arg: Number => arg.doubleValue() + case arg: Char => arg.toDouble + } + + def padCaptureSign(argStr: String, prefix: String) = { + val firstChar = argStr.charAt(0) + if (firstChar == '+' || firstChar == '-') + pad(argStr.substring(1), firstChar+prefix) + else + pad(argStr, prefix) + } + + def strRepeat(s: String, times: Int) = { + var result: String = "" + var i = times + while (i > 0) { + result += s + i -= 1 + } + result + } + + def with_+(s: String, preventZero: scala.Boolean = false) = { + if (s.charAt(0) != '-') { + if (hasFlag("+")) + pad(s, "+", preventZero) + else if (hasFlag(" ")) + pad(s, " ", preventZero) + else + pad(s, "", preventZero) + } else { + if (hasFlag("(")) + pad(s.substring(1) + ")", "(", preventZero) + else + pad(s.substring(1), "-", preventZero) + } + } + + def pad(argStr: String, prefix: String = "", + preventZero: Boolean = false) = { + val prePadLen = argStr.length + prefix.length + + val padStr = { + if (width <= prePadLen) { + prefix + argStr + } else { + val padRight = hasFlag("-") + val padZero = hasFlag("0") && !preventZero + val padLength = width - prePadLen + val padChar: String = if (padZero) "0" else " " + val padding = strRepeat(padChar, padLength) + + if (padZero && padRight) + throw new java.util.IllegalFormatFlagsException(flags) + else if (padRight) prefix + argStr + padding + else if (padZero) prefix + padding + argStr + else padding + prefix + argStr + } + } + + val casedStr = + if (conversion.isUpper) padStr.toUpperCase() + else padStr + dest.append(casedStr) + } + + (conversion: @switch) match { + case 'b' | 'B' => pad { arg match { + case null => "false" + case b: Boolean => String.valueOf(b) + case _ => "true" + } } + case 'h' | 'H' => pad { + if (arg eq null) "null" + else Integer.toHexString(arg.hashCode) + } + case 's' | 'S' => arg match { + case null if !hasFlag("#") => pad("null") + case formattable: Formattable => + val flags = ( + (if (hasFlag("-")) FormattableFlags.LEFT_JUSTIFY else 0) | + (if (hasFlag("#")) FormattableFlags.ALTERNATE else 0) | + (if (conversion.isUpper) FormattableFlags.UPPERCASE else 0) + ) + + formattable.formatTo(this, flags, + if (hasWidth) width.toInt else -1, + if (hasPrecision) precision.toInt else -1) + None // no further processing + case t: AnyRef if !hasFlag("#") => pad(t.toString) + case _ => + throw new FormatFlagsConversionMismatchException("#", 's') + } + case 'c' | 'C' => + pad(js.String.fromCharCode(intArg)) + case 'd' => + with_+(numberArg.toString()) + case 'o' => + val str = (arg: Any) match { + case arg: scala.Int => Integer.toOctalString(arg) + case arg: scala.Long => Long.toOctalString(arg) + } + padCaptureSign(str, if (hasFlag("#")) "0" else "") + case 'x' | 'X' => + val str = (arg: Any) match { + case arg: scala.Int => Integer.toHexString(arg) + case arg: scala.Long => Long.toHexString(arg) + } + padCaptureSign(str, if (hasFlag("#")) "0x" else "") + case 'e' | 'E' => + sciNotation(if (hasPrecision) precision else 6) + case 'g' | 'G' => + val m = Math.abs(numberArg) + // precision handling according to JavaDoc + // precision here means number of significant digits + // not digits after decimal point + val p = + if (!hasPrecision) 6 + else if (precision == 0) 1 + else precision + // between 1e-4 and 10e(p): display as fixed + if (m >= 1e-4 && m < Math.pow(10, p)) { + val sig = Math.ceil(Math.log10(m)) + with_+(numberArg.toFixed(Math.max(p - sig, 0))) + } else sciNotation(p - 1) + case 'f' => + with_+({ + // JavaDoc: 6 is default precision + numberArg.toFixed(if (hasPrecision) precision else 6) + }, numberArg.isNaN || numberArg.isInfinite) + } + + def sciNotation(precision: Int) = { + val exp = numberArg.toExponential(precision) + with_+({ + // check if we need additional 0 padding in exponent + // JavaDoc: at least 2 digits + if ("e" == exp.charAt(exp.length - 3)) { + exp.substring(0, exp.length - 1) + "0" + + exp.charAt(exp.length - 1) + } else exp + }, numberArg.isNaN || numberArg.isInfinite) + } + } + } + + this + } + + def ioException(): IOException = null + def locale(): Locale = ifNotClosed { null } + def out(): Appendable = ifNotClosed { dest } + + override def toString(): String = out().toString() + + @inline private def ifNotClosed[T](body: => T): T = + if (closed) throwClosedException() + else body + + private def throwClosedException(): Nothing = + throw new FormatterClosedException() + +} + +object Formatter { + + private class RegExpExtractor(val regexp: js.RegExp) { + def unapply(str: String): Option[js.RegExp.ExecResult] = { + Option(regexp.exec(str)) + } + } + + private val RegularChunk = new RegExpExtractor(new js.RegExp("""^[^\x25]+""")) + private val DoublePercent = new RegExpExtractor(new js.RegExp("""^\x25{2}""")) + private val EOLChunk = new RegExpExtractor(new js.RegExp("""^\x25n""")) + private val FormattedChunk = new RegExpExtractor(new js.RegExp( + """^\x25(?:([1-9]\d*)\$)?([-#+ 0,\(<]*)(\d*)(?:\.(\d+))?([A-Za-z])""")) + +} diff --git a/examples/scala-js/javalib/src/main/scala/java/util/Random.scala b/examples/scala-js/javalib/src/main/scala/java/util/Random.scala new file mode 100644 index 0000000..80428fb --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/util/Random.scala @@ -0,0 +1,119 @@ +package java.util + +import scala.annotation.tailrec + +import scala.scalajs.js + +class Random(seed_in: Long) extends AnyRef with java.io.Serializable { + + private var seed: Long = _ + + // see nextGaussian() + private var nextNextGaussian: Double = _ + private var haveNextNextGaussian: Boolean = false + + setSeed(seed_in) + + def this() = this(Random.randomSeed()) + + def setSeed(seed_in: Long): Unit = { + seed = (seed_in ^ 0x5DEECE66DL) & ((1L << 48) - 1) + haveNextNextGaussian = false + } + + protected def next(bits: Int): Int = { + seed = (seed * 0x5DEECE66DL + 0xBL) & ((1L << 48) - 1) + (seed >>> (48 - bits)).toInt + } + + def nextDouble(): Double = + ((next(26).toLong << 27) + next(27)) / (1L << 53).toDouble + + def nextBoolean(): Boolean = next(1) != 0 + + def nextInt(): Int = next(32) + + def nextInt(n: Int): Int = { + if (n <= 0) + throw new IllegalArgumentException("n must be positive"); + + if ((n & -n) == n) // i.e., n is a power of 2 + ((n * next(31).toLong) >> 31).toInt + else { + @tailrec + def loop(): Int = { + val bits = next(31) + val value = bits % n + if (bits - value + (n-1) < 0) loop() + else value + } + + loop() + } + } + + def nextLong(): Long = (next(32).toLong << 32) + next(32) + + def nextFloat(): Float = next(24) / (1 << 24).toFloat + + def nextBytes(bytes: Array[Byte]): Unit = { + var i = 0 + while (i < bytes.length) { + var rnd = nextInt() + var n = Math.min(bytes.length - i, 4) + while (n > 0) { + bytes(i) = rnd.toByte + rnd >>= 8 + n -= 1 + i += 1 + } + } + } + + def nextGaussian(): Double = { + // See http://www.protonfish.com/jslib/boxmuller.shtml + + /* The Box-Muller algorithm produces two random numbers at once. We save + * the second one in `nextNextGaussian` to be used by the next call to + * nextGaussian(). + */ + + if (haveNextNextGaussian) { + haveNextNextGaussian = false + return nextNextGaussian + } + + var x, y, rds: Double = 0 + + /* Get two random numbers from -1 to 1. + * If the radius is zero or greater than 1, throw them out and pick two new + * ones. + * Rejection sampling throws away about 20% of the pairs. + */ + do { + x = nextDouble()*2-1 + y = nextDouble()*2-1 + rds = x*x + y*y + } while (rds == 0 || rds > 1) + + val c = Math.sqrt(-2 * Math.log(rds) / rds) + + // Save y*c for next time + nextNextGaussian = y*c + haveNextNextGaussian = true + + // And return x*c + x*c + } +} + +object Random { + + /** Generate a random long from JS RNG to seed a new Random */ + private def randomSeed(): Long = + (randomInt().toLong << 32) | (randomInt().toLong & 0xffffffffL) + + private def randomInt(): Int = + (Math.floor(js.Math.random() * 4294967296.0) - 2147483648.0).toInt + +} diff --git a/examples/scala-js/javalib/src/main/scala/java/util/Throwables.scala b/examples/scala-js/javalib/src/main/scala/java/util/Throwables.scala new file mode 100644 index 0000000..c4bb3d6 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/util/Throwables.scala @@ -0,0 +1,166 @@ +package java.util + +class ServiceConfigurationError(s: String, e: Throwable) extends Error(s, e) { + def this(s: String) = this(s, null) +} + +class ConcurrentModificationException(s: String) extends RuntimeException(s) { + def this() = this(null) +} + +class DuplicateFormatFlagsException private() extends IllegalFormatException { + private var flags: String = null + def this(f: String) { + this() + if (f == null) + throw new NullPointerException() + flags = f + } + def getFlags(): String = flags + override def getMessage(): String = s"Flags = '$flags'" +} + +class EmptyStackException extends RuntimeException + +class FormatFlagsConversionMismatchException private(private val c: Char) extends IllegalFormatException { + private var f: String = null + def this(f: String, c: Char) { + this(c) + if (f == null) + throw new NullPointerException() + this.f = f + } + def getFlags(): String = f + def getConversion(): Char = c + override def getMessage(): String = "Conversion = " + c + ", Flags = " + f +} + +class FormatterClosedException extends IllegalStateException + +class IllegalFormatCodePointException(private val c: Int) extends IllegalFormatException { + def getCodePoint(): Int = c + override def getMessage(): String = s"Code point = $c" +} + +class IllegalFormatConversionException private(private val c: Char) extends IllegalFormatException { + private var arg: Class[_] = null + def this(c: Char, arg: Class[_]) { + this(c) + if (arg == null) + throw new NullPointerException() + this.arg = arg + } + def getConversion(): Char = c + def getArgumentClass(): Class[_] = arg + override def getMessage(): String = s"$c != ${arg.getName()}" +} + +class IllegalFormatException private[util] () extends IllegalArgumentException + +class IllegalFormatFlagsException private() extends IllegalFormatException { + private var flags: String = null + def this(f: String) { + this() + if (f == null) + throw new NullPointerException() + this.flags = f + } + def getFlags(): String = flags + override def getMessage(): String = "Flags = '" + flags + "'" +} + +class IllegalFormatPrecisionException(private val p: Int) extends IllegalFormatException { + def getPrecision(): Int = p + override def getMessage(): String = Integer.toString(p) +} + +class IllegalFormatWidthException(private val w: Int) extends IllegalFormatException { + def getWidth(): Int = w + override def getMessage(): String = Integer.toString(w) +} + +class IllformedLocaleException(s: String, errorIndex: Int) + extends RuntimeException(s + (if(errorIndex < 0) "" else " [at index " + errorIndex + "]")) { + def this() = this(null, -1) + def this(s: String) = this(s, -1) + def getErrorIndex(): Int = errorIndex +} + +class InputMismatchException(s: String) extends NoSuchElementException(s) { + def this() = this(null) +} + +class InvalidPropertiesFormatException(s: String) extends java.io.IOException(s) { + def this(e: Throwable) { + this(if(e == null) null.asInstanceOf[String] else e.toString()) + this.initCause(e) + } + // private def writeObject(out: java.io.ObjectOutputStream) = + // throw new java.io.NotSerializableException("Not serializable.") + // private def readObject(in: java.io.ObjectInputStream) = + // throw new java.io.NotSerializableException("Not serializable.") +} + +class MissingFormatArgumentException private() extends IllegalFormatException { + private var s: String = null + def this(s: String) { + this() + if (s == null) + throw new NullPointerException() + this.s = s + } + def getFormatSpecifier(): String = s + override def getMessage(): String = "Format specifier '" + s + "'" +} + +class MissingFormatWidthException private() extends IllegalFormatException { + private var s: String = null + def this(s: String) { + this() + if (s == null) + throw new NullPointerException() + this.s = s + } + def getFormatSpecifier(): String = s + override def getMessage(): String = s +} + +class MissingResourceException private[util]( + s: String, private var className: String, private var key: String, e: Throwable) + extends RuntimeException(s, e) { + def this(s: String, className: String, key: String) = this(s, className, key, null) + def getClassName(): String = className + def getKey(): String = key +} + +class NoSuchElementException(s: String) extends RuntimeException(s) { + def this() = this(null) +} + +class TooManyListenersException(s: String) extends Exception(s) { + def this() = this(null) +} + +class UnknownFormatConversionException private () extends IllegalFormatException { + private var s: String = null + def this(s: String) { + this() + if (s == null) + throw new NullPointerException() + this.s = s + } + def getConversion(): String = s + override def getMessage(): String = s"Conversion = '$s'" +} + +class UnknownFormatFlagsException private() extends IllegalFormatException { + private var flags: String = null + def this(f: String) { + this() + if (f == null) + throw new NullPointerException() + this.flags = f + } + def getFlags(): String = flags + override def getMessage(): String = "Flags = " + flags +} diff --git a/examples/scala-js/javalib/src/main/scala/java/util/UUID.scala b/examples/scala-js/javalib/src/main/scala/java/util/UUID.scala new file mode 100644 index 0000000..9e6c1d4 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/util/UUID.scala @@ -0,0 +1,163 @@ +package java.util + +import java.lang.{Long => JLong} + +import scala.scalajs.js + +final class UUID private ( + private val i1: Int, private val i2: Int, + private val i3: Int, private val i4: Int, + private[this] var l1: JLong, private[this] var l2: JLong) + extends AnyRef with java.io.Serializable with Comparable[UUID] { + + import UUID._ + + /* Most significant long: + * + * 0xFFFFFFFF00000000 time_low + * 0x00000000FFFF0000 time_mid + * 0x000000000000F000 version + * 0x0000000000000FFF time_hi + * + * Least significant long: + * + * 0xC000000000000000 variant + * 0x3FFF000000000000 clock_seq + * 0x0000FFFFFFFFFFFF node + */ + + def this(mostSigBits: Long, leastSigBits: Long) = { + this((mostSigBits >>> 32).toInt, mostSigBits.toInt, + (leastSigBits >>> 32).toInt, leastSigBits.toInt, + mostSigBits, leastSigBits) + } + + def getLeastSignificantBits(): Long = { + if (l2 eq null) + l2 = (i3.toLong << 32) | (i4.toLong & 0xffffffffL) + l2.longValue + } + + def getMostSignificantBits(): Long = { + if (l1 eq null) + l1 = (i1.toLong << 32) | (i2.toLong & 0xffffffffL) + l1.longValue + } + + def version(): Int = + (i2 & 0xf000) >> 12 + + def variant(): Int = { + if ((i3 & 0x80000000) == 0) { + // MSB0 not set: NCS backwards compatibility variant + 0 + } else if ((i3 & 0x40000000) != 0) { + // MSB1 set: either MS reserved or future reserved + (i3 & 0xe0000000) >>> 29 + } else { + // MSB1 not set: RFC 4122 variant + 2 + } + } + + def timestamp(): Long = { + if (version() != TimeBased) + throw new UnsupportedOperationException("Not a time-based UUID") + (((i2 >>> 16) | ((i2 & 0x0fff) << 16)).toLong << 32) | (i1.toLong & 0xffffffffL) + } + + def clockSequence(): Int = { + if (version() != TimeBased) + throw new UnsupportedOperationException("Not a time-based UUID") + (i3 & 0x3fff0000) >> 16 + } + + def node(): Long = { + if (version() != TimeBased) + throw new UnsupportedOperationException("Not a time-based UUID") + ((i3 & 0xffff).toLong << 32) | (i4.toLong & 0xffffffffL) + } + + override def toString(): String = { + @inline def paddedHex8(i: Int): String = { + val s = Integer.toHexString(i) + "00000000".substring(s.length) + s + } + + @inline def paddedHex4(i: Int): String = { + val s = Integer.toHexString(i) + "0000".substring(s.length) + s + } + + paddedHex8(i1) + "-" + paddedHex4(i2 >>> 16) + "-" + paddedHex4(i2 & 0xffff) + "-" + + paddedHex4(i3 >>> 16) + "-" + paddedHex4(i3 & 0xffff) + paddedHex8(i4) + } + + override def hashCode(): Int = + i1 ^ i2 ^ i3 ^ i4 + + override def equals(that: Any): Boolean = that match { + case that: UUID => + i1 == that.i1 && i2 == that.i2 && i3 == that.i3 && i4 == that.i4 + case _ => + false + } + + def compareTo(that: UUID): Int = { + if (this.i1 != that.i1) { + if (this.i1 > that.i1) 1 else -1 + } else if (this.i2 != that.i2) { + if (this.i2 > that.i2) 1 else -1 + } else if (this.i3 != that.i3) { + if (this.i3 > that.i3) 1 else -1 + } else if (this.i4 != that.i4) { + if (this.i4 > that.i4) 1 else -1 + } else { + 0 + } + } +} + +object UUID { + private final val TimeBased = 1 + private final val DCESecurity = 2 + private final val NameBased = 3 + private final val Random = 4 + + private lazy val rng = new Random() // TODO Use java.security.SecureRandom + + def randomUUID(): UUID = { + val i1 = rng.nextInt() + val i2 = (rng.nextInt() & ~0x0000f000) | 0x00004000 + val i3 = (rng.nextInt() & ~0xc0000000) | 0x80000000 + val i4 = rng.nextInt() + new UUID(i1, i2, i3, i4, null, null) + } + + // Not implemented (requires messing with MD5 or SHA-1): + //def nameUUIDFromBytes(name: Array[Byte]): UUID = ??? + + def fromString(name: String): UUID = { + import Integer.parseInt + + def fail(): Nothing = + throw new IllegalArgumentException("Illegal UUID string: "+name) + + @inline def parseHex8(his: String, los: String): Int = + (parseInt(his, 16) << 16) | parseInt(los, 16) + + if (name.length != 36 || name.charAt(8) != '-' || + name.charAt(13) != '-' || name.charAt(18) != '-' || name.charAt(23) != '-') + fail() + + try { + val i1 = parseHex8(name.substring(0, 4), name.substring(4, 8)) + val i2 = parseHex8(name.substring(9, 13), name.substring(14, 18)) + val i3 = parseHex8(name.substring(19, 23), name.substring(24, 28)) + val i4 = parseHex8(name.substring(28, 32), name.substring(32, 36)) + new UUID(i1, i2, i3, i4, null, null) + } catch { + case _: NumberFormatException => fail() + } + } +} diff --git a/examples/scala-js/javalib/src/main/scala/java/util/concurrent/ExecutionException.scala b/examples/scala-js/javalib/src/main/scala/java/util/concurrent/ExecutionException.scala new file mode 100644 index 0000000..6d04889 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/util/concurrent/ExecutionException.scala @@ -0,0 +1,9 @@ +package java.util.concurrent + +class ExecutionException(message: String, cause: Throwable) + extends Exception(message, cause) { + + protected def this() = this(null, null) + protected def this(message: String) = this(message, null) + def this(cause: Throwable) = this(null, cause) +} diff --git a/examples/scala-js/javalib/src/main/scala/java/util/concurrent/Executor.scala b/examples/scala-js/javalib/src/main/scala/java/util/concurrent/Executor.scala new file mode 100644 index 0000000..d030551 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/util/concurrent/Executor.scala @@ -0,0 +1,5 @@ +package java.util.concurrent + +trait Executor { + def execute(command: Runnable): Unit +} diff --git a/examples/scala-js/javalib/src/main/scala/java/util/concurrent/TimeUnit.scala b/examples/scala-js/javalib/src/main/scala/java/util/concurrent/TimeUnit.scala new file mode 100644 index 0000000..a77dbfc --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/util/concurrent/TimeUnit.scala @@ -0,0 +1,133 @@ +package java.util.concurrent + +abstract class TimeUnit private (_index: Int, + _name: String) extends java.io.Serializable { + + def convert(a: Long, u: TimeUnit): Long + + def toNanos(a: Long): Long + def toMicros(a: Long): Long + def toMillis(a: Long): Long + def toSeconds(a: Long): Long + def toMinutes(a: Long): Long + def toHours(a: Long): Long + def toDays(a: Long): Long + + // not used + //private[concurrent] def excessNanos(a: Long, b: Long): Int + + def name(): String = _name + def ordinal(): Int = _index + + // methods that cannot be implemented + //def timedWait(arg1: AnyRef, arg2: Long): Unit + //def timedJoin(arg1: Thread, arg2: Long): Unit + //def sleep(arg1: Long): Unit + + override def toString() = name() +} + +object TimeUnit { + final val NANOSECONDS: TimeUnit = new TimeUnit(0, "NANOSECONDS") { + def convert(a: Long, u: TimeUnit): Long = u.toNanos(a) + def toNanos(a: Long): Long = a + def toMicros(a: Long): Long = a / (C1/C0) + def toMillis(a: Long): Long = a / (C2/C0) + def toSeconds(a: Long): Long = a / (C3/C0) + def toMinutes(a: Long): Long = a / (C4/C0) + def toHours(a: Long): Long = a / (C5/C0) + def toDays(a: Long): Long = a / (C6/C0) + } + + final val MICROSECONDS: TimeUnit = new TimeUnit(1, "MICROSECONDS") { + def convert(a: Long, u: TimeUnit): Long = u.toMicros(a) + def toNanos(a: Long): Long = x(a, C1/C0, MAX/(C1/C0)) + def toMicros(a: Long): Long = a + def toMillis(a: Long): Long = a / (C2/C1) + def toSeconds(a: Long): Long = a / (C3/C1) + def toMinutes(a: Long): Long = a / (C4/C1) + def toHours(a: Long): Long = a / (C5/C1) + def toDays(a: Long): Long = a / (C6/C1) + } + + final val MILLISECONDS: TimeUnit = new TimeUnit(2, "MILLISECONDS") { + def convert(a: Long, u: TimeUnit): Long = u.toMillis(a) + def toNanos(a: Long): Long = x(a, C2/C0, MAX/(C2/C0)) + def toMicros(a: Long): Long = x(a, C2/C1, MAX/(C2/C1)) + def toMillis(a: Long): Long = a + def toSeconds(a: Long): Long = a / (C3/C2) + def toMinutes(a: Long): Long = a / (C4/C2) + def toHours(a: Long): Long = a / (C5/C2) + def toDays(a: Long): Long = a / (C6/C2) + } + + final val SECONDS: TimeUnit = new TimeUnit(3, "SECONDS") { + def convert(a: Long, u: TimeUnit): Long = u.toSeconds(a) + def toNanos(a: Long): Long = x(a, C3/C0, MAX/(C3/C0)) + def toMicros(a: Long): Long = x(a, C3/C1, MAX/(C3/C1)) + def toMillis(a: Long): Long = x(a, C3/C2, MAX/(C3/C2)) + def toSeconds(a: Long): Long = a + def toMinutes(a: Long): Long = a / (C4/C3) + def toHours(a: Long): Long = a / (C5/C3) + def toDays(a: Long): Long = a / (C6/C3) + } + + final val MINUTES: TimeUnit = new TimeUnit(4, "MINUTES") { + def convert(a: Long, u: TimeUnit): Long = u.toMinutes(a) + def toNanos(a: Long): Long = x(a, C4/C0, MAX/(C4/C0)) + def toMicros(a: Long): Long = x(a, C4/C1, MAX/(C4/C1)) + def toMillis(a: Long): Long = x(a, C4/C2, MAX/(C4/C2)) + def toSeconds(a: Long): Long = x(a, C4/C3, MAX/(C4/C3)) + def toMinutes(a: Long): Long = a + def toHours(a: Long): Long = a / (C5/C4) + def toDays(a: Long): Long = a / (C6/C4) + } + + final val HOURS: TimeUnit = new TimeUnit(5, "HOURS") { + def convert(a: Long, u: TimeUnit): Long = u.toHours(a) + def toNanos(a: Long): Long = x(a, C5/C0, MAX/(C5/C0)) + def toMicros(a: Long): Long = x(a, C5/C1, MAX/(C5/C1)) + def toMillis(a: Long): Long = x(a, C5/C2, MAX/(C5/C2)) + def toSeconds(a: Long): Long = x(a, C5/C3, MAX/(C5/C3)) + def toMinutes(a: Long): Long = x(a, C5/C4, MAX/(C5/C4)) + def toHours(a: Long): Long = a + def toDays(a: Long): Long = a / (C6/C5) + } + + final val DAYS: TimeUnit = new TimeUnit(6, "DAYS") { + def convert(a: Long, u: TimeUnit): Long = u.toDays(a) + def toNanos(a: Long): Long = x(a, C6/C0, MAX/(C6/C0)) + def toMicros(a: Long): Long = x(a, C6/C1, MAX/(C6/C1)) + def toMillis(a: Long): Long = x(a, C6/C2, MAX/(C6/C2)) + def toSeconds(a: Long): Long = x(a, C6/C3, MAX/(C6/C3)) + def toMinutes(a: Long): Long = x(a, C6/C4, MAX/(C6/C4)) + def toHours(a: Long): Long = x(a, C6/C5, MAX/(C6/C5)) + def toDays(a: Long): Long = a + } + + private[this] val _values: Array[TimeUnit] = + Array(NANOSECONDS, MICROSECONDS, MILLISECONDS, SECONDS, MINUTES, HOURS, DAYS) + + // deliberately without type ascription to make them compile-time constants + private final val C0 = 1L + private final val C1 = C0 * 1000L + private final val C2 = C1 * 1000L + private final val C3 = C2 * 1000L + private final val C4 = C3 * 60L + private final val C5 = C4 * 60L + private final val C6 = C5 * 24L + private final val MAX = Long.MaxValue + + def values(): Array[TimeUnit] = _values.clone() + + def valueOf(name: String): TimeUnit = { + _values.find(_.name == name).getOrElse( + throw new IllegalArgumentException("No enum const TimeUnit." + name)) + } + + private def x(a: Long, b: Long, max: Long): Long = { + if (a > max) MAX + else if (a < -max) -MAX + else a * b + } +} diff --git a/examples/scala-js/javalib/src/main/scala/java/util/concurrent/atomic/AtomicBoolean.scala b/examples/scala-js/javalib/src/main/scala/java/util/concurrent/atomic/AtomicBoolean.scala new file mode 100644 index 0000000..5675c31 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/util/concurrent/atomic/AtomicBoolean.scala @@ -0,0 +1,33 @@ +package java.util.concurrent.atomic + +class AtomicBoolean(private[this] var value: Boolean) extends Serializable { + def this() = this(false) + + final def get(): Boolean = value + + final def compareAndSet(expect: Boolean, update: Boolean): Boolean = { + if (expect != value) false else { + value = update + true + } + } + + // For some reason, this method is not final + def weakCompareAndSet(expect: Boolean, update: Boolean): Boolean = + compareAndSet(expect, update) + + final def set(newValue: Boolean): Unit = + value = newValue + + final def lazySet(newValue: Boolean): Unit = + set(newValue) + + final def getAndSet(newValue: Boolean): Boolean = { + val old = value + value = newValue + old + } + + override def toString(): String = + value.toString() +} diff --git a/examples/scala-js/javalib/src/main/scala/java/util/concurrent/atomic/AtomicInteger.scala b/examples/scala-js/javalib/src/main/scala/java/util/concurrent/atomic/AtomicInteger.scala new file mode 100644 index 0000000..1f24b7b --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/util/concurrent/atomic/AtomicInteger.scala @@ -0,0 +1,63 @@ +package java.util.concurrent.atomic + +class AtomicInteger(private[this] var value: Int) + extends Number with Serializable { + + def this() = this(0) + + final def get(): Int = value + + final def set(newValue: Int): Unit = + value = newValue + + final def lazySet(newValue: Int): Unit = + set(newValue) + + final def getAndSet(newValue: Int): Int = { + val old = value + value = newValue + old + } + + final def compareAndSet(expect: Int, update: Int): Boolean = { + if (expect != value) false else { + value = update + true + } + } + + final def weakCompareAndSet(expect: Int, update: Int): Boolean = + compareAndSet(expect, update) + + final def getAndIncrement(): Int = + getAndAdd(1) + + final def getAndDecrement(): Int = + getAndAdd(-1) + + @inline final def getAndAdd(delta: Int): Int = { + val old = value + value = old + delta + old + } + + final def incrementAndGet(): Int = + addAndGet(1) + + final def decrementAndGet(): Int = + addAndGet(-1) + + @inline final def addAndGet(delta: Int): Int = { + val newValue = value + delta + value = newValue + newValue + } + + override def toString(): String = + value.toString() + + def intValue(): Int = value + def longValue(): Long = value.toLong + def floatValue(): Float = value.toFloat + def doubleValue(): Double = value.toDouble +} diff --git a/examples/scala-js/javalib/src/main/scala/java/util/concurrent/atomic/AtomicLong.scala b/examples/scala-js/javalib/src/main/scala/java/util/concurrent/atomic/AtomicLong.scala new file mode 100644 index 0000000..5bfecf2 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/util/concurrent/atomic/AtomicLong.scala @@ -0,0 +1,61 @@ +package java.util.concurrent.atomic + +class AtomicLong(private[this] var value: Long) extends Number with Serializable { + def this() = this(0L) + + final def get(): Long = value + + final def set(newValue: Long): Unit = + value = newValue + + final def lazySet(newValue: Long): Unit = + set(newValue) + + final def getAndSet(newValue: Long): Long = { + val old = value + value = newValue + old + } + + final def compareAndSet(expect: Long, update: Long): Boolean = { + if (expect != value) false else { + value = update + true + } + } + + final def weakCompareAndSet(expect: Long, update: Long): Boolean = + compareAndSet(expect, update) + + final def getAndIncrement(): Long = + getAndAdd(1L) + + final def getAndDecrement(): Long = + getAndAdd(-1L) + + @inline final def getAndAdd(delta: Long): Long = { + val old = value + value = old + delta + old + } + + final def incrementAndGet(): Long = + addAndGet(1L) + + final def decrementAndGet(): Long = + addAndGet(-1L) + + @inline final def addAndGet(delta: Long): Long = { + val newValue = value + delta + value = newValue + newValue + } + + override def toString(): String = + value.toString() + + def intValue(): Int = value.toInt + def longValue(): Long = value + def floatValue(): Float = value.toFloat + def doubleValue(): Double = value.toDouble +} diff --git a/examples/scala-js/javalib/src/main/scala/java/util/concurrent/atomic/AtomicReference.scala b/examples/scala-js/javalib/src/main/scala/java/util/concurrent/atomic/AtomicReference.scala new file mode 100644 index 0000000..650b1e0 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/util/concurrent/atomic/AtomicReference.scala @@ -0,0 +1,34 @@ +package java.util.concurrent.atomic + +class AtomicReference[T <: AnyRef]( + private[this] var value: T) extends Serializable { + + def this() = this(null.asInstanceOf[T]) + + final def get(): T = value + + final def set(newValue: T): Unit = + value = newValue + + final def lazySet(newValue: T): Unit = + set(newValue) + + final def compareAndSet(expect: T, update: T): Boolean = { + if (expect ne value) false else { + value = update + true + } + } + + final def weakCompareAndSet(expect: T, update: T): Boolean = + compareAndSet(expect, update) + + final def getAndSet(newValue: T): T = { + val old = value + value = newValue + old + } + + override def toString(): String = + String.valueOf(value) +} diff --git a/examples/scala-js/javalib/src/main/scala/java/util/regex/MatchResult.scala b/examples/scala-js/javalib/src/main/scala/java/util/regex/MatchResult.scala new file mode 100644 index 0000000..f321c60 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/util/regex/MatchResult.scala @@ -0,0 +1,13 @@ +package java.util.regex + +trait MatchResult { + def groupCount(): Int + + def start(): Int + def end(): Int + def group(): String + + def start(group: Int): Int + def end(group: Int): Int + def group(group: Int): String +} diff --git a/examples/scala-js/javalib/src/main/scala/java/util/regex/Matcher.scala b/examples/scala-js/javalib/src/main/scala/java/util/regex/Matcher.scala new file mode 100644 index 0000000..331f56b --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/util/regex/Matcher.scala @@ -0,0 +1,274 @@ +package java.util.regex + +import scala.language.implicitConversions + +import scala.annotation.switch + +import scala.scalajs.js + +final class Matcher private[regex] ( + private var pattern0: Pattern, private var input0: CharSequence, + private var regionStart0: Int, private var regionEnd0: Int) + extends AnyRef with MatchResult { + + import Matcher._ + + def pattern(): Pattern = pattern0 + + // Configuration (updated manually) + private var regexp = new js.RegExp(pattern0.jspattern, pattern0.jsflags) + private var inputstr = input0.subSequence(regionStart0, regionEnd0).toString + + // Match result (updated by successful matches) + private var lastMatch: js.RegExp.ExecResult = null + private var lastMatchIsValid = false + private var canStillFind = true + + // Append state (updated by replacement methods) + private var appendPos: Int = 0 + + // Lookup methods + + def matches(): Boolean = { + reset() + find() + // TODO this check is wrong with non-greedy patterns + // Further, it might be wrong to just use ^$ delimiters for two reasons: + // - They might already be there + // - They might not behave as expected when newline characters are present + if ((lastMatch ne null) && (start != 0 || end != inputstr.length)) + reset() + lastMatch ne null + } + + def lookingAt(): Boolean = { + reset() + find() + if ((lastMatch ne null) && (start != 0)) + reset() + lastMatch ne null + } + + def find(): Boolean = if (canStillFind) { + lastMatchIsValid = true + lastMatch = regexp.exec(inputstr) + if (lastMatch ne null) { + if (lastMatch(0).get.isEmpty) + regexp.lastIndex += 1 + } else { + canStillFind = false + } + lastMatch ne null + } else false + + def find(start: Int): Boolean = { + reset() + regexp.lastIndex = start + find() + } + + // Replace methods + + def appendReplacement(sb: StringBuffer, replacement: String): Matcher = { + sb.append(inputstr.substring(appendPos, start)) + + @inline def isDigit(c: Char) = c >= '0' && c <= '9' + + val len = replacement.length + var i = 0 + while (i < len) { + replacement.charAt(i) match { + case '$' => + i += 1 + val j = i + while (i < len && isDigit(replacement.charAt(i))) + i += 1 + val group = Integer.parseInt(replacement.substring(j, i)) + sb.append(this.group(group)) + + case '\\' => + i += 1 + if (i < len) + sb.append(replacement.charAt(i)) + i += 1 + + case c => + sb.append(c) + i += 1 + } + } + + appendPos = end + this + } + + def appendTail(sb: StringBuffer): StringBuffer = { + sb.append(inputstr.substring(appendPos)) + appendPos = inputstr.length + sb + } + + def replaceFirst(replacement: String): String = { + reset() + + if (find()) { + val sb = new StringBuffer + appendReplacement(sb, replacement) + appendTail(sb) + sb.toString + } else { + inputstr + } + } + + def replaceAll(replacement: String): String = { + reset() + + val sb = new StringBuffer + while (find()) { + appendReplacement(sb, replacement) + } + appendTail(sb) + + sb.toString + } + + // Reset methods + + def reset(): Matcher = { + regexp.lastIndex = 0 + lastMatch = null + lastMatchIsValid = false + canStillFind = true + appendPos = 0 + this + } + + def reset(input: CharSequence): Matcher = { + regionStart0 = 0 + regionEnd0 = input.length() + input0 = input + inputstr = input0.toString + reset() + } + + def usePattern(pattern: Pattern): Matcher = { + val prevLastIndex = regexp.lastIndex + pattern0 = pattern + regexp = new js.RegExp(pattern.jspattern, pattern.jsflags) + regexp.lastIndex = prevLastIndex + lastMatch = null + this + } + + // Query state methods - implementation of MatchResult + + private def ensureLastMatch: js.RegExp.ExecResult = { + if (lastMatch == null) + throw new IllegalStateException("No match available") + lastMatch + } + + def groupCount(): Int = ensureLastMatch.length-1 + + def start(): Int = ensureLastMatch.index + def end(): Int = start() + group().length + def group(): String = ensureLastMatch(0).get + + def start(group: Int): Int = { + if (group == 0) start() + else { + val last = ensureLastMatch + // not provided by JS RegExp, so we make up something that at least + // will have some sound behavior from scala.util.matching.Regex + last(group).fold(-1) { + groupStr => inputstr.indexOf(groupStr, last.index) + } + } + } + + def end(group: Int): Int = { + val s = start(group) + if (s == -1) -1 + else s + this.group(group).length + } + + def group(group: Int): String = ensureLastMatch(group).orNull + + // Seal the state + + def toMatchResult(): MatchResult = new SealedResult(inputstr, lastMatch) + + // Other query state methods + + def hitEnd(): Boolean = + lastMatchIsValid && (lastMatch == null || end() == inputstr.length) + + //def requireEnd(): Boolean // I don't understand the spec + + // Stub methods for region management + + def regionStart(): Int = regionStart0 + def regionEnd(): Int = regionEnd0 + def region(start: Int, end: Int): Matcher = + new Matcher(pattern0, input0, start, end) + + def hasTransparentBounds(): Boolean = false + //def useTransparentBounds(b: Boolean): Matcher + + def hasAnchoringBounds(): Boolean = true + //def useAnchoringBounds(b: Boolean): Matcher +} + +object Matcher { + def quoteReplacement(s: String): String = { + var result = "" + var i = 0 + while (i < s.length) { + val c = s.charAt(i) + result += ((c: @switch) match { + case '\\' | '$' => "\\"+c + case _ => c + }) + i += 1 + } + result + } + + private final class SealedResult(inputstr: String, + lastMatch: js.RegExp.ExecResult) extends MatchResult { + + def groupCount(): Int = ensureLastMatch.length-1 + + def start(): Int = ensureLastMatch.index + def end(): Int = start() + group().length + def group(): String = ensureLastMatch(0).get + + def start(group: Int): Int = { + if (group == 0) start() + else { + val last = ensureLastMatch + + // not provided by JS RegExp, so we make up something that at least + // will have some sound behavior from scala.util.matching.Regex + last(group).fold(-1) { + groupStr => inputstr.indexOf(groupStr, last.index) + } + } + } + + def end(group: Int): Int = { + val s = start(group) + if (s == -1) -1 + else s + this.group(group).length + } + + def group(group: Int): String = ensureLastMatch(group).orNull + + private def ensureLastMatch: js.RegExp.ExecResult = { + if (lastMatch == null) + throw new IllegalStateException("No match available") + lastMatch + } + } +} diff --git a/examples/scala-js/javalib/src/main/scala/java/util/regex/Pattern.scala b/examples/scala-js/javalib/src/main/scala/java/util/regex/Pattern.scala new file mode 100644 index 0000000..fda103f --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/util/regex/Pattern.scala @@ -0,0 +1,154 @@ +package java.util.regex + +import scala.annotation.switch + +import scala.scalajs.js + +final class Pattern private (pattern0: String, flags0: Int) + extends Serializable { + + import Pattern._ + + def pattern(): String = pattern0 + def flags(): Int = flags1 + + private[regex] val (jspattern, flags1) = { + if ((flags0 & LITERAL) != 0) (quote(pattern0), flags0) + else { + trySplitHack(pattern0, flags0) orElse + tryFlagHack(pattern0, flags0) getOrElse + (pattern0, flags0) + } + } + + private[regex] val jsflags = { + var f = "g" + if ((flags & CASE_INSENSITIVE) != 0) + f += "i" + if ((flags & MULTILINE) != 0) + f += "m" + f + } + + override def toString(): String = pattern0 + + def matcher(input: CharSequence): Matcher = + new Matcher(this, input, 0, input.length) + + def split(input: CharSequence): Array[String] = + split(input, 0) + + def split(input: CharSequence, limit: Int): Array[String] = { + val lim = if (limit > 0) limit else Int.MaxValue + + val result = js.Array[String]() + val inputStr = input.toString + val matcher = this.matcher(inputStr) + var prevEnd = 0 + + // Actually split original string + while ((result.length < lim-1) && matcher.find()) { + result.push(inputStr.substring(prevEnd, matcher.start)) + prevEnd = matcher.end + } + result.push(inputStr.substring(prevEnd)) + + // Remove a leading empty element iff the first match was zero-length + // and there is no other place the regex matches + if (prevEnd == 0 && result.length == 2 && (lim > 2 || !matcher.find())) { + Array(inputStr) + } else { + var len = result.length + if (limit == 0) { + while (len > 1 && result(len-1).isEmpty) + len -= 1 + } + + val actualResult = new Array[String](len) + result.copyToArray(actualResult) + actualResult + } + } +} + +object Pattern { + final val UNIX_LINES = 0x01 + final val CASE_INSENSITIVE = 0x02 + final val COMMENTS = 0x04 + final val MULTILINE = 0x08 + final val LITERAL = 0x10 + final val DOTALL = 0x20 + final val UNICODE_CASE = 0x40 + final val CANON_EQ = 0x80 + final val UNICODE_CHARACTER_CLASS = 0x100 + + def compile(regex: String, flags: Int): Pattern = + new Pattern(regex, flags) + + def compile(regex: String): Pattern = + new Pattern(regex, 0) + + def matches(regex: String, input: CharSequence): Boolean = + compile(regex).matcher(input).matches() + + def quote(s: String): String = { + var result = "" + var i = 0 + while (i < s.length) { + val c = s.charAt(i) + result += ((c: @switch) match { + case '\\' | '.' | '(' | ')' | '[' | ']' | '{' | '}' | '|' + | '?' | '*' | '+' | '^' | '$' => "\\"+c + case _ => c + }) + i += 1 + } + result + } + + /** This is a hack to support StringLike.split(). + * It replaces occurrences of \Q<char>\E by quoted(<char>) + */ + @inline + private def trySplitHack(pat: String, flags: Int) = { + val m = splitHackPat.exec(pat) + if (m != null) + Some((quote(m(1).get), flags)) + else + None + } + + @inline + private def tryFlagHack(pat: String, flags0: Int) = { + val m = flagHackPat.exec(pat) + if (m != null) { + val newPat = pat.substring(m(0).get.length) // cut off the flag specifiers + val flags1 = m(1).fold(flags0) { chars => + chars.foldLeft(flags0) { (f, c) => f | charToFlag(c) } + } + val flags2 = m(2).fold(flags1) { chars => + chars.foldLeft(flags1) { (f, c) => f & ~charToFlag(c) } + } + Some((newPat, flags2)) + } else + None + } + + private def charToFlag(c: Char) = (c: @switch) match { + case 'i' => CASE_INSENSITIVE + case 'd' => UNIX_LINES + case 'm' => MULTILINE + case 's' => DOTALL + case 'u' => UNICODE_CASE + case 'x' => COMMENTS + case 'U' => UNICODE_CHARACTER_CLASS + case _ => sys.error("bad in-pattern flag") + } + + /** matches \Q<char>\E to support StringLike.split */ + private val splitHackPat = new js.RegExp("^\\\\Q(.|\\n|\\r)\\\\E$") + + /** regex to match flag specifiers in regex. E.g. (?u), (?-i), (?U-i) */ + private val flagHackPat = + new js.RegExp("^\\(\\?([idmsuxU]*)(?:-([idmsuxU]*))?\\)") +} diff --git a/examples/scala-js/library-aux/src/main/scala/scala/runtime/ArrayRuntime.scala b/examples/scala-js/library-aux/src/main/scala/scala/runtime/ArrayRuntime.scala new file mode 100644 index 0000000..ceda199 --- /dev/null +++ b/examples/scala-js/library-aux/src/main/scala/scala/runtime/ArrayRuntime.scala @@ -0,0 +1,16 @@ +package scala.runtime + +/** Not for public consumption. Usage by the runtime only. + */ + +object ArrayRuntime { + def cloneArray(array: Array[Boolean]): Array[Boolean] = array.clone() + def cloneArray(array: Array[Char]): Array[Char] = array.clone() + def cloneArray(array: Array[Byte]): Array[Byte] = array.clone() + def cloneArray(array: Array[Short]): Array[Short] = array.clone() + def cloneArray(array: Array[Int]): Array[Int] = array.clone() + def cloneArray(array: Array[Long]): Array[Long] = array.clone() + def cloneArray(array: Array[Float]): Array[Float] = array.clone() + def cloneArray(array: Array[Double]): Array[Double] = array.clone() + def cloneArray(array: Array[Object]): Array[Object] = array.clone() +} diff --git a/examples/scala-js/library-aux/src/main/scala/scala/runtime/BoxedUnit.scala b/examples/scala-js/library-aux/src/main/scala/scala/runtime/BoxedUnit.scala new file mode 100644 index 0000000..b6ac773 --- /dev/null +++ b/examples/scala-js/library-aux/src/main/scala/scala/runtime/BoxedUnit.scala @@ -0,0 +1,18 @@ +package scala.runtime + +/* This is a hijacked class. Its only instance is the value 'undefined'. + * Constructors are not emitted. + */ +class BoxedUnit private { + @inline override def equals(that: Any): Boolean = + this eq that.asInstanceOf[AnyRef] + + @inline override def hashCode(): Int = 0 + + @inline override def toString(): String = "undefined" +} + +object BoxedUnit { + val UNIT: BoxedUnit = sys.error("stub") + val TYPE: Class[Void] = sys.error("stub") +} diff --git a/examples/scala-js/library-aux/src/main/scala/scala/runtime/RefTypes.scala b/examples/scala-js/library-aux/src/main/scala/scala/runtime/RefTypes.scala new file mode 100644 index 0000000..4724d13 --- /dev/null +++ b/examples/scala-js/library-aux/src/main/scala/scala/runtime/RefTypes.scala @@ -0,0 +1,165 @@ +package scala.runtime + +import java.io.Serializable + +@inline +class BooleanRef(var elem: Boolean) extends Serializable { + override def toString() = String.valueOf(elem) +} +object BooleanRef { + def create(elem: Boolean): BooleanRef = new BooleanRef(elem) + def zero(): BooleanRef = new BooleanRef(false) +} + +@inline +class VolatileBooleanRef(var elem: Boolean) extends Serializable { + override def toString() = String.valueOf(elem) +} +object VolatileBooleanRef { + def create(elem: Boolean): VolatileBooleanRef = new VolatileBooleanRef(elem) + def zero(): VolatileBooleanRef = new VolatileBooleanRef(false) +} + +@inline +class CharRef(var elem: Char) extends Serializable { + override def toString() = String.valueOf(elem) +} +object CharRef { + def create(elem: Char): CharRef = new CharRef(elem) + def zero(): CharRef = new CharRef(0.toChar) +} + +@inline +class VolatileCharRef(var elem: Char) extends Serializable { + override def toString() = String.valueOf(elem) +} +object VolatileCharRef { + def create(elem: Char): VolatileCharRef = new VolatileCharRef(elem) + def zero(): VolatileCharRef = new VolatileCharRef(0.toChar) +} + +@inline +class ByteRef(var elem: Byte) extends Serializable { + override def toString() = String.valueOf(elem) +} +object ByteRef { + def create(elem: Byte): ByteRef = new ByteRef(elem) + def zero(): ByteRef = new ByteRef(0) +} + +@inline +class VolatileByteRef(var elem: Byte) extends Serializable { + override def toString() = String.valueOf(elem) +} +object VolatileByteRef { + def create(elem: Byte): VolatileByteRef = new VolatileByteRef(elem) + def zero(): VolatileByteRef = new VolatileByteRef(0) +} + +@inline +class ShortRef(var elem: Short) extends Serializable { + override def toString() = String.valueOf(elem) +} +object ShortRef { + def create(elem: Short): ShortRef = new ShortRef(elem) + def zero(): ShortRef = new ShortRef(0) +} + +@inline +class VolatileShortRef(var elem: Short) extends Serializable { + override def toString() = String.valueOf(elem) +} +object VolatileShortRef { + def create(elem: Short): VolatileShortRef = new VolatileShortRef(elem) + def zero(): VolatileShortRef = new VolatileShortRef(0) +} + +@inline +class IntRef(var elem: Int) extends Serializable { + override def toString() = String.valueOf(elem) +} +object IntRef { + def create(elem: Int): IntRef = new IntRef(elem) + def zero(): IntRef = new IntRef(0) +} + +@inline +class VolatileIntRef(var elem: Int) extends Serializable { + override def toString() = String.valueOf(elem) +} +object VolatileIntRef { + def create(elem: Int): VolatileIntRef = new VolatileIntRef(elem) + def zero(): VolatileIntRef = new VolatileIntRef(0) +} + +@inline +class LongRef(var elem: Long) extends Serializable { + override def toString() = String.valueOf(elem) +} +object LongRef { + def create(elem: Long): LongRef = new LongRef(elem) + def zero(): LongRef = new LongRef(0) +} + +@inline +class VolatileLongRef(var elem: Long) extends Serializable { + override def toString() = String.valueOf(elem) +} +object VolatileLongRef { + def create(elem: Long): VolatileLongRef = new VolatileLongRef(elem) + def zero(): VolatileLongRef = new VolatileLongRef(0) +} + +@inline +class FloatRef(var elem: Float) extends Serializable { + override def toString() = String.valueOf(elem) +} +object FloatRef { + def create(elem: Float): FloatRef = new FloatRef(elem) + def zero(): FloatRef = new FloatRef(0) +} + +@inline +class VolatileFloatRef(var elem: Float) extends Serializable { + override def toString() = String.valueOf(elem) +} +object VolatileFloatRef { + def create(elem: Float): VolatileFloatRef = new VolatileFloatRef(elem) + def zero(): VolatileFloatRef = new VolatileFloatRef(0) +} + +@inline +class DoubleRef(var elem: Double) extends Serializable { + override def toString() = String.valueOf(elem) +} +object DoubleRef { + def create(elem: Double): DoubleRef = new DoubleRef(elem) + def zero(): DoubleRef = new DoubleRef(0) +} + +@inline +class VolatileDoubleRef(var elem: Double) extends Serializable { + override def toString() = String.valueOf(elem) +} +object VolatileDoubleRef { + def create(elem: Double): VolatileDoubleRef = new VolatileDoubleRef(elem) + def zero(): VolatileDoubleRef = new VolatileDoubleRef(0) +} + +@inline +class ObjectRef[A](var elem: A) extends Serializable { + override def toString() = String.valueOf(elem) +} +object ObjectRef { + def create[A](elem: A): ObjectRef[A] = new ObjectRef(elem) + def zero(): ObjectRef[Object] = new ObjectRef(null) +} + +@inline +class VolatileObjectRef[A](var elem: A) extends Serializable { + override def toString() = String.valueOf(elem) +} +object VolatileObjectRef { + def create[A](elem: A): VolatileObjectRef[A] = new VolatileObjectRef(elem) + def zero(): VolatileObjectRef[Object] = new VolatileObjectRef(null) +} diff --git a/examples/scala-js/library-aux/src/main/scala/scala/runtime/Statics.scala b/examples/scala-js/library-aux/src/main/scala/scala/runtime/Statics.scala new file mode 100644 index 0000000..b4e7e52 --- /dev/null +++ b/examples/scala-js/library-aux/src/main/scala/scala/runtime/Statics.scala @@ -0,0 +1,89 @@ +package scala.runtime + +/** Not for public consumption. Usage by the runtime only. + */ + +object Statics { + def mix(hash: Int, data: Int): Int = { + var h = mixLast(hash, data) + h = Integer.rotateLeft(h, 13) + (h * 5) + 0xe6546b64 + } + + def mixLast(hash: Int, data: Int): Int = { + var k = data + k *= 0xcc9e2d51 + k = Integer.rotateLeft(k, 15) + k *= 0x1b873593 + hash ^ k + } + + def finalizeHash(hash: Int, length: Int): Int = { + avalanche(hash ^ length) + } + + /** Force all bits of the hash to avalanche. Used for finalizing the hash. */ + def avalanche(h0: Int): Int = { + var h = h0 + h ^= h >>> 16 + h *= 0x85ebca6b + h ^= h >>> 13 + h *= 0xc2b2ae35 + h ^= h >>> 16 + h + } + + def longHash(lv: Long): Int = { + lv.toInt + /* + val iv = lv.toInt | 0 + if (iv == lv) iv + else (lv ^ (lv >>> 32)).toInt | 0 + */ + } + + def doubleHash(dv: Double): Int = { + dv.toInt + /* + val iv = dv.toInt | 0 + if (iv == dv) + return iv + + val fv = dv.toFloat + if (fv == dv) + return java.lang.Float.floatToIntBits(fv) + + val lv = dv.toLong + if (lv == dv) + return lv.toInt | 0 + + val lv2 == java.lang.Double.doubleToLongBits(dv) + return (lv2 ^ (lv2 >>> 32)).toInt | 0 + */ + } + + def floatHash(fv: Float): Int = { + fv.toInt + /* + val iv = fv.toInt + if (iv == fv) + return iv + + val lv = fv.toLong + if (lv == fv) + return (lv ^ (lv >>> 32)).toInt | 0 + + return java.lang.Float.floatToIntBits(fv) + */ + } + + def anyHash(x: Any): Int = { + x match { + case null => 0 + case x: Long => longHash(x) + case x: Double => doubleHash(x) + case x: Float => floatHash(x) + case _ => x.hashCode() + } + } +} diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/concurrent/JSExecutionContext.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/concurrent/JSExecutionContext.scala new file mode 100644 index 0000000..c159dcb --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/concurrent/JSExecutionContext.scala @@ -0,0 +1,24 @@ +package scala.scalajs.concurrent + +import scala.concurrent.ExecutionContext + +/** + * Execution contexts for use in JavaScript + * + * Enables the use of Futures/Promises + * @author Tobias Schlatter + */ +object JSExecutionContext { + + /** execution context that runs immediately. beware of stack growth! */ + val runNow = RunNowExecutionContext + /** execution context that submits into the JavaScript runtime's + * task queue */ + val queue = QueueExecutionContext + + object Implicits { + implicit val runNow: ExecutionContext = JSExecutionContext.runNow + implicit val queue: ExecutionContext = JSExecutionContext.queue + } + +} diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/concurrent/QueueExecutionContext.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/concurrent/QueueExecutionContext.scala new file mode 100644 index 0000000..1f2ee6f --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/concurrent/QueueExecutionContext.scala @@ -0,0 +1,17 @@ +package scala.scalajs.concurrent + +import scala.concurrent.ExecutionContext +import scalajs.js + +private[concurrent] object QueueExecutionContext extends ExecutionContext { + + def execute(runnable: Runnable) = { + val lambda: js.Function = () => + try { runnable.run() } catch { case t: Throwable => reportFailure(t) } + js.Dynamic.global.setTimeout(lambda, 0) + } + + def reportFailure(t: Throwable) = + Console.err.println("Failure in async execution: " + t) + +} diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/concurrent/RunNowExcecutionContext.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/concurrent/RunNowExcecutionContext.scala new file mode 100644 index 0000000..ba113b4 --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/concurrent/RunNowExcecutionContext.scala @@ -0,0 +1,14 @@ +package scala.scalajs.concurrent + +import scala.concurrent.ExecutionContext + +private[concurrent] object RunNowExecutionContext extends ExecutionContext { + + def execute(runnable: Runnable) = + try { runnable.run() } + catch { case t: Throwable => reportFailure(t) } + + def reportFailure(t: Throwable) = + Console.err.println("Failure in async execution: " + t) + +} diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/js/Array.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/js/Array.scala new file mode 100644 index 0000000..4c9cf23 --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/js/Array.scala @@ -0,0 +1,173 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js API ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +/** + * All doc-comments marked as "MDN" are by Mozilla Contributors, + * distributed under the Creative Commons Attribution-ShareAlike license from + * https://developer.mozilla.org/en-US/docs/Web/Reference/API + */ +package scala.scalajs.js + +import annotation._ + +/** + * Arrays are list-like objects whose prototype has methods to perform + * traversal and mutation operations. Neither the length of a JavaScript + * array nor the types of its elements are fixed. Since an array's size + * length grow or shrink at any time, JavaScript arrays are not guaranteed + * to be dense. In general, these are convenient characteristics; but if + * these features are not desirable for your particular use, you might + * consider using typed arrays. + * + * MDN + * + * To construct a new array with uninitialized elements, use the constructor + * of this class. To construct a new array with specified elements, as if + * you used the array literal syntax in JavaScript, use the + * [[Array$.apply Array.apply]] method instead. + * + * @tparam A Type of the elements of the array + * + * @constructor Creates a new array of length 0. + */ +class Array[A] extends Object { + /** Creates a new array with the given length. + * @param arrayLength Initial length of the array. + */ + def this(arrayLength: Int) = this() + + // Do not expose this one - use js.Array(item1, item2, ...) instead + // def this(items: A*) = this() + + /** Length of the array. */ + def length: Int = native + + /** Sets the length of the array. + * If the new length is bigger than the old length, created slots are + * filled with `undefined` (irrespective of the type argument `A`!). + * If the new length is smaller than the old length, the array is shrunk. + */ + def length_=(v: Int): Unit = native + + /** Access the element at the given index. */ + @JSBracketAccess + def apply(index: Int): A = native + /** Set the element at the given index. */ + @JSBracketAccess + def update(index: Int, value: A): Unit = native + + /** + * concat creates a new array consisting of the elements in the this object + * on which it is called, followed in order by, for each argument, the + * elements of that argument (if the argument is an array) or the argument + * itself (if the argument is not an array). + * + * MDN + */ + def concat[B >: A](items: Array[_ <: B]*): Array[B] = native + + /** + * The join() method joins all elements of an array into a string. + * + * separator Specifies a string to separate each element of the array. + * The separator is converted to a string if necessary. If omitted, the + * array elements are separated with a comma. + */ + def join(seperator: String = ","): String = native + + /** + * The pop() method removes the last element from an array and returns that + * element. + * + * MDN + */ + def pop(): A = native + + /** + * The push() method mutates an array by appending the given elements and + * returning the new length of the array. + * + * MDN + */ + def push(items: A*): Int = native + + /** + * The reverse() method reverses an array in place. The first array element + * becomes the last and the last becomes the first. + * + * MDN + */ + @JSName("reverse") + def reverseInPlace(): Array[A] = native + + /** + * The shift() method removes the first element from an array and returns that + * element. This method changes the length of the array. + * + * MDN + */ + def shift(): A = native + + /** + * The slice() method returns a shallow copy of a portion of an array. + * + * MDN + */ + @JSName("slice") + def jsSlice(start: Int = 0, end: Int = Int.MaxValue): Array[A] = native + + /** + * The sort() method sorts the elements of an array in place and returns the + * array. The sort is not necessarily stable. The default sort order is + * lexicographic (not numeric). + * + * If compareFunction is not supplied, elements are sorted by converting them + * to strings and comparing strings in lexicographic ("dictionary" or "telephone + * book," not numerical) order. For example, "80" comes before "9" in + * lexicographic order, but in a numeric sort 9 comes before 80. + * + * MDN + */ + def sort(compareFn: Function2[A, A, Int] = ???): Array[A] = native + + /** Removes and adds new elements at a given index in the array. + * + * This method first removes `deleteCount` elements starting from the index + * `index`, then inserts the new elements `items` at that index. + * + * If `index` is negative, it is treated as that number of elements starting + * from the end of the array. + * + * @param index Index where to start changes + * @param deleteCount Number of elements to delete from index + * @param items Elements to insert at index + * @return An array of the elements that were deleted + */ + def splice(index: Int, deleteCount: Int, items: A*): Array[A] = native + + /** + * The unshift() method adds one or more elements to the beginning of an array + * and returns the new length of the array. + * + * MDN + */ + def unshift(items: A*): Int = native +} + +/** Factory for [[js.Array]] objects. */ +object Array extends Object { + // Do not expose this one - use new Array(len) instead + // def apply[A](arrayLength: Int): Array[A] = native + + /** Creates a new array with the given items. */ + def apply[A](items: A*): Array[A] = sys.error("stub") + + /** Returns true if the given value is an array. */ + def isArray(arg: Any): Boolean = native +} diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/js/ArrayOps.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/js/ArrayOps.scala new file mode 100644 index 0000000..f7948ca --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/js/ArrayOps.scala @@ -0,0 +1,119 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js API ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + +package scala.scalajs.js + +import scala.annotation.tailrec + +import scala.collection.mutable +import mutable.Builder + +/** Equivalent of scm.ArrayOps for js.Array */ +@inline +final class ArrayOps[A](private[this] val array: Array[A]) + extends mutable.ArrayLike[A, Array[A]] + with Builder[A, Array[A]] { + + import ArrayOps._ + + /** Creates a new empty [[ArrayOps]]. */ + def this() = this(Array()) + + // Implementation of ArrayLike + + @inline def apply(index: Int): A = array(index) + @inline def length: Int = array.length + @inline def update(index: Int, element: A): Unit = array(index) = element + + def seq: IndexedSeq[A] = new WrappedArray(array) + + override def repr: Array[A] = array + + override protected[this] def thisCollection: mutable.IndexedSeq[A] = + toCollection(array) + override protected[this] def toCollection( + repr: Array[A]): mutable.IndexedSeq[A] = new WrappedArray(repr) + + protected[this] def newBuilder: Builder[A, Array[A]] = + new ArrayOps[A] + + // Implementation of Builder + + @inline def +=(elem: A): this.type = { + array.push(elem) + this + } + + @inline def clear(): Unit = + array.length = 0 + + @inline def result(): Array[A] = array + + // Scala notation for a fast concat() + + @inline def ++[B >: A](that: Array[_ <: B]): Array[B] = + concat(array, that) + + // Methods whose inherited implementations do not play nice with the optimizer + + override def reduceLeft[B >: A](op: (B, A) => B): B = { + val length = this.length + if (length <= 0) + throwUnsupported("empty.reduceLeft") + + @inline + @tailrec + def loop(start: Int, z: B): B = + if (start == length) z + else loop(start+1, op(z, this(start))) + + loop(1, this(0)) + } + + override def reduceRight[B >: A](op: (A, B) => B): B = { + val length = this.length + if (length <= 0) + throwUnsupported("empty.reduceRight") + + @inline + @tailrec + def loop(end: Int, z: B): B = + if (end == 0) z + else loop(end-1, op(this(end-1), z)) + + loop(length-1, this(length-1)) + } + +} + +object ArrayOps { + + /** Extract the throw in a separate, non-inlineable method. */ + private def throwUnsupported(msg: String): Nothing = + throw new UnsupportedOperationException(msg) + + /** Non-inlined implementation of [[ArrayOps.++]]. */ + private def concat[A](left: Array[_ <: A], right: Array[_ <: A]): Array[A] = { + val leftLength = left.length + val rightLength = right.length + val result = new Array[A](leftLength + rightLength) + + @inline + @tailrec + def loop(src: Array[_ <: A], i: Int, len: Int, offset: Int): Unit = + if (i != len) { + result(i+offset) = src(i) + loop(src, i+1, len, offset) + } + + loop(left, 0, leftLength, 0) + loop(right, 0, rightLength, leftLength) + result + } + +} diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/js/Date.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/js/Date.scala new file mode 100644 index 0000000..18795c7 --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/js/Date.scala @@ -0,0 +1,225 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js API ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +/** + * All doc-comments marked as "MDN" are by Mozilla Contributors, + * distributed under the Creative Commons Attribution-ShareAlike license from + * https://developer.mozilla.org/en-US/docs/Web/Reference/API + */ +package scala.scalajs.js + +/** + * Creates a JavaScript Date instance that represents a single moment in time. + * Date objects are based on a time value that is the number of milliseconds + * since 1 January, 1970 UTC. + * + * MDN + */ +class Date extends Object { + + def this(value: Double) = this() + def this(value: String) = this() + + def this(year: Int, month: Int, date: Int = 1, hours: Int = 0, + minutes: Int = 0, seconds: Int = 0, ms: Int = 0) = this() + + def toDateString(): String = native + def toTimeString(): String = native + def toLocaleDateString(): String = native + def toLocaleTimeString(): String = native + + override def valueOf(): Double = native + + def getTime(): Double = native + + /** + * Returns the year (4 digits for 4-digit years) of the specified date according to local time. + * + * MDN + */ + def getFullYear(): Int = native + + /** + * Returns the year (4 digits for 4-digit years) in the specified date according to universal time. + * + * MDN + */ + def getUTCFullYear(): Int = native + + /** + * Returns the month (0-11) in the specified date according to local time. + * + * MDN + */ + def getMonth(): Int = native + + /** + * Returns the month (0-11) in the specified date according to universal time. + * + * MDN + */ + def getUTCMonth(): Int = native + + /** + * Returns the day of the month (1-31) for the specified date according to local time. + * + * MDN + */ + def getDate(): Int = native + + /** + * Returns the day (date) of the month (1-31) in the specified date according to universal time. + * + * MDN + */ + def getUTCDate(): Int = native + + /** + * Returns the day of the week (0-6) for the specified date according to local time. + * + * MDN + */ + def getDay(): Int = native + + /** + * Returns the day of the week (0-6) in the specified date according to universal time. + * MDN + */ + def getUTCDay(): Int = native + + /** + * Returns the hour (0-23) in the specified date according to local time. + * + * MDN + */ + def getHours(): Int = native + + /** + * Returns the hours (0-23) in the specified date according to universal time. + * + * MDN + */ + def getUTCHours(): Int = native + + /** + * Returns the minutes (0-59) in the specified date according to local time. + * + * MDN + */ + def getMinutes(): Int = native + + /** + * Returns the minutes (0-59) in the specified date according to universal time. + * + * MDN + */ + def getUTCMinutes(): Int = native + + /** + * Returns the seconds (0-59) in the specified date according to local time. + * + * MDN + */ + def getSeconds(): Int = native + + /** + * Returns the seconds (0-59) in the specified date according to universal time. + * + * MDN + */ + def getUTCSeconds(): Int = native + + /** + * Returns the milliseconds (0-999) in the specified date according to local time. + * + * MDN + */ + def getMilliseconds(): Int = native + + /** + * Returns the milliseconds (0-999) in the specified date according to universal time. + * + * MDN + */ + def getUTCMilliseconds(): Int = native + + /** + * Returns the time-zone offset in minutes for the current locale. + * + * MDN + */ + def getTimezoneOffset(): Int = native + + def setTime(time: Double): Unit = native + def setMilliseconds(ms: Int): Unit = native + def setUTCMilliseconds(ms: Int): Unit = native + def setSeconds(sec: Int, ms: Int = getMilliseconds()): Unit = native + def setUTCSeconds(sec: Int, ms: Int = getMilliseconds()): Unit = native + def setMinutes(min: Int, sec: Int = getSeconds(), + ms: Int = getMilliseconds()): Unit = native + def setUTCMinutes(min: Int, sec: Int = getSeconds(), + ms: Int = getMilliseconds()): Unit = native + def setHours(hours: Int, min: Int = getMinutes(), + sec: Int = getSeconds(), ms: Int = getMilliseconds()): Unit = native + def setUTCHours(hours: Int, min: Int = getMinutes(), + sec: Int = getSeconds(), ms: Int = getMilliseconds()): Unit = native + + def setDate(date: Int): Unit = native + def setUTCDate(date: Int): Unit = native + def setMonth(month: Int, date: Int = getDate()): Unit = native + def setUTCMonth(month: Int, date: Int = getDate()): Unit = native + def setFullYear(year: Int, month: Int = getMonth(), + date: Int = getDate()): Unit = native + def setUTCFullYear(year: Int, month: Int = getMonth(), + date: Int = getDate()): Unit = native + + def toUTCString(): String = native + def toISOString(): String = native + def toJSON(key: Any): String = native + def toJSON(): String = native +} + +/** Factory for [[js.Date]] objects. */ +object Date extends Object { + def apply(): String = native + + /** + * Parses a string representation of a date and returns the number of + * milliseconds since 1 January, 1970, 00:00:00, local time. + * + * The parse method takes a date string (such as "Dec 25, 1995") and returns + * the number of milliseconds since January 1, 1970, 00:00:00 UTC. The local + * time zone is used to interpret arguments that do not contain time zone + * information. This function is useful for setting date values based on + * string values, for example in conjunction with the setTime() method and + * the Date object. + * + * Given a string representing a time, parse returns the time value. It + * accepts the RFC2822 / IETF date syntax (RFC2822 Section 3.3), e.g. + * "Mon, 25 Dec 1995 13:30:00 GMT". It understands the continental US time- + * zone abbreviations, but for general use, use a time-zone offset, for + * example, "Mon, 25 Dec 1995 13:30:00 +0430" (4 hours, 30 minutes east of + * the Greenwich meridian). If you do not specify a time zone, the local time + * zone is assumed. GMT and UTC are considered equivalent. + * + * MDN + */ + def parse(s: String): Int = native + + def UTC(year: Int, month: Int, date: Int = 1, hours: Int = 0, + minutes: Int = 0, seconds: Int = 0, ms: Int = 0): Double = native + + /** + * Returns the numeric value corresponding to the current time - the number + * of milliseconds elapsed since 1 January 1970 00:00:00 UTC. + * + * MDN + */ + def now(): Double = native +} diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/js/Dictionary.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/js/Dictionary.scala new file mode 100644 index 0000000..fa68d08 --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/js/Dictionary.scala @@ -0,0 +1,92 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js API ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +/** + * All doc-comments marked as "MDN" are by Mozilla Contributors, + * distributed under the Creative Commons Attribution-ShareAlike license from + * https://developer.mozilla.org/en-US/docs/Web/Reference/API + */ +package scala.scalajs.js + +import annotation.JSBracketAccess + +/** Dictionary "view" of a JavaScript value. + * + * Using objects as dictionaries (maps from strings to values) through their + * properties is a common idiom in JavaScript. This trait lets you treat an + * object as such a dictionary. + * + * To use it, cast your object, say `x`, into a [[Dictionary]] using + * {{{ + * val xDict = x.asInstanceOf[js.Dictionary[Int]] + * }}} + * then use it as + * {{{ + * xDict("prop") = 5 + * println(xDict("prop")) // displays 5 + * xDict.delete("prop") // removes the property "prop" + * println(xDict("prop")) // displays undefined + * }}} + * + * To enumerate all the keys of a dictionary, use [[js.Object.keys]], which + * returns a [[js.Array]] of the properties. It can be used in a for + * comprehension like this: + * {{{ + * for (prop <- js.Object.keys(xDict)) { + * val value = xDict(prop) + * println(prop + " -> " + value) + * } + * }}} + */ +sealed trait Dictionary[A] extends Object { + /** Reads a field of this object by its name. + * + * This will fail with a ClassCastException if the key doesn't exist and + * the return type doesn't allow js.undefined as value. If the return type + * does allow js.undefined, applying with a non-existent key will return + * js.undefined. + */ + @JSBracketAccess + def apply(key: String): A = native + + /** Reads a field of this object by its name. + * + * This will return undefined if the key doesn't exist. It will also return + * undefined if the value associated with the key is undefined. To truly + * check for the existence of a property, use [[js.Object.hasOwnProperty]]. + */ + @JSBracketAccess + def get(key: String): UndefOr[A] = native + + /** Writes a field of this object by its name. */ + @JSBracketAccess + def update(key: String, value: A): Unit = native + + /** Deletes a property of this object by its name. + * The property must be configurable. + * This method is equivalent to the "delete" keyword in JavaScript. + * + * Since we are using strict mode, this throws an exception, if the property + * isn't configurable. + */ + def delete(key: String): Unit = sys.error("stub") +} + +/** Factory for [[Dictionary]] instances. */ +object Dictionary { + /** Returns a new empty dictionary */ + def empty[A]: Dictionary[A] = (new Object).asInstanceOf[Dictionary[A]] + + def apply[A](properties: (String, A)*): Dictionary[A] = { + val result = empty[A] + for ((key, value) <- properties) + result(key) = value + result + } +} diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/js/Error.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/js/Error.scala new file mode 100644 index 0000000..129ff41 --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/js/Error.scala @@ -0,0 +1,114 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js API ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +/** + * All doc-comments marked as "MDN" are by Mozilla Contributors, + * distributed under the Creative Commons Attribution-ShareAlike license from + * https://developer.mozilla.org/en-US/docs/Web/Reference/API + */ +package scala.scalajs.js + +class Error(message0: String = "") extends Object { + val name: String = native + /** + * Human-readable description of the error + * + * MDN + */ + val message: String = native +} + +object Error extends Object { + def apply(message: String = ""): Error = native +} + +/** + * An instance representing an error that occurs regarding the global function + * eval() + * + * MDN + */ +class EvalError(message: String = "") extends Error + +object EvalError extends Object { + def apply(message: String = ""): EvalError = native +} + +/** + * An instance representing an error that occurs when a numeric variable or + * parameter is outside of its valid range. + * + * A RangeError is thrown when trying to pass a number as an argument to a + * function that does not allow a range that includes that number. This can + * be encountered when to create an array of an illegal length with the Array + * constructor, or when passing bad values to the numeric methods toExponential, + * toFixed, or toPrecision. + * + * MDN + */ +class RangeError(message: String = "") extends Error + +object RangeError extends Object { + def apply(message: String = ""): RangeError = native +} + +/** + * Represents an error when a non-existent variable is referenced. + * + * A ReferenceError is thrown when trying to dereference a variable that has + * not been declared. + * + * MDN + */ +class ReferenceError(message: String = "") extends Error + +object ReferenceError extends Object { + def apply(message: String = ""): ReferenceError = native +} + +/** + * Represents an error when trying to interpret syntactically invalid code. + * + * A SyntaxError is thrown when the JavaScript engine encounters tokens or + * token order that does not conform to the syntax of the language when parsing code. + * + * MDN + */ +class SyntaxError(message: String = "") extends Error + +object SyntaxError extends Object { + def apply(message: String = ""): SyntaxError = native +} + +/** + * Represents an error when a value is not of the expected type. + * + * A TypeError is thrown when an operand or argument passed to a function is + * incompatible with the type expected by that operator or function. + * + * MDN + */ +class TypeError(message: String = "") extends Error + +object TypeError extends Object { + def apply(message: String = ""): TypeError = native +} + +/** + * Represents an error when a malformed URI is encountered. + * + * A URIError is thrown when the URI handling functions are passed a malformed URI. + * + * MDN + */ +class URIError(message: String = "") extends Error + +object URIError extends Object { + def apply(message: String = ""): URIError = native +} diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/js/Function.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/js/Function.scala new file mode 100644 index 0000000..4a7d1d9 --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/js/Function.scala @@ -0,0 +1,194 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js API ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +/** + * All doc-comments marked as "MDN" are by Mozilla Contributors, + * distributed under the Creative Commons Attribution-ShareAlike license from + * https://developer.mozilla.org/en-US/docs/Web/Reference/API + */ +package scala.scalajs.js + +/** + * The Function constructor creates a new Function object. In JavaScript every + * function is actually a Function object. + * + * Function objects created with the Function constructor are parsed when the + * function is created. This is less efficient than declaring a function and calling + * it within your code, because functions declared with the function statement + * are parsed with the rest of the code. + * + * All arguments passed to the function are treated as the names of the + * identifiers of the parameters in the function to be created, in the order + * in which they are passed. + * + * Note: Functions created with the Function constructor do not create closures + * to their creation contexts; they always are created in the global scope. + * When running them, they will only be able to access their own local + * variables and global ones, not the ones from the scope in which the Function + * constructor was called. This is different from using eval with code for + * a function expression. + * + * Invoking the Function constructor as a function (without using the new + * operator) has the same effect as invoking it as a constructor. + * + * MDN + */ +class Function(args: String*) extends Object { + /** + * length is a property of a function object, and indicates how many arguments + * the function expects, i.e. the number of formal parameters. This number + * does not include the rest parameter. By contrast, arguments.length is local + * to a function and provides the number of arguments actually passed to the + * function. + * + * MDN + */ + val length: Int = native + + /** + * The call() method calls a function with a given this value and arguments + * provided individually. + * + * You can assign a different this object when calling an existing function. + * this refers to the current object, the calling object. With call, you + * can write a method once and then inherit it in another object, without + * having to rewrite the method for the new object. + * + * apply is very similar to call(), except for the type of arguments it supports. + * You can use an arguments array instead of a named set of parameters. With + * apply, you can use an array literal, for example, + * + * fun.apply(this, ['eat', 'bananas']) + * + * or an Array object, for example, + * + * fun.apply(this, new Array('eat', 'bananas')). + * + * MDN + * + * Scala.js-specific note: call() can be used instead of the apply() method + * available in JavaScript. Simply use the :_* notation to expand a Seq as + * variadic arguments, e.g., + * + * {{{ + * someFun.call(thisArg, argSeq: _*) + * }}} + * + */ + def call(thisArg: Any, argArray: Any*): Dynamic = native + + // Do not expose apply: use call(thisArg, argArray: _*) instead. + // def apply[A](thisArg: Any, argArray: Array[A]): Dynamic = native + // def apply(thisArg: Any): Dynamic = native + + /** + * The bind() method creates a new function that, when called, has its this + * keyword set to the provided value, with a given sequence of arguments + * preceding any provided when the new function is called. + * + * MDN + */ + def bind(thisArg: Any, argArray: Any*): Dynamic = native +} + +object Function extends Object { + def apply(args: String*): Function = native +} + +trait Function0[+R] extends Function { + def apply(): R +} + +trait Function1[-T1, +R] extends Function { + def apply(arg1: T1): R +} + +trait Function2[-T1, -T2, +R] extends Function { + def apply(arg1: T1, arg2: T2): R +} + +trait Function3[-T1, -T2, -T3, +R] extends Function { + def apply(arg1: T1, arg2: T2, arg3: T3): R +} + +trait Function4[-T1, -T2, -T3, -T4, +R] extends Function { + def apply(arg1: T1, arg2: T2, arg3: T3, arg4: T4): R +} + +trait Function5[-T1, -T2, -T3, -T4, -T5, +R] extends Function { + def apply(arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5): R +} + +trait Function6[-T1, -T2, -T3, -T4, -T5, -T6, +R] extends Function { + def apply(arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6): R +} + +trait Function7[-T1, -T2, -T3, -T4, -T5, -T6, -T7, +R] extends Function { + def apply(arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7): R +} + +trait Function8[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, +R] extends Function { + def apply(arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8): R +} + +trait Function9[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, +R] extends Function { + def apply(arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8, arg9: T9): R +} + +trait Function10[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, +R] extends Function { + def apply(arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8, arg9: T9, arg10: T10): R +} + +trait Function11[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, +R] extends Function { + def apply(arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8, arg9: T9, arg10: T10, arg11: T11): R +} + +trait Function12[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, +R] extends Function { + def apply(arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8, arg9: T9, arg10: T10, arg11: T11, arg12: T12): R +} + +trait Function13[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, +R] extends Function { + def apply(arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8, arg9: T9, arg10: T10, arg11: T11, arg12: T12, arg13: T13): R +} + +trait Function14[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, -T14, +R] extends Function { + def apply(arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8, arg9: T9, arg10: T10, arg11: T11, arg12: T12, arg13: T13, arg14: T14): R +} + +trait Function15[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, -T14, -T15, +R] extends Function { + def apply(arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8, arg9: T9, arg10: T10, arg11: T11, arg12: T12, arg13: T13, arg14: T14, arg15: T15): R +} + +trait Function16[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, -T14, -T15, -T16, +R] extends Function { + def apply(arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8, arg9: T9, arg10: T10, arg11: T11, arg12: T12, arg13: T13, arg14: T14, arg15: T15, arg16: T16): R +} + +trait Function17[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, -T14, -T15, -T16, -T17, +R] extends Function { + def apply(arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8, arg9: T9, arg10: T10, arg11: T11, arg12: T12, arg13: T13, arg14: T14, arg15: T15, arg16: T16, arg17: T17): R +} + +trait Function18[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, -T14, -T15, -T16, -T17, -T18, +R] extends Function { + def apply(arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8, arg9: T9, arg10: T10, arg11: T11, arg12: T12, arg13: T13, arg14: T14, arg15: T15, arg16: T16, arg17: T17, arg18: T18): R +} + +trait Function19[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, -T14, -T15, -T16, -T17, -T18, -T19, +R] extends Function { + def apply(arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8, arg9: T9, arg10: T10, arg11: T11, arg12: T12, arg13: T13, arg14: T14, arg15: T15, arg16: T16, arg17: T17, arg18: T18, arg19: T19): R +} + +trait Function20[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, -T14, -T15, -T16, -T17, -T18, -T19, -T20, +R] extends Function { + def apply(arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8, arg9: T9, arg10: T10, arg11: T11, arg12: T12, arg13: T13, arg14: T14, arg15: T15, arg16: T16, arg17: T17, arg18: T18, arg19: T19, arg20: T20): R +} + +trait Function21[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, -T14, -T15, -T16, -T17, -T18, -T19, -T20, -T21, +R] extends Function { + def apply(arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8, arg9: T9, arg10: T10, arg11: T11, arg12: T12, arg13: T13, arg14: T14, arg15: T15, arg16: T16, arg17: T17, arg18: T18, arg19: T19, arg20: T20, arg21: T21): R +} + +trait Function22[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, -T14, -T15, -T16, -T17, -T18, -T19, -T20, -T21, -T22, +R] extends Function { + def apply(arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8, arg9: T9, arg10: T10, arg11: T11, arg12: T12, arg13: T13, arg14: T14, arg15: T15, arg16: T16, arg17: T17, arg18: T18, arg19: T19, arg20: T20, arg21: T21, arg22: T22): R +} diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/js/GlobalScope.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/js/GlobalScope.scala new file mode 100644 index 0000000..67c09ed --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/js/GlobalScope.scala @@ -0,0 +1,21 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js API ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + + +package scala.scalajs.js + +/** Marker trait for top-level objects representing the JS global scope. + * + * When calling method on a top-level object or package object that is a + * subtype of GlobalScope, the receiver is dropped, and the JavaScript global + * scope is used instead. + * + * @see [[http://www.scala-js.org/doc/calling-javascript.html Calling JavaScript from Scala.js]] + */ +trait GlobalScope extends Object diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/js/JSApp.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/js/JSApp.scala new file mode 100644 index 0000000..fd12207 --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/js/JSApp.scala @@ -0,0 +1,20 @@ +package scala.scalajs.js + +import annotation.{JSExport, JSExportDescendentObjects} + +/** Base class for top-level, entry point main objects. + * + * Objects inheriting from [[JSApp]] are automatically exported to JavaScript + * under their fully qualified name, and their [[main]] method as well. + * + * [[JSApp]] is typically used to mark the entry point of a Scala.js + * application. As such, the sbt plugin also recognizes top-level objects + * extending [[JSApp]]. It allows to run their [[main]] method with `sbt run`, + * and can also generate a tiny JavaScript launcher snippet executing the + * [[main]] method of one specific [[JSApp]] object. + */ +@JSExportDescendentObjects +trait JSApp { + @JSExport + def main(): Unit +} diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/js/JSArrayOps.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/js/JSArrayOps.scala new file mode 100644 index 0000000..71f705d --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/js/JSArrayOps.scala @@ -0,0 +1,264 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js API ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +/** + * All doc-comments marked as "MDN" are by Mozilla Contributors, + * distributed under the Creative Commons Attribution-ShareAlike license from + * https://developer.mozilla.org/en-US/docs/Web/Reference/API + */ +package scala.scalajs.js + +import scala.language.implicitConversions + +import scala.scalajs.js.annotation._ + +/** Discouraged native JavaScript Array methods. + * + * In general, you should prefer the Scala collection methods available + * implicitly through [[ArrayOps]], because they are inlineable, and hence + * faster. + * + * To enable the use of these functions on js.[[Array]]s, import the implicit + * conversion [[JSArrayOps.jsArrayOps]]. + */ +trait JSArrayOps[A] extends Object { + + /** + * The indexOf() method returns the first index at which a given element can + * be found in the array, or -1 if it is not present. + * + * MDN + */ + @JSName("indexOf") + def jsIndexOf(searchElement: A, fromIndex: Int): Int = native + @JSName("indexOf") + def jsIndexOf(searchElement: A): Int = native + + /** + * The lastIndexOf() method returns the last index at which a given element + * can be found in the array, or -1 if it is not present. The array is + * searched backwards, starting at fromIndex. + * + * MDN + */ + @JSName("lastIndexOf") + def jsLastIndexOf(searchElement: A, fromIndex: Int): Int = native + @JSName("lastIndexOf") + def jsLastIndexOf(searchElement: A): Int = native + + /** + * The every method executes the provided callback function once for each + * element present in the array until it finds one where callback returns + * a falsy value (a value that becomes false when converted to a Boolean). + * If such an element is found, the every method immediately returns false. + * Otherwise, if callback returned a true value for all elements, every + * will return true. callback is invoked only for indexes of the array + * which have assigned values; it is not invoked for indexes which have been + * deleted or which have never been assigned values. + * + * callback is invoked with three arguments: + * + * - the value of the element + * - the index of the element + * - and the Array object being traversed. + * + * If a thisObject parameter is provided to every, it will be used as the + * this for each invocation of the callback. If it is not provided, or is + * null, the global object associated with callback is used instead. + * + * every does not mutate the array on which it is called. + * + * every acts like the "for all" quantifier in mathematics. In particular, for + * an empty array, it returns true. (It is vacuously true that all elements of + * the empty set satisfy any given condition.) + * + * MDN + */ + @JSName("every") + def jsEvery[T](callbackfn: ThisFunction3[T, A, Int, Array[A], Boolean], + thisArg: T): Boolean = native + @JSName("every") + def jsEvery(callbackfn: Function3[A, Int, Array[A], Boolean]): Boolean = native + + /** + * some executes the callback function once for each element present in the + * array until it finds one where callback returns a true value. If such an + * element is found, some immediately returns true. Otherwise, some returns + * false. callback is invoked only for indexes of the array which have assigned + * values; it is not invoked for indexes which have been deleted or which + * have never been assigned values. + * + * callback is invoked with three arguments: the value of the element, the index + * of the element, and the Array object being traversed. + * + * If a thisObject parameter is provided to some, it will be used as the this + * for each invocation of the callback. If it is not provided, or is null, + * the global object associated with callback is used instead. + * + * some does not mutate the array on which it is called. + * + * MDN + */ + @JSName("some") + def jsSome[T](callbackfn: ThisFunction3[T, A, Int, Array[A], Boolean], + thisArg: T): Boolean = native + @JSName("some") + def jsSome(callbackfn: Function3[A, Int, Array[A], Boolean]): Boolean = native + @JSName("some") + def jsSome(callbackfn: Function2[A, Int, Boolean]): Boolean = native + @JSName("some") + def jsSome(callbackfn: Function1[A, Boolean]): Boolean = native + + /** + * forEach executes the provided callback once for each element of the array + * with an assigned value. It is not invoked for indexes which have been deleted + * or which have been initialized to undefined. + * + * callback is invoked with three arguments: + * + * - the element value + * - the element index + * - the array being traversed + * + * If a thisArg parameter is provided to forEach, it will be used as the + * this value for each callback invocation as if callback.call(thisArg, + * element, index, array) was called. If thisArg is undefined or null, + * the this value within the function depends on whether the function + * is in strict mode or not (passed value if in strict mode, global object + * if in non-strict mode). + * + * MDN + */ + @JSName("forEach") + def jsForEach[T](callbackfn: ThisFunction3[T, A, Int, Array[A], _], + thisArg: T): Unit = native + @JSName("forEach") + def jsForEach(callbackfn: Function3[A, Int, Array[A], _]): Unit = native + @JSName("forEach") + def jsForEach(callbackfn: Function2[A, Int, _]): Unit = native + @JSName("forEach") + def jsForEach(callbackfn: Function1[A, _]): Unit = native + + /** + * map calls a provided callback function once for each element in an array, + * in order, and constructs a new array from the results. callback is + * invoked only for indexes of the array which have assigned values; it is + * not invoked for indexes which have been deleted or which have never been + * assigned values. + * + * callback is invoked with three arguments: the value of the element, the + * index of the element, and the Array object being traversed. + * + * If a thisArg parameter is provided to map, it will be used as the this for + * each invocation of the callback. If it is not provided, or is null, the + * global object associated with callback is used instead. + * + * map does not mutate the array on which it is called. + * + * MDN + */ + @JSName("map") + def jsMap[B, T](callbackfn: ThisFunction3[T, A, Int, Array[A], B], + thisArg: T): Array[B] = native + @JSName("map") + def jsMap[B](callbackfn: Function3[A, Int, Array[A], B]): Array[B] = native + @JSName("map") + def jsMap[B](callbackfn: Function2[A, Int, B]): Array[B] = native + @JSName("map") + def jsMap[B](callbackfn: Function1[A, B]): Array[B] = native + + /** + * filter calls a provided callback function once for each element in an array, + * and constructs a new array of all the values for which callback returns a true + * value. callback is invoked only for indexes of the array which have assigned + * values; it is not invoked for indexes which have been deleted or which have + * never been assigned values. Array elements which do not pass the callback + * test are simply skipped, and are not included in the new array. + * + * callback is invoked with three arguments: + * + * - the value of the element + * - the index of the element + * - the Array object being traversed + * + * If a thisObject parameter is provided to filter, it will be used as the this + * for each invocation of the callback. If it is not provided, or is null, the + * global object associated with callback is used instead. + * + * filter does not mutate the array on which it is called. + * + * MDN + */ + @JSName("filter") + def jsFilter[T](callbackfn: ThisFunction3[T, A, Int, Array[A], Boolean], + thisArg: T): Array[A] = native + @JSName("filter") + def jsFilter(callbackfn: Function3[A, Int, Array[A], Boolean]): Array[A] = native + @JSName("filter") + def jsFilter(callbackfn: Function2[A, Int, Boolean]): Array[A] = native + @JSName("filter") + def jsFilter(callbackfn: Function1[A, Boolean]): Array[A] = native + + /** + * reduce executes the callback function once for each element present in + * the array, excluding holes in the array, receiving four arguments: the + * initial value (or value from the previous callback call), the value of + * the current element, the current index, and the array over which + * iteration is occurring. + * + * The first time the callback is called, previousValue and currentValue can + * be one of two values. If initialValue is provided in the call to reduce, + * then previousValue will be equal to initialValue and currentValue will be + * equal to the first value in the array. If no initialValue was provided, + * then previousValue will be equal to the first value in the array and + * currentValue will be equal to the second. + * + * MDN + */ + @JSName("reduce") + def jsReduce[B](callbackfn: Function4[B, A, Int, Array[A], B], initialValue: B): B = native + @JSName("reduce") + def jsReduce[B](callbackfn: Function3[B, A, Int, B], initialValue: B): B = native + @JSName("reduce") + def jsReduce[B](callbackfn: Function2[B, A, B], initialValue: B): B = native + @JSName("reduce") + def jsReduce[B](callbackfn: Function4[B, A, Int, Array[A], B]): B = native + @JSName("reduce") + def jsReduce[B](callbackfn: Function3[B, A, Int, B]): B = native + @JSName("reduce") + def jsReduce[B](callbackfn: Function2[B, A, B]): B = native + + /** + * reduceRight executes the callback function once for each element present + * in the array, excluding holes in the array, receiving four arguments: + * the initial value (or value from the previous callback call), the value + * of the current element, the current index, and the array over which + * iteration is occurring. + * + * MDN + */ + @JSName("reduceRight") + def jsReduceRight[B](callbackfn: Function4[B, A, Int, Array[A], B], initialValue: B): B = native + @JSName("reduceRight") + def jsReduceRight[B](callbackfn: Function3[B, A, Int, B], initialValue: B): B = native + @JSName("reduceRight") + def jsReduceRight[B](callbackfn: Function2[B, A, B], initialValue: B): B = native + @JSName("reduceRight") + def jsReduceRight[B](callbackfn: Function4[B, A, Int, Array[A], B]): B = native + @JSName("reduceRight") + def jsReduceRight[B](callbackfn: Function3[B, A, Int, B]): B = native + @JSName("reduceRight") + def jsReduceRight[B](callbackfn: Function2[B, A, B]): B = native + +} + +object JSArrayOps { + @inline implicit def jsArrayOps[A](array: Array[A]): JSArrayOps[A] = + array.asInstanceOf[JSArrayOps[A]] +} diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/js/JSConverters.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/js/JSConverters.scala new file mode 100644 index 0000000..1b21d61 --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/js/JSConverters.scala @@ -0,0 +1,54 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js API ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.js + +import scala.language.implicitConversions + +import scala.collection._ + +import scala.scalajs.runtime.genTraversableOnce2jsArray + +/** A collection of decorators that allow converting Scala types to + * corresponding JS facade types + */ +object JSConverters { + + implicit class JSRichOption[T](val opt: Option[T]) extends AnyVal { + @inline final def orUndefined: UndefOr[T] = + opt.fold[UndefOr[T]](undefined)(v => v) + } + + implicit class JSRichGenTraversableOnce[T]( + val col: GenTraversableOnce[T]) extends AnyVal { + @inline final def toJSArray: Array[T] = genTraversableOnce2jsArray(col) + } + + implicit class JSRichGenMap[T](val map: GenMap[String, T]) extends AnyVal { + @inline final def toJSDictionary: Dictionary[T] = { + val result = Dictionary.empty[T] + map.foreach { case (key, value) => result(key) = value } + result + } + } + + @inline + implicit def genTravConvertible2JSRichGenTrav[T, C](coll: C)( + implicit ev: C => GenTraversableOnce[T]): JSRichGenTraversableOnce[T] = + new JSRichGenTraversableOnce(coll) + + /** Special case for scala.Array of [[genTravConvertible2JSRichGenTrav]]. + * Needed for the 2.10.x series. + */ + @inline + implicit def array2JSRichGenTrav[T]( + arr: scala.Array[T]): JSRichGenTraversableOnce[T] = + new JSRichGenTraversableOnce(arr) + +} diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/js/JSON.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/js/JSON.scala new file mode 100644 index 0000000..8404f40 --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/js/JSON.scala @@ -0,0 +1,51 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js API ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +/** + * All doc-comments marked as "MDN" are by Mozilla Contributors, + * distributed under the Creative Commons Attribution-ShareAlike license from + * https://developer.mozilla.org/en-US/docs/Web/Reference/API + */ +package scala.scalajs.js + +/** + * The JSON object contains methods for converting values to JavaScript Object + * Notation (JSON) and for converting JSON to values. + * + * MDN + */ +object JSON extends Object { + /** + * Parse a string as JSON, optionally transforming the value produced by parsing. + * @param text The string to parse as JSON. See the JSON object for a + * description of JSON syntax. + * @param reviver If a function, prescribes how the value originally produced + * by parsing is transformed, before being returned. + * + * MDN + */ + def parse(text: String, reviver: Function2[Any, Any, Any] = ???): Dynamic = native + + /** + * Convert a value to JSON, optionally replacing values if a replacer function + * is specified, or optionally including only the specified properties if a + * replacer array is specified. + * + * @param value The value to convert to a JSON string. + * @param replacer If a function, transforms values and properties encountered + * while stringifying; if an array, specifies the set of + * properties included in objects in the final string. + * @param space Causes the resulting string to be pretty-printed. + * + * MDN + */ + def stringify(value: Any, replacer: Function2[String, Any, Any] = ???, space: Any = ???): String = native + def stringify(value: Any, replacer: Array[Any]): String = native + def stringify(value: Any, replacer: Array[Any], space: Any): String = native +} diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/js/JavaScriptException.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/js/JavaScriptException.scala new file mode 100644 index 0000000..e4803c8 --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/js/JavaScriptException.scala @@ -0,0 +1,20 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js API ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + + +package scala.scalajs.js + +case class JavaScriptException(exception: scala.Any) extends RuntimeException { + override def toString() = exception.toString() + + override def fillInStackTrace(): Throwable = { + scala.scalajs.runtime.StackTrace.captureState(this, exception) + this + } +} diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/js/Math.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/js/Math.scala new file mode 100644 index 0000000..02caaa0 --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/js/Math.scala @@ -0,0 +1,277 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js API ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +/** + * All doc-comments marked as "MDN" are by Mozilla Contributors, + * distributed under the Creative Commons Attribution-ShareAlike license from + * https://developer.mozilla.org/en-US/docs/Web/Reference/API + */ +package scala.scalajs.js + +/** + * Math is a built-in object that has properties and methods for mathematical + * constants and functions. Not a function object. + * + * MDN + */ +object Math extends Object { + /** + * Euler's constant and the base of natural logarithms, approximately 2.718. + * + * MDN + */ + val E: Double = native + /** + * Natural logarithm of 10, approximately 2.303. + * + * MDN + */ + val LN10: Double = native + /** + * Natural logarithm of 2, approximately 0.693. + * + * MDN + */ + val LN2: Double = native + /** + * Base 2 logarithm of E, approximately 1.443. + * + * MDN + */ + val LOG2E: Double = native + /** + * Base 10 logarithm of E, approximately 0.434. + * + * MSN + */ + val LOG10E: Double = native + /** + * Ratio of the circumference of a circle to its diameter, approximately 3.14159. + * + * MDN + */ + val PI: Double = native + + /** + * Square root of 1/2; equivalently, 1 over the square root of 2, approximately 0.707. + * + * MDN + */ + val SQRT1_2: Double = native + + /** + * Square root of 2, approximately 1.414. + * + * MDN + */ + val SQRT2: Double = native + + /** + * Returns the absolute value of a number. + * + * Passing a non-numeric string or undefined/empty variable returns NaN. + * Passing null returns 0. + * + * MDN + */ + def abs(x: Int): Int = native + + /** + * Returns the absolute value of a number. + * + * Passing a non-numeric string or undefined/empty variable returns NaN. + * Passing null returns 0. + * + * MDN + */ + def abs(x: Double): Double = native + + /** + * The Math.acos() function returns the arccosine (in radians) of a number. + * + * The acos method returns a numeric value between 0 and pi radians for x + * between -1 and 1. If the value of number is outside this range, it returns NaN. + * + * MDN + */ + def acos(x: Double): Double = native + + /** + * The Math.asin() function returns the arcsine (in radians) of a number. + * + * The asin method returns a numeric value between -pi/2 and pi/2 radians for x + * between -1 and 1. If the value of number is outside this range, it returns NaN. + * + * MDN + */ + def asin(x: Double): Double = native + + /** + * The Math.atan() function returns the arctangent (in radians) of a number. + * + * The atan method returns a numeric value between -pi/2 and pi/2 radians. + * + * MDN + */ + def atan(x: Double): Double = native + + /** + * The Math.atan2() function returns the arctangent of the quotient of its + * arguments. + * + * The atan2 method returns a numeric value between -pi and pi representing + * the angle theta of an (x,y) point. This is the counterclockwise angle, + * measured in radians, between the positive X axis, and the point (x,y). + * Note that the arguments to this function pass the y-coordinate first and + * the x-coordinate second. + * + * atan2 is passed separate x and y arguments, and atan is passed the ratio + * of those two arguments. + * + * MDN + */ + def atan2(y: Double, x: Double): Double = native + + /** + * The Math.ceil() function returns the smallest integer greater than or + * equal to a number. + * + * MDN + */ + def ceil(x: Double): Double = native + + /** + * The Math.cos() function returns the cosine of a number. + * + * The cos method returns a numeric value between -1 and 1, which represents + * the cosine of the angle. + * + * MDN + */ + def cos(x: Double): Double = native + + /** + * The Math.exp() function returns E^x, where x is the argument, and E is + * Euler's constant, the base of the natural logarithms. + * + * MDN + */ + def exp(x: Double): Double = native + + /** + * The Math.floor() function returns the largest integer less than or equal + * to a number. + * + * MDN + */ + def floor(x: Double): Double = native + + /** + * The Math.log() function returns the natural logarithm (base E) of a number. + * + * If the value of number is negative, the return value is always NaN. + * + * MDN + */ + def log(x: Double): Double = native + + /** + * The Math.max() function returns the largest of zero or more numbers. + * + * MDN + */ + def max(value1: Int, values: Int*): Int = native + + /** + * The Math.max() function returns the largest of zero or more numbers. + * + * If no arguments are given, the result is - Infinity. + * + * If at least one of arguments cannot be converted to a number, the result is NaN. + * + * MDN + */ + def max(values: Double*): Double = native + + /** + * The Math.min() function returns the smallest of zero or more numbers. + * + * MDN + */ + def min(value1: Int, values: Int*): Int = native + + /** + * The Math.min() function returns the smallest of zero or more numbers. + * + * If no arguments are given, the result is Infinity. + * + * If at least one of arguments cannot be converted to a number, the result is NaN. + * + * MDN + */ + def min(values: Double*): Double = native + + /** + * The Math.pow() function returns the base to the exponent Power, that is, base^^exponent. + * + * MDN + */ + def pow(x: Double, y: Double): Double = native + + /** + * The Math.random() function returns a floating-point, pseudo-random number in + * the range [0, 1) that is, from 0 (inclusive) up to but not including 1 + * (exclusive), which you can then scale to your desired range. + * + * The random number generator is seeded from the current time, as in Java. + * + * MDN + */ + def random(): Double = native + + /** + * The Math.round() function returns the value of a number rounded to the + * nearest integer. + * + * If the fractional portion of number is .5 or greater, the argument is + * rounded to the next higher integer. If the fractional portion of number + * is less than .5, the argument is rounded to the next lower integer. + * + * MDN + */ + def round(x: Double): Double = native + + /** + * The Math.sin() function returns the sine of a number. + * + * The sin method returns a numeric value between -1 and 1, which represents + * the sine of the angle given in radians. + * + * MDN + */ + def sin(x: Double): Double = native + + /** + * The Math.sqrt() function returns the square root (x\sqrt{x}) of a number. + * + * If the value of number is negative, sqrt returns NaN. + * + * MDN + */ + def sqrt(x: Double): Double = native + + /** + * The Math.tan() function returns the tangent of a number. + * + * The tan method returns a numeric value that represents the tangent of the angle. + * + * MDN + */ + def tan(x: Double): Double = native +} diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/js/Primitives.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/js/Primitives.scala new file mode 100644 index 0000000..1a3d88c --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/js/Primitives.scala @@ -0,0 +1,872 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js API ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +/** + * All doc-comments marked as "MDN" are by Mozilla Contributors, + * distributed under the Creative Commons Attribution-ShareAlike license from + * https://developer.mozilla.org/en-US/docs/Web/Reference/API + */ +package scala.scalajs.js + +import annotation.JSBracketAccess + +import scala.language.{ dynamics, implicitConversions } +import scala.reflect.ClassTag +import scala.collection.{ immutable, mutable } +import scala.collection.generic.CanBuildFrom + +/** Super-type of all JavaScript values. + * + * All values of a subtype of this trait represent JavaScript values, without + * boxing of proxying of any kind. + */ +sealed trait Any extends scala.AnyRef { +} + +/** Provides implicit conversions from Scala values to JavaScript values. */ +object Any extends LowPrioAnyImplicits { + @inline implicit def fromUnit(value: Unit): prim.Undefined = + value.asInstanceOf[prim.Undefined] + @inline implicit def fromBoolean(value: scala.Boolean): prim.Boolean = + value.asInstanceOf[prim.Boolean] + @inline implicit def fromByte(value: scala.Byte): prim.Number = + value.asInstanceOf[prim.Number] + @inline implicit def fromShort(value: scala.Short): prim.Number = + value.asInstanceOf[prim.Number] + @inline implicit def fromInt(value: scala.Int): prim.Number = + value.asInstanceOf[prim.Number] + @inline implicit def fromLong(value: scala.Long): prim.Number = + value.toDouble.asInstanceOf[prim.Number] + @inline implicit def fromFloat(value: scala.Float): prim.Number = + value.asInstanceOf[prim.Number] + @inline implicit def fromDouble(value: scala.Double): prim.Number = + value.asInstanceOf[prim.Number] + @inline implicit def fromString(s: java.lang.String): prim.String = + s.asInstanceOf[prim.String] + + @inline implicit def toDouble(value: prim.Number): scala.Double = + value.asInstanceOf[scala.Double] + @inline implicit def toBoolean(value: prim.Boolean): scala.Boolean = + value.asInstanceOf[scala.Boolean] + @inline implicit def toScalaString(value: prim.String): java.lang.String = + value.asInstanceOf[java.lang.String] + + implicit def jsArrayOps[A](array: Array[A]): ArrayOps[A] = + new ArrayOps(array) + + implicit def canBuildFromArray[A]: CanBuildFrom[Array[_], A, Array[A]] = { + @inline + class CanBuildFromArray extends CanBuildFrom[Array[_], A, Array[A]] { + def apply(from: Array[_]): mutable.Builder[A, Array[A]] = + new ArrayOps[A] + def apply(): mutable.Builder[A, Array[A]] = + new ArrayOps[A] + } + new CanBuildFromArray + } + + implicit def fromFunction0[R](f: scala.Function0[R]): Function0[R] = sys.error("stub") + implicit def fromFunction1[T1, R](f: scala.Function1[T1, R]): Function1[T1, R] = sys.error("stub") + implicit def fromFunction2[T1, T2, R](f: scala.Function2[T1, T2, R]): Function2[T1, T2, R] = sys.error("stub") + implicit def fromFunction3[T1, T2, T3, R](f: scala.Function3[T1, T2, T3, R]): Function3[T1, T2, T3, R] = sys.error("stub") + implicit def fromFunction4[T1, T2, T3, T4, R](f: scala.Function4[T1, T2, T3, T4, R]): Function4[T1, T2, T3, T4, R] = sys.error("stub") + implicit def fromFunction5[T1, T2, T3, T4, T5, R](f: scala.Function5[T1, T2, T3, T4, T5, R]): Function5[T1, T2, T3, T4, T5, R] = sys.error("stub") + implicit def fromFunction6[T1, T2, T3, T4, T5, T6, R](f: scala.Function6[T1, T2, T3, T4, T5, T6, R]): Function6[T1, T2, T3, T4, T5, T6, R] = sys.error("stub") + implicit def fromFunction7[T1, T2, T3, T4, T5, T6, T7, R](f: scala.Function7[T1, T2, T3, T4, T5, T6, T7, R]): Function7[T1, T2, T3, T4, T5, T6, T7, R] = sys.error("stub") + implicit def fromFunction8[T1, T2, T3, T4, T5, T6, T7, T8, R](f: scala.Function8[T1, T2, T3, T4, T5, T6, T7, T8, R]): Function8[T1, T2, T3, T4, T5, T6, T7, T8, R] = sys.error("stub") + implicit def fromFunction9[T1, T2, T3, T4, T5, T6, T7, T8, T9, R](f: scala.Function9[T1, T2, T3, T4, T5, T6, T7, T8, T9, R]): Function9[T1, T2, T3, T4, T5, T6, T7, T8, T9, R] = sys.error("stub") + implicit def fromFunction10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, R](f: scala.Function10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, R]): Function10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, R] = sys.error("stub") + implicit def fromFunction11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, R](f: scala.Function11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, R]): Function11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, R] = sys.error("stub") + implicit def fromFunction12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, R](f: scala.Function12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, R]): Function12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, R] = sys.error("stub") + implicit def fromFunction13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, R](f: scala.Function13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, R]): Function13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, R] = sys.error("stub") + implicit def fromFunction14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, R](f: scala.Function14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, R]): Function14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, R] = sys.error("stub") + implicit def fromFunction15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, R](f: scala.Function15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, R]): Function15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, R] = sys.error("stub") + implicit def fromFunction16[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, R](f: scala.Function16[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, R]): Function16[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, R] = sys.error("stub") + implicit def fromFunction17[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, R](f: scala.Function17[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, R]): Function17[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, R] = sys.error("stub") + implicit def fromFunction18[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, R](f: scala.Function18[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, R]): Function18[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, R] = sys.error("stub") + implicit def fromFunction19[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, R](f: scala.Function19[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, R]): Function19[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, R] = sys.error("stub") + implicit def fromFunction20[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, R](f: scala.Function20[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, R]): Function20[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, R] = sys.error("stub") + implicit def fromFunction21[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, R](f: scala.Function21[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, R]): Function21[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, R] = sys.error("stub") + implicit def fromFunction22[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, R](f: scala.Function22[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, R]): Function22[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, R] = sys.error("stub") + + implicit def toFunction0[R](f: Function0[R]): scala.Function0[R] = () => f() + implicit def toFunction1[T1, R](f: Function1[T1, R]): scala.Function1[T1, R] = (x1) => f(x1) + implicit def toFunction2[T1, T2, R](f: Function2[T1, T2, R]): scala.Function2[T1, T2, R] = (x1, x2) => f(x1, x2) + implicit def toFunction3[T1, T2, T3, R](f: Function3[T1, T2, T3, R]): scala.Function3[T1, T2, T3, R] = (x1, x2, x3) => f(x1, x2, x3) + implicit def toFunction4[T1, T2, T3, T4, R](f: Function4[T1, T2, T3, T4, R]): scala.Function4[T1, T2, T3, T4, R] = (x1, x2, x3, x4) => f(x1, x2, x3, x4) + implicit def toFunction5[T1, T2, T3, T4, T5, R](f: Function5[T1, T2, T3, T4, T5, R]): scala.Function5[T1, T2, T3, T4, T5, R] = (x1, x2, x3, x4, x5) => f(x1, x2, x3, x4, x5) + implicit def toFunction6[T1, T2, T3, T4, T5, T6, R](f: Function6[T1, T2, T3, T4, T5, T6, R]): scala.Function6[T1, T2, T3, T4, T5, T6, R] = (x1, x2, x3, x4, x5, x6) => f(x1, x2, x3, x4, x5, x6) + implicit def toFunction7[T1, T2, T3, T4, T5, T6, T7, R](f: Function7[T1, T2, T3, T4, T5, T6, T7, R]): scala.Function7[T1, T2, T3, T4, T5, T6, T7, R] = (x1, x2, x3, x4, x5, x6, x7) => f(x1, x2, x3, x4, x5, x6, x7) + implicit def toFunction8[T1, T2, T3, T4, T5, T6, T7, T8, R](f: Function8[T1, T2, T3, T4, T5, T6, T7, T8, R]): scala.Function8[T1, T2, T3, T4, T5, T6, T7, T8, R] = (x1, x2, x3, x4, x5, x6, x7, x8) => f(x1, x2, x3, x4, x5, x6, x7, x8) + implicit def toFunction9[T1, T2, T3, T4, T5, T6, T7, T8, T9, R](f: Function9[T1, T2, T3, T4, T5, T6, T7, T8, T9, R]): scala.Function9[T1, T2, T3, T4, T5, T6, T7, T8, T9, R] = (x1, x2, x3, x4, x5, x6, x7, x8, x9) => f(x1, x2, x3, x4, x5, x6, x7, x8, x9) + implicit def toFunction10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, R](f: Function10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, R]): scala.Function10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, R] = (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10) => f(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10) + implicit def toFunction11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, R](f: Function11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, R]): scala.Function11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, R] = (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11) => f(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11) + implicit def toFunction12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, R](f: Function12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, R]): scala.Function12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, R] = (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12) => f(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12) + implicit def toFunction13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, R](f: Function13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, R]): scala.Function13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, R] = (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13) => f(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13) + implicit def toFunction14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, R](f: Function14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, R]): scala.Function14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, R] = (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14) => f(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14) + implicit def toFunction15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, R](f: Function15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, R]): scala.Function15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, R] = (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15) => f(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15) + implicit def toFunction16[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, R](f: Function16[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, R]): scala.Function16[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, R] = (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16) => f(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16) + implicit def toFunction17[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, R](f: Function17[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, R]): scala.Function17[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, R] = (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17) => f(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17) + implicit def toFunction18[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, R](f: Function18[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, R]): scala.Function18[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, R] = (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18) => f(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18) + implicit def toFunction19[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, R](f: Function19[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, R]): scala.Function19[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, R] = (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19) => f(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19) + implicit def toFunction20[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, R](f: Function20[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, R]): scala.Function20[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, R] = (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20) => f(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20) + implicit def toFunction21[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, R](f: Function21[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, R]): scala.Function21[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, R] = (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21) => f(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21) + implicit def toFunction22[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, R](f: Function22[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, R]): scala.Function22[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, R] = (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21, x22) => f(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21, x22) +} + +trait LowPrioAnyImplicits { + @inline implicit def richDouble(num: prim.Number): scala.runtime.RichDouble = + new scala.runtime.RichDouble(Any.toDouble(num)) + @inline implicit def richBoolean(b: prim.Boolean): scala.runtime.RichBoolean = + new scala.runtime.RichBoolean(Any.toBoolean(b)) + + @inline implicit def stringOps(string: prim.String): immutable.StringOps = + new immutable.StringOps(Any.toScalaString(string)) + + implicit def wrapArray[A](array: Array[A]): WrappedArray[A] = + new WrappedArray(array) + implicit def wrapDictionary[A](dict: Dictionary[A]): WrappedDictionary[A] = + new WrappedDictionary(dict) +} + +/** Dynamically typed JavaScript value. + * + * Values of this trait accept all possible JavaScript operations in a + * dynamically typed way. You can read and write any field, call any method, + * apply any JavaScript operator to values of this type. + */ +sealed trait Dynamic extends Any with scala.Dynamic { + /** Calls a method of this object. */ + def applyDynamic(name: java.lang.String)(args: Any*): Dynamic = sys.error("stub") + + /** Reads a field of this object. */ + def selectDynamic(name: java.lang.String): Dynamic = sys.error("stub") + + /** Writes a field of this object. */ + def updateDynamic(name: java.lang.String)(value: Any): Unit = sys.error("stub") + + /** Calls this object as a callable. */ + def apply(args: Any*): Dynamic = native + + import prim.Number + + def unary_!(): Boolean = native + + def unary_+(): Number = native + def unary_-(): Number = native + def unary_~(): Number = native + + def +(that: Number): Dynamic = native // could be a String if this is a String + def -(that: Number): Number = native + def *(that: Number): Number = native + def /(that: Number): Number = native + def %(that: Number): Number = native + def <<(that: Number): Number = native + def >>(that: Number): Number = native + def >>>(that: Number): Number = native + def &(that: Number): Number = native + def |(that: Number): Number = native + def ^(that: Number): Number = native + + def +(that: Dynamic): Dynamic = native // could be String if this or that is a String + def -(that: Dynamic): Number = native + def *(that: Dynamic): Number = native + def /(that: Dynamic): Number = native + def %(that: Dynamic): Number = native + def <<(that: Dynamic): Number = native + def >>(that: Dynamic): Number = native + def >>>(that: Dynamic): Number = native + def &(that: Dynamic): Number = native + def |(that: Dynamic): Number = native + def ^(that: Dynamic): Number = native + + def <(that: Number): Boolean = native + def >(that: Number): Boolean = native + def <=(that: Number): Boolean = native + def >=(that: Number): Boolean = native + + def <(that: String): Boolean = native + def >(that: String): Boolean = native + def <=(that: String): Boolean = native + def >=(that: String): Boolean = native + + def <(that: Dynamic): Boolean = native + def >(that: Dynamic): Boolean = native + def <=(that: Dynamic): Boolean = native + def >=(that: Dynamic): Boolean = native + + /* The result of (dyn && bool) and (dyn || bool) has, in theory, type + * (Dynamic v Boolean). This type cannot be expressed in Scala, but if it + * could, the operations one could apply on a (Dynamic v Boolean) would be + * the *intersection* of the operations one can apply on a Dynamic and on a + * Boolean. Since any operation can be applied on a Dynamic, this + * intersection is equal to the set of operations supported by Boolean. + * Hence the result type is restricted to Boolean. + */ + def &&(that: Boolean): Boolean = native + def ||(that: Boolean): Boolean = native + + def &&(that: Dynamic): Dynamic = native + def ||(that: Dynamic): Dynamic = native + + // Work around the annoying implicits in Predef in Scala 2.10. + def x: Dynamic = native + def x_=(value: Any): Unit = native +} + +/** Factory for dynamically typed JavaScript values. */ +object Dynamic { + /** Dynamic view of the global scope. */ + @inline def global: Dynamic = scala.scalajs.runtime.environmentInfo.global + + /** Instantiates a new object of a JavaScript class. */ + def newInstance(clazz: Dynamic)(args: Any*): Object with Dynamic = sys.error("stub") + + /** Creates a new object with a literal syntax. + * + * For example, + * js.Dynamic.literal(foo = 3, bar = "foobar") + * returns the JavaScript object + * {foo: 3, bar: "foobar"} + */ + object literal extends scala.Dynamic { + /** literal creation like this: + * js.Dynamic.literal(name1 = "value", name2 = "value") + */ + def applyDynamicNamed(name: java.lang.String)( + fields: (java.lang.String, Any)*): Object with Dynamic = sys.error("stub") + + /** literal creation like this: + * js.Dynamic.literal("name1" -> "value", "name2" -> "value") + * + * Note that this could be simply `def apply`, but this would make the + * applyDynamicNamed fail, since a call with named arguments would + * be routed to the `def apply`, rather than def dynamic version. + */ + def applyDynamic(name: java.lang.String)( + fields: (java.lang.String, Any)*): Object with Dynamic = sys.error("stub") + + } +} + +/** Base class of all JavaScript objects. */ +class Object extends Any { + def this(value: Any) = this() + + def toLocaleString(): String = native + def valueOf(): scala.Any = native + + /** Tests whether this object has the specified property as a direct property. + * + * Unlike [[js.Object.hasProperty]], this method does not check down the + * object's prototype chain. + * + * MDN + */ + def hasOwnProperty(v: String): Boolean = native + + /** Tests whether this object is in the prototype chain of another object. */ + def isPrototypeOf(v: Object): Boolean = native + + /** Tests whether the specified property in an object can be enumerated by a + * call to [[js.Object.properties]], with the exception of properties + * inherited through the prototype chain. If the object does not have the + * specified property, this method returns false. + * + * MDN + */ + def propertyIsEnumerable(v: String): Boolean = native +} + +/** The top-level `Object` JavaScript object. */ +object Object extends Object { + def apply(): Object = native + def apply(value: Any): Object = native + + /** Tests whether the object has a property on itself or in its prototype + * chain. This method is the equivalent of `p in o` in JavaScript. + */ + def hasProperty(o: Object, p: String): Boolean = sys.error("stub") + + /** + * The Object.getPrototypeOf() method returns the prototype (i.e. the + * internal [[Prototype]]) of the specified object. + * + * MDN + */ + def getPrototypeOf(o: Object): Object = native + + /** + * The Object.getOwnPropertyDescriptor() method returns a property descriptor + * for an own property (that is, one directly present on an object, not + * present by dint of being along an object's prototype chain) of a given object. + * + * MDN + */ + def getOwnPropertyDescriptor(o: Object, p: String): PropertyDescriptor = native + + /** + * Object.getOwnPropertyNames returns an array whose elements are strings + * corresponding to the enumerable and non-enumerable properties found + * directly upon obj. The ordering of the enumerable properties in the array + * is consistent with the ordering exposed by a for...in loop (or by Object.keys) + * over the properties of the object. The ordering of the non-enumerable + * properties in the array, and among the enumerable properties, is not defined. + * + * MDN + */ + def getOwnPropertyNames(o: Object): Array[String] = native + + /** + * The Object.create() method creates a new object with the specified + * prototype object and properties. + * + * MDN + */ + def create(o: Object, properties: Any): Object = native + def create(o: Object): Object = native + + /** + * The Object.defineProperty() method defines a new property directly on an + * object, or modifies an existing property on an object, and returns the + * object. + * + * This method allows precise addition to or modification of a property on an + * object. Normal property addition through assignment creates properties + * which show up during property enumeration (for...in loop or Object.keys method), + * whose values may be changed, and which may be deleted. This method allows + * these extra details to be changed from their defaults. + * + * Property descriptors present in objects come in two main flavors: data + * descriptors and accessor descriptors. A data descriptor is a property + * that has a value, which may or may not be writable. An accessor descriptor + * is a property described by a getter-setter pair of functions. A descriptor + * must be one of these two flavors; it cannot be both. + * + * MDN + */ + def defineProperty(o: Object, p: String, attributes: PropertyDescriptor): o.type = native + + /** + * The Object.defineProperties() method defines new or modifies existing + * properties directly on an object, returning the object. + * + * MDN + */ + def defineProperties(o: Object, properties: Any): o.type = native + + /** + * The Object.seal() method seals an object, preventing new properties from + * being added to it and marking all existing properties as non-configurable. + * Values of present properties can still be changed as long as they are + * writable. + * + * MDN + */ + def seal(o: Object): o.type = native + + /** + * The Object.freeze() method freezes an object: that is, prevents new properties + * from being added to it; prevents existing properties from being removed; + * and prevents existing properties, or their enumerability, configurability, + * or writability, from being changed. In essence the object is made effectively + * immutable. The method returns the object being frozen. + * + * MDN + */ + def freeze(o: Object): o.type = native + + /** + * The Object.preventExtensions() method prevents new properties from ever + * being added to an object (i.e. prevents future extensions to the object). + * + * An object is extensible if new properties can be added to it. preventExtensions + * marks an object as no longer extensible, so that it will never have + * properties beyond the ones it had at the time it was marked as non-extensible. + * Note that the properties of a non-extensible object, in general, may still be + * deleted. Attempting to add new properties to a non-extensible object will + * fail, either silently or by throwing a TypeError (most commonly, but not + * exclusively, when in strict mode). + * + * Object.preventExtensions only prevents addition of own properties. Properties + * can still be added to the object prototype. However, calling Object.preventExtensions + * on an object will also prevent extensions on its __proto__ property. + * + * MDN + */ + def preventExtensions(o: Object): o.type = native + + /** + * Returns true if the object is sealed, otherwise false. An object is sealed + * if it is not extensible and if all its properties are non-configurable and + * therefore not removable (but not necessarily non-writable). + * + * MDN + */ + def isSealed(o: Object): Boolean = native + + /** + * The Object.isFrozen() determines if an object is frozen. + * + * An object is frozen if and only if it is not extensible, all its properties + * are non-configurable, and all its data properties (that is, properties which + * are not accessor properties with getter or setter components) are non-writable. + * + * MDN + */ + def isFrozen(o: Object): Boolean = native + + /** + * Determines if extending of an object is allowed + * + * Objects are extensible by default: they can have new properties added to + * them, and (in engines that support __proto__ their __proto__ property) + * can be modified. An object can be marked as non-extensible using + * Object.preventExtensions, Object.seal, or Object.freeze + * + * MDN + */ + def isExtensible(o: Object): Boolean = native + + /** + * The Object.keys() method returns an array of a given object's own enumerable + * properties, in the same order as that provided by a for...in loop (the + * difference being that a for-in loop enumerates properties in the prototype + * chain as well). + * + * MDN + */ + def keys(o: Object): Array[String] = native + + /** Returns the names of all the enumerable properties of this object, + * including properties in its prototype chain. + * + * This method returns the same set of names that would be enumerated by + * a for-in loop in JavaScript, but not necessarily in the same order. + * + * If the underlying implementation guarantees an order for for-in loops, + * then this is guaranteed to be consistent with [[keys]], in the sense + * that the list returned by [[keys]] is a sublist of the list returned by + * this method (not just a subset). + */ + def properties(o: Any): Array[String] = sys.error("stub") +} + +package prim { + +/** Primitive JavaScript number. + * + * In most situations, you should not need this trait, and use + * [[scala.Double]] instead (or [[scala.Int]] where appropriate). + */ +sealed trait Number extends Any { + def unary_+(): Number = native + def unary_-(): Number = native + def unary_~(): Number = native + + def +(that: Number): Number = native + def -(that: Number): Number = native + def *(that: Number): Number = native + def /(that: Number): Number = native + def %(that: Number): Number = native + def <<(that: Number): Number = native + def >>(that: Number): Number = native + def >>>(that: Number): Number = native + def &(that: Number): Number = native + def |(that: Number): Number = native + def ^(that: Number): Number = native + + def +(that: Dynamic): Dynamic = native // could be a String if that is a String + def -(that: Dynamic): Number = native + def *(that: Dynamic): Number = native + def /(that: Dynamic): Number = native + def %(that: Dynamic): Number = native + def <<(that: Dynamic): Number = native + def >>(that: Dynamic): Number = native + def >>>(that: Dynamic): Number = native + def &(that: Dynamic): Number = native + def |(that: Dynamic): Number = native + def ^(that: Dynamic): Number = native + + def <(that: Number): Boolean = native + def >(that: Number): Boolean = native + def <=(that: Number): Boolean = native + def >=(that: Number): Boolean = native + + def <(that: Dynamic): Boolean = native + def >(that: Dynamic): Boolean = native + def <=(that: Dynamic): Boolean = native + def >=(that: Dynamic): Boolean = native + + def toString(radix: Number): String = native + + /** + * Returns a string representation of number that does not use exponential + * notation and has exactly digits digits after the decimal place. The number + * is rounded if necessary, and the fractional part is padded with zeros if + * necessary so that it has the specified length. If number is greater than + * 1e+21, this method simply calls Number.prototype.toString() and returns + * a string in exponential notation. + * + * MDN + */ + def toFixed(fractionDigits: Number): String = native + def toFixed(): String = native + + /** + * Returns a string representing a Number object in exponential notation with one + * digit before the decimal point, rounded to fractionDigits digits after the + * decimal point. If the fractionDigits argument is omitted, the number of + * digits after the decimal point defaults to the number of digits necessary + * to represent the value uniquely. + * + * If a number has more digits that requested by the fractionDigits parameter, + * the number is rounded to the nearest number represented by fractionDigits + * digits. See the discussion of rounding in the description of the toFixed() + * method, which also applies to toExponential(). + * + * MDN + */ + def toExponential(fractionDigits: Number): String = native + def toExponential(): String = native + + /** + * Returns a string representing a Number object in fixed-point or exponential + * notation rounded to precision significant digits. See the discussion of + * rounding in the description of the Number.prototype.toFixed() method, which + * also applies to toPrecision. + * + * If the precision argument is omitted, behaves as Number.prototype.toString(). + * If it is a non-integer value, it is rounded to the nearest integer. + * + * MDN + */ + def toPrecision(precision: Number): String = native + def toPrecision(): String = native +} + +/** The top-level `Number` JavaScript object */ +object Number extends Object { + /** + * The Number.MAX_VALUE property represents the maximum numeric value + * representable in JavaScript. + * + * The MAX_VALUE property has a value of approximately 1.79E+308. Values + * larger than MAX_VALUE are represented as "Infinity". + * + * MDN + */ + val MAX_VALUE: Double = native + /** + * The Number.MIN_VALUE property represents the smallest positive numeric + * value representable in JavaScript. + * + * The MIN_VALUE property is the number closest to 0, not the most negative + * number, that JavaScript can represent. + * + * MIN_VALUE has a value of approximately 5e-324. Values smaller than MIN_VALUE + * ("underflow values") are converted to 0. + * + * MDN + */ + val MIN_VALUE: Double = native + /** + * The Number.NaN property represents Not-A-Number. Equivalent of NaN. + * + * MDN + */ + val NaN: Double = native + + /** + * The Number.NEGATIVE_INFINITY property represents the negative Infinity value. + * + * MDN + */ + val NEGATIVE_INFINITY: Double = native + /** + * The Number.POSITIVE_INFINITY property represents the positive Infinity value. + * + * MDN + */ + val POSITIVE_INFINITY: Double = native +} + +/** Primitive JavaScript boolean. + * + * In most situations, you should not need this trait, and use + * [[scala.Boolean]] instead. + */ +sealed trait Boolean extends Any { + def unary_!(): scala.Boolean = native + + def &&(that: Boolean): Boolean = native + def ||(that: Boolean): Boolean = native + + // See the comment in `Dynamic` for the rationale of returning Boolean here. + def &&(that: Dynamic): Boolean = native + def ||(that: Dynamic): Boolean = native +} + +/** The top-level `Boolean` JavaScript object. */ +object Boolean extends Object + +/** Primitive JavaScript string. + * + * In most situations, you should not need this trait, and use + * [[java.lang.String]] instead. + */ +sealed trait String extends Any { + def +(that: String): String = native + def +(that: Any): String = native + def +(that: Dynamic): String = native + + def < (that: String): Boolean = native + def < (that: Dynamic): Boolean = native + + def > (that: String): Boolean = native + def > (that: Dynamic): Boolean = native + + def <=(that: String): Boolean = native + def <=(that: Dynamic): Boolean = native + + def >=(that: String): Boolean = native + def >=(that: Dynamic): Boolean = native + + /** + * This property returns the number of code units in the string. UTF-16, + * the string format used by JavaScript, uses a single 16-bit code unit to + * represent the most common characters, but needs to use two code units for + * less commonly-used characters, so it's possible for the value returned by + * length to not match the actual number of characters in the string. + * + * For an empty string, length is 0. + * + * MDN + */ + val length: Number = native + + /** + * The chartAt() method returns the specified character from a string. + * + * Characters in a string are indexed from left to right. The index of the + * first character is 0, and the index of the last character in a string + * called stringName is stringName.length - 1. If the index you supply is out + * of range, JavaScript returns an empty string. + * + * MDN + */ + def charAt(pos: Number): String = native + + /** + * The charCodeAt() method returns the numeric Unicode value of the character + * at the given index (except for unicode codepoints > 0x10000). + * + * MDN + */ + def charCodeAt(index: Number): Number = native + + /** + * concat combines the text from one or more strings and returns a new string. + * Changes to the text in one string do not affect the other string. + * MDN + */ + def concat(strings: String*): String = native + + /** + * Returns the index within the calling String object of the first occurrence + * of the specified value, starting the search at fromIndex, + * + * returns -1 if the value is not found. + * + * MDN + */ + def indexOf(searchString: String, position: Number): Number = native + def indexOf(searchString: String): Number = native + + /** + * Returns the index within the calling String object of the last occurrence + * of the specified value, or -1 if not found. The calling string is searched + * backward, starting at fromIndex. + * + * MDN + */ + def lastIndexOf(searchString: String, position: Number): Number = native + def lastIndexOf(searchString: String): Number = native + + /** + * Returns a number indicating whether a reference string comes before or + * after or is the same as the given string in sort order. The new locales + * and options arguments let applications specify the language whose sort + * order should be used and customize the behavior of the function. In older + * implementations, which ignore the locales and options arguments, the locale + * and sort order used are entirely implementation dependent. + * + * MDN + */ + def localeCompare(that: String): Number = native + + /** + * Used to retrieve the matches when matching a string against a regular + * expression. + * + * If the regular expression does not include the g flag, returns the same + * result as regexp.exec(string). The returned Array has an extra input + * property, which contains the original string that was parsed. In addition, + * it has an index property, which represents the zero-based index of the + * match in the string. + * + * If the regular expression includes the g flag, the method returns an Array + * containing all matches. If there were no matches, the method returns null. + * + * MDN + */ + def `match`(regexp: String): Array[String] = native + def `match`(regexp: RegExp): Array[String] = native + + /** + * Returns a new string with some or all matches of a pattern replaced by a + * replacement. The pattern can be a string or a RegExp, and the replacement + * can be a string or a function to be called for each match. + * + * This method does not change the String object it is called on. It simply + * returns a new string. + * + * To perform a global search and replace, either include the g switch in the + * regular expression or if the first parameter is a string, include g in the + * flags parameter. + * + * MDN + */ + def replace(searchValue: String, replaceValue: String): String = native + def replace(searchValue: String, replaceValue: Any): String = native + def replace(searchValue: RegExp, replaceValue: String): String = native + def replace(searchValue: RegExp, replaceValue: Any): String = native + + /** + * If successful, search returns the index of the regular expression inside + * the string. Otherwise, it returns -1. + * + * When you want to know whether a pattern is found in a string use search + * (similar to the regular expression test method); for more information + * (but slower execution) use match (similar to the regular expression exec + * method). + * + * MDN + */ + def search(regexp: String): Number = native + def search(regexp: RegExp): Number = native + + /** + * slice extracts the text from one string and returns a new string. Changes + * to the text in one string do not affect the other string. + * + * slice extracts up to but not including endSlice. string.slice(1,4) extracts + * the second character through the fourth character (characters indexed 1, 2, + * and 3). + * + * As an example, string.slice(2,-1) extracts the third character through the + * second to last character in the string. + * + * MDN + */ + def slice(start: Number, end: Number): String = native + def slice(start: Number): String = native + + /** + * Splits a String object into an array of strings by separating the string + * into substrings. + * + * When found, separator is removed from the string and the substrings are + * returned in an array. If separator is omitted, the array contains one + * element consisting of the entire string. If separator is an empty string, + * string is converted to an array of characters. + * + * If separator is a regular expression that contains capturing parentheses, + * then each time separator is matched, the results (including any undefined + * results) of the capturing parentheses are spliced into the output array. + * However, not all browsers support this capability. + * + * Note: When the string is empty, split returns an array containing one + * empty string, rather than an empty array. + * + * MDN + */ + def split(separator: String, limit: Number): Array[String] = native + def split(separator: String): Array[String] = native + def split(separator: RegExp, limit: Number): Array[String] = native + def split(separator: RegExp): Array[String] = native + + /** + * Returns a subset of a string between one index and another, or through + * the end of the string. + * + * MDN + */ + def substring(start: Number, end: Number): String = native + def substring(start: Number): String = native + + /** + * Returns the calling string value converted to lowercase. + * + * MDN + */ + def toLowerCase(): String = native + + /** + * The toLocaleLowerCase method returns the value of the string converted to + * lower case according to any locale-specific case mappings. toLocaleLowerCase + * does not affect the value of the string itself. In most cases, this will + * produce the same result as toLowerCase(), but for some locales, such as + * Turkish, whose case mappings do not follow the default case mappings in Unicode, + * there may be a different result. + * + * MDN + */ + def toLocaleLowerCase(): String = native + + /** + * Returns the calling string value converted to uppercase. + * + * MDN + */ + def toUpperCase(): String = native + + /** + * The toLocaleUpperCase method returns the value of the string converted to + * upper case according to any locale-specific case mappings. toLocaleUpperCase + * does not affect the value of the string itself. In most cases, this will + * produce the same result as toUpperCase(), but for some locales, such as + * Turkish, whose case mappings do not follow the default case mappings in Unicode, + * there may be a different result. + * + * MDN + */ + def toLocaleUpperCase(): String = native + + /** + * Removes whitespace from both ends of the string. + * + * MDN + */ + def trim(): String = native +} + +/** The top-level `String` JavaScript object. */ +object String extends Object { + def fromCharCode(codes: Int*): java.lang.String = native +} + +/** Primitive JavaScript undefined value. + * + * In most situations, you should not need this trait, and use + * [[scala.Unit]] instead. + */ +sealed trait Undefined extends Any + +} diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/js/PropertyDescriptor.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/js/PropertyDescriptor.scala new file mode 100644 index 0000000..b91e01d --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/js/PropertyDescriptor.scala @@ -0,0 +1,20 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js API ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + + +package scala.scalajs.js + +trait PropertyDescriptor extends Object { + var configurable: Boolean = native + var enumerable: Boolean = native + var value: Any = native + var writable: Boolean = native + var get: Function0[Any] = native + var set: Function1[Any, Any] = native +} diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/js/RegExp.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/js/RegExp.scala new file mode 100644 index 0000000..73f465a --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/js/RegExp.scala @@ -0,0 +1,108 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js API ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +/** + * All doc-comments marked as "MDN" are by Mozilla Contributors, + * distributed under the Creative Commons Attribution-ShareAlike license from + * https://developer.mozilla.org/en-US/docs/Web/Reference/API + */ +package scala.scalajs.js + +/** + * The RegExp constructor creates a regular expression object for matching + * text with a pattern. + * + * MDN + */ +class RegExp(pattern: String, flags: String = "") extends Object { + /** + * The source property returns a String containing the text of the pattern, + * excluding the forward slashes. It is a read-only property of an individual + * regular expression instance. source does not contain any flags (like "g", + * "i" or "m") of the regular expression. + * + * MDN + */ + val source: String = native + /** + * The value of global is a Boolean and true if the "g" flag was used; + * otherwise, false. The "g" flag indicates that the regular expression + * should be tested against all possible matches in a string. + * + * MDN + */ + val global: Boolean = native + /** + * The value of ignoreCase is a Boolean and true if the "i" flag was used; + * otherwise, false. The "i" flag indicates that case should be ignored while + * attempting a match in a string. + * + * MDN + */ + val ignoreCase: Boolean = native + /** + * The value of multiline is a Boolean and is true if the "m" flag was used; + * otherwise, false. The "m" flag indicates that a multiline input string + * should be treated as multiple lines. For example, if "m" is used, "^" and + * "$" change from matching at only the start or end of the entire string to + * the start or end of any line within the string. + + * MDN + */ + val multiline: Boolean = native + + /** + * The lastIndex is a read/write integer property of regular expressions that + * specifies the index at which to start the next match. + * + * MDN + */ + var lastIndex: Int = native + + /** + * The exec() method executes a search for a match in a specified string. + * Returns a result array, or null. + * + * If you are executing a match simply to find true or false, use the + * RegExp.test() method or the String.search() method. + * + * If the match succeeds, the exec method returns an array and updates properties + * of the regular expression object. The returned array has the matched text + * as the first item, and then one item for each capturing parenthesis that + * matched containing the text that was captured. + * + * If the match fails, the exec method returns null. + * + * MDN + */ + def exec(string: String): RegExp.ExecResult = native + + /** + * The test() method executes a search for a match between a regular expression + * and a specified string. Returns true or false. + * + * You can use test() whenever want to know whether a pattern is found in a + * string (similar to the String.search method); for more information (but + * slower execution) use the exec method (similar to the String.match method). + * As with exec (or in combination with it), test called multiple times on the + * same global regular expression instance will advance past the previous match. + * + * MDN + */ + def test(string: String): Boolean = native +} + +object RegExp extends Object { + def apply(pattern: String, flags: String = ""): RegExp = native + + trait ExecResult extends Array[UndefOr[String]] { + var index: Int = native + var input: String = native + } +} diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/js/ThisFunction.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/js/ThisFunction.scala new file mode 100644 index 0000000..2506eed --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/js/ThisFunction.scala @@ -0,0 +1,160 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js API ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +/** + * All doc-comments marked as "MDN" are by Mozilla Contributors, + * distributed under the Creative Commons Attribution-ShareAlike license from + * https://developer.mozilla.org/en-US/docs/Web/Reference/API + */ +package scala.scalajs.js + +import scala.language.implicitConversions + +/** A JavaScript function where `this` is considered as a first parameter. + * + * @see [[http://www.scala-js.org/doc/calling-javascript.html Calling JavaScript from Scala.js]] + */ +trait ThisFunction extends Function { +} + +object ThisFunction { + implicit def fromFunction1[T1, R](f: scala.Function1[T1, R]): ThisFunction0[T1, R] = sys.error("stub") + implicit def fromFunction2[T1, T2, R](f: scala.Function2[T1, T2, R]): ThisFunction1[T1, T2, R] = sys.error("stub") + implicit def fromFunction3[T1, T2, T3, R](f: scala.Function3[T1, T2, T3, R]): ThisFunction2[T1, T2, T3, R] = sys.error("stub") + implicit def fromFunction4[T1, T2, T3, T4, R](f: scala.Function4[T1, T2, T3, T4, R]): ThisFunction3[T1, T2, T3, T4, R] = sys.error("stub") + implicit def fromFunction5[T1, T2, T3, T4, T5, R](f: scala.Function5[T1, T2, T3, T4, T5, R]): ThisFunction4[T1, T2, T3, T4, T5, R] = sys.error("stub") + implicit def fromFunction6[T1, T2, T3, T4, T5, T6, R](f: scala.Function6[T1, T2, T3, T4, T5, T6, R]): ThisFunction5[T1, T2, T3, T4, T5, T6, R] = sys.error("stub") + implicit def fromFunction7[T1, T2, T3, T4, T5, T6, T7, R](f: scala.Function7[T1, T2, T3, T4, T5, T6, T7, R]): ThisFunction6[T1, T2, T3, T4, T5, T6, T7, R] = sys.error("stub") + implicit def fromFunction8[T1, T2, T3, T4, T5, T6, T7, T8, R](f: scala.Function8[T1, T2, T3, T4, T5, T6, T7, T8, R]): ThisFunction7[T1, T2, T3, T4, T5, T6, T7, T8, R] = sys.error("stub") + implicit def fromFunction9[T1, T2, T3, T4, T5, T6, T7, T8, T9, R](f: scala.Function9[T1, T2, T3, T4, T5, T6, T7, T8, T9, R]): ThisFunction8[T1, T2, T3, T4, T5, T6, T7, T8, T9, R] = sys.error("stub") + implicit def fromFunction10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, R](f: scala.Function10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, R]): ThisFunction9[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, R] = sys.error("stub") + implicit def fromFunction11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, R](f: scala.Function11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, R]): ThisFunction10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, R] = sys.error("stub") + implicit def fromFunction12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, R](f: scala.Function12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, R]): ThisFunction11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, R] = sys.error("stub") + implicit def fromFunction13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, R](f: scala.Function13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, R]): ThisFunction12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, R] = sys.error("stub") + implicit def fromFunction14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, R](f: scala.Function14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, R]): ThisFunction13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, R] = sys.error("stub") + implicit def fromFunction15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, R](f: scala.Function15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, R]): ThisFunction14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, R] = sys.error("stub") + implicit def fromFunction16[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, R](f: scala.Function16[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, R]): ThisFunction15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, R] = sys.error("stub") + implicit def fromFunction17[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, R](f: scala.Function17[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, R]): ThisFunction16[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, R] = sys.error("stub") + implicit def fromFunction18[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, R](f: scala.Function18[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, R]): ThisFunction17[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, R] = sys.error("stub") + implicit def fromFunction19[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, R](f: scala.Function19[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, R]): ThisFunction18[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, R] = sys.error("stub") + implicit def fromFunction20[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, R](f: scala.Function20[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, R]): ThisFunction19[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, R] = sys.error("stub") + implicit def fromFunction21[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, R](f: scala.Function21[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, R]): ThisFunction20[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, R] = sys.error("stub") + implicit def fromFunction22[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, R](f: scala.Function22[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, R]): ThisFunction21[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, R] = sys.error("stub") + + implicit def toFunction1[T1, R](f: ThisFunction0[T1, R]): scala.Function1[T1, R] = (x1) => f(x1) + implicit def toFunction2[T1, T2, R](f: ThisFunction1[T1, T2, R]): scala.Function2[T1, T2, R] = (x1, x2) => f(x1, x2) + implicit def toFunction3[T1, T2, T3, R](f: ThisFunction2[T1, T2, T3, R]): scala.Function3[T1, T2, T3, R] = (x1, x2, x3) => f(x1, x2, x3) + implicit def toFunction4[T1, T2, T3, T4, R](f: ThisFunction3[T1, T2, T3, T4, R]): scala.Function4[T1, T2, T3, T4, R] = (x1, x2, x3, x4) => f(x1, x2, x3, x4) + implicit def toFunction5[T1, T2, T3, T4, T5, R](f: ThisFunction4[T1, T2, T3, T4, T5, R]): scala.Function5[T1, T2, T3, T4, T5, R] = (x1, x2, x3, x4, x5) => f(x1, x2, x3, x4, x5) + implicit def toFunction6[T1, T2, T3, T4, T5, T6, R](f: ThisFunction5[T1, T2, T3, T4, T5, T6, R]): scala.Function6[T1, T2, T3, T4, T5, T6, R] = (x1, x2, x3, x4, x5, x6) => f(x1, x2, x3, x4, x5, x6) + implicit def toFunction7[T1, T2, T3, T4, T5, T6, T7, R](f: ThisFunction6[T1, T2, T3, T4, T5, T6, T7, R]): scala.Function7[T1, T2, T3, T4, T5, T6, T7, R] = (x1, x2, x3, x4, x5, x6, x7) => f(x1, x2, x3, x4, x5, x6, x7) + implicit def toFunction8[T1, T2, T3, T4, T5, T6, T7, T8, R](f: ThisFunction7[T1, T2, T3, T4, T5, T6, T7, T8, R]): scala.Function8[T1, T2, T3, T4, T5, T6, T7, T8, R] = (x1, x2, x3, x4, x5, x6, x7, x8) => f(x1, x2, x3, x4, x5, x6, x7, x8) + implicit def toFunction9[T1, T2, T3, T4, T5, T6, T7, T8, T9, R](f: ThisFunction8[T1, T2, T3, T4, T5, T6, T7, T8, T9, R]): scala.Function9[T1, T2, T3, T4, T5, T6, T7, T8, T9, R] = (x1, x2, x3, x4, x5, x6, x7, x8, x9) => f(x1, x2, x3, x4, x5, x6, x7, x8, x9) + implicit def toFunction10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, R](f: ThisFunction9[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, R]): scala.Function10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, R] = (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10) => f(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10) + implicit def toFunction11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, R](f: ThisFunction10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, R]): scala.Function11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, R] = (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11) => f(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11) + implicit def toFunction12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, R](f: ThisFunction11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, R]): scala.Function12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, R] = (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12) => f(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12) + implicit def toFunction13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, R](f: ThisFunction12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, R]): scala.Function13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, R] = (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13) => f(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13) + implicit def toFunction14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, R](f: ThisFunction13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, R]): scala.Function14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, R] = (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14) => f(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14) + implicit def toFunction15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, R](f: ThisFunction14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, R]): scala.Function15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, R] = (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15) => f(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15) + implicit def toFunction16[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, R](f: ThisFunction15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, R]): scala.Function16[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, R] = (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16) => f(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16) + implicit def toFunction17[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, R](f: ThisFunction16[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, R]): scala.Function17[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, R] = (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17) => f(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17) + implicit def toFunction18[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, R](f: ThisFunction17[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, R]): scala.Function18[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, R] = (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18) => f(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18) + implicit def toFunction19[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, R](f: ThisFunction18[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, R]): scala.Function19[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, R] = (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19) => f(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19) + implicit def toFunction20[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, R](f: ThisFunction19[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, R]): scala.Function20[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, R] = (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20) => f(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20) + implicit def toFunction21[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, R](f: ThisFunction20[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, R]): scala.Function21[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, R] = (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21) => f(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21) + implicit def toFunction22[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, R](f: ThisFunction21[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, R]): scala.Function22[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, R] = (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21, x22) => f(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21, x22) +} + +trait ThisFunction0[-T0, +R] extends ThisFunction { + def apply(thisArg: T0): R +} + +trait ThisFunction1[-T0, -T1, +R] extends ThisFunction { + def apply(thisArg: T0, arg1: T1): R +} + +trait ThisFunction2[-T0, -T1, -T2, +R] extends ThisFunction { + def apply(thisArg: T0, arg1: T1, arg2: T2): R +} + +trait ThisFunction3[-T0, -T1, -T2, -T3, +R] extends ThisFunction { + def apply(thisArg: T0, arg1: T1, arg2: T2, arg3: T3): R +} + +trait ThisFunction4[-T0, -T1, -T2, -T3, -T4, +R] extends ThisFunction { + def apply(thisArg: T0, arg1: T1, arg2: T2, arg3: T3, arg4: T4): R +} + +trait ThisFunction5[-T0, -T1, -T2, -T3, -T4, -T5, +R] extends ThisFunction { + def apply(thisArg: T0, arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5): R +} + +trait ThisFunction6[-T0, -T1, -T2, -T3, -T4, -T5, -T6, +R] extends ThisFunction { + def apply(thisArg: T0, arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6): R +} + +trait ThisFunction7[-T0, -T1, -T2, -T3, -T4, -T5, -T6, -T7, +R] extends ThisFunction { + def apply(thisArg: T0, arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7): R +} + +trait ThisFunction8[-T0, -T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, +R] extends ThisFunction { + def apply(thisArg: T0, arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8): R +} + +trait ThisFunction9[-T0, -T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, +R] extends ThisFunction { + def apply(thisArg: T0, arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8, arg9: T9): R +} + +trait ThisFunction10[-T0, -T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, +R] extends ThisFunction { + def apply(thisArg: T0, arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8, arg9: T9, arg10: T10): R +} + +trait ThisFunction11[-T0, -T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, +R] extends ThisFunction { + def apply(thisArg: T0, arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8, arg9: T9, arg10: T10, arg11: T11): R +} + +trait ThisFunction12[-T0, -T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, +R] extends ThisFunction { + def apply(thisArg: T0, arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8, arg9: T9, arg10: T10, arg11: T11, arg12: T12): R +} + +trait ThisFunction13[-T0, -T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, +R] extends ThisFunction { + def apply(thisArg: T0, arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8, arg9: T9, arg10: T10, arg11: T11, arg12: T12, arg13: T13): R +} + +trait ThisFunction14[-T0, -T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, -T14, +R] extends ThisFunction { + def apply(thisArg: T0, arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8, arg9: T9, arg10: T10, arg11: T11, arg12: T12, arg13: T13, arg14: T14): R +} + +trait ThisFunction15[-T0, -T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, -T14, -T15, +R] extends ThisFunction { + def apply(thisArg: T0, arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8, arg9: T9, arg10: T10, arg11: T11, arg12: T12, arg13: T13, arg14: T14, arg15: T15): R +} + +trait ThisFunction16[-T0, -T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, -T14, -T15, -T16, +R] extends ThisFunction { + def apply(thisArg: T0, arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8, arg9: T9, arg10: T10, arg11: T11, arg12: T12, arg13: T13, arg14: T14, arg15: T15, arg16: T16): R +} + +trait ThisFunction17[-T0, -T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, -T14, -T15, -T16, -T17, +R] extends ThisFunction { + def apply(thisArg: T0, arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8, arg9: T9, arg10: T10, arg11: T11, arg12: T12, arg13: T13, arg14: T14, arg15: T15, arg16: T16, arg17: T17): R +} + +trait ThisFunction18[-T0, -T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, -T14, -T15, -T16, -T17, -T18, +R] extends ThisFunction { + def apply(thisArg: T0, arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8, arg9: T9, arg10: T10, arg11: T11, arg12: T12, arg13: T13, arg14: T14, arg15: T15, arg16: T16, arg17: T17, arg18: T18): R +} + +trait ThisFunction19[-T0, -T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, -T14, -T15, -T16, -T17, -T18, -T19, +R] extends ThisFunction { + def apply(thisArg: T0, arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8, arg9: T9, arg10: T10, arg11: T11, arg12: T12, arg13: T13, arg14: T14, arg15: T15, arg16: T16, arg17: T17, arg18: T18, arg19: T19): R +} + +trait ThisFunction20[-T0, -T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, -T14, -T15, -T16, -T17, -T18, -T19, -T20, +R] extends ThisFunction { + def apply(thisArg: T0, arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8, arg9: T9, arg10: T10, arg11: T11, arg12: T12, arg13: T13, arg14: T14, arg15: T15, arg16: T16, arg17: T17, arg18: T18, arg19: T19, arg20: T20): R +} + +trait ThisFunction21[-T0, -T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, -T14, -T15, -T16, -T17, -T18, -T19, -T20, -T21, +R] extends ThisFunction { + def apply(thisArg: T0, arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8, arg9: T9, arg10: T10, arg11: T11, arg12: T12, arg13: T13, arg14: T14, arg15: T15, arg16: T16, arg17: T17, arg18: T18, arg19: T19, arg20: T20, arg21: T21): R +} diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/js/UndefOr.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/js/UndefOr.scala new file mode 100644 index 0000000..b356e3a --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/js/UndefOr.scala @@ -0,0 +1,254 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js API ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.js + +import scala.language.implicitConversions + +/** Value of type A or the JS undefined value. + * In a type system with union types, this would really be + * `A | js.prim.Undefined`. Since Scala does not have union types, but this + * particular union is crucial to many interoperability scenarios, it is + * provided as this trait. + * + * An API similar to that of [[scala.Option]] is provided through the + * [[UndefOrOps]] implicit class, with the understanding that `undefined` is + * the None value. + */ +@scala.scalajs.js.annotation.RawJSType // Don't do this at home! +sealed trait UndefOr[+A] + +object UndefOr { + implicit def any2undefOrA[A](value: A): UndefOr[A] = + value.asInstanceOf[UndefOr[A]] + + implicit def undef2undefOr(value: prim.Undefined): UndefOr[Nothing] = + value.asInstanceOf[UndefOr[Nothing]] + + implicit def undefOr2ops[A](value: UndefOr[A]): UndefOrOps[A] = + new UndefOrOps(value) + + implicit def undefOr2jsAny[A](value: UndefOr[A])(implicit ev: A => Any): Any = + value.map(ev).asInstanceOf[Any] +} + +final class UndefOrOps[A](val self: UndefOr[A]) extends AnyVal { + import UndefOrOps._ + + /** Returns true if the option is `undefined`, false otherwise. + */ + @inline final def isEmpty: Boolean = isUndefined(self) + + /** Returns true if the option is not `undefined`, false otherwise. + */ + @inline final def isDefined: Boolean = !isEmpty + + /** Returns the option's value. + * @note The option must be nonEmpty. + * @throws Predef.NoSuchElementException if the option is empty. + */ + @inline final def get: A = + if (isEmpty) throw new NoSuchElementException("undefined.get") + else self.asInstanceOf[A] + + @inline final private def forceGet: A = self.asInstanceOf[A] + + /** Returns the option's value if the option is nonempty, otherwise + * return the result of evaluating `default`. + * + * @param default the default expression. + */ + @inline final def getOrElse[B >: A](default: => B): B = + if (isEmpty) default else this.forceGet + + /** Returns the option's value if it is nonempty, + * or `null` if it is empty. + * Although the use of null is discouraged, code written to use + * $option must often interface with code that expects and returns nulls. + * @example {{{ + * val initalText: Option[String] = getInitialText + * val textField = new JComponent(initalText.orNull,20) + * }}} + */ + @inline final def orNull[A1 >: A](implicit ev: Null <:< A1): A1 = + this getOrElse ev(null) + + /** Returns a $some containing the result of applying $f to this $option's + * value if this $option is nonempty. + * Otherwise return $none. + * + * @note This is similar to `flatMap` except here, + * $f does not need to wrap its result in an $option. + * + * @param f the function to apply + * @see flatMap + * @see foreach + */ + @inline final def map[B](f: A => B): UndefOr[B] = + if (isEmpty) undefined else f(this.forceGet) + + /** Returns the result of applying $f to this $option's + * value if the $option is nonempty. Otherwise, evaluates + * expression `ifEmpty`. + * + * @note This is equivalent to `$option map f getOrElse ifEmpty`. + * + * @param ifEmpty the expression to evaluate if empty. + * @param f the function to apply if nonempty. + */ + @inline final def fold[B](ifEmpty: => B)(f: A => B): B = + if (isEmpty) ifEmpty else f(this.forceGet) + + /** Returns the result of applying $f to this $option's value if + * this $option is nonempty. + * Returns $none if this $option is empty. + * Slightly different from `map` in that $f is expected to + * return an $option (which could be $none). + * + * @param f the function to apply + * @see map + * @see foreach + */ + @inline final def flatMap[B](f: A => UndefOr[B]): UndefOr[B] = + if (isEmpty) undefined else f(this.forceGet) + + def flatten[B](implicit ev: A <:< UndefOr[B]): UndefOr[B] = + if (isEmpty) undefined else ev(this.forceGet) + + /** Returns this $option if it is nonempty '''and''' applying the predicate $p to + * this $option's value returns true. Otherwise, return $none. + * + * @param p the predicate used for testing. + */ + @inline final def filter(p: A => Boolean): UndefOr[A] = + if (isEmpty || p(this.forceGet)) self else undefined + + /** Returns this $option if it is nonempty '''and''' applying the predicate $p to + * this $option's value returns false. Otherwise, return $none. + * + * @param p the predicate used for testing. + */ + @inline final def filterNot(p: A => Boolean): UndefOr[A] = + if (isEmpty || !p(this.forceGet)) self else undefined + + /** Returns false if the option is $none, true otherwise. + * @note Implemented here to avoid the implicit conversion to Iterable. + */ + final def nonEmpty = isDefined + + /** Necessary to keep $option from being implicitly converted to + * [[scala.collection.Iterable]] in `for` comprehensions. + */ + @inline final def withFilter(p: A => Boolean): WithFilter[A] = + new WithFilter(self, p) + + /** Returns true if this option is nonempty '''and''' the predicate + * $p returns true when applied to this $option's value. + * Otherwise, returns false. + * + * @param p the predicate to test + */ + @inline final def exists(p: A => Boolean): Boolean = + !isEmpty && p(this.forceGet) + + /** Returns true if this option is empty '''or''' the predicate + * $p returns true when applied to this $option's value. + * + * @param p the predicate to test + */ + @inline final def forall(p: A => Boolean): Boolean = + isEmpty || p(this.forceGet) + + /** Apply the given procedure $f to the option's value, + * if it is nonempty. Otherwise, do nothing. + * + * @param f the procedure to apply. + * @see map + * @see flatMap + */ + @inline final def foreach[U](f: A => U): Unit = + if (!isEmpty) f(this.forceGet) + + /** Returns a $some containing the result of + * applying `pf` to this $option's contained + * value, '''if''' this option is + * nonempty '''and''' `pf` is defined for that value. + * Returns $none otherwise. + * + * @param pf the partial function. + * @return the result of applying `pf` to this $option's + * value (if possible), or $none. + */ + @inline final def collect[B](pf: PartialFunction[A, B]): UndefOr[B] = + if (isEmpty) undefined + else pf.applyOrElse(this.forceGet, (_: A) => undefined).asInstanceOf[UndefOr[B]] + + /** Returns this $option if it is nonempty, + * otherwise return the result of evaluating `alternative`. + * @param alternative the alternative expression. + */ + @inline final def orElse[B >: A](alternative: => UndefOr[B]): UndefOr[B] = + if (isEmpty) alternative else self + + /** Returns a singleton iterator returning the $option's value + * if it is nonempty, or an empty iterator if the option is empty. + */ + def iterator: Iterator[A] = + if (isEmpty) scala.collection.Iterator.empty + else scala.collection.Iterator.single(this.forceGet) + + /** Returns a singleton list containing the $option's value + * if it is nonempty, or the empty list if the $option is empty. + */ + def toList: List[A] = + if (isEmpty) Nil else this.forceGet :: Nil + + /** Returns a [[scala.util.Left]] containing the given + * argument `left` if this $option is empty, or + * a [[scala.util.Right]] containing this $option's value if + * this is nonempty. + * + * @param left the expression to evaluate and return if this is empty + * @see toLeft + */ + @inline final def toRight[X](left: => X): Either[X, A] = + if (isEmpty) Left(left) else Right(this.forceGet) + + /** Returns a [[scala.util.Right]] containing the given + * argument `right` if this is empty, or + * a [[scala.util.Left]] containing this $option's value + * if this $option is nonempty. + * + * @param right the expression to evaluate and return if this is empty + * @see toRight + */ + @inline final def toLeft[X](right: => X): Either[A, X] = + if (isEmpty) Right(right) else Left(this.forceGet) + + /** Returns a [[scala.Some]] containing this $options's value + * if this $option is nonempty, [[scala.None]] otherwise. + */ + @inline final def toOption: Option[A] = + if (isEmpty) None else Some(this.forceGet) +} + +object UndefOrOps { + + /** We need a whole WithFilter class to honor the "doesn't create a new + * collection" contract even though it seems unlikely to matter much in a + * collection with max size 1. + */ + class WithFilter[A](self: UndefOr[A], p: A => Boolean) { + def map[B](f: A => B): UndefOr[B] = self filter p map f + def flatMap[B](f: A => UndefOr[B]): UndefOr[B] = self filter p flatMap f + def foreach[U](f: A => U): Unit = self filter p foreach f + def withFilter(q: A => Boolean): WithFilter[A] = + new WithFilter[A](self, x => p(x) && q(x)) + } +} diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/js/WrappedArray.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/js/WrappedArray.scala new file mode 100644 index 0000000..3626c15 --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/js/WrappedArray.scala @@ -0,0 +1,92 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js API ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + +package scala.scalajs.js + +import scala.language.implicitConversions + +import scala.collection.mutable +import mutable.Builder + +import scala.collection.generic.{CanBuildFrom, GenericCompanion, SeqFactory} + +/** Equivalent of scm.WrappedArray for js.Array */ +@inline +final class WrappedArray[A](val array: Array[A]) + extends mutable.AbstractBuffer[A] + with scala.collection.generic.GenericTraversableTemplate[A, WrappedArray] + with mutable.IndexedSeq[A] + with mutable.BufferLike[A, WrappedArray[A]] + with mutable.ArrayLike[A, WrappedArray[A]] + with Builder[A, WrappedArray[A]] { + + /** Creates a new empty [[WrappedArray]]. */ + def this() = this(Array()) + + override def companion: GenericCompanion[WrappedArray] = WrappedArray + + // IndexedSeq interface + + @inline def update(index: Int, elem: A): Unit = array(index) = elem + @inline def apply(index: Int): A = array(index) + @inline def length: Int = array.length + + // Builder interface + + @inline def +=(elem: A): this.type = { + array.push(elem) + this + } + + @inline def clear(): Unit = + array.length = 0 + + @inline def result(): WrappedArray[A] = this + + // Rest of BufferLike interface + + @inline def +=:(elem: A): this.type = { + array.unshift(elem) + this + } + + @inline override def ++=:(xs: TraversableOnce[A]): this.type = { + array.unshift(xs.toSeq: _*) + this + } + + @inline def insertAll(n: Int, + elems: scala.collection.Traversable[A]): Unit = { + array.splice(n, 0, elems.toSeq: _*) + } + + @inline def remove(n: Int): A = + array.splice(n, 1)(0) + + @inline override def remove(n: Int, count: Int): Unit = + array.splice(n, count) + + @inline override def stringPrefix: String = "WrappedArray" + +} + +/** $factoryInfo + * @define coll wrapped array + * @define Coll `WrappedArray` + */ +object WrappedArray extends SeqFactory[WrappedArray] { + /** $genericCanBuildFromInfo */ + implicit def canBuildFrom[A]: CanBuildFrom[Coll, A, WrappedArray[A]] = + ReusableCBF.asInstanceOf[GenericCanBuildFrom[A]] + + def newBuilder[A]: Builder[A, WrappedArray[A]] = new WrappedArray[A] + + implicit def toJSArray[A](wrappedArray: WrappedArray[A]): Array[A] = + wrappedArray.array + +} diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/js/WrappedDictionary.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/js/WrappedDictionary.scala new file mode 100644 index 0000000..f215e6e --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/js/WrappedDictionary.scala @@ -0,0 +1,89 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js API ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + +package scala.scalajs.js + +import scala.collection.mutable +import mutable.Builder + +import scala.collection.generic.CanBuildFrom + +/** Wrapper to use a js.Dictionary as a scala.mutable.Map */ +@inline +class WrappedDictionary[A](val dict: Dictionary[A]) + extends mutable.AbstractMap[String, A] + with mutable.Map[String, A] + with mutable.MapLike[String, A, WrappedDictionary[A]] { + + def get(key: String): Option[A] = { + if (contains(key)) + Some(dict(key)) + else + None + } + + override def contains(key: String): Boolean = + dict.hasOwnProperty(key) + + def -=(key: String): this.type = { + dict.delete(key) + this + } + + def +=(kv: (String, A)): this.type = { + dict(kv._1) = kv._2 + this + } + + def iterator: Iterator[(String, A)] = new Iterator[(String, A)] { + private[this] val keys = Object.keys(dict) + private[this] var index: Int = 0 + def hasNext(): Boolean = index < keys.length + def next(): (String, A) = { + val key = keys(index) + index += 1 + (key, dict(key)) + } + } + + override def keys: Iterable[String] = + Object.keys(dict) + + override def empty: WrappedDictionary[A] = + new WrappedDictionary(Dictionary.empty) + +} + +object WrappedDictionary { + // Note: We can't extend MutableMapFactory[WrappedDictionary] since + // it requires support for any type of key + + def empty[A]: WrappedDictionary[A] = new WrappedDictionary(Dictionary.empty) + + type CBF[A] = CanBuildFrom[WrappedDictionary[_], (String, A), WrappedDictionary[A]] + implicit def canBuildFrom[A]: CBF[A] = new CBF[A] { + def apply(from: WrappedDictionary[_]): Builder[(String, A), WrappedDictionary[A]] = + new WrappedDictionaryBuilder[A] + def apply(): Builder[(String, A), WrappedDictionary[A]] = + new WrappedDictionaryBuilder[A] + } + + class WrappedDictionaryBuilder[A] + extends Builder[(String, A), WrappedDictionary[A]] { + private[this] var dict: Dictionary[A] = Dictionary.empty + def +=(elem: (String, A)): this.type = { + dict(elem._1) = elem._2 + this + } + def clear(): Unit = + dict = Dictionary.empty + def result(): WrappedDictionary[A] = + new WrappedDictionary(dict) + } + +} diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/js/annotation/JSBracketAccess.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/js/annotation/JSBracketAccess.scala new file mode 100644 index 0000000..596e327 --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/js/annotation/JSBracketAccess.scala @@ -0,0 +1,17 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2003-2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + + + +package scala.scalajs.js.annotation + +/** Marks the annotated method as representing bracket access in JavaScript. + * + * @see [[http://www.scala-js.org/doc/calling-javascript.html Calling JavaScript from Scala.js]] + */ +class JSBracketAccess extends scala.annotation.StaticAnnotation diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/js/annotation/JSExport.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/js/annotation/JSExport.scala new file mode 100644 index 0000000..0fa9a4e --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/js/annotation/JSExport.scala @@ -0,0 +1,19 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2003-2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + + + +package scala.scalajs.js.annotation + +/** Specifies that the given entity should be exported for use in raw JS. + * + * @see [[http://www.scala-js.org/doc/export-to-javascript.html Export Scala.js APIs to JavaScript]] + */ +class JSExport extends scala.annotation.StaticAnnotation { + def this(name: String) = this() +} diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/js/annotation/JSExportAll.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/js/annotation/JSExportAll.scala new file mode 100644 index 0000000..8174595 --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/js/annotation/JSExportAll.scala @@ -0,0 +1,21 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2003-2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + + + +package scala.scalajs.js.annotation + +/** Exports all public members directly defined in a class / object. + * + * Strictly equivalent to putting [[JSExport]] on every public member. + * Note: You are allowed to export protected members, but you'll have to do + * this explicitly on each member. + * + * @see [[http://www.scala-js.org/doc/export-to-javascript.html Export Scala.js APIs to JavaScript]] + */ +class JSExportAll extends scala.annotation.StaticAnnotation diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/js/annotation/JSExportDescendentClasses.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/js/annotation/JSExportDescendentClasses.scala new file mode 100644 index 0000000..9f2be96 --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/js/annotation/JSExportDescendentClasses.scala @@ -0,0 +1,20 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js API ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + + +package scala.scalajs.js.annotation + +/** Specifies that all the concrete classes extending the annotated class or + * should have all their public constructors exported for use in raw JS. + * The constructors exported this way are exported under their fully + * qualified name. + * + * @see [[http://www.scala-js.org/doc/export-to-javascript.html Export Scala.js APIs to JavaScript]] + */ +class JSExportDescendentClasses extends scala.annotation.StaticAnnotation diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/js/annotation/JSExportDescendentObjects.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/js/annotation/JSExportDescendentObjects.scala new file mode 100644 index 0000000..c196b53 --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/js/annotation/JSExportDescendentObjects.scala @@ -0,0 +1,20 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js API ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + + +package scala.scalajs.js.annotation + +/** Specifies that all the objects extending the annotated class or trait + * should be exported for use in raw JS. + * Note that objects exported this way are exported under their fully + * qualified name. + * + * @see [[http://www.scala-js.org/doc/export-to-javascript.html Export Scala.js APIs to JavaScript]] + */ +class JSExportDescendentObjects extends scala.annotation.StaticAnnotation diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/js/annotation/JSExportNamed.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/js/annotation/JSExportNamed.scala new file mode 100644 index 0000000..718404a --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/js/annotation/JSExportNamed.scala @@ -0,0 +1,38 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2003-2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + + + +package scala.scalajs.js.annotation + +/** Exports the given method to JavaScript with named parameters. + * + * It can then be called like this: + * {{{ + * obj.foo({ + * param1: value1 + * param2: value2 + * param7: value3 + * }); + * }}} + * + * Note that named exports don't support overloading. Therefore the + * following will fail: + * {{{ + * class A { + * @JSExportNamed + * def a(foo: Int) = foo + 1 + * @JSExportNamed + * def a(bar: String) = "Hello " + bar + * } + * }}} + * @see [[http://www.scala-js.org/doc/export-to-javascript.html Export Scala.js APIs to JavaScript]] + */ +class JSExportNamed extends scala.annotation.StaticAnnotation { + def this(name: String) = this() +} diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/js/annotation/JSName.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/js/annotation/JSName.scala new file mode 100644 index 0000000..5401749 --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/js/annotation/JSName.scala @@ -0,0 +1,17 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2003-2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + + + +package scala.scalajs.js.annotation + +/** Specifies the JavaScript name of an entity. + * + * @see [[http://www.scala-js.org/doc/calling-javascript.html Calling JavaScript from Scala.js]] + */ +class JSName(name: String) extends scala.annotation.StaticAnnotation diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/js/annotation/README.md b/examples/scala-js/library/src/main/scala/scala/scalajs/js/annotation/README.md new file mode 100644 index 0000000..9ce7ebf --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/js/annotation/README.md @@ -0,0 +1,3 @@ +**Attention**: Some files in here are also published in the Scala.js stubs JVM library (see the stubs project in the Scala.js build). + +If you add (or rename) a file, make sure the files in the stubs project are up to date. diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/js/annotation/RawJSType.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/js/annotation/RawJSType.scala new file mode 100644 index 0000000..a5bb771 --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/js/annotation/RawJSType.scala @@ -0,0 +1,23 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2003-2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + + + +package scala.scalajs.js.annotation + +/** Marks the annotated class, trait or object as a raw JavaScript type. + * + * This annotation is added automatically by the compiler to all classes, + * traits and objects inheriting directly or indirectly from + * [[scala.scalajs.js.Any]]. It marks the annotated entity as being a raw + * JavaScript type, i.e., one that represents type information for an entity + * defined in JavaScript code. + * + * Do not use this annotation yourself. + */ +class RawJSType extends scala.annotation.StaticAnnotation diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/js/package.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/js/package.scala new file mode 100644 index 0000000..4a17ba6 --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/js/package.scala @@ -0,0 +1,161 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js API ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + + +package scala.scalajs + +/** Contains primitive types for interoperability with JavaScript libraries. + * This package is only relevant to the Scala.js compiler, and should not be + * referenced by any project compiled to the JVM. + * + * All the values and methods in this package object are representatives of + * standard variables and functions available in the top-level scope, as + * standardized in ECMAScript 5.1. + * + * == Guide == + * + * General documentation on Scala.js is available at + * [[http://www.scala-js.org/doc/]]. + * + * == Overview == + * + * The trait [[js.Any]] is the super type of all JavaScript values. + * + * All class, trait and object definitions that inherit, directly or + * indirectly, from [[js.Any]] do not have actual implementations in Scala. + * They are only the manifestation of static types representing libraries + * written directly in JavaScript. It is not possible to implement yourself + * a subclass of [[js.Any]]: all the method definitions will be ignored when + * compiling to JavaScript. + * + * Implicit conversions to and from standard Scala types to their equivalent + * in JavaScript are provided. For example, from Scala arrays to JavaScript + * arrays and back. + * + * The most important subclasses of [[js.Any]] are: + * - [[js.Dynamic]], a dynamically typed interface to JavaScript APIs + * - [[js.Object]], the superclass of all statically typed JavaScript classes, + * which has subclasses for all the classes standardized in ECMAScript 5.1, + * among which: + * - [[js.Array]] + * - [[js.Function]] (and subtraits with specific number of parameters) + * - [[js.ThisFunction]] and its subtraits for functions that take the + * JavaScript `this` as an explicit parameters + * - [[js.Dictionary]] to access the properties of an object in a + * dictionary-like way + * - [[js.Date]] + * - [[js.RegExp]] + * + * The trait [[js.Dynamic]] is a special subtrait of [[js.Any]]. It can + * represent any JavaScript value in a dynamically-typed way. It is possible + * to call any method and read and write any field of a value of type + * [[js.Dynamic]]. + * + * The package [[scala.scalajs.js.prim]] gives definitions for the four + * primitive types of JavaScript as subtraits of [[js.Any]], but generally + * it is preferable to use the corresponding Scala type. + * - [[js.prim.Number]] corresponds to [[scala.Double]] + * - [[js.prim.Boolean]] corresponds to [[scala.Boolean]] + * - [[js.prim.String]] corresponds to [[java.lang.String]] + * - [[js.prim.Undefined]] corresponds to [[scala.Unit]] + * + * [[js.UndefOr]] gives a [[scala.Option]]-like interface where the JavaScript + * value `undefined` takes the role of `None`. + */ +package object js extends js.GlobalScope { + /** The type of JavaScript numbers, which is [[scala.Double]]. */ + type Number = scala.Double + /** The type of JavaScript booleans, which is [[scala.Boolean]]. */ + type Boolean = scala.Boolean + /** The type of JavaScript strings, which is [[java.lang.String]]. */ + type String = java.lang.String + /** The type of the JavaScript undefined value, which is [[scala.Unit]]. */ + type Undefined = scala.Unit + + /** The top-level `Number` JavaScript object. */ + val Number: js.prim.Number.type = native + /** The top-level `Boolean` JavaScript object. */ + val Boolean: js.prim.Boolean.type = native + /** The top-level `String` JavaScript object. */ + val String: js.prim.String.type = native + + /** The constant Not-a-Number. */ + val NaN: Double = native + /** The constant Positive Infinity. */ + val Infinity: Double = native + + /** The undefined value. */ + def undefined: js.prim.Undefined = sys.error("stub") + + /** Tests whether the given value is undefined. */ + def isUndefined(v: scala.Any): Boolean = sys.error("stub") + + /** Returns the type of `x` as identified by `typeof x` in JavaScript. */ + def typeOf(x: Any): String = sys.error("stub") + + /** Invokes any available debugging functionality. + * If no debugging functionality is available, this statement has no effect. + * + * MDN + * + * Browser support: + * - Has no effect in Rhino nor, apparently, in Firefox + * - In Chrome, it has no effect unless the developer tools are opened + * beforehand. + */ + def debugger(): Unit = sys.error("stub") + + /** Evaluates JavaScript code and returns the result. */ + def eval(x: String): Any = native + + /** Parses a string as an integer with a given radix. */ + def parseInt(s: String, radix: Int): js.Number = native + /** Parses a string as an integer with auto-detected radix. */ + def parseInt(s: String): js.Number = native + /** Parses a string as a floating point number. */ + def parseFloat(string: String): Double = native + + /** Tests whether the given value is Not-a-Number. */ + def isNaN(number: Double): Boolean = native + /** Tests whether the given value is a finite number. */ + def isFinite(number: Double): Boolean = native + + /** Decodes a Uniform Resource Identifier (URI). + * @see [[encodeURI]] + */ + def decodeURI(encodedURI: String): String = native + + /** Decodes a Uniform Resource Identifier (URI) component. + * @see [[encodeURIComponent]] + */ + def decodeURIComponent(encodedURIComponent: String): String = native + + /** Encodes a Uniform Resource Identifier (URI). + * @see [[decodeURI]] + */ + def encodeURI(uri: String): String = native + + /** Encodes a Uniform Resource Identifier (URI) component. + * @see [[decodeURIComponent]] + */ + def encodeURIComponent(uriComponent: String): String = native + + /** Denotes a method body as native JavaScript. For use in facade types: + * + * {{{ + * class MyJSClass extends js.Object { + * def myMethod(x: String): Int = js.native + * } + * }}} + */ + def native: Nothing = sys.error("A method defined in a JavaScript raw " + + "type of a Scala.js library has been called. This is most likely " + + "because you tried to run Scala.js binaries on the JVM. Make sure you " + + "are using the JVM version of the libraries.") +} diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/js/typedarray/ArrayBuffer.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/js/typedarray/ArrayBuffer.scala new file mode 100644 index 0000000..b8b8160 --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/js/typedarray/ArrayBuffer.scala @@ -0,0 +1,17 @@ +package scala.scalajs.js.typedarray + +import scala.scalajs.js + +class ArrayBuffer(length: Int) extends js.Object { + + /** Length of this buffer in bytes */ + val byteLength: Int = js.native + + /** Returns a copy of the given slice of this array buffer */ + def slice(begin: Int, end: Int = ???): ArrayBuffer = js.native + + // Note: Some specifications specify a static isView method on ArrayBuffer + // that checks whether a given object is an ArrayBufferView. We omit it here + // since neither Node.js nor PhantomJS support it at the time of writing. + +} diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/js/typedarray/ArrayBufferInputStream.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/js/typedarray/ArrayBufferInputStream.scala new file mode 100644 index 0000000..f3d2afb --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/js/typedarray/ArrayBufferInputStream.scala @@ -0,0 +1,88 @@ +package scala.scalajs.js.typedarray + +import java.io.InputStream + +/** A java.io.InputStream wrapping a JavaScript ArrayBuffer + * + * This class is extremely similar to a ByteArrayInputStream, but + * uses ArrayBuffers as the underlying representation. Stream + * implementations may special case on this stream for better + * performance and access the underlying buffer directly. (They still + * need to make sure the internal pointers are properly aligned + * though). + * + * This stream has several public members (n.b. [[buffer]], [[offset]], + * [[length]] and [[pos]]) in order to allow JavaScript aware applications to + * special case on this kind of stream and access the underlying + * [[ArrayBuffer]] directly for efficiency. In this case it is the client's + * responsibility to synchronize [[pos]], as if the stream were read normally + * (if the context in which it is used requires this). + * + * @param buffer Underlying ArrayBuffer + * @param offset Offset in bytes in [[buffer]] + * @param length Length in bytes in [[buffer]] + */ +class ArrayBufferInputStream(val buffer: ArrayBuffer, val offset: Int, + val length: Int) extends InputStream { + + /** Convenience constructor. Strictly equivalent to + * {{new ArrayBufferInputStream(buffer, 0, buffer.byteLength)} + */ + def this(buffer: ArrayBuffer) = this(buffer, 0, buffer.byteLength) + + private val uintView = new Uint8Array(buffer, offset, length) + private val byteView = new Int8Array(buffer, offset, length) + + /** Used to persist [[pos]] when mark is called */ + protected var mark: Int = 0 + + /** Next byte to read in the buffer (after adding offset). + * + * Use [[skip]] to update (protects from overrun and moving backwards). + */ + @inline def pos: Int = _pos + @inline protected def pos_=(x: Int): Unit = _pos = x + private[this] var _pos: Int = 0 + + override def available(): Int = length - pos + override def mark(readlimit: Int): Unit = { mark = pos } + override def markSupported(): Boolean = true + def read(): Int = { + if (pos < length) { + val res = uintView(pos) + pos += 1 + res + } else -1 + } + + override def read(b: Array[Byte], off: Int, reqLen: Int): Int = { + if (off < 0 || reqLen < 0 || reqLen > b.length - off) + throw new IndexOutOfBoundsException + + val len = Math.min(reqLen, length - pos) + + if (reqLen == 0) + 0 // 0 requested, 0 returned + else if (len == 0) + -1 // nothing to read at all + else { + var i = 0 + while (i < len) { + b(i + off) = byteView(pos + i) + i += 1 + } + pos += len + len + } + } + + override def reset(): Unit = { pos = mark } + + /** Skips a given number of bytes. Always skips the maximum number possible */ + override def skip(n: Long): Long = { + val k = Math.max(0, Math.min(n, length - pos)).toInt + pos += k + k.toLong + } + +} diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/js/typedarray/ArrayBufferView.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/js/typedarray/ArrayBufferView.scala new file mode 100644 index 0000000..6b25bf5 --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/js/typedarray/ArrayBufferView.scala @@ -0,0 +1,14 @@ +package scala.scalajs.js.typedarray + +import scala.scalajs.js + +trait ArrayBufferView extends js.Object { + /** The underlying buffer of this ArrayBufferView */ + val buffer: ArrayBuffer = js.native + + /** The number of bytes of this ArrayBufferView */ + val byteLength: Int = js.native + + /** The offset of this ArrayBufferView in the underlying buffer */ + val byteOffset: Int = js.native +} diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/js/typedarray/DataView.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/js/typedarray/DataView.scala new file mode 100644 index 0000000..d97544c --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/js/typedarray/DataView.scala @@ -0,0 +1,26 @@ +package scala.scalajs.js.typedarray + +import scala.scalajs.js + +class DataView(buffer: ArrayBuffer, byteOffset: Int = 0, + byteLength: Int = ???) extends ArrayBufferView { + + def getInt8(byteOffset: Int): Byte = js.native + def getUint8(byteOffset: Int): Short = js.native + def getInt16(byteOffset: Int, littleEndian: Boolean = false): Short = js.native + def getUint16(byteOffset: Int, littleEndian: Boolean = false): Int = js.native + def getInt32(byteOffset: Int, littleEndian: Boolean = false): Int = js.native + def getUint32(byteOffset: Int, littleEndian: Boolean = false): Double = js.native + def getFloat32(byteOffset: Int, littleEndian: Boolean = false): Float = js.native + def getFloat64(byteOffset: Int, littleEndian: Boolean = false): Double = js.native + + def setInt8(byteOffset: Int, value: Byte): Unit = js.native + def setUint8(byteOffset: Int, value: Short): Unit = js.native + def setInt16(byteOffset: Int, value: Short, littleEndian: Boolean = false): Unit = js.native + def setUint16(byteOffset: Int, value: Int, littleEndian: Boolean = false): Unit = js.native + def setInt32(byteOffset: Int, value: Int, littleEndian: Boolean = false): Unit = js.native + def setUint32(byteOffset: Int, value: Double, littleEndian: Boolean = false): Unit = js.native + def setFloat32(byteOffset: Int, value: Float, littleEndian: Boolean = false): Unit = js.native + def setFloat64(byteOffset: Int, value: Double, littleEndian: Boolean = false): Unit = js.native + +} diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/js/typedarray/Float32Array.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/js/typedarray/Float32Array.scala new file mode 100644 index 0000000..abb0dd9 --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/js/typedarray/Float32Array.scala @@ -0,0 +1,24 @@ +package scala.scalajs.js.typedarray + +import scala.scalajs.js + +class Float32Array private extends TypedArray[Float, Float32Array] { + + /** Constructs a Float32Array with the given length. Initialized to all 0 */ + def this(length: Int) = this() + + /** Creates a new Float32Array with the same elements than the given TypedArray + * + * The elements are converted before being stored in the new Int8Array. + */ + def this(typedArray: TypedArray[_, _]) = this() + + /** Creates a new Float32Array with the elements in the given array */ + def this(array: js.Array[_]) = this() + + /** Creates a Float32Array view on the given ArrayBuffer */ + def this(buffer: ArrayBuffer, byteOffset: Int = 0, length: Int = ???) = this() + +} + +object Float32Array extends TypedArrayStatic diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/js/typedarray/Float64Array.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/js/typedarray/Float64Array.scala new file mode 100644 index 0000000..526b376 --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/js/typedarray/Float64Array.scala @@ -0,0 +1,24 @@ +package scala.scalajs.js.typedarray + +import scala.scalajs.js + +class Float64Array private extends TypedArray[Double, Float64Array] { + + /** Constructs a Float64Array with the given length. Initialized to all 0 */ + def this(length: Int) = this() + + /** Creates a new Float64Array with the same elements than the given TypedArray + * + * The elements are converted before being stored in the new Int8Array. + */ + def this(typedArray: TypedArray[_, _]) = this() + + /** Creates a new Float64Array with the elements in the given array */ + def this(array: js.Array[_]) = this() + + /** Creates a Float64Array view on the given ArrayBuffer */ + def this(buffer: ArrayBuffer, byteOffset: Int = 0, length: Int = ???) = this() + +} + +object Float64Array extends TypedArrayStatic diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/js/typedarray/Int16Array.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/js/typedarray/Int16Array.scala new file mode 100644 index 0000000..c71f101 --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/js/typedarray/Int16Array.scala @@ -0,0 +1,24 @@ +package scala.scalajs.js.typedarray + +import scala.scalajs.js + +class Int16Array private extends TypedArray[Short, Int16Array] { + + /** Constructs a Int16Array with the given length. Initialized to all 0 */ + def this(length: Int) = this() + + /** Creates a new Int16Array with the same elements than the given TypedArray + * + * The elements are converted before being stored in the new Int8Array. + */ + def this(typedArray: TypedArray[_, _]) = this() + + /** Creates a new Int16Array with the elements in the given array */ + def this(array: js.Array[_]) = this() + + /** Creates a Int16Array view on the given ArrayBuffer */ + def this(buffer: ArrayBuffer, byteOffset: Int = 0, length: Int = ???) = this() + +} + +object Int16Array extends TypedArrayStatic diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/js/typedarray/Int32Array.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/js/typedarray/Int32Array.scala new file mode 100644 index 0000000..37208e9 --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/js/typedarray/Int32Array.scala @@ -0,0 +1,24 @@ +package scala.scalajs.js.typedarray + +import scala.scalajs.js + +class Int32Array private extends TypedArray[Int, Int32Array] { + + /** Constructs a Int32Array with the given length. Initialized to all 0 */ + def this(length: Int) = this() + + /** Creates a new Int32Array with the same elements than the given TypedArray + * + * The elements are converted before being stored in the new Int8Array. + */ + def this(typedArray: TypedArray[_, _]) = this() + + /** Creates a new Int32Array with the elements in the given array */ + def this(array: js.Array[_]) = this() + + /** Creates a Int32Array view on the given ArrayBuffer */ + def this(buffer: ArrayBuffer, byteOffset: Int = 0, length: Int = ???) = this() + +} + +object Int32Array extends TypedArrayStatic diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/js/typedarray/Int8Array.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/js/typedarray/Int8Array.scala new file mode 100644 index 0000000..690ff07 --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/js/typedarray/Int8Array.scala @@ -0,0 +1,24 @@ +package scala.scalajs.js.typedarray + +import scala.scalajs.js + +class Int8Array private extends TypedArray[Byte, Int8Array] { + + /** Constructs a Int8Array with the given length. Initialized to all 0 */ + def this(length: Int) = this() + + /** Creates a new Int8Array with the same elements than the given TypedArray + * + * The elements are converted before being stored in the new Int8Array. + */ + def this(typedArray: TypedArray[_, _]) = this() + + /** Creates a new Int8Array with the elements in the given array */ + def this(array: js.Array[_]) = this() + + /** Creates a Int8Array view on the given ArrayBuffer */ + def this(buffer: ArrayBuffer, byteOffset: Int = 0, length: Int = ???) = this() + +} + +object Int8Array extends TypedArrayStatic diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/js/typedarray/TypedArray.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/js/typedarray/TypedArray.scala new file mode 100644 index 0000000..4e33b5d --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/js/typedarray/TypedArray.scala @@ -0,0 +1,46 @@ +package scala.scalajs.js.typedarray + +import scala.scalajs.js +import scala.scalajs.js.annotation.JSBracketAccess + +trait TypedArray[T, Repr] extends ArrayBufferView { + + /** The number of elements in this TypedArray */ + val length: Int = js.native + + /** Retrieve element at index */ + @JSBracketAccess + def apply(index: Int): T = js.native + + /** Set element at index */ + @JSBracketAccess + def update(index: Int, value: T): Unit = js.native + + /** Retrieve element at index */ + @JSBracketAccess + def get(index: Int): T = js.native + + /** Set element at index */ + @JSBracketAccess + def set(index: Int, value: T): Unit = js.native + + /** Set the values of typedArray in this TypedArray */ + def set(typedArray: TypedArray[_, _]): Unit = js.native + + /** Set the values of typedArray in this TypedArray at given offset */ + def set(typedArray: TypedArray[_, _], offset: Int): Unit = js.native + + /** Set the values from array in this TypedArray */ + def set(array: js.Array[_]): Unit = js.native + + /** Set the values from array in this TypedArray at given offset */ + def set(array: js.Array[_], offset: Int): Unit = js.native + + /** Create a new TypedArray view of this TypedArray at given location */ + def subarray(begin: Int, end: Int = ???): Repr = js.native + +} + +trait TypedArrayStatic extends js.Object { + val BYTES_PER_ELEMENT: Int = js.native +} diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/js/typedarray/Uint16Array.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/js/typedarray/Uint16Array.scala new file mode 100644 index 0000000..82d2847 --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/js/typedarray/Uint16Array.scala @@ -0,0 +1,24 @@ +package scala.scalajs.js.typedarray + +import scala.scalajs.js + +class Uint16Array private extends TypedArray[Int, Uint16Array] { + + /** Constructs a Uint16Array with the given length. Initialized to all 0 */ + def this(length: Int) = this() + + /** Creates a new Uint16Array with the same elements than the given TypedArray + * + * The elements are converted before being stored in the new Int8Array. + */ + def this(typedArray: TypedArray[_, _]) = this() + + /** Creates a new Uint16Array with the elements in the given array */ + def this(array: js.Array[_]) = this() + + /** Creates a Uint16Array view on the given ArrayBuffer */ + def this(buffer: ArrayBuffer, byteOffset: Int = 0, length: Int = ???) = this() + +} + +object Uint16Array extends TypedArrayStatic diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/js/typedarray/Uint32Array.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/js/typedarray/Uint32Array.scala new file mode 100644 index 0000000..9742e19 --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/js/typedarray/Uint32Array.scala @@ -0,0 +1,24 @@ +package scala.scalajs.js.typedarray + +import scala.scalajs.js + +class Uint32Array private extends TypedArray[Double, Uint32Array] { + + /** Constructs a Uint32Array with the given length. Initialized to all 0 */ + def this(length: Int) = this() + + /** Creates a new Uint32Array with the same elements than the given TypedArray + * + * The elements are converted before being stored in the new Int8Array. + */ + def this(typedArray: TypedArray[_, _]) = this() + + /** Creates a new Uint32Array with the elements in the given array */ + def this(array: js.Array[_]) = this() + + /** Creates a Uint32Array view on the given ArrayBuffer */ + def this(buffer: ArrayBuffer, byteOffset: Int = 0, length: Int = ???) = this() + +} + +object Uint32Array extends TypedArrayStatic diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/js/typedarray/Uint8Array.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/js/typedarray/Uint8Array.scala new file mode 100644 index 0000000..f54904c --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/js/typedarray/Uint8Array.scala @@ -0,0 +1,24 @@ +package scala.scalajs.js.typedarray + +import scala.scalajs.js + +class Uint8Array private extends TypedArray[Short, Uint8Array] { + + /** Constructs a Uint8Array with the given length. Initialized to all 0 */ + def this(length: Int) = this() + + /** Creates a new Uint8Array with the same elements than the given TypedArray + * + * The elements are converted before being stored in the new Int8Array. + */ + def this(typedArray: TypedArray[_, _]) = this() + + /** Creates a new Uint8Array with the elements in the given array */ + def this(array: js.Array[_]) = this() + + /** Creates a Uint8Array view on the given ArrayBuffer */ + def this(buffer: ArrayBuffer, byteOffset: Int = 0, length: Int = ???) = this() + +} + +object Uint8Array extends TypedArrayStatic diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/js/typedarray/Uint8ClampedArray.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/js/typedarray/Uint8ClampedArray.scala new file mode 100644 index 0000000..601d65c --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/js/typedarray/Uint8ClampedArray.scala @@ -0,0 +1,24 @@ +package scala.scalajs.js.typedarray + +import scala.scalajs.js + +class Uint8ClampedArray private extends TypedArray[Int, Uint8ClampedArray] { + + /** Constructs a Uint8ClampedArray with the given length. Initialized to all 0 */ + def this(length: Int) = this() + + /** Creates a new Uint8ClampedArray with the same elements than the given TypedArray + * + * The elements are converted before being stored in the new Int8Array. + */ + def this(typedArray: TypedArray[_, _]) = this() + + /** Creates a new Uint8ClampedArray with the elements in the given array */ + def this(array: js.Array[_]) = this() + + /** Creates a Uint8ClampedArray view on the given ArrayBuffer */ + def this(buffer: ArrayBuffer, byteOffset: Int = 0, length: Int = ???) = this() + +} + +object Uint8ClampedArray extends TypedArrayStatic diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/js/typedarray/package.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/js/typedarray/package.scala new file mode 100644 index 0000000..0ab5a05 --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/js/typedarray/package.scala @@ -0,0 +1,145 @@ +package scala.scalajs.js + +import JSConverters._ + +/** The typdearray package provides facade types for JavaScript + * ArrayBuffer, TypeArrays and DataView. Further, it provides + * conversions between primitive Scala arrays and TypedArrays + */ +package object typedarray { + + // Implicit classes scala.Array -> TypedArray + implicit class AB2TA(val array: scala.Array[Byte]) extends AnyVal { + def toTypedArray: Int8Array = byteArray2Int8Array(array) + } + + implicit class AS2TA(val array: scala.Array[Short]) extends AnyVal { + def toTypedArray: Int16Array = shortArray2Int16Array(array) + } + + implicit class AC2TA(val array: scala.Array[Char]) extends AnyVal { + def toTypedArray: Uint16Array = charArray2Uint16Array(array) + } + + implicit class AI2TA(val array: scala.Array[Int]) extends AnyVal { + def toTypedArray: Int32Array = intArray2Int32Array(array) + } + + implicit class AF2TA(val array: scala.Array[Float]) extends AnyVal { + def toTypedArray: Float32Array = floatArray2Float32Array(array) + } + + implicit class AD2TA(val array: scala.Array[Double]) extends AnyVal { + def toTypedArray: Float64Array = doubleArray2Float64Array(array) + } + + // Implicit classes TypedArray -> scala.Array + implicit class TA2AB(val array: Int8Array) extends AnyVal { + def toArray: scala.Array[Byte] = int8Array2ByteArray(array) + } + + implicit class TA2AS(val array: Int16Array) extends AnyVal { + def toArray: scala.Array[Short] = int16Array2ShortArray(array) + } + + implicit class TA2AC(val array: Uint16Array) extends AnyVal { + def toArray: scala.Array[Char] = uint16Array2CharArray(array) + } + + implicit class TA2AI(val array: Int32Array) extends AnyVal { + def toArray: scala.Array[Int] = int32Array2IntArray(array) + } + + implicit class TA2AF(val array: Float32Array) extends AnyVal { + def toArray: scala.Array[Float] = float32Array2FloatArray(array) + } + + implicit class TA2AD(val array: Float64Array) extends AnyVal { + def toArray: scala.Array[Double] = float64Array2DoubleArray(array) + } + + // scala.Array -> TypedArray + + def byteArray2Int8Array(array: scala.Array[Byte]): Int8Array = + array2typedArrayImpl(array, new Int8Array(array.length)) + + def shortArray2Int16Array(array: scala.Array[Short]): Int16Array = + array2typedArrayImpl(array, new Int16Array(array.length)) + + def charArray2Uint16Array(array: scala.Array[Char]): Uint16Array = { + // Can't use array2typedArrayImpl because Uint16Array contains Ints + val len = array.length + val dest = new Uint16Array(len) + var i = 0 + while (i < len) { + dest(i) = array(i).toInt + i += 1 + } + dest + } + + def intArray2Int32Array(array: scala.Array[Int]): Int32Array = + array2typedArrayImpl(array, new Int32Array(array.length)) + + def floatArray2Float32Array(array: scala.Array[Float]): Float32Array = + array2typedArrayImpl(array, new Float32Array(array.length)) + + def doubleArray2Float64Array(array: scala.Array[Double]): Float64Array = + array2typedArrayImpl(array, new Float64Array(array.length)) + + @inline private def array2typedArrayImpl[ + @specialized(Byte, Short, Int, Float, Double) T, + Repr <: TypedArray[T, Repr]]( + array: scala.Array[T], dest: Repr): Repr = { + val len = array.length + var i = 0 + while (i < len) { + dest(i) = array(i) + i += 1 + } + dest + } + + // TypedArray -> scala.Array + + def int8Array2ByteArray(array: Int8Array): scala.Array[Byte] = + typedArray2arrayImpl(array, new scala.Array(array.length)) + + def int16Array2ShortArray(array: Int16Array): scala.Array[Short] = + typedArray2arrayImpl(array, new scala.Array(array.length)) + + def uint16Array2CharArray(array: Uint16Array): scala.Array[Char] = { + // Can't use typedArray2arrayImpl because Uint16Array contains Ints + val len = array.length + val dest = new scala.Array[Char](len) + var i = 0 + while (i < len) { + dest(i) = array(i).toChar + i += 1 + } + dest + } + + def int32Array2IntArray(array: Int32Array): scala.Array[Int] = + typedArray2arrayImpl(array, new scala.Array(array.length)) + + def float32Array2FloatArray(array: Float32Array): scala.Array[Float] = + typedArray2arrayImpl(array, new scala.Array(array.length)) + + def float64Array2DoubleArray(array: Float64Array): scala.Array[Double] = + typedArray2arrayImpl(array, new scala.Array(array.length)) + + @inline private def typedArray2arrayImpl[ + @specialized(Byte, Short, Int, Float, Double) T, + Repr <: TypedArray[T, Repr]]( + array: Repr, dest: scala.Array[T]): scala.Array[T] = { + val len = dest.length + var i = 0 + while (i < len) { + dest(i) = array(i) + i += 1 + } + dest + } + +} diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/niocharset/ISO_8859_1.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/niocharset/ISO_8859_1.scala new file mode 100644 index 0000000..7765f0c --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/niocharset/ISO_8859_1.scala @@ -0,0 +1,19 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js API ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.niocharset + +import java.nio.charset._ + +private[niocharset] object ISO_8859_1 extends ISO_8859_1_And_US_ASCII_Common( + "ISO-8859-1", Array( + "csISOLatin1", "IBM-819", "iso-ir-100", "8859_1", "ISO_8859-1", "l1", + "ISO8859-1", "ISO_8859_1", "cp819", "ISO8859_1", "latin1", + "ISO_8859-1:1987", "819", "IBM819"), + maxValue = 0xff) diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/niocharset/ISO_8859_1_And_US_ASCII_Common.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/niocharset/ISO_8859_1_And_US_ASCII_Common.scala new file mode 100644 index 0000000..ddc83db --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/niocharset/ISO_8859_1_And_US_ASCII_Common.scala @@ -0,0 +1,197 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js API ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.niocharset + +import scala.annotation.tailrec + +import java.nio._ +import java.nio.charset._ + +/** This is a very specific common implementation for ISO_8859_1 and US_ASCII. + * Only a single constant changes between the two algorithms (`maxValue`). + * No attempt was made at generalizing this to other potential charsets. + * + * `maxValue` is therefore either 0xff (ISO_8859_1) or 0x7f (US_ASCII). + */ +private[niocharset] abstract class ISO_8859_1_And_US_ASCII_Common protected ( + name: String, aliases: Array[String], + private val maxValue: Int) extends Charset(name, aliases) { + + def contains(that: Charset): Boolean = that match { + case that: ISO_8859_1_And_US_ASCII_Common => this.maxValue >= that.maxValue + case _ => false + } + + def newDecoder(): CharsetDecoder = new Decoder + def newEncoder(): CharsetEncoder = new Encoder + + private class Decoder extends CharsetDecoder( + ISO_8859_1_And_US_ASCII_Common.this, 1.0f, 1.0f) { + def decodeLoop(in: ByteBuffer, out: CharBuffer): CoderResult = { + val maxValue = ISO_8859_1_And_US_ASCII_Common.this.maxValue + val inRemaining = in.remaining + if (inRemaining == 0) { + CoderResult.UNDERFLOW + } else { + val outRemaining = out.remaining + val overflow = outRemaining < inRemaining + val rem = if (overflow) outRemaining else inRemaining + + if (in.hasArray && out.hasArray) { + val inArr = in.array + val inOffset = in.arrayOffset + val inStart = in.position + inOffset + val inEnd = inStart + rem + + val outArr = out.array + val outOffset = out.arrayOffset + val outStart = out.position + outOffset + + var inPos = inStart + var outPos = outStart + while (inPos != inEnd) { + // Apparently ignoring the bit 7 in US_ASCII is the expected behavior + outArr(outPos) = (inArr(inPos).toInt & maxValue).toChar + inPos += 1 + outPos += 1 + } + + in.position(inPos - inOffset) + out.position(outPos - outOffset) + } else { + /* Here, it's fine to read all the remaining bytes from the input, + * because we will *always* use all of them. + */ + var i = 0 + while (i != rem) { + // Apparently ignoring the bit 7 in US_ASCII is the expected behavior + out.put((in.get(i).toInt & maxValue).toChar) + i += 1 + } + } + + if (overflow) CoderResult.OVERFLOW + else CoderResult.UNDERFLOW + } + } + } + + private class Encoder extends CharsetEncoder( + ISO_8859_1_And_US_ASCII_Common.this, 1.0f, 1.0f) { + def encodeLoop(in: CharBuffer, out: ByteBuffer): CoderResult = { + import java.lang.Character.{MIN_SURROGATE, MAX_SURROGATE} + + val maxValue = ISO_8859_1_And_US_ASCII_Common.this.maxValue + val inRemaining = in.remaining + if (inRemaining == 0) { + CoderResult.UNDERFLOW + } else { + if (in.hasArray && out.hasArray) { + val outRemaining = out.remaining + val overflow = outRemaining < inRemaining + val rem = if (overflow) outRemaining else inRemaining + + val inArr = in.array + val inOffset = in.arrayOffset + val inStart = in.position + inOffset + val inEnd = inStart + rem + + val outArr = out.array + val outOffset = out.arrayOffset + val outStart = out.position + outOffset + + @inline + @tailrec + def loop(inPos: Int, outPos: Int): CoderResult = { + @inline + def finalize(result: CoderResult): CoderResult = { + in.position(inPos - inOffset) + out.position(outPos - outOffset) + result + } + + if (inPos == inEnd) { + finalize { + if (overflow) CoderResult.OVERFLOW + else CoderResult.UNDERFLOW + } + } else { + val c = inArr(inPos) + if (c <= maxValue) { + outArr(outPos) = c.toByte + loop(inPos+1, outPos+1) + } else { + finalize { + if (Character.isLowSurrogate(c)) { + CoderResult.malformedForLength(1) + } else if (Character.isHighSurrogate(c)) { + if (inPos + 1 < in.limit) { + val c2 = inArr(inPos+1) + if (Character.isLowSurrogate(c2)) + CoderResult.unmappableForLength(2) + else + CoderResult.malformedForLength(1) + } else { + CoderResult.UNDERFLOW + } + } else { + CoderResult.unmappableForLength(1) + } + } + } + } + } + + loop(inStart, outStart) + } else { + // Not both have arrays + @inline + @tailrec + def loop(): CoderResult = { + if (!in.hasRemaining) { + CoderResult.UNDERFLOW + } else if (!out.hasRemaining) { + CoderResult.OVERFLOW + } else { + val c = in.get() + if (c <= maxValue) { + out.put(c.toByte) + loop() + } else { + if (Character.isLowSurrogate(c)) { + in.position(in.position - 1) + CoderResult.malformedForLength(1) + } else if (Character.isHighSurrogate(c)) { + if (in.hasRemaining) { + val c2 = in.get() + in.position(in.position - 2) + if (Character.isLowSurrogate(c2)) { + CoderResult.unmappableForLength(2) + } else { + CoderResult.malformedForLength(1) + } + } else { + in.position(in.position - 1) + CoderResult.UNDERFLOW + } + } else { + in.position(in.position - 1) + CoderResult.unmappableForLength(1) + } + } + } + } + + loop() + } + } + } + } +} diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/niocharset/StandardCharsets.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/niocharset/StandardCharsets.scala new file mode 100644 index 0000000..38615f6 --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/niocharset/StandardCharsets.scala @@ -0,0 +1,42 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js API ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.niocharset + +import java.nio.charset._ + +/** Standard charsets. + * This is basically the same as [[java.nio.charset.StandardCharsets]], but + * it is also available when compiling with a JDK 6. + */ +object StandardCharsets { + import scala.scalajs.niocharset + + /** ISO-8859-1, aka latin1. */ + def ISO_8859_1: Charset = niocharset.ISO_8859_1 + + /** US-ASCII. */ + def US_ASCII: Charset = niocharset.US_ASCII + + /** UTF-8. */ + def UTF_8: Charset = niocharset.UTF_8 + + /** UTF-16 Big Endian without BOM. */ + def UTF_16BE: Charset = niocharset.UTF_16BE + + /** UTF-16 Little Endian without BOM. */ + def UTF_16LE: Charset = niocharset.UTF_16LE + + /** UTF-16 with an optional BOM. + * When encoding, Big Endian is always used. + * When decoding, the BOM specifies what endianness to use. If no BOM is + * found, it defaults to Big Endian. + */ + def UTF_16: Charset = niocharset.UTF_16 +} diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/niocharset/US_ASCII.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/niocharset/US_ASCII.scala new file mode 100644 index 0000000..746c75b --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/niocharset/US_ASCII.scala @@ -0,0 +1,19 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js API ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.niocharset + +import java.nio.charset._ + +private[niocharset] object US_ASCII extends ISO_8859_1_And_US_ASCII_Common( + "US-ASCII", Array( + "cp367", "ascii7", "ISO646-US", "646", "csASCII", "us", "iso_646.irv:1983", + "ISO_646.irv:1991", "IBM367", "ASCII", "default", "ANSI_X3.4-1986", + "ANSI_X3.4-1968", "iso-ir-6"), + maxValue = 0x7f) diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/niocharset/UTF_16.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/niocharset/UTF_16.scala new file mode 100644 index 0000000..9d1748a --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/niocharset/UTF_16.scala @@ -0,0 +1,17 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js API ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.niocharset + +import java.nio.charset._ + +private[niocharset] object UTF_16 extends UTF_16_Common( + "UTF-16", Array( + "utf16", "UTF_16", "UnicodeBig", "unicode"), + endianness = UTF_16_Common.AutoEndian) diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/niocharset/UTF_16BE.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/niocharset/UTF_16BE.scala new file mode 100644 index 0000000..dece191 --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/niocharset/UTF_16BE.scala @@ -0,0 +1,17 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js API ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.niocharset + +import java.nio.charset._ + +private[niocharset] object UTF_16BE extends UTF_16_Common( + "UTF-16BE", Array( + "X-UTF-16BE", "UTF_16BE", "ISO-10646-UCS-2", "UnicodeBigUnmarked"), + endianness = UTF_16_Common.BigEndian) diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/niocharset/UTF_16LE.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/niocharset/UTF_16LE.scala new file mode 100644 index 0000000..de469c4 --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/niocharset/UTF_16LE.scala @@ -0,0 +1,17 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js API ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.niocharset + +import java.nio.charset._ + +private[niocharset] object UTF_16LE extends UTF_16_Common( + "UTF-16LE", Array( + "UnicodeLittleUnmarked", "UTF_16LE", "X-UTF-16LE"), + endianness = UTF_16_Common.LittleEndian) diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/niocharset/UTF_16_Common.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/niocharset/UTF_16_Common.scala new file mode 100644 index 0000000..3330d9c --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/niocharset/UTF_16_Common.scala @@ -0,0 +1,205 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js API ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.niocharset + +import scala.annotation.tailrec + +import java.nio._ +import java.nio.charset._ + +/** This is a very specific common implementation for UTF_16BE and UTF_16LE. + */ +private[niocharset] abstract class UTF_16_Common protected ( + name: String, aliases: Array[String], + private val endianness: Int) extends Charset(name, aliases) { + + import UTF_16_Common._ + + def contains(that: Charset): Boolean = true + + def newDecoder(): CharsetDecoder = new Decoder + def newEncoder(): CharsetEncoder = new Encoder + + private class Decoder extends CharsetDecoder( + UTF_16_Common.this, 0.5f, 1.0f) { + private var endianness = UTF_16_Common.this.endianness + + override protected def implReset(): Unit = { + super.implReset() + endianness = UTF_16_Common.this.endianness + } + + def decodeLoop(in: ByteBuffer, out: CharBuffer): CoderResult = { + @inline + @tailrec + def loop(): CoderResult = { + if (in.remaining < 2) CoderResult.UNDERFLOW + else { + val b1 = in.get() & 0xff + val b2 = in.get() & 0xff + + val wasBOM = if (endianness == AutoEndian) { + // Read BOM + if (b1 == 0xfe && b2 == 0xff) { + endianness = BigEndian + true + } else if (b1 == 0xff && b2 == 0xfe) { + endianness = LittleEndian + true + } else { + // Not a valid BOM: default to BigEndian and start reading + endianness = BigEndian + false + } + } else false + + if (wasBOM) { + loop() + } else { + val bigEndian = endianness == BigEndian + + @inline def bytes2char(hi: Int, lo: Int): Char = + (if (bigEndian) (hi << 8) | lo else (lo << 8) | hi).toChar + + val c1 = bytes2char(b1, b2) + + if (Character.isLowSurrogate(c1)) { + in.position(in.position - 2) + CoderResult.malformedForLength(2) + } else if (!Character.isHighSurrogate(c1)) { + if (out.remaining == 0) { + in.position(in.position - 2) + CoderResult.OVERFLOW + } else { + out.put(c1) + loop() + } + } else { + if (in.remaining < 2) { + in.position(in.position - 2) + CoderResult.UNDERFLOW + } else { + val b3 = in.get() & 0xff + val b4 = in.get() & 0xff + val c2 = bytes2char(b3, b4) + + if (!Character.isLowSurrogate(c2)) { + in.position(in.position - 4) + CoderResult.malformedForLength(2) + } else { + if (out.remaining < 2) { + in.position(in.position - 4) + CoderResult.OVERFLOW + } else { + out.put(c1) + out.put(c2) + loop() + } + } + } + } + } + } + } + + loop() + } + } + + private class Encoder extends CharsetEncoder( + UTF_16_Common.this, 2.0f, 2.0f, + // Character 0xfffd + if (endianness == LittleEndian) Array(-3, -1) else Array(-1, -3)) { + + private var needToWriteBOM: Boolean = endianness == AutoEndian + + override protected def implReset(): Unit = { + super.implReset() + needToWriteBOM = endianness == AutoEndian + } + + def encodeLoop(in: CharBuffer, out: ByteBuffer): CoderResult = { + if (needToWriteBOM) { + if (out.remaining < 2) { + return CoderResult.OVERFLOW + } else { + // Always encode in big endian + out.put(0xfe.toByte) + out.put(0xff.toByte) + needToWriteBOM = false + } + } + + val bigEndian = endianness != LittleEndian + + @inline + def putChar(c: Char): Unit = { + if (bigEndian) { + out.put((c >> 8).toByte) + out.put(c.toByte) + } else { + out.put(c.toByte) + out.put((c >> 8).toByte) + } + } + + @inline + @tailrec + def loop(): CoderResult = { + if (in.remaining == 0) CoderResult.UNDERFLOW + else { + val c1 = in.get() + + if (Character.isLowSurrogate(c1)) { + in.position(in.position - 1) + CoderResult.malformedForLength(1) + } else if (!Character.isHighSurrogate(c1)) { + if (out.remaining < 2) { + in.position(in.position - 1) + CoderResult.OVERFLOW + } else { + putChar(c1) + loop() + } + } else { + if (in.remaining < 1) { + in.position(in.position - 1) + CoderResult.UNDERFLOW + } else { + val c2 = in.get() + + if (!Character.isLowSurrogate(c2)) { + in.position(in.position - 2) + CoderResult.malformedForLength(1) + } else { + if (out.remaining < 4) { + in.position(in.position - 2) + CoderResult.OVERFLOW + } else { + putChar(c1) + putChar(c2) + loop() + } + } + } + } + } + } + + loop() + } + } +} + +private[niocharset] object UTF_16_Common { + final val AutoEndian = 0 + final val BigEndian = 1 + final val LittleEndian = 2 +} diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/niocharset/UTF_8.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/niocharset/UTF_8.scala new file mode 100644 index 0000000..57f4ad6 --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/niocharset/UTF_8.scala @@ -0,0 +1,455 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js API ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.niocharset + +import scala.annotation.{switch, tailrec} + +import java.nio._ +import java.nio.charset._ + +private[niocharset] object UTF_8 extends Charset("UTF-8", Array( + "UTF8", "unicode-1-1-utf-8")) { + + import java.lang.Character._ + + def contains(that: Charset): Boolean = true + + def newDecoder(): CharsetDecoder = new Decoder + def newEncoder(): CharsetEncoder = new Encoder + + /* The next table contains information about UTF-8 charset and + * correspondence of 1st byte to the length of sequence + * For information please visit http://www.ietf.org/rfc/rfc3629.txt + * + * ------------------------------------------------------------------- + * 0 1 2 3 Value + * ------------------------------------------------------------------- + * 0xxxxxxx 00000000 00000000 0xxxxxxx + * 110yyyyy 10xxxxxx 00000000 00000yyy yyxxxxxx + * 1110zzzz 10yyyyyy 10xxxxxx 00000000 zzzzyyyy yyxxxxxx + * 11110uuu 10zzzzzz 10yyyyyy 10xxxxxx 000uuuzz zzzzyyyy yyxxxxxx + */ + + private val lengthByLeading: Array[Int] = Array( + // 10wwwwww + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + // 110yyyyy + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + // 1110zzzz + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + // 11110uuu + 4, 4, 4, 4, 4, 4, 4, 4, + // > 11110111 + -1, -1, -1, -1, -1, -1, -1, -1 + ) + + @inline + private class DecodedMultiByte(val failure: CoderResult, + val high: Char, val low: Char) + + private object DecodedMultiByte { + @inline def apply(failure: CoderResult): DecodedMultiByte = + new DecodedMultiByte(failure, 0, 0) + + @inline def apply(single: Char): DecodedMultiByte = + new DecodedMultiByte(null, single, 0) + + @inline def apply(high: Char, low: Char): DecodedMultiByte = + new DecodedMultiByte(null, high, low) + } + + private class Decoder extends CharsetDecoder(UTF_8, 1.0f, 1.0f) { + def decodeLoop(in: ByteBuffer, out: CharBuffer): CoderResult = { + if (in.hasArray && out.hasArray) + decodeLoopArray(in, out) + else + decodeLoopNoArray(in, out) + } + + private def decodeLoopArray(in: ByteBuffer, out: CharBuffer): CoderResult = { + val inArray = in.array + val inOffset = in.arrayOffset + val inStart = in.position + inOffset + val inEnd = in.limit + inOffset + + val outArray = out.array + val outOffset = out.arrayOffset + val outStart = out.position + outOffset + val outEnd = out.limit + outOffset + + @inline + @tailrec + def loop(inPos: Int, outPos: Int): CoderResult = { + @inline + def finalize(result: CoderResult): CoderResult = { + in.position(inPos - inOffset) + out.position(outPos - outOffset) + result + } + + if (inPos == inEnd) { + finalize(CoderResult.UNDERFLOW) + } else { + val leading = inArray(inPos).toInt + if (leading >= 0) { + // US-ASCII repertoire + if (outPos == outEnd) { + finalize(CoderResult.OVERFLOW) + } else { + outArray(outPos) = leading.toChar + loop(inPos+1, outPos+1) + } + } else { + // Multi-byte + val length = lengthByLeading(leading & 0x7f) + if (length == -1) { + finalize(CoderResult.malformedForLength(1)) + } else if (inPos + length > inEnd) { + finalize(CoderResult.UNDERFLOW) + } else { + val decoded = { + val b2 = inArray(inPos+1) + if (length == 2) decode2(leading, b2) + else if (length == 3) decode3(leading, b2, inArray(inPos+2)) + else decode4(leading, b2, inArray(inPos+2), inArray(inPos+3)) + } + + if (decoded.failure != null) { + finalize(decoded.failure) + } else if (decoded.low == 0) { + // not a surrogate pair + if (outPos == outEnd) + finalize(CoderResult.OVERFLOW) + else { + outArray(outPos) = decoded.high + loop(inPos+length, outPos+1) + } + } else { + // a surrogate pair + if (outPos + 2 > outEnd) + finalize(CoderResult.OVERFLOW) + else { + outArray(outPos) = decoded.high + outArray(outPos+1) = decoded.low + loop(inPos+length, outPos+2) + } + } + } + } + } + } + + loop(inStart, outStart) + } + + private def decodeLoopNoArray(in: ByteBuffer, out: CharBuffer): CoderResult = { + @inline + @tailrec + def loop(): CoderResult = { + @inline + def finalize(read: Int, result: CoderResult): CoderResult = { + in.position(in.position - read) + result + } + + if (!in.hasRemaining) { + CoderResult.UNDERFLOW + } else { + val leading = in.get().toInt + if (leading >= 0) { + // US-ASCII repertoire + if (!out.hasRemaining) { + finalize(1, CoderResult.OVERFLOW) + } else { + out.put(leading.toChar) + loop() + } + } else { + // Multi-byte + val length = lengthByLeading(leading & 0x7f) + if (length == -1) { + finalize(1, CoderResult.malformedForLength(1)) + } else if (in.remaining < length-1) { + finalize(1, CoderResult.UNDERFLOW) + } else { + val decoded = { + if (length == 2) decode2(leading, in.get()) + else if (length == 3) decode3(leading, in.get(), in.get()) + else decode4(leading, in.get(), in.get(), in.get()) + } + + if (decoded.failure != null) { + finalize(length, decoded.failure) + } else if (decoded.low == 0) { + // not a surrogate pair + if (!out.hasRemaining) + finalize(length, CoderResult.OVERFLOW) + else { + out.put(decoded.high) + loop() + } + } else { + // a surrogate pair + if (out.remaining < 2) + finalize(length, CoderResult.OVERFLOW) + else { + out.put(decoded.high) + out.put(decoded.low) + loop() + } + } + } + } + } + } + + loop() + } + + @inline private def isInvalidNextByte(b: Int): Boolean = + (b & 0xc0) != 0x80 + + @inline private def decode2(b1: Int, b2: Int): DecodedMultiByte = { + if (isInvalidNextByte(b2)) + DecodedMultiByte(CoderResult.malformedForLength(1)) + else { + val codePoint = (((b1 & 0x1f) << 6) | (b2 & 0x3f)) + // By construction, 0 <= codePoint <= 0x7ff < MIN_SURROGATE + if (codePoint < 0x80) { + // Should have been encoded with only 1 byte + DecodedMultiByte(CoderResult.malformedForLength(2)) + } else { + DecodedMultiByte(codePoint.toChar) + } + } + } + + @inline private def decode3(b1: Int, b2: Int, b3: Int): DecodedMultiByte = { + if (isInvalidNextByte(b2)) + DecodedMultiByte(CoderResult.malformedForLength(1)) + else if (isInvalidNextByte(b3)) + DecodedMultiByte(CoderResult.malformedForLength(2)) + else { + val codePoint = (((b1 & 0xf) << 12) | ((b2 & 0x3f) << 6) | (b3 & 0x3f)) + // By construction, 0 <= codePoint <= 0xffff < MIN_SUPPLEMENTARY_CODE_POINT + if ((codePoint < 0x800) || + (codePoint >= MIN_SURROGATE && codePoint <= MAX_SURROGATE)) { + // Should have been encoded with only 1 or 2 bytes + // or it is a surrogate, which is not a valid code point + DecodedMultiByte(CoderResult.malformedForLength(3)) + } else { + DecodedMultiByte(codePoint.toChar) + } + } + } + + @inline private def decode4(b1: Int, b2: Int, b3: Int, b4: Int): DecodedMultiByte = { + if (isInvalidNextByte(b2)) + DecodedMultiByte(CoderResult.malformedForLength(1)) + else if (isInvalidNextByte(b3)) + DecodedMultiByte(CoderResult.malformedForLength(2)) + else if (isInvalidNextByte(b4)) + DecodedMultiByte(CoderResult.malformedForLength(3)) + else { + val codePoint = (((b1 & 0x7) << 18) | ((b2 & 0x3f) << 12) | + ((b3 & 0x3f) << 6) | (b4 & 0x3f)) + // By construction, 0 <= codePoint <= 0x1fffff + if (codePoint < 0x10000 || codePoint > MAX_CODE_POINT) { + // It should have been encoded with 1, 2, or 3 bytes + // or it is not a valid code point + DecodedMultiByte(CoderResult.malformedForLength(4)) + } else { + // Here, we need to encode the code point as a surrogate pair. + // http://en.wikipedia.org/wiki/UTF-16 + val offsetCodePoint = codePoint - 0x10000 + DecodedMultiByte( + ((offsetCodePoint >> 10) | 0xd800).toChar, + ((offsetCodePoint & 0x3ff) | 0xdc00).toChar) + } + } + } + } + + private class Encoder extends CharsetEncoder(UTF_8, 1.1f, 4.0f) { + def encodeLoop(in: CharBuffer, out: ByteBuffer): CoderResult = { + if (in.hasArray && out.hasArray) + encodeLoopArray(in, out) + else + encodeLoopNoArray(in, out) + } + + private def encodeLoopArray(in: CharBuffer, out: ByteBuffer): CoderResult = { + val inArray = in.array + val inOffset = in.arrayOffset + val inStart = in.position + inOffset + val inEnd = in.limit + inOffset + + val outArray = out.array + val outOffset = out.arrayOffset + val outStart = out.position + outOffset + val outEnd = out.limit + outOffset + + @inline + @tailrec + def loop(inPos: Int, outPos: Int): CoderResult = { + @inline + def finalize(result: CoderResult): CoderResult = { + in.position(inPos - inOffset) + out.position(outPos - outOffset) + result + } + + if (inPos == inEnd) { + finalize(CoderResult.UNDERFLOW) + } else { + val c1 = inArray(inPos) + + if (c1 < 0x80) { + // Encoding in one byte + if (outPos == outEnd) + finalize(CoderResult.OVERFLOW) + else { + outArray(outPos) = c1.toByte + loop(inPos+1, outPos+1) + } + } else if (c1 < 0x800) { + // Encoding in 2 bytes (by construction, not a surrogate) + if (outPos + 2 > outEnd) + finalize(CoderResult.OVERFLOW) + else { + outArray(outPos) = ((c1 >> 6) | 0xc0).toByte + outArray(outPos+1) = ((c1 & 0x3f) | 0x80).toByte + loop(inPos+1, outPos+2) + } + } else if (!isSurrogate(c1)) { + // Not a surrogate, encoding in 3 bytes + if (outPos + 3 > outEnd) + finalize(CoderResult.OVERFLOW) + else { + outArray(outPos) = ((c1 >> 12) | 0xe0).toByte + outArray(outPos+1) = (((c1 >> 6) & 0x3f) | 0x80).toByte + outArray(outPos+2) = ((c1 & 0x3f) | 0x80).toByte + loop(inPos+1, outPos+3) + } + } else if (isHighSurrogate(c1)) { + // Should have a low surrogate that follows + if (inPos + 1 == inEnd) + finalize(CoderResult.UNDERFLOW) + else { + val c2 = inArray(inPos+1) + if (!isLowSurrogate(c2)) { + finalize(CoderResult.malformedForLength(1)) + } else { + // Surrogate pair, encoding in 4 bytes + if (outPos + 4 > outEnd) + finalize(CoderResult.OVERFLOW) + else { + val cp = toCodePoint(c1, c2) + outArray(outPos) = ((cp >> 18) | 0xf0).toByte + outArray(outPos+1) = (((cp >> 12) & 0x3f) | 0x80).toByte + outArray(outPos+2) = (((cp >> 6) & 0x3f) | 0x80).toByte + outArray(outPos+3) = ((cp & 0x3f) | 0x80).toByte + loop(inPos+2, outPos+4) + } + } + } + } else { + finalize(CoderResult.malformedForLength(1)) + } + } + } + + loop(inStart, outStart) + } + + private def encodeLoopNoArray(in: CharBuffer, out: ByteBuffer): CoderResult = { + @inline + @tailrec + def loop(): CoderResult = { + @inline + def finalize(read: Int, result: CoderResult): CoderResult = { + in.position(in.position - read) + result + } + + if (!in.hasRemaining) { + CoderResult.UNDERFLOW + } else { + val c1 = in.get() + + if (c1 < 0x80) { + // Encoding in one byte + if (!out.hasRemaining) + finalize(1, CoderResult.OVERFLOW) + else { + out.put(c1.toByte) + loop() + } + } else if (c1 < 0x800) { + // Encoding in 2 bytes (by construction, not a surrogate) + if (out.remaining < 2) + finalize(1, CoderResult.OVERFLOW) + else { + out.put(((c1 >> 6) | 0xc0).toByte) + out.put(((c1 & 0x3f) | 0x80).toByte) + loop() + } + } else if (!isSurrogate(c1)) { + // Not a surrogate, encoding in 3 bytes + if (out.remaining < 3) + finalize(1, CoderResult.OVERFLOW) + else { + out.put(((c1 >> 12) | 0xe0).toByte) + out.put((((c1 >> 6) & 0x3f) | 0x80).toByte) + out.put(((c1 & 0x3f) | 0x80).toByte) + loop() + } + } else if (isHighSurrogate(c1)) { + // Should have a low surrogate that follows + if (!in.hasRemaining) + finalize(1, CoderResult.UNDERFLOW) + else { + val c2 = in.get() + if (!isLowSurrogate(c2)) { + finalize(2, CoderResult.malformedForLength(1)) + } else { + // Surrogate pair, encoding in 4 bytes + if (out.remaining < 4) + finalize(2, CoderResult.OVERFLOW) + else { + val cp = toCodePoint(c1, c2) + out.put(((cp >> 18) | 0xf0).toByte) + out.put((((cp >> 12) & 0x3f) | 0x80).toByte) + out.put((((cp >> 6) & 0x3f) | 0x80).toByte) + out.put(((cp & 0x3f) | 0x80).toByte) + loop() + } + } + } + } else { + finalize(1, CoderResult.malformedForLength(1)) + } + } + } + + loop() + } + } + + private final val SurrogateMask = 0xf800 // 11111 0 00 00000000 + private final val SurrogateID = 0xd800 // 11011 0 00 00000000 + + @inline private def isSurrogate(c: Char): Boolean = + (c & SurrogateMask) == SurrogateID +} diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/runtime/AnonFunctions.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/runtime/AnonFunctions.scala new file mode 100644 index 0000000..861d81a --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/runtime/AnonFunctions.scala @@ -0,0 +1,119 @@ +package scala.scalajs.runtime + +import scala.scalajs.js +import scala.runtime._ + +@inline +final class AnonFunction0[+R](f: js.Function0[R]) extends AbstractFunction0[R] { + override def apply(): R = f() +} + +@inline +final class AnonFunction1[-T1, +R](f: js.Function1[T1, R]) extends AbstractFunction1[T1, R] { + override def apply(arg1: T1): R = f(arg1) +} + +@inline +final class AnonFunction2[-T1, -T2, +R](f: js.Function2[T1, T2, R]) extends AbstractFunction2[T1, T2, R] { + override def apply(arg1: T1, arg2: T2): R = f(arg1, arg2) +} + +@inline +final class AnonFunction3[-T1, -T2, -T3, +R](f: js.Function3[T1, T2, T3, R]) extends AbstractFunction3[T1, T2, T3, R] { + override def apply(arg1: T1, arg2: T2, arg3: T3): R = f(arg1, arg2, arg3) +} + +@inline +final class AnonFunction4[-T1, -T2, -T3, -T4, +R](f: js.Function4[T1, T2, T3, T4, R]) extends AbstractFunction4[T1, T2, T3, T4, R] { + override def apply(arg1: T1, arg2: T2, arg3: T3, arg4: T4): R = f(arg1, arg2, arg3, arg4) +} + +@inline +final class AnonFunction5[-T1, -T2, -T3, -T4, -T5, +R](f: js.Function5[T1, T2, T3, T4, T5, R]) extends AbstractFunction5[T1, T2, T3, T4, T5, R] { + override def apply(arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5): R = f(arg1, arg2, arg3, arg4, arg5) +} + +@inline +final class AnonFunction6[-T1, -T2, -T3, -T4, -T5, -T6, +R](f: js.Function6[T1, T2, T3, T4, T5, T6, R]) extends AbstractFunction6[T1, T2, T3, T4, T5, T6, R] { + override def apply(arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6): R = f(arg1, arg2, arg3, arg4, arg5, arg6) +} + +@inline +final class AnonFunction7[-T1, -T2, -T3, -T4, -T5, -T6, -T7, +R](f: js.Function7[T1, T2, T3, T4, T5, T6, T7, R]) extends AbstractFunction7[T1, T2, T3, T4, T5, T6, T7, R] { + override def apply(arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7): R = f(arg1, arg2, arg3, arg4, arg5, arg6, arg7) +} + +@inline +final class AnonFunction8[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, +R](f: js.Function8[T1, T2, T3, T4, T5, T6, T7, T8, R]) extends AbstractFunction8[T1, T2, T3, T4, T5, T6, T7, T8, R] { + override def apply(arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8): R = f(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8) +} + +@inline +final class AnonFunction9[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, +R](f: js.Function9[T1, T2, T3, T4, T5, T6, T7, T8, T9, R]) extends AbstractFunction9[T1, T2, T3, T4, T5, T6, T7, T8, T9, R] { + override def apply(arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8, arg9: T9): R = f(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) +} + +@inline +final class AnonFunction10[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, +R](f: js.Function10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, R]) extends AbstractFunction10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, R] { + override def apply(arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8, arg9: T9, arg10: T10): R = f(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10) +} + +@inline +final class AnonFunction11[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, +R](f: js.Function11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, R]) extends AbstractFunction11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, R] { + override def apply(arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8, arg9: T9, arg10: T10, arg11: T11): R = f(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11) +} + +@inline +final class AnonFunction12[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, +R](f: js.Function12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, R]) extends AbstractFunction12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, R] { + override def apply(arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8, arg9: T9, arg10: T10, arg11: T11, arg12: T12): R = f(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12) +} + +@inline +final class AnonFunction13[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, +R](f: js.Function13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, R]) extends AbstractFunction13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, R] { + override def apply(arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8, arg9: T9, arg10: T10, arg11: T11, arg12: T12, arg13: T13): R = f(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13) +} + +@inline +final class AnonFunction14[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, -T14, +R](f: js.Function14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, R]) extends AbstractFunction14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, R] { + override def apply(arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8, arg9: T9, arg10: T10, arg11: T11, arg12: T12, arg13: T13, arg14: T14): R = f(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14) +} + +@inline +final class AnonFunction15[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, -T14, -T15, +R](f: js.Function15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, R]) extends AbstractFunction15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, R] { + override def apply(arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8, arg9: T9, arg10: T10, arg11: T11, arg12: T12, arg13: T13, arg14: T14, arg15: T15): R = f(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15) +} + +@inline +final class AnonFunction16[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, -T14, -T15, -T16, +R](f: js.Function16[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, R]) extends AbstractFunction16[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, R] { + override def apply(arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8, arg9: T9, arg10: T10, arg11: T11, arg12: T12, arg13: T13, arg14: T14, arg15: T15, arg16: T16): R = f(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16) +} + +@inline +final class AnonFunction17[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, -T14, -T15, -T16, -T17, +R](f: js.Function17[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, R]) extends AbstractFunction17[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, R] { + override def apply(arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8, arg9: T9, arg10: T10, arg11: T11, arg12: T12, arg13: T13, arg14: T14, arg15: T15, arg16: T16, arg17: T17): R = f(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17) +} + +@inline +final class AnonFunction18[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, -T14, -T15, -T16, -T17, -T18, +R](f: js.Function18[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, R]) extends AbstractFunction18[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, R] { + override def apply(arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8, arg9: T9, arg10: T10, arg11: T11, arg12: T12, arg13: T13, arg14: T14, arg15: T15, arg16: T16, arg17: T17, arg18: T18): R = f(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18) +} + +@inline +final class AnonFunction19[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, -T14, -T15, -T16, -T17, -T18, -T19, +R](f: js.Function19[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, R]) extends AbstractFunction19[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, R] { + override def apply(arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8, arg9: T9, arg10: T10, arg11: T11, arg12: T12, arg13: T13, arg14: T14, arg15: T15, arg16: T16, arg17: T17, arg18: T18, arg19: T19): R = f(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18, arg19) +} + +@inline +final class AnonFunction20[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, -T14, -T15, -T16, -T17, -T18, -T19, -T20, +R](f: js.Function20[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, R]) extends AbstractFunction20[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, R] { + override def apply(arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8, arg9: T9, arg10: T10, arg11: T11, arg12: T12, arg13: T13, arg14: T14, arg15: T15, arg16: T16, arg17: T17, arg18: T18, arg19: T19, arg20: T20): R = f(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18, arg19, arg20) +} + +@inline +final class AnonFunction21[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, -T14, -T15, -T16, -T17, -T18, -T19, -T20, -T21, +R](f: js.Function21[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, R]) extends AbstractFunction21[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, R] { + override def apply(arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8, arg9: T9, arg10: T10, arg11: T11, arg12: T12, arg13: T13, arg14: T14, arg15: T15, arg16: T16, arg17: T17, arg18: T18, arg19: T19, arg20: T20, arg21: T21): R = f(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18, arg19, arg20, arg21) +} + +@inline +final class AnonFunction22[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, -T14, -T15, -T16, -T17, -T18, -T19, -T20, -T21, -T22, +R](f: js.Function22[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, R]) extends AbstractFunction22[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, R] { + override def apply(arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8, arg9: T9, arg10: T10, arg11: T11, arg12: T12, arg13: T13, arg14: T14, arg15: T15, arg16: T16, arg17: T17, arg18: T18, arg19: T19, arg20: T20, arg21: T21, arg22: T22): R = f(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18, arg19, arg20, arg21, arg22) +} diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/runtime/Bits.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/runtime/Bits.scala new file mode 100644 index 0000000..38b2c3e --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/runtime/Bits.scala @@ -0,0 +1,240 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js API ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.runtime + +import scala.scalajs.js +import js.Dynamic.global +import js.typedarray + +/** Low-level stuff. */ +object Bits { + + val areTypedArraysSupported = ( + !(!global.ArrayBuffer) && !(!global.Int32Array) && + !(!global.Float32Array) && !(!global.Float64Array)) + + private val arrayBuffer = + if (areTypedArraysSupported) new typedarray.ArrayBuffer(8) + else null + + private val int32Array = + if (areTypedArraysSupported) new typedarray.Int32Array(arrayBuffer, 0, 2) + else null + + private val float32Array = + if (areTypedArraysSupported) new typedarray.Float32Array(arrayBuffer, 0, 2) + else null + + private val float64Array = + if (areTypedArraysSupported) new typedarray.Float64Array(arrayBuffer, 0, 1) + else null + + val areTypedArraysBigEndian = { + if (areTypedArraysSupported) { + int32Array(0) = 0x01020304 + (new typedarray.Int8Array(arrayBuffer, 0, 8))(0) == 0x01 + } else { + true // as good a value as any + } + } + + private val highOffset = if (areTypedArraysBigEndian) 0 else 1 + private val lowOffset = if (areTypedArraysBigEndian) 1 else 0 + + /** Hash code of a number (excluding Longs). + * + * Because of the common encoding for integer and floating point values, + * the hashCode of Floats and Doubles must align with that of Ints for the + * common values. + * + * For other values, we use the hashCode specified by the JavaDoc for + * *Doubles*, even for values which are valid Float values. Because of the + * previous point, we cannot align completely with the Java specification, + * so there is no point trying to be a bit more aligned here. Always using + * the Double version should typically be faster on VMs without fround + * support because we avoid several fround operations. + */ + def numberHashCode(value: Double): Int = { + val iv = value.toInt + if (iv == value) iv + else doubleToLongBits(value).hashCode() + } + + def intBitsToFloat(bits: Int): Float = { + if (areTypedArraysSupported) { + int32Array(0) = bits + float32Array(0) + } else { + intBitsToFloatPolyfill(bits).toFloat + } + } + + def floatToIntBits(value: Float): Int = { + if (areTypedArraysSupported) { + float32Array(0) = value + int32Array(0) + } else { + floatToIntBitsPolyfill(value.toDouble) + } + } + + def longBitsToDouble(bits: Long): Double = { + if (areTypedArraysSupported) { + int32Array(highOffset) = (bits >>> 32).toInt + int32Array(lowOffset) = bits.toInt + float64Array(0) + } else { + longBitsToDoublePolyfill(bits) + } + } + + def doubleToLongBits(value: Double): Long = { + if (areTypedArraysSupported) { + float64Array(0) = value + ((int32Array(highOffset).toLong << 32) | + (int32Array(lowOffset).toLong & 0xffffffffL)) + } else { + doubleToLongBitsPolyfill(value) + } + } + + /* --- Polyfills for floating point bit manipulations --- + * + * Originally inspired by + * https://github.com/inexorabletash/polyfill/blob/a682f42c1092280bb01907c245979fb07219513d/typedarray.js#L150-L255 + * + * Note that if typed arrays are not supported, it is almost certain that + * fround is not supported natively, so Float operations are extremely slow. + * + * We therefore do all computations in Doubles here, which is also more + * predictable, since the results do not depend on strict floats semantics. + */ + + private def intBitsToFloatPolyfill(bits: Int): Double = { + val ebits = 8 + val fbits = 23 + val s = bits < 0 + val e = (bits >> fbits) & ((1 << ebits) - 1) + val f = bits & ((1 << fbits) - 1) + decodeIEEE754(ebits, fbits, s, e, f) + } + + private def floatToIntBitsPolyfill(value: Double): Int = { + val ebits = 8 + val fbits = 23 + val (s, e, f) = encodeIEEE754(ebits, fbits, value) + (if (s) 0x80000000 else 0) | (e << fbits) | f.toInt + } + + private def longBitsToDoublePolyfill(bits: Long): Double = { + val ebits = 11 + val fbits = 52 + val hifbits = fbits-32 + val hi = (bits >>> 32).toInt + val lo = ((bits.toInt: js.prim.Number) >>> 0).toDouble + val s = hi < 0 + val e = (hi >> hifbits) & ((1 << ebits) - 1) + val f = (hi & ((1 << hifbits) - 1)).toDouble * 0x100000000L.toDouble + lo + decodeIEEE754(ebits, fbits, s, e, f) + } + + private def doubleToLongBitsPolyfill(value: Double): Long = { + val ebits = 11 + val fbits = 52 + val hifbits = fbits-32 + val (s, e, f) = encodeIEEE754(ebits, fbits, value) + val hif = (f / 0x100000000L.toDouble).toInt + val hi = (if (s) 0x80000000 else 0) | (e << hifbits) | hif + val lo = f.toInt + (hi.toLong << 32) | (lo.toLong & 0xffffffffL) + } + + @inline private def decodeIEEE754(ebits: Int, fbits: Int, + s: Boolean, e: Int, f: Double): Double = { + + import Math.pow + + val bias = (1 << (ebits-1)) - 1 // constant + + if (e == (1 << ebits) - 1) { + // Special + if (f != 0.0) Double.NaN + else if (s) Double.NegativeInfinity + else Double.PositiveInfinity + } else if (e > 0) { + // Normalized + val x = pow(2, e-bias) * (1 + f / pow(2, fbits)) + if (s) -x else x + } else if (f != 0.0) { + // Subnormal + val x = pow(2, -(bias-1)) * (f / pow(2, fbits)) + if (s) -x else x + } else { + // Zero + if (s) -0.0 else 0.0 + } + } + + @inline private def encodeIEEE754(ebits: Int, fbits: Int, + v: Double): (Boolean, Int, Double) = { + + import Math._ + + val bias = (1 << (ebits-1)) - 1 // constant + + if (v.isNaN) { + // http://dev.w3.org/2006/webapi/WebIDL/#es-type-mapping + (false, (1 << ebits) - 1, pow(2, fbits-1)) + } else if (v.isInfinite) { + (v < 0, (1 << ebits) - 1, 0.0) + } else if (v == 0.0) { + (1 / v == Double.NegativeInfinity, 0, 0.0) + } else { + val LN2 = 0.6931471805599453 + + val s = v < 0 + val av = if (s) -v else v + + if (av >= pow(2, 1-bias)) { + val twoPowFbits = pow(2, fbits) + + var e = min(floor(log(av) / LN2).toInt, 1023) + var f = roundToEven(av / pow(2, e) * twoPowFbits) + if (f / twoPowFbits >= 2) { + e = e + 1 + f = 1 + } + if (e > bias) { + // Overflow + e = (1 << ebits) - 1 + f = 0 + } else { + // Normalized + e = e + bias + f = f - twoPowFbits + } + (s, e, f) + } else { + // Subnormal + (s, 0, roundToEven(av / pow(2, 1-bias-fbits))) + } + } + } + + @inline private[runtime] def roundToEven(n: Double): Double = { + val w = Math.floor(n) + val f = n - w + if (f < 0.5) w + else if (f > 0.5) w + 1 + else if (w % 2 != 0) w + 1 + else w + } + +} diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/runtime/BooleanReflectiveCall.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/runtime/BooleanReflectiveCall.scala new file mode 100644 index 0000000..0cd562a --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/runtime/BooleanReflectiveCall.scala @@ -0,0 +1,31 @@ +package scala.scalajs.runtime + +import java.lang.{Boolean => JBoolean} + +/** Explicit box for boolean values when doing a reflective call. + * This class and its methods are only here to properly support reflective + * calls on booleans. + */ +class BooleanReflectiveCall(value: Boolean) { + + // Methods of java.lang.Boolean + + def booleanValue(): Boolean = value + + def compareTo(that: JBoolean): Int = + new JBoolean(value).compareTo(that) + def compareTo(that: AnyRef): Int = + new JBoolean(value).compareTo(that.asInstanceOf[JBoolean]) + + // Methods of scala.Boolean + + def unary_! : Boolean = !value + def ==(x: Boolean): Boolean = value == x + def !=(x: Boolean): Boolean = value != x + def ||(x: Boolean): Boolean = value || x + def &&(x: Boolean): Boolean = value && x + def |(x: Boolean): Boolean = value | x + def &(x: Boolean): Boolean = value & x + def ^(x: Boolean): Boolean = value ^ x + +} diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/runtime/IntegerReflectiveCall.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/runtime/IntegerReflectiveCall.scala new file mode 100644 index 0000000..ddf65df --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/runtime/IntegerReflectiveCall.scala @@ -0,0 +1,87 @@ +package scala.scalajs.runtime + +import java.lang.{Double => JDouble, Integer => JInteger} + +/** Explicit box for number values when doing a reflective call that was + * identified to be a call on Int rather than on Double (based on the + * result type of the method called reflectively). + * This class and its methods are only here to properly support reflective + * calls on numbers. + */ +class IntegerReflectiveCall(value: Int) { + + // Methods of scala.Int whose result type is different than in scala.Double + + def unary_+ : scala.Int = value + def unary_- : scala.Int = -value + + def +(x: scala.Byte): scala.Int = value + x + def +(x: scala.Short): scala.Int = value + x + def +(x: scala.Char): scala.Int = value + x + def +(x: scala.Int): scala.Int = value + x + def +(x: scala.Long): scala.Long = value + x + def +(x: scala.Float): scala.Float = value + x + def +(x: scala.Double): scala.Double = value + x + + def -(x: scala.Byte): scala.Int = value - x + def -(x: scala.Short): scala.Int = value - x + def -(x: scala.Char): scala.Int = value - x + def -(x: scala.Int): scala.Int = value - x + def -(x: scala.Long): scala.Long = value - x + def -(x: scala.Float): scala.Float = value - x + def -(x: scala.Double): scala.Double = value - x + + def *(x: scala.Byte): scala.Int = value * x + def *(x: scala.Short): scala.Int = value * x + def *(x: scala.Char): scala.Int = value * x + def *(x: scala.Int): scala.Int = value * x + def *(x: scala.Long): scala.Long = value * x + def *(x: scala.Float): scala.Float = value * x + def *(x: scala.Double): scala.Double = value * x + + def /(x: scala.Byte): scala.Int = value / x + def /(x: scala.Short): scala.Int = value / x + def /(x: scala.Char): scala.Int = value / x + def /(x: scala.Int): scala.Int = value / x + def /(x: scala.Long): scala.Long = value / x + def /(x: scala.Float): scala.Float = value / x + def /(x: scala.Double): scala.Double = value / x + + def %(x: scala.Byte): scala.Int = value % x + def %(x: scala.Short): scala.Int = value % x + def %(x: scala.Char): scala.Int = value % x + def %(x: scala.Int): scala.Int = value % x + def %(x: scala.Long): scala.Long = value % x + def %(x: scala.Float): scala.Float = value % x + def %(x: scala.Double): scala.Double = value % x + + // Methods of scala.Int that are not defined on scala.Double + + def unary_~ : scala.Int = ~value + + def <<(x: scala.Int): scala.Int = value << x + def <<(x: scala.Long): scala.Int = value << x + def >>>(x: scala.Int): scala.Int = value >>> x + def >>>(x: scala.Long): scala.Int = value >>> x + def >>(x: scala.Int): scala.Int = value >> x + def >>(x: scala.Long): scala.Int = value >> x + + def |(x: scala.Byte): scala.Int = value | x + def |(x: scala.Short): scala.Int = value | x + def |(x: scala.Char): scala.Int = value | x + def |(x: scala.Int): scala.Int = value | x + def |(x: scala.Long): scala.Long = value | x + + def &(x: scala.Byte): scala.Int = value & x + def &(x: scala.Short): scala.Int = value & x + def &(x: scala.Char): scala.Int = value & x + def &(x: scala.Int): scala.Int = value & x + def &(x: scala.Long): scala.Long = value & x + + def ^(x: scala.Byte): scala.Int = value ^ x + def ^(x: scala.Short): scala.Int = value ^ x + def ^(x: scala.Char): scala.Int = value ^ x + def ^(x: scala.Int): scala.Int = value ^ x + def ^(x: scala.Long): scala.Long = value ^ x + +} diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/runtime/NumberReflectiveCall.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/runtime/NumberReflectiveCall.scala new file mode 100644 index 0000000..a237861 --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/runtime/NumberReflectiveCall.scala @@ -0,0 +1,162 @@ +package scala.scalajs.runtime + +import java.lang.{Double => JDouble, Integer => JInteger} + +/** Explicit box for number values when doing a reflective call. + * This class and its methods are only here to properly support reflective + * calls on numbers. + */ +class NumberReflectiveCall(value: Double) { + + // Methods of java.lang.Double and java.lang.Integer + + def byteValue(): Byte = value.toByte + def shortValue(): Short = value.toShort + def intValue(): Int = value.toInt + def longValue(): scala.Long = value.toLong + def floatValue(): Float = value.toFloat + def doubleValue(): Double = value + + def compareTo(that: JDouble): Int = + new JDouble(value).compareTo(that) + def compareTo(that: JInteger): Int = + new JDouble(value).compareTo(new JDouble(that.doubleValue())) + def compareTo(that: AnyRef): Int = + new JDouble(value).compareTo(that.asInstanceOf[JDouble]) + + def isNaN(): scala.Boolean = new JDouble(value).isNaN() + def isInfinite(): scala.Boolean = new JDouble(value).isInfinite() + + // Methods of scala.Double + + def toByte: scala.Byte = value.toByte + def toShort: scala.Short = value.toShort + def toChar: scala.Char = value.toChar + def toInt: scala.Int = value.toInt + def toLong: scala.Long = value.toLong + def toFloat: scala.Float = value.toFloat + def toDouble: scala.Double = value + + def unary_+ : scala.Double = value + def unary_- : scala.Double = -value + + def +(x: String): String = value + x + + def ==(x: scala.Byte): scala.Boolean = value == x + def ==(x: scala.Short): scala.Boolean = value == x + def ==(x: scala.Char): scala.Boolean = value == x + def ==(x: scala.Int): scala.Boolean = value == x + def ==(x: scala.Long): scala.Boolean = value == x + def ==(x: scala.Float): scala.Boolean = value == x + def ==(x: scala.Double): scala.Boolean = value == x + + def !=(x: scala.Byte): scala.Boolean = value != x + def !=(x: scala.Short): scala.Boolean = value != x + def !=(x: scala.Char): scala.Boolean = value != x + def !=(x: scala.Int): scala.Boolean = value != x + def !=(x: scala.Long): scala.Boolean = value != x + def !=(x: scala.Float): scala.Boolean = value != x + def !=(x: scala.Double): scala.Boolean = value != x + + def <(x: scala.Byte): scala.Boolean = value < x + def <(x: scala.Short): scala.Boolean = value < x + def <(x: scala.Char): scala.Boolean = value < x + def <(x: scala.Int): scala.Boolean = value < x + def <(x: scala.Long): scala.Boolean = value < x + def <(x: scala.Float): scala.Boolean = value < x + def <(x: scala.Double): scala.Boolean = value < x + + def <=(x: scala.Byte): scala.Boolean = value <= x + def <=(x: scala.Short): scala.Boolean = value <= x + def <=(x: scala.Char): scala.Boolean = value <= x + def <=(x: scala.Int): scala.Boolean = value <= x + def <=(x: scala.Long): scala.Boolean = value <= x + def <=(x: scala.Float): scala.Boolean = value <= x + def <=(x: scala.Double): scala.Boolean = value <= x + + def >(x: scala.Byte): scala.Boolean = value > x + def >(x: scala.Short): scala.Boolean = value > x + def >(x: scala.Char): scala.Boolean = value > x + def >(x: scala.Int): scala.Boolean = value > x + def >(x: scala.Long): scala.Boolean = value > x + def >(x: scala.Float): scala.Boolean = value > x + def >(x: scala.Double): scala.Boolean = value > x + + def >=(x: scala.Byte): scala.Boolean = value >= x + def >=(x: scala.Short): scala.Boolean = value >= x + def >=(x: scala.Char): scala.Boolean = value >= x + def >=(x: scala.Int): scala.Boolean = value >= x + def >=(x: scala.Long): scala.Boolean = value >= x + def >=(x: scala.Float): scala.Boolean = value >= x + def >=(x: scala.Double): scala.Boolean = value >= x + + def +(x: scala.Byte): scala.Double = value + x + def +(x: scala.Short): scala.Double = value + x + def +(x: scala.Char): scala.Double = value + x + def +(x: scala.Int): scala.Double = value + x + def +(x: scala.Long): scala.Double = value + x + def +(x: scala.Float): scala.Double = value + x + def +(x: scala.Double): scala.Double = value + x + + def -(x: scala.Byte): scala.Double = value - x + def -(x: scala.Short): scala.Double = value - x + def -(x: scala.Char): scala.Double = value - x + def -(x: scala.Int): scala.Double = value - x + def -(x: scala.Long): scala.Double = value - x + def -(x: scala.Float): scala.Double = value - x + def -(x: scala.Double): scala.Double = value - x + + def *(x: scala.Byte): scala.Double = value * x + def *(x: scala.Short): scala.Double = value * x + def *(x: scala.Char): scala.Double = value * x + def *(x: scala.Int): scala.Double = value * x + def *(x: scala.Long): scala.Double = value * x + def *(x: scala.Float): scala.Double = value * x + def *(x: scala.Double): scala.Double = value * x + + def /(x: scala.Byte): scala.Double = value / x + def /(x: scala.Short): scala.Double = value / x + def /(x: scala.Char): scala.Double = value / x + def /(x: scala.Int): scala.Double = value / x + def /(x: scala.Long): scala.Double = value / x + def /(x: scala.Float): scala.Double = value / x + def /(x: scala.Double): scala.Double = value / x + + def %(x: scala.Byte): scala.Double = value % x + def %(x: scala.Short): scala.Double = value % x + def %(x: scala.Char): scala.Double = value % x + def %(x: scala.Int): scala.Double = value % x + def %(x: scala.Long): scala.Double = value % x + def %(x: scala.Float): scala.Double = value % x + def %(x: scala.Double): scala.Double = value % x + + // Methods of scala.Int that are not defined on scala.Double + + def unary_~ : scala.Int = ~value.toInt + + def <<(x: scala.Int): scala.Int = value.toInt << x + def <<(x: scala.Long): scala.Int = value.toInt << x + def >>>(x: scala.Int): scala.Int = value.toInt >>> x + def >>>(x: scala.Long): scala.Int = value.toInt >>> x + def >>(x: scala.Int): scala.Int = value.toInt >> x + def >>(x: scala.Long): scala.Int = value.toInt >> x + + def |(x: scala.Byte): scala.Int = value.toInt | x + def |(x: scala.Short): scala.Int = value.toInt | x + def |(x: scala.Char): scala.Int = value.toInt | x + def |(x: scala.Int): scala.Int = value.toInt | x + def |(x: scala.Long): scala.Long = value.toInt | x + + def &(x: scala.Byte): scala.Int = value.toInt & x + def &(x: scala.Short): scala.Int = value.toInt & x + def &(x: scala.Char): scala.Int = value.toInt & x + def &(x: scala.Int): scala.Int = value.toInt & x + def &(x: scala.Long): scala.Long = value.toInt & x + + def ^(x: scala.Byte): scala.Int = value.toInt ^ x + def ^(x: scala.Short): scala.Int = value.toInt ^ x + def ^(x: scala.Char): scala.Int = value.toInt ^ x + def ^(x: scala.Int): scala.Int = value.toInt ^ x + def ^(x: scala.Long): scala.Long = value.toInt ^ x + +} diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/runtime/RuntimeLong.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/runtime/RuntimeLong.scala new file mode 100644 index 0000000..3bd6fb6 --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/runtime/RuntimeLong.scala @@ -0,0 +1,686 @@ +package scala.scalajs.runtime + +import scala.annotation.tailrec + +/** + * emulate a Java-Long using three integers. + * taken from gwt LongLib: + * com.google.gwt.lang.LongLib + * + * only used by runtime + * + * holds values l, m, h (low, middle, high) + * s.t. (x.l + ((long) x.m << 22) + ((long) x.h << 44)) is equal to + * the original value + */ +final class RuntimeLong( + val l: Int, + val m: Int, + val h: Int +) extends Number with Comparable[java.lang.Long] { x => + + import RuntimeLong._ + + /** Construct from an Int. + * This is the implementation of RuntimeLong.fromInt() in a way that does not + * require to load to module RuntimeLong. + */ + def this(value: Int) = this( + value & RuntimeLong.MASK, + (value >> RuntimeLong.BITS) & RuntimeLong.MASK, + if (value < 0) RuntimeLong.MASK_2 else 0) + + /** Creates a new RuntimeLong but masks bits as follows: + * l & MASK, m & MASK, h & MASK_2 + */ + @inline private def masked(l: Int, m: Int, h: Int) = + new RuntimeLong(l & MASK, m & MASK, h & MASK_2) + + def toByte: Byte = toInt.toByte + def toShort: Short = toInt.toShort + def toChar: Char = toInt.toChar + def toInt: Int = l | (m << BITS) + def toLong: Long = x.asInstanceOf[Long] + def toFloat: Float = toDouble.toFloat + def toDouble: Double = + if (isMinValue) -9223372036854775808.0 + else if (isNegative) -((-x).toDouble) + else l + m * TWO_PWR_22_DBL + h * TWO_PWR_44_DBL + + // java.lang.Number + override def byteValue(): Byte = toByte + override def shortValue(): Short = toShort + def intValue(): Int = toInt + def longValue(): Long = toLong + def floatValue(): Float = toFloat + def doubleValue(): Double = toDouble + + // java.lang.Comparable + overload taking scala.Long + def compareTo(that: RuntimeLong): Int = + if (this equals that) 0 else if (this > that) 1 else -1 + def compareTo(that: java.lang.Long): Int = + compareTo(that.asInstanceOf[RuntimeLong]) + + def unary_~ : RuntimeLong = masked(~x.l, ~x.m, ~x.h) + def unary_+ : RuntimeLong = x + def unary_- : RuntimeLong = { + val neg0 = (~x.l + 1) & MASK + val neg1 = (~x.m + (if (neg0 == 0) 1 else 0)) & MASK + val neg2 = (~x.h + (if (neg0 == 0 && neg1 == 0) 1 else 0)) & MASK_2 + new RuntimeLong(neg0, neg1, neg2) + } + + def +(y: String): String = x.toString + y + + def <<(n_in: Int): RuntimeLong = { + /* crop MSB. Note: This will cause (2L << 65 == 2L << 1) + * apparently this is as specified + */ + val n = n_in & 63 + + if (n < BITS) { + val remBits = BITS - n + masked(x.l << n, + (x.m << n) | (x.l >> remBits), + (x.h << n) | (x.m >> remBits)) + } else if (n < BITS01) { + val shfBits = n - BITS + val remBits = BITS01 - n + masked(0, x.l << shfBits, (x.m << shfBits) | (x.l >> remBits)) + } else { + masked(0, 0, x.l << (n - BITS01)) + } + + } + + /** + * logical right shift + */ + def >>>(n_in: Int): RuntimeLong = { + val n = n_in & 63 + if (n < BITS) { + val remBits = BITS - n + masked((x.l >> n) | (x.m << remBits), + // FIXME is this really >> and not >>>?? + (x.m >> n) | (x.h << remBits), + x.h >>> n) + } else if (n < BITS01) { + val shfBits = n - BITS + val remBits = BITS01 - n + // FIXME is this really >> and not >>>?? + masked((x.m >> shfBits) | (x.h << remBits), + x.h >>> shfBits, 0) + } else { + masked(x.h >>> (n - BITS01), 0, 0) + } + } + + /** + * arithmetic right shift + */ + def >>(n_in: Int): RuntimeLong = { + val n = n_in & 63; + + // Sign extend x.h + val negative = (x.h & SIGN_BIT_VALUE) != 0 + val xh = if (negative) x.h | ~MASK_2 else x.h + + if (n < BITS) { + val remBits = BITS - n + // FIXME IMHO the first two >> should be >>> + masked((x.l >> n) | (x.m << remBits), + (x.m >> n) | (xh << remBits), + xh >> n) + } else if (n < BITS01) { + val shfBits = n - BITS + val remBits = BITS01 - n + // FIXME IMHO the first >> should be >>> + masked((x.m >> shfBits) | (xh << remBits), + xh >> shfBits, + if (negative) MASK_2 else 0) + } else { + masked(xh >> (n - BITS01), + if (negative) MASK else 0, + if (negative) MASK_2 else 0) + } + + } + + def equals(y: RuntimeLong): Boolean = + x.l == y.l && x.m == y.m && x.h == y.h + + override def equals(that: Any): Boolean = that match { + case y: RuntimeLong => x.equals(y) + case _ => false + } + + def notEquals(that: RuntimeLong) = !equals(that) + + override def hashCode(): Int = { + (this ^ (this >>> 32)).toInt + } + + @inline + def <(y: RuntimeLong): Boolean = y > x + @inline + def <=(y: RuntimeLong): Boolean = y >= x + + def >(y: RuntimeLong): Boolean = { + if (!x.isNegative) + y.isNegative || + x.h > y.h || + x.h == y.h && x.m > y.m || + x.h == y.h && x.m == y.m && x.l > y.l + else !( + !y.isNegative || + x.h < y.h || + x.h == y.h && x.m < y.m || + x.h == y.h && x.m == y.m && x.l <= y.l + ) + } + + def >=(y: RuntimeLong): Boolean = { + if (!x.isNegative) + y.isNegative || + x.h > y.h || + x.h == y.h && x.m > y.m || + x.h == y.h && x.m == y.m && x.l >= y.l + else !( + !y.isNegative || + x.h < y.h || + x.h == y.h && x.m < y.m || + x.h == y.h && x.m == y.m && x.l < y.l + ) + } + + def |(y: RuntimeLong): RuntimeLong = + new RuntimeLong(x.l | y.l, x.m | y.m, x.h | y.h) + def &(y: RuntimeLong): RuntimeLong = + new RuntimeLong(x.l & y.l, x.m & y.m, x.h & y.h) + def ^(y: RuntimeLong): RuntimeLong = + new RuntimeLong(x.l ^ y.l, x.m ^ y.m, x.h ^ y.h) + + def +(y: RuntimeLong): RuntimeLong = { + val sum0 = x.l + y.l + val sum1 = x.m + y.m + (sum0 >> BITS) + val sum2 = x.h + y.h + (sum1 >> BITS) + masked(sum0, sum1, sum2) + } + + /** + * subtraction + * note: gwt implements this individually + */ + def -(y: RuntimeLong): RuntimeLong = x + (-y) + + // This assumes that BITS == 22 + def *(y: RuntimeLong): RuntimeLong = { + + /** divides v in 13bit chunks */ + @inline def chunk13(v: RuntimeLong) = ( + v.l & 0x1fff, + (v.l >> 13) | ((v.m & 0xf) << 9), + (v.m >> 4) & 0x1fff, + (v.m >> 17) | ((v.h & 0xff) << 5), + (v.h & 0xfff00) >> 8 + ) + + val (a0, a1, a2, a3, a4) = chunk13(x) + val (b0, b1, b2, b3, b4) = chunk13(y) + + // Compute partial products + // Optimization: if b is small, avoid multiplying by parts that are 0 + var p0 = a0 * b0; // << 0 + var p1 = a1 * b0; // << 13 + var p2 = a2 * b0; // << 26 + var p3 = a3 * b0; // << 39 + var p4 = a4 * b0; // << 52 + + if (b1 != 0) { + p1 += a0 * b1; + p2 += a1 * b1; + p3 += a2 * b1; + p4 += a3 * b1; + } + if (b2 != 0) { + p2 += a0 * b2; + p3 += a1 * b2; + p4 += a2 * b2; + } + if (b3 != 0) { + p3 += a0 * b3; + p4 += a1 * b3; + } + if (b4 != 0) { + p4 += a0 * b4; + } + + // Accumulate into 22-bit chunks: + // .........................................c10|...................c00| + // |....................|..................xxxx|xxxxxxxxxxxxxxxxxxxxxx| p0 + // |....................|......................|......................| + // |....................|...................c11|......c01.............| + // |....................|....xxxxxxxxxxxxxxxxxx|xxxxxxxxx.............| p1 + // |....................|......................|......................| + // |.................c22|...............c12....|......................| + // |..........xxxxxxxxxx|xxxxxxxxxxxxxxxxxx....|......................| p2 + // |....................|......................|......................| + // |.................c23|..c13.................|......................| + // |xxxxxxxxxxxxxxxxxxxx|xxxxx.................|......................| p3 + // |....................|......................|......................| + // |.........c24........|......................|......................| + // |xxxxxxxxxxxx........|......................|......................| p4 + + val c00 = p0 & 0x3fffff; + val c01 = (p1 & 0x1ff) << 13; + val c0 = c00 + c01; + + val c10 = p0 >> 22; + val c11 = p1 >> 9; + val c12 = (p2 & 0x3ffff) << 4; + val c13 = (p3 & 0x1f) << 17; + val c1 = c10 + c11 + c12 + c13; + + val c22 = p2 >> 18; + val c23 = p3 >> 5; + val c24 = (p4 & 0xfff) << 8; + val c2 = c22 + c23 + c24; + + // Propagate high bits from c0 -> c1, c1 -> c2 + val c1n = c1 + (c0 >> BITS) + + masked(c0, c1n, c2 + (c1n >> BITS)) + } + + def /(y: RuntimeLong): RuntimeLong = (x divMod y)(0) + def %(y: RuntimeLong): RuntimeLong = (x divMod y)(1) + + //override def getClass(): Class[Long] = null + + def toBinaryString: String = { + val zeros = "0000000000000000000000" // 22 zeros + @inline def padBinary22(i: Int) = { + val s = Integer.toBinaryString(i) + zeros.substring(s.length) + s + } + + if (h != 0) Integer.toBinaryString(h) + padBinary22(m) + padBinary22(l) + else if (m != 0) Integer.toBinaryString(m) + padBinary22(l) + else Integer.toBinaryString(l) + } + + def toHexString: String = { + val zeros = "000000" // 6 zeros + @inline def padHex(i: Int, len: Int) = { + val s = Integer.toHexString(i) + zeros.substring(s.length + (6-len)) + s + } + + val mp = m >> 2 + val lp = l | ((m & 0x3) << BITS) + + if (h != 0) Integer.toHexString(h) + padHex(mp, 5) + padHex(lp, 6) + else if (mp != 0) Integer.toHexString(mp) + padHex(lp, 6) + else Integer.toHexString(lp) + } + + def toOctalString: String = { + val zeros = "0000000" // 7 zeros + @inline def padOctal7(i: Int) = { + val s = Integer.toOctalString(i) + zeros.substring(s.length) + s + } + + val lp = l & (MASK >> 1) + val mp = ((m & (MASK >> 2)) << 1) | (l >> (BITS - 1)) + val hp = (h << 2) | (m >> (BITS - 2)) + + if (hp != 0) Integer.toOctalString(hp) + padOctal7(mp) + padOctal7(lp) + else if (mp != 0) Integer.toOctalString(mp) + padOctal7(lp) + else Integer.toOctalString(lp) + } + + // Any API // + + override def toString: String = { + if (isZero) "0" + // Check for MinValue, because its not negatable + else if (isMinValue) "-9223372036854775808" + else if (isNegative) "-" + (-x).toString + else { + val tenPow9 = TenPow9 // local copy to access CachedConstants only once + + @tailrec + @inline + def toString0(v: RuntimeLong, acc: String): String = + if (v.isZero) acc + else { + val quotRem = v.divMod(tenPow9) + val quot = quotRem(0) + val rem = quotRem(1) + + val digits = rem.toInt.toString + val zeroPrefix = + if (quot.isZero) "" + else "000000000".substring(digits.length) // (9 - digits.length) zeros + + toString0(quot, zeroPrefix + digits + acc) + } + + toString0(x, "") + } + } + + def bitCount: Int = + Integer.bitCount(l) + Integer.bitCount(m) + Integer.bitCount(h) + + // helpers // + + @inline private def isZero = l == 0 && m == 0 && h == 0 + @inline private def isMinValue = x.equals(MinValue) + @inline private def isNegative = (h & SIGN_BIT_VALUE) != 0 + @inline private def abs = if (isNegative) -x else x + + def signum: RuntimeLong = + if (isNegative) MinusOne else if (isZero) Zero else One + + def numberOfLeadingZeros: Int = + if (h != 0) Integer.numberOfLeadingZeros(h) - (32 - BITS2) + else if (m != 0) Integer.numberOfLeadingZeros(m) - (32 - BITS) + (64 - BITS01) + else Integer.numberOfLeadingZeros(l) - (32 - BITS) + (64 - BITS) + + def numberOfTrailingZeros: Int = + if (l != 0) Integer.numberOfTrailingZeros(l) + else if (m != 0) Integer.numberOfTrailingZeros(m) + BITS + else Integer.numberOfTrailingZeros(h) + BITS01 + + /** return log_2(x) if power of 2 or -1 otherwise */ + private def powerOfTwo = + if (h == 0 && m == 0 && l != 0 && (l & (l - 1)) == 0) + Integer.numberOfTrailingZeros(l) + else if (h == 0 && m != 0 && l == 0 && (m & (m - 1)) == 0) + Integer.numberOfTrailingZeros(m) + BITS + else if (h != 0 && m == 0 && l == 0 && (h & (h - 1)) == 0) + Integer.numberOfTrailingZeros(h) + BITS01 + else + -1 + + private def setBit(bit: Int) = + if (bit < BITS) + new RuntimeLong(l | (1 << bit), m, h) + else if (bit < BITS01) + new RuntimeLong(l, m | (1 << (bit - BITS)), h) + else + new RuntimeLong(l, m, h | (1 << (bit - BITS01))) + + private def divMod(y: RuntimeLong): scala.scalajs.js.Array[RuntimeLong] = { + import scala.scalajs.js + if (y.isZero) throw new ArithmeticException("/ by zero") + else if (x.isZero) js.Array(Zero, Zero) + else if (y.isMinValue) { + // MinValue / MinValue == 1, rem = 0 + // otherwise == 0, rem x + if (x.isMinValue) js.Array(One, Zero) + else js.Array(Zero, x) + } else { + val xNegative = x.isNegative + val yNegative = y.isNegative + + val xMinValue = x.isMinValue + + val pow = y.powerOfTwo + if (pow >= 0) { + if (xMinValue) { + val z = x >> pow + js.Array(if (yNegative) -z else z, Zero) + } else { + // x is not min value, so we can calculate absX + val absX = x.abs + val absZ = absX >> pow + val z = if (xNegative ^ yNegative) -absZ else absZ + val remAbs = absX.maskRight(pow) + val rem = if (xNegative) -remAbs else remAbs + js.Array(z, rem) + } + } else { + val absY = y.abs + + val newX = { + if (xMinValue) + MaxValue + else { + val absX = x.abs + if (absX < absY) + return js.Array(Zero, x) // <-- ugly but fast + else + absX + } + } + divModHelper(newX, absY, xNegative, yNegative, xMinValue) + } + } + } + + @inline + private def maskRight(bits: Int) = { + if (bits <= BITS) + new RuntimeLong(l & ((1 << bits) - 1), 0, 0) + else if (bits <= BITS01) + new RuntimeLong(l, m & ((1 << (bits - BITS)) - 1), 0) + else + new RuntimeLong(l, m, h & ((1 << (bits - BITS01)) - 1)) + } + + /** + * performs division in "normal cases" + * @param x absolute value of the numerator + * @param y absolute value of the denominator + * @param xNegative whether numerator was negative + * @param yNegative whether denominator was negative + * @param xMinValue whether numerator was Long.minValue + */ + @inline + private def divModHelper(x: RuntimeLong, y: RuntimeLong, + xNegative: Boolean, yNegative: Boolean, + xMinValue: Boolean): scala.scalajs.js.Array[RuntimeLong] = { + import scala.scalajs.js + + @inline + @tailrec + def divide0(shift: Int, yShift: RuntimeLong, curX: RuntimeLong, + quot: RuntimeLong): (RuntimeLong, RuntimeLong) = + if (shift < 0 || curX.isZero) (quot, curX) else { + val newX = curX - yShift + if (!newX.isNegative) + divide0(shift-1, yShift >> 1, newX, quot.setBit(shift)) + else + divide0(shift-1, yShift >> 1, curX, quot) + } + + val shift = y.numberOfLeadingZeros - x.numberOfLeadingZeros + val yShift = y << shift + + val (absQuot, absRem) = divide0(shift, yShift, x, Zero) + + val quot = if (xNegative ^ yNegative) -absQuot else absQuot + val rem = + if (xNegative && xMinValue) -absRem - One + else if (xNegative) -absRem + else absRem + + js.Array(quot, rem) + } + + /* + * Methods of scala.Long + * The following methods are only here to properly support reflective calls + * on longs. YOU MUST NOT USE THESE METHODS. + */ + + //protected def unary_~ : Long = ~toLong // already defined + //protected def unary_+ : Long = toLong // already defined + //protected def unary_- : Long = -toLong // already defined + + //protected def <<(y: Int): Long = toLong << y // already defined + protected def <<(y: Long): Long = toLong << y + //protected def >>>(y: Int): Long = toLong >>> y // already defined + protected def >>>(y: Long): Long = toLong >>> y + //protected def >>(y: Int): Long = toLong >> y // already defined + protected def >>(y: Long): Long = toLong >> y + + protected def ==(y: Byte): Boolean = toLong == y + protected def ==(y: Short): Boolean = toLong == y + protected def ==(y: Char): Boolean = toLong == y + protected def ==(y: Int): Boolean = toLong == y + protected def ==(y: Long): Boolean = toLong == y + protected def ==(y: Float): Boolean = toLong == y + protected def ==(y: Double): Boolean = toLong == y + + protected def !=(y: Byte): Boolean = toLong != y + protected def !=(y: Short): Boolean = toLong != y + protected def !=(y: Char): Boolean = toLong != y + protected def !=(y: Int): Boolean = toLong != y + protected def !=(y: Long): Boolean = toLong != y + protected def !=(y: Float): Boolean = toLong != y + protected def !=(y: Double): Boolean = toLong != y + + protected def <(y: Byte): Boolean = toLong < y + protected def <(y: Short): Boolean = toLong < y + protected def <(y: Char): Boolean = toLong < y + protected def <(y: Int): Boolean = toLong < y + protected def <(y: Long): Boolean = toLong < y + protected def <(y: Float): Boolean = toLong < y + protected def <(y: Double): Boolean = toLong < y + + protected def <=(y: Byte): Boolean = toLong <= y + protected def <=(y: Short): Boolean = toLong <= y + protected def <=(y: Char): Boolean = toLong <= y + protected def <=(y: Int): Boolean = toLong <= y + protected def <=(y: Long): Boolean = toLong <= y + protected def <=(y: Float): Boolean = toLong <= y + protected def <=(y: Double): Boolean = toLong <= y + + protected def >(y: Byte): Boolean = toLong > y + protected def >(y: Short): Boolean = toLong > y + protected def >(y: Char): Boolean = toLong > y + protected def >(y: Int): Boolean = toLong > y + protected def >(y: Long): Boolean = toLong > y + protected def >(y: Float): Boolean = toLong > y + protected def >(y: Double): Boolean = toLong > y + + protected def >=(y: Byte): Boolean = toLong >= y + protected def >=(y: Short): Boolean = toLong >= y + protected def >=(y: Char): Boolean = toLong >= y + protected def >=(y: Int): Boolean = toLong >= y + protected def >=(y: Long): Boolean = toLong >= y + protected def >=(y: Float): Boolean = toLong >= y + protected def >=(y: Double): Boolean = toLong >= y + + protected def |(y: Byte): Long = toLong | y + protected def |(y: Short): Long = toLong | y + protected def |(y: Char): Long = toLong | y + protected def |(y: Int): Long = toLong | y + protected def |(y: Long): Long = toLong | y + + protected def &(y: Byte): Long = toLong & y + protected def &(y: Short): Long = toLong & y + protected def &(y: Char): Long = toLong & y + protected def &(y: Int): Long = toLong & y + protected def &(y: Long): Long = toLong & y + + protected def ^(y: Byte): Long = toLong ^ y + protected def ^(y: Short): Long = toLong ^ y + protected def ^(y: Char): Long = toLong ^ y + protected def ^(y: Int): Long = toLong ^ y + protected def ^(y: Long): Long = toLong ^ y + + protected def +(y: Byte): Long = toLong + y + protected def +(y: Short): Long = toLong + y + protected def +(y: Char): Long = toLong + y + protected def +(y: Int): Long = toLong + y + protected def +(y: Long): Long = toLong + y + protected def +(y: Float): Float = toLong + y + protected def +(y: Double): Double = toLong + y + + protected def -(y: Byte): Long = toLong - y + protected def -(y: Short): Long = toLong - y + protected def -(y: Char): Long = toLong - y + protected def -(y: Int): Long = toLong - y + protected def -(y: Long): Long = toLong - y + protected def -(y: Float): Float = toLong - y + protected def -(y: Double): Double = toLong - y + + protected def *(y: Byte): Long = toLong - y + protected def *(y: Short): Long = toLong - y + protected def *(y: Char): Long = toLong - y + protected def *(y: Int): Long = toLong - y + protected def *(y: Long): Long = toLong - y + protected def *(y: Float): Float = toLong - y + protected def *(y: Double): Double = toLong - y + + protected def /(y: Byte): Long = toLong / y + protected def /(y: Short): Long = toLong / y + protected def /(y: Char): Long = toLong / y + protected def /(y: Int): Long = toLong / y + protected def /(y: Long): Long = toLong / y + protected def /(y: Float): Float = toLong / y + protected def /(y: Double): Double = toLong / y + + protected def %(y: Byte): Long = toLong % y + protected def %(y: Short): Long = toLong % y + protected def %(y: Char): Long = toLong % y + protected def %(y: Int): Long = toLong % y + protected def %(y: Long): Long = toLong % y + protected def %(y: Float): Float = toLong % y + protected def %(y: Double): Double = toLong % y + +} + +object RuntimeLong { + + /** number of relevant bits in each Long.l and Long.m */ + private final val BITS = 22 + /** number of relevant bits in Long.l and Long.m together */ + private final val BITS01 = 2 * BITS + /** number of relevant bits in Long.h */ + private final val BITS2 = 64 - BITS01 + /** bitmask for Long.l and Long.m */ + private final val MASK = (1 << BITS) - 1 + /** bitmask for Long.h */ + private final val MASK_2 = (1 << BITS2) - 1 + + private[runtime] final val SIGN_BIT = BITS2 - 1 + private[runtime] final val SIGN_BIT_VALUE = 1 << SIGN_BIT + private[runtime] final val TWO_PWR_15_DBL = 0x8000 * 1.0 + private[runtime] final val TWO_PWR_16_DBL = 0x10000 * 1.0 + private[runtime] final val TWO_PWR_22_DBL = 0x400000 * 1.0 + private[runtime] final val TWO_PWR_31_DBL = TWO_PWR_16_DBL * TWO_PWR_15_DBL + private[runtime] final val TWO_PWR_32_DBL = TWO_PWR_16_DBL * TWO_PWR_16_DBL + private[runtime] final val TWO_PWR_44_DBL = TWO_PWR_22_DBL * TWO_PWR_22_DBL + private[runtime] final val TWO_PWR_63_DBL = TWO_PWR_32_DBL * TWO_PWR_31_DBL + + // Cache the instances for some "literals" used in this implementation + val Zero = new RuntimeLong( 0, 0, 0) // 0L + val One = new RuntimeLong( 1, 0, 0) // 1L + val MinusOne = new RuntimeLong( MASK, MASK, MASK_2) // -1L + val MinValue = new RuntimeLong( 0, 0, 524288) // Long.MinValue + val MaxValue = new RuntimeLong(4194303, 4194303, 524287) // Long.MaxValue + val TenPow9 = new RuntimeLong(1755648, 238, 0) // 1000000000L with 9 zeros + + def fromDouble(value: Double): RuntimeLong = { + if (java.lang.Double.isNaN(value)) Zero + else if (value < -TWO_PWR_63_DBL) MinValue + else if (value >= TWO_PWR_63_DBL) MaxValue + else if (value < 0) -fromDouble(-value) + else { + var acc = value + val a2 = if (acc >= TWO_PWR_44_DBL) (acc / TWO_PWR_44_DBL).toInt else 0 + acc -= a2 * TWO_PWR_44_DBL + val a1 = if (acc >= TWO_PWR_22_DBL) (acc / TWO_PWR_22_DBL).toInt else 0 + acc -= a1 * TWO_PWR_22_DBL + val a0 = acc.toInt + new RuntimeLong(a0, a1, a2) + } + } + +} diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/runtime/RuntimeString.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/runtime/RuntimeString.scala new file mode 100644 index 0000000..f65b1b5 --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/runtime/RuntimeString.scala @@ -0,0 +1,338 @@ +package scala.scalajs.runtime + +import scala.scalajs.js +import scala.scalajs.js.prim.{String => jsString} + +import java.nio.ByteBuffer +import java.nio.charset.Charset +import java.util.regex._ + +/** Implementation for methods on java.lang.String. + * + * Strings are represented at runtime by JavaScript strings, but they have + * a lot of methods. The compiler forwards methods on java.lang.String to the + * methods in the object, passing `this` as the first argument, that we + * consistently call `thiz` in this object. + */ +private[runtime] object RuntimeString { + + @inline + def charAt(thiz: String, index: Int): Char = + (thiz: jsString).charCodeAt(index).asInstanceOf[Int].toChar + + def codePointAt(thiz: String, index: Int): Int = { + val high = thiz.charAt(index) + if (index+1 < thiz.length) { + val low = thiz.charAt(index+1) + if (Character.isSurrogatePair(high, low)) + Character.toCodePoint(high, low) + else + high.toInt + } else { + high.toInt + } + } + + def hashCode(thiz: String): Int = { + var res = 0 + var mul = 1 // holds pow(31, length-i-1) + var i = thiz.length-1 + while (i >= 0) { + res += thiz.charAt(i) * mul + mul *= 31 + i -= 1 + } + res + } + + @inline + def compareTo(thiz: String, anotherString: String): Int = { + if (thiz.equals(anotherString)) 0 + else if ((thiz: jsString) < (anotherString: jsString)) -1 + else 1 + } + + def compareToIgnoreCase(thiz: String, str: String): Int = + thiz.toLowerCase().compareTo(str.toLowerCase()) + + @inline + def equalsIgnoreCase(thiz: String, that: String): Boolean = + thiz.toLowerCase() == (if (that == null) null else that.toLowerCase()) + + @inline + def concat(thiz: String, s: String): String = + checkNull(thiz) + s + + @inline + def contains(thiz: String, s: CharSequence): Boolean = + thiz.indexOf(s.toString) != -1 + + def endsWith(thiz: String, suffix: String): Boolean = + ((thiz: jsString).substring(thiz.length - suffix.length): String) == suffix + + def getBytes(thiz: String): Array[Byte] = + thiz.getBytes(Charset.defaultCharset) + + def getBytes(thiz: String, charsetName: String): Array[Byte] = + thiz.getBytes(Charset.forName(charsetName)) + + def getBytes(thiz: String, charset: Charset): Array[Byte] = + charset.encode(thiz).array() + + def getChars(thiz: String, srcBegin: Int, srcEnd: Int, + dst: Array[Char], dstBegin: Int): Unit = { + if (srcEnd > thiz.length || // first test uses thiz + srcBegin < 0 || + srcEnd < 0 || + srcBegin > srcEnd) { + throw new StringIndexOutOfBoundsException("Index out of Bound") + } + + val offset = dstBegin - srcBegin + var i = srcBegin + while (i < srcEnd) { + dst(i+offset) = thiz.charAt(i) + i += 1 + } + } + + def indexOf(thiz: String, ch: Int): Int = + thiz.indexOf(fromCodePoint(ch)) + + def indexOf(thiz: String, ch: Int, fromIndex: Int): Int = + thiz.indexOf(fromCodePoint(ch), fromIndex) + + @inline + def indexOf(thiz: String, str: String): Int = + (thiz: jsString).indexOf(str).asInstanceOf[Int] + + @inline + def indexOf(thiz: String, str: String, fromIndex: Int): Int = + (thiz: jsString).indexOf(str, fromIndex).asInstanceOf[Int] + + /* Just returning this string is a valid implementation for `intern` in + * JavaScript, since strings are primitive values. Therefore, value equality + * and reference equality is the same. + */ + @inline + def intern(thiz: String): String = + checkNull(thiz) + + @inline + def isEmpty(thiz: String): Boolean = + checkNull(thiz) == "" + + def lastIndexOf(thiz: String, ch: Int): Int = + thiz.lastIndexOf(fromCodePoint(ch)) + + def lastIndexOf(thiz: String, ch: Int, fromIndex: Int): Int = + thiz.lastIndexOf(fromCodePoint(ch), fromIndex) + + @inline + def lastIndexOf(thiz: String, str: String): Int = + (thiz: jsString).lastIndexOf(str).asInstanceOf[Int] + + @inline + def lastIndexOf(thiz: String, str: String, fromIndex: Int): Int = + (thiz: jsString).lastIndexOf(str, fromIndex).asInstanceOf[Int] + + @inline + def length(thiz: String): Int = + (thiz: jsString).length.asInstanceOf[Int] + + @inline + def matches(thiz: String, regex: String): Boolean = { + checkNull(thiz) + Pattern.matches(regex, thiz) + } + + @inline + def replace(thiz: String, oldChar: Char, newChar: Char): String = + (thiz: String).replace(oldChar.toString, newChar.toString) + + @inline + def replace(thiz: String, target: CharSequence, replacement: CharSequence): String = + (thiz: jsString).split(target.toString).join(replacement.toString) + + def replaceAll(thiz: String, regex: String, replacement: String): String = { + checkNull(thiz) + Pattern.compile(regex).matcher(thiz).replaceAll(replacement) + } + + def replaceFirst(thiz: String, regex: String, replacement: String): String = { + checkNull(thiz) + Pattern.compile(regex).matcher(thiz).replaceFirst(replacement) + } + + @inline + def split(thiz: String, regex: String): Array[String] = + thiz.split(regex, 0) + + def split(thiz: String, regex: String, limit: Int): Array[String] = { + checkNull(thiz) + Pattern.compile(regex).split(thiz, limit) + } + + @inline + def startsWith(thiz: String, prefix: String): Boolean = + thiz.startsWith(prefix, 0) + + @inline + def startsWith(thiz: String, prefix: String, toffset: Int): Boolean = + ((thiz: jsString).substring(toffset, prefix.length): String) == prefix + + @inline + def subSequence(thiz: String, beginIndex: Int, endIndex: Int): CharSequence = + thiz.substring(beginIndex, endIndex) + + @inline + def substring(thiz: String, beginIndex: Int): String = + (thiz: jsString).substring(beginIndex) + + @inline + def substring(thiz: String, beginIndex: Int, endIndex: Int): String = + (thiz: jsString).substring(beginIndex, endIndex) + + def toCharArray(thiz: String): Array[Char] = { + val length = thiz.length + val result = new Array[Char](length) + var i = 0 + while (i < length) { + result(i) = thiz.charAt(i) + i += 1 + } + result + } + + @inline + def toLowerCase(thiz: String): String = + (thiz: jsString).toLowerCase() + + @inline + def toUpperCase(thiz: String): String = + (thiz: jsString).toUpperCase() + + @inline + def trim(thiz: String): String = + (thiz: jsString).trim() + + // Constructors + + def newString(): String = "" + + def newString(value: Array[Char]): String = + newString(value, 0, value.length) + + def newString(value: Array[Char], offset: Int, count: Int): String = { + val end = offset + count + if (offset < 0 || end < offset || end > value.length) + throw new StringIndexOutOfBoundsException + + val charCodes = new js.Array[Int] + var i = offset + while (i != end) { + charCodes += value(i).toInt + i += 1 + } + js.String.fromCharCode(charCodes: _*) + } + + def newString(bytes: Array[Byte]): String = + newString(bytes, Charset.defaultCharset) + + def newString(bytes: Array[Byte], charsetName: String): String = + newString(bytes, Charset.forName(charsetName)) + + def newString(bytes: Array[Byte], charset: Charset): String = + charset.decode(ByteBuffer.wrap(bytes)).toString() + + def newString(bytes: Array[Byte], offset: Int, length: Int): String = + newString(bytes, offset, length, Charset.defaultCharset) + + def newString(bytes: Array[Byte], offset: Int, length: Int, + charsetName: String): String = + newString(bytes, offset, length, Charset.forName(charsetName)) + + def newString(bytes: Array[Byte], offset: Int, length: Int, + charset: Charset): String = + charset.decode(ByteBuffer.wrap(bytes, offset, length)).toString() + + def newString(codePoints: Array[Int], offset: Int, count: Int): String = { + val end = offset + count + if (offset < 0 || end < offset || end > codePoints.length) + throw new StringIndexOutOfBoundsException + + val charCodes = new js.Array[Int] + var i = offset + while (i != end) { + val cp = codePoints(i) + if (cp < 0 || cp > Character.MAX_CODE_POINT) + throw new IllegalArgumentException + if (cp <= Character.MAX_VALUE) { + charCodes += cp + } else { + val offsetCp = cp - 0x10000 + charCodes += (offsetCp >> 10) | 0xd800 + charCodes += (offsetCp & 0x3ff) | 0xdc00 + } + i += 1 + } + js.String.fromCharCode(charCodes: _*) + } + + def newString(original: String): String = + checkNull(original) + + def newString(buffer: java.lang.StringBuffer): String = + buffer.toString + + def newString(builder: java.lang.StringBuilder): String = + builder.toString + + // Static methods (aka methods on the companion object) + + def valueOf(value: Boolean): String = value.toString() + def valueOf(value: Char): String = value.toString() + def valueOf(value: Byte): String = value.toString() + def valueOf(value: Short): String = value.toString() + def valueOf(value: Int): String = value.toString() + def valueOf(value: Long): String = value.toString() + def valueOf(value: Float): String = value.toString() + def valueOf(value: Double): String = value.toString() + + def valueOf(value: Object): String = + if (value eq null) "null" else value.toString() + + def valueOf(data: Array[Char]): String = + valueOf(data, 0, data.length) + + def valueOf(data: Array[Char], offset: Int, count: Int): String = + newString(data, offset, count) + + def format(format: String, args: Array[AnyRef]): String = { + val frm = new java.util.Formatter() + val res = frm.format(format, args: _*).toString() + frm.close() + res + } + + // Helpers + + @inline + private def checkNull(s: String): s.type = + if (s == null) throw new NullPointerException() + else s + + private def fromCodePoint(codePoint: Int): String = { + if ((codePoint & ~Character.MAX_VALUE) == 0) + js.String.fromCharCode(codePoint) + else if (codePoint < 0 || codePoint > Character.MAX_CODE_POINT) + throw new IllegalArgumentException + else { + val offsetCp = codePoint - 0x10000 + js.String.fromCharCode( + (offsetCp >> 10) | 0xd800, (offsetCp & 0x3ff) | 0xdc00) + } + } + +} diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/runtime/StackTrace.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/runtime/StackTrace.scala new file mode 100644 index 0000000..a9e2c00 --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/runtime/StackTrace.scala @@ -0,0 +1,507 @@ +package scala.scalajs.runtime + +import scala.annotation.tailrec + +import scala.scalajs.js +import scala.scalajs.js.prim.{String => jsString} + +/** Conversions of JavaScript stack traces to Java stack traces. + */ +object StackTrace { + + /* !!! Note that in this unit, we go to great lengths *not* to use anything + * from the Scala collections library. + * + * This minimizes the risk of runtime errors during the process of decoding + * errors, which would be very bad if it happened. + */ + + /** Captures browser-specific state recording the current stack trace. + * The state is stored as a magic field of the throwable, and will be used + * by `extract()` to create an Array[StackTraceElement]. + */ + def captureState(throwable: Throwable): Unit = { + captureState(throwable, createException()) + } + + /** Creates a JS Error with the current stack trace state. */ + private def createException(): Any = { + try { + this.asInstanceOf[js.Dynamic].undef() // it does not exist, that's the point + } catch { + case js.JavaScriptException(e) => e + } + } + + /** Captures browser-specific state recording the stack trace of a JS error. + * The state is stored as a magic field of the throwable, and will be used + * by `extract()` to create an Array[StackTraceElement]. + */ + def captureState(throwable: Throwable, e: Any): Unit = { + throwable.asInstanceOf[js.Dynamic].stackdata = e.asInstanceOf[js.Any] + } + + /** Tests whether we're running under Rhino. */ + private lazy val isRhino: Boolean = { + try { + js.Dynamic.global.Packages.org.mozilla.javascript.JavaScriptException + true + } catch { + case js.JavaScriptException(_) => false + } + } + + /** Extracts a throwable's stack trace from captured browser-specific state. + * If no stack trace state has been recorded, or if the state cannot be + * analyzed in meaningful way (because we don't know the browser), an + * empty array is returned. + */ + def extract(throwable: Throwable): Array[StackTraceElement] = + extract(throwable.asInstanceOf[js.Dynamic].stackdata) + + /** Extracts a stack trace from captured browser-specific stackdata. + * If no stack trace state has been recorded, or if the state cannot be + * analyzed in meaningful way (because we don't know the browser), an + * empty array is returned. + */ + def extract(stackdata: js.Dynamic): Array[StackTraceElement] = { + val lines = normalizeStackTraceLines(stackdata) + normalizedLinesToStackTrace(lines) + } + + /* Converts an array of frame entries in normalized form to a stack trace. + * Each line must have either the format + * <functionName>@<fileName>:<lineNumber>:<columnNumber> + * or + * <functionName>@<fileName>:<lineNumber> + * For some reason, on some browsers, we sometimes have empty lines too. + * In the rest of the function, we convert the non-empty lines into + * StackTraceElements. + */ + private def normalizedLinesToStackTrace( + lines: js.Array[jsString]): Array[StackTraceElement] = { + val NormalizedFrameLine = """^([^\@]*)\@(.*):([0-9]+)$""".re + val NormalizedFrameLineWithColumn = """^([^\@]*)\@(.*):([0-9]+):([0-9]+)$""".re + + val trace = new js.Array[JSStackTraceElem] + var i = 0 + while (i < lines.length) { + val line = lines(i) + if (!line.isEmpty) { + val mtch1 = NormalizedFrameLineWithColumn.exec(line) + if (mtch1 ne null) { + val (className, methodName) = extractClassMethod(mtch1(1).get) + trace.push(JSStackTraceElem(className, methodName, mtch1(2).get, + mtch1(3).get.toInt, mtch1(4).get.toInt)) + } else { + val mtch2 = NormalizedFrameLine.exec(line) + if (mtch2 ne null) { + val (className, methodName) = extractClassMethod(mtch2(1).get) + trace.push(JSStackTraceElem(className, + methodName, mtch2(2).get, mtch2(3).get.toInt)) + } else { + // just in case + trace.push(JSStackTraceElem("<jscode>", line, null, -1)) + } + } + } + i += 1 + } + + // Map stack trace through environment (if supported) + val envInfo = environmentInfo + val hasMapper = envInfo != js.undefined && envInfo != null && + js.typeOf(envInfo.sourceMapper) == "function" + + val mappedTrace = + if (hasMapper) + envInfo.sourceMapper(trace).asInstanceOf[js.Array[JSStackTraceElem]] + else + trace + + // Convert JS objects to java.lang.StackTraceElements + // While loop due to space concerns + val result = new Array[StackTraceElement](mappedTrace.length) + + i = 0 + while (i < mappedTrace.length) { + val jsSte = mappedTrace(i) + val ste = new StackTraceElement(jsSte.declaringClass, jsSte.methodName, + jsSte.fileName, jsSte.lineNumber) + + jsSte.columnNumber foreach { cn => + // Store column in magic field + ste.asInstanceOf[js.Dynamic].columnNumber = cn + } + + result(i) = ste + i += 1 + } + + result + } + + /** Tries and extract the class name and method from the JS function name. + * The recognized patterns are + * ScalaJS.c.<encoded class name>.prototype.<encoded method name> + * ScalaJS.c.<encoded class name>.<encoded method name> + * ScalaJS.i.<encoded trait impl name>__<encoded method name> + * ScalaJS.m.<encoded module name> + * When the function name is none of those, the pair + * ("<jscode>", functionName) + * is returned, which will instruct StackTraceElement.toString() to only + * display the function name. + */ + private def extractClassMethod(functionName: String): (String, String) = { + val PatC = """^ScalaJS\.c\.([^\.]+)(?:\.prototype)?\.([^\.]+)$""".re + val PatI = """^(?:Object\.)?ScalaJS\.i\.((?:_[^_]|[^_])+)__([^\.]+)$""".re + val PatM = """^(?:Object\.)?ScalaJS\.m\.([^.\.]+)$""".re + + var isModule = false + var mtch = PatC.exec(functionName) + if (mtch eq null) { + mtch = PatI.exec(functionName) + if (mtch eq null) { + mtch = PatM.exec(functionName) + isModule = true + } + } + + if (mtch ne null) { + val className = decodeClassName(mtch(1).get + (if (isModule) "$" else "")) + val methodName = if (isModule) + "<clinit>" // that's how it would be reported on the JVM + else + decodeMethodName(mtch(2).get) + (className, methodName) + } else { + ("<jscode>", functionName) + } + } + + // decodeClassName ----------------------------------------------------------- + + // !!! Duplicate logic: this code must be in sync with ir.Definitions + + private def decodeClassName(encodedName: String): String = { + val encoded = + if (encodedName.charAt(0) == '$') encodedName.substring(1) + else encodedName + val base = if (decompressedClasses.hasOwnProperty(encoded)) { + decompressedClasses(encoded) + } else { + @tailrec + def loop(i: Int): String = { + if (i < compressedPrefixes.length) { + val prefix = compressedPrefixes(i) + if (encoded.startsWith(prefix)) + decompressedPrefixes(prefix) + encoded.substring(prefix.length) + else + loop(i+1) + } else { + // no prefix matches + if (encoded.startsWith("L")) encoded.substring(1) + else encoded // just in case + } + } + loop(0) + } + base.replace("_", ".").replace("$und", "_") + } + + private val decompressedClasses: js.Dictionary[String] = { + val dict = js.Dynamic.literal( + O = "java_lang_Object", + T = "java_lang_String", + V = "scala_Unit", + Z = "scala_Boolean", + C = "scala_Char", + B = "scala_Byte", + S = "scala_Short", + I = "scala_Int", + J = "scala_Long", + F = "scala_Float", + D = "scala_Double" + ).asInstanceOf[js.Dictionary[String]] + + var index = 0 + while (index <= 22) { + if (index >= 2) + dict("T"+index) = "scala_Tuple"+index + dict("F"+index) = "scala_Function"+index + index += 1 + } + + dict + } + + private val decompressedPrefixes = js.Dynamic.literal( + sjsr_ = "scala_scalajs_runtime_", + sjs_ = "scala_scalajs_", + sci_ = "scala_collection_immutable_", + scm_ = "scala_collection_mutable_", + scg_ = "scala_collection_generic_", + sc_ = "scala_collection_", + sr_ = "scala_runtime_", + s_ = "scala_", + jl_ = "java_lang_", + ju_ = "java_util_" + ).asInstanceOf[js.Dictionary[String]] + + private val compressedPrefixes = js.Object.keys(decompressedPrefixes) + + // end of decodeClassName ---------------------------------------------------- + + private def decodeMethodName(encodedName: String): String = { + if (encodedName startsWith "init___") { + "<init>" + } else { + val methodNameLen = encodedName.indexOf("__") + if (methodNameLen < 0) encodedName + else encodedName.substring(0, methodNameLen) + } + } + + private implicit class StringRE(val s: String) extends AnyVal { + def re: js.RegExp = new js.RegExp(s) + def re(mods: String): js.RegExp = new js.RegExp(s, mods) + } + + /* --------------------------------------------------------------------------- + * Start copy-paste-translate from stacktrace.js + * + * From here on, most of the code has been copied from + * https://github.com/stacktracejs/stacktrace.js + * and translated to Scala.js almost literally, with some adaptations. + * + * Most comments -and lack thereof- have also been copied therefrom. + */ + + private def normalizeStackTraceLines(e: js.Dynamic): js.Array[jsString] = { + /* You would think that we could test once and for all which "mode" to + * adopt. But the format can actually differ for different exceptions + * on some browsers, e.g., exceptions in Chrome there may or may not have + * arguments or stack. + */ + if (!e) { + js.Array[jsString]() + } else if (isRhino) { + extractRhino(e) + } else if (!(!e.arguments) && !(!e.stack)) { + extractChrome(e) + } else if (!(!e.stack) && !(!e.sourceURL)) { + extractSafari(e) + } else if (!(!e.stack) && !(!e.number)) { + extractIE(e) + } else if (!(!e.stack) && !(!e.fileName)) { + extractFirefox(e) + } else if (!(!e.message) && !(!e.`opera#sourceloc`)) { + // e.message.indexOf("Backtrace:") > -1 -> opera9 + // 'opera#sourceloc' in e -> opera9, opera10a + // !e.stacktrace -> opera9 + if (!e.stacktrace) { + extractOpera9(e) // use e.message + } else if ((e.message.indexOf("\n") > -1) && + (e.message.split("\n").length > e.stacktrace.split("\n").length)) { + // e.message may have more stack entries than e.stacktrace + extractOpera9(e) // use e.message + } else { + extractOpera10a(e) // use e.stacktrace + } + } else if (!(!e.message) && !(!e.stack) && !(!e.stacktrace)) { + // e.stacktrace && e.stack -> opera10b + if (e.stacktrace.indexOf("called from line") < 0) { + extractOpera10b(e) + } else { + extractOpera11(e) + } + } else if (!(!e.stack) && !e.fileName) { + /* Chrome 27 does not have e.arguments as earlier versions, + * but still does not have e.fileName as Firefox */ + extractChrome(e) + } else { + extractOther(e) + } + } + + private def extractRhino(e: js.Dynamic): js.Array[jsString] = { + (e.stack.asInstanceOf[js.UndefOr[jsString]]).getOrElse[jsString]("") + .replace("""^\s+at\s+""".re("gm"), "") // remove 'at' and indentation + .replace("""^(.+?)(?: \((.+)\))?$""".re("gm"), "$2@$1") + .replace("""\r\n?""".re("gm"), "\n") // Rhino has platform-dependent EOL's + .split("\n") + } + + private def extractChrome(e: js.Dynamic): js.Array[jsString] = { + (e.stack.asInstanceOf[jsString] + "\n") + .replace("""^[\s\S]+?\s+at\s+""".re, " at ") // remove message + .replace("""^\s+(at eval )?at\s+""".re("gm"), "") // remove 'at' and indentation + .replace("""^([^\(]+?)([\n])""".re("gm"), "{anonymous}() ($1)$2") // see note + .replace("""^Object.<anonymous>\s*\(([^\)]+)\)""".re("gm"), "{anonymous}() ($1)") + .replace("""^([^\(]+|\{anonymous\}\(\)) \((.+)\)$""".re("gm"), "$1@$2") + .split("\n") + .jsSlice(0, -1) + + /* Note: there was a $ next to the \n here in the original code, but it + * chokes with method names with $'s, which are generated often by Scala.js. + */ + } + + private def extractFirefox(e: js.Dynamic): js.Array[jsString] = { + (e.stack.asInstanceOf[jsString]) + .replace("""(?:\n@:0)?\s+$""".re("m"), "") + .replace("""^(?:\((\S*)\))?@""".re("gm"), "{anonymous}($1)@") + .split("\n") + } + + private def extractIE(e: js.Dynamic): js.Array[jsString] = { + (e.stack.asInstanceOf[jsString]) + .replace("""^\s*at\s+(.*)$""".re("gm"), "$1") + .replace("""^Anonymous function\s+""".re("gm"), "{anonymous}() ") + .replace("""^([^\(]+|\{anonymous\}\(\))\s+\((.+)\)$""".re("gm"), "$1@$2") + .split("\n") + .jsSlice(1) + } + + private def extractSafari(e: js.Dynamic): js.Array[jsString] = { + (e.stack.asInstanceOf[jsString]) + .replace("""\[native code\]\n""".re("m"), "") + .replace("""^(?=\w+Error\:).*$\n""".re("m"), "") + .replace("""^@""".re("gm"), "{anonymous}()@") + .split("\n") + } + + private def extractOpera9(e: js.Dynamic): js.Array[jsString] = { + // " Line 43 of linked script file://localhost/G:/js/stacktrace.js\n" + // " Line 7 of inline#1 script in file://localhost/G:/js/test/functional/testcase1.html\n" + val lineRE = """Line (\d+).*script (?:in )?(\S+)""".re("i") + val lines = (e.message.asInstanceOf[jsString]).split("\n") + val result = new js.Array[jsString] + + var i = 2 + val len = lines.length.toInt + while (i < len) { + val mtch = lineRE.exec(lines(i)) + if (mtch ne null) { + result.push("{anonymous}()@" + mtch(2).get + ":" + mtch(1).get + /* + " -- " + lines(i+1).replace("""^\s+""".re, "") */) + } + i += 2 + } + + result + } + + private def extractOpera10a(e: js.Dynamic): js.Array[jsString] = { + // " Line 27 of linked script file://localhost/G:/js/stacktrace.js\n" + // " Line 11 of inline#1 script in file://localhost/G:/js/test/functional/testcase1.html: In function foo\n" + val lineRE = """Line (\d+).*script (?:in )?(\S+)(?:: In function (\S+))?$""".re("i") + val lines = (e.stacktrace.asInstanceOf[jsString]).split("\n") + val result = new js.Array[jsString] + + var i = 0 + val len = lines.length.toInt + while (i < len) { + val mtch = lineRE.exec(lines(i)) + if (mtch ne null) { + val fnName = mtch(3).getOrElse("{anonymous}") + result.push(fnName + "()@" + mtch(2).get + ":" + mtch(1).get + /* + " -- " + lines(i+1).replace("""^\s+""".re, "")*/) + } + i += 2 + } + + result + } + + private def extractOpera10b(e: js.Dynamic): js.Array[jsString] = { + // "<anonymous function: run>([arguments not available])@file://localhost/G:/js/stacktrace.js:27\n" + + // "printStackTrace([arguments not available])@file://localhost/G:/js/stacktrace.js:18\n" + + // "@file://localhost/G:/js/test/functional/testcase1.html:15" + val lineRE = """^(.*)@(.+):(\d+)$""".re + val lines = (e.stacktrace.asInstanceOf[jsString]).split("\n") + val result = new js.Array[jsString] + + var i = 0 + val len = lines.length.toInt + while (i < len) { + val mtch = lineRE.exec(lines(i)) + if (mtch ne null) { + val fnName = mtch(1).fold("global code")(_ + "()") + result.push(fnName + "@" + mtch(2).get + ":" + mtch(3).get) + } + i += 1 + } + + result + } + + private def extractOpera11(e: js.Dynamic): js.Array[jsString] = { + val lineRE = """^.*line (\d+), column (\d+)(?: in (.+))? in (\S+):$""".re + val lines = (e.stacktrace.asInstanceOf[jsString]).split("\n") + val result = new js.Array[jsString] + + var i = 0 + val len = lines.length.toInt + while (i < len) { + val mtch = lineRE.exec(lines(i)) + if (mtch ne null) { + val location = mtch(4).get + ":" + mtch(1).get + ":" + mtch(2).get + val fnName0 = mtch(2).getOrElse("global code") + val fnName = (fnName0: jsString) + .replace("""<anonymous function: (\S+)>""".re, "$1") + .replace("""<anonymous function>""".re, "{anonymous}") + result.push(fnName + "@" + location + /* + " -- " + lines(i+1).replace("""^\s+""".re, "")*/) + } + i += 2 + } + + result + } + + private def extractOther(e: js.Dynamic): js.Array[jsString] = { + js.Array() + } + + /* End copy-paste-translate from stacktrace.js + * --------------------------------------------------------------------------- + */ + + trait JSStackTraceElem extends js.Object { + var declaringClass: String = js.native + var methodName: String = js.native + var fileName: String = js.native + /** 1-based line number */ + var lineNumber: Int = js.native + /** 1-based optional columnNumber */ + var columnNumber: js.UndefOr[Int] = js.native + } + + object JSStackTraceElem { + @inline + def apply(declaringClass: String, methodName: String, + fileName: String, lineNumber: Int, + columnNumber: js.UndefOr[Int] = js.undefined): JSStackTraceElem = { + js.Dynamic.literal( + declaringClass = declaringClass, + methodName = methodName, + fileName = fileName, + lineNumber = lineNumber, + columnNumber = columnNumber + ).asInstanceOf[JSStackTraceElem] + } + } + + /** + * Implicit class to access magic column element created in STE + */ + implicit class ColumnStackTraceElement(ste: StackTraceElement) { + def getColumnNumber: Int = { + val num = ste.asInstanceOf[js.Dynamic].columnNumber + if (!(!num)) num.asInstanceOf[Int] + else -1 // Not very Scala-ish, but consistent with StackTraceElemnt + } + } + +} diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/runtime/UndefinedBehaviorError.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/runtime/UndefinedBehaviorError.scala new file mode 100644 index 0000000..b06ed7d --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/runtime/UndefinedBehaviorError.scala @@ -0,0 +1,23 @@ +package scala.scalajs.runtime + +import scala.util.control.ControlThrowable + +/** Error thrown when an undefined behavior in Fatal mode has been detected. + * This error should never be caught. It indicates a severe programming bug. + * In Unchecked mode, the program may behave arbitrarily. + * The `cause` is set to the exception that would have been thrown if the + * given behavior was in Compliant mode. + * If your program relies on the proper kind of exception being thrown, as if + * running on the JVM, you should set the appropriate behavior to Compliant. + * Note that this will have (potentially major) performance impacts. + */ +class UndefinedBehaviorError(message: String, cause: Throwable) + extends java.lang.Error(message, cause) with ControlThrowable { + + def this(cause: Throwable) = + this("An undefined behavior was detected" + + (if (cause == null) "" else ": "+cause.getMessage), cause) + + override def fillInStackTrace(): Throwable = + super[Error].fillInStackTrace() +} diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/runtime/package.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/runtime/package.scala new file mode 100644 index 0000000..59c774c --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/runtime/package.scala @@ -0,0 +1,176 @@ +package scala.scalajs + +import scala.annotation.tailrec + +import scala.collection.GenTraversableOnce + +package object runtime { + + def wrapJavaScriptException(e: Any): Throwable = e match { + case e: Throwable => e + case _ => js.JavaScriptException(e) + } + + def unwrapJavaScriptException(th: Throwable): Any = th match { + case js.JavaScriptException(e) => e + case _ => th + } + + def cloneObject(from: js.Object): js.Object = { + val ctor = ({ (self: js.Dictionary[js.Any], from: js.Dictionary[js.Any]) => + for (key <- from.keys) + self(key) = from(key) + }: js.ThisFunction).asInstanceOf[js.Dynamic] + ctor.prototype = js.Object.getPrototypeOf(from) + js.Dynamic.newInstance(ctor)(from) + } + + @inline final def genTraversableOnce2jsArray[A]( + col: GenTraversableOnce[A]): js.Array[A] = { + col match { + case col: js.ArrayOps[A] => col.result() + case col: js.WrappedArray[A] => col.array + case _ => + val result = new js.Array[A] + col.foreach(x => result.push(x)) + result + } + } + + /** Instantiates a JS object with variadic arguments to the constructor. */ + def newJSObjectWithVarargs(ctor: js.Dynamic, args: js.Array[_]): js.Any = { + // Not really "possible" in JavaScript, so we emulate what it would be. + val c = ((() => ()): js.Function).asInstanceOf[js.Dynamic] + c.prototype = ctor.prototype + val instance = js.Dynamic.newInstance(c)() + val result = ctor.applyDynamic("apply")(instance, args) + (result: js.Any) match { + case _:js.prim.Undefined | _:js.prim.Number | _:js.prim.Boolean | + _:js.prim.String | null => + instance + case _ => + result + } + } + + /** Returns an array of the enumerable properties in an object's prototype + * chain. + * + * This is the implementation of [[js.Object.properties]]. + */ + def propertiesOf(obj: js.Any): js.Array[String] = { + // See http://stackoverflow.com/questions/26445248/ + if (obj == null || js.isUndefined(obj)) { + js.Array() + } else { + val result = new js.Array[String] + val alreadySeen = js.Dictionary.empty[Boolean] + + @tailrec + def loop(obj: js.Object): Unit = { + if (obj != null) { + // Add own enumerable properties that have not been seen yet + val enumProps = js.Object.keys(obj) + val enumPropsLen = enumProps.length + var i = 0 + while (i < enumPropsLen) { + val prop = enumProps(i) + if (!alreadySeen.get(prop).isDefined) + result.push(prop) + i += 1 + } + + /* Add all own properties to the alreadySeen set, including + * non-enumerable ones. + */ + val allProps = js.Object.getOwnPropertyNames(obj) + val allPropsLen = allProps.length + var j = 0 + while (j < allPropsLen) { + alreadySeen(allProps(j)) = true + j += 1 + } + + // Continue with the next object in the prototype chain + loop(js.Object.getPrototypeOf(obj)) + } + } + loop(js.Object(obj)) + + result + } + } + + /** Information about the environment Scala.js runs in. */ + def environmentInfo: js.Dynamic = sys.error("stub") + + /** Polyfill for fround in case we use strict Floats and even Typed Arrays + * are not available. + * Note: this method returns a Double, even though the value is meant + * to be a Float. It cannot return a Float because that would require to + * do `x.toFloat` somewhere in here, which would itself, in turn, call this + * method. + */ + def froundPolyfill(v: Double): Double = { + /* Originally inspired by the Typed Array polyfills written by Joshua Bell: + * https://github.com/inexorabletash/polyfill/blob/a682f42c1092280bb01907c245979fb07219513d/typedarray.js#L150-L255 + * Then simplified quite a lot because + * 1) we do not need to produce the actual bit string that serves as + * storage of the floats, and + * 2) we are only interested in the float32 case. + */ + import Math._ + + // Special cases + if (v.isNaN || v == 0.0 || v.isInfinite) { + v + } else { + val LN2 = 0.6931471805599453 + val ebits = 8 + val fbits = 23 + val bias = (1 << (ebits-1)) - 1 + val twoPowFbits = (1 << fbits).toDouble + val SubnormalThreshold = 1.1754943508222875E-38 // pow(2, 1-bias) + + val isNegative = v < 0 + val av = if (isNegative) -v else v + + val absResult = if (av >= SubnormalThreshold) { + val e0 = floor(log(av) / LN2) + // 1-bias <= e0 <= 1024 + if (e0 > bias) { + // Overflow + Double.PositiveInfinity + } else { + val twoPowE0 = pow(2, e0) + val f0 = Bits.roundToEven(av / twoPowE0 * twoPowFbits) + if (f0 / twoPowFbits >= 2) { + //val e = e0 + 1.0 // not used + val f = 1.0 + if (e0 > bias-1) { // === (e > bias) because e0 is whole + // Overflow + Double.PositiveInfinity + } else { + // Normalized case 1 + val twoPowE = 2*twoPowE0 + twoPowE * (1.0 + (f - twoPowFbits) / twoPowFbits) + } + } else { + // Normalized case 2 + // val e = e0 // not used + val f = f0 + val twoPowE = twoPowE0 + twoPowE * (1.0 + (f - twoPowFbits) / twoPowFbits) + } + } + } else { + // Subnormal + val rounder = Float.MinPositiveValue.toDouble + Bits.roundToEven(av / rounder) * rounder + } + + if (isNegative) -absResult else absResult + } + } + +} diff --git a/examples/scala-js/no-ir-check-test/src/test/scala/scala/scalajs/testsuite/noircheck/DummyParentsTest.scala b/examples/scala-js/no-ir-check-test/src/test/scala/scala/scalajs/testsuite/noircheck/DummyParentsTest.scala new file mode 100644 index 0000000..30c4172 --- /dev/null +++ b/examples/scala-js/no-ir-check-test/src/test/scala/scala/scalajs/testsuite/noircheck/DummyParentsTest.scala @@ -0,0 +1,34 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.noircheck + +import org.scalajs.jasminetest.JasmineTest + +object DummyParentsTest extends JasmineTest { + + describe("Linking Stages") { + + it("should provide dummy parents if required") { + + import scala.concurrent.forkjoin._ + + // scala.concurrent.forkjoin.ForkJoinWorkerThread is not defined + class DummyFJWorkerThread extends ForkJoinWorkerThread(null) { + override def onStart(): Unit = { /* something */ } + } + + val x = "1".toInt + + if (x + x < 0) { + // Ensure DummyFuture is not DCEd, but never instantiated + new DummyFJWorkerThread() + } + } + + } +} diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/BlacklistedTests.txt b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/BlacklistedTests.txt new file mode 100644 index 0000000..241b188 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/BlacklistedTests.txt @@ -0,0 +1,891 @@ +# +# POS +# + +# Using Jsoup, what's that? +pos/cycle-jsoup.scala + +# Using scala.actors +pos/t533.scala +pos/functions.scala +pos/MailBox.scala + +# +# NEG +# + +# Using the compiler API + +neg/t6446-additional +neg/t6446-list +neg/t6446-missing +neg/t6446-show-phases.scala + +# Screws up, but not really our problem (error: None.get instead of +# phase ordering error) +neg/t7494-multi-right-after +neg/t7494-right-after-before +neg/t7622-multi-followers +neg/t7622-cyclic-dependency + +# Uses some strange macro cross compile mechanism. +neg/macro-incompatible-macro-engine-c.scala + +# Uses .java files +neg/t6289 + +# +# RUN +# + +# Relies on the exact toString() representation of Floats/Doubles +run/t2378.scala + +# Relies on !box(+0.0).equals(box(-0.0)) +run/number-parsing.scala + +# Uses ClassTags on existentials which are broken in Scala (see #251) +run/valueclasses-classtag-existential.scala + +# Relies on a particular execution speed +run/t5857.scala + +# Using parts of the javalib we don't plan to support + +run/t0412.scala +run/t3518.scala +run/t6198.scala +run/t5018.scala +run/t2417.scala +run/t6197.scala +run/t4813.scala +run/lazy-concurrent.scala +run/t5880.scala +run/mapConserve.scala +run/t3667.scala +run/bigDecimalTest.scala +run/t3038d.scala +run/numbereq.scala +run/shutdownhooks.scala +run/t4658.scala +run/t4201.scala +run/t5590.scala +run/deeps.scala +run/t3895b.scala +run/t2813.2.scala +run/t5974.scala +run/hashset.scala +run/t5262.scala +run/t5293.scala +run/t5293-map.scala +run/serialize-stream.scala +run/sysprops.scala + +run/colltest.scala +run/equality.scala +run/t2849.scala +run/t1360.scala +run/t6114.scala +run/t7269.scala +run/t3199b.scala + +# Uses java.util.Collections +run/java-erasure.scala +run/t2250.scala + +# Uses java.math.BigDecimal / BigInteger +run/bigDecimalCache.scala +run/hashhash.scala +run/is-valid-num.scala +run/range.scala +run/stringinterpolation_macro-run.scala +run/t5356.scala +run/t6064.scala + +# Documented semantic difference on numbers (float precision) +run/interpolation.scala +run/interpolationMultiline1.scala + +# Documented semantic difference on String.split(x: Array[Char]) +run/t0325.scala + +# Using Threads +run/t6969.scala +run/inner-obj-auto.scala +run/predef-cycle.scala +run/synchronized.scala + +# Uses java.security +run/t2318.scala + +# Tries to catch java.lang.StackOverflowError +run/t6154.scala + +# Tries to catch java.lang.OutOfMemoryError +run/t7880.scala + +# Taking too much time, because JavaScript is not as fast as the JVM + +run/t3822.scala +run/collections.scala +run/t3989.scala +run/adding-growing-set.scala +run/t3242.scala +run/hashCodeDistribution.scala +run/t408.scala +run/t6584.scala +run/t6853.scala +run/UnrolledBuffer.scala + +# Crashes Rhino + +run/bridges.scala +run/patmat-exprs.scala + +# Using partest properties + +run/tailcalls.scala +run/t4294.scala +run/t6331b.scala + +# Using IO + +run/Predef.readLine.scala +run/t6488.scala + +# Object{Output|Input}Streams +run/t6935.scala +run/t8188.scala + +# Using System.getProperties + +run/t4426.scala + +# Using ExecutionContext.global +run/t7336.scala +run/t7775.scala +run/future-flatmap-exec-count.scala + +# Using reflection + +run/t6063 + +run/mixin-bridge-methods.scala +run/t5125.scala +run/outertest.scala +run/t6223.scala +run/t5652b +run/elidable-opt.scala +run/nullable-lazyvals.scala +run/t4794.scala +run/t5652 +run/t5652c +run/getClassTest-old.scala + +run/reflection-repl-classes.scala +run/t5256e.scala +run/typetags_core.scala +run/reflection-constructormirror-toplevel-badpath.scala +run/t5276_1b.scala +run/reflection-sorted-decls.scala +run/toolbox_typecheck_implicitsdisabled.scala +run/t5418b.scala +run/toolbox_typecheck_macrosdisabled2.scala +run/abstypetags_serialize.scala +run/all-overridden.scala +run/showraw_tree_kinds.scala +run/showraw_tree_types_ids.scala +run/showraw_tree_types_typed.scala +run/showraw_tree_ids.scala +run/showraw_tree_ultimate.scala +run/t5266_2.scala +run/t5274_1.scala +run/t5224.scala +run/reflection-sanitychecks.scala +run/t6086-vanilla.scala +run/t5277_2.scala +run/reflection-methodsymbol-params.scala +run/reflection-valueclasses-standard.scala +run/t5274_2.scala +run/t5423.scala +run/reflection-modulemirror-toplevel-good.scala +run/t5419.scala +run/t5271_3.scala +run/reflection-enclosed-nested-basic.scala +run/reflection-enclosed-nested-nested-basic.scala +run/fail-non-value-types.scala +run/reflection-mem-typecheck.scala +run/exprs_serialize.scala +run/t5258a.scala +run/typetags_without_scala_reflect_manifest_lookup.scala +run/t4110-new.scala +run/t5273_2b_newpatmat.scala +run/t6277.scala +run/t5335.scala +run/toolbox_typecheck_macrosdisabled.scala +run/reflection-modulemirror-inner-good.scala +run/t5229_2.scala +run/typetags_multi.scala +run/typetags_without_scala_reflect_typetag_manifest_interop.scala +run/reflection-constructormirror-toplevel-good.scala +run/reflection-magicsymbols-invoke.scala +run/t6392b.scala +run/t5229_1.scala +run/reflection-magicsymbols-vanilla.scala +run/t5225_2.scala +run/origins.scala +run/runtimeEval1.scala +run/reflection-implClass.scala +run/reflection-enclosed-nested-inner-basic.scala +run/reflection-fieldmirror-ctorparam.scala +run/t6181.scala +run/reflection-magicsymbols-repl.scala +run/t5272_2_newpatmat.scala +run/t5270.scala +run/t5418a.scala +run/t5276_2b.scala +run/t5256f.scala +run/reflection-enclosed-basic.scala +run/reflection-constructormirror-inner-badpath.scala +run/interop_typetags_are_manifests.scala +run/newTags.scala +run/t5273_1_newpatmat.scala +run/reflection-constructormirror-nested-good.scala +run/t2236-new.scala +run/existentials3-new.scala +run/t6323b.scala +run/t5943a1.scala +run/reflection-fieldmirror-getsetval.scala +run/t5272_1_oldpatmat.scala +run/t5256h.scala +run/t1195-new.scala +run/t5840.scala +run/reflection-methodsymbol-returntype.scala +run/reflection-fieldmirror-accessorsareokay.scala +run/reflection-sorted-members.scala +run/reflection-allmirrors-tostring.scala +run/valueclasses-typetag-existential.scala +run/toolbox_console_reporter.scala +run/reflection-enclosed-inner-inner-basic.scala +run/t5256b.scala +run/bytecodecs.scala +run/elidable.scala +run/freetypes_false_alarm1.scala +run/freetypes_false_alarm2.scala +run/getClassTest-new.scala +run/idempotency-extractors.scala +run/idempotency-case-classes.scala +run/idempotency-this.scala +run/idempotency-labels.scala +run/idempotency-lazy-vals.scala +run/interop_manifests_are_abstypetags.scala +run/interop_manifests_are_typetags.scala +run/abstypetags_core.scala +run/macro-reify-abstypetag-notypeparams +run/macro-reify-abstypetag-typeparams-tags +run/macro-reify-abstypetag-typeparams-notags +run/macro-reify-abstypetag-usetypetag +run/macro-reify-freevars +run/macro-reify-splice-outside-reify +run/macro-reify-tagless-a +run/macro-reify-type +run/macro-reify-typetag-typeparams-tags +run/macro-reify-typetag-notypeparams +run/macro-undetparams-implicitval +run/manifests-new.scala +run/manifests-old.scala +run/no-pickle-skolems +run/position-val-def.scala +run/reflect-priv-ctor.scala +run/primitive-sigs-2-new.scala +run/primitive-sigs-2-old.scala +run/reflection-enclosed-inner-basic.scala +run/reflection-enclosed-inner-nested-basic.scala +run/reflection-constructormirror-inner-good.scala +run/reflection-constructormirror-nested-badpath.scala +run/reflection-fancy-java-classes +run/reflection-fieldsymbol-navigation.scala +run/reflection-fieldmirror-nmelocalsuffixstring.scala +run/reflection-fieldmirror-getsetvar.scala +run/reflection-fieldmirror-privatethis.scala +run/reflection-implicit.scala +run/reflection-mem-glbs.scala +run/reflection-mem-tags.scala +run/reflection-java-annotations +run/reflection-java-crtp +run/reflection-methodsymbol-typeparams.scala +run/reflection-modulemirror-nested-badpath.scala +run/reflection-modulemirror-inner-badpath.scala +run/reflection-modulemirror-nested-good.scala +run/reflection-modulemirror-toplevel-badpath.scala +run/reflection-sync-subtypes.scala +run/reflinit.scala +run/reflection-valueclasses-derived.scala +run/reflection-valueclasses-magic.scala +run/resetattrs-this.scala +run/runtimeEval2.scala +run/showraw_aliases.scala +run/showraw_mods.scala +run/shortClass.scala +run/showraw_nosymbol.scala +run/showraw_tree.scala +run/showraw_tree_types_untyped.scala +run/t1167.scala +run/t2577.scala +run/t2873.scala +run/t2886.scala +run/t2251b.scala +run/t3346j.scala +run/t3507-new.scala +run/t3569.scala +run/t5125b.scala +run/t5225_1.scala +run/t3425b +run/t5256a.scala +run/t5230.scala +run/t5256c.scala +run/t5256g.scala +run/t5266_1.scala +run/t5269.scala +run/t5271_1.scala +run/t5271_2.scala +run/t5271_4.scala +run/t5272_1_newpatmat.scala +run/t5272_2_oldpatmat.scala +run/t5273_1_oldpatmat.scala +run/t5273_2a_newpatmat.scala +run/t5273_2a_oldpatmat.scala +run/t5275.scala +run/t5276_1a.scala +run/t5276_2a.scala +run/t5277_1.scala +run/t5279.scala +run/t5334_1.scala +run/t5334_2.scala +run/t5415.scala +run/t5418.scala +run/t5676.scala +run/t5704.scala +run/t5710-1.scala +run/t5710-2.scala +run/t5770.scala +run/t5894.scala +run/t5816.scala +run/t5824.scala +run/t5912.scala +run/t5942.scala +run/t5943a2.scala +run/t6023.scala +run/t6113.scala +run/t6175.scala +run/t6178.scala +run/t6199-mirror.scala +run/t6199-toolbox.scala +run/t6220.scala +run/t6240-universe-code-gen.scala +run/t6221 +run/t6260b.scala +run/t6259.scala +run/t6287.scala +run/t6261.scala +run/t6308.scala +run/t6344.scala +run/t6392a.scala +run/t6591_1.scala +run/t6591_2.scala +run/t6591_3.scala +run/t6591_5.scala +run/t6591_6.scala +run/t6591_7.scala +run/t6608.scala +run/t6677.scala +run/t6687.scala +run/t6715.scala +run/t6719.scala +run/t6793.scala +run/t6860.scala +run/t6793b.scala +run/t6793c.scala +run/t7045.scala +run/t7046.scala +run/t7008-scala-defined +run/t7120b.scala +run/t7151.scala +run/t7214.scala +run/t7235.scala +run/t7331a.scala +run/t7331b.scala +run/t7331c.scala +run/t7558.scala +run/t7556 +run/t7779.scala +run/t7868b.scala +run/toolbox_current_run_compiles.scala +run/toolbox_default_reporter_is_silent.scala +run/toolbox_parse_package.scala +run/toolbox_silent_reporter.scala +run/toolbox_typecheck_inferimplicitvalue.scala +run/trait-renaming +run/typetags_serialize.scala +run/valueclasses-typetag-basic.scala +run/WeakHashSetTest.scala +run/valueclasses-typetag-generic.scala +run/t4023.scala +run/t4024.scala +run/t6380.scala +run/t5273_2b_oldpatmat.scala +run/t8104 +run/t8047.scala +run/t6992 +run/var-arity-class-symbol.scala +run/typetags_symbolof_x.scala +run/typecheck +run/t8190.scala +run/t8192 +run/t8177f.scala +run/t8199.scala +run/t7932.scala +run/t7700.scala +run/t7570c.scala +run/t7570b.scala +run/t7533.scala +run/t7570a.scala +run/t7044 +run/t7328.scala +run/t6733.scala +run/t6554.scala +run/t6732.scala +run/t6379 +run/t6411b.scala +run/t6411a.scala +run/t6260c.scala +run/t6260-delambdafy.scala +run/showdecl +run/reflection-sync-potpourri.scala +run/reflection-tags.scala +run/reflection-companiontype.scala +run/reflection-scala-annotations.scala +run/reflection-idtc.scala +run/macro-reify-nested-b2 +run/mixin-signatures.scala +run/reflection-companion.scala +run/macro-reify-nested-b1 +run/macro-reify-nested-a2 +run/macro-reify-nested-a1 +run/macro-reify-chained2 +run/macro-reify-chained1 +run/inferred-type-constructors.scala +run/mirror_symbolof_x.scala + +run/reify_newimpl_29.scala +run/reify_magicsymbols.scala +run/reify_inheritance.scala +run/reify_newimpl_12.scala +run/reify_typerefs_2b.scala +run/reify_csv.scala +run/reify_inner2.scala +run/reify_maps_oldpatmat.scala +run/reify_newimpl_43.scala +run/reify_nested_inner_refers_to_local.scala +run/reify_closure7.scala +run/reify_closure8b.scala +run/reify_typerefs_3b.scala +run/reify_newimpl_44.scala +run/reify_newimpl_06.scala +run/reify_newimpl_05.scala +run/reify_newimpl_20.scala +run/reify_newimpl_23.scala +run/reify_metalevel_breach_-1_refers_to_1.scala +run/reify_newimpl_41.scala +run/reify-repl-fail-gracefully.scala +run/reify_fors_oldpatmat.scala +run/reify_inner3.scala +run/reify_closure8a.scala +run/reify_closures10.scala +run/reify_ann2a.scala +run/reify_newimpl_51.scala +run/reify_newimpl_47.scala +run/reify_extendbuiltins.scala +run/reify_newimpl_30.scala +run/reify_newimpl_38.scala +run/reify_closure2a.scala +run/reify_newimpl_45.scala +run/reify_closure1.scala +run/reify_generic2.scala +run/reify_printf.scala +run/reify_closure6.scala +run/reify_newimpl_37.scala +run/reify_newimpl_35.scala +run/reify_typerefs_3a.scala +run/reify_newimpl_25.scala +run/reify_ann4.scala +run/reify_typerefs_1b.scala +run/reify_newimpl_22.scala +run/reify_this.scala +run/reify_typerefs_2a.scala +run/reify_newimpl_03.scala +run/reify_newimpl_48.scala +run/reify_varargs.scala +run/reify_newimpl_42.scala +run/reify_newimpl_15.scala +run/reify_nested_inner_refers_to_global.scala +run/reify_newimpl_02.scala +run/reify_newimpl_01.scala +run/reify_fors_newpatmat.scala +run/reify_classfileann_a.scala +run/reify_nested_outer_refers_to_local.scala +run/reify_newimpl_13.scala +run/reify_closure5a.scala +run/reify_inner4.scala +run/reify_sort.scala +run/reify_ann1a.scala +run/reify_classfileann_b.scala +run/reify_closure4a.scala +run/reify_newimpl_33.scala +run/reify_sort1.scala +run/reify_properties.scala +run/reify_generic.scala +run/reify_newimpl_27.scala +run/reify-aliases.scala +run/reify_ann3.scala +run/reify-staticXXX.scala +run/reify_ann1b.scala +run/reify_ann5.scala +run/reify_anonymous.scala +run/reify-each-node-type.scala +run/reify_copypaste2.scala +run/reify_closure3a.scala +run/reify_copypaste1.scala +run/reify_complex.scala +run/reify_for1.scala +run/reify_getter.scala +run/reify_implicits-new.scala +run/reify_inner1.scala +run/reify_implicits-old.scala +run/reify_lazyunit.scala +run/reify_lazyevaluation.scala +run/reify_maps_newpatmat.scala +run/reify_metalevel_breach_+0_refers_to_1.scala +run/reify_metalevel_breach_-1_refers_to_0_a.scala +run/reify_metalevel_breach_-1_refers_to_0_b.scala +run/reify_nested_outer_refers_to_global.scala +run/reify_newimpl_04.scala +run/reify_newimpl_14.scala +run/reify_newimpl_11.scala +run/reify_newimpl_18.scala +run/reify_newimpl_19.scala +run/reify_newimpl_31.scala +run/reify_newimpl_21.scala +run/reify_newimpl_36.scala +run/reify_newimpl_39.scala +run/reify_newimpl_40.scala +run/reify_newimpl_49.scala +run/reify_newimpl_50.scala +run/reify_newimpl_52.scala +run/reify_renamed_term_basic.scala +run/reify_renamed_term_local_to_reifee.scala +run/reify_renamed_term_overloaded_method.scala +run/reify_renamed_type_basic.scala +run/reify_renamed_type_local_to_reifee.scala +run/reify_renamed_type_spliceable.scala +run/reify_typerefs_1a.scala +run/reify_timeofday.scala +run/reify_renamed_term_t5841.scala + +# Uses refletction indirectly through +# scala.runtime.ScalaRunTime.replStringOf +run/t6634.scala + +# Using reflection to invoke macros. These tests actually don't require +# or test reflection, but use it to separate compilation units nicely. +# It's a pity we cannot use them + +run/macro-abort-fresh +run/macro-expand-varargs-explicit-over-nonvarargs-bad +run/macro-invalidret-doesnt-conform-to-def-rettype +run/macro-invalidret-nontypeable +run/macro-invalidusage-badret +run/macro-invalidusage-partialapplication +run/macro-invalidusage-partialapplication-with-tparams +run/macro-reflective-ma-normal-mdmi +run/macro-reflective-mamd-normal-mi + +# Using macros, but indirectly creating calls to reflection +run/macro-reify-unreify + +# Using Enumeration in a way we cannot fix + +run/enums.scala +run/t3719.scala + +# Expecting some particular value of hashCode() + +run/MeterCaseClass.scala +run/t5608.scala +run/caseClassHash.scala +run/Meter.scala + +# Exceptions that become JavaScriptException + +run/pf-catch.scala +run/exceptions-2.scala +run/exceptions-nest.scala + +# Expecting unsupported exceptions (e.g. ArrayIndexOutOfBounds) +run/optimizer-array-load.scala + +# Playing with classfile format + +run/classfile-format-51.scala +run/classfile-format-52.scala + +# Concurrent collections (TrieMap) +# has too much stuff implemented in *.java, so no support +run/triemap-hash.scala + +# Using parallel collections + +run/t5375.scala +run/t4894.scala +run/ctries-new +run/collection-conversions.scala +run/concurrent-map-conversions.scala +run/t4761.scala +run/concurrent-stream.scala +run/t7498.scala +run/t6448.scala +run/ctries-old +run/map_java_conversions.scala +run/parmap-ops.scala +run/pc-conversions.scala +run/t4459.scala +run/t4608.scala +run/t4723.scala +run/t4895.scala +run/t6052.scala +run/t6410.scala +run/t6467.scala +run/t6908.scala + +# Using scala.xml + +run/t4124.scala + +# Using Swing + +run/t3613.scala + +# Using the REPL + +run/t4285.scala +run/constant-type.scala +run/repl-bare-expr.scala +run/repl-parens.scala +run/repl-assign.scala +run/t5583.scala +run/treePrint.scala +run/constrained-types.scala +run/repl-power.scala +run/t4710.scala +run/repl-paste.scala +run/repl-reset.scala +run/repl-paste-3.scala +run/t6329_repl.scala +run/t6273.scala +run/repl-paste-2.scala +run/t5655.scala +run/t5072.scala +run/repl-colon-type.scala +run/kind-repl-command.scala +run/repl-trim-stack-trace.scala +run/t4594-repl-settings.scala +run/repl-save.scala +run/repl-paste-raw.scala +run/repl-paste-4.scala +run/t7801.scala +run/repl-backticks.scala +run/t6633.scala + +# Using the Repl (scala.tools.partest.ReplTest) +run/class-symbol-contravariant.scala +run/lub-visibility.scala +run/macro-bundle-repl.scala +run/macro-repl-basic.scala +run/macro-repl-dontexpand.scala +run/macro-system-properties.scala +run/reflection-equality.scala +run/reflection-repl-elementary.scala +run/reify_newimpl_26.scala +run/repl-javap-app.scala +run/repl-out-dir.scala +run/repl-term-macros.scala +run/repl-transcript.scala +run/repl-type-verbose.scala +run/t3376.scala +run/t4025.scala +run/t4172.scala +run/t4216.scala +run/t4542.scala +run/t4671.scala +run/t5256d.scala +run/t5535.scala +run/t5537.scala +run/t5789.scala +run/t6086-repl.scala +run/t6146b.scala +run/t6187.scala +run/t6320.scala +run/t6381.scala +run/t6434.scala +run/t6439.scala +run/t6507.scala +run/t6549.scala +run/t6937.scala +run/t7185.scala +run/t7319.scala +run/t7482a.scala +run/t7634.scala +run/t7747-repl.scala +run/t7805-repl-i.scala +run/tpeCache-tyconCache.scala +run/repl-empty-package +run/repl-javap-def.scala +run/repl-javap-fun.scala +run/repl-javap-mem.scala +run/repl-javap-memfun.scala +run/repl-javap-more-fun.scala +run/repl-javap-outdir +run/repl-javap.scala +run/repl-javap-outdir-funs +run/t6329_repl_bug.scala + +# Using Scala Script (partest.ScriptTest) + +run/t7711-script-args.scala + +# Using the compiler API + +run/t2512.scala +run/analyzerPlugins.scala +run/test-cpp.scala +run/compiler-asSeenFrom.scala +run/t5603.scala +run/t6440.scala +run/t5545.scala +run/existentials-in-compiler.scala +run/global-showdef.scala +run/inline-ex-handlers.scala +run/stream_length.scala +run/annotatedRetyping.scala +run/imain.scala +run/existential-rangepos.scala +run/delambdafy_uncurry_byname_inline.scala +run/delambdafy_uncurry_byname_method.scala +run/delambdafy_uncurry_inline.scala +run/delambdafy_t6555.scala +run/delambdafy_uncurry_method.scala +run/delambdafy_t6028.scala +run/memberpos.scala +run/programmatic-main.scala +run/reflection-names.scala +run/settings-parse.scala +run/sm-interpolator.scala +run/t1501.scala +run/t1500.scala +run/sammy_java8.scala +run/t1618.scala +run/t2464 +run/t4072.scala +run/t5064.scala +run/t5313.scala +run/t5385.scala +run/t5699.scala +run/t5717.scala +run/t5940.scala +run/t6028.scala +run/t6194.scala +run/t6288b-jump-position.scala +run/t6669.scala +run/t6745-2.scala +run/t6955.scala +run/t6956.scala +run/t7096.scala +run/t7271.scala +run/t7337.scala +run/t7398.scala +run/t7569.scala +run/t7852.scala +run/t7817-tree-gen.scala +run/t7825.scala +run/t7933.scala +run/t7843-jsr223-service.scala + +# partest.DirectTest +run/t6288.scala +run/t6331.scala +run/t6440b.scala +run/t6555.scala +run/t7876.scala +run/typetags_without_scala_reflect_typetag_lookup.scala +run/dynamic-updateDynamic.scala +run/dynamic-selectDynamic.scala +run/dynamic-applyDynamic.scala +run/dynamic-applyDynamicNamed.scala +run/t4841-isolate-plugins +run/large_code.scala +run/macroPlugins-namerHooks.scala +run/t4287inferredMethodTypes.scala +run/t4841-no-plugin.scala +run/t4332.scala +run/t8029.scala +run/t8046 + +# partest.BytecodeTest +run/t6546 +run/t7106 +run/t7974 + +# Using .java source files + +run/t4317 +run/t4238 +run/t2296c +run/t4119 +run/t4283 +run/t4891 +run/t6168 +run/t6168b +run/t6240a +run/t6240b +run/t6548 +run/t6989 +run/t7008 +run/t7246 +run/t7246b +run/t7359 +run/t7439 +run/t7455 +run/t7510 +run/t7582-private-within +run/t7582 +run/t7582b +run/t3897 +run/t7374 +run/t3452e +run/t3452g +run/t3452d +run/t3452b-bcode +run/t3452b +run/t3452a +run/t1430 +run/t4729 + +# Using scala-script +run/t7791-script-linenums.scala + +# Suffers from bug in Node.js (https://github.com/joyent/node/issues/7528) +run/range-unit.scala + +### Incorrect partests ### +# Badly uses constract of Console.print (no flush) +run/t429.scala diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/BuglistedTests.txt b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/BuglistedTests.txt new file mode 100644 index 0000000..42c6146 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/BuglistedTests.txt @@ -0,0 +1,4 @@ +# The tests in this file should pass but have never passed so far +# use scala.tools.partest.scalajs.testunknownonly to only run tests +# which are neither in BuglistedTests.txt, WhitelistedTests.txt or +# BlacklistedTests.txt diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/NoDCEWarn.txt b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/NoDCEWarn.txt new file mode 100644 index 0000000..cc5aff0 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/NoDCEWarn.txt @@ -0,0 +1,8 @@ +Ljava_math_MathContext$ +Ljava_math_BigDecimal$ +Ljava_math_BigDecimal +Ljava_math_BigInteger$ +jl_Class$ +jl_Class.getClassLoader__jl_ClassLoader +jl_Class.getPackage__jl_Package +jl_Class.getInterfaces__Ajl_Class diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/WhitelistedTests.txt b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/WhitelistedTests.txt new file mode 100644 index 0000000..c4df1ec --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/WhitelistedTests.txt @@ -0,0 +1,2929 @@ +pos/spec-super.scala +pos/t1035.scala +pos/t5897.scala +pos/irrefutable.scala +pos/spec-partialmap.scala +pos/tcpoly_seq.scala +pos/partialfun.scala +pos/t2795-new.scala +pos/clsrefine.scala +pos/t0774 +pos/t1070.scala +pos/t5957 +pos/looping-jsig.scala +pos/t3274.scala +pos/spec-fields-old.scala +pos/t262.scala +pos/t7486.scala +pos/t2261.scala +pos/t6600.scala +pos/t4786.scala +pos/t5406.scala +pos/tcpoly_late_method_params.scala +pos/t2726 +pos/pos-bug1210.scala +pos/t3312.scala +pos/manifest1-old.scala +pos/gadt-gilles.scala +pos/t4842.scala +pos/ted.scala +pos/NoCyclicReference.scala +pos/t3568.scala +pos/t0030.scala +pos/t2635.scala +pos/t7232b +pos/t0017.scala +pos/t812.scala +pos/t2179.scala +pos/t651.scala +pos/spurious-overload.scala +pos/t758.scala +pos/t4760.scala +pos/t1672.scala +pos/mixins.scala +pos/patterns.scala +pos/t1260.scala +pos/t6551.scala +pos/t2060.scala +pos/t6575a.scala +pos/t1318.scala +pos/t4266.scala +pos/t0695 +pos/protected-static +pos/t5738.scala +pos/t1226.scala +pos/t5013 +pos/t6215.scala +pos/t5692b +pos/traits.scala +pos/t2994a.scala +pos/t3371.scala +pos/t613.scala +pos/t6499.scala +pos/xlint1.scala +pos/t1150 +pos/sealed-final.scala +pos/test4a.scala +pos/t2664.scala +pos/t3528.scala +pos/t3174.scala +pos/t6994.scala +pos/t4812.scala +pos/t5777.scala +pos/t5223.scala +pos/t439.scala +pos/t3079.scala +pos/t5829.scala +pos/t0036.scala +pos/scoping2.scala +pos/t4717.scala +pos/t4257.scala +pos/t1210a.scala +pos/getClassType.scala +pos/t5330.scala +pos/t4524.scala +pos/t2945.scala +pos/t6562.scala +pos/t0273.scala +pos/override-object-yes.scala +pos/t7426.scala +pos/t6601 +pos/t3076 +pos/seq-ordering.scala +pos/spec-groups.scala +pos/t296.scala +pos/t5545 +pos/spec-multiplectors.scala +pos/t1789.scala +pos/t2569 +pos/ksbug1.scala +pos/t0599.scala +pos/local-objects.scala +pos/t0081.scala +pos/t5756.scala +pos/t7126.scala +pos/t7716.scala +pos/t2797.scala +pos/t5399.scala +pos/t1101 +pos/t767.scala +pos/contrib467.scala +pos/t7532b +pos/self-type-override.scala +pos/t4853.scala +pos/t839.scala +pos/t5644 +pos/t5853.scala +pos/t5178.scala +pos/unapplyNeedsMemberType.scala +pos/t5390.scala +pos/t6575b.scala +pos/t151.scala +pos/t2665.scala +pos/t5120.scala +pos/erasure-nsquared.scala +pos/arrays3.scala +pos/t3136.scala +pos/inline-access-levels +pos/t3972.scala +pos/t2591.scala +pos/t3486 +pos/variances-flip.scala +pos/annotated-original +pos/typesafecons.scala +pos/stable.scala +pos/t1996.scala +pos/t3037.scala +pos/t1711 +pos/t3374.scala +pos/t0029.scala +pos/t3278.scala +pos/matthias3.scala +pos/t5546.scala +pos/t4020.scala +pos/matthias4.scala +pos/value-class-override-spec.scala +pos/arrays2.scala +pos/t5119.scala +pos/t2613.scala +pos/t4070b.scala +pos/virtpatmat_exist_uncurry.scala +pos/modules1.scala +pos/spec-constr-new.scala +pos/t6335.scala +pos/t675.scala +pos/t0644.scala +pos/t5892.scala +pos/t360.scala +pos/override.scala +pos/t1798.scala +pos/strip-tvars-for-lubbasetypes.scala +pos/hk-infer.scala +pos/t2119.scala +pos/t0231.scala +pos/t1459 +pos/t1381-new.scala +pos/t2610.scala +pos/t2708.scala +pos/t5604b +pos/t3951 +pos/t361.scala +pos/t319.scala +pos/largecasetest.scala +pos/switchUnbox.scala +pos/typetags.scala +pos/java-access-pos +pos/t803.scala +pos/t3898.scala +pos/t5692a +pos/t2421.scala +pos/t1102 +pos/t0654.scala +pos/exhaust_alternatives.scala +pos/t807.scala +pos/t5702-pos-infix-star.scala +pos/t1186 +pos/t1439.scala +pos/t7427.scala +pos/virtpatmat_binding_opt.scala +pos/t247.scala +pos/abstract.scala +pos/gen-traversable-methods.scala +pos/t2795-old.scala +pos/t5639 +pos/t2667.scala +pos/t2405.scala +pos/t1438.scala +pos/SI-7100.scala +pos/t1659.scala +pos/unchecked-a.scala +pos/t3636.scala +pos/t6745.scala +pos/t2809.scala +pos/t7022.scala +pos/t6447.scala +pos/t6367.scala +pos/t5846.scala +pos/lubs.scala +pos/t1987a.scala +pos/spec-arrays.scala +pos/virtpatmat_anonfun_for.scala +pos/listpattern.scala +pos/t5742.scala +pos/test5refine.scala +pos/switch-small.scala +pos/t5604 +pos/return_thistype.scala +pos/t348plus.scala +pos/t3420.scala +pos/t3440.scala +pos/maxim1.scala +pos/caseClassInMethod.scala +pos/t7239.scala +pos/t3833.scala +pos/t6675.scala +pos/t4402 +pos/t5953.scala +pos/t1152 +pos/t0591.scala +pos/t210.scala +pos/t7035.scala +pos/t5769.scala +pos/pmbug.scala +pos/t2331.scala +pos/t5240.scala +pos/t304.scala +pos/annotated-treecopy +pos/t2081.scala +pos/t0904.scala +pos/t7649.scala +pos/t3498-new.scala +pos/contrib701.scala +pos/t6624.scala +pos/t3924.scala +pos/t374.scala +pos/t1642 +pos/t1591_pos.scala +pos/depmet_implicit_oopsla_session_2.scala +pos/t5899.scala +pos/thistype.scala +pos/t4176b.scala +pos/elidable-tparams.scala +pos/lambdalift.scala +pos/nothing_manifest_disambig-old.scala +pos/t372.scala +pos/t5399a.scala +pos/t2782.scala +pos/patmat-extract-tparam.scala +pos/t4114.scala +pos/unapplyVal.scala +pos/t2486.scala +pos/t5877b.scala +pos/t0625.scala +pos/t6358_2.scala +pos/viewtest1.scala +pos/t1237.scala +pos/scala-singleton.scala +pos/t1254 +pos/t5504 +pos/bounds.scala +pos/t3631.scala +pos/t3177.scala +pos/unapplyContexts2.scala +pos/t0438.scala +pos/t1642b.scala +pos/inferbroadtype.scala +pos/t1858.scala +pos/t3731.scala +pos/t6963c.scala +pos/classtag-pos.scala +pos/t6221.scala +pos/t3343.scala +pos/spec-asseenfrom.scala +pos/t604.scala +pos/spec-example1.scala +pos/t0786.scala +pos/annot-inner.scala +pos/t5886.scala +pos/t1056.scala +pos/t294 +pos/spec-Function1.scala +pos/t1836 +pos/spec-private.scala +pos/depmet_implicit_tpbetareduce.scala +pos/exhaust_2.scala +pos/t7532 +pos/t5175.scala +pos/t802.scala +pos/t5809.scala +pos/tcpoly_typesub.scala +pos/t6029.scala +pos/contextbounds-implicits-new.scala +pos/t3480.scala +pos/patterns3.scala +pos/caseaccs.scala +pos/spec-sparsearray-old.scala +pos/patterns1213.scala +pos/spec-traits.scala +pos/t0020.scala +pos/cycle +pos/t5968.scala +pos/typealiases.scala +pos/init.scala +pos/t697.scala +pos/t2693.scala +pos/t2377 +pos/unapplyGeneric.scala +pos/t1385.scala +pos/t3363-old.scala +pos/t1236.scala +pos/t0068.scala +pos/t4052.scala +pos/lambdalift1.scala +pos/z1730.scala +pos/variances-local.scala +pos/virtpatmat_gadt_array.scala +pos/t2421_delitedsl.scala +pos/t5626.scala +pos/t690.scala +pos/t711.scala +pos/t6547.scala +pos/t1937 +pos/t3999 +pos/SI-7060.scala +pos/t2305.scala +pos/t2168.scala +pos/t2660.scala +pos/t1693.scala +pos/inliner2.scala +pos/t2799.scala +pos/t6966.scala +pos/t1001.scala +pos/S5.scala +pos/t0301.scala +pos/t1048.scala +pos/t415.scala +pos/t6386.scala +pos/t2187.scala +pos/hashhash-overloads.scala +pos/t6921.scala +pos/t0227.scala +pos/t6556.scala +pos/t3946 +pos/t1053.scala +pos/t1000.scala +pos/t0586.scala +pos/t7011.scala +pos/t7329.scala +pos/t4975.scala +pos/t1131.scala +pos/t1027.scala +pos/t2913.scala +pos/t3494.scala +pos/t5606.scala +pos/t4716.scala +pos/tcpoly_gm.scala +pos/t4859.scala +pos/t514.scala +pos/lexical.scala +pos/t2624.scala +pos/t4036.scala +pos/t2741 +pos/t703.scala +pos/five-dot-f.scala +pos/t805.scala +pos/strings.scala +pos/t2433 +pos/t6925.scala +pos/t1085.scala +pos/t7461 +pos/t1942 +pos/spec-lists.scala +pos/t3349 +pos/tcpoly_infer_ticket474.scala +pos/t1614 +pos/virtpatmat_reach_const.scala +pos/t2194.scala +pos/t6976 +pos/t1560.scala +pos/t6891.scala +pos/t3883.scala +pos/infersingle.scala +pos/gui.scala +pos/t1164.scala +pos/t3175-pos.scala +pos/t4336.scala +pos/annotations2.scala +pos/proj-rec-test.scala +pos/t2973.scala +pos/t1123.scala +pos/t6205.scala +pos/t5727.scala +pos/t6537.scala +pos/t6712.scala +pos/t3866.scala +pos/t4831.scala +pos/selftails.scala +pos/t397.scala +pos/spec-vector.scala +pos/t7233b.scala +pos/t1391.scala +pos/spec.scala +pos/t3106.scala +pos/contextbounds-implicits-old.scala +pos/packageobjs.scala +pos/michel3.scala +pos/t628.scala +pos/collections.scala +pos/tcpoly_boundedmonad.scala +pos/t7668.scala +pos/t0032.scala +pos/t0069.scala +pos/t4345.scala +pos/t3521 +pos/t3071.scala +pos/tcpoly_infer_easy.scala +pos/t289.scala +pos/t4365 +pos/rangepos-anonapply.scala +pos/t5033.scala +pos/lambda.scala +pos/S8.scala +pos/t6014.scala +pos/t1785.scala +pos/t6034.scala +pos/t7433.scala +pos/imp2-pos.scala +pos/t0504.scala +pos/t1272.scala +pos/t0612 +pos/value-class-override-no-spec.scala +pos/overloaded-unapply.scala +pos/t5859.scala +pos/chang +pos/localmodules.scala +pos/t4237.scala +pos/rangepos-patmat.scala +pos/t1974.scala +pos/t0054.scala +pos/michel2.scala +pos/t0770.scala +pos/t1146.scala +pos/t2441pos.scala +pos/t5099.scala +pos/tcpoly_seq_typealias.scala +pos/t946.scala +pos/tcpoly_infer_ticket1864.scala +pos/t4579.scala +pos/t4737 +pos/t7377b.scala +pos/t616.scala +pos/t201.scala +pos/t6355pos.scala +pos/escapes2.scala +pos/t1675.scala +pos/t3890.scala +pos/t6040.scala +pos/spec-tailcall.scala +pos/existentials.scala +pos/t5317.scala +pos/t7782b.scala +pos/t4758.scala +pos/t7296.scala +pos/t6896.scala +pos/cls1.scala +pos/t402.scala +pos/gosh.scala +pos/t2619.scala +pos/javaConversions-2.10-regression.scala +pos/t759.scala +pos/t5259.scala +pos/t5130.scala +pos/t5156.scala +pos/t0905.scala +pos/package-implicit +pos/t2669.scala +pos/trait-parents.scala +pos/virtpatmat_exhaust.scala +pos/patterns1.scala +pos/t7014 +pos/t1231 +pos/t1751 +pos/t7233.scala +pos/t6022.scala +pos/tcpoly_checkkinds_mix.scala +pos/depmet_implicit_norm_ret.scala +pos/package-case.scala +pos/philippe4.scala +pos/michel6.scala +pos/t4188.scala +pos/t3936 +pos/t1280.scala +pos/t6722.scala +pos/t796.scala +pos/t5542.scala +pos/t3927.scala +pos/t2293.scala +pos/t3800.scala +pos/t7285a.scala +pos/t927.scala +pos/t4494.scala +pos/t3864 +pos/ilya2 +pos/t2940 +pos/S1.scala +pos/tcpoly_wildcards.scala +pos/tryexpr.scala +pos/t6089b.scala +pos/depmet_implicit_oopsla_zipwith.scala +pos/t245.scala +pos/t6146.scala +pos/t1782 +pos/t851.scala +pos/spec-thistype.scala +pos/tcpoly_poly.scala +pos/t6815_import.scala +pos/t4649.scala +pos/t0453.scala +pos/t5020.scala +pos/ilya +pos/t2435.scala +pos/t1279a.scala +pos/t2171.scala +pos/t1957.scala +pos/gadts2.scala +pos/t3567 +pos/Z.scala +pos/t1203b +pos/nested2.scala +pos/t1896 +pos/viewtest2.scala +pos/t5541.scala +pos/existentials-harmful.scala +pos/t4063.scala +pos/t6485a +pos/t1208.scala +pos/t5041.scala +pos/unapplyComplex.scala +pos/t3384.scala +pos/t4112.scala +pos/t788.scala +pos/hklub0.scala +pos/t757.scala +pos/t1197 +pos/t359.scala +pos/t5667.scala +pos/t1107a.scala +pos/virtpatmat_castbinder.scala +pos/t267.scala +pos/t3419 +pos/t3861.scala +pos/t6797.scala +pos/spec-localdefs.scala +pos/t3404 +pos/t4457_1.scala +pos/matthias5.scala +pos/spec-polymeth.scala +pos/kinds.scala +pos/t2310.scala +pos/t6552.scala +pos/valdefs.scala +pos/hkarray.scala +pos/homonym.scala +pos/t1235 +pos/t3429 +pos/t0053.scala +pos/depmet_implicit_chaining_zw.scala +pos/virtpatmat_partialfun_nsdnho.scala +pos/t6664.scala +pos/ticket2251.scala +pos/t3495.scala +pos/super +pos/t121.scala +pos/javaConversions-2.10-ambiguity.scala +pos/t1803.scala +pos/t5877.scala +pos/t0085.scala +pos/t3582.scala +pos/t2939.scala +pos/t1422_pos.scala +pos/manifest1-new.scala +pos/t7505.scala +pos/t5720-ownerous.scala +pos/misc-unapply_pos.scala +pos/tcpoly_variance_pos.scala +pos/t5127.scala +pos/t6123-explaintypes-implicits.scala +pos/t2764 +pos/presuperContext.scala +pos/spec-simple.scala +pos/t3120 +pos/t5729.scala +pos/tcpoly_infer_ticket716.scala +pos/tcpoly_bounds1.scala +pos/t7369.scala +pos/imports-pos.scala +pos/t5654.scala +pos/t0123.scala +pos/raw-map +pos/t5330b.scala +pos/t6485b +pos/t6072.scala +pos/t5692c.scala +pos/t3430.scala +pos/tcpoly_param_scoping.scala +pos/t6204-b.scala +pos/attachments-typed-another-ident +pos/t5359.scala +pos/ticket2197.scala +pos/t720.scala +pos/t2130-2.scala +pos/t2260.scala +pos/t0304.scala +pos/t464.scala +pos/spec-maps.scala +pos/annotDepMethType.scala +pos/t6117.scala +pos/t911.scala +pos/t757a.scala +pos/t2504.scala +pos/t1381-old.scala +pos/t1232 +pos/needstypeearly.scala +pos/moduletrans.scala +pos/t4957.scala +pos/kinzer.scala +pos/t318.scala +pos/widen-existential.scala +pos/t0095.scala +pos/t566.scala +pos/tcpoly_overloaded.scala +pos/t7516 +pos/t7232 +pos/t698.scala +pos/t0002.scala +pos/t0288 +pos/t2994b.scala +pos/cls.scala +pos/t3622 +pos/t3671.scala +pos/tcpoly_subst.scala +pos/t5703 +pos/depmet_implicit_oopsla_session_simpler.scala +pos/t5022.scala +pos/builders.scala +pos/spec-foo.scala +pos/t756.scala +pos/t1569.scala +pos/implicit-unwrap-tc.scala +pos/t3688.scala +pos/t5198.scala +pos/t432.scala +pos/t6022b.scala +pos/channels.scala +pos/t1075.scala +pos/null.scala +pos/t1840 +pos/t6479.scala +pos/t6311.scala +pos/t0039.scala +pos/t1119.scala +pos/t573.scala +pos/t1136.scala +pos/t3938 +pos/spec-sealed.scala +pos/tcpoly_return_overriding.scala +pos/t3582b.scala +pos/t229.scala +pos/t3498-old.scala +pos/t531.scala +pos/t4545.scala +pos/t6651.scala +pos/t2133.scala +pos/tinondefcons.scala +pos/t6157.scala +pos/t6358.scala +pos/t7690.scala +pos/t5779-numeq-warn.scala +pos/list-extractor.scala +pos/t892.scala +pos/t2127.scala +pos/t7180.scala +pos/nullary_poly.scala +pos/virtpatmat_exist3.scala +pos/t1176 +pos/spec-funs.scala +pos/specialize10.scala +pos/t6514.scala +pos/exhaustive_heuristics.scala +pos/t0066.scala +pos/t460.scala +pos/t2130-1.scala +pos/t124.scala +pos/annotations.scala +pos/pat_gilles.scala +pos/array-interfaces.scala +pos/t6210.scala +pos/t3792.scala +pos/implicits-old.scala +pos/t389.scala +pos/t115.scala +pos/virtpatmat_exhaust_unchecked.scala +pos/scoping3.scala +pos/t6033.scala +pos/depmet_implicit_oopsla_session.scala +pos/t602.scala +pos/test5.scala +pos/t611.scala +pos/t5932.scala +pos/t4910.scala +pos/unapplySeq.scala +pos/t344.scala +pos/t3363-new.scala +pos/t4018.scala +pos/t4553.scala +pos/t5082.scala +pos/t3869.scala +pos/t3836.scala +pos/tcpoly_typeapp.scala +pos/t1409 +pos/nonlocal-unchecked.scala +pos/t0082.scala +pos/z1720.scala +pos/t7232c +pos/t2018.scala +pos/t3943 +pos/t2187-2.scala +pos/unicode-decode.scala +pos/t4757 +pos/t0710.scala +pos/t0305.scala +pos/t160.scala +pos/t7591 +pos/simplelists.scala +pos/List1.scala +pos/t516.scala +pos/t6648.scala +pos/t5165 +pos/t0055.scala +pos/t4744 +pos/t7377 +pos/t5726.scala +pos/t0091.scala +pos/t6595.scala +pos/compile.scala +pos/depmet_1_pos.scala +pos/t7364 +pos/philippe3.scala +pos/spec-doubledef-old.scala +pos/t4651.scala +pos/tcpoly_infer_implicit_tuple_wrapper.scala +pos/t6274.scala +pos/tcpoly_infer_explicit_tuple_wrapper.scala +pos/ticket2201.scala +pos/spec-fields-new.scala +pos/optmatch.scala +pos/t7517.scala +pos/t3560.scala +pos/t0165.scala +pos/t0872.scala +pos/t522.scala +pos/t2234.scala +pos/t5031_2.scala +pos/tcpoly_method.scala +pos/t6482.scala +pos/pos-bug1241.scala +pos/implicits-new.scala +pos/t2484.scala +pos/t2425.scala +pos/t1049.scala +pos/michel4.scala +pos/t5958.scala +pos/virtpatmat_instof_valuetype.scala +pos/spec-t6286.scala +pos/t873.scala +pos/t3137.scala +pos/Transactions.scala +pos/t0064.scala +pos/t7486-named.scala +pos/t5444.scala +pos/simple-exceptions.scala +pos/t1006.scala +pos/t7200b.scala +pos/t3777.scala +pos/t4840.scala +pos/t211.scala +pos/nullary.scala +pos/michel1.scala +pos/t5031_3 +pos/typealias_dubious.scala +pos/spec-doubledef-new.scala +pos/philippe1.scala +pos/thistypes.scala +pos/t3570.scala +pos/t6516.scala +pos/context.scala +pos/t3808.scala +pos/philippe2.scala +pos/constfold.scala +pos/t1292.scala +pos/t1147.scala +pos/t404.scala +pos/t4430.scala +pos/A.scala +pos/spec-partially.scala +pos/t5796.scala +pos/t2409 +pos/t284-pos.scala +pos/t5313.scala +pos/t2464 +pos/t1591b.scala +pos/hk-match +pos/t595.scala +pos/t6846.scala +pos/t6162-inheritance.scala +pos/relax_implicit_divergence.scala +pos/patterns2.scala +pos/t4692.scala +pos/t3837.scala +pos/t661.scala +pos/t2810.scala +pos/depexists.scala +pos/virtpatmat_exist4.scala +pos/t5245.scala +pos/t7190.scala +pos/isApplicableSafe.scala +pos/t6204-a.scala +pos/t0076.scala +pos/t1756.scala +pos/t1745 +pos/t6091.scala +pos/t0154.scala +pos/t530.scala +pos/t2094.scala +pos/t1034.scala +pos/t6084.scala +pos/t2454.scala +pos/t2956 +pos/tcpoly_ticket2096.scala +pos/attachments-typed-ident +pos/polymorphic-case-class.scala +pos/t252.scala +pos/spec-constr-old.scala +pos/t2421c.scala +pos/t122.scala +pos/t6574.scala +pos/t3859.scala +pos/spec-params-old.scala +pos/t1196 +pos/t4593.scala +pos/t596.scala +pos/t615.scala +pos/t7689.scala +pos/t3960.scala +pos/t3986.scala +pos/exbound.scala +pos/t2545.scala +pos/t1722 +pos/t159.scala +pos/t3272.scala +pos/t6301.scala +pos/t2794.scala +pos/t3048.scala +pos/t4970.scala +pos/t607.scala +pos/FPTest.scala +pos/test1.scala +pos/t3252.scala +pos/t4176.scala +pos/t112606A.scala +pos/t2183.scala +pos/t430-feb09.scala +pos/t6275.scala +pos/t1832.scala + +neg/volatile_no_override.scala +neg/t800.scala +neg/t5426.scala +neg/t2462a.scala +neg/t2641.scala +neg/classtags_dont_use_typetags.scala +neg/t5031 +neg/t2275b.scala +neg/macro-qmarkqmarkqmark.scala +neg/t4879.scala +neg/t5956.scala +neg/t4196.scala +neg/reify_ann2b.scala +neg/t6666b.scala +neg/warn-unused-privates.scala +neg/t6928.scala +neg/t6337.scala +neg/sealed-java-enums.scala +neg/t563.scala +neg/t900.scala +neg/deadline-inf-illegal.scala +neg/t766.scala +neg/t5429.scala +neg/overloaded-implicit.scala +neg/t875.scala +neg/abstract-class-error +neg/unchecked2.scala +neg/predef-masking.scala +neg/viewtest.scala +neg/macro-noexpand +neg/varargs.scala +neg/t963b.scala +neg/t909.scala +neg/sensitive2.scala +neg/t5390b.scala +neg/abstraction-from-volatile-type-error.scala +neg/macro-exception +neg/t4431.scala +neg/t5689.scala +neg/valueclasses.scala +neg/overload.scala +neg/t0204.scala +neg/t908.scala +neg/t750 +neg/patmatexhaust.scala +neg/macro-invalidusage-badtargs +neg/t1168.scala +neg/t5761.scala +neg/t0503.scala +neg/t7235.scala +neg/t1215.scala +neg/primitive-sigs-1 +neg/t5578.scala +neg/names-defaults-neg-warn.scala +neg/t6436b.scala +neg/t3098 +neg/t910.scala +neg/parstar.scala +neg/t4568.scala +neg/newpat_unreachable.scala +neg/warn-unused-imports.scala +neg/t1181.scala +neg/t5903c +neg/t7294.scala +neg/t4091.scala +neg/t5452-old.scala +neg/t5696.scala +neg/t0209.scala +neg/t2910.scala +neg/t7388.scala +neg/noMember2.scala +neg/no-predef.scala +neg/t6952.scala +neg/t1909b.scala +neg/abstract-report2.scala +neg/t5318.scala +neg/t6074.scala +neg/t7171.scala +neg/abstract-vars.scala +neg/unchecked-impossible.scala +neg/variances-refinement.scala +neg/t3453.scala +neg/t5189.scala +neg/t4302.scala +neg/xmltruncated7.scala + +run/t7249.scala +run/t3563.scala +run/t6111.scala +run/classtags_multi.scala +run/t5201.scala +run/checked.scala +run/valueclasses-classtag-basic.scala +run/t7171.scala +run/t5053.scala +run/t4535.scala +run/t5923d +run/t7291.scala +run/partialfun.scala +run/macro-term-declared-in-package-object +run/mapValues.scala +run/gadts.scala +run/t2386-new.scala +run/virtpatmat_stringinterp.scala +run/t657.scala +run/t0017.scala +run/t5713 +run/t576.scala +run/t3580.scala +run/virtpatmat_partial.scala +run/t6646.scala +run/mixins.scala +run/t1672.scala +run/macro-expand-implicit-macro-has-implicit +run/tuple-match.scala +run/t7039.scala +run/virtpatmat_opt_sharing.scala +run/virtpatmat_casting.scala +run/t2176.scala +run/eta-expand-star2.scala +run/macro-impl-relaxed +run/intmap.scala +run/t751.scala +run/t1591.scala +run/macro-typecheck-implicitsdisabled +run/t6911.scala +run/t5604.scala +run/macro-term-declared-in-default-param +run/collection-stacks.scala +run/multi-array.scala +run/t4560b.scala +run/buffer-slice.scala +run/t5629.scala +run/t6690.scala +run/matchonstream.scala +run/t3603.scala +run/lazy-exprs.scala +run/macro-quasiquotes +run/Course-2002-13.scala +run/t6337a.scala +run/exoticnames.scala +run/t0936.scala +run/existentials3-old.scala +run/runtime-richChar.scala +run/t6272.scala +run/t7215.scala +run/t1939.scala +run/ReverseSeqView.scala +run/lazy-leaks.scala +run/t0048.scala +run/t3994.scala +run/t2241.scala +run/t627.scala +run/t5966.scala +run/getClassTest-valueClass.scala +run/t3619.scala +run/t1300.scala +run/t2177.scala +run/t3760.scala +run/t1829.scala +run/macro-expand-implicit-macro-is-view +run/t889.scala +run/QueueTest.scala +run/t4537 +run/t3699.scala +run/valueclasses-manifest-basic.scala +run/t1192.scala +run/macro-expand-tparams-bounds +run/macro-expand-nullary-generic +run/t1434.scala +run/t6443-varargs.scala +run/macro-term-declared-in-trait +run/t4080.scala +run/t2236-old.scala +run/matcharraytail.scala +run/infiniteloop.scala +run/t5733.scala +run/virtpatmat_nested_lists.scala +run/t5158.scala +run/t6695.scala +run/t6070.scala +run/t4558.scala +run/exc2.scala +run/patmat-behavior-2.scala +run/overloads.scala +run/iterator-iterate-lazy.scala +run/t6957.scala +run/transform.scala +run/t5500.scala +run/t6663.scala +run/castsingleton.scala +run/t4147.scala +run/virtpatmat_staging.scala +run/t4565_1.scala +run/t5588.scala +run/run-bug4840.scala +run/t3496.scala +run/t5867.scala +run/search.scala +run/t3112.scala +run/hashsetremove.scala +run/interop_manifests_are_classtags.scala +run/t6443.scala +run/macro-expand-tparams-prefix +run/contrib674.scala +run/t3508.scala +run/t4300.scala +run/virtpatmat_typed.scala +run/macro-term-declared-in-class-object +run/map_test.scala +run/t5040.scala +run/t4827b.scala +run/lift-and-unlift.scala +run/t6574b.scala +run/t7240 +run/t3984.scala +run/virtpatmat_tailcalls_verifyerror.scala +run/macro-term-declared-in-class-class +run/emptypf.scala +run/t6631.scala +run/t6104.scala +run/t2818.scala +run/t3761-overload-byname.scala +run/t2526.scala +run/phantomValueClass.scala +run/t3126.scala +run/arybufgrow.scala +run/t3980.scala +run/t7375b +run/t6077_patmat_cse_irrefutable.scala +run/classmanifests_new_core.scala +run/t3395.scala +run/name-based-patmat.scala +run/inliner-infer.scala +run/t5171.scala +run/t3726.scala +run/null-hash.scala +run/t4027.scala +run/t2544.scala +run/patmatnew.scala +run/t5923b +run/t7242.scala +run/classtags_core.scala +run/streamWithFilter.scala +run/t3038b.scala +run/macro-expand-varargs-explicit-over-nonvarargs-good +run/macro-divergence-spurious +run/macro-duplicate +run/t2958.scala +run/patch-boundary.scala +run/t2333.scala +run/lazy-override-run.scala +run/macro-quasiinvalidbody-c +run/t5037.scala +run/takeAndDrop.scala +run/t6126.scala +run/t0883.scala +run/t7617a +run/t4171.scala +run/empty-array.scala +run/t7198.scala +run/t493.scala +run/genericValueClass.scala +run/t0677-old.scala +run/t1373.scala +run/t4461.scala +run/t6011b.scala +run/t7584.scala +run/t3935.scala +run/t6928-run.scala +run/t744.scala +run/t3241.scala +run/blame_eye_triple_eee-double.scala +run/t3829.scala +run/t5577.scala +run/t5914.scala +run/t601.scala +run/t5610.scala +run/macro-basic-mamd-mi +run/t6150.scala +run/stringbuilder.scala +run/t7290.scala +run/t6888.scala +run/t6327.scala +run/virtpatmat_unapplyseq.scala +run/t4656.scala +run/macro-term-declared-in-method +run/macro-expand-implicit-macro-is-implicit +run/blame_eye_triple_eee-float.scala +run/t4482.scala +run/t5488.scala +run/matchemptyarray.scala +run/t3714.scala +run/richWrapperEquals.scala +run/t5328.scala +run/stream_flatmap_odds.scala +run/implicitclasses.scala +run/t6827.scala +run/t6394b +run/complicatedmatch.scala +run/valueclasses-classmanifest-basic.scala +run/unreachable.scala +run/caseclasses.scala +run/withIndex.scala +run/exc1.scala +run/amp.scala +run/t1423.scala +run/t594.scala +run/t6353.scala +run/byname.scala +run/vector1.scala +run/t5879.scala +run/t1048.scala +run/t5080.scala +run/t4190.scala +run/caseClassEquality.scala +run/macro-enclosures +run/collections-toSelf.scala +run/implicits.scala +run/finalvar.scala +run/lazy-locals.scala +run/t7231.scala +run/t0508.scala +run/t6628.scala +run/t6406-regextract.scala +run/t0911.scala +run/t4013c.scala +run/t3502.scala +run/t5648.scala +run/retclosure.scala +run/t2857.scala +run/t4859.scala +run/t5162.scala +run/t3038.scala +run/classof.scala +run/t4062.scala +run/unapplyArray.scala +run/t4297.scala +run/t5923a +run/iterators.scala +run/t1537.scala +run/boolexprs.scala +run/valueclasses-classtag-generic.scala +run/macro-term-declared-in-anonymous +run/tcpoly_monads.scala +run/t5407.scala +run/scan.scala +run/forvaleq.scala +run/null-and-intersect.scala +run/t7047 +run/t0607.scala +run/sequenceComparisons.scala +run/t4396.scala +run/macro-undetparams-consfromsls +run/t2029.scala +run/t1220.scala +run/option-fold.scala +run/t5284c.scala +run/macro-auto-duplicate +run/t3529.scala +run/t4697.scala +run/t2251.scala +run/t5300.scala +run/virtpatmat_valdef.scala +run/t2147.scala +run/virtpatmat_extends_product.scala +run/list_map.scala +run/t1333.scala +run/matchbytes.scala +run/valueclasses-classmanifest-existential.scala +run/records.scala +run/t3088.scala +run/macro-def-path-dependent +run/t6443-by-name.scala +run/t1044.scala +run/delay-good.scala +run/case-class-23.scala +run/weakconform.scala +run/patmat-bind-typed.scala +run/t4835.scala +run/t3097.scala +run/t405.scala +run/existentials.scala +run/t2876.scala +run/t4809.scala +run/t1427.scala +run/t6135.scala +run/t3575.scala +run/t5688.scala +run/t6900.scala +run/macro-expand-unapply-a +run/t6677b.scala +run/t7375a.scala +run/t7300.scala +run/t6246.scala +run/typed-annotated +run/elidable-noflags.scala +run/t0042.scala +run/t3050.scala +run/t4536.scala +run/NestedClasses.scala +run/t3877.scala +run/seqlike-kmp.scala +run/t5907.scala +run/t266.scala +run/missingparams.scala +run/t2255.scala +run/private-inline.scala +run/t3488.scala +run/t3950.scala +run/typealias_overriding.scala +run/constant-optimization.scala +run/t7507.scala +run/t6090.scala +run/iterator-concat.scala +run/t4582.scala +run/macro-term-declared-in-class +run/macro-typecheck-macrosdisabled2 +run/t3425.scala +run/t4935.scala +run/t3326.scala +run/boolord.scala +run/t1141.scala +run/virtpatmat_unapply.scala +run/t5971.scala +run/t3651.scala +run/macro-sip19-revised +run/pure-args-byname-noinline.scala +run/preinits.scala +run/t5532.scala +run/concat-two-strings.scala +run/t3269.scala +run/macro-impl-default-params +run/t2162.scala +run/matchonseq.scala +run/t5428.scala +run/macro-expand-overload +run/t4660.scala +run/enrich-gentraversable.scala +run/macro-expand-override +run/t4054.scala +run/t4753.scala +run/valueclasses-manifest-generic.scala +run/macro-typecheck-macrosdisabled +run/t2308a.scala +run/duplicate-meth.scala +run/interop_classtags_are_classmanifests.scala +run/t3232.scala +run/t2075.scala +run/virtpatmat_partial_backquoted.scala +run/try-2.scala +run/macro-openmacros +run/macro-undetparams-macroitself +run/t6318_derived.scala +run/deprecate-early-type-defs.scala +run/dead-code-elimination.scala +run/t4827.scala +run/Course-2002-07.scala +run/slice-strings.scala +run/t6292.scala +run/t6206.scala +run/t1042.scala +run/t1718.scala +run/t2074_2.scala +run/arraycopy.scala +run/indexedSeq.scala +run/macro-term-declared-in-implicit-class +run/t3511.scala +run/t6290.scala +run/distinct.scala +run/virtpatmat_alts.scala +run/valueclasses-pavlov.scala +run/exceptions.scala +run/t1368.scala +run/t5856.scala +run/t6968.scala +run/names-defaults.scala +run/macro-expand-tparams-implicit +run/t5881.scala +run/t3540.scala +run/virtpatmat_try.scala +run/t7181.scala +run/value-class-extractor.scala +run/value-class-extractor-2.scala +run/t3150.scala +run/exc.scala +run/t3516.scala +run/delay-bad.scala +run/infix.scala +run/t1309.scala +run/t6370.scala +run/t6725-2.scala +run/macro-impl-tparam-typetag-is-optional +run/macro-term-declared-in-block +run/matchnull.scala +run/t2127.scala +run/t7325.scala +run/groupby.scala +run/t3932.scala +run/t4871.scala +run/longmap.scala +run/t1524.scala +run/t6187b.scala +run/kmpSliceSearch.scala +run/t7088.scala +run/t5804.scala +run/stringbuilder-drop.scala +run/t5753_1 +pos/cyclics-pos.scala +pos/cfcrash.scala +pos/tcpoly_higherorder_bound_method.scala +pos/t5084.scala +pos/trait-force-info.scala +pos/macro-qmarkqmarkqmark.scala +pos/t7785.scala +pos/nested.scala +pos/t3152.scala +pos/t5031 +pos/t6925b.scala +pos/t1107b +pos/t5012.scala +pos/virtpatmat_obj_in_case.scala +pos/t4938.scala +pos/t3856.scala +pos/spec-cyclic.scala +pos/aliases.scala +pos/typerep_pos.scala +pos/t119.scala +pos/t1050.scala +pos/t3670.scala +pos/t6145.scala +pos/t7315.scala +pos/t5930.scala +pos/t789.scala +pos/t5071.scala +pos/t4731.scala +pos/t4547.scala +pos/t2038.scala +pos/testCoercionThis.scala +pos/t2444.scala +pos/t5744 +pos/t780.scala +pos/t1722-A.scala +pos/virtpatmat_exist1.scala +pos/t6225.scala +pos/t762.scala +pos/t0204.scala +pos/rebind.scala +pos/spec-short.scala +pos/comp-rec-test.scala +pos/lub-dealias-widen.scala +pos/t1168.scala +pos/modules.scala +pos/t4220.scala +pos/t4070.scala +pos/t175.scala +pos/t2500.scala +pos/t5029.scala +pos/itay.scala +pos/t4202.scala +pos/t1987b +pos/t3534.scala +pos/infer2-pos.scala +pos/spec-sparsearray-new.scala +pos/t7091.scala +pos/ticket0137.scala +pos/collectGenericCC.scala +pos/t640.scala +pos/t4305.scala +pos/extractor-types.scala +pos/t3880.scala +pos/spec-annotations.scala +pos/t3577.scala +pos/compile1.scala +pos/spec-t3497.scala +pos/hkrange.scala +pos/t287.scala +pos/t7294.scala +pos/t6008.scala +pos/t4432.scala +pos/CustomGlobal.scala +pos/patmat.scala +pos/t2413 +pos/t2910.scala +pos/t592.scala +pos/t6245 +pos/infer.scala +pos/t7228.scala +pos/compound.scala +pos/attributes.scala +pos/t6771.scala +pos/t1090.scala +pos/t684.scala +pos/t577.scala +pos/t4273.scala +pos/t6278-synth-def.scala +pos/t6184.scala +neg/t0214.scala +neg/t4842.scala +neg/t6214.scala +neg/reify_nested_inner_refers_to_local.scala +neg/t576.scala +neg/t5969.scala +neg/tcpoly_variance.scala +neg/t7509.scala +neg/mixins.scala +neg/parent-inherited-twice-error.scala +neg/macro-abort +neg/constructor-init-order.scala +neg/t6042.scala +neg/t0590.scala +neg/eta-expand-star-deprecation.scala +neg/t4221.scala +neg/t6263.scala +neg/t783.scala +neg/t5554.scala +neg/macro-invalidsig-params-badtype +neg/multi-array.scala +neg/raw-types-stubs +neg/spec-overrides.scala +neg/t836.scala +neg/t7289_status_quo.scala +neg/t5675.scala +neg/macro-quasiquotes +neg/t6667.scala +neg/t6597.scala +neg/t6264.scala +neg/t0345.scala +neg/t7294b.scala +neg/t5340.scala +neg/t2144.scala +neg/t1010.scala +neg/t1838.scala +neg/t5189b.scala +neg/reify_metalevel_breach_-1_refers_to_1.scala +neg/t6601 +neg/wellkinded_wrongarity.scala +neg/t3909.scala +neg/t876.scala +neg/t5390.scala +neg/unit2anyref.scala +neg/t0351.scala +neg/t5120.scala +neg/t1038.scala +neg/t5878.scala +neg/qualifying-class-error-2.scala +neg/t3816.scala +neg/tailrec.scala +neg/volatile.scala +neg/t944.scala +neg/t1705.scala +neg/t3977.scala +neg/t5553_2.scala +neg/t5318c.scala +neg/overload-msg.scala +neg/t5440.scala +neg/t6335.scala +neg/compile-time-only-b.scala +neg/t501.scala +neg/override.scala +neg/t663.scala +neg/t5892.scala +neg/t1980.scala +neg/macro-false-deprecation-warning +neg/t5148.scala +neg/t585.scala +neg/t3776.scala +neg/interop_classtags_arenot_manifests.scala +neg/t4044.scala +neg/macro-invalidusage-nontypeable +neg/t6375.scala +neg/t500.scala +neg/t4877.scala +neg/t5357.scala +neg/interop_abstypetags_arenot_manifests.scala +neg/t4460a.scala +neg/t5318b.scala +neg/t3234.scala +neg/t4440.scala +neg/t6663.scala +neg/t6357.scala +neg/gadts1.scala +neg/cyclics.scala +neg/t5060.scala +neg/scopes.scala +run/t4013.scala +run/value-class-extractor-seq.scala +run/macro-expand-tparams-explicit +run/tuples.scala +run/t5753_2 +run/t0528.scala +run/t5105.scala +run/t1195-old.scala +run/t7341.scala +run/t3670.scala +run/t2594_tcpoly.scala +run/t3895.scala +run/t0668.scala +run/slices.scala +run/t6666a.scala +run/valueclasses-classmanifest-generic.scala +run/t2316_run.scala +run/t3004.scala +run/viewtest.scala +run/t6481.scala +run/t0005.scala +run/t4110-old.scala +run/t4766.scala +run/t5500b.scala +run/t7407b.scala +run/backreferences.scala +run/arrayview.scala +run/t629.scala +run/t5903c +run/unittest_collection.scala +run/spec-nlreturn.scala +run/macro-term-declared-in-object-object +run/triple-quoted-expr.scala +run/t5937.scala +run/t6011c.scala +run/macro-expand-implicit-argument +run/try.scala +run/t1987b +run/t6089.scala +run/macro-range +run/t2524.scala +run/t4770.scala +run/virtpatmat_unapplyprod.scala +run/t1535.scala +run/ctor-order.scala +pos/t5210.scala +pos/t5384.scala +pos/rangepos.scala +pos/t443.scala +pos/t1480.scala +pos/t116.scala +pos/seqtest2.scala +pos/scoping1.scala +pos/t4269.scala +pos/lookupswitch.scala +pos/t3642 +pos/t5706.scala +pos/SI-5788.scala +pos/t7264 +pos/t0031.scala +pos/macro-deprecate-dont-touch-backquotedidents.scala +pos/t6815.scala +pos/test4refine.scala +pos/michel5.scala +pos/t0851.scala +pos/t1185.scala +pos/sudoku.scala +pos/t7520.scala +pos/t6208.scala +pos/t3411.scala +pos/t295.scala +pos/S3.scala +pos/t0674.scala +pos/t6664b.scala +pos/variances_pos.scala +pos/liftcode_polymorphic.scala +pos/t3174b.scala +pos/t7232d +pos/t578.scala +pos/implicit-infix-ops.scala +pos/t4363.scala +pos/t532.scala +pos/exponential-spec.scala +pos/t599.scala +pos/t5862.scala +pos/t4603 +pos/t3676.scala +pos/t1357.scala +pos/native-warning.scala +pos/t1230 +pos/t6028 +pos/t4275.scala +pos/overloaded_extractor_and_regular_def.scala +pos/t4205 +pos/matthias1.scala +pos/testcast.scala +pos/generic-sigs.scala +pos/t0093.scala +pos/specializes-sym-crash.scala +pos/t0061.scala +pos/t2429.scala +pos/t694.scala +pos/javaReadsSigs +pos/t2023.scala +pos/t704.scala +pos/t2208_pos.scala +pos/t5137.scala +pos/t2683.scala +pos/t0049.scala +pos/t1029 +pos/t4243.scala +pos/typerep-stephane.scala +pos/t177.scala +pos/t5967.scala +pos/t430.scala +pos/virtpatmat_infer_single_1.scala +pos/pat_iuli.scala +pos/t1071.scala +pos/t7226.scala +pos/t1843.scala +pos/t419.scala +pos/t7364b +pos/t1159.scala +pos/t5305.scala +pos/t7694.scala +pos/t6047.scala +pos/t3578.scala +pos/t2082.scala +pos/setter-not-implicit.scala +pos/t1133.scala +pos/t3862.scala +pos/t942 +pos/nothing_manifest_disambig-new.scala +pos/iterator-traversable-mix.scala +pos/eta.scala +pos/test4.scala +pos/t2691.scala +pos/t4502.scala +pos/t7183.scala +pos/protected-t1010.scala +pos/X.scala +pos/virtpatmat_exist2.scala +pos/t4911.scala +pos/t3477.scala +pos/t4173.scala +pos/t7782.scala +pos/t2399.scala +pos/virtpatmat_alts_subst.scala +pos/propagate.scala +pos/t2421b_pos.scala +pos/t183.scala +pos/t7033.scala +pos/t3612.scala +pos/t5330c.scala +pos/t3020.scala +pos/t4869.scala +pos/t3373.scala +pos/spec-params-new.scala +pos/t3672.scala +pos/t4501.scala +pos/t1565.scala +pos/t3774.scala +pos/t6942 +neg/t3275.scala +neg/t421.scala +neg/t5702-neg-bad-brace.scala +neg/t3663 +neg/badtok-1.scala +neg/t677.scala +neg/t7756b.scala +neg/t6534.scala +neg/t6276.scala +neg/t5762.scala +neg/abstract.scala +neg/t2405.scala +neg/t0418.scala +neg/t5390c.scala +neg/lazyvals.scala +neg/lubs.scala +neg/abstract-report.scala +neg/t4163.scala +neg/t5702-neg-bad-and-wild.scala +neg/macro-invalidret +neg/t6728.scala +neg/t5152.scala +neg/t1432.scala +neg/abstract-inaccessible.scala +neg/import-precedence.scala +neg/t2462b.scala +neg/macro-invalidusage-presuper +neg/specification-scopes +neg/t6048.scala +neg/t4079 +neg/macro-basic-mamdmi +neg/t7020.scala +neg/t3015.scala +neg/t0207.scala +neg/t2296b +neg/t0673 +neg/t3761-overload-byname.scala +neg/t6675.scala +neg/t5529.scala +neg/sensitive.scala +neg/t742.scala +neg/t5067.scala +neg/t6162-overriding.scala +neg/variances.scala +neg/t5728.scala +neg/t6323a.scala +neg/compile-time-only-a.scala +neg/t6795.scala +neg/t2494.scala +neg/t3649.scala +neg/macro-invalidsig +neg/t2796.scala +neg/t112706A.scala +neg/t0764.scala +neg/t3757 +neg/t1431.scala +neg/exhausting.scala +neg/t1523.scala +neg/t779.scala +neg/xmltruncated1.scala +neg/t2208.scala +neg/t2078.scala +neg/t521.scala +neg/null-unsoundness.scala +neg/stmt-expr-discard.scala +neg/t0513.scala +neg/unchecked-abstract.scala +neg/t4460c.scala +neg/divergent-implicit.scala +neg/t5078.scala +neg/t1701.scala +neg/t0816.scala +neg/t1672b.scala +neg/macro-invalidusage-badbounds +neg/tailrec-2.scala +neg/t4064.scala +neg/reflection-names-neg.scala +neg/t5510.scala +neg/t3873.scala +neg/tailrec-3.scala +neg/t0226.scala +neg/t2031.scala +neg/t633.scala +neg/constrs.scala +neg/anyval-anyref-parent.scala +neg/t7290.scala +neg/t1041.scala +neg/patternalts.scala +neg/error_tooManyArgsPattern.scala +neg/checksensibleUnit.scala +neg/t6539 +neg/t4417.scala +neg/wellkinded_app.scala +neg/for-comprehension-old.scala +neg/t2779.scala +neg/object-not-a-value.scala +neg/t2968b.scala +neg/t6483.scala +neg/t6902.scala +neg/t6963a.scala +neg/t3399.scala +neg/t0015.scala +neg/t3995.scala +neg/t276.scala +neg/t6758.scala +neg/t2441.scala +neg/cycle-bounds.scala +neg/t1241.scala +neg/t4137.scala +neg/unicode-unterminated-quote.scala +neg/t4762.scala +neg/typeerror.scala +neg/implicits.scala +neg/t961.scala +neg/ambiguous-float-dots2.scala +neg/t2416.scala +neg/t5799.scala +neg/t7285.scala +neg/implicit-shadow.scala +neg/t2388.scala +neg/java-access-neg +neg/found-req-variance.scala +neg/hk-bad-bounds.scala +neg/t3224.scala +neg/t1033.scala +neg/t7385.scala +neg/t5882.scala +neg/t4541.scala +neg/t2973.scala +neg/t6406-regextract.scala +neg/t6666.scala +neg/t4831.scala +neg/t425.scala +neg/t1845.scala +neg/t3683b.scala +neg/t2801.scala +neg/t6083.scala +neg/t0528neg.scala +neg/stringinterpolation_macro-neg.scala +neg/t668.scala +neg/t5666.scala +neg/t4271.scala +neg/interop_typetags_arenot_classmanifests.scala +neg/t1355.scala +neg/t715.scala +neg/t7238.scala +neg/t7473.scala +neg/t7292-removal.scala +neg/tcpoly_infer_ticket1162.scala +neg/t4098.scala +neg/t6013 +neg/t6227.scala +neg/t464-neg.scala +neg/badtok-3.scala +neg/t6082.scala +neg/anytrait.scala +neg/valueclasses-doubledefs.scala +neg/t7519.scala +neg/overloaded-unapply.scala +neg/t1163.scala +neg/wellkinded_bounds.scala +neg/t7292-deprecation.scala +neg/t5044.scala +neg/t0842.scala +neg/t6436.scala +neg/interop_typetags_arenot_classtags.scala +neg/t3653.scala +neg/higherkind_novalue.scala +neg/t935.scala +neg/t6040.scala +neg/annot-nonconst.scala +neg/macro-deprecate-idents.scala +neg/illegal-stmt-start.scala +neg/t565.scala +neg/case-collision.scala +neg/t3209.scala +neg/t5821.scala +neg/abstract-class-2.scala +neg/t846.scala +neg/quasiquotes-syntax-error-position.scala +neg/t3987.scala +neg/t877.scala +neg/t0117.scala +neg/t692.scala +neg/t6666d.scala +neg/t5702-neg-ugly-xbrace.scala +neg/t7752.scala +neg/case-collision2.scala +neg/t6526.scala +neg/t2213.scala +neg/t7756a.scala +neg/t845.scala +neg/macro-override-macro-overrides-abstract-method-a +neg/tcpoly_ticket2101.scala +neg/delayed-init-ref.scala +neg/caseinherit.scala +neg/t3189.scala +neg/unchecked-suppress.scala +neg/t2180.scala +neg/t1371.scala +neg/macro-cyclic +neg/t6123-explaintypes-macros +neg/t4134.scala +neg/t691.scala +neg/t2421b.scala +neg/t4691_exhaust_extractor.scala +neg/t4419.scala +neg/t5801.scala +neg/t650.scala +neg/t5735.scala +neg/t696.scala +neg/t882.scala +neg/t2968.scala +neg/t7507.scala +neg/macro-invalidusage-badargs +neg/macro-reify-typetag-typeparams-notags +neg/wellkinded_app2.scala +neg/t4425b.scala +neg/t2296a +neg/t1878.scala +neg/t649.scala +neg/override-object-no.scala +neg/t4174.scala +neg/t2070.scala +neg/sabin2.scala +neg/t5903e +neg/t6566a.scala +neg/finitary-error.scala +neg/t4818.scala +neg/t3614.scala +neg/t6666c.scala +neg/ticket513.scala +neg/suggest-similar.scala +neg/t4457_1.scala +neg/t6666e.scala +neg/tcpoly_bounds.scala +neg/t4727.scala +neg/t4425.scala +neg/macro-invalidusage-methodvaluesyntax +neg/t3854.scala +neg/t3006.scala +neg/t5580b.scala +neg/t5378.scala +neg/t639.scala +neg/wrong-args-for-none.scala +neg/t7171b.scala +neg/t5361.scala +neg/unreachablechar.scala +neg/t5572.scala +neg/t7757a.scala +neg/macro-invalidimpl +neg/t2773.scala +neg/t6359.scala +neg/saito.scala +neg/xmltruncated2.scala +neg/t667.scala +neg/t3934.scala +neg/t6771b.scala +neg/t4584.scala +neg/wellkinded_wrongarity2.scala +neg/t7369.scala +neg/t1477.scala +neg/t5617.scala +neg/t7299.scala +neg/faculty.scala +neg/virtpatmat_reach_null.scala +neg/macro-reify-typetag-hktypeparams-notags +neg/t1224.scala +neg/xmltruncated3.scala +neg/t1872.scala +neg/t558.scala +neg/t7110.scala +neg/any-vs-anyref.scala +neg/t6340.scala +neg/t4166.scala +neg/t2918.scala +neg/t5856.scala +neg/t4989.scala +neg/t0003.scala +neg/t1183.scala +neg/t963.scala +neg/t4515.scala +neg/valueclasses-pavlov.scala +neg/t608.scala +neg/choices.scala +neg/patmat-type-check.scala +neg/valueclasses-impl-restrictions.scala +neg/imp2.scala +neg/protected-constructors.scala +neg/t6788.scala +neg/nullary-override.scala +neg/t200.scala +neg/t343.scala +neg/names-defaults-neg-ref.scala +neg/tcpoly_typealias.scala +neg/classtags_contextbound_b.scala +neg/t729.scala +neg/t5683.scala +neg/t4928.scala +neg/t700.scala +neg/t7669.scala +neg/macro-invalidshape +neg/t6011.scala +neg/t7325.scala +neg/check-dead.scala +neg/t550.scala +neg/t5663-badwarneq.scala +neg/t0699 +neg/nopredefs.scala +neg/t3507-old.scala +neg/t5352.scala +neg/t6336.scala +neg/interop_classmanifests_arenot_typetags.scala +neg/sealed-final-neg.scala +neg/t2102.scala +neg/t7636.scala +neg/t5031b +neg/t798.scala +neg/t5702-neg-bad-xbrace.scala +neg/t0899.scala +neg/cyclics-import.scala +neg/badtok-2.scala +neg/t473.scala +neg/t3160ambiguous.scala +neg/t5106.scala +neg/t1286 +neg/macro-override-macro-overrides-abstract-method-b +neg/t0259.scala +neg/t510.scala +neg/t3836.scala +neg/t5830.scala +neg/t1548 +neg/t5580a.scala +neg/forward.scala +neg/t591.scala +neg/t6558b.scala +neg/t556.scala +neg/xmltruncated4.scala +neg/t5497.scala +neg/t409.scala +neg/t6283.scala +neg/override-object-flag.scala +neg/constructor-prefix-error.scala +neg/eta-expand-star.scala +neg/t3392.scala +neg/t1275.scala +neg/nested-fn-print.scala +neg/t7330.scala +neg/t2275a.scala +neg/t630.scala +neg/t4270.scala +neg/t2775.scala +neg/pat_unreachable.scala +neg/t4158.scala +neg/unit-returns-value.scala +neg/t1422.scala +neg/reify_metalevel_breach_-1_refers_to_0_b.scala +neg/reassignment.scala +neg/t3683a.scala +neg/noMember1.scala +neg/macro-without-xmacros-b +neg/t1106.scala +neg/t5182.scala +neg/t6889.scala +neg/t4217.scala +neg/t7501 +neg/t5063.scala +neg/t1009.scala +neg/t997.scala +neg/unchecked.scala +neg/classtags_contextbound_c.scala +neg/applydynamic_sip.scala +neg/t7715.scala +neg/t588.scala +neg/t6667b.scala +neg/t7757b.scala +neg/t4069.scala +neg/t515.scala +neg/variances2.scala +neg/t1049.scala +neg/t7289.scala +neg/t1623.scala +neg/permanent-blindness.scala +neg/t5803.scala +neg/super-cast-or-test.scala +neg/nonlocal-warning.scala +neg/t5687.scala +neg/t5903a +neg/t6566b.scala +neg/unchecked-knowable.scala +neg/t5093.scala +neg/protected-static-fail +neg/type-diagnostics.scala +neg/forgot-interpolator.scala +neg/interop_abstypetags_arenot_classmanifests.scala +neg/t5376.scala +neg/t545.scala +neg/xmlcorner.scala +neg/switch.scala +neg/depmet_1.scala +neg/abstract-concrete-methods.scala +neg/t4987.scala +neg/t5452-new.scala +neg/t750b +neg/unchecked-refinement.scala +neg/t418.scala +neg/t5354.scala +neg/t3736.scala +neg/t631.scala +neg/t6829.scala +neg/t0218.scala +neg/volatile-intersection.scala +neg/t412.scala +neg/t693.scala +neg/t4882.scala +neg/t1960.scala +neg/macro-divergence-controlled +neg/t712.scala +neg/t5544 +neg/t3222.scala +neg/t3604.scala +neg/t1112.scala +neg/t7157 +neg/accesses.scala +neg/t452.scala +neg/t6162-inheritance +neg/t2442 +neg/t6567.scala +neg/lazy-override.scala +neg/abstract-explaintypes.scala +neg/nested-annotation.scala +neg/t5753 +neg/t4283b +neg/t3691.scala +neg/infix-op-positions.scala +neg/t3403.scala +neg/t4851 +neg/structural.scala +neg/error_dependentMethodTpeConversionToFunction.scala +neg/t5839.scala +neg/t5553_1.scala +neg/reify_metalevel_breach_+0_refers_to_1.scala +neg/t752.scala +neg/t6574.scala +neg/t3714-neg.scala +neg/t4457_2.scala +neg/t2148.scala +neg/t3240.scala +neg/t1364.scala +neg/saferJavaConversions.scala +neg/t414.scala +neg/t5493.scala +neg/classtags_contextbound_a.scala +neg/reify_metalevel_breach_-1_refers_to_0_a.scala +neg/t3118.scala +neg/t512.scala +neg/t2336.scala +neg/t856.scala +neg/xmltruncated6.scala +neg/t2206.scala +neg/virtpatmat_unreach_select.scala +neg/t6258.scala +neg/t6815.scala +neg/not-possible-cause.scala +neg/dbldef.scala +neg/qualifying-class-error-1.scala +neg/t835.scala +neg/t5455.scala +neg/t6558.scala +neg/t708.scala +neg/macro-nontypeablebody +neg/t0565.scala +neg/xmltruncated5.scala +neg/t5390d.scala +neg/t520.scala +neg/t6138.scala +neg/macro-without-xmacros-a +neg/t7214neg.scala +neg/t2870.scala +neg/t593.scala +neg/t4541b.scala +neg/t4460b.scala +neg/t284.scala +neg/t2488.scala +neg/macro-override-method-overrides-macro +neg/interop_abstypetags_arenot_classtags.scala +neg/t3769.scala +neg/warn-inferred-any.scala +neg/t664.scala +neg/t5903d +neg/t562.scala +neg/t2316.scala +neg/t0152.scala +neg/migration28.scala +neg/t6443c.scala +neg/tcpoly_override.scala +neg/t7324.scala +neg/t987.scala +neg/t5903b +neg/t3481.scala +neg/t6912.scala +neg/tcpoly_variance_enforce.scala +neg/t3913.scala +neg/names-defaults-neg.scala +neg/t765.scala +neg/t5358.scala +neg/t391.scala +neg/serialversionuid-not-const.scala +neg/t771.scala +neg/t0903.scala +neg/catch-all.scala +neg/classmanifests_new_deprecations.scala +neg/t0606.scala +neg/t5189_inferred.scala +neg/macro-reify-typetag-useabstypetag +neg/t5543.scala +neg/logImplicits.scala +neg/interop_typetags_without_classtags_arenot_manifests.scala +neg/t6535.scala +neg/t7259.scala +neg/t2139.scala +neg/t278.scala +neg/t5564.scala +neg/unchecked3.scala +neg/virtpatmat_reach_sealed_unsealed.scala +neg/checksensible.scala +neg/t7721.scala +run/t3798.scala +run/macro-expand-varargs-explicit-over-varargs +run/t3888.scala +run/t0677-new.scala +run/t3273.scala +run/t3763.scala +run/t2755.scala +run/t920.scala +run/t5610a.scala +run/literals.scala +run/proxy.scala +run/unapply.scala +run/t5830.scala +run/array-addition.scala +run/macro-expand-nullary-nongeneric +run/macro-basic-ma-mdmi +run/valueclasses-constr.scala +run/t1247.scala +run/t3487.scala +run/rawstrings.scala +run/patmat-seqs.scala +run/eta-expand-star.scala +run/t7436.scala +run/t3996.scala +run/constructors.scala +run/t498.scala +run/t3835.scala +run/t298.scala +run/t2867.scala +run/t7120 +run/virtpatmat_literal.scala +run/t2175.scala +run/t2503.scala +run/t3026.scala +run/t603.scala +run/t0091.scala +run/t6394a +run/macro-expand-varargs-implicit-over-varargs +run/t7407.scala +run/t2552.scala +run/priorityQueue.scala +run/virtpatmat_npe.scala +run/macro-sip19 +run/t6644.scala +run/t6614.scala +run/t2005.scala +run/t4680.scala +run/t5903a +run/classtags_contextbound.scala +run/Course-2002-05.scala +run/applydynamic_sip.scala +run/t1766.scala +run/retsynch.scala +run/t7715.scala +run/t102.scala +run/nonlocalreturn.scala +run/macro-reify-staticXXX +run/Course-2002-06.scala +run/t6863.scala +run/t6500.scala +run/macro-impl-rename-context +run/t4351.scala +run/t5009.scala +run/macro-term-declared-in-annotation +run/t6271.scala +run/array-existential-bound.scala +run/t6443b.scala +run/t1987.scala +run/MutableListTest.scala +run/t7571.scala +run/t5488-fn.scala +run/macro-bodyexpandstoimpl +run/macro-reify-ref-to-packageless +run/t2212.scala +run/macro-expand-varargs-implicit-over-nonvarargs +run/t0807.scala +run/patmat-behavior.scala +run/t2446.scala +run/tuple-zipped.scala +run/breakout.scala +run/t4122.scala +run/macro-settings +run/t7157 +run/t1323.scala +run/t4013b.scala +run/t6309.scala +run/t4047.scala +run/t5544 +run/t978.scala +run/t3361.scala +run/t6611.scala +run/t5387.scala +run/t5656.scala +run/t4897.scala +run/numeric-range.scala +run/t4777.scala +run/Course-2002-03.scala +run/string-extractor.scala +run/view-headoption.scala +run/patmat_unapp_abstype-new.scala +run/stream-stack-overflow-filter-map.scala +run/macro-impl-tparam-only-in-impl +run/t6559.scala +run/macro-reify-tagful-a +run/macro-expand-multiple-arglists +run/t4709.scala +run/t3509.scala +run/t5284b.scala +run/t7617b +run/t3923.scala +run/virtpatmat_apply.scala +run/t363.scala +run/manifests-undeprecated-in-2.10.0.scala +run/matchintasany.scala +run/t3970.scala +run/t4996.scala +run/t5530.scala +run/macro-term-declared-in-object-class +run/t3242b.scala +run/indexedSeq-apply.scala +run/t107.scala +run/t2337.scala +run/t3758-old.scala +run/t2754.scala +run/valueclasses-manifest-existential.scala +run/flat-flat-flat.scala +run/t6673.scala +run/interpolationMultiline2.scala +run/t3493.scala +run/t0631.scala +run/t2800.scala +run/t6506.scala +run/t6260.scala +run/t2418.scala +run/t4415.scala +run/classmanifests_new_alias.scala +run/t5380.scala +run/tcpoly_parseridioms.scala +run/t1747.scala +run/t5903d +run/t3530.scala +run/t216.scala +run/macro-term-declared-in-refinement +run/t4592.scala +run/t2488.scala +run/t3327.scala +run/t5614.scala +run/t5903b +run/iterables.scala +run/t3964.scala +run/t6329_vanilla.scala +run/t3038c +run/t1697.scala +run/t2030.scala +run/t3397.scala +run/t1005.scala +run/t3353.scala +run/t1466.scala +run/t3186.scala +run/tcpoly_overriding.scala +run/t5394.scala +run/t5284.scala +run/unboxingBug.scala +run/t7200.scala +run/macro-reify-basic +run/t153.scala +run/iterator3444.scala +run/macro-expand-implicit-macro-is-val +run/macro-basic-ma-md-mi +run/interpolationArgs.scala +run/t4954.scala +run/t3645.scala +run/transpose.scala +run/t3887.scala +run/t4288.scala +run/unittest_iterator.scala +run/t5543.scala +run/macro-term-declared-in-object +run/iq.scala +run/t2788.scala +run/t2027.scala +run/macro-expand-recursive +run/t949.scala +run/t1909b.scala +run/delambdafy-nested-by-name.scala +run/delambdafy-two-lambdas.scala +run/macro-blackbox-materialization +run/lists-run.scala +run/macro-parse-position +run/macro-parse-position-malformed +run/macro-whitebox-dynamic-materialization +run/macro-whitebox-extractor +run/macro-vampire-false-warning +run/macro-whitebox-fundep-materialization +run/macro-whitebox-structural +run/mutable-treeset.scala +run/static-module-method.scala +run/sort.scala +run/t1909.scala +run/t1909c.scala +run/t3346a.scala +run/t3346d.scala +run/t3346f.scala +run/t3346h.scala +run/t3346g.scala +run/t3832.scala +run/t4742.scala +run/t5377.scala +run/t5923c.scala +run/t6188.scala +run/t6333.scala +run/t6385.scala +run/t7899.scala +run/t7899-regression.scala +run/t7584b.scala +run/t7223.scala +run/t7859 +run/t7868.scala +run/t7871 +run/arrayclone-new.scala +run/arrayclone-old.scala +run/bitsets.scala +run/comparable-comparator.scala +run/colltest1.scala +run/t2106.scala +run/t5986.scala +run/view-iterator-stream.scala +run/array-charSeq.scala +pos/signatures +pos/t1263 +pos/t3249 +neg/t4749.scala +neg/main1.scala +neg/t7251 +neg/t7494-after-terminal +neg/t7494-before-parser +neg/t7494-right-after-terminal +run/lazy-traits.scala +run/OrderingTest.scala +run/ReplacementMatching.scala +run/patmat-finally.scala +run/t3158.scala +run/t3346e.scala +run/t4398.scala +run/t4930.scala +run/t6534.scala +pos/sammy_scope.scala +pos/delambdafy-patterns.scala +pos/private-types-after-typer.scala +pos/delambdafy-lambdalift.scala +pos/sammy_poly.scala +pos/sammy_single.scala +pos/SI-4012-b.scala +pos/sammy_twice.scala +pos/t3160.scala +pos/t1014.scala +pos/t4970b.scala +pos/t2698.scala +pos/t5845.scala +pos/t6201.scala +pos/t6260a.scala +pos/t7688.scala +pos/t7818.scala +pos/t1203a.scala +pos/t7834.scala +pos/t7853.scala +pos/t7815.scala +pos/t7853-partial-function.scala +pos/t7864.scala +pos/t7928.scala +pos/t7902.scala +pos/t7944.scala +pos/t7847 +neg/accesses2.scala +neg/bad-advice.scala +neg/gadts2.scala +neg/gadts2-strict.scala +neg/macro-bundle-abstract.scala +neg/macro-bundle-object.scala +neg/macro-bundle-trait.scala +neg/macro-blackbox-dynamic-materialization +neg/macro-blackbox-extractor +neg/run-gadts-strict.scala +neg/macro-blackbox-structural +neg/sammy_restrictions.scala +neg/sammy_wrong_arity.scala +neg/t2462c.scala +neg/t3346b.scala +neg/t1909-object.scala +neg/macro-blackbox-fundep-materialization +neg/t3346c.scala +neg/t3871.scala +neg/t3871b.scala +neg/t3971.scala +neg/t3346i.scala +neg/t6120.scala +neg/t6260c.scala +neg/t6680a.scala +neg/t7239.scala +neg/t7007.scala +neg/t7605-deprecation.scala +neg/t7622-missing-required.scala +neg/t7629-view-bounds-deprecation.scala +neg/t7834neg.scala +neg/t7783.scala +neg/t7848-interp-warn.scala +neg/t7519-b +neg/t7622-missing-dependency +neg/t7870.scala +neg/t7877.scala +neg/t7895.scala +neg/t7895b.scala +neg/t7899.scala +neg/t7895c.scala +neg/t7859 +run/t4752.scala +run/t2087-and-2400.scala +run/t3855.scala +run/t6637.scala +run/t6731.scala +pos/t3999b.scala +run/t0432.scala +run/t2514.scala +run/t7817.scala +run/t874.scala +run/type-currying.scala +run/t3616.scala +run/t3687.scala +run/t4570.scala +run/t5612.scala +run/t1110.scala +run/t2636.scala +run/verify-ctor.scala +run/t3647.scala +run/t4560.scala +run/t6632.scala +run/hashCodeBoxesRunTime.scala +run/richs.scala +run/t6725-1.scala +pos/t7776.scala +run/fors.scala +run/t6706.scala +run/t3175.scala +run/delambdafy-dependent-on-param-subst.scala +run/t4332b.scala +run/t8048a +run/t8017 +run/t7985b.scala +run/t8100.scala +run/patmat-mix-case-extractor.scala +run/t4750.scala +run/t7912.scala +run/delambdafy-dependent-on-param-subst-2.scala +run/t8048b +run/t8091.scala +run/macroPlugins-macroRuntime +run/macro-default-params +run/t6355.scala +run/t7777 +run/t8002.scala +run/t8015-ffc.scala +run/macro-subpatterns +run/t7985.scala +run/macroPlugins-macroArgs +run/t7326.scala +run/t5045.scala +run/value-class-partial-func-depmet.scala +run/t6329_vanilla_bug.scala +run/macroPlugins-macroExpand +run/t8010.scala +run/macroPlugins-typedMacroBody +run/t7406.scala +run/t6253c.scala +run/t6253a.scala +run/t6253b.scala +pos/t8146a.scala +pos/t8046c.scala +pos/t8002-nested-scope.scala +pos/t8132.scala +pos/t8045.scala +pos/overzealous-assert-genbcode.scala +pos/t8128.scala +pos/t8013 +pos/t8064b +pos/t6780.scala +pos/t7987 +pos/bcode_throw_null +pos/t8064 +pos/t8046.scala +pos/t6231.scala +pos/t7983.scala +pos/t5508.scala +pos/t5508-min.scala +pos/t8023b.scala +pos/t6231b.scala +pos/debug-reset-local-attrs.scala +pos/t8054.scala +pos/t2066.scala +pos/dotless-targs.scala +pos/t8120.scala +pos/t5508-min-okay.scala +pos/t8060.scala +pos/t8001 +pos/t8138.scala +pos/t8111.scala +pos/t8062 +pos/t8011.scala +pos/t8146b.scala +pos/t8046b.scala +pos/t8023.scala +pos/t5508-min-okay2.scala +pos/macro-implicit-invalidate-on-error.scala +neg/t6563.scala +neg/missing-param-type-tuple.scala +neg/not-a-legal-formal-parameter-tuple.scala +neg/t7897.scala +neg/t8015-ffa.scala +neg/quasiquotes-unliftable-not-found.scala +neg/t2066b.scala +neg/dotless-targs.scala +neg/patmat-classtag-compound.scala +neg/t2066.scala +neg/t8035-deprecated.scala +neg/t6675b.scala +neg/t8104 +neg/t7872.scala +neg/t7850.scala +neg/t7967.scala +neg/macro-bundle-overloaded.scala +neg/t6355a.scala +neg/class-of-double-targs.scala +neg/t6355b.scala +neg/macro-reify-splice-splice +neg/macro-bundle-noncontext.scala +neg/t8015-ffb.scala +neg/t8035-removed.scala +neg/t7984.scala +neg/t8024.scala +neg/t8024b.scala +neg/t8157.scala +neg/t8146-non-finitary-2.scala +neg/t8006.scala +neg/t7872c.scala +neg/t8146-non-finitary.scala +neg/t7872b.scala +neg/t6920.scala +run/t6200.scala +run/t6196.scala +run/macro-bundle-context-refinement +run/macro-enclosingowner-detectvar +run/macro-enclosingowner-sbt +run/macro-bundle-context-alias +run/macro-bundle-whitebox-use-refined +run/macro-bundle-whitebox-use-raw +neg/name-lookup-stable.scala +neg/t0764b.scala +neg/no-implicit-to-anyref-any-val.scala +neg/t1503.scala +neg/t4728.scala +neg/t6455.scala +neg/t6260-named.scala +neg/t6844.scala +neg/t7475c.scala +neg/t7475e.scala +neg/t7475f.scala +neg/macro-bundle-whitebox-use-raw +neg/macro-bundle-whitebox-use-refined +neg/macro-incompatible-macro-engine-b +neg/t7980.scala +neg/macro-incompatible-macro-engine-a +neg/t8143a.scala +neg/t8072.scala +neg/t8207.scala +neg/t8182.scala +neg/t8219-any-any-ref-equals.scala +neg/t8177a.scala +neg/t8228.scala +neg/t8229.scala +neg/t8237-default.scala +neg/t8244b.scala +neg/t8244e +neg/t8244c.scala +neg/t8265.scala +neg/t8266-invalid-interp.scala +neg/t6931 +neg/t8376 +neg/t8372.scala +neg/t8300-overloading.scala +neg/t8244 +neg/t8158 +neg/t8431.scala +pos/implicit-anyval-2.10.scala +pos/delambdafy_t6260_method.scala +pos/macro-bundle-disambiguate-bundle.scala +pos/macro-bundle-disambiguate-nonbundle.scala +pos/package-ob-case +pos/t1786-counter.scala +pos/reflection-compat-api-universe.scala +pos/list-optim-check.scala +pos/existential-java-case-class +pos/t1786-cycle.scala +pos/reflection-compat-c.scala +pos/t3452f.scala +pos/reflection-compat-ru.scala +pos/t2066-2.10-compat.scala +pos/reflection-compat-macro-universe.scala +pos/t5900a.scala +pos/t5760-pkgobj-warn +pos/t5954a +pos/t5954b +pos/t5954d +pos/t6260.scala +pos/t5165b +pos/t5954c +pos/t6260b.scala +pos/t7475b.scala +pos/t7475a.scala +pos/t7753.scala +pos/t7322.scala +pos/t6948.scala +pos/t7475d.scala +pos/t7475e.scala +pos/t6169 +pos/t7788.scala +pos/t7919.scala +pos/t8177a.scala +pos/t8177.scala +pos/t8170.scala +pos/t8170b.scala +pos/t8177d.scala +pos/t8177b.scala +pos/t8177e.scala +pos/t8134 +pos/t8177h.scala +pos/t8177g.scala +pos/t8207.scala +pos/t8187.scala +pos/t8219.scala +pos/t8219b.scala +pos/t8224.scala +pos/t8237.scala +pos/t8223.scala +pos/t8237b.scala +pos/t8300-conversions-a.scala +pos/t8300-conversions-b.scala +pos/t8209a +pos/t8209b +pos/t8244d +pos/t8300-overloading.scala +pos/t8300-patmat-a.scala +pos/t8300-patmat-b.scala +pos/t8315b.scala +pos/t8306.scala +pos/t8301.scala +pos/t8324.scala +pos/t8315.scala +pos/t8301b.scala +pos/t8363.scala +pos/t8367.scala +pos/t8369a.scala +pos/t8369b.scala +pos/t8403.scala +pos/t8364.scala +pos/t8352 +pos/t8376 +neg/macro-bundle-nonpublic-c.scala +neg/literate_existentials.scala +neg/macro-bundle-nonpublic-impl.scala +neg/macro-bundle-ambiguous.scala +neg/macro-bundle-priority-bundle.scala +neg/macro-bundle-need-qualifier.scala +neg/macro-bundle-nonstatic.scala +neg/macro-bundle-polymorphic.scala +neg/macro-bundle-priority-nonbundle.scala +neg/macro-bundle-wrongcontext-a.scala +neg/macro-bundle-wrongcontext-b.scala +run/t8425 +run/t8245.scala +run/t8266-octal-interp.scala +run/t8280.scala +run/t8395.scala +run/t8321 +run/t8153.scala +run/t8233-bcode.scala +run/t8197.scala +run/t8197b.scala +run/t8233.scala +run/t8133 +run/t8133b +run/t7475b.scala +run/t7445.scala +run/t6814 +run/t4577.scala +run/t5134.scala +run/t3452f.scala +run/t3452h.scala +run/t3452c.scala +run/t3452.scala +run/t261.scala +run/t3235-minimal.scala +run/t1503_future.scala +run/t5565.scala +pos/t8411 +pos/t8460.scala +run/t8428.scala +run/t8437 +run/absoverride.scala +run/arrays.scala +run/duration-coarsest.scala +run/iterator-from.scala +run/SymbolsTest.scala +run/t1074.scala +run/t1505.scala +run/streams.scala +run/t2111.scala +run/t4601.scala +pos/SI-4012-a.scala +pos/SI-7638.scala +neg/t3692-new.scala +run/t7015.scala +run/finally.scala +run/bugs.scala +run/t1503.scala +run/t4148.scala +run/t7763.scala +run/issue192.scala + +# Adapt checkfiles for (1.0).toString == "1" +run/Course-2002-01.scala +run/t0421-new.scala +run/runtime.scala +run/t0421-old.scala +run/spec-self.scala +run/t5552.scala +run/Course-2002-02.scala +run/Course-2002-04.scala +run/promotion.scala +run/t4617.scala +run/Course-2002-09.scala +run/t5866.scala +run/try-catch-unify.scala +run/impconvtimes.scala +run/Course-2002-10.scala +run/Course-2002-08.scala +run/t6318_primitives.scala + +# Adapt checkfiles for ().toString == "undefined" +run/t5680.scala +run/dynamic-anyval.scala +run/macro-bundle-toplevel +run/macro-bundle-whitebox-decl +run/t6662 +run/t3702.scala +run/t7657 +run/macro-bundle-static +run/structural.scala + +# Adapt checkfiles for print & flush (which we cannot 100% emulate) +run/imports.scala +run/misc.scala + +# Adapt checkfiles for compiler phase list +run/t6102.scala +neg/t7494-no-options + +# Adapt checkfiles for different behavior with boxed types +run/t5568.scala +run/virtpatmat_typetag.scala +run/virtpatmat_switch.scala +run/t5629b.scala diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/neg/t7494-no-options.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/neg/t7494-no-options.check new file mode 100644 index 0000000..581da38 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/neg/t7494-no-options.check @@ -0,0 +1,42 @@ +error: Error: ploogin takes no options + phase name id description + ---------- -- ----------- + parser 1 parse source into ASTs, perform simple desugaring + namer 2 resolve names, attach symbols to named trees +packageobjects 3 load package objects + typer 4 the meat and potatoes: type the trees + jsinterop 5 + patmat 6 translate match expressions +superaccessors 7 add super accessors in traits and nested classes + extmethods 8 add extension methods for inline classes + pickler 9 serialize symbol tables + refchecks 10 reference/override checking, translate nested objects + uncurry 11 uncurry, translate function values to anonymous classes + tailcalls 12 replace tail calls by jumps + specialize 13 @specialized-driven class and method specialization + explicitouter 14 this refs to outer pointers + erasure 15 erase types, add interfaces for traits + posterasure 16 clean up erased inline classes + lazyvals 17 allocate bitmaps, translate lazy vals into lazified defs + lambdalift 18 move nested functions to top level + constructors 19 move field definitions into constructors + flatten 20 eliminate inner classes + mixin 21 mixin composition + jscode 22 + cleanup 23 platform-specific cleanups, generate reflective calls + delambdafy 24 remove lambdas + icode 25 generate portable intermediate code +#partest !-optimise + jvm 26 generate JVM bytecode + ploogin 27 A sample phase that does so many things it's kind of hard... + terminal 28 the last phase during a compilation run +#partest -optimise + inliner 26 optimization: do inlining +inlinehandlers 27 optimization: inline exception handlers + closelim 28 optimization: eliminate uncalled closures + constopt 29 optimization: optimize null and other constants + dce 30 optimization: eliminate dead code + jvm 31 generate JVM bytecode + ploogin 32 A sample phase that does so many things it's kind of hard... + terminal 33 the last phase during a compilation run +#partest diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/Course-2002-01.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/Course-2002-01.check new file mode 100644 index 0000000..fcda943 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/Course-2002-01.check @@ -0,0 +1,37 @@ +Course-2002-01.scala:41: warning: method loop in object M0 does nothing other than call itself recursively + def loop: Int = loop; + ^ +232 +667 +11 +10 +62.8318 +62.8318 +62.8318 +4 +81 +256 +25 +1 +737 +1 +0 +1 +76 +1.4142156862745097 +1.7321428571428572 +2.0000000929222947 +1.4142156862745097 +1.7321428571428572 +2.0000000929222947 +1.4142156862745097 +1.7321428571428572 +2.0000000929222947 +sqrt(2) = 1.4142135623746899 +sqrt(2) = 1.4142135623746899 +cbrt(2) = 1.2599210500177698 +1 +1 1 +1 2 1 +1 3 3 1 +1 4 6 4 1 diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/Course-2002-02.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/Course-2002-02.check new file mode 100644 index 0000000..ab75cfd --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/Course-2002-02.check @@ -0,0 +1,187 @@ +7 +120 + +10 +100 +2.083333333333333 +3025.7687714031754 +pi = 3.1659792728432152 + +10 +100 +2.083333333333333 +3025.7687714031754 +pi = 3.1659792728432152 + +10 +100 +2.083333333333333 +3025.7687714031754 +pi = 3.1659792728432152 + +10 +100 +2.083333333333333 +3025.7687714031754 +pi = 3.1659792728432152 + +10 +100 +2.083333333333333 +3025.7687714031754 +pi = 3.1659792728432152 + +10 +100 +2.083333333333333 +3025.7687714031754 +pi = 3.1659792728432152 + +10 +100 +2.083333333333333 +3025.7687714031754 +pi = 3.1659792728432152 + +pi = 3.181104885577714 +pi = 3.181104885577714 + +10 +100 +2.083333333333333 +3025.7687714031754 +pi = 3.1659792728432152 +pi = 3.181104885577714 +pi = 3.181104885577714 + +1.5 +1.4166666666666665 +1.4142156862745097 +1.4142135623746899 +sqrt(2) = 1.4142135623746899 + +1.5 +1.4166666666666665 +1.4142156862745097 +1.4142135623746899 +sqrt(2) = 1.4142135623746899 + +1 + 2 + .. + 5 = 15 +1 * 2 * .. * 5 = 120 + +1^2 + 2^2 + .. + 5^2 = 55 +1^2 * 2^2 * .. * 5^2 = 14400 + +factorial(0) = 1 +factorial(1) = 1 +factorial(2) = 2 +factorial(3) = 6 +factorial(4) = 24 +factorial(5) = 120 + +1 + 2 + .. + 5 = 15 +1 * 2 * .. * 5 = 120 + +1^2 + 2^2 + .. + 5^2 = 55 +1^2 * 2^2 * .. * 5^2 = 14400 + +factorial(0) = 1 +factorial(1) = 1 +factorial(2) = 2 +factorial(3) = 6 +factorial(4) = 24 +factorial(5) = 120 + +1 + 2 + .. + 5 = 15 +1 * 2 * .. * 5 = 120 + +1^2 + 2^2 + .. + 5^2 = 55 +1^2 * 2^2 * .. * 5^2 = 14400 + +factorial(0) = 1 +factorial(1) = 1 +factorial(2) = 2 +factorial(3) = 6 +factorial(4) = 24 +factorial(5) = 120 + +fib(0) = 0 +fib(1) = 1 +fib(2) = 1 +fib(3) = 2 +fib(4) = 3 +fib(5) = 5 +fib(6) = 8 +fib(7) = 13 +fib(8) = 21 +fib(9) = 34 +fib(0) = 0 +fib(1) = 1 +fib(2) = 1 +fib(3) = 2 +fib(4) = 3 +fib(5) = 5 +fib(6) = 8 +fib(7) = 13 +fib(8) = 21 +fib(9) = 34 +power(0,0) = 1 +power(0,1) = 0 +power(0,2) = 0 +power(0,3) = 0 +power(0,4) = 0 +power(0,5) = 0 +power(0,6) = 0 +power(0,7) = 0 +power(0,8) = 0 + +power(1,0) = 1 +power(1,1) = 1 +power(1,2) = 1 +power(1,3) = 1 +power(1,4) = 1 +power(1,5) = 1 +power(1,6) = 1 +power(1,7) = 1 +power(1,8) = 1 + +power(2,0) = 1 +power(2,1) = 2 +power(2,2) = 4 +power(2,3) = 8 +power(2,4) = 16 +power(2,5) = 32 +power(2,6) = 64 +power(2,7) = 128 +power(2,8) = 256 + +power(3,0) = 1 +power(3,1) = 3 +power(3,2) = 9 +power(3,3) = 27 +power(3,4) = 81 +power(3,5) = 243 +power(3,6) = 729 +power(3,7) = 2187 +power(3,8) = 6561 + +power(4,0) = 1 +power(4,1) = 4 +power(4,2) = 16 +power(4,3) = 64 +power(4,4) = 256 +power(4,5) = 1024 +power(4,6) = 4096 +power(4,7) = 16384 +power(4,8) = 65536 + +power(5,0) = 1 +power(5,1) = 5 +power(5,2) = 25 +power(5,3) = 125 +power(5,4) = 625 +power(5,5) = 3125 +power(5,6) = 15625 +power(5,7) = 78125 +power(5,8) = 390625 + diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/Course-2002-04.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/Course-2002-04.check new file mode 100644 index 0000000..fc6ad96 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/Course-2002-04.check @@ -0,0 +1,64 @@ +list0 = List(6, 3, 1, 8, 7, 1, 2, 5, 8, 4, 3, 4, 8) +list1 = List(1, 1, 2, 3, 3, 4, 4, 5, 6, 7, 8, 8, 8) +list2 = List(1, 1, 2, 3, 3, 4, 4, 5, 6, 7, 8, 8, 8) +list3 = List(1, 1, 2, 3, 3, 4, 4, 5, 6, 7, 8, 8, 8) +list4 = List(1, 1, 2, 3, 3, 4, 4, 5, 6, 7, 8, 8, 8) +list5 = List(8, 8, 8, 7, 6, 5, 4, 4, 3, 3, 2, 1, 1) +list6 = List(8, 8, 8, 7, 6, 5, 4, 4, 3, 3, 2, 1, 1) + +list0: List() -> List() +list1: List(0) -> List(0) +list2: List(0, 1) -> List(0, 1) +list3: List(1, 0) -> List(0, 1) +list4: List(0, 1, 2) -> List(0, 1, 2) +list5: List(1, 0, 2) -> List(0, 1, 2) +list6: List(0, 1, 2) -> List(0, 1, 2) +list7: List(1, 0, 2) -> List(0, 1, 2) +list8: List(2, 0, 1) -> List(0, 1, 2) +list9: List(2, 1, 0) -> List(0, 1, 2) +listA: List(6, 3, 1, 8, 7, 1, 2, 5, 8, 4) -> List(1, 1, 2, 3, 4, 5, 6, 7, 8, 8) + +f(x) = 5x^3+7x^2+5x+9 +f(0) = 9 +f(1) = 26 +f(2) = 87 +f(3) = 222 + +v1 = List(2, 3, 4) +v2 = List(6, 7, 8) + +id = List(List(1, 0, 0), List(0, 1, 0), List(0, 0, 1)) +m1 = List(List(2, 0, 0), List(0, 2, 0), List(0, 0, 2)) +m2 = List(List(1, 2, 3), List(4, 5, 6), List(7, 8, 9)) + +v1 * v1 = 29 +v1 * v2 = 65 +v2 * v1 = 65 +v1 * v2 = 65 + +id * v1 = List(2, 3, 4) +m1 * v1 = List(4, 6, 8) +m2 * v1 = List(20, 47, 74) + +trn(id) = List(List(1, 0, 0), List(0, 1, 0), List(0, 0, 1)) +trn(m1) = List(List(2, 0, 0), List(0, 2, 0), List(0, 0, 2)) +trn(m2) = List(List(1, 4, 7), List(2, 5, 8), List(3, 6, 9)) + +List(v1) * id = List(List(2, 3, 4)) +List(v1) * m1 = List(List(4, 6, 8)) +List(v1) * m2 = List(List(42, 51, 60)) + +id * List(v1) = List(List(2, 3, 4), List(0, 0, 0), List(0, 0, 0)) +m1 * List(v1) = List(List(4, 6, 8), List(0, 0, 0), List(0, 0, 0)) +m2 * List(v1) = List(List(2, 3, 4), List(8, 12, 16), List(14, 21, 28)) + +id * id = List(List(1, 0, 0), List(0, 1, 0), List(0, 0, 1)) +id * m1 = List(List(2, 0, 0), List(0, 2, 0), List(0, 0, 2)) +m1 * id = List(List(2, 0, 0), List(0, 2, 0), List(0, 0, 2)) +m1 * m1 = List(List(4, 0, 0), List(0, 4, 0), List(0, 0, 4)) +id * m2 = List(List(1, 2, 3), List(4, 5, 6), List(7, 8, 9)) +m2 * id = List(List(1, 2, 3), List(4, 5, 6), List(7, 8, 9)) +m1 * m2 = List(List(2, 4, 6), List(8, 10, 12), List(14, 16, 18)) +m2 * m1 = List(List(2, 4, 6), List(8, 10, 12), List(14, 16, 18)) +m2 * m2 = List(List(30, 36, 42), List(66, 81, 96), List(102, 126, 150)) + diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/Course-2002-08.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/Course-2002-08.check new file mode 100644 index 0000000..0585d5b --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/Course-2002-08.check @@ -0,0 +1,171 @@ +x = abc +count = 111 +x = hello +count = 112 + +account deposit 50 -> undefined +account withdraw 20 -> 30 +account withdraw 20 -> 10 +account withdraw 15 -> + +x deposit 30 -> undefined +y withdraw 20 -> + +x deposit 30 -> undefined +x withdraw 20 -> 10 + +x deposit 30 -> undefined +y withdraw 20 -> 10 + +2^0 = 1 +2^1 = 2 +2^2 = 4 +2^3 = 8 + +2^0 = 1 +2^1 = 2 +2^2 = 4 +2^3 = 8 + +1 2 3 +List(1, 2, 3) + +out 0 new-value = false +*** simulation started *** +out 1 new-value = true +!0 = 1 + +*** simulation started *** +out 2 new-value = false +!1 = 0 + +out 2 new-value = false + +*** simulation started *** +0 & 0 = 0 + +*** simulation started *** +0 & 1 = 0 + +*** simulation started *** +out 11 new-value = true +out 11 new-value = false +1 & 0 = 0 + +*** simulation started *** +out 14 new-value = true +1 & 1 = 1 + +out 14 new-value = false + +*** simulation started *** +0 | 0 = 0 + +*** simulation started *** +out 24 new-value = true +0 | 1 = 1 + +*** simulation started *** +1 | 0 = 1 + +*** simulation started *** +1 | 1 = 1 + +sum 34 new-value = false +carry 34 new-value = false + +*** simulation started *** +0 + 0 = 0 + +*** simulation started *** +sum 47 new-value = true +0 + 1 = 1 + +*** simulation started *** +carry 50 new-value = true +carry 50 new-value = false +sum 54 new-value = false +sum 54 new-value = true +1 + 0 = 1 + +*** simulation started *** +carry 57 new-value = true +sum 61 new-value = false +1 + 1 = 2 + +sum 61 new-value = false +carry 61 new-value = false + +*** simulation started *** +0 + 0 + 0 = 0 + +*** simulation started *** +sum 82 new-value = true +0 + 0 + 1 = 1 + +*** simulation started *** +sum 89 new-value = false +carry 90 new-value = true +sum 97 new-value = true +carry 98 new-value = false +0 + 1 + 0 = 1 + +*** simulation started *** +sum 113 new-value = false +carry 114 new-value = true +0 + 1 + 1 = 2 + +*** simulation started *** +sum 121 new-value = true +carry 122 new-value = false +sum 129 new-value = false +sum 129 new-value = true +1 + 0 + 0 = 1 + +*** simulation started *** +carry 137 new-value = true +sum 144 new-value = false +1 + 0 + 1 = 2 + +*** simulation started *** +carry 152 new-value = false +sum 152 new-value = true +sum 158 new-value = false +carry 159 new-value = true +1 + 1 + 0 = 2 + +*** simulation started *** +sum 173 new-value = true +1 + 1 + 1 = 3 + +in 0 new-value = false +ctrl0 0 new-value = false +ctrl1 0 new-value = false +ctrl2 0 new-value = false +out0 0 new-value = false +out1 0 new-value = false +out2 0 new-value = false +out3 0 new-value = false +out4 0 new-value = false +out5 0 new-value = false +out6 0 new-value = false +out7 0 new-value = false +in 0 new-value = true +*** simulation started *** +out0 10 new-value = true +ctrl0 10 new-value = true +*** simulation started *** +out1 13 new-value = true +out0 14 new-value = false +ctrl1 14 new-value = true +*** simulation started *** +out3 20 new-value = true +out1 21 new-value = false +ctrl2 21 new-value = true +*** simulation started *** +out7 30 new-value = true +out3 31 new-value = false +ctrl0 31 new-value = false +*** simulation started *** +out7 34 new-value = false +out6 35 new-value = true diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/Course-2002-09.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/Course-2002-09.check new file mode 100644 index 0000000..c921361 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/Course-2002-09.check @@ -0,0 +1,50 @@ +Probe: f = 32 +Probe: c = 0 +Probe: f = ? +Probe: c = ? + +Probe: f = 212 +Probe: c = 100 +Probe: f = ? +Probe: c = ? + +Probe: c = 0 +Probe: f = 32 +Probe: c = ? +Probe: f = ? + +Probe: c = 100 +Probe: f = 212 +Probe: c = ? +Probe: f = ? + +0 Celsius -> 32 Fahrenheits +100 Celsius -> 212 Fahrenheits +32 Fahrenheits -> 0 Celsius +212 Fahrenheits -> 100 Celsius + +a = ?, b = ?, c = ? => ? * ? = ? +a = 2, b = ?, c = ? => 2 * ? = ? +a = ?, b = 3, c = ? => ? * 3 = ? +a = ?, b = ?, c = 6 => ? * ? = 6 +a = 2, b = 3, c = ? => 2 * 3 = 6 +a = 2, b = ?, c = 6 => 2 * 3 = 6 +a = ?, b = 3, c = 6 => 2 * 3 = 6 +a = 2, b = 3, c = 6 => 2 * 3 = 6 + +a = 0, b = ?, c = ? => 0 * ? = 0 +a = ?, b = 0, c = ? => ? * 0 = 0 +a = ?, b = ?, c = 0 => ? * ? = 0 +a = 0, b = 7, c = ? => 0 * 7 = 0 +a = 7, b = 0, c = ? => 7 * 0 = 0 +a = 0, b = 0, c = ? => 0 * 0 = 0 +a = 0, b = ?, c = 0 => 0 * ? = 0 +a = ?, b = 0, c = 0 => ? * 0 = 0 +a = 0, b = 7, c = 0 => 0 * 7 = 0 +a = 7, b = 0, c = 0 => 7 * 0 = 0 +a = 0, b = 0, c = 0 => 0 * 0 = 0 + +a = 3, b = 4 => c = 5 +a = 3, c = 5 => b = 4 +b = 4, c = 5 => a = 3 + diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/Course-2002-10.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/Course-2002-10.check new file mode 100644 index 0000000..847f0fa --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/Course-2002-10.check @@ -0,0 +1,46 @@ +fib(0) = 0 +fib(1) = 1 +fib(2) = 1 +fib(3) = 2 +fib(4) = 3 +fib(5) = 5 +fib(6) = 8 +fib(7) = 13 +fib(8) = 21 +fib(9) = 34 +fib(10) = 55 +fib(11) = 89 +fib(12) = 144 +fib(13) = 233 +fib(14) = 377 +fib(15) = 610 +fib(16) = 987 +fib(17) = 1597 +fib(18) = 2584 +fib(19) = 4181 + +pi(0) = 4 , 3.166666666666667 , 4 +pi(1) = 2.666666666666667 , 3.1333333333333337, 3.166666666666667 +pi(2) = 3.466666666666667 , 3.1452380952380956, 3.142105263157895 +pi(3) = 2.8952380952380956, 3.1396825396825396, 3.1415993573190044 +pi(4) = 3.33968253968254 , 3.142712842712843 , 3.141592714033778 +pi(5) = 2.976046176046176 , 3.140881340881341 , 3.1415926539752923 +pi(6) = 3.283738483738484 , 3.142071817071817 , 3.141592653591176 +pi(7) = 3.017071817071817 , 3.1412548236077646, 3.141592653589777 +pi(8) = 3.252365934718876 , 3.1418396189294024, 3.141592653589794 +pi(9) = 3.0418396189294024, 3.141406718496502 , 3.1415926535897936 +pi = 3.141592653589793 , 3.141592653589793 , 3.141592653589793 + +ln(0) = 1 , 0.7 , 1 +ln(1) = 0.5 , 0.6904761904761905, 0.7 +ln(2) = 0.8333333333333333, 0.6944444444444444, 0.6932773109243697 +ln(3) = 0.5833333333333333, 0.6924242424242424, 0.6931488693329254 +ln(4) = 0.7833333333333333, 0.6935897435897436, 0.6931471960735491 +ln(5) = 0.6166666666666667, 0.6928571428571428, 0.6931471806635636 +ln(6) = 0.7595238095238095, 0.6933473389355742, 0.6931471805604038 +ln(7) = 0.6345238095238095, 0.6930033416875522, 0.6931471805599444 +ln(8) = 0.7456349206349207, 0.6932539682539682, 0.6931471805599426 +ln(9) = 0.6456349206349206, 0.6930657506744463, 0.6931471805599453 +ln = 0.6931471805599453, 0.6931471805599453, 0.6931471805599453 + +prime numbers: 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 101 103 107 109 113 diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/bugs.sem b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/bugs.sem new file mode 100644 index 0000000..d36898b --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/bugs.sem @@ -0,0 +1 @@ +asInstanceOfs diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/dynamic-anyval.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/dynamic-anyval.check new file mode 100644 index 0000000..c125372 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/dynamic-anyval.check @@ -0,0 +1,4 @@ +undefined.dingo(bippy, 5) +List(1, 2, 3).dingo(bippy, 5) +undefined.dingo(bippy, 5) +List(1, 2, 3).dingo(bippy, 5) diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/impconvtimes.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/impconvtimes.check new file mode 100644 index 0000000..082377e --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/impconvtimes.check @@ -0,0 +1 @@ +3.0 * Hour = Measure(3,Hour) diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/imports.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/imports.check new file mode 100644 index 0000000..1aad598 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/imports.check @@ -0,0 +1,21 @@ +In C_ico, v_ico .toString() returns ↩ +↪C_ico -> ok +In C_ico, field .toString() returns ↩ +↪C_ico -> ok +In C_ico, method.toString() returns ↩ +↪C_ico -> ok + +In C_ioc, v_ioc .toString() returns ↩ +↪C_ioc -> ok +In C_ioc, field .toString() returns ↩ +↪C_ioc -> ok +In C_ioc, method.toString() returns ↩ +↪C_ioc -> ok + +In C_oic, v_oic .toString() returns ↩ +↪C_oic -> ok +In C_oic, field .toString() returns ↩ +↪C_oic -> ok +In C_oic, method.toString() returns ↩ +↪C_oic -> ok + diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/issue192.sem b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/issue192.sem new file mode 100644 index 0000000..10abbf7 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/issue192.sem @@ -0,0 +1 @@ +strictFloats diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/macro-bundle-static.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/macro-bundle-static.check new file mode 100644 index 0000000..e2e7628 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/macro-bundle-static.check @@ -0,0 +1,6 @@ +undefined +Int +undefined +true +IntInt +true diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/macro-bundle-toplevel.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/macro-bundle-toplevel.check new file mode 100644 index 0000000..e2e7628 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/macro-bundle-toplevel.check @@ -0,0 +1,6 @@ +undefined +Int +undefined +true +IntInt +true diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/macro-bundle-whitebox-decl.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/macro-bundle-whitebox-decl.check new file mode 100644 index 0000000..e2e7628 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/macro-bundle-whitebox-decl.check @@ -0,0 +1,6 @@ +undefined +Int +undefined +true +IntInt +true diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/misc.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/misc.check new file mode 100644 index 0000000..6043817 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/misc.check @@ -0,0 +1,62 @@ +misc.scala:46: warning: a pure expression does nothing in statement position; you may be omitting necessary parentheses + 42; + ^ +misc.scala:47: warning: a pure expression does nothing in statement position; you may be omitting necessary parentheses + 42l; + ^ +misc.scala:48: warning: a pure expression does nothing in statement position; you may be omitting necessary parentheses + 23.5f; + ^ +misc.scala:49: warning: a pure expression does nothing in statement position; you may be omitting necessary parentheses + 23.5; + ^ +misc.scala:50: warning: a pure expression does nothing in statement position; you may be omitting necessary parentheses + "Hello"; + ^ +misc.scala:51: warning: a pure expression does nothing in statement position; you may be omitting necessary parentheses + 32 + 45; + ^ +misc.scala:62: warning: a pure expression does nothing in statement position; you may be omitting necessary parentheses + x; + ^ +misc.scala:74: warning: a pure expression does nothing in statement position; you may be omitting necessary parentheses + 1 < 2; + ^ +### Hello +### 17 +### Bye + +### fib(0) = ↩ +↪1 +### fib(1) = ↩ +↪1 +### fib(2) = ↩ +↪2 +### fib(3) = ↩ +↪3 +### fib(4) = ↩ +↪5 +=== MyClass::toString === +=== MySubclass::toString === +=== MyClass::test === + +identity + +A.a = 1 +B.a = 5 +B.b = 2 + +X.a = 4 +Y.a = 11 +Y.b = 5 +Y.b = 5 + +X::foo + +Y::foo +X::foo + +3 +3 + +true diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/promotion.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/promotion.check new file mode 100644 index 0000000..41e36c3 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/promotion.check @@ -0,0 +1,4 @@ +2 +6 +20 +30 diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/runtime.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/runtime.check new file mode 100644 index 0000000..0450b94 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/runtime.check @@ -0,0 +1,70 @@ +runtime.scala:141: warning: comparing values of types Null and Null using `eq' will always yield true + check(true , null eq null, null ne null); + ^ +runtime.scala:141: warning: comparing values of types Null and Null using `ne' will always yield false + check(true , null eq null, null ne null); + ^ +<<< Test0 +[false,true] +[0,1,2] +[3,4,5] +[a,b,c] +[6,7,8] +[9,10,11] +[12,13] +[14,15] +[string] +>>> Test0 + +<<< Test1 +10 +14 +15 +16 +20 +23 +24 +25 +26 +>>> Test1 + +<<< Test2 +A +M0 +N0 + +A +N0 +M0 + +A +M0 +M1 +N0 + +A +N0 +N1 +M0 + +>>> Test2 + +<<< Test3 +Ok +Ok +Ok +Ok +Ok +Ok +Ok +Ok +Ok +Ok +Ok +Ok +Ok +Ok +Ok +Ok +>>> Test3 + diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/spec-self.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/spec-self.check new file mode 100644 index 0000000..fd3c81a --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/spec-self.check @@ -0,0 +1,2 @@ +5 +5 diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/structural.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/structural.check new file mode 100644 index 0000000..2fec112 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/structural.check @@ -0,0 +1,37 @@ + 1. hey + 2. 11 + 3. dee + 4. iei + 5. 6 + 6. 51 + 7. 2 + 8. 11 +10. 12 +11. eitch +12. 1 +13. ohone +14. 1 +15. undefined +16. one +17. tieone +18. 2 +19. true +20. 1 +21. undefined +22. one +23. oy +24. 1 +25. null +26. iei +31. 4 +32. undefined +33. iei +33. tieone +1 +2 +3 +4 +5 +caught +3 +2 diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/t0421-new.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/t0421-new.check new file mode 100644 index 0000000..00d29b7 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/t0421-new.check @@ -0,0 +1,3 @@ +[Array(0, 1),Array(2, 3),Array(4, 5)] +[Array(31)] +[Array(24, 32)] diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/t0421-old.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/t0421-old.check new file mode 100644 index 0000000..00d29b7 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/t0421-old.check @@ -0,0 +1,3 @@ +[Array(0, 1),Array(2, 3),Array(4, 5)] +[Array(31)] +[Array(24, 32)] diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/t1503.sem b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/t1503.sem new file mode 100644 index 0000000..d36898b --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/t1503.sem @@ -0,0 +1 @@ +asInstanceOfs diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/t3702.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/t3702.check new file mode 100644 index 0000000..3fce987 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/t3702.check @@ -0,0 +1,2 @@ +undefined +6 diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/t4148.sem b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/t4148.sem new file mode 100644 index 0000000..d36898b --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/t4148.sem @@ -0,0 +1 @@ +asInstanceOfs diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/t4617.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/t4617.check new file mode 100644 index 0000000..a6790f1 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/t4617.check @@ -0,0 +1 @@ +Str 8 diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/t5552.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/t5552.check new file mode 100644 index 0000000..4704611 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/t5552.check @@ -0,0 +1,2 @@ +(3,3) +(3,3) diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/t5568.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/t5568.check new file mode 100644 index 0000000..6f30cc5 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/t5568.check @@ -0,0 +1,9 @@ +void +int +class scala.runtime.BoxedUnit +class scala.runtime.BoxedUnit +class java.lang.Byte +class java.lang.Byte +5 +5 +5 diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/t5629b.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/t5629b.check new file mode 100644 index 0000000..c65298a --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/t5629b.check @@ -0,0 +1,10 @@ +=== pf(1): +MySmartPF.apply entered... +newPF.applyOrElse entered... +default +scala.MatchError: 1 (of class java.lang.Byte) +=== pf(42): +MySmartPF.apply entered... +newPF.applyOrElse entered... +ok +=== done diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/t5680.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/t5680.check new file mode 100644 index 0000000..a3b8b64 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/t5680.check @@ -0,0 +1,3 @@ +[Lscala.runtime.BoxedUnit +undefined +undefined diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/t5866.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/t5866.check new file mode 100644 index 0000000..64df1ce --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/t5866.check @@ -0,0 +1,2 @@ +0 +Foo(0) diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/t6102.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/t6102.check new file mode 100644 index 0000000..120082e --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/t6102.check @@ -0,0 +1,27 @@ +[running phase parser on t6102.scala] +[running phase namer on t6102.scala] +[running phase packageobjects on t6102.scala] +[running phase typer on t6102.scala] +[running phase jsinterop on t6102.scala] +[running phase patmat on t6102.scala] +[running phase superaccessors on t6102.scala] +[running phase extmethods on t6102.scala] +[running phase pickler on t6102.scala] +[running phase refchecks on t6102.scala] +[running phase uncurry on t6102.scala] +[running phase tailcalls on t6102.scala] +[running phase specialize on t6102.scala] +[running phase explicitouter on t6102.scala] +[running phase erasure on t6102.scala] +[running phase posterasure on t6102.scala] +[running phase lazyvals on t6102.scala] +[running phase lambdalift on t6102.scala] +[running phase constructors on t6102.scala] +[running phase flatten on t6102.scala] +[running phase mixin on t6102.scala] +[running phase jscode on t6102.scala] +[running phase cleanup on t6102.scala] +[running phase delambdafy on t6102.scala] +[running phase icode on t6102.scala] +[running phase dce on t6102.scala] +[running phase jvm on icode] diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/t6318_primitives.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/t6318_primitives.check new file mode 100644 index 0000000..08decef --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/t6318_primitives.check @@ -0,0 +1,36 @@ +true +Some(1) +false +None +true +Some(1) +false +None +true +Some() +false +None +true +Some(1) +false +None +true +Some(1) +false +None +true +Some(1) +false +None +true +Some(1) +false +None +true +Some(true) +false +None +true +Some(undefined) +false +None diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/t6662.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/t6662.check new file mode 100644 index 0000000..417b7b5 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/t6662.check @@ -0,0 +1 @@ +undefined diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/t7657.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/t7657.check new file mode 100644 index 0000000..1a87c1e --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/t7657.check @@ -0,0 +1,3 @@ +undefined +undefined +undefined diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/t7763.sem b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/t7763.sem new file mode 100644 index 0000000..d36898b --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/t7763.sem @@ -0,0 +1 @@ +asInstanceOfs diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/try-catch-unify.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/try-catch-unify.check new file mode 100644 index 0000000..813f011 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/try-catch-unify.check @@ -0,0 +1,4 @@ +Failure(java.lang.NumberFormatException: For input string: "Hi") +Success(5) +O NOES +Failure(java.lang.NumberFormatException: For input string: "Hi") diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/virtpatmat_switch.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/virtpatmat_switch.check new file mode 100644 index 0000000..0900a9c --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/virtpatmat_switch.check @@ -0,0 +1,7 @@ +zero +one +many +got a +got b +got some letter +scala.MatchError: 5 (of class java.lang.Byte)
\ No newline at end of file diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/virtpatmat_typetag.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/virtpatmat_typetag.check new file mode 100644 index 0000000..e95c3d0 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.0/run/virtpatmat_typetag.check @@ -0,0 +1,10 @@ +1 is not a Int; it's a class java.lang.Byte +1 is a java.lang.Integer +1 is not a java.lang.String; it's a class java.lang.Byte +true is a Any +woele is a java.lang.String +1 is not a Int; it's a class java.lang.Byte +1 is a java.lang.Integer +1 is not a java.lang.String; it's a class java.lang.Byte +true is a Any +woele is a java.lang.String diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/BlacklistedTests.txt b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/BlacklistedTests.txt new file mode 100644 index 0000000..18a1c5d --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/BlacklistedTests.txt @@ -0,0 +1,899 @@ +# +# POS +# + +# Using Jsoup, what's that? +pos/cycle-jsoup.scala + +# Using scala.actors +pos/t533.scala +pos/functions.scala +pos/MailBox.scala + +# +# NEG +# + +# Using the compiler API + +neg/t6446-additional +neg/t6446-list +neg/t6446-missing +neg/t6446-show-phases.scala + +# Screws up, but not really our problem (error: None.get instead of +# phase ordering error) +neg/t7494-multi-right-after +neg/t7494-right-after-before +neg/t7622-multi-followers +neg/t7622-cyclic-dependency + +# Uses some strange macro cross compile mechanism. +neg/macro-incompatible-macro-engine-c.scala + +# Uses .java files +neg/t6289 + +# +# RUN +# + +# Relies on the exact toString() representation of Floats/Doubles +run/t2378.scala + +# Relies on !box(+0.0).equals(box(-0.0)) +run/number-parsing.scala + +# Uses ClassTags on existentials which are broken in Scala (see #251) +run/valueclasses-classtag-existential.scala + +# Relies on a particular execution speed +run/t5857.scala + +# Using parts of the javalib we don't plan to support + +run/t0412.scala +run/t3518.scala +run/t6198.scala +run/t5018.scala +run/t2417.scala +run/t6197.scala +run/t4813.scala +run/lazy-concurrent.scala +run/t5880.scala +run/mapConserve.scala +run/t3667.scala +run/bigDecimalTest.scala +run/t3038d.scala +run/numbereq.scala +run/shutdownhooks.scala +run/t4658.scala +run/t4201.scala +run/t5590.scala +run/deeps.scala +run/t3895b.scala +run/t2813.2.scala +run/t5974.scala +run/hashset.scala +run/t5262.scala +run/t5293.scala +run/t5293-map.scala +run/serialize-stream.scala +run/sysprops.scala + +run/colltest.scala +run/equality.scala +run/t2849.scala +run/t1360.scala +run/t6114.scala +run/t7269.scala +run/t3199b.scala + +# Uses java.util.Collections +run/java-erasure.scala +run/t2250.scala + +# Uses java.math.BigDecimal / BigInteger +run/bigDecimalCache.scala +run/hashhash.scala +run/is-valid-num.scala +run/range.scala +run/stringinterpolation_macro-run.scala +run/t5356.scala +run/t6064.scala + +# Documented semantic difference on numbers (float precision) +run/interpolation.scala +run/interpolationMultiline1.scala + +# Documented semantic difference on String.split(x: Array[Char]) +run/t0325.scala + +# Using Threads +run/t6969.scala +run/inner-obj-auto.scala +run/predef-cycle.scala +run/synchronized.scala + +# Uses java.security +run/t2318.scala + +# Tries to catch java.lang.StackOverflowError +run/t6154.scala + +# Tries to catch java.lang.OutOfMemoryError +run/t7880.scala + +# Taking too much time, because JavaScript is not as fast as the JVM + +run/t3822.scala +run/collections.scala +run/t3989.scala +run/adding-growing-set.scala +run/t3242.scala +run/hashCodeDistribution.scala +run/t408.scala +run/t6584.scala +run/t6853.scala +run/UnrolledBuffer.scala + +# Crashes Rhino + +run/bridges.scala +run/patmat-exprs.scala + +# Using partest properties + +run/tailcalls.scala +run/t4294.scala +run/t6331b.scala + +# Using IO + +run/Predef.readLine.scala +run/t6488.scala +run/t6988.scala + +# Object{Output|Input}Streams +run/t6935.scala +run/t8188.scala + +# Using System.getProperties + +run/t4426.scala + +# Using ExecutionContext.global +run/t7336.scala +run/t7775.scala +run/future-flatmap-exec-count.scala + +# Using reflection + +run/t6063 + +run/mixin-bridge-methods.scala +run/t5125.scala +run/outertest.scala +run/t6223.scala +run/t5652b +run/elidable-opt.scala +run/nullable-lazyvals.scala +run/t4794.scala +run/t5652 +run/t5652c +run/getClassTest-old.scala + +run/reflection-repl-classes.scala +run/t5256e.scala +run/typetags_core.scala +run/reflection-constructormirror-toplevel-badpath.scala +run/t5276_1b.scala +run/reflection-sorted-decls.scala +run/toolbox_typecheck_implicitsdisabled.scala +run/t5418b.scala +run/toolbox_typecheck_macrosdisabled2.scala +run/abstypetags_serialize.scala +run/all-overridden.scala +run/showraw_tree_kinds.scala +run/showraw_tree_types_ids.scala +run/showraw_tree_types_typed.scala +run/showraw_tree_ids.scala +run/showraw_tree_ultimate.scala +run/t5266_2.scala +run/t5274_1.scala +run/t5224.scala +run/reflection-sanitychecks.scala +run/t6086-vanilla.scala +run/t5277_2.scala +run/reflection-methodsymbol-params.scala +run/reflection-valueclasses-standard.scala +run/t5274_2.scala +run/t5423.scala +run/reflection-modulemirror-toplevel-good.scala +run/t5419.scala +run/t5271_3.scala +run/reflection-enclosed-nested-basic.scala +run/reflection-enclosed-nested-nested-basic.scala +run/fail-non-value-types.scala +run/reflection-mem-typecheck.scala +run/exprs_serialize.scala +run/t5258a.scala +run/typetags_without_scala_reflect_manifest_lookup.scala +run/t4110-new.scala +run/t5273_2b_newpatmat.scala +run/t6277.scala +run/t5335.scala +run/toolbox_typecheck_macrosdisabled.scala +run/reflection-modulemirror-inner-good.scala +run/t5229_2.scala +run/typetags_multi.scala +run/typetags_without_scala_reflect_typetag_manifest_interop.scala +run/reflection-constructormirror-toplevel-good.scala +run/reflection-magicsymbols-invoke.scala +run/t6392b.scala +run/t5229_1.scala +run/reflection-magicsymbols-vanilla.scala +run/t5225_2.scala +run/origins.scala +run/runtimeEval1.scala +run/reflection-implClass.scala +run/reflection-enclosed-nested-inner-basic.scala +run/reflection-fieldmirror-ctorparam.scala +run/t6181.scala +run/reflection-magicsymbols-repl.scala +run/t5272_2_newpatmat.scala +run/t5270.scala +run/t5418a.scala +run/t5276_2b.scala +run/t5256f.scala +run/reflection-enclosed-basic.scala +run/reflection-constructormirror-inner-badpath.scala +run/interop_typetags_are_manifests.scala +run/newTags.scala +run/t5273_1_newpatmat.scala +run/reflection-constructormirror-nested-good.scala +run/t2236-new.scala +run/existentials3-new.scala +run/t6323b.scala +run/t5943a1.scala +run/reflection-fieldmirror-getsetval.scala +run/t5272_1_oldpatmat.scala +run/t5256h.scala +run/t1195-new.scala +run/t5840.scala +run/reflection-methodsymbol-returntype.scala +run/reflection-fieldmirror-accessorsareokay.scala +run/reflection-sorted-members.scala +run/reflection-allmirrors-tostring.scala +run/valueclasses-typetag-existential.scala +run/toolbox_console_reporter.scala +run/reflection-enclosed-inner-inner-basic.scala +run/t5256b.scala +run/bytecodecs.scala +run/elidable.scala +run/freetypes_false_alarm1.scala +run/freetypes_false_alarm2.scala +run/getClassTest-new.scala +run/idempotency-extractors.scala +run/idempotency-case-classes.scala +run/idempotency-this.scala +run/idempotency-labels.scala +run/idempotency-lazy-vals.scala +run/interop_manifests_are_abstypetags.scala +run/interop_manifests_are_typetags.scala +run/abstypetags_core.scala +run/macro-reify-abstypetag-notypeparams +run/macro-reify-abstypetag-typeparams-tags +run/macro-reify-abstypetag-typeparams-notags +run/macro-reify-abstypetag-usetypetag +run/macro-reify-freevars +run/macro-reify-splice-outside-reify +run/macro-reify-tagless-a +run/macro-reify-type +run/macro-reify-typetag-typeparams-tags +run/macro-reify-typetag-notypeparams +run/macro-undetparams-implicitval +run/manifests-new.scala +run/manifests-old.scala +run/no-pickle-skolems +run/position-val-def.scala +run/reflect-priv-ctor.scala +run/primitive-sigs-2-new.scala +run/primitive-sigs-2-old.scala +run/reflection-enclosed-inner-basic.scala +run/reflection-enclosed-inner-nested-basic.scala +run/reflection-constructormirror-inner-good.scala +run/reflection-constructormirror-nested-badpath.scala +run/reflection-fancy-java-classes +run/reflection-fieldsymbol-navigation.scala +run/reflection-fieldmirror-nmelocalsuffixstring.scala +run/reflection-fieldmirror-getsetvar.scala +run/reflection-fieldmirror-privatethis.scala +run/reflection-implicit.scala +run/reflection-mem-glbs.scala +run/reflection-mem-tags.scala +run/reflection-java-annotations +run/reflection-java-crtp +run/reflection-methodsymbol-typeparams.scala +run/reflection-modulemirror-nested-badpath.scala +run/reflection-modulemirror-inner-badpath.scala +run/reflection-modulemirror-nested-good.scala +run/reflection-modulemirror-toplevel-badpath.scala +run/reflection-sync-subtypes.scala +run/reflinit.scala +run/reflection-valueclasses-derived.scala +run/reflection-valueclasses-magic.scala +run/resetattrs-this.scala +run/runtimeEval2.scala +run/showraw_aliases.scala +run/showraw_mods.scala +run/shortClass.scala +run/showraw_nosymbol.scala +run/showraw_tree.scala +run/showraw_tree_types_untyped.scala +run/t1167.scala +run/t2577.scala +run/t2873.scala +run/t2886.scala +run/t2251b.scala +run/t3346j.scala +run/t3507-new.scala +run/t3569.scala +run/t5125b.scala +run/t5225_1.scala +run/t3425b +run/t5256a.scala +run/t5230.scala +run/t5256c.scala +run/t5256g.scala +run/t5266_1.scala +run/t5269.scala +run/t5271_1.scala +run/t5271_2.scala +run/t5271_4.scala +run/t5272_1_newpatmat.scala +run/t5272_2_oldpatmat.scala +run/t5273_1_oldpatmat.scala +run/t5273_2a_newpatmat.scala +run/t5273_2a_oldpatmat.scala +run/t5275.scala +run/t5276_1a.scala +run/t5276_2a.scala +run/t5277_1.scala +run/t5279.scala +run/t5334_1.scala +run/t5334_2.scala +run/t5415.scala +run/t5418.scala +run/t5676.scala +run/t5704.scala +run/t5710-1.scala +run/t5710-2.scala +run/t5770.scala +run/t5894.scala +run/t5816.scala +run/t5824.scala +run/t5912.scala +run/t5942.scala +run/t5943a2.scala +run/t6023.scala +run/t6113.scala +run/t6175.scala +run/t6178.scala +run/t6199-mirror.scala +run/t6199-toolbox.scala +run/t6220.scala +run/t6240-universe-code-gen.scala +run/t6221 +run/t6260b.scala +run/t6259.scala +run/t6287.scala +run/t6261.scala +run/t6308.scala +run/t6344.scala +run/t6392a.scala +run/t6591_1.scala +run/t6591_2.scala +run/t6591_3.scala +run/t6591_5.scala +run/t6591_6.scala +run/t6591_7.scala +run/t6608.scala +run/t6677.scala +run/t6687.scala +run/t6715.scala +run/t6719.scala +run/t6793.scala +run/t6860.scala +run/t6793b.scala +run/t6793c.scala +run/t7045.scala +run/t7046.scala +run/t7008-scala-defined +run/t7120b.scala +run/t7151.scala +run/t7214.scala +run/t7235.scala +run/t7331a.scala +run/t7331b.scala +run/t7331c.scala +run/t7558.scala +run/t7556 +run/t7779.scala +run/t7868b.scala +run/toolbox_current_run_compiles.scala +run/toolbox_default_reporter_is_silent.scala +run/toolbox_parse_package.scala +run/toolbox_silent_reporter.scala +run/toolbox_typecheck_inferimplicitvalue.scala +run/trait-renaming +run/typetags_serialize.scala +run/valueclasses-typetag-basic.scala +run/WeakHashSetTest.scala +run/valueclasses-typetag-generic.scala +run/t4023.scala +run/t4024.scala +run/t6380.scala +run/t5273_2b_oldpatmat.scala +run/t8104 +run/t8047.scala +run/t6992 +run/var-arity-class-symbol.scala +run/typetags_symbolof_x.scala +run/typecheck +run/t8190.scala +run/t8192 +run/t8177f.scala +run/t8199.scala +run/t7932.scala +run/t7700.scala +run/t7570c.scala +run/t7570b.scala +run/t7533.scala +run/t7570a.scala +run/t7044 +run/t7328.scala +run/t6733.scala +run/t6554.scala +run/t6732.scala +run/t6379 +run/t6411b.scala +run/t6411a.scala +run/t6260c.scala +run/t6260-delambdafy.scala +run/showdecl +run/reflection-sync-potpourri.scala +run/reflection-tags.scala +run/reflection-companiontype.scala +run/reflection-scala-annotations.scala +run/reflection-idtc.scala +run/macro-reify-nested-b2 +run/mixin-signatures.scala +run/reflection-companion.scala +run/macro-reify-nested-b1 +run/macro-reify-nested-a2 +run/macro-reify-nested-a1 +run/macro-reify-chained2 +run/macro-reify-chained1 +run/inferred-type-constructors.scala +run/mirror_symbolof_x.scala +run/t8196.scala +run/t8549b.scala +run/t8574.scala +run/t8549.scala + +run/reify_newimpl_29.scala +run/reify_magicsymbols.scala +run/reify_inheritance.scala +run/reify_newimpl_12.scala +run/reify_typerefs_2b.scala +run/reify_csv.scala +run/reify_inner2.scala +run/reify_maps_oldpatmat.scala +run/reify_newimpl_43.scala +run/reify_nested_inner_refers_to_local.scala +run/reify_closure7.scala +run/reify_closure8b.scala +run/reify_typerefs_3b.scala +run/reify_newimpl_44.scala +run/reify_newimpl_06.scala +run/reify_newimpl_05.scala +run/reify_newimpl_20.scala +run/reify_newimpl_23.scala +run/reify_metalevel_breach_-1_refers_to_1.scala +run/reify_newimpl_41.scala +run/reify-repl-fail-gracefully.scala +run/reify_fors_oldpatmat.scala +run/reify_inner3.scala +run/reify_closure8a.scala +run/reify_closures10.scala +run/reify_ann2a.scala +run/reify_newimpl_51.scala +run/reify_newimpl_47.scala +run/reify_extendbuiltins.scala +run/reify_newimpl_30.scala +run/reify_newimpl_38.scala +run/reify_closure2a.scala +run/reify_newimpl_45.scala +run/reify_closure1.scala +run/reify_generic2.scala +run/reify_printf.scala +run/reify_closure6.scala +run/reify_newimpl_37.scala +run/reify_newimpl_35.scala +run/reify_typerefs_3a.scala +run/reify_newimpl_25.scala +run/reify_ann4.scala +run/reify_typerefs_1b.scala +run/reify_newimpl_22.scala +run/reify_this.scala +run/reify_typerefs_2a.scala +run/reify_newimpl_03.scala +run/reify_newimpl_48.scala +run/reify_varargs.scala +run/reify_newimpl_42.scala +run/reify_newimpl_15.scala +run/reify_nested_inner_refers_to_global.scala +run/reify_newimpl_02.scala +run/reify_newimpl_01.scala +run/reify_fors_newpatmat.scala +run/reify_classfileann_a.scala +run/reify_nested_outer_refers_to_local.scala +run/reify_newimpl_13.scala +run/reify_closure5a.scala +run/reify_inner4.scala +run/reify_sort.scala +run/reify_ann1a.scala +run/reify_classfileann_b.scala +run/reify_closure4a.scala +run/reify_newimpl_33.scala +run/reify_sort1.scala +run/reify_properties.scala +run/reify_generic.scala +run/reify_newimpl_27.scala +run/reify-aliases.scala +run/reify_ann3.scala +run/reify-staticXXX.scala +run/reify_ann1b.scala +run/reify_ann5.scala +run/reify_anonymous.scala +run/reify-each-node-type.scala +run/reify_copypaste2.scala +run/reify_closure3a.scala +run/reify_copypaste1.scala +run/reify_complex.scala +run/reify_for1.scala +run/reify_getter.scala +run/reify_implicits-new.scala +run/reify_inner1.scala +run/reify_implicits-old.scala +run/reify_lazyunit.scala +run/reify_lazyevaluation.scala +run/reify_maps_newpatmat.scala +run/reify_metalevel_breach_+0_refers_to_1.scala +run/reify_metalevel_breach_-1_refers_to_0_a.scala +run/reify_metalevel_breach_-1_refers_to_0_b.scala +run/reify_nested_outer_refers_to_global.scala +run/reify_newimpl_04.scala +run/reify_newimpl_14.scala +run/reify_newimpl_11.scala +run/reify_newimpl_18.scala +run/reify_newimpl_19.scala +run/reify_newimpl_31.scala +run/reify_newimpl_21.scala +run/reify_newimpl_36.scala +run/reify_newimpl_39.scala +run/reify_newimpl_40.scala +run/reify_newimpl_49.scala +run/reify_newimpl_50.scala +run/reify_newimpl_52.scala +run/reify_renamed_term_basic.scala +run/reify_renamed_term_local_to_reifee.scala +run/reify_renamed_term_overloaded_method.scala +run/reify_renamed_type_basic.scala +run/reify_renamed_type_local_to_reifee.scala +run/reify_renamed_type_spliceable.scala +run/reify_typerefs_1a.scala +run/reify_timeofday.scala +run/reify_renamed_term_t5841.scala + +# Uses refletction indirectly through +# scala.runtime.ScalaRunTime.replStringOf +run/t6634.scala + +# Using reflection to invoke macros. These tests actually don't require +# or test reflection, but use it to separate compilation units nicely. +# It's a pity we cannot use them + +run/macro-abort-fresh +run/macro-expand-varargs-explicit-over-nonvarargs-bad +run/macro-invalidret-doesnt-conform-to-def-rettype +run/macro-invalidret-nontypeable +run/macro-invalidusage-badret +run/macro-invalidusage-partialapplication +run/macro-invalidusage-partialapplication-with-tparams +run/macro-reflective-ma-normal-mdmi +run/macro-reflective-mamd-normal-mi + +# Using macros, but indirectly creating calls to reflection +run/macro-reify-unreify + +# Using Enumeration in a way we cannot fix + +run/enums.scala +run/t3719.scala + +# Expecting some particular value of hashCode() + +run/MeterCaseClass.scala +run/t5608.scala +run/caseClassHash.scala +run/Meter.scala + +# Exceptions that become JavaScriptException + +run/pf-catch.scala +run/exceptions-2.scala +run/exceptions-nest.scala + +# Expecting unsupported exceptions (e.g. ArrayIndexOutOfBounds) +run/optimizer-array-load.scala + +# Playing with classfile format + +run/classfile-format-51.scala +run/classfile-format-52.scala + +# Concurrent collections (TrieMap) +# has too much stuff implemented in *.java, so no support +run/triemap-hash.scala + +# Using parallel collections + +run/t5375.scala +run/t4894.scala +run/ctries-new +run/collection-conversions.scala +run/concurrent-map-conversions.scala +run/t4761.scala +run/concurrent-stream.scala +run/t7498.scala +run/t6448.scala +run/ctries-old +run/map_java_conversions.scala +run/parmap-ops.scala +run/pc-conversions.scala +run/t4459.scala +run/t4608.scala +run/t4723.scala +run/t4895.scala +run/t6052.scala +run/t6410.scala +run/t6467.scala +run/t6908.scala + +# Using scala.xml + +run/t4124.scala + +# Using Swing + +run/t3613.scala + +# Using the REPL + +run/t4285.scala +run/constant-type.scala +run/repl-bare-expr.scala +run/repl-parens.scala +run/repl-assign.scala +run/t5583.scala +run/treePrint.scala +run/constrained-types.scala +run/repl-power.scala +run/t4710.scala +run/repl-paste.scala +run/repl-reset.scala +run/repl-paste-3.scala +run/t6329_repl.scala +run/t6273.scala +run/repl-paste-2.scala +run/t5655.scala +run/t5072.scala +run/repl-colon-type.scala +run/kind-repl-command.scala +run/repl-trim-stack-trace.scala +run/t4594-repl-settings.scala +run/repl-save.scala +run/repl-paste-raw.scala +run/repl-paste-4.scala +run/t7801.scala +run/repl-backticks.scala +run/t6633.scala + +# Using the Repl (scala.tools.partest.ReplTest) +run/class-symbol-contravariant.scala +run/lub-visibility.scala +run/macro-bundle-repl.scala +run/macro-repl-basic.scala +run/macro-repl-dontexpand.scala +run/macro-system-properties.scala +run/reflection-equality.scala +run/reflection-repl-elementary.scala +run/reify_newimpl_26.scala +run/repl-javap-app.scala +run/repl-out-dir.scala +run/repl-term-macros.scala +run/repl-transcript.scala +run/repl-type-verbose.scala +run/t3376.scala +run/t4025.scala +run/t4172.scala +run/t4216.scala +run/t4542.scala +run/t4671.scala +run/t5256d.scala +run/t5535.scala +run/t5537.scala +run/t5789.scala +run/t6086-repl.scala +run/t6146b.scala +run/t6187.scala +run/t6320.scala +run/t6381.scala +run/t6434.scala +run/t6439.scala +run/t6507.scala +run/t6549.scala +run/t6937.scala +run/t7185.scala +run/t7319.scala +run/t7482a.scala +run/t7634.scala +run/t7747-repl.scala +run/t7805-repl-i.scala +run/tpeCache-tyconCache.scala +run/repl-empty-package +run/repl-javap-def.scala +run/repl-javap-fun.scala +run/repl-javap-mem.scala +run/repl-javap-memfun.scala +run/repl-javap-more-fun.scala +run/repl-javap-outdir +run/repl-javap.scala +run/repl-javap-outdir-funs +run/t6329_repl_bug.scala + +# Using Scala Script (partest.ScriptTest) + +run/t7711-script-args.scala + +# Using the compiler API + +run/t2512.scala +run/analyzerPlugins.scala +run/test-cpp.scala +run/compiler-asSeenFrom.scala +run/t5603.scala +run/t6440.scala +run/t5545.scala +run/existentials-in-compiler.scala +run/global-showdef.scala +run/inline-ex-handlers.scala +run/stream_length.scala +run/annotatedRetyping.scala +run/imain.scala +run/existential-rangepos.scala +run/delambdafy_uncurry_byname_inline.scala +run/delambdafy_uncurry_byname_method.scala +run/delambdafy_uncurry_inline.scala +run/delambdafy_t6555.scala +run/delambdafy_uncurry_method.scala +run/delambdafy_t6028.scala +run/memberpos.scala +run/programmatic-main.scala +run/reflection-names.scala +run/settings-parse.scala +run/sm-interpolator.scala +run/t1501.scala +run/t1500.scala +run/sammy_java8.scala +run/t1618.scala +run/t2464 +run/t4072.scala +run/t5064.scala +run/t5313.scala +run/t5385.scala +run/t5699.scala +run/t5717.scala +run/t5940.scala +run/t6028.scala +run/t6194.scala +run/t6288b-jump-position.scala +run/t6669.scala +run/t6745-2.scala +run/t6955.scala +run/t6956.scala +run/t7096.scala +run/t7271.scala +run/t7337.scala +run/t7398.scala +run/t7569.scala +run/t7852.scala +run/t7817-tree-gen.scala +run/t7825.scala +run/t7933.scala +run/t7843-jsr223-service.scala + +# partest.DirectTest +run/t6288.scala +run/t6331.scala +run/t6440b.scala +run/t6555.scala +run/t7876.scala +run/typetags_without_scala_reflect_typetag_lookup.scala +run/dynamic-updateDynamic.scala +run/dynamic-selectDynamic.scala +run/dynamic-applyDynamic.scala +run/dynamic-applyDynamicNamed.scala +run/t4841-isolate-plugins +run/large_code.scala +run/macroPlugins-namerHooks.scala +run/t4287inferredMethodTypes.scala +run/t4841-no-plugin.scala +run/t4332.scala +run/t8029.scala +run/t8046 +run/t5905-features.scala +run/t5905b-features.scala + +# partest.BytecodeTest +run/t6546 +run/t7106 +run/t7974 + +# Using .java source files + +run/t4317 +run/t4238 +run/t2296c +run/t4119 +run/t4283 +run/t4891 +run/t6168 +run/t6168b +run/t6240a +run/t6240b +run/t6548 +run/t6989 +run/t7008 +run/t7246 +run/t7246b +run/t7359 +run/t7439 +run/t7455 +run/t7510 +run/t7582-private-within +run/t7582 +run/t7582b +run/t3897 +run/t7374 +run/t3452e +run/t3452g +run/t3452d +run/t3452b-bcode +run/t3452b +run/t3452a +run/t1430 +run/t4729 +run/t8442 + +# Using scala-script +run/t7791-script-linenums.scala + +# Suffers from bug in Node.js (https://github.com/joyent/node/issues/7528) +run/range-unit.scala + +### Incorrect partests ### +# Badly uses constract of Console.print (no flush) +run/t429.scala diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/BuglistedTests.txt b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/BuglistedTests.txt new file mode 100644 index 0000000..42c6146 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/BuglistedTests.txt @@ -0,0 +1,4 @@ +# The tests in this file should pass but have never passed so far +# use scala.tools.partest.scalajs.testunknownonly to only run tests +# which are neither in BuglistedTests.txt, WhitelistedTests.txt or +# BlacklistedTests.txt diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/NoDCEWarn.txt b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/NoDCEWarn.txt new file mode 100644 index 0000000..cc5aff0 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/NoDCEWarn.txt @@ -0,0 +1,8 @@ +Ljava_math_MathContext$ +Ljava_math_BigDecimal$ +Ljava_math_BigDecimal +Ljava_math_BigInteger$ +jl_Class$ +jl_Class.getClassLoader__jl_ClassLoader +jl_Class.getPackage__jl_Package +jl_Class.getInterfaces__Ajl_Class diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/WhitelistedTests.txt b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/WhitelistedTests.txt new file mode 100644 index 0000000..1483b1e --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/WhitelistedTests.txt @@ -0,0 +1,2949 @@ +pos/spec-super.scala +pos/t1035.scala +pos/t5897.scala +pos/irrefutable.scala +pos/spec-partialmap.scala +pos/tcpoly_seq.scala +pos/partialfun.scala +pos/t2795-new.scala +pos/clsrefine.scala +pos/t0774 +pos/t1070.scala +pos/t5957 +pos/looping-jsig.scala +pos/t3274.scala +pos/spec-fields-old.scala +pos/t262.scala +pos/t7486.scala +pos/t2261.scala +pos/t6600.scala +pos/t4786.scala +pos/t5406.scala +pos/tcpoly_late_method_params.scala +pos/t2726 +pos/pos-bug1210.scala +pos/t3312.scala +pos/manifest1-old.scala +pos/gadt-gilles.scala +pos/t4842.scala +pos/ted.scala +pos/NoCyclicReference.scala +pos/t3568.scala +pos/t0030.scala +pos/t2635.scala +pos/t7232b +pos/t0017.scala +pos/t812.scala +pos/t2179.scala +pos/t651.scala +pos/spurious-overload.scala +pos/t758.scala +pos/t4760.scala +pos/t1672.scala +pos/mixins.scala +pos/patterns.scala +pos/t1260.scala +pos/t6551.scala +pos/t2060.scala +pos/t6575a.scala +pos/t1318.scala +pos/t4266.scala +pos/t0695 +pos/protected-static +pos/t5738.scala +pos/t1226.scala +pos/t5013 +pos/t6215.scala +pos/t5692b +pos/traits.scala +pos/t2994a.scala +pos/t3371.scala +pos/t613.scala +pos/t6499.scala +pos/xlint1.scala +pos/t1150 +pos/sealed-final.scala +pos/test4a.scala +pos/t2664.scala +pos/t3528.scala +pos/t3174.scala +pos/t6994.scala +pos/t4812.scala +pos/t5777.scala +pos/t5223.scala +pos/t439.scala +pos/t3079.scala +pos/t5829.scala +pos/t0036.scala +pos/scoping2.scala +pos/t4717.scala +pos/t4257.scala +pos/t1210a.scala +pos/getClassType.scala +pos/t5330.scala +pos/t4524.scala +pos/t2945.scala +pos/t6562.scala +pos/t0273.scala +pos/override-object-yes.scala +pos/t7426.scala +pos/t6601 +pos/t3076 +pos/seq-ordering.scala +pos/spec-groups.scala +pos/t296.scala +pos/t5545 +pos/spec-multiplectors.scala +pos/t1789.scala +pos/t2569 +pos/ksbug1.scala +pos/t0599.scala +pos/local-objects.scala +pos/t0081.scala +pos/t5756.scala +pos/t7126.scala +pos/t7716.scala +pos/t2797.scala +pos/t5399.scala +pos/t1101 +pos/t767.scala +pos/contrib467.scala +pos/t7532b +pos/self-type-override.scala +pos/t4853.scala +pos/t839.scala +pos/t5644 +pos/t5853.scala +pos/t5178.scala +pos/unapplyNeedsMemberType.scala +pos/t5390.scala +pos/t6575b.scala +pos/t151.scala +pos/t2665.scala +pos/t5120.scala +pos/erasure-nsquared.scala +pos/arrays3.scala +pos/t3136.scala +pos/inline-access-levels +pos/t3972.scala +pos/t2591.scala +pos/t3486 +pos/variances-flip.scala +pos/annotated-original +pos/typesafecons.scala +pos/stable.scala +pos/t1996.scala +pos/t3037.scala +pos/t1711 +pos/t3374.scala +pos/t0029.scala +pos/t3278.scala +pos/matthias3.scala +pos/t5546.scala +pos/t4020.scala +pos/matthias4.scala +pos/value-class-override-spec.scala +pos/arrays2.scala +pos/t5119.scala +pos/t2613.scala +pos/t4070b.scala +pos/virtpatmat_exist_uncurry.scala +pos/modules1.scala +pos/spec-constr-new.scala +pos/t6335.scala +pos/t675.scala +pos/t0644.scala +pos/t5892.scala +pos/t360.scala +pos/override.scala +pos/t1798.scala +pos/strip-tvars-for-lubbasetypes.scala +pos/hk-infer.scala +pos/t2119.scala +pos/t0231.scala +pos/t1459 +pos/t1381-new.scala +pos/t2610.scala +pos/t2708.scala +pos/t5604b +pos/t3951 +pos/t361.scala +pos/t319.scala +pos/largecasetest.scala +pos/switchUnbox.scala +pos/typetags.scala +pos/java-access-pos +pos/t803.scala +pos/t3898.scala +pos/t5692a +pos/t2421.scala +pos/t1102 +pos/t0654.scala +pos/exhaust_alternatives.scala +pos/t807.scala +pos/t5702-pos-infix-star.scala +pos/t1186 +pos/t1439.scala +pos/t7427.scala +pos/virtpatmat_binding_opt.scala +pos/t247.scala +pos/abstract.scala +pos/gen-traversable-methods.scala +pos/t2795-old.scala +pos/t5639 +pos/t2667.scala +pos/t2405.scala +pos/t1438.scala +pos/SI-7100.scala +pos/t1659.scala +pos/unchecked-a.scala +pos/t3636.scala +pos/t6745.scala +pos/t2809.scala +pos/t7022.scala +pos/t6447.scala +pos/t6367.scala +pos/t5846.scala +pos/lubs.scala +pos/t1987a.scala +pos/spec-arrays.scala +pos/virtpatmat_anonfun_for.scala +pos/listpattern.scala +pos/t5742.scala +pos/test5refine.scala +pos/switch-small.scala +pos/t5604 +pos/return_thistype.scala +pos/t348plus.scala +pos/t3420.scala +pos/t3440.scala +pos/maxim1.scala +pos/caseClassInMethod.scala +pos/t7239.scala +pos/t3833.scala +pos/t6675.scala +pos/t4402 +pos/t5953.scala +pos/t1152 +pos/t0591.scala +pos/t210.scala +pos/t7035.scala +pos/t5769.scala +pos/pmbug.scala +pos/t2331.scala +pos/t5240.scala +pos/t304.scala +pos/annotated-treecopy +pos/t2081.scala +pos/t0904.scala +pos/t7649.scala +pos/t3498-new.scala +pos/contrib701.scala +pos/t6624.scala +pos/t3924.scala +pos/t374.scala +pos/t1642 +pos/t1591_pos.scala +pos/depmet_implicit_oopsla_session_2.scala +pos/t5899.scala +pos/thistype.scala +pos/t4176b.scala +pos/elidable-tparams.scala +pos/lambdalift.scala +pos/nothing_manifest_disambig-old.scala +pos/t372.scala +pos/t5399a.scala +pos/t2782.scala +pos/patmat-extract-tparam.scala +pos/t4114.scala +pos/unapplyVal.scala +pos/t2486.scala +pos/t5877b.scala +pos/t0625.scala +pos/t6358_2.scala +pos/viewtest1.scala +pos/t1237.scala +pos/scala-singleton.scala +pos/t1254 +pos/t5504 +pos/bounds.scala +pos/t3631.scala +pos/t3177.scala +pos/unapplyContexts2.scala +pos/t0438.scala +pos/t1642b.scala +pos/inferbroadtype.scala +pos/t1858.scala +pos/t3731.scala +pos/t6963c.scala +pos/classtag-pos.scala +pos/t6221.scala +pos/t3343.scala +pos/spec-asseenfrom.scala +pos/t604.scala +pos/spec-example1.scala +pos/t0786.scala +pos/annot-inner.scala +pos/t5886.scala +pos/t1056.scala +pos/t294 +pos/spec-Function1.scala +pos/t1836 +pos/spec-private.scala +pos/depmet_implicit_tpbetareduce.scala +pos/exhaust_2.scala +pos/t7532 +pos/t5175.scala +pos/t802.scala +pos/t5809.scala +pos/tcpoly_typesub.scala +pos/t6029.scala +pos/contextbounds-implicits-new.scala +pos/t3480.scala +pos/patterns3.scala +pos/caseaccs.scala +pos/spec-sparsearray-old.scala +pos/patterns1213.scala +pos/spec-traits.scala +pos/t0020.scala +pos/cycle +pos/t5968.scala +pos/typealiases.scala +pos/init.scala +pos/t697.scala +pos/t2693.scala +pos/t2377 +pos/unapplyGeneric.scala +pos/t1385.scala +pos/t3363-old.scala +pos/t1236.scala +pos/t0068.scala +pos/t4052.scala +pos/lambdalift1.scala +pos/z1730.scala +pos/variances-local.scala +pos/virtpatmat_gadt_array.scala +pos/t2421_delitedsl.scala +pos/t5626.scala +pos/t690.scala +pos/t711.scala +pos/t6547.scala +pos/t1937 +pos/t3999 +pos/SI-7060.scala +pos/t2305.scala +pos/t2168.scala +pos/t2660.scala +pos/t1693.scala +pos/inliner2.scala +pos/t2799.scala +pos/t6966.scala +pos/t1001.scala +pos/S5.scala +pos/t0301.scala +pos/t1048.scala +pos/t415.scala +pos/t6386.scala +pos/t2187.scala +pos/hashhash-overloads.scala +pos/t6921.scala +pos/t0227.scala +pos/t6556.scala +pos/t3946 +pos/t1053.scala +pos/t1000.scala +pos/t0586.scala +pos/t7011.scala +pos/t7329.scala +pos/t4975.scala +pos/t1131.scala +pos/t1027.scala +pos/t2913.scala +pos/t3494.scala +pos/t5606.scala +pos/t4716.scala +pos/tcpoly_gm.scala +pos/t4859.scala +pos/t514.scala +pos/lexical.scala +pos/t2624.scala +pos/t4036.scala +pos/t2741 +pos/t703.scala +pos/five-dot-f.scala +pos/t805.scala +pos/strings.scala +pos/t2433 +pos/t6925.scala +pos/t1085.scala +pos/t7461 +pos/t1942 +pos/spec-lists.scala +pos/t3349 +pos/tcpoly_infer_ticket474.scala +pos/t1614 +pos/virtpatmat_reach_const.scala +pos/t2194.scala +pos/t6976 +pos/t1560.scala +pos/t6891.scala +pos/t3883.scala +pos/infersingle.scala +pos/gui.scala +pos/t1164.scala +pos/t3175-pos.scala +pos/t4336.scala +pos/annotations2.scala +pos/proj-rec-test.scala +pos/t2973.scala +pos/t1123.scala +pos/t6205.scala +pos/t5727.scala +pos/t6537.scala +pos/t6712.scala +pos/t3866.scala +pos/t4831.scala +pos/selftails.scala +pos/t397.scala +pos/spec-vector.scala +pos/t7233b.scala +pos/t1391.scala +pos/spec.scala +pos/t3106.scala +pos/contextbounds-implicits-old.scala +pos/packageobjs.scala +pos/michel3.scala +pos/t628.scala +pos/collections.scala +pos/tcpoly_boundedmonad.scala +pos/t7668.scala +pos/t0032.scala +pos/t0069.scala +pos/t4345.scala +pos/t3521 +pos/t3071.scala +pos/tcpoly_infer_easy.scala +pos/t289.scala +pos/t4365 +pos/rangepos-anonapply.scala +pos/t5033.scala +pos/lambda.scala +pos/S8.scala +pos/t6014.scala +pos/t1785.scala +pos/t6034.scala +pos/t7433.scala +pos/imp2-pos.scala +pos/t0504.scala +pos/t1272.scala +pos/t0612 +pos/value-class-override-no-spec.scala +pos/overloaded-unapply.scala +pos/t5859.scala +pos/chang +pos/localmodules.scala +pos/t4237.scala +pos/rangepos-patmat.scala +pos/t1974.scala +pos/t0054.scala +pos/michel2.scala +pos/t0770.scala +pos/t1146.scala +pos/t2441pos.scala +pos/t5099.scala +pos/tcpoly_seq_typealias.scala +pos/t946.scala +pos/tcpoly_infer_ticket1864.scala +pos/t4579.scala +pos/t4737 +pos/t7377b.scala +pos/t616.scala +pos/t201.scala +pos/t6355pos.scala +pos/escapes2.scala +pos/t1675.scala +pos/t3890.scala +pos/t6040.scala +pos/spec-tailcall.scala +pos/existentials.scala +pos/t5317.scala +pos/t7782b.scala +pos/t4758.scala +pos/t7296.scala +pos/t6896.scala +pos/cls1.scala +pos/t402.scala +pos/gosh.scala +pos/t2619.scala +pos/javaConversions-2.10-regression.scala +pos/t759.scala +pos/t5259.scala +pos/t5130.scala +pos/t5156.scala +pos/t0905.scala +pos/package-implicit +pos/t2669.scala +pos/trait-parents.scala +pos/virtpatmat_exhaust.scala +pos/patterns1.scala +pos/t7014 +pos/t1231 +pos/t1751 +pos/t7233.scala +pos/t6022.scala +pos/tcpoly_checkkinds_mix.scala +pos/depmet_implicit_norm_ret.scala +pos/package-case.scala +pos/philippe4.scala +pos/michel6.scala +pos/t4188.scala +pos/t3936 +pos/t1280.scala +pos/t6722.scala +pos/t796.scala +pos/t5542.scala +pos/t3927.scala +pos/t2293.scala +pos/t3800.scala +pos/t7285a.scala +pos/t927.scala +pos/t4494.scala +pos/t3864 +pos/ilya2 +pos/t2940 +pos/S1.scala +pos/tcpoly_wildcards.scala +pos/tryexpr.scala +pos/t6089b.scala +pos/depmet_implicit_oopsla_zipwith.scala +pos/t245.scala +pos/t6146.scala +pos/t1782 +pos/t851.scala +pos/spec-thistype.scala +pos/tcpoly_poly.scala +pos/t6815_import.scala +pos/t4649.scala +pos/t0453.scala +pos/t5020.scala +pos/ilya +pos/t2435.scala +pos/t1279a.scala +pos/t2171.scala +pos/t1957.scala +pos/gadts2.scala +pos/t3567 +pos/Z.scala +pos/t1203b +pos/nested2.scala +pos/t1896 +pos/viewtest2.scala +pos/t5541.scala +pos/existentials-harmful.scala +pos/t4063.scala +pos/t6485a +pos/t1208.scala +pos/t5041.scala +pos/unapplyComplex.scala +pos/t3384.scala +pos/t4112.scala +pos/t788.scala +pos/hklub0.scala +pos/t757.scala +pos/t1197 +pos/t359.scala +pos/t5667.scala +pos/t1107a.scala +pos/virtpatmat_castbinder.scala +pos/t267.scala +pos/t3419 +pos/t3861.scala +pos/t6797.scala +pos/spec-localdefs.scala +pos/t3404 +pos/t4457_1.scala +pos/matthias5.scala +pos/spec-polymeth.scala +pos/kinds.scala +pos/t2310.scala +pos/t6552.scala +pos/valdefs.scala +pos/hkarray.scala +pos/homonym.scala +pos/t1235 +pos/t3429 +pos/t0053.scala +pos/depmet_implicit_chaining_zw.scala +pos/virtpatmat_partialfun_nsdnho.scala +pos/t6664.scala +pos/ticket2251.scala +pos/t3495.scala +pos/super +pos/t121.scala +pos/javaConversions-2.10-ambiguity.scala +pos/t1803.scala +pos/t5877.scala +pos/t0085.scala +pos/t3582.scala +pos/t2939.scala +pos/t1422_pos.scala +pos/manifest1-new.scala +pos/t7505.scala +pos/t5720-ownerous.scala +pos/misc-unapply_pos.scala +pos/tcpoly_variance_pos.scala +pos/t5127.scala +pos/t6123-explaintypes-implicits.scala +pos/t2764 +pos/presuperContext.scala +pos/spec-simple.scala +pos/t3120 +pos/t5729.scala +pos/tcpoly_infer_ticket716.scala +pos/tcpoly_bounds1.scala +pos/t7369.scala +pos/imports-pos.scala +pos/t5654.scala +pos/t0123.scala +pos/raw-map +pos/t5330b.scala +pos/t6485b +pos/t6072.scala +pos/t5692c.scala +pos/t3430.scala +pos/tcpoly_param_scoping.scala +pos/t6204-b.scala +pos/attachments-typed-another-ident +pos/t5359.scala +pos/ticket2197.scala +pos/t720.scala +pos/t2130-2.scala +pos/t2260.scala +pos/t0304.scala +pos/t464.scala +pos/spec-maps.scala +pos/annotDepMethType.scala +pos/t6117.scala +pos/t911.scala +pos/t757a.scala +pos/t2504.scala +pos/t1381-old.scala +pos/t1232 +pos/needstypeearly.scala +pos/moduletrans.scala +pos/t4957.scala +pos/kinzer.scala +pos/t318.scala +pos/widen-existential.scala +pos/t0095.scala +pos/t566.scala +pos/tcpoly_overloaded.scala +pos/t7516 +pos/t7232 +pos/t698.scala +pos/t0002.scala +pos/t0288 +pos/t2994b.scala +pos/cls.scala +pos/t3622 +pos/t3671.scala +pos/tcpoly_subst.scala +pos/t5703 +pos/depmet_implicit_oopsla_session_simpler.scala +pos/t5022.scala +pos/builders.scala +pos/spec-foo.scala +pos/t756.scala +pos/t1569.scala +pos/implicit-unwrap-tc.scala +pos/t3688.scala +pos/t5198.scala +pos/t432.scala +pos/t6022b.scala +pos/channels.scala +pos/t1075.scala +pos/null.scala +pos/t1840 +pos/t6479.scala +pos/t6311.scala +pos/t0039.scala +pos/t1119.scala +pos/t573.scala +pos/t1136.scala +pos/t3938 +pos/spec-sealed.scala +pos/tcpoly_return_overriding.scala +pos/t3582b.scala +pos/t229.scala +pos/t3498-old.scala +pos/t531.scala +pos/t4545.scala +pos/t6651.scala +pos/t2133.scala +pos/tinondefcons.scala +pos/t6157.scala +pos/t6358.scala +pos/t7690.scala +pos/t5779-numeq-warn.scala +pos/list-extractor.scala +pos/t892.scala +pos/t2127.scala +pos/t7180.scala +pos/nullary_poly.scala +pos/virtpatmat_exist3.scala +pos/t1176 +pos/spec-funs.scala +pos/specialize10.scala +pos/t6514.scala +pos/exhaustive_heuristics.scala +pos/t0066.scala +pos/t460.scala +pos/t2130-1.scala +pos/t124.scala +pos/annotations.scala +pos/pat_gilles.scala +pos/array-interfaces.scala +pos/t6210.scala +pos/t3792.scala +pos/implicits-old.scala +pos/t389.scala +pos/t115.scala +pos/virtpatmat_exhaust_unchecked.scala +pos/scoping3.scala +pos/t6033.scala +pos/depmet_implicit_oopsla_session.scala +pos/t602.scala +pos/test5.scala +pos/t611.scala +pos/t5932.scala +pos/t4910.scala +pos/unapplySeq.scala +pos/t344.scala +pos/t3363-new.scala +pos/t4018.scala +pos/t4553.scala +pos/t5082.scala +pos/t3869.scala +pos/t3836.scala +pos/tcpoly_typeapp.scala +pos/t1409 +pos/nonlocal-unchecked.scala +pos/t0082.scala +pos/z1720.scala +pos/t7232c +pos/t2018.scala +pos/t3943 +pos/t2187-2.scala +pos/unicode-decode.scala +pos/t4757 +pos/t0710.scala +pos/t0305.scala +pos/t160.scala +pos/t7591 +pos/simplelists.scala +pos/List1.scala +pos/t516.scala +pos/t6648.scala +pos/t5165 +pos/t0055.scala +pos/t4744 +pos/t7377 +pos/t5726.scala +pos/t0091.scala +pos/t6595.scala +pos/compile.scala +pos/depmet_1_pos.scala +pos/t7364 +pos/philippe3.scala +pos/spec-doubledef-old.scala +pos/t4651.scala +pos/tcpoly_infer_implicit_tuple_wrapper.scala +pos/t6274.scala +pos/tcpoly_infer_explicit_tuple_wrapper.scala +pos/ticket2201.scala +pos/spec-fields-new.scala +pos/optmatch.scala +pos/t7517.scala +pos/t3560.scala +pos/t0165.scala +pos/t0872.scala +pos/t522.scala +pos/t2234.scala +pos/t5031_2.scala +pos/tcpoly_method.scala +pos/t6482.scala +pos/pos-bug1241.scala +pos/implicits-new.scala +pos/t2484.scala +pos/t2425.scala +pos/t1049.scala +pos/michel4.scala +pos/t5958.scala +pos/virtpatmat_instof_valuetype.scala +pos/spec-t6286.scala +pos/t873.scala +pos/t3137.scala +pos/Transactions.scala +pos/t0064.scala +pos/t7486-named.scala +pos/t5444.scala +pos/simple-exceptions.scala +pos/t1006.scala +pos/t7200b.scala +pos/t3777.scala +pos/t4840.scala +pos/t211.scala +pos/nullary.scala +pos/michel1.scala +pos/t5031_3 +pos/typealias_dubious.scala +pos/spec-doubledef-new.scala +pos/philippe1.scala +pos/thistypes.scala +pos/t3570.scala +pos/t6516.scala +pos/context.scala +pos/t3808.scala +pos/philippe2.scala +pos/constfold.scala +pos/t1292.scala +pos/t1147.scala +pos/t404.scala +pos/t4430.scala +pos/A.scala +pos/spec-partially.scala +pos/t5796.scala +pos/t2409 +pos/t284-pos.scala +pos/t5313.scala +pos/t2464 +pos/t1591b.scala +pos/hk-match +pos/t595.scala +pos/t6846.scala +pos/t6162-inheritance.scala +pos/relax_implicit_divergence.scala +pos/patterns2.scala +pos/t4692.scala +pos/t3837.scala +pos/t661.scala +pos/t2810.scala +pos/depexists.scala +pos/virtpatmat_exist4.scala +pos/t5245.scala +pos/t7190.scala +pos/isApplicableSafe.scala +pos/t6204-a.scala +pos/t0076.scala +pos/t1756.scala +pos/t1745 +pos/t6091.scala +pos/t0154.scala +pos/t530.scala +pos/t2094.scala +pos/t1034.scala +pos/t6084.scala +pos/t2454.scala +pos/t2956 +pos/tcpoly_ticket2096.scala +pos/attachments-typed-ident +pos/polymorphic-case-class.scala +pos/t252.scala +pos/spec-constr-old.scala +pos/t2421c.scala +pos/t122.scala +pos/t6574.scala +pos/t3859.scala +pos/spec-params-old.scala +pos/t1196 +pos/t4593.scala +pos/t596.scala +pos/t615.scala +pos/t7689.scala +pos/t3960.scala +pos/t3986.scala +pos/exbound.scala +pos/t2545.scala +pos/t1722 +pos/t159.scala +pos/t3272.scala +pos/t6301.scala +pos/t2794.scala +pos/t3048.scala +pos/t4970.scala +pos/t607.scala +pos/FPTest.scala +pos/test1.scala +pos/t3252.scala +pos/t4176.scala +pos/t112606A.scala +pos/t2183.scala +pos/t430-feb09.scala +pos/t6275.scala +pos/t1832.scala + +neg/volatile_no_override.scala +neg/t800.scala +neg/t5426.scala +neg/t2462a.scala +neg/t2641.scala +neg/classtags_dont_use_typetags.scala +neg/t5031 +neg/t2275b.scala +neg/macro-qmarkqmarkqmark.scala +neg/t4879.scala +neg/t5956.scala +neg/t4196.scala +neg/reify_ann2b.scala +neg/t6666b.scala +neg/warn-unused-privates.scala +neg/t6928.scala +neg/t6337.scala +neg/sealed-java-enums.scala +neg/t563.scala +neg/t900.scala +neg/deadline-inf-illegal.scala +neg/t766.scala +neg/t5429.scala +neg/overloaded-implicit.scala +neg/t875.scala +neg/abstract-class-error +neg/unchecked2.scala +neg/predef-masking.scala +neg/viewtest.scala +neg/macro-noexpand +neg/varargs.scala +neg/t963b.scala +neg/t909.scala +neg/sensitive2.scala +neg/t5390b.scala +neg/abstraction-from-volatile-type-error.scala +neg/macro-exception +neg/t4431.scala +neg/t5689.scala +neg/valueclasses.scala +neg/overload.scala +neg/t0204.scala +neg/t908.scala +neg/t750 +neg/patmatexhaust.scala +neg/macro-invalidusage-badtargs +neg/t1168.scala +neg/t5761.scala +neg/t0503.scala +neg/t7235.scala +neg/t1215.scala +neg/primitive-sigs-1 +neg/t5578.scala +neg/names-defaults-neg-warn.scala +neg/t6436b.scala +neg/t3098 +neg/t910.scala +neg/parstar.scala +neg/t4568.scala +neg/newpat_unreachable.scala +neg/warn-unused-imports.scala +neg/t1181.scala +neg/t5903c +neg/t7294.scala +neg/t4091.scala +neg/t5452-old.scala +neg/t5696.scala +neg/t0209.scala +neg/t2910.scala +neg/t7388.scala +neg/noMember2.scala +neg/no-predef.scala +neg/t6952.scala +neg/t1909b.scala +neg/abstract-report2.scala +neg/t5318.scala +neg/t6074.scala +neg/t7171.scala +neg/abstract-vars.scala +neg/unchecked-impossible.scala +neg/variances-refinement.scala +neg/t3453.scala +neg/t5189.scala +neg/t4302.scala +neg/xmltruncated7.scala + +run/t7249.scala +run/t3563.scala +run/t6111.scala +run/classtags_multi.scala +run/t5201.scala +run/checked.scala +run/valueclasses-classtag-basic.scala +run/t7171.scala +run/t5053.scala +run/t4535.scala +run/t5923d +run/t7291.scala +run/partialfun.scala +run/macro-term-declared-in-package-object +run/mapValues.scala +run/gadts.scala +run/t2386-new.scala +run/virtpatmat_stringinterp.scala +run/t657.scala +run/t0017.scala +run/t5713 +run/t576.scala +run/t3580.scala +run/virtpatmat_partial.scala +run/t6646.scala +run/mixins.scala +run/t1672.scala +run/macro-expand-implicit-macro-has-implicit +run/tuple-match.scala +run/t7039.scala +run/virtpatmat_opt_sharing.scala +run/virtpatmat_casting.scala +run/t2176.scala +run/eta-expand-star2.scala +run/macro-impl-relaxed +run/intmap.scala +run/t751.scala +run/t1591.scala +run/macro-typecheck-implicitsdisabled +run/t6911.scala +run/t5604.scala +run/macro-term-declared-in-default-param +run/collection-stacks.scala +run/multi-array.scala +run/t4560b.scala +run/buffer-slice.scala +run/t5629.scala +run/t6690.scala +run/matchonstream.scala +run/t3603.scala +run/lazy-exprs.scala +run/macro-quasiquotes +run/Course-2002-13.scala +run/t6337a.scala +run/exoticnames.scala +run/t0936.scala +run/existentials3-old.scala +run/runtime-richChar.scala +run/t6272.scala +run/t7215.scala +run/t1939.scala +run/ReverseSeqView.scala +run/lazy-leaks.scala +run/t0048.scala +run/t3994.scala +run/t2241.scala +run/t627.scala +run/t5966.scala +run/getClassTest-valueClass.scala +run/t3619.scala +run/t1300.scala +run/t2177.scala +run/t3760.scala +run/t1829.scala +run/macro-expand-implicit-macro-is-view +run/t889.scala +run/QueueTest.scala +run/t4537 +run/t3699.scala +run/valueclasses-manifest-basic.scala +run/t1192.scala +run/macro-expand-tparams-bounds +run/macro-expand-nullary-generic +run/t1434.scala +run/t6443-varargs.scala +run/macro-term-declared-in-trait +run/t4080.scala +run/t2236-old.scala +run/matcharraytail.scala +run/infiniteloop.scala +run/t5733.scala +run/virtpatmat_nested_lists.scala +run/t5158.scala +run/t6695.scala +run/t6070.scala +run/t4558.scala +run/exc2.scala +run/patmat-behavior-2.scala +run/overloads.scala +run/iterator-iterate-lazy.scala +run/t6957.scala +run/transform.scala +run/t5500.scala +run/t6663.scala +run/castsingleton.scala +run/t4147.scala +run/virtpatmat_staging.scala +run/t4565_1.scala +run/t5588.scala +run/run-bug4840.scala +run/t3496.scala +run/t5867.scala +run/search.scala +run/t3112.scala +run/hashsetremove.scala +run/interop_manifests_are_classtags.scala +run/t6443.scala +run/macro-expand-tparams-prefix +run/contrib674.scala +run/t3508.scala +run/t4300.scala +run/virtpatmat_typed.scala +run/macro-term-declared-in-class-object +run/map_test.scala +run/t5040.scala +run/t4827b.scala +run/lift-and-unlift.scala +run/t6574b.scala +run/t7240 +run/t3984.scala +run/virtpatmat_tailcalls_verifyerror.scala +run/macro-term-declared-in-class-class +run/emptypf.scala +run/t6631.scala +run/t6104.scala +run/t2818.scala +run/t3761-overload-byname.scala +run/t2526.scala +run/phantomValueClass.scala +run/t3126.scala +run/arybufgrow.scala +run/t3980.scala +run/t7375b +run/t6077_patmat_cse_irrefutable.scala +run/classmanifests_new_core.scala +run/t3395.scala +run/name-based-patmat.scala +run/inliner-infer.scala +run/t5171.scala +run/t3726.scala +run/null-hash.scala +run/t4027.scala +run/t2544.scala +run/patmatnew.scala +run/t5923b +run/t7242.scala +run/classtags_core.scala +run/streamWithFilter.scala +run/t3038b.scala +run/macro-expand-varargs-explicit-over-nonvarargs-good +run/macro-divergence-spurious +run/macro-duplicate +run/t2958.scala +run/patch-boundary.scala +run/t2333.scala +run/lazy-override-run.scala +run/macro-quasiinvalidbody-c +run/t5037.scala +run/takeAndDrop.scala +run/t6126.scala +run/t0883.scala +run/t7617a +run/t4171.scala +run/empty-array.scala +run/t7198.scala +run/t493.scala +run/genericValueClass.scala +run/t0677-old.scala +run/t1373.scala +run/t4461.scala +run/t6011b.scala +run/t7584.scala +run/t3935.scala +run/t6928-run.scala +run/t744.scala +run/t3241.scala +run/blame_eye_triple_eee-double.scala +run/t3829.scala +run/t5577.scala +run/t5914.scala +run/t601.scala +run/t5610.scala +run/macro-basic-mamd-mi +run/t6150.scala +run/stringbuilder.scala +run/t7290.scala +run/t6888.scala +run/t6327.scala +run/virtpatmat_unapplyseq.scala +run/t4656.scala +run/macro-term-declared-in-method +run/macro-expand-implicit-macro-is-implicit +run/blame_eye_triple_eee-float.scala +run/t4482.scala +run/t5488.scala +run/matchemptyarray.scala +run/t3714.scala +run/richWrapperEquals.scala +run/t5328.scala +run/stream_flatmap_odds.scala +run/implicitclasses.scala +run/t6827.scala +run/t6394b +run/complicatedmatch.scala +run/valueclasses-classmanifest-basic.scala +run/unreachable.scala +run/caseclasses.scala +run/withIndex.scala +run/exc1.scala +run/amp.scala +run/t1423.scala +run/t594.scala +run/t6353.scala +run/byname.scala +run/vector1.scala +run/t5879.scala +run/t1048.scala +run/t5080.scala +run/t4190.scala +run/caseClassEquality.scala +run/macro-enclosures +run/collections-toSelf.scala +run/implicits.scala +run/finalvar.scala +run/lazy-locals.scala +run/t7231.scala +run/t0508.scala +run/t6628.scala +run/t6406-regextract.scala +run/t0911.scala +run/t4013c.scala +run/t3502.scala +run/t5648.scala +run/retclosure.scala +run/t2857.scala +run/t4859.scala +run/t5162.scala +run/t3038.scala +run/classof.scala +run/t4062.scala +run/unapplyArray.scala +run/t4297.scala +run/t5923a +run/iterators.scala +run/t1537.scala +run/boolexprs.scala +run/valueclasses-classtag-generic.scala +run/macro-term-declared-in-anonymous +run/tcpoly_monads.scala +run/t5407.scala +run/scan.scala +run/forvaleq.scala +run/null-and-intersect.scala +run/t7047 +run/t0607.scala +run/sequenceComparisons.scala +run/t4396.scala +run/macro-undetparams-consfromsls +run/t2029.scala +run/t1220.scala +run/option-fold.scala +run/t5284c.scala +run/macro-auto-duplicate +run/t3529.scala +run/t4697.scala +run/t2251.scala +run/t5300.scala +run/virtpatmat_valdef.scala +run/t2147.scala +run/virtpatmat_extends_product.scala +run/list_map.scala +run/t1333.scala +run/matchbytes.scala +run/valueclasses-classmanifest-existential.scala +run/records.scala +run/t3088.scala +run/macro-def-path-dependent +run/t6443-by-name.scala +run/t1044.scala +run/delay-good.scala +run/case-class-23.scala +run/weakconform.scala +run/patmat-bind-typed.scala +run/t4835.scala +run/t3097.scala +run/t405.scala +run/existentials.scala +run/t2876.scala +run/t4809.scala +run/t1427.scala +run/t6135.scala +run/t3575.scala +run/t5688.scala +run/t6900.scala +run/macro-expand-unapply-a +run/t6677b.scala +run/t7375a.scala +run/t7300.scala +run/t6246.scala +run/typed-annotated +run/elidable-noflags.scala +run/t0042.scala +run/t3050.scala +run/t4536.scala +run/NestedClasses.scala +run/t3877.scala +run/seqlike-kmp.scala +run/t5907.scala +run/t266.scala +run/missingparams.scala +run/t2255.scala +run/private-inline.scala +run/t3488.scala +run/t3950.scala +run/typealias_overriding.scala +run/constant-optimization.scala +run/t7507.scala +run/t6090.scala +run/iterator-concat.scala +run/t4582.scala +run/macro-term-declared-in-class +run/macro-typecheck-macrosdisabled2 +run/t3425.scala +run/t4935.scala +run/t3326.scala +run/boolord.scala +run/t1141.scala +run/virtpatmat_unapply.scala +run/t5971.scala +run/t3651.scala +run/macro-sip19-revised +run/pure-args-byname-noinline.scala +run/preinits.scala +run/t5532.scala +run/concat-two-strings.scala +run/t3269.scala +run/macro-impl-default-params +run/t2162.scala +run/matchonseq.scala +run/t5428.scala +run/macro-expand-overload +run/t4660.scala +run/enrich-gentraversable.scala +run/macro-expand-override +run/t4054.scala +run/t4753.scala +run/valueclasses-manifest-generic.scala +run/macro-typecheck-macrosdisabled +run/t2308a.scala +run/duplicate-meth.scala +run/interop_classtags_are_classmanifests.scala +run/t3232.scala +run/t2075.scala +run/virtpatmat_partial_backquoted.scala +run/try-2.scala +run/macro-openmacros +run/macro-undetparams-macroitself +run/t6318_derived.scala +run/deprecate-early-type-defs.scala +run/dead-code-elimination.scala +run/t4827.scala +run/Course-2002-07.scala +run/slice-strings.scala +run/t6292.scala +run/t6206.scala +run/t1042.scala +run/t1718.scala +run/t2074_2.scala +run/arraycopy.scala +run/indexedSeq.scala +run/macro-term-declared-in-implicit-class +run/t3511.scala +run/t6290.scala +run/distinct.scala +run/virtpatmat_alts.scala +run/valueclasses-pavlov.scala +run/exceptions.scala +run/t1368.scala +run/t5856.scala +run/t6968.scala +run/names-defaults.scala +run/macro-expand-tparams-implicit +run/t5881.scala +run/t3540.scala +run/virtpatmat_try.scala +run/t7181.scala +run/value-class-extractor.scala +run/value-class-extractor-2.scala +run/t3150.scala +run/exc.scala +run/t3516.scala +run/delay-bad.scala +run/infix.scala +run/t1309.scala +run/t6370.scala +run/t6725-2.scala +run/macro-impl-tparam-typetag-is-optional +run/macro-term-declared-in-block +run/matchnull.scala +run/t2127.scala +run/t7325.scala +run/groupby.scala +run/t3932.scala +run/t4871.scala +run/longmap.scala +run/t1524.scala +run/t6187b.scala +run/kmpSliceSearch.scala +run/t7088.scala +run/t5804.scala +run/stringbuilder-drop.scala +run/t5753_1 +pos/cyclics-pos.scala +pos/cfcrash.scala +pos/tcpoly_higherorder_bound_method.scala +pos/t5084.scala +pos/trait-force-info.scala +pos/macro-qmarkqmarkqmark.scala +pos/t7785.scala +pos/nested.scala +pos/t3152.scala +pos/t5031 +pos/t6925b.scala +pos/t1107b +pos/t5012.scala +pos/virtpatmat_obj_in_case.scala +pos/t4938.scala +pos/t3856.scala +pos/spec-cyclic.scala +pos/aliases.scala +pos/typerep_pos.scala +pos/t119.scala +pos/t1050.scala +pos/t3670.scala +pos/t6145.scala +pos/t7315.scala +pos/t5930.scala +pos/t789.scala +pos/t5071.scala +pos/t4731.scala +pos/t4547.scala +pos/t2038.scala +pos/testCoercionThis.scala +pos/t2444.scala +pos/t5744 +pos/t780.scala +pos/t1722-A.scala +pos/virtpatmat_exist1.scala +pos/t6225.scala +pos/t762.scala +pos/t0204.scala +pos/rebind.scala +pos/spec-short.scala +pos/comp-rec-test.scala +pos/lub-dealias-widen.scala +pos/t1168.scala +pos/modules.scala +pos/t4220.scala +pos/t4070.scala +pos/t175.scala +pos/t2500.scala +pos/t5029.scala +pos/itay.scala +pos/t4202.scala +pos/t1987b +pos/t3534.scala +pos/infer2-pos.scala +pos/spec-sparsearray-new.scala +pos/t7091.scala +pos/ticket0137.scala +pos/collectGenericCC.scala +pos/t640.scala +pos/t4305.scala +pos/extractor-types.scala +pos/t3880.scala +pos/spec-annotations.scala +pos/t3577.scala +pos/compile1.scala +pos/spec-t3497.scala +pos/hkrange.scala +pos/t287.scala +pos/t7294.scala +pos/t6008.scala +pos/t4432.scala +pos/CustomGlobal.scala +pos/patmat.scala +pos/t2413 +pos/t2910.scala +pos/t592.scala +pos/t6245 +pos/infer.scala +pos/t7228.scala +pos/compound.scala +pos/attributes.scala +pos/t6771.scala +pos/t1090.scala +pos/t684.scala +pos/t577.scala +pos/t4273.scala +pos/t6278-synth-def.scala +pos/t6184.scala +neg/t0214.scala +neg/t4842.scala +neg/t6214.scala +neg/reify_nested_inner_refers_to_local.scala +neg/t576.scala +neg/t5969.scala +neg/tcpoly_variance.scala +neg/t7509.scala +neg/mixins.scala +neg/parent-inherited-twice-error.scala +neg/macro-abort +neg/constructor-init-order.scala +neg/t6042.scala +neg/t0590.scala +neg/eta-expand-star-deprecation.scala +neg/t4221.scala +neg/t6263.scala +neg/t783.scala +neg/t5554.scala +neg/macro-invalidsig-params-badtype +neg/multi-array.scala +neg/raw-types-stubs +neg/spec-overrides.scala +neg/t836.scala +neg/t7289_status_quo.scala +neg/t5675.scala +neg/macro-quasiquotes +neg/t6667.scala +neg/t6597.scala +neg/t6264.scala +neg/t0345.scala +neg/t7294b.scala +neg/t5340.scala +neg/t2144.scala +neg/t1010.scala +neg/t1838.scala +neg/t5189b.scala +neg/reify_metalevel_breach_-1_refers_to_1.scala +neg/t6601 +neg/wellkinded_wrongarity.scala +neg/t3909.scala +neg/t876.scala +neg/t5390.scala +neg/unit2anyref.scala +neg/t0351.scala +neg/t5120.scala +neg/t1038.scala +neg/t5878.scala +neg/qualifying-class-error-2.scala +neg/t3816.scala +neg/tailrec.scala +neg/volatile.scala +neg/t944.scala +neg/t1705.scala +neg/t3977.scala +neg/t5553_2.scala +neg/t5318c.scala +neg/overload-msg.scala +neg/t5440.scala +neg/t6335.scala +neg/compile-time-only-b.scala +neg/t501.scala +neg/override.scala +neg/t663.scala +neg/t5892.scala +neg/t1980.scala +neg/macro-false-deprecation-warning +neg/t5148.scala +neg/t585.scala +neg/t3776.scala +neg/interop_classtags_arenot_manifests.scala +neg/t4044.scala +neg/macro-invalidusage-nontypeable +neg/t6375.scala +neg/t500.scala +neg/t4877.scala +neg/t5357.scala +neg/interop_abstypetags_arenot_manifests.scala +neg/t4460a.scala +neg/t5318b.scala +neg/t3234.scala +neg/t4440.scala +neg/t6663.scala +neg/t6357.scala +neg/gadts1.scala +neg/cyclics.scala +neg/t5060.scala +neg/scopes.scala +run/t4013.scala +run/value-class-extractor-seq.scala +run/macro-expand-tparams-explicit +run/tuples.scala +run/t5753_2 +run/t0528.scala +run/t5105.scala +run/t1195-old.scala +run/t7341.scala +run/t3670.scala +run/t2594_tcpoly.scala +run/t3895.scala +run/t0668.scala +run/slices.scala +run/t6666a.scala +run/valueclasses-classmanifest-generic.scala +run/t2316_run.scala +run/t3004.scala +run/viewtest.scala +run/t6481.scala +run/t0005.scala +run/t4110-old.scala +run/t4766.scala +run/t5500b.scala +run/t7407b.scala +run/backreferences.scala +run/arrayview.scala +run/t629.scala +run/t5903c +run/unittest_collection.scala +run/spec-nlreturn.scala +run/macro-term-declared-in-object-object +run/triple-quoted-expr.scala +run/t5937.scala +run/t6011c.scala +run/macro-expand-implicit-argument +run/try.scala +run/t1987b +run/t6089.scala +run/macro-range +run/t2524.scala +run/t4770.scala +run/virtpatmat_unapplyprod.scala +run/t1535.scala +run/ctor-order.scala +pos/t5210.scala +pos/t5384.scala +pos/rangepos.scala +pos/t443.scala +pos/t1480.scala +pos/t116.scala +pos/seqtest2.scala +pos/scoping1.scala +pos/t4269.scala +pos/lookupswitch.scala +pos/t3642 +pos/t5706.scala +pos/SI-5788.scala +pos/t7264 +pos/t0031.scala +pos/macro-deprecate-dont-touch-backquotedidents.scala +pos/t6815.scala +pos/test4refine.scala +pos/michel5.scala +pos/t0851.scala +pos/t1185.scala +pos/sudoku.scala +pos/t7520.scala +pos/t6208.scala +pos/t3411.scala +pos/t295.scala +pos/S3.scala +pos/t0674.scala +pos/t6664b.scala +pos/variances_pos.scala +pos/liftcode_polymorphic.scala +pos/t3174b.scala +pos/t7232d +pos/t578.scala +pos/implicit-infix-ops.scala +pos/t4363.scala +pos/t532.scala +pos/exponential-spec.scala +pos/t599.scala +pos/t5862.scala +pos/t4603 +pos/t3676.scala +pos/t1357.scala +pos/native-warning.scala +pos/t1230 +pos/t6028 +pos/t4275.scala +pos/overloaded_extractor_and_regular_def.scala +pos/t4205 +pos/matthias1.scala +pos/testcast.scala +pos/generic-sigs.scala +pos/t0093.scala +pos/specializes-sym-crash.scala +pos/t0061.scala +pos/t2429.scala +pos/t694.scala +pos/javaReadsSigs +pos/t2023.scala +pos/t704.scala +pos/t2208_pos.scala +pos/t5137.scala +pos/t2683.scala +pos/t0049.scala +pos/t1029 +pos/t4243.scala +pos/typerep-stephane.scala +pos/t177.scala +pos/t5967.scala +pos/t430.scala +pos/virtpatmat_infer_single_1.scala +pos/pat_iuli.scala +pos/t1071.scala +pos/t7226.scala +pos/t1843.scala +pos/t419.scala +pos/t7364b +pos/t1159.scala +pos/t5305.scala +pos/t7694.scala +pos/t6047.scala +pos/t3578.scala +pos/t2082.scala +pos/setter-not-implicit.scala +pos/t1133.scala +pos/t3862.scala +pos/t942 +pos/nothing_manifest_disambig-new.scala +pos/iterator-traversable-mix.scala +pos/eta.scala +pos/test4.scala +pos/t2691.scala +pos/t4502.scala +pos/t7183.scala +pos/protected-t1010.scala +pos/X.scala +pos/virtpatmat_exist2.scala +pos/t4911.scala +pos/t3477.scala +pos/t4173.scala +pos/t7782.scala +pos/t2399.scala +pos/virtpatmat_alts_subst.scala +pos/propagate.scala +pos/t2421b_pos.scala +pos/t183.scala +pos/t7033.scala +pos/t3612.scala +pos/t5330c.scala +pos/t3020.scala +pos/t4869.scala +pos/t3373.scala +pos/spec-params-new.scala +pos/t3672.scala +pos/t4501.scala +pos/t1565.scala +pos/t3774.scala +pos/t6942 +neg/t3275.scala +neg/t421.scala +neg/t5702-neg-bad-brace.scala +neg/t3663 +neg/badtok-1.scala +neg/t677.scala +neg/t7756b.scala +neg/t6534.scala +neg/t6276.scala +neg/t5762.scala +neg/abstract.scala +neg/t2405.scala +neg/t0418.scala +neg/t5390c.scala +neg/lazyvals.scala +neg/lubs.scala +neg/abstract-report.scala +neg/t4163.scala +neg/t5702-neg-bad-and-wild.scala +neg/macro-invalidret +neg/t6728.scala +neg/t5152.scala +neg/t1432.scala +neg/abstract-inaccessible.scala +neg/import-precedence.scala +neg/t2462b.scala +neg/macro-invalidusage-presuper +neg/specification-scopes +neg/t6048.scala +neg/t4079 +neg/macro-basic-mamdmi +neg/t7020.scala +neg/t3015.scala +neg/t0207.scala +neg/t2296b +neg/t0673 +neg/t3761-overload-byname.scala +neg/t6675.scala +neg/t5529.scala +neg/sensitive.scala +neg/t742.scala +neg/t5067.scala +neg/t6162-overriding.scala +neg/variances.scala +neg/t5728.scala +neg/t6323a.scala +neg/compile-time-only-a.scala +neg/t6795.scala +neg/t2494.scala +neg/t3649.scala +neg/macro-invalidsig +neg/t2796.scala +neg/t112706A.scala +neg/t0764.scala +neg/t3757 +neg/t1431.scala +neg/exhausting.scala +neg/t1523.scala +neg/t779.scala +neg/xmltruncated1.scala +neg/t2208.scala +neg/t2078.scala +neg/t521.scala +neg/null-unsoundness.scala +neg/stmt-expr-discard.scala +neg/t0513.scala +neg/unchecked-abstract.scala +neg/t4460c.scala +neg/divergent-implicit.scala +neg/t5078.scala +neg/t1701.scala +neg/t0816.scala +neg/t1672b.scala +neg/macro-invalidusage-badbounds +neg/tailrec-2.scala +neg/t4064.scala +neg/reflection-names-neg.scala +neg/t5510.scala +neg/t3873.scala +neg/tailrec-3.scala +neg/t0226.scala +neg/t2031.scala +neg/t633.scala +neg/constrs.scala +neg/anyval-anyref-parent.scala +neg/t7290.scala +neg/t1041.scala +neg/patternalts.scala +neg/error_tooManyArgsPattern.scala +neg/checksensibleUnit.scala +neg/t6539 +neg/t4417.scala +neg/wellkinded_app.scala +neg/for-comprehension-old.scala +neg/t2779.scala +neg/object-not-a-value.scala +neg/t2968b.scala +neg/t6483.scala +neg/t6902.scala +neg/t6963a.scala +neg/t3399.scala +neg/t0015.scala +neg/t3995.scala +neg/t276.scala +neg/t6758.scala +neg/t2441.scala +neg/cycle-bounds.scala +neg/t1241.scala +neg/t4137.scala +neg/unicode-unterminated-quote.scala +neg/t4762.scala +neg/typeerror.scala +neg/implicits.scala +neg/t961.scala +neg/ambiguous-float-dots2.scala +neg/t2416.scala +neg/t5799.scala +neg/t7285.scala +neg/implicit-shadow.scala +neg/t2388.scala +neg/java-access-neg +neg/found-req-variance.scala +neg/hk-bad-bounds.scala +neg/t3224.scala +neg/t1033.scala +neg/t7385.scala +neg/t5882.scala +neg/t4541.scala +neg/t2973.scala +neg/t6406-regextract.scala +neg/t6666.scala +neg/t4831.scala +neg/t425.scala +neg/t1845.scala +neg/t3683b.scala +neg/t2801.scala +neg/t6083.scala +neg/t0528neg.scala +neg/stringinterpolation_macro-neg.scala +neg/t668.scala +neg/t5666.scala +neg/t4271.scala +neg/interop_typetags_arenot_classmanifests.scala +neg/t1355.scala +neg/t715.scala +neg/t7238.scala +neg/t7473.scala +neg/t7292-removal.scala +neg/tcpoly_infer_ticket1162.scala +neg/t4098.scala +neg/t6013 +neg/t6227.scala +neg/t464-neg.scala +neg/badtok-3.scala +neg/t6082.scala +neg/anytrait.scala +neg/valueclasses-doubledefs.scala +neg/t7519.scala +neg/overloaded-unapply.scala +neg/t1163.scala +neg/wellkinded_bounds.scala +neg/t7292-deprecation.scala +neg/t5044.scala +neg/t0842.scala +neg/t6436.scala +neg/interop_typetags_arenot_classtags.scala +neg/t3653.scala +neg/higherkind_novalue.scala +neg/t935.scala +neg/t6040.scala +neg/annot-nonconst.scala +neg/macro-deprecate-idents.scala +neg/illegal-stmt-start.scala +neg/t565.scala +neg/case-collision.scala +neg/t3209.scala +neg/t5821.scala +neg/abstract-class-2.scala +neg/t846.scala +neg/quasiquotes-syntax-error-position.scala +neg/t3987.scala +neg/t877.scala +neg/t0117.scala +neg/t692.scala +neg/t6666d.scala +neg/t5702-neg-ugly-xbrace.scala +neg/t7752.scala +neg/case-collision2.scala +neg/t6526.scala +neg/t2213.scala +neg/t7756a.scala +neg/t845.scala +neg/macro-override-macro-overrides-abstract-method-a +neg/tcpoly_ticket2101.scala +neg/delayed-init-ref.scala +neg/caseinherit.scala +neg/t3189.scala +neg/unchecked-suppress.scala +neg/t2180.scala +neg/t1371.scala +neg/macro-cyclic +neg/t6123-explaintypes-macros +neg/t4134.scala +neg/t691.scala +neg/t2421b.scala +neg/t4691_exhaust_extractor.scala +neg/t4419.scala +neg/t5801.scala +neg/t650.scala +neg/t5735.scala +neg/t696.scala +neg/t882.scala +neg/t2968.scala +neg/t7507.scala +neg/macro-invalidusage-badargs +neg/macro-reify-typetag-typeparams-notags +neg/wellkinded_app2.scala +neg/t4425b.scala +neg/t2296a +neg/t1878.scala +neg/t649.scala +neg/override-object-no.scala +neg/t4174.scala +neg/t2070.scala +neg/sabin2.scala +neg/t5903e +neg/t6566a.scala +neg/finitary-error.scala +neg/t4818.scala +neg/t3614.scala +neg/t6666c.scala +neg/ticket513.scala +neg/suggest-similar.scala +neg/t4457_1.scala +neg/t6666e.scala +neg/tcpoly_bounds.scala +neg/t4727.scala +neg/t4425.scala +neg/macro-invalidusage-methodvaluesyntax +neg/t3854.scala +neg/t3006.scala +neg/t5580b.scala +neg/t5378.scala +neg/t639.scala +neg/wrong-args-for-none.scala +neg/t7171b.scala +neg/t5361.scala +neg/unreachablechar.scala +neg/t5572.scala +neg/t7757a.scala +neg/macro-invalidimpl +neg/t2773.scala +neg/t6359.scala +neg/saito.scala +neg/xmltruncated2.scala +neg/t667.scala +neg/t3934.scala +neg/t6771b.scala +neg/t4584.scala +neg/wellkinded_wrongarity2.scala +neg/t7369.scala +neg/t1477.scala +neg/t5617.scala +neg/t7299.scala +neg/faculty.scala +neg/virtpatmat_reach_null.scala +neg/macro-reify-typetag-hktypeparams-notags +neg/t1224.scala +neg/xmltruncated3.scala +neg/t1872.scala +neg/t558.scala +neg/t7110.scala +neg/any-vs-anyref.scala +neg/t6340.scala +neg/t4166.scala +neg/t2918.scala +neg/t5856.scala +neg/t4989.scala +neg/t0003.scala +neg/t1183.scala +neg/t963.scala +neg/t4515.scala +neg/valueclasses-pavlov.scala +neg/t608.scala +neg/choices.scala +neg/patmat-type-check.scala +neg/valueclasses-impl-restrictions.scala +neg/imp2.scala +neg/protected-constructors.scala +neg/t6788.scala +neg/nullary-override.scala +neg/t200.scala +neg/t343.scala +neg/names-defaults-neg-ref.scala +neg/tcpoly_typealias.scala +neg/classtags_contextbound_b.scala +neg/t729.scala +neg/t5683.scala +neg/t4928.scala +neg/t700.scala +neg/t7669.scala +neg/macro-invalidshape +neg/t6011.scala +neg/t7325.scala +neg/check-dead.scala +neg/t550.scala +neg/t5663-badwarneq.scala +neg/t0699 +neg/nopredefs.scala +neg/t3507-old.scala +neg/t5352.scala +neg/t6336.scala +neg/interop_classmanifests_arenot_typetags.scala +neg/sealed-final-neg.scala +neg/t2102.scala +neg/t7636.scala +neg/t5031b +neg/t798.scala +neg/t5702-neg-bad-xbrace.scala +neg/t0899.scala +neg/cyclics-import.scala +neg/badtok-2.scala +neg/t473.scala +neg/t3160ambiguous.scala +neg/t5106.scala +neg/t1286 +neg/macro-override-macro-overrides-abstract-method-b +neg/t0259.scala +neg/t510.scala +neg/t3836.scala +neg/t5830.scala +neg/t1548 +neg/t5580a.scala +neg/forward.scala +neg/t591.scala +neg/t6558b.scala +neg/t556.scala +neg/xmltruncated4.scala +neg/t5497.scala +neg/t409.scala +neg/t6283.scala +neg/override-object-flag.scala +neg/constructor-prefix-error.scala +neg/eta-expand-star.scala +neg/t3392.scala +neg/t1275.scala +neg/nested-fn-print.scala +neg/t7330.scala +neg/t2275a.scala +neg/t630.scala +neg/t4270.scala +neg/t2775.scala +neg/pat_unreachable.scala +neg/t4158.scala +neg/unit-returns-value.scala +neg/t1422.scala +neg/reify_metalevel_breach_-1_refers_to_0_b.scala +neg/reassignment.scala +neg/t3683a.scala +neg/noMember1.scala +neg/macro-without-xmacros-b +neg/t1106.scala +neg/t5182.scala +neg/t6889.scala +neg/t4217.scala +neg/t7501 +neg/t5063.scala +neg/t1009.scala +neg/t997.scala +neg/unchecked.scala +neg/classtags_contextbound_c.scala +neg/applydynamic_sip.scala +neg/t7715.scala +neg/t588.scala +neg/t6667b.scala +neg/t7757b.scala +neg/t4069.scala +neg/t515.scala +neg/variances2.scala +neg/t1049.scala +neg/t7289.scala +neg/t1623.scala +neg/permanent-blindness.scala +neg/t5803.scala +neg/super-cast-or-test.scala +neg/nonlocal-warning.scala +neg/t5687.scala +neg/t5903a +neg/t6566b.scala +neg/unchecked-knowable.scala +neg/t5093.scala +neg/protected-static-fail +neg/type-diagnostics.scala +neg/forgot-interpolator.scala +neg/interop_abstypetags_arenot_classmanifests.scala +neg/t5376.scala +neg/t545.scala +neg/xmlcorner.scala +neg/switch.scala +neg/depmet_1.scala +neg/abstract-concrete-methods.scala +neg/t4987.scala +neg/t5452-new.scala +neg/t750b +neg/unchecked-refinement.scala +neg/t418.scala +neg/t5354.scala +neg/t3736.scala +neg/t631.scala +neg/t6829.scala +neg/t0218.scala +neg/volatile-intersection.scala +neg/t412.scala +neg/t693.scala +neg/t4882.scala +neg/t1960.scala +neg/macro-divergence-controlled +neg/t712.scala +neg/t5544 +neg/t3222.scala +neg/t3604.scala +neg/t1112.scala +neg/t7157 +neg/accesses.scala +neg/t452.scala +neg/t6162-inheritance +neg/t2442 +neg/t6567.scala +neg/lazy-override.scala +neg/abstract-explaintypes.scala +neg/nested-annotation.scala +neg/t5753 +neg/t4283b +neg/t3691.scala +neg/infix-op-positions.scala +neg/t3403.scala +neg/t4851 +neg/structural.scala +neg/error_dependentMethodTpeConversionToFunction.scala +neg/t5839.scala +neg/t5553_1.scala +neg/reify_metalevel_breach_+0_refers_to_1.scala +neg/t752.scala +neg/t6574.scala +neg/t3714-neg.scala +neg/t4457_2.scala +neg/t2148.scala +neg/t3240.scala +neg/t1364.scala +neg/saferJavaConversions.scala +neg/t414.scala +neg/t5493.scala +neg/classtags_contextbound_a.scala +neg/reify_metalevel_breach_-1_refers_to_0_a.scala +neg/t3118.scala +neg/t512.scala +neg/t2336.scala +neg/t856.scala +neg/xmltruncated6.scala +neg/t2206.scala +neg/virtpatmat_unreach_select.scala +neg/t6258.scala +neg/t6815.scala +neg/not-possible-cause.scala +neg/dbldef.scala +neg/qualifying-class-error-1.scala +neg/t835.scala +neg/t5455.scala +neg/t6558.scala +neg/t708.scala +neg/macro-nontypeablebody +neg/t0565.scala +neg/xmltruncated5.scala +neg/t5390d.scala +neg/t520.scala +neg/t6138.scala +neg/macro-without-xmacros-a +neg/t7214neg.scala +neg/t2870.scala +neg/t593.scala +neg/t4541b.scala +neg/t4460b.scala +neg/t284.scala +neg/t2488.scala +neg/macro-override-method-overrides-macro +neg/interop_abstypetags_arenot_classtags.scala +neg/t3769.scala +neg/warn-inferred-any.scala +neg/t664.scala +neg/t5903d +neg/t562.scala +neg/t2316.scala +neg/t0152.scala +neg/migration28.scala +neg/t6443c.scala +neg/tcpoly_override.scala +neg/t7324.scala +neg/t987.scala +neg/t5903b +neg/t3481.scala +neg/t6912.scala +neg/tcpoly_variance_enforce.scala +neg/t3913.scala +neg/names-defaults-neg.scala +neg/t765.scala +neg/t5358.scala +neg/t391.scala +neg/serialversionuid-not-const.scala +neg/t771.scala +neg/t0903.scala +neg/catch-all.scala +neg/classmanifests_new_deprecations.scala +neg/t0606.scala +neg/t5189_inferred.scala +neg/macro-reify-typetag-useabstypetag +neg/t5543.scala +neg/logImplicits.scala +neg/interop_typetags_without_classtags_arenot_manifests.scala +neg/t6535.scala +neg/t7259.scala +neg/t2139.scala +neg/t278.scala +neg/t5564.scala +neg/unchecked3.scala +neg/virtpatmat_reach_sealed_unsealed.scala +neg/checksensible.scala +neg/t7721.scala +run/t3798.scala +run/macro-expand-varargs-explicit-over-varargs +run/t3888.scala +run/t0677-new.scala +run/t3273.scala +run/t3763.scala +run/t2755.scala +run/t920.scala +run/t5610a.scala +run/literals.scala +run/proxy.scala +run/unapply.scala +run/t5830.scala +run/array-addition.scala +run/macro-expand-nullary-nongeneric +run/macro-basic-ma-mdmi +run/valueclasses-constr.scala +run/t1247.scala +run/t3487.scala +run/rawstrings.scala +run/patmat-seqs.scala +run/eta-expand-star.scala +run/t7436.scala +run/t3996.scala +run/constructors.scala +run/t498.scala +run/t3835.scala +run/t298.scala +run/t2867.scala +run/t7120 +run/virtpatmat_literal.scala +run/t2175.scala +run/t2503.scala +run/t3026.scala +run/t603.scala +run/t0091.scala +run/t6394a +run/macro-expand-varargs-implicit-over-varargs +run/t7407.scala +run/t2552.scala +run/priorityQueue.scala +run/virtpatmat_npe.scala +run/macro-sip19 +run/t6644.scala +run/t6614.scala +run/t2005.scala +run/t4680.scala +run/t5903a +run/classtags_contextbound.scala +run/Course-2002-05.scala +run/applydynamic_sip.scala +run/t1766.scala +run/retsynch.scala +run/t7715.scala +run/t102.scala +run/nonlocalreturn.scala +run/macro-reify-staticXXX +run/Course-2002-06.scala +run/t6863.scala +run/t6500.scala +run/macro-impl-rename-context +run/t4351.scala +run/t5009.scala +run/macro-term-declared-in-annotation +run/t6271.scala +run/array-existential-bound.scala +run/t6443b.scala +run/t1987.scala +run/MutableListTest.scala +run/t7571.scala +run/t5488-fn.scala +run/macro-bodyexpandstoimpl +run/macro-reify-ref-to-packageless +run/t2212.scala +run/macro-expand-varargs-implicit-over-nonvarargs +run/t0807.scala +run/patmat-behavior.scala +run/t2446.scala +run/tuple-zipped.scala +run/breakout.scala +run/t4122.scala +run/macro-settings +run/t7157 +run/t1323.scala +run/t4013b.scala +run/t6309.scala +run/t4047.scala +run/t5544 +run/t978.scala +run/t3361.scala +run/t6611.scala +run/t5387.scala +run/t5656.scala +run/t4897.scala +run/numeric-range.scala +run/t4777.scala +run/Course-2002-03.scala +run/string-extractor.scala +run/view-headoption.scala +run/patmat_unapp_abstype-new.scala +run/stream-stack-overflow-filter-map.scala +run/macro-impl-tparam-only-in-impl +run/t6559.scala +run/macro-reify-tagful-a +run/macro-expand-multiple-arglists +run/t4709.scala +run/t3509.scala +run/t5284b.scala +run/t7617b +run/t3923.scala +run/virtpatmat_apply.scala +run/t363.scala +run/manifests-undeprecated-in-2.10.0.scala +run/matchintasany.scala +run/t3970.scala +run/t4996.scala +run/t5530.scala +run/macro-term-declared-in-object-class +run/t3242b.scala +run/indexedSeq-apply.scala +run/t107.scala +run/t2337.scala +run/t3758-old.scala +run/t2754.scala +run/valueclasses-manifest-existential.scala +run/flat-flat-flat.scala +run/t6673.scala +run/interpolationMultiline2.scala +run/t3493.scala +run/t0631.scala +run/t2800.scala +run/t6506.scala +run/t6260.scala +run/t2418.scala +run/t4415.scala +run/classmanifests_new_alias.scala +run/t5380.scala +run/tcpoly_parseridioms.scala +run/t1747.scala +run/t5903d +run/t3530.scala +run/t216.scala +run/macro-term-declared-in-refinement +run/t4592.scala +run/t2488.scala +run/t3327.scala +run/t5614.scala +run/t5903b +run/iterables.scala +run/t3964.scala +run/t6329_vanilla.scala +run/t3038c +run/t1697.scala +run/t2030.scala +run/t3397.scala +run/t1005.scala +run/t3353.scala +run/t1466.scala +run/t3186.scala +run/tcpoly_overriding.scala +run/t5394.scala +run/t5284.scala +run/unboxingBug.scala +run/t7200.scala +run/macro-reify-basic +run/t153.scala +run/iterator3444.scala +run/macro-expand-implicit-macro-is-val +run/macro-basic-ma-md-mi +run/interpolationArgs.scala +run/t4954.scala +run/t3645.scala +run/transpose.scala +run/t3887.scala +run/t4288.scala +run/unittest_iterator.scala +run/t5543.scala +run/macro-term-declared-in-object +run/iq.scala +run/t2788.scala +run/t2027.scala +run/macro-expand-recursive +run/t949.scala +run/t1909b.scala +run/delambdafy-nested-by-name.scala +run/delambdafy-two-lambdas.scala +run/macro-blackbox-materialization +run/lists-run.scala +run/macro-parse-position +run/macro-parse-position-malformed +run/macro-whitebox-dynamic-materialization +run/macro-whitebox-extractor +run/macro-vampire-false-warning +run/macro-whitebox-fundep-materialization +run/macro-whitebox-structural +run/mutable-treeset.scala +run/static-module-method.scala +run/sort.scala +run/t1909.scala +run/t1909c.scala +run/t3346a.scala +run/t3346d.scala +run/t3346f.scala +run/t3346h.scala +run/t3346g.scala +run/t3832.scala +run/t4742.scala +run/t5377.scala +run/t5923c.scala +run/t6188.scala +run/t6333.scala +run/t6385.scala +run/t7899.scala +run/t7899-regression.scala +run/t7584b.scala +run/t7223.scala +run/t7859 +run/t7868.scala +run/t7871 +run/arrayclone-new.scala +run/arrayclone-old.scala +run/bitsets.scala +run/comparable-comparator.scala +run/colltest1.scala +run/t2106.scala +run/t5986.scala +run/view-iterator-stream.scala +run/array-charSeq.scala +pos/signatures +pos/t1263 +pos/t3249 +neg/t4749.scala +neg/main1.scala +neg/t7251 +neg/t7494-after-terminal +neg/t7494-before-parser +neg/t7494-right-after-terminal +run/lazy-traits.scala +run/OrderingTest.scala +run/ReplacementMatching.scala +run/patmat-finally.scala +run/t3158.scala +run/t3346e.scala +run/t4398.scala +run/t4930.scala +run/t6534.scala +pos/sammy_scope.scala +pos/delambdafy-patterns.scala +pos/private-types-after-typer.scala +pos/delambdafy-lambdalift.scala +pos/sammy_poly.scala +pos/sammy_single.scala +pos/SI-4012-b.scala +pos/sammy_twice.scala +pos/t3160.scala +pos/t1014.scala +pos/t4970b.scala +pos/t2698.scala +pos/t5845.scala +pos/t6201.scala +pos/t6260a.scala +pos/t7688.scala +pos/t7818.scala +pos/t1203a.scala +pos/t7834.scala +pos/t7853.scala +pos/t7815.scala +pos/t7853-partial-function.scala +pos/t7864.scala +pos/t7928.scala +pos/t7902.scala +pos/t7944.scala +pos/t7847 +neg/accesses2.scala +neg/bad-advice.scala +neg/gadts2.scala +neg/gadts2-strict.scala +neg/macro-bundle-abstract.scala +neg/macro-bundle-object.scala +neg/macro-bundle-trait.scala +neg/macro-blackbox-dynamic-materialization +neg/macro-blackbox-extractor +neg/run-gadts-strict.scala +neg/macro-blackbox-structural +neg/sammy_restrictions.scala +neg/sammy_wrong_arity.scala +neg/t2462c.scala +neg/t3346b.scala +neg/t1909-object.scala +neg/macro-blackbox-fundep-materialization +neg/t3346c.scala +neg/t3871.scala +neg/t3871b.scala +neg/t3971.scala +neg/t3346i.scala +neg/t6120.scala +neg/t6260c.scala +neg/t6680a.scala +neg/t7239.scala +neg/t7007.scala +neg/t7605-deprecation.scala +neg/t7622-missing-required.scala +neg/t7629-view-bounds-deprecation.scala +neg/t7834neg.scala +neg/t7783.scala +neg/t7848-interp-warn.scala +neg/t7519-b +neg/t7622-missing-dependency +neg/t7870.scala +neg/t7877.scala +neg/t7895.scala +neg/t7895b.scala +neg/t7899.scala +neg/t7895c.scala +neg/t7859 +run/t4752.scala +run/t2087-and-2400.scala +run/t3855.scala +run/t6637.scala +run/t6731.scala +pos/t3999b.scala +run/t0432.scala +run/t2514.scala +run/t7817.scala +run/t874.scala +run/type-currying.scala +run/t3616.scala +run/t3687.scala +run/t4570.scala +run/t5612.scala +run/t1110.scala +run/t2636.scala +run/verify-ctor.scala +run/t3647.scala +run/t4560.scala +run/t6632.scala +run/hashCodeBoxesRunTime.scala +run/richs.scala +run/t6725-1.scala +pos/t7776.scala +run/fors.scala +run/t6706.scala +run/t3175.scala +run/delambdafy-dependent-on-param-subst.scala +run/t4332b.scala +run/t8048a +run/t8017 +run/t7985b.scala +run/t8100.scala +run/patmat-mix-case-extractor.scala +run/t4750.scala +run/t7912.scala +run/delambdafy-dependent-on-param-subst-2.scala +run/t8048b +run/t8091.scala +run/macroPlugins-macroRuntime +run/macro-default-params +run/t6355.scala +run/t7777 +run/t8002.scala +run/t8015-ffc.scala +run/macro-subpatterns +run/t7985.scala +run/macroPlugins-macroArgs +run/t7326.scala +run/t5045.scala +run/value-class-partial-func-depmet.scala +run/t6329_vanilla_bug.scala +run/macroPlugins-macroExpand +run/t8010.scala +run/macroPlugins-typedMacroBody +run/t7406.scala +run/t6253c.scala +run/t6253a.scala +run/t6253b.scala +pos/t8146a.scala +pos/t8046c.scala +pos/t8002-nested-scope.scala +pos/t8132.scala +pos/t8045.scala +pos/overzealous-assert-genbcode.scala +pos/t8128.scala +pos/t8013 +pos/t8064b +pos/t6780.scala +pos/t7987 +pos/bcode_throw_null +pos/t8064 +pos/t8046.scala +pos/t6231.scala +pos/t7983.scala +pos/t5508.scala +pos/t5508-min.scala +pos/t8023b.scala +pos/t6231b.scala +pos/debug-reset-local-attrs.scala +pos/t8054.scala +pos/t2066.scala +pos/dotless-targs.scala +pos/t8120.scala +pos/t5508-min-okay.scala +pos/t8060.scala +pos/t8001 +pos/t8138.scala +pos/t8111.scala +pos/t8062 +pos/t8011.scala +pos/t8146b.scala +pos/t8046b.scala +pos/t8023.scala +pos/t5508-min-okay2.scala +pos/macro-implicit-invalidate-on-error.scala +neg/t6563.scala +neg/missing-param-type-tuple.scala +neg/not-a-legal-formal-parameter-tuple.scala +neg/t7897.scala +neg/t8015-ffa.scala +neg/quasiquotes-unliftable-not-found.scala +neg/t2066b.scala +neg/dotless-targs.scala +neg/patmat-classtag-compound.scala +neg/t2066.scala +neg/t8035-deprecated.scala +neg/t6675b.scala +neg/t8104 +neg/t7872.scala +neg/t7850.scala +neg/t7967.scala +neg/macro-bundle-overloaded.scala +neg/t6355a.scala +neg/class-of-double-targs.scala +neg/t6355b.scala +neg/macro-reify-splice-splice +neg/macro-bundle-noncontext.scala +neg/t8015-ffb.scala +neg/t8035-removed.scala +neg/t7984.scala +neg/t8024.scala +neg/t8024b.scala +neg/t8157.scala +neg/t8146-non-finitary-2.scala +neg/t8006.scala +neg/t7872c.scala +neg/t8146-non-finitary.scala +neg/t7872b.scala +neg/t6920.scala +run/t6200.scala +run/t6196.scala +run/macro-bundle-context-refinement +run/macro-enclosingowner-detectvar +run/macro-enclosingowner-sbt +run/macro-bundle-context-alias +run/macro-bundle-whitebox-use-refined +run/macro-bundle-whitebox-use-raw +neg/name-lookup-stable.scala +neg/t0764b.scala +neg/no-implicit-to-anyref-any-val.scala +neg/t1503.scala +neg/t4728.scala +neg/t6455.scala +neg/t6260-named.scala +neg/t6844.scala +neg/t7475c.scala +neg/t7475e.scala +neg/t7475f.scala +neg/macro-bundle-whitebox-use-raw +neg/macro-bundle-whitebox-use-refined +neg/macro-incompatible-macro-engine-b +neg/t7980.scala +neg/macro-incompatible-macro-engine-a +neg/t8143a.scala +neg/t8072.scala +neg/t8207.scala +neg/t8182.scala +neg/t8219-any-any-ref-equals.scala +neg/t8177a.scala +neg/t8228.scala +neg/t8229.scala +neg/t8237-default.scala +neg/t8244b.scala +neg/t8244e +neg/t8244c.scala +neg/t8265.scala +neg/t8266-invalid-interp.scala +neg/t6931 +neg/t8376 +neg/t8372.scala +neg/t8300-overloading.scala +neg/t8244 +neg/t8158 +neg/t8431.scala +pos/implicit-anyval-2.10.scala +pos/delambdafy_t6260_method.scala +pos/macro-bundle-disambiguate-bundle.scala +pos/macro-bundle-disambiguate-nonbundle.scala +pos/package-ob-case +pos/t1786-counter.scala +pos/reflection-compat-api-universe.scala +pos/list-optim-check.scala +pos/existential-java-case-class +pos/t1786-cycle.scala +pos/reflection-compat-c.scala +pos/t3452f.scala +pos/reflection-compat-ru.scala +pos/t2066-2.10-compat.scala +pos/reflection-compat-macro-universe.scala +pos/t5900a.scala +pos/t5760-pkgobj-warn +pos/t5954a +pos/t5954b +pos/t5954d +pos/t6260.scala +pos/t5165b +pos/t5954c +pos/t6260b.scala +pos/t7475b.scala +pos/t7475a.scala +pos/t7753.scala +pos/t7322.scala +pos/t6948.scala +pos/t7475d.scala +pos/t7475e.scala +pos/t6169 +pos/t7788.scala +pos/t7919.scala +pos/t8177a.scala +pos/t8177.scala +pos/t8170.scala +pos/t8170b.scala +pos/t8177d.scala +pos/t8177b.scala +pos/t8177e.scala +pos/t8134 +pos/t8177h.scala +pos/t8177g.scala +pos/t8207.scala +pos/t8187.scala +pos/t8219.scala +pos/t8219b.scala +pos/t8224.scala +pos/t8237.scala +pos/t8223.scala +pos/t8237b.scala +pos/t8300-conversions-a.scala +pos/t8300-conversions-b.scala +pos/t8209a +pos/t8209b +pos/t8244d +pos/t8300-overloading.scala +pos/t8300-patmat-a.scala +pos/t8300-patmat-b.scala +pos/t8315b.scala +pos/t8306.scala +pos/t8301.scala +pos/t8324.scala +pos/t8315.scala +pos/t8301b.scala +pos/t8363.scala +pos/t8367.scala +pos/t8369a.scala +pos/t8369b.scala +pos/t8403.scala +pos/t8364.scala +pos/t8352 +pos/t8376 +neg/macro-bundle-nonpublic-c.scala +neg/literate_existentials.scala +neg/macro-bundle-nonpublic-impl.scala +neg/macro-bundle-ambiguous.scala +neg/macro-bundle-priority-bundle.scala +neg/macro-bundle-need-qualifier.scala +neg/macro-bundle-nonstatic.scala +neg/macro-bundle-polymorphic.scala +neg/macro-bundle-priority-nonbundle.scala +neg/macro-bundle-wrongcontext-a.scala +neg/macro-bundle-wrongcontext-b.scala +run/t8425 +run/t8245.scala +run/t8266-octal-interp.scala +run/t8280.scala +run/t8395.scala +run/t8321 +run/t8153.scala +run/t8233-bcode.scala +run/t8197.scala +run/t8197b.scala +run/t8233.scala +run/t8133 +run/t8133b +run/t7475b.scala +run/t7445.scala +run/t6814 +run/t4577.scala +run/t5134.scala +run/t3452f.scala +run/t3452h.scala +run/t3452c.scala +run/t3452.scala +run/t261.scala +run/t3235-minimal.scala +run/t1503_future.scala +run/t5565.scala +pos/t8411 +pos/t8460.scala +run/t8428.scala +run/t8437 +run/absoverride.scala +run/arrays.scala +run/duration-coarsest.scala +run/iterator-from.scala +run/SymbolsTest.scala +run/t1074.scala +run/t1505.scala +run/streams.scala +run/t2111.scala +run/t4601.scala +pos/SI-4012-a.scala +pos/SI-7638.scala +neg/t3692-new.scala +run/t7015.scala +run/t7992b.scala +run/t7992.scala +run/t8570.scala +pos/t8157-2.10.scala +pos/t8325.scala +pos/t8523.scala +pos/t8578.scala +pos/t8329.scala +pos/t8497 +pos/t8546.scala +pos/t8531 +neg/t8325-c.scala +neg/t8325-b.scala +neg/t8325.scala +neg/t6988.scala +neg/t8463.scala +neg/t8450.scala +neg/t8430.scala +run/finally.scala +run/bugs.scala +run/t1503.scala +run/t4148.scala +run/t7763.scala +run/issue192.scala + +# Adapt checkfiles for (1.0).toString == "1" +run/Course-2002-01.scala +run/t0421-new.scala +run/runtime.scala +run/t0421-old.scala +run/spec-self.scala +run/t5552.scala +run/Course-2002-02.scala +run/Course-2002-04.scala +run/promotion.scala +run/t4617.scala +run/Course-2002-09.scala +run/t5866.scala +run/try-catch-unify.scala +run/impconvtimes.scala +run/Course-2002-10.scala +run/Course-2002-08.scala +run/t6318_primitives.scala + +# Adapt checkfiles for ().toString == "undefined" +run/t5680.scala +run/dynamic-anyval.scala +run/macro-bundle-toplevel +run/macro-bundle-whitebox-decl +run/t6662 +run/t8570a.scala +run/t3702.scala +run/t7657 +run/macro-bundle-static +run/structural.scala + +# Adapt checkfiles for print & flush (which we cannot 100% emulate) +run/imports.scala +run/misc.scala + +# Adapt checkfiles for compiler phase list +run/t6102.scala +neg/t7494-no-options + +# Adapt checkfiles for different behavior with boxed types +run/t5568.scala +run/virtpatmat_typetag.scala +run/virtpatmat_switch.scala +run/t5629b.scala + diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/neg/t7494-no-options.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/neg/t7494-no-options.check new file mode 100644 index 0000000..581da38 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/neg/t7494-no-options.check @@ -0,0 +1,42 @@ +error: Error: ploogin takes no options + phase name id description + ---------- -- ----------- + parser 1 parse source into ASTs, perform simple desugaring + namer 2 resolve names, attach symbols to named trees +packageobjects 3 load package objects + typer 4 the meat and potatoes: type the trees + jsinterop 5 + patmat 6 translate match expressions +superaccessors 7 add super accessors in traits and nested classes + extmethods 8 add extension methods for inline classes + pickler 9 serialize symbol tables + refchecks 10 reference/override checking, translate nested objects + uncurry 11 uncurry, translate function values to anonymous classes + tailcalls 12 replace tail calls by jumps + specialize 13 @specialized-driven class and method specialization + explicitouter 14 this refs to outer pointers + erasure 15 erase types, add interfaces for traits + posterasure 16 clean up erased inline classes + lazyvals 17 allocate bitmaps, translate lazy vals into lazified defs + lambdalift 18 move nested functions to top level + constructors 19 move field definitions into constructors + flatten 20 eliminate inner classes + mixin 21 mixin composition + jscode 22 + cleanup 23 platform-specific cleanups, generate reflective calls + delambdafy 24 remove lambdas + icode 25 generate portable intermediate code +#partest !-optimise + jvm 26 generate JVM bytecode + ploogin 27 A sample phase that does so many things it's kind of hard... + terminal 28 the last phase during a compilation run +#partest -optimise + inliner 26 optimization: do inlining +inlinehandlers 27 optimization: inline exception handlers + closelim 28 optimization: eliminate uncalled closures + constopt 29 optimization: optimize null and other constants + dce 30 optimization: eliminate dead code + jvm 31 generate JVM bytecode + ploogin 32 A sample phase that does so many things it's kind of hard... + terminal 33 the last phase during a compilation run +#partest diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/Course-2002-01.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/Course-2002-01.check new file mode 100644 index 0000000..fcda943 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/Course-2002-01.check @@ -0,0 +1,37 @@ +Course-2002-01.scala:41: warning: method loop in object M0 does nothing other than call itself recursively + def loop: Int = loop; + ^ +232 +667 +11 +10 +62.8318 +62.8318 +62.8318 +4 +81 +256 +25 +1 +737 +1 +0 +1 +76 +1.4142156862745097 +1.7321428571428572 +2.0000000929222947 +1.4142156862745097 +1.7321428571428572 +2.0000000929222947 +1.4142156862745097 +1.7321428571428572 +2.0000000929222947 +sqrt(2) = 1.4142135623746899 +sqrt(2) = 1.4142135623746899 +cbrt(2) = 1.2599210500177698 +1 +1 1 +1 2 1 +1 3 3 1 +1 4 6 4 1 diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/Course-2002-02.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/Course-2002-02.check new file mode 100644 index 0000000..ab75cfd --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/Course-2002-02.check @@ -0,0 +1,187 @@ +7 +120 + +10 +100 +2.083333333333333 +3025.7687714031754 +pi = 3.1659792728432152 + +10 +100 +2.083333333333333 +3025.7687714031754 +pi = 3.1659792728432152 + +10 +100 +2.083333333333333 +3025.7687714031754 +pi = 3.1659792728432152 + +10 +100 +2.083333333333333 +3025.7687714031754 +pi = 3.1659792728432152 + +10 +100 +2.083333333333333 +3025.7687714031754 +pi = 3.1659792728432152 + +10 +100 +2.083333333333333 +3025.7687714031754 +pi = 3.1659792728432152 + +10 +100 +2.083333333333333 +3025.7687714031754 +pi = 3.1659792728432152 + +pi = 3.181104885577714 +pi = 3.181104885577714 + +10 +100 +2.083333333333333 +3025.7687714031754 +pi = 3.1659792728432152 +pi = 3.181104885577714 +pi = 3.181104885577714 + +1.5 +1.4166666666666665 +1.4142156862745097 +1.4142135623746899 +sqrt(2) = 1.4142135623746899 + +1.5 +1.4166666666666665 +1.4142156862745097 +1.4142135623746899 +sqrt(2) = 1.4142135623746899 + +1 + 2 + .. + 5 = 15 +1 * 2 * .. * 5 = 120 + +1^2 + 2^2 + .. + 5^2 = 55 +1^2 * 2^2 * .. * 5^2 = 14400 + +factorial(0) = 1 +factorial(1) = 1 +factorial(2) = 2 +factorial(3) = 6 +factorial(4) = 24 +factorial(5) = 120 + +1 + 2 + .. + 5 = 15 +1 * 2 * .. * 5 = 120 + +1^2 + 2^2 + .. + 5^2 = 55 +1^2 * 2^2 * .. * 5^2 = 14400 + +factorial(0) = 1 +factorial(1) = 1 +factorial(2) = 2 +factorial(3) = 6 +factorial(4) = 24 +factorial(5) = 120 + +1 + 2 + .. + 5 = 15 +1 * 2 * .. * 5 = 120 + +1^2 + 2^2 + .. + 5^2 = 55 +1^2 * 2^2 * .. * 5^2 = 14400 + +factorial(0) = 1 +factorial(1) = 1 +factorial(2) = 2 +factorial(3) = 6 +factorial(4) = 24 +factorial(5) = 120 + +fib(0) = 0 +fib(1) = 1 +fib(2) = 1 +fib(3) = 2 +fib(4) = 3 +fib(5) = 5 +fib(6) = 8 +fib(7) = 13 +fib(8) = 21 +fib(9) = 34 +fib(0) = 0 +fib(1) = 1 +fib(2) = 1 +fib(3) = 2 +fib(4) = 3 +fib(5) = 5 +fib(6) = 8 +fib(7) = 13 +fib(8) = 21 +fib(9) = 34 +power(0,0) = 1 +power(0,1) = 0 +power(0,2) = 0 +power(0,3) = 0 +power(0,4) = 0 +power(0,5) = 0 +power(0,6) = 0 +power(0,7) = 0 +power(0,8) = 0 + +power(1,0) = 1 +power(1,1) = 1 +power(1,2) = 1 +power(1,3) = 1 +power(1,4) = 1 +power(1,5) = 1 +power(1,6) = 1 +power(1,7) = 1 +power(1,8) = 1 + +power(2,0) = 1 +power(2,1) = 2 +power(2,2) = 4 +power(2,3) = 8 +power(2,4) = 16 +power(2,5) = 32 +power(2,6) = 64 +power(2,7) = 128 +power(2,8) = 256 + +power(3,0) = 1 +power(3,1) = 3 +power(3,2) = 9 +power(3,3) = 27 +power(3,4) = 81 +power(3,5) = 243 +power(3,6) = 729 +power(3,7) = 2187 +power(3,8) = 6561 + +power(4,0) = 1 +power(4,1) = 4 +power(4,2) = 16 +power(4,3) = 64 +power(4,4) = 256 +power(4,5) = 1024 +power(4,6) = 4096 +power(4,7) = 16384 +power(4,8) = 65536 + +power(5,0) = 1 +power(5,1) = 5 +power(5,2) = 25 +power(5,3) = 125 +power(5,4) = 625 +power(5,5) = 3125 +power(5,6) = 15625 +power(5,7) = 78125 +power(5,8) = 390625 + diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/Course-2002-04.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/Course-2002-04.check new file mode 100644 index 0000000..fc6ad96 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/Course-2002-04.check @@ -0,0 +1,64 @@ +list0 = List(6, 3, 1, 8, 7, 1, 2, 5, 8, 4, 3, 4, 8) +list1 = List(1, 1, 2, 3, 3, 4, 4, 5, 6, 7, 8, 8, 8) +list2 = List(1, 1, 2, 3, 3, 4, 4, 5, 6, 7, 8, 8, 8) +list3 = List(1, 1, 2, 3, 3, 4, 4, 5, 6, 7, 8, 8, 8) +list4 = List(1, 1, 2, 3, 3, 4, 4, 5, 6, 7, 8, 8, 8) +list5 = List(8, 8, 8, 7, 6, 5, 4, 4, 3, 3, 2, 1, 1) +list6 = List(8, 8, 8, 7, 6, 5, 4, 4, 3, 3, 2, 1, 1) + +list0: List() -> List() +list1: List(0) -> List(0) +list2: List(0, 1) -> List(0, 1) +list3: List(1, 0) -> List(0, 1) +list4: List(0, 1, 2) -> List(0, 1, 2) +list5: List(1, 0, 2) -> List(0, 1, 2) +list6: List(0, 1, 2) -> List(0, 1, 2) +list7: List(1, 0, 2) -> List(0, 1, 2) +list8: List(2, 0, 1) -> List(0, 1, 2) +list9: List(2, 1, 0) -> List(0, 1, 2) +listA: List(6, 3, 1, 8, 7, 1, 2, 5, 8, 4) -> List(1, 1, 2, 3, 4, 5, 6, 7, 8, 8) + +f(x) = 5x^3+7x^2+5x+9 +f(0) = 9 +f(1) = 26 +f(2) = 87 +f(3) = 222 + +v1 = List(2, 3, 4) +v2 = List(6, 7, 8) + +id = List(List(1, 0, 0), List(0, 1, 0), List(0, 0, 1)) +m1 = List(List(2, 0, 0), List(0, 2, 0), List(0, 0, 2)) +m2 = List(List(1, 2, 3), List(4, 5, 6), List(7, 8, 9)) + +v1 * v1 = 29 +v1 * v2 = 65 +v2 * v1 = 65 +v1 * v2 = 65 + +id * v1 = List(2, 3, 4) +m1 * v1 = List(4, 6, 8) +m2 * v1 = List(20, 47, 74) + +trn(id) = List(List(1, 0, 0), List(0, 1, 0), List(0, 0, 1)) +trn(m1) = List(List(2, 0, 0), List(0, 2, 0), List(0, 0, 2)) +trn(m2) = List(List(1, 4, 7), List(2, 5, 8), List(3, 6, 9)) + +List(v1) * id = List(List(2, 3, 4)) +List(v1) * m1 = List(List(4, 6, 8)) +List(v1) * m2 = List(List(42, 51, 60)) + +id * List(v1) = List(List(2, 3, 4), List(0, 0, 0), List(0, 0, 0)) +m1 * List(v1) = List(List(4, 6, 8), List(0, 0, 0), List(0, 0, 0)) +m2 * List(v1) = List(List(2, 3, 4), List(8, 12, 16), List(14, 21, 28)) + +id * id = List(List(1, 0, 0), List(0, 1, 0), List(0, 0, 1)) +id * m1 = List(List(2, 0, 0), List(0, 2, 0), List(0, 0, 2)) +m1 * id = List(List(2, 0, 0), List(0, 2, 0), List(0, 0, 2)) +m1 * m1 = List(List(4, 0, 0), List(0, 4, 0), List(0, 0, 4)) +id * m2 = List(List(1, 2, 3), List(4, 5, 6), List(7, 8, 9)) +m2 * id = List(List(1, 2, 3), List(4, 5, 6), List(7, 8, 9)) +m1 * m2 = List(List(2, 4, 6), List(8, 10, 12), List(14, 16, 18)) +m2 * m1 = List(List(2, 4, 6), List(8, 10, 12), List(14, 16, 18)) +m2 * m2 = List(List(30, 36, 42), List(66, 81, 96), List(102, 126, 150)) + diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/Course-2002-08.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/Course-2002-08.check new file mode 100644 index 0000000..0585d5b --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/Course-2002-08.check @@ -0,0 +1,171 @@ +x = abc +count = 111 +x = hello +count = 112 + +account deposit 50 -> undefined +account withdraw 20 -> 30 +account withdraw 20 -> 10 +account withdraw 15 -> + +x deposit 30 -> undefined +y withdraw 20 -> + +x deposit 30 -> undefined +x withdraw 20 -> 10 + +x deposit 30 -> undefined +y withdraw 20 -> 10 + +2^0 = 1 +2^1 = 2 +2^2 = 4 +2^3 = 8 + +2^0 = 1 +2^1 = 2 +2^2 = 4 +2^3 = 8 + +1 2 3 +List(1, 2, 3) + +out 0 new-value = false +*** simulation started *** +out 1 new-value = true +!0 = 1 + +*** simulation started *** +out 2 new-value = false +!1 = 0 + +out 2 new-value = false + +*** simulation started *** +0 & 0 = 0 + +*** simulation started *** +0 & 1 = 0 + +*** simulation started *** +out 11 new-value = true +out 11 new-value = false +1 & 0 = 0 + +*** simulation started *** +out 14 new-value = true +1 & 1 = 1 + +out 14 new-value = false + +*** simulation started *** +0 | 0 = 0 + +*** simulation started *** +out 24 new-value = true +0 | 1 = 1 + +*** simulation started *** +1 | 0 = 1 + +*** simulation started *** +1 | 1 = 1 + +sum 34 new-value = false +carry 34 new-value = false + +*** simulation started *** +0 + 0 = 0 + +*** simulation started *** +sum 47 new-value = true +0 + 1 = 1 + +*** simulation started *** +carry 50 new-value = true +carry 50 new-value = false +sum 54 new-value = false +sum 54 new-value = true +1 + 0 = 1 + +*** simulation started *** +carry 57 new-value = true +sum 61 new-value = false +1 + 1 = 2 + +sum 61 new-value = false +carry 61 new-value = false + +*** simulation started *** +0 + 0 + 0 = 0 + +*** simulation started *** +sum 82 new-value = true +0 + 0 + 1 = 1 + +*** simulation started *** +sum 89 new-value = false +carry 90 new-value = true +sum 97 new-value = true +carry 98 new-value = false +0 + 1 + 0 = 1 + +*** simulation started *** +sum 113 new-value = false +carry 114 new-value = true +0 + 1 + 1 = 2 + +*** simulation started *** +sum 121 new-value = true +carry 122 new-value = false +sum 129 new-value = false +sum 129 new-value = true +1 + 0 + 0 = 1 + +*** simulation started *** +carry 137 new-value = true +sum 144 new-value = false +1 + 0 + 1 = 2 + +*** simulation started *** +carry 152 new-value = false +sum 152 new-value = true +sum 158 new-value = false +carry 159 new-value = true +1 + 1 + 0 = 2 + +*** simulation started *** +sum 173 new-value = true +1 + 1 + 1 = 3 + +in 0 new-value = false +ctrl0 0 new-value = false +ctrl1 0 new-value = false +ctrl2 0 new-value = false +out0 0 new-value = false +out1 0 new-value = false +out2 0 new-value = false +out3 0 new-value = false +out4 0 new-value = false +out5 0 new-value = false +out6 0 new-value = false +out7 0 new-value = false +in 0 new-value = true +*** simulation started *** +out0 10 new-value = true +ctrl0 10 new-value = true +*** simulation started *** +out1 13 new-value = true +out0 14 new-value = false +ctrl1 14 new-value = true +*** simulation started *** +out3 20 new-value = true +out1 21 new-value = false +ctrl2 21 new-value = true +*** simulation started *** +out7 30 new-value = true +out3 31 new-value = false +ctrl0 31 new-value = false +*** simulation started *** +out7 34 new-value = false +out6 35 new-value = true diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/Course-2002-09.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/Course-2002-09.check new file mode 100644 index 0000000..c921361 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/Course-2002-09.check @@ -0,0 +1,50 @@ +Probe: f = 32 +Probe: c = 0 +Probe: f = ? +Probe: c = ? + +Probe: f = 212 +Probe: c = 100 +Probe: f = ? +Probe: c = ? + +Probe: c = 0 +Probe: f = 32 +Probe: c = ? +Probe: f = ? + +Probe: c = 100 +Probe: f = 212 +Probe: c = ? +Probe: f = ? + +0 Celsius -> 32 Fahrenheits +100 Celsius -> 212 Fahrenheits +32 Fahrenheits -> 0 Celsius +212 Fahrenheits -> 100 Celsius + +a = ?, b = ?, c = ? => ? * ? = ? +a = 2, b = ?, c = ? => 2 * ? = ? +a = ?, b = 3, c = ? => ? * 3 = ? +a = ?, b = ?, c = 6 => ? * ? = 6 +a = 2, b = 3, c = ? => 2 * 3 = 6 +a = 2, b = ?, c = 6 => 2 * 3 = 6 +a = ?, b = 3, c = 6 => 2 * 3 = 6 +a = 2, b = 3, c = 6 => 2 * 3 = 6 + +a = 0, b = ?, c = ? => 0 * ? = 0 +a = ?, b = 0, c = ? => ? * 0 = 0 +a = ?, b = ?, c = 0 => ? * ? = 0 +a = 0, b = 7, c = ? => 0 * 7 = 0 +a = 7, b = 0, c = ? => 7 * 0 = 0 +a = 0, b = 0, c = ? => 0 * 0 = 0 +a = 0, b = ?, c = 0 => 0 * ? = 0 +a = ?, b = 0, c = 0 => ? * 0 = 0 +a = 0, b = 7, c = 0 => 0 * 7 = 0 +a = 7, b = 0, c = 0 => 7 * 0 = 0 +a = 0, b = 0, c = 0 => 0 * 0 = 0 + +a = 3, b = 4 => c = 5 +a = 3, c = 5 => b = 4 +b = 4, c = 5 => a = 3 + diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/Course-2002-10.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/Course-2002-10.check new file mode 100644 index 0000000..847f0fa --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/Course-2002-10.check @@ -0,0 +1,46 @@ +fib(0) = 0 +fib(1) = 1 +fib(2) = 1 +fib(3) = 2 +fib(4) = 3 +fib(5) = 5 +fib(6) = 8 +fib(7) = 13 +fib(8) = 21 +fib(9) = 34 +fib(10) = 55 +fib(11) = 89 +fib(12) = 144 +fib(13) = 233 +fib(14) = 377 +fib(15) = 610 +fib(16) = 987 +fib(17) = 1597 +fib(18) = 2584 +fib(19) = 4181 + +pi(0) = 4 , 3.166666666666667 , 4 +pi(1) = 2.666666666666667 , 3.1333333333333337, 3.166666666666667 +pi(2) = 3.466666666666667 , 3.1452380952380956, 3.142105263157895 +pi(3) = 2.8952380952380956, 3.1396825396825396, 3.1415993573190044 +pi(4) = 3.33968253968254 , 3.142712842712843 , 3.141592714033778 +pi(5) = 2.976046176046176 , 3.140881340881341 , 3.1415926539752923 +pi(6) = 3.283738483738484 , 3.142071817071817 , 3.141592653591176 +pi(7) = 3.017071817071817 , 3.1412548236077646, 3.141592653589777 +pi(8) = 3.252365934718876 , 3.1418396189294024, 3.141592653589794 +pi(9) = 3.0418396189294024, 3.141406718496502 , 3.1415926535897936 +pi = 3.141592653589793 , 3.141592653589793 , 3.141592653589793 + +ln(0) = 1 , 0.7 , 1 +ln(1) = 0.5 , 0.6904761904761905, 0.7 +ln(2) = 0.8333333333333333, 0.6944444444444444, 0.6932773109243697 +ln(3) = 0.5833333333333333, 0.6924242424242424, 0.6931488693329254 +ln(4) = 0.7833333333333333, 0.6935897435897436, 0.6931471960735491 +ln(5) = 0.6166666666666667, 0.6928571428571428, 0.6931471806635636 +ln(6) = 0.7595238095238095, 0.6933473389355742, 0.6931471805604038 +ln(7) = 0.6345238095238095, 0.6930033416875522, 0.6931471805599444 +ln(8) = 0.7456349206349207, 0.6932539682539682, 0.6931471805599426 +ln(9) = 0.6456349206349206, 0.6930657506744463, 0.6931471805599453 +ln = 0.6931471805599453, 0.6931471805599453, 0.6931471805599453 + +prime numbers: 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 101 103 107 109 113 diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/bugs.sem b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/bugs.sem new file mode 100644 index 0000000..d36898b --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/bugs.sem @@ -0,0 +1 @@ +asInstanceOfs diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/dynamic-anyval.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/dynamic-anyval.check new file mode 100644 index 0000000..c125372 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/dynamic-anyval.check @@ -0,0 +1,4 @@ +undefined.dingo(bippy, 5) +List(1, 2, 3).dingo(bippy, 5) +undefined.dingo(bippy, 5) +List(1, 2, 3).dingo(bippy, 5) diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/impconvtimes.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/impconvtimes.check new file mode 100644 index 0000000..082377e --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/impconvtimes.check @@ -0,0 +1 @@ +3.0 * Hour = Measure(3,Hour) diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/imports.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/imports.check new file mode 100644 index 0000000..1aad598 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/imports.check @@ -0,0 +1,21 @@ +In C_ico, v_ico .toString() returns ↩ +↪C_ico -> ok +In C_ico, field .toString() returns ↩ +↪C_ico -> ok +In C_ico, method.toString() returns ↩ +↪C_ico -> ok + +In C_ioc, v_ioc .toString() returns ↩ +↪C_ioc -> ok +In C_ioc, field .toString() returns ↩ +↪C_ioc -> ok +In C_ioc, method.toString() returns ↩ +↪C_ioc -> ok + +In C_oic, v_oic .toString() returns ↩ +↪C_oic -> ok +In C_oic, field .toString() returns ↩ +↪C_oic -> ok +In C_oic, method.toString() returns ↩ +↪C_oic -> ok + diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/issue192.sem b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/issue192.sem new file mode 100644 index 0000000..10abbf7 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/issue192.sem @@ -0,0 +1 @@ +strictFloats diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/macro-bundle-static.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/macro-bundle-static.check new file mode 100644 index 0000000..e2e7628 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/macro-bundle-static.check @@ -0,0 +1,6 @@ +undefined +Int +undefined +true +IntInt +true diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/macro-bundle-toplevel.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/macro-bundle-toplevel.check new file mode 100644 index 0000000..e2e7628 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/macro-bundle-toplevel.check @@ -0,0 +1,6 @@ +undefined +Int +undefined +true +IntInt +true diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/macro-bundle-whitebox-decl.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/macro-bundle-whitebox-decl.check new file mode 100644 index 0000000..e2e7628 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/macro-bundle-whitebox-decl.check @@ -0,0 +1,6 @@ +undefined +Int +undefined +true +IntInt +true diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/misc.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/misc.check new file mode 100644 index 0000000..6043817 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/misc.check @@ -0,0 +1,62 @@ +misc.scala:46: warning: a pure expression does nothing in statement position; you may be omitting necessary parentheses + 42; + ^ +misc.scala:47: warning: a pure expression does nothing in statement position; you may be omitting necessary parentheses + 42l; + ^ +misc.scala:48: warning: a pure expression does nothing in statement position; you may be omitting necessary parentheses + 23.5f; + ^ +misc.scala:49: warning: a pure expression does nothing in statement position; you may be omitting necessary parentheses + 23.5; + ^ +misc.scala:50: warning: a pure expression does nothing in statement position; you may be omitting necessary parentheses + "Hello"; + ^ +misc.scala:51: warning: a pure expression does nothing in statement position; you may be omitting necessary parentheses + 32 + 45; + ^ +misc.scala:62: warning: a pure expression does nothing in statement position; you may be omitting necessary parentheses + x; + ^ +misc.scala:74: warning: a pure expression does nothing in statement position; you may be omitting necessary parentheses + 1 < 2; + ^ +### Hello +### 17 +### Bye + +### fib(0) = ↩ +↪1 +### fib(1) = ↩ +↪1 +### fib(2) = ↩ +↪2 +### fib(3) = ↩ +↪3 +### fib(4) = ↩ +↪5 +=== MyClass::toString === +=== MySubclass::toString === +=== MyClass::test === + +identity + +A.a = 1 +B.a = 5 +B.b = 2 + +X.a = 4 +Y.a = 11 +Y.b = 5 +Y.b = 5 + +X::foo + +Y::foo +X::foo + +3 +3 + +true diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/promotion.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/promotion.check new file mode 100644 index 0000000..41e36c3 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/promotion.check @@ -0,0 +1,4 @@ +2 +6 +20 +30 diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/runtime.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/runtime.check new file mode 100644 index 0000000..0450b94 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/runtime.check @@ -0,0 +1,70 @@ +runtime.scala:141: warning: comparing values of types Null and Null using `eq' will always yield true + check(true , null eq null, null ne null); + ^ +runtime.scala:141: warning: comparing values of types Null and Null using `ne' will always yield false + check(true , null eq null, null ne null); + ^ +<<< Test0 +[false,true] +[0,1,2] +[3,4,5] +[a,b,c] +[6,7,8] +[9,10,11] +[12,13] +[14,15] +[string] +>>> Test0 + +<<< Test1 +10 +14 +15 +16 +20 +23 +24 +25 +26 +>>> Test1 + +<<< Test2 +A +M0 +N0 + +A +N0 +M0 + +A +M0 +M1 +N0 + +A +N0 +N1 +M0 + +>>> Test2 + +<<< Test3 +Ok +Ok +Ok +Ok +Ok +Ok +Ok +Ok +Ok +Ok +Ok +Ok +Ok +Ok +Ok +Ok +>>> Test3 + diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/spec-self.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/spec-self.check new file mode 100644 index 0000000..fd3c81a --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/spec-self.check @@ -0,0 +1,2 @@ +5 +5 diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/structural.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/structural.check new file mode 100644 index 0000000..2fec112 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/structural.check @@ -0,0 +1,37 @@ + 1. hey + 2. 11 + 3. dee + 4. iei + 5. 6 + 6. 51 + 7. 2 + 8. 11 +10. 12 +11. eitch +12. 1 +13. ohone +14. 1 +15. undefined +16. one +17. tieone +18. 2 +19. true +20. 1 +21. undefined +22. one +23. oy +24. 1 +25. null +26. iei +31. 4 +32. undefined +33. iei +33. tieone +1 +2 +3 +4 +5 +caught +3 +2 diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/t0421-new.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/t0421-new.check new file mode 100644 index 0000000..00d29b7 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/t0421-new.check @@ -0,0 +1,3 @@ +[Array(0, 1),Array(2, 3),Array(4, 5)] +[Array(31)] +[Array(24, 32)] diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/t0421-old.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/t0421-old.check new file mode 100644 index 0000000..00d29b7 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/t0421-old.check @@ -0,0 +1,3 @@ +[Array(0, 1),Array(2, 3),Array(4, 5)] +[Array(31)] +[Array(24, 32)] diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/t1503.sem b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/t1503.sem new file mode 100644 index 0000000..d36898b --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/t1503.sem @@ -0,0 +1 @@ +asInstanceOfs diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/t3702.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/t3702.check new file mode 100644 index 0000000..3fce987 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/t3702.check @@ -0,0 +1,2 @@ +undefined +6 diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/t4148.sem b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/t4148.sem new file mode 100644 index 0000000..d36898b --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/t4148.sem @@ -0,0 +1 @@ +asInstanceOfs diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/t4617.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/t4617.check new file mode 100644 index 0000000..a6790f1 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/t4617.check @@ -0,0 +1 @@ +Str 8 diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/t5552.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/t5552.check new file mode 100644 index 0000000..4704611 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/t5552.check @@ -0,0 +1,2 @@ +(3,3) +(3,3) diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/t5568.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/t5568.check new file mode 100644 index 0000000..6f30cc5 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/t5568.check @@ -0,0 +1,9 @@ +void +int +class scala.runtime.BoxedUnit +class scala.runtime.BoxedUnit +class java.lang.Byte +class java.lang.Byte +5 +5 +5 diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/t5629b.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/t5629b.check new file mode 100644 index 0000000..c65298a --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/t5629b.check @@ -0,0 +1,10 @@ +=== pf(1): +MySmartPF.apply entered... +newPF.applyOrElse entered... +default +scala.MatchError: 1 (of class java.lang.Byte) +=== pf(42): +MySmartPF.apply entered... +newPF.applyOrElse entered... +ok +=== done diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/t5680.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/t5680.check new file mode 100644 index 0000000..a3b8b64 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/t5680.check @@ -0,0 +1,3 @@ +[Lscala.runtime.BoxedUnit +undefined +undefined diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/t5866.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/t5866.check new file mode 100644 index 0000000..64df1ce --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/t5866.check @@ -0,0 +1,2 @@ +0 +Foo(0) diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/t6102.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/t6102.check new file mode 100644 index 0000000..120082e --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/t6102.check @@ -0,0 +1,27 @@ +[running phase parser on t6102.scala] +[running phase namer on t6102.scala] +[running phase packageobjects on t6102.scala] +[running phase typer on t6102.scala] +[running phase jsinterop on t6102.scala] +[running phase patmat on t6102.scala] +[running phase superaccessors on t6102.scala] +[running phase extmethods on t6102.scala] +[running phase pickler on t6102.scala] +[running phase refchecks on t6102.scala] +[running phase uncurry on t6102.scala] +[running phase tailcalls on t6102.scala] +[running phase specialize on t6102.scala] +[running phase explicitouter on t6102.scala] +[running phase erasure on t6102.scala] +[running phase posterasure on t6102.scala] +[running phase lazyvals on t6102.scala] +[running phase lambdalift on t6102.scala] +[running phase constructors on t6102.scala] +[running phase flatten on t6102.scala] +[running phase mixin on t6102.scala] +[running phase jscode on t6102.scala] +[running phase cleanup on t6102.scala] +[running phase delambdafy on t6102.scala] +[running phase icode on t6102.scala] +[running phase dce on t6102.scala] +[running phase jvm on icode] diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/t6318_primitives.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/t6318_primitives.check new file mode 100644 index 0000000..08decef --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/t6318_primitives.check @@ -0,0 +1,36 @@ +true +Some(1) +false +None +true +Some(1) +false +None +true +Some() +false +None +true +Some(1) +false +None +true +Some(1) +false +None +true +Some(1) +false +None +true +Some(1) +false +None +true +Some(true) +false +None +true +Some(undefined) +false +None diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/t6662.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/t6662.check new file mode 100644 index 0000000..417b7b5 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/t6662.check @@ -0,0 +1 @@ +undefined diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/t7657.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/t7657.check new file mode 100644 index 0000000..1a87c1e --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/t7657.check @@ -0,0 +1,3 @@ +undefined +undefined +undefined diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/t7763.sem b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/t7763.sem new file mode 100644 index 0000000..d36898b --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/t7763.sem @@ -0,0 +1 @@ +asInstanceOfs diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/t8570a.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/t8570a.check new file mode 100644 index 0000000..417b7b5 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/t8570a.check @@ -0,0 +1 @@ +undefined diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/try-catch-unify.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/try-catch-unify.check new file mode 100644 index 0000000..813f011 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/try-catch-unify.check @@ -0,0 +1,4 @@ +Failure(java.lang.NumberFormatException: For input string: "Hi") +Success(5) +O NOES +Failure(java.lang.NumberFormatException: For input string: "Hi") diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/virtpatmat_switch.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/virtpatmat_switch.check new file mode 100644 index 0000000..0900a9c --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/virtpatmat_switch.check @@ -0,0 +1,7 @@ +zero +one +many +got a +got b +got some letter +scala.MatchError: 5 (of class java.lang.Byte)
\ No newline at end of file diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/virtpatmat_typetag.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/virtpatmat_typetag.check new file mode 100644 index 0000000..e95c3d0 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.1/run/virtpatmat_typetag.check @@ -0,0 +1,10 @@ +1 is not a Int; it's a class java.lang.Byte +1 is a java.lang.Integer +1 is not a java.lang.String; it's a class java.lang.Byte +true is a Any +woele is a java.lang.String +1 is not a Int; it's a class java.lang.Byte +1 is a java.lang.Integer +1 is not a java.lang.String; it's a class java.lang.Byte +true is a Any +woele is a java.lang.String diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/BlacklistedTests.txt b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/BlacklistedTests.txt new file mode 100644 index 0000000..e04dabe --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/BlacklistedTests.txt @@ -0,0 +1,914 @@ +# +# POS +# + +# Using Jsoup, what's that? +pos/cycle-jsoup.scala + +# Using scala.actors +pos/t533.scala +pos/functions.scala +pos/MailBox.scala + +# +# NEG +# + +# Using the compiler API + +neg/t6446-additional +neg/t6446-list +neg/t6446-missing +neg/t6446-show-phases.scala + +# Screws up, but not really our problem (error: None.get instead of +# phase ordering error) +neg/t7494-multi-right-after +neg/t7494-right-after-before +neg/t7622-multi-followers +neg/t7622-cyclic-dependency + +# Uses some strange macro cross compile mechanism. +neg/macro-incompatible-macro-engine-c.scala + +# Uses .java files +neg/t6289 + +# +# RUN +# + +# Relies on the exact toString() representation of Floats/Doubles +run/t2378.scala + +# Relies on !box(+0.0).equals(box(-0.0)) +run/number-parsing.scala + +# Uses ClassTags on existentials which are broken in Scala (see #251) +run/valueclasses-classtag-existential.scala + +# Relies on a particular execution speed +run/t5857.scala + +# Using parts of the javalib we don't plan to support + +run/t0412.scala +run/t3518.scala +run/t6198.scala +run/t5018.scala +run/t2417.scala +run/t6197.scala +run/t4813.scala +run/lazy-concurrent.scala +run/t5880.scala +run/mapConserve.scala +run/t3667.scala +run/bigDecimalTest.scala +run/t3038d.scala +run/numbereq.scala +run/shutdownhooks.scala +run/t4658.scala +run/t4201.scala +run/t5590.scala +run/deeps.scala +run/t3895b.scala +run/t2813.2.scala +run/t5974.scala +run/hashset.scala +run/t5262.scala +run/t5293.scala +run/t5293-map.scala +run/serialize-stream.scala +run/sysprops.scala + +run/colltest.scala +run/equality.scala +run/t2849.scala +run/t1360.scala +run/t6114.scala +run/t7269.scala +run/t3199b.scala +run/t8690.scala + +# Uses java.util.Collections +run/java-erasure.scala +run/t2250.scala + +# Uses java.math.BigDecimal / BigInteger +run/bigDecimalCache.scala +run/hashhash.scala +run/is-valid-num.scala +run/range.scala +run/stringinterpolation_macro-run.scala +run/t5356.scala +run/t6064.scala + +# Documented semantic difference on numbers (float precision) +run/interpolation.scala +run/interpolationMultiline1.scala + +# Documented semantic difference on String.split(x: Array[Char]) +run/t0325.scala + +# Using Threads +run/t6969.scala +run/inner-obj-auto.scala +run/predef-cycle.scala +run/synchronized.scala + +# Uses java.security +run/t2318.scala + +# Tries to catch java.lang.StackOverflowError +run/t6154.scala + +# Tries to catch java.lang.OutOfMemoryError +run/t7880.scala + +# Taking too much time, because JavaScript is not as fast as the JVM + +run/t3822.scala +run/collections.scala +run/t3989.scala +run/adding-growing-set.scala +run/t3242.scala +run/hashCodeDistribution.scala +run/t408.scala +run/t6584.scala +run/t6853.scala +run/UnrolledBuffer.scala + +# Crashes Rhino + +run/bridges.scala +run/patmat-exprs.scala + +# Using partest properties + +run/tailcalls.scala +run/t4294.scala +run/t6331b.scala + +# Using IO + +run/Predef.readLine.scala +run/t6488.scala +run/t6988.scala + +# Object{Output|Input}Streams +run/t6935.scala +run/t8188.scala + +# Using System.getProperties + +run/t4426.scala + +# Using ExecutionContext.global +run/t7336.scala +run/t7775.scala +run/future-flatmap-exec-count.scala + +# Using reflection + +run/t6063 + +run/mixin-bridge-methods.scala +run/t5125.scala +run/outertest.scala +run/t6223.scala +run/t5652b +run/elidable-opt.scala +run/nullable-lazyvals.scala +run/t4794.scala +run/t5652 +run/t5652c +run/getClassTest-old.scala + +run/reflection-repl-classes.scala +run/t5256e.scala +run/typetags_core.scala +run/reflection-constructormirror-toplevel-badpath.scala +run/t5276_1b.scala +run/reflection-sorted-decls.scala +run/toolbox_typecheck_implicitsdisabled.scala +run/t5418b.scala +run/toolbox_typecheck_macrosdisabled2.scala +run/abstypetags_serialize.scala +run/all-overridden.scala +run/showraw_tree_kinds.scala +run/showraw_tree_types_ids.scala +run/showraw_tree_types_typed.scala +run/showraw_tree_ids.scala +run/showraw_tree_ultimate.scala +run/t5266_2.scala +run/t5274_1.scala +run/t5224.scala +run/reflection-sanitychecks.scala +run/t6086-vanilla.scala +run/t5277_2.scala +run/reflection-methodsymbol-params.scala +run/reflection-valueclasses-standard.scala +run/t5274_2.scala +run/t5423.scala +run/reflection-modulemirror-toplevel-good.scala +run/t5419.scala +run/t5271_3.scala +run/reflection-enclosed-nested-basic.scala +run/reflection-enclosed-nested-nested-basic.scala +run/fail-non-value-types.scala +run/reflection-mem-typecheck.scala +run/exprs_serialize.scala +run/t5258a.scala +run/typetags_without_scala_reflect_manifest_lookup.scala +run/t4110-new.scala +run/t5273_2b_newpatmat.scala +run/t6277.scala +run/t5335.scala +run/toolbox_typecheck_macrosdisabled.scala +run/reflection-modulemirror-inner-good.scala +run/t5229_2.scala +run/typetags_multi.scala +run/typetags_without_scala_reflect_typetag_manifest_interop.scala +run/reflection-constructormirror-toplevel-good.scala +run/reflection-magicsymbols-invoke.scala +run/t6392b.scala +run/t5229_1.scala +run/reflection-magicsymbols-vanilla.scala +run/t5225_2.scala +run/origins.scala +run/runtimeEval1.scala +run/reflection-implClass.scala +run/reflection-enclosed-nested-inner-basic.scala +run/reflection-fieldmirror-ctorparam.scala +run/t6181.scala +run/reflection-magicsymbols-repl.scala +run/t5272_2_newpatmat.scala +run/t5270.scala +run/t5418a.scala +run/t5276_2b.scala +run/t5256f.scala +run/reflection-enclosed-basic.scala +run/reflection-constructormirror-inner-badpath.scala +run/interop_typetags_are_manifests.scala +run/newTags.scala +run/t5273_1_newpatmat.scala +run/reflection-constructormirror-nested-good.scala +run/t2236-new.scala +run/existentials3-new.scala +run/t6323b.scala +run/t5943a1.scala +run/reflection-fieldmirror-getsetval.scala +run/t5272_1_oldpatmat.scala +run/t5256h.scala +run/t1195-new.scala +run/t5840.scala +run/reflection-methodsymbol-returntype.scala +run/reflection-fieldmirror-accessorsareokay.scala +run/reflection-sorted-members.scala +run/reflection-allmirrors-tostring.scala +run/valueclasses-typetag-existential.scala +run/toolbox_console_reporter.scala +run/reflection-enclosed-inner-inner-basic.scala +run/t5256b.scala +run/bytecodecs.scala +run/elidable.scala +run/freetypes_false_alarm1.scala +run/freetypes_false_alarm2.scala +run/getClassTest-new.scala +run/idempotency-extractors.scala +run/idempotency-case-classes.scala +run/idempotency-this.scala +run/idempotency-labels.scala +run/idempotency-lazy-vals.scala +run/interop_manifests_are_abstypetags.scala +run/interop_manifests_are_typetags.scala +run/abstypetags_core.scala +run/macro-reify-abstypetag-notypeparams +run/macro-reify-abstypetag-typeparams-tags +run/macro-reify-abstypetag-typeparams-notags +run/macro-reify-abstypetag-usetypetag +run/macro-reify-freevars +run/macro-reify-splice-outside-reify +run/macro-reify-tagless-a +run/macro-reify-type +run/macro-reify-typetag-typeparams-tags +run/macro-reify-typetag-notypeparams +run/macro-undetparams-implicitval +run/manifests-new.scala +run/manifests-old.scala +run/no-pickle-skolems +run/position-val-def.scala +run/reflect-priv-ctor.scala +run/primitive-sigs-2-new.scala +run/primitive-sigs-2-old.scala +run/reflection-enclosed-inner-basic.scala +run/reflection-enclosed-inner-nested-basic.scala +run/reflection-constructormirror-inner-good.scala +run/reflection-constructormirror-nested-badpath.scala +run/reflection-fancy-java-classes +run/reflection-fieldsymbol-navigation.scala +run/reflection-fieldmirror-nmelocalsuffixstring.scala +run/reflection-fieldmirror-getsetvar.scala +run/reflection-fieldmirror-privatethis.scala +run/reflection-implicit.scala +run/reflection-mem-glbs.scala +run/reflection-mem-tags.scala +run/reflection-java-annotations +run/reflection-java-crtp +run/reflection-methodsymbol-typeparams.scala +run/reflection-modulemirror-nested-badpath.scala +run/reflection-modulemirror-inner-badpath.scala +run/reflection-modulemirror-nested-good.scala +run/reflection-modulemirror-toplevel-badpath.scala +run/reflection-sync-subtypes.scala +run/reflinit.scala +run/reflection-valueclasses-derived.scala +run/reflection-valueclasses-magic.scala +run/resetattrs-this.scala +run/runtimeEval2.scala +run/showraw_aliases.scala +run/showraw_mods.scala +run/shortClass.scala +run/showraw_nosymbol.scala +run/showraw_tree.scala +run/showraw_tree_types_untyped.scala +run/t1167.scala +run/t2577.scala +run/t2873.scala +run/t2886.scala +run/t2251b.scala +run/t3346j.scala +run/t3507-new.scala +run/t3569.scala +run/t5125b.scala +run/t5225_1.scala +run/t3425b +run/t5256a.scala +run/t5230.scala +run/t5256c.scala +run/t5256g.scala +run/t5266_1.scala +run/t5269.scala +run/t5271_1.scala +run/t5271_2.scala +run/t5271_4.scala +run/t5272_1_newpatmat.scala +run/t5272_2_oldpatmat.scala +run/t5273_1_oldpatmat.scala +run/t5273_2a_newpatmat.scala +run/t5273_2a_oldpatmat.scala +run/t5275.scala +run/t5276_1a.scala +run/t5276_2a.scala +run/t5277_1.scala +run/t5279.scala +run/t5334_1.scala +run/t5334_2.scala +run/t5415.scala +run/t5418.scala +run/t5676.scala +run/t5704.scala +run/t5710-1.scala +run/t5710-2.scala +run/t5770.scala +run/t5894.scala +run/t5816.scala +run/t5824.scala +run/t5912.scala +run/t5942.scala +run/t5943a2.scala +run/t6023.scala +run/t6113.scala +run/t6175.scala +run/t6178.scala +run/t6199-mirror.scala +run/t6199-toolbox.scala +run/t6220.scala +run/t6240-universe-code-gen.scala +run/t6221 +run/t6260b.scala +run/t6259.scala +run/t6287.scala +run/t6261.scala +run/t6308.scala +run/t6344.scala +run/t6392a.scala +run/t6591_1.scala +run/t6591_2.scala +run/t6591_3.scala +run/t6591_5.scala +run/t6591_6.scala +run/t6591_7.scala +run/t6608.scala +run/t6677.scala +run/t6687.scala +run/t6715.scala +run/t6719.scala +run/t6793.scala +run/t6860.scala +run/t6793b.scala +run/t6793c.scala +run/t7045.scala +run/t7046.scala +run/t7008-scala-defined +run/t7120b.scala +run/t7151.scala +run/t7214.scala +run/t7235.scala +run/t7331a.scala +run/t7331b.scala +run/t7331c.scala +run/t7558.scala +run/t7556 +run/t7779.scala +run/t7868b.scala +run/toolbox_current_run_compiles.scala +run/toolbox_default_reporter_is_silent.scala +run/toolbox_parse_package.scala +run/toolbox_silent_reporter.scala +run/toolbox_typecheck_inferimplicitvalue.scala +run/trait-renaming +run/typetags_serialize.scala +run/valueclasses-typetag-basic.scala +run/WeakHashSetTest.scala +run/valueclasses-typetag-generic.scala +run/t4023.scala +run/t4024.scala +run/t6380.scala +run/t5273_2b_oldpatmat.scala +run/t8104 +run/t8047.scala +run/t6992 +run/var-arity-class-symbol.scala +run/typetags_symbolof_x.scala +run/typecheck +run/t8190.scala +run/t8192 +run/t8177f.scala +run/t8199.scala +run/t7932.scala +run/t7700.scala +run/t7570c.scala +run/t7570b.scala +run/t7533.scala +run/t7570a.scala +run/t7044 +run/t7328.scala +run/t6733.scala +run/t6554.scala +run/t6732.scala +run/t6379 +run/t6411b.scala +run/t6411a.scala +run/t6260c.scala +run/t6260-delambdafy.scala +run/showdecl +run/reflection-sync-potpourri.scala +run/reflection-tags.scala +run/reflection-companiontype.scala +run/reflection-scala-annotations.scala +run/reflection-idtc.scala +run/macro-reify-nested-b2 +run/mixin-signatures.scala +run/reflection-companion.scala +run/macro-reify-nested-b1 +run/macro-reify-nested-a2 +run/macro-reify-nested-a1 +run/macro-reify-chained2 +run/macro-reify-chained1 +run/inferred-type-constructors.scala +run/mirror_symbolof_x.scala +run/t8196.scala +run/t8549b.scala +run/t8574.scala +run/t8549.scala +run/t8637.scala +run/t8346.scala + +run/reify_newimpl_29.scala +run/reify_magicsymbols.scala +run/reify_inheritance.scala +run/reify_newimpl_12.scala +run/reify_typerefs_2b.scala +run/reify_csv.scala +run/reify_inner2.scala +run/reify_maps_oldpatmat.scala +run/reify_newimpl_43.scala +run/reify_nested_inner_refers_to_local.scala +run/reify_closure7.scala +run/reify_closure8b.scala +run/reify_typerefs_3b.scala +run/reify_newimpl_44.scala +run/reify_newimpl_06.scala +run/reify_newimpl_05.scala +run/reify_newimpl_20.scala +run/reify_newimpl_23.scala +run/reify_metalevel_breach_-1_refers_to_1.scala +run/reify_newimpl_41.scala +run/reify-repl-fail-gracefully.scala +run/reify_fors_oldpatmat.scala +run/reify_inner3.scala +run/reify_closure8a.scala +run/reify_closures10.scala +run/reify_ann2a.scala +run/reify_newimpl_51.scala +run/reify_newimpl_47.scala +run/reify_extendbuiltins.scala +run/reify_newimpl_30.scala +run/reify_newimpl_38.scala +run/reify_closure2a.scala +run/reify_newimpl_45.scala +run/reify_closure1.scala +run/reify_generic2.scala +run/reify_printf.scala +run/reify_closure6.scala +run/reify_newimpl_37.scala +run/reify_newimpl_35.scala +run/reify_typerefs_3a.scala +run/reify_newimpl_25.scala +run/reify_ann4.scala +run/reify_typerefs_1b.scala +run/reify_newimpl_22.scala +run/reify_this.scala +run/reify_typerefs_2a.scala +run/reify_newimpl_03.scala +run/reify_newimpl_48.scala +run/reify_varargs.scala +run/reify_newimpl_42.scala +run/reify_newimpl_15.scala +run/reify_nested_inner_refers_to_global.scala +run/reify_newimpl_02.scala +run/reify_newimpl_01.scala +run/reify_fors_newpatmat.scala +run/reify_classfileann_a.scala +run/reify_nested_outer_refers_to_local.scala +run/reify_newimpl_13.scala +run/reify_closure5a.scala +run/reify_inner4.scala +run/reify_sort.scala +run/reify_ann1a.scala +run/reify_classfileann_b.scala +run/reify_closure4a.scala +run/reify_newimpl_33.scala +run/reify_sort1.scala +run/reify_properties.scala +run/reify_generic.scala +run/reify_newimpl_27.scala +run/reify-aliases.scala +run/reify_ann3.scala +run/reify-staticXXX.scala +run/reify_ann1b.scala +run/reify_ann5.scala +run/reify_anonymous.scala +run/reify-each-node-type.scala +run/reify_copypaste2.scala +run/reify_closure3a.scala +run/reify_copypaste1.scala +run/reify_complex.scala +run/reify_for1.scala +run/reify_getter.scala +run/reify_implicits-new.scala +run/reify_inner1.scala +run/reify_implicits-old.scala +run/reify_lazyunit.scala +run/reify_lazyevaluation.scala +run/reify_maps_newpatmat.scala +run/reify_metalevel_breach_+0_refers_to_1.scala +run/reify_metalevel_breach_-1_refers_to_0_a.scala +run/reify_metalevel_breach_-1_refers_to_0_b.scala +run/reify_nested_outer_refers_to_global.scala +run/reify_newimpl_04.scala +run/reify_newimpl_14.scala +run/reify_newimpl_11.scala +run/reify_newimpl_18.scala +run/reify_newimpl_19.scala +run/reify_newimpl_31.scala +run/reify_newimpl_21.scala +run/reify_newimpl_36.scala +run/reify_newimpl_39.scala +run/reify_newimpl_40.scala +run/reify_newimpl_49.scala +run/reify_newimpl_50.scala +run/reify_newimpl_52.scala +run/reify_renamed_term_basic.scala +run/reify_renamed_term_local_to_reifee.scala +run/reify_renamed_term_overloaded_method.scala +run/reify_renamed_type_basic.scala +run/reify_renamed_type_local_to_reifee.scala +run/reify_renamed_type_spliceable.scala +run/reify_typerefs_1a.scala +run/reify_timeofday.scala +run/reify_renamed_term_t5841.scala + +# Uses refletction indirectly through +# scala.runtime.ScalaRunTime.replStringOf +run/t6634.scala + +# Using reflection to invoke macros. These tests actually don't require +# or test reflection, but use it to separate compilation units nicely. +# It's a pity we cannot use them + +run/macro-abort-fresh +run/macro-expand-varargs-explicit-over-nonvarargs-bad +run/macro-invalidret-doesnt-conform-to-def-rettype +run/macro-invalidret-nontypeable +run/macro-invalidusage-badret +run/macro-invalidusage-partialapplication +run/macro-invalidusage-partialapplication-with-tparams +run/macro-reflective-ma-normal-mdmi +run/macro-reflective-mamd-normal-mi + +# Using macros, but indirectly creating calls to reflection +run/macro-reify-unreify + +# Using Enumeration in a way we cannot fix + +run/enums.scala +run/t3719.scala +run/t8611b.scala + +# Expecting some particular value of hashCode() + +run/MeterCaseClass.scala +run/t5608.scala +run/caseClassHash.scala +run/Meter.scala + +# Exceptions that become JavaScriptException + +run/pf-catch.scala +run/exceptions-2.scala +run/exceptions-nest.scala +run/t8601c.scala +run/t8601b.scala + +# Expecting unsupported exceptions (e.g. ArrayIndexOutOfBounds) +run/optimizer-array-load.scala +run/t8601.scala + +# Playing with classfile format + +run/classfile-format-51.scala +run/classfile-format-52.scala + +# Concurrent collections (TrieMap) +# has too much stuff implemented in *.java, so no support +run/triemap-hash.scala + +# Using parallel collections + +run/t5375.scala +run/t4894.scala +run/ctries-new +run/collection-conversions.scala +run/concurrent-map-conversions.scala +run/t4761.scala +run/concurrent-stream.scala +run/t7498.scala +run/t6448.scala +run/ctries-old +run/map_java_conversions.scala +run/parmap-ops.scala +run/pc-conversions.scala +run/t4459.scala +run/t4608.scala +run/t4723.scala +run/t4895.scala +run/t6052.scala +run/t6410.scala +run/t6467.scala +run/t6908.scala + +# Using scala.xml + +run/t4124.scala + +# Using Swing + +run/t3613.scala + +# Using the REPL + +run/t4285.scala +run/constant-type.scala +run/repl-bare-expr.scala +run/repl-parens.scala +run/repl-assign.scala +run/t5583.scala +run/treePrint.scala +run/constrained-types.scala +run/repl-power.scala +run/t4710.scala +run/repl-paste.scala +run/repl-reset.scala +run/repl-paste-3.scala +run/t6329_repl.scala +run/t6273.scala +run/repl-paste-2.scala +run/t5655.scala +run/t5072.scala +run/repl-colon-type.scala +run/kind-repl-command.scala +run/repl-trim-stack-trace.scala +run/t4594-repl-settings.scala +run/repl-save.scala +run/repl-paste-raw.scala +run/repl-paste-4.scala +run/t7801.scala +run/repl-backticks.scala +run/t6633.scala + +# Using the Repl (scala.tools.partest.ReplTest) +run/class-symbol-contravariant.scala +run/lub-visibility.scala +run/macro-bundle-repl.scala +run/macro-repl-basic.scala +run/macro-repl-dontexpand.scala +run/macro-system-properties.scala +run/reflection-equality.scala +run/reflection-repl-elementary.scala +run/reify_newimpl_26.scala +run/repl-javap-app.scala +run/repl-out-dir.scala +run/repl-term-macros.scala +run/repl-transcript.scala +run/repl-type-verbose.scala +run/t3376.scala +run/t4025.scala +run/t4172.scala +run/t4216.scala +run/t4542.scala +run/t4671.scala +run/t5256d.scala +run/t5535.scala +run/t5537.scala +run/t5789.scala +run/t6086-repl.scala +run/t6146b.scala +run/t6187.scala +run/t6320.scala +run/t6381.scala +run/t6434.scala +run/t6439.scala +run/t6507.scala +run/t6549.scala +run/t6937.scala +run/t7185.scala +run/t7319.scala +run/t7482a.scala +run/t7634.scala +run/t7747-repl.scala +run/t7805-repl-i.scala +run/tpeCache-tyconCache.scala +run/repl-empty-package +run/repl-javap-def.scala +run/repl-javap-fun.scala +run/repl-javap-mem.scala +run/repl-javap-memfun.scala +run/repl-javap-more-fun.scala +run/repl-javap-outdir +run/repl-javap.scala +run/repl-javap-outdir-funs +run/t6329_repl_bug.scala + +# Using Scala Script (partest.ScriptTest) + +run/t7711-script-args.scala + +# Using the compiler API + +run/t2512.scala +run/analyzerPlugins.scala +run/test-cpp.scala +run/compiler-asSeenFrom.scala +run/t5603.scala +run/t6440.scala +run/t5545.scala +run/existentials-in-compiler.scala +run/global-showdef.scala +run/inline-ex-handlers.scala +run/stream_length.scala +run/annotatedRetyping.scala +run/imain.scala +run/existential-rangepos.scala +run/delambdafy_uncurry_byname_inline.scala +run/delambdafy_uncurry_byname_method.scala +run/delambdafy_uncurry_inline.scala +run/delambdafy_t6555.scala +run/delambdafy_uncurry_method.scala +run/delambdafy_t6028.scala +run/memberpos.scala +run/programmatic-main.scala +run/reflection-names.scala +run/settings-parse.scala +run/sm-interpolator.scala +run/t1501.scala +run/t1500.scala +run/sammy_java8.scala +run/t1618.scala +run/t2464 +run/t4072.scala +run/t5064.scala +run/t5313.scala +run/t5385.scala +run/t5699.scala +run/t5717.scala +run/t5940.scala +run/t6028.scala +run/t6194.scala +run/t6288b-jump-position.scala +run/t6669.scala +run/t6745-2.scala +run/t6955.scala +run/t6956.scala +run/t7096.scala +run/t7271.scala +run/t7337.scala +run/t7398.scala +run/t7569.scala +run/t7852.scala +run/t7817-tree-gen.scala +run/t7825.scala +run/t7933.scala +run/t7843-jsr223-service.scala + +# partest.DirectTest +run/t6288.scala +run/t6331.scala +run/t6440b.scala +run/t6555.scala +run/t7876.scala +run/typetags_without_scala_reflect_typetag_lookup.scala +run/dynamic-updateDynamic.scala +run/dynamic-selectDynamic.scala +run/dynamic-applyDynamic.scala +run/dynamic-applyDynamicNamed.scala +run/t4841-isolate-plugins +run/large_code.scala +run/macroPlugins-namerHooks.scala +run/t4287inferredMethodTypes.scala +run/t4841-no-plugin.scala +run/t4332.scala +run/t8029.scala +run/t8046 +run/t5905-features.scala +run/t5905b-features.scala +run/large_class.scala +run/t8708_b +run/icode-reader-dead-code.scala + +# partest.BytecodeTest +run/t6546 +run/t7106 +run/t7974 +run/t8601-closure-elim.scala + +# partest.JavapTest +run/t8608-no-format.scala + +# Using .java source files + +run/t4317 +run/t4238 +run/t2296c +run/t4119 +run/t4283 +run/t4891 +run/t6168 +run/t6168b +run/t6240a +run/t6240b +run/t6548 +run/t6989 +run/t7008 +run/t7246 +run/t7246b +run/t7359 +run/t7439 +run/t7455 +run/t7510 +run/t7582-private-within +run/t7582 +run/t7582b +run/t3897 +run/t7374 +run/t3452e +run/t3452g +run/t3452d +run/t3452b-bcode +run/t3452b +run/t3452a +run/t1430 +run/t4729 +run/t8442 +run/t8601e + +# Using scala-script +run/t7791-script-linenums.scala + +# Suffers from bug in Node.js (https://github.com/joyent/node/issues/7528) +run/range-unit.scala + +### Incorrect partests ### +# Badly uses constract of Console.print (no flush) +run/t429.scala diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/BuglistedTests.txt b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/BuglistedTests.txt new file mode 100644 index 0000000..42c6146 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/BuglistedTests.txt @@ -0,0 +1,4 @@ +# The tests in this file should pass but have never passed so far +# use scala.tools.partest.scalajs.testunknownonly to only run tests +# which are neither in BuglistedTests.txt, WhitelistedTests.txt or +# BlacklistedTests.txt diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/NoDCEWarn.txt b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/NoDCEWarn.txt new file mode 100644 index 0000000..cc5aff0 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/NoDCEWarn.txt @@ -0,0 +1,8 @@ +Ljava_math_MathContext$ +Ljava_math_BigDecimal$ +Ljava_math_BigDecimal +Ljava_math_BigInteger$ +jl_Class$ +jl_Class.getClassLoader__jl_ClassLoader +jl_Class.getPackage__jl_Package +jl_Class.getInterfaces__Ajl_Class diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/WhitelistedTests.txt b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/WhitelistedTests.txt new file mode 100644 index 0000000..5538de1 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/WhitelistedTests.txt @@ -0,0 +1,2976 @@ +pos/spec-super.scala +pos/t1035.scala +pos/t5897.scala +pos/irrefutable.scala +pos/spec-partialmap.scala +pos/tcpoly_seq.scala +pos/partialfun.scala +pos/t2795-new.scala +pos/clsrefine.scala +pos/t0774 +pos/t1070.scala +pos/t5957 +pos/looping-jsig.scala +pos/t3274.scala +pos/spec-fields-old.scala +pos/t262.scala +pos/t7486.scala +pos/t2261.scala +pos/t6600.scala +pos/t4786.scala +pos/t5406.scala +pos/tcpoly_late_method_params.scala +pos/t2726 +pos/pos-bug1210.scala +pos/t3312.scala +pos/manifest1-old.scala +pos/gadt-gilles.scala +pos/t4842.scala +pos/ted.scala +pos/NoCyclicReference.scala +pos/t3568.scala +pos/t0030.scala +pos/t2635.scala +pos/t7232b +pos/t0017.scala +pos/t812.scala +pos/t2179.scala +pos/t651.scala +pos/spurious-overload.scala +pos/t758.scala +pos/t4760.scala +pos/t1672.scala +pos/mixins.scala +pos/patterns.scala +pos/t1260.scala +pos/t6551.scala +pos/t2060.scala +pos/t6575a.scala +pos/t1318.scala +pos/t4266.scala +pos/t0695 +pos/protected-static +pos/t5738.scala +pos/t1226.scala +pos/t5013 +pos/t6215.scala +pos/t5692b +pos/traits.scala +pos/t2994a.scala +pos/t3371.scala +pos/t613.scala +pos/t6499.scala +pos/xlint1.scala +pos/t1150 +pos/sealed-final.scala +pos/test4a.scala +pos/t2664.scala +pos/t3528.scala +pos/t3174.scala +pos/t6994.scala +pos/t4812.scala +pos/t5777.scala +pos/t5223.scala +pos/t439.scala +pos/t3079.scala +pos/t5829.scala +pos/t0036.scala +pos/scoping2.scala +pos/t4717.scala +pos/t4257.scala +pos/t1210a.scala +pos/getClassType.scala +pos/t5330.scala +pos/t4524.scala +pos/t2945.scala +pos/t6562.scala +pos/t0273.scala +pos/override-object-yes.scala +pos/t7426.scala +pos/t6601 +pos/t3076 +pos/seq-ordering.scala +pos/spec-groups.scala +pos/t296.scala +pos/t5545 +pos/spec-multiplectors.scala +pos/t1789.scala +pos/t2569 +pos/ksbug1.scala +pos/t0599.scala +pos/local-objects.scala +pos/t0081.scala +pos/t5756.scala +pos/t7126.scala +pos/t7716.scala +pos/t2797.scala +pos/t5399.scala +pos/t1101 +pos/t767.scala +pos/contrib467.scala +pos/t7532b +pos/self-type-override.scala +pos/t4853.scala +pos/t839.scala +pos/t5644 +pos/t5853.scala +pos/t5178.scala +pos/unapplyNeedsMemberType.scala +pos/t5390.scala +pos/t6575b.scala +pos/t151.scala +pos/t2665.scala +pos/t5120.scala +pos/erasure-nsquared.scala +pos/arrays3.scala +pos/t3136.scala +pos/inline-access-levels +pos/t3972.scala +pos/t2591.scala +pos/t3486 +pos/variances-flip.scala +pos/annotated-original +pos/typesafecons.scala +pos/stable.scala +pos/t1996.scala +pos/t3037.scala +pos/t1711 +pos/t3374.scala +pos/t0029.scala +pos/t3278.scala +pos/matthias3.scala +pos/t5546.scala +pos/t4020.scala +pos/matthias4.scala +pos/value-class-override-spec.scala +pos/arrays2.scala +pos/t5119.scala +pos/t2613.scala +pos/t4070b.scala +pos/virtpatmat_exist_uncurry.scala +pos/modules1.scala +pos/spec-constr-new.scala +pos/t6335.scala +pos/t675.scala +pos/t0644.scala +pos/t5892.scala +pos/t360.scala +pos/override.scala +pos/t1798.scala +pos/strip-tvars-for-lubbasetypes.scala +pos/hk-infer.scala +pos/t2119.scala +pos/t0231.scala +pos/t1459 +pos/t1381-new.scala +pos/t2610.scala +pos/t2708.scala +pos/t5604b +pos/t3951 +pos/t361.scala +pos/t319.scala +pos/largecasetest.scala +pos/switchUnbox.scala +pos/typetags.scala +pos/java-access-pos +pos/t803.scala +pos/t3898.scala +pos/t5692a +pos/t2421.scala +pos/t1102 +pos/t0654.scala +pos/exhaust_alternatives.scala +pos/t807.scala +pos/t5702-pos-infix-star.scala +pos/t1186 +pos/t1439.scala +pos/t7427.scala +pos/virtpatmat_binding_opt.scala +pos/t247.scala +pos/abstract.scala +pos/gen-traversable-methods.scala +pos/t2795-old.scala +pos/t5639 +pos/t2667.scala +pos/t2405.scala +pos/t1438.scala +pos/SI-7100.scala +pos/t1659.scala +pos/unchecked-a.scala +pos/t3636.scala +pos/t6745.scala +pos/t2809.scala +pos/t7022.scala +pos/t6447.scala +pos/t6367.scala +pos/t5846.scala +pos/lubs.scala +pos/t1987a.scala +pos/spec-arrays.scala +pos/virtpatmat_anonfun_for.scala +pos/listpattern.scala +pos/t5742.scala +pos/test5refine.scala +pos/switch-small.scala +pos/t5604 +pos/return_thistype.scala +pos/t348plus.scala +pos/t3420.scala +pos/t3440.scala +pos/maxim1.scala +pos/caseClassInMethod.scala +pos/t7239.scala +pos/t3833.scala +pos/t6675.scala +pos/t4402 +pos/t5953.scala +pos/t1152 +pos/t0591.scala +pos/t210.scala +pos/t7035.scala +pos/t5769.scala +pos/pmbug.scala +pos/t2331.scala +pos/t5240.scala +pos/t304.scala +pos/annotated-treecopy +pos/t2081.scala +pos/t0904.scala +pos/t7649.scala +pos/t3498-new.scala +pos/contrib701.scala +pos/t6624.scala +pos/t3924.scala +pos/t374.scala +pos/t1642 +pos/t1591_pos.scala +pos/depmet_implicit_oopsla_session_2.scala +pos/t5899.scala +pos/thistype.scala +pos/t4176b.scala +pos/elidable-tparams.scala +pos/lambdalift.scala +pos/nothing_manifest_disambig-old.scala +pos/t372.scala +pos/t5399a.scala +pos/t2782.scala +pos/patmat-extract-tparam.scala +pos/t4114.scala +pos/unapplyVal.scala +pos/t2486.scala +pos/t5877b.scala +pos/t0625.scala +pos/t6358_2.scala +pos/viewtest1.scala +pos/t1237.scala +pos/scala-singleton.scala +pos/t1254 +pos/t5504 +pos/bounds.scala +pos/t3631.scala +pos/t3177.scala +pos/unapplyContexts2.scala +pos/t0438.scala +pos/t1642b.scala +pos/inferbroadtype.scala +pos/t1858.scala +pos/t3731.scala +pos/t6963c.scala +pos/classtag-pos.scala +pos/t6221.scala +pos/t3343.scala +pos/spec-asseenfrom.scala +pos/t604.scala +pos/spec-example1.scala +pos/t0786.scala +pos/annot-inner.scala +pos/t5886.scala +pos/t1056.scala +pos/t294 +pos/spec-Function1.scala +pos/t1836 +pos/spec-private.scala +pos/depmet_implicit_tpbetareduce.scala +pos/exhaust_2.scala +pos/t7532 +pos/t5175.scala +pos/t802.scala +pos/t5809.scala +pos/tcpoly_typesub.scala +pos/t6029.scala +pos/contextbounds-implicits-new.scala +pos/t3480.scala +pos/patterns3.scala +pos/caseaccs.scala +pos/spec-sparsearray-old.scala +pos/patterns1213.scala +pos/spec-traits.scala +pos/t0020.scala +pos/cycle +pos/t5968.scala +pos/typealiases.scala +pos/init.scala +pos/t697.scala +pos/t2693.scala +pos/t2377 +pos/unapplyGeneric.scala +pos/t1385.scala +pos/t3363-old.scala +pos/t1236.scala +pos/t0068.scala +pos/t4052.scala +pos/lambdalift1.scala +pos/z1730.scala +pos/variances-local.scala +pos/virtpatmat_gadt_array.scala +pos/t2421_delitedsl.scala +pos/t5626.scala +pos/t690.scala +pos/t711.scala +pos/t6547.scala +pos/t1937 +pos/t3999 +pos/SI-7060.scala +pos/t2305.scala +pos/t2168.scala +pos/t2660.scala +pos/t1693.scala +pos/inliner2.scala +pos/t2799.scala +pos/t6966.scala +pos/t1001.scala +pos/S5.scala +pos/t0301.scala +pos/t1048.scala +pos/t415.scala +pos/t6386.scala +pos/t2187.scala +pos/hashhash-overloads.scala +pos/t6921.scala +pos/t0227.scala +pos/t6556.scala +pos/t3946 +pos/t1053.scala +pos/t1000.scala +pos/t0586.scala +pos/t7011.scala +pos/t7329.scala +pos/t4975.scala +pos/t1131.scala +pos/t1027.scala +pos/t2913.scala +pos/t3494.scala +pos/t5606.scala +pos/t4716.scala +pos/tcpoly_gm.scala +pos/t4859.scala +pos/t514.scala +pos/lexical.scala +pos/t2624.scala +pos/t4036.scala +pos/t2741 +pos/t703.scala +pos/five-dot-f.scala +pos/t805.scala +pos/strings.scala +pos/t2433 +pos/t6925.scala +pos/t1085.scala +pos/t7461 +pos/t1942 +pos/spec-lists.scala +pos/t3349 +pos/tcpoly_infer_ticket474.scala +pos/t1614 +pos/virtpatmat_reach_const.scala +pos/t2194.scala +pos/t6976 +pos/t1560.scala +pos/t6891.scala +pos/t3883.scala +pos/infersingle.scala +pos/gui.scala +pos/t1164.scala +pos/t3175-pos.scala +pos/t4336.scala +pos/annotations2.scala +pos/proj-rec-test.scala +pos/t2973.scala +pos/t1123.scala +pos/t6205.scala +pos/t5727.scala +pos/t6537.scala +pos/t6712.scala +pos/t3866.scala +pos/t4831.scala +pos/selftails.scala +pos/t397.scala +pos/spec-vector.scala +pos/t7233b.scala +pos/t1391.scala +pos/spec.scala +pos/t3106.scala +pos/contextbounds-implicits-old.scala +pos/packageobjs.scala +pos/michel3.scala +pos/t628.scala +pos/collections.scala +pos/tcpoly_boundedmonad.scala +pos/t7668.scala +pos/t0032.scala +pos/t0069.scala +pos/t4345.scala +pos/t3521 +pos/t3071.scala +pos/tcpoly_infer_easy.scala +pos/t289.scala +pos/t4365 +pos/rangepos-anonapply.scala +pos/t5033.scala +pos/lambda.scala +pos/S8.scala +pos/t6014.scala +pos/t1785.scala +pos/t6034.scala +pos/t7433.scala +pos/imp2-pos.scala +pos/t0504.scala +pos/t1272.scala +pos/t0612 +pos/value-class-override-no-spec.scala +pos/overloaded-unapply.scala +pos/t5859.scala +pos/chang +pos/localmodules.scala +pos/t4237.scala +pos/rangepos-patmat.scala +pos/t1974.scala +pos/t0054.scala +pos/michel2.scala +pos/t0770.scala +pos/t1146.scala +pos/t2441pos.scala +pos/t5099.scala +pos/tcpoly_seq_typealias.scala +pos/t946.scala +pos/tcpoly_infer_ticket1864.scala +pos/t4579.scala +pos/t4737 +pos/t7377b.scala +pos/t616.scala +pos/t201.scala +pos/t6355pos.scala +pos/escapes2.scala +pos/t1675.scala +pos/t3890.scala +pos/t6040.scala +pos/spec-tailcall.scala +pos/existentials.scala +pos/t5317.scala +pos/t7782b.scala +pos/t4758.scala +pos/t7296.scala +pos/t6896.scala +pos/cls1.scala +pos/t402.scala +pos/gosh.scala +pos/t2619.scala +pos/javaConversions-2.10-regression.scala +pos/t759.scala +pos/t5259.scala +pos/t5130.scala +pos/t5156.scala +pos/t0905.scala +pos/package-implicit +pos/t2669.scala +pos/trait-parents.scala +pos/virtpatmat_exhaust.scala +pos/patterns1.scala +pos/t7014 +pos/t1231 +pos/t1751 +pos/t7233.scala +pos/t6022.scala +pos/tcpoly_checkkinds_mix.scala +pos/depmet_implicit_norm_ret.scala +pos/package-case.scala +pos/philippe4.scala +pos/michel6.scala +pos/t4188.scala +pos/t3936 +pos/t1280.scala +pos/t6722.scala +pos/t796.scala +pos/t5542.scala +pos/t3927.scala +pos/t2293.scala +pos/t3800.scala +pos/t7285a.scala +pos/t927.scala +pos/t4494.scala +pos/t3864 +pos/ilya2 +pos/t2940 +pos/S1.scala +pos/tcpoly_wildcards.scala +pos/tryexpr.scala +pos/t6089b.scala +pos/depmet_implicit_oopsla_zipwith.scala +pos/t245.scala +pos/t6146.scala +pos/t1782 +pos/t851.scala +pos/spec-thistype.scala +pos/tcpoly_poly.scala +pos/t6815_import.scala +pos/t4649.scala +pos/t0453.scala +pos/t5020.scala +pos/ilya +pos/t2435.scala +pos/t1279a.scala +pos/t2171.scala +pos/t1957.scala +pos/gadts2.scala +pos/t3567 +pos/Z.scala +pos/t1203b +pos/nested2.scala +pos/t1896 +pos/viewtest2.scala +pos/t5541.scala +pos/existentials-harmful.scala +pos/t4063.scala +pos/t6485a +pos/t1208.scala +pos/t5041.scala +pos/unapplyComplex.scala +pos/t3384.scala +pos/t4112.scala +pos/t788.scala +pos/hklub0.scala +pos/t757.scala +pos/t1197 +pos/t359.scala +pos/t5667.scala +pos/t1107a.scala +pos/virtpatmat_castbinder.scala +pos/t267.scala +pos/t3419 +pos/t3861.scala +pos/t6797.scala +pos/spec-localdefs.scala +pos/t3404 +pos/t4457_1.scala +pos/matthias5.scala +pos/spec-polymeth.scala +pos/kinds.scala +pos/t2310.scala +pos/t6552.scala +pos/valdefs.scala +pos/hkarray.scala +pos/homonym.scala +pos/t1235 +pos/t3429 +pos/t0053.scala +pos/depmet_implicit_chaining_zw.scala +pos/virtpatmat_partialfun_nsdnho.scala +pos/t6664.scala +pos/ticket2251.scala +pos/t3495.scala +pos/super +pos/t121.scala +pos/javaConversions-2.10-ambiguity.scala +pos/t1803.scala +pos/t5877.scala +pos/t0085.scala +pos/t3582.scala +pos/t2939.scala +pos/t1422_pos.scala +pos/manifest1-new.scala +pos/t7505.scala +pos/t5720-ownerous.scala +pos/misc-unapply_pos.scala +pos/tcpoly_variance_pos.scala +pos/t5127.scala +pos/t6123-explaintypes-implicits.scala +pos/t2764 +pos/presuperContext.scala +pos/spec-simple.scala +pos/t3120 +pos/t5729.scala +pos/tcpoly_infer_ticket716.scala +pos/tcpoly_bounds1.scala +pos/t7369.scala +pos/imports-pos.scala +pos/t5654.scala +pos/t0123.scala +pos/raw-map +pos/t5330b.scala +pos/t6485b +pos/t6072.scala +pos/t5692c.scala +pos/t3430.scala +pos/tcpoly_param_scoping.scala +pos/t6204-b.scala +pos/attachments-typed-another-ident +pos/t5359.scala +pos/ticket2197.scala +pos/t720.scala +pos/t2130-2.scala +pos/t2260.scala +pos/t0304.scala +pos/t464.scala +pos/spec-maps.scala +pos/annotDepMethType.scala +pos/t6117.scala +pos/t911.scala +pos/t757a.scala +pos/t2504.scala +pos/t1381-old.scala +pos/t1232 +pos/needstypeearly.scala +pos/moduletrans.scala +pos/t4957.scala +pos/kinzer.scala +pos/t318.scala +pos/widen-existential.scala +pos/t0095.scala +pos/t566.scala +pos/tcpoly_overloaded.scala +pos/t7516 +pos/t7232 +pos/t698.scala +pos/t0002.scala +pos/t0288 +pos/t2994b.scala +pos/cls.scala +pos/t3622 +pos/t3671.scala +pos/tcpoly_subst.scala +pos/t5703 +pos/depmet_implicit_oopsla_session_simpler.scala +pos/t5022.scala +pos/builders.scala +pos/spec-foo.scala +pos/t756.scala +pos/t1569.scala +pos/implicit-unwrap-tc.scala +pos/t3688.scala +pos/t5198.scala +pos/t432.scala +pos/t6022b.scala +pos/channels.scala +pos/t1075.scala +pos/null.scala +pos/t1840 +pos/t6479.scala +pos/t6311.scala +pos/t0039.scala +pos/t1119.scala +pos/t573.scala +pos/t1136.scala +pos/t3938 +pos/spec-sealed.scala +pos/tcpoly_return_overriding.scala +pos/t3582b.scala +pos/t229.scala +pos/t3498-old.scala +pos/t531.scala +pos/t4545.scala +pos/t6651.scala +pos/t2133.scala +pos/tinondefcons.scala +pos/t6157.scala +pos/t6358.scala +pos/t7690.scala +pos/t5779-numeq-warn.scala +pos/list-extractor.scala +pos/t892.scala +pos/t2127.scala +pos/t7180.scala +pos/nullary_poly.scala +pos/virtpatmat_exist3.scala +pos/t1176 +pos/spec-funs.scala +pos/specialize10.scala +pos/t6514.scala +pos/exhaustive_heuristics.scala +pos/t0066.scala +pos/t460.scala +pos/t2130-1.scala +pos/t124.scala +pos/annotations.scala +pos/pat_gilles.scala +pos/array-interfaces.scala +pos/t6210.scala +pos/t3792.scala +pos/implicits-old.scala +pos/t389.scala +pos/t115.scala +pos/virtpatmat_exhaust_unchecked.scala +pos/scoping3.scala +pos/t6033.scala +pos/depmet_implicit_oopsla_session.scala +pos/t602.scala +pos/test5.scala +pos/t611.scala +pos/t5932.scala +pos/t4910.scala +pos/unapplySeq.scala +pos/t344.scala +pos/t3363-new.scala +pos/t4018.scala +pos/t4553.scala +pos/t5082.scala +pos/t3869.scala +pos/t3836.scala +pos/tcpoly_typeapp.scala +pos/t1409 +pos/nonlocal-unchecked.scala +pos/t0082.scala +pos/z1720.scala +pos/t7232c +pos/t2018.scala +pos/t3943 +pos/t2187-2.scala +pos/unicode-decode.scala +pos/t4757 +pos/t0710.scala +pos/t0305.scala +pos/t160.scala +pos/t7591 +pos/simplelists.scala +pos/List1.scala +pos/t516.scala +pos/t6648.scala +pos/t5165 +pos/t0055.scala +pos/t4744 +pos/t7377 +pos/t5726.scala +pos/t0091.scala +pos/t6595.scala +pos/compile.scala +pos/depmet_1_pos.scala +pos/t7364 +pos/philippe3.scala +pos/spec-doubledef-old.scala +pos/t4651.scala +pos/tcpoly_infer_implicit_tuple_wrapper.scala +pos/t6274.scala +pos/tcpoly_infer_explicit_tuple_wrapper.scala +pos/ticket2201.scala +pos/spec-fields-new.scala +pos/optmatch.scala +pos/t7517.scala +pos/t3560.scala +pos/t0165.scala +pos/t0872.scala +pos/t522.scala +pos/t2234.scala +pos/t5031_2.scala +pos/tcpoly_method.scala +pos/t6482.scala +pos/pos-bug1241.scala +pos/implicits-new.scala +pos/t2484.scala +pos/t2425.scala +pos/t1049.scala +pos/michel4.scala +pos/t5958.scala +pos/virtpatmat_instof_valuetype.scala +pos/spec-t6286.scala +pos/t873.scala +pos/t3137.scala +pos/Transactions.scala +pos/t0064.scala +pos/t7486-named.scala +pos/t5444.scala +pos/simple-exceptions.scala +pos/t1006.scala +pos/t7200b.scala +pos/t3777.scala +pos/t4840.scala +pos/t211.scala +pos/nullary.scala +pos/michel1.scala +pos/t5031_3 +pos/typealias_dubious.scala +pos/spec-doubledef-new.scala +pos/philippe1.scala +pos/thistypes.scala +pos/t3570.scala +pos/t6516.scala +pos/context.scala +pos/t3808.scala +pos/philippe2.scala +pos/constfold.scala +pos/t1292.scala +pos/t1147.scala +pos/t404.scala +pos/t4430.scala +pos/A.scala +pos/spec-partially.scala +pos/t5796.scala +pos/t2409 +pos/t284-pos.scala +pos/t5313.scala +pos/t2464 +pos/t1591b.scala +pos/hk-match +pos/t595.scala +pos/t6846.scala +pos/t6162-inheritance.scala +pos/relax_implicit_divergence.scala +pos/patterns2.scala +pos/t4692.scala +pos/t3837.scala +pos/t661.scala +pos/t2810.scala +pos/depexists.scala +pos/virtpatmat_exist4.scala +pos/t5245.scala +pos/t7190.scala +pos/isApplicableSafe.scala +pos/t6204-a.scala +pos/t0076.scala +pos/t1756.scala +pos/t1745 +pos/t6091.scala +pos/t0154.scala +pos/t530.scala +pos/t2094.scala +pos/t1034.scala +pos/t6084.scala +pos/t2454.scala +pos/t2956 +pos/tcpoly_ticket2096.scala +pos/attachments-typed-ident +pos/polymorphic-case-class.scala +pos/t252.scala +pos/spec-constr-old.scala +pos/t2421c.scala +pos/t122.scala +pos/t6574.scala +pos/t3859.scala +pos/spec-params-old.scala +pos/t1196 +pos/t4593.scala +pos/t596.scala +pos/t615.scala +pos/t7689.scala +pos/t3960.scala +pos/t3986.scala +pos/exbound.scala +pos/t2545.scala +pos/t1722 +pos/t159.scala +pos/t3272.scala +pos/t6301.scala +pos/t2794.scala +pos/t3048.scala +pos/t4970.scala +pos/t607.scala +pos/FPTest.scala +pos/test1.scala +pos/t3252.scala +pos/t4176.scala +pos/t112606A.scala +pos/t2183.scala +pos/t430-feb09.scala +pos/t6275.scala +pos/t1832.scala + +neg/volatile_no_override.scala +neg/t800.scala +neg/t5426.scala +neg/t2462a.scala +neg/t2641.scala +neg/classtags_dont_use_typetags.scala +neg/t5031 +neg/t2275b.scala +neg/macro-qmarkqmarkqmark.scala +neg/t4879.scala +neg/t5956.scala +neg/t4196.scala +neg/reify_ann2b.scala +neg/t6666b.scala +neg/warn-unused-privates.scala +neg/t6928.scala +neg/t6337.scala +neg/sealed-java-enums.scala +neg/t563.scala +neg/t900.scala +neg/deadline-inf-illegal.scala +neg/t766.scala +neg/t5429.scala +neg/overloaded-implicit.scala +neg/t875.scala +neg/abstract-class-error +neg/unchecked2.scala +neg/predef-masking.scala +neg/viewtest.scala +neg/macro-noexpand +neg/varargs.scala +neg/t963b.scala +neg/t909.scala +neg/sensitive2.scala +neg/t5390b.scala +neg/abstraction-from-volatile-type-error.scala +neg/macro-exception +neg/t4431.scala +neg/t5689.scala +neg/valueclasses.scala +neg/overload.scala +neg/t0204.scala +neg/t908.scala +neg/t750 +neg/patmatexhaust.scala +neg/macro-invalidusage-badtargs +neg/t1168.scala +neg/t5761.scala +neg/t0503.scala +neg/t7235.scala +neg/t1215.scala +neg/primitive-sigs-1 +neg/t5578.scala +neg/names-defaults-neg-warn.scala +neg/t6436b.scala +neg/t3098 +neg/t910.scala +neg/parstar.scala +neg/t4568.scala +neg/newpat_unreachable.scala +neg/warn-unused-imports.scala +neg/t1181.scala +neg/t5903c +neg/t7294.scala +neg/t4091.scala +neg/t5452-old.scala +neg/t5696.scala +neg/t0209.scala +neg/t2910.scala +neg/t7388.scala +neg/noMember2.scala +neg/no-predef.scala +neg/t6952.scala +neg/t1909b.scala +neg/abstract-report2.scala +neg/t5318.scala +neg/t6074.scala +neg/t7171.scala +neg/abstract-vars.scala +neg/unchecked-impossible.scala +neg/variances-refinement.scala +neg/t3453.scala +neg/t5189.scala +neg/t4302.scala +neg/xmltruncated7.scala + +run/t7249.scala +run/t3563.scala +run/t6111.scala +run/classtags_multi.scala +run/t5201.scala +run/checked.scala +run/valueclasses-classtag-basic.scala +run/t7171.scala +run/t5053.scala +run/t4535.scala +run/t5923d +run/t7291.scala +run/partialfun.scala +run/macro-term-declared-in-package-object +run/mapValues.scala +run/gadts.scala +run/t2386-new.scala +run/virtpatmat_stringinterp.scala +run/t657.scala +run/t0017.scala +run/t5713 +run/t576.scala +run/t3580.scala +run/virtpatmat_partial.scala +run/t6646.scala +run/mixins.scala +run/t1672.scala +run/macro-expand-implicit-macro-has-implicit +run/tuple-match.scala +run/t7039.scala +run/virtpatmat_opt_sharing.scala +run/virtpatmat_casting.scala +run/t2176.scala +run/eta-expand-star2.scala +run/macro-impl-relaxed +run/intmap.scala +run/t751.scala +run/t1591.scala +run/macro-typecheck-implicitsdisabled +run/t6911.scala +run/t5604.scala +run/macro-term-declared-in-default-param +run/collection-stacks.scala +run/multi-array.scala +run/t4560b.scala +run/buffer-slice.scala +run/t5629.scala +run/t6690.scala +run/matchonstream.scala +run/t3603.scala +run/lazy-exprs.scala +run/macro-quasiquotes +run/Course-2002-13.scala +run/t6337a.scala +run/exoticnames.scala +run/t0936.scala +run/existentials3-old.scala +run/runtime-richChar.scala +run/t6272.scala +run/t7215.scala +run/t1939.scala +run/ReverseSeqView.scala +run/lazy-leaks.scala +run/t0048.scala +run/t3994.scala +run/t2241.scala +run/t627.scala +run/t5966.scala +run/getClassTest-valueClass.scala +run/t3619.scala +run/t1300.scala +run/t2177.scala +run/t3760.scala +run/t1829.scala +run/macro-expand-implicit-macro-is-view +run/t889.scala +run/QueueTest.scala +run/t4537 +run/t3699.scala +run/valueclasses-manifest-basic.scala +run/t1192.scala +run/macro-expand-tparams-bounds +run/macro-expand-nullary-generic +run/t1434.scala +run/t6443-varargs.scala +run/macro-term-declared-in-trait +run/t4080.scala +run/t2236-old.scala +run/matcharraytail.scala +run/infiniteloop.scala +run/t5733.scala +run/virtpatmat_nested_lists.scala +run/t5158.scala +run/t6695.scala +run/t6070.scala +run/t4558.scala +run/exc2.scala +run/patmat-behavior-2.scala +run/overloads.scala +run/iterator-iterate-lazy.scala +run/t6957.scala +run/transform.scala +run/t5500.scala +run/t6663.scala +run/castsingleton.scala +run/t4147.scala +run/virtpatmat_staging.scala +run/t4565_1.scala +run/t5588.scala +run/run-bug4840.scala +run/t3496.scala +run/t5867.scala +run/search.scala +run/t3112.scala +run/hashsetremove.scala +run/interop_manifests_are_classtags.scala +run/t6443.scala +run/macro-expand-tparams-prefix +run/contrib674.scala +run/t3508.scala +run/t4300.scala +run/virtpatmat_typed.scala +run/macro-term-declared-in-class-object +run/map_test.scala +run/t5040.scala +run/t4827b.scala +run/lift-and-unlift.scala +run/t6574b.scala +run/t7240 +run/t3984.scala +run/virtpatmat_tailcalls_verifyerror.scala +run/macro-term-declared-in-class-class +run/emptypf.scala +run/t6631.scala +run/t6104.scala +run/t2818.scala +run/t3761-overload-byname.scala +run/t2526.scala +run/phantomValueClass.scala +run/t3126.scala +run/arybufgrow.scala +run/t3980.scala +run/t7375b +run/t6077_patmat_cse_irrefutable.scala +run/classmanifests_new_core.scala +run/t3395.scala +run/name-based-patmat.scala +run/inliner-infer.scala +run/t5171.scala +run/t3726.scala +run/null-hash.scala +run/t4027.scala +run/t2544.scala +run/patmatnew.scala +run/t5923b +run/t7242.scala +run/classtags_core.scala +run/streamWithFilter.scala +run/t3038b.scala +run/macro-expand-varargs-explicit-over-nonvarargs-good +run/macro-divergence-spurious +run/macro-duplicate +run/t2958.scala +run/patch-boundary.scala +run/t2333.scala +run/lazy-override-run.scala +run/macro-quasiinvalidbody-c +run/t5037.scala +run/takeAndDrop.scala +run/t6126.scala +run/t0883.scala +run/t7617a +run/t4171.scala +run/empty-array.scala +run/t7198.scala +run/t493.scala +run/genericValueClass.scala +run/t0677-old.scala +run/t1373.scala +run/t4461.scala +run/t6011b.scala +run/t7584.scala +run/t3935.scala +run/t6928-run.scala +run/t744.scala +run/t3241.scala +run/blame_eye_triple_eee-double.scala +run/t3829.scala +run/t5577.scala +run/t5914.scala +run/t601.scala +run/t5610.scala +run/macro-basic-mamd-mi +run/t6150.scala +run/stringbuilder.scala +run/t7290.scala +run/t6888.scala +run/t6327.scala +run/virtpatmat_unapplyseq.scala +run/t4656.scala +run/macro-term-declared-in-method +run/macro-expand-implicit-macro-is-implicit +run/blame_eye_triple_eee-float.scala +run/t4482.scala +run/t5488.scala +run/matchemptyarray.scala +run/t3714.scala +run/richWrapperEquals.scala +run/t5328.scala +run/stream_flatmap_odds.scala +run/implicitclasses.scala +run/t6827.scala +run/t6394b +run/complicatedmatch.scala +run/valueclasses-classmanifest-basic.scala +run/unreachable.scala +run/caseclasses.scala +run/withIndex.scala +run/exc1.scala +run/amp.scala +run/t1423.scala +run/t594.scala +run/t6353.scala +run/byname.scala +run/vector1.scala +run/t5879.scala +run/t1048.scala +run/t5080.scala +run/t4190.scala +run/caseClassEquality.scala +run/macro-enclosures +run/collections-toSelf.scala +run/implicits.scala +run/finalvar.scala +run/lazy-locals.scala +run/t7231.scala +run/t0508.scala +run/t6628.scala +run/t6406-regextract.scala +run/t0911.scala +run/t4013c.scala +run/t3502.scala +run/t5648.scala +run/retclosure.scala +run/t2857.scala +run/t4859.scala +run/t5162.scala +run/t3038.scala +run/classof.scala +run/t4062.scala +run/unapplyArray.scala +run/t4297.scala +run/t5923a +run/iterators.scala +run/t1537.scala +run/boolexprs.scala +run/valueclasses-classtag-generic.scala +run/macro-term-declared-in-anonymous +run/tcpoly_monads.scala +run/t5407.scala +run/scan.scala +run/forvaleq.scala +run/null-and-intersect.scala +run/t7047 +run/t0607.scala +run/sequenceComparisons.scala +run/t4396.scala +run/macro-undetparams-consfromsls +run/t2029.scala +run/t1220.scala +run/option-fold.scala +run/t5284c.scala +run/macro-auto-duplicate +run/t3529.scala +run/t4697.scala +run/t2251.scala +run/t5300.scala +run/virtpatmat_valdef.scala +run/t2147.scala +run/virtpatmat_extends_product.scala +run/list_map.scala +run/t1333.scala +run/matchbytes.scala +run/valueclasses-classmanifest-existential.scala +run/records.scala +run/t3088.scala +run/macro-def-path-dependent +run/t6443-by-name.scala +run/t1044.scala +run/delay-good.scala +run/case-class-23.scala +run/weakconform.scala +run/patmat-bind-typed.scala +run/t4835.scala +run/t3097.scala +run/t405.scala +run/existentials.scala +run/t2876.scala +run/t4809.scala +run/t1427.scala +run/t6135.scala +run/t3575.scala +run/t5688.scala +run/t6900.scala +run/macro-expand-unapply-a +run/t6677b.scala +run/t7375a.scala +run/t7300.scala +run/t6246.scala +run/typed-annotated +run/elidable-noflags.scala +run/t0042.scala +run/t3050.scala +run/t4536.scala +run/NestedClasses.scala +run/t3877.scala +run/seqlike-kmp.scala +run/t5907.scala +run/t266.scala +run/missingparams.scala +run/t2255.scala +run/private-inline.scala +run/t3488.scala +run/t3950.scala +run/typealias_overriding.scala +run/constant-optimization.scala +run/t7507.scala +run/t6090.scala +run/iterator-concat.scala +run/t4582.scala +run/macro-term-declared-in-class +run/macro-typecheck-macrosdisabled2 +run/t3425.scala +run/t4935.scala +run/t3326.scala +run/boolord.scala +run/t1141.scala +run/virtpatmat_unapply.scala +run/t5971.scala +run/t3651.scala +run/macro-sip19-revised +run/pure-args-byname-noinline.scala +run/preinits.scala +run/t5532.scala +run/concat-two-strings.scala +run/t3269.scala +run/macro-impl-default-params +run/t2162.scala +run/matchonseq.scala +run/t5428.scala +run/macro-expand-overload +run/t4660.scala +run/enrich-gentraversable.scala +run/macro-expand-override +run/t4054.scala +run/t4753.scala +run/valueclasses-manifest-generic.scala +run/macro-typecheck-macrosdisabled +run/t2308a.scala +run/duplicate-meth.scala +run/interop_classtags_are_classmanifests.scala +run/t3232.scala +run/t2075.scala +run/virtpatmat_partial_backquoted.scala +run/try-2.scala +run/macro-openmacros +run/macro-undetparams-macroitself +run/t6318_derived.scala +run/deprecate-early-type-defs.scala +run/dead-code-elimination.scala +run/t4827.scala +run/Course-2002-07.scala +run/slice-strings.scala +run/t6292.scala +run/t6206.scala +run/t1042.scala +run/t1718.scala +run/t2074_2.scala +run/arraycopy.scala +run/indexedSeq.scala +run/macro-term-declared-in-implicit-class +run/t3511.scala +run/t6290.scala +run/distinct.scala +run/virtpatmat_alts.scala +run/valueclasses-pavlov.scala +run/exceptions.scala +run/t1368.scala +run/t5856.scala +run/t6968.scala +run/names-defaults.scala +run/macro-expand-tparams-implicit +run/t5881.scala +run/t3540.scala +run/virtpatmat_try.scala +run/t7181.scala +run/value-class-extractor.scala +run/value-class-extractor-2.scala +run/t3150.scala +run/exc.scala +run/t3516.scala +run/delay-bad.scala +run/infix.scala +run/t1309.scala +run/t6370.scala +run/t6725-2.scala +run/macro-impl-tparam-typetag-is-optional +run/macro-term-declared-in-block +run/matchnull.scala +run/t2127.scala +run/t7325.scala +run/groupby.scala +run/t3932.scala +run/t4871.scala +run/longmap.scala +run/t1524.scala +run/t6187b.scala +run/kmpSliceSearch.scala +run/t7088.scala +run/t5804.scala +run/stringbuilder-drop.scala +run/t5753_1 +pos/cyclics-pos.scala +pos/cfcrash.scala +pos/tcpoly_higherorder_bound_method.scala +pos/t5084.scala +pos/trait-force-info.scala +pos/macro-qmarkqmarkqmark.scala +pos/t7785.scala +pos/nested.scala +pos/t3152.scala +pos/t5031 +pos/t6925b.scala +pos/t1107b +pos/t5012.scala +pos/virtpatmat_obj_in_case.scala +pos/t4938.scala +pos/t3856.scala +pos/spec-cyclic.scala +pos/aliases.scala +pos/typerep_pos.scala +pos/t119.scala +pos/t1050.scala +pos/t3670.scala +pos/t6145.scala +pos/t7315.scala +pos/t5930.scala +pos/t789.scala +pos/t5071.scala +pos/t4731.scala +pos/t4547.scala +pos/t2038.scala +pos/testCoercionThis.scala +pos/t2444.scala +pos/t5744 +pos/t780.scala +pos/t1722-A.scala +pos/virtpatmat_exist1.scala +pos/t6225.scala +pos/t762.scala +pos/t0204.scala +pos/rebind.scala +pos/spec-short.scala +pos/comp-rec-test.scala +pos/lub-dealias-widen.scala +pos/t1168.scala +pos/modules.scala +pos/t4220.scala +pos/t4070.scala +pos/t175.scala +pos/t2500.scala +pos/t5029.scala +pos/itay.scala +pos/t4202.scala +pos/t1987b +pos/t3534.scala +pos/infer2-pos.scala +pos/spec-sparsearray-new.scala +pos/t7091.scala +pos/ticket0137.scala +pos/collectGenericCC.scala +pos/t640.scala +pos/t4305.scala +pos/extractor-types.scala +pos/t3880.scala +pos/spec-annotations.scala +pos/t3577.scala +pos/compile1.scala +pos/spec-t3497.scala +pos/hkrange.scala +pos/t287.scala +pos/t7294.scala +pos/t6008.scala +pos/t4432.scala +pos/CustomGlobal.scala +pos/patmat.scala +pos/t2413 +pos/t2910.scala +pos/t592.scala +pos/t6245 +pos/infer.scala +pos/t7228.scala +pos/compound.scala +pos/attributes.scala +pos/t6771.scala +pos/t1090.scala +pos/t684.scala +pos/t577.scala +pos/t4273.scala +pos/t6278-synth-def.scala +pos/t6184.scala +neg/t0214.scala +neg/t4842.scala +neg/t6214.scala +neg/reify_nested_inner_refers_to_local.scala +neg/t576.scala +neg/t5969.scala +neg/tcpoly_variance.scala +neg/t7509.scala +neg/mixins.scala +neg/parent-inherited-twice-error.scala +neg/macro-abort +neg/constructor-init-order.scala +neg/t6042.scala +neg/t0590.scala +neg/eta-expand-star-deprecation.scala +neg/t4221.scala +neg/t6263.scala +neg/t783.scala +neg/t5554.scala +neg/macro-invalidsig-params-badtype +neg/multi-array.scala +neg/raw-types-stubs +neg/spec-overrides.scala +neg/t836.scala +neg/t7289_status_quo.scala +neg/t5675.scala +neg/macro-quasiquotes +neg/t6667.scala +neg/t6597.scala +neg/t6264.scala +neg/t0345.scala +neg/t7294b.scala +neg/t5340.scala +neg/t2144.scala +neg/t1010.scala +neg/t1838.scala +neg/t5189b.scala +neg/reify_metalevel_breach_-1_refers_to_1.scala +neg/t6601 +neg/wellkinded_wrongarity.scala +neg/t3909.scala +neg/t876.scala +neg/t5390.scala +neg/unit2anyref.scala +neg/t0351.scala +neg/t5120.scala +neg/t1038.scala +neg/t5878.scala +neg/qualifying-class-error-2.scala +neg/t3816.scala +neg/tailrec.scala +neg/volatile.scala +neg/t944.scala +neg/t1705.scala +neg/t3977.scala +neg/t5553_2.scala +neg/t5318c.scala +neg/overload-msg.scala +neg/t5440.scala +neg/t6335.scala +neg/compile-time-only-b.scala +neg/t501.scala +neg/override.scala +neg/t663.scala +neg/t5892.scala +neg/t1980.scala +neg/macro-false-deprecation-warning +neg/t5148.scala +neg/t585.scala +neg/t3776.scala +neg/interop_classtags_arenot_manifests.scala +neg/t4044.scala +neg/macro-invalidusage-nontypeable +neg/t6375.scala +neg/t500.scala +neg/t4877.scala +neg/t5357.scala +neg/interop_abstypetags_arenot_manifests.scala +neg/t4460a.scala +neg/t5318b.scala +neg/t3234.scala +neg/t4440.scala +neg/t6663.scala +neg/t6357.scala +neg/gadts1.scala +neg/cyclics.scala +neg/t5060.scala +neg/scopes.scala +run/t4013.scala +run/value-class-extractor-seq.scala +run/macro-expand-tparams-explicit +run/tuples.scala +run/t5753_2 +run/t0528.scala +run/t5105.scala +run/t1195-old.scala +run/t7341.scala +run/t3670.scala +run/t2594_tcpoly.scala +run/t3895.scala +run/t0668.scala +run/slices.scala +run/t6666a.scala +run/valueclasses-classmanifest-generic.scala +run/t2316_run.scala +run/t3004.scala +run/viewtest.scala +run/t6481.scala +run/t0005.scala +run/t4110-old.scala +run/t4766.scala +run/t5500b.scala +run/t7407b.scala +run/backreferences.scala +run/arrayview.scala +run/t629.scala +run/t5903c +run/unittest_collection.scala +run/spec-nlreturn.scala +run/macro-term-declared-in-object-object +run/triple-quoted-expr.scala +run/t5937.scala +run/t6011c.scala +run/macro-expand-implicit-argument +run/try.scala +run/t1987b +run/t6089.scala +run/macro-range +run/t2524.scala +run/t4770.scala +run/virtpatmat_unapplyprod.scala +run/t1535.scala +run/ctor-order.scala +pos/t5210.scala +pos/t5384.scala +pos/rangepos.scala +pos/t443.scala +pos/t1480.scala +pos/t116.scala +pos/seqtest2.scala +pos/scoping1.scala +pos/t4269.scala +pos/lookupswitch.scala +pos/t3642 +pos/t5706.scala +pos/SI-5788.scala +pos/t7264 +pos/t0031.scala +pos/macro-deprecate-dont-touch-backquotedidents.scala +pos/t6815.scala +pos/test4refine.scala +pos/michel5.scala +pos/t0851.scala +pos/t1185.scala +pos/sudoku.scala +pos/t7520.scala +pos/t6208.scala +pos/t3411.scala +pos/t295.scala +pos/S3.scala +pos/t0674.scala +pos/t6664b.scala +pos/variances_pos.scala +pos/liftcode_polymorphic.scala +pos/t3174b.scala +pos/t7232d +pos/t578.scala +pos/implicit-infix-ops.scala +pos/t4363.scala +pos/t532.scala +pos/exponential-spec.scala +pos/t599.scala +pos/t5862.scala +pos/t4603 +pos/t3676.scala +pos/t1357.scala +pos/native-warning.scala +pos/t1230 +pos/t6028 +pos/t4275.scala +pos/overloaded_extractor_and_regular_def.scala +pos/t4205 +pos/matthias1.scala +pos/testcast.scala +pos/generic-sigs.scala +pos/t0093.scala +pos/specializes-sym-crash.scala +pos/t0061.scala +pos/t2429.scala +pos/t694.scala +pos/javaReadsSigs +pos/t2023.scala +pos/t704.scala +pos/t2208_pos.scala +pos/t5137.scala +pos/t2683.scala +pos/t0049.scala +pos/t1029 +pos/t4243.scala +pos/typerep-stephane.scala +pos/t177.scala +pos/t5967.scala +pos/t430.scala +pos/virtpatmat_infer_single_1.scala +pos/pat_iuli.scala +pos/t1071.scala +pos/t7226.scala +pos/t1843.scala +pos/t419.scala +pos/t7364b +pos/t1159.scala +pos/t5305.scala +pos/t7694.scala +pos/t6047.scala +pos/t3578.scala +pos/t2082.scala +pos/setter-not-implicit.scala +pos/t1133.scala +pos/t3862.scala +pos/t942 +pos/nothing_manifest_disambig-new.scala +pos/iterator-traversable-mix.scala +pos/eta.scala +pos/test4.scala +pos/t2691.scala +pos/t4502.scala +pos/t7183.scala +pos/protected-t1010.scala +pos/X.scala +pos/virtpatmat_exist2.scala +pos/t4911.scala +pos/t3477.scala +pos/t4173.scala +pos/t7782.scala +pos/t2399.scala +pos/virtpatmat_alts_subst.scala +pos/propagate.scala +pos/t2421b_pos.scala +pos/t183.scala +pos/t7033.scala +pos/t3612.scala +pos/t5330c.scala +pos/t3020.scala +pos/t4869.scala +pos/t3373.scala +pos/spec-params-new.scala +pos/t3672.scala +pos/t4501.scala +pos/t1565.scala +pos/t3774.scala +pos/t6942 +neg/t3275.scala +neg/t421.scala +neg/t5702-neg-bad-brace.scala +neg/t3663 +neg/badtok-1.scala +neg/t677.scala +neg/t7756b.scala +neg/t6534.scala +neg/t6276.scala +neg/t5762.scala +neg/abstract.scala +neg/t2405.scala +neg/t0418.scala +neg/t5390c.scala +neg/lazyvals.scala +neg/lubs.scala +neg/abstract-report.scala +neg/t4163.scala +neg/t5702-neg-bad-and-wild.scala +neg/macro-invalidret +neg/t6728.scala +neg/t5152.scala +neg/t1432.scala +neg/abstract-inaccessible.scala +neg/import-precedence.scala +neg/t2462b.scala +neg/macro-invalidusage-presuper +neg/specification-scopes +neg/t6048.scala +neg/t4079 +neg/macro-basic-mamdmi +neg/t7020.scala +neg/t3015.scala +neg/t0207.scala +neg/t2296b +neg/t0673 +neg/t3761-overload-byname.scala +neg/t6675.scala +neg/t5529.scala +neg/sensitive.scala +neg/t742.scala +neg/t5067.scala +neg/t6162-overriding.scala +neg/variances.scala +neg/t5728.scala +neg/t6323a.scala +neg/compile-time-only-a.scala +neg/t6795.scala +neg/t2494.scala +neg/t3649.scala +neg/macro-invalidsig +neg/t2796.scala +neg/t112706A.scala +neg/t0764.scala +neg/t3757 +neg/t1431.scala +neg/exhausting.scala +neg/t1523.scala +neg/t779.scala +neg/xmltruncated1.scala +neg/t2208.scala +neg/t2078.scala +neg/t521.scala +neg/null-unsoundness.scala +neg/stmt-expr-discard.scala +neg/t0513.scala +neg/unchecked-abstract.scala +neg/t4460c.scala +neg/divergent-implicit.scala +neg/t5078.scala +neg/t1701.scala +neg/t0816.scala +neg/t1672b.scala +neg/macro-invalidusage-badbounds +neg/tailrec-2.scala +neg/t4064.scala +neg/reflection-names-neg.scala +neg/t5510.scala +neg/t3873.scala +neg/tailrec-3.scala +neg/t0226.scala +neg/t2031.scala +neg/t633.scala +neg/constrs.scala +neg/anyval-anyref-parent.scala +neg/t7290.scala +neg/t1041.scala +neg/patternalts.scala +neg/error_tooManyArgsPattern.scala +neg/checksensibleUnit.scala +neg/t6539 +neg/t4417.scala +neg/wellkinded_app.scala +neg/for-comprehension-old.scala +neg/t2779.scala +neg/object-not-a-value.scala +neg/t2968b.scala +neg/t6483.scala +neg/t6902.scala +neg/t6963a.scala +neg/t3399.scala +neg/t0015.scala +neg/t3995.scala +neg/t276.scala +neg/t6758.scala +neg/t2441.scala +neg/cycle-bounds.scala +neg/t1241.scala +neg/t4137.scala +neg/unicode-unterminated-quote.scala +neg/t4762.scala +neg/typeerror.scala +neg/implicits.scala +neg/t961.scala +neg/ambiguous-float-dots2.scala +neg/t2416.scala +neg/t5799.scala +neg/t7285.scala +neg/implicit-shadow.scala +neg/t2388.scala +neg/java-access-neg +neg/found-req-variance.scala +neg/hk-bad-bounds.scala +neg/t3224.scala +neg/t1033.scala +neg/t7385.scala +neg/t5882.scala +neg/t4541.scala +neg/t2973.scala +neg/t6406-regextract.scala +neg/t6666.scala +neg/t4831.scala +neg/t425.scala +neg/t1845.scala +neg/t3683b.scala +neg/t2801.scala +neg/t6083.scala +neg/t0528neg.scala +neg/stringinterpolation_macro-neg.scala +neg/t668.scala +neg/t5666.scala +neg/t4271.scala +neg/interop_typetags_arenot_classmanifests.scala +neg/t1355.scala +neg/t715.scala +neg/t7238.scala +neg/t7473.scala +neg/t7292-removal.scala +neg/tcpoly_infer_ticket1162.scala +neg/t4098.scala +neg/t6013 +neg/t6227.scala +neg/t464-neg.scala +neg/badtok-3.scala +neg/t6082.scala +neg/anytrait.scala +neg/valueclasses-doubledefs.scala +neg/t7519.scala +neg/overloaded-unapply.scala +neg/t1163.scala +neg/wellkinded_bounds.scala +neg/t7292-deprecation.scala +neg/t5044.scala +neg/t0842.scala +neg/t6436.scala +neg/interop_typetags_arenot_classtags.scala +neg/t3653.scala +neg/higherkind_novalue.scala +neg/t935.scala +neg/t6040.scala +neg/annot-nonconst.scala +neg/macro-deprecate-idents.scala +neg/illegal-stmt-start.scala +neg/t565.scala +neg/case-collision.scala +neg/t3209.scala +neg/t5821.scala +neg/abstract-class-2.scala +neg/t846.scala +neg/quasiquotes-syntax-error-position.scala +neg/t3987.scala +neg/t877.scala +neg/t0117.scala +neg/t692.scala +neg/t6666d.scala +neg/t5702-neg-ugly-xbrace.scala +neg/t7752.scala +neg/case-collision2.scala +neg/t6526.scala +neg/t2213.scala +neg/t7756a.scala +neg/t845.scala +neg/macro-override-macro-overrides-abstract-method-a +neg/tcpoly_ticket2101.scala +neg/delayed-init-ref.scala +neg/caseinherit.scala +neg/t3189.scala +neg/unchecked-suppress.scala +neg/t2180.scala +neg/t1371.scala +neg/macro-cyclic +neg/t6123-explaintypes-macros +neg/t4134.scala +neg/t691.scala +neg/t2421b.scala +neg/t4691_exhaust_extractor.scala +neg/t4419.scala +neg/t5801.scala +neg/t650.scala +neg/t5735.scala +neg/t696.scala +neg/t882.scala +neg/t2968.scala +neg/t7507.scala +neg/macro-invalidusage-badargs +neg/macro-reify-typetag-typeparams-notags +neg/wellkinded_app2.scala +neg/t4425b.scala +neg/t2296a +neg/t1878.scala +neg/t649.scala +neg/override-object-no.scala +neg/t4174.scala +neg/t2070.scala +neg/sabin2.scala +neg/t5903e +neg/t6566a.scala +neg/finitary-error.scala +neg/t4818.scala +neg/t3614.scala +neg/t6666c.scala +neg/ticket513.scala +neg/suggest-similar.scala +neg/t4457_1.scala +neg/t6666e.scala +neg/tcpoly_bounds.scala +neg/t4727.scala +neg/t4425.scala +neg/macro-invalidusage-methodvaluesyntax +neg/t3854.scala +neg/t3006.scala +neg/t5580b.scala +neg/t5378.scala +neg/t639.scala +neg/wrong-args-for-none.scala +neg/t7171b.scala +neg/t5361.scala +neg/unreachablechar.scala +neg/t5572.scala +neg/t7757a.scala +neg/macro-invalidimpl +neg/t2773.scala +neg/t6359.scala +neg/saito.scala +neg/xmltruncated2.scala +neg/t667.scala +neg/t3934.scala +neg/t6771b.scala +neg/t4584.scala +neg/wellkinded_wrongarity2.scala +neg/t7369.scala +neg/t1477.scala +neg/t5617.scala +neg/t7299.scala +neg/faculty.scala +neg/virtpatmat_reach_null.scala +neg/macro-reify-typetag-hktypeparams-notags +neg/t1224.scala +neg/xmltruncated3.scala +neg/t1872.scala +neg/t558.scala +neg/t7110.scala +neg/any-vs-anyref.scala +neg/t6340.scala +neg/t4166.scala +neg/t2918.scala +neg/t5856.scala +neg/t4989.scala +neg/t0003.scala +neg/t1183.scala +neg/t963.scala +neg/t4515.scala +neg/valueclasses-pavlov.scala +neg/t608.scala +neg/choices.scala +neg/patmat-type-check.scala +neg/valueclasses-impl-restrictions.scala +neg/imp2.scala +neg/protected-constructors.scala +neg/t6788.scala +neg/nullary-override.scala +neg/t200.scala +neg/t343.scala +neg/names-defaults-neg-ref.scala +neg/tcpoly_typealias.scala +neg/classtags_contextbound_b.scala +neg/t729.scala +neg/t5683.scala +neg/t4928.scala +neg/t700.scala +neg/t7669.scala +neg/macro-invalidshape +neg/t6011.scala +neg/t7325.scala +neg/check-dead.scala +neg/t550.scala +neg/t5663-badwarneq.scala +neg/t0699 +neg/nopredefs.scala +neg/t3507-old.scala +neg/t5352.scala +neg/t6336.scala +neg/interop_classmanifests_arenot_typetags.scala +neg/sealed-final-neg.scala +neg/t2102.scala +neg/t7636.scala +neg/t5031b +neg/t798.scala +neg/t5702-neg-bad-xbrace.scala +neg/t0899.scala +neg/cyclics-import.scala +neg/badtok-2.scala +neg/t473.scala +neg/t3160ambiguous.scala +neg/t5106.scala +neg/t1286 +neg/macro-override-macro-overrides-abstract-method-b +neg/t0259.scala +neg/t510.scala +neg/t3836.scala +neg/t5830.scala +neg/t1548 +neg/t5580a.scala +neg/forward.scala +neg/t591.scala +neg/t6558b.scala +neg/t556.scala +neg/xmltruncated4.scala +neg/t5497.scala +neg/t409.scala +neg/t6283.scala +neg/override-object-flag.scala +neg/constructor-prefix-error.scala +neg/eta-expand-star.scala +neg/t3392.scala +neg/t1275.scala +neg/nested-fn-print.scala +neg/t7330.scala +neg/t2275a.scala +neg/t630.scala +neg/t4270.scala +neg/t2775.scala +neg/pat_unreachable.scala +neg/t4158.scala +neg/unit-returns-value.scala +neg/t1422.scala +neg/reify_metalevel_breach_-1_refers_to_0_b.scala +neg/reassignment.scala +neg/t3683a.scala +neg/noMember1.scala +neg/macro-without-xmacros-b +neg/t1106.scala +neg/t5182.scala +neg/t6889.scala +neg/t4217.scala +neg/t7501 +neg/t5063.scala +neg/t1009.scala +neg/t997.scala +neg/unchecked.scala +neg/classtags_contextbound_c.scala +neg/applydynamic_sip.scala +neg/t7715.scala +neg/t588.scala +neg/t6667b.scala +neg/t7757b.scala +neg/t4069.scala +neg/t515.scala +neg/variances2.scala +neg/t1049.scala +neg/t7289.scala +neg/t1623.scala +neg/permanent-blindness.scala +neg/t5803.scala +neg/super-cast-or-test.scala +neg/nonlocal-warning.scala +neg/t5687.scala +neg/t5903a +neg/t6566b.scala +neg/unchecked-knowable.scala +neg/t5093.scala +neg/protected-static-fail +neg/type-diagnostics.scala +neg/forgot-interpolator.scala +neg/interop_abstypetags_arenot_classmanifests.scala +neg/t5376.scala +neg/t545.scala +neg/xmlcorner.scala +neg/switch.scala +neg/depmet_1.scala +neg/abstract-concrete-methods.scala +neg/t4987.scala +neg/t5452-new.scala +neg/t750b +neg/unchecked-refinement.scala +neg/t418.scala +neg/t5354.scala +neg/t3736.scala +neg/t631.scala +neg/t6829.scala +neg/t0218.scala +neg/volatile-intersection.scala +neg/t412.scala +neg/t693.scala +neg/t4882.scala +neg/t1960.scala +neg/macro-divergence-controlled +neg/t712.scala +neg/t5544 +neg/t3222.scala +neg/t3604.scala +neg/t1112.scala +neg/t7157 +neg/accesses.scala +neg/t452.scala +neg/t6162-inheritance +neg/t2442 +neg/t6567.scala +neg/lazy-override.scala +neg/abstract-explaintypes.scala +neg/nested-annotation.scala +neg/t5753 +neg/t4283b +neg/t3691.scala +neg/infix-op-positions.scala +neg/t3403.scala +neg/t4851 +neg/structural.scala +neg/error_dependentMethodTpeConversionToFunction.scala +neg/t5839.scala +neg/t5553_1.scala +neg/reify_metalevel_breach_+0_refers_to_1.scala +neg/t752.scala +neg/t6574.scala +neg/t3714-neg.scala +neg/t4457_2.scala +neg/t2148.scala +neg/t3240.scala +neg/t1364.scala +neg/saferJavaConversions.scala +neg/t414.scala +neg/t5493.scala +neg/classtags_contextbound_a.scala +neg/reify_metalevel_breach_-1_refers_to_0_a.scala +neg/t3118.scala +neg/t512.scala +neg/t2336.scala +neg/t856.scala +neg/xmltruncated6.scala +neg/t2206.scala +neg/virtpatmat_unreach_select.scala +neg/t6258.scala +neg/t6815.scala +neg/not-possible-cause.scala +neg/dbldef.scala +neg/qualifying-class-error-1.scala +neg/t835.scala +neg/t5455.scala +neg/t6558.scala +neg/t708.scala +neg/macro-nontypeablebody +neg/t0565.scala +neg/xmltruncated5.scala +neg/t5390d.scala +neg/t520.scala +neg/t6138.scala +neg/macro-without-xmacros-a +neg/t7214neg.scala +neg/t2870.scala +neg/t593.scala +neg/t4541b.scala +neg/t4460b.scala +neg/t284.scala +neg/t2488.scala +neg/macro-override-method-overrides-macro +neg/interop_abstypetags_arenot_classtags.scala +neg/t3769.scala +neg/warn-inferred-any.scala +neg/t664.scala +neg/t5903d +neg/t562.scala +neg/t2316.scala +neg/t0152.scala +neg/migration28.scala +neg/t6443c.scala +neg/tcpoly_override.scala +neg/t7324.scala +neg/t987.scala +neg/t5903b +neg/t3481.scala +neg/t6912.scala +neg/tcpoly_variance_enforce.scala +neg/t3913.scala +neg/names-defaults-neg.scala +neg/t765.scala +neg/t5358.scala +neg/t391.scala +neg/serialversionuid-not-const.scala +neg/t771.scala +neg/t0903.scala +neg/catch-all.scala +neg/classmanifests_new_deprecations.scala +neg/t0606.scala +neg/t5189_inferred.scala +neg/macro-reify-typetag-useabstypetag +neg/t5543.scala +neg/logImplicits.scala +neg/interop_typetags_without_classtags_arenot_manifests.scala +neg/t6535.scala +neg/t7259.scala +neg/t2139.scala +neg/t278.scala +neg/t5564.scala +neg/unchecked3.scala +neg/virtpatmat_reach_sealed_unsealed.scala +neg/checksensible.scala +neg/t7721.scala +run/t3798.scala +run/macro-expand-varargs-explicit-over-varargs +run/t3888.scala +run/t0677-new.scala +run/t3273.scala +run/t3763.scala +run/t2755.scala +run/t920.scala +run/t5610a.scala +run/literals.scala +run/proxy.scala +run/unapply.scala +run/t5830.scala +run/array-addition.scala +run/macro-expand-nullary-nongeneric +run/macro-basic-ma-mdmi +run/valueclasses-constr.scala +run/t1247.scala +run/t3487.scala +run/rawstrings.scala +run/patmat-seqs.scala +run/eta-expand-star.scala +run/t7436.scala +run/t3996.scala +run/constructors.scala +run/t498.scala +run/t3835.scala +run/t298.scala +run/t2867.scala +run/t7120 +run/virtpatmat_literal.scala +run/t2175.scala +run/t2503.scala +run/t3026.scala +run/t603.scala +run/t0091.scala +run/t6394a +run/macro-expand-varargs-implicit-over-varargs +run/t7407.scala +run/t2552.scala +run/priorityQueue.scala +run/virtpatmat_npe.scala +run/macro-sip19 +run/t6644.scala +run/t6614.scala +run/t2005.scala +run/t4680.scala +run/t5903a +run/classtags_contextbound.scala +run/Course-2002-05.scala +run/applydynamic_sip.scala +run/t1766.scala +run/retsynch.scala +run/t7715.scala +run/t102.scala +run/nonlocalreturn.scala +run/macro-reify-staticXXX +run/Course-2002-06.scala +run/t6863.scala +run/t6500.scala +run/macro-impl-rename-context +run/t4351.scala +run/t5009.scala +run/macro-term-declared-in-annotation +run/t6271.scala +run/array-existential-bound.scala +run/t6443b.scala +run/t1987.scala +run/MutableListTest.scala +run/t7571.scala +run/t5488-fn.scala +run/macro-bodyexpandstoimpl +run/macro-reify-ref-to-packageless +run/t2212.scala +run/macro-expand-varargs-implicit-over-nonvarargs +run/t0807.scala +run/patmat-behavior.scala +run/t2446.scala +run/tuple-zipped.scala +run/breakout.scala +run/t4122.scala +run/macro-settings +run/t7157 +run/t1323.scala +run/t4013b.scala +run/t6309.scala +run/t4047.scala +run/t5544 +run/t978.scala +run/t3361.scala +run/t6611.scala +run/t5387.scala +run/t5656.scala +run/t4897.scala +run/numeric-range.scala +run/t4777.scala +run/Course-2002-03.scala +run/string-extractor.scala +run/view-headoption.scala +run/patmat_unapp_abstype-new.scala +run/stream-stack-overflow-filter-map.scala +run/macro-impl-tparam-only-in-impl +run/t6559.scala +run/macro-reify-tagful-a +run/macro-expand-multiple-arglists +run/t4709.scala +run/t3509.scala +run/t5284b.scala +run/t7617b +run/t3923.scala +run/virtpatmat_apply.scala +run/t363.scala +run/manifests-undeprecated-in-2.10.0.scala +run/matchintasany.scala +run/t3970.scala +run/t4996.scala +run/t5530.scala +run/macro-term-declared-in-object-class +run/t3242b.scala +run/indexedSeq-apply.scala +run/t107.scala +run/t2337.scala +run/t3758-old.scala +run/t2754.scala +run/valueclasses-manifest-existential.scala +run/flat-flat-flat.scala +run/t6673.scala +run/interpolationMultiline2.scala +run/t3493.scala +run/t0631.scala +run/t2800.scala +run/t6506.scala +run/t6260.scala +run/t2418.scala +run/t4415.scala +run/classmanifests_new_alias.scala +run/t5380.scala +run/tcpoly_parseridioms.scala +run/t1747.scala +run/t5903d +run/t3530.scala +run/t216.scala +run/macro-term-declared-in-refinement +run/t4592.scala +run/t2488.scala +run/t3327.scala +run/t5614.scala +run/t5903b +run/iterables.scala +run/t3964.scala +run/t6329_vanilla.scala +run/t3038c +run/t1697.scala +run/t2030.scala +run/t3397.scala +run/t1005.scala +run/t3353.scala +run/t1466.scala +run/t3186.scala +run/tcpoly_overriding.scala +run/t5394.scala +run/t5284.scala +run/unboxingBug.scala +run/t7200.scala +run/macro-reify-basic +run/t153.scala +run/iterator3444.scala +run/macro-expand-implicit-macro-is-val +run/macro-basic-ma-md-mi +run/interpolationArgs.scala +run/t4954.scala +run/t3645.scala +run/transpose.scala +run/t3887.scala +run/t4288.scala +run/unittest_iterator.scala +run/t5543.scala +run/macro-term-declared-in-object +run/iq.scala +run/t2788.scala +run/t2027.scala +run/macro-expand-recursive +run/t949.scala +run/t1909b.scala +run/delambdafy-nested-by-name.scala +run/delambdafy-two-lambdas.scala +run/macro-blackbox-materialization +run/lists-run.scala +run/macro-parse-position +run/macro-parse-position-malformed +run/macro-whitebox-dynamic-materialization +run/macro-whitebox-extractor +run/macro-vampire-false-warning +run/macro-whitebox-fundep-materialization +run/macro-whitebox-structural +run/mutable-treeset.scala +run/static-module-method.scala +run/sort.scala +run/t1909.scala +run/t1909c.scala +run/t3346a.scala +run/t3346d.scala +run/t3346f.scala +run/t3346h.scala +run/t3346g.scala +run/t3832.scala +run/t4742.scala +run/t5377.scala +run/t5923c.scala +run/t6188.scala +run/t6333.scala +run/t6385.scala +run/t7899.scala +run/t7899-regression.scala +run/t7584b.scala +run/t7223.scala +run/t7859 +run/t7868.scala +run/t7871 +run/arrayclone-new.scala +run/arrayclone-old.scala +run/bitsets.scala +run/comparable-comparator.scala +run/colltest1.scala +run/t2106.scala +run/t5986.scala +run/view-iterator-stream.scala +run/array-charSeq.scala +pos/signatures +pos/t1263 +pos/t3249 +neg/t4749.scala +neg/main1.scala +neg/t7251 +neg/t7494-after-terminal +neg/t7494-before-parser +neg/t7494-right-after-terminal +run/lazy-traits.scala +run/OrderingTest.scala +run/ReplacementMatching.scala +run/patmat-finally.scala +run/t3158.scala +run/t3346e.scala +run/t4398.scala +run/t4930.scala +run/t6534.scala +pos/sammy_scope.scala +pos/delambdafy-patterns.scala +pos/private-types-after-typer.scala +pos/delambdafy-lambdalift.scala +pos/sammy_poly.scala +pos/sammy_single.scala +pos/SI-4012-b.scala +pos/sammy_twice.scala +pos/t3160.scala +pos/t1014.scala +pos/t4970b.scala +pos/t2698.scala +pos/t5845.scala +pos/t6201.scala +pos/t6260a.scala +pos/t7688.scala +pos/t7818.scala +pos/t1203a.scala +pos/t7834.scala +pos/t7853.scala +pos/t7815.scala +pos/t7853-partial-function.scala +pos/t7864.scala +pos/t7928.scala +pos/t7902.scala +pos/t7944.scala +pos/t7847 +neg/accesses2.scala +neg/bad-advice.scala +neg/gadts2.scala +neg/gadts2-strict.scala +neg/macro-bundle-abstract.scala +neg/macro-bundle-object.scala +neg/macro-bundle-trait.scala +neg/macro-blackbox-dynamic-materialization +neg/macro-blackbox-extractor +neg/run-gadts-strict.scala +neg/macro-blackbox-structural +neg/sammy_restrictions.scala +neg/sammy_wrong_arity.scala +neg/t2462c.scala +neg/t3346b.scala +neg/t1909-object.scala +neg/macro-blackbox-fundep-materialization +neg/t3346c.scala +neg/t3871.scala +neg/t3871b.scala +neg/t3971.scala +neg/t3346i.scala +neg/t6120.scala +neg/t6260c.scala +neg/t6680a.scala +neg/t7239.scala +neg/t7007.scala +neg/t7605-deprecation.scala +neg/t7622-missing-required.scala +neg/t7629-view-bounds-deprecation.scala +neg/t7834neg.scala +neg/t7783.scala +neg/t7848-interp-warn.scala +neg/t7519-b +neg/t7622-missing-dependency +neg/t7870.scala +neg/t7877.scala +neg/t7895.scala +neg/t7895b.scala +neg/t7899.scala +neg/t7895c.scala +neg/t7859 +run/t4752.scala +run/t2087-and-2400.scala +run/t3855.scala +run/t6637.scala +run/t6731.scala +pos/t3999b.scala +run/t0432.scala +run/t2514.scala +run/t7817.scala +run/t874.scala +run/type-currying.scala +run/t3616.scala +run/t3687.scala +run/t4570.scala +run/t5612.scala +run/t1110.scala +run/t2636.scala +run/verify-ctor.scala +run/t3647.scala +run/t4560.scala +run/t6632.scala +run/hashCodeBoxesRunTime.scala +run/richs.scala +run/t6725-1.scala +pos/t7776.scala +run/fors.scala +run/t6706.scala +run/t3175.scala +run/delambdafy-dependent-on-param-subst.scala +run/t4332b.scala +run/t8048a +run/t8017 +run/t7985b.scala +run/t8100.scala +run/patmat-mix-case-extractor.scala +run/t4750.scala +run/t7912.scala +run/delambdafy-dependent-on-param-subst-2.scala +run/t8048b +run/t8091.scala +run/macroPlugins-macroRuntime +run/macro-default-params +run/t6355.scala +run/t7777 +run/t8002.scala +run/t8015-ffc.scala +run/macro-subpatterns +run/t7985.scala +run/macroPlugins-macroArgs +run/t7326.scala +run/t5045.scala +run/value-class-partial-func-depmet.scala +run/t6329_vanilla_bug.scala +run/macroPlugins-macroExpand +run/t8010.scala +run/macroPlugins-typedMacroBody +run/t7406.scala +run/t6253c.scala +run/t6253a.scala +run/t6253b.scala +pos/t8146a.scala +pos/t8046c.scala +pos/t8002-nested-scope.scala +pos/t8132.scala +pos/t8045.scala +pos/overzealous-assert-genbcode.scala +pos/t8128.scala +pos/t8013 +pos/t8064b +pos/t6780.scala +pos/t7987 +pos/bcode_throw_null +pos/t8064 +pos/t8046.scala +pos/t6231.scala +pos/t7983.scala +pos/t5508.scala +pos/t5508-min.scala +pos/t8023b.scala +pos/t6231b.scala +pos/debug-reset-local-attrs.scala +pos/t8054.scala +pos/t2066.scala +pos/dotless-targs.scala +pos/t8120.scala +pos/t5508-min-okay.scala +pos/t8060.scala +pos/t8001 +pos/t8138.scala +pos/t8111.scala +pos/t8062 +pos/t8011.scala +pos/t8146b.scala +pos/t8046b.scala +pos/t8023.scala +pos/t5508-min-okay2.scala +pos/macro-implicit-invalidate-on-error.scala +neg/t6563.scala +neg/missing-param-type-tuple.scala +neg/not-a-legal-formal-parameter-tuple.scala +neg/t7897.scala +neg/t8015-ffa.scala +neg/quasiquotes-unliftable-not-found.scala +neg/t2066b.scala +neg/dotless-targs.scala +neg/patmat-classtag-compound.scala +neg/t2066.scala +neg/t8035-deprecated.scala +neg/t6675b.scala +neg/t8104 +neg/t7872.scala +neg/t7850.scala +neg/t7967.scala +neg/macro-bundle-overloaded.scala +neg/t6355a.scala +neg/class-of-double-targs.scala +neg/t6355b.scala +neg/macro-reify-splice-splice +neg/macro-bundle-noncontext.scala +neg/t8015-ffb.scala +neg/t8035-removed.scala +neg/t7984.scala +neg/t8024.scala +neg/t8024b.scala +neg/t8157.scala +neg/t8146-non-finitary-2.scala +neg/t8006.scala +neg/t7872c.scala +neg/t8146-non-finitary.scala +neg/t7872b.scala +neg/t6920.scala +run/t6200.scala +run/t6196.scala +run/macro-bundle-context-refinement +run/macro-enclosingowner-detectvar +run/macro-enclosingowner-sbt +run/macro-bundle-context-alias +run/macro-bundle-whitebox-use-refined +run/macro-bundle-whitebox-use-raw +neg/name-lookup-stable.scala +neg/t0764b.scala +neg/no-implicit-to-anyref-any-val.scala +neg/t1503.scala +neg/t4728.scala +neg/t6455.scala +neg/t6260-named.scala +neg/t6844.scala +neg/t7475c.scala +neg/t7475e.scala +neg/t7475f.scala +neg/macro-bundle-whitebox-use-raw +neg/macro-bundle-whitebox-use-refined +neg/macro-incompatible-macro-engine-b +neg/t7980.scala +neg/macro-incompatible-macro-engine-a +neg/t8143a.scala +neg/t8072.scala +neg/t8207.scala +neg/t8182.scala +neg/t8219-any-any-ref-equals.scala +neg/t8177a.scala +neg/t8228.scala +neg/t8229.scala +neg/t8237-default.scala +neg/t8244b.scala +neg/t8244e +neg/t8244c.scala +neg/t8265.scala +neg/t8266-invalid-interp.scala +neg/t6931 +neg/t8376 +neg/t8372.scala +neg/t8300-overloading.scala +neg/t8244 +neg/t8158 +neg/t8431.scala +pos/implicit-anyval-2.10.scala +pos/delambdafy_t6260_method.scala +pos/macro-bundle-disambiguate-bundle.scala +pos/macro-bundle-disambiguate-nonbundle.scala +pos/package-ob-case +pos/t1786-counter.scala +pos/reflection-compat-api-universe.scala +pos/list-optim-check.scala +pos/existential-java-case-class +pos/t1786-cycle.scala +pos/reflection-compat-c.scala +pos/t3452f.scala +pos/reflection-compat-ru.scala +pos/t2066-2.10-compat.scala +pos/reflection-compat-macro-universe.scala +pos/t5900a.scala +pos/t5760-pkgobj-warn +pos/t5954a +pos/t5954b +pos/t5954d +pos/t6260.scala +pos/t5165b +pos/t5954c +pos/t6260b.scala +pos/t7475b.scala +pos/t7475a.scala +pos/t7753.scala +pos/t7322.scala +pos/t6948.scala +pos/t7475d.scala +pos/t7475e.scala +pos/t6169 +pos/t7788.scala +pos/t7919.scala +pos/t8177a.scala +pos/t8177.scala +pos/t8170.scala +pos/t8170b.scala +pos/t8177d.scala +pos/t8177b.scala +pos/t8177e.scala +pos/t8134 +pos/t8177h.scala +pos/t8177g.scala +pos/t8207.scala +pos/t8187.scala +pos/t8219.scala +pos/t8219b.scala +pos/t8224.scala +pos/t8237.scala +pos/t8223.scala +pos/t8237b.scala +pos/t8300-conversions-a.scala +pos/t8300-conversions-b.scala +pos/t8209a +pos/t8209b +pos/t8244d +pos/t8300-overloading.scala +pos/t8300-patmat-a.scala +pos/t8300-patmat-b.scala +pos/t8315b.scala +pos/t8306.scala +pos/t8301.scala +pos/t8324.scala +pos/t8315.scala +pos/t8301b.scala +pos/t8363.scala +pos/t8367.scala +pos/t8369a.scala +pos/t8369b.scala +pos/t8403.scala +pos/t8364.scala +pos/t8352 +pos/t8376 +neg/macro-bundle-nonpublic-c.scala +neg/literate_existentials.scala +neg/macro-bundle-nonpublic-impl.scala +neg/macro-bundle-ambiguous.scala +neg/macro-bundle-priority-bundle.scala +neg/macro-bundle-need-qualifier.scala +neg/macro-bundle-nonstatic.scala +neg/macro-bundle-polymorphic.scala +neg/macro-bundle-priority-nonbundle.scala +neg/macro-bundle-wrongcontext-a.scala +neg/macro-bundle-wrongcontext-b.scala +run/t8425 +run/t8245.scala +run/t8266-octal-interp.scala +run/t8280.scala +run/t8395.scala +run/t8321 +run/t8153.scala +run/t8233-bcode.scala +run/t8197.scala +run/t8197b.scala +run/t8233.scala +run/t8133 +run/t8133b +run/t7475b.scala +run/t7445.scala +run/t6814 +run/t4577.scala +run/t5134.scala +run/t3452f.scala +run/t3452h.scala +run/t3452c.scala +run/t3452.scala +run/t261.scala +run/t3235-minimal.scala +run/t1503_future.scala +run/t5565.scala +pos/t8411 +pos/t8460.scala +run/t8428.scala +run/t8437 +run/absoverride.scala +run/arrays.scala +run/duration-coarsest.scala +run/iterator-from.scala +run/SymbolsTest.scala +run/t1074.scala +run/t1505.scala +run/streams.scala +run/t2111.scala +run/t4601.scala +pos/SI-4012-a.scala +pos/SI-7638.scala +neg/t3692-new.scala +run/t7015.scala +run/t7992b.scala +run/t7992.scala +run/t8570.scala +pos/t8157-2.10.scala +pos/t8325.scala +pos/t8523.scala +pos/t8578.scala +pos/t8329.scala +pos/t8497 +pos/t8546.scala +pos/t8531 +neg/t8325-c.scala +neg/t8325-b.scala +neg/t8325.scala +neg/t6988.scala +neg/t8463.scala +neg/t8450.scala +neg/t8430.scala +run/finally.scala +neg/t8630.scala +neg/t8035-no-adapted-args.scala +neg/t8675b.scala +neg/t8610-arg.scala +neg/t8736-c.scala +neg/tailrec-4.scala +neg/double-def-top-level +neg/t8610.scala +neg/aladdin1055 +neg/virtpatmat_exhaust_compound.scala +neg/t8675.scala +neg/t8525.scala +pos/t8736.scala +pos/t8625.scala +pos/t8596.scala +pos/t8617.scala +pos/t8736-b.scala +pos/t8708 +pos/macro-attachments +run/t8611a.scala +run/t8738.scala +run/macro-rangepos-args +run/t8610.scala +run/macro-rangepos-subpatterns +run/t8611c.scala +run/macroPlugins-isBlackbox +run/t8601d.scala +run/t8607.scala +run/bugs.scala +run/t1503.scala +run/t4148.scala +run/t7763.scala +run/issue192.scala + +# Adapt checkfiles for (1.0).toString == "1" +run/Course-2002-01.scala +run/t0421-new.scala +run/runtime.scala +run/t0421-old.scala +run/spec-self.scala +run/t5552.scala +run/Course-2002-02.scala +run/Course-2002-04.scala +run/promotion.scala +run/t4617.scala +run/Course-2002-09.scala +run/t5866.scala +run/try-catch-unify.scala +run/impconvtimes.scala +run/Course-2002-10.scala +run/Course-2002-08.scala + +# Adapt checkfiles for ().toString == "undefined" +run/t5680.scala +run/dynamic-anyval.scala +run/macro-bundle-toplevel +run/macro-bundle-whitebox-decl +run/t6662 +run/t8570a.scala +run/t3702.scala +run/t7657 +run/macro-bundle-static +run/structural.scala + +# Adapt checkfiles for print & flush (which we cannot 100% emulate) +run/imports.scala +run/misc.scala + +# Adapt checkfiles for compiler phase list +run/t6102.scala +neg/t7494-no-options + +# Adapt checkfiles for different behavior with boxed types +run/t5568.scala +run/virtpatmat_typetag.scala +run/virtpatmat_switch.scala +run/t5629b.scala +run/t6318_primitives.scala diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/neg/t7494-no-options.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/neg/t7494-no-options.check new file mode 100644 index 0000000..581da38 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/neg/t7494-no-options.check @@ -0,0 +1,42 @@ +error: Error: ploogin takes no options + phase name id description + ---------- -- ----------- + parser 1 parse source into ASTs, perform simple desugaring + namer 2 resolve names, attach symbols to named trees +packageobjects 3 load package objects + typer 4 the meat and potatoes: type the trees + jsinterop 5 + patmat 6 translate match expressions +superaccessors 7 add super accessors in traits and nested classes + extmethods 8 add extension methods for inline classes + pickler 9 serialize symbol tables + refchecks 10 reference/override checking, translate nested objects + uncurry 11 uncurry, translate function values to anonymous classes + tailcalls 12 replace tail calls by jumps + specialize 13 @specialized-driven class and method specialization + explicitouter 14 this refs to outer pointers + erasure 15 erase types, add interfaces for traits + posterasure 16 clean up erased inline classes + lazyvals 17 allocate bitmaps, translate lazy vals into lazified defs + lambdalift 18 move nested functions to top level + constructors 19 move field definitions into constructors + flatten 20 eliminate inner classes + mixin 21 mixin composition + jscode 22 + cleanup 23 platform-specific cleanups, generate reflective calls + delambdafy 24 remove lambdas + icode 25 generate portable intermediate code +#partest !-optimise + jvm 26 generate JVM bytecode + ploogin 27 A sample phase that does so many things it's kind of hard... + terminal 28 the last phase during a compilation run +#partest -optimise + inliner 26 optimization: do inlining +inlinehandlers 27 optimization: inline exception handlers + closelim 28 optimization: eliminate uncalled closures + constopt 29 optimization: optimize null and other constants + dce 30 optimization: eliminate dead code + jvm 31 generate JVM bytecode + ploogin 32 A sample phase that does so many things it's kind of hard... + terminal 33 the last phase during a compilation run +#partest diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/Course-2002-01.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/Course-2002-01.check new file mode 100644 index 0000000..fcda943 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/Course-2002-01.check @@ -0,0 +1,37 @@ +Course-2002-01.scala:41: warning: method loop in object M0 does nothing other than call itself recursively + def loop: Int = loop; + ^ +232 +667 +11 +10 +62.8318 +62.8318 +62.8318 +4 +81 +256 +25 +1 +737 +1 +0 +1 +76 +1.4142156862745097 +1.7321428571428572 +2.0000000929222947 +1.4142156862745097 +1.7321428571428572 +2.0000000929222947 +1.4142156862745097 +1.7321428571428572 +2.0000000929222947 +sqrt(2) = 1.4142135623746899 +sqrt(2) = 1.4142135623746899 +cbrt(2) = 1.2599210500177698 +1 +1 1 +1 2 1 +1 3 3 1 +1 4 6 4 1 diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/Course-2002-02.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/Course-2002-02.check new file mode 100644 index 0000000..ab75cfd --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/Course-2002-02.check @@ -0,0 +1,187 @@ +7 +120 + +10 +100 +2.083333333333333 +3025.7687714031754 +pi = 3.1659792728432152 + +10 +100 +2.083333333333333 +3025.7687714031754 +pi = 3.1659792728432152 + +10 +100 +2.083333333333333 +3025.7687714031754 +pi = 3.1659792728432152 + +10 +100 +2.083333333333333 +3025.7687714031754 +pi = 3.1659792728432152 + +10 +100 +2.083333333333333 +3025.7687714031754 +pi = 3.1659792728432152 + +10 +100 +2.083333333333333 +3025.7687714031754 +pi = 3.1659792728432152 + +10 +100 +2.083333333333333 +3025.7687714031754 +pi = 3.1659792728432152 + +pi = 3.181104885577714 +pi = 3.181104885577714 + +10 +100 +2.083333333333333 +3025.7687714031754 +pi = 3.1659792728432152 +pi = 3.181104885577714 +pi = 3.181104885577714 + +1.5 +1.4166666666666665 +1.4142156862745097 +1.4142135623746899 +sqrt(2) = 1.4142135623746899 + +1.5 +1.4166666666666665 +1.4142156862745097 +1.4142135623746899 +sqrt(2) = 1.4142135623746899 + +1 + 2 + .. + 5 = 15 +1 * 2 * .. * 5 = 120 + +1^2 + 2^2 + .. + 5^2 = 55 +1^2 * 2^2 * .. * 5^2 = 14400 + +factorial(0) = 1 +factorial(1) = 1 +factorial(2) = 2 +factorial(3) = 6 +factorial(4) = 24 +factorial(5) = 120 + +1 + 2 + .. + 5 = 15 +1 * 2 * .. * 5 = 120 + +1^2 + 2^2 + .. + 5^2 = 55 +1^2 * 2^2 * .. * 5^2 = 14400 + +factorial(0) = 1 +factorial(1) = 1 +factorial(2) = 2 +factorial(3) = 6 +factorial(4) = 24 +factorial(5) = 120 + +1 + 2 + .. + 5 = 15 +1 * 2 * .. * 5 = 120 + +1^2 + 2^2 + .. + 5^2 = 55 +1^2 * 2^2 * .. * 5^2 = 14400 + +factorial(0) = 1 +factorial(1) = 1 +factorial(2) = 2 +factorial(3) = 6 +factorial(4) = 24 +factorial(5) = 120 + +fib(0) = 0 +fib(1) = 1 +fib(2) = 1 +fib(3) = 2 +fib(4) = 3 +fib(5) = 5 +fib(6) = 8 +fib(7) = 13 +fib(8) = 21 +fib(9) = 34 +fib(0) = 0 +fib(1) = 1 +fib(2) = 1 +fib(3) = 2 +fib(4) = 3 +fib(5) = 5 +fib(6) = 8 +fib(7) = 13 +fib(8) = 21 +fib(9) = 34 +power(0,0) = 1 +power(0,1) = 0 +power(0,2) = 0 +power(0,3) = 0 +power(0,4) = 0 +power(0,5) = 0 +power(0,6) = 0 +power(0,7) = 0 +power(0,8) = 0 + +power(1,0) = 1 +power(1,1) = 1 +power(1,2) = 1 +power(1,3) = 1 +power(1,4) = 1 +power(1,5) = 1 +power(1,6) = 1 +power(1,7) = 1 +power(1,8) = 1 + +power(2,0) = 1 +power(2,1) = 2 +power(2,2) = 4 +power(2,3) = 8 +power(2,4) = 16 +power(2,5) = 32 +power(2,6) = 64 +power(2,7) = 128 +power(2,8) = 256 + +power(3,0) = 1 +power(3,1) = 3 +power(3,2) = 9 +power(3,3) = 27 +power(3,4) = 81 +power(3,5) = 243 +power(3,6) = 729 +power(3,7) = 2187 +power(3,8) = 6561 + +power(4,0) = 1 +power(4,1) = 4 +power(4,2) = 16 +power(4,3) = 64 +power(4,4) = 256 +power(4,5) = 1024 +power(4,6) = 4096 +power(4,7) = 16384 +power(4,8) = 65536 + +power(5,0) = 1 +power(5,1) = 5 +power(5,2) = 25 +power(5,3) = 125 +power(5,4) = 625 +power(5,5) = 3125 +power(5,6) = 15625 +power(5,7) = 78125 +power(5,8) = 390625 + diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/Course-2002-04.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/Course-2002-04.check new file mode 100644 index 0000000..fc6ad96 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/Course-2002-04.check @@ -0,0 +1,64 @@ +list0 = List(6, 3, 1, 8, 7, 1, 2, 5, 8, 4, 3, 4, 8) +list1 = List(1, 1, 2, 3, 3, 4, 4, 5, 6, 7, 8, 8, 8) +list2 = List(1, 1, 2, 3, 3, 4, 4, 5, 6, 7, 8, 8, 8) +list3 = List(1, 1, 2, 3, 3, 4, 4, 5, 6, 7, 8, 8, 8) +list4 = List(1, 1, 2, 3, 3, 4, 4, 5, 6, 7, 8, 8, 8) +list5 = List(8, 8, 8, 7, 6, 5, 4, 4, 3, 3, 2, 1, 1) +list6 = List(8, 8, 8, 7, 6, 5, 4, 4, 3, 3, 2, 1, 1) + +list0: List() -> List() +list1: List(0) -> List(0) +list2: List(0, 1) -> List(0, 1) +list3: List(1, 0) -> List(0, 1) +list4: List(0, 1, 2) -> List(0, 1, 2) +list5: List(1, 0, 2) -> List(0, 1, 2) +list6: List(0, 1, 2) -> List(0, 1, 2) +list7: List(1, 0, 2) -> List(0, 1, 2) +list8: List(2, 0, 1) -> List(0, 1, 2) +list9: List(2, 1, 0) -> List(0, 1, 2) +listA: List(6, 3, 1, 8, 7, 1, 2, 5, 8, 4) -> List(1, 1, 2, 3, 4, 5, 6, 7, 8, 8) + +f(x) = 5x^3+7x^2+5x+9 +f(0) = 9 +f(1) = 26 +f(2) = 87 +f(3) = 222 + +v1 = List(2, 3, 4) +v2 = List(6, 7, 8) + +id = List(List(1, 0, 0), List(0, 1, 0), List(0, 0, 1)) +m1 = List(List(2, 0, 0), List(0, 2, 0), List(0, 0, 2)) +m2 = List(List(1, 2, 3), List(4, 5, 6), List(7, 8, 9)) + +v1 * v1 = 29 +v1 * v2 = 65 +v2 * v1 = 65 +v1 * v2 = 65 + +id * v1 = List(2, 3, 4) +m1 * v1 = List(4, 6, 8) +m2 * v1 = List(20, 47, 74) + +trn(id) = List(List(1, 0, 0), List(0, 1, 0), List(0, 0, 1)) +trn(m1) = List(List(2, 0, 0), List(0, 2, 0), List(0, 0, 2)) +trn(m2) = List(List(1, 4, 7), List(2, 5, 8), List(3, 6, 9)) + +List(v1) * id = List(List(2, 3, 4)) +List(v1) * m1 = List(List(4, 6, 8)) +List(v1) * m2 = List(List(42, 51, 60)) + +id * List(v1) = List(List(2, 3, 4), List(0, 0, 0), List(0, 0, 0)) +m1 * List(v1) = List(List(4, 6, 8), List(0, 0, 0), List(0, 0, 0)) +m2 * List(v1) = List(List(2, 3, 4), List(8, 12, 16), List(14, 21, 28)) + +id * id = List(List(1, 0, 0), List(0, 1, 0), List(0, 0, 1)) +id * m1 = List(List(2, 0, 0), List(0, 2, 0), List(0, 0, 2)) +m1 * id = List(List(2, 0, 0), List(0, 2, 0), List(0, 0, 2)) +m1 * m1 = List(List(4, 0, 0), List(0, 4, 0), List(0, 0, 4)) +id * m2 = List(List(1, 2, 3), List(4, 5, 6), List(7, 8, 9)) +m2 * id = List(List(1, 2, 3), List(4, 5, 6), List(7, 8, 9)) +m1 * m2 = List(List(2, 4, 6), List(8, 10, 12), List(14, 16, 18)) +m2 * m1 = List(List(2, 4, 6), List(8, 10, 12), List(14, 16, 18)) +m2 * m2 = List(List(30, 36, 42), List(66, 81, 96), List(102, 126, 150)) + diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/Course-2002-08.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/Course-2002-08.check new file mode 100644 index 0000000..0585d5b --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/Course-2002-08.check @@ -0,0 +1,171 @@ +x = abc +count = 111 +x = hello +count = 112 + +account deposit 50 -> undefined +account withdraw 20 -> 30 +account withdraw 20 -> 10 +account withdraw 15 -> + +x deposit 30 -> undefined +y withdraw 20 -> + +x deposit 30 -> undefined +x withdraw 20 -> 10 + +x deposit 30 -> undefined +y withdraw 20 -> 10 + +2^0 = 1 +2^1 = 2 +2^2 = 4 +2^3 = 8 + +2^0 = 1 +2^1 = 2 +2^2 = 4 +2^3 = 8 + +1 2 3 +List(1, 2, 3) + +out 0 new-value = false +*** simulation started *** +out 1 new-value = true +!0 = 1 + +*** simulation started *** +out 2 new-value = false +!1 = 0 + +out 2 new-value = false + +*** simulation started *** +0 & 0 = 0 + +*** simulation started *** +0 & 1 = 0 + +*** simulation started *** +out 11 new-value = true +out 11 new-value = false +1 & 0 = 0 + +*** simulation started *** +out 14 new-value = true +1 & 1 = 1 + +out 14 new-value = false + +*** simulation started *** +0 | 0 = 0 + +*** simulation started *** +out 24 new-value = true +0 | 1 = 1 + +*** simulation started *** +1 | 0 = 1 + +*** simulation started *** +1 | 1 = 1 + +sum 34 new-value = false +carry 34 new-value = false + +*** simulation started *** +0 + 0 = 0 + +*** simulation started *** +sum 47 new-value = true +0 + 1 = 1 + +*** simulation started *** +carry 50 new-value = true +carry 50 new-value = false +sum 54 new-value = false +sum 54 new-value = true +1 + 0 = 1 + +*** simulation started *** +carry 57 new-value = true +sum 61 new-value = false +1 + 1 = 2 + +sum 61 new-value = false +carry 61 new-value = false + +*** simulation started *** +0 + 0 + 0 = 0 + +*** simulation started *** +sum 82 new-value = true +0 + 0 + 1 = 1 + +*** simulation started *** +sum 89 new-value = false +carry 90 new-value = true +sum 97 new-value = true +carry 98 new-value = false +0 + 1 + 0 = 1 + +*** simulation started *** +sum 113 new-value = false +carry 114 new-value = true +0 + 1 + 1 = 2 + +*** simulation started *** +sum 121 new-value = true +carry 122 new-value = false +sum 129 new-value = false +sum 129 new-value = true +1 + 0 + 0 = 1 + +*** simulation started *** +carry 137 new-value = true +sum 144 new-value = false +1 + 0 + 1 = 2 + +*** simulation started *** +carry 152 new-value = false +sum 152 new-value = true +sum 158 new-value = false +carry 159 new-value = true +1 + 1 + 0 = 2 + +*** simulation started *** +sum 173 new-value = true +1 + 1 + 1 = 3 + +in 0 new-value = false +ctrl0 0 new-value = false +ctrl1 0 new-value = false +ctrl2 0 new-value = false +out0 0 new-value = false +out1 0 new-value = false +out2 0 new-value = false +out3 0 new-value = false +out4 0 new-value = false +out5 0 new-value = false +out6 0 new-value = false +out7 0 new-value = false +in 0 new-value = true +*** simulation started *** +out0 10 new-value = true +ctrl0 10 new-value = true +*** simulation started *** +out1 13 new-value = true +out0 14 new-value = false +ctrl1 14 new-value = true +*** simulation started *** +out3 20 new-value = true +out1 21 new-value = false +ctrl2 21 new-value = true +*** simulation started *** +out7 30 new-value = true +out3 31 new-value = false +ctrl0 31 new-value = false +*** simulation started *** +out7 34 new-value = false +out6 35 new-value = true diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/Course-2002-09.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/Course-2002-09.check new file mode 100644 index 0000000..c921361 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/Course-2002-09.check @@ -0,0 +1,50 @@ +Probe: f = 32 +Probe: c = 0 +Probe: f = ? +Probe: c = ? + +Probe: f = 212 +Probe: c = 100 +Probe: f = ? +Probe: c = ? + +Probe: c = 0 +Probe: f = 32 +Probe: c = ? +Probe: f = ? + +Probe: c = 100 +Probe: f = 212 +Probe: c = ? +Probe: f = ? + +0 Celsius -> 32 Fahrenheits +100 Celsius -> 212 Fahrenheits +32 Fahrenheits -> 0 Celsius +212 Fahrenheits -> 100 Celsius + +a = ?, b = ?, c = ? => ? * ? = ? +a = 2, b = ?, c = ? => 2 * ? = ? +a = ?, b = 3, c = ? => ? * 3 = ? +a = ?, b = ?, c = 6 => ? * ? = 6 +a = 2, b = 3, c = ? => 2 * 3 = 6 +a = 2, b = ?, c = 6 => 2 * 3 = 6 +a = ?, b = 3, c = 6 => 2 * 3 = 6 +a = 2, b = 3, c = 6 => 2 * 3 = 6 + +a = 0, b = ?, c = ? => 0 * ? = 0 +a = ?, b = 0, c = ? => ? * 0 = 0 +a = ?, b = ?, c = 0 => ? * ? = 0 +a = 0, b = 7, c = ? => 0 * 7 = 0 +a = 7, b = 0, c = ? => 7 * 0 = 0 +a = 0, b = 0, c = ? => 0 * 0 = 0 +a = 0, b = ?, c = 0 => 0 * ? = 0 +a = ?, b = 0, c = 0 => ? * 0 = 0 +a = 0, b = 7, c = 0 => 0 * 7 = 0 +a = 7, b = 0, c = 0 => 7 * 0 = 0 +a = 0, b = 0, c = 0 => 0 * 0 = 0 + +a = 3, b = 4 => c = 5 +a = 3, c = 5 => b = 4 +b = 4, c = 5 => a = 3 + diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/Course-2002-10.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/Course-2002-10.check new file mode 100644 index 0000000..847f0fa --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/Course-2002-10.check @@ -0,0 +1,46 @@ +fib(0) = 0 +fib(1) = 1 +fib(2) = 1 +fib(3) = 2 +fib(4) = 3 +fib(5) = 5 +fib(6) = 8 +fib(7) = 13 +fib(8) = 21 +fib(9) = 34 +fib(10) = 55 +fib(11) = 89 +fib(12) = 144 +fib(13) = 233 +fib(14) = 377 +fib(15) = 610 +fib(16) = 987 +fib(17) = 1597 +fib(18) = 2584 +fib(19) = 4181 + +pi(0) = 4 , 3.166666666666667 , 4 +pi(1) = 2.666666666666667 , 3.1333333333333337, 3.166666666666667 +pi(2) = 3.466666666666667 , 3.1452380952380956, 3.142105263157895 +pi(3) = 2.8952380952380956, 3.1396825396825396, 3.1415993573190044 +pi(4) = 3.33968253968254 , 3.142712842712843 , 3.141592714033778 +pi(5) = 2.976046176046176 , 3.140881340881341 , 3.1415926539752923 +pi(6) = 3.283738483738484 , 3.142071817071817 , 3.141592653591176 +pi(7) = 3.017071817071817 , 3.1412548236077646, 3.141592653589777 +pi(8) = 3.252365934718876 , 3.1418396189294024, 3.141592653589794 +pi(9) = 3.0418396189294024, 3.141406718496502 , 3.1415926535897936 +pi = 3.141592653589793 , 3.141592653589793 , 3.141592653589793 + +ln(0) = 1 , 0.7 , 1 +ln(1) = 0.5 , 0.6904761904761905, 0.7 +ln(2) = 0.8333333333333333, 0.6944444444444444, 0.6932773109243697 +ln(3) = 0.5833333333333333, 0.6924242424242424, 0.6931488693329254 +ln(4) = 0.7833333333333333, 0.6935897435897436, 0.6931471960735491 +ln(5) = 0.6166666666666667, 0.6928571428571428, 0.6931471806635636 +ln(6) = 0.7595238095238095, 0.6933473389355742, 0.6931471805604038 +ln(7) = 0.6345238095238095, 0.6930033416875522, 0.6931471805599444 +ln(8) = 0.7456349206349207, 0.6932539682539682, 0.6931471805599426 +ln(9) = 0.6456349206349206, 0.6930657506744463, 0.6931471805599453 +ln = 0.6931471805599453, 0.6931471805599453, 0.6931471805599453 + +prime numbers: 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 101 103 107 109 113 diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/bugs.sem b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/bugs.sem new file mode 100644 index 0000000..d36898b --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/bugs.sem @@ -0,0 +1 @@ +asInstanceOfs diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/dynamic-anyval.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/dynamic-anyval.check new file mode 100644 index 0000000..c125372 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/dynamic-anyval.check @@ -0,0 +1,4 @@ +undefined.dingo(bippy, 5) +List(1, 2, 3).dingo(bippy, 5) +undefined.dingo(bippy, 5) +List(1, 2, 3).dingo(bippy, 5) diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/impconvtimes.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/impconvtimes.check new file mode 100644 index 0000000..082377e --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/impconvtimes.check @@ -0,0 +1 @@ +3.0 * Hour = Measure(3,Hour) diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/imports.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/imports.check new file mode 100644 index 0000000..1aad598 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/imports.check @@ -0,0 +1,21 @@ +In C_ico, v_ico .toString() returns ↩ +↪C_ico -> ok +In C_ico, field .toString() returns ↩ +↪C_ico -> ok +In C_ico, method.toString() returns ↩ +↪C_ico -> ok + +In C_ioc, v_ioc .toString() returns ↩ +↪C_ioc -> ok +In C_ioc, field .toString() returns ↩ +↪C_ioc -> ok +In C_ioc, method.toString() returns ↩ +↪C_ioc -> ok + +In C_oic, v_oic .toString() returns ↩ +↪C_oic -> ok +In C_oic, field .toString() returns ↩ +↪C_oic -> ok +In C_oic, method.toString() returns ↩ +↪C_oic -> ok + diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/issue192.sem b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/issue192.sem new file mode 100644 index 0000000..10abbf7 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/issue192.sem @@ -0,0 +1 @@ +strictFloats diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/macro-bundle-static.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/macro-bundle-static.check new file mode 100644 index 0000000..e2e7628 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/macro-bundle-static.check @@ -0,0 +1,6 @@ +undefined +Int +undefined +true +IntInt +true diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/macro-bundle-toplevel.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/macro-bundle-toplevel.check new file mode 100644 index 0000000..e2e7628 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/macro-bundle-toplevel.check @@ -0,0 +1,6 @@ +undefined +Int +undefined +true +IntInt +true diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/macro-bundle-whitebox-decl.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/macro-bundle-whitebox-decl.check new file mode 100644 index 0000000..e2e7628 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/macro-bundle-whitebox-decl.check @@ -0,0 +1,6 @@ +undefined +Int +undefined +true +IntInt +true diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/misc.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/misc.check new file mode 100644 index 0000000..6043817 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/misc.check @@ -0,0 +1,62 @@ +misc.scala:46: warning: a pure expression does nothing in statement position; you may be omitting necessary parentheses + 42; + ^ +misc.scala:47: warning: a pure expression does nothing in statement position; you may be omitting necessary parentheses + 42l; + ^ +misc.scala:48: warning: a pure expression does nothing in statement position; you may be omitting necessary parentheses + 23.5f; + ^ +misc.scala:49: warning: a pure expression does nothing in statement position; you may be omitting necessary parentheses + 23.5; + ^ +misc.scala:50: warning: a pure expression does nothing in statement position; you may be omitting necessary parentheses + "Hello"; + ^ +misc.scala:51: warning: a pure expression does nothing in statement position; you may be omitting necessary parentheses + 32 + 45; + ^ +misc.scala:62: warning: a pure expression does nothing in statement position; you may be omitting necessary parentheses + x; + ^ +misc.scala:74: warning: a pure expression does nothing in statement position; you may be omitting necessary parentheses + 1 < 2; + ^ +### Hello +### 17 +### Bye + +### fib(0) = ↩ +↪1 +### fib(1) = ↩ +↪1 +### fib(2) = ↩ +↪2 +### fib(3) = ↩ +↪3 +### fib(4) = ↩ +↪5 +=== MyClass::toString === +=== MySubclass::toString === +=== MyClass::test === + +identity + +A.a = 1 +B.a = 5 +B.b = 2 + +X.a = 4 +Y.a = 11 +Y.b = 5 +Y.b = 5 + +X::foo + +Y::foo +X::foo + +3 +3 + +true diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/promotion.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/promotion.check new file mode 100644 index 0000000..41e36c3 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/promotion.check @@ -0,0 +1,4 @@ +2 +6 +20 +30 diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/runtime.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/runtime.check new file mode 100644 index 0000000..0450b94 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/runtime.check @@ -0,0 +1,70 @@ +runtime.scala:141: warning: comparing values of types Null and Null using `eq' will always yield true + check(true , null eq null, null ne null); + ^ +runtime.scala:141: warning: comparing values of types Null and Null using `ne' will always yield false + check(true , null eq null, null ne null); + ^ +<<< Test0 +[false,true] +[0,1,2] +[3,4,5] +[a,b,c] +[6,7,8] +[9,10,11] +[12,13] +[14,15] +[string] +>>> Test0 + +<<< Test1 +10 +14 +15 +16 +20 +23 +24 +25 +26 +>>> Test1 + +<<< Test2 +A +M0 +N0 + +A +N0 +M0 + +A +M0 +M1 +N0 + +A +N0 +N1 +M0 + +>>> Test2 + +<<< Test3 +Ok +Ok +Ok +Ok +Ok +Ok +Ok +Ok +Ok +Ok +Ok +Ok +Ok +Ok +Ok +Ok +>>> Test3 + diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/spec-self.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/spec-self.check new file mode 100644 index 0000000..fd3c81a --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/spec-self.check @@ -0,0 +1,2 @@ +5 +5 diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/structural.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/structural.check new file mode 100644 index 0000000..2fec112 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/structural.check @@ -0,0 +1,37 @@ + 1. hey + 2. 11 + 3. dee + 4. iei + 5. 6 + 6. 51 + 7. 2 + 8. 11 +10. 12 +11. eitch +12. 1 +13. ohone +14. 1 +15. undefined +16. one +17. tieone +18. 2 +19. true +20. 1 +21. undefined +22. one +23. oy +24. 1 +25. null +26. iei +31. 4 +32. undefined +33. iei +33. tieone +1 +2 +3 +4 +5 +caught +3 +2 diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/t0421-new.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/t0421-new.check new file mode 100644 index 0000000..00d29b7 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/t0421-new.check @@ -0,0 +1,3 @@ +[Array(0, 1),Array(2, 3),Array(4, 5)] +[Array(31)] +[Array(24, 32)] diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/t0421-old.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/t0421-old.check new file mode 100644 index 0000000..00d29b7 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/t0421-old.check @@ -0,0 +1,3 @@ +[Array(0, 1),Array(2, 3),Array(4, 5)] +[Array(31)] +[Array(24, 32)] diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/t1503.sem b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/t1503.sem new file mode 100644 index 0000000..d36898b --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/t1503.sem @@ -0,0 +1 @@ +asInstanceOfs diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/t3702.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/t3702.check new file mode 100644 index 0000000..3fce987 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/t3702.check @@ -0,0 +1,2 @@ +undefined +6 diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/t4148.sem b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/t4148.sem new file mode 100644 index 0000000..d36898b --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/t4148.sem @@ -0,0 +1 @@ +asInstanceOfs diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/t4617.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/t4617.check new file mode 100644 index 0000000..a6790f1 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/t4617.check @@ -0,0 +1 @@ +Str 8 diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/t5552.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/t5552.check new file mode 100644 index 0000000..4704611 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/t5552.check @@ -0,0 +1,2 @@ +(3,3) +(3,3) diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/t5568.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/t5568.check new file mode 100644 index 0000000..6f30cc5 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/t5568.check @@ -0,0 +1,9 @@ +void +int +class scala.runtime.BoxedUnit +class scala.runtime.BoxedUnit +class java.lang.Byte +class java.lang.Byte +5 +5 +5 diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/t5629b.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/t5629b.check new file mode 100644 index 0000000..c65298a --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/t5629b.check @@ -0,0 +1,10 @@ +=== pf(1): +MySmartPF.apply entered... +newPF.applyOrElse entered... +default +scala.MatchError: 1 (of class java.lang.Byte) +=== pf(42): +MySmartPF.apply entered... +newPF.applyOrElse entered... +ok +=== done diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/t5680.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/t5680.check new file mode 100644 index 0000000..a3b8b64 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/t5680.check @@ -0,0 +1,3 @@ +[Lscala.runtime.BoxedUnit +undefined +undefined diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/t5866.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/t5866.check new file mode 100644 index 0000000..64df1ce --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/t5866.check @@ -0,0 +1,2 @@ +0 +Foo(0) diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/t6102.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/t6102.check new file mode 100644 index 0000000..120082e --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/t6102.check @@ -0,0 +1,27 @@ +[running phase parser on t6102.scala] +[running phase namer on t6102.scala] +[running phase packageobjects on t6102.scala] +[running phase typer on t6102.scala] +[running phase jsinterop on t6102.scala] +[running phase patmat on t6102.scala] +[running phase superaccessors on t6102.scala] +[running phase extmethods on t6102.scala] +[running phase pickler on t6102.scala] +[running phase refchecks on t6102.scala] +[running phase uncurry on t6102.scala] +[running phase tailcalls on t6102.scala] +[running phase specialize on t6102.scala] +[running phase explicitouter on t6102.scala] +[running phase erasure on t6102.scala] +[running phase posterasure on t6102.scala] +[running phase lazyvals on t6102.scala] +[running phase lambdalift on t6102.scala] +[running phase constructors on t6102.scala] +[running phase flatten on t6102.scala] +[running phase mixin on t6102.scala] +[running phase jscode on t6102.scala] +[running phase cleanup on t6102.scala] +[running phase delambdafy on t6102.scala] +[running phase icode on t6102.scala] +[running phase dce on t6102.scala] +[running phase jvm on icode] diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/t6318_primitives.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/t6318_primitives.check new file mode 100644 index 0000000..654ef1b --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/t6318_primitives.check @@ -0,0 +1,54 @@ +Checking if byte matches byte +Some(1) +Checking if byte matches short +Some(1) +Checking if class java.lang.Byte matches byte +Some(1) +Checking if short matches short +Some(1) +Checking if short matches char +None +Checking if class java.lang.Byte matches short +Some(1) +Checking if char matches char +Some() +Checking if char matches int +None +Checking if class java.lang.Character matches char +Some() +Checking if int matches int +Some(1) +Checking if int matches long +None +Checking if class java.lang.Byte matches int +Some(1) +Checking if long matches long +Some(1) +Checking if long matches float +None +Checking if class java.lang.Long matches long +Some(1) +Checking if float matches float +Some(1) +Checking if float matches double +Some(1) +Checking if class java.lang.Byte matches float +Some(1) +Checking if double matches double +Some(1) +Checking if double matches boolean +None +Checking if class java.lang.Byte matches double +Some(1) +Checking if boolean matches boolean +Some(true) +Checking if boolean matches void +None +Checking if class java.lang.Boolean matches boolean +Some(true) +Checking if void matches void +Some(undefined) +Checking if void matches byte +None +Checking if class scala.runtime.BoxedUnit matches void +Some(undefined) diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/t6662.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/t6662.check new file mode 100644 index 0000000..417b7b5 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/t6662.check @@ -0,0 +1 @@ +undefined diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/t7657.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/t7657.check new file mode 100644 index 0000000..1a87c1e --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/t7657.check @@ -0,0 +1,3 @@ +undefined +undefined +undefined diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/t7763.sem b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/t7763.sem new file mode 100644 index 0000000..d36898b --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/t7763.sem @@ -0,0 +1 @@ +asInstanceOfs diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/t8570a.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/t8570a.check new file mode 100644 index 0000000..417b7b5 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/t8570a.check @@ -0,0 +1 @@ +undefined diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/try-catch-unify.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/try-catch-unify.check new file mode 100644 index 0000000..813f011 --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/try-catch-unify.check @@ -0,0 +1,4 @@ +Failure(java.lang.NumberFormatException: For input string: "Hi") +Success(5) +O NOES +Failure(java.lang.NumberFormatException: For input string: "Hi") diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/virtpatmat_switch.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/virtpatmat_switch.check new file mode 100644 index 0000000..0900a9c --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/virtpatmat_switch.check @@ -0,0 +1,7 @@ +zero +one +many +got a +got b +got some letter +scala.MatchError: 5 (of class java.lang.Byte)
\ No newline at end of file diff --git a/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/virtpatmat_typetag.check b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/virtpatmat_typetag.check new file mode 100644 index 0000000..048c3ae --- /dev/null +++ b/examples/scala-js/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.2/run/virtpatmat_typetag.check @@ -0,0 +1,10 @@ +1 is a Int +1 is a java.lang.Integer +1 is not a java.lang.String; it's a class java.lang.Byte +true is a Any +woele is a java.lang.String +1 is a Int +1 is a java.lang.Integer +1 is not a java.lang.String; it's a class java.lang.Byte +true is a Any +woele is a java.lang.String diff --git a/examples/scala-js/partest/src/main/scala/scala/tools/nsc/MainGenericRunner.scala b/examples/scala-js/partest/src/main/scala/scala/tools/nsc/MainGenericRunner.scala new file mode 100644 index 0000000..6857142 --- /dev/null +++ b/examples/scala-js/partest/src/main/scala/scala/tools/nsc/MainGenericRunner.scala @@ -0,0 +1,220 @@ +package scala.tools.nsc + +/* Super hacky overriding of the MainGenericRunner used by partest */ + +import scala.scalajs.ir + +import scala.scalajs.tools.sem.Semantics +import scala.scalajs.tools.classpath._ +import scala.scalajs.tools.classpath.builder._ +import scala.scalajs.tools.logging._ +import scala.scalajs.tools.io._ +import scala.scalajs.tools.optimizer.ScalaJSOptimizer +import scala.scalajs.tools.optimizer.ScalaJSClosureOptimizer +import scala.scalajs.tools.optimizer.ParIncOptimizer +import scala.scalajs.tools.env.JSConsole + +import scala.scalajs.sbtplugin.env.rhino.RhinoJSEnv +import scala.scalajs.sbtplugin.env.nodejs.NodeJSEnv +import scala.scalajs.sbtplugin.JSUtils._ + +import scala.tools.partest.scalajs.ScalaJSPartestOptions._ + +import java.io.File +import scala.io.Source + +import Properties.{ versionString, copyrightString } +import GenericRunnerCommand._ + +class ScalaConsoleJSConsole extends JSConsole { + def log(msg: Any) = scala.Console.out.println(msg.toString) +} + +class MainGenericRunner { + def errorFn(ex: Throwable): Boolean = { + ex.printStackTrace() + false + } + def errorFn(str: String): Boolean = { + scala.Console.err println str + false + } + + val optMode = OptMode.fromId(sys.props("scalajs.partest.optMode")) + + def noWarnMissing = { + import ScalaJSOptimizer._ + + for { + fname <- sys.props.get("scalajs.partest.noWarnFile").toList + line <- Source.fromFile(fname).getLines + if !line.startsWith("#") + } yield line.split('.') match { + case Array(className) => NoWarnClass(className) + case Array(className, methodName) => NoWarnMethod(className, methodName) + } + } + + def readSemantics() = { + val opt = sys.props.get("scalajs.partest.compliantSems") + opt.fold(Semantics.Defaults) { str => + val sems = str.split(',') + Semantics.compliantTo(sems.toList) + } + } + + def process(args: Array[String]): Boolean = { + val command = new GenericRunnerCommand(args.toList, (x: String) => errorFn(x)) + import command.{ settings, howToRun, thingToRun } + + if (!command.ok) return errorFn("\n" + command.shortUsageMsg) + else if (settings.version) return errorFn("Scala code runner %s -- %s".format(versionString, copyrightString)) + else if (command.shouldStopWithInfo) return errorFn("shouldStopWithInfo") + + if (howToRun != AsObject) + return errorFn("Scala.js runner can only run an object") + + // Load basic Scala.js classpath (used for running or further packaging) + val usefulClasspathEntries = for { + url <- settings.classpathURLs + f = urlToFile(url) + if (f.isDirectory || f.getName.startsWith("scalajs-library")) + } yield f + val classpath = + PartialClasspathBuilder.build(usefulClasspathEntries).resolve() + + val logger = new ScalaConsoleLogger(Level.Warn) + val jsConsole = new ScalaConsoleJSConsole + + val mainObjName = ir.Definitions.encodeClassName(thingToRun) + val baseRunner = runnerJSFile(mainObjName, command.arguments) + val semantics = readSemantics() + + def fastOpted = fastOptimize(classpath, mainObjName, logger, semantics) + def fullOpted = fullOptimize(classpath, mainObjName, logger, + baseRunner, semantics.optimized) + + val runner = { + if (optMode == FullOpt) + fullOptRunner() + else + baseRunner + } + + val env = + if (optMode == NoOpt) new RhinoJSEnv(semantics) + else new NodeJSEnv + + val runClasspath = optMode match { + case NoOpt => classpath + case FastOpt => fastOpted + case FullOpt => fullOpted + } + + env.jsRunner(runClasspath, runner, logger, jsConsole).run() + + true + } + + private def runnerJSFile(mainObj: String, args: List[String]) = { + val jsObj = "ScalaJS.m." + mainObj + val jsArgs = argArray(args) + new MemVirtualJSFile("Generated launcher file"). + withContent(s"$jsObj().main__AT__V($jsArgs);") + } + + /** constructs a scala.Array[String] with the given elements */ + private def argArray(args: List[String]) = { + s"""ScalaJS.makeNativeArrayWrapper( + ScalaJS.d.T.getArrayOf(), + ${listToJS(args)})""" + } + + private def fastOptimize( + classpath: IRClasspath, + mainObjName: String, + logger: Logger, + semantics: Semantics) = { + import ScalaJSOptimizer._ + + val optimizer = newScalaJSOptimizer(semantics) + val output = WritableMemVirtualJSFile("partest fastOpt file") + + optimizer.optimizeCP( + Inputs(classpath, + manuallyReachable = fastOptReachable(mainObjName), + noWarnMissing = noWarnMissing + ), + OutputConfig( + output = output, + wantSourceMap = false, + checkIR = true + ), + logger) + } + + private def fastOptReachable(mainObjName: String) = { + import ScalaJSOptimizer._ + List( + ReachObject(mainObjName), + ReachMethod(mainObjName + '$', "main__AT__V", static = false) + ) + } + + private def fullOptimize( + classpath: IRClasspath, + mainObjName: String, + logger: Logger, + runner: VirtualJSFile, + semantics: Semantics) = { + import ScalaJSClosureOptimizer._ + + val fastOptimizer = newScalaJSOptimizer(semantics) + val fullOptimizer = new ScalaJSClosureOptimizer(semantics) + val output = WritableMemVirtualJSFile("partest fullOpt file") + val exportFile = fullOptExportFile(runner) + + fullOptimizer.optimizeCP(fastOptimizer, Inputs( + input = ScalaJSOptimizer.Inputs( + classpath, + manuallyReachable = fastOptReachable(mainObjName), + noWarnMissing = noWarnMissing), + additionalExports = exportFile :: Nil), + OutputConfig( + output, + checkIR = true, + wantSourceMap = false), + logger) + + } + + private def newScalaJSOptimizer(semantics: Semantics) = + new ScalaJSOptimizer(semantics, new ParIncOptimizer(_)) + + /** generates an exporter statement for the google closure compiler that runs + * what the normal test would + */ + private def fullOptExportFile(runnerFile: VirtualJSFile) = { + new MemVirtualJSFile("partest fullOpt exports").withContent( + s"""this["runFullOptPartest"] = function() { ${runnerFile.content} };""" + ) + } + + private def fullOptRunner() = new MemVirtualJSFile("partest fullOpt runner"). + withContent("runFullOptPartest();") + + private def urlToFile(url: java.net.URL) = { + try { + new File(url.toURI()) + } catch { + case e: java.net.URISyntaxException => new File(url.getPath()) + } + } +} + +object MainGenericRunner extends MainGenericRunner { + def main(args: Array[String]) { + if (!process(args)) + sys.exit(1) + } +} diff --git a/examples/scala-js/partest/src/main/scala/scala/tools/partest/scalajs/PartestInterface.scala b/examples/scala-js/partest/src/main/scala/scala/tools/partest/scalajs/PartestInterface.scala new file mode 100644 index 0000000..0dc2189 --- /dev/null +++ b/examples/scala-js/partest/src/main/scala/scala/tools/partest/scalajs/PartestInterface.scala @@ -0,0 +1,119 @@ +/* NOTE + * Most of this file is copy-pasted from + * https://github.com/scala/scala-partest-interface + * It is unfortunately not configurable enough, hence the duplication + */ + +package scala.tools.partest +package scalajs + +import scala.language.reflectiveCalls + +import sbt.testing.Fingerprint +import sbt.testing.TaskDef +import sbt.testing.EventHandler +import sbt.testing.Logger +import sbt.testing.Task +import sbt.testing.AnnotatedFingerprint +import java.net.URLClassLoader +import java.io.File + +object Framework { + // as partest is not driven by test classes discovered by sbt, need to add this marker fingerprint to definedTests + val fingerprint = new AnnotatedFingerprint { def isModule = true; def annotationName = "partest" } + + // TODO how can we export `fingerprint` so that a user can just add this to their build.sbt + // definedTests in Test += new sbt.TestDefinition("partest", fingerprint, true, Array()) +} +class Framework extends sbt.testing.Framework { + def fingerprints: Array[Fingerprint] = Array(Framework.fingerprint) + def name: String = "partest" + + def runner(args: Array[String], remoteArgs: Array[String], testClassLoader: ClassLoader): sbt.testing.Runner = + new Runner(args, remoteArgs, testClassLoader) +} + +/** Represents one run of a suite of tests. + */ +case class Runner(args: Array[String], remoteArgs: Array[String], testClassLoader: ClassLoader) extends sbt.testing.Runner { + /** Returns an array of tasks that when executed will run tests and suites determined by the + * passed <code>TaskDef</code>s. + * + * <p> + * Each returned task, when executed, will run tests and suites determined by the + * test class name, fingerprints, "explicitly specified" field, and selectors of one of the passed <code>TaskDef</code>s. + * </p> + * + * <p> + * This <code>tasks</code> method may be called with <code>TaskDef</code>s containing the same value for <code>testClassName</code> but + * different fingerprints. For example, if both a class and its companion object were test classes, the <code>tasks</code> method could be + * passed an array containing <code>TaskDef</code>s with the same name but with a different value for <code>fingerprint.isModule</code>. + * </p> + * + * <p> + * A test framework may "reject" a requested task by returning no <code>Task</code> for that <code>TaskDef</code>. + * </p> + * + * @param taskDefs the <code>TaskDef</code>s for requested tasks + * @return an array of <code>Task</code>s + * @throws IllegalStateException if invoked after <code>done</code> has been invoked. + */ + def tasks(taskDefs: Array[TaskDef]): Array[sbt.testing.Task] = + taskDefs map (PartestTask(_, args): sbt.testing.Task) + + /** Indicates the client is done with this <code>Runner</code> instance. + * + * @return a possibly multi-line summary string, or the empty string if no summary is provided -- TODO + */ + def done(): String = "" +} + +/** Run partest in this VM. Assumes we're running in a forked VM! + * + * TODO: make configurable + */ +case class PartestTask(taskDef: TaskDef, args: Array[String]) extends Task { + + // Get scala version through test name + val scalaVersion = taskDef.fullyQualifiedName.stripPrefix("partest-") + + /** Executes this task, possibly returning to the client new tasks to execute. */ + def execute(eventHandler: EventHandler, loggers: Array[Logger]): Array[Task] = { + val forkedCp = scala.util.Properties.javaClassPath + val classLoader = new URLClassLoader(forkedCp.split(java.io.File.pathSeparator).map(new File(_).toURI.toURL)) + + if (Runtime.getRuntime().maxMemory() / (1024*1024) < 800) + loggers foreach (_.warn(s"""Low heap size detected (~ ${Runtime.getRuntime().maxMemory() / (1024*1024)}M). Please add the following to your build.sbt: javaOptions in Test += "-Xmx1G"""")) + + val maybeOptions = + ScalaJSPartestOptions(args, str => loggers.foreach(_.error(str))) + + maybeOptions foreach { options => + val runner = SBTRunner( + Framework.fingerprint, eventHandler, loggers, + new File(s"../partest/fetchedSources/${scalaVersion}"), + classLoader, null, null, Array.empty[String], options, scalaVersion) + + try runner execute Array("run", "pos", "neg") + catch { + case ex: ClassNotFoundException => + loggers foreach { l => l.error("Please make sure partest is running in a forked VM by including the following line in build.sbt:\nfork in Test := true") } + throw ex + } + } + + Array() + } + + type SBTRunner = { def execute(kinds: Array[String]): String } + + // use reflection to instantiate scala.tools.partest.scalajs.ScalaJSSBTRunner, + // casting to the structural type SBTRunner above so that method calls on the result will be invoked reflectively as well + private def SBTRunner(partestFingerprint: Fingerprint, eventHandler: EventHandler, loggers: Array[Logger], testRoot: File, testClassLoader: URLClassLoader, javaCmd: File, javacCmd: File, scalacArgs: Array[String], options: ScalaJSPartestOptions, scalaVersion: String): SBTRunner = { + val runnerClass = Class.forName("scala.tools.partest.scalajs.ScalaJSSBTRunner") + runnerClass.getConstructors()(0).newInstance(partestFingerprint, eventHandler, loggers, testRoot, testClassLoader, javaCmd, javacCmd, scalacArgs, options, scalaVersion).asInstanceOf[SBTRunner] + } + + /** A possibly zero-length array of string tags associated with this task. */ + def tags: Array[String] = Array() +} diff --git a/examples/scala-js/partest/src/main/scala/scala/tools/partest/scalajs/ScalaJSPartest.scala b/examples/scala-js/partest/src/main/scala/scala/tools/partest/scalajs/ScalaJSPartest.scala new file mode 100644 index 0000000..edd0ea9 --- /dev/null +++ b/examples/scala-js/partest/src/main/scala/scala/tools/partest/scalajs/ScalaJSPartest.scala @@ -0,0 +1,203 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2013 LAMP/EPFL + * @author Sébastien Doeraene + */ + +package scala.tools.partest +package scalajs + +import nest._ +import Path._ + +import scala.tools.nsc.{ Global, Settings } +import scala.tools.nsc.reporters.{ Reporter } +import scala.tools.nsc.plugins.Plugin + +import scala.scalajs.compiler.ScalaJSPlugin + +import scala.io.Source + +import sbt.testing.{ EventHandler, Logger, Fingerprint } +import java.io.File +import java.net.URLClassLoader + +trait ScalaJSDirectCompiler extends DirectCompiler { + override def newGlobal(settings: Settings, reporter: Reporter): PartestGlobal = { + new PartestGlobal(settings, reporter) { + override protected def loadRoughPluginsList(): List[Plugin] = { + (super.loadRoughPluginsList() :+ + Plugin.instantiate(classOf[ScalaJSPlugin], this)) + } + } + } +} + +class ScalaJSRunner(testFile: File, suiteRunner: SuiteRunner, + scalaJSOverridePath: String, options: ScalaJSPartestOptions, + noWarnFile: File) extends nest.Runner(testFile, suiteRunner) { + + private val compliantSems: List[String] = { + scalaJSConfigFile("sem").fold(List.empty[String]) { file => + Source.fromFile(file).getLines.toList + } + } + + override val checkFile: File = { + scalaJSConfigFile("check") getOrElse { + // this is super.checkFile, but apparently we can't do that + new FileOps(testFile).changeExtension("check") + } + } + + private def scalaJSConfigFile(ext: String): Option[File] = { + val overrideFile = s"$scalaJSOverridePath/$kind/$fileBase.$ext" + val url = getClass.getResource(overrideFile) + Option(url).map(url => new File(url.toURI)) + } + + override def newCompiler = new DirectCompiler(this) with ScalaJSDirectCompiler + override def extraJavaOptions = { + super.extraJavaOptions ++ Seq( + s"-Dscalajs.partest.noWarnFile=${noWarnFile.getAbsolutePath}", + s"-Dscalajs.partest.optMode=${options.optMode.id}", + s"-Dscalajs.partest.compliantSems=${compliantSems.mkString(",")}" + ) + } +} + +trait ScalaJSSuiteRunner extends SuiteRunner { + + // Stuff to mix in + + val options: ScalaJSPartestOptions + + /** Full scala version name. Used to discover blacklist (etc.) files */ + val scalaVersion: String + + // Stuff we provide + + override def banner: String = { + import scala.scalajs.ir.ScalaJSVersions.{ current => currentVersion } + + super.banner.trim + s""" + |Scala.js version is: $currentVersion + |Scala.js options are: + |optimizer: ${options.optMode.shortStr} + |testFilter: ${options.testFilter.descr} + """.stripMargin + } + + override def runTest(testFile: File): TestState = { + // Mostly copy-pasted from SuiteRunner.runTest(), unfortunately :-( + val runner = new ScalaJSRunner(testFile, this, listDir, options, noWarnFile) + + // when option "--failed" is provided execute test only if log + // is present (which means it failed before) + val state = + if (failed && !runner.logFile.canRead) + runner.genPass() + else { + val (state, elapsed) = + try timed(runner.run()) + catch { + case t: Throwable => throw new RuntimeException(s"Error running $testFile", t) + } + NestUI.reportTest(state) + runner.cleanup() + state + } + onFinishTest(testFile, state) + } + + override def runTestsForFiles(kindFiles: Array[File], + kind: String): Array[TestState] = { + super.runTestsForFiles(kindFiles.filter(shouldUseTest), kind) + } + + private lazy val listDir = + s"/scala/tools/partest/scalajs/$scalaVersion" + + private lazy val buglistedTestFileNames = + readTestList(s"$listDir/BuglistedTests.txt") + + private lazy val blacklistedTestFileNames = + readTestList(s"$listDir/BlacklistedTests.txt") + + private lazy val whitelistedTestFileNames = + readTestList(s"$listDir/WhitelistedTests.txt") + + private lazy val noWarnFile: File = { + val url = getClass.getResource(s"$listDir/NoDCEWarn.txt") + assert(url != null, "Need NoDCEWarn.txt file") + new File(url.toURI).getAbsolutePath() + } + + private def readTestList(resourceName: String): Set[String] = { + val source = scala.io.Source.fromURL(getClass.getResource(resourceName)) + + val fileNames = for { + line <- source.getLines + trimmed = line.trim + if trimmed != "" && !trimmed.startsWith("#") + } yield extendShortTestName(trimmed) + + fileNames.toSet + } + + private def extendShortTestName(testName: String) = { + val srcDir = PathSettings.srcDir + (srcDir / testName).toCanonical.getAbsolutePath + } + + private lazy val testFilter: String => Boolean = { + import ScalaJSPartestOptions._ + options.testFilter match { + case UnknownTests => { absPath => + !blacklistedTestFileNames.contains(absPath) && + !whitelistedTestFileNames.contains(absPath) && + !buglistedTestFileNames.contains(absPath) + } + case BlacklistedTests => blacklistedTestFileNames + case BuglistedTests => buglistedTestFileNames + case WhitelistedTests => whitelistedTestFileNames + case SomeTests(names) => names.map(extendShortTestName _).toSet + } + } + + private def shouldUseTest(testFile: File): Boolean = { + val absPath = testFile.toCanonical.getAbsolutePath + testFilter(absPath) + } +} + +/* Pre-mixin ScalaJSSuiteRunner in SBTRunner, because this is looked up + * via reflection from the sbt partest interface of Scala.js + */ +class ScalaJSSBTRunner( + partestFingerprint: Fingerprint, + eventHandler: EventHandler, + loggers: Array[Logger], + testRoot: File, + testClassLoader: URLClassLoader, + javaCmd: File, + javacCmd: File, + scalacArgs: Array[String], + val options: ScalaJSPartestOptions, + val scalaVersion: String +) extends SBTRunner( + partestFingerprint, eventHandler, loggers, "test/files", testClassLoader, + javaCmd, javacCmd, scalacArgs +) with ScalaJSSuiteRunner { + + // The test root for partest is read out through the system properties, + // not passed as an argument + sys.props("partest.root") = testRoot.getAbsolutePath() + + // Partests take at least 5h. We double, just to be sure. (default is 4 hours) + sys.props("partest.timeout") = "10 hours" + + // Set showDiff on global UI module + if (options.showDiff) + NestUI.setDiffOnFail() + +} diff --git a/examples/scala-js/partest/src/main/scala/scala/tools/partest/scalajs/ScalaJSPartestOptions.scala b/examples/scala-js/partest/src/main/scala/scala/tools/partest/scalajs/ScalaJSPartestOptions.scala new file mode 100644 index 0000000..1f1680a --- /dev/null +++ b/examples/scala-js/partest/src/main/scala/scala/tools/partest/scalajs/ScalaJSPartestOptions.scala @@ -0,0 +1,109 @@ +package scala.tools.partest.scalajs + +class ScalaJSPartestOptions private ( + val testFilter: ScalaJSPartestOptions.TestFilter, + val optMode: ScalaJSPartestOptions.OptMode, + val showDiff: Boolean +) + +object ScalaJSPartestOptions { + + sealed abstract class TestFilter { + def descr: String + } + case object UnknownTests extends TestFilter { + override def descr: String = "Unknown" + } + case object BlacklistedTests extends TestFilter { + override def descr: String = "Blacklisted" + } + case object WhitelistedTests extends TestFilter { + override def descr: String = "Whitelisted" + } + case object BuglistedTests extends TestFilter { + override def descr: String = "Buglisted" + } + case class SomeTests(names: List[String]) extends TestFilter { + override def descr: String = "Custom " + this.toString + override def toString() = + names.map(x => s""""$x"""").mkString("[", ", ", "]") + } + + sealed abstract class OptMode { + def shortStr: String + def id: String + } + object OptMode { + def fromId(id: String): OptMode = id match { + case "none" => NoOpt + case "fast" => FastOpt + case "full" => FullOpt + case _ => sys.error(s"Unknown optimization mode: $id") + } + } + case object NoOpt extends OptMode { + def shortStr: String = "None" + def id: String = "none" + } + case object FastOpt extends OptMode { + def shortStr: String = "Fast" + def id: String = "fast" + } + case object FullOpt extends OptMode { + def shortStr: String = "Full" + def id: String = "full" + } + + def apply(args: Array[String], + errorReporter: String => Unit): Option[ScalaJSPartestOptions] = { + + var failed = false + + var filter: Option[TestFilter] = None + var optMode: OptMode = NoOpt + var showDiff: Boolean = false + + def error(msg: String) = { + failed = true + errorReporter(msg) + } + + def setFilter(newFilter: TestFilter) = (filter, newFilter) match { + case (Some(SomeTests(oldNames)), SomeTests(newNames)) => + // Merge test names + filter = Some(SomeTests(oldNames ++ newNames)) + case (Some(fil), newFilter) => + error(s"You cannot specify twice what tests to use (already specified: $fil, new: $newFilter)") + case (None, newFilter) => + filter = Some(newFilter) + } + + for (arg <- args) arg match { + case "--fastOpt" => + optMode = FastOpt + case "--noOpt" => + optMode = NoOpt + case "--fullOpt" => + optMode = FullOpt + case "--blacklisted" => + setFilter(BlacklistedTests) + case "--buglisted" => + setFilter(BuglistedTests) + case "--whitelisted" => + setFilter(WhitelistedTests) + case "--unknown" => + setFilter(UnknownTests) + case "--showDiff" => + showDiff = true + case _ => + setFilter(SomeTests(arg :: Nil)) + } + + if (failed) None + else Some { + new ScalaJSPartestOptions( + filter.getOrElse(WhitelistedTests), optMode, showDiff) + } + } + +} diff --git a/examples/scala-js/project/ExternalCompile.scala b/examples/scala-js/project/ExternalCompile.scala new file mode 100644 index 0000000..7d37c81 --- /dev/null +++ b/examples/scala-js/project/ExternalCompile.scala @@ -0,0 +1,116 @@ +import sbt._ +import Keys._ + +import scala.scalajs.sbtplugin.ScalaJSPlugin +import ScalaJSPlugin.autoImport.jsDependencyManifest + +object ExternalCompile { + + private val isWindows = + System.getProperty("os.name").toLowerCase().indexOf("win") >= 0 + + val scalaJSExternalCompileConfigSettings: Seq[Setting[_]] = inTask(compile)( + Defaults.runnerTask + ) ++ Seq( + fork in compile := true, + trapExit in compile := true, + javaOptions in compile += "-Xmx512M", + + compile := { + val inputs = (compileInputs in compile).value + import inputs.config._ + + val s = streams.value + val logger = s.log + val cacheDir = s.cacheDirectory + + // Discover classpaths + + def cpToString(cp: Seq[File]) = + cp.map(_.getAbsolutePath).mkString(java.io.File.pathSeparator) + + val compilerCp = inputs.compilers.scalac.scalaInstance.allJars + val cpStr = cpToString(classpath) + + // List all my dependencies (recompile if any of these changes) + + val allMyDependencies = classpath filterNot (_ == classesDirectory) flatMap { cpFile => + if (cpFile.isDirectory) (cpFile ** "*.class").get + else Seq(cpFile) + } + + // Compile + + val cachedCompile = FileFunction.cached(cacheDir / "compile", + FilesInfo.lastModified, FilesInfo.exists) { dependencies => + + logger.info( + "Compiling %d Scala sources to %s..." format ( + sources.size, classesDirectory)) + + if (classesDirectory.exists) + IO.delete(classesDirectory) + IO.createDirectory(classesDirectory) + + val sourcesArgs = sources.map(_.getAbsolutePath()).toList + + /* run.run() below in doCompileJS() will emit a call to its + * logger.info("Running scala.tools.nsc.scalajs.Main [...]") + * which we do not want to see. We use this patched logger to + * filter out that particular message. + */ + val patchedLogger = new Logger { + def log(level: Level.Value, message: => String) = { + val msg = message + if (level != Level.Info || + !msg.startsWith("Running scala.tools.nsc.Main")) + logger.log(level, msg) + } + def success(message: => String) = logger.success(message) + def trace(t: => Throwable) = logger.trace(t) + } + + def doCompile(sourcesArgs: List[String]): Unit = { + val run = (runner in compile).value + run.run("scala.tools.nsc.Main", compilerCp, + "-cp" :: cpStr :: + "-d" :: classesDirectory.getAbsolutePath() :: + options ++: + sourcesArgs, + patchedLogger) foreach sys.error + } + + /* Crude way of overcoming the Windows limitation on command line + * length. + */ + if ((fork in compile).value && isWindows && + (sourcesArgs.map(_.length).sum > 1536)) { + IO.withTemporaryFile("sourcesargs", ".txt") { sourceListFile => + IO.writeLines(sourceListFile, sourcesArgs) + doCompile(List("@"+sourceListFile.getAbsolutePath())) + } + } else { + doCompile(sourcesArgs) + } + + // Output is all files in classesDirectory + (classesDirectory ** AllPassFilter).get.toSet + } + + cachedCompile((sources ++ allMyDependencies).toSet) + + // We do not have dependency analysis when compiling externally + sbt.inc.Analysis.Empty + }, + + // Make sure jsDependencyManifest runs after compile, otherwise compile + // might remove the entire directory afterwards. + jsDependencyManifest <<= jsDependencyManifest.dependsOn(compile) + ) + + val scalaJSExternalCompileSettings = ( + inConfig(Compile)(scalaJSExternalCompileConfigSettings) ++ + inConfig(Test)(scalaJSExternalCompileConfigSettings) + ) + +} diff --git a/examples/scala-js/project/JavaLangObject.scala b/examples/scala-js/project/JavaLangObject.scala new file mode 100644 index 0000000..ff82b94 --- /dev/null +++ b/examples/scala-js/project/JavaLangObject.scala @@ -0,0 +1,243 @@ +/* + * Hard-coded IR for java.lang.Object. + */ + +import scala.scalajs.ir +import ir._ +import ir.Definitions._ +import ir.Infos._ +import ir.Trees._ +import ir.Types._ +import ir.Position.NoPosition + +/** Hard-coded IR for java.lang.Object. + * We cannot so much as begin to fake a compilation of java.lang.Object, + * because Object is hijacked so much by scalac itself that it does not like + * at all to try to compile that class. So we have to bypass entirely the + * compiler to define java.lang.Object. + */ +object JavaLangObject { + + /** Optimizer hints with `@inline` + * Unfortunately we do not have access to private details of + * [[OptimizerHints]], so we cannot do this cleanly. But it is fine + * somehow because we're part of the same project implementation. + */ + private def inlineOptimizerHints = new OptimizerHints(2) + + val InfoAndTree = (Info, Definition) + + private def Info = ClassInfo( + name = "java.lang.Object", + encodedName = "O", + ancestorCount = 0, + kind = ClassKind.Class, + superClass = "", + ancestors = List("O"), + methods = List( + MethodInfo("__init__"), + MethodInfo("init___"), + MethodInfo("hashCode__I", + calledMethods = Map( + "jl_System$" -> List("identityHashCode__O__I") + ), + accessedModules = List("jl_System") + ), + MethodInfo("equals__O__Z", + optimizerHints = inlineOptimizerHints + ), + MethodInfo("clone__O", + calledMethods = Map( + "sjsr_package$" -> List("cloneObject__sjs_js_Object__sjs_js_Object"), + "jl_CloneNotSupportedException" -> List("init___") + ), + instantiatedClasses = List("jl_CloneNotSupportedException"), + accessedModules = List("sjsr_package"), + accessedClassData = List("jl_Cloneable"), + optimizerHints = inlineOptimizerHints + ), + MethodInfo("notify__V"), + MethodInfo("notifyAll__V"), + MethodInfo("toString__T", + calledMethods = Map( + "O" -> List("hashCode__I"), + "jl_Class" -> List("getName__T"), + "jl_Integer$" -> List("toHexString__I__T") + ), + accessedModules = List("jl_Integer") + ), + MethodInfo("finalize__V"), + MethodInfo("clone__", + calledMethods = Map( + "O" -> List("clone__O") + ) + ), + MethodInfo("notify__", + calledMethods = Map( + "O" -> List("notify__V") + ) + ), + MethodInfo("notifyAll__", + calledMethods = Map( + "O" -> List("notifyAll__V") + ) + ), + MethodInfo("finalize__", + calledMethods = Map( + "O" -> List("finalize__V") + ) + ) + ) + ) + + private def Definition = { + implicit val DummyPos = NoPosition + + // ClassType(Object) is normally invalid, but not in this class def + val ThisType = ClassType(ObjectClass) + + val classDef = ClassDef( + Ident("O", Some("java.lang.Object")), + ClassKind.Class, + None, + Nil, + List( + /* def this() = () */ + MethodDef( + Ident("init___", Some("<init>")), + Nil, + AnyType, + This()(ThisType))(None), + + /* def hashCode(): Int = System.identityHashCode(this) */ + MethodDef( + Ident("hashCode__I", Some("hashCode__I")), + Nil, + IntType, + { + Apply( + LoadModule(ClassType("jl_System$")), + Ident("identityHashCode__O__I", Some("identityHashCode")), + List(This()(ThisType)))(IntType) + })(None), + + /* def equals(that: Object): Boolean = this eq that */ + MethodDef( + Ident("equals__O__Z", Some("equals__O__Z")), + List(ParamDef(Ident("that", Some("that")), AnyType, mutable = false)), + BooleanType, + { + BinaryOp(BinaryOp.===, + This()(ThisType), + VarRef(Ident("that", Some("that")), mutable = false)(AnyType)) + })(None), + + /* protected def clone(): Object = + * if (this.isInstanceOf[Cloneable]) <clone>(this) + * else throw new CloneNotSupportedException() + */ + MethodDef( + Ident("clone__O", Some("clone__O")), + Nil, + AnyType, + { + If(IsInstanceOf(This()(ThisType), ClassType("jl_Cloneable")), { + Apply(LoadModule(ClassType("sjsr_package$")), + Ident("cloneObject__sjs_js_Object__sjs_js_Object", Some("cloneObject")), + List(This()(ThisType)))(AnyType) + }, { + Throw(New(ClassType("jl_CloneNotSupportedException"), + Ident("init___", Some("<init>")), Nil)) + })(AnyType) + })(None), + + /* def toString(): String = + * getClass().getName() + "@" + Integer.toHexString(hashCode()) + */ + MethodDef( + Ident("toString__T", Some("toString__T")), + Nil, + ClassType(StringClass), + { + BinaryOp(BinaryOp.String_+, BinaryOp(BinaryOp.String_+, + Apply( + GetClass(This()(ThisType)), + Ident("getName__T"), Nil)(ClassType(StringClass)), + // + + StringLiteral("@")), + // + + Apply( + LoadModule(ClassType("jl_Integer$")), + Ident("toHexString__I__T"), + List(Apply(This()(ThisType), Ident("hashCode__I"), Nil)(IntType)))( + ClassType(StringClass))) + })(None), + + /* Since wait() is not supported in any way, a correct implementation + * of notify() and notifyAll() is to do nothing. + */ + + /* def notify(): Unit = () */ + MethodDef( + Ident("notify__V", Some("notify__V")), + Nil, + NoType, + Skip())(None), + + /* def notifyAll(): Unit = () */ + MethodDef( + Ident("notifyAll__V", Some("notifyAll__V")), + Nil, + NoType, + Skip())(None), + + /* def finalize(): Unit = () */ + MethodDef( + Ident("finalize__V", Some("finalize__V")), + Nil, + NoType, + Skip())(None), + + /* Reflective proxies + * Note that we do not need to proxy the following methods, since + * they are defined on Any in the Scala hierarchy and therefore a + * reflective call is never emitted: + * - equals + * - getClass + * - hashCode + * - toString + */ + + MethodDef(Ident("clone__"), Nil, AnyType, + Apply(This()(ThisType), Ident("clone__O"), Nil)(AnyType))(None), + + MethodDef(Ident("notify__"), Nil, AnyType, Block( + Apply(This()(ThisType), Ident("notify__V"), Nil)(NoType), + Undefined()))(None), + + MethodDef(Ident("notifyAll__"), Nil, AnyType, Block( + Apply(This()(ThisType), Ident("notifyAll__V"), Nil)(NoType), + Undefined()))(None), + + MethodDef(Ident("finalize__"), Nil, AnyType, Block( + Apply(This()(ThisType), Ident("finalize__V"), Nil)(NoType), + Undefined()))(None), + + // Exports + + /* JSExport for toString(). */ + MethodDef( + StringLiteral("toString"), + Nil, + AnyType, + { + Apply(This()(ThisType), + Ident("toString__T", Some("toString__T")), + Nil)(ClassType(StringClass)) + })(None) + )) + + Hashers.hashClassDef(classDef) + } + +} diff --git a/examples/scala-js/project/JavaLangString.scala b/examples/scala-js/project/JavaLangString.scala new file mode 100644 index 0000000..fc68b29 --- /dev/null +++ b/examples/scala-js/project/JavaLangString.scala @@ -0,0 +1,215 @@ +/* + * Hard-coded IR for java.lang.String. + */ + +import scala.scalajs.ir +import ir._ +import ir.Definitions._ +import ir.Infos._ +import ir.Trees._ +import ir.Types._ +import ir.Position.NoPosition + +/** Hard-coded IR for java.lang.String. + * Unlike for the other hijacked classes, scalac does not like at all to + * compile even a mocked version of java.lang.String. So we have to bypass + * entirely the compiler to define java.lang.String. + */ +object JavaLangString { + + /** Optimizer hints with `@inline` + * Unfortunately we do not have access to private details of + * [[OptimizerHints]], so we cannot do this cleanly. But it is fine + * somehow because we're part of the same project implementation. + */ + private def inlineOptimizerHints = new OptimizerHints(2) + + val InfoAndTree = (Info, Definition) + + private def Info = ClassInfo( + name = "java.lang.String", + encodedName = "T", + ancestorCount = 1, + kind = ClassKind.HijackedClass, + superClass = "O", + ancestors = List( + "T", "Ljava_io_Serializable", "jl_CharSequence", "jl_Comparable", "O"), + methods = List( + MethodInfo("equals__O__Z", + optimizerHints = inlineOptimizerHints + ), + MethodInfo("hashCode__I", + calledMethods = Map( + "sjsr_RuntimeString$" -> List("hashCode__T__I") + ), + accessedModules = List("sjsr_RuntimeString"), + optimizerHints = inlineOptimizerHints + ), + MethodInfo("compareTo__T__I", + calledMethods = Map( + "sjsr_RuntimeString$" -> List("compareTo__T__T__I") + ), + accessedModules = List("sjsr_RuntimeString"), + optimizerHints = inlineOptimizerHints + ), + MethodInfo("compareTo__O__I", + calledMethods = Map( + "T" -> List("compareTo__T__I") + ), + optimizerHints = inlineOptimizerHints + ), + MethodInfo("toString__T", + optimizerHints = inlineOptimizerHints + ), + MethodInfo("charAt__I__C", + calledMethods = Map( + "sjsr_RuntimeString$" -> List("charAt__T__I__C") + ), + accessedModules = List("sjsr_RuntimeString"), + optimizerHints = inlineOptimizerHints + ), + MethodInfo("length__I", + calledMethods = Map( + "sjsr_RuntimeString$" -> List("length__T__I") + ), + accessedModules = List("sjsr_RuntimeString"), + optimizerHints = inlineOptimizerHints + ), + MethodInfo("subSequence__I__I__jl_CharSequence", + calledMethods = Map( + "sjsr_RuntimeString$" -> List("subSequence__T__I__I__jl_CharSequence") + ), + accessedModules = List("sjsr_RuntimeString"), + optimizerHints = inlineOptimizerHints + ) + ) + ) + + private def Definition = { + implicit val DummyPos = NoPosition + + val ThisType = ClassType(StringClass) + + ClassDef( + Ident("T", Some("java.lang.String")), + ClassKind.HijackedClass, + Some(Ident("O", Some("java.lang.Object"))), + List( + Ident("Ljava_io_Serializable", Some("java.io.Serializable")), + Ident("jl_CharSequence", Some("java.lang.CharSequence")), + Ident("jl_Comparable", Some("java.lang.Comparable")), + Ident("O", Some("java.lang.Object")) + ), + List( + /* def equals(that: Object): Boolean = this eq that */ + MethodDef( + Ident("equals__O__Z", Some("equals__O__Z")), + List(ParamDef(Ident("that", Some("that")), AnyType, mutable = false)), + BooleanType, + { + BinaryOp(BinaryOp.===, + This()(ThisType), + VarRef(Ident("that", Some("that")), mutable = false)(AnyType)) + })(None), + + /* def hashCode(): Int = RuntimeString.hashCode(this) */ + MethodDef( + Ident("hashCode__I", Some("hashCode__I")), + Nil, + IntType, + { + Apply( + LoadModule(ClassType("sjsr_RuntimeString$")), + Ident("hashCode__T__I", Some("hashCode__T__I")), + List(This()(ThisType)))(IntType) + })(None), + + /* def compareTo(that: String): Int = RuntimeString.compareTo(this, that) */ + MethodDef( + Ident("compareTo__T__I", Some("compareTo__T__I")), + List(ParamDef(Ident("that", Some("that")), ThisType, mutable = false)), + IntType, + { + Apply( + LoadModule(ClassType("sjsr_RuntimeString$")), + Ident("compareTo__T__T__I", Some("compareTo__T__T__I")), + List( + This()(ThisType), + VarRef(Ident("that", Some("that")), mutable = false)(ThisType)))(IntType) + })(None), + + /* def compareTo(that: Object): Int = compareTo(that.asInstanceOf[String]) */ + MethodDef( + Ident("compareTo__O__I", Some("compareTo__O__I")), + List(ParamDef(Ident("that", Some("that")), AnyType, mutable = false)), + IntType, + { + Apply( + This()(ThisType), + Ident("compareTo__T__I", Some("compareTo__T__I")), + List(AsInstanceOf( + VarRef(Ident("that", Some("that")), mutable = false)(AnyType), + ThisType)))(IntType) + })(None), + + /* def toString(): String = this */ + MethodDef( + Ident("toString__T", Some("toString__T")), + Nil, + ClassType(StringClass), + { + This()(ThisType) + })(None), + + /* def charAt(i: Int): Char = RuntimeString.charAt(this, i) */ + MethodDef( + Ident("charAt__I__C", Some("charAt__I__C")), + List(ParamDef(Ident("i", Some("i")), IntType, mutable = false)), + IntType, + { + Apply( + LoadModule(ClassType("sjsr_RuntimeString$")), + Ident("charAt__T__I__C", Some("charAt__T__I__C")), + List( + This()(ThisType), + VarRef(Ident("i", Some("i")), mutable = false)(IntType)))(IntType) + })(None), + + /* def length(): Int = RuntimeString.length(this) */ + MethodDef( + Ident("length__I", Some("length__I")), + Nil, + IntType, + { + Apply( + LoadModule(ClassType("sjsr_RuntimeString$")), + Ident("length__T__I", Some("length__T__I")), + List(This()(ThisType)))(IntType) + })(None), + + /* def subSequence(begin: Int, end: Int): CharSequence = + * RuntimeString.subSequence(this, begin, end) + */ + MethodDef( + Ident("subSequence__I__I__jl_CharSequence", + Some("subSequence__I__I__jl_CharSequence")), + List( + ParamDef(Ident("begin", Some("begin")), IntType, mutable = false), + ParamDef(Ident("end", Some("end")), IntType, mutable = false) + ), + ClassType("jl_CharSequence"), + { + Apply( + LoadModule(ClassType("sjsr_RuntimeString$")), + Ident("subSequence__T__I__I__jl_CharSequence", + Some("subSequence__T__I__I__jl_CharSequence")), + List( + This()(ThisType), + VarRef(Ident("begin", Some("begin")), mutable = false)(IntType), + VarRef(Ident("end", Some("end")), mutable = false)(IntType)))( + ClassType("jl_CharSequence")) + })(None) + )) + } + +} diff --git a/examples/scala-js/project/ScalaJSBuild.scala b/examples/scala-js/project/ScalaJSBuild.scala new file mode 100644 index 0000000..5cbc405 --- /dev/null +++ b/examples/scala-js/project/ScalaJSBuild.scala @@ -0,0 +1,891 @@ +import sbt._ +import Keys._ + +import bintray.Plugin.bintrayPublishSettings +import bintray.Keys.{repository, bintrayOrganization, bintray} + +import java.io.{ + BufferedOutputStream, + FileOutputStream, + BufferedWriter, + FileWriter +} + +import scala.collection.mutable +import scala.util.Properties + +import scala.scalajs.ir +import scala.scalajs.sbtplugin._ +import scala.scalajs.sbtplugin.env.rhino.RhinoJSEnv +import ScalaJSPlugin.autoImport._ +import ExternalCompile.scalaJSExternalCompileSettings +import Implicits._ + +import scala.scalajs.tools.sourcemap._ +import scala.scalajs.tools.io.MemVirtualJSFile + +import sbtassembly.Plugin.{AssemblyKeys, assemblySettings} +import AssemblyKeys.{assembly, assemblyOption} + +object ScalaJSBuild extends Build { + + val fetchedScalaSourceDir = settingKey[File]( + "Directory the scala source is fetched to") + val fetchScalaSource = taskKey[File]( + "Fetches the scala source for the current scala version") + val shouldPartest = settingKey[Boolean]( + "Whether we should partest the current scala version (and fail if we can't)") + + val commonSettings = Seq( + organization := "org.scala-lang.modules.scalajs", + version := scalaJSVersion, + + normalizedName ~= { + _.replace("scala.js", "scalajs").replace("scala-js", "scalajs") + }, + + homepage := Some(url("http://scala-js.org/")), + licenses += ("BSD New", + url("https://github.com/scala-js/scala-js/blob/master/LICENSE")), + + shouldPartest := { + val testListDir = ( + (resourceDirectory in (partestSuite, Test)).value / "scala" + / "tools" / "partest" / "scalajs" / scalaVersion.value + ) + testListDir.exists + }, + + scalacOptions ++= Seq( + "-deprecation", + "-unchecked", + "-feature", + "-encoding", "utf8" + ) + ) + + private val snapshotsOrReleases = + if (scalaJSIsSnapshotVersion) "snapshots" else "releases" + + private def publishToScalaJSRepoSettings = Seq( + publishTo := { + Seq("PUBLISH_USER", "PUBLISH_PASS").map(Properties.envOrNone) match { + case Seq(Some(user), Some(pass)) => + Some(Resolver.sftp( + s"scala-js-$snapshotsOrReleases", + "repo.scala-js.org", + s"/home/scalajsrepo/www/repo/$snapshotsOrReleases")( + Resolver.ivyStylePatterns) as (user, pass)) + case _ => + None + } + } + ) + + private def publishToBintraySettings = ( + bintrayPublishSettings + ) ++ Seq( + repository in bintray := "scala-js-releases", + bintrayOrganization in bintray := Some("scala-js") + ) + + val publishSettings = ( + if (Properties.envOrNone("PUBLISH_TO_BINTRAY") == Some("true")) + publishToBintraySettings + else + publishToScalaJSRepoSettings + ) ++ Seq( + publishMavenStyle := false + ) + + val defaultSettings = commonSettings ++ Seq( + scalaVersion := "2.11.2" + ) + + val myScalaJSSettings = ScalaJSPluginInternal.scalaJSAbstractSettings ++ Seq( + autoCompilerPlugins := true, + scalaJSOptimizerOptions ~= (_.withCheckScalaJSIR(true)) + ) + + val scalaJSSourceMapSettings = scalacOptions ++= { + if (scalaJSIsSnapshotVersion) Seq() + else Seq( + // Link source maps to github sources + "-P:scalajs:mapSourceURI:" + root.base.toURI + + "->https://raw.githubusercontent.com/scala-js/scala-js/v" + + scalaJSVersion + "/" + ) + } + + /** Depend library as if (exportJars in library) was set to false */ + val compileWithLibrarySetting = { + internalDependencyClasspath in Compile ++= { + val prods = (products in (library, Compile)).value + val analysis = (compile in (library, Compile)).value + + prods.map(p => Classpaths.analyzed(p, analysis)) + } + } + + // Used when compiling the compiler, adding it to scalacOptions does not help + scala.util.Properties.setProp("scalac.patmat.analysisBudget", "1024") + + override lazy val settings = super.settings ++ Seq( + // Most of the projects cross-compile + crossScalaVersions := Seq( + "2.10.2", + "2.10.3", + "2.10.4", + "2.11.0", + "2.11.1", + "2.11.2", + "2.11.4" + ), + // Default stage + scalaJSStage in Global := PreLinkStage + ) + + lazy val root: Project = Project( + id = "scalajs", + base = file("."), + settings = defaultSettings ++ Seq( + name := "Scala.js", + publishArtifact in Compile := false, + + clean := clean.dependsOn( + // compiler, library and jasmineTestFramework are aggregated + clean in tools, clean in toolsJS, clean in plugin, + clean in javalanglib, clean in javalib, clean in scalalib, + clean in libraryAux, clean in javalibEx, + clean in examples, clean in helloworld, + clean in reversi, clean in testingExample, + clean in testSuite, clean in noIrCheckTest, + clean in javalibExTestSuite, + clean in partest, clean in partestSuite).value, + + publish := {}, + publishLocal := {} + ) + ).aggregate( + compiler, library, testBridge, jasmineTestFramework + ) + + /* This project is not really used in the build. Its sources are actually + * added as sources of the `compiler` project (and meant to be added to the + * `tools` project as well). + * It exists mostly to be used in IDEs, because they don't like very much to + * have code in a project that lives outside the project's directory, and + * like even less that code be shared by different projects. + */ + lazy val irProject: Project = Project( + id = "ir", + base = file("ir"), + settings = defaultSettings ++ Seq( + name := "Scala.js IR", + exportJars := true + ) + ) + + lazy val compiler: Project = Project( + id = "compiler", + base = file("compiler"), + settings = defaultSettings ++ publishSettings ++ Seq( + name := "Scala.js compiler", + crossVersion := CrossVersion.full, // because compiler api is not binary compatible + unmanagedSourceDirectories in Compile += + (scalaSource in (irProject, Compile)).value, + libraryDependencies ++= Seq( + "org.scala-lang" % "scala-compiler" % scalaVersion.value, + "org.scala-lang" % "scala-reflect" % scalaVersion.value, + "com.novocode" % "junit-interface" % "0.9" % "test" + ), + testOptions += Tests.Setup { () => + val testOutDir = (streams.value.cacheDirectory / "scalajs-compiler-test") + IO.createDirectory(testOutDir) + sys.props("scala.scalajs.compiler.test.output") = + testOutDir.getAbsolutePath + sys.props("scala.scalajs.compiler.test.scalajslib") = + (artifactPath in (library, Compile, packageBin)).value.getAbsolutePath + sys.props("scala.scalajs.compiler.test.scalalib") = { + + def isScalaLib(att: Attributed[File]) = { + att.metadata.get(moduleID.key).exists { mId => + mId.organization == "org.scala-lang" && + mId.name == "scala-library" && + mId.revision == scalaVersion.value + } + } + + val lib = (managedClasspath in Test).value.find(isScalaLib) + lib.map(_.data.getAbsolutePath).getOrElse { + streams.value.log.error("Couldn't find Scala library on the classpath. CP: " + (managedClasspath in Test).value); "" + } + } + }, + exportJars := true + ) + ) + + val commonToolsSettings = Seq( + name := "Scala.js tools", + + unmanagedSourceDirectories in Compile ++= Seq( + baseDirectory.value.getParentFile / "shared/src/main/scala", + (scalaSource in (irProject, Compile)).value), + + sourceGenerators in Compile <+= Def.task { + ScalaJSEnvGenerator.generateEnvHolder( + baseDirectory.value.getParentFile, + (sourceManaged in Compile).value) + } + ) + + lazy val tools: Project = Project( + id = "tools", + base = file("tools/jvm"), + settings = defaultSettings ++ publishSettings ++ ( + commonToolsSettings + ) ++ Seq( + scalaVersion := "2.10.4", + + libraryDependencies ++= Seq( + "com.google.javascript" % "closure-compiler" % "v20130603", + "com.googlecode.json-simple" % "json-simple" % "1.1.1", + "com.novocode" % "junit-interface" % "0.9" % "test" + ) + ) + ) + + lazy val toolsJS: Project = Project( + id = "toolsJS", + base = file("tools/js"), + settings = defaultSettings ++ myScalaJSSettings ++ publishSettings ++ ( + commonToolsSettings + ) ++ Seq( + crossVersion := ScalaJSCrossVersion.binary + ) ++ inConfig(Test) { + // Redefine test to run Node.js and link HelloWorld + test := { + if (scalaJSStage.value == Stage.PreLink) + error("Can't run toolsJS/test in preLink stage") + + val cp = { + for (e <- (fullClasspath in Test).value) + yield JSUtils.toJSstr(e.data.getAbsolutePath) + } + + val runCode = """ + var framework = org.scalajs.jasminetest.JasmineTestFramework(); + framework.setTags("typedarray") + + // Load tests + scalajs.TestDetector().loadDetectedTests(); + + var reporter = new scalajs.JasmineConsoleReporter(true); + + // Setup and run Jasmine + var jasmineEnv = jasmine.getEnv(); + jasmineEnv.addReporter(reporter); + jasmineEnv.execute(); + """ + + val code = { + s""" + var lib = scalajs.QuickLinker().linkNode(${cp.mkString(", ")}); + var run = ${JSUtils.toJSstr(runCode)}; + + eval("(function() { 'use strict'; "+ + "var __ScalaJSEnv = null; " + + lib + "; " + run + "}).call(this);"); + """ + } + + val launcher = new MemVirtualJSFile("Generated launcher file") + .withContent(code) + + val runner = jsEnv.value.jsRunner(scalaJSExecClasspath.value, + launcher, streams.value.log, scalaJSConsole.value) + + runner.run() + } + } + ).dependsOn(compiler % "plugin", javalibEx, testSuite % "test->test") + + lazy val plugin: Project = Project( + id = "sbtPlugin", + base = file("sbt-plugin"), + settings = commonSettings ++ publishSettings ++ Seq( + name := "Scala.js sbt plugin", + sbtPlugin := true, + scalaBinaryVersion := + CrossVersion.binaryScalaVersion(scalaVersion.value), + libraryDependencies ++= Seq( + "org.mozilla" % "rhino" % "1.7R4", + "org.webjars" % "envjs" % "1.2", + "com.novocode" % "junit-interface" % "0.9" % "test" + ) ++ ScalaJSPluginInternal.phantomJSJettyModules.map(_ % "provided") + ) + ).dependsOn(tools) + + lazy val delambdafySetting = { + scalacOptions ++= ( + if (scalaBinaryVersion.value == "2.10") Seq() + else Seq("-Ydelambdafy:method")) + } + + private def serializeHardcodedIR(base: File, + infoAndTree: (ir.Infos.ClassInfo, ir.Trees.ClassDef)): File = { + // We assume that there are no weird characters in the full name + val fullName = infoAndTree._1.name + val output = base / (fullName.replace('.', '/') + ".sjsir") + + if (!output.exists()) { + IO.createDirectory(output.getParentFile) + val stream = new BufferedOutputStream(new FileOutputStream(output)) + try { + ir.InfoSerializers.serialize(stream, infoAndTree._1) + ir.Serializers.serialize(stream, infoAndTree._2) + } finally { + stream.close() + } + } + output + } + + lazy val javalanglib: Project = Project( + id = "javalanglib", + base = file("javalanglib"), + settings = defaultSettings ++ myScalaJSSettings ++ Seq( + name := "java.lang library for Scala.js", + publishArtifact in Compile := false, + delambdafySetting, + scalacOptions += "-Yskip:cleanup,icode,jvm", + scalaJSSourceMapSettings, + compileWithLibrarySetting, + + resourceGenerators in Compile <+= Def.task { + val base = (resourceManaged in Compile).value + Seq( + serializeHardcodedIR(base, JavaLangObject.InfoAndTree), + serializeHardcodedIR(base, JavaLangString.InfoAndTree) + ) + } + ) ++ ( + scalaJSExternalCompileSettings + ) + ).dependsOn(compiler % "plugin") + + lazy val javalib: Project = Project( + id = "javalib", + base = file("javalib"), + settings = defaultSettings ++ myScalaJSSettings ++ Seq( + name := "Java library for Scala.js", + publishArtifact in Compile := false, + delambdafySetting, + scalacOptions += "-Yskip:cleanup,icode,jvm", + scalaJSSourceMapSettings, + compileWithLibrarySetting + ) ++ ( + scalaJSExternalCompileSettings + ) + ).dependsOn(compiler % "plugin") + + lazy val scalalib: Project = Project( + id = "scalalib", + base = file("scalalib"), + settings = defaultSettings ++ myScalaJSSettings ++ Seq( + name := "Scala library for Scala.js", + publishArtifact in Compile := false, + delambdafySetting, + compileWithLibrarySetting, + + // The Scala lib is full of warnings we don't want to see + scalacOptions ~= (_.filterNot( + Set("-deprecation", "-unchecked", "-feature") contains _)), + + + scalacOptions ++= List( + // Do not generate .class files + "-Yskip:cleanup,icode,jvm", + // Tell plugin to hack fix bad classOf trees + "-P:scalajs:fixClassOf", + // Link source maps to github sources of original Scalalib + "-P:scalajs:mapSourceURI:" + + fetchedScalaSourceDir.value.toURI + + "->https://raw.githubusercontent.com/scala/scala/v" + + scalaVersion.value + "/" + ), + + // Link sources in override directories to our GitHub repo + scalaJSSourceMapSettings, + + fetchedScalaSourceDir := ( + baseDirectory.value / "fetchedSources" / + scalaVersion.value + ), + + fetchScalaSource := { + val s = streams.value + val cacheDir = s.cacheDirectory + val ver = scalaVersion.value + val trgDir = fetchedScalaSourceDir.value + + val report = updateClassifiers.value + val scalaLibSourcesJar = report.select( + configuration = Set("compile"), + module = moduleFilter(name = "scala-library"), + artifact = artifactFilter(`type` = "src")).headOption.getOrElse { + sys.error(s"Could not fetch scala-library sources for version $ver") + } + + FileFunction.cached(cacheDir / s"fetchScalaSource-$ver", + FilesInfo.lastModified, FilesInfo.exists) { dependencies => + s.log.info(s"Fetching Scala library sources to $trgDir...") + + if (trgDir.exists) + IO.delete(trgDir) + IO.createDirectory(trgDir) + IO.unzip(scalaLibSourcesJar, trgDir) + } (Set(scalaLibSourcesJar)) + + trgDir + }, + + unmanagedSourceDirectories in Compile := { + // Calculates all prefixes of the current Scala version + // (including the empty prefix) to construct override + // directories like the following: + // - override-2.10.2-RC1 + // - override-2.10.2 + // - override-2.10 + // - override-2 + // - override + val ver = scalaVersion.value + val base = baseDirectory.value + val parts = ver.split(Array('.','-')) + val verList = parts.inits.map { ps => + val len = ps.mkString(".").length + // re-read version, since we lost '.' and '-' + ver.substring(0, len) + } + def dirStr(v: String) = + if (v.isEmpty) "overrides" else s"overrides-$v" + val dirs = verList.map(base / dirStr(_)).filter(_.exists) + dirs.toSeq // most specific shadow less specific + }, + + // Compute sources + // Files in earlier src dirs shadow files in later dirs + sources in Compile := { + // Sources coming from the sources of Scala + val scalaSrcDir = fetchScalaSource.value + + // All source directories (overrides shadow scalaSrcDir) + val sourceDirectories = + (unmanagedSourceDirectories in Compile).value :+ scalaSrcDir + + // Filter sources with overrides + def normPath(f: File): String = + f.getPath.replace(java.io.File.separator, "/") + + val sources = mutable.ListBuffer.empty[File] + val paths = mutable.Set.empty[String] + + for { + srcDir <- sourceDirectories + normSrcDir = normPath(srcDir) + src <- (srcDir ** "*.scala").get + } { + val normSrc = normPath(src) + val path = normSrc.substring(normSrcDir.length) + val useless = + path.contains("/scala/collection/parallel/") || + path.contains("/scala/util/parsing/") + if (!useless) { + if (paths.add(path)) + sources += src + else + streams.value.log.debug(s"not including $src") + } + } + + sources.result() + }, + + // Continuation plugin (when using 2.10.x) + autoCompilerPlugins := true, + libraryDependencies ++= { + val ver = scalaVersion.value + if (ver.startsWith("2.10.")) + Seq(compilerPlugin("org.scala-lang.plugins" % "continuations" % ver)) + else + Nil + }, + scalacOptions ++= { + if (scalaVersion.value.startsWith("2.10.")) + Seq("-P:continuations:enable") + else + Nil + } + ) ++ ( + scalaJSExternalCompileSettings + ) + ).dependsOn(compiler % "plugin") + + lazy val libraryAux: Project = Project( + id = "libraryAux", + base = file("library-aux"), + settings = defaultSettings ++ myScalaJSSettings ++ Seq( + name := "Scala.js aux library", + publishArtifact in Compile := false, + delambdafySetting, + scalacOptions += "-Yskip:cleanup,icode,jvm", + scalaJSSourceMapSettings, + compileWithLibrarySetting + ) ++ ( + scalaJSExternalCompileSettings + ) + ).dependsOn(compiler % "plugin") + + lazy val library: Project = Project( + id = "library", + base = file("library"), + settings = defaultSettings ++ publishSettings ++ myScalaJSSettings ++ Seq( + name := "Scala.js library", + delambdafySetting, + scalaJSSourceMapSettings, + scalacOptions in (Compile, doc) += "-implicits", + exportJars := true + ) ++ ( + scalaJSExternalCompileSettings + ) ++ inConfig(Compile)(Seq( + /* Add the .sjsir files from other lib projects + * (but not .class files) + */ + mappings in packageBin ++= { + val allProducts = ( + (products in javalanglib).value ++ + (products in javalib).value ++ + (products in scalalib).value ++ + (products in libraryAux).value) + val filter = ("*.sjsir": NameFilter) + allProducts.flatMap(base => Path.selectSubpaths(base, filter)) + } + )) + ).dependsOn(compiler % "plugin") + + lazy val javalibEx: Project = Project( + id = "javalibEx", + base = file("javalib-ex"), + settings = defaultSettings ++ publishSettings ++ myScalaJSSettings ++ Seq( + name := "Scala.js JavaLib Ex", + delambdafySetting, + scalacOptions += "-Yskip:cleanup,icode,jvm", + scalaJSSourceMapSettings, + exportJars := true, + jsDependencies += + "org.webjars" % "jszip" % "2.4.0" / "jszip.min.js" commonJSName "JSZip" + ) ++ ( + scalaJSExternalCompileSettings + ) + ).dependsOn(compiler % "plugin", library) + + lazy val stubs: Project = Project( + id = "stubs", + base = file("stubs"), + settings = defaultSettings ++ publishSettings ++ Seq( + name := "Scala.js Stubs" + ) + ) + + // Scala.js command line interface + lazy val cli: Project = Project( + id = "cli", + base = file("cli"), + settings = defaultSettings ++ assemblySettings ++ Seq( + name := "Scala.js CLI", + scalaVersion := "2.10.4", // adapt version to tools + libraryDependencies ++= Seq( + "com.github.scopt" %% "scopt" % "3.2.0" + ), + + // assembly options + mainClass in assembly := None, // don't want an executable JAR + assemblyOption in assembly ~= { _.copy(includeScala = false) }, + AssemblyKeys.jarName in assembly := + s"${normalizedName.value}-assembly_${scalaBinaryVersion.value}-${version.value}.jar" + ) + ).dependsOn(tools) + + // Test framework + lazy val testBridge = Project( + id = "testBridge", + base = file("test-bridge"), + settings = defaultSettings ++ publishSettings ++ myScalaJSSettings ++ Seq( + name := "Scala.js test bridge", + delambdafySetting, + scalaJSSourceMapSettings + ) + ).dependsOn(compiler % "plugin", library) + + lazy val jasmineTestFramework = Project( + id = "jasmineTestFramework", + base = file("jasmine-test-framework"), + settings = defaultSettings ++ publishSettings ++ myScalaJSSettings ++ Seq( + name := "Scala.js jasmine test framework", + + jsDependencies ++= Seq( + ProvidedJS / "jasmine-polyfills.js", + "org.webjars" % "jasmine" % "1.3.1" / + "jasmine.js" dependsOn "jasmine-polyfills.js" + ), + scalaJSSourceMapSettings + ) + ).dependsOn(compiler % "plugin", library, testBridge) + + // Examples + + lazy val examples: Project = Project( + id = "examples", + base = file("examples"), + settings = defaultSettings ++ Seq( + name := "Scala.js examples" + ) + ).aggregate(helloworld, reversi, testingExample) + + lazy val exampleSettings = defaultSettings ++ myScalaJSSettings + + lazy val helloworld: Project = Project( + id = "helloworld", + base = file("examples") / "helloworld", + settings = exampleSettings ++ Seq( + name := "Hello World - Scala.js example", + moduleName := "helloworld", + persistLauncher := true + ) + ).dependsOn(compiler % "plugin", library) + + lazy val reversi = Project( + id = "reversi", + base = file("examples") / "reversi", + settings = exampleSettings ++ Seq( + name := "Reversi - Scala.js example", + moduleName := "reversi" + ) + ).dependsOn(compiler % "plugin", library) + + lazy val testingExample = Project( + id = "testingExample", + base = file("examples") / "testing", + settings = exampleSettings ++ Seq( + name := "Testing - Scala.js example", + moduleName := "testing", + + jsDependencies ++= Seq( + RuntimeDOM % "test", + "org.webjars" % "jquery" % "1.10.2" / "jquery.js" % "test" + ) + ) + ).dependsOn(compiler % "plugin", library, jasmineTestFramework % "test") + + // Testing + + lazy val testSuite: Project = Project( + id = "testSuite", + base = file("test-suite"), + settings = defaultSettings ++ myScalaJSSettings ++ Seq( + name := "Scala.js test suite", + publishArtifact in Compile := false, + + scalacOptions ~= (_.filter(_ != "-deprecation")), + + sources in Test ++= { + if (!scalaVersion.value.startsWith("2.10") && + scalacOptions.value.contains("-Xexperimental")) { + (((sourceDirectory in Test).value / "require-sam") ** "*.scala").get + } else { + Nil + } + }, + + /* Generate a scala source file that throws exceptions in + various places (while attaching the source line to the + exception). When we catch the exception, we can then + compare the attached source line and the source line + calculated via the source maps. + + see test-suite/src/test/resources/SourceMapTestTemplate.scala + */ + sourceGenerators in Test <+= Def.task { + val dir = (sourceManaged in Test).value + IO.createDirectory(dir) + + val template = IO.read((resourceDirectory in Test).value / + "SourceMapTestTemplate.scala") + + def lineNo(cs: CharSequence) = + (0 until cs.length).count(i => cs.charAt(i) == '\n') + 1 + + var i = 0 + val pat = "/\\*{2,3}/".r + val replaced = pat.replaceAllIn(template, { mat => + val lNo = lineNo(mat.before) + val res = + if (mat.end - mat.start == 5) + // matching a /***/ + s"if (TC.is($i)) { throw new TestException($lNo) } else " + else + // matching a /**/ + s"; if (TC.is($i)) { throw new TestException($lNo) } ;" + + i += 1 + + res + }) + + val outFile = dir / "SourceMapTest.scala" + IO.write(outFile, replaced.replace("0/*<testCount>*/", i.toString)) + Seq(outFile) + } + ) + ).dependsOn(compiler % "plugin", library, jasmineTestFramework % "test") + + lazy val noIrCheckTest: Project = Project( + id = "noIrCheckTest", + base = file("no-ir-check-test"), + settings = defaultSettings ++ myScalaJSSettings ++ Seq( + name := "Scala.js not IR checked tests", + scalaJSOptimizerOptions ~= (_.withCheckScalaJSIR(false)), + publishArtifact in Compile := false + ) + ).dependsOn(compiler % "plugin", library, jasmineTestFramework % "test") + + lazy val javalibExTestSuite: Project = Project( + id = "javalibExTestSuite", + base = file("javalib-ex-test-suite"), + settings = defaultSettings ++ myScalaJSSettings ++ Seq( + name := "JavaLib Ex Test Suite", + publishArtifact in Compile := false, + + scalacOptions in Test ~= (_.filter(_ != "-deprecation")) + ) + ).dependsOn(compiler % "plugin", javalibEx, jasmineTestFramework % "test") + + lazy val partest: Project = Project( + id = "partest", + base = file("partest"), + settings = defaultSettings ++ Seq( + name := "Partest for Scala.js", + moduleName := "scalajs-partest", + + resolvers += Resolver.typesafeIvyRepo("releases"), + + fetchedScalaSourceDir := ( + baseDirectory.value / "fetchedSources" / + scalaVersion.value + ), + + fetchScalaSource := { + import org.eclipse.jgit.api._ + + val s = streams.value + val ver = scalaVersion.value + val trgDir = fetchedScalaSourceDir.value + + if (!trgDir.exists) { + s.log.info(s"Fetching Scala source version $ver") + + // Make parent dirs and stuff + IO.createDirectory(trgDir) + + // Clone scala source code + new CloneCommand() + .setDirectory(trgDir) + .setURI("https://github.com/scala/scala.git") + .call() + } + + // Checkout proper ref. We do this anyway so we fail if + // something is wrong + val git = Git.open(trgDir) + s.log.info(s"Checking out Scala source version $ver") + git.checkout().setName(s"v$ver").call() + + trgDir + }, + + libraryDependencies ++= { + if (shouldPartest.value) + Seq( + "org.scala-sbt" % "sbt" % sbtVersion.value, + "org.scala-lang.modules" %% "scala-partest" % "1.0.1", + "com.google.javascript" % "closure-compiler" % "v20130603", + "org.mozilla" % "rhino" % "1.7R4", + "com.googlecode.json-simple" % "json-simple" % "1.1.1" + ) + else Seq() + }, + + sources in Compile := { + if (shouldPartest.value) { + // Partest sources and some sources of sbtplugin (see above) + val baseSrcs = (sources in Compile).value + // Sources for tools (and hence IR) + val toolSrcs = (sources in (tools, Compile)).value + // Individual sources from the sbtplugin + val pluginSrcs = { + val pluginBase = ((scalaSource in (plugin, Compile)).value / + "scala/scalajs/sbtplugin") + + val scalaFilter: FileFilter = "*.scala" + val files = ( + (pluginBase * "JSUtils.scala") +++ + (pluginBase / "env" * scalaFilter) +++ + (pluginBase / "env" / "nodejs" ** scalaFilter) +++ + (pluginBase / "env" / "rhino" ** scalaFilter)) + + files.get + } + toolSrcs ++ baseSrcs ++ pluginSrcs + } else Seq() + } + + ) + ).dependsOn(compiler) + + lazy val partestSuite: Project = Project( + id = "partestSuite", + base = file("partest-suite"), + settings = defaultSettings ++ Seq( + name := "Scala.js partest suite", + + fork in Test := true, + javaOptions in Test += "-Xmx1G", + + testFrameworks ++= { + if (shouldPartest.value) + Seq(new TestFramework("scala.tools.partest.scalajs.Framework")) + else Seq() + }, + + definedTests in Test <++= Def.taskDyn[Seq[sbt.TestDefinition]] { + if (shouldPartest.value) Def.task { + val _ = (fetchScalaSource in partest).value + Seq(new sbt.TestDefinition( + s"partest-${scalaVersion.value}", + // marker fingerprint since there are no test classes + // to be discovered by sbt: + new sbt.testing.AnnotatedFingerprint { + def isModule = true + def annotationName = "partest" + }, + true, + Array() + )) + } else { + Def.task(Seq()) + } + } + ) + ).dependsOn(partest % "test", library) +} diff --git a/examples/scala-js/project/build.properties b/examples/scala-js/project/build.properties new file mode 100644 index 0000000..64abd37 --- /dev/null +++ b/examples/scala-js/project/build.properties @@ -0,0 +1 @@ +sbt.version=0.13.6 diff --git a/examples/scala-js/project/build.sbt b/examples/scala-js/project/build.sbt new file mode 100644 index 0000000..b110ef2 --- /dev/null +++ b/examples/scala-js/project/build.sbt @@ -0,0 +1,48 @@ +resolvers += Resolver.url( + "bintray-sbt-plugin-releases", + url("http://dl.bintray.com/content/sbt/sbt-plugin-releases"))( + Resolver.ivyStylePatterns) + +addSbtPlugin("me.lessis" % "bintray-sbt" % "0.1.2") + +addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.11.2") + +libraryDependencies += "com.google.javascript" % "closure-compiler" % "v20130603" + +libraryDependencies += "org.mozilla" % "rhino" % "1.7R4" + +libraryDependencies += "org.webjars" % "envjs" % "1.2" + +libraryDependencies += "org.eclipse.jgit" % "org.eclipse.jgit.pgm" % "3.2.0.201312181205-r" + +libraryDependencies += "com.googlecode.json-simple" % "json-simple" % "1.1.1" + +libraryDependencies += "org.eclipse.jetty" % "jetty-websocket" % "8.1.16.v20140903" + +libraryDependencies += "org.eclipse.jetty" % "jetty-server" % "8.1.16.v20140903" + + +unmanagedSourceDirectories in Compile ++= { + val root = baseDirectory.value.getParentFile + Seq( + root / "ir/src/main/scala", + root / "tools/shared/src/main/scala", + root / "tools/jvm/src/main/scala", + root / "sbt-plugin/src/main/scala" + ) +} + +// Add the ScalaJSEnvGenerator to the build (its in the build of the build) +sources in Compile += + baseDirectory.value / "project" / "ScalaJSEnvGenerator.scala" + +sourceGenerators in Compile <+= Def.task { + ScalaJSEnvGenerator.generateEnvHolder( + baseDirectory.value.getParentFile / "tools", + (sourceManaged in Compile).value) +} + +unmanagedResourceDirectories in Compile += { + val root = baseDirectory.value.getParentFile + root / "tools/src/main/resources" +} diff --git a/examples/scala-js/project/project/ScalaJSEnvGenerator.scala b/examples/scala-js/project/project/ScalaJSEnvGenerator.scala new file mode 100644 index 0000000..a36479f --- /dev/null +++ b/examples/scala-js/project/project/ScalaJSEnvGenerator.scala @@ -0,0 +1,31 @@ +import sbt._ + +object ScalaJSEnvGenerator { + + /** Generate a *.scala file that contains the scalajsenv as literal string + * + * We need this so the tools don't rely on I/O and/or resources. + */ + def generateEnvHolder(baseDir: File, sourceDir: File): Seq[File] = { + val trg = sourceDir / "ScalaJSEnvHolder.scala" + val env = baseDir / "scalajsenv.js" + + if (!trg.exists() || trg.lastModified() < env.lastModified()) { + val scalajsenv = IO.read(env).replaceAllLiterally("$", "$$") + + val scalaCode = + s""" + package scala.scalajs.tools.corelib + + private[corelib] object ScalaJSEnvHolder { + final val scalajsenv = raw\"\"\"$scalajsenv\"\"\" + } + """ + + IO.write(trg, scalaCode) + } + + Seq(trg) + } + +} diff --git a/examples/scala-js/sbt-plugin-test/README.md b/examples/scala-js/sbt-plugin-test/README.md new file mode 100644 index 0000000..71414a3 --- /dev/null +++ b/examples/scala-js/sbt-plugin-test/README.md @@ -0,0 +1,7 @@ +This is a standalone SBT project to test the Scala.js SBT plugin as it +will be used by the users. (Through normal SBT dependency +management). This needs the Scala.js artifacts to be published +locally. + +It has two subprojects, to test Scala.js with and without DOM. Both +subprojects have a main class and a test. diff --git a/examples/scala-js/sbt-plugin-test/build.sbt b/examples/scala-js/sbt-plugin-test/build.sbt new file mode 100644 index 0000000..dd2e12e --- /dev/null +++ b/examples/scala-js/sbt-plugin-test/build.sbt @@ -0,0 +1,44 @@ +import scala.scalajs.sbtplugin.RuntimeDOM + +name := "Scala.js sbt test" + +version := scalaJSVersion + +val baseSettings = Seq( + version := scalaJSVersion, + scalaVersion := "2.11.2", + libraryDependencies += + "org.scala-lang.modules.scalajs" %% "scalajs-jasmine-test-framework" % scalaJSVersion % "test" +) + +lazy val root = project.in(file(".")). + aggregate(noDOM, withDOM) + +lazy val noDOM = project.settings(baseSettings: _*). + enablePlugins(ScalaJSPlugin). + settings( + name := "Scala.js sbt test w/o DOM" + ) + +lazy val withDOM = project.settings(baseSettings: _*). + enablePlugins(ScalaJSPlugin). + settings( + name := "Scala.js sbt test w/ DOM", + jsDependencies ++= Seq( + RuntimeDOM, + "org.webjars" % "jquery" % "1.10.2" / "jquery.js" + ) + ) + +lazy val jetty9 = project.settings(baseSettings: _*). + enablePlugins(ScalaJSPlugin). + settings( + name := "Scala.js sbt test with jetty9 on classpath", + jsDependencies ++= Seq( + RuntimeDOM, + "org.webjars" % "jquery" % "1.10.2" / "jquery.js" + ), + // Use PhantomJS, allow cross domain requests + postLinkJSEnv := PhantomJSEnv(args = Seq("--web-security=no")).value, + Jetty9Test.runSetting + ) diff --git a/examples/scala-js/sbt-plugin-test/jetty9/src/main/resources/test.txt b/examples/scala-js/sbt-plugin-test/jetty9/src/main/resources/test.txt new file mode 100644 index 0000000..68300b8 --- /dev/null +++ b/examples/scala-js/sbt-plugin-test/jetty9/src/main/resources/test.txt @@ -0,0 +1 @@ +It works! diff --git a/examples/scala-js/sbt-plugin-test/noDOM/src/main/scala/sbttest/noDOM/Lib.scala b/examples/scala-js/sbt-plugin-test/noDOM/src/main/scala/sbttest/noDOM/Lib.scala new file mode 100644 index 0000000..884c422 --- /dev/null +++ b/examples/scala-js/sbt-plugin-test/noDOM/src/main/scala/sbttest/noDOM/Lib.scala @@ -0,0 +1,11 @@ +package sbttest.noDOM + +object Lib { + + /** appends `_foo` to a string */ + def foo(x: String): String = x + "foo" + + /** squares a number */ + def sq(x: Int): Int = x * x + +} diff --git a/examples/scala-js/sbt-plugin-test/noDOM/src/main/scala/sbttest/noDOM/TestApp.scala b/examples/scala-js/sbt-plugin-test/noDOM/src/main/scala/sbttest/noDOM/TestApp.scala new file mode 100644 index 0000000..16a4cbe --- /dev/null +++ b/examples/scala-js/sbt-plugin-test/noDOM/src/main/scala/sbttest/noDOM/TestApp.scala @@ -0,0 +1,12 @@ +package sbttest.noDOM + +import scala.scalajs.js + +object TestApp extends js.JSApp { + + def main(): Unit = { + println(Lib.foo("Hello World")) + println(Lib.sq(10)) + } + +} diff --git a/examples/scala-js/sbt-plugin-test/noDOM/src/test/scala/sbttest/noDOM/LibTest.scala b/examples/scala-js/sbt-plugin-test/noDOM/src/test/scala/sbttest/noDOM/LibTest.scala new file mode 100644 index 0000000..bc24eda --- /dev/null +++ b/examples/scala-js/sbt-plugin-test/noDOM/src/test/scala/sbttest/noDOM/LibTest.scala @@ -0,0 +1,19 @@ +package sbttest.noDOM + +import org.scalajs.jasminetest.JasmineTest + +object LibTest extends JasmineTest { + + describe("Dummy Library") { + it("should provide `foo`") { + expect(Lib.foo("")).toEqual("foo") + expect(Lib.foo("a")).toEqual("afoo") + } + + it("should provide `sq`") { + expect(Lib.sq(0)).toEqual(0) + expect(Lib.sq(10)).toEqual(100) + } + } + +} diff --git a/examples/scala-js/sbt-plugin-test/project/Jetty9Test.scala b/examples/scala-js/sbt-plugin-test/project/Jetty9Test.scala new file mode 100644 index 0000000..6a84114 --- /dev/null +++ b/examples/scala-js/sbt-plugin-test/project/Jetty9Test.scala @@ -0,0 +1,83 @@ +import sbt._ +import Keys._ + +import scala.scalajs.sbtplugin._ +import ScalaJSPlugin.autoImport._ +import Implicits._ + +import scala.scalajs.tools.env._ +import scala.scalajs.tools.io._ + +import org.eclipse.jetty.server._ +import org.eclipse.jetty.server.handler._ +import org.eclipse.jetty.util.component._ + +import java.io.File + +object Jetty9Test { + + private val jettyPort = 23548 + + val runSetting = run <<= Def.inputTask { + val env = (jsEnv in Compile).value.asInstanceOf[ComJSEnv] + val cp = (scalaJSExecClasspath in Compile).value + val jsConsole = scalaJSConsole.value + + val code = new MemVirtualJSFile("runner.js").withContent( + """ + scalajsCom.init(function(msg) { + jQuery.ajax({ + url: msg, + success: function(dat) { + scalajsCom.send(dat.trim()); + scalajsCom.close(); + }, + error: function() { + scalajsCom.send("failed!"); + scalajsCom.close(); + } + }); + }); + """ + ) + + val runner = env.comRunner(cp, code, streams.value.log, jsConsole) + + runner.start() + + val jetty = setupJetty((resourceDirectory in Compile).value) + + jetty.addLifeCycleListener(new AbstractLifeCycle.AbstractLifeCycleListener { + override def lifeCycleStarted(event: LifeCycle): Unit = { + try { + runner.send(s"http://localhost:$jettyPort/test.txt") + val msg = runner.receive() + val expected = "It works!" + if (msg != expected) + sys.error(s"""received "$msg" instead of "$expected"""") + } finally { + runner.close() + jetty.stop() + } + } + }) + + jetty.start() + runner.await() + jetty.join() + } + + private def setupJetty(dir: File): Server = { + val server = new Server(jettyPort) + + val resource_handler = new ResourceHandler() + resource_handler.setResourceBase(dir.getAbsolutePath) + + val handlers = new HandlerList() + handlers.setHandlers(Array(resource_handler, new DefaultHandler())) + server.setHandler(handlers) + + server + } + +} diff --git a/examples/scala-js/sbt-plugin-test/project/build.properties b/examples/scala-js/sbt-plugin-test/project/build.properties new file mode 100644 index 0000000..64abd37 --- /dev/null +++ b/examples/scala-js/sbt-plugin-test/project/build.properties @@ -0,0 +1 @@ +sbt.version=0.13.6 diff --git a/examples/scala-js/sbt-plugin-test/project/build.sbt b/examples/scala-js/sbt-plugin-test/project/build.sbt new file mode 100644 index 0000000..8419289 --- /dev/null +++ b/examples/scala-js/sbt-plugin-test/project/build.sbt @@ -0,0 +1,4 @@ +addSbtPlugin("org.scala-lang.modules.scalajs" % "scalajs-sbt-plugin" % + scala.scalajs.ir.ScalaJSVersions.current) + +libraryDependencies += "org.eclipse.jetty" % "jetty-server" % "9.2.3.v20140905" diff --git a/examples/scala-js/sbt-plugin-test/project/project/build.sbt b/examples/scala-js/sbt-plugin-test/project/project/build.sbt new file mode 100644 index 0000000..fb20cb7 --- /dev/null +++ b/examples/scala-js/sbt-plugin-test/project/project/build.sbt @@ -0,0 +1 @@ +sources in Compile += baseDirectory.value / "../../../ir/src/main/scala/scala/scalajs/ir/ScalaJSVersions.scala" diff --git a/examples/scala-js/sbt-plugin-test/withDOM/src/main/scala/sbttest/withDOM/Lib.scala b/examples/scala-js/sbt-plugin-test/withDOM/src/main/scala/sbttest/withDOM/Lib.scala new file mode 100644 index 0000000..e431557 --- /dev/null +++ b/examples/scala-js/sbt-plugin-test/withDOM/src/main/scala/sbttest/withDOM/Lib.scala @@ -0,0 +1,28 @@ +package sbttest.withDOM + +import scala.scalajs.js + +object Lib { + + val document: js.Dynamic = js.Dynamic.global.document + val jQuery: js.Dynamic = js.Dynamic.global.jQuery + + def getElementsByTagName(name: String): js.Array[js.Dynamic] = + document.getElementsByTagName(name).asInstanceOf[js.Array[js.Dynamic]] + + /** appends a <p> with the message to the document */ + def appendDocument(msg: String): Unit = { + val trg = { + val bodies = getElementsByTagName("body") + if (bodies.length > 0) + bodies(0) + else + document + } + + val elem = document.createElement("p") + elem.appendChild(document.createTextNode(msg)) + trg.appendChild(elem) + } + +} diff --git a/examples/scala-js/sbt-plugin-test/withDOM/src/main/scala/sbttest/withDOM/TestApp.scala b/examples/scala-js/sbt-plugin-test/withDOM/src/main/scala/sbttest/withDOM/TestApp.scala new file mode 100644 index 0000000..e61ed20 --- /dev/null +++ b/examples/scala-js/sbt-plugin-test/withDOM/src/main/scala/sbttest/withDOM/TestApp.scala @@ -0,0 +1,14 @@ +package sbttest.withDOM + +import scala.scalajs.js + +object TestApp extends js.JSApp { + + def main(): Unit = { + Lib.appendDocument("Hello World") + Lib.appendDocument("Still Here!") + + println(Lib.jQuery("p").text()) + } + +} diff --git a/examples/scala-js/sbt-plugin-test/withDOM/src/test/scala/sbttest/withDOM/LibTest.scala b/examples/scala-js/sbt-plugin-test/withDOM/src/test/scala/sbttest/withDOM/LibTest.scala new file mode 100644 index 0000000..e08e6e3 --- /dev/null +++ b/examples/scala-js/sbt-plugin-test/withDOM/src/test/scala/sbttest/withDOM/LibTest.scala @@ -0,0 +1,24 @@ +package sbttest.withDOM + +import scala.scalajs.js + +import org.scalajs.jasminetest.JasmineTest + +object LibTest extends JasmineTest { + + describe("Dummy Library") { + + it("should provide jQuery") { + expect(Lib.jQuery).toBeDefined + } + + it("should append an element") { + def count = Lib.jQuery("p").length.asInstanceOf[Int] + val oldCount = count + Lib.appendDocument("foo") + expect(count - oldCount).toEqual(1) + } + + } + +} diff --git a/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/AbstractJSDeps.scala b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/AbstractJSDeps.scala new file mode 100644 index 0000000..9eb7f69 --- /dev/null +++ b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/AbstractJSDeps.scala @@ -0,0 +1,81 @@ +package scala.scalajs.sbtplugin + +import sbt._ + +import StringUtilities.nonEmpty + +import scala.scalajs.tools.jsdep.JSDependency + +/** Something JavaScript related a project may depend on. Either a JavaScript + * module/library, or the DOM at runtime. */ +sealed trait AbstractJSDep { + def configurations: Option[String] + + protected def withConfigs(configs: Option[String]): AbstractJSDep + + def %(configurations: Configuration): AbstractJSDep = %(configurations.name) + def %(configurations: String): AbstractJSDep = { + require(this.configurations.isEmpty, + "Configurations already specified for jsModule " + this) + nonEmpty(configurations, "Configurations") + withConfigs(Some(configurations)) + } + +} + +/** A JavaScript module/library a Scala.js project may depend on */ +sealed trait JSModuleID extends AbstractJSDep { + def jsDep: JSDependency + + protected def withJSDep(jsDep: JSDependency): JSModuleID + + def commonJSName(name: String): JSModuleID = + withJSDep(jsDep = jsDep.commonJSName(name)) + + def dependsOn(names: String*): JSModuleID = + withJSDep(jsDep = jsDep.dependsOn(names: _*)) +} + +/** A JavaScript module that resides inside a jar (probably webjar) */ +final case class JarJSModuleID( + module: ModuleID, + jsDep: JSDependency) extends JSModuleID { + + def configurations: Option[String] = module.configurations + + protected def withConfigs(configs: Option[String]): JSModuleID = + copy(module = module.copy(configurations = configs)) + protected def withJSDep(jsDep: JSDependency): JSModuleID = + copy(jsDep = jsDep) +} + +object JarJSModuleID { + def apply(module: ModuleID, name: String): JarJSModuleID = + JarJSModuleID(module, new JSDependency(name, Nil)) +} + +/** A JavaScript module that we depend on, but is provided externally or + * by the project itself */ +final case class ProvidedJSModuleID( + jsDep: JSDependency, + configurations: Option[String]) extends JSModuleID { + + protected def withConfigs(configs: Option[String]): JSModuleID = + copy(configurations = configs) + protected def withJSDep(jsDep: JSDependency): JSModuleID = + copy(jsDep = jsDep) +} + +object ProvidedJSModuleID { + def apply(name: String, configurations: Option[String]): ProvidedJSModuleID = + ProvidedJSModuleID(new JSDependency(name, Nil), configurations) +} + +sealed case class RuntimeDOM( + configurations: Option[String]) extends AbstractJSDep { + + protected def withConfigs(configs: Option[String]): RuntimeDOM = + copy(configurations = configs) +} + +object RuntimeDOM extends RuntimeDOM(None) diff --git a/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/Implicits.scala b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/Implicits.scala new file mode 100644 index 0000000..0c1559f --- /dev/null +++ b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/Implicits.scala @@ -0,0 +1,34 @@ +package scala.scalajs.sbtplugin + +import scala.language.implicitConversions + +import scala.scalajs.tools.logging._ +import sbt.{Logger => SbtLogger, Level => SbtLevel} + +object Implicits { + private class SbtLoggerWrapper(underlying: SbtLogger) extends Logger { + def log(level: Level, message: => String): Unit = + underlying.log(level, message) + def success(message: => String): Unit = + underlying.success(message) + def trace(t: => Throwable): Unit = + underlying.trace(t) + } + + implicit def sbtLogger2ToolsLogger(logger: SbtLogger): Logger = + new SbtLoggerWrapper(logger) + + implicit def sbtLevel2ToolsLevel(level: SbtLevel.Value): Level = level match { + case SbtLevel.Error => Level.Error + case SbtLevel.Warn => Level.Warn + case SbtLevel.Info => Level.Info + case SbtLevel.Debug => Level.Debug + } + + implicit def toolsLevel2sbtLevel(level: Level): SbtLevel.Value = level match { + case Level.Error => SbtLevel.Error + case Level.Warn => SbtLevel.Warn + case Level.Info => SbtLevel.Info + case Level.Debug => SbtLevel.Debug + } +} diff --git a/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/JSUtils.scala b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/JSUtils.scala new file mode 100644 index 0000000..a59f105 --- /dev/null +++ b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/JSUtils.scala @@ -0,0 +1,35 @@ +package scala.scalajs.sbtplugin + +object JSUtils { + def listToJS(xs: List[String]): String = + xs.map(toJSstr _).mkString("[",",","]") + + /** (almost) stolen from scala.scalajs.compiler.JSPrinters */ + def toJSstr(str: String): String = { + /* Note that Java and JavaScript happen to use the same encoding for + * Unicode, namely UTF-16, which means that 1 char from Java always equals + * 1 char in JavaScript. */ + val builder = new StringBuilder() + builder.append('"') + str foreach { + case '\\' => builder.append("\\\\") + case '"' => builder.append("\\\"") + case '\u0007' => builder.append("\\a") + case '\u0008' => builder.append("\\b") + case '\u0009' => builder.append("\\t") + case '\u000A' => builder.append("\\n") + case '\u000B' => builder.append("\\v") + case '\u000C' => builder.append("\\f") + case '\u000D' => builder.append("\\r") + case c => + if (c >= 32 && c <= 126) builder.append(c.toChar) // ASCII printable characters + else builder.append(f"\\u$c%04x") + } + builder.append('"') + builder.result() + } + + def dot2bracket(name: String): String = { + name.split('.').map(s => s"""[${toJSstr(s)}]""").mkString + } +} diff --git a/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/LoggerJSConsole.scala b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/LoggerJSConsole.scala new file mode 100644 index 0000000..ecfb546 --- /dev/null +++ b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/LoggerJSConsole.scala @@ -0,0 +1,18 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js sbt plugin ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.sbtplugin + +import sbt.Logger +import scala.scalajs.tools.env.JSConsole + +/** A proxy for a Logger that looks like a Mozilla console object */ +class LoggerJSConsole(logger: Logger) extends JSConsole { + def log(msg: Any): Unit = logger.info(msg.toString) +} diff --git a/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/OptimizerOptions.scala b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/OptimizerOptions.scala new file mode 100644 index 0000000..25d6178 --- /dev/null +++ b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/OptimizerOptions.scala @@ -0,0 +1,74 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js sbt plugin ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.sbtplugin + +import OptimizerOptions._ + +/** Various options for the Scala.js optimizer tool chain + * + * This is not a case class and does have a private constructor so that we + * can add fields in a binary-compatible manner. + * + * Use [[OptimizerOptions.apply]] and the `with` methods to create a configured + * instance. + */ +final class OptimizerOptions private ( + /** Whether to parallelize the optimizer (currently fastOptJS only) **/ + val parallel: Boolean = true, + /** Whether to run the optimizer in batch (i.e. non-incremental) mode */ + val batchMode: Boolean = false, + /** Whether to run the Scala.js optimizer */ + val disableOptimizer: Boolean = false, + /** Whether to pretty-print in fullOptJS */ + val prettyPrintFullOptJS: Boolean = false, + /** Perform expensive checks of the sanity of the Scala.js IR */ + val checkScalaJSIR: Boolean = false +) { + + def withParallel(parallel: Boolean): OptimizerOptions = { + new OptimizerOptions(parallel, batchMode, + disableOptimizer, prettyPrintFullOptJS, checkScalaJSIR) + } + + def withBatchMode(batchMode: Boolean): OptimizerOptions = { + new OptimizerOptions(parallel, batchMode, + disableOptimizer, prettyPrintFullOptJS, checkScalaJSIR) + } + + def withDisableOptimizer(disableOptimizer: Boolean): OptimizerOptions = { + new OptimizerOptions(parallel, batchMode, + disableOptimizer, prettyPrintFullOptJS, checkScalaJSIR) + } + + def withPrettyPrintFullOptJS(prettyPrintFullOptJS: Boolean): OptimizerOptions = { + new OptimizerOptions(parallel, batchMode, + disableOptimizer, prettyPrintFullOptJS, checkScalaJSIR) + } + + def withCheckScalaJSIR(checkScalaJSIR: Boolean): OptimizerOptions = { + new OptimizerOptions(parallel, batchMode, + disableOptimizer, prettyPrintFullOptJS, checkScalaJSIR) + } + + override def toString: String = { + s"""OptimizerOptions( + | parallel = $parallel + | batchMode = $batchMode + | disableOptimizer = $disableOptimizer + | prettyPrintFullOptJS = $prettyPrintFullOptJS + | checkScalaJSIR = $checkScalaJSIR + |)""".stripMargin + } + +} + +object OptimizerOptions { + def apply() = new OptimizerOptions() +} diff --git a/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/ScalaJSCrossVersion.scala b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/ScalaJSCrossVersion.scala new file mode 100644 index 0000000..d813622 --- /dev/null +++ b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/ScalaJSCrossVersion.scala @@ -0,0 +1,48 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js sbt plugin ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.sbtplugin + +import sbt._ + +import scala.scalajs.ir.ScalaJSVersions + +object ScalaJSCrossVersion { + private val scalaJSVersionUnmapped: String => String = + _ => s"sjs$currentBinaryVersion" + + private val scalaJSVersionMap: String => String = + version => s"sjs${currentBinaryVersion}_$version" + + private final val ReleaseVersion = + raw"""(\d+)\.(\d+)\.(\d+)""".r + private final val MinorSnapshotVersion = + raw"""(\d+)\.(\d+)\.([1-9]\d*)-SNAPSHOT""".r + + val currentBinaryVersion = binaryScalaJSVersion(ScalaJSVersions.current) + + def binaryScalaJSVersion(full: String): String = full match { + case ReleaseVersion(major, minor, release) => s"$major.$minor" + case MinorSnapshotVersion(major, minor, _) => s"$major.$minor" + case _ => full + } + + def scalaJSMapped(cross: CrossVersion): CrossVersion = cross match { + case CrossVersion.Disabled => + CrossVersion.binaryMapped(scalaJSVersionUnmapped) + case cross: CrossVersion.Binary => + CrossVersion.binaryMapped(cross.remapVersion andThen scalaJSVersionMap) + case cross: CrossVersion.Full => + CrossVersion.fullMapped(cross.remapVersion andThen scalaJSVersionMap) + } + + def binary: CrossVersion = scalaJSMapped(CrossVersion.binary) + + def full: CrossVersion = scalaJSMapped(CrossVersion.full) +} diff --git a/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/ScalaJSPlugin.scala b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/ScalaJSPlugin.scala new file mode 100644 index 0000000..b33e2fb --- /dev/null +++ b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/ScalaJSPlugin.scala @@ -0,0 +1,179 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js sbt plugin ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.sbtplugin + +import sbt._ + +import scala.scalajs.tools.sem.Semantics +import scala.scalajs.tools.classpath._ +import scala.scalajs.tools.io.VirtualJSFile +import scala.scalajs.tools.env.{JSEnv, JSConsole} +import scala.scalajs.tools.optimizer.ScalaJSOptimizer + +import scala.scalajs.ir.ScalaJSVersions + +import scala.scalajs.sbtplugin.env.nodejs.NodeJSEnv +import scala.scalajs.sbtplugin.env.phantomjs.PhantomJSEnv + +object ScalaJSPlugin extends AutoPlugin { + override def requires = plugins.JvmPlugin + + object autoImport extends impl.DependencyBuilders { + import KeyRanks._ + + // Some constants + val scalaJSVersion = ScalaJSVersions.current + val scalaJSIsSnapshotVersion = ScalaJSVersions.currentIsSnapshot + val scalaJSBinaryVersion = ScalaJSCrossVersion.currentBinaryVersion + + // Stage values + val PreLinkStage = Stage.PreLink + val FastOptStage = Stage.FastOpt + val FullOptStage = Stage.FullOpt + + // Factory methods for JSEnvs + + /** + * Creates a [[Def.Initialize]] for a NodeJSEnv. Use this to explicitly + * specify in your build that you would like to run with Node.js: + * + * {{{ + * postLinkJSEnv := NodeJSEnv().value + * }}} + * + * Note that the resulting [[Setting]] is not scoped at all, but must be + * scoped in a project that has the ScalaJSPlugin enabled to work properly. + * Therefore, either put the upper line in your project settings (common + * case) or scope it manually, using [[Project.inScope]]. + */ + def NodeJSEnv( + executable: String = "node", + args: Seq[String] = Seq.empty, + env: Map[String, String] = Map.empty + ): Def.Initialize[Task[NodeJSEnv]] = Def.task { + new NodeJSEnv(executable, args, env) + } + + /** + * Creates a [[Def.Initialize]] for a PhantomJSEnv. Use this to explicitly + * specify in your build that you would like to run with PhantomJS: + * + * {{{ + * postLinkJSEnv := PhantomJSEnv().value + * }}} + * + * Note that the resulting [[Setting]] is not scoped at all, but must be + * scoped in a project that has the ScalaJSPlugin enabled to work properly. + * Therefore, either put the upper line in your project settings (common + * case) or scope it manually, using [[Project.inScope]]. + */ + def PhantomJSEnv( + executable: String = "phantomjs", + args: Seq[String] = Seq.empty, + env: Map[String, String] = Map.empty, + autoExit: Boolean = true + ): Def.Initialize[Task[PhantomJSEnv]] = Def.task { + val loader = scalaJSPhantomJSClassLoader.value + new PhantomJSEnv(executable, args, env, autoExit, loader) + } + + // All our public-facing keys + + val fastOptJS = TaskKey[Attributed[File]]("fastOptJS", + "Quickly link all compiled JavaScript into a single file", APlusTask) + val fullOptJS = TaskKey[Attributed[File]]("fullOptJS", + "Link all compiled JavaScript into a single file and fully optimize", APlusTask) + + val scalaJSStage = SettingKey[Stage]("scalaJSStage", + "The optimization stage at which run and test are executed", APlusSetting) + + val packageScalaJSLauncher = TaskKey[Attributed[File]]("packageScalaJSLauncher", + "Writes the persistent launcher file. Fails if the mainClass is ambigous", CTask) + + val packageJSDependencies = TaskKey[File]("packageJSDependencies", + "Packages all dependencies of the preLink classpath in a single file. " + + "Set skip in packageJSDependencies := false to run automatically", AMinusTask) + + val jsDependencyManifest = TaskKey[File]("jsDependencyManifest", + "Writes the JS_DEPENDENCIES file.", DTask) + + val scalaJSPreLinkClasspath = TaskKey[IRClasspath]("scalaJSPreLinkClasspath", + "Completely resolved classpath just after compilation", DTask) + + val scalaJSExecClasspath = TaskKey[CompleteClasspath]("scalaJSExecClasspath", + "The classpath used for running and testing", DTask) + + val scalaJSLauncher = TaskKey[Attributed[VirtualJSFile]]("scalaJSLauncher", + "Code used to run. (Attributed with used class name)", DTask) + + val scalaJSConsole = TaskKey[JSConsole]("scalaJSConsole", + "The JS console used by the Scala.js runner/tester", DTask) + + val preLinkJSEnv = TaskKey[JSEnv]("preLinkJSEnv", + "The jsEnv used to execute before linking (packaging / optimizing) Scala.js files", BSetting) + val postLinkJSEnv = TaskKey[JSEnv]("postLinkJSEnv", + "The jsEnv used to execute after linking (packaging / optimizing) Scala.js files", AMinusSetting) + + val jsEnv = TaskKey[JSEnv]("jsEnv", + "A JVM-like environment where Scala.js files can be run and tested", DTask) + + val requiresDOM = SettingKey[Boolean]("requiresDOM", + "Whether this projects needs the DOM. Overrides anything inherited through dependencies.", AMinusSetting) + + val scalaJSTestFramework = SettingKey[String]("scalaJSTestFramework", + "The Scala.js class that is used as a test framework, for example a class that wraps Jasmine", ASetting) + + val relativeSourceMaps = SettingKey[Boolean]("relativeSourceMaps", + "Make the referenced paths on source maps relative to target path", BPlusSetting) + + val emitSourceMaps = SettingKey[Boolean]("emitSourceMaps", + "Whether package and optimize stages should emit source maps at all", BPlusSetting) + + val jsDependencies = SettingKey[Seq[AbstractJSDep]]("jsDependencies", + "JavaScript libraries this project depends upon. Also used to depend on the DOM.", APlusSetting) + + val scalaJSSemantics = SettingKey[Semantics]("scalaJSSemantics", + "Configurable semantics of Scala.js.", BPlusSetting) + + val jsDependencyFilter = SettingKey[PartialClasspath.DependencyFilter]("jsDependencyFilter", + "The filter applied to the raw JavaScript dependencies before execution", CSetting) + + val checkScalaJSSemantics = SettingKey[Boolean]("checkScalaJSSemantics", + "Whether to check that the current semantics meet compliance " + + "requirements of dependencies.", CSetting) + + val persistLauncher = SettingKey[Boolean]("persistLauncher", + "Tell optimize/package tasks to write the laucher file to disk. " + + "If this is set, your project may only have a single mainClass or you must explicitly set it", AMinusSetting) + + val scalaJSOptimizerOptions = SettingKey[OptimizerOptions]("scalaJSOptimizerOptions", + "All kinds of options for the Scala.js optimizer stages", DSetting) + + /** Class loader for PhantomJSEnv. Used to load jetty8. */ + val scalaJSPhantomJSClassLoader = TaskKey[ClassLoader]("scalaJSPhantomJSClassLoader", + "Private class loader to load jetty8 without polluting classpath. Only use this " + + "as the `jettyClassLoader` argument of the PhantomJSEnv", + KeyRanks.Invisible) + } + + import autoImport._ + import ScalaJSPluginInternal._ + + override def globalSettings: Seq[Setting[_]] = { + super.globalSettings ++ Seq( + scalaJSStage := Stage.PreLink + ) + } + + override def projectSettings: Seq[Setting[_]] = ( + scalaJSAbstractSettings ++ + scalaJSEcosystemSettings + ) +} diff --git a/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/ScalaJSPluginInternal.scala b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/ScalaJSPluginInternal.scala new file mode 100644 index 0000000..fe97f0b --- /dev/null +++ b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/ScalaJSPluginInternal.scala @@ -0,0 +1,598 @@ +package scala.scalajs.sbtplugin + +import sbt._ +import sbt.inc.{IncOptions, ClassfileManager} +import Keys._ + +import Implicits._ +import JSUtils._ + +import scala.scalajs.tools.sem.Semantics +import scala.scalajs.tools.io.{IO => toolsIO, _} +import scala.scalajs.tools.classpath._ +import scala.scalajs.tools.classpath.builder._ +import scala.scalajs.tools.jsdep._ +import scala.scalajs.tools.optimizer.{ + ScalaJSOptimizer, + ScalaJSClosureOptimizer, + IncOptimizer, + ParIncOptimizer +} +import scala.scalajs.tools.corelib.CoreJSLibs + +import scala.scalajs.tools.env._ +import scala.scalajs.sbtplugin.env.rhino.RhinoJSEnv +import scala.scalajs.sbtplugin.env.nodejs.NodeJSEnv +import scala.scalajs.sbtplugin.env.phantomjs.{PhantomJSEnv, PhantomJettyClassLoader} + +import scala.scalajs.ir.ScalaJSVersions + +import scala.scalajs.sbtplugin.testing.{TestFramework, JSClasspathLoader} + +import scala.util.Try + +import java.nio.charset.Charset +import java.net.URLClassLoader + +/** Contains settings used by ScalaJSPlugin that should not be automatically + * be in the *.sbt file's scope. + */ +object ScalaJSPluginInternal { + + import ScalaJSPlugin.autoImport._ + + /** Dummy setting to ensure we do not fork in Scala.js run & test. */ + val scalaJSEnsureUnforked = SettingKey[Boolean]("ensureUnforked", + "Scala.js internal: Fails if fork is true.", KeyRanks.Invisible) + + /** Dummy setting to persist Scala.js optimizer */ + val scalaJSOptimizer = SettingKey[ScalaJSOptimizer]("scalaJSOptimizer", + "Scala.js internal: Setting to persist the optimizer", KeyRanks.Invisible) + + /** Internal task to calculate whether a project requests the DOM + * (through jsDependencies or requiresDOM) */ + val scalaJSRequestsDOM = TaskKey[Boolean]("scalaJSRequestsDOM", + "Scala.js internal: Whether a project really wants the DOM. " + + "Calculated using requiresDOM and jsDependencies", KeyRanks.Invisible) + + /** Default post link environment */ + val scalaJSDefaultPostLinkJSEnv = TaskKey[JSEnv]("scalaJSDefaultPostLinkJSEnv", + "Scala.js internal: Default for postLinkJSEnv", KeyRanks.Invisible) + + /** Lookup key for CompleteClasspath in attribute maps */ + val scalaJSCompleteClasspath = + AttributeKey[CompleteClasspath]("scalaJSCompleteClasspath") + + /** Patches the IncOptions so that .sjsir files are pruned as needed. + * + * This complicated logic patches the ClassfileManager factory of the given + * IncOptions with one that is aware of .sjsir files emitted by the Scala.js + * compiler. This makes sure that, when a .class file must be deleted, the + * corresponding .sjsir file are also deleted. + */ + def scalaJSPatchIncOptions(incOptions: IncOptions): IncOptions = { + val inheritedNewClassfileManager = incOptions.newClassfileManager + val newClassfileManager = () => new ClassfileManager { + private[this] val inherited = inheritedNewClassfileManager() + + def delete(classes: Iterable[File]): Unit = { + inherited.delete(classes flatMap { classFile => + val scalaJSFiles = if (classFile.getPath endsWith ".class") { + val f = FileVirtualFile.withExtension(classFile, ".class", ".sjsir") + if (f.exists) List(f) + else Nil + } else Nil + classFile :: scalaJSFiles + }) + } + + def generated(classes: Iterable[File]): Unit = inherited.generated(classes) + def complete(success: Boolean): Unit = inherited.complete(success) + } + incOptions.copy(newClassfileManager = newClassfileManager) + } + + private def scalaJSOptimizerSetting(key: TaskKey[_]): Setting[_] = ( + scalaJSOptimizer in key := { + val semantics = (scalaJSSemantics in key).value + if ((scalaJSOptimizerOptions in key).value.parallel) + new ScalaJSOptimizer(semantics, new ParIncOptimizer(_)) + else + new ScalaJSOptimizer(semantics, new IncOptimizer(_)) + } + ) + + val scalaJSConfigSettings: Seq[Setting[_]] = Seq( + incOptions ~= scalaJSPatchIncOptions + ) ++ Seq( + + scalaJSPreLinkClasspath := { + val cp = fullClasspath.value + val pcp = PartialClasspathBuilder.build(Attributed.data(cp).toList) + val ccp = pcp.resolve(jsDependencyFilter.value) + + if (checkScalaJSSemantics.value) + ccp.checkCompliance(scalaJSSemantics.value) + + ccp + }, + + artifactPath in fastOptJS := + ((crossTarget in fastOptJS).value / + ((moduleName in fastOptJS).value + "-fastopt.js")), + + scalaJSOptimizerSetting(fastOptJS), + + fastOptJS := { + val s = streams.value + val output = (artifactPath in fastOptJS).value + val taskCache = + WritableFileVirtualTextFile(s.cacheDirectory / "fastopt-js") + + IO.createDirectory(output.getParentFile) + + val relSourceMapBase = + if ((relativeSourceMaps in fastOptJS).value) + Some(output.getParentFile.toURI()) + else None + + val opts = (scalaJSOptimizerOptions in fastOptJS).value + + import ScalaJSOptimizer._ + val outCP = (scalaJSOptimizer in fastOptJS).value.optimizeCP( + Inputs(input = (scalaJSPreLinkClasspath in fastOptJS).value), + OutputConfig( + output = WritableFileVirtualJSFile(output), + cache = Some(taskCache), + wantSourceMap = (emitSourceMaps in fastOptJS).value, + relativizeSourceMapBase = relSourceMapBase, + checkIR = opts.checkScalaJSIR, + disableOptimizer = opts.disableOptimizer, + batchMode = opts.batchMode), + s.log) + + Attributed.blank(output).put(scalaJSCompleteClasspath, outCP) + }, + fastOptJS <<= + fastOptJS.dependsOn(packageJSDependencies, packageScalaJSLauncher), + + artifactPath in fullOptJS := + ((crossTarget in fullOptJS).value / + ((moduleName in fullOptJS).value + "-opt.js")), + + scalaJSSemantics in fullOptJS := + (scalaJSSemantics in fastOptJS).value.optimized, + + scalaJSOptimizerSetting(fullOptJS), + + fullOptJS := { + val s = streams.value + val output = (artifactPath in fullOptJS).value + val taskCache = + WritableFileVirtualTextFile(s.cacheDirectory / "fullopt-js") + + IO.createDirectory(output.getParentFile) + + val relSourceMapBase = + if ((relativeSourceMaps in fullOptJS).value) + Some(output.getParentFile.toURI()) + else None + + val opts = (scalaJSOptimizerOptions in fullOptJS).value + + val semantics = (scalaJSSemantics in fullOptJS).value + + import ScalaJSClosureOptimizer._ + val outCP = new ScalaJSClosureOptimizer(semantics).optimizeCP( + (scalaJSOptimizer in fullOptJS).value, + Inputs(ScalaJSOptimizer.Inputs( + input = (scalaJSPreLinkClasspath in fullOptJS).value)), + OutputConfig( + output = WritableFileVirtualJSFile(output), + cache = Some(taskCache), + wantSourceMap = (emitSourceMaps in fullOptJS).value, + relativizeSourceMapBase = relSourceMapBase, + checkIR = opts.checkScalaJSIR, + disableOptimizer = opts.disableOptimizer, + batchMode = opts.batchMode, + prettyPrint = opts.prettyPrintFullOptJS), + s.log) + + Attributed.blank(output).put(scalaJSCompleteClasspath, outCP) + }, + + artifactPath in packageScalaJSLauncher := + ((crossTarget in packageScalaJSLauncher).value / + ((moduleName in packageScalaJSLauncher).value + "-launcher.js")), + + skip in packageScalaJSLauncher := !persistLauncher.value, + + packageScalaJSLauncher <<= Def.taskDyn { + if ((skip in packageScalaJSLauncher).value) + Def.task(Attributed.blank((artifactPath in packageScalaJSLauncher).value)) + else Def.task { + mainClass.value map { mainCl => + val file = (artifactPath in packageScalaJSLauncher).value + IO.write(file, launcherContent(mainCl), Charset.forName("UTF-8")) + + // Attach the name of the main class used, (ab?)using the name key + Attributed(file)(AttributeMap.empty.put(name.key, mainCl)) + } getOrElse { + sys.error("Cannot write launcher file, since there is no or multiple mainClasses") + } + } + }, + + artifactPath in packageJSDependencies := + ((crossTarget in packageJSDependencies).value / + ((moduleName in packageJSDependencies).value + "-jsdeps.js")), + + packageJSDependencies <<= Def.taskDyn { + if ((skip in packageJSDependencies).value) + Def.task((artifactPath in packageJSDependencies).value) + else Def.task { + val cp = scalaJSPreLinkClasspath.value + val output = (artifactPath in packageJSDependencies).value + val taskCache = WritableFileVirtualJSFile( + streams.value.cacheDirectory / "package-js-deps") + + IO.createDirectory(output.getParentFile) + + val outFile = WritableFileVirtualTextFile(output) + CacheUtils.cached(cp.version, outFile, Some(taskCache)) { + toolsIO.concatFiles(outFile, cp.jsLibs.map(_.lib)) + } + + output + } + }, + + jsDependencyManifest := { + val myModule = thisProject.value.id + val config = configuration.value.name + + // Collect all libraries + val jsDeps = jsDependencies.value.collect { + case dep: JSModuleID if dep.configurations.forall(_ == config) => + dep.jsDep + } + + val requiresDOM = jsDependencies.value.exists { + case RuntimeDOM(configurations) => + configurations.forall(_ == config) + case _ => false + } + + val compliantSemantics = scalaJSSemantics.value.compliants + + val manifest = new JSDependencyManifest(new Origin(myModule, config), + jsDeps.toList, requiresDOM, compliantSemantics) + + // Write dependency file to class directory + val targetDir = classDirectory.value + IO.createDirectory(targetDir) + + val file = targetDir / JSDependencyManifest.ManifestFileName + val vfile = WritableFileVirtualTextFile(file) + + // Prevent writing if unnecessary to not invalidate dependencies + val needWrite = !vfile.exists || { + Try { + val readManifest = JSDependencyManifest.read(vfile) + readManifest != manifest + } getOrElse true + } + + if (needWrite) + JSDependencyManifest.write(manifest, vfile) + + file + }, + + products <<= products.dependsOn(jsDependencyManifest), + + console <<= console.dependsOn(Def.task( + streams.value.log.warn("Scala REPL doesn't work with Scala.js. You " + + "are running a JVM REPL. JavaScript things won't work.") + )), + + // Give tasks ability to check we are not forking at build reading time + scalaJSEnsureUnforked := { + if (fork.value) + sys.error("Scala.js cannot be run in a forked JVM") + else + true + }, + + scalaJSRequestsDOM := + requiresDOM.?.value.getOrElse(scalaJSExecClasspath.value.requiresDOM), + + // Default jsEnv + jsEnv <<= Def.taskDyn { + scalaJSStage.value match { + case Stage.PreLink => + Def.task { + preLinkJSEnv.?.value.getOrElse { + new RhinoJSEnv(scalaJSSemantics.value, + withDOM = scalaJSRequestsDOM.value) + } + } + case Stage.FastOpt | Stage.FullOpt => + Def.task(scalaJSDefaultPostLinkJSEnv.value) + } + }, + + // Wire jsEnv and sources for other stages + scalaJSDefaultPostLinkJSEnv := postLinkJSEnv.?.value.getOrElse { + if (scalaJSRequestsDOM.value) + new PhantomJSEnv(jettyClassLoader = scalaJSPhantomJSClassLoader.value) + else + new NodeJSEnv + }, + + scalaJSExecClasspath <<= Def.taskDyn { + scalaJSStage.value match { + case Stage.PreLink => + Def.task { scalaJSPreLinkClasspath.value } + case Stage.FastOpt => + Def.task { fastOptJS.value.get(scalaJSCompleteClasspath).get } + case Stage.FullOpt => + Def.task { fullOptJS.value.get(scalaJSCompleteClasspath).get } + } + } + ) + + /** Run a class in a given environment using a given launcher */ + private def jsRun(env: JSEnv, cp: CompleteClasspath, mainCl: String, + launcher: VirtualJSFile, jsConsole: JSConsole, log: Logger) = { + + log.info("Running " + mainCl) + log.debug(s"with JSEnv of type ${env.getClass()}") + log.debug(s"with classpath of type ${cp.getClass}") + + // Actually run code + env.jsRunner(cp, launcher, log, jsConsole).run() + } + + private def launcherContent(mainCl: String) = { + // If we are running in Node.js, we need to bracket select on + // global rather than this + """((typeof global === "object" && global && + global["Object"] === Object) ? global : this)""" + + s"${dot2bracket(mainCl)}().main();\n" + } + + private def memLauncher(mainCl: String) = { + new MemVirtualJSFile("Generated launcher file") + .withContent(launcherContent(mainCl)) + } + + // These settings will be filtered by the stage dummy tasks + val scalaJSRunSettings = Seq( + mainClass in scalaJSLauncher := (mainClass in run).value, + scalaJSLauncher <<= Def.taskDyn { + if (persistLauncher.value) + Def.task(packageScalaJSLauncher.value.map(FileVirtualJSFile)) + else Def.task { + (mainClass in scalaJSLauncher).value map { mainClass => + val memLaunch = memLauncher(mainClass) + Attributed[VirtualJSFile](memLaunch)( + AttributeMap.empty.put(name.key, mainClass)) + } getOrElse { + sys.error("No main class detected.") + } + } + }, + + /* We do currently not discover objects containing a + * + * def main(args: Array[String]): Unit + * + * Support will be added again, as soon as we can run them + * reliably (e.g. without implicitly requiring that an exported + * + * def main(): Unit + * + * exists alongside. + */ + discoveredMainClasses := { + import xsbt.api.{Discovered, Discovery} + + val jsApp = "scala.scalajs.js.JSApp" + + def isJSApp(discovered: Discovered) = + discovered.isModule && discovered.baseClasses.contains(jsApp) + + Discovery(Set(jsApp), Set.empty)(Tests.allDefs(compile.value)) collect { + case (definition, discovered) if isJSApp(discovered) => + definition.name + } + }, + + run <<= Def.inputTask { + // use assert to prevent warning about pure expr in stat pos + assert(scalaJSEnsureUnforked.value) + + val launch = scalaJSLauncher.value + val className = launch.get(name.key).getOrElse("<unknown class>") + jsRun(jsEnv.value, scalaJSExecClasspath.value, className, + launch.data, scalaJSConsole.value, streams.value.log) + }, + + runMain <<= { + // Implicits for parsing + import sbinary.DefaultProtocol.StringFormat + import Cache.seqFormat + + val parser = Defaults.loadForParser(discoveredMainClasses)((s, names) => + Defaults.runMainParser(s, names getOrElse Nil)) + + Def.inputTask { + // use assert to prevent warning about pure expr in stat pos + assert(scalaJSEnsureUnforked.value) + + val mainCl = parser.parsed._1 + jsRun(jsEnv.value, scalaJSExecClasspath.value, mainCl, + memLauncher(mainCl), scalaJSConsole.value, streams.value.log) + } + } + ) + + val scalaJSCompileSettings = ( + scalaJSConfigSettings ++ + scalaJSRunSettings + ) + + val scalaJSTestFrameworkSettings = Seq( + // Copied from Defaults, but scoped. We need a JVM loader in + // loadedTestFrameworks to find out whether the framework exists. + testLoader in loadedTestFrameworks := { + TestFramework.createTestLoader( + Attributed.data(fullClasspath.value), + scalaInstance.value, + IO.createUniqueDirectory(taskTemporaryDirectory.value)) + }, + + loadedTestFrameworks := { + // use assert to prevent warning about pure expr in stat pos + assert(scalaJSEnsureUnforked.value) + + val loader = (testLoader in loadedTestFrameworks).value + val isTestFrameworkDefined = try { + Class.forName(scalaJSTestFramework.value, false, loader) + true + } catch { + case _: ClassNotFoundException => false + } + if (isTestFrameworkDefined) { + loadedTestFrameworks.value.updated( + sbt.TestFramework(classOf[TestFramework].getName), + new TestFramework( + environment = jsEnv.value, + jsConsole = scalaJSConsole.value, + testFramework = scalaJSTestFramework.value) + ) + } else { + loadedTestFrameworks.value + } + }, + + // Pseudo loader to pass classpath to test framework + testLoader := JSClasspathLoader(scalaJSExecClasspath.value) + ) + + val scalaJSTestBuildSettings = ( + scalaJSConfigSettings + ) ++ ( + Seq(fastOptJS, fullOptJS, packageScalaJSLauncher, + packageJSDependencies) map { packageJSTask => + moduleName in packageJSTask := moduleName.value + "-test" + } + ) + + val scalaJSTestSettings = ( + scalaJSTestBuildSettings ++ + scalaJSTestFrameworkSettings + ) + + val scalaJSDependenciesSettings = Seq( + // add all the webjars your jsDependencies depend upon + libraryDependencies ++= jsDependencies.value.collect { + case JarJSModuleID(module, _) => module + } + ) + + val scalaJSDefaultBuildConfigs = ( + inConfig(Compile)(scalaJSConfigSettings) ++ // build settings for Compile + inConfig(Test)(scalaJSTestBuildSettings) ++ + scalaJSDependenciesSettings + ) + + val scalaJSDefaultConfigs = ( + inConfig(Compile)(scalaJSCompileSettings) ++ + inConfig(Test)(scalaJSTestSettings) ++ + scalaJSDependenciesSettings + ) + + val phantomJSJettyModules = Seq( + "org.eclipse.jetty" % "jetty-websocket" % "8.1.16.v20140903", + "org.eclipse.jetty" % "jetty-server" % "8.1.16.v20140903" + ) + + val scalaJSProjectBaseSettings = Seq( + relativeSourceMaps := false, + persistLauncher := false, + + skip in packageJSDependencies := true, + + scalaJSTestFramework := "org.scalajs.jasminetest.JasmineTestFramework", + + emitSourceMaps := true, + + scalaJSOptimizerOptions := OptimizerOptions(), + + jsDependencies := Seq(), + jsDependencyFilter := identity, + + scalaJSSemantics := Semantics.Defaults, + checkScalaJSSemantics := true, + + scalaJSConsole := ConsoleJSConsole, + + clean <<= clean.dependsOn(Def.task { + // have clean reset incremental optimizer state + (scalaJSOptimizer in (Compile, fastOptJS)).value.clean() + (scalaJSOptimizer in (Test, fastOptJS)).value.clean() + }), + + /* Depend on jetty artifacts in dummy configuration to be able to inject + * them into the PhantomJS runner if necessary. + * See scalaJSPhantomJSClassLoader + */ + ivyConfigurations += config("phantom-js-jetty").hide, + libraryDependencies ++= phantomJSJettyModules.map(_ % "phantom-js-jetty"), + scalaJSPhantomJSClassLoader := { + val report = update.value + val jars = report.select(configurationFilter("phantom-js-jetty")) + + val jettyLoader = + new URLClassLoader(jars.map(_.toURI.toURL).toArray, null) + + new PhantomJettyClassLoader(jettyLoader, getClass.getClassLoader) + } + ) + + val scalaJSAbstractSettings: Seq[Setting[_]] = ( + scalaJSProjectBaseSettings ++ + scalaJSDefaultConfigs + ) + + val scalaJSAbstractBuildSettings: Seq[Setting[_]] = ( + scalaJSProjectBaseSettings ++ + scalaJSDefaultBuildConfigs + ) + + val scalaJSReleasesResolver = Resolver.url("scala-js-releases", + url("http://dl.bintray.com/content/scala-js/scala-js-releases"))( + Resolver.ivyStylePatterns) + val scalaJSSnapshotsResolver = Resolver.url("scala-js-snapshots", + url("http://repo.scala-js.org/repo/snapshots/"))( + Resolver.ivyStylePatterns) + + val scalaJSEcosystemSettings = Seq( + // the resolver to find the compiler and library (and others) + resolvers ++= Seq(scalaJSReleasesResolver, scalaJSSnapshotsResolver), + + // you will need the Scala.js compiler plugin + autoCompilerPlugins := true, + addCompilerPlugin( + "org.scala-lang.modules.scalajs" % "scalajs-compiler" % scalaJSVersion cross CrossVersion.full), + + // and of course the Scala.js library + libraryDependencies += "org.scala-lang.modules.scalajs" %% "scalajs-library" % scalaJSVersion, + + // and you will want to be cross-compiled on the Scala.js binary version + crossVersion := ScalaJSCrossVersion.binary + ) + +} diff --git a/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/Stage.scala b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/Stage.scala new file mode 100644 index 0000000..7f7b916 --- /dev/null +++ b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/Stage.scala @@ -0,0 +1,18 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js sbt plugin ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.sbtplugin + +sealed trait Stage + +object Stage { + case object PreLink extends Stage + case object FullOpt extends Stage + case object FastOpt extends Stage +} diff --git a/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/ExternalJSEnv.scala b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/ExternalJSEnv.scala new file mode 100644 index 0000000..e0aa557 --- /dev/null +++ b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/ExternalJSEnv.scala @@ -0,0 +1,200 @@ +package scala.scalajs.sbtplugin.env + +import scala.scalajs.tools.io._ +import scala.scalajs.tools.classpath._ +import scala.scalajs.tools.env._ +import scala.scalajs.tools.logging._ + +import scala.scalajs.sbtplugin.JSUtils._ + +import java.io.{ Console => _, _ } +import scala.io.Source + +import scala.concurrent.{Future, Promise} +import scala.util.Try + +abstract class ExternalJSEnv( + final protected val additionalArgs: Seq[String], + final protected val additionalEnv: Map[String, String]) extends AsyncJSEnv { + + /** Printable name of this VM */ + protected def vmName: String + + /** Command to execute (on shell) for this VM */ + protected def executable: String + + protected class AbstractExtRunner(protected val classpath: CompleteClasspath, + protected val code: VirtualJSFile, protected val logger: Logger, + protected val console: JSConsole) { + + /** JS files used to setup VM */ + protected def initFiles(): Seq[VirtualJSFile] = Nil + + /** Sends required data to VM Stdin (can throw) */ + protected def sendVMStdin(out: OutputStream): Unit = {} + + /** VM arguments excluding executable. Override to adapt. + * Overrider is responsible to add additionalArgs. + */ + protected def getVMArgs(): Seq[String] = additionalArgs + + /** VM environment. Override to adapt. + * + * Default is `sys.env` and [[additionalEnv]] + */ + protected def getVMEnv(): Map[String, String] = + sys.env ++ additionalEnv + + /** Get files that are a library (i.e. that do not run anything) */ + protected def getLibJSFiles(): Seq[VirtualJSFile] = + initFiles() ++ classpath.allCode + + /** Get all files that are passed to VM (libraries and code) */ + protected def getJSFiles(): Seq[VirtualJSFile] = + getLibJSFiles() :+ code + + /** write a single JS file to a writer using an include fct if appropriate */ + protected def writeJSFile(file: VirtualJSFile, writer: Writer): Unit = { + // The only platform-independent way to do this in JS is to dump the file. + writer.write(file.content) + writer.write('\n') + } + + /** Pipe stdin and stdout from/to VM */ + final protected def pipeVMData(vmInst: Process): Unit = { + // Send stdin to VM. + val out = vmInst.getOutputStream() + try { sendVMStdin(out) } + finally { out.close() } + + // Pipe stdout to console + pipeToConsole(vmInst.getInputStream(), console) + + // We are probably done (stdin is closed). Report any errors + val errSrc = Source.fromInputStream(vmInst.getErrorStream(), "UTF-8") + try { errSrc.getLines.foreach(err => logger.error(err)) } + finally { errSrc.close } + } + + /** Wait for the VM to terminate, verify exit code */ + final protected def waitForVM(vmInst: Process): Unit = { + // Make sure we are done. + vmInst.waitFor() + + // Get return value and return + val retVal = vmInst.exitValue + if (retVal != 0) + sys.error(s"$vmName exited with code $retVal") + } + + protected def startVM(): Process = { + val vmArgs = getVMArgs() + val vmEnv = getVMEnv() + + val allArgs = executable +: vmArgs + val pBuilder = new ProcessBuilder(allArgs: _*) + + pBuilder.environment().clear() + for ((name, value) <- vmEnv) + pBuilder.environment().put(name, value) + + pBuilder.start() + } + + /** send a bunch of JS files to an output stream */ + final protected def sendJS(files: Seq[VirtualJSFile], + out: OutputStream): Unit = { + val writer = new BufferedWriter(new OutputStreamWriter(out, "UTF-8")) + try sendJS(files, writer) + finally writer.close() + } + + /** send a bunch of JS files to a writer */ + final protected def sendJS(files: Seq[VirtualJSFile], out: Writer): Unit = + files.foreach { writeJSFile(_, out) } + + /** pipe lines from input stream to JSConsole */ + final protected def pipeToConsole(in: InputStream, console: JSConsole) = { + val source = Source.fromInputStream(in, "UTF-8") + try { source.getLines.foreach(console.log _) } + finally { source.close() } + } + + } + + protected class ExtRunner(classpath: CompleteClasspath, code: VirtualJSFile, + logger: Logger, console: JSConsole + ) extends AbstractExtRunner(classpath, code, logger, console) + with JSRunner { + + def run(): Unit = { + val vmInst = startVM() + + pipeVMData(vmInst) + waitForVM(vmInst) + } + } + + protected class AsyncExtRunner(classpath: CompleteClasspath, + code: VirtualJSFile, logger: Logger, console: JSConsole + ) extends AbstractExtRunner(classpath, code, logger, console) + with AsyncJSRunner { + + private[this] var vmInst: Process = null + private[this] var ioThreadEx: Throwable = null + private[this] val promise = Promise[Unit] + + private[this] val thread = new Thread { + override def run(): Unit = { + // This thread should not be interrupted, so it is safe to use Trys + val pipeResult = Try(pipeVMData(vmInst)) + val vmComplete = Try(waitForVM(vmInst)) + + // Store IO exception + pipeResult recover { + case e => ioThreadEx = e + } + + // Chain Try's the other way: We want VM failure first, then IO failure + promise.complete(pipeResult orElse vmComplete) + } + } + + def start(): Future[Unit] = { + require(vmInst == null, "start() may only be called once") + vmInst = startVM() + thread.start() + promise.future + } + + def stop(): Unit = { + require(vmInst != null, "start() must have been called") + vmInst.destroy() + } + + def isRunning(): Boolean = { + require(vmInst != null, "start() must have been called") + // Emulate JDK 8 Process.isAlive + try { + vmInst.exitValue() + false + } catch { + case e: IllegalThreadStateException => + true + } + } + + def await(): Unit = { + require(vmInst != null, "start() must have been called") + thread.join() + waitForVM(vmInst) + + // At this point, the VM itself didn't fail. We need to check if + // anything bad happened while piping the data from the VM + + if (ioThreadEx != null) + throw ioThreadEx + } + } + +} diff --git a/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/VirtualFileMaterializer.scala b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/VirtualFileMaterializer.scala new file mode 100644 index 0000000..fca1c47 --- /dev/null +++ b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/VirtualFileMaterializer.scala @@ -0,0 +1,67 @@ +package scala.scalajs.sbtplugin.env + +import scala.scalajs.tools.io.{IO => _, _} + +import sbt.IO + +import java.io.File + +/** A helper class to temporarily store virtual files to the filesystem. + * + * Can be used with tools that require real files. + * @param singleDir if true, forces files to be copied into + * [[cacheDir]]. Useful to setup include directories for + * example. + */ +final class VirtualFileMaterializer(singleDir: Boolean = false) { + + val cacheDir = { + val dir = IO.createTemporaryDirectory + dir.deleteOnExit() + dir + } + + /** Create a target file to write/copy to. Will also call + * deleteOnExit on the file. + */ + private def trgFile(name: String): File = { + val f = new File(cacheDir, name) + f.deleteOnExit() + f + } + + private def materializeFileVF(vf: FileVirtualFile): File = { + if (!singleDir) vf.file + else { + val trg = trgFile(vf.name) + IO.copyFile(vf.file, trg) + trg + } + } + + def materialize(vf: VirtualTextFile): File = vf match { + case vf: FileVirtualFile => materializeFileVF(vf) + case _ => + val trg = trgFile(vf.name) + IO.write(trg, vf.content) + trg + } + + def materialize(vf: VirtualBinaryFile): File = vf match { + case vf: FileVirtualFile => materializeFileVF(vf) + case _ => + val trg = trgFile(vf.name) + IO.write(trg, vf.content) + trg + } + + /** Removes the cache directory. Any operation on this + * VirtualFileMaterializer is invalid after [[close]] has been + * called. + */ + def close(): Unit = { + cacheDir.listFiles().foreach(_.delete) + cacheDir.delete() + } + +} diff --git a/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/nodejs/NodeJSEnv.scala b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/nodejs/NodeJSEnv.scala new file mode 100644 index 0000000..dfabe23 --- /dev/null +++ b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/nodejs/NodeJSEnv.scala @@ -0,0 +1,306 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js sbt plugin ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.sbtplugin.env.nodejs + +import scala.scalajs.sbtplugin.env._ +import scala.scalajs.sbtplugin.JSUtils.toJSstr + +import scala.scalajs.tools.io._ +import scala.scalajs.tools.classpath._ +import scala.scalajs.tools.env._ +import scala.scalajs.tools.jsdep._ +import scala.scalajs.tools.logging._ + +import scala.scalajs.sbtplugin.JSUtils._ + +import java.io.{ Console => _, _ } +import java.net._ + +import scala.io.Source + +class NodeJSEnv( + nodejsPath: String = "node", + addArgs: Seq[String] = Seq.empty, + addEnv: Map[String, String] = Map.empty +) extends ExternalJSEnv(addArgs, addEnv) with ComJSEnv { + + protected def vmName: String = "node.js" + protected def executable: String = nodejsPath + + override def jsRunner(classpath: CompleteClasspath, code: VirtualJSFile, + logger: Logger, console: JSConsole): JSRunner = { + new NodeRunner(classpath, code, logger, console) + } + + override def asyncRunner(classpath: CompleteClasspath, code: VirtualJSFile, + logger: Logger, console: JSConsole): AsyncJSRunner = { + new AsyncNodeRunner(classpath, code, logger, console) + } + + override def comRunner(classpath: CompleteClasspath, code: VirtualJSFile, + logger: Logger, console: JSConsole): ComJSRunner = { + new ComNodeRunner(classpath, code, logger, console) + } + + protected class NodeRunner(classpath: CompleteClasspath, + code: VirtualJSFile, logger: Logger, console: JSConsole + ) extends ExtRunner(classpath, code, logger, console) + with AbstractNodeRunner + + protected class AsyncNodeRunner(classpath: CompleteClasspath, + code: VirtualJSFile, logger: Logger, console: JSConsole + ) extends AsyncExtRunner(classpath, code, logger, console) + with AbstractNodeRunner + + protected class ComNodeRunner(classpath: CompleteClasspath, + code: VirtualJSFile, logger: Logger, console: JSConsole + ) extends AsyncNodeRunner(classpath, code, logger, console) + with ComJSRunner { + + /** Retry-timeout to wait for the JS VM to connect */ + private final val acceptTimeout = 1000 + + private[this] val serverSocket = + new ServerSocket(0, 0, InetAddress.getByName(null)) // Loopback address + private[this] var comSocket: Socket = _ + private[this] var jvm2js: DataOutputStream = _ + private[this] var js2jvm: DataInputStream = _ + + private def comSetup = new MemVirtualJSFile("comSetup.js").withContent( + s""" + (function() { + // The socket for communication + var socket = null; + // The callback where received messages go + var recvCallback = null; + + // Buffers received data + var inBuffer = new Buffer(0); + + function onData(data) { + inBuffer = Buffer.concat([inBuffer, data]); + tryReadMsg(); + } + + function tryReadMsg() { + if (inBuffer.length < 4) return; + var msgLen = inBuffer.readInt32BE(0); + var byteLen = 4 + msgLen * 2; + + if (inBuffer.length < byteLen) return; + var res = ""; + + for (var i = 0; i < msgLen; ++i) + res += String.fromCharCode(inBuffer.readInt16BE(4 + i * 2)); + + inBuffer = inBuffer.slice(byteLen); + + recvCallback(res); + } + + global.scalajsCom = { + init: function(recvCB) { + if (socket !== null) throw new Error("Com already open"); + + var net = require('net'); + recvCallback = recvCB; + socket = net.connect(${serverSocket.getLocalPort}); + socket.on('data', onData); + }, + send: function(msg) { + if (socket === null) throw new Error("Com not open"); + + var len = msg.length; + var buf = new Buffer(4 + len * 2); + buf.writeInt32BE(len, 0); + for (var i = 0; i < len; ++i) + buf.writeInt16BE(msg.charCodeAt(i), 4 + i * 2); + socket.write(buf); + }, + close: function() { + if (socket === null) throw new Error("Com not open"); + socket.end(); + } + } + }).call(this); + """ + ) + + def send(msg: String): Unit = { + if (awaitConnection()) { + jvm2js.writeInt(msg.length) + jvm2js.writeChars(msg) + jvm2js.flush() + } + } + + def receive(): String = { + if (!awaitConnection()) + throw new ComJSEnv.ComClosedException + try { + val len = js2jvm.readInt() + val carr = Array.fill(len)(js2jvm.readChar()) + String.valueOf(carr) + } catch { + case e: EOFException => + throw new ComJSEnv.ComClosedException + } + } + + def close(): Unit = { + serverSocket.close() + if (jvm2js != null) + jvm2js.close() + if (js2jvm != null) + js2jvm.close() + if (comSocket != null) + comSocket.close() + } + + override def stop(): Unit = { + close() + super.stop() + } + + /** Waits until the JS VM has established a connection or terminates + * @return true if the connection was established + */ + private def awaitConnection(): Boolean = { + serverSocket.setSoTimeout(acceptTimeout) + while (comSocket == null && isRunning) { + try { + comSocket = serverSocket.accept() + jvm2js = new DataOutputStream( + new BufferedOutputStream(comSocket.getOutputStream())) + js2jvm = new DataInputStream( + new BufferedInputStream(comSocket.getInputStream())) + } catch { + case to: SocketTimeoutException => + } + } + + comSocket != null + } + + override protected def initFiles(): Seq[VirtualJSFile] = + super.initFiles :+ comSetup + + override protected def finalize(): Unit = close() + } + + protected trait AbstractNodeRunner extends AbstractExtRunner { + + protected[this] val libCache = new VirtualFileMaterializer(true) + + /** File(s) to automatically install source-map-support. + * Is used by [[initFiles]], override to change/disable. + */ + protected def installSourceMap(): Seq[VirtualJSFile] = Seq( + new MemVirtualJSFile("sourceMapSupport.js").withContent( + """ + try { + require('source-map-support').install(); + } catch (e) {} + """ + ) + ) + + /** File(s) to hack console.log to prevent if from changing `%%` to `%`. + * Is used by [[initFiles]], override to change/disable. + */ + protected def fixPercentConsole(): Seq[VirtualJSFile] = Seq( + new MemVirtualJSFile("nodeConsoleHack.js").withContent( + """ + // Hack console log to duplicate double % signs + (function() { + var oldLog = console.log; + var newLog = function() { + var args = arguments; + if (args.length >= 1 && args[0] !== void 0 && args[0] !== null) { + args[0] = args[0].toString().replace(/%/g, "%%"); + } + oldLog.apply(console, args); + }; + console.log = newLog; + })(); + """ + ) + ) + + /** File(s) to define `__ScalaJSEnv`. Defines `exitFunction`. + * Is used by [[initFiles]], override to change/disable. + */ + protected def runtimeEnv(): Seq[VirtualJSFile] = Seq( + new MemVirtualJSFile("scalaJSEnvInfo.js").withContent( + """ + __ScalaJSEnv = { + exitFunction: function(status) { process.exit(status); } + }; + """ + ) + ) + + /** Concatenates results from [[installSourceMap]], [[fixPercentConsole]] and + * [[runtimeEnv]] (in this order). + */ + override protected def initFiles(): Seq[VirtualJSFile] = + installSourceMap() ++ fixPercentConsole() ++ runtimeEnv() + + /** Libraries are loaded via require in Node.js */ + override protected def getLibJSFiles(): Seq[VirtualJSFile] = { + initFiles() ++ + classpath.jsLibs.map(requireLibrary) :+ + classpath.scalaJSCode + } + + /** Rewrites a library virtual file to a require statement if possible */ + protected def requireLibrary(dep: ResolvedJSDependency): VirtualJSFile = { + dep.info.commonJSName.fold(dep.lib) { varname => + val fname = dep.lib.name + libCache.materialize(dep.lib) + new MemVirtualJSFile(s"require-$fname").withContent( + s"""$varname = require(${toJSstr(fname)});""" + ) + } + } + + // Send code to Stdin + override protected def sendVMStdin(out: OutputStream): Unit = { + sendJS(getJSFiles(), out) + } + + /** write a single JS file to a writer using an include fct if appropriate + * uses `require` if the file exists on the filesystem + */ + override protected def writeJSFile(file: VirtualJSFile, + writer: Writer): Unit = { + file match { + case file: FileVirtualJSFile => + val fname = toJSstr(file.file.getAbsolutePath) + writer.write(s"require($fname);\n") + case _ => + super.writeJSFile(file, writer) + } + } + + // Node.js specific (system) environment + override protected def getVMEnv(): Map[String, String] = { + val baseNodePath = sys.env.get("NODE_PATH").filter(_.nonEmpty) + val nodePath = libCache.cacheDir.getAbsolutePath + + baseNodePath.fold("")(p => File.pathSeparator + p) + + sys.env ++ Seq( + "NODE_MODULE_CONTEXTS" -> "0", + "NODE_PATH" -> nodePath + ) ++ additionalEnv + } + } + +} diff --git a/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/JettyWebsocketManager.scala b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/JettyWebsocketManager.scala new file mode 100644 index 0000000..3dec79c --- /dev/null +++ b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/JettyWebsocketManager.scala @@ -0,0 +1,126 @@ +package scala.scalajs.sbtplugin.env.phantomjs + +import javax.servlet.http.HttpServletRequest + +import org.eclipse.jetty.server.Server +import org.eclipse.jetty.server.nio.SelectChannelConnector +import org.eclipse.jetty.websocket.{WebSocket, WebSocketHandler} +import org.eclipse.jetty.util.component.{LifeCycle, AbstractLifeCycle} +import org.eclipse.jetty.util.log + +private[phantomjs] final class JettyWebsocketManager( + wsListener: WebsocketListener) extends WebsocketManager { thisMgr => + + private[this] var webSocketConn: WebSocket.Connection = null + private[this] var closed = false + + // We can just set the logger here, since we are supposed to be protected by + // the private ClassLoader that loads us reflectively. + log.Log.setLog(new WSLogger("root")) + + private[this] val connector = new SelectChannelConnector + + connector.setHost("localhost") + connector.setPort(0) + + private[this] val server = new Server() + + server.addConnector(connector) + server.setHandler(new WebSocketHandler { + // Support Hixie 76 for Phantom.js + getWebSocketFactory().setMinVersion(-1) + + override def doWebSocketConnect( + request: HttpServletRequest, protocol: String): WebSocket = + new ComWebSocketListener + }) + + server.addLifeCycleListener(new AbstractLifeCycle.AbstractLifeCycleListener { + override def lifeCycleStarted(event: LifeCycle): Unit = { + if (event.isRunning()) + wsListener.onRunning() + } + }) + + private class ComWebSocketListener extends WebSocket.OnTextMessage { + override def onOpen(connection: WebSocket.Connection): Unit = { + thisMgr.synchronized { + if (isConnected) + throw new IllegalStateException("Client connected twice") + webSocketConn = connection + } + wsListener.onOpen() + } + + override def onClose(statusCode: Int, reason: String): Unit = { + thisMgr.synchronized { + webSocketConn = null + closed = true + } + wsListener.onClose() + server.stop() + + if (statusCode != 1000) { + throw new Exception("Abnormal closing of connection. " + + s"Code: $statusCode, Reason: $reason") + } + } + + override def onMessage(message: String): Unit = + wsListener.onMessage(message) + } + + private class WSLogger(fullName: String) extends log.AbstractLogger { + private[this] var debugEnabled = false + + def debug(msg: String, args: Object*): Unit = + if (debugEnabled) log("DEBUG", msg, args) + + def debug(msg: String, thrown: Throwable): Unit = + if (debugEnabled) log("DEBUG", msg, thrown) + + def debug(thrown: Throwable): Unit = + if (debugEnabled) log("DEBUG", thrown) + + def getName(): String = fullName + + def ignore(ignored: Throwable): Unit = () + + def info(msg: String, args: Object*): Unit = log("INFO", msg, args) + def info(msg: String, thrown: Throwable): Unit = log("INFO", msg, thrown) + def info(thrown: Throwable): Unit = log("INFO", thrown) + + def warn(msg: String, args: Object*): Unit = log("WARN", msg, args) + def warn(msg: String, thrown: Throwable): Unit = log("WARN", msg, thrown) + def warn(thrown: Throwable): Unit = log("WARN", thrown) + + def isDebugEnabled(): Boolean = debugEnabled + def setDebugEnabled(enabled: Boolean): Unit = debugEnabled = enabled + + private def log(lvl: String, msg: String, args: Object*): Unit = + wsListener.log(s"$lvl: $msg " + args.mkString(", ")) + + private def log(lvl: String, msg: String, thrown: Throwable): Unit = + wsListener.log(s"$lvl: $msg $thrown\n{$thrown.getStackStrace}") + + private def log(lvl: String, thrown: Throwable): Unit = + wsListener.log(s"$lvl: $thrown\n{$thrown.getStackStrace}") + + protected def newLogger(fullName: String) = new WSLogger(fullName) + } + + def start(): Unit = server.start() + + def stop(): Unit = server.stop() + + def isConnected: Boolean = webSocketConn != null && !closed + def isClosed: Boolean = closed + + def localPort: Int = connector.getLocalPort() + + def sendMessage(msg: String) = synchronized { + if (webSocketConn != null) + webSocketConn.sendMessage(msg) + } + +} diff --git a/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala new file mode 100644 index 0000000..7bb47d2 --- /dev/null +++ b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala @@ -0,0 +1,466 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js sbt plugin ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.sbtplugin.env.phantomjs + +import scala.scalajs.sbtplugin.env._ + +import scala.scalajs.tools.io._ +import scala.scalajs.tools.classpath._ +import scala.scalajs.tools.env._ +import scala.scalajs.tools.logging._ + +import scala.scalajs.sbtplugin.JSUtils._ + +import java.io.{ Console => _, _ } +import java.net._ + +import scala.io.Source +import scala.collection.mutable +import scala.annotation.tailrec + +class PhantomJSEnv( + phantomjsPath: String = "phantomjs", + addArgs: Seq[String] = Seq.empty, + addEnv: Map[String, String] = Map.empty, + val autoExit: Boolean = true, + jettyClassLoader: ClassLoader = getClass().getClassLoader() +) extends ExternalJSEnv(addArgs, addEnv) with ComJSEnv { + + import PhantomJSEnv._ + + protected def vmName: String = "PhantomJS" + protected def executable: String = phantomjsPath + + override def jsRunner(classpath: CompleteClasspath, code: VirtualJSFile, + logger: Logger, console: JSConsole): JSRunner = { + new PhantomRunner(classpath, code, logger, console) + } + + override def asyncRunner(classpath: CompleteClasspath, code: VirtualJSFile, + logger: Logger, console: JSConsole): AsyncJSRunner = { + new AsyncPhantomRunner(classpath, code, logger, console) + } + + override def comRunner(classpath: CompleteClasspath, code: VirtualJSFile, + logger: Logger, console: JSConsole): ComJSRunner = { + new ComPhantomRunner(classpath, code, logger, console) + } + + protected class PhantomRunner(classpath: CompleteClasspath, + code: VirtualJSFile, logger: Logger, console: JSConsole + ) extends ExtRunner(classpath, code, logger, console) + with AbstractPhantomRunner + + protected class AsyncPhantomRunner(classpath: CompleteClasspath, + code: VirtualJSFile, logger: Logger, console: JSConsole + ) extends AsyncExtRunner(classpath, code, logger, console) + with AbstractPhantomRunner + + protected class ComPhantomRunner(classpath: CompleteClasspath, + code: VirtualJSFile, logger: Logger, console: JSConsole + ) extends AsyncPhantomRunner(classpath, code, logger, console) + with ComJSRunner with WebsocketListener { + + private def loadMgr() = { + val clazz = jettyClassLoader.loadClass( + "scala.scalajs.sbtplugin.env.phantomjs.JettyWebsocketManager") + + val ctors = clazz.getConstructors() + assert(ctors.length == 1, "JettyWebsocketManager may only have one ctor") + + val mgr = ctors.head.newInstance(this) + + mgr.asInstanceOf[WebsocketManager] + } + + val mgr: WebsocketManager = loadMgr() + + def onRunning(): Unit = synchronized(notifyAll()) + def onOpen(): Unit = synchronized(notifyAll()) + def onClose(): Unit = synchronized(notifyAll()) + + def onMessage(msg: String): Unit = synchronized { + recvBuf.enqueue(msg) + notifyAll() + } + + def log(msg: String): Unit = logger.debug(s"PhantomJS WS Jetty: $msg") + + private[this] val recvBuf = mutable.Queue.empty[String] + + mgr.start() + + /** The websocket server starts asynchronously, but we need the port it is + * running on. This method waits until the port is non-negative and + * returns its value. + */ + private def waitForPort(): Int = { + while (mgr.localPort < 0) + wait() + mgr.localPort + } + + private def comSetup = { + def maybeExit(code: Int) = + if (autoExit) + s"window.callPhantom({ action: 'exit', returnValue: $code });" + else + "" + + val serverPort = waitForPort() + + val code = s""" + |(function() { + | var MaxPayloadSize = $MaxCharPayloadSize; + | + | // The socket for communication + | var websocket = null; + | + | // Buffer for messages sent before socket is open + | var outMsgBuf = null; + | + | function sendImpl(msg) { + | var frags = (msg.length / MaxPayloadSize) | 0; + | + | for (var i = 0; i < frags; ++i) { + | var payload = msg.substring( + | i * MaxPayloadSize, (i + 1) * MaxPayloadSize); + | websocket.send("1" + payload); + | } + | + | websocket.send("0" + msg.substring(frags * MaxPayloadSize)); + | } + | + | function recvImpl(recvCB) { + | var recvBuf = ""; + | + | return function(evt) { + | var newData = recvBuf + evt.data.substring(1); + | if (evt.data.charAt(0) == "0") { + | recvBuf = ""; + | recvCB(newData); + | } else if (evt.data.charAt(0) == "1") { + | recvBuf = newData; + | } else { + | throw new Error("Bad fragmentation flag in " + evt.data); + | } + | }; + | } + | + | window.scalajsCom = { + | init: function(recvCB) { + | if (websocket !== null) throw new Error("Com already open"); + | + | outMsgBuf = []; + | + | websocket = new WebSocket("ws://localhost:$serverPort"); + | + | websocket.onopen = function(evt) { + | for (var i = 0; i < outMsgBuf.length; ++i) + | sendImpl(outMsgBuf[i]); + | outMsgBuf = null; + | }; + | websocket.onclose = function(evt) { + | websocket = null; + | ${maybeExit(0)} + | }; + | websocket.onmessage = recvImpl(recvCB); + | websocket.onerror = function(evt) { + | websocket = null; + | throw new Error("Websocket failed: " + evt); + | }; + | + | // Take over responsibility to auto exit + | window.callPhantom({ + | action: 'setAutoExit', + | autoExit: false + | }); + | }, + | send: function(msg) { + | if (websocket === null) + | return; // we are closed already. ignore message + | + | if (outMsgBuf !== null) + | outMsgBuf.push(msg); + | else + | sendImpl(msg); + | }, + | close: function() { + | if (websocket === null) + | return; // we are closed already. all is well. + | + | if (outMsgBuf !== null) + | // Reschedule ourselves to give onopen a chance to kick in + | window.setTimeout(window.scalajsCom.close, 10); + | else + | websocket.close(); + | } + | } + |}).call(this);""".stripMargin + + new MemVirtualJSFile("comSetup.js").withContent(code) + } + + def send(msg: String): Unit = synchronized { + if (awaitConnection()) { + val fragParts = msg.length / MaxCharPayloadSize + + for (i <- 0 until fragParts) { + val payload = msg.substring( + i * MaxCharPayloadSize, (i + 1) * MaxCharPayloadSize) + mgr.sendMessage("1" + payload) + } + + mgr.sendMessage("0" + msg.substring(fragParts * MaxCharPayloadSize)) + } + } + + def receive(): String = synchronized { + if (recvBuf.isEmpty && !awaitConnection()) + throw new ComJSEnv.ComClosedException + + @tailrec + def loop(acc: String): String = { + val frag = receiveFrag() + val newAcc = acc + frag.substring(1) + + if (frag(0) == '0') + newAcc + else if (frag(0) == '1') + loop(newAcc) + else + throw new AssertionError("Bad fragmentation flag in " + frag) + } + + loop("") + } + + private def receiveFrag(): String = { + while (recvBuf.isEmpty && !mgr.isClosed) + wait() + + if (recvBuf.isEmpty) + throw new ComJSEnv.ComClosedException + else + recvBuf.dequeue() + } + + def close(): Unit = mgr.stop() + + override def stop(): Unit = { + close() + super.stop() + } + + /** Waits until the JS VM has established a connection, or the VM + * terminated. Returns true if a connection was established. + */ + private def awaitConnection(): Boolean = { + while (!mgr.isConnected && !mgr.isClosed && isRunning) + wait(200) // We sleep-wait for isRunning + + mgr.isConnected + } + + override protected def initFiles(): Seq[VirtualJSFile] = + super.initFiles :+ comSetup + } + + protected trait AbstractPhantomRunner extends AbstractExtRunner { + + override protected def getVMArgs() = + // Add launcher file to arguments + additionalArgs :+ createTmpLauncherFile().getAbsolutePath + + /** In phantom.js, we include JS using HTML */ + override protected def writeJSFile(file: VirtualJSFile, writer: Writer) = { + file match { + case file: FileVirtualJSFile => + val fname = htmlEscape(file.file.getAbsolutePath) + writer.write( + s"""<script type="text/javascript" src="$fname"></script>""" + "\n") + case _ => + writer.write("""<script type="text/javascript">""" + "\n") + writer.write(s"// Virtual File: ${file.path}\n") + writer.write(file.content) + writer.write("</script>\n") + } + } + + /** + * PhantomJS doesn't support Function.prototype.bind. We polyfill it. + * https://github.com/ariya/phantomjs/issues/10522 + */ + override protected def initFiles(): Seq[VirtualJSFile] = Seq( + new MemVirtualJSFile("bindPolyfill.js").withContent( + """ + |// Polyfill for Function.bind from Facebook react: + |// https://github.com/facebook/react/blob/3dc10749080a460e48bee46d769763ec7191ac76/src/test/phantomjs-shims.js + |// Originally licensed under Apache 2.0 + |(function() { + | + | var Ap = Array.prototype; + | var slice = Ap.slice; + | var Fp = Function.prototype; + | + | if (!Fp.bind) { + | // PhantomJS doesn't support Function.prototype.bind natively, so + | // polyfill it whenever this module is required. + | Fp.bind = function(context) { + | var func = this; + | var args = slice.call(arguments, 1); + | + | function bound() { + | var invokedAsConstructor = func.prototype && (this instanceof func); + | return func.apply( + | // Ignore the context parameter when invoking the bound function + | // as a constructor. Note that this includes not only constructor + | // invocations using the new keyword but also calls to base class + | // constructors such as BaseClass.call(this, ...) or super(...). + | !invokedAsConstructor && context || this, + | args.concat(slice.call(arguments)) + | ); + | } + | + | // The bound function must share the .prototype of the unbound + | // function so that any object created by one constructor will count + | // as an instance of both constructors. + | bound.prototype = func.prototype; + | + | return bound; + | }; + | } + | + |})(); + |""".stripMargin + ), + new MemVirtualJSFile("scalaJSEnvInfo.js").withContent( + """ + |__ScalaJSEnv = { + | exitFunction: function(status) { + | window.callPhantom({ + | action: 'exit', + | returnValue: status | 0 + | }); + | } + |}; + """.stripMargin + ) + ) + + protected def writeWebpageLauncher(out: Writer): Unit = { + out.write("<html>\n<head>\n<title>Phantom.js Launcher</title>\n") + sendJS(getLibJSFiles(), out) + writeCodeLauncher(code, out) + out.write("</head>\n<body></body>\n</html>\n") + } + + protected def createTmpLauncherFile(): File = { + val webF = createTmpWebpage() + + val launcherTmpF = File.createTempFile("phantomjs-launcher", ".js") + launcherTmpF.deleteOnExit() + + val out = new FileWriter(launcherTmpF) + + try { + out.write( + s"""// Scala.js Phantom.js launcher + |var page = require('webpage').create(); + |var url = ${toJSstr(webF.getAbsolutePath)}; + |var autoExit = $autoExit; + |page.onConsoleMessage = function(msg) { + | console.log(msg); + |}; + |page.onError = function(msg, trace) { + | console.error(msg); + | if (trace && trace.length) { + | console.error(''); + | trace.forEach(function(t) { + | console.error(' ' + t.file + ':' + t.line + (t.function ? ' (in function "' + t.function +'")' : '')); + | }); + | } + | + | phantom.exit(2); + |}; + |page.onCallback = function(data) { + | if (!data.action) { + | console.error('Called callback without action'); + | phantom.exit(3); + | } else if (data.action === 'exit') { + | phantom.exit(data.returnValue || 0); + | } else if (data.action === 'setAutoExit') { + | if (typeof(data.autoExit) === 'boolean') + | autoExit = data.autoExit; + | else + | autoExit = true; + | } else { + | console.error('Unknown callback action ' + data.action); + | phantom.exit(4); + | } + |}; + |page.open(url, function (status) { + | if (autoExit || status !== 'success') + | phantom.exit(status !== 'success'); + |}); + |""".stripMargin) + } finally { + out.close() + } + + logger.debug( + "PhantomJS using launcher at: " + launcherTmpF.getAbsolutePath()) + + launcherTmpF + } + + protected def createTmpWebpage(): File = { + val webTmpF = File.createTempFile("phantomjs-launcher-webpage", ".html") + webTmpF.deleteOnExit() + + val out = new BufferedWriter(new FileWriter(webTmpF)) + try { + writeWebpageLauncher(out) + } finally { + out.close() + } + + logger.debug( + "PhantomJS using webpage launcher at: " + webTmpF.getAbsolutePath()) + + webTmpF + } + + protected def writeCodeLauncher(code: VirtualJSFile, out: Writer): Unit = { + out.write("""<script type="text/javascript">""" + "\n") + out.write("// Phantom.js code launcher\n") + out.write(s"// Origin: ${code.path}\n") + out.write("window.addEventListener('load', function() {\n") + out.write(code.content) + out.write("}, false);\n") + out.write("</script>\n") + } + } + + protected def htmlEscape(str: String): String = str.flatMap { + case '<' => "<" + case '>' => ">" + case '"' => """ + case '&' => "&" + case c => c :: Nil + } + +} + +object PhantomJSEnv { + private final val MaxByteMessageSize = 32768 // 32 KB + private final val MaxCharMessageSize = MaxByteMessageSize / 2 // 2B per char + private final val MaxCharPayloadSize = MaxCharMessageSize - 1 // frag flag +} diff --git a/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJettyClassLoader.scala b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJettyClassLoader.scala new file mode 100644 index 0000000..02c229b --- /dev/null +++ b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJettyClassLoader.scala @@ -0,0 +1,63 @@ +package scala.scalajs.sbtplugin.env.phantomjs + +import scala.scalajs.tools.io.IO + +/** A special [[ClassLoader]] to load the Jetty 8 dependency of [[PhantomJSEnv]] + * in a private space. + * + * It loads everything that belongs to [[JettyWebsocketManager]] itself (while + * retrieving the requested class file from its parent. + * For all other classes, it first tries to load them from [[jettyLoader]], + * which should only contain the Jetty 8 classpath. + * If this fails, it delegates to its parent. + * + * The rationale is, that [[JettyWebsocketManager]] and its dependees can use + * the classes on the Jetty 8 classpath, while they remain hidden from the rest + * of the Java world. This allows to load another version of Jetty in the same + * JVM for the rest of the project. + */ +private[sbtplugin] class PhantomJettyClassLoader(jettyLoader: ClassLoader, + parent: ClassLoader) extends ClassLoader(parent) { + + def this(loader: ClassLoader) = + this(loader, ClassLoader.getSystemClassLoader()) + + /** Classes needed to bridge private jetty classpath and public PhantomJS + * Basically everything defined in JettyWebsocketManager. + */ + private val bridgeClasses = Set( + "scala.scalajs.sbtplugin.env.phantomjs.JettyWebsocketManager", + "scala.scalajs.sbtplugin.env.phantomjs.JettyWebsocketManager$WSLogger", + "scala.scalajs.sbtplugin.env.phantomjs.JettyWebsocketManager$ComWebSocketListener", + "scala.scalajs.sbtplugin.env.phantomjs.JettyWebsocketManager$$anon$1", + "scala.scalajs.sbtplugin.env.phantomjs.JettyWebsocketManager$$anon$2" + ) + + override protected def loadClass(name: String, resolve: Boolean): Class[_] = { + if (bridgeClasses.contains(name)) { + // Load bridgeClasses manually since they must be associated to this + // class loader, rather than the parent class loader in order to find the + // jetty classes + + // First check if we have loaded it already + Option(findLoadedClass(name)) getOrElse { + val wsManager = + parent.getResourceAsStream(name.replace('.', '/') + ".class") + + if (wsManager == null) { + throw new ClassNotFoundException(name) + } else { + val buf = IO.readInputStreamToByteArray(wsManager) + defineClass(name, buf, 0, buf.length) + } + } + } else { + try { + jettyLoader.loadClass(name) + } catch { + case _: ClassNotFoundException => + super.loadClass(name, resolve) + } + } + } +} diff --git a/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/WebsocketListener.scala b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/WebsocketListener.scala new file mode 100644 index 0000000..4faac64 --- /dev/null +++ b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/WebsocketListener.scala @@ -0,0 +1,10 @@ +package scala.scalajs.sbtplugin.env.phantomjs + +private[phantomjs] trait WebsocketListener { + def onRunning(): Unit + def onOpen(): Unit + def onClose(): Unit + def onMessage(msg: String): Unit + + def log(msg: String): Unit +} diff --git a/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/WebsocketManager.scala b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/WebsocketManager.scala new file mode 100644 index 0000000..a466841 --- /dev/null +++ b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/WebsocketManager.scala @@ -0,0 +1,10 @@ +package scala.scalajs.sbtplugin.env.phantomjs + +private[phantomjs] trait WebsocketManager { + def start(): Unit + def stop(): Unit + def sendMessage(msg: String): Unit + def localPort: Int + def isConnected: Boolean + def isClosed: Boolean +} diff --git a/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/rhino/LazyScalaJSScope.scala b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/rhino/LazyScalaJSScope.scala new file mode 100644 index 0000000..d4cdaee --- /dev/null +++ b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/rhino/LazyScalaJSScope.scala @@ -0,0 +1,96 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js sbt plugin ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.sbtplugin.env.rhino + +import scala.collection.mutable + +import org.mozilla.javascript.Scriptable + +/** A proxy for a ScalaJS "scope" field that loads scripts lazily + * + * E.g., ScalaJS.c, which is a scope with the Scala.js classes, can be + * turned to a LazyScalaJSScope. Upon first access to a field of ScalaJS.c, + * say ScalaJS.c.scala_Option, the script defining that particular + * field will be loaded. + * This is possible because the relative path to the script can be derived + * from the name of the property being accessed. + * + * It is immensely useful, because it allows to load lazily only the scripts + * that are actually needed. + */ +class LazyScalaJSScope( + coreLib: ScalaJSCoreLib, + globalScope: Scriptable, + base: Scriptable, + isModule: Boolean = false, + isTraitImpl: Boolean = false) extends Scriptable { + + private val fields = mutable.HashMap.empty[String, Any] + private var prototype: Scriptable = _ + private var parentScope: Scriptable = _ + + { + // Pre-fill fields with the properties of `base` + for (id <- base.getIds()) { + (id.asInstanceOf[Any]: @unchecked) match { + case name: String => put(name, this, base.get(name, base)) + case index: Int => put(index, this, base.get(index, base)) + } + } + } + + private def load(name: String): Unit = + coreLib.load(globalScope, propNameToEncodedName(name)) + + private def propNameToEncodedName(name: String): String = { + if (isTraitImpl) name.split("__")(0) + else if (isModule) name + "$" + else name + } + + override def getClassName() = "LazyScalaJSScope" + + override def get(name: String, start: Scriptable) = { + fields.getOrElse(name, { + load(name) + fields.getOrElse(name, Scriptable.NOT_FOUND) + }).asInstanceOf[AnyRef] + } + override def get(index: Int, start: Scriptable) = + get(index.toString, start) + + override def has(name: String, start: Scriptable) = + fields.contains(name) + override def has(index: Int, start: Scriptable) = + has(index.toString, start) + + override def put(name: String, start: Scriptable, value: Any) = { + fields(name) = value + } + override def put(index: Int, start: Scriptable, value: Any) = + put(index.toString, start, value) + + override def delete(name: String) = () + override def delete(index: Int) = () + + override def getPrototype() = prototype + override def setPrototype(value: Scriptable) = prototype = value + + override def getParentScope() = parentScope + override def setParentScope(value: Scriptable) = parentScope = value + + override def getIds() = fields.keys.toArray + + override def getDefaultValue(hint: java.lang.Class[_]) = { + base.getDefaultValue(hint) + } + + override def hasInstance(instance: Scriptable) = false +} diff --git a/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/rhino/RhinoJSEnv.scala b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/rhino/RhinoJSEnv.scala new file mode 100644 index 0000000..cd35ff6 --- /dev/null +++ b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/rhino/RhinoJSEnv.scala @@ -0,0 +1,303 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js sbt plugin ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.sbtplugin.env.rhino + +import scala.scalajs.tools.sem.Semantics +import scala.scalajs.tools.io._ +import scala.scalajs.tools.classpath._ +import scala.scalajs.tools.env._ +import scala.scalajs.tools.logging._ + +import scala.io.Source + +import scala.collection.mutable + +import scala.concurrent.{Future, Promise, Await} +import scala.concurrent.duration.Duration + +import org.mozilla.javascript._ + +class RhinoJSEnv(semantics: Semantics, + withDOM: Boolean = false) extends ComJSEnv { + + import RhinoJSEnv._ + + /** Executes code in an environment where the Scala.js library is set up to + * load its classes lazily. + * + * Other .js scripts in the inputs are executed eagerly before the provided + * `code` is called. + */ + override def jsRunner(classpath: CompleteClasspath, code: VirtualJSFile, + logger: Logger, console: JSConsole): JSRunner = { + new Runner(classpath, code, logger, console) + } + + private class Runner(classpath: CompleteClasspath, code: VirtualJSFile, + logger: Logger, console: JSConsole) extends JSRunner { + def run(): Unit = internalRunJS(classpath, code, logger, console, None) + } + + override def asyncRunner(classpath: CompleteClasspath, code: VirtualJSFile, + logger: Logger, console: JSConsole): AsyncJSRunner = { + new AsyncRunner(classpath, code, logger, console) + } + + private class AsyncRunner(classpath: CompleteClasspath, code: VirtualJSFile, + logger: Logger, console: JSConsole) extends AsyncJSRunner { + + private[this] val promise = Promise[Unit] + + private[this] val thread = new Thread { + override def run(): Unit = { + try { + internalRunJS(classpath, code, logger, console, optChannel) + promise.success(()) + } catch { + case t: Throwable => + promise.failure(t) + } + } + } + + def start(): Future[Unit] = { + thread.start() + promise.future + } + + def stop(): Unit = thread.interrupt() + + def isRunning(): Boolean = !promise.isCompleted + + def await(): Unit = Await.result(promise.future, Duration.Inf) + + protected def optChannel(): Option[Channel] = None + } + + override def comRunner(classpath: CompleteClasspath, code: VirtualJSFile, + logger: Logger, console: JSConsole): ComJSRunner = { + new ComRunner(classpath, code, logger, console) + } + + private class ComRunner(classpath: CompleteClasspath, code: VirtualJSFile, + logger: Logger, console: JSConsole) + extends AsyncRunner(classpath, code, logger, console) with ComJSRunner { + + private[this] val channel = new Channel + + override protected def optChannel(): Option[Channel] = Some(channel) + + def send(msg: String): Unit = { + try { + channel.sendToJS(msg) + } catch { + case _: ChannelClosedException => + throw new ComJSEnv.ComClosedException + } + } + + def receive(): String = { + try { + channel.recvJVM() + } catch { + case _: ChannelClosedException => + throw new ComJSEnv.ComClosedException + } + } + + def close(): Unit = channel.close() + + override def stop(): Unit = { + close() + super.stop() + } + + } + + private def internalRunJS(classpath: CompleteClasspath, code: VirtualJSFile, + logger: Logger, console: JSConsole, optChannel: Option[Channel]): Unit = { + + val context = Context.enter() + try { + val scope = context.initStandardObjects() + + if (withDOM) { + // Fetch env.rhino.js from webjar + val name = "env.rhino.js" + val path = "/META-INF/resources/webjars/envjs/1.2/" + name + val resource = getClass.getResource(path) + assert(resource != null, s"need $name as resource") + + // Rhino can't optimize envjs + context.setOptimizationLevel(-1) + + // Don't print envjs header + scope.addFunction("print", args => ()) + + // Pipe file to Rhino + val reader = Source.fromURL(resource).bufferedReader + context.evaluateReader(scope, reader, name, 1, null); + + // No need to actually define print here: It is captured by envjs to + // implement console.log, which we'll override in the next statement + } + + // Make sure Rhino does not do its magic for JVM top-level packages (#364) + val PackagesObject = + ScriptableObject.getProperty(scope, "Packages").asInstanceOf[Scriptable] + val topLevelPackageIds = ScriptableObject.getPropertyIds(PackagesObject) + for (id <- topLevelPackageIds) (id: Any) match { + case name: String => ScriptableObject.deleteProperty(scope, name) + case index: Int => ScriptableObject.deleteProperty(scope, index) + case _ => // should not happen, I think, but with Rhino you never know + } + + // Setup console.log + val jsconsole = context.newObject(scope) + jsconsole.addFunction("log", _.foreach(console.log _)) + ScriptableObject.putProperty(scope, "console", jsconsole) + + // Optionally setup scalaJSCom + var recvCallback: Option[String => Unit] = None + for (channel <- optChannel) { + val comObj = context.newObject(scope) + + comObj.addFunction("send", s => + channel.sendToJVM(Context.toString(s(0)))) + + comObj.addFunction("init", s => s(0) match { + case f: Function => + val cb: String => Unit = + msg => f.call(context, scope, scope, Array(msg)) + recvCallback = Some(cb) + case _ => + sys.error("First argument to init must be a function") + }) + + comObj.addFunction("close", _ => { + // Tell JVM side we won't send anything + channel.close() + // Internally register that we're done + recvCallback = None + }) + + ScriptableObject.putProperty(scope, "scalajsCom", comObj) + } + + try { + // Make the classpath available. Either through lazy loading or by + // simply inserting + classpath match { + case cp: IRClasspath => + // Setup lazy loading classpath and source mapper + val optLoader = if (cp.scalaJSIR.nonEmpty) { + val loader = new ScalaJSCoreLib(semantics, cp) + + // Setup sourceMapper + val scalaJSenv = context.newObject(scope) + + scalaJSenv.addFunction("sourceMapper", args => { + val trace = Context.toObject(args(0), scope) + loader.mapStackTrace(trace, context, scope) + }) + + ScriptableObject.putProperty(scope, "__ScalaJSEnv", scalaJSenv) + + Some(loader) + } else { + None + } + + // Load JS libraries + cp.jsLibs.foreach(dep => context.evaluateFile(scope, dep.lib)) + + optLoader.foreach(_.insertInto(context, scope)) + case cp => + cp.allCode.foreach(context.evaluateFile(scope, _)) + } + + context.evaluateFile(scope, code) + + // Callback the com channel if necessary (if comCallback = None, channel + // wasn't initialized on the client) + for ((channel, callback) <- optChannel zip recvCallback) { + try { + while (recvCallback.isDefined) + callback(channel.recvJS()) + } catch { + case _: ChannelClosedException => + // the JVM side closed the connection + } + } + + // Enusre the channel is closed to release JVM side + optChannel.foreach(_.close) + + } catch { + case e: RhinoException => + // Trace here, since we want to be in the context to trace. + logger.trace(e) + sys.error(s"Exception while running JS code: ${e.getMessage}") + } + } finally { + Context.exit() + } + } + +} + +object RhinoJSEnv { + + /** Communication channel between the Rhino thread and the rest of the JVM */ + private class Channel { + private[this] var _closed = false + private[this] val js2jvm = mutable.Queue.empty[String] + private[this] val jvm2js = mutable.Queue.empty[String] + + def sendToJS(msg: String): Unit = synchronized { + jvm2js.enqueue(msg) + notify() + } + + def sendToJVM(msg: String): Unit = synchronized { + js2jvm.enqueue(msg) + notify() + } + + def recvJVM(): String = synchronized { + while (js2jvm.isEmpty && ensureOpen()) + wait() + + js2jvm.dequeue() + } + + def recvJS(): String = synchronized { + while (jvm2js.isEmpty && ensureOpen()) + wait() + + jvm2js.dequeue() + } + + def close(): Unit = synchronized { + _closed = true + notify() + } + + /** Throws if the channel is closed and returns true */ + private def ensureOpen(): Boolean = { + if (_closed) + throw new ChannelClosedException + true + } + } + + private class ChannelClosedException extends Exception + +} diff --git a/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/rhino/ScalaJSCoreLib.scala b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/rhino/ScalaJSCoreLib.scala new file mode 100644 index 0000000..e937e5b --- /dev/null +++ b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/rhino/ScalaJSCoreLib.scala @@ -0,0 +1,173 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js sbt plugin ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.sbtplugin.env.rhino + +import scala.collection.mutable + +import org.mozilla.javascript.{Context, Scriptable} + +import scala.scalajs.ir + +import scala.scalajs.tools.sem.Semantics +import scala.scalajs.tools.javascript.{Printers, ScalaJSClassEmitter} +import scala.scalajs.tools.io._ +import scala.scalajs.tools.classpath._ +import scala.scalajs.tools.corelib._ + +class ScalaJSCoreLib(semantics: Semantics, classpath: IRClasspath) { + import ScalaJSCoreLib._ + + private val (providers, exportedSymbols) = { + val providers = mutable.Map.empty[String, VirtualScalaJSIRFile] + val exportedSymbols = mutable.ListBuffer.empty[String] + + for (irFile <- classpath.scalaJSIR) { + val info = irFile.roughInfo + providers += info.encodedName -> irFile + if (info.isExported) + exportedSymbols += info.encodedName + } + + (providers, exportedSymbols) + } + + def insertInto(context: Context, scope: Scriptable) = { + CoreJSLibs.libs(semantics).foreach(context.evaluateFile(scope, _)) + lazifyScalaJSFields(scope) + + // Make sure exported symbols are loaded + val ScalaJS = Context.toObject(scope.get("ScalaJS", scope), scope) + val c = Context.toObject(ScalaJS.get("c", ScalaJS), scope) + for (encodedName <- exportedSymbols) + c.get(encodedName, c) + } + + /** Source maps the given stack trace (where possible) */ + def mapStackTrace(stackTrace: Scriptable, + context: Context, scope: Scriptable): Scriptable = { + val count = Context.toNumber(stackTrace.get("length", stackTrace)).toInt + + // Maps file -> max line (0-based) + val neededMaps = mutable.Map.empty[String, Int] + + // Collect required line counts + for (i <- 0 until count) { + val elem = Context.toObject(stackTrace.get(i, stackTrace), scope) + val fileName = Context.toString(elem.get("fileName", elem)) + + if (fileName.endsWith(PseudoFileSuffix) && + providers.contains(fileName.stripSuffix(PseudoFileSuffix))) { + + val curMaxLine = neededMaps.getOrElse(fileName, -1) + val reqLine = Context.toNumber(elem.get("lineNumber", elem)).toInt - 1 + + if (reqLine > curMaxLine) + neededMaps.put(fileName, reqLine) + } + } + + // Map required files + val maps = + for ((fileName, maxLine) <- neededMaps) + yield (fileName, getSourceMapper(fileName, maxLine)) + + // Create new stack trace to return + val res = context.newArray(scope, count) + + for (i <- 0 until count) { + val elem = Context.toObject(stackTrace.get(i, stackTrace), scope) + val fileName = Context.toString(elem.get("fileName", elem)) + val line = Context.toNumber(elem.get("lineNumber", elem)).toInt - 1 + + val pos = maps.get(fileName).fold(ir.Position.NoPosition)(_(line)) + + val newElem = + if (pos.isDefined) newPosElem(scope, context, elem, pos) + else elem + + res.put(i, res, newElem) + } + + res + } + + private def getSourceMapper(fileName: String, untilLine: Int) = { + val irFile = providers(fileName.stripSuffix(PseudoFileSuffix)) + val mapper = new Printers.ReverseSourceMapPrinter(untilLine) + val classDef = irFile.tree + val desugared = new ScalaJSClassEmitter(semantics).genClassDef(classDef) + mapper.reverseSourceMap(desugared) + mapper + } + + private def newPosElem(scope: Scriptable, context: Context, + origElem: Scriptable, pos: ir.Position): Scriptable = { + assert(pos.isDefined) + + val elem = context.newObject(scope) + + elem.put("declaringClass", elem, origElem.get("declaringClass", origElem)) + elem.put("methodName", elem, origElem.get("methodName", origElem)) + elem.put("fileName", elem, pos.source.toString) + elem.put("lineNumber", elem, pos.line + 1) + elem.put("columnNumber", elem, pos.column + 1) + + elem + } + + private val scalaJSLazyFields = Seq( + Info("d"), + Info("c"), + Info("h"), + Info("i", isTraitImpl = true), + Info("n", isModule = true), + Info("m", isModule = true), + Info("is"), + Info("as"), + Info("isArrayOf"), + Info("asArrayOf")) + + private def lazifyScalaJSFields(scope: Scriptable) = { + val ScalaJS = Context.toObject(scope.get("ScalaJS", scope), scope) + + def makeLazyScalaJSScope(base: Scriptable, isModule: Boolean, isTraitImpl: Boolean) = + new LazyScalaJSScope(this, scope, base, isModule, isTraitImpl) + + for (Info(name, isModule, isTraitImpl) <- scalaJSLazyFields) { + val base = ScalaJS.get(name, ScalaJS).asInstanceOf[Scriptable] + val lazified = makeLazyScalaJSScope(base, isModule, isTraitImpl) + ScalaJS.put(name, ScalaJS, lazified) + } + } + + private[rhino] def load(scope: Scriptable, encodedName: String): Unit = { + providers.get(encodedName) foreach { irFile => + val codeWriter = new java.io.StringWriter + val printer = new Printers.JSTreePrinter(codeWriter) + val classDef = irFile.tree + val desugared = new ScalaJSClassEmitter(semantics).genClassDef(classDef) + printer.printTopLevelTree(desugared) + printer.complete() + val ctx = Context.getCurrentContext() + val fakeFileName = encodedName + PseudoFileSuffix + ctx.evaluateString(scope, codeWriter.toString(), + fakeFileName, 1, null) + } + } +} + +object ScalaJSCoreLib { + private case class Info(name: String, + isModule: Boolean = false, isTraitImpl: Boolean = false) + + private val EncodedNameLine = raw""""encodedName": *"([^"]+)"""".r.unanchored + + private final val PseudoFileSuffix = ".sjsir" +} diff --git a/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/rhino/package.scala b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/rhino/package.scala new file mode 100644 index 0000000..926fbb2 --- /dev/null +++ b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/rhino/package.scala @@ -0,0 +1,42 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js sbt plugin ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.sbtplugin.env + +import org.mozilla.javascript._ + +import scala.scalajs.tools.io._ + +package object rhino { + + implicit class ContextOps(val self: Context) extends AnyVal { + def evaluateFile(scope: Scriptable, file: VirtualJSFile, + securityDomain: AnyRef = null): Any = { + self.evaluateString(scope, file.content, file.path, 1, securityDomain) + } + } + + implicit class ScriptableObjectOps(val self: Scriptable) { + def addFunction(name: String, function: Array[AnyRef] => Any) = { + val rhinoFunction = + new BaseFunction { + ScriptRuntime.setFunctionProtoAndParent(this, self) + override def call(context: Context, scope: Scriptable, + thisObj: Scriptable, args: Array[AnyRef]): AnyRef = { + function(args) match { + case () => Undefined.instance + case r => r.asInstanceOf[AnyRef] + } + } + } + + ScriptableObject.putProperty(self, name, rhinoFunction) + } + } +} diff --git a/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/impl/DependencyBuilders.scala b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/impl/DependencyBuilders.scala new file mode 100644 index 0000000..32ffb94 --- /dev/null +++ b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/impl/DependencyBuilders.scala @@ -0,0 +1,99 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js sbt plugin ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.sbtplugin +package impl + +import scala.language.implicitConversions +import scala.language.experimental.macros + +import sbt._ + +import StringUtilities.nonEmpty + +trait DependencyBuilders { + final implicit def toScalaJSGroupID(groupID: String): ScalaJSGroupID = { + nonEmpty(groupID, "Group ID") + new ScalaJSGroupID(groupID) + } + + /** Builder to allow for stuff like: + * + * ProvidedJS / "foo.js" + * ProvidedJS / "foo.js" % "test" + * + */ + object ProvidedJS { + def /(name: String): ProvidedJSModuleID = ProvidedJSModuleID(name, None) + } + + /** Builder to allow for stuff like: + * + * "org.webjars" % "jquery" % "1.10.2" / "jquery.js" + * "org.webjars" % "jquery" % "1.10.2" / "jquery.js" % "test" + * + */ + implicit class JSModuleIDBuilder(module: ModuleID) { + def /(name: String): JarJSModuleID = JarJSModuleID(module, name) + } +} + +final class ScalaJSGroupID private[sbtplugin] (private val groupID: String) { + def %%%(artifactID: String): CrossGroupArtifactID = + macro ScalaJSGroupID.auto_impl + + def %%%!(artifactID: String): CrossGroupArtifactID = + ScalaJSGroupID.withCross(this, artifactID, ScalaJSCrossVersion.binary) +} + +object ScalaJSGroupID { + import scala.reflect.macros.Context + + /** Internal. Used by the macro implementing [[ScalaJSGroupID.%%%]]. Use: + * {{{ + * ("a" % artifactID % revision).cross(cross) + * }}} + * instead. + */ + def withCross(groupID: ScalaJSGroupID, artifactID: String, + cross: CrossVersion): CrossGroupArtifactID = { + nonEmpty(artifactID, "Artifact ID") + new CrossGroupArtifactID(groupID.groupID, artifactID, cross) + } + + def auto_impl(c: Context { type PrefixType = ScalaJSGroupID })( + artifactID: c.Expr[String]): c.Expr[CrossGroupArtifactID] = { + import c.universe._ + + // Hack to work around bug in sbt macros (wrong way of collecting local + // definitions) + val keysSym = rootMirror.staticModule( + "_root_.scala.scalajs.sbtplugin.ScalaJSPlugin.autoImport") + val keys = c.Expr[ScalaJSPlugin.autoImport.type](Ident(keysSym)) + + reify { + val cross = { + if (keys.splice.jsDependencies.?.value.isDefined) + ScalaJSCrossVersion.binary + else + CrossVersion.binary + } + ScalaJSGroupID.withCross(c.prefix.splice, artifactID.splice, cross) + } + } + +} + +final class CrossGroupArtifactID(groupID: String, + artifactID: String, crossVersion: CrossVersion) { + def %(revision: String): ModuleID = { + nonEmpty(revision, "Revision") + ModuleID(groupID, artifactID, revision).cross(crossVersion) + } +} diff --git a/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/Events.scala b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/Events.scala new file mode 100644 index 0000000..f13c195 --- /dev/null +++ b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/Events.scala @@ -0,0 +1,35 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js sbt plugin ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.sbtplugin.testing + +import sbt.testing.{Event => SbtEvent, _} + +class Events(taskDef: TaskDef) { + + abstract class Event(val status: Status, + val throwable: OptionalThrowable = new OptionalThrowable) extends SbtEvent { + val fullyQualifiedName = taskDef.fullyQualifiedName + val fingerprint = taskDef.fingerprint + val selector = taskDef.selectors.headOption.getOrElse(new SuiteSelector) + val duration = -1L + } + + case class Error(exception: Throwable) extends Event( + Status.Error, new OptionalThrowable(exception)) + + case class Failure(exception: Throwable) extends Event( + Status.Failure, new OptionalThrowable(exception)) + + case object Succeeded extends Event(Status.Success) + case object Skipped extends Event(Status.Skipped) + case object Pending extends Event(Status.Pending) + case object Ignored extends Event(Status.Ignored) + case object Canceled extends Event(Status.Canceled) +} diff --git a/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/JSClasspathLoader.scala b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/JSClasspathLoader.scala new file mode 100644 index 0000000..bfe0ffc --- /dev/null +++ b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/JSClasspathLoader.scala @@ -0,0 +1,15 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js sbt plugin ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.sbtplugin.testing + +import scala.scalajs.tools.classpath.CompleteClasspath + +/** A dummy ClassLoader to pass on Scala.js ClasspathContents to tests */ +final case class JSClasspathLoader(cp: CompleteClasspath) extends ClassLoader diff --git a/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/SbtTestLoggerAccWrapper.scala b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/SbtTestLoggerAccWrapper.scala new file mode 100644 index 0000000..dfebe00 --- /dev/null +++ b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/SbtTestLoggerAccWrapper.scala @@ -0,0 +1,22 @@ +package scala.scalajs.sbtplugin.testing + +import scala.scalajs.tools.logging._ +import sbt.testing.{ Logger => SbtTestLogger } + +class SbtTestLoggerAccWrapper(logger: Seq[SbtTestLogger]) extends Logger { + + import scala.scalajs.sbtplugin.Implicits._ + import Level._ + + def log(level: Level, message: => String): Unit = level match { + case Error => logger.foreach(_.error(message)) + case Warn => logger.foreach(_.warn(message)) + case Info => logger.foreach(_.info(message)) + case Debug => logger.foreach(_.debug(message)) + } + + def success(message: => String): Unit = logger.foreach(_.info(message)) + + def trace(t: => Throwable): Unit = logger.foreach(_.trace(t)) + +} diff --git a/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/TestException.scala b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/TestException.scala new file mode 100644 index 0000000..b4cb09b --- /dev/null +++ b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/TestException.scala @@ -0,0 +1,9 @@ +package scala.scalajs.sbtplugin.testing + +/** Dummy Exception to wrap stack traces passed to SBT */ +class TestException( + message: String, + stackTrace: Array[StackTraceElement] +) extends Exception(message) { + override def getStackTrace = stackTrace +} diff --git a/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/TestFramework.scala b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/TestFramework.scala new file mode 100644 index 0000000..ab43bfe --- /dev/null +++ b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/TestFramework.scala @@ -0,0 +1,52 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js sbt plugin ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.sbtplugin.testing + +import scala.scalajs.tools.env._ +import scala.scalajs.tools.classpath._ + +import sbt._ +import sbt.testing._ +import sbt.classpath.ClasspathFilter + +import java.net.URLClassLoader + +class TestFramework( + environment: JSEnv, + jsConsole: JSConsole, + testFramework: String) extends Framework { + + val name = "Scala.js Test Framework" + + lazy val fingerprints = Array[Fingerprint](f1) + + private val f1 = new SubclassFingerprint { + val isModule = true + val superclassName = "scala.scalajs.testbridge.Test" + val requireNoArgConstructor = true + } + + def runner(args: Array[String], remoteArgs: Array[String], + testClassLoader: ClassLoader): Runner = { + + val jsClasspath = extractClasspath(testClassLoader) + new TestRunner(environment, jsClasspath, jsConsole, + testFramework, args, remoteArgs) + } + + /** extract classpath from ClassLoader (which must be a JSClasspathLoader) */ + private def extractClasspath(cl: ClassLoader) = cl match { + case cl: JSClasspathLoader => cl.cp + case _ => + sys.error("The Scala.js framework only works with a class loader of " + + s"type JSClasspathLoader (${cl.getClass} given)") + } + +} diff --git a/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/TestOutputConsole.scala b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/TestOutputConsole.scala new file mode 100644 index 0000000..9aad956 --- /dev/null +++ b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/TestOutputConsole.scala @@ -0,0 +1,190 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js sbt plugin ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.sbtplugin.testing + +import sbt.testing.Logger +import sbt.testing.EventHandler + +import scala.scalajs.tools.env.JSConsole +import scala.scalajs.tools.sourcemap.SourceMapper +import scala.scalajs.tools.classpath.{CompleteClasspath, IRClasspath} + +import scala.collection.mutable.ArrayBuffer + +import scala.util.Try + +import java.util.regex._ + +/** This parses the messages sent from the test bridge and forwards + * the calls to SBT. It also buffers all log messages and allows to + * pipe them to multiple loggers in a synchronized fashion. This + * ensures that log messages aren't interleaved due to parallelism. + */ +class TestOutputConsole( + base: JSConsole, + handler: EventHandler, + events: Events, + classpath: CompleteClasspath, + noSourceMap: Boolean) extends JSConsole { + + import TestOutputConsole._ + import events._ + + private val traceBuf = new ArrayBuffer[StackTraceElement] + private val logBuffer = new ArrayBuffer[LogElement] + + /* See #727: source mapping does not work with CompleteIRClasspath, so + * don't bother to try. + */ + private val ignoreSourceMapping = + noSourceMap || classpath.isInstanceOf[IRClasspath] + + private lazy val sourceMapper = new SourceMapper(classpath) + + override def log(msg: Any): Unit = { + val data = msg.toString + val sepPos = data.indexOf("|") + + if (sepPos == -1) + log(_.error, s"Malformed message: $data") + else { + val op = data.substring(0, sepPos) + val message = unescape(data.substring(sepPos + 1)) + + op match { + case "console-log" => + base.log(message) + case "error" => + val trace = getTrace() + logWithEvent(_.error, + messageWithStack(message, trace), + Error(new TestException(message, trace)) + ) + case "failure" => + val trace = getTrace() + logWithEvent(_.error, + messageWithStack(message, trace), + Failure(new TestException(message, trace)) + ) + case "succeeded" => + noTrace() + logWithEvent(_.info, message, Succeeded) + case "skipped" => + noTrace() + logWithEvent(_.info, message, Skipped) + case "pending" => + noTrace() + logWithEvent(_.info, message, Pending) + case "ignored" => + noTrace() + logWithEvent(_.info, message, Ignored) + case "canceled" => + noTrace() + logWithEvent(_.info, message, Canceled) + case "error-log" => + noTrace() + log(_.error, message) + case "info" => + noTrace() + log(_.info, message) + case "warn" => + noTrace() + log(_.warn, message) + case "trace" => + val Array(className, methodName, fileName, + lineNumberStr, columnNumberStr) = message.split('|') + + def tryParse(num: String, name: String) = Try(num.toInt).getOrElse { + log(_.warn, s"Couldn't parse $name number in StackTrace: $num") + -1 + } + + val lineNumber = tryParse(lineNumberStr, "line") + val columnNumber = tryParse(columnNumberStr, "column") + + val ste = + new StackTraceElement(className, methodName, fileName, lineNumber) + + if (ignoreSourceMapping) + traceBuf += ste + else + traceBuf += sourceMapper.map(ste, columnNumber) + case _ => + noTrace() + log(_.error, s"Unknown op: $op. Originating log message: $data") + } + } + } + + private def noTrace() = { + if (traceBuf.nonEmpty) + log(_.warn, s"Discarding ${traceBuf.size} stack elements") + traceBuf.clear() + } + + private def getTrace() = { + val res = traceBuf.toArray + traceBuf.clear() + res + } + + private def messageWithStack(message: String, stack: Array[StackTraceElement]): String = + message + stack.mkString("\n", "\n", "") + + private def log(method: LogMethod, message: String): Unit = + logBuffer.append(LogElement(method, message)) + + private def logWithEvent(method: LogMethod, + message: String, event: Event): Unit = { + handler handle event + log(method, message) + } + + def pipeLogsTo(loggers: Array[Logger]): Unit = { + TestOutputConsole.synchronized { + for { + LogElement(method, message) <- logBuffer + logger <- loggers + } method(logger) { + if (logger.ansiCodesSupported) message + else removeColors(message) + } + } + } + + def allLogs: List[LogElement] = logBuffer.toList + + private val colorPattern = raw"\033\[\d{1,2}m" + + private def removeColors(message: String): String = + message.replaceAll(colorPattern, "") + + private val unEscPat = Pattern.compile("(\\\\\\\\|\\\\n|\\\\r)") + private def unescape(message: String): String = { + val m = unEscPat.matcher(message) + val res = new StringBuffer() + while (m.find()) { + val repl = m.group() match { + case "\\\\" => "\\\\" + case "\\n" => "\n" + case "\\r" => "\r" + } + m.appendReplacement(res, repl); + } + m.appendTail(res); + res.toString + } + +} + +object TestOutputConsole { + type LogMethod = Logger => (String => Unit) + case class LogElement(method: LogMethod, message: String) +} diff --git a/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/TestRunner.scala b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/TestRunner.scala new file mode 100644 index 0000000..e5ca2a2 --- /dev/null +++ b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/TestRunner.scala @@ -0,0 +1,37 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js sbt plugin ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.sbtplugin.testing + +import sbt.testing._ + +import scala.scalajs.tools.env._ +import scala.scalajs.tools.classpath._ + +class TestRunner( + environment: JSEnv, + classpath: CompleteClasspath, + jsConsole: JSConsole, + testFramework: String, + val args: Array[String], + val remoteArgs: Array[String]) extends Runner { + + def tasks(taskDefs: Array[TaskDef]): Array[Task] = if (_done) { + throw new IllegalStateException("Done has already been called") + } else { + taskDefs.map(TestTask(environment, classpath, jsConsole, testFramework, args)) + } + + def done(): String = { + _done = true + "" + } + + private var _done = false +} diff --git a/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/TestTask.scala b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/TestTask.scala new file mode 100644 index 0000000..b1cabb9 --- /dev/null +++ b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/TestTask.scala @@ -0,0 +1,110 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js sbt plugin ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.sbtplugin.testing + +import sbt.testing._ + +import scala.scalajs.tools.io._ +import scala.scalajs.tools.classpath._ +import scala.scalajs.tools.env._ + +import scala.scalajs.sbtplugin.JSUtils._ + +import scala.annotation.tailrec +import scala.util.control.NonFatal + +class TestTask( + env: JSEnv, + classpath: CompleteClasspath, + jsConsole: JSConsole, + testFramework: String, + args: Array[String], + val taskDef: TaskDef) extends Task { + + import TestTask._ + + val tags = Array.empty[String] + val options = readArgs(args.toList) + + def execute(eventHandler: EventHandler, + loggers: Array[Logger]): Array[Task] = { + + val runnerFile = testRunnerFile(options.frameworkArgs) + val testConsole = new TestOutputConsole(jsConsole, eventHandler, + new Events(taskDef), classpath, options.noSourceMap) + val logger = new SbtTestLoggerAccWrapper(loggers) + + // Actually execute test + env.jsRunner(classpath, runnerFile, logger, testConsole).run() + + testConsole.pipeLogsTo(loggers) + + Array.empty + } + + private def testRunnerFile(args: List[String]) = { + val testKey = taskDef.fullyQualifiedName + + // Note that taskDef does also have the selector, fingerprint and + // explicitlySpecified value we could pass to the framework. However, we + // believe that these are only moderately useful. Therefore, we'll silently + // ignore them. + + val jsArgArray = listToJS(args) + new MemVirtualJSFile("Generated test launcher file"). + withContent(s"""this${dot2bracket(testFramework)}().safeRunTest( + | scala.scalajs.testbridge.internal.ConsoleTestOutput(), + | $jsArgArray, + | this${dot2bracket(testKey)});""".stripMargin) + } + + +} + +object TestTask { + + def apply(environment: JSEnv, classpath: CompleteClasspath, + jsConsole: JSConsole, testFramework: String, args: Array[String] + )(taskDef: TaskDef) = + new TestTask(environment, classpath, jsConsole, + testFramework, args, taskDef) + + case class ArgOptions( + noSourceMap: Boolean, + frameworkArgs: List[String] + ) + + private def readArgs(args0: List[String]) = { + // State for each option + var noSourceMap = false + + def mkOptions(frameworkArgs: List[String]) = + ArgOptions(noSourceMap, frameworkArgs) + + @tailrec + def read0(args: List[String]): ArgOptions = args match { + case "-no-source-map" :: xs => + noSourceMap = true + read0(xs) + + // Explicitly end our argument list + case "--" :: xs => + mkOptions(xs) + + // Unknown argument + case xs => + mkOptions(xs) + + } + + read0(args0) + } + +} diff --git a/examples/scala-js/sbt-plugin/src/test/scala/scala/scalajs/sbtplugin/test/env/AsyncTests.scala b/examples/scala-js/sbt-plugin/src/test/scala/scala/scalajs/sbtplugin/test/env/AsyncTests.scala new file mode 100644 index 0000000..422c17b --- /dev/null +++ b/examples/scala-js/sbt-plugin/src/test/scala/scala/scalajs/sbtplugin/test/env/AsyncTests.scala @@ -0,0 +1,37 @@ +package scala.scalajs.sbtplugin.test.env + +import scala.scalajs.tools.env._ +import scala.scalajs.tools.io._ +import scala.scalajs.tools.classpath.PartialClasspath +import scala.scalajs.tools.logging._ + +import org.junit.Test +import org.junit.Assert._ + +import scala.concurrent.Await +import scala.concurrent.duration.Duration + +/** A couple of tests that test communication for mix-in into a test suite */ +trait AsyncTests { + + protected def newJSEnv: AsyncJSEnv + + private def emptyCP = PartialClasspath.empty.resolve() + + private def asyncRunner(code: String) = { + val codeVF = new MemVirtualJSFile("testScript.js").withContent(code) + newJSEnv.asyncRunner(emptyCP, codeVF, + new ScalaConsoleLogger(Level.Warn), ConsoleJSConsole) + } + + @Test + def futureTest = { + val runner = asyncRunner("") + val fut = runner.start() + + Await.result(fut, Duration.Inf) + + assertFalse("VM should be terminated", runner.isRunning) + } + +} diff --git a/examples/scala-js/sbt-plugin/src/test/scala/scala/scalajs/sbtplugin/test/env/ComTests.scala b/examples/scala-js/sbt-plugin/src/test/scala/scala/scalajs/sbtplugin/test/env/ComTests.scala new file mode 100644 index 0000000..c16decd --- /dev/null +++ b/examples/scala-js/sbt-plugin/src/test/scala/scala/scalajs/sbtplugin/test/env/ComTests.scala @@ -0,0 +1,206 @@ +package scala.scalajs.sbtplugin.test.env + +import scala.scalajs.tools.env._ +import scala.scalajs.tools.io._ +import scala.scalajs.tools.classpath.PartialClasspath +import scala.scalajs.tools.logging._ + +import org.junit.Test +import org.junit.Assert._ + +/** A couple of tests that test communication for mix-in into a test suite */ +trait ComTests extends AsyncTests { + + protected def newJSEnv: ComJSEnv + + private def emptyCP = PartialClasspath.empty.resolve() + + private def comRunner(code: String) = { + val codeVF = new MemVirtualJSFile("testScript.js").withContent(code) + newJSEnv.comRunner(emptyCP, codeVF, + new ScalaConsoleLogger(Level.Warn), ConsoleJSConsole) + } + + private def assertThrowClosed(msg: String, body: => Unit): Unit = { + val thrown = try { + body + false + } catch { + case _: ComJSEnv.ComClosedException => + true + } + + assertTrue(msg, thrown) + } + + @Test + def comCloseJVMTest = { + val com = comRunner(s""" + scalajsCom.init(function(msg) { scalajsCom.send("received: " + msg); }); + scalajsCom.send("Hello World"); + """) + + com.start() + + assertEquals("Hello World", com.receive()) + + for (i <- 0 to 10) { + com.send(i.toString) + assertEquals(s"received: $i", com.receive()) + } + + com.close() + com.await() + } + + def comCloseJSTestCommon(timeout: Long) = { + val com = comRunner(s""" + scalajsCom.init(function(msg) {}); + for (var i = 0; i < 10; ++i) + scalajsCom.send("msg: " + i); + scalajsCom.close(); + """) + + com.start() + + Thread.sleep(timeout) + + for (i <- 0 until 10) + assertEquals(s"msg: $i", com.receive()) + + assertThrowClosed("Expect receive to throw after closing of channel", + com.receive()) + + com.close() + com.await() + } + + @Test + def comCloseJSTest = comCloseJSTestCommon(0) + + @Test + def comCloseJSTestDelayed = comCloseJSTestCommon(1000) + + @Test + def doubleCloseTest = { + val n = 10 + val com = pingPongRunner(n) + + com.start() + + for (i <- 0 until n) { + com.send("ping") + assertEquals("pong", com.receive()) + } + + com.close() + com.await() + } + + @Test + def multiEnvTest = { + val n = 10 + val envs = List.fill(5)(pingPongRunner(10)) + + envs.foreach(_.start()) + + val ops = List[ComJSRunner => Unit]( + _.send("ping"), + com => assertEquals("pong", com.receive()) + ) + + for { + i <- 0 until n + env <- envs + op <- ops + } op(env) + + envs.foreach(_.close()) + envs.foreach(_.await()) + } + + private def pingPongRunner(count: Int) = { + comRunner(s""" + var seen = 0; + scalajsCom.init(function(msg) { + scalajsCom.send("pong"); + if (++seen >= $count) + scalajsCom.close(); + }); + """) + } + + @Test + def largeMessageTest = { + // 1KB data + val baseMsg = new String(Array.tabulate(512)(_.toChar)) + val baseLen = baseMsg.length + + // Max message size: 1KB * 2^(2*iters+1) = 1MB + val iters = 4 + + val com = comRunner(""" + scalajsCom.init(function(msg) { + scalajsCom.send(msg + msg); + }); + """) + + com.start() + + com.send(baseMsg) + + def resultFactor(iters: Int) = Math.pow(2, 2 * iters + 1).toInt + + for (i <- 0 until iters) { + val reply = com.receive() + + val factor = resultFactor(i) + + assertEquals(baseLen * factor, reply.length) + + for (j <- 0 until factor) + assertEquals(baseMsg, reply.substring(j * baseLen, (j + 1) * baseLen)) + + com.send(reply + reply) + } + + val lastLen = com.receive().length + assertEquals(baseLen * resultFactor(iters), lastLen) + + com.close() + com.await() + } + + @Test + def noInitTest = { + val com = comRunner("") + + com.start() + com.send("Dummy") + com.close() + com.await() + } + + @Test + def stopTest = { + val com = comRunner(s"""scalajsCom.init(function(msg) {});""") + + com.start() + + // Make sure the VM doesn't terminate. + Thread.sleep(1000) + + assertTrue("VM should still be running", com.isRunning) + + // Stop VM instead of closing channel + com.stop() + + try { + com.await() + fail("Stopped VM should be in failure state") + } catch { + case _: Throwable => + } + } + +} diff --git a/examples/scala-js/sbt-plugin/src/test/scala/scala/scalajs/sbtplugin/test/env/JSEnvTest.scala b/examples/scala-js/sbt-plugin/src/test/scala/scala/scalajs/sbtplugin/test/env/JSEnvTest.scala new file mode 100644 index 0000000..2a44c80 --- /dev/null +++ b/examples/scala-js/sbt-plugin/src/test/scala/scala/scalajs/sbtplugin/test/env/JSEnvTest.scala @@ -0,0 +1,44 @@ +package scala.scalajs.sbtplugin.test.env + +import scala.scalajs.tools.env.JSEnv +import scala.scalajs.tools.io.MemVirtualJSFile +import scala.scalajs.tools.classpath.PartialClasspath +import scala.scalajs.tools.logging.NullLogger +import scala.scalajs.tools.env.NullJSConsole + +import org.junit.Assert._ + +abstract class JSEnvTest { + + protected def newJSEnv: JSEnv + + implicit class RunMatcher(codeStr: String) { + + val emptyCP = PartialClasspath.empty.resolve() + val code = new MemVirtualJSFile("testScript.js").withContent(codeStr) + + def hasOutput(expectedOut: String): Unit = { + + val console = new StoreJSConsole() + val logger = new StoreLogger() + + newJSEnv.jsRunner(emptyCP, code, logger, console).run() + + val log = logger.getLog + + assertTrue("VM shouldn't produce log. Log:\n" + + log.mkString("\n"), log.isEmpty) + assertEquals("Output should match", expectedOut, console.getLog) + } + + def fails(): Unit = { + try { + newJSEnv.jsRunner(emptyCP, code, NullLogger, NullJSConsole).run() + assertTrue("Code snipped should fail", false) + } catch { + case e: Exception => + } + } + } + +} diff --git a/examples/scala-js/sbt-plugin/src/test/scala/scala/scalajs/sbtplugin/test/env/NodeJSTest.scala b/examples/scala-js/sbt-plugin/src/test/scala/scala/scalajs/sbtplugin/test/env/NodeJSTest.scala new file mode 100644 index 0000000..9a58b5c --- /dev/null +++ b/examples/scala-js/sbt-plugin/src/test/scala/scala/scalajs/sbtplugin/test/env/NodeJSTest.scala @@ -0,0 +1,54 @@ +package scala.scalajs.sbtplugin.test.env + +import scala.scalajs.sbtplugin.env.nodejs.NodeJSEnv + +import org.junit.Test + +class NodeJSTest extends JSEnvTest with ComTests { + + protected def newJSEnv = new NodeJSEnv + + /** Node.js strips double percentage signs - #500 */ + @Test + def percentageTest = { + val counts = 1 to 15 + val argcs = 1 to 3 + val strings = counts.map("%" * _) + + val strlists = for { + count <- argcs + string <- strings + } yield List.fill(count)(string) + + val codes = for { + strlist <- strlists + } yield { + val args = strlist.map(s => s""""$s"""").mkString(", ") + s"console.log($args);\n" + } + + val result = strlists.map(_.mkString(" ") + "\n").mkString("") + + codes.mkString("").hasOutput(result) + } + + /** Node.js console.log hack didn't allow to log non-Strings - #561 */ + @Test + def nonStringTest = { + + """ + console.log(1); + console.log(undefined); + console.log(null); + console.log({}); + console.log([1,2]); + """ hasOutput + """|1 + |undefined + |null + |[object Object] + |1,2 + |""".stripMargin + } + +} diff --git a/examples/scala-js/sbt-plugin/src/test/scala/scala/scalajs/sbtplugin/test/env/PhantomJSTest.scala b/examples/scala-js/sbt-plugin/src/test/scala/scala/scalajs/sbtplugin/test/env/PhantomJSTest.scala new file mode 100644 index 0000000..23e240d --- /dev/null +++ b/examples/scala-js/sbt-plugin/src/test/scala/scala/scalajs/sbtplugin/test/env/PhantomJSTest.scala @@ -0,0 +1,21 @@ +package scala.scalajs.sbtplugin.test.env + +import scala.scalajs.sbtplugin.env.phantomjs.PhantomJSEnv + +import org.junit.Test + +class PhantomJSTest extends JSEnvTest with ComTests { + + protected def newJSEnv = new PhantomJSEnv + + @Test + def failureTest = { + + """ + var a = {}; + a.foo(); + """.fails() + + } + +} diff --git a/examples/scala-js/sbt-plugin/src/test/scala/scala/scalajs/sbtplugin/test/env/RhinoJSEnvTest.scala b/examples/scala-js/sbt-plugin/src/test/scala/scala/scalajs/sbtplugin/test/env/RhinoJSEnvTest.scala new file mode 100644 index 0000000..4066c41 --- /dev/null +++ b/examples/scala-js/sbt-plugin/src/test/scala/scala/scalajs/sbtplugin/test/env/RhinoJSEnvTest.scala @@ -0,0 +1,9 @@ +package scala.scalajs.sbtplugin.test.env + +import scala.scalajs.sbtplugin.env.rhino._ + +import scala.scalajs.tools.sem._ + +class RhinoJSEnvTest extends ComTests { + protected def newJSEnv = new RhinoJSEnv(Semantics.Defaults) +} diff --git a/examples/scala-js/sbt-plugin/src/test/scala/scala/scalajs/sbtplugin/test/env/StoreJSConsole.scala b/examples/scala-js/sbt-plugin/src/test/scala/scala/scalajs/sbtplugin/test/env/StoreJSConsole.scala new file mode 100644 index 0000000..9c7a84a --- /dev/null +++ b/examples/scala-js/sbt-plugin/src/test/scala/scala/scalajs/sbtplugin/test/env/StoreJSConsole.scala @@ -0,0 +1,14 @@ +package scala.scalajs.sbtplugin.test.env + +import scala.scalajs.tools.env._ + +class StoreJSConsole extends JSConsole { + private[this] val buf = new StringBuilder() + + def log(msg: Any): Unit = { + buf.append(msg.toString) + buf.append('\n') + } + + def getLog: String = buf.toString +} diff --git a/examples/scala-js/sbt-plugin/src/test/scala/scala/scalajs/sbtplugin/test/env/StoreLogger.scala b/examples/scala-js/sbt-plugin/src/test/scala/scala/scalajs/sbtplugin/test/env/StoreLogger.scala new file mode 100644 index 0000000..985b149 --- /dev/null +++ b/examples/scala-js/sbt-plugin/src/test/scala/scala/scalajs/sbtplugin/test/env/StoreLogger.scala @@ -0,0 +1,29 @@ +package scala.scalajs.sbtplugin.test.env + +import scala.scalajs.tools.logging._ + +import scala.collection.mutable.ListBuffer + +class StoreLogger extends Logger { + import StoreLogger._ + + private[this] val buf = new ListBuffer[LogElem] + + def log(level: Level, message: => String): Unit = + buf += Log(level, message) + def success(message: => String): Unit = + buf += Success(message) + def trace(t: => Throwable): Unit = + buf += Trace(t) + + def getLog: List[LogElem] = buf.toList +} + +object StoreLogger { + + abstract class LogElem + case class Log(level: Level, message: String) extends LogElem + case class Success(message: String) extends LogElem + case class Trace(t: Throwable) extends LogElem + +} diff --git a/examples/scala-js/scalalib/overrides-2.10/scala/Console.scala b/examples/scala-js/scalalib/overrides-2.10/scala/Console.scala new file mode 100644 index 0000000..7fc2d50 --- /dev/null +++ b/examples/scala-js/scalalib/overrides-2.10/scala/Console.scala @@ -0,0 +1,468 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2003-2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + + + +package scala + +import java.io.{BufferedReader, InputStream, InputStreamReader, + IOException, OutputStream, PrintStream, Reader} +import java.text.MessageFormat +import scala.util.DynamicVariable + + +/** Implements functionality for + * printing Scala values on the terminal as well as reading specific values. + * Also defines constants for marking up text on ANSI terminals. + * + * @author Matthias Zenger + * @version 1.0, 03/09/2003 + */ +object Console { + + /** Foreground color for ANSI black */ + final val BLACK = "\033[30m" + /** Foreground color for ANSI red */ + final val RED = "\033[31m" + /** Foreground color for ANSI green */ + final val GREEN = "\033[32m" + /** Foreground color for ANSI yellow */ + final val YELLOW = "\033[33m" + /** Foreground color for ANSI blue */ + final val BLUE = "\033[34m" + /** Foreground color for ANSI magenta */ + final val MAGENTA = "\033[35m" + /** Foreground color for ANSI cyan */ + final val CYAN = "\033[36m" + /** Foreground color for ANSI white */ + final val WHITE = "\033[37m" + + /** Background color for ANSI black */ + final val BLACK_B = "\033[40m" + /** Background color for ANSI red */ + final val RED_B = "\033[41m" + /** Background color for ANSI green */ + final val GREEN_B = "\033[42m" + /** Background color for ANSI yellow */ + final val YELLOW_B = "\033[43m" + /** Background color for ANSI blue */ + final val BLUE_B = "\033[44m" + /** Background color for ANSI magenta */ + final val MAGENTA_B = "\033[45m" + /** Background color for ANSI cyan */ + final val CYAN_B = "\033[46m" + /** Background color for ANSI white */ + final val WHITE_B = "\033[47m" + + /** Reset ANSI styles */ + final val RESET = "\033[0m" + /** ANSI bold */ + final val BOLD = "\033[1m" + /** ANSI underlines */ + final val UNDERLINED = "\033[4m" + /** ANSI blink */ + final val BLINK = "\033[5m" + /** ANSI reversed */ + final val REVERSED = "\033[7m" + /** ANSI invisible */ + final val INVISIBLE = "\033[8m" + + private val outVar = new DynamicVariable[PrintStream](java.lang.System.out) + private val errVar = new DynamicVariable[PrintStream](java.lang.System.err) + private val inVar = new DynamicVariable[BufferedReader](null) + //new BufferedReader(new InputStreamReader(java.lang.System.in))) + + /** The default output, can be overridden by `setOut` */ + def out = outVar.value + /** The default error, can be overridden by `setErr` */ + def err = errVar.value + /** The default input, can be overridden by `setIn` */ + def in = inVar.value + + /** Sets the default output stream. + * + * @param out the new output stream. + */ + def setOut(out: PrintStream) { outVar.value = out } + + /** Sets the default output stream for the duration + * of execution of one thunk. + * + * @example {{{ + * withOut(Console.err) { println("This goes to default _error_") } + * }}} + * + * @param out the new output stream. + * @param thunk the code to execute with + * the new output stream active + * @return the results of `thunk` + * @see `withOut[T](out:OutputStream)(thunk: => T)` + */ + def withOut[T](out: PrintStream)(thunk: =>T): T = + outVar.withValue(out)(thunk) + + /** Sets the default output stream. + * + * @param out the new output stream. + */ + def setOut(out: OutputStream): Unit = + setOut(new PrintStream(out)) + + /** Sets the default output stream for the duration + * of execution of one thunk. + * + * @param out the new output stream. + * @param thunk the code to execute with + * the new output stream active + * @return the results of `thunk` + * @see `withOut[T](out:PrintStream)(thunk: => T)` + */ + def withOut[T](out: OutputStream)(thunk: =>T): T = + withOut(new PrintStream(out))(thunk) + + + /** Sets the default error stream. + * + * @param err the new error stream. + */ + def setErr(err: PrintStream) { errVar.value = err } + + /** Set the default error stream for the duration + * of execution of one thunk. + * @example {{{ + * withErr(Console.out) { println("This goes to default _out_") } + * }}} + * + * @param err the new error stream. + * @param thunk the code to execute with + * the new error stream active + * @return the results of `thunk` + * @see `withErr[T](err:OutputStream)(thunk: =>T)` + */ + def withErr[T](err: PrintStream)(thunk: =>T): T = + errVar.withValue(err)(thunk) + + /** Sets the default error stream. + * + * @param err the new error stream. + */ + def setErr(err: OutputStream): Unit = + setErr(new PrintStream(err)) + + /** Sets the default error stream for the duration + * of execution of one thunk. + * + * @param err the new error stream. + * @param thunk the code to execute with + * the new error stream active + * @return the results of `thunk` + * @see `withErr[T](err:PrintStream)(thunk: =>T)` + */ + def withErr[T](err: OutputStream)(thunk: =>T): T = + withErr(new PrintStream(err))(thunk) + + + /** Sets the default input stream. + * + * @param reader specifies the new input stream. + */ + def setIn(reader: Reader) { + inVar.value = new BufferedReader(reader) + } + + /** Sets the default input stream for the duration + * of execution of one thunk. + * + * @example {{{ + * val someFile:Reader = openFile("file.txt") + * withIn(someFile) { + * // Reads a line from file.txt instead of default input + * println(readLine) + * } + * }}} + * + * @param thunk the code to execute with + * the new input stream active + * + * @return the results of `thunk` + * @see `withIn[T](in:InputStream)(thunk: =>T)` + */ + def withIn[T](reader: Reader)(thunk: =>T): T = + inVar.withValue(new BufferedReader(reader))(thunk) + + /** Sets the default input stream. + * + * @param in the new input stream. + */ + def setIn(in: InputStream) { + setIn(new InputStreamReader(in)) + } + + /** Sets the default input stream for the duration + * of execution of one thunk. + * + * @param in the new input stream. + * @param thunk the code to execute with + * the new input stream active + * @return the results of `thunk` + * @see `withIn[T](reader:Reader)(thunk: =>T)` + */ + def withIn[T](in: InputStream)(thunk: =>T): T = + withIn(new InputStreamReader(in))(thunk) + + /** Prints an object to `out` using its `toString` method. + * + * @param obj the object to print; may be null. + */ + def print(obj: Any) { + out.print(if (null == obj) "null" else obj.toString()) + } + + /** Flushes the output stream. This function is required when partial + * output (i.e. output not terminated by a newline character) has + * to be made visible on the terminal. + */ + def flush() { out.flush() } + + /** Prints a newline character on the default output. + */ + def println() { out.println() } + + /** Prints out an object to the default output, followed by a newline character. + * + * @param x the object to print. + */ + def println(x: Any) { out.println(x) } + + /** Prints its arguments as a formatted string to the default output, + * based on a string pattern (in a fashion similar to printf in C). + * + * The interpretation of the formatting patterns is described in + * <a href="" target="contentFrame" class="java/util/Formatter"> + * `java.util.Formatter`</a>. + * + * @param text the pattern for formatting the arguments. + * @param args the arguments used to instantiating the pattern. + * @throws java.lang.IllegalArgumentException if there was a problem with the format string or arguments + */ + def printf(text: String, args: Any*) { out.print(text format (args : _*)) } + + /** Read a full line from the default input. Returns `null` if the end of the + * input stream has been reached. + * + * @return the string read from the terminal or null if the end of stream was reached. + */ + def readLine(): String = in.readLine() + + /** Print formatted text to the default output and read a full line from the default input. + * Returns `null` if the end of the input stream has been reached. + * + * @param text the format of the text to print out, as in `printf`. + * @param args the parameters used to instantiate the format, as in `printf`. + * @return the string read from the default input + */ + def readLine(text: String, args: Any*): String = { + printf(text, args: _*) + readLine() + } + + /** Reads a boolean value from an entire line of the default input. + * Has a fairly liberal interpretation of the input. + * + * @return the boolean value read, or false if it couldn't be converted to a boolean + * @throws java.io.EOFException if the end of the input stream has been reached. + */ + def readBoolean(): Boolean = { + val s = readLine() + if (s == null) + throw new java.io.EOFException("Console has reached end of input") + else + s.toLowerCase() match { + case "true" => true + case "t" => true + case "yes" => true + case "y" => true + case _ => false + } + } + + /** Reads a byte value from an entire line of the default input. + * + * @return the Byte that was read + * @throws java.io.EOFException if the end of the + * input stream has been reached. + * @throws java.lang.NumberFormatException if the value couldn't be converted to a Byte + */ + def readByte(): Byte = { + val s = readLine() + if (s == null) + throw new java.io.EOFException("Console has reached end of input") + else + s.toByte + } + + /** Reads a short value from an entire line of the default input. + * + * @return the short that was read + * @throws java.io.EOFException if the end of the + * input stream has been reached. + * @throws java.lang.NumberFormatException if the value couldn't be converted to a Short + */ + def readShort(): Short = { + val s = readLine() + if (s == null) + throw new java.io.EOFException("Console has reached end of input") + else + s.toShort + } + + /** Reads a char value from an entire line of the default input. + * + * @return the Char that was read + * @throws java.io.EOFException if the end of the + * input stream has been reached. + * @throws java.lang.StringIndexOutOfBoundsException if the line read from default input was empty + */ + def readChar(): Char = { + val s = readLine() + if (s == null) + throw new java.io.EOFException("Console has reached end of input") + else + s charAt 0 + } + + /** Reads an int value from an entire line of the default input. + * + * @return the Int that was read + * @throws java.io.EOFException if the end of the + * input stream has been reached. + * @throws java.lang.NumberFormatException if the value couldn't be converted to an Int + */ + def readInt(): Int = { + val s = readLine() + if (s == null) + throw new java.io.EOFException("Console has reached end of input") + else + s.toInt + } + + /** Reads an long value from an entire line of the default input. + * + * @return the Long that was read + * @throws java.io.EOFException if the end of the + * input stream has been reached. + * @throws java.lang.NumberFormatException if the value couldn't be converted to a Long + */ + def readLong(): Long = { + val s = readLine() + if (s == null) + throw new java.io.EOFException("Console has reached end of input") + else + s.toLong + } + + /** Reads a float value from an entire line of the default input. + * @return the Float that was read. + * @throws java.io.EOFException if the end of the + * input stream has been reached. + * @throws java.lang.NumberFormatException if the value couldn't be converted to a Float + * + */ + def readFloat(): Float = { + val s = readLine() + if (s == null) + throw new java.io.EOFException("Console has reached end of input") + else + s.toFloat + } + + /** Reads a double value from an entire line of the default input. + * + * @return the Double that was read. + * @throws java.io.EOFException if the end of the + * input stream has been reached. + * @throws java.lang.NumberFormatException if the value couldn't be converted to a Float + */ + def readDouble(): Double = { + val s = readLine() + if (s == null) + throw new java.io.EOFException("Console has reached end of input") + else + s.toDouble + } + + /** Reads in some structured input (from the default input), specified by + * a format specifier. See class `java.text.MessageFormat` for details of + * the format specification. + * + * @param format the format of the input. + * @return a list of all extracted values. + * @throws java.io.EOFException if the end of the input stream has been + * reached. + */ + def readf(format: String): List[Any] = { + val s = readLine() + if (s == null) + throw new java.io.EOFException("Console has reached end of input") + else + textComponents(new MessageFormat(format).parse(s)) + } + + /** Reads in some structured input (from the default input), specified by + * a format specifier, returning only the first value extracted, according + * to the format specification. + * + * @param format format string, as accepted by `readf`. + * @return The first value that was extracted from the input + */ + def readf1(format: String): Any = readf(format).head + + /** Reads in some structured input (from the default input), specified + * by a format specifier, returning only the first two values extracted, + * according to the format specification. + * + * @param format format string, as accepted by `readf`. + * @return A [[scala.Tuple2]] containing the first two values extracted + */ + def readf2(format: String): (Any, Any) = { + val res = readf(format) + (res.head, res.tail.head) + } + + /** Reads in some structured input (from the default input), specified + * by a format specifier, returning only the first three values extracted, + * according to the format specification. + * + * @param format format string, as accepted by `readf`. + * @return A [[scala.Tuple3]] containing the first three values extracted + */ + def readf3(format: String): (Any, Any, Any) = { + val res = readf(format) + (res.head, res.tail.head, res.tail.tail.head) + } + + private def textComponents(a: Array[AnyRef]): List[Any] = { + var i: Int = a.length - 1 + var res: List[Any] = Nil + while (i >= 0) { + res = (a(i) match { + case x: java.lang.Boolean => x.booleanValue() + case x: java.lang.Byte => x.byteValue() + case x: java.lang.Short => x.shortValue() + case x: java.lang.Character => x.charValue() + case x: java.lang.Integer => x.intValue() + case x: java.lang.Long => x.longValue() + case x: java.lang.Float => x.floatValue() + case x: java.lang.Double => x.doubleValue() + case x => x + }) :: res; + i -= 1 + } + res + } +} diff --git a/examples/scala-js/scalalib/overrides-2.10/scala/collection/immutable/NumericRange.scala b/examples/scala-js/scalalib/overrides-2.10/scala/collection/immutable/NumericRange.scala new file mode 100644 index 0000000..d3971ee --- /dev/null +++ b/examples/scala-js/scalalib/overrides-2.10/scala/collection/immutable/NumericRange.scala @@ -0,0 +1,285 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2006-2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + + +package scala.collection +package immutable + +import mutable.{ Builder, ListBuffer } +import generic._ + +/** `NumericRange` is a more generic version of the + * `Range` class which works with arbitrary types. + * It must be supplied with an `Integral` implementation of the + * range type. + * + * Factories for likely types include `Range.BigInt`, `Range.Long`, + * and `Range.BigDecimal`. `Range.Int` exists for completeness, but + * the `Int`-based `scala.Range` should be more performant. + * + * {{{ + * val r1 = new Range(0, 100, 1) + * val veryBig = Int.MaxValue.toLong + 1 + * val r2 = Range.Long(veryBig, veryBig + 100, 1) + * assert(r1 sameElements r2.map(_ - veryBig)) + * }}} + * + * TODO: Now the specialization exists there is no clear reason to have + * separate classes for Range/NumericRange. Investigate and consolidate. + * + * @author Paul Phillips + * @version 2.8 + * @define Coll `NumericRange` + * @define coll numeric range + * @define mayNotTerminateInf + * @define willNotTerminateInf + */ +abstract class NumericRange[T] + (val start: T, val end: T, val step: T, val isInclusive: Boolean) + (implicit num: Integral[T]) +extends AbstractSeq[T] with IndexedSeq[T] with Serializable { + /** Note that NumericRange must be invariant so that constructs + * such as "1L to 10 by 5" do not infer the range type as AnyVal. + */ + import num._ + + // See comment in Range for why this must be lazy. + private lazy val numRangeElements: Int = + NumericRange.count(start, end, step, isInclusive) + + override def length = numRangeElements + override def isEmpty = length == 0 + override lazy val last: T = + if (length == 0) Nil.last + else locationAfterN(length - 1) + + /** Create a new range with the start and end values of this range and + * a new `step`. + */ + def by(newStep: T): NumericRange[T] = copy(start, end, newStep) + + /** Create a copy of this range. + */ + def copy(start: T, end: T, step: T): NumericRange[T] + + override def foreach[U](f: T => U) { + var count = 0 + var current = start + while (count < length) { + f(current) + current += step + count += 1 + } + } + + // TODO: these private methods are straight copies from Range, duplicated + // to guard against any (most likely illusory) performance drop. They should + // be eliminated one way or another. + + // Counts how many elements from the start meet the given test. + private def skipCount(p: T => Boolean): Int = { + var current = start + var counted = 0 + + while (counted < length && p(current)) { + counted += 1 + current += step + } + counted + } + // Tests whether a number is within the endpoints, without testing + // whether it is a member of the sequence (i.e. when step > 1.) + private def isWithinBoundaries(elem: T) = !isEmpty && ( + (step > zero && start <= elem && elem <= last ) || + (step < zero && last <= elem && elem <= start) + ) + // Methods like apply throw exceptions on invalid n, but methods like take/drop + // are forgiving: therefore the checks are with the methods. + private def locationAfterN(n: Int): T = start + (step * fromInt(n)) + + // When one drops everything. Can't ever have unchecked operations + // like "end + 1" or "end - 1" because ranges involving Int.{ MinValue, MaxValue } + // will overflow. This creates an exclusive range where start == end + // based on the given value. + private def newEmptyRange(value: T) = NumericRange(value, value, step) + + final override def take(n: Int): NumericRange[T] = ( + if (n <= 0 || length == 0) newEmptyRange(start) + else if (n >= length) this + else new NumericRange.Inclusive(start, locationAfterN(n - 1), step) + ) + + final override def drop(n: Int): NumericRange[T] = ( + if (n <= 0 || length == 0) this + else if (n >= length) newEmptyRange(end) + else copy(locationAfterN(n), end, step) + ) + + def apply(idx: Int): T = { + if (idx < 0 || idx >= length) throw new IndexOutOfBoundsException(idx.toString) + else locationAfterN(idx) + } + + import NumericRange.defaultOrdering + + override def min[T1 >: T](implicit ord: Ordering[T1]): T = + if (ord eq defaultOrdering(num)) { + if (num.signum(step) > 0) start + else last + } else super.min(ord) + + override def max[T1 >: T](implicit ord: Ordering[T1]): T = + if (ord eq defaultOrdering(num)) { + if (num.signum(step) > 0) last + else start + } else super.max(ord) + + // Motivated by the desire for Double ranges with BigDecimal precision, + // we need some way to map a Range and get another Range. This can't be + // done in any fully general way because Ranges are not arbitrary + // sequences but step-valued, so we have a custom method only we can call + // which we promise to use responsibly. + // + // The point of it all is that + // + // 0.0 to 1.0 by 0.1 + // + // should result in + // + // NumericRange[Double](0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0) + // + // and not + // + // NumericRange[Double](0.0, 0.1, 0.2, 0.30000000000000004, 0.4, 0.5, 0.6000000000000001, 0.7000000000000001, 0.8, 0.9) + // + // or perhaps more importantly, + // + // (0.1 to 0.3 by 0.1 contains 0.3) == true + // + private[immutable] def mapRange[A](fm: T => A)(implicit unum: Integral[A]): NumericRange[A] = { + val self = this + + // XXX This may be incomplete. + new NumericRange[A](fm(start), fm(end), fm(step), isInclusive) { + def copy(start: A, end: A, step: A): NumericRange[A] = + if (isInclusive) NumericRange.inclusive(start, end, step) + else NumericRange(start, end, step) + + private lazy val underlyingRange: NumericRange[T] = self + override def foreach[U](f: A => U) { underlyingRange foreach (x => f(fm(x))) } + override def isEmpty = underlyingRange.isEmpty + override def apply(idx: Int): A = fm(underlyingRange(idx)) + override def containsTyped(el: A) = underlyingRange exists (x => fm(x) == el) + } + } + + // a well-typed contains method. + def containsTyped(x: T): Boolean = + isWithinBoundaries(x) && (((x - start) % step) == zero) + + override def contains(x: Any): Boolean = + try containsTyped(x.asInstanceOf[T]) + catch { case _: ClassCastException => false } + + final override def sum[B >: T](implicit num: Numeric[B]): B = { + import num.Ops + if (isEmpty) this.num fromInt 0 + else if (numRangeElements == 1) head + else ((this.num fromInt numRangeElements) * (head + last) / (this.num fromInt 2)) + } + + override lazy val hashCode = super.hashCode() + override def equals(other: Any) = other match { + case x: NumericRange[_] => + (x canEqual this) && (length == x.length) && ( + (length == 0) || // all empty sequences are equal + (start == x.start && last == x.last) // same length and same endpoints implies equality + ) + case _ => + super.equals(other) + } + + override def toString() = { + val endStr = if (length > Range.MAX_PRINT) ", ... )" else ")" + take(Range.MAX_PRINT).mkString("NumericRange(", ", ", endStr) + } +} + +/** A companion object for numeric ranges. + */ +object NumericRange { + + /** Calculates the number of elements in a range given start, end, step, and + * whether or not it is inclusive. Throws an exception if step == 0 or + * the number of elements exceeds the maximum Int. + */ + def count[T](start: T, end: T, step: T, isInclusive: Boolean)(implicit num: Integral[T]): Int = { + val zero = num.zero + val upward = num.lt(start, end) + val posStep = num.gt(step, zero) + + if (step == zero) throw new IllegalArgumentException("step cannot be 0.") + else if (start == end) if (isInclusive) 1 else 0 + else if (upward != posStep) 0 + else { + val diff = num.minus(end, start) + val jumps = num.toLong(num.quot(diff, step)) + val remainder = num.rem(diff, step) + val longCount = jumps + ( + if (!isInclusive && zero == remainder) 0 else 1 + ) + + /** The edge cases keep coming. Since e.g. + * Long.MaxValue + 1 == Long.MinValue + * we do some more improbable seeming checks lest + * overflow turn up as an empty range. + */ + // The second condition contradicts an empty result. + val isOverflow = longCount == 0 && num.lt(num.plus(start, step), end) == upward + + if (longCount > scala.Int.MaxValue || longCount < 0L || isOverflow) { + val word = if (isInclusive) "to" else "until" + val descr = List(start, word, end, "by", step) mkString " " + + throw new IllegalArgumentException(descr + ": seqs cannot contain more than Int.MaxValue elements.") + } + longCount.toInt + } + } + + class Inclusive[T](start: T, end: T, step: T)(implicit num: Integral[T]) + extends NumericRange(start, end, step, true) { + def copy(start: T, end: T, step: T): Inclusive[T] = + NumericRange.inclusive(start, end, step) + + def exclusive: Exclusive[T] = NumericRange(start, end, step) + } + + class Exclusive[T](start: T, end: T, step: T)(implicit num: Integral[T]) + extends NumericRange(start, end, step, false) { + def copy(start: T, end: T, step: T): Exclusive[T] = + NumericRange(start, end, step) + + def inclusive: Inclusive[T] = NumericRange.inclusive(start, end, step) + } + + def apply[T](start: T, end: T, step: T)(implicit num: Integral[T]): Exclusive[T] = + new Exclusive(start, end, step) + def inclusive[T](start: T, end: T, step: T)(implicit num: Integral[T]): Inclusive[T] = + new Inclusive(start, end, step) + + private[collection] val defaultOrdering = Map[Numeric[_], Ordering[_]]( + Numeric.IntIsIntegral -> Ordering.Int, + Numeric.ShortIsIntegral -> Ordering.Short, + Numeric.ByteIsIntegral -> Ordering.Byte, + Numeric.CharIsIntegral -> Ordering.Char, + Numeric.LongIsIntegral -> Ordering.Long + ) + +} + diff --git a/examples/scala-js/scalalib/overrides-2.10/scala/collection/immutable/Range.scala b/examples/scala-js/scalalib/overrides-2.10/scala/collection/immutable/Range.scala new file mode 100644 index 0000000..5c8758d --- /dev/null +++ b/examples/scala-js/scalalib/overrides-2.10/scala/collection/immutable/Range.scala @@ -0,0 +1,424 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2006-2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + + +package scala.collection.immutable + +import scala.collection.parallel.immutable.ParRange + +/** The `Range` class represents integer values in range + * ''[start;end)'' with non-zero step value `step`. + * It's a special case of an indexed sequence. + * For example: + * + * {{{ + * val r1 = 0 until 10 + * val r2 = r1.start until r1.end by r1.step + 1 + * println(r2.length) // = 5 + * }}} + * + * @param start the start of this range. + * @param end the exclusive end of the range. + * @param step the step for the range. + * + * @author Martin Odersky + * @author Paul Phillips + * @version 2.8 + * @since 2.5 + * @see [[http://docs.scala-lang.org/overviews/collections/concrete-immutable-collection-classes.html#ranges "Scala's Collection Library overview"]] + * section on `Ranges` for more information. + * + * @define coll range + * @define mayNotTerminateInf + * @define willNotTerminateInf + * @define doesNotUseBuilders + * '''Note:''' this method does not use builders to construct a new range, + * and its complexity is O(1). + */ +@SerialVersionUID(7618862778670199309L) +@inline +class Range(val start: Int, val end: Int, val step: Int) +extends scala.collection.AbstractSeq[Int] + with IndexedSeq[Int] + with scala.collection.CustomParallelizable[Int, ParRange] + with Serializable +{ + override def par = new ParRange(this) + + private def gap = end.toLong - start.toLong + private def isExact = gap % step == 0 + private def hasStub = isInclusive || !isExact + private def longLength = gap / step + ( if (hasStub) 1 else 0 ) + + // Check cannot be evaluated eagerly because we have a pattern where + // ranges are constructed like: "x to y by z" The "x to y" piece + // should not trigger an exception. So the calculation is delayed, + // which means it will not fail fast for those cases where failing was + // correct. + override final val isEmpty = ( + (start > end && step > 0) + || (start < end && step < 0) + || (start == end && !isInclusive) + ) + final val numRangeElements: Int = { + if (step == 0) throw new IllegalArgumentException("step cannot be 0.") + else if (isEmpty) 0 + else { + val len = longLength + if (len > scala.Int.MaxValue) -1 + else len.toInt + } + } + final val lastElement = start + (numRangeElements - 1) * step + final val terminalElement = start + numRangeElements * step + + override def last = if (isEmpty) Nil.last else lastElement + + override def min[A1 >: Int](implicit ord: Ordering[A1]): Int = + if (ord eq Ordering.Int) { + if (step > 0) start + else last + } else super.min(ord) + + override def max[A1 >: Int](implicit ord: Ordering[A1]): Int = + if (ord eq Ordering.Int) { + if (step > 0) last + else start + } else super.max(ord) + + protected def copy(start: Int, end: Int, step: Int): Range = new Range(start, end, step) + + /** Create a new range with the `start` and `end` values of this range and + * a new `step`. + * + * @return a new range with a different step + */ + def by(step: Int): Range = copy(start, end, step) + + def isInclusive = false + + override def size = length + override def length = if (numRangeElements < 0) fail() else numRangeElements + + private def fail() = Range.fail(start, end, step, isInclusive) + private def validateMaxLength() { + if (numRangeElements < 0) + fail() + } + + def validateRangeBoundaries(f: Int => Any): Boolean = { + validateMaxLength() + + start != Int.MinValue || end != Int.MinValue || { + var count = 0 + var num = start + while (count < numRangeElements) { + f(num) + count += 1 + num += step + } + false + } + } + + final def apply(idx: Int): Int = { + validateMaxLength() + if (idx < 0 || idx >= numRangeElements) throw new IndexOutOfBoundsException(idx.toString) + else start + (step * idx) + } + + @inline final override def foreach[@specialized(Unit) U](f: Int => U) { + validateMaxLength() + val isCommonCase = (start != Int.MinValue || end != Int.MinValue) + var i = start + var count = 0 + val terminal = terminalElement + val step = this.step + while( + if(isCommonCase) { i != terminal } + else { count < numRangeElements } + ) { + f(i) + count += 1 + i += step + } + } + + /** Creates a new range containing the first `n` elements of this range. + * + * $doesNotUseBuilders + * + * @param n the number of elements to take. + * @return a new range consisting of `n` first elements. + */ + final override def take(n: Int): Range = ( + if (n <= 0 || isEmpty) newEmptyRange(start) + else if (n >= numRangeElements) this + else new Range.Inclusive(start, locationAfterN(n - 1), step) + ) + + /** Creates a new range containing all the elements of this range except the first `n` elements. + * + * $doesNotUseBuilders + * + * @param n the number of elements to drop. + * @return a new range consisting of all the elements of this range except `n` first elements. + */ + final override def drop(n: Int): Range = ( + if (n <= 0 || isEmpty) this + else if (n >= numRangeElements) newEmptyRange(end) + else copy(locationAfterN(n), end, step) + ) + + /** Creates a new range containing all the elements of this range except the last one. + * + * $doesNotUseBuilders + * + * @return a new range consisting of all the elements of this range except the last one. + */ + final override def init: Range = { + if (isEmpty) + Nil.init + + dropRight(1) + } + + /** Creates a new range containing all the elements of this range except the first one. + * + * $doesNotUseBuilders + * + * @return a new range consisting of all the elements of this range except the first one. + */ + final override def tail: Range = { + if (isEmpty) + Nil.tail + + drop(1) + } + + // Counts how many elements from the start meet the given test. + private def skipCount(p: Int => Boolean): Int = { + var current = start + var counted = 0 + + while (counted < numRangeElements && p(current)) { + counted += 1 + current += step + } + counted + } + // Tests whether a number is within the endpoints, without testing + // whether it is a member of the sequence (i.e. when step > 1.) + private def isWithinBoundaries(elem: Int) = !isEmpty && ( + (step > 0 && start <= elem && elem <= last ) || + (step < 0 && last <= elem && elem <= start) + ) + // Methods like apply throw exceptions on invalid n, but methods like take/drop + // are forgiving: therefore the checks are with the methods. + private def locationAfterN(n: Int) = start + (step * n) + + // When one drops everything. Can't ever have unchecked operations + // like "end + 1" or "end - 1" because ranges involving Int.{ MinValue, MaxValue } + // will overflow. This creates an exclusive range where start == end + // based on the given value. + private def newEmptyRange(value: Int) = new Range(value, value, step) + + final override def takeWhile(p: Int => Boolean): Range = take(skipCount(p)) + final override def dropWhile(p: Int => Boolean): Range = drop(skipCount(p)) + final override def span(p: Int => Boolean): (Range, Range) = splitAt(skipCount(p)) + + /** Creates a pair of new ranges, first consisting of elements before `n`, and the second + * of elements after `n`. + * + * $doesNotUseBuilders + */ + final override def splitAt(n: Int) = (take(n), drop(n)) + + /** Creates a new range consisting of the `length - n` last elements of the range. + * + * $doesNotUseBuilders + */ + final override def takeRight(n: Int): Range = drop(numRangeElements - n) + + /** Creates a new range consisting of the initial `length - n` elements of the range. + * + * $doesNotUseBuilders + */ + final override def dropRight(n: Int): Range = take(numRangeElements - n) + + /** Returns the reverse of this range. + * + * $doesNotUseBuilders + */ + final override def reverse: Range = + if (isEmpty) this + else new Range.Inclusive(last, start, -step) + + /** Make range inclusive. + */ + def inclusive = + if (isInclusive) this + else new Range.Inclusive(start, end, step) + + final def contains(x: Int) = isWithinBoundaries(x) && ((x - start) % step == 0) + + final override def sum[B >: Int](implicit num: Numeric[B]): Int = { + if (isEmpty) 0 + else if (numRangeElements == 1) head + else (numRangeElements.toLong * (head + last) / 2).toInt + } + + override def toIterable = this + + override def toSeq = this + + override def equals(other: Any) = other match { + case x: Range => + (x canEqual this) && (length == x.length) && ( + isEmpty || // all empty sequences are equal + (start == x.start && last == x.last) // same length and same endpoints implies equality + ) + case _ => + super.equals(other) + } + /** Note: hashCode can't be overridden without breaking Seq's + * equals contract. + */ + + override def toString() = { + val endStr = if (numRangeElements > Range.MAX_PRINT) ", ... )" else ")" + take(Range.MAX_PRINT).mkString("Range(", ", ", endStr) + } +} + +/** A companion object for the `Range` class. + */ +object Range { + private[immutable] val MAX_PRINT = 512 // some arbitrary value + + private def description(start: Int, end: Int, step: Int, isInclusive: Boolean) = + "%d %s %d by %s".format(start, if (isInclusive) "to" else "until", end, step) + + private def fail(start: Int, end: Int, step: Int, isInclusive: Boolean) = + throw new IllegalArgumentException(description(start, end, step, isInclusive) + + ": seqs cannot contain more than Int.MaxValue elements.") + + /** Counts the number of range elements. + * @pre step != 0 + * If the size of the range exceeds Int.MaxValue, the + * result will be negative. + */ + def count(start: Int, end: Int, step: Int, isInclusive: Boolean): Int = { + if (step == 0) + throw new IllegalArgumentException("step cannot be 0.") + + val isEmpty = ( + if (start == end) !isInclusive + else if (start < end) step < 0 + else step > 0 + ) + if (isEmpty) 0 + else { + // Counts with Longs so we can recognize too-large ranges. + val gap: Long = end.toLong - start.toLong + val jumps: Long = gap / step + // Whether the size of this range is one larger than the + // number of full-sized jumps. + val hasStub = isInclusive || (gap % step != 0) + val result: Long = jumps + ( if (hasStub) 1 else 0 ) + + if (result > scala.Int.MaxValue) -1 + else result.toInt + } + } + def count(start: Int, end: Int, step: Int): Int = + count(start, end, step, false) + + @inline + class Inclusive(start: Int, end: Int, step: Int) extends Range(start, end, step) { +// override def par = new ParRange(this) + override def isInclusive = true + override protected def copy(start: Int, end: Int, step: Int): Range = new Inclusive(start, end, step) + } + + /** Make a range from `start` until `end` (exclusive) with given step value. + * @note step != 0 + */ + def apply(start: Int, end: Int, step: Int): Range = new Range(start, end, step) + + /** Make a range from `start` until `end` (exclusive) with step value 1. + */ + def apply(start: Int, end: Int): Range = new Range(start, end, 1) + + /** Make an inclusive range from `start` to `end` with given step value. + * @note step != 0 + */ + def inclusive(start: Int, end: Int, step: Int): Range.Inclusive = new Inclusive(start, end, step) + + /** Make an inclusive range from `start` to `end` with step value 1. + */ + def inclusive(start: Int, end: Int): Range.Inclusive = new Inclusive(start, end, 1) + + // BigInt and Long are straightforward generic ranges. + object BigInt { + def apply(start: BigInt, end: BigInt, step: BigInt) = NumericRange(start, end, step) + def inclusive(start: BigInt, end: BigInt, step: BigInt) = NumericRange.inclusive(start, end, step) + } + + object Long { + def apply(start: Long, end: Long, step: Long) = NumericRange(start, end, step) + def inclusive(start: Long, end: Long, step: Long) = NumericRange.inclusive(start, end, step) + } + + // BigDecimal uses an alternative implementation of Numeric in which + // it pretends to be Integral[T] instead of Fractional[T]. See Numeric for + // details. The intention is for it to throw an exception anytime + // imprecision or surprises might result from anything, although this may + // not yet be fully implemented. + object BigDecimal { + implicit val bigDecAsIntegral = scala.math.Numeric.BigDecimalAsIfIntegral + + def apply(start: BigDecimal, end: BigDecimal, step: BigDecimal) = + NumericRange(start, end, step) + def inclusive(start: BigDecimal, end: BigDecimal, step: BigDecimal) = + NumericRange.inclusive(start, end, step) + } + + // Double works by using a BigDecimal under the hood for precise + // stepping, but mapping the sequence values back to doubles with + // .doubleValue. This constructs the BigDecimals by way of the + // String constructor (valueOf) instead of the Double one, which + // is necessary to keep 0.3d at 0.3 as opposed to + // 0.299999999999999988897769753748434595763683319091796875 or so. + object Double { + implicit val bigDecAsIntegral = scala.math.Numeric.BigDecimalAsIfIntegral + implicit val doubleAsIntegral = scala.math.Numeric.DoubleAsIfIntegral + def toBD(x: Double): BigDecimal = scala.math.BigDecimal valueOf x + + def apply(start: Double, end: Double, step: Double) = + BigDecimal(toBD(start), toBD(end), toBD(step)) mapRange (_.doubleValue) + + def inclusive(start: Double, end: Double, step: Double) = + BigDecimal.inclusive(toBD(start), toBD(end), toBD(step)) mapRange (_.doubleValue) + } + + // As there is no appealing default step size for not-really-integral ranges, + // we offer a partially constructed object. + class Partial[T, U](f: T => U) { + def by(x: T): U = f(x) + } + + // Illustrating genericity with Int Range, which should have the same behavior + // as the original Range class. However we leave the original Range + // indefinitely, for performance and because the compiler seems to bootstrap + // off it and won't do so with our parameterized version without modifications. + object Int { + def apply(start: Int, end: Int, step: Int) = NumericRange(start, end, step) + def inclusive(start: Int, end: Int, step: Int) = NumericRange.inclusive(start, end, step) + } +} diff --git a/examples/scala-js/scalalib/overrides-2.10/scala/collection/mutable/Buffer.scala b/examples/scala-js/scalalib/overrides-2.10/scala/collection/mutable/Buffer.scala new file mode 100644 index 0000000..ec7763b --- /dev/null +++ b/examples/scala-js/scalalib/overrides-2.10/scala/collection/mutable/Buffer.scala @@ -0,0 +1,50 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2003-2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + + + +package scala.collection +package mutable + +import generic._ + +import scala.scalajs.js + +/** Buffers are used to create sequences of elements incrementally by + * appending, prepending, or inserting new elements. It is also + * possible to access and modify elements in a random access fashion + * via the index of the element in the current sequence. + * + * @author Matthias Zenger + * @author Martin Odersky + * @version 2.8 + * @since 1 + * + * @tparam A type of the elements contained in this buffer. + * + * @define Coll `Buffer` + * @define coll buffer + */ +trait Buffer[A] extends Seq[A] + with GenericTraversableTemplate[A, Buffer] + with BufferLike[A, Buffer[A]] + with scala.Cloneable { + override def companion: GenericCompanion[Buffer] = Buffer +} + +/** $factoryInfo + * @define coll buffer + * @define Coll `Buffer` + */ +object Buffer extends SeqFactory[Buffer] { + implicit def canBuildFrom[A]: CanBuildFrom[Coll, A, Buffer[A]] = ReusableCBF.asInstanceOf[GenericCanBuildFrom[A]] + def newBuilder[A]: Builder[A, Buffer[A]] = new js.WrappedArray +} + +/** Explicit instantiation of the `Buffer` trait to reduce class file size in subclasses. */ +private[scala] abstract class AbstractBuffer[A] extends AbstractSeq[A] with Buffer[A] diff --git a/examples/scala-js/scalalib/overrides-2.10/scala/compat/Platform.scala b/examples/scala-js/scalalib/overrides-2.10/scala/compat/Platform.scala new file mode 100644 index 0000000..77bf7de --- /dev/null +++ b/examples/scala-js/scalalib/overrides-2.10/scala/compat/Platform.scala @@ -0,0 +1,133 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2002-2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + + + +package scala.compat + +import java.lang.System + +object Platform { + + /** Thrown when a stack overflow occurs because a method or function recurses too deeply. + * + * On the JVM, this is a type alias for `java.lang.StackOverflowError`, which itself extends `java.lang.Error`. + * The same rules apply to catching a `java.lang.Error` as for Java, that it indicates a serious problem that a reasonable application should not try and catch. + */ + type StackOverflowError = java.lang.StackOverflowError + + /** This is a type alias for `java.util.ConcurrentModificationException`, + * which may be thrown by methods that detect an invalid modification of an object. + * For example, many common collection types do not allow modifying a collection + * while it is being iterated over. + */ + type ConcurrentModificationException = java.util.ConcurrentModificationException + + /** Copies `length` elements of array `src` starting at position `srcPos` to the + * array `dest` starting at position `destPos`. If `src`==`dest`, the copying will + * behave as if the elements copied from `src` were first copied to a temporary + * array before being copied back into the array at the destination positions. + * + * @param src A non-null array as source for the copy. + * @param srcPos The starting index in the source array. + * @param dest A non-null array as destination for the copy. + * @param destPos The starting index in the destination array. + * @param length The number of elements to be copied. + * @throws java.lang.NullPointerException If either `src` or `dest` are `null`. + * @throws java.lang.ArrayStoreException If either `src` or `dest` are not of type + * [java.lang.Array]; or if the element type of `src` is not + * compatible with that of `dest`. + * @throws java.lang.IndexOutOfBoundsException If either srcPos` or `destPos` are + * outside of the bounds of their respective arrays; or if `length` + * is negative; or if there are less than `length` elements available + * after `srcPos` or `destPos` in `src` and `dest` respectively. + */ + @inline + def arraycopy(src: AnyRef, srcPos: Int, dest: AnyRef, destPos: Int, length: Int) { + System.arraycopy(src, srcPos, dest, destPos, length) + } + + /** Creates a new array of the specified type and given length. + * + * Note that if `elemClass` is a subclass of [[scala.AnyVal]] then the returned value is an Array of the corresponding java primitive type. + * For example, the following code `scala.compat.Platform.createArray(classOf[Int], 4)` returns an array of the java primitive type `int`. + * + * For a [[scala.AnyVal]] array, the values of the array are set to 0 for ''numeric value types'' ([[scala.Double]], [[scala.Float]], [[scala.Long]], [[scala.Int]], [[scala.Char]], + * [[scala.Short]], and [[scala.Byte]]), and `false` for [[scala.Boolean]]. Creation of an array of type [[scala.Unit]] is not possible. + * + * For subclasses of [[scala.AnyRef]], the values of the array are set to `null`. + * + * The caller must cast the returned value to the correct type. + * + * @example {{{ + * val a = scala.compat.Platform.createArray(classOf[Int], 4).asInstanceOf[Array[Int]] // returns Array[Int](0, 0, 0, 0) + * }}} + * + * @param elemClass the `Class` object of the component type of the array + * @param length the length of the new array. + * @return an array of the given component type as an `AnyRef`. + * @throws `java.lang.NullPointerException` If `elemClass` is `null`. + * @throws `java.lang.IllegalArgumentException` if componentType is [[scala.Unit]] or `java.lang.Void.TYPE` + * @throws `java.lang.NegativeArraySizeException` if the specified length is negative + */ + @inline + def createArray(elemClass: Class[_], length: Int): AnyRef = + java.lang.reflect.Array.newInstance(elemClass, length) + + /** Assigns the value of 0 to each element in the array. + * @param arr A non-null Array[Int]. + * @throws `java.lang.NullPointerException` If `arr` is `null`. + */ + @inline + def arrayclear(arr: Array[Int]) { java.util.Arrays.fill(arr, 0) } + + /** Returns the `Class` object associated with the class or interface with the given string name using the current `ClassLoader`. + * On the JVM, invoking this method is equivalent to: `java.lang.Class.forName(name)` + * + * For more information, please see the Java documentation for [[java.lang.Class]]. + * + * @param name the fully qualified name of the desired class. + * @return the `Class` object for the class with the specified name. + * @throws `java.lang.LinkageError` if the linkage fails + * @throws `java.lang.ExceptionInInitializerError` if the initialization provoked by this method fails + * @throws `java.lang.ClassNotFoundException` if the class cannot be located + * @example {{{ + * val a = scala.compat.Platform.getClassForName("java.lang.Integer") // returns the Class[_] for java.lang.Integer + * }}} + */ + @inline + def getClassForName(name: String): Class[_] = java.lang.Class.forName(name) + + /** The default line separator. + * + * On the JavaScript backend, this is always "\n". + */ + val EOL = "\n" + + /** The current time in milliseconds. The time is counted since 1 January 1970 + * UTC. + * + * Note that the operating system timer used to obtain this value may be less + * precise than a millisecond. + */ + @inline + def currentTime: Long = System.currentTimeMillis() + + /** Runs the garbage collector. + * + * This is a request that the underlying JVM runs the garbage collector. + * The results of this call depends heavily on the JVM used. + * The underlying JVM is free to ignore this request. + */ + @inline + def collectGarbage(): Unit = System.gc() + + /** The name of the default character set encoding as a string */ + @inline + def defaultCharsetName: String = java.nio.charset.Charset.defaultCharset.name +} diff --git a/examples/scala-js/scalalib/overrides-2.10/scala/package.scala b/examples/scala-js/scalalib/overrides-2.10/scala/package.scala new file mode 100644 index 0000000..5aad9a9 --- /dev/null +++ b/examples/scala-js/scalalib/overrides-2.10/scala/package.scala @@ -0,0 +1,138 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2003-2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + + +/** + * Core Scala types. They are always available without an explicit import. + * @contentDiagram hideNodes "scala.Serializable" + */ +package object scala { + type Throwable = java.lang.Throwable + type Exception = java.lang.Exception + type Error = java.lang.Error + + type RuntimeException = java.lang.RuntimeException + type NullPointerException = java.lang.NullPointerException + type ClassCastException = java.lang.ClassCastException + type IndexOutOfBoundsException = java.lang.IndexOutOfBoundsException + type ArrayIndexOutOfBoundsException = java.lang.ArrayIndexOutOfBoundsException + type StringIndexOutOfBoundsException = java.lang.StringIndexOutOfBoundsException + type UnsupportedOperationException = java.lang.UnsupportedOperationException + type IllegalArgumentException = java.lang.IllegalArgumentException + type NoSuchElementException = java.util.NoSuchElementException + type NumberFormatException = java.lang.NumberFormatException + type AbstractMethodError = java.lang.AbstractMethodError + type InterruptedException = java.lang.InterruptedException + + // A dummy used by the specialization annotation. + val AnyRef = new Specializable { + override def toString = "object AnyRef" + } + + @deprecated("instead of `@serializable class C`, use `class C extends Serializable`", "2.9.0") + type serializable = annotation.serializable + + @deprecated("instead of `@cloneable class C`, use `class C extends Cloneable`", "2.10.0") + type cloneable = annotation.cloneable + + type TraversableOnce[+A] = scala.collection.TraversableOnce[A] + + type Traversable[+A] = scala.collection.Traversable[A] + val Traversable = scala.collection.Traversable + + type Iterable[+A] = scala.collection.Iterable[A] + val Iterable = scala.collection.Iterable + + type Seq[+A] = scala.collection.Seq[A] + val Seq = scala.collection.Seq + + type IndexedSeq[+A] = scala.collection.IndexedSeq[A] + val IndexedSeq = scala.collection.IndexedSeq + + type Iterator[+A] = scala.collection.Iterator[A] + val Iterator = scala.collection.Iterator + + type BufferedIterator[+A] = scala.collection.BufferedIterator[A] + + type List[+A] = scala.collection.immutable.List[A] + val List = scala.collection.immutable.List + + val Nil = scala.collection.immutable.Nil + + type ::[A] = scala.collection.immutable.::[A] + val :: = scala.collection.immutable.:: + + val +: = scala.collection.+: + val :+ = scala.collection.:+ + + type Stream[+A] = scala.collection.immutable.Stream[A] + val Stream = scala.collection.immutable.Stream + val #:: = scala.collection.immutable.Stream.#:: + + type Vector[+A] = scala.collection.immutable.Vector[A] + val Vector = scala.collection.immutable.Vector + + type StringBuilder = scala.collection.mutable.StringBuilder + val StringBuilder = scala.collection.mutable.StringBuilder + + type Range = scala.collection.immutable.Range + val Range = scala.collection.immutable.Range + + // Numeric types which were moved into scala.math.* + + type BigDecimal = scala.math.BigDecimal + lazy val BigDecimal = scala.math.BigDecimal + + type BigInt = scala.math.BigInt + lazy val BigInt = scala.math.BigInt + + type Equiv[T] = scala.math.Equiv[T] + val Equiv = scala.math.Equiv + + type Fractional[T] = scala.math.Fractional[T] + type Integral[T] = scala.math.Integral[T] + + type Numeric[T] = scala.math.Numeric[T] + val Numeric = scala.math.Numeric + + type Ordered[T] = scala.math.Ordered[T] + val Ordered = scala.math.Ordered + + type Ordering[T] = scala.math.Ordering[T] + val Ordering = scala.math.Ordering + + type PartialOrdering[T] = scala.math.PartialOrdering[T] + type PartiallyOrdered[T] = scala.math.PartiallyOrdered[T] + + type Either[+A, +B] = scala.util.Either[A, B] + val Either = scala.util.Either + + type Left[+A, +B] = scala.util.Left[A, B] + val Left = scala.util.Left + + type Right[+A, +B] = scala.util.Right[A, B] + val Right = scala.util.Right + + // Annotations which we might move to annotation.* +/* + type SerialVersionUID = annotation.SerialVersionUID + type cloneable = annotation.cloneable + type deprecated = annotation.deprecated + type deprecatedName = annotation.deprecatedName + type inline = annotation.inline + type native = annotation.native + type noinline = noannotation.inline + type remote = annotation.remote + type serializable = annotation.serializable + type specialized = annotation.specialized + type transient = annotation.transient + type throws = annotation.throws + type unchecked = annotation.unchecked.unchecked + type volatile = annotation.volatile + */ +} diff --git a/examples/scala-js/scalalib/overrides-2.11/scala/Console.scala b/examples/scala-js/scalalib/overrides-2.11/scala/Console.scala new file mode 100644 index 0000000..b85f8dc --- /dev/null +++ b/examples/scala-js/scalalib/overrides-2.11/scala/Console.scala @@ -0,0 +1,222 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2003-2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + +package scala + +import java.io.{ BufferedReader, InputStream, InputStreamReader, OutputStream, PrintStream, Reader } +import scala.io.{ AnsiColor, StdIn } +import scala.util.DynamicVariable + +/** Implements functionality for + * printing Scala values on the terminal as well as reading specific values. + * Also defines constants for marking up text on ANSI terminals. + * + * @author Matthias Zenger + * @version 1.0, 03/09/2003 + */ +object Console extends DeprecatedConsole with AnsiColor { + private val outVar = new DynamicVariable[PrintStream](java.lang.System.out) + private val errVar = new DynamicVariable[PrintStream](java.lang.System.err) + private val inVar = new DynamicVariable[BufferedReader](null) + //new BufferedReader(new InputStreamReader(java.lang.System.in))) + + protected def setOutDirect(out: PrintStream): Unit = outVar.value = out + protected def setErrDirect(err: PrintStream): Unit = errVar.value = err + protected def setInDirect(in: BufferedReader): Unit = inVar.value = in + + /** The default output, can be overridden by `setOut` */ + def out = outVar.value + /** The default error, can be overridden by `setErr` */ + def err = errVar.value + /** The default input, can be overridden by `setIn` */ + def in = inVar.value + + /** Sets the default output stream for the duration + * of execution of one thunk. + * + * @example {{{ + * withOut(Console.err) { println("This goes to default _error_") } + * }}} + * + * @param out the new output stream. + * @param thunk the code to execute with + * the new output stream active + * @return the results of `thunk` + * @see `withOut[T](out:OutputStream)(thunk: => T)` + */ + def withOut[T](out: PrintStream)(thunk: =>T): T = + outVar.withValue(out)(thunk) + + /** Sets the default output stream for the duration + * of execution of one thunk. + * + * @param out the new output stream. + * @param thunk the code to execute with + * the new output stream active + * @return the results of `thunk` + * @see `withOut[T](out:PrintStream)(thunk: => T)` + */ + def withOut[T](out: OutputStream)(thunk: =>T): T = + withOut(new PrintStream(out))(thunk) + + /** Set the default error stream for the duration + * of execution of one thunk. + * @example {{{ + * withErr(Console.out) { println("This goes to default _out_") } + * }}} + * + * @param err the new error stream. + * @param thunk the code to execute with + * the new error stream active + * @return the results of `thunk` + * @see `withErr[T](err:OutputStream)(thunk: =>T)` + */ + def withErr[T](err: PrintStream)(thunk: =>T): T = + errVar.withValue(err)(thunk) + + /** Sets the default error stream for the duration + * of execution of one thunk. + * + * @param err the new error stream. + * @param thunk the code to execute with + * the new error stream active + * @return the results of `thunk` + * @see `withErr[T](err:PrintStream)(thunk: =>T)` + */ + def withErr[T](err: OutputStream)(thunk: =>T): T = + withErr(new PrintStream(err))(thunk) + + /** Sets the default input stream for the duration + * of execution of one thunk. + * + * @example {{{ + * val someFile:Reader = openFile("file.txt") + * withIn(someFile) { + * // Reads a line from file.txt instead of default input + * println(readLine) + * } + * }}} + * + * @param thunk the code to execute with + * the new input stream active + * + * @return the results of `thunk` + * @see `withIn[T](in:InputStream)(thunk: =>T)` + */ + def withIn[T](reader: Reader)(thunk: =>T): T = + inVar.withValue(new BufferedReader(reader))(thunk) + + /** Sets the default input stream for the duration + * of execution of one thunk. + * + * @param in the new input stream. + * @param thunk the code to execute with + * the new input stream active + * @return the results of `thunk` + * @see `withIn[T](reader:Reader)(thunk: =>T)` + */ + def withIn[T](in: InputStream)(thunk: =>T): T = + withIn(new InputStreamReader(in))(thunk) + + /** Prints an object to `out` using its `toString` method. + * + * @param obj the object to print; may be null. + */ + def print(obj: Any) { + out.print(if (null == obj) "null" else obj.toString()) + } + + /** Flushes the output stream. This function is required when partial + * output (i.e. output not terminated by a newline character) has + * to be made visible on the terminal. + */ + def flush() { out.flush() } + + /** Prints a newline character on the default output. + */ + def println() { out.println() } + + /** Prints out an object to the default output, followed by a newline character. + * + * @param x the object to print. + */ + def println(x: Any) { out.println(x) } + + /** Prints its arguments as a formatted string to the default output, + * based on a string pattern (in a fashion similar to printf in C). + * + * The interpretation of the formatting patterns is described in + * <a href="" target="contentFrame" class="java/util/Formatter"> + * `java.util.Formatter`</a>. + * + * @param text the pattern for formatting the arguments. + * @param args the arguments used to instantiating the pattern. + * @throws java.lang.IllegalArgumentException if there was a problem with the format string or arguments + */ + def printf(text: String, args: Any*) { out.print(text format (args : _*)) } +} + +private[scala] abstract class DeprecatedConsole { + self: Console.type => + + /** Internal usage only. */ + protected def setOutDirect(out: PrintStream): Unit + protected def setErrDirect(err: PrintStream): Unit + protected def setInDirect(in: BufferedReader): Unit + + @deprecated("Use the method in scala.io.StdIn", "2.11.0") def readBoolean(): Boolean = StdIn.readBoolean() + @deprecated("Use the method in scala.io.StdIn", "2.11.0") def readByte(): Byte = StdIn.readByte() + @deprecated("Use the method in scala.io.StdIn", "2.11.0") def readChar(): Char = StdIn.readChar() + @deprecated("Use the method in scala.io.StdIn", "2.11.0") def readDouble(): Double = StdIn.readDouble() + @deprecated("Use the method in scala.io.StdIn", "2.11.0") def readFloat(): Float = StdIn.readFloat() + @deprecated("Use the method in scala.io.StdIn", "2.11.0") def readInt(): Int = StdIn.readInt() + @deprecated("Use the method in scala.io.StdIn", "2.11.0") def readLine(): String = StdIn.readLine() + @deprecated("Use the method in scala.io.StdIn", "2.11.0") def readLine(text: String, args: Any*): String = StdIn.readLine(text, args: _*) + @deprecated("Use the method in scala.io.StdIn", "2.11.0") def readLong(): Long = StdIn.readLong() + @deprecated("Use the method in scala.io.StdIn", "2.11.0") def readShort(): Short = StdIn.readShort() + @deprecated("Use the method in scala.io.StdIn", "2.11.0") def readf(format: String): List[Any] = StdIn.readf(format) + @deprecated("Use the method in scala.io.StdIn", "2.11.0") def readf1(format: String): Any = StdIn.readf1(format) + @deprecated("Use the method in scala.io.StdIn", "2.11.0") def readf2(format: String): (Any, Any) = StdIn.readf2(format) + @deprecated("Use the method in scala.io.StdIn", "2.11.0") def readf3(format: String): (Any, Any, Any) = StdIn.readf3(format) + + /** Sets the default output stream. + * + * @param out the new output stream. + */ + @deprecated("Use withOut", "2.11.0") def setOut(out: PrintStream): Unit = setOutDirect(out) + + /** Sets the default output stream. + * + * @param out the new output stream. + */ + @deprecated("Use withOut", "2.11.0") def setOut(out: OutputStream): Unit = setOutDirect(new PrintStream(out)) + + /** Sets the default error stream. + * + * @param err the new error stream. + */ + @deprecated("Use withErr", "2.11.0") def setErr(err: PrintStream): Unit = setErrDirect(err) + + /** Sets the default error stream. + * + * @param err the new error stream. + */ + @deprecated("Use withErr", "2.11.0") def setErr(err: OutputStream): Unit = setErrDirect(new PrintStream(err)) + + /** Sets the default input stream. + * + * @param reader specifies the new input stream. + */ + @deprecated("Use withIn", "2.11.0") def setIn(reader: Reader): Unit = setInDirect(new BufferedReader(reader)) + + /** Sets the default input stream. + * + * @param in the new input stream. + */ + @deprecated("Use withIn", "2.11.0") def setIn(in: InputStream): Unit = setInDirect(new BufferedReader(new InputStreamReader(in))) +} diff --git a/examples/scala-js/scalalib/overrides-2.11/scala/collection/immutable/NumericRange.scala b/examples/scala-js/scalalib/overrides-2.11/scala/collection/immutable/NumericRange.scala new file mode 100644 index 0000000..51f9f68 --- /dev/null +++ b/examples/scala-js/scalalib/overrides-2.11/scala/collection/immutable/NumericRange.scala @@ -0,0 +1,346 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2006-2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + +package scala +package collection +package immutable + +import mutable.{ Builder, ListBuffer } + +/** `NumericRange` is a more generic version of the + * `Range` class which works with arbitrary types. + * It must be supplied with an `Integral` implementation of the + * range type. + * + * Factories for likely types include `Range.BigInt`, `Range.Long`, + * and `Range.BigDecimal`. `Range.Int` exists for completeness, but + * the `Int`-based `scala.Range` should be more performant. + * + * {{{ + * val r1 = new Range(0, 100, 1) + * val veryBig = Int.MaxValue.toLong + 1 + * val r2 = Range.Long(veryBig, veryBig + 100, 1) + * assert(r1 sameElements r2.map(_ - veryBig)) + * }}} + * + * TODO: Now the specialization exists there is no clear reason to have + * separate classes for Range/NumericRange. Investigate and consolidate. + * + * @author Paul Phillips + * @version 2.8 + * @define Coll `NumericRange` + * @define coll numeric range + * @define mayNotTerminateInf + * @define willNotTerminateInf + */ +abstract class NumericRange[T] + (val start: T, val end: T, val step: T, val isInclusive: Boolean) + (implicit num: Integral[T]) +extends AbstractSeq[T] with IndexedSeq[T] with Serializable { + /** Note that NumericRange must be invariant so that constructs + * such as "1L to 10 by 5" do not infer the range type as AnyVal. + */ + import num._ + + // See comment in Range for why this must be lazy. + private lazy val numRangeElements: Int = + NumericRange.count(start, end, step, isInclusive) + + override def length = numRangeElements + override def isEmpty = length == 0 + override lazy val last: T = + if (length == 0) Nil.last + else locationAfterN(length - 1) + + /** Create a new range with the start and end values of this range and + * a new `step`. + */ + def by(newStep: T): NumericRange[T] = copy(start, end, newStep) + + /** Create a copy of this range. + */ + def copy(start: T, end: T, step: T): NumericRange[T] + + override def foreach[U](f: T => U) { + var count = 0 + var current = start + while (count < length) { + f(current) + current += step + count += 1 + } + } + + // TODO: these private methods are straight copies from Range, duplicated + // to guard against any (most likely illusory) performance drop. They should + // be eliminated one way or another. + + // Tests whether a number is within the endpoints, without testing + // whether it is a member of the sequence (i.e. when step > 1.) + private def isWithinBoundaries(elem: T) = !isEmpty && ( + (step > zero && start <= elem && elem <= last ) || + (step < zero && last <= elem && elem <= start) + ) + // Methods like apply throw exceptions on invalid n, but methods like take/drop + // are forgiving: therefore the checks are with the methods. + private def locationAfterN(n: Int): T = start + (step * fromInt(n)) + + // When one drops everything. Can't ever have unchecked operations + // like "end + 1" or "end - 1" because ranges involving Int.{ MinValue, MaxValue } + // will overflow. This creates an exclusive range where start == end + // based on the given value. + private def newEmptyRange(value: T) = NumericRange(value, value, step) + + final override def take(n: Int): NumericRange[T] = ( + if (n <= 0 || length == 0) newEmptyRange(start) + else if (n >= length) this + else new NumericRange.Inclusive(start, locationAfterN(n - 1), step) + ) + + final override def drop(n: Int): NumericRange[T] = ( + if (n <= 0 || length == 0) this + else if (n >= length) newEmptyRange(end) + else copy(locationAfterN(n), end, step) + ) + + def apply(idx: Int): T = { + if (idx < 0 || idx >= length) throw new IndexOutOfBoundsException(idx.toString) + else locationAfterN(idx) + } + + import NumericRange.defaultOrdering + + override def min[T1 >: T](implicit ord: Ordering[T1]): T = + if (ord eq defaultOrdering(num)) { + if (num.signum(step) > 0) start + else last + } else super.min(ord) + + override def max[T1 >: T](implicit ord: Ordering[T1]): T = + if (ord eq defaultOrdering(num)) { + if (num.signum(step) > 0) last + else start + } else super.max(ord) + + // Motivated by the desire for Double ranges with BigDecimal precision, + // we need some way to map a Range and get another Range. This can't be + // done in any fully general way because Ranges are not arbitrary + // sequences but step-valued, so we have a custom method only we can call + // which we promise to use responsibly. + // + // The point of it all is that + // + // 0.0 to 1.0 by 0.1 + // + // should result in + // + // NumericRange[Double](0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0) + // + // and not + // + // NumericRange[Double](0.0, 0.1, 0.2, 0.30000000000000004, 0.4, 0.5, 0.6000000000000001, 0.7000000000000001, 0.8, 0.9) + // + // or perhaps more importantly, + // + // (0.1 to 0.3 by 0.1 contains 0.3) == true + // + private[immutable] def mapRange[A](fm: T => A)(implicit unum: Integral[A]): NumericRange[A] = { + val self = this + + // XXX This may be incomplete. + new NumericRange[A](fm(start), fm(end), fm(step), isInclusive) { + def copy(start: A, end: A, step: A): NumericRange[A] = + if (isInclusive) NumericRange.inclusive(start, end, step) + else NumericRange(start, end, step) + + private lazy val underlyingRange: NumericRange[T] = self + override def foreach[U](f: A => U) { underlyingRange foreach (x => f(fm(x))) } + override def isEmpty = underlyingRange.isEmpty + override def apply(idx: Int): A = fm(underlyingRange(idx)) + override def containsTyped(el: A) = underlyingRange exists (x => fm(x) == el) + } + } + + // a well-typed contains method. + def containsTyped(x: T): Boolean = + isWithinBoundaries(x) && (((x - start) % step) == zero) + + override def contains[A1 >: T](x: A1): Boolean = + try containsTyped(x.asInstanceOf[T]) + catch { case _: ClassCastException => false } + + final override def sum[B >: T](implicit num: Numeric[B]): B = { + // arithmetic series formula can be used for regular addition + if ((num eq scala.math.Numeric.IntIsIntegral)|| + (num eq scala.math.Numeric.ShortIsIntegral)|| + (num eq scala.math.Numeric.ByteIsIntegral)|| + (num eq scala.math.Numeric.CharIsIntegral)|| + (num eq scala.math.Numeric.LongIsIntegral)) { + val numAsIntegral = num.asInstanceOf[Integral[B]] + import numAsIntegral._ + if (isEmpty) num fromInt 0 + else if (numRangeElements == 1) head + else ((num fromInt numRangeElements) * (head + last) / (num fromInt 2)) + } else { + // user provided custom Numeric, we cannot rely on arithmetic series formula + if (isEmpty) num.zero + else { + var acc = num.zero + var i = head + var idx = 0 + while(idx < length) { + acc = num.plus(acc, i) + i = i + step + idx = idx + 1 + } + acc + } + } + } + + override lazy val hashCode = super.hashCode() + override def equals(other: Any) = other match { + case x: NumericRange[_] => + (x canEqual this) && (length == x.length) && ( + (length == 0) || // all empty sequences are equal + (start == x.start && last == x.last) // same length and same endpoints implies equality + ) + case _ => + super.equals(other) + } + + override def toString() = { + val endStr = if (length > Range.MAX_PRINT) ", ... )" else ")" + take(Range.MAX_PRINT).mkString("NumericRange(", ", ", endStr) + } +} + +/** A companion object for numeric ranges. + */ +object NumericRange { + + /** Calculates the number of elements in a range given start, end, step, and + * whether or not it is inclusive. Throws an exception if step == 0 or + * the number of elements exceeds the maximum Int. + */ + def count[T](start: T, end: T, step: T, isInclusive: Boolean)(implicit num: Integral[T]): Int = { + val zero = num.zero + val upward = num.lt(start, end) + val posStep = num.gt(step, zero) + + if (step == zero) throw new IllegalArgumentException("step cannot be 0.") + else if (start == end) if (isInclusive) 1 else 0 + else if (upward != posStep) 0 + else { + /* We have to be frightfully paranoid about running out of range. + * We also can't assume that the numbers will fit in a Long. + * We will assume that if a > 0, -a can be represented, and if + * a < 0, -a+1 can be represented. We also assume that if we + * can't fit in Int, we can represent 2*Int.MaxValue+3 (at least). + * And we assume that numbers wrap rather than cap when they overflow. + */ + // Check whether we can short-circuit by deferring to Int range. + val startint = num.toInt(start) + if (start == num.fromInt(startint)) { + val endint = num.toInt(end) + if (end == num.fromInt(endint)) { + val stepint = num.toInt(step) + if (step == num.fromInt(stepint)) { + return { + if (isInclusive) Range.inclusive(startint, endint, stepint).length + else Range (startint, endint, stepint).length + } + } + } + } + // If we reach this point, deferring to Int failed. + // Numbers may be big. + val one = num.one + val limit = num.fromInt(Int.MaxValue) + def check(t: T): T = + if (num.gt(t, limit)) throw new IllegalArgumentException("More than Int.MaxValue elements.") + else t + // If the range crosses zero, it might overflow when subtracted + val startside = num.signum(start) + val endside = num.signum(end) + num.toInt{ + if (startside*endside >= 0) { + // We're sure we can subtract these numbers. + // Note that we do not use .rem because of different conventions for Long and BigInt + val diff = num.minus(end, start) + val quotient = check(num.quot(diff, step)) + val remainder = num.minus(diff, num.times(quotient, step)) + if (!isInclusive && zero == remainder) quotient else check(num.plus(quotient, one)) + } + else { + // We might not even be able to subtract these numbers. + // Jump in three pieces: + // * start to -1 or 1, whichever is closer (waypointA) + // * one step, which will take us at least to 0 (ends at waypointB) + // * there to the end + val negone = num.fromInt(-1) + val startlim = if (posStep) negone else one + val startdiff = num.minus(startlim, start) + val startq = check(num.quot(startdiff, step)) + val waypointA = if (startq == zero) start else num.plus(start, num.times(startq, step)) + val waypointB = num.plus(waypointA, step) + check { + if (num.lt(waypointB, end) != upward) { + // No last piece + if (isInclusive && waypointB == end) num.plus(startq, num.fromInt(2)) + else num.plus(startq, one) + } + else { + // There is a last piece + val enddiff = num.minus(end,waypointB) + val endq = check(num.quot(enddiff, step)) + val last = if (endq == zero) waypointB else num.plus(waypointB, num.times(endq, step)) + // Now we have to tally up all the pieces + // 1 for the initial value + // startq steps to waypointA + // 1 step to waypointB + // endq steps to the end (one less if !isInclusive and last==end) + num.plus(startq, num.plus(endq, if (!isInclusive && last==end) one else num.fromInt(2))) + } + } + } + } + } + } + + class Inclusive[T](start: T, end: T, step: T)(implicit num: Integral[T]) + extends NumericRange(start, end, step, true) { + def copy(start: T, end: T, step: T): Inclusive[T] = + NumericRange.inclusive(start, end, step) + + def exclusive: Exclusive[T] = NumericRange(start, end, step) + } + + class Exclusive[T](start: T, end: T, step: T)(implicit num: Integral[T]) + extends NumericRange(start, end, step, false) { + def copy(start: T, end: T, step: T): Exclusive[T] = + NumericRange(start, end, step) + + def inclusive: Inclusive[T] = NumericRange.inclusive(start, end, step) + } + + def apply[T](start: T, end: T, step: T)(implicit num: Integral[T]): Exclusive[T] = + new Exclusive(start, end, step) + def inclusive[T](start: T, end: T, step: T)(implicit num: Integral[T]): Inclusive[T] = + new Inclusive(start, end, step) + + private[collection] val defaultOrdering = Map[Numeric[_], Ordering[_]]( + Numeric.IntIsIntegral -> Ordering.Int, + Numeric.ShortIsIntegral -> Ordering.Short, + Numeric.ByteIsIntegral -> Ordering.Byte, + Numeric.CharIsIntegral -> Ordering.Char, + Numeric.LongIsIntegral -> Ordering.Long + ) + +} + diff --git a/examples/scala-js/scalalib/overrides-2.11/scala/collection/immutable/Range.scala b/examples/scala-js/scalalib/overrides-2.11/scala/collection/immutable/Range.scala new file mode 100644 index 0000000..45eed20 --- /dev/null +++ b/examples/scala-js/scalalib/overrides-2.11/scala/collection/immutable/Range.scala @@ -0,0 +1,516 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2006-2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + + +package scala +package collection.immutable + +import scala.collection.parallel.immutable.ParRange + +/** The `Range` class represents integer values in range + * ''[start;end)'' with non-zero step value `step`. + * It's a special case of an indexed sequence. + * For example: + * + * {{{ + * val r1 = 0 until 10 + * val r2 = r1.start until r1.end by r1.step + 1 + * println(r2.length) // = 5 + * }}} + * + * Ranges that contain more than `Int.MaxValue` elements can be created, but + * these overfull ranges have only limited capabilities. Any method that + * could require a collection of over `Int.MaxValue` length to be created, or + * could be asked to index beyond `Int.MaxValue` elements will throw an + * exception. Overfull ranges can safely be reduced in size by changing + * the step size (e.g. `by 3`) or taking/dropping elements. `contains`, + * `equals`, and access to the ends of the range (`head`, `last`, `tail`, + * `init`) are also permitted on overfull ranges. + * + * @param start the start of this range. + * @param end the exclusive end of the range. + * @param step the step for the range. + * + * @author Martin Odersky + * @author Paul Phillips + * @version 2.8 + * @since 2.5 + * @see [[http://docs.scala-lang.org/overviews/collections/concrete-immutable-collection-classes.html#ranges "Scala's Collection Library overview"]] + * section on `Ranges` for more information. + * + * @define coll range + * @define mayNotTerminateInf + * @define willNotTerminateInf + * @define doesNotUseBuilders + * '''Note:''' this method does not use builders to construct a new range, + * and its complexity is O(1). + */ +@SerialVersionUID(7618862778670199309L) +@inline +@deprecatedInheritance("The implementation details of Range makes inheriting from it unwise.", "2.11.0") +class Range(val start: Int, val end: Int, val step: Int) +extends scala.collection.AbstractSeq[Int] + with IndexedSeq[Int] + with scala.collection.CustomParallelizable[Int, ParRange] + with Serializable +{ + override def par = new ParRange(this) + + private def gap = end.toLong - start.toLong + private def isExact = gap % step == 0 + private def hasStub = isInclusive || !isExact + private def longLength = gap / step + ( if (hasStub) 1 else 0 ) + + // Check cannot be evaluated eagerly because we have a pattern where + // ranges are constructed like: "x to y by z" The "x to y" piece + // should not trigger an exception. So the calculation is delayed, + // which means it will not fail fast for those cases where failing was + // correct. + override final val isEmpty = ( + (start > end && step > 0) + || (start < end && step < 0) + || (start == end && !isInclusive) + ) + @deprecated("This method will be made private, use `length` instead.", "2.11") + final val numRangeElements: Int = { + if (step == 0) throw new IllegalArgumentException("step cannot be 0.") + else if (isEmpty) 0 + else { + val len = longLength + if (len > scala.Int.MaxValue) -1 + else len.toInt + } + } + @deprecated("This method will be made private, use `last` instead.", "2.11") + final val lastElement = + if (isEmpty) start - step + else step match { + case 1 => if (isInclusive) end else end-1 + case -1 => if (isInclusive) end else end+1 + case _ => + val remainder = (gap % step).toInt + if (remainder != 0) end - remainder + else if (isInclusive) end + else end - step + } + + @deprecated("This method will be made private.", "2.11") + final val terminalElement = lastElement + step + + /** The last element of this range. This method will return the correct value + * even if there are too many elements to iterate over. + */ + override def last = if (isEmpty) Nil.last else lastElement + override def head = if (isEmpty) Nil.head else start + + override def min[A1 >: Int](implicit ord: Ordering[A1]): Int = + if (ord eq Ordering.Int) { + if (step > 0) head + else last + } else super.min(ord) + + override def max[A1 >: Int](implicit ord: Ordering[A1]): Int = + if (ord eq Ordering.Int) { + if (step > 0) last + else head + } else super.max(ord) + + protected def copy(start: Int, end: Int, step: Int): Range = new Range(start, end, step) + + /** Create a new range with the `start` and `end` values of this range and + * a new `step`. + * + * @return a new range with a different step + */ + def by(step: Int): Range = copy(start, end, step) + + def isInclusive = false + + override def size = length + override def length = if (numRangeElements < 0) fail() else numRangeElements + + private def fail() = Range.fail(start, end, step, isInclusive) + private def validateMaxLength() { + if (numRangeElements < 0) + fail() + } + + final def apply(idx: Int): Int = { + validateMaxLength() + if (idx < 0 || idx >= numRangeElements) throw new IndexOutOfBoundsException(idx.toString) + else start + (step * idx) + } + + @inline final override def foreach[@specialized(Unit) U](f: Int => U) { + validateMaxLength() + val isCommonCase = (start != Int.MinValue || end != Int.MinValue) + var i = start + var count = 0 + val terminal = terminalElement + val step = this.step + while( + if(isCommonCase) { i != terminal } + else { count < numRangeElements } + ) { + f(i) + count += 1 + i += step + } + } + + /** Creates a new range containing the first `n` elements of this range. + * + * $doesNotUseBuilders + * + * @param n the number of elements to take. + * @return a new range consisting of `n` first elements. + */ + final override def take(n: Int): Range = ( + if (n <= 0 || isEmpty) newEmptyRange(start) + else if (n >= numRangeElements && numRangeElements >= 0) this + else { + // May have more than Int.MaxValue elements in range (numRangeElements < 0) + // but the logic is the same either way: take the first n + new Range.Inclusive(start, locationAfterN(n - 1), step) + } + ) + + /** Creates a new range containing all the elements of this range except the first `n` elements. + * + * $doesNotUseBuilders + * + * @param n the number of elements to drop. + * @return a new range consisting of all the elements of this range except `n` first elements. + */ + final override def drop(n: Int): Range = ( + if (n <= 0 || isEmpty) this + else if (n >= numRangeElements && numRangeElements >= 0) newEmptyRange(end) + else { + // May have more than Int.MaxValue elements (numRangeElements < 0) + // but the logic is the same either way: go forwards n steps, keep the rest + copy(locationAfterN(n), end, step) + } + ) + + /** Creates a new range containing all the elements of this range except the last one. + * + * $doesNotUseBuilders + * + * @return a new range consisting of all the elements of this range except the last one. + */ + final override def init: Range = { + if (isEmpty) + Nil.init + + dropRight(1) + } + + /** Creates a new range containing all the elements of this range except the first one. + * + * $doesNotUseBuilders + * + * @return a new range consisting of all the elements of this range except the first one. + */ + final override def tail: Range = { + if (isEmpty) + Nil.tail + + drop(1) + } + + // Advance from the start while we meet the given test + private def argTakeWhile(p: Int => Boolean): Long = { + if (isEmpty) start + else { + var current = start + val stop = last + while (current != stop && p(current)) current += step + if (current != stop || !p(current)) current + else current.toLong + step + } + } + // Methods like apply throw exceptions on invalid n, but methods like take/drop + // are forgiving: therefore the checks are with the methods. + private def locationAfterN(n: Int) = start + (step * n) + + // When one drops everything. Can't ever have unchecked operations + // like "end + 1" or "end - 1" because ranges involving Int.{ MinValue, MaxValue } + // will overflow. This creates an exclusive range where start == end + // based on the given value. + private def newEmptyRange(value: Int) = new Range(value, value, step) + + final override def takeWhile(p: Int => Boolean): Range = { + val stop = argTakeWhile(p) + if (stop==start) newEmptyRange(start) + else { + val x = (stop - step).toInt + if (x == last) this + else new Range.Inclusive(start, x, step) + } + } + final override def dropWhile(p: Int => Boolean): Range = { + val stop = argTakeWhile(p) + if (stop == start) this + else { + val x = (stop - step).toInt + if (x == last) newEmptyRange(last) + else new Range.Inclusive(x + step, last, step) + } + } + final override def span(p: Int => Boolean): (Range, Range) = { + val border = argTakeWhile(p) + if (border == start) (newEmptyRange(start), this) + else { + val x = (border - step).toInt + if (x == last) (this, newEmptyRange(last)) + else (new Range.Inclusive(start, x, step), new Range.Inclusive(x+step, last, step)) + } + } + + /** Creates a pair of new ranges, first consisting of elements before `n`, and the second + * of elements after `n`. + * + * $doesNotUseBuilders + */ + final override def splitAt(n: Int) = (take(n), drop(n)) + + /** Creates a new range consisting of the `length - n` last elements of the range. + * + * $doesNotUseBuilders + */ + final override def takeRight(n: Int): Range = { + if (n <= 0) newEmptyRange(start) + else if (numRangeElements >= 0) drop(numRangeElements - n) + else { + // Need to handle over-full range separately + val y = last + val x = y - step.toLong*(n-1) + if ((step > 0 && x < start) || (step < 0 && x > start)) this + else new Range.Inclusive(x.toInt, y, step) + } + } + + /** Creates a new range consisting of the initial `length - n` elements of the range. + * + * $doesNotUseBuilders + */ + final override def dropRight(n: Int): Range = { + if (n <= 0) this + else if (numRangeElements >= 0) take(numRangeElements - n) + else { + // Need to handle over-full range separately + val y = last - step.toInt*n + if ((step > 0 && y < start) || (step < 0 && y > start)) newEmptyRange(start) + else new Range.Inclusive(start, y.toInt, step) + } + } + + /** Returns the reverse of this range. + * + * $doesNotUseBuilders + */ + final override def reverse: Range = + if (isEmpty) this + else new Range.Inclusive(last, start, -step) + + /** Make range inclusive. + */ + def inclusive = + if (isInclusive) this + else new Range.Inclusive(start, end, step) + + final def contains(x: Int) = { + if (x==end && !isInclusive) false + else if (step > 0) { + if (x < start || x > end) false + else (step == 1) || (((x - start) % step) == 0) + } + else { + if (x < end || x > start) false + else (step == -1) || (((x - start) % step) == 0) + } + } + + final override def sum[B >: Int](implicit num: Numeric[B]): Int = { + if (num eq scala.math.Numeric.IntIsIntegral) { + // this is normal integer range with usual addition. arithmetic series formula can be used + if (isEmpty) 0 + else if (numRangeElements == 1) head + else (numRangeElements.toLong * (head + last) / 2).toInt + } else { + // user provided custom Numeric, we cannot rely on arithmetic series formula + if (isEmpty) num.toInt(num.zero) + else { + var acc = num.zero + var i = head + while(i != terminalElement) { + acc = num.plus(acc, i) + i = i + step + } + num.toInt(acc) + } + } + } + + override def toIterable = this + + override def toSeq = this + + override def equals(other: Any) = other match { + case x: Range => + // Note: this must succeed for overfull ranges (length > Int.MaxValue) + (x canEqual this) && { + if (isEmpty) x.isEmpty // empty sequences are equal + else // this is non-empty... + x.nonEmpty && start == x.start && { // ...so other must contain something and have same start + val l0 = last + (l0 == x.last && ( // And same end + start == l0 || step == x.step // And either the same step, or not take any steps + )) + } + } + case _ => + super.equals(other) + } + /** Note: hashCode can't be overridden without breaking Seq's + * equals contract. + */ + + override def toString() = { + val endStr = + if (numRangeElements > Range.MAX_PRINT || (!isEmpty && numRangeElements < 0)) ", ... )" else ")" + take(Range.MAX_PRINT).mkString("Range(", ", ", endStr) + } +} + +/** A companion object for the `Range` class. + */ +object Range { + private[immutable] val MAX_PRINT = 512 // some arbitrary value + + private def description(start: Int, end: Int, step: Int, isInclusive: Boolean) = + "%d %s %d by %s".format(start, if (isInclusive) "to" else "until", end, step) + + private def fail(start: Int, end: Int, step: Int, isInclusive: Boolean) = + throw new IllegalArgumentException(description(start, end, step, isInclusive) + + ": seqs cannot contain more than Int.MaxValue elements.") + + /** Counts the number of range elements. + * @pre step != 0 + * If the size of the range exceeds Int.MaxValue, the + * result will be negative. + */ + def count(start: Int, end: Int, step: Int, isInclusive: Boolean): Int = { + if (step == 0) + throw new IllegalArgumentException("step cannot be 0.") + + val isEmpty = ( + if (start == end) !isInclusive + else if (start < end) step < 0 + else step > 0 + ) + if (isEmpty) 0 + else { + // Counts with Longs so we can recognize too-large ranges. + val gap: Long = end.toLong - start.toLong + val jumps: Long = gap / step + // Whether the size of this range is one larger than the + // number of full-sized jumps. + val hasStub = isInclusive || (gap % step != 0) + val result: Long = jumps + ( if (hasStub) 1 else 0 ) + + if (result > scala.Int.MaxValue) -1 + else result.toInt + } + } + def count(start: Int, end: Int, step: Int): Int = + count(start, end, step, isInclusive = false) + + @inline + class Inclusive(start: Int, end: Int, step: Int) extends Range(start, end, step) { +// override def par = new ParRange(this) + override def isInclusive = true + override protected def copy(start: Int, end: Int, step: Int): Range = new Inclusive(start, end, step) + } + + /** Make a range from `start` until `end` (exclusive) with given step value. + * @note step != 0 + */ + def apply(start: Int, end: Int, step: Int): Range = new Range(start, end, step) + + /** Make a range from `start` until `end` (exclusive) with step value 1. + */ + def apply(start: Int, end: Int): Range = new Range(start, end, 1) + + /** Make an inclusive range from `start` to `end` with given step value. + * @note step != 0 + */ + def inclusive(start: Int, end: Int, step: Int): Range.Inclusive = new Inclusive(start, end, step) + + /** Make an inclusive range from `start` to `end` with step value 1. + */ + def inclusive(start: Int, end: Int): Range.Inclusive = new Inclusive(start, end, 1) + + // BigInt and Long are straightforward generic ranges. + object BigInt { + def apply(start: BigInt, end: BigInt, step: BigInt) = NumericRange(start, end, step) + def inclusive(start: BigInt, end: BigInt, step: BigInt) = NumericRange.inclusive(start, end, step) + } + + object Long { + def apply(start: Long, end: Long, step: Long) = NumericRange(start, end, step) + def inclusive(start: Long, end: Long, step: Long) = NumericRange.inclusive(start, end, step) + } + + // BigDecimal uses an alternative implementation of Numeric in which + // it pretends to be Integral[T] instead of Fractional[T]. See Numeric for + // details. The intention is for it to throw an exception anytime + // imprecision or surprises might result from anything, although this may + // not yet be fully implemented. + object BigDecimal { + implicit val bigDecAsIntegral = scala.math.Numeric.BigDecimalAsIfIntegral + + def apply(start: BigDecimal, end: BigDecimal, step: BigDecimal) = + NumericRange(start, end, step) + def inclusive(start: BigDecimal, end: BigDecimal, step: BigDecimal) = + NumericRange.inclusive(start, end, step) + } + + // Double works by using a BigDecimal under the hood for precise + // stepping, but mapping the sequence values back to doubles with + // .doubleValue. This constructs the BigDecimals by way of the + // String constructor (valueOf) instead of the Double one, which + // is necessary to keep 0.3d at 0.3 as opposed to + // 0.299999999999999988897769753748434595763683319091796875 or so. + object Double { + implicit val bigDecAsIntegral = scala.math.Numeric.BigDecimalAsIfIntegral + implicit val doubleAsIntegral = scala.math.Numeric.DoubleAsIfIntegral + def toBD(x: Double): BigDecimal = scala.math.BigDecimal valueOf x + + def apply(start: Double, end: Double, step: Double) = + BigDecimal(toBD(start), toBD(end), toBD(step)) mapRange (_.doubleValue) + + def inclusive(start: Double, end: Double, step: Double) = + BigDecimal.inclusive(toBD(start), toBD(end), toBD(step)) mapRange (_.doubleValue) + } + + // As there is no appealing default step size for not-really-integral ranges, + // we offer a partially constructed object. + class Partial[T, U](f: T => U) { + def by(x: T): U = f(x) + } + + // Illustrating genericity with Int Range, which should have the same behavior + // as the original Range class. However we leave the original Range + // indefinitely, for performance and because the compiler seems to bootstrap + // off it and won't do so with our parameterized version without modifications. + object Int { + def apply(start: Int, end: Int, step: Int) = NumericRange(start, end, step) + def inclusive(start: Int, end: Int, step: Int) = NumericRange.inclusive(start, end, step) + } +} diff --git a/examples/scala-js/scalalib/overrides-2.11/scala/collection/mutable/Buffer.scala b/examples/scala-js/scalalib/overrides-2.11/scala/collection/mutable/Buffer.scala new file mode 100644 index 0000000..2171cb9 --- /dev/null +++ b/examples/scala-js/scalalib/overrides-2.11/scala/collection/mutable/Buffer.scala @@ -0,0 +1,51 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2003-2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + + + +package scala +package collection +package mutable + +import generic._ + +import scala.scalajs.js + +/** Buffers are used to create sequences of elements incrementally by + * appending, prepending, or inserting new elements. It is also + * possible to access and modify elements in a random access fashion + * via the index of the element in the current sequence. + * + * @author Matthias Zenger + * @author Martin Odersky + * @version 2.8 + * @since 1 + * + * @tparam A type of the elements contained in this buffer. + * + * @define Coll `Buffer` + * @define coll buffer + */ +trait Buffer[A] extends Seq[A] + with GenericTraversableTemplate[A, Buffer] + with BufferLike[A, Buffer[A]] + with scala.Cloneable { + override def companion: GenericCompanion[Buffer] = Buffer +} + +/** $factoryInfo + * @define coll buffer + * @define Coll `Buffer` + */ +object Buffer extends SeqFactory[Buffer] { + implicit def canBuildFrom[A]: CanBuildFrom[Coll, A, Buffer[A]] = ReusableCBF.asInstanceOf[GenericCanBuildFrom[A]] + def newBuilder[A]: Builder[A, Buffer[A]] = new js.WrappedArray +} + +/** Explicit instantiation of the `Buffer` trait to reduce class file size in subclasses. */ +abstract class AbstractBuffer[A] extends AbstractSeq[A] with Buffer[A] diff --git a/examples/scala-js/scalalib/overrides-2.11/scala/compat/Platform.scala b/examples/scala-js/scalalib/overrides-2.11/scala/compat/Platform.scala new file mode 100644 index 0000000..cdb6916 --- /dev/null +++ b/examples/scala-js/scalalib/overrides-2.11/scala/compat/Platform.scala @@ -0,0 +1,132 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2002-2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + +package scala +package compat + +import java.lang.System + +object Platform { + + /** Thrown when a stack overflow occurs because a method or function recurses too deeply. + * + * On the JVM, this is a type alias for `java.lang.StackOverflowError`, which itself extends `java.lang.Error`. + * The same rules apply to catching a `java.lang.Error` as for Java, that it indicates a serious problem that a reasonable application should not try and catch. + */ + type StackOverflowError = java.lang.StackOverflowError + + /** This is a type alias for `java.util.ConcurrentModificationException`, + * which may be thrown by methods that detect an invalid modification of an object. + * For example, many common collection types do not allow modifying a collection + * while it is being iterated over. + */ + type ConcurrentModificationException = java.util.ConcurrentModificationException + + /** Copies `length` elements of array `src` starting at position `srcPos` to the + * array `dest` starting at position `destPos`. If `src`==`dest`, the copying will + * behave as if the elements copied from `src` were first copied to a temporary + * array before being copied back into the array at the destination positions. + * + * @param src A non-null array as source for the copy. + * @param srcPos The starting index in the source array. + * @param dest A non-null array as destination for the copy. + * @param destPos The starting index in the destination array. + * @param length The number of elements to be copied. + * @throws java.lang.NullPointerException If either `src` or `dest` are `null`. + * @throws java.lang.ArrayStoreException If either `src` or `dest` are not of type + * [java.lang.Array]; or if the element type of `src` is not + * compatible with that of `dest`. + * @throws java.lang.IndexOutOfBoundsException If either srcPos` or `destPos` are + * outside of the bounds of their respective arrays; or if `length` + * is negative; or if there are less than `length` elements available + * after `srcPos` or `destPos` in `src` and `dest` respectively. + */ + @inline + def arraycopy(src: AnyRef, srcPos: Int, dest: AnyRef, destPos: Int, length: Int) { + System.arraycopy(src, srcPos, dest, destPos, length) + } + + /** Creates a new array of the specified type and given length. + * + * Note that if `elemClass` is a subclass of [[scala.AnyVal]] then the returned value is an Array of the corresponding java primitive type. + * For example, the following code `scala.compat.Platform.createArray(classOf[Int], 4)` returns an array of the java primitive type `int`. + * + * For a [[scala.AnyVal]] array, the values of the array are set to 0 for ''numeric value types'' ([[scala.Double]], [[scala.Float]], [[scala.Long]], [[scala.Int]], [[scala.Char]], + * [[scala.Short]], and [[scala.Byte]]), and `false` for [[scala.Boolean]]. Creation of an array of type [[scala.Unit]] is not possible. + * + * For subclasses of [[scala.AnyRef]], the values of the array are set to `null`. + * + * The caller must cast the returned value to the correct type. + * + * @example {{{ + * val a = scala.compat.Platform.createArray(classOf[Int], 4).asInstanceOf[Array[Int]] // returns Array[Int](0, 0, 0, 0) + * }}} + * + * @param elemClass the `Class` object of the component type of the array + * @param length the length of the new array. + * @return an array of the given component type as an `AnyRef`. + * @throws `java.lang.NullPointerException` If `elemClass` is `null`. + * @throws `java.lang.IllegalArgumentException` if componentType is [[scala.Unit]] or `java.lang.Void.TYPE` + * @throws `java.lang.NegativeArraySizeException` if the specified length is negative + */ + @inline + def createArray(elemClass: Class[_], length: Int): AnyRef = + java.lang.reflect.Array.newInstance(elemClass, length) + + /** Assigns the value of 0 to each element in the array. + * @param arr A non-null Array[Int]. + * @throws `java.lang.NullPointerException` If `arr` is `null`. + */ + @inline + def arrayclear(arr: Array[Int]) { java.util.Arrays.fill(arr, 0) } + + /** Returns the `Class` object associated with the class or interface with the given string name using the current `ClassLoader`. + * On the JVM, invoking this method is equivalent to: `java.lang.Class.forName(name)` + * + * For more information, please see the Java documentation for [[java.lang.Class]]. + * + * @param name the fully qualified name of the desired class. + * @return the `Class` object for the class with the specified name. + * @throws `java.lang.LinkageError` if the linkage fails + * @throws `java.lang.ExceptionInInitializerError` if the initialization provoked by this method fails + * @throws `java.lang.ClassNotFoundException` if the class cannot be located + * @example {{{ + * val a = scala.compat.Platform.getClassForName("java.lang.Integer") // returns the Class[_] for java.lang.Integer + * }}} + */ + @inline + def getClassForName(name: String): Class[_] = java.lang.Class.forName(name) + + /** The default line separator. + * + * On the JavaScript backend, this is always "\n". + */ + val EOL = "\n" + + /** The current time in milliseconds. The time is counted since 1 January 1970 + * UTC. + * + * Note that the operating system timer used to obtain this value may be less + * precise than a millisecond. + */ + @inline + def currentTime: Long = System.currentTimeMillis() + + /** Runs the garbage collector. + * + * This is a request that the underlying JVM runs the garbage collector. + * The results of this call depends heavily on the JVM used. + * The underlying JVM is free to ignore this request. + */ + @inline + def collectGarbage(): Unit = System.gc() + + /** The name of the default character set encoding as a string */ + @inline + def defaultCharsetName: String = java.nio.charset.Charset.defaultCharset.name +} diff --git a/examples/scala-js/scalalib/overrides-2.11/scala/package.scala b/examples/scala-js/scalalib/overrides-2.11/scala/package.scala new file mode 100644 index 0000000..21051d4 --- /dev/null +++ b/examples/scala-js/scalalib/overrides-2.11/scala/package.scala @@ -0,0 +1,133 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2003-2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + + +/** + * Core Scala types. They are always available without an explicit import. + * @contentDiagram hideNodes "scala.Serializable" + */ +package object scala { + type Throwable = java.lang.Throwable + type Exception = java.lang.Exception + type Error = java.lang.Error + + type RuntimeException = java.lang.RuntimeException + type NullPointerException = java.lang.NullPointerException + type ClassCastException = java.lang.ClassCastException + type IndexOutOfBoundsException = java.lang.IndexOutOfBoundsException + type ArrayIndexOutOfBoundsException = java.lang.ArrayIndexOutOfBoundsException + type StringIndexOutOfBoundsException = java.lang.StringIndexOutOfBoundsException + type UnsupportedOperationException = java.lang.UnsupportedOperationException + type IllegalArgumentException = java.lang.IllegalArgumentException + type NoSuchElementException = java.util.NoSuchElementException + type NumberFormatException = java.lang.NumberFormatException + type AbstractMethodError = java.lang.AbstractMethodError + type InterruptedException = java.lang.InterruptedException + + // A dummy used by the specialization annotation. + val AnyRef = new Specializable { + override def toString = "object AnyRef" + } + + type TraversableOnce[+A] = scala.collection.TraversableOnce[A] + + type Traversable[+A] = scala.collection.Traversable[A] + val Traversable = scala.collection.Traversable + + type Iterable[+A] = scala.collection.Iterable[A] + val Iterable = scala.collection.Iterable + + type Seq[+A] = scala.collection.Seq[A] + val Seq = scala.collection.Seq + + type IndexedSeq[+A] = scala.collection.IndexedSeq[A] + val IndexedSeq = scala.collection.IndexedSeq + + type Iterator[+A] = scala.collection.Iterator[A] + val Iterator = scala.collection.Iterator + + type BufferedIterator[+A] = scala.collection.BufferedIterator[A] + + type List[+A] = scala.collection.immutable.List[A] + val List = scala.collection.immutable.List + + val Nil = scala.collection.immutable.Nil + + type ::[A] = scala.collection.immutable.::[A] + val :: = scala.collection.immutable.:: + + val +: = scala.collection.+: + val :+ = scala.collection.:+ + + type Stream[+A] = scala.collection.immutable.Stream[A] + val Stream = scala.collection.immutable.Stream + val #:: = scala.collection.immutable.Stream.#:: + + type Vector[+A] = scala.collection.immutable.Vector[A] + val Vector = scala.collection.immutable.Vector + + type StringBuilder = scala.collection.mutable.StringBuilder + val StringBuilder = scala.collection.mutable.StringBuilder + + type Range = scala.collection.immutable.Range + val Range = scala.collection.immutable.Range + + // Numeric types which were moved into scala.math.* + + type BigDecimal = scala.math.BigDecimal + lazy val BigDecimal = scala.math.BigDecimal + + type BigInt = scala.math.BigInt + lazy val BigInt = scala.math.BigInt + + type Equiv[T] = scala.math.Equiv[T] + val Equiv = scala.math.Equiv + + type Fractional[T] = scala.math.Fractional[T] + val Fractional = scala.math.Fractional + + type Integral[T] = scala.math.Integral[T] + val Integral = scala.math.Integral + + type Numeric[T] = scala.math.Numeric[T] + val Numeric = scala.math.Numeric + + type Ordered[T] = scala.math.Ordered[T] + val Ordered = scala.math.Ordered + + type Ordering[T] = scala.math.Ordering[T] + val Ordering = scala.math.Ordering + + type PartialOrdering[T] = scala.math.PartialOrdering[T] + type PartiallyOrdered[T] = scala.math.PartiallyOrdered[T] + + type Either[+A, +B] = scala.util.Either[A, B] + val Either = scala.util.Either + + type Left[+A, +B] = scala.util.Left[A, B] + val Left = scala.util.Left + + type Right[+A, +B] = scala.util.Right[A, B] + val Right = scala.util.Right + + // Annotations which we might move to annotation.* +/* + type SerialVersionUID = annotation.SerialVersionUID + type deprecated = annotation.deprecated + type deprecatedName = annotation.deprecatedName + type inline = annotation.inline + type native = annotation.native + type noinline = annotation.noinline + type remote = annotation.remote + type specialized = annotation.specialized + type transient = annotation.transient + type throws = annotation.throws + type unchecked = annotation.unchecked.unchecked + type volatile = annotation.volatile + */ +} diff --git a/examples/scala-js/scalalib/overrides/scala/App.scala b/examples/scala-js/scalalib/overrides/scala/App.scala new file mode 100644 index 0000000..c49817b --- /dev/null +++ b/examples/scala-js/scalalib/overrides/scala/App.scala @@ -0,0 +1,83 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2010-2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + +package scala + +import scala.compat.Platform.currentTime +import scala.collection.mutable.ListBuffer + +import scala.scalajs.js.annotation.JSExport + +/** The `App` trait can be used to quickly turn objects + * into executable programs. Here is an example: + * {{{ + * object Main extends App { + * Console.println("Hello World: " + (args mkString ", ")) + * } + * }}} + * Here, object `Main` inherits the `main` method of `App`. + * + * `args` returns the current command line arguments as an array. + * + * ==Caveats== + * + * '''''It should be noted that this trait is implemented using the [[DelayedInit]] + * functionality, which means that fields of the object will not have been initialized + * before the main method has been executed.''''' + * + * It should also be noted that the `main` method will not normally need to be overridden: + * the purpose is to turn the whole class body into the “main method”. You should only + * chose to override it if you know what you are doing. + * + * @author Martin Odersky + * @version 2.1, 15/02/2011 + */ +trait App extends DelayedInit { + + /** The time when the execution of this program started, in milliseconds since 1 + * January 1970 UTC. */ + val executionStart: Long = currentTime + + /** The command line arguments passed to the application's `main` method. + */ + protected def args: Array[String] = _args + + private var _args: Array[String] = _ + + private val initCode = new ListBuffer[() => Unit] + + /** The init hook. This saves all initialization code for execution within `main`. + * This method is normally never called directly from user code. + * Instead it is called as compiler-generated code for those classes and objects + * (but not traits) that inherit from the `DelayedInit` trait and that do not + * themselves define a `delayedInit` method. + * @param body the initialization code to be stored for later execution + */ + override def delayedInit(body: => Unit) { + initCode += (() => body) + } + + /** The main method. + * This stores all argument so that they can be retrieved with `args` + * and the executes all initialization code segments in the order they were + * passed to `delayedInit` + * + * @param args the arguments passed to the main method + */ + def main(args: Array[String]) = { + this._args = args + for (proc <- initCode) proc() + + /* DELETED for Scala.js + if (util.Properties.propIsSet("scala.time")) { + val total = currentTime - executionStart + Console.println("[total " + total + "ms]") + } + */ + } +} diff --git a/examples/scala-js/scalalib/overrides/scala/Enumeration.scala b/examples/scala-js/scalalib/overrides/scala/Enumeration.scala new file mode 100644 index 0000000..bdc1701 --- /dev/null +++ b/examples/scala-js/scalalib/overrides/scala/Enumeration.scala @@ -0,0 +1,284 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2002-2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + +package scala + +import scala.collection.{ mutable, immutable, generic, SortedSetLike, AbstractSet } +import java.lang.reflect.{ Modifier, Method => JMethod, Field => JField } +import scala.reflect.NameTransformer._ +import java.util.regex.Pattern + +/** Defines a finite set of values specific to the enumeration. Typically + * these values enumerate all possible forms something can take and provide + * a lightweight alternative to case classes. + * + * Each call to a `Value` method adds a new unique value to the enumeration. + * To be accessible, these values are usually defined as `val` members of + * the evaluation. + * + * All values in an enumeration share a common, unique type defined as the + * `Value` type member of the enumeration (`Value` selected on the stable + * identifier path of the enumeration instance). + * + * @example {{{ + * object Main extends App { + * + * object WeekDay extends Enumeration { + * type WeekDay = Value + * val Mon, Tue, Wed, Thu, Fri, Sat, Sun = Value + * } + * import WeekDay._ + * + * def isWorkingDay(d: WeekDay) = ! (d == Sat || d == Sun) + * + * WeekDay.values filter isWorkingDay foreach println + * } + * // output: + * // Mon + * // Tue + * // Wed + * // Thu + * // Fri + * }}} + * + * @param initial The initial value from which to count the integers that + * identifies values at run-time. + * @author Matthias Zenger + */ +@SerialVersionUID(8476000850333817230L) +abstract class Enumeration (initial: Int) extends Serializable { + thisenum => + + def this() = this(0) + + /* Note that `readResolve` cannot be private, since otherwise + the JVM does not invoke it when deserializing subclasses. */ + protected def readResolve(): AnyRef = ??? + + /** The name of this enumeration. + */ + override def toString = + (getClass.getName.stripSuffix("$").split('.')).last.split('$').last + + /** The mapping from the integer used to identify values to the actual + * values. */ + private val vmap: mutable.Map[Int, Value] = new mutable.HashMap + + /** The cache listing all values of this enumeration. */ + @transient private var vset: ValueSet = null + @transient @volatile private var vsetDefined = false + + /** The mapping from the integer used to identify values to their + * names. */ + private val nmap: mutable.Map[Int, String] = new mutable.HashMap + + /** The values of this enumeration as a set. + */ + def values: ValueSet = { + if (!vsetDefined) { + vset = (ValueSet.newBuilder ++= vmap.values).result() + vsetDefined = true + } + vset + } + + /** The integer to use to identify the next created value. */ + protected var nextId: Int = initial + + /** The string to use to name the next created value. */ + protected var nextName: Iterator[String] = _ + + private def nextNameOrNull = + if (nextName != null && nextName.hasNext) nextName.next() else null + + /** The highest integer amongst those used to identify values in this + * enumeration. */ + private var topId = initial + + /** The lowest integer amongst those used to identify values in this + * enumeration, but no higher than 0. */ + private var bottomId = if(initial < 0) initial else 0 + + /** The one higher than the highest integer amongst those used to identify + * values in this enumeration. */ + final def maxId = topId + + /** The value of this enumeration with given id `x` + */ + final def apply(x: Int): Value = vmap(x) + + /** Return a `Value` from this `Enumeration` whose name matches + * the argument `s`. The names are determined automatically via reflection. + * + * @param s an `Enumeration` name + * @return the `Value` of this `Enumeration` if its name matches `s` + * @throws NoSuchElementException if no `Value` with a matching + * name is in this `Enumeration` + */ + final def withName(s: String): Value = { + val (unnamed, named) = values partition { + _.toString().startsWith("<Unknown name for enum field ") + } + + named.find(_.toString == s) match { + case Some(v) => v + // If we have unnamed values, we issue a detailed error message + case None if unnamed.nonEmpty => + throw new NoSuchElementException( + s"""Couldn't find enum field with name $s. + |However, there were the following unnamed fields: + |${unnamed.mkString(" ","\n ","")}""".stripMargin) + // Normal case (no unnamed Values) + case _ => None.get + } + } + + /** Creates a fresh value, part of this enumeration. */ + protected final def Value: Value = Value(nextId) + + /** Creates a fresh value, part of this enumeration, identified by the + * integer `i`. + * + * @param i An integer that identifies this value at run-time. It must be + * unique amongst all values of the enumeration. + * @return Fresh value identified by `i`. + */ + protected final def Value(i: Int): Value = Value(i, nextNameOrNull) + + /** Creates a fresh value, part of this enumeration, called `name`. + * + * @param name A human-readable name for that value. + * @return Fresh value called `name`. + */ + protected final def Value(name: String): Value = Value(nextId, name) + + /** Creates a fresh value, part of this enumeration, called `name` + * and identified by the integer `i`. + * + * @param i An integer that identifies this value at run-time. It must be + * unique amongst all values of the enumeration. + * @param name A human-readable name for that value. + * @return Fresh value with the provided identifier `i` and name `name`. + */ + protected final def Value(i: Int, name: String): Value = new Val(i, name) + + /** The type of the enumerated values. */ + @SerialVersionUID(7091335633555234129L) + abstract class Value extends Ordered[Value] with Serializable { + /** the id and bit location of this enumeration value */ + def id: Int + /** a marker so we can tell whose values belong to whom come reflective-naming time */ + private[Enumeration] val outerEnum = thisenum + + override def compare(that: Value): Int = + if (this.id < that.id) -1 + else if (this.id == that.id) 0 + else 1 + override def equals(other: Any) = other match { + case that: Enumeration#Value => (outerEnum eq that.outerEnum) && (id == that.id) + case _ => false + } + override def hashCode: Int = id.## + + /** Create a ValueSet which contains this value and another one */ + def + (v: Value) = ValueSet(this, v) + } + + /** A class implementing the [[scala.Enumeration.Value]] type. This class + * can be overridden to change the enumeration's naming and integer + * identification behaviour. + */ + @SerialVersionUID(0 - 3501153230598116017L) + protected class Val(i: Int, name: String) + extends Value with Serializable { + + def this(i: Int) = this(i, nextNameOrNull) + def this(name: String) = this(nextId, name) + def this() = this(nextId) + + assert(!vmap.isDefinedAt(i), "Duplicate id: " + i) + vmap(i) = this + vsetDefined = false + nextId = i + 1 + if (nextId > topId) topId = nextId + if (i < bottomId) bottomId = i + def id = i + override def toString() = + if (name != null) name + // Scala.js specific + else s"<Unknown name for enum field #$i of class ${getClass}>" + + protected def readResolve(): AnyRef = { + val enum = thisenum.readResolve().asInstanceOf[Enumeration] + if (enum.vmap == null) this + else enum.vmap(i) + } + } + + /** An ordering by id for values of this set */ + object ValueOrdering extends Ordering[Value] { + def compare(x: Value, y: Value): Int = x compare y + } + + /** A class for sets of values. + * Iterating through this set will yield values in increasing order of their ids. + * + * @param nnIds The set of ids of values (adjusted so that the lowest value does + * not fall below zero), organized as a `BitSet`. + */ + class ValueSet private[ValueSet] (private[this] var nnIds: immutable.BitSet) + extends AbstractSet[Value] + with immutable.SortedSet[Value] + with SortedSetLike[Value, ValueSet] + with Serializable { + + implicit def ordering: Ordering[Value] = ValueOrdering + def rangeImpl(from: Option[Value], until: Option[Value]): ValueSet = + new ValueSet(nnIds.rangeImpl(from.map(_.id - bottomId), until.map(_.id - bottomId))) + + override def empty = ValueSet.empty + def contains(v: Value) = nnIds contains (v.id - bottomId) + def + (value: Value) = new ValueSet(nnIds + (value.id - bottomId)) + def - (value: Value) = new ValueSet(nnIds - (value.id - bottomId)) + def iterator = nnIds.iterator map (id => thisenum.apply(bottomId + id)) + // This is only defined in 2.11. We change its implementation so it also + // compiles on 2.10. + def keysIteratorFrom(start: Value) = from(start).keySet.toIterator + //nnIds keysIteratorFrom start.id map (id => thisenum.apply(bottomId + id)) + override def stringPrefix = thisenum + ".ValueSet" + /** Creates a bit mask for the zero-adjusted ids in this set as a + * new array of longs */ + def toBitMask: Array[Long] = nnIds.toBitMask + } + + /** A factory object for value sets */ + object ValueSet { + import generic.CanBuildFrom + + /** The empty value set */ + val empty = new ValueSet(immutable.BitSet.empty) + /** A value set consisting of given elements */ + def apply(elems: Value*): ValueSet = (newBuilder ++= elems).result() + /** A value set containing all the values for the zero-adjusted ids + * corresponding to the bits in an array */ + def fromBitMask(elems: Array[Long]): ValueSet = new ValueSet(immutable.BitSet.fromBitMask(elems)) + /** A builder object for value sets */ + def newBuilder: mutable.Builder[Value, ValueSet] = new mutable.Builder[Value, ValueSet] { + private[this] val b = new mutable.BitSet + def += (x: Value) = { b += (x.id - bottomId); this } + def clear() = b.clear() + def result() = new ValueSet(b.toImmutable) + } + /** The implicit builder for value sets */ + implicit def canBuildFrom: CanBuildFrom[ValueSet, Value, ValueSet] = + new CanBuildFrom[ValueSet, Value, ValueSet] { + def apply(from: ValueSet) = newBuilder + def apply() = newBuilder + } + } +}
\ No newline at end of file diff --git a/examples/scala-js/scalalib/overrides/scala/Symbol.scala b/examples/scala-js/scalalib/overrides/scala/Symbol.scala new file mode 100644 index 0000000..1af9d28 --- /dev/null +++ b/examples/scala-js/scalalib/overrides/scala/Symbol.scala @@ -0,0 +1,117 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2003-2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + +package scala + +import scala.scalajs.js + +/** This class provides a simple way to get unique objects for equal strings. + * Since symbols are interned, they can be compared using reference equality. + * Instances of `Symbol` can be created easily with Scala's built-in quote + * mechanism. + * + * For instance, the [[http://scala-lang.org/#_top Scala]] term `'mysym` will + * invoke the constructor of the `Symbol` class in the following way: + * `Symbol("mysym")`. + * + * @author Martin Odersky, Iulian Dragos + * @version 1.8 + */ +final class Symbol private (val name: String) extends Serializable { + /** Converts this symbol to a string. + */ + override def toString(): String = "'" + name + + @throws(classOf[java.io.ObjectStreamException]) + private def readResolve(): Any = Symbol.apply(name) + override def hashCode = name.hashCode() + override def equals(other: Any) = this eq other.asInstanceOf[AnyRef] +} + +// Modified to use Scala.js specific cache +object Symbol extends JSUniquenessCache[Symbol] { + override def apply(name: String): Symbol = super.apply(name) + protected def valueFromKey(name: String): Symbol = new Symbol(name) + protected def keyFromValue(sym: Symbol): Option[String] = Some(sym.name) +} + +private[scala] abstract class JSUniquenessCache[V] +{ + private val map = js.Dictionary.empty[js.Any] // V | js.Undefined + + protected def valueFromKey(k: String): V + protected def keyFromValue(v: V): Option[String] + + def apply(name: String): V = { + val cachedSym = map(name) + if (js.isUndefined(cachedSym)) { + val sym = valueFromKey(name) + map(name) = sym.asInstanceOf[js.Any] + sym.asInstanceOf[V] + } else { + cachedSym.asInstanceOf[V] + } + } + + def unapply(other: V): Option[String] = keyFromValue(other) +} + +/** This is private so it won't appear in the library API, but + * abstracted to offer some hope of reusability. */ +/* DELETED for Scala.js +private[scala] abstract class UniquenessCache[K >: js.String, V >: Null] +{ + + import java.lang.ref.WeakReference + import java.util.WeakHashMap + import java.util.concurrent.locks.ReentrantReadWriteLock + + private val rwl = new ReentrantReadWriteLock() + private val rlock = rwl.readLock + private val wlock = rwl.writeLock + private val map = new WeakHashMap[K, WeakReference[V]] + + protected def valueFromKey(k: K): V + protected def keyFromValue(v: V): Option[K] + + def apply(name: K): V = { + def cached(): V = { + rlock.lock + try { + val reference = map get name + if (reference == null) null + else reference.get // will be null if we were gc-ed + } + finally rlock.unlock + } + def updateCache(): V = { + wlock.lock + try { + val res = cached() + if (res != null) res + else { + // If we don't remove the old String key from the map, we can + // wind up with one String as the key and a different String as + // as the name field in the Symbol, which can lead to surprising + // GC behavior and duplicate Symbols. See SI-6706. + map remove name + val sym = valueFromKey(name) + map.put(name, new WeakReference(sym)) + sym + } + } + finally wlock.unlock + } + + val res = cached() + if (res == null) updateCache() + else res + } + def unapply(other: V): Option[K] = keyFromValue(other) +} +*/ diff --git a/examples/scala-js/scalalib/overrides/scala/concurrent/impl/AbstractPromise.scala b/examples/scala-js/scalalib/overrides/scala/concurrent/impl/AbstractPromise.scala new file mode 100644 index 0000000..8ea135e --- /dev/null +++ b/examples/scala-js/scalalib/overrides/scala/concurrent/impl/AbstractPromise.scala @@ -0,0 +1,29 @@ +package scala.concurrent.impl + +/** + * JavaScript specific implementation of AbstractPromise + * + * This basically implements a "CAS" in Scala for JavaScript. Its + * implementation is trivial because there is no multi-threading. + * + * @author Tobias Schlatter + */ +abstract class AbstractPromise { + + private var state: AnyRef = _ + + protected final + def updateState(oldState: AnyRef, newState: AnyRef): Boolean = { + if (state eq oldState) { + state = newState + true + } else false + } + + protected final def getState: AnyRef = state + +} + +object AbstractPromise { + protected def updater = ??? +} diff --git a/examples/scala-js/scalalib/overrides/scala/math/ScalaNumber.scala b/examples/scala-js/scalalib/overrides/scala/math/ScalaNumber.scala new file mode 100644 index 0000000..811346d --- /dev/null +++ b/examples/scala-js/scalalib/overrides/scala/math/ScalaNumber.scala @@ -0,0 +1,21 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2006-2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + + + +package scala.math + +/** A marker class for Number types introduced by Scala + * @author Martin Odersky, Paul Phillips + * @version 2.8 + * @since 2.8 + */ +abstract class ScalaNumber extends java.lang.Number { + protected def isWhole(): Boolean + def underlying(): Object +} diff --git a/examples/scala-js/scalalib/overrides/scala/runtime/BoxesRunTime.scala b/examples/scala-js/scalalib/overrides/scala/runtime/BoxesRunTime.scala new file mode 100644 index 0000000..5df7fd1 --- /dev/null +++ b/examples/scala-js/scalalib/overrides/scala/runtime/BoxesRunTime.scala @@ -0,0 +1,124 @@ +package scala.runtime + +import scala.math.ScalaNumber + +object BoxesRunTime { + def boxToCharacter(c: Char): java.lang.Character = + java.lang.Character.valueOf(c) + + def unboxToChar(c: Object): Char = + if (c eq null) 0 + else c.asInstanceOf[java.lang.Character].charValue() + + def equals(x: Object, y: Object): Boolean = + if (x eq y) true + else equals2(x, y) + + @inline // only called by equals(), not by codegen + def equals2(x: Object, y: Object): Boolean = { + x match { + case xn: java.lang.Number => equalsNumObject(xn, y) + case xc: java.lang.Character => equalsCharObject(xc, y) + case null => y eq null + case _ => x.equals(y) + } + } + + def equalsNumObject(xn: java.lang.Number, y: Object): Boolean = { + y match { + case yn: java.lang.Number => equalsNumNum(xn, yn) + case yc: java.lang.Character => equalsNumChar(xn, yc) + case _ => + if (xn eq null) + y eq null + else + xn.equals(y) + } + } + + def equalsNumNum(xn: java.lang.Number, yn: java.lang.Number): Boolean = { + (xn: Any) match { + case xn: Double => + (yn: Any) match { + case yn: Double => xn == yn + case yn: Long => xn == yn + case yn: ScalaNumber => yn.equals(xn) // xn is not a ScalaNumber + case _ => false // xn.equals(yn) must be false here + } + case xn: Long => + (yn: Any) match { + case yn: Long => xn == yn + case yn: Double => xn == yn + case yn: ScalaNumber => yn.equals(xn) // xn is not a ScalaNumber + case _ => false // xn.equals(yn) must be false here + } + case null => yn eq null + case _ => xn.equals(yn) + } + } + + def equalsCharObject(xc: java.lang.Character, y: Object): Boolean = { + y match { + case yc: java.lang.Character => xc.charValue() == yc.charValue() + case yn: java.lang.Number => equalsNumChar(yn, xc) + case _ => + if (xc eq null) + y eq null + else + false // xc.equals(y) must be false here, because y is not a Char + } + } + + @inline + private def equalsNumChar(xn: java.lang.Number, yc: java.lang.Character): Boolean = { + (xn: Any) match { + case xn: Double => xn == yc.charValue() + case xn: Long => xn == yc.charValue() + case _ => + if (xn eq null) yc eq null + else xn.equals(yc) + } + } + + def hashFromLong(n: java.lang.Long): Int = { + val iv = n.intValue() + if (iv == n.longValue()) iv + else n.hashCode() + } + + def hashFromDouble(n: java.lang.Double): Int = { + val iv = n.intValue() + val dv = n.doubleValue() + if (iv == dv) { + iv + } else { + val lv = n.longValue() + if (lv == dv) { + java.lang.Long.valueOf(lv).hashCode() + } else { + // don't test the case floatValue() == dv + n.hashCode() + } + } + } + + def hashFromFloat(n: java.lang.Float): Int = { + hashFromDouble(java.lang.Double.valueOf(n.doubleValue())) + } + + def hashFromNumber(n: java.lang.Number): Int = { + (n: Any) match { + case n: Int => n + case n: java.lang.Long => hashFromLong(n) + case n: java.lang.Double => hashFromDouble(n) + case n => n.hashCode() + } + } + + def hashFromObject(a: Object): Int = { + a match { + case a: java.lang.Number => hashFromNumber(a) + case a => a.hashCode() + } + } +} diff --git a/examples/scala-js/scalalib/overrides/scala/util/control/NoStackTrace.scala b/examples/scala-js/scalalib/overrides/scala/util/control/NoStackTrace.scala new file mode 100644 index 0000000..bcc2839 --- /dev/null +++ b/examples/scala-js/scalalib/overrides/scala/util/control/NoStackTrace.scala @@ -0,0 +1,33 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2003-2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + +package scala +package util.control + +/** A trait for exceptions which, for efficiency reasons, do not + * fill in the stack trace. Stack trace suppression can be disabled + * on a global basis via a system property wrapper in + * [[scala.sys.SystemProperties]]. + * + * @author Paul Phillips + * @since 2.8 + */ +trait NoStackTrace extends Throwable { + override def fillInStackTrace(): Throwable = + if (NoStackTrace.noSuppression) super.fillInStackTrace() + else this +} + +object NoStackTrace { + final def noSuppression = _noSuppression + + // two-stage init to make checkinit happy, since sys.SystemProperties.noTraceSupression.value calls back into NoStackTrace.noSuppression + final private var _noSuppression = false + // !!! Disabled in Scala.js because SystemProperties is not supported + //_noSuppression = sys.SystemProperties.noTraceSupression.value +} diff --git a/examples/scala-js/scripts/assemble-cli.sh b/examples/scala-js/scripts/assemble-cli.sh new file mode 100755 index 0000000..8d898e8 --- /dev/null +++ b/examples/scala-js/scripts/assemble-cli.sh @@ -0,0 +1,96 @@ +#! /bin/sh + +set -e + +# Assembles the CLI tools for a given Scala binary version. + +if [ $# -lt 1 ]; then + echo "Usage: $(basename $0) <binary scala version> [noclean|nobuild]" >&2 + exit 1 +fi + +BINVER=$1 +case $BINVER in + 2.10) + FULLVERS="2.10.2 2.10.3 2.10.4" + BASEVER="2.10.4" + ;; + 2.11) + FULLVERS="2.11.0 2.11.1 2.11.2 2.11.4" + BASEVER="2.11.2" # Tools do not compile for 2.11.4 (see #1215) + ;; + *) + echo "Invalid Scala version $BINVER" >&2 + exit 2 +esac + +if [ "$2" != "nobuild" ]; then + # Subshell to generate SBT commands + ( + if [ "$2" != "noclean" ]; then + echo "clean" + fi + + # Assemble cli-tools + echo "project cli" + echo "++$BASEVER" + echo "assembly" + + # Package Scala.js library + echo "project library" + echo "++$BASEVER" + echo "package" + + # Package compiler + echo "project compiler" + for i in $FULLVERS; do + echo "++$i" + echo "package" + done + ) | sbt || exit $? +fi + +# Copy stuff to right location +BASE="$(dirname $0)/.." + +# Target directories +TRG_BASE="$BASE/cli/pack" +TRG_VER="$TRG_BASE/$BINVER" +TRG_LIB="$TRG_VER/lib" +TRG_BIN="$TRG_VER/bin" + +rm -rf $TRG_VER +mkdir -p $TRG_LIB +mkdir -p $TRG_BIN + +SCALAJS_VER=$(ls $BASE/cli/target/scala-$BINVER/scalajs-cli-assembly_$BINVER-*.jar | grep -oE '[0-9]+\.[0-9]+\.[0-9]+(-SNAPSHOT|-RC[0-9]+|-M[0-9]+)?') + +cp $BASE/cli/target/scala-$BINVER/scalajs-cli-assembly_$BINVER-$SCALAJS_VER.jar $TRG_LIB +cp $BASE/library/target/scala-$BINVER/scalajs-library_$BINVER-$SCALAJS_VER.jar $TRG_LIB + +for i in $FULLVERS; do + cp $BASE/compiler/target/scala-$BINVER/scalajs-compiler_$i-$SCALAJS_VER.jar $TRG_LIB +done + +PAT="s/@SCALA_BIN_VER@/$BINVER/; s/@SCALAJS_VER@/$SCALAJS_VER/" +PREF=$BASE/cli/src/main/resources/ +for i in $PREF*; do + out=$TRG_BIN/${i#$PREF} + # Redirect sed output, since in-place edit doesn't work + # cross-platform + sed "$PAT" $i > $out + # Add executable flag if required + if [ -x $i ]; then + chmod +x $out + fi +done + +# Tar and zip the whole thing up +OUT=scalajs_$BINVER-$SCALAJS_VER + +tar cfz $TRG_BASE/$OUT.tgz --exclude '*~' -C $TRG_VER lib bin +( + if [ -f $OUT.zip ]; then rm $OUT.zip; fi + cd $TRG_VER + zip -r ../$OUT.zip -r lib bin -x '*~' +) diff --git a/examples/scala-js/scripts/build-all-js.sh b/examples/scala-js/scripts/build-all-js.sh new file mode 100755 index 0000000..de0e295 --- /dev/null +++ b/examples/scala-js/scripts/build-all-js.sh @@ -0,0 +1,15 @@ +#! /bin/sh + +tasks="fastOptJS fullOptJS" +projects="helloworld/ reversi/ testingExample/test: testSuite/test:" + +for v in 2.11.4 2.10.4; do + echo "++$v" + echo "package" + + for p in $projects; do + for t in $tasks; do + echo "$p$t" + done + done +done | sbt diff --git a/examples/scala-js/scripts/publish-to-bintray.sh b/examples/scala-js/scripts/publish-to-bintray.sh new file mode 100755 index 0000000..3b455ea --- /dev/null +++ b/examples/scala-js/scripts/publish-to-bintray.sh @@ -0,0 +1,35 @@ +#! /bin/sh + +if [ $# -eq 1 -a "$1" = "-x" ]; then + export PUBLISH_TO_BINTRAY=true + CMD=sbt +else + echo "Showing commands that would be issued to SBT. Use -x to run" + CMD=cat +fi + +FULL_VERSIONS="2.10.2 2.10.3 2.10.4 2.11.0 2.11.1 2.11.2 2.11.4" +BIN_VERSIONS="2.10.4 2.11.2" # Tools do not compile on 2.11.4 (see #1215) +SBT_VERSION="2.10.4" + +LIBS="library javalibEx jasmineTestFramework tools toolsJS testBridge stubs" + +# Publish compiler +for v in $FULL_VERSIONS; do + echo "++$v" + echo "compiler/publish" +done | $CMD + +# Package libraries +for p in $LIBS; do + for v in $BIN_VERSIONS; do + echo "++$v" + echo "$p/publish" + done | $CMD +done + +# Publish sbt-plugin +( + echo "++$SBT_VERSION" + echo "sbtPlugin/publish" +) | $CMD diff --git a/examples/scala-js/stubs/src/main/scala/scala/scalajs/js/annotation/ExportAnnotations.scala b/examples/scala-js/stubs/src/main/scala/scala/scalajs/js/annotation/ExportAnnotations.scala new file mode 100644 index 0000000..be23596 --- /dev/null +++ b/examples/scala-js/stubs/src/main/scala/scala/scalajs/js/annotation/ExportAnnotations.scala @@ -0,0 +1,24 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2003-2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + + +package scala.scalajs.js.annotation + +import scala.annotation.Annotation + +class JSExportAll extends scala.annotation.Annotation +class JSExportDescendentObjects extends scala.annotation.Annotation +class JSExportDescendentClasses extends scala.annotation.Annotation + +class JSExportNamed extends scala.annotation.Annotation { + def this(name: String) = this() +} + +class JSExport extends scala.annotation.Annotation { + def this(name: String) = this() +} diff --git a/examples/scala-js/test-bridge/src/main/scala/scala/scalajs/testbridge/Test.scala b/examples/scala-js/test-bridge/src/main/scala/scala/scalajs/testbridge/Test.scala new file mode 100644 index 0000000..05d3077 --- /dev/null +++ b/examples/scala-js/test-bridge/src/main/scala/scala/scalajs/testbridge/Test.scala @@ -0,0 +1,17 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js API ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.testbridge + +import scala.scalajs.js +import js.annotation.JSExportDescendentObjects + +/** Marker trait for Scala.js tests */ +@JSExportDescendentObjects +trait Test diff --git a/examples/scala-js/test-bridge/src/main/scala/scala/scalajs/testbridge/TestFramework.scala b/examples/scala-js/test-bridge/src/main/scala/scala/scalajs/testbridge/TestFramework.scala new file mode 100644 index 0000000..f855cf6 --- /dev/null +++ b/examples/scala-js/test-bridge/src/main/scala/scala/scalajs/testbridge/TestFramework.scala @@ -0,0 +1,36 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js API ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.testbridge + +import scala.scalajs.js +import js.annotation.{ JSExportDescendentObjects, JSExport } + +/** This trait should be sub classed (as object) by a concrete test framework + * + * It will receive a call to runTest for each object on the classpath + * extending Test + */ +@JSExportDescendentObjects +trait TestFramework { + @JSExport + final def safeRunTest(testOutput: TestOutput, args: js.Array[String])( + test: js.Function0[Test]): Unit = { + try { + runTest(testOutput, args)(test) + } catch { + case e: Throwable => + testOutput.error(s"Test framework ${getClass.getName} failed:") + testOutput.error(e.getMessage, e.getStackTrace) + } + } + + def runTest(testOutput: TestOutput, args: js.Array[String])( + test: js.Function0[Test]): Unit +} diff --git a/examples/scala-js/test-bridge/src/main/scala/scala/scalajs/testbridge/TestOutput.scala b/examples/scala-js/test-bridge/src/main/scala/scala/scalajs/testbridge/TestOutput.scala new file mode 100644 index 0000000..5f3b8b6 --- /dev/null +++ b/examples/scala-js/test-bridge/src/main/scala/scala/scalajs/testbridge/TestOutput.scala @@ -0,0 +1,34 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js API ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.testbridge + +trait TestOutput { + + type Color + + val errorColor: Color + val successColor: Color + val infoColor: Color + + def color(message: String, color: Color): String + + def error(message: String, stack: Array[StackTraceElement]): Unit + def error(message: String): Unit + def failure(message: String, stack: Array[StackTraceElement]): Unit + def failure(message: String): Unit + def succeeded(message: String): Unit + def skipped(message: String): Unit + def pending(message: String): Unit + def ignored(message: String): Unit + def canceled(message: String): Unit + + def log: TestOutputLog + +} diff --git a/examples/scala-js/test-bridge/src/main/scala/scala/scalajs/testbridge/TestOutputLog.scala b/examples/scala-js/test-bridge/src/main/scala/scala/scalajs/testbridge/TestOutputLog.scala new file mode 100644 index 0000000..3369592 --- /dev/null +++ b/examples/scala-js/test-bridge/src/main/scala/scala/scalajs/testbridge/TestOutputLog.scala @@ -0,0 +1,16 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js API ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.testbridge + +trait TestOutputLog { + def info(message: String): Unit + def warn(message: String): Unit + def error(message: String): Unit +} diff --git a/examples/scala-js/test-bridge/src/main/scala/scala/scalajs/testbridge/internal/ConsoleTestOutput.scala b/examples/scala-js/test-bridge/src/main/scala/scala/scalajs/testbridge/internal/ConsoleTestOutput.scala new file mode 100644 index 0000000..c89a32e --- /dev/null +++ b/examples/scala-js/test-bridge/src/main/scala/scala/scalajs/testbridge/internal/ConsoleTestOutput.scala @@ -0,0 +1,118 @@ +package scala.scalajs.testbridge.internal + +import scala.scalajs.js +import scala.scalajs.js.annotation.JSExport + +import scala.scalajs.testbridge._ + +import scala.scalajs.runtime.StackTrace.ColumnStackTraceElement + +/** Implementation of TestOutput. + * + * Attention: This class monkey-patches console.log. Make sure it is loaded + * before any output. It also should always be paired with a + * [[scala.scalajs.sbtplugin.testing.TestOutputConsole]] on the JVM side. + */ +@JSExport +protected object ConsoleTestOutput extends TestOutput { + + /** monkey-patches console.log when class is loaded */ + private val savedConsoleLog: js.Function1[String, Unit] = { + import js.Dynamic.{ global => g } + + val console = g.console + val savedLog = console.log + + val patch = (new MonkeyPatchConsole).asInstanceOf[js.Dynamic] + + // Need to write updateDynamic explicitly here. Since 2.10.x + // chokes on this ("erroneous or inaccessible type") + console.updateDynamic("log")(patch.log.bind(patch)) + + savedLog.bind(console).asInstanceOf[js.Function1[String, Unit]] + } + + type Color = String + + val errorColor = "\u001b[31m" + val successColor = "\u001b[32m" + val infoColor = "\u001b[34m" + + private val reset = "\u001b[0m" + + def color(message: String, color: String): String = + message.split('\n').mkString(color, reset + '\n' + color, reset) + + def error(message: String, + stack: Array[StackTraceElement]): Unit = { + sendTrace(stack) + send("error", message) + } + + def error(message: String): Unit = + error(message, Array.empty) + + def failure(message: String, + stack: Array[StackTraceElement]): Unit = { + sendTrace(stack) + send("failure", message) + } + + def failure(message: String): Unit = + failure(message, Array.empty) + + def succeeded(message: String): Unit = + send("succeeded", message) + + def skipped(message: String): Unit = + send("skipped", message) + + def pending(message: String): Unit = + send("pending", message) + + def ignored(message: String): Unit = + send("ignored", message) + + def canceled(message: String): Unit = + send("canceled", message) + + val log = + new TestOutputLog { + def info(message: String): Unit = send("info", message) + def warn(message: String): Unit = send("warn", message) + def error(message: String): Unit = send("error-log", message) + } + + private def send(fct: String, msg: String) = { + val escaped = msg + .replace("\\", "\\\\") + .replace("\n", "\\n") + .replace("\r", "\\r") + savedConsoleLog(fct + "|" + escaped) + } + + private def sendTrace(stack: Array[StackTraceElement]) = for { + el <- stack + } send("trace", serializeStackElem(el)) + + private def serializeStackElem(e: StackTraceElement) = { + + val flds = List( + e.getClassName, + e.getMethodName, + e.getFileName, + e.getLineNumber.toString, + e.getColumnNumber.toString) + + flds.mkString("|") + } + + /** used to monkey-patch console (only log will be used) + * we can't write a simple lambda, because we need varargs + */ + private class MonkeyPatchConsole { + @JSExport + def log(msg: js.Any*): Unit = send("console-log", msg.mkString(" ")) + } + +} diff --git a/examples/scala-js/test-suite/run-jasmine-tests.js b/examples/scala-js/test-suite/run-jasmine-tests.js new file mode 100644 index 0000000..03bacf1 --- /dev/null +++ b/examples/scala-js/test-suite/run-jasmine-tests.js @@ -0,0 +1,18 @@ +window.onload = function() { + var framework = org.scalajs.jasminetest.JasmineTestFramework(); + framework.setTags("typedarray") + + // Load tests + scalajs.TestDetector().loadDetectedTests(); + + // Setup and run Jasmine + var jasmineEnv = jasmine.getEnv(); + var htmlReporter = new jasmine.HtmlReporter(); + jasmineEnv.addReporter(htmlReporter); + jasmineEnv.specFilter = function(spec) { + return htmlReporter.specFilter(spec); + }; + jasmineEnv.execute(); + + framework.clearTags() +}; diff --git a/examples/scala-js/test-suite/scalajs-test-suite-2.10-fastopt.html b/examples/scala-js/test-suite/scalajs-test-suite-2.10-fastopt.html new file mode 100644 index 0000000..ee06db0 --- /dev/null +++ b/examples/scala-js/test-suite/scalajs-test-suite-2.10-fastopt.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<html> +<head> + <title>Scala.js Test Suite - Jasmine HTML reporter</title> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <link rel="stylesheet" type="text/css" href="http://cdn.jsdelivr.net/jasmine/1.3.1/jasmine.css"></link> +</head> +<body> +<script type="text/javascript" src="http://cdn.jsdelivr.net/jasmine/1.3.1/jasmine.js"></script> +<script type="text/javascript" src="http://cdn.jsdelivr.net/jasmine/1.3.1/jasmine-html.js"></script> +<script type="text/javascript" src="./target/scala-2.10/scalajs-test-suite-test-fastopt.js"></script> +<script type="text/javascript" src="run-jasmine-tests.js"></script> + +</body> +</html> diff --git a/examples/scala-js/test-suite/scalajs-test-suite-2.10.html b/examples/scala-js/test-suite/scalajs-test-suite-2.10.html new file mode 100644 index 0000000..7885784 --- /dev/null +++ b/examples/scala-js/test-suite/scalajs-test-suite-2.10.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<html> +<head> + <title>Scala.js Test Suite - Jasmine HTML reporter</title> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <link rel="stylesheet" type="text/css" href="http://cdn.jsdelivr.net/jasmine/1.3.1/jasmine.css"></link> +</head> +<body> +<script type="text/javascript" src="http://cdn.jsdelivr.net/jasmine/1.3.1/jasmine.js"></script> +<script type="text/javascript" src="http://cdn.jsdelivr.net/jasmine/1.3.1/jasmine-html.js"></script> +<script type="text/javascript" src="./target/scala-2.10/scalajs-test-suite-test-opt.js"></script> +<script type="text/javascript" src="run-jasmine-tests.js"></script> + +</body> +</html> diff --git a/examples/scala-js/test-suite/scalajs-test-suite-2.11-fastopt.html b/examples/scala-js/test-suite/scalajs-test-suite-2.11-fastopt.html new file mode 100644 index 0000000..5387586 --- /dev/null +++ b/examples/scala-js/test-suite/scalajs-test-suite-2.11-fastopt.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<html> +<head> + <title>Scala.js Test Suite - Jasmine HTML reporter</title> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <link rel="stylesheet" type="text/css" href="http://cdn.jsdelivr.net/jasmine/1.3.1/jasmine.css"></link> +</head> +<body> +<script type="text/javascript" src="http://cdn.jsdelivr.net/jasmine/1.3.1/jasmine.js"></script> +<script type="text/javascript" src="http://cdn.jsdelivr.net/jasmine/1.3.1/jasmine-html.js"></script> +<script type="text/javascript" src="./target/scala-2.11/scalajs-test-suite-test-fastopt.js"></script> +<script type="text/javascript" src="run-jasmine-tests.js"></script> + +</body> +</html> diff --git a/examples/scala-js/test-suite/scalajs-test-suite-2.11.html b/examples/scala-js/test-suite/scalajs-test-suite-2.11.html new file mode 100644 index 0000000..038ac14 --- /dev/null +++ b/examples/scala-js/test-suite/scalajs-test-suite-2.11.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<html> +<head> + <title>Scala.js Test Suite - Jasmine HTML reporter</title> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <link rel="stylesheet" type="text/css" href="http://cdn.jsdelivr.net/jasmine/1.3.1/jasmine.css"></link> +</head> +<body> +<script type="text/javascript" src="http://cdn.jsdelivr.net/jasmine/1.3.1/jasmine.js"></script> +<script type="text/javascript" src="http://cdn.jsdelivr.net/jasmine/1.3.1/jasmine-html.js"></script> +<script type="text/javascript" src="./target/scala-2.11/scalajs-test-suite-test-opt.js"></script> +<script type="text/javascript" src="run-jasmine-tests.js"></script> + +</body> +</html> diff --git a/examples/scala-js/test-suite/src/test/require-sam/scala/scalajs/testsuite/jsinterop/ArraySAMTest.scala b/examples/scala-js/test-suite/src/test/require-sam/scala/scalajs/testsuite/jsinterop/ArraySAMTest.scala new file mode 100644 index 0000000..534d203 --- /dev/null +++ b/examples/scala-js/test-suite/src/test/require-sam/scala/scalajs/testsuite/jsinterop/ArraySAMTest.scala @@ -0,0 +1,32 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.jsinterop + +import scala.scalajs.js + +import org.scalajs.jasminetest.JasmineTest + +object ArraySAMTest extends JasmineTest { + + import js.JSArrayOps._ + + describe("scala.scalajs.js.Array with SAM support") { + + it("should provide jsMap") { + expect(js.Array("Sc", "ala", ".", "js").jsMap(_.length)).toEqual( + js.Array(2, 3, 1, 2)) + } + + it("should provide jsFilter") { + expect(js.Array(56, 30, -20, 33, 54, 86).jsFilter(_ % 3 != 0)).toEqual( + js.Array(56, -20, 86)) + } + + } + +} diff --git a/examples/scala-js/test-suite/src/test/resources/SourceMapTestTemplate.scala b/examples/scala-js/test-suite/src/test/resources/SourceMapTestTemplate.scala new file mode 100644 index 0000000..2b135ef --- /dev/null +++ b/examples/scala-js/test-suite/src/test/resources/SourceMapTestTemplate.scala @@ -0,0 +1,655 @@ +package scala.scalajs.testsuite.compiler + +import org.scalajs.jasminetest.{JasmineTest, JasmineTestFramework} + +/** The test counter */ +private[testsuite] object TC { + var testNum: Int = _ + + def is(x: Int): Boolean = testNum == x +} + +/** Exception to test source maps. Not a ControlThrowable, because it has + * NoStackTrace which would defeat its purpose + */ +case class TestException(lineNo: Int) extends Exception + +/** + * Template to generate source-map tests, verifying that the line numbers + * reported in the source-mapped stacktraces match up with the line number + * that the error originated from. + * + * /two-star/s in this file are replaced with a code-snippet to throw + * an exception if `testNum` is set to the /two-star/'s index. The + * exception is then caught and its stacktrace checked to see + * that it reports the line number expected (stored in the error + * message). /three-star/s in are replaced with a dangling else (to + * allow throwing in expression position). + * `0/*<testCount>*/` is replaced by the number of /n-star/s in the + * file. + */ +object SourceMapTest extends JasmineTest { + + val testCount: Int = 0/*<testCount>*/ + + if (JasmineTestFramework.hasTag("nodejs")) { + scalajs.js.Dynamic.global.require("source-map-support").install() + } + + when("source-maps"). + describe("Source Maps") { + + for (i <- 0 until testCount) { + it(s"work (test $i)") { + TC.testNum = i + + try { + run() + sys.error("No exception thrown") + } catch { + case e @ TestException(lineNo) => + val trace0 = e.getStackTrace.toList + val trace1 = trace0.dropWhile( + _.getFileName.endsWith("/scala/scalajs/runtime/StackTrace.scala")) + val trace2 = trace1.dropWhile( + _.getFileName.endsWith("/java/lang/Throwables.scala")) + + val exSte :: throwSte :: _ = trace2 + + expect(exSte.getFileName).toContain("/SourceMapTest.scala") + // line where `case class TestException is written` above + expect(exSte.getLineNumber).toBe(15) + + expect(throwSte.getFileName).toContain("/SourceMapTest.scala") + expect(throwSte.getLineNumber).toBe(lineNo) + } + } + } + } + + def get(json: JsValue, index: Int) = { + /**/ + /**/json.asInstanceOf[JsArray].value(index).value + } + + def get(json: JsValue, index: Int, fieldName: String) = { + /**/ + /**//***/json.asInstanceOf[JsArray].value(index).asInstanceOf[JsObject].value(fieldName).value + } + def run() = { + /**/ + /**/val ugly = + """ + |[ + | "JSON Test Pattern pass1", + | {"object with 1 member":["array with 1 element"]}, + | {}, + | [], + | -42, + | true, + | false, + | null, + | { + | "integer": 1234567890, + | "real": -9876.543210, + | "e": 0.123456789e-12, + | "E": 1.234567890E+34, + | "": 23456789012E66, + | "zero": 0, + | "one": 1, + | "space": " ", + | "quote": "\"", + | "backslash": "\\", + | "controls": "\b\f\n\r\t", + | "slash": "/ & \/", + | "alpha": "abcdefghijklmnopqrstuvwyz", + | "ALPHA": "ABCDEFGHIJKLMNOPQRSTUVWYZ", + | "digit": "0123456789", + | "0123456789": "digit", + | "special": "`1~!@#$%^&*()_+-={':[,]}|;.</>?", + | "hex": "\u0123\u4567\u89AB\uCDEF\uabcd\uef4A", + | "true": true, + | "false": false, + | "null": null, + | "array":[ ], + | "object":{ }, + | "address": "50 St. James Street", + | "url": "http://www.JSON.org/", + | "comment": "// /* <!-- --", + | "# -- --> */": " ", + | " s p a c e d " :[1,2 , 3 + | + |, + | + |4 , 5 , 6 ,7 ],"compact":[1,2,3,4,5,6,7], + | "jsontext": "{\"object with 1 member\":[\"array with 1 element\"]}", + | "quotes": "" \u005Cu0022 %22 0x22 034 "", + | "\/\\\"\uCAFE\uBABE\uAB98\uFCDE\ubcda\uef4A\b\f\n\r\t`1~!@#$%^&*()_+-=[]{}|;:',./<>?" + |: "A key can be any string" + | }, + | 0.5 ,98.6 + |, + |99.44 + |, + | + |1066, + |1e1, + |0.1e1, + |1e-1, + |1e00,2e+00,2e-00 + |,"rosebud"] + """.stripMargin/**/ + /**/val json = new Json()/**/ + /**/val parsed = json.read(ugly)/**/ + /**/ + /**/val unparsed = json.write(parsed)/**/ + /**/val reparsed = json.read(unparsed)/**/ + + for (json <- Seq(parsed, reparsed)){/**/ + assert(get(json, 0) == "JSON Test Pattern pass1") + /**/ + assert(get(json, 5) == true) + assert(get(json, 6) == false) + assert(get(json, 8, "real") == "-9876.543210")/**/ + /**/assert(get(json, 8, "comment") == "// /* <!-- --") + assert(get(json, 8, "jsontext") == "{\"object with 1 member\":[\"array with 1 element\"]}") + assert(get(json, 19) == "rosebud")/**/ + } + /**/ + } +} + + + +sealed trait JsValue { + def value: Any +} + +case class JsString(value: java.lang.String) extends JsValue + +case class JsObject(value: Map[String, JsValue]) extends JsValue + +case class JsArray(value: Seq[JsValue]) extends JsValue + +case class JsNumber(value: java.lang.String) extends JsValue + +sealed trait JsBoolean extends JsValue { + def value: Boolean +} + +case object JsFalse extends JsBoolean { + def value = {/**/false} +} + +case object JsTrue extends JsBoolean { + def value = {/**/true} +} + +case object JsNull extends JsValue { + def value = {null} +} + +trait Writer{ + /**/ + def writeToBuffer(v: JsValue, sb: StringBuffer): Unit = v match { + case JsString(s) => + /**/sb.append('"')/**/ + /**/var i = 0/**/ + while(i < s.length){/**/ + /**/s.charAt(i) match { + case '\\' => /**/sb.append("\\\\")/**/ + case '"' => sb.append("\\\"") + case '/' => /**/sb.append("\\/")/**/ + case '\b' => sb.append("\\b") + case '\t' => sb.append("\\t") + case '\n' => /**/sb.append("\\n")/**/ + case '\f' => sb.append("\\f") + case '\r' => sb.append("\\r") + case c => + if (c < ' '){ + val t = "000" + Integer.toHexString(c) + sb.append("\\u" + t.takeRight(4)) + }else{ + sb.append(c.toString) + } + } + i += 1 + } + /**/ + sb.append('"') + /**/ + case JsObject(kvs) => + /**/ + sb.append("{") + /**/ + var first = true + kvs.foreach(kv => { + /**/ + val (k, v) = kv + if (first) + first = false + else + sb.append(", ") + + /**/ + writeToBuffer(JsString(k), sb) + sb.append(": ") + /**/ + writeToBuffer(v, sb) + }) + sb.append("}") + + case JsArray(vs) => /**/ + sb.append("[") + if (vs.length > 0) writeToBuffer(vs(0), sb) + var i = 1 + while(i < vs.length){ + sb.append(", ") + writeToBuffer(vs(i), sb) + i += 1 + } + sb.append("]") + case JsNumber(d) => sb.append(d) + case JsFalse => sb.append("false") + case JsTrue => sb.append("true") + case JsNull => sb.append("null") + } + /**/ +} +class Writer2 extends Writer{ + /**/ + def write(v: JsValue): String = { + /**/ + val sb = new StringBuffer() + writeToBuffer(v, sb) + sb.toString + } + /**/ +} +class Json extends Writer2{ + + /** + * Self-contained JSON parser adapted from + * + * https://github.com/nestorpersist/json + */ + def read(s: String): JsValue = { + + // *** Character Kinds + /**/ + type CharKind = Int + val Letter = 0 + val Digit = 1 + val Minus = 2 + val Quote = 3 + val Colon = 4 + val Comma = 5 + val Lbra = 6 + val Rbra = 7 + val Larr = 8 + val Rarr = 9 + val Blank = 10 + val Other = 11 + val Eof = 12 + val Slash = 13 + + // *** Token Kinds + + type TokenKind = Int + val ID = 0 + val STRING = 1 + val NUMBER = 2 + val BIGNUMBER = 3 + val FLOATNUMBER = 4 + val COLON = 5 + val COMMA = 6 + val LOBJ = 7 + val ROBJ = 8 + val LARR = 9 + val RARR = 10 + val BLANK = 11 + val EOF = 12 + /**/ + // *** Character => CharKind Map *** + + val charKind = (0 to 255).toArray.map { + case c if 'a'.toInt <= c && c <= 'z'.toInt => Letter + case c if 'A'.toInt <= c && c <= 'Z'.toInt => Letter + case c if '0'.toInt <= c && c <= '9'.toInt => Digit + case '-' => /**/Minus + case ',' => /**/Comma + case '"' => /**/Quote + case ':' => /**/Colon + case '{' => /**/Lbra + case '}' => Rbra + case '[' => Larr + case ']' => Rarr + case ' ' => Blank + case '\t' => Blank + case '\n' => Blank + case '\r' => Blank + case '/' => Slash + case _ => Other + } + + // *** Character Escapes + /**/ + val escapeMap = Map[Int, String]( + '\\'.toInt -> "\\", + '/'.toInt -> "/", + '\"'.toInt -> "\"", + 'b'.toInt -> "\b", + 'f'.toInt -> "\f", + 'n'.toInt -> "\n", + 'r'.toInt -> "\r", + 't'.toInt -> "\t" + ) + // *** Import Shared Data *** + + // *** INPUT STRING *** + + // array faster than accessing string directly using charAt + //final val s1 = s.toCharArray() + val size = s.size + + // *** CHARACTERS *** + + var pos = 0 + + var ch: Int = 0 + var chKind: CharKind = 0 + var chLinePos: Int = 0 + var chCharPos: Int = 0 + + def chNext() = {/**/ + if (pos < size) {/**/ + //ch = s1(pos).toInt + /**/ch = s.charAt(pos)/**/ + /**/chKind = /***/if (ch < 255) {/**/ + /**//***/charKind(ch) + } else {/**/ + /**//***/Other + }/**/ + pos += 1 + if (ch == '\n'.toInt) { + chLinePos += 1 + chCharPos = 1 + } else {/**/ + chCharPos += 1/**/ + } + } else { + ch = -1 + pos = size + 1 + chKind = Eof + } + }/**/ + /**/ + /**/ + /**/def chError(msg: String): Nothing = { + throw new Json.Exception(msg, s, chLinePos, chCharPos) + } + + def chMark = pos - 1 + + def chSubstr(first: Int, delta: Int = 0) = { + s.substring(first, pos - 1 - delta) + } + + // *** LEXER *** + + var tokenKind = BLANK + var tokenValue = "" + var linePos = 1 + var charPos = 1 + + def getDigits() = { + while (chKind == Digit) chNext() + } + + def handleDigit() { + val first = chMark + getDigits() + val k1 = if (ch == '.'.toInt) { + chNext() + getDigits() + BIGNUMBER + } else { + NUMBER + } + val k2 = if (ch == 'E'.toInt || ch == 'e'.toInt) { + chNext() + if (ch == '+'.toInt) { + chNext() + } else if (ch == '-'.toInt) { + chNext() + } + getDigits() + FLOATNUMBER + } else { + k1 + } + /**/tokenKind = k2/**/ + /**/tokenValue = chSubstr(first)/**/ + /**/}/**/ + /**/ + def handleRaw() { + chNext() + val first = chMark + var state = 0 + do { + if (chKind == Eof) chError("EOF encountered in raw string") + state = (ch, state) match { + case ('}', _) => 1 + case ('"', 1) => 2 + case ('"', 2) => 3 + case ('"', 3) => 0 + case _ => 0 + } + + chNext() + } while (state != 3) + tokenKind = STRING + tokenValue = chSubstr(first, 3) + } + + def handle(i: Int) = { + chNext() + tokenKind = i + tokenValue = "" + } + + def tokenNext() { + do { + linePos = chLinePos + charPos = chCharPos + val kind: Int = chKind + kind match { + case Letter => + val first = chMark + while (chKind == Letter || chKind == Digit) { + chNext() + } + tokenKind = ID + tokenValue = chSubstr(first) + + case Digit => handleDigit() + case Minus => + chNext() + handleDigit() + tokenValue = "-" + tokenValue + + case Quote => + val sb = new StringBuilder(50) + chNext() + var first = chMark + while (ch != '"'.toInt && ch >= 32) { + if (ch == '\\'.toInt) { + sb.append(chSubstr(first)) + chNext() + escapeMap.get(ch) match { + case Some(s) => + sb.append(s) + chNext() + + case None => + if (ch != 'u'.toInt) chError("Illegal escape") + chNext() + var code = 0 + for (i <- 1 to 4) { + val ch1 = ch.toChar.toString + val i = "0123456789abcdef".indexOf(ch1.toLowerCase) + if (i == -1) chError("Illegal hex character") + code = code * 16 + i + chNext() + } + sb.append(code.toChar.toString) + } + first = chMark + } else { + chNext() + } + } + if (ch != '"') chError("Unexpected string character: " + ch.toChar) + + sb.append(chSubstr(first)) + + tokenKind = STRING + + tokenValue = sb.toString() + chNext() + if (tokenValue.length() == 0 && ch == '{') { + handleRaw() + } + + case Colon => handle(COLON)/**/ + case Comma => handle(COMMA)/**/ + case Lbra => handle(LOBJ)/**/ + case Rbra => handle(ROBJ)/**/ + case Larr => handle(LARR)/**/ + case Rarr => handle(RARR)/**/ + case Blank => + do chNext() while (chKind == Blank) + tokenKind = BLANK + tokenValue = "" + + case Other => chError("Unexpected character: " + ch.toChar + " " + ch) + case Eof => + chNext() + tokenKind = EOF + tokenValue = "" + + case Slash => + if (chKind != Slash) chError("Expecting Slash") + do chNext() while (ch != '\n' && chKind != Eof) + tokenKind = BLANK + tokenValue = "" + + } + } while (tokenKind == BLANK) + } + /**/ + def tokenError(msg: String): Nothing = { + throw new Json.Exception(msg, s, linePos, charPos) + } + /**/ + // *** PARSER *** + + def handleEof() = tokenError("Unexpected eof") + def handleUnexpected(i: String) = tokenError(s"Unexpected input: [$i]") + + def handleArray(): JsArray = { + tokenNext() + var result = List.empty[JsValue] + while (tokenKind != RARR) {/**/ + result = getJson() :: result + /**/tokenKind match{ + case COMMA => /**/tokenNext() + case RARR => // do nothing + case _ => tokenError("Expecting , or ]") + } + } + tokenNext() + JsArray(result.reverse) + } + + def handleObject(): JsObject = { + tokenNext() + var result = List.empty[(String, JsValue)] + /**/ + while (tokenKind != ROBJ) { + if (tokenKind != STRING && tokenKind != ID) tokenError("Expecting string or name") + val name = tokenValue + tokenNext() + if (tokenKind != COLON) tokenError("Expecting :") + tokenNext() + result = (name -> getJson()) :: result + tokenKind match{ + case COMMA => tokenNext() + case ROBJ => // do nothing + case _ => tokenError("Expecting , or }") + } + } + tokenNext() + JsObject(result.toMap) + } + def handleNumber(name: String, f: String => Unit) = { + try { + f(tokenValue) + } catch { + case _: Throwable => tokenError("Bad " + name) + } + val old = tokenValue + tokenNext() + + JsNumber(old) + } + def getJson(): JsValue = { + val kind: Int = tokenKind + val result: JsValue = kind match { + case ID => + val result: JsValue = tokenValue match { + case "true" => JsTrue + case "false" => JsFalse + case "null" => JsNull + case _ => tokenError("Not true, false, or null") + } + + tokenNext() + result + + case STRING => + val result = tokenValue + tokenNext() + JsString(result) + + case NUMBER => handleNumber("NUMBER", _.toLong) + case BIGNUMBER => handleNumber("BIGNUMBER", _.toDouble) + case FLOATNUMBER => handleNumber("FLOATNUMBER", _.toDouble) + case COLON => handleUnexpected(":") + case COMMA => handleUnexpected(",") + case LOBJ => handleObject() + case ROBJ => handleUnexpected("}") + case LARR => handleArray() + case RARR => handleUnexpected("]") + case EOF => handleEof() + } + result + } + def parse(): JsValue = { + chNext() + tokenNext() + val result = getJson + if (tokenKind != EOF) tokenError("Excess input") + result + } + parse() + } +} + +object Json { + class Exception(val msg: String, + val input: String, + val line: Int, + val char: Int) + extends scala.Exception(s"JsonParse Error: $msg line $line [$char] in $input") +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/compiler/BooleanTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/compiler/BooleanTest.scala new file mode 100644 index 0000000..8343244 --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/compiler/BooleanTest.scala @@ -0,0 +1,41 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.compiler + +import org.scalajs.jasminetest.JasmineTest +import scala.scalajs.js + +object BooleanTest extends JasmineTest { + + describe("Boolean primitives") { + + it("&, | and ^ on booleans should return booleans") { + expect(js.typeOf(true & false)).toEqual("boolean") + expect(js.typeOf(true | false)).toEqual("boolean") + expect(js.typeOf(true ^ false)).toEqual("boolean") + } + + it("&, | and ^ on booleans should return correct results") { + expect(false & false).toBeFalsy + expect(false & true).toBeFalsy + expect(true & false).toBeFalsy + expect(true & true).toBeTruthy + + expect(false | false).toBeFalsy + expect(true | false).toBeTruthy + expect(false | true).toBeTruthy + expect(true | true).toBeTruthy + + expect(false ^ false).toBeFalsy + expect(true ^ false).toBeTruthy + expect(false ^ true).toBeTruthy + expect(true ^ true).toBeFalsy + } + + } +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/compiler/ByteTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/compiler/ByteTest.scala new file mode 100644 index 0000000..9f48993 --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/compiler/ByteTest.scala @@ -0,0 +1,40 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.compiler + +import org.scalajs.jasminetest.JasmineTest +import scala.scalajs.js + +object ByteTest extends JasmineTest { + + describe("Byte primitives") { + + it("should always be in their range") { + def test(x: Int, y: Byte): Unit = + expect(x.toByte).toEqual(y) + + test(0, 0) + test(127, 127) + test(128, -128) + test(-128, -128) + test(-500, 12) + test(-90000, 112) + test(123456789, 21) + test(-40000, -64) + test(65536, 0) + test(32768, 0) + + def testC(x: Char, y: Byte): Unit = + expect(x.toByte).toEqual(y) + + testC(-1.toChar, -1) + testC(200.toChar, -56) + } + + } +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/compiler/CharTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/compiler/CharTest.scala new file mode 100644 index 0000000..edc2660 --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/compiler/CharTest.scala @@ -0,0 +1,40 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.compiler + +import org.scalajs.jasminetest.JasmineTest +import scala.scalajs.js.Any.fromInt + +object CharTest extends JasmineTest { + + describe("Char primitives") { + + it("should always be positive (when coerced)") { + expect(-3.toByte.toChar.toInt).toEqual(65533) + expect(-100.toShort.toChar.toInt).toEqual(65436) + expect(-66000.toChar.toInt).toEqual(65072) + expect(-4567L.toChar.toInt).toEqual(60969) + expect(-5.3f.toChar.toInt).toEqual(65531) + expect(-7.9.toChar.toInt).toEqual(65529) + } + + it("should overflow (when coerced)") { + expect(347876543.toChar.toInt).toEqual(11455) + expect(34234567876543L.toChar.toInt).toEqual(57279) + } + + it("should overflow with *") { + def test(a: Char, b: Char, expected: Int): Unit = + expect(a * b).toEqual(expected) + + // note: expected values are constant-folded by the compiler on the JVM + test(Char.MaxValue, Char.MaxValue, Char.MaxValue * Char.MaxValue) + } + + } +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/compiler/FloatTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/compiler/FloatTest.scala new file mode 100644 index 0000000..5eda04a --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/compiler/FloatTest.scala @@ -0,0 +1,73 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.compiler + +import org.scalajs.jasminetest.JasmineTest + +object FloatTest extends JasmineTest { + + def froundNotInlined(x: Double): Float = { + val y = x // so you don't inline me + y.toFloat + } + + when("strict-floats"). + describe("Strict floats") { + + it("fround for special values") { + expect(froundNotInlined(Double.NaN).isNaN).toBeTruthy + expect(1 / froundNotInlined(0.0).toDouble).toBe(Double.PositiveInfinity) + expect(1 / froundNotInlined(-0.0).toDouble).toBe(Double.NegativeInfinity) + expect(froundNotInlined(Double.PositiveInfinity)).toBe(Float.PositiveInfinity) + expect(froundNotInlined(Double.NegativeInfinity)).toBe(Float.NegativeInfinity) + } + + it("fround overflows") { + expect(froundNotInlined(1e200)).toBe(Double.PositiveInfinity) + expect(froundNotInlined(-1e200)).toBe(Double.NegativeInfinity) + } + + it("fround underflows") { + expect(1 / froundNotInlined(1e-300).toDouble).toBe(Double.PositiveInfinity) + expect(1 / froundNotInlined(-1e-300).toDouble).toBe(Double.NegativeInfinity) + } + + it("fround normal cases") { + def test(input: Double, expected: Double): Unit = + expect(input.toFloat.toDouble).toBe(expected) + + // From MDN documentation + test(0.0, 0.0) + test(1.0, 1.0) + test(1.5, 1.5) + test(1.337, 1.3370000123977661) + test(-4.3, -4.300000190734863) + + // Some bounds + test(Float.MinPositiveValue.toDouble, Float.MinPositiveValue.toDouble) + test(Float.MaxValue.toDouble, Float.MaxValue.toDouble) + test(Float.MinValue.toDouble, Float.MinValue.toDouble) + + // Randomly generated Doubles + test(2.705609035558863E20, 2.7056090763400262E20) + test(-1.447710531503027E15, -1.447710532042752E15) + test(-5.1970024617732836E13, -5.1970022834176E13) + test(1.627661085098256E31, 1.6276610930768024E31) + test(-3.7731947682593834E-32, -3.7731946313230934E-32) + test(34.48229849163326, 34.4822998046875) + test(26.62034396181652, 26.620344161987305) + test(8.198435190113375E-24, 8.198434961596576E-24) + test(-6.079928908440255E-23, -6.079928963558556E-23) + test(3.3756949828462674E-13, 3.37569490589662E-13) + test(-1.2599049874324274E33, -1.2599049641449257E33) + test(6.08574575776438E-10, 6.085745796191588E-10) + test(1.973497969450596E-21, 1.973498047135062E-21) + } + + } +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/compiler/InstanceTestsHijackedBoxedClassesTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/compiler/InstanceTestsHijackedBoxedClassesTest.scala new file mode 100644 index 0000000..1379b80 --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/compiler/InstanceTestsHijackedBoxedClassesTest.scala @@ -0,0 +1,98 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.compiler + +import org.scalajs.jasminetest.JasmineTest +import scala.scalajs.js + +object InstanceTestsHijackedBoxedClassesTest extends JasmineTest { + + describe("Instance tests for hijacked boxed classes") { + + it("should support isInstanceOf (positive)") { + expect((() : Any).isInstanceOf[Unit] ).toBeTruthy + expect((false : Any).isInstanceOf[Boolean]).toBeTruthy + expect(('a' : Any).isInstanceOf[Char] ).toBeTruthy + expect((65.toByte : Any).isInstanceOf[Byte] ).toBeTruthy + expect((654.toShort: Any).isInstanceOf[Short] ).toBeTruthy + expect((-4321 : Any).isInstanceOf[Int] ).toBeTruthy + expect((684321L : Any).isInstanceOf[Long] ).toBeTruthy + expect((3.14f : Any).isInstanceOf[Float] ).toBeTruthy + expect((3.14 : Any).isInstanceOf[Double] ).toBeTruthy + } + + it("should support isInstanceOf (negative)") { + expect((12345: Any).isInstanceOf[Unit] ).toBeFalsy + expect((12345: Any).isInstanceOf[Boolean]).toBeFalsy + expect((12345: Any).isInstanceOf[Char] ).toBeFalsy + expect(('a' : Any).isInstanceOf[Byte] ).toBeFalsy + expect(('b' : Any).isInstanceOf[Short] ).toBeFalsy + expect(('c' : Any).isInstanceOf[Int] ).toBeFalsy + expect(('d' : Any).isInstanceOf[Long] ).toBeFalsy + expect(('f' : Any).isInstanceOf[Float] ).toBeFalsy + expect(('g' : Any).isInstanceOf[Double] ).toBeFalsy + } + + it("should support asInstanceOf (positive)") { + def swallow(x: Any): Unit = () + swallow((() : Any).asInstanceOf[Unit] ) + swallow((false : Any).asInstanceOf[Boolean]) + swallow(('a' : Any).asInstanceOf[Char] ) + swallow((65.toByte : Any).asInstanceOf[Byte] ) + swallow((654.toShort: Any).asInstanceOf[Short] ) + swallow((-4321 : Any).asInstanceOf[Int] ) + swallow((684321L : Any).asInstanceOf[Long] ) + swallow((3.14f : Any).asInstanceOf[Float] ) + swallow((3.14 : Any).asInstanceOf[Double] ) + } + + when("compliant-asinstanceof"). + it("should support asInstanceOf (negative)") { + expect(() => (12345: Any).asInstanceOf[Unit] ).toThrow + expect(() => (12345: Any).asInstanceOf[Boolean]).toThrow + expect(() => (12345: Any).asInstanceOf[Char] ).toThrow + expect(() => ('a' : Any).asInstanceOf[Byte] ).toThrow + expect(() => ('b' : Any).asInstanceOf[Short] ).toThrow + expect(() => ('c' : Any).asInstanceOf[Int] ).toThrow + expect(() => ('d' : Any).asInstanceOf[Long] ).toThrow + expect(() => ('f' : Any).asInstanceOf[Float] ).toThrow + expect(() => ('g' : Any).asInstanceOf[Double] ).toThrow + } + + it("should support isInstanceOf via java.lang.Class (positive)") { + def test(x: Any, clazz: Class[_]): Unit = + expect(clazz.isInstance(x)).toBeTruthy + + test(() , classOf[scala.runtime.BoxedUnit]) + test(false , classOf[java.lang.Boolean]) + test('a' , classOf[java.lang.Character]) + test(65.toByte , classOf[java.lang.Byte]) + test(654.toShort, classOf[java.lang.Short]) + test(-4321 , classOf[java.lang.Integer]) + test(684321L , classOf[java.lang.Long]) + test(3.14f , classOf[java.lang.Float]) + test(3.14 , classOf[java.lang.Double]) + } + + it("should support isInstanceOf via java.lang.Class (negative)") { + def test(x: Any, clazz: Class[_]): Unit = + expect(clazz.isInstance(x)).toBeFalsy + + test(12345, classOf[scala.runtime.BoxedUnit]) + test(12345, classOf[java.lang.Boolean]) + test(12345, classOf[java.lang.Character]) + test('a' , classOf[java.lang.Byte]) + test('b' , classOf[java.lang.Short]) + test('c' , classOf[java.lang.Integer]) + test('d' , classOf[java.lang.Long]) + test('e' , classOf[java.lang.Float]) + test('f' , classOf[java.lang.Double]) + } + + } +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/compiler/IntTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/compiler/IntTest.scala new file mode 100644 index 0000000..60f8773 --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/compiler/IntTest.scala @@ -0,0 +1,206 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.compiler + +import org.scalajs.jasminetest.JasmineTest + +object IntTest extends JasmineTest { + + /* General note on the way these tests are written: + * We leverage the constant folding applied by the Scala compiler to write + * sound tests. We always perform the same operation, on the same operands, + * once in a way constant folding understands, and once in a way it doesn't. + * Since constant folding is performed on the JVM, we know it has the right + * semantics. + */ + + // final val without type ascription to make sure these are constant-folded + final val MinVal = Int.MinValue + final val MaxVal = Int.MaxValue + final val AlmostMinVal = Int.MinValue + 43 + final val AlmostMaxVal = Int.MaxValue - 36 + + describe("Int primitives") { + + it("should support unary -") { + def test(a: Int, expected: Int): Unit = + expect(-a).toEqual(expected) + + test(56, -56) + test(0, 0) + test(-36, 36) + + test(MaxVal, -MaxVal) + test(MinVal, -MinVal) + test(-MaxVal, MaxVal) + test(AlmostMinVal, -AlmostMinVal) + test(AlmostMaxVal, -AlmostMaxVal) + } + + it("should support +") { + def test(a: Int, b: Int, expected: Int): Unit = + expect(a + b).toEqual(expected) + + test(56, 654, 56 + 654) + test(0, 25, 0 + 25) + test(-36, 13, -36 + 13) + + test(MaxVal, 1, MaxVal + 1) + test(MinVal, -1, MinVal - 1) + test(MaxVal, MinVal, MaxVal + MinVal) + test(AlmostMinVal, -100, AlmostMinVal - 100) + test(AlmostMaxVal, 123, AlmostMaxVal + 123) + } + + it("should support -") { + def test(a: Int, b: Int, expected: Int): Unit = + expect(a - b).toEqual(expected) + + test(56, 654, 56 - 654) + test(0, 25, 0 - 25) + test(-36, 13, -36 - 13) + + test(MaxVal, -1, MaxVal + 1) + test(MinVal, 1, MinVal - 1) + test(MaxVal, MinVal, MaxVal - MinVal) + test(AlmostMinVal, 100, AlmostMinVal - 100) + test(AlmostMaxVal, -123, AlmostMaxVal + 123) + } + + it("should support *") { + def test(a: Int, b: Int, expected: Int): Unit = + expect(a * b).toEqual(expected) + + test(56, 654, 56 * 654) + test(0, 25, 0 * 25) + test(-36, 13, -36 * 13) + test(-5, -6, -5 * -6) + + test(MinVal, 1, MinVal * 1) + test(MinVal, -1, MinVal * -1) + test(MaxVal, 1, MaxVal * 1) + test(MaxVal, -1, MaxVal * -1) + + test(MaxVal, MinVal, MaxVal * MinVal) + test(MaxVal, MaxVal, MaxVal * MaxVal) + test(MinVal, MaxVal, MinVal * MaxVal) + test(MinVal, MinVal, MinVal * MinVal) + + test(AlmostMaxVal, 2, AlmostMaxVal * 2) + test(AlmostMaxVal, 5, AlmostMaxVal * 5) + test(AlmostMaxVal, -7, AlmostMaxVal * -7) + test(AlmostMaxVal, -14, AlmostMaxVal * -14) + test(AlmostMinVal, 100, AlmostMinVal * 100) + test(AlmostMaxVal, -123, AlmostMaxVal * -123) + } + + it("should support /") { + def test(a: Int, b: Int, expected: Int): Unit = + expect(a / b).toEqual(expected) + + test(654, 56, 654 / 56) + test(0, 25, 0 / 25) + test(-36, 13, -36 / 13) + test(-55, -6, -55 / -6) + + test(MinVal, 1, MinVal / 1) + test(MinVal, -1, MinVal / -1) + test(MaxVal, 1, MaxVal / 1) + test(MaxVal, -1, MaxVal / -1) + + test(MaxVal, MinVal, MaxVal / MinVal) + test(MaxVal, MaxVal, MaxVal / MaxVal) + test(MinVal, MaxVal, MinVal / MaxVal) + test(MinVal, MinVal, MinVal / MinVal) + + test(AlmostMaxVal, 2, AlmostMaxVal / 2) + test(AlmostMaxVal, 5, AlmostMaxVal / 5) + test(AlmostMaxVal, -7, AlmostMaxVal / -7) + test(AlmostMaxVal, -14, AlmostMaxVal / -14) + test(AlmostMinVal, 100, AlmostMinVal / 100) + test(AlmostMaxVal, -123, AlmostMaxVal / -123) + } + + unless("phantomjs"). // see #593 + it("should support %") { + def test(a: Int, b: Int, expected: Int): Unit = + expect(a % b).toEqual(expected) + + test(654, 56, 654 % 56) + test(0, 25, 0 % 25) + test(-36, 13, -36 % 13) + test(-55, -6, -55 % -6) + + test(MinVal, 1, MinVal % 1) + test(MinVal, -1, MinVal % -1) + test(MaxVal, 1, MaxVal % 1) + test(MaxVal, -1, MaxVal % -1) + + test(MaxVal, MinVal, MaxVal % MinVal) + test(MaxVal, MaxVal, MaxVal % MaxVal) + test(MinVal, MaxVal, MinVal % MaxVal) + test(MinVal, MinVal, MinVal % MinVal) + + test(AlmostMaxVal, 2, AlmostMaxVal % 2) + test(AlmostMaxVal, 5, AlmostMaxVal % 5) + test(AlmostMaxVal, -7, AlmostMaxVal % -7) + test(AlmostMaxVal, -14, AlmostMaxVal % -14) + test(AlmostMinVal, 100, AlmostMinVal % 100) + test(AlmostMaxVal, -123, AlmostMaxVal % -123) + } + + it("should support <<") { + def test(a: Int, b: Int, expected: Int): Unit = + expect(a << b).toEqual(expected) + + test(0, 5, 0 << 5) + test(1, 5, 1 << 5) + test(13, 4, 13 << 4) + test(-35, 5, -35 << 5) + test(345, 0, 345 << 0) + + test(MinVal, 0, MinVal << 0) + test(MaxVal, 0, MaxVal << 0) + test(MinVal, 1, MinVal << 1) + test(MaxVal, 1, MaxVal << 1) + } + + it("should support >>") { + def test(a: Int, b: Int, expected: Int): Unit = + expect(a >> b).toEqual(expected) + + test(0, 5, 0 >> 5) + test(32, 5, 32 >> 5) + test(31, 4, 31 >> 4) + test(-355, 5, -355 >> 5) + test(345, 0, 345 >> 0) + + test(MinVal, 0, MinVal >> 0) + test(MaxVal, 0, MaxVal >> 0) + test(MinVal, 1, MinVal >> 1) + test(MaxVal, 1, MaxVal >> 1) + } + + it("should support >>>") { + def test(a: Int, b: Int, expected: Int): Unit = + expect(a >>> b).toEqual(expected) + + test(0, 5, 0 >>> 5) + test(32, 5, 32 >>> 5) + test(31, 4, 31 >>> 4) + test(-355, 5, -355 >>> 5) + test(345, 0, 345 >>> 0) + + test(MinVal, 0, MinVal >>> 0) + test(MaxVal, 0, MaxVal >>> 0) + test(MinVal, 1, MinVal >>> 1) + test(MaxVal, 1, MaxVal >>> 1) + } + + } +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/compiler/InteroperabilityTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/compiler/InteroperabilityTest.scala new file mode 100644 index 0000000..594345a --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/compiler/InteroperabilityTest.scala @@ -0,0 +1,528 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.compiler + +import scala.scalajs.js +import org.scalajs.jasminetest.{JasmineTest, JasmineTestFramework} +import scala.scalajs.js.annotation._ + +/* + * Based on examples in: + * http://lampwww.epfl.ch/~doeraene/scala-js/doc/js-interoperability.html + */ +object InteroperabilityTest extends JasmineTest { + + describe("JavaScript interoperability") { + + it("should support backquotes to escape Scala fields") { + val obj = js.eval(""" + var interoperabilityTestFieldEscape = { + def: 0, + val: function(x) { if (x) this.def = x; return this.def; } + }; + interoperabilityTestFieldEscape; + """).asInstanceOf[InteroperabilityTestFieldEscape] + + obj.`def` = 7357 + expect(obj.`def`).toEqual(7357) + expect(obj.`val`()).toEqual(7357) + expect(obj.`val`(42)).toEqual(42) + } + + it("should support @JSName to specify the JavaScript name for fields") { + val obj = js.eval(""" + var interoperabilityTestJSName = { + def: 42, + val: function(x) { if (x) this.def = x; return this.def; } + }; + interoperabilityTestJSName; + """).asInstanceOf[InteroperabilityTestJSName] + + expect(obj.value()).toEqual(42) + expect(obj.value(7357)).toEqual(7357) + } + + it("should translate explicit getter and setter names to field access") { + val obj = js.eval(""" + var interoperabilityTestProperty = { a: 1 }; + interoperabilityTestProperty; + """).asInstanceOf[InteroperabilityTestProperty] + + expect(obj.a).toEqual(1) + obj.a = 100 + expect(obj.a).toEqual(100) + } + + it("should support @JSName together with field access") { + val obj = js.eval(""" + var interoperabilityTestProperty = { b: 1 }; + interoperabilityTestProperty; + """).asInstanceOf[InteroperabilityTestPropertyNamed] + + expect(obj.a).toEqual(1) + obj.a = 100 + expect(obj.a).toEqual(100) + expect(obj.b).toEqual(100) + } + + it("should support @JSBracketAccess to specify access using []-subscription") { + val obj = js.eval(""" + var interoperabilityTestJSBracketAccess = [ 0, 1, 7357 ]; + interoperabilityTestJSBracketAccess; + """).asInstanceOf[InteroperabilityTestJSBracketAccess] + + expect(obj(2)).toEqual(7357) + obj(2) = 42 + expect(obj(2)).toEqual(42) + } + + it("should allow instanciation of JS classes inheriting from js.Object") { + js.eval(""" + var InteroperabilityTestInherit = { + Pattern: function(x) { + this.field = 42; + this.method = function() { + return '42'; + }; + this.getConstructorParam = function() { + return x; + }; + } + } + """) + + val obj = new InteroperabilityTestPattern("Scala.js") + expect(obj.field).toEqual(42) + expect(obj.method).toEqual("42") + expect(obj.getConstructorParam).toEqual("Scala.js") + } + + it("should acces top-level JS objects via Scala objects inheriting from js.Object") { + js.eval(""" + var InteroperabilityTestTopLevelObject = function(value) { + return { + value: value, + valueAsInt: function() { + return parseInt(value); + } + }; + } + """) + + // Use alias for convenience: see end of file for definition + val TopLevel = InteroperabilityTestTopLevel + + val obj = TopLevel("7357") + expect(obj.value).toEqual("7357") + expect(obj.valueAsInt).toEqual(7357) + } + + it("should allow to call JS methods with variadic parameters") { + val obj = js.eval(""" + var obj = { + foo: function() { + var args = new Array(arguments.length); + for (var i = 0; i < arguments.length; i++) + args[i] = arguments[i]; + return args; + } + }; + obj; + """) + + val elems = Seq[js.Any]("plop", 42, 51) + + val dyn = obj.asInstanceOf[js.Dynamic] + expect(dyn.foo()).toEqual(js.Array()) + expect(dyn.foo(3, 6)).toEqual(js.Array(3, 6)) + expect(dyn.foo("hello", false)).toEqual(js.Array("hello", false)) + expect(dyn.applyDynamic("foo")(elems: _*)).toEqual(js.Array("plop", 42, 51)) + + val stat = obj.asInstanceOf[InteroperabilityTestVariadicMethod] + expect(stat.foo()).toEqual(js.Array()) + expect(stat.foo(3, 6)).toEqual(js.Array(3, 6)) + expect(stat.foo("hello", false)).toEqual(js.Array("hello", false)) + expect(stat.foo(elems: _*)).toEqual(js.Array("plop", 42, 51)) + } + + it("should allow to call JS constructors with variadic parameters") { + import js.Dynamic.{newInstance => jsnew} + + js.eval(""" + var InteroperabilityTestVariadicCtor = function() { + var args = new Array(arguments.length); + for (var i = 0; i < arguments.length; i++) + args[i] = arguments[i]; + this.args = args; + }; + """) + + val elems = Seq[js.Any]("plop", 42, 51) + + val ctor = js.Dynamic.global.InteroperabilityTestVariadicCtor + expect(jsnew(ctor)().args).toEqual(js.Array()) + expect(jsnew(ctor)(3, 6).args).toEqual(js.Array(3, 6)) + expect(jsnew(ctor)("hello", false).args).toEqual(js.Array("hello", false)) + expect(jsnew(ctor)(elems: _*).args).toEqual(js.Array("plop", 42, 51)) + + import scala.scalajs.testsuite.compiler.{InteroperabilityTestVariadicCtor => C} + expect(new C().args).toEqual(js.Array()) + expect(new C(3, 6).args).toEqual(js.Array(3, 6)) + expect(new C("hello", false).args).toEqual(js.Array("hello", false)) + expect(new C(elems: _*).args).toEqual(js.Array("plop", 42, 51)) + } + + it("should acces top-level JS objects via Scala object inheriting from js.GlobalScope") { + js.eval(""" + var interoperabilityTestGlobalScopeValue = "7357"; + var interoperabilityTestGlobalScopeValueAsInt = function() { + return parseInt(interoperabilityTestGlobalScopeValue); + }; + """) + + // Use alias for convenience: see end of file for definition + val Global = InteroperabilityTestGlobalScope + + expect(Global.interoperabilityTestGlobalScopeValue).toEqual("7357") + expect(Global.interoperabilityTestGlobalScopeValueAsInt).toEqual(7357) + + Global.interoperabilityTestGlobalScopeValue = "42" + expect(Global.interoperabilityTestGlobalScopeValueAsInt).toEqual(42) + } + + it("should protect receiver of raw JS apply if it's a select - #804") { + val rawReceiver = js.eval(""" + var interoperabilityTestRawReceiver = { + member: 0xbad, + check: function(raw) { return this.member ? this.member : raw; } + }; + interoperabilityTestRawReceiver; + """).asInstanceOf[InteroperabilityTestRawReceiver] + + expect(rawReceiver.check(7357)).toEqual(7357) + + val check = rawReceiver.check + expect(check(0x600d)).toEqual(0x600d) + + class InScalaSelect(check: js.Function1[Int, Int]) { + @JSExport + val member: Int = 0xbad2 + def test(): Unit = expect(check(5894)).toEqual(5894) + } + new InScalaSelect(check).test() + } + + it("should properly handle default parameters") { + val obj = js.eval(""" + var interoperabilityTestDefaultParam = { + fun: function() { return arguments; } + }; + interoperabilityTestDefaultParam; + """).asInstanceOf[InteroperabilityTestDefaultParam] + + // Helpers + import js.Dynamic.{literal => args} + val undef = js.undefined + + expect(obj.simple(1)).toEqual(args(`0` = 1)) + expect(obj.simple(1,5)).toEqual(args(`0` = 1, `1` = 5)) + expect(obj.named(y = 5)).toEqual(args(`0` = undef, `1` = 5)) + expect(obj.named(x = 5)).toEqual(args(`0` = 5)) + expect(obj.multi()(1,2,3,4)()).toEqual(args( + `0` = undef, + `1` = 1, + `2` = 2, + `3` = 3, + `4` = 4)) + expect(obj.multi(2)()(5)).toEqual(args( + `0` = 2, + `1` = 5)) + } + + it("should properly handle default parameters for constructors - #791") { + js.eval(""" + var InteroperabilityTestCtor = function(x,y) { + this.values = Array(x || 6, y || 8) + } + """); + + expect(new InteroperabilityTestCtor().values).toEqual(js.Array(6,8)) + expect(new InteroperabilityTestCtor(y = 7).values).toEqual(js.Array(6,7)) + expect(new InteroperabilityTestCtor(3).values).toEqual(js.Array(3,8)) + expect(new InteroperabilityTestCtor(10, 2).values).toEqual(js.Array(10,2)) + } + + it("should generate exports for methods inherited from traits - #178") { + import js.annotation.JSExport + + trait Foo { + @JSExport + def theValue = 1 + } + class Bar extends Foo + + val x = (new Bar).asInstanceOf[js.Dynamic] + + // Call the export by using js.Dynamic + expect(x.theValue).toEqual(1) + } + + it("should allow constructor params that are vals/vars in facades - #1277") { + js.eval(""" + var InteroparabilityCtorInlineValue = function(x,y) { + this.x = x; + this.y = y; + } + """) + + val obj = new InteroparabilityCtorInlineValue(10, -1) + + expect(obj.x).toEqual(10) + expect(obj.y).toEqual(-1) + + obj.y = 100 + + expect(obj.x).toEqual(10) + expect(obj.y).toEqual(100) + } + + it("should unbox Chars received from calling a JS interop method") { + val obj = js.eval(""" + var obj = { + get: function() { return JSUtils().stringToChar('e'); } + }; + obj; + """).asInstanceOf[InteroperabilityTestCharResult] + + expect(obj.get().toInt).toEqual('e'.toInt) + } + + it("should box Chars given to a JS interop method") { + val obj = js.eval(""" + var obj = { + twice: function(c) { c = JSUtils().charToString(c); return c+c; } + }; + obj; + """).asInstanceOf[InteroperabilityTestCharParam] + + expect(obj.twice('x')).toEqual("xx") + } + + it("should unbox value classes received from calling a JS interop method") { + val obj = js.eval(""" + var obj = { + test: function(vc) { return vc; } + }; + obj; + """).asInstanceOf[InteroperabilityTestValueClassResult] + + val r = obj.test(new SomeValueClass(5)) + expect(r.i).toEqual(5) + } + + it("should box value classes given to a JS interop method") { + val obj = js.eval(""" + var obj = { + stringOf: function(vc) { return vc.toString(); } + }; + obj; + """).asInstanceOf[InteroperabilityTestValueClassParam] + + val vc = new SomeValueClass(7) + expect(obj.stringOf(vc)).toEqual("SomeValueClass(7)") + } + + it("should not unbox values received from JS method in statement position") { + /* To test this, we verify that a purposefully ill-typed facade does not + * throw a ClassCastException when called in statement position. + */ + val obj = js.eval(""" + var obj = { + test: function() { return 4; } // typed as String in the trait + }; + obj; + """).asInstanceOf[InteroperabilityTestNoUnboxResultInStatement] + obj.test() // in statement position, should not throw + if (JasmineTestFramework.hasTag("compliant-asinstanceof")) + expect(() => obj.test()).toThrow // in expression position, should throw + } + + when("compliant-asinstanceof"). + it("should protect conversions from JS types to Scala types") { + class Foo + val foo: Any = new Foo + + val invalidNumber: js.prim.Number = foo.asInstanceOf[js.prim.Number] + val nullNumber: js.prim.Number = null + expect(() => invalidNumber: Double).toThrow + expect(nullNumber: Double).toEqual(0) + + val invalidBoolean: js.prim.Boolean = foo.asInstanceOf[js.prim.Boolean] + val nullBoolean: js.prim.Boolean = null + expect(() => invalidBoolean: Boolean).toThrow + expect(nullBoolean: Boolean).toEqual(false) + + val invalidString: js.prim.String = foo.asInstanceOf[js.prim.String] + val nullString: js.prim.String = null + expect(() => invalidString: String).toThrow + expect(nullString: String).toBeNull + } + + when("compliant-asinstanceof"). + it("should asInstanceOf values received from calling a JS interop method") { + val obj = js.eval(""" + var obj = { + testChar: function() { return 5; }, + testInt: function() { return 6.4; }, + testShort: function() { return 60000; }, + testDouble: function() { return JSUtils().stringToChar('e'); }, + testString: function() { return {}; }, + testValueClass: function() { return "hello"; }, + testNormalClass: function() { return 45; }, + testAny: function() { return {}; } + }; + obj; + """).asInstanceOf[InteroperabilityTestAsInstanceOfResult] + + expect(() => obj.testChar()).toThrow + expect(() => obj.testInt()).toThrow + expect(() => obj.testShort()).toThrow + expect(() => obj.testDouble()).toThrow + expect(() => obj.testString()).toThrow + expect(() => obj.testValueClass()).toThrow + expect(() => obj.testNormalClass()).toThrow + expect(() => obj.testAny()).not.toThrow + } + + } + + trait InteroperabilityTestFieldEscape extends js.Object { + var `def`: Int = js.native + def `val`(): Int = js.native + def `val`(n: Int): Int = js.native + } + + trait InteroperabilityTestJSName extends js.Object { + @JSName("val") + def value(): Int = js.native + @JSName("val") + def value(n: Int): Int = js.native + } + + trait InteroperabilityTestProperty extends js.Object { + def a_=(x: Int): Unit = js.native + def a: Int = js.native + } + + trait InteroperabilityTestPropertyNamed extends js.Object { + @JSName("b") + def a_=(x: Int): Unit = js.native + @JSName("b") + def a: Int = js.native + def b: Int = js.native + } + + trait InteroperabilityTestJSBracketAccess extends js.Object { + @JSBracketAccess + def apply(index: Int): Int = js.native + @JSBracketAccess + def update(index: Int, v: Int): Unit = js.native + } + + trait InteroperabilityTestRawReceiver extends js.Object { + val check: js.Function1[Int, Int] = js.native + } + + /** Trait with different method signatures, all forwarded to the same + * JS raw function that returns the argument list for inspection + */ + trait InteroperabilityTestDefaultParam extends js.Object { + @JSName("fun") + def simple(x: Int, y: Int = 5): js.Any = js.native + @JSName("fun") + def named(x: Int = 1, y: Int = 1, z: Int = 1): js.Any = js.native + @JSName("fun") + def multi(x: Int = 1)(ys: Int*)(z: Int = 1): js.Any = js.native + } + + trait InteroperabilityTestCharResult extends js.Object { + def get(): Char = js.native + } + + trait InteroperabilityTestCharParam extends js.Object { + def twice(c: Char): String = js.native + } + + trait InteroperabilityTestValueClassResult extends js.Object { + def test(vc: Any): SomeValueClass = js.native + } + + trait InteroperabilityTestValueClassParam extends js.Object { + def stringOf(vc: SomeValueClass): String = js.native + } + + trait InteroperabilityTestNoUnboxResultInStatement extends js.Object { + def test(): String = js.native + } + + trait InteroperabilityTestAsInstanceOfResult extends js.Object { + def testChar(): Char = js.native + def testInt(): Int = js.native + def testShort(): Short = js.native + def testDouble(): Double = js.native + def testString(): String = js.native + def testValueClass(): SomeValueClass = js.native + def testNormalClass(): List[Int] = js.native + def testAny(): Any = js.native + } +} + +/* + * Helper classes, traits and objects defined here since they cannot be nested. + */ + +@JSName("InteroperabilityTestInherit.Pattern") +class InteroperabilityTestPattern protected () extends js.Object { + def this(pattern: String) = this() + val field: Int = js.native + def method(): String = js.native + def getConstructorParam(): String = js.native +} + +trait InteroperabilityTestTopLevel extends js.Object { + val value: String = js.native + def valueAsInt(): Int = js.native +} + +@JSName("InteroperabilityTestTopLevelObject") +object InteroperabilityTestTopLevel extends js.Object { + def apply(value: String): InteroperabilityTestTopLevel = js.native +} + +trait InteroperabilityTestVariadicMethod extends js.Object { + def foo(args: Any*): js.Array[Any] = js.native +} + +class InteroperabilityTestVariadicCtor(inargs: Any*) extends js.Object { + val args: js.Array[Any] = js.native +} + +object InteroperabilityTestGlobalScope extends js.GlobalScope { + var interoperabilityTestGlobalScopeValue: String = js.native + def interoperabilityTestGlobalScopeValueAsInt(): Int = js.native +} + +class SomeValueClass(val i: Int) extends AnyVal { + override def toString(): String = s"SomeValueClass($i)" +} + +class InteroperabilityTestCtor(x: Int = 5, y: Int = ???) extends js.Object { + def values: js.Array[Int] = js.native +} + +class InteroparabilityCtorInlineValue(val x: Int, var y: Int) extends js.Object diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/compiler/LongTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/compiler/LongTest.scala new file mode 100644 index 0000000..01d6f6e --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/compiler/LongTest.scala @@ -0,0 +1,159 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.compiler + +import scala.scalajs.js + +import org.scalajs.jasminetest.JasmineTest + +/** + * tests the compiler re-patching of native longs to + * scala.scalajs.runtime.Long + * see scala.scalajs.testsuite.jsinterop.RuntimeLongTest + * for a test of the implementation itself + */ +object LongTest extends JasmineTest { + + describe("JavaScript 64-bit long compatibility") { + it("should correctly handle literals") { + expect(5L + 100L == 105L).toBeTruthy + expect(2147483649L + 2L == 2147483651L).toBeTruthy + expect(-2147483648L * 4 == -8589934592L).toBeTruthy + expect(4503599627370510L * (-4) == -18014398509482040L).toBeTruthy + } + + it("should correctly dispatch unary ops on Longs") { + val x = 10L + expect(-x == -10L).toBeTruthy + val y = 5L + expect(-y == -5L).toBeTruthy + expect(+y == 5L).toBeTruthy + expect(~y == -6L).toBeTruthy + } + + it("should correctly dispatch binary ops on Longs") { + expect(5L * 5F == 25F).toBeTruthy + expect(5L % 4F == 1F).toBeTruthy + expect(5F * 4L == 20F).toBeTruthy + } + + it("should support shifts with Longs - #622") { + def l(x: Long): Long = x + def i(x: Int): Int = x + + expect(l(-7L) >>> 100L == 268435455L).toBeTruthy + expect(l(-7L) >> 100L == -1L).toBeTruthy + expect(l(-7L) >> 100 == -1L).toBeTruthy + expect(l(-7L) >>> 100 == 268435455).toBeTruthy + expect(l(-7L) << 100L == -481036337152L).toBeTruthy + expect(l(-7L) << 100 == -481036337152L).toBeTruthy + expect(l(7L) << 100L == 481036337152L).toBeTruthy + expect(l(8L) << 100L == 549755813888L).toBeTruthy + expect(l(-7L) >>> 4 == 1152921504606846975L).toBeTruthy + + expect(i(7) << 100).toEqual(112) + expect(i(-7) >> 100).toEqual(-1) + expect(i(-7) >>> 100).toEqual(268435455) + expect(i(-65) >> 100).toEqual(-5) + expect(i(-65) >> 4).toEqual(-5) + } + + it("primitives should convert to Long") { + // Byte + expect(112.toByte.toLong == 112L).toBeTruthy + // Short + expect((-10).toShort.toLong == -10L).toBeTruthy + // Char + expect('A'.toLong == 65L).toBeTruthy + // Int + expect(5.toLong == 5L).toBeTruthy + // Long + expect(10L.toLong == 10L).toBeTruthy + // Float + expect(100000.6f.toLong == 100000L).toBeTruthy + // Double + expect(100000.6.toLong == 100000L).toBeTruthy + } + + it("should support hashCode()") { + expect(0L .hashCode()).toEqual(0) + expect(55L .hashCode()).toEqual(55) + expect((-12L) .hashCode()).toEqual(11) + expect(10006548L .hashCode()).toEqual(10006548) + expect((-1098748L).hashCode()).toEqual(1098747) + + expect(613354684553L .hashCode()).toEqual(-825638905) + expect(9863155567412L .hashCode()).toEqual(1910653900) + expect(3632147899696541255L.hashCode()).toEqual(1735398658) + expect(7632147899696541255L.hashCode()).toEqual(-1689438124) + } + + it("should support ##") { + expect(0L .##).toEqual(0) + expect(55L .##).toEqual(55) + expect((-12L) .##).toEqual(-12) + expect(10006548L .##).toEqual(10006548) + expect((-1098748L).##).toEqual(-1098748) + + expect(9863155567412L .##).toEqual(1910653900) + expect(3632147899696541255L.##).toEqual(1735398658) + + // These two (correctly) give different results on 2.10 and 2.11 + //expect(613354684553L .##).toEqual(-825638905) // xx06 on 2.10 + //expect(7632147899696541255L.##).toEqual(-1689438124) // xx25 on 2.10 + } + + it("should correctly concat to string") { + val x = 20L + expect("asdf" + 5L + x + "hello").toEqual("asdf520hello") + expect(x + "hello").toEqual("20hello") + } + + it("string should convert to Long") { + expect("45678901234567890".toLong == 45678901234567890L).toBeTruthy + } + + it("should convert from and to js.prim.Number") { + val x = 5: js.prim.Number + expect((5L: js.prim.Number) == x).toBeTruthy + expect(x.toLong == 5L).toBeTruthy + } + + it("should correctly implement is/asInstanceOf Longs") { + val dyn: Any = 5L + val stat: Long = 5L + + expect(stat.asInstanceOf[Long]).toEqual(5L) + // models current scala behavior. See SI-1448 + expect(stat.asInstanceOf[Int]).toEqual(5) + + expect(stat.isInstanceOf[Long]).toBeTruthy + expect(stat.isInstanceOf[Int]).toBeFalsy + + expect(dyn.asInstanceOf[Long]).toEqual(5L) + + expect(dyn.isInstanceOf[Long]).toBeTruthy + expect(dyn.isInstanceOf[Int]).toBeFalsy + } + + when("compliant-asinstanceof"). + it("should correctly implement asInstanceOf Longs (negative)") { + val dyn: Any = 5L + + expect(() => dyn.asInstanceOf[Int]).toThrow + } + + it("should correctly compare to other numeric types") { + expect(5L == 5).toBeTruthy + expect(5 == 5L).toBeTruthy + expect(4 == 5l).toBeFalsy + expect('A' == 65L).toBeTruthy + } + } + +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/compiler/OptimizerTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/compiler/OptimizerTest.scala new file mode 100644 index 0000000..986c25a --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/compiler/OptimizerTest.scala @@ -0,0 +1,43 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.compiler + +import org.scalajs.jasminetest.JasmineTest + +object OptimizerTest extends JasmineTest { + + describe("Inlineable classes") { + + it("must update fields of `this` in the computation of other fields - #1153") { + val foo = new InlineClassDependentFields(5) + expect(foo.x).toEqual(5) + expect(foo.b).toBeTruthy + expect(foo.y).toEqual(11) + } + + it("must not break code that assigns `this` to a field") { + val foo = new InlineClassThisAlias(5) + expect(foo.z).toEqual(5) + } + + } + + @inline + class InlineClassDependentFields(val x: Int) { + val b = x > 3 + val y = if (b) x + 6 else x-2 + } + + @inline + class InlineClassThisAlias(val x: Int) { + val t = this + val y = x + val z = t.y + } + +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/compiler/ReflectionTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/compiler/ReflectionTest.scala new file mode 100644 index 0000000..3e1d7a2 --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/compiler/ReflectionTest.scala @@ -0,0 +1,69 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.compiler + +import scala.language.implicitConversions + +import scala.scalajs.js + +import org.scalajs.jasminetest.JasmineTest + +/** Tests the little reflection we support */ +object ReflectionTest extends JasmineTest { + + describe("Scala.js Reflection (through java.lang.Class)") { + it("should append $ to class name of objects") { + expect(TestObject.getClass.getName).toEqual( + "scala.scalajs.testsuite.compiler.ReflectionTest$TestObject$") + } + + it("should support isInstance") { + class A + class B extends A + val b = new B + expect(classOf[A].isInstance(b)).toBeTruthy + expect(classOf[A].isInstance("hello")).toBeFalsy + } + + it("getClass() for normal types") { + class Foo { + def bar() = super.getClass() + } + val foo = new Foo + expect(foo.getClass() eq classOf[Foo]).toBeTruthy + expect(foo.bar() eq classOf[Foo]).toBeTruthy + } + + it("getClass() for anti-boxed primitive types") { + implicit def classAsAny(c: java.lang.Class[_]): js.Any = + c.asInstanceOf[js.Any] + expect((false: Any).getClass).toBe(classOf[java.lang.Boolean]) + expect(('a': Any).getClass).toBe(classOf[java.lang.Character]) + expect((1.toByte: Any).getClass).toBe(classOf[java.lang.Byte]) + expect((1.toShort: Any).getClass).toBe(classOf[java.lang.Byte]) + expect((1: Any).getClass).toBe(classOf[java.lang.Byte]) + expect((1L: Any).getClass).toBe(classOf[java.lang.Long]) + expect((1.5f: Any).getClass).toBe(classOf[java.lang.Float]) + expect((1.5: Any).getClass).toBe(classOf[java.lang.Float]) + expect(((): Any).getClass).toBe(classOf[scala.runtime.BoxedUnit]) + } + + it("Class.isAssignableFrom should mimic runtime type tests behavior - #879") { + expect(classOf[Short].isAssignableFrom(classOf[Byte])).toBeTruthy + expect(classOf[Byte].isAssignableFrom(classOf[Byte])).toBeTruthy + expect(classOf[Byte].isAssignableFrom(classOf[Short])).toBeFalsy + expect(classOf[Int].isAssignableFrom(classOf[Byte])).toBeTruthy + expect(classOf[Double].isAssignableFrom(classOf[Int])).toBeTruthy + expect(classOf[Int].isAssignableFrom(classOf[Double])).toBeFalsy + expect(classOf[Long].isAssignableFrom(classOf[Int])).toBeFalsy + } + } + + object TestObject + +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/compiler/ReflectiveCallTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/compiler/ReflectiveCallTest.scala new file mode 100644 index 0000000..6020a5f --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/compiler/ReflectiveCallTest.scala @@ -0,0 +1,330 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.compiler + +import scala.scalajs.js +import org.scalajs.jasminetest.JasmineTest + +import language.reflectiveCalls + +import java.lang.{Float => JFloat, Double => JDouble} + +object ReflectiveCallTest extends JasmineTest { + + describe("Reflective Calls") { + it("should allow subtyping in return types") { + class A { def x = 1 } + class B extends A { override def x = 2 } + + object Generator { + def generate(): B = new B + } + + def f(x: { def generate(): A }) = x.generate + + expect(f(Generator).x).toEqual(2) + } + + it("should allow this.type in return types") { + type valueType = { def value: this.type } + def f(x: valueType) = x.value + + class StringValue(x: String) { + def value: this.type = this + override def toString = s"StringValue($x)" + } + + expect(f(new StringValue("foo")).toString).toEqual("StringValue(foo)") + } + + it("should allow generic return types") { + case class Tata(name: String) + + object Rec { + def e(x: Tata) = new Tata("iei") + } + + def m[T](r: Object { def e(x: Tata): T}) = + r.e(new Tata("foo")) + + expect(m[Tata](Rec).toString).toEqual("Tata(iei)") + } + + it("should work with unary methods on primitive types") { + def fInt(x: Any { def unary_- :Int }) = -x + expect(fInt(1.toByte)).toEqual(-1) + expect(fInt(1.toShort)).toEqual(-1) + expect(fInt(1.toChar)).toEqual(-1) + expect(fInt(1)).toEqual(-1) + + def fLong(x: Any { def unary_- :Long }) = -x + expect(fLong(1L)).toEqual(-1L) + + def fFloat(x: Any { def unary_- :Float}) = -x + expect(fFloat(1.5f)).toEqual(-1.5f) + + def fDouble(x: Any { def unary_- :Double }) = -x + expect(fDouble(1.5)).toEqual(-1.5) + + def fBoolean(x: Any { def unary_! :Boolean }) = !x + expect(fBoolean(false)).toBeTruthy + expect(fBoolean(true)).toBeFalsy + } + + it("should work with binary operators on primitive types") { + def fLong(x: Any { def +(x: Long): Long }) = x + 5L + expect(fLong(5.toByte)).toEqual(10L) + expect(fLong(10.toShort)).toEqual(15L) + expect(fLong(10.toChar)).toEqual(15L) + expect(fLong(-1)).toEqual(4L) + expect(fLong(17L)).toEqual(22L) + + def fInt(x: Any { def /(x: Int): Int }) = x / 7 + expect(fInt(65.toByte)).toEqual(9) + expect(fInt(15.toShort)).toEqual(2) + expect(fInt(25.toChar)).toEqual(3) + expect(fInt(-40)).toEqual(-5) + + def fShort(x: Any { def +(x: Short): Int }) = x + 6.toShort + expect(fShort(65.toByte)).toEqual(71) + expect(fShort(15.toShort)).toEqual(21) + expect(fShort(25.toChar)).toEqual(31) + expect(fShort(-40)).toEqual(-34) + + def fFloat(x: Any { def %(x: Float): Float}) = x % 3.4f + expect(fFloat(5.5f)).toEqual(2.1f) + + def fDouble(x: Any { def /(x: Double): Double }) = x / 1.4 + expect(fDouble(-1.5)).toEqual(-1.0714285714285714) + + def fBoolean(x: Any { def &&(x: Boolean): Boolean }) = x && true + expect(fBoolean(false)).toBeFalsy + expect(fBoolean(true)).toBeTruthy + } + + it("should work with equality operators on primitive types") { + def fNum(obj: Any { def ==(x: Int): Boolean }) = obj == 5 + expect(fNum(5.toByte)).toBeTruthy + expect(fNum(6.toByte)).toBeFalsy + expect(fNum(5.toShort)).toBeTruthy + expect(fNum(7.toShort)).toBeFalsy + expect(fNum(5.toChar)).toBeTruthy + expect(fNum('r')).toBeFalsy + expect(fNum(5)).toBeTruthy + expect(fNum(-4)).toBeFalsy + expect(fNum(5L)).toBeTruthy + expect(fNum(400L)).toBeFalsy + expect(fNum(5.0f)).toBeTruthy + expect(fNum(5.6f)).toBeFalsy + expect(fNum(5.0)).toBeTruthy + expect(fNum(7.9)).toBeFalsy + def fBool(obj: Any { def ==(x: Boolean): Boolean }) = obj == false + expect(fBool(true)).toBeFalsy + expect(fBool(false)).toBeTruthy + + def fNumN(obj: Any { def !=(x: Int): Boolean }) = obj != 5 + expect(fNumN(5.toByte)).toBeFalsy + expect(fNumN(6.toByte)).toBeTruthy + expect(fNumN(5.toShort)).toBeFalsy + expect(fNumN(7.toShort)).toBeTruthy + expect(fNumN(5.toChar)).toBeFalsy + expect(fNumN('r')).toBeTruthy + expect(fNumN(5)).toBeFalsy + expect(fNumN(-4)).toBeTruthy + expect(fNumN(5L)).toBeFalsy + expect(fNumN(400L)).toBeTruthy + expect(fNumN(5.0f)).toBeFalsy + expect(fNumN(5.6f)).toBeTruthy + expect(fNumN(5.0)).toBeFalsy + expect(fNumN(7.9)).toBeTruthy + def fBoolN(obj: Any { def !=(x: Boolean): Boolean }) = obj != false + expect(fBoolN(true)).toBeTruthy + expect(fBoolN(false)).toBeFalsy + + } + + it("should work with Arrays") { + type UPD = { def update(i: Int, x: String): Unit } + type APL = { def apply(i: Int): String } + type LEN = { def length: Int } + type CLONE = Any { def clone(): Object } + def upd(obj: UPD, i: Int, x: String) = obj.update(i,x) + def apl(obj: APL, i: Int) = obj.apply(i) + def len(obj: LEN) = obj.length + def clone(obj: CLONE) = obj.clone + + val x = Array("asdf","foo","bar") + val y = clone(x).asInstanceOf[Array[String]] + + expect(len(x)).toEqual(3) + expect(apl(x,0)).toEqual("asdf") + upd(x,1,"2foo") + expect(x(1)).toEqual("2foo") + expect(y(1)).toEqual("foo") + } + + it("should work with Arrays of primitive values") { + type UPD = { def update(i: Int, x: Int): Unit } + type APL = { def apply(i: Int): Int} + type LEN = { def length: Int } + type CLONE = Any { def clone(): Object } + def upd(obj: UPD, i: Int, x: Int) = obj.update(i,x) + def apl(obj: APL, i: Int) = obj.apply(i) + def len(obj: LEN) = obj.length + def clone(obj: CLONE) = obj.clone + + val x = Array(5,2,8) + val y = clone(x).asInstanceOf[Array[Int]] + + expect(len(x)).toEqual(3) + expect(apl(x,0)).toEqual(5) + upd(x,1,1000) + expect(x(1)).toEqual(1000) + expect(y(1)).toEqual(2) + } + + it("should work with Strings") { + def get(obj: { def codePointAt(str: Int): Int }) = + obj.codePointAt(1) + expect(get("Hi")).toEqual('i'.toInt) + + def sub(x: { def substring(x: Int): AnyRef }) = x.substring(5) + expect(sub("asdfasdfasdf") == "sdfasdf").toBeTruthy + + type LEN_A = { def length: Any } + def lenA(x: LEN_A) = x.length + expect(lenA("asdf") == 4).toBeTruthy + } + + it("should properly generate forwarders for inherited methods") { + trait A { + def foo: Int + } + + abstract class B extends A + + class C extends B { + def foo = 1 + } + + def call(x: { def foo: Int }) = x.foo + + expect(call(new C)).toEqual(1) + } + + it("should work on java.lang.Object.{ notify, notifyAll } - #303") { + type ObjNotifyLike = Any { + def notify(): Unit + def notifyAll(): Unit + } + def objNotifyTest(obj: ObjNotifyLike) = { + obj.notify() + obj.notifyAll() + 1 + } + + class A + + expect(objNotifyTest(new A())).toEqual(1) + } + + it("should work on java.lang.Object.clone - #303") { + type ObjCloneLike = Any { def clone(): AnyRef } + def objCloneTest(obj: ObjCloneLike) = obj.clone() + + class B(val x: Int) extends Cloneable { + override def clone() = super.clone + } + + val b = new B(1) + val bClone = objCloneTest(b).asInstanceOf[B] + + expect(b eq bClone).toBeFalsy + expect(bClone.x).toEqual(1) + } + + it("should work on scala.AnyRef.{ eq, ne } - #303") { + type ObjEqLike = Any { + def eq(that: AnyRef): Boolean + def ne(that: AnyRef): Boolean + } + def objEqTest(obj: ObjEqLike, that: AnyRef) = obj eq that + def objNeTest(obj: ObjEqLike, that: AnyRef) = obj ne that + + class A + + val a1 = new A + val a2 = new A + + expect(objEqTest(a1,a2)).toBeFalsy + expect(objEqTest(a1,a1)).toBeTruthy + + expect(objNeTest(a1,a2)).toBeTruthy + expect(objNeTest(a1,a1)).toBeFalsy + } + + it("should work on java.lang.{Float,Double}.{isNaN,isInfinite}") { + type FloatingNumberLike = Any { + def isNaN(): Boolean + def isInfinite(): Boolean + } + def test(x: FloatingNumberLike, isNaN: Boolean, + isInfinite: Boolean) = { + expect(x.isNaN()).toEqual(isNaN) + expect(x.isInfinite()).toEqual(isInfinite) + } + + test(new JFloat(Float.NaN), true, false) + test(new JFloat(Float.PositiveInfinity), false, true) + test(new JFloat(Float.NegativeInfinity), false, true) + test(new JFloat(54.67), false, false) + + test(new JDouble(Double.NaN), true, false) + test(new JDouble(Double.PositiveInfinity), false, true) + test(new JDouble(Double.NegativeInfinity), false, true) + test(new JDouble(54.67), false, false) + } + + it("should work with default arguments - #390") { + def pimpIt(a: Int) = new { + def foo(b: Int, c: Int = 1): Int = a + b + c + } + + expect(pimpIt(1).foo(2)).toEqual(4) + expect(pimpIt(2).foo(2,4)).toEqual(8) + } + + it("should unbox all types of arguments - #899") { + class Foo { + def makeInt: Int = 5 + def testInt(x: Int): Unit = expect(x).toEqual(5) + + def makeRef: Option[String] = Some("hi") + def testRef(x: Option[String]): Unit = expect(x == Some("hi")).toBeTruthy + } + + /* Note: we should also test with value classes, except that Scala itself + * does not support value classes as parameters or result type of + * methods in structural types. + */ + + def test(foo: { + def makeInt: Int + def testInt(x: Int): Unit + def makeRef: Option[String] + def testRef(x: Option[String]): Unit + }): Unit = { + foo.testInt(foo.makeInt) + foo.testRef(foo.makeRef) + } + + test(new Foo) + } + + } +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/compiler/RegressionTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/compiler/RegressionTest.scala new file mode 100644 index 0000000..19cceb2 --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/compiler/RegressionTest.scala @@ -0,0 +1,287 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.compiler + +import scala.annotation.tailrec + +import scala.scalajs.js +import org.scalajs.jasminetest.JasmineTest + +object RegressionTest extends JasmineTest { + + class Bug218Foo[T](val x: T) extends AnyVal + + describe("Scala.js compiler regression tests") { + + it("Wrong division conversion (7 / 2.0) - #18") { + val div = 7 / 2.0 + expect(div).toEqual(3.5) + expect(div.getClass.getName).toEqual("double") + + val mod = 7 % 2.0 + expect(mod).toEqual(1.0) + expect(mod.getClass.getName).toEqual("double") + } + + it("js.prim.String + js.prim.String is ambiguous - #20") { + val a: js.prim.String = "a" + val b: js.prim.String = "b" + val c: js.prim.String = a + b + expect(c).toEqual("ab") + } + + it("Abort with some pattern match guards - #22") { + object PatternMatchGuards { + def go(f: Int => Int) = f(1) + def main(): Unit = { + go { + case x if false => x + } + } + } + // Nothing to check + } + + it("Bad encoding for characters spanning 2 UTF-16 chars - #23") { + val str = "A∀\uD835\uDCAB" + var s: String = "" + for (c <- str) { + val code: Int = c + s = s + code + " " + } + expect(s).toEqual("65 8704 55349 56491 ") + } + + it("String concatenation with null - #26") { + val x: Object = null + expect(x + "check").toEqual("nullcheck") + } + + class Bug66A(s: String, e: Object) { + def this(e: Object) = this("", e) + def this(s: String) = this(s, "") + } + class Bug66B(s: String, e: Object) extends Bug66A(s) + + it("should emit static calls when forwarding to another constructor - #66") { + new Bug66B("", "") + } + + it("should not swallow Unit expressions when converting to js.prim.Undefined - #83") { + var effectHappened = false + def doEffect(): Unit = effectHappened = true + def f(): js.prim.Undefined = doEffect() + f() + expect(effectHappened).toBeTruthy + } + + it("should correctly call subSequence on non-string CharSequences - #55") { + val arr: CharSequence = Array('a','b','c','d') + val ss = arr.subSequence(2,3) + expect(ss.length()).toEqual(1) + expect(ss.charAt(0)).toEqual('c') + } + + it("should correctly concat primitive values to strings - #113") { + expect(4 + "foo").toEqual("4foo") + expect('a' + "foo").toEqual("afoo") + } + + it("should resolve overloads on scala.Function.apply when converting to js.Function - #125") { + class Fct extends Function1[Int,Any] { + def apply(n: Int) = n + } + + val scalaFunction = new Fct + val jsFunction: js.Any = scalaFunction + val thisFunction: js.ThisFunction = scalaFunction + } + + it("should correctly dispatch calls on private functions - #165") { + class A { + private def x = 1 + def value = x + } + class B extends A { + private def x = 2 + } + expect(new B().value).toEqual(1) + } + + it("should correctly mangle JavaScript reserved identifiers - #153") { + // Class name + class break { + // class variable + var continue = 1 + // method name + def switch = { + // local name + val default = 2 + default + } + } + trait Foo { + // static member (through mixin) + def function = 3 + } + + val x = new break with Foo + expect(x.continue).toEqual(1) + expect(x.switch).toEqual(2) + expect(x.function).toEqual(3) + } + + it("should correctly mangle identifiers starting with a digit - #153") { + // Class name + class `0` { + // class variable + var `1` = 1 + // method name + def `2` = { + // local name + val `22` = 2 + `22` + } + } + trait Foo { + // static member (through mixin) + def `3` = 3 + } + + val x = new `0` with Foo + expect(x.`1`).toEqual(1) + expect(x.`2`).toEqual(2) + expect(x.`3`).toEqual(3) + } + + it("should reserve `eval` and `arguments` - #743") { + val eval = 5 + expect(eval).toEqual(5) + val arguments = "hello" + expect(arguments).toEqual("hello") + } + + it("should support class literals for existential value types - #218") { + expect(scala.reflect.classTag[Bug218Foo[_]].toString).toEqual( + "scala.scalajs.testsuite.compiler.RegressionTest$Bug218Foo") + } + + it("should support Buffer - #268") { + val a = scala.collection.mutable.Buffer.empty[Int] + a.insert(0, 0) + a.remove(0) + for (i <- 0 to 10) { + a.insert(a.length / 2, i) + } + expect(a.mkString(", ")).toEqual("1, 3, 5, 7, 9, 10, 8, 6, 4, 2, 0") + } + + it("should not call equals when comparing with a literal null - #362") { + class A { override def equals(x: Any) = !(this == null) } + + val x = new A + val y = new A + + // If the null comparisons actually call equals, the following two will + // cause infinite recursion + expect(x == y).toBeTruthy + expect(y == x).toBeTruthy + } + + it("should unbox null to the zero of types - #674") { + class Box[A] { + var value: A = _ + } + def zero[A]: A = new Box[A].value + + /* Note: the same shape of test for Unit does not work, but it seems to + * be a problem in scalac because it does not work on the JVM either. + */ + + val bool = zero[Boolean] + expect((bool: Any).isInstanceOf[Boolean]).toBeTruthy + expect(bool == false).toBeTruthy + + val char = zero[Char] + expect((char: Any).isInstanceOf[Char]).toBeTruthy + expect(char == '\u0000').toBeTruthy + + val byte = zero[Byte] + expect((byte: Any).isInstanceOf[Byte]).toBeTruthy + expect(byte == 0.toByte).toBeTruthy + + val short = zero[Short] + expect((short: Any).isInstanceOf[Short]).toBeTruthy + expect(short == 0.toShort).toBeTruthy + + val int = zero[Int] + expect((int: Any).isInstanceOf[Int]).toBeTruthy + expect(int == 0).toBeTruthy + + val long = zero[Long] + expect((long: Any).isInstanceOf[Long]).toBeTruthy + expect(long == 0L).toBeTruthy + + val float = zero[Float] + expect((float: Any).isInstanceOf[Float]).toBeTruthy + expect(float == 0.0f).toBeTruthy + + val double = zero[Double] + expect((double: Any).isInstanceOf[Double]).toBeTruthy + expect(double == 0.0).toBeTruthy + + val ref = zero[AnyRef] + expect(ref == null).toBeTruthy + } + + it("Param defs in tailrec methods should be considered mutable - #825") { + @tailrec + def foo(x: Int, y: Int): Unit = { + if (x < y) foo(y, x) + else { + expect(x).toEqual(4) + expect(y).toEqual(2) + } + } + foo(2, 4) + } + + it("null.synchronized should throw - #874") { + expect(() => null.synchronized(5)).toThrow + } + + it("x.synchronized should preserve side-effects of x") { + var c = 0 + def x = { c += 1; this } + expect(x.synchronized(5)).toEqual(5) + expect(c).toEqual(1) + } + + it("IR checker should allow Apply/Select on NullType and NothingType - #1123") { + def giveMeANull(): Null = null + expect(() => (giveMeANull(): StringBuilder).append(5)).toThrow + expect(() => (giveMeANull(): scala.runtime.IntRef).elem).toThrow + + def giveMeANothing(): Nothing = sys.error("boom") + expect(() => (giveMeANothing(): StringBuilder).append(5)).toThrow + expect(() => (giveMeANothing(): scala.runtime.IntRef).elem).toThrow + } + + it("should not put bad flags on caseaccessor export forwarders - #1191") { + // This test used to choke patmat + + @scala.scalajs.js.annotation.JSExportAll + case class T(one: Int, two: Int) + + val T(a, b) = T(1, 2) + + expect(a).toEqual(1) + expect(b).toEqual(2) + } + } +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/compiler/RuntimeTypesTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/compiler/RuntimeTypesTest.scala new file mode 100644 index 0000000..b1a32c4 --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/compiler/RuntimeTypesTest.scala @@ -0,0 +1,77 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.compiler + +import scala.scalajs.js +import org.scalajs.jasminetest.JasmineTest + +import scala.util.{ Try, Failure } + +object RuntimeTypesTest extends JasmineTest { + + describe("scala.Nothing") { + + when("compliant-asinstanceof"). + it("casts to scala.Nothing should fail") { + val msg = Try("a".asInstanceOf[Nothing]) match { + case Failure(thr: ClassCastException) => thr.getMessage + case _ => "not failed" + } + expect(msg).toEqual("a is not an instance of scala.runtime.Nothing$") + } + + it("Array[Nothing] should be allowed to exists and be castable") { + val arr = Array[Nothing]() + arr.asInstanceOf[Array[Nothing]] + } + + it("Array[Array[Nothing]], too") { + val arr = Array[Array[Nothing]]() + arr.asInstanceOf[Array[Array[Nothing]]] + // This apparently works too... Dunno why + arr.asInstanceOf[Array[Nothing]] + } + + } + + describe("scala.Null") { + + when("compliant-asinstanceof"). + it("casts to scala.Null should fail for everything else but null") { + val msg = Try("a".asInstanceOf[Null]) match { + case Failure(thr: ClassCastException) => thr.getMessage + case _ => "not failed" + } + expect(msg).toEqual("a is not an instance of scala.runtime.Null$") + } + + it("classTag of scala.Null should contain proper Class[_] - #297") { + val tag = scala.reflect.classTag[Null] + expect(tag.runtimeClass != null).toBeTruthy + expect(tag.runtimeClass.getName).toEqual("scala.runtime.Null$") + } + + it("casts to scala.Null should succeed on null") { + null.asInstanceOf[Null] + } + + it("Array[Null] should be allowed to exist and be castable") { + val arr = Array.fill[Null](5)(null) + arr.asInstanceOf[Array[Null]] + } + + it("Array[Array[Null]] too") { + val arr = Array.fill[Null](5,5)(null) + arr.asInstanceOf[Array[Array[Null]]] + // This apparently works too... Dunno why + arr.asInstanceOf[Array[Null]] + } + + } + +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/compiler/ShortTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/compiler/ShortTest.scala new file mode 100644 index 0000000..10746ba --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/compiler/ShortTest.scala @@ -0,0 +1,38 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.compiler + +import org.scalajs.jasminetest.JasmineTest +import scala.scalajs.js + +object ShortTest extends JasmineTest { + + describe("Short primitives") { + + it("should always be in their range") { + def test(x: Int, y: Short): Unit = + expect(x.toShort).toEqual(y) + + test(0, 0) + test(-500, -500) + test(-90000, -24464) + test(123456789, -13035) + test(-40000, 25536) + test(65536, 0) + test(32768, -32768) + + def testC(x: Char, y: Short): Unit = + expect(x.toShort).toEqual(y) + + testC(-1.toChar, -1) + testC(200.toChar, 200) + testC(60000.toChar, -5536) + } + + } +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/compiler/UnitTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/compiler/UnitTest.scala new file mode 100644 index 0000000..8e2be64 --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/compiler/UnitTest.scala @@ -0,0 +1,47 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.compiler + +import org.scalajs.jasminetest.JasmineTest +import scala.scalajs.js + +object UnitTest extends JasmineTest { + + describe("Unit primitive") { + + it("should have toString()") { + expect(().toString()).toEqual("undefined") + expect(((): Any).toString()).toEqual("undefined") + } + + it("should have hashCode()") { + expect(().hashCode()).toEqual(0) + expect(((): Any).hashCode()).toEqual(0) + expect(().##).toEqual(0) + } + + it("should equal itself") { + expect(().equals(())).toBeTruthy + expect(((): Any).equals((): Any)).toBeTruthy + } + + it("should not equal other values") { + def testAgainst(v: Any): Unit = { + expect(().equals(v)).toBeFalsy + expect(((): Any).equals(v)).toBeFalsy + } + + testAgainst(0) + testAgainst(1) + testAgainst(null) + testAgainst(false) + testAgainst("") + } + + } +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/ArraysTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/ArraysTest.scala new file mode 100644 index 0000000..d0d97f1 --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/ArraysTest.scala @@ -0,0 +1,749 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.javalib + +import language.implicitConversions + +import scala.scalajs.js +import scala.scalajs.js.JSConverters._ + +import org.scalajs.jasminetest.JasmineTest + +import java.util.{ Arrays, Comparator } + +import scala.reflect.ClassTag + +object ArraysTest extends ArraysTest + +/** This is also used in the typedarray package to test scala.Arrays backed + * by TypedArrays + */ +trait ArraysTest extends JasmineTest { + + // Just in here, we allow ourselves to do this + implicit def array2jsArray[T](arr: Array[T]): js.Array[T] = arr.toJSArray + + /** Overridden by typedarray tests */ + def Array[T : ClassTag](v: T*): scala.Array[T] = scala.Array(v: _*) + + /** Overridden by typedarray tests */ + def testBody(suite: => Unit) = describe("java.util.Arrays")(suite) + + val stringComparator = new Comparator[String]() { + def compare(s1: String, s2: String) = s1.compareTo(s2) + } + + val intComparator = new Comparator[Int]() { + def compare(i1: Int, i2: Int) = i1 - i2 + } + + testBody { + + it("should respond to `sort` for Int") { + val scalaInts = Array(5, 3, 6, 1, 2, 4) + val ints = new Array[Object](scalaInts.length) + for (i <- 0 until scalaInts.length) + ints(i) = scalaInts(i).asInstanceOf[Object] + val sorted = Array(1, 2, 3, 4, 5, 6) + + Arrays.sort(ints, intComparator.asInstanceOf[Comparator[Object]]) + expect(ints).toEqual(Array(1, 2, 3, 4, 5, 6)) + } + + it("should respond to `sort` for String") { + val scalajs: Array[Object] = Array("S", "c", "a", "l", "a", ".", "j", "s") + val sorted = Array(".", "S", "a", "a", "c", "j", "l", "s") + + Arrays.sort(scalajs, stringComparator.asInstanceOf[Comparator[Object]]) + expect(scalajs).toEqual(sorted) + } + + it("should respond to `fill` for Boolean") { + val booleans = new Array[Boolean](6) + Arrays.fill(booleans, false) + expect(booleans).toEqual(Array(false, false, false, false, false, false)) + + Arrays.fill(booleans, true) + expect(booleans).toEqual(Array(true, true, true, true, true, true)) + } + + it("should respond to `fill` with start and end index for Boolean") { + val booleans = new Array[Boolean](6) + Arrays.fill(booleans, 1, 4, true) + expect(booleans).toEqual(Array(false, true, true, true, false, false)) + } + + it("should respond to `fill` for Byte") { + val bytes = new Array[Byte](6) + Arrays.fill(bytes, 42.toByte) + expect(bytes).toEqual(Array[Byte](42, 42, 42, 42, 42, 42)) + + Arrays.fill(bytes, -1.toByte) + expect(bytes).toEqual(Array[Byte](-1, -1, -1, -1, -1, -1)) + } + + it("should respond to `fill` with start and end index for Byte") { + val bytes = new Array[Byte](6) + Arrays.fill(bytes, 1, 4, 42.toByte) + expect(bytes).toEqual(Array[Byte](0, 42, 42, 42, 0, 0)) + + Arrays.fill(bytes, 2, 5, -1.toByte) + expect(bytes).toEqual(Array[Byte](0, 42, -1, -1, -1, 0)) + } + + it("should respond to `fill` for Short") { + val shorts = new Array[Short](6) + Arrays.fill(shorts, 42.toShort) + expect(shorts).toEqual(Array[Short](42, 42, 42, 42, 42, 42)) + + Arrays.fill(shorts, -1.toShort) + expect(shorts).toEqual(Array[Short](-1, -1, -1, -1, -1, -1)) + } + + it("should respond to `fill` with start and end index for Short") { + val shorts = new Array[Short](6) + Arrays.fill(shorts, 1, 4, 42.toShort) + expect(shorts).toEqual(Array[Short](0, 42, 42, 42, 0, 0)) + + Arrays.fill(shorts, 2, 5, -1.toShort) + expect(shorts).toEqual(Array[Short](0, 42, -1, -1, -1, 0)) + } + + it("should respond to `fill` for Int") { + val ints = new Array[Int](6) + Arrays.fill(ints, 42) + expect(ints).toEqual(Array(42, 42, 42, 42, 42, 42)) + + Arrays.fill(ints, -1) + expect(ints).toEqual(Array(-1, -1, -1, -1, -1, -1)) + } + + it("should respond to `fill` with start and end index for Int") { + val ints = new Array[Int](6) + Arrays.fill(ints, 1, 4, 42) + expect(ints).toEqual(Array(0, 42, 42, 42, 0, 0)) + + Arrays.fill(ints, 2, 5, -1) + expect(ints).toEqual(Array(0, 42, -1, -1, -1, 0)) + } + + it("should respond to `fill` for Long") { + val longs = new Array[Long](6) + Arrays.fill(longs, 42L) + expect(longs).toEqual(Array(42L, 42L, 42L, 42L, 42L, 42L)) + + Arrays.fill(longs, -1L) + expect(longs).toEqual(Array(-1L, -1L, -1L, -1L, -1L, -1L)) + } + + it("should respond to `fill` with start and end index for Long") { + val longs = new Array[Long](6) + Arrays.fill(longs, 1, 4, 42L) + expect(longs).toEqual(Array(0L, 42L, 42L, 42L, 0L, 0L)) + + Arrays.fill(longs, 2, 5, -1L) + expect(longs).toEqual(Array(0L, 42L, -1L, -1L, -1L, 0L)) + } + + it("should respond to `fill` for Float") { + val floats = new Array[Float](6) + Arrays.fill(floats, 42.0f) + expect(floats).toEqual(Array(42.0f, 42.0f, 42.0f, 42.0f, 42.0f, 42.0f)) + + Arrays.fill(floats, -1.0f) + expect(floats).toEqual(Array(-1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f)) + } + + it("should respond to `fill` with start and end index for Float") { + val floats = new Array[Float](6) + Arrays.fill(floats, 1, 4, 42.0f) + expect(floats).toEqual(Array(0.0f, 42.0f, 42.0f, 42.0f, 0.0f, 0.0f)) + + Arrays.fill(floats, 2, 5, -1.0f) + expect(floats).toEqual(Array(0.0f, 42.0f, -1.0f, -1.0f, -1.0f, 0.0f)) + } + + it("should respond to `fill` for Double") { + val doubles = new Array[Double](6) + Arrays.fill(doubles, 42.0) + expect(doubles).toEqual(Array(42.0, 42.0, 42.0, 42.0, 42.0, 42.0)) + + Arrays.fill(doubles, -1.0f) + expect(doubles).toEqual(Array(-1.0, -1.0, -1.0, -1.0, -1.0, -1.0)) + } + + it("should respond to `fill` with start and end index for Double") { + val doubles = new Array[Double](6) + Arrays.fill(doubles, 1, 4, 42.0) + expect(doubles).toEqual(Array(0.0, 42.0, 42.0, 42.0, 0.0, 0.0)) + + Arrays.fill(doubles, 2, 5, -1.0) + expect(doubles).toEqual(Array(0.0, 42.0, -1.0, -1.0, -1.0, 0.0)) + } + + it("should respond to `fill` for AnyRef") { + val array = new Array[AnyRef](6) + Arrays.fill(array, "a") + expect(array).toEqual(Array[AnyRef]("a", "a", "a", "a", "a", "a")) + + Arrays.fill(array, "b") + expect(array).toEqual(Array[AnyRef]("b", "b", "b", "b", "b", "b")) + } + + it("should respond to `fill` with start and end index for AnyRef") { + val bytes = new Array[AnyRef](6) + Arrays.fill(bytes, 1, 4, "a") + expect(bytes).toEqual(Array[AnyRef](null, "a", "a", "a", null, null)) + + Arrays.fill(bytes, 2, 5, "b") + expect(bytes).toEqual(Array[AnyRef](null, "a", "b", "b", "b", null)) + } + + it("should respond to `binarySearch` with start index, end index and key for Long") { + val longs: Array[Long] = Array(1, 2, 3, 5, 6, 7) + var ret = Arrays.binarySearch(longs, 0, 6, 5) + expect(ret).toEqual(3) + + ret = Arrays.binarySearch(longs, 0, 6, 0) + expect(ret).toEqual(-1) + + ret = Arrays.binarySearch(longs, 0, 6, 4) + expect(ret).toEqual(-4) + + ret = Arrays.binarySearch(longs, 0, 6, 8) + expect(ret).toEqual(-7) + } + + it("should respond to `binarySearch` with key for Long") { + val longs: Array[Long] = Array(1, 2, 3, 5, 6, 7) + var ret = Arrays.binarySearch(longs, 5) + expect(ret).toEqual(3) + + ret = Arrays.binarySearch(longs, 0) + expect(ret).toEqual(-1) + + ret = Arrays.binarySearch(longs, 4) + expect(ret).toEqual(-4) + + ret = Arrays.binarySearch(longs, 8) + expect(ret).toEqual(-7) + } + + it("should respond to `binarySearch` with start index, end index and key for Int") { + val ints: Array[Int] = Array(1, 2, 3, 5, 6, 7) + var ret = Arrays.binarySearch(ints, 0, 6, 5) + expect(ret).toEqual(3) + + ret = Arrays.binarySearch(ints, 0, 6, 0) + expect(ret).toEqual(-1) + + ret = Arrays.binarySearch(ints, 0, 6, 4) + expect(ret).toEqual(-4) + + ret = Arrays.binarySearch(ints, 0, 6, 8) + expect(ret).toEqual(-7) + } + + it("should respond to `binarySearch` with key for Int") { + val ints: Array[Int] = Array(1, 2, 3, 5, 6, 7) + var ret = Arrays.binarySearch(ints, 5) + expect(ret).toEqual(3) + + ret = Arrays.binarySearch(ints, 0) + expect(ret).toEqual(-1) + + ret = Arrays.binarySearch(ints, 4) + expect(ret).toEqual(-4) + + ret = Arrays.binarySearch(ints, 8) + expect(ret).toEqual(-7) + } + + it("should respond to `binarySearch` with start index, end index and key for Short") { + val shorts: Array[Short] = Array(1, 2, 3, 5, 6, 7) + var ret = Arrays.binarySearch(shorts, 0, 6, 5.toShort) + expect(ret).toEqual(3) + + ret = Arrays.binarySearch(shorts, 0, 6, 0.toShort) + expect(ret).toEqual(-1) + + ret = Arrays.binarySearch(shorts, 0, 6, 4.toShort) + expect(ret).toEqual(-4) + + ret = Arrays.binarySearch(shorts, 0, 6, 8.toShort) + expect(ret).toEqual(-7) + } + + it("should respond to `binarySearch` with key for Short") { + val shorts: Array[Short] = Array(1, 2, 3, 5, 6, 7) + var ret = Arrays.binarySearch(shorts, 5.toShort) + expect(ret).toEqual(3) + + ret = Arrays.binarySearch(shorts, 0.toShort) + expect(ret).toEqual(-1) + + ret = Arrays.binarySearch(shorts, 4.toShort) + expect(ret).toEqual(-4) + + ret = Arrays.binarySearch(shorts, 8.toShort) + expect(ret).toEqual(-7) + } + + it("should respond to `binarySearch` with start index, end index and key for Char") { + val chars: Array[Char] = Array('b', 'c', 'd', 'f', 'g', 'h') + var ret = Arrays.binarySearch(chars, 0, 6, 'f') + expect(ret).toEqual(3) + + ret = Arrays.binarySearch(chars, 0, 6, 'a') + expect(ret).toEqual(-1) + + ret = Arrays.binarySearch(chars, 0, 6, 'e') + expect(ret).toEqual(-4) + + ret = Arrays.binarySearch(chars, 0, 6, 'i') + expect(ret).toEqual(-7) + } + + it("should respond to `binarySearch` with key for Char") { + val chars: Array[Char] = Array('b', 'c', 'd', 'f', 'g', 'h') + var ret = Arrays.binarySearch(chars, 'f') + expect(ret).toEqual(3) + + ret = Arrays.binarySearch(chars, 'a') + expect(ret).toEqual(-1) + + ret = Arrays.binarySearch(chars, 'e') + expect(ret).toEqual(-4) + + ret = Arrays.binarySearch(chars, 'i') + expect(ret).toEqual(-7) + } + + it("should respond to `binarySearch` with start index, end index and key for Double") { + val doubles: Array[Double] = Array(0.1, 0.2, 0.3, 0.5, 0.6, 0.7) + var ret = Arrays.binarySearch(doubles, 0, 6, 0.5) + expect(ret).toEqual(3) + + ret = Arrays.binarySearch(doubles, 0, 6, 0.0) + expect(ret).toEqual(-1) + + ret = Arrays.binarySearch(doubles, 0, 6, 0.4) + expect(ret).toEqual(-4) + + ret = Arrays.binarySearch(doubles, 0, 6, 0.8) + expect(ret).toEqual(-7) + } + + it("should respond to `binarySearch` with key for Double") { + val doubles: Array[Double] = Array(0.1, 0.2, 0.3, 0.5, 0.6, 0.7) + var ret = Arrays.binarySearch(doubles, 0.5) + expect(ret).toEqual(3) + + ret = Arrays.binarySearch(doubles, 0.0) + expect(ret).toEqual(-1) + + ret = Arrays.binarySearch(doubles, 0.4) + expect(ret).toEqual(-4) + + ret = Arrays.binarySearch(doubles, 0.8) + expect(ret).toEqual(-7) + } + + it("should respond to `binarySearch` with start index, end index and key for Float") { + val floats: Array[Float] = Array(0.1f, 0.2f, 0.3f, 0.5f, 0.6f, 0.7f) + var ret = Arrays.binarySearch(floats, 0, 6, 0.5f) + expect(ret).toEqual(3) + + ret = Arrays.binarySearch(floats, 0, 6, 0.0f) + expect(ret).toEqual(-1) + + ret = Arrays.binarySearch(floats, 0, 6, 0.4f) + expect(ret).toEqual(-4) + + ret = Arrays.binarySearch(floats, 0, 6, 0.8f) + expect(ret).toEqual(-7) + } + + it("should respond to `binarySearch` with key for Float") { + val floats: Array[Float] = Array(0.1f, 0.2f, 0.3f, 0.5f, 0.6f, 0.7f) + var ret = Arrays.binarySearch(floats, 0.5f) + expect(ret).toEqual(3) + + ret = Arrays.binarySearch(floats, 0.0f) + expect(ret).toEqual(-1) + + ret = Arrays.binarySearch(floats, 0.4f) + expect(ret).toEqual(-4) + + ret = Arrays.binarySearch(floats, 0.8f) + expect(ret).toEqual(-7) + } + + it("should respond to `binarySearch` with start index, end index and key for AnyRef") { + val strings: Array[AnyRef] = Array("aa", "abc", "cc", "zz", "zzzs", "zzzt") + var ret = Arrays.binarySearch(strings, 0, 6, "zz") + expect(ret).toEqual(3) + + ret = Arrays.binarySearch(strings, 0, 6, "a") + expect(ret).toEqual(-1) + + ret = Arrays.binarySearch(strings, 0, 6, "cd") + expect(ret).toEqual(-4) + + ret = Arrays.binarySearch(strings, 0, 6, "zzzz") + expect(ret).toEqual(-7) + } + + it("should respond to `binarySearch` with key for AnyRef") { + val strings: Array[AnyRef] = Array("aa", "abc", "cc", "zz", "zzzs", "zzzt") + var ret = Arrays.binarySearch(strings, "zz") + expect(ret).toEqual(3) + + ret = Arrays.binarySearch(strings, "a") + expect(ret).toEqual(-1) + + ret = Arrays.binarySearch(strings, "cd") + expect(ret).toEqual(-4) + + ret = Arrays.binarySearch(strings, "zzzz") + expect(ret).toEqual(-7) + } + + it("should check ranges of input to `binarySearch`") { + def expectException(block: => Unit)(expected: PartialFunction[Throwable, Unit]): Unit = { + val catchAll: PartialFunction[Throwable, Unit] = { + case e: Throwable => expect(e.getClass.getName).toBe("not thrown") + } + + try { + block + expect("exception").toBe("thrown") + } catch expected orElse catchAll + } + + val array = Array(0, 1, 3, 4) + + expectException({ Arrays.binarySearch(array, 3, 2, 2) }) { + case exception: IllegalArgumentException => + expect(exception.getMessage).toBe("fromIndex(3) > toIndex(2)") + } + + // start/end comparison is made before index ranges checks + expectException({ Arrays.binarySearch(array, 7, 5, 2) }) { + case exception: IllegalArgumentException => + expect(exception.getMessage).toBe("fromIndex(7) > toIndex(5)") + } + + expectException({ Arrays.binarySearch(array, -1, 4, 2) }) { + case exception: ArrayIndexOutOfBoundsException => + expect(exception.getMessage).toBe("Array index out of range: -1") + } + + expectException({ Arrays.binarySearch(array, 0, 5, 2) }) { + case exception: ArrayIndexOutOfBoundsException => + expect(exception.getMessage).toBe("Array index out of range: 5") + } + } + + it("should respond to `copyOf` with key for Int") { + val ints: Array[Int] = Array(1, 2, 3) + val intscopy = Arrays.copyOf(ints, 5) + expect(intscopy).toEqual(Array(1, 2, 3, 0, 0)) + } + + it("should respond to `copyOf` with key for Long") { + val longs: Array[Long] = Array(1, 2, 3) + val longscopy = Arrays.copyOf(longs, 5) + expect(longscopy).toEqual(Array[Long](1, 2, 3, 0, 0)) + } + + it("should respond to `copyOf` with key for Short") { + val shorts: Array[Short] = Array(1, 2, 3) + val shortscopy = Arrays.copyOf(shorts, 5) + expect(shortscopy).toEqual(Array[Short](1, 2, 3, 0, 0)) + } + + it("should respond to `copyOf` with key for Byte") { + val bytes: Array[Byte] = Array(42, 43, 44) + val floatscopy = Arrays.copyOf(bytes, 5) + expect(floatscopy).toEqual(Array[Byte](42, 43, 44, 0, 0)) + } + + it("should respond to `copyOf` with key for Char") { + val chars: Array[Char] = Array('a', 'b', '0') + val charscopy = Arrays.copyOf(chars, 5) + expect(charscopy(4)).toEqual(0.toChar) + } + + it("should respond to `copyOf` with key for Double") { + val doubles: Array[Double] = Array(0.1, 0.2, 0.3) + val doublescopy = Arrays.copyOf(doubles, 5) + expect(doublescopy).toEqual(Array[Double](0.1, 0.2, 0.3, 0, 0)) + } + + it("should respond to `copyOf` with key for Float") { + val floats: Array[Float] = Array(0.1f, 0.2f, 0.3f) + val floatscopy = Arrays.copyOf(floats, 5) + expect(floatscopy).toEqual(Array[Float](0.1f, 0.2f, 0.3f, 0f, 0f)) + } + + it("should respond to `copyOf` with key for Boolean") { + val bools: Array[Boolean] = Array(false, true, false) + val boolscopy = Arrays.copyOf(bools, 5) + expect(boolscopy).toEqual(Array[Boolean](false, true, false, false, false)) + } + + it("should respond to `copyOf` with key for AnyRef") { + val anyrefs: Array[AnyRef] = Array("a", "b", "c") + val anyrefscopy = Arrays.copyOf(anyrefs, 5) + expect(anyrefscopy.getClass() == classOf[Array[AnyRef]]).toBeTruthy + expect(anyrefscopy).toEqual(Array[AnyRef]("a", "b", "c", null, null)) + + val sequences: Array[CharSequence] = Array("a", "b", "c") + val sequencescopy = Arrays.copyOf(sequences, 2) + expect(sequencescopy.getClass() == classOf[Array[CharSequence]]) + expect(sequencescopy).toEqual(Array[CharSequence]("a", "b")) + } + + it("should respond to `copyOfRange` for AnyRef") { + val anyrefs: Array[AnyRef] = Array("a", "b", "c", "d", "e") + val anyrefscopy = Arrays.copyOfRange(anyrefs, 2, 4) + expect(anyrefscopy.getClass() == classOf[Array[AnyRef]]).toBeTruthy + expect(anyrefscopy).toEqual(Array[AnyRef]("c", "d")) + + val sequences: Array[CharSequence] = Array("a", "b", "c", "d", "e") + val sequencescopy = Arrays.copyOfRange(sequences, 1, 5) + expect(sequencescopy.getClass() == classOf[Array[CharSequence]]) + expect(sequencescopy).toEqual(Array[CharSequence]("b", "c", "d", "e")) + } + + it("should respond to `hashCode` for Boolean") { + expect(Arrays.hashCode(null: Array[Boolean])).toEqual(0) + expect(Arrays.hashCode(Array[Boolean]())).toEqual(1) + expect(Arrays.hashCode(Array[Boolean](false))).toEqual(1268) + expect(Arrays.hashCode(Array[Boolean](true, false))).toEqual(40359) + } + + it("should respond to `hashCode` for Chars") { + expect(Arrays.hashCode(null: Array[Char])).toEqual(0) + expect(Arrays.hashCode(Array[Char]())).toEqual(1) + expect(Arrays.hashCode(Array[Char]('a'))).toEqual(128) + expect(Arrays.hashCode(Array[Char]('c', '&'))).toEqual(4068) + expect(Arrays.hashCode(Array[Char]('-', '5', 'q'))).toEqual(74792) + expect(Arrays.hashCode(Array[Char]('.', ' ', '\u4323', 'v', '~'))).toEqual(88584920) + } + + it("should respond to `hashCode` for Bytes") { + expect(Arrays.hashCode(null: Array[Byte])).toEqual(0) + expect(Arrays.hashCode(Array[Byte]())).toEqual(1) + expect(Arrays.hashCode(Array[Byte](1))).toEqual(32) + expect(Arrays.hashCode(Array[Byte](7, -125))).toEqual(1053) + expect(Arrays.hashCode(Array[Byte](3, 0, 45))).toEqual(32719) + expect(Arrays.hashCode(Array[Byte](0, 45, 100, 1, 1))).toEqual(30065878) + } + + it("should respond to `hashCode` for Shorts") { + expect(Arrays.hashCode(null: Array[Short])).toEqual(0) + expect(Arrays.hashCode(Array[Short]())).toEqual(1) + expect(Arrays.hashCode(Array[Short](1))).toEqual(32) + expect(Arrays.hashCode(Array[Short](7, -125))).toEqual(1053) + expect(Arrays.hashCode(Array[Short](3, 0, 4534))).toEqual(37208) + expect(Arrays.hashCode(Array[Short](0, 45, 100, 1, 1))).toEqual(30065878) + } + + it("should respond to `hashCode` for Ints") { + expect(Arrays.hashCode(null: Array[Int])).toEqual(0) + expect(Arrays.hashCode(Array[Int]())).toEqual(1) + expect(Arrays.hashCode(Array[Int](1))).toEqual(32) + expect(Arrays.hashCode(Array[Int](7, -125))).toEqual(1053) + expect(Arrays.hashCode(Array[Int](3, 0, 4534))).toEqual(37208) + expect(Arrays.hashCode(Array[Int](0, 45, 100, 1, 1, Int.MaxValue))).toEqual(-1215441431) + } + + it("should respond to `hashCode` for Longs") { + expect(Arrays.hashCode(null: Array[Long])).toEqual(0) + expect(Arrays.hashCode(Array[Long]())).toEqual(1) + expect(Arrays.hashCode(Array[Long](1L))).toEqual(32) + expect(Arrays.hashCode(Array[Long](7L, -125L))).toEqual(1302) + expect(Arrays.hashCode(Array[Long](3L, 0L, 4534L))).toEqual(37208) + expect(Arrays.hashCode(Array[Long](0L, 45L, 100L, 1L, 1L, Int.MaxValue))).toEqual(-1215441431) + expect(Arrays.hashCode(Array[Long](0L, 34573566354545L, 100L, 1L, 1L, Int.MaxValue))).toEqual(-1952288964) + } + + it("should respond to `hashCode` for Floats") { + expect(Arrays.hashCode(null: Array[Float])).toEqual(0) + expect(Arrays.hashCode(Array[Float]())).toEqual(1) + expect(Arrays.hashCode(Array[Float](1f))).toEqual(32) + expect(Arrays.hashCode(Array[Float](7.2f, -125.2f))).toEqual(-2082726591) + expect(Arrays.hashCode(Array[Float](302.1f, 0.0f, 4534f))).toEqual(-1891539602) + expect(Arrays.hashCode(Array[Float](0.0f, 45f, -100f, 1.1f, -1f, 3567f))).toEqual(-1591440133) + } + + it("should respond to `hashCode` for Doubles") { + expect(Arrays.hashCode(null: Array[Double])).toEqual(0) + expect(Arrays.hashCode(Array[Double]())).toEqual(1) + expect(Arrays.hashCode(Array[Double](1.1))).toEqual(-1503133662) + expect(Arrays.hashCode(Array[Double](7.3, -125.23))).toEqual(-2075734168) + expect(Arrays.hashCode(Array[Double](3.9, 0.2, 4534.9))).toEqual(-557562564) + expect(Arrays.hashCode(Array[Double](0.1, 45.1, -100.0, 1.1, 1.7))).toEqual(-1750344582) + expect(Arrays.hashCode(Array[Double](0.0, 34573566354545.9, 100.2, 1.1, 1.2, Int.MaxValue))).toEqual(-1764602991) + } + + it("should respond to `hashCode` for AnyRef") { + expect(Arrays.hashCode(null: Array[AnyRef])).toEqual(0) + expect(Arrays.hashCode(Array[AnyRef]())).toEqual(1) + expect(Arrays.hashCode(Array[AnyRef](null, null))).toEqual(961) + expect(Arrays.hashCode(Array[AnyRef]("a", "b", null))).toEqual(126046) + expect(Arrays.hashCode(Array[AnyRef](null, "a", "b", null, "fooooo"))).toEqual(-1237252983) + } + + it("should respond to `equals` for Booleans") { + val a1 = Array(true, false) + + expect(Arrays.equals(a1, a1)).toBeTruthy + expect(Arrays.equals(a1, Array(true, false))).toBeTruthy + + expect(Arrays.equals(a1, Array(true))).toBeFalsy + expect(Arrays.equals(a1, Array(false))).toBeFalsy + expect(Arrays.equals(a1, Array[Boolean]())).toBeFalsy + expect(Arrays.equals(a1, Array(false, true))).toBeFalsy + expect(Arrays.equals(a1, Array(false, true, false))).toBeFalsy + } + + it("should respond to `equals` for Bytes") { + val a1 = Array[Byte](1, -7, 10) + + expect(Arrays.equals(null: Array[Byte], null: Array[Byte])).toBeTruthy + expect(Arrays.equals(a1, a1)).toBeTruthy + expect(Arrays.equals(a1, Array[Byte](1, -7, 10))).toBeTruthy + + expect(Arrays.equals(a1, null)).toBeFalsy + expect(Arrays.equals(a1, Array[Byte](3))).toBeFalsy + expect(Arrays.equals(a1, Array[Byte](1))).toBeFalsy + expect(Arrays.equals(a1, Array[Byte]())).toBeFalsy + expect(Arrays.equals(a1, Array[Byte](1, -7, 11))).toBeFalsy + expect(Arrays.equals(a1, Array[Byte](1, -7, 11, 20))).toBeFalsy + } + + it("should respond to `equals` for Chars") { + val a1 = Array[Char]('a', '0', '-') + + expect(Arrays.equals(null: Array[Char], null: Array[Char])).toBeTruthy + expect(Arrays.equals(a1, a1)).toBeTruthy + expect(Arrays.equals(a1, Array[Char]('a', '0', '-'))).toBeTruthy + + expect(Arrays.equals(a1, null)).toBeFalsy + expect(Arrays.equals(a1, Array[Char]('z'))).toBeFalsy + expect(Arrays.equals(a1, Array[Char]('a'))).toBeFalsy + expect(Arrays.equals(a1, Array[Char]())).toBeFalsy + expect(Arrays.equals(a1, Array[Char]('a', '0', '+'))).toBeFalsy + expect(Arrays.equals(a1, Array[Char]('a', '0', '-', 'z'))).toBeFalsy + } + + it("should respond to `equals` for Shorts") { + val a1 = Array[Short](1, -7, 10) + + expect(Arrays.equals(null: Array[Short], null: Array[Short])).toBeTruthy + expect(Arrays.equals(a1, a1)).toBeTruthy + expect(Arrays.equals(a1, Array[Short](1, -7, 10))).toBeTruthy + + expect(Arrays.equals(a1, null)).toBeFalsy + expect(Arrays.equals(a1, Array[Short](3))).toBeFalsy + expect(Arrays.equals(a1, Array[Short](1))).toBeFalsy + expect(Arrays.equals(a1, Array[Short]())).toBeFalsy + expect(Arrays.equals(a1, Array[Short](1, -7, 11))).toBeFalsy + expect(Arrays.equals(a1, Array[Short](1, -7, 11, 20))).toBeFalsy + } + + it("should respond to `equals` for Ints") { + val a1 = Array[Int](1, -7, 10) + + expect(Arrays.equals(null: Array[Int], null: Array[Int])).toBeTruthy + expect(Arrays.equals(a1, a1)).toBeTruthy + expect(Arrays.equals(a1, Array[Int](1, -7, 10))).toBeTruthy + + expect(Arrays.equals(a1, null)).toBeFalsy + expect(Arrays.equals(a1, Array[Int](3))).toBeFalsy + expect(Arrays.equals(a1, Array[Int](1))).toBeFalsy + expect(Arrays.equals(a1, Array[Int]())).toBeFalsy + expect(Arrays.equals(a1, Array[Int](1, -7, 11))).toBeFalsy + expect(Arrays.equals(a1, Array[Int](1, -7, 11, 20))).toBeFalsy + } + + it("should respond to `equals` for Longs") { + val a1 = Array[Long](1L, -7L, 10L) + + expect(Arrays.equals(null: Array[Long], null: Array[Long])).toBeTruthy + expect(Arrays.equals(a1, a1)).toBeTruthy + expect(Arrays.equals(a1, Array[Long](1L, -7L, 10L))).toBeTruthy + + expect(Arrays.equals(a1, null)).toBeFalsy + expect(Arrays.equals(a1, Array[Long](3L))).toBeFalsy + expect(Arrays.equals(a1, Array[Long](1L))).toBeFalsy + expect(Arrays.equals(a1, Array[Long]())).toBeFalsy + expect(Arrays.equals(a1, Array[Long](1L, -7L, 11L))).toBeFalsy + expect(Arrays.equals(a1, Array[Long](1L, -7L, 11L, 20L))).toBeFalsy + } + + it("should respond to `equals` for Floats") { + val a1 = Array[Float](1.1f, -7.4f, 10.0f) + + expect(Arrays.equals(null: Array[Float], null: Array[Float])).toBeTruthy + expect(Arrays.equals(a1, a1)).toBeTruthy + expect(Arrays.equals(a1, Array[Float](1.1f, -7.4f, 10.0f))).toBeTruthy + + expect(Arrays.equals(a1, null)).toBeFalsy + expect(Arrays.equals(a1, Array[Float](3.0f))).toBeFalsy + expect(Arrays.equals(a1, Array[Float](1.1f))).toBeFalsy + expect(Arrays.equals(a1, Array[Float]())).toBeFalsy + expect(Arrays.equals(a1, Array[Float](1.1f, -7.4f, 11.0f))).toBeFalsy + expect(Arrays.equals(a1, Array[Float](1.1f, -7.4f, 10.0f, 20.0f))).toBeFalsy + } + + it("should respond to `equals` for Doubles") { + val a1 = Array[Double](1.1, -7.4, 10.0) + + expect(Arrays.equals(null: Array[Double], null: Array[Double])).toBeTruthy + expect(Arrays.equals(a1, a1)).toBeTruthy + expect(Arrays.equals(a1, Array[Double](1.1, -7.4, 10.0))).toBeTruthy + + expect(Arrays.equals(a1, null)).toBeFalsy + expect(Arrays.equals(a1, Array[Double](3.0))).toBeFalsy + expect(Arrays.equals(a1, Array[Double](1.1))).toBeFalsy + expect(Arrays.equals(a1, Array[Double]())).toBeFalsy + expect(Arrays.equals(a1, Array[Double](1.1, -7.4, 11.0))).toBeFalsy + expect(Arrays.equals(a1, Array[Double](1.1, -7.4, 10.0, 20.0))).toBeFalsy + } + + it("should respond to `equals` for AnyRefs") { + class A(private val x: Int) { + override def equals(that: Any) = that match { + case that: A => this.x == that.x + case _ => false + } + } + + def A(x: Int) = new A(x) + + val a1 = Array[AnyRef](A(1), A(-7), A(10)) + + expect(Arrays.equals(null: Array[AnyRef], null: Array[AnyRef])).toBeTruthy + expect(Arrays.equals(a1, a1)).toBeTruthy + expect(Arrays.equals(a1, Array[AnyRef](A(1), A(-7), A(10)))).toBeTruthy + + expect(Arrays.equals(a1, null)).toBeFalsy + expect(Arrays.equals(a1, Array[AnyRef](A(3)))).toBeFalsy + expect(Arrays.equals(a1, Array[AnyRef](A(1)))).toBeFalsy + expect(Arrays.equals(a1, Array[AnyRef]())).toBeFalsy + expect(Arrays.equals(a1, Array[AnyRef](A(1), null, A(11)))).toBeFalsy + expect(Arrays.equals(a1, Array[AnyRef](A(1), A(-7), A(11), A(20)))).toBeFalsy + } + + } + +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/AtomicTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/AtomicTest.scala new file mode 100644 index 0000000..c4f065a --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/AtomicTest.scala @@ -0,0 +1,119 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.javalib + +import scala.language.implicitConversions + +import scala.scalajs.js + +import org.scalajs.jasminetest.JasmineTest + +object AtomicTest extends JasmineTest { + + describe("java.util.concurrent.AtomicLong") { + + it("Should have all the normal operations") { + val atomic = new java.util.concurrent.atomic.AtomicLong(10) + expect(atomic.get()).toEqual(10) + atomic.set(20) + expect(atomic.get()).toEqual(20) + expect(atomic.getAndIncrement()).toEqual(20) + expect(atomic.get()).toEqual(21) + expect(atomic.getAndDecrement()).toEqual(21) + expect(atomic.get()).toEqual(20) + expect(atomic.getAndSet(0)).toEqual(20) + expect(atomic.get()).toEqual(0) + expect(atomic.incrementAndGet()).toEqual(1) + expect(atomic.get()).toEqual(1) + expect(atomic.decrementAndGet()).toEqual(0) + expect(atomic.get()).toEqual(0) + expect(atomic.addAndGet(10)).toEqual(10) + expect(atomic.get()).toEqual(10) + expect(atomic.intValue).toEqual(10) + expect(atomic.longValue).toEqual(10L) + expect(atomic.floatValue).toEqual(10.0f) + expect(atomic.doubleValue).toEqual(10) + expect(atomic.compareAndSet(1, 20)).toEqual(false) + expect(atomic.get()).toEqual(10) + expect(atomic.compareAndSet(10, 20)).toEqual(true) + expect(atomic.get()).toEqual(20) + } + } + describe("java.util.concurrent.AtomicInteger") { + it("Should have all the normal operations") { + val atomic = new java.util.concurrent.atomic.AtomicInteger(10) + expect(atomic.get()).toEqual(10) + atomic.set(20) + expect(atomic.get()).toEqual(20) + expect(atomic.getAndIncrement()).toEqual(20) + expect(atomic.get()).toEqual(21) + expect(atomic.getAndDecrement()).toEqual(21) + expect(atomic.get()).toEqual(20) + expect(atomic.getAndSet(0)).toEqual(20) + expect(atomic.get()).toEqual(0) + expect(atomic.incrementAndGet()).toEqual(1) + expect(atomic.get()).toEqual(1) + expect(atomic.decrementAndGet()).toEqual(0) + expect(atomic.get()).toEqual(0) + expect(atomic.addAndGet(10)).toEqual(10) + expect(atomic.get()).toEqual(10) + expect(atomic.intValue).toEqual(10) + expect(atomic.longValue).toEqual(10L) + expect(atomic.floatValue).toEqual(10.0f) + expect(atomic.doubleValue).toEqual(10) + expect(atomic.compareAndSet(1, 20)).toEqual(false) + expect(atomic.get()).toEqual(10) + expect(atomic.compareAndSet(10, 20)).toEqual(true) + expect(atomic.get()).toEqual(20) + } + } + describe("java.util.concurrent.AtomicBoolean") { + it("Should have all the normal operations") { + val atomic = new java.util.concurrent.atomic.AtomicBoolean(true) + expect(atomic.get()).toEqual(true) + atomic.set(false) + expect(atomic.get()).toEqual(false) + expect(atomic.compareAndSet(true, true)).toEqual(false) + expect(atomic.get()).toEqual(false) + expect(atomic.compareAndSet(false, true)).toEqual(true) + expect(atomic.get()).toEqual(true) + expect(atomic.getAndSet(false)).toEqual(true) + expect(atomic.get()).toEqual(false) + } + } + describe("java.util.concurrent.AtomicReference") { + it("Should have all the normal operations") { + val thing1 = Foo(5) + val thing1bis = Foo(5) // equals(), but not the same reference + val thing2 = Foo(10) + + implicit def foo2js(foo: Foo): js.Any = foo.asInstanceOf[js.Any] + + // sanity + expect(thing1 == thing1bis).toBeTruthy + expect(thing1 == thing2).toBeFalsy + expect(thing1).toBe(thing1) + expect(thing1).not.toBe(thing1bis) + + // actual test + val atomic = new java.util.concurrent.atomic.AtomicReference(thing1) + expect(atomic.get()).toBe(thing1) + atomic.set(thing2) + expect(atomic.get()).toBe(thing2) + expect(atomic.compareAndSet(thing1, thing1)).toEqual(false) + expect(atomic.get()).toBe(thing2) + expect(atomic.compareAndSet(thing2, thing1)).toEqual(true) + expect(atomic.get()).toBe(thing1) + expect(atomic.compareAndSet(thing1bis, thing2)).toEqual(false) + expect(atomic.getAndSet(thing2)).toBe(thing1) + expect(atomic.get()).toBe(thing2) + } + } + + case class Foo(i: Int) +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/BooleanTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/BooleanTest.scala new file mode 100644 index 0000000..87c65e9 --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/BooleanTest.scala @@ -0,0 +1,62 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.javalib + +import java.lang.{Boolean => JBoolean} + +import org.scalajs.jasminetest.JasmineTest + +/** + * tests the implementation of the java standard library Boolean + */ +object BooleanTest extends JasmineTest { + + describe("java.lang.Boolean") { + + it("should provide `booleanValue`") { + expect(JBoolean.TRUE.booleanValue()).toBe(true) + expect(JBoolean.FALSE.booleanValue()).toBe(false) + expect(() => (null: JBoolean).booleanValue()).toThrow + } + + it("should provide `compareTo`") { + def compare(x: Boolean, y: Boolean): Int = + new JBoolean(x).compareTo(new JBoolean(y)) + + expect(compare(false, false)).toEqual(0) + expect(compare(false, true)).toBeLessThan(0) + expect(compare(true, false)).toBeGreaterThan(0) + expect(compare(true, true)).toEqual(0) + } + + it("should be a Comparable") { + def compare(x: Any, y: Any): Int = + x.asInstanceOf[Comparable[Any]].compareTo(y) + + expect(compare(false, false)).toEqual(0) + expect(compare(false, true)).toBeLessThan(0) + expect(compare(true, false)).toBeGreaterThan(0) + expect(compare(true, true)).toEqual(0) + } + + it("should parse strings") { + def test(s: String, v: Boolean): Unit = { + expect(JBoolean.parseBoolean(s)).toEqual(v) + expect(JBoolean.valueOf(s).booleanValue()).toEqual(v) + expect(new JBoolean(s).booleanValue()).toEqual(v) + } + + test("false", false) + test("true", true) + test("TrUe", true) + test(null, false) + test("truee", false) + } + + } +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/ByteTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/ByteTest.scala new file mode 100644 index 0000000..b033c59 --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/ByteTest.scala @@ -0,0 +1,64 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.javalib + +import java.lang.{Byte => JByte} + +import org.scalajs.jasminetest.JasmineTest + +/** + * tests the implementation of the java standard library Byte + */ +object ByteTest extends JasmineTest { + + describe("java.lang.Byte") { + + it("should provide `compareTo`") { + def compare(x: Byte, y: Byte): Int = + new JByte(x).compareTo(new JByte(y)) + + expect(compare(0.toByte, 5.toByte)).toBeLessThan(0) + expect(compare(10.toByte, 9.toByte)).toBeGreaterThan(0) + expect(compare(-2.toByte, -1.toByte)).toBeLessThan(0) + expect(compare(3.toByte, 3.toByte)).toEqual(0) + } + + it("should be a Comparable") { + def compare(x: Any, y: Any): Int = + x.asInstanceOf[Comparable[Any]].compareTo(y) + + expect(compare(0.toByte, 5.toByte)).toBeLessThan(0) + expect(compare(10.toByte, 9.toByte)).toBeGreaterThan(0) + expect(compare(-2.toByte, -1.toByte)).toBeLessThan(0) + expect(compare(3.toByte, 3.toByte)).toEqual(0) + } + + it("should parse strings") { + def test(s: String, v: Byte): Unit = { + expect(JByte.parseByte(s)).toEqual(v) + expect(JByte.valueOf(s).byteValue()).toEqual(v) + expect(new JByte(s).byteValue()).toEqual(v) + } + + test("0", 0) + test("5", 5) + test("127", 127) + test("-100", -100) + } + + it("should reject invalid strings when parsing") { + def test(s: String): Unit = + expect(() => JByte.parseByte(s)).toThrow + + test("abc") + test("") + test("200") // out of range + } + + } +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/CharacterTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/CharacterTest.scala new file mode 100644 index 0000000..f206221 --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/CharacterTest.scala @@ -0,0 +1,672 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.javalib + +import org.scalajs.jasminetest.JasmineTest +import scala.scalajs.js + +object CharacterTest extends JasmineTest { + + describe("java.lang.Character") { + + it("should provide `isISOControl`") { + val isoControlChars = (('\u0000' to '\u001F') ++ ('\u007F' to '\u009F')).map(_.toInt).toSet + isoControlChars foreach { c => + expect(Character.isISOControl(c)).toEqual(true) + } + + val randomInts = List.fill(100)(scala.util.Random.nextInt) + ((-1000 to 1000) ++ randomInts).filterNot(isoControlChars) foreach { c => + expect(Character.isISOControl(c)).toEqual(false) + } + } + + it("should provide `digit`") { + expect(Character.digit('a', 16)).toEqual(10) + expect(Character.digit('}', 5)).toEqual(-1) + expect(Character.digit('1', 50)).toEqual(-1) + expect(Character.digit('1', 36)).toEqual(1) + expect(Character.digit('Z', 36)).toEqual(35) + expect(Character.digit('\uFF22', 20)).toEqual(11) + } + + it("should provide isDigit") { + expect(Character.isDigit('a')).toBeFalsy + expect(Character.isDigit('0')).toBeTruthy + expect(Character.isDigit('5')).toBeTruthy + expect(Character.isDigit('9')).toBeTruthy + expect(Character.isDigit('z')).toBeFalsy + expect(Character.isDigit(' ')).toBeFalsy + } + + it("should provide `compareTo`") { + def compare(x: Char, y: Char): Int = + new Character(x).compareTo(new Character(y)) + + expect(compare('0', '5')).toBeLessThan(0) + expect(compare('o', 'g')).toBeGreaterThan(0) + expect(compare('A', 'a')).toBeLessThan(0) + expect(compare('b', 'b')).toEqual(0) + } + + it("should be a Comparable") { + def compare(x: Any, y: Any): Int = + x.asInstanceOf[Comparable[Any]].compareTo(y) + + expect(compare('0', '5')).toBeLessThan(0) + expect(compare('o', 'g')).toBeGreaterThan(0) + expect(compare('A', 'a')).toBeLessThan(0) + expect(compare('b', 'b')).toEqual(0) + } + + it("should provide isIdentifierIgnorable") { + for (c <- '\u0000' to '\u0008') + expect(Character.isIdentifierIgnorable(c)).toBeTruthy + + for (c <- '\u000E' to '\u001B') + expect(Character.isIdentifierIgnorable(c)).toBeTruthy + + for (c <- '\u007F' to '\u009F') + expect(Character.isIdentifierIgnorable(c)).toBeTruthy + + // Exhaustive list of Cf category. Unicode 7.0.0 + expect(Character.isIdentifierIgnorable('\u00AD')).toBeTruthy + expect(Character.isIdentifierIgnorable('\u0600')).toBeTruthy + expect(Character.isIdentifierIgnorable('\u0601')).toBeTruthy + expect(Character.isIdentifierIgnorable('\u0602')).toBeTruthy + expect(Character.isIdentifierIgnorable('\u0603')).toBeTruthy + expect(Character.isIdentifierIgnorable('\u0604')).toBeTruthy + expect(Character.isIdentifierIgnorable('\u0605')).toBeTruthy + expect(Character.isIdentifierIgnorable('\u061C')).toBeTruthy + expect(Character.isIdentifierIgnorable('\u06DD')).toBeTruthy + expect(Character.isIdentifierIgnorable('\u070F')).toBeTruthy + expect(Character.isIdentifierIgnorable('\u180E')).toBeTruthy + expect(Character.isIdentifierIgnorable('\u200B')).toBeTruthy + expect(Character.isIdentifierIgnorable('\u200C')).toBeTruthy + expect(Character.isIdentifierIgnorable('\u200D')).toBeTruthy + expect(Character.isIdentifierIgnorable('\u200E')).toBeTruthy + expect(Character.isIdentifierIgnorable('\u200F')).toBeTruthy + expect(Character.isIdentifierIgnorable('\u202A')).toBeTruthy + expect(Character.isIdentifierIgnorable('\u202B')).toBeTruthy + expect(Character.isIdentifierIgnorable('\u202C')).toBeTruthy + expect(Character.isIdentifierIgnorable('\u202D')).toBeTruthy + expect(Character.isIdentifierIgnorable('\u202E')).toBeTruthy + expect(Character.isIdentifierIgnorable('\u2060')).toBeTruthy + expect(Character.isIdentifierIgnorable('\u2061')).toBeTruthy + expect(Character.isIdentifierIgnorable('\u2062')).toBeTruthy + expect(Character.isIdentifierIgnorable('\u2063')).toBeTruthy + expect(Character.isIdentifierIgnorable('\u2064')).toBeTruthy + expect(Character.isIdentifierIgnorable('\u2066')).toBeTruthy + expect(Character.isIdentifierIgnorable('\u2067')).toBeTruthy + expect(Character.isIdentifierIgnorable('\u2068')).toBeTruthy + expect(Character.isIdentifierIgnorable('\u2069')).toBeTruthy + expect(Character.isIdentifierIgnorable('\u206A')).toBeTruthy + expect(Character.isIdentifierIgnorable('\u206B')).toBeTruthy + expect(Character.isIdentifierIgnorable('\u206C')).toBeTruthy + expect(Character.isIdentifierIgnorable('\u206D')).toBeTruthy + expect(Character.isIdentifierIgnorable('\u206E')).toBeTruthy + expect(Character.isIdentifierIgnorable('\u206F')).toBeTruthy + expect(Character.isIdentifierIgnorable('\uFEFF')).toBeTruthy + expect(Character.isIdentifierIgnorable('\uFFF9')).toBeTruthy + expect(Character.isIdentifierIgnorable('\uFFFA')).toBeTruthy + expect(Character.isIdentifierIgnorable('\uFFFB')).toBeTruthy + + // BUG in JDK? 17B4 should be "Mn", Java says "Cf" + //expect(Character.isIdentifierIgnorable('\u17b4')).toBeTruthy + + // 100 randomly generated negatives + expect(Character.isIdentifierIgnorable('\u745a')).toBeFalsy + expect(Character.isIdentifierIgnorable('\ub445')).toBeFalsy + expect(Character.isIdentifierIgnorable('\ub23a')).toBeFalsy + expect(Character.isIdentifierIgnorable('\ub029')).toBeFalsy + expect(Character.isIdentifierIgnorable('\ufb5c')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u1b67')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u943b')).toBeFalsy + expect(Character.isIdentifierIgnorable('\ue766')).toBeFalsy + expect(Character.isIdentifierIgnorable('\uad12')).toBeFalsy + expect(Character.isIdentifierIgnorable('\ub80b')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u7341')).toBeFalsy + expect(Character.isIdentifierIgnorable('\ubc73')).toBeFalsy + expect(Character.isIdentifierIgnorable('\uabb9')).toBeFalsy + expect(Character.isIdentifierIgnorable('\ub34b')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u1063')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u272f')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u3801')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u53a6')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u2ec2')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u540c')).toBeFalsy + expect(Character.isIdentifierIgnorable('\uc85f')).toBeFalsy + expect(Character.isIdentifierIgnorable('\ud2c8')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u551b')).toBeFalsy + expect(Character.isIdentifierIgnorable('\uc0a1')).toBeFalsy + expect(Character.isIdentifierIgnorable('\ud25a')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u2b98')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u398b')).toBeFalsy + expect(Character.isIdentifierIgnorable('\ubc77')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u54cc')).toBeFalsy + expect(Character.isIdentifierIgnorable('\uc9a0')).toBeFalsy + expect(Character.isIdentifierIgnorable('\ud10f')).toBeFalsy + expect(Character.isIdentifierIgnorable('\uf7e1')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u0f29')).toBeFalsy + expect(Character.isIdentifierIgnorable('\uafcd')).toBeFalsy + expect(Character.isIdentifierIgnorable('\uf187')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u6287')).toBeFalsy + expect(Character.isIdentifierIgnorable('\uacb6')).toBeFalsy + expect(Character.isIdentifierIgnorable('\uff99')).toBeFalsy + expect(Character.isIdentifierIgnorable('\ub59e')).toBeFalsy + expect(Character.isIdentifierIgnorable('\uf630')).toBeFalsy + expect(Character.isIdentifierIgnorable('\ufaec')).toBeFalsy + expect(Character.isIdentifierIgnorable('\ua7d7')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u3eab')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u54a5')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u393a')).toBeFalsy + expect(Character.isIdentifierIgnorable('\uc621')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u766c')).toBeFalsy + expect(Character.isIdentifierIgnorable('\ud64c')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u8beb')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u44e2')).toBeFalsy + expect(Character.isIdentifierIgnorable('\ub6f6')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u58b6')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u3bad')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u3c28')).toBeFalsy + expect(Character.isIdentifierIgnorable('\ufbfd')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u585f')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u7227')).toBeFalsy + expect(Character.isIdentifierIgnorable('\ucea7')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u2c82')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u686d')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u120d')).toBeFalsy + expect(Character.isIdentifierIgnorable('\uf3db')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u320a')).toBeFalsy + expect(Character.isIdentifierIgnorable('\ud96e')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u85eb')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u9648')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u08a4')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u9db7')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u82c7')).toBeFalsy + expect(Character.isIdentifierIgnorable('\ufe12')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u0eaf')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u96dc')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u3a2a')).toBeFalsy + expect(Character.isIdentifierIgnorable('\uc72e')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u3745')).toBeFalsy + expect(Character.isIdentifierIgnorable('\ubcf9')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u5f66')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u9be1')).toBeFalsy + expect(Character.isIdentifierIgnorable('\ud81d')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u3ca3')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u3e82')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u7ce4')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u33ca')).toBeFalsy + expect(Character.isIdentifierIgnorable('\ue725')).toBeFalsy + expect(Character.isIdentifierIgnorable('\uef49')).toBeFalsy + expect(Character.isIdentifierIgnorable('\ue2cf')).toBeFalsy + expect(Character.isIdentifierIgnorable('\udcf0')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u5f2e')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u2a63')).toBeFalsy + expect(Character.isIdentifierIgnorable('\ud2d2')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u8023')).toBeFalsy + expect(Character.isIdentifierIgnorable('\ua957')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u10ba')).toBeFalsy + expect(Character.isIdentifierIgnorable('\uf85f')).toBeFalsy + expect(Character.isIdentifierIgnorable('\uc40d')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u2509')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u0d8e')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u9db8')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u824d')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u5670')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u6005')).toBeFalsy + expect(Character.isIdentifierIgnorable('\ub8de')).toBeFalsy + expect(Character.isIdentifierIgnorable('\uff5c')).toBeFalsy + expect(Character.isIdentifierIgnorable('\ub36d')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u0cf2')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u82f6')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u9206')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u95e1')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u990f')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u9fc7')).toBeFalsy + expect(Character.isIdentifierIgnorable('\udffb')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u0ecb')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u7563')).toBeFalsy + expect(Character.isIdentifierIgnorable('\uf0ff')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u6b2e')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u894c')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u8f06')).toBeFalsy + expect(Character.isIdentifierIgnorable('\uffa9')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u37b0')).toBeFalsy + expect(Character.isIdentifierIgnorable('\u3e04')).toBeFalsy + + } + + it("should provide isUnicodeIdentifierStart") { + // 100 randomly generated positives and 100 randomly generated negatives + + expect(Character.isUnicodeIdentifierStart('\ud6d5')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u3f9c')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u3a40')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u53af')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u1636')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u4884')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\ucba4')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u1ee4')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u6dec')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u10d4')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u631f')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u3661')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u55f8')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\ub4ef')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\ud509')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u65b5')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u316b')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\ub270')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u7f0f')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\uff84')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u11cc')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u0294')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u51b1')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u9ae2')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u304a')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\ud5c7')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u3b4b')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u5e42')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u51fc')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\uc148')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\uc1ae')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u7372')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\uc116')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u5d29')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u8753')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u50f8')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u3f9d')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u1f44')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\ucd43')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u9126')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u8d2e')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u4f5c')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u66d7')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\ua30b')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u140b')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\ub264')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u7b35')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u15e4')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\ubb37')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u34e3')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\uac3e')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\ubd0e')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\ub641')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u1580')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u30c1')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\ub0c8')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u8681')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u7f14')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u4142')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u56c1')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u0444')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u9964')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\ub5c0')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u43d8')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u479e')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u0853')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\ube08')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u9346')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\uf9c1')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u0e8a')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u212c')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u810c')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u8089')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u1331')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\ua5f7')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u5e5e')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u613b')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u34a7')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\ud15b')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\uc1fc')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u92f1')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u3ae6')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\ufceb')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u7584')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\ufe98')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\ubb23')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u7961')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u4445')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u4d5f')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u61cb')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u5176')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\ub987')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u906a')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u4317')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u93ad')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u825a')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u7ff8')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u533a')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\u5617')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\ufcc6')).toBeTruthy + expect(Character.isUnicodeIdentifierStart('\ue398')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\ueab6')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\ue7bc')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\uf8ab')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\ue27f')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\uebea')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\ueedc')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\uf091')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\u2785')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\u287b')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\uf042')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\u20f9')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\u23d6')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\udc5b')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\ued16')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\u1b6b')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\ue7ba')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\uf7fa')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\u2125')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\uea97')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\ue624')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\ufbb8')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\u2730')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\udb89')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\ue30d')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\u2e24')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\uf03e')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\uda27')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\u28fc')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\u9ffe')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\ude19')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\u0b70')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\uddfc')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\ued53')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\ue8cb')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\udccc')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\u00a3')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\u0bed')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\u0c68')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\uf47b')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\u0f96')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\ue9c3')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\uf784')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\uef4b')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\udee1')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\u2f61')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\uf622')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\u19f9')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\ud86a')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\ued83')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\uf7e4')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\uecce')).toBeFalsy + + // BUG in JDK? A699 should be "Ll", Java says "Cn" + // expect(Character.isUnicodeIdentifierStart('\ua699')).toBeFalsy + + expect(Character.isUnicodeIdentifierStart('\uaa5f')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\udf24')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\u2e0e')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\uf322')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\ue137')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\ued19')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\u21ab')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\ue972')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\udbf2')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\uf54c')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\u4dd3')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\u2769')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\ue363')).toBeFalsy + + // BUG in JDK? 1BBB should be "Lo", Java says "Cn" + // expect(Character.isUnicodeIdentifierStart('\u1bbb')).toBeFalsy + + expect(Character.isUnicodeIdentifierStart('\ueae7')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\u2bf3')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\ue704')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\u1c7f')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\uf52b')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\ue9e3')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\u259b')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\uf250')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\uf42f')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\ue244')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\u20d9')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\ua881')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\u0ee6')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\u2203')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\u0fc7')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\u07fc')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\udb86')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\u2a70')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\u2bb7')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\uecf0')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\ude48')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\u0a3b')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\u20b8')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\uf898')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\u23e6')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\ud8ba')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\uda1e')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\udc12')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\u2a06')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\u0888')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\ud9ec')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\uf81f')).toBeFalsy + expect(Character.isUnicodeIdentifierStart('\uf817')).toBeFalsy + } + + it("should provide isUnicodeIdentifierPart") { + // 100 randomly generated positives and 100 randomly generated negatives + + expect(Character.isUnicodeIdentifierPart('\u48d3')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u0905')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u8f51')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u9bcb')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\ud358')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u1538')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\uffcf')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u83ec')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u3a89')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\ub63a')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\ufe24')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u2d62')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u15ca')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u4fa4')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u47d1')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u831c')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u84e6')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u7783')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\ua03c')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u6ecf')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u147f')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u67a9')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u8b6c')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u3410')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u2cc0')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\ua332')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u9733')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u5df3')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u3fd7')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u6611')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u55b4')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u8bc8')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u6f74')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u6c97')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u6a86')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u6000')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u614f')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u206e')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\ua801')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u9edf')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\ub42c')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u7fcd')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u8a60')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u182f')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u5d0a')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\uaf9c')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u9d4b')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u5088')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\uc1a6')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\ubbe4')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\uad25')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u4653')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u8add')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u3d1c')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u80a8')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u810e')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\uc1d2')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\ub984')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u9d13')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u37c2')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u13cd')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u53f9')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u98b7')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u57f3')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\ub554')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u0176')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\ua318')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u9704')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u8d52')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u940a')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u0fa5')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u38d1')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u3b33')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u93bb')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u03bd')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u4c88')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\ud67d')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\ubcbf')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u3867')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u4368')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u8f2d')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u049a')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u4c01')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u5589')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u5e71')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\ua1fd')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u3a4a')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\uc111')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\ub465')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u95af')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\ubf2c')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u8488')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u4317')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u6b77')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u8995')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u7467')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u16b7')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u3ca0')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u5332')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\u8654')).toBeTruthy + expect(Character.isUnicodeIdentifierPart('\ua8c8')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\ue3ca')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\uebee')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\u270e')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\uf0ac')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\ue9ec')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\u296a')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\u33fd')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\ue5f4')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\ueb01')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\uf38b')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\u2e6f')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\uea69')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\uf155')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\u0f0e')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\ueb80')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\ud959')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\ue25e')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\uf566')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\ue4a3')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\uec44')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\u3297')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\u3214')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\u1bfd')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\u4dd0')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\uea99')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\u309b')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\uf592')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\uf4dd')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\udfaf')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\udd38')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\uf820')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\uaacd')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\uff5b')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\ude36')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\ue33b')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\udbce')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\ue1f6')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\uf78a')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\ueb44')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\uebd4')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\u1df7')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\u2f10')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\u1cbf')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\u2362')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\uebeb')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\u2ede')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\u221d')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\u2021')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\udf41')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\u05f5')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\u24ab')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\uee15')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\uf175')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\uf35c')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\udc7b')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\ud883')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\uf341')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\ueec6')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\u2f57')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\uff64')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\ue6a4')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\uec34')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\u22a5')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\uf5ac')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\u3360')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\u28b0')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\uf678')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\ue0e4')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\u233f')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\u0afa')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\u2013')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\ud7af')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\ud98e')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\ud8a5')).toBeFalsy + + // BUG in JDK? A79E should be "Lu", Java says "Cn" + // expect(Character.isUnicodeIdentifierPart('\ua79e')).toBeFalsy + + expect(Character.isUnicodeIdentifierPart('\u1806')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\ue07a')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\u2748')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\uabad')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\uec5c')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\ue832')).toBeFalsy + + // BUG in JDK? 08A9 should be "Lo", Java says "Cn" + // expect(Character.isUnicodeIdentifierPart('\u08a9')).toBeFalsy + + expect(Character.isUnicodeIdentifierPart('\ue4bd')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\u208a')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\uf840')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\uf570')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\uef1e')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\u2bd4')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\ue385')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\udc18')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\u0af0')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\u244a')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\uf01e')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\uf114')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\ue9c4')).toBeFalsy + + // BUG in JDK? AAF4 should be "Lm", Java says "Cn" + // expect(Character.isUnicodeIdentifierPart('\uaaf4')).toBeFalsy + + expect(Character.isUnicodeIdentifierPart('\uf7b9')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\udd2f')).toBeFalsy + expect(Character.isUnicodeIdentifierPart('\u2d2c')).toBeFalsy + } + + } +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/ClassTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/ClassTest.scala new file mode 100644 index 0000000..424edd6 --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/ClassTest.scala @@ -0,0 +1,30 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.javalib + +import org.scalajs.jasminetest.JasmineTest +import scala.scalajs.js + +object ClassTest extends JasmineTest { + + describe("java.lang.Class") { + + it("should provide getSimpleName()") { + expect(classOf[java.lang.Integer].getSimpleName()).toEqual("Integer") + expect(classOf[java.lang.Class[_]].getSimpleName()).toEqual("Class") + expect(classOf[scala.collection.Map[_, _]].getSimpleName()).toEqual("Map") + expect(classOf[ClassTestClass#InnerClass].getSimpleName()).toEqual("InnerClass") + } + + } + +} + +class ClassTestClass { + class InnerClass +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/DateTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/DateTest.scala new file mode 100644 index 0000000..f62f926 --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/DateTest.scala @@ -0,0 +1,87 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.javalib + +import java.util.Date + +import org.scalajs.jasminetest.JasmineTest + +/** + * tests the implementation of the java standard library Date + */ +object DateTest extends JasmineTest { + + describe("java.lang.Date") { + + it("should provide `compareTo`") { + def compare(x: Date, y: Date): Int = { + x.compareTo(y) + } + + expect(compare(new Date(97, 11, 5, 0, 0), new Date(98, 11, 5, 0, 0))).toBeLessThan(0) + expect(compare(new Date(98, 11, 5, 0, 0), new Date(97, 11, 5, 0, 0))).toBeGreaterThan(0) + expect(compare(new Date(97, 11, 5, 0, 0), new Date(97, 11, 5))).toEqual(0) + expect(compare(new Date(97, 11, 5, 0, 0), new Date(97, 11, 5, 0, 1))).toBeLessThan(0) + expect(compare(new Date(97, 11, 5), new Date(97, 11, 5, 0, 0))).toEqual(0) + } + + it("should be a Comparable") { + def compare(x: Any, y: Any): Int = + x.asInstanceOf[Comparable[Any]].compareTo(y) + + expect(compare(new Date(97, 11, 5, 0, 0), new Date(98, 11, 5, 0, 0))).toBeLessThan(0) + expect(compare(new Date(98, 11, 5, 0, 0), new Date(97, 11, 5, 0, 0))).toBeGreaterThan(0) + expect(compare(new Date(97, 11, 5, 0, 0), new Date(97, 11, 5))).toEqual(0) + expect(compare(new Date(97, 11, 5, 0, 0), new Date(97, 11, 5, 0, 1))).toBeLessThan(0) + expect(compare(new Date(97, 11, 5), new Date(97, 11, 5, 0, 0))).toEqual(0) + } + + it("should parse strings") { + def test(s: String, v: Date): Unit = + expect(new Date(s).compareTo(v)).toEqual(0) + + test("Nov 5 1997 5:23:27 GMT", new Date(Date.UTC(97, 10, 5, 5, 23, 27))) + test("Nov 1 1997 GMT", new Date(Date.UTC(97,10,1, 0, 0, 0))) + test("Jan 1 1970 18:11:01 GMT", new Date(Date.UTC(70,0,1,18,11,1))) + } + + it("should provide after") { + expect(new Date(97, 11, 5, 0, 0).after(new Date(98, 11, 5, 0, 0))).toBe(false) + expect(new Date(99, 11, 5, 0, 0).after(new Date(98, 11, 5, 0, 0))).toBe(true) + expect(new Date(99, 11, 5, 0, 0).after(new Date(99, 11, 5, 0, 0))).toBe(false) + } + + it("should provide before") { + expect(new Date(97, 11, 5, 0, 0).before(new Date(98, 11, 5, 0, 0))).toBe(true) + expect(new Date(99, 11, 5, 0, 0).before(new Date(98, 11, 5, 0, 0))).toBe(false) + expect(new Date(99, 11, 5, 0, 0).before(new Date(99, 11, 5, 0, 0))).toBe(false) + } + + it("should provide clone") { + def testClone(date: Date) = { + val cloned = date.clone() + date == cloned + } + + expect(testClone(new Date(97, 11, 5, 0, 0))).toBe(true) + expect(testClone(new Date(92, 14, 6, 2, 1))).toBe(true) + expect(testClone(new Date(4, 1, 2, 3, 0, 0))).toBe(true) + } + + it("should respond to getYear") { + def testYear(year: Int) = { + val date = new Date() + date.setYear(year) + expect(date.getYear).toEqual(year) + } + testYear(1940) + testYear(1920) + testYear(2030) + } + } +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/DoubleTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/DoubleTest.scala new file mode 100644 index 0000000..59c6e10 --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/DoubleTest.scala @@ -0,0 +1,217 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.javalib + +import org.scalajs.jasminetest.JasmineTest +import scala.scalajs.js + +import java.lang.{Double => JDouble} + +import scala.util.Try + +object DoubleTest extends JasmineTest { + + describe("java.lang.Double") { + + it("should provide proper `equals`") { + expect(Double.box(0.0) == Double.box(-0.0)).toBeTruthy + expect(Double.box(Double.NaN) == Double.box(Double.NaN)).toBeTruthy + } + + it("hashCode") { + def hashCodeNotInlined(x: Any): Int = { + var y = x // do not inline + y.hashCode + } + + def test(x: Double, expected: Int): Unit = { + expect(x.hashCode).toEqual(expected) + expect(hashCodeNotInlined(x)).toEqual(expected) + } + + test(0.0, 0) + test(-0.0, 0) + test(1234.0, 1234) + test(1.5, 1073217536) + test(Math.PI, 340593891) + test(-54.0, -54) + + test(Double.MinPositiveValue, 1) + test(Double.MinValue, 1048576) + test(Double.MaxValue, -2146435072) + + test(Double.NaN, 2146959360) + test(Double.PositiveInfinity, 2146435072) + test(Double.NegativeInfinity, -1048576) + } + + it("should provide `toString` with integer values when an integer") { + expect(0.0.toString).toEqual("0") + expect(-0.0.toString).toEqual("0") + expect(Double.NaN.toString).toEqual("NaN") + expect(Double.PositiveInfinity.toString).toEqual("Infinity") + expect(Double.NegativeInfinity.toString).toEqual("-Infinity") + expect(5.0.toString).toEqual("5") + expect(-5.0.toString).toEqual("-5") + expect(1.2.toString).toEqual("1.2") + } + + it("should parse strings") { + expect("0.0".toDouble).toEqual(0.0f) + expect("NaN".toDouble.isNaN).toBeTruthy + expect(Try("asdf".toDouble).isFailure).toBeTruthy + + def test(s: String, v: Double): Unit = { + expect(JDouble.parseDouble(s)).toBeCloseTo(v) + expect(JDouble.valueOf(s).doubleValue()).toBeCloseTo(v) + expect(new JDouble(s).doubleValue()).toBeCloseTo(v) + } + + test("0", 0.0) + test("5.3", 5.3) + test("127e2", 12700.0) + test("127E-2", 1.27) + test("1E+1", 10) + test("-123.4", -123.4) + test("65432.1", 65432.10) + test("-87654.321", -87654.321) + test("+.3f", 0.3) + } + + it("should reject invalid strings when parsing") { + def test(s: String): Unit = + expect(() => JDouble.parseDouble(s)).toThrow + + test("4.3.5") + test("4e3.5") + test("hello world") + test("--4") + test("4E-3.2") + } + + it("should provide `compareTo`") { + def compare(x: Double, y: Double): Int = + new JDouble(x).compareTo(new JDouble(y)) + + expect(compare(0.0, 5.5)).toBeLessThan(0) + expect(compare(10.5, 10.2)).toBeGreaterThan(0) + expect(compare(-2.1, -1.0)).toBeLessThan(0) + expect(compare(3.14, 3.14)).toEqual(0) + + // From compareTo's point of view, NaN is equal to NaN + expect(compare(Double.NaN, Double.NaN)).toEqual(0) + } + + it("should be a Comparable") { + def compare(x: Any, y: Any): Int = + x.asInstanceOf[Comparable[Any]].compareTo(y) + + expect(compare(0.0, 5.5)).toBeLessThan(0) + expect(compare(10.5, 10.2)).toBeGreaterThan(0) + expect(compare(-2.1, -1.0)).toBeLessThan(0) + expect(compare(3.14, 3.14)).toEqual(0) + + // From compareTo's point of view, NaN is equal to NaN + expect(compare(Double.NaN, Double.NaN)).toEqual(0) + } + + it("should provide isInfinite - #515") { + expect(Double.PositiveInfinity.isInfinite).toBeTruthy + expect(Double.NegativeInfinity.isInfinite).toBeTruthy + expect((1.0/0).isInfinite).toBeTruthy + expect((-1.0/0).isInfinite).toBeTruthy + expect((0.0).isInfinite).toBeFalsy + } + + it("isNaN") { + def f(v: Double): Boolean = { + var v2 = v // do not inline + v2.isNaN + } + + expect(f(Double.NaN)).toBeTruthy + + expect(f(Double.PositiveInfinity)).toBeFalsy + expect(f(Double.NegativeInfinity)).toBeFalsy + expect(f(1.0 / 0)).toBeFalsy + expect(f(-1.0 / 0)).toBeFalsy + expect(f(0.0)).toBeFalsy + expect(f(3.0)).toBeFalsy + expect(f(-1.5)).toBeFalsy + } + + it("longBitsToDouble") { + def isZero(v: Double, neg: Boolean): Boolean = { + (v == 0.0) && (1 / v == ( + if (neg) Double.NegativeInfinity + else Double.PositiveInfinity)) + } + + import JDouble.{longBitsToDouble => f} + + // Specials + expect(f(0x7ff0000000000000L)).toEqual(Double.PositiveInfinity) + expect(f(0xfff0000000000000L)).toEqual(Double.NegativeInfinity) + expect(isZero(f(0x0000000000000000L), false)).toBeTruthy + expect(isZero(f(0x8000000000000000L), true)).toBeTruthy + expect(f(0x7ff8000000000000L).isNaN).toBeTruthy // canonical NaN + + // Non-canonical NaNs + expect(f(0x7ff0000000000001L).isNaN).toBeTruthy // smallest positive NaN + expect(f(0x7ff15ab515d3cca1L).isNaN).toBeTruthy // an arbitrary positive NaN + expect(f(0x7fffffffffffffffL).isNaN).toBeTruthy // largest positive NaN + expect(f(0xfff0000000000001L).isNaN).toBeTruthy // smallest negative NaN + expect(f(0xfff15ab515d3cca1L).isNaN).toBeTruthy // an arbitrary negative NaN + expect(f(0xffffffffffffffffL).isNaN).toBeTruthy // largest negative NaN + + // Normal forms + expect(f(0x0010000000000000L)).toEqual(2.2250738585072014e-308) // smallest pos normal form + expect(f(0x7fefffffffffffffL)).toEqual(1.7976931348623157e308) // largest pos normal form + expect(f(0x4d124568bc6584caL)).toEqual(1.8790766677624813e63) // an arbitrary pos normal form + expect(f(0x8010000000000000L)).toEqual(-2.2250738585072014e-308) // smallest neg normal form + expect(f(0xffefffffffffffffL)).toEqual(-1.7976931348623157e308) // largest neg normal form + expect(f(0xcd124568bc6584caL)).toEqual(-1.8790766677624813e63) // an arbitrary neg normal form + + // Subnormal forms + expect(f(0x0000000000000001L)).toEqual(Double.MinPositiveValue) // smallest pos subnormal form + expect(f(0x000fffffffffffffL)).toEqual(2.225073858507201e-308) // largest pos subnormal form + expect(f(0x000c5d44ae45cb60L)).toEqual(1.719471609939382e-308) // an arbitrary pos subnormal form + expect(f(0x8000000000000001L)).toEqual(-Double.MinPositiveValue) // smallest neg subnormal form + expect(f(0x800fffffffffffffL)).toEqual(-2.225073858507201e-308) // largest neg subnormal form + expect(f(0x800c5d44ae45cb60L)).toEqual(-1.719471609939382e-308) // an arbitrary neg subnormal form + } + + it("doubleToLongBits") { + import JDouble.{doubleToLongBits => f} + + // Specials + expect(f(Double.PositiveInfinity) == 0x7ff0000000000000L).toBeTruthy + expect(f(Double.NegativeInfinity) == 0xfff0000000000000L) + expect(f(0.0) == 0x0000000000000000L).toBeTruthy + expect(f(-0.0) == 0x8000000000000000L).toBeTruthy + expect(f(Double.NaN) == 0x7ff8000000000000L).toBeTruthy // canonical NaN + + // Normal forms + expect(f(2.2250738585072014e-308) == 0x0010000000000000L).toBeTruthy // smallest pos normal form + expect(f(1.7976931348623157e308) == 0x7fefffffffffffffL).toBeTruthy // largest pos normal form + expect(f(1.8790766677624813e63) == 0x4d124568bc6584caL).toBeTruthy // an arbitrary pos normal form + expect(f(-2.2250738585072014e-308) == 0x8010000000000000L).toBeTruthy // smallest neg normal form + expect(f(-1.7976931348623157e308) == 0xffefffffffffffffL).toBeTruthy // largest neg normal form + expect(f(-1.8790766677624813e63) == 0xcd124568bc6584caL).toBeTruthy // an arbitrary neg normal form + + // Subnormal forms + expect(f(Double.MinPositiveValue) == 0x0000000000000001L).toBeTruthy // smallest pos subnormal form + expect(f(2.225073858507201e-308) == 0x000fffffffffffffL).toBeTruthy // largest pos subnormal form + expect(f(1.719471609939382e-308) == 0x000c5d44ae45cb60L).toBeTruthy // an arbitrary pos subnormal form + expect(f(-Double.MinPositiveValue) == 0x8000000000000001L).toBeTruthy // smallest neg subnormal form + expect(f(-2.225073858507201e-308) == 0x800fffffffffffffL).toBeTruthy // largest neg subnormal form + expect(f(-1.719471609939382e-308) == 0x800c5d44ae45cb60L).toBeTruthy // an arbitrary neg subnormal form + } + + } +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/FloatTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/FloatTest.scala new file mode 100644 index 0000000..e45a34a --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/FloatTest.scala @@ -0,0 +1,221 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.javalib + +import org.scalajs.jasminetest.JasmineTest +import scala.scalajs.js + +import java.lang.{Float => JFloat} + +import scala.util.Try + +object FloatTest extends JasmineTest { + + describe("java.lang.Float") { + + it("should provide proper `equals`") { + expect(Float.box(0.0f) == Float.box(-0.0f)).toBeTruthy + expect(Float.box(Float.NaN) == Float.box(Float.NaN)).toBeTruthy + } + + it("hashCode") { + def hashCodeNotInlined(x: Any): Int = { + var y = x // do not inline + y.hashCode + } + + def test(x: Float, expected: Int): Unit = { + expect(x.hashCode).toEqual(expected) + expect(hashCodeNotInlined(x)).toEqual(expected) + } + + test(0.0f, 0) + test(-0.0f, 0) + test(1234.0f, 1234) + test(1.5f, 1073217536) + test(-54f, -54) + + test(Float.MinPositiveValue, 916455424) + test(Float.MinValue, 670040063) + test(Float.MaxValue, -1477443585) + + test(Float.NaN, 2146959360) + test(Float.PositiveInfinity, 2146435072) + test(Float.NegativeInfinity, -1048576) + } + + it("should provide `toString` with integer values when an integer") { + expect(0.0f.toString).toEqual("0") + expect(-0.0f.toString).toEqual("0") + expect(Float.NaN.toString).toEqual("NaN") + expect(Float.PositiveInfinity.toString).toEqual("Infinity") + expect(Float.NegativeInfinity.toString).toEqual("-Infinity") + expect(5.0f.toString).toEqual("5") + expect(-5.0f.toString).toEqual("-5") + + // We need to explicitly cut the string here, since floats are + // represented by doubles (but the literal is emitted as + // float). Therefore there may be some imprecision. This is + // documented as semantic difference. + expect(1.2f.toString.substring(0,3)).toEqual("1.2") + } + + it("should parse strings") { + expect("0.0".toFloat).toEqual(0.0f) + expect("NaN".toFloat.isNaN).toBeTruthy + expect(Try("asdf".toFloat).isFailure).toBeTruthy + + def test(s: String, v: Float): Unit = { + expect(JFloat.parseFloat(s)).toBeCloseTo(v) + expect(JFloat.valueOf(s).floatValue()).toBeCloseTo(v) + expect(new JFloat(s).floatValue()).toBeCloseTo(v) + } + + test("0", 0.0f) + test("5.3", 5.3f) + test("127e2", 12700.0f) + test("127E-2", 1.27f) + test("1E+1", 10f) + test("-123.4", -123.4f) + test("65432.1", 65432.10f) + test("-87654.321", -87654.321f) + test("+.3f", 0.3f) + } + + it("should reject invalid strings when parsing") { + def test(s: String): Unit = + expect(() => JFloat.parseFloat(s)).toThrow + + test("4.3.5") + test("4e3.5") + test("hello world") + test("--4") + test("4E-3.2") + } + + it("should provide `compareTo`") { + def compare(x: Float, y: Float): Int = + new JFloat(x).compareTo(new JFloat(y)) + + expect(compare(0.0f, 5.5f)).toBeLessThan(0) + expect(compare(10.5f, 10.2f)).toBeGreaterThan(0) + expect(compare(-2.1f, -1.0f)).toBeLessThan(0) + expect(compare(3.14f, 3.14f)).toEqual(0) + + // From compareTo's point of view, NaN is equal to NaN + expect(compare(Float.NaN, Float.NaN)).toEqual(0) + } + + it("should be a Comparable") { + def compare(x: Any, y: Any): Int = + x.asInstanceOf[Comparable[Any]].compareTo(y) + + expect(compare(0.0f, 5.5f)).toBeLessThan(0) + expect(compare(10.5f, 10.2f)).toBeGreaterThan(0) + expect(compare(-2.1f, -1.0f)).toBeLessThan(0) + expect(compare(3.14f, 3.14f)).toEqual(0) + + // From compareTo's point of view, NaN is equal to NaN + expect(compare(Float.NaN, Float.NaN)).toEqual(0) + } + + it("should provide isInfinite - #515") { + expect(Float.PositiveInfinity.isInfinite).toBeTruthy + expect(Float.NegativeInfinity.isInfinite).toBeTruthy + expect((1f/0).isInfinite).toBeTruthy + expect((-1f/0).isInfinite).toBeTruthy + expect(0f.isInfinite).toBeFalsy + } + + it("isNaN") { + def f(v: Float): Boolean = { + var v2 = v // do not inline + v2.isNaN + } + + expect(f(Float.NaN)).toBeTruthy + + expect(f(Float.PositiveInfinity)).toBeFalsy + expect(f(Float.NegativeInfinity)).toBeFalsy + expect(f(1f / 0)).toBeFalsy + expect(f(-1f / 0)).toBeFalsy + expect(f(0f)).toBeFalsy + expect(f(3f)).toBeFalsy + expect(f(-1.5f)).toBeFalsy + } + + it("intBitsToFloat") { + def isZero(v: Float, neg: Boolean): Boolean = { + (v == 0.0f) && (1 / v == ( + if (neg) Float.NegativeInfinity + else Float.PositiveInfinity)) + } + + import JFloat.{intBitsToFloat => f} + + // Specials + expect(f(0x7f800000)).toEqual(Float.PositiveInfinity) + expect(f(0xff800000)).toEqual(Float.NegativeInfinity) + expect(isZero(f(0x00000000), false)).toBeTruthy + expect(isZero(f(0x80000000), true)).toBeTruthy + expect(f(0x7fc00000).isNaN).toBeTruthy // canonical NaN + + // Non-canonical NaNs + expect(f(0x7f800001).isNaN).toBeTruthy // smallest positive NaN + expect(f(0x7f915ab5).isNaN).toBeTruthy // an arbitrary positive NaN + expect(f(0x7fffffff).isNaN).toBeTruthy // largest positive NaN + expect(f(0xff800001).isNaN).toBeTruthy // smallest negative NaN + expect(f(0xff915ab5).isNaN).toBeTruthy // an arbitrary negative NaN + expect(f(0xffffffff).isNaN).toBeTruthy // largest negative NaN + + // Normal forms + expect(f(0x00800000)).toEqual(1.17549435e-38f) // smallest pos normal form + expect(f(0x7f7fffff)).toEqual(3.4028234e38f) // largest pos normal form + expect(f(0x4d124568)).toEqual(1.53376384e8f) // an arbitrary pos normal form + expect(f(0x80800000)).toEqual(-1.17549435e-38f) // smallest neg normal form + expect(f(0xff7fffff)).toEqual(-3.4028234e38f) // largest neg normal form + expect(f(0xcd124568)).toEqual(-1.53376384e8f) // an arbitrary neg normal form + + // Subnormal forms + expect(f(0x00000001)).toEqual(Float.MinPositiveValue) // smallest pos subnormal form + expect(f(0x007fffff)).toEqual(1.1754942e-38f) // largest pos subnormal form + expect(f(0x007c5d44)).toEqual(1.1421059e-38f) // an arbitrary pos subnormal form + expect(f(0x80000001)).toEqual(-Float.MinPositiveValue) // smallest neg subnormal form + expect(f(0x807fffff)).toEqual(-1.1754942e-38f) // largest neg subnormal form + expect(f(0x807c5d44)).toEqual(-1.1421059e-38f) // an arbitrary neg subnormal form + } + + it("floatToIntBits") { + import JFloat.{floatToIntBits => f} + + // Specials + expect(f(Float.PositiveInfinity)).toEqual(0x7f800000) + expect(f(Float.NegativeInfinity)).toEqual(0xff800000) + expect(f(0.0f)).toEqual(0x00000000) + expect(f(-0.0f)).toEqual(0x80000000) + expect(f(Float.NaN)).toEqual(0x7fc00000) // canonical NaN + + // Normal forms + expect(f(1.17549435e-38f)).toEqual(0x00800000) // smallest pos normal form + expect(f(3.4028234e38f)).toEqual(0x7f7fffff) // largest pos normal form + expect(f(1.53376384e8f)).toEqual(0x4d124568) // an arbitrary pos normal form + expect(f(-1.17549435e-38f)).toEqual(0x80800000) // smallest neg normal form + expect(f(-3.4028234e38f)).toEqual(0xff7fffff) // largest neg normal form + expect(f(-1.53376384e8f)).toEqual(0xcd124568) // an arbitrary neg normal form + + // Subnormal forms + expect(f(Float.MinPositiveValue)).toEqual(0x00000001) // smallest pos subnormal form + expect(f(1.1754942e-38f)).toEqual(0x007fffff) // largest pos subnormal form + expect(f(1.1421059e-38f)).toEqual(0x007c5d44) // an arbitrary pos subnormal form + expect(f(-Float.MinPositiveValue)).toEqual(0x80000001) // smallest neg subnormal form + expect(f(-1.1754942e-38f)).toEqual(0x807fffff) // largest neg subnormal form + expect(f(-1.1421059e-38f)).toEqual(0x807c5d44) // an arbitrary neg subnormal form + } + + } +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/FormatterTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/FormatterTest.scala new file mode 100644 index 0000000..e7a705c --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/FormatterTest.scala @@ -0,0 +1,241 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.javalib + +import scala.scalajs.js +import org.scalajs.jasminetest.JasmineTest + +import java.util.{ Formatter, Formattable, FormattableFlags } + +import java.lang.{ + Double => JDouble, + Float => JFloat, + Integer => JInteger, + Long => JLong, + Byte => JByte, + Short => JShort, + Boolean => JBoolean, + String => JString +} + + +object FormatterTest extends JasmineTest { + + class HelperClass + class FormattableClass extends Formattable { + var frm: Formatter = _ + var flags: Int = _ + var width: Int = _ + var precision: Int = _ + var calls = 0 + def formatTo(frm: Formatter, flags: Int, width: Int, precision: Int) = { + this.calls += 1 + this.flags = flags + this.width = width + this.precision = precision + frm.out().append("foobar") + } + + def expectCalled(times: Int, flags: Int, width: Int, precision: Int) = { + expect(this.calls).toEqual(times) + expect(this.flags).toEqual(flags) + expect(this.width).toEqual(width) + expect(this.precision).toEqual(precision) + } + + } + + def expectF(format: String, args: AnyRef*) = { + val fmt = new Formatter() + val res = fmt.format(format, args:_*).toString() + fmt.close() + expect(res) + } + + def expectFC(format: String, flags: Int, width: Int, precision: Int) = { + val fc = new FormattableClass + val exp = expectF(format, fc) + fc.expectCalled(1, flags, width, precision) + exp + } + + def expectThrow(format: String, args: AnyRef*) = { + val fmt = new Formatter() + expect(() => fmt.format(format, args:_*)).toThrow + } + + describe("java.util.Formatter") { + + // Explicitly define these as `var`'s to avoid any compile-time constant folding + var IntMax: Int = Int.MaxValue + var IntMin: Int = Int.MinValue + var ByteMax: Byte = Byte.MaxValue + var ByteMin: Byte = Byte.MinValue + var ShortMax: Short = Short.MaxValue + var ShortMin: Short = Short.MinValue + + it("should provide 'b' conversion") { + expectF("%b", null).toEqual("false") + expectF("%b", true: JBoolean).toEqual(JString.valueOf(true)) + expectF("%b", false: JBoolean).toEqual(JString.valueOf(false)) + expectF("%b", new HelperClass).toEqual("true") + } + + it("should provide 'h' conversion") { + val x = new HelperClass + expectF("%h", x).toEqual(Integer.toHexString(x.hashCode())) + expectF("%H", x).toEqual(Integer.toHexString(x.hashCode()).toUpperCase()) + expectF("%h", null).toEqual("null") + } + + it("should provide 's' conversion") { + expectFC("%s", 0, -1, -1).toEqual("foobar") + expectFC("%-s", FormattableFlags.LEFT_JUSTIFY, -1, -1).toEqual("foobar") + expectFC("%-10s", FormattableFlags.LEFT_JUSTIFY, 10, -1).toEqual("foobar") + expectFC("%#-10.2s", FormattableFlags.LEFT_JUSTIFY | + FormattableFlags.ALTERNATE, 10, 2).toEqual("foobar") + expectFC("%#10.2S", FormattableFlags.UPPERCASE | + FormattableFlags.ALTERNATE, 10, 2).toEqual("foobar") + expectF("%10s", "hello").toEqual(" hello") + expectF("%-10s", "hello").toEqual("hello ") + expectThrow("%#s", "hello") + } + + it("should provide 'c' conversion") { + expectF("%-5c", new Character('!')).toEqual("! ") + } + + it("should provide 'd' conversion") { + expectF("%d", new Integer(5)).toEqual("5") + expectF("%05d", new Integer(5)).toEqual("00005") + expectF("%5d", new Integer(-10)).toEqual(" -10") + expectF("%05d", new Integer(-10)).toEqual("-0010") + } + + it("should provide 'o' conversion") { + expectF("%o", new JInteger(8)).toEqual("10") + expectF("%05o", new JInteger(16)).toEqual("00020") + expectF("%5o", new JInteger(-10)).toEqual("37777777766") + expectF("%05o", new JInteger(-10)).toEqual("37777777766") + expectF("%o", new JByte(8.toByte)).toEqual("10") + expectF("%05o", new JByte(16.toByte)).toEqual("00020") + expectF("%14o", new JByte(-10.toByte)).toEqual(" 37777777766") + expectF("%05o", new JByte(-10.toByte)).toEqual("37777777766") + expectF("%o", new JShort(8.toShort)).toEqual("10") + expectF("%05o", new JShort(16.toShort)).toEqual("00020") + expectF("%5o", new JShort(-10.toShort)).toEqual("37777777766") + expectF("%015o",new JShort(-10.toShort)).toEqual("000037777777766") + expectF("%05o", new JLong(-5L)).toEqual("1777777777777777777773") + } + + it("should provide 'x' conversion") { + expectF("%0#5x", new JInteger(5)).toEqual("0x005") + expectF("%#5x", new JInteger(5)).toEqual(" 0x5") + expectF("%#5X", new JInteger(5)).toEqual(" 0X5") + expectF("%x", new JInteger(-3)).toEqual("fffffffd") + expectF("%x", new JByte(-4.toByte)).toEqual("fffffffc") + expectF("%0#5x", new JByte(5.toByte)).toEqual("0x005") + expectF("%#5x", new JByte(5.toByte)).toEqual(" 0x5") + expectF("%#5X", new JByte(5.toByte)).toEqual(" 0X5") + expectF("%x", new JByte(-3.toByte)).toEqual("fffffffd") + expectF("%0#5x", new JShort(5.toShort)).toEqual("0x005") + expectF("%#5x", new JShort(5.toShort)).toEqual(" 0x5") + expectF("%#5X", new JShort(5.toShort)).toEqual(" 0X5") + expectF("%x", new JShort(-3.toShort)).toEqual("fffffffd") + expectF("%x", new JLong(-5L)).toEqual("fffffffffffffffb") + expectF("%X", new JLong(26L)).toEqual("1A") + } + + it("should provide 'e' conversion") { + expectF("%e", new JDouble(1000)).toEqual("1.000000e+03") + expectF("%.0e", new JDouble(1.2e100)).toEqual("1e+100") + // We use 1.51e100 in this test, since we seem to have a floating + // imprecision at exactly 1.5e100 that yields to a rounding error + // towards (1e+100 instead of 2e+100) + expectF("%.0e", new JDouble(1.51e100)).toEqual("2e+100") + expectF("%10.2e", new JDouble(1.2e100)).toEqual(" 1.20e+100") + expectF("%012.4e", new JFloat(1.2e-21f)).toEqual("001.2000e-21") + expectF("%012.4E", new JFloat(1.2e-21f)).toEqual("001.2000E-21") + expectF("%(015.4e", new JFloat(-1.2e-21f)).toEqual("(0001.2000e-21)") + + // Tests with infinity and NaN + expectF("%e", new JDouble(Double.PositiveInfinity)).toEqual("Infinity") + expectF("%e", new JDouble(Double.NegativeInfinity)).toEqual("-Infinity") + expectF("%010e", new JDouble(Double.PositiveInfinity)).toEqual(" Infinity") + expectF("%-10e", new JDouble(Double.PositiveInfinity)).toEqual("Infinity ") + expectF("%(e", new JDouble(Double.NegativeInfinity)).toEqual("(Infinity)") + expectF("%010e", new JDouble(Double.NaN)).toEqual(" NaN") + } + + it("should provide 'g' conversion") { + expectF("%g", new JDouble(.5e-4)).toEqual("5.00000e-05") + expectF("%g", new JDouble(3e-4)).toEqual("0.000300000") + expectF("%.3g", new JDouble(3e-4)).toEqual("0.000300") + expectF("%.2g", new JDouble(1e-3)).toEqual("0.0010") + expectF("%g", new JDouble(3e5)).toEqual("300000") + expectF("%.3g", new JDouble(3e5)).toEqual("3.00e+05") + expectF("%04g", new JDouble(Double.NaN)).toEqual(" NaN") + } + + it("should provide 'f' conversion") { + expectF("%f", new JDouble(3.3)).toEqual("3.300000") + expectF("%0(9.4f", new JDouble(-4.6)).toEqual("(04.6000)") + expectF("%f", new JFloat(3e10f)).toEqual("30000001024.000000") + expectF("%f", new JDouble(3e10)).toEqual("30000000000.000000") + expectF("%04f", new JDouble(Double.NaN)).toEqual(" NaN") + } + + it("should support '%%'") { + expectF("%d%%%d", new JInteger(1), new JInteger(2)).toEqual("1%2") + } + + it("should support '%n'") { + expectF("%d%n%d", new JInteger(1), new JInteger(2)).toEqual("1\n2") + } + + it("should survive `null` and `undefined`") { + expectF("%s", null).toEqual("null") + expectF("%s", js.undefined).toEqual("undefined") + } + + it("should allow 'f' string interpolation to survive `null` and `undefined`") { + expect(f"${null}%s").toEqual("null") + expect(f"${js.undefined}%s").toEqual("undefined") + } + + it("should allow positional arguments") { + expectF("%2$d %1$d", new JInteger(1), new JInteger(2)).toEqual("2 1") + expectF("%2$d %2$d %d", new JInteger(1), new JInteger(2)).toEqual("2 2 1") + expectF("%2$d %<d %d", new JInteger(1), new JInteger(2)).toEqual("2 2 1") + } + + it("should fail when called after close") { + val f = new Formatter() + f.close() + expect(() => f.toString()).toThrow + } + + it("should fail with bad format specifier") { + expectThrow("hello world%") + expectThrow("%%%") + expectThrow("%q") + expectThrow("%1") + expectThrow("%_f") + } + + it("should fail with not enough arguments") { + expectThrow("%f") + expectThrow("%d%d%d", new JInteger(1), new JInteger(1)) + expectThrow("%10$d", new JInteger(1)) + } + + } + + +} + diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/IntegerTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/IntegerTest.scala new file mode 100644 index 0000000..5a01de4 --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/IntegerTest.scala @@ -0,0 +1,161 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.javalib + +import org.scalajs.jasminetest.JasmineTest +import scala.scalajs.js + +object IntegerTest extends JasmineTest { + + describe("java.lang.Integer") { + + // Explicitly define these as `var`'s to avoid any compile-time constant folding + var MaxValue: Int = Int.MaxValue + var MinValue: Int = Int.MinValue + + it("should provide `reverseBytes` used by scala.Enumeration") { + expect(Integer.reverseBytes(0xdeadbeef)).toEqual(0xefbeadde) + } + + it("should provide `rotateLeft`") { + expect(Integer.rotateLeft(0x689cd401, 0)).toEqual(0x689cd401) + expect(Integer.rotateLeft(0x689cd401, 1)).toEqual(0xd139a802) + expect(Integer.rotateLeft(0x689cd401, 8)).toEqual(0x9cd40168) + expect(Integer.rotateLeft(0x689cd401, 13)).toEqual(0x9a802d13) + expect(Integer.rotateLeft(0x689cd401, 32)).toEqual(0x689cd401) + expect(Integer.rotateLeft(0x689cd401, 33)).toEqual(0xd139a802) + expect(Integer.rotateLeft(0x689cd401, 43)).toEqual(0xe6a00b44) + expect(Integer.rotateLeft(0x689cd401, -1)).toEqual(0xb44e6a00) + expect(Integer.rotateLeft(0x689cd401, -28)).toEqual(0x89cd4016) + expect(Integer.rotateLeft(0x689cd401, -39)).toEqual(0x2d139a8) + } + + it("should provide `rotateRight`") { + expect(Integer.rotateRight(0x689cd401, 0)).toEqual(0x689cd401) + expect(Integer.rotateRight(0x689cd401, 1)).toEqual(0xb44e6a00) + expect(Integer.rotateRight(0x689cd401, 8)).toEqual(0x1689cd4) + expect(Integer.rotateRight(0x689cd401, 13)).toEqual(0xa00b44e6) + expect(Integer.rotateRight(0x689cd401, 32)).toEqual(0x689cd401) + expect(Integer.rotateRight(0x689cd401, 33)).toEqual(0xb44e6a00) + expect(Integer.rotateRight(0x689cd401, 43)).toEqual(0x802d139a) + expect(Integer.rotateRight(0x689cd401, -1)).toEqual(0xd139a802) + expect(Integer.rotateRight(0x689cd401, -28)).toEqual(0x1689cd40) + expect(Integer.rotateRight(0x689cd401, -39)).toEqual(0x4e6a00b4) + } + + it("should provide `bitCount` used by Map") { + abstract sealed class Status + case object Used extends Status + case object Current extends Status + case object OneMove extends Status + case object MultipleMoves extends Status + case object Other extends Status + + val map = Map(Used -> 0, Other -> 0, Current -> 0, MultipleMoves -> 1, OneMove -> 2) + + expect(map.size).toEqual(5) + expect(map(MultipleMoves)).toEqual(1) + } + + it("should provide `numberOfTrailingZeros`") { + expect(Integer.numberOfTrailingZeros(0xa3c49000)).toEqual(12) + expect(Integer.numberOfTrailingZeros(0x43f49020)).toEqual(5) + expect(Integer.numberOfTrailingZeros(0x43c08000)).toEqual(15) + expect(Integer.numberOfTrailingZeros(0)).toEqual(32) + } + + it("should provide `toBinaryString` for values in range") { + expect(Integer.toBinaryString(-1)).toEqual("11111111111111111111111111111111") + expect(Integer.toBinaryString(-10001)).toEqual("11111111111111111101100011101111") + expect(Integer.toBinaryString(MinValue)).toEqual("10000000000000000000000000000000") + expect(Integer.toBinaryString(MaxValue)).toEqual("1111111111111111111111111111111") + } + + it("should provide `toHexString` for values in range") { + expect(Integer.toHexString(-1)).toEqual("ffffffff") + expect(Integer.toHexString(-10001)).toEqual("ffffd8ef") + expect(Integer.toHexString(MinValue)).toEqual("80000000") + expect(Integer.toHexString(-2147000002)).toEqual("8007613e") + expect(Integer.toHexString(MaxValue)).toEqual("7fffffff") + } + + it("should provide `toOctalString` for values in range") { + expect(Integer.toOctalString(-1)).toEqual("37777777777") + expect(Integer.toOctalString(-10001)).toEqual("37777754357") + expect(Integer.toOctalString(MinValue)).toEqual("20000000000") + expect(Integer.toOctalString(MaxValue)).toEqual("17777777777") + } + + it("should provide `compareTo`") { + def compare(x: Int, y: Int): Int = + new Integer(x).compareTo(new Integer(y)) + + expect(compare(0, 5)).toBeLessThan(0) + expect(compare(10, 9)).toBeGreaterThan(0) + expect(compare(-2, -1)).toBeLessThan(0) + expect(compare(3, 3)).toEqual(0) + } + + it("should be a Comparable") { + def compare(x: Any, y: Any): Int = + x.asInstanceOf[Comparable[Any]].compareTo(y) + + expect(compare(0, 5)).toBeLessThan(0) + expect(compare(10, 9)).toBeGreaterThan(0) + expect(compare(-2, -1)).toBeLessThan(0) + expect(compare(3, 3)).toEqual(0) + } + + it("should parse strings") { + def test(s: String, v: Int, radix: Int = 10): Unit = { + expect(Integer.parseInt(s, radix)).toEqual(v) + expect(Integer.valueOf(s, radix).intValue()).toEqual(v) + if (radix == 10) + expect(new Integer(s).intValue()).toEqual(v) + } + + test("0", 0) + test("5", 5) + test("127", 127) + test("-100", -100) + test("30000", 30000) + test("-90000", -90000) + test("Kona", 411787, 27) + test("+42", 42) + test("-0", 0) + test("-FF", -255, 16) + } + + it("should reject invalid strings when parsing") { + def test(s: String, radix: Int = 10): Unit = + expect(() => Integer.parseInt(s, radix)).toThrow + + test("abc") + test("5a") + test("2147483648") + test("99", 8) + test("-") + test("") + } + + it("should parse strings in base 16") { + def test(s: String, v: Int): Unit = { + expect(Integer.parseInt(s, 16)).toEqual(v) + expect(Integer.valueOf(s, 16).intValue()).toEqual(v) + } + + test("0", 0x0) + test("5", 0x5) + test("ff", 0xff) + test("-24", -0x24) + test("30000", 0x30000) + test("-90000", -0x90000) + } + + } +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/LongTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/LongTest.scala new file mode 100644 index 0000000..86783c3 --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/LongTest.scala @@ -0,0 +1,178 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.javalib + +import java.lang.{Long => JLong} + +import org.scalajs.jasminetest.JasmineTest + +/** + * tests the implementation of the java standard library Long + * requires jsinterop/LongTest to work to make sense + */ +object LongTest extends JasmineTest { + + describe("java.lang.Long") { + it("should provide `reverseBytes`") { + expect(JLong.reverseBytes(0xf5ab689cd401ff14L) == 0x14ff01d49c68abf5L).toBeTruthy + } + + it("should provide `rotateLeft`") { + expect(JLong.rotateLeft(0xf5ab689cd401ff14L, 0) == 0xf5ab689cd401ff14L).toBeTruthy + expect(JLong.rotateLeft(0xf5ab689cd401ff14L, 1) == 0xeb56d139a803fe29L).toBeTruthy + expect(JLong.rotateLeft(0xf5ab689cd401ff14L, 8) == 0xab689cd401ff14f5L).toBeTruthy + expect(JLong.rotateLeft(0xf5ab689cd401ff14L, 13) == 0x6d139a803fe29eb5L).toBeTruthy + expect(JLong.rotateLeft(0xf5ab689cd401ff14L, 64) == 0xf5ab689cd401ff14L).toBeTruthy + expect(JLong.rotateLeft(0xf5ab689cd401ff14L, 65) == 0xeb56d139a803fe29L).toBeTruthy + expect(JLong.rotateLeft(0xf5ab689cd401ff14L, 80) == 0x689cd401ff14f5abL).toBeTruthy + expect(JLong.rotateLeft(0xf5ab689cd401ff14L, -1) == 0x7ad5b44e6a00ff8aL).toBeTruthy + expect(JLong.rotateLeft(0xf5ab689cd401ff14L, -56) == 0xab689cd401ff14f5L).toBeTruthy + expect(JLong.rotateLeft(0xf5ab689cd401ff14L, -70) == 0x53d6ada2735007fcL).toBeTruthy + } + + it("should provide `rotateRight`") { + expect(JLong.rotateRight(0xf5ab689cd401ff14L, 0) == 0xf5ab689cd401ff14L).toBeTruthy + expect(JLong.rotateRight(0xf5ab689cd401ff14L, 1) == 0x7ad5b44e6a00ff8aL).toBeTruthy + expect(JLong.rotateRight(0xf5ab689cd401ff14L, 8) == 0x14f5ab689cd401ffL).toBeTruthy + expect(JLong.rotateRight(0xf5ab689cd401ff14L, 13) == 0xf8a7ad5b44e6a00fL).toBeTruthy + expect(JLong.rotateRight(0xf5ab689cd401ff14L, 64) == 0xf5ab689cd401ff14L).toBeTruthy + expect(JLong.rotateRight(0xf5ab689cd401ff14L, 65) == 0x7ad5b44e6a00ff8aL).toBeTruthy + expect(JLong.rotateRight(0xf5ab689cd401ff14L, 80) == 0xff14f5ab689cd401L).toBeTruthy + expect(JLong.rotateRight(0xf5ab689cd401ff14L, -1) == 0xeb56d139a803fe29L).toBeTruthy + expect(JLong.rotateRight(0xf5ab689cd401ff14L, -56) == 0x14f5ab689cd401ffL).toBeTruthy + expect(JLong.rotateRight(0xf5ab689cd401ff14L, -70) == 0x6ada2735007fc53dL).toBeTruthy + } + + it("should implement bitCount") { + expect(JLong.bitCount(0L)).toEqual(0) + expect(JLong.bitCount(35763829229342837L)).toEqual(26) + expect(JLong.bitCount(-350003829229342837L)).toEqual(32) + } + + it("should provide `compareTo`") { + def compare(x: Long, y: Long): Int = + new JLong(x).compareTo(new JLong(y)) + + expect(compare(0L, 5L)).toBeLessThan(0) + expect(compare(10L, 9L)).toBeGreaterThan(0) + expect(compare(-2L, -1L)).toBeLessThan(0) + expect(compare(3L, 3L)).toEqual(0) + } + + it("should be a Comparable") { + def compare(x: Any, y: Any): Int = + x.asInstanceOf[Comparable[Any]].compareTo(y) + + expect(compare(0L, 5L)).toBeLessThan(0) + expect(compare(10L, 9L)).toBeGreaterThan(0) + expect(compare(-2L, -1L)).toBeLessThan(0) + expect(compare(3L, 3L)).toEqual(0) + } + + it("should parse strings") { + def test(s: String, v: Long): Unit = { + expect(JLong.parseLong(s)).toEqual(v) + expect(JLong.valueOf(s).longValue()).toEqual(v) + expect(new JLong(s).longValue()).toEqual(v) + } + + test("0", 0L) + test("5", 5L) + test("127", 127L) + test("-100", -100L) + test("30000", 30000L) + test("-90000", -90000L) + test("4", 4L) + test("-4", -4L) + test("4000000000", 4000000000L) + test("-18014398509482040", -18014398509482040L) + } + + it("should reject invalid strings when parsing") { + def test(s: String): Unit = + expect(() => JLong.parseLong(s)).toThrow + + test("abc") + test("asdf") + test("") + } + + it("should parse strings in base 16") { + def test(s: String, v: Long): Unit = { + expect(JLong.parseLong(s, 16)).toEqual(v) + expect(JLong.valueOf(s, 16).longValue()).toEqual(v) + } + + test("0", 0x0L) + test("5", 0x5L) + test("ff", 0xffL) + test("-24", -0x24L) + test("30000", 0x30000L) + test("-90000", -0x90000L) + } + + it("should implement toString") { + expect(Int.MaxValue.toLong.toString).toEqual("2147483647") + expect((-50L).toString).toEqual("-50") + expect((-1000000000L).toString).toEqual("-1000000000") + expect((Int.MaxValue.toLong+1L).toString).toEqual("2147483648") + expect(Int.MinValue.toLong.toString).toEqual("-2147483648") + } + + it("should implement toBinaryString") { + expect(JLong.toBinaryString( 0L)).toEqual("0") + expect(JLong.toBinaryString( -1L)).toEqual("1111111111111111111111111111111111111111111111111111111111111111") + expect(JLong.toBinaryString( 456324454L)).toEqual("11011001100101111010101100110") + expect(JLong.toBinaryString( -456324454L)).toEqual("1111111111111111111111111111111111100100110011010000101010011010") + expect(JLong.toBinaryString( 98765432158845L)).toEqual("10110011101001110011110011111111111101001111101") + expect(JLong.toBinaryString(-49575304457780L)).toEqual("1111111111111111110100101110100101011001100101101001000111001100") + expect(JLong.toBinaryString(Long.MinValue )).toEqual("1000000000000000000000000000000000000000000000000000000000000000") + expect(JLong.toBinaryString(Long.MaxValue )).toEqual("111111111111111111111111111111111111111111111111111111111111111") + } + + it("should implement toHexString") { + expect(JLong.toHexString( 0L)).toEqual("0") + expect(JLong.toHexString( -1L)).toEqual("ffffffffffffffff") + expect(JLong.toHexString( 456324454L)).toEqual("1b32f566") + expect(JLong.toHexString( -456324454L)).toEqual("ffffffffe4cd0a9a") + expect(JLong.toHexString( 98765432158845L)).toEqual("59d39e7ffa7d") + expect(JLong.toHexString(-49575304457780L)).toEqual("ffffd2e9599691cc") + expect(JLong.toHexString(Long.MinValue )).toEqual("8000000000000000") + expect(JLong.toHexString(Long.MaxValue )).toEqual("7fffffffffffffff") + } + + it("should implement toOctalString") { + expect(JLong.toOctalString( 0L)).toEqual("0") + expect(JLong.toOctalString( -1L)).toEqual("1777777777777777777777") + expect(JLong.toOctalString( 456324454L)).toEqual("3314572546") + expect(JLong.toOctalString( -456324454L)).toEqual("1777777777774463205232") + expect(JLong.toOctalString( 98765432158845L)).toEqual("2635163637775175") + expect(JLong.toOctalString(-49575304457780L)).toEqual("1777776456453145510714") + expect(JLong.toOctalString(Long.MinValue )).toEqual("1000000000000000000000") + expect(JLong.toOctalString(Long.MaxValue )).toEqual("777777777777777777777") + } + + it("should correctly compute trailing zeros") { + expect(JLong.numberOfTrailingZeros(0xff10000000000000L)).toEqual(52) + expect(JLong.numberOfTrailingZeros(0xff20000000000000L)).toEqual(53) + expect(JLong.numberOfTrailingZeros(0xff40000000000000L)).toEqual(54) + expect(JLong.numberOfTrailingZeros(0xff80000000000000L)).toEqual(55) + + expect(JLong.numberOfTrailingZeros(0x0000010000000000L)).toEqual(40) + expect(JLong.numberOfTrailingZeros(0x0000020000000000L)).toEqual(41) + expect(JLong.numberOfTrailingZeros(0x0000040000000000L)).toEqual(42) + expect(JLong.numberOfTrailingZeros(0x0000080000000000L)).toEqual(43) + + expect(JLong.numberOfTrailingZeros(0x0000000000010000L)).toEqual(16) + expect(JLong.numberOfTrailingZeros(0x0000000000020000L)).toEqual(17) + expect(JLong.numberOfTrailingZeros(0x0000000000040000L)).toEqual(18) + expect(JLong.numberOfTrailingZeros(0x0000000000080000L)).toEqual(19) + } + + } +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/MathTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/MathTest.scala new file mode 100644 index 0000000..a3c887a --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/MathTest.scala @@ -0,0 +1,142 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.javalib + +import scala.scalajs.js +import org.scalajs.jasminetest.JasmineTest +import java.lang.Math + +object MathTest extends JasmineTest { + + describe("java.lang.Math") { + + it("should respond to `cbrt`") { + expect(1 / Math.cbrt(-0.0) < 0).toBeTruthy + expect(Math.cbrt(27.0)).toEqual(3.0) + expect(Math.cbrt(1000000.0)).toEqual(100.0) + expect(Math.cbrt(1000000000.0)).toEqual(1000.0) + expect(Math.cbrt(-1.0E24)).toEqual(-100000000.0) + expect(Math.cbrt(-65890311319.0E24)).toEqual(-4039.0E8) + } + + it("should respond to `log1p`") { + expect(Math.log1p(-2.0).isNaN).toBeTruthy + expect(Math.log1p(js.Number.NaN.toDouble).isNaN).toBeTruthy + expect(Math.log1p(0.0)).toEqual(0.0) + } + + it("should respond to `log10`") { + expect(Math.log10(-230.0).isNaN).toBeTruthy + expect(Math.log10(js.Number.NaN.toDouble).isNaN).toBeTruthy + } + + it("should respond to `signum` for Double") { + expect(Math.signum(234394.2198273)).toEqual(1.0) + expect(Math.signum(-124937498.58)).toEqual(-1.0) + + expect(Math.signum(+0.0)).toEqual(0.0) + expect(1 / Math.signum(+0.0) > 0).toBeTruthy + + expect(Math.signum(-0.0)).toEqual(-0.0) + expect(1 / Math.signum(-0.0) < 0).toBeTruthy + + expect(Math.signum(js.Number.NaN.toDouble).isNaN).toBeTruthy + } + + it("should respond to `signum` for Float") { + expect(Math.signum(234394.2198273f)).toEqual(1.0f) + expect(Math.signum(-124937498.58f)).toEqual(-1.0f) + + expect(Math.signum(+0.0f)).toEqual(0.0f) + expect(1 / Math.signum(+0.0f) > 0).toBeTruthy + + expect(Math.signum(-0.0f)).toEqual(-0.0f) + expect(1 / Math.signum(-0.0f) < 0).toBeTruthy + + expect(Math.signum(js.Number.NaN.toFloat).isNaN).toBeTruthy + } + + it("should respond to `nextUp` for Double") { + expect(Math.nextUp(Double.PositiveInfinity)).toEqual(Double.PositiveInfinity) + expect(Math.nextUp(Double.NegativeInfinity)).toEqual(-Double.MaxValue) + expect(Math.nextUp(Double.MaxValue)).toEqual(Double.PositiveInfinity) + expect(Math.nextUp(-Double.MaxValue)).toEqual(-1.7976931348623155e+308) + expect(Math.nextUp(-Double.MinValue)).toEqual(Double.PositiveInfinity) + expect(Math.nextUp(0.0)).toEqual(Double.MinValue) + expect(Math.nextUp(-0.0)).toEqual(Double.MinValue) + expect(Math.nextUp(9007199254740991.0)).toEqual(9007199254740992.0) + expect(Math.nextUp(9007199254740992.0)).toEqual(9007199254740994.0) + expect(Math.nextUp(1.0)).toEqual(1 + 2.2204460492503130808472633361816E-16) + } + + it("should respond to `nextAfter` for Double") { + expect(Math.nextAfter(1.0, js.Number.NaN.toDouble).isNaN).toBeTruthy + expect(Math.nextAfter(js.Number.NaN.toDouble, 1.0).isNaN).toBeTruthy + expect(Math.nextAfter(0.0, 0.0)).toEqual(0.0) + expect(Math.nextAfter(0.0, -0.0)).toEqual(-0.0) + expect(Math.nextAfter(-0.0, 0.0)).toEqual(0.0) + expect(Math.nextAfter(-0.0, -0.0)).toEqual(-0.0) + expect(Math.nextAfter(Double.MinValue, Double.NegativeInfinity)).toEqual(Double.NegativeInfinity) + expect(Math.nextAfter(-Double.MinValue, Double.PositiveInfinity)).toEqual(Double.PositiveInfinity) + expect(Math.nextAfter(Double.PositiveInfinity, Double.NegativeInfinity)).toEqual(Double.MaxValue) + expect(Math.nextAfter(Double.NegativeInfinity, Double.PositiveInfinity)).toEqual(-Double.MaxValue) + expect(Math.nextAfter(Double.MaxValue, Double.PositiveInfinity)).toEqual(Double.PositiveInfinity) + expect(Math.nextAfter(-Double.MaxValue, Double.NegativeInfinity)).toEqual(Double.NegativeInfinity) + expect(Math.nextAfter(1.0, 1.0)).toEqual(1.0) + } + + it("should respond to `ulp` for Double") { + expect(Math.ulp(3.4)).toEqual(4.440892098500626E-16) + expect(Math.ulp(3.423E109)).toEqual(4.1718496795330275E93) + expect(Math.ulp(0.0)).toEqual(Double.MinValue) + } + + it("should respond to `hypot`") { + expect(Math.hypot(0.0, 0.0)).toBeCloseTo(0.0) + expect(Math.hypot(3.0, 4.0)).toBeCloseTo(5.0) + expect(Math.hypot(3.0, js.Number.NaN.toDouble).isNaN).toBeTruthy + expect(Math.hypot(Double.NegativeInfinity, 4.0)).toEqual(Double.PositiveInfinity) + } + + it("should respond to `expm1`") { + expect(1 / Math.expm1(-0.0) < 0).toBeTruthy + expect(Math.expm1(-0.0)).toBeCloseTo(0.0) + expect(Math.expm1(3.0)).toBeCloseTo(19.085536923187668) + expect(Math.expm1(15.0)).toBeCloseTo(3269016.3724721107) + expect(Math.expm1(1.8E10)).toEqual(Double.PositiveInfinity) + expect(Math.expm1(Double.PositiveInfinity)).toEqual(Double.PositiveInfinity) + expect(Math.expm1(Double.NegativeInfinity)).toBeCloseTo(-1.0) + expect(Math.expm1(4.9E-324)).toBeCloseTo(4.9E-324) + } + + it("should respond to `sinh`") { + expect(Math.sinh(-1234.56)).toEqual(Double.NegativeInfinity) + expect(Math.sinh(1234.56)).toEqual(Double.PositiveInfinity) + expect(Math.sinh(0.0)).toBeCloseTo(0.0) + expect(Math.sinh(Double.PositiveInfinity)).toEqual(Double.PositiveInfinity) + } + + it("should respond to `cosh`") { + expect(Math.cosh(-1234.56)).toEqual(Double.PositiveInfinity) + expect(Math.cosh(1234.56)).toEqual(Double.PositiveInfinity) + expect(Math.cosh(-0.0)).toBeCloseTo(1.0) + expect(Math.cosh(Double.PositiveInfinity)).toEqual(Double.PositiveInfinity) + } + + it("should respond to `tanh`") { + expect(Math.tanh(-1234.56)).toBeCloseTo(-1.0) + expect(Math.tanh(-120.56)).toBeCloseTo(-1.0) + expect(Math.tanh(1234.56)).toBeCloseTo(1.0) + expect(Math.tanh(0.0)).toBeCloseTo(0.0) + expect(Math.tanh(Double.PositiveInfinity)).toBeCloseTo(1.0) + expect(Math.tanh(Double.NegativeInfinity)).toBeCloseTo(-1.0) + } + + } + +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/MockByteArrayOutputStream.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/MockByteArrayOutputStream.scala new file mode 100644 index 0000000..3356a85 --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/MockByteArrayOutputStream.scala @@ -0,0 +1,47 @@ +package scala.scalajs.testsuite.javalib + +import java.io._ + +/** A ByteArrayOutputStream that exposes various hooks for testing purposes. */ +class MockByteArrayOutputStream extends ByteArrayOutputStream { + private var _flushed: Boolean = true + private var _closed: Boolean = false + + var throwing: Boolean = false + + def flushed: Boolean = _flushed + def closed: Boolean = _closed + + private def maybeThrow(): Unit = { + if (throwing) + throw new IOException("MockByteArrayOutputStream throws") + } + + private def writeOp[A](op: => A): A = { + maybeThrow() + _flushed = false + op + } + + override def flush(): Unit = { + maybeThrow() + super.flush() + _flushed = true + } + + override def close(): Unit = { + maybeThrow() + super.close() + _closed = true + } + + override def write(c: Int): Unit = + writeOp(super.write(c)) + + override def write(b: Array[Byte]): Unit = + writeOp(super.write(b)) + + override def write(b: Array[Byte], off: Int, len: Int): Unit = + writeOp(super.write(b, off, len)) + +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/ObjectTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/ObjectTest.scala new file mode 100644 index 0000000..3584454 --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/ObjectTest.scala @@ -0,0 +1,72 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.javalib + +import org.scalajs.jasminetest.JasmineTest + +import scala.scalajs.js + +object ObjectTest extends JasmineTest { + + describe("java.lang.Object") { + + it("should provide `equals`") { + case class xy(x: Int, y: Int) + + val l = List(xy(1, 2), xy(2, 1)) + val xy12 = xy(1, 2) + + expect(l.contains(xy12)).toBeTruthy + expect(l.exists(_ == xy12)).toBeTruthy // the workaround + } + + it("everything but null should be an Object") { + expect((() : Any).isInstanceOf[Object]).toBeTruthy + expect((true : Any).isInstanceOf[Object]).toBeTruthy + expect(('a' : Any).isInstanceOf[Object]).toBeTruthy + expect((1.toByte : Any).isInstanceOf[Object]).toBeTruthy + expect((658.toShort : Any).isInstanceOf[Object]).toBeTruthy + expect((60000 : Any).isInstanceOf[Object]).toBeTruthy + expect((12345678910112L: Any).isInstanceOf[Object]).toBeTruthy + expect((6.5f : Any).isInstanceOf[Object]).toBeTruthy + expect((12.4 : Any).isInstanceOf[Object]).toBeTruthy + expect((new Object : Any).isInstanceOf[Object]).toBeTruthy + expect(("hello" : Any).isInstanceOf[Object]).toBeTruthy + expect((List(1) : Any).isInstanceOf[Object]).toBeTruthy + expect((Array(1) : Any).isInstanceOf[Object]).toBeTruthy + expect((Array(Nil) : Any).isInstanceOf[Object]).toBeTruthy + expect((new js.Object : Any).isInstanceOf[Object]).toBeTruthy + expect((js.Array(5) : Any).isInstanceOf[Object]).toBeTruthy + } + + it("null should not be an Object") { + expect((null: Any).isInstanceOf[Object]).toBeFalsy + } + + it("everything should cast to Object successfully, including null") { + expect(() => (() : Any).asInstanceOf[Object]).not.toThrow + expect(() => (true : Any).asInstanceOf[Object]).not.toThrow + expect(() => ('a' : Any).asInstanceOf[Object]).not.toThrow + expect(() => (1.toByte : Any).asInstanceOf[Object]).not.toThrow + expect(() => (658.toShort : Any).asInstanceOf[Object]).not.toThrow + expect(() => (60000 : Any).asInstanceOf[Object]).not.toThrow + expect(() => (12345678910112L: Any).asInstanceOf[Object]).not.toThrow + expect(() => (6.5f : Any).asInstanceOf[Object]).not.toThrow + expect(() => (12.4 : Any).asInstanceOf[Object]).not.toThrow + expect(() => (new Object : Any).asInstanceOf[Object]).not.toThrow + expect(() => ("hello" : Any).asInstanceOf[Object]).not.toThrow + expect(() => (List(1) : Any).asInstanceOf[Object]).not.toThrow + expect(() => (Array(1) : Any).asInstanceOf[Object]).not.toThrow + expect(() => (Array(Nil) : Any).asInstanceOf[Object]).not.toThrow + expect(() => (new js.Object : Any).asInstanceOf[Object]).not.toThrow + expect(() => (js.Array(5) : Any).asInstanceOf[Object]).not.toThrow + expect(() => (null : Any).asInstanceOf[Object]).not.toThrow + } + + } +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/OutputStreamWriterTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/OutputStreamWriterTest.scala new file mode 100644 index 0000000..7987a4c --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/OutputStreamWriterTest.scala @@ -0,0 +1,134 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.javalib + +import java.io._ + +import scala.scalajs.js +import js.JSConverters._ +import org.scalajs.jasminetest.JasmineTest + +object OutputStreamWriterTest extends JasmineTest { + private def newOSWriter(): (OutputStreamWriter, MockByteArrayOutputStream) = { + val bos = new MockByteArrayOutputStream + val osw = new OutputStreamWriter(bos) + (osw, bos) + } + + describe("java.io.OutputStreamWriter") { + it("flush") { + val (osw, bos) = newOSWriter() + bos.write(1) + osw.write("ABC") + expect(bos.flushed).toBeFalsy + osw.flush() + expect(bos.flushed).toBeTruthy + } + + it("close") { + val (osw, bos) = newOSWriter() + bos.write(1) + osw.write("ABC") + expect(bos.flushed).toBeFalsy + + osw.close() + expect(bos.flushed).toBeTruthy + expect(bos.closed).toBeTruthy + + // can double-close without error + osw.close() + + // when closed, other operations cause error + expect(() => osw.write('A')).toThrow + expect(() => osw.write("never printed")).toThrow + expect(() => osw.write(Array('a', 'b'))).toThrow + expect(() => osw.append("hello", 1, 3)).toThrow + expect(() => osw.flush()).toThrow + + // at the end of it all, bos is still what it was when it was closed + expect(bos.toByteArray().toJSArray).toEqual(js.Array(1, 65, 66, 67)) + } + + def testW(body: OutputStreamWriter => Unit, + expected: js.Array[Int], alreadyFlushed: Boolean = false): Unit = { + val (osw, bos) = newOSWriter() + body(osw) + if (!alreadyFlushed) { + expect(bos.size).toEqual(0) // write() methods should buffer + osw.flush() + } + expect(bos.flushed).toBeTruthy + expect(bos.toByteArray.toJSArray).toEqual(expected.map(_.toByte)) + } + + it("write(), ASCII repertoire") { + // Pure ASCII + testW(_.write('\n'), js.Array('\n')) + testW(_.write("hello\n"), js.Array('h', 'e', 'l', 'l', 'o', '\n')) + testW(_.write("hello\nworld", 3, 4), js.Array('l', 'o', '\n', 'w')) + testW(_.write(Array('A', '\n')), js.Array('A', '\n')) + testW(_.write(Array('A', 'B', '\n', 'C'), 1, 2), js.Array('B', '\n')) + } + + it("write(), Unicode repertoire without surrogates") { + testW(_.write('é'), js.Array(0xc3, 0xa9)) + testW(_.write("こんにちは"), js.Array( + 0xe3, 0x81, 0x93, 0xe3, 0x82, 0x93, 0xe3, 0x81, 0xab, 0xe3, 0x81, 0xa1, 0xe3, 0x81, 0xaf)) + testW(_.write("Καλημέρα", 3, 4), js.Array( + 0xce, 0xb7, 0xce, 0xbc, 0xce, 0xad, 0xcf, 0x81)) + } + + it("write(), surrogate pairs") { + testW(_.write("\ud83d\udca9"), js.Array(0xf0, 0x9f, 0x92, 0xa9)) + testW(_.write("ab\ud83d\udca9cd", 1, 3), js.Array('b', 0xf0, 0x9f, 0x92, 0xa9)) + } + + it("write(), surrogate pairs spread across multiple writes") { + testW({ osw => osw.write('\ud83d'); osw.write('\udca9') }, + js.Array(0xf0, 0x9f, 0x92, 0xa9)) + + testW({ osw => osw.write('\ud83d'); osw.flush(); osw.write('\udca9') }, + js.Array(0xf0, 0x9f, 0x92, 0xa9)) + + testW({ osw => osw.write("ab\ud83d"); osw.write('\udca9') }, + js.Array('a', 'b', 0xf0, 0x9f, 0x92, 0xa9)) + + testW({ osw => osw.write("ab\ud83d"); osw.write("\udca9cd") }, + js.Array('a', 'b', 0xf0, 0x9f, 0x92, 0xa9, 'c', 'd')) + + testW({ osw => osw.write("ab\ud83dzz", 1, 2); osw.write("ww\udca9cd", 2, 2) }, + js.Array('b', 0xf0, 0x9f, 0x92, 0xa9, 'c')) + } + + it("write(), malformed surrogates") { + testW(_.write("\ud83da"), js.Array('?', 'a')) + testW(_.write("\udca9"), js.Array('?')) + } + + it("write(), malformed surrogates spread across multiple writes") { + testW({ osw => osw.write('\ud83d'); osw.write('a') }, + js.Array('?', 'a')) + + testW({ osw => osw.write("ab\ud83d"); osw.write("\ud83d") }, + js.Array('a', 'b', '?')) + + testW({ osw => osw.write("ab\ud83d"); osw.write("\ud83dc") }, + js.Array('a', 'b', '?', '?', 'c')) + } + + it("write(), malformed surrogates at end of input") { + testW({ osw => osw.write('\ud83d'); osw.close() }, + js.Array('?'), alreadyFlushed = true) + + testW({ osw => osw.write("ab\ud83d"); osw.close() }, + js.Array('a', 'b', '?'), alreadyFlushed = true) + } + + } + +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/PrintStreamTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/PrintStreamTest.scala new file mode 100644 index 0000000..01c872b --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/PrintStreamTest.scala @@ -0,0 +1,296 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.javalib + +import java.io._ + +import scala.scalajs.js +import js.JSConverters._ +import org.scalajs.jasminetest.JasmineTest + +object PrintStreamTest extends JasmineTest { + private def newPrintStream( + autoFlush: Boolean = false): (MockPrintStream, MockByteArrayOutputStream) = { + val bos = new MockByteArrayOutputStream + val ps = new MockPrintStream(bos, autoFlush) + (ps, bos) + } + + describe("java.io.PrintStream") { + it("flush") { + val (ps, bos) = newPrintStream() + ps.print("hello") + expect(bos.flushed).toBeFalsy + ps.flush() + expect(bos.flushed).toBeTruthy + } + + it("close") { + val (ps, bos) = newPrintStream() + ps.write(Array[Byte](1)) + expect(bos.flushed).toBeFalsy + + ps.close() + expect(bos.flushed).toBeTruthy + expect(bos.closed).toBeTruthy + expect(ps.checkError()).toBeFalsy + + // can double-close without error + ps.close() + expect(ps.checkError()).toBeFalsy + ps.clearError() + + // when closed, other operations cause error + def expectCausesError(body: => Unit): Unit = { + body + expect(ps.checkError()).toBeTruthy + ps.clearError() + } + expectCausesError(ps.print("never printed")) + expectCausesError(ps.write(Array[Byte]('a', 'b'))) + expectCausesError(ps.append("hello", 1, 3)) + expectCausesError(ps.flush()) + + // at the end of it all, bos is still what it was when it was closed + expect(bos.toByteArray.toJSArray).toEqual(js.Array(1)) + } + + it("write, pass the bytes through") { + def test(body: PrintStream => Unit, expected: js.Array[Int], + testFlushed: Boolean = false): Unit = { + val (ps, bos) = newPrintStream(autoFlush = true) + body(ps) + if (testFlushed) + expect(bos.flushed).toBeTruthy + expect(ps.checkError()).toBeFalsy + expect(bos.toByteArray.toJSArray).toEqual(expected.map(_.toByte)) + } + + test(_.write('a'), js.Array('a')) + test(_.write('\n'), js.Array('\n'), testFlushed = true) + test(_.write(Array[Byte]('A', '\n')), + js.Array('A', '\n'), testFlushed = true) + test(_.write(Array[Byte]('A', 'B', '\n', 'C'), 1, 2), + js.Array('B', '\n'), testFlushed = true) + + test(_.write('é'.toByte), js.Array('é')) + test(_.write(Array[Byte]('é'.toByte, 'à'.toByte)), js.Array('é', 'à')) + } + + it("print") { + def test(body: PrintStream => Unit, expected: String, + testFlushed: Boolean = false): Unit = { + val (ps, bos) = newPrintStream(autoFlush = true) + body(ps) + if (testFlushed) + expect(bos.flushed).toBeTruthy + expect(ps.checkError()).toBeFalsy + expect(bos.toString()).toBe(expected) + } + + test(_.print(true), "true") + test(_.print('Z'), "Z") + test(_.print('\n'), "\n", testFlushed = true) + test(_.print(5), "5") + test(_.print(1234567891011L), "1234567891011") + test(_.print(1.5f), "1.5") + test(_.print(Math.PI), "3.141592653589793") + test(_.print(Array('A', '\n')), "A\n", testFlushed = true) + test(_.print("hello\n"), "hello\n", testFlushed = true) + test(_.print(null: String), "null") + test(_.print((1, 2)), "(1,2)") + test(_.print(null: AnyRef), "null") + } + + it("print encodes in UTF-8") { + def test(body: PrintStream => Unit, expected: js.Array[Int]): Unit = { + val (ps, bos) = newPrintStream(autoFlush = false) + body(ps) + expect(ps.checkError()).toBeFalsy + expect(bos.toByteArray.toJSArray).toEqual(expected.map(_.toByte)) + } + + test(_.print('é'), js.Array(0xc3, 0xa9)) + test(_.print("こんにちは"), js.Array( + 0xe3, 0x81, 0x93, 0xe3, 0x82, 0x93, 0xe3, 0x81, 0xab, 0xe3, 0x81, 0xa1, 0xe3, 0x81, 0xaf)) + test(_.print("ημέρ"), js.Array( + 0xce, 0xb7, 0xce, 0xbc, 0xce, 0xad, 0xcf, 0x81)) + + test(_.print("\ud83d\udca9"), js.Array(0xf0, 0x9f, 0x92, 0xa9)) + test(_.print("b\ud83d\udca9c"), js.Array('b', 0xf0, 0x9f, 0x92, 0xa9, 'c')) + + test({ osw => osw.print("ab\ud83d"); osw.print('\udca9') }, + js.Array('a', 'b', 0xf0, 0x9f, 0x92, 0xa9)) + + test({ osw => osw.print("ab\ud83d"); osw.print("\udca9cd") }, + js.Array('a', 'b', 0xf0, 0x9f, 0x92, 0xa9, 'c', 'd')) + + // Start of malformed sequences + + test(_.print("\ud83da"), js.Array('?', 'a')) + test(_.print("\udca9"), js.Array('?')) + + test({ osw => osw.print('\ud83d'); osw.print('a') }, + js.Array('?', 'a')) + + test({ osw => osw.print("ab\ud83d"); osw.print("\ud83d") }, + js.Array('a', 'b', '?')) + + test({ osw => osw.print("ab\ud83d"); osw.print("\ud83dc") }, + js.Array('a', 'b', '?', '?', 'c')) + + test({ osw => osw.print('\ud83d'); osw.close() }, + js.Array('?')) + + test({ osw => osw.print("ab\ud83d"); osw.close() }, + js.Array('a', 'b', '?')) + } + + for (autoFlush <- Seq(true, false)) { + val title = + if (autoFlush) "println forwards and flushes when autoFlush is true" + else "println forwards, does not flush when autoFlush is false" + it(title) { + def test(body: PrintStream => Unit, expected: String): Unit = { + val (ps, bos) = newPrintStream(autoFlush = autoFlush) + body(ps) + if (autoFlush) expect(bos.flushed).toBeTruthy + else expect(bos.flushed).toBeFalsy + expect(ps.checkError()).toBeFalsy + expect(bos.toString()).toBe(expected) + } + + test(_.println(), "\n") + test(_.println(true), "true\n") + test(_.println('Z'), "Z\n") + test(_.println('\n'), "\n\n") + test(_.println(5), "5\n") + test(_.println(1234567891011L), "1234567891011\n") + test(_.println(1.5f), "1.5\n") + test(_.println(Math.PI), "3.141592653589793\n") + test(_.println(Array('A', '\n')), "A\n\n") + test(_.println("hello\n"), "hello\n\n") + test(_.println(null: String), "null\n") + test(_.println((1, 2)), "(1,2)\n") + test(_.println(null: AnyRef), "null\n") + } + } + + for (autoFlush <- Seq(true, false)) { + val title = + if (autoFlush) "printf/format, which flushes when autoFlush is true" + else "printf/format, does not flush when autoFlush is false" + it(title) { + def test(body: PrintStream => Unit, expected: String): Unit = { + val (ps, bos) = newPrintStream(autoFlush = autoFlush) + body(ps) + if (autoFlush) expect(bos.flushed).toBeTruthy + else expect(bos.flushed).toBeFalsy + expect(ps.checkError()).toBeFalsy + expect(bos.toString()).toBe(expected) + } + + test(_.printf("%04d", Int.box(5)), "0005") + test(_.format("%.5f", Double.box(Math.PI)), "3.14159") + } + } + + it("append") { + def test(body: PrintStream => Unit, expected: String, + testFlushed: Boolean = false): Unit = { + val (ps, bos) = newPrintStream(autoFlush = true) + body(ps) + if (testFlushed) + expect(bos.flushed).toBeTruthy + expect(ps.checkError()).toBeFalsy + expect(bos.toString()).toBe(expected) + } + + test(_.append("hello\n"), "hello\n", testFlushed = true) + test(_.append(null: CharSequence), "null") + test(_.append("hello\nworld", 3, 6), "lo\n", testFlushed = true) + test(_.append(null: CharSequence, 1, 2), "u") + test(_.append('A'), "A") + test(_.append('\n'), "\n", testFlushed = true) + } + + it("traps all IOException and updates checkError") { + def test(body: PrintStream => Unit): Unit = { + val (ps, bos) = newPrintStream() + bos.throwing = true + body(ps) + expect(ps.checkError()).toBeTruthy + } + + test(_.flush()) + test(_.close()) + + test(_.write('Z')) + test(_.write(Array[Byte]('A', 'B'))) + test(_.write(Array[Byte]('A', 'B'), 1, 1)) + + test(_.print(true)) + test(_.print('Z')) + test(_.print('\n')) + test(_.print(5)) + test(_.print(1234567891011L)) + test(_.print(1.5f)) + test(_.print(Math.PI)) + test(_.print(Array('A', '\n'))) + test(_.print("hello\n")) + test(_.print(null: String)) + test(_.print((1, 2))) + test(_.print(null: AnyRef)) + + test(_.println()) + test(_.println(true)) + test(_.println('Z')) + test(_.println('\n')) + test(_.println(5)) + test(_.println(1234567891011L)) + test(_.println(1.5f)) + test(_.println(Math.PI)) + test(_.println(Array('A', '\n'))) + test(_.println("hello\n")) + test(_.println(null: String)) + test(_.println((1, 2))) + test(_.println(null: AnyRef)) + + test(_.append("hello\n")) + test(_.append(null: CharSequence)) + test(_.append("hello\nworld", 3, 6)) + test(_.append(null: CharSequence, 1, 2)) + test(_.append('A')) + test(_.append('\n')) + } + + it("write short-circuits pending high surrogates in print") { + val (ps, bos) = newPrintStream() + ps.print('A') + expect(bos.toByteArray.toJSArray).toEqual(js.Array[Byte]('A')) + ps.print('\ud83d') + expect(bos.toByteArray.toJSArray).toEqual(js.Array[Byte]('A')) + ps.flush() + expect(bos.toByteArray.toJSArray).toEqual(js.Array[Byte]('A')) + ps.write('Z') + expect(bos.toByteArray.toJSArray).toEqual(js.Array[Byte]('A', 'Z')) + ps.print('\udca9') + expect(bos.toByteArray.toJSArray).toEqual(js.Array[Byte]( + 'A', 'Z', -16, -97, -110, -87)) + } + } + + /** A PrintStream that exposes various hooks for testing purposes. */ + private class MockPrintStream(out: OutputStream, + autoFlush: Boolean) extends PrintStream(out, autoFlush) { + def this(out: OutputStream) = this(out, false) + + override def clearError(): Unit = super.clearError() + } + +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/PrintWriterTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/PrintWriterTest.scala new file mode 100644 index 0000000..b2b3309 --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/PrintWriterTest.scala @@ -0,0 +1,287 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.javalib + +import scala.language.implicitConversions + +import java.io._ + +import scala.scalajs.js +import org.scalajs.jasminetest.JasmineTest + +object PrintWriterTest extends JasmineTest { + private def newPrintWriter( + autoFlush: Boolean = false): (MockPrintWriter, MockStringWriter) = { + val sw = new MockStringWriter + val pw = new MockPrintWriter(sw, autoFlush) + (pw, sw) + } + + describe("java.io.PrintWriter") { + it("flush") { + val (pw, sw) = newPrintWriter() + pw.print("hello") + expect(sw.flushed).toBeFalsy + pw.flush() + expect(sw.flushed).toBeTruthy + } + + it("close") { + val (pw, sw) = newPrintWriter() + pw.write("begin") + expect(sw.flushed).toBeFalsy + + pw.close() + expect(sw.flushed).toBeTruthy + expect(sw.closed).toBeTruthy + expect(pw.checkError()).toBeFalsy + + // can double-close without error + pw.close() + expect(pw.checkError()).toBeFalsy + pw.clearError() + + // when closed, other operations cause error + def expectCausesError(body: => Unit): Unit = { + body + expect(pw.checkError()).toBeTruthy + pw.clearError() + } + expectCausesError(pw.print("never printed")) + expectCausesError(pw.write(Array('a', 'b'))) + expectCausesError(pw.append("hello", 1, 3)) + expectCausesError(pw.flush()) + + // at the end of it all, sw is still what it was when it was closed + expect(sw.toString()).toBe("begin") + } + + it("write, does not flush even with \\n") { + def test(body: PrintWriter => Unit, expected: String): Unit = { + val (pw, sw) = newPrintWriter(autoFlush = true) + body(pw) + expect(sw.flushed).toBeFalsy + expect(pw.checkError()).toBeFalsy + expect(sw.toString()).toBe(expected) + } + + test(_.write('\n'), "\n") + test(_.write("hello\n"), "hello\n") + test(_.write("hello\nworld", 3, 3), "lo\n") + test(_.write(Array('A', '\n')), "A\n") + test(_.write(Array('A', 'B', '\n', 'C'), 1, 2), "B\n") + } + + it("print, does not flush even with \\n") { + def test(body: PrintWriter => Unit, expected: String): Unit = { + val (pw, sw) = newPrintWriter(autoFlush = true) + body(pw) + expect(sw.flushed).toBeFalsy + expect(pw.checkError()).toBeFalsy + expect(sw.toString()).toBe(expected) + } + + test(_.print(true), "true") + test(_.print('Z'), "Z") + test(_.print('\n'), "\n") + test(_.print(5), "5") + test(_.print(1234567891011L), "1234567891011") + test(_.print(1.5f), "1.5") + test(_.print(Math.PI), "3.141592653589793") + test(_.print(Array('A', '\n')), "A\n") + test(_.print("hello\n"), "hello\n") + test(_.print(null: String), "null") + test(_.print((1, 2)), "(1,2)") + test(_.print(null: AnyRef), "null") + } + + for (autoFlush <- Seq(true, false)) { + val title = + if (autoFlush) "println forwards and flushes when autoFlush is true" + else "println forwards, does not flush when autoFlush is false" + it(title) { + def test(body: PrintWriter => Unit, expected: String): Unit = { + val (pw, sw) = newPrintWriter(autoFlush = autoFlush) + body(pw) + if (autoFlush) expect(sw.flushed).toBeTruthy + else expect(sw.flushed).toBeFalsy + expect(pw.checkError()).toBeFalsy + expect(sw.toString()).toBe(expected) + } + + test(_.println(), "\n") + test(_.println(true), "true\n") + test(_.println('Z'), "Z\n") + test(_.println('\n'), "\n\n") + test(_.println(5), "5\n") + test(_.println(1234567891011L), "1234567891011\n") + test(_.println(1.5f), "1.5\n") + test(_.println(Math.PI), "3.141592653589793\n") + test(_.println(Array('A', '\n')), "A\n\n") + test(_.println("hello\n"), "hello\n\n") + test(_.println(null: String), "null\n") + test(_.println((1, 2)), "(1,2)\n") + test(_.println(null: AnyRef), "null\n") + } + } + + for (autoFlush <- Seq(true, false)) { + val title = + if (autoFlush) "printf/format, which flushes when autoFlush is true" + else "printf/format, does not flush when autoFlush is false" + it(title) { + def test(body: PrintWriter => Unit, expected: String): Unit = { + val (pw, sw) = newPrintWriter(autoFlush = autoFlush) + body(pw) + if (autoFlush) expect(sw.flushed).toBeTruthy + else expect(sw.flushed).toBeFalsy + expect(pw.checkError()).toBeFalsy + expect(sw.toString()).toBe(expected) + } + + test(_.printf("%04d", Int.box(5)), "0005") + test(_.format("%.5f", Double.box(Math.PI)), "3.14159") + } + } + + it("append, does not flush even with \\n") { + def test(body: PrintWriter => Unit, expected: String): Unit = { + val (pw, sw) = newPrintWriter(autoFlush = true) + body(pw) + expect(sw.flushed).toBeFalsy + expect(pw.checkError()).toBeFalsy + expect(sw.toString()).toBe(expected) + } + + test(_.append("hello\n"), "hello\n") + test(_.append(null: CharSequence), "null") + test(_.append("hello\nworld", 3, 6), "lo\n") + test(_.append(null: CharSequence, 1, 2), "u") + test(_.append('A'), "A") + test(_.append('\n'), "\n") + } + + it("traps all IOException and updates checkError") { + def test(body: PrintWriter => Unit): Unit = { + val (pw, sw) = newPrintWriter() + sw.throwing = true + body(pw) + expect(pw.checkError()).toBeTruthy + } + + test(_.flush()) + test(_.close()) + + test(_.write('Z')) + test(_.write("booh")) + test(_.write("booh", 1, 1)) + test(_.write(Array('A', 'B'))) + test(_.write(Array('A', 'B'), 1, 1)) + + test(_.print(true)) + test(_.print('Z')) + test(_.print('\n')) + test(_.print(5)) + test(_.print(1234567891011L)) + test(_.print(1.5f)) + test(_.print(Math.PI)) + test(_.print(Array('A', '\n'))) + test(_.print("hello\n")) + test(_.print(null: String)) + test(_.print((1, 2))) + test(_.print(null: AnyRef)) + + test(_.println()) + test(_.println(true)) + test(_.println('Z')) + test(_.println('\n')) + test(_.println(5)) + test(_.println(1234567891011L)) + test(_.println(1.5f)) + test(_.println(Math.PI)) + test(_.println(Array('A', '\n'))) + test(_.println("hello\n")) + test(_.println(null: String)) + test(_.println((1, 2))) + test(_.println(null: AnyRef)) + + test(_.append("hello\n")) + test(_.append(null: CharSequence)) + test(_.append("hello\nworld", 3, 6)) + test(_.append(null: CharSequence, 1, 2)) + test(_.append('A')) + test(_.append('\n')) + } + } + + /** A PrintWriter that exposes various hooks for testing purposes. */ + private class MockPrintWriter(out: Writer, + autoFlush: Boolean) extends PrintWriter(out, autoFlush) { + def this(out: Writer) = this(out, false) + + override def clearError(): Unit = super.clearError() + } + + /** A StringWriter that exposes various hooks for testing purposes. */ + private class MockStringWriter extends StringWriter { + private var _flushed: Boolean = true + private var _closed: Boolean = false + + var throwing: Boolean = false + + def flushed: Boolean = _flushed + def closed: Boolean = _closed + + private def maybeThrow(): Unit = { + if (throwing) + throw new IOException("MockStringWriter throws") + } + + private def writeOp[A](op: => A): A = { + maybeThrow() + _flushed = false + op + } + + override def flush(): Unit = { + maybeThrow() + super.flush() + _flushed = true + } + + override def close(): Unit = { + maybeThrow() + super.close() + _closed = true + } + + override def append(c: Char): StringWriter = + writeOp(super.append(c)) + + override def append(csq: CharSequence): StringWriter = + writeOp(super.append(csq)) + + override def append(csq: CharSequence, start: Int, end: Int): StringWriter = + writeOp(super.append(csq, start, end)) + + override def write(c: Int): Unit = + writeOp(super.write(c)) + + override def write(cbuf: Array[Char]): Unit = + writeOp(super.write(cbuf)) + + override def write(cbuf: Array[Char], off: Int, len: Int): Unit = + writeOp(super.write(cbuf, off, len)) + + override def write(str: String): Unit = + writeOp(super.write(str)) + + override def write(str: String, off: Int, len: Int): Unit = + writeOp(super.write(str, off, len)) + } +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/RandomTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/RandomTest.scala new file mode 100644 index 0000000..1794d4a --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/RandomTest.scala @@ -0,0 +1,225 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.javalib + +import org.scalajs.jasminetest.JasmineTest + +import java.util.Random + +import scala.scalajs.js +import scala.scalajs.js.JSConverters._ + +object RandomTest extends JasmineTest { + + describe("java.util.Random") { + + it("should produce bits according to spec with seed=10") { + val random = new HackRandom(10) + + expect(random.next(10)).toBe(747) + expect(random.next(1)).toBe(0) + expect(random.next(6)).toBe(16) + expect(random.next(20)).toBe(432970) + expect(random.next(32)).toBe(254270492) + } + + it("should produce bits according to spec with seed=-5") { + val random = new HackRandom(-5) + + expect(random.next(10)).toBe(275) + expect(random.next(1)).toBe(0) + expect(random.next(6)).toBe(21) + expect(random.next(20)).toBe(360349) + expect(random.next(32)).toBe(1635930704) + } + + it("should produce bits according to spec with seed=MaxLong") { + val random = new HackRandom(Long.MaxValue) + + expect(random.next(10)).toBe(275) + expect(random.next(1)).toBe(0) + expect(random.next(6)).toBe(0) + expect(random.next(20)).toBe(574655) + expect(random.next(32)).toBe(-1451336087) + } + + it("should produce bits according to spec with seed=MinInt") { + val random = new HackRandom(Int.MinValue) + + expect(random.next(10)).toBe(388) + expect(random.next(1)).toBe(0) + expect(random.next(6)).toBe(25) + expect(random.next(20)).toBe(352095) + expect(random.next(32)).toBe(-2140124682) + } + + it("should allow resetting the seed") { + val random = new HackRandom(11) + expect(random.next(10)).toBe(747) + expect(random.next(1)).toBe(1) + expect(random.next(6)).toBe(27) + + random.setSeed(11) + expect(random.next(10)).toBe(747) + expect(random.next(1)).toBe(1) + expect(random.next(6)).toBe(27) + } + + it("should reset nextNextGaussian when setting the seed") { + val random = new Random(-1) + expect(random.nextGaussian()).toBe(1.7853314409882288) + random.setSeed(-1) + expect(random.nextGaussian()).toBe(1.7853314409882288) + } + + it("should correctly implement nextDouble") { + val random = new Random(-45) + expect(random.nextDouble()).toBe(0.27288421395636253) + expect(random.nextDouble()).toBe(0.5523165360074201) + expect(random.nextDouble()).toBe(0.5689979434708298) + expect(random.nextDouble()).toBe(0.9961166166874871) + expect(random.nextDouble()).toBe(0.5368984665202684) + expect(random.nextDouble()).toBe(0.19849067496547423) + expect(random.nextDouble()).toBe(0.6021019223595357) + expect(random.nextDouble()).toBe(0.06132131151816378) + expect(random.nextDouble()).toBe(0.7303867762743866) + expect(random.nextDouble()).toBe(0.7426529384056163) + } + + it("should correctly implement nextBoolean") { + val random = new Random(4782934) + expect(random.nextBoolean()).toBe(false) + expect(random.nextBoolean()).toBe(true) + expect(random.nextBoolean()).toBe(true) + expect(random.nextBoolean()).toBe(false) + expect(random.nextBoolean()).toBe(false) + expect(random.nextBoolean()).toBe(false) + expect(random.nextBoolean()).toBe(true) + expect(random.nextBoolean()).toBe(false) + } + + it("should correctly implement nextInt") { + val random = new Random(-84638) + expect(random.nextInt()).toBe(-1217585344) + expect(random.nextInt()).toBe(1665699216) + expect(random.nextInt()).toBe(382013296) + expect(random.nextInt()).toBe(1604432482) + expect(random.nextInt()).toBe(-1689010196) + expect(random.nextInt()).toBe(1743354032) + expect(random.nextInt()).toBe(454046816) + expect(random.nextInt()).toBe(922172344) + expect(random.nextInt()).toBe(-1890515287) + expect(random.nextInt()).toBe(1397525728) + } + + it("should correctly implement nextInt(n)") { + val random = new Random(7) + expect(random.nextInt(76543)).toBe(32736) + expect(() => random.nextInt(0)).toThrow + expect(random.nextInt(45)).toBe(29) + expect(random.nextInt(945)).toBe(60) + expect(random.nextInt(35694839)).toBe(20678044) + expect(random.nextInt(35699)).toBe(23932) + expect(random.nextInt(3699)).toBe(2278) + expect(random.nextInt(10)).toBe(8) + } + + it("should correctly implement nextInt(n) for powers of 2") { + val random = new Random(-56938) + + expect(random.nextInt(32)).toBe(8) + expect(random.nextInt(8)).toBe(3) + expect(random.nextInt(128)).toBe(3) + expect(random.nextInt(4096)).toBe(1950) + expect(random.nextInt(8192)).toBe(3706) + expect(random.nextInt(8192)).toBe(4308) + expect(random.nextInt(8192)).toBe(3235) + expect(random.nextInt(8192)).toBe(7077) + expect(random.nextInt(8192)).toBe(2392) + expect(random.nextInt(32)).toBe(31) + } + + it("should correctly implement nextLong") { + val random = new Random(205620432625028L) + expect(random.nextLong()).toBe(3710537363280377478L) + expect(random.nextLong()).toBe(4121778334981170700L) + expect(random.nextLong()).toBe(289540773990891960L) + expect(random.nextLong()).toBe(307008980197674441L) + expect(random.nextLong()).toBe(7527069864796025013L) + expect(random.nextLong()).toBe(-4563192874520002144L) + expect(random.nextLong()).toBe(7619507045427546529L) + expect(random.nextLong()).toBe(-7888117030898487184L) + expect(random.nextLong()).toBe(-3499168703537933266L) + expect(random.nextLong()).toBe(-1998975913933474L) + } + + it("should correctly implement nextFloat") { + val random = new Random(-3920005825473L) + expect(random.nextFloat()).toBeCloseTo(0.059591234, 7) + expect(random.nextFloat()).toBeCloseTo(0.7007871, 7) + expect(random.nextFloat()).toBeCloseTo(0.39173192, 7) + expect(random.nextFloat()).toBeCloseTo(0.0647918, 7) + expect(random.nextFloat()).toBeCloseTo(0.9029677, 7) + expect(random.nextFloat()).toBeCloseTo(0.18226051, 7) + expect(random.nextFloat()).toBeCloseTo(0.94444054, 7) + expect(random.nextFloat()).toBeCloseTo(0.008844078, 7) + expect(random.nextFloat()).toBeCloseTo(0.08891684, 7) + expect(random.nextFloat()).toBeCloseTo(0.06482434, 7) + } + + it("should correctly implement nextBytes") { + val random = new Random(7399572013373333L) + + def test(exps: Int*) = { + val exp = js.Array(exps.map(_.toByte): _*) + val buf = new Array[Byte](exp.length) + random.nextBytes(buf) + expect(buf.toJSArray).toEqual(exp) + } + + test(62, 89, 68, -91, 10, 0, 85) + test(-89, -76, 88, 121, -25, 47, 58, -8, 78, 20, -77, 84, -3, + -33, 58, -9, 11, 57, -118, 40, -74, -86, 78, 123, 58) + test(-77, 112, -116) + test() + test(-84, -96, 108) + test(57, -106, 42, -100, -47, -84, 67, -48, 45) + } + + it("should correctly implement nextGaussian") { + val random = new Random(2446004) + expect(random.nextGaussian()).toBe(-0.5043346938630431) + expect(random.nextGaussian()).toBe(-0.3250983270156675) + expect(random.nextGaussian()).toBe(-0.23799457294994966) + expect(random.nextGaussian()).toBe(0.4164610631507695) + expect(random.nextGaussian()).toBe(0.22086348814760687) + expect(random.nextGaussian()).toBe(-0.706833209972521) + expect(random.nextGaussian()).toBe(0.6730758289772553) + expect(random.nextGaussian()).toBe(0.2797393696191283) + expect(random.nextGaussian()).toBe(-0.2979099632667685) + expect(random.nextGaussian()).toBe(0.37443415981434314) + expect(random.nextGaussian()).toBe(0.9584801742918951) + expect(random.nextGaussian()).toBe(1.1762179112229345) + expect(random.nextGaussian()).toBe(0.8736960092848826) + expect(random.nextGaussian()).toBe(0.12301554931271008) + expect(random.nextGaussian()).toBe(-0.6052081187207353) + expect(random.nextGaussian()).toBe(-0.2015925608755316) + expect(random.nextGaussian()).toBe(-1.0071216119742104) + expect(random.nextGaussian()).toBe(0.6734222041441913) + expect(random.nextGaussian()).toBe(0.3990565555091522) + expect(random.nextGaussian()).toBe(2.0051627385915154) + } + + } + + /** Helper class to access next */ + class HackRandom(seed: Long) extends Random(seed) { + override def next(bits: Int): Int = super.next(bits) + } + +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/ReadersTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/ReadersTest.scala new file mode 100644 index 0000000..f970141 --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/ReadersTest.scala @@ -0,0 +1,244 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.javalib + +import scala.annotation.tailrec + +import java.io._ + +import scala.scalajs.js +import scala.scalajs.js.JSConverters._ + +import org.scalajs.jasminetest.JasmineTest + +/** Tests for our implementation of java.io._ reader classes */ +object ReadersTest extends JasmineTest { + + describe("java.io.StringReader") { + val str = "asdf" + def newReader = new StringReader(str) + + it("should provide read()") { + val r = newReader + + for (c <- str) { + expect(r.read().toChar).toEqual(c) + } + + expect(r.read()).toEqual(-1) + } + + it("should provide read(buf: Array[Char], off: Int, len: Int)") { + val r = newReader + val buf = new Array[Char](10) + + expect(r.read(buf, 2, 8)).toBe(4) + expect(buf.map(_.toInt).toJSArray).toEqual( + js.Array[Int](0,0,'a','s','d','f',0,0,0,0)) + } + + it("should provide read(java.nio.CharBuffer)") { + val r = newReader + val buf0 = java.nio.CharBuffer.allocate(25) + buf0.position(3) + val buf = buf0.slice() + buf.position(4) + buf.limit(14) + + expect(r.read(buf)).toBe(4) + expect(buf.position()).toBe(8) + buf.flip() + expect(buf.toString().map(_.toInt).toJSArray).toEqual( + js.Array[Int](0, 0, 0, 0, 'a', 's', 'd', 'f')) + } + + it("should provide ready") { + val r = newReader + + for (c <- str) { + expect(r.ready()).toBeTruthy + expect(r.read().toChar).toEqual(c) + } + + expect(r.ready()).toBeFalsy + expect(r.read()).toEqual(-1) + } + + it("should provide mark/reset") { + val r = newReader + r.mark(str.length) + + for (c <- str) { + expect(r.read().toChar).toEqual(c) + } + expect(r.read()).toEqual(-1) + + r.reset() + + for (c <- str) { + expect(r.read().toChar).toEqual(c) + } + expect(r.read()).toEqual(-1) + } + + it("should provide skip") { + val r = newReader + + expect(r.read()).toEqual('a') + expect(r.skip(2L).toInt).toBe(2) + + expect(r.read()).toEqual('f') + expect(r.read()).toEqual(-1) + } + + it("should provide close") { + val r = newReader + + r.close() + expect(() => r.read()).toThrow + } + + it("should support marking") { + expect(newReader.markSupported).toBeTruthy + } + } + + describe("java.io.BufferedReader") { + val str = "line1\nline2\r\n\nline4\rline5" + def newReader = new BufferedReader(new StringReader(str), 3) + + it("should provide read()") { + val r = newReader + + for (c <- str) { + expect(r.read().toChar).toEqual(c) + } + expect(r.read()).toEqual(-1) + } + + it("should provide read(cbuf)") { + var read = 0 + val r = newReader + val buf = new Array[Char](15) + + // twice to force filling internal buffer + for (_ <- 0 to 1) { + val len = r.read(buf) + expect(len).toBeGreaterThan(0) + + for (i <- 0 until len) + expect(buf(i)).toEqual(str.charAt(i+read)) + + read += len + } + } + + it("should provide read(cbuf, off, len)") { + var read = 0 + val r = newReader + val buf = new Array[Char](15) + + // twice to force filling internal buffer + for (_ <- 0 to 1) { + val len = r.read(buf, 1, 10) + expect(len).toBeGreaterThan(0) + expect(len).toBeLessThan(11) + + for (i <- 0 until len) + expect(buf(i+1)).toEqual(str.charAt(i+read)) + + read += len + } + } + + it("should provide mark/reset") { + val r = newReader + expect(r.read()).toEqual('l') + + // force moving and resizing buffer + r.mark(10) + + for (i <- 0 until 10) { + expect(r.read()).toEqual(str.charAt(i+1)) + } + + r.reset() + + for (i <- 1 until str.length) { + expect(r.read()).toEqual(str.charAt(i)) + } + } + + it("should provide readLine") { + val r = newReader + + expect(r.readLine()).toEqual("line1") + expect(r.readLine()).toEqual("line2") + expect(r.readLine()).toEqual("") + expect(r.readLine()).toEqual("line4") + expect(r.readLine()).toEqual("line5") + expect(r.readLine()).toEqual(null) + } + + it("should readLine on an empty stream") { + val r = new BufferedReader(new StringReader("")) + + expect(r.readLine()).toEqual(null) + } + + it("should readline with empty lines only") { + val r = new BufferedReader(new StringReader("\n\r\n\r\r\n"), 1) + + for (_ <- 1 to 4) + expect(r.readLine()).toEqual("") + + expect(r.readLine()).toEqual(null) + } + + it("should support marking") { + expect(newReader.markSupported).toBeTruthy + } + } + + describe("java.io.InputStreamReader") { + + it("should read UTF8") { + + val buf = Array[Byte](72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, + 46, -29, -127, -109, -29, -126, -109, -29, -127, -85, -29, -127, -95, + -29, -127, -81, -26, -105, -91, -26, -100, -84, -24, -86, -98, -29, + -126, -110, -24, -86, -83, -29, -126, -127, -29, -127, -66, -29, -127, + -103, -29, -127, -117, -29, -128, -126) + + val r = new InputStreamReader(new ByteArrayInputStream(buf)) + + def expectRead(str: String) = { + val buf = new Array[Char](str.length) + @tailrec + def readAll(readSoFar: Int): Int = { + if (readSoFar == buf.length) readSoFar + else { + val newlyRead = r.read(buf, readSoFar, buf.length - readSoFar) + if (newlyRead == -1) readSoFar + else readAll(readSoFar + newlyRead) + } + } + expect(readAll(0)).toBe(str.length) + expect(new String(buf)).toEqual(str) + } + + expectRead("Hello World.") + expectRead("こんにちは") + expectRead("日本語を読めますか。") + expect(r.read()).toBe(-1) + + } + + } + +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/ReferenceTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/ReferenceTest.scala new file mode 100644 index 0000000..cf0fd0c --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/ReferenceTest.scala @@ -0,0 +1,28 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.javalib + +import org.scalajs.jasminetest.JasmineTest + +object ReferenceTest extends JasmineTest { + + describe("java.land.ref.Reference") { + + it("Should have all the normal operations") { + val s = "string" + val ref = new java.lang.ref.WeakReference(s) + expect(ref.get).toEqual(s) + expect(ref.enqueue).toEqual(false) + expect(ref.isEnqueued).toEqual(false) + ref.clear + // can't use `expect` because it tries to be clever and .toString things, + // which makes it blow up when you pass in null + assert(ref.get == null) + } + } +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/RegexTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/RegexTest.scala new file mode 100644 index 0000000..a27584a --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/RegexTest.scala @@ -0,0 +1,397 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.javalib + +import scala.scalajs.js +import scala.scalajs.js.JSConverters._ + +import org.scalajs.jasminetest.JasmineTest + +import java.util.regex.Pattern + +object RegexTest extends JasmineTest { + + describe("java.util.regex.Pattern") { + + it("should respond to `matches`") { + expect(Pattern.matches("[Scal]*\\.js", "Scala.js")).toBeTruthy + expect(Pattern.matches(".[cal]*\\.j.", "Scala.js")).toBeTruthy + expect(Pattern.matches(".*\\.js", "Scala.js")).toBeTruthy + expect(Pattern.matches("S[a-z]*", "Scala.js")).toBeFalsy + } + + it("should respond to `matches` with flags") { + matches("scala.js", "Scala.js") + matches("SCALA.JS", "Scala.js") + matches("waz*up", "WAZZZZZZZZZZZUP") + + def matches(regex: String, input: String): Unit = { + val result = Pattern.compile(regex, Pattern.CASE_INSENSITIVE).matcher(input) + expect(result.matches()).toBeTruthy + } + } + + it("should respond to `split`") { + val result = Pattern.compile("[aj]").split("Scala.js") + val expected = js.Array("Sc", "l", ".", "s") + expect(result.length).toEqual(4) + expect(result.toJSArray).toEqual(expected) + + // Tests from JavaDoc + split("boo:and:foo", ":", Array("boo", "and", "foo")) + split("boo:and:foo", "o", Array("b", "", ":and:f")) + + // Splitting the empty string must return 1 element - #987 + split("", "a", Array("")) + split("", "\\*", Array("")) + split("", "\n", Array("")) + split("", "", Array("")) + + // Should remove leading empty match under some conditions - #1171 + // These tests are "measured" on the JVM since the spec is unclear + split("abc", "(?=a)", Array("abc")) + split("abc", "(?=b)", Array("a", "bc")) + split("abc", "(?=a)|b", Array("", "a", "c")) + split("abc", "", Array("", "a", "b", "c")) + split("abc", "(?=a)|(?=b)", Array("", "a", "bc")) + split("abc", "(?=a)|(?=a)", Array("abc")) + split("abc", "(?=a|b)", Array("", "a", "bc")) + split("abc", "(?=a|d)", Array("abc")) + split("abc", "^d*", Array("abc")) + split("abc", "d*", Array("", "a", "b", "c")) + split("a", "", Array("", "a")) + split("a", "^d*", Array("a")) + split("a", "d*", Array("", "a")) + split("a", "(?=a)", Array("a")) + split("ab", "a", Array("", "b")) + + def split(input: String, regex: String, expected: Array[String]): Unit = { + val result = Pattern.compile(regex).split(input) + expect(result.toJSArray).toEqual(expected.toJSArray) + } + } + + it("should respond to `split` with limit") { + // Tests from JavaDoc + splitWithLimit("boo:and:foo", ":", 2, Array("boo", "and:foo")) + splitWithLimit("boo:and:foo", ":", 5, Array("boo", "and", "foo")) + splitWithLimit("boo:and:foo", ":", -2, Array("boo", "and", "foo")) + splitWithLimit("boo:and:foo", "o", 5, Array("b", "", ":and:f", "", "")) + splitWithLimit("boo:and:foo", "o", -2, Array("b", "", ":and:f", "", "")) + splitWithLimit("boo:and:foo", "o", 0, Array("b", "", ":and:f")) + + // Splitting the empty string must return 1 element - #987 + splitWithLimit("", "a", 0, Array("")) + splitWithLimit("", "\\*", 5, Array("")) + splitWithLimit("", "\n", -2, Array("")) + splitWithLimit("", "", 1, Array("")) + + // Should remove leading empty match under some conditions - #1171 + splitWithLimit("abc", "", 2, Array("", "abc")) + splitWithLimit("abc", "(?=a)", 2, Array("abc")) + splitWithLimit("ab", "a", 1, Array("ab")) + + def splitWithLimit(input: String, regex: String, limit: Int, expected: Array[String]): Unit = { + val result = Pattern.compile(regex).split(input, limit) + expect(result.toJSArray).toEqual(expected.toJSArray) + } + } + + it("should respond to `flags`") { + val pattern0 = Pattern.compile("a") + val pattern1 = Pattern.compile("a", 0) + val flags2 = Pattern.CASE_INSENSITIVE | Pattern.DOTALL + val pattern2 = Pattern.compile("a", flags2) + + expect(pattern0.flags).toEqual(0) + expect(pattern1.flags).toEqual(0) + expect(pattern2.flags).toEqual(flags2) + } + + it("should respond to `pattern` and `toString`") { + def checkPatternAndToString(regex: String): Unit = { + val pattern0 = Pattern.compile(regex) + expect(pattern0.pattern).toEqual(regex) + expect(pattern0.toString).toEqual(regex) + + val pattern1 = Pattern.compile(regex, Pattern.CASE_INSENSITIVE) + expect(pattern1.pattern).toEqual(regex) + expect(pattern1.toString).toEqual(regex) + } + + checkPatternAndToString("a*b+c") + checkPatternAndToString("\\S[(a1]a.js") + } + + it("should respond to `quote`") { + val splitWithQuote = Pattern.compile(Pattern.quote("$1&$2")).split("Scala$1&$2.js") + val splitNoQuote = Pattern.compile("$1&$2").split("Scala$1&$2.js") + expect(splitWithQuote.mkString).toEqual("Scala.js") + expect(splitNoQuote.mkString).toEqual("Scala$1&$2.js") + } + + } + + describe("java.util.regex.Matcher") { + + it("should respond to `find`") { + val matcher = Pattern.compile("a").matcher("Scala.js") + + expect(matcher.find()).toBeTruthy + expect(matcher.find()).toBeTruthy + expect(matcher.find()).toBeFalsy + expect(matcher.find(4)).toBeTruthy + expect(matcher.find()).toBeFalsy + } + + it("should respond to `start`, `end`, `group`, and `toMatchResult`") { + val matcher = Pattern.compile("\\s(([A-Za-z]{5}(hum)?).js)\\s").matcher("Write Scala.js everyday!") + + def checkGroup0(start: Int, end: Int, group: String) = + checkGroup(start, 5, end, 15, group, " Scala.js ") + + def checkGroup1(start: Int, end: Int, group: String) = + checkGroup(start, 6, end, 14, group, "Scala.js") + + def checkGroup2(start: Int, end: Int, group: String) = + checkGroup(start, 6, end, 11, group, "Scala") + + def checkGroup3(start: Int, end: Int, group: String) = + checkGroup(start, -1, end, -1, group, null) + + def checkGroup(start: Int, startExpected: Int, end: Int, endExpected: Int, + group: String, groupExpected: String): Unit = { + expect(start).toEqual(startExpected) + expect(end).toEqual(endExpected) + expect(group).toEqual(groupExpected) + } + + expect(matcher.find()).toBeTruthy + expect(matcher.groupCount).toEqual(3) + checkGroup0(matcher.start, matcher.end, matcher.group) + checkGroup0(matcher.start(0), matcher.end(0), matcher.group(0)) + checkGroup1(matcher.start(1), matcher.end(1), matcher.group(1)) + checkGroup2(matcher.start(2), matcher.end(2), matcher.group(2)) + checkGroup3(matcher.start(3), matcher.end(3), matcher.group(3)) + + val matchResult = matcher.toMatchResult + expect(matchResult.groupCount).toEqual(3) + checkGroup0(matchResult.start, matchResult.end, matchResult.group) + checkGroup0(matchResult.start(0), matchResult.end(0), matchResult.group(0)) + checkGroup1(matchResult.start(1), matchResult.end(1), matchResult.group(1)) + checkGroup2(matchResult.start(2), matchResult.end(2), matchResult.group(2)) + checkGroup3(matchResult.start(3), matchResult.end(3), matchResult.group(3)) + } + + it("should respond to `matches`") { + val matcher0 = Pattern.compile("S[a-z]+").matcher("Scala") + val matcher1 = Pattern.compile("S[a-z]+").matcher("Scala.js") + + expect(matcher0.matches()).toBeTruthy + expect(matcher1.matches()).toBeFalsy + } + + it("should respond to `reset`") { + val matcher = Pattern.compile("S[a-z]+").matcher("Scalable") + + expect(matcher.find()).toBeTruthy + expect(matcher.find()).toBeFalsy + matcher.reset() + expect(matcher.find()).toBeTruthy + } + + it("should respond to `reset(String)`") { + val matcher = Pattern.compile("S[a-z]+").matcher("Scalable") + + expect(matcher.matches()).toBeTruthy + matcher.reset("Scala.js") + expect(matcher.matches()).toBeFalsy + } + + it("should respond to `usePattern`") { + val patternNoDots = Pattern.compile("S[a-z]+") + val patternWithDots = Pattern.compile("S[a-z.]+") + + val matcher0 = patternNoDots.matcher("Scala.js") + expect(matcher0.matches()).toBeFalsy + matcher0.usePattern(patternWithDots) + expect(matcher0.matches()).toBeTruthy + + val matcher1 = patternWithDots.matcher("Scala.js") + expect(matcher1.matches()).toBeTruthy + matcher1.usePattern(patternNoDots) + expect(matcher1.matches()).toBeFalsy + } + + it("should respond to `lookingAt`") { + val matcher0 = Pattern.compile("S[a-z]+").matcher("Scala") + val matcher1 = Pattern.compile("S[a-z]+").matcher("Scala.js") + val matcher2 = Pattern.compile("[a-z]+").matcher("Scala.js") + + expect(matcher0.lookingAt()).toBeTruthy + expect(matcher1.lookingAt()).toBeTruthy + expect(matcher2.lookingAt()).toBeFalsy + + val matcher3 = Pattern.compile("S[a-z]+").matcher("Scala.js") + expect(matcher3.find()).toBeTruthy + expect(matcher3.lookingAt()).toBeTruthy + } + + it("should respond to `hitEnd`") { + val matcher0 = Pattern.compile("S[a-z]*").matcher("Scala.js") + expect(matcher0.find()).toBeTruthy + expect(matcher0.hitEnd).toBeFalsy + expect(matcher0.find()).toBeFalsy + expect(matcher0.hitEnd).toBeTruthy + + val matcher1 = Pattern.compile("[A-Za-z]+").matcher("Scala.js") + expect(matcher1.find()).toBeTruthy + expect(matcher1.hitEnd).toBeFalsy + expect(matcher1.group).toBe("Scala") + expect(matcher1.find()).toBeTruthy + expect(matcher1.hitEnd).toBeTruthy + expect(matcher1.group).toBe("js") + expect(matcher1.lookingAt()).toBeTruthy + expect(matcher1.group).toBe("Scala") + expect(matcher1.hitEnd).toBeFalsy + } + + it("should respond to `region`") { + val matcher0 = Pattern.compile("S[a-z]+").matcher("A Scalable Solution") + + val region0to3 = matcher0.region(0, 3) + expect(region0to3.regionStart).toBe(0) + expect(region0to3.regionEnd).toBe(3) + expect(region0to3.find()).toBeFalsy + + val region0to15 = matcher0.region(0, 15) + expect(region0to15.regionStart).toBe(0) + expect(region0to15.regionEnd).toBe(15) + expect(region0to15.find()).toBeTruthy + expect(region0to15.group).toEqual("Scalable") + + val region2to7 = region0to15.region(2, 7) + expect(region2to7.regionStart).toBe(2) + expect(region2to7.regionEnd).toBe(7) + expect(region2to7.find()).toBeTruthy + expect(region2to7.group).toEqual("Scala") + + val region5toEnd = matcher0.region(5, matcher0.regionEnd) + expect(region5toEnd.regionStart).toBe(5) + expect(region5toEnd.regionEnd).toBe(19) + expect(region5toEnd.find()).toBeTruthy + expect(region5toEnd.group).toEqual("Solution") + + val matcher1 = Pattern.compile("0[xX][A-Fa-f0-9]{3}$").matcher("In CSS, 0xc4fe is not a color") + + val region5to13 = matcher1.region(5, 13) + expect(region5to13.regionStart).toBe(5) + expect(region5to13.regionEnd).toBe(13) + expect(region5to13.find()).toBeTruthy + expect(region5to13.group).toEqual("0xc4f") + + val region5to20 = matcher1.region(5, 20) + expect(region5to20.regionStart).toBe(5) + expect(region5to20.regionEnd).toBe(20) + expect(region5to20.find()).toBeFalsy + } + + it("should respond to `appendReplacement` and `appendTail`") { + // From the JavaDoc + val matcher = Pattern.compile("cat").matcher("one cat two cats in the yard") + val sb = new StringBuffer + + while (matcher.find()) { + matcher.appendReplacement(sb, "dog") + } + matcher.appendTail(sb) + + expect(sb.toString).toBe("one dog two dogs in the yard") + } + + it("should respond to `replaceAll`") { + // From the JavaDoc + val matcher = Pattern.compile("a*b").matcher("aabfooaabfooabfoob") + expect(matcher.replaceAll("-")).toBe("-foo-foo-foo-") + } + + it("should respond to `replaceFirst`") { + // From the JavaDoc + val matcher = Pattern.compile("dog").matcher("zzzdogzzzdogzzz") + expect(matcher.replaceFirst("cat")).toBe("zzzcatzzzdogzzz") + } + + it("should throw exception if match accessors are called before `find`") { + def checkInvalidAccess(block: => Unit): Unit = { + val exception: Throwable = try { + block + throw new Error("No exception thrown") + } catch { + case e: Throwable => e + } + + expect(exception.getClass.getName).toBe("java.lang.IllegalStateException") + expect(exception.getMessage).toBe("No match available") + } + + val matcher = Pattern.compile("(Sc([a-z]*))").matcher("Scala.js") + + checkInvalidAccess { matcher.start } + checkInvalidAccess { matcher.end } + checkInvalidAccess { matcher.group } + checkInvalidAccess { matcher.group(42) } + + val matchResult = matcher.toMatchResult + + checkInvalidAccess { matchResult.start } + checkInvalidAccess { matchResult.end } + checkInvalidAccess { matchResult.group } + checkInvalidAccess { matchResult.group(42) } + } + + it("should correctly handle zero-length matches") { + val pat = Pattern.compile("a*?") + val mat = pat.matcher("aaaaa") + for (i <- 0 to 5) { + expect(mat.find()).toBeTruthy + expect(mat.start).toEqual(i) + expect(mat.end).toEqual(i) + } + + // Make sure we don't suddenly re-match + for (i <- 0 to 5) { + expect(mat.find()).toBeFalsy + } + } + + it("should support in-pattern flags - #997") { + val p0 = Pattern.compile("(?i)abc") + + expect(p0.flags() & Pattern.CASE_INSENSITIVE).not.toBe(0) + + val m0 = p0.matcher("abcABC") + + expect(m0.find()).toBeTruthy + expect(m0.group()).toEqual("abc") + expect(m0.find()).toBeTruthy + expect(m0.group()).toEqual("ABC") + expect(m0.find()).toBeFalsy + + val p1 = Pattern.compile("(?-i)abc", Pattern.CASE_INSENSITIVE) + + expect(p1.flags() & Pattern.CASE_INSENSITIVE).toBe(0) + + val m1 = p1.matcher("abcABC") + + expect(m1.find()).toBeTruthy + expect(m1.group()).toEqual("abc") + expect(m1.find()).toBeFalsy + } + + } +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/ShortTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/ShortTest.scala new file mode 100644 index 0000000..e59f2e9 --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/ShortTest.scala @@ -0,0 +1,66 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.javalib + +import java.lang.{Short => JShort} + +import org.scalajs.jasminetest.JasmineTest + +/** + * tests the implementation of the java standard library Short + */ +object ShortTest extends JasmineTest { + + describe("java.lang.Short") { + + it("should provide `compareTo`") { + def compare(x: Short, y: Short): Int = + new JShort(x).compareTo(new JShort(y)) + + expect(compare(0.toShort, 5.toShort)).toBeLessThan(0) + expect(compare(10.toShort, 9.toShort)).toBeGreaterThan(0) + expect(compare(-2.toShort, -1.toShort)).toBeLessThan(0) + expect(compare(3.toShort, 3.toShort)).toEqual(0) + } + + it("should be a Comparable") { + def compare(x: Any, y: Any): Int = + x.asInstanceOf[Comparable[Any]].compareTo(y) + + expect(compare(0.toShort, 5.toShort)).toBeLessThan(0) + expect(compare(10.toShort, 9.toShort)).toBeGreaterThan(0) + expect(compare(-2.toShort, -1.toShort)).toBeLessThan(0) + expect(compare(3.toShort, 3.toShort)).toEqual(0) + } + + it("should parse strings") { + def test(s: String, v: Short): Unit = { + expect(JShort.parseShort(s)).toEqual(v) + expect(JShort.valueOf(s).shortValue()).toEqual(v) + expect(new JShort(s).shortValue()).toEqual(v) + } + + test("0", 0) + test("5", 5) + test("127", 127) + test("-100", -100) + test("30000", 30000) + } + + it("should reject invalid strings when parsing") { + def test(s: String): Unit = + expect(() => JShort.parseShort(s)).toThrow + + test("abc") + test("") + test("60000") // out of range + test("-90000") // out of range + } + + } +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/StackTraceElementTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/StackTraceElementTest.scala new file mode 100644 index 0000000..b02a889 --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/StackTraceElementTest.scala @@ -0,0 +1,29 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.javalib + +import scala.scalajs.js + +import org.scalajs.jasminetest.JasmineTest + +object StackTraceElementTest extends JasmineTest { + + describe("java.lang.StackTraceElement") { + it("should use the magic columnNumber field in its toString") { + val st = new StackTraceElement("MyClass", "myMethod", "myFile.scala", 1) + st.asInstanceOf[js.Dynamic].columnNumber = 5 + expect(st.toString).toEqual("MyClass.myMethod(myFile.scala:1:5)") + } + + it("should leave toString unmodified without magic columnNumber") { + val st = new StackTraceElement("MyClass", "myMethod", "myFile.scala", 1) + expect(st.toString).toEqual("MyClass.myMethod(myFile.scala:1)") + } + } + +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/StreamsTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/StreamsTest.scala new file mode 100644 index 0000000..6975614 --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/StreamsTest.scala @@ -0,0 +1,308 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.javalib + +import java.io._ + +import scala.language.implicitConversions + +import scala.scalajs.js + +import org.scalajs.jasminetest.JasmineTest + +/** Tests for our implementation of java.io._ stream classes */ +object StreamsTest extends JasmineTest with CommonStreamsTests { + + // Need to define this again, otherwise conversion on function + // triggers for Seqs + override implicit def traversable2array[T]( + a: TraversableOnce[T]): js.Array[T] = super.traversable2array(a) + + describe("java.io.InputStream") { + + class DummyInputStream(val length: Int) extends InputStream { + private var i: Int = 0 + def read(): Int = if (i < length) { i += 1; i } else -1 + } + + it("should provide a default implementation of `read` to an array") { + val stream = new DummyInputStream(200) + + val buf = new Array[Byte](50) + + // Should read first 50 bytes (next: 51) + expect(stream.read(buf)).toBe(50) + expect(buf).toEqual((1 to 50)) + + // Should read another 20 (next: 71) + expect(stream.read(buf, 10, 20)).toBe(20) + expect(buf).toEqual((1 to 10) ++ (51 to 70) ++ (31 to 50)) + + // Test some Exception conditions + expect(() => stream.read(null, 0, 10)).toThrow + expect(() => stream.read(buf, -1, 10)).toThrow + expect(() => stream.read(buf, 0, -1)).toThrow + expect(() => stream.read(buf, 10, 100)).toThrow + // Buffer should be unmodified + expect(buf).toEqual((1 to 10) ++ (51 to 70) ++ (31 to 50)) + + // Should read nothing (next: 71) + expect(stream.read(buf, 10, 0)).toBe(0) + expect(buf).toEqual((1 to 10) ++ (51 to 70) ++ (31 to 50)) + + // Skip 40 bytes (next: 111) + expect(stream.skip(40)).toBe(40) + + // Read 50 bytes, should wrap (next: 161) + expect(stream.read(buf)).toBe(50) + expect(buf).toEqual((111 to 127) ++ (-128 to -96)) + + // Read 45 bytes, should read 40 (next: EOF) + expect(stream.read(buf, 5, 45)).toBe(40) + expect(buf).toEqual((111 to 115) ++ (-95 to -56) ++ (-100 to -96)) + + // Read 50 bytes, should read nothing + expect(stream.read(buf)).toBe(-1) + expect(stream.read(buf, 0, 0)).toBe(0) + expect(buf).toEqual((111 to 115) ++ (-95 to -56) ++ (-100 to -96)) + } + + it("should provide a default implementation of `skip`") { + val stream = new DummyInputStream(10) + + expect(stream.skip(5)).toBe(5) + expect(stream.read()).toBe(6) + expect(stream.skip(1)).toBe(1) + expect(stream.read()).toBe(8) + expect(stream.skip(-5)).toBe(0) + expect(stream.read()).toBe(9) + expect(stream.skip(0)).toBe(0) + expect(stream.read()).toBe(10) + expect(stream.skip(10)).toBe(0) + } + + } + + describe("java.io.ByteArrayInputStream") { + byteArrayInputStreamLikeTests(seq => + new ByteArrayInputStream(seq.map(_.toByte).toArray)) + } + + describe("java.io.ByteArrayOutputStream") { + + it("should support simple write(x: Int)") { + val out = new ByteArrayOutputStream() + + for (i <- 0 to 9) + out.write(i) + + expect(out.toByteArray).toEqual(js.Array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)) + } + + it("should support simple write(x: Array[Byte])") { + val out = new ByteArrayOutputStream() + val arr = Array[Byte](0, 1, 2, 3, 4, 5) + + out.write(arr, 1, 4) + out.write(arr) + + expect(out.toByteArray).toEqual(js.Array(1, 2, 3, 4, 0, 1, 2, 3, 4, 5)) + } + + it("should support write(x: Array[Byte]) with buffer resize") { + val out = new ByteArrayOutputStream(16) + val arr = Array[Byte](0, 1, 2, 3, 4, 5, 6, 7, 8, 9) + + out.write(arr) + out.write(arr) + + expect(out.toByteArray).toEqual(arr ++ arr) + } + + it("should support toString (with UTF8)") { + val buf = Array[Byte](72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, + 46, -29, -127, -109, -29, -126, -109, -29, -127, -85, -29, -127, -95, + -29, -127, -81, -26, -105, -91, -26, -100, -84, -24, -86, -98, -29, + -126, -110, -24, -86, -83, -29, -126, -127, -29, -127, -66, -29, -127, + -103, -29, -127, -117, -29, -128, -126) + + val out = new ByteArrayOutputStream() + out.write(buf) + + expect(out.toString).toEqual("Hello World.こんにちは日本語を読めますか。") + } + + it("should support reset()") { + val out = new ByteArrayOutputStream() + for (i <- 0 to 9) out.write(i) + out.reset() + for (i <- 0 to 9) out.write(i) + + expect(out.toByteArray).toEqual(js.Array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)) + } + + } + +} + +/** tests also used by typedarray.ArrayBufferInputStreamTests */ +trait CommonStreamsTests extends JasmineTest { + + implicit def traversable2array[T](a: TraversableOnce[T]): js.Array[T] = { + import js.JSConverters._ + a.toJSArray + } + + implicit def array2array[T](a: Array[T]): js.Array[T] = { + import js.JSConverters._ + a.toJSArray + } + + def byteArrayInputStreamLikeTests(mkStream: Seq[Int] => InputStream): Unit = { + val length = 50 + def newStream = mkStream(1 to length) + + it("should provide `read()`") { + val stream = newStream + + for (i <- 1 to length) + expect(stream.read()).toBe(i) + + for (_ <- 1 to 5) + expect(stream.read()).toBe(-1) + } + + it("should provide `read(buf)`") { + val stream = newStream + val buf = new Array[Byte](10) + + expect(stream.read(buf)).toBe(10) + expect(buf).toEqual(1 to 10) + + expect(stream.skip(35)).toBe(35) + + expect(stream.read(buf)).toBe(5) + expect(buf).toEqual((46 to 50) ++ (6 to 10)) + + expect(stream.read(buf)).toBe(-1) + expect(stream.read()).toBe(-1) + } + + it("should provide full-argument `read`") { + val stream = newStream + val buf = new Array[Byte](20) + + expect(stream.read(buf, 10, 5)).toBe(5) + expect(buf).toEqual(Seq.fill(10)(0) ++ (1 to 5) ++ Seq.fill(5)(0)) + + expect(stream.read(buf, 0, 20)).toBe(20) + expect(buf).toEqual(6 to 25) + + expect(stream.read(buf, 10, 0)).toBe(0) + expect(buf).toEqual(6 to 25) + + expect(() => stream.read(buf, -1, 0)).toThrow + expect(() => stream.read(buf, 0, -1)).toThrow + expect(() => stream.read(buf, 100, 0)).toThrow + expect(() => stream.read(buf, 10, 100)).toThrow + expect(buf).toEqual(6 to 25) + + expect(stream.skip(20)).toBe(20) + + expect(stream.read(buf, 0, 10)).toBe(5) + expect(buf).toEqual((46 to 50) ++ (11 to 25)) + + expect(stream.read(buf, 0, 10)).toBe(-1) + expect(stream.read(buf, 0, 0)).toBe(0) + expect(buf).toEqual((46 to 50) ++ (11 to 25)) + + } + + it("should provide `available`") { + val stream = newStream + + def mySkip(n: Int) = for (_ <- 1 to n) expect(stream.read()).not.toBe(-1) + def check(n: Int) = expect(stream.available).toBe(n) + + check(50) + mySkip(5) + check(45) + expect(stream.skip(10)).toBe(10) + check(35) + mySkip(30) + check(5) + expect(stream.skip(20)).toBe(5) + check(0) + } + + it("should provide `skip`") { + val stream = newStream + + expect(stream.skip(7)).toBe(7) + + for (i <- 8 to 32) + expect(stream.read()).toBe(i) + + expect(stream.skip(0)).toBe(0) + expect(stream.read()).toBe(33) + expect(stream.skip(-4)).toBe(0) + expect(stream.read()).toBe(34) + + expect(stream.skip(30)).toBe(16) + expect(stream.skip(30)).toBe(0) + } + + it("should return true from `markSupported`") { + expect(newStream.markSupported).toBe(true) + } + + it("should provide no-op `close`") { + val stream = newStream + + for (i <- 1 to length) { + stream.close() + expect(stream.read()).toBe(i) + } + } + + it("should provide `mark`/`reset`") { + val stream = newStream + + def read(range: Range) = for (i <- range) expect(stream.read()).toBe(i) + + read(1 to 10) + stream.reset() // mark must be 0 at creation + read(1 to 5) + stream.mark(length) + read(6 to 22) + stream.reset() + read(6 to 20) + stream.reset() + read(6 to 25) + stream.reset() + expect(stream.skip(40)).toBe(40) + stream.mark(length) + read(46 to 50) + stream.reset() + read(46 to 50) + stream.mark(length) + expect(stream.read()).toBe(-1) + stream.reset() + expect(stream.read()).toBe(-1) + } + + it("should return positive integers when calling read") { + val stream = mkStream(Seq(-1, -2, -3)) + expect(stream.read()).toBe(255) + expect(stream.read()).toBe(254) + expect(stream.read()).toBe(253) + expect(stream.read()).toBe(-1) + } + } + +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/StringBufferTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/StringBufferTest.scala new file mode 100644 index 0000000..a034ce6 --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/StringBufferTest.scala @@ -0,0 +1,219 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.javalib + +import scala.reflect.{classTag, ClassTag} +import scala.scalajs.js +import org.scalajs.jasminetest.JasmineTest + +object StringBufferTest extends JasmineTest { + + def shouldThrow[T : ClassTag](fn: => Unit) = + try { + fn + expect("exception").toBe("thrown") + } catch { + case e: T => + case x: Throwable => expect(x.toString).toBe(classTag[T].runtimeClass.getSimpleName) + } + + describe("java.lang.StringBuffer") { + + def newBuf = new java.lang.StringBuffer + def initBuf(str: String) = new java.lang.StringBuffer(str) + + it("should respond to `append`") { + expect(newBuf.append("asdf").toString).toEqual("asdf") + expect(newBuf.append(null: AnyRef).toString).toEqual("null") + expect(newBuf.append(null: String).toString).toEqual("null") + expect(newBuf.append(null: CharSequence,0,2).toString).toEqual("nu") + expect(newBuf.append(js.undefined).toString).toEqual("undefined") + expect(newBuf.append(true).toString).toEqual("true") + expect(newBuf.append('a').toString).toEqual("a") + expect(newBuf.append(Array('a','b','c','d')).toString).toEqual("abcd") + expect(newBuf.append(Array('a','b','c','d'), 1, 2).toString).toEqual("bc") + expect(newBuf.append(4.toByte).toString).toEqual("4") + expect(newBuf.append(304.toShort).toString).toEqual("304") + expect(newBuf.append(100000).toString).toEqual("100000") + expect(newBuf.append(2.5f).toString).toEqual("2.5") + expect(newBuf.append(3.5).toString).toEqual("3.5") + } + + it("should respond to `insert`") { + expect(newBuf.insert(0, "asdf").toString).toEqual("asdf") + expect(newBuf.insert(0, null: AnyRef).toString).toEqual("null") + expect(newBuf.insert(0, null: String).toString).toEqual("null") + expect(newBuf.insert(0, null: CharSequence,0,2).toString).toEqual("nu") + expect(newBuf.insert(0, js.undefined).toString).toEqual("undefined") + expect(newBuf.insert(0, true).toString).toEqual("true") + expect(newBuf.insert(0, 'a').toString).toEqual("a") + expect(newBuf.insert(0, Array('a','b','c','d')).toString).toEqual("abcd") + expect(newBuf.insert(0, Array('a','b','c','d'), 1, 2).toString).toEqual("bc") + expect(newBuf.insert(0, 4.toByte).toString).toEqual("4") + expect(newBuf.insert(0, 304.toShort).toString).toEqual("304") + expect(newBuf.insert(0, 100000).toString).toEqual("100000") + expect(newBuf.insert(0, 2.5f).toString).toEqual("2.5") + expect(newBuf.insert(0, 3.5).toString).toEqual("3.5") + + expect(initBuf("adef").insert(1, "bc")).toEqual("abcdef") + expect(initBuf("abcd").insert(4, "ef")).toEqual("abcdef") + expect(initBuf("adef").insert(1, Array('b','c'))).toEqual("abcdef") + expect(initBuf("adef").insert(1, initBuf("bc"))).toEqual("abcdef") + expect(initBuf("abef").insert(2, Array('a','b','c','d','e'), 2, 2)).toEqual("abcdef") + expect(initBuf("abef").insert(2, initBuf("abcde"), 2, 4)).toEqual("abcdef") + + shouldThrow[StringIndexOutOfBoundsException](initBuf("abcd").insert(5, "whatever")) + shouldThrow[StringIndexOutOfBoundsException](initBuf("abcd").insert(-1, "whatever")) + } + + it("should respond to `deleteCharAt`") { + expect(initBuf("0123").deleteCharAt(1).toString).toEqual("023") + expect(initBuf("0123").deleteCharAt(0).toString).toEqual("123") + expect(initBuf("0123").deleteCharAt(3).toString).toEqual("012") + shouldThrow[StringIndexOutOfBoundsException](initBuf("0123").deleteCharAt(-1)) + shouldThrow[StringIndexOutOfBoundsException](initBuf("0123").deleteCharAt(4)) + } + + it("should respond to `replace`") { + expect(initBuf("0123").replace(1,3,"bc").toString).toEqual("0bc3") + expect(initBuf("0123").replace(0,4,"abcd").toString).toEqual("abcd") + expect(initBuf("0123").replace(0,10,"abcd").toString).toEqual("abcd") + expect(initBuf("0123").replace(3,10,"defg").toString).toEqual("012defg") + expect(initBuf("0123").replace(0,1,"xxxx").toString).toEqual("xxxx123") + expect(initBuf("0123").replace(1,1,"xxxx").toString).toEqual("0xxxx123") + + shouldThrow[StringIndexOutOfBoundsException](initBuf("0123").replace(-1,3,"x")) + shouldThrow[StringIndexOutOfBoundsException](initBuf("0123").replace(4,5,"x")) + } + + it("should respond to `setCharAt`") { + val buf = newBuf + buf.append("foobar") + + buf.setCharAt(2, 'x') + expect(buf.toString).toEqual("foxbar") + + buf.setCharAt(5, 'h') + expect(buf.toString).toEqual("foxbah") + + expect(() => buf.setCharAt(-1, 'h')).toThrow + expect(() => buf.setCharAt(6, 'h')).toThrow + } + + it("should properly setLength") { + val buf = newBuf + buf.append("foobar") + + expect(() => buf.setLength(-3)).toThrow + + expect({ buf.setLength(3); buf.toString }).toEqual("foo") + expect({ buf.setLength(6); buf.toString }).toEqual("foo\u0000\u0000\u0000") + } + + } + + describe("java.lang.StringBuilder") { + + def newBuilder = new java.lang.StringBuilder + def initBuilder(str: String) = new java.lang.StringBuilder(str) + + it("should respond to `append`") { + expect(newBuilder.append("asdf").toString).toEqual("asdf") + expect(newBuilder.append(null: AnyRef).toString).toEqual("null") + expect(newBuilder.append(null: String).toString).toEqual("null") + expect(newBuilder.append(null: CharSequence,0,2).toString).toEqual("nu") + expect(newBuilder.append(js.undefined).toString).toEqual("undefined") + expect(newBuilder.append(true).toString).toEqual("true") + expect(newBuilder.append('a').toString).toEqual("a") + expect(newBuilder.append(Array('a','b','c','d')).toString).toEqual("abcd") + expect(newBuilder.append(Array('a','b','c','d'), 1, 2).toString).toEqual("bc") + expect(newBuilder.append(4.toByte).toString).toEqual("4") + expect(newBuilder.append(304.toShort).toString).toEqual("304") + expect(newBuilder.append(100000).toString).toEqual("100000") + expect(newBuilder.append(2.5f).toString).toEqual("2.5") + expect(newBuilder.append(3.5).toString).toEqual("3.5") + } + + it("should respond to `insert`") { + expect(newBuilder.insert(0, "asdf").toString).toEqual("asdf") + expect(newBuilder.insert(0, null: AnyRef).toString).toEqual("null") + expect(newBuilder.insert(0, null: String).toString).toEqual("null") + expect(newBuilder.insert(0, null: CharSequence,0,2).toString).toEqual("nu") + expect(newBuilder.insert(0, js.undefined).toString).toEqual("undefined") + expect(newBuilder.insert(0, true).toString).toEqual("true") + expect(newBuilder.insert(0, 'a').toString).toEqual("a") + expect(newBuilder.insert(0, Array('a','b','c','d')).toString).toEqual("abcd") + expect(newBuilder.insert(0, Array('a','b','c','d'), 1, 2).toString).toEqual("bc") + expect(newBuilder.insert(0, 4.toByte).toString).toEqual("4") + expect(newBuilder.insert(0, 304.toShort).toString).toEqual("304") + expect(newBuilder.insert(0, 100000).toString).toEqual("100000") + expect(newBuilder.insert(0, 2.5f).toString).toEqual("2.5") + expect(newBuilder.insert(0, 3.5).toString).toEqual("3.5") + + expect(initBuilder("adef").insert(1, "bc")).toEqual("abcdef") + expect(initBuilder("abcd").insert(4, "ef")).toEqual("abcdef") + expect(initBuilder("adef").insert(1, Array('b','c'))).toEqual("abcdef") + expect(initBuilder("adef").insert(1, initBuilder("bc"))).toEqual("abcdef") + expect(initBuilder("abef").insert(2, Array('a','b','c','d','e'), 2, 2)).toEqual("abcdef") + expect(initBuilder("abef").insert(2, initBuilder("abcde"), 2, 4)).toEqual("abcdef") + + shouldThrow[StringIndexOutOfBoundsException](initBuilder("abcd").insert(5, "whatever")) + shouldThrow[StringIndexOutOfBoundsException](initBuilder("abcd").insert(-1, "whatever")) + } + + it("should allow string interpolation to survive `null` and `undefined`") { + expect(s"${null}").toEqual("null") + expect(s"${js.undefined}").toEqual("undefined") + } + + it("should respond to `deleteCharAt`") { + expect(initBuilder("0123").deleteCharAt(1).toString).toEqual("023") + expect(initBuilder("0123").deleteCharAt(0).toString).toEqual("123") + expect(initBuilder("0123").deleteCharAt(3).toString).toEqual("012") + shouldThrow[StringIndexOutOfBoundsException](initBuilder("0123").deleteCharAt(-1)) + shouldThrow[StringIndexOutOfBoundsException](initBuilder("0123").deleteCharAt(4)) + } + + it("should respond to `replace`") { + expect(initBuilder("0123").replace(1,3,"bc").toString).toEqual("0bc3") + expect(initBuilder("0123").replace(0,4,"abcd").toString).toEqual("abcd") + expect(initBuilder("0123").replace(0,10,"abcd").toString).toEqual("abcd") + expect(initBuilder("0123").replace(3,10,"defg").toString).toEqual("012defg") + expect(initBuilder("0123").replace(0,1,"xxxx").toString).toEqual("xxxx123") + expect(initBuilder("0123").replace(1,1,"xxxx").toString).toEqual("0xxxx123") + + shouldThrow[StringIndexOutOfBoundsException](initBuilder("0123").replace(-1,3,"x")) + shouldThrow[StringIndexOutOfBoundsException](initBuilder("0123").replace(4,5,"x")) + } + + it("should respond to `setCharAt`") { + val b = newBuilder + b.append("foobar") + + b.setCharAt(2, 'x') + expect(b.toString).toEqual("foxbar") + + b.setCharAt(5, 'h') + expect(b.toString).toEqual("foxbah") + + expect(() => b.setCharAt(-1, 'h')).toThrow + expect(() => b.setCharAt(6, 'h')).toThrow + } + + it("should properly setLength") { + val b = newBuilder + b.append("foobar") + + expect(() => b.setLength(-3)).toThrow + + expect({ b.setLength(3); b.toString }).toEqual("foo") + expect({ b.setLength(6); b.toString }).toEqual("foo\u0000\u0000\u0000") + } + + } +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/StringTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/StringTest.scala new file mode 100644 index 0000000..a49e521 --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/StringTest.scala @@ -0,0 +1,237 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.javalib + +import scala.scalajs.js +import scala.scalajs.js.JSConverters._ + +import org.scalajs.jasminetest.JasmineTest + +object StringTest extends JasmineTest { + + describe("java.lang.String") { + + it("should respond to `length`") { + expect("Scala.js".length).toEqual(8) + expect("".length).toEqual(0) + } + + it("should respond to `intern`") { + val s = "Scala.js" + expect(s.intern).toEqual(s) + } + + it("should respond to `equals`") { + expect("Scala.js".equals("Scala.js")).toBeTruthy + expect("Scala.js".equals("Java")).toBeFalsy + } + + it("should respond to `equalsIgnoreCase`") { + expect("Scala.JS".equalsIgnoreCase("Scala.js")).toBeTruthy + expect("åløb".equalsIgnoreCase("ÅLØb")).toBeTruthy + expect("Scala.js".equalsIgnoreCase("Java")).toBeFalsy + expect("Scala.js".equalsIgnoreCase(null)).toBeFalsy + } + + it("should respond to `compareTo`") { + expect("Scala.js".compareTo("Scala")).toBeGreaterThan(0) + expect("Scala.js".compareTo("Scala.js")).toBe(0) + expect("Scala.js".compareTo("banana")).toBeLessThan(0) + } + + it("should respond to `compareToIgnoreCase`") { + expect("Scala.JS".compareToIgnoreCase("Scala.js")).toBe(0) + expect("Scala.JS".compareToIgnoreCase("scala")).toBeGreaterThan(0) + expect("åløb".compareToIgnoreCase("ÅLØB")).toBe(0) + expect("Java".compareToIgnoreCase("Scala")).toBeLessThan(0) + } + + it("should respond to `isEmpty`") { + expect("Scala.js".isEmpty).toBeFalsy + expect("".isEmpty).toBeTruthy + } + + it("should respond to `contains`") { + expect("Scala.js".contains("Scala")).toBeTruthy + expect("Scala.js".contains("Scala.js")).toBeTruthy + expect("ananas".contains("na")).toBeTruthy + expect("Scala.js".contains("scala")).toBeFalsy + } + + it("should respond to `startWith`") { + expect("Scala.js".startsWith("Scala")).toBeTruthy + expect("Scala.js".startsWith("Scala.js")).toBeTruthy + expect("Scala.js".startsWith("scala")).toBeFalsy + expect("ananas".startsWith("an")).toBeTruthy + } + + it("should respond to `endsWith`") { + expect("Scala.js".endsWith("js")).toBeTruthy + expect("Scala.js".endsWith("Scala.js")).toBeTruthy + expect("Scala.js".endsWith("JS")).toBeFalsy + expect("banana".endsWith("na")).toBeTruthy + } + + it("should respond to `indexOf(String)`") { + expect("Scala.js".indexOf("js")).toBe(6) + expect("Scala.js".indexOf("Scala.js")).toBe(0) + expect("ananas".indexOf("na")).toBe(1) + expect("Scala.js".indexOf("Java")).toBe(-1) + } + + it("should respond to `indexOf(int)`") { + expect("abc\uD834\uDF06def\uD834\uDF06def".indexOf(0x61)).toEqual(0) + expect("abc\uD834\uDF06def\uD834\uDF06def".indexOf(0x1D306)).toEqual(3) + expect("abc\uD834\uDF06def\uD834\uDF06def".indexOf(0xD834)).toEqual(3) + expect("abc\uD834\uDF06def\uD834\uDF06def".indexOf(0xDF06)).toEqual(4) + expect("abc\uD834\uDF06def\uD834\uDF06def".indexOf(0x64)).toEqual(5) + } + + it("should respond to `lastIndexOf(String)`") { + expect("Scala.js".lastIndexOf("Scala.js")).toBe(0) + expect("ananas".lastIndexOf("na")).toBe(3) + expect("Scala.js".lastIndexOf("Java")).toBe(-1) + } + + it("should respond to `lastIndexOf(int)`") { + expect("abc\uD834\uDF06def\uD834\uDF06def".lastIndexOf(0x61)).toEqual(0) + expect("abc\uD834\uDF06def\uD834\uDF06def".lastIndexOf(0x1D306)).toEqual(8) + expect("abc\uD834\uDF06def\uD834\uDF06def".lastIndexOf(0xD834)).toEqual(8) + expect("abc\uD834\uDF06def\uD834\uDF06def".lastIndexOf(0xDF06)).toEqual(9) + expect("abc\uD834\uDF06def\uD834\uDF06def".lastIndexOf(0x64)).toEqual(10) + } + + it("should respond to `toUpperCase`") { + expect("Scala.js".toUpperCase()).toBe("SCALA.JS") + } + + it("should respond to `toLowerCase`") { + expect("Scala.js".toLowerCase()).toBe("scala.js") + } + + it("should respond to `charAt`") { + expect("Scala.js".charAt(5)).toBe('.') + expect("Scala.js".charAt(6)).not.toBe('.') + } + + when("compliant-asinstanceofs"). + it("charAt() should throw with out-of-bound indices") { + // Type Strings both as CharSequence and String. One will invoke the + // helper, the other directly the method on RuntimeString. + expect(() => ("Scala.js": CharSequence).charAt(-3)).toThrow + expect(() => ("Scala.js": CharSequence).charAt(20)).toThrow + expect(() => "Scala.js".charAt(-3)).toThrow + expect(() => "Scala.js".charAt(20)).toThrow + } + + it("should respond to `codePointAt`") { + // String that starts with a BMP symbol + expect("abc\uD834\uDF06def".codePointAt(0)).toEqual(0x61) + expect("abc\uD834\uDF06def".codePointAt(3)).toEqual(0x1D306) + expect("abc\uD834\uDF06def".codePointAt(4)).toEqual(0xDF06) + expect("abc\uD834\uDF06def".codePointAt(5)).toEqual(0x64) + + // String that starts with an astral symbol + expect("\uD834\uDF06def".codePointAt(0)).toEqual(0x1D306) + expect("\uD834\uDF06def".codePointAt(1)).toEqual(0xDF06) + + // Lone high surrogates + expect("\uD834abc".codePointAt(0)).toEqual(0xD834) + + // Lone low surrogates + expect("\uDF06abc".codePointAt(0)).toEqual(0xDF06) + } + + it("should respond to `subSequence`") { + expect("Scala.js".subSequence(0, 5)).toBe("Scala") + expect("Scala.js".subSequence(6, 8)).toBe("js") + expect("Scala.js".subSequence(3, 5)).toBe("la") + expect("Scala.js".subSequence(3, 3)).toBe("") + } + + it("should respond to `replace`") { + expect("Scala.js".replace(".js", "")).toBe("Scala") + expect("Scala.js".replace("JS", "")).toBe("Scala.js") + expect("aa".replace('a', 'b')).toBe("bb") // #25 + } + + it("should respond to `matches`") { + expect("Scala.js".matches(".*js")).toBeTruthy + expect("Scala.js".matches(".*JS")).toBeFalsy + } + + it("should respond to `split`") { + expect("Scala.js".split("a").toJSArray).toEqual(js.Array("Sc", "l", ".js")) + expect("asdf".split("").toJSArray).toEqual(js.Array("","a","s","d","f")) + expect("asdf".split("", -1).toJSArray).toEqual(js.Array("","a","s","d","f", "")) + } + + it("should respond to `split` with char as argument") { + expect("Scala.js".split('.').toJSArray).toEqual(js.Array("Scala","js")) + for (i <- 0 to 32) { + val c = i.toChar + expect(s"blah${c}blah${c}blah${c}blah".split(c).toJSArray).toEqual( + js.Array("blah", "blah", "blah", "blah")) + } + } + + it("should respond to `toCharArray`") { + expect("Scala.js".toCharArray()(5)).toEqual('.') + } + + it("should respond to `hashCode`") { + expect("a`jkxzcbfaslkjfbkj,289oinkasdf".hashCode()).toEqual(-1395193631) + expect("-34".hashCode()).toEqual(44878) + expect("".hashCode()).toEqual(0) + } + + it("should respond to `getChars`") { + val trg = new Array[Char](10) + "asdf_foo".getChars(2, 6, trg, 3) + val exp = Array(0,0,0,'d','f','_','f',0,0,0) + + for ((i,e) <- trg zip exp) { + expect(i).toEqual(e) + } + } + + + it("should respond to `concat`") { + expect("asdf".concat("fdsa")).toEqual("asdffdsa") + } + + it("should respond to constructors") { + val charArray = + Array('a', 'b', 'c', 'd', '\uD834', '\uDF06', 'e', 'f', 'g', 'h', 'i') + val codePointArray = + Array(65, 0x1D306, 67, 68, 0xD834, 69, 72, 0xDF06) + + expect(new String()).toEqual("") + expect(new String(charArray)).toEqual("abcd\uD834\uDF06efghi") + expect(new String(charArray, 3, 5)).toEqual("d\uD834\uDF06ef") + expect(new String(codePointArray, 1, 5)).toEqual("\uD834\uDF06CD\uD834E") + expect(new String("foo")).toEqual("foo") + expect(new String(new StringBuffer("buffer-foo"))).toEqual("buffer-foo") + expect(new String(new java.lang.StringBuilder("builder-foo")) + ).toEqual("builder-foo") + } + + it("should provide `format`") { + expect(String.format("%d", new Integer(5))).toEqual("5") + expect(String.format("%05d", new Integer(5))).toEqual("00005") + expect(String.format("%0#5x", new Integer(5))).toEqual("0x005") + expect(String.format("%#5x", new Integer(5))).toEqual(" 0x5") + expect(String.format("%#5X", new Integer(5))).toEqual(" 0X5") + expect(String.format("%5d", new Integer(-10))).toEqual(" -10") + expect(String.format("%05d", new Integer(-10))).toEqual("-0010") + expect(String.format("%x", new Integer(-3))).toEqual("fffffffd") + expect(String.format("%x", new java.lang.Byte(-4.toByte))).toEqual("fffffffc") + } + + } +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/SystemTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/SystemTest.scala new file mode 100644 index 0000000..4435e00 --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/SystemTest.scala @@ -0,0 +1,118 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.javalib + +import language.implicitConversions + +import scala.scalajs.js +import scala.scalajs.js.JSConverters._ + +import org.scalajs.jasminetest.JasmineTest + +object SystemTest extends JasmineTest { + + // Just in here, we allow ourselves to do this + implicit def array2jsArray[T](arr: Array[T]): js.Array[T] = arr.toJSArray + + describe("java.lang.System") { + + it("should respond to `arraycopy`") { + val object0 = Array[Any]("[", "b", "c", "d", "e", "f", "]") + val object1 = Array[Any](() => true, 1, "2", '3', 4.0, true, object0) + + System.arraycopy(object1, 1, object0, 1, 5) + expect(object0.mkString).toEqual("[1234true]") + + val string0 = Array("a", "b", "c", "d", "e", "f") + val string1 = Array("1", "2", "3", "4") + + System.arraycopy(string1, 0, string0, 3, 3) + expect(string0.mkString).toEqual("abc123") + + val ab01Chars = Array("ab".toCharArray, "01".toCharArray) + val chars = new Array[Array[Char]](32) + System.arraycopy(ab01Chars, 0, chars, 0, 2) + for (i <- Seq(0, 2, 4, 8, 16)) { + System.arraycopy(chars, i / 4, chars, i, i) + } + + expect(chars.filter(_ == null).length).toEqual(12) + expect(chars.filter(_ != null).map(_.mkString).mkString). + toEqual("ab01ab0101ab01ab0101ab0101ab01ab0101ab01") + } + + it("should respond to `arraycopy` with range overlaps for the same array") { + val array = new Array[Int](10) + + for (i <- 1 to 6) { + array(i) = i + } + + expect(array).toEqual(Array(0, 1, 2, 3, 4, 5, 6, 0, 0, 0)) + System.arraycopy(array, 0, array, 3, 7) + expect(array).toEqual(Array(0, 1, 2, 0, 1, 2, 3, 4, 5, 6)) + + System.arraycopy(array, 0, array, 1, 0) + expect(array).toEqual(Array(0, 1, 2, 0, 1, 2, 3, 4, 5, 6)) + + System.arraycopy(array, 0, array, 1, 9) + expect(array).toEqual(Array(0, 0, 1, 2, 0, 1, 2, 3, 4, 5)) + + System.arraycopy(array, 1, array, 0, 9) + expect(array).toEqual(Array(0, 1, 2, 0, 1, 2, 3, 4, 5, 5)) + + System.arraycopy(array, 0, array, 0, 10) + expect(array).toEqual(Array(0, 1, 2, 0, 1, 2, 3, 4, 5, 5)) + + val reversed = array.reverse + System.arraycopy(reversed, 5, array, 5, 5) + expect(array).toEqual(Array(0, 1, 2, 0, 1, 1, 0, 2, 1, 0)) + } + + it("should provide identityHashCode") { + /* This test is more restrictive than the spec, but we know our + * implementation will always pass the test. + */ + class HasIDHashCode + + val x1 = new HasIDHashCode + val x2 = new HasIDHashCode + val x1FirstHash = x1.hashCode() + expect(x1.hashCode()).toEqual(x1FirstHash) + expect(x1.hashCode()).not.toEqual(x2.hashCode()) + expect(x1.hashCode()).toEqual(x1FirstHash) + + expect(System.identityHashCode(x1)).toEqual(x1FirstHash) + expect(System.identityHashCode(x2)).toEqual(x2.hashCode()) + } + + it("identityHashCode should by-pass .hashCode()") { + val list1 = List(1, 3, 5) + val list2 = List(1, 3, 5) + expect(list1 == list2).toBeTruthy + expect(list1.hashCode()).toEqual(list2.hashCode()) + expect(System.identityHashCode(list1)).not.toEqual(System.identityHashCode(list2)) + } + + it("identityHashCode(null)") { + expect(System.identityHashCode(null)).toEqual(0) + } + + it("identityHashCode of values implemented as JS primitives") { + expect(System.identityHashCode("foo")).toEqual("foo".hashCode()) + expect(System.identityHashCode("")).toEqual("".hashCode()) + + expect(System.identityHashCode(false)).toEqual(false.hashCode()) + expect(System.identityHashCode(true)).toEqual(true.hashCode()) + + expect(System.identityHashCode(5)).toEqual(5.hashCode()) + expect(System.identityHashCode(789456)).toEqual(789456.hashCode()) + } + + } +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/ThrowablesTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/ThrowablesTest.scala new file mode 100644 index 0000000..7e53975 --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/ThrowablesTest.scala @@ -0,0 +1,90 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.javalib + +import org.scalajs.jasminetest.JasmineTest + +object ThrowablesTest extends JasmineTest { + describe("java.lang.Throwables, java.util.Throwables") { + + it("should define all java.lang and java.util Errors/Exceptions") { + new ArithmeticException() + new ArrayIndexOutOfBoundsException() + new ArrayStoreException() + new ClassCastException() + new ClassNotFoundException() + new CloneNotSupportedException() + // Needs an instance of java.lang.Enum. + // import scala.language.existentials + // new EnumConstantNotPresentException(null.asInstanceOf[Class[_ <: Enum[T] forSome { type T <: Enum[T] }]], null) + new Exception() + new IllegalAccessException() + new IllegalArgumentException() + new IllegalMonitorStateException() + new IllegalStateException() + new IllegalThreadStateException() + new IndexOutOfBoundsException() + new InstantiationException() + new InterruptedException() + new NegativeArraySizeException() + new NoSuchFieldException() + new NoSuchMethodException() + new NullPointerException() + new NumberFormatException() + new RuntimeException() + new SecurityException() + new StringIndexOutOfBoundsException() + new TypeNotPresentException(null, null) + new UnsupportedOperationException() + new AbstractMethodError() + new AssertionError() + new ClassCircularityError() + new ClassFormatError() + new Error() + new ExceptionInInitializerError() + new IllegalAccessError() + new IncompatibleClassChangeError() + new InstantiationError() + new InternalError() + new LinkageError() + new NoClassDefFoundError() + new NoSuchFieldError() + new NoSuchMethodError() + new OutOfMemoryError() + new StackOverflowError() + new UnknownError() + new UnsatisfiedLinkError() + new UnsupportedClassVersionError() + new VerifyError() + new VirtualMachineError() {} + + import java.util._ + new ServiceConfigurationError("") + new ConcurrentModificationException() + new DuplicateFormatFlagsException("") + new EmptyStackException() + new FormatFlagsConversionMismatchException("", '\u0000') + new FormatterClosedException() + new IllegalFormatCodePointException(0) + new IllegalFormatConversionException('\u0000', new Object().getClass) + new IllegalFormatFlagsException("") + new IllegalFormatPrecisionException(0) + new IllegalFormatWidthException(0) + new InputMismatchException() + // Needs java.io.IOException. + // new InvalidPropertiesFormatException("") + new MissingFormatArgumentException("") + new MissingFormatWidthException("") + new MissingResourceException(null, null, null) + new NoSuchElementException() + new TooManyListenersException() + new UnknownFormatConversionException("") + new UnknownFormatFlagsException("") + } + } +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/TimeUnitTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/TimeUnitTest.scala new file mode 100644 index 0000000..79cd8a8 --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/TimeUnitTest.scala @@ -0,0 +1,135 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.javalib + +import scala.scalajs.js +import org.scalajs.jasminetest.JasmineTest +import java.util.concurrent.TimeUnit + +object TimeUnitTest extends JasmineTest { + + describe("java.util.concurrent.TimeUnit") { + + it("should respond to `toNanos`") { + expect(TimeUnit.NANOSECONDS.toNanos(42L)).toEqual(42L) + expect(TimeUnit.MICROSECONDS.toNanos(42L)).toEqual(42000L) + expect(TimeUnit.MILLISECONDS.toNanos(42L)).toEqual(42000000L) + expect(TimeUnit.SECONDS.toNanos(42L)).toEqual(42000000000L) + expect(TimeUnit.MINUTES.toNanos(42L)).toEqual(2520000000000L) + expect(TimeUnit.HOURS.toNanos(42L)).toEqual(151200000000000L) + expect(TimeUnit.DAYS.toNanos(42L)).toEqual(3628800000000000L) + } + + it("should respond to `toMicros`") { + expect(TimeUnit.NANOSECONDS.toMicros(42L)).toEqual(0L) + expect(TimeUnit.NANOSECONDS.toMicros(42123L)).toEqual(42L) + expect(TimeUnit.MICROSECONDS.toMicros(42L)).toEqual(42L) + expect(TimeUnit.MILLISECONDS.toMicros(42L)).toEqual(42000L) + expect(TimeUnit.SECONDS.toMicros(42L)).toEqual(42000000L) + expect(TimeUnit.MINUTES.toMicros(42L)).toEqual(2520000000L) + expect(TimeUnit.HOURS.toMicros(42L)).toEqual(151200000000L) + expect(TimeUnit.DAYS.toMicros(42L)).toEqual(3628800000000L) + } + + it("should respond to `toMillis`") { + expect(TimeUnit.NANOSECONDS.toMillis(42L)).toEqual(0L) + expect(TimeUnit.NANOSECONDS.toMillis(42000123L)).toEqual(42L) + expect(TimeUnit.MICROSECONDS.toMillis(42L)).toEqual(0L) + expect(TimeUnit.MICROSECONDS.toMillis(42123L)).toEqual(42L) + expect(TimeUnit.MILLISECONDS.toMillis(42L)).toEqual(42L) + expect(TimeUnit.SECONDS.toMillis(42L)).toEqual(42000L) + expect(TimeUnit.MINUTES.toMillis(42L)).toEqual(2520000L) + expect(TimeUnit.HOURS.toMillis(42L)).toEqual(151200000L) + expect(TimeUnit.DAYS.toMillis(42L)).toEqual(3628800000L) + } + + it("should respond to `toSeconds`") { + expect(TimeUnit.NANOSECONDS.toSeconds(42L)).toEqual(0L) + expect(TimeUnit.NANOSECONDS.toSeconds(42000000123L)).toEqual(42L) + expect(TimeUnit.MICROSECONDS.toSeconds(42L)).toEqual(0L) + expect(TimeUnit.MICROSECONDS.toSeconds(42000123L)).toEqual(42L) + expect(TimeUnit.MILLISECONDS.toSeconds(42L)).toEqual(0L) + expect(TimeUnit.MILLISECONDS.toSeconds(42123L)).toEqual(42L) + expect(TimeUnit.SECONDS.toSeconds(42L)).toEqual(42L) + expect(TimeUnit.MINUTES.toSeconds(42L)).toEqual(2520L) + expect(TimeUnit.HOURS.toSeconds(42L)).toEqual(151200L) + expect(TimeUnit.DAYS.toSeconds(42L)).toEqual(3628800L) + } + + it("should respond to `toMinutes`") { + expect(TimeUnit.NANOSECONDS.toMinutes(42L)).toEqual(0L) + expect(TimeUnit.NANOSECONDS.toMinutes(2520000007380L)).toEqual(42L) + expect(TimeUnit.MICROSECONDS.toMinutes(42L)).toEqual(0L) + expect(TimeUnit.MICROSECONDS.toMinutes(2520007380L)).toEqual(42L) + expect(TimeUnit.MILLISECONDS.toMinutes(42L)).toEqual(0L) + expect(TimeUnit.MILLISECONDS.toMinutes(2520738L)).toEqual(42L) + expect(TimeUnit.SECONDS.toMinutes(42L)).toEqual(0L) + expect(TimeUnit.SECONDS.toMinutes(2520L)).toEqual(42L) + expect(TimeUnit.MINUTES.toMinutes(42L)).toEqual(42L) + expect(TimeUnit.HOURS.toMinutes(42L)).toEqual(2520L) + expect(TimeUnit.DAYS.toMinutes(42L)).toEqual(60480L) + } + + it("should respond to `toHours`") { + expect(TimeUnit.NANOSECONDS.toHours(42L)).toEqual(0L) + expect(TimeUnit.NANOSECONDS.toHours(151200000442800L)).toEqual(42L) + expect(TimeUnit.MICROSECONDS.toHours(42L)).toEqual(0L) + expect(TimeUnit.MICROSECONDS.toHours(151200442800L)).toEqual(42L) + expect(TimeUnit.MILLISECONDS.toHours(42L)).toEqual(0L) + expect(TimeUnit.MILLISECONDS.toHours(151244280L)).toEqual(42L) + expect(TimeUnit.SECONDS.toHours(42L)).toEqual(0L) + expect(TimeUnit.SECONDS.toHours(151200L)).toEqual(42L) + expect(TimeUnit.MINUTES.toHours(42L)).toEqual(0L) + expect(TimeUnit.MINUTES.toHours(2520L)).toEqual(42L) + expect(TimeUnit.HOURS.toHours(42L)).toEqual(42L) + expect(TimeUnit.DAYS.toHours(42L)).toEqual(1008L) + } + + it("should respond to `toDays`") { + expect(TimeUnit.NANOSECONDS.toDays(42L)).toEqual(0L) + expect(TimeUnit.NANOSECONDS.toDays(3628800010627200L)).toEqual(42L) + expect(TimeUnit.MICROSECONDS.toDays(42L)).toEqual(0L) + expect(TimeUnit.MICROSECONDS.toDays(3628810627200L)).toEqual(42L) + expect(TimeUnit.MILLISECONDS.toDays(42L)).toEqual(0L) + expect(TimeUnit.MILLISECONDS.toDays(3629862720L)).toEqual(42L) + expect(TimeUnit.SECONDS.toDays(42L)).toEqual(0L) + expect(TimeUnit.SECONDS.toDays(3628800L)).toEqual(42L) + expect(TimeUnit.MINUTES.toDays(42L)).toEqual(0L) + expect(TimeUnit.MINUTES.toDays(60480L)).toEqual(42L) + expect(TimeUnit.HOURS.toDays(42L)).toEqual(1L) + expect(TimeUnit.HOURS.toDays(1008L)).toEqual(42L) + expect(TimeUnit.DAYS.toDays(42L)).toEqual(42L) + } + + it("should respond to `values`") { + val values = TimeUnit.values() + expect(values.length).toEqual(7) + expectTimeUnit(values(0), TimeUnit.NANOSECONDS) + expectTimeUnit(values(1), TimeUnit.MICROSECONDS) + expectTimeUnit(values(2), TimeUnit.MILLISECONDS) + expectTimeUnit(values(3), TimeUnit.SECONDS) + expectTimeUnit(values(4), TimeUnit.MINUTES) + expectTimeUnit(values(5), TimeUnit.HOURS) + expectTimeUnit(values(6), TimeUnit.DAYS) + } + + it("should respond to `valueOf`") { + expectTimeUnit(TimeUnit.valueOf("NANOSECONDS"), TimeUnit.NANOSECONDS) + expectTimeUnit(TimeUnit.valueOf("MICROSECONDS"), TimeUnit.MICROSECONDS) + expectTimeUnit(TimeUnit.valueOf("MILLISECONDS"), TimeUnit.MILLISECONDS) + expectTimeUnit(TimeUnit.valueOf("SECONDS"), TimeUnit.SECONDS) + expectTimeUnit(TimeUnit.valueOf("MINUTES"), TimeUnit.MINUTES) + expectTimeUnit(TimeUnit.valueOf("HOURS"), TimeUnit.HOURS) + expectTimeUnit(TimeUnit.valueOf("DAYS"), TimeUnit.DAYS) + } + + } + + def expectTimeUnit(actual: TimeUnit, expected: TimeUnit): Unit = + expect(actual.asInstanceOf[js.Any]).toEqual(expected.asInstanceOf[js.Any]) +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/URITest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/URITest.scala new file mode 100644 index 0000000..65a049f --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/URITest.scala @@ -0,0 +1,312 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.javalib + +import scala.scalajs.js +import org.scalajs.jasminetest.JasmineTest + +import java.net.URI + +object URITest extends JasmineTest { + + def expectURI(uri: URI, isAbsolute: Boolean, isOpaque: Boolean)( + authority: String = null, fragment: String = null, + host: String = null, path: String = null, port: Int = -1, + query: String = null, scheme: String = null, userInfo: String = null, + schemeSpecificPart: String = null)(rawAuthority: String = authority, + rawFragment: String = fragment, rawPath: String = path, + rawQuery: String = query, rawUserInfo: String = userInfo, + rawSchemeSpecificPart: String = schemeSpecificPart): Unit = { + + expect(uri.getAuthority()).toBe(authority) + expect(uri.getFragment()).toBe(fragment) + expect(uri.getHost()).toBe(host) + expect(uri.getPath()).toBe(path) + expect(uri.getPort()).toBe(port) + expect(uri.getQuery()).toBe(query) + expect(uri.getRawAuthority()).toBe(rawAuthority) + expect(uri.getRawFragment()).toBe(rawFragment) + expect(uri.getRawPath()).toBe(rawPath) + expect(uri.getRawQuery()).toBe(rawQuery) + expect(uri.getRawSchemeSpecificPart()).toBe(rawSchemeSpecificPart) + expect(uri.getRawUserInfo()).toBe(rawUserInfo) + expect(uri.getScheme()).toBe(scheme) + expect(uri.getSchemeSpecificPart()).toBe(schemeSpecificPart) + expect(uri.getUserInfo()).toBe(userInfo) + expect(uri.isAbsolute()).toBe(isAbsolute) + expect(uri.isOpaque()).toBe(isOpaque) + + } + + describe("java.net.URI") { + + it("should parse vanilla absolute URIs") { + expectURI(new URI("http://java.sun.com/j2se/1.3/"), true, false)( + scheme = "http", + host = "java.sun.com", + path = "/j2se/1.3/", + authority = "java.sun.com", + schemeSpecificPart = "//java.sun.com/j2se/1.3/")() + } + + it("should parse absolute URIs with IPv6") { + val uri = new URI("http://hans@[ffff::0:128.4.5.3]:345/~hans/") + expectURI(uri, true, false)( + scheme = "http", + host = "[ffff::0:128.4.5.3]", + userInfo = "hans", + port = 345, + path = "/~hans/", + authority = "hans@[ffff::0:128.4.5.3]:345", + schemeSpecificPart = "//hans@[ffff::0:128.4.5.3]:345/~hans/" + )() + } + + it("should parse absolute URIs without authority") { + expectURI(new URI("file:/~/calendar"), true, false)( + scheme = "file", + path = "/~/calendar", + schemeSpecificPart = "/~/calendar")() + } + + it("should parse absolute URIs with empty authority") { + expectURI(new URI("file:///~/calendar"), true, false)( + authority = "", + scheme = "file", + path = "/~/calendar", + schemeSpecificPart = "///~/calendar")() + } + + it("should parse opaque URIs") { + expectURI(new URI("mailto:java-net@java.sun.com"), true, true)( + scheme = "mailto", + schemeSpecificPart = "java-net@java.sun.com")() + + expectURI(new URI("news:comp.lang.java"), true, true)( + scheme = "news", + schemeSpecificPart = "comp.lang.java")() + + expectURI(new URI("urn:isbn:096139210x"), true, true)( + scheme = "urn", + schemeSpecificPart = "isbn:096139210x")() + } + + it("should parse relative URIs") { + expectURI(new URI("docs/guide/collections/designfaq.html#28"), false, false)( + path = "docs/guide/collections/designfaq.html", + fragment = "28", + schemeSpecificPart = "docs/guide/collections/designfaq.html#28" + )() + expectURI(new URI("../../../demo/jfc/SwingSet2/src/SwingSet2.java"), false, false)( + path = "../../../demo/jfc/SwingSet2/src/SwingSet2.java", + schemeSpecificPart = "../../../demo/jfc/SwingSet2/src/SwingSet2.java" + )() + } + + it("should parse relative URIs with IPv4") { + expectURI(new URI("//123.5.6.3:45/bar"), false, false)( + authority = "123.5.6.3:45", + host = "123.5.6.3", + port = 45, + path = "/bar", + schemeSpecificPart = "//123.5.6.3:45/bar" + )() + } + + it("should parse relative URIs with registry-based authority") { + expectURI(new URI("//foo:bar"), false, false)( + authority = "foo:bar", + schemeSpecificPart = "//foo:bar" + )() + } + + it("should parse relative URIs with escapes") { + expectURI(new URI("//ma%5dx:secret@example.com:8000/foo"), false, false)( + authority = "ma]x:secret@example.com:8000", + userInfo = "ma]x:secret", + host = "example.com", + port = 8000, + path = "/foo", + schemeSpecificPart = "//ma]x:secret@example.com:8000/foo")( + rawUserInfo = "ma%5dx:secret", + rawAuthority = "ma%5dx:secret@example.com:8000", + rawSchemeSpecificPart = "//ma%5dx:secret@example.com:8000/foo") + } + + it("should parse relative URIs with fragment only") { + expectURI(new URI("#foo"), false, false)( + fragment = "foo", + path = "", + schemeSpecificPart = "#foo" + )() + } + + it("should provide compareTo") { + val x = new URI("http://example.com/asdf%6a") + val y = new URI("http://example.com/asdf%6A") + val z = new URI("http://example.com/asdfj") + val rel = new URI("/foo/bar") + + expect(x.compareTo(y)).toBeGreaterThan(0) + expect(x.compareTo(z)).toBeLessThan(0) + expect(y.compareTo(z)).toBeLessThan(0) + expect(x.compareTo(x)).toBe(0) + expect(y.compareTo(y)).toBe(0) + expect(z.compareTo(z)).toBe(0) + expect(x.compareTo(rel)).toBeGreaterThan(0) + expect(y.compareTo(rel)).toBeGreaterThan(0) + expect(z.compareTo(rel)).toBeGreaterThan(0) + expect(rel.compareTo(rel)).toBe(0) + } + + it("should provide equals") { + val x = new URI("http://example.com/asdf%6a") + val y = new URI("http://example.com/asdf%6A") + val z = new URI("http://example.com/asdfj") + + expect(x == y).toBeTruthy + expect(x == z).toBeFalsy + expect(y == z).toBeFalsy + expect(x == x).toBeTruthy + expect(y == y).toBeTruthy + expect(z == z).toBeTruthy + + expect(new URI("foo:helloWorld%6b%6C") == new URI("foo:helloWorld%6C%6b")) + } + + it("should provide normalize") { + expectURI(new URI("http://example.com/../asef/../../").normalize, true, false)( + scheme = "http", + host = "example.com", + authority = "example.com", + path = "/../../", + schemeSpecificPart = "//example.com/../../")() + expectURI(new URI("http://example.com/../as/./ef/foo/../../").normalize, true, false)( + scheme = "http", + host = "example.com", + authority = "example.com", + path = "/../as/", + schemeSpecificPart = "//example.com/../as/")() + expectURI(new URI("bar/../fo:o/./bar").normalize, false, false)( + path = "./fo:o/bar", + schemeSpecificPart = "./fo:o/bar")() + expectURI(new URI("bar/..//fo:o//./bar").normalize, false, false)( + path = "./fo:o/bar", + schemeSpecificPart = "./fo:o/bar")() + + val x = new URI("http://www.example.com/foo/bar") + expect(x.normalize eq x).toBeTruthy + } + + it("should provide resolve - JavaDoc examples") { + val base = "http://java.sun.com/j2se/1.3/" + val relative1 = "docs/guide/collections/designfaq.html#28" + val resolved1 = + "http://java.sun.com/j2se/1.3/docs/guide/collections/designfaq.html#28" + val relative2 = "../../../demo/jfc/SwingSet2/src/SwingSet2.java" + val resolved2 = + "http://java.sun.com/j2se/1.3/demo/jfc/SwingSet2/src/SwingSet2.java" + + expect(new URI(base).resolve(relative1).toString).toEqual(resolved1) + expect(new URI(resolved1).resolve(relative2).toString).toEqual(resolved2) + } + + it("should provide resolve - RFC2396 examples") { + val base = new URI("http://a/b/c/d;p?q") + def resTest(ref: String, trg: String) = + expect(base.resolve(ref).toString).toEqual(trg) + + // Normal examples + resTest("g:h" , "g:h") + resTest("g" , "http://a/b/c/g") + resTest("./g" , "http://a/b/c/g") + resTest("g/" , "http://a/b/c/g/") + resTest("/g" , "http://a/g") + resTest("//g" , "http://g") + resTest("?y" , "http://a/b/c/?y") + resTest("g?y" , "http://a/b/c/g?y") + resTest("#s" , "http://a/b/c/d;p?q#s") + resTest("g#s" , "http://a/b/c/g#s") + resTest("g?y#s" , "http://a/b/c/g?y#s") + resTest(";x" , "http://a/b/c/;x") + resTest("g;x" , "http://a/b/c/g;x") + resTest("g;x?y#s", "http://a/b/c/g;x?y#s") + resTest("." , "http://a/b/c/") + resTest("./" , "http://a/b/c/") + resTest(".." , "http://a/b/") + resTest("../" , "http://a/b/") + resTest("../g" , "http://a/b/g") + resTest("../.." , "http://a/") + resTest("../../" , "http://a/") + resTest("../../g", "http://a/g") + + // Abnormal examples + resTest("../../../g" , "http://a/../g") + resTest("../../../../g", "http://a/../../g") + resTest("/./g" , "http://a/./g") + resTest("/../g" , "http://a/../g") + resTest("g." , "http://a/b/c/g.") + resTest(".g" , "http://a/b/c/.g") + resTest("g.." , "http://a/b/c/g..") + resTest("..g" , "http://a/b/c/..g") + resTest("./../g" , "http://a/b/g") + resTest("./g/." , "http://a/b/c/g/") + resTest("g/./h" , "http://a/b/c/g/h") + resTest("g/../h" , "http://a/b/c/h") + resTest("g;x=1/./y" , "http://a/b/c/g;x=1/y") + resTest("g;x=1/../y" , "http://a/b/c/y") + resTest("g?y/./x" , "http://a/b/c/g?y/./x") + resTest("g?y/../x" , "http://a/b/c/g?y/../x") + resTest("g#s/./x" , "http://a/b/c/g#s/./x") + resTest("g#s/../x" , "http://a/b/c/g#s/../x") + resTest("http:g" , "http:g") + } + + it("should provide normalize - examples derived from RFC relativize") { + expectURI(new URI("http://a/b/c/..").normalize, true, false)( + scheme = "http", + host = "a", + authority = "a", + path = "/b/", + schemeSpecificPart = "//a/b/")() + + expectURI(new URI("http://a/b/c/.").normalize, true, false)( + scheme = "http", + host = "a", + authority = "a", + path = "/b/c/", + schemeSpecificPart = "//a/b/c/")() + } + + it("should provide relativize") { + val x = new URI("http://f%4Aoo@asdf/a") + val y = new URI("http://fJoo@asdf/a/b/") + val z = new URI("http://f%4aoo@asdf/a/b/") + expect(x.relativize(y) eq y).toBeTruthy + expect(x.relativize(z).toString()).toEqual("b/") + + def relTest(base: String, trg: String, exp: String) = + expect(new URI(base).relativize(new URI(trg)).toString()).toEqual(exp) + + relTest("http://a.ch/a", "http://a.ch/a/b", "b") + relTest("http://a.ch/a/", "http://a.ch/a/b", "b") + relTest("https://a.ch/a", "http://a.ch/a/b", "http://a.ch/a/b") + relTest("/a/b/c", "/a/b/c/d/e", "d/e") + relTest("/a/b/c/", "/a/b/c/d/e", "d/e") + relTest("/a/b/c/", "/a/b/c/foo:e/d", "foo:e/d") // see bug JDK-7037120 + relTest("../a/b", "../a/b/c", "c") + } + + it("should provide hashCode") { + expect(new URI("http://example.com/asdf%6a").hashCode).toEqual( + new URI("http://example.com/asdf%6A").hashCode) + } + + } + +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/UUIDTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/UUIDTest.scala new file mode 100644 index 0000000..b22fe02 --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/javalib/UUIDTest.scala @@ -0,0 +1,180 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.javalib + +import scala.scalajs.js +import org.scalajs.jasminetest.JasmineTest + +import java.util.UUID + +object UUIDTest extends JasmineTest { + describe("java.util.UUID") { + it("constructor") { + val uuid = new UUID(0xf81d4fae7dec11d0L, 0xa76500a0c91e6bf6L) + expect(uuid.getMostSignificantBits() == 0xf81d4fae7dec11d0L).toBeTruthy + expect(uuid.getLeastSignificantBits() == 0xa76500a0c91e6bf6L).toBeTruthy + expect(uuid.variant()).toEqual(2) + expect(uuid.version()).toEqual(1) + expect(uuid.timestamp() == 0x1d07decf81d4faeL).toBeTruthy + expect(uuid.clockSequence()).toEqual(0x2765) + expect(uuid.node() == 0xA0C91E6BF6L).toBeTruthy + } + + it("getLeastSignificantBits") { + expect(new UUID(0L, 0L).getLeastSignificantBits() == 0L).toBeTruthy + expect(new UUID(0L, Long.MinValue).getLeastSignificantBits() == Long.MinValue).toBeTruthy + expect(new UUID(0L, Long.MaxValue).getLeastSignificantBits() == Long.MaxValue).toBeTruthy + } + + it("getMostSignificantBits") { + expect(new UUID(0L, 0L).getMostSignificantBits() == 0L).toBeTruthy + expect(new UUID(Long.MinValue, 0L).getMostSignificantBits() == Long.MinValue).toBeTruthy + expect(new UUID(Long.MaxValue, 0L).getMostSignificantBits() == Long.MaxValue).toBeTruthy + } + + it("version") { + expect(new UUID(0L, 0L).version()).toEqual(0) + expect(new UUID(0x0000000000001000L, 0L).version()).toEqual(1) + expect(new UUID(0x00000000000f2f00L, 0L).version()).toEqual(2) + } + + it("variant") { + expect(new UUID(0L, 0L).variant()).toEqual(0) + expect(new UUID(0L, 0x7000000000000000L).variant()).toEqual(0) + expect(new UUID(0L, 0x3ff0000000000000L).variant()).toEqual(0) + expect(new UUID(0L, 0x1ff0000000000000L).variant()).toEqual(0) + + expect(new UUID(0L, 0x8000000000000000L).variant()).toEqual(2) + expect(new UUID(0L, 0xb000000000000000L).variant()).toEqual(2) + expect(new UUID(0L, 0xaff0000000000000L).variant()).toEqual(2) + expect(new UUID(0L, 0x9ff0000000000000L).variant()).toEqual(2) + + expect(new UUID(0L, 0xc000000000000000L).variant()).toEqual(6) + expect(new UUID(0L, 0xdf00000000000000L).variant()).toEqual(6) + } + + it("timestamp") { + expect(new UUID(0x0000000000001000L, + 0x8000000000000000L).timestamp() == 0L).toBeTruthy + expect(new UUID(0x7777777755551333L, + 0x8000000000000000L).timestamp() == 0x333555577777777L).toBeTruthy + + expect(() => new UUID(0x0000000000000000L, 0x8000000000000000L).timestamp()).toThrow + expect(() => new UUID(0x0000000000002000L, 0x8000000000000000L).timestamp()).toThrow + } + + it("clockSequence") { + expect(new UUID(0x0000000000001000L, 0x8000000000000000L).clockSequence()).toEqual(0) + expect(new UUID(0x0000000000001000L, 0x8fff000000000000L).clockSequence()).toEqual(0x0fff) + expect(new UUID(0x0000000000001000L, 0xBfff000000000000L).clockSequence()).toEqual(0x3fff) + + expect(() => new UUID(0x0000000000000000L, 0x8000000000000000L).clockSequence()).toThrow + expect(() => new UUID(0x0000000000002000L, 0x8000000000000000L).clockSequence()).toThrow + } + + it("node") { + expect(new UUID(0x0000000000001000L, 0x8000000000000000L).node() == 0L).toBeTruthy + expect(new UUID(0x0000000000001000L, 0x8000ffffffffffffL).node() == 0xffffffffffffL).toBeTruthy + + expect(() => new UUID(0x0000000000000000L, 0x8000000000000000L).node()).toThrow + expect(() => new UUID(0x0000000000002000L, 0x8000000000000000L).node()).toThrow + } + + it("compareTo") { + val uuid0101 = new UUID(1L, 1L) + val uuid0111 = new UUID(1L, 0x100000001L) + val uuid1000 = new UUID(0x100000000L, 0L) + + expect(uuid0101.compareTo(uuid0101)).toEqual(0) + expect(uuid0111.compareTo(uuid0111)).toEqual(0) + expect(uuid1000.compareTo(uuid1000)).toEqual(0) + + expect(uuid0101.compareTo(uuid0111)).toBeLessThan(0) + expect(uuid0101.compareTo(uuid1000)).toBeLessThan(0) + expect(uuid0111.compareTo(uuid1000)).toBeLessThan(0) + + expect(uuid0111.compareTo(uuid0101)).toBeGreaterThan(0) + expect(uuid1000.compareTo(uuid0101)).toBeGreaterThan(0) + expect(uuid1000.compareTo(uuid0111)).toBeGreaterThan(0) + } + + it("hashCode") { + expect(new UUID(0L, 0L).hashCode()).toEqual(0) + expect(new UUID(123L, 123L).hashCode()).toEqual(new UUID(123L, 123L).hashCode()) + } + + it("equals") { + val uuid1 = new UUID(0L, 0L) + expect(uuid1.equals(uuid1)).toBeTruthy + expect(uuid1.equals(null)).toBeFalsy + expect(uuid1.equals("something else")).toBeFalsy + + val uuid2 = new UUID(0L, 0L) + expect(uuid1.equals(uuid2)).toBeTruthy + + val uuid3 = new UUID(0xf81d4fae7dec11d0L, 0xa76500a0c91e6bf6L) + val uuid4 = new UUID(0xf81d4fae7dec11d0L, 0xa76500a0c91e6bf6L) + expect(uuid3.equals(uuid4)).toBeTruthy + expect(uuid3.equals(uuid1)).toBeFalsy + + expect(uuid3.equals(new UUID(0x781d4fae7dec11d0L, 0xa76500a0c91e6bf6L))).toBeFalsy + expect(uuid3.equals(new UUID(0xf81d4fae7dec11d1L, 0xa76500a0c91e6bf6L))).toBeFalsy + expect(uuid3.equals(new UUID(0xf81d4fae7dec11d0L, 0xa76530a0c91e6bf6L))).toBeFalsy + expect(uuid3.equals(new UUID(0xf81d4fae7dec11d0L, 0xa76500a0c91e6cf6L))).toBeFalsy + } + + it("toString") { + expect(new UUID(0xf81d4fae7dec11d0L, + 0xa76500a0c91e6bf6L).toString()).toEqual("f81d4fae-7dec-11d0-a765-00a0c91e6bf6") + expect(new UUID(0x0000000000001000L, + 0x8000000000000000L).toString()).toEqual("00000000-0000-1000-8000-000000000000") + } + + it("randomUUID") { + val uuid = UUID.randomUUID() + expect(uuid.variant()).toEqual(2) + expect(uuid.version()).toEqual(4) + } + + it("fromString") { + val uuid1 = UUID.fromString("f81d4fae-7dec-11d0-a765-00a0c91e6bf6") + expect(uuid1.equals(new UUID(0xf81d4fae7dec11d0L, 0xa76500a0c91e6bf6L))).toBeTruthy + expect(uuid1.getMostSignificantBits() == 0xf81d4fae7dec11d0L).toBeTruthy + expect(uuid1.getLeastSignificantBits() == 0xa76500a0c91e6bf6L).toBeTruthy + expect(uuid1.variant()).toEqual(2) + expect(uuid1.version()).toEqual(1) + expect(uuid1.timestamp() == 130742845922168750L).toBeTruthy + expect(uuid1.clockSequence()).toEqual(10085) + expect(uuid1.node() == 690568981494L).toBeTruthy + + val uuid2 = UUID.fromString("00000000-0000-1000-8000-000000000000") + expect(uuid2.equals(new UUID(0x0000000000001000L, 0x8000000000000000L))) + expect(uuid2.getMostSignificantBits() == 0x0000000000001000L).toBeTruthy + expect(uuid2.getLeastSignificantBits() == 0x8000000000000000L).toBeTruthy + expect(uuid2.variant()).toEqual(2) + expect(uuid2.version()).toEqual(1) + expect(uuid2.timestamp() == 0L).toBeTruthy + expect(uuid2.clockSequence()).toEqual(0) + expect(uuid2.node() == 0L).toBeTruthy + + expect(() => UUID.fromString(null)).toThrow + expect(() => UUID.fromString("")).toThrow + expect(() => UUID.fromString("f81d4fae_7dec-11d0-a765-00a0c91e6bf6")).toThrow + expect(() => UUID.fromString("f81d4fae-7dec_11d0-a765-00a0c91e6bf6")).toThrow + expect(() => UUID.fromString("f81d4fae-7dec-11d0_a765-00a0c91e6bf6")).toThrow + expect(() => UUID.fromString("f81d4fae-7dec-11d0-a765_00a0c91e6bf6")).toThrow + expect(() => UUID.fromString("-7dec-11d0-a765-00a0c91e6bf6")).toThrow + expect(() => UUID.fromString("f81d4fae--11d0-a765-00a0c91e6bf6")).toThrow + expect(() => UUID.fromString("f81d4fae-7dec--a765-00a0c91e6bf6")).toThrow + expect(() => UUID.fromString("f81d4fae-7dec-11d0--00a0c91e6bf6")).toThrow + expect(() => UUID.fromString("f81d4fae-7dec-11d0-a765-")).toThrow + expect(() => UUID.fromString("f81d4fae-7dec-11d0-a765")).toThrow + expect(() => UUID.fromString("f81d4fae-7dZc-11d0-a765-00a0c91e6bf6")).toThrow + } + } +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/jsinterop/ArrayTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/jsinterop/ArrayTest.scala new file mode 100644 index 0000000..2ffd6b7 --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/jsinterop/ArrayTest.scala @@ -0,0 +1,94 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.jsinterop + +import scala.scalajs.js +import org.scalajs.jasminetest.JasmineTest + +object ArrayTest extends JasmineTest { + + describe("scala.scalajs.js.Array") { + + it("should provide implicit conversion from js.Array to ArrayOps - String") { + var propCount = 0 + var propString = "" + + for (item <- js.Array("Sc", "ala", ".", "js")) { + expect(item.isInstanceOf[String]).toBeTruthy + propCount += 1 + propString += item + } + + expect(propCount).toEqual(4) + expect(propString).toEqual("Scala.js") + } + + it("should provide implicit conversion from js.Array to ArrayOps - Int") { + var propCount = 0 + var propString = "" + + for (item <- js.Array(7, 3, 5, 7)) { + expect(item.isInstanceOf[Int]).toBeTruthy + propCount += 1 + propString += item + } + + expect(propCount).toEqual(4) + expect(propString).toEqual("7357") + } + + it("should provide implicit conversion from js.Array to ArrayOps - Char") { + var propCount = 0 + var propString = "" + + for (item <- js.Array('S', 'c', 'a', 'l', 'a')) { + expect(item.isInstanceOf[Char]).toBeTruthy + propCount += 1 + propString += item + } + + expect(propCount).toEqual(5) + expect(propString).toEqual("Scala") + } + + it("should provide implicit conversion from js.Array to ArrayOps - value class") { + var propCount = 0 + var propString = "" + + for (item <- js.Array(new VC(5), new VC(-4))) { + expect(item.isInstanceOf[VC]).toBeTruthy + propCount += 1 + propString += item + } + + expect(propCount).toEqual(2) + expect(propString).toEqual("VC(5)VC(-4)") + } + + } + + describe("scala.scalajs.js.JSConverters.JSRichGenTraversableOnce") { + + import js.JSConverters._ + + it("should provide toJSArray") { + expect(List("foo", "bar").toJSArray).toEqual(js.Array("foo", "bar")) + expect(Iterator(1, 2, 3).toJSArray).toEqual(js.Array(1, 2, 3)) + expect(Array(0.3, 7.3, 8.9).toJSArray).toEqual(js.Array(0.3, 7.3, 8.9)) + expect(None.toJSArray).toEqual(js.Array()) + // The following fails on 2.10.x + //expect(Some("Hello World").toJSArray).toEqual(js.Array("Hello World")) + } + + } + + private class VC(val x: Int) extends AnyVal { + override def toString(): String = s"VC($x)" + } + +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/jsinterop/AsyncTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/jsinterop/AsyncTest.scala new file mode 100644 index 0000000..e37c89e --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/jsinterop/AsyncTest.scala @@ -0,0 +1,121 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.jsinterop + +import scala.scalajs.js +import scala.scalajs.js.JSConverters._ + +import org.scalajs.jasminetest.JasmineTest + +import scala.concurrent.{Future, ExecutionContext} +import scala.scalajs.concurrent.JSExecutionContext + +import scala.collection.mutable.ArrayBuffer + +import org.scalajs.jasmine.JasmineExpectation + +object AsyncTest extends JasmineTest { + + def asyncTest(implicit executor: ExecutionContext) = { + val steps = new ArrayBuffer[String] + + steps += "prep-future" + + val f1 = Future { + steps += "future" + 1 + 2 + 3 + } + + steps += "prep-map" + + val f2 = f1 map { x => + steps += "map" + x * 2 + } + + steps += "prep-foreach" + + f2 foreach { _ => steps += "foreach" } + + steps += "done" + + steps + } + + def expect(abuf: ArrayBuffer[String]): JasmineExpectation = + expect(abuf.toJSArray) + + describe("scala.scalajs.concurrent.JSExecutionContext.queue") { + + beforeEach { + jasmine.Clock.useMock() + } + + it("should correctly order future calls") { + val res = asyncTest(JSExecutionContext.queue) + + expect(res).toEqual(js.Array( + "prep-future", + "prep-map", + "prep-foreach", + "done")) + + jasmine.Clock.tick(1) + + expect(res).toEqual(js.Array( + "prep-future", + "prep-map", + "prep-foreach", + "done", + "future", + "map", + "foreach")) + } + + } + + describe("scala.scalajs.concurrent.JSExecutionContext.runNow") { + + it("should correctly order future calls") { + val res = asyncTest(JSExecutionContext.runNow) + + expect(res).toEqual(js.Array( + "prep-future", + "future", + "prep-map", + "map", + "prep-foreach", + "foreach", + "done")) + } + + } + + describe("scala.concurrent.Future") { + + it("should support map") { + implicit val ec = JSExecutionContext.runNow + val f = Future(3).map(x => x*2) + expect(f.value.get.get).toEqual(6) + } + + it("should support flatMap") { + implicit val ec = JSExecutionContext.runNow + val f = Future(Future(3)).flatMap(x => x) + expect(f.value.get.get).toEqual(3) + } + + it("should support sequence") { + implicit val ec = JSExecutionContext.runNow + val f = Future.sequence(Seq(Future(3), Future(5))) + expect(f.value.get.get.toJSArray).toEqual(js.Array(3, 5)) + } + + } + +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/jsinterop/DictionaryTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/jsinterop/DictionaryTest.scala new file mode 100644 index 0000000..8b45395 --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/jsinterop/DictionaryTest.scala @@ -0,0 +1,79 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.jsinterop + +import scala.scalajs.js +import org.scalajs.jasminetest.JasmineTest + +object DictionaryTest extends JasmineTest { + + describe("scala.scalajs.js.Dictionary") { + + it("should provide an equivalent of the JS delete keyword - #255") { + val obj = js.Dictionary.empty[js.Any] + obj("foo") = 42 + obj("bar") = "foobar" + + expect(obj("foo")).toEqual(42) + expect(obj("bar")).toEqual("foobar") + obj.delete("foo") + expect(obj("foo")).toBeUndefined + expect(obj.asInstanceOf[js.Object].hasOwnProperty("foo")).toBeFalsy + expect(obj("bar")).toEqual("foobar") + } + + // This doesn't work on Rhino due to lack of full strict mode support - #679 + unless("rhino"). + it("should behave as specified when deleting a non-configurable property - #461 - #679") { + val obj = js.Dictionary.empty[js.Any] + js.Object.defineProperty(obj.asInstanceOf[js.Object], "nonconfig", + js.Dynamic.literal(value = 4, writable = false).asInstanceOf[js.PropertyDescriptor]) + expect(obj("nonconfig")).toEqual(4) + expect(() => obj.delete("nonconfig")).toThrow + expect(obj("nonconfig")).toEqual(4) + } + + it("should provide `get`") { + val obj = js.Dictionary.empty[Int] + obj("hello") = 1 + + expect(obj.get("hello").isDefined).toBeTruthy + expect(obj.get("world").isDefined).toBeFalsy + } + + it("should treat delete as a statement - #907") { + val obj = js.Dictionary("a" -> "A") + obj.delete("a") + } + + it("should desugar arguments to delete statements - #908") { + val kh = js.Dynamic.literal( key = "a" ).asInstanceOf[KeyHolder] + val dict = js.Dictionary[String]("a" -> "A") + def a[T](foo: String) = dict.asInstanceOf[T] + a[js.Dictionary[String]]("foo").delete(kh.key) + } + + } + + trait KeyHolder extends js.Object { + def key: String = js.native + } + + describe("scala.scalajs.js.JSConverters.JSRichGenMap") { + + import js.JSConverters._ + + it("should provide toJSDictionary") { + expect(Map("a" -> 1, "b" -> 2).toJSDictionary).toEqual( + js.Dynamic.literal(a = 1, b = 2)) + expect(Map("a" -> "foo", "b" -> "bar").toJSDictionary).toEqual( + js.Dynamic.literal(a = "foo", b = "bar")) + } + + } +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/jsinterop/DynamicTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/jsinterop/DynamicTest.scala new file mode 100644 index 0000000..2b6942f --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/jsinterop/DynamicTest.scala @@ -0,0 +1,187 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.jsinterop + +import scala.scalajs.js +import org.scalajs.jasminetest.JasmineTest + +import js.annotation.JSExport + +object DynamicTest extends JasmineTest { + + describe("scala.scalajs.js.Dynamic") { + + it("should workaround Scala 2.10 issue with implicit conversion for dynamic fields named x - #8") { + class Point(val x: Int, val y: Int) + + def jsonToPoint(json: js.Dynamic) = { + new Point(json.x.toString.toInt, json.y.toString.toInt) + } + + val json = js.eval("var dynamicTestPoint = { x: 1, y: 2 }; dynamicTestPoint;") + val point = jsonToPoint(json.asInstanceOf[js.Dynamic]) + + expect(point.x).toEqual(1) + expect(point.y).toEqual(2) + } + + it("should allow to call functions with arguments named x") { + class A { + def a = 1 + } + + class B extends A { + @JSExport + def x(par: Int) = a + par // make sure `this` is bound correctly in JS + } + + val b = (new B).asInstanceOf[js.Dynamic] + + expect(b.x(10)).toEqual(11) + } + + it("should allow instanciating JS classes dynamically - #10") { + val DynamicTestClass = js.eval(""" + var DynamicTestClass = function(x) { + this.x = x; + }; + DynamicTestClass; + """).asInstanceOf[js.Dynamic] + val obj = js.Dynamic.newInstance(DynamicTestClass)("Scala.js") + expect(obj.x).toEqual("Scala.js") + } + + it("should allow instantiating JS classes dynamically with varargs - #708") { + val DynamicTestClassVarArgs = js.eval(""" + var DynamicTestClassVarArgs = function() { + this.count = arguments.length; + for (var i = 0; i < arguments.length; i++) + this['elem'+i] = arguments[i]; + }; + DynamicTestClassVarArgs; + """).asInstanceOf[js.Dynamic] + + val obj1 = js.Dynamic.newInstance(DynamicTestClassVarArgs)("Scala.js") + expect(obj1.count).toEqual(1) + expect(obj1.elem0).toEqual("Scala.js") + + val obj2 = js.Dynamic.newInstance(DynamicTestClassVarArgs)( + "Scala.js", 42, true) + expect(obj2.count).toEqual(3) + expect(obj2.elem0).toEqual("Scala.js") + expect(obj2.elem1).toEqual(42) + expect(obj2.elem2).toEqual(true) + + def obj3Args: Seq[js.Any] = Seq("Scala.js", 42, true) + val obj3 = js.Dynamic.newInstance(DynamicTestClassVarArgs)(obj3Args: _*) + expect(obj3.count).toEqual(3) + expect(obj3.elem0).toEqual("Scala.js") + expect(obj3.elem1).toEqual(42) + expect(obj3.elem2).toEqual(true) + } + + it("should provide an object literal construction") { + import js.Dynamic.{ literal => obj } + val x = obj(foo = 3, bar = "foobar") + expect(x.foo).toEqual(3) + expect(x.bar).toEqual("foobar") + expect(x.unknown).toBeUndefined() + + val y = obj( + inner = obj(name = "inner obj"), + fun = { () => 42 } + ) + expect(y.inner.name).toEqual("inner obj") + expect(y.fun()).toEqual(42) + + expect(obj().anything).toBeUndefined() + } + + it("should provide object literal construction with dynamic naming") { + import js.Dynamic.{ literal => obj } + val x = obj("foo" -> 3, "bar" -> "foobar") + expect(x.foo).toEqual(3) + expect(x.bar).toEqual("foobar") + expect(x.unknown).toBeUndefined() + + val tup1 = ("hello1", 3: js.Any) + val tup2 = ("hello2", 10: js.Any) + + val y = obj(tup1, tup2) + expect(y.hello1).toEqual(3) + expect(y.hello2).toEqual(10) + + var count = 0 + val z = obj({ count += 1; ("foo", "bar")}) + expect(z.foo).toEqual("bar") + expect(count).toEqual(1) + } + + it("should allow to create an empty object with the literal syntax") { + import js.Dynamic.{ literal => obj } + val x = obj() + expect(x.isInstanceOf[js.Object]).toBeTruthy() + } + + it("should properly encode object literal property names") { + import js.Dynamic.{ literal => obj } + + val obj0 = obj("3-" -> 42) + expect(obj0.`3-`).toEqual(42) + + val obj0Dict = obj0.asInstanceOf[js.Dictionary[js.Any]] + expect(obj0Dict("3-")).toEqual(42) + + val checkEvilProperties = js.eval(""" + function dynamicLiteralNameEncoding_checkEvilProperties(x) { + return x['.o[3√!|-pr()per7:3$];'] === ' such eval '; + } + dynamicLiteralNameEncoding_checkEvilProperties + """).asInstanceOf[js.Function1[js.Any, Boolean]] + val obj1 = obj( + ".o[3√!|-pr()per7:3$];" -> " such eval ").asInstanceOf[js.Dictionary[js.Any]] + expect(obj1(".o[3√!|-pr()per7:3$];")).toEqual(" such eval ") + expect(checkEvilProperties(obj1)).toEqual(true) + + val checkQuotesProperty = js.eval(""" + function dynamicLiteralNameEncoding_quote(x) { + return x["'" + '"'] === 7357; + } + dynamicLiteralNameEncoding_quote + """).asInstanceOf[js.Function1[js.Any, Boolean]] + + val quote = '"' + + Seq( + obj("'" + quote -> 7357), + obj(s"'$quote" -> 7357), + obj("'\"" -> 7357), + obj("'" + quote -> 7357) + ).foreach { o => + val dict = o.asInstanceOf[js.Dictionary[js.Any]] + expect(dict("'\"")).toEqual(7357) + expect(dict("'" + quote)).toEqual(7357) + expect(dict(s"'$quote")).toEqual(7357) + expect(checkQuotesProperty(o)).toEqual(true) + } + } + + it("should return subclasses of js.Object in literal construction - #783") { + import js.Dynamic.{ literal => obj } + + val a: js.Object = obj(theValue = 1) + expect(a.hasOwnProperty("theValue")).toBeTruthy + expect(a.hasOwnProperty("noValue")).toBeFalsy + + val b: js.Object = obj("theValue" -> 2) + expect(b.hasOwnProperty("theValue")).toBeTruthy + expect(b.hasOwnProperty("noValue")).toBeFalsy + + } + } +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/jsinterop/ExportsTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/jsinterop/ExportsTest.scala new file mode 100644 index 0000000..d577d8d --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/jsinterop/ExportsTest.scala @@ -0,0 +1,1075 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.jsinterop + +import scala.scalajs.js +import js.annotation._ +import org.scalajs.jasminetest.{JasmineTest, JasmineTestFramework} + +import scala.annotation.meta + +object ExportsTest extends JasmineTest { + + /** This package in the JS (export) namespace */ + val jsPackage = js.Dynamic.global.scala.scalajs.testsuite.jsinterop + + describe("@JSExport") { + + it("should offer exports for methods with implicit name") { + class Foo { + @JSExport + def bar(): Int = 42 + @JSExport + def double(x: Int): Int = x*2 + } + + val foo = (new Foo).asInstanceOf[js.Dynamic] + expect(js.typeOf(foo.bar)).toBe("function") + expect(foo.bar()).toEqual(42) + expect(foo.double(3)).toEqual(6) + } + + it("should offer exports for methods with explicit name") { + class Foo { + @JSExport("theAnswer") + def bar(): Int = 42 + @JSExport("doubleTheParam") + def double(x: Int): Int = x*2 + } + + val foo = (new Foo).asInstanceOf[js.Dynamic] + expect(foo.bar).toBeUndefined + expect(js.typeOf(foo.theAnswer)).toBe("function") + expect(foo.theAnswer()).toEqual(42) + expect(foo.doubleTheParam(3)).toEqual(6) + } + + it("should offer exports for methods with constant folded name") { + class Foo { + @JSExport(ExportNameHolder.methodName) + def bar(): Int = 42 + } + + val foo = (new Foo).asInstanceOf[js.Dynamic] + expect(foo.bar).toBeUndefined + expect(foo.myMethod()).toEqual(42) + } + + it("should offer exports for protected methods") { + class Foo { + @JSExport + protected def bar(): Int = 42 + + @JSExport + protected[testsuite] def foo(): Int = 100 + } + + val foo = (new Foo).asInstanceOf[js.Dynamic] + expect(js.typeOf(foo.bar)).toBe("function") + expect(foo.bar()).toEqual(42) + expect(js.typeOf(foo.foo)).toBe("function") + expect(foo.foo()).toEqual(100) + } + + it("should offer exports for properties with implicit name") { + class Foo { + private[this] var myY: String = "hello" + @JSExport + val answer: Int = 42 + @JSExport + var x: Int = 3 + @JSExport + def doubleX: Int = x*2 + @JSExport + def y: String = myY + " get" + @JSExport + def y_=(v: String): Unit = myY = v + " set" + } + + val foo = (new Foo).asInstanceOf[js.Dynamic] + expect(js.typeOf(foo.answer)).toBe("number") + expect(foo.answer).toEqual(42) + expect(foo.x).toEqual(3) + expect(foo.doubleX).toEqual(6) + foo.x = 23 + expect(foo.x).toEqual(23) + expect(foo.doubleX).toEqual(46) + expect(foo.y).toEqual("hello get") + foo.y = "world" + expect(foo.y).toEqual("world set get") + } + + it("should offer exports for properties with explicit name") { + class Foo { + private[this] var myY: String = "hello" + @JSExport("answer") + val answerScala: Int = 42 + @JSExport("x") + var xScala: Int = 3 + @JSExport("doubleX") + def doubleXScala: Int = xScala*2 + @JSExport("y") + def yGetter: String = myY + " get" + @JSExport("y") + def ySetter_=(v: String): Unit = myY = v + " set" + } + + val foo = (new Foo).asInstanceOf[js.Dynamic] + expect(foo.answerScala).toBeUndefined + expect(js.typeOf(foo.answer)).toBe("number") + expect(foo.answer).toEqual(42) + expect(foo.x).toEqual(3) + expect(foo.doubleX).toEqual(6) + foo.x = 23 + expect(foo.x).toEqual(23) + expect(foo.doubleX).toEqual(46) + expect(foo.y).toEqual("hello get") + foo.y = "world" + expect(foo.y).toEqual("world set get") + } + + it("should offer exports for protected properties") { + class Foo { + @JSExport + protected val x: Int = 42 + @JSExport + protected[testsuite] val y: Int = 43 + } + + val foo = (new Foo).asInstanceOf[js.Dynamic] + expect(foo.x).toEqual(42) + expect(foo.y).toEqual(43) + } + + it("should offer overloaded exports for methods") { + class Foo { + @JSExport("foobar") + def foo(): Int = 42 + @JSExport("foobar") + def bar(x: Int): Int = x*2 + } + + val foo = (new Foo).asInstanceOf[js.Dynamic] + expect(js.typeOf(foo.foobar)).toBe("function") + expect(foo.foobar()).toEqual(42) + expect(foo.foobar(3)).toEqual(6) + } + + it("should offer multiple exports for the same method") { + class Foo { + @JSExport + @JSExport("b") + @JSExport("c") + def a(): Int = 1 + } + + val foo = (new Foo).asInstanceOf[js.Dynamic] + expect(js.typeOf(foo.a)).toBe("function") + expect(js.typeOf(foo.b)).toBe("function") + expect(js.typeOf(foo.c)).toBe("function") + + expect(foo.a()).toEqual(1) + expect(foo.b()).toEqual(1) + expect(foo.c()).toEqual(1) + } + + it("should inherit exports from traits") { + trait Foo { + @JSExport + def x: Int + + @JSExport + def method(x: Int): Int + } + + class Bar extends Foo { + val x = 1 + def method(x: Int) = 2 * x + } + + val bar = (new Bar).asInstanceOf[js.Dynamic] + expect(bar.x).toEqual(1) + expect(js.typeOf(bar.method)).toBe("function") + expect(bar.method(2)).toEqual(4) + } + + it("should offer overloading with inherited exports") { + class A { + @JSExport + def foo(x: Int) = 2*x + } + + class B extends A{ + @JSExport("foo") + def bar(x: String) = s"Hello $x" + } + + val b = (new B).asInstanceOf[js.Dynamic] + expect(js.typeOf(b.foo)).toBe("function") + expect(b.foo(1)).toEqual(2) + expect(b.foo("World")).toEqual("Hello World") + } + + it("should offer exports for generic methods") { + class Foo { + @JSExport + def gen[T <: AnyRef](x: T) = x + } + + val x = (new Object).asInstanceOf[js.Any] + + val foo = (new Foo).asInstanceOf[js.Dynamic] + expect(js.typeOf(foo.gen)).toBe("function") + expect(foo.gen(x)).toBe(x) + } + + it("should offer exports for lambda return types") { + class Foo { + @JSExport + def lambda(x: Int) = (y: Int) => x + y + } + + val foo = (new Foo).asInstanceOf[js.Dynamic] + expect(js.typeOf(foo.lambda)).toBe("function") + + val lambda = foo.lambda(5).asInstanceOf[Function1[Int,Int]] + + expect(lambda(4)).toEqual(9) + } + + it("should offer exports for multi parameter lists") { + class Foo { + @JSExport + def multiParam(x: Int)(y: Int): Int = x + y + } + + val foo = (new Foo).asInstanceOf[js.Dynamic] + expect(js.typeOf(foo.multiParam)).toBe("function") + expect(foo.multiParam(5,6)).toEqual(11) + } + + it("should offer exports for default arguments") { + class Foo { + @JSExport + def defArg(x: Int = 1) = x + } + + val foo = (new Foo).asInstanceOf[js.Dynamic] + expect(js.typeOf(foo.defArg)).toBe("function") + expect(foo.defArg(5)).toEqual(5) + } + + it("should offer exports for weird stuff") { + class UhOh { + // Something no one should export + @JSExport + def ahem[T : Comparable](x: T)(implicit y: Int) = ??? + } + + val x = (new UhOh).asInstanceOf[js.Dynamic] + expect(js.typeOf(x.ahem)).toBe("function") + } + + it("should offer exports with value class return types") { + class Foo { + @JSExport + def vc(x: Int) = new SomeValueClass(x) + } + + val foo = (new Foo).asInstanceOf[js.Dynamic] + expect(js.typeOf(foo.vc)).toBe("function") + + // The result should be a boxed SomeValueClass + val result = foo.vc(5) + expect(js.typeOf(result)).toEqual("object") + expect((result: Any).isInstanceOf[SomeValueClass]).toBeTruthy + expect((result: Any) == (new SomeValueClass(5))).toBeTruthy + } + + it("should allow exports with Any as return type") { + class A + class Foo { + @JSExport + def foo(switch: Boolean): Any = + if (switch) 1 else new A + } + + val foo = (new Foo).asInstanceOf[js.Dynamic] + expect(foo.foo(true).isInstanceOf[Int]).toBeTruthy + expect(foo.foo(false).isInstanceOf[A]).toBeTruthy + } + + it("should accept boxed value classes as parameter") { + class Foo { + @JSExport + def vc(x: SomeValueClass) = x.i + } + + val foo = (new Foo).asInstanceOf[js.Dynamic] + expect(js.typeOf(foo.vc)).toBe("function") + + // The parameter should be a boxed SomeValueClass + val valueCls = new SomeValueClass(7) + val result = foo.vc(valueCls.asInstanceOf[js.Any]) + expect(js.typeOf(result)).toEqual("number") + expect(result).toEqual(7) + } + + it("should offer exports for overridden methods with refined return type") { + class A + class B extends A + + class C1 { + @JSExport + def x: A = new A + } + + class C2 extends C1 { + override def x: B = new B + } + + val c2 = (new C2).asInstanceOf[js.Dynamic] + expect(c2.x.isInstanceOf[B]).toBeTruthy + } + + it("should offer exports for methods with refined types as return type") { + class A { + @JSExport + def foo(x: String): js.Object with js.Dynamic = + js.Dynamic.literal(arg = x) + } + + val a = (new A).asInstanceOf[js.Dynamic] + expect(a.foo("hello")).toEqual(js.Dynamic.literal(arg = "hello")) + } + + it("should offer exports for variable argument methods - #393") { + class A { + @JSExport + def foo(i: String*) = i.mkString("|") + } + + val a = (new A).asInstanceOf[js.Dynamic] + + expect(a.foo()).toEqual("") + expect(a.foo("a", "b", "c")).toEqual("a|b|c") + expect(a.foo("a", "b", "c", "d")).toEqual("a|b|c|d") + } + + it("should correctly overload in view of difficult repeated parameter lists") { + class A { + @JSExport + def foo(a: String, b: String, i: Int, c: String) = 1 + + @JSExport + def foo(a: String*) = 2 + + @JSExport + def foo(x: Int)(a: Int*) = x * 100000 + a.sum + } + + val a = (new A).asInstanceOf[js.Dynamic] + + expect(a.foo()).toEqual(2) + expect(a.foo("asdf")).toEqual(2) + expect(a.foo("asdf", "foo")).toEqual(2) + expect(a.foo("asdf", "foo", "bar")).toEqual(2) + expect(a.foo("asdf", "foo", 1, "bar")).toEqual(1) + expect(a.foo("asdf", "foo", "foo", "bar")).toEqual(2) + expect(a.foo(5, 1, 2, 3, 10)).toEqual(500016) + expect(a.foo(1)).toEqual(100000) + } + + it("should offer exports with default arguments") { + class A { + var oneCount: Int = 0 + def one = { + oneCount += 1 + 1 + } + @JSExport + def foo(a: Int = one)(b: Int = a + one)(c: Int = b + one) = + a + b + c + } + + val a = new A + val jsa = a.asInstanceOf[js.Dynamic] + + expect(jsa.foo()).toEqual(6) + expect(a.oneCount).toEqual(3) + + expect(jsa.foo(2)).toEqual(9) + expect(a.oneCount).toEqual(5) + + expect(jsa.foo(2,4)).toEqual(11) + expect(a.oneCount).toEqual(6) + + expect(jsa.foo(2,4,10)).toEqual(16) + expect(a.oneCount).toEqual(6) + + expect(jsa.foo((),4,10)).toEqual(15) + expect(a.oneCount).toEqual(7) + + expect(jsa.foo((),4)).toEqual(10) + expect(a.oneCount).toEqual(9) + } + + it("should correctly overload methods in presence of default parameters") { + class A { + @JSExport + def foo(a: Int)(b: Int = 5)(c: Int = 7) = 1000 + a + b + c + + @JSExport + def foo(a: Int, b: String) = 2 + + @JSExport + def foo(a: Int, b: Int, c: String) = 3 + } + + val a = (new A).asInstanceOf[js.Dynamic] + + expect(a.foo(1)).toEqual(1013) + expect(a.foo(1, 4)).toEqual(1012) + expect(a.foo(1, 4, 5)).toEqual(1010) + expect(a.foo(1, "foo")).toEqual(2) + expect(a.foo(1, 2, "foo")).toEqual(3) + + } + + it("should prefer overloads taking a js.Undefined over methods with default parameters") { + class A { + @JSExport + def foo(a: Int)(b: String = "asdf") = s"$a $b" + + @JSExport + def foo(a: Int, b: js.prim.Undefined) = "woot" + } + + val a = (new A).asInstanceOf[js.Dynamic] + + expect(a.foo(1)).toEqual("1 asdf") + expect(a.foo(2, "omg")).toEqual("2 omg") + expect(a.foo(1, ())).toEqual("woot") + + } + + it("should correctly overload methods in presence of default parameters and repeated parameters") { + class A { + @JSExport + def foo(x: Int, y: Int = 1) = x + y + @JSExport + def foo(x: String*) = x.mkString("|") + } + + val a = (new A).asInstanceOf[js.Dynamic] + + expect(a.foo(1)).toEqual(2) + expect(a.foo(1, 2)).toEqual(3) + expect(a.foo()).toEqual("") + expect(a.foo("foo")).toEqual("foo") + expect(a.foo("foo","bar")).toEqual("foo|bar") + + } + + it("should correctly overload exports called `toString`") { + class A { + override def toString(): String = "no arg" + @JSExport + def toString(x: Int): String = s"with arg: $x" + } + + val a = (new A).asInstanceOf[js.Dynamic] + expect(a.applyDynamic("toString")()).toEqual("no arg") + expect(a.applyDynamic("toString")(1)).toEqual("with arg: 1") + } + + it("should allow to explicitly export toString") { + class A { + @JSExport("toString") + override def toString(): String = "called" + } + + val a = (new A).asInstanceOf[js.Dynamic] + expect(a.applyDynamic("toString")()).toEqual("called") + } + + it("should correctly box repeated parameter lists with value classes") { + class A { + @JSExport + def foo(vcs: SomeValueClass*) = vcs.map(_.i).sum + } + + val vc1 = new SomeValueClass(1) + val vc2 = new SomeValueClass(2) + val a = (new A).asInstanceOf[js.Dynamic] + + expect(a.foo(vc1.asInstanceOf[js.Any], vc2.asInstanceOf[js.Any])).toEqual(3) + } + + it("should offer exports for objects with implicit name") { + val accessor = jsPackage.ExportedObject + expect(accessor).toBeDefined + expect(js.typeOf(accessor)).toEqual("function") + val obj = accessor() + expect(obj).toBeDefined + expect(js.typeOf(obj)).toEqual("object") + expect(obj.witness).toEqual("witness") + } + + it("should offer exports for objects with explicit name") { + val accessor = js.Dynamic.global.TheExportedObject + expect(accessor).toBeDefined + expect(js.typeOf(accessor)).toEqual("function") + val obj = accessor() + expect(obj).toBeDefined + expect(js.typeOf(obj)).toEqual("object") + expect(obj.witness).toEqual("witness") + } + + it("should offer exports for objects with qualified name") { + val accessor = js.Dynamic.global.qualified.testobject.ExportedObject + expect(accessor).toBeDefined + expect(js.typeOf(accessor)).toEqual("function") + val obj = accessor() + expect(obj).toBeDefined + expect(js.typeOf(obj)).toEqual("object") + expect(obj.witness).toEqual("witness") + } + + it("should offer exports for objects with constant folded name") { + val accessor = js.Dynamic.global.ConstantFoldedObjectExport + expect(accessor).toBeDefined + expect(js.typeOf(accessor)).toEqual("function") + val obj = accessor() + expect(obj).toBeDefined + expect(js.typeOf(obj)).toEqual("object") + expect(obj.witness).toEqual("witness") + } + + it("should offer exports for protected objects") { + val accessor = jsPackage.ProtectedExportedObject + expect(accessor).toBeDefined + expect(js.typeOf(accessor)).toEqual("function") + val obj = accessor() + expect(obj).toBeDefined + expect(js.typeOf(obj)).toEqual("object") + expect(obj.witness).toEqual("witness") + } + + it("should offer exports for classes with implicit name") { + val constr = jsPackage.ExportedClass + expect(constr).toBeDefined + expect(js.typeOf(constr)).toEqual("function") + val obj = js.Dynamic.newInstance(constr)(5) + expect(obj.x).toEqual(5) + } + + it("should offer exports for classes with explicit name") { + val constr = js.Dynamic.global.TheExportedClass + expect(constr).toBeDefined + expect(js.typeOf(constr)).toEqual("function") + val obj = js.Dynamic.newInstance(constr)(5) + expect(obj.x).toEqual(5) + } + + it("should offer exports for classes with qualified name") { + val constr = js.Dynamic.global.qualified.testclass.ExportedClass + expect(constr).toBeDefined + expect(js.typeOf(constr)).toEqual("function") + val obj = js.Dynamic.newInstance(constr)(5) + expect(obj.x).toEqual(5) + } + + it("should offer exports for classes with constant folded name") { + val constr = js.Dynamic.global.ConstantFoldedClassExport + expect(constr).toBeDefined + expect(js.typeOf(constr)).toEqual("function") + val obj = js.Dynamic.newInstance(constr)(5) + expect(obj.x).toEqual(5) + } + + it("should offer exports for protected classes") { + val constr = jsPackage.ProtectedExportedClass + expect(constr).toBeDefined + expect(js.typeOf(constr)).toEqual("function") + val obj = js.Dynamic.newInstance(constr)(5) + expect(obj.x).toEqual(5) + } + + it("should offer export for classes with repeated parameters in ctor") { + val constr = jsPackage.ExportedVarArgClass + expect(js.Dynamic.newInstance(constr)().result).toEqual("") + expect(js.Dynamic.newInstance(constr)("a").result).toEqual("a") + expect(js.Dynamic.newInstance(constr)("a", "b").result).toEqual("a|b") + expect(js.Dynamic.newInstance(constr)("a", "b", "c").result).toEqual("a|b|c") + expect(js.Dynamic.newInstance(constr)(5, "a").result).toEqual("Number: <5>|a") + } + + it("should offer export for classes with default parameters in ctor") { + val constr = jsPackage.ExportedDefaultArgClass + expect(js.Dynamic.newInstance(constr)(1,2,3).result).toEqual(6) + expect(js.Dynamic.newInstance(constr)(1).result).toEqual(106) + expect(js.Dynamic.newInstance(constr)(1,2).result).toEqual(103) + } + + it("should correctly disambiguate overloads involving longs") { + + class Foo { + @JSExport + def foo(x: Int) = 1 + @JSExport + def foo(x: Long) = 2 + } + + val foo = (new Foo).asInstanceOf[js.Dynamic] + + // Create a long factory we can call dynamically to retrieve an unboxed + // long which is typed as a js.Any + object LongFactory { + @JSExport + def aLong = 1L + } + val trueJsLong = LongFactory.asInstanceOf[js.Dynamic].aLong + + expect(foo.foo(1)).toEqual(1) + expect(foo.foo(trueJsLong)).toEqual(2) + } + + it("should return boxed Chars") { + class Foo { + @JSExport + def bar(x: Int): Char = x.toChar + } + val foo = (new Foo).asInstanceOf[js.Dynamic] + + val funs = js.eval(""" + var funs = { + testIsChar: function(foo) { return JSUtils().isChar(foo.bar(65)); }, + testCharValue: function(foo) { return JSUtils().charToString(foo.bar(65)); } + }; funs; + """).asInstanceOf[js.Dynamic] + + expect(funs.testIsChar(foo)).toBeTruthy + expect(funs.testCharValue(foo)).toEqual("A") + } + + it("should take boxed Chars as parameter") { + class Foo { + @JSExport + def bar(x: Char): Int = x.toInt + } + val foo = (new Foo).asInstanceOf[js.Dynamic] + + val f = js.eval(""" + var f = function(foo) { return foo.bar(JSUtils().stringToChar('e')); }; + f; + """).asInstanceOf[js.Dynamic] + + expect(f(foo)).toEqual('e'.toInt) + } + + it("should be able to disambiguate an Int from a Char") { + class Foo { + @JSExport + def bar(x: Char): String = "char: "+x + @JSExport + def bar(x: Int): String = "int: "+x + } + val foo = (new Foo).asInstanceOf[js.Dynamic] + + val funs = js.eval(""" + var funs = { + testChar: function(foo) { return foo.bar(JSUtils().stringToChar('S')); }, + testInt: function(foo) { return foo.bar(68); } + }; funs; + """).asInstanceOf[js.Dynamic] + + expect(funs.testChar(foo)).toEqual("char: S") + expect(funs.testInt(foo)).toEqual("int: 68") + } + + it("should support exporting constructor parameter fields - #970") { + class Foo(@(JSExport @meta.field) val x: Int) + val foo = (new Foo(1)).asInstanceOf[js.Dynamic] + expect(foo.x).toEqual(1) + } + + it("should support exporting case class fields - #970") { + case class Foo(@(JSExport @meta.field) x: Int) + val foo = (new Foo(1)).asInstanceOf[js.Dynamic] + expect(foo.x).toEqual(1) + } + + it("should support exporting lazy values - #977") { + class Foo { + @JSExport + lazy val x = 1 + } + val foo = (new Foo).asInstanceOf[js.Dynamic] + expect(foo.x).toEqual(1) + } + + it("should support exporting all members of a class") { + @JSExportAll + class Foo { + val a = 1 + + @JSExport // double annotation allowed + def b = 2 + + lazy val c = 3 + + class Bar // not exported, but should not fail + } + + val foo = (new Foo).asInstanceOf[js.Dynamic] + + expect(foo.a).toEqual(1) + expect(foo.b).toEqual(2) + expect(foo.c).toEqual(3) + } + + it("should not export synthetic members with @JSExportAll - #1195") { + @JSExportAll + case class Foo(x: Int) + + val foo = Foo(1).asInstanceOf[js.Dynamic] + + expect(foo.x).toEqual(1) + expect(foo.copy).toBeUndefined + } + + it("should allow mutliple equivalent JSExport annotations") { + class Foo { + @JSExport + @JSExport("a") + @JSExport + @JSExport("a") + def b = 1 + } + + val foo = (new Foo).asInstanceOf[js.Dynamic] + + expect(foo.b).toEqual(1) + } + + it("should support named exports") { + import js.Dynamic.{literal => lit} + + class FooNamed { + @JSExportNamed("bar1") + def bar(x: Int, y: Int) = x + y + + @JSExportNamed("bar2") + @JSExport + def bar(x: Int = 1)(y: Int = x)(z: Int = y) = x + y + z + } + + val foo = (new FooNamed).asInstanceOf[js.Dynamic] + + expect(foo.bar1(lit(x = 1, y = 2))).toEqual(3) + if (JasmineTestFramework.hasTag("compliant-asinstanceof")) + expect(() => foo.bar1(lit(x = 1))).toThrow // missing arg + expect(foo.bar2(lit())).toEqual(3) + expect(foo.bar2(lit(x = 2))).toEqual(6) + expect(foo.bar2(lit(y = 2))).toEqual(5) + expect(foo.bar2(lit(y = 2, z = 1))).toEqual(4) + expect(foo.bar(2)).toEqual(6) + expect(foo.bar(2,3)).toEqual(8) + } + + it("should support named constructor exports") { + import js.Dynamic.{literal => lit} + + val constr = jsPackage.ExportedNamedArgClass + expect(js.Dynamic.newInstance(constr)(lit(x = 2)).result).toEqual("22true") + expect(js.Dynamic.newInstance(constr)(lit(y = "foo")).result).toEqual("1foofalse") + expect(js.Dynamic.newInstance(constr)(lit(z = true, y = "foo")).result).toEqual("1footrue") + } + + it("should support exporting under 'org' namespace - #364") { + val accessor = js.Dynamic.global.org.ExportedUnderOrgObject + expect(js.typeOf(accessor)).toEqual("function") + val obj = accessor() + expect(obj).toBe(ExportedUnderOrgObject.asInstanceOf[js.Any]) + } + + when("compliant-asinstanceof"). + it("should reject bad values for arguments of primitive value type") { + class Foo { + @JSExport + def doBool(x: Boolean) = x + @JSExport + def doChar(x: Char) = x + @JSExport + def doByte(x: Byte) = x + @JSExport + def doShort(x: Short) = x + @JSExport + def doInt(x: Int) = x + @JSExport + def doLong(x: Long) = x + @JSExport + def doFloat(x: Float) = x + @JSExport + def doDouble(x: Double) = x + @JSExport + def doUnit(x: Unit) = x + } + + val foo = (new Foo).asInstanceOf[js.Dynamic] + + // Nulls + expect(() => foo.doBool(null)).toThrow + expect(() => foo.doChar(null)).toThrow + expect(() => foo.doByte(null)).toThrow + expect(() => foo.doShort(null)).toThrow + expect(() => foo.doInt(null)).toThrow + expect(() => foo.doLong(null)).toThrow + expect(() => foo.doFloat(null)).toThrow + expect(() => foo.doDouble(null)).toThrow + expect(() => foo.doUnit(null)).toThrow + + // Class type + expect(() => foo.doBool(foo)).toThrow + expect(() => foo.doChar(foo)).toThrow + expect(() => foo.doByte(foo)).toThrow + expect(() => foo.doShort(foo)).toThrow + expect(() => foo.doInt(foo)).toThrow + expect(() => foo.doLong(foo)).toThrow + expect(() => foo.doFloat(foo)).toThrow + expect(() => foo.doDouble(foo)).toThrow + expect(() => foo.doUnit(foo)).toThrow + + // Bad values + expect(() => foo.doBool(1)).toThrow + expect(() => foo.doBool("a")).toThrow + + expect(() => foo.doChar(1)).toThrow + expect(() => foo.doChar("a")).toThrow + + expect(() => foo.doByte(300)).toThrow + expect(() => foo.doByte("a")).toThrow + + expect(() => foo.doShort(32768)).toThrow + expect(() => foo.doShort("a")).toThrow + + expect(() => foo.doInt(3.2)).toThrow + expect(() => foo.doInt("a")).toThrow + + expect(() => foo.doLong(3.2)).toThrow + expect(() => foo.doLong(3)).toThrow + expect(() => foo.doLong("a")).toThrow + + expect(() => foo.doFloat("a")).toThrow + } + + when("compliant-asinstanceof"). + it("should reject bad values for arguments of value class type - #613") { + class Foo { + @JSExport + def doVC(x: SomeValueClass) = x + } + + val foo = (new Foo).asInstanceOf[js.Dynamic] + + expect(() => foo.doVC(null)).toThrow + expect(() => foo.doVC(foo)).toThrow + expect(() => foo.doVC(1)).toThrow + expect(() => foo.doVC("a")).toThrow + } + + when("compliant-asinstanceof"). + it("should reject bad values for arguments of class type") { + class A + class B + + class Foo { + @JSExport + def doA(x: A) = x + } + + val foo = (new Foo).asInstanceOf[js.Dynamic] + + expect(() => foo.doA(1)).toThrow + expect(() => foo.doA((new B).asInstanceOf[js.Any])).toThrow + expect(() => foo.doA("a")).toThrow + } + + it("should offer exports for classes ending in _= - #1090") { + val constr = jsPackage.ExportClassSetterNamed_= + val obj = js.Dynamic.newInstance(constr)() + expect(obj.x).toBe(1) + } + + it("should offer exports for objects ending in _= - #1090") { + expect(jsPackage.ExportObjSetterNamed_=().x).toBe(1) + } + + } // describe + + describe("@JSExportDescendentObjects") { + + it("should offer auto exports for objects extending a trait") { + val accessor = + js.Dynamic.global.scala.scalajs.testsuite.jsinterop.AutoExportedTraitObject + expect(accessor).toBeDefined + expect(js.typeOf(accessor)).toEqual("function") + val obj = accessor() + expect(obj).toBeDefined + expect(obj).toBe(AutoExportedTraitObject.asInstanceOf[js.Any]) + } + + it("should offer auto exports for objects extending a class") { + val accessor = + js.Dynamic.global.scala.scalajs.testsuite.jsinterop.AutoExportedClassObject + expect(accessor).toBeDefined + expect(js.typeOf(accessor)).toEqual("function") + val obj = accessor() + expect(obj).toBeDefined + expect(obj).toBe(AutoExportedClassObject.asInstanceOf[js.Any]) + } + + } + + describe("@JSExportDescendentClasses") { + + it("should offer auto exports for classes extending a trait") { + val ctor = + js.Dynamic.global.scala.scalajs.testsuite.jsinterop.AutoExportedTraitClass + expect(ctor).toBeDefined + expect(js.typeOf(ctor)).toEqual("function") + + val obj1 = js.Dynamic.newInstance(ctor)() + expect(obj1).toBeDefined + expect(obj1.x).toBe(5) + + val obj2 = js.Dynamic.newInstance(ctor)(100) + expect(obj2).toBeDefined + expect(obj2.x).toBe(100) + } + + it("should offer auto exports for classes extending a class") { + val ctor = + js.Dynamic.global.scala.scalajs.testsuite.jsinterop.AutoExportedClassClass + expect(ctor).toBeDefined + expect(js.typeOf(ctor)).toEqual("function") + + val obj1 = js.Dynamic.newInstance(ctor)() + expect(obj1).toBeDefined + expect(obj1.x).toBe(5) + + val obj2 = js.Dynamic.newInstance(ctor)(100) + expect(obj2).toBeDefined + expect(obj2.x).toBe(100) + } + + } + +} + +object ExportNameHolder { + final val className = "ConstantFoldedClassExport" + final val objectName = "ConstantFoldedObjectExport" + final val methodName = "myMethod" +} + +@JSExport +@JSExport("TheExportedObject") +@JSExport("qualified.testobject.ExportedObject") // purposefully halfway the same as ExportedClass +@JSExport(ExportNameHolder.objectName) +object ExportedObject { + @JSExport + def witness: String = "witness" +} + +@JSExport +protected object ProtectedExportedObject { + @JSExport + def witness: String = "witness" +} + +@JSExport +@JSExport("TheExportedClass") +@JSExport("qualified.testclass.ExportedClass") // purposefully halfway the same as ExportedObject +@JSExport(ExportNameHolder.className) +class ExportedClass(_x: Int) { + @JSExport + val x = _x +} + +@JSExport +protected class ProtectedExportedClass(_x: Int) { + @JSExport + val x = _x +} + +@JSExport +class ExportedVarArgClass(x: String*) { + + @JSExport + def this(x: Int, y: String) = this(s"Number: <$x>", y) + + @JSExport + def result = x.mkString("|") +} + +@JSExport +class ExportedDefaultArgClass(x: Int, y: Int, z: Int) { + + @JSExport + def this(x: Int, y: Int = 5) = this(x, y, 100) + + @JSExport + def result = x + y + z +} + +@JSExport("org.ExportedUnderOrgObject") +object ExportedUnderOrgObject + +@JSExportDescendentClasses +@JSExportDescendentObjects +trait AutoExportTrait + +object AutoExportedTraitObject extends AutoExportTrait +class AutoExportedTraitClass(_x: Int) extends AutoExportTrait { + def this() = this(5) + @JSExport + def x: Int = _x +} + +@JSExportDescendentClasses +@JSExportDescendentObjects +class AutoExportClass + +object AutoExportedClassObject extends AutoExportClass +class AutoExportedClassClass(_x: Int) extends AutoExportTrait { + def this() = this(5) + @JSExport + def x: Int = _x +} + +class SomeValueClass(val i: Int) extends AnyVal + +@JSExportNamed +class ExportedNamedArgClass(x: Int = 1)(y: String = x.toString)(z: Boolean = y != "foo") { + @JSExport + val result = x + y + z +} + +@JSExport +class ExportClassSetterNamed_= { + @JSExport + val x = 1 +} + +@JSExport +object ExportObjSetterNamed_= { + @JSExport + val x = 1 +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/jsinterop/FunctionTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/jsinterop/FunctionTest.scala new file mode 100644 index 0000000..4973e64 --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/jsinterop/FunctionTest.scala @@ -0,0 +1,41 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.jsinterop + +import scala.scalajs.js +import org.scalajs.jasminetest.JasmineTest + +object FunctionTest extends JasmineTest { + + import js.Dynamic.{literal => lit} + + describe("scala.scalajs.js.Function") { + + it("should support call() with expanded arguments") { + val f = js.eval(""" + var f = function() { return arguments; }; f; + """).asInstanceOf[js.Function] + + expect(f.call(null, 42, true)).toEqual(lit( + `0` = 42, + `1` = true)) + } + + it("should support call() with the :_* notation to expand a Seq") { + val f = js.eval(""" + var f = function() { return arguments; }; f; + """).asInstanceOf[js.Function] + + val args = Seq[js.Any](42, true) + expect(f.call(null, args: _*)).toEqual(lit( + `0` = 42, + `1` = true)) + } + + } +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/jsinterop/MiscInteropTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/jsinterop/MiscInteropTest.scala new file mode 100644 index 0000000..08211c3 --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/jsinterop/MiscInteropTest.scala @@ -0,0 +1,89 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.jsinterop + +import scala.scalajs.js +import org.scalajs.jasminetest.JasmineTest + +object MiscInteropTest extends JasmineTest { + + describe("scala.scalajs.js.package") { + + it("should provide an equivalent to `typeof x`") { + import js.typeOf + expect(typeOf(5)).toEqual("number") + expect(typeOf(false)).toEqual("boolean") + expect(typeOf("hello")).toEqual("string") + expect(typeOf(null)).toEqual("object") + expect(typeOf(new js.Object)).toEqual("object") + expect(typeOf(())).toEqual("undefined") + expect(typeOf(() => 42)).toEqual("function") + } + } + + describe("scala.scalajs.js.Object") { + + it("should provide an equivalent to `p in o`") { + import js.Object.{ hasProperty => hasProp } + val o = js.Dynamic.literal(foo = 5, bar = "foobar").asInstanceOf[js.Object] + expect(hasProp(o, "foo")).toBeTruthy + expect(hasProp(o, "foobar")).toBeFalsy + expect(hasProp(o, "toString")).toBeTruthy // in prototype + } + + it("should respect evaluation order for `hasProperty`") { + import js.Object.{ hasProperty => hasProp } + var indicator = 3 + def o() = { + indicator += 4 + js.Dynamic.literal(x = 5).asInstanceOf[js.Object] + } + def p() = { + indicator *= 2 + "x" + } + expect(hasProp(o(), p())).toBeTruthy + expect(indicator).toEqual(14) + } + + it("should provide equivalent of JS for-in loop of {} - #13") { + val obj = js.eval("var dictionaryTest13 = { a: 'Scala.js', b: 7357 }; dictionaryTest13;") + val dict = obj.asInstanceOf[js.Dictionary[js.Any]] + var propCount = 0 + var propString = "" + + for (prop <- js.Object.properties(dict)) { + propCount += 1 + propString += dict(prop) + } + + expect(propCount).toEqual(2) + expect(propString).toEqual("Scala.js7357") + } + + it("should provide equivalent of JS for-in loop of [] - #13") { + val obj = js.eval("var arrayTest13 = [ 7, 3, 5, 7 ]; arrayTest13;") + val array = obj.asInstanceOf[js.Dictionary[js.Any]] + var propCount = 0 + var propString = "" + + for (prop <- js.Object.properties(array)) { + propCount += 1 + propString += array(prop) + } + + expect(propCount).toEqual(4) + expect(propString).toEqual("7357") + } + + it("should compile js.undefined") { + expect(() => js.undefined.asInstanceOf[js.prim.Number].toString(10)).toThrow + } + } + +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/jsinterop/RuntimeLongTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/jsinterop/RuntimeLongTest.scala new file mode 100644 index 0000000..589f379 --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/jsinterop/RuntimeLongTest.scala @@ -0,0 +1,140 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.jsinterop + +import scala.scalajs.runtime.RuntimeLong + +import org.scalajs.jasmine.JasmineExpectation +import org.scalajs.jasminetest.JasmineTest + +import scala.util.Try + +/** + * test the runtime Long implementation directly + * does not depend on magic compiler Long rewriting + */ +object RuntimeLongTest extends JasmineTest { + + import RuntimeLong.fromDouble + + /** overload expect for long to add toString */ + def expect(l: RuntimeLong): JasmineExpectation = expect(l.toHexString) + + describe("scala.scalajs.runtime.RuntimeLong") { + + def fromInt(x: Int): RuntimeLong = new RuntimeLong(x) + + val maxInt = fromInt(Int.MaxValue) + val minInt = fromInt(Int.MinValue) + val one = fromInt(1) + val billion = fromInt(1000000000) + + val `4503599627370510L` = new RuntimeLong( 14, 0, 256) + val `613354684553L` = new RuntimeLong( 639113, 146235, 0) + val `9863155567412L` = new RuntimeLong(2247476, 2351559, 0) + val `3632147899696541255L` = new RuntimeLong(1568327, 2954580, 206463) + val `7632147899696541255L` = new RuntimeLong(2616903, 1593290, 433837) + + it("should correctly implement negation") { + expect(-fromInt(5)).toEqual("fffffffffffffffb") + expect(-fromInt(0)).toEqual("0") + expect(-minInt ).toEqual("80000000") + } + + it("should correctly implement comparison") { + expect(fromInt(7) < fromInt(15)).toBe(true) + expect(fromInt(15) < fromInt(15)).toBe(false) + expect(fromInt(15) <= fromInt(15)).toBe(true) + expect(fromInt(14) <= fromInt(15)).toBe(true) + expect(fromInt(15) > fromInt(15)).toBe(false) + expect(fromInt(14) > fromInt(15)).toBe(false) + expect(fromInt(16) > fromInt(15)).toBe(true) + expect(fromInt(15) >= fromInt(15)).toBe(true) + expect(fromInt(14) >= fromInt(15)).toBe(false) + expect(fromInt(16) >= fromInt(15)).toBe(true) + } + + it("should correctly implement addition") { + expect(fromInt(7) + fromInt(15)).toEqual("16") + expect( maxInt + maxInt ).toEqual("fffffffe") + expect( maxInt + one ).toEqual("80000000") + } + + it("should correctly implement subtraction") { + expect(fromInt(7) - fromInt(15)).toEqual("fffffffffffffff8") + expect( maxInt - maxInt ).toEqual("0") + } + + it("should correctly implement multiplication") { + expect(fromInt(7) * fromInt(15)).toEqual("69") + expect(fromInt(-7) * fromInt(15)).toEqual("ffffffffffffff97") + expect( maxInt * maxInt ).toEqual("3fffffff00000001") + expect(`4503599627370510L` * fromInt(-4)).toEqual("ffbfffffffffffc8") + } + + it("should correctly implement division") { + expect( fromInt(7) / fromInt(15)).toEqual("0") + expect( fromInt(24) / fromInt(5) ).toEqual("4") + expect( fromInt(24) / fromInt(-5)).toEqual("fffffffffffffffc") + expect( maxInt / fromInt(-5)).toEqual("ffffffffe6666667") + expect( maxInt / billion ).toEqual("2") + expect((maxInt+one) / billion ).toEqual("2") + } + + it("should correctly implement modulus") { + expect( fromInt(7) % fromInt(15)).toEqual("7") + expect( fromInt(24) % fromInt(5) ).toEqual("4") + expect( fromInt(24) % fromInt(-5)).toEqual("4") + expect( maxInt % billion ).toEqual("8ca6bff") + expect((maxInt+one) % billion ).toEqual("8ca6c00") + expect( maxInt % fromInt(-5)).toEqual("2") + } + + it("should correctly implement toString") { + expect(maxInt.toString).toEqual("2147483647") + expect(fromInt(-50).toString).toEqual("-50") + expect(fromInt(-1000000000).toString).toEqual("-1000000000") + expect((maxInt+one).toString).toEqual("2147483648") + expect(minInt.toString).toEqual("-2147483648") + } + + it("should correctly implement fromDouble") { + expect(fromDouble( 4.5)).toEqual("4") + expect(fromDouble(-4.5)).toEqual("fffffffffffffffc") + } + + it("should correctly implement toDouble") { + expect(fromInt(5).toDouble).toEqual(5.0) + expect((maxInt+one).toDouble).toEqual(2147483648.0) + } + + it("should correctly implement numberOfLeadingZeros") { + expect(fromInt( 0).numberOfLeadingZeros).toEqual(64) + expect(fromInt( 1).numberOfLeadingZeros).toEqual(63) + expect(fromInt(-1).numberOfLeadingZeros).toEqual(0) + expect(fromInt( 2).numberOfLeadingZeros).toEqual(62) + } + + it("should implement hashCode() according to spec in j.l.Long") { + expect(fromInt(0 ).hashCode()).toEqual(0) + expect(fromInt(55 ).hashCode()).toEqual(55) + expect(fromInt(-12 ).hashCode()).toEqual(11) + expect(fromInt(10006548).hashCode()).toEqual(10006548) + expect(fromInt(-1098748).hashCode()).toEqual(1098747) + + expect(`613354684553L` .hashCode()).toEqual(-825638905) + expect(`9863155567412L` .hashCode()).toEqual(1910653900) + expect(`3632147899696541255L`.hashCode()).toEqual(1735398658) + expect(`7632147899696541255L`.hashCode()).toEqual(-1689438124) + } + + } + +} + + diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/jsinterop/StrangeNamedTests.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/jsinterop/StrangeNamedTests.scala new file mode 100644 index 0000000..846c80b --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/jsinterop/StrangeNamedTests.scala @@ -0,0 +1,28 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.jsinterop + +import org.scalajs.jasminetest.JasmineTest + +object `1_TestName` extends JasmineTest { + describe("a test with name 1_TestName") { + it("should run") {} + } +} + +object eval extends JasmineTest { + describe("a test with name eval") { + it("should run") {} + } +} + +object `\u1f4a7` extends JasmineTest { + describe("a test with name \u1f4a9") { + it("should run") {} + } +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/jsinterop/ThisFunctionTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/jsinterop/ThisFunctionTest.scala new file mode 100644 index 0000000..4ac9058 --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/jsinterop/ThisFunctionTest.scala @@ -0,0 +1,70 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.jsinterop + +import scala.scalajs.js +import org.scalajs.jasminetest.JasmineTest + +object ThisFunctionTest extends JasmineTest { + + describe("scala.scalajs.js.ThisFunctionN") { + + it("should provide an implicit conversion from Scala function to js.ThisFunction") { + val g = js.eval(""" + var g = function(f, x) { return f.call(x, 42, x.foo); }; g; + """).asInstanceOf[js.Function2[js.ThisFunction2[ + js.Dynamic, Int, String, String], js.Dynamic, String]] + + val f = { (thiz: js.Dynamic, v: Int, u: String) => + expect(thiz).toBeTruthy() + expect(thiz.foobar).toEqual("foobar") + u + v + } + val obj = js.Object().asInstanceOf[js.Dynamic] + obj.foo = "foo" + obj.foobar = "foobar" + expect(g(f, obj)).toEqual("foo42") + } + + it("should accept a lambda where a js.ThisFunction is expected") { + val g = js.eval(""" + var g = function(f, x) { return f.call(x, 42, x.foo); }; g; + """).asInstanceOf[js.Function2[js.ThisFunction2[ + js.Dynamic, Int, String, String], js.Dynamic, String]] + + val obj = js.Object().asInstanceOf[js.Dynamic] + obj.foo = "foo" + obj.foobar = "foobar" + expect(g({ (thiz: js.Dynamic, v: Int, u: String) => + expect(thiz).toBeTruthy() + expect(thiz.foobar).toEqual("foobar") + u + v + }, obj)).toEqual("foo42") + } + + it("should bind the first argument to this when applying js.ThisFunctionN") { + val g = js.eval(""" + var g = function(x) { return this.foo + ":" + x; }; g; + """).asInstanceOf[js.ThisFunction1[js.Dynamic, Int, String]] + val obj = js.Object().asInstanceOf[js.Dynamic] + obj.foo = "foo" + expect(g(obj, 42)).toEqual("foo:42") + } + + it("should provide an implicit conversion from js.ThisFunction to Scala function") { + val g = js.eval(""" + var g = function(x) { return this.foo + ":" + x; }; g; + """).asInstanceOf[js.ThisFunction1[js.Dynamic, Int, String]] + val f: scala.Function2[js.Dynamic, Int, String] = g + val obj = js.Object().asInstanceOf[js.Dynamic] + obj.foo = "foo" + expect(f(obj, 42)).toEqual("foo:42") + } + + } +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/jsinterop/UndefOrTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/jsinterop/UndefOrTest.scala new file mode 100644 index 0000000..28eae15 --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/jsinterop/UndefOrTest.scala @@ -0,0 +1,191 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.jsinterop + +import scala.scalajs.js +import org.scalajs.jasminetest.JasmineTest + +import js.annotation.JSExport + +object UndefOrTest extends JasmineTest { + + def some[A](v: A): js.UndefOr[A] = v + def none[A]: js.UndefOr[A] = js.undefined + + describe("scala.scalajs.js.UndefOr[A]") { + + it("convert A to js.UndefOr[A]") { + val x: js.UndefOr[Int] = 42 + expect(x.isEmpty).toBeFalsy + expect(x.isDefined).toBeTruthy + expect(x.nonEmpty).toBeTruthy + expect(x.get).toEqual(42) + } + + it("convert undefined to js.UndefOr[A]") { + val x: js.UndefOr[Int] = js.undefined + expect(x.isEmpty).toBeTruthy + expect(x.isDefined).toBeFalsy + expect(x.nonEmpty).toBeFalsy + expect(() => x.get).toThrow + } + + it("convert to js.Any when A <% js.Any") { + val x: js.UndefOr[Int] = 42 + expect(x).toEqual(42) + + val y: js.UndefOr[String] = js.undefined + expect(y).toBeUndefined + } + + it("getOrElse") { + expect(some("hello").getOrElse("ko")).toEqual("hello") + expect(none[String].getOrElse("ok")).toEqual("ok") + + var defaultComputed = false + expect(some("test") getOrElse { + defaultComputed = true + "ko" + }).toEqual("test") + expect(defaultComputed).toBeFalsy + } + + it("orNull") { + expect(some("hello").orNull).toEqual("hello") + expect(none[String].orNull).toBeNull + } + + it("map") { + expect(some(62).map(_ / 3)).toEqual(62 / 3) + expect(none[Int].map(_ / 3)).toBeUndefined + } + + it("fold") { + expect(some(3).fold(10)(_ * 2)).toEqual(6) + expect(none[Int].fold(10)(_ * 2)).toEqual(10) + } + + it("flatMap") { + def f(x: Int): js.UndefOr[Int] = if (x > 0) x+3 else js.undefined + expect(some(6).flatMap(f)).toEqual(9) + expect(some(-6).flatMap(f)).toBeUndefined + expect(none[Int].flatMap(f)).toBeUndefined + } + + it("flatten") { + expect(some(some(7)).flatten.isDefined).toBeTruthy + expect(some(some(7)).flatten.get).toEqual(7) + expect(some(none[Int]).flatten.isDefined).toBeFalsy + expect(none[js.UndefOr[Int]].flatten.isDefined).toBeFalsy + } + + it("filter") { + expect(some(7).filter(_ > 0).isDefined).toBeTruthy + expect(some(7).filter(_ > 0).get).toEqual(7) + expect(some(7).filter(_ < 0).isDefined).toBeFalsy + expect(none[Int].filter(_ < 0).isDefined).toBeFalsy + } + + it("filterNot") { + expect(some(7).filterNot(_ < 0).isDefined).toBeTruthy + expect(some(7).filterNot(_ < 0).get).toEqual(7) + expect(some(7).filterNot(_ > 0).isDefined).toBeFalsy + expect(none[Int].filterNot(_ > 0).isDefined).toBeFalsy + } + + it("exists") { + expect(some(7).exists(_ > 0)).toBeTruthy + expect(some(7).exists(_ < 0)).toBeFalsy + expect(none[Int].exists(_ > 0)).toBeFalsy + } + + it("forall") { + expect(some(7).forall(_ > 0)).toBeTruthy + expect(some(7).forall(_ < 0)).toBeFalsy + expect(none[Int].forall(_ > 0)).toBeTruthy + } + + it("foreach") { + var witness1 = 3 + some(42).foreach(witness1 = _) + expect(witness1).toEqual(42) + + var witness2 = 3 + none[Int].foreach(witness2 = _) + expect(witness2).toEqual(3) + } + + it("collect") { + expect(some("hello") collect { + case "hello" => "ok" + }).toEqual("ok") + expect(some("hello") collect { + case "notthis" => "ko" + }).toBeUndefined + expect(none[String] collect { + case "hello" => "ko" + }).toBeUndefined + } + + it("collect should call guard at most once") { + var witness = 0 + def guard(x: String) = { + witness += 1 + true + } + expect(some("hello") collect { + case x @ "hello" if guard(x) => "ok" + }).toEqual("ok") + expect(witness).toEqual(1) + } + + it("orElse") { + expect(some(true) orElse some(false)).toBeTruthy + expect(some("ok") orElse none).toEqual("ok") + expect(none orElse some("yes")).toEqual("yes") + expect(none orElse none).toBeUndefined + } + + it("toList") { + import scala.scalajs.js.JSConverters._ + + expect(some("hello").toList.toJSArray).toEqual(js.Array("hello")) + expect(none[String].toList.toJSArray).toEqual(js.Array()) + } + + it("toLeft and toRight") { + expect(some("left").toLeft("right").isInstanceOf[Left[_, _]]).toBeTruthy + expect(none[String].toLeft("right").isInstanceOf[Right[_, _]]).toBeTruthy + expect(some("right").toRight("left").isInstanceOf[Right[_, _]]).toBeTruthy + expect(none[String].toRight("left").isInstanceOf[Left[_, _]]).toBeTruthy + } + + it("toOption") { + expect(some("foo").toOption == Some("foo")).toBeTruthy + expect(none.toOption == None).toBeTruthy + } + + } + + describe("scala.scalajs.js.JSConverters.JSRichOption") { + + import js.JSConverters._ + + it("should provide orUndefined") { + expect(Some("asdf").orUndefined).toEqual("asdf") + expect((None: Option[String]).orUndefined).toBeUndefined + + // This doesn't work on 2.10, since it doesn't infer + // Nothing <:< js.Any to implicitly convert UndefOr[Nothing] to + // js.Any + // expect(None.orUndefined).toBeUndefined + } + + } + +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/library/ArrayOpsTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/library/ArrayOpsTest.scala new file mode 100644 index 0000000..a1957a5 --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/library/ArrayOpsTest.scala @@ -0,0 +1,117 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.library + +import scala.scalajs.js +import org.scalajs.jasminetest.JasmineTest + +import scala.reflect.ClassTag + +import js.JSConverters._ + +object ArrayOpsTest extends JasmineTest { + + describe("scala.scalajs.js.ArrayOps") { + + // Methods we actually implement + + it("should implement apply") { + val array = js.Array(3,4,5,6,3,4) + val ops: js.ArrayOps[Int] = array + + expect(ops(0)).toEqual(3) + expect(ops(3)).toEqual(6) + + array(0) = 4 + expect(ops(0)).toEqual(4) + } + + it("should implement update") { + val array = js.Array(3,4,5,6,3,4) + val ops: js.ArrayOps[Int] = array + + expect(array(1)).toEqual(4) + ops(1) = 5 + expect(array(1)).toEqual(5) + + ops(5) = 10 + expect(array(5)).toEqual(10) + } + + it("should implement length") { + val array = js.Array(3,4,5,6,3,4) + val ops: js.ArrayOps[Int] = array + + expect(ops.length).toEqual(6) + array.push(1) + expect(ops.length).toEqual(7) + } + + it("should implement seq") { + val array = js.Array(3,4,5,6,3,4) + val ops: js.ArrayOps[Int] = array + val seq = ops.seq + + expect(seq.toList == List(3,4,5,6,3,4)).toBeTruthy + } + + it("should implement reduceLeft") { + val array = js.Array(100, 6, 2, 56, -1) + expect(array.reduceLeft(_ - _)).toEqual(37) + expect(() => js.Array[Int]().reduceLeft(_ + _)).toThrow + } + + it("should implement reduceRight") { + val array = js.Array("hello", "world") + expect(array.reduceRight(_ + ", " + _)).toEqual("hello, world") + expect(() => js.Array[Int]().reduceRight(_ + _)).toThrow + } + + it("should implement ++") { + val left = js.Array("hello", "world") + val right = js.Array("and", "everyone", "else") + expect(left ++ right).toEqual( + js.Array("hello", "world", "and", "everyone", "else")) + + val ints = js.Array(4, 3) + expect(left ++ ints).toEqual(js.Array("hello", "world", 4, 3)) + } + + // Some arbitrary methods to test the builders + + it("should implement collect") { + def ct[A : ClassTag](x: A) = implicitly[ClassTag[A]] + val array = js.Array(3,4,5,6,3,4) + val res = array.collect { + case x if x > 4 => 2*x + } + + expect(ct(res).runtimeClass == classOf[js.Array[Int]]).toBeTruthy + expect(res).toEqual(js.Array(10, 12)) + } + + it("should implement diff") { + val array = js.Array(1,2,1,3,1,10,9) + val diff = array.diff(Seq(1,3,9)) + expect(diff).toEqual(js.Array(2,1,1,10)) + } + + it("should implement toList - #843") { + val array = js.Array(1,2,1,3,1,10,9) + val list = array.toList + expect(list.toJSArray).toEqual(array) + } + + it("should implement to[T] - #843") { + val array = js.Array(1,2,1,3,1,10,9) + val list = array.to[List] + expect(list.toJSArray).toEqual(array) + } + + } +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/library/WrappedArrayTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/library/WrappedArrayTest.scala new file mode 100644 index 0000000..e4fed0a --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/library/WrappedArrayTest.scala @@ -0,0 +1,118 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.library + +import scala.scalajs.js +import org.scalajs.jasminetest.JasmineTest + +import scala.collection.mutable + +object WrappedArrayTest extends JasmineTest { + + describe("scala.scalajs.js.WrappedArray") { + + // Methods we actually implement + + it("should implement apply") { + val array = js.Array(3,4,5,6,3,4) + val seq: Seq[Int] = array + + expect(seq(0)).toEqual(3) + expect(seq(3)).toEqual(6) + + array(0) = 4 + expect(seq(0)).toEqual(4) + } + + it("should implement update") { + val array = js.Array(3,4,5,6,3,4) + val seq: mutable.Seq[Int] = array + + expect(array(1)).toEqual(4) + seq(1) = 5 + expect(array(1)).toEqual(5) + + seq(5) = 10 + expect(array(5)).toEqual(10) + } + + it("should implement length") { + val array = js.Array(3,4,5,6,3,4) + val seq: Seq[Int] = array + + expect(seq.length).toEqual(6) + array.push(1) + expect(seq.length).toEqual(7) + } + + it("should implement +=:") { + val array = js.Array(5, 8, 9) + 3 +=: array + expect(array).toEqual(js.Array(3, 5, 8, 9)) + } + + it("should implement ++=:") { + val array = js.Array(5, 8, 9) + js.Array(2, 0) ++=: array + expect(array).toEqual(js.Array(2, 0, 5, 8, 9)) + Seq(-3, -45, 1) ++=: array + expect(array).toEqual(js.Array(-3, -45, 1, 2, 0, 5, 8, 9)) + } + + it("should implement insertAll") { + val array = js.Array(5, 8, 9) + array.insertAll(2, js.Array(2, 0)) + expect(array).toEqual(js.Array(5, 8, 2, 0, 9)) + array.insertAll(1, Seq(-3, -45, 1)) + expect(array).toEqual(js.Array(5, -3, -45, 1, 8, 2, 0, 9)) + } + + it("should implement remove") { + val array = js.Array(5, 8, 2, 0, 9) + expect(array.remove(1)).toEqual(8) + expect(array).toEqual(js.Array(5, 2, 0, 9)) + + array.remove(0, 3) + expect(array).toEqual(js.Array(9)) + } + + // Some arbitrary methods to test the builders + + it("should implement collect") { + // Ascribe to right type here, so we'll actually produce a WrappedArray + val seq: js.WrappedArray[Int] = js.Array(3,4,5,6,3,4) + val res = seq.collect { + case x if x > 4 => 2*x + } + + expect(res.getClass == classOf[js.WrappedArray[Int]]).toBeTruthy + expect(res.toList == List(10,12)).toBeTruthy + expect(res.array).toEqual(js.Array(10,12)) + } + + it("should implement diff") { + val seq: Seq[Int] = js.Array(1,2,1,3,1,10,9) + val diff = seq.diff(Seq(1,3,9)) + expect(diff.toList == List(2,1,1,10)).toBeTruthy + } + + it("should implement toList") { + val seq: Seq[Int] = js.Array(1,2,1,3,1,10,9) + val list = seq.toList + expect(list == List(1,2,1,3,1,10,9)).toBeTruthy + } + + it("should implement to[T]") { + val seq: Seq[Int] = js.Array(1,2,1,3,1,10,9) + val list = seq.to[List] + expect(list == List(1,2,1,3,1,10,9)).toBeTruthy + } + + } + +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/library/WrappedDictionaryTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/library/WrappedDictionaryTest.scala new file mode 100644 index 0000000..3b95f55 --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/library/WrappedDictionaryTest.scala @@ -0,0 +1,106 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.library + +import scala.scalajs.js +import org.scalajs.jasminetest.JasmineTest + +import scala.collection.mutable + +import scala.reflect.ClassTag + +object WrappedDictionaryTest extends JasmineTest { + + describe("scala.scalajs.js.WrappedDictionary") { + + // Methods we actually implement + + it("should implement get") { + val map: mutable.Map[String, Any] = + js.Dictionary("a" -> "a", "b" -> 6, "e" -> js.undefined) + expect(map.get("a") == Some("a")).toBeTruthy + expect(map.get("b") == Some(6)).toBeTruthy + expect(map.get("e") == Some(())).toBeTruthy + expect(map.get("f") == None).toBeTruthy + } + + it("should implement += and -=") { + val dict = js.Dictionary[String]() + val map: mutable.Map[String, String] = dict + + expect(js.Object.properties(dict)).toEqual(js.Array()) + + map += "hello" -> "world" + expect(dict("hello")).toEqual("world") + map += "foo" -> "bar" + expect(dict("foo")).toEqual("bar") + map -= "hello" + expect(dict.get("hello").isDefined).toBeFalsy + expect(js.Object.properties(dict)).toEqual(js.Array("foo")) + } + + it("should implement iterator") { + val elems = ('a' to 'e').map(_.toString).zip(1 to 5) + val dict = js.Dictionary[Int]() + val map: mutable.Map[String, Int] = dict + + dict ++= elems + + expect(map.iterator.toList.sorted.sameElements(elems)).toBeTruthy + } + + // Some arbitrary methods to test the builders + + it("should implement map") { + def ct[A : ClassTag](x: A) = implicitly[ClassTag[A]] + val dict = js.Dictionary[Int]() + dict ++= Seq("one" -> 1, "two" -> 2, "three" -> 3) + + val mapChr = dict.map { case (k,v) => k(0) -> v * 2 } + val mapStr = dict.map { case (k,v) => k(0).toString -> v * 2 } + + expect(ct(mapChr).runtimeClass == classOf[js.WrappedDictionary[_]]).toBeFalsy + expect(ct(mapStr).runtimeClass == classOf[js.WrappedDictionary[_]]).toBeTruthy + + expect(mapChr.size).toBe(2) + expect(mapStr.size).toBe(2) + } + + it("should implement withFilter") { + val dict = js.Dictionary[Int]() + val flt = dict.withFilter { case (k,v) => v > 5 || k == "a" } + def size = flt.map(x => x).size + + expect(size).toBe(0) + dict += "a" -> 1 + expect(size).toBe(1) + dict += "b" -> 2 + expect(size).toBe(1) + dict += "c" -> 6 + expect(size).toBe(2) + dict += "b" -> 7 + expect(size).toBe(3) + dict -= "a" + expect(size).toBe(2) + } + + it("should implement toList") { + val dict = js.Dictionary("a" -> "a", "b" -> 6, "e" -> js.undefined) + val list = dict.toList + expect(list.size).toBe(3) + } + + it("should implement to[T]") { + val dict = js.Dictionary("a" -> "a", "b" -> 6, "e" -> js.undefined) + val list = dict.to[List] + expect(list.size).toBe(3) + } + + } + +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/niobuffer/BaseBufferTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/niobuffer/BaseBufferTest.scala new file mode 100644 index 0000000..a1f1b71 --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/niobuffer/BaseBufferTest.scala @@ -0,0 +1,178 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.niobuffer + +import org.scalajs.jasminetest.JasmineTest + +import java.nio._ + +abstract class BaseBufferTest extends JasmineTest { + trait BaseFactory { + type BufferType <: Buffer + + val createsReadOnly: Boolean = false + + def allocBuffer(capacity: Int): BufferType + + def allocBuffer(pos: Int, limit: Int, capacity: Int): BufferType = { + val buf = allocBuffer(capacity) + buf.limit(limit).position(pos) + buf + } + } + + type Factory <: BaseFactory + + def commonTests(factory: Factory): Unit = { + import factory._ + + it("allocate") { + val buf = allocBuffer(10) + expect(buf.position).toEqual(0) + expect(buf.limit).toEqual(10) + expect(buf.capacity).toEqual(10) + + expect(allocBuffer(0).capacity).toEqual(0) + + expect(() => allocBuffer(-1)).toThrow + + val buf2 = allocBuffer(1, 5, 9) + expect(buf2.position()).toEqual(1) + expect(buf2.limit()).toEqual(5) + expect(buf2.capacity()).toEqual(9) + } + + it("isReadOnly()") { + val buf = allocBuffer(10) + if (createsReadOnly) + expect(buf.isReadOnly()).toBeTruthy + else + expect(buf.isReadOnly()).toBeFalsy + } + + it("position") { + val buf = allocBuffer(10) + buf.position(3) + expect(buf.position()).toEqual(3) + buf.position(10) + expect(buf.position()).toEqual(10) + buf.position(0) + expect(buf.position()).toEqual(0) + + expect(() => buf.position(-1)).toThrow + expect(() => buf.position(11)).toThrow + expect(buf.position()).toEqual(0) + + val buf2 = allocBuffer(1, 5, 9) + expect(buf2.position()).toEqual(1) + buf2.position(5) + expect(buf2.position()).toEqual(5) + expect(() => buf2.position(6)).toThrow + expect(buf2.position()).toEqual(5) + } + + it("limit") { + val buf = allocBuffer(10) + buf.position(3) + buf.limit(7) + expect(buf.limit()).toEqual(7) + expect(buf.position()).toEqual(3) + expect(() => buf.limit(11)).toThrow + expect(buf.limit()).toEqual(7) + expect(() => buf.limit(-1)).toThrow + expect(buf.limit()).toEqual(7) + expect(buf.position()).toEqual(3) + + buf.position(5) + buf.limit(4) + expect(buf.limit()).toEqual(4) + expect(buf.position()).toEqual(4) + } + + it("mark() and reset()") { + val buf = allocBuffer(10) + + // Initially, the mark should not be set + expect(() => buf.reset()).toThrow + + // Simple test + buf.position(3) + buf.mark() + buf.position(8) + buf.reset() + expect(buf.position()).toEqual(3) + + // reset() should not have cleared the mark + buf.position(5) + buf.reset() + expect(buf.position()).toEqual(3) + + // setting position() below the mark should clear the mark + buf.position(2) + expect(() => buf.reset()).toThrow + } + + it("clear()") { + val buf = allocBuffer(3, 6, 10) + buf.mark() + buf.position(4) + + buf.clear() + expect(buf.position()).toEqual(0) + expect(buf.limit()).toEqual(10) // the capacity + expect(buf.capacity()).toEqual(10) + expect(() => buf.reset()).toThrow + } + + it("flip()") { + val buf = allocBuffer(3, 6, 10) + buf.mark() + buf.position(4) + + buf.flip() + expect(buf.position()).toEqual(0) + expect(buf.limit()).toEqual(4) // old position + expect(buf.capacity()).toEqual(10) + expect(() => buf.reset()).toThrow + } + + it("rewind()") { + val buf = allocBuffer(3, 6, 10) + buf.mark() + buf.position(4) + + buf.rewind() + expect(buf.position()).toEqual(0) + expect(buf.limit()).toEqual(6) // unchanged + expect(buf.capacity()).toEqual(10) + expect(() => buf.reset()).toThrow + } + + it("remaining() and hasRemaining()") { + val buf = allocBuffer(3, 7, 10) + expect(buf.remaining()).toEqual(7-3) + expect(buf.hasRemaining()).toBeTruthy + + buf.position(6) + expect(buf.remaining()).toEqual(7-6) + expect(buf.hasRemaining()).toBeTruthy + + buf.limit(9) + expect(buf.remaining()).toEqual(9-6) + expect(buf.hasRemaining()).toBeTruthy + + buf.limit(2) + expect(buf.remaining()).toEqual(0) + expect(buf.hasRemaining()).toBeFalsy + + buf.position(0) + expect(buf.remaining()).toEqual(2) + expect(buf.hasRemaining()).toBeTruthy + } + } +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/niobuffer/ByteBufferTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/niobuffer/ByteBufferTest.scala new file mode 100644 index 0000000..9ec831f --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/niobuffer/ByteBufferTest.scala @@ -0,0 +1,330 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.niobuffer + +import java.nio._ + +import scala.scalajs.js +import js.JSConverters._ + +object ByteBufferTest extends BaseBufferTest { + trait Factory extends BaseFactory { + type BufferType = ByteBuffer + + def withContent(capacity: Int, content: Byte*): ByteBuffer = + withContent(0, capacity, capacity, content: _*) + + def withContent(pos: Int, limit: Int, capacity: Int, + content: Byte*): ByteBuffer = { + val buf = allocBuffer(pos, limit, capacity) + buf.put(content.toArray) + buf.position(pos) + buf + } + } + + def byteRange(start: Int, end: Int): Array[Byte] = + (start until end).map(_.toByte).toArray + + def defineTests(factory: Factory): Unit = { + import factory._ + + commonTests(factory) + + it("absolute get()") { + val buf = withContent(10, byteRange(0, 10): _*) + expect(buf.get(0)).toEqual(0) + expect(buf.position()).toEqual(0) + expect(buf.get(3)).toEqual(3) + expect(buf.position()).toEqual(0) + + expect(() => buf.get(-1)).toThrow + expect(() => buf.get(15)).toThrow + + buf.limit(4) + expect(() => buf.get(5)).toThrow + } + + + if (!createsReadOnly) { + it("absolute put()") { + val buf = allocBuffer(10) + buf.put(5, 42) + expect(buf.position()).toEqual(0) + buf.put(3, 2) + expect(buf.get(3)).toEqual(2) + expect(buf.get(5)).toEqual(42) + expect(buf.get(7)).toEqual(0) + + expect(() => buf.put(-1, 2)).toThrow + expect(() => buf.put(14, 9)).toThrow + + buf.limit(4) + expect(() => buf.put(4, 1)).toThrow + } + } else { + it("absolute put() - read-only") { + val buf = allocBuffer(10) + expect(() => buf.put(2, 1)).toThrow + expect(buf.get(2)).toEqual(0) + expect(buf.position()).toEqual(0) + + expect(() => buf.put(-2, 1)).toThrow + expect(() => buf.put(12, 1)).toThrow + } + } + + it("relative get()") { + val buf = withContent(10, byteRange(0, 10): _*) + expect(buf.get()).toEqual(0) + expect(buf.position()).toEqual(1) + buf.position(3) + expect(buf.get()).toEqual(3) + expect(buf.position()).toEqual(4) + + buf.limit(4) + expect(() => buf.get()).toThrow + } + + if (!createsReadOnly) { + it("relative put()") { + val buf = allocBuffer(10) + buf.put(5.toByte) + expect(buf.position()).toEqual(1) + expect(buf.get(0)).toEqual(5) + + buf.position(3) + buf.put(36.toByte) + expect(buf.position()).toEqual(4) + expect(buf.get(3)).toEqual(36) + + buf.position(10) + expect(() => buf.put(3.toByte)).toThrow + } + } else { + it("relative put() - read-only") { + val buf = allocBuffer(10) + expect(() => buf.put(5.toByte)).toThrow + expect(buf.position()).toEqual(0) + expect(buf.get(0)).toEqual(0) + + buf.position(10) + expect(() => buf.put(3.toByte)).toThrow + } + } + + it("relative bulk get()") { + val buf = withContent(10, byteRange(0, 10): _*) + val a = new Array[Byte](4) + buf.get(a) + expect(a.toJSArray).toEqual(js.Array(0, 1, 2, 3)) + expect(buf.position()).toEqual(4) + + buf.position(6) + buf.get(a, 1, 2) + expect(a.toJSArray).toEqual(js.Array(0, 6, 7, 3)) + expect(buf.position()).toEqual(8) + + expect(() => buf.get(a)).toThrow + expect(buf.position()).toEqual(8) + expect(a.toJSArray).toEqual(js.Array(0, 6, 7, 3)) + } + + if (!createsReadOnly) { + it("relative bulk put()") { + val buf = allocBuffer(10) + buf.put(Array[Byte](6, 7, 12)) + expect((0 to 3).map(buf.get(_)).toJSArray).toEqual(js.Array(6, 7, 12, 0)) + expect(buf.position()).toEqual(3) + + buf.position(2) + buf.put(Array[Byte](44, 55, 66, 77, 88), 2, 2) + expect((0 to 4).map(buf.get(_)).toJSArray).toEqual(js.Array(6, 7, 66, 77, 0)) + expect(buf.position()).toEqual(4) + + expect(() => buf.put(Array.fill[Byte](10)(0))).toThrow + expect(buf.position()).toEqual(4) + expect((0 to 4).map(buf.get(_)).toJSArray).toEqual(js.Array(6, 7, 66, 77, 0)) + } + } else { + it("relative bulk put() - read-only") { + val buf = allocBuffer(10) + expect(() => buf.put(Array[Byte](6, 7, 12))).toThrow + expect(buf.position()).toEqual(0) + expect(buf.get(0)).toEqual(0) + + buf.position(8) + expect(() => buf.put(Array[Byte](6, 7, 12))).toThrow + expect(buf.position()).toEqual(8) + expect(buf.get(8)).toEqual(0) + } + } + + if (!createsReadOnly) { + it("compact()") { + val buf = withContent(10, byteRange(0, 10): _*) + buf.position(6) + buf.mark() + + buf.compact() + expect(buf.position()).toEqual(4) + expect(buf.limit()).toEqual(10) + expect(() => buf.reset()).toThrow + + for (i <- 0 until 4) + expect(buf.get(i)).toEqual(i + 6) + } + } else { + it("compact() - read-only") { + val buf = allocBuffer(10) + expect(() => buf.compact()).toThrow + } + } + + it("slice()" + (if (createsReadOnly) " - read-only" else "")) { + val buf1 = withContent(10, byteRange(0, 10): _*) + buf1.position(3) + buf1.limit(7) + buf1.mark() + val buf2 = buf1.slice() + expect(buf2.position()).toEqual(0) + expect(buf2.limit()).toEqual(4) + expect(buf2.capacity()).toEqual(4) + expect(() => buf2.reset()).toThrow + expect(buf2.get(1)).toEqual(4) + + buf2.position(2) + expect(buf1.position()).toEqual(3) + + if (!createsReadOnly) { + buf2.put(89.toByte) + expect(buf1.get(5)).toEqual(89) + expect(buf2.position()).toEqual(3) + expect(buf1.position()).toEqual(3) + } + + expect(() => buf2.limit(5)).toThrow + expect(buf2.limit()).toEqual(4) + + buf2.limit(3) + expect(buf1.limit()).toEqual(7) + + if (!createsReadOnly) { + buf1.put(3, 23) + expect(buf2.get(0)).toEqual(23) + } + } + + it("duplicate()" + (if (createsReadOnly) " - read-only" else "")) { + val buf1 = withContent(10, byteRange(0, 10): _*) + buf1.position(3) + buf1.limit(7) + buf1.mark() + val buf2 = buf1.duplicate() + expect(buf2.position()).toEqual(3) + expect(buf2.limit()).toEqual(7) + expect(buf2.capacity()).toEqual(10) + expect(buf2.get(4)).toEqual(4) + + buf2.position(4) + expect(buf1.position()).toEqual(3) + expect(buf2.position()).toEqual(4) + + buf2.reset() + expect(buf2.position()).toEqual(3) + buf2.position(4) + + if (!createsReadOnly) { + buf2.put(89.toByte) + expect(buf1.get(4)).toEqual(89) + expect(buf2.position()).toEqual(5) + expect(buf1.position()).toEqual(3) + } + + buf2.limit(5) + expect(buf1.limit()).toEqual(7) + + if (!createsReadOnly) { + buf1.put(6, 23) + buf2.limit(10) + expect(buf2.get(6)).toEqual(23) + } + } + } + + describe("Allocated ByteBuffer") { + defineTests(new Factory { + def allocBuffer(capacity: Int): ByteBuffer = + ByteBuffer.allocate(capacity) + }) + } + + class WrappedByteBufferFactory extends Factory { + def allocBuffer(capacity: Int): ByteBuffer = + ByteBuffer.wrap(new Array[Byte](capacity)) + + override def allocBuffer(pos: Int, limit: Int, capacity: Int): ByteBuffer = + ByteBuffer.wrap(new Array[Byte](capacity), pos, limit-pos) + + override def withContent(pos: Int, limit: Int, capacity: Int, + content: Byte*): ByteBuffer = { + val after = capacity - (pos + content.size) + ByteBuffer.wrap( + (Seq.fill(pos)(0.toByte) ++ content ++ Seq.fill(after)(0.toByte)).toArray, + pos, limit-pos) + } + } + + describe("Wrapped ByteBuffer") { + defineTests(new WrappedByteBufferFactory) + } + + describe("Read-only wrapped ByteBuffer") { + defineTests(new WrappedByteBufferFactory { + override val createsReadOnly = true + + override def allocBuffer(capacity: Int): ByteBuffer = + super.allocBuffer(capacity).asReadOnlyBuffer() + + override def allocBuffer(pos: Int, limit: Int, capacity: Int): ByteBuffer = + super.allocBuffer(pos, limit, capacity).asReadOnlyBuffer() + + override def withContent(pos: Int, limit: Int, capacity: Int, + content: Byte*): ByteBuffer = + super.withContent(pos, limit, capacity, content: _*).asReadOnlyBuffer() + }) + } + + describe("Sliced ByteBuffer") { + defineTests(new Factory { + def allocBuffer(capacity: Int): ByteBuffer = { + if (capacity < 0) + throw new IllegalArgumentException + val buf = ByteBuffer.allocate(capacity+25) + buf.position(17) + buf.limit(17+capacity) + buf.slice() + } + + override def withContent(pos: Int, limit: Int, capacity: Int, + content: Byte*): ByteBuffer = { + if (!(0 <= pos && pos <= limit && limit <= capacity)) + throw new IllegalArgumentException + val buf = ByteBuffer.allocate(capacity+25) + buf.position(9+pos) + buf.put(content.toArray) + buf.position(9) + buf.limit(9+capacity) + val buf2 = buf.slice() + buf2.position(pos) + buf2.limit(limit) + buf2 + } + }) + } +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/niobuffer/CharBufferTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/niobuffer/CharBufferTest.scala new file mode 100644 index 0000000..b13c478 --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/niobuffer/CharBufferTest.scala @@ -0,0 +1,388 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.niobuffer + +import java.nio._ + +import scala.scalajs.js +import js.JSConverters._ + +object CharBufferTest extends BaseBufferTest { + trait Factory extends BaseFactory { + type BufferType = CharBuffer + + def withContent(capacity: Int, content: Char*): CharBuffer = + withContent(0, capacity, capacity, content: _*) + + def withContent(pos: Int, limit: Int, capacity: Int, + content: Char*): CharBuffer = { + val buf = allocBuffer(pos, limit, capacity) + buf.put(content.toArray) + buf.position(pos) + buf + } + } + + def zeros(n: Int): String = + "\u0000"*n + + def charRange(start: Int, end: Int): Array[Char] = + (start until end).map(_.toChar).toArray + + def defineTests(factory: Factory): Unit = { + import factory._ + + commonTests(factory) + + it("absolute get()") { + val buf = withContent(10, charRange(0, 10): _*) + expect(buf.get(0)).toEqual(0) + expect(buf.position()).toEqual(0) + expect(buf.get(3)).toEqual(3) + expect(buf.position()).toEqual(0) + + expect(() => buf.get(-1)).toThrow + expect(() => buf.get(15)).toThrow + + buf.limit(4) + expect(() => buf.get(5)).toThrow + } + + + if (!createsReadOnly) { + it("absolute put()") { + val buf = allocBuffer(10) + buf.put(5, 42) + expect(buf.position()).toEqual(0) + buf.put(3, 2) + expect(buf.get(3)).toEqual(2) + expect(buf.get(5)).toEqual(42) + expect(buf.get(7)).toEqual(0) + + expect(() => buf.put(-1, 2)).toThrow + expect(() => buf.put(14, 9)).toThrow + + buf.limit(4) + expect(() => buf.put(4, 1)).toThrow + } + } else { + it("absolute put() - read-only") { + val buf = allocBuffer(10) + expect(() => buf.put(2, 1)).toThrow + expect(buf.get(2)).toEqual(0) + expect(buf.position()).toEqual(0) + + expect(() => buf.put(-2, 1)).toThrow + expect(() => buf.put(12, 1)).toThrow + } + } + + it("relative get()") { + val buf = withContent(10, charRange(0, 10): _*) + expect(buf.get()).toEqual(0) + expect(buf.position()).toEqual(1) + buf.position(3) + expect(buf.get()).toEqual(3) + expect(buf.position()).toEqual(4) + + buf.limit(4) + expect(() => buf.get()).toThrow + } + + if (!createsReadOnly) { + it("relative put()") { + val buf = allocBuffer(10) + buf.put(5.toChar) + expect(buf.position()).toEqual(1) + expect(buf.get(0)).toEqual(5) + + buf.position(3) + buf.put(36.toChar) + expect(buf.position()).toEqual(4) + expect(buf.get(3)).toEqual(36) + + buf.position(10) + expect(() => buf.put(3.toChar)).toThrow + } + } else { + it("relative put() - read-only") { + val buf = allocBuffer(10) + expect(() => buf.put(5.toChar)).toThrow + expect(buf.position()).toEqual(0) + expect(buf.get(0)).toEqual(0) + + buf.position(10) + expect(() => buf.put(3.toChar)).toThrow + } + } + + it("relative bulk get()") { + val buf = withContent(10, charRange(0, 10): _*) + val a = new Array[Char](4) + buf.get(a) + expect(a.map(_.toInt).toJSArray).toEqual(js.Array(0, 1, 2, 3)) + expect(buf.position()).toEqual(4) + + buf.position(6) + buf.get(a, 1, 2) + expect(a.map(_.toInt).toJSArray).toEqual(js.Array(0, 6, 7, 3)) + expect(buf.position()).toEqual(8) + + expect(() => buf.get(a)).toThrow + expect(buf.position()).toEqual(8) + expect(a.map(_.toInt).toJSArray).toEqual(js.Array(0, 6, 7, 3)) + } + + if (!createsReadOnly) { + it("relative bulk put()") { + val buf = allocBuffer(10) + buf.put(Array[Char](6, 7, 12)) + expect((0 to 3).map(buf.get(_).toInt).toJSArray).toEqual(js.Array(6, 7, 12, 0)) + expect(buf.position()).toEqual(3) + + buf.position(2) + buf.put(Array[Char](44, 55, 66, 77, 88), 2, 2) + expect((0 to 4).map(buf.get(_).toInt).toJSArray).toEqual(js.Array(6, 7, 66, 77, 0)) + expect(buf.position()).toEqual(4) + + expect(() => buf.put(Array.fill[Char](10)(0))).toThrow + expect(buf.position()).toEqual(4) + expect((0 to 4).map(buf.get(_).toInt).toJSArray).toEqual(js.Array(6, 7, 66, 77, 0)) + } + } else { + it("relative bulk put() - read-only") { + val buf = allocBuffer(10) + expect(() => buf.put(Array[Char](6, 7, 12))).toThrow + expect(buf.position()).toEqual(0) + expect(buf.get(0)).toEqual(0) + + buf.position(8) + expect(() => buf.put(Array[Char](6, 7, 12))).toThrow + expect(buf.position()).toEqual(8) + expect(buf.get(8)).toEqual(0) + } + } + + if (!createsReadOnly) { + it("compact()") { + val buf = withContent(10, charRange(0, 10): _*) + buf.position(6) + buf.mark() + + buf.compact() + expect(buf.position()).toEqual(4) + expect(buf.limit()).toEqual(10) + expect(() => buf.reset()).toThrow + + for (i <- 0 until 4) + expect(buf.get(i).toInt).toEqual(i + 6) + } + } else { + it("compact() - read-only") { + val buf = allocBuffer(10) + expect(() => buf.compact()).toThrow + } + } + + it("slice()" + (if (createsReadOnly) " - read-only" else "")) { + val buf1 = withContent(10, charRange(0, 10): _*) + buf1.position(3) + buf1.limit(7) + buf1.mark() + val buf2 = buf1.slice() + expect(buf2.position()).toEqual(0) + expect(buf2.limit()).toEqual(4) + expect(buf2.capacity()).toEqual(4) + expect(() => buf2.reset()).toThrow + expect(buf2.get(1)).toEqual(4) + + buf2.position(2) + expect(buf1.position()).toEqual(3) + + if (!createsReadOnly) { + buf2.put(89.toChar) + expect(buf1.get(5)).toEqual(89) + expect(buf2.position()).toEqual(3) + expect(buf1.position()).toEqual(3) + } + + expect(() => buf2.limit(5)).toThrow + expect(buf2.limit()).toEqual(4) + + buf2.limit(3) + expect(buf1.limit()).toEqual(7) + + if (!createsReadOnly) { + buf1.put(3, 23) + expect(buf2.get(0)).toEqual(23) + } + } + + it("duplicate()" + (if (createsReadOnly) " - read-only" else "")) { + val buf1 = withContent(10, charRange(0, 10): _*) + buf1.position(3) + buf1.limit(7) + buf1.mark() + val buf2 = buf1.duplicate() + expect(buf2.position()).toEqual(3) + expect(buf2.limit()).toEqual(7) + expect(buf2.capacity()).toEqual(10) + expect(buf2.get(4)).toEqual(4) + + buf2.position(4) + expect(buf1.position()).toEqual(3) + expect(buf2.position()).toEqual(4) + + buf2.reset() + expect(buf2.position()).toEqual(3) + buf2.position(4) + + if (!createsReadOnly) { + buf2.put(89.toChar) + expect(buf1.get(4)).toEqual(89) + expect(buf2.position()).toEqual(5) + expect(buf1.position()).toEqual(3) + } + + buf2.limit(5) + expect(buf1.limit()).toEqual(7) + + if (!createsReadOnly) { + buf1.put(6, 23) + buf2.limit(10) + expect(buf2.get(6)).toEqual(23) + } + } + } + + describe("Allocated CharBuffer") { + defineTests(new Factory { + def allocBuffer(capacity: Int): CharBuffer = + CharBuffer.allocate(capacity) + }) + } + + class WrappedCharBufferFactory extends Factory { + def allocBuffer(capacity: Int): CharBuffer = + CharBuffer.wrap(new Array[Char](capacity)) + + override def allocBuffer(pos: Int, limit: Int, capacity: Int): CharBuffer = + CharBuffer.wrap(new Array[Char](capacity), pos, limit-pos) + + override def withContent(pos: Int, limit: Int, capacity: Int, + content: Char*): CharBuffer = { + val after = capacity - (pos + content.size) + CharBuffer.wrap( + (Seq.fill(pos)(0.toChar) ++ content ++ Seq.fill(after)(0.toChar)).toArray, + pos, limit-pos) + } + } + + describe("Wrapped CharBuffer") { + defineTests(new WrappedCharBufferFactory) + } + + describe("Read-only wrapped CharBuffer") { + defineTests(new WrappedCharBufferFactory { + override val createsReadOnly = true + + override def allocBuffer(capacity: Int): CharBuffer = + super.allocBuffer(capacity).asReadOnlyBuffer() + + override def allocBuffer(pos: Int, limit: Int, capacity: Int): CharBuffer = + super.allocBuffer(pos, limit, capacity).asReadOnlyBuffer() + + override def withContent(pos: Int, limit: Int, capacity: Int, + content: Char*): CharBuffer = + super.withContent(pos, limit, capacity, content: _*).asReadOnlyBuffer() + }) + } + + describe("CharBuffer wrapping a CharSequence") { + defineTests(new Factory { + override val createsReadOnly = true + + def allocBuffer(capacity: Int): CharBuffer = { + if (capacity < 0) + throw new IllegalArgumentException + CharBuffer.wrap(zeros(capacity)) + } + + override def allocBuffer(pos: Int, limit: Int, capacity: Int): CharBuffer = { + if (capacity < 0) + throw new IllegalArgumentException + CharBuffer.wrap(zeros(capacity), pos, limit-pos) + } + + override def withContent(pos: Int, limit: Int, capacity: Int, + content: Char*): CharBuffer = { + val after = capacity - (pos + content.size) + CharBuffer.wrap( + zeros(pos) + content.mkString + zeros(after), + pos, limit-pos) + } + }) + } + + describe("Sliced CharBuffer") { + defineTests(new Factory { + def allocBuffer(capacity: Int): CharBuffer = { + if (capacity < 0) + throw new IllegalArgumentException + val buf = CharBuffer.allocate(capacity+25) + buf.position(17) + buf.limit(17+capacity) + buf.slice() + } + + override def withContent(pos: Int, limit: Int, capacity: Int, + content: Char*): CharBuffer = { + if (!(0 <= pos && pos <= limit && limit <= capacity)) + throw new IllegalArgumentException + val buf = CharBuffer.allocate(capacity+25) + buf.position(9+pos) + buf.put(content.toArray) + buf.position(9) + buf.limit(9+capacity) + val buf2 = buf.slice() + buf2.position(pos) + buf2.limit(limit) + buf2 + } + }) + } + + describe("Sliced CharBuffer wrapping a CharSequence") { + defineTests(new Factory { + override val createsReadOnly = true + + def allocBuffer(capacity: Int): CharBuffer = { + if (capacity < 0) + throw new IllegalArgumentException + val buf = CharBuffer.wrap(zeros(capacity+25)) + buf.position(17) + buf.limit(17+capacity) + buf.slice() + } + + override def withContent(pos: Int, limit: Int, capacity: Int, + content: Char*): CharBuffer = { + if (!(0 <= pos && pos <= limit && limit <= capacity)) + throw new IllegalArgumentException + val after = (25+capacity) - (9+pos+content.size) + val buf = CharBuffer.wrap(zeros(9+pos) + content.mkString + zeros(after)) + buf.position(9) + buf.limit(9+capacity) + val buf2 = buf.slice() + buf2.position(pos) + buf2.limit(limit) + buf2 + } + }) + } +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/niocharset/BaseCharsetTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/niocharset/BaseCharsetTest.scala new file mode 100644 index 0000000..adda838 --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/niocharset/BaseCharsetTest.scala @@ -0,0 +1,234 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.niocharset + +import scala.language.implicitConversions + +import scala.util._ + +import java.nio._ +import java.nio.charset._ + +import scala.scalajs.js +import js.JSConverters._ + +import org.scalajs.jasminetest.JasmineTest + +class BaseCharsetTest(val charset: Charset) extends JasmineTest { + import BaseCharsetTest._ + + protected val AllErrorActions = Seq( + CodingErrorAction.IGNORE, + CodingErrorAction.REPLACE, + CodingErrorAction.REPORT) + + protected val ReportActions = Seq( + CodingErrorAction.REPORT) + + protected def testDecode(in: ByteBuffer)( + outParts: OutPart[CharBuffer]*): Unit = { + + def testOneConfig(malformedAction: CodingErrorAction, + unmappableAction: CodingErrorAction): Unit = { + + val decoder = charset.newDecoder() + decoder.onMalformedInput(malformedAction) + decoder.onUnmappableCharacter(unmappableAction) + + val actualTry = Try { + in.mark() + val buf = + try decoder.decode(in) + finally in.reset() + val actualChars = new Array[Char](buf.remaining()) + buf.get(actualChars) + actualChars + } + + val expectedTry = Try { + val expectedChars = Array.newBuilder[Char] + outParts foreach { + case BufferPart(buf) => + val bufArray = new Array[Char](buf.remaining) + buf.mark() + try buf.get(bufArray) + finally buf.reset() + expectedChars ++= bufArray + case Malformed(len) => + malformedAction match { + case CodingErrorAction.IGNORE => + case CodingErrorAction.REPLACE => + expectedChars ++= decoder.replacement() + case CodingErrorAction.REPORT => + throw new MalformedInputException(len) + } + case Unmappable(len) => + unmappableAction match { + case CodingErrorAction.IGNORE => + case CodingErrorAction.REPLACE => + expectedChars ++= decoder.replacement() + case CodingErrorAction.REPORT => + throw new UnmappableCharacterException(len) + } + } + expectedChars.result() + } + + (actualTry, expectedTry) match { + case (Failure(actualEx: MalformedInputException), + Failure(expectedEx: MalformedInputException)) => + expect(actualEx.getInputLength()).toEqual(expectedEx.getInputLength()) + + case (Failure(actualEx: UnmappableCharacterException), + Failure(expectedEx: UnmappableCharacterException)) => + expect(actualEx.getInputLength()).toEqual(expectedEx.getInputLength()) + + case (Success(actualChars), Success(expectedChars)) => + expect(actualChars.map(_.toInt).toJSArray).toEqual( + expectedChars.map(_.toInt).toJSArray) + + case _ => + // For the error message + expect(actualTry.asInstanceOf[js.Any]).toBe( + expectedTry.asInstanceOf[js.Any]) + } + } + + val hasAnyMalformed = outParts.exists(_.isInstanceOf[Malformed]) + val hasAnyUnmappable = outParts.exists(_.isInstanceOf[Unmappable]) + + for { + malformedAction <- if (hasAnyMalformed) AllErrorActions else ReportActions + unmappableAction <- if (hasAnyUnmappable) AllErrorActions else ReportActions + } { + testOneConfig(malformedAction, unmappableAction) + } + } + + protected def testEncode(in: CharBuffer)( + outParts: OutPart[ByteBuffer]*): Unit = { + + def testOneConfig(malformedAction: CodingErrorAction, + unmappableAction: CodingErrorAction): Unit = { + + val encoder = charset.newEncoder() + encoder.onMalformedInput(malformedAction) + encoder.onUnmappableCharacter(unmappableAction) + + val actualTry = Try { + in.mark() + val buf = + try encoder.encode(in) + finally in.reset() + val actualBytes = new Array[Byte](buf.remaining()) + buf.get(actualBytes) + actualBytes + } + + val expectedTry = Try { + val expectedBytes = Array.newBuilder[Byte] + outParts foreach { + case BufferPart(buf) => + val bufArray = new Array[Byte](buf.remaining) + buf.mark() + try buf.get(bufArray) + finally buf.reset() + expectedBytes ++= bufArray + case Malformed(len) => + malformedAction match { + case CodingErrorAction.IGNORE => + case CodingErrorAction.REPLACE => + expectedBytes ++= encoder.replacement() + case CodingErrorAction.REPORT => + throw new MalformedInputException(len) + } + case Unmappable(len) => + unmappableAction match { + case CodingErrorAction.IGNORE => + case CodingErrorAction.REPLACE => + expectedBytes ++= encoder.replacement() + case CodingErrorAction.REPORT => + throw new UnmappableCharacterException(len) + } + } + expectedBytes.result() + } + + (actualTry, expectedTry) match { + case (Failure(actualEx: MalformedInputException), + Failure(expectedEx: MalformedInputException)) => + expect(actualEx.getInputLength()).toEqual(expectedEx.getInputLength()) + + case (Failure(actualEx: UnmappableCharacterException), + Failure(expectedEx: UnmappableCharacterException)) => + expect(actualEx.getInputLength()).toEqual(expectedEx.getInputLength()) + + case (Success(actualBytes), Success(expectedBytes)) => + expect(actualBytes.toJSArray).toEqual(expectedBytes.toJSArray) + + case _ => + // For the error message + expect(actualTry.asInstanceOf[js.Any]).toBe( + expectedTry.asInstanceOf[js.Any]) + } + } + + val hasAnyMalformed = outParts.exists(_.isInstanceOf[Malformed]) + val hasAnyUnmappable = outParts.exists(_.isInstanceOf[Unmappable]) + + for { + malformedAction <- if (hasAnyMalformed) AllErrorActions else ReportActions + unmappableAction <- if (hasAnyUnmappable) AllErrorActions else ReportActions + } { + testOneConfig(malformedAction, unmappableAction) + } + } +} + +object BaseCharsetTest { + sealed abstract class OutPart[+BufferType <: Buffer] + final case class BufferPart[BufferType <: Buffer](buf: BufferType) extends OutPart[BufferType] + final case class Malformed(length: Int) extends OutPart[Nothing] + final case class Unmappable(length: Int) extends OutPart[Nothing] + + object OutPart { + implicit def fromBuffer[BufferType <: Buffer](buf: BufferType): BufferPart[BufferType] = + BufferPart(buf) + } + + implicit class Interpolators(val sc: StringContext) extends AnyVal { + def bb(args: Any*): ByteBuffer = { + val strings = sc.parts.iterator + val expressions = args.iterator + val buf = Array.newBuilder[Byte] + + def appendStr(s: String): Unit = { + val s1 = s.replace(" ", "") + require(s1.length % 2 == 0) + for (i <- 0 until s1.length by 2) + buf += java.lang.Integer.parseInt(s1.substring(i, i+2), 16).toByte + } + + appendStr(strings.next()) + while (strings.hasNext) { + expressions.next() match { + case b: Byte => buf += b + case bytes: Array[Byte] => buf ++= bytes + case bytes: Seq[_] => + buf ++= bytes.map(_.asInstanceOf[Number].byteValue()) + } + appendStr(strings.next()) + } + + ByteBuffer.wrap(buf.result()) + } + + def cb(args: Any*): CharBuffer = + CharBuffer.wrap(sc.s(args: _*)) + } +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/niocharset/CharsetTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/niocharset/CharsetTest.scala new file mode 100644 index 0000000..15b0150 --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/niocharset/CharsetTest.scala @@ -0,0 +1,74 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.niocharset + +import scala.language.implicitConversions + +import java.nio._ +import java.nio.charset._ + +import scala.scalajs.js +import scala.scalajs.niocharset.StandardCharsets._ + +import org.scalajs.jasminetest.JasmineTest + +object CharsetTest extends JasmineTest { + implicit def charsetAsJSAny(charset: Charset): js.Any = + charset.asInstanceOf[js.Any] + + describe("java.nio.charset.Charset") { + it("defaultCharset") { + expect(Charset.defaultCharset()).toBe(UTF_8) + } + + it("forName") { + expect(Charset.forName("ISO-8859-1")).toBe(ISO_8859_1) + expect(Charset.forName("Iso8859-1")).toBe(ISO_8859_1) + expect(Charset.forName("iso_8859_1")).toBe(ISO_8859_1) + expect(Charset.forName("LaTin1")).toBe(ISO_8859_1) + expect(Charset.forName("l1")).toBe(ISO_8859_1) + + expect(Charset.forName("US-ASCII")).toBe(US_ASCII) + expect(Charset.forName("Default")).toBe(US_ASCII) + + expect(Charset.forName("UTF-8")).toBe(UTF_8) + expect(Charset.forName("utf-8")).toBe(UTF_8) + expect(Charset.forName("UtF8")).toBe(UTF_8) + expect(Charset.forName("UTF_8")).toBe(UTF_8) + expect(Charset.forName("UTF-8")).toBe(UTF_8) + + expect(Charset.forName("UTF-16BE")).toBe(UTF_16BE) + expect(Charset.forName("Utf_16BE")).toBe(UTF_16BE) + expect(Charset.forName("UnicodeBigUnmarked")).toBe(UTF_16BE) + + expect(Charset.forName("UTF-16le")).toBe(UTF_16LE) + expect(Charset.forName("Utf_16le")).toBe(UTF_16LE) + expect(Charset.forName("UnicodeLittleUnmarked")).toBe(UTF_16LE) + + expect(Charset.forName("UTF-16")).toBe(UTF_16) + expect(Charset.forName("Utf_16")).toBe(UTF_16) + expect(Charset.forName("unicode")).toBe(UTF_16) + expect(Charset.forName("UnicodeBig")).toBe(UTF_16) + + expect(() => Charset.forName("this-charset-does-not-exist")).toThrow + } + + it("isSupported") { + expect(Charset.isSupported("ISO-8859-1")).toBeTruthy + expect(Charset.isSupported("US-ASCII")).toBeTruthy + expect(Charset.isSupported("Default")).toBeTruthy + expect(Charset.isSupported("utf-8")).toBeTruthy + expect(Charset.isSupported("UnicodeBigUnmarked")).toBeTruthy + expect(Charset.isSupported("Utf_16le")).toBeTruthy + expect(Charset.isSupported("UTF-16")).toBeTruthy + expect(Charset.isSupported("unicode")).toBeTruthy + + expect(Charset.isSupported("this-charset-does-not-exist")).toBeFalsy + } + } +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/niocharset/Latin1Test.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/niocharset/Latin1Test.scala new file mode 100644 index 0000000..f6ddc36 --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/niocharset/Latin1Test.scala @@ -0,0 +1,91 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.niocharset + +import java.nio._ +import java.nio.charset._ + +import scala.scalajs.niocharset.StandardCharsets + +import BaseCharsetTest._ + +object Latin1Test extends BaseCharsetTest(StandardCharsets.ISO_8859_1) { + describe("ISO-8859-1") { + it("decode") { + // Simple tests + + testDecode(bb"48 65 6c 6c 6f")(cb"Hello") + testDecode(bb"42 6f 6e 6a 6f 75 72")(cb"Bonjour") + + testDecode(bb"00 01 0a 10 20")(cb"\u0000\u0001\u000a\u0010 ") + testDecode(bb"7f 7f")(cb"\u007f\u007f") + + testDecode(bb"c8 e5 ec ec ef")(cb"Èåììï") + testDecode(bb"c2 ef ee ea ef f5 f2")(cb"Âïîêïõò") + + testDecode(bb"80 81 8a 90 a0")(cb"\u0080\u0081\u008a\u0090\u00a0") + testDecode(bb"ff ff")(cb"ÿÿ") + } + + it("encode") { + // Simple tests + + testEncode(cb"Hello")(bb"48 65 6c 6c 6f") + testEncode(cb"Bonjour")(bb"42 6f 6e 6a 6f 75 72") + + testEncode(cb"\u0000\u0001\u000a\u0010 ")(bb"00 01 0a 10 20") + testEncode(cb"\u007f\u007f")(bb"7f 7f") + + testEncode(cb"Èåììï")(bb"c8 e5 ec ec ef") + testEncode(cb"Âïîêïõò")(bb"c2 ef ee ea ef f5 f2") + + testEncode(cb"\u0080\u0081\u008a\u0090\u00a0")(bb"80 81 8a 90 a0") + testEncode(cb"ÿÿ")(bb"ff ff") + + // Unmappable characters + + testEncode(cb"\u0100")(Unmappable(1)) + testEncode(cb"\u07ff")(Unmappable(1)) + testEncode(cb"\ue000")(Unmappable(1)) + testEncode(cb"\uffff")(Unmappable(1)) + testEncode(cb"\ud835\udcd7")(Unmappable(2)) + + testEncode(cb"\u0100A")(Unmappable(1), bb"41") + testEncode(cb"\u07ffA")(Unmappable(1), bb"41") + testEncode(cb"\ue000A")(Unmappable(1), bb"41") + testEncode(cb"\uffffA")(Unmappable(1), bb"41") + testEncode(cb"\ud835\udcd7A")(Unmappable(2), bb"41") + + // Single UTF-16 surrogates + testEncode(cb"\ud800")(Malformed(1)) + testEncode(cb"\udaff")(Malformed(1)) + testEncode(cb"\udb80")(Malformed(1)) + testEncode(cb"\udbff")(Malformed(1)) + testEncode(cb"\udc00")(Malformed(1)) + testEncode(cb"\udf80")(Malformed(1)) + testEncode(cb"\udfff")(Malformed(1)) + + // High UTF-16 surrogates not followed by low surrogates + testEncode(cb"\ud800A")(Malformed(1), bb"41") + testEncode(cb"\ud800\ud800")(Malformed(1), Malformed(1)) + testEncode(cb"\ud800\ud835\udcd7")(Malformed(1), Unmappable(2)) + testEncode(cb"\udbffA")(Malformed(1), bb"41") + testEncode(cb"\udbff\udb8f")(Malformed(1), Malformed(1)) + testEncode(cb"\udbff\ud835\udcd7")(Malformed(1), Unmappable(2)) + } + + it("isLegalReplacement") { + val encoder = charset.newEncoder + expect(encoder.isLegalReplacement(Array(0x00.toByte))).toBeTruthy + expect(encoder.isLegalReplacement(Array(0x41.toByte))).toBeTruthy + expect(encoder.isLegalReplacement(Array('?'.toByte))).toBeTruthy + expect(encoder.isLegalReplacement(Array(0x80.toByte))).toBeTruthy + expect(encoder.isLegalReplacement(Array(0xff.toByte))).toBeTruthy + } + } +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/niocharset/USASCIITest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/niocharset/USASCIITest.scala new file mode 100644 index 0000000..2352d3e --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/niocharset/USASCIITest.scala @@ -0,0 +1,93 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.niocharset + +import java.nio._ +import java.nio.charset._ + +import scala.scalajs.niocharset.StandardCharsets + +import BaseCharsetTest._ + +object USASCIITest extends BaseCharsetTest(StandardCharsets.US_ASCII) { + describe("US-ASCII") { + it("decode") { + // Simple tests + + testDecode(bb"48 65 6c 6c 6f")(cb"Hello") + testDecode(bb"42 6f 6e 6a 6f 75 72")(cb"Bonjour") + + testDecode(bb"00 01 0a 10 20")(cb"\u0000\u0001\u000a\u0010 ") + testDecode(bb"7f 7f")(cb"\u007f\u007f") + + // Bit 7 is ignored, giving the same results as above + + testDecode(bb"c8 e5 ec ec ef")(cb"Hello") + testDecode(bb"c2 ef ee ea ef f5 f2")(cb"Bonjour") + + testDecode(bb"80 81 8a 90 a0")(cb"\u0000\u0001\u000a\u0010 ") + testDecode(bb"ff ff")(cb"\u007f\u007f") + } + + it("encode") { + // Simple tests + + testEncode(cb"Hello")(bb"48 65 6c 6c 6f") + testEncode(cb"Bonjour")(bb"42 6f 6e 6a 6f 75 72") + + testEncode(cb"\u0000\u0001\u000a\u0010 ")(bb"00 01 0a 10 20") + testEncode(cb"\u007f\u007f")(bb"7f 7f") + + // Unmappable characters + + testEncode(cb"é")(Unmappable(1)) + testEncode(cb"\u0080")(Unmappable(1)) + testEncode(cb"\u00ff")(Unmappable(1)) + testEncode(cb"\u0100")(Unmappable(1)) + testEncode(cb"\u07ff")(Unmappable(1)) + testEncode(cb"\ue000")(Unmappable(1)) + testEncode(cb"\uffff")(Unmappable(1)) + testEncode(cb"\ud835\udcd7")(Unmappable(2)) + + testEncode(cb"éA")(Unmappable(1), bb"41") + testEncode(cb"\u0080A")(Unmappable(1), bb"41") + testEncode(cb"\u00ffA")(Unmappable(1), bb"41") + testEncode(cb"\u0100A")(Unmappable(1), bb"41") + testEncode(cb"\u07ffA")(Unmappable(1), bb"41") + testEncode(cb"\ue000A")(Unmappable(1), bb"41") + testEncode(cb"\uffffA")(Unmappable(1), bb"41") + testEncode(cb"\ud835\udcd7A")(Unmappable(2), bb"41") + + // Single UTF-16 surrogates + testEncode(cb"\ud800")(Malformed(1)) + testEncode(cb"\udaff")(Malformed(1)) + testEncode(cb"\udb80")(Malformed(1)) + testEncode(cb"\udbff")(Malformed(1)) + testEncode(cb"\udc00")(Malformed(1)) + testEncode(cb"\udf80")(Malformed(1)) + testEncode(cb"\udfff")(Malformed(1)) + + // High UTF-16 surrogates not followed by low surrogates + testEncode(cb"\ud800A")(Malformed(1), bb"41") + testEncode(cb"\ud800\ud800")(Malformed(1), Malformed(1)) + testEncode(cb"\ud800\ud835\udcd7")(Malformed(1), Unmappable(2)) + testEncode(cb"\udbffA")(Malformed(1), bb"41") + testEncode(cb"\udbff\udb8f")(Malformed(1), Malformed(1)) + testEncode(cb"\udbff\ud835\udcd7")(Malformed(1), Unmappable(2)) + } + + it("isLegalReplacement") { + val encoder = charset.newEncoder + expect(encoder.isLegalReplacement(Array(0x00.toByte))).toBeTruthy + expect(encoder.isLegalReplacement(Array(0x41.toByte))).toBeTruthy + expect(encoder.isLegalReplacement(Array('?'.toByte))).toBeTruthy + expect(encoder.isLegalReplacement(Array(0x80.toByte))).toBeTruthy + expect(encoder.isLegalReplacement(Array(0xff.toByte))).toBeTruthy + } + } +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/niocharset/UTF16Test.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/niocharset/UTF16Test.scala new file mode 100644 index 0000000..85d4eff --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/niocharset/UTF16Test.scala @@ -0,0 +1,155 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.niocharset + +import java.nio._ +import java.nio.charset._ + +import scala.scalajs.niocharset.StandardCharsets + +import BaseCharsetTest._ + +abstract class BaseUTF16Test(charset: Charset) extends BaseCharsetTest(charset) { + describe(charset.name) { + it("decode") { + // ASCII characters + testDecode(bb"0042 006f 006e 006a 006f 0075 0072")(cb"Bonjour") + + // Other characters without surrogate pairs + testDecode(bb"0047 0072 00fc 00df 0020 0047 006f 0074 0074")(cb"Grüß Gott") + testDecode(bb"039a 03b1 03bb 03b7 03bc 03ad 03c1 03b1")(cb"Καλημέρα") + testDecode(bb"0635 0628 0627 062d 0020 0627 0644 062e 064a 0631")(cb"صباح الخير") + testDecode(bb"3053 3093 306b 3061 306f")(cb"こんにちは") + testDecode(bb"0414 043e 0431 0440 044b 0439 0020 0434 0435 043d 044c")(cb"Добрый день") + testDecode(bb"4f60 597d")(cb"你好") + + // 4-byte characters + testDecode(bb"d835 dcd7 d835 dcee d835 dcf5 d835 dcf5 d835 dcf8")( + cb"\ud835\udcd7\ud835\udcee\ud835\udcf5\ud835\udcf5\ud835\udcf8") + + testDecode(bb"")(cb"") + + // Here begin the sequences with at least one error + + // Single UTF-16 surrogates + testDecode(bb"d800")(Malformed(2)) + testDecode(bb"daff")(Malformed(2)) + testDecode(bb"db80")(Malformed(2)) + testDecode(bb"dbff")(Malformed(2)) + testDecode(bb"dc00")(Malformed(2)) + testDecode(bb"df80")(Malformed(2)) + testDecode(bb"dfff")(Malformed(2)) + + // High UTF-16 surrogates not followed by low surrogates + testDecode(bb"d800 0041")(Malformed(2), cb"A") + testDecode(bb"d800 d800")(Malformed(2), Malformed(2)) + testDecode(bb"d800 d835 dcd7")(Malformed(2), cb"\ud835\udcd7") + testDecode(bb"dbff 0041")(Malformed(2), cb"A") + testDecode(bb"dbff db8f")(Malformed(2), Malformed(2)) + testDecode(bb"dbff d835 dcd7")(Malformed(2), cb"\ud835\udcd7") + + // Lonely byte at the end + testDecode(bb"0041 41")(cb"A", Malformed(1)) + } + + it("encode") { + // ASCII characters + testEncode(cb"Bonjour")(bb"0042 006f 006e 006a 006f 0075 0072") + + // Other characters without surrogate pairs + testEncode(cb"Grüß Gott")(bb"0047 0072 00fc 00df 0020 0047 006f 0074 0074") + testEncode(cb"Καλημέρα")(bb"039a 03b1 03bb 03b7 03bc 03ad 03c1 03b1") + testEncode(cb"صباح الخير")(bb"0635 0628 0627 062d 0020 0627 0644 062e 064a 0631") + testEncode(cb"こんにちは")(bb"3053 3093 306b 3061 306f") + testEncode(cb"Добрый день")(bb"0414 043e 0431 0440 044b 0439 0020 0434 0435 043d 044c") + testEncode(cb"你好")(bb"4f60 597d") + + // 4-byte characters + testEncode(cb"\ud835\udcd7\ud835\udcee\ud835\udcf5\ud835\udcf5\ud835\udcf8")( + bb"d835 dcd7 d835 dcee d835 dcf5 d835 dcf5 d835 dcf8") + + testEncode(cb"")(bb"") + + // Here begin the sequences with at least one error + + // Single UTF-16 surrogates + testEncode(cb"\ud800")(Malformed(1)) + testEncode(cb"\udaff")(Malformed(1)) + testEncode(cb"\udb80")(Malformed(1)) + testEncode(cb"\udbff")(Malformed(1)) + testEncode(cb"\udc00")(Malformed(1)) + testEncode(cb"\udf80")(Malformed(1)) + testEncode(cb"\udfff")(Malformed(1)) + + // High UTF-16 surrogates not followed by low surrogates + testEncode(cb"\ud800A")(Malformed(1), bb"0041") + testEncode(cb"\ud800\ud800")(Malformed(1), Malformed(1)) + testEncode(cb"\ud800\ud835\udcd7")(Malformed(1), bb"d835 dcd7") + testEncode(cb"\udbffA")(Malformed(1), bb"0041") + testEncode(cb"\udbff\udb8f")(Malformed(1), Malformed(1)) + testEncode(cb"\udbff\ud835\udcd7")(Malformed(1), bb"d835 dcd7") + } + } +} + +object UTF16BETest extends BaseUTF16Test(StandardCharsets.UTF_16BE) + +object UTF16LETest extends BaseUTF16Test(StandardCharsets.UTF_16LE) { + override protected def testDecode(in: ByteBuffer)( + outParts: OutPart[CharBuffer]*): Unit = { + flipByteBuffer(in) + super.testDecode(in)(outParts: _*) + } + + override protected def testEncode(in: CharBuffer)( + outParts: OutPart[ByteBuffer]*): Unit = { + for (BufferPart(buf) <- outParts) + flipByteBuffer(buf) + super.testEncode(in)(outParts: _*) + } + + /** Flips all pairs of bytes in a byte buffer, except a potential lonely + * last byte. + */ + def flipByteBuffer(buf: ByteBuffer): Unit = { + buf.mark() + while (buf.remaining() >= 2) { + val high = buf.get() + val low = buf.get() + buf.position(buf.position - 2) + buf.put(low) + buf.put(high) + } + buf.reset() + } +} + +object UTF16Test extends BaseUTF16Test(StandardCharsets.UTF_16) { + def BigEndianBOM = ByteBuffer.wrap(Array(0xfe.toByte, 0xff.toByte)) + + override protected def testDecode(in: ByteBuffer)( + outParts: OutPart[CharBuffer]*): Unit = { + // Without BOM, big endian is assumed + super.testDecode(in)(outParts: _*) + + // With BOM, big endian + val inWithBOM = ByteBuffer.allocate(2+in.remaining) + inWithBOM.put(BigEndianBOM).put(in).flip() + super.testDecode(inWithBOM)(outParts: _*) + + // With BOM, little endian + UTF16LETest.flipByteBuffer(inWithBOM) + super.testDecode(inWithBOM)(outParts: _*) + } + + override protected def testEncode(in: CharBuffer)( + outParts: OutPart[ByteBuffer]*): Unit = { + if (in.remaining == 0) super.testEncode(in)(outParts: _*) + else super.testEncode(in)(BufferPart(BigEndianBOM) +: outParts: _*) + } +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/niocharset/UTF8Test.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/niocharset/UTF8Test.scala new file mode 100644 index 0000000..fbb6a9c --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/niocharset/UTF8Test.scala @@ -0,0 +1,240 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.niocharset + +import java.nio._ +import java.nio.charset._ + +import scala.scalajs.niocharset.StandardCharsets + +import BaseCharsetTest._ + +object UTF8Test extends BaseCharsetTest(StandardCharsets.UTF_8) { + describe("UTF-8") { + it("decode") { + def OutSeq(elems: OutPart[CharBuffer]*): Seq[OutPart[CharBuffer]] = + Seq[OutPart[CharBuffer]](elems: _*) + + // 1-byte characters + testDecode(bb"42 6f 6e 6a 6f 75 72")(cb"Bonjour") + + // 2-byte characters + testDecode(bb"47 72 c3 bc c3 9f 20 47 6f 74 74")(cb"Grüß Gott") + testDecode(bb"ce 9a ce b1 ce bb ce b7 ce bc ce ad cf 81 ce b1")(cb"Καλημέρα") + testDecode(bb"d8 b5 d8 a8 d8 a7 d8 ad 20 d8 a7 d9 84 d8 ae d9 8a d8 b1")(cb"صباح الخير") + + // 3-byte characters + testDecode(bb"e3 81 93 e3 82 93 e3 81 ab e3 81 a1 e3 81 af")(cb"こんにちは") + testDecode(bb"d0 94 d0 be d0 b1 d1 80 d1 8b d0 b9 20 d0 b4 d0 b5 d0 bd d1 8c")(cb"Добрый день") + testDecode(bb"e4 bd a0 e5 a5 bd")(cb"你好") + + // 4-byte characters + testDecode(bb"f0 9d 93 97 f0 9d 93 ae f0 9d 93 b5 f0 9d 93 b5 f0 9d 93 b8")( + cb"\ud835\udcd7\ud835\udcee\ud835\udcf5\ud835\udcf5\ud835\udcf8") + + testDecode(bb"")(cb"") + + // First sequence of a certain length + testDecode(bb"00")(cb"\u0000") + testDecode(bb"c2 80")(cb"\u0080") + testDecode(bb"e0 a0 80")(cb"\u0800") + testDecode(bb"f0 90 80 80")(cb"\ud800\udc00") + + // Last sequence of a certain length + testDecode(bb"7f")(cb"\u007f") + testDecode(bb"df bf")(cb"\u07ff") + testDecode(bb"ef bf bf")(cb"\uffff") + testDecode(bb"f4 8f bf bf")(cb"\udbff\udfff") + + // Other boundary conditions + testDecode(bb"ed 9f bf")(cb"\ud7ff") + testDecode(bb"ee 80 80")(cb"\ue000") + testDecode(bb"ef bf bd")(cb"\ufffd") + + // Here begin the sequences with at least one error + + // Code point too big + testDecode(bb"f4 90 80 80")(Malformed(4)) + testDecode(bb"41 f4 90 80 80 42")(cb"A", Malformed(4), cb"B") + + // Unexpected continuation bytes (each is reported separately) + testDecode(bb"80")(Malformed(1)) + testDecode(bb"bf")(Malformed(1)) + testDecode(bb"80 80")(Malformed(1), Malformed(1)) + testDecode(bb"80 80 80")(Malformed(1), Malformed(1), Malformed(1)) + testDecode(bb"80 80 80 80")(Malformed(1), Malformed(1), Malformed(1), Malformed(1)) + testDecode(bb"80 80 80 80 80")(Malformed(1), Malformed(1), Malformed(1), Malformed(1), Malformed(1)) + testDecode(bb"41 80 80 42 80 43")(cb"A", Malformed(1), Malformed(1), cb"B", Malformed(1), cb"C") + + // Lonely start characters, separated by spaces + /* We add 2 spaces at the end so that the last 4-byte start does not + * swallow the last space as an underflow. */ + testDecode(bb"${(0xc0 to 0xf4).flatMap(c => Seq(c, 32)) ++ Seq[Byte](32, 32)}")( + (0xc0 to 0xf4).flatMap(i => OutSeq(Malformed(1), cb" ")) ++ OutSeq(cb" "): _*) + // Now we let it swallow the last space + testDecode(bb"${Seq[Byte](32) ++ (0xc0 to 0xf4).flatMap(c => Seq(c, 32))}")( + (0xc0 to 0xf4).flatMap(i => OutSeq(cb" ", Malformed(1))): _*) + + // Sequences with some continuation bytes missing + testDecode(bb"c2")(Malformed(1)) + testDecode(bb"e0")(Malformed(1)) + testDecode(bb"e0 a0")(Malformed(2)) + testDecode(bb"f0")(Malformed(1)) + testDecode(bb"f0 90")(Malformed(2)) + testDecode(bb"f0 90 80")(Malformed(3)) + // and all of them concatenated + testDecode(bb"c2 e0 e0 a0 f0 f0 90 f0 90 80")( + Seq(1, 1, 2, 1, 2, 3).map(Malformed(_)): _*) + // and with normal sequences interspersed + testDecode(bb"c2 41 e0 41 e0 a0 41 f0 41 f0 90 41 f0 90 80 41")( + Seq(1, 1, 2, 1, 2, 3).flatMap(l => Seq[OutPart[CharBuffer]](Malformed(l), cb"A")): _*) + + // Impossible bytes + testDecode(bb"fe")(Malformed(1)) + testDecode(bb"ff")(Malformed(1)) + testDecode(bb"fe fe ff ff")(Malformed(1), Malformed(1), Malformed(1), Malformed(1)) + // Old 5-byte and 6-byte starts + testDecode(bb"f8 80 80 80 af")( + Malformed(1), Malformed(1), Malformed(1), Malformed(1), Malformed(1)) + testDecode(bb"fc 80 80 80 80 af")( + Malformed(1), Malformed(1), Malformed(1), Malformed(1), Malformed(1), Malformed(1)) + + // Overlong sequences (encoded with more bytes than necessary) + // Overlong '/' + testDecode(bb"c0 af")(Malformed(2)) + testDecode(bb"e0 80 af")(Malformed(3)) + testDecode(bb"f0 80 80 af")(Malformed(4)) + // Maximum overlong sequences + testDecode(bb"c1 bf")(Malformed(2)) + testDecode(bb"e0 9f bf")(Malformed(3)) + testDecode(bb"f0 8f bf bf")(Malformed(4)) + // Overlong NUL + testDecode(bb"c0 80")(Malformed(2)) + testDecode(bb"e0 80 80")(Malformed(3)) + testDecode(bb"f0 80 80 80")(Malformed(4)) + + // Single UTF-16 surrogates + testDecode(bb"ed a0 80")(Malformed(3)) + testDecode(bb"ed ad bf")(Malformed(3)) + testDecode(bb"ed ae 80")(Malformed(3)) + testDecode(bb"ed af bf")(Malformed(3)) + testDecode(bb"ed b0 80")(Malformed(3)) + testDecode(bb"ed be 80")(Malformed(3)) + testDecode(bb"ed bf bf")(Malformed(3)) + + // Paired UTF-16 surrogates + testDecode(bb"ed a0 80 ed b0 80")(Malformed(3), Malformed(3)) + testDecode(bb"ed a0 80 ed bf bf")(Malformed(3), Malformed(3)) + testDecode(bb"ed ad bf ed b0 80")(Malformed(3), Malformed(3)) + testDecode(bb"ed ad bf ed bf bf")(Malformed(3), Malformed(3)) + testDecode(bb"ed ae 80 ed b0 80")(Malformed(3), Malformed(3)) + testDecode(bb"ed ae 80 ed bf bf")(Malformed(3), Malformed(3)) + testDecode(bb"ed af bf ed b0 80")(Malformed(3), Malformed(3)) + testDecode(bb"ed af bf ed bf bf")(Malformed(3), Malformed(3)) + } + + it("encode") { + def OutSeq(elems: OutPart[ByteBuffer]*): Seq[OutPart[ByteBuffer]] = + Seq[OutPart[ByteBuffer]](elems: _*) + + // 1-byte characters + testEncode(cb"Bonjour")(bb"42 6f 6e 6a 6f 75 72") + + // 2-byte characters + testEncode(cb"Grüß Gott")(bb"47 72 c3 bc c3 9f 20 47 6f 74 74") + testEncode(cb"Καλημέρα")(bb"ce 9a ce b1 ce bb ce b7 ce bc ce ad cf 81 ce b1") + testEncode(cb"صباح الخير")(bb"d8 b5 d8 a8 d8 a7 d8 ad 20 d8 a7 d9 84 d8 ae d9 8a d8 b1") + + // 3-byte characters + testEncode(cb"こんにちは")(bb"e3 81 93 e3 82 93 e3 81 ab e3 81 a1 e3 81 af") + testEncode(cb"Добрый день")(bb"d0 94 d0 be d0 b1 d1 80 d1 8b d0 b9 20 d0 b4 d0 b5 d0 bd d1 8c") + testEncode(cb"你好")(bb"e4 bd a0 e5 a5 bd") + + // 4-byte characters + testEncode(cb"\ud835\udcd7\ud835\udcee\ud835\udcf5\ud835\udcf5\ud835\udcf8")( + bb"f0 9d 93 97 f0 9d 93 ae f0 9d 93 b5 f0 9d 93 b5 f0 9d 93 b8") + + testEncode(cb"")(bb"") + + // First sequence of a certain length + testEncode(cb"\u0000")(bb"00") + testEncode(cb"\u0080")(bb"c2 80") + testEncode(cb"\u0800")(bb"e0 a0 80") + testEncode(cb"\ud800\udc00")(bb"f0 90 80 80") + + // Last sequence of a certain length + testEncode(cb"\u007f")(bb"7f") + testEncode(cb"\u07ff")(bb"df bf") + testEncode(cb"\uffff")(bb"ef bf bf") + testEncode(cb"\udbff\udfff")(bb"f4 8f bf bf") + + // Other boundary conditions + testEncode(cb"\ud7ff")(bb"ed 9f bf") + testEncode(cb"\ue000")(bb"ee 80 80") + testEncode(cb"\ufffd")(bb"ef bf bd") + + // Here begin the sequences with at least one error + + // Single UTF-16 surrogates + testEncode(cb"\ud800")(Malformed(1)) + testEncode(cb"\udaff")(Malformed(1)) + testEncode(cb"\udb80")(Malformed(1)) + testEncode(cb"\udbff")(Malformed(1)) + testEncode(cb"\udc00")(Malformed(1)) + testEncode(cb"\udf80")(Malformed(1)) + testEncode(cb"\udfff")(Malformed(1)) + + // High UTF-16 surrogates not followed by low surrogates + testEncode(cb"\ud800A")(Malformed(1), bb"41") + testEncode(cb"\ud800\ud800")(Malformed(1), Malformed(1)) + testEncode(cb"\ud800\ud835\udcd7")(Malformed(1), bb"f0 9d 93 97") + testEncode(cb"\udbffA")(Malformed(1), bb"41") + testEncode(cb"\udbff\udb8f")(Malformed(1), Malformed(1)) + testEncode(cb"\udbff\ud835\udcd7")(Malformed(1), bb"f0 9d 93 97") + } + + it("isLegalReplacement") { + val encoder = charset.newEncoder + + // The good ones + + expect(encoder.isLegalReplacement(Array(0x00.toByte))).toBeTruthy + expect(encoder.isLegalReplacement(Array(0x41.toByte))).toBeTruthy + expect(encoder.isLegalReplacement(Array('?'.toByte))).toBeTruthy + expect(encoder.isLegalReplacement(Array(0x7f.toByte))).toBeTruthy + + expect(encoder.isLegalReplacement( + Array(0xc2.toByte, 0x80.toByte))).toBeTruthy + expect(encoder.isLegalReplacement( + Array(0xdf.toByte, 0xbf.toByte))).toBeTruthy + + expect(encoder.isLegalReplacement( + Array(0xe0.toByte, 0xa0.toByte, 0x80.toByte))).toBeTruthy + expect(encoder.isLegalReplacement( + Array(0xef.toByte, 0xbf.toByte, 0xbf.toByte))).toBeTruthy + + expect(encoder.isLegalReplacement( + Array(0xf0.toByte, 0x90.toByte, 0x80.toByte, 0x80.toByte))).toBeTruthy + expect(encoder.isLegalReplacement( + Array(0xf4.toByte, 0x8f.toByte, 0xbf.toByte, 0xbf.toByte))).toBeTruthy + + // The bad ones + + expect(encoder.isLegalReplacement(Array(0x80.toByte))).toBeFalsy + expect(encoder.isLegalReplacement(Array(0xbf.toByte))).toBeFalsy + expect(encoder.isLegalReplacement(Array(0xc2.toByte))).toBeFalsy + expect(encoder.isLegalReplacement(Array(0xdf.toByte))).toBeFalsy + expect(encoder.isLegalReplacement(Array(0xe0.toByte))).toBeFalsy + expect(encoder.isLegalReplacement(Array(0xef.toByte))).toBeFalsy + expect(encoder.isLegalReplacement(Array(0xf4.toByte))).toBeFalsy + + expect(encoder.isLegalReplacement( + Array(0xe0.toByte, 0x80.toByte))).toBeFalsy + } + } +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/scalalib/EnumerationTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/scalalib/EnumerationTest.scala new file mode 100644 index 0000000..f5b17d9 --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/scalalib/EnumerationTest.scala @@ -0,0 +1,95 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.scalalib + +import org.scalajs.jasminetest.JasmineTest + +object EnumerationTest extends JasmineTest { + + describe("scala.Enumeration") { + + it("should use explicit naming for enumerated values - #38") { + object HelpLevel extends Enumeration { + type HelpLevel = Value + val None = Value("None") + val Basic = Value("Basic") + val Medium = Value("Medium") + val Full = Value("Full") + } + + val h = HelpLevel.None + + expect(h.toString).toEqual("None") + } + + it("should allow implicit naming for values") { + object HelpLevel extends Enumeration { + type HelpLevel = Value + val None, Basic, Medium, Full = Value + val Special = Value(100) + val / = Value + } + + val h = HelpLevel.Medium + expect(h.toString).toEqual("Medium") + expect(HelpLevel.Special.toString).toEqual("Special") + expect(HelpLevel./.toString).toEqual("$div") + } + + it("should give a pseudo toString to unnamed values") { + object Test extends Enumeration { + private val nullStr: String = null + val A = Value(nullStr) // Circumvent compiler replacement and warning + } + + expect(Test.A.toString.startsWith( + "<Unknown name for enum field #0 of class ")).toBeTruthy + } + + it("should give a graceful error message upon name based query when unnamed fields are present") { + object Test extends Enumeration { + private val nullStr: String = null + val A = Value(nullStr) // Circumvent compiler replacement and warning + } + + expect(() => Test.withName("A")).toThrow + expect { + try { Test.withName("A"); ??? } + catch { case e: NoSuchElementException => e.getMessage } + } toContain { + """Couldn't find enum field with name A. + |However, there were the following unnamed fields:""".stripMargin + } + } + + it("should respond to `toString`") { + expect(FooBarEnum.toString).toEqual("FooBarEnum") + } + + it("should respond to `values`") { + expect(FooBarEnum.values.toString).toEqual( + "FooBarEnum.ValueSet(A, B, C, D, E, F)") + } + + it("should allow setting nextName") { + object Test extends Enumeration { + nextName = Iterator("x","y","z") + val a,b,c = Value + } + + expect(Test.values.mkString("|")).toEqual("x|y|z") + } + + } + + /** Object is here due to issues with Enumeration.toString inside closures */ + object FooBarEnum extends Enumeration { + val A,B,C,D,E,F = Value + } + +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/scalalib/RangesTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/scalalib/RangesTest.scala new file mode 100644 index 0000000..511e183 --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/scalalib/RangesTest.scala @@ -0,0 +1,27 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.scalalib + +import org.scalajs.jasminetest.JasmineTest + +object RangesTest extends JasmineTest { + + describe("Collection ranges") { + + it("Iterable.range should not emit dce warnings - #650") { + Iterable.range(1, 10) + } + + it("Iterable.range and simple range should be equal") { + // Mostly to exercise more methods of ranges for dce warnings + expect(Iterable.range(0, 10).toList == (0 until 10).toList).toBeTruthy + } + + } + +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/scalalib/SymbolTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/scalalib/SymbolTest.scala new file mode 100644 index 0000000..3612629 --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/scalalib/SymbolTest.scala @@ -0,0 +1,63 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.scalalib + +import scala.scalajs.js +import org.scalajs.jasminetest.JasmineTest + +object SymbolTest extends JasmineTest { + + describe("scala.Symbol") { + + it("should ensure unique identity") { + def expectEqual(sym1: Symbol, sym2: Symbol): Unit = { + expect(sym1 eq sym2).toBeTruthy + expect(sym1 == sym2).toBeTruthy + expect(sym1.equals(sym2)).toBeTruthy + expect(sym1.## == sym2.##).toBeTruthy + } + + expectEqual('ScalaJS, Symbol("ScalaJS")) + expectEqual('$, Symbol("$")) + expectEqual('-, Symbol("-")) + + val `42` = Symbol("42") + val map = Map[Symbol, js.Any](Symbol("ScalaJS") -> "Scala.js", '$ -> 1.2, `42` -> 42) + expect(map('ScalaJS)).toEqual("Scala.js") + expect(map(Symbol("$"))).toEqual(1.2) + expect(map(Symbol("42"))).toEqual(42) + expect(map(`42`)).toEqual(42) + } + + it("should support `name`") { + val scalajs = 'ScalaJS + + expect(scalajs.name).toEqual("ScalaJS") + expect(Symbol("$").name).toEqual("$") + expect('$$.name).toEqual("$$") + expect('-.name).toEqual("-") + expect('*.name).toEqual("*") + expect(Symbol("'").name).toEqual("'") + expect(Symbol("\"").name).toEqual("\"") + } + + it("should support `toString`") { + val scalajs = 'ScalaJS + + expect(scalajs.toString).toEqual("'ScalaJS") + expect(Symbol("$").toString).toEqual("'$") + expect('$$.toString).toEqual("'$$") + expect('-.toString).toEqual("'-") + expect('*.toString).toEqual("'*") + expect(Symbol("'").toString).toEqual("''") + expect(Symbol("\"").toString).toEqual("'\"") + } + + } + +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/typedarray/ArrayBufferInputStreamTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/typedarray/ArrayBufferInputStreamTest.scala new file mode 100644 index 0000000..7c49d0c --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/typedarray/ArrayBufferInputStreamTest.scala @@ -0,0 +1,36 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.typedarray + +import scala.scalajs.testsuite.javalib.CommonStreamsTests + +import scala.scalajs.js.typedarray._ + +import org.scalajs.jasminetest.JasmineTest + +/** Tests for our implementation of java.io._ stream classes */ +object ArrayBufferInputStreamTest extends JasmineTest with CommonStreamsTests { + + when("typedarray"). + describe("scala.scalajs.js.typedarray.ArrayBufferInputStream") { + byteArrayInputStreamLikeTests(seq => new ArrayBufferInputStream( + new Int8Array(seq).buffer)) + } + + when("typedarray"). + describe("scala.scalajs.js.typedarray.ArrayBufferInputStream - with offset") { + byteArrayInputStreamLikeTests { seq => + val off = 100 + val data = new Int8Array(seq.size + off) + data.set(seq, off) + + new ArrayBufferInputStream(data.buffer, off, seq.size) + } + } + +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/typedarray/ArrayBufferTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/typedarray/ArrayBufferTest.scala new file mode 100644 index 0000000..9e64c7f --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/typedarray/ArrayBufferTest.scala @@ -0,0 +1,39 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.typedarray + +import org.scalajs.jasminetest.JasmineTest + +import scala.scalajs.js.typedarray._ + +object ArrayBufferTest extends JasmineTest { + + when("typedarry"). + describe("ArrayBuffer") { + + it("should provide a length constructor") { + val x = new ArrayBuffer(100) + expect(x.isInstanceOf[ArrayBuffer]).toBeTruthy + expect(x.byteLength).toBe(100) + } + + it("should provide `slice` with one argument") { + val x = new ArrayBuffer(100) + val y = x.slice(10) + expect(y.byteLength).toBe(90) + } + + it("should provide `slice` with two arguments") { + val x = new ArrayBuffer(100) + val y = x.slice(10, 20) + expect(y.byteLength).toBe(10) + } + + } + +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/typedarray/ArraysTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/typedarray/ArraysTest.scala new file mode 100644 index 0000000..1976f3f --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/typedarray/ArraysTest.scala @@ -0,0 +1,45 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.typedarray + +import scala.scalajs.js.typedarray._ +import scala.scalajs.js.JSConverters._ + +import scala.reflect._ + +import org.scalajs.jasminetest.JasmineTest + +import scala.scalajs.testsuite.javalib + +object ArraysTest extends javalib.ArraysTest { + + override def Array[T : ClassTag](v: T*): scala.Array[T] = classTag[T] match { + case ClassTag.Byte => + new Int8Array(v.asInstanceOf[Seq[Byte]].toJSArray) + .toArray.asInstanceOf[scala.Array[T]] + case ClassTag.Short => + new Int16Array(v.asInstanceOf[Seq[Short]].toJSArray) + .toArray.asInstanceOf[scala.Array[T]] + case ClassTag.Int => + new Int32Array(v.asInstanceOf[Seq[Int]].toJSArray) + .toArray.asInstanceOf[scala.Array[T]] + case ClassTag.Float => + new Float32Array(v.asInstanceOf[Seq[Float]].toJSArray) + .toArray.asInstanceOf[scala.Array[T]] + case ClassTag.Double => + new Float64Array(v.asInstanceOf[Seq[Double]].toJSArray) + .toArray.asInstanceOf[scala.Array[T]] + case _ => scala.Array(v: _*) + } + + override def testBody(suite: => Unit) = { + when("typedarray"). + describe("java.util.Arrays backed with TypedArrays")(suite) + } + +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/typedarray/DataViewTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/typedarray/DataViewTest.scala new file mode 100644 index 0000000..76c5639 --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/typedarray/DataViewTest.scala @@ -0,0 +1,275 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.typedarray + +import org.scalajs.jasminetest.JasmineTest + +import scala.scalajs.js.typedarray._ + +object DataViewTest extends JasmineTest { + + when("typedarray"). + describe("DataView") { + + it("should provide an ArrayBuffer only constructor") { + val buf = new ArrayBuffer(10) + val view = new DataView(buf) + expect(view.isInstanceOf[DataView]).toBeTruthy + expect(view.byteLength).toBe(10) + expect(view.byteOffset).toBe(0) + expect(view.getInt8(0)).toBe(0) + } + + it("should provide an ArrayBuffer and offset constructor") { + val buf = new ArrayBuffer(10) + val view = new DataView(buf, 2) + expect(view.isInstanceOf[DataView]).toBeTruthy + expect(view.byteLength).toBe(8) + expect(view.byteOffset).toBe(2) + expect(view.getInt8(0)).toBe(0) + } + + it("should provide an ArrayBuffer, offset and length constructor") { + val buf = new ArrayBuffer(10) + val view = new DataView(buf, 3, 2) + expect(view.isInstanceOf[DataView]).toBeTruthy + expect(view.byteLength).toBe(2) + expect(view.byteOffset).toBe(3) + expect(view.getInt8(0)).toBe(0) + } + + it("should provide `getInt8`") { + val view = new DataView(new ArrayBuffer(4)) + + view.setInt8(0, 1) + view.setInt8(1, 127) + view.setInt8(3, -50) + + expect(view.getInt8(0)).toBe(1) + expect(view.getInt8(1)).toBe(127) + expect(view.getInt8(2)).toBe(0) + expect(view.getInt8(3)).toBe(-50) + } + + it("should provide `getUint8`") { + val view = new DataView(new ArrayBuffer(4)) + + view.setInt8(0, 1) + view.setInt8(1, -127) + + expect(view.getUint8(0)).toBe(1) + expect(view.getUint8(1)).toBe(129) + expect(view.getUint8(2)).toBe(0) + } + + it("should provide `getInt16`") { + val view = new DataView(new ArrayBuffer(4)) + + view.setUint8(0, 0x01) + view.setUint8(1, 0xFF) + view.setUint8(2, 0xB3) + view.setUint8(3, 0x30) + + expect(view.getInt16(0)).toBe(0x01FF) + expect(view.getInt16(0, true)).toBe(-255) + expect(view.getInt16(1)).toBe(-77) + expect(view.getInt16(2, true)).toBe(0x30B3) + } + + it("should provide `getUint16`") { + val view = new DataView(new ArrayBuffer(4)) + + view.setUint8(0, 0x01) + view.setUint8(1, 0xFF) + view.setUint8(2, 0xB3) + view.setUint8(3, 0x30) + + expect(view.getUint16(0)).toBe(0x01FF) + expect(view.getUint16(0, true)).toBe(0xFF01) + expect(view.getUint16(1)).toBe(0xFFB3) + expect(view.getUint16(2, true)).toBe(0x30B3) + } + + it("should provide `getInt32`") { + val view = new DataView(new ArrayBuffer(5)) + + view.setUint8(0, 0x01) + view.setUint8(1, 0xFF) + view.setUint8(2, 0xB3) + view.setUint8(3, 0x30) + view.setUint8(4, 0x1A) + + expect(view.getInt32(0)).toBe(0x01FFB330) + expect(view.getInt32(0, true)).toBe(0x30B3FF01) + expect(view.getInt32(1)).toBe(0xFFB3301A) // is negative since Int + expect(view.getInt32(1, true)).toBe(0x1A30B3FF) + } + + it("should provide `getUint32`") { + val view = new DataView(new ArrayBuffer(5)) + + view.setUint8(0, 0x01) + view.setUint8(1, 0xFF) + view.setUint8(2, 0xB3) + view.setUint8(3, 0x30) + view.setUint8(4, 0x1A) + + expect(view.getUint32(0)).toBe(0x01FFB330) + expect(view.getUint32(0, true)).toBe(0x30B3FF01) + expect(view.getUint32(1)).toBe(0xFFB3301AL.toDouble) + expect(view.getUint32(1, true)).toBe(0x1A30B3FF) + } + + it("should provide `getFloat32`") { + val view = new DataView(new ArrayBuffer(5)) + + view.setFloat32(0, 0.1f) + + expect(view.getFloat32(0)).toBeCloseTo(0.1, 7) + expect(view.getFloat32(1)).not.toBeCloseTo(0.1, 7) + expect(view.getFloat32(0, true)).not.toBeCloseTo(0.1, 7) + expect(view.getFloat32(1, true)).not.toBeCloseTo(0.1, 7) + } + + it("should provide `getFloat64`") { + val view = new DataView(new ArrayBuffer(9)) + + view.setFloat64(0, 0.5) + + expect(view.getFloat64(0)).toBe(0.5) + expect(view.getFloat64(1)).not.toBe(0.5) + expect(view.getFloat64(0, true)).not.toBe(0.5) + expect(view.getFloat64(1, true)).not.toBe(0.5) + } + + it("should provide `setInt8`") { + val view = new DataView(new ArrayBuffer(4)) + + view.setInt8(0, 1) + view.setInt8(1, 127) + view.setInt8(2, -20) + view.setInt8(3, -50) + + expect(view.getUint8(0)).toBe(0x01) + expect(view.getUint8(1)).toBe(0x7F) + expect(view.getUint8(2)).toBe(0xEC) + expect(view.getUint8(3)).toBe(0xCE) + } + + it("should provide `setUint8`") { + val view = new DataView(new ArrayBuffer(4)) + + view.setUint8(0, 0x01) + view.setUint8(1, 0xFC) + view.setUint8(2, 0x4D) + view.setUint8(3, 0x8C) + + expect(view.getInt8(0)).toBe(1) + expect(view.getInt8(1)).toBe(-4) + expect(view.getInt8(2)).toBe(0x4D) + expect(view.getInt8(3)).toBe(-116) + } + + it("should provide `setInt16`") { + val view = new DataView(new ArrayBuffer(4)) + + view.setInt16(0, 0x7F3B, true) + view.setInt16(2, -1) + + expect(view.getUint8(0)).toBe(0x3B) + expect(view.getUint8(1)).toBe(0x7F) + expect(view.getUint8(2)).toBe(0xFF) + expect(view.getUint8(3)).toBe(0xFF) + } + + it("should provide `setUint16`") { + val view = new DataView(new ArrayBuffer(4)) + + view.setUint16(0, 0x7F3B, true) + view.setUint16(2, 0xFCBA) + + expect(view.getUint8(0)).toBe(0x3B) + expect(view.getUint8(1)).toBe(0x7F) + expect(view.getUint8(2)).toBe(0xFC) + expect(view.getUint8(3)).toBe(0xBA) + + view.setUint16(1, 0x03BC) + + expect(view.getUint8(0)).toBe(0x3B) + expect(view.getUint8(1)).toBe(0x03) + expect(view.getUint8(2)).toBe(0xBC) + expect(view.getUint8(3)).toBe(0xBA) + } + + it("should provide `setInt32`") { + val view = new DataView(new ArrayBuffer(8)) + + view.setInt32(0, 0x7F3B8342, true) + view.setInt32(3, 0xFCBA1020) + + expect(view.getUint8(0)).toBe(0x42) + expect(view.getUint8(1)).toBe(0x83) + expect(view.getUint8(2)).toBe(0x3B) + expect(view.getUint8(3)).toBe(0xFC) + expect(view.getUint8(4)).toBe(0xBA) + expect(view.getUint8(5)).toBe(0x10) + expect(view.getUint8(6)).toBe(0x20) + expect(view.getUint8(7)).toBe(0x00) + } + + it("should provide `setUint32`") { + val view = new DataView(new ArrayBuffer(8)) + + view.setUint32(0, 0x7F3B8342, true) + view.setUint32(3, 0xFCBA1020L.toDouble) + + expect(view.getUint8(0)).toBe(0x42) + expect(view.getUint8(1)).toBe(0x83) + expect(view.getUint8(2)).toBe(0x3B) + expect(view.getUint8(3)).toBe(0xFC) + expect(view.getUint8(4)).toBe(0xBA) + expect(view.getUint8(5)).toBe(0x10) + expect(view.getUint8(6)).toBe(0x20) + expect(view.getUint8(7)).toBe(0x00) + } + + it("should provide `setFloat32`") { + val view = new DataView(new ArrayBuffer(5)) + + view.setFloat32(0, 0.1f) + view.setFloat32(1, 0.4f) + + expect(view.getFloat32(1)).toBeCloseTo(0.4, 7) + expect(view.getFloat32(0)).not.toBeCloseTo(0.1, 7) + + view.setFloat32(0, 0.1f, true) + view.setFloat32(1, 0.4f, true) + + expect(view.getFloat32(0, true)).not.toBeCloseTo(0.1, 7) + expect(view.getFloat32(1, true)).toBeCloseTo(0.4, 7) + } + + it("should provide `getFloat64`") { + val view = new DataView(new ArrayBuffer(9)) + + view.setFloat64(0, 0.5) + view.setFloat64(1, 0.6) + + expect(view.getFloat64(0)).not.toBe(0.5) + expect(view.getFloat64(1)).toBe(0.6) + + view.setFloat64(0, 0.5, true) + view.setFloat64(1, 0.6, true) + + expect(view.getFloat64(0, true)).not.toBe(0.5) + expect(view.getFloat64(1, true)).toBe(0.6) + } + + } + +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/typedarray/TypedArrayConversionTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/typedarray/TypedArrayConversionTest.scala new file mode 100644 index 0000000..dc9a056 --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/typedarray/TypedArrayConversionTest.scala @@ -0,0 +1,197 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.typedarray + +import org.scalajs.jasminetest.JasmineTest + +import scala.scalajs.js +import js.typedarray._ + +object TypedArrayConversionTest extends JasmineTest { + + when("typedarray"). + describe("TypedArray to scala.Array conversions") { + + def data(factor: Double) = js.Array(-1,1,2,3,4,5,6,7,8).map((_: Int) * factor) + def sum(factor: Double) = (8 * 9 / 2 - 1) * factor + + it("should convert an Int8Array to a scala.Array[Byte]") { + val x = new Int8Array(data(1)) + val y = x.toArray + + expect(y.getClass == classOf[scala.Array[Byte]]).toBeTruthy + expect(y.sum).toEqual(sum(1)) + + // Ensure its a copy + x(0) = 0 + expect(y.sum).toEqual(sum(1)) + } + + it("should convert an Int16Array to a scala.Array[Short]") { + val x = new Int16Array(data(100)) + val y = x.toArray + + expect(y.getClass == classOf[scala.Array[Short]]).toBeTruthy + expect(y.sum).toEqual(sum(100)) + + // Ensure its a copy + x(0) = 0 + expect(y.sum).toEqual(sum(100)) + } + + it("should convert an Uint16Array to a scala.Array[Char]") { + val data = js.Array((1 to 6).map(_ * 10000): _*) + val sum = (6*7/2*10000).toChar + + val x = new Uint16Array(data) + val y = x.toArray + + expect(y.getClass == classOf[scala.Array[Char]]).toBeTruthy + expect(y.sum).toEqual(sum) + + // Ensure its a copy + x(0) = 0 + expect(y.sum).toEqual(sum) + } + + it("should convert an Int32Array to a scala.Array[Int]") { + val x = new Int32Array(data(10000)) + val y = x.toArray + + expect(y.getClass == classOf[scala.Array[Int]]).toBeTruthy + expect(y.sum).toEqual(sum(10000)) + + // Ensure its a copy + x(0) = 0 + expect(y.sum).toEqual(sum(10000)) + } + + it("should convert a Float32Array to a scala.Array[Float]") { + val x = new Float32Array(data(0.2)) + val y = x.toArray + + expect(y.getClass == classOf[scala.Array[Float]]).toBeTruthy + expect(y.sum).toBeCloseTo(sum(0.2), 6) + + // Ensure its a copy + x(0) = 0 + expect(y.sum).toBeCloseTo(sum(0.2), 6) + } + + it("should convert a Float64Array to a scala.Array[Double]") { + val x = new Float64Array(data(0.2)) + val y = x.toArray + + expect(y.getClass == classOf[scala.Array[Double]]).toBeTruthy + expect(y.sum).toEqual(sum(0.2)) + + // Ensure its a copy + x(0) = 0 + expect(y.sum).toEqual(sum(0.2)) + } + + } + + when("typedarray"). + describe("scala.Array to TypedArray conversions") { + + it("should convert a scala.Array[Byte] to an Int8Array") { + val x = (Byte.MinValue to Byte.MaxValue).map(_.toByte).toArray + val y = x.toTypedArray + + expect(y.isInstanceOf[Int8Array]).toBeTruthy + expect(y.length).toBe(x.length) + + for (i <- 0 until y.length) + expect(y(i)).toBe(x(i)) + + // Ensure its a copy + x(0) = 0 + expect(y(0)).toBe(Byte.MinValue) + } + + it("should convert a scala.Array[Short] to an Int16Array") { + val x = ((Short.MinValue to (Short.MinValue + 1000)) ++ + ((Short.MaxValue - 1000) to Short.MaxValue)).map(_.toShort).toArray + val y = x.toTypedArray + + expect(y.isInstanceOf[Int16Array]).toBeTruthy + expect(y.length).toBe(x.length) + + for (i <- 0 until y.length) + expect(y(i)).toBe(x(i)) + + // Ensure its a copy + x(0) = 0 + expect(y(0)).toBe(Short.MinValue) + } + + it("should convert a scala.Array[Char] to an Uint16Array") { + val x = ((Char.MaxValue - 1000) to Char.MaxValue).map(_.toChar).toArray + val y = x.toTypedArray + + expect(y.isInstanceOf[Uint16Array]).toBeTruthy + expect(y.length).toBe(x.length) + + for (i <- 0 until y.length) + expect(y(i)).toBe(x(i).toInt) + + // Ensure its a copy + x(0) = 0 + expect(y(0)).toBe(Char.MaxValue - 1000) + } + + it("should convert a scala.Array[Int] to an Int32Array") { + val x = ((Int.MinValue to (Int.MinValue + 1000)) ++ + ((Int.MaxValue - 1000) to Int.MaxValue)).toArray + val y = x.toTypedArray + + expect(y.isInstanceOf[Int32Array]).toBeTruthy + expect(y.length).toBe(x.length) + + for (i <- 0 until y.length) + expect(y(i)).toBe(x(i)) + + // Ensure its a copy + x(0) = 0 + expect(y(0)).toBe(Int.MinValue) + } + + it("should convert a scala.Array[Float] to a Float32Array") { + val x = Array[Float](1.0f, 2.0f, -2.3f, 5.3f) + val y = x.toTypedArray + + expect(y.isInstanceOf[Float32Array]).toBeTruthy + expect(y.length).toBe(x.length) + + for (i <- 0 until y.length) + expect(y(i)).toBe(x(i)) + + // Ensure its a copy + x(0) = 0 + expect(y(0)).toBe(1.0f) + } + + it("should convert a scala.Array[Double] to a Float64Array") { + val x = Array[Double](1.0, 2.0, -2.3, 5.3) + val y = x.toTypedArray + + expect(y.isInstanceOf[Float64Array]).toBeTruthy + expect(y.length).toBe(x.length) + + for (i <- 0 until y.length) + expect(y(i)).toBe(x(i)) + + // Ensure its a copy + x(0) = 0 + expect(y(0)).toBe(1.0) + } + + } + +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/typedarray/TypedArrayTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/typedarray/TypedArrayTest.scala new file mode 100644 index 0000000..1dfbbd4 --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/typedarray/TypedArrayTest.scala @@ -0,0 +1,332 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.typedarray + +import org.scalajs.jasminetest.JasmineTest +import org.scalajs.jasmine.JasmineExpectation + +import scala.scalajs.js +import js.typedarray._ + +/** Shallow test for TypedArrays. Basically just tests that the method exist and + * return something which could be a right result. It is probably sufficient to + * test whether a runtime supports TypedArrays. + */ +object TypedArrayTest extends JasmineTest { + + /** Generalized tests for all TypedArrays */ + def tests[V, T <: TypedArray[V, T]](name: String, + bytesPerElement: => Int, + lenCtor: Int => T, + tarrCtor: TypedArray[_, _] => T, + arrCtor: js.Array[_] => T, + bufCtor1: (ArrayBuffer) => T, + bufCtor2: (ArrayBuffer, Int) => T, + bufCtor3: (ArrayBuffer, Int, Int) => T, + hasType: Any => Boolean, + expectV: V => JasmineExpectation, + intToV: Int => V + ) = { + + when("typedarray"). + describe(name) { + + it(s"should allow constructing a new $name with length") { + val x = lenCtor(10) + expect(hasType(x)).toBeTruthy + expect(x.length).toBe(10) + } + + it(s"should allow constructing a new $name from an Int8Array") { + val x = tarrCtor(new Float32Array(js.Array(3, 7))) + expect(hasType(x)).toBeTruthy + expect(x.length).toBe(2) + + expectV(x(0)).toEqual(3) + expectV(x(1)).toEqual(7) + } + + it(s"should allow constructing a new $name from a js.Array") { + val x = arrCtor(js.Array(5,6,7)) + expect(hasType(x)).toBeTruthy + expect(x.length).toBe(3) + + expectV(x(0)).toEqual(5) + expectV(x(1)).toEqual(6) + expectV(x(2)).toEqual(7) + } + + it(s"should allow constructing a new $name from an ArrayBuffer (1 arg)") { + val buf = arrCtor(js.Array(5, 6, 7, 8)).buffer + val x = bufCtor1(buf) + expect(hasType(x)).toBeTruthy + expect(x.length).toBe(4) + + expectV(x(0)).toEqual(5) + expectV(x(1)).toEqual(6) + expectV(x(2)).toEqual(7) + expectV(x(3)).toEqual(8) + } + + it(s"should allow constructing a new $name from an ArrayBuffer (2 arg)") { + val buf = arrCtor(js.Array(5, 6, 7, 8)).buffer + val x = bufCtor2(buf, bytesPerElement) + expect(hasType(x)).toBeTruthy + expect(x.length).toBe(3) + + expectV(x(0)).toEqual(6) + expectV(x(1)).toEqual(7) + expectV(x(2)).toEqual(8) + } + + it(s"should allow constructing a new $name from an ArrayBuffer (3 arg)") { + val buf = arrCtor(js.Array(5, 6, 7, 8)).buffer + val x = bufCtor3(buf, bytesPerElement, 2) + expect(hasType(x)).toBeTruthy + expect(x.length).toBe(2) + + expectV(x(0)).toEqual(6) + expectV(x(1)).toEqual(7) + } + + it("should allow retrieving the length") { + val x = lenCtor(100) + expect(x.length).toBe(100) + } + + it("should allow retrieving an element") { + val x = arrCtor(js.Array(5)) + expectV(x(0)).toBe(5) + } + + it("should allow setting an element") { + val x = lenCtor(2) + + x(0) = intToV(5) + x(1) = intToV(10) + + expectV(x(0)).toBe(5) + expectV(x(1)).toBe(10) + } + + it("should provide `get`") { + val x = arrCtor(js.Array(10)) + expectV(x.get(0)).toBe(10) + } + + it("should provide `set` for a single element") { + val x = lenCtor(10) + x.set(0, intToV(5)) + x.set(1, intToV(6)) + + expectV(x(0)).toBe(5) + expectV(x(1)).toBe(6) + expectV(x(2)).toBe(0) + } + + it("should provide `set` for a js.Array with one arguments") { + val x = lenCtor(10) + x.set(js.Array(5,6,7)) + expectV(x(0)).toBe(5) + expectV(x(1)).toBe(6) + expectV(x(2)).toBe(7) + expectV(x(3)).toBe(0) + expectV(x(4)).toBe(0) + expectV(x(5)).toBe(0) + } + + it("should provide `set` for a js.Array with two arguments") { + val x = lenCtor(10) + x.set(js.Array(5,6,7), 2) + expectV(x(0)).toBe(0) + expectV(x(1)).toBe(0) + expectV(x(2)).toBe(5) + expectV(x(3)).toBe(6) + expectV(x(4)).toBe(7) + expectV(x(5)).toBe(0) + } + + it("should provide `set` for a TypedArray with one argument") { + val x = lenCtor(10) + x.set(new Int8Array(js.Array(5,6,7))) + expectV(x(0)).toBe(5) + expectV(x(1)).toBe(6) + expectV(x(2)).toBe(7) + expectV(x(3)).toBe(0) + expectV(x(4)).toBe(0) + expectV(x(5)).toBe(0) + } + + it("should provide `set` for a TypedArray with two arguments") { + val x = lenCtor(10) + x.set(new Int8Array(js.Array(5,6,7)), 2) + expectV(x(0)).toBe(0) + expectV(x(1)).toBe(0) + expectV(x(2)).toBe(5) + expectV(x(3)).toBe(6) + expectV(x(4)).toBe(7) + expectV(x(5)).toBe(0) + } + + it("should provide `subarray` with one argument") { + val x = arrCtor(js.Array(1,2,3,4,5,6,7,8,9)) + val y = x.subarray(2) + + expect(y.length).toBe(7) + expectV(y(0)).toBe(3) + + x(2) = intToV(100) + + expectV(y(0)).toBe(100) + } + + it("should provide `subarray` with two arguments") { + val x = arrCtor(js.Array(1,2,3,4,5,6,7,8,9)) + val y = x.subarray(2, 4) + + expect(y.length).toBe(2) + expectV(y(0)).toBe(3) + + x(2) = intToV(100) + + expectV(y(0)).toBe(100) + } + + it("should provide `buffer`") { + val x = arrCtor(js.Array(1,2,3,4,5,6,7,8,9)) + val y = bufCtor3(x.buffer, 0, 2) + + expect(x.buffer eq y.buffer).toBeTruthy + } + + it("should provide `byteLength`") { + val x = arrCtor(js.Array(0 until bytesPerElement * 4: _*)) + val y = bufCtor3(x.buffer, bytesPerElement, 3) + + expect(y.byteLength).toBe(3 * bytesPerElement) + } + + it("should provide `byteOffset`") { + val x = arrCtor(js.Array(0 until bytesPerElement * 4: _*)) + val y = bufCtor3(x.buffer, bytesPerElement, 3) + + expect(y.byteOffset).toBe(bytesPerElement) + } + + } + } + + tests("Int8Array", + Int8Array.BYTES_PER_ELEMENT, + len => new Int8Array(len), + tarr => new Int8Array(tarr), + arr => new Int8Array(arr), + buf => new Int8Array(buf), + (buf, start) => new Int8Array(buf, start), + (buf, start, end) => new Int8Array(buf, start, end), + _.isInstanceOf[Int8Array], + expect (_: Byte), + _.toByte) + + tests("Uint8Array", + Uint8Array.BYTES_PER_ELEMENT, + len => new Uint8Array(len), + tarr => new Uint8Array(tarr), + arr => new Uint8Array(arr), + buf => new Uint8Array(buf), + (buf, start) => new Uint8Array(buf, start), + (buf, start, end) => new Uint8Array(buf, start, end), + _.isInstanceOf[Uint8Array], + expect (_: Short), + _.toShort) + + tests("Uint8ClampedArray", + Uint8ClampedArray.BYTES_PER_ELEMENT, + len => new Uint8ClampedArray(len), + tarr => new Uint8ClampedArray(tarr), + arr => new Uint8ClampedArray(arr), + buf => new Uint8ClampedArray(buf), + (buf, start) => new Uint8ClampedArray(buf, start), + (buf, start, end) => new Uint8ClampedArray(buf, start, end), + _.isInstanceOf[Uint8ClampedArray], + expect (_: Int), + _.toInt) + + tests("Int16Array", + Int16Array.BYTES_PER_ELEMENT, + len => new Int16Array(len), + tarr => new Int16Array(tarr), + arr => new Int16Array(arr), + buf => new Int16Array(buf), + (buf, start) => new Int16Array(buf, start), + (buf, start, end) => new Int16Array(buf, start, end), + _.isInstanceOf[Int16Array], + expect (_: Short), + _.toShort) + + tests("Uint16Array", + Uint16Array.BYTES_PER_ELEMENT, + len => new Uint16Array(len), + tarr => new Uint16Array(tarr), + arr => new Uint16Array(arr), + buf => new Uint16Array(buf), + (buf, start) => new Uint16Array(buf, start), + (buf, start, end) => new Uint16Array(buf, start, end), + _.isInstanceOf[Uint16Array], + expect (_: Int), + _.toInt) + + tests("Int32Array", + Int32Array.BYTES_PER_ELEMENT, + len => new Int32Array(len), + tarr => new Int32Array(tarr), + arr => new Int32Array(arr), + buf => new Int32Array(buf), + (buf, start) => new Int32Array(buf, start), + (buf, start, end) => new Int32Array(buf, start, end), + _.isInstanceOf[Int32Array], + expect (_: Int), + _.toInt) + + tests("Uint32Array", + Uint32Array.BYTES_PER_ELEMENT, + len => new Uint32Array(len), + tarr => new Uint32Array(tarr), + arr => new Uint32Array(arr), + buf => new Uint32Array(buf), + (buf, start) => new Uint32Array(buf, start), + (buf, start, end) => new Uint32Array(buf, start, end), + _.isInstanceOf[Uint32Array], + expect (_: Double), + _.toDouble) + + tests("Float32Array", + Float32Array.BYTES_PER_ELEMENT, + len => new Float32Array(len), + tarr => new Float32Array(tarr), + arr => new Float32Array(arr), + buf => new Float32Array(buf), + (buf, start) => new Float32Array(buf, start), + (buf, start, end) => new Float32Array(buf, start, end), + _.isInstanceOf[Float32Array], + expect (_: Float), + _.toFloat) + + tests("Float64Array", + Float64Array.BYTES_PER_ELEMENT, + len => new Float64Array(len), + tarr => new Float64Array(tarr), + arr => new Float64Array(arr), + buf => new Float64Array(buf), + (buf, start) => new Float64Array(buf, start), + (buf, start, end) => new Float64Array(buf, start, end), + _.isInstanceOf[Float64Array], + expect (_: Double), + _.toDouble) + +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/utils/JSUtils.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/utils/JSUtils.scala new file mode 100644 index 0000000..4b2ac62 --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/utils/JSUtils.scala @@ -0,0 +1,26 @@ +package scala.scalajs.testsuite.utils + +import scala.scalajs.js +import js.annotation.JSExport + +@JSExport("JSUtils") +object JSUtils { + /* We use java.lang.Character explicitly, because this class is used by + * tests that check that Chars are actually boxed by the compiler. + * If we rely on the compiler doing the job in here, we might have false + * positives just because the value was never boxed, and never unboxed. + */ + + @JSExport + def isChar(c: Any): Boolean = c.isInstanceOf[java.lang.Character] + + @JSExport + def stringToChar(s: String): java.lang.Character = { + assert(s.length == 1, "makeChar() requires a string of length 1") + new java.lang.Character(s.charAt(0)) + } + + @JSExport + def charToString(c: Any): String = + c.asInstanceOf[java.lang.Character].toString() +} diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/utils/TestDetector.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/utils/TestDetector.scala new file mode 100644 index 0000000..ad67a95 --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/utils/TestDetector.scala @@ -0,0 +1,33 @@ +package scala.scalajs.testsuite.utils + +import scala.scalajs.js +import js.annotation.JSExport +import js.JSConverters._ + +/** An ad-hoc but centralized way to detect tests in this test suite */ +@JSExport("scalajs.TestDetector") +object TestDetector { + + private final val basePackage = "scala.scalajs.testsuite" + + def detectTestNames(): List[String] = detectTestsInternal().map(_._2).toList + + @JSExport + def loadDetectedTests(): Unit = detectTestsInternal().foreach(_._1()) + + private def detectTestsInternal() = { + val parts = basePackage.split('.') + val base = parts.foldLeft(js.Dynamic.global)(_.selectDynamic(_)) + + // We make sure to use only exported modules (not classes) by checking + // .prototype of the exporters. + for { + pName <- js.Object.properties(base) + testName <- js.Object.properties(base.selectDynamic(pName)) + test = base.selectDynamic(pName).selectDynamic(testName) + if js.Object.getPrototypeOf(test.prototype.asInstanceOf[js.Object]) eq + js.Object.asInstanceOf[js.Dynamic].prototype + } yield (test, s"$basePackage.$pName.$testName") + } + +} diff --git a/examples/scala-js/tools/js/src/main/scala/scala/scalajs/tools/classpath/builder/NodeFileSystem.scala b/examples/scala-js/tools/js/src/main/scala/scala/scalajs/tools/classpath/builder/NodeFileSystem.scala new file mode 100644 index 0000000..d1eee10 --- /dev/null +++ b/examples/scala-js/tools/js/src/main/scala/scala/scalajs/tools/classpath/builder/NodeFileSystem.scala @@ -0,0 +1,67 @@ +package scala.scalajs.tools.classpath.builder + +import scala.scalajs.tools.io._ + +import scala.scalajs.js + +import scala.collection.immutable.Traversable + +import java.io._ + +/** FileSystem implementation using Node.js */ +trait NodeFileSystem extends FileSystem { + + import NodeFileSystem.fs + + type File = String + + private def stats(f: String) = fs.statSync(f) + + val DummyVersion: String = "DUMMY_FILE" + + def isDirectory(f: String): Boolean = + stats(f).isDirectory().asInstanceOf[Boolean] + + def isFile(f: String): Boolean = + stats(f).isFile().asInstanceOf[Boolean] + + def isJSFile(f: String): Boolean = + isFile(f) && f.endsWith(".js") + + def isIRFile(f: String): Boolean = + isFile(f) && f.endsWith(".sjsir") + + def isJARFile(f: String): Boolean = + isFile(f) && f.endsWith(".jar") + + def exists(f: String): Boolean = + fs.existsSync(f).asInstanceOf[Boolean] + + def getName(f: String): String = + VirtualFile.nameFromPath(f) + + def getAbsolutePath(f: String): String = + fs.realpathSync(f).asInstanceOf[String] + + def getVersion(f: String): String = + stats(f).mtime.asInstanceOf[js.Date].getTime.toString + + def listFiles(d: String): Traversable[String] = { + require(isDirectory(d)) + val prefix = if (d.endsWith("/")) d else d + "/" + + fs.readdirSync(d).asInstanceOf[js.Array[String]].toList.map(prefix + _) + } + + def toJSFile(f: String): VirtualJSFile = new NodeVirtualJSFile(f) + def toIRFile(f: String): VirtualScalaJSIRFile = new NodeVirtualScalaJSIRFile(f) + def toReader(f: String): Reader = + new NodeVirtualTextFile(f).reader + def toInputStream(f: String): InputStream = + new NodeVirtualBinaryFile(f).inputStream + +} + +private object NodeFileSystem { + private val fs = js.Dynamic.global.require("fs") +} diff --git a/examples/scala-js/tools/js/src/main/scala/scala/scalajs/tools/io/NodeVirtualFiles.scala b/examples/scala-js/tools/js/src/main/scala/scala/scalajs/tools/io/NodeVirtualFiles.scala new file mode 100644 index 0000000..6a0c3ee --- /dev/null +++ b/examples/scala-js/tools/js/src/main/scala/scala/scalajs/tools/io/NodeVirtualFiles.scala @@ -0,0 +1,62 @@ +package scala.scalajs.tools.io + +import scala.scalajs.js +import scala.scalajs.js.typedarray._ + +import java.io._ +import java.net.URI + +class NodeVirtualFile(override val path: String) extends VirtualFile { + import NodeFS.fs + + override def version: Option[String] = { + val stat = fs.statSync(path) + if (js.isUndefined(stat.mtime)) + None + else + Some(stat.mtime.asInstanceOf[js.Date].getTime.toString) + } + + override def exists: Boolean = + fs.existsSync(path).asInstanceOf[Boolean] + + override def toURI: URI = { + val abspath = fs.realpathSync(path).asInstanceOf[String] + new URI("file", abspath, null) + } +} + +class NodeVirtualTextFile(p: String) extends NodeVirtualFile(p) + with VirtualTextFile { + import NodeFS.fs + + override def content: String = { + val options = js.Dynamic.literal(encoding = "UTF-8") + fs.readFileSync(path, options).asInstanceOf[String] + } +} + +class NodeVirtualBinaryFile(p: String) extends NodeVirtualFile(p) + with VirtualBinaryFile { + import NodeFS.fs + + private def buf: ArrayBuffer = + new Uint8Array(fs.readFileSync(path).asInstanceOf[js.Array[Int]]).buffer + + override def content: Array[Byte] = new Int8Array(buf).toArray + override def inputStream: InputStream = new ArrayBufferInputStream(buf) +} + +class NodeVirtualJSFile(p: String) extends NodeVirtualTextFile(p) + with VirtualJSFile { + + /** Always returns None. We can't read them on JS anyway */ + override def sourceMap: Option[String] = None +} + +class NodeVirtualScalaJSIRFile(p: String) + extends NodeVirtualBinaryFile(p) with VirtualSerializedScalaJSIRFile + +private[io] object NodeFS { + val fs = js.Dynamic.global.require("fs") +} diff --git a/examples/scala-js/tools/js/src/main/scala/scala/scalajs/tools/json/Impl.scala b/examples/scala-js/tools/js/src/main/scala/scala/scalajs/tools/json/Impl.scala new file mode 100644 index 0000000..89c7255 --- /dev/null +++ b/examples/scala-js/tools/js/src/main/scala/scala/scalajs/tools/json/Impl.scala @@ -0,0 +1,36 @@ +package scala.scalajs.tools.json + +import scala.scalajs.tools.io.IO + +import scala.scalajs.js + +import java.io.{Writer, Reader} + +private[json] object Impl extends AbstractJSONImpl { + + type Repr = js.Any + + def fromString(x: String): Repr = x + def fromNumber(x: Number): Repr = x.doubleValue() + def fromBoolean(x: Boolean): Repr = x + def fromList(x: List[Repr]): Repr = js.Array(x: _*) + def fromMap(x: Map[String, Repr]): Repr = js.Dictionary(x.toSeq: _*) + + def toString(x: Repr): String = x.asInstanceOf[String] + def toNumber(x: Repr): Number = x.asInstanceOf[Double] + def toBoolean(x: Repr): Boolean = x.asInstanceOf[Boolean] + def toList(x: Repr): List[Repr] = x.asInstanceOf[js.Array[Repr]].toList + def toMap(x: Repr): Map[String, Repr] = + x.asInstanceOf[js.Dictionary[Repr]].toMap + + def serialize(x: Repr): String = js.JSON.stringify(x) + + def serialize(x: Repr, writer: Writer): Unit = + writer.write(serialize(x)) + + def deserialize(str: String): Repr = js.JSON.parse(str) + + def deserialize(reader: Reader): Repr = + deserialize(IO.readReaderToString(reader)) + +} diff --git a/examples/scala-js/tools/js/src/test/scala/scala/scalajs/tools/js/test/JasmineReporter.scala b/examples/scala-js/tools/js/src/test/scala/scala/scalajs/tools/js/test/JasmineReporter.scala new file mode 100644 index 0000000..7b63871 --- /dev/null +++ b/examples/scala-js/tools/js/src/test/scala/scala/scalajs/tools/js/test/JasmineReporter.scala @@ -0,0 +1,71 @@ +package scala.scalajs.tools.js.test + +import org.scalajs.jasmine.Suite + +import org.scalajs.jasminetest._ + +import scala.scalajs.js.annotation.JSExport + +import scala.scalajs.testbridge._ + +object JSConsoleTestOutput extends TestOutput { + + type Color = Null + + val errorColor: Color = null + val successColor: Color = null + val infoColor: Color = null + + def color(message: String, color: Color): String = message + + def error(message: String, stack: Array[StackTraceElement]): Unit = + withStack(message, stack) + + def error(message: String): Unit = println(message) + + def failure(message: String, stack: Array[StackTraceElement]): Unit = + withStack(message, stack) + + def failure(message: String): Unit = println(message) + def succeeded(message: String): Unit = println(message) + def skipped(message: String): Unit = println(message) + def pending(message: String): Unit = println(message) + def ignored(message: String): Unit = println(message) + def canceled(message: String): Unit = println(message) + + object log extends TestOutputLog { + def info(message: String): Unit = println(message) + def warn(message: String): Unit = println(message) + def error(message: String): Unit = println(message) + } + + private def withStack(message: String, stack: Array[StackTraceElement]) = + println(message + stack.mkString("\n", "\n", "")) + +} + +@JSExport("scalajs.JasmineConsoleReporter") +class JasmineConsoleReporter(throwOnFail: Boolean = false) + extends JasmineTestReporter(JSConsoleTestOutput) { + + private var suiteFails: Int = 0 + private var suiteCount: Int = 0 + + override def reportSuiteResults(suite: Suite): Unit = { + super.reportSuiteResults(suite) + if (suite.results().failedCount > 0) + suiteFails += 1 + suiteCount += 1 + } + + override def reportRunnerResults(): Unit = { + super.reportRunnerResults() + val failed = suiteFails > 0 + val resStr = if (failed) "Failed" else "Passed" + println(s"$resStr: Total $suiteCount, Failed $suiteFails") + + if (failed && throwOnFail) + sys.error("Jasmine test suite failed.") + } + +} diff --git a/examples/scala-js/tools/js/src/test/scala/scala/scalajs/tools/js/test/QuickLinker.scala b/examples/scala-js/tools/js/src/test/scala/scala/scalajs/tools/js/test/QuickLinker.scala new file mode 100644 index 0000000..580c4c5 --- /dev/null +++ b/examples/scala-js/tools/js/src/test/scala/scala/scalajs/tools/js/test/QuickLinker.scala @@ -0,0 +1,37 @@ +package scala.scalajs.tools.js.test + +import scala.scalajs.tools.sem.Semantics +import scala.scalajs.tools.io._ +import scala.scalajs.tools.logging._ +import scala.scalajs.tools.classpath._ +import scala.scalajs.tools.classpath.builder._ +import scala.scalajs.tools.optimizer._ + +import scala.scalajs.js.annotation.JSExport + +@JSExport("scalajs.QuickLinker") +object QuickLinker { + + /** Link a Scala.js application on Node.js */ + @JSExport + def linkNode(cpEntries: String*): String = { + val builder = new AbstractPartialClasspathBuilder with NodeFileSystem + val cp = builder.build(cpEntries.toList) + + val complete = cp.resolve() + + val optimizer = new ScalaJSOptimizer(Semantics.Defaults.optimized) + + val out = WritableMemVirtualJSFile("out.js") + + import ScalaJSOptimizer._ + val optimized = optimizer.optimizeCP( + Inputs(complete), + OutputConfig(out), + new ScalaConsoleLogger + ) + + out.content + } + +} diff --git a/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/classpath/builder/JarLibClasspathBuilder.scala b/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/classpath/builder/JarLibClasspathBuilder.scala new file mode 100644 index 0000000..5bc488c --- /dev/null +++ b/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/classpath/builder/JarLibClasspathBuilder.scala @@ -0,0 +1,13 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js tools ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.tools.classpath.builder + +class JarLibClasspathBuilder extends AbstractJarLibClasspathBuilder + with PhysicalFileSystem diff --git a/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/classpath/builder/PartialClasspathBuilder.scala b/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/classpath/builder/PartialClasspathBuilder.scala new file mode 100644 index 0000000..626c74c --- /dev/null +++ b/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/classpath/builder/PartialClasspathBuilder.scala @@ -0,0 +1,38 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js tools ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.tools.classpath.builder + +import scala.scalajs.tools.classpath._ +import scala.collection.immutable.Seq + +/** + * Allows to create a PartialClasspathBuilder from a (filesystem) classpath + * + * Rules for classpath reading: + * - IR goes to scalaJSIR + * - Descends into JARs + * - Entries stay in order of ‘cp‘, IR remains unordered + * - Earlier IR entries shadow later IR entries with the same relative path + * - JS goes to availableLibs (earlier libs take precedence) + * - JS_DEPENDENCIES are added to dependencies + */ +class PartialClasspathBuilder extends AbstractPartialClasspathBuilder + with PhysicalFileSystem + +object PartialClasspathBuilder { + /** Convenience method. The same as + * + * {{{ + * (new PartialClasspathBuilder).build(cp) + * }}} + */ + def build(cp: Seq[java.io.File]): PartialClasspath = + (new PartialClasspathBuilder).build(cp) +} diff --git a/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/classpath/builder/PhysicalFileSystem.scala b/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/classpath/builder/PhysicalFileSystem.scala new file mode 100644 index 0000000..a0dd7a5 --- /dev/null +++ b/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/classpath/builder/PhysicalFileSystem.scala @@ -0,0 +1,41 @@ +package scala.scalajs.tools.classpath.builder + +import scala.scalajs.tools.io._ + +import scala.collection.immutable.Traversable + +import java.io._ + +/** FileSystem implementation using java.io._ */ +trait PhysicalFileSystem extends FileSystem { + + type File = java.io.File + + val DummyVersion: String = "DUMMY_FILE" + + def isDirectory(f: File): Boolean = f.isDirectory + def isFile(f: File): Boolean = f.isFile + def isJSFile(f: File): Boolean = f.isFile && f.getName.endsWith(".js") + def isIRFile(f: File): Boolean = f.isFile && f.getName.endsWith(".sjsir") + def isJARFile(f: File): Boolean = f.isFile && f.getName.endsWith(".jar") + def exists(f: File): Boolean = f.exists + + def getName(f: File): String = f.getName + def getAbsolutePath(f: File): String = f.getAbsolutePath + def getVersion(f: File): String = f.lastModified.toString + + def listFiles(d: File): Traversable[File] = { + require(d.isDirectory) + Option(d.listFiles).map(_.toList).getOrElse { + throw new IOException(s"Couldn't list files in $d") + } + } + + def toJSFile(f: File): VirtualJSFile = FileVirtualJSFile(f) + def toIRFile(f: File): VirtualScalaJSIRFile = FileVirtualScalaJSIRFile(f) + def toReader(f: File): Reader = + new BufferedReader(new FileReader(f)) + def toInputStream(f: File): InputStream = + new BufferedInputStream(new FileInputStream(f)) + +} diff --git a/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/io/FileVirtualFiles.scala b/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/io/FileVirtualFiles.scala new file mode 100644 index 0000000..da29225 --- /dev/null +++ b/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/io/FileVirtualFiles.scala @@ -0,0 +1,157 @@ +package scala.scalajs.tools.io + +import scala.annotation.tailrec + +import java.io._ +import java.net.URI + +/** A [[VirtualFile]] implemented by an actual file on the file system. */ +class FileVirtualFile(val file: File) extends VirtualFile { + import FileVirtualFile._ + + override def path = file.getPath + + override def name = file.getName + + override def version: Option[String] = { + if (!file.isFile) None + else Some(file.lastModified.toString) + } + + override def exists: Boolean = file.exists + + override def toURI: URI = file.toURI +} + +object FileVirtualFile extends (File => FileVirtualFile) { + def apply(f: File): FileVirtualFile = + new FileVirtualFile(f) + + /** Tests whether the given file has the specified extension. + * Extension contain the '.', so a typical value for `ext` would be ".js". + * The comparison is case-sensitive. + */ + def hasExtension(file: File, ext: String): Boolean = + file.getName.endsWith(ext) + + /** Returns a new file with the same parent as the given file but a different + * name. + */ + def withName(file: File, newName: String): File = + new File(file.getParentFile(), newName) + + /** Returns a new file with the same path as the given file but a different + * extension. + * Extension contain the '.', so a typical value for `ext` would be ".js". + * Precondition: hasExtension(file, oldExt) + */ + def withExtension(file: File, oldExt: String, newExt: String): File = { + require(hasExtension(file, oldExt), + s"File $file does not have extension '$oldExt'") + withName(file, file.getName.stripSuffix(oldExt) + newExt) + } +} + +/** A [[VirtualTextFile]] implemented by an actual file on the file system. */ +class FileVirtualTextFile(f: File) extends FileVirtualFile(f) + with VirtualTextFile { + import FileVirtualTextFile._ + + override def content: String = readFileToString(file) + override def reader: Reader = new BufferedReader(new FileReader(f)) +} + +object FileVirtualTextFile extends (File => FileVirtualTextFile) { + def apply(f: File): FileVirtualTextFile = + new FileVirtualTextFile(f) + + /** Reads the entire content of a file as a UTF-8 string. */ + def readFileToString(file: File): String = { + val stream = new FileInputStream(file) + try IO.readInputStreamToString(stream) + finally stream.close() + } +} + +trait WritableFileVirtualTextFile extends FileVirtualTextFile + with WritableVirtualTextFile { + override def contentWriter: Writer = { + new BufferedWriter(new OutputStreamWriter( + new FileOutputStream(file), "UTF-8")) + } +} + +object WritableFileVirtualTextFile { + def apply(f: File): WritableFileVirtualTextFile = + new FileVirtualTextFile(f) with WritableFileVirtualTextFile +} + +/** A [[VirtualBinaryFile]] implemented by an actual file on the file system. */ +class FileVirtualBinaryFile(f: File) extends FileVirtualFile(f) + with VirtualBinaryFile { + import FileVirtualBinaryFile._ + + override def inputStream: InputStream = + new BufferedInputStream(new FileInputStream(file)) + + override def content: Array[Byte] = + readFileToByteArray(file) +} + +object FileVirtualBinaryFile extends (File => FileVirtualBinaryFile) { + def apply(f: File): FileVirtualBinaryFile = + new FileVirtualBinaryFile(f) + + /** Reads the entire content of a file as byte array. */ + def readFileToByteArray(file: File): Array[Byte] = { + val stream = new FileInputStream(file) + try IO.readInputStreamToByteArray(stream) + finally stream.close() + } +} + +class FileVirtualJSFile(f: File) extends FileVirtualTextFile(f) + with VirtualJSFile { + import FileVirtualFile._ + import FileVirtualTextFile._ + + val sourceMapFile: File = withExtension(file, ".js", ".js.map") + + override def sourceMap: Option[String] = { + if (sourceMapFile.exists) Some(readFileToString(sourceMapFile)) + else None + } +} + +object FileVirtualJSFile extends (File => FileVirtualJSFile) { + def apply(f: File): FileVirtualJSFile = + new FileVirtualJSFile(f) +} + +trait WritableFileVirtualJSFile extends FileVirtualJSFile + with WritableFileVirtualTextFile + with WritableVirtualJSFile { + + override def sourceMapWriter: Writer = { + new BufferedWriter(new OutputStreamWriter( + new FileOutputStream(sourceMapFile), "UTF-8")) + } +} + +object WritableFileVirtualJSFile { + def apply(f: File): WritableFileVirtualJSFile = + new FileVirtualJSFile(f) with WritableFileVirtualJSFile +} + +class FileVirtualScalaJSIRFile(f: File) + extends FileVirtualBinaryFile(f) with VirtualSerializedScalaJSIRFile + +object FileVirtualScalaJSIRFile extends (File => FileVirtualScalaJSIRFile) { + import FileVirtualFile._ + + def apply(f: File): FileVirtualScalaJSIRFile = + new FileVirtualScalaJSIRFile(f) + + def isScalaJSIRFile(file: File): Boolean = + hasExtension(file, ".sjsir") +} diff --git a/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/json/Impl.scala b/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/json/Impl.scala new file mode 100644 index 0000000..ea847e3 --- /dev/null +++ b/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/json/Impl.scala @@ -0,0 +1,38 @@ +package scala.scalajs.tools.json + +import org.json.simple.JSONValue + +import scala.collection.JavaConverters._ + +import java.io.{Writer, Reader} + +private[json] object Impl extends AbstractJSONImpl { + + type Repr = Object + + def fromString(x: String): Repr = x + def fromNumber(x: Number): Repr = x + def fromBoolean(x: Boolean): Repr = java.lang.Boolean.valueOf(x) + def fromList(x: List[Repr]): Repr = x.asJava + def fromMap(x: Map[String, Repr]): Repr = x.asJava + + def toString(x: Repr): String = x.asInstanceOf[String] + def toNumber(x: Repr): Number = x.asInstanceOf[Number] + def toBoolean(x: Repr): Boolean = + x.asInstanceOf[java.lang.Boolean].booleanValue() + def toList(x: Repr): List[Repr] = + x.asInstanceOf[java.util.List[Repr]].asScala.toList + def toMap(x: Repr): Map[String, Repr] = + x.asInstanceOf[java.util.Map[String, Repr]].asScala.toMap + + def serialize(x: Repr): String = + JSONValue.toJSONString(x) + + def serialize(x: Repr, writer: Writer): Unit = + JSONValue.writeJSONString(x, writer) + + def deserialize(str: String): Repr = JSONValue.parse(str) + + def deserialize(reader: Reader): Repr = JSONValue.parse(reader) + +} diff --git a/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/ClosureAstBuilder.scala b/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/ClosureAstBuilder.scala new file mode 100644 index 0000000..8d2eb2b --- /dev/null +++ b/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/ClosureAstBuilder.scala @@ -0,0 +1,47 @@ +package scala.scalajs.tools.optimizer + +import scala.scalajs.ir +import ir.Position.NoPosition + +import scala.scalajs.tools.javascript.Trees.Tree + +import com.google.javascript.rhino._ +import com.google.javascript.rhino.jstype.{StaticSourceFile, SimpleSourceFile} +import com.google.javascript.jscomp._ + +import scala.collection.mutable + +import java.net.URI + +class ClosureAstBuilder( + relativizeBaseURI: Option[URI] = None) extends JSTreeBuilder { + + private val transformer = new ClosureAstTransformer(relativizeBaseURI) + private val treeBuf = mutable.ListBuffer.empty[Node] + + def addJSTree(tree: Tree): Unit = + treeBuf += transformer.transformStat(tree)(NoPosition) + + lazy val closureAST: SourceAst = { + val root = transformer.setNodePosition(IR.script(treeBuf: _*), NoPosition) + + treeBuf.clear() + + new ClosureAstBuilder.ScalaJSSourceAst(root) + } + +} + +object ClosureAstBuilder { + // Dummy Source AST class + + class ScalaJSSourceAst(root: Node) extends SourceAst { + def getAstRoot(compiler: AbstractCompiler): Node = root + def clearAst(): Unit = () // Just for GC. Nonsensical here. + def getInputId(): InputId = root.getInputId() + def getSourceFile(): SourceFile = + root.getStaticSourceFile().asInstanceOf[SourceFile] + def setSourceFile(file: SourceFile): Unit = + if (getSourceFile() ne file) throw new IllegalStateException + } +} diff --git a/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/ClosureAstTransformer.scala b/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/ClosureAstTransformer.scala new file mode 100644 index 0000000..af22501 --- /dev/null +++ b/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/ClosureAstTransformer.scala @@ -0,0 +1,397 @@ +package scala.scalajs.tools.optimizer + +import scala.scalajs.ir +import ir.Position +import ir.Position.NoPosition + +import scala.scalajs.tools.javascript.Trees._ + +import com.google.javascript.rhino._ +import com.google.javascript.jscomp._ + +import scala.collection.mutable +import scala.annotation.tailrec + +import java.net.URI + +class ClosureAstTransformer(val relativizeBaseURI: Option[URI] = None) { + + private val inputId = new InputId("Scala.js IR") + + private val dummySourceName = new java.net.URI("virtualfile:scala.js-ir") + + def transformStat(tree: Tree)(implicit parentPos: Position): Node = + innerTransformStat(tree, tree.pos orElse parentPos) + + private def innerTransformStat(tree: Tree, pos_in: Position): Node = { + implicit val pos = pos_in + + wrapTransform(tree) { + case VarDef(ident, _, EmptyTree) => + new Node(Token.VAR, transformName(ident)) + case VarDef(ident, _, rhs) => + val node = transformName(ident) + node.addChildToFront(transformExpr(rhs)) + new Node(Token.VAR, node) + case Skip() => + new Node(Token.EMPTY) + case Block(stats) => + transformBlock(stats, pos) + case Labeled(label, body) => + new Node(Token.LABEL, transformLabel(label), transformBlock(body)) + case Return(EmptyTree) => + new Node(Token.RETURN) + case Return(expr) => + new Node(Token.RETURN, transformExpr(expr)) + case If(cond, thenp, Skip()) => + new Node(Token.IF, transformExpr(cond), transformBlock(thenp)) + case If(cond, thenp, elsep) => + new Node(Token.IF, transformExpr(cond), + transformBlock(thenp), transformBlock(elsep)) + case While(cond, body, None) => + new Node(Token.WHILE, transformExpr(cond), transformBlock(body)) + case While(cond, body, Some(label)) => + val whileNode = + new Node(Token.WHILE, transformExpr(cond), transformBlock(body)) + new Node(Token.LABEL, transformLabel(label), + setNodePosition(whileNode, pos)) + case DoWhile(body, cond, None) => + new Node(Token.DO, transformBlock(body), transformExpr(cond)) + case DoWhile(body, cond, Some(label)) => + val doNode = + new Node(Token.DO, transformBlock(body), transformExpr(cond)) + new Node(Token.LABEL, transformLabel(label), + setNodePosition(doNode, pos)) + case Try(block, errVar, handler, EmptyTree) => + val catchPos = handler.pos orElse pos + val catchNode = + new Node(Token.CATCH, transformName(errVar), transformBlock(handler)) + val blockNode = + new Node(Token.BLOCK, setNodePosition(catchNode, catchPos)) + new Node(Token.TRY, transformBlock(block), + setNodePosition(blockNode, catchPos)) + case Try(block, _, EmptyTree, finalizer) => + val blockNode = setNodePosition(new Node(Token.BLOCK), pos) + new Node(Token.TRY, transformBlock(block), blockNode, + transformBlock(finalizer)) + case Try(block, errVar, handler, finalizer) => + val catchPos = handler.pos orElse pos + val catchNode = + new Node(Token.CATCH, transformName(errVar), transformBlock(handler)) + val blockNode = + new Node(Token.BLOCK, setNodePosition(catchNode, catchPos)) + new Node(Token.TRY, transformBlock(block), + setNodePosition(blockNode, catchPos), transformBlock(finalizer)) + case Throw(expr) => + new Node(Token.THROW, transformExpr(expr)) + case Break(None) => + new Node(Token.BREAK) + case Break(Some(label)) => + new Node(Token.BREAK, transformLabel(label)) + case Continue(None) => + new Node(Token.CONTINUE) + case Continue(Some(label)) => + new Node(Token.CONTINUE, transformLabel(label)) + + case Switch(selector, cases, default) => + val switchNode = new Node(Token.SWITCH, transformExpr(selector)) + + for ((expr, body) <- cases) { + val bodyNode = transformBlock(body) + bodyNode.putBooleanProp(Node.SYNTHETIC_BLOCK_PROP, true) + val caseNode = new Node(Token.CASE, transformExpr(expr), bodyNode) + switchNode.addChildToBack( + setNodePosition(caseNode, expr.pos orElse pos)) + } + + if (default != EmptyTree) { + val bodyNode = transformBlock(default) + bodyNode.putBooleanProp(Node.SYNTHETIC_BLOCK_PROP, true) + val caseNode = new Node(Token.DEFAULT_CASE, bodyNode) + switchNode.addChildToBack( + setNodePosition(caseNode, default.pos orElse pos)) + } + + switchNode + + case Debugger() => + new Node(Token.DEBUGGER) + case _ => + // We just assume it is an expression + new Node(Token.EXPR_RESULT, transformExpr(tree)) + } + } + + def transformExpr(tree: Tree)(implicit parentPos: Position): Node = + innerTransformExpr(tree, tree.pos orElse parentPos) + + private def innerTransformExpr(tree: Tree, pos_in: Position): Node = { + implicit val pos = pos_in + + wrapTransform(tree) { + case Block(exprs) => + exprs.map(transformExpr).reduceRight { (expr1, expr2) => + setNodePosition(new Node(Token.COMMA, expr1, expr2), pos) + } + case If(cond, thenp, elsep) => + new Node(Token.HOOK, transformExpr(cond), + transformExpr(thenp), transformExpr(elsep)) + case Assign(lhs, rhs) => + new Node(Token.ASSIGN, transformExpr(lhs), transformExpr(rhs)) + case New(ctor, args) => + val node = new Node(Token.NEW, transformExpr(ctor)) + args.foreach(arg => node.addChildToBack(transformExpr(arg))) + node + case DotSelect(qualifier, item) => + new Node(Token.GETPROP, transformExpr(qualifier), transformString(item)) + case BracketSelect(qualifier, item) => + new Node(Token.GETELEM, transformExpr(qualifier), transformExpr(item)) + + case Apply(fun, args) => + val node = new Node(Token.CALL, transformExpr(fun)) + args.foreach(arg => node.addChildToBack(transformExpr(arg))) + + // Closure needs to know (from the parser), if the call has a bound + // `this` or not. Since JSDesugar inserts protects calls if necessary, + // it is sufficient to check if we have a select as target + if (!fun.isInstanceOf[DotSelect] && + !fun.isInstanceOf[BracketSelect]) + node.putBooleanProp(Node.FREE_CALL, true) + + node + + case Delete(prop) => + new Node(Token.DELPROP, transformExpr(prop)) + case UnaryOp(op, lhs) => + mkUnaryOp(op, transformExpr(lhs)) + case BinaryOp(op, lhs, rhs) => + mkBinaryOp(op, transformExpr(lhs), transformExpr(rhs)) + case ArrayConstr(items) => + val node = new Node(Token.ARRAYLIT) + items.foreach(i => node.addChildToBack(transformExpr(i))) + node + + case ObjectConstr(fields) => + val node = new Node(Token.OBJECTLIT) + + for ((name, expr) <- fields) { + val fldNode = transformStringKey(name) + fldNode.addChildToBack(transformExpr(expr)) + node.addChildToBack(fldNode) + } + + node + + case Undefined() => + new Node(Token.VOID, setNodePosition(Node.newNumber(0.0), pos)) + case Null() => + new Node(Token.NULL) + case BooleanLiteral(value) => + if (value) new Node(Token.TRUE) else new Node(Token.FALSE) + case IntLiteral(value) => + Node.newNumber(value) + case DoubleLiteral(value) => + Node.newNumber(value) + case StringLiteral(value) => + Node.newString(value) + case VarRef(ident, _) => + transformName(ident) + case This() => + new Node(Token.THIS) + + case Function(args, body) => + // Note that a Function may also be a statement (when it is named), + // but Scala.js does not have such an IR node + val paramList = new Node(Token.PARAM_LIST) + args.foreach(arg => paramList.addChildToBack(transformParam(arg))) + + val emptyName = setNodePosition(Node.newString(Token.NAME, ""), pos) + + new Node(Token.FUNCTION, emptyName, paramList, transformBlock(body)) + + case _ => + throw new TransformException(s"Unknown tree of class ${tree.getClass()}") + } + } + + def transformParam(param: ParamDef)(implicit parentPos: Position): Node = + transformName(param.name) + + def transformName(ident: Ident)(implicit parentPos: Position): Node = + setNodePosition(Node.newString(Token.NAME, ident.name), + ident.pos orElse parentPos) + + def transformLabel(ident: Ident)(implicit parentPos: Position): Node = + setNodePosition(Node.newString(Token.LABEL_NAME, ident.name), + ident.pos orElse parentPos) + + def transformString(pName: PropertyName)(implicit parentPos: Position): Node = + setNodePosition(Node.newString(pName.name), pName.pos orElse parentPos) + + def transformStringKey(pName: PropertyName)( + implicit parentPos: Position): Node = { + val node = Node.newString(Token.STRING_KEY, pName.name) + + if (pName.isInstanceOf[StringLiteral]) + node.setQuotedString() + + setNodePosition(node, pName.pos orElse parentPos) + } + + def transformBlock(tree: Tree)(implicit parentPos: Position): Node = { + val pos = if (tree.pos.isDefined) tree.pos else parentPos + wrapTransform(tree) { + case Block(stats) => + transformBlock(stats, pos) + case tree => + transformBlock(List(tree), pos) + } (pos) + } + + def transformBlock(stats: List[Tree], blockPos: Position): Node = { + @inline def ctorDoc(node: Node) = { + val b = new JSDocInfoBuilder(false) + b.recordConstructor() + b.build(node) + } + + val block = new Node(Token.BLOCK) + + // The Rhino IR attaches DocComments to the following nodes (rather than + // having individual nodes). We preprocess these here. + @tailrec + def loop(ts: List[Tree], nextIsCtor: Boolean = false): Unit = ts match { + case DocComment(text) :: tss if text.startsWith("@constructor") => + loop(tss, nextIsCtor = true) + case DocComment(text) :: tss => + loop(tss) + case t :: tss => + val node = transformStat(t)(blockPos) + if (nextIsCtor) { + // The @constructor must be propagated through an ExprResult node + val trg = + if (node.isExprResult()) node.getChildAtIndex(0) + else node + + trg.setJSDocInfo(ctorDoc(trg)) + } + + block.addChildToBack(node) + + loop(tss) + case Nil => + } + + loop(stats) + + block + } + + @inline + private def wrapTransform(tree: Tree)(body: Tree => Node)( + implicit pos: Position): Node = { + try { + setNodePosition(body(tree), pos) + } catch { + case e: TransformException => + throw e // pass through + case e: RuntimeException => + throw new TransformException(tree, e) + } + } + + def setNodePosition(node: Node, pos: ir.Position): node.type = { + if (pos != ir.Position.NoPosition) { + attachSourceFile(node, pos.source) + node.setLineno(pos.line+1) + node.setCharno(pos.column) + } else { + attachSourceFile(node, dummySourceName) + } + node + } + + private def attachSourceFile(node: Node, source: URI): node.type = { + val str = sourceUriToString(source) + + node.setInputId(inputId) + node.setStaticSourceFile(new SourceFile(str)) + + node + } + + private def sourceUriToString(uri: URI): String = { + val relURI = relativizeBaseURI.fold(uri)(ir.Utils.relativize(_, uri)) + ir.Utils.fixFileURI(relURI).toASCIIString + } + + // Helpers for IR + @inline + private def mkUnaryOp(op: String, lhs: Node): Node = { + val tok = op match { + case "!" => Token.NOT + case "~" => Token.BITNOT + case "+" => Token.POS + case "-" => Token.NEG + case "typeof" => Token.TYPEOF + case _ => throw new TransformException(s"Unhandled unary op: $op") + } + + new Node(tok, lhs) + } + + @inline + private def mkBinaryOp(op: String, lhs: Node, rhs: Node): Node = { + val tok = op match { + case "|" => Token.BITOR + case "&" => Token.BITAND + case "^" => Token.BITXOR + case "==" => Token.EQ + case "!=" => Token.NE + case "<" => Token.LT + case "<=" => Token.LE + case ">" => Token.GT + case ">=" => Token.GE + case "<<" => Token.LSH + case ">>" => Token.RSH + case ">>>" => Token.URSH + case "+" => Token.ADD + case "-" => Token.SUB + case "*" => Token.MUL + case "/" => Token.DIV + case "%" => Token.MOD + case "===" => Token.SHEQ + case "!==" => Token.SHNE + case "in" => Token.IN + case "instanceof" => Token.INSTANCEOF + case "||" => Token.OR + case "&&" => Token.AND + case _ => + throw new TransformException(s"Unhandled binary op: $op") + } + + new Node(tok, lhs, rhs) + } + + // Exception wrapper in transforms + + class TransformException private (msg: String, e: Throwable) + extends RuntimeException(msg, e) { + + def this(tree: Tree, e: Throwable) = + this(TransformException.mkMsg(tree), e) + + def this(msg: String) = this(msg, null) + } + + object TransformException { + import ir.Printers._ + import java.io._ + + private def mkMsg(tree: Tree): String = { + "Exception while translating Scala.js JS tree to GCC IR at tree:\n" + + tree.show + } + } + +} diff --git a/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/ConcurrencyUtils.scala b/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/ConcurrencyUtils.scala new file mode 100644 index 0000000..471ed65 --- /dev/null +++ b/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/ConcurrencyUtils.scala @@ -0,0 +1,74 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js tools ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.tools.optimizer + +import scala.annotation.tailrec + +import scala.collection.concurrent.TrieMap + +import java.util.concurrent.atomic._ + +private[optimizer] object ConcurrencyUtils { + + /** An atomic accumulator supports adding single elements and retrieving and + * deleting all contained elements */ + type AtomicAcc[T] = AtomicReference[List[T]] + + object AtomicAcc { + @inline final def empty[T]: AtomicAcc[T] = + new AtomicReference[List[T]](Nil) + @inline final def apply[T](l: List[T]): AtomicAcc[T] = + new AtomicReference(l) + } + + implicit class AtomicAccOps[T](val acc: AtomicAcc[T]) extends AnyVal { + @inline final def size: Int = acc.get.size + + @inline + final def +=(x: T): Unit = AtomicAccOps.append(acc, x) + + @inline + final def removeAll(): List[T] = AtomicAccOps.removeAll(acc) + } + + object AtomicAccOps { + @inline + @tailrec + private final def append[T](acc: AtomicAcc[T], x: T): Boolean = { + val oldV = acc.get + val newV = x :: oldV + acc.compareAndSet(oldV, newV) || append(acc, x) + } + + @inline + private final def removeAll[T](acc: AtomicAcc[T]): List[T] = + acc.getAndSet(Nil) + } + + type TrieSet[T] = TrieMap[T, Null] + + implicit class TrieSetOps[T](val set: TrieSet[T]) extends AnyVal { + @inline final def +=(x: T): Unit = set.put(x, null) + } + + object TrieSet { + @inline final def empty[T]: TrieSet[T] = TrieMap.empty + } + + implicit class TrieMapOps[K,V](val map: TrieMap[K,V]) extends AnyVal { + @inline final def getOrPut(k: K, default: => V): V = { + map.get(k).getOrElse { + val v = default + map.putIfAbsent(k, v).getOrElse(v) + } + } + } + +} diff --git a/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/LoggerErrorManager.scala b/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/LoggerErrorManager.scala new file mode 100644 index 0000000..d51dd7b --- /dev/null +++ b/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/LoggerErrorManager.scala @@ -0,0 +1,38 @@ +package scala.scalajs.tools.optimizer + +import com.google.javascript.jscomp.{ BasicErrorManager, CheckLevel, JSError } + +import scala.scalajs.tools.logging.Logger + +/** A Google Closure Error Manager that forwards to a tools.logging.Logger */ +class LoggerErrorManager(private val log: Logger) extends BasicErrorManager { + + /** Ugly hack to disable FRACTIONAL_BITWISE_OPERAND warnings. Since its + * DiagnosticType (PeepholeFoldConstants.FRACTIONAL_BITWISE_OPERAND) is + * package private. + */ + override def report(level: CheckLevel, error: JSError): Unit = { + if (error.getType.key == "JSC_FRACTIONAL_BITWISE_OPERAND") + super.report(CheckLevel.OFF, error) + else + super.report(level, error) + } + + def println(level: CheckLevel, error: JSError): Unit = level match { + case CheckLevel.WARNING => log.warn (s"Closure: ${error}") + case CheckLevel.ERROR => log.error(s"Closure: ${error}") + case CheckLevel.OFF => + } + + protected def printSummary(): Unit = { + val msg = s"Closure: ${getErrorCount} error(s), ${getWarningCount} warning(s)" + + if (getErrorCount > 0) + log.error(msg) + else if (getWarningCount > 0) + log.warn(msg) + else + log.info(msg) + } + +} diff --git a/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/ParIncOptimizer.scala b/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/ParIncOptimizer.scala new file mode 100644 index 0000000..422238e --- /dev/null +++ b/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/ParIncOptimizer.scala @@ -0,0 +1,188 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js tools ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.tools.optimizer + +import scala.collection.{GenTraversableOnce, GenIterable} +import scala.collection.concurrent.TrieMap +import scala.collection.parallel.mutable.{ParTrieMap, ParArray} + +import java.util.concurrent.atomic._ + +import scala.scalajs.tools.sem.Semantics + +import ConcurrencyUtils._ + +class ParIncOptimizer(semantics: Semantics) extends GenIncOptimizer(semantics) { + + protected object CollOps extends GenIncOptimizer.AbsCollOps { + type Map[K, V] = TrieMap[K, V] + type ParMap[K, V] = ParTrieMap[K, V] + type AccMap[K, V] = TrieMap[K, AtomicAcc[V]] + type ParIterable[V] = ParArray[V] + type Addable[V] = AtomicAcc[V] + + def emptyAccMap[K, V]: AccMap[K, V] = TrieMap.empty + def emptyMap[K, V]: Map[K, V] = TrieMap.empty + def emptyParMap[K, V]: ParMap[K, V] = ParTrieMap.empty + def emptyParIterable[V]: ParIterable[V] = ParArray.empty + + // Operations on ParMap + def put[K, V](map: ParMap[K, V], k: K, v: V): Unit = map.put(k, v) + def remove[K, V](map: ParMap[K, V], k: K): Option[V] = map.remove(k) + + def retain[K, V](map: ParMap[K, V])(p: (K, V) => Boolean): Unit = { + map.foreach { case (k, v) => + if (!p(k, v)) + map.remove(k) + } + } + + // Operations on AccMap + def acc[K, V](map: AccMap[K, V], k: K, v: V): Unit = + map.getOrPut(k, AtomicAcc.empty) += v + + def getAcc[K, V](map: AccMap[K, V], k: K): GenIterable[V] = + map.get(k).fold[Iterable[V]](Nil)(_.removeAll()).toParArray + + def parFlatMapKeys[A, B](map: AccMap[A, _])( + f: A => GenTraversableOnce[B]): GenIterable[B] = + map.keys.flatMap(f).toParArray + + // Operations on ParIterable + def prepAdd[V](it: ParIterable[V]): Addable[V] = + AtomicAcc(it.toList) + + def add[V](addable: Addable[V], v: V): Unit = + addable += v + + def finishAdd[V](addable: Addable[V]): ParIterable[V] = + addable.removeAll().toParArray + } + + private val _interfaces = TrieMap.empty[String, InterfaceType] + protected def getInterface(encodedName: String): InterfaceType = + _interfaces.getOrPut(encodedName, new ParInterfaceType(encodedName)) + + private val methodsToProcess: AtomicAcc[MethodImpl] = AtomicAcc.empty + protected def scheduleMethod(method: MethodImpl): Unit = + methodsToProcess += method + + protected def newMethodImpl(owner: MethodContainer, + encodedName: String): MethodImpl = new ParMethodImpl(owner, encodedName) + + protected def processAllTaggedMethods(): Unit = { + val methods = methodsToProcess.removeAll().toParArray + logProcessingMethods(methods.count(!_.deleted)) + for (method <- methods) + method.process() + } + + private class ParInterfaceType(encName: String) extends InterfaceType(encName) { + private val ancestorsAskers = TrieSet.empty[MethodImpl] + private val dynamicCallers = TrieMap.empty[String, TrieSet[MethodImpl]] + private val staticCallers = TrieMap.empty[String, TrieSet[MethodImpl]] + + private var _ancestors: List[String] = encodedName :: Nil + + private val _instantiatedSubclasses: TrieSet[Class] = TrieSet.empty + + /** PROCESS PASS ONLY. Concurrency safe except with + * [[addInstantiatedSubclass]] and [[removeInstantiatedSubclass]] + */ + def instantiatedSubclasses: Iterable[Class] = + _instantiatedSubclasses.keys + + /** UPDATE PASS ONLY. Concurrency safe except with + * [[instantiatedSubclasses]] + */ + def addInstantiatedSubclass(x: Class): Unit = + _instantiatedSubclasses += x + + /** UPDATE PASS ONLY. Concurrency safe except with + * [[instantiatedSubclasses]] + */ + def removeInstantiatedSubclass(x: Class): Unit = + _instantiatedSubclasses -= x + + /** PROCESS PASS ONLY. Concurrency safe except with [[ancestors_=]] */ + def ancestors: List[String] = _ancestors + + /** UPDATE PASS ONLY. Not concurrency safe. */ + def ancestors_=(v: List[String]): Unit = { + if (v != _ancestors) { + _ancestors = v + ancestorsAskers.keysIterator.foreach(_.tag()) + ancestorsAskers.clear() + } + } + + /** PROCESS PASS ONLY. Concurrency safe except with [[ancestors_=]]. */ + def registerAskAncestors(asker: MethodImpl): Unit = + ancestorsAskers += asker + + /** PROCESS PASS ONLY. */ + def registerDynamicCaller(methodName: String, caller: MethodImpl): Unit = + dynamicCallers.getOrPut(methodName, TrieSet.empty) += caller + + /** PROCESS PASS ONLY. */ + def registerStaticCaller(methodName: String, caller: MethodImpl): Unit = + staticCallers.getOrPut(methodName, TrieSet.empty) += caller + + /** UPDATE PASS ONLY. */ + def unregisterDependee(dependee: MethodImpl): Unit = { + ancestorsAskers -= dependee + dynamicCallers.valuesIterator.foreach(_ -= dependee) + staticCallers.valuesIterator.foreach(_ -= dependee) + } + + /** UPDATE PASS ONLY. */ + def tagDynamicCallersOf(methodName: String): Unit = + dynamicCallers.remove(methodName).foreach(_.keysIterator.foreach(_.tag())) + + /** UPDATE PASS ONLY. */ + def tagStaticCallersOf(methodName: String): Unit = + staticCallers.remove(methodName).foreach(_.keysIterator.foreach(_.tag())) + } + + private class ParMethodImpl(owner: MethodContainer, + encodedName: String) extends MethodImpl(owner, encodedName) { + + private val bodyAskers = TrieSet.empty[MethodImpl] + + /** PROCESS PASS ONLY. */ + def registerBodyAsker(asker: MethodImpl): Unit = + bodyAskers += asker + + /** UPDATE PASS ONLY. */ + def unregisterDependee(dependee: MethodImpl): Unit = + bodyAskers -= dependee + + /** UPDATE PASS ONLY. */ + def tagBodyAskers(): Unit = { + bodyAskers.keysIterator.foreach(_.tag()) + bodyAskers.clear() + } + + private val _registeredTo = AtomicAcc.empty[Unregisterable] + private val tagged = new AtomicBoolean(false) + + protected def registeredTo(intf: Unregisterable): Unit = + _registeredTo += intf + + protected def unregisterFromEverywhere(): Unit = { + _registeredTo.removeAll().foreach(_.unregisterDependee(this)) + } + + protected def protectTag(): Boolean = !tagged.getAndSet(true) + protected def resetTag(): Unit = tagged.set(false) + + } + +} diff --git a/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/ScalaJSClosureOptimizer.scala b/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/ScalaJSClosureOptimizer.scala new file mode 100644 index 0000000..146b2b8 --- /dev/null +++ b/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/ScalaJSClosureOptimizer.scala @@ -0,0 +1,216 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js tools ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.tools.optimizer + +import scala.scalajs.tools.sem.Semantics +import scala.scalajs.tools.classpath._ +import scala.scalajs.tools.logging._ +import scala.scalajs.tools.io._ +import scala.scalajs.tools.corelib.CoreJSLibs + +import com.google.javascript.jscomp.{ + SourceFile => ClosureSource, + Compiler => ClosureCompiler, + CompilerOptions => ClosureOptions, + _ +} +import scala.collection.JavaConverters._ +import scala.collection.immutable.{Seq, Traversable} + +import java.net.URI + +/** Scala.js Closure optimizer: does advanced optimizations with Closure. */ +class ScalaJSClosureOptimizer(semantics: Semantics) { + import ScalaJSClosureOptimizer._ + + private def toClosureSource(file: VirtualJSFile) = + ClosureSource.fromReader(file.toURI.toString(), file.reader) + + private def toClosureInput(file: VirtualJSFile) = + new CompilerInput(toClosureSource(file)) + + /** Fully optimizes an [[IRClasspath]] by asking the ScalaJSOptimizer to + * emit a closure AST and then compiling this AST directly + */ + def optimizeCP(optimizer: ScalaJSOptimizer, + inputs: Inputs[ScalaJSOptimizer.Inputs[IRClasspath]], + outCfg: OutputConfig, logger: Logger): LinkedClasspath = { + + val cp = inputs.input.input + + CacheUtils.cached(cp.version, outCfg.output, outCfg.cache) { + logger.info(s"Full Optimizing ${outCfg.output.path}") + + val irFastOptInput = + inputs.input.copy(input = inputs.input.input.scalaJSIR) + val irFullOptInput = inputs.copy(input = irFastOptInput) + + optimizeIR(optimizer, irFullOptInput, outCfg, logger) + } + + new LinkedClasspath(cp.jsLibs, outCfg.output, cp.requiresDOM, cp.version) + } + + def optimizeIR(optimizer: ScalaJSOptimizer, + inputs: Inputs[ScalaJSOptimizer.Inputs[Traversable[VirtualScalaJSIRFile]]], + outCfg: OutputConfig, logger: Logger): Unit = { + + // Build Closure IR via ScalaJSOptimizer + val builder = new ClosureAstBuilder(outCfg.relativizeSourceMapBase) + + optimizer.optimizeIR(inputs.input, outCfg, builder, logger) + + // Build a Closure JSModule which includes the core libs + val module = new JSModule("Scala.js") + + for (lib <- CoreJSLibs.libs(semantics)) + module.add(toClosureInput(lib)) + + val ast = builder.closureAST + module.add(new CompilerInput(ast, ast.getInputId(), false)) + + for (export <- inputs.additionalExports) + module.add(toClosureInput(export)) + + // Compile the module + val closureExterns = + (ScalaJSExternsFile +: inputs.additionalExterns).map(toClosureSource) + + val options = closureOptions(outCfg) + val compiler = closureCompiler(logger) + + val result = GenIncOptimizer.logTime(logger, "Closure Compiler pass") { + compiler.compileModules( + closureExterns.asJava, List(module).asJava, options) + } + + GenIncOptimizer.logTime(logger, "Write Closure result") { + writeResult(result, compiler, outCfg.output) + } + } + + private def writeResult(result: Result, compiler: ClosureCompiler, + output: WritableVirtualJSFile): Unit = { + + val outputContent = if (result.errors.nonEmpty) "" + else "(function(){'use strict';" + compiler.toSource + "}).call(this);\n" + + val sourceMap = Option(compiler.getSourceMap()) + + // Write optimized code + val w = output.contentWriter + try { + w.write(outputContent) + if (sourceMap.isDefined) + w.write("//# sourceMappingURL=" + output.name + ".map\n") + } finally w.close() + + // Write source map (if available) + sourceMap.foreach { sm => + val w = output.sourceMapWriter + try sm.appendTo(w, output.name) + finally w.close() + } + } + + private def closureOptions(optConfig: OptimizerConfig, + noSourceMap: Boolean = false) = { + + val options = new ClosureOptions + options.prettyPrint = optConfig.prettyPrint + CompilationLevel.ADVANCED_OPTIMIZATIONS.setOptionsForCompilationLevel(options) + options.setLanguageIn(ClosureOptions.LanguageMode.ECMASCRIPT5) + options.setCheckGlobalThisLevel(CheckLevel.OFF) + + if (!noSourceMap && optConfig.wantSourceMap) { + options.setSourceMapOutputPath(optConfig.output.name + ".map") + options.setSourceMapDetailLevel(SourceMap.DetailLevel.ALL) + } + + options + } + + private def closureCompiler(logger: Logger) = { + val compiler = new ClosureCompiler + compiler.setErrorManager(new LoggerErrorManager(logger)) + compiler + } +} + +object ScalaJSClosureOptimizer { + /** Inputs of the Scala.js Closure optimizer. */ + final case class Inputs[T]( + /** Input to optimize (classpath or file-list) */ + input: T, + /** Additional externs files to be given to Closure. */ + additionalExterns: Seq[VirtualJSFile] = Nil, + /** Additional exports to be given to Closure. + * These files are just appended to the classpath, given to Closure, + * but not used in the Scala.js optimizer pass when direct optimizing + */ + additionalExports: Seq[VirtualJSFile] = Nil + ) + + /** Configuration the closure part of the optimizer needs. + * See [[OutputConfig]] for a description of the fields. + */ + trait OptimizerConfig { + val output: WritableVirtualJSFile + val cache: Option[WritableVirtualTextFile] + val wantSourceMap: Boolean + val prettyPrint: Boolean + val relativizeSourceMapBase: Option[URI] + } + + /** Configuration for the output of the Scala.js Closure optimizer */ + final case class OutputConfig( + /** Writer for the output */ + output: WritableVirtualJSFile, + /** Cache file */ + cache: Option[WritableVirtualTextFile] = None, + /** If true, performs expensive checks of the IR for the used parts. */ + checkIR: Boolean = false, + /** If true, the optimizer removes trees that have not been used in the + * last run from the cache. Otherwise, all trees that has been used once, + * are kept in memory. */ + unCache: Boolean = true, + /** If true, no optimizations are performed */ + disableOptimizer: Boolean = false, + /** If true, nothing is performed incrementally */ + batchMode: Boolean = false, + /** Ask to produce source map for the output */ + wantSourceMap: Boolean = false, + /** Pretty-print the output. */ + prettyPrint: Boolean = false, + /** Base path to relativize paths in the source map */ + relativizeSourceMapBase: Option[URI] = None + ) extends OptimizerConfig with ScalaJSOptimizer.OptimizerConfig + + /** Minimal set of externs to compile Scala.js-emitted code with Closure. */ + val ScalaJSExterns = """ + /** @constructor */ + function Object() {} + Object.protoype.toString = function() {}; + /** @constructor */ + function Array() {} + Array.prototype.length = 0; + /** @constructor */ + function Function() {} + Function.prototype.constructor = function() {}; + Function.prototype.call = function() {}; + Function.prototype.apply = function() {}; + var global = {}; + var __ScalaJSEnv = {}; + """ + + val ScalaJSExternsFile = new MemVirtualJSFile("ScalaJSExterns.js"). + withContent(ScalaJSExterns) + +} diff --git a/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/sourcemap/SourceMapper.scala b/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/sourcemap/SourceMapper.scala new file mode 100644 index 0000000..6f856a9 --- /dev/null +++ b/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/sourcemap/SourceMapper.scala @@ -0,0 +1,88 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js sbt plugin ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.tools.sourcemap + +import scala.scalajs.tools.classpath._ + +import com.google.debugging.sourcemap._ + +class SourceMapper(classpath: CompleteClasspath) { + + def map(ste: StackTraceElement, columnNumber: Int): StackTraceElement = { + val mapped = for { + sourceMap <- findSourceMap(ste.getFileName) + } yield map(ste, columnNumber, sourceMap) + + mapped.getOrElse(ste) + } + + def map(ste: StackTraceElement, columnNumber: Int, + sourceMap: String): StackTraceElement = { + + val sourceMapConsumer = + SourceMapConsumerFactory + .parse(sourceMap) + .asInstanceOf[SourceMapConsumerV3] + + /* **Attention** + * - StackTrace positions are 1-based + * - SourceMapConsumer.getMappingForLine() is 1-based + */ + + val lineNumber = ste.getLineNumber + val column = + if (columnNumber == -1) getFirstColumn(sourceMapConsumer, lineNumber) + else columnNumber + + val originalMapping = + sourceMapConsumer.getMappingForLine(lineNumber, column) + + if (originalMapping != null) { + new StackTraceElement( + ste.getClassName, + ste.getMethodName, + originalMapping.getOriginalFile, + originalMapping.getLineNumber) + } else ste + } + + /** both `lineNumber` and the resulting column are 1-based */ + private def getFirstColumn(sourceMapConsumer: SourceMapConsumerV3, + lineNumber1Based: Int) = { + + /* **Attention** + * - SourceMapConsumerV3.EntryVisitor is 0-based!!! + */ + + val lineNumber = lineNumber1Based - 1 + + var column: Option[Int] = None + + sourceMapConsumer.visitMappings( + new SourceMapConsumerV3.EntryVisitor { + def visit(sourceName: String, + symbolName: String, + sourceStartPosition: FilePosition, + startPosition: FilePosition, + endPosition: FilePosition): Unit = + if (!column.isDefined && startPosition.getLine == lineNumber) + column = Some(startPosition.getColumn) + }) + + val column0Based = column.getOrElse(0) + column0Based + 1 + } + + private def findSourceMap(path: String) = { + val candidates = classpath.allCode.filter(_.path == path) + if (candidates.size != 1) None // better no sourcemap than a wrong one + else candidates.head.sourceMap + } +} diff --git a/examples/scala-js/tools/jvm/src/test/resources/test.jar b/examples/scala-js/tools/jvm/src/test/resources/test.jar new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/examples/scala-js/tools/jvm/src/test/resources/test.jar diff --git a/examples/scala-js/tools/jvm/src/test/scala/scala/scalajs/tools/classpath/builder/test/ClasspathElementsTraverserTest.scala b/examples/scala-js/tools/jvm/src/test/scala/scala/scalajs/tools/classpath/builder/test/ClasspathElementsTraverserTest.scala new file mode 100644 index 0000000..72d01e4 --- /dev/null +++ b/examples/scala-js/tools/jvm/src/test/scala/scala/scalajs/tools/classpath/builder/test/ClasspathElementsTraverserTest.scala @@ -0,0 +1,42 @@ +package scala.scalajs.tools.classpath.builder.test + +import scala.scalajs.tools.classpath.builder._ +import scala.scalajs.tools.jsdep.JSDependencyManifest +import scala.scalajs.tools.io.VirtualScalaJSIRFile +import scala.scalajs.tools.io.VirtualJSFile + +import java.io.File + +import org.junit.Test +import org.junit.Assert._ + +class ClasspathElementsTraverserTest { + class TestElementTraverser extends ClasspathElementsTraverser with PhysicalFileSystem { + protected def handleDepManifest(m: => JSDependencyManifest): Unit = ??? + protected def handleIR(relPath: String, ir: => VirtualScalaJSIRFile): Unit = ??? + protected def handleJS(js: => VirtualJSFile): Unit = ??? + + def test(f: File): String = traverseClasspathElements(Seq(f)) + } + + val cpElementTraverser = new TestElementTraverser() + + @Test + def testNotExistingJarFile(): Unit = { + val res = cpElementTraverser.test(new File("dummy.jar")) + assertTrue(res.endsWith(cpElementTraverser.DummyVersion)) + } + + @Test + def testExistingDirectory(): Unit = { + val res = cpElementTraverser.test(new File("tools/jvm/src/test/resources")) + assertTrue(res.indexOf(cpElementTraverser.DummyVersion) < 0) + } + + @Test + def testExistingJarFile(): Unit = { + val res = cpElementTraverser.test(new File("tools/jvm/src/test/resources/test.jar")) + assertTrue(res.indexOf(cpElementTraverser.DummyVersion) < 0) + } +} + diff --git a/examples/scala-js/tools/jvm/src/test/scala/scala/scalajs/tools/classpath/builder/test/JarBuilderTest.scala b/examples/scala-js/tools/jvm/src/test/scala/scala/scalajs/tools/classpath/builder/test/JarBuilderTest.scala new file mode 100644 index 0000000..6ed68fc --- /dev/null +++ b/examples/scala-js/tools/jvm/src/test/scala/scala/scalajs/tools/classpath/builder/test/JarBuilderTest.scala @@ -0,0 +1,74 @@ +package scala.scalajs.tools.classpath.builder.test + +import scala.scalajs.tools.classpath.builder._ + +import org.junit.Test +import org.junit.Assert._ + +class JarBuilderTest { + + val jsFileContent = "window.alert('Hello World');" + val jsFileName = "lib.js" + + private def getJARInputStream = { + // Keep imports local so we don't accidentally use something we shouldn't + import java.util.zip._ + import java.io._ + + // Write a ZIP to a byte array + val outBuf = new ByteArrayOutputStream + val out = new ZipOutputStream(outBuf) + + out.putNextEntry(new ZipEntry(jsFileName)) + val w = new OutputStreamWriter(out, "UTF-8") + w.write(jsFileContent) + w.flush() + + out.close() + + new ByteArrayInputStream(outBuf.toByteArray) + } + + /** Trivial FS that has only one jar */ + trait TrivialFS extends FileSystem { + // Keep imports local so we don't accidentally use something we shouldn't + import java.io.{InputStream, Reader} + import scala.scalajs.tools.io._ + import scala.collection.immutable.Traversable + + type File = Unit + + val DummyVersion: String = "DUMMY" + + def isDirectory(f: File): Boolean = false + def isFile(f: File): Boolean = true + def isJSFile(f: File): Boolean = false + def isIRFile(f: File): Boolean = false + def isJARFile(f: File): Boolean = true + def exists(f: File): Boolean = true + + def getName(f: File): String = "jar" + def getAbsolutePath(f: File): String = "jar" + def getVersion(f: File): String = "" + + def listFiles(d: File): Traversable[File] = ??? + + def toJSFile(f: File): VirtualJSFile = ??? + def toIRFile(f: File): VirtualScalaJSIRFile = ??? + def toReader(f: File): Reader = ??? + def toInputStream(f: File): InputStream = getJARInputStream + } + + class TestJARBuilder extends AbstractJarLibClasspathBuilder with TrivialFS + + @Test + def readInMemoryJarClasspath { + val builder = new TestJARBuilder + val cp = builder.build(Seq(())) + + assertArrayEquals( + Array[Object](jsFileName -> jsFileContent), + cp.availableLibs.mapValues(_.content).toArray[Object]) + } + +} diff --git a/examples/scala-js/tools/scalajsenv.js b/examples/scala-js/tools/scalajsenv.js new file mode 100644 index 0000000..01a84ba --- /dev/null +++ b/examples/scala-js/tools/scalajsenv.js @@ -0,0 +1,772 @@ +/* Scala.js runtime support + * Copyright 2013 LAMP/EPFL + * Author: Sébastien Doeraene + */ + +/* ---------------------------------- * + * The top-level Scala.js environment * + * ---------------------------------- */ + +var ScalaJS = {}; + +// Get the environment info +ScalaJS.env = (typeof __ScalaJSEnv === "object" && __ScalaJSEnv) ? __ScalaJSEnv : {}; + +// Global scope +ScalaJS.g = + (typeof ScalaJS.env["global"] === "object" && ScalaJS.env["global"]) + ? ScalaJS.env["global"] + : ((typeof global === "object" && global && global["Object"] === Object) ? global : this); +ScalaJS.env["global"] = ScalaJS.g; + +// Where to send exports +ScalaJS.e = + (typeof ScalaJS.env["exportsNamespace"] === "object" && ScalaJS.env["exportsNamespace"]) + ? ScalaJS.env["exportsNamespace"] : ScalaJS.g; +ScalaJS.env["exportsNamespace"] = ScalaJS.e; + +// Freeze the environment info +ScalaJS.g["Object"]["freeze"](ScalaJS.env); + +// Other fields +ScalaJS.d = {}; // Data for types +ScalaJS.c = {}; // Scala.js constructors +ScalaJS.h = {}; // Inheritable constructors (without initialization code) +ScalaJS.i = {}; // Implementation class modules +ScalaJS.n = {}; // Module instances +ScalaJS.m = {}; // Module accessors +ScalaJS.is = {}; // isInstanceOf methods +ScalaJS.isArrayOf = {}; // isInstanceOfArrayOf methods +//!if asInstanceOfs != Unchecked +ScalaJS.as = {}; // asInstanceOf methods +ScalaJS.asArrayOf = {}; // asInstanceOfArrayOf methods +//!endif +ScalaJS.lastIDHash = 0; // last value attributed to an id hash code + +// Core mechanism + +ScalaJS.makeIsArrayOfPrimitive = function(primitiveData) { + return function(obj, depth) { + return !!(obj && obj.$classData && + (obj.$classData.arrayDepth === depth) && + (obj.$classData.arrayBase === primitiveData)); + } +}; + +//!if asInstanceOfs != Unchecked +ScalaJS.makeAsArrayOfPrimitive = function(isInstanceOfFunction, arrayEncodedName) { + return function(obj, depth) { + if (isInstanceOfFunction(obj, depth) || (obj === null)) + return obj; + else + ScalaJS.throwArrayCastException(obj, arrayEncodedName, depth); + } +}; +//!endif + +/** Encode a property name for runtime manipulation + * Usage: + * env.propertyName({someProp:0}) + * Returns: + * "someProp" + * Useful when the property is renamed by a global optimizer (like Closure) + * but we must still get hold of a string of that name for runtime + * reflection. + */ +ScalaJS.propertyName = function(obj) { + var result; + for (var prop in obj) + result = prop; + return result; +}; + +// Runtime functions + +ScalaJS.isScalaJSObject = function(obj) { + return !!(obj && obj.$classData); +}; + +//!if asInstanceOfs != Unchecked +ScalaJS.throwClassCastException = function(instance, classFullName) { +//!if asInstanceOfs == Compliant + throw new ScalaJS.c.jl_ClassCastException().init___T( + instance + " is not an instance of " + classFullName); +//!else + throw new ScalaJS.c.sjsr_UndefinedBehaviorError().init___jl_Throwable( + new ScalaJS.c.jl_ClassCastException().init___T( + instance + " is not an instance of " + classFullName)); +//!endif +}; + +ScalaJS.throwArrayCastException = function(instance, classArrayEncodedName, depth) { + for (; depth; --depth) + classArrayEncodedName = "[" + classArrayEncodedName; + ScalaJS.throwClassCastException(instance, classArrayEncodedName); +}; +//!endif + +ScalaJS.makeNativeArrayWrapper = function(arrayClassData, nativeArray) { + return new arrayClassData.constr(nativeArray); +}; + +ScalaJS.newArrayObject = function(arrayClassData, lengths) { + return ScalaJS.newArrayObjectInternal(arrayClassData, lengths, 0); +}; + +ScalaJS.newArrayObjectInternal = function(arrayClassData, lengths, lengthIndex) { + var result = new arrayClassData.constr(lengths[lengthIndex]); + + if (lengthIndex < lengths.length-1) { + var subArrayClassData = arrayClassData.componentData; + var subLengthIndex = lengthIndex+1; + var underlying = result.u; + for (var i = 0; i < underlying.length; i++) { + underlying[i] = ScalaJS.newArrayObjectInternal( + subArrayClassData, lengths, subLengthIndex); + } + } + + return result; +}; + +ScalaJS.checkNonNull = function(obj) { + return obj !== null ? obj : ScalaJS.throwNullPointerException(); +}; + +ScalaJS.throwNullPointerException = function() { + throw new ScalaJS.c.jl_NullPointerException().init___(); +}; + +ScalaJS.objectToString = function(instance) { + if (instance === void 0) + return "undefined"; + else + return instance.toString(); +}; + +ScalaJS.objectGetClass = function(instance) { + switch (typeof instance) { + case "string": + return ScalaJS.d.T.getClassOf(); + case "number": + var v = instance | 0; + if (v === instance) { // is the value integral? + if (ScalaJS.isByte(v)) + return ScalaJS.d.jl_Byte.getClassOf(); + else if (ScalaJS.isShort(v)) + return ScalaJS.d.jl_Short.getClassOf(); + else + return ScalaJS.d.jl_Integer.getClassOf(); + } else { + if (ScalaJS.isFloat(instance)) + return ScalaJS.d.jl_Float.getClassOf(); + else + return ScalaJS.d.jl_Double.getClassOf(); + } + case "boolean": + return ScalaJS.d.jl_Boolean.getClassOf(); + case "undefined": + return ScalaJS.d.sr_BoxedUnit.getClassOf(); + default: + if (instance === null) + ScalaJS.throwNullPointerException(); + else if (ScalaJS.is.sjsr_RuntimeLong(instance)) + return ScalaJS.d.jl_Long.getClassOf(); + else if (ScalaJS.isScalaJSObject(instance)) + return instance.$classData.getClassOf(); + else + return null; // Exception? + } +}; + +ScalaJS.objectClone = function(instance) { + if (ScalaJS.isScalaJSObject(instance) || (instance === null)) + return instance.clone__O(); + else + throw new ScalaJS.c.jl_CloneNotSupportedException().init___(); +}; + +ScalaJS.objectNotify = function(instance) { + // final and no-op in java.lang.Object + if (instance === null) + instance.notify__V(); +}; + +ScalaJS.objectNotifyAll = function(instance) { + // final and no-op in java.lang.Object + if (instance === null) + instance.notifyAll__V(); +}; + +ScalaJS.objectFinalize = function(instance) { + if (ScalaJS.isScalaJSObject(instance) || (instance === null)) + instance.finalize__V(); + // else no-op +}; + +ScalaJS.objectEquals = function(instance, rhs) { + if (ScalaJS.isScalaJSObject(instance) || (instance === null)) + return instance.equals__O__Z(rhs); + else if (typeof instance === "number") + return typeof rhs === "number" && ScalaJS.numberEquals(instance, rhs); + else + return instance === rhs; +}; + +ScalaJS.numberEquals = function(lhs, rhs) { + return ( + lhs === rhs // 0.0 === -0.0 to prioritize the Int case over the Double case + ) || ( + // are they both NaN? + (lhs !== lhs) && (rhs !== rhs) + ); +}; + +ScalaJS.objectHashCode = function(instance) { + switch (typeof instance) { + case "string": + return ScalaJS.m.sjsr_RuntimeString().hashCode__T__I(instance); + case "number": + return ScalaJS.m.sjsr_Bits().numberHashCode__D__I(instance); + case "boolean": + return instance ? 1231 : 1237; + case "undefined": + return 0; + default: + if (ScalaJS.isScalaJSObject(instance) || instance === null) + return instance.hashCode__I(); + else + return 42; // TODO? + } +}; + +ScalaJS.comparableCompareTo = function(instance, rhs) { + switch (typeof instance) { + case "string": +//!if asInstanceOfs != Unchecked + ScalaJS.as.T(rhs); +//!endif + return instance === rhs ? 0 : (instance < rhs ? -1 : 1); + case "number": +//!if asInstanceOfs != Unchecked + ScalaJS.as.jl_Number(rhs); +//!endif + return ScalaJS.numberEquals(instance, rhs) ? 0 : (instance < rhs ? -1 : 1); + case "boolean": +//!if asInstanceOfs != Unchecked + ScalaJS.asBoolean(rhs); +//!endif + return instance - rhs; // yes, this gives the right result + default: + return instance.compareTo__O__I(rhs); + } +}; + +ScalaJS.charSequenceLength = function(instance) { + if (typeof(instance) === "string") +//!if asInstanceOfs != Unchecked + return ScalaJS.uI(instance["length"]); +//!else + return instance["length"] | 0; +//!endif + else + return instance.length__I(); +}; + +ScalaJS.charSequenceCharAt = function(instance, index) { + if (typeof(instance) === "string") +//!if asInstanceOfs != Unchecked + return ScalaJS.uI(instance["charCodeAt"](index)) & 0xffff; +//!else + return instance["charCodeAt"](index) & 0xffff; +//!endif + else + return instance.charAt__I__C(index); +}; + +ScalaJS.charSequenceSubSequence = function(instance, start, end) { + if (typeof(instance) === "string") +//!if asInstanceOfs != Unchecked + return ScalaJS.as.T(instance["substring"](start, end)); +//!else + return instance["substring"](start, end); +//!endif + else + return instance.subSequence__I__I__jl_CharSequence(start, end); +}; + +ScalaJS.booleanBooleanValue = function(instance) { + if (typeof instance === "boolean") return instance; + else return instance.booleanValue__Z(); +}; + +ScalaJS.numberByteValue = function(instance) { + if (typeof instance === "number") return (instance << 24) >> 24; + else return instance.byteValue__B(); +}; +ScalaJS.numberShortValue = function(instance) { + if (typeof instance === "number") return (instance << 16) >> 16; + else return instance.shortValue__S(); +}; +ScalaJS.numberIntValue = function(instance) { + if (typeof instance === "number") return instance | 0; + else return instance.intValue__I(); +}; +ScalaJS.numberLongValue = function(instance) { + if (typeof instance === "number") + return ScalaJS.m.sjsr_RuntimeLong().fromDouble__D__sjsr_RuntimeLong(instance); + else + return instance.longValue__J(); +}; +ScalaJS.numberFloatValue = function(instance) { + if (typeof instance === "number") return ScalaJS.fround(instance); + else return instance.floatValue__F(); +}; +ScalaJS.numberDoubleValue = function(instance) { + if (typeof instance === "number") return instance; + else return instance.doubleValue__D(); +}; + +ScalaJS.isNaN = function(instance) { + return instance !== instance; +}; + +ScalaJS.isInfinite = function(instance) { + return !ScalaJS.g["isFinite"](instance) && !ScalaJS.isNaN(instance); +}; + +ScalaJS.propertiesOf = function(obj) { + var result = []; + for (var prop in obj) + result["push"](prop); + return result; +}; + +ScalaJS.systemArraycopy = function(src, srcPos, dest, destPos, length) { + var srcu = src.u; + var destu = dest.u; + if (srcu !== destu || destPos < srcPos || srcPos + length < destPos) { + for (var i = 0; i < length; i++) + destu[destPos+i] = srcu[srcPos+i]; + } else { + for (var i = length-1; i >= 0; i--) + destu[destPos+i] = srcu[srcPos+i]; + } +}; + +ScalaJS.systemIdentityHashCode = function(obj) { + if (ScalaJS.isScalaJSObject(obj)) { + var hash = obj["$idHashCode$0"]; + if (hash !== void 0) { + return hash; + } else { + hash = (ScalaJS.lastIDHash + 1) | 0; + ScalaJS.lastIDHash = hash; + obj["$idHashCode$0"] = hash; + return hash; + } + } else if (obj === null) { + return 0; + } else { + return ScalaJS.objectHashCode(obj); + } +}; + +// is/as for hijacked boxed classes (the non-trivial ones) + +ScalaJS.isByte = function(v) { + return (v << 24 >> 24) === v; +}; + +ScalaJS.isShort = function(v) { + return (v << 16 >> 16) === v; +}; + +ScalaJS.isInt = function(v) { + return (v | 0) === v; +}; + +ScalaJS.isFloat = function(v) { + return v !== v || ScalaJS.fround(v) === v; +}; + +//!if asInstanceOfs != Unchecked +ScalaJS.asUnit = function(v) { + if (v === void 0) + return v; + else + ScalaJS.throwClassCastException(v, "scala.runtime.BoxedUnit"); +}; + +ScalaJS.asBoolean = function(v) { + if (typeof v === "boolean" || v === null) + return v; + else + ScalaJS.throwClassCastException(v, "java.lang.Boolean"); +}; + +ScalaJS.asByte = function(v) { + if (ScalaJS.isByte(v) || v === null) + return v; + else + ScalaJS.throwClassCastException(v, "java.lang.Byte"); +}; + +ScalaJS.asShort = function(v) { + if (ScalaJS.isShort(v) || v === null) + return v; + else + ScalaJS.throwClassCastException(v, "java.lang.Short"); +}; + +ScalaJS.asInt = function(v) { + if (ScalaJS.isInt(v) || v === null) + return v; + else + ScalaJS.throwClassCastException(v, "java.lang.Integer"); +}; + +ScalaJS.asFloat = function(v) { + if (ScalaJS.isFloat(v) || v === null) + return v; + else + ScalaJS.throwClassCastException(v, "java.lang.Float"); +}; + +ScalaJS.asDouble = function(v) { + if (typeof v === "number" || v === null) + return v; + else + ScalaJS.throwClassCastException(v, "java.lang.Double"); +}; +//!endif + +// Unboxes + +//!if asInstanceOfs != Unchecked +ScalaJS.uZ = function(value) { + return !!ScalaJS.asBoolean(value); +}; +ScalaJS.uB = function(value) { + return ScalaJS.asByte(value) | 0; +}; +ScalaJS.uS = function(value) { + return ScalaJS.asShort(value) | 0; +}; +ScalaJS.uI = function(value) { + return ScalaJS.asInt(value) | 0; +}; +ScalaJS.uJ = function(value) { + return null === value ? ScalaJS.m.sjsr_RuntimeLong().Zero$1 + : ScalaJS.as.sjsr_RuntimeLong(value); +}; +ScalaJS.uF = function(value) { + /* Here, it is fine to use + instead of fround, because asFloat already + * ensures that the result is either null or a float. + */ + return +ScalaJS.asFloat(value); +}; +ScalaJS.uD = function(value) { + return +ScalaJS.asDouble(value); +}; +//!else +ScalaJS.uJ = function(value) { + return null === value ? ScalaJS.m.sjsr_RuntimeLong().Zero$1 : value; +}; +//!endif + +// TypeArray conversions + +ScalaJS.byteArray2TypedArray = function(value) { return new Int8Array(value.u); }; +ScalaJS.shortArray2TypedArray = function(value) { return new Int16Array(value.u); }; +ScalaJS.charArray2TypedArray = function(value) { return new Uint16Array(value.u); }; +ScalaJS.intArray2TypedArray = function(value) { return new Int32Array(value.u); }; +ScalaJS.floatArray2TypedArray = function(value) { return new Float32Array(value.u); }; +ScalaJS.doubleArray2TypedArray = function(value) { return new Float64Array(value.u); }; + +ScalaJS.typedArray2ByteArray = function(value) { + var arrayClassData = ScalaJS.d.B.getArrayOf(); + return new arrayClassData.constr(new Int8Array(value)); +}; +ScalaJS.typedArray2ShortArray = function(value) { + var arrayClassData = ScalaJS.d.S.getArrayOf(); + return new arrayClassData.constr(new Int16Array(value)); +}; +ScalaJS.typedArray2CharArray = function(value) { + var arrayClassData = ScalaJS.d.C.getArrayOf(); + return new arrayClassData.constr(new Uint16Array(value)); +}; +ScalaJS.typedArray2IntArray = function(value) { + var arrayClassData = ScalaJS.d.I.getArrayOf(); + return new arrayClassData.constr(new Int32Array(value)); +}; +ScalaJS.typedArray2FloatArray = function(value) { + var arrayClassData = ScalaJS.d.F.getArrayOf(); + return new arrayClassData.constr(new Float32Array(value)); +}; +ScalaJS.typedArray2DoubleArray = function(value) { + var arrayClassData = ScalaJS.d.D.getArrayOf(); + return new arrayClassData.constr(new Float64Array(value)); +}; + +/* We have to force a non-elidable *read* of ScalaJS.e, otherwise Closure will + * eliminate it altogether, along with all the exports, which is ... er ... + * plain wrong. + */ +this["__ScalaJSExportsNamespace"] = ScalaJS.e; + +// Type data constructors + +/** @constructor */ +ScalaJS.PrimitiveTypeData = function(zero, arrayEncodedName, displayName) { + // Runtime support + this.constr = undefined; + this.parentData = undefined; + this.ancestors = {}; + this.componentData = null; + this.zero = zero; + this.arrayEncodedName = arrayEncodedName; + this._classOf = undefined; + this._arrayOf = undefined; + this.isArrayOf = function(obj, depth) { return false; }; + + // java.lang.Class support + this["name"] = displayName; + this["isPrimitive"] = true; + this["isInterface"] = false; + this["isArrayClass"] = false; + this["isInstance"] = function(obj) { return false; }; +}; + +/** @constructor */ +ScalaJS.ClassTypeData = function(internalNameObj, isInterface, fullName, + parentData, ancestors, isInstance, isArrayOf) { + var internalName = ScalaJS.propertyName(internalNameObj); + + isInstance = isInstance || function(obj) { + return !!(obj && obj.$classData && obj.$classData.ancestors[internalName]); + }; + + isArrayOf = isArrayOf || function(obj, depth) { + return !!(obj && obj.$classData && (obj.$classData.arrayDepth === depth) + && obj.$classData.arrayBase.ancestors[internalName]) + }; + + // Runtime support + this.constr = undefined; + this.parentData = parentData; + this.ancestors = ancestors; + this.componentData = null; + this.zero = null; + this.arrayEncodedName = "L"+fullName+";"; + this._classOf = undefined; + this._arrayOf = undefined; + this.isArrayOf = isArrayOf; + + // java.lang.Class support + this["name"] = fullName; + this["isPrimitive"] = false; + this["isInterface"] = isInterface; + this["isArrayClass"] = false; + this["isInstance"] = isInstance; +}; + +/** @constructor */ +ScalaJS.ArrayTypeData = function(componentData) { + // The constructor + + var componentZero = componentData.zero; + + // The zero for the Long runtime representation + // is a special case here, since the class has not + // been defined yet, when this file is read + if (componentZero == "longZero") + componentZero = ScalaJS.m.sjsr_RuntimeLong().Zero$1; + + /** @constructor */ + var ArrayClass = function(arg) { + if (typeof(arg) === "number") { + // arg is the length of the array + this.u = new Array(arg); + for (var i = 0; i < arg; i++) + this.u[i] = componentZero; + } else { + // arg is a native array that we wrap + this.u = arg; + } + } + ArrayClass.prototype = new ScalaJS.h.O; + ArrayClass.prototype.constructor = ArrayClass; + ArrayClass.prototype.$classData = this; + + ArrayClass.prototype.clone__O = function() { + if (this.u instanceof Array) + return new ArrayClass(this.u["slice"](0)); + else + // The underlying Array is a TypedArray + return new ArrayClass(this.u.constructor(this.u)); + }; + + // Don't generate reflective call proxies. The compiler special cases + // reflective calls to methods on scala.Array + + // The data + + var encodedName = "[" + componentData.arrayEncodedName; + var componentBase = componentData.arrayBase || componentData; + var componentDepth = componentData.arrayDepth || 0; + var arrayDepth = componentDepth + 1; + + var isInstance = function(obj) { + return componentBase.isArrayOf(obj, arrayDepth); + } + + // Runtime support + this.constr = ArrayClass; + this.parentData = ScalaJS.d.O; + this.ancestors = {O: 1}; + this.componentData = componentData; + this.arrayBase = componentBase; + this.arrayDepth = arrayDepth; + this.zero = null; + this.arrayEncodedName = encodedName; + this._classOf = undefined; + this._arrayOf = undefined; + this.isArrayOf = undefined; + + // java.lang.Class support + this["name"] = encodedName; + this["isPrimitive"] = false; + this["isInterface"] = false; + this["isArrayClass"] = true; + this["isInstance"] = isInstance; +}; + +ScalaJS.ClassTypeData.prototype.getClassOf = function() { + if (!this._classOf) + this._classOf = new ScalaJS.c.jl_Class().init___jl_ScalaJSClassData(this); + return this._classOf; +}; + +ScalaJS.ClassTypeData.prototype.getArrayOf = function() { + if (!this._arrayOf) + this._arrayOf = new ScalaJS.ArrayTypeData(this); + return this._arrayOf; +}; + +// java.lang.Class support + +ScalaJS.ClassTypeData.prototype["getFakeInstance"] = function() { + if (this === ScalaJS.d.T) + return "some string"; + else if (this === ScalaJS.d.jl_Boolean) + return false; + else if (this === ScalaJS.d.jl_Byte || + this === ScalaJS.d.jl_Short || + this === ScalaJS.d.jl_Integer || + this === ScalaJS.d.jl_Float || + this === ScalaJS.d.jl_Double) + return 0; + else if (this === ScalaJS.d.jl_Long) + return ScalaJS.m.sjsr_RuntimeLong().Zero$1; + else if (this === ScalaJS.d.sr_BoxedUnit) + return void 0; + else + return {$classData: this}; +}; + +ScalaJS.ClassTypeData.prototype["getSuperclass"] = function() { + return this.parentData ? this.parentData.getClassOf() : null; +}; + +ScalaJS.ClassTypeData.prototype["getComponentType"] = function() { + return this.componentData ? this.componentData.getClassOf() : null; +}; + +ScalaJS.ClassTypeData.prototype["newArrayOfThisClass"] = function(lengths) { + var arrayClassData = this; + for (var i = 0; i < lengths.length; i++) + arrayClassData = arrayClassData.getArrayOf(); + return ScalaJS.newArrayObject(arrayClassData, lengths); +}; + +ScalaJS.PrimitiveTypeData.prototype = ScalaJS.ClassTypeData.prototype; +ScalaJS.ArrayTypeData.prototype = ScalaJS.ClassTypeData.prototype; + +// Create primitive types + +ScalaJS.d.V = new ScalaJS.PrimitiveTypeData(undefined, "V", "void"); +ScalaJS.d.Z = new ScalaJS.PrimitiveTypeData(false, "Z", "boolean"); +ScalaJS.d.C = new ScalaJS.PrimitiveTypeData(0, "C", "char"); +ScalaJS.d.B = new ScalaJS.PrimitiveTypeData(0, "B", "byte"); +ScalaJS.d.S = new ScalaJS.PrimitiveTypeData(0, "S", "short"); +ScalaJS.d.I = new ScalaJS.PrimitiveTypeData(0, "I", "int"); +ScalaJS.d.J = new ScalaJS.PrimitiveTypeData("longZero", "J", "long"); +ScalaJS.d.F = new ScalaJS.PrimitiveTypeData(0.0, "F", "float"); +ScalaJS.d.D = new ScalaJS.PrimitiveTypeData(0.0, "D", "double"); + +// Instance tests for array of primitives + +ScalaJS.isArrayOf.Z = ScalaJS.makeIsArrayOfPrimitive(ScalaJS.d.Z); +ScalaJS.d.Z.isArrayOf = ScalaJS.isArrayOf.Z; + +ScalaJS.isArrayOf.C = ScalaJS.makeIsArrayOfPrimitive(ScalaJS.d.C); +ScalaJS.d.C.isArrayOf = ScalaJS.isArrayOf.C; + +ScalaJS.isArrayOf.B = ScalaJS.makeIsArrayOfPrimitive(ScalaJS.d.B); +ScalaJS.d.B.isArrayOf = ScalaJS.isArrayOf.B; + +ScalaJS.isArrayOf.S = ScalaJS.makeIsArrayOfPrimitive(ScalaJS.d.S); +ScalaJS.d.S.isArrayOf = ScalaJS.isArrayOf.S; + +ScalaJS.isArrayOf.I = ScalaJS.makeIsArrayOfPrimitive(ScalaJS.d.I); +ScalaJS.d.I.isArrayOf = ScalaJS.isArrayOf.I; + +ScalaJS.isArrayOf.J = ScalaJS.makeIsArrayOfPrimitive(ScalaJS.d.J); +ScalaJS.d.J.isArrayOf = ScalaJS.isArrayOf.J; + +ScalaJS.isArrayOf.F = ScalaJS.makeIsArrayOfPrimitive(ScalaJS.d.F); +ScalaJS.d.F.isArrayOf = ScalaJS.isArrayOf.F; + +ScalaJS.isArrayOf.D = ScalaJS.makeIsArrayOfPrimitive(ScalaJS.d.D); +ScalaJS.d.D.isArrayOf = ScalaJS.isArrayOf.D; + +//!if asInstanceOfs != Unchecked +// asInstanceOfs for array of primitives +ScalaJS.asArrayOf.Z = ScalaJS.makeAsArrayOfPrimitive(ScalaJS.isArrayOf.Z, "Z"); +ScalaJS.asArrayOf.C = ScalaJS.makeAsArrayOfPrimitive(ScalaJS.isArrayOf.C, "C"); +ScalaJS.asArrayOf.B = ScalaJS.makeAsArrayOfPrimitive(ScalaJS.isArrayOf.B, "B"); +ScalaJS.asArrayOf.S = ScalaJS.makeAsArrayOfPrimitive(ScalaJS.isArrayOf.S, "S"); +ScalaJS.asArrayOf.I = ScalaJS.makeAsArrayOfPrimitive(ScalaJS.isArrayOf.I, "I"); +ScalaJS.asArrayOf.J = ScalaJS.makeAsArrayOfPrimitive(ScalaJS.isArrayOf.J, "J"); +ScalaJS.asArrayOf.F = ScalaJS.makeAsArrayOfPrimitive(ScalaJS.isArrayOf.F, "F"); +ScalaJS.asArrayOf.D = ScalaJS.makeAsArrayOfPrimitive(ScalaJS.isArrayOf.D, "D"); +//!endif + +// Polyfills + +ScalaJS.imul = ScalaJS.g["Math"]["imul"] || (function(a, b) { + // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/imul + var ah = (a >>> 16) & 0xffff; + var al = a & 0xffff; + var bh = (b >>> 16) & 0xffff; + var bl = b & 0xffff; + // the shift by 0 fixes the sign on the high part + // the final |0 converts the unsigned value into a signed value + return ((al * bl) + (((ah * bl + al * bh) << 16) >>> 0) | 0); +}); + +ScalaJS.fround = ScalaJS.g["Math"]["fround"] || +//!if floats == Strict + (ScalaJS.g["Float32Array"] ? (function(v) { + var array = new ScalaJS.g["Float32Array"](1); + array[0] = v; + return array[0]; + }) : (function(v) { + return ScalaJS.m.sjsr_package().froundPolyfill__D__D(+v); + })); +//!else + (function(v) { + return +v; + }); +//!endif diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/CompleteClasspath.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/CompleteClasspath.scala new file mode 100644 index 0000000..6646a7b --- /dev/null +++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/CompleteClasspath.scala @@ -0,0 +1,35 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js tools ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.tools.classpath + +import scala.collection.immutable.Seq + +import scala.scalajs.tools.io.VirtualJSFile +import scala.scalajs.tools.jsdep.ResolutionInfo + +/** A classpath where nothing is missing. Therefore: + * - All JS libraries are resolved and ordered + * - The CoreJSLibs are present + * - Nothing can be added anymore + */ +abstract class CompleteClasspath( + /** Resolved JS libraries */ + val jsLibs: Seq[ResolvedJSDependency], + val requiresDOM: Boolean, + val version: Option[String] +) { + + /** Fully linked Scala.js code */ + def scalaJSCode: VirtualJSFile + + /** All code in this complete classpath */ + final def allCode: Seq[VirtualJSFile] = jsLibs.map(_.lib) :+ scalaJSCode + +} diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/ComplianceRequirement.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/ComplianceRequirement.scala new file mode 100644 index 0000000..f6ec36f --- /dev/null +++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/ComplianceRequirement.scala @@ -0,0 +1,7 @@ +package scala.scalajs.tools.classpath + +import scala.scalajs.tools.jsdep.Origin + +/** Expresses a requirement for a given semantic to be compliant */ +final class ComplianceRequirement( + val semantics: String, val origins: List[Origin]) diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/Exceptions.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/Exceptions.scala new file mode 100644 index 0000000..62bf75f --- /dev/null +++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/Exceptions.scala @@ -0,0 +1,42 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js tools ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.tools.classpath + +import scala.scalajs.tools.jsdep.ResolutionInfo + +class MissingJSLibException(val dependencies: List[ResolutionInfo]) + extends Exception(MissingJSLibException.mkMsg(dependencies)) + +object MissingJSLibException { + private def mkMsg(deps: List[ResolutionInfo]): String = { + val msg = new StringBuilder() + msg.append("Missing dependencies: \n") + for (d <- deps) { + msg.append(s"- ${d.resourceName}") + msg.append(s" originating from: ${d.origins.mkString(", ")}\n") + } + msg.toString() + } +} + +class BadComplianceException(val unmet: List[ComplianceRequirement]) + extends Exception(BadComplianceException.mkMsg(unmet)) + +object BadComplianceException { + private def mkMsg(unmets: List[ComplianceRequirement]): String = { + val msg = new StringBuilder() + msg.append("Unmet required semantic compliance(s): \n") + for (unmet <- unmets) { + msg.append(s"- ${unmet.semantics}") + msg.append(s" originating from: ${unmet.origins.mkString(", ")}\n") + } + msg.toString + } +} diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/IRClasspath.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/IRClasspath.scala new file mode 100644 index 0000000..a92293b --- /dev/null +++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/IRClasspath.scala @@ -0,0 +1,69 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js tools ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.tools.classpath + +import scala.collection.immutable.{Seq, Traversable} + +import scala.scalajs.tools.sem.Semantics +import scala.scalajs.tools.io._ +import scala.scalajs.tools.logging._ +import scala.scalajs.tools.optimizer.ScalaJSOptimizer +import scala.scalajs.tools.jsdep.ResolutionInfo + +/** A [[CompleteClasspath]] that contains only IR as scalaJSCode */ +final class IRClasspath( + /** The JS libraries the IR code depends on */ + jsLibs: Seq[ResolvedJSDependency], + val requiredCompliance: Traversable[ComplianceRequirement], + /** The IR itself. Ancestor count is used for later ordering */ + val scalaJSIR: Traversable[VirtualScalaJSIRFile], + requiresDOM: Boolean, + version: Option[String] +) extends CompleteClasspath(jsLibs, requiresDOM, version) { + + /** Orders and optimizes the contained IR. + * + * Consider using [[ScalaJSOptimizer]] for a canonical way to do so. It + * allows to persist the resulting file and create a source map, as well as + * using non-default [[Semantics]]. + */ + override lazy val scalaJSCode: VirtualJSFile = { + import ScalaJSOptimizer._ + + val outName = "temporary-fastOpt.js" + + if (scalaJSIR.nonEmpty) { + val semantics = Semantics.compliantTo(requiredCompliance.map(_.semantics)) + val output = WritableMemVirtualJSFile(outName) + new ScalaJSOptimizer(semantics).optimizeCP( + Inputs(this), + OutputConfig(output), + NullLogger) + output + } else { + // We cannot run the optimizer without IR, because it will complain about + // java.lang.Object missing. However, an empty JS file is perfectly valid + // for no IR at all. + VirtualJSFile.empty(outName) + } + } + + /** Checks whether the given semantics are compliant with the requirements of + * this CompleteClasspath. Throws an exception otherwise. + */ + final def checkCompliance(semantics: Semantics): Unit = { + val unmet = requiredCompliance filterNot { compliance => + semantics.isCompliant(compliance.semantics) + } + + if (unmet.nonEmpty) + throw new BadComplianceException(unmet.toList) + } +} diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/LinkedClasspath.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/LinkedClasspath.scala new file mode 100644 index 0000000..3ace785 --- /dev/null +++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/LinkedClasspath.scala @@ -0,0 +1,26 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js tools ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.tools.classpath + +import scala.scalajs.tools.io.VirtualJSFile +import scala.scalajs.tools.jsdep.ResolutionInfo + +import scala.collection.immutable.Seq + +/** A [[CompleteClasspath]] that is fully linked (either with the + * [[ScalaJSOptimizer]] or the Closure Optimizer. It contains only a single + * file that is scalaJSCode. + */ +final class LinkedClasspath( + jsLibs: Seq[ResolvedJSDependency], + val scalaJSCode: VirtualJSFile, + requiresDOM: Boolean, + version: Option[String] +) extends CompleteClasspath(jsLibs, requiresDOM, version) diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/PartialClasspath.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/PartialClasspath.scala new file mode 100644 index 0000000..949cd6e --- /dev/null +++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/PartialClasspath.scala @@ -0,0 +1,99 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js tools ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.tools.classpath + +import scala.collection.immutable.{Seq, Traversable} + +import scala.scalajs.tools.jsdep._ +import scala.scalajs.tools.io._ +import scala.scalajs.tools.corelib.CoreJSLibs + +/** A partial Scala.js classpath is a collection of: + * - Scala.js binary files *.sjsir + * - Native JavaScript libraries + * - Description of dependencies on other JavaScript libraries + * + * PartialClasspaths can be combined (using [[merge]]) and eventually resolved + * to a [[CompleteIRClasspath]] + */ +final class PartialClasspath( + /** Description of JS libraries the content of this classpath depends on */ + val dependencies: Traversable[JSDependencyManifest], + /** JS libraries this partial classpath provides */ + val availableLibs: Map[String, VirtualJSFile], + /** Scala.js IR contained in this PartialClasspath (unordered) */ + val scalaJSIR: Traversable[VirtualScalaJSIRFile], + val version: Option[String] +) { + import PartialClasspath.DependencyFilter + + /** Merges another [[PartialClasspath]] with this one. This means: + * - Concatenate/merge dependencies + * - Merge availableLibs (libs in that shadow libs in this) + * - Merge Scala.js IR + */ + def merge(that: PartialClasspath): PartialClasspath = { + new PartialClasspath( + this.dependencies ++ that.dependencies, + this.availableLibs ++ that.availableLibs, + this.scalaJSIR ++ that.scalaJSIR, + CacheUtils.joinVersions(this.version, that.version)) + } + + /** Construct a [[IRClasspath]] out of this [[PartialClasspath]] by + * resolving library dependencies (and failing if they are not met) + */ + def resolve(filter: DependencyFilter = identity): IRClasspath = { + new IRClasspath(resolveDependencies(filter), mergeCompliance(), scalaJSIR, + dependencies.exists(_.requiresDOM), version) + } + + /** Constructs an ordered list of JS libraries to include. Fails if: + * - Dependencies have cycles + * - Not all dependencies are available + */ + protected def resolveDependencies( + filter: DependencyFilter): List[ResolvedJSDependency] = { + val flatDeps = filter(dependencies.flatMap(_.flatten)) + val includeList = JSDependencyManifest.createIncludeList(flatDeps) + + val missingDeps = includeList.filterNot { info => + availableLibs.contains(info.resourceName) + } + + if (missingDeps.nonEmpty) + throw new MissingJSLibException(missingDeps) + + for (info <- includeList) + yield new ResolvedJSDependency(availableLibs(info.resourceName), info) + } + + protected def mergeCompliance(): Traversable[ComplianceRequirement] = { + val flatTups = for { + dependency <- dependencies + semantics <- dependency.compliantSemantics + } yield (semantics, dependency.origin) + + for { + (semantics, tups) <- flatTups.groupBy(_._1) + } yield new ComplianceRequirement(semantics, tups.map(_._2).toList) + } + +} + +object PartialClasspath { + + type DependencyFilter = + Traversable[FlatJSDependency] => Traversable[FlatJSDependency] + + /** Creates an empty PartialClasspath */ + def empty: PartialClasspath = + new PartialClasspath(Nil, Map.empty, Nil, Some("")) +} diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/ResolvedJSDependency.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/ResolvedJSDependency.scala new file mode 100644 index 0000000..12ae8dc --- /dev/null +++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/ResolvedJSDependency.scala @@ -0,0 +1,10 @@ +package scala.scalajs.tools.classpath + +import scala.scalajs.tools.io._ +import scala.scalajs.tools.jsdep._ + +/** A dependency on a native JavaScript library that has been successfully + * resolved + */ +final class ResolvedJSDependency( + val lib: VirtualJSFile, val info: ResolutionInfo) diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/AbstractJarLibClasspathBuilder.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/AbstractJarLibClasspathBuilder.scala new file mode 100644 index 0000000..77f8509 --- /dev/null +++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/AbstractJarLibClasspathBuilder.scala @@ -0,0 +1,53 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js tools ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.tools.classpath.builder + +import scala.collection.mutable + +import scala.scalajs.tools.io._ +import scala.scalajs.tools.jsdep.JSDependencyManifest +import scala.scalajs.tools.classpath._ + +/** reads a ScalaJS library JAR into a CP + * - IR files go to scalaJSCode + * - JS files go to availableLibs + * - Reads a potential top-level JS_DEPENDENCIES file + */ +trait AbstractJarLibClasspathBuilder extends JarTraverser { + + private val irFiles = mutable.ListBuffer.empty[VirtualScalaJSIRFile] + private val jsFiles = mutable.Map.empty[String, VirtualJSFile] + private var dependency: Option[JSDependencyManifest] = None + + def build(jar: File): PartialClasspath = { + val v = traverseJar(jar) + new PartialClasspath(dependency.toList, + jsFiles.toMap, irFiles.toList, Some(v)) + } + + override protected def handleIR(relPath: String, + ir: => VirtualScalaJSIRFile): Unit = { + // We don't need to implement shadowing here: We have only a single JAR + irFiles += ir + } + + override protected def handleJS(js: => VirtualJSFile): Unit = { + val file = js + if (!jsFiles.contains(file.name)) + jsFiles += file.name -> file + } + + override protected def handleDepManifest(m: => JSDependencyManifest): Unit = { + if (dependency.isDefined) + sys.error("A JAR cannot have multiple JS dependency manifests") + dependency = Some(m) + } + +} diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/AbstractPartialClasspathBuilder.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/AbstractPartialClasspathBuilder.scala new file mode 100644 index 0000000..9889f4c --- /dev/null +++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/AbstractPartialClasspathBuilder.scala @@ -0,0 +1,47 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js tools ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.tools.classpath.builder + +import scala.scalajs.tools.jsdep.JSDependencyManifest +import scala.scalajs.tools.classpath._ +import scala.scalajs.tools.io._ + +import scala.collection.mutable +import scala.collection.immutable.Seq + +trait AbstractPartialClasspathBuilder extends ClasspathContentHandler + with ClasspathElementsTraverser { + + private val jsDepManifests = mutable.ListBuffer.empty[JSDependencyManifest] + private val irFiles = mutable.Map.empty[String, VirtualScalaJSIRFile] + private val otherJSFiles = mutable.Map.empty[String, VirtualJSFile] + + override protected def handleIR(relPath: String, + ir: => VirtualScalaJSIRFile): Unit = { + if (!irFiles.contains(relPath)) + irFiles += relPath -> ir + } + + override protected def handleJS(js: => VirtualJSFile): Unit = { + val file = js + if (!otherJSFiles.contains(file.name)) + otherJSFiles += file.name -> file + } + + override protected def handleDepManifest(m: => JSDependencyManifest): Unit = { + jsDepManifests += m + } + + def build(cp: Seq[File]): PartialClasspath = { + val version = traverseClasspathElements(cp) + new PartialClasspath(jsDepManifests.toList, otherJSFiles.toMap, + irFiles.values.toList, Some(version)) + } +} diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/ClasspathContentHandler.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/ClasspathContentHandler.scala new file mode 100644 index 0000000..71106c2 --- /dev/null +++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/ClasspathContentHandler.scala @@ -0,0 +1,25 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js tools ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.tools.classpath.builder + +import scala.scalajs.tools.io._ +import scala.scalajs.tools.jsdep._ +import scala.scalajs.tools.classpath._ + +import java.io._ + +import scala.collection.immutable.Seq + +/** Base-trait used by traversers to handle content with callbacks */ +trait ClasspathContentHandler { + protected def handleIR(relPath: String, ir: => VirtualScalaJSIRFile): Unit + protected def handleJS(js: => VirtualJSFile): Unit + protected def handleDepManifest(m: => JSDependencyManifest): Unit +} diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/ClasspathElementsTraverser.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/ClasspathElementsTraverser.scala new file mode 100644 index 0000000..9403ce3 --- /dev/null +++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/ClasspathElementsTraverser.scala @@ -0,0 +1,38 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js tools ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.tools.classpath.builder + +import scala.scalajs.tools.io._ + +/** A helper trait to traverse an arbitrary classpath element + * (i.e. a JAR or a directory). + */ +trait ClasspathElementsTraverser extends JarTraverser + with DirTraverser + with FileSystem { + + protected def traverseClasspathElements(cp: Seq[File]): String = + CacheUtils.joinVersions(cp.map(readEntriesInClasspathElement _): _*) + + /** Adds the Scala.js classpath entries in a directory or jar. + * Returns the accumulated version + */ + private def readEntriesInClasspathElement(element: File): String = { + if (!exists(element)) + getDummyVersion(element) + else if (isDirectory(element)) + traverseDir(element) + else if (isJARFile(element)) + traverseJar(element) + else + sys.error(s"$element (in classpath) exists and is neither JAR or directory") + } + +} diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/DirTraverser.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/DirTraverser.scala new file mode 100644 index 0000000..6609b29 --- /dev/null +++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/DirTraverser.scala @@ -0,0 +1,60 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js tools ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.tools.classpath.builder + +import scala.scalajs.tools.io._ +import scala.scalajs.tools.jsdep.JSDependencyManifest + +import scala.collection.mutable + +trait DirTraverser extends ClasspathContentHandler with FileSystem { + + /** Traverses elements, returns a version string */ + protected def traverseDir(dir: File): String = { + val versions = mutable.SortedSet.empty[String] + + recurseDir(dir, "", versions) + + // Construct version + CacheUtils.joinVersions(versions.toSeq: _*) + } + + /** Recursively adds the Scala.js classpath entries in a directory */ + private def recurseDir(dir: File, dirPath: String, + versions: mutable.SortedSet[String]): Unit = { + val files = listFiles(dir) + for (file <- files) { + val name = getName(file) + if (isDirectory(file)) { + recurseDir(file, dirPath + name + "/", versions) + } else { + val path = dirPath + name + path match { + case JSDependencyManifest.ManifestFileName => + versions += getGlobalVersion(file) + val reader = toReader(file) + try handleDepManifest(JSDependencyManifest.read(reader)) + finally reader.close() + + case _ if isJSFile(file) => + versions += getGlobalVersion(file) + handleJS(toJSFile(file)) + + case _ if isIRFile(file) => + versions += getGlobalVersion(file) + handleIR(path, toIRFile(file)) + + case _ => // ignore other files + } + } + } + } + +} diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/FileSystem.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/FileSystem.scala new file mode 100644 index 0000000..99a8ca2 --- /dev/null +++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/FileSystem.scala @@ -0,0 +1,57 @@ +package scala.scalajs.tools.classpath.builder + +import scala.scalajs.tools.io._ + +import scala.collection.immutable.Traversable + +import java.io.{InputStream, Reader} + +/** Abstraction of a FileSystem, so classpath builders can be used with virtual + * file systems + */ +trait FileSystem { + + type File + + /** Dummy version constant to identify files for which a version can not be + * found. + * This constant should never collide with the result of getVersion. + */ + val DummyVersion: String + + def isDirectory(f: File): Boolean + def isFile(f: File): Boolean + def isJSFile(f: File): Boolean + def isIRFile(f: File): Boolean + def isJARFile(f: File): Boolean + def exists(f: File): Boolean + + def getName(f: File): String + /** A string that uniquely identifies this file's location */ + def getAbsolutePath(f: File): String + /** A string that identifies the version of a file: If it equals the version + * of another file with the same absolute path, the two files must be equal. + * This is usually the lastModified date, but ordering is not required + */ + def getVersion(f: File): String + /** A string that globally identifies the version of a file: If it equals the + * global version of any other file, they must equal. + */ + def getGlobalVersion(f: File): String = + CacheUtils.joinVersions(getAbsolutePath(f), getVersion(f)) + + /** A string that globally identifies a file for which a version can not be + * found. Example: a file that does not exists. + */ + def getDummyVersion(f: File): String = + CacheUtils.joinVersions(getAbsolutePath(f), DummyVersion) + + /** List files in a directory */ + def listFiles(d: File): Traversable[File] + + def toJSFile(f: File): VirtualJSFile + def toIRFile(f: File): VirtualScalaJSIRFile + def toReader(f: File): Reader + def toInputStream(f: File): InputStream + +} diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/JarTraverser.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/JarTraverser.scala new file mode 100644 index 0000000..bbf5270 --- /dev/null +++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/JarTraverser.scala @@ -0,0 +1,85 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js tools ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.tools.classpath.builder + +import scala.collection.mutable +import scala.annotation.tailrec + +import java.util.zip._ +import java.io.{InputStream, InputStreamReader, Reader} + +import scala.scalajs.tools.io._ +import scala.scalajs.tools.jsdep.JSDependencyManifest + +trait JarTraverser extends ClasspathContentHandler with FileSystem { + + private val jsFiles = mutable.Map.empty[String, MemVirtualJSFile] + + /** Traverse a Jar and return a version */ + protected def traverseJar(jar: File): String = { + val zipStream = new ZipInputStream(toInputStream(jar)) + + try readEntries(zipStream, getAbsolutePath(jar)) + finally zipStream.close() + + for { + (_, jsFile) <- jsFiles + if jsFile.content != "" // drop if this is just a lone sourcemap + } handleJS(jsFile) + + getGlobalVersion(jar) + } + + private def getOrCreateJSFile(relPath: String, fullPath: String, fn: String) = + jsFiles.getOrElseUpdate(relPath, new MemVirtualJSFile(fullPath) { + override val name = fn + }) + + @tailrec + private def readEntries(in: ZipInputStream, jarPath: String): Unit = { + val entry = in.getNextEntry() + if (entry != null) { + readEntry(entry, in, jarPath) + readEntries(in, jarPath) + } + } + + private def readEntry(entry: ZipEntry, in: InputStream, jarPath: String) = { + val longName = entry.getName + val shortName = VirtualFile.nameFromPath(longName) + val fullPath = jarPath + "#" + longName + + def entryReader: Reader = new InputStreamReader(in, "UTF-8") + def entryContent: String = IO.readInputStreamToString(in) + def entryBinaryContent: Array[Byte] = IO.readInputStreamToByteArray(in) + def entryVersion: Option[String] = Some(entry.getTime.toString) + + if (longName == JSDependencyManifest.ManifestFileName) + handleDepManifest(JSDependencyManifest.read(entryReader)) + else if (longName.endsWith(".js")) { + val relPath = longName + getOrCreateJSFile(relPath, fullPath, shortName) + .withContent(entryContent) + .withVersion(entryVersion) + } else if (longName.endsWith(".js.map")) { + // assume the source map of a JS file + val relPath = longName.dropRight(".map".length) + getOrCreateJSFile(relPath, fullPath, shortName) + .withSourceMap(Some(entryContent)) + } else if (longName.endsWith(".sjsir")) { + val vf = new MemVirtualSerializedScalaJSIRFile(fullPath) { + override val name = shortName + }.withContent(entryBinaryContent) + .withVersion(entryVersion) + + handleIR(longName, vf) + } + } +} diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/corelib/CoreJSLibs.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/corelib/CoreJSLibs.scala new file mode 100644 index 0000000..ecbea1f --- /dev/null +++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/corelib/CoreJSLibs.scala @@ -0,0 +1,113 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js tools ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.tools.corelib + +import java.net.URI + +import scala.scalajs.ir.ScalaJSVersions +import scala.scalajs.tools.io._ + +import scala.scalajs.tools.sem._ + +import scala.collection.immutable.Seq +import scala.collection.mutable + +object CoreJSLibs { + + private val cachedLibBySemantics = + mutable.HashMap.empty[Semantics, VirtualJSFile] + + private val ScalaJSEnvLines = + ScalaJSEnvHolder.scalajsenv.split("\n|\r\n?") + + private val gitHubBaseURI = + new URI("https://raw.githubusercontent.com/scala-js/scala-js/") + + def libs(semantics: Semantics): Seq[VirtualJSFile] = synchronized { + Seq(cachedLibBySemantics.getOrElseUpdate(semantics, makeLib(semantics))) + } + + private def makeLib(semantics: Semantics): VirtualJSFile = { + new ScalaJSEnvVirtualJSFile(makeContent(semantics)) + } + + private def makeContent(semantics: Semantics): String = { + // This is a basic sort-of-C-style preprocessor + + def getOption(name: String): String = name match { + case "asInstanceOfs" => + semantics.asInstanceOfs.toString() + case "floats" => + if (semantics.strictFloats) "Strict" + else "Loose" + } + + var skipping = false + var skipDepth = 0 + val lines = for (line <- ScalaJSEnvLines) yield { + val includeThisLine = if (skipping) { + if (line == "//!else" && skipDepth == 1) { + skipping = false + skipDepth = 0 + } else if (line == "//!endif") { + skipDepth -= 1 + if (skipDepth == 0) + skipping = false + } else if (line.startsWith("//!if ")) { + skipDepth += 1 + } + false + } else { + if (line.startsWith("//!")) { + if (line.startsWith("//!if ")) { + val Array(_, option, op, value) = line.split(" ") + val optionValue = getOption(option) + val success = op match { + case "==" => optionValue == value + case "!=" => optionValue != value + } + if (!success) { + skipping = true + skipDepth = 1 + } + } else if (line == "//!else") { + skipping = true + skipDepth = 1 + } else if (line == "//!endif") { + // nothing to do + } else { + throw new MatchError(line) + } + false + } else { + true + } + } + if (includeThisLine) line + else "" // blank line preserves line numbers in source maps + } + + lines.mkString("", "\n", "\n") + } + + private class ScalaJSEnvVirtualJSFile(override val content: String) extends VirtualJSFile { + override def path: String = "scalajsenv.js" + override def version: Option[String] = Some("") + override def exists: Boolean = true + + override def toURI: URI = { + if (!ScalaJSVersions.currentIsSnapshot) + gitHubBaseURI.resolve(s"v${ScalaJSVersions.current}/tools/$path") + else + super.toURI + } + } + +} diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/AsyncJSEnv.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/AsyncJSEnv.scala new file mode 100644 index 0000000..d439ae2 --- /dev/null +++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/AsyncJSEnv.scala @@ -0,0 +1,19 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js sbt plugin ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.tools.env + +import scala.scalajs.tools.io._ +import scala.scalajs.tools.classpath._ +import scala.scalajs.tools.logging._ + +trait AsyncJSEnv extends JSEnv { + def asyncRunner(classpath: CompleteClasspath, code: VirtualJSFile, + logger: Logger, console: JSConsole): AsyncJSRunner +} diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/AsyncJSRunner.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/AsyncJSRunner.scala new file mode 100644 index 0000000..09e2dda --- /dev/null +++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/AsyncJSRunner.scala @@ -0,0 +1,19 @@ +package scala.scalajs.tools.env + +import scala.concurrent.Future + +trait AsyncJSRunner { + /** Start the associated run and returns a Future that completes when the run + * terminates. + */ + def start(): Future[Unit] + + /** Abort the associated run */ + def stop(): Unit + + /** Checks whether this async runner is still running */ + def isRunning(): Boolean + + /** Await completion of the started Run. Throws if the run failed */ + def await(): Unit +} diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/ComJSEnv.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/ComJSEnv.scala new file mode 100644 index 0000000..882e46a --- /dev/null +++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/ComJSEnv.scala @@ -0,0 +1,38 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js sbt plugin ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.tools.env + +import scala.scalajs.tools.io._ +import scala.scalajs.tools.classpath._ +import scala.scalajs.tools.logging._ + +/** An [[AsyncJSEnv]] that provides communication to and from the JS VM. + * + * Inside the VM there is a global JavaScript object named `scalajsCom` that + * can be used to control the message channel. It's operations are: + * {{{ + * // initialize com (with callback) + * scalajsCom.init(function(msg) { console.log("Received: " + msg); }); + * + * // send a message to host system + * scalajsCom.send("my message"); + * + * // close com (releases callback, allowing VM to terminate) + * scalajsCom.close(); + * }}} + */ +trait ComJSEnv extends AsyncJSEnv { + def comRunner(classpath: CompleteClasspath, code: VirtualJSFile, + logger: Logger, console: JSConsole): ComJSRunner +} + +object ComJSEnv { + class ComClosedException extends Exception("JSCom has been closed") +} diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/ComJSRunner.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/ComJSRunner.scala new file mode 100644 index 0000000..44302b8 --- /dev/null +++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/ComJSRunner.scala @@ -0,0 +1,22 @@ +package scala.scalajs.tools.env + +trait ComJSRunner extends AsyncJSRunner { + + /** Send a message to the JS VM. Throws if the message cannot be sent. */ + def send(msg: String): Unit + + /** Block until a message is received. Throws a [[ComClosedExcpetion]] + * if the channel is closed before a message is received. + */ + def receive(): String + + /** Close the communication channel. Allows the VM to terminate if it is + * still waiting for callback. The JVM side **must** call close in + * order to be able to expect termination of the VM. + * + * Calling [[stop]] on a [ComJSRunner]] automatically closes the + * channel. + */ + def close(): Unit + +} diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/ConsoleJSConsole.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/ConsoleJSConsole.scala new file mode 100644 index 0000000..5b3d055 --- /dev/null +++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/ConsoleJSConsole.scala @@ -0,0 +1,17 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js sbt plugin ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.tools.env + +/** A JS console that prints on the console */ +object ConsoleJSConsole extends JSConsole { + override def log(msg: Any): Unit = { + Console.println(msg) + } +} diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/JSConsole.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/JSConsole.scala new file mode 100644 index 0000000..a93768f --- /dev/null +++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/JSConsole.scala @@ -0,0 +1,15 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js sbt plugin ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.tools.env + +/** Trait representing a JS console */ +trait JSConsole { + def log(msg: Any): Unit +} diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/JSEnv.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/JSEnv.scala new file mode 100644 index 0000000..f1fbf44 --- /dev/null +++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/JSEnv.scala @@ -0,0 +1,20 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js sbt plugin ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.tools.env + +import scala.scalajs.tools.io._ +import scala.scalajs.tools.classpath._ +import scala.scalajs.tools.logging._ + +trait JSEnv { + /** Prepare a runner for the code in the virtual file. */ + def jsRunner(classpath: CompleteClasspath, code: VirtualJSFile, + logger: Logger, console: JSConsole): JSRunner +} diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/JSRunner.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/JSRunner.scala new file mode 100644 index 0000000..460fff0 --- /dev/null +++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/JSRunner.scala @@ -0,0 +1,15 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js sbt plugin ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.tools.env + +trait JSRunner { + /** Run the associated JS code. Throw if an error occurs. */ + def run(): Unit +} diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/NullJSConsole.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/NullJSConsole.scala new file mode 100644 index 0000000..8147bbe --- /dev/null +++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/NullJSConsole.scala @@ -0,0 +1,5 @@ +package scala.scalajs.tools.env + +object NullJSConsole extends JSConsole { + def log(msg: Any): Unit = {} +} diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/io/CacheUtils.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/io/CacheUtils.scala new file mode 100644 index 0000000..14773f8 --- /dev/null +++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/io/CacheUtils.scala @@ -0,0 +1,52 @@ +package scala.scalajs.tools.io + +object CacheUtils { + + def joinVersions(vs: Option[String]*): Option[String] = { + val bld = new StringBuilder + + @scala.annotation.tailrec + def loop(vs: Seq[Option[String]]): Option[String] = { + vs match { + case Some(v) :: vss => + bld.append(mangleVersionString(v)) + loop(vss) + case None :: _ => + None + case Nil => + Some(bld.toString) + } + } + + loop(vs.toList) + } + + def joinVersions(vs: String*): String = + vs.map(mangleVersionString _).mkString + + private def mangleVersionString(str: String) = s"${str.length}:$str" + + def cached(version: Option[String], output: VirtualFile, + cache: Option[WritableVirtualTextFile])(action: => Unit): Unit = { + + val upToDate = output.exists && ( + for { + v <- version + c <- cache if c.exists + } yield c.content == v + ).getOrElse(false) + + // Are we outdated? + if (!upToDate) { + action + + // Write cache + for (c <- cache; v <- version) { + val w = c.contentWriter + try w.write(v) + finally w.close() + } + } + } + +} diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/io/IO.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/io/IO.scala new file mode 100644 index 0000000..b69b07c --- /dev/null +++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/io/IO.scala @@ -0,0 +1,116 @@ +package scala.scalajs.tools.io + +import scala.annotation.tailrec + +import scala.reflect.ClassTag + +import java.io._ + +object IO { + /** Returns the lines in an input stream. + * Lines do not contain the new line characters. + */ + def readLines(stream: InputStream): List[String] = + readLines(new InputStreamReader(stream)) + + /** Returns the lines in a string. + * Lines do not contain the new line characters. + */ + def readLines(content: String): List[String] = + readLines(new StringReader(content)) + + /** Returns the lines in a reader. + * Lines do not contain the new line characters. + */ + def readLines(reader: Reader): List[String] = { + val br = new BufferedReader(reader) + try { + val builder = List.newBuilder[String] + @tailrec + def loop(): Unit = { + val line = br.readLine() + if (line ne null) { + builder += line + loop() + } + } + loop() + builder.result() + } finally { + br.close() + } + } + + /** Reads the entire content of a reader as a string. */ + def readReaderToString(reader: Reader): String = { + val buffer = newBuffer[Char] + val builder = new StringBuilder + @tailrec + def loop(): Unit = { + val len = reader.read(buffer) + if (len > 0) { + builder.appendAll(buffer, 0, len) + loop() + } + } + loop() + builder.toString() + } + + /** Reads the entire content of an input stream as a UTF-8 string. */ + def readInputStreamToString(stream: InputStream): String = { + val reader = new BufferedReader(new InputStreamReader(stream, "UTF-8")) + readReaderToString(reader) + } + + /** Reads the entire content of an input stream as a byte array. */ + def readInputStreamToByteArray(stream: InputStream): Array[Byte] = { + val builder = new ByteArrayOutputStream() + val buffer = newBuffer[Byte] + @tailrec + def loop(): Unit = { + val size = stream.read(buffer) + if (size > 0) { + builder.write(buffer, 0, size) + loop() + } + } + loop() + builder.toByteArray() + } + + /** Concatenates a bunch of VirtualTextFiles to a WritableVirtualTextFile. + * Adds a '\n' after each file. + */ + def concatFiles(output: WritableVirtualTextFile, + files: Seq[VirtualTextFile]): Unit = { + val buffer = newBuffer[Char] + val out = output.contentWriter + + try { + for (file <- files) { + val reader = file.reader + + @tailrec + def loop(): Unit = { + val size = reader.read(buffer) + if (size > 0) { + out.write(buffer, 0, size) + loop() + } + } + + try loop() + finally reader.close() + + // New line after each file + out.write('\n') + } + } finally { + out.close() + } + } + + @inline + private def newBuffer[T : ClassTag] = new Array[T](4096) +} diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/io/MemFiles.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/io/MemFiles.scala new file mode 100644 index 0000000..68f66dc --- /dev/null +++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/io/MemFiles.scala @@ -0,0 +1,105 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js tools ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.tools.io + +import java.io._ + +/** A base class for simple in-memory mutable virtual files. */ +class MemVirtualFile(val path: String) extends VirtualFile { + private[this] var _version: Option[String] = None + + override def version: Option[String] = _version + def version_=(v: Option[String]): Unit = _version = v + + final def withVersion(v: Option[String]): this.type = { + version = v + this + } + + override def exists: Boolean = true +} + +/** A simple in-memory mutable virtual text file. */ +class MemVirtualTextFile(p: String) extends MemVirtualFile(p) + with VirtualTextFile { + private[this] var _content: String = "" + + override def content: String = _content + def content_=(v: String): Unit = _content = v + + final def withContent(v: String): this.type = { + content = v + this + } +} + +trait WritableMemVirtualTextFile extends MemVirtualTextFile + with WritableVirtualTextFile { + def contentWriter: Writer = new StringWriter { + override def close(): Unit = { + super.close() + WritableMemVirtualTextFile.this.content = this.toString + } + } +} + +object WritableMemVirtualTextFile { + def apply(path: String): WritableMemVirtualTextFile = + new MemVirtualTextFile(path) with WritableMemVirtualTextFile +} + +/** A simple in-memory mutable virtual binary file. */ +class MemVirtualBinaryFile(p: String) extends MemVirtualFile(p) + with VirtualBinaryFile { + private[this] var _content: Array[Byte] = new Array[Byte](0) + + override def content: Array[Byte] = _content + def content_=(v: Array[Byte]): Unit = _content = v + + final def withContent(v: Array[Byte]): this.type = { + content = v + this + } +} + +/** A simple in-memory mutable virtual JS file. */ +class MemVirtualJSFile(p: String) extends MemVirtualTextFile(p) + with VirtualJSFile { + private[this] var _sourceMap: Option[String] = None + + override def sourceMap: Option[String] = _sourceMap + def sourceMap_=(v: Option[String]): Unit = _sourceMap = v + + final def withSourceMap(v: Option[String]): this.type = { + sourceMap = v + this + } +} + +trait WritableMemVirtualJSFile extends MemVirtualJSFile + with WritableVirtualJSFile + with WritableMemVirtualTextFile { + + def sourceMapWriter: Writer = new StringWriter { + override def close(): Unit = { + super.close() + WritableMemVirtualJSFile.this.sourceMap = Some(this.toString) + } + } +} + +object WritableMemVirtualJSFile { + def apply(path: String): WritableMemVirtualJSFile = + new MemVirtualJSFile(path) with WritableMemVirtualJSFile +} + +/** A simple in-memory mutable virtual serialized Scala.js IR file. */ +class MemVirtualSerializedScalaJSIRFile(p: String) extends MemVirtualBinaryFile(p) + with VirtualSerializedScalaJSIRFile diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/io/VirtualFiles.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/io/VirtualFiles.scala new file mode 100644 index 0000000..c62ab5c --- /dev/null +++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/io/VirtualFiles.scala @@ -0,0 +1,169 @@ +package scala.scalajs.tools.io + +import java.io._ +import java.net.URI + +import scala.scalajs.ir +import scala.scalajs.tools.sourcemap._ + +/** A virtual input file. + */ +trait VirtualFile { + /** Path of the file, including everything. + * Unique if possible (used for lookup). */ + def path: String + + /** Name of the file/writer, including extension */ + def name: String = VirtualFile.nameFromPath(path) + + /** Optionally returns an implementation-dependent "version" token. + * Versions are compared with ==. + * If non-empty, a different version must be returned when the content + * changes. It should be equal if the content has not changed, but it is + * not mandatory. + * Such a token can be used by caches: the file need not be read and + * processed again if its version has not changed. + */ + def version: Option[String] = None + + /** Whether this file exists. Reading a non-existent file may fail */ + def exists: Boolean + + /** URI for this virtual file */ + def toURI: URI = { + new URI( + "virtualfile", // Pseudo-Scheme + path, // Scheme specific part + null // Fragment + ) + } +} + +object VirtualFile { + /** Splits at the last slash and returns remainder */ + def nameFromPath(path: String): String = { + val pos = path.lastIndexOf('/') + if (pos == -1) path + else path.substring(pos + 1) + } +} + +/** A virtual input file. + */ +trait VirtualTextFile extends VirtualFile { + /** Returns the content of the file. */ + def content: String + + /** Returns a new Reader of the file. */ + def reader: Reader = new StringReader(content) + + /** Returns the lines in the content. + * Lines do not contain the new line characters. + */ + def readLines(): List[String] = IO.readLines(reader) +} + +object VirtualTextFile { + def empty(path: String): VirtualTextFile = + new MemVirtualTextFile(path) +} + +trait WritableVirtualTextFile extends VirtualTextFile { + def contentWriter: Writer +} + +/** A virtual binary input file. + */ +trait VirtualBinaryFile extends VirtualFile { + /** Returns the content of the file. */ + def content: Array[Byte] + + /** Returns a new InputStream of the file. */ + def inputStream: InputStream = new ByteArrayInputStream(content) +} + +/** A virtual input file which contains JavaScript code. + * It may have a source map associated with it. + */ +trait VirtualJSFile extends VirtualTextFile { + /** Optionally, content of the source map file associated with this + * JavaScript source. + */ + def sourceMap: Option[String] = None +} + +object VirtualJSFile { + def empty(path: String): VirtualJSFile = + new MemVirtualJSFile(path).withVersion(Some(path)) +} + +trait WritableVirtualJSFile extends WritableVirtualTextFile with VirtualJSFile { + def sourceMapWriter: Writer +} + +/** A virtual Scala.js IR file. + * It contains the class info and the IR tree. + */ +trait VirtualScalaJSIRFile extends VirtualFile { + /** Rough class info of this file. */ + def roughInfo: ir.Infos.RoughClassInfo = info + + /** Class info of this file. */ + def info: ir.Infos.ClassInfo = + infoAndTree._1 + + /** IR Tree of this file. */ + def tree: ir.Trees.ClassDef = + infoAndTree._2 + + /** Class info and IR tree of this file. */ + def infoAndTree: (ir.Infos.ClassInfo, ir.Trees.ClassDef) +} + +/** Base trait for virtual Scala.js IR files that are serialized as binary file. + */ +trait VirtualSerializedScalaJSIRFile extends VirtualBinaryFile with VirtualScalaJSIRFile { + /** Rough class info of this file. */ + override def roughInfo: ir.Infos.RoughClassInfo = { + // Overridden to read only the necessary parts + val stream = inputStream + try { + ir.InfoSerializers.deserializeRoughInfo(stream) + } catch { + case e: IOException => + throw new IOException(s"Failed to deserialize rough info of $path", e) + } finally { + stream.close() + } + } + + /** Class info of this file. */ + override def info: ir.Infos.ClassInfo = { + // Overridden to read only the necessary parts + val stream = inputStream + try { + ir.InfoSerializers.deserializeFullInfo(stream) + } catch { + case e: IOException => + throw new IOException(s"Failed to deserialize info of $path", e) + } finally { + stream.close() + } + } + + /** Class info and IR tree of this file. */ + override def infoAndTree: (ir.Infos.ClassInfo, ir.Trees.ClassDef) = { + val stream = inputStream + try { + val (version, info) = ir.InfoSerializers.deserializeVersionFullInfo(stream) + val tree = ir.Serializers.deserialize( + stream, version).asInstanceOf[ir.Trees.ClassDef] + (info, tree) + } catch { + case e: IOException => + throw new IOException(s"Failed to deserialize $path", e) + } finally { + stream.close() + } + } +} diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/javascript/JSDesugaring.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/javascript/JSDesugaring.scala new file mode 100644 index 0000000..b4d4005 --- /dev/null +++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/javascript/JSDesugaring.scala @@ -0,0 +1,1525 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js tools ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.tools.javascript + +import scala.language.implicitConversions + +import scala.annotation.switch + +import scala.scalajs.ir._ +import Position._ +import Transformers._ +import scala.scalajs.ir.Trees._ +import scala.scalajs.ir.Types._ + +import scala.scalajs.tools.sem._ +import CheckedBehavior._ + +import scala.scalajs.tools.javascript.{Trees => js} + +/** Desugaring of the IR to regular ES5 JavaScript. + * + * The major difference between the IR and JS is that most constructs can be + * used in expression position. The main work of the desugaring is to + * unnest complex constructs in expression position so that they become + * statements. + * + * The general idea is two-folded: + * 1) Unnest complex constructs in "argument position": + * When a complex construct is used in a non-rhs expression position + * (argument to a function, operand, condition of an if, etc.), that we + * call "argument position", declare a variable before the statement, + * assign the complex construct to it and then use that variable in the + * argument position instead. + * 2) Push LHS's inside complex RHS's: + * When an rhs is a complex construct, push the lhs inside the complex + * construct. Are considered lhs: + * * Assign, i.e., `x =` + * * VarDef, i.e., `var x =` + * * Return, i.e., `return` + * * (EmptyTree is also used as a trick for code reuse) + * In fact, think that, in this context, LHS means: what to do with the + * result of evaluating the RHS. + * + * -------------------------------------------------------------------------- + * + * Typical example, consider the method call: + * + * obj.meth({ + * var x = foo(42); + * x*x + * }); + * + * According to rule 1), the block that is passed as a parameter to obj.meth + * is first extracted in a synthetic var: + * + * var x\$1 = { + * var x = foo(42); + * x*x + * } + * obj.meth(x\$1); + * + * Then, according to rule 2), the lhs `var x\$1 =` is pushed inside the block: + * + * { + * var x = foo(42); + * var x\$1 = x*x; + * } + * obj.meth(x\$1); + * + * Because bare blocks are non-significant in JS, this is equivalent to + * + * var x = foo(42); + * var x\$1 = x*x; + * obj.meth(x\$1); + * + * -------------------------------------------------------------------------- + * + * JSDesugaring does all this in a single pass, but it helps to think that: + * * Rule 1) is implemented by unnest(), and used most notably in + * * transformStat() for statement-only constructs + * * pushLhsInto() for statement-or-expression constructs + * * Rule 2) is implemented by pushLhsInto() + * * Emitting the class structure is delegated to [[ScalaJSClassEmitter]]. + * + * There are a few other things that JSDesugaring takes care of: + * * Transform Scala expressions into their JS equivalent, taking the + * Scala.js class encoding into account. + * * And tiny details. + * + * @author Sébastien Doeraene + */ +object JSDesugaring { + + private final val ScalaJSEnvironmentName = "ScalaJS" + + /** Desugars a statement of the IR into ES5 JavaScript. */ + def desugarJavaScript(tree: Tree, semantics: Semantics): js.Tree = { + new JSDesugar(semantics).transformStat(tree) + } + + private[javascript] implicit def transformIdent(ident: Ident): js.Ident = + js.Ident(ident.name, ident.originalName)(ident.pos) + + private[javascript] def transformParamDef(paramDef: ParamDef): js.ParamDef = + js.ParamDef(paramDef.name, paramDef.mutable)(paramDef.pos) + + private class JSDesugar(semantics: Semantics) { + + // Synthetic variables + + var syntheticVarCounter: Int = 0 + + def newSyntheticVar()(implicit pos: Position): Ident = { + syntheticVarCounter += 1 + Ident("jsx$" + syntheticVarCounter, None) + } + + def resetSyntheticVarCounterIn[A](f: => A): A = { + val savedCounter = syntheticVarCounter + syntheticVarCounter = 0 + try f + finally syntheticVarCounter = savedCounter + } + + // Record names + + def makeRecordFieldIdent(recIdent: Ident, fieldIdent: Ident)( + implicit pos: Position): Ident = + makeRecordFieldIdent(recIdent.name, recIdent.originalName, + fieldIdent.name, fieldIdent.originalName) + + def makeRecordFieldIdent(recIdent: Ident, + fieldName: String, fieldOrigiName: Option[String])( + implicit pos: Position): Ident = + makeRecordFieldIdent(recIdent.name, recIdent.originalName, + fieldName, fieldOrigiName) + + def makeRecordFieldIdent(recName: String, recOrigName: Option[String], + fieldName: String, fieldOrigName: Option[String])( + implicit pos: Position): Ident = { + val name = recName + "_$_" + fieldName + val originalName = Some(recOrigName.getOrElse(recName) + "." + + fieldOrigName.getOrElse(fieldName)) + Ident(name, originalName) + } + + // LHS'es for labeled expressions + + var labeledExprLHSes: Map[Ident, Tree] = Map.empty + + // Now the work + + /** Desugar a statement of the IR into ES5 JS */ + def transformStat(tree: Tree): js.Tree = { + implicit val pos = tree.pos + + tree match { + // Statement-only language constructs + + case Skip() => + js.Skip() + + case VarDef(varIdent, RecordType(fields), recMutable, EmptyTree) => + js.Block(for { + RecordType.Field(fieldName, fieldOrigName, tpe, fieldMutable) <- fields + } yield { + transformStat { + VarDef(makeRecordFieldIdent(varIdent, fieldName, fieldOrigName), + tpe, recMutable || fieldMutable, EmptyTree) + } + }) + + case VarDef(name, _, mutable, EmptyTree) => + js.VarDef(name, mutable, js.EmptyTree) + + case VarDef(_, _, _, rhs) => + pushLhsInto(tree, rhs) + + case Assign(RecordFieldVarRef(lhs), rhs) => + pushLhsInto(Assign(lhs, EmptyTree), rhs) + + case Assign(select @ Select(qualifier, item, mutable), rhs) => + unnest(qualifier, rhs) { (newQualifier, newRhs) => + js.Assign( + js.DotSelect(transformExpr(newQualifier), item)(select.pos), + transformExpr(newRhs)) + } + + case Assign(select @ ArraySelect(array, index), rhs) => + unnest(List(array, index, rhs)) { + case List(newArray, newIndex, newRhs) => + js.Assign( + js.BracketSelect(js.DotSelect(transformExpr(newArray), + js.Ident("u"))(select.pos), + transformExpr(newIndex))(select.pos), + transformExpr(newRhs)) + } + + case Assign(select @ JSDotSelect(qualifier, item), rhs) => + unnest(qualifier, rhs) { (newQualifier, newRhs) => + js.Assign( + js.DotSelect(transformExpr(newQualifier), item)(select.pos), + transformExpr(newRhs)) + } + + case Assign(select @ JSBracketSelect(qualifier, item), rhs) => + unnest(List(qualifier, item, rhs)) { + case List(newQualifier, newItem, newRhs) => + js.Assign( + js.BracketSelect(transformExpr(newQualifier), + transformExpr(newItem))(select.pos), + transformExpr(newRhs)) + } + + case Assign(_ : VarRef, rhs) => + pushLhsInto(tree, rhs) + + case Assign(_, _) => + sys.error(s"Illegal Assign in transformStat: $tree") + + case StoreModule(cls, value) => + assert(cls.className.endsWith("$"), + s"Trying to store module for non-module class $cls") + val moduleName = cls.className.dropRight(1) + unnest(value) { newValue => + js.Assign( + js.DotSelect(envField("n"), Ident(moduleName)), + transformExpr(newValue)) + } + + case While(cond, body, label) => + /* We cannot simply unnest(cond) here, because that would eject the + * evaluation of the condition out of the loop. + */ + val newLabel = label.map(transformIdent) + if (isExpression(cond)) { + js.While(transformExpr(cond), transformStat(body), newLabel) + } else { + js.While(js.BooleanLiteral(true), { + unnest(cond) { newCond => + js.If(transformExpr(newCond), transformStat(body), js.Break()) + } + }, newLabel) + } + + case DoWhile(body, cond, label) => + /* We cannot simply unnest(cond) here, because that would eject the + * evaluation of the condition out of the loop. + */ + val newLabel = label.map(transformIdent) + if (isExpression(cond)) { + js.DoWhile(transformStat(body), transformExpr(cond), newLabel) + } else { + /* This breaks 'continue' statements for this loop, but we don't + * care because we never emit continue statements for do..while + * loops. + */ + js.While(js.BooleanLiteral(true), { + js.Block(transformStat(body), { + unnest(cond) { newCond => + js.If(transformExpr(newCond), js.Skip(), js.Break()) + } + }) + }, newLabel) + } + + case Debugger() => + js.Debugger() + + case JSDelete(JSDotSelect(obj, prop)) => + unnest(obj) { (newObj) => + js.Delete(js.DotSelect(transformExpr(newObj), prop)) + } + + case JSDelete(JSBracketSelect(obj, prop)) => + unnest(obj, prop) { (newObj, newProp) => + js.Delete(js.BracketSelect( + transformExpr(newObj), transformExpr(newProp))) + } + + // Treat 'return' as an LHS + + case Return(expr, label) => + pushLhsInto(tree, expr) + + /* Anything else is an expression => pushLhsInto(EmptyTree, _) + * In order not to duplicate all the code of pushLhsInto() here, we + * use a trick: EmptyTree is a dummy LHS that says "do nothing + * with the result of the rhs". + * This is exactly what an expression statement is doing: it evaluates + * the expression, but does nothing with its result. + */ + + case _ => + pushLhsInto(EmptyTree, tree) + } + } + + private object RecordFieldVarRef { + def unapply(tree: Tree): Option[VarRef] = { + tree match { + case Select(RecordVarRef(VarRef(recIdent, recMutable)), + fieldIdent, fieldMutable) => + implicit val pos = tree.pos + Some(VarRef(makeRecordFieldIdent(recIdent, fieldIdent), + recMutable || fieldMutable)(tree.tpe)) + case _ => + None + } + } + } + + private object RecordVarRef { + def unapply(tree: Tree): Option[VarRef] = { + if (!tree.tpe.isInstanceOf[RecordType]) None + else { + tree match { + case tree: VarRef => Some(tree) + case Select(RecordVarRef(VarRef(recIdent, recMutable)), + fieldIdent, fieldMutable) => + implicit val pos = tree.pos + Some(VarRef(makeRecordFieldIdent(recIdent, fieldIdent), + recMutable || fieldMutable)(tree.tpe)) + } + } + } + } + + /** Unnest complex constructs in argument position in temporary variables + * + * If all the arguments are JS expressions, there is nothing to do. + * Any argument that is not a JS expression must be unnested and stored + * in a temporary variable before the statement produced by `makeStat`. + * + * But *this changes the evaluation order!* In order not to lose it, it + * is necessary to also unnest arguments that are expressions but that + * are supposed to be evaluated before the argument-to-be-unnested and + * could have side-effects or even whose evaluation could be influenced + * by the side-effects of another unnested argument. + * + * Without deep effect analysis, which we do not do, we need to take + * a very pessimistic approach, and unnest any expression that contains + * an identifier (except those after the last non-expression argument). + * Hence the predicate `isPureExpressionWithoutIdent`. + */ + def unnest(args: List[Tree])( + makeStat: List[Tree] => js.Tree): js.Tree = { + if (args forall isExpression) makeStat(args) + else { + val extractedStatements = new scala.collection.mutable.ListBuffer[js.Tree] + + /* Attention! Everything must be processed recursively + * *right-to-left*! Indeed, the point is that noExtractYet will tell + * whether anything supposed to be evaluated *after* the currently + * being processed expression has been (at least partly) extracted + * in temporary variables (or simply statements, in the Block case). + * If nothing has, we can keep more in place without having to extract + * that expression in a temporary variable. + */ + + def rec(arg: Tree): Tree = { + def noExtractYet = extractedStatements.isEmpty + + if (if (noExtractYet) isExpression(arg) else isPureExpression(arg)) { + arg + } else { + implicit val pos = arg.pos + arg match { + case Block(stats :+ expr) => + val result = rec(expr) // right-to-left, remember? + // Put the stats in a Block because ++=: is not smart + js.Block(stats.map(transformStat)) +=: extractedStatements + result + + case UnaryOp(op, lhs) => + UnaryOp(op, rec(lhs)) + case BinaryOp(op, lhs, rhs) => + val newRhs = rec(rhs) + BinaryOp(op, rec(lhs), newRhs) + case JSBinaryOp(op, lhs, rhs) => + val newRhs = rec(rhs) + JSBinaryOp(op, rec(lhs), newRhs) + case JSUnaryOp(op, lhs) => + JSUnaryOp(op, rec(lhs)) + case IsInstanceOf(expr, tpe) => + IsInstanceOf(rec(expr), tpe) + + case AsInstanceOf(expr, tpe) + if noExtractYet || semantics.asInstanceOfs == Unchecked => + AsInstanceOf(rec(expr), tpe) + case Unbox(expr, tpe) + if noExtractYet || semantics.asInstanceOfs == Unchecked => + Unbox(rec(expr), tpe) + + case NewArray(tpe, lengths) => + NewArray(tpe, recs(lengths)) + case ArrayValue(tpe, elems) => + ArrayValue(tpe, recs(elems)) + case JSArrayConstr(items) => + JSArrayConstr(recs(items)) + case JSObjectConstr(items) => + val newValues = recs(items.map(_._2)) + JSObjectConstr(items.map(_._1) zip newValues) + case Closure(captureParams, params, body, captureValues) => + Closure(captureParams, params, body, recs(captureValues)) + + case New(cls, constr, args) if noExtractYet => + New(cls, constr, recs(args)) + case Select(qualifier, item, mutable) if noExtractYet => + Select(rec(qualifier), item, mutable)(arg.tpe) + case Apply(receiver, method, args) if noExtractYet => + val newArgs = recs(args) + Apply(rec(receiver), method, newArgs)(arg.tpe) + case StaticApply(receiver, cls, method, args) if noExtractYet => + val newArgs = recs(args) + StaticApply(rec(receiver), cls, method, newArgs)(arg.tpe) + case TraitImplApply(impl, method, args) if noExtractYet => + TraitImplApply(impl, method, recs(args))(arg.tpe) + case ArrayLength(array) if noExtractYet => + ArrayLength(rec(array)) + case ArraySelect(array, index) if noExtractYet => + val newIndex = rec(index) + ArraySelect(rec(array), newIndex)(arg.tpe) + case CallHelper(helper, args) if noExtractYet => + CallHelper(helper, recs(args))(arg.tpe) + + case If(cond, thenp, elsep) + if noExtractYet && isExpression(thenp) && isExpression(elsep) => + If(rec(cond), thenp, elsep)(arg.tpe) + + case _ => + val temp = newSyntheticVar() + val computeTemp = + pushLhsInto(VarDef(temp, arg.tpe, mutable = false, EmptyTree), arg) + computeTemp +=: extractedStatements + VarRef(temp, mutable = false)(arg.tpe) + } + } + } + + def recs(args: List[Tree]): List[Tree] = { + // This is a right-to-left map + args.foldRight[List[Tree]](Nil) { (arg, acc) => + rec(arg) :: acc + } + } + + val newArgs = recs(args) + + assert(extractedStatements.nonEmpty, + "Reached computeTemps with no temp to compute") + + val newStatement = makeStat(newArgs) + js.Block(extractedStatements.result() ::: List(newStatement))(newStatement.pos) + } + } + + /** Same as above, for a single argument */ + def unnest(arg: Tree)( + makeStat: Tree => js.Tree): js.Tree = { + unnest(List(arg)) { + case List(newArg) => makeStat(newArg) + } + } + + /** Same as above, for two arguments */ + def unnest(lhs: Tree, rhs: Tree)( + makeStat: (Tree, Tree) => js.Tree): js.Tree = { + unnest(List(lhs, rhs)) { + case List(newLhs, newRhs) => makeStat(newLhs, newRhs) + } + } + + /** Same as above, for one head argument and a list of arguments */ + def unnest(arg0: Tree, args: List[Tree])( + makeStat: (Tree, List[Tree]) => js.Tree): js.Tree = { + unnest(arg0 :: args) { newArgs => + makeStat(newArgs.head, newArgs.tail) + } + } + + /** Common implementation for the functions below. + * A pure expression can be moved around or executed twice, because it + * will always produce the same result and never have side-effects. + * A side-effect free expression can be elided if its result is not used. + */ + private def isExpressionInternal(tree: Tree, + allowUnpure: Boolean, allowSideEffects: Boolean): Boolean = { + + require(!allowSideEffects || allowUnpure) + + def test(tree: Tree): Boolean = tree match { + // Atomic expressions + case _: Literal => true + case _: This => true + case _: JSEnvInfo => true + + // Vars and fields (side-effect free, pure if immutable) + case VarRef(_, mutable) => + allowUnpure || !mutable + case Select(qualifier, item, mutable) => + (allowUnpure || !mutable) && test(qualifier) + + // Expressions preserving pureness + case Block(trees) => trees forall test + case If(cond, thenp, elsep) => test(cond) && test(thenp) && test(elsep) + case BinaryOp(_, lhs, rhs) => test(lhs) && test(rhs) + case UnaryOp(_, lhs) => test(lhs) + case JSBinaryOp(_, lhs, rhs) => test(lhs) && test(rhs) + case JSUnaryOp(_, lhs) => test(lhs) + case ArrayLength(array) => test(array) + case IsInstanceOf(expr, _) => test(expr) + + // Expressions preserving side-effect freedom + case NewArray(tpe, lengths) => + allowUnpure && (lengths forall test) + case ArrayValue(tpe, elems) => + allowUnpure && (elems forall test) + case ArraySelect(array, index) => + allowUnpure && test(array) && test(index) + case JSArrayConstr(items) => + allowUnpure && (items forall test) + case JSObjectConstr(items) => + allowUnpure && (items forall (item => test(item._2))) + case Closure(captureParams, params, body, captureValues) => + allowUnpure && (captureValues forall test) + + // Scala expressions that can always have side-effects + case New(cls, constr, args) => + allowSideEffects && (args forall test) + case LoadModule(cls) => // unfortunately + allowSideEffects + case Apply(receiver, method, args) => + allowSideEffects && test(receiver) && (args forall test) + case StaticApply(receiver, cls, method, args) => + allowSideEffects && test(receiver) && (args forall test) + case TraitImplApply(impl, method, args) => + allowSideEffects && (args forall test) + case GetClass(arg) => + allowSideEffects && test(arg) + case CallHelper(helper, args) => + allowSideEffects && (args forall test) + + // Casts + case AsInstanceOf(expr, _) => + (allowSideEffects || semantics.asInstanceOfs == Unchecked) && test(expr) + case Unbox(expr, _) => + (allowSideEffects || semantics.asInstanceOfs == Unchecked) && test(expr) + + // Because the env is a frozen object, env["global"] is pure + case JSBracketSelect(JSEnvInfo(), StringLiteral("global")) => true + + // JavaScript expressions that can always have side-effects + case JSNew(fun, args) => + allowSideEffects && test(fun) && (args forall test) + case JSDotSelect(qualifier, item) => + allowSideEffects && test(qualifier) + case JSBracketSelect(qualifier, item) => + allowSideEffects && test(qualifier) && test(item) + case JSFunctionApply(fun, args) => + allowSideEffects && test(fun) && (args forall test) + case JSDotMethodApply(receiver, method, args) => + allowSideEffects && test(receiver) && (args forall test) + case JSBracketMethodApply(receiver, method, args) => + allowSideEffects && test(receiver) && test(method) && (args forall test) + + // Non-expressions + case _ => false + } + test(tree) + } + + /** Test whether the given tree is a standard JS expression. + */ + def isExpression(tree: Tree): Boolean = + isExpressionInternal(tree, allowUnpure = true, allowSideEffects = true) + + /** Test whether the given tree is a side-effect-free standard JS expression. + */ + def isSideEffectFreeExpression(tree: Tree): Boolean = + isExpressionInternal(tree, allowUnpure = true, allowSideEffects = false) + + /** Test whether the given tree is a pure standard JS expression. + */ + def isPureExpression(tree: Tree): Boolean = + isExpressionInternal(tree, allowUnpure = false, allowSideEffects = false) + + def doVarDef(ident: Ident, tpe: Type, mutable: Boolean, rhs: Tree): js.Tree = { + implicit val pos = rhs.pos + tpe match { + case RecordType(fields) => + val elems = (rhs: @unchecked) match { + case RecordValue(_, elems) => + elems + case VarRef(rhsIdent, rhsMutable) => + for (RecordType.Field(fName, fOrigName, fTpe, fMutable) <- fields) + yield VarRef(makeRecordFieldIdent(rhsIdent, fName, fOrigName), + rhsMutable || fMutable)(fTpe) + } + js.Block(for { + (RecordType.Field(fName, fOrigName, fTpe, fMutable), + fRhs) <- fields zip elems + } yield { + doVarDef(makeRecordFieldIdent(ident, fName, fOrigName), + fTpe, mutable || fMutable, fRhs) + }) + + case _ => + js.VarDef(ident, mutable, transformExpr(rhs)) + } + } + + def doAssign(lhs: Tree, rhs: Tree): js.Tree = { + implicit val pos = rhs.pos + lhs.tpe match { + case RecordType(fields) => + val VarRef(ident, mutable) = lhs + val elems = (rhs: @unchecked) match { + case VarRef(rhsIdent, rhsMutable) => + for (RecordType.Field(fName, fOrigName, fTpe, fMutable) <- fields) + yield VarRef(makeRecordFieldIdent(rhsIdent, fName, fOrigName), + rhsMutable || fMutable)(fTpe) + } + js.Block(for { + (RecordType.Field(fName, fOrigName, fTpe, fMutable), + fRhs) <- fields zip elems + } yield { + doAssign(VarRef(makeRecordFieldIdent(ident, fName, fOrigName), + mutable || fMutable)(fTpe), fRhs) + }) + + case _ => + js.Assign(transformExpr(lhs), transformExpr(rhs)) + } + } + + /** Push an lhs into a (potentially complex) rhs + * lhs can be either a EmptyTree, a VarDef, a Assign or a + * Return + */ + def pushLhsInto(lhs: Tree, rhs: Tree): js.Tree = { + implicit val rhsPos = rhs.pos + + /** Push the current lhs further into a deeper rhs */ + @inline def redo(newRhs: Tree) = pushLhsInto(lhs, newRhs) + + if (rhs.tpe == NothingType && lhs != EmptyTree) { + /* A touch of peephole dead code elimination. + * Actually necessary to handle pushing an lhs into an infinite loop, + * for example. + */ + val transformedRhs = pushLhsInto(EmptyTree, rhs) + lhs match { + case VarDef(name, _, mutable, _) => + /* We still need to declare the var, in case it is used somewhere + * else in the function, where we can't dce it. + */ + js.Block(js.VarDef(name, true, js.EmptyTree), transformedRhs) + case _ => + transformedRhs + } + } else (rhs match { + // Handle the Block before testing whether it is an expression + + case Block(stats :+ expr) => + js.Block((stats map transformStat) :+ redo(expr)) + + // Base case, rhs is already a regular JS expression + + case _ if isExpression(rhs) => + (lhs: @unchecked) match { + case EmptyTree => + if (isSideEffectFreeExpression(rhs)) js.Skip() + else transformExpr(rhs) + case VarDef(name, tpe, mutable, _) => + doVarDef(name, tpe, mutable, rhs) + case Assign(lhs, _) => + doAssign(lhs, rhs) + case Return(_, None) => + js.Return(transformExpr(rhs)) + case Return(_, label @ Some(l)) => + labeledExprLHSes(l) match { + case newLhs @ Return(_, _) => + pushLhsInto(newLhs, rhs) // no need to break here + case newLhs => + js.Block(pushLhsInto(newLhs, rhs), + js.Break(label.map(transformIdent))) + } + } + + // Almost base case with RecordValue + + case RecordValue(recTpe, elems) => + (lhs: @unchecked) match { + case EmptyTree => + js.Block(elems map transformStat) + case VarDef(name, tpe, mutable, _) => + unnest(elems) { newElems => + doVarDef(name, tpe, mutable, RecordValue(recTpe, newElems)) + } + case Assign(lhs, _) => + unnest(elems) { newElems => + val temp = newSyntheticVar() + js.Block( + doVarDef(temp, recTpe, false, RecordValue(recTpe, newElems)), + doAssign(lhs, VarRef(temp, false)(recTpe))) + } + case Return(_, label @ Some(l)) => + val newLhs = labeledExprLHSes(l) + js.Block(pushLhsInto(newLhs, rhs), + js.Break(label.map(transformIdent))) + } + + // Control flow constructs + + case Labeled(label, tpe, body) => + val savedMap = labeledExprLHSes + labeledExprLHSes = labeledExprLHSes + (label -> lhs) + try { + lhs match { + case Return(_, _) => redo(body) + case _ => js.Labeled(label, redo(body)) + } + } finally { + labeledExprLHSes = savedMap + } + + case Return(expr, _) => + pushLhsInto(rhs, expr) + + case Continue(label) => + js.Continue(label.map(transformIdent)) + + case If(cond, thenp, elsep) => + unnest(cond) { newCond => + js.If(transformExpr(newCond), redo(thenp), redo(elsep)) + } + + case Try(block, errVar, handler, finalizer) => + val newHandler = + if (handler == EmptyTree) js.EmptyTree else redo(handler) + val newFinalizer = + if (finalizer == EmptyTree) js.EmptyTree else transformStat(finalizer) + + if (newHandler != js.EmptyTree && newFinalizer != js.EmptyTree) { + /* The Google Closure Compiler wrongly eliminates finally blocks, if + * the catch block throws an exception. + * Issues: #563, google/closure-compiler#186 + * + * Therefore, we desugar + * + * try { ... } catch { ... } finally { ... } + * + * into + * + * try { try { ... } catch { ... } } finally { ... } + */ + js.Try(js.Try(redo(block), errVar, newHandler, js.EmptyTree), + errVar, js.EmptyTree, newFinalizer) + } else + js.Try(redo(block), errVar, newHandler, newFinalizer) + + // TODO Treat throw as an LHS? + case Throw(expr) => + unnest(expr) { newExpr => + js.Throw(transformExpr(newExpr)) + } + + /** Matches are desugared into switches + * + * A match is different from a switch in two respects, both linked + * to match being designed to be used in expression position in + * Extended-JS. + * + * * There is no fall-through from one case to the next one, hence, + * no break statement. + * * Match supports _alternatives_ explicitly (with a switch, one + * would use the fall-through behavior to implement alternatives). + */ + case Match(selector, cases, default) => + unnest(selector) { newSelector => + val newCases = { + for { + (values, body) <- cases + newValues = (values map transformExpr) + // add the break statement + newBody = js.Block(redo(body), js.Break()) + // desugar alternatives into several cases falling through + caze <- (newValues.init map (v => (v, js.Skip()))) :+ (newValues.last, newBody) + } yield { + caze + } + } + val newDefault = + if (default == EmptyTree) js.EmptyTree + else redo(default) + js.Switch(transformExpr(newSelector), newCases, newDefault) + } + + // Scala expressions (if we reach here their arguments are not expressions) + + case New(cls, ctor, args) => + unnest(args) { newArgs => + redo(New(cls, ctor, newArgs)) + } + + case Select(qualifier, item, mutable) => + unnest(qualifier) { newQualifier => + redo(Select(newQualifier, item, mutable)(rhs.tpe)) + } + + case Apply(receiver, method, args) => + unnest(receiver, args) { (newReceiver, newArgs) => + redo(Apply(newReceiver, method, newArgs)(rhs.tpe)) + } + + case StaticApply(receiver, cls, method, args) => + unnest(receiver, args) { (newReceiver, newArgs) => + redo(StaticApply(newReceiver, cls, method, newArgs)(rhs.tpe)) + } + + case TraitImplApply(impl, method, args) => + unnest(args) { newArgs => + redo(TraitImplApply(impl, method, newArgs)(rhs.tpe)) + } + + case UnaryOp(op, lhs) => + unnest(lhs) { newLhs => + redo(UnaryOp(op, newLhs)) + } + + case BinaryOp(op, lhs, rhs) => + unnest(lhs, rhs) { (newLhs, newRhs) => + redo(BinaryOp(op, newLhs, newRhs)) + } + + case NewArray(tpe, lengths) => + unnest(lengths) { newLengths => + redo(NewArray(tpe, newLengths)) + } + + case ArrayValue(tpe, elems) => + unnest(elems) { newElems => + redo(ArrayValue(tpe, newElems)) + } + + case ArrayLength(array) => + unnest(array) { newArray => + redo(ArrayLength(newArray)) + } + + case ArraySelect(array, index) => + unnest(array, index) { (newArray, newIndex) => + redo(ArraySelect(newArray, newIndex)(rhs.tpe)) + } + + case IsInstanceOf(expr, cls) => + unnest(expr) { newExpr => + redo(IsInstanceOf(newExpr, cls)) + } + + case AsInstanceOf(expr, cls) => + if (semantics.asInstanceOfs == Unchecked) { + redo(expr) + } else { + unnest(expr) { newExpr => + redo(AsInstanceOf(newExpr, cls)) + } + } + + case Unbox(expr, charCode) => + unnest(expr) { newExpr => + redo(Unbox(newExpr, charCode)) + } + + case GetClass(expr) => + unnest(expr) { newExpr => + redo(GetClass(newExpr)) + } + + case CallHelper(helper, args) => + unnest(args) { newArgs => + redo(CallHelper(helper, newArgs)(rhs.tpe)) + } + + // JavaScript expressions (if we reach here their arguments are not expressions) + + case JSNew(ctor, args) => + unnest(ctor :: args) { newCtorAndArgs => + val newCtor :: newArgs = newCtorAndArgs + redo(JSNew(newCtor, newArgs)) + } + + case JSFunctionApply(fun, args) => + unnest(fun :: args) { newFunAndArgs => + val newFun :: newArgs = newFunAndArgs + redo(JSFunctionApply(newFun, newArgs)) + } + + case JSDotMethodApply(receiver, method, args) => + unnest(receiver :: args) { newReceiverAndArgs => + val newReceiver :: newArgs = newReceiverAndArgs + redo(JSDotMethodApply(newReceiver, method, newArgs)) + } + + case JSBracketMethodApply(receiver, method, args) => + unnest(receiver :: method :: args) { newReceiverAndArgs => + val newReceiver :: newMethod :: newArgs = newReceiverAndArgs + redo(JSBracketMethodApply(newReceiver, newMethod, newArgs)) + } + + case JSDotSelect(qualifier, item) => + unnest(qualifier) { newQualifier => + redo(JSDotSelect(newQualifier, item)) + } + + case JSBracketSelect(qualifier, item) => + unnest(qualifier, item) { (newQualifier, newItem) => + redo(JSBracketSelect(newQualifier, newItem)) + } + + case JSUnaryOp(op, lhs) => + unnest(lhs) { newLhs => + redo(JSUnaryOp(op, newLhs)) + } + + case JSBinaryOp("&&", lhs, rhs) => + if (lhs.tpe == BooleanType) { + redo(If(lhs, rhs, BooleanLiteral(false))(AnyType)) + } else { + unnest(lhs) { newLhs => + redo(If(newLhs, rhs, newLhs)(AnyType)) + } + } + + case JSBinaryOp("||", lhs, rhs) => + if (lhs.tpe == BooleanType) { + redo(If(lhs, BooleanLiteral(true), rhs)(AnyType)) + } else { + unnest(lhs) { newLhs => + redo(If(newLhs, newLhs, rhs)(AnyType)) + } + } + + case JSBinaryOp(op, lhs, rhs) => + unnest(lhs, rhs) { (newLhs, newRhs) => + redo(JSBinaryOp(op, newLhs, newRhs)) + } + + case JSArrayConstr(items) => + unnest(items) { newItems => + redo(JSArrayConstr(newItems)) + } + + case JSObjectConstr(fields) => + val names = fields map (_._1) + val items = fields map (_._2) + unnest(items) { newItems => + redo(JSObjectConstr(names.zip(newItems))) + } + + // Closures + + case Closure(captureParams, params, body, captureValues) => + unnest(captureValues) { newCaptureValues => + redo(Closure(captureParams, params, body, newCaptureValues)) + } + + case _ => + if (lhs == EmptyTree) { + /* Go "back" to transformStat() after having dived into + * expression statements. Remember that (lhs == EmptyTree) + * is a trick that we use to "add" all the code of pushLhsInto() + * to transformStat(). + */ + rhs match { + case _:Skip | _:VarDef | _:Assign | _:While | _:DoWhile | + _:Debugger | _:JSDelete | _:StoreModule | _:ClassDef => + transformStat(rhs) + case _ => + sys.error("Illegal tree in JSDesugar.pushLhsInto():\n" + + "lhs = " + lhs + "\n" + "rhs = " + rhs + + " of class " + rhs.getClass) + } + } else { + sys.error("Illegal tree in JSDesugar.pushLhsInto():\n" + + "lhs = " + lhs + "\n" + "rhs = " + rhs + + " of class " + rhs.getClass) + } + }) + } + + // Desugar Scala operations to JavaScript operations ----------------------- + + /** Desugar an expression of the IR into ES5 JS */ + def transformExpr(tree: Tree): js.Tree = { + import TreeDSL._ + + implicit val pos = tree.pos + + def or0(tree: js.Tree): js.Tree = + js.BinaryOp("|", tree, js.IntLiteral(0)) + + tree match { + // Control flow constructs + + case Block(stats :+ expr) => + js.Block((stats map transformStat) :+ transformExpr(expr)) + + // Note that these work even if thenp/elsep is not a BooleanType + case If(cond, BooleanLiteral(true), elsep) => + js.BinaryOp("||", transformExpr(cond), transformExpr(elsep)) + case If(cond, thenp, BooleanLiteral(false)) => + js.BinaryOp("&&", transformExpr(cond), transformExpr(thenp)) + + case If(cond, thenp, elsep) => + js.If(transformExpr(cond), transformExpr(thenp), transformExpr(elsep)) + + // Scala expressions + + case New(cls, ctor, args) => + js.Apply(js.New(encodeClassVar(cls.className), Nil) DOT ctor, + args map transformExpr) + + case LoadModule(cls) => + genLoadModule(cls.className) + + case RecordFieldVarRef(VarRef(name, mutable)) => + js.VarRef(name, mutable) + + case Select(qualifier, item, _) => + transformExpr(qualifier) DOT item + + case Apply(receiver, method, args) => + val newReceiver = transformExpr(receiver) + val newArgs = args map transformExpr + if (isMaybeHijackedClass(receiver.tpe) && + !Definitions.isReflProxyName(method.name)) { + val helperName = hijackedClassMethodToHelperName(method.name) + genCallHelper(helperName, newReceiver :: newArgs: _*) + } else { + js.Apply(newReceiver DOT method, newArgs) + } + + case StaticApply(receiver, cls, method, args) => + val fun = encodeClassVar(cls.className).prototype DOT method + js.Apply(fun DOT "call", (receiver :: args) map transformExpr) + + case TraitImplApply(impl, method, args) => + js.Apply(envField("i") DOT method, args map transformExpr) + + case UnaryOp(op, lhs) => + import UnaryOp._ + val newLhs = transformExpr(lhs) + (op: @switch) match { + case `typeof` => js.UnaryOp("typeof", newLhs) + case Boolean_! => js.UnaryOp("!", newLhs) + case DoubleToInt => js.BinaryOp("|", newLhs, js.IntLiteral(0)) + + case LongToInt => genLongMethodApply(newLhs, LongImpl.toInt) + case LongToDouble => genLongMethodApply(newLhs, LongImpl.toDouble) + + case DoubleToFloat => genFround(newLhs) + + case IntToLong => + genNewLong(LongImpl.initFromInt, newLhs) + case DoubleToLong => + genLongModuleApply(LongImpl.fromDouble, newLhs) + } + + case BinaryOp(op, lhs, rhs) => + import BinaryOp._ + val lhs1 = lhs match { + case UnaryOp(UnaryOp.DoubleToInt, inner) + if op == Int_& || op == Int_<< => + /* This case is emitted typically by conversions from + * Float/Double to Char/Byte/Short. We have to introduce an + * (int) cast in the IR so that it typechecks, but in JavaScript + * this is redundant because & and << already convert both their + * operands to ints. So we get rid of the conversion here. + */ + inner + case _ => + lhs + } + + val newLhs = transformExpr(lhs1) + val newRhs = transformExpr(rhs) + + (op: @switch) match { + case === | Num_== | Boolean_== => js.BinaryOp("===", newLhs, newRhs) + case !== | Num_!= | Boolean_!= => js.BinaryOp("!==", newLhs, newRhs) + + case String_+ => + if (lhs.tpe == StringType || rhs.tpe == StringType) + js.BinaryOp("+", newLhs, newRhs) + else + js.BinaryOp("+", js.BinaryOp("+", js.StringLiteral(""), newLhs), newRhs) + + case `in` => js.BinaryOp("in", newLhs, newRhs) + case `instanceof` => js.BinaryOp("instanceof", newLhs, newRhs) + + case Int_+ => or0(js.BinaryOp("+", newLhs, newRhs)) + case Int_- => + lhs match { + case IntLiteral(0) => or0(js.UnaryOp("-", newRhs)) + case _ => or0(js.BinaryOp("-", newLhs, newRhs)) + } + case Int_* => genCallHelper("imul", newLhs, newRhs) + case Int_/ => or0(js.BinaryOp("/", newLhs, newRhs)) + case Int_% => js.BinaryOp("%", newLhs, newRhs) + + case Int_| => js.BinaryOp("|", newLhs, newRhs) + case Int_& => js.BinaryOp("&", newLhs, newRhs) + case Int_^ => + lhs match { + case IntLiteral(-1) => js.UnaryOp("~", newRhs) + case _ => js.BinaryOp("^", newLhs, newRhs) + } + case Int_<< => js.BinaryOp("<<", newLhs, newRhs) + case Int_>>> => or0(js.BinaryOp(">>>", newLhs, newRhs)) + case Int_>> => js.BinaryOp(">>", newLhs, newRhs) + + case Float_+ => genFround(js.BinaryOp("+", newLhs, newRhs)) + case Float_- => + genFround(lhs match { + case DoubleLiteral(0.0) => js.UnaryOp("-", newRhs) + case _ => js.BinaryOp("-", newLhs, newRhs) + }) + case Float_* => genFround(js.BinaryOp("*", newLhs, newRhs)) + case Float_/ => genFround(js.BinaryOp("/", newLhs, newRhs)) + case Float_% => genFround(js.BinaryOp("%", newLhs, newRhs)) + + case Double_+ => js.BinaryOp("+", newLhs, newRhs) + case Double_- => + lhs match { + case DoubleLiteral(0.0) => js.UnaryOp("-", newRhs) + case _ => js.BinaryOp("-", newLhs, newRhs) + } + case Double_* => js.BinaryOp("*", newLhs, newRhs) + case Double_/ => js.BinaryOp("/", newLhs, newRhs) + case Double_% => js.BinaryOp("%", newLhs, newRhs) + + case Num_< => js.BinaryOp("<", newLhs, newRhs) + case Num_<= => js.BinaryOp("<=", newLhs, newRhs) + case Num_> => js.BinaryOp(">", newLhs, newRhs) + case Num_>= => js.BinaryOp(">=", newLhs, newRhs) + + case Long_+ => genLongMethodApply(newLhs, LongImpl.+, newRhs) + case Long_- => + lhs match { + case LongLiteral(0L) => genLongMethodApply(newRhs, LongImpl.UNARY_-) + case _ => genLongMethodApply(newLhs, LongImpl.-, newRhs) + } + case Long_* => genLongMethodApply(newLhs, LongImpl.*, newRhs) + case Long_/ => genLongMethodApply(newLhs, LongImpl./, newRhs) + case Long_% => genLongMethodApply(newLhs, LongImpl.%, newRhs) + + case Long_| => genLongMethodApply(newLhs, LongImpl.|, newRhs) + case Long_& => genLongMethodApply(newLhs, LongImpl.&, newRhs) + case Long_^ => + lhs match { + case LongLiteral(-1L) => genLongMethodApply(newRhs, LongImpl.UNARY_~) + case _ => genLongMethodApply(newLhs, LongImpl.^, newRhs) + } + case Long_<< => genLongMethodApply(newLhs, LongImpl.<<, newRhs) + case Long_>>> => genLongMethodApply(newLhs, LongImpl.>>>, newRhs) + case Long_>> => genLongMethodApply(newLhs, LongImpl.>>, newRhs) + + case Long_== => genLongMethodApply(newLhs, LongImpl.===, newRhs) + case Long_!= => genLongMethodApply(newLhs, LongImpl.!==, newRhs) + case Long_< => genLongMethodApply(newLhs, LongImpl.<, newRhs) + case Long_<= => genLongMethodApply(newLhs, LongImpl.<=, newRhs) + case Long_> => genLongMethodApply(newLhs, LongImpl.>, newRhs) + case Long_>= => genLongMethodApply(newLhs, LongImpl.>=, newRhs) + + case Boolean_| => !(!js.BinaryOp("|", newLhs, newRhs)) + case Boolean_& => !(!js.BinaryOp("&", newLhs, newRhs)) + } + + case NewArray(tpe, lengths) => + genCallHelper("newArrayObject", + genClassDataOf(tpe), js.ArrayConstr(lengths map transformExpr)) + + case ArrayValue(tpe, elems) => + genCallHelper("makeNativeArrayWrapper", + genClassDataOf(tpe), js.ArrayConstr(elems map transformExpr)) + + case ArrayLength(array) => + js.BracketSelect(js.DotSelect(transformExpr(array), + Ident("u")), js.StringLiteral("length")) + + case ArraySelect(array, index) => + js.BracketSelect(js.DotSelect(transformExpr(array), + Ident("u")), transformExpr(index)) + + case IsInstanceOf(expr, cls) => + genIsInstanceOf(transformExpr(expr), cls) + + case AsInstanceOf(expr, cls) => + val newExpr = transformExpr(expr) + if (semantics.asInstanceOfs == Unchecked) newExpr + else genAsInstanceOf(newExpr, cls) + + case Unbox(expr, charCode) => + val newExpr = transformExpr(expr) + + if (semantics.asInstanceOfs == Unchecked) { + (charCode: @switch) match { + case 'Z' => !(!newExpr) + case 'B' | 'S' | 'I' => js.BinaryOp("|", newExpr, js.IntLiteral(0)) + case 'J' => genCallHelper("uJ", newExpr) + case 'F' => genFround(newExpr) + case 'D' => js.UnaryOp("+", newExpr) + } + } else { + genCallHelper("u"+charCode, newExpr) + } + + case GetClass(expr) => + genCallHelper("objectGetClass", transformExpr(expr)) + + case CallHelper(helper, args) => + genCallHelper(helper, args map transformExpr: _*) + + // JavaScript expressions + + case JSBracketSelect(JSEnvInfo(), StringLiteral("global")) => + // Shortcut for this field which is heavily used + envField("g") + + case JSNew(constr, args) => + js.New(transformExpr(constr), args map transformExpr) + + case JSDotSelect(qualifier, item) => + js.DotSelect(transformExpr(qualifier), item) + + case JSBracketSelect(qualifier, item) => + js.BracketSelect(transformExpr(qualifier), transformExpr(item)) + + case JSFunctionApply(fun, args) => + /* Protect the fun so that if it is, e.g., + * path.f + * we emit + * (0, path.f)(args...) + * instead of + * path.f(args...) + * If we emit the latter, then `this` will be bound to `path` in + * `f`, which is sometimes extremely harmful (e.g., for builtin + * methods of `window`). + */ + val transformedFun = transformExpr(fun) + val protectedFun = transformedFun match { + case _:js.DotSelect | _:js.BracketSelect => + js.Block(js.IntLiteral(0), transformedFun) + case _ => + transformedFun + } + js.Apply(protectedFun, args map transformExpr) + + case JSDotMethodApply(receiver, method, args) => + js.Apply(js.DotSelect(transformExpr(receiver), method), + args map transformExpr) + + case JSBracketMethodApply(receiver, method, args) => + js.Apply(js.BracketSelect(transformExpr(receiver), + transformExpr(method)), args map transformExpr) + + case JSUnaryOp(op, lhs) => + js.UnaryOp(op, transformExpr(lhs)) + + case JSBinaryOp(op, lhs, rhs) => + js.BinaryOp(op, transformExpr(lhs), transformExpr(rhs)) + + case JSArrayConstr(items) => + js.ArrayConstr(items map transformExpr) + + case JSObjectConstr(fields) => + js.ObjectConstr(fields map { + case (name: Ident, value) => + (transformIdent(name), transformExpr(value)) + case (StringLiteral(name), value) => + (js.StringLiteral(name), transformExpr(value)) + }) + + case JSEnvInfo() => + envField("env") + + // Literals + + case Undefined() => js.Undefined() + case Null() => js.Null() + case BooleanLiteral(value) => js.BooleanLiteral(value) + case IntLiteral(value) => js.IntLiteral(value) + case FloatLiteral(value) => js.DoubleLiteral(value.toDouble) + case DoubleLiteral(value) => js.DoubleLiteral(value) + case StringLiteral(value) => js.StringLiteral(value) + + case LongLiteral(0L) => + genLongModuleApply(LongImpl.Zero) + case LongLiteral(value) => + val (l, m, h) = LongImpl.extractParts(value) + genNewLong(LongImpl.initFromParts, + js.IntLiteral(l), js.IntLiteral(m), js.IntLiteral(h)) + + case ClassOf(cls) => + js.Apply(js.DotSelect(genClassDataOf(cls), Ident("getClassOf")), Nil) + + // Atomic expressions + + case VarRef(name, mutable) => + js.VarRef(name, mutable) + + case This() => + js.This() + + case Closure(captureParams, params, body, captureValues) => + val transformedBody = { + val withReturn = Return(body, None) + transformStat(withReturn) match { + case js.Block(stats :+ js.Return(js.Undefined())) => js.Block(stats) + case other => other + } + } + + val innerFunction = + js.Function(params.map(transformParamDef), transformedBody) + + if (captureParams.isEmpty) { + innerFunction + } else { + js.Apply( + js.Function(captureParams.map(transformParamDef), { + js.Return(innerFunction) + }), + captureValues.map(transformExpr)) + } + + // Invalid trees + + case _ => + sys.error("Invalid tree in JSDesugar.transformExpr() "+ + s"of class ${tree.getClass}") + } + } + + def isMaybeHijackedClass(tpe: Type): Boolean = tpe match { + case ClassType(cls) => + Definitions.HijackedClasses.contains(cls) || + Definitions.AncestorsOfHijackedClasses.contains(cls) + case AnyType | UndefType | BooleanType | IntType | LongType | + FloatType | DoubleType | StringType => + true + case _ => + false + } + + val hijackedClassMethodToHelperName: Map[String, String] = Map( + "toString__T" -> "objectToString", + "clone__O" -> "objectClone", + "finalize__V" -> "objectFinalize", + "notify__V" -> "objectNotify", + "notifyAll__V" -> "objectNotifyAll", + "equals__O__Z" -> "objectEquals", + "hashCode__I" -> "objectHashCode", + + "length__I" -> "charSequenceLength", + "charAt__I__C" -> "charSequenceCharAt", + "subSequence__I__I__jl_CharSequence" -> "charSequenceSubSequence", + + "compareTo__O__I" -> "comparableCompareTo", + "compareTo__jl_Boolean__I" -> "comparableCompareTo", + "compareTo__jl_Byte__I" -> "comparableCompareTo", + "compareTo__jl_Short__I" -> "comparableCompareTo", + "compareTo__jl_Integer__I" -> "comparableCompareTo", + "compareTo__jl_Long__I" -> "comparableCompareTo", + "compareTo__jl_Float__I" -> "comparableCompareTo", + "compareTo__jl_Double__I" -> "comparableCompareTo", + "compareTo__jl_String__I" -> "comparableCompareTo", + + "booleanValue__Z" -> "booleanBooleanValue", + + "byteValue__B" -> "numberByteValue", + "shortValue__S" -> "numberShortValue", + "intValue__I" -> "numberIntValue", + "longValue__J" -> "numberLongValue", + "floatValue__F" -> "numberFloatValue", + "doubleValue__D" -> "numberDoubleValue", + + "isNaN__Z" -> "isNaN", + "isInfinite__Z" -> "isInfinite" + ) + + def genClassDataOf(cls: ReferenceType)(implicit pos: Position): js.Tree = { + cls match { + case ClassType(className) => + encodeClassField("d", className) + case ArrayType(base, dims) => + (1 to dims).foldLeft(encodeClassField("d", base)) { (prev, _) => + js.Apply(js.DotSelect(prev, js.Ident("getArrayOf")), Nil) + } + } + } + + private def genFround(arg: js.Tree)(implicit pos: Position): js.Tree = { + genCallHelper("fround", arg) + } + + private def genNewLong(ctor: String, args: js.Tree*)( + implicit pos: Position): js.Tree = { + import TreeDSL._ + js.Apply( + js.New(encodeClassVar(LongImpl.RuntimeLongClass), Nil) DOT ctor, + args.toList) + } + + private def genLongMethodApply(receiver: js.Tree, methodName: String, + args: js.Tree*)(implicit pos: Position): js.Tree = { + import TreeDSL._ + js.Apply(receiver DOT methodName, args.toList) + } + + private def genLongModuleApply(methodName: String, args: js.Tree*)( + implicit pos: Position): js.Tree = { + import TreeDSL._ + js.Apply( + genLoadModule(LongImpl.RuntimeLongModuleClass) DOT methodName, + args.toList) + } + + private def genLoadModule(moduleClass: String)( + implicit pos: Position): js.Tree = { + import TreeDSL._ + assert(moduleClass.endsWith("$"), + s"Trying to load module for non-module class $moduleClass") + val moduleName = moduleClass.dropRight(1) + js.Apply(envField("m") DOT moduleName, Nil) + } + + } + + // Helpers + + private[javascript] def genIsInstanceOf(expr: js.Tree, cls: ReferenceType)( + implicit pos: Position): js.Tree = + genIsAsInstanceOf(expr, cls, test = true) + + private def genAsInstanceOf(expr: js.Tree, cls: ReferenceType)( + implicit pos: Position): js.Tree = + genIsAsInstanceOf(expr, cls, test = false) + + private def genIsAsInstanceOf(expr: js.Tree, cls: ReferenceType, test: Boolean)( + implicit pos: Position): js.Tree = { + import Definitions._ + import TreeDSL._ + + cls match { + case ClassType(className0) => + val className = + if (className0 == BoxedLongClass) LongImpl.RuntimeLongClass + else className0 + + if (HijackedBoxedClasses.contains(className)) { + if (test) { + className match { + case BoxedUnitClass => expr === js.Undefined() + case BoxedBooleanClass => typeof(expr) === "boolean" + case BoxedByteClass => genCallHelper("isByte", expr) + case BoxedShortClass => genCallHelper("isShort", expr) + case BoxedIntegerClass => genCallHelper("isInt", expr) + case BoxedFloatClass => genCallHelper("isFloat", expr) + case BoxedDoubleClass => typeof(expr) === "number" + } + } else { + className match { + case BoxedUnitClass => genCallHelper("asUnit", expr) + case BoxedBooleanClass => genCallHelper("asBoolean", expr) + case BoxedByteClass => genCallHelper("asByte", expr) + case BoxedShortClass => genCallHelper("asShort", expr) + case BoxedIntegerClass => genCallHelper("asInt", expr) + case BoxedFloatClass => genCallHelper("asFloat", expr) + case BoxedDoubleClass => genCallHelper("asDouble", expr) + } + } + } else { + js.Apply( + envField(if (test) "is" else "as") DOT js.Ident(className), + List(expr)) + } + + case ArrayType(base, depth) => + js.Apply( + envField(if (test) "isArrayOf" else "asArrayOf") DOT js.Ident(base), + List(expr, js.IntLiteral(depth))) + } + } + + private[javascript] def genCallHelper(helperName: String, args: js.Tree*)( + implicit pos: Position): js.Tree = + js.Apply(envField(helperName), args.toList) + + private[javascript] def encodeClassVar(className: String)( + implicit pos: Position): js.Tree = + encodeClassField("c", className) + + private[javascript] def encodeClassField(field: String, className: String)( + implicit pos: Position): js.Tree = + js.DotSelect(envField(field), js.Ident(className)) + + private[javascript] def envField(field: String)(implicit pos: Position): js.Tree = + js.DotSelect(js.VarRef(js.Ident(ScalaJSEnvironmentName), false), + js.Ident(field)) + + private[javascript] implicit class MyTreeOps(val self: js.Tree) { + def prototype(implicit pos: Position): js.Tree = + js.DotSelect(self, js.Ident("prototype")) + } +} diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/javascript/LongImpl.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/javascript/LongImpl.scala new file mode 100644 index 0000000..70b81a3 --- /dev/null +++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/javascript/LongImpl.scala @@ -0,0 +1,116 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js tools ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.tools.javascript + +object LongImpl { + final val RuntimeLongClass = "sjsr_RuntimeLong" + final val RuntimeLongModuleClass = "sjsr_RuntimeLong$" + + private final val SigUnary = "__sjsr_RuntimeLong" + private final val SigBinary = "__sjsr_RuntimeLong__sjsr_RuntimeLong" + private final val SigShift = "__I__sjsr_RuntimeLong" + private final val SigCompare = "__sjsr_RuntimeLong__Z" + + final val UNARY_- = "unary$und$minus" + SigUnary + final val UNARY_~ = "unary$und$tilde" + SigUnary + + final val + = "$$plus" + SigBinary + final val - = "$$minus" + SigBinary + final val * = "$$times" + SigBinary + final val / = "$$div" + SigBinary + final val % = "$$percent" + SigBinary + + final val | = "$$bar" + SigBinary + final val & = "$$amp" + SigBinary + final val ^ = "$$up" + SigBinary + + final val << = "$$less$less" + SigShift + final val >>> = "$$greater$greater$greater" + SigShift + final val >> = "$$greater$greater" + SigShift + + final val === = "equals" + SigCompare + final val !== = "notEquals" + SigCompare + final val < = "$$less" + SigCompare + final val <= = "$$less$eq" + SigCompare + final val > = "$$greater" + SigCompare + final val >= = "$$greater$eq" + SigCompare + + final val toInt = "toInt" + "__I" + final val toDouble = "toDouble" + "__D" + + final val byteValue = "byteValue__B" + final val shortValue = "shortValue__S" + final val intValue = "intValue__I" + final val longValue = "longValue__J" + final val floatValue = "floatValue__F" + final val doubleValue = "doubleValue__D" + + final val equals_ = "equals__O__Z" + final val hashCode_ = "hashCode__I" + final val compareTo = "compareTo__jl_Long__I" + final val compareToO = "compareTo__O__I" + + private val OperatorMethods = Set( + UNARY_-, UNARY_~, this.+, this.-, *, /, %, |, &, ^, <<, >>>, >>, + ===, !==, <, <=, >, >=, toInt, toDouble) + + private val BoxedLongMethods = Set( + byteValue, shortValue, intValue, longValue, floatValue, doubleValue, + equals_, hashCode_, compareTo, compareToO) + + val AllMethods = OperatorMethods ++ BoxedLongMethods + + // Methods used for intrinsics + + final val bitCount = "bitCount__I" + final val signum = "signum__sjsr_RuntimeLong" + final val numberOfLeadingZeros = "numberOfLeadingZeros__I" + final val numberOfTrailingZeros = "numberOfTrailingZeros__I" + final val toBinaryString = "toBinaryString__T" + final val toHexString = "toHexString__T" + final val toOctalString = "toOctalString__T" + + val AllIntrinsicMethods = Set( + bitCount, signum, numberOfLeadingZeros, numberOfTrailingZeros, + toBinaryString, toHexString, toOctalString) + + // Constructors + + final val initFromParts = "init___I__I__I" + final val initFromInt = "init___I" + + val AllConstructors = Set( + initFromParts, initFromInt) + + // Methods on the companion + + final val fromDouble = "fromDouble__D__sjsr_RuntimeLong" + + final val Zero = "Zero__sjsr_RuntimeLong" + + val AllModuleMethods = Set( + fromDouble, Zero) + + // Boldly copied from library/scala.scalajs.runtime.RuntimeLong + + /** Number of relevant bits in l and m each. */ + private final val BITS = 22 + /** Number of relevant bits in l and m together. */ + private final val BITS01 = 2 * BITS + /** Number of relevant bits in h. */ + private final val BITS2 = 64 - BITS01 + /** Bitmask for l and m. */ + private final val MASK = (1 << BITS) - 1 + /** Bitmask for h. */ + private final val MASK_2 = (1 << BITS2) - 1 + + def extractParts(value: Long): (Int, Int, Int) = + (value.toInt & MASK, (value >> BITS).toInt & MASK, (value >> BITS01).toInt & MASK_2) +} diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/javascript/Printers.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/javascript/Printers.scala new file mode 100644 index 0000000..264c548 --- /dev/null +++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/javascript/Printers.scala @@ -0,0 +1,420 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js tools ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.tools.javascript + +import scala.annotation.switch + +import scala.util.control.Breaks + +import java.io.Writer +import java.net.URI + +import scala.scalajs.ir +import ir.Position +import ir.Position.NoPosition +import ir.Printers.IndentationManager +import ir.Utils.escapeJS + +import Trees._ + +import scala.scalajs.tools.sourcemap.SourceMapWriter + +object Printers { + + class JSTreePrinter(protected val out: Writer) extends IndentationManager { + def printTopLevelTree(tree: Tree) { + tree match { + case Skip() => + // do not print anything + case Block(stats) => + for (stat <- stats) + printTopLevelTree(stat) + case _ => + printStat(tree) + if (shouldPrintSepAfterTree(tree)) + print(";") + println() + } + } + + protected def shouldPrintSepAfterTree(tree: Tree): Boolean = + !tree.isInstanceOf[DocComment] + + protected def printBlock(tree: Tree): Unit = { + val trees = tree match { + case Block(trees) => trees + case _ => List(tree) + } + print("{"); indent(); println() + printSeq(trees) { x => + printStat(x) + } { x => + if (shouldPrintSepAfterTree(x)) + print(";") + println() + } + undent(); println(); print("}") + } + + protected def printSig(args: List[ParamDef]): Unit = { + printRow(args, "(", ", ", ")") + print(" ") + } + + protected def printArgs(args: List[Tree]): Unit = { + printRow(args, "(", ", ", ")") + } + + def printStat(tree: Tree): Unit = + printTree(tree, isStat = true) + + def printTree(tree: Tree, isStat: Boolean): Unit = { + tree match { + case EmptyTree => + print("<empty>") + + // Comments + + case DocComment(text) => + val lines = text.split("\n").toList + if (lines.tail.isEmpty) { + print("/** ", lines.head, " */") + } else { + print("/** ", lines.head); println() + for (line <- lines.tail) { + print(" * ", line); println() + } + print(" */") + } + + // Definitions + + case VarDef(ident, mutable, rhs) => + print("var ", ident) + if (rhs != EmptyTree) + print(" = ", rhs) + + case ParamDef(ident, mutable) => + print(ident) + + // Control flow constructs + + case Skip() => + print("/*<skip>*/") + + case tree @ Block(trees) => + if (isStat) + printBlock(tree) + else + printRow(trees, "(", ", ", ")") + + case Labeled(label, body) => + print(label, ": ") + printBlock(body) + + case Assign(lhs, rhs) => + print(lhs, " = ", rhs) + + case Return(expr) => + print("return ", expr) + + case If(cond, thenp, elsep) => + if (isStat) { + print("if (", cond, ") ") + printBlock(thenp) + elsep match { + case Skip() => () + case If(_, _, _) => + print(" else ") + printTree(elsep, isStat) + case _ => + print(" else ") + printBlock(elsep) + } + } else { + print("(", cond, " ? ", thenp, " : ", elsep, ")") + } + + case While(cond, body, label) => + if (label.isDefined) + print(label.get, ": ") + print("while (", cond, ") ") + printBlock(body) + + case DoWhile(body, cond, label) => + if (label.isDefined) + print(label.get, ": ") + print("do ") + printBlock(body) + print(" while (", cond, ")") + + case Try(block, errVar, handler, finalizer) => + print("try ") + printBlock(block) + if (handler != EmptyTree) { + print(" catch (", errVar, ") ") + printBlock(handler) + } + if (finalizer != EmptyTree) { + print(" finally ") + printBlock(finalizer) + } + + case Throw(expr) => + print("throw ", expr) + + case Break(label) => + if (label.isEmpty) print("break") + else print("break ", label.get) + + case Continue(label) => + if (label.isEmpty) print("continue") + else print("continue ", label.get) + + case Switch(selector, cases, default) => + print("switch (", selector, ") ") + print("{"); indent + for ((value, body) <- cases) { + println() + print("case ", value, ":"); indent; println() + printStat(body) + print(";") + undent + } + if (default != EmptyTree) { + println() + print("default:"); indent; println() + printStat(default) + print(";") + undent + } + undent; println(); print("}") + + case Debugger() => + print("debugger") + + // Expressions + + case New(ctor, args) => + def containsOnlySelectsFromAtom(tree: Tree): Boolean = tree match { + case DotSelect(qual, _) => containsOnlySelectsFromAtom(qual) + case BracketSelect(qual, _) => containsOnlySelectsFromAtom(qual) + case VarRef(_, _) => true + case This() => true + case _ => false // in particular, Apply + } + if (containsOnlySelectsFromAtom(ctor)) + print("new ", ctor) + else + print("new (", ctor, ")") + printArgs(args) + + case DotSelect(qualifier, item) => + print(qualifier, ".", item) + + case BracketSelect(qualifier, item) => + print(qualifier, "[", item, "]") + + case Apply(fun, args) => + print(fun) + printArgs(args) + + case Delete(prop) => + print("delete ", prop) + + case UnaryOp("typeof", lhs) => + print("typeof(", lhs, ")") + + case UnaryOp(op, lhs) => + print("(", op, lhs, ")") + + case BinaryOp(op, lhs, rhs) => + print("(", lhs, " ", op, " ", rhs, ")") + + case ArrayConstr(items) => + printRow(items, "[", ", ", "]") + + case ObjectConstr(Nil) => + print("{}") + + case ObjectConstr(fields) => + print("{"); indent; println() + printSeq(fields) { + case (name, value) => print(name, ": ", value) + } { _ => + print(",") + println() + } + undent; println(); print("}") + + // Literals + + case Undefined() => + print("(void 0)") + + case Null() => + print("null") + + case BooleanLiteral(value) => + print(if (value) "true" else "false") + + case IntLiteral(value) => + if (value >= 0) + print(value) + else + print("(", value, ")") + + case DoubleLiteral(value) => + if (value == 0 && 1 / value < 0) + print("(-0)") + else if (value >= 0) + print(value) + else + print("(", value, ")") + + case StringLiteral(value) => + print("\"", escapeJS(value), "\"") + + // Atomic expressions + + case VarRef(ident, _) => + print(ident) + + case This() => + print("this") + + case Function(args, body) => + print("(function") + printSig(args) + printBlock(body) + print(")") + + case _ => + print(s"<error, elem of class ${tree.getClass()}>") + } + } + + protected def printIdent(ident: Ident): Unit = + printString(escapeJS(ident.name)) + + def printOne(arg: Any): Unit = arg match { + case tree: Tree => + printTree(tree, isStat = false) + case ident: Ident => + printIdent(ident) + case arg => + printString(if (arg == null) "null" else arg.toString) + } + + protected def printString(s: String): Unit = { + out.write(s) + } + + // Make it public + override def println(): Unit = super.println() + + def complete(): Unit = () + } + + class JSTreePrinterWithSourceMap(_out: Writer, + sourceMap: SourceMapWriter) extends JSTreePrinter(_out) { + + private var column = 0 + + override def printTree(tree: Tree, isStat: Boolean): Unit = { + val pos = tree.pos + if (pos.isDefined) + sourceMap.startNode(column, pos) + + super.printTree(tree, isStat) + + if (pos.isDefined) + sourceMap.endNode(column) + } + + override protected def printIdent(ident: Ident): Unit = { + if (ident.pos.isDefined) + sourceMap.startNode(column, ident.pos, ident.originalName) + super.printIdent(ident) + if (ident.pos.isDefined) + sourceMap.endNode(column) + } + + override def println(): Unit = { + super.println() + sourceMap.nextLine() + column = this.indentMargin + } + + override protected def printString(s: String): Unit = { + // assume no EOL char in s, and assume s only has ASCII characters + super.printString(s) + column += s.length() + } + + override def complete(): Unit = { + sourceMap.complete() + super.complete() + } + } + + /** Prints a tree to find original locations based on line numbers. + * @param untilLine last 0-based line the positions should be recorded for + */ + class ReverseSourceMapPrinter(untilLine: Int) + extends JSTreePrinter(ReverseSourceMapPrinter.NullWriter) { + + private val positions = Array.fill(untilLine+1)(NoPosition) + private var curLine = 0 + + private val doneBreak = new Breaks + + def apply(x: Int): Position = positions(x) + + def reverseSourceMap(tree: Tree): Unit = doneBreak.breakable { + printTopLevelTree(tree) + } + + override def printTree(tree: Tree, isStat: Boolean): Unit = { + if (positions(curLine).isEmpty) + positions(curLine) = tree.pos + + super.printTree(tree, isStat) + } + + override protected def printIdent(ident: Ident): Unit = { + if (positions(curLine).isEmpty) + positions(curLine) = ident.pos + + super.printIdent(ident) + } + + override def println(): Unit = { + super.println() + curLine += 1 + if (curLine > untilLine) + doneBreak.break() + } + + override protected def printString(s: String): Unit = { + // assume no EOL char in s, and assume s only has ASCII characters + // therefore, we fully ignore the string + } + } + + object ReverseSourceMapPrinter { + private object NullWriter extends Writer { + def close(): Unit = () + def flush(): Unit = () + def write(buf: Array[Char], off: Int, len: Int): Unit = () + } + } + +} diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/javascript/ScalaJSClassEmitter.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/javascript/ScalaJSClassEmitter.scala new file mode 100644 index 0000000..b249f88 --- /dev/null +++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/javascript/ScalaJSClassEmitter.scala @@ -0,0 +1,569 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js tools ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.tools.javascript + +import scala.scalajs.ir._ +import Position._ +import Transformers._ +import scala.scalajs.ir.Trees._ +import Types._ + +import scala.scalajs.tools.sem._ +import CheckedBehavior.Unchecked + +import scala.scalajs.tools.javascript.{Trees => js} + +/** Defines methods to emit Scala.js classes to JavaScript code. + * The results are completely desugared. + */ +final class ScalaJSClassEmitter(semantics: Semantics) { + + import JSDesugaring._ + + /** Desugar a Scala.js class into ECMAScript 5 constructs */ + def genClassDef(tree: ClassDef): js.Tree = { + implicit val pos = tree.pos + val kind = tree.kind + + var reverseParts: List[js.Tree] = Nil + + if (kind == ClassKind.TraitImpl) { + reverseParts ::= genTraitImpl(tree) + } else { + if (kind.isClass) + reverseParts ::= genClass(tree) + if (kind.isClass || kind == ClassKind.Interface || + tree.name.name == Definitions.StringClass) + reverseParts ::= genInstanceTests(tree) + reverseParts ::= genArrayInstanceTests(tree) + reverseParts ::= genTypeData(tree) + if (kind.isClass) + reverseParts ::= genSetTypeData(tree) + if (kind == ClassKind.ModuleClass) + reverseParts ::= genModuleAccessor(tree) + if (kind.isClass) + reverseParts ::= genClassExports(tree) + } + + js.Block(reverseParts.reverse) + } + + def genClass(tree: ClassDef): js.Tree = { + val className = tree.name.name + val typeFunctionDef = genConstructor(tree) + val memberDefs = tree.defs collect { + case m: MethodDef => + genMethod(className, m) + case p: PropertyDef => + genProperty(className, p) + } + + js.Block(typeFunctionDef :: memberDefs)(tree.pos) + } + + /** Generates the JS constructor for a class. */ + def genConstructor(tree: ClassDef): js.Tree = { + assert(tree.kind.isClass) + + val classIdent = tree.name + val className = classIdent.name + val tpe = ClassType(className) + + assert(tree.parent.isDefined || className == Definitions.ObjectClass, + "Class $className is missing a parent class") + + val ctorFun = { + val superCtorCall = tree.parent.fold[js.Tree] { + js.Skip()(tree.pos) + } { parentIdent => + implicit val pos = tree.pos + js.Apply( + js.DotSelect(encodeClassVar(parentIdent.name), js.Ident("call")), + List(js.This())) + } + val fieldDefs = for { + field @ VarDef(name, vtpe, mutable, rhs) <- tree.defs + } yield { + implicit val pos = field.pos + desugarJavaScript( + Assign(Select(This()(tpe), name, mutable)(vtpe), rhs), + semantics) + } + js.Function(Nil, + js.Block(superCtorCall :: fieldDefs)(tree.pos))(tree.pos) + } + + { + implicit val pos = tree.pos + val typeVar = encodeClassVar(className) + val docComment = js.DocComment("@constructor") + val ctorDef = js.Assign(typeVar, ctorFun) + + val chainProto = tree.parent.fold[js.Tree] { + js.Skip() + } { parentIdent => + js.Block( + js.Assign(typeVar.prototype, + js.New(js.DotSelect(envField("h"), parentIdent), Nil)), + genAddToPrototype(className, js.Ident("constructor"), typeVar) + ) + } + + val inheritableCtorDef = { + val inheritableCtorVar = + js.DotSelect(envField("h"), classIdent) + js.Block( + js.DocComment("@constructor"), + js.Assign(inheritableCtorVar, js.Function(Nil, js.Skip())), + js.Assign(inheritableCtorVar.prototype, typeVar.prototype) + ) + } + + js.Block(docComment, ctorDef, chainProto, inheritableCtorDef) + } + } + + /** Generates a method. */ + def genMethod(className: String, method: MethodDef): js.Tree = { + implicit val pos = method.pos + val methodFun = js.Function(method.args.map(transformParamDef), + desugarBody(method.body, method.resultType == NoType)) + genAddToPrototype(className, method.name, methodFun) + } + + /** Generates a property. */ + def genProperty(className: String, property: PropertyDef): js.Tree = { + implicit val pos = property.pos + val classType = ClassType(className) + + // defineProperty method + val defProp = + js.BracketSelect(js.VarRef(js.Ident("Object"), false), + js.StringLiteral("defineProperty")) + + // class prototype + val proto = encodeClassVar(className).prototype + + // property name + val name = property.name match { + case StringLiteral(value) => + js.StringLiteral(value) + case id: Ident => + // We need to work around the closure compiler. Call propertyName to + // get a string representation of the optimized name + genCallHelper("propertyName", + js.ObjectConstr(transformIdent(id) -> js.IntLiteral(0) :: Nil)) + } + + // Options passed to the defineProperty method + val descriptor = js.ObjectConstr { + // Basic config + val base = + js.StringLiteral("enumerable") -> js.BooleanLiteral(true) :: Nil + + // Optionally add getter + val wget = + if (property.getterBody == EmptyTree) base + else js.StringLiteral("get") -> + js.Function(Nil, desugarBody(property.getterBody, isStat = false)) :: base + + // Optionally add setter + if (property.setterBody == EmptyTree) wget + else js.StringLiteral("set") -> + js.Function(transformParamDef(property.setterArg) :: Nil, + desugarBody(property.setterBody, isStat = true)) :: wget + } + + js.Apply(defProp, proto :: name :: descriptor :: Nil) + } + + /** Generate `classVar.prototype.name = value` */ + def genAddToPrototype(className: String, name: js.PropertyName, + value: js.Tree)(implicit pos: Position): js.Tree = { + val proto = encodeClassVar(className).prototype + val select = name match { + case name: js.Ident => js.DotSelect(proto, name) + case name: js.StringLiteral => js.BracketSelect(proto, name) + } + js.Assign(select, value) + } + + /** Generate `classVar.prototype.name = value` */ + def genAddToPrototype(className: String, name: PropertyName, + value: js.Tree)(implicit pos: Position): js.Tree = { + val newName = name match { + case ident: Ident => transformIdent(ident) + case StringLiteral(value) => js.StringLiteral(value) + } + genAddToPrototype(className, newName, value) + } + + def genInstanceTests(tree: ClassDef): js.Tree = { + import Definitions._ + import TreeDSL._ + + implicit val pos = tree.pos + + val classIdent = transformIdent(tree.name) + val className = classIdent.name + val displayName = decodeClassName(className) + + val isAncestorOfString = + AncestorsOfStringClass.contains(className) + val isAncestorOfHijackedNumberClass = + AncestorsOfHijackedNumberClasses.contains(className) + val isAncestorOfBoxedBooleanClass = + AncestorsOfBoxedBooleanClass.contains(className) + + val objParam = js.ParamDef(Ident("obj"), mutable = false) + val obj = objParam.ref + + val createIsStat = { + envField("is") DOT classIdent := + js.Function(List(objParam), js.Return(className match { + case Definitions.ObjectClass => + js.BinaryOp("!==", obj, js.Null()) + + case Definitions.StringClass => + js.UnaryOp("typeof", obj) === js.StringLiteral("string") + + case _ => + var test = (obj && (obj DOT "$classData") && + (obj DOT "$classData" DOT "ancestors" DOT classIdent)) + + if (isAncestorOfString) + test = test || ( + js.UnaryOp("typeof", obj) === js.StringLiteral("string")) + if (isAncestorOfHijackedNumberClass) + test = test || ( + js.UnaryOp("typeof", obj) === js.StringLiteral("number")) + if (isAncestorOfBoxedBooleanClass) + test = test || ( + js.UnaryOp("typeof", obj) === js.StringLiteral("boolean")) + + !(!test) + })) + } + + val createAsStat = if (semantics.asInstanceOfs == Unchecked) { + js.Skip() + } else { + envField("as") DOT classIdent := + js.Function(List(objParam), js.Return(className match { + case Definitions.ObjectClass => + obj + + case _ => + js.If(js.Apply(envField("is") DOT classIdent, List(obj)) || + (obj === js.Null()), { + obj + }, { + genCallHelper("throwClassCastException", + obj, js.StringLiteral(displayName)) + }) + })) + } + + js.Block(createIsStat, createAsStat) + } + + def genArrayInstanceTests(tree: ClassDef): js.Tree = { + import Definitions._ + import TreeDSL._ + + implicit val pos = tree.pos + + val classIdent = transformIdent(tree.name) + val className = classIdent.name + val displayName = decodeClassName(className) + + val objParam = js.ParamDef(Ident("obj"), mutable = false) + val obj = objParam.ref + + val depthParam = js.ParamDef(Ident("depth"), mutable = false) + val depth = depthParam.ref + + val createIsArrayOfStat = { + envField("isArrayOf") DOT classIdent := + js.Function(List(objParam, depthParam), className match { + case Definitions.ObjectClass => + val dataVarDef = js.VarDef(Ident("data"), false, { + obj && (obj DOT "$classData") + }) + val data = dataVarDef.ref + js.Block( + dataVarDef, + js.If(!data, { + js.Return(js.BooleanLiteral(false)) + }, { + val arrayDepthVarDef = js.VarDef(Ident("arrayDepth"), false, { + (data DOT "arrayDepth") || js.IntLiteral(0) + }) + val arrayDepth = arrayDepthVarDef.ref + js.Block( + arrayDepthVarDef, + js.Return { + // Array[A] </: Array[Array[A]] + !js.BinaryOp("<", arrayDepth, depth) && ( + // Array[Array[A]] <: Array[Object] + js.BinaryOp(">", arrayDepth, depth) || + // Array[Int] </: Array[Object] + !js.BracketSelect(data DOT "arrayBase", js.StringLiteral("isPrimitive")) + ) + }) + })) + + case _ => + js.Return(!(!(obj && (obj DOT "$classData") && + ((obj DOT "$classData" DOT "arrayDepth") === depth) && + (obj DOT "$classData" DOT "arrayBase" DOT "ancestors" DOT classIdent)))) + }) + } + + val createAsArrayOfStat = if (semantics.asInstanceOfs == Unchecked) { + js.Skip() + } else { + envField("asArrayOf") DOT classIdent := + js.Function(List(objParam, depthParam), js.Return { + js.If(js.Apply(envField("isArrayOf") DOT classIdent, List(obj, depth)) || + (obj === js.Null()), { + obj + }, { + genCallHelper("throwArrayCastException", + obj, js.StringLiteral("L"+displayName+";"), depth) + }) + }) + } + + js.Block(createIsArrayOfStat, createAsArrayOfStat) + } + + def genTypeData(tree: ClassDef): js.Tree = { + import Definitions._ + import TreeDSL._ + + implicit val pos = tree.pos + + val classIdent = transformIdent(tree.name) + val className = classIdent.name + val kind = tree.kind + assert(kind.isType) + + val isObjectClass = + className == ObjectClass + val isHijackedBoxedClass = + HijackedBoxedClasses.contains(className) + val isAncestorOfHijackedClass = + AncestorsOfHijackedClasses.contains(className) + + val parentData = tree.parent.fold[js.Tree] { + if (isObjectClass) js.Null() + else js.Undefined() + } { parent => + envField("d") DOT parent + } + + val ancestorsRecord = js.ObjectConstr( + for (ancestor <- classIdent :: tree.ancestors.map(transformIdent)) + yield (ancestor, js.IntLiteral(1))) + + val typeData = js.New(envField("ClassTypeData"), List( + js.ObjectConstr(List(classIdent -> js.IntLiteral(0))), + js.BooleanLiteral(kind == ClassKind.Interface), + js.StringLiteral(decodeClassName(className)), + parentData, + ancestorsRecord + ) ++ ( + // Optional parameter isInstance + if (isObjectClass) { + /* Object has special ScalaJS.is.O *and* ScalaJS.isArrayOf.O. */ + List( + envField("is") DOT classIdent, + envField("isArrayOf") DOT classIdent) + } else if (isHijackedBoxedClass) { + /* Hijacked boxed classes have a special isInstanceOf test. */ + val xParam = js.ParamDef(Ident("x"), mutable = false) + List(js.Function(List(xParam), js.Return { + genIsInstanceOf(xParam.ref, ClassType(className)) + })) + } else if (isAncestorOfHijackedClass || className == StringClass) { + /* java.lang.String and ancestors of hijacked classes have a normal + * ScalaJS.is.pack_Class test but with a non-standard behavior. */ + List(envField("is") DOT classIdent) + } else { + // For other classes, the isInstance function can be inferred. + Nil + } + )) + + envField("d") DOT classIdent := typeData + } + + def genSetTypeData(tree: ClassDef): js.Tree = { + import TreeDSL._ + + implicit val pos = tree.pos + + assert(tree.kind.isClass) + + encodeClassVar(tree.name.name).prototype DOT "$classData" := + envField("d") DOT tree.name + } + + def genModuleAccessor(tree: ClassDef): js.Tree = { + import TreeDSL._ + + implicit val pos = tree.pos + + val classIdent = transformIdent(tree.name) + val className = classIdent.name + val tpe = ClassType(className) + + require(tree.kind == ClassKind.ModuleClass, + s"genModuleAccessor called with non-module class: $className") + assert(className.endsWith("$")) + + val moduleName = className.dropRight(1) + val moduleIdent = Ident(moduleName) + + val moduleInstanceVar = envField("n") DOT moduleIdent + val accessorVar = envField("m") DOT moduleIdent + + val createModuleInstanceField = { + moduleInstanceVar := js.Undefined() + } + + val createAccessor = { + accessorVar := js.Function(Nil, js.Block( + js.If(!(moduleInstanceVar), { + moduleInstanceVar := + js.Apply(js.New(encodeClassVar(className), Nil) DOT js.Ident("init___"), Nil) + }, js.Skip()), + js.Return(moduleInstanceVar) + )) + } + + js.Block(createModuleInstanceField, createAccessor) + } + + def genClassExports(tree: ClassDef): js.Tree = { + val exports = tree.defs collect { + case e: ConstructorExportDef => + genConstructorExportDef(tree, e) + case e: ModuleExportDef => + genModuleExportDef(tree, e) + } + + js.Block(exports)(tree.pos) + } + + def genConstructorExportDef(cd: ClassDef, tree: ConstructorExportDef): js.Tree = { + import TreeDSL._ + + implicit val pos = tree.pos + val classType = ClassType(cd.name.name) + val ConstructorExportDef(fullName, args, body) = tree + + val baseCtor = envField("c") DOT cd.name + val (createNamespace, expCtorVar) = genCreateNamespaceInExports(fullName) + + js.Block( + createNamespace, + js.DocComment("@constructor"), + expCtorVar := js.Function(args.map(transformParamDef), js.Block( + js.Apply(js.DotSelect(baseCtor, js.Ident("call")), List(js.This())), + desugarBody(body, isStat = true) + )), + expCtorVar DOT "prototype" := baseCtor DOT "prototype" + ) + } + + def genModuleExportDef(cd: ClassDef, tree: ModuleExportDef): js.Tree = { + import TreeDSL._ + + implicit val pos = tree.pos + + val baseAccessor = + envField("m") DOT cd.name.name.dropRight(1) + val (createNamespace, expAccessorVar) = + genCreateNamespaceInExports(tree.fullName) + + js.Block( + createNamespace, + expAccessorVar := baseAccessor + ) + } + + def genTraitImpl(tree: ClassDef): js.Tree = { + val traitImplName = tree.name.name + val defs = tree.defs collect { + case m: MethodDef => + genTraitImplMethod(traitImplName, m) + } + js.Block(defs)(tree.pos) + } + + def genTraitImplMethod(traitImplName: String, tree: MethodDef): js.Tree = { + implicit val pos = tree.pos + val MethodDef(name: Ident, args, resultType, body) = tree + js.Assign( + js.DotSelect(envField("i"), name), + js.Function(args.map(transformParamDef), + desugarBody(body, resultType == NoType))) + } + + /** Generate a dummy parent. Used by ScalaJSOptimizer */ + def genDummyParent(name: String): js.Tree = { + implicit val pos = Position.NoPosition + + js.Block( + js.DocComment("@constructor (dummy parent)")) + js.Assign(js.DotSelect(envField("h"), js.Ident(name)), + js.Function(Nil, js.Skip()) + ) + } + + // Helpers + + /** Desugars a function body of the IR into ES5 JavaScript. */ + private def desugarBody(tree: Tree, isStat: Boolean): js.Tree = { + implicit val pos = tree.pos + val withReturn = + if (isStat) tree + else Return(tree) + desugarJavaScript(withReturn, semantics) match { + case js.Block(stats :+ js.Return(js.Undefined())) => js.Block(stats) + case other => other + } + } + + /** Gen JS code for assigning an rhs to a qualified name in the exports scope. + * For example, given the qualified name "foo.bar.Something", generates: + * + * ScalaJS.e["foo"] = ScalaJS.e["foo"] || {}; + * ScalaJS.e["foo"]["bar"] = ScalaJS.e["foo"]["bar"] || {}; + * + * Returns (statements, ScalaJS.e["foo"]["bar"]["Something"]) + */ + private def genCreateNamespaceInExports(qualName: String)( + implicit pos: Position): (js.Tree, js.Tree) = { + val parts = qualName.split("\\.") + val statements = List.newBuilder[js.Tree] + var namespace = envField("e") + for (i <- 0 until parts.length-1) { + namespace = js.BracketSelect(namespace, js.StringLiteral(parts(i))) + statements += + js.Assign(namespace, js.BinaryOp("||", namespace, js.ObjectConstr(Nil))) + } + val lhs = js.BracketSelect(namespace, js.StringLiteral(parts.last)) + (js.Block(statements.result()), lhs) + } + +} diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/javascript/TreeDSL.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/javascript/TreeDSL.scala new file mode 100644 index 0000000..3ac54d8 --- /dev/null +++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/javascript/TreeDSL.scala @@ -0,0 +1,50 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js tools ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.tools.javascript + +import scala.language.implicitConversions + +import scala.scalajs.ir.Position + +import Trees._ + +private[javascript] object TreeDSL { + implicit class TreeOps(val self: Tree) extends AnyVal { + /** Select a member */ + def DOT(field: Ident)(implicit pos: Position): DotSelect = + DotSelect(self, field) + + /** Select a member */ + def DOT(field: String)(implicit pos: Position): DotSelect = + DotSelect(self, Ident(field)) + + // Some operators that we use + + def ===(that: Tree)(implicit pos: Position): Tree = + BinaryOp("===", self, that) + def ===(that: String)(implicit pos: Position): Tree = + BinaryOp("===", self, StringLiteral(that)) + + def unary_!()(implicit pos: Position): Tree = + UnaryOp("!", self) + def &&(that: Tree)(implicit pos: Position): Tree = + BinaryOp("&&", self, that) + def ||(that: Tree)(implicit pos: Position): Tree = + BinaryOp("||", self, that) + + // Other constructs + + def :=(that: Tree)(implicit pos: Position): Tree = + Assign(self, that) + } + + def typeof(expr: Tree)(implicit pos: Position): Tree = + UnaryOp("typeof", expr) +} diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/javascript/Trees.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/javascript/Trees.scala new file mode 100644 index 0000000..0b86d1b --- /dev/null +++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/javascript/Trees.scala @@ -0,0 +1,194 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js tools ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.tools.javascript + +import scala.annotation.switch + +import scala.scalajs.ir +import ir.Position +import ir.Position.NoPosition + +object Trees { + import ir.Trees.requireValidIdent + + /** AST node of JavaScript. */ + abstract sealed class Tree { + val pos: Position + + def show: String = { + val writer = new java.io.StringWriter + val printer = new Printers.JSTreePrinter(writer) + printer.printTree(this, isStat = true) + writer.toString() + } + } + + case object EmptyTree extends Tree { + val pos = NoPosition + } + + // Comments + + case class DocComment(text: String)(implicit val pos: Position) extends Tree + + // Identifiers and properties + + sealed trait PropertyName { + def name: String + def pos: Position + } + + case class Ident(name: String, originalName: Option[String])( + implicit val pos: Position) extends PropertyName { + requireValidIdent(name) + } + + object Ident { + def apply(name: String)(implicit pos: Position): Ident = + new Ident(name, Some(name)) + } + + // Definitions + + case class VarDef(name: Ident, mutable: Boolean, rhs: Tree)(implicit val pos: Position) extends Tree { + def ref(implicit pos: Position): Tree = + VarRef(name, mutable = mutable) + } + + case class ParamDef(name: Ident, mutable: Boolean)(implicit val pos: Position) extends Tree { + def ref(implicit pos: Position): Tree = + VarRef(name, mutable = mutable) + } + + // Control flow constructs + + case class Skip()(implicit val pos: Position) extends Tree + + class Block private (val stats: List[Tree])(implicit val pos: Position) extends Tree { + override def toString(): String = + stats.mkString("Block(", ",", ")") + } + + object Block { + def apply(stats: List[Tree])(implicit pos: Position): Tree = { + val flattenedStats = stats flatMap { + case Skip() => Nil + case Block(subStats) => subStats + case other => other :: Nil + } + flattenedStats match { + case Nil => Skip() + case only :: Nil => only + case _ => new Block(flattenedStats) + } + } + + def apply(stats: Tree*)(implicit pos: Position): Tree = + apply(stats.toList) + + def unapply(block: Block): Some[List[Tree]] = Some(block.stats) + } + + case class Labeled(label: Ident, body: Tree)(implicit val pos: Position) extends Tree + + case class Assign(lhs: Tree, rhs: Tree)(implicit val pos: Position) extends Tree { + require(lhs match { + case _:VarRef | _:DotSelect | _:BracketSelect => true + case _ => false + }, s"Invalid lhs for Assign: $lhs") + } + + case class Return(expr: Tree)(implicit val pos: Position) extends Tree + + case class If(cond: Tree, thenp: Tree, elsep: Tree)(implicit val pos: Position) extends Tree + + case class While(cond: Tree, body: Tree, label: Option[Ident] = None)(implicit val pos: Position) extends Tree + + case class DoWhile(body: Tree, cond: Tree, label: Option[Ident] = None)(implicit val pos: Position) extends Tree + + case class Try(block: Tree, errVar: Ident, handler: Tree, finalizer: Tree)(implicit val pos: Position) extends Tree + + case class Throw(expr: Tree)(implicit val pos: Position) extends Tree + + case class Break(label: Option[Ident] = None)(implicit val pos: Position) extends Tree + + case class Continue(label: Option[Ident] = None)(implicit val pos: Position) extends Tree + + case class Switch(selector: Tree, cases: List[(Tree, Tree)], default: Tree)(implicit val pos: Position) extends Tree + + case class Debugger()(implicit val pos: Position) extends Tree + + // Expressions + + case class New(ctor: Tree, args: List[Tree])(implicit val pos: Position) extends Tree + + case class DotSelect(qualifier: Tree, item: Ident)(implicit val pos: Position) extends Tree + + case class BracketSelect(qualifier: Tree, item: Tree)(implicit val pos: Position) extends Tree + + /** Syntactic apply. + * It is a method call if fun is a dot-select or bracket-select. It is a + * function call otherwise. + */ + case class Apply(fun: Tree, args: List[Tree])(implicit val pos: Position) extends Tree + + case class Delete(prop: Tree)(implicit val pos: Position) extends Tree { + require(prop match { + case _:DotSelect | _:BracketSelect => true + case _ => false + }, s"Invalid prop for Delete: $prop") + } + + /** Unary operation (always preserves pureness). + * + * Operations which do not preserve pureness are not allowed in this tree. + * These are notably ++ and -- + */ + case class UnaryOp(op: String, lhs: Tree)(implicit val pos: Position) extends Tree + + /** Binary operation (always preserves pureness). + * + * Operations which do not preserve pureness are not allowed in this tree. + * These are notably +=, -=, *=, /= and %= + */ + case class BinaryOp(op: String, lhs: Tree, rhs: Tree)(implicit val pos: Position) extends Tree + + case class ArrayConstr(items: List[Tree])(implicit val pos: Position) extends Tree + + case class ObjectConstr(fields: List[(PropertyName, Tree)])(implicit val pos: Position) extends Tree + + // Literals + + /** Marker for literals. Literals are always pure. */ + sealed trait Literal extends Tree + + case class Undefined()(implicit val pos: Position) extends Literal + + case class Null()(implicit val pos: Position) extends Literal + + case class BooleanLiteral(value: Boolean)(implicit val pos: Position) extends Literal + + case class IntLiteral(value: Int)(implicit val pos: Position) extends Literal + + case class DoubleLiteral(value: Double)(implicit val pos: Position) extends Literal + + case class StringLiteral(value: String)( + implicit val pos: Position) extends Literal with PropertyName { + override def name = value + } + + // Atomic expressions + + case class VarRef(ident: Ident, mutable: Boolean)(implicit val pos: Position) extends Tree + + case class This()(implicit val pos: Position) extends Tree + + case class Function(args: List[ParamDef], body: Tree)(implicit val pos: Position) extends Tree +} diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/jsdep/Exceptions.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/jsdep/Exceptions.scala new file mode 100644 index 0000000..dd7f635 --- /dev/null +++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/jsdep/Exceptions.scala @@ -0,0 +1,59 @@ +package scala.scalajs.tools.jsdep + +abstract class DependencyException(msg: String) extends Exception(msg) + +class MissingDependencyException( + val originatingLib: FlatJSDependency, + val missingLib: String +) extends DependencyException( + s"The JS dependency ${originatingLib.resourceName} declared " + + s"from ${originatingLib.origin} has an unmet transitive " + + s"dependency $missingLib") + +class CyclicDependencyException( + val participants: List[ResolutionInfo] +) extends DependencyException( + CyclicDependencyException.mkMsg(participants)) + +object CyclicDependencyException { + private def mkMsg(parts: List[ResolutionInfo]) = { + val lookup = parts.map(p => (p.resourceName, p)).toMap + + val msg = new StringBuilder() + msg.append("There is a loop in the following JS dependencies:\n") + + def str(info: ResolutionInfo) = + s"${info.resourceName} from: ${info.origins.mkString(", ")}" + + for (dep <- parts) { + msg.append(s" ${str(dep)} which depends on\n") + for (name <- dep.dependencies) { + val rdep = lookup(name) + msg.append(s" - ${str(rdep)}\n") + } + } + + msg.toString() + } +} + +class ConflictingNameException( + val participants: List[FlatJSDependency] +) extends DependencyException( + ConflictingNameException.mkMsg(participants)) + +object ConflictingNameException { + private def mkMsg(parts: List[FlatJSDependency]) = { + val resName = parts.head.resourceName + + val msg = new StringBuilder() + msg.append(s"Name conflicts in:\n") + + for (p <- parts) { + msg.append(p) + msg.append('\n') + } + + sys.error(msg.toString()) + } +} diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/jsdep/FlatJSDependency.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/jsdep/FlatJSDependency.scala new file mode 100644 index 0000000..0c55e88 --- /dev/null +++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/jsdep/FlatJSDependency.scala @@ -0,0 +1,17 @@ +package scala.scalajs.tools.jsdep + +import scala.scalajs.ir.Trees.isValidIdentifier + +/** The same as a [[JSDependency]] but containing the origin from the containing + * JSDependencyManifest. This class is used for filtering of dependencies. + */ +final class FlatJSDependency( + val origin: Origin, + val resourceName: String, + val dependencies: List[String] = Nil, + val commonJSName: Option[String] = None) { + + require(commonJSName.forall(isValidIdentifier), + "commonJSName must be a valid JavaScript identifier") + +} diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/jsdep/JSDependency.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/jsdep/JSDependency.scala new file mode 100644 index 0000000..2e6f8d1 --- /dev/null +++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/jsdep/JSDependency.scala @@ -0,0 +1,66 @@ +package scala.scalajs.tools.jsdep + +import scala.scalajs.tools.json._ + +import scala.scalajs.ir.Trees.isValidIdentifier + +/** Expresses a dependency on a raw JS library and the JS libraries this library + * itself depends on. + * + * Both the [[resourceName]] and each element of [[dependencies]] is the + * unqualified filename of the library (e.g. "jquery.js"). + * + * @param resourceName Filename of the JavaScript file to include + * (e.g. "jquery.js") + * @param dependencies Filenames of JavaScript files that must be included + * before this JavaScript file. + * @param commonJSName A JavaScript variable name this dependency should be + * required in a commonJS environment (n.b. Node.js). Should only be set if + * the JavaScript library will register its exports. + */ +final class JSDependency( + val resourceName: String, + val dependencies: List[String] = Nil, + val commonJSName: Option[String] = None) { + + require(commonJSName.forall(isValidIdentifier), + "commonJSName must be a valid JavaScript identifier") + + def dependsOn(names: String*): JSDependency = + copy(dependencies = dependencies ++ names) + def commonJSName(name: String): JSDependency = + copy(commonJSName = Some(name)) + def withOrigin(origin: Origin): FlatJSDependency = + new FlatJSDependency(origin, resourceName, dependencies, commonJSName) + + private def copy( + resourceName: String = this.resourceName, + dependencies: List[String] = this.dependencies, + commonJSName: Option[String] = this.commonJSName) = { + new JSDependency(resourceName, dependencies, commonJSName) + } +} + +object JSDependency { + + implicit object JSDepJSONSerializer extends JSONSerializer[JSDependency] { + def serialize(x: JSDependency): JSON = { + new JSONObjBuilder() + .fld("resourceName", x.resourceName) + .opt("dependencies", + if (x.dependencies.nonEmpty) Some(x.dependencies) else None) + .opt("commonJSName", x.commonJSName) + .toJSON + } + } + + implicit object JSDepJSONDeserializer extends JSONDeserializer[JSDependency] { + def deserialize(x: JSON): JSDependency = { + val obj = new JSONObjExtractor(x) + new JSDependency( + obj.fld[String] ("resourceName"), + obj.opt[List[String]]("dependencies").getOrElse(Nil), + obj.opt[String] ("commonJSName")) + } + } +} diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/jsdep/JSDependencyManifest.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/jsdep/JSDependencyManifest.scala new file mode 100644 index 0000000..24491b4 --- /dev/null +++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/jsdep/JSDependencyManifest.scala @@ -0,0 +1,130 @@ +package scala.scalajs.tools.jsdep + +import scala.scalajs.tools.json._ +import scala.scalajs.tools.io._ + +import scala.collection.immutable.{Seq, Traversable} + +import java.io.{Reader, Writer} + +/** The information written to a "JS_DEPENDENCIES" manifest file. */ +final class JSDependencyManifest( + val origin: Origin, + val libDeps: List[JSDependency], + val requiresDOM: Boolean, + val compliantSemantics: List[String]) { + def flatten: List[FlatJSDependency] = libDeps.map(_.withOrigin(origin)) +} + +object JSDependencyManifest { + + final val ManifestFileName = "JS_DEPENDENCIES" + + def createIncludeList( + flatDeps: Traversable[FlatJSDependency]): List[ResolutionInfo] = { + val jsDeps = mergeManifests(flatDeps) + + // Verify all dependencies are met + for { + lib <- flatDeps + dep <- lib.dependencies + if !jsDeps.contains(dep) + } throw new MissingDependencyException(lib, dep) + + // Sort according to dependencies and return + + // Very simple O(n²) topological sort for elements assumed to be distinct + // Copied :( from GenJSExports (but different exception) + @scala.annotation.tailrec + def loop(coll: List[ResolutionInfo], + acc: List[ResolutionInfo]): List[ResolutionInfo] = { + + if (coll.isEmpty) acc + else if (coll.tail.isEmpty) coll.head :: acc + else { + val (selected, pending) = coll.partition { x => + coll forall { y => (x eq y) || !y.dependencies.contains(x.resourceName) } + } + + if (selected.nonEmpty) + loop(pending, selected ::: acc) + else + throw new CyclicDependencyException(pending) + } + } + + loop(jsDeps.values.toList, Nil) + } + + /** Merges multiple JSDependencyManifests into a map of map: + * resourceName -> ResolutionInfo + */ + private def mergeManifests(flatDeps: Traversable[FlatJSDependency]) = { + @inline + def hasConflict(x: FlatJSDependency, y: FlatJSDependency) = ( + x.commonJSName.isDefined && + y.commonJSName.isDefined && + (x.resourceName == y.resourceName ^ + x.commonJSName == y.commonJSName) + ) + + val conflicts = flatDeps.filter(x => + flatDeps.exists(y => hasConflict(x,y))) + + if (conflicts.nonEmpty) + throw new ConflictingNameException(conflicts.toList) + + flatDeps.groupBy(_.resourceName).mapValues { sameName => + new ResolutionInfo( + resourceName = sameName.head.resourceName, + dependencies = sameName.flatMap(_.dependencies).toSet, + origins = sameName.map(_.origin).toList, + commonJSName = sameName.flatMap(_.commonJSName).headOption + ) + } + } + + implicit object JSDepManJSONSerializer extends JSONSerializer[JSDependencyManifest] { + @inline def optList[T](x: List[T]): Option[List[T]] = + if (x.nonEmpty) Some(x) else None + + def serialize(x: JSDependencyManifest): JSON = { + new JSONObjBuilder() + .fld("origin", x.origin) + .opt("libDeps", optList(x.libDeps)) + .opt("requiresDOM", if (x.requiresDOM) Some(true) else None) + .opt("compliantSemantics", optList(x.compliantSemantics)) + .toJSON + } + } + + implicit object JSDepManJSONDeserializer extends JSONDeserializer[JSDependencyManifest] { + def deserialize(x: JSON): JSDependencyManifest = { + val obj = new JSONObjExtractor(x) + new JSDependencyManifest( + obj.fld[Origin] ("origin"), + obj.opt[List[JSDependency]]("libDeps").getOrElse(Nil), + obj.opt[Boolean] ("requiresDOM").getOrElse(false), + obj.opt[List[String]] ("compliantSemantics").getOrElse(Nil)) + } + } + + def write(dep: JSDependencyManifest, output: WritableVirtualTextFile): Unit = { + val writer = output.contentWriter + try write(dep, writer) + finally writer.close() + } + + def write(dep: JSDependencyManifest, writer: Writer): Unit = + writeJSON(dep.toJSON, writer) + + def read(file: VirtualTextFile): JSDependencyManifest = { + val reader = file.reader + try read(reader) + finally reader.close() + } + + def read(reader: Reader): JSDependencyManifest = + fromJSON[JSDependencyManifest](readJSON(reader)) + +} diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/jsdep/Origin.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/jsdep/Origin.scala new file mode 100644 index 0000000..a2c6b2d --- /dev/null +++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/jsdep/Origin.scala @@ -0,0 +1,28 @@ +package scala.scalajs.tools.jsdep + +import scala.scalajs.tools.json._ + +/** The place a JSDependency originated from */ +final class Origin(val moduleName: String, val configuration: String) { + override def toString(): String = s"$moduleName:$configuration" +} + +object Origin { + implicit object OriginJSONSerializer extends JSONSerializer[Origin] { + def serialize(x: Origin): JSON = { + new JSONObjBuilder() + .fld("moduleName", x.moduleName) + .fld("configuration", x.configuration) + .toJSON + } + } + + implicit object OriginDeserializer extends JSONDeserializer[Origin] { + def deserialize(x: JSON): Origin = { + val obj = new JSONObjExtractor(x) + new Origin( + obj.fld[String]("moduleName"), + obj.fld[String]("configuration")) + } + } +} diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/jsdep/ResolutionInfo.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/jsdep/ResolutionInfo.scala new file mode 100644 index 0000000..2aa177e --- /dev/null +++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/jsdep/ResolutionInfo.scala @@ -0,0 +1,21 @@ +package scala.scalajs.tools.jsdep + +import scala.scalajs.ir.Trees.isValidIdentifier + +/** Information about a resolved JSDependency + * + * @param resourceName Filename of the JavaScript file + * @param dependencies Filenames this dependency depends on + * @param origins Who declared this dependency + * @param commonJSName Variable name in commonJS environments + */ +final class ResolutionInfo( + val resourceName: String, + val dependencies: Set[String], + val origins: List[Origin], + val commonJSName: Option[String]) { + + require(commonJSName.forall(isValidIdentifier), + "commonJSName must be a valid JavaScript identifier") + +} diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/json/AbstractJSONImpl.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/json/AbstractJSONImpl.scala new file mode 100644 index 0000000..ad5d79e --- /dev/null +++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/json/AbstractJSONImpl.scala @@ -0,0 +1,32 @@ +package scala.scalajs.tools.json + +import java.io.{Reader, Writer} + +/** A JSON implementation. Has a representation type and methods to convert + * this type to/from primitives, lists and maps. + * + * Further, it can write/read a value of this type to a string. + */ +private[json] trait AbstractJSONImpl { + + type Repr + + def fromString(x: String): Repr + def fromNumber(x: Number): Repr + def fromBoolean(x: Boolean): Repr + def fromList(x: List[Repr]): Repr + def fromMap(x: Map[String, Repr]): Repr + + def toString(x: Repr): String + def toNumber(x: Repr): Number + def toBoolean(x: Repr): Boolean + def toList(x: Repr): List[Repr] + def toMap(x: Repr): Map[String, Repr] + + def serialize(x: Repr): String + def serialize(x: Repr, writer: Writer): Unit + + def deserialize(str: String): Repr + def deserialize(reader: Reader): Repr + +} diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/json/JSONDeserializer.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/json/JSONDeserializer.scala new file mode 100644 index 0000000..e854e9a --- /dev/null +++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/json/JSONDeserializer.scala @@ -0,0 +1,30 @@ +package scala.scalajs.tools.json + +trait JSONDeserializer[T] { + def deserialize(x: JSON): T +} + +object JSONDeserializer { + + implicit object stringJSON extends JSONDeserializer[String] { + def deserialize(x: JSON): String = Impl.toString(x) + } + + implicit object intJSON extends JSONDeserializer[Int] { + def deserialize(x: JSON): Int = Impl.toNumber(x).intValue() + } + + implicit object booleanJSON extends JSONDeserializer[Boolean] { + def deserialize(x: JSON): Boolean = Impl.toBoolean(x) + } + + implicit def listJSON[T : JSONDeserializer] = new JSONDeserializer[List[T]] { + def deserialize(x: JSON): List[T] = Impl.toList(x).map(fromJSON[T] _) + } + + implicit def mapJSON[V : JSONDeserializer] = new JSONDeserializer[Map[String, V]] { + def deserialize(x: JSON): Map[String, V] = + Impl.toMap(x).mapValues(fromJSON[V] _) + } + +} diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/json/JSONObjBuilder.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/json/JSONObjBuilder.scala new file mode 100644 index 0000000..dd98f49 --- /dev/null +++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/json/JSONObjBuilder.scala @@ -0,0 +1,20 @@ +package scala.scalajs.tools.json + +import scala.collection.mutable + +class JSONObjBuilder { + + private val flds = mutable.Map.empty[String, JSON] + + def fld[T : JSONSerializer](name: String, v: T): this.type = { + flds.put(name, v.toJSON) + this + } + + def opt[T : JSONSerializer](name: String, v: Option[T]): this.type = { + v.foreach(v => flds.put(name, v.toJSON)) + this + } + + def toJSON: JSON = Impl.fromMap(flds.toMap) +} diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/json/JSONObjExtractor.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/json/JSONObjExtractor.scala new file mode 100644 index 0000000..e49f7e4 --- /dev/null +++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/json/JSONObjExtractor.scala @@ -0,0 +1,13 @@ +package scala.scalajs.tools.json + +import scala.collection.mutable + +class JSONObjExtractor(rawData: JSON) { + private val data = Impl.toMap(rawData) + + def fld[T : JSONDeserializer](name: String): T = + fromJSON[T](data(name)) + + def opt[T : JSONDeserializer](name: String): Option[T] = + data.get(name).map(fromJSON[T] _) +} diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/json/JSONSerializer.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/json/JSONSerializer.scala new file mode 100644 index 0000000..e26c92a --- /dev/null +++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/json/JSONSerializer.scala @@ -0,0 +1,32 @@ +package scala.scalajs.tools.json + +trait JSONSerializer[T] { + def serialize(x: T): JSON +} + +object JSONSerializer { + + implicit object stringJSON extends JSONSerializer[String] { + def serialize(x: String): JSON = Impl.fromString(x) + } + + implicit object intJSON extends JSONSerializer[Int] { + def serialize(x: Int): JSON = Impl.fromNumber(x) + } + + implicit object booleanJSON extends JSONSerializer[Boolean] { + def serialize(x: Boolean): JSON = Impl.fromBoolean(x) + } + + implicit def listJSON[T : JSONSerializer] = new JSONSerializer[List[T]] { + def serialize(x: List[T]): JSON = Impl.fromList(x.map(_.toJSON)) + } + + implicit def mapJSON[V : JSONSerializer] = { + new JSONSerializer[Map[String, V]] { + def serialize(x: Map[String, V]): JSON = + Impl.fromMap(x.mapValues(_.toJSON)) + } + } + +} diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/json/package.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/json/package.scala new file mode 100644 index 0000000..551893a --- /dev/null +++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/json/package.scala @@ -0,0 +1,26 @@ +package scala.scalajs.tools + +import java.io.{Reader, Writer} + +/** Some type-class lightweight wrappers around simple-json. + * + * They allow to write [[xyz.toJSON]] to obtain classes that can be + * serialized by simple-json and [[fromJSON[T](xyz)]] to get an + * object back. + */ +package object json { + type JSON = Impl.Repr + + implicit class JSONPimp[T : JSONSerializer](x: T) { + def toJSON: JSON = implicitly[JSONSerializer[T]].serialize(x) + } + + def fromJSON[T](x: JSON)(implicit d: JSONDeserializer[T]): T = + d.deserialize(x) + + def writeJSON(x: JSON, writer: Writer): Unit = Impl.serialize(x, writer) + def jsonToString(x: JSON): String = Impl.serialize(x) + def readJSON(str: String): JSON = Impl.deserialize(str) + def readJSON(reader: Reader): JSON = Impl.deserialize(reader) + +} diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/logging/Level.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/logging/Level.scala new file mode 100644 index 0000000..fbbf39d --- /dev/null +++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/logging/Level.scala @@ -0,0 +1,24 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js tools ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.tools.logging + +import scala.math.Ordered + +abstract sealed class Level extends Ordered[Level] { x => + protected val order: Int + def compare(y: Level) = x.order - y.order +} + +object Level { + case object Error extends Level { protected val order = 4 } + case object Warn extends Level { protected val order = 3 } + case object Info extends Level { protected val order = 2 } + case object Debug extends Level { protected val order = 1 } +} diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/logging/Logger.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/logging/Logger.scala new file mode 100644 index 0000000..3664f51 --- /dev/null +++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/logging/Logger.scala @@ -0,0 +1,25 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js tools ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.tools.logging + +/** Abstract logger for our tools. Designed after sbt's Loggers. */ +trait Logger { + def log(level: Level, message: => String): Unit + def success(message: => String): Unit + def trace(t: => Throwable): Unit + + def error(message: => String): Unit = log(Level.Error, message) + def warn(message: => String): Unit = log(Level.Warn, message) + def info(message: => String): Unit = log(Level.Info, message) + def debug(message: => String): Unit = log(Level.Debug, message) + + def time(title: String, nanos: Long): Unit = + debug(s"$title: ${nanos / 1000} us") +} diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/logging/NullLogger.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/logging/NullLogger.scala new file mode 100644 index 0000000..0e36f89 --- /dev/null +++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/logging/NullLogger.scala @@ -0,0 +1,7 @@ +package scala.scalajs.tools.logging + +object NullLogger extends Logger { + def log(level: Level, message: => String): Unit = {} + def success(message: => String): Unit = {} + def trace(t: => Throwable): Unit = {} +} diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/logging/ScalaConsoleLogger.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/logging/ScalaConsoleLogger.scala new file mode 100644 index 0000000..e2c9efc --- /dev/null +++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/logging/ScalaConsoleLogger.scala @@ -0,0 +1,15 @@ +package scala.scalajs.tools.logging + +class ScalaConsoleLogger(minLevel: Level = Level.Debug) extends Logger { + + def log(level: Level, message: =>String): Unit = if (level >= minLevel) { + if (level == Level.Warn || level == Level.Error) + scala.Console.err.println(message) + else + scala.Console.out.println(message) + } + def success(message: => String): Unit = info(message) + def trace(t: => Throwable): Unit = + // This is error level, so no checking + t.printStackTrace() +} diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/optimizer/Analyzer.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/optimizer/Analyzer.scala new file mode 100644 index 0000000..9cdd764 --- /dev/null +++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/optimizer/Analyzer.scala @@ -0,0 +1,587 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js tools ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.tools.optimizer + +import scala.annotation.tailrec + +import scala.collection.mutable + +import scala.scalajs.ir +import ir.{ClassKind, Definitions, Infos} + +import scala.scalajs.tools.sem._ +import scala.scalajs.tools.javascript.LongImpl +import scala.scalajs.tools.logging._ + +import ScalaJSOptimizer._ + +class Analyzer(logger0: Logger, semantics: Semantics, + allData: Seq[Infos.ClassInfo], globalWarnEnabled: Boolean, + isBeforeOptimizer: Boolean) { + /* Set this to true to debug the DCE analyzer. + * We don't rely on config to disable 'debug' messages because we want + * to use 'debug' for displaying more stack trace info that the user can + * see with the 'last' command. + */ + val DebugAnalyzer = false + + object logger extends Logger { + var indentation: String = "" + + def indent(): Unit = indentation += " " + def undent(): Unit = indentation = indentation.substring(2) + + def log(level: Level, message: => String) = + logger0.log(level, indentation+message) + def success(message: => String) = + logger0.success(indentation+message) + def trace(t: => Throwable) = + logger0.trace(t) + + def indented[A](body: => A): A = { + indent() + try body + finally undent() + } + + def debugIndent[A](message: => String)(body: => A): A = { + if (DebugAnalyzer) { + debug(message) + indented(body) + } else { + body + } + } + + def temporarilyNotIndented[A](body: => A): A = { + val savedIndent = indentation + indentation = "" + try body + finally indentation = savedIndent + } + } + + sealed trait From + case class FromMethod(methodInfo: MethodInfo) extends From + case object FromCore extends From + case object FromExports extends From + case object FromManual extends From + + var allAvailable: Boolean = true + + val classInfos: mutable.Map[String, ClassInfo] = { + val cs = for (classData <- allData) + yield (classData.encodedName, new ClassInfo(classData)) + mutable.Map.empty[String, ClassInfo] ++ cs + } + + def lookupClass(encodedName: String): ClassInfo = { + classInfos.get(encodedName) match { + case Some(info) => info + case None => + val c = new ClassInfo(createMissingClassInfo(encodedName)) + classInfos += encodedName -> c + c.nonExistent = true + c.linkClasses() + c + } + } + + def lookupModule(encodedName: String): ClassInfo = { + lookupClass(encodedName+"$") + } + + linkClasses() + + def linkClasses(): Unit = { + if (!classInfos.contains(ir.Definitions.ObjectClass)) + sys.error("Fatal error: could not find java.lang.Object on the classpath") + for (classInfo <- classInfos.values.toList) + classInfo.linkClasses() + } + + def computeReachability(manuallyReachable: Seq[ManualReachability], + noWarnMissing: Seq[NoWarnMissing]): Unit = { + // Stuff reachable from core symbols always should warn + reachCoreSymbols() + + // Disable warnings as requested + noWarnMissing.foreach(disableWarning _) + + // Reach all user stuff + manuallyReachable.foreach(reachManually _) + for (classInfo <- classInfos.values) + classInfo.reachExports() + } + + /** Reach symbols used directly by scalajsenv.js. */ + def reachCoreSymbols(): Unit = { + import semantics._ + import CheckedBehavior._ + + implicit val from = FromCore + + def instantiateClassWith(className: String, constructor: String): ClassInfo = { + val info = lookupClass(className) + info.instantiated() + info.callMethod(constructor) + info + } + + val ObjectClass = instantiateClassWith("O", "init___") + ObjectClass.callMethod("toString__T") + ObjectClass.callMethod("equals__O__Z") + + instantiateClassWith("jl_NullPointerException", "init___") + + if (asInstanceOfs != Unchecked) + instantiateClassWith("jl_ClassCastException", "init___T") + + if (asInstanceOfs == Fatal) + instantiateClassWith("sjsr_UndefinedBehaviorError", "init___jl_Throwable") + + instantiateClassWith("jl_Class", "init___jl_ScalaJSClassData") + + val RTStringModuleClass = lookupClass("sjsr_RuntimeString$") + RTStringModuleClass.accessModule() + RTStringModuleClass.callMethod("hashCode__T__I") + + val RTLongClass = lookupClass(LongImpl.RuntimeLongClass) + RTLongClass.instantiated() + for (method <- LongImpl.AllConstructors ++ LongImpl.AllMethods) + RTLongClass.callMethod(method) + + if (isBeforeOptimizer) { + for (method <- LongImpl.AllIntrinsicMethods) + RTLongClass.callMethod(method) + } + + val RTLongModuleClass = lookupClass(LongImpl.RuntimeLongModuleClass) + RTLongModuleClass.accessModule() + for (method <- LongImpl.AllModuleMethods) + RTLongModuleClass.callMethod(method) + + if (isBeforeOptimizer) { + for (hijacked <- Definitions.HijackedClasses) + lookupClass(hijacked).instantiated() + } else { + for (hijacked <- Definitions.HijackedClasses) + lookupClass(hijacked).accessData() + } + + if (semantics.strictFloats) { + val RuntimePackage = lookupClass("sjsr_package$") + RuntimePackage.accessModule() + RuntimePackage.callMethod("froundPolyfill__D__D") + } + + val BitsModuleClass = lookupClass("sjsr_Bits$") + BitsModuleClass.accessModule() + BitsModuleClass.callMethod("numberHashCode__D__I") + } + + def reachManually(info: ManualReachability) = { + implicit val from = FromManual + + // Don't lookupClass here, since we don't want to create any + // symbols. If a symbol doesn't exist, we fail. + info match { + case ReachObject(name) => classInfos(name + "$").accessModule() + case Instantiate(name) => classInfos(name).instantiated() + case ReachMethod(className, methodName, static) => + classInfos(className).callMethod(methodName, static) + } + } + + def disableWarning(noWarn: NoWarnMissing) = noWarn match { + case NoWarnClass(className) => + lookupClass(className).warnEnabled = false + case NoWarnMethod(className, methodName) => + lookupClass(className).lookupMethod(methodName).warnEnabled = false + } + + class ClassInfo(data: Infos.ClassInfo) { + val encodedName = data.encodedName + val ancestorCount = data.ancestorCount + val isStaticModule = data.kind == ClassKind.ModuleClass + val isInterface = data.kind == ClassKind.Interface + val isImplClass = data.kind == ClassKind.TraitImpl + val isRawJSType = data.kind == ClassKind.RawJSType + val isHijackedClass = data.kind == ClassKind.HijackedClass + val isClass = !isInterface && !isImplClass && !isRawJSType + val isExported = data.isExported + + val hasData = !isImplClass + val hasMoreThanData = isClass && !isHijackedClass + + var superClass: ClassInfo = _ + val ancestors = mutable.ListBuffer.empty[ClassInfo] + val descendants = mutable.ListBuffer.empty[ClassInfo] + + var nonExistent: Boolean = false + var warnEnabled: Boolean = true + + def linkClasses(): Unit = { + if (data.superClass != "") + superClass = lookupClass(data.superClass) + ancestors ++= data.ancestors.map(lookupClass) + for (ancestor <- ancestors) + ancestor.descendants += this + } + + lazy val descendentClasses = descendants.filter(_.isClass) + + def optimizerHints: Infos.OptimizerHints = data.optimizerHints + + var isInstantiated: Boolean = false + var isAnySubclassInstantiated: Boolean = false + var isModuleAccessed: Boolean = false + var isDataAccessed: Boolean = false + + var instantiatedFrom: Option[From] = None + + val delayedCalls = mutable.Map.empty[String, From] + + def isNeededAtAll = + isDataAccessed || + isAnySubclassInstantiated || + (isImplClass && methodInfos.values.exists(_.isReachable)) + + lazy val methodInfos: mutable.Map[String, MethodInfo] = { + val ms = for (methodData <- data.methods) + yield (methodData.encodedName, new MethodInfo(this, methodData)) + mutable.Map.empty[String, MethodInfo] ++ ms + } + + def lookupMethod(methodName: String): MethodInfo = { + tryLookupMethod(methodName).getOrElse { + val syntheticData = createMissingMethodInfo(methodName) + val m = new MethodInfo(this, syntheticData) + m.nonExistent = true + methodInfos += methodName -> m + m + } + } + + def tryLookupMethod(methodName: String): Option[MethodInfo] = { + assert(isClass || isImplClass, + s"Cannot call lookupMethod($methodName) on non-class $this") + @tailrec + def loop(ancestorInfo: ClassInfo): Option[MethodInfo] = { + if (ancestorInfo ne null) { + ancestorInfo.methodInfos.get(methodName) match { + case Some(m) if !m.isAbstract => Some(m) + case _ => loop(ancestorInfo.superClass) + } + } else { + None + } + } + loop(this) + } + + override def toString(): String = encodedName + + /** Start reachability algorithm with the exports for that class. */ + def reachExports(): Unit = { + implicit val from = FromExports + + // Myself + if (isExported) { + assert(!isImplClass, "An implementation class must not be exported") + if (isStaticModule) accessModule() + else instantiated() + } + + // My methods + for (methodInfo <- methodInfos.values) { + if (methodInfo.isExported) + callMethod(methodInfo.encodedName) + } + } + + def accessModule()(implicit from: From): Unit = { + assert(isStaticModule, s"Cannot call accessModule() on non-module $this") + if (!isModuleAccessed) { + logger.debugIndent(s"$this.isModuleAccessed = true") { + isModuleAccessed = true + instantiated() + callMethod("init___") + } + } + } + + def instantiated()(implicit from: From): Unit = { + if (!isInstantiated && isClass) { + logger.debugIndent(s"$this.isInstantiated = true") { + isInstantiated = true + instantiatedFrom = Some(from) + ancestors.foreach(_.subclassInstantiated()) + } + + for ((methodName, from) <- delayedCalls) + delayedCallMethod(methodName)(from) + } + } + + private def subclassInstantiated()(implicit from: From): Unit = { + if (!isAnySubclassInstantiated && isClass) { + logger.debugIndent(s"$this.isAnySubclassInstantiated = true") { + isAnySubclassInstantiated = true + if (instantiatedFrom.isEmpty) + instantiatedFrom = Some(from) + accessData() + methodInfos.get("__init__").foreach(_.reachStatic()) + } + } + } + + def accessData()(implicit from: From): Unit = { + if (!isDataAccessed && hasData) { + checkExistent() + if (DebugAnalyzer) + logger.debug(s"$this.isDataAccessed = true") + isDataAccessed = true + } + } + + def checkExistent()(implicit from: From): Unit = { + if (nonExistent) { + if (warnEnabled && globalWarnEnabled) { + logger.warn(s"Referring to non-existent class $encodedName") + warnCallStack() + } + nonExistent = false + allAvailable = false + } + } + + def callMethod(methodName: String, static: Boolean = false)( + implicit from: From): Unit = { + logger.debugIndent(s"calling${if (static) " static" else ""} $this.$methodName") { + if (isImplClass) { + // methods in impl classes are always implicitly called statically + lookupMethod(methodName).reachStatic() + } else if (isConstructorName(methodName)) { + // constructors are always implicitly called statically + lookupMethod(methodName).reachStatic() + } else if (static) { + assert(!isReflProxyName(methodName), + s"Trying to call statically refl proxy $this.$methodName") + lookupMethod(methodName).reachStatic() + } else { + for (descendentClass <- descendentClasses) { + if (descendentClass.isInstantiated) + descendentClass.delayedCallMethod(methodName) + else + descendentClass.delayedCalls += ((methodName, from)) + } + } + } + } + + private def delayedCallMethod(methodName: String)(implicit from: From): Unit = { + if (isReflProxyName(methodName)) { + tryLookupMethod(methodName).foreach(_.reach(this)) + } else { + lookupMethod(methodName).reach(this) + } + } + } + + class MethodInfo(val owner: ClassInfo, data: Infos.MethodInfo) { + + val encodedName = data.encodedName + val isAbstract = data.isAbstract + val isExported = data.isExported + val isReflProxy = isReflProxyName(encodedName) + + def optimizerHints: Infos.OptimizerHints = data.optimizerHints + + var isReachable: Boolean = false + + var calledFrom: Option[From] = None + var instantiatedSubclass: Option[ClassInfo] = None + + var nonExistent: Boolean = false + var warnEnabled: Boolean = true + + override def toString(): String = s"$owner.$encodedName" + + def reachStatic()(implicit from: From): Unit = { + assert(!isAbstract, + s"Trying to reach statically the abstract method $this") + + checkExistent() + + if (!isReachable) { + logger.debugIndent(s"$this.isReachable = true") { + isReachable = true + calledFrom = Some(from) + doReach() + } + } + } + + def reach(inClass: ClassInfo)(implicit from: From): Unit = { + assert(owner.isClass, + s"Trying to reach dynamically the non-class method $this") + assert(!isConstructorName(encodedName), + s"Trying to reach dynamically the constructor $this") + + checkExistent() + + if (!isReachable) { + logger.debugIndent(s"$this.isReachable = true") { + isReachable = true + calledFrom = Some(from) + instantiatedSubclass = Some(inClass) + doReach() + } + } + } + + private def checkExistent()(implicit from: From) = { + if (nonExistent) { + if (warnEnabled && owner.warnEnabled && globalWarnEnabled) { + logger.temporarilyNotIndented { + logger.warn(s"Referring to non-existent method $this") + warnCallStack() + } + } + allAvailable = false + } + } + + private[this] def doReach(): Unit = { + logger.debugIndent(s"$this.doReach()") { + implicit val from = FromMethod(this) + + if (owner.isImplClass) + owner.checkExistent() + + for (moduleName <- data.accessedModules) { + lookupModule(moduleName).accessModule() + } + + for (className <- data.instantiatedClasses) { + lookupClass(className).instantiated() + } + + for (className <- data.accessedClassData) { + lookupClass(className).accessData() + } + + for ((className, methods) <- data.calledMethods) { + val classInfo = lookupClass(className) + for (methodName <- methods) + classInfo.callMethod(methodName) + } + + for ((className, methods) <- data.calledMethodsStatic) { + val classInfo = lookupClass(className) + for (methodName <- methods) + classInfo.callMethod(methodName, static = true) + } + } + } + } + + def isReflProxyName(encodedName: String): Boolean = { + encodedName.endsWith("__") && + (encodedName != "init___") && (encodedName != "__init__") + } + + def isConstructorName(encodedName: String): Boolean = + encodedName.startsWith("init___") || (encodedName == "__init__") + + private def createMissingClassInfo(encodedName: String): Infos.ClassInfo = { + val kind = + if (encodedName.endsWith("$")) ClassKind.ModuleClass + else if (encodedName.endsWith("$class")) ClassKind.TraitImpl + else ClassKind.Class + Infos.ClassInfo( + name = s"<$encodedName>", + encodedName = encodedName, + isExported = false, + ancestorCount = if (kind.isClass) 1 else 0, + kind = kind, + superClass = if (kind.isClass) "O" else "", + ancestors = List(encodedName, "O"), + methods = List( + createMissingMethodInfo("__init__"), + createMissingMethodInfo("init___")) + ) + } + + private def createMissingMethodInfo(encodedName: String, + isAbstract: Boolean = false): Infos.MethodInfo = { + Infos.MethodInfo(encodedName = encodedName, isAbstract = isAbstract) + } + + def warnCallStack()(implicit from: From): Unit = { + val seenInfos = mutable.Set.empty[AnyRef] + + def rec(level: Level, optFrom: Option[From], + verb: String = "called"): Unit = { + val involvedClasses = new mutable.ListBuffer[ClassInfo] + + def onlyOnce(info: AnyRef): Boolean = { + if (seenInfos.add(info)) { + true + } else { + logger.log(level, " (already seen, not repeating call stack)") + false + } + } + + @tailrec + def loopTrace(optFrom: Option[From], verb: String = "called"): Unit = { + optFrom match { + case None => + logger.log(level, s"$verb from ... er ... nowhere!? (this is a bug in dce)") + case Some(from) => + from match { + case FromMethod(methodInfo) => + logger.log(level, s"$verb from $methodInfo") + if (onlyOnce(methodInfo)) { + methodInfo.instantiatedSubclass.foreach(involvedClasses += _) + loopTrace(methodInfo.calledFrom) + } + case FromCore => + logger.log(level, s"$verb from scalajs-corejslib.js") + case FromExports => + logger.log(level, "exported to JavaScript with @JSExport") + case FromManual => + logger.log(level, "manually made reachable") + } + } + } + + logger.indented { + loopTrace(optFrom, verb = verb) + } + + if (involvedClasses.nonEmpty) { + logger.log(level, "involving instantiated classes:") + logger.indented { + for (classInfo <- involvedClasses.result().distinct) { + logger.log(level, s"$classInfo") + if (onlyOnce(classInfo)) + rec(Level.Debug, classInfo.instantiatedFrom, verb = "instantiated") + // recurse with Debug log level not to overwhelm the user + } + } + } + } + + rec(Level.Warn, Some(from)) + } +} diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/optimizer/GenIncOptimizer.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/optimizer/GenIncOptimizer.scala new file mode 100644 index 0000000..47e1f87 --- /dev/null +++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/optimizer/GenIncOptimizer.scala @@ -0,0 +1,921 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js tools ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.tools.optimizer + +import language.higherKinds + +import scala.annotation.{switch, tailrec} + +import scala.collection.{GenMap, GenTraversableOnce, GenIterable, GenIterableLike} +import scala.collection.mutable + +import scala.scalajs.ir._ +import Definitions.isConstructorName +import Infos.OptimizerHints +import Trees._ +import Types._ + +import scala.scalajs.tools.sem._ + +import scala.scalajs.tools.javascript +import javascript.Trees.{Tree => JSTree} +import javascript.ScalaJSClassEmitter + +import scala.scalajs.tools.logging._ + +/** Incremental optimizer. + * An incremental optimizer consumes the reachability analysis produced by + * an [[Analyzer]], as well as trees for classes, trait impls, etc., and + * optimizes them in an incremental way. + * It maintains state between runs to do a minimal amount of work on every + * run, based on detecting what parts of the program must be re-optimized, + * and keeping optimized results from previous runs for the rest. + */ +abstract class GenIncOptimizer(semantics: Semantics) { + import GenIncOptimizer._ + + protected val CollOps: AbsCollOps + + private val classEmitter = new ScalaJSClassEmitter(semantics) + + private var logger: Logger = _ + + /** Are we in batch mode? I.e., are we running from scratch? + * Various parts of the algorithm can be skipped entirely when running in + * batch mode. + */ + private var batchMode: Boolean = false + + /** Should positions be considered when comparing tree hashes */ + private var considerPositions: Boolean = _ + + private var objectClass: Class = _ + private val classes = CollOps.emptyMap[String, Class] + private val traitImpls = CollOps.emptyParMap[String, TraitImpl] + + protected def getInterface(encodedName: String): InterfaceType + + /** Schedule a method for processing in the PROCESS PASS */ + protected def scheduleMethod(method: MethodImpl): Unit + + protected def newMethodImpl(owner: MethodContainer, + encodedName: String): MethodImpl + + def findTraitImpl(encodedName: String): TraitImpl = traitImpls(encodedName) + def findClass(encodedName: String): Class = classes(encodedName) + + def getTraitImpl(encodedName: String): Option[TraitImpl] = traitImpls.get(encodedName) + def getClass(encodedName: String): Option[Class] = classes.get(encodedName) + + type GetClassTreeIfChanged = + (String, Option[String]) => Option[(ClassDef, Option[String])] + + private def withLogger[A](logger: Logger)(body: => A): A = { + assert(this.logger == null) + this.logger = logger + try body + finally this.logger = null + } + + /** Update the incremental analyzer with a new run. */ + def update(analyzer: Analyzer, + getClassTreeIfChanged: GetClassTreeIfChanged, considerPositions: Boolean, + logger: Logger): Unit = withLogger(logger) { + + batchMode = objectClass == null + this.considerPositions = considerPositions + logger.debug(s"Optimizer batch mode: $batchMode") + + logTime(logger, "Incremental part of inc. optimizer") { + /* UPDATE PASS */ + updateAndTagEverything(analyzer, getClassTreeIfChanged) + } + + logTime(logger, "Optimizer part of inc. optimizer") { + /* PROCESS PASS */ + processAllTaggedMethods() + } + } + + /** Incremental part: update state and detect what needs to be re-optimized. + * UPDATE PASS ONLY. (This IS the update pass). + */ + private def updateAndTagEverything(analyzer: Analyzer, + getClassTreeIfChanged: GetClassTreeIfChanged): Unit = { + + val neededClasses = CollOps.emptyParMap[String, analyzer.ClassInfo] + val neededTraitImpls = CollOps.emptyParMap[String, analyzer.ClassInfo] + for { + classInfo <- analyzer.classInfos.values + if classInfo.isNeededAtAll + } { + if (classInfo.isClass && classInfo.isAnySubclassInstantiated) + CollOps.put(neededClasses, classInfo.encodedName, classInfo) + else if (classInfo.isImplClass) + CollOps.put(neededTraitImpls, classInfo.encodedName, classInfo) + } + + /* Remove deleted trait impls, and update existing trait impls. + * We don't even have to notify callers in case of additions or removals + * because callers have got to be invalidated by themselves. + * Only changed methods need to trigger notifications. + * + * Non-batch mode only. + */ + assert(!batchMode || traitImpls.isEmpty) + if (!batchMode) { + CollOps.retain(traitImpls) { (traitImplName, traitImpl) => + CollOps.remove(neededTraitImpls, traitImplName).fold { + /* Deleted trait impl. Mark all its methods as deleted, and remove it + * from known trait impls. + */ + traitImpl.methods.values.foreach(_.delete()) + + false + } { traitImplInfo => + /* Existing trait impl. Update it. */ + val (added, changed, removed) = + traitImpl.updateWith(traitImplInfo, getClassTreeIfChanged) + for (method <- changed) + traitImpl.myInterface.tagStaticCallersOf(method) + + true + } + } + } + + /* Add new trait impls. + * Easy, we don't have to notify anyone. + */ + for (traitImplInfo <- neededTraitImpls.values) { + val traitImpl = new TraitImpl(traitImplInfo.encodedName) + CollOps.put(traitImpls, traitImpl.encodedName, traitImpl) + traitImpl.updateWith(traitImplInfo, getClassTreeIfChanged) + } + + if (!batchMode) { + /* Class removals: + * * If a class is deleted or moved, delete its entire subtree (because + * all its descendants must also be deleted or moved). + * * If an existing class was instantiated but is no more, notify callers + * of its methods. + * + * Non-batch mode only. + */ + val objectClassStillExists = + objectClass.walkClassesForDeletions(neededClasses.get(_)) + assert(objectClassStillExists, "Uh oh, java.lang.Object was deleted!") + + /* Class changes: + * * Delete removed methods, update existing ones, add new ones + * * Update the list of ancestors + * * Class newly instantiated + * + * Non-batch mode only. + */ + objectClass.walkForChanges( + CollOps.remove(neededClasses, _).get, + getClassTreeIfChanged, + Set.empty) + } + + /* Class additions: + * * Add new classes (including those that have moved from elsewhere). + * In batch mode, we avoid doing notifications. + */ + + // Group children by (immediate) parent + val newChildrenByParent = CollOps.emptyAccMap[String, Analyzer#ClassInfo] + + for (classInfo <- neededClasses.values) { + val superInfo = classInfo.superClass + if (superInfo == null) { + assert(batchMode, "Trying to add java.lang.Object in incremental mode") + objectClass = new Class(None, classInfo.encodedName) + classes += classInfo.encodedName -> objectClass + objectClass.setupAfterCreation(classInfo, getClassTreeIfChanged) + } else { + CollOps.acc(newChildrenByParent, superInfo.encodedName, classInfo) + } + } + + val getNewChildren = + (name: String) => CollOps.getAcc(newChildrenByParent, name) + + // Walk the tree to add children + if (batchMode) { + objectClass.walkForAdditions(getNewChildren, getClassTreeIfChanged) + } else { + val existingParents = + CollOps.parFlatMapKeys(newChildrenByParent)(classes.get) + for (parent <- existingParents) + parent.walkForAdditions(getNewChildren, getClassTreeIfChanged) + } + + } + + /** Optimizer part: process all methods that need reoptimizing. + * PROCESS PASS ONLY. (This IS the process pass). + */ + protected def processAllTaggedMethods(): Unit + + protected def logProcessingMethods(count: Int): Unit = + logger.debug(s"Optimizing $count methods.") + + /** Base class for [[Class]] and [[TraitImpl]]. */ + abstract class MethodContainer(val encodedName: String) { + def thisType: Type + + val myInterface = getInterface(encodedName) + + val methods = mutable.Map.empty[String, MethodImpl] + + var lastVersion: Option[String] = None + + private def reachableMethodsOf(info: Analyzer#ClassInfo): Set[String] = { + (for { + methodInfo <- info.methodInfos.values + if methodInfo.isReachable && !methodInfo.isAbstract + } yield { + methodInfo.encodedName + }).toSet + } + + /** UPDATE PASS ONLY. Global concurrency safe but not on same instance */ + def updateWith(info: Analyzer#ClassInfo, + getClassTreeIfChanged: GetClassTreeIfChanged): (Set[String], Set[String], Set[String]) = { + myInterface.ancestors = info.ancestors.map(_.encodedName).toList + + val addedMethods = Set.newBuilder[String] + val changedMethods = Set.newBuilder[String] + val deletedMethods = Set.newBuilder[String] + + val reachableMethods = reachableMethodsOf(info) + val methodSetChanged = methods.keySet != reachableMethods + if (methodSetChanged) { + // Remove deleted methods + methods retain { (methodName, method) => + if (reachableMethods.contains(methodName)) { + true + } else { + deletedMethods += methodName + method.delete() + false + } + } + // Clear lastVersion if there are new methods + if (reachableMethods.exists(!methods.contains(_))) + lastVersion = None + } + for ((tree, version) <- getClassTreeIfChanged(encodedName, lastVersion)) { + lastVersion = version + this match { + case cls: Class => + cls.isModuleClass = tree.kind == ClassKind.ModuleClass + cls.fields = for (field @ VarDef(_, _, _, _) <- tree.defs) yield field + case _ => + } + tree.defs.foreach { + case methodDef: MethodDef if methodDef.name.isInstanceOf[Ident] && + reachableMethods.contains(methodDef.name.name) => + val methodName = methodDef.name.name + + val methodInfo = info.methodInfos(methodName) + methods.get(methodName).fold { + addedMethods += methodName + val method = newMethodImpl(this, methodName) + method.updateWith(methodInfo, methodDef) + methods(methodName) = method + method + } { method => + if (method.updateWith(methodInfo, methodDef)) + changedMethods += methodName + method + } + + case _ => // ignore + } + } + + (addedMethods.result(), changedMethods.result(), deletedMethods.result()) + } + } + + /** Class in the class hierarchy (not an interface). + * A class may be a module class. + * A class knows its superclass and the interfaces it implements. It also + * maintains a list of its direct subclasses, so that the instances of + * [[Class]] form a tree of the class hierarchy. + */ + class Class(val superClass: Option[Class], + _encodedName: String) extends MethodContainer(_encodedName) { + if (encodedName == Definitions.ObjectClass) { + assert(superClass.isEmpty) + assert(objectClass == null) + } else { + assert(superClass.isDefined) + } + + /** Parent chain from this to Object. */ + val parentChain: List[Class] = + this :: superClass.fold[List[Class]](Nil)(_.parentChain) + + /** Reverse parent chain from Object to this. */ + val reverseParentChain: List[Class] = + parentChain.reverse + + def thisType: Type = ClassType(encodedName) + + var interfaces: Set[InterfaceType] = Set.empty + var subclasses: CollOps.ParIterable[Class] = CollOps.emptyParIterable + var isInstantiated: Boolean = false + + var isModuleClass: Boolean = false + var hasElidableModuleAccessor: Boolean = false + + var fields: List[VarDef] = Nil + var isInlineable: Boolean = false + var tryNewInlineable: Option[RecordValue] = None + + override def toString(): String = + encodedName + + /** Walk the class hierarchy tree for deletions. + * This includes "deleting" classes that were previously instantiated but + * are no more. + * UPDATE PASS ONLY. Not concurrency safe on same instance. + */ + def walkClassesForDeletions( + getClassInfoIfNeeded: String => Option[Analyzer#ClassInfo]): Boolean = { + def sameSuperClass(info: Analyzer#ClassInfo): Boolean = + if (info.superClass == null) superClass.isEmpty + else superClass.exists(_.encodedName == info.superClass.encodedName) + + getClassInfoIfNeeded(encodedName) match { + case Some(classInfo) if sameSuperClass(classInfo) => + // Class still exists. Recurse. + subclasses = subclasses.filter( + _.walkClassesForDeletions(getClassInfoIfNeeded)) + if (isInstantiated && !classInfo.isInstantiated) + notInstantiatedAnymore() + true + case _ => + // Class does not exist or has been moved. Delete the entire subtree. + deleteSubtree() + false + } + } + + /** Delete this class and all its subclasses. UPDATE PASS ONLY. */ + def deleteSubtree(): Unit = { + delete() + for (subclass <- subclasses) + subclass.deleteSubtree() + } + + /** UPDATE PASS ONLY. */ + private def delete(): Unit = { + if (isInstantiated) + notInstantiatedAnymore() + for (method <- methods.values) + method.delete() + classes -= encodedName + /* Note: no need to tag methods that call *statically* one of the methods + * of the deleted classes, since they've got to be invalidated by + * themselves. + */ + } + + /** UPDATE PASS ONLY. */ + def notInstantiatedAnymore(): Unit = { + assert(isInstantiated) + isInstantiated = false + for (intf <- interfaces) { + intf.removeInstantiatedSubclass(this) + for (methodName <- allMethods().keys) + intf.tagDynamicCallersOf(methodName) + } + } + + /** UPDATE PASS ONLY. */ + def walkForChanges( + getClassInfo: String => Analyzer#ClassInfo, + getClassTreeIfChanged: GetClassTreeIfChanged, + parentMethodAttributeChanges: Set[String]): Unit = { + + val classInfo = getClassInfo(encodedName) + + val (addedMethods, changedMethods, deletedMethods) = + updateWith(classInfo, getClassTreeIfChanged) + + val oldInterfaces = interfaces + val newInterfaces = + classInfo.ancestors.map(info => getInterface(info.encodedName)).toSet + interfaces = newInterfaces + + val methodAttributeChanges = + (parentMethodAttributeChanges -- methods.keys ++ + addedMethods ++ changedMethods ++ deletedMethods) + + // Tag callers with dynamic calls + val wasInstantiated = isInstantiated + isInstantiated = classInfo.isInstantiated + assert(!(wasInstantiated && !isInstantiated), + "(wasInstantiated && !isInstantiated) should have been handled "+ + "during deletion phase") + + if (isInstantiated) { + if (wasInstantiated) { + val existingInterfaces = oldInterfaces.intersect(newInterfaces) + for { + intf <- existingInterfaces + methodName <- methodAttributeChanges + } { + intf.tagDynamicCallersOf(methodName) + } + if (newInterfaces.size != oldInterfaces.size || + newInterfaces.size != existingInterfaces.size) { + val allMethodNames = allMethods().keys + for { + intf <- oldInterfaces ++ newInterfaces -- existingInterfaces + methodName <- allMethodNames + } { + intf.tagDynamicCallersOf(methodName) + } + } + } else { + val allMethodNames = allMethods().keys + for (intf <- interfaces) { + intf.addInstantiatedSubclass(this) + for (methodName <- allMethodNames) + intf.tagDynamicCallersOf(methodName) + } + } + } + + // Tag callers with static calls + for (methodName <- methodAttributeChanges) + myInterface.tagStaticCallersOf(methodName) + + // Module class specifics + updateHasElidableModuleAccessor() + + // Inlineable class + if (updateIsInlineable(classInfo)) { + for (method <- methods.values; if isConstructorName(method.encodedName)) + myInterface.tagStaticCallersOf(method.encodedName) + } + + // Recurse in subclasses + for (cls <- subclasses) + cls.walkForChanges(getClassInfo, getClassTreeIfChanged, + methodAttributeChanges) + } + + /** UPDATE PASS ONLY. */ + def walkForAdditions( + getNewChildren: String => GenIterable[Analyzer#ClassInfo], + getClassTreeIfChanged: GetClassTreeIfChanged): Unit = { + + val subclassAcc = CollOps.prepAdd(subclasses) + + for (classInfo <- getNewChildren(encodedName)) { + val cls = new Class(Some(this), classInfo.encodedName) + CollOps.add(subclassAcc, cls) + classes += classInfo.encodedName -> cls + cls.setupAfterCreation(classInfo, getClassTreeIfChanged) + cls.walkForAdditions(getNewChildren, getClassTreeIfChanged) + } + + subclasses = CollOps.finishAdd(subclassAcc) + } + + /** UPDATE PASS ONLY. */ + def updateHasElidableModuleAccessor(): Unit = { + hasElidableModuleAccessor = + isAdHocElidableModuleAccessor(encodedName) || + (isModuleClass && lookupMethod("init___").exists(isElidableModuleConstructor)) + } + + /** UPDATE PASS ONLY. */ + def updateIsInlineable(classInfo: Analyzer#ClassInfo): Boolean = { + val oldTryNewInlineable = tryNewInlineable + isInlineable = classInfo.optimizerHints.hasInlineAnnot + if (!isInlineable) { + tryNewInlineable = None + } else { + val allFields = reverseParentChain.flatMap(_.fields) + val (fieldValues, fieldTypes) = (for { + VarDef(Ident(name, originalName), tpe, mutable, rhs) <- allFields + } yield { + (rhs, RecordType.Field(name, originalName, tpe, mutable)) + }).unzip + tryNewInlineable = Some( + RecordValue(RecordType(fieldTypes), fieldValues)(Position.NoPosition)) + } + tryNewInlineable != oldTryNewInlineable + } + + /** UPDATE PASS ONLY. */ + def setupAfterCreation(classInfo: Analyzer#ClassInfo, + getClassTreeIfChanged: GetClassTreeIfChanged): Unit = { + + updateWith(classInfo, getClassTreeIfChanged) + interfaces = + classInfo.ancestors.map(info => getInterface(info.encodedName)).toSet + + isInstantiated = classInfo.isInstantiated + + if (batchMode) { + if (isInstantiated) { + /* Only add the class to all its ancestor interfaces */ + for (intf <- interfaces) + intf.addInstantiatedSubclass(this) + } + } else { + val allMethodNames = allMethods().keys + + if (isInstantiated) { + /* Add the class to all its ancestor interfaces + notify all callers + * of any of the methods. + * TODO: be more selective on methods that are notified: it is not + * necessary to modify callers of methods defined in a parent class + * that already existed in the previous run. + */ + for (intf <- interfaces) { + intf.addInstantiatedSubclass(this) + for (methodName <- allMethodNames) + intf.tagDynamicCallersOf(methodName) + } + } + + /* Tag static callers because the class could have been *moved*, + * not just added. + */ + for (methodName <- allMethodNames) + myInterface.tagStaticCallersOf(methodName) + } + + updateHasElidableModuleAccessor() + updateIsInlineable(classInfo) + } + + /** UPDATE PASS ONLY. */ + private def isElidableModuleConstructor(impl: MethodImpl): Boolean = { + def isTriviallySideEffectFree(tree: Tree): Boolean = tree match { + case _:VarRef | _:Literal | _:This => true + case _ => false + } + def isElidableStat(tree: Tree): Boolean = tree match { + case Block(stats) => + stats.forall(isElidableStat) + case Assign(Select(This(), _, _), rhs) => + isTriviallySideEffectFree(rhs) + case TraitImplApply(ClassType(traitImpl), methodName, List(This())) => + traitImpls(traitImpl).methods(methodName.name).originalDef.body match { + case Skip() => true + case _ => false + } + case StaticApply(This(), ClassType(cls), methodName, args) => + Definitions.isConstructorName(methodName.name) && + args.forall(isTriviallySideEffectFree) && + impl.owner.asInstanceOf[Class].superClass.exists { superCls => + superCls.encodedName == cls && + superCls.lookupMethod(methodName.name).exists(isElidableModuleConstructor) + } + case StoreModule(_, _) => + true + case _ => + isTriviallySideEffectFree(tree) + } + isElidableStat(impl.originalDef.body) + } + + /** All the methods of this class, including inherited ones. + * It has () so we remember this is an expensive operation. + * UPDATE PASS ONLY. + */ + def allMethods(): scala.collection.Map[String, MethodImpl] = { + val result = mutable.Map.empty[String, MethodImpl] + for (parent <- reverseParentChain) + result ++= parent.methods + result + } + + /** BOTH PASSES. */ + @tailrec + final def lookupMethod(methodName: String): Option[MethodImpl] = { + methods.get(methodName) match { + case Some(impl) => Some(impl) + case none => + superClass match { + case Some(p) => p.lookupMethod(methodName) + case none => None + } + } + } + } + + /** Trait impl. */ + class TraitImpl(_encodedName: String) extends MethodContainer(_encodedName) { + def thisType: Type = NoType + } + + /** Thing from which a [[MethodImpl]] can unregister itself from. */ + trait Unregisterable { + /** UPDATE PASS ONLY. */ + def unregisterDependee(dependee: MethodImpl): Unit + } + + /** Type of a class or interface. + * Types are created on demand when a method is called on a given + * [[ClassType]]. + * + * Fully concurrency safe unless otherwise noted. + */ + abstract class InterfaceType(val encodedName: String) extends Unregisterable { + + override def toString(): String = + s"intf $encodedName" + + /** PROCESS PASS ONLY. Concurrency safe except with + * [[addInstantiatedSubclass]] and [[removeInstantiatedSubclass]] + */ + def instantiatedSubclasses: Iterable[Class] + + /** UPDATE PASS ONLY. Concurrency safe except with + * [[instantiatedSubclasses]] + */ + def addInstantiatedSubclass(x: Class): Unit + + /** UPDATE PASS ONLY. Concurrency safe except with + * [[instantiatedSubclasses]] + */ + def removeInstantiatedSubclass(x: Class): Unit + + /** PROCESS PASS ONLY. Concurrency safe except with [[ancestors_=]] */ + def ancestors: List[String] + + /** UPDATE PASS ONLY. Not concurrency safe. */ + def ancestors_=(v: List[String]): Unit + + /** PROCESS PASS ONLY. Concurrency safe except with [[ancestors_=]]. */ + def registerAskAncestors(asker: MethodImpl): Unit + + /** PROCESS PASS ONLY. */ + def registerDynamicCaller(methodName: String, caller: MethodImpl): Unit + + /** PROCESS PASS ONLY. */ + def registerStaticCaller(methodName: String, caller: MethodImpl): Unit + + /** UPDATE PASS ONLY. */ + def tagDynamicCallersOf(methodName: String): Unit + + /** UPDATE PASS ONLY. */ + def tagStaticCallersOf(methodName: String): Unit + } + + /** A method implementation. + * It must be concrete, and belong either to a [[Class]] or a [[TraitImpl]]. + * + * A single instance is **not** concurrency safe (unless otherwise noted in + * a method comment). However, the global state modifications are + * concurrency safe. + */ + abstract class MethodImpl(val owner: MethodContainer, + val encodedName: String) extends OptimizerCore.MethodImpl + with OptimizerCore.AbstractMethodID + with Unregisterable { + private[this] var _deleted: Boolean = false + + var optimizerHints: OptimizerHints = OptimizerHints.empty + var originalDef: MethodDef = _ + var desugaredDef: JSTree = _ + var preciseInfo: Infos.MethodInfo = _ + + def thisType: Type = owner.thisType + def deleted: Boolean = _deleted + + override def toString(): String = + s"$owner.$encodedName" + + /** PROCESS PASS ONLY. */ + def registerBodyAsker(asker: MethodImpl): Unit + + /** UPDATE PASS ONLY. */ + def tagBodyAskers(): Unit + + /** PROCESS PASS ONLY. */ + private def registerAskAncestors(intf: InterfaceType): Unit = { + intf.registerAskAncestors(this) + registeredTo(intf) + } + + /** PROCESS PASS ONLY. */ + private def registerDynamicCall(intf: InterfaceType, + methodName: String): Unit = { + intf.registerDynamicCaller(methodName, this) + registeredTo(intf) + } + + /** PROCESS PASS ONLY. */ + private def registerStaticCall(intf: InterfaceType, + methodName: String): Unit = { + intf.registerStaticCaller(methodName, this) + registeredTo(intf) + } + + /** PROCESS PASS ONLY. */ + def registerAskBody(target: MethodImpl): Unit = { + target.registerBodyAsker(this) + registeredTo(target) + } + + /** PROCESS PASS ONLY. */ + protected def registeredTo(intf: Unregisterable): Unit + + /** UPDATE PASS ONLY. */ + protected def unregisterFromEverywhere(): Unit + + /** Return true iff this is the first time this method is called since the + * last reset (via [[resetTag]]). + * UPDATE PASS ONLY. + */ + protected def protectTag(): Boolean + + /** PROCESS PASS ONLY. */ + protected def resetTag(): Unit + + /** Returns true if the method's attributes changed. + * Attributes are whether it is inlineable, and whether it is a trait + * impl forwarder. Basically this is what is declared in + * [[OptimizerCore.AbstractMethodID]]. + * In the process, tags all the body askers if the body changes. + * UPDATE PASS ONLY. Not concurrency safe on same instance. + */ + def updateWith(methodInfo: Analyzer#MethodInfo, + methodDef: MethodDef): Boolean = { + assert(!_deleted, "updateWith() called on a deleted method") + + val bodyChanged = { + originalDef == null || + (methodDef.hash zip originalDef.hash).forall { + case (h1, h2) => !Hashers.hashesEqual(h1, h2, considerPositions) + } + } + + if (bodyChanged) + tagBodyAskers() + + val hints = methodInfo.optimizerHints + val changed = hints != optimizerHints || bodyChanged + if (changed) { + val oldAttributes = (inlineable, isTraitImplForwarder) + + optimizerHints = hints + originalDef = methodDef + desugaredDef = null + preciseInfo = null + updateInlineable() + tag() + + val newAttributes = (inlineable, isTraitImplForwarder) + newAttributes != oldAttributes + } else { + false + } + } + + /** UPDATE PASS ONLY. Not concurrency safe on same instance. */ + def delete(): Unit = { + assert(!_deleted, "delete() called twice") + _deleted = true + if (protectTag()) + unregisterFromEverywhere() + } + + /** Concurrency safe with itself and [[delete]] on the same instance + * + * [[tag]] can be called concurrently with [[delete]] when methods in + * traits/classes are updated. + * + * UPDATE PASS ONLY. + */ + def tag(): Unit = if (protectTag()) { + scheduleMethod(this) + unregisterFromEverywhere() + } + + /** PROCESS PASS ONLY. */ + def process(): Unit = if (!_deleted) { + val (optimizedDef, info) = new Optimizer().optimize(thisType, originalDef) + desugaredDef = + if (owner.isInstanceOf[Class]) + classEmitter.genMethod(owner.encodedName, optimizedDef) + else + classEmitter.genTraitImplMethod(owner.encodedName, optimizedDef) + preciseInfo = info + resetTag() + } + + /** All methods are PROCESS PASS ONLY */ + private class Optimizer extends OptimizerCore(semantics) { + type MethodID = MethodImpl + + val myself: MethodImpl.this.type = MethodImpl.this + + protected def getMethodBody(method: MethodID): MethodDef = { + MethodImpl.this.registerAskBody(method) + method.originalDef + } + + protected def dynamicCall(intfName: String, + methodName: String): List[MethodID] = { + val intf = getInterface(intfName) + MethodImpl.this.registerDynamicCall(intf, methodName) + intf.instantiatedSubclasses.flatMap(_.lookupMethod(methodName)).toList + } + + protected def staticCall(className: String, + methodName: String): Option[MethodID] = { + val clazz = classes(className) + MethodImpl.this.registerStaticCall(clazz.myInterface, methodName) + clazz.lookupMethod(methodName) + } + + protected def traitImplCall(traitImplName: String, + methodName: String): Option[MethodID] = { + val traitImpl = traitImpls(traitImplName) + registerStaticCall(traitImpl.myInterface, methodName) + traitImpl.methods.get(methodName) + } + + protected def getAncestorsOf(intfName: String): List[String] = { + val intf = getInterface(intfName) + registerAskAncestors(intf) + intf.ancestors + } + + protected def hasElidableModuleAccessor(moduleClassName: String): Boolean = + classes(moduleClassName).hasElidableModuleAccessor + + protected def tryNewInlineableClass(className: String): Option[RecordValue] = + classes(className).tryNewInlineable + } + } + +} + +object GenIncOptimizer { + + private val isAdHocElidableModuleAccessor = + Set("s_Predef$") + + private[optimizer] def logTime[A](logger: Logger, + title: String)(body: => A): A = { + val startTime = System.nanoTime() + val result = body + val endTime = System.nanoTime() + val elapsedTime = endTime - startTime + logger.time(title, elapsedTime) + result + } + + private[optimizer] trait AbsCollOps { + type Map[K, V] <: mutable.Map[K, V] + type ParMap[K, V] <: GenMap[K, V] + type AccMap[K, V] + type ParIterable[V] <: GenIterableLike[V, ParIterable[V]] + type Addable[V] + + def emptyAccMap[K, V]: AccMap[K, V] + def emptyMap[K, V]: Map[K, V] + def emptyParMap[K, V]: ParMap[K, V] + def emptyParIterable[V]: ParIterable[V] + + // Operations on ParMap + def put[K, V](map: ParMap[K, V], k: K, v: V): Unit + def remove[K, V](map: ParMap[K, V], k: K): Option[V] + def retain[K, V](map: ParMap[K, V])(p: (K, V) => Boolean): Unit + + // Operations on AccMap + def acc[K, V](map: AccMap[K, V], k: K, v: V): Unit + def getAcc[K, V](map: AccMap[K, V], k: K): GenIterable[V] + def parFlatMapKeys[A, B](map: AccMap[A, _])( + f: A => GenTraversableOnce[B]): GenIterable[B] + + // Operations on ParIterable + def prepAdd[V](it: ParIterable[V]): Addable[V] + def add[V](addable: Addable[V], v: V): Unit + def finishAdd[V](addable: Addable[V]): ParIterable[V] + + } + +} diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/optimizer/IRChecker.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/optimizer/IRChecker.scala new file mode 100644 index 0000000..6329826 --- /dev/null +++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/optimizer/IRChecker.scala @@ -0,0 +1,854 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js tools ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.tools.optimizer + +import scala.language.implicitConversions + +import scala.annotation.switch + +import scala.collection.mutable + +import scala.scalajs.ir._ +import Definitions._ +import Trees._ +import Types._ + +import scala.scalajs.tools.logging._ + +/** Checker for the validity of the IR. */ +class IRChecker(analyzer: Analyzer, allClassDefs: Seq[ClassDef], logger: Logger) { + import IRChecker._ + + private var _errorCount: Int = 0 + def errorCount: Int = _errorCount + + private val classes: mutable.Map[String, CheckedClass] = { + mutable.Map.empty[String, CheckedClass] ++= + allClassDefs.map(new CheckedClass(_)).map(c => c.name -> c) + } + + def check(): Boolean = { + for { + classDef <- allClassDefs + if analyzer.classInfos(classDef.name.name).isNeededAtAll + } { + classDef.kind match { + case ClassKind.Class | ClassKind.ModuleClass => checkClass(classDef) + case ClassKind.TraitImpl => checkTraitImpl(classDef) + case _ => + } + } + errorCount == 0 + } + + def checkClass(classDef: ClassDef): Unit = { + if (!analyzer.classInfos(classDef.name.name).isAnySubclassInstantiated) + return + + for (member <- classDef.defs) { + implicit val ctx = ErrorContext(member) + member match { + // Scala declarations + case v @ VarDef(_, _, _, _) => + checkFieldDef(v, classDef) + case m: MethodDef if m.name.isInstanceOf[Ident] => + checkMethodDef(m, classDef) + + // Exports + case m: MethodDef if m.name.isInstanceOf[StringLiteral] => + checkExportedMethodDef(m, classDef) + case member @ PropertyDef(_: StringLiteral, _, _, _) => + checkExportedPropertyDef(member, classDef) + case member @ ConstructorExportDef(_, _, _) => + checkConstructorExportDef(member, classDef) + case member @ ModuleExportDef(_) => + checkModuleExportDef(member, classDef) + + // Anything else is illegal + case _ => + reportError(s"Illegal class member of type ${member.getClass.getName}") + } + } + } + + def checkTraitImpl(classDef: ClassDef): Unit = { + for (member <- classDef.defs) { + implicit val ctx = ErrorContext(member) + member match { + case m: MethodDef => + checkMethodDef(m, classDef) + case _ => + reportError(s"Invalid member for a TraitImpl") + } + } + } + + def checkFieldDef(fieldDef: VarDef, classDef: ClassDef): Unit = { + val VarDef(name, tpe, mutable, rhs) = fieldDef + implicit val ctx = ErrorContext(fieldDef) + + if (tpe == NoType) + reportError(s"VarDef cannot have type NoType") + else + typecheckExpect(rhs, Env.empty, tpe) + } + + def checkMethodDef(methodDef: MethodDef, classDef: ClassDef): Unit = { + val MethodDef(Ident(name, _), params, resultType, body) = methodDef + implicit val ctx = ErrorContext(methodDef) + + if (!analyzer.classInfos(classDef.name.name).methodInfos(name).isReachable) + return + + for (ParamDef(name, tpe, _) <- params) + if (tpe == NoType) + reportError(s"Parameter $name has type NoType") + + val resultTypeForSig = + if (isConstructorName(name)) NoType + else resultType + + val advertizedSig = (params.map(_.ptpe), resultTypeForSig) + val sigFromName = inferMethodType(name, + inTraitImpl = classDef.kind == ClassKind.TraitImpl) + if (advertizedSig != sigFromName) { + reportError( + s"The signature of ${classDef.name.name}.$name, which is "+ + s"$advertizedSig, does not match its name (should be $sigFromName).") + } + + val thisType = + if (!classDef.kind.isClass) NoType + else ClassType(classDef.name.name) + val bodyEnv = Env.fromSignature(thisType, params, resultType) + if (resultType == NoType) + typecheckStat(body, bodyEnv) + else + typecheckExpect(body, bodyEnv, resultType) + } + + def checkExportedMethodDef(methodDef: MethodDef, classDef: ClassDef): Unit = { + val MethodDef(_, params, resultType, body) = methodDef + implicit val ctx = ErrorContext(methodDef) + + if (!classDef.kind.isClass) { + reportError(s"Exported method def can only appear in a class") + return + } + + for (ParamDef(name, tpe, _) <- params) { + if (tpe == NoType) + reportError(s"Parameter $name has type NoType") + else if (tpe != AnyType) + reportError(s"Parameter $name of exported method def has type $tpe, "+ + "but must be Any") + } + + if (resultType != AnyType) { + reportError(s"Result type of exported method def is $resultType, "+ + "but must be Any") + } + + val thisType = ClassType(classDef.name.name) + val bodyEnv = Env.fromSignature(thisType, params, resultType) + .withArgumentsVar(methodDef.pos) + typecheckExpect(body, bodyEnv, resultType) + } + + def checkExportedPropertyDef(propDef: PropertyDef, classDef: ClassDef): Unit = { + val PropertyDef(_, getterBody, setterArg, setterBody) = propDef + implicit val ctx = ErrorContext(propDef) + + if (!classDef.kind.isClass) { + reportError(s"Exported property def can only appear in a class") + return + } + + val thisType = ClassType(classDef.name.name) + + if (getterBody != EmptyTree) { + val getterBodyEnv = Env.fromSignature(thisType, Nil, AnyType) + typecheckExpect(getterBody, getterBodyEnv, AnyType) + } + + if (setterBody != EmptyTree) { + if (setterArg.ptpe != AnyType) + reportError("Setter argument of exported property def has type "+ + s"${setterArg.ptpe}, but must be Any") + + val setterBodyEnv = Env.fromSignature(thisType, List(setterArg), NoType) + typecheckStat(setterBody, setterBodyEnv) + } + } + + def checkConstructorExportDef(ctorDef: ConstructorExportDef, + classDef: ClassDef): Unit = { + val ConstructorExportDef(_, params, body) = ctorDef + implicit val ctx = ErrorContext(ctorDef) + + if (!classDef.kind.isClass) { + reportError(s"Exported constructor def can only appear in a class") + return + } + + for (ParamDef(name, tpe, _) <- params) { + if (tpe == NoType) + reportError(s"Parameter $name has type NoType") + else if (tpe != AnyType) + reportError(s"Parameter $name of exported constructor def has type "+ + s"$tpe, but must be Any") + } + + val thisType = ClassType(classDef.name.name) + val bodyEnv = Env.fromSignature(thisType, params, NoType) + .withArgumentsVar(ctorDef.pos) + typecheckStat(body, bodyEnv) + } + + def checkModuleExportDef(moduleDef: ModuleExportDef, + classDef: ClassDef): Unit = { + implicit val ctx = ErrorContext(moduleDef) + + if (classDef.kind != ClassKind.ModuleClass) + reportError(s"Exported module def can only appear in a module class") + } + + def typecheckStat(tree: Tree, env: Env): Env = { + implicit val ctx = ErrorContext(tree) + + tree match { + case VarDef(ident, vtpe, mutable, rhs) => + typecheckExpect(rhs, env, vtpe) + env.withLocal(LocalDef(ident.name, vtpe, mutable)(tree.pos)) + + case Skip() => + env + + case Assign(select, rhs) => + select match { + case Select(_, Ident(name, _), false) => + /* TODO In theory this case would verify that we never assign to + * an immutable field. But we cannot do that because we *do* emit + * such assigns in constructors. + * In the future we might want to check that only these legal + * special cases happen, and nothing else. But it seems non-trivial + * to do so, so currently we trust scalac not to make us emit + * illegal assigns. + */ + //reportError(s"Assignment to immutable field $name.") + case VarRef(Ident(name, _), false) => + reportError(s"Assignment to immutable variable $name.") + case _ => + } + val lhsTpe = typecheckExpr(select, env) + val expectedRhsTpe = select match { + case _:JSDotSelect | _:JSBracketSelect => AnyType + case _ => lhsTpe + } + typecheckExpect(rhs, env, expectedRhsTpe) + env + + case StoreModule(cls, value) => + if (!cls.className.endsWith("$")) + reportError("StoreModule of non-module class $cls") + typecheckExpect(value, env, ClassType(cls.className)) + env + + case Block(stats) => + (env /: stats) { (prevEnv, stat) => + typecheckStat(stat, prevEnv) + } + env + + case Labeled(label, NoType, body) => + typecheckStat(body, env.withLabeledReturnType(label.name, AnyType)) + env + + case If(cond, thenp, elsep) => + typecheckExpect(cond, env, BooleanType) + typecheckStat(thenp, env) + typecheckStat(elsep, env) + env + + case While(cond, body, label) => + typecheckExpect(cond, env, BooleanType) + typecheckStat(body, env) + env + + case DoWhile(body, cond, label) => + typecheckStat(body, env) + typecheckExpect(cond, env, BooleanType) + env + + case Try(block, errVar, handler, finalizer) => + typecheckStat(block, env) + if (handler != EmptyTree) { + val handlerEnv = + env.withLocal(LocalDef(errVar.name, AnyType, false)(errVar.pos)) + typecheckStat(handler, handlerEnv) + } + if (finalizer != EmptyTree) { + typecheckStat(finalizer, env) + } + env + + case Match(selector, cases, default) => + typecheckExpr(selector, env) + for ((alts, body) <- cases) { + alts.foreach(typecheckExpr(_, env)) + typecheckStat(body, env) + } + typecheckStat(default, env) + env + + case Debugger() => + env + + case JSDelete(JSDotSelect(obj, prop)) => + typecheckExpr(obj, env) + env + + case JSDelete(JSBracketSelect(obj, prop)) => + typecheckExpr(obj, env) + typecheckExpr(prop, env) + env + + case _ => + typecheck(tree, env) + env + } + } + + def typecheckExpect(tree: Tree, env: Env, expectedType: Type)( + implicit ctx: ErrorContext): Unit = { + val tpe = typecheckExpr(tree, env) + if (!isSubtype(tpe, expectedType)) + reportError(s"$expectedType expected but $tpe found "+ + s"for tree of type ${tree.getClass.getName}") + } + + def typecheckExpr(tree: Tree, env: Env): Type = { + implicit val ctx = ErrorContext(tree) + if (tree.tpe == NoType) + reportError(s"Expression tree has type NoType") + typecheck(tree, env) + } + + def typecheck(tree: Tree, env: Env): Type = { + implicit val ctx = ErrorContext(tree) + + def checkApplyGeneric(methodName: String, methodFullName: String, + args: List[Tree], inTraitImpl: Boolean): Unit = { + val (methodParams, resultType) = inferMethodType(methodName, inTraitImpl) + if (args.size != methodParams.size) + reportError(s"Arity mismatch: ${methodParams.size} expected but "+ + s"${args.size} found") + for ((actual, formal) <- args zip methodParams) { + typecheckExpect(actual, env, formal) + } + if (!isConstructorName(methodName) && tree.tpe != resultType) + reportError(s"Call to $methodFullName of type $resultType "+ + s"typed as ${tree.tpe}") + } + + tree match { + // Control flow constructs + + case Block(statsAndExpr) => + val stats :+ expr = statsAndExpr + val envAfterStats = (env /: stats) { (prevEnv, stat) => + typecheckStat(stat, prevEnv) + } + typecheckExpr(expr, envAfterStats) + + case Labeled(label, tpe, body) => + typecheckExpect(body, env.withLabeledReturnType(label.name, tpe), tpe) + + case Return(expr, label) => + env.returnTypes.get(label.map(_.name)).fold[Unit] { + reportError(s"Cannot return to label $label.") + typecheckExpr(expr, env) + } { returnType => + typecheckExpect(expr, env, returnType) + } + + case If(cond, thenp, elsep) => + val tpe = tree.tpe + typecheckExpect(cond, env, BooleanType) + typecheckExpect(thenp, env, tpe) + typecheckExpect(elsep, env, tpe) + + case While(BooleanLiteral(true), body, label) if tree.tpe == NothingType => + typecheckStat(body, env) + + case Try(block, errVar, handler, finalizer) => + val tpe = tree.tpe + typecheckExpect(block, env, tpe) + if (handler != EmptyTree) { + val handlerEnv = + env.withLocal(LocalDef(errVar.name, AnyType, false)(errVar.pos)) + typecheckExpect(handler, handlerEnv, tpe) + } + if (finalizer != EmptyTree) { + typecheckStat(finalizer, env) + } + + case Throw(expr) => + typecheckExpr(expr, env) + + case Continue(label) => + /* Here we could check that it is indeed legal to break to the + * specified label. However, if we do anything illegal here, it will + * result in a SyntaxError in JavaScript anyway, so we do not really + * care. + */ + + case Match(selector, cases, default) => + val tpe = tree.tpe + typecheckExpr(selector, env) + for ((alts, body) <- cases) { + alts.foreach(typecheckExpr(_, env)) + typecheckExpect(body, env, tpe) + } + typecheckExpect(default, env, tpe) + + // Scala expressions + + case New(cls, ctor, args) => + val clazz = lookupClass(cls) + if (!clazz.kind.isClass) + reportError(s"new $cls which is not a class") + checkApplyGeneric(ctor.name, s"$cls.$ctor", args, + inTraitImpl = false) + + case LoadModule(cls) => + if (!cls.className.endsWith("$")) + reportError("LoadModule of non-module class $cls") + + case Select(qualifier, Ident(item, _), mutable) => + val qualType = typecheckExpr(qualifier, env) + qualType match { + case ClassType(cls) => + val clazz = lookupClass(cls) + if (!clazz.kind.isClass) { + reportError(s"Cannot select $item of non-class $cls") + } else { + clazz.lookupField(item).fold[Unit] { + reportError(s"Class $cls does not have a field $item") + } { fieldDef => + if (fieldDef.tpe != tree.tpe) + reportError(s"Select $cls.$item of type "+ + s"${fieldDef.tpe} typed as ${tree.tpe}") + if (fieldDef.mutable != mutable) + reportError(s"Select $cls.$item with "+ + s"mutable=${fieldDef.mutable} marked as mutable=$mutable") + } + } + case NullType | NothingType => + // always ok + case _ => + reportError(s"Cannot select $item of non-class type $qualType") + } + + case Apply(receiver, Ident(method, _), args) => + val receiverType = typecheckExpr(receiver, env) + checkApplyGeneric(method, s"$receiverType.$method", args, + inTraitImpl = false) + + case StaticApply(receiver, cls, Ident(method, _), args) => + typecheckExpect(receiver, env, cls) + checkApplyGeneric(method, s"$cls.$method", args, inTraitImpl = false) + + case TraitImplApply(impl, Ident(method, _), args) => + val clazz = lookupClass(impl) + if (clazz.kind != ClassKind.TraitImpl) + reportError(s"Cannot trait-impl apply method of non-trait-impl $impl") + checkApplyGeneric(method, s"$impl.$method", args, inTraitImpl = true) + + case UnaryOp(op, lhs) => + import UnaryOp._ + (op: @switch) match { + case `typeof` => + typecheckExpr(lhs, env) + case IntToLong => + typecheckExpect(lhs, env, IntType) + case LongToInt | LongToDouble => + typecheckExpect(lhs, env, LongType) + case DoubleToInt | DoubleToFloat | DoubleToLong => + typecheckExpect(lhs, env, DoubleType) + case Boolean_! => + typecheckExpect(lhs, env, BooleanType) + } + + case BinaryOp(op, lhs, rhs) => + import BinaryOp._ + (op: @switch) match { + case === | !== | String_+ => + typecheckExpr(lhs, env) + typecheckExpr(rhs, env) + case `in` => + typecheckExpect(lhs, env, ClassType(StringClass)) + typecheckExpr(rhs, env) + case `instanceof` => + typecheckExpr(lhs, env) + typecheckExpr(rhs, env) + case Int_+ | Int_- | Int_* | Int_/ | Int_% | + Int_| | Int_& | Int_^ | Int_<< | Int_>>> | Int_>> => + typecheckExpect(lhs, env, IntType) + typecheckExpect(rhs, env, IntType) + case Float_+ | Float_- | Float_* | Float_/ | Float_% => + typecheckExpect(lhs, env, FloatType) + typecheckExpect(lhs, env, FloatType) + case Long_+ | Long_- | Long_* | Long_/ | Long_% | + Long_| | Long_& | Long_^ | + Long_== | Long_!= | Long_< | Long_<= | Long_> | Long_>= => + typecheckExpect(lhs, env, LongType) + typecheckExpect(rhs, env, LongType) + case Long_<< | Long_>>> | Long_>> => + typecheckExpect(lhs, env, LongType) + typecheckExpect(rhs, env, IntType) + case Double_+ | Double_- | Double_* | Double_/ | Double_% | + Num_== | Num_!= | Num_< | Num_<= | Num_> | Num_>= => + typecheckExpect(lhs, env, DoubleType) + typecheckExpect(lhs, env, DoubleType) + case Boolean_== | Boolean_!= | Boolean_| | Boolean_& => + typecheckExpect(lhs, env, BooleanType) + typecheckExpect(rhs, env, BooleanType) + } + + case NewArray(tpe, lengths) => + for (length <- lengths) + typecheckExpect(length, env, IntType) + + case ArrayValue(tpe, elems) => + val elemType = arrayElemType(tpe) + for (elem <- elems) + typecheckExpect(elem, env, elemType) + + case ArrayLength(array) => + val arrayType = typecheckExpr(array, env) + if (!arrayType.isInstanceOf[ArrayType]) + reportError(s"Array type expected but $arrayType found") + + case ArraySelect(array, index) => + typecheckExpect(index, env, IntType) + typecheckExpr(array, env) match { + case arrayType: ArrayType => + if (tree.tpe != arrayElemType(arrayType)) + reportError(s"Array select of array type $arrayType typed as ${tree.tpe}") + case arrayType => + reportError(s"Array type expected but $arrayType found") + } + + case IsInstanceOf(expr, cls) => + typecheckExpr(expr, env) + + case AsInstanceOf(expr, cls) => + typecheckExpr(expr, env) + + case Unbox(expr, _) => + typecheckExpr(expr, env) + + case GetClass(expr) => + typecheckExpr(expr, env) + + // JavaScript expressions + + case JSNew(ctor, args) => + typecheckExpr(ctor, env) + for (arg <- args) + typecheckExpr(arg, env) + + case JSDotSelect(qualifier, item) => + typecheckExpr(qualifier, env) + + case JSBracketSelect(qualifier, item) => + typecheckExpr(qualifier, env) + typecheckExpr(item, env) + + case JSFunctionApply(fun, args) => + typecheckExpr(fun, env) + for (arg <- args) + typecheckExpr(arg, env) + + case JSDotMethodApply(receiver, method, args) => + typecheckExpr(receiver, env) + for (arg <- args) + typecheckExpr(arg, env) + + case JSBracketMethodApply(receiver, method, args) => + typecheckExpr(receiver, env) + typecheckExpr(method, env) + for (arg <- args) + typecheckExpr(arg, env) + + case JSUnaryOp(op, lhs) => + typecheckExpr(lhs, env) + + case JSBinaryOp(op, lhs, rhs) => + typecheckExpr(lhs, env) + typecheckExpr(rhs, env) + + case JSArrayConstr(items) => + for (item <- items) + typecheckExpr(item, env) + + case JSObjectConstr(fields) => + for ((_, value) <- fields) + typecheckExpr(value, env) + + case JSEnvInfo() => + + // Literals + + case _: Literal => + + // Atomic expressions + + case VarRef(Ident(name, _), mutable) => + env.locals.get(name).fold[Unit] { + reportError(s"Cannot find variable $name in scope") + } { localDef => + if (tree.tpe != localDef.tpe) + reportError(s"Variable $name of type ${localDef.tpe} "+ + s"typed as ${tree.tpe}") + if (mutable != localDef.mutable) + reportError(s"Variable $name with mutable=${localDef.mutable} "+ + s"marked as mutable=$mutable") + } + + case This() => + if (!isSubtype(env.thisTpe, tree.tpe)) + reportError(s"this of type ${env.thisTpe} typed as ${tree.tpe}") + + case Closure(captureParams, params, body, captureValues) => + if (captureParams.size != captureValues.size) + reportError("Mismatched size for captures: "+ + s"${captureParams.size} params vs ${captureValues.size} values") + + for ((ParamDef(name, ctpe, mutable), value) <- captureParams zip captureValues) { + if (mutable) + reportError(s"Capture parameter $name cannot be mutable") + if (ctpe == NoType) + reportError(s"Parameter $name has type NoType") + else + typecheckExpect(value, env, ctpe) + } + + for (ParamDef(name, ptpe, mutable) <- params) { + if (ptpe == NoType) + reportError(s"Parameter $name has type NoType") + else if (ptpe != AnyType) + reportError(s"Closure parameter $name has type $ptpe instead of any") + } + + val bodyEnv = Env.fromSignature( + AnyType, captureParams ++ params, AnyType) + typecheckExpect(body, bodyEnv, AnyType) + + case _ => + reportError(s"Invalid expression tree") + } + + tree.tpe + } + + def inferMethodType(encodedName: String, inTraitImpl: Boolean)( + implicit ctx: ErrorContext): (List[Type], Type) = { + def dropPrivateMarker(params: List[String]): List[String] = + if (params.nonEmpty && params.head.startsWith("p")) params.tail + else params + + if (isConstructorName(encodedName)) { + assert(!inTraitImpl, "Trait impl should not have a constructor") + val params = dropPrivateMarker( + encodedName.stripPrefix("init___").split("__").toList) + if (params == List("")) (Nil, NoType) + else (params.map(decodeType), NoType) + } else if (isReflProxyName(encodedName)) { + assert(!inTraitImpl, "Trait impl should not have refl proxy methods") + val params = dropPrivateMarker(encodedName.split("__").toList.tail) + (params.map(decodeType), AnyType) + } else { + val paramsAndResult0 = + encodedName.split("__").toList.tail + val paramsAndResult1 = + if (inTraitImpl) paramsAndResult0.tail + else paramsAndResult0 + val paramsAndResult = + dropPrivateMarker(paramsAndResult1) + (paramsAndResult.init.map(decodeType), decodeType(paramsAndResult.last)) + } + } + + def decodeType(encodedName: String)(implicit ctx: ErrorContext): Type = { + if (encodedName.isEmpty) NoType + else if (encodedName.charAt(0) == 'A') { + // array type + val dims = encodedName.indexWhere(_ != 'A') + val base = encodedName.substring(dims) + ArrayType(base, dims) + } else if (encodedName.length == 1) { + (encodedName.charAt(0): @switch) match { + case 'V' => NoType + case 'Z' => BooleanType + case 'C' | 'B' | 'S' | 'I' => IntType + case 'J' => LongType + case 'F' => FloatType + case 'D' => DoubleType + case 'O' => AnyType + case 'T' => ClassType(StringClass) // NOT StringType + } + } else if (encodedName == "sr_Nothing$") { + NothingType + } else if (encodedName == "sr_Null$") { + NullType + } else { + val clazz = lookupClass(encodedName) + if (clazz.kind == ClassKind.RawJSType) AnyType + else ClassType(encodedName) + } + } + + def arrayElemType(arrayType: ArrayType)(implicit ctx: ErrorContext): Type = { + if (arrayType.dimensions == 1) decodeType(arrayType.baseClassName) + else ArrayType(arrayType.baseClassName, arrayType.dimensions-1) + } + + def reportError(msg: String)(implicit ctx: ErrorContext): Unit = { + logger.error(s"$ctx: $msg") + _errorCount += 1 + } + + def lookupClass(className: String)(implicit ctx: ErrorContext): CheckedClass = { + classes.getOrElseUpdate(className, { + reportError(s"Cannot find class $className") + new CheckedClass(className, ClassKind.Class, + Some(ObjectClass), Set(ObjectClass)) + }) + } + + def lookupClass(classType: ClassType)(implicit ctx: ErrorContext): CheckedClass = + lookupClass(classType.className) + + def isSubclass(lhs: String, rhs: String)(implicit ctx: ErrorContext): Boolean = { + lookupClass(lhs).isSubclass(lookupClass(rhs)) + } + + def isSubtype(lhs: Type, rhs: Type)(implicit ctx: ErrorContext): Boolean = { + Types.isSubtype(lhs, rhs)(isSubclass) + } + + class Env( + /** Type of `this`. Can be NoType. */ + val thisTpe: Type, + /** Local variables in scope (including through closures). */ + val locals: Map[String, LocalDef], + /** Return types by label. */ + val returnTypes: Map[Option[String], Type] + ) { + def withThis(thisTpe: Type): Env = + new Env(thisTpe, this.locals, this.returnTypes) + + def withLocal(localDef: LocalDef): Env = + new Env(thisTpe, locals + (localDef.name -> localDef), returnTypes) + + def withLocals(localDefs: TraversableOnce[LocalDef]): Env = + new Env(thisTpe, locals ++ localDefs.map(d => d.name -> d), returnTypes) + + def withReturnType(returnType: Type): Env = + new Env(this.thisTpe, this.locals, returnTypes + (None -> returnType)) + + def withLabeledReturnType(label: String, returnType: Type): Env = + new Env(this.thisTpe, this.locals, returnTypes + (Some(label) -> returnType)) + + def withArgumentsVar(pos: Position): Env = + withLocal(LocalDef("arguments", AnyType, mutable = false)(pos)) + } + + object Env { + val empty: Env = new Env(NoType, Map.empty, Map.empty) + + def fromSignature(thisType: Type, params: List[ParamDef], + resultType: Type): Env = { + val paramLocalDefs = + for (p @ ParamDef(name, tpe, mutable) <- params) yield + name.name -> LocalDef(name.name, tpe, mutable)(p.pos) + new Env(thisType, paramLocalDefs.toMap, + Map(None -> (if (resultType == NoType) AnyType else resultType))) + } + } + + class CheckedClass( + val name: String, + val kind: ClassKind, + val superClassName: Option[String], + val ancestors: Set[String], + _fields: TraversableOnce[CheckedField] = Nil) { + + val fields = _fields.map(f => f.name -> f).toMap + + lazy val superClass = superClassName.map(classes) + + def this(classDef: ClassDef) = { + this(classDef.name.name, classDef.kind, + classDef.parent.map(_.name), + classDef.ancestors.map(_.name).toSet, + CheckedClass.collectFields(classDef)) + } + + def isSubclass(that: CheckedClass): Boolean = + this == that || ancestors.contains(that.name) + + def isAncestorOfHijackedClass: Boolean = + AncestorsOfHijackedClasses.contains(name) + + def lookupField(name: String): Option[CheckedField] = + fields.get(name).orElse(superClass.flatMap(_.lookupField(name))) + } + + object CheckedClass { + private def collectFields(classDef: ClassDef) = { + classDef.defs collect { + case VarDef(Ident(name, _), tpe, mutable, _) => + new CheckedField(name, tpe, mutable) + } + } + } + + class CheckedField(val name: String, val tpe: Type, val mutable: Boolean) +} + +object IRChecker { + private final class ErrorContext(val tree: Tree) extends AnyVal { + override def toString(): String = { + val pos = tree.pos + s"${pos.source}(${pos.line+1}:${pos.column+1}:${tree.getClass.getSimpleName})" + } + + def pos: Position = tree.pos + } + + private object ErrorContext { + implicit def tree2errorContext(tree: Tree): ErrorContext = + ErrorContext(tree) + + def apply(tree: Tree): ErrorContext = + new ErrorContext(tree) + } + + private def isConstructorName(name: String): Boolean = + name.startsWith("init___") + + private def isReflProxyName(name: String): Boolean = + name.endsWith("__") && !isConstructorName(name) + + case class LocalDef(name: String, tpe: Type, mutable: Boolean)(val pos: Position) +} diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/optimizer/IncOptimizer.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/optimizer/IncOptimizer.scala new file mode 100644 index 0000000..d115618 --- /dev/null +++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/optimizer/IncOptimizer.scala @@ -0,0 +1,158 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js tools ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.tools.optimizer + +import scala.collection.{GenTraversableOnce, GenIterable} +import scala.collection.mutable + +import scala.scalajs.tools.sem.Semantics + +class IncOptimizer(semantics: Semantics) extends GenIncOptimizer(semantics) { + + protected object CollOps extends GenIncOptimizer.AbsCollOps { + type Map[K, V] = mutable.Map[K, V] + type ParMap[K, V] = mutable.Map[K, V] + type AccMap[K, V] = mutable.Map[K, mutable.ListBuffer[V]] + type ParIterable[V] = mutable.ListBuffer[V] + type Addable[V] = mutable.ListBuffer[V] + + def emptyAccMap[K, V]: AccMap[K, V] = mutable.Map.empty + def emptyMap[K, V]: Map[K, V] = mutable.Map.empty + def emptyParMap[K, V]: ParMap[K, V] = mutable.Map.empty + def emptyParIterable[V]: ParIterable[V] = mutable.ListBuffer.empty + + // Operations on ParMap + def put[K, V](map: ParMap[K, V], k: K, v: V): Unit = map.put(k, v) + def remove[K, V](map: ParMap[K, V], k: K): Option[V] = map.remove(k) + + def retain[K, V](map: ParMap[K, V])(p: (K, V) => Boolean): Unit = + map.retain(p) + + // Operations on AccMap + def acc[K, V](map: AccMap[K, V], k: K, v: V): Unit = + map.getOrElseUpdate(k, mutable.ListBuffer.empty) += v + + def getAcc[K, V](map: AccMap[K, V], k: K): GenIterable[V] = + map.getOrElse(k, Nil) + + def parFlatMapKeys[A, B](map: AccMap[A, _])( + f: A => GenTraversableOnce[B]): GenIterable[B] = + map.keys.flatMap(f).toList + + // Operations on ParIterable + def prepAdd[V](it: ParIterable[V]): Addable[V] = it + def add[V](addable: Addable[V], v: V): Unit = addable += v + def finishAdd[V](addable: Addable[V]): ParIterable[V] = addable + } + + private val _interfaces = mutable.Map.empty[String, InterfaceType] + protected def getInterface(encodedName: String): InterfaceType = + _interfaces.getOrElseUpdate(encodedName, new SeqInterfaceType(encodedName)) + + private val methodsToProcess = mutable.ListBuffer.empty[MethodImpl] + protected def scheduleMethod(method: MethodImpl): Unit = + methodsToProcess += method + + protected def newMethodImpl(owner: MethodContainer, + encodedName: String): MethodImpl = new SeqMethodImpl(owner, encodedName) + + protected def processAllTaggedMethods(): Unit = { + logProcessingMethods(methodsToProcess.count(!_.deleted)) + for (method <- methodsToProcess) + method.process() + methodsToProcess.clear() + } + + private class SeqInterfaceType(encName: String) extends InterfaceType(encName) { + private val ancestorsAskers = mutable.Set.empty[MethodImpl] + private val dynamicCallers = mutable.Map.empty[String, mutable.Set[MethodImpl]] + private val staticCallers = mutable.Map.empty[String, mutable.Set[MethodImpl]] + + private var _ancestors: List[String] = encodedName :: Nil + + private var _instantiatedSubclasses: Set[Class] = Set.empty + + def instantiatedSubclasses: Iterable[Class] = _instantiatedSubclasses + + def addInstantiatedSubclass(x: Class): Unit = + _instantiatedSubclasses += x + + def removeInstantiatedSubclass(x: Class): Unit = + _instantiatedSubclasses -= x + + def ancestors: List[String] = _ancestors + + def ancestors_=(v: List[String]): Unit = { + if (v != _ancestors) { + _ancestors = v + ancestorsAskers.foreach(_.tag()) + ancestorsAskers.clear() + } + } + + def registerAskAncestors(asker: MethodImpl): Unit = + ancestorsAskers += asker + + def registerDynamicCaller(methodName: String, caller: MethodImpl): Unit = + dynamicCallers.getOrElseUpdate(methodName, mutable.Set.empty) += caller + + def registerStaticCaller(methodName: String, caller: MethodImpl): Unit = + staticCallers.getOrElseUpdate(methodName, mutable.Set.empty) += caller + + def unregisterDependee(dependee: MethodImpl): Unit = { + ancestorsAskers -= dependee + dynamicCallers.values.foreach(_ -= dependee) + staticCallers.values.foreach(_ -= dependee) + } + + def tagDynamicCallersOf(methodName: String): Unit = + dynamicCallers.remove(methodName).foreach(_.foreach(_.tag())) + + def tagStaticCallersOf(methodName: String): Unit = + staticCallers.remove(methodName).foreach(_.foreach(_.tag())) + } + + private class SeqMethodImpl(owner: MethodContainer, + encodedName: String) extends MethodImpl(owner, encodedName) { + + private val bodyAskers = mutable.Set.empty[MethodImpl] + + def registerBodyAsker(asker: MethodImpl): Unit = + bodyAskers += asker + + def unregisterDependee(dependee: MethodImpl): Unit = + bodyAskers -= dependee + + def tagBodyAskers(): Unit = { + bodyAskers.foreach(_.tag()) + bodyAskers.clear() + } + + private var _registeredTo: List[Unregisterable] = Nil + private var tagged = false + + protected def registeredTo(intf: Unregisterable): Unit = + _registeredTo ::= intf + + protected def unregisterFromEverywhere(): Unit = { + _registeredTo.foreach(_.unregisterDependee(this)) + _registeredTo = Nil + } + + protected def protectTag(): Boolean = { + val res = !tagged + tagged = true + res + } + protected def resetTag(): Unit = tagged = false + + } + +} diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/optimizer/JSTreeBuilder.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/optimizer/JSTreeBuilder.scala new file mode 100644 index 0000000..3d37a56 --- /dev/null +++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/optimizer/JSTreeBuilder.scala @@ -0,0 +1,16 @@ +package scala.scalajs.tools.optimizer + +import scala.scalajs.ir +import scala.scalajs.tools.javascript + +/** An abstract builder taking IR or JSTrees */ +trait JSTreeBuilder { + /** Add a JavaScript tree representing a statement. + * The tree must be a valid JavaScript tree (typically obtained by + * desugaring a full-fledged IR tree). + */ + def addJSTree(tree: javascript.Trees.Tree): Unit + + /** Completes the builder. */ + def complete(): Unit = () +} diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/optimizer/OptimizerCore.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/optimizer/OptimizerCore.scala new file mode 100644 index 0000000..364038b --- /dev/null +++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/optimizer/OptimizerCore.scala @@ -0,0 +1,3572 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js tools ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.tools.optimizer + +import scala.language.implicitConversions + +import scala.annotation.{switch, tailrec} + +import scala.collection.mutable + +import scala.util.control.{NonFatal, ControlThrowable, TailCalls} +import scala.util.control.TailCalls.{done => _, _} // done is a too generic term + +import scala.scalajs.ir._ +import Definitions.{ObjectClass, isConstructorName, isReflProxyName} +import Infos.OptimizerHints +import Trees._ +import Types._ + +import scala.scalajs.tools.sem.Semantics +import scala.scalajs.tools.javascript.LongImpl +import scala.scalajs.tools.logging._ + +/** Optimizer core. + * Designed to be "mixed in" [[IncOptimizer#MethodImpl#Optimizer]]. + * This is the core of the optimizer. It contains all the smart things the + * optimizer does. To perform inlining, it relies on abstract protected + * methods to identify the target of calls. + */ +private[optimizer] abstract class OptimizerCore(semantics: Semantics) { + import OptimizerCore._ + + type MethodID <: AbstractMethodID + + val myself: MethodID + + /** Returns the body of a method. */ + protected def getMethodBody(method: MethodID): MethodDef + + /** Returns the list of possible targets for a dynamically linked call. */ + protected def dynamicCall(intfName: String, + methodName: String): List[MethodID] + + /** Returns the target of a static call. */ + protected def staticCall(className: String, + methodName: String): Option[MethodID] + + /** Returns the target of a trait impl call. */ + protected def traitImplCall(traitImplName: String, + methodName: String): Option[MethodID] + + /** Returns the list of ancestors of a class or interface. */ + protected def getAncestorsOf(encodedName: String): List[String] + + /** Tests whether the given module class has an elidable accessor. + * In other words, whether it is safe to discard a LoadModule of that + * module class which is not used. + */ + protected def hasElidableModuleAccessor(moduleClassName: String): Boolean + + /** Tests whether the given class is inlineable. + * @return None if the class is not inlineable, Some(value) if it is, where + * value is a RecordValue with the initial value of its fields. + */ + protected def tryNewInlineableClass(className: String): Option[RecordValue] + + private val usedLocalNames = mutable.Set.empty[String] + private val usedLabelNames = mutable.Set.empty[String] + private var statesInUse: List[State[_]] = Nil + + private var disableOptimisticOptimizations: Boolean = false + private var rollbacksCount: Int = 0 + + private val attemptedInlining = mutable.ListBuffer.empty[MethodID] + + private var curTrampolineId = 0 + + def optimize(thisType: Type, originalDef: MethodDef): (MethodDef, Infos.MethodInfo) = { + try { + val MethodDef(name, params, resultType, body) = originalDef + val (newParams, newBody) = try { + transformIsolatedBody(Some(myself), thisType, params, resultType, body) + } catch { + case _: TooManyRollbacksException => + usedLocalNames.clear() + usedLabelNames.clear() + statesInUse = Nil + disableOptimisticOptimizations = true + transformIsolatedBody(Some(myself), thisType, params, resultType, body) + } + val m = MethodDef(name, newParams, resultType, newBody)(None)(originalDef.pos) + val info = recreateInfo(m) + (m, info) + } catch { + case NonFatal(cause) => + throw new OptimizeException(myself, attemptedInlining.distinct.toList, cause) + case e: Throwable => + // This is a fatal exception. Don't wrap, just output debug info error + Console.err.println(exceptionMsg(myself, attemptedInlining.distinct.toList)) + throw e + } + } + + private def withState[A, B](state: State[A])(body: => B): B = { + statesInUse ::= state + try body + finally statesInUse = statesInUse.tail + } + + private def freshLocalName(base: String): String = + freshNameGeneric(usedLocalNames, base) + + private def freshLabelName(base: String): String = + freshNameGeneric(usedLabelNames, base) + + private val isReserved = isKeyword ++ Seq("arguments", "eval", "ScalaJS") + + private def freshNameGeneric(usedNames: mutable.Set[String], base: String): String = { + val result = if (!usedNames.contains(base) && !isReserved(base)) { + base + } else { + var i = 1 + while (usedNames.contains(base + "$" + i)) + i += 1 + base + "$" + i + } + usedNames += result + result + } + + private def tryOrRollback(body: CancelFun => TailRec[Tree])( + fallbackFun: () => TailRec[Tree]): TailRec[Tree] = { + if (disableOptimisticOptimizations) { + fallbackFun() + } else { + val trampolineId = curTrampolineId + val savedUsedLocalNames = usedLocalNames.toSet + val savedUsedLabelNames = usedLabelNames.toSet + val savedStates = statesInUse.map(_.makeBackup()) + + body { () => + throw new RollbackException(trampolineId, savedUsedLocalNames, + savedUsedLabelNames, savedStates, fallbackFun) + } + } + } + + private def isSubclass(lhs: String, rhs: String): Boolean = + getAncestorsOf(lhs).contains(rhs) + + private val isSubclassFun = isSubclass _ + private def isSubtype(lhs: Type, rhs: Type): Boolean = + Types.isSubtype(lhs, rhs)(isSubclassFun) + + /** Transforms a statement. + * + * For valid expression trees, it is always the case that + * {{{ + * transformStat(tree) + * === + * pretransformExpr(tree)(finishTransformStat) + * }}} + */ + private def transformStat(tree: Tree)(implicit scope: Scope): Tree = + transform(tree, isStat = true) + + /** Transforms an expression. + * + * It is always the case that + * {{{ + * transformExpr(tree) + * === + * pretransformExpr(tree)(finishTransformExpr) + * }}} + */ + private def transformExpr(tree: Tree)(implicit scope: Scope): Tree = + transform(tree, isStat = false) + + /** Transforms a tree. */ + private def transform(tree: Tree, isStat: Boolean)( + implicit scope: Scope): Tree = { + + @inline implicit def pos = tree.pos + val result = tree match { + // Definitions + + case VarDef(_, _, _, rhs) => + /* A local var that is last (or alone) in its block is not terribly + * useful. Get rid of it. + * (Non-last VarDefs in blocks are handled in transformBlock.) + */ + transformStat(rhs) + + // Control flow constructs + + case tree: Block => + transformBlock(tree, isStat) + + case Labeled(ident @ Ident(label, _), tpe, body) => + trampoline { + returnable(label, if (isStat) NoType else tpe, body, isStat, + usePreTransform = false)(finishTransform(isStat)) + } + + case Assign(lhs, rhs) => + val cont = { (preTransLhs: PreTransform) => + resolveLocalDef(preTransLhs) match { + case PreTransRecordTree(lhsTree, lhsOrigType, lhsCancelFun) => + val recordType = lhsTree.tpe.asInstanceOf[RecordType] + pretransformNoLocalDef(rhs) { + case PreTransRecordTree(rhsTree, rhsOrigType, rhsCancelFun) => + if (rhsTree.tpe != recordType || rhsOrigType != lhsOrigType) + lhsCancelFun() + TailCalls.done(Assign(lhsTree, rhsTree)) + case _ => + lhsCancelFun() + } + case PreTransTree(lhsTree, _) => + TailCalls.done(Assign(lhsTree, transformExpr(rhs))) + } + } + trampoline { + lhs match { + case lhs: Select => + pretransformSelectCommon(lhs, isLhsOfAssign = true)(cont) + case _ => + pretransformExpr(lhs)(cont) + } + } + + case Return(expr, optLabel) => + val optInfo = optLabel match { + case Some(Ident(label, _)) => + Some(scope.env.labelInfos(label)) + case None => + scope.env.labelInfos.get("") + } + optInfo.fold[Tree] { + Return(transformExpr(expr), None) + } { info => + val newOptLabel = Some(Ident(info.newName, None)) + if (!info.acceptRecords) { + val newExpr = transformExpr(expr) + info.returnedTypes.value ::= (newExpr.tpe, RefinedType(newExpr.tpe)) + Return(newExpr, newOptLabel) + } else trampoline { + pretransformNoLocalDef(expr) { texpr => + texpr match { + case PreTransRecordTree(newExpr, origType, cancelFun) => + info.returnedTypes.value ::= (newExpr.tpe, origType) + TailCalls.done(Return(newExpr, newOptLabel)) + case PreTransTree(newExpr, tpe) => + info.returnedTypes.value ::= (newExpr.tpe, tpe) + TailCalls.done(Return(newExpr, newOptLabel)) + } + } + } + } + + case If(cond, thenp, elsep) => + val newCond = transformExpr(cond) + newCond match { + case BooleanLiteral(condValue) => + if (condValue) transform(thenp, isStat) + else transform(elsep, isStat) + case _ => + val newThenp = transform(thenp, isStat) + val newElsep = transform(elsep, isStat) + val refinedType = + constrainedLub(newThenp.tpe, newElsep.tpe, tree.tpe) + foldIf(newCond, newThenp, newElsep)(refinedType) + } + + case While(cond, body, optLabel) => + val newCond = transformExpr(cond) + newCond match { + case BooleanLiteral(false) => Skip() + case _ => + optLabel match { + case None => + While(newCond, transformStat(body), None) + + case Some(labelIdent @ Ident(label, _)) => + val newLabel = freshLabelName(label) + val info = new LabelInfo(newLabel, acceptRecords = false) + While(newCond, { + val bodyScope = scope.withEnv( + scope.env.withLabelInfo(label, info)) + transformStat(body)(bodyScope) + }, Some(Ident(newLabel, None)(labelIdent.pos))) + } + } + + case DoWhile(body, cond, None) => + val newBody = transformStat(body) + val newCond = transformExpr(cond) + newCond match { + case BooleanLiteral(false) => newBody + case _ => DoWhile(newBody, newCond, None) + } + + case Try(block, errVar, EmptyTree, finalizer) => + val newBlock = transform(block, isStat) + val newFinalizer = transformStat(finalizer) + Try(newBlock, errVar, EmptyTree, newFinalizer)(newBlock.tpe) + + case Try(block, errVar @ Ident(name, originalName), handler, finalizer) => + val newBlock = transform(block, isStat) + + val newName = freshLocalName(name) + val newOriginalName = originalName.orElse(Some(name)) + val localDef = LocalDef(RefinedType(AnyType), true, + ReplaceWithVarRef(newName, newOriginalName, new SimpleState(true), None)) + val newHandler = { + val handlerScope = scope.withEnv(scope.env.withLocalDef(name, localDef)) + transform(handler, isStat)(handlerScope) + } + + val newFinalizer = transformStat(finalizer) + + val refinedType = constrainedLub(newBlock.tpe, newHandler.tpe, tree.tpe) + Try(newBlock, Ident(newName, newOriginalName)(errVar.pos), + newHandler, newFinalizer)(refinedType) + + case Throw(expr) => + Throw(transformExpr(expr)) + + case Continue(optLabel) => + val newOptLabel = optLabel map { label => + Ident(scope.env.labelInfos(label.name).newName, None)(label.pos) + } + Continue(newOptLabel) + + case Match(selector, cases, default) => + val newSelector = transformExpr(selector) + newSelector match { + case newSelector: Literal => + val body = cases collectFirst { + case (alts, body) if alts.exists(literal_===(_, newSelector)) => body + } getOrElse default + transform(body, isStat) + case _ => + Match(newSelector, + cases map (c => (c._1, transform(c._2, isStat))), + transform(default, isStat))(tree.tpe) + } + + // Scala expressions + + case New(cls, ctor, args) => + New(cls, ctor, args map transformExpr) + + case StoreModule(cls, value) => + StoreModule(cls, transformExpr(value)) + + case tree: Select => + trampoline { + pretransformSelectCommon(tree, isLhsOfAssign = false)( + finishTransform(isStat = false)) + } + + case tree: Apply => + trampoline { + pretransformApply(tree, isStat, usePreTransform = false)( + finishTransform(isStat)) + } + + case tree: StaticApply => + trampoline { + pretransformStaticApply(tree, isStat, usePreTransform = false)( + finishTransform(isStat)) + } + + case tree: TraitImplApply => + trampoline { + pretransformTraitImplApply(tree, isStat, usePreTransform = false)( + finishTransform(isStat)) + } + + case tree @ UnaryOp(_, arg) => + if (isStat) transformStat(arg) + else transformUnaryOp(tree) + + case tree @ BinaryOp(op, lhs, rhs) => + if (isStat) Block(transformStat(lhs), transformStat(rhs)) + else transformBinaryOp(tree) + + case NewArray(tpe, lengths) => + NewArray(tpe, lengths map transformExpr) + + case ArrayValue(tpe, elems) => + ArrayValue(tpe, elems map transformExpr) + + case ArrayLength(array) => + ArrayLength(transformExpr(array)) + + case ArraySelect(array, index) => + ArraySelect(transformExpr(array), transformExpr(index))(tree.tpe) + + case RecordValue(tpe, elems) => + RecordValue(tpe, elems map transformExpr) + + case IsInstanceOf(expr, ClassType(ObjectClass)) => + transformExpr(BinaryOp(BinaryOp.!==, expr, Null())) + + case IsInstanceOf(expr, tpe) => + trampoline { + pretransformExpr(expr) { texpr => + val result = { + if (isSubtype(texpr.tpe.base, tpe)) { + if (texpr.tpe.isNullable) + BinaryOp(BinaryOp.!==, finishTransformExpr(texpr), Null()) + else + Block(finishTransformStat(texpr), BooleanLiteral(true)) + } else { + if (texpr.tpe.isExact) + Block(finishTransformStat(texpr), BooleanLiteral(false)) + else + IsInstanceOf(finishTransformExpr(texpr), tpe) + } + } + TailCalls.done(result) + } + } + + case AsInstanceOf(expr, ClassType(ObjectClass)) => + transformExpr(expr) + + case AsInstanceOf(expr, cls) => + trampoline { + pretransformExpr(tree)(finishTransform(isStat)) + } + + case Unbox(arg, charCode) => + trampoline { + pretransformExpr(arg) { targ => + foldUnbox(targ, charCode)(finishTransform(isStat)) + } + } + + case GetClass(expr) => + GetClass(transformExpr(expr)) + + // JavaScript expressions + + case JSNew(ctor, args) => + JSNew(transformExpr(ctor), args map transformExpr) + + case JSDotSelect(qualifier, item) => + JSDotSelect(transformExpr(qualifier), item) + + case JSBracketSelect(qualifier, item) => + JSBracketSelect(transformExpr(qualifier), transformExpr(item)) + + case tree: JSFunctionApply => + trampoline { + pretransformJSFunctionApply(tree, isStat, usePreTransform = false)( + finishTransform(isStat)) + } + + case JSDotMethodApply(receiver, method, args) => + JSDotMethodApply(transformExpr(receiver), method, + args map transformExpr) + + case JSBracketMethodApply(receiver, method, args) => + JSBracketMethodApply(transformExpr(receiver), transformExpr(method), + args map transformExpr) + + case JSDelete(JSDotSelect(obj, prop)) => + JSDelete(JSDotSelect(transformExpr(obj), prop)) + + case JSDelete(JSBracketSelect(obj, prop)) => + JSDelete(JSBracketSelect(transformExpr(obj), transformExpr(prop))) + + case JSUnaryOp(op, lhs) => + JSUnaryOp(op, transformExpr(lhs)) + + case JSBinaryOp(op, lhs, rhs) => + JSBinaryOp(op, transformExpr(lhs), transformExpr(rhs)) + + case JSArrayConstr(items) => + JSArrayConstr(items map transformExpr) + + case JSObjectConstr(fields) => + JSObjectConstr(fields map { + case (name, value) => (name, transformExpr(value)) + }) + + // Atomic expressions + + case _:VarRef | _:This => + trampoline { + pretransformExpr(tree)(finishTransform(isStat)) + } + + case Closure(captureParams, params, body, captureValues) => + transformClosureCommon(captureParams, params, body, + captureValues.map(transformExpr)) + + // Trees that need not be transformed + + case _:Skip | _:Debugger | _:LoadModule | + _:JSEnvInfo | _:Literal | EmptyTree => + tree + } + + if (isStat) keepOnlySideEffects(result) + else result + } + + private def transformClosureCommon(captureParams: List[ParamDef], + params: List[ParamDef], body: Tree, newCaptureValues: List[Tree])( + implicit pos: Position): Closure = { + + val (allNewParams, newBody) = + transformIsolatedBody(None, AnyType, captureParams ++ params, AnyType, body) + val (newCaptureParams, newParams) = + allNewParams.splitAt(captureParams.size) + + Closure(newCaptureParams, newParams, newBody, newCaptureValues) + } + + private def transformBlock(tree: Block, isStat: Boolean)( + implicit scope: Scope): Tree = { + def transformList(stats: List[Tree])( + implicit scope: Scope): Tree = stats match { + case last :: Nil => + transform(last, isStat) + + case (VarDef(Ident(name, originalName), vtpe, mutable, rhs)) :: rest => + trampoline { + pretransformExpr(rhs) { trhs => + withBinding(Binding(name, originalName, vtpe, mutable, trhs)) { + (restScope, cont1) => + val newRest = transformList(rest)(restScope) + cont1(PreTransTree(newRest, RefinedType(newRest.tpe))) + } (finishTransform(isStat)) + } + } + + case stat :: rest => + val transformedStat = transformStat(stat) + if (transformedStat.tpe == NothingType) transformedStat + else Block(transformedStat, transformList(rest))(stat.pos) + + case Nil => // silence the exhaustivity warning in a sensible way + Skip()(tree.pos) + } + transformList(tree.stats)(scope) + } + + /** Pretransforms a list of trees as a list of [[PreTransform]]s. + * This is a convenience method to use pretransformExpr on a list. + */ + private def pretransformExprs(trees: List[Tree])( + cont: List[PreTransform] => TailRec[Tree])( + implicit scope: Scope): TailRec[Tree] = { + trees match { + case first :: rest => + pretransformExpr(first) { tfirst => + pretransformExprs(rest) { trest => + cont(tfirst :: trest) + } + } + + case Nil => + cont(Nil) + } + } + + /** Pretransforms two trees as a pair of [[PreTransform]]s. + * This is a convenience method to use pretransformExpr on two trees. + */ + private def pretransformExprs(tree1: Tree, tree2: Tree)( + cont: (PreTransform, PreTransform) => TailRec[Tree])( + implicit scope: Scope): TailRec[Tree] = { + pretransformExpr(tree1) { ttree1 => + pretransformExpr(tree2) { ttree2 => + cont(ttree1, ttree2) + } + } + } + + /** Pretransforms a tree and a list of trees as [[PreTransform]]s. + * This is a convenience method to use pretransformExpr. + */ + private def pretransformExprs(first: Tree, rest: List[Tree])( + cont: (PreTransform, List[PreTransform]) => TailRec[Tree])( + implicit scope: Scope): TailRec[Tree] = { + pretransformExpr(first) { tfirst => + pretransformExprs(rest) { trest => + cont(tfirst, trest) + } + } + } + + /** Pretransforms a tree to get a refined type while avoiding to force + * things we might be able to optimize by folding and aliasing. + */ + private def pretransformExpr(tree: Tree)(cont: PreTransCont)( + implicit scope: Scope): TailRec[Tree] = tailcall { + @inline implicit def pos = tree.pos + + tree match { + case tree: Block => + pretransformBlock(tree)(cont) + + case VarRef(Ident(name, _), _) => + val localDef = scope.env.localDefs.getOrElse(name, + sys.error(s"Cannot find local def '$name' at $pos\n" + + s"While optimizing $myself\n" + + s"Env is ${scope.env}\nInlining ${scope.implsBeingInlined}")) + cont(PreTransLocalDef(localDef)) + + case This() => + val localDef = scope.env.localDefs.getOrElse("this", + sys.error(s"Found invalid 'this' at $pos\n" + + s"While optimizing $myself\n" + + s"Env is ${scope.env}\nInlining ${scope.implsBeingInlined}")) + cont(PreTransLocalDef(localDef)) + + case If(cond, thenp, elsep) => + val newCond = transformExpr(cond) + newCond match { + case BooleanLiteral(condValue) => + if (condValue) pretransformExpr(thenp)(cont) + else pretransformExpr(elsep)(cont) + case _ => + tryOrRollback { cancelFun => + pretransformNoLocalDef(thenp) { tthenp => + pretransformNoLocalDef(elsep) { telsep => + (tthenp, telsep) match { + case (PreTransRecordTree(thenTree, thenOrigType, thenCancelFun), + PreTransRecordTree(elseTree, elseOrigType, elseCancelFun)) => + val commonType = + if (thenTree.tpe == elseTree.tpe && + thenOrigType == elseOrigType) thenTree.tpe + else cancelFun() + val refinedOrigType = + constrainedLub(thenOrigType, elseOrigType, tree.tpe) + cont(PreTransRecordTree( + If(newCond, thenTree, elseTree)(commonType), + refinedOrigType, + cancelFun)) + + case (PreTransRecordTree(thenTree, thenOrigType, thenCancelFun), _) + if telsep.tpe.isNothingType => + cont(PreTransRecordTree( + If(newCond, thenTree, finishTransformExpr(telsep))(thenTree.tpe), + thenOrigType, + thenCancelFun)) + + case (_, PreTransRecordTree(elseTree, elseOrigType, elseCancelFun)) + if tthenp.tpe.isNothingType => + cont(PreTransRecordTree( + If(newCond, finishTransformExpr(tthenp), elseTree)(elseTree.tpe), + elseOrigType, + elseCancelFun)) + + case _ => + val newThenp = finishTransformExpr(tthenp) + val newElsep = finishTransformExpr(telsep) + val refinedType = + constrainedLub(newThenp.tpe, newElsep.tpe, tree.tpe) + cont(PreTransTree( + foldIf(newCond, newThenp, newElsep)(refinedType))) + } + } + } + } { () => + val newThenp = transformExpr(thenp) + val newElsep = transformExpr(elsep) + val refinedType = + constrainedLub(newThenp.tpe, newElsep.tpe, tree.tpe) + cont(PreTransTree( + foldIf(newCond, newThenp, newElsep)(refinedType))) + } + } + + case Match(selector, cases, default) => + val newSelector = transformExpr(selector) + newSelector match { + case newSelector: Literal => + val body = cases collectFirst { + case (alts, body) if alts.exists(literal_===(_, newSelector)) => body + } getOrElse default + pretransformExpr(body)(cont) + case _ => + cont(PreTransTree(Match(newSelector, + cases map (c => (c._1, transformExpr(c._2))), + transformExpr(default))(tree.tpe))) + } + + case Labeled(ident @ Ident(label, _), tpe, body) => + returnable(label, tpe, body, isStat = false, usePreTransform = true)(cont) + + case New(cls @ ClassType(className), ctor, args) => + tryNewInlineableClass(className) match { + case Some(initialValue) => + pretransformExprs(args) { targs => + tryOrRollback { cancelFun => + inlineClassConstructor( + new AllocationSite(tree), + cls, initialValue, ctor, targs, cancelFun)(cont) + } { () => + cont(PreTransTree( + New(cls, ctor, targs.map(finishTransformExpr)), + RefinedType(cls, isExact = true, isNullable = false))) + } + } + case None => + cont(PreTransTree( + New(cls, ctor, args.map(transformExpr)), + RefinedType(cls, isExact = true, isNullable = false))) + } + + case tree: Select => + pretransformSelectCommon(tree, isLhsOfAssign = false)(cont) + + case tree: Apply => + pretransformApply(tree, isStat = false, + usePreTransform = true)(cont) + + case tree: StaticApply => + pretransformStaticApply(tree, isStat = false, + usePreTransform = true)(cont) + + case tree: TraitImplApply => + pretransformTraitImplApply(tree, isStat = false, + usePreTransform = true)(cont) + + case tree: JSFunctionApply => + pretransformJSFunctionApply(tree, isStat = false, + usePreTransform = true)(cont) + + case AsInstanceOf(expr, tpe) => + pretransformExpr(expr) { texpr => + tpe match { + case ClassType(ObjectClass) => + cont(texpr) + case _ => + if (isSubtype(texpr.tpe.base, tpe)) { + cont(texpr) + } else { + cont(PreTransTree( + AsInstanceOf(finishTransformExpr(texpr), tpe))) + } + } + } + + case Closure(captureParams, params, body, captureValues) => + pretransformExprs(captureValues) { tcaptureValues => + tryOrRollback { cancelFun => + val captureBindings = for { + (ParamDef(Ident(name, origName), tpe, mutable), value) <- + captureParams zip tcaptureValues + } yield { + Binding(name, origName, tpe, mutable, value) + } + withNewLocalDefs(captureBindings) { (captureLocalDefs, cont1) => + val alreadyUsedState = new SimpleState[Boolean](false) + withState(alreadyUsedState) { + val replacement = TentativeClosureReplacement( + captureParams, params, body, captureLocalDefs, + alreadyUsedState, cancelFun) + val localDef = LocalDef( + RefinedType(AnyType, isExact = false, isNullable = false), + mutable = false, + replacement) + cont1(PreTransLocalDef(localDef)) + } + } (cont) + } { () => + val newClosure = transformClosureCommon(captureParams, params, body, + tcaptureValues.map(finishTransformExpr)) + cont(PreTransTree( + newClosure, + RefinedType(AnyType, isExact = false, isNullable = false))) + } + } + + case _ => + val result = transformExpr(tree) + cont(PreTransTree(result, RefinedType(result.tpe))) + } + } + + private def pretransformBlock(tree: Block)( + cont: PreTransCont)( + implicit scope: Scope): TailRec[Tree] = { + def pretransformList(stats: List[Tree])( + cont: PreTransCont)( + implicit scope: Scope): TailRec[Tree] = stats match { + case last :: Nil => + pretransformExpr(last)(cont) + + case (VarDef(Ident(name, originalName), vtpe, mutable, rhs)) :: rest => + pretransformExpr(rhs) { trhs => + withBinding(Binding(name, originalName, vtpe, mutable, trhs)) { + (restScope, cont1) => + pretransformList(rest)(cont1)(restScope) + } (cont) + } + + case stat :: rest => + implicit val pos = tree.pos + val transformedStat = transformStat(stat) + transformedStat match { + case Skip() => + pretransformList(rest)(cont) + case _ => + if (transformedStat.tpe == NothingType) + cont(PreTransTree(transformedStat, RefinedType.Nothing)) + else { + pretransformList(rest) { trest => + cont(PreTransBlock(transformedStat :: Nil, trest)) + } + } + } + + case Nil => // silence the exhaustivity warning in a sensible way + TailCalls.done(Skip()(tree.pos)) + } + pretransformList(tree.stats)(cont)(scope) + } + + private def pretransformSelectCommon(tree: Select, isLhsOfAssign: Boolean)( + cont: PreTransCont)( + implicit scope: Scope): TailRec[Tree] = { + val Select(qualifier, item, mutable) = tree + pretransformExpr(qualifier) { preTransQual => + pretransformSelectCommon(tree.tpe, preTransQual, item, mutable, + isLhsOfAssign)(cont)(scope, tree.pos) + } + } + + private def pretransformSelectCommon(expectedType: Type, + preTransQual: PreTransform, item: Ident, mutable: Boolean, + isLhsOfAssign: Boolean)( + cont: PreTransCont)( + implicit scope: Scope, pos: Position): TailRec[Tree] = { + preTransQual match { + case PreTransLocalDef(LocalDef(_, _, + InlineClassBeingConstructedReplacement(fieldLocalDefs, cancelFun))) => + val fieldLocalDef = fieldLocalDefs(item.name) + if (!isLhsOfAssign || fieldLocalDef.mutable) { + cont(PreTransLocalDef(fieldLocalDef)) + } else { + /* This is an assignment to an immutable field of a inlineable class + * being constructed, but that does not appear at the "top-level" of + * one of its constructors. We cannot handle those, so we cancel. + * (Assignments at the top-level are normal initializations of these + * fields, and are transformed as vals in inlineClassConstructor.) + */ + cancelFun() + } + case PreTransLocalDef(LocalDef(_, _, + InlineClassInstanceReplacement(_, fieldLocalDefs, cancelFun))) => + val fieldLocalDef = fieldLocalDefs(item.name) + if (!isLhsOfAssign || fieldLocalDef.mutable) { + cont(PreTransLocalDef(fieldLocalDef)) + } else { + /* In an ideal world, this should not happen (assigning to an + * immutable field of an already constructed object). However, since + * we cannot IR-check that this does not happen (see #1021), this is + * effectively allowed by the IR spec. We are therefore not allowed + * to crash. We cancel instead. This will become an actual field + * (rather than an optimized local val) which is not considered pure + * (for that same reason). + */ + cancelFun() + } + case _ => + resolveLocalDef(preTransQual) match { + case PreTransRecordTree(newQual, origType, cancelFun) => + val recordType = newQual.tpe.asInstanceOf[RecordType] + val field = recordType.findField(item.name) + val sel = Select(newQual, item, mutable)(field.tpe) + sel.tpe match { + case _: RecordType => + cont(PreTransRecordTree(sel, RefinedType(expectedType), cancelFun)) + case _ => + cont(PreTransTree(sel, RefinedType(sel.tpe))) + } + + case PreTransTree(newQual, _) => + cont(PreTransTree(Select(newQual, item, mutable)(expectedType), + RefinedType(expectedType))) + } + } + } + + /** Resolves any LocalDef in a [[PreTransform]]. */ + private def resolveLocalDef(preTrans: PreTransform): PreTransGenTree = { + implicit val pos = preTrans.pos + preTrans match { + case PreTransBlock(stats, result) => + resolveLocalDef(result) match { + case PreTransRecordTree(tree, tpe, cancelFun) => + PreTransRecordTree(Block(stats :+ tree), tpe, cancelFun) + case PreTransTree(tree, tpe) => + PreTransTree(Block(stats :+ tree), tpe) + } + + case PreTransLocalDef(localDef @ LocalDef(tpe, mutable, replacement)) => + replacement match { + case ReplaceWithRecordVarRef(name, originalName, + recordType, used, cancelFun) => + used.value = true + PreTransRecordTree( + VarRef(Ident(name, originalName), mutable)(recordType), + tpe, cancelFun) + + case InlineClassInstanceReplacement(recordType, fieldLocalDefs, cancelFun) => + if (!isImmutableType(recordType)) + cancelFun() + PreTransRecordTree( + RecordValue(recordType, recordType.fields.map( + f => fieldLocalDefs(f.name).newReplacement)), + tpe, cancelFun) + + case _ => + PreTransTree(localDef.newReplacement, localDef.tpe) + } + + case preTrans: PreTransGenTree => + preTrans + } + } + + /** Combines pretransformExpr and resolveLocalDef in one convenience method. */ + private def pretransformNoLocalDef(tree: Tree)( + cont: PreTransGenTree => TailRec[Tree])( + implicit scope: Scope): TailRec[Tree] = { + pretransformExpr(tree) { ttree => + cont(resolveLocalDef(ttree)) + } + } + + /** Finishes a pretransform, either a statement or an expression. */ + private def finishTransform(isStat: Boolean): PreTransCont = { preTrans => + TailCalls.done { + if (isStat) finishTransformStat(preTrans) + else finishTransformExpr(preTrans) + } + } + + /** Finishes an expression pretransform to get a normal [[Tree]]. + * This method (together with finishTransformStat) must not be called more + * than once per pretransform and per translation. + * By "per translation", we mean in an alternative path through + * `tryOrRollback`. It could still be called several times as long as + * it is once in the 'try' part and once in the 'fallback' part. + */ + private def finishTransformExpr(preTrans: PreTransform): Tree = { + implicit val pos = preTrans.pos + preTrans match { + case PreTransBlock(stats, result) => + Block(stats :+ finishTransformExpr(result)) + case PreTransLocalDef(localDef) => + localDef.newReplacement + case PreTransRecordTree(_, _, cancelFun) => + cancelFun() + case PreTransTree(tree, _) => + tree + } + } + + /** Finishes a statement pretransform to get a normal [[Tree]]. + * This method (together with finishTransformExpr) must not be called more + * than once per pretransform and per translation. + * By "per translation", we mean in an alternative path through + * `tryOrRollback`. It could still be called several times as long as + * it is once in the 'try' part and once in the 'fallback' part. + */ + private def finishTransformStat(stat: PreTransform): Tree = stat match { + case PreTransBlock(stats, result) => + Block(stats :+ finishTransformStat(result))(stat.pos) + case PreTransLocalDef(_) => + Skip()(stat.pos) + case PreTransRecordTree(tree, _, _) => + keepOnlySideEffects(tree) + case PreTransTree(tree, _) => + keepOnlySideEffects(tree) + } + + /** Keeps only the side effects of a Tree (overapproximation). */ + private def keepOnlySideEffects(stat: Tree): Tree = stat match { + case _:VarRef | _:This | _:Literal => + Skip()(stat.pos) + case Block(init :+ last) => + Block(init :+ keepOnlySideEffects(last))(stat.pos) + case LoadModule(ClassType(moduleClassName)) => + if (hasElidableModuleAccessor(moduleClassName)) Skip()(stat.pos) + else stat + case Select(LoadModule(ClassType(moduleClassName)), _, _) => + if (hasElidableModuleAccessor(moduleClassName)) Skip()(stat.pos) + else stat + case Closure(_, _, _, captureValues) => + Block(captureValues.map(keepOnlySideEffects))(stat.pos) + case UnaryOp(_, arg) => + keepOnlySideEffects(arg) + case If(cond, thenp, elsep) => + (keepOnlySideEffects(thenp), keepOnlySideEffects(elsep)) match { + case (Skip(), Skip()) => keepOnlySideEffects(cond) + case (newThenp, newElsep) => If(cond, newThenp, newElsep)(NoType)(stat.pos) + } + case BinaryOp(_, lhs, rhs) => + Block(keepOnlySideEffects(lhs), keepOnlySideEffects(rhs))(stat.pos) + case RecordValue(_, elems) => + Block(elems.map(keepOnlySideEffects))(stat.pos) + case _ => + stat + } + + private def pretransformApply(tree: Apply, isStat: Boolean, + usePreTransform: Boolean)( + cont: PreTransCont)( + implicit scope: Scope): TailRec[Tree] = { + val Apply(receiver, methodIdent @ Ident(methodName, _), args) = tree + implicit val pos = tree.pos + + pretransformExpr(receiver) { treceiver => + def treeNotInlined0(transformedArgs: List[Tree]) = + cont(PreTransTree(Apply(finishTransformExpr(treceiver), methodIdent, + transformedArgs)(tree.tpe)(tree.pos), RefinedType(tree.tpe))) + + def treeNotInlined = treeNotInlined0(args.map(transformExpr)) + + treceiver.tpe.base match { + case NothingType => + cont(treceiver) + case NullType => + cont(PreTransTree(Block( + finishTransformStat(treceiver), + CallHelper("throwNullPointerException")(NothingType)))) + case _ => + if (isReflProxyName(methodName)) { + // Never inline reflective proxies + treeNotInlined + } else { + val cls = boxedClassForType(treceiver.tpe.base) + val impls = + if (treceiver.tpe.isExact) staticCall(cls, methodName).toList + else dynamicCall(cls, methodName) + val allocationSite = treceiver.tpe.allocationSite + if (impls.isEmpty || impls.exists(impl => + scope.implsBeingInlined((allocationSite, impl)))) { + // isEmpty could happen, have to leave it as is for the TypeError + treeNotInlined + } else if (impls.size == 1) { + val target = impls.head + pretransformExprs(args) { targs => + val intrinsicCode = getIntrinsicCode(target) + if (intrinsicCode >= 0) { + callIntrinsic(intrinsicCode, Some(treceiver), targs, + isStat, usePreTransform)(cont) + } else if (target.inlineable || shouldInlineBecauseOfArgs(treceiver :: targs)) { + inline(allocationSite, Some(treceiver), targs, target, + isStat, usePreTransform)(cont) + } else { + treeNotInlined0(targs.map(finishTransformExpr)) + } + } + } else { + if (impls.forall(_.isTraitImplForwarder)) { + val reference = impls.head + val TraitImplApply(ClassType(traitImpl), Ident(methodName, _), _) = + getMethodBody(reference).body + if (!impls.tail.forall(getMethodBody(_).body match { + case TraitImplApply(ClassType(`traitImpl`), + Ident(`methodName`, _), _) => true + case _ => false + })) { + // Not all calling the same method in the same trait impl + treeNotInlined + } else { + pretransformExprs(args) { targs => + inline(allocationSite, Some(treceiver), targs, reference, + isStat, usePreTransform)(cont) + } + } + } else { + // TODO? Inline multiple non-trait-impl-forwarder with the exact same body? + treeNotInlined + } + } + } + } + } + } + + private def boxedClassForType(tpe: Type): String = (tpe: @unchecked) match { + case ClassType(cls) => cls + case AnyType => Definitions.ObjectClass + case UndefType => Definitions.BoxedUnitClass + case BooleanType => Definitions.BoxedBooleanClass + case IntType => Definitions.BoxedIntegerClass + case LongType => Definitions.BoxedLongClass + case FloatType => Definitions.BoxedFloatClass + case DoubleType => Definitions.BoxedDoubleClass + case StringType => Definitions.StringClass + case ArrayType(_, _) => Definitions.ObjectClass + } + + private def pretransformStaticApply(tree: StaticApply, isStat: Boolean, + usePreTransform: Boolean)( + cont: PreTransCont)( + implicit scope: Scope): TailRec[Tree] = { + val StaticApply(receiver, clsType @ ClassType(cls), + methodIdent @ Ident(methodName, _), args) = tree + implicit val pos = tree.pos + + def treeNotInlined0(transformedReceiver: Tree, transformedArgs: List[Tree]) = + cont(PreTransTree(StaticApply(transformedReceiver, clsType, + methodIdent, transformedArgs)(tree.tpe), RefinedType(tree.tpe))) + + def treeNotInlined = + treeNotInlined0(transformExpr(receiver), args.map(transformExpr)) + + if (isReflProxyName(methodName)) { + // Never inline reflective proxies + treeNotInlined + } else { + val optTarget = staticCall(cls, methodName) + if (optTarget.isEmpty) { + // just in case + treeNotInlined + } else { + val target = optTarget.get + pretransformExprs(receiver, args) { (treceiver, targs) => + val intrinsicCode = getIntrinsicCode(target) + if (intrinsicCode >= 0) { + callIntrinsic(intrinsicCode, Some(treceiver), targs, + isStat, usePreTransform)(cont) + } else { + val shouldInline = + target.inlineable || shouldInlineBecauseOfArgs(treceiver :: targs) + val allocationSite = treceiver.tpe.allocationSite + val beingInlined = + scope.implsBeingInlined((allocationSite, target)) + + if (shouldInline && !beingInlined) { + inline(allocationSite, Some(treceiver), targs, target, + isStat, usePreTransform)(cont) + } else { + treeNotInlined0(finishTransformExpr(treceiver), + targs.map(finishTransformExpr)) + } + } + } + } + } + } + + private def pretransformTraitImplApply(tree: TraitImplApply, isStat: Boolean, + usePreTransform: Boolean)( + cont: PreTransCont)( + implicit scope: Scope): TailRec[Tree] = { + val TraitImplApply(implType @ ClassType(impl), + methodIdent @ Ident(methodName, _), args) = tree + implicit val pos = tree.pos + + def treeNotInlined0(transformedArgs: List[Tree]) = + cont(PreTransTree(TraitImplApply(implType, methodIdent, + transformedArgs)(tree.tpe), RefinedType(tree.tpe))) + + def treeNotInlined = treeNotInlined0(args.map(transformExpr)) + + val optTarget = traitImplCall(impl, methodName) + if (optTarget.isEmpty) { + // just in case + treeNotInlined + } else { + val target = optTarget.get + pretransformExprs(args) { targs => + val intrinsicCode = getIntrinsicCode(target) + if (intrinsicCode >= 0) { + callIntrinsic(intrinsicCode, None, targs, + isStat, usePreTransform)(cont) + } else { + val shouldInline = + target.inlineable || shouldInlineBecauseOfArgs(targs) + val allocationSite = targs.headOption.flatMap(_.tpe.allocationSite) + val beingInlined = + scope.implsBeingInlined((allocationSite, target)) + + if (shouldInline && !beingInlined) { + inline(allocationSite, None, targs, target, + isStat, usePreTransform)(cont) + } else { + treeNotInlined0(targs.map(finishTransformExpr)) + } + } + } + } + } + + private def pretransformJSFunctionApply(tree: JSFunctionApply, + isStat: Boolean, usePreTransform: Boolean)( + cont: PreTransCont)( + implicit scope: Scope, pos: Position): TailRec[Tree] = { + val JSFunctionApply(fun, args) = tree + implicit val pos = tree.pos + + pretransformExpr(fun) { tfun => + tfun match { + case PreTransLocalDef(LocalDef(_, false, + closure @ TentativeClosureReplacement( + captureParams, params, body, captureLocalDefs, + alreadyUsed, cancelFun))) if !alreadyUsed.value => + alreadyUsed.value = true + pretransformExprs(args) { targs => + inlineBody( + Some(PreTransTree(Undefined())), // `this` is `undefined` + captureParams ++ params, AnyType, body, + captureLocalDefs.map(PreTransLocalDef(_)) ++ targs, isStat, + usePreTransform)(cont) + } + + case _ => + cont(PreTransTree( + JSFunctionApply(finishTransformExpr(tfun), args.map(transformExpr)))) + } + } + } + + private def shouldInlineBecauseOfArgs( + receiverAndArgs: List[PreTransform]): Boolean = { + def isLikelyOptimizable(arg: PreTransform): Boolean = arg match { + case PreTransBlock(_, result) => + isLikelyOptimizable(result) + + case PreTransLocalDef(localDef) => + localDef.replacement match { + case TentativeClosureReplacement(_, _, _, _, _, _) => true + case ReplaceWithRecordVarRef(_, _, _, _, _) => true + case InlineClassBeingConstructedReplacement(_, _) => true + case InlineClassInstanceReplacement(_, _, _) => true + case _ => false + } + + case PreTransRecordTree(_, _, _) => + true + + case _ => + arg.tpe.base match { + case ClassType("s_Predef$$less$colon$less" | "s_Predef$$eq$colon$eq") => + true + case _ => + false + } + } + receiverAndArgs.exists(isLikelyOptimizable) + } + + private def inline(allocationSite: Option[AllocationSite], + optReceiver: Option[PreTransform], + args: List[PreTransform], target: MethodID, isStat: Boolean, + usePreTransform: Boolean)( + cont: PreTransCont)( + implicit scope: Scope, pos: Position): TailRec[Tree] = { + + attemptedInlining += target + + val MethodDef(_, formals, resultType, body) = getMethodBody(target) + + body match { + case Skip() => + assert(isStat, "Found Skip() in expression position") + cont(PreTransTree( + Block((optReceiver ++: args).map(finishTransformStat)), + RefinedType.NoRefinedType)) + + case _: Literal => + cont(PreTransTree( + Block((optReceiver ++: args).map(finishTransformStat) :+ body), + RefinedType(body.tpe))) + + case This() if args.isEmpty => + assert(optReceiver.isDefined, + "There was a This(), there should be a receiver") + cont(optReceiver.get) + + case Select(This(), field, mutable) if formals.isEmpty => + assert(optReceiver.isDefined, + "There was a This(), there should be a receiver") + pretransformSelectCommon(body.tpe, optReceiver.get, field, mutable, + isLhsOfAssign = false)(cont) + + case Assign(lhs @ Select(This(), field, mutable), VarRef(Ident(rhsName, _), _)) + if formals.size == 1 && formals.head.name.name == rhsName => + assert(isStat, "Found Assign in expression position") + assert(optReceiver.isDefined, + "There was a This(), there should be a receiver") + pretransformSelectCommon(lhs.tpe, optReceiver.get, field, mutable, + isLhsOfAssign = true) { preTransLhs => + // TODO Support assignment of record + cont(PreTransTree( + Assign(finishTransformExpr(preTransLhs), + finishTransformExpr(args.head)), + RefinedType.NoRefinedType)) + } + + case _ => + val targetID = (allocationSite, target) + inlineBody(optReceiver, formals, resultType, body, args, isStat, + usePreTransform)(cont)(scope.inlining(targetID), pos) + } + } + + private def inlineBody(optReceiver: Option[PreTransform], + formals: List[ParamDef], resultType: Type, body: Tree, + args: List[PreTransform], isStat: Boolean, + usePreTransform: Boolean)( + cont: PreTransCont)( + implicit scope: Scope, pos: Position): TailRec[Tree] = tailcall { + + val optReceiverBinding = optReceiver map { receiver => + Binding("this", None, receiver.tpe.base, false, receiver) + } + + val argsBindings = for { + (ParamDef(Ident(name, originalName), tpe, mutable), arg) <- formals zip args + } yield { + Binding(name, originalName, tpe, mutable, arg) + } + + withBindings(optReceiverBinding ++: argsBindings) { (bodyScope, cont1) => + returnable("", resultType, body, isStat, usePreTransform)( + cont1)(bodyScope, pos) + } (cont) (scope.withEnv(OptEnv.Empty)) + } + + private def callIntrinsic(code: Int, optTReceiver: Option[PreTransform], + targs: List[PreTransform], isStat: Boolean, usePreTransform: Boolean)( + cont: PreTransCont)( + implicit pos: Position): TailRec[Tree] = { + + import Intrinsics._ + + implicit def string2ident(s: String): Ident = Ident(s, None) + + lazy val newArgs = targs.map(finishTransformExpr) + + @inline def contTree(result: Tree) = cont(PreTransTree(result)) + + @inline def StringClassType = ClassType(Definitions.StringClass) + + def asRTLong(arg: Tree): Tree = + AsInstanceOf(arg, ClassType(LongImpl.RuntimeLongClass)) + def firstArgAsRTLong: Tree = + asRTLong(newArgs.head) + + (code: @switch) match { + // java.lang.System + + case ArrayCopy => + assert(isStat, "System.arraycopy must be used in statement position") + contTree(CallHelper("systemArraycopy", newArgs)(NoType)) + case IdentityHashCode => + contTree(CallHelper("systemIdentityHashCode", newArgs)(IntType)) + + // scala.scalajs.runtime package object + + case PropertiesOf => + contTree(CallHelper("propertiesOf", newArgs)(AnyType)) + + // java.lang.Long + + case LongToString => + contTree(Apply(firstArgAsRTLong, "toString__T", Nil)(StringClassType)) + case LongCompare => + contTree(Apply(firstArgAsRTLong, "compareTo__sjsr_RuntimeLong__I", + List(asRTLong(newArgs(1))))(IntType)) + case LongBitCount => + contTree(Apply(firstArgAsRTLong, LongImpl.bitCount, Nil)(IntType)) + case LongSignum => + contTree(Apply(firstArgAsRTLong, LongImpl.signum, Nil)(LongType)) + case LongLeading0s => + contTree(Apply(firstArgAsRTLong, LongImpl.numberOfLeadingZeros, Nil)(IntType)) + case LongTrailing0s => + contTree(Apply(firstArgAsRTLong, LongImpl.numberOfTrailingZeros, Nil)(IntType)) + case LongToBinStr => + contTree(Apply(firstArgAsRTLong, LongImpl.toBinaryString, Nil)(StringClassType)) + case LongToHexStr => + contTree(Apply(firstArgAsRTLong, LongImpl.toHexString, Nil)(StringClassType)) + case LongToOctalStr => + contTree(Apply(firstArgAsRTLong, LongImpl.toOctalString, Nil)(StringClassType)) + + // TypedArray conversions + + case ByteArrayToInt8Array => + contTree(CallHelper("byteArray2TypedArray", newArgs)(AnyType)) + case ShortArrayToInt16Array => + contTree(CallHelper("shortArray2TypedArray", newArgs)(AnyType)) + case CharArrayToUint16Array => + contTree(CallHelper("charArray2TypedArray", newArgs)(AnyType)) + case IntArrayToInt32Array => + contTree(CallHelper("intArray2TypedArray", newArgs)(AnyType)) + case FloatArrayToFloat32Array => + contTree(CallHelper("floatArray2TypedArray", newArgs)(AnyType)) + case DoubleArrayToFloat64Array => + contTree(CallHelper("doubleArray2TypedArray", newArgs)(AnyType)) + + case Int8ArrayToByteArray => + contTree(CallHelper("typedArray2ByteArray", newArgs)(AnyType)) + case Int16ArrayToShortArray => + contTree(CallHelper("typedArray2ShortArray", newArgs)(AnyType)) + case Uint16ArrayToCharArray => + contTree(CallHelper("typedArray2CharArray", newArgs)(AnyType)) + case Int32ArrayToIntArray => + contTree(CallHelper("typedArray2IntArray", newArgs)(AnyType)) + case Float32ArrayToFloatArray => + contTree(CallHelper("typedArray2FloatArray", newArgs)(AnyType)) + case Float64ArrayToDoubleArray => + contTree(CallHelper("typedArray2DoubleArray", newArgs)(AnyType)) + } + } + + private def inlineClassConstructor(allocationSite: AllocationSite, + cls: ClassType, initialValue: RecordValue, + ctor: Ident, args: List[PreTransform], cancelFun: CancelFun)( + cont: PreTransCont)( + implicit scope: Scope, pos: Position): TailRec[Tree] = { + + val RecordValue(recordType, initialFieldValues) = initialValue + + pretransformExprs(initialFieldValues) { tinitialFieldValues => + val initialFieldBindings = for { + (RecordType.Field(name, originalName, tpe, mutable), value) <- + recordType.fields zip tinitialFieldValues + } yield { + Binding(name, originalName, tpe, mutable, value) + } + + withNewLocalDefs(initialFieldBindings) { (initialFieldLocalDefList, cont1) => + val fieldNames = initialValue.tpe.fields.map(_.name) + val initialFieldLocalDefs = + Map(fieldNames zip initialFieldLocalDefList: _*) + + inlineClassConstructorBody(allocationSite, initialFieldLocalDefs, + cls, cls, ctor, args, cancelFun) { (finalFieldLocalDefs, cont2) => + cont2(PreTransLocalDef(LocalDef( + RefinedType(cls, isExact = true, isNullable = false, + allocationSite = Some(allocationSite)), + mutable = false, + InlineClassInstanceReplacement(recordType, finalFieldLocalDefs, cancelFun)))) + } (cont1) + } (cont) + } + } + + private def inlineClassConstructorBody( + allocationSite: AllocationSite, + inputFieldsLocalDefs: Map[String, LocalDef], cls: ClassType, + ctorClass: ClassType, ctor: Ident, args: List[PreTransform], + cancelFun: CancelFun)( + buildInner: (Map[String, LocalDef], PreTransCont) => TailRec[Tree])( + cont: PreTransCont)( + implicit scope: Scope): TailRec[Tree] = tailcall { + + val target = staticCall(ctorClass.className, ctor.name).getOrElse(cancelFun()) + val targetID = (Some(allocationSite), target) + if (scope.implsBeingInlined.contains(targetID)) + cancelFun() + + val MethodDef(_, formals, _, BlockOrAlone(stats, This())) = + getMethodBody(target) + + val argsBindings = for { + (ParamDef(Ident(name, originalName), tpe, mutable), arg) <- formals zip args + } yield { + Binding(name, originalName, tpe, mutable, arg) + } + + withBindings(argsBindings) { (bodyScope, cont1) => + val thisLocalDef = LocalDef( + RefinedType(cls, isExact = true, isNullable = false), false, + InlineClassBeingConstructedReplacement(inputFieldsLocalDefs, cancelFun)) + val statsScope = bodyScope.inlining(targetID).withEnv( + bodyScope.env.withLocalDef("this", thisLocalDef)) + inlineClassConstructorBodyList(allocationSite, thisLocalDef, + inputFieldsLocalDefs, cls, stats, cancelFun)( + buildInner)(cont1)(statsScope) + } (cont) (scope.withEnv(OptEnv.Empty)) + } + + private def inlineClassConstructorBodyList( + allocationSite: AllocationSite, + thisLocalDef: LocalDef, inputFieldsLocalDefs: Map[String, LocalDef], + cls: ClassType, stats: List[Tree], cancelFun: CancelFun)( + buildInner: (Map[String, LocalDef], PreTransCont) => TailRec[Tree])( + cont: PreTransCont)( + implicit scope: Scope): TailRec[Tree] = { + stats match { + case This() :: rest => + inlineClassConstructorBodyList(allocationSite, thisLocalDef, + inputFieldsLocalDefs, cls, rest, cancelFun)(buildInner)(cont) + + case Assign(s @ Select(ths: This, + Ident(fieldName, fieldOrigName), false), value) :: rest => + pretransformExpr(value) { tvalue => + withNewLocalDef(Binding(fieldName, fieldOrigName, s.tpe, false, + tvalue)) { (localDef, cont1) => + if (localDef.contains(thisLocalDef)) { + /* Uh oh, there is a `val x = ...this...`. We can't keep it, + * because this field will not be updated with `newThisLocalDef`. + */ + cancelFun() + } + val newFieldsLocalDefs = + inputFieldsLocalDefs.updated(fieldName, localDef) + val newThisLocalDef = LocalDef( + RefinedType(cls, isExact = true, isNullable = false), false, + InlineClassBeingConstructedReplacement(newFieldsLocalDefs, cancelFun)) + val restScope = scope.withEnv(scope.env.withLocalDef( + "this", newThisLocalDef)) + inlineClassConstructorBodyList(allocationSite, + newThisLocalDef, newFieldsLocalDefs, cls, rest, cancelFun)( + buildInner)(cont1)(restScope) + } (cont) + } + + /* if (cond) + * throw e + * else + * this.outer = value + * + * becomes + * + * this.outer = + * if (cond) throw e + * else value + * + * Typical shape of initialization of outer pointer of inner classes. + */ + case If(cond, th: Throw, + Assign(Select(This(), _, false), value)) :: rest => + // work around a bug of the compiler (these should be @-bindings) + val stat = stats.head.asInstanceOf[If] + val ass = stat.elsep.asInstanceOf[Assign] + val lhs = ass.lhs + inlineClassConstructorBodyList(allocationSite, thisLocalDef, + inputFieldsLocalDefs, cls, + Assign(lhs, If(cond, th, value)(lhs.tpe)(stat.pos))(ass.pos) :: rest, + cancelFun)(buildInner)(cont) + + case StaticApply(ths: This, superClass, superCtor, args) :: rest + if isConstructorName(superCtor.name) => + pretransformExprs(args) { targs => + inlineClassConstructorBody(allocationSite, inputFieldsLocalDefs, + cls, superClass, superCtor, targs, + cancelFun) { (outputFieldsLocalDefs, cont1) => + val newThisLocalDef = LocalDef( + RefinedType(cls, isExact = true, isNullable = false), false, + InlineClassBeingConstructedReplacement(outputFieldsLocalDefs, cancelFun)) + val restScope = scope.withEnv(scope.env.withLocalDef( + "this", newThisLocalDef)) + inlineClassConstructorBodyList(allocationSite, + newThisLocalDef, outputFieldsLocalDefs, + cls, rest, cancelFun)(buildInner)(cont1)(restScope) + } (cont) + } + + case VarDef(Ident(name, originalName), tpe, mutable, rhs) :: rest => + pretransformExpr(rhs) { trhs => + withBinding(Binding(name, originalName, tpe, mutable, trhs)) { (restScope, cont1) => + inlineClassConstructorBodyList(allocationSite, + thisLocalDef, inputFieldsLocalDefs, + cls, rest, cancelFun)(buildInner)(cont1)(restScope) + } (cont) + } + + case stat :: rest => + val transformedStat = transformStat(stat) + transformedStat match { + case Skip() => + inlineClassConstructorBodyList(allocationSite, + thisLocalDef, inputFieldsLocalDefs, + cls, rest, cancelFun)(buildInner)(cont) + case _ => + if (transformedStat.tpe == NothingType) + cont(PreTransTree(transformedStat, RefinedType.Nothing)) + else { + inlineClassConstructorBodyList(allocationSite, + thisLocalDef, inputFieldsLocalDefs, + cls, rest, cancelFun) { (outputFieldsLocalDefs, cont1) => + buildInner(outputFieldsLocalDefs, { tinner => + cont1(PreTransBlock(transformedStat :: Nil, tinner)) + }) + }(cont) + } + } + + case Nil => + buildInner(inputFieldsLocalDefs, cont) + } + } + + private def foldIf(cond: Tree, thenp: Tree, elsep: Tree)(tpe: Type)( + implicit pos: Position): Tree = { + import BinaryOp._ + + @inline def default = If(cond, thenp, elsep)(tpe) + cond match { + case BooleanLiteral(v) => + if (v) thenp + else elsep + + case _ => + @inline def negCond = foldUnaryOp(UnaryOp.Boolean_!, cond) + if (thenp.tpe == BooleanType && elsep.tpe == BooleanType) { + (cond, thenp, elsep) match { + case (_, BooleanLiteral(t), BooleanLiteral(e)) => + if (t == e) Block(keepOnlySideEffects(cond), thenp) + else if (t) cond + else negCond + + case (_, BooleanLiteral(false), _) => + foldIf(negCond, elsep, BooleanLiteral(false))(tpe) // canonical && form + case (_, _, BooleanLiteral(true)) => + foldIf(negCond, BooleanLiteral(true), thenp)(tpe) // canonical || form + + /* if (lhs === null) rhs === null else lhs === rhs + * -> lhs === rhs + * This is the typical shape of a lhs == rhs test where + * the equals() method has been inlined as a reference + * equality test. + */ + case (BinaryOp(BinaryOp.===, VarRef(lhsIdent, _), Null()), + BinaryOp(BinaryOp.===, VarRef(rhsIdent, _), Null()), + BinaryOp(BinaryOp.===, VarRef(lhsIdent2, _), VarRef(rhsIdent2, _))) + if lhsIdent2 == lhsIdent && rhsIdent2 == rhsIdent => + elsep + + // Example: (x > y) || (x == y) -> (x >= y) + case (BinaryOp(op1 @ (Num_== | Num_!= | Num_< | Num_<= | Num_> | Num_>=), l1, r1), + BooleanLiteral(true), + BinaryOp(op2 @ (Num_== | Num_!= | Num_< | Num_<= | Num_> | Num_>=), l2, r2)) + if ((l1.isInstanceOf[Literal] || l1.isInstanceOf[VarRef]) && + (r1.isInstanceOf[Literal] || r1.isInstanceOf[VarRef]) && + (l1 == l2 && r1 == r2)) => + val canBeEqual = + ((op1 == Num_==) || (op1 == Num_<=) || (op1 == Num_>=)) || + ((op2 == Num_==) || (op2 == Num_<=) || (op2 == Num_>=)) + val canBeLessThan = + ((op1 == Num_!=) || (op1 == Num_<) || (op1 == Num_<=)) || + ((op2 == Num_!=) || (op2 == Num_<) || (op2 == Num_<=)) + val canBeGreaterThan = + ((op1 == Num_!=) || (op1 == Num_>) || (op1 == Num_>=)) || + ((op2 == Num_!=) || (op2 == Num_>) || (op2 == Num_>=)) + + fold3WayComparison(canBeEqual, canBeLessThan, canBeGreaterThan, l1, r1) + + // Example: (x >= y) && (x <= y) -> (x == y) + case (BinaryOp(op1 @ (Num_== | Num_!= | Num_< | Num_<= | Num_> | Num_>=), l1, r1), + BinaryOp(op2 @ (Num_== | Num_!= | Num_< | Num_<= | Num_> | Num_>=), l2, r2), + BooleanLiteral(false)) + if ((l1.isInstanceOf[Literal] || l1.isInstanceOf[VarRef]) && + (r1.isInstanceOf[Literal] || r1.isInstanceOf[VarRef]) && + (l1 == l2 && r1 == r2)) => + val canBeEqual = + ((op1 == Num_==) || (op1 == Num_<=) || (op1 == Num_>=)) && + ((op2 == Num_==) || (op2 == Num_<=) || (op2 == Num_>=)) + val canBeLessThan = + ((op1 == Num_!=) || (op1 == Num_<) || (op1 == Num_<=)) && + ((op2 == Num_!=) || (op2 == Num_<) || (op2 == Num_<=)) + val canBeGreaterThan = + ((op1 == Num_!=) || (op1 == Num_>) || (op1 == Num_>=)) && + ((op2 == Num_!=) || (op2 == Num_>) || (op2 == Num_>=)) + + fold3WayComparison(canBeEqual, canBeLessThan, canBeGreaterThan, l1, r1) + + case _ => default + } + } else { + (thenp, elsep) match { + case (Skip(), Skip()) => keepOnlySideEffects(cond) + case (Skip(), _) => foldIf(negCond, elsep, thenp)(tpe) + + case _ => default + } + } + } + } + + private def transformUnaryOp(tree: UnaryOp)(implicit scope: Scope): Tree = { + import UnaryOp._ + + implicit val pos = tree.pos + val UnaryOp(op, arg) = tree + + (op: @switch) match { + case LongToInt => + trampoline { + pretransformExpr(arg) { (targ) => + TailCalls.done { + foldUnaryOp(op, finishTransformOptLongExpr(targ)) + } + } + } + + case _ => + foldUnaryOp(op, transformExpr(arg)) + } + } + + private def transformBinaryOp(tree: BinaryOp)(implicit scope: Scope): Tree = { + import BinaryOp._ + + implicit val pos = tree.pos + val BinaryOp(op, lhs, rhs) = tree + + (op: @switch) match { + case === | !== => + trampoline { + pretransformExprs(lhs, rhs) { (tlhs, trhs) => + TailCalls.done(foldReferenceEquality(tlhs, trhs, op == ===)) + } + } + + case Long_== | Long_!= | Long_< | Long_<= | Long_> | Long_>= => + trampoline { + pretransformExprs(lhs, rhs) { (tlhs, trhs) => + TailCalls.done { + if (isLiteralOrOptimizableLong(tlhs) && + isLiteralOrOptimizableLong(trhs)) { + foldBinaryOp(op, finishTransformOptLongExpr(tlhs), + finishTransformOptLongExpr(trhs)) + } else { + foldBinaryOp(op, finishTransformExpr(tlhs), + finishTransformExpr(trhs)) + } + } + } + } + + case _ => + foldBinaryOp(op, transformExpr(lhs), transformExpr(rhs)) + } + } + + private def isLiteralOrOptimizableLong(texpr: PreTransform): Boolean = { + texpr match { + case PreTransTree(LongLiteral(_), _) => + true + case PreTransLocalDef(LocalDef(_, _, replacement)) => + replacement match { + case ReplaceWithVarRef(_, _, _, Some(_)) => true + case ReplaceWithConstant(LongLiteral(_)) => true + case _ => false + } + case _ => + false + } + } + + private def finishTransformOptLongExpr(targ: PreTransform): Tree = targ match { + case PreTransLocalDef(LocalDef(tpe, false, + ReplaceWithVarRef(_, _, _, Some(argValue)))) => + argValue() + case _ => + finishTransformExpr(targ) + } + + private def foldUnaryOp(op: UnaryOp.Code, arg: Tree)( + implicit pos: Position): Tree = { + import UnaryOp._ + @inline def default = UnaryOp(op, arg) + (op: @switch) match { + case Boolean_! => + arg match { + case BooleanLiteral(v) => BooleanLiteral(!v) + case UnaryOp(Boolean_!, x) => x + + case BinaryOp(innerOp, l, r) => + val newOp = (innerOp: @switch) match { + case BinaryOp.=== => BinaryOp.!== + case BinaryOp.!== => BinaryOp.=== + + case BinaryOp.Num_== => BinaryOp.Num_!= + case BinaryOp.Num_!= => BinaryOp.Num_== + case BinaryOp.Num_< => BinaryOp.Num_>= + case BinaryOp.Num_<= => BinaryOp.Num_> + case BinaryOp.Num_> => BinaryOp.Num_<= + case BinaryOp.Num_>= => BinaryOp.Num_< + + case BinaryOp.Long_== => BinaryOp.Long_!= + case BinaryOp.Long_!= => BinaryOp.Long_== + case BinaryOp.Long_< => BinaryOp.Long_>= + case BinaryOp.Long_<= => BinaryOp.Long_> + case BinaryOp.Long_> => BinaryOp.Long_<= + case BinaryOp.Long_>= => BinaryOp.Long_< + + case BinaryOp.Boolean_== => BinaryOp.Boolean_!= + case BinaryOp.Boolean_!= => BinaryOp.Boolean_== + + case _ => -1 + } + if (newOp == -1) default + else BinaryOp(newOp, l, r) + + case _ => default + } + + case IntToLong => + arg match { + case IntLiteral(v) => LongLiteral(v.toLong) + case _ => default + } + + case LongToInt => + arg match { + case LongLiteral(v) => IntLiteral(v.toInt) + case UnaryOp(IntToLong, x) => x + + case BinaryOp(BinaryOp.Long_+, x, y) => + foldBinaryOp(BinaryOp.Int_+, + foldUnaryOp(LongToInt, x), + foldUnaryOp(LongToInt, y)) + case BinaryOp(BinaryOp.Long_-, x, y) => + foldBinaryOp(BinaryOp.Int_-, + foldUnaryOp(LongToInt, x), + foldUnaryOp(LongToInt, y)) + + case _ => default + } + + case LongToDouble => + arg match { + case LongLiteral(v) => DoubleLiteral(v.toDouble) + case _ => default + } + case DoubleToInt => + arg match { + case _ if arg.tpe == IntType => arg + case NumberLiteral(v) => IntLiteral(v.toInt) + case _ => default + } + case DoubleToFloat => + arg match { + case _ if arg.tpe == FloatType => arg + case NumberLiteral(v) => FloatLiteral(v.toFloat) + case _ => default + } + case DoubleToLong => + arg match { + case _ if arg.tpe == IntType => foldUnaryOp(IntToLong, arg) + case NumberLiteral(v) => LongLiteral(v.toLong) + case _ => default + } + case _ => + default + } + } + + /** Performs === for two literals. + * The result is always known statically. + */ + private def literal_===(lhs: Literal, rhs: Literal): Boolean = { + (lhs, rhs) match { + case (IntLiteral(l), IntLiteral(r)) => l == r + case (FloatLiteral(l), FloatLiteral(r)) => l == r + case (NumberLiteral(l), NumberLiteral(r)) => l == r + case (LongLiteral(l), LongLiteral(r)) => l == r + case (BooleanLiteral(l), BooleanLiteral(r)) => l == r + case (StringLiteral(l), StringLiteral(r)) => l == r + case (Undefined(), Undefined()) => true + case (Null(), Null()) => true + case _ => false + } + } + + private def foldBinaryOp(op: BinaryOp.Code, lhs: Tree, rhs: Tree)( + implicit pos: Position): Tree = { + import BinaryOp._ + @inline def default = BinaryOp(op, lhs, rhs) + (op: @switch) match { + case === | !== => + val positive = (op == ===) + (lhs, rhs) match { + case (lhs: Literal, rhs: Literal) => + BooleanLiteral(literal_===(lhs, rhs) == positive) + + case (_: Literal, _) => foldBinaryOp(op, rhs, lhs) + case _ => default + } + + case Int_+ => + (lhs, rhs) match { + case (IntLiteral(l), IntLiteral(r)) => IntLiteral(l + r) + case (_, IntLiteral(_)) => foldBinaryOp(Int_+, rhs, lhs) + case (IntLiteral(0), _) => rhs + + case (IntLiteral(x), + BinaryOp(innerOp @ (Int_+ | Int_-), IntLiteral(y), z)) => + foldBinaryOp(innerOp, IntLiteral(x+y), z) + + case _ => default + } + + case Int_- => + (lhs, rhs) match { + case (_, IntLiteral(r)) => foldBinaryOp(Int_+, lhs, IntLiteral(-r)) + + case (IntLiteral(x), BinaryOp(Int_+, IntLiteral(y), z)) => + foldBinaryOp(Int_-, IntLiteral(x-y), z) + case (IntLiteral(x), BinaryOp(Int_-, IntLiteral(y), z)) => + foldBinaryOp(Int_+, IntLiteral(x-y), z) + + case (_, BinaryOp(Int_-, IntLiteral(0), x)) => + foldBinaryOp(Int_+, lhs, x) + + case _ => default + } + + case Int_* => + (lhs, rhs) match { + case (IntLiteral(l), IntLiteral(r)) => IntLiteral(l * r) + case (_, IntLiteral(_)) => foldBinaryOp(Int_*, rhs, lhs) + + case (IntLiteral(1), _) => rhs + case (IntLiteral(-1), _) => foldBinaryOp(Int_-, IntLiteral(0), lhs) + + case _ => default + } + + case Int_/ => + (lhs, rhs) match { + case (IntLiteral(l), IntLiteral(r)) if r != 0 => IntLiteral(l / r) + + case (_, IntLiteral(1)) => lhs + case (_, IntLiteral(-1)) => foldBinaryOp(Int_-, IntLiteral(0), lhs) + + case _ => default + } + + case Int_% => + (lhs, rhs) match { + case (IntLiteral(l), IntLiteral(r)) if r != 0 => IntLiteral(l % r) + case (_, IntLiteral(1 | -1)) => + Block(keepOnlySideEffects(lhs), IntLiteral(0)) + case _ => default + } + + case Int_| => + (lhs, rhs) match { + case (IntLiteral(l), IntLiteral(r)) => IntLiteral(l | r) + case (_, IntLiteral(_)) => foldBinaryOp(Int_|, rhs, lhs) + case (IntLiteral(0), _) => rhs + + case (IntLiteral(x), BinaryOp(Int_|, IntLiteral(y), z)) => + foldBinaryOp(Int_|, IntLiteral(x | y), z) + + case _ => default + } + + case Int_& => + (lhs, rhs) match { + case (IntLiteral(l), IntLiteral(r)) => IntLiteral(l & r) + case (_, IntLiteral(_)) => foldBinaryOp(Int_&, rhs, lhs) + case (IntLiteral(-1), _) => rhs + + case (IntLiteral(x), BinaryOp(Int_&, IntLiteral(y), z)) => + foldBinaryOp(Int_&, IntLiteral(x & y), z) + + case _ => default + } + + case Int_^ => + (lhs, rhs) match { + case (IntLiteral(l), IntLiteral(r)) => IntLiteral(l ^ r) + case (_, IntLiteral(_)) => foldBinaryOp(Int_^, rhs, lhs) + case (IntLiteral(0), _) => rhs + + case (IntLiteral(x), BinaryOp(Int_^, IntLiteral(y), z)) => + foldBinaryOp(Int_^, IntLiteral(x ^ y), z) + + case _ => default + } + + case Int_<< => + (lhs, rhs) match { + case (IntLiteral(l), IntLiteral(r)) => IntLiteral(l << r) + case (_, IntLiteral(x)) if x % 32 == 0 => lhs + case _ => default + } + + case Int_>>> => + (lhs, rhs) match { + case (IntLiteral(l), IntLiteral(r)) => IntLiteral(l >>> r) + case (_, IntLiteral(x)) if x % 32 == 0 => lhs + case _ => default + } + + case Int_>> => + (lhs, rhs) match { + case (IntLiteral(l), IntLiteral(r)) => IntLiteral(l >> r) + case (_, IntLiteral(x)) if x % 32 == 0 => lhs + case _ => default + } + + case Long_+ => + (lhs, rhs) match { + case (LongLiteral(l), LongLiteral(r)) => LongLiteral(l + r) + case (_, LongLiteral(_)) => foldBinaryOp(Long_+, rhs, lhs) + case (LongLiteral(0), _) => rhs + + case (LongLiteral(x), + BinaryOp(innerOp @ (Long_+ | Long_-), LongLiteral(y), z)) => + foldBinaryOp(innerOp, LongLiteral(x+y), z) + + case _ => default + } + + case Long_- => + (lhs, rhs) match { + case (_, LongLiteral(r)) => foldBinaryOp(Long_+, LongLiteral(-r), lhs) + + case (LongLiteral(x), BinaryOp(Long_+, LongLiteral(y), z)) => + foldBinaryOp(Long_-, LongLiteral(x-y), z) + case (LongLiteral(x), BinaryOp(Long_-, LongLiteral(y), z)) => + foldBinaryOp(Long_+, LongLiteral(x-y), z) + + case (_, BinaryOp(BinaryOp.Long_-, LongLiteral(0L), x)) => + foldBinaryOp(Long_+, lhs, x) + + case _ => default + } + + case Long_* => + (lhs, rhs) match { + case (LongLiteral(l), LongLiteral(r)) => LongLiteral(l * r) + case (_, LongLiteral(_)) => foldBinaryOp(Long_*, rhs, lhs) + + case (LongLiteral(1), _) => rhs + case (LongLiteral(-1), _) => foldBinaryOp(Long_-, LongLiteral(0), lhs) + + case _ => default + } + + case Long_/ => + (lhs, rhs) match { + case (_, LongLiteral(0)) => default + case (LongLiteral(l), LongLiteral(r)) => LongLiteral(l / r) + + case (_, LongLiteral(1)) => lhs + case (_, LongLiteral(-1)) => foldBinaryOp(Long_-, LongLiteral(0), lhs) + + case (LongFromInt(x), LongFromInt(y: IntLiteral)) if y.value != -1 => + LongFromInt(foldBinaryOp(Int_/, x, y)) + + case _ => default + } + + case Long_% => + (lhs, rhs) match { + case (_, LongLiteral(0)) => default + case (LongLiteral(l), LongLiteral(r)) => LongLiteral(l % r) + + case (_, LongLiteral(1L | -1L)) => + Block(keepOnlySideEffects(lhs), LongLiteral(0L)) + + case (LongFromInt(x), LongFromInt(y)) => + LongFromInt(foldBinaryOp(Int_%, x, y)) + + case _ => default + } + + case Long_| => + (lhs, rhs) match { + case (LongLiteral(l), LongLiteral(r)) => LongLiteral(l | r) + case (_, LongLiteral(_)) => foldBinaryOp(Long_|, rhs, lhs) + case (LongLiteral(0), _) => rhs + + case (LongLiteral(x), BinaryOp(Long_|, LongLiteral(y), z)) => + foldBinaryOp(Long_|, LongLiteral(x | y), z) + + case _ => default + } + + case Long_& => + (lhs, rhs) match { + case (LongLiteral(l), LongLiteral(r)) => LongLiteral(l & r) + case (_, LongLiteral(_)) => foldBinaryOp(Long_&, rhs, lhs) + case (LongLiteral(-1), _) => rhs + + case (LongLiteral(x), BinaryOp(Long_&, LongLiteral(y), z)) => + foldBinaryOp(Long_&, LongLiteral(x & y), z) + + case _ => default + } + + case Long_^ => + (lhs, rhs) match { + case (LongLiteral(l), LongLiteral(r)) => LongLiteral(l ^ r) + case (_, LongLiteral(_)) => foldBinaryOp(Long_^, rhs, lhs) + case (LongLiteral(0), _) => rhs + + case (LongLiteral(x), BinaryOp(Long_^, LongLiteral(y), z)) => + foldBinaryOp(Long_^, LongLiteral(x ^ y), z) + + case _ => default + } + + case Long_<< => + (lhs, rhs) match { + case (LongLiteral(l), IntLiteral(r)) => LongLiteral(l << r) + case (_, IntLiteral(x)) if x % 64 == 0 => lhs + case _ => default + } + + case Long_>>> => + (lhs, rhs) match { + case (LongLiteral(l), IntLiteral(r)) => LongLiteral(l >>> r) + case (_, IntLiteral(x)) if x % 64 == 0 => lhs + case _ => default + } + + case Long_>> => + (lhs, rhs) match { + case (LongLiteral(l), IntLiteral(r)) => LongLiteral(l >> r) + case (_, IntLiteral(x)) if x % 64 == 0 => lhs + case _ => default + } + + case Long_== | Long_!= => + val positive = (op == Long_==) + (lhs, rhs) match { + case (LongLiteral(l), LongLiteral(r)) => + BooleanLiteral((l == r) == positive) + + case (LongFromInt(x), LongFromInt(y)) => + foldBinaryOp(if (positive) === else !==, x, y) + case (LongFromInt(x), LongLiteral(y)) => + assert(y > Int.MaxValue || y < Int.MinValue) + Block(keepOnlySideEffects(x), BooleanLiteral(!positive)) + + case (BinaryOp(Long_+, LongLiteral(x), y), LongLiteral(z)) => + foldBinaryOp(op, y, LongLiteral(z-x)) + case (BinaryOp(Long_-, LongLiteral(x), y), LongLiteral(z)) => + foldBinaryOp(op, y, LongLiteral(x-z)) + + case (LongLiteral(_), _) => foldBinaryOp(op, rhs, lhs) + case _ => default + } + + case Long_< | Long_<= | Long_> | Long_>= => + def flippedOp = (op: @switch) match { + case Long_< => Long_> + case Long_<= => Long_>= + case Long_> => Long_< + case Long_>= => Long_<= + } + + def intOp = (op: @switch) match { + case Long_< => Num_< + case Long_<= => Num_<= + case Long_> => Num_> + case Long_>= => Num_>= + } + + (lhs, rhs) match { + case (LongLiteral(l), LongLiteral(r)) => + val result = (op: @switch) match { + case Long_< => l < r + case Long_<= => l <= r + case Long_> => l > r + case Long_>= => l >= r + } + BooleanLiteral(result) + + case (_, LongLiteral(Long.MinValue)) => + if (op == Long_< || op == Long_>=) + Block(keepOnlySideEffects(lhs), BooleanLiteral(op == Long_>=)) + else + foldBinaryOp(if (op == Long_<=) Long_== else Long_!=, lhs, rhs) + + case (_, LongLiteral(Long.MaxValue)) => + if (op == Long_> || op == Long_<=) + Block(keepOnlySideEffects(lhs), BooleanLiteral(op == Long_<=)) + else + foldBinaryOp(if (op == Long_>=) Long_== else Long_!=, lhs, rhs) + + case (LongFromInt(x), LongFromInt(y)) => + foldBinaryOp(intOp, x, y) + case (LongFromInt(x), LongLiteral(y)) => + assert(y > Int.MaxValue || y < Int.MinValue) + val result = + if (y > Int.MaxValue) op == Long_< || op == Long_<= + else op == Long_> || op == Long_>= + Block(keepOnlySideEffects(x), BooleanLiteral(result)) + + /* x + y.toLong > z + * -x on both sides + * requires x + y.toLong not to overflow, and z - x likewise + * y.toLong > z - x + */ + case (BinaryOp(Long_+, LongLiteral(x), y @ LongFromInt(_)), LongLiteral(z)) + if canAddLongs(x, Int.MinValue) && + canAddLongs(x, Int.MaxValue) && + canSubtractLongs(z, x) => + foldBinaryOp(op, y, LongLiteral(z-x)) + + /* x - y.toLong > z + * -x on both sides + * requires x - y.toLong not to overflow, and z - x likewise + * -(y.toLong) > z - x + */ + case (BinaryOp(Long_-, LongLiteral(x), y @ LongFromInt(_)), LongLiteral(z)) + if canSubtractLongs(x, Int.MinValue) && + canSubtractLongs(x, Int.MaxValue) && + canSubtractLongs(z, x) => + if (z-x != Long.MinValue) { + // Since -(y.toLong) does not overflow, we can negate both sides + foldBinaryOp(flippedOp, y, LongLiteral(-(z-x))) + } else { + /* -(y.toLong) > Long.MinValue + * Depending on the operator, this is either always true or + * always false. + */ + val result = (op == Long_>) || (op == Long_>=) + Block(keepOnlySideEffects(y), BooleanLiteral(result)) + } + + /* x.toLong + y.toLong > Int.MaxValue.toLong + * + * This is basically testing whether x+y overflows in positive. + * If x <= 0 or y <= 0, this cannot happen -> false. + * If x > 0 and y > 0, this can be detected with x+y < 0. + * Therefore, we rewrite as: + * + * x > 0 && y > 0 && x+y < 0. + * + * This requires to evaluate x and y once. + */ + case (BinaryOp(Long_+, LongFromInt(x), LongFromInt(y)), + LongLiteral(Int.MaxValue)) => + trampoline { + withNewLocalDefs(List( + Binding("x", None, IntType, false, PreTransTree(x)), + Binding("y", None, IntType, false, PreTransTree(y)))) { + (tempsLocalDefs, cont) => + val List(tempXDef, tempYDef) = tempsLocalDefs + val tempX = tempXDef.newReplacement + val tempY = tempYDef.newReplacement + cont(PreTransTree( + AndThen(AndThen( + BinaryOp(Num_>, tempX, IntLiteral(0)), + BinaryOp(Num_>, tempY, IntLiteral(0))), + BinaryOp(Num_<, BinaryOp(Int_+, tempX, tempY), IntLiteral(0))))) + } (finishTransform(isStat = false)) + } + + case (LongLiteral(_), _) => foldBinaryOp(flippedOp, rhs, lhs) + case _ => default + } + + case Float_+ => + (lhs, rhs) match { + case (FloatLiteral(l), FloatLiteral(r)) => FloatLiteral(l + r) + case (FloatLiteral(0), _) => rhs + case (_, FloatLiteral(_)) => foldBinaryOp(Float_+, rhs, lhs) + + case (FloatLiteral(x), + BinaryOp(innerOp @ (Float_+ | Float_-), FloatLiteral(y), z)) => + foldBinaryOp(innerOp, FloatLiteral(x+y), z) + + case _ => default + } + + case Float_- => + (lhs, rhs) match { + case (_, FloatLiteral(r)) => foldBinaryOp(Float_+, lhs, FloatLiteral(-r)) + + case (FloatLiteral(x), BinaryOp(Float_+, FloatLiteral(y), z)) => + foldBinaryOp(Float_-, FloatLiteral(x-y), z) + case (FloatLiteral(x), BinaryOp(Float_-, FloatLiteral(y), z)) => + foldBinaryOp(Float_+, FloatLiteral(x-y), z) + + case (_, BinaryOp(BinaryOp.Float_-, FloatLiteral(0), x)) => + foldBinaryOp(Float_+, lhs, x) + + case _ => default + } + + case Float_* => + (lhs, rhs) match { + case (FloatLiteral(l), FloatLiteral(r)) => FloatLiteral(l * r) + case (_, FloatLiteral(_)) => foldBinaryOp(Float_*, rhs, lhs) + + case (FloatLiteral(1), _) => rhs + case (FloatLiteral(-1), _) => foldBinaryOp(Float_-, FloatLiteral(0), lhs) + + case _ => default + } + + case Float_/ => + (lhs, rhs) match { + case (FloatLiteral(l), FloatLiteral(r)) => FloatLiteral(l / r) + + case (_, FloatLiteral(1)) => lhs + case (_, FloatLiteral(-1)) => foldBinaryOp(Float_-, FloatLiteral(0), lhs) + + case _ => default + } + + case Float_% => + (lhs, rhs) match { + case (FloatLiteral(l), FloatLiteral(r)) => FloatLiteral(l % r) + case _ => default + } + + case Double_+ => + (lhs, rhs) match { + case (NumberLiteral(l), NumberLiteral(r)) => DoubleLiteral(l + r) + case (NumberLiteral(0), _) => rhs + case (_, NumberLiteral(_)) => foldBinaryOp(Double_+, rhs, lhs) + + case (NumberLiteral(x), + BinaryOp(innerOp @ (Double_+ | Double_-), NumberLiteral(y), z)) => + foldBinaryOp(innerOp, DoubleLiteral(x+y), z) + + case _ => default + } + + case Double_- => + (lhs, rhs) match { + case (_, NumberLiteral(r)) => foldBinaryOp(Double_+, lhs, DoubleLiteral(-r)) + + case (NumberLiteral(x), BinaryOp(Double_+, NumberLiteral(y), z)) => + foldBinaryOp(Double_-, DoubleLiteral(x-y), z) + case (NumberLiteral(x), BinaryOp(Double_-, NumberLiteral(y), z)) => + foldBinaryOp(Double_+, DoubleLiteral(x-y), z) + + case (_, BinaryOp(BinaryOp.Double_-, NumberLiteral(0), x)) => + foldBinaryOp(Double_+, lhs, x) + + case _ => default + } + + case Double_* => + (lhs, rhs) match { + case (NumberLiteral(l), NumberLiteral(r)) => DoubleLiteral(l * r) + case (_, NumberLiteral(_)) => foldBinaryOp(Double_*, rhs, lhs) + + case (NumberLiteral(1), _) => rhs + case (NumberLiteral(-1), _) => foldBinaryOp(Double_-, DoubleLiteral(0), lhs) + + case _ => default + } + + case Double_/ => + (lhs, rhs) match { + case (NumberLiteral(l), NumberLiteral(r)) => DoubleLiteral(l / r) + + case (_, NumberLiteral(1)) => lhs + case (_, NumberLiteral(-1)) => foldBinaryOp(Double_-, DoubleLiteral(0), lhs) + + case _ => default + } + + case Double_% => + (lhs, rhs) match { + case (NumberLiteral(l), NumberLiteral(r)) => DoubleLiteral(l % r) + case _ => default + } + + case Boolean_== | Boolean_!= => + val positive = (op == Boolean_==) + (lhs, rhs) match { + case (BooleanLiteral(l), _) => + if (l == positive) rhs + else foldUnaryOp(UnaryOp.Boolean_!, rhs) + case (_, BooleanLiteral(r)) => + if (r == positive) lhs + else foldUnaryOp(UnaryOp.Boolean_!, lhs) + case _ => + default + } + + case Boolean_| => + (lhs, rhs) match { + case (_, BooleanLiteral(false)) => lhs + case (BooleanLiteral(false), _) => rhs + case _ => default + } + + case Boolean_& => + (lhs, rhs) match { + case (_, BooleanLiteral(true)) => lhs + case (BooleanLiteral(true), _) => rhs + case _ => default + } + + case Num_== | Num_!= => + val positive = (op == Num_==) + (lhs, rhs) match { + case (lhs: Literal, rhs: Literal) => + BooleanLiteral(literal_===(lhs, rhs) == positive) + + case (BinaryOp(Int_+, IntLiteral(x), y), IntLiteral(z)) => + foldBinaryOp(op, y, IntLiteral(z-x)) + case (BinaryOp(Int_-, IntLiteral(x), y), IntLiteral(z)) => + foldBinaryOp(op, y, IntLiteral(x-z)) + + case (_: Literal, _) => foldBinaryOp(op, rhs, lhs) + case _ => default + } + + case Num_< | Num_<= | Num_> | Num_>= => + def flippedOp = (op: @switch) match { + case Num_< => Num_> + case Num_<= => Num_>= + case Num_> => Num_< + case Num_>= => Num_<= + } + + if (lhs.tpe == IntType && rhs.tpe == IntType) { + (lhs, rhs) match { + case (IntLiteral(l), IntLiteral(r)) => + val result = (op: @switch) match { + case Num_< => l < r + case Num_<= => l <= r + case Num_> => l > r + case Num_>= => l >= r + } + BooleanLiteral(result) + + case (_, IntLiteral(Int.MinValue)) => + if (op == Num_< || op == Num_>=) + Block(keepOnlySideEffects(lhs), BooleanLiteral(op == Num_>=)) + else + foldBinaryOp(if (op == Num_<=) Num_== else Num_!=, lhs, rhs) + + case (_, IntLiteral(Int.MaxValue)) => + if (op == Num_> || op == Num_<=) + Block(keepOnlySideEffects(lhs), BooleanLiteral(op == Num_<=)) + else + foldBinaryOp(if (op == Num_>=) Num_== else Num_!=, lhs, rhs) + + case (IntLiteral(_), _) => foldBinaryOp(flippedOp, rhs, lhs) + case _ => default + } + } else { + (lhs, rhs) match { + case (NumberLiteral(l), NumberLiteral(r)) => + val result = (op: @switch) match { + case Num_< => l < r + case Num_<= => l <= r + case Num_> => l > r + case Num_>= => l >= r + } + BooleanLiteral(result) + + case _ => default + } + } + + case _ => + default + } + } + + private def fold3WayComparison(canBeEqual: Boolean, canBeLessThan: Boolean, + canBeGreaterThan: Boolean, lhs: Tree, rhs: Tree)( + implicit pos: Position): Tree = { + import BinaryOp._ + if (canBeEqual) { + if (canBeLessThan) { + if (canBeGreaterThan) + Block(keepOnlySideEffects(lhs), keepOnlySideEffects(rhs), BooleanLiteral(true)) + else + foldBinaryOp(Num_<=, lhs, rhs) + } else { + if (canBeGreaterThan) + foldBinaryOp(Num_>=, lhs, rhs) + else + foldBinaryOp(Num_==, lhs, rhs) + } + } else { + if (canBeLessThan) { + if (canBeGreaterThan) + foldBinaryOp(Num_!=, lhs, rhs) + else + foldBinaryOp(Num_<, lhs, rhs) + } else { + if (canBeGreaterThan) + foldBinaryOp(Num_>, lhs, rhs) + else + Block(keepOnlySideEffects(lhs), keepOnlySideEffects(rhs), BooleanLiteral(false)) + } + } + } + + private def foldUnbox(arg: PreTransform, charCode: Char)( + cont: PreTransCont): TailRec[Tree] = { + (charCode: @switch) match { + case 'Z' if arg.tpe.base == BooleanType => cont(arg) + case 'I' if arg.tpe.base == IntType => cont(arg) + case 'F' if arg.tpe.base == FloatType => cont(arg) + case 'J' if arg.tpe.base == LongType => cont(arg) + case 'D' if arg.tpe.base == DoubleType || + arg.tpe.base == IntType || arg.tpe.base == FloatType => cont(arg) + case _ => + cont(PreTransTree(Unbox(finishTransformExpr(arg), charCode)(arg.pos))) + } + } + + private def foldReferenceEquality(tlhs: PreTransform, trhs: PreTransform, + positive: Boolean = true)(implicit pos: Position): Tree = { + (tlhs, trhs) match { + case (_, PreTransTree(Null(), _)) if !tlhs.tpe.isNullable => + Block( + finishTransformStat(tlhs), + BooleanLiteral(!positive)) + case (PreTransTree(Null(), _), _) if !trhs.tpe.isNullable => + Block( + finishTransformStat(trhs), + BooleanLiteral(!positive)) + case _ => + foldBinaryOp(if (positive) BinaryOp.=== else BinaryOp.!==, + finishTransformExpr(tlhs), finishTransformExpr(trhs)) + } + } + + private def finishTransformCheckNull(preTrans: PreTransform)( + implicit pos: Position): Tree = { + if (preTrans.tpe.isNullable) { + val transformed = finishTransformExpr(preTrans) + CallHelper("checkNonNull", transformed)(transformed.tpe) + } else { + finishTransformExpr(preTrans) + } + } + + def transformIsolatedBody(optTarget: Option[MethodID], + thisType: Type, params: List[ParamDef], resultType: Type, + body: Tree): (List[ParamDef], Tree) = { + val (paramLocalDefs, newParamDefs) = (for { + p @ ParamDef(ident @ Ident(name, originalName), ptpe, mutable) <- params + } yield { + val newName = freshLocalName(name) + val newOriginalName = originalName.orElse(Some(newName)) + val localDef = LocalDef(RefinedType(ptpe), mutable, + ReplaceWithVarRef(newName, newOriginalName, new SimpleState(true), None)) + val newParamDef = ParamDef( + Ident(newName, newOriginalName)(ident.pos), ptpe, mutable)(p.pos) + ((name -> localDef), newParamDef) + }).unzip + + val thisLocalDef = + if (thisType == NoType) None + else { + Some("this" -> LocalDef( + RefinedType(thisType, isExact = false, isNullable = false), + false, ReplaceWithThis())) + } + + val allLocalDefs = thisLocalDef ++: paramLocalDefs + + val scope0 = optTarget.fold(Scope.Empty)( + target => Scope.Empty.inlining((None, target))) + val scope = scope0.withEnv(OptEnv.Empty.withLocalDefs(allLocalDefs)) + val newBody = + transform(body, resultType == NoType)(scope) + + (newParamDefs, newBody) + } + + private def returnable(oldLabelName: String, resultType: Type, + body: Tree, isStat: Boolean, usePreTransform: Boolean)( + cont: PreTransCont)( + implicit scope: Scope, pos: Position): TailRec[Tree] = tailcall { + val newLabel = freshLabelName( + if (oldLabelName.isEmpty) "inlinereturn" else oldLabelName) + + def doMakeTree(newBody: Tree, returnedTypes: List[Type]): Tree = { + val refinedType = + returnedTypes.reduce(constrainedLub(_, _, resultType)) + val returnCount = returnedTypes.size - 1 + + tryOptimizePatternMatch(oldLabelName, refinedType, + returnCount, newBody) getOrElse { + Labeled(Ident(newLabel, None), refinedType, newBody) + } + } + + val info = new LabelInfo(newLabel, acceptRecords = usePreTransform) + withState(info.returnedTypes) { + val bodyScope = scope.withEnv(scope.env.withLabelInfo(oldLabelName, info)) + + if (usePreTransform) { + assert(!isStat, "Cannot use pretransform in statement position") + tryOrRollback { cancelFun => + pretransformExpr(body) { tbody0 => + val returnedTypes0 = info.returnedTypes.value + if (returnedTypes0.isEmpty) { + // no return to that label, we can eliminate it + cont(tbody0) + } else { + val tbody = resolveLocalDef(tbody0) + val (newBody, returnedTypes) = tbody match { + case PreTransRecordTree(bodyTree, origType, _) => + (bodyTree, (bodyTree.tpe, origType) :: returnedTypes0) + case PreTransTree(bodyTree, tpe) => + (bodyTree, (bodyTree.tpe, tpe) :: returnedTypes0) + } + val (actualTypes, origTypes) = returnedTypes.unzip + val refinedOrigType = + origTypes.reduce(constrainedLub(_, _, resultType)) + actualTypes.collectFirst { + case actualType: RecordType => actualType + }.fold[TailRec[Tree]] { + // None of the returned types are records + cont(PreTransTree( + doMakeTree(newBody, actualTypes), refinedOrigType)) + } { recordType => + if (actualTypes.exists(t => t != recordType && t != NothingType)) + cancelFun() + + val resultTree = doMakeTree(newBody, actualTypes) + + if (origTypes.exists(t => t != refinedOrigType && !t.isNothingType)) + cancelFun() + + cont(PreTransRecordTree(resultTree, refinedOrigType, cancelFun)) + } + } + } (bodyScope) + } { () => + returnable(oldLabelName, resultType, body, isStat, + usePreTransform = false)(cont) + } + } else { + val newBody = transform(body, isStat)(bodyScope) + val returnedTypes0 = info.returnedTypes.value.map(_._1) + if (returnedTypes0.isEmpty) { + // no return to that label, we can eliminate it + cont(PreTransTree(newBody, RefinedType(newBody.tpe))) + } else { + val returnedTypes = newBody.tpe :: returnedTypes0 + val tree = doMakeTree(newBody, returnedTypes) + cont(PreTransTree(tree, RefinedType(tree.tpe))) + } + } + } + } + + def tryOptimizePatternMatch(oldLabelName: String, refinedType: Type, + returnCount: Int, newBody: Tree): Option[Tree] = { + if (!oldLabelName.startsWith("matchEnd")) None + else { + newBody match { + case Block(stats) => + @tailrec + def createRevAlts(xs: List[Tree], acc: List[(Tree, Tree)]): List[(Tree, Tree)] = xs match { + case If(cond, body, Skip()) :: xr => + createRevAlts(xr, (cond, body) :: acc) + case remaining => + (EmptyTree, Block(remaining)(remaining.head.pos)) :: acc + } + val revAlts = createRevAlts(stats, Nil) + + if (revAlts.size == returnCount) { + @tailrec + def constructOptimized(revAlts: List[(Tree, Tree)], elsep: Tree): Option[Tree] = { + revAlts match { + case (cond, body) :: revAltsRest => + body match { + case BlockOrAlone(prep, + Return(result, Some(Ident(newLabel, _)))) => + val result1 = + if (refinedType == NoType) keepOnlySideEffects(result) + else result + val prepAndResult = Block(prep :+ result1)(body.pos) + if (cond == EmptyTree) { + assert(elsep == EmptyTree) + constructOptimized(revAltsRest, prepAndResult) + } else { + assert(elsep != EmptyTree) + constructOptimized(revAltsRest, + foldIf(cond, prepAndResult, elsep)(refinedType)(cond.pos)) + } + case _ => + None + } + case Nil => + Some(elsep) + } + } + constructOptimized(revAlts, EmptyTree) + } else None + case _ => + None + } + } + } + + private def withBindings(bindings: List[Binding])( + buildInner: (Scope, PreTransCont) => TailRec[Tree])( + cont: PreTransCont)( + implicit scope: Scope): TailRec[Tree] = { + withNewLocalDefs(bindings) { (localDefs, cont1) => + val newMappings = for { + (binding, localDef) <- bindings zip localDefs + } yield { + binding.name -> localDef + } + buildInner(scope.withEnv(scope.env.withLocalDefs(newMappings)), cont1) + } (cont) + } + + private def withBinding(binding: Binding)( + buildInner: (Scope, PreTransCont) => TailRec[Tree])( + cont: PreTransCont)( + implicit scope: Scope): TailRec[Tree] = { + withNewLocalDef(binding) { (localDef, cont1) => + buildInner(scope.withEnv(scope.env.withLocalDef(binding.name, localDef)), + cont1) + } (cont) + } + + private def withNewLocalDefs(bindings: List[Binding])( + buildInner: (List[LocalDef], PreTransCont) => TailRec[Tree])( + cont: PreTransCont): TailRec[Tree] = { + bindings match { + case first :: rest => + withNewLocalDef(first) { (firstLocalDef, cont1) => + withNewLocalDefs(rest) { (restLocalDefs, cont2) => + buildInner(firstLocalDef :: restLocalDefs, cont2) + } (cont1) + } (cont) + + case Nil => + buildInner(Nil, cont) + } + } + + private def isImmutableType(tpe: Type): Boolean = tpe match { + case RecordType(fields) => + fields.forall(f => !f.mutable && isImmutableType(f.tpe)) + case _ => + true + } + + private def withNewLocalDef(binding: Binding)( + buildInner: (LocalDef, PreTransCont) => TailRec[Tree])( + cont: PreTransCont): TailRec[Tree] = tailcall { + val Binding(name, originalName, declaredType, mutable, value) = binding + implicit val pos = value.pos + + def withDedicatedVar(tpe: RefinedType): TailRec[Tree] = { + val newName = freshLocalName(name) + val newOriginalName = originalName.orElse(Some(name)) + + val used = new SimpleState(false) + withState(used) { + def doBuildInner(localDef: LocalDef)(varDef: => VarDef)( + cont: PreTransCont): TailRec[Tree] = { + buildInner(localDef, { tinner => + if (used.value) { + cont(PreTransBlock(varDef :: Nil, tinner)) + } else { + tinner match { + case PreTransLocalDef(`localDef`) => + cont(value) + case _ if tinner.contains(localDef) => + cont(PreTransBlock(varDef :: Nil, tinner)) + case _ => + val rhsSideEffects = finishTransformStat(value) + rhsSideEffects match { + case Skip() => + cont(tinner) + case _ => + if (rhsSideEffects.tpe == NothingType) + cont(PreTransTree(rhsSideEffects, RefinedType.Nothing)) + else + cont(PreTransBlock(rhsSideEffects :: Nil, tinner)) + } + } + } + }) + } + + resolveLocalDef(value) match { + case PreTransRecordTree(valueTree, valueTpe, cancelFun) => + val recordType = valueTree.tpe.asInstanceOf[RecordType] + if (!isImmutableType(recordType)) + cancelFun() + val localDef = LocalDef(valueTpe, mutable, + ReplaceWithRecordVarRef(newName, newOriginalName, recordType, + used, cancelFun)) + doBuildInner(localDef) { + VarDef(Ident(newName, newOriginalName), recordType, mutable, + valueTree) + } (cont) + + case PreTransTree(valueTree, valueTpe) => + def doDoBuildInner(optValueTree: Option[() => Tree])( + cont1: PreTransCont) = { + val localDef = LocalDef(tpe, mutable, ReplaceWithVarRef( + newName, newOriginalName, used, optValueTree)) + doBuildInner(localDef) { + VarDef(Ident(newName, newOriginalName), tpe.base, mutable, + optValueTree.fold(valueTree)(_())) + } (cont1) + } + if (mutable) { + doDoBuildInner(None)(cont) + } else (valueTree match { + case LongFromInt(arg) => + withNewLocalDef( + Binding("x", None, IntType, false, PreTransTree(arg))) { + (intLocalDef, cont1) => + doDoBuildInner(Some( + () => LongFromInt(intLocalDef.newReplacement)))( + cont1) + } (cont) + + case BinaryOp(op @ (BinaryOp.Long_+ | BinaryOp.Long_-), + LongFromInt(intLhs), LongFromInt(intRhs)) => + withNewLocalDefs(List( + Binding("x", None, IntType, false, PreTransTree(intLhs)), + Binding("y", None, IntType, false, PreTransTree(intRhs)))) { + (intLocalDefs, cont1) => + val List(lhsLocalDef, rhsLocalDef) = intLocalDefs + doDoBuildInner(Some( + () => BinaryOp(op, + LongFromInt(lhsLocalDef.newReplacement), + LongFromInt(rhsLocalDef.newReplacement))))( + cont1) + } (cont) + + case _ => + doDoBuildInner(None)(cont) + }) + } + } + } + + if (value.tpe.isNothingType) { + cont(value) + } else if (mutable) { + withDedicatedVar(RefinedType(declaredType)) + } else { + val refinedType = value.tpe + value match { + case PreTransBlock(stats, result) => + withNewLocalDef(binding.copy(value = result))(buildInner) { tresult => + cont(PreTransBlock(stats, tresult)) + } + + case PreTransLocalDef(localDef) if !localDef.mutable => + buildInner(localDef, cont) + + case PreTransTree(literal: Literal, _) => + buildInner(LocalDef(refinedType, false, + ReplaceWithConstant(literal)), cont) + + case PreTransTree(VarRef(Ident(refName, refOriginalName), false), _) => + buildInner(LocalDef(refinedType, false, + ReplaceWithVarRef(refName, refOriginalName, + new SimpleState(true), None)), cont) + + case _ => + withDedicatedVar(refinedType) + } + } + } + + /** Finds a type as precise as possible which is a supertype of lhs and rhs + * but still a subtype of upperBound. + * Requires that lhs and rhs be subtypes of upperBound, obviously. + */ + private def constrainedLub(lhs: RefinedType, rhs: RefinedType, + upperBound: Type): RefinedType = { + if (upperBound == NoType) RefinedType(upperBound) + else if (lhs == rhs) lhs + else if (lhs.isNothingType) rhs + else if (rhs.isNothingType) lhs + else { + RefinedType(constrainedLub(lhs.base, rhs.base, upperBound), + false, lhs.isNullable || rhs.isNullable) + } + } + + /** Finds a type as precise as possible which is a supertype of lhs and rhs + * but still a subtype of upperBound. + * Requires that lhs and rhs be subtypes of upperBound, obviously. + */ + private def constrainedLub(lhs: Type, rhs: Type, upperBound: Type): Type = { + // TODO Improve this + if (upperBound == NoType) upperBound + else if (lhs == rhs) lhs + else if (lhs == NothingType) rhs + else if (rhs == NothingType) lhs + else upperBound + } + + /** Trampolines a pretransform */ + private def trampoline(tailrec: => TailRec[Tree]): Tree = { + curTrampolineId += 1 + + val myTrampolineId = curTrampolineId + + try { + var rec = () => tailrec + + while (true) { + try { + return rec().result + } catch { + case e: RollbackException if e.trampolineId == myTrampolineId => + rollbacksCount += 1 + if (rollbacksCount > MaxRollbacksPerMethod) + throw new TooManyRollbacksException + + usedLocalNames.clear() + usedLocalNames ++= e.savedUsedLocalNames + usedLabelNames.clear() + usedLabelNames ++= e.savedUsedLabelNames + for ((state, backup) <- statesInUse zip e.savedStates) + state.asInstanceOf[State[Any]].restore(backup) + + rec = e.cont + } + } + + sys.error("Reached end of infinite loop") + } finally { + curTrampolineId -= 1 + } + } +} + +private[optimizer] object OptimizerCore { + + private final val MaxRollbacksPerMethod = 256 + + private final class TooManyRollbacksException + extends scala.util.control.ControlThrowable + + private val AnonFunctionClassPrefix = "sjsr_AnonFunction" + + private type CancelFun = () => Nothing + private type PreTransCont = PreTransform => TailRec[Tree] + + private case class RefinedType private (base: Type, isExact: Boolean, + isNullable: Boolean)( + val allocationSite: Option[AllocationSite], dummy: Int = 0) { + + def isNothingType: Boolean = base == NothingType + } + + private object RefinedType { + def apply(base: Type, isExact: Boolean, isNullable: Boolean, + allocationSite: Option[AllocationSite]): RefinedType = + new RefinedType(base, isExact, isNullable)(allocationSite) + + def apply(base: Type, isExact: Boolean, isNullable: Boolean): RefinedType = + RefinedType(base, isExact, isNullable, None) + + def apply(tpe: Type): RefinedType = tpe match { + case BooleanType | IntType | FloatType | DoubleType | StringType | + UndefType | NothingType | _:RecordType | NoType => + RefinedType(tpe, isExact = true, isNullable = false) + case NullType => + RefinedType(tpe, isExact = true, isNullable = true) + case _ => + RefinedType(tpe, isExact = false, isNullable = true) + } + + val NoRefinedType = RefinedType(NoType) + val Nothing = RefinedType(NothingType) + } + + private class AllocationSite(private val node: Tree) { + override def equals(that: Any): Boolean = that match { + case that: AllocationSite => this.node eq that.node + case _ => false + } + + override def hashCode(): Int = + System.identityHashCode(node) + + override def toString(): String = + s"AllocationSite($node)" + } + + private case class LocalDef( + tpe: RefinedType, + mutable: Boolean, + replacement: LocalDefReplacement) { + + def newReplacement(implicit pos: Position): Tree = replacement match { + case ReplaceWithVarRef(name, originalName, used, _) => + used.value = true + VarRef(Ident(name, originalName), mutable)(tpe.base) + + case ReplaceWithRecordVarRef(_, _, _, _, cancelFun) => + cancelFun() + + case ReplaceWithThis() => + This()(tpe.base) + + case ReplaceWithConstant(value) => + value + + case TentativeClosureReplacement(_, _, _, _, _, cancelFun) => + cancelFun() + + case InlineClassBeingConstructedReplacement(_, cancelFun) => + cancelFun() + + case InlineClassInstanceReplacement(_, _, cancelFun) => + cancelFun() + } + + def contains(that: LocalDef): Boolean = { + (this eq that) || (replacement match { + case TentativeClosureReplacement(_, _, _, captureLocalDefs, _, _) => + captureLocalDefs.exists(_.contains(that)) + case InlineClassInstanceReplacement(_, fieldLocalDefs, _) => + fieldLocalDefs.valuesIterator.exists(_.contains(that)) + case _ => + false + }) + } + } + + private sealed abstract class LocalDefReplacement + + private final case class ReplaceWithVarRef(name: String, + originalName: Option[String], + used: SimpleState[Boolean], + longOpTree: Option[() => Tree]) extends LocalDefReplacement + + private final case class ReplaceWithRecordVarRef(name: String, + originalName: Option[String], + recordType: RecordType, + used: SimpleState[Boolean], + cancelFun: CancelFun) extends LocalDefReplacement + + private final case class ReplaceWithThis() extends LocalDefReplacement + + private final case class ReplaceWithConstant( + value: Tree) extends LocalDefReplacement + + private final case class TentativeClosureReplacement( + captureParams: List[ParamDef], params: List[ParamDef], body: Tree, + captureValues: List[LocalDef], + alreadyUsed: SimpleState[Boolean], + cancelFun: CancelFun) extends LocalDefReplacement + + private final case class InlineClassBeingConstructedReplacement( + fieldLocalDefs: Map[String, LocalDef], + cancelFun: CancelFun) extends LocalDefReplacement + + private final case class InlineClassInstanceReplacement( + recordType: RecordType, + fieldLocalDefs: Map[String, LocalDef], + cancelFun: CancelFun) extends LocalDefReplacement + + private final class LabelInfo( + val newName: String, + val acceptRecords: Boolean, + /** (actualType, originalType), actualType can be a RecordType. */ + val returnedTypes: SimpleState[List[(Type, RefinedType)]] = new SimpleState(Nil)) + + private class OptEnv( + val localDefs: Map[String, LocalDef], + val labelInfos: Map[String, LabelInfo]) { + + def withLocalDef(oldName: String, rep: LocalDef): OptEnv = + new OptEnv(localDefs + (oldName -> rep), labelInfos) + + def withLocalDefs(reps: List[(String, LocalDef)]): OptEnv = + new OptEnv(localDefs ++ reps, labelInfos) + + def withLabelInfo(oldName: String, info: LabelInfo): OptEnv = + new OptEnv(localDefs, labelInfos + (oldName -> info)) + + def withinFunction(paramLocalDefs: List[(String, LocalDef)]): OptEnv = + new OptEnv(localDefs ++ paramLocalDefs, Map.empty) + + override def toString(): String = { + "localDefs:"+localDefs.mkString("\n ", "\n ", "\n") + + "labelInfos:"+labelInfos.mkString("\n ", "\n ", "") + } + } + + private object OptEnv { + val Empty: OptEnv = new OptEnv(Map.empty, Map.empty) + } + + private class Scope(val env: OptEnv, + val implsBeingInlined: Set[(Option[AllocationSite], AbstractMethodID)]) { + def withEnv(env: OptEnv): Scope = + new Scope(env, implsBeingInlined) + + def inlining(impl: (Option[AllocationSite], AbstractMethodID)): Scope = { + assert(!implsBeingInlined(impl), s"Circular inlining of $impl") + new Scope(env, implsBeingInlined + impl) + } + } + + private object Scope { + val Empty: Scope = new Scope(OptEnv.Empty, Set.empty) + } + + /** The result of pretransformExpr(). + * It has a `tpe` as precisely refined as if a full transformExpr() had + * been performed. + * It is also not dependent on the environment anymore. In some sense, it + * has "captured" its environment at definition site. + */ + private sealed abstract class PreTransform { + def pos: Position + val tpe: RefinedType + + def contains(localDef: LocalDef): Boolean = this match { + case PreTransBlock(_, result) => + result.contains(localDef) + case PreTransLocalDef(thisLocalDef) => + thisLocalDef.contains(localDef) + case _ => + false + } + } + + private final class PreTransBlock private (val stats: List[Tree], + val result: PreTransLocalDef) extends PreTransform { + def pos = result.pos + val tpe = result.tpe + + assert(stats.nonEmpty) + + override def toString(): String = + s"PreTransBlock($stats,$result)" + } + + private object PreTransBlock { + def apply(stats: List[Tree], result: PreTransform): PreTransform = { + if (stats.isEmpty) result + else { + result match { + case PreTransBlock(innerStats, innerResult) => + new PreTransBlock(stats ++ innerStats, innerResult) + case result: PreTransLocalDef => + new PreTransBlock(stats, result) + case PreTransRecordTree(tree, tpe, cancelFun) => + PreTransRecordTree(Block(stats :+ tree)(tree.pos), tpe, cancelFun) + case PreTransTree(tree, tpe) => + PreTransTree(Block(stats :+ tree)(tree.pos), tpe) + } + } + } + + def unapply(preTrans: PreTransBlock): Some[(List[Tree], PreTransLocalDef)] = + Some(preTrans.stats, preTrans.result) + } + + private sealed abstract class PreTransNoBlock extends PreTransform + + private final case class PreTransLocalDef(localDef: LocalDef)( + implicit val pos: Position) extends PreTransNoBlock { + val tpe: RefinedType = localDef.tpe + } + + private sealed abstract class PreTransGenTree extends PreTransNoBlock + + private final case class PreTransRecordTree(tree: Tree, + tpe: RefinedType, cancelFun: CancelFun) extends PreTransGenTree { + def pos = tree.pos + + assert(tree.tpe.isInstanceOf[RecordType], + s"Cannot create a PreTransRecordTree with non-record type ${tree.tpe}") + } + + private final case class PreTransTree(tree: Tree, + tpe: RefinedType) extends PreTransGenTree { + def pos: Position = tree.pos + + assert(!tree.tpe.isInstanceOf[RecordType], + s"Cannot create a Tree with record type ${tree.tpe}") + } + + private object PreTransTree { + def apply(tree: Tree): PreTransTree = + PreTransTree(tree, RefinedType(tree.tpe)) + } + + private final case class Binding(name: String, originalName: Option[String], + declaredType: Type, mutable: Boolean, value: PreTransform) + + private object NumberLiteral { + def unapply(tree: Literal): Option[Double] = tree match { + case DoubleLiteral(v) => Some(v) + case IntLiteral(v) => Some(v.toDouble) + case FloatLiteral(v) => Some(v.toDouble) + case _ => None + } + } + + private object LongFromInt { + def apply(x: Tree)(implicit pos: Position): Tree = x match { + case IntLiteral(v) => LongLiteral(v) + case _ => UnaryOp(UnaryOp.IntToLong, x) + } + + def unapply(tree: Tree): Option[Tree] = tree match { + case LongLiteral(v) if v.toInt == v => Some(IntLiteral(v.toInt)(tree.pos)) + case UnaryOp(UnaryOp.IntToLong, x) => Some(x) + case _ => None + } + } + + private object AndThen { + def apply(lhs: Tree, rhs: Tree)(implicit pos: Position): Tree = + If(lhs, rhs, BooleanLiteral(false))(BooleanType) + } + + /** Tests whether `x + y` is valid without falling out of range. */ + private def canAddLongs(x: Long, y: Long): Boolean = + if (y >= 0) x+y >= x + else x+y < x + + /** Tests whether `x - y` is valid without falling out of range. */ + private def canSubtractLongs(x: Long, y: Long): Boolean = + if (y >= 0) x-y <= x + else x-y > x + + /** Tests whether `-x` is valid without falling out of range. */ + private def canNegateLong(x: Long): Boolean = + x != Long.MinValue + + private object Intrinsics { + final val ArrayCopy = 1 + final val IdentityHashCode = ArrayCopy + 1 + + final val PropertiesOf = IdentityHashCode + 1 + + final val LongToString = PropertiesOf + 1 + final val LongCompare = LongToString + 1 + final val LongBitCount = LongCompare + 1 + final val LongSignum = LongBitCount + 1 + final val LongLeading0s = LongSignum + 1 + final val LongTrailing0s = LongLeading0s + 1 + final val LongToBinStr = LongTrailing0s + 1 + final val LongToHexStr = LongToBinStr + 1 + final val LongToOctalStr = LongToHexStr + 1 + + final val ByteArrayToInt8Array = LongToOctalStr + 1 + final val ShortArrayToInt16Array = ByteArrayToInt8Array + 1 + final val CharArrayToUint16Array = ShortArrayToInt16Array + 1 + final val IntArrayToInt32Array = CharArrayToUint16Array + 1 + final val FloatArrayToFloat32Array = IntArrayToInt32Array + 1 + final val DoubleArrayToFloat64Array = FloatArrayToFloat32Array + 1 + + final val Int8ArrayToByteArray = DoubleArrayToFloat64Array + 1 + final val Int16ArrayToShortArray = Int8ArrayToByteArray + 1 + final val Uint16ArrayToCharArray = Int16ArrayToShortArray + 1 + final val Int32ArrayToIntArray = Uint16ArrayToCharArray + 1 + final val Float32ArrayToFloatArray = Int32ArrayToIntArray + 1 + final val Float64ArrayToDoubleArray = Float32ArrayToFloatArray + 1 + + val intrinsics: Map[String, Int] = Map( + "jl_System$.arraycopy__O__I__O__I__I__V" -> ArrayCopy, + "jl_System$.identityHashCode__O__I" -> IdentityHashCode, + + "sjsr_package$.propertiesOf__sjs_js_Any__sjs_js_Array" -> PropertiesOf, + + "jl_Long$.toString__J__T" -> LongToString, + "jl_Long$.compare__J__J__I" -> LongCompare, + "jl_Long$.bitCount__J__I" -> LongBitCount, + "jl_Long$.signum__J__J" -> LongSignum, + "jl_Long$.numberOfLeadingZeros__J__I" -> LongLeading0s, + "jl_Long$.numberOfTrailingZeros__J__I" -> LongTrailing0s, + "jl_long$.toBinaryString__J__T" -> LongToBinStr, + "jl_Long$.toHexString__J__T" -> LongToHexStr, + "jl_Long$.toOctalString__J__T" -> LongToOctalStr, + + "sjs_js_typedarray_package$.byteArray2Int8Array__AB__sjs_js_typedarray_Int8Array" -> ByteArrayToInt8Array, + "sjs_js_typedarray_package$.shortArray2Int16Array__AS__sjs_js_typedarray_Int16Array" -> ShortArrayToInt16Array, + "sjs_js_typedarray_package$.charArray2Uint16Array__AC__sjs_js_typedarray_Uint16Array" -> CharArrayToUint16Array, + "sjs_js_typedarray_package$.intArray2Int32Array__AI__sjs_js_typedarray_Int32Array" -> IntArrayToInt32Array, + "sjs_js_typedarray_package$.floatArray2Float32Array__AF__sjs_js_typedarray_Float32Array" -> FloatArrayToFloat32Array, + "sjs_js_typedarray_package$.doubleArray2Float64Array__AD__sjs_js_typedarray_Float64Array" -> DoubleArrayToFloat64Array, + + "sjs_js_typedarray_package$.int8Array2ByteArray__sjs_js_typedarray_Int8Array__AB" -> Int8ArrayToByteArray, + "sjs_js_typedarray_package$.int16Array2ShortArray__sjs_js_typedarray_Int16Array__AS" -> Int16ArrayToShortArray, + "sjs_js_typedarray_package$.uint16Array2CharArray__sjs_js_typedarray_Uint16Array__AC" -> Uint16ArrayToCharArray, + "sjs_js_typedarray_package$.int32Array2IntArray__sjs_js_typedarray_Int32Array__AI" -> Int32ArrayToIntArray, + "sjs_js_typedarray_package$.float32Array2FloatArray__sjs_js_typedarray_Float32Array__AF" -> Float32ArrayToFloatArray, + "sjs_js_typedarray_package$.float64Array2DoubleArray__sjs_js_typedarray_Float64Array__AD" -> Float64ArrayToDoubleArray + ).withDefaultValue(-1) + } + + private def getIntrinsicCode(target: AbstractMethodID): Int = + Intrinsics.intrinsics(target.toString) + + private trait State[A] { + def makeBackup(): A + def restore(backup: A): Unit + } + + private class SimpleState[A](var value: A) extends State[A] { + def makeBackup(): A = value + def restore(backup: A): Unit = value = backup + } + + trait AbstractMethodID { + def inlineable: Boolean + def isTraitImplForwarder: Boolean + } + + /** Parts of [[GenIncOptimizer#MethodImpl]] with decisions about optimizations. */ + abstract class MethodImpl { + def encodedName: String + def optimizerHints: OptimizerHints + def originalDef: MethodDef + def thisType: Type + + var inlineable: Boolean = false + var isTraitImplForwarder: Boolean = false + + protected def updateInlineable(): Unit = { + val MethodDef(Ident(methodName, _), params, _, body) = originalDef + + isTraitImplForwarder = body match { + // Shape of forwarders to trait impls + case TraitImplApply(impl, method, args) => + ((args.size == params.size + 1) && + (args.head.isInstanceOf[This]) && + (args.tail.zip(params).forall { + case (VarRef(Ident(aname, _), _), + ParamDef(Ident(pname, _), _, _)) => aname == pname + case _ => false + })) + + case _ => false + } + + inlineable = optimizerHints.hasInlineAnnot || isTraitImplForwarder || { + val MethodDef(_, params, _, body) = originalDef + body match { + case _:Skip | _:This | _:Literal => true + + // Shape of accessors + case Select(This(), _, _) if params.isEmpty => true + case Assign(Select(This(), _, _), VarRef(_, _)) + if params.size == 1 => true + + // Shape of trivial call-super constructors + case Block(stats) + if params.isEmpty && isConstructorName(encodedName) && + stats.forall(isTrivialConstructorStat) => true + + // Simple method + case SimpleMethodBody() => true + + case _ => false + } + } + } + } + + private def isTrivialConstructorStat(stat: Tree): Boolean = stat match { + case This() => + true + case StaticApply(This(), _, _, Nil) => + true + case TraitImplApply(_, Ident(methodName, _), This() :: Nil) => + methodName.contains("__$init$__") + case _ => + false + } + + private object SimpleMethodBody { + @tailrec + def unapply(body: Tree): Boolean = body match { + case New(_, _, args) => areSimpleArgs(args) + case Apply(receiver, _, args) => areSimpleArgs(receiver :: args) + case StaticApply(receiver, _, _, args) => areSimpleArgs(receiver :: args) + case TraitImplApply(_, _, args) => areSimpleArgs(args) + case Select(qual, _, _) => isSimpleArg(qual) + case IsInstanceOf(inner, _) => isSimpleArg(inner) + + case Block(List(inner, Undefined())) => + unapply(inner) + + case Unbox(inner, _) => unapply(inner) + case AsInstanceOf(inner, _) => unapply(inner) + + case _ => isSimpleArg(body) + } + + private def areSimpleArgs(args: List[Tree]): Boolean = + args.forall(isSimpleArg) + + @tailrec + private def isSimpleArg(arg: Tree): Boolean = arg match { + case New(_, _, Nil) => true + case Apply(receiver, _, Nil) => isTrivialArg(receiver) + case StaticApply(receiver, _, _, Nil) => isTrivialArg(receiver) + case TraitImplApply(_, _, Nil) => true + + case ArrayLength(array) => isTrivialArg(array) + case ArraySelect(array, index) => isTrivialArg(array) && isTrivialArg(index) + + case Unbox(inner, _) => isSimpleArg(inner) + case AsInstanceOf(inner, _) => isSimpleArg(inner) + + case _ => + isTrivialArg(arg) + } + + private def isTrivialArg(arg: Tree): Boolean = arg match { + case _:VarRef | _:This | _:Literal | _:LoadModule => + true + case _ => + false + } + } + + private object BlockOrAlone { + def unapply(tree: Tree): Some[(List[Tree], Tree)] = Some(tree match { + case Block(init :+ last) => (init, last) + case _ => (Nil, tree) + }) + } + + /** Recreates precise [[Infos.MethodInfo]] from the optimized [[MethodDef]]. */ + private def recreateInfo(methodDef: MethodDef): Infos.MethodInfo = { + new RecreateInfoTraverser().recreateInfo(methodDef) + } + + private final class RecreateInfoTraverser extends Traversers.Traverser { + import RecreateInfoTraverser._ + + private val calledMethods = mutable.Map.empty[String, mutable.Set[String]] + private val calledMethodsStatic = mutable.Map.empty[String, mutable.Set[String]] + private val instantiatedClasses = mutable.Set.empty[String] + private val accessedModules = mutable.Set.empty[String] + private val accessedClassData = mutable.Set.empty[String] + + def recreateInfo(methodDef: MethodDef): Infos.MethodInfo = { + traverse(methodDef.body) + Infos.MethodInfo( + encodedName = methodDef.name.name, + calledMethods = calledMethods.toMap.mapValues(_.toList), + calledMethodsStatic = calledMethodsStatic.toMap.mapValues(_.toList), + instantiatedClasses = instantiatedClasses.toList, + accessedModules = accessedModules.toList, + accessedClassData = accessedClassData.toList) + } + + private def addCalledMethod(container: String, methodName: String): Unit = + calledMethods.getOrElseUpdate(container, mutable.Set.empty) += methodName + + private def addCalledMethodStatic(container: String, methodName: String): Unit = + calledMethodsStatic.getOrElseUpdate(container, mutable.Set.empty) += methodName + + private def refTypeToClassData(tpe: ReferenceType): String = tpe match { + case ClassType(cls) => cls + case ArrayType(base, _) => base + } + + def addAccessedClassData(encodedName: String): Unit = { + if (!AlwaysPresentClassData.contains(encodedName)) + accessedClassData += encodedName + } + + def addAccessedClassData(tpe: ReferenceType): Unit = + addAccessedClassData(refTypeToClassData(tpe)) + + override def traverse(tree: Tree): Unit = { + tree match { + case New(ClassType(cls), ctor, _) => + instantiatedClasses += cls + addCalledMethodStatic(cls, ctor.name) + + case Apply(receiver, method, _) => + receiver.tpe match { + case ClassType(cls) if !Definitions.HijackedClasses.contains(cls) => + addCalledMethod(cls, method.name) + case AnyType => + addCalledMethod(Definitions.ObjectClass, method.name) + case ArrayType(_, _) if method.name != "clone__O" => + /* clone__O is overridden in the pseudo Array classes and is + * always kept anyway, because it is in scalajsenv.js. + * Other methods delegate to Object, which we can model with + * a static call to Object.method. + */ + addCalledMethodStatic(Definitions.ObjectClass, method.name) + case _ => + // Nothing to do + } + + case StaticApply(_, ClassType(cls), method, _) => + addCalledMethodStatic(cls, method.name) + case TraitImplApply(ClassType(impl), method, _) => + addCalledMethodStatic(impl, method.name) + + case LoadModule(ClassType(cls)) => + accessedModules += cls.stripSuffix("$") + + case NewArray(tpe, _) => + addAccessedClassData(tpe) + case ArrayValue(tpe, _) => + addAccessedClassData(tpe) + case IsInstanceOf(_, cls) => + addAccessedClassData(cls) + case AsInstanceOf(_, cls) => + addAccessedClassData(cls) + case ClassOf(cls) => + addAccessedClassData(cls) + + case _ => + } + super.traverse(tree) + } + } + + private object RecreateInfoTraverser { + /** Class data that are never eliminated by dce, so we don't need to + * record them. + */ + private val AlwaysPresentClassData = { + import Definitions._ + Set("V", "Z", "C", "B", "S", "I", "J", "F", "D", + ObjectClass, StringClass) + } + } + + private def exceptionMsg(myself: AbstractMethodID, + attemptedInlining: List[AbstractMethodID]) = { + val buf = new StringBuilder() + + buf.append("The Scala.js optimizer crashed while optimizing " + myself) + + buf.append("\nMethods attempted to inline:\n") + + for (m <- attemptedInlining) { + buf.append("* ") + buf.append(m) + buf.append('\n') + } + + buf.toString + } + + private class RollbackException(val trampolineId: Int, + val savedUsedLocalNames: Set[String], + val savedUsedLabelNames: Set[String], + val savedStates: List[Any], + val cont: () => TailRec[Tree]) extends ControlThrowable + + class OptimizeException(val myself: AbstractMethodID, + val attemptedInlining: List[AbstractMethodID], cause: Throwable + ) extends Exception(exceptionMsg(myself, attemptedInlining), cause) + +} diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/optimizer/ScalaJSOptimizer.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/optimizer/ScalaJSOptimizer.scala new file mode 100644 index 0000000..646484b --- /dev/null +++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/optimizer/ScalaJSOptimizer.scala @@ -0,0 +1,552 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js tools ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.tools.optimizer + +import scala.annotation.{switch, tailrec} + +import scala.collection.mutable +import scala.collection.immutable.{Seq, Traversable} + +import java.net.URI + +import scala.scalajs.ir +import ir.Infos +import ir.ClassKind + +import scala.scalajs.tools.logging._ +import scala.scalajs.tools.io._ +import scala.scalajs.tools.classpath._ +import scala.scalajs.tools.sourcemap._ +import scala.scalajs.tools.corelib._ + +import scala.scalajs.tools.sem.Semantics + +import scala.scalajs.tools.javascript +import javascript.{Trees => js} + +/** Scala.js optimizer: does type-aware global dce. */ +class ScalaJSOptimizer( + semantics: Semantics, + optimizerFactory: (Semantics) => GenIncOptimizer) { + import ScalaJSOptimizer._ + + private val classEmitter = new javascript.ScalaJSClassEmitter(semantics) + + private[this] var persistentState: PersistentState = new PersistentState + private[this] var optimizer: GenIncOptimizer = optimizerFactory(semantics) + + def this(semantics: Semantics) = this(semantics, new IncOptimizer(_)) + + /** Applies Scala.js-specific optimizations to a CompleteIRClasspath. + * See [[ScalaJSOptimizer.Inputs]] for details about the required and + * optional inputs. + * See [[ScalaJSOptimizer.OutputConfig]] for details about the configuration + * for the output of this method. + * Returns a [[CompleteCIClasspath]] containing the result of the + * optimizations. + * + * analyzes, dead code eliminates and concatenates IR content + * - Maintains/establishes order + * - No IR in result + * - CoreJSLibs in result (since they are implicitly in the CompleteIRCP) + */ + def optimizeCP(inputs: Inputs[IRClasspath], outCfg: OutputConfig, + logger: Logger): LinkedClasspath = { + + val cp = inputs.input + + CacheUtils.cached(cp.version, outCfg.output, outCfg.cache) { + logger.info(s"Fast optimizing ${outCfg.output.path}") + optimizeIR(inputs.copy(input = inputs.input.scalaJSIR), outCfg, logger) + } + + new LinkedClasspath(cp.jsLibs, outCfg.output, cp.requiresDOM, cp.version) + } + + def optimizeIR(inputs: Inputs[Traversable[VirtualScalaJSIRFile]], + outCfg: OutputConfig, logger: Logger): Unit = { + + val builder = { + import outCfg._ + if (wantSourceMap) + new JSFileBuilderWithSourceMap(output.name, + output.contentWriter, + output.sourceMapWriter, + relativizeSourceMapBase) + else + new JSFileBuilder(output.name, output.contentWriter) + } + + builder.addLine("'use strict';") + CoreJSLibs.libs(semantics).foreach(builder.addFile _) + + optimizeIR(inputs, outCfg, builder, logger) + + builder.complete() + builder.closeWriters() + } + + def optimizeIR(inputs: Inputs[Traversable[VirtualScalaJSIRFile]], + outCfg: OptimizerConfig, builder: JSTreeBuilder, logger: Logger): Unit = { + + /* Handle tree equivalence: If we handled source maps so far, positions are + still up-to-date. Otherwise we need to flush the state if proper + positions are requested now. + */ + if (outCfg.wantSourceMap && !persistentState.wasWithSourceMap) + clean() + + persistentState.wasWithSourceMap = outCfg.wantSourceMap + + persistentState.startRun() + try { + import inputs._ + val allData = + GenIncOptimizer.logTime(logger, "Read info") { + readAllData(inputs.input, logger) + } + val (useOptimizer, refinedAnalyzer) = GenIncOptimizer.logTime( + logger, "Optimizations part") { + val analyzer = + GenIncOptimizer.logTime(logger, "Compute reachability") { + val analyzer = new Analyzer(logger, semantics, allData, + globalWarnEnabled = true, + isBeforeOptimizer = !outCfg.disableOptimizer) + analyzer.computeReachability(manuallyReachable, noWarnMissing) + analyzer + } + if (outCfg.checkIR) { + GenIncOptimizer.logTime(logger, "Check IR") { + if (analyzer.allAvailable) + checkIR(analyzer, logger) + else if (inputs.noWarnMissing.isEmpty) + sys.error("Could not check IR because there where linking errors.") + } + } + def getClassTreeIfChanged(encodedName: String, + lastVersion: Option[String]): Option[(ir.Trees.ClassDef, Option[String])] = { + val persistentFile = persistentState.encodedNameToPersistentFile(encodedName) + persistentFile.treeIfChanged(lastVersion) + } + + val useOptimizer = analyzer.allAvailable && !outCfg.disableOptimizer + + if (outCfg.batchMode) + optimizer = optimizerFactory(semantics) + + val refinedAnalyzer = if (useOptimizer) { + GenIncOptimizer.logTime(logger, "Inliner") { + optimizer.update(analyzer, getClassTreeIfChanged, + outCfg.wantSourceMap, logger) + } + GenIncOptimizer.logTime(logger, "Refined reachability analysis") { + val refinedData = computeRefinedData(allData, optimizer) + val refinedAnalyzer = new Analyzer(logger, semantics, refinedData, + globalWarnEnabled = false, + isBeforeOptimizer = false) + refinedAnalyzer.computeReachability(manuallyReachable, noWarnMissing) + refinedAnalyzer + } + } else { + if (inputs.noWarnMissing.isEmpty && !outCfg.disableOptimizer) + logger.warn("Not running the inliner because there where linking errors.") + analyzer + } + (useOptimizer, refinedAnalyzer) + } + GenIncOptimizer.logTime(logger, "Write DCE'ed output") { + buildDCEedOutput(builder, refinedAnalyzer, useOptimizer) + } + } finally { + persistentState.endRun(outCfg.unCache) + logger.debug( + s"Inc. opt stats: reused: ${persistentState.statsReused} -- "+ + s"invalidated: ${persistentState.statsInvalidated} -- "+ + s"trees read: ${persistentState.statsTreesRead}") + } + } + + /** Resets all persistent state of this optimizer */ + def clean(): Unit = { + persistentState = new PersistentState + optimizer = optimizerFactory(semantics) + } + + private def readAllData(ir: Traversable[VirtualScalaJSIRFile], + logger: Logger): scala.collection.Seq[Infos.ClassInfo] = { + ir.map(persistentState.getPersistentIRFile(_).info).toSeq + } + + private def checkIR(analyzer: Analyzer, logger: Logger): Unit = { + val allClassDefs = for { + classInfo <- analyzer.classInfos.values + persistentIRFile <- persistentState.encodedNameToPersistentFile.get( + classInfo.encodedName) + } yield persistentIRFile.tree + val checker = new IRChecker(analyzer, allClassDefs.toSeq, logger) + if (!checker.check()) + sys.error(s"There were ${checker.errorCount} IR checking errors.") + } + + private def computeRefinedData( + allData: scala.collection.Seq[Infos.ClassInfo], + optimizer: GenIncOptimizer): scala.collection.Seq[Infos.ClassInfo] = { + + def refineMethodInfo(container: optimizer.MethodContainer, + methodInfo: Infos.MethodInfo): Infos.MethodInfo = { + container.methods.get(methodInfo.encodedName).fold(methodInfo) { + methodImpl => methodImpl.preciseInfo + } + } + + def refineMethodInfos(container: optimizer.MethodContainer, + methodInfos: List[Infos.MethodInfo]): List[Infos.MethodInfo] = { + methodInfos.map(m => refineMethodInfo(container, m)) + } + + def refineClassInfo(container: optimizer.MethodContainer, + info: Infos.ClassInfo): Infos.ClassInfo = { + val refinedMethods = refineMethodInfos(container, info.methods) + Infos.ClassInfo(info.name, info.encodedName, info.isExported, + info.ancestorCount, info.kind, info.superClass, info.ancestors, + Infos.OptimizerHints.empty, refinedMethods) + } + + for { + info <- allData + } yield { + info.kind match { + case ClassKind.Class | ClassKind.ModuleClass => + optimizer.getClass(info.encodedName).fold(info) { + cls => refineClassInfo(cls, info) + } + + case ClassKind.TraitImpl => + optimizer.getTraitImpl(info.encodedName).fold(info) { + impl => refineClassInfo(impl, info) + } + + case _ => + info + } + } + } + + private def buildDCEedOutput(builder: JSTreeBuilder, + analyzer: Analyzer, useInliner: Boolean): Unit = { + + def compareClassInfo(lhs: analyzer.ClassInfo, rhs: analyzer.ClassInfo) = { + if (lhs.ancestorCount != rhs.ancestorCount) lhs.ancestorCount < rhs.ancestorCount + else lhs.encodedName.compareTo(rhs.encodedName) < 0 + } + + def addPersistentFile(classInfo: analyzer.ClassInfo, + persistentFile: PersistentIRFile) = { + import ir.Trees._ + import javascript.JSDesugaring.{desugarJavaScript => desugar} + + val d = persistentFile.desugared + lazy val classDef = { + persistentState.statsTreesRead += 1 + persistentFile.tree + } + + def addTree(tree: js.Tree): Unit = + builder.addJSTree(tree) + + def addReachableMethods(emitFun: (String, MethodDef) => js.Tree): Unit = { + /* This is a bit convoluted because we have to: + * * avoid to use classDef at all if we already know all the needed methods + * * if any new method is needed, better to go through the defs once + */ + val methodNames = d.methodNames.getOrElseUpdate( + classDef.defs collect { + case MethodDef(Ident(encodedName, _), _, _, _) => encodedName + }) + val reachableMethods = methodNames.filter( + name => classInfo.methodInfos(name).isReachable) + if (reachableMethods.forall(d.methods.contains(_))) { + for (encodedName <- reachableMethods) { + addTree(d.methods(encodedName)) + } + } else { + classDef.defs.foreach { + case m: MethodDef if m.name.isInstanceOf[Ident] => + if (classInfo.methodInfos(m.name.name).isReachable) { + addTree(d.methods.getOrElseUpdate(m.name.name, + emitFun(classInfo.encodedName, m))) + } + case _ => + } + } + } + + if (classInfo.isImplClass) { + if (useInliner) { + for { + method <- optimizer.findTraitImpl(classInfo.encodedName).methods.values + if (classInfo.methodInfos(method.encodedName).isReachable) + } { + addTree(method.desugaredDef) + } + } else { + addReachableMethods(classEmitter.genTraitImplMethod) + } + } else if (!classInfo.hasMoreThanData) { + // there is only the data anyway + addTree(d.wholeClass.getOrElseUpdate( + classEmitter.genClassDef(classDef))) + } else { + if (classInfo.isAnySubclassInstantiated) { + addTree(d.constructor.getOrElseUpdate( + classEmitter.genConstructor(classDef))) + if (useInliner) { + for { + method <- optimizer.findClass(classInfo.encodedName).methods.values + if (classInfo.methodInfos(method.encodedName).isReachable) + } { + addTree(method.desugaredDef) + } + } else { + addReachableMethods(classEmitter.genMethod) + } + addTree(d.exportedMembers.getOrElseUpdate(js.Block { + classDef.defs collect { + case m: MethodDef if m.name.isInstanceOf[StringLiteral] => + classEmitter.genMethod(classInfo.encodedName, m) + case p: PropertyDef => + classEmitter.genProperty(classInfo.encodedName, p) + } + }(classDef.pos))) + } + if (classInfo.isDataAccessed) { + addTree(d.typeData.getOrElseUpdate(js.Block( + classEmitter.genInstanceTests(classDef), + classEmitter.genArrayInstanceTests(classDef), + classEmitter.genTypeData(classDef) + )(classDef.pos))) + } + if (classInfo.isAnySubclassInstantiated) + addTree(d.setTypeData.getOrElseUpdate( + classEmitter.genSetTypeData(classDef))) + if (classInfo.isModuleAccessed) + addTree(d.moduleAccessor.getOrElseUpdate( + classEmitter.genModuleAccessor(classDef))) + addTree(d.classExports.getOrElseUpdate( + classEmitter.genClassExports(classDef))) + } + } + + + for { + classInfo <- analyzer.classInfos.values.toSeq.sortWith(compareClassInfo) + if classInfo.isNeededAtAll + } { + val optPersistentFile = + persistentState.encodedNameToPersistentFile.get(classInfo.encodedName) + + // if we have a persistent file, this is not a dummy class + optPersistentFile.fold { + if (classInfo.isAnySubclassInstantiated) { + // Subclass will emit constructor that references this dummy class. + // Therefore, we need to emit a dummy parent. + builder.addJSTree( + classEmitter.genDummyParent(classInfo.encodedName)) + } + } { pf => addPersistentFile(classInfo, pf) } + } + } +} + +object ScalaJSOptimizer { + /** Inputs of the Scala.js optimizer. */ + final case class Inputs[T]( + /** The CompleteNCClasspath or the IR files to be packaged. */ + input: T, + /** Manual additions to reachability */ + manuallyReachable: Seq[ManualReachability] = Nil, + /** Elements we won't warn even if they don't exist */ + noWarnMissing: Seq[NoWarnMissing] = Nil + ) + + sealed abstract class ManualReachability + final case class ReachObject(name: String) extends ManualReachability + final case class Instantiate(name: String) extends ManualReachability + final case class ReachMethod(className: String, methodName: String, + static: Boolean) extends ManualReachability + + sealed abstract class NoWarnMissing + final case class NoWarnClass(className: String) extends NoWarnMissing + final case class NoWarnMethod(className: String, methodName: String) + extends NoWarnMissing + + /** Configurations relevant to the optimizer */ + trait OptimizerConfig { + /** Ask to produce source map for the output. Is used in the incremental + * optimizer to decide whether a position change should trigger re-inlining + */ + val wantSourceMap: Boolean + /** If true, performs expensive checks of the IR for the used parts. */ + val checkIR: Boolean + /** If true, the optimizer removes trees that have not been used in the + * last run from the cache. Otherwise, all trees that has been used once, + * are kept in memory. */ + val unCache: Boolean + /** If true, no optimizations are performed */ + val disableOptimizer: Boolean + /** If true, nothing is performed incrementally */ + val batchMode: Boolean + } + + /** Configuration for the output of the Scala.js optimizer. */ + final case class OutputConfig( + /** Writer for the output. */ + output: WritableVirtualJSFile, + /** Cache file */ + cache: Option[WritableVirtualTextFile] = None, + /** Ask to produce source map for the output */ + wantSourceMap: Boolean = false, + /** Base path to relativize paths in the source map. */ + relativizeSourceMapBase: Option[URI] = None, + /** If true, performs expensive checks of the IR for the used parts. */ + checkIR: Boolean = false, + /** If true, the optimizer removes trees that have not been used in the + * last run from the cache. Otherwise, all trees that has been used once, + * are kept in memory. */ + unCache: Boolean = true, + /** If true, no optimizations are performed */ + disableOptimizer: Boolean = false, + /** If true, nothing is performed incrementally */ + batchMode: Boolean = false + ) extends OptimizerConfig + + // Private helpers ----------------------------------------------------------- + + private final class PersistentState { + val files = mutable.Map.empty[String, PersistentIRFile] + val encodedNameToPersistentFile = + mutable.Map.empty[String, PersistentIRFile] + + var statsReused: Int = 0 + var statsInvalidated: Int = 0 + var statsTreesRead: Int = 0 + + var wasWithSourceMap: Boolean = true + + def startRun(): Unit = { + statsReused = 0 + statsInvalidated = 0 + statsTreesRead = 0 + for (file <- files.values) + file.startRun() + } + + def getPersistentIRFile(irFile: VirtualScalaJSIRFile): PersistentIRFile = { + val file = files.getOrElseUpdate(irFile.path, + new PersistentIRFile(irFile.path)) + if (file.updateFile(irFile)) + statsReused += 1 + else + statsInvalidated += 1 + encodedNameToPersistentFile += ((file.info.encodedName, file)) + file + } + + def endRun(unCache: Boolean): Unit = { + // "Garbage-collect" persisted versions of files that have disappeared + files.retain((_, f) => f.cleanAfterRun(unCache)) + encodedNameToPersistentFile.clear() + } + } + + private final class PersistentIRFile(val path: String) { + import ir.Trees._ + + private[this] var existedInThisRun: Boolean = false + private[this] var desugaredUsedInThisRun: Boolean = false + + private[this] var irFile: VirtualScalaJSIRFile = null + private[this] var version: Option[String] = None + private[this] var _info: Infos.ClassInfo = null + private[this] var _tree: ClassDef = null + private[this] var _desugared: Desugared = null + + def startRun(): Unit = { + existedInThisRun = false + desugaredUsedInThisRun = false + } + + def updateFile(irFile: VirtualScalaJSIRFile): Boolean = { + existedInThisRun = true + this.irFile = irFile + if (version.isDefined && version == irFile.version) { + // yeepeeh, nothing to do + true + } else { + version = irFile.version + _info = irFile.info + _tree = null + _desugared = null + false + } + } + + def info: Infos.ClassInfo = _info + + def desugared: Desugared = { + desugaredUsedInThisRun = true + if (_desugared == null) + _desugared = new Desugared + _desugared + } + + def tree: ClassDef = { + if (_tree == null) + _tree = irFile.tree + _tree + } + + def treeIfChanged(lastVersion: Option[String]): Option[(ClassDef, Option[String])] = { + if (lastVersion.isDefined && lastVersion == version) None + else Some((tree, version)) + } + + /** Returns true if this file should be kept for the next run at all. */ + def cleanAfterRun(unCache: Boolean): Boolean = { + irFile = null + if (unCache && !desugaredUsedInThisRun) + _desugared = null // free desugared if unused in this run + existedInThisRun + } + } + + private final class Desugared { + // for class kinds that are not decomposed + val wholeClass = new OneTimeCache[js.Tree] + + val constructor = new OneTimeCache[js.Tree] + val methodNames = new OneTimeCache[List[String]] + val methods = mutable.Map.empty[String, js.Tree] + val exportedMembers = new OneTimeCache[js.Tree] + val typeData = new OneTimeCache[js.Tree] + val setTypeData = new OneTimeCache[js.Tree] + val moduleAccessor = new OneTimeCache[js.Tree] + val classExports = new OneTimeCache[js.Tree] + } + + private final class OneTimeCache[A >: Null] { + private[this] var value: A = null + def getOrElseUpdate(v: => A): A = { + if (value == null) + value = v + value + } + } +} diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/sem/CheckedBehavior.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/sem/CheckedBehavior.scala new file mode 100644 index 0000000..4668b3c --- /dev/null +++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/sem/CheckedBehavior.scala @@ -0,0 +1,24 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js tools ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.tools.sem + +sealed abstract class CheckedBehavior { + import CheckedBehavior._ + def optimized: CheckedBehavior = this match { + case Fatal => Unchecked + case _ => this + } +} + +object CheckedBehavior { + case object Compliant extends CheckedBehavior + case object Fatal extends CheckedBehavior + case object Unchecked extends CheckedBehavior +} diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/sem/Semantics.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/sem/Semantics.scala new file mode 100644 index 0000000..9d17b06 --- /dev/null +++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/sem/Semantics.scala @@ -0,0 +1,97 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js tools ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.tools.sem + +import scala.collection.immutable.Traversable + +final class Semantics private ( + val asInstanceOfs: CheckedBehavior, + val strictFloats: Boolean) { + + import Semantics._ + + def withAsInstanceOfs(behavior: CheckedBehavior): Semantics = + copy(asInstanceOfs = behavior) + + def withStrictFloats(strictFloats: Boolean): Semantics = + copy(strictFloats = strictFloats) + + def optimized: Semantics = + copy(asInstanceOfs = this.asInstanceOfs.optimized) + + override def equals(that: Any): Boolean = that match { + case that: Semantics => + this.asInstanceOfs == that.asInstanceOfs && + this.strictFloats == that.strictFloats + case _ => + false + } + + override def hashCode(): Int = { + import scala.util.hashing.MurmurHash3._ + var acc = HashSeed + acc = mix(acc, asInstanceOfs.hashCode) + acc = mixLast(acc, strictFloats.##) + finalizeHash(acc, 1) + } + + override def toString(): String = { + s"""Semantics( + | asInstanceOfs = $asInstanceOfs, + | strictFloats = $strictFloats + |)""".stripMargin + } + + /** Checks whether the given semantics setting is Java compliant */ + def isCompliant(name: String): Boolean = name match { + case "asInstanceOfs" => asInstanceOfs == CheckedBehavior.Compliant + case "strictFloats" => strictFloats + case _ => false + } + + /** Retrieve a list of semantics which are set to compliant */ + def compliants: List[String] = { + def cl(name: String, cond: Boolean) = if (cond) List(name) else Nil + + cl("asInstanceOfs", asInstanceOfs == CheckedBehavior.Compliant) ++ + cl("strictFloats", strictFloats) + } + + private def copy( + asInstanceOfs: CheckedBehavior = this.asInstanceOfs, + strictFloats: Boolean = this.strictFloats): Semantics = { + new Semantics( + asInstanceOfs = asInstanceOfs, + strictFloats = strictFloats) + } +} + +object Semantics { + private val HashSeed = + scala.util.hashing.MurmurHash3.stringHash(classOf[Semantics].getName) + + val Defaults: Semantics = new Semantics( + asInstanceOfs = CheckedBehavior.Fatal, + strictFloats = false) + + def compliantTo(semantics: Traversable[String]): Semantics = { + import Defaults._ + import CheckedBehavior._ + + val semsSet = semantics.toSet + + def sw[T](name: String, compliant: T, default: T): T = + if (semsSet.contains(name)) compliant else default + + new Semantics( + asInstanceOfs = sw("asInstanceOfs", Compliant, asInstanceOfs), + strictFloats = sw("strictFloats", true, strictFloats)) + } +} diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/sourcemap/JSFileBuilder.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/sourcemap/JSFileBuilder.scala new file mode 100644 index 0000000..1bf2254 --- /dev/null +++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/sourcemap/JSFileBuilder.scala @@ -0,0 +1,144 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js tools ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.tools.sourcemap + +import scala.annotation.tailrec + +import scala.collection.mutable + +import java.io._ +import java.util.regex.Pattern +import java.net.{ URI, URISyntaxException } + +import scala.scalajs.ir.Position +import scala.scalajs.tools.{javascript => js} +import scala.scalajs.tools.io._ +import scala.scalajs.tools.optimizer.JSTreeBuilder + +class JSFileBuilder(val name: String, + protected val outputWriter: Writer) extends JSTreeBuilder { + def addLine(line: String): Unit = { + outputWriter.write(line) + outputWriter.write('\n') + } + + def addLines(lines: Seq[String]): Unit = + lines.foreach(addLine) + + def addFile(file: VirtualJSFile): Unit = + addPartsOfFile(file)(!_.startsWith("//# sourceMappingURL=")) + + def addPartsOfFile(file: VirtualJSFile)(selector: String => Boolean): Unit = { + for (line <- file.readLines() if selector(line)) + addLine(line) + } + + /** Add a JavaScript tree representing a statement. + * The tree must be a valid JavaScript tree (typically obtained by + * desugaring a full-fledged IR tree). + */ + def addJSTree(tree: js.Trees.Tree): Unit = { + val printer = new js.Printers.JSTreePrinter(outputWriter) + printer.printTopLevelTree(tree) + // Do not close the printer: we do not have ownership of the writers + } + + /** Closes the underlying writer(s). + */ + def closeWriters(): Unit = { + outputWriter.close() + } +} + +class JSFileBuilderWithSourceMapWriter(n: String, ow: Writer, + protected val sourceMapWriter: SourceMapWriter) + extends JSFileBuilder(n, ow) { + + override def addLine(line: String): Unit = { + super.addLine(line) + sourceMapWriter.nextLine() + } + + private final val NotSelected = -1 + + override def addPartsOfFile(file: VirtualJSFile)( + selector: String => Boolean): Unit = { + val br = new BufferedReader(file.reader) + try { + // Select lines, and remember offsets + val offsets = new mutable.ArrayBuffer[Int] // (maybe NotSelected) + val selectedLineLengths = new mutable.ArrayBuffer[Int] + var line: String = br.readLine() + var selectedCount = 0 + while (line != null) { + if (selector(line)) { + super.addLine(line) // super call not to advance line in source map + offsets += selectedCount + selectedLineLengths += line.length + selectedCount += 1 + } else { + offsets += NotSelected + } + line = br.readLine() + } + + /* We ignore a potential source map. + * This happens typically for corejslib.js and other helper files + * written directly in JS. + * We generate a fake line-by-line source map for these on the fly + */ + val sourceFile = file.toURI + + for (lineNumber <- 0 until offsets.size) { + val offset = offsets(lineNumber) + if (offset != NotSelected) { + val originalPos = Position(sourceFile, lineNumber, 0) + sourceMapWriter.startNode(0, originalPos, None) + sourceMapWriter.endNode(selectedLineLengths(offset)) + sourceMapWriter.nextLine() + } + } + } finally { + br.close() + } + } + + override def addJSTree(tree: js.Trees.Tree): Unit = { + val printer = new js.Printers.JSTreePrinterWithSourceMap( + outputWriter, sourceMapWriter) + printer.printTopLevelTree(tree) + // Do not close the printer: we do not have ownership of the writers + } + + override def complete(): Unit = { + super.complete() + sourceMapWriter.complete() + } + +} + +class JSFileBuilderWithSourceMap(n: String, ow: Writer, + sourceMapOutputWriter: Writer, + relativizeSourceMapBasePath: Option[URI] = None) + extends JSFileBuilderWithSourceMapWriter( + n, ow, + new SourceMapWriter(sourceMapOutputWriter, n, + relativizeSourceMapBasePath)) { + + override def complete(): Unit = { + addLine("//# sourceMappingURL=" + name + ".map") + super.complete() + } + + override def closeWriters(): Unit = { + super.closeWriters() + sourceMapOutputWriter.close() + } +} diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/sourcemap/SourceMapWriter.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/sourcemap/SourceMapWriter.scala new file mode 100644 index 0000000..5d8bdb1 --- /dev/null +++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/sourcemap/SourceMapWriter.scala @@ -0,0 +1,213 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js tools ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.tools.sourcemap + +import java.io.Writer +import java.net.URI + +import scala.collection.mutable.{ ListBuffer, HashMap, Stack, StringBuilder } + +import scala.scalajs.ir +import ir.Position +import ir.Position._ +import ir.Utils + +object SourceMapWriter { + private val Base64Map = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + + "abcdefghijklmnopqrstuvwxyz" + + "0123456789+/" + + // Some constants for writeBase64VLQ + // Each base-64 digit covers 6 bits, but 1 is used for the continuation + private final val VLQBaseShift = 5 + private final val VLQBase = 1 << VLQBaseShift + private final val VLQBaseMask = VLQBase - 1 + private final val VLQContinuationBit = VLQBase + + private def jsonString(s: String) = + "\"" + Utils.escapeJS(s) + "\"" +} + +class SourceMapWriter( + val out: Writer, + val generatedFile: String, + val relativizeBaseURI: Option[URI] = None) { + + import SourceMapWriter._ + + private val sources = new ListBuffer[String] + private val _srcToIndex = new HashMap[SourceFile, Int] + + private val names = new ListBuffer[String] + private val _nameToIndex = new HashMap[String, Int] + + private val nodePosStack = new Stack[(Position, Option[String])] + nodePosStack.push((NoPosition, None)) + + private var lineCountInGenerated = 0 + private var lastColumnInGenerated = 0 + private var firstSegmentOfLine = true + private var lastSource: SourceFile = null + private var lastSourceIndex = 0 + private var lastLine: Int = 0 + private var lastColumn: Int = 0 + private var lastNameIndex: Int = 0 + + private var pendingColumnInGenerated: Int = -1 + private var pendingPos: Position = NoPosition + private var pendingName: Option[String] = None + + writeHeader() + + private def sourceToIndex(source: SourceFile) = + _srcToIndex.getOrElseUpdate(source, + (sources += sourceToURI(source)).size-1) + + /** Relatively hacky way to get a Web-friendly URI to the source file */ + private def sourceToURI(source: SourceFile): String = { + val uri = source + val relURI = relativizeBaseURI.fold(uri)(Utils.relativize(_, uri)) + + Utils.fixFileURI(relURI).toASCIIString + } + + private def nameToIndex(name: String) = + _nameToIndex.getOrElseUpdate(name, (names += name).size-1) + + private def writeHeader(): Unit = { + out.write("{\n") + out.write("\"version\": 3,\n") + out.write("\"file\": " + jsonString(generatedFile) + ",\n") + out.write("\"mappings\": \"") + } + + def nextLine(): Unit = { + writePendingSegment() + out.write(';') + lineCountInGenerated += 1 + lastColumnInGenerated = 0 + firstSegmentOfLine = true + pendingColumnInGenerated = -1 + pendingPos = nodePosStack.top._1 + pendingName = nodePosStack.top._2 + } + + def startNode(column: Int, originalPos: Position, + originalName: Option[String] = None): Unit = { + nodePosStack.push((originalPos, originalName)) + startSegment(column, originalPos, originalName) + } + + def endNode(column: Int): Unit = { + nodePosStack.pop() + startSegment(column, nodePosStack.top._1, nodePosStack.top._2) + } + + private def startSegment(startColumn: Int, originalPos: Position, + originalName: Option[String]): Unit = { + // There is no point in outputting a segment with the same information + if ((originalPos == pendingPos) && (originalName == pendingName)) + return + + // Write pending segment if it covers a non-empty range + if (startColumn != pendingColumnInGenerated) + writePendingSegment() + + // New pending + pendingColumnInGenerated = startColumn + pendingPos = originalPos + pendingName = + if (startColumn != pendingColumnInGenerated) originalName + else pendingName orElse originalName + } + + private def writePendingSegment() { + if (pendingColumnInGenerated < 0) + return + + // Segments of a line are separated by ',' + if (firstSegmentOfLine) firstSegmentOfLine = false + else out.write(',') + + // Generated column field + writeBase64VLQ(pendingColumnInGenerated-lastColumnInGenerated) + lastColumnInGenerated = pendingColumnInGenerated + + // If the position is NoPosition, stop here + if (!pendingPos.isDefined) + return + + // Extract relevant properties of pendingPos + val source = pendingPos.source + val line = pendingPos.line + val column = pendingPos.column + + // Source index field + if (source == lastSource) { // highly likely + writeBase64VLQ0() + } else { + val sourceIndex = sourceToIndex(source) + writeBase64VLQ(sourceIndex-lastSourceIndex) + lastSource = source + lastSourceIndex = sourceIndex + } + + // Line field + writeBase64VLQ(line - lastLine) + lastLine = line + + // Column field + writeBase64VLQ(column - lastColumn) + lastColumn = column + + // Name field + if (pendingName.isDefined) { + val nameIndex = nameToIndex(pendingName.get) + writeBase64VLQ(nameIndex-lastNameIndex) + lastNameIndex = nameIndex + } + } + + def complete(): Unit = { + writePendingSegment() + + out.write("\",\n") + out.write( + sources.map(jsonString(_)).mkString("\"sources\": [", ", ", "],\n")) + out.write( + names.map(jsonString(_)).mkString("\"names\": [", ", ", "],\n")) + out.write("\"lineCount\": "+lineCountInGenerated+"\n") + out.write("}\n") + } + + /** Write the Base 64 VLQ of an integer to the mappings + * Inspired by the implementation in Closure Compiler: + * http://code.google.com/p/closure-compiler/source/browse/src/com/google/debugging/sourcemap/Base64VLQ.java + */ + private def writeBase64VLQ(value0: Int): Unit = { + // Sign is encoded in the least significant bit + var value = + if (value0 < 0) ((-value0) << 1) + 1 + else value0 << 1 + + // Write as many base-64 digits as necessary to encode value + do { + var digit = value & VLQBaseMask + value = value >>> VLQBaseShift + if (value != 0) + digit |= VLQContinuationBit + out.write(Base64Map.charAt(digit)) + } while (value != 0) + } + + private def writeBase64VLQ0(): Unit = + out.write('A') +} |