All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.sciss.proc.impl.CodeImpl.scala Maven / Gradle / Ivy

/*
 *  CodeImpl.scala
 *  (SoundProcesses)
 *
 *  Copyright (c) 2010-2024 Hanns Holger Rutz. All rights reserved.
 *
 *	This software is published under the GNU Affero General Public License v3+
 *
 *
 *  For further information, please contact Hanns Holger Rutz at
 *  [email protected]
 */

package de.sciss.proc.impl

import java.io.{ByteArrayInputStream, ByteArrayOutputStream}
import de.sciss.serial.{ConstFormat, DataInput, DataOutput}
import de.sciss.proc.Code
import de.sciss.proc.Code.Import
import de.sciss.proc.legacy.ActionRaw

import scala.collection.immutable.{IndexedSeq => Vec, Seq => ISeq}
import scala.concurrent.{Future, blocking}

object CodeImpl {
  private final val COOKIE        = 0x436F6465  // "Code"
  private final val COOKIE_EMPTY  = 0x436F6400

  // ---- type ----

  @volatile private var map = Map.empty[Int, Code.Type]

  def addType(tpe: Code.Type): Unit = sync.synchronized {
    val typeId = tpe.id
    if (map.contains(typeId))
      throw new IllegalArgumentException(s"Code type $typeId was already registered ($tpe overrides ${map(typeId)})")

    map += typeId -> tpe
  }

  def getType(id: Int): Code.Type = map.getOrElse(id, sys.error(s"Unknown element type $id"))

  def apply(id: Int, source: String): Code = getType(id).mkCode(source)

  def types: ISeq[Code.Type] = (map - ActionRaw.Code.id).valuesIterator.toList.sortBy(_.humanName)

  // ----

  def unpackJar(bytes: Array[Byte]): Map[String, Array[Byte]] = {
    // cf. http://stackoverflow.com/questions/8909743/
    import java.util.jar._

    val in = new JarInputStream(new ByteArrayInputStream(bytes))
    val b  = Map.newBuilder[String, Array[Byte]]

    while ({
      val entry = in.getNextJarEntry
      (entry != null) && {
        if (!entry.isDirectory) {
          val name = entry.getName
          val bs  = new ByteArrayOutputStream
          val arr = new Array[Byte](1024)
          while ({
            val sz = in.read(arr, 0, 1024)
            (sz > 0) && { bs.write(arr, 0, sz); true }
          }) ()
          val bytes = bs.toByteArray
          b += mkClassName(name) -> bytes
        }
        true
      }
    }) ()

    in.close()
    b.result()
  }

  /* Converts a jar entry name with slashes to a class name with dots
   * and dropping the `class` extension
   */
  private def mkClassName(path: String): String = {
    require(path.endsWith(".class"))
    path.substring(0, path.length - 6).replace("/", ".")
  }

  implicit object format extends ConstFormat[Code] {
    def write(v: Code, out: DataOutput): Unit =
      if (v == null) {
        out.writeInt(COOKIE_EMPTY)
      } else {
        out.writeInt(COOKIE)
        out.writeInt(v.tpe.id)
        out.writeUTF(v.source)
      }

    def read(in: DataInput): Code = {
      val cookie = in.readInt()
      if (cookie != COOKIE) {
        if (cookie == COOKIE_EMPTY) null
        else sys.error(s"Unexpected cookie $cookie (requires $COOKIE)")
      } else {
        val id      = in.readInt()
        val source  = in.readUTF()
        Code.apply(id, source)
      }
    }
  }

  //  // note: the Scala compiler is _not_ reentrant!!
  //  private implicit val executionContext: ExecutionContextExecutor =
  //    ExecutionContext.fromExecutor(Executors.newSingleThreadExecutor())

  def future[A](fun: => A)(implicit compiler: Code.Compiler): Future[A] =
    concurrent.Future(fun)(compiler.executionContext)

  // ---- imports ----

  private val sync = new AnyRef

  import Import._

