如何在Perl6中实现递归语法

时间:2018-07-16 03:01:07

标签: perl6 raku

我正在尝试使用Perl6语法实现Markdown解析器,并被块引用卡住了。 blockquote段落不能用嵌套的大括号表示,因为它是特定格式的行的列表。但是从语义上讲,这是一个嵌套的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是只读对象。其次,import pandas as pd import numpy as np df= pd.DataFrame(np.nan, columns =["A","B","C"], index =np.arange(5)) 对其进行了有效的修改,使得无法将任何东西注入原始树。

有什么解决方案,然后对解析的数据进行后分析,提取并重新解析块引用,重复...吗?

2 个答案:

答案 0 :(得分:6)

扩展@HåkonHægland的评论...

  

$/是一个只读对象,有效地使其无法向原始树中注入任何东西。

不太清楚:

  • 从本质上讲,$/是一个符号,而不是一个对象,无论它是否绑定到一个对象。像P6中的任何其他符号一样,它始终可以自由反弹。 ($/ := 42将始终有效。)

  • 但是您指的是分配。分配的语义由要分配给的项目确定。如果它们是不是容器的普通对象,则它们将不支持lvalue语义,如果尝试为其分配它们,则会出现Cannot modify an immutable ...错误。在这种意义上,Match对象是不可变的。

您可以做的是通过使用Match方法将任意数据挂在任何.make对象上。 (make例程在$/上调用此方法。)这是将自定义数据存储在解析树中的方式。

要访问在解析树/ Match对象的给定节点中所做的操作,请在该节点上调用.made(或.ast,这是同义词)。

make通常用于解析树中的较高节点,其中包括针对较低层节点所做的。

请尝试以下未经测试的代码,看看能得到什么,然后注释一下它是否失败了,您无法找到使之运行的方法,或者从上面的最后两段考虑到那里,评论其效果:

token mdBlockquote {
    <mdBQLine>+ {
        make .parse: [~] $m<mdBQLine>.map: { $_<mdBQLineBody> };
    }
}

答案 1 :(得分:5)

好的,这是我使用的最终解决方案。语法规则如下:

    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将取代它。

在调用$bq-body之前,Blockquote主体已预取到.parse中,因为如果将表达式直接作为参数传递,则会产生令人困惑的副作用。

.parse上调用

self.WHAT以避免弄乱当前的语法对象。

此规则将以$m.ast结尾,其中包含一个Match对象,该对象又将包含操作生成的数据。然后,相应的动作方法将执行以下操作:

    method mdBlockquote ($m) {
        my $bq = self.makeNode( "Blockquote" );
        $bq.push( $m.ast.ast );
        $m.make( $bq );
    }

由于action对象构建了适用于将markdown轻松转换为其他格式的简化AST,因此此处发生的事情是,它获取由递归.parse生成的那棵树的分支并将其移植到主树中。 / p>

很棒的是,该代码支持开箱即用的嵌套块引用,不需要特殊处理。不好的是,它仍然是很多额外的代码,而类似:

    token mdBlockquote {
        <mdBQLine>+ $<mdBQBody>={
            my $bq-body =  [~] $<mdBQLine>.map( { $_<mdBQLineBody> } ); 
            self.WHAT.parse(
                $bq-body,
                actions => self.actions.clone,
            );
        }
    }

看起来会更好,并且不需要采取超出正常职责范围的干预行动。