Perl 6 Grammar 之分割结构化文本

如何使用 Grammar 分割一个有规律的文本文件? 首先这个文本有规律, 但是却是多行的。
我想将这样的文档分为独立的. 比如下面这个例子, 我想将他们分成3个独立的文本, 每个文本包含: [时间] Title 以及下面的 content lines. 实际的文件会有上千个, 最终输出的文本的名字是按照括号里面的时间来。

sample.txt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[28/04/2015 12:32] Title1
content line 1
content line 2
content line 3
content line 4
content line 5
balabala
balabala
[28/04/2015 12:16] Title2
content line 6
balabala
content line 7
[27/04/2015 17:30] ​Title3
content line 8
content line 9
content line 10

下面是解析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
use Grammar::Tracer;
# 开启 Grammar 调试有助于排错
grammar StructedText {
token TOP { ^ <entry>+ $ }
token entry {
<head> \s* # 每一项有一个标题
<line>+ \s* # 每个标题下面有很多行
}
token head { '[' <datetime> ']' \s+ <title> }
token datetime { <filedate> \s+ <filetime> }
token filedate { [\d+]+ % '/' }
token filetime { [\d+]+ % ':' }
token title { \N+ }
token line {
[
<!head> # 前面不是 head 标题
. # 点号匹配换行符
]+
}
}
# Method 'ast' not found for invocant of class 'Str'
# make ~$<filetime>.subst(':', '-', :g).ast;
# 字符串是没有 ast 方法的, Match 对象才有。
class StructedText::Actions {
method line($/) { $/.make: ~$/ }
method title($/) { $/.make: ~$/}
method datetime($/) { $/.make: ~$/.subst(rx/<[:/]>/, '-', :g) } # 在datime 中处理文件名, 替换掉特殊符号
method head($/) { $/.make: ~$<datetime>.ast } # head 使用了 datetime 这个 submatch 来构建 ast
method entry($/) { make $<head>.ast => $<line>».made; }
method TOP($/) { $/.make: $<entry>».ast; }
}
my $actions = StructedText::Actions.new;
my $parsed = StructedText.parsefile('sample.txt', :$actions).made;
if $parsed {
for @$parsed -> $e {
say ~$e.key;
}
}

astmade 一样, 都是使用孩子节点上已经制造好的(make)的 ast, 处理后再附件到($/.make)父节点的 $/ 上。
例如, 我们的 TOP 在使用 entry 时, 需要从 entry 中取回 ast ($<entry>».ast) 供它附加.而 method entry 中用到了 <head>, 这时 一定是制造好了的(make), 我们现在要用它, 就使用 .ast 语法来取回这个 ast 片段, 同理, method entry 中用到了 $line, 则子节点 $line 已经为我们制造好了(method line($/) { $/.make: ~$/ }), 使用时用 .made 方法取回就行了。

总而言之, TOP 是树根的话, 那么这一级会取回所有的 AST 块, 而它的孩子节点会依次取回下一节点的 AST 块, 逐级逐级的准备好(make)数据块, 然后再逐级往上附加数据块。

1
2
3
28-04-2015 12-32
28-04-2015 12-16
27-04-2015 17-30

完整的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
grammar StructedText {
token TOP { ^ <entry>+ $ }
token entry {
<head> \s* # 每一项有一个标题
<line>+ \s* # 每个标题下面有很多行
}
token head { '[' <datetime> ']' \s+ <title> }
token datetime { <filedate> \s+ <filetime> }
token filedate { [\d+]+ % '/' }
token filetime { [\d+]+ % ':' }
token title { \N+ }
token line {
[
<!head> # 前面不是 head 标题
. # 点号匹配换行符
]+
}
}
class StructedText::Actions {
method line ($/) { $/.make: ~$/ }
method filedate($/) { $/.make: ~$/.subst(rx/<[:/]>/, '-', :g) }
method head ($/) { $/.make: ~$/.subst(rx/<[:/]>/, '-', :g) }
method entry ($/) { make $<head>.ast => $<line>».made; }
method TOP ($/) { $/.make: $<entry>».ast; }
}
my $actions = StructedText::Actions.new;
my $parsed = StructedText.parsefile('sample.txt', :$actions).made;
if $parsed {
for @$parsed -> $e {
my $filename = ~$e.key.match(/'[' <( <-[\[\]]>+ )> ']'/) ~ ".txt";
my $fh = open $filename, :w;
$fh.say: ~$e.key;
for $e.value -> $v {
$fh.say: $v;
}
$fh.close;
say "生成文件 $filename ";
}
}
------ Young For Perl 6! ------