メンバページ: Go

やさしい Lisp の作り方 番外編 (MIDP Lisp on J2ME MIDP)

PHS/携帯電話で動作する Lisp 処理系の作り方


このページでは Willcom/SANYO の PHS である WA310SA で動作する Lisp 処理系を作っていきます。


1日目
1開発環境の設定
2J2ME/MIDP プログラムの作り方一巡り
3Lisp 画面の作成
4Lisp 処理系の作成方針
5MIDP Lisp 処理系の完成
6MIDP 開発の注意点
7PHS の JavaVM の性能
2日目
8GUI の再設計
9履歴機能の追加と組込み S 式関数
102日目のMIDP Lisp の PHS での実行
11MIDP Lisp の WX310SA での簡単な使い方

1日目の MIDP Lisp はここにあります。
2日目の MIDP Lisp はここにあります。
PHS/携帯電話への直接のダウンロードはここから

開発環境の設定

2005年11月に発売された Willcom キャリアの SANYO 製の PHS 「WX310SA」には、PHS で Access 社の JavaVM が搭載されています。 JavaVM のプロファイルは、J2ME CLDCMIDP2.0 対応に なっています。

まずは、それに対応した開発環境をインストール、設定します。

  1. J2ME Wireless (MIDP2.0) のダウンロード
    以下の URI からダウンロードできます。
    http://java.sun.com/products/sjwtoolkit/ja_download-2_2.html
    ここでは Windows 版のものをダウンロードしました。
    但し、ユーザ認証が必要になります。
  2. J2ME MIDP2.0 のインストール
    ダウンロードしたファイルを実行するだけでインストールできます。
    動作検証した j2se の版は j2se 1.4.2_02 となっています。
    またインストール先のデフォルトフォルダは C:\WTK22 になります。
  3. MIDP の Eclipse プラグインのダウンロード
    以下の URI からダウンロードできます。
    http://sourceforge.net/project/showfiles.php?group_id=86829
  4. Eclipse MIDP プラグインのインストール
    ダウンロードしたものを既存の Eclipse フォルダにオーバーライトします。
  5. Eclipse プラグインの設定
    「ウィンドウ」、 「設定」、「J2ME」、 「Platform Components」、
    「Wireless Toolkits」を右クリック、 「add Wireless Toolkit」、
    「Browse」で 1 で入れたフォルダを指定(デフォルトは C:\WTK22)します。
    WX310SA は CLDC1.1 の MIDP 2.0 で動作しますので、
    jad ファイルやエミュレータの設定をそれに合わせます。
    エミュレータは「構成及び実行」,「enulation」,「...MIDP2.0...」で設定します。
このページトップへ

MIDLet プログラムの作り方一巡り  

MIDP プログラム MIDLet の作成を一巡りします。

開発は Eclipse か、 WTK(J2ME MIDP 2.0に含まれる開発環境)で行います。ここでは Eclipse で作成することにします。また、その比較は後述します。

なお、今回使用している Eclipse は 3.1 のもので、JDK は 1.4.2 です。

プロジェクトの作成

Eclipse で「新規」「プロジェクト」「J2ME」「J2ME Midlet Suite」で、プロジェクトを新規作成します。プロジェクト名などを入れていきます。

クラスの作成

次にクラスを新規作成します。新規に作成したプロジェクトを選択し右クリックして、「新規」、「その他」、「J2ME」、「J2ME Midlet」を選びます。
「次へ」を押し、パッケージ名やクラス名を入れ、「終了」を押します。

次に実際のプログラムを作成していきます。

エミュレータでの実行

「実行」 「構成および実行」,「Wireless Toolkit Emulator」を選択し、「新規」を押し、そこで名前やプロジェクト、Exeutable Midlet にチェックを入れ、実行するクラス名を入れます。

パッケージング

プロジェクトを選択し、右クリックして、「J2ME」「Create Packages」で、 deployed フォルダの下に jad, jar ファイルが生成されます。

デプロイメント

パッケージングした jad ファイルと jar ファイルを PHS の実機にデプロイメントします。これには、PHS でダウンロードする方法と、miniSD を経由してデプロイメントする方法があります。ここでは簡単な miniSD 経由の方法を紹介します。

miniSD へ jad ファイルなどを持っていくには、(1) PHS へファイル添付してメイルで送る、(2) PHS を USB ストレージにしてコピーする、(3) miniSD を PHS から取り出して、直接コピーする方法があります。

