Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:

  This is my first post on perl monks :)

My problem -> i need to make my own custom if statement. In particular, I need an if statment that, if the condition holds, performs like a normal if statment. However if the condition fails, I still need it to go through the if body and output the functions that were not executed.

So basically, any information on writing custom-made control flow statements, or even just to let me know if this is possible, would be greatly appreciated.

Many thanks

Replies are listed 'Best First'.
Re: Making my own control flow structure
by Corion (Patriarch) on Oct 09, 2006 at 12:25 UTC

    In Perl, there are no custom-made control flow statements like in Lisp, and there are no convenient ways to dump the statements not executed. You can fudge something like it by using eval, in the following way:

    sub document_if { my ($cond,$code) = @_; if ($cond) { eval $code } else { print "Not executing:\n"; print $code; print "\n---\n"; }; };

    Which you can then clumsily use like this:

    document_if( 1 == 0, q{ print "The sky has fallen down.\n"; });

    There are a lot of drawbacks to this, as you can't conveniently pass variables into the if block for example. The upside is that the format of the code and the comments will still be output.

    The second alternative would be to pass a real code block and then use B::Deparse to get at a string representation:

    use strict; use B::Deparse; my $deparse = B::Deparse->new("-p", "-sC"); sub document_if { my ($cond,$code) = @_; if ($cond) { $code->() } else { print "Not executing:\n"; print $deparse->coderef2text($code); print "\n---\n"; }; };

    Which you can then use like this, the clumsyness gets pushed into a different corner here:

    document_if( 1 == 0, sub{ print "The sky has fallen down.\n"; });

    This has the advantage of passing around closures and hence working with local and lexical variables. It has the disadvantage of relying on B::Deparse which doesn't always give you back what you wrote but only gives you back the Artists Rendition.

Re: Making my own control flow structure
by jbert (Priest) on Oct 09, 2006 at 13:15 UTC
    The only way I can think of to do this in perl is with a source filter. This allows you to effectively re-write the perl to do whatever you want (think lisp macros, but without the regularity of lisp s-exps, hence more complex).

    In your case, you could introduce a new keyword (say 'tif' for tracked-if) and rewrite:

    tif(xxx) { some; perl; code; }
    if(xxx) { some; perl; code; } else { print some; print perl; print code; }
    The CPAN Filter::Simple module looks like a good place to start, but I don't really have any experience writing these things, to be honest.
Re: Making my own control flow structure
by dtr (Scribe) on Oct 09, 2006 at 17:01 UTC

    Have you ever seen the Runops::Switch module (available from the CPAN).

    That module allows you to override the way that perl interprets its opcodes.

    I *imagine* that you could combine the techniques used in that module, together with the B::Deparse module in order to do what you want.

    From what I remember, all "if" and "unless" statements in perl are internally translated into "AND" (or "OR") statements. So:- if(i > 3) { print "bob" } is internally the same as i > 3 && print "bob". So you probably want to start by looking at overriding the OP_AND opcode....

Re: Making my own control flow structure
by Ryszard (Priest) on Oct 09, 2006 at 12:40 UTC
    mmmm interesting question, care to share the problem you are trying to solve? TIMTOWTDI!
      I am writing a scripting language for test automation of a certain product for a certain company. One of the features of the language is that every function, whether or not it is executed should output something so it can be correctly audited by a test auditor. So there may be situations such as if(condition1){ doAction(1); } now in this situation, even if condition1 is not true, I still need doAction to print something out saying that it is not running because of condition1. I know I could do ... else{ print "Not doing doAction(1) because of condition1\n"; } but that is not sufficient for what I need. It's not standard, not auditable, and leaves the output up to the scripter (the language is supposed to be usable by non scripters) Hope that kinda explains my problem
        ok, so how about this:
        if ($condition) { &doAction; } else { print "doAction not executed due to a failure of $condition\n"; }
        if you have a whole bunch of $conditions then you could easily pregenerate the codeblock. cool, dynamic code... !

        If you're writing a scripting language, then it's easy.

        Just write your if keyword in such a way that when a condition is not true you walk the AST of the then block and dump it as 'Not doing this, that or the other because of condition1', or whatever you need.

        But, more generally, this seems to be a barking mad thing to want to do. What are you going to do about nested ifs? Case statements? Loops? "None of these functions got executed because the loop terminated". If you continue down that road you could well end up with a situation where the audit information swamps the test output, which is in nobody's best interest. Any auditor worth their pay is going to be able to do a far better job of auditing by reading the code than they are by examining the entrails of a run trace.

        How about "None of these functions got run, because nobody executed the script"?

Re: Making my own control flow structure
by dtr (Scribe) on Oct 09, 2006 at 21:36 UTC

    Just another quick thought - have you ever seen the Devel::Cover module?

    It's designed for something completely different - which is showing which parts of your code have been accessed whilst running your test suite. However, it does produce very nice looking html, showing you which branches of your code have been followed, etc.

    This might just do the job!

Re: Making my own control flow structure
by traveler (Parson) on Oct 09, 2006 at 22:06 UTC
    Some (semi-) random thoughts that might help:
    • If you can limit your language to only contain functions as the bodies of ifs, things should be easier
    • You could use a source filter or perhaps the CPP to translate ifdo expr function(args) to something like if(expr){function(1,args)}else {function(0,args)} and then use one or more of the Aspect:: modules to strip out the first arg and print an appropriate message. Or you could put a print in the else if that would be alright.
    • If you want to see the unexecuted code you could use a source filter to store it away and then print it out instead of deparsing it. That way you get the actual code.
    • You could write a translater from scratch to translate your language into perl and then run the result :-)
Re: Making my own control flow structure
by Argel (Prior) on Oct 10, 2006 at 01:06 UTC
    If by functions in the body you mean subroutines then use the if statement to set a flag (e.g. $ok=1) and then test for it in each subroutine.