Created
June 26, 2012 18:51
-
-
Save vikrum/2997954 to your computer and use it in GitHub Desktop.
Scala REPL exposed via Netty
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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