どの方法をとっても、最終的には「PCデータ」を格納するフォルダ(PC_INOUT)に入れます。このフォルダからインストールしないと正しくインストールできません。メイルによる方法では「その他のデータ」フォルダに入っていますので、それを「PCデータ」フォルダに移動する必要があります。

インストール

「PCデータ」に格納した jad ファイルを選択し「決定」ボタンを押すことで Java プログラムがインストールされます。jad ファイル名と jar ファイル名が同一で区別できないときでも「決定」ボタンが表示されるのは jad ファイルのみですので、決定ボタンが表示されるものを選び、決定ボタンを押します。

実行

「カメラ」ボタンの長押しで Java実行環境を起動し、Java プログラムを選択して、実行します。

(おまけ)アイコンの作成と登録

アイコンの画像ファイルを作成します。サイズは20x20 以内がいいでしょう。PNG などもサポートしています。

それを適当なフォルダに格納します。慣例としては res/icons の下に格納します。

jad ファイルにアイコンを登録します。Eclipse で jad ファイルを編集して、「optional」タグにある「MIdlet Icon:」にアイコン画像ファイル名を入れます。例えば、「res/icons/MIDLisp.PNG」のようにします。

このページトップへ

Lisp 画面の作成

まず、Lisp の画面を作成します。TextField で入力した文字列を S 式としてリードし、それを評価して、値の S 式を文字列に直して、TextField に出力するようにします。画面としては、入力用のTextField と出力用の TextField をレイアウトし、評価ボタンと終了ボタンを作ります。

ここで出力も TextField にしたものは、見た目が統一されているという理由です。

画面の Java プログラムを以下に示します。

public class Lisp extends MIDlet implements CommandListener {
  private Form lispForm;
  private TextField input;
  private TextField output;
  private Command evalCommmand;
  private Command endCommand;
  static public MainLisp lisp;

  public Lisp() {
    super();
    lispForm = new Form("MIDP Lisp");
    input = new TextField("S式入力", "", 200, TextField.ANY);
    output = new TextField("値", "", 200, TextField.ANY);
    evalCommmand = new Command("評価", Command.OK, 1);
    endCommand = new Command("終了", Command.EXIT, -1);
    lispForm.addCommand(evalCommmand);
    lispForm.addCommand(endCommand);
    lispForm.append(input);
    lispForm.append(output);
    lispForm.setCommandListener(this);
    lisp = new MainLisp();
  }

  public void commandAction(Command c, Displayable d) {
    if (c == endCommand) {
      notifyDestroyed();
    } else if (c == evalCommmand) {
(snip)
    }
  }
                
  protected void startApp() throws MIDletStateChangeException {
    Display.getDisplay(this).setCurrent(lispForm);
  }

  protected void pauseApp() {}
  protected void destroyApp(boolean arg0) throws MIDletStateChangeException {}
}

AWT や Swing とほぼ同じ感覚でプログラムすることが可能です。

このページトップへ

Lisp 処理系の作成方針

Lisp 処理系そのものは、以前に作成した funado,lisp を元に作成します。名前は MIDP で動作しますので 「MIDP Lisp」 と呼びます。

組込み Java では、メモリフットプリントを小さくする必要があります。MIDP Lisp のメモリフットプリントは、Lisp のシンボルを格納するシンボルテーブル、Lisp の S 式を格納するヒープメモリやLisp プログラムを実行するときに使用するスタックメモリが影響を与えます。さらに Lisp が動作するときに一時的に使用する Java のデータ領域(Java ヒープ)もあります。

  1. シンボルテーブル --- Java のハッシュテーブルを使わずに独自作成することで、高速化しメモリの使用量を抑えます。サイズは256 にしています
  2. ヒープ --- 制限していません。単純に new して Java のメモリ管理に委譲しています
  3. スタック --- サイズを 1024 にしています
  4. Java ヒープ --- 特に制限していません。
  5. Java スタック --- 特に制限していません。

プログラム自身のサイズも小さくすることを考えます。

プリンタは文字列で受け渡すことにより、廃止しました。つまり、文字列化がプリンタになります。これや他のことにより、プログラムサイズは、ベンチマーク用関数 tak が動作する程度の仕様で750行程度に抑えてあります。Hashtable を手作りしなければ 700 行程度のものになります。

このページトップへ

MIDP開発の注意点

エミュレータを動作するときには、デフォルトでは jad/jar ファイルで動作するのではなく、通常と同じ「構成及び実行」で指定したスタートクラスを含む設定と class ファイルで動作します。このため、「エミュレータで動作するが、実機では動作しない」という状況は、プロファイルのアンマッチ以外ではここでも起こります。

