我正在研究一种 PEG 语法,该语法采用音乐编程语言中的代码并创建音乐事件的解析树(音符、和弦、音量/速度变化等)。我的 MPL 的一个特点是它支持语音,即同时发生的不同事件序列。我很难让我的Instaparse语法正确地解析它……我想要的是一个voices由一个或多个voices 组成的标签,每个标签都包含一个语音定义(例如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"]]]]]]]]]
出于某种原因,voice1 标签或其voice-events标签没有像预期的那样终止,第二个标签作为第一个标签的voice一部分被吞没。我也不希望有第二个标签;2 应该在主标签内。voicevoice-eventsvoicesvoicevoices
我想要的是这样的:
[: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标签有关。这可能与我如何使用负前瞻有关,我认为我还没有完全理解。谁能弄清楚我如何修复我的语法?
谢谢!:)
解决了!
谢谢,@丹尼尔尼尔!我已经重新编写了我的语法,这正是我想要的方式:
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的 s 都被消耗并归入前一个voicesevent中。通过voices退出 an 的定义event并重新定义part为可变数量的voices分组或events,我消除了歧义并让语法按照我想要的方式运行。
之后,其中的eventsa被正确分组,但是当我需要它们都在同一个分组中时voice,我仍然遇到每个声音都在其自己的单独标签中的问题。我通过指定标签以 a或文件结尾 ( ) 来解决此问题,换句话说,更具体地说明了我希望标签使用多少代码。voicesvoicesvoices"V0:"\zvoices
这个故事的寓意是,如果你正在编写一个 PEG 语法并且你遇到了问题,你可能需要让你的定义不那么模棱两可!我最终也完全没有使用否定前瞻,我认为这对简化/消除我的语法有很大帮助。