我正在研究一种PEG语法,该语法采用音乐编程语言编写代码,并创建音乐事件(音符,和弦,音量/速度变化等)的解析树。我的MPL的一个功能是它支持声音,即同时发生的不同事件序列。我很难让我的Instaparse语法正确解析它...我想要的是一个由一个或多个voices
组成的voice
标记,每个标记都包含一个语音定义(例如V1:
),然后是任意数量的事件。 voices
标记应以V0:
结尾(这意味着分割声音的结尾,而我们又回到一种声音,即“零语音”),或者文件的结尾。
这是我正在进行的语法的摘录(为清楚起见,我省略了note
,chord
等的定义):
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
被吞噬为第一个voice
的voice-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*"
最大的变化是我定义
part
和event
的方式。之前,我已经定义了这些术语,以使voices
是一个事件,因此所有后续的voice
都将被消耗并集中到先前的voice
的event
中。通过从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