提升精神:"Semantic actions are evil"?

Boost Spirit: "Semantic actions are evil"?

本文关键字:actions are evil Semantic      更新时间:2023-10-16

阅读和观看这个演讲:http://boost-spirit.com/home/2011/06/12/ast-construction-with-the-universal-tree/
我发现了这句话——基本上我们被建议不要使用语义动作。

我必须承认,我已经有了这样的感觉:带有语义动作的语法实际上看起来有点丑。而且,当我需要扩展/更改它们时,需要对语义操作进行大量的"微观管理"。在演示文稿中演示的使用属性语法的方法似乎更加优雅和有前途。

所以我想问:这是一个"官方"的观点吗?我是否应该学习如何使用属性语法并更详细地避免语义操作?如果是这样的话——我想要求一些基本的(甚至是微不足道的)例子来演示这种方法——LISP解释器太复杂了,我无法咀嚼……

我相信哈特穆特马上就会回答。在此之前,这是我的看法:

这不是一个正式的点。

语义动作有一些缺点

  • 语义动作最简单的缺点关注点分离的风格概念。您希望在一个地方表示语法,而在另一个地方表示语义。这有助于可维护性(特别是关于编译Spirit语法的冗长编译时间)

  • 如果它们有副作用(这是经常发生的情况),则会产生更复杂的影响。想象一下,当语义操作具有副作用时,从已解析节点回溯:解析器状态将被恢复,但外部效果不会。

    在某种程度上,仅使用属性就像在函数式程序中使用确定性的纯函数一样,当程序仅由纯函数组成时,更容易推断程序的正确性(或者,在本例中是语法状态机)。

  • 语义行为倾向于(但不一定如此)通过值引入更多的复制;这与大量回溯相结合,可能会降低性能。当然,如果语义动作很"重",这本身就会阻碍解析的性能。


语义动作适用于各种用途。事实上,如果您需要解析具有上下文敏感性的重要语法,则无法转义它们。

  1. 考虑使用qi::locals<>继承属性(来自Mini XML - ASTs!示例的代码)—它们涉及语义操作:

    xml =
            start_tag                   [at_c<0>(_val) = _1]
        >>  *node                      
        >>  end_tag(at_c<0>(_val)) // passing the name from the 
                                   // ... start_tag as inherited attribute
    ;
    

    或者使用qi::locals:

    rule<char const*, locals<char> > rl;
    rl = alpha[_a = _1] >> char_(_a); // get two identical characters
    test_parser("aa", rl); // pass
    test_parser("ax", rl); // fail
    
    在我看来,这些语义操作通常不会造成什么问题,因为当它们被返回时,下一次执行通过(相同的)语义操作时,局部将被新的、正确的值覆盖。
  2. 另外,有些作业真的是"又快又脏",不需要使用或手工滚动的AST类型:

     qi::phrase_parse(first, last, // imagine qi::istream_iterator... 
         intesting_string_pattern  // we want to match certain patterns on the fly
                [ log_interesting_strings ], // and pass them to our logger
         noise_skipper             // but we skip all noise
     );
    
    在这里,语义动作是解析器函数的核心。它是有效的,因为在节点的语义操作层面没有回溯。
  3. 语义行为是灵业语义行为的镜像,它们通常比气造成的问题要少;因此,即使只是为了接口/API的一致性,语义操作也是"一件好事",并提高了Boost Spirit作为一个整体的可用性。

相关文章: