Perl 6 语法,第1部分

Perl 6 语言内置了对 语法 的支持。您可以将语法视为正则表达式和诸如 yaccbison 等实用工具的结合,或者更复杂的语法工具,如 ANTLR。所有这些——包括词法分析器、解析器和语义处理——通常是编译器的独立部分,都是内置的,并且可以在安装了新的 Perl 6 之后立即使用。

要感受到语法的强大之处,只需说一点:Perl 6 的自身语法是用 Perl 6 编写的,作为一个巨大的语法类 Perl6::Grammar

在这篇文章中,我将通过一些示例来阐述语法的基础知识。所有必需的语言结构将在进行过程中解释。

解析数字

解析数字似乎是一个简单的任务,直到你开始考虑用户可能使用的不同格式,包括负数、浮点数、科学记数法中的数字以及 C 的长长整数等特殊形式的数字。

让我们从最简单的形式开始:数字作为一串数字的序列。例如,1、42、123 或 1000。Perl 6 的语法是一种特殊的类,有自己的关键词。语法的第一个规则必须(默认)命名为 TOP,以下是完全解析我们第一组数字的程序

grammar N {
    token TOP {
        <digit>+
    }
}

for <1 42 123 1000 -3> -> $n {
    say N.parse($n) ?? "OK $n" !! "NOT OK $n";
}

当调用 N 语法的 parse 方法时,Perl 会尝试将给定的字符串与 TOP 方法进行匹配。在我们的例子中,这是一个 token,这意味着字符串的各个部分之间不能有任何可选的空格。由于 TOP 仅在消耗整个字符串时才成功,因此不需要使用显式的锚点 ^$ 来绑定标记的边缘。

与正则表达式一样,标记和规则可以包括其他标记、规则或通过它们的名称引用的正则表达式。在我们的第一个例子中,TOP 标记需要匹配数字的内置方法 digit。与标准 Perl 5 正则表达式中的量化符相同,+ 量化符允许前面的原子重复一次或多次。

到目前为止,我们的简单语法只能解析无符号整数。任何负数都无法解析

OK 1
OK 42
OK 123
OK 1000
NOT OK -3

让我们更新语法并引入表示可选符号的标记,该符号可以是 +-

grammar N {
    token TOP {
        ['+' | '-']?
        <digit>+
    }
}

在这里,方括号将两个备选方案组合在一起:'+' | '-'。量化符 ? 要求只有一个这样的字符,或者没有。在 Perl 6 中,方括号仅创建一个分组,但不捕获其内容。请注意,+- 都被引用,因为 Perl 6 将任何非字母数字字符视为特殊字符,除非它被引用或用 \ 转义。

下一步是添加对浮点数的支持。一个临时的解决方案可以创建一个包含数字和 '.' 字符的字符类,但这将是完全错误的。例如,包含两个点的字符串 3..14 会通过这个过滤器。所以,不要这样做

grammar N {
    token TOP {
        ['+' | '-']?
        <digit>+
        ['.' <digit>+]?
    }
}

这个语法现在允许一个可选部分,该部分由点和另一串数字组成,当数字是整数或包含显式的小数部分时,它工作得很好,例如 3.14。它对于缺少其中一个部分的数字失败:3..14

尝试使用量词使部分内容可选会使语法难以阅读且容易出错。例如,以下标记符匹配上述所有数字,但也匹配单个 .

grammar N {
    token TOP {
        ['+' | '-']?
        <digit>*
        ['.' <digit>*]?
    }
}

是时候引入更多标记符了。将数字序列提取为单独的标记符,并明确列出所有变体

grammar N {
    token TOP {
        <sign>?
        <value>
    }
    token sign {
        '+' | '-'
    }
    token digits {
        <digit>+
    }
    token value {
        | <digits> '.' <digits>
        | '.' <digits>
        | <digits> '.'
        | <digits>
    }
}

value 标记符封装了变体:它包含接受数字的四种不同表示形式。竖线将它们分隔开。为了统一,允许在第一个变体之前添加一个额外的竖线,这样所有变体都可以用简单的ASCII艺术强调。

当前的语法已经足够智能,可以拒绝单个点号

OK 1
OK 42
OK 123
OK 1000
OK -3
OK 3.14
OK 3.
OK .14
NOT OK .

最后一步是支持科学记数法中的数字。为此添加另一个变体是一个简单的候选方案

grammar N {
    token TOP {
        <sign>?
        [
            | <value> <exp> <sign>? <digits>
            | <value>
        ]
    }
    token sign {
        '+' | '-'
    }
    token exp {
        'e' | 'E'
    }
    token digits {
        <digit>+
    }
    token value {
        | <digits> '.' <digits>
        | '.' <digits>
        | <digits> '.'
        | <digits>
    }
}

用以下案例测试语法

for <1 42 123 1000 -3
     3.14 3. .14 .
     -3.14 -3. -.14
     10E2 10e2 -10e2 -1.2e3 10e-3 -10e-3 -10.2e-33
    > -> $n {
    say N.parse($n) ?? "OK $n" !! "NOT OK $n";
}

一切正常。但是等等,在Perl中,下划线也被允许出现在数字中!拥有合适的语法,添加对此的支持很容易;只需修改 digits 标记符即可

token digits {
    <digit>+ ['_' <digit>+]?
}

不符合规则的字符串仍然被忽略

OK 100_000
NOT OK _1
NOT OK 1_
NOT OK 1__0

结论

通过几个简单的步骤,我们创建了一个可以理解不同格式数字的语法。作为练习,你可以添加对前缀 0x0b0o(十六进制、二进制和八进制)以及后缀(如C中的 1000L)的支持。语法仅用于检查数字格式的有效性,其功能远不止于此。在Perl 6中,你可以在语法中添加 动作;这些是当相应的规则或标记符成功匹配时执行的代码块。但这是另一天的故事。

标签

Andrew Shitov

Andrew自2000年起一直是Perl 6和Raku的爱好者,是多本书籍的作者,也是Perl和Raku活动的组织者,包括三个年度欧洲会议。目前正在撰写他的新书《使用Raku创建编译器》。

浏览他们的文章

反馈

这篇文章有什么问题吗?请通过在GitHub上打开问题或拉取请求来帮助我们