Perl 6 中的 Actions 和抽象语法树

有一段结构化的文本, 写一个 Grammar 来解析它:

name = Animal Facts
author = jnthn

[cat]
desc = The smartest and cutest
cuteness = 100000

[dugong]
desc = The cow of the sea
cuteness = -10

[magpie]
desc = crow; raven; rook; jackdaw; chough; magpie; jay
cuteness = 99

每一段都是一个章节, 有的章节没有[cat]这样的标题, 要求 grammar 生成一个散列, 散列的键是方括号中的单词, 如果没有就默认为 _ , 散列的值是一个散列的数组, 数组里面的每个散列的键为等号左边的单词, 键值为等号右边的字符。Grammar 如下:

use v6;
#use Grammar::Debugger;
grammar INIFile::Grammar {
    token TOP {
        ^
             # 条目
        
+ # 章节 $ } token section { '[' ~ ']' \n # 每个章节含有多个条目 entry } token entries { [ | \n | \n # entry 可以为空 ]+ } token entry { \h* '=' \h* } token key { \w+ } token value { \N+ } } class INIFileActions { method entries($/) { my %entries; for $ -> $e { %entries{$e} := ~$e; } make %entries; } method TOP($/) { my %result; %result<_> := $.ast; for $
-> $sec { %result{$sec} := $sec.ast; } make %result; } } my $m := INIFile::Grammar.parse(Q{ name = Animal Facts author = jnthn [cat] desc = The smartest and cutest cuteness = 100000 [dugong] desc = The cow of the sea cuteness = -10 [magpie] desc = crow; raven; rook; jackdaw; chough; magpie; jay cuteness = 99 }, :actions(INIFileActions)); my %sections := $m.ast; for %sections -> $sec { say("章节: {$sec.key}"); for $sec.value -> $entry { say(" {$entry.key}: {$entry.value}"); } }

make 是一个函数, 接收单个参数, make 的作用是, 对于每一个 method 中对应的 $_ , 存储生成的抽象语法树(AST)(片段)到 $/ 中。 .ast 用于从已保存的 AST 抽象语法树中检索提取 AST (片段), » 相当于一个循环, 即检索每一个 $ 之类的语法树。

use v6;

grammar INIFile::Grammar {
    token TOP {
        ^
        
+ # 章节 $ } token section { [ '[' ~ ']' \n ]? # [key] 这一行是可选的 # 每个章节含有多个条目 entry } token entries { [ | \n | \n # entry 可以为空 ]+ } token entry { \h* '=' \h* } token key { \w+ } token value { \N+ } } class INIFileActions { method key ($/) { $/.make: ~$/ } method value ($/) { $/.make: ~$/ } method entry ($/) { $/.make: $.ast => $.ast } method entries($/) { $/.make: $».ast } method section($/) { $/.make: $.ast // '_' => $.ast } # 如果 key 不存在就默认为 `_` method TOP($/) { $/.make: $
».ast; # 等价于 $/.make($
».ast); # '-' => $.ast # '_' 没有 ast 方法 } } my $m = INIFile::Grammar.parse(Q{ name = Animal Facts author = jnthn [cat] desc = The smartest and cutest cuteness = 100000 [dugong] desc = The cow of the sea cuteness = -10 [magpie] desc = crow; raven; rook; jackdaw; chough; magpie; jay cuteness = 99 }, :actions(INIFileActions)).ast; for @$m -> $sec { say("章节: {$sec.key}"); for $sec.value -> $entry { say(" {$entry.key}: {$entry.value}"); } }

结果输出为:

章节: dugong                                                                                     
    desc: The cow of the sea                                                                   
    cuteness: -10                                                                              
章节: cat                                                                                        
    desc: The smartest and cutest                                                              
    cuteness: 100000                                                                           
章节: _                                                                                          
    name: Animal Facts                                                                         
    author: jnthn                                                                              
章节: magpie                                                                                     
    desc: crow; raven; rook; jackdaw; chough; magpie; jay                                      
    cuteness: 99                                                                               

Grammar 的解析是从上至下的, 从 top-level (TOP) 开始, 到分支(branch)。 Actions 中的方法是随着解析而执行的, 但是抽象语法树(AST) 的存储和检索是自下而上的, 只有底部的存储完($/.make:)了, 其上层部分才可以使用 .ast.made 方法进行检索, 检索到之后各自进行处理后再次存储, 以供它的上层部分使用, 以此类推。

注意, 第一段代码中 $m 存储的是散列, 而第二段代码中, $m 存储的是数组! 这说明可以返回散列和数组两种形式。留待改天。

你可能感兴趣的:(Perl 6 中的 Actions 和抽象语法树)