├── 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 | " | Header 1 | \n",
117 | " Header 2 | \n",
118 | "
\n",
119 | " \n",
120 | " | row 1, cell 1 | \n",
121 | " row 1, cell 2 | \n",
122 | "
\n",
123 | " \n",
124 | " | row 2, cell 1 | \n",
125 | " row 2, cell 2 | \n",
126 | "
\n",
127 | "
"
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 |
--------------------------------------------------------------------------------