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

Hi Perl Monks,

I am using a range operator in matching #if(or)#ifdef(or)#ifndef to the corresponding #endif directive and remove the code in between them. If I have a nested #if directive my perl code doesn't work.

Here with I am posting a C code snippet

{ #if 1 #define done 9 #define do 10 #ifdef #define def1 11 #define thread 12 #endif #define mutex 13 #define inter 14 #endif #define intel 15 #define intel_64 16 }
from the current perl code the output is like this
{ #define mutex 13 #define inter 14 #endif #define intel 15 #define intel_64 16 }
but actually my output has to be like this
{ #define intel 15 #define intel_64 16 }
here is my perl code please help me out in tracking the nested #if using the range operator,
my ($start, $stop) = (qr '#ifdef|#if|#ifndef|#else', qr '#endif'); open(file1,$file) or die "Error opening input: " . $!; open(file2, ">$filea") or die "Error opening input: " . $!; while (<file1>) { if (/$start/ .. /$stop/) { next; } else { print file2 $_ ; } }

Thanks a lot,

-Prassi

Replies are listed 'Best First'.
Re: nested #if directive matching matching
by tobyink (Canon) on Jun 26, 2012 at 10:04 UTC

    You want to use a stack.

    A stack is an abstract concept in programming. It is basically a list that you can only access from the tail end. You can push things onto the stack, and pop the item on the top off the stack, and you can detect when the stack is empty.

    As I said, stacks are an abstract concept; you need a concrete realisation of them. In Perl, that's an array. Perl helpfully provides built-in push and pop functions, and makes it easy to check if an array is empty.

    This is the basic technique you want...

    my @if_stack; while (<$input>) { if (/$start/) { push @if_stack, $_; next; } if (/$stop/) { die unless @if_stack; pop @if_stack; next; } if (@if_stack) { # got a line which is inside a conditional } else { # got a line which is not inside a conditional print $output $_; } }

    In fact, in this case you could get away with using a data structure even simpler than a stack - a counter will do. You simply pretend you've got an imaginary stack, and use a counter that keeps track of how many items are on the stack. Instead of pushing onto the stack, increment the counter; instead of popping, decrement.

    my $if_stack; while (<$input>) { if (/$start/) { $if_stack++; next; } if (/$stop/) { die unless $if_stack; $if_stack--; next; } if ($if_stack) { # got a line which is inside a conditional } else { # got a line which is not inside a conditional print $output $_; } }

    I maintain that the stack is the better solution because you can, for example, print a dump of the current stack when you detect an error, which can help in identifying where things have gone wrong. That said, if you're crunching though vast quantities of text, the counter will probably perform slightly faster.

    perl -E'sub Monkey::do{say$_,for@_,do{($monkey=[caller(0)]->[3])=~s{::}{ }and$monkey}}"Monkey say"->Monkey::do'
      Hi Tobyink,

      your code fails if the code encounters #else

      Regards,

      -Prassi

        No it doesn't. I just neglected to post my version of $start and $stop.

        perl -E'sub Monkey::do{say$_,for@_,do{($monkey=[caller(0)]->[3])=~s{::}{ }and$monkey}}"Monkey say"->Monkey::do'
Re: nested #if directive matching matching
by choroba (Cardinal) on Jun 26, 2012 at 10:09 UTC
    The range operator is not going to help you much in this case (if you do not want to rely on indentation which I do not suggest). But you can simply count the depth of nesting the if's:
    #!/usr/bin/perl use warnings; use strict; my $depth; while (<DATA>) { /#ifdef|#if|#ifndef/ and $depth++; print unless $depth; /#endif/ and $depth--; # this comes after the print not to be prin +ted } __DATA__ { #if 1 #define done 9 #define do 10 #ifdef #define def1 11 #define thread 12 #endif #define mutex 13 #define inter 14 #endif #define intel 15 #define intel_64 16 }

      choroba:

      Actually, you *do* need a stack if you're going to properly handle the #else clauses. Otherwise, you won't remember the previous value(s) of the print vs. don't print states.

      #if 0 this shouldn't print #if 1 nor should this #else nor this #end #else but this should #if 1 as should this #else but not this #end This should print, but where are you storing the information to know it without a stack? #else If you got that one right, howzabout this one? #end

      ...roboticus

      When your only tool is a hammer, all problems look like your thumb.

        That was not part of the spec :-)

      You probably don't want #else to increment $depth.

      perl -E'sub Monkey::do{say$_,for@_,do{($monkey=[caller(0)]->[3])=~s{::}{ }and$monkey}}"Monkey say"->Monkey::do'
        Thanks, removed. I took it from the OP, and it is not present in the test data :-)

        BTW, /#if/ matches both /#ifdef/ and /#ifndef/...

Re: nested #if directive matching matching
by Anonymous Monk on Jun 26, 2012 at 10:17 UTC
Re: nested #if directive matching matching
by roboticus (Chancellor) on Jun 26, 2012 at 12:19 UTC

    prassi:

    Since 1 is true, I'd expect to get exactly what you're getting. If you change it to "#if 0" do you get what you were wanting?

    Update: The quick hack I mentioned previously used the stack (as does tonyink's reply). It mostly wanted a way to handle #include files for getting the definitions of other #defines & such.

    ...roboticus

    When your only tool is a hammer, all problems look like your thumb.

      Hi Roboticus,

      In my actual code of .h file I am interested to extract only those #define which are out of any directive statement, so it is either #if 1 or #if 0 it has to be ignored. Yes even "#if 0" the code works

      Regards, -Prassi