我无法重现你的问题。
我唯一能猜测的是您没有使用正确的编码打开文件。
或者更糟糕的是,您从 STDIN 获取文件,但没有选择正确的代码页。 (这是有道理的,因为你的输出也是 mojibake。)
Rakudo 并不真正执行代码页,因此如果您未将环境设置为 utf8,则必须更改$*STDIN
(and $*STDOUT
) 来匹配任何内容。
我现在将假装您已将帖子发布到 CodeReview.StackExchange.com。
首先,我不知道为什么你要为如此小的东西创建一个完整的语法,而这可以通过简单的正则表达式轻松完成。
my token HEB {
'<hebrewname>'
$<t_word> = [<.graph>+]
'</hebrewname>'
}
my token CHA {
'<c n="' $<t_number> = [\d+] '">'
}
my token VER {
'<v n="' $<t_number> = [\d+] '">'
}
my token WOR {
'<w>' $<t_word> = [<.graph>+] '</w>'
}
老实说,这仍然比您似乎需要的要多,因为每个正则表达式只处理一个元素。
这也忽略了我真的不喜欢你给元素命名,比如t_word
and t_number
。这是毫无意义的,因为它们在里面$/
,并且 Grammar 也没有任何此类类似命名的方法,因此它们不会干扰任何其他名称空间。如果必须给它们起名字,请给它们起描述性的名字。
你可以只限制$/
仅对您关心的部分进行字符串化<(…)>
。 (它在这里起作用是因为你只捕获一件事。)
<(
意味着忽略之前的一切,并且)>
意味着忽略之后的一切。
my token HEB {
'<hebrewname>'
<( <.graph>+ )> # $/ will contain only what <.graph>+ matches
'</hebrewname>'
}
my token CHA {
'<c n="' <( \d+ )> '">'
}
my token VER {
'<v n="' <( \d+ )> '">'
}
my token WOR {
'<w>' <( <.graph>+ )> '</w>'
}
您正在解析它,就好像它只是一个面向行的文件一样。
这确实具有一定的意义,因为它被格式化为一个,并且这会导致更少的内存使用。
为此使用命名正则表达式,更不用说整个语法了,有点大材小用了。当对于这种简单的匹配来说并不真正需要逻辑时,它还会分离逻辑。
以下是我如何以面向行的方式解析该文件:
my $in-names = False;
my %names;
my @chapters;
my @verses;
my @current-verse;
for $file_in.lines {
when /'<names>' / { $in-names = True }
when /'</names>'/ { $in-names = False }
# chapter
when /'<c n="' <( \d+ )> '">'/ {
@verses := @chapters[ +$/ - 1 ] //= [];
}
when /'</c>'/ {
# finalize this chapter
# for example print out statistics
# (only needed if you don't want `default` to catch it)
}
# verse
when /'<v n="' <( \d+ )> '">'/ {
@current-verse := @verses[ +$/ - 1 ] //= [];
}
when /'</v>'/ {
# finalize this verse
}
# word
when /'<w>' <( <.graph>+ )> '</w>'/ {
push @current-verse, ~$/;
}
# name tags
# must be after more specific regexes
when /'<' <tag=.ident> '>' $<value> = [<.ident>|\d+] {} "</$<tag>>"/ {
if $in-names {
%names{~$<tag>} = ~$<value>
} else {
note "not handling $<tag> => $<value> outside of <names>"
}
}
default { note "unexpected text '$_'" }
}
注意when
让你不必做next
.
因为我们只是使用$_
代替$line
,这样我们就可以直接使用正则表达式作为这些条件的条件when
声明。
我懒得用^
or $
所以也没有必要trim
or use ^\s*
and \s*$
.
它确实使它变得更加脆弱,所以如果它成为问题,您可能需要更改它。
如果您真的只想像现在一样进行简单的线路处理,我相信您可以更改上述内容以满足您的需求。
我想让这对将来遇到这个问题的人更有用。因此,我从文件创建了一个数据结构,而不是遵循您正在做的事情。
真的,如果我要的话,我可能只会接触语法.parse()
一次性完成整个文件。
这就是这样的语法。
grammar Book {
rule TOP {
<names>
<chapter> +
# note that there needs to be a space between <chapter> and +
# so that whitespace can be between <c…>…</c> elements
}
rule names {
'<names>' ~ '</names>'
<name> +
}
token name {
'<' <tag=.ident> '>'
$<name> = [<.ident>|\d+]
{}
"</$<tag>>"
}
rule chapter {
# note space before ]
['<c n="' <number> '">' ] ~ '</c>'
<verse> +
}
rule verse {
['<v n="' <number> '">' ] ~ '</v>'
<word> +
}
token number { \d+ }
token word { '<w>' <( <.graph>+ )> '</w>' }
}
进行与您一直在做的类似的处理
class Line-Actions {
has IO::Handle:D $.file-out is required;
has $!number-type is default<chapter>;
method name ($/) {
if $<tag> eq 'hebrewname' {
say "hebrew name of book is $<name>";
}
}
# note that .chapter and .verse will run at the end
# of parsing them, which is too late for when .word is processed
# so we do it in .number instead
method number ($/) {
say "$!number-type number is $/";
$!number-type = 'verse';
}
method chapter ($/) {
# reset to default of "chapter"
# as the next .number will be for the next chapter
$!number-type = Nil;
}
method word ($/) {
say "word is $/";
$!file-out.print(~$/);
say "number of graphemes in word is $/.chars()";
.say for "$/".comb.map: *.uninames.join(', ');
}
}
Book.parsefile(
$filename,
actions => Line-Actions.new( 'outfile.txt'.IO.open(:w) )
);