1

如果标题不够清楚,我对词法分析和解析很抱歉。

基本上,我使用 Jison 来解析一些文本,并且试图让词法分析器理解缩进。这是有问题的一点:

(\r\n|\r|\n)+\s*      %{
                        parser.indentCount = parser.indentCount || [0];

                        var indentation = yytext.replace(/^(\r\n|\r|\n)+/, '').length;

                        if (indentation > parser.indentCount[0]) {
                           parser.indentCount.unshift(indentation);
                           return 'INDENT';
                        }

                        var tokens = [];

                        while (indentation < parser.indentCount[0]) {
                          tokens.push('DEDENT');
                          parser.indentCount.shift();
                        }

                        if (tokens.length) {
                           return tokens;
                        }

                        if (!indentation.length) {
                          return 'NEWLINE';
                        }
                      %}

到目前为止,几乎所有这些都按预期工作。一个问题是我尝试返回一组DEDENT标记的行。似乎 Jison 只是将该数组转换为一个字符串,这导致我得到一个解析错误,例如Expecting ........, got DEDENT,DEDENT.

我希望我能做的就是手动将一些DEDENT令牌推送到堆栈上。也许有类似的功能this.pushToken('DEDENT')或类似的东西。但是 Jison 文档不是很好,我可以使用一些帮助。

有什么想法吗?

编辑:

在查看生成的解析器代码后,我似乎已经能够解决这个问题。这似乎是可行的......

if (tokens.length) {
  var args = arguments;

  tokens.slice(1).forEach(function () {
    lexer.performAction.apply(this, args);
  }.bind(this));

  return 'DEDENT';
}

这会诱使词法分析器对堆栈中的每个输入使用完全相同的输入来执行另一个操作DEDENT,从而允许它添加适当的 dents。但是,感觉很糟糕,我担心可能会出现无法预料的问题。

如果有人对更好的方法有任何想法,我仍然会喜欢它。

4

1 回答 1

1

几天后,我最终找到了一个更好的答案。这是它的样子:

(\r\n|\r|\n)+[ \t]*   %{
                        parser.indentCount = parser.indentCount || [0];
                        parser.forceDedent = parser.forceDedent || 0;

                        if (parser.forceDedent) {
                          parser.forceDedent -= 1;
                          this.unput(yytext);
                          return 'DEDENT';
                        }

                        var indentation = yytext.replace(/^(\r\n|\r|\n)+/, '').length;

                        if (indentation > parser.indentCount[0]) {
                           parser.indentCount.unshift(indentation);
                           return 'INDENT';
                        }

                        var dedents = [];

                        while (indentation < parser.indentCount[0]) {
                          dedents.push('DEDENT');
                          parser.indentCount.shift();
                        }

                        if (dedents.length) {
                           parser.forceDedent = dedents.length - 1;
                           this.unput(yytext);
                           return 'DEDENT';
                        }

                        return `NEWLINE`;
                      %}

首先,我修改了我的捕获正则表达式,以确保我不会在一系列非换行符空格后无意中捕获额外的换行符。

接下来,我们确保有 2 个“全局”变量。indentCount将跟踪我们当前的缩进长度。如果 a 的值大于 0,forceDedent将强制我们返回它。DEDENT

接下来,我们有一个条件来测试 上的真值forceDedent。如果我们有一个,我们会将其减 1,并使用该unput函数确保我们在相同的模式上至少迭代一次,但对于这次迭代,我们将返回一个DEDENT.

如果我们没有返回,我们会得到当前缩进的长度。

如果当前缩进大于我们最近的缩进,我们将在我们的indentCount变量上跟踪它并返回一个INDENT.

如果我们还没有回来,那么是时候为可能的凹痕做好准备了。我们将制作一个数组来跟踪它们。

当我们检测到凹痕时,用户可能会尝试一次关闭 1 个或多个块。因此,我们需要DEDENT在用户关闭时包含尽可能多的块。我们设置了一个循环,并说只要当前缩进小于我们最近的缩进,我们就会将 a 添加DEDENT到我们的列表中并将一个项目从indentCount.

如果我们跟踪任何 dents,我们需要确保它们都被词法分析器返回。因为词法分析器一次只能返回 1 个标记,所以我们将在此处返回 1,但我们还将设置forceDedent变量以确保我们也返回其余的标记。为了确保我们再次迭代这个模式并且可以插入这些凹痕,我们将使用该unput函数。

在任何其他情况下,我们将只返回一个NEWLINE.

于 2016-10-23T16:20:12.990 回答