オントロジー工学による機能モデリングツール「OntoGear」

MetaMoji、オントロジー工学による機能モデリングツール「OntoGear」を提供へ – Enterprise Watch

私が今かかわっているTECSは、ソフトウェア開発における設計の観点からみると、抽象度のレベルは、ソースコードに落とし込む直前ぐらいに当たります。

つまり、TECSで表現されるものは、かなり(ソースコードによる)実装に近いものになるということです。
TECSのレベルでの設計の良し悪しは、さらにその上流に当たる部分の良し悪しや、上流での意図や決定をどれだけ反映しているかが大きく影響してきます。

TECSによる開発も、TECSとして提供される技術だけで完結するものではなく、より良い結果を得るためにはやはり上流でのより良い設計が必要です。

その意味で、今回のモデリングツールといのは興味深いです。
無償提供ということですので、早速入手して試してみようと思います。

追記:提供は5月10日からの予定でした。それまで待つことにします。

[TECS]コンポーネントWGのミーティングに参加

昨日(2010/02/23)はTOPPERSプロジェクト コンポーネント仕様WGのミーティングに参加しました。
今回のミーティングは3月11、12日に開催されるNEP(エヌイーピー。名古屋大学組込みシステム人材育成プログラム)の公開講座「TECSを用いたコンポーネントベース開発入門」の準備です。
コンポーネント化した制御プログラムで動くPUPPY2の動作を確認したり、講座のテキストの見直し、修正を行いました。
私はもっぱらテキストのレビュワーをつとめました。

PUPPYは一軸駆動、PUPPY2は2軸独立駆動であり、共通部分と差分という視点での設計を通して、コンポーネント化の利点を実感してもらいたいと思っています。
またTOPPERS/ASPのサービスコールを、直接C言語の関数として呼び出すのではなく、コンポーネントの提供する機能として利用することで、こまごまとしたことを気にせず(そのあたりはコンポーネント内部で面倒を見てくれます)便利に使えることも気が付いてほしいです。

今回は近来になく時間を費やし、いつもなら飲み会が終わって解散するぐらいの時間にやっと退出することになりました。
私も駅の改札の前で、次に来るのが終電だと気がつき、あわててとび乗って安堵することが出来ました。

TECS講座が3月11、12日に名古屋大学で開催されます

名古屋大学の組込みシステム研究センター(NCES)の公開講座として、

 「TECSを用いたコンポーネントベース開発入門 」

が3月11、12日に開催されます。

ちなみにNEP(エヌイーピー)とは名古屋大学組込みシステム人材育成プログラムのことであり、NCESが提供する組込みシステム技術者を育成する教育プログラム群の総称だそうです。

今回は、NEP(エヌイーピー)の3種類の教育プログラム(学生向けの「OJL」,社会人向けの「公開講座」、「共同研究」)のうちの公開講座になります。
また今回の公開講座は受講料金が無料です。

当初予定の定員にすぐに達したため、募集が締め切られていましたが、定員を10名追加し、募集が再開されました。

