2013-05-27 2 views
5

Скажем, мы хотели построить такую ​​функцию, как require, которая известна на многих языках сценариев в Scala.Изменение пути класса компилятора от макроса Scala?

Если требуется библиотека, как это ...

require("some-library.jar") 

... мы должны были бы способ, чтобы добавить эту банку в путь к классам компилятора, когда требуется макрос выполняется. Как это может быть сделано?

+0

Вы можете попробовать адаптировать это решение http://stackoverflow.com/questions/1010919/adding-files-to-java-classpath-at-runtime –

+0

Речь идет об изменении пути к среде выполнения, а не в том, что мне нужно. –

+0

Но если он находится в определении макроса (_not_ сгенерированный код), он должен запускаться компилятором и изменять путь_its_ runtime classpath. –

ответ

5

Другой интересный вопрос @ Ким-Стебель.

Моя первая идея, которая не затрагивает ваш вопрос, заключается в том, что ваш компилятор может настроить путь класса макросов с помощью findMacroClassLoader. REPL использует это благодаря @extempore.

Это было бы полезно для идиомы, такой как требование («myfoo.jar») {mymacro}, возможно.

Вопрос в том, можно ли обновить путь к классу компилятора. Это может быть возможно путем отбрасывания из вашего контекстного юниверса в компилятор, для которого isCompilerUniverse == true. Тогда вы можете platform.updateClassPath.

Update с кодом:

Вот что-то нравится эта идея использования специального заполнителя в классе путь для требуемого макроса.

Необычным способом, упомянутым ниже, было бы использование пользовательского пути класса, который может сообщать обо всех необходимых классах в этом месте. Старый код, упомянутый ниже, был для виртуального каталога в пути к классу.

Этот быстрый и дрянной способ использует файловую систему для распаковки вашей банки и просто просит компилятор выполнить ее повторное сканирование.

Заполнитель должен быть фактическим директором из-за ограничения в 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 

[email protected]:~/tmp/pathmaker$ . ./b 
Unpack special.jar 
sample.scala:8: Updated symbols List(package special) 
    require("special.jar") 
     ^
Special(7,seven) 

Макрос не пробирает его по пути выполнения, что является сложной задачей.

Но я предполагаю, что требуемый макрос может делать удобные вещи, например, условно захватывать разные версии фляги с различными характеристиками времени компиляции (константы и т. Д.).

Update, просто проверить, что это довольно дикий:

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 для другого приложения, поэтому я не изучал ограничения y и др.

Обновление: одно ограничение контролирует, когда макрос расширяется. Я хотел показать, как что-то компилирует против старого API против нового API, и должен был обернуть код в блоках, которые доступны, прежде чем необходимо уверены символы:

require("oldfoo.jar") 
locally { 
    import foo._ 
    // something 
    require("newfoo.jar") 
    // try again 
} 

Старого нюанс: Извините за туманный ответ, я знаю, вы не соблюдаете этого; Я попробую позже, но, может быть, кто-то сделает шаг вперед с ясностью.

Раньше я подключился к реализации «платформы» для компилятора global, но это, надеюсь, излишнее для этого варианта использования. В этот момент вы можете делать все, что хотите, с помощью classpath, но я думаю, что вам нужно что-то более готовое.

+0

+1 Еще один интересный ответ @ som-snytt. –

+0

@ som-snytt вы можете расширить примеры кода о том, как вы можете использовать глобальный компилятор для полного изменения пути к классам, все значения в классе scala.tools.nsc.Global кажутся неизменными. –

+0

@ RicardoGladwell Изменчивой вещью является путь класса. В этом решении файловая система используется для обновления содержимого местоположения, и компилятору предлагается обновить это местоположение. Также возможно создать экземпляр глобального с пользовательской реализацией ClassPath и фактически сделать эквивалент, но он не изящный. В настоящее время предпринимаются усилия по улучшению абстракции ClassPath. –

0

Как утверждает Евгений Бурманко в this question, определения в настоящее время (Scala 2.10) не будут добавлены/удалены вне макроса. Если вы хотите использовать macro paradise, может быть и так.

+0

Если я правильно понимаю ответ Юджина, он просто ссылается на определения _created_ от макроса. Кроме того, «может быть, путь» не совсем то, что я искал в ответ. ;) –

 Смежные вопросы

  • Нет связанных вопросов^_^