Running in the compile time

作者:   發佈於:  

Yesterday I made this snippet runnable in Perl5:

Foo->bar(42) {
    say "fubar";
};

The {} bare-block there is the last argument to the invoked bar. In this case, the method bar receives 3 parameters: the context (Foo class), the 42, and a code ref.

Since this is basically pretty rubyish, I made it into Rubyish in this commit.

If you're interested in how to make it possible, read on.

The implementation requires B::OPCheck, B::Hooks::Parser, and B::Hooks::EndOfScope.

With B::OPCheck, you can ask the compiler to call your callback when it visits to a certain type of OP, which is basically a node in the parsed syntax tree. By saying this:

use B::OPCheck const => check => \&callback;

Your callback sub-routine will be invoked whenever a const OP is visited. Basically that means any bare-words, including sub-routine name in both declaration, definition, and invocation. Therefore, when perl reads to the point of bar in Foo->bar, it calls the callback. The even greater fact is, the bar is not compiled yet, so you can modified it.

To know what can be hooked besides const, checkout the perl source code, and read opnames.h.

To modify it, you use either B::Hooks::Parser, or Devel::Declare. The former is like a reduced version of Devel::Declare, but very useful.

The function B::Hooks::Parser::get_linestr can get the current line of code that's being parsed, and B::Hooks::Parser::get_linestr_offset can give you the position in the current line that has not been parsed yet. You can then modify anything afterwards, appending new code to there, and put it back with B::Hooks::Parser::set_linestr. Once the callback is finished, it returns to the perl compiler, and the compiling process goes on.

The whole callback is running in the compile time.

These B::* modules makes it very easy to extend the perl compiler -- don't even need to re-build perl. That's the beauty of it.