ここの部分は将来増補していくようにします。

このページトップへ

MIDP Lisp 処理系の完成

以下に作成した Lisp 処理系を置きます。以前に作成した funado,lisp のサブセットになっています。 ベンチマーク関数 tak は動作するようにしています。

MIDP Lisp 処理系 (MIDPLisp.jad)

MIDP Lisp 処理系 (MIDPLisp.jar)

MIDP Lisp 処理系 (MIDPLisp.java ソースは1個のファイルのみ)

MIDP Lisp 処理系 (一式: ソース、jar, jad ファイル、 Eclipse プロジェクトファイルなど)

 


                  エミュレータの画面イメージ
このページトップへ

PHS の JavaVM の性能

それでは、早速 tak でベンチマークをしてみました。

(defun tak (x y z)
  (if (>= y x) 
      z 
      (tak (tak (- x 1) y z) 
           (tak (- y 1) z x) 
           (tak (- z 1) x y))))

tak5 (tak 10 5 0) で計測してみました。その結果は約 14秒でした。
これでも昔(うん十年前)のLisp マシンの性能に匹敵します。また上記の tak 関数は1行で入力する必要があります。

GUI がシンプルですので、他の MIDLet マシン (ボーダフォンなど)でも動作するかと思います。是非、ベンチマークしてみてください。
(注意) 2006/2/6 の版では tak は最初から組み込んでいて、ボタン一つで tak5 が実行できるようになっています。ベンチマークされる方はご一報くだされば、この版を差し上げます。但し、GUI が複雑になっています。

一方、エミュレータでの実行では Movile Pentium 1.3GHz、512MB のノートPC上で実時間(我慢できる時間)に結果は返ってきませんでした。

 

万が一、気に入りましたら、是非使ってみてください。

See you again !!

このページトップへ

GUI の再設計

今までのは、Lisp 画面がシンプルでしたので、GUI の設計をして、作成していきます。

右キー(WA310SA では「Web」キー)で、入力した S 式を評価し、左キー(WA310SA では「メール」キー)で終了するという簡単なものでした。

そこでイメージボタンやソフトボタンを配置して、GUI を設計してみます。

ソフトボタンとして、まずは右キーにも割り当てている「評価」ボタンを画面上ソフトボタンにします。また入力テキストフィールドや出力フィールドをクリアするためのボタンも用意します。さらに入力 S 式の履歴機能を作成し、そのボタンとして「履歴↓」(履歴の過去方向への検索)と「履歴↑」(履歴の未来方向への検索)を付けるようにします。さらにベンチマーク関数の tak を直ぐに実行できるように「tak5」ボタンも作成します。

イメージボタンとして、MIDP Lisp のロゴを先頭に表示して、それをクリックするとメッセージが表示されるようにします。

これらの画面イメージを以下に示します。


          GUI 部の再設計


以下に、MIDlet クラスを紹介します。

public class Lisp extends MIDlet {
  static public MiniLisp lisp;
  static public Lisp lispLet;
	
  public Lisp(){
    lisp = new MiniLisp();
    lispLet = this;
  }
	
  protected void startApp() throws MIDletStateChangeException {
    Display.getDisplay(this).setCurrent(new LispForm());		
  }

  protected void pauseApp() {}
  protected void destroyApp(boolean arg0) throws MIDletStateChangeException {}
}

画面関係のプログラムもリファクタリングします。今までのものは MIDlet の Lisp クラスに Form も入れていましたが、今回はそれを分離しました。

class LispForm extends Form implements ItemCommandListener, CommandListener {
  private TextField input;
  private TextField output;
  private StringItem evalButton;
  private StringItem clearButton;
  private StringItem historyButton;
  private StringItem rhistoryButton;
  private StringItem benchButton;
  private Command evalCommand;
  private Command endCommand;
  private ImageItem evalImage;

