It's Classes All the Way Down

楽土

当我在为一个网络 api 构建缓存时,我发现自己在同一个数据上走了两遍,以解决缺乏正确的输入法的问题。JSON 只知道字符串,尽管大部分字段都是整数和时间戳。我在用 JSON::Fast 解析JSON后,通过强制 .map-ing 来修复类型。

@stations.=hyper.map: { # Here be heisendragons!
    .<lastchangetime> = .<lastchangetime>
        ?? DateTime.new(.<lastchangetime>.subst(' ', 'T') ~ 'Z', :formatter(&ISO8601))
        !! DateTime;
    .<clickcount> = .<clickcount>.Int;
    .<lastcheckok> = .<lastcheckok>.Int.Bool;

    (note "$_/$stations-count processed" if $_ %% 1000) with $++;

    .Hash
};

hyper 对加速有很大帮助,但会给CPU缓存带来很大压力。一定有更好的方法。

然后lizmat展示了 Rakudo 的胆量所在。

m: grammar A { token a { }; rule a { } }
OUTPUT: «5===SORRY!5=== Error while compiling <tmp>␤Package 'A' already has a regex 'a' 
(did you mean to declare a multi-method?)␤

标记是 regex 或者是方法。但是如果标记是方法,那么 grammar 必须是类。而这就允许我们对 grammar 进行子类化。

grammar WWW::Radiobrowser::JSON is JSON {
    token TOP {\s* <top-array> \s* }
    rule top-array      { '[' ~ ']' <station-list> }
    rule station-list   { <station> * % ',' }
    rule station        { '{' ~ '}' <attribute-list> }
    rule attribute-list { <attribute> * % ',' }

    token year { \d+ } token month { \d ** 2 } token day { \d ** 2 } token hour { \d ** 2 } token minute { \d ** 2 } token second { \d ** 2}
    token date { <year> '-' <month> '-' <day> ' ' <hour> ':' <minute> ':' <second> }

    token bool { <value:true> || <value:false> }

    token empty-string { '""' }

    token number { <value:number> }

    proto token attribute { * }
    token attribute:sym<clickcount> { '"clickcount"' \s* ':' \s* '"' <number> '"' }
    token attribute:sym<lastchangetime> { '"lastchangetime"' \s* ':' \s* '"' <date> '"' }
    token attribute:sym<lastcheckok> { '"lastcheckok"' \s* ':' \s* '"' <bool> '"' }
}

在这里,我们将一些标记重载,并转发调用在父 grammar 中得到不同名称的标记。动作类也随之而来。

class WWW::Radiobrowser::JSON::Actions is JSON::Actions {
    method TOP($/) {
        make $<top-array>.made;
    }
    method top-array($/) {
        make $<station-list>.made.item;
    }
    method station-list($/) {
        make $<station>.hyper.map(*.made).flat; # Here be heisendragons!
    }
    method station($/) {
        make $<attribute-list>.made.hash.item;
    }
    method attribute-list($/) {
        make $<attribute>».made.flat;
    }
    method date($_) { .make: DateTime.new(.<year>.Int, .<month>.Int, .<day>.Int, .<hour>.Int, .<minute>.Int, .<second>.Num) }
    method bool($_) { .make: .<value>.made ?? Bool::True !! Bool::False }
    method empty-string($_) { .make: Str }

    method attribute:sym<clickcount>($/) { make 'clickcount' => $/<number>.Int; }
    method attribute:sym<lastchangetime>($/) { make 'lastchangetime' => $/<date>.made; }
    method attribute:sym<lastcheckok>($/) { make 'lastcheckok' => $/<bool>.made; }
}

如果你想知道如何调用一个名字如此古怪的方法,请使用 postfix:<.> 的引号版本。

class C { method m:sym<s>{} }
C.new.'m:sym<s>'()

我把上面的例子截断了。完整的源代码可以在这里找到。.hyper 版本的速度还是挺快的,但也有 heisenbuggy。事实上 .hyper 在程序启动后执行速度过快或在递归 Routine 中使用时,可能根本无法工作。这主要是由于 grammer 是 Rakudo 中最古老的部分之一,要想让它快起来,工作量最小。这是一个可以解决的问题。我很期待 grammar 万事通。

如果你有 gramamr 请不要藏起来。有人可能需要它们才会有品位。

by gfldex

comments powered by Disqus