Skip to content

Instantly share code, notes, and snippets.

@vikrum
Created June 26, 2012 18:51
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vikrum/2997954 to your computer and use it in GitHub Desktop.
Save vikrum/2997954 to your computer and use it in GitHub Desktop.
Scala REPL exposed via Netty
object App extends Logging {
def main(args: Array[String]) = {
setupNetty
scalaReplConsole
}
/**
* Setup Netty for things like logging, etc. This should happen first. Do not get rid of this.
* You'll have a calamity on your hands if it needs to log under stress and it is unable to.
*/
def setupNetty = {
// See http://codeslinger.posterous.com/using-a-real-logger-with-netty-will-cause-bos
InternalLoggerFactory.setDefaultFactory(new Slf4JLoggerFactory())
}
/**
* Scala REPL console
*/
def scalaReplConsole = {
val server: ServerBootstrap = new ServerBootstrap(
new NioServerSocketChannelFactory(
Executors.newCachedThreadPool(),
Executors.newCachedThreadPool()));
server.getPipeline.addLast("frameDecoder", new DelimiterBasedFrameDecoder(80, ChannelBuffers.copiedBuffer("\n", CharsetUtil.UTF_8)))
server.getPipeline.addLast("stringDecoder", new StringDecoder(CharsetUtil.UTF_8))
server.getPipeline.addLast("stringEncoder", new StringEncoder(CharsetUtil.UTF_8))
server.getPipeline.addLast("scalaRepl", new ScalaReplConsoleHandler())
server.setOption("child.keepAlive", true);
server.bind(new InetSocketAddress(InetAddress.getLoopbackAddress, scalaReplPort))
info("Scala REPL console listening on " + scalaReplPort);
}
}
import io.netty.channel.{ChannelStateEvent, SimpleChannelUpstreamHandler, ChannelHandlerContext, MessageEvent}
import tools.nsc.interpreter.IMain
import io.netty.buffer.{ChannelBuffers, ChannelBufferOutputStream}
import tools.nsc.{NewLinePrintWriter, GenericRunnerSettings}
import java.io.{PrintStream, OutputStream, Writer, BufferedOutputStream}
import ch.qos.logback.classic.Logger
import org.slf4j.LoggerFactory
/**
* Created by IntelliJ IDEA.
* User: vikrum
* Date: 3/19/12
* Time: 8:51 AM
* To change this template use File | Settings | File Templates.
*/
class ScalaReplConsoleHandler extends SimpleChannelUpstreamHandler {
var interpreter: IMain = null
override def channelConnected(ctx: ChannelHandlerContext , e: ChannelStateEvent) = {
val settings: GenericRunnerSettings = new GenericRunnerSettings(errorStr => {
ctx.getChannel.write(errorStr)
})
val compilerPath = java.lang.Class.forName("scala.tools.nsc.Interpreter").getProtectionDomain.getCodeSource.getLocation
val libPath = java.lang.Class.forName("scala.Some").getProtectionDomain.getCodeSource.getLocation
val urls = java.lang.Thread.currentThread.getContextClassLoader match {
case cl: java.net.URLClassLoader => cl.getURLs.toList
}
val classpath = urls map {_.toString}
println("classpath: " + classpath)
println("compiler path: " + compilerPath)
println("libpath: " + libPath)
settings.classpath.value = classpath.distinct.mkString(java.io.File.pathSeparator)
settings.bootclasspath.value = List(settings.bootclasspath.value, compilerPath, libPath) mkString java.io.File.pathSeparator
settings.usejavacp.value = true;
val channelWriter = new ChannelWriter(ctx)
Console.setOut(new PrintStream(new ChannelOutputStream(channelWriter), true))
Console.setErr(new PrintStream(new ChannelOutputStream(channelWriter), true))
interpreter = new IMain(settings, new NewLinePrintWriter(channelWriter, true)) {
override protected def parentClassLoader: ClassLoader = this.getClass.getClassLoader
}
}
override def channelDisconnected(ctx: ChannelHandlerContext , e: ChannelStateEvent) = {
interpreter.reset()
interpreter.close()
interpreter = null
}
override def messageReceived(ctx: ChannelHandlerContext , e: MessageEvent) = {
if(e != null && e.getMessage != null) {
e.getMessage match {
case str: String => {
if("bye\n".equals(str)) {
ctx.getChannel.close()
}
else {
interpreter.interpret(str)
}
}
}
}
}
class ChannelWriter(ctx: ChannelHandlerContext) extends Writer {
def close = {}
def flush = {}
def write(buffer: Array[Char], offset: Int, length: Int) = {
if (length > 0)
write(new String(buffer.slice(offset, offset+length)))
}
override def write(str: String) {
ctx.getChannel.write(str)
}
}
class ChannelOutputStream(out: Writer) extends OutputStream {
override def write(i: Int) = {
out.write(i)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment