├── project ├── build.properties ├── plugins.sbt └── Build.scala ├── lib └── src │ └── main │ └── scala │ ├── package.scala │ ├── Data.scala │ ├── IScala.scala │ ├── MIME.scala │ ├── Image.scala │ ├── DisplayFunctions.scala │ ├── DisplayObject.scala │ ├── Objects.scala │ ├── Repr.scala │ ├── Enums.scala │ └── Display.scala ├── src ├── main │ ├── scala │ │ ├── package.scala │ │ ├── Types.scala │ │ ├── Completion.scala │ │ ├── Session.scala │ │ ├── Results.scala │ │ ├── Connection.scala │ │ ├── Sockets.scala │ │ ├── Builtins.scala │ │ ├── HMAC.scala │ │ ├── UUID.scala │ │ ├── Util.scala │ │ ├── Runner.scala │ │ ├── Sbt.scala │ │ ├── DB.scala │ │ ├── Capture.scala │ │ ├── Communication.scala │ │ ├── Options.scala │ │ ├── IScala.scala │ │ ├── Magics.scala │ │ ├── Handlers.scala │ │ ├── Json.scala │ │ ├── Interpreter.scala │ │ └── Msg.scala │ ├── scala_2.11 │ │ └── Compatibility.scala │ ├── java │ │ └── Threading.java │ └── scala_2.10 │ │ └── Compatibility.scala └── test │ └── scala │ ├── Notebooks.scala │ ├── utils │ ├── InterpreterUtil.scala │ └── NotebookUtil.scala │ ├── Json.scala │ └── Interpreter.scala ├── .gitignore ├── .travis.yml ├── core └── src │ └── main │ └── scala │ ├── Utils.scala │ ├── Core.scala │ ├── Enum.scala │ └── JsMacroImpl.scala ├── LICENSE ├── sbt ├── examples └── Display.ipynb └── README.md /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.6 2 | sbt.launcher.md5=9371cbb34dd1d1e8e7233d9140cef22e 3 | -------------------------------------------------------------------------------- /lib/src/main/scala/package.scala: -------------------------------------------------------------------------------- 1 | package org.refptr.iscala 2 | 3 | package object display extends DisplayFunctions 4 | -------------------------------------------------------------------------------- /src/main/scala/package.scala: -------------------------------------------------------------------------------- 1 | package org.refptr 2 | 3 | package object iscala { 4 | val logger = sbt.ConsoleLogger() 5 | } 6 | -------------------------------------------------------------------------------- /src/main/scala/Types.scala: -------------------------------------------------------------------------------- 1 | package org.refptr.iscala 2 | 3 | package object msg { 4 | type Metadata = Map[String, String] 5 | val Metadata = Map 6 | } 7 | -------------------------------------------------------------------------------- /src/main/scala_2.11/Compatibility.scala: -------------------------------------------------------------------------------- 1 | package org.refptr.iscala 2 | 3 | trait Compatibility 4 | trait InterpreterCompatibility extends Compatibility { self: Interpreter => } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.sw[opn] 2 | *~ 3 | 4 | target/ 5 | /project/sbt-launch-*.jar 6 | 7 | /.idea/ 8 | /.idea_modules/ 9 | 10 | /bin/ 11 | /profile*.json 12 | 13 | Untitled*.ipynb 14 | .ipynb_checkpoints/ 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: scala 2 | 3 | scala: 4 | - 2.10.4 5 | - 2.11.2 6 | 7 | jdk: 8 | - openjdk6 9 | - oraclejdk7 10 | - oraclejdk8 11 | 12 | script: ./sbt ++$TRAVIS_SCALA_VERSION compile test 13 | -------------------------------------------------------------------------------- /core/src/main/scala/Utils.scala: -------------------------------------------------------------------------------- 1 | package org.refptr.iscala 2 | 3 | object Utils { 4 | def snakify(name: String): String = 5 | name.replaceAll("([A-Z]+)([A-Z][a-z])", "$1_$2") 6 | .replaceAll("([a-z\\d])([A-Z])", "$1_$2") 7 | .toLowerCase 8 | } 9 | -------------------------------------------------------------------------------- /lib/src/main/scala/Data.scala: -------------------------------------------------------------------------------- 1 | package org.refptr.iscala 2 | 3 | import display.MIME 4 | 5 | case class Data(items: (MIME, String)*) { 6 | def apply(mime: MIME): Option[String] = items.find(_._1 == mime).map(_._2) 7 | 8 | def default: Option[String] = apply(MIME.`text/plain`) 9 | } 10 | -------------------------------------------------------------------------------- /src/main/scala/Completion.scala: -------------------------------------------------------------------------------- 1 | package org.refptr.iscala 2 | 3 | import scala.tools.nsc.interpreter.{IMain,Parsed,JLineCompletion} 4 | 5 | class IScalaCompletion(intp: IMain) extends JLineCompletion(intp) { 6 | def collectCompletions(input: String): List[String] = { 7 | topLevelFor(Parsed.dotted(input, input.length)) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.github.mpeltonen" % "sbt-idea" % "1.5.1") 2 | 3 | addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.9.1") 4 | 5 | addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "0.8.0-M1") 6 | 7 | scalacOptions ++= Seq("-deprecation", "-unchecked", "-feature", "-language:_") 8 | 9 | libraryDependencies += "com.typesafe.play" %% "play-json" % "2.4.0-M1" 10 | -------------------------------------------------------------------------------- /src/main/java/Threading.java: -------------------------------------------------------------------------------- 1 | package org.refptr.iscala; 2 | 3 | // XXX: This is a hack to silence deperection warning on Thread.stop() 4 | // in src/main/scala/Interpreter.scala. javac will still complain, but 5 | // we aren't going to change this any more and scalc will remain silent. 6 | class Threading { 7 | final static void stop(Thread thread) { 8 | thread.stop(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/test/scala/Notebooks.scala: -------------------------------------------------------------------------------- 1 | package org.refptr.iscala 2 | package tests 3 | 4 | import java.io.File 5 | import org.specs2.mutable.Specification 6 | 7 | class NotebooksSpec extends Specification with NotebookUtil { 8 | sequential 9 | 10 | "IScala" should { 11 | "interpret Display.ipynb" in { 12 | new File("examples/Display.ipynb") must beInterpretable 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/scala/Session.scala: -------------------------------------------------------------------------------- 1 | package org.refptr.iscala 2 | 3 | class Session { 4 | lazy val id: Int = DB.newSession() 5 | 6 | def endSession(num_cmds: Int) { 7 | DB.endSession(id)(num_cmds) 8 | } 9 | 10 | def addHistory(line: Int, source: String) { 11 | DB.addHistory(id)(line, source) 12 | } 13 | 14 | def addOutputHistory(line: Int, output: String) { 15 | DB.addOutputHistory(id)(line, output) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /core/src/main/scala/Core.scala: -------------------------------------------------------------------------------- 1 | package org.refptr.iscala 2 | 3 | import scala.reflect.macros.Context 4 | 5 | object Core { 6 | def implicitlyOptImpl[T: c.WeakTypeTag](c: Context): c.Expr[Option[T]] = { 7 | import c.universe._ 8 | 9 | c.inferImplicitValue(weakTypeOf[T]) match { 10 | case EmptyTree => reify { None } 11 | case impl => reify { Some(c.Expr[T](impl).splice) } 12 | } 13 | } 14 | 15 | def implicitlyOpt[T]: Option[T] = macro Core.implicitlyOptImpl[T] 16 | } 17 | -------------------------------------------------------------------------------- /lib/src/main/scala/IScala.scala: -------------------------------------------------------------------------------- 1 | package org.refptr.iscala 2 | package display 3 | 4 | private[iscala] trait Conn { 5 | def display_data(data: Data): Unit 6 | } 7 | 8 | object IScala { 9 | private[iscala] def withConn[T](conn: Conn)(block: => T): T = { 10 | _conn = Some(conn) 11 | try { 12 | block 13 | } finally { 14 | _conn = None 15 | } 16 | } 17 | 18 | private var _conn: Option[Conn] = None 19 | private def conn: Conn = _conn getOrElse sys.error("Not in IScala") 20 | 21 | def display_data(data: Data) { 22 | conn.display_data(data) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/scala/Results.scala: -------------------------------------------------------------------------------- 1 | package org.refptr.iscala 2 | 3 | object Results { 4 | sealed trait Result 5 | sealed trait Success extends Result 6 | sealed trait Failure extends Result 7 | 8 | final case class Value(value: Any, tpe: String, repr: Data) extends Success 9 | final case object NoValue extends Success 10 | 11 | final case class Exception(name: String, msg: String, stacktrace: List[String], exception: Throwable) extends Failure { 12 | def traceback = s"$name: $msg" :: stacktrace.map(" " + _) 13 | } 14 | final case object Error extends Failure 15 | final case object Incomplete extends Failure 16 | final case object Cancelled extends Failure 17 | } 18 | -------------------------------------------------------------------------------- /lib/src/main/scala/MIME.scala: -------------------------------------------------------------------------------- 1 | package org.refptr.iscala 2 | package display 3 | 4 | sealed abstract class MIME(val name: String) 5 | object MIME { 6 | case object `text/plain` extends MIME("text/plain") 7 | case object `text/html` extends MIME("text/html") 8 | case object `text/markdown` extends MIME("text/markdown") 9 | case object `text/latex` extends MIME("text/latex") 10 | case object `application/json` extends MIME("application/json") 11 | case object `application/javascript` extends MIME("application/javascript") 12 | case object `image/png` extends MIME("image/png") 13 | case object `image/jpeg` extends MIME("image/jpeg") 14 | case object `image/svg+xml` extends MIME("image/svg+xml") 15 | } 16 | -------------------------------------------------------------------------------- /lib/src/main/scala/Image.scala: -------------------------------------------------------------------------------- 1 | package org.refptr.iscala 2 | package display 3 | 4 | import java.io.ByteArrayOutputStream 5 | import java.awt.image.RenderedImage 6 | import javax.imageio.ImageIO 7 | 8 | import org.apache.commons.codec.binary.Base64 9 | 10 | trait ImageImplicits { 11 | private def encodeImage(format: String)(image: RenderedImage): String = { 12 | val output = new ByteArrayOutputStream() 13 | val bytes = try { 14 | ImageIO.write(image, format, output) 15 | output.toByteArray 16 | } finally { 17 | output.close() 18 | } 19 | Base64.encodeBase64String(bytes) 20 | } 21 | 22 | implicit val PNGDisplayRenderedImage = PNGDisplay[RenderedImage](encodeImage("PNG") _) 23 | } 24 | -------------------------------------------------------------------------------- /src/main/scala/Connection.scala: -------------------------------------------------------------------------------- 1 | package org.refptr.iscala 2 | 3 | import org.refptr.iscala.json.{Json,JsonImplicits} 4 | 5 | case class Connection( 6 | ip: String, 7 | transport: String, 8 | stdin_port: Int, 9 | control_port: Int, 10 | hb_port: Int, 11 | shell_port: Int, 12 | iopub_port: Int, 13 | key: String, 14 | signature_scheme: Option[String]) 15 | 16 | object Connection { 17 | import JsonImplicits._ 18 | implicit val ConnectionJSON = Json.format[Connection] 19 | 20 | lazy val default = { 21 | val port0 = 5678 22 | Connection(ip="127.0.0.1", 23 | transport="tcp", 24 | stdin_port=port0, 25 | control_port=port0+1, 26 | hb_port=port0+2, 27 | shell_port=port0+3, 28 | iopub_port=port0+4, 29 | key=UUID.uuid4().toString, 30 | signature_scheme=None) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/scala/Sockets.scala: -------------------------------------------------------------------------------- 1 | package org.refptr.iscala 2 | 3 | import org.zeromq.ZMQ 4 | 5 | class Sockets(connection: Connection) { 6 | val ctx = ZMQ.context(1) 7 | 8 | val publish = ctx.socket(ZMQ.PUB) 9 | val requests = ctx.socket(ZMQ.ROUTER) 10 | val control = ctx.socket(ZMQ.ROUTER) 11 | val stdin = ctx.socket(ZMQ.ROUTER) 12 | val heartbeat = ctx.socket(ZMQ.REP) 13 | 14 | private def toURI(port: Int) = 15 | s"${connection.transport}://${connection.ip}:$port" 16 | 17 | publish.bind(toURI(connection.iopub_port)) 18 | requests.bind(toURI(connection.shell_port)) 19 | control.bind(toURI(connection.control_port)) 20 | stdin.bind(toURI(connection.stdin_port)) 21 | heartbeat.bind(toURI(connection.hb_port)) 22 | 23 | def terminate() { 24 | publish.close() 25 | requests.close() 26 | control.close() 27 | stdin.close() 28 | heartbeat.close() 29 | 30 | ctx.term() 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/scala/Builtins.scala: -------------------------------------------------------------------------------- 1 | package org.refptr.iscala 2 | 3 | import msg.{Msg,MsgType,input_reply} 4 | 5 | class Builtins(interpreter: Interpreter, ipy: Communication, msg: Msg[_]) { 6 | 7 | def raw_input(): String = { 8 | // TODO: drop stale replies 9 | // TODO: prompt 10 | ipy.send_stdin(msg, "") 11 | ipy.recv_stdin().collect { 12 | case msg if msg.header.msg_type == MsgType.input_reply => 13 | msg.asInstanceOf[Msg[input_reply]] 14 | } map { 15 | _.content.value match { 16 | case "\u0004" => throw new java.io.EOFException() 17 | case value => value 18 | } 19 | } getOrElse "" 20 | } 21 | 22 | val bindings = List( 23 | ("raw_input", "() => String", raw_input _)) 24 | 25 | interpreter.intp.beSilentDuring { 26 | bindings.foreach { case (name, tpe, value) => 27 | interpreter.intp.bind(name, tpe, value) 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/scala/HMAC.scala: -------------------------------------------------------------------------------- 1 | package org.refptr.iscala 2 | 3 | import javax.crypto.Mac 4 | import javax.crypto.spec.SecretKeySpec 5 | 6 | object HMAC { 7 | def apply(key: String, algorithm: Option[String]=None): HMAC = 8 | if (key.isEmpty) NoHMAC else new DoHMAC(key) 9 | } 10 | 11 | sealed trait HMAC { 12 | def hexdigest(args: Seq[String]): String 13 | 14 | final def apply(args: String*) = hexdigest(args) 15 | } 16 | 17 | final class DoHMAC(key: String, algorithm: Option[String]=None) extends HMAC { 18 | private val _algorithm = algorithm getOrElse "hmac-sha256" replace ("-", "") 19 | private val mac = Mac.getInstance(_algorithm) 20 | private val keySpec = new SecretKeySpec(key.getBytes, _algorithm) 21 | mac.init(keySpec) 22 | 23 | def hexdigest(args: Seq[String]): String = { 24 | mac synchronized { 25 | args.map(_.getBytes).foreach(mac.update) 26 | Util.hex(mac.doFinal()) 27 | } 28 | } 29 | } 30 | 31 | object NoHMAC extends HMAC { 32 | def hexdigest(args: Seq[String]): String = "" 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-2014 by Mateusz Paprocki and contributors. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /src/main/scala/UUID.scala: -------------------------------------------------------------------------------- 1 | package org.refptr.iscala 2 | 3 | class UUID private (uuid: java.util.UUID, dashes: Boolean=true, upper: Boolean=false) { 4 | override def toString: String = { 5 | val repr0 = uuid.toString 6 | val repr1 = if (dashes) repr0 else repr0.replace("-", "") 7 | val repr2 = if (upper) repr1.toUpperCase else repr1 8 | repr2 9 | } 10 | } 11 | 12 | object UUID { 13 | def fromString(uuid: String): Option[UUID] = { 14 | val (actualUuid, dashes) = 15 | if (uuid.contains("-")) (uuid, true) 16 | else (List(uuid.slice( 0, 8), 17 | uuid.slice( 8, 12), 18 | uuid.slice(12, 16), 19 | uuid.slice(16, 20), 20 | uuid.slice(20, 32)).mkString("-"), false) 21 | 22 | val upper = uuid.exists("ABCDF" contains _) 23 | 24 | try { 25 | Some(new UUID(java.util.UUID.fromString(actualUuid), dashes, upper)) 26 | } catch { 27 | case e: java.lang.IllegalArgumentException => 28 | None 29 | } 30 | } 31 | 32 | def uuid4(): UUID = new UUID(java.util.UUID.randomUUID) 33 | } 34 | -------------------------------------------------------------------------------- /src/test/scala/utils/InterpreterUtil.scala: -------------------------------------------------------------------------------- 1 | package org.refptr.iscala 2 | package tests 3 | 4 | trait InterpreterUtil { 5 | object Plain { 6 | def unapply(data: Data): Option[String] = data match { 7 | case Data((display.MIME.`text/plain`, output)) => Some(output) 8 | case _ => None 9 | } 10 | } 11 | 12 | object NoOutput { 13 | def unapply[T](output: Output[T]): Option[T] = output match { 14 | case Output(value, "", "") => Some(value) 15 | case _ => None 16 | } 17 | } 18 | 19 | // XXX: if (fork) ("", true) else (sys.props("java.class.path"), false) 20 | protected val intp = new Interpreter(sys.props("java.class.path"), Nil, false) 21 | 22 | def interpret(code: String): Output[Results.Result] = { 23 | Capture.captureOutput { 24 | code match { 25 | case Magic(name, input, Some(magic)) => 26 | magic(intp, input) 27 | case Magic(name, _, None) => 28 | Results.Error // s"ERROR: Line magic function `%$name` not found." 29 | case "" => 30 | Results.NoValue 31 | case _ => 32 | intp.interpret(code) 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/scala_2.10/Compatibility.scala: -------------------------------------------------------------------------------- 1 | package org.refptr.iscala 2 | 3 | import scala.reflect.runtime.universe 4 | 5 | import scala.tools.nsc.Global 6 | import scala.tools.nsc.interpreter.IMain 7 | 8 | trait Compatibility { 9 | implicit class UniverseOps(u: universe.type) { 10 | def TermName(s: String): universe.TermName = u.newTermName(s) 11 | } 12 | 13 | implicit class GlobalOps(global: Global) { 14 | @inline final def exitingTyper[T](op: => T): T = global.afterTyper(op) 15 | } 16 | } 17 | 18 | trait InterpreterCompatibility extends Compatibility { self: Interpreter => 19 | import intp.Request 20 | import intp.global.{nme,newTermName,afterTyper,Name,Symbol} 21 | 22 | implicit class IMainOps(imain: intp.type) { 23 | def originalPath(name: Name): String = imain.pathToName(name) 24 | def originalPath(symbol: Symbol): String = backticked(afterTyper(symbol.fullName)) 25 | 26 | def backticked(s: String): String = ( 27 | (s split '.').toList map { 28 | case "_" => "_" 29 | case s if nme.keywords(newTermName(s)) => s"`$s`" 30 | case s => s 31 | } mkString "." 32 | ) 33 | } 34 | 35 | implicit class RequestOps(req: Request) { 36 | def value: Symbol = 37 | Some(req.handlers.last) 38 | .filter(_.definesValue) 39 | .map(handler => req.definedSymbols(handler.definesTerm.get)) 40 | .getOrElse(intp.global.NoSymbol) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/test/scala/utils/NotebookUtil.scala: -------------------------------------------------------------------------------- 1 | package org.refptr.iscala 2 | package tests 3 | 4 | import java.io.File 5 | import scala.io.Source 6 | 7 | import play.api.libs.json.Json 8 | import org.specs2.matcher.{Matcher,Expectable} 9 | 10 | trait NotebookUtil extends InterpreterUtil { 11 | case class Notebook(nbformat: Int, nbformat_minor: Int, worksheets: List[Worksheet]) 12 | case class Worksheet(cells: List[CodeCell]) 13 | case class CodeCell(input: List[String], language: String, collapsed: Boolean) { 14 | def code: String = input.mkString 15 | } 16 | 17 | implicit val CodeCellReads = Json.reads[CodeCell] 18 | implicit val WorksheetReads = Json.reads[Worksheet] 19 | implicit val NotebookReads = Json.reads[Notebook] 20 | 21 | def loadNotebook(file: File): Notebook = { 22 | Json.parse(Source.fromFile(file).mkString).as[Notebook] 23 | } 24 | 25 | object beInterpretable extends Matcher[File] { 26 | def apply[S <: File](s: Expectable[S]) = { 27 | val notebook = loadNotebook(s.value) 28 | 29 | val outcome = notebook.worksheets.flatMap(_.cells.map(_.code)).collectFirst { code => 30 | interpret(code) match { 31 | case Output(result: Results.Failure, _, _) => (code, result) 32 | } 33 | } 34 | 35 | val (ok, code) = outcome match { 36 | case Some((code, _)) => (false, code) 37 | case None => (true, "") 38 | } 39 | 40 | result(ok, s"${s.description} is interpretable", s"${s.description} is not interpretable, fails at:\n\n$code", s) 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /sbt: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function usage { 4 | echo "Usage: `basename $0` [OPTION|COMMAND]... -- [JVM OPTION]..." 5 | } 6 | 7 | while getopts h OPT; 8 | do 9 | case "$OPT" in 10 | h) 11 | usage 12 | exit 0 13 | ;; 14 | \?) 15 | usage 16 | exit 1 17 | ;; 18 | esac 19 | done 20 | 21 | shift `expr $OPTIND - 1` 22 | 23 | SEP=" -- " 24 | OPTS=$@ 25 | 26 | SBT_OPTS="${OPTS%$SEP*}" 27 | 28 | if [ "$SBT_OPTS" != "$OPTS" ]; 29 | then 30 | JVM_OPTS="${OPTS#*$SEP}" 31 | else 32 | JVM_OPTS="" 33 | fi 34 | 35 | function get_property { 36 | echo $(cat project/build.properties | grep "^$1" | cut -d'=' -f2-) 37 | } 38 | 39 | JVM_DEFAULTS=" \ 40 | -Dfile.encoding=UTF-8 \ 41 | -Xss8M \ 42 | -Xmx2G \ 43 | -XX:MaxPermSize=1024M \ 44 | -XX:ReservedCodeCacheSize=64M \ 45 | -XX:+UseConcMarkSweepGC \ 46 | -XX:+CMSClassUnloadingEnabled" 47 | 48 | JVM_OPTS="$JVM_DEFAULTS $JVM_OPTS" 49 | 50 | SBT_VERSION="$(get_property sbt.version)" 51 | SBT_LAUNCHER="$(dirname $0)/project/sbt-launch-$SBT_VERSION.jar" 52 | 53 | if [ ! -e "$SBT_LAUNCHER" ]; 54 | then 55 | URL="http://repo.typesafe.com/typesafe/ivy-releases/org.scala-sbt/sbt-launch/$SBT_VERSION/sbt-launch.jar" 56 | wget -O $SBT_LAUNCHER $URL 57 | fi 58 | 59 | EXPECTED_MD5="$(get_property sbt.launcher.md5)" 60 | COMPUTED_MD5="$(openssl md5 -hex < $SBT_LAUNCHER | cut -d' ' -f2)" 61 | 62 | if [ "$EXPECTED_MD5" != "$COMPUTED_MD5" ]; 63 | then 64 | echo "$SBT_LAUNCHER has invalid MD5 signature: expected $EXPECTED_MD5, got $COMPUTED_MD5" 65 | exit 1 66 | fi 67 | 68 | java $JVM_OPTS -jar $SBT_LAUNCHER $SBT_OPTS 69 | -------------------------------------------------------------------------------- /lib/src/main/scala/DisplayFunctions.scala: -------------------------------------------------------------------------------- 1 | package org.refptr.iscala 2 | package display 3 | 4 | import scala.reflect.macros.Context 5 | 6 | object DisplayFunctions extends DisplayFunctions { 7 | def displayImpl[T: c.WeakTypeTag](c: Context)(obj: c.Expr[T]): c.Expr[Unit] = { 8 | import c.universe._ 9 | reify { display_data(Repr.displaysImpl[T](c).splice, obj.splice) } 10 | } 11 | } 12 | 13 | trait DisplayFunctions { 14 | protected def display_data[T](repr: Repr[T], obj: T) { 15 | IScala.display_data(repr.stringify(obj)) 16 | } 17 | 18 | def display[T](obj: T): Unit = macro DisplayFunctions.displayImpl[T] 19 | 20 | def display_plain[T:PlainDisplay](obj: T) = { 21 | display_data(Repr(plain=Some(implicitly[PlainDisplay[T]])), obj) 22 | } 23 | 24 | def display_html[T:HTMLDisplay](obj: T) = { 25 | display_data(Repr(html=Some(implicitly[HTMLDisplay[T]])), obj) 26 | } 27 | 28 | def display_markdown[T:MarkdownDisplay](obj: T) = { 29 | display_data(Repr(markdown=Some(implicitly[MarkdownDisplay[T]])), obj) 30 | } 31 | 32 | def display_latex[T:LatexDisplay](obj: T) = { 33 | display_data(Repr(latex=Some(implicitly[LatexDisplay[T]])), obj) 34 | } 35 | 36 | def display_json[T:JSONDisplay](obj: T) = { 37 | display_data(Repr(json=Some(implicitly[JSONDisplay[T]])), obj) 38 | } 39 | 40 | def display_javascript[T:JavascriptDisplay](obj: T) = { 41 | display_data(Repr(javascript=Some(implicitly[JavascriptDisplay[T]])), obj) 42 | } 43 | 44 | def display_svg[T:SVGDisplay](obj: T) = { 45 | display_data(Repr(svg=Some(implicitly[SVGDisplay[T]])), obj) 46 | } 47 | 48 | def display_png[T:PNGDisplay](obj: T) = { 49 | display_data(Repr(png=Some(implicitly[PNGDisplay[T]])), obj) 50 | } 51 | 52 | def display_jpeg[T:JPEGDisplay](obj: T) = { 53 | display_data(Repr(jpeg=Some(implicitly[JPEGDisplay[T]])), obj) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lib/src/main/scala/DisplayObject.scala: -------------------------------------------------------------------------------- 1 | package org.refptr.iscala 2 | package display 3 | 4 | trait DisplayObject 5 | 6 | trait PlainDisplayObject extends DisplayObject { 7 | def toPlain: String 8 | } 9 | 10 | trait HTMLDisplayObject extends DisplayObject { 11 | def toHTML: String 12 | } 13 | 14 | trait MarkdownDisplayObject extends DisplayObject { 15 | def toMarkdown: String 16 | } 17 | 18 | trait LatexDisplayObject extends DisplayObject { 19 | def toLatex: String 20 | } 21 | 22 | trait JSONDisplayObject extends DisplayObject { 23 | def toJSON: String 24 | } 25 | 26 | trait JavascriptDisplayObject extends DisplayObject { 27 | def toJavascript: String 28 | } 29 | 30 | trait SVGDisplayObject extends DisplayObject { 31 | def toSVG: String 32 | } 33 | 34 | trait PNGDisplayObject extends DisplayObject { 35 | def toPNG: String 36 | } 37 | 38 | trait JPEGDisplayObject extends DisplayObject { 39 | def toJPEG: String 40 | } 41 | 42 | trait DisplayObjectImplicits { 43 | implicit val PlainDisplayPlainObject = PlainDisplay[PlainDisplayObject] (_.toPlain) 44 | implicit val HTMLDisplayHTMLObject = HTMLDisplay[HTMLDisplayObject] (_.toHTML) 45 | implicit val MarkdownDisplayMarkdownObject = MarkdownDisplay[MarkdownDisplayObject] (_.toMarkdown) 46 | implicit val LatexDisplayLatexObject = LatexDisplay[LatexDisplayObject] (_.toLatex) 47 | implicit val JSONDisplayJSONObject = JSONDisplay[JSONDisplayObject] (_.toJSON) 48 | implicit val JavascriptDisplayJavaScriptObject = JavascriptDisplay[JavascriptDisplayObject] (_.toJavascript) 49 | implicit val SVGDisplaySVGObject = SVGDisplay[SVGDisplayObject] (_.toSVG) 50 | implicit val PNGDisplayPNGObject = PNGDisplay[PNGDisplayObject] (_.toPNG) 51 | implicit val JPEGDisplayJPEGObject = JPEGDisplay[JPEGDisplayObject] (_.toJPEG) 52 | } 53 | -------------------------------------------------------------------------------- /lib/src/main/scala/Objects.scala: -------------------------------------------------------------------------------- 1 | package org.refptr.iscala 2 | package display 3 | 4 | import java.net.URL 5 | 6 | case class Math(math: String) extends LatexDisplayObject { 7 | def toLatex = "$$" + math + "$$" 8 | } 9 | 10 | case class Latex(latex: String) extends LatexDisplayObject { 11 | def toLatex = latex 12 | } 13 | 14 | class IFrame(src: URL, width: Int, height: Int) extends HTMLDisplayObject { 15 | protected def iframe() = 16 | 21 | 22 | def toHTML = iframe().toString 23 | } 24 | 25 | object IFrame { 26 | def apply(src: URL, width: Int, height: Int): IFrame = new IFrame(src, width, height) 27 | } 28 | 29 | case class YouTubeVideo(id: String, width: Int=400, height: Int=300) 30 | extends IFrame(new URL("https", "www.youtube.com", s"/embed/$id"), width, height) 31 | 32 | case class VimeoVideo(id: String, width: Int=400, height: Int=300) 33 | extends IFrame(new URL("https", "player.vimeo.com", s"/video/$id"), width, height) 34 | 35 | case class ScribdDocument(id: String, width: Int=400, height: Int=300) 36 | extends IFrame(new URL("https", "www.scribd.com", s"/embeds/$id/content"), width, height) 37 | 38 | case class ImageURL(url: URL, width: Option[Int], height: Option[Int]) extends HTMLDisplayObject { 39 | def toHTML = xml.Text(w.toString))} 41 | height={height.map(h => xml.Text(h.toString))}> toString 42 | } 43 | 44 | object ImageURL { 45 | def apply(url: URL): ImageURL = ImageURL(url, None, None) 46 | def apply(url: String): ImageURL = ImageURL(new URL(url)) 47 | def apply(url: URL, width: Int, height: Int): ImageURL = ImageURL(url, Some(width), Some(height)) 48 | def apply(url: String, width: Int, height: Int): ImageURL = ImageURL(new URL(url), width, height) 49 | } 50 | -------------------------------------------------------------------------------- /src/main/scala/Util.scala: -------------------------------------------------------------------------------- 1 | package org.refptr.iscala 2 | 3 | import java.io.File 4 | import java.util.{Timer,TimerTask} 5 | import java.lang.management.ManagementFactory 6 | 7 | trait ScalaUtil { 8 | def scalaVersion = scala.util.Properties.versionNumberString 9 | } 10 | 11 | trait OSUtil { 12 | def getpid(): Int = { 13 | val name = ManagementFactory.getRuntimeMXBean().getName() 14 | name.takeWhile(_ != '@').toInt 15 | } 16 | } 17 | 18 | trait IOUtil { 19 | def newThread(fn: Thread => Unit)(body: => Unit): Thread = { 20 | val thread = new Thread(new Runnable { 21 | override def run() = body 22 | }) 23 | fn(thread) 24 | thread.start 25 | thread 26 | } 27 | 28 | def timer(seconds: Int)(body: => Unit): Timer = { 29 | val alarm = new Timer(true) 30 | val task = new TimerTask { def run() = body } 31 | alarm.schedule(task, seconds*1000) 32 | alarm 33 | } 34 | } 35 | 36 | trait StringUtil { 37 | /** Find longest common prefix of a list of strings. 38 | */ 39 | def commonPrefix(xs: List[String]): String = { 40 | if (xs.isEmpty || xs.contains("")) "" 41 | else xs.head.head match { 42 | case ch => 43 | if (xs.tail forall (_.head == ch)) "" + ch + commonPrefix(xs map (_.tail)) 44 | else "" 45 | } 46 | } 47 | 48 | /** Find longest string that is a suffix of `head` and prefix of `tail`. 49 | * 50 | * Example: 51 | * 52 | * isInstance 53 | * x.is 54 | * ^^ 55 | * 56 | * >>> Util.suffixPrefix("x.is", "isInstance") 57 | * "is" 58 | */ 59 | def suffixPrefix(head: String, tail: String): String = { 60 | var prefix = head 61 | while (!tail.startsWith(prefix)) { 62 | prefix = prefix.drop(1) 63 | } 64 | prefix 65 | } 66 | 67 | def hex(bytes: Seq[Byte]): String = { 68 | bytes.map("%02x" format _).mkString 69 | } 70 | } 71 | 72 | trait Util extends ScalaUtil with OSUtil with IOUtil with StringUtil 73 | object Util extends Util 74 | -------------------------------------------------------------------------------- /src/main/scala/Runner.scala: -------------------------------------------------------------------------------- 1 | package org.refptr.iscala 2 | 3 | import java.util.concurrent.locks.ReentrantLock 4 | import Util.{newThread,timer} 5 | 6 | class Runner(classLoader: => ClassLoader) { 7 | 8 | class Execution(body: => Results.Result) { 9 | private var _result: Option[Results.Result] = None 10 | 11 | private val lock = new ReentrantLock() 12 | private val finished = lock.newCondition() 13 | 14 | private def withLock[T](body: => T) = { 15 | lock.lock() 16 | try body 17 | finally lock.unlock() 18 | } 19 | 20 | private def setResult(result: Results.Result) = withLock { 21 | _result = Some(result) 22 | finished.signal() 23 | } 24 | 25 | private val _thread = newThread { 26 | _.setContextClassLoader(classLoader) 27 | } { 28 | setResult(body) 29 | } 30 | 31 | private[Runner] def cancel() = if (running) setResult(Results.Cancelled) 32 | 33 | private[Runner] def interrupt() = _thread.interrupt() 34 | private[Runner] def stop() = Threading.stop(_thread) 35 | 36 | def alive = _thread.isAlive 37 | def running = !_result.isDefined 38 | 39 | def await() = withLock { while (running) finished.await() } 40 | def result() = { await(); _result.getOrElse(sys.exit) } 41 | 42 | override def toString = s"Execution(thread=${_thread})" 43 | } 44 | 45 | private var _current: Option[Execution] = None 46 | def current = _current 47 | 48 | def execute(body: => Results.Result): Execution = { 49 | val execution = new Execution(body) 50 | _current = Some(execution) 51 | execution 52 | } 53 | 54 | def clear() { 55 | _current.foreach(_.cancel()) 56 | _current = None 57 | } 58 | 59 | def cancel() { 60 | current.foreach { execution => 61 | execution.interrupt() 62 | execution.cancel() 63 | timer(5) { 64 | if (execution.alive) { 65 | logger.debug(s"Forcefully stopping ${execution}") 66 | execution.stop() 67 | } 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/scala/Sbt.scala: -------------------------------------------------------------------------------- 1 | package org.refptr.iscala 2 | 3 | import java.io.File 4 | 5 | import sbt.{ 6 | ShowLines, 7 | Logger, ConsoleLogger, 8 | ModuleID,ModuleInfo,CrossVersion,Resolver, 9 | IvyPaths,InlineIvyConfiguration,IvySbt,IvyScala,IvyActions, 10 | InlineConfiguration,UpdateConfiguration, 11 | UpdateOptions,UpdateLogging,UnresolvedWarningConfiguration} 12 | 13 | import Util.scalaVersion 14 | 15 | object Modules { 16 | val Compiler = ModuleID("org.scala-lang", "scala-compiler", scalaVersion) 17 | val IScala = ModuleID("org.refptr.iscala", "IScala", "0.3-SNAPSHOT", crossVersion=CrossVersion.binary) 18 | } 19 | 20 | case class ClassPath(jars: Seq[File]) { 21 | def classpath: String = ClassPath.joinFiles(jars: _*) 22 | } 23 | 24 | object ClassPath { 25 | def joinFiles(paths: File*): String = join(paths map (_.getAbsolutePath): _*) 26 | def join(paths: String*): String = paths filterNot (_ == "") mkString File.pathSeparator 27 | } 28 | 29 | object Sbt { 30 | def resolve(modules: Seq[ModuleID], resolvers: Seq[Resolver]): Option[ClassPath] = 31 | resolve(modules, resolvers, ConsoleLogger()) 32 | 33 | def resolve(modules: Seq[ModuleID], resolvers: Seq[Resolver], logger: Logger): Option[ClassPath] = { 34 | val paths = new IvyPaths(new File("."), None) 35 | val allResolvers = Resolver.withDefaultResolvers(resolvers) 36 | val ivyConf = new InlineIvyConfiguration(paths, allResolvers, Nil, Nil, false, None, Seq("sha1", "md5"), None, UpdateOptions(), logger) 37 | val ivySbt = new IvySbt(ivyConf) 38 | val binaryScalaVersion = CrossVersion.binaryScalaVersion(scalaVersion) 39 | val ivyScala = new IvyScala(scalaVersion, binaryScalaVersion, Nil, checkExplicit=true, filterImplicit=false, overrideScalaVersion=false) 40 | val settings = new InlineConfiguration(Modules.IScala, ModuleInfo("IScala"), modules, ivyScala=Some(ivyScala)) 41 | val module = new ivySbt.Module(settings) 42 | val updateConf = new UpdateConfiguration(None, false, UpdateLogging.DownloadOnly) 43 | val updateReport = IvyActions.updateEither(module, updateConf, UnresolvedWarningConfiguration(), logger) 44 | updateReport match { 45 | case Right(report) => 46 | Some(ClassPath(report.toSeq.map { case (_, _, _, jar) => jar }.distinct)) 47 | case Left(warning) => 48 | import ShowLines._ 49 | warning.lines.foreach(logger.error(_)) 50 | None 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/src/main/scala/Repr.scala: -------------------------------------------------------------------------------- 1 | package org.refptr.iscala 2 | package display 3 | 4 | case class Repr[-T]( 5 | plain : Option[PlainDisplay[T]] = None, 6 | html : Option[HTMLDisplay[T]] = None, 7 | markdown : Option[MarkdownDisplay[T]] = None, 8 | latex : Option[LatexDisplay[T]] = None, 9 | json : Option[JSONDisplay[T]] = None, 10 | javascript : Option[JavascriptDisplay[T]] = None, 11 | svg : Option[SVGDisplay[T]] = None, 12 | png : Option[PNGDisplay[T]] = None, 13 | jpeg : Option[JPEGDisplay[T]] = None) { 14 | 15 | def stringify(obj: T): Data = { 16 | val displays = List(plain, html, markdown, latex, json, javascript, svg, png, jpeg) 17 | Data(displays.flatten.map { display => display.mime -> display.stringify(obj) }: _*) 18 | } 19 | } 20 | 21 | object Repr { 22 | import scala.reflect.macros.Context 23 | 24 | def displaysImpl[T: c.WeakTypeTag](c: Context): c.Expr[Repr[T]] = { 25 | import c.universe._ 26 | import Core.implicitlyOptImpl 27 | 28 | // XXX: Null causes "ambiguous implicit values" error together with a contravariant 29 | // type param of display type class, so handle it in a special way (Null is anyway 30 | // useless, but lets cover all cases). 31 | if (weakTypeOf[T] <:< weakTypeOf[Null]) { 32 | reify { 33 | Repr(plain = implicitlyOptImpl[PlainDisplay[T]](c) splice) 34 | } 35 | } else { 36 | reify { 37 | Repr(plain = implicitlyOptImpl[PlainDisplay[T]](c) splice, 38 | html = implicitlyOptImpl[HTMLDisplay[T]](c) splice, 39 | markdown = implicitlyOptImpl[MarkdownDisplay[T]](c) splice, 40 | latex = implicitlyOptImpl[LatexDisplay[T]](c) splice, 41 | json = implicitlyOptImpl[JSONDisplay[T]](c) splice, 42 | javascript = implicitlyOptImpl[JavascriptDisplay[T]](c) splice, 43 | svg = implicitlyOptImpl[SVGDisplay[T]](c) splice, 44 | png = implicitlyOptImpl[PNGDisplay[T]](c) splice, 45 | jpeg = implicitlyOptImpl[JPEGDisplay[T]](c) splice) 46 | } 47 | } 48 | } 49 | 50 | def displays[T]: Repr[T] = macro displaysImpl[T] 51 | 52 | def stringifyImpl[T: c.WeakTypeTag](c: Context)(obj: c.Expr[T]): c.Expr[Data] = { 53 | import c.universe._ 54 | reify { displaysImpl[T](c).splice.stringify(obj.splice) } 55 | } 56 | 57 | def stringify[T](obj: T): Data = macro stringifyImpl[T] 58 | } 59 | -------------------------------------------------------------------------------- /src/main/scala/DB.scala: -------------------------------------------------------------------------------- 1 | package org.refptr.iscala 2 | 3 | import java.sql.Timestamp 4 | 5 | import scala.slick.driver.SQLiteDriver.simple._ 6 | import Database.dynamicSession 7 | 8 | class Sessions(tag: Tag) extends Table[(Int, Timestamp, Option[Timestamp], Option[Int], String)](tag, "sessions") { 9 | def session = column[Int]("session", O.PrimaryKey, O.AutoInc) 10 | def start = column[Timestamp]("start") 11 | def end = column[Option[Timestamp]]("end") 12 | def num_cmds = column[Option[Int]]("num_cmds") 13 | def remark = column[String]("remark") 14 | 15 | def * = (session, start, end, num_cmds, remark) 16 | } 17 | 18 | class History(tag: Tag) extends Table[(Int, Int, String, String)](tag, "history") { 19 | def session = column[Int]("session") 20 | def line = column[Int]("line") 21 | def source = column[String]("source") 22 | def source_raw = column[String]("source_raw") 23 | 24 | def pk = primaryKey("pk_session_line", (session, line)) 25 | 26 | def * = (session, line, source, source_raw) 27 | } 28 | 29 | class OutputHistory(tag: Tag) extends Table[(Int, Int, String)](tag, "output_history") { 30 | def session = column[Int]("session") 31 | def line = column[Int]("line") 32 | def output = column[String]("output") 33 | 34 | def pk = primaryKey("pk_session_line", (session, line)) 35 | 36 | def * = (session, line, output) 37 | } 38 | 39 | object DB { 40 | val Sessions = TableQuery[Sessions] 41 | val History = TableQuery[History] 42 | val OutputHistory = TableQuery[OutputHistory] 43 | 44 | lazy val dbPath = { 45 | val profile = IScala.config.profile_dir 46 | if (!profile.exists) profile.createDirectory() 47 | profile / "history.sqlite" path 48 | } 49 | 50 | lazy val db = { 51 | val db = Database.forURL(s"jdbc:sqlite:$dbPath", driver="org.sqlite.JDBC") 52 | db.withDynSession { 53 | Seq(Sessions, History, OutputHistory) foreach { table => 54 | try { 55 | table.ddl.create 56 | } catch { 57 | case error: java.sql.SQLException if error.getMessage contains "already exists" => 58 | } 59 | } 60 | } 61 | db 62 | } 63 | 64 | import db.withDynSession 65 | 66 | private def now = new Timestamp(System.currentTimeMillis) 67 | 68 | def newSession(): Int = withDynSession { 69 | (Sessions returning Sessions.map(_.session)) += (0, now, None, None, "") 70 | } 71 | 72 | def endSession(session: Int)(num_cmds: Int): Unit = withDynSession { 73 | val q = for { s <- Sessions if s.session === session } yield (s.end, s.num_cmds) 74 | q.update(Some(now), Some(num_cmds)) 75 | } 76 | 77 | def addHistory(session: Int)(line: Int, source: String): Unit = withDynSession { 78 | History += (session, line, source, source) 79 | } 80 | 81 | def addOutputHistory(session: Int)(line: Int, output: String): Unit = withDynSession { 82 | OutputHistory += (session, line, output) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /core/src/main/scala/Enum.scala: -------------------------------------------------------------------------------- 1 | package org.refptr.iscala 2 | 3 | import scala.annotation.StaticAnnotation 4 | import scala.reflect.macros.Context 5 | 6 | trait EnumType { 7 | val name: String = toString 8 | } 9 | 10 | trait LowerCase { self: EnumType => 11 | override val name = toString.toLowerCase 12 | } 13 | 14 | trait SnakeCase { self: EnumType => 15 | override val name = Utils.snakify(toString) 16 | } 17 | 18 | trait Enumerated[T <: EnumType] { 19 | type ValueType = T 20 | 21 | val values: Set[T] 22 | val fromString: PartialFunction[String, T] 23 | 24 | final def unapply(name: String): Option[T] = fromString.lift(name) 25 | 26 | override def toString: String = { 27 | val name = getClass.getSimpleName.stripSuffix("$") 28 | s"$name(${values.map(_.name).mkString(", ")})" 29 | } 30 | } 31 | 32 | class enum extends StaticAnnotation { 33 | def macroTransform(annottees: Any*): Any = macro EnumImpl.enumTransformImpl 34 | } 35 | 36 | private object EnumImpl { 37 | def enumTransformImpl(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = { 38 | import c.universe._ 39 | 40 | annottees.map(_.tree) match { 41 | case ModuleDef(mods, name, tpl @ Template(parents, sf, body)) :: Nil => 42 | val enumImpl = reify { EnumImpl } 43 | val methods = List( 44 | q"final val values: Set[ValueType] = $enumImpl.values[ValueType]", 45 | q"final val fromString: PartialFunction[String, ValueType] = $enumImpl.fromString[ValueType]") 46 | val module = ModuleDef(mods, name, Template(parents, sf, body ++ methods)) 47 | c.Expr[Any](Block(module :: Nil, Literal(Constant(())))) 48 | case _ => c.abort(c.enclosingPosition, "@enum annotation can only be applied to an object") 49 | } 50 | } 51 | 52 | private def children[T <: EnumType : c.WeakTypeTag](c: Context): Set[c.universe.Symbol] = { 53 | import c.universe._ 54 | 55 | val tpe = weakTypeOf[T] 56 | val cls = tpe.typeSymbol.asClass 57 | 58 | if (!cls.isSealed) c.error(c.enclosingPosition, "must be a sealed trait or class") 59 | val children = tpe.typeSymbol.asClass.knownDirectSubclasses 60 | if (children.isEmpty) c.error(c.enclosingPosition, "no enumerations found") 61 | 62 | children 63 | } 64 | 65 | def values[T <: EnumType]: Set[T] = macro EnumImpl.valuesImpl[T] 66 | 67 | def valuesImpl[T <: EnumType : c.WeakTypeTag](c: Context): c.Expr[Set[T]] = { 68 | import c.universe._ 69 | 70 | val tpe = weakTypeOf[T] 71 | val values = children[T](c).map(_.name.toTermName) 72 | 73 | c.Expr[Set[T]](q"Set[$tpe](..$values)") 74 | } 75 | 76 | def fromString[T <: EnumType]: PartialFunction[String, T] = macro EnumImpl.fromStringImpl[T] 77 | 78 | def fromStringImpl[T <: EnumType : c.WeakTypeTag](c: Context): c.Expr[PartialFunction[String, T]] = { 79 | import c.universe._ 80 | 81 | val tpe = weakTypeOf[T] 82 | val cases = children[T](c).map { child => cq"${child.name.toTermName}.name => ${child.name.toTermName}" } 83 | 84 | c.Expr[PartialFunction[String, T]](q"{ case ..$cases }: PartialFunction[String, $tpe]") 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /lib/src/main/scala/Enums.scala: -------------------------------------------------------------------------------- 1 | package org.refptr.iscala 2 | package widgets 3 | 4 | sealed trait BorderStyle extends EnumType with SnakeCase 5 | @enum object BorderStyle extends Enumerated[BorderStyle] { 6 | case object None extends BorderStyle 7 | case object Hidden extends BorderStyle 8 | case object Dotted extends BorderStyle 9 | case object Dashed extends BorderStyle 10 | case object Solid extends BorderStyle 11 | case object Double extends BorderStyle 12 | case object Groove extends BorderStyle 13 | case object Ridge extends BorderStyle 14 | case object Inset extends BorderStyle 15 | case object Outset extends BorderStyle 16 | case object Initial extends BorderStyle 17 | case object Inherit extends BorderStyle 18 | } 19 | 20 | sealed trait FontStyle extends EnumType with SnakeCase 21 | @enum object FontStyle extends Enumerated[FontStyle] { 22 | case object Normal extends FontStyle 23 | case object Italic extends FontStyle 24 | case object Oblique extends FontStyle 25 | case object Initial extends FontStyle 26 | case object Inherit extends FontStyle 27 | } 28 | 29 | sealed trait FontWeight extends EnumType with SnakeCase 30 | @enum object FontWeight extends Enumerated[FontWeight] { 31 | case object Normal extends FontWeight 32 | case object Bold extends FontWeight 33 | case object Bolder extends FontWeight 34 | case object Lighter extends FontWeight 35 | case object Initial extends FontWeight 36 | case object Inherit extends FontWeight 37 | case object `100` extends FontWeight 38 | case object `200` extends FontWeight 39 | case object `300` extends FontWeight 40 | case object `400` extends FontWeight 41 | case object `500` extends FontWeight 42 | case object `600` extends FontWeight 43 | case object `700` extends FontWeight 44 | case object `800` extends FontWeight 45 | case object `900` extends FontWeight 46 | } 47 | 48 | sealed trait ButtonStyle extends EnumType with SnakeCase 49 | @enum object ButtonStyle extends Enumerated[ButtonStyle] { 50 | case object Primary extends ButtonStyle 51 | case object Success extends ButtonStyle 52 | case object Info extends ButtonStyle 53 | case object Warning extends ButtonStyle 54 | case object Danger extends ButtonStyle 55 | } 56 | 57 | sealed trait BoxStyle extends EnumType with SnakeCase 58 | @enum object BoxStyle extends Enumerated[BoxStyle] { 59 | case object Success extends BoxStyle 60 | case object Info extends BoxStyle 61 | case object Warning extends BoxStyle 62 | case object Danger extends BoxStyle 63 | } 64 | 65 | sealed trait Orientation extends EnumType with SnakeCase 66 | @enum object Orientation extends Enumerated[Orientation] { 67 | case object Horizontal extends Orientation 68 | case object Vertical extends Orientation 69 | } 70 | 71 | sealed trait Location extends EnumType with SnakeCase 72 | @enum object Location extends Enumerated[Location] { 73 | case object Start extends Location 74 | case object Center extends Location 75 | case object End extends Location 76 | case object Baseline extends Location 77 | case object Stretch extends Location 78 | } 79 | 80 | sealed trait Overflow extends EnumType with SnakeCase 81 | @enum object Overflow extends Enumerated[Overflow] { 82 | case object Visible extends Overflow 83 | case object Hidden extends Overflow 84 | case object Scroll extends Overflow 85 | case object Auto extends Overflow 86 | case object Initial extends Overflow 87 | case object Inherit extends Overflow 88 | } 89 | -------------------------------------------------------------------------------- /lib/src/main/scala/Display.scala: -------------------------------------------------------------------------------- 1 | package org.refptr.iscala 2 | package display 3 | 4 | import scala.annotation.implicitNotFound 5 | 6 | trait Display[-T] { 7 | def mime: MIME 8 | def stringify(obj: T): String 9 | } 10 | 11 | object Display extends DisplayImplicits with DisplayObjectImplicits with ImageImplicits 12 | 13 | @implicitNotFound(msg="Can't find Plain display type class for type ${T}.") 14 | trait PlainDisplay[-T] extends Display[T] { val mime = MIME.`text/plain` } 15 | @implicitNotFound(msg="Can't find HTML display type class for type ${T}.") 16 | trait HTMLDisplay[-T] extends Display[T] { val mime = MIME.`text/html` } 17 | @implicitNotFound(msg="Can't find Markdown display type class for type ${T}.") 18 | trait MarkdownDisplay[-T] extends Display[T] { val mime = MIME.`text/markdown` } 19 | @implicitNotFound(msg="Can't find Latex display type class for type ${T}.") 20 | trait LatexDisplay[-T] extends Display[T] { val mime = MIME.`text/latex` } 21 | @implicitNotFound(msg="Can't find JSON display type class for type ${T}.") 22 | trait JSONDisplay[-T] extends Display[T] { val mime = MIME.`application/json` } 23 | @implicitNotFound(msg="Can't find Javascript display type class for type ${T}.") 24 | trait JavascriptDisplay[-T] extends Display[T] { val mime = MIME.`application/javascript` } 25 | @implicitNotFound(msg="Can't find SVG display type class for type ${T}.") 26 | trait SVGDisplay[-T] extends Display[T] { val mime = MIME.`image/svg+xml` } 27 | @implicitNotFound(msg="Can't find PNG display type class for type ${T}.") 28 | trait PNGDisplay[-T] extends Display[T] { val mime = MIME.`image/png` } 29 | @implicitNotFound(msg="Can't find JPEG display type class for type ${T}.") 30 | trait JPEGDisplay[-T] extends Display[T] { val mime = MIME.`image/jpeg` } 31 | 32 | object PlainDisplay { 33 | def apply[T](fn: T => String): PlainDisplay[T] = new PlainDisplay[T] { 34 | def stringify(obj: T) = fn(obj) 35 | } 36 | } 37 | 38 | object HTMLDisplay { 39 | def apply[T](fn: T => String): HTMLDisplay[T] = new HTMLDisplay[T] { 40 | def stringify(obj: T) = fn(obj) 41 | } 42 | 43 | def apply[T, U: HTMLDisplay](fn: T => U): HTMLDisplay[T] = new HTMLDisplay[T] { 44 | def stringify(obj: T) = implicitly[HTMLDisplay[U]].stringify(fn(obj)) 45 | } 46 | } 47 | 48 | object MarkdownDisplay { 49 | def apply[T](fn: T => String): MarkdownDisplay[T] = new MarkdownDisplay[T] { 50 | def stringify(obj: T) = fn(obj) 51 | } 52 | } 53 | 54 | object LatexDisplay { 55 | def apply[T](fn: T => String): LatexDisplay[T] = new LatexDisplay[T] { 56 | def stringify(obj: T) = fn(obj) 57 | } 58 | } 59 | 60 | object JSONDisplay { 61 | def apply[T](fn: T => String): JSONDisplay[T] = new JSONDisplay[T] { 62 | def stringify(obj: T) = fn(obj) 63 | } 64 | } 65 | 66 | object JavascriptDisplay { 67 | def apply[T](fn: T => String): JavascriptDisplay[T] = new JavascriptDisplay[T] { 68 | def stringify(obj: T) = fn(obj) 69 | } 70 | } 71 | 72 | object SVGDisplay { 73 | def apply[T](fn: T => String): SVGDisplay[T] = new SVGDisplay[T] { 74 | def stringify(obj: T) = fn(obj) 75 | } 76 | } 77 | 78 | object PNGDisplay { 79 | def apply[T](fn: T => String): PNGDisplay[T] = new PNGDisplay[T] { 80 | def stringify(obj: T) = fn(obj) 81 | } 82 | } 83 | 84 | object JPEGDisplay { 85 | def apply[T](fn: T => String): JPEGDisplay[T] = new JPEGDisplay[T] { 86 | def stringify(obj: T) = fn(obj) 87 | } 88 | } 89 | 90 | trait DisplayImplicits { 91 | implicit val PlainDisplayAny = PlainDisplay[Any](scala.runtime.ScalaRunTime.stringOf _) 92 | implicit val HTMLDisplayNodeSeq = HTMLDisplay[xml.NodeSeq](_.toString) 93 | } 94 | -------------------------------------------------------------------------------- /src/main/scala/Capture.scala: -------------------------------------------------------------------------------- 1 | package org.refptr.iscala 2 | 3 | class WatchStream(input: java.io.InputStream, size: Int, fn: String => Unit) extends Thread { 4 | override def run() { 5 | val buffer = new Array[Byte](size) 6 | 7 | try { 8 | while (true) { 9 | val n = input.read(buffer) 10 | fn(new String(buffer.take(n))) 11 | 12 | if (n < size) { 13 | Thread.sleep(50) // a little delay to accumulate output 14 | } 15 | } 16 | } catch { 17 | case _: java.io.IOException => // stream was closed so job is done 18 | } 19 | } 20 | } 21 | 22 | abstract class Capture { self => 23 | 24 | def stdout(data: String): Unit 25 | def stderr(data: String): Unit 26 | 27 | def withOut[T](newOut: java.io.PrintStream)(block: => T): T = { 28 | Console.withOut(newOut) { 29 | val oldOut = System.out 30 | System.setOut(newOut) 31 | try { 32 | block 33 | } finally { 34 | System.setOut(oldOut) 35 | } 36 | } 37 | } 38 | 39 | def withErr[T](newErr: java.io.PrintStream)(block: => T): T = { 40 | Console.withErr(newErr) { 41 | val oldErr = System.err 42 | System.setErr(newErr) 43 | try { 44 | block 45 | } finally { 46 | System.setErr(oldErr) 47 | } 48 | } 49 | } 50 | 51 | def withOutAndErr[T](newOut: java.io.PrintStream, 52 | newErr: java.io.PrintStream)(block: => T): T = { 53 | withOut(newOut) { 54 | withErr(newErr) { 55 | block 56 | } 57 | } 58 | } 59 | 60 | // This is a heavyweight solution to start stream watch threads per 61 | // input, but currently it's the cheapest approach that works well in 62 | // multiple thread setup. Note that piped streams work only in thread 63 | // pairs (producer -> consumer) and we start one thread per execution, 64 | // so technically speaking we have multiple producers, which completely 65 | // breaks the earlier intuitive approach. 66 | 67 | def apply[T](block: => T): T = { 68 | val size = 10240 69 | 70 | val stdoutIn = new java.io.PipedInputStream(size) 71 | val stderrIn = new java.io.PipedInputStream(size) 72 | 73 | val stderrOut = new java.io.PipedOutputStream(stderrIn) 74 | val stdoutOut = new java.io.PipedOutputStream(stdoutIn) 75 | 76 | val stdout = new java.io.PrintStream(stdoutOut) 77 | val stderr = new java.io.PrintStream(stderrOut) 78 | 79 | val stdoutThread = new WatchStream(stdoutIn, size, self.stdout) 80 | val stderrThread = new WatchStream(stderrIn, size, self.stderr) 81 | 82 | stdoutThread.start() 83 | stderrThread.start() 84 | 85 | try { 86 | val result = withOutAndErr(stdout, stderr) { block } 87 | 88 | stdoutOut.flush() 89 | stderrOut.flush() 90 | 91 | // Wait until both streams get dry because we have to 92 | // send messages with streams' data before execute_reply 93 | // is send. Otherwise there will be no output in clients 94 | // or it will be incomplete. 95 | while (stdoutIn.available > 0 || stderrIn.available > 0) 96 | Thread.sleep(10) 97 | 98 | result 99 | } finally { 100 | // This will effectively terminate threads. 101 | stdoutOut.close() 102 | stderrOut.close() 103 | stdoutIn.close() 104 | stderrIn.close() 105 | } 106 | } 107 | } 108 | 109 | case class Output[T](value: T, out: String = "", err: String = "") 110 | 111 | class StringCapture(out: StringBuilder, err: StringBuilder) extends Capture { 112 | def stdout(data: String) = out.append(data) 113 | def stderr(data: String) = err.append(data) 114 | } 115 | 116 | object Capture { 117 | def captureOutput[T](block: => T): Output[T] = { 118 | val out = new StringBuilder 119 | val err = new StringBuilder 120 | val capture = new StringCapture(out, err) 121 | val value = capture { block } 122 | Output(value, out.toString, err.toString) 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /core/src/main/scala/JsMacroImpl.scala: -------------------------------------------------------------------------------- 1 | package org.refptr.iscala.core 2 | 3 | import play.api.libs.json.{Reads,Writes,Format,JsMacroImpl=>PlayMacroImpl} 4 | 5 | import scala.reflect.macros.Context 6 | 7 | trait JsonImpl { 8 | def reads[A]: Reads[A] = macro PlayMacroImpl.readsImpl[A] 9 | def writes[A]: Writes[A] = macro JsMacroImpl.sealedWritesImpl[A] 10 | def format[A]: Format[A] = macro PlayMacroImpl.formatImpl[A] 11 | } 12 | 13 | object JsMacroImpl { 14 | 15 | /* JSON writer for sealed traits. 16 | * 17 | * This macro generates code equivalent to: 18 | * ``` 19 | * new Writes[T] { 20 | * val $writes$T_1 = Json.writes[T_1] 21 | * ... 22 | * val $writes$T_n = Json.writes[T_n] 23 | * 24 | * def writes(obj: T) = (obj match { 25 | * case o: T_1 => $writes$T_1.writes(o) 26 | * ... 27 | * case o: T_n => $writes$T_n.writes(o) 28 | * }) ++ JsObject(List( 29 | * ("field_1", Json.toJson(obj.field_1)), 30 | * ... 31 | * ("field_n", Json.toJson(obj.field_n)))) 32 | * } 33 | * ``` 34 | * 35 | * `T` is a sealed trait with case subclasses `T_1`, ... `T_n`. Fields `field_1`, 36 | * ..., `field_n` are `T`'s vals that don't appear in `T_i` constructors. 37 | */ 38 | def sealedWritesImpl[T: c.WeakTypeTag](c: Context): c.Expr[Writes[T]] = { 39 | import c.universe._ 40 | 41 | val tpe = weakTypeOf[T] 42 | val symbol = tpe.typeSymbol 43 | 44 | if (!symbol.isClass) { 45 | c.abort(c.enclosingPosition, "expected a class or trait") 46 | } 47 | 48 | val cls = symbol.asClass 49 | 50 | if (!cls.isTrait) { 51 | PlayMacroImpl.writesImpl[T](c) 52 | } else if (!cls.isSealed) { 53 | c.abort(c.enclosingPosition, "expected a sealed trait") 54 | } else { 55 | val children = cls.knownDirectSubclasses.toList 56 | 57 | if (children.isEmpty) { 58 | c.abort(c.enclosingPosition, "trait has no subclasses") 59 | } else if (!children.forall(_.isClass) || !children.map(_.asClass).forall(_.isCaseClass)) { 60 | c.abort(c.enclosingPosition, "all children must be case classes") 61 | } else { 62 | val named = children.map { child => 63 | (child, newTermName("$writes$" + child.name.toString)) 64 | } 65 | 66 | val valDefs = named.map { case (child, name) => 67 | q"val $name = play.api.libs.json.Json.writes[$child]" 68 | } 69 | 70 | val caseDefs = named.map { case (child, name) => 71 | CaseDef( 72 | Bind(newTermName("o"), Typed(Ident(nme.WILDCARD), 73 | Ident(child))), 74 | EmptyTree, 75 | q"$name.writes(o)") 76 | } 77 | 78 | val names = children.flatMap( 79 | _.typeSignature 80 | .declaration(nme.CONSTRUCTOR) 81 | .asMethod 82 | .paramss(0) 83 | .map(_.name.toString) 84 | ).toSet 85 | 86 | val fieldNames = cls.typeSignature 87 | .declarations 88 | .toList 89 | .filter(_.isMethod) 90 | .map(_.asMethod) 91 | .filter(_.isStable) 92 | .filter(_.isPublic) 93 | .map(_.name.toString) 94 | .filterNot(names contains _) 95 | 96 | val fieldDefs = fieldNames.map { fieldName => 97 | val name = newTermName(fieldName) 98 | q"($fieldName, play.api.libs.json.Json.toJson(obj.$name))" 99 | } 100 | 101 | val matchDef = Match(q"obj", caseDefs) 102 | 103 | c.Expr[Writes[T]]( 104 | q""" 105 | new Writes[$symbol] { 106 | ..$valDefs 107 | 108 | def writes(obj: $symbol) = 109 | $matchDef ++ play.api.libs.json.JsObject(List(..$fieldDefs)) 110 | } 111 | """) 112 | } 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/main/scala/Communication.scala: -------------------------------------------------------------------------------- 1 | package org.refptr.iscala 2 | 3 | import org.zeromq.{ZMQ,ZMQException} 4 | 5 | import play.api.libs.json.{Reads,Writes,JsResultException} 6 | 7 | import org.refptr.iscala.msg._ 8 | import org.refptr.iscala.msg.formats._ 9 | 10 | import org.refptr.iscala.json.JsonUtil._ 11 | 12 | class Communication(zmq: Sockets, connection: Connection) { 13 | private val hmac = HMAC(connection.key, connection.signature_scheme) 14 | 15 | private val DELIMITER = "" 16 | 17 | def send[T<:ToIPython:Writes](socket: ZMQ.Socket, msg: Msg[T]) { 18 | val idents = msg.idents 19 | val header = toJSON(msg.header) 20 | val parent_header = msg.parent_header.map(toJSON(_)).getOrElse("{}") 21 | val metadata = toJSON(msg.metadata) 22 | val content = toJSON(msg.content) 23 | 24 | socket.synchronized { 25 | logger.debug(s"sending: $msg") 26 | idents.foreach(socket.send(_, ZMQ.SNDMORE)) 27 | socket.send(DELIMITER, ZMQ.SNDMORE) 28 | socket.send(hmac(header, parent_header, metadata, content), ZMQ.SNDMORE) 29 | socket.send(header, ZMQ.SNDMORE) 30 | socket.send(parent_header, ZMQ.SNDMORE) 31 | socket.send(metadata, ZMQ.SNDMORE) 32 | socket.send(content) 33 | } 34 | } 35 | 36 | def recv(socket: ZMQ.Socket): Option[Msg[FromIPython]] = { 37 | val (idents, signature, header, parent_header, metadata, content) = socket.synchronized { 38 | (Stream.continually { socket.recvStr() }.takeWhile(_ != DELIMITER).toList, 39 | socket.recvStr(), 40 | socket.recvStr(), 41 | socket.recvStr(), 42 | socket.recvStr(), 43 | socket.recvStr()) 44 | } 45 | 46 | val expectedSignature = hmac(header, parent_header, metadata, content) 47 | 48 | if (signature != expectedSignature) { 49 | logger.error(s"Invalid HMAC signature, got $signature, expected $expectedSignature") 50 | None 51 | } else try { 52 | val _header = header.as[Header] 53 | val _parent_header = parent_header.as[Option[Header]] 54 | val _metadata = metadata.as[Metadata] 55 | val _content = _header.msg_type match { 56 | case MsgType.execute_request => Some(content.as[execute_request]) 57 | case MsgType.complete_request => Some(content.as[complete_request]) 58 | case MsgType.kernel_info_request => Some(content.as[kernel_info_request]) 59 | case MsgType.object_info_request => Some(content.as[object_info_request]) 60 | case MsgType.connect_request => Some(content.as[connect_request]) 61 | case MsgType.shutdown_request => Some(content.as[shutdown_request]) 62 | case MsgType.history_request => Some(content.as[history_request]) 63 | case MsgType.input_reply => Some(content.as[input_reply]) 64 | case MsgType.comm_open => Some(content.as[comm_open]) 65 | case MsgType.comm_msg => Some(content.as[comm_msg]) 66 | case MsgType.comm_close => Some(content.as[comm_close]) 67 | case _ => 68 | logger.warn(s"Unexpected message type: ${_header.msg_type}") 69 | None 70 | } 71 | _content.map { _content => 72 | val msg = Msg(idents, _header, _parent_header, _metadata, _content) 73 | logger.debug(s"received: $msg") 74 | msg 75 | } 76 | } catch { 77 | case e: JsResultException => 78 | logger.error(s"JSON deserialization error: ${e.getMessage}") 79 | None 80 | } 81 | } 82 | 83 | def publish[T<:ToIPython:Writes](msg: Msg[T]) = send(zmq.publish, msg) 84 | 85 | def send_status(state: ExecutionState) { 86 | publish(Msg( 87 | "status" :: Nil, 88 | Header(msg_id=UUID.uuid4(), 89 | username="scala_kernel", 90 | session=UUID.uuid4(), 91 | msg_type=MsgType.status), 92 | None, 93 | Metadata(), 94 | status( 95 | execution_state=state))) 96 | } 97 | 98 | def send_ok(msg: Msg[_], execution_count: Int) { 99 | send(zmq.requests, msg.reply(MsgType.execute_reply, 100 | execute_ok_reply( 101 | execution_count=execution_count, 102 | payload=Nil, 103 | user_expressions=Map.empty))) 104 | } 105 | 106 | def send_error(msg: Msg[_], execution_count: Int, error: String) { 107 | send_error(msg, pyerr(execution_count, "", "", error.split("\n").toList)) 108 | } 109 | 110 | def send_error(msg: Msg[_], err: pyerr) { 111 | publish(msg.pub(MsgType.pyerr, err)) 112 | send(zmq.requests, msg.reply(MsgType.execute_reply, 113 | execute_error_reply( 114 | execution_count=err.execution_count, 115 | ename=err.ename, 116 | evalue=err.evalue, 117 | traceback=err.traceback))) 118 | } 119 | 120 | def send_abort(msg: Msg[_], execution_count: Int) { 121 | send(zmq.requests, msg.reply(MsgType.execute_reply, 122 | execute_abort_reply( 123 | execution_count=execution_count))) 124 | } 125 | 126 | def send_stream(msg: Msg[_], name: String, data: String) { 127 | publish(msg.pub(MsgType.stream, stream(name=name, data=data))) 128 | } 129 | 130 | def send_stdin(msg: Msg[_], prompt: String) { 131 | send(zmq.stdin, msg.reply(MsgType.input_request, input_request(prompt=prompt))) 132 | } 133 | 134 | def recv_stdin(): Option[Msg[FromIPython]] = recv(zmq.stdin) 135 | 136 | def send_display_data(msg: Msg[_], data: Data) { 137 | publish(msg.pub(MsgType.display_data, display_data(source="IScala", data=data, metadata=Map.empty))) 138 | } 139 | 140 | def silently[T](block: => T) { 141 | try { 142 | block 143 | } catch { 144 | case _: ZMQException => 145 | } 146 | } 147 | 148 | def busy[T](block: => T): T = { 149 | send_status(ExecutionState.busy) 150 | 151 | try { 152 | block 153 | } finally { 154 | send_status(ExecutionState.idle) 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/test/scala/Json.scala: -------------------------------------------------------------------------------- 1 | package org.refptr.iscala 2 | package tests 3 | 4 | import json._ 5 | import JsonImplicits._ 6 | 7 | import org.specs2.mutable.Specification 8 | 9 | class JsonSpec extends Specification { 10 | type MyType = Int 11 | 12 | sealed trait FooBar extends EnumType with SnakeCase 13 | @enum object FooBar extends Enumerated[FooBar] { 14 | case object FooBaz extends FooBar 15 | case object BarBaz extends FooBar 16 | } 17 | 18 | object FooBarBaz extends Enumeration { 19 | type FooBarBaz = Value 20 | val Foo = Value 21 | val Bar = Value 22 | val Baz = Value 23 | } 24 | 25 | case class Embedded(string: String) 26 | 27 | case class TupleCaseClass( 28 | boolean: Tuple1[Boolean], 29 | string: Tuple1[String], 30 | int: Tuple1[Int], 31 | double: Tuple1[Double], 32 | enum: Tuple1[FooBar], 33 | enumeration: Tuple1[FooBarBaz.Value], 34 | embedded: Tuple1[Embedded], 35 | myType: Tuple1[MyType], 36 | uuid: Tuple1[UUID], 37 | tuple: Tuple1[(String, Int)], 38 | either: Tuple1[Either[String, Int]], 39 | option: Tuple1[Option[String]], 40 | array: Tuple1[Array[String]], 41 | list: Tuple1[List[String]], 42 | map: Tuple1[Map[String, String]], 43 | stringInt: (String, Int), 44 | stringIntDouble: (String, Int, Double), 45 | stringIntDoubleEmbedded: (String, Int, Double, Embedded)) 46 | 47 | case class EitherCaseClass( 48 | eitherBooleanString: Either[Boolean, String], 49 | eitherIntDouble: Either[Int, Double], 50 | eitherEnumEmbedded: Either[FooBar, Embedded], 51 | eitherEnumerationEmbedded: Either[FooBarBaz.Value, Embedded], 52 | eitherMyTypeEither: Either[MyType, Either[Boolean, String]], 53 | eitherOptionArray: Either[Option[Boolean], Array[String]], 54 | eitherListMap: Either[List[Boolean], Map[String, String]]) 55 | 56 | case class OptionCaseClass( 57 | optionBoolean: Option[Boolean], 58 | optionString: Option[String], 59 | optionInt: Option[Int], 60 | optionDouble: Option[Double], 61 | optionEnum: Option[FooBar], 62 | optionEnumeation: Option[FooBarBaz.Value], 63 | optionEmbedded: Option[Embedded], 64 | optionMyType: Option[MyType], 65 | optionUUID: Option[UUID], 66 | optionTuple: Option[(Boolean, String)], 67 | optionEither: Option[Either[Boolean, String]], 68 | optionOption: Option[Option[String]], 69 | optionArray: Option[Array[String]], 70 | optionList: Option[List[String]], 71 | optionMap: Option[Map[String, String]]) 72 | 73 | case class ArrayCaseClass( 74 | arrayBoolean: Array[Boolean], 75 | arrayString: Array[String], 76 | arrayInt: Array[Int], 77 | arrayDouble: Array[Double], 78 | arrayEnum: Array[FooBar], 79 | arrayEnumeration: Array[FooBarBaz.Value], 80 | arrayEmbedded: Array[Embedded], 81 | arrayMyType: Array[MyType], 82 | arrayUUID: Array[UUID], 83 | arrayTuple: Array[(Boolean, String)], 84 | arrayEither: Array[Either[Boolean, String]], 85 | arrayOption: Array[Option[String]], 86 | arrayArray: Array[Array[String]], 87 | arrayList: Array[List[String]], 88 | arrayMap: Array[Map[String, String]]) 89 | 90 | case class ListCaseClass( 91 | listBoolean: List[Boolean], 92 | listString: List[String], 93 | listInt: List[Int], 94 | listDouble: List[Double], 95 | listEnum: List[FooBar], 96 | listEnumeration: List[FooBarBaz.Value], 97 | listEmbedded: List[Embedded], 98 | listMyType: List[MyType], 99 | listUUID: List[UUID], 100 | listTuple: List[(Boolean, String)], 101 | listEither: List[Either[Boolean, String]], 102 | listOption: List[Option[String]], 103 | listArray: List[Array[String]], 104 | listList: List[List[String]], 105 | listMap: List[Map[String, String]]) 106 | 107 | case class MapCaseClass( 108 | mapBoolean: Map[String, Boolean], 109 | mapString: Map[String, String], 110 | mapInt: Map[String, Int], 111 | mapDouble: Map[String, Double], 112 | mapEnum: Map[String, FooBar], 113 | mapEnumeration: Map[String, FooBarBaz.Value], 114 | mapEmbedded: Map[String, Embedded], 115 | mapMyType: Map[String, MyType], 116 | mapUUID: Map[String, UUID], 117 | mapTuple: Map[String, (Boolean, String)], 118 | mapEither: Map[String, Either[Boolean, String]], 119 | mapOption: Map[String, Option[String]], 120 | mapArray: Map[String, Array[String]], 121 | mapList: Map[String, List[String]], 122 | mapMap: Map[String, Map[String, String]]) 123 | 124 | case class CaseClass( 125 | boolean: Boolean, 126 | string: String, 127 | int: Int, 128 | double: Double, 129 | enum: FooBar, 130 | enumeration: FooBarBaz.Value, 131 | embedded: Embedded, 132 | myType: MyType, 133 | uuid: UUID, 134 | tuple: TupleCaseClass, 135 | either: EitherCaseClass, 136 | option: OptionCaseClass, 137 | array: ArrayCaseClass, 138 | list: ListCaseClass, 139 | map: MapCaseClass) 140 | 141 | case class NoFields() 142 | case class OneField(field: String) 143 | 144 | implicit val FooBarJSON = EnumJson.format(FooBar) 145 | implicit val FooBarBazJSON = EnumerationJson.format(FooBarBaz) 146 | implicit val EmbeddedJSON = Json.format[Embedded] 147 | implicit val TupleCaseClassJSON = Json.format[TupleCaseClass] 148 | implicit val EitherCaseClassJSON = Json.format[EitherCaseClass] 149 | implicit val OptionCaseClassJSON = Json.format[OptionCaseClass] 150 | implicit val ArrayCaseClassJSON = Json.format[ArrayCaseClass] 151 | implicit val ListCaseClassJSON = Json.format[ListCaseClass] 152 | implicit val MapCaseClassJSON = Json.format[MapCaseClass] 153 | implicit val CaseClassJSON = Json.format[CaseClass] 154 | implicit val NoFieldsJSON = Json.noFields[NoFields] 155 | implicit val OneFieldJSON = Json.format[OneField] 156 | 157 | "Json" should { 158 | "support enumerations" in { 159 | skipped 160 | } 161 | 162 | "support case classes" in { 163 | skipped 164 | } 165 | 166 | "support empty case classes" in { 167 | skipped 168 | } 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/main/scala/Options.scala: -------------------------------------------------------------------------------- 1 | package org.refptr.iscala 2 | 3 | import java.io.File 4 | 5 | import scalax.io.JavaConverters._ 6 | import scalax.file.Path 7 | 8 | import scopt.{OptionParser,Read} 9 | import scala.util.parsing.combinator.JavaTokenParsers 10 | import sbt.{ModuleID,CrossVersion,Resolver,MavenRepository,Level} 11 | 12 | private object CustomReads { 13 | implicit val pathReads: Read[Path] = Read.reads(Path.fromString _) 14 | 15 | implicit val modulesReads: Read[List[ModuleID]] = Read.reads { string => 16 | object ModulesParsers extends JavaTokenParsers { 17 | def crossVersion: Parser[CrossVersion] = "::" ^^^ CrossVersion.binary | ":" ^^^ CrossVersion.Disabled 18 | 19 | def string: Parser[String] = "[^,:]+".r 20 | 21 | def module: Parser[ModuleID] = string ~ crossVersion ~ string ~ ":" ~ string ^^ { 22 | case organization ~ crossVersion ~ name ~ _ ~ revision => 23 | ModuleID(organization, name, revision, crossVersion=crossVersion) 24 | } 25 | 26 | def modules: Parser[List[ModuleID]] = rep1sep(module, ",") 27 | 28 | def parse(input: String): List[ModuleID] = { 29 | parseAll(modules, input) match { 30 | case Success(result, _) => 31 | result 32 | case failure: NoSuccess => 33 | throw new IllegalArgumentException(s"Invalid module specification.") 34 | } 35 | } 36 | } 37 | 38 | ModulesParsers.parse(string) 39 | } 40 | 41 | implicit val resolversReads: Read[List[Resolver]] = Read.reads { string => 42 | object Bintray { 43 | def unapply(string: String): Option[(String, String)] = { 44 | string.split(":") match { 45 | case Array("bintray", user, repo) => Some((user, repo)) 46 | case _ => None 47 | } 48 | } 49 | } 50 | 51 | string.split(",").toList.flatMap { 52 | case "sonatype" => Resolver.sonatypeRepo("releases") :: Resolver.sonatypeRepo("snapshots") :: Nil 53 | case "sonatype:releases" => Resolver.sonatypeRepo("releases") :: Nil 54 | case "sonatype:snapshots" => Resolver.sonatypeRepo("snapshots") :: Nil 55 | case "typesafe" => Resolver.typesafeRepo("releases") :: Resolver.typesafeRepo("snapshots") :: Nil 56 | case "typesafe:releases" => Resolver.typesafeRepo("releases") :: Nil 57 | case "typesafe:snapshots" => Resolver.typesafeRepo("snapshots") :: Nil 58 | case "typesafe-ivy" => Resolver.typesafeIvyRepo("releases") :: Resolver.typesafeIvyRepo("snapshots") :: Nil 59 | case "typesafe-ivy:releases" => Resolver.typesafeIvyRepo("releases") :: Nil 60 | case "typesafe-ivy:snapshots" => Resolver.typesafeIvyRepo("snapshots") :: Nil 61 | case Bintray(user, repo) => Resolver.bintrayRepo(user, repo) :: Nil 62 | case url => MavenRepository(url, url) :: Nil 63 | } 64 | } 65 | } 66 | 67 | class Options(args: Array[String]) { 68 | val IPyHome = Path.fromString(System.getProperty("user.home")) / ".ipython" 69 | 70 | case class Config( 71 | connection_file: Option[Path] = None, 72 | parent: Boolean = false, 73 | profile_dir: Path = IPyHome / "profile_scala", 74 | debug: Boolean = false, 75 | javacp: Boolean = true, 76 | classpath: String = "", 77 | modules: List[ModuleID] = Nil, 78 | resolvers: List[Resolver] = Nil, 79 | args: List[String] = Nil) 80 | 81 | val config: Config = { 82 | import CustomReads._ 83 | 84 | val parser = new scopt.OptionParser[Config]("IScala") { 85 | opt[Path]('f', "connection-file") 86 | .action { (connection_file, config) => config.copy(connection_file = Some(connection_file)) } 87 | .text("path to IPython's connection file") 88 | 89 | opt[Path]("profile") 90 | .action { (profile, config) => config.copy(connection_file = Some(profile)) } 91 | .text("alias for --connection-file=FILE") 92 | 93 | opt[Unit]("parent") 94 | .action { (_, config) => config.copy(parent = true) } 95 | .text("indicate that IPython started this engine") 96 | 97 | opt[Path]("profile-dir") 98 | .action { (profile_dir, config) => config.copy(profile_dir = profile_dir) } 99 | .text("location of the IPython profile to use") 100 | 101 | opt[Unit]('d', "debug") 102 | .action { (_, config) => config.copy(debug = true) } 103 | .text("print debug messages to the terminal") 104 | 105 | opt[Unit]('J', "no-javacp") 106 | .action { (_, config) => config.copy(javacp = false) } 107 | .text("use java's classpath for the embedded interpreter") 108 | 109 | opt[String]('c', "classpath") 110 | .unbounded() 111 | .action { (classpath, config) => config.copy(classpath = ClassPath.join(config.classpath, classpath)) } 112 | .text("scpecify where to find user class files, e.g. -c my_project/target/scala-2.11/classes") 113 | 114 | opt[List[ModuleID]]('m', "modules") 115 | .unbounded() 116 | .action { (modules, config) => config.copy(modules = config.modules ++ modules) } 117 | .text("automatically managed dependencies, e.g. -m org.parboiled::parboiled:2.0.1") 118 | 119 | opt[List[Resolver]]('r', "resolvers") 120 | .unbounded() 121 | .action { (resolvers, config) => config.copy(resolvers = config.resolvers ++ resolvers) } 122 | .text("additional resolvers for automatically managed dependencies, e.g. -r sonatype:releases") 123 | 124 | // arg[String]("...") 125 | // .unbounded() 126 | // .optional() 127 | // .action { (arg, config) => config.copy(args=config.args :+ arg) } 128 | // .text("arguments to pass directly to Scala compiler") 129 | 130 | help("help") text("prints this usage text") 131 | } 132 | 133 | // parser.parse(args, Config()) getOrElse { sys.exit(1) } 134 | 135 | val (iscala_args, scala_args) = args.span(_ != "--") 136 | 137 | parser.parse(iscala_args, Config()) map { 138 | _.copy(args=scala_args.drop(1).toList) 139 | } getOrElse { 140 | sys.exit(1) 141 | } 142 | } 143 | 144 | if (config.debug) { 145 | logger.setLevel(Level.Debug) 146 | } 147 | 148 | Settings.libraryDependencies = config.modules 149 | Settings.resolvers = config.resolvers 150 | } 151 | -------------------------------------------------------------------------------- /src/main/scala/IScala.scala: -------------------------------------------------------------------------------- 1 | package org.refptr.iscala 2 | 3 | import sun.misc.{Signal,SignalHandler} 4 | 5 | import org.zeromq.ZMQ 6 | 7 | import scalax.io.JavaConverters._ 8 | import scalax.file.Path 9 | 10 | import json.JsonUtil._ 11 | import msg._ 12 | 13 | object IScala extends App { 14 | private val options = new Options(args) 15 | 16 | def config: Options#Config = options.config 17 | 18 | val thread = new Thread { 19 | override def run() { 20 | val iscala = new IScala(options.config) 21 | iscala.heartBeat.join() 22 | } 23 | } 24 | 25 | thread.setName("IScala") 26 | thread.setDaemon(true) 27 | thread.start() 28 | thread.join() 29 | } 30 | 31 | class IScala(config: Options#Config) extends Parent { 32 | val connection = config.connection_file match { 33 | case Some(path) => path.string.as[Connection] 34 | case None => 35 | val file = Path(s"kernel-${Util.getpid()}.json") 36 | logger.info(s"connect ipython with --existing ${file.toAbsolute.path}") 37 | val connection = Connection.default 38 | file.write(toJSON(connection)) 39 | connection 40 | } 41 | 42 | val classpath = { 43 | val (baseClasspath, baseModules) = config.javacp match { 44 | case false => ("", Modules.Compiler :: Nil) 45 | case true => (sys.props("java.class.path"), Nil) 46 | } 47 | 48 | val modules = baseModules ++ config.modules 49 | val resolvers = config.resolvers 50 | 51 | val resolved = Sbt.resolve(modules, resolvers).map(_.classpath) getOrElse { 52 | sys.error("Failed to resolve dependencies") 53 | } 54 | 55 | ClassPath.join(baseClasspath, config.classpath, resolved) 56 | } 57 | 58 | val interpreter = new Interpreter(classpath, config.args) 59 | 60 | val zmq = new Sockets(connection) 61 | val ipy = new Communication(zmq, connection) 62 | 63 | def welcome() { 64 | import scala.util.Properties._ 65 | println(s"Welcome to Scala $versionNumberString ($javaVmName, Java $javaVersion)") 66 | } 67 | 68 | Runtime.getRuntime().addShutdownHook(new Thread() { 69 | override def run() { 70 | logger.debug("Terminating IScala") 71 | interpreter.finish() 72 | } 73 | }) 74 | 75 | Signal.handle(new Signal("INT"), new SignalHandler { 76 | private var previously = System.currentTimeMillis 77 | 78 | def handle(signal: Signal) { 79 | if (!config.parent) { 80 | val now = System.currentTimeMillis 81 | if (now - previously < 500) sys.exit() else previously = now 82 | } 83 | 84 | interpreter.cancel() 85 | } 86 | }) 87 | 88 | class HeartBeat extends Thread { 89 | override def run() { 90 | ZMQ.proxy(zmq.heartbeat, zmq.heartbeat, null) 91 | } 92 | } 93 | 94 | (config.connection_file, config.parent) match { 95 | case (Some(file), true) => 96 | // This setup means that this kernel was started by IPython. Currently 97 | // IPython is unable to terminate IScala without explicitly killing it 98 | // or sending shutdown_request. To fix that, IScala watches the profile 99 | // file whether it exists or not. When the file is removed, IScala is 100 | // terminated. 101 | 102 | class FileWatcher(file: Path, interval: Int) extends Thread { 103 | override def run() { 104 | while (true) { 105 | if (file.exists) Thread.sleep(interval) 106 | else sys.exit() 107 | } 108 | } 109 | } 110 | 111 | val fileWatcher = new FileWatcher(file, 1000) 112 | fileWatcher.setName(s"FileWatcher(${file.path})") 113 | fileWatcher.start() 114 | case _ => 115 | } 116 | 117 | val ExecuteHandler = new ExecuteHandler(this) 118 | val CompleteHandler = new CompleteHandler(this) 119 | val KernelInfoHandler = new KernelInfoHandler(this) 120 | val ObjectInfoHandler = new ObjectInfoHandler(this) 121 | val ConnectHandler = new ConnectHandler(this) 122 | val ShutdownHandler = new ShutdownHandler(this) 123 | val HistoryHandler = new HistoryHandler(this) 124 | val CommOpenHandler = new CommOpenHandler(this) 125 | val CommMsgHandler = new CommMsgHandler(this) 126 | val CommCloseHandler = new CommCloseHandler(this) 127 | 128 | class Conn(msg: Msg[_]) extends display.Conn { 129 | def display_data(data: Data) { 130 | ipy.send_display_data(msg, data) 131 | } 132 | } 133 | 134 | class EventLoop(socket: ZMQ.Socket) extends Thread { 135 | def dispatch[T <: FromIPython](msg: Msg[T]) { 136 | display.IScala.withConn(new Conn(msg)) { 137 | msg.header.msg_type match { 138 | case MsgType.execute_request => ExecuteHandler(socket, msg.asInstanceOf[Msg[execute_request]]) 139 | case MsgType.complete_request => CompleteHandler(socket, msg.asInstanceOf[Msg[complete_request]]) 140 | case MsgType.kernel_info_request => KernelInfoHandler(socket, msg.asInstanceOf[Msg[kernel_info_request]]) 141 | case MsgType.object_info_request => ObjectInfoHandler(socket, msg.asInstanceOf[Msg[object_info_request]]) 142 | case MsgType.connect_request => ConnectHandler(socket, msg.asInstanceOf[Msg[connect_request]]) 143 | case MsgType.shutdown_request => ShutdownHandler(socket, msg.asInstanceOf[Msg[shutdown_request]]) 144 | case MsgType.history_request => HistoryHandler(socket, msg.asInstanceOf[Msg[history_request]]) 145 | case MsgType.comm_open => CommOpenHandler(socket, msg.asInstanceOf[Msg[comm_open]]) 146 | case MsgType.comm_msg => CommMsgHandler(socket, msg.asInstanceOf[Msg[comm_msg]]) 147 | case MsgType.comm_close => CommCloseHandler(socket, msg.asInstanceOf[Msg[comm_close]]) 148 | case _ => 149 | } 150 | } 151 | } 152 | 153 | override def run() { 154 | try { 155 | while (true) { 156 | ipy.recv(socket).foreach(dispatch) 157 | } 158 | } catch { 159 | case exc: Exception => 160 | zmq.terminate() // this will gracefully terminate heartbeat 161 | throw exc 162 | } 163 | } 164 | } 165 | 166 | val heartBeat = new HeartBeat 167 | heartBeat.setName("HeartBeat") 168 | heartBeat.start() 169 | 170 | logger.debug("Starting kernel event loop") 171 | ipy.send_status(ExecutionState.starting) 172 | 173 | val requestsLoop = new EventLoop(zmq.requests) 174 | requestsLoop.setName("RequestsEventLoop") 175 | requestsLoop.start() 176 | 177 | welcome() 178 | } 179 | -------------------------------------------------------------------------------- /src/main/scala/Magics.scala: -------------------------------------------------------------------------------- 1 | package org.refptr.iscala 2 | 3 | import scala.util.parsing.combinator.JavaTokenParsers 4 | import sbt.{ModuleID,CrossVersion,Resolver,MavenRepository} 5 | 6 | trait MagicParsers[T] extends JavaTokenParsers { 7 | def string: Parser[String] = stringLiteral ^^ { 8 | case string => string.stripPrefix("\"").stripSuffix("\"") 9 | } 10 | 11 | def magic: Parser[T] 12 | 13 | def parse(input: String): Either[T, String] = { 14 | parseAll(magic, input) match { 15 | case Success(result, _) => Left(result) 16 | case failure: NoSuccess => Right(failure.toString) 17 | } 18 | } 19 | } 20 | 21 | object EmptyParsers extends MagicParsers[Unit] { 22 | def magic: Parser[Unit] = "".^^^(()) 23 | } 24 | 25 | object EntireParsers extends MagicParsers[String] { 26 | def magic: Parser[String] = ".*".r 27 | } 28 | 29 | sealed trait Op 30 | case object Add extends Op 31 | case object Del extends Op 32 | case object Show extends Op 33 | case class LibraryDependencies(op: Op, modules: List[ModuleID]=Nil) 34 | case class Resolvers(op: Op, resolvers: List[Resolver]=Nil) 35 | case class TypeSpec(code: String, verbose: Boolean) 36 | 37 | object LibraryDependenciesParser extends MagicParsers[LibraryDependencies] { 38 | def crossVersion: Parser[CrossVersion] = "%%" ^^^ CrossVersion.binary | "%" ^^^ CrossVersion.Disabled 39 | 40 | def module: Parser[ModuleID] = string ~ crossVersion ~ string ~ "%" ~ string ^^ { 41 | case organization ~ crossVersion ~ name ~ _ ~ revision => 42 | ModuleID(organization, name, revision, crossVersion=crossVersion) 43 | } 44 | 45 | def op: Parser[Op] = "+=" ^^^ Add | "-=" ^^^ Del 46 | 47 | def modify: Parser[LibraryDependencies] = op ~ module ^^ { 48 | case op ~ module => LibraryDependencies(op, List(module)) 49 | } 50 | 51 | def show: Parser[LibraryDependencies] = "" ^^^ LibraryDependencies(Show) 52 | 53 | def magic: Parser[LibraryDependencies] = modify | show 54 | } 55 | 56 | object TypeParser extends MagicParsers[TypeSpec] { 57 | def magic: Parser[TypeSpec] = opt("-v" | "--verbose") ~ ".*".r ^^ { 58 | case verbose ~ code => TypeSpec(code, verbose.isDefined) 59 | } 60 | } 61 | 62 | object ResolversParser extends MagicParsers[Resolvers] { 63 | 64 | def sonatypeReleases: Parser[Resolver] = 65 | "sonatypeReleases" ^^^ Resolver.sonatypeRepo("releases") 66 | def sonatypeSnapshots: Parser[Resolver] = 67 | "sonatypeSnapshots" ^^^ Resolver.sonatypeRepo("snapshots") 68 | 69 | def optsRepo: Parser[Resolver] = 70 | sonatypeReleases | sonatypeSnapshots 71 | 72 | def sonatypeRepo: Parser[Resolver] = 73 | "sonatypeRepo" ~> "(" ~> string <~ ")" ^^ { case name => Resolver.sonatypeRepo(name) } 74 | def typesafeRepo: Parser[Resolver] = 75 | "typesafeRepo" ~> "(" ~> string <~ ")" ^^ { case name => Resolver.typesafeRepo(name) } 76 | def typesafeIvyRepo: Parser[Resolver] = 77 | "typesafeIvyRepo" ~> "(" ~> string <~ ")" ^^ { case name => Resolver.typesafeIvyRepo(name) } 78 | def sbtPluginRepo: Parser[Resolver] = 79 | "sbtPluginRepo" ~> "(" ~> string <~ ")" ^^ { case name => Resolver.sbtPluginRepo(name) } 80 | def bintrayRepo: Parser[Resolver] = 81 | "bintrayRepo" ~> "(" ~> string ~ "," ~ string <~ ")" ^^ { case user ~ _ ~ repo => Resolver.bintrayRepo(user, repo) } 82 | def jcenterRepo: Parser[Resolver] = 83 | "jcenterRepo" ^^^ Resolver.jcenterRepo 84 | 85 | def resolverRepo: Parser[Resolver] = 86 | sonatypeRepo | typesafeRepo | typesafeIvyRepo | sbtPluginRepo | bintrayRepo | jcenterRepo 87 | 88 | def optsRepos: Parser[Resolver] = "Opts" ~> "." ~> "resolver" ~> "." ~> optsRepo 89 | 90 | def resolverRepos: Parser[Resolver] = "Resolver" ~> "." ~> resolverRepo 91 | 92 | def mavenRepo: Parser[Resolver] = string ~ "at" ~ string ^^ { 93 | case name ~ _ ~ root => MavenRepository(name, root) 94 | } 95 | 96 | def resolver: Parser[Resolver] = optsRepos | resolverRepos | mavenRepo 97 | 98 | def op: Parser[Op] = "+=" ^^^ Add | "-=" ^^^ Del 99 | 100 | def modify: Parser[Resolvers] = op ~ resolver ^^ { 101 | case op ~ resolver => Resolvers(op, List(resolver)) 102 | } 103 | 104 | def show: Parser[Resolvers] = "" ^^^ Resolvers(Show) 105 | 106 | def magic: Parser[Resolvers] = modify | show 107 | } 108 | 109 | object Settings { 110 | var libraryDependencies: List[ModuleID] = Nil 111 | var resolvers: List[Resolver] = Nil 112 | } 113 | 114 | abstract class Magic[T](val name: Symbol, parser: MagicParsers[T]) { 115 | def apply(interpreter: Interpreter, input: String): Results.Result = { 116 | parser.parse(input) match { 117 | case Left(result) => 118 | handle(interpreter, result) 119 | case Right(error) => 120 | println(error) 121 | Results.Error // TODO: error 122 | } 123 | } 124 | 125 | def handle(interpreter: Interpreter, result: T): Results.Result 126 | } 127 | 128 | object Magic { 129 | val magics = List(LibraryDependenciesMagic, ResolversMagic, UpdateMagic, TypeMagic, ResetMagic, ClassPathMagic) 130 | val pattern = "^%([a-zA-Z_][a-zA-Z0-9_]*)(.*)\n*$".r 131 | 132 | def unapply(code: String): Option[(String, String, Option[Magic[_]])] = code match { 133 | case pattern(name, input) => Some((name, input, magics.find(_.name.name == name))) 134 | case _ => None 135 | } 136 | } 137 | 138 | abstract class EmptyMagic(name: Symbol) extends Magic(name, EmptyParsers) { 139 | def handle(interpreter: Interpreter, unit: Unit): Results.Result = handle(interpreter) 140 | def handle(interpreter: Interpreter): Results.Result 141 | } 142 | 143 | abstract class EntireMagic(name: Symbol) extends Magic(name, EntireParsers) { 144 | def handle(interpreter: Interpreter, code: String): Results.Result 145 | } 146 | 147 | object LibraryDependenciesMagic extends Magic('libraryDependencies, LibraryDependenciesParser) { 148 | def handle(interpreter: Interpreter, dependencies: LibraryDependencies) = { 149 | dependencies match { 150 | case LibraryDependencies(Show, _) => 151 | println(Settings.libraryDependencies) 152 | case LibraryDependencies(Add, dependencies) => 153 | Settings.libraryDependencies ++= dependencies 154 | case LibraryDependencies(Del, dependencies) => 155 | // TODO: should be 156 | // 157 | // Settings.libraryDependencies = Settings.libraryDependencies.filterNot(dependencies contains _) 158 | // 159 | // but `CrossVersion` doesn't implement `equals` method, so we have to compare manually. 160 | 161 | Settings.libraryDependencies = Settings.libraryDependencies.filterNot { existingDep => 162 | dependencies.find { removeDep => 163 | removeDep.organization == existingDep.organization && 164 | removeDep.name == existingDep.name && 165 | removeDep.revision == existingDep.revision && 166 | (removeDep.crossVersion == existingDep.crossVersion || 167 | (removeDep.crossVersion.isInstanceOf[CrossVersion.Binary] && 168 | existingDep.crossVersion.isInstanceOf[CrossVersion.Binary])) 169 | } isDefined 170 | } 171 | } 172 | Results.NoValue 173 | } 174 | } 175 | 176 | object ResolversMagic extends Magic('resolvers, ResolversParser) { 177 | def handle(interpreter: Interpreter, resolvers: Resolvers) = { 178 | resolvers match { 179 | case Resolvers(Show, _) => 180 | println(Settings.resolvers) 181 | case Resolvers(Add, resolvers) => 182 | Settings.resolvers ++= resolvers 183 | case Resolvers(Del, resolvers) => 184 | Settings.resolvers = Settings.resolvers.filterNot(resolvers contains _) 185 | } 186 | Results.NoValue 187 | } 188 | } 189 | 190 | object UpdateMagic extends EmptyMagic('update) { 191 | def handle(interpreter: Interpreter) = { 192 | Sbt.resolve(Settings.libraryDependencies, Settings.resolvers) map { cp => 193 | interpreter.classpath(cp) 194 | if (interpreter.isInitialized) interpreter.reset() 195 | Results.NoValue 196 | } getOrElse { 197 | Results.Error 198 | } 199 | } 200 | } 201 | 202 | object TypeMagic extends Magic('type, TypeParser) { 203 | def handle(interpreter: Interpreter, spec: TypeSpec) = { 204 | interpreter.typeInfo(spec.code, spec.verbose).map(println) 205 | Results.NoValue 206 | } 207 | } 208 | 209 | object ResetMagic extends EmptyMagic('reset) { 210 | def handle(interpreter: Interpreter) = { 211 | interpreter.reset() 212 | Results.NoValue 213 | } 214 | } 215 | 216 | object ClassPathMagic extends EmptyMagic('classpath) { 217 | def handle(interpreter: Interpreter) = { 218 | val cp = interpreter.settings.classpath.value.split(java.io.File.pathSeparator).toList 219 | interpreter.intp.beSilentDuring { interpreter.bind("cp", "List[String]", cp) } 220 | println(interpreter.settings.classpath.value) 221 | Results.NoValue 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /src/main/scala/Handlers.scala: -------------------------------------------------------------------------------- 1 | package org.refptr.iscala 2 | 3 | import org.zeromq.ZMQ 4 | 5 | import org.refptr.iscala.msg._ 6 | import org.refptr.iscala.msg.formats._ 7 | 8 | trait Parent { 9 | val connection: Connection 10 | val ipy: Communication 11 | val interpreter: Interpreter 12 | } 13 | 14 | abstract class Handler[T <: FromIPython](parent: Parent) extends ((ZMQ.Socket, Msg[T]) => Unit) 15 | 16 | class ExecuteHandler(parent: Parent) extends Handler[execute_request](parent) { 17 | import parent.{ipy,interpreter} 18 | 19 | class StreamCapture(msg: Msg[_]) extends Capture { 20 | def stream(name: String, data: String) { 21 | ipy.silently { 22 | ipy.send_stream(msg, "stdout", data) 23 | } 24 | } 25 | 26 | def stdout(data: String) = stream("stdout", data) 27 | def stderr(data: String) = stream("stderr", data) 28 | } 29 | 30 | def apply(socket: ZMQ.Socket, msg: Msg[execute_request]) { 31 | import interpreter.n 32 | 33 | val content = msg.content 34 | val code = content.code 35 | val silent = content.silent || code.trim.endsWith(";") 36 | val store_history = content.store_history getOrElse !silent 37 | 38 | if (code.trim.isEmpty) { 39 | ipy.send_ok(msg, n) 40 | return 41 | } 42 | 43 | interpreter.nextInput() 44 | interpreter.storeInput(code) 45 | 46 | ipy.publish(msg.pub(MsgType.pyin, 47 | pyin( 48 | execution_count=n, 49 | code=code))) 50 | 51 | ipy.busy { 52 | val capture = new StreamCapture(msg) 53 | interpreter.resetOutput() 54 | 55 | code match { 56 | case Magic(name, input, Some(magic)) => 57 | val ir = capture { 58 | magic(interpreter, input) 59 | } 60 | 61 | ir match { 62 | case _: Results.Success => 63 | ipy.send_ok(msg, n) 64 | case _: Results.Failure => 65 | ipy.send_error(msg, n, interpreter.output.toString) 66 | } 67 | case Magic(name, _, None) => 68 | ipy.send_error(msg, n, s"ERROR: Line magic function `%$name` not found.") 69 | case _ => 70 | val ir = capture { 71 | interpreter.interpret(code) 72 | } 73 | 74 | ir match { 75 | case result @ Results.Value(value, tpe, repr) if !silent => 76 | if (store_history) { 77 | repr.default foreach { output => 78 | interpreter.storeOutput(result, output) 79 | } 80 | } 81 | 82 | ipy.publish(msg.pub(MsgType.pyout, 83 | pyout( 84 | execution_count=n, 85 | data=repr))) 86 | 87 | ipy.send_ok(msg, n) 88 | case _: Results.Success => 89 | ipy.send_ok(msg, n) 90 | case exc @ Results.Exception(name, message, _, _) => 91 | ipy.send_error(msg, pyerr(n, name, message, exc.traceback)) 92 | case Results.Error => 93 | ipy.send_error(msg, n, interpreter.output.toString) 94 | case Results.Incomplete => 95 | ipy.send_error(msg, n, "incomplete") 96 | case Results.Cancelled => 97 | ipy.send_abort(msg, n) 98 | } 99 | } 100 | } 101 | } 102 | } 103 | 104 | class CompleteHandler(parent: Parent) extends Handler[complete_request](parent) { 105 | import parent.{ipy,interpreter} 106 | 107 | def apply(socket: ZMQ.Socket, msg: Msg[complete_request]) { 108 | val text = if (msg.content.text.isEmpty) { 109 | // Notebook only gives us line and cursor_pos 110 | val pos = msg.content.cursor_pos 111 | val upToCursor = msg.content.line.splitAt(pos)._1 112 | upToCursor.split("""[^\w.%]""").last 113 | } else { 114 | msg.content.text 115 | } 116 | 117 | val matches = if (msg.content.line.startsWith("%")) { 118 | val prefix = text.stripPrefix("%") 119 | Magic.magics.map(_.name.name).filter(_.startsWith(prefix)).map("%" + _) 120 | } else { 121 | val completions = interpreter.completions(text) 122 | val common = Util.commonPrefix(completions) 123 | var prefix = Util.suffixPrefix(text, common) 124 | completions.map(_.stripPrefix(prefix)).map(text + _) 125 | } 126 | 127 | ipy.send(socket, msg.reply(MsgType.complete_reply, 128 | complete_reply( 129 | status=ExecutionStatus.ok, 130 | matches=matches, 131 | matched_text=text))) 132 | } 133 | } 134 | 135 | class KernelInfoHandler(parent: Parent) extends Handler[kernel_info_request](parent) { 136 | import parent.ipy 137 | 138 | def apply(socket: ZMQ.Socket, msg: Msg[kernel_info_request]) { 139 | val scalaVersion = Util.scalaVersion 140 | .split(Array('.', '-')) 141 | .take(3) 142 | .map(_.toInt) 143 | .toList 144 | 145 | ipy.send(socket, msg.reply(MsgType.kernel_info_reply, 146 | kernel_info_reply( 147 | protocol_version=Protocol.version, 148 | language_version=scalaVersion, 149 | language="scala"))) 150 | } 151 | } 152 | 153 | class ConnectHandler(parent: Parent) extends Handler[connect_request](parent) { 154 | import parent.ipy 155 | 156 | def apply(socket: ZMQ.Socket, msg: Msg[connect_request]) { 157 | ipy.send(socket, msg.reply(MsgType.connect_reply, 158 | connect_reply( 159 | shell_port=parent.connection.shell_port, 160 | iopub_port=parent.connection.iopub_port, 161 | stdin_port=parent.connection.stdin_port, 162 | hb_port=parent.connection.hb_port))) 163 | } 164 | } 165 | 166 | class ShutdownHandler(parent: Parent) extends Handler[shutdown_request](parent) { 167 | import parent.ipy 168 | 169 | def apply(socket: ZMQ.Socket, msg: Msg[shutdown_request]) { 170 | ipy.send(socket, msg.reply(MsgType.shutdown_reply, 171 | shutdown_reply( 172 | restart=msg.content.restart))) 173 | sys.exit() 174 | } 175 | } 176 | 177 | class ObjectInfoHandler(parent: Parent) extends Handler[object_info_request](parent) { 178 | import parent.ipy 179 | 180 | def apply(socket: ZMQ.Socket, msg: Msg[object_info_request]) { 181 | ipy.send(socket, msg.reply(MsgType.object_info_reply, 182 | object_info_notfound_reply( 183 | name=msg.content.oname))) 184 | } 185 | } 186 | 187 | class HistoryHandler(parent: Parent) extends Handler[history_request](parent) { 188 | import parent.{ipy,interpreter} 189 | 190 | def apply(socket: ZMQ.Socket, msg: Msg[history_request]) { 191 | import scala.slick.driver.SQLiteDriver.simple._ 192 | import Database.dynamicSession 193 | 194 | val raw = msg.content.raw 195 | 196 | var query = for { 197 | (input, output) <- DB.History leftJoin DB.OutputHistory on ((in, out) => in.session === out.session && in.line === out.line) 198 | } yield (input.session, input.line, (if (raw) input.source_raw else input.source), output.output.?) 199 | 200 | msg.content.hist_access_type match { 201 | case HistAccessType.range => 202 | val session = msg.content.session getOrElse 0 203 | 204 | val actualSession = 205 | if (session == 0) interpreter.session.id 206 | else if (session > 0) session 207 | else interpreter.session.id - session 208 | 209 | query = query.filter(_._1 === actualSession) 210 | 211 | for (start <- msg.content.start) 212 | query = query.filter(_._2 >= start) 213 | 214 | for (stop <- msg.content.stop) 215 | query = query.filter(_._2 < stop) 216 | case HistAccessType.tail | HistAccessType.search => 217 | // TODO: add support for `pattern` and `unique` 218 | query = query.sortBy(r => (r._1.desc, r._2.desc)) 219 | 220 | for (n <- msg.content.n) 221 | query = query.take(n) 222 | } 223 | 224 | val rawHistory = DB.db.withDynSession { query.list } 225 | val history = 226 | if (msg.content.output) 227 | rawHistory.map { case (session, line, input, output) => (session, line, Right((input, output))) } 228 | else 229 | rawHistory.map { case (session, line, input, output) => (session, line, Left(input)) } 230 | 231 | ipy.send(socket, msg.reply(MsgType.history_reply, 232 | history_reply( 233 | history=history))) 234 | } 235 | } 236 | 237 | class CommOpenHandler(parent: Parent) extends Handler[comm_open](parent) { 238 | def apply(socket: ZMQ.Socket, msg: Msg[comm_open]) { println(msg) } 239 | } 240 | 241 | class CommMsgHandler(parent: Parent) extends Handler[comm_msg](parent) { 242 | def apply(socket: ZMQ.Socket, msg: Msg[comm_msg]) { println(msg) } 243 | } 244 | 245 | class CommCloseHandler(parent: Parent) extends Handler[comm_close](parent) { 246 | def apply(socket: ZMQ.Socket, msg: Msg[comm_close]) { println(msg) } 247 | } 248 | -------------------------------------------------------------------------------- /examples/Display.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "kernelspec": { 4 | "codemirror_mode": { 5 | "mode": "clike", 6 | "name": "text/x-scala" 7 | }, 8 | "display_name": "IScala (Scala 2.11)", 9 | "language": "scala", 10 | "name": "iscala-2.11" 11 | }, 12 | "name": "", 13 | "signature": "sha256:aee7ddabba7036cdc58036c691d14286a20263f44ac7d09d718829bcbe41ed32" 14 | }, 15 | "nbformat": 3, 16 | "nbformat_minor": 0, 17 | "worksheets": [ 18 | { 19 | "cells": [ 20 | { 21 | "cell_type": "code", 22 | "collapsed": false, 23 | "input": [ 24 | "%libraryDependencies += \"com.scalatags\" %% \"scalatags\" % \"0.4.1\"" 25 | ], 26 | "language": "scala", 27 | "metadata": {}, 28 | "outputs": [] 29 | }, 30 | { 31 | "cell_type": "code", 32 | "collapsed": false, 33 | "input": [ 34 | "%libraryDependencies += \"io.continuum.bokeh\" %% \"bokeh\" % \"0.2\"" 35 | ], 36 | "language": "scala", 37 | "metadata": {}, 38 | "outputs": [] 39 | }, 40 | { 41 | "cell_type": "code", 42 | "collapsed": false, 43 | "input": [ 44 | "%libraryDependencies += \"org.jfree\" % \"jfreechart\" % \"1.0.19\"" 45 | ], 46 | "language": "scala", 47 | "metadata": {}, 48 | "outputs": [] 49 | }, 50 | { 51 | "cell_type": "code", 52 | "collapsed": false, 53 | "input": [ 54 | "%update" 55 | ], 56 | "language": "scala", 57 | "metadata": {}, 58 | "outputs": [] 59 | }, 60 | { 61 | "cell_type": "code", 62 | "collapsed": false, 63 | "input": [ 64 | "
XXX
" 65 | ], 66 | "language": "scala", 67 | "metadata": {}, 68 | "outputs": [] 69 | }, 70 | { 71 | "cell_type": "code", 72 | "collapsed": false, 73 | "input": [ 74 | "import scalatags.Text.all._" 75 | ], 76 | "language": "scala", 77 | "metadata": {}, 78 | "outputs": [] 79 | }, 80 | { 81 | "cell_type": "code", 82 | "collapsed": false, 83 | "input": [ 84 | "div(style:=\"color: red\", \"XXX\")" 85 | ], 86 | "language": "scala", 87 | "metadata": {}, 88 | "outputs": [] 89 | }, 90 | { 91 | "cell_type": "code", 92 | "collapsed": false, 93 | "input": [ 94 | "implicit val HTMLTypedTag = org.refptr.iscala.display.HTMLDisplay[scalatags.Text.TypedTag[String]](_.toString)" 95 | ], 96 | "language": "scala", 97 | "metadata": {}, 98 | "outputs": [] 99 | }, 100 | { 101 | "cell_type": "code", 102 | "collapsed": false, 103 | "input": [ 104 | "div(style:=\"color: red\", \"XXX\")" 105 | ], 106 | "language": "scala", 107 | "metadata": {}, 108 | "outputs": [] 109 | }, 110 | { 111 | "cell_type": "code", 112 | "collapsed": false, 113 | "input": [ 114 | "\n", 115 | " \n", 116 | " \n", 117 | " \n", 118 | " \n", 119 | " \n", 120 | " \n", 121 | " \n", 122 | " \n", 123 | " \n", 124 | " \n", 125 | " \n", 126 | " \n", 127 | "
Header 1Header 2
row 1, cell 1row 1, cell 2
row 2, cell 1row 2, cell 2
" 128 | ], 129 | "language": "scala", 130 | "metadata": {}, 131 | "outputs": [] 132 | }, 133 | { 134 | "cell_type": "code", 135 | "collapsed": false, 136 | "input": [ 137 | "org.refptr.iscala.display.Math(\"\"\"F(k) = \\int_{-\\infty}^{\\infty} f(x) e^{2\\pi i k} dx\"\"\")" 138 | ], 139 | "language": "scala", 140 | "metadata": {}, 141 | "outputs": [] 142 | }, 143 | { 144 | "cell_type": "code", 145 | "collapsed": false, 146 | "input": [ 147 | "org.refptr.iscala.display.Latex(\"\"\"\n", 148 | "\\begin{eqnarray}\n", 149 | "\\nabla \\times \\vec{\\mathbf{B}} -\\, \\frac1c\\, \\frac{\\partial\\vec{\\mathbf{E}}}{\\partial t} & = \\frac{4\\pi}{c}\\vec{\\mathbf{j}} \\\\\n", 150 | "\\nabla \\cdot \\vec{\\mathbf{E}} & = 4 \\pi \\rho \\\\\n", 151 | "\\nabla \\times \\vec{\\mathbf{E}}\\, +\\, \\frac1c\\, \\frac{\\partial\\vec{\\mathbf{B}}}{\\partial t} & = \\vec{\\mathbf{0}} \\\\\n", 152 | "\\nabla \\cdot \\vec{\\mathbf{B}} & = 0 \n", 153 | "\\end{eqnarray}\n", 154 | "\"\"\")" 155 | ], 156 | "language": "scala", 157 | "metadata": {}, 158 | "outputs": [] 159 | }, 160 | { 161 | "cell_type": "code", 162 | "collapsed": false, 163 | "input": [ 164 | "org.refptr.iscala.display.ImageURL(\"http://scala-lang.org/resources/img/smooth-spiral.png\", 100, 200)" 165 | ], 166 | "language": "scala", 167 | "metadata": {}, 168 | "outputs": [] 169 | }, 170 | { 171 | "cell_type": "code", 172 | "collapsed": false, 173 | "input": [ 174 | "org.refptr.iscala.display.YouTubeVideo(\"1j_HxD4iLn8\")" 175 | ], 176 | "language": "scala", 177 | "metadata": {}, 178 | "outputs": [] 179 | }, 180 | { 181 | "cell_type": "code", 182 | "collapsed": false, 183 | "input": [ 184 | "Nil" 185 | ], 186 | "language": "scala", 187 | "metadata": {}, 188 | "outputs": [] 189 | }, 190 | { 191 | "cell_type": "code", 192 | "collapsed": false, 193 | "input": [ 194 | "implicit val PlainNil = org.refptr.iscala.display.PlainDisplay[Nil.type](_ => \"Nil\")" 195 | ], 196 | "language": "scala", 197 | "metadata": {}, 198 | "outputs": [] 199 | }, 200 | { 201 | "cell_type": "code", 202 | "collapsed": false, 203 | "input": [ 204 | "Nil" 205 | ], 206 | "language": "scala", 207 | "metadata": {}, 208 | "outputs": [] 209 | }, 210 | { 211 | "cell_type": "code", 212 | "collapsed": false, 213 | "input": [ 214 | "implicit val PlainNil = org.refptr.iscala.display.PlainDisplay[Nil.type](_ => \"NIL\")" 215 | ], 216 | "language": "scala", 217 | "metadata": {}, 218 | "outputs": [] 219 | }, 220 | { 221 | "cell_type": "code", 222 | "collapsed": false, 223 | "input": [ 224 | "Nil" 225 | ], 226 | "language": "scala", 227 | "metadata": {}, 228 | "outputs": [] 229 | }, 230 | { 231 | "cell_type": "code", 232 | "collapsed": false, 233 | "input": [ 234 | "import io.continuum.bokeh._" 235 | ], 236 | "language": "scala", 237 | "metadata": {}, 238 | "outputs": [] 239 | }, 240 | { 241 | "cell_type": "code", 242 | "collapsed": false, 243 | "input": [ 244 | "val resources = Resources.CDN" 245 | ], 246 | "language": "scala", 247 | "metadata": {}, 248 | "outputs": [] 249 | }, 250 | { 251 | "cell_type": "code", 252 | "collapsed": false, 253 | "input": [ 254 | "implicit val HTMLPlot = org.refptr.iscala.display.HTMLDisplay[Plot, xml.NodeSeq] { plot =>\n", 255 | " val context = new PlotContext().children(plot :: Nil)\n", 256 | " val writer = new HTMLFileWriter(context :: Nil, Some(resources))\n", 257 | " writer.renderPlots(writer.specs())\n", 258 | "}" 259 | ], 260 | "language": "scala", 261 | "metadata": {}, 262 | "outputs": [] 263 | }, 264 | { 265 | "cell_type": "code", 266 | "collapsed": false, 267 | "input": [ 268 | "(resources.styles ++ resources.scripts): xml.NodeSeq" 269 | ], 270 | "language": "scala", 271 | "metadata": {}, 272 | "outputs": [] 273 | }, 274 | { 275 | "cell_type": "code", 276 | "collapsed": false, 277 | "input": [ 278 | "import Tools._\n", 279 | "import math.{Pi=>pi,sin}\n", 280 | "\n", 281 | "val x = -2*pi to 2*pi by 0.1\n", 282 | "val y = x.map(sin)\n", 283 | "\n", 284 | "val source = new ColumnDataSource().addColumn('x, x).addColumn('y, y)\n", 285 | "\n", 286 | "val xdr = new DataRange1d().sources(source.columns('x) :: Nil)\n", 287 | "val ydr = new DataRange1d().sources(source.columns('y) :: Nil)\n", 288 | "\n", 289 | "val plot = new Plot().x_range(xdr).y_range(ydr).tools(Pan|WheelZoom)\n", 290 | "\n", 291 | "val xaxis = new LinearAxis().plot(plot).location(Location.Below)\n", 292 | "val yaxis = new LinearAxis().plot(plot).location(Location.Left)\n", 293 | "plot.below <<= (xaxis :: _)\n", 294 | "plot.left <<= (yaxis :: _)\n", 295 | "\n", 296 | "val circle = new Circle().x('x).y('y).size(5).fill_color(Color.Red).line_color(Color.Black)\n", 297 | "val glyph = new Glyph().data_source(source).glyph(circle)\n", 298 | "\n", 299 | "plot.renderers := List(xaxis, yaxis, glyph)" 300 | ], 301 | "language": "scala", 302 | "metadata": {}, 303 | "outputs": [] 304 | }, 305 | { 306 | "cell_type": "code", 307 | "collapsed": false, 308 | "input": [ 309 | "plot" 310 | ], 311 | "language": "scala", 312 | "metadata": {}, 313 | "outputs": [] 314 | }, 315 | { 316 | "cell_type": "code", 317 | "collapsed": false, 318 | "input": [ 319 | "import org.jfree.data.xy.{XYSeries,XYSeriesCollection}\n", 320 | "import org.jfree.chart.ChartFactory\n", 321 | "import math.{Pi=>pi,sin}\n", 322 | "\n", 323 | "val x = -2*pi to 2*pi by 0.1\n", 324 | "val y = x.map(sin)\n", 325 | "\n", 326 | "val series = new XYSeries(\"sin(x)\")\n", 327 | "(x zip y).foreach { case (x, y) => series.add(x, y) }\n", 328 | "\n", 329 | "val dataset = new XYSeriesCollection()\n", 330 | "dataset.addSeries(series)\n", 331 | "\n", 332 | "val chart = ChartFactory.createXYLineChart(\"y = sin(x)\", \"x\", \"y\", dataset)\n", 333 | "chart.createBufferedImage(500, 300)" 334 | ], 335 | "language": "scala", 336 | "metadata": {}, 337 | "outputs": [] 338 | }, 339 | { 340 | "cell_type": "code", 341 | "collapsed": false, 342 | "input": [], 343 | "language": "scala", 344 | "metadata": {}, 345 | "outputs": [] 346 | } 347 | ], 348 | "metadata": {} 349 | } 350 | ] 351 | } 352 | -------------------------------------------------------------------------------- /src/main/scala/Json.scala: -------------------------------------------------------------------------------- 1 | package org.refptr.iscala 2 | package json 3 | 4 | import scala.reflect.ClassTag 5 | 6 | import play.api.libs.json.{Json=>PlayJson,Reads,Writes,OWrites,Format,JsPath} 7 | import play.api.libs.json.{JsResult,JsSuccess,JsError} 8 | import play.api.libs.json.{JsValue,JsString,JsArray,JsObject} 9 | 10 | object JsonUtil { 11 | def toJSON[T:Writes](obj: T): String = 12 | PlayJson.stringify(PlayJson.toJson(obj)) 13 | 14 | def fromJSON[T:Reads](json: String): T = 15 | PlayJson.parse(json).as[T] 16 | 17 | implicit class JsonString(json: String) { 18 | def as[T:Reads] = fromJSON[T](json) 19 | } 20 | } 21 | 22 | object Json extends core.JsonImpl { 23 | import PlayJson.{JsValueWrapper,toJsFieldJsValueWrapper} 24 | 25 | def fromJson[T:Reads](json: JsValue): JsResult[T] = PlayJson.fromJson(json) 26 | def toJson[T:Writes](obj: T): JsValue = PlayJson.toJson(obj) 27 | 28 | def obj(fields: (String, JsValueWrapper)*): JsObject = PlayJson.obj(fields: _*) 29 | def arr(fields: JsValueWrapper*): JsArray = PlayJson.arr(fields: _*) 30 | 31 | def noFields[A:ClassTag]: Format[A] = NoFields.format 32 | } 33 | 34 | object NoFields { 35 | def reads[T:ClassTag]: Reads[T] = new Reads[T] { 36 | def reads(json: JsValue) = json match { 37 | case JsObject(seq) if seq.isEmpty => 38 | JsSuccess(implicitly[ClassTag[T]].runtimeClass.newInstance.asInstanceOf[T]) 39 | case _ => 40 | JsError("Not an empty object") 41 | } 42 | } 43 | 44 | def writes[T]: OWrites[T] = new OWrites[T] { 45 | def writes(t: T) = JsObject(Nil) 46 | } 47 | 48 | def format[T:ClassTag]: Format[T] = { 49 | Format(reads, writes) 50 | } 51 | } 52 | 53 | object EnumerationJson { 54 | def reads[E <: Enumeration](enum: E): Reads[E#Value] = new Reads[E#Value] { 55 | def reads(json: JsValue): JsResult[E#Value] = json match { 56 | case JsString(string) => 57 | try { 58 | JsSuccess(enum.withName(string)) 59 | } catch { 60 | case _: NoSuchElementException => 61 | JsError(s"Enumeration expected of type: ${enum.getClass}, but it does not appear to contain the value: $string") 62 | } 63 | case _ => 64 | JsError("Value of type String expected") 65 | } 66 | } 67 | 68 | def writes[E <: Enumeration]: Writes[E#Value] = new Writes[E#Value] { 69 | def writes(value: E#Value): JsValue = JsString(value.toString) 70 | } 71 | 72 | def format[E <: Enumeration](enum: E): Format[E#Value] = { 73 | Format(reads(enum), writes) 74 | } 75 | } 76 | 77 | object EnumJson { 78 | def reads[T <: EnumType](enum: Enumerated[T]): Reads[T] = new Reads[T] { 79 | def reads(json: JsValue): JsResult[T] = json match { 80 | case JsString(enum(value)) => JsSuccess(value) 81 | case _ => 82 | JsError("Value of type String expected") 83 | //JsError(s"Enumeration expected of type: ${enum.getClass}, but it does not appear to contain the value: $string") 84 | } 85 | } 86 | 87 | def writes[T <: EnumType]: Writes[T] = new Writes[T] { 88 | def writes(value: T): JsValue = JsString(value.name) 89 | } 90 | 91 | def format[T <: EnumType](enum: Enumerated[T]): Format[T] = { 92 | Format(reads(enum), writes) 93 | } 94 | } 95 | 96 | trait EitherJson { 97 | implicit def EitherReads[T1:Reads, T2:Reads]: Reads[Either[T1, T2]] = new Reads[Either[T1, T2]] { 98 | def reads(json: JsValue) = { 99 | implicitly[Reads[T1]].reads(json) match { 100 | case JsSuccess(left, _) => JsSuccess(Left(left)) 101 | case _ => 102 | implicitly[Reads[T2]].reads(json) match { 103 | case JsSuccess(right, _) => JsSuccess(Right(right)) 104 | case _ => JsError("Either[T1, T2] failed") 105 | } 106 | } 107 | } 108 | } 109 | 110 | implicit def EitherWrites[T1:Writes, T2:Writes]: Writes[Either[T1, T2]] = new Writes[Either[T1, T2]] { 111 | def writes(t: Either[T1, T2]) = t match { 112 | case Left(left) => implicitly[Writes[T1]].writes(left) 113 | case Right(right) => implicitly[Writes[T2]].writes(right) 114 | } 115 | } 116 | } 117 | 118 | trait TupleJson { 119 | implicit def Tuple1Reads[T1:Reads]: Reads[Tuple1[T1]] = new Reads[Tuple1[T1]] { 120 | def reads(json: JsValue) = json match { 121 | case JsArray(List(j1)) => 122 | (implicitly[Reads[T1]].reads(j1)) match { 123 | case JsSuccess(v1, _) => JsSuccess(new Tuple1(v1)) 124 | case e1: JsError => e1 125 | } 126 | case _ => JsError("Not an array") 127 | } 128 | } 129 | 130 | implicit def Tuple1Writes[T1:Writes]: Writes[Tuple1[T1]] = new Writes[Tuple1[T1]] { 131 | def writes(t: Tuple1[T1]) = JsArray(Seq(implicitly[Writes[T1]].writes(t._1))) 132 | } 133 | 134 | implicit def Tuple2Reads[T1:Reads, T2:Reads]: Reads[(T1, T2)] = new Reads[(T1, T2)] { 135 | def reads(json: JsValue) = json match { 136 | case JsArray(List(j1, j2)) => 137 | (implicitly[Reads[T1]].reads(j1), 138 | implicitly[Reads[T2]].reads(j2)) match { 139 | case (JsSuccess(v1, _), JsSuccess(v2, _)) => JsSuccess((v1, v2)) 140 | case (e1: JsError, _) => e1 141 | case (_, e2: JsError) => e2 142 | } 143 | case _ => JsError("Not an array") 144 | } 145 | } 146 | 147 | implicit def Tuple2Writes[T1:Writes, T2:Writes]: Writes[(T1, T2)] = new Writes[(T1, T2)] { 148 | def writes(t: (T1, T2)) = JsArray(Seq(implicitly[Writes[T1]].writes(t._1), 149 | implicitly[Writes[T2]].writes(t._2))) 150 | } 151 | 152 | implicit def Tuple3Reads[T1:Reads, T2:Reads, T3:Reads]: Reads[(T1, T2, T3)] = new Reads[(T1, T2, T3)] { 153 | def reads(json: JsValue) = json match { 154 | case JsArray(List(j1, j2, j3)) => 155 | (implicitly[Reads[T1]].reads(j1), 156 | implicitly[Reads[T2]].reads(j2), 157 | implicitly[Reads[T3]].reads(j3)) match { 158 | case (JsSuccess(v1, _), JsSuccess(v2, _), JsSuccess(v3, _)) => JsSuccess((v1, v2, v3)) 159 | case (e1: JsError, _, _) => e1 160 | case (_, e2: JsError, _) => e2 161 | case (_, _, e3: JsError) => e3 162 | } 163 | case _ => JsError("Not an array") 164 | } 165 | } 166 | 167 | implicit def Tuple3Writes[T1:Writes, T2:Writes, T3:Writes]: Writes[(T1, T2, T3)] = new Writes[(T1, T2, T3)] { 168 | def writes(t: (T1, T2, T3)) = JsArray(Seq(implicitly[Writes[T1]].writes(t._1), 169 | implicitly[Writes[T2]].writes(t._2), 170 | implicitly[Writes[T3]].writes(t._3))) 171 | } 172 | 173 | implicit def Tuple4Reads[T1:Reads, T2:Reads, T3:Reads, T4:Reads]: Reads[(T1, T2, T3, T4)] = new Reads[(T1, T2, T3, T4)] { 174 | def reads(json: JsValue) = json match { 175 | case JsArray(List(j1, j2, j3, j4)) => 176 | (implicitly[Reads[T1]].reads(j1), 177 | implicitly[Reads[T2]].reads(j2), 178 | implicitly[Reads[T3]].reads(j3), 179 | implicitly[Reads[T4]].reads(j4)) match { 180 | case (JsSuccess(v1, _), JsSuccess(v2, _), JsSuccess(v3, _), JsSuccess(v4, _)) => JsSuccess((v1, v2, v3, v4)) 181 | case (e1: JsError, _, _, _) => e1 182 | case (_, e2: JsError, _, _) => e2 183 | case (_, _, e3: JsError, _) => e3 184 | case (_, _, _, e4: JsError) => e4 185 | } 186 | case _ => JsError("Not an array") 187 | } 188 | } 189 | 190 | implicit def Tuple4Writes[T1:Writes, T2:Writes, T3:Writes, T4:Writes]: Writes[(T1, T2, T3, T4)] = new Writes[(T1, T2, T3, T4)] { 191 | def writes(t: (T1, T2, T3, T4)) = JsArray(Seq(implicitly[Writes[T1]].writes(t._1), 192 | implicitly[Writes[T2]].writes(t._2), 193 | implicitly[Writes[T3]].writes(t._3), 194 | implicitly[Writes[T4]].writes(t._4))) 195 | } 196 | } 197 | 198 | trait MapJson { 199 | implicit def MapReads[V:Reads]: Reads[Map[String, V]] = new Reads[Map[String, V]] { 200 | def reads(json: JsValue) = json match { 201 | case JsObject(obj) => 202 | var hasErrors = false 203 | 204 | val r = obj.map { case (key, value) => 205 | implicitly[Reads[V]].reads(value) match { 206 | case JsSuccess(v, _) => Right(key -> v) 207 | case JsError(e) => 208 | hasErrors = true 209 | Left(e.map { case (p, valerr) => (JsPath \ key) ++ p -> valerr }) 210 | } 211 | } 212 | 213 | if (hasErrors) { 214 | JsError(r.collect { case Left(t) => t }.reduceLeft((acc, v) => acc ++ v)) 215 | } else { 216 | JsSuccess(r.collect { case Right(t) => t }.toMap) 217 | } 218 | case _ => JsError("Not an object") 219 | } 220 | } 221 | 222 | implicit def MapWrites[V:Writes]: OWrites[Map[String, V]] = new OWrites[Map[String, V]] { 223 | def writes(t: Map[String, V]) = 224 | JsObject(t.map { case (k, v) => (k, implicitly[Writes[V]].writes(v)) }.toList) 225 | } 226 | } 227 | 228 | trait UUIDJson { 229 | implicit val UUIDReads: Reads[UUID] = new Reads[UUID] { 230 | def reads(json: JsValue) = json match { 231 | case JsString(uuid) => 232 | UUID.fromString(uuid) 233 | .map(JsSuccess(_)) 234 | .getOrElse{JsError(s"Invalid UUID string: $uuid")} 235 | case _ => JsError("Not a string") 236 | } 237 | } 238 | 239 | implicit val UUIDWrites: Writes[UUID] = new Writes[UUID] { 240 | def writes(t: UUID) = JsString(t.toString) 241 | } 242 | } 243 | 244 | trait JsonImplicits extends EitherJson with TupleJson with MapJson with UUIDJson 245 | object JsonImplicits extends JsonImplicits 246 | -------------------------------------------------------------------------------- /src/main/scala/Interpreter.scala: -------------------------------------------------------------------------------- 1 | package org.refptr.iscala 2 | 3 | import java.io.File 4 | import scala.collection.mutable 5 | 6 | import scala.tools.nsc.interpreter.{IMain,CommandLine,IR} 7 | import scala.tools.nsc.util.Exceptional.unwrap 8 | 9 | class Interpreter(classpath: String, args: Seq[String], embedded: Boolean=false) extends InterpreterCompatibility { 10 | protected val commandLine = new CommandLine(args.toList, println) 11 | 12 | if (embedded) { 13 | commandLine.settings.embeddedDefaults[this.type] 14 | } 15 | 16 | private val _classpath: String = { 17 | val cp = commandLine.settings.classpath 18 | cp.value = ClassPath.join(cp.value, classpath) 19 | logger.debug(s"classpath: ${cp.value}") 20 | cp.value 21 | } 22 | 23 | val output = new java.io.StringWriter 24 | val printer = new java.io.PrintWriter(output) 25 | 26 | val intp: IMain = new IMain(settings, printer) 27 | val runner = new Runner(intp.classLoader) 28 | 29 | private var _session = new Session 30 | private var _n: Int = 0 31 | 32 | def session = _session 33 | def n = _n 34 | 35 | val In = mutable.Map.empty[Int, String] 36 | val Out = mutable.Map.empty[Int, Any] 37 | 38 | def reset() { 39 | finish() 40 | _session = new Session 41 | _n = 0 42 | In.clear() 43 | Out.clear() 44 | intp.reset() 45 | } 46 | 47 | def finish() { 48 | _session.endSession(_n) 49 | } 50 | 51 | def isInitialized = intp.isInitializeComplete 52 | 53 | def resetOutput() { // TODO: this shouldn't be maintained externally 54 | output.getBuffer.setLength(0) 55 | } 56 | 57 | def nextInput(): Int = { _n += 1; _n } 58 | 59 | def storeInput(input: String) { 60 | In(n) = input 61 | session.addHistory(n, input) 62 | } 63 | 64 | def storeOutput(result: Results.Value, output: String) { 65 | Out(n) = result.value 66 | session.addOutputHistory(n, output) 67 | bind("_" + n, result.tpe, result.value) 68 | } 69 | 70 | def settings = commandLine.settings 71 | 72 | def classpath(cp: ClassPath) { 73 | settings.classpath.value = ClassPath.join(_classpath, cp.classpath) 74 | } 75 | 76 | lazy val completer = new IScalaCompletion(intp) 77 | 78 | def completions(input: String): List[String] = { 79 | completer.collectCompletions(input) 80 | } 81 | 82 | def withRunner(block: => Results.Result): Results.Result = { 83 | try { 84 | runner.execute { block } result() 85 | } finally { 86 | runner.clear() 87 | } 88 | } 89 | 90 | def withOutput[T](block: => T): (T, String) = { 91 | resetOutput() 92 | try { 93 | (block, output.toString) 94 | } finally { 95 | resetOutput() 96 | } 97 | } 98 | 99 | def withException[T](req: intp.Request)(block: => T): Either[T, Results.Result] = { 100 | try { 101 | Left(block) 102 | } catch { 103 | case original: Throwable => 104 | val exception = unwrap(original) 105 | req.lineRep.bindError(original) 106 | 107 | val name = unmangle(exception.getClass.getName) 108 | val msg = Option(exception.getMessage).map(unmangle _) getOrElse "" 109 | val stacktrace = exception 110 | .getStackTrace() 111 | .takeWhile(_.getFileName != "") 112 | .map(stringify _) 113 | .toList 114 | 115 | Right(Results.Exception(name, msg, stacktrace, exception)) 116 | } 117 | } 118 | 119 | def runCode(moduleName: String, fieldName: String): Any = { 120 | import scala.reflect.runtime.{universe=>u} 121 | val mirror = u.runtimeMirror(intp.classLoader) 122 | val module = mirror.staticModule(moduleName) 123 | val instance = mirror.reflectModule(module).instance 124 | val im = mirror.reflect(instance) 125 | val fieldTerm = u.TermName(fieldName) 126 | val field = im.symbol.typeSignature.member(fieldTerm).asTerm 127 | im.reflectField(field).get 128 | } 129 | 130 | def display(req: intp.Request): Either[Data, Results.Result] = { 131 | import intp.memberHandlers.MemberHandler 132 | 133 | val displayName = "$display" 134 | val displayPath = req.lineRep.pathTo(displayName) 135 | 136 | object DisplayObjectSourceCode extends IMain.CodeAssembler[MemberHandler] { 137 | import intp.global.NoSymbol 138 | 139 | val NS = "org.refptr.iscala" 140 | 141 | val displayResult = req.value match { 142 | case NoSymbol => s"$NS.Data()" 143 | case symbol => s"$NS.display.Repr.stringify(${intp.originalPath(symbol)})" 144 | } 145 | 146 | val preamble = 147 | s""" 148 | |object $displayName { 149 | | ${req.importsPreamble} 150 | | val $displayName: $NS.Data = ${intp.executionWrapper} { 151 | | $displayResult 152 | """.stripMargin 153 | 154 | val postamble = 155 | s""" 156 | | } 157 | | ${req.importsTrailer} 158 | | val $displayName = this${req.accessPath}.$displayName 159 | |} 160 | """.stripMargin 161 | 162 | val generate = (handler: MemberHandler) => "" 163 | } 164 | 165 | val code = DisplayObjectSourceCode(req.handlers) 166 | 167 | if (!req.lineRep.compile(code)) Right(Results.Error) 168 | else withException(req) { runCode(displayPath, displayName) }.left.map { 169 | case Data(items @ _*) => Data(items map { case (mime, string) => (mime, unmangle(string)) }: _*) 170 | } 171 | } 172 | 173 | def loadAndRunReq(req: intp.Request): Results.Result = { 174 | import intp.memberHandlers.{MemberHandler,MemberDefHandler,ValHandler,DefHandler,AssignHandler} 175 | import intp.naming.sessionNames 176 | 177 | def definesValue(handler: MemberHandler): Boolean = { 178 | // MemberHandler.definesValue has slightly different meaning from what is 179 | // needed in loadAndRunReq. We don't want to eagerly evaluate lazy vals 180 | // or 0-arity defs, so we handle those cases here. 181 | if (!handler.definesValue) { 182 | false 183 | } else { 184 | handler match { 185 | case handler: ValHandler if handler.mods.isLazy => false 186 | case handler: DefHandler => false 187 | case _ => true 188 | } 189 | } 190 | } 191 | 192 | def typeOf(handler: MemberHandler): String = { 193 | val symbolName = handler match { 194 | case handler: MemberDefHandler => handler.name 195 | case handler: AssignHandler => handler.name 196 | case _ => intp.global.nme.NO_NAME 197 | } 198 | 199 | req.lookupTypeOf(symbolName) 200 | } 201 | 202 | val handler = req.handlers.last 203 | val hasValue = definesValue(handler) 204 | 205 | val evalName = if (hasValue) sessionNames.result else sessionNames.print 206 | val evalResult = withException(req) { req.lineRep.call(evalName) } 207 | 208 | intp.recordRequest(req) 209 | 210 | evalResult match { 211 | case Left(value) => 212 | lazy val valueType = typeOf(handler) 213 | 214 | if (hasValue && valueType != "Unit") { 215 | display(req) match { 216 | case Left(repr) => Results.Value(value, valueType, repr) 217 | case Right(result) => result 218 | } 219 | } else 220 | Results.NoValue 221 | case Right(result) => result 222 | } 223 | } 224 | 225 | def interpret(line: String): Results.Result = interpret(line, false) 226 | 227 | def interpret(line: String, synthetic: Boolean): Results.Result = { 228 | import intp.Request 229 | 230 | def requestFromLine(line: String, synthetic: Boolean): Either[IR.Result, Request] = { 231 | // XXX: Dirty hack to call a private method IMain.requestFromLine 232 | val method = classOf[IMain].getDeclaredMethod("requestFromLine", classOf[String], classOf[Boolean]) 233 | val args = Array(line, synthetic).map(_.asInstanceOf[AnyRef]) 234 | method.setAccessible(true) 235 | method.invoke(intp, args: _*).asInstanceOf[Either[IR.Result, Request]] 236 | } 237 | 238 | requestFromLine(line, synthetic) match { 239 | case Left(IR.Incomplete) => Results.Incomplete 240 | case Left(_) => Results.Error // parse error 241 | case Right(req) => 242 | // null indicates a disallowed statement type; otherwise compile 243 | // and fail if false (implying e.g. a type error) 244 | if (req == null || !req.compile) Results.Error 245 | else withRunner { loadAndRunReq(req) } 246 | } 247 | } 248 | 249 | def bind(name: String, boundType: String, value: Any, modifiers: List[String] = Nil): IR.Result = { 250 | val imports = (intp.definedTypes ++ intp.definedTerms) match { 251 | case Nil => "/* imports */" 252 | case names => names.map(intp.originalPath(_)).map("import " + _).mkString("\n ") 253 | } 254 | 255 | val bindRep = new intp.ReadEvalPrint() 256 | val source = s""" 257 | |object ${bindRep.evalName} { 258 | | $imports 259 | | var value: ${boundType} = _ 260 | | def set(x: Any) = value = x.asInstanceOf[${boundType}] 261 | |} 262 | """.stripMargin 263 | 264 | bindRep.compile(source) 265 | bindRep.callEither("set", value) match { 266 | case Right(_) => 267 | val line = "%sval %s = %s.value".format(modifiers map (_ + " ") mkString, name, bindRep.evalPath) 268 | intp.interpret(line) 269 | case Left(_) => 270 | IR.Error 271 | } 272 | } 273 | 274 | def cancel() = runner.cancel() 275 | 276 | def stringify(obj: Any): String = unmangle(obj.toString) 277 | 278 | def unmangle(string: String): String = intp.naming.unmangle(string) 279 | 280 | def typeInfo(code: String): Option[String] = { 281 | typeInfo(code, false) 282 | } 283 | 284 | def typeInfo(code: String, deconstruct: Boolean): Option[String] = { 285 | typeInfo(intp.symbolOfLine(code), deconstruct) 286 | } 287 | 288 | def typeInfo(symbol: intp.global.Symbol, deconstruct: Boolean): Option[String] = { 289 | import intp.global.{Symbol,Type,NullaryMethodType,OverloadedType} 290 | 291 | def removeNullaryMethod(symbol: Symbol): Type = { 292 | symbol.typeSignature match { 293 | case tpe @ OverloadedType(_, alt) => 294 | val alternatives = alt.map(removeNullaryMethod _).map(_.typeSymbol) 295 | tpe.copy(alternatives=alternatives) 296 | case NullaryMethodType(resultType) if symbol.isAccessor => resultType 297 | case tpe => tpe 298 | } 299 | } 300 | 301 | if (symbol.exists) { 302 | Some(intp.global.exitingTyper { 303 | val tpe = removeNullaryMethod(symbol) 304 | stringify(if (deconstruct) intp.deconstruct.show(tpe) else tpe) 305 | }) 306 | } else None 307 | } 308 | } 309 | -------------------------------------------------------------------------------- /project/Build.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | import Keys._ 3 | 4 | import sbtassembly.{Plugin=>SbtAssembly} 5 | import org.sbtidea.SbtIdeaPlugin 6 | import com.typesafe.sbt.SbtNativePackager 7 | 8 | import play.api.libs.json.Json 9 | 10 | object Dependencies { 11 | val isScala_2_10 = Def.setting { 12 | scalaVersion.value.startsWith("2.10") 13 | } 14 | 15 | def scala_2_10(moduleID: ModuleID) = 16 | Def.setting { if (isScala_2_10.value) Seq(moduleID) else Seq.empty } 17 | 18 | def scala_2_11_+(moduleID: ModuleID) = 19 | Def.setting { if (!isScala_2_10.value) Seq(moduleID) else Seq.empty } 20 | 21 | val scalaio = { 22 | val namespace = "com.github.scala-incubator.io" 23 | val version = "0.4.3" 24 | Seq(namespace %% "scala-io-core" % version, 25 | namespace %% "scala-io-file" % version) 26 | } 27 | 28 | val ivy = Def.setting { 29 | val organization = if (isScala_2_10.value) "org.scala-sbt" else "org.refptr.sbt211" 30 | organization % "ivy" % "0.13.6" 31 | } 32 | 33 | val scopt = "com.github.scopt" %% "scopt" % "3.2.0" 34 | 35 | val jeromq = "org.zeromq" % "jeromq" % "0.3.4" 36 | 37 | val play_json = "com.typesafe.play" %% "play-json" % "2.4.0-M1" 38 | 39 | val slick = "com.typesafe.slick" %% "slick" % "2.1.0" 40 | 41 | val codec = "commons-codec" % "commons-codec" % "1.9" 42 | 43 | val sqlite = "org.xerial" % "sqlite-jdbc" % "3.7.2" 44 | 45 | val slf4j = "org.slf4j" % "slf4j-nop" % "1.6.4" 46 | 47 | val specs2 = "org.specs2" %% "specs2" % "2.3.11" % Test 48 | 49 | val reflect = Def.setting { "org.scala-lang" % "scala-reflect" % scalaVersion.value } 50 | 51 | val compiler = Def.setting { "org.scala-lang" % "scala-compiler" % scalaVersion.value } 52 | 53 | val paradise = "org.scalamacros" % "paradise" % "2.0.1" cross CrossVersion.full 54 | 55 | val quasiquotes = scala_2_10("org.scalamacros" %% "quasiquotes" % "2.0.0") 56 | 57 | val xml = scala_2_11_+("org.scala-lang.modules" %% "scala-xml" % "1.0.2") 58 | } 59 | 60 | object IScalaBuild extends Build { 61 | override lazy val settings = super.settings ++ Seq( 62 | organization := "org.refptr.iscala", 63 | name := "IScala", 64 | version := "0.3-SNAPSHOT", 65 | description := "Scala-language backend for IPython", 66 | homepage := Some(url("http://iscala.github.io")), 67 | licenses := Seq("MIT-style" -> url("http://www.opensource.org/licenses/mit-license.php")), 68 | scalaVersion := "2.11.2", 69 | crossScalaVersions := Seq("2.10.4", "2.11.2"), 70 | scalacOptions ++= Seq("-deprecation", "-unchecked", "-feature", "-language:_"), 71 | addCompilerPlugin(Dependencies.paradise), 72 | shellPrompt := { state => 73 | "refptr (%s)> ".format(Project.extract(state).currentProject.id) 74 | }, 75 | cancelable := true) 76 | 77 | val release = taskKey[File]("Create a set of archives and installers for new release") 78 | 79 | val ipyCommands = settingKey[List[(String, String)]]("IPython commands (e.g. console) and their command line options") 80 | 81 | val debugPort = settingKey[Int]("Port for remote debugging") 82 | val debugCommand = taskKey[Seq[String]]("JVM command line options enabling remote debugging") 83 | 84 | val develScripts = taskKey[Seq[File]]("Development scripts generated in bin/2.xx/") 85 | val userScripts = taskKey[Seq[File]]("User scripts generated in target/scala-2.xx/bin/") 86 | 87 | val kernelArgs = taskKey[List[String]]("...") 88 | val kernelSpec = taskKey[KernelSpec]("...") 89 | val writeKernelSpec = taskKey[Unit]("...") 90 | 91 | case class KernelSpec( 92 | argv: List[String], 93 | display_name: String, 94 | language: String, 95 | codemirror_mode: Option[SyntaxMode] = None, 96 | env: Map[String, String] = Map.empty, 97 | help_links: List[HelpLink] = Nil) 98 | 99 | case class SyntaxMode(name: String, mode: String) 100 | case class HelpLink(text: String, url: String /*java.net.URL*/) 101 | 102 | implicit val HelpLinkJSON = Json.format[HelpLink] 103 | implicit val SyntaxModeJSON = Json.format[SyntaxMode] 104 | implicit val KernelSpecJSON = Json.format[KernelSpec] 105 | 106 | lazy val ideaSettings = SbtIdeaPlugin.settings 107 | 108 | lazy val assemblySettings = SbtAssembly.assemblySettings ++ { 109 | import SbtAssembly.AssemblyKeys._ 110 | Seq(test in assembly := {}, 111 | jarName in assembly := s"${name.value}.jar", 112 | target in assembly := crossTarget.value / "lib", 113 | assembly <<= assembly dependsOn userScripts) 114 | } 115 | 116 | lazy val packagerSettings = SbtNativePackager.packagerSettings ++ { 117 | import SbtNativePackager.NativePackagerKeys._ 118 | import SbtNativePackager.Universal 119 | import SbtAssembly.AssemblyKeys.assembly 120 | Seq(packageName in Universal := { 121 | s"${name.value}-${scalaBinaryVersion.value}-${version.value}" 122 | }, 123 | mappings in Universal ++= { 124 | assembly.value pair relativeTo(crossTarget.value) 125 | }, 126 | mappings in Universal ++= { 127 | userScripts.value pair relativeTo(crossTarget.value) 128 | }, 129 | mappings in Universal ++= { 130 | val paths = Seq("README.md", "LICENSE") 131 | paths.map(path => (file(path), path)) 132 | }, 133 | release <<= packageZipTarball in Universal) 134 | } 135 | 136 | lazy val pluginSettings = ideaSettings ++ assemblySettings ++ packagerSettings 137 | 138 | lazy val iscalaSettings = Defaults.coreDefaultSettings ++ pluginSettings ++ { 139 | Seq(resolvers += { 140 | val github = url("https://raw.githubusercontent.com/mattpap/mvn-repo/master/releases") 141 | Resolver.url("github-releases", github)(Resolver.ivyStylePatterns) 142 | }, 143 | libraryDependencies ++= { 144 | import Dependencies._ 145 | scalaio ++ Seq(ivy.value, scopt, jeromq, play_json, slick, sqlite, slf4j, specs2, compiler.value) 146 | }, 147 | unmanagedSourceDirectories in Compile += { 148 | (sourceDirectory in Compile).value / s"scala_${scalaBinaryVersion.value}" 149 | }, 150 | fork := true, 151 | initialCommands in Compile := """ 152 | import scala.reflect.runtime.{universe=>u} 153 | import scala.tools.nsc.interpreter._ 154 | import scalax.io.JavaConverters._ 155 | import scalax.file.Path 156 | import scala.slick.driver.SQLiteDriver.simple._ 157 | import Database.dynamicSession 158 | import org.refptr.iscala._ 159 | """, 160 | kernelArgs := { 161 | val classpath = (fullClasspath in Compile).value.files.mkString(java.io.File.pathSeparator) 162 | val main = (mainClass in Compile).value getOrElse sys.error("unknown main class") 163 | List("java", "-cp", classpath, main, "--profile", "{connection_file}", "--parent") 164 | }, 165 | kernelSpec := { 166 | KernelSpec( 167 | argv = kernelArgs.value, 168 | display_name = s"IScala (Scala ${scalaBinaryVersion.value})", 169 | language = "scala", 170 | codemirror_mode = Some(SyntaxMode("text/x-scala", "clike"))) 171 | }, 172 | writeKernelSpec := { 173 | val dir = Path.userHome / ".ipython" / "kernels" / s"IScala-${scalaBinaryVersion.value}" 174 | val spec = Json.stringify(Json.toJson(kernelSpec.value)) 175 | IO.createDirectory(dir) 176 | IO.write(dir / "kernel.json", spec) 177 | }, 178 | ipyCommands := List( 179 | "console" -> "--no-banner", 180 | "qtconsole" -> "", 181 | "notebook" -> ""), 182 | debugPort := 5005, 183 | debugCommand := { 184 | Seq("-Xdebug", s"-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=127.0.0.1:${debugPort.value}") 185 | }, 186 | develScripts := { 187 | val classpath = (fullClasspath in Compile).value.files.mkString(java.io.File.pathSeparator) 188 | val main = (mainClass in Compile).value getOrElse sys.error("unknown main class") 189 | 190 | val java_args = Seq("java", "-cp", classpath, main) 191 | val ipy_args = Seq("--profile", "{connection_file}", "--parent") 192 | val kernel_args = (java_args ++ ipy_args).map(arg => s"'$arg'").mkString(", ") 193 | val extra_args = """$(python -c "import shlex; print(shlex.split('$*'))")""" 194 | val kernel_cmd = s"[$kernel_args] + $extra_args" 195 | 196 | val binDirectory = baseDirectory.value / "bin" / scalaBinaryVersion.value 197 | IO.createDirectory(binDirectory) 198 | 199 | def shebang(script: String) = s"#!/bin/bash\n$script" 200 | 201 | val kernel = "kernel" -> (java_args :+ "$*").mkString(" ") 202 | 203 | val scripts = kernel :: (ipyCommands.value map { case (name, options) => 204 | name -> s"""ipython $name --profile scala $options --KernelManager.kernel_cmd="$kernel_cmd"""" 205 | }) 206 | 207 | scripts map { case (name, content) => 208 | val file = binDirectory / name 209 | streams.value.log.info(s"Writing ${file.getPath}") 210 | IO.write(file, shebang(content)) 211 | file.setExecutable(true) 212 | file 213 | } 214 | }, 215 | userScripts := { 216 | val baseDir = "$(dirname $(dirname $(readlink -f $0)))" 217 | val jarName = { 218 | import SbtAssembly.AssemblyKeys.{assembly,jarName=>jar} 219 | (jar in assembly).value 220 | } 221 | 222 | val args = Seq("java", "-jar", "$JAR", "--profile", "{connection_file}", "--parent") 223 | val kernel_args = args.map(arg => s"'$arg'").mkString(", ") 224 | val extra_args = """$(python -c "import shlex; print(shlex.split('$*'))")""" 225 | val kernel_cmd = s"[$kernel_args] + $extra_args" 226 | 227 | val binDirectory = crossTarget.value / "bin" 228 | IO.createDirectory(binDirectory) 229 | 230 | ipyCommands.value map { case (command, options) => 231 | val output = s""" 232 | |#!/bin/bash 233 | |JAR="$baseDir/lib/$jarName" 234 | |ipython $command --profile scala $options --KernelManager.kernel_cmd="$kernel_cmd" 235 | """.stripMargin.trim + "\n" 236 | val file = binDirectory / command 237 | streams.value.log.info(s"Writing ${file.getPath}") 238 | IO.write(file, output) 239 | file.setExecutable(true) 240 | file 241 | } 242 | }) 243 | } 244 | 245 | lazy val coreSettings = Defaults.coreDefaultSettings ++ Seq( 246 | libraryDependencies ++= { 247 | import Dependencies._ 248 | quasiquotes.value ++ Seq(reflect.value, play_json, specs2) 249 | } 250 | ) 251 | 252 | lazy val libSettings = Defaults.coreDefaultSettings ++ Seq( 253 | libraryDependencies ++= { 254 | import Dependencies._ 255 | quasiquotes.value ++ xml.value ++ Seq(reflect.value, codec, specs2) 256 | } 257 | ) 258 | 259 | lazy val IScala = project in file(".") settings(iscalaSettings: _*) dependsOn(core, lib) aggregate(core, lib) 260 | lazy val core = project in file("core") settings(coreSettings: _*) 261 | lazy val lib = project in file("lib") settings(libSettings: _*) dependsOn(core) 262 | 263 | override def projects = Seq(IScala, core, lib) 264 | } 265 | -------------------------------------------------------------------------------- /src/test/scala/Interpreter.scala: -------------------------------------------------------------------------------- 1 | package org.refptr.iscala 2 | package tests 3 | 4 | import org.specs2.mutable.Specification 5 | 6 | class InterpreterSpec extends Specification with InterpreterUtil { 7 | sequential 8 | 9 | "Interpreter" should { 10 | import Results._ 11 | 12 | val Obj = "^([^@]+)@([0-9a-fA-F]+)$".r 13 | 14 | "support primitive values" in { 15 | interpret("1") must beLike { case NoOutput(Value(1, "Int", Plain("1"))) => ok } 16 | interpret("1.0") must beLike { case NoOutput(Value(1.0, "Double", Plain("1.0"))) => ok } 17 | interpret("true") must beLike { case NoOutput(Value(true, "Boolean", Plain("true"))) => ok } 18 | interpret("false") must beLike { case NoOutput(Value(false, "Boolean", Plain("false"))) => ok } 19 | interpret("\"XXX\"") must beLike { case NoOutput(Value("XXX", "String", Plain("XXX"))) => ok } 20 | 21 | } 22 | 23 | "support function values" in { 24 | interpret("() => 1") must beLike { case NoOutput(Value(_, "() => Int", Plain(""))) => ok } 25 | interpret("(x: Int) => x + 1") must beLike { case NoOutput(Value(_, "Int => Int", Plain(""))) => ok } 26 | interpret("(x: Int, y: Int) => x*y + 1") must beLike { case NoOutput(Value(_, "(Int, Int) => Int", Plain(""))) => ok } 27 | } 28 | 29 | "support container values" in { 30 | interpret("List(1, 2, 3)") must beLike { case NoOutput(Value(_, "List[Int]", Plain("List(1, 2, 3)"))) => ok } 31 | interpret("Array(1, 2, 3)") must beLike { case NoOutput(Value(_, "Array[Int]", Plain("Array(1, 2, 3)"))) => ok } 32 | } 33 | 34 | "support null values" in { 35 | interpret("null") must beLike { case NoOutput(Value(null, "Null", Plain("null"))) => ok } 36 | interpret("null: String") must beLike { case NoOutput(Value(null, "String", Plain("null"))) => ok } 37 | } 38 | 39 | "support unit value" in { 40 | interpret("()") must beLike { case NoOutput(NoValue) => ok } 41 | } 42 | 43 | "support imports" in { 44 | interpret("import scala.xml") must beLike { case NoOutput(NoValue) => ok } 45 | } 46 | 47 | "support printing" in { 48 | interpret("println(\"XXX\")") must beLike { 49 | case Output(NoValue, _, "") => ok // TODO: "XXX\n" 50 | } 51 | interpret("print(\"XXX\")") must beLike { 52 | case Output(NoValue, _, "") => ok // TODO: "XXX" 53 | } 54 | 55 | interpret("System.out.println(\"XXX\")") must beLike { 56 | case Output(NoValue, _, "") => ok // TODO: "XXX\n" 57 | } 58 | interpret("System.out.print(\"XXX\")") must beLike { 59 | case Output(NoValue, _, "") => ok // TODO: "XXX" 60 | } 61 | } 62 | 63 | "support long running code" in { 64 | interpret("(1 to 5).foreach { i => println(i); Thread.sleep(1000) }") must beLike { 65 | case Output(NoValue, _, "") => ok // TODO: "1\n2\n3\n4\n5\n" 66 | } 67 | } 68 | 69 | "support arithmetics" in { 70 | interpret("1 + 2 + 3") must beLike { case NoOutput(Value(_, "Int", Plain("6"))) => ok } 71 | } 72 | 73 | "support defining values" in { 74 | interpret("val x = 1") must beLike { case NoOutput(Value(1, "Int", Plain("1"))) => ok } 75 | interpret("x") must beLike { case NoOutput(Value(1, "Int", Plain("1"))) => ok } 76 | interpret("100*x + 17") must beLike { case NoOutput(Value(117, "Int", Plain("117"))) => ok } 77 | } 78 | 79 | "support defining variables" in { 80 | interpret("var y = 1") must beLike { case NoOutput(Value(1, "Int", Plain("1"))) => ok } 81 | interpret("y") must beLike { case NoOutput(Value(1, "Int", Plain("1"))) => ok } 82 | interpret("100*y + 17") must beLike { case NoOutput(Value(117, "Int", Plain("117"))) => ok } 83 | interpret("y = 2") must beLike { case NoOutput(Value(2, "Int", Plain("2"))) => ok } 84 | interpret("100*y + 17") must beLike { case NoOutput(Value(217, "Int", Plain("217"))) => ok } 85 | } 86 | 87 | "support defining lazy values" in { 88 | interpret("var initialized = false") must beLike { case NoOutput(Value(false, "Boolean", _)) => ok } 89 | interpret("lazy val z = { initialized = true; 1 }") must beLike { case NoOutput(NoValue) => ok } 90 | interpret("initialized") must beLike { case NoOutput(Value(false, "Boolean", _)) => ok } 91 | interpret("z + 1") must beLike { case NoOutput(Value(_, "Int", _)) => ok } 92 | interpret("initialized") must beLike { case NoOutput(Value(true, "Boolean", _)) => ok } 93 | } 94 | 95 | "support defining classes" in { 96 | interpret("class Foo(a: Int) { def bar(b: String) = b*a }") must beLike { 97 | case NoOutput(NoValue) => ok 98 | } 99 | interpret("val foo = new Foo(5)") must beLike { 100 | case NoOutput(Value(_, "Foo", _)) => ok 101 | } 102 | interpret("foo.bar(\"xyz\")") must beLike { 103 | case NoOutput(Value(_, "String", Plain("xyzxyzxyzxyzxyz"))) => ok 104 | } 105 | } 106 | 107 | "support exceptions" in { 108 | interpret("1/0") must beLike { 109 | case NoOutput(Exception("java.lang.ArithmeticException", "/ by zero", _, exc: ArithmeticException)) => ok 110 | } 111 | 112 | interpret("java.util.UUID.fromString(\"xyz\")") must beLike { 113 | case NoOutput(Exception("java.lang.IllegalArgumentException", "Invalid UUID string: xyz", _, exc: IllegalArgumentException)) => ok 114 | } 115 | 116 | interpret("throw new java.lang.IllegalArgumentException") must beLike { 117 | case NoOutput(Exception("java.lang.IllegalArgumentException", "", _, exc: IllegalArgumentException)) => ok 118 | } 119 | 120 | interpret("throw new java.lang.IllegalArgumentException(\"custom message\")") must beLike { 121 | case NoOutput(Exception("java.lang.IllegalArgumentException", "custom message", _, exc: IllegalArgumentException)) => ok 122 | } 123 | 124 | interpret("throw new java.lang.IllegalArgumentException(foo.getClass.getName)") must beLike { 125 | case NoOutput(Exception("java.lang.IllegalArgumentException", "Foo", _, exc: IllegalArgumentException)) => ok 126 | } 127 | } 128 | 129 | "support custom exceptions" in { 130 | interpret("class MyException(x: Int) extends Exception(s\"failed with $x\")") must beLike { 131 | case NoOutput(NoValue) => ok 132 | } 133 | 134 | interpret("throw new MyException(117)") must beLike { 135 | case NoOutput(Exception("MyException", "failed with 117", _, _)) => ok 136 | } 137 | } 138 | 139 | "support value patterns" in { 140 | interpret("""val obj = "^([^@]+)@([0-9a-fA-F]+)$".r""") must beLike { 141 | case NoOutput(Value(_, "scala.util.matching.Regex", Plain("^([^@]+)@([0-9a-fA-F]+)$"))) => ok 142 | } 143 | 144 | interpret("""val obj(name, hash) = "Macros$@88a4ee1"""") must beLike { 145 | case NoOutput(Value(_, "String", Plain("88a4ee1"))) => ok 146 | } 147 | 148 | interpret("name") must beLike { 149 | case NoOutput(Value(_, "String", Plain("Macros$"))) => ok 150 | } 151 | 152 | 153 | interpret("hash") must beLike { 154 | case NoOutput(Value(_, "String", Plain("88a4ee1"))) => ok 155 | } 156 | 157 | interpret("""val obj(name, hash) = "Macros$@88a4ee1x"""") must beLike { 158 | case NoOutput(Exception("scala.MatchError", "Macros$@88a4ee1x (of class java.lang.String)", _, exc: scala.MatchError)) => ok 159 | } 160 | } 161 | 162 | "support macros" in { 163 | interpret(""" 164 | import scala.language.experimental.macros 165 | import scala.reflect.macros.Context 166 | 167 | object Macros { 168 | def membersImpl[A: c.WeakTypeTag](c: Context): c.Expr[List[String]] = { 169 | import c.universe._ 170 | val tpe = weakTypeOf[A] 171 | val members = tpe.declarations.map(_.name.decoded).toList.distinct 172 | val literals = members.map(member => Literal(Constant(member))) 173 | c.Expr[List[String]](Apply(reify(List).tree, literals)) 174 | } 175 | 176 | def members[A] = macro membersImpl[A] 177 | } 178 | """) must beLike { 179 | case NoOutput(Value(_, "Macros.type", Plain(Obj("Macros$", _)))) => ok 180 | } 181 | 182 | val plain = "List(, toByte, toShort, toChar, toInt, toLong, toFloat, toDouble, unary_~, unary_+, unary_-, +, <<, >>>, >>, ==, !=, <, <=, >, >=, |, &, ^, -, *, /, %, getClass)" 183 | 184 | interpret("Macros.members[Int]") must beLike { 185 | case NoOutput(Value(_, "List[String]", Plain(plain))) => ok 186 | } 187 | } 188 | 189 | "support display framework" in { 190 | interpret("Nil") must beLike { 191 | case NoOutput(Value(_, "scala.collection.immutable.Nil.type", Plain("List()"))) => ok 192 | } 193 | interpret("implicit val PlainNil = org.refptr.iscala.display.PlainDisplay[Nil.type](_ => \"Nil\")") must beLike { 194 | case NoOutput(Value(_, "org.refptr.iscala.display.PlainDisplay[scala.collection.immutable.Nil.type]", _)) => ok 195 | } 196 | interpret("Nil") must beLike { 197 | case NoOutput(Value(_, "scala.collection.immutable.Nil.type", Plain("Nil"))) => ok 198 | } 199 | interpret("implicit val PlainNil = org.refptr.iscala.display.PlainDisplay[Nil.type](_ => \"NIL\")") must beLike { 200 | case NoOutput(Value(_, "org.refptr.iscala.display.PlainDisplay[scala.collection.immutable.Nil.type]", _)) => ok 201 | } 202 | interpret("Nil") must beLike { 203 | case NoOutput(Value(_, "scala.collection.immutable.Nil.type", Plain("NIL"))) => ok 204 | } 205 | interpret("implicit val PlainNil = org.refptr.iscala.display.PlainDisplay[Nothing](obj => ???)") must beLike { 206 | case NoOutput(Value(_, "org.refptr.iscala.display.PlainDisplay[Nothing]", _)) => ok 207 | } 208 | interpret("Nil") must beLike { 209 | case NoOutput(Value(_, "scala.collection.immutable.Nil.type", Plain("List()"))) => ok 210 | } 211 | } 212 | 213 | "support empty input" in { 214 | interpret("") must beLike { 215 | case NoOutput(NoValue) => ok 216 | } 217 | 218 | interpret(" ") must beLike { 219 | case NoOutput(Incomplete) => ok 220 | } 221 | } 222 | 223 | "support typeInfo()" in { 224 | intp.typeInfo("val a =") === None 225 | intp.typeInfo("val a = 5") === Some("Int") 226 | intp.typeInfo("val a = 5; val b = \"foo\"") === Some("Int String") 227 | } 228 | 229 | "support completions()" in { 230 | intp.completions("1.") === List( 231 | "%", "&", "*", "+", "-", "/", ">", ">=", ">>", ">>>", "^", 232 | "asInstanceOf", "isInstanceOf", 233 | "toByte", "toChar", "toDouble", "toFloat", "toInt", "toLong", "toShort", "toString", 234 | "unary_+", "unary_-", "unary_~", 235 | "|") 236 | intp.completions("1.to") === List("toByte", "toChar", "toDouble", "toFloat", "toInt", "toLong", "toShort", "toString") 237 | intp.completions("1.toC") === List("toChar") 238 | intp.completions("1.toCx") === Nil 239 | intp.completions("List(1).") === Nil 240 | } 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IScala 2 | 3 | **IScala** is a [Scala-language](http://scala-lang.org) backend for [IPython](http://ipython.org). 4 | 5 | [![Build Status][travis]](https://travis-ci.org/mattpap/IScala) 6 | 7 | ## Requirements 8 | 9 | * [IPython](http://ipython.org/ipython-doc/stable/install/install.html) 1.0+ 10 | * [Java](http://wwww.java.com) JRE 1.6+ 11 | 12 | ## Usage 13 | 14 | First obtain a copy of IScala from [here](https://github.com/mattpap/IScala/releases). The 15 | package comes with pre-compiled `IScala.jar` and collection of scripts for running IPython's 16 | console, qtconsole and notebook. `IScala.jar` contains all project dependencies and resources, 17 | so you can move IScala easily around. To start IPython's console, simply issue `bin/console` 18 | in a terminal. This will start `ipython console` and setup it to use IScala backend instead 19 | of the default Python one. Issue `bin/qtconsole` or `bin/notebook` to start IPython's Qt 20 | console or notebook, respectively. 21 | 22 | To start IPython with IScala backend manually, issue: 23 | ``` 24 | ipython console --KernelManager.kernel_cmd='["java", "-jar", "lib/IScala.jar", "--connection-file", "{connection_file}", "--parent"]' 25 | ``` 26 | The same works for `qtconsole` and `notebook`, and is, in principle, what scripts in `bin/` 27 | do. Note that you may have to provide a full path to `IScala.jar`. Option `--parent` is 28 | important and tells IScala that it was started by IPython and is not a standalone kernel. 29 | If not provided, double `^C` (`INT` signal) within 500 ms terminates IScala. Otherwise, 30 | `TERM` signal or shutdown message is needed to terminate IScala gracefully. As a safety 31 | measure, IScala also watches connection file that IPython provided. If the file is removed, 32 | the respective kernel is terminated. 33 | 34 | You can also create a `scala` profile for IPython. To do this, issue: 35 | ``` 36 | $ ipython profile create scala 37 | [ProfileCreate] WARNING | Generating default config file: u'~/.config/ipython/profile_scala/ipython_config.py' 38 | [ProfileCreate] WARNING | Generating default config file: u'~/.config/ipython/profile_scala/ipython_qtconsole_config.py' 39 | [ProfileCreate] WARNING | Generating default config file: u'~/.config/ipython/profile_scala/ipython_notebook_config.py' 40 | ``` 41 | Then add the following line: 42 | ``` 43 | c.KernelManager.kernel_cmd = ["java", "-jar", "$ISCALA_PATH/lib/IScala.jar", "--connection-file", "{connection_file}", "--parent"]" 44 | ``` 45 | to `~/.config/ipython/profile_scala/ipython_config.py`. Replace `$ISCALA_PATH` with the actual 46 | location of `IScala.jar`. Then you can run IPython with `ipython console --profile scala`. 47 | 48 | To start a standalone kernel simply issue: 49 | ``` 50 | $ java -jar lib/IScala.jar 51 | connect ipython with --existing kernel-18271.json 52 | Welcome to Scala 2.10.2 (OpenJDK 64-Bit Server VM, Java 1.6.0_27) 53 | ``` 54 | This creates a connection file `kernel-PID.json`, where `PID` is the process ID of IScala 55 | kernel. You can connect IPython using `--existing kernel-PID.json`. You can provide an 56 | existing connection file with `--connection-file` option. 57 | 58 | IScala supports other options as well. See `java -jar IScala.jar -h` for details. Note 59 | that you can also pass options directly to Scala compiler after `--` delimiter: 60 | ``` 61 | $ java -jar IScala.jar --connection-file kernel.json -- -Xprint:typer 62 | ``` 63 | This will start standalone IScala with preexisting connection file and make Scala compiler 64 | print Scala syntax trees after _typer_ compiler phase. 65 | 66 | ## Example 67 | 68 | ``` 69 | $ bin/console 70 | Welcome to Scala 2.10.2 (OpenJDK 64-Bit Server VM, Java 1.6.0_27) 71 | 72 | In [1]: 1 73 | Out[1]: 1 74 | 75 | In [2]: 1 + 2 + 3 76 | Out[2]: 6 77 | 78 | In [3]: (1 to 5).foreach { i => println(i); Thread.sleep(1000) } 79 | 1 80 | 2 81 | 3 82 | 4 83 | 5 84 | 85 | In [4]: val x = 1 86 | Out[4]: 1 87 | 88 | In [5]: x 89 | Out[5]: 1 90 | 91 | In [6]: 100*x + 17 92 | Out[6]: 117 93 | 94 | In [7]: x. 95 | x.% x.- x.>> x.isInstanceOf x.toFloat x.toString x.| 96 | x.& x./ x.>>> x.toByte x.toInt x.unary_+ 97 | x.* x.> x.^ x.toChar x.toLong x.unary_- 98 | x.+ x.>= x.asInstanceOf x.toDouble x.toShort x.unary_~ 99 | 100 | In [7]: x.to 101 | x.toByte x.toChar x.toDouble x.toFloat x.toInt x.toLong x.toShort x.toString 102 | 103 | In [7]: x.toS 104 | x.toShort x.toString 105 | 106 | In [7]: 1/0 107 | java.lang.ArithmeticException: / by zero 108 | 109 | In [8]: java.util.UUID.fromString("abc") 110 | java.lang.IllegalArgumentException: Invalid UUID string: abc 111 | java.util.UUID.fromString(UUID.java:226) 112 | 113 | In [9]: class Foo(a: Int) { def bar(b: String) = b*a } 114 | 115 | In [10]: new Foo(5) 116 | Out[10]: Foo@70f4d063 117 | 118 | In [11]: _10.bar("xyz") 119 | Out[11]: xyzxyzxyzxyzxyz 120 | 121 | In [12]: import scala.language.experimental.macros 122 | 123 | In [13]: import scala.reflect.macros.Context 124 | 125 | In [14]: object Macros { 126 | ...: def membersImpl[A: c.WeakTypeTag](c: Context): c.Expr[List[String]] = { 127 | ...: import c.universe._ 128 | ...: val tpe = weakTypeOf[A] 129 | ...: val members = tpe.declarations.map(_.name.decoded).toList.distinct 130 | ...: val literals = members.map(member => Literal(Constant(member))) 131 | ...: c.Expr[List[String]](Apply(reify(List).tree, literals)) 132 | ...: } 133 | ...: 134 | ...: def members[A] = macro membersImpl[A] 135 | ...: } 136 | ...: 137 | 138 | In [15]: Macros.members[Int] 139 | Out[15]: List(, toByte, toShort, toChar, toInt, toLong, toFloat, toDouble, unary_~, 140 | unary_+, unary_-, +, <<, >>>, >>, ==, !=, <, <=, >, >=, |, &, ^, -, *, /, %, getClass) 141 | ``` 142 | 143 | ## Magics 144 | 145 | IScala supports magic commands similarly to IPython, but the set of magics is 146 | different to match the specifics of Scala and JVM. Magic commands consist of 147 | percent sign `%` followed by an identifier and optional input to a magic. Magic 148 | command's syntax may resemble valid Scala, but every magic implements its own 149 | domain specific parser. 150 | 151 | ### Type information 152 | 153 | To infer the type of an expression use `%type expr`. This doesn't require 154 | evaluation of `expr`, only compilation up to _typer_ phase. You can also 155 | get compiler's internal type trees with `%type -v` or `%type --verbose`. 156 | 157 | ``` 158 | In [1]: %type 1 159 | Int 160 | 161 | In [2]: %type -v 1 162 | TypeRef(TypeSymbol(final abstract class Int extends AnyVal)) 163 | 164 | In [3]: val x = "" + 1 165 | Out[3]: 1 166 | 167 | In [4]: %type x 168 | String 169 | 170 | In [5]: %type List(1, 2, 3) 171 | List[Int] 172 | 173 | In [6]: %type List("x" -> 1, "y" -> 2, "z" -> 3) 174 | List[(String, Int)] 175 | 176 | In [7]: %type List("x" -> 1, "y" -> 2, "z" -> 3.0) 177 | List[(String, AnyVal)] 178 | ``` 179 | 180 | ### Library management 181 | 182 | Library management is done by [sbt](http://www.scala-sbt.org/). There is no 183 | need for a build file, because settings are managed by IScala. To add a 184 | dependency use `%libraryDependencies += moduleID`, where `moduleID` follows 185 | `organization % name % revision` syntax. You can also use `%%` to track 186 | dependencies that have binary dependency on Scala. Scala version used is 187 | the same that IScala was compiled against. 188 | 189 | To resolve dependencies issue `%update`. If successful this will restart 190 | the interpreter to allow it to use the new classpath. Note that this 191 | will erase the state of the interpreter, so you will have to recompute 192 | all values from scratch. Restarts don't affect interpreter's settings. 193 | 194 | ``` 195 | In [1]: import scalaj.collection.Imports._ 196 | :7: error: not found: value scalaj 197 | import scalaj.collection.Imports._ 198 | ^ 199 | 200 | In [2]: %libraryDependencies += "org.scalaj" %% "scalaj-collection" % "1.5" 201 | 202 | In [3]: %update 203 | [info] Resolving org.scalaj#scalaj-collection_2.10;1.5 ... 204 | [info] Resolving org.scala-lang#scala-library;2.10.2 ... 205 | 206 | In [1]: import scalaj.collection.Imports._ 207 | 208 | In [2]: List(1, 2, 3) 209 | Out[2]: List(1, 2, 3) 210 | 211 | In [3]: _2.asJava 212 | Out[3]: [1, 2, 3] 213 | 214 | In [4]: _3.isInstanceOf[List[_]] 215 | Out[4]: false 216 | 217 | In [5]: _3.isInstanceOf[java.util.List[_]] 218 | Out[5]: true 219 | 220 | In [6]: %libraryDependencies 221 | List(org.scalaj:scalaj-collection:1.5) 222 | ``` 223 | 224 | If a dependency can't be resolved, `%update` will fail gracefully. For example, 225 | if we use `com.scalaj` organization instead of `org.scalaj`, then we will get 226 | the following error: 227 | 228 | ``` 229 | In [1]: %libraryDependencies += "com.scalaj" %% "scalaj-collection" % "1.5" 230 | 231 | In [2]: %update 232 | [info] Resolving com.scalaj#scalaj-collection_2.10;1.5 ... 233 | [warn] module not found: com.scalaj#scalaj-collection_2.10;1.5 234 | [warn] ==== sonatype-releases: tried 235 | [warn] https://oss.sonatype.org/content/repositories/releases/com/scalaj/scalaj-collection_2.10/1.5/scalaj-collection_2.10-1.5.pom 236 | [warn] :::::::::::::::::::::::::::::::::::::::::::::: 237 | [warn] :: UNRESOLVED DEPENDENCIES :: 238 | [warn] :::::::::::::::::::::::::::::::::::::::::::::: 239 | [warn] :: com.scalaj#scalaj-collection_2.10;1.5: not found 240 | [warn] :::::::::::::::::::::::::::::::::::::::::::::: 241 | [error] unresolved dependency: com.scalaj#scalaj-collection_2.10;1.5: not found 242 | ``` 243 | 244 | By default IScala uses Sonatype's releases maven repository. To add more 245 | repositories use `%resolvers += "Repo Name" at "https://path/to/repository"` 246 | and run `%update` again. 247 | 248 | ## Development 249 | 250 | Obtain a copy of IScala either by cloning [this](git@github.com:mattpap/IScala.git) 251 | repository or download it from [here](https://github.com/mattpap/IScala/archive/master.zip). 252 | We use [SBT](http://www.scala-sbt.org/) for dependency management, compilation and deployment. 253 | In a terminal issue: 254 | ``` 255 | $ cd IScala 256 | $ ./sbt 257 | ``` 258 | This will start SBT console (which will be indicated by `>` prefix). On first run 259 | SBT will download itself, its dependencies and plugins, and compile project build 260 | file. From here you can compile the project by issuing `compile` command: 261 | ``` 262 | > compile 263 | ``` 264 | It implicitly run `update` task, so on first run it will download all project 265 | dependencies (including Scala standard library and compiler), so it may take a 266 | while. Note that dependencies are cached in `~/.ivy2` directory, so they will be 267 | picked up next time SBT is run (also in other projects compiled from the same 268 | account). 269 | 270 | Ignore any (deprecation) warnings you will get. To start IScala issue: 271 | ``` 272 | > run 273 | [info] Running org.refptr.iscala.IScala 274 | [info] connect ipython with --existing kernel-18271.json 275 | [info] Welcome to Scala 2.10.2 (OpenJDK 64-Bit Server VM, Java 1.6.0_27) 276 | ``` 277 | This is an equivalent of starting a standalone IScala kernel from a terminal. To 278 | terminate a kernel press `Ctrl+C` (SBT my signal an error). Finally to generate 279 | a JAR file with IScala's class files, resources and dependencies, issue `assembly`. 280 | You can run it with: 281 | ``` 282 | $ java -jar IScala.jar 283 | ``` 284 | Unless you made any changes, this is exactly the JAR you can download from IScala's 285 | releases page. 286 | 287 | ## Status 288 | 289 | This is an early work in progress. Main features and majority of IPython's message 290 | specification were implemented, however certain features are not yet available 291 | (e.g. introspection) or are limited in functionality and subject to major changes. 292 | Report any problems and submit enhancement proposals [here](https://github.com/mattpap/IScala/issues). 293 | 294 | ## Acknowledgment 295 | 296 | This work is substantially based on [IJulia](https://github.com/JuliaLang/IJulia.jl), 297 | a [Julia-language](http://julialang.org/) backend for IPython. 298 | 299 | ## License 300 | 301 | Copyright © 2013-2014 by Mateusz Paprocki and contributors. 302 | 303 | Published under The MIT License, see LICENSE. 304 | 305 | [travis]: https://api.travis-ci.org/mattpap/IScala.png?branch=master 306 | -------------------------------------------------------------------------------- /src/main/scala/Msg.scala: -------------------------------------------------------------------------------- 1 | package org.refptr.iscala 2 | package msg 3 | 4 | // Implementation of IPython protocol specification 4.1 5 | 6 | object Protocol { 7 | val version = (4, 1) 8 | } 9 | 10 | sealed trait ExecutionStatus extends EnumType 11 | @enum object ExecutionStatus extends Enumerated[ExecutionStatus] { 12 | case object ok extends ExecutionStatus 13 | case object error extends ExecutionStatus 14 | case object abort extends ExecutionStatus 15 | } 16 | 17 | sealed trait HistAccessType extends EnumType 18 | @enum object HistAccessType extends Enumerated[HistAccessType] { 19 | case object range extends HistAccessType 20 | case object tail extends HistAccessType 21 | case object search extends HistAccessType 22 | } 23 | 24 | sealed trait ExecutionState extends EnumType 25 | @enum object ExecutionState extends Enumerated[ExecutionState] { 26 | case object busy extends ExecutionState 27 | case object idle extends ExecutionState 28 | case object starting extends ExecutionState 29 | } 30 | 31 | sealed trait MsgType extends EnumType 32 | @enum object MsgType extends Enumerated[MsgType] { 33 | case object execute_request extends MsgType 34 | case object execute_reply extends MsgType 35 | case object object_info_request extends MsgType 36 | case object object_info_reply extends MsgType 37 | case object complete_request extends MsgType 38 | case object complete_reply extends MsgType 39 | case object history_request extends MsgType 40 | case object history_reply extends MsgType 41 | case object connect_request extends MsgType 42 | case object connect_reply extends MsgType 43 | case object kernel_info_request extends MsgType 44 | case object kernel_info_reply extends MsgType 45 | case object shutdown_request extends MsgType 46 | case object shutdown_reply extends MsgType 47 | case object stream extends MsgType 48 | case object display_data extends MsgType 49 | case object pyin extends MsgType 50 | case object pyout extends MsgType 51 | case object pyerr extends MsgType 52 | case object status extends MsgType 53 | case object input_request extends MsgType 54 | case object input_reply extends MsgType 55 | case object comm_open extends MsgType 56 | case object comm_msg extends MsgType 57 | case object comm_close extends MsgType 58 | } 59 | 60 | sealed trait Content 61 | sealed trait FromIPython extends Content 62 | sealed trait ToIPython extends Content 63 | 64 | case class Header( 65 | msg_id: UUID, 66 | username: String, 67 | session: UUID, 68 | msg_type: MsgType) 69 | 70 | case class Msg[+T <: Content]( 71 | idents: List[String], // XXX: Should be List[UUID]? 72 | header: Header, 73 | parent_header: Option[Header], 74 | metadata: Metadata, 75 | content: T) { 76 | 77 | private def replyHeader(msg_type: MsgType): Header = 78 | header.copy(msg_id=UUID.uuid4(), msg_type=msg_type) 79 | 80 | private def replyMsg[T <: ToIPython](idents: List[String], msg_type: MsgType, content: T, metadata: Metadata): Msg[T] = 81 | Msg(idents, replyHeader(msg_type), Some(header), metadata, content) 82 | 83 | def pub[T <: ToIPython](msg_type: MsgType, content: T, metadata: Metadata=Metadata()): Msg[T] = { 84 | val tpe = content match { 85 | case content: stream => content.name 86 | case _ => msg_type.toString 87 | } 88 | replyMsg(tpe :: Nil, msg_type, content, metadata) 89 | } 90 | 91 | def reply[T <: ToIPython](msg_type: MsgType, content: T, metadata: Metadata=Metadata()): Msg[T] = 92 | replyMsg(idents, msg_type, content, metadata) 93 | } 94 | 95 | case class execute_request( 96 | // Source code to be executed by the kernel, one or more lines. 97 | code: String, 98 | 99 | // A boolean flag which, if True, signals the kernel to execute 100 | // this code as quietly as possible. This means that the kernel 101 | // will compile the code with 'exec' instead of 'single' (so 102 | // sys.displayhook will not fire), forces store_history to be False, 103 | // and will *not*: 104 | // - broadcast exceptions on the PUB socket 105 | // - do any logging 106 | // 107 | // The default is False. 108 | silent: Boolean, 109 | 110 | // A boolean flag which, if True, signals the kernel to populate history 111 | // The default is True if silent is False. If silent is True, store_history 112 | // is forced to be False. 113 | store_history: Option[Boolean]=None, 114 | 115 | // A dict mapping names to expressions to be evaluated in the user's dict. The 116 | // rich display-data representation of each will be evaluated after execution. 117 | // See the display_data content for the structure of the representation data. 118 | user_expressions: Map[String, String], 119 | 120 | // Some frontends (e.g. the Notebook) do not support stdin requests. If 121 | // raw_input is called from code executed from such a frontend, a 122 | // StdinNotImplementedError will be raised. 123 | allow_stdin: Boolean) extends FromIPython 124 | 125 | sealed trait execute_reply extends ToIPython { 126 | // One of: 'ok' OR 'error' OR 'abort' 127 | val status: ExecutionStatus 128 | 129 | // The global kernel counter that increases by one with each request that 130 | // stores history. This will typically be used by clients to display 131 | // prompt numbers to the user. If the request did not store history, this will 132 | // be the current value of the counter in the kernel. 133 | val execution_count: Int 134 | } 135 | 136 | case class execute_ok_reply( 137 | execution_count: Int, 138 | 139 | // 'payload' will be a list of payload dicts. 140 | // Each execution payload is a dict with string keys that may have been 141 | // produced by the code being executed. It is retrieved by the kernel at 142 | // the end of the execution and sent back to the front end, which can take 143 | // action on it as needed. See main text for further details. 144 | payload: List[Map[String, String]], 145 | 146 | // Results for the user_expressions. 147 | user_expressions: Map[String, String]) extends execute_reply { 148 | 149 | val status = ExecutionStatus.ok 150 | } 151 | 152 | case class execute_error_reply( 153 | execution_count: Int, 154 | 155 | // Exception name, as a string 156 | ename: String, 157 | // Exception value, as a string 158 | evalue: String, 159 | 160 | // The traceback will contain a list of frames, represented each as a 161 | // string. For now we'll stick to the existing design of ultraTB, which 162 | // controls exception level of detail statefully. But eventually we'll 163 | // want to grow into a model where more information is collected and 164 | // packed into the traceback object, with clients deciding how little or 165 | // how much of it to unpack. But for now, let's start with a simple list 166 | // of strings, since that requires only minimal changes to ultratb as 167 | // written. 168 | traceback: List[String]) extends execute_reply { 169 | 170 | val status = ExecutionStatus.error 171 | } 172 | 173 | case class execute_abort_reply( 174 | execution_count: Int) extends execute_reply { 175 | 176 | val status = ExecutionStatus.abort 177 | } 178 | 179 | case class object_info_request( 180 | // The (possibly dotted) name of the object to be searched in all 181 | // relevant namespaces 182 | oname: String, 183 | 184 | // The level of detail desired. The default (0) is equivalent to typing 185 | // 'x?' at the prompt, 1 is equivalent to 'x??'. 186 | detail_level: Int) extends FromIPython 187 | 188 | case class ArgSpec( 189 | // The names of all the arguments 190 | args: List[String], 191 | // The name of the varargs (*args), if any 192 | varargs: String, 193 | // The name of the varkw (**kw), if any 194 | varkw: String, 195 | // The values (as strings) of all default arguments. Note 196 | // that these must be matched *in reverse* with the 'args' 197 | // list above, since the first positional args have no default 198 | // value at all. 199 | defaults: List[String]) 200 | 201 | sealed trait object_info_reply extends ToIPython { 202 | // The name the object was requested under 203 | val name: String 204 | 205 | // Boolean flag indicating whether the named object was found or not. If 206 | // it's false, all other fields will be empty. 207 | val found: Boolean 208 | } 209 | 210 | case class object_info_notfound_reply( 211 | name: String) extends object_info_reply { 212 | 213 | val found = false 214 | } 215 | 216 | case class object_info_found_reply( 217 | name: String, 218 | 219 | // Flags for magics and system aliases 220 | ismagic: Boolean, 221 | isalias: Boolean, 222 | 223 | // The name of the namespace where the object was found ('builtin', 224 | // 'magics', 'alias', 'interactive', etc.) 225 | namespace: String, 226 | 227 | // The type name will be type.__name__ for normal Python objects, but it 228 | // can also be a string like 'Magic function' or 'System alias' 229 | type_name: String, 230 | 231 | // The string form of the object, possibly truncated for length if 232 | // detail_level is 0 233 | string_form: String, 234 | 235 | // For objects with a __class__ attribute this will be set 236 | base_class: String, 237 | 238 | // For objects with a __len__ attribute this will be set 239 | length: String, 240 | 241 | // If the object is a function, class or method whose file we can find, 242 | // we give its full path 243 | file: String, 244 | 245 | // For pure Python callable objects, we can reconstruct the object 246 | // definition line which provides its call signature. For convenience this 247 | // is returned as a single 'definition' field, but below the raw parts that 248 | // compose it are also returned as the argspec field. 249 | definition: String, 250 | 251 | // The individual parts that together form the definition string. Clients 252 | // with rich display capabilities may use this to provide a richer and more 253 | // precise representation of the definition line (e.g. by highlighting 254 | // arguments based on the user's cursor position). For non-callable 255 | // objects, this field is empty. 256 | argspec: ArgSpec, 257 | 258 | // For instances, provide the constructor signature (the definition of 259 | // the __init__ method): 260 | init_definition: String, 261 | 262 | // Docstrings: for any object (function, method, module, package) with a 263 | // docstring, we show it. But in addition, we may provide additional 264 | // docstrings. For example, for instances we will show the constructor 265 | // and class docstrings as well, if available. 266 | docstring: String, 267 | 268 | // For instances, provide the constructor and class docstrings 269 | init_docstring: String, 270 | class_docstring: String, 271 | 272 | // If it's a callable object whose call method has a separate docstring and 273 | // definition line: 274 | call_def: String, 275 | call_docstring: String, 276 | 277 | // If detail_level was 1, we also try to find the source code that 278 | // defines the object, if possible. The string 'None' will indicate 279 | // that no source was found. 280 | source: String) extends object_info_reply { 281 | 282 | val found = true 283 | } 284 | 285 | case class complete_request( 286 | // The text to be completed, such as 'a.is' 287 | // this may be an empty string if the frontend does not do any lexing, 288 | // in which case the kernel must figure out the completion 289 | // based on 'line' and 'cursor_pos'. 290 | text: String, 291 | 292 | // The full line, such as 'print a.is'. This allows completers to 293 | // make decisions that may require information about more than just the 294 | // current word. 295 | line: String, 296 | 297 | // The entire block of text where the line is. This may be useful in the 298 | // case of multiline completions where more context may be needed. Note: if 299 | // in practice this field proves unnecessary, remove it to lighten the 300 | // messages. 301 | 302 | block: Option[String], 303 | 304 | // The position of the cursor where the user hit 'TAB' on the line. 305 | cursor_pos: Int) extends FromIPython 306 | 307 | case class complete_reply( 308 | // The list of all matches to the completion request, such as 309 | // ['a.isalnum', 'a.isalpha'] for the above example. 310 | matches: List[String], 311 | 312 | // the substring of the matched text 313 | // this is typically the common prefix of the matches, 314 | // and the text that is already in the block that would be replaced by the full completion. 315 | // This would be 'a.is' in the above example. 316 | matched_text: String, 317 | 318 | // status should be 'ok' unless an exception was raised during the request, 319 | // in which case it should be 'error', along with the usual error message content 320 | // in other messages. 321 | status: ExecutionStatus) extends ToIPython 322 | 323 | case class history_request( 324 | // If True, also return output history in the resulting dict. 325 | output: Boolean, 326 | 327 | // If True, return the raw input history, else the transformed input. 328 | raw: Boolean, 329 | 330 | // So far, this can be 'range', 'tail' or 'search'. 331 | hist_access_type: HistAccessType, 332 | 333 | // If hist_access_type is 'range', get a range of input cells. session can 334 | // be a positive session number, or a negative number to count back from 335 | // the current session. 336 | session: Option[Int], 337 | 338 | // start and stop are line numbers within that session. 339 | start: Option[Int], 340 | stop: Option[Int], 341 | 342 | // If hist_access_type is 'tail' or 'search', get the last n cells. 343 | n: Option[Int], 344 | 345 | // If hist_access_type is 'search', get cells matching the specified glob 346 | // pattern (with * and ? as wildcards). 347 | pattern: Option[String], 348 | 349 | // If hist_access_type is 'search' and unique is true, do not 350 | // include duplicated history. Default is false. 351 | unique: Option[Boolean]) extends FromIPython 352 | 353 | case class history_reply( 354 | // A list of 3 tuples, either: 355 | // (session, line_number, input) or 356 | // (session, line_number, (input, output)), 357 | // depending on whether output was False or True, respectively. 358 | history: List[(Int, Int, Either[String, (String, Option[String])])]) extends ToIPython 359 | 360 | case class connect_request() extends FromIPython 361 | 362 | case class connect_reply( 363 | // The port the shell ROUTER socket is listening on. 364 | shell_port: Int, 365 | // The port the PUB socket is listening on. 366 | iopub_port: Int, 367 | // The port the stdin ROUTER socket is listening on. 368 | stdin_port: Int, 369 | // The port the heartbeat socket is listening on. 370 | hb_port: Int) extends ToIPython 371 | 372 | case class kernel_info_request() extends FromIPython 373 | 374 | case class kernel_info_reply( 375 | // Version of messaging protocol (mandatory). 376 | // The first integer indicates major version. It is incremented when 377 | // there is any backward incompatible change. 378 | // The second integer indicates minor version. It is incremented when 379 | // there is any backward compatible change. 380 | protocol_version: (Int, Int), 381 | 382 | // IPython version number (optional). 383 | // Non-python kernel backend may not have this version number. 384 | // The last component is an extra field, which may be 'dev' or 385 | // 'rc1' in development version. It is an empty string for 386 | // released version. 387 | ipython_version: Option[(Int, Int, Int, String)]=None, 388 | 389 | // Language version number (mandatory). 390 | // It is Python version number (e.g., [2, 7, 3]) for the kernel 391 | // included in IPython. 392 | language_version: List[Int], 393 | 394 | // Programming language in which kernel is implemented (mandatory). 395 | // Kernel included in IPython returns 'python'. 396 | language: String) extends ToIPython 397 | 398 | case class shutdown_request( 399 | // whether the shutdown is final, or precedes a restart 400 | restart: Boolean) extends FromIPython 401 | 402 | case class shutdown_reply( 403 | // whether the shutdown is final, or precedes a restart 404 | restart: Boolean) extends ToIPython 405 | 406 | case class stream( 407 | // The name of the stream is one of 'stdout', 'stderr' 408 | name: String, 409 | 410 | // The data is an arbitrary string to be written to that stream 411 | data: String) extends ToIPython 412 | 413 | case class display_data( 414 | // Who create the data 415 | source: String, 416 | 417 | // The data dict contains key/value pairs, where the kids are MIME 418 | // types and the values are the raw data of the representation in that 419 | // format. 420 | data: Data, 421 | 422 | // Any metadata that describes the data 423 | metadata: Metadata) extends ToIPython 424 | 425 | case class pyin( 426 | // Source code to be executed, one or more lines 427 | code: String, 428 | 429 | // The counter for this execution is also provided so that clients can 430 | // display it, since IPython automatically creates variables called _iN 431 | // (for input prompt In[N]). 432 | execution_count: Int) extends ToIPython 433 | 434 | case class pyout( 435 | // The counter for this execution is also provided so that clients can 436 | // display it, since IPython automatically creates variables called _N 437 | // (for prompt N). 438 | execution_count: Int, 439 | 440 | // data and metadata are identical to a display_data message. 441 | // the object being displayed is that passed to the display hook, 442 | // i.e. the *result* of the execution. 443 | data: Data, 444 | metadata: Metadata = Metadata()) extends ToIPython 445 | 446 | case class pyerr( 447 | execution_count: Int, 448 | 449 | // Exception name, as a string 450 | ename: String, 451 | // Exception value, as a string 452 | evalue: String, 453 | 454 | // The traceback will contain a list of frames, represented each as a 455 | // string. For now we'll stick to the existing design of ultraTB, which 456 | // controls exception level of detail statefully. But eventually we'll 457 | // want to grow into a model where more information is collected and 458 | // packed into the traceback object, with clients deciding how little or 459 | // how much of it to unpack. But for now, let's start with a simple list 460 | // of strings, since that requires only minimal changes to ultratb as 461 | // written. 462 | traceback: List[String]) extends ToIPython 463 | 464 | object pyerr { 465 | // XXX: can't use apply(), because of https://github.com/playframework/playframework/issues/2031 466 | def fromThrowable(execution_count: Int, exception: Throwable): pyerr = { 467 | val name = exception.getClass.getName 468 | val value = Option(exception.getMessage) getOrElse "" 469 | val stacktrace = exception 470 | .getStackTrace() 471 | .takeWhile(_.getFileName != "") 472 | .toList 473 | val traceback = s"$name: $value" :: stacktrace.map(" " + _) 474 | 475 | pyerr(execution_count=execution_count, 476 | ename=name, 477 | evalue=value, 478 | traceback=traceback) 479 | } 480 | } 481 | 482 | case class status( 483 | // When the kernel starts to execute code, it will enter the 'busy' 484 | // state and when it finishes, it will enter the 'idle' state. 485 | // The kernel will publish state 'starting' exactly once at process startup. 486 | execution_state: ExecutionState) extends ToIPython 487 | 488 | case class clear_output( 489 | // Wait to clear the output until new output is available. Clears the 490 | // existing output immediately before the new output is displayed. 491 | // Useful for creating simple animations with minimal flickering. 492 | _wait: Boolean) extends ToIPython 493 | 494 | case class input_request( 495 | prompt: String) extends ToIPython 496 | 497 | case class input_reply( 498 | value: String) extends FromIPython 499 | 500 | import play.api.libs.json.JsObject 501 | 502 | case class comm_open( 503 | comm_id: UUID, 504 | target_name: String, 505 | data: JsObject) extends ToIPython with FromIPython 506 | 507 | case class comm_msg( 508 | comm_id: UUID, 509 | data: JsObject) extends ToIPython with FromIPython 510 | 511 | case class comm_close( 512 | comm_id: UUID, 513 | data: JsObject) extends ToIPython with FromIPython 514 | 515 | // XXX: This was originally in src/main/scala/Formats.scala, but due to 516 | // a bug in the compiler related to `knownDirectSubclasses` and possibly 517 | // also other bugs (e.g. `isCaseClass`), formats had to be moved here 518 | // and explicit type annotations had to be added for formats of sealed 519 | // traits. Otherwise no known subclasses will be reported. 520 | 521 | import org.refptr.iscala.json.{Json,EnumJson,JsonImplicits} 522 | import org.refptr.iscala.msg._ 523 | import JsonImplicits._ 524 | 525 | import play.api.libs.json.{JsObject,Writes} 526 | 527 | package object formats { 528 | import display.MIME 529 | 530 | implicit val MIMEFormat = new Writes[MIME] { 531 | def writes(mime: MIME) = implicitly[Writes[String]].writes(mime.name) 532 | } 533 | 534 | implicit val DataFormat = new Writes[Data] { 535 | def writes(data: Data) = { 536 | JsObject(data.items.map { case (mime, value) => 537 | mime.name -> implicitly[Writes[String]].writes(value) 538 | }) 539 | } 540 | } 541 | 542 | implicit val MsgTypeFormat = EnumJson.format(MsgType) 543 | implicit val HeaderFormat = Json.format[Header] 544 | 545 | implicit val ExecutionStatusFormat = EnumJson.format(ExecutionStatus) 546 | implicit val ExecutionStateFormat = EnumJson.format(ExecutionState) 547 | implicit val HistAccessTypeFormat = EnumJson.format(HistAccessType) 548 | 549 | implicit val ArgSpecFormat = Json.format[ArgSpec] 550 | 551 | implicit val ExecuteRequestJSON = Json.format[execute_request] 552 | implicit val ExecuteReplyJSON: Writes[execute_reply] = Json.writes[execute_reply] 553 | 554 | implicit val ObjectInfoRequestJSON = Json.format[object_info_request] 555 | implicit val ObjectInfoReplyJSON: Writes[object_info_reply] = Json.writes[object_info_reply] 556 | 557 | implicit val CompleteRequestJSON = Json.format[complete_request] 558 | implicit val CompleteReplyJSON = Json.format[complete_reply] 559 | 560 | implicit val HistoryRequestJSON = Json.format[history_request] 561 | implicit val HistoryReplyJSON = Json.format[history_reply] 562 | 563 | implicit val ConnectRequestJSON = Json.noFields[connect_request] 564 | implicit val ConnectReplyJSON = Json.format[connect_reply] 565 | 566 | implicit val KernelInfoRequestJSON = Json.noFields[kernel_info_request] 567 | implicit val KernelInfoReplyJSON = Json.format[kernel_info_reply] 568 | 569 | implicit val ShutdownRequestJSON = Json.format[shutdown_request] 570 | implicit val ShutdownReplyJSON = Json.format[shutdown_reply] 571 | 572 | implicit val StreamJSON = Json.writes[stream] 573 | implicit val DisplayDataJSON = Json.writes[display_data] 574 | implicit val PyinJSON = Json.writes[pyin] 575 | implicit val PyoutJSON = Json.writes[pyout] 576 | implicit val PyerrJSON = Json.writes[pyerr] 577 | implicit val StatusJSON = Json.writes[status] 578 | implicit val ClearOutputJSON = new Writes[clear_output] { 579 | def writes(obj: clear_output) = { 580 | // NOTE: `wait` is a final member on Object, so we have to go through hoops 581 | JsObject(Seq("wait" -> implicitly[Writes[Boolean]].writes(obj._wait))) 582 | } 583 | } 584 | 585 | implicit val InputRequestJSON = Json.format[input_request] 586 | implicit val InputReplyJSON = Json.format[input_reply] 587 | 588 | implicit val CommOpenJSON = Json.format[comm_open] 589 | implicit val CommMsgJSON = Json.format[comm_msg] 590 | implicit val CommCloseJSON = Json.format[comm_close] 591 | } 592 | --------------------------------------------------------------------------------