KMyaccでjavascriptで動く独自文法パーサを書いた

独自タグを使ったリアルタイムHTMLエディタのデモ
http://d.hatena.ne.jp/ruby-U/20080908/1220865377
で使ってるパーサはパーサのパの字も知らないときに勢いで書いてみたもの
半月もかかった挙げ句にごらんの通りの出来・・・

switch(post){
	case "、":
		postfix = "normal"; break;
	case "。":tag.");					
		var t = alex.tags.get(alesTagName, "noValue", prefix);
		t.setStartLine(this.nowLine);
		t.setEndLine(this.nowLine);
		//プロパティを次の行から調べさせるらしい
		//グローバルな現在行情報を書き換えるらしいぜ!
		var pp = new PropertyParser(this.nowLine+1);
		var p = pp.parse();
		var keys = "";
		for(keys in p){
			t.addProp(keys, p[keys]);
		}
		//なんだこの処理は?
		(this.nowLine > alex.line) ? alex.line = this.nowLine : this.nowLine = alex.line -1;
		this.tag.addTag(t);
		return true;
		break;
	case "":
		postfix = "oneLine"; 
//以降続く

非常に読みにくく理解しにくい メンテナンス性は最悪
何か修正すると何かバグが発生するという恐ろしい泥沼具合


うへーと思って放っておいたのだが、つい最近javascript対応のパーサジェネレータなるものを発見したのでまるっと全部新しく書き直してみることに


kmyacc - 多言語対応LALRパーサー生成系
http://www005.upp.so-net.ne.jp/kmori/kmyacc/


KMyaccユーザーズガイド
http://www005.upp.so-net.ne.jp/kmori/kmyacc/kmyacc.html
をひとしきりながめたが、使い方がさっぱりわからない
具体的な例をググってみる


一番わかりやすかったのがこのサイト
Flash/Flex で作る俺様言語(kmyacc編)(2) - kmyacc で BASICっぽい言語を作ってみよう
http://www.hakkaku.net/articles/20080307-158


最初、どのサイトを見ても例外なく電卓作成で、

exp:	NUMBER
	| exp '+' exp	{ $$ = $1 + $3; }
	| exp '-' exp	{ $$ = $1 - $3; }
	| exp '*' exp	{ $$ = $1 * $3; }
	| exp '/' exp	{ $$ = $1 / $3; }
	;

とか呪文のように書いてあってさっぱりわからなかったが、そのサイトには

expr : expr '+' expr
     {
         var n:YYNode = new YYNode();
         n.node_type = CALC_PLUS;
         n.children = [$1, $2];
         $$ = n;
     }

などの例があった
これを見ればもうわかる
{ 〜 }のあいだに好きな処理を書き込んでいけばOKということらしい


今回は以下のように実装してみた


行を読んでトークンに分解するscannerは

if(isLine){
	return [
		LINE,     //token情報
		str,      //テキスト
		this.line //第何行か
		];
}

のようにget()されるたびに一つづつトークン+情報を配列で返して


yylexで

function yylex(){
	yylval = scanner.get();
	logger.info("yylex() -> " + yylval);
	return yylval[0];
}

配列はそのままyylvalに入れて、トークンを返す


パーサは定義を元に、帰ってきたトークンから文法を判断する
例えば、

values  //テキストの配列
	: value { $$ = [$1]; } 
	| values value {
		 	if( $1 instanceof Array ){
		 		$1.push($2);
		 		$$ = $1;
		 	}else{
		 		$$ = [$1, $2];
		 	}
		 }
	;
value //テキスト
	:LINE { $$ = $1[1]; } 
	;
line
	:LINE 
		{
			var t = new Node($1[0], EB_NONE);
			t.startline = $1[2];
			t.endline = $1[2];
			t.child = [$1[1]];
			$$ = t;
		}
	;

のように定義すると、LINEというトークンはline(Node)になることもあるし、value(string)になってvalues(stringの配列)になることもある


HTMLタグのツリーを生成するためのパーサなのでタグは入れ子になる
タグがそれぞれ

push: function(t){
	return [this, t];
},

のpushメソッドを実装していると

tags
	:tag { $$ = [$1]; }
	|tags tag 
		{
			$1.push($2);
			$$ = $1;
		}
	;
tag
	:normal { $$ = $1; }
	|oneline { $$ = $1; }
	|novalue { $$ = $1; }
	|line { $$ = $1; }
	;

とすることで、tagsを指定した場合は必ず配列が、tagを指定した場合にはそのものが来ることが保証できる


つまり

normal
	:prefix IDENTIFIER LEFT_QUOTE property_expression tags RIGHT_QUOTE 
		{
			var t = new Node($2[1], $1);
			t.startline = $2[2];
			//子要素を(そのまま)セットする
			t.setChild($5);
			t.endline = $6[2];
			if($4){
				t.addProperty($4);
			}
			$$ = t;
		}
	;
oneline
	:prefix IDENTIFIER property_expression tag 
		{
			var t = new Node($2[1], $1);
			t.startline = $2[2];
			t.endline = $4[2];
			if($3){
				t.addProperty($3);
			}
			//子要素を配列に入れてセットする
			t.setChild([$4]);
	 		$$ = t;
	 	}
	;

のようにできる


こんなかんじで3日ぐらいで以前書いたパーサのところまでは実装できてしまった・・・
何かを作るときはもうちょっと方法論を調べてから、というのが今回の教訓