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

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

# Introduction

みなさんは[rakudo-and-nqp-internals-course](https://github.com/edumentab/rakudo-and-nqp-internals-course)を知っていますか?
rakudo-and-nqp-internalsはMoarVMの開発者である[Jonathan Worthington氏](http://www.jnthn.net)の作ったnqpとrakudoの内部構造に関するチュートリアルです。
スライドだけじゃなくて、エクササイズ問題も用意されています。
http://edumentab.github.io/rakudo-and-nqp-internals-course/
というわけで、詳しい話はスライドを見てもらうとして、今回のアドベントカレンダーでは私がエクササイズ問題に挑戦していきたいと思います。

ちなみに、3日目では[Exercise 1](https://qiita.com/titsuki/items/dac696b92da271e6c52d)に挑戦しました。
今日の10日目はExercise 2に挑戦します。

# Exercise 2

## 2.1 Grammar

+ [Perl 6のirclog](https://irclog.perlgeek.de/perl6/2017-12-10) の[ログデータ](https://irclog.perlgeek.de/perl6/2017-12-10/text)をGrammarでパースしてみようという問題です。
+ このログデータは時刻, ニックネーム, テキストの三要素で一つの行が構成され、行と行の間は改行区切りになっています。 

```
!# nqp                                                                                                                                                           
                                                                                                                                                                 
grammar Utterance::Grammar {                                                                                                                                     
  token TOP {                                                                                                                                                    
    <linelist>                                                                                                                                                   
  }                                                                                                                                                              
  token linelist {                                                                                                                                               
    [                                                                                                                                                            
    | <line> \n                                                                                                                                                  
    | \n                                                                                                                                                         
    ]+                                                                                                                                                           
  }                                                                                                                                                              
  token line { <time> <.ws> <nick> <.ws> <text> }                                                                                                                
  token time { \d+ ':' \d+ }                                                                                                                                     
  token nick { \w+ }                                                                                                                                             
  token text { <[\w\h]>+ }　# Note: Don't use \s                                                                                                                 
}                                                                                                                                                                
                                                                                                                                                                 
Utterance::Grammar.HOW.trace-on(Utterance::Grammar);  # (#1)                                                                                                           
my $m := Utterance::Grammar.parse(Q{                                                                                                                             
00:00  abc            abc de fa                                                                                                                                  
00:00  abc            abc de fb                                                                                                                                  
});  # (#2)                                                                                                                                                            
                                                                                                                                                                 
say($m<linelist>);                                                                                                                                               
```

+ 少なくともこの例では、ほとんど[Grammars](https://docs.perl6.org/language/grammars)と文法的な違いはありません。
+ `.How.trace-on`を利用するとパーサーの内部の動作を閲覧することができるようになります。 (#1)
+ `Q{ }`を利用することでirclogのフォーマットのテキストを簡単に表現することができます。(#2)

## 2.2 Actions

+ 行を表現するクラスUtterance::Model (※チュートリアルでは名称をUtteranceと指定してあるが、微妙に名称を変えています)を作って、これのインスタンスの配列でログを表現してみましょうという問題です。
  + Utterance::Modelが持つのは、time, nick, text の3つです。

```
!# nqp                                                                                                                                                           
                                                                                                                                                                 
grammar Utterance::Grammar {                                                                                                                                     
  token TOP {                                                                                                                                                    
    <linelist>                                                                                                                                                   
  }                                                                                                                                                              
  token linelist {                                                                                                                                               
    [                                                                                                                                                            
    | <line> \n                                                                                                                                                  
    | \n                                                                                                                                                         
    ]+                                                                                                                                                           
  }                                                                                                                                                              
  token line { <time> <.ws> <nick> <.ws> <text> }                                                                                                                
  token time { \d+ ':' \d+ }                                                                                                                                     
  token nick { \w+ }                                                                                                                                             
  token text { <[\w\h]>+ }　# Note: Don't use \s                                                                                                                 
}                                                                                                                                                                
                                                                                                                                                                 
class Utterance::Model {                                                                                                                                         
  has $!time;                                                                                                                                                    
  has $!nick;                                                                                                                                                    
  has $!text;                                                                                                                                                    
                                                                                                                                                                 
  method new(:$time!, :$nick!, :$text!) {                                                                                                                        
     my $obj := nqp::create(self);                                                                                                                               
     $obj.BUILD(:$time, :$nick, :$text);                                                                                                                         
     $obj;                                                                                                                                                       
  }                                                                                                                                                              
  method BUILD(:$time!, :$nick!, :$text!) {                                                                                                                      
     $!time := $time;                                                                                                                                            
     $!nick := $nick;                                                                                                                                            
     $!text := $text;                                                                                                                                            
  }                                                                                                                                                              
  method time() { $!time } # (#1)                                                                                                                                      
  method nick() { $!nick }                                                                                                                                       
  method text() { $!text }                                                                                                                                       
}                                                                                                                                                                
                                                                                                                                                                                                                                                                                                                             
class Utterance::Actions {                                                                                                                                       
  method TOP($/) { make $<linelist>.made }                                                                                                                       
  method linelist($/) {                                                                                                                                          
    my @array;                                                                                                                                                   
    for $<line> {                                                                                                                                                
      nqp::push(@array, Utterance::Model.new(:time($_<time>), :nick($_<nick>), :text($_<text>))); # (#2)                                                                
    }                                                                                                                                                            
    make @array;                                                                                                                                                 
  }                                                                                                                                                              
  method line($/) { make { time => $<time>.made, nick => $<nick>.made, text => $<text>.made } }                                                                  
  method time($/) { make ~$<time> }                                                                                                                              
  method nick($/) { make ~$<nick> }                                                                                                                              
  method text($/) { make ~$<text> }                                                                                                                              
}                                                                                                                                                                
                                                                                                                                                                 
#Utterance::Grammar.HOW.trace-on(Utterance::Grammar);                                                                                                            
                                                                                                                                                                 
my $model := Utterance::Model.new(:time("00:00"), :nick("abc"), :text("abc de fa"));                                                                             
is($model.time, "00:00");                                                                                                                                        
is($model.nick, "abc");                                                                                                                                          
is($model.text, "abc de fa");                                                                                                                                    
                                                                                                                                                                 
my $m := Utterance::Grammar.parse(Q{                                                                                                                             
00:00  abc            abc de fa                                                                                                                                  
00:00  abc            abc de fb                                                                                                                                  
}, :actions(Utterance::Actions));                                                                                                                                
                                                                                                                                                                 
my @result := $m.ast;                                                                                                                                            
                                                                                                                                                                 
is(+@result, 2); # (#3)                                                                                                                                           
                                                                                                                                                                 
is(@result[0].time, "00:00");                                                                                                                                    
is(@result[0].nick, "abc");                                                                                                                                      
is(@result[0].text, "abc de fa");                                                                                                                                
                                                                                                                                                                 
is(@result[1].time, "00:00");                                                                                                                                    
is(@result[1].nick, "abc");                                                                                                                                      
is(@result[1].text, "abc de fb");                                                                                                                                              
```

+ nqpのクラスではゲッターを自動生成する機能 (e.g. `$.text`)が入っていないので、自分でゲッターを記述します。(e.g. `time()`) (#1)
+ クラスのインスタンスの配列を生成したいときは、`method linelist()`のところで、`line`をイテレートして、`@array`にインスタンスをpushすることで実現できます (#2)
+ nqpでも`is()`などを使ったテストを記述することができます (#3)


2.3, 2.4は解いてないです :)
以上、10日目の投稿でした。

# ライセンス
rakudo-and-nqp-internals-course is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.