如何使用Jison lexer返回多个令牌

时间:2016-10-22 07:07:24

标签: javascript jison

我是lexing和解析的新手,如果标题不够明确,我很抱歉。

基本上,我正在使用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的完全相同的输入来执行另一个动作,从而允许它添加到适当的dedents中。然而,感觉很糟糕,我担心会出现无法预料的问题。

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

1 个答案:

答案 0 :(得分: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&#34;全球&#34;变量。 indentCount将跟踪我们当前的缩进长度。 forceDedent会强制我们返回DEDENT,如果它的值大于0。

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

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

如果当前缩进大于我们最近的缩进,我们会在indentCount变量上跟踪该缩进并返回INDENT

如果我们还没有回来,那么现在是时候为可能的受访者做准备了。我们制作一个数组来跟踪它们。

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

如果我们跟踪任何dedents,我们需要确保所有这些都被lexer返回。因为词法分析器一次只能返回1个令牌,所以我们在这里返回1,但我们也会设置forceDedent变量以确保我们还返回其余部分。为了确保我们再次迭代这个模式并插入那些dedents,我们将使用unput函数。

在任何其他情况下,我们只会返回NEWLINE