  private val exCtlActImports = Vec(     // what should go inside?
    Import("de.sciss.numbers.Implicits"   , All),
    Import("de.sciss.proc.ExImport"       , All),
    Import("de.sciss.lucre.expr.graph"    , All)
  )

  private var importsMap = Map[Int, Vec[Import]](
    Code.Proc.id -> Vec(
      Import("de.sciss.synth", List(Ignore("Buffer"), Wildcard)),
      Import("de.sciss.synth.Import", All),
      Import("de.sciss.synth.ugen", List(Ignore("DiskIn"), Ignore("DiskOut"), Ignore("VDiskIn"), Ignore("PartConv"),
        Ignore("BufChannels"), Ignore("BufRateScale"), Ignore("BufSampleRate"), Wildcard)),
      Import("de.sciss.synth.proc.graph", All),
      Import("de.sciss.synth.proc.graph.Ops", All)
    ),
    Code.Control.id -> exCtlActImports,
    Code.Action .id -> exCtlActImports,
  ) ++ Code.Program.seq.map { tpe =>
    tpe.id -> exCtlActImports
  }

  def registerImports(id: Int, imports: Seq[Import]): Unit = sync.synchronized {
    importsMap += id -> importsMap.get(id).fold(imports.toIndexedSeq)(_ ++ imports)
  }

  def getImports(id: Int): Vec[Import] = importsMap(id)

  // ---- internals ----

//  final def execute[I, O, A, Repr <: Code { type In = I; type Out = O }](code: Repr, in: I)
//                                                                     (implicit w: Wrapper[I, O, A, Repr],
//                                                                      compiler: Code.Compiler): O = {
//    w.wrap(in) {
//      compileThunk(code, w, execute = true)
//    }
//  }

  def compileBody[I, O, A, Repr <: Code.T[I, O]](code: Repr, resName: String)
                                                (implicit compiler: Code.Compiler): Future[Unit] =
    future {
      blocking {
        compileThunk[A](code, resName = resName, execute = false)
      }
      ()
    }

  /** Compiles a source code consisting of a body which is wrapped in its prelude/postlude,
    * and returns the raw jar file produced in the compilation.
    *
    * @param  name  Note: it is currently unused
    */
  def compileToJar(name: String, code: Code, prelude: String, postlude: String)
                  (implicit compiler: Code.Compiler): Array[Byte] = {
    val impS    = importsPrelude(code, indent = 2)
    val source  =
      s"${Code.packagePrelude}$impS$prelude${code.source}$postlude"

    // println(source)

    compiler.compile(source)
  }

  object Run {
    def apply[A](execute: Boolean)(thunk: => A): A = if (execute) thunk else null.asInstanceOf[A]
  }

  private val pkgCode = "de.sciss.proc.impl.CodeImpl"

  def importsPrelude(code: Code, indent: Int = 0): String =
    importsMap(code.tpe.id).iterator.map(i => s"${"  " * indent}${i.sourceString}\n").mkString

  def typeName[A](cl: Class[A]): String = {
    val isUnit = cl == classOf[Unit]
    if (isUnit) "Unit" else {
      val n0 = cl.getName
      val tp = cl.getTypeParameters.length
      if (tp == 0) n0 else Seq.fill(tp)("_").mkString(s"$n0[", "_", "]")
    }
  }

  // note: synchronous.
  def compileThunk[A](code: Code, resName: String, execute: Boolean)
                     (implicit compiler: Code.Compiler): A = {
    val impS    = importsPrelude(code, indent = 1)
//    val aTpe    = tt.tpe.toString // not `clazz.getName` which gives `void` instead of `Unit`!
    val isUnit  = resName == "Unit"
//    val resName = typeName(resCl)
    val synth =
      s"""$pkgCode.Run[$resName]($execute) {
        |$impS
        |
        |""".stripMargin + code.source + "\n}"

    val res: Any = compiler.interpret(synth, print = false, execute = execute && !isUnit)
    res.asInstanceOf[A]
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy