Perl6
コンパイラ
構文解析
Perl 6Day 24

rakudo-and-nqp-internals-courseに挑戦した話 その4

こんにちは、24日目の投稿になります。

Introduction

みなさんはrakudo-and-nqp-internals-courseを知っていますか?
rakudo-and-nqp-internalsはMoarVMの開発者であるJonathan Worthington氏の作ったnqpとrakudoの内部構造に関するチュートリアルです。

スライドだけじゃなくて、エクササイズ問題も用意されています。
http://edumentab.github.io/rakudo-and-nqp-internals-course/

というわけで、詳しい話はスライドを見てもらうとして、今回のアドベントカレンダーでは私がエクササイズ問題に挑戦していきたいと思います。

24日目はExercise 4に挑戦します。

4.1

  • PHPっぽいecho関数を実装してみましょうという問題です

ソースコード:

use NQPHLL; # (#1)

grammar NQP::Grammar is HLL::Grammar { # (#2)
    token TOP { <statementlist> }
    rule statementlist { [ <statement> \n+ ]+ }

    proto token statement {*}
    token statement:sym<echo> {
        <sym> <.ws> <?["]> <quote_EXPR: ':q'> ';'? # (#6)
    }
    token ws { <!ww> \h* || \h+ }
}

class NQP::Actions is HLL::Actions { # (#3)
    method TOP($/) {
         make QAST::Block.new( $<statementlist>.ast ); # QAST::Blockについてはスライドp133参照 
    }
    method statementlist($/) {
         my $stmts := QAST::Stmts.new( :node($/) ); # QAST::Stmtsについてはスライドp142参照
         for $<statement> {
             $stmts.push($_.ast)
         }
         make $stmts;
    }
    method statement:sym<echo>($/) { # (#6)
         make QAST::Op.new( # QAST::Opについてはスライドp136参照
              :op('say'),
              $<quote_EXPR>.ast
         );
    }
}

class NQP::Compiler is HLL::Compiler { # (#4)
}

my $nqpcomp := NQP::Compiler.new();
$nqpcomp.language('nqp');
$nqpcomp.parsegrammar(NQP::Grammar);
$nqpcomp.parseactions(NQP::Actions);

sub MAIN(*@ARGS) {
    $nqpcomp.command_line(@ARGS, :encoding('utf8')); # (#5)
}

  • NQPHLLをuseします (#1)
  • HLL::Grammar (#2), HLL::Actions (#3), HLL::Compiler (#4) を継承したクラスをそれぞれ作ります
  • Rubyishの例(スライドp84)を参考にしてMAINを記述します。この関数の中で.command_lineを呼び出します (#5)
  • echo関数を書きましょう。これもRubyishのputs(スライドp123)とほぼ一緒です。 (#6)

実行

中の挙動がわかるように--target=parseを指定して実行してみましょう:

$ nqp 4.1.nqp --target=parse
> echo "hello";
- statementlist: echo "hello";

  - statement: 1 matches
    - sym: echo
    - quote_EXPR: "hello"
      - quote_delimited: "hello"
        - stopper: "
        - starter: "
        - quote_atom: 1 matches
          - quote_atom:  isa NQPArray

今度はオプションを指定しないで実行してみましょう:

$ nqp 4.1.nqp
> echo "hello";
hello

できました。

4.2

  • 下記のような問題です
    • 4.1で書いてきたコードについて、phpのechoは自動的に改行を入れたりしないので、sayじゃなくてprintで書くようにしましょう
    • またecho("with parentheses");のように()つきでも出力できるようにしましょう

ソースコード:

use NQPHLL;

grammar NQP::Grammar is HLL::Grammar {
    token TOP { <statementlist> }
    rule statementlist { [ <statement> \n+ ]+ }

    proto token statement {*}
    token statement:sym<echo> {
        <sym> <.ws> [
        | <?["]> <quote_EXPR: ':q', ':b'> # (#1)
        | '(' <?["]> <quote_EXPR: ':q', ':b'> ')' # (#2)
        ] ';'?

    }
    token ws { <!ww> \h* || \h+ }
}

class NQP::Actions is HLL::Actions {
    method TOP($/) {
         make QAST::Block.new( $<statementlist>.ast ); # QAST::Blockについてはスライドp133参照
    }
    method statementlist($/) {
         my $stmts := QAST::Stmts.new( :node($/) ); # QAST::Stmtsについてはスライドp142参照
         for $<statement> {
             $stmts.push($_.ast)
         }
         make $stmts;
    }
    method statement:sym<echo>($/) {
         make QAST::Op.new( # QAST::Opについてはスライドp136参照
              :op('print'),
              $<quote_EXPR>.ast
         );
    }
}

class NQP::Compiler is HLL::Compiler {
}

my $nqpcomp := NQP::Compiler.new();
$nqpcomp.language('nqp');
$nqpcomp.parsegrammar(NQP::Grammar);
$nqpcomp.parseactions(NQP::Actions);

sub MAIN(*@ARGS) {
    $nqpcomp.command_line(@ARGS, :encoding('utf8'));
}

  • バックスラッシュのあるシーケンスをパースできるようにするために:bをquote_EXPRの引数に追加で指定します (#1)
  • echo("hello");のように()で囲んでも出力ができるようにシーケンスを追加します (#2)

実行

$ nqp 4.2.nqp
> echo ("hello\nI'm larray");
hello
I'm larray> 

よさそうです。

以上、24日目の投稿でした。

ライセンス

rakudo-and-nqp-internals-course is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.