diff options
Diffstat (limited to 'library/src/main/scala/scala/scalajs/runtime')
10 files changed, 2369 insertions, 0 deletions
diff --git a/library/src/main/scala/scala/scalajs/runtime/AnonFunctions.scala b/library/src/main/scala/scala/scalajs/runtime/AnonFunctions.scala new file mode 100644 index 0000000..861d81a --- /dev/null +++ b/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/library/src/main/scala/scala/scalajs/runtime/Bits.scala b/library/src/main/scala/scala/scalajs/runtime/Bits.scala new file mode 100644 index 0000000..38b2c3e --- /dev/null +++ b/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/library/src/main/scala/scala/scalajs/runtime/BooleanReflectiveCall.scala b/library/src/main/scala/scala/scalajs/runtime/BooleanReflectiveCall.scala new file mode 100644 index 0000000..0cd562a --- /dev/null +++ b/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/library/src/main/scala/scala/scalajs/runtime/IntegerReflectiveCall.scala b/library/src/main/scala/scala/scalajs/runtime/IntegerReflectiveCall.scala new file mode 100644 index 0000000..ddf65df --- /dev/null +++ b/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/library/src/main/scala/scala/scalajs/runtime/NumberReflectiveCall.scala b/library/src/main/scala/scala/scalajs/runtime/NumberReflectiveCall.scala new file mode 100644 index 0000000..a237861 --- /dev/null +++ b/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/library/src/main/scala/scala/scalajs/runtime/RuntimeLong.scala b/library/src/main/scala/scala/scalajs/runtime/RuntimeLong.scala new file mode 100644 index 0000000..3bd6fb6 --- /dev/null +++ b/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/library/src/main/scala/scala/scalajs/runtime/RuntimeString.scala b/library/src/main/scala/scala/scalajs/runtime/RuntimeString.scala new file mode 100644 index 0000000..f65b1b5 --- /dev/null +++ b/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/library/src/main/scala/scala/scalajs/runtime/StackTrace.scala b/library/src/main/scala/scala/scalajs/runtime/StackTrace.scala new file mode 100644 index 0000000..a9e2c00 --- /dev/null +++ b/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/library/src/main/scala/scala/scalajs/runtime/UndefinedBehaviorError.scala b/library/src/main/scala/scala/scalajs/runtime/UndefinedBehaviorError.scala new file mode 100644 index 0000000..b06ed7d --- /dev/null +++ b/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/library/src/main/scala/scala/scalajs/runtime/package.scala b/library/src/main/scala/scala/scalajs/runtime/package.scala new file mode 100644 index 0000000..59c774c --- /dev/null +++ b/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 + } + } + +} |