講義を聞くだけでなく、マイコンボードを用いたプログラミング演習もあります。
これは昨年のESECやET2008のTOPPERSプロジェクトのブースで展示された、二軸二輪独立駆動型倒立制御ユニット(http://www.hokutodenshi.co.jp/7/PUPPY.htm#puppy2)(北斗電子製)とのことです。

リモコンではなく、前後左右に自らが動く(その制御の部分がコンポーネントとして作られている)ものです。
目の前で動かせるのは楽しそうですね。

受講の申し込み受付先 http://www.nces.is.nagoya-u.ac.jp/NEP/appli_4.htm
NEP(ネヌイーピー)のサイト http://www.nces.is.nagoya-u.ac.jp/NEP/
NCESのサイト http://www.nces.is.nagoya-u.ac.jp/
TOPPERSプロジェクトのサイト http://www.toppers.jp/

JRubyでもtecsgenのテストケースが通るのを確認しました

raccのparser.rbへのとりあえずのパッチ作成に載せた試作パッチを、Linux(Fedora 7)とcgywin(1.5.25)上でJRubyで実行し、tecsgenの配布物に含まれるテストケースが通ることを確認しました。

Javaは、JDK 1.6 update14、JRubyは1.3.1を使用しました。

ただし、それぞれの箇所でいささか工夫が必要でした。

*Linux
tecsgenがCのヘッダファイルを読み込む(typedefで定義された型名や構造体タグ名を認識するため)時に、C処理系の独自の予約語が存在すると、tecsgenはそれを認識で来ません。
これによるパース失敗を避けるため、独自の予約語を読み込み前に削除しています。
具体的にはtecsgenの内部でCプリプロセッサ(あるいはC処理系のCプリプロセス処理機能)を呼び出し、空文字列に置換してから、ヘッダファイルを読み込みます。
具体的にはtecsgenのコマンドラインオプション-Dで空文字列に置き換える文字を指定します。
ただし、この指定が”-D__extension__”とされていたため、Linuxのgcc(4.1.3)では”1″に置き換えられて、コンパイルエラーになりました。
#cygwinではこの指定でも問題が起きませんでした。
#現在公開されているtecsgenは、主にcygwinで動作確認されています。

Linuxでは”-D__extension__=”とする必要がありました。

*cygwin
cygwinでJRubyを実行する場合、環境変数RUBYLIBへのパス名の設定方法に工夫が必要でした。
この工夫は、Rubyの場合は必要ありませんでした。

cygwinでフルパスで指定する場合、/cygdrive/c/のように
ドライブ名を/cygdrive/ドライブ名という指定をすることが
可能です。
しかし、この形式で指定した場合、rubyのスクリプトで
クラスFileのexpand_pathメソッドでフルパスに変換した
場合、
C:/cygdrive/c
と、先頭にドライブ名をつけたにも関わらず、/cygdrive/c
もつけたままであるため、存在しないパスになってしまいました。
これは、cygwinの標準のrubyとか、cygwin中から呼び出した
mswin32版rubyでは、経験しなかったことです。
どこが原因かまでは調べていませんが、cygwin上で、JRubyを
使ってtecsgen.rbを実行して、”ファイルが見つからない”
というようなエラーが発生した場合は、環境変数RUBYLIBの値を
確認してください。
もし、”/cygdrive/c”のような形式であれば、この部分をとり
さったパス名を指定してください。
以下をコマンドラインで実行してもよいと思います。

export RUBYLIB=`echo $PWD | sed ‘s/^\/cygdrive\/.//’`

raccのparser.rbへのとりあえずのパッチ作成

racc 1.2.6のcparseで無限ループ発生せずで、parser.rbとcparseの参照するテーブルの内容が異なるのではないかという予想を立てていましたが、またしてもこの予想は外れていました.
cparseはC言語によるrubyの拡張ライブラリとして実装されてており、参照するテーブルは同一でした.
拡張ライブラリ側で参照する際に、参照する位置がずれたりしないかとも考えましたが、拡張ライブラリのデバッグ用出力を有効にして、parse.rbとcparser.cの両者の出力内容を比較した結果、文法エラーになるトークンを読み込んで、エラーと判定し、エラー回復モードに入るまではまったく同じでした。
違いは、cparse.cがエラー処理として、(仮想的な)エラートークンをシフトするのに対し、parser.rbは文法エラーになるトークンをそのままシフトすることでした。

振り出しに戻ってしまったのですが、よくよくcparse.cを調べてみると、エラー回復モード中に、エラートークンを読み込む処理をする処理がか書かれていました。
それに対して、parser.rbには、該当する処理が存在しませんでした.

そこで、parser.rbに必要な処理を追加すのが、以下に示すパッチです.

ただし、cparse.cでは、(エラー回復モード中のエラー処理には)goto文で飛んできて、(エラー処理が終われば)goto文で(飛んできた所の次の行に)戻っているため、parser.rbにどう書くのが適切かは自信がないです。
また、tecsgenに同梱されているテストケースを実行すると、@racc_state(パーサ内部の状態遷移を管理する配列)がnilになる場合があり、とりあえず動作させるために、@racc_stateがnliの場合には、処理を打ちきるという修正を加えました.

上記の事情を踏まえた上での、ruby 1.8.7に同梱されたraccのparser.rbに対するパッチを以下に示します.

これにより、cparseでなくparser.rbで実行した時でも、テストケースを全て実行できました。
すくなくとも無限ループにはなりませんでした。
#Linuxのruby1.8.7での実行結果です.
#もともとの発端になったJRubyではまだ試していません.

Index: parser.rb
===================================================================
--- parser.rb   (リビジョン 23907)
+++ parser.rb   (作業コピー)
@@ -115,6 +115,10 @@
 
       catch(:racc_end_parse) {
         while true
+#addeb by komianmi
+          break unless @racc_state[-1]
+          break if @racc_state.length < 1
+#end
           if i = action_pointer[@racc_state[-1]]
             if @racc_read_next
               if @racc_t != 0   # not EOF
@@ -294,6 +298,47 @@
             racc_e_pop @racc_state, @racc_tstack, @racc_vstack
           end
         end
+#added by kominami
+        # shiftreduce error token 
+        if act > 0 and act < shift_n
+          #
+          # shift
+          #
+          @racc_vstack.push @racc_val
+          @racc_state.push act
+          @racc_read_next = true
+          if @yydebug
+            @racc_tstack.push @racc_t
+            racc_shift 1, @racc_tstack, @racc_vstack
+          end
+        elsif (act < 0 && act > -(reduce_n)) 
+          #
+          # reduce
+          #
+          code = catch(:racc_jump) {
+            @racc_state.push _racc_do_reduce(arg, act)
+            false
+          }
+          if code
+            case code
+            when 1 # yyerror
+              @racc_user_yyerror = true   # user_yyerror
+              return -reduce_n
+            when 2 # yyaccept
+              return shift_n
+            else
+            raise '[Racc Bug] unknown jump code'
+            end
+          end
+
+        elsif (act == shift_n)
+          racc_accept if yydebug
+          return vstack[0]
+
+        else 
+            rb_raise(RaccBug, "[Racc Bug] unknown act value %ld", act);
+        end
+#end
         return act
 
       else

racc 1.2.6のcparseで無限ループ発生せず

<a href=”http://www.northern-cross.info/wordpress/index.php/archives/26″>racc 1.3のcparseで無限ループ発生</a>で、cparseでもエラー回復モードからエラー回復できず、無限ループに陥ったことを報告しました。

1.3.0以外の(別のことが原因で実行できなかった1.3.1, 1.3.を除いて)1.3.*では無限ループになりませんでした.

また、1.3.0の一つ前のバージョンの、1.2.6では無限ループが発生しませんでした.

1.3.0と1.2.6は構造体のメンバに直接アクセスするか、構造体へのポインタを介してアクセスすか程度の違いしかなく、無限ループになるかならないかは、参照するテーブルの違いの可能性が大きいです。

より具体的に言えば、テーブルを作成するメソッドの違いではないかということです。

複数のテーブル間の参照がからんでくるので、テーブルの内容の一がずれていたり指している先がどこか一ヶ所でもずれてたら、その後の参照内容が間違ってしまいます。

実際、1.3.0では、文法エラーとなるトークンを読み込んだとき、文法エラーであるとは判定しています。

しかし、それに対するアクションが定義済みと判断し、エラーとなるトークンをそのままシフト、還元して、最終的に無限ループになってしまいます。

もしアクションを示すテーブルの一つ前の要素をさしていたら、アクションは未定義と判定され、状態を一つ前に戻し、$endを読みこむことになり、エラー回復につながるのですが。

次は、テーブル作成の部分を調べてみます。

racc 1.3のcparseで無限ループ発生

raccのparser.rbとcparseの実装上の違いで、「振る舞いが異なる原因となるところを見つけました」と書きました.
その後、この予想を確かめるべくraccのリポジトリから、どの時点でcparseとparser.rbの処理が異なるようになったか、を

調べたのはhttp://i.loveruby.net/svn/public/racc/trunkです。
以前はcvsのリポジトリを使っていたようでしたが、現在はCVSのリポジトリにはアクセスで来ませんでした。
SVNのリポジトリでは、以下が最も古いログです。
r1611 | 1999-06-30 11:52:10 +0900 (水, 30 6月 1999) | 1 line

ただし、ディレクトリ構成も過去からずいぶん変更されているようで、あるリビジョン以前では、目的とするcparse.cの過去のリビジョンとのdiffをとるのが面倒になりました。

結局、raccの過去のアーカイブから調べたいバージョンを取ってきて、ローカルで展開して比較することにしました。
ログを元に推測が出来る分、当てずっぽうよりはずいぶんマシです.

さて私の予想で、今回も外れた部分があります。
私が注目していたのは、エラー回復モードに入ってから、エラーとなった場合のトークンに、対応するアクションが存在するかしないかを、いろいろ調べて、なければラベルerror_popに飛んでいたところでした。

しかし、racc 1.4まで遡っても、cparse.cでは無限ループになりませんでした.

racc1.4は、エラー判定はするものの、それ以降のバージョンのコードと比較して、判定方法はとてもあっさりしていました。

私としては、あんなにしつこくチェックしているから、エラーの判定漏れがないのかなと思っていましたが、文法エラーの時に、(エラー時のアクションが定義されていなければ)ひとつ前の状態にもどるという方針が(今回のようなエラーに対しては)有効のようです。

バージョンを遡って試してみたところ、racc1.3になってやっとcparseでもエラーになることを発見しました.

ただし、1.3といっても1.3.*すべてを試したわけではないので、もっと絞り込む必要があります.

raccのparser.rbとcparseの実装上の違い

raccのparser.rbとcparseの違いでは、入力に対する振る舞いの違いについてのべました。
その後、両者を比較して、振る舞いが異なる原因となるところを見つけました.

ソースは、ruby 1.8.7のsvnのtrunkの最新版です。

パス: .
URL: http://svn.ruby-lang.org/repos/ruby/branches/ruby_1_8
リポジトリのルート: http://svn.ruby-lang.org/repos/ruby
リポジトリ UUID: b2dd03c8-39d4-4d8f-98ff-823fe69b080e
リビジョン: 23875
ノード種別: ディレクトリ
準備中の処理: 特になし
最終変更者: svn
最終変更リビジョン: 23875
最終変更日時: 2009-06-28 05:11:49 +0900 (日, 28 6月 2009)

なお以下から入手したracc 1.4.6でもparser.rbの該当個所は変更されていませんでした.
http://rubyforge.org/projects/racc/

違いはcparse.cの452行目から始まる以下の関数内での処理にあります。

   452	static void
   453	parse_main(struct cparse_params *v, VALUE tok, VALUE val, int resume)

  (略)

(ここに来るまでに、エラー回復モードになっています)

   591	    /* check if we can shift/reduce error token */
   592	    D_printf("(err) k1=%ld\n", v->curstate);
   593	    D_printf("(err) k2=%d (error)\n", ERROR_TOKEN);
   594	    while (1) {
   595	        tmp = AREF(v->action_pointer, v->curstate);
   596	        if (NIL_P(tmp)) goto error_pop;
   597	        D_puts("(err) pointer[k1] ok");
   598	
   599	        i = NUM2LONG(tmp) + ERROR_TOKEN;
   600	        D_printf("(err) i=%ld\n", i);
   601	        if (i < 0) goto error_pop;
   602	
   603	        act_value = AREF(v->action_table, i);
   604	        if (NIL_P(act_value)) {
   605	            D_puts("(err) table[i] == nil");
   606	            goto error_pop;
   607	        }
   608	        act = NUM2LONG(act_value);
   609	        D_printf("(err) table[i]=%ld\n", act);
   610	
   611	        tmp = AREF(v->action_check, i);
   612	        if (NIL_P(tmp)) {
   613	            D_puts("(err) check[i] == nil");
   614	            goto error_pop;
   615	        }
   616	        if (NUM2LONG(tmp) != v->curstate) {
   617	            D_puts("(err) check[i] != k1");
   618	            goto error_pop;
   619	        }
   620	
   621	        D_puts("(err) found: can handle error token");
   622	        break;
   623	          
   624	      error_pop:
   625	        D_puts("(err) act not found: can't handle error token; pop");
   626	
   627	        if (RARRAY(v->state)->len < = 1) {
   628	            v->retval = Qnil;
   629	            v->fin = CP_FIN_CANTPOP;
   630	            return;
   631	        }
   632	        POP(v->state);
   633	        POP(v->vstack);
   634	        v->curstate = num_to_long(LAST_I(v->state));
   635	        if (v->debug) {
   636	            POP(v->tstack);
   637	            rb_funcall(v->parser, id_d_e_pop,
   638	                       3, v->state, v->tstack, v->vstack);
   639	        }
   640	    }

この中で、ラベルerror_popを指定したgoto文がありますが、これに該当する処理がparser.rbには存在しません。
ラベルerror_popでは、エラーとなったトークンにに対するアクションが見つからない場合、そのエラートークンを読み込まなかったことにして(スタックからpopして)います。

parser.rbではこの処理に飛ぶかどうかの判定をするところがありません。
また、判定できるように参照するテーブルなどがつくられているかどうかも、不明です。
#まだそこまで調べれていません。
#今回の調査のきっかけとなったケースでは、はエラートークンに対するアクションが得られるのですが、そのアクションをとると、最終的に無限ループにおちいってしまいました。

raccのparser.rbとcparseの違い

さらに引き続き(raccのエラー回復モードは頑張りすぎ?tecsgen の Exerb版, Jruby 版の障害について)、raccのparser.rbとcparseの動作の違いを調べています.

昨日は、parser.rbがエラー回復モードで、定義された文法を満たすトークンの並びを得ようとして、(入力が終わっているのに)延々と次のトークンを得ようとして、無限ループに陥っているようだと書きました。
このことを指して「頑張りすぎ」と評していました。

そして、同じ不完全な内容の入力(不完全故に文法エラーになる)に対して、正常終了して文法エラーを報告するcparseは「頑張らない」ようにしているのではないかと予想していました.

今回、cparseを用いて、同じくtecsgen -yを指定してデバッグ表示させて見たところ、以外な結果になりました。

celltype tTaskMain {};

に対して、’}’を読み込むと文法エラーになります。
そしてエラー回復モードに入ります。

ここでcparseの場合、いったん読み込んだトークンを
捨てて、$endというトークンを読み込みます。
その次のトークンとして「}」を読み込みます。
後は、入力の通りにトークンを読み込みます。

