Search code examples
rubytreetop

Why an empty string seems emitted instead of a custom node, sometimes, in a Treetop grammar?


I'd like your advice concerning a recurring problem concerning my usage of Treetop,that I cannot fix...from time to time. I'm probably missing something.

I suspect many of you have the right idiom or habits to solve that.

I generally use Treetop like the following :

  1. I define my grammar in a .tt file
  2. I modify it to emit custom parse tree objets (that inherit Treetop::Runtime::SyntaxNode). These classes are defined in a "parsetree.rb" file.
  3. these custom objects have a to_ast method to turn them recursively into "pure" Treetop-independent classes (that constitute my final AST). I have two separate modules (ParseTree & AST) for that.

However, I hit a classical error message, that I cannot generally fix :

parsetree.rb:380:in `to_ast': undefined method `to_ast' for SyntaxNode
offset=149, "":Treetop::Runtime::SyntaxNode (NoMethodError)

I am puzzled here because an empty string "" seems to be emitted instead of one of my custom nodes.

In this example, on this line 380 I have the following code ( it is about a finite state machine)

# in parsetree.rb
class Next < Tree
      def to_ast
        ret=Ldl::Ast::Next.new
        ret.name=ns.to_ast
        if cond
          ret.condition=cond.c.to_ast
        end
        ret.actions=acts.to_ast # <==== line 380
        ret
      end
    end

 class NextActions < Tree
      def to_ast
        eqs.elements.collect{|eq| eq.to_ast}
      end
    end

And my piece of grammar concerned by the error is :

rule nextstate
    space? 'next' space ns:identifier space? cond:('?' space? c:expression)? space
    acts:next_actions? <Ldl::ParseTree::Next>
end  

rule next_actions
    space? eqs:equation+ space 'end' space <Ldl::ParseTree::NextActions>
end

Solution

  • Your problem is with the behaviour of optional expressions.

    The acts:next_actions is optional. If this optional element is unmatched in the input, you don't get a NextActions node but an epsilon. You should detect that by saying something like:

    ret.actions = acts.empty? ? [] : acts.to_ast
    

    The same problem may occur because the sequence named by the tag cond: is optional. If this sequence doesn't exist in the input, then it has no content "c". In that case "cond" will still be defined and your "if" statement will be true, but cond.c.to_ast will fail.

    Short answer: When you use an optional expression, you should tag it and test the tag for empty? before trying to use the content.