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