エラー回復モードに入ったときに(入力には存在しない)仮想的なトークンを読み込んでいます。

この影響で、必要なトークンが不足しているために発生した文法エラーから回復することができていました。

先日以下の入力なら、parser.rbでも無限ループにはいらないと書きました.

celltype tTaskMain {}};

cparseの場合、エラー回復モード時に、あたかも上記のような入力がされていたかのように振る舞います。

$endが追加されるのは、他の文法エラーになる入力でも確かめてみました。

最初の私の予想(入力の終わりを特別扱いして、頑張らない)は外れていました。
cparseが採用しているエラー回復処理の方針は、どこかで聞いた覚えがあるのですが、思い出せません。またちょっとググってみても分かりませんでした。

(追記)
上の書き方ですと、parser.rbとcparse.soがまるっきりエラー回復モードのやりかたが違っているように受け取られるかも知れません。
しかし、rubyとC言語の違いはありますが、アルゴリズムとしては、(私が今まで調べて、理解した範囲では)どちらも同じに見えます。
動作の違いは、微妙な判定条件の違いとか、参照するテール(テーブルはそれぞれ用に作成されます)の内容の違いによるのではないかと感じています。

Reblog this post [with Zemanta]

raccのエラー回復モードは頑張りすぎ?

昨日の続きで、TECSジェネレータでの文法定義と、CDLファイルでのエラーになる書き方を調べています。

