インタプリタでも作ってみようかと (1)

作業環境が整ったので、もう少しコード書いてみたりしないとなぁと思ったので練習がてら、インタプリタでも作ってみようかなとちまちまと書いています。
気づいたら2時間くらいずっとPCに向かっていたりで、なんだか睡眠時間を盗まれたような気分になったりもしてますが(^_^;)

なんとなくで題材はECMA Scriptのインタプリタにでもしようかと。
仕様が公開されているし、それなりに複雑だしで丁度よさそうなので。

数年前に仕事で作った事もあるんだけど、もうすっかり忘れてる(^_^;)
これなら少しはリハビリになるかな...

パーサ周りは面倒なのでbison/flexを使う事に。
bison/flexのC++実装は(使ったことが無いので)不安の方が多いので、その辺りのインタフェースはCとの互換性を保ちつつ、ある程度メインのコード部分はC++にしようかなと。
どうせ練習だし、詳細な仕様は作りながら考えればいいや、という適当具合w

bison/flexの細かい使い方の詳細などは、チュートリアル&サンプルコードも豊富な以下のサイトが非常に参考になります。
書籍化もされてるので、手元に本を置きたい方はそちらもおすすめかも。

プログラミング言語を作る
「なぜプログラミング言語なんか作るんですか?」と聞かれたら、その答はそこにyaccがあるからだ。 で充分だろうと私は思います(yaccが何かは後述します)。

現状の進捗度合いとしては、こんな感じ。

  1. 何も考えずに言語仕様からBNFを引っ張ってくる
  2. yacc(bison)形式に整形
  3. yaccだけでは難しいところはflex(lex)におまかせる
  4. とりあえず void * でunionのテンプレート作る
  5. 所々ダミーなどを挟みつつ、一応コンパイル出来るところまで

言語仕様のダウンロード〜BNF抽出

ECMAのサイトから、ECMA-262(ECMA Script)の仕様書(PDF)を引っ張ってきます。
中身は余り確認してないのですが、とりあえずこのファイルのA.3〜A.5あたりからBNFの定義をコピペしてソースファイルを作成します。

BNFからyaccソース生成

中身は後回しとして、まずは文法規則の整合性だけ整える様に気をつけつつ...

[ecmayacc.y:74-82]

Identifier
    : IdentifierName
    {
      /* [but not ReservedWord] */
      if (LGK_isReserved (TODO, $1))
        YYABORT;
      $$ = $1;
    }
    ;

こんな感じに文法規則だけでフォローしきれない部分は適当にやっつけ。
細かい解析木の構築周りとかは詳細考えていないのでとりあえず中身ありません(^_^;)

[ecmayacc.y:262-275]

RelationalExpression
    : ShiftExpression
    | RelationalExpression '<' ShiftExpression
    {}
    | RelationalExpression '>' ShiftExpression
    {}
    | RelationalExpression EP_GTEQ ShiftExpression
    {}
    | RelationalExpression EP_LTEQ ShiftExpression
    {}
    | RelationalExpression EK_INSTANCEOF ShiftExpression
    {}
    | RelationalExpression EK_IN ShiftExpression
    ;

yaccの文法規則で表現できない ">=" などの演算子は適時 EP_xxx に置き換え、予約語関連は EK_xxx に置き換えて仮置きします。
この辺りは後でlexの方でやっつけることになります。
ひとまずyaccソースを完成語 grep -o 'EP_[A-Z]+' などで単語単位で切り出して%tokenを作成します。

token関連を頼りにlexソース作成

そしてBNFも確認しつつ、%token関連で引っ張り出した物は別途lex側で拾う様に。

[ecmalex.l:33]

<INITIAL>"instanceof" return EK_INSTANCEOF;

[ecmalex.l:46]

<INITIAL>">="      return EP_GTEQ;

解析用のunion生成

解析木の設計を先にしておけばこの辺りは悩まないのだけど、現状何も考えていないので、適当に必要になりそうなタイプの名前だけ決めて、型は void* の状態で unionだけ確保しておきます。

[ecmayacc.y:13-27]

%union {
  void *ident;
  void *literal;
  void *expr;
  void *array;
    ...
  void *cases;
}

これに合わせて %type などの設定もしておけば、warning(useless rule系)は多発するけどひとまずコンパイルまで完了するくらいはいけるはず!
適当にmake掛けつつ、typoなどの修正したところで本日の作業完了。

進捗度合い: 5%くらい?

作成途中のソース類はとりあえずGitHubに上げています。

また、手元の環境はMac OS-X 10.6.7/MacPorts 1.9.2: bison 2.3/flex 2.5.35/GNU Make 2.5.35/gcc 4.2.1という感じでとりあえずコンパイルできたら良しとしています( ´ ▽ ` )ノ
ちまちまと更新しつつ連載できたらなぁ(^_^;)