7.4. Using B for Simple Things

OK, so now we have the objects - what can we do with them? B provides accessor methods similar to the fields of the structures in op.h and sv.h. For instance, we can find out the type of the root op like this:

$op=B::main_root; print $op->type;
178
Oops: op_type is actually an enum, so we can't really get much from looking at that directly; however, B also gives us the name method, which is a little friendlier:
$op=B::main_root; print $op->name;
leave
We can also use flags, private, targ, and so on - in fact, everything we saw prefixed by op_ in the B::Debug example above.

What about traversing the op tree, then? You should be happy to learn that first, sibling, next and friends return the B::OP object for the related op. That's to say, you can follow the op tree in execution order by doing something like this:

#!/usr/bin/perl -cl
use B; 
CHECK { 
	  $op=B::main_start; 
	  print $op->name while $op=$op->next;
} 

print $a+$b;
...
      

Except that's not quite there; when you get to the last op in the sequence, the "enter" at the root of the tree, op_next will be a null pointer. B represents a null pointer by the B::NULL object, which has no methods. This has the handy property that if $op is a B::NULL, then $$op will be zero. So we can print the name of each op in execution order by saying:

	  $op=B::main_start; 
	  print $op->name while $op=$op->next and $$op;

Walking the tree in normal order is a bit more tricky, since we have to make the right moves appropriate for each type of op: we need to look at both first and last links from binary ops, for instance, but only the first from a unary op. Thankfully, B provides a function which does this all for us: walkoptree_slow. This arranges to call a user-specified method on each op in turn. Of course, to make it useful, we have to define the method...

#!/usr/bin/perl -cl
use B; 
CHECK { 
      B::walkoptree_slow(B::main_root, "print_it", 0); 
      sub B::OP::print_it { my $self = shift; print $self->name }
} 

print $a+$b;
...
Since all ops inherit from B::OP, this duly produces:
leave
enter
nextstate
print
pushmark
add
null
gvsv
null
gvsv
     
      
We can also use the knowledge that walkoptree_slow passes the recursion level as a parameter to the callback method, and prettify the tree a little, like this:
      sub B::OP::print_it {
          my ($self,$level)=@_;
          print "    "x$level, $self->name
      }
      
leave
    enter
    nextstate
    print
        pushmark
        add
            null
                gvsv
            null
                gvsv
      
    
See how we're starting to approximate B::Terse? Actually, B::Terse uses the B::peekop function, a little like this:
      sub B::OP::print_it {
          my ($self,$level)=@_;
          print "    "x$level, B::peekop($self);
      }
      
LISTOP (0x81142c8) leave
    OP (0x81142f0) enter
    COP (0x8114288) nextstate
    LISTOP (0x8114240) print
        OP (0x8114268) pushmark
        BINOP (0x811d920) add
            UNOP (0x8115840) null
                SVOP (0x8143158) gvsv
            UNOP (0x811d900) null
                SVOP (0x8115860) gvsv
      
      
All that's missing is that B::Terse provides slightly more information based on each different type of op, and that can be easily done by putting methods in the individual op classes: B::LISTOP, B::UNOP and so on.

Let's finish off our little compiler - let's call it B::Simple - by turning it into a module that can be used from the O front-end. This is easy enough to do in our case, once we remember that compile has to return a callback subroutine reference:

package B::Simple;
use B qw(main_root peekop walkoptree_slow);

sub B::OP::print_it {
    my ($self,$level)=@_;
    print "    "x$level, peekop($self);
}

sub compile {
    return sub { walkoptree_slow(main_root, "print_it", 0); }
}

1;
If we save the above code as B/Simple.pm, we can run it on our own programs with perl -MO=Simple .... We have a backend compiler module!