今回も途中報告です。

前回に書いたerror-7.cdlは、以下の文法の記述に誤りがある場合のテスト-ケースでした.

文法定義

celltype
: CELLTYPE celltype_name ‘{‘ celltype_statement_list ‘}’ ‘;’

error-7.cdlの内容

celltype TaskMain {
/* attributeの定義が空でならsyntax error */
/*    attr {

x = 0;

};
*/
};

これはコメントを無視すると、以下と同等です。

celltyope TaskMain {};

文法定義と比較すると、celltype_statement_listという要素が不足しているため、文法エラーとなります。

実際、raccの生成したパーザはこの文法エラーを認識して、on_error()を呼び出しています.

ここで、on_error()とは、raccを用いてパーザを作成する人が、エラー処理を行うために自由に定義できるメソッドです。

on_error()でパース自体を中止することもできます。

また中止せずに、raccが生成したパーザにエラー回復モードで処理を続行させることができます。

エラー回復モードは、文法エラーが見つかった箇所を、エラーだとは認識しつつも、そこに正しい記述があったものとみなして、パース処理をつづけることです。

今回の文法定義の場合を例にとると、celltype_statement_listが存在するべきところに、「}」があったため、エラーとなります.

この後エラー回復モードで処理を続けようとして、「}」をcelltype_statement_listだとみなします。

