Javaによるモジュールの実装

モジュールの初期化スクリプトは、load可能なクラスで実装することができます。 したがって、pnuts.lang.Executableインタフェースの実装クラスで実現できます。 しかし、通常はpnuts.ext.ModuleBaseのサブクラスで実装されることが多いです。

たとえば、"my_module"モジュールのを定義してみましょう。デフォルトの初期化スクリプトは、"my_module/init" なので、my_module.initクラスをpnuts.ext.ModuleBaseのサブクラスとして定義します。public Object execute(Context)メソッドで、"my_module"パッケージに関数helloを定義します。

package my_module;

import pnuts.ext.ModuleBase;
import pnuts.lang.Context;
import pnuts.lang.Package;

public class init extends ModuleBase  {
    public Object execute(Context context){
        Package pkg = Package.getPackage("my_module", context);
	  pkg.set("hello".intern(), new hello(), context);
	  return null;
    }
}

Package.set()メソッドに渡すシンボルはintern()されたStringオブジェクトでなければならないことに注意して下さい。

他のモジュールへの依存

モジュールが別のモジュールに依存している場合(モジュール実装のために他のモジュールが必要になる場合)、 ModuleBase.getRequiredModules()メソッドでモジュール名の配列を返すように定義します。

たとえば、次の例は、pnuts.libとpnuts.ioに依存するmy_moduleモジュールを定義する例です。

package my_module;

import pnuts.lang.Context;
import pnuts.ext.ModuleBase;

public class init extends ModuleBase {
    protected String[] getRequiredModules(){
        return new String[]{"pnuts.lib", "pnuts.io"};
    }
    ...
}

サブモジュール

モジュールをuse()したときに自動的にuse()されるモジュールをサブモジュールと呼びます。 サブモジュールを定義するには、ModuleBase.getSubModules()メソッドでモジュール名の配列を返すように 定義します。

たとえば、次の例は、functionalモジュールをサブモジュールとして定義します。

package my_module;

import pnuts.lang.Context;
import pnuts.ext.ModuleBase;

public class init extends ModuleBase {
    protected String[] getSubModules(){
        return new String[]{"functional"};
    }
    ...
}

サブモジュールや依存するモジュールを実行時に指定する方法

getRequiredModules()やgetSubModules()でモジュールのリストを定義するのではなく、execute()メソッドで Context.usePackage()を呼び出すことで、サブモジュールや依存するモジュールを実行時に定義することができます。

手順は、次のようになります。

  1. context.usePackage()でサブモジュールを指定する。
  2. context.clearPackage()メソッドを呼び出す。
  3. context.usePackage()で依存するモジュールを指定する。

たとえば、次の例は、functionalモジュールをサブモジュールとし、pnuts.libとpnuts.ioに依存するmy_moduleモジュールを定義する例です。

package my_module;

import pnuts.lang.Context;
import pnuts.ext.ModuleBase;

public class init extends ModuleBase {
    public Object execute(Context context){
        context.usePackage("functional");
        context.clearPackages();  // モジュール・リストのリセット
        context.usePackage("pnuts.lib");
        context.usePackage("pnuts.io");
	...
    }
}

初期化スクリプトの指定

META-INF/pnuts/module/モジュール名 というリソースファイルの一行目に初期化スクリプトの名前を指定することができます。 たとえば、java.netモジュールの初期化スクリプトは、META-INF/pnuts/module/java.netというファイルの一行目で、

org/pnuts/java_net/init
のように指定されています。

JavaによるAutoload機能の利用

Autoloadはモジュールの初期化のためにかかる時間を小さくするために使われます。

pnuts.lang.Packageクラスのautoload()メソッドは、Pnutsのスクリプト、またはLoad可能なクラスをautoloadするために利用できます。しかし、ここで説明するpnuts.ext.ModuleBaseクラスは、autoloadを行うためのもっと使いやすいメソッドをいくつか提供しています。

(1) Pnutsで実装された関数のAutoload

Pnutsのスクリプト、またはLoad可能なクラスをautoloadするにはModuleBase.autoload()メソッドを使います。 次の例は、my_module/hello.pnutにhello()という関数が定義されているとして、その関数が使われたときにはじめてスクリプトをロードするように設定しています。

package my_module;

import pnuts.lang.Context;
import pnuts.ext.ModuleBase;

public class init extends ModuleBase {
    static String[] files  = { "my_module/hello" };
    static String[][] functions = {
        { "hello" }
    };
    
    public Object execute(Context context){
	for (int i = 0; i < files.length; i++){
	    autoload(functions[i], files[i], context);
	}
	return null;
    }
}

(2) Javaで実装された関数のAutoload

PnutsFunctionのサブクラスとして定義されたPnuts関数をautoloadするには、ModuleBase.autoloadFunction()メソッドを使います。 たとえば、次のクラスはmy_moduleモジュールの初期化処理を定義しています。シンボル'hello'の値を初めて参照したとき、org.pnuts.my_module.hello クラスが読み込まれて、デフォルト・コンストラクタでインスタンス化され、Pnutsインタプリタに返されます。

package my_module;

import pnuts.lang.Context;
import pnuts.ext.ModuleBase;

public class init extends ModuleBase {
    static String[] javaFunctions = { "hello" };
    
    protected String getPrefix(){
	return "org.pnuts";
    }

    public Object execute(Context context){
	for (int i = 0; i < javaFunctions.length; i++){
	    autoloadFunction(javaFunctions[i], context);
	}
	return null;
    }
}

getPrefix()メソッドは、クラス名のプリフィックスを定義します(デフォルトはnull)。 この例では、クラス名は、プリフィックス"org.pnuts"、モジュール名 "my_module"、関数名 "hello"から"org.pnuts.my_module.hello"というクラス名が構成されます。 getPrefix()メソッドを定義しない場合は、クラス名のプリフィックスが空文字になります。

(3) JavaのクラスのAutoload

クラス名(短い名前)をはじめて参照したときにクラスをロードするには、ModuleBase.autoloadClass()メソッドを使います。たとえば、次の例は、VectorとHashtableという名前を定義しています。

package my_module;

import pnuts.lang.Context;
import pnuts.ext.ModuleBase;

public class init extends ModuleBase {
    public Object execute(Context context){
	for (int i = 0; i < javaFunctions.length; i++){
	    autoloadClass("java.util", "Vector", context);
	    autoloadClass("java.util", "Hashtable", context);
	}
	return null;
    }
}

エラーメッセージのローカライズ

pnuts.libモジュールが使われている場合は、関数formatMessage()が利用できますが、pnuts.libモジュールに依存しないモジュールで同じことをするにはどうしたらよいでしょう?

初期化コードがpnuts.ext.ModuleBaseのサブクラスである場合、モジュールの関数定義の中で関数ERROR()を利用できます。

ERROR ( String errorType {, String param_1 ... } )

この関数は、"errors.properties"ファイルからメッセージ・リソースを見つけ、関数formatMessage(),と同じやりかたでメッセージをフォーマットし、pnuts.lang.PnutsExceptionを送出します。

if (!isFunction(obj)){
  ERROR("must_be_a_function", obj)
}
must_be_a_function=A function is expected: {0}