【发布时间】:2014-03-30 22:01:56
【问题描述】:
我正在研究一种 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"]]]]]]]]]
由于某种原因,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 是一个事件,因此任何后续的voices 都被消耗并集中到之前的voice 的events 中。通过将voices 拉出event 的定义并将part 重新定义为voices 分组或events 的可变数量,我消除了歧义并让语法按照我想要的方式运行到。
在那之后,voice 中的 events 分组正确,但是当我需要它们都在同一个 @ 987654364@分组。我通过指定voices 标记以"V0:" 或文件结尾(\z) 结尾来解决此问题,换句话说,更具体地说明了我希望voices 标记消耗多少代码。
这个故事的寓意是,如果您正在编写 PEG 语法并且遇到问题,那么您可能需要让您的定义不那么模棱两可!我最终也完全没有使用否定前瞻,我认为这对简化/消除我的语法有很大帮助。
【问题讨论】:
-
可能是示例中第二行末尾的缺失行终止符(或任何 '\z' 表示)。
-
不会 \z 是字符串的结尾吗?输入作为字符串传入,例如
"V1: o2 b1/>b o2 g+/>g+ o2 g/>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标记有关......看起来他们的行为很贪婪。顺便说一句,我的输出只是神秘地改变了......我将在上面更新。 -
已编辑。现在我的问题更简单了,尽管我仍然感到困惑。