文法では、この後に「}」と「;」がこの順番に記述されていなければなりません。

しかし、入力としては「;」しか存在しません。

raccは入力の終わりを$endというトークンとして扱います。

しかし、これが来ても、文法エラーが解消されません.パーザは次の入力を求めますが、実際の入力は終わっているので、$endしか渡してもらえません.

結局ここで無限ループが発生してしまいます。

実は、同じエラー内容でも以下の場合は、パーザは文法エラーを報告し、正常に終了します.

(文法としてはエラーになるが、正常終了する場合の記述)

celltyope TaskMain {}};

最初の「}」が来た時点でエラーと判断されますが、エラー回復モードになっても「}」、「;」と続くため、celltypeの文法定義に合致し、エラー回復モードは終了します。その後入力の終わり$endが来て、パーザは正常終了します。

エラー回復モードが(期待するより)頑張りすぎているのかもしれません.

ただし、入力の終わりがきたら必ず打ち切るようにするには、入力の終わりを特別扱いし、どんな状態であっても終了するルーチンをつくるか、どの状態からでも入力の終わりが来たら終了する状態遷移テーブルを作成する必要があると思います。

後、raccが手本にしたyaccの仕様がどうだったのか、確認したほうがいいかな。

raccの動作が、yaccの仕様の通りであれば、on_error()で対処するのがbetterだと思います.