从样本文件中,您将需要根据要从这些例子中得出的结论来做出决策。假设您有以下三个示例:(每个都是一个单独的文件)
f() {}
f(a,b) {b+a}
int x = 5;
您可以简单地指定两个将接受这些样本的语法:
平凡的语法一:
start ::= f() {} | f(a,b) {b+a} | int x = 5;
平凡的语法二:
start ::= tokens
tokens ::= token tokens | <empty>
token ::= identifier | literal | { | } | ( | ) | , | + | = | ;
第一个很简单,因为它仅接受三个样本。第二个是微不足道的,因为它接受所有可能使用这些令牌类型的东西。[在此讨论中,我假设您不太关心令牌生成器的设计:假设标识符,数字和标点符号作为令牌很简单,您可以从任何脚本语言中借用任何令牌集。无论如何。]
因此,您需要遵循的过程是从高级别开始,然后确定“我要允许每个实例有多少个?” 如果语法构造可以重复任意多次(例如method
在类中使用s),那么您将希望使用以下形式的规则:
methods ::= method methods | empty
在EBNF中最好将其表示为:
methods ::= {method}
当您只需要零个或一个实例(这意味着该构造是可选的,如extends
Java类的子句),或者您想要允许一个或多个实例(如声明中的变量初始值设定项)时,这可能会很明显。)。您需要注意一些问题,例如要求在元素之间使用分隔符(如,
在参数列表中),在每个元素之后要求使用终止符(如使用;
to split语句)或不需要使用分隔符或终止符(视情况而定)与类中的方法)。
如果您的语言使用算术表达式,则很容易从现有语言的优先规则中进行复制。最好坚持使用众所周知的东西,例如C的表达式规则,而不是追求异国情调,但前提是所有其他条件都相等。
除了优先级问题(相互解析的内容)和重复性问题(每个元素应该出现多少个,如何将它们分开?)之外,您还需要考虑顺序:必须总是在某些事物之前出现某些事物吗?如果包括一件事,是否应该排除另一件事?
此时,您可能会倾向于语法上强制执行某些规则,例如,如果Person
指定了的年龄,则您也不想再指定其生日。尽管您可以构造语法,但是在解析完所有内容之后,您可能会发现通过“语义检查”通过可以更轻松地实施此语法。我认为这样可以使语法更简单,并且在违反规则时可以提供更好的错误消息。