如何在Perl6中实现递归文法
How to implement recursive grammar in Perl6
我正在尝试使用 Perl6 语法实现 Markdown 解析器,但遇到了块引号问题。块引用段落不能用嵌套大括号表示,因为它是特定格式行的列表。但从语义上讲,它是一个嵌套的 markdown。
基本上,这一切都归结为以下定义:
token mdBlockquote {
<mdBQLine>+ {
my $quoted = [~] $m<mdBQLine>.map: { $_<mdBQLineBody> };
}
}
mdBQLine
令牌的实际实现与此处无关。唯一需要注意的重要事项是 mdBQLineBody
键包含实际引用的行 >
已经被剥离。毕竟,对于一个块:
> # quote1
> quote2
>
> quote3
quote3.1
$quoted
标量将包含:
# quote1
quote2
quote3
quote3.1
现在,重点是将上述数据解析并注入回 Match
对象 $/
。这就是我完全不知道的地方。最明显的解决方案:
token mdBlockquote {
<mdBQLine>+ {
my $quoted = [~] $m<mdBQLine>.map: { $_<mdBQLineBody> };
$<mdBQParsed> = self.parse( $quoted, actions => self.actions );
}
}
一次失败有两个原因:首先,$/
是一个只读对象;其次,.parse
对其进行有效修改,使其无法向原始树中注入任何内容。
然后有什么解决方案吗post-分析解析的数据,提取并重新解析块引用,重复...?
扩展@HåkonHægland 的评论...
$/
is a read-only object ... effectively making it impossible to inject anything into the original tree.
不完全是:
迂腐地说,$/
是一个符号,而不是一个对象,无论它是否绑定到一个对象。如果它是一个参数(并且没有用 is rw
或 is copy
声明),那么它是只读的,否则它可以自由反弹,例如。 $/ := 42
.
但是你指的是分配给一个键。分配的语义由分配给的项目决定。如果它们是不是容器的普通对象,那么它们将不支持 lvalue 语义,如果您尝试分配给它们,您将收到 Cannot modify an immutable ...
错误。 Match
对象在这个意义上是不可变的。
你可以做的是使用 .make
方法将任意数据从任何 Match
对象挂起。 (make
例程在 $/
上调用此方法。)这就是您将自定义数据存储在分析树中的方式。
要访问在解析树/Match
对象的给定节点中生成的内容,请在该节点上调用 .made
(或同义词 .ast
)。
通常,您 make
为解析树中的较高节点所做的工作包括为较低级别节点所做的工作。
请尝试以下未经测试的代码,看看会得到什么,如果它严重失败并且您无法找到使其工作的方法,请发表评论,或者从那里构建并考虑上面的最后两段,并评论其结果:
token mdBlockquote {
<mdBQLine>+ {
make .parse: [~] $m<mdBQLine>.map: { $_<mdBQLineBody> };
}
}
好的,这是我使用的最终解决方案。语法规则如下所示:
token mdBlockquote {
<mdBQLine>+ {
my $m = $/;
my $bq-body = [~] $m<mdBQLine>.map( { $_<mdBQLineBody> } );
$m.make(
self.WHAT.parse(
$bq-body,
actions => self.actions.clone,
)
);
}
}
这里的重要技巧是在 $m
中备份 $/
,因为 .parse
将替换它。
块引用正文在调用 .parse
之前预取到 $bq-body
中,因为如果将表达式直接作为参数传递,会产生令人困惑的副作用。
在 self.WHAT
上调用 .parse
以避免弄乱当前语法对象。
此规则将以 $m.ast
结束,其中包含一个 Match
对象,而该对象又将包含操作生成的数据。相应的操作方法然后执行以下操作:
method mdBlockquote ($m) {
my $bq = self.makeNode( "Blockquote" );
$bq.push( $m.ast.ast );
$m.make( $bq );
}
由于 actions 对象构建了一个流线型的 AST,适用于将 markdown 简单地转换为其他格式,这里发生的是它获取由递归 .parse
生成的那棵树的一个分支,并将其移植到主树中.
很棒的是代码支持开箱即用的嵌套块引用,不需要特殊处理。不好的是它仍然有很多额外的代码,而类似于:
token mdBlockquote {
<mdBQLine>+ $<mdBQBody>={
my $bq-body = [~] $<mdBQLine>.map( { $_<mdBQLineBody> } );
self.WHAT.parse(
$bq-body,
actions => self.actions.clone,
);
}
}
看起来会好很多,并且不需要超出其正常职责的操作对象干预。
我正在尝试使用 Perl6 语法实现 Markdown 解析器,但遇到了块引号问题。块引用段落不能用嵌套大括号表示,因为它是特定格式行的列表。但从语义上讲,它是一个嵌套的 markdown。
基本上,这一切都归结为以下定义:
token mdBlockquote {
<mdBQLine>+ {
my $quoted = [~] $m<mdBQLine>.map: { $_<mdBQLineBody> };
}
}
mdBQLine
令牌的实际实现与此处无关。唯一需要注意的重要事项是 mdBQLineBody
键包含实际引用的行 >
已经被剥离。毕竟,对于一个块:
> # quote1
> quote2
>
> quote3
quote3.1
$quoted
标量将包含:
# quote1
quote2
quote3
quote3.1
现在,重点是将上述数据解析并注入回 Match
对象 $/
。这就是我完全不知道的地方。最明显的解决方案:
token mdBlockquote {
<mdBQLine>+ {
my $quoted = [~] $m<mdBQLine>.map: { $_<mdBQLineBody> };
$<mdBQParsed> = self.parse( $quoted, actions => self.actions );
}
}
一次失败有两个原因:首先,$/
是一个只读对象;其次,.parse
对其进行有效修改,使其无法向原始树中注入任何内容。
然后有什么解决方案吗post-分析解析的数据,提取并重新解析块引用,重复...?
扩展@HåkonHægland 的评论...
$/
is a read-only object ... effectively making it impossible to inject anything into the original tree.
不完全是:
迂腐地说,
$/
是一个符号,而不是一个对象,无论它是否绑定到一个对象。如果它是一个参数(并且没有用is rw
或is copy
声明),那么它是只读的,否则它可以自由反弹,例如。$/ := 42
.但是你指的是分配给一个键。分配的语义由分配给的项目决定。如果它们是不是容器的普通对象,那么它们将不支持 lvalue 语义,如果您尝试分配给它们,您将收到
Cannot modify an immutable ...
错误。Match
对象在这个意义上是不可变的。
你可以做的是使用 .make
方法将任意数据从任何 Match
对象挂起。 (make
例程在 $/
上调用此方法。)这就是您将自定义数据存储在分析树中的方式。
要访问在解析树/Match
对象的给定节点中生成的内容,请在该节点上调用 .made
(或同义词 .ast
)。
通常,您 make
为解析树中的较高节点所做的工作包括为较低级别节点所做的工作。
请尝试以下未经测试的代码,看看会得到什么,如果它严重失败并且您无法找到使其工作的方法,请发表评论,或者从那里构建并考虑上面的最后两段,并评论其结果:
token mdBlockquote {
<mdBQLine>+ {
make .parse: [~] $m<mdBQLine>.map: { $_<mdBQLineBody> };
}
}
好的,这是我使用的最终解决方案。语法规则如下所示:
token mdBlockquote {
<mdBQLine>+ {
my $m = $/;
my $bq-body = [~] $m<mdBQLine>.map( { $_<mdBQLineBody> } );
$m.make(
self.WHAT.parse(
$bq-body,
actions => self.actions.clone,
)
);
}
}
这里的重要技巧是在 $m
中备份 $/
,因为 .parse
将替换它。
块引用正文在调用 .parse
之前预取到 $bq-body
中,因为如果将表达式直接作为参数传递,会产生令人困惑的副作用。
self.WHAT
上调用 .parse
以避免弄乱当前语法对象。
此规则将以 $m.ast
结束,其中包含一个 Match
对象,而该对象又将包含操作生成的数据。相应的操作方法然后执行以下操作:
method mdBlockquote ($m) {
my $bq = self.makeNode( "Blockquote" );
$bq.push( $m.ast.ast );
$m.make( $bq );
}
由于 actions 对象构建了一个流线型的 AST,适用于将 markdown 简单地转换为其他格式,这里发生的是它获取由递归 .parse
生成的那棵树的一个分支,并将其移植到主树中.
很棒的是代码支持开箱即用的嵌套块引用,不需要特殊处理。不好的是它仍然有很多额外的代码,而类似于:
token mdBlockquote {
<mdBQLine>+ $<mdBQBody>={
my $bq-body = [~] $<mdBQLine>.map( { $_<mdBQLineBody> } );
self.WHAT.parse(
$bq-body,
actions => self.actions.clone,
);
}
}
看起来会好很多,并且不需要超出其正常职责的操作对象干预。