yaccによるWebスクリプト言語
※「catch.jp-wiki」から移動したページです。
Part1.でJavascript電卓ができたので、次は簡単なスクリプト言語を作ってみます。スクリプト言語の開発に必要な基本的な技術を確認するために、ものすごーく簡単なスクリプト言語を作ります。
このページでは、kmyaccをツールとして、Javascriptで動くものを作ります。構文木などは作らないで、別のプログラミング言語へ変換だけやります。
とりあえず、SaruMane Script(猿真似スクリプト)という名前を付けることにしました。
スキャナーを作る
スクリプト言語では、電卓と違って、文字列やコメントなどを扱うので、まずスキャナー(字句解析)を作り直します。 構文解析に先立って、コードの文字の切り出しと分類を行うのが、このスキャナー。字句解析とかレキシカルアナライザー(Lexical analyzer)と呼ばれます。
電卓では、yaccのサンプル定義ファイルに、スキャナーのコードが埋め込まれていましたが、それを差し替えます。
とりあえず、ここでは、スキャナーが動作するサンプルページを用意しました。
文字の判別には、正規表現を使っています。
元の電卓用スキャナーとyaccの実装スタイルに合わせて、グローバル変数(!)を使っています。
スキャナーの動作
スキャナは、コードを次のように分類して構文解析に送ります。
- IDENT
- 役割:識別子(変数名や関数名)
- 規則:最初の文字が、英文字(a-zとA-Z)にアンダーバー(_)。その後ろに英数字
- NUMBER
- 役割:数値
- 規則:小数点も使える。
- STRING
- 役割:文字列
- 規則:ダブルクォーテーション(")かシングルクォーテーション(')で囲む
- COMMENT
- 役割:コード中のコメント(注釈)。
- 規則:「//」
- 記号
- 計算記号やカッコなど
次のように入力した場合
123abc"xyz"//comment
次のように表示されます。
-1 : 123abc"xyz"//comment,token : 123,yylex : 257
-1 : abc"xyz"//comment,token : abc,yylex : 258
-1 : "xyz"//comment,token : xyz,yylex : 259
-1 : //comment,token : //comment,yylex : COMMENT1
先頭は改行位置(改行記号がない場合は「-1」)。そのあと、現在解析している文字、切り出し結果、分類結果と続きます。
なお、コメント文を判別できるようにしています。通常、コメント文は無視して捨てるけれど、この簡易スクリプト言語は変換器なので残すようにしています。
スキャナーとパーサーを組み合わせる
動作確認のため、スキャナーで分類した結果を素通しするような構文解析器(parser:パーサー)を作ってみました。
program:/* empty */
| program stmt { setAnswer(outputs); }
;
stmt: NUMBER { outputs += "NUMBER: " + yylval + "<br> ";}
| IDENT { outputs += "IDENT: " + yylval + "<br> ";}
| STRING { outputs += "STRING: " + yylval + "<br> ";}
| COMMENT { outputs += "COMMENT: " + yylval + "<br> ";}
| '\n' { outputs += "(CR)<br> "; }
| error { $$ = ""; }
;
スキャナーが分類した結果は、構文解析側で判断して、分類とデータを表示します。
たとえば
123abc"xyz"//456
と入力すると、
NUMBER: 123
IDENT: abc
STRING: xyz
COMMENT: //456
と表示されます。
プログラムっぽいものを書けるようにする
次は、パーサーを拡張して、代入文と関数と引数を書けるようにします。 これで、プログラムっぽいものが、記述できるようになります。
たとえば、次のように入力すると
a=200
x()
y(100)
z(1,2,3,4)
a=1*(2+3)
b=x(100)
c=200 //comment
次のように、そのまま表示されます。
a=200
x()
y(100)
z(1,2,3,4)
a=1*(2+3)
b=x(100)
c=200
//comment
入力したプログラムを、いったんスキャナーでバラバラにして、それをパーサーで組み立てなおしているのです。
ここまでの段階で対応しているのは、次の構文です。
- 関数呼び出し funcall
- 関数名(引数) //引数あり
- 関数名() //引数なし
- 代入文 assign
- 変数名=数式
-
コメント文 「//」から行末まで
-
引数 args
- 数式1, 数式2・・・引数3
- 数式1
-
数式 expr
- 足し算, 引き算, 掛け算, 割り算, 余り
- カッコによる優先順位付け, 関数呼び出し
-
基本データ構造 primary
- 識別子(変数名, 関数名)
- 数値
- 文字列 「””」か「' '」で囲んだもの。エスケープ記号とか効かない
構文ルールに合致しない場合は、「syntax error」と表示されます。
たとえば、次のように、文字だけ書いても、関数呼び出しでも代入文でもコメント文でもないため、「syntax error」と表示されます。
abc
ここまでの、ソースファイルは次のようになっています。
Processing.jsっぽく書けるようにする
ここで作っている簡易スクリプト言語は、ターゲット言語をProcessing.jsにしています。
Processing.jsは、ビジュアルデザイン用プログラミング言語Processing(プロセッシング)のWeb版です(JavascriptベースのProcessing)。グラフィックアニメーションやユーザーによる操作が取り扱いやすくなっています。
Processing.jsによる、アニメーションサンプルはこんな感じです。
サンプル > mouse_move
簡易スクリプト言語の主な機能は、次のような感じにします。
- 命令や関数などはProcessing.jsとおんなじ。
- 末尾のセミコロン(;)は不要。
- If文とかFor文とかWhile文とかも、(とりあえず)なし。Processing.jsだと、なしでもそれらしいプログラムが作れるので。
- クラス定義はあるけれど、クラス生成は(今のところ)なし。
で、こんなふうに末尾のセミコロンなしのプログラムを変換すると
void setup() {
size(250, 250)
frameRate(20)
}
void draw() {
background(0)
ellipse(width/2, height/2, mouseX, mouseY)
}
こんなふうに、Processinng.jsに変換されます。
void setup() { size(250, 250);
frameRate(20);
}
void draw() { background(0);
ellipse(width/2, height/2, mouseX, mouseY);
}
サンプルページとソースファイルは次のようになっています。
これだけでは、機能的には特別うれしいところはありませんが、独自文法のスクリプト言語の作るもとになるはず。
Processing.jsに変換して実行する
ついに、できました!
変換したコードをProcessing.jsに渡して実行します。
とりあえず、サンプルページのRunボタンをクリック。それから、右側の黒色エリアにマウスポインタを合わせると、その位置に合わせて楕円が描かれます。そのすぐ下には、変換後のスクリプトが表示されます。
動作上の制約
非常に簡易的なスクリプト言語なので、次のような注意が必要です。
ブロックコメントに非対応
「/* */」を使った、複数行にまたがるコメントは、今のところ使えません。
デバッグツールによるデバッグ
スクリプトは、変換して直接実行しているので、デバッグツールを適用できません。
変換後のスクリプトを、Processing.jsに直接貼り付けるともう少しやりやすくなるかも。
グローバル変数の衝突回避
SaruMane Scriptは、いくつかグローバル変数を使っています(yyyBuffer、yyyToken、yylval)。特別な名前にしてあるので、衝突することは、ほとんどないと思いますが、念のため注意してください。
グローバル変数を使っていると、他のプログラムと衝突するので、Javascriptの場合は変換プログラムをクロージャーにするとか、対策しなくちゃならないなー。と思っていたけれど、調べてみたら、ちょっと手間がかかるので、後回しにしました。
ソース
参考資料
-
Rubyで作る奇妙なプログラミング言語~Esoteric Language~ Amazon
- あなたは「+-><.,[]」の8つの記号しかないプログラミング言語や、空白だけで構成されるプログラミング言語があるのをご存じだろうか。本書では、そんな奇妙な言語(Esoteric Language)を題材にプログラミング言語の作り方を解説します。
- Rubyを256倍使うための本_無道編 Amazon
- Ruby+Raccなら、プログラミング言語開発だって楽しくなる!パーサジェネレータという硬派なネタを256倍流に完全解説。コレさえあれば、キミも今日から言語開発者だ!
- まつもと直伝 プログラミングのオキテ 第6回 メタプログラミング
- http://itpro.nikkeibp.co.jp/article/COLUMN/20070604/273453/
- まとめてみると,Rubyは非常にDSL向きな言語だと分かります。
-
Racc で遊ぼう
- Raccで電卓をいろいろ作るチュートリアル
- http://www.mnet.ne.jp/~tnomura/racc.html
-
極めよRuby道:第5回 Rubyで書くフロントエンドとパーザ
- ParserGenerator - catch.jp
- JavaScript - 100行で書く俺様プログラミング言語コンパイラAltJS編 - Qiita