【问题标题】:PEG grammar not working as expectedPEG 语法未按预期工作
【发布时间】:2014-03-30 22:01:56
【问题描述】:

我正在研究一种 PEG 语法,该语法采用音乐编程语言中的代码并创建音乐事件(音符、和弦、音量/速度变化等)的解析树。我的 MPL 的一个特点是它支持语音,即同时发生的不同事件序列。我很难让我的Instaparse 语法正确地解析它......我想要的是一个由一个或多个voices 组成的voices 标签,每个标签都包含一个语音定义(例如V1:),然后是任意数量的事件。 voices 标记应该以 V0: 结尾(这意味着拆分的声音结束,我们回到只有一个声音,或“声音零”),或者文件的结尾。

这是我正在编写的语法的摘录(为了清楚起见,我省略了notechord 等的定义):

part                    = <ows> event+
<event>                 = chord | note | rest | octave-change |
                          attribute-change | voices |
                          marker | at-marker

voices                  = voice+ 
voice                   = !voices voice-number voice-events? 
                          (<voice-zero> | #"\z")
voice-number            = <"V"> #"[1-9]\d*" <":"> <ows>
<voice-zero>            = <"V0:"> <ows>
voice-events            = !voices event+ 

...

ows                     = #"\s*"

给定以下代码:

V1: o2 b1/>b o2 g+/>g+ o2 g/>g 
V0: e8 f+ g+ a b2

运行解析器会得到以下输出:

[:part 
  [:voices 
    [:voice [:voice-number "1"] 
            [:voice-events 
              [:octave-change "2"] [:chord [:note [:pitch "b"] 
              [:duration "1"]] [:octave-change ">"] [:note [:pitch "b"]]] 
              [:octave-change "2"] [:chord [:note [:pitch "g+"]] 
              [:octave-change ">"] [:note [:pitch "g+"]]] 
              [:octave-change "2"] [:chord [:note [:pitch "g"]]
              [:octave-change ">"] [:note [:pitch "g"]]]]]] 
  [:note [:pitch "e"] [:duration "8"]] 
  [:note [:pitch "f+"]] 
  [:note [:pitch "g+"]] 
  [:note [:pitch "a"]] 
  [:note [:pitch "b"] [:duration "2"]]]

这正是我想要的。 V0: 表示 voices 标记的结束,最后 5 个注释在 part 标记内独立。

但是,当我将 V0 更改为 V2 时,我得到了这个:

[:part 
  [:voices 
    [:voice [:voice-number "1"] 
            [:voice-events 
              [:octave-change "2"] [:chord [:note [:pitch "b"] [:duration "1"]] 
              [:octave-change ">"] [:note [:pitch "b"]]] [:octave-change "2"] 
              [:chord [:note [:pitch "g+"]] [:octave-change ">"] 
              [:note [:pitch "g+"]]] [:octave-change "2"] 
              [:chord [:note [:pitch "g"]] [:octave-change ">"] 
              [:note [:pitch "g"]]] 
              [:voices 
                [:voice [:voice-number "2"] 
                [:voice-events 
                  [:note [:pitch "e"] [:duration "8"]] [:note [:pitch "f+"]] 
                  [:note [:pitch "g+"]] [:note [:pitch "a"]] 
                  [:note [:pitch "b"] [:duration "2"]]]]]]]]]

由于某种原因,voice 1 标记或其voice-events 标记未按预期终止,第二个voice 被吞并为第一个voicevoice-events 的一部分.我也不希望有第二个voices 标签; voice 2 应该在主要的 voices 标记内。

我想要的是这个:

[:part 
  [:voices 
    [:voice [:voice-number "1"] 
            [:voice-events 
              [:octave-change "2"] [:chord [:note [:pitch "b"] [:duration "1"]] 
              [:octave-change ">"] [:note [:pitch "b"]]] [:octave-change "2"] 
              [:chord [:note [:pitch "g+"]] [:octave-change ">"] 
              [:note [:pitch "g+"]]] [:octave-change "2"] 
              [:chord [:note [:pitch "g"]] [:octave-change ">"] 
              [:note [:pitch "g"]]]]]
    [:voice [:voice-number "2"] 
            [:voice-events 
              [:note [:pitch "e"] [:duration "8"]] [:note [:pitch "f+"]] 
              [:note [:pitch "g+"]] [:note [:pitch "a"]] 
              [:note [:pitch "b"] [:duration "2"]]]]]]

我不知道我做错了什么,但我认为这与我如何定义 voice 标记和/或 voice-events 标记有关。这可能与我如何使用负前瞻有关,我认为我还没有完全理解。谁能弄清楚我该如何修正我的语法?

谢谢! :)

解决了!

谢谢,@DanielNeal!我已经重新设计了我的语法,这正是我想要的方式:

part                    = <ows> (voices | event)+
<event>                 = chord | note | rest | octave-change |
                          attribute-change | marker | at-marker

voices                  = voice+ (<voice-zero> | <#"\z">)
voice                   = voice-number event*
voice-number            = <"V"> #"[1-9]\d*" <":"> <ows>
<voice-zero>            = <"V0:"> <ows>

...

ows                     = #"\s*"

最大的变化在于我如何定义partevent;之前,我定义了这些术语,使得voices 是一个事件,因此任何后续的voices 都被消耗并集中到之前的voiceevents 中。通过将voices 拉出event 的定义并将part 重新定义为voices 分组或events 的可变数量,我消除了歧义并让语法按照我想要的方式运行到。

在那之后,voice 中的 events 分组正确,但是当我需要它们都在同一个 @ 987654364@分组。我通过指定voices 标记以"V0:" 或文件结尾(\z) 结尾来解决此问题,换句话说,更具体地说明了我希望voices 标记消耗多少代码。

这个故事的寓意是,如果您正在编写 PEG 语法并且遇到问题,那么您可能需要让您的定义不那么模棱两可!我最终也完全没有使用否定前瞻,我认为这对简化/消除我的语法有很大帮助。

【问题讨论】:

  • 可能是示例中第二行末尾的缺失行终止符(或任何 '\z' 表示)。
  • 不会 \z 是字符串的结尾吗?输入作为字符串传入,例如"V1: o2 b1/&gt;b o2 g+/&gt;g+ o2 g/&gt;g\nV2: e8 f+ g+ a b2"
  • 是的,很抱歉。实际上你的语法对于V1-9 是模棱两可的——语音的语法生产允许空的语音事件,这正是你观察到的。 V0 不会出现此问题,它是您输入的最后一个 V 子句。
  • 这是故意的——我想允许像"V1: c d e\nV2:" 这样的情况,我想将其解析为[:part [:voices [:voice [:voice-number "1"] [:voice-events [:note [:pitch "c"]] [:note [:pitch "d"]] [:note [:pitch "e"]]]] [:voice [:voice-number "2"]]]]。无论如何,我尝试通过删除? 来强制设置voice-events,但它没有任何区别。我认为我的问题与没有正确终止 voice 和/或 voice-events 标记有关......看起来他们的行为很贪婪。顺便说一句,我的输出只是神秘地改变了......我将在上面更新。
  • 已编辑。现在我的问题更简单了,尽管我仍然感到困惑。

标签: parsing clojure peg


【解决方案1】:

我认为你是对的 - 导致问题的是负面的前瞻性。 没有你完整的语法,我无法正确测试,但是这一行:

voice-events = !voices event+ 

表示与voices不匹配的东西后跟一个或多个events

我假设 voice-events 不应该以递归方式在其中包含 voices,但目前它确实 - 间接地。每个event 可以包含voices,而voice-events 可以包含events

在上面的示例中,V1 中的第一个事件是八度移位(匹配非语音条件)。这允许随后发生的语音在event 定义中被使用。如果这是有道理的。

要解决这个问题,您可以(也许)反过来定义它:

voice-event = chord | note | rest | octave-change | attribute-change | marker | at-marker
event       = voice-event | voices

【讨论】:

  • 啊哈!这很有意义——谢谢你的解释。我想我误解了负前瞻的工作原理。我将重新定义我的语法,使其类似于上述内容,并在今晚尝试。我觉得这会解决它。
  • 是的,成功了!我还必须做一些其他的调整,以使我的语法不那么模棱两可。如果您好奇,我将工作版本发布为对我的问题的编辑。再次感谢您的帮助!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-03-05
  • 2017-10-27
  • 1970-01-01
  • 2012-08-16
  • 2020-03-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多