Pnuts API は、Java プログラムから Pnuts の機能を利用するための API です。この API を利用する際に特別な初期化の手続き等は必要なく、Java プログラムの任意の場所で利用することができます。Pnutsインタープリタは、それを利用する Java プログラムと仮想機械のスレッド資源とオブジェクト空間を共有します。
クラス 説明 pnuts.lang.Pnuts スクリプトを実行するためのpublic staticメソッドを提供 pnuts.lang.Context スクリプト実行の環境 pnuts.lang.Package 関数の外側の名前空間 pnuts.lang.PnutsFunction Pnutsの関数 pnuts.lang.PnutsException スクリプト実行時の例外 Pnuts APIの中心的なクラス
pnuts.lang.Pnuts クラスには、インタープリタの実行を開始するためのメソッドがあります。
実行できるスクリプトの種類は、次のとおりです。
スクリプト文字列を実行するには、Pnuts.eval()メソッドを使います。
- public static Object eval( String exp , Context context );
次の例は、Pnuts.eval() メソッドを利用する単純な例です。スクリプト(文字列)と、最初に生成したContextオブジェクト(後述)を、Pnuts.eval()メソッドに渡しています。
import pnuts.lang.*;
import java.math.*;
class Foo {
public static void main(String arg[]){
Context context = new Context();
BigInteger bint = (BigInteger)Pnuts.eval("1<<100", context);
...
}
}
スクリプト(InputStream/Reader)をパーズする(実行はしない)には、Pnuts.parse(InputStream/Reader) メソッドを使います。 pnuts.lang.Pnuts クラスのインスタンスを生成できるのは、このメソッドだけです。 pnuts.lang.Pnuts クラスのインスタンスは run(Context) メソッドで実行できます。
- public static Pnuts parse( InputStream in ) throws ParseException;
- public static Pnuts parse( Reader in ) throws ParseException;
- public static Pnuts parse( Reader in, Object scriptSource, Context context) throws ParseException;
- public Object run( Context context);
次のプログラムは、はじめにスクリプトのパーズだけを行い、後で実行する方法を示します。文法エラーを処理するには、ParseExceptionをcatchします。
import pnuts.lang.*;
import java.io.*;
class ParseTest {
public static void main(String a[]){
Pnuts p = null;
try {
p = Pnuts.parse(new FileInputStream(a[0]));
} catch (ParseException e1){
...
} catch (IOException e2){
...
}
...
p.run(new Context());
}
}
複数の文法エラーを取得するには、次のメソッドのいずれかを用います。
- public static Pnuts parse( Reader in , ParseEnvironment env) throws ParseException;
- public static Pnuts parse( Reader in, Object scriptSource, Context context, ParseEnvironment env) throws ParseException;
import pnuts.lang.*;
import java.io.*;
public class Test {
public static void main(String[] args) throws IOException {
try {
FileReader r = new FileReader(args[0]);
Pnuts.parse(r, new ParseEnvironment(){
public void handleParseException(ParseException e) throws ParseException {
System.out.println(e);
}
});
} catch (ParseException e){
throw new Error(); // never happens
}
}
}
スクリプト・ソースは、Pnutsオブジェクトの属性の一つで、スクリプトの場所(通常はURL)を表します。
- public Object getScriptSource();
- public void setScriptSource(Object scriptSource);
スクリプト・ソースの情報は、おもにデバッガが使います。
PnutsFunctionは、Pnutsの関数を表します。PnutsFunction.call()メソッドを使うと Pnuts の関数を実行することができます。
- public Object call( Object[] args, Context context );
import pnuts.lang.*;
class Foo {
public static void main(String arg[]){
Context context = new Context();
PnutsFunction func = (PnutsFunction)Pnuts.eval("function foo() 100", context);
...
System.out.println(func.call(new Object[]{}, context));
}
}
Javaによる関数の定義を参照。
パッケージは、複数の再利用可能なスクリプトを同時に利用するときに名前の衝突を防ぐための名前空間です。1つのパッケージは1つの記号表を持ちます。
Packageクラスには、パッケージ名と親パッケージをパラメータに取るコンストラクタがあります。 これらのコンストラクタを呼び出すことで、パッケージ階層が、暗黙的または明示的に作られます。
- public Package();
- public Package( String name );
- public Package( String name, Package parent);
パッケージ階層の中で親パッケージを持たないパッケージのことをルート・パッケージと呼びます。parentに明示的にnullが指定された場合、そのパッケージ自身が新たなルートパッケージになります。スクリプトの中でload()やloadFile()等により別のスクリプトを読み込むと、そのスクリプトは、カレント・パッケージを基準としたルート・パッケージで実行を開始します。
コンストラクタで、parentが指定されない場合は、グローバル・パッケージと呼ばれる特別なルート・パッケージが親パッケージになります。pnutsコマンドからスクリプトを実行する場合には、、グローバル・パッケージがカレント・パッケージの状態でスクリプトが実行され、グローバル・パッケージ以外にルート・パッケージを作成する必要性はあまりありません。
しかし、隔離されたスクリプト実行環境には、グローバル・パッケージ以外のルート・パッケージが必要になります。スクリプト実行環境ごとに、ルート・パッケージが用意され、そのルート・パッケージ以下の階層(以下、パッケージ・ツリーと呼ぶ)に、そのスクリプト実行環境で定義されるすべての名前が互いに干渉しないように保存されます。
グローバル・パッケージは、pnuts.lang.Packageクラスのstatic変数から参照されるので、GCの対象になることはありませんが、それ以外のルート・パッケージは、通常のオブジェクトと同様に、他のどのオブジェクトからも参照されなくなるとGCの対象になります。
Package オブジェクトを作るために、次のメソッドがよく使われます。pkgNameという名のパッケージがない場合はそのパッケージが作成されます。作成されたパッケージは、(カレントパッケージを基準とした)ルートパッケージに管理され、パッケージ名で識別されます。
- public static Package getPackage( String pkgName, Context context );
次のメソッドはパッケージに含まれる記号に対するアクセスメソッドです。
- public Object get( String symbol, Context context );
- public void set( String symbol, Object value, Context context);
- public boolean defined( String symbol, Context context);
- public void clear( String symbol, Context context);
get()メソッドだけは、変数定義が見付かるまでパッケージ階層を遡ります。それ以外のメソッドは、そのPackage内の記号表にだけアクセスします。
これらのメソッドのパラメータsymbolは、intern()されたStringオブジェクトでなければなりません。
Autoloadの機能を利用すると、変数が参照されるまで変数の値を代入するのを遅らせることができます。
Package.get(String, Context)は、記号表内に変数が未定義の場合に、autoload()メソッドでその変数のAutoloadフックが登録されていれば、そのフックを起動します。Autoloadフックが登録されていない場合や、Autoloadフックを起動しても変数が定義されない場合は、親Packageを遡って同じ処理を繰り返します。
- public void autoload( String symbol, String file, Context context );
- public void autoload( String symbol, AutoloadHook hook );
Context.usePackage()メソッドを呼び出して、未登録のモジュールを登録しようとすると、そのモジュールの初期化スクリプトが実行されます。その時に、カレントパッケージを含むパッケージ木の中で、モジュールに対応するサブパッケージが自動的に作成され、その中にモジュールの関数が定義されます。
Packageオブジェクトは、モジュールとして使われる場合には、モジュールが公開する名前を保持しています。モジュールの初期化スクリプトは、モジュールが公開する名前をPackage.export()メソッドで登録します。
- public void export( String symbol );
モジュールの初期化スクリプトの実行が終わったときに、一つも名前が公開されなかった場合は、 Pacakgeに定義された(匿名でない)関数が自動的に公開されます。
また、pnuts.ext.ModuleBaseクラスのautoload()メソッドやautoloadFunction()メソッドは、処理の中でexport()メソッドを呼んでいるため、それらのメソッドを用いる場合は直接export()メソッドを呼ぶ必要はありません。
Contextオブジェクトは、実行前に設定される各種の情報や、実行中に変化する状態を保持します。 スクリプトを実行する際に指定されたContextは、実行中のスクリプトから常に参照されます。
Context オブジェクトは次のような Pnuts の実行環境の内部状態を保持します。スクリプトを実行するためには、最初に明示的にContextオブジェクトを作成する必要があります。
- public Context();
- public Context(Package pkg);
- public Context(Package pkg, Object source);
- public Context(Context template);
Context クラスのデフォルトコンストラクタは、グローバル・パッケージをカレント・パッケージとする Context オブジェクトを生成します。コンストラクタ Context(Package anPackage)は、Contextを生成しカレント・パッケージを指定します。
Pnuts.eval()やPnuts.load()のパラメータにContextを指定することで、複数のインタープリタに同じ Context を渡すこともできます。また、同時に複数のスレッドから利用することもできます。
スクリプトから組み込み関数eval()、load()、require()、loadFile()が呼ばれると、使用中のContextのクローン(複製)が作られて渡されます。
Contextオブジェクトには、クローン間で共有される属性、クローンを作成する度にコピーされる属性、クローンを作成する度に初期値にリセットされる属性があります。
属性 クローン作成時の動作 クラスローダ 共有 use()で追加されたモジュール 共有 load()で読み込まれたファイルのリスト 共有 メッセージの出力先 コピー コンテクスト・ローカル変数 コピー Implementationオブジェクト コピー Configuration コピー 文字コード コピー import された Java パッケージのリスト リセット カレント・パッケージ リセット
クローン間で共有される属性は、あるクローンで値を修正すると、同じ種から作られたクローンすべてに影響します。
クローンの度にコピーされる属性は、子孫に同じ値を継承しますが、親や先祖のコンテクストには影響を与えません。
組み込み関数load()、require()、loadFile()が呼ばれると、カレントパッケージをパッケージ階層の一番上のパッケージ(通常はグローバル・パッケージ)に設定し、importの状態を初期値(java.lang.*と*)に戻した上で、指定されたスクリプトが実行されます。
すべての属性をコピーしたい場合(共有したくない場合)は、コンストラクタContext(Context)を使います。
Context オブジェクトには次の3種類の出力先を別々に指定できます。
- public OutputStream getOutputStream ();
- public void setOutputStream (OutputStream out);
- public PrintWriter getWriter ();
- public void setWriter (Writer writer);
- public PrintWriter getErrorWriter ();
- public void setErrorWriter (Writer out );
- public PrintWriter getTerminalWriter ();
- public void setTerminalWriter (Writer out );
コンテクスト・ローカル変数は、コンテクストにバインドされた一種の環境変数です。関数呼び出しをまたいで状態を保持したい場合に用います。
- public void set (String symbol , Object value);
- public Object get (String symbol );
パラメータsymbol は、intern()された文字列でなければならないことに注意して下さい。
コンテクスト・ローカル変数は、Contextオブジェクトに対するフィールド・アクセス式でアクセスできます。
context = getContext() context.foo ==> null context.foo = date()
Contextはある文字コードを使うことを宣言することができます。
- public void setScriptEncoding (String ecoding );
- public String getScriptEncoding ();
クラスローダをコンテクストに対応付けることができます。クラス名を解決するためや、load()関数がスクリプトを探すために使われます。
- public void setClassLoader (ClassLoader loader );
- public ClassLoader getClassLoader ();
「インタプリタ実装のカスタマイズ」を参照。
- public void setImplementation (Implementation impl );
- public Implementation getImplementation ();
Configurationは、メソッドやフィールド候補の範囲や、フィールド参照のセ マンティクスを定義したものです。
- context . setConfiguration ( Configuration impl )
- context . getConfiguration ( )
use()関数およびContext.usePackage()メソッドは、実行中のContextに対してモジュールを登録します。
- public boolean usePackage (String name );
- public String[] usedPackages ();
- public void clearPackages ();
usePackage() は、モジュールをコンテクストに登録し、モジュールの初期化スクリプトを実行します。 usedPackages() は、登録済みのモジュール名のリストを返します。 clearPackages() は、モジュールをリセットします。
Contextは、登録ずみのモジュールのリストを保持していて、モジュールが提供する関数を参照する際に、そのリストから関数を検索します。モジュール関数の定義は、ContextではなくPackageが保持します。
- eval(expr)
- 実行時の Context のコピーを使ってexprを評価
- eval(expr, context)
- contextのコピーを使ってexprを評価
- Pnuts::eval(expr)
- 新に作成した Context を使ってexprを評価
- Pnuts::eval(expr, Context())と等価
- Pnuts::eval(expr, context)
- contextを使ってexprを評価
Pnuts::eval(expr, context)に実行中の Context を与えると、import() 関数などで呼び出し側のコンテクストに影響を与えることが可能です。 一方、eval() はコピーを使いますので、import()や package()関数でもとの Context を変更することはできません。
Pnuts.eval(), Pnuts.load(), または Pnuts.loadFile() メソッドをつうじてスクリプトを実行中に例外が送出された場合、その例外は呼び出し元に伝わります。
送出される例外は pnuts.lang.PnutsException で、実際の例外をカプセル化します。元の例外を取り出すには、PnutsException.getThrowable()メソッドを呼び出します。
- public Throwable getThrowable();
import pnuts.lang.*;
import java.io.*;
class Foo {
public static void main(String arg[]){
try {
Object ret = Pnuts.eval(arg[0], new Context());
System.out.println("ret = " + ret);
} catch (PnuteException e){
if (e.getThrowable() instanceof IOException){
e.printStackTrace();
} else {
System.out.println("caught: " + e);
}
}
}
}
getScriptSource()は、エラーのあったスクリプトのソースを返します。通常は、URLオブジェクトです。 getLine()は、エラーのあった行番号を返します。
- public Object getScriptSource();
- public int getLine();
Pnuts.run(Context)で実行されるスクリプトで発生したエラーに関する情報を得るためには、Pnuts.setScriptSource()で、スクリプト・ソースsourceを指定しておく必要があります。
import pnuts.lang.*;
import java.io.*;
class Foo {
public static void main(String arg[]){
try {
File file = new File(arg[0]);
FileInputStream in = new FileInputStream(file);
Pnuts expr = Pnuts.parse(in);
expr.setScriptSource(file);
expr.run(new Context());
} catch (PnuteException e){
System.out.println(e.getScriptSource() + ": " + e.getLine());
}
}
}