  public LispForm() {
    super("MIDP Lisp");
                
    // イメージ
    Image image = null;
    try {
      image = Image.createImage("/res/icons/title.PNG");                
    } catch (Exception e) {
      e.printStackTrace();
    }
    evalImage = new ImageItem("", image, 
                  Item.LAYOUT_2 | Item.LAYOUT_NEWLINE_AFTER, "評価", Item.PLAIN);
    evalImage.setDefaultCommand(new Command("評価", Command.SCREEN, 1));
    evalImage.setItemCommandListener(this);
    append(evalImage);          

タイトルのイメージを貼り付けました。/res/icons/title.PNG がソースイメージファイルになります。 Item.PLAIN ですので見かけは単なるイメージに見えますが、コマンドを設定していますので、ソフトボタンとして動作します。

レイアウトは MIDP2.0 のデフォルトである Item.LAYOUT_2 です。Item.LAYOUT_NEWLIBE_AFTER はイメージの表示後、改行するように指示を出しています。

    // 入力
    input = new TextField("S式入力", "", 200, TextField.ANY);
    input.setLayout(Item.LAYOUT_2);
    append(input);

この入力テキストフィールドは前回のものと同じです。

    // ボタン
    evalButton = new StringItem("", "評価", Item.BUTTON);
    evalButton.setLayout(Item.LAYOUT_2 | Item.LAYOUT_NEWLINE_BEFORE);
    evalCommand = new Command("評価", Command.SCREEN, 1);
    evalButton.setDefaultCommand(evalCommand);
    evalButton.setItemCommandListener(this);
    append(evalButton);

ソフトボタンを StringItem で作成します。画面イメージは Item.BUTTON で指定してボタンのように見えます。 イメージボタンのときと同様にコマンドの設定を行います。レイアウトは Item.LAYOUT_NEWLINE_BEFORE で 直前に改行を入れるようにしています。

以下に示すように他のボタンも同様に定義します。

    clearButton = new StringItem("", "クリア", Item.BUTTON);
    clearButton.setLayout(Item.LAYOUT_2 | Item.LAYOUT_DEFAULT);
    clearButton.setDefaultCommand(new Command("クリア", Command.SCREEN, 2));
    clearButton.setItemCommandListener(this);
    append(clearButton);

    historyButton = new StringItem("", "履歴↓", Item.BUTTON);
    historyButton.setLayout(Item.LAYOUT_2 | Item.LAYOUT_DEFAULT);
    historyButton.setDefaultCommand(new Command("履歴↓", Command.SCREEN, 3));
    historyButton.setItemCommandListener(this);
    append(historyButton);

    rhistoryButton = new StringItem("", "履歴↑", Item.BUTTON);
    rhistoryButton.setLayout(Item.LAYOUT_2 | Item.LAYOUT_DEFAULT);
    rhistoryButton.setDefaultCommand(new Command("履歴↑", Command.SCREEN, 4));
    rhistoryButton.setItemCommandListener(this);
    append(rhistoryButton);

    benchButton = new StringItem("", "tak5", Item.BUTTON);
    benchButton.setLayout(Item.LAYOUT_2 | Item.LAYOUT_DEFAULT);
    benchButton.setDefaultCommand(new Command("ベンチ", Command.SCREEN, 5));
    benchButton.setItemCommandListener(this);
    append(benchButton);

これでソフトボタンを定義したことになります。

    // 出力
    output = new TextField("値", "", 200, TextField.ANY);
    output.setLayout(Item.LAYOUT_2);
    append(output);

    // コマンド
    addCommand(evalCommand);

    endCommand = new Command("終了", Command.EXIT, -1);
    addCommand(endCommand);	

    setCommandListener(this);

    // 履歴
    history = new History();

  }

出力テキストフィールドやコマンドの登録は前回のものと同じです。履歴の history は履歴管理のために使用します。(後述)

  public void commandAction(Command c, Item item) {
    if (item == evalImage) {
      input.setString("'(Welcome to MIDP Lisp !!)");
      output.setString("  Let's enjoy Lisping ...");                    
    } else if (item == evalButton) {
      eval();
    } else if (item == clearButton) {
      input.setString("");
      output.setString("");                     
    } else if (item == historyButton) {
      input.setString(history.down());
    } else if (item == rhistoryButton) {
      input.setString(history.up());
    } else if (item == benchButton) {
      java.lang.String sexp = "(tak 10 5 0)";
      input.setString(sexp);
      eval(sexp);
    }
  


コマンドアクションは、ソフトボタンに対応するものはここで定義します。 なお、上記にある history オブジェクトは後で別途定義します。

  public void commandAction(Command c, Displayable d) {
    if (c == endCommand) {
      Lisp.lispLet.notifyDestroyed();
    } else if (c == evalCommand) {
      eval();
    }
  }
}

ハードコマンドに対するアクションは前回と同じものになります。

これで上記の画面に対するプログラムになります。このプログラムからも AWT や Swing と同じフレームワークであり、それほど違和感なくプログラミングできるようになっています。

このページトップへ

履歴機能の追加と組込み S 式関数

Lisp 処理系の拡張機能として、 履歴機能を実装してみます。Lisp 自身ではなく、外部機能として実装します。 入力 S 式を Java の文字列として、サイクリックな配列に格納するようにします。

class History {
  private java.lang.String history[];
  private int historyp;
  private int searchp;
  final static int HISTORYSIZE = 10;

  History (){
    history = new java.lang.String[HISTORYSIZE];
    historyp = searchp = 0;	
    for (int i = 0; i < HISTORYSIZE; i++) history[i] = "";
 }
	
  void add(java.lang.String str){
    if (historyp >= HISTORYSIZE) historyp = 0;
    history[historyp++] = str;
    searchp = historyp;
  }
	
  java.lang.String down(){
    if (searchp <= 0) searchp = HISTORYSIZE;
    return history[--searchp];
  }
	
  java.lang.String up(){
    if (searchp >= HISTORYSIZE - 1) searchp = -1;
    return history[++searchp];
  }	
}

文字列配列 history をサイクリックに使用しています。

次に関数 tak を予め、組込み S 式として、 Lisp の起動時に組み込みます。

public Lisp(){
  lisp = new MiniLisp();
    try {
      java.lang.String str = "(defun tak (x y z) (if (>= y x) ... (snip)
      lisp.eval.eval(lisp.read.readFromString(str));
    } catch (Exception e) {
      e.printStackTrace();
    }
    lispLet = this;
}

ここでは MIDlet クラスの Lisp コンストラクタに入れました。 MiniLisp の中に入れるのが正しい位置ですが、MiniLisp を汚したくないという意味で ここに入れました。

これで、今回の Lisp 処理系の拡張は終わりです。次はこれを PHS に持っていって動作させてみましょう。

このページトップへ

2日目のMIDP Lisp の PHS での実行

1 日目のときと同様に jar ファイルと jad ファイルを PHS に持っていき、インストールします。

画面はエミュレータと同じではなく、各ボタンが横一列の大きさになります。

width を調べると 238 になります。WX310SA の画面幅は 240 です。見掛けは以下のような感じになります。

MIDP Lisp
タイトルイメージ
S 式入力
'(Welcome to MIDP Lisp!!)
評価
クリア
履歴↓
履歴↑
tak5

(WELCOME TO MIDP LISP !!)
PHS 画面 WX310SA

PHS での画面イメージ


以下に作成した Lisp 処理系を置きます。

MIDP Lisp 処理系 (MIDPLisp2.jad)

MIDP Lisp 処理系 (MIDPLisp2.jar)

MIDP Lisp 処理系 (一式: ソース、jar, jad ファイル、 Eclipse プロジェクトファイルなど)

このページトップへ

MIDP Lisp の WX310SA での簡単な使い方

最初の使い方を紹介します。

  1. ウェルカムメッセージの表示
    MIDP Lisp を起動すると、フォーカスはタイトルイメージに当たっています。ここで「MENU」キーを押すと、'(Welcome to MIDP Lisp !!) のメッセージが入力テキストフィールドに表示されます。出力フィールドには Let's Enjoy Lisping ... のメッセージが表示されます。
  2. 画面のクリア
    方向キーで下へ移動するとフォーカスが下へ移動します。テキストをクリアするためにクリアへ移動して、MENU キーを押しますと画面がクリアされます。
  3. S 式の入力と評価
    方向キーで入力テキストフィールドへ移動し、MENU キーを押すことで S 式が入力できます。そこで例えば、(+ 2 3) を入力して、決定ボタンを押し、Web キーを押すことで評価され、5 が出力フィールドに表示されます。または方向キーで評価ボタンへ移動し MENU キーを押します。
  4. tak5 の評価
    tak5 のボタンを押すと約 14 秒後に 結果が表示されます。

次に機能一覧を以下に示します。

  1. タイトルイメージ --- ウェルカムメッセージ表示
  2. 入力テキストフィールド --- 任意の S 式を入力します
  3. 評価ボタン --- 入力フィールドの S 式を評価して、値を出力フィールドに表示します
  4. クリアボタン --- 入力フィールドと出力フィールドをクリアします
  5. tak5 ボタン --- tak5 を実行します。約14秒後に結果が表示されます
  6. 評価キー --- 入力テキストフィールドに入力時に押すと、評価します
  7. 終了キー --- MIDP Lisp を終了させます
このページトップへ
Copyright (c) 2006 OKI Software Co., Ltd.