21
votes

Reading and watching this presentation: http://boost-spirit.com/home/2011/06/12/ast-construction-with-the-universal-tree/
I've discovered this statement -- basically we are suggested not to use semantic actions.

I must admit, that I've already felt something like that: grammars with semantic actions actually look kinda ugly. and, when I needed to extend/change them, it took a lot of "micromanagement" exactly with semantic actions. The approach with attribute grammar, demonstrated in the presentation, seems to be much more elegant and promising.

So I'd like to ask: is this is an "official" point? Should I learn how to work with attribute grammar and avoid semantic actions in more detail? If so -- I'd like to = ask for some basic (maybe even trivial) examples, demonstrating such an approach -- the LISP interpreter is too complex for me to chew...

1
Best asked on the Spirit-General list...sehe
What "LISP interpreter"? is there a LISP interpreter written in Spirit somewhere?alfC

1 Answers

28
votes

I'm sure Hartmut will answer in a second. Till then, this is my take:

No that is not an official point.

Semantic actions have some drawbacks

  • The simplest disadvantage of semantic actions is the stylistic notion of separation of concerns. You want to express syntax in one place, and semantics in another. This helps maintainability (especially with regards to the lengthy compile times for compiling Spirit Grammars)

  • More complicated implications if they have side-effects (which is frequently the case). Imagine backtracking from a parsed node when the semantic action had a side-effect: the parser state will be reverted, but the external effects aren't.

    In a way, using attributes only is like using deterministic, pure functions in a functional program, it is easier to reason about the correctness of a program (or, in this case the grammar state machine) when it is composed of pure functions only.

  • Semantic actions have a tendency (but not necessarily so) to introduce more copying around by value; this, in combination with heavy backtracking, could reduce performance. Of course, if the semantic action is 'heavy' this, in itself, is going to hinder performance of parsing.


Semantic actions are good for various purposes. In fact, if you need to parse non-trivial grammars with context sensitivity you cannot escape them.

  1. Consider the use of qi::locals<> and inherited attributes (code from the Mini XML - ASTs! sample) - they involve semantic actions:

    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
    ;
    

    Or one using 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
    

    IMO, these semantic action pose less of a problem usually, because when they get backtracked, the next time execution passes (the same) semantic action, the local will just get overwritten by the new, correct, value.

  2. Also, some jobs are really 'quick-and-dirty' and don't warrant the use of utree or a hand-rolled AST type:

     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
     );
    

    Here, the semantic action is the core of the parsers function. It works, because no backtracking is involved at the level of nodes with semantic actions.

  3. The semantic actions are a mirror-image of semantic actions in Spirit Karma, where they usually pose less of the problems than in Qi; so even if only for interface/API consistency, semantic actions are 'a good thing' and enhance the usability of Boost Spirit as a whole.