从Scala宏更改编译器的类路径?
假设我们想要在
Scala中构建像许多脚本语言中已知的require这样的功能.
如果我们需要像这样的图书馆…… require("some-library.jar") …当执行require宏时,我们需要一种方法将此jar添加到编译器的类路径中.如何才能做到这一点? 解决方法
另一个有趣的@ kim-stebel问题.
我的第一个想法是,您的编译器可以使用findMacroClassLoader自定义宏类路径. REPL使用了@extempore. 这可能对于像需要(“myfoo.jar”){mymacro}这样的成语很有用. 您的问题是您是否可以更新编译器的类路径.这可以通过从您的上下文Universe向下转换到编译器来实现,其中isCompilerUniverse == true.然后你可以使用platform.updateClassPath. 使用代码更新: 这就是在类路径中为所需宏使用特殊占位符的想法. 下面提到的奇特方法是使用可以报告该位置所有必需类的自定义类路径.下面提到的旧代码用于类路径中的虚拟目录. 这种快速而俗气的方式使用文件系统解压缩jar并只要求编译器重新扫描它. 由于invalidateClassPathEntries的限制,占位符必须是实际目录,invalidateClassPathEntries想要检查规范文件路径是否在类路径上. package alacs import scala.language.experimental.macros import scala.reflect.macros.Context import scala.sys.process._ import java.io.File /** A require macro to dynamically fudge the compilation classpath. */ object PathMaker { // special place to unpack required libs,must be on the initial classpath val entry = "required" // whether to report updated syms without overly verbose -verbose val talky = true def require(c: Context)(name: c.Expr[String]): c.Expr[Unit] = { import c.universe._ val st = c.universe.asInstanceOf[scala.reflect.internal.SymbolTable] if (st.isCompilerUniverse) { val Literal(Constant(what: String)) = name.tree if (update(what)) { val global = st.asInstanceOf[scala.tools.nsc.Global] val (updated,_) = global invalidateClassPathEntries entry c.info(c.enclosingPosition,s"Updated symbols $updated",force = talky) } else { c.abort(c.enclosingPosition,s"Couldn't unpack '$what' into '$entry'") } } reify { () } } // figure out where name is,and update the special class path entry def update(name: String): Boolean = { // Process doesn't parse the command,it just splits on space,// something about working on Windows //val status = s"sh -c "mkdir $entry ; ( cd $entry ; jar xf ../$name )"".! // but Process can set cwd for you val command = s"jar xf ../$name" val status = Process(command,new File(entry)).! (status == 0) } } 必需的API: package alacs import scala.language.experimental.macros object Require { def require(name: String): Unit = macro PathMaker.require } 用法: package sample import alacs.Require._ /** Sample app requiring something not on the class path. */ object Test extends App { require("special.jar") import special._ Console println Special(7,"seven") } 在special.jar中打包的东西 package special case class Special(i: Int,s: String) 通过这种方式测试: rm -rf required mkdir required skalac pathmaker.scala skalac -cp .:required require.scala sample.scala skala -cp .:special.jar sample.Test apm@mara:~/tmp/pathmaker$. ./b Unpack special.jar sample.scala:8: Updated symbols List(package special) require("special.jar") ^ Special(7,seven) 宏不会将它潜入运行时路径,这是脚本化的事情. 但是我认为require宏可以做一些方便的事情,比如有条件地抓住jar的不同版本,具有不同的编译时特性(常量等). 更新,只是验证它非常疯狂: require("fast.jar") import constants._ Console println speed require("slow.jar") Console println speed 哪里 package object constants { //final val speed = 55 final val speed = 85 } $skalac -d fast.jar constants.scala 并使用内联常量运行它 85 55 新警告:这是我的第一个宏,我正在查看另一个应用程序的invalidateClassPathEntries,所以我还没有探讨限制. 更新:一个限制是控制宏何时展开.我想展示一些东西如何编译旧api与新api,并且必须将代码包装在块中以确保符号在需要之前可用: require("oldfoo.jar") locally { import foo._ // something require("newfoo.jar") // try again } 老警告: 以前,我已经深入研究了编译器全局的“平台”实现,但是对于这个用例来说,这有望过度.此时,您可以使用类路径执行任何操作,但我认为您需要更多开箱即用的功能. (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |