我正在研究一种PEG语法,该语法采用音乐编程语言编写代码,并创建音乐事件(音符,和弦,音量/速度变化等)的解析树。我的MPL的一个功能是它支持声音,即同时发生的不同事件序列。我很难让我的Instaparse语法正确解析它...我想要的是一个由一个或多个voices组成的voice标记,每个标记都包含一个语音定义(例如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是一个事件,因此所有后续的voice都将被消耗并集中到先前的voiceevent中。通过从voices的定义中拉出event并将part重新定义为可变数量的voices分组或event,我消除了歧义,使语法表现出我想要的方式至。
之后,events中的voice被正确地分组了,但是当我需要它们都属于同一个voices分组时,我仍然有一个问题,每个声音都在自己的单独voices标签中。我通过指定voices标记以"V0:"或文件(\z)的结尾来解决此问题,换句话说,是更具体地说明我希望voices标记消耗多少代码。
这个故事的寓意是,如果您正在编写PEG语法而遇到问题,则可能需要使定义变得不太含糊!我也最终根本没有使用否定的前瞻,这对简化/消除语法歧义很有帮助。

最佳答案

我认为您是对的-造成问题的原因是负面的前瞻。
没有完整的语法,我将无法正确测试,但以下内容:

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

09-05 04:55