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; 178Oops: 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; leaveWe 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 gvsvWe 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 gvsvSee 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) gvsvAll 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!