実行時のScalaコードの簡単なコンパイル

それでは、始めましょう。 Scalaが好きなのは、Javaの半分のコードを書くことができるからです。 明確で表現力豊かなコード。 さらに、Scalaでは、一般にJava開発者がアクセスできないことを行うことができます。高次の一般的なもの、およびマニフェストを使用してランタイムで一般的な型を認識することです。



説明することの1つは、プログラム実行中のScalaコードのコンパイルです。 これは、リモートソースからのコードを実行したり、JSのeval関数に類似した自己変更プログラムを記述したりする場合に必要になることがあります。



ScalaとJavaの最も重要な違いは、コンパイラがjavacのようなネイティブプログラムではなく、 jarファイルの形式になっていることです。つまり、ソースコードからJVMバイトコードを取得する必要があるときに誰も呼び出す必要のない単純なライブラリです。



このメソッドの唯一の欠点は、ScalaコンパイラAPIには単純なタスクのための非常に便利なインターフェイスがないことです。ソースを取得し、そこからコンパイルされたクラスの束を取得するだけで、Java Reflection APIを使用してインスタンスを作成できます。 だから、ある日、コンパイル用のシンプルなラッパーを作成することにしました。これがストーリーになります。 Stack Overflowとテーマサイトから少しずつコードを収集しました。



Scalaコードをコンパイルするためのインターフェースは、 scala.tools.nsc.Globalクラスによって提供されます。 コンパイルを呼び出すには、このクラスのインスタンスを作成してから、ネストされたRunクラスのインスタンスを作成し、compileSourcesメソッドを実行する必要があります。



val compiler = new Global ( settings, reporter ) <br/>

new compiler. Run ( ) compileSources ( sources )






ここではすべてが非常に簡単ですが、コンパイルには設定(設定)、コンパイルエラーとソース自体(ソース)を収集するレポーターが必要です。



設定はscala.tools.nsc.Settings型で、私のニーズの設定では2つのパラメーターで十分でした:usejavacpは、コンパイラーが現在のJavaクラスパスを使用する必要があることを決定します(これは、コンパイルするソースコードに外部クラスへのリンクが含まれる場合があるため便利です)環境)、および2番目のパラメーターoutputDirs。 実際、Globalクラスにあるコンパイラは、コンパイルされたクラスをディスク上のファイルとしてのみ追加できます。 私はディスクに接続したくなかったので、回避策が見つかりました。ちなみに、メモリ内の仮想ディレクトリを使用してください。



その結果、私の設定コードは次のようになります。



val settings = new Settings<br/>

<br/>

settings. usejavacp . value = true <br/>

<br/>

val directory = new VirtualDirectory ( "(memory)" , None ) <br/>

settings. outputDirs . setSingleOutput ( directory )






次に、レポーターを構成する必要があります。 scala.tools.nsc.reporters.StoreReporterを使用しました



val reporter = new StoreReporter ( )





コンパイルに関するすべての問題を保存し、コンパイルが完了するとすぐにエラーを分析できます。



最後に、ソースコードを準備します。 それほど単純ではなく、コンパイラはscala.tools.nsc.util.SourceFileインスタンスからのリストを受け入れますが、覚えているように、ソースコードの行しかありません。 短いScala構文のおかげで、変換は簡単です。



val sources = List ( new BatchSourceFile ( "<source>" , source ) )





ソースコードを配置するファイルを1つ作成します。



これですべての準備が整ったので、コンパイラを呼び出すことができます。最初に行うことは、チェックです。ソースコードはどのようにコンパイルされましたか。



if ( reporter. hasErrors ) { <br/>

throw new CompilationFailedException ( source,<br/>

reporter. infos . map ( info = > ( info. pos . line , info. msg ) ) ) <br/>

}






簡単にするために、エラー(警告ではなく)の場合、コンパイルが失敗したという例外をスローします。これは例外です。



class CompilationFailedException ( val programme: String , val messages: Iterable [ ( Int, String ) ] ) <br/>

extends Exception ( messages. map ( message = > message._1 + ". " + message._2 ) . mkString ( "n" ) )






すべてがうまくいけば、クラスはすでに組み立てられていますが、同じ仮想フォルダーから何らかの方法でそれらをロードします。 組み込みクラスscala.tools.nsc.interpreter.AbstractFileClassLoaderを使用します。



val classLoader = new AbstractFileClassLoader ( directory, this . getClass . getClassLoader ( ) )





AbstractFileClassLoaderは、新しいコンパイルごとに新しいものを作成するため、クラスを再コンパイルする場合、以前のインカネーションと競合することなく、2回目の読み込みに成功します。



クラスローダーを作成したら、仮想フォルダーのファイルを調べて、その中のファイルからクラスを読み込む必要があります。 Scalaでは、1つのソースファイルに複数のクラスを含めることができますが、これらのクラスは必ずしも互いに入れ子になっているわけではありません.JVMバイトコードにコンパイルされると、そのような複数のクラスは複数のファイルに分解され、入れ子になったクラスはClassName $ InnerClassName.classという名前の別のクラスにあります。 このコードを使用して単一のインターフェイスの実装をコンパイルしたため、結果のクラスに何が期待できるかを常に把握できました。 ちなみに、メインクラスと同等になるように努力したネストされたクラスは、私に強く干渉しました。ロードするときに、名前に$記号が含まれている場合はスキップします。



for ( classFile < - directory ; if ( ! classFile. name . contains ( '$' ) ) ) yield { <br/>

<br/>

val path = classFile. path <br/>

val fullQualifiedName = path. substring ( path. indexOf ( '/' ) + 1 , path. lastIndexOf ( '.' ) ) . replace ( "/" , "." ) <br/>

<br/>

classLoader. loadClass ( fullQualifiedName ) <br/>

}






クラスをロードするには、パッケージ構造を含む完全修飾名を取得する必要があります。 この名前を再作成するために、フォルダー内のパス操作を使用しました。



さて、これでクラスがロードされ、上記の構築はこれらのクラスのリストを返します。



それだけです これは、見かけ上は単純ではあるが、些細ではないタスクですが、コードのページを書くことによってのみ達成できる方法です。



結果のクラスの全文:



/*!# Compiler<br/>

<br/>

This class is a wrapper over Scala Compiler API<br/>

which has simple interface just accepting the source code string.<br/>

<br/>

Compiles the source code assuming that it is a .scala source file content.<br/>

It used a classpath of the environment that called the `Compiler` class.<br/>

*/
<br/>

<br/>

import tools. nsc . { Global, Settings } <br/>

import tools.nsc.io._ <br/>

import tools.nsc.reporters.StoreReporter <br/>

import tools.nsc.interpreter.AbstractFileClassLoader <br/>

import tools.nsc.util._ <br/>

<br/>

class Compiler { <br/>

<br/>

def compile ( source: String ) : Iterable [ Class [ _ ] ] = { <br/>

<br/>

// prepare the code you want to compile <br/>

val sources = List ( new BatchSourceFile ( "<source>" , source ) ) <br/>

<br/>

// Setting the compiler settings <br/>

val settings = new Settings<br/>

<br/>

/*! Take classpath from currently running scala environment. */ <br/>

settings. usejavacp . value = true <br/>

<br/>

/*! Save class files for compiled classes into a virtual directory in memory. */ <br/>

val directory = new VirtualDirectory ( "(memory)" , None ) <br/>

settings. outputDirs . setSingleOutput ( directory ) <br/>

<br/>

val reporter = new StoreReporter ( ) <br/>

val compiler = new Global ( settings, reporter ) <br/>

new compiler. Run ( ) compileSources ( sources ) <br/>

<br/>

/*! After the compilation if errors occured, `CompilationFailedException`<br/>

is being thrown with a detailed message. */
<br/>

if ( reporter. hasErrors ) { <br/>

throw new CompilationFailedException ( source,<br/>

reporter. infos . map ( info = > ( info. pos . line , info. msg ) ) ) <br/>

} <br/>

<br/>

/*! Each time new `AbstractFileClassLoader` is created for loading classes<br/>

it gives an opportunity to treat same name classes loading well.<br/>

*/
<br/>

// Loading new compiled classes <br/>

val classLoader = new AbstractFileClassLoader ( directory, this . getClass . getClassLoader ( ) ) <br/>

<br/>

/*! When classes are loading inner classes are being skipped. */ <br/>

for ( classFile < - directory ; if ( ! classFile. name . contains ( '$' ) ) ) yield { <br/>

<br/>

/*! Each file name is being constructed from a path in the virtual directory. */ <br/>

val path = classFile. path <br/>

val fullQualifiedName = path. substring ( path. indexOf ( '/' ) + 1 ,path. lastIndexOf ( '.' ) ) . replace ( "/" , "." ) <br/>

<br/>

Console. println ( fullQualifiedName ) <br/>

<br/>

/*! Loaded classes are collecting into a returning collection with `yield`. */ <br/>

classLoader. loadClass ( fullQualifiedName ) <br/>

} <br/>

} <br/>

} <br/>

<br/>

/*!### Compilation exception<br/>

<br/>

Compilation exception is defined this way.<br/>

It contains program was compiling and error positions with messages<br/>

of what went wrong during compilation.<br/>

*/
<br/>

class CompilationFailedException ( val programme: String ,<br/>

val messages: Iterable [ ( Int, String ) ] ) <br/>

extends Exception ( messages. map ( message = > message._1 + ". " + message._2 ) . mkString ( "n" ) ) <br/>







ところで、コメントの形式に興味があるなら、これはCircumflex Doccoです。これは視覚的なドキュメントにとって素晴らしいことです。



最後に、それは間違っています。Scalaは急速に開発されており、開発者がAPIを変更することもあると言わなければなりません。 このコードはバージョン2.9.0.1で正常にテストされており、すべての2.8.xおよび2.9.xで動作するはずです。



All Articles