使用 PEG.js 解析缩进级别

12

我基本上有和这个问题一样的问题,但我希望在这个答案方面得到更多的指导。

该答案成功地生成了一个字符串数组,其中每行输入都包含“INDENT”和“DEDENT”。它似乎主要使用 PEG.js 进行令牌化,但没有进行真正的解析。

那么,我如何扩展他的示例以进行一些实际的解析呢?

举个例子,我如何改变这个语法:

start = obj
obj = id:id children:(indent obj* outdent)?
    {
        if (children) {
            let o = {}; o[id] = children[1];
            return o;
        } else {
            return id;
        }
    }
id = [a-z]
indent = '{'
outdent = '}'

如何使用缩进代替花括号来划分代码块,并且仍然得到相同的输出?

(使用http://pegjs.majda.cz/online测试该语法,输入:a{bcd{zyx{}}})


请提供一个示例,你想要输入的样子。抱歉,这不是翻译。 - Nils Lindemann
2个回答

21

解析器:

// do not use result cache, nor line and column tracking

{ var indentStack = [], indent = ""; }

start
  = INDENT? l:line
    { return l; }

line
  = SAMEDENT line:(!EOL c:. { return c; })+ EOL?
    children:( INDENT c:line* DEDENT { return c; })?
    { var o = {}; o[line] = children; return children ? o : line.join(""); }

EOL
  = "\r\n" / "\n" / "\r"

SAMEDENT
  = i:[ \t]* &{ return i.join("") === indent; }

INDENT
  = &(i:[ \t]+ &{ return i.length > indent.length; }
      { indentStack.push(indent); indent = i.join(""); pos = offset; })

DEDENT
  = { indent = indentStack.pop(); }

输入:

a
  b
  c
  d
    z
    y
    x

输出:

{
   "a": [
      "b",
      "c",
      {
         "d": [
            "z",
            "y",
            "x"
         ]
      }
   ]
}

它无法解析空对象(最后一个x),不过这很容易解决。关键在于SAMEDENT规则,当缩进级别未改变时,它会成功。 INDENTDEDENT会改变当前缩进级别,但不会改变文本中的位置pos = offset


我非常感激这个答案。请问您是如何想出这种方法的? - jiyinyiyong
如果我直接复制/粘贴到http://pegjs.majda.cz/online中,它无法编译。经过一些调整后,也不清楚如何“修复”它。 - Clearly
刚刚测试了一下,这段代码片段编译并且产生了预期的输出结果。不确定你遇到了什么错误。 - chakrit
1
这个如何处理回溯情况?如果你有一组具有多个可能子表达式的优先规则,那么在第一个子表达式中进行一系列合法匹配后,indent.stack不会被搞乱吗?接着第二个子表达式被解析时会出现混乱。 - Elf Sternberg
我在 http://pegjs.majda.cz/online (pegjs v 0.8) 尝试了这个,但出现了错误,但在 http://peg.arcanis.fr/1PvOax/ (pegjs v 0.7) 上可以工作。 - joeriks
1
@joeriks:我已经修复了,可以在v0.8.0上运行,如果还有帮助的话。 - Boaz Yaniv

5

2021更新

这是一个在在线 playground上运行的可工作示例,使用了Peggy.js。Peggy.js是活跃开发中的PEG.js的分支。PEG.js已被David Maida停止维护。

该示例展示了如何解析INDENTSAMEDENTDEDENT规则以及如何使用解析位置。请查看控制台日志。

它使用了其他解析器生成器可能不知道的这些语法:

(文件顶部)

  • {{...}}(全局初始化器)- 在解析器生成时运行...
  • {...}(每次解析初始化器)- 在解析器实例化时运行...

(文件内)

  • X {...}(操作)- 当 X 成功时执行 ...。可以使用初始值设定项中的变量。如果 ... 返回了某些内容,则会替换 X 的返回值。
  • $X - 返回使用 X 解析的原始文本,而不是 X 的结果。
  • ... @X ...(pluck 运算符)- 用 X 的结果替换 ... X ... 的结果。
  • X &{...}(谓词)- "X 的成功还需要满足 ... 的条件"。
  • X = &(...) - 如果 ... 成功,则 X 成功。 ... 不消耗任何输入。

请参阅 docs 以获取更多信息。

{{
    console.clear()
    console.log('Parser generated')
}}

{
    let indentstack = []
    let indent = ''
    function found (what) {
        let loc = location()
        console.log(`[${loc.start.line}:${loc.start.column} - ${loc.end.line}:${loc.end.column}] found ${what}`)
    }
    console.log('Parser instantiated')
}

DOCUMENT = NEWLINES? @THINGS NEWLINES? _

THINGS = ( SAMEDENT @( OBJECT / LINE ) )*

OBJECT = key:KEY childs:(BLOCK / INLINE) {
    found(`object "${key}"`)
    let o = {}
    o[key] = childs
    return o
}

KEY = @$( [^ \t\r\n:]+ ) _ ':' _

BLOCK = NEWLINES INDENT @THINGS DEDENT

INLINE = line:LINE { return [line] }

LINE = text:$( (!EOL .)+ ) NEWLINES? {
    found(`line "${text}"`)
    return text
}

INDENT = &(
    spaces:$( [ \t]+ ) &{
        return spaces.length > indent.length
    } {
        indentstack.push(indent)
        indent = spaces
    }
) {
    found('indent')
}

SAMEDENT = spaces:$( [ \t]* ) &{
    return spaces === indent
} {
    found('samedent')
}

/* Because of this rule, results cache must be disabled */
DEDENT = &{
    indent = indentstack.pop()
    return true
} {
    found('dedent')
}

_ = [ \t]*
EOL = '\r\n' / '\n' / '\r'
NEWLINES = (_ EOL)+

/* Test with this input

H:
  a
  b
  c
  G:
    d
    e
    f

*/

旧答案

这是对@Jakub Kulhan语法的修复,适用于PEG.js v 0.10.0。最后一行需要更改为= &{ indent = indentStack.pop(); return true;},因为PEG.js现在不再允许在语法中使用独立的动作({...})。这一行现在是一个谓词(&{...}),它总是成功的(return true;)。

我还删除了pos = offset;,因为它会产生一个错误offset is not defined。可能Jakub是指旧版本的PEG.js中可用的一些全局变量。PEG.js现在提供了location()函数,该函数返回一个包含偏移量和其他信息的对象。

// do not use result cache, nor line and column tracking

{ var indentStack = [], indent = ""; }

start
  = INDENT? l:line
    { return l; }

line
  = SAMEDENT line:(!EOL c:. { return c; })+ EOL?
    children:( INDENT c:line* DEDENT { return c; })?
    { var o = {}; o[line] = children; return children ? o : line.join(""); }

EOL
  = "\r\n" / "\n" / "\r"

SAMEDENT
  = i:[ \t]* &{ return i.join("") === indent; }

INDENT
  = &(i:[ \t]+ &{ return i.length > indent.length; }
      { indentStack.push(indent); indent = i.join(""); })

DEDENT
  = &{ indent = indentStack.pop(); return true;}

从v 0.11.0开始,PEG.js还支持值提取运算符@,这将使编写该语法更加简单,但由于它目前不在在线解析器中,我将不会将其添加到此示例中。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接