diff --git a/COMPILING.md b/COMPILING.md index 40f2b96c..68c2ea1e 100644 --- a/COMPILING.md +++ b/COMPILING.md @@ -50,7 +50,10 @@ Test suite is useful if you plan on modifying the compiler. Some test dependenci #### Prerequisites -* JDK 1.8 with Nashorn (tests don't work on newer versions) +* JDK 1.8 or later + + * Millfork up to version 0.3.22 used to require exactly JDK 1.8 with Nashorn, + as the tests didn't work on newer versions * sbt diff --git a/build.sbt b/build.sbt index 6fb93b1d..891447eb 100644 --- a/build.sbt +++ b/build.sbt @@ -32,6 +32,12 @@ libraryDependencies += "eu.rekawek.coffeegb" % "coffee-gb" % "1.0.0" % "test" libraryDependencies += "roug.org.osnine" % "osnine-core" % "2.0-SNAPSHOT" % "test" +libraryDependencies += "org.graalvm.sdk" % "graal-sdk" % "20.2.0" % "test" + +libraryDependencies += "org.graalvm.js" % "js" % "20.2.0" % "test" + +libraryDependencies += "org.graalvm.js" % "js-scriptengine" % "20.2.0" % "test" + mainClass in Compile := Some("millfork.Main") assemblyJarName := "millfork.jar" diff --git a/src/test/scala/millfork/test/emu/NashornEmulator.scala b/src/test/scala/millfork/test/emu/NashornEmulator.scala index 86e4ab0e..659f83c0 100644 --- a/src/test/scala/millfork/test/emu/NashornEmulator.scala +++ b/src/test/scala/millfork/test/emu/NashornEmulator.scala @@ -1,27 +1,27 @@ package millfork.test.emu -import javax.script.{Bindings, ScriptEngine, ScriptEngineManager} -import java.io.FileReader -import java.nio.file.Paths +import java.nio.file.{Files, Paths} -import jdk.nashorn.api.scripting.ScriptObjectMirror +import org.graalvm.polyglot.{Context, Value} import scala.language.dynamics /** + * Despite its name, it no longer uses Nashorn, but GraalJS instead. * @author Karol Stasiak */ object NashornEmulator { - lazy val engine: ScriptEngine = { - val jsFile = Paths.get(classOf[Nothing].getResource("/cpu.js").toURI).toFile - val engine: ScriptEngine = new ScriptEngineManager().getEngineByName("nashorn") - engine.eval(new FileReader(jsFile)) - engine + lazy val engine: Context = { + val jsFile = Paths.get(classOf[Nothing].getResource("/cpu.js").toURI) + import org.graalvm.polyglot.Context + val polyglot = Context.create() + polyglot.eval("js", new String(Files.readAllBytes(jsFile))) + polyglot } private def newCpu(): JsObject = { - JsObject(engine.get("CPU_65816").asInstanceOf[ScriptObjectMirror]).construct() + JsObject(engine.eval("js", "CPU_65816")).construct() } def run(memory: Array[Byte], steps: Long, start: Int): (Long, Array[Byte]) = { @@ -32,14 +32,15 @@ object NashornEmulator { val memory0 = cpu.mmu.memory.!("0") for (i <- 0 until 1 << 16) memory0.!(i.toString, memory(i) & 0xff) cpu.r.pc = start + cpu.r.sp = 0x1ff cpu.r.k = 0 var count = 0L - while (count < steps && cpu.r.s.*[Number].intValue().&(0xff) > 1) { + while (count < steps && cpu.r.s.toInt.&(0xff) > 1) { cpu.step() count += 1 } - val newMemory = (0 until 1 << 16).map(i => memory0.!(i.toString).*[Number].byteValue()).toArray - val cycles = cpu.cycle_count.*[Number].longValue() + val newMemory = (0 until 1 << 16).map(i => memory0.!(i.toString).toByte).toArray + val cycles = cpu.cycle_count.toLong cycles -> newMemory } @@ -52,60 +53,74 @@ object NashornEmulator { } } -case class JsObject(private val mirror: Any) extends Dynamic { +case class JsObject(private val mirror: Value) extends Dynamic { + def !(index: Long): JsObject = { + if (mirror.hasArrayElements) return JsObject(mirror.getArrayElement(index)) + else throw new IllegalArgumentException(s"Accessing index $index of $getUnderlyingClass") + } - def !(index: JsObject): JsObject = - mirror match { - case x: ScriptObjectMirror => JsObject(x.get(index.mirror)) - case _ => throw new IllegalArgumentException(s"Accessing field $index of $getUnderlyingClass") - } + def !(index: String): JsObject = { + if (mirror.hasMembers) return JsObject(mirror.getMember(index)) + else throw new IllegalArgumentException(s"Accessing field $index of $getUnderlyingClass") + } - def !(index: Any): JsObject = - mirror match { - case x: ScriptObjectMirror => JsObject(x.get(index)) - case _ => throw new IllegalArgumentException(s"Accessing field $index of $getUnderlyingClass") - } + def !(index: String, value: JsObject): Unit = { + if (mirror.hasMembers) mirror.putMember(index, value.mirror) + else throw new IllegalArgumentException(s"Setting field $index of $getUnderlyingClass") + } - def !(index: String, value:Any): Unit = - mirror match { - case x: ScriptObjectMirror => x.setMember(index, value) - case _ => throw new IllegalArgumentException(s"Setting field $index of $getUnderlyingClass") - } + def !(index: String, value:Any): Unit = { + if (mirror.hasMembers) mirror.putMember(index, value) + else throw new IllegalArgumentException(s"Setting field $index of $getUnderlyingClass") + } - def selectDynamic(name: String): JsObject = - mirror match { - case x: ScriptObjectMirror => JsObject(x.get(name).asInstanceOf[Any] match { - case y: Double => if (y.isValidInt) y.toInt else y - case y => y - }) - case _ => throw new IllegalArgumentException(s"Accessing field $name of $getUnderlyingClass") - } + def !(index: Long, value: JsObject): Unit = { + if (mirror.hasArrayElements) mirror.setArrayElement(index, value.mirror) + else throw new IllegalArgumentException(s"Setting field $index of $getUnderlyingClass") + } + + def !(index: Long, value:Any): Unit = { + if (mirror.hasArrayElements) mirror.setArrayElement(index, value) + else throw new IllegalArgumentException(s"Setting field $index of $getUnderlyingClass") + } + + def selectDynamic(name: String): JsObject = { + if (mirror.hasMembers) return JsObject(mirror.getMember(name)) + else throw new IllegalArgumentException(s"Accessing field $name of $getUnderlyingClass") + } private def getUnderlyingClass = { if (mirror.asInstanceOf[AnyRef] eq null) "null" else mirror.getClass.getSimpleName } - def updateDynamic(name: String)(value: Any): Unit = - mirror match { - case x: ScriptObjectMirror => x.setMember(name, value) - case _ => throw new IllegalArgumentException(s"Setting field $name of $getUnderlyingClass") + def updateDynamic(name: String)(value: Any): Unit = { + if (mirror.hasMembers) { + value match { + case o:JsObject => + mirror.putMember(name, o.mirror) + case _ => + mirror.putMember(name, value) + } } + else throw new IllegalArgumentException(s"Setting field $name of $getUnderlyingClass") + } - def applyDynamic(name: String)(params: Any*): JsObject = - mirror match { - case x: ScriptObjectMirror => JsObject(x.callMember(name, params.toArray)) - case _ => throw new IllegalArgumentException(s"Accessing field $name of $getUnderlyingClass") - } + def applyDynamic(name: String)(params: Any*): JsObject = { + if (mirror.canInvokeMember(name)) JsObject(mirror.invokeMember(name, params.toArray)) + else throw new IllegalArgumentException(s"Invoking method $name of $getUnderlyingClass") + } - def construct(params: Any*): JsObject = - mirror match { - case x: ScriptObjectMirror => JsObject(x.newObject(params.toArray)) - case _ => throw new IllegalArgumentException(s"Using $getUnderlyingClass as a constructor") - } + def construct(params: Any*): JsObject = { + if (mirror.canInstantiate) JsObject(mirror.newInstance(params.toArray)) + else throw new IllegalArgumentException(s"Using $getUnderlyingClass as a constructor") + } - @inline - def *[T]: T = mirror.asInstanceOf[T] + def toInt: Int = mirror.asInt() + + def toLong: Long = mirror.asLong() + + def toByte: Byte = mirror.asInt().toByte override def toString: String = String.valueOf(mirror) }