以太:非终结符的诗意设计
夜深了,咖啡馆里只有我一个人,凌乱的草稿纸上满是文法规则。以太,我的以太,我要让你成为一门伟大的语言。现在,最关键的问题之一就是:非终结符,应该怎么写?
以太的非终结符:拒绝平庸
那些教科书上的东西,什么大写字母啊,简直是扼杀创造力!以太,必须有自己的风格。我们追求的是简洁、直观、强大,非终结符的设计必须服务于这个目标。
1. 语义化的命名
拒绝 A、B、C 这种毫无意义的符号。我们要用有意义的名字,让代码自己说话。例如,定义一个表达式,我们可以直接用 expression 这个词。或者,更进一步,使用驼峰命名法,比如 arithmeticExpression,这样就能清晰地表达表达式的类型。
好处:
- 可读性大大提升: 一眼就能看出非终结符的含义,无需查阅文法规则。
- 降低认知负担: 程序员无需在抽象符号和实际概念之间建立映射。
- 代码即文档: 代码本身就清晰地表达了程序的结构。
坏处:
- 名称可能过长: 但这是可以接受的,毕竟可读性更重要。
- 需要规范命名: 团队需要统一的命名规范,以保证一致性。
2. 特定的前缀/后缀
为了更清晰地区分终结符和非终结符,我们可以使用特定的前缀或后缀。例如,所有非终结符都以 $ 开头,或者以 _nt 结尾。我个人更倾向于使用后缀,因为这样可以更自然地阅读代码。
例如:
expression_ntstatement_ntblock_nt
这种方式既能保证可读性,又能明确地区分终结符和非终结符。
3. 表达式的层次结构
表达式是编程语言的核心,它的定义必须清晰、简洁。我们要避免产生二义性,同时又要支持丰富的运算。
传统的做法是使用大量的非终结符来定义优先级和结合性,例如:
expression -> term | expression + term | expression - term
term -> factor | term * factor | term / factor
factor -> identifier | number | ( expression )
这种方式虽然有效,但过于繁琐。在“以太”中,我们要尝试更优雅的方式。
我的想法是,引入一个优先级表,让编译器自动推导出运算符的优先级和结合性。这样,我们只需要定义一个 expression 非终结符,然后指定运算符的优先级即可。
例如:
expression -> identifier | number | expression operator expression | ( expression )
然后,定义一个优先级表:
| 运算符 | 优先级 | 结合性 |
|---|---|---|
* / |
2 | 左结合 |
+ - |
1 | 左结合 |
编译器会根据这张表自动生成语法分析树,从而正确地处理表达式的优先级和结合性。
4. 自定义运算符的影响
允许自定义运算符是一个强大的特性,但也会增加编译器的复杂性。我们需要一种机制,让程序员能够指定自定义运算符的优先级和结合性。
我的想法是,允许程序员在定义运算符时,同时指定其优先级和结合性。例如:
operator ** (priority: 3, associativity: right) { ... }
这样,编译器就能自动将自定义运算符添加到优先级表中,并正确地处理它们的优先级和结合性。
5. 文法规则的书写
“以太”的文法规则应该简洁明了,易于理解。我倾向于使用类似 EBNF 的扩展巴科斯范式,因为它更易于阅读和编写。
例如:
expression = term, { ("+" | "-"), term } ;
term = factor, { ("*" | "/"), factor } ;
factor = identifier | number | "(", expression, ")" ;
这种方式清晰地表达了表达式的层次结构,同时也易于转换为代码。
总结
“以太”的非终结符设计,必须服务于简洁、直观、强大的目标。我们要拒绝学院派的死板教条,大胆创新,创造一种既优雅又高效的语法。让“以太”的代码像诗一样美丽,像闪电一样高效!
现在,我需要更多咖啡,然后把这些想法变成现实!