こんにちは、10日目の投稿になります。
Introduction
みなさんはrakudo-and-nqp-internals-courseを知っていますか?
rakudo-and-nqp-internalsはMoarVMの開発者であるJonathan Worthington氏の作ったnqpとrakudoの内部構造に関するチュートリアルです。
スライドだけじゃなくて、エクササイズ問題も用意されています。
http://edumentab.github.io/rakudo-and-nqp-internals-course/
というわけで、詳しい話はスライドを見てもらうとして、今回のアドベントカレンダーでは私がエクササイズ問題に挑戦していきたいと思います。
ちなみに、3日目ではExercise 1に挑戦しました。
今日の10日目はExercise 2に挑戦します。
Exercise 2
2.1 Grammar
- Perl 6のirclog のログデータを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と文法的な違いはありません。
-
.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.