【问题标题】:Implement heredocs with trim indent using PEG.js使用 PEG.js 实现带有修剪缩进的 heredocs
【发布时间】:2021-12-02 13:57:36
【问题描述】:

我正在使用一种类似于 ruby​​ 的语言 gaiman,我正在使用 PEG.js 来生成解析器。

你知道有没有办法用适当的缩进来实现 heredocs?

xxx =  <<<END
       hello
       world
       END

输出应该是:

"hello
world"

我需要这个,因为这段代码看起来不太好:

def foo(arg) {
  if arg == "here" then
     return <<<END
xxx
  xxx
END
  end
end

这是用户想要返回的函数:

"xxx
  xxx"

我希望代码看起来像这样:

def foo(arg) {
  if arg == "here" then
     return <<<END
            xxx
              xxx
            END
  end
end

如果我修剪所有行,用户将无法在需要时使用带有前导空格的字符串。有谁知道 PEG.js 是否允许这样做?

我还没有任何用于 heredocs 的代码,只是想确定我想要的东西是否可行。

编辑:

所以我尝试实现 heredocs,但问题是 PEG 不允许反向引用。

heredoc = "<<<" marker:[\w]+ "\n" text:[\s\S]+ marker {
    return text.join('');
}

它表示标记未定义。至于修剪我想我可以使用location()函数

【问题讨论】:

    标签: parsing code-generation peg


    【解决方案1】:

    我不认为这是解析器生成器的合理期望;几乎没有人能与挑战相媲美。

    首先,识别 here-string 语法本质上是上下文相关的,因为结束分隔符必须是在 &lt;&lt;&lt; 标记之后提供的分隔符的精确副本。所以你需要一个自定义词法分析器,这意味着你需要一个解析器生成器,它允许你使用自定义词法分析器。 (因此,假设您需要无扫描解析器的解析器生成器可能不是最佳选择。)

    识别 here-string 标记的结尾应该不会太难,尽管您不能使用单个正则表达式来做到这一点。我的方法是使用自定义扫描函数,将此处的字符串分成一系列行,将它们连接起来,直到到达仅包含结束分隔符的行。

    一旦您识别出文字的文本,您只需要按照您想要的方式规范化空格就是&lt;&lt;&lt; 开始的列号。这样,您可以修剪字符串文字中的每一行。所以你只需要一个准确报告令牌位置的词法扫描器。修剪通常不会在生成的词法扫描器内完成;相反,它将是相关的语义动作。 (同样,它可能是语法中的语义动作。但它始终是您编写的代码。)

    当您修剪文字时,您需要处理不可能的情况,因为用户没有遵守缩进要求。你需要对制表符做一些事情;正确处理这些可能意味着您需要一个词法扫描器来计算可见列位置而不是字符偏移量。

    我不知道 peg.js 是否符合这些要求,因为我不使用它。 (我确实看过文档,但没有看到任何关于如何合并自定义扫描仪功能的迹象。但这并不意味着没有办法做到这一点。)我希望至少上面的讨论让您可以查看要使用的解析器生成器的详细文档,否则可以找到适合您在此用例中使用的其他解析器生成器。

    【讨论】:

      【解决方案2】:

      这是 PEG.js 的 Peggy 继任者中不再维护的 heredocs 的实现。此代码基于GitHub issue

      heredoc = "<<<" begin:marker "\n" text:($any_char+ "\n")+ _ end:marker (
          &{ return begin === end; }
        / '' { error(`Expected matched marker "${begin}", but marker "${end}" was found`); }
      ) {
          const loc = location();
          const min = loc.start.column - 1;
          const re = new RegExp(`\\s{${min}}`);
          return text.map(line => {
              return line[0].replace(re, '');
          }).join('\n');
      }
      any_char = (!"\n" .)
      marker_char = (!" " !"\n" .)
      marker "Marker" = $marker_char+
      
      _ "whitespace"
        = [ \t\n\r]* { return []; }
      

      编辑: 在heredoc 之后,上面的另一段代码无法使用,这里有更好的语法:

      { let heredoc_begin = null; }
      
      heredoc = "<<<" beginMarker "\n" text:content endMarker {
          const loc = location();
          const min = loc.start.column - 1;
          const re = new RegExp(`^\\s{${min}}`, 'mg');
          return {
              type: 'Literal',
              value: text.replace(re, '')
          };
      }
      __ = (!"\n" !" " .)
      marker 'Marker' = $__+
      beginMarker = m:marker { heredoc_begin = m; }
      endMarker = "\n" " "* end:marker &{ return heredoc_begin === end; }
      content = $(!endMarker .)*
      

      【讨论】:

        猜你喜欢
        • 2010-11-25
        • 2012-07-24
        • 2011-10-20
        • 2011-05-03
        • 2010-11-20
        • 2021-06-04
        • 1970-01-01
        • 2010-11-08
        • 2015-04-12
        相关资源
        最近更新 更多