use strict;
use warnings;

# Copyright (C) 2016, John Ulvr

# Credits also go to Joel Peshkin who helped write the code


require 5.10.0;

use version; our $VERSION = version->declare("v0.1.2");

package BRCM::UnifdefPlus;
use Storable qw(dclone);

my $showErrs=1;

use Text::Balanced qw (
  extract_delimited
  extract_bracketed
  extract_quotelike
  extract_codeblock
  extract_variable
  extract_tagged
  extract_multiple
  gen_delimited_pat
  gen_extract_tagged
);

#use unique names (TRUE and FALSE can cause the code to change...)

# terminology: 
#       simplified means that a constant expression was resolved (no macros)
#       resolved means that an expression containing at least one known macro, and
#          no unknown macros was resolved.
#       rss is resoved/simplified state

my $RESOLVED_PREFIX = "__unifdef_resolved_";
my $SIMPLIFIED_PREFIX = "__unifdef_simplified_";
my $TRUE_RESOLVED =  $RESOLVED_PREFIX."1";
my $FALSE_RESOLVED = $RESOLVED_PREFIX."0";
my $TRUE_SIMPLIFIED =  $SIMPLIFIED_PREFIX."1";
my $FALSE_SIMPLIFIED = $SIMPLIFIED_PREFIX."0";
my $Y = qw(__unkconfig_y__);
my $M = qw(__unkconfig_m__);
my $N = qw(__unkconfig_n__);
my $COMMENT = qr/^\s*#.*$/;
my $BLANK_LINE = qr/^\s*$/;

my $BRACE_MATCH;
$BRACE_MATCH = qr/ (?: \w++ | \s++ | [\|&!=\"\\]++ | \((??{$BRACE_MATCH})\))* /x;

#RSS refers to resolve-simplify state
use constant RSS_UNCHANGED => 0;    # no macro expansion or expression simplification
use constant RSS_SIMPLIFIED => 1;   # result is a simplified expression (no macros involved)
use constant RSS_RESOLVED => 2;     # result is a resolved expression (at least one macro
                                    #    resolved, and surrounding expressions simplified)

my %rss_strings = (
    RSS_UNCHANGED => "UNCHANGED",
    RSS_SIMPLIFIED => "SIMPLIFIED",
    RSS_RESOLVED => "RESOLVED",
);

my $RESOLVED_PREFIX_PTRN = qr/$RESOLVED_PREFIX/;
my $TRUE_RESOLVED_PTRN =  qr/\b(?:$RESOLVED_PREFIX)[1-9][0-9]*\b/;
my $FALSE_RESOLVED_PTRN = qr/\b(?:$RESOLVED_PREFIX)0\b/;
my $TRUE_SIMPLIFIED_PTRN =  qr/\b(?:$SIMPLIFIED_PREFIX)[0-9][1-9]*\b/;
my $FALSE_SIMPLIFIED_PTRN = qr/\b(?:$SIMPLIFIED_PREFIX)0\b/;

my $TRUE_PTRN =  qr/(?:\b(?:$SIMPLIFIED_PREFIX|$RESOLVED_PREFIX|)[1-9][0-9]*|0x[1-9][0-9]*|TRUE)\b/;
my $FALSE_PTRN = qr/(?:\b(?:$SIMPLIFIED_PREFIX|$RESOLVED_PREFIX|)0|0x0|NULL|FALSE)\b/;


sub getValue($) {
    my $string = shift;
    return $1 if ($string =~ /^(?:$RESOLVED_PREFIX|$SIMPLIFIED_PREFIX)(\d+)$/);
    return $string if ($string =~ /^([1-9][0-9]*)$/);        
    return $string if ($string =~ /^0$/);        
    return hex $string if ($string =~ /^0x[0-9A-Fa-f]+$/);
    return oct $string if ($string =~ /^0[0-9]*$/);
    return 1 if ($string eq "TRUE");
    return 0 if ($string eq "FALSE");
    return "unknown";
}

sub isNumber_($) {
    my $string = shift;
    return getValue($string) ne "unknown";
}

sub trim($) {
    my $string = shift;
    $string =~ s/^\s+//;
    $string =~ s/\s+$//;
    return $string;
}

sub trimWs($) {
    my $string = shift;
    #my ($ws1, $ws2);
    $string =~ /^(\s*)(\S*(?:\s+\S+)*)(\s*)$/;
    return ($1,$2,$3);
}

#from: http://www.perlmonks.org/?node_id=406883
sub max {
    my ($m, @vars) = @_;
    for (@vars) {
        $m = $_ if $_ > $m;
    }
    return $m;
}

sub min {
    my ($m, @vars) = @_;
    for (@vars) {
        $m = $_ if $_ < $m;
    }
    return $m;
}

sub shouldPrint {
    my $self          = shift;
    my $keep          = shift;    
    return 1 if $keep == 1;
    return 1 if $keep == 2 && $self->{simplifiedonly} == 0;
    return 0
}


# Note: taken from
#http://en.cppreference.com/w/cpp/language/operator_precedence
my %opPrecidences = (
    '!'  => 3,
    '==' => 9,
    '!=' => 9,
    '&&' => 13,
    '||' => 14,
    "<" => 8,
    ">" => 8,
    ">=" => 8,
    "<=" => 8,
    "+" => 6,
    "-" => 6,
    "*" => 5,
    "/" => 5,
    "%" => 5,
    "&" => 10,
    "^" => 11,
    "," => 17,     #used for function-like macro parameter lists
);

my $MAX_OP_PREC = 30;


my $CWS = qr{(?:(?:\s)|(?://.*$)|(?:\/\*.*?\*\/))*}s;
my $EXPR = {
    'C' => {
        IF     => qr/^((?:$CWS)#(?:$CWS)if\s+)(.*?)\s*$/s,
        ELSEIF => qr/^((?:$CWS)#(?:$CWS)elif\s+)(.*?)\s*$/s,
        ELSE   => qr/^((?:$CWS)#(?:$CWS)else\s*)(.*?)\s*$/s,
        ENDIF  => qr/^((?:$CWS)#(?:$CWS)endif\s*)(.*?)\s*$/s,
        IFDEF  => qr/^((?:$CWS)#(?:$CWS)ifdef\s+)(.*?)\s*$/s,
        IFNDEF => qr/^((?:$CWS)#(?:$CWS)ifndef\s+)(.*?)\s*$/s,
        START_ML_COMMENT =>
qr{^((("(\\\\.|[^"\\\\])*")|(\\\'(\\\\.|[^\\\\])*\\\')|[^\'"/])*/\*)(\**[^\*/]|[^\*])*$}s,
        END_ML_COMMENT => qr{\*/(\**[^\*/]|[^\*])*$}s,
        LIT_IF     => "#if ",
        LIT_ELSE   => "#else ",
        LIT_ELSEIF => "#elif ",
        LIT_ENDIF  => "#endif ",
        WHITESPACE => $CWS,
    },
    'Makefile' => {
        IF     => qr/^(\s*ifdef\s+BCM_KF\s*#\s*)(.*?)\s*$/s,
        ELSEIF => qr/^___NOT_MATCHABLE_STRING___$/s,
        ELSE   => qr/^(\s*else\s*#\s*BCM_KF\s*)(.*?)\s*$/s,
        ENDIF  => qr/^(\s*endif\s*#\s*BCM_KF\s*)(.*?)\s*$/s,
        IFDEF  => qr/^___NOT_MATCHABLE_STRING___$/s,
        IFNDEF => qr/^___NOT_MATCHABLE_STRING___$/s,
        START_ML_COMMENT => qr/^___NOT_MATCHABLE_STRING___$/s,
        END_ML_COMMENT => qr/^___NOT_MATCHABLE_STRING___$/s,
        LIT_IF     => "ifdef BCM_KF #  ",
        LIT_ELSE   => "else # BCM_KF ",
        LIT_ENDIF  => "endif # BCM_KF",
        WHITESPACE => qr{(?:(?:\s)|(?:\#.*$))*}s,
    },
    'Kconfig' => {
    }
};

my $OUTFILE;
my $DBGOUT;
*DBGOUT = *STDOUT;

# the following is a whitespace character sequence used to replace a \
# line ending.  
# This should not cotnain a newline as it breaks the script in several places
my $LINE_CONT = "\t  \t   \t \t \t   \t   \t    \t";

sub new {
    my $class = shift;
    my %opts  = @_;
    my $self  = {};
    $self->{lang} = $opts{lang} || 'C';

    $self->{dbg}       = $opts{dbg};
    $self->{defines}   = dclone( $opts{defines} );
    $self->{undefines} = dclone( $opts{undefines} );
    $self->{simplifiedonly} = $opts{simplifiedonly} || 0;
    $self->{error}     = '';
    bless( $self, $class );
    return $self;
}

sub parse {
    my $self   = shift;
    my %opts   = @_;
    my $INFILE = $opts{INFILE};
    $OUTFILE = $opts{OUTFILE};
    my @inlines = <$INFILE>;
    $self->{inlines}     = \@inlines;
    $self->{wasModified} = 0;
    $self->{lang} = $opts{lang} || $self->{lang};
    $self->{infile} = $INFILE;
    $self->{dbg} = $opts{dbg};
  
    if ($self->{lang} eq 'Kconfig') {
        my @undefineKeys = keys( %{ $self->{undefines} } );
        my @defineKeys = keys( %{ $self->{defines} } );
        $self->{kDefines} = {};
        foreach (@undefineKeys) {
            my $modKey = $_;
            $modKey =~ s/^CONFIG_//;
            $self->{kDefines}{$modKey} = $N;
        }
        foreach (@defineKeys) {
            my $modKey = $_;
            $modKey =~ s/^CONFIG_//;
            $self->{kDefines}{$modKey} = $Y if ($self->{defines}{$_} eq "y");
            $self->{kDefines}{$modKey} = $M if ($self->{defines}{$_} eq "m");
            $self->{kDefines}{$modKey} = $N if ($self->{defines}{$_} eq "n");
        }
        while(1) {
            my @outEntry = ();
            my $isVisible = $self->kconfigReadNextEntry(\@outEntry);
            if ($isVisible == 2) {            
                $self->{error} = "Unexpected end condition at line $..  Aborting";
                print STDERR "".$self->{error}."\n" if($showErrs);
                return;
            }
            if ($isVisible) {
                print $OUTFILE @outEntry;
            }
            else
            {
                $self->{wasModified} = 1;
            }
            last if (scalar(@{$self->{inlines}}) <= 0);
        }
    }
    else {
        if ( ! defined($EXPR->{$self->{lang}} )) {
            $self->{error} = "Error: unknown language $self->{lang}.\n";
            print STDERR "".$self->{error}."\n" if($showErrs);
            return;
        }
    
        my ( $closingExpr, $closingArg ) = $self->parseLines(2);

        if ($closingExpr) {
            $self->{error} =
              "Error: unexpected $closingExpr $closingArg at line $..  Aborting";
            print STDERR "".$self->{error}."\n" if($showErrs);
            return;
        }
    }
    return 2 if($self->{error});
    return ( $self->{wasModified} );
}

sub parseCondition {
    my $self = shift;
    my $lang = $self->{lang};
    my $WS = $EXPR->{$lang}->{WHITESPACE};

    # replace known variables
    my $condition     = shift;
    my $origCondition = $condition;

    my @defineKeys = keys( %{ $self->{defines} } );
    foreach (@defineKeys) {
        my $defineKey = $_;
        $condition =~ s/\bdefined\s*\(\s*$defineKey\s*\)/$TRUE_RESOLVED/g;
        $condition =~ s/\bdefined\s+$defineKey\b/$TRUE_RESOLVED/g;
        $condition =~ s/\b$defineKey\b/$RESOLVED_PREFIX$self->{defines}{$defineKey}/g;
    }

    my @undefineKeys = keys( %{ $self->{undefines} } );
    foreach (@undefineKeys) {
        my $undefineKey = $_;
        $condition =~ s/\bdefined\s*\(\s*$undefineKey\s*\)/$FALSE_RESOLVED/g;
        $condition =~ s/\bdefined\s+$undefineKey\b/$FALSE_RESOLVED/g;
    }

    if ( $condition eq $origCondition ) {
        print DBGOUT "Not simplifying $condition\n" if ($self->{dbg});
        return ($condition, 0);
    }

    print DBGOUT "simplifying $condition\n" if ($self->{dbg});

    my $remainder;
    ( $condition, $remainder ) =
      $self->simplifyExpr( $condition, $MAX_OP_PREC, 0 );

    if ( !$condition ) {
        print STDERR "Could not parse condition: $condition\n";
        return ($origCondition, 0);
    }
    else {
        $self->{wasModified} = 1;
        return ($condition . $remainder, 1);
    }
}

sub parseFuncMacro {
    my $self = shift;
    my $expr = shift;
    my $lang = $self->{lang};
    my $WS = $EXPR->{$lang}->{WHITESPACE};
   
    if ($expr =~ /^($WS)(\w+)(\s*)(\(.*)$/) {
        my $ws_bm = $1;
        my $macroName = $2;
        my $ws_am = $3;
        my $rest = $4;

        my ($params, $remainder) = extract_bracketed( $rest, "()" ) or return;
        $params =~ s/^\((.*)\)$/$1/s;

        return ($ws_bm, $macroName, $ws_am, $params, $remainder);
    }
    return;
}

sub simplifyExpr {
    my $self          = shift;
    my $string1       = shift;
    my $currentOpPrec = shift;
    my $level         = shift;
    my $lang          = $self->{lang};

    $level += 1;

    my $i      = 0;
    my $dbgStr = "<".sprintf("%2d",$currentOpPrec).">";
    while ( $i < $level ) {
        $dbgStr .= "  ";
        $i++;
    }

    my $WS = $EXPR->{$lang}->{WHITESPACE};

    my $remainder;

    my $operand1;
    my $operator;
    my $operand2;

    my $operand1_ns;      #operand1 , not-simplified.
    my $operand2_ns;

    my $ws_bo1;    #whitespace before operand1
    my $ws_ao1;    #whitespace after operand1
    my $ws_bo2;    #whitespace before operand2
    my $ws_ao2;    #whitespace after operand2

    my $rss_o1 = RSS_UNCHANGED;
    my $rss_o2 = RSS_UNCHANGED;
    


    print DBGOUT
"\n$dbgStr Simplifying expression .$string1. ($currentOpPrec) (level=$level)\n"
      if $self->{dbg};

    #read first operand:

    #test for brackets:
    if ( $string1 =~ /^($WS)\((.*)$/s ) {
        my $braceExpr;
        my $remainder2;
        my $remainder3;
        my $ws_bm;         #whitespace before macro
        my $ws_am;         #whitespace after macro
        my $macro;
        my $params;
        my $ws_bb = $1;    #whitespace before brackets
        my $ws_ib;         #whitespace inside of brackets (front)
        my $rsstate;
                           #get expression within braces       
        ( $braceExpr, $remainder ) = extract_bracketed( $string1, "()" );
        $braceExpr =~ s/^\(($WS)(.*)\)$/$2/s;
        $ws_ib = $1;
        

        $rss_o1 = RSS_UNCHANGED;
        print DBGOUT "$dbgStr braceExpr=.$braceExpr.\n" if $self->{dbg};
        
        #simplify expression within braces
        ( $braceExpr, $remainder2, $rsstate, $operand1_ns ) =
          $self->simplifyExpr( $braceExpr, $MAX_OP_PREC, $level );
        print DBGOUT "$dbgStr WARNING: raminder2 not null ($remainder2)\n" 
          if ($remainder2!~/^\s*$/);

        print DBGOUT "$dbgStr ns1=\"$operand1_ns\"\n" if $self->{dbg};
        $operand1_ns = "(".$ws_ib.$operand1_ns.$remainder2.")";
        print DBGOUT "$dbgStr ... ns1=\"$operand1_ns\"\n" if $self->{dbg};

        if ( $braceExpr =~ /^($WS)((?:\w+)|(?:defined\s+\w+))($WS)$/s ) {
            print DBGOUT "$dbgStr Removing braces on simple term\n" if $self->{dbg};
            $operand1 = $2;
            $ws_bo1   = $ws_bb;
            $ws_ao1   = $3.$remainder2;
            $rss_o1  = max(RSS_SIMPLIFIED,$rsstate);
        }
        else {
            print DBGOUT "$dbgStr Not removing braces\n" if $self->{dbg};
            $operand1 = "($braceExpr$remainder2)";
            $ws_bo1   = $ws_bb;
            $ws_ao1   = "";
            $rss_o1 = $rsstate;
        }
    }

    #test for not operator:
    elsif ( $string1 =~ /^($WS)!($WS)(.*)$/s ) {
        $ws_bo1 = $1;
        my $ws_an = $2;    #whitespace after not
        my $notOperand;
        my $notOperand_rss;
        my $notOperand_ns;

        ( $notOperand, $remainder, $notOperand_rss, $notOperand_ns ) =
          $self->simplifyExpr( $3, $opPrecidences{"!"} + 1, $level )
          or return;
        $ws_ao1   = "";
        $operand1_ns = "!".$ws_an.$notOperand_ns;
        if ( $notOperand =~ /^($WS)($FALSE_RESOLVED_PTRN)($WS)$/s ) {
            $operand1 = "$TRUE_RESOLVED";
            $rss_o1 = RSS_RESOLVED;
        }
        elsif ( $notOperand =~ /^($WS)($TRUE_RESOLVED_PTRN)($WS)$/s ) {
            $operand1 = "$FALSE_RESOLVED";
            $rss_o1 = RSS_RESOLVED;
        }
        elsif ( $notOperand =~ /^($WS)($FALSE_PTRN)($WS)$/s ) {
            $operand1 = "$TRUE_SIMPLIFIED";
            $rss_o1 = max(RSS_SIMPLIFIED, $notOperand_rss);
        }
        elsif ( $notOperand =~ /^($WS)($TRUE_PTRN)($WS)$/s ) {
            $operand1 = "$FALSE_SIMPLIFIED";
            $rss_o1 = max(RSS_SIMPLIFIED, $notOperand_rss);
        }
        else {
            $operand1 = "!" . $ws_an . $notOperand;
            $rss_o1 = $notOperand_rss;
        }
    }

    # test for function-like macro:
    elsif ( my ($ws_bm, $macro, $ws_am, $params, $remainder2) = 
                  $self->parseFuncMacro($string1)) {
        #simplify expression within braces
        print DBGOUT "$dbgStr ... macro: .$macro.$params.\n" if $self->{dbg};
        my ( $sparams, $remainder3, $sparams_rss, $sparams_ns );
        if ( $params =~ /^($WS)$/ ) {
            $sparams_ns = $params;
            $sparams = $params;
            $remainder3 = "";
            $sparams_rss = RSS_UNCHANGED;
        }
        else {
            ( $sparams, $remainder3, $sparams_rss, $sparams_ns ) =
              $self->simplifyExpr( $params, $MAX_OP_PREC, $level ) or return;
        }
        $operand1_ns = $macro.$ws_am."(".$sparams_ns.$remainder3.")";
        $operand1 = $macro.$ws_am."(".$sparams.$remainder3.")";
        $ws_bo1 = $ws_bm;
        $remainder = $remainder2;
        $rss_o1 = $sparams_rss;
    }

    # get next single term:
    elsif ( $string1 =~ /^($WS)(\w+)\b(.*)/s ) {
        $ws_bo1    = $1;
        $operand1  = $2;
        $remainder = $3;

        my $braceExpr = "";

        $operand1_ns = $operand1;
        
        if ($operand1 =~ /$RESOLVED_PREFIX_PTRN/) {
            $rss_o1 = RSS_RESOLVED;
        }
        else {
            $rss_o1 = RSS_UNCHANGED;
        }

        if (($operand1 eq "defined") 
          && ($remainder =~ /((?:$WS)\w+)\b(.*)$/)
          ) {
            $operand1 .= $1; 
            $operand1_ns = $operand1;
            $remainder = $2;
            #don't need to worry about resolving here, as resolving all known 
            #"defined XXX" terms has already been done.
        }
    }
    else {
        print DBGOUT "$dbgStr NO MTACH\n" if $self->{dbg};
    }

    while ( !( $remainder =~ /^($WS)$/s ) ) {
        #TBD: base operator regex on operators hash...
        if ( $remainder =~ /^($WS)([\!\|\>\<\=&\*\+\-\/\%\^\&\,]+)($WS)(.*)$/s ) {
        #my $allops=join('|',quotemeta keys %opPrecidences)
        #if ( $remainder =~ /^($WS)($allops)($WS)(.*)$/s ) {
            $ws_ao1 = $1;
            $operator = $2;
            $ws_bo2 = $3;
            my $remainder2 = $4;
            if ( exists $opPrecidences{$operator} ) {
                if ( $opPrecidences{$operator} < $currentOpPrec ) {

                    ( $operand2, $remainder, $rss_o2, $operand2_ns ) =
                      $self->simplifyExpr( $remainder2,
                        $opPrecidences{$operator}, $level )
                      or return;

                    $ws_ao2 = "";  # whitespace will belong before next operand.
                    

# Note, the followin scheme can probably be written a bit neater, but I'm new to perl...

# simplifying:
                    print DBGOUT 
                       "$dbgStr $operand1.$ws_ao1.$operator.$ws_bo2.$operand2 ==> \n"
                       if $self->{dbg};

                    if (
                        ( $operator eq "&&" )
                        && (   ( $operand1 =~ /^($WS)$FALSE_RESOLVED_PTRN($WS)$/s )
                            || ( $operand2 =~ /^($WS)$FALSE_RESOLVED_PTRN($WS)$/s ) )
                      )
                    {
                        $operand1 = "$FALSE_RESOLVED";
                        $rss_o1   = RSS_RESOLVED;
                        $operand1_ns = $operand1;
                        $ws_bo1   = $ws_bo1;
                        $ws_ao1   = $ws_ao2;
                    }
                    elsif (
                        ( $operator eq "||" )
                        && (   ( $operand1 =~ /^($WS)$TRUE_RESOLVED_PTRN($WS)$/s )
                            || ( $operand2 =~ /^($WS)$TRUE_RESOLVED_PTRN($WS)$/s ) )
                      )
                    {
                        print DBGOUT "$dbgStr ... got here1\n" if $self->{dbg};
                        $operand1 = "$TRUE_RESOLVED";
                        $rss_o1   = RSS_RESOLVED;
                        $operand1_ns = $operand1;
                        $ws_bo1   = $ws_bo1;
                        $ws_ao1   = $ws_ao2;
                    }
                    elsif ( $operator eq "," ) {
                        #ensure we don't try to simplify , operators
                        $operand1 = $operand1.$ws_ao1.$operator.$ws_bo2.$operand2;
                        $rss_o1   = max($rss_o1, $rss_o2, RSS_SIMPLIFIED);
                        $operand1_ns = $operand1_ns.$ws_ao1.$operator.$ws_bo2.$operand2_ns;
                        $ws_bo1   = $ws_bo1;
                        $ws_ao1   = $ws_ao1;
                    }                    
                    elsif ( isNumber_($operand1) && isNumber_($operand2) ) 
                    {                        
                        # note: this has an effect of simplifying constant expressions
                        
                        my $newVal;
                        my $evalErr;
                        my $expression = getValue($operand1)." ".$operator." ".getValue($operand2);
                        $newVal = eval "$expression" || 0;
                        print DBGOUT "$dbgStr eval:\"$expression\" ==> \"$newVal\"\n" if $self->{dbg};
                        $evalErr = $@;
                        if ( ! $evalErr ) { 
                            $rss_o1   = max($rss_o1, $rss_o2, RSS_SIMPLIFIED);
                            $operand1 = ($rss_o1==RSS_SIMPLIFIED?$SIMPLIFIED_PREFIX:$RESOLVED_PREFIX).$newVal;
                        } else {
                            print DBGOUT "$dbgStr evalErr -- reverting\n"  if $self->{dbg};
                            $operand1 =
                              $operand1 . $ws_ao1 . $operator . $ws_bo2 . $operand2;
                            $rss_o1 = max($rss_o1, $rss_o2);
                        }
                        
                        $operand1_ns = ($rss_o1 == RSS_RESOLVED) ?
                            $operand1 :
                            $operand1_ns.$ws_ao1.$operator.$ws_bo2.$operand2_ns;                        
                        $ws_bo1 = $ws_bo1;
                        $ws_ao1 = $ws_ao2; 
                    }

                    # after this point we know that at least one side is not completely simplified:
                    
                    elsif (
                        ( $operator eq "&&" )
                        && (   ( $operand1 =~ /^($WS)$FALSE_SIMPLIFIED_PTRN($WS)$/s )
                            || ( $operand2 =~ /^($WS)$FALSE_SIMPLIFIED_PTRN($WS)$/s ) )
                      )
                    {
                        #$operand1_ns = $operand1_ns;
                        $operand1 = "$FALSE_SIMPLIFIED";
                        $rss_o1   = RSS_SIMPLIFIED;
                        $operand1_ns = $ws_bo1.$operand1_ns.$ws_ao1.$operator.$ws_bo2.$operand2_ns.$ws_ao2;
                        $ws_bo1   = $ws_bo1;
                        $ws_ao1   = $ws_ao2;
                    }
                    elsif (( $operator eq "&&" )
                        && ( $operand1 =~ /^($WS)$TRUE_PTRN($WS)$/s ) )
                    {
                        $operand1 = $operand2;
                        $rss_o1   = max($rss_o1, $rss_o2, RSS_SIMPLIFIED);
                        $operand1_ns = ($rss_o1==RSS_RESOLVED)?
                            $operand1 :
                            $ws_bo1.$operand1_ns.$ws_ao1.$operator.$ws_bo2.$operand2_ns.$ws_ao2;
                        $ws_bo1   = $ws_bo2;
                        $ws_ao1   = $ws_ao2;
                    }
                    elsif (( $operator eq "&&" )
                        && ( $operand2 =~ /^($WS)$TRUE_PTRN($WS)$/s ) )
                    {
                        $operand1 = $operand1;
                        $rss_o1   = max($rss_o1, $rss_o2, RSS_SIMPLIFIED);
                        $operand1_ns = ($rss_o1==RSS_RESOLVED)?
                            $operand1 :
                            $ws_bo1.$operand1_ns.$ws_ao1.$operator.$ws_bo2.$operand2_ns.$ws_ao2;
                        $ws_bo1   = $ws_bo1;
                        $ws_ao1   = $ws_ao1;
                    }
                    elsif (
                        ( $operator eq "||" )
                        && (   ( $operand1 =~ /^($WS)$TRUE_SIMPLIFIED_PTRN($WS)$/s )
                            || ( $operand2 =~ /^($WS)$TRUE_SIMPLIFIED_PTRN($WS)$/s ) )
                      )
                    {
                        $operand1 = "$TRUE_SIMPLIFIED";
                        $rss_o1   = max($rss_o1, $rss_o2, RSS_SIMPLIFIED);
                        $operand1_ns = $ws_bo1.$operand1_ns.$ws_ao1.$operator.$ws_bo2.$operand2_ns.$ws_ao2;
                        $ws_bo1   = $ws_bo1;
                        $ws_ao1   = $ws_ao2;
                    }
                    elsif (( $operator eq "||" )
                        && ( $operand1 =~ /^($WS)$FALSE_PTRN($WS)$/s ) )
                    {
                        $operand1 = $operand2;
                        $rss_o1   = max($rss_o1, $rss_o2, RSS_SIMPLIFIED);
                        $operand1_ns = ($rss_o1 == RSS_RESOLVED) ?
                            $operand1 :
                            $ws_bo1.$operand1_ns.$ws_ao1.$operator.$ws_bo2.$operand2_ns.$ws_ao2;
                        $ws_bo1   = $ws_bo2;
                        $ws_ao1   = $ws_ao2;
                    }
                    elsif (( $operator eq "||" )
                        && ( $operand2 =~ /^($WS)$FALSE_PTRN($WS)$/s ) )
                    {
                        $operand1 = $operand1;
                        $rss_o1   = max($rss_o1, $rss_o2, RSS_SIMPLIFIED);
                        $operand1_ns = ($rss_o1 == RSS_RESOLVED) ?
                            $operand1 :
                            $operand1_ns.$ws_ao1.$operator.$ws_bo2.$operand2_ns;
                        $ws_bo1   = $ws_bo1;
                        $ws_ao1   = $ws_ao1;
                    }
                    elsif ( ($operator eq "==") 
                        && ($operand1 eq $operand2) ) {
                        $operand1_ns = $operand1.$ws_ao1.$operator.$ws_bo2.$operand2;
                        $rss_o1 = max($rss_o1, $rss_o2, RSS_SIMPLIFIED);
                        $operand1 = $rss_o1==RSS_RESOLVED?"$TRUE_RESOLVED":"$TRUE_SIMPLIFIED";
                        $operand1_ns = ($rss_o1 == RSS_RESOLVED) ?
                            $operand1 :
                            $ws_bo1.$operand1_ns.$ws_ao1.$operator.$ws_bo2.$operand2_ns.$ws_ao2;                        
                        $ws_bo1 = $ws_bo1;
                        $ws_ao1 = $ws_ao2;
                    }
                    elsif ( ($operator eq "!=") && ($operand1 eq $operand2) ) {
                        $rss_o1 = max($rss_o1, $rss_o2, RSS_SIMPLIFIED);
                        $operand1 = $rss_o1==RSS_RESOLVED?"$FALSE_RESOLVED":"$FALSE_SIMPLIFIED";
                        $operand1_ns = ($rss_o1 == RSS_RESOLVED) ?
                            $operand1 :
                            $ws_bo1.$operand1_ns.$ws_ao1.$operator.$ws_bo2.$operand2_ns.$ws_ao2;                        
                        $ws_bo1 = $ws_bo1;
                        $ws_ao1 = $ws_ao2;
                    }
                    else {
                        # we have an expression which we can't simplify any further.  
                        # unsimplify both operands if they are simplified.
                        print DBGOUT "$dbgStr Unsimplifying: $operand1 $operator $operand2\n" 
                          if $self->{dbg};
                        print DBGOUT "$dbgStr ... ns1 \"$operand1_ns\" ns2 \"$operand2_ns\"\n" 
                          if $self->{dbg};

                        if ($rss_o1 == RSS_SIMPLIFIED) {
                            $operand1 = $operand1_ns;
                            $rss_o1 = RSS_UNCHANGED;
                        }
                        
                        if ($rss_o2 == RSS_SIMPLIFIED) {
                            $operand2 = $operand2_ns;
                            $rss_o2 = RSS_UNCHANGED;
                        }
                        
                        $operand1 =~ s/\b$SIMPLIFIED_PREFIX|$RESOLVED_PREFIX//g;
                        $operand2 =~ s/\b$SIMPLIFIED_PREFIX|$RESOLVED_PREFIX//g;
                        
                        $operand1 =
                          $operand1 . $ws_ao1 . $operator . $ws_bo2 . $operand2;
                        $operand1_ns = $operand1;
                        $ws_bo1 = $ws_bo1;
                        $ws_ao1 = $ws_ao2;
                        $rss_o1 = max($rss_o1, $rss_o2);
                        print DBGOUT "$dbgStr ... Unsimplified: $operand1\n" 
                          if $self->{dbg};
                    }
                    print DBGOUT "$dbgStr      ==> \"$operand1\", r:\"$remainder\", ns:\"$operand1_ns\" \n" 
                          if $self->{dbg};
                    
                }
                else {
                    print DBGOUT
"$dbgStr op precidence exceded: returning \"$operand1\", r:\"$remainder\", ns:\"$operand1_ns\"\n\n"
                      if $self->{dbg};
                    return ( $operand1, $remainder, $rss_o1, $operand1_ns );
                }
                print DBGOUT "$dbgStr operand1 = $operand1, r:$remainder\n"
                  if $self->{dbg};
            }
            else {
                $self->{error} = "operator not found .$operator.";
                print STDERR "".$self->{error}."\n" if($showErrs);
                return;    # error
            }
        }

        print DBGOUT
"$dbgStr end while loop o1=\"$operand1\", r=\"$remainder\", s=".$rss_o1.",ns:\"$operand1_ns\"\n\n"
          if $self->{dbg};

        $operator = "";
        $operand2 = "";
    }

    return ( $operand1, $remainder, $rss_o1, $operand1_ns );
}

sub parseIf {
    my $self       = shift;
    my $litExpr    = shift;
    my $expression = shift;
    my $keep       = shift;
    my $simplified = shift;
    my $lang       = $self->{lang};
    my $nextExprType;
    my $nextConditional;
    my $line;

    my $WS = $EXPR->{$lang}->{WHITESPACE};

    $keep = 1 if ( $simplified && $keep == 2);
    
    if ( $simplified && $expression =~ /^($WS)($TRUE_PTRN)($WS)$/ ) {

        # do not echo #if.
        # find next #else / #elseif, and wipe till #endif:

        ( $nextExprType, $nextConditional ) = $self->parseLines($keep);
        while ( $nextExprType =~ /$EXPR->{$lang}->{ELSEIF}/s ) {
            ( $nextExprType, $nextConditional ) = $self->parseLines(0);
            if (!$nextExprType) {
                $self->{error} = "unexpected end of file";
                print STDERR "".$self->{error}."\n" if($showErrs);
                return;
              }
        }
        if ( $nextExprType =~ /$EXPR->{$lang}->{ELSE}/s ) {
            ( $nextExprType, $nextConditional ) = $self->parseLines(0);
        }
        if (!$nextExprType) {
            $self->{error} = "unexpected end of file";
            print STDERR "".$self->{error}."\n" if($showErrs);
            return;
        }
        if ( $nextExprType !~ /$EXPR->{$lang}->{ENDIF}/s ) {
            $self->{error} = "unterminated if ($expression)";
            print STDERR "".$self->{error}."\n" if($showErrs);
            return;
        }
        return (1);
    }
    elsif ( $simplified && $expression =~ /^($WS)$FALSE_PTRN($WS)$/ ) {

        #do not echo #if.
        #if we find an elseif, treat it as an if
        #if we find an else, echo till endif (do not echo endif)

        my ( $nextExprType, $nextConditional ) = $self->parseLines(0);
        if (!$nextExprType) {
             $self->{error} = "unterminated if ($expression)";
             print STDERR "".$self->{error}."\n" if($showErrs);
             return;
        }
        
        if ( $nextExprType =~ /$EXPR->{$lang}->{ELSEIF}/s ) {
            my ($nextExpression, $wasSimplified) = $self->parseCondition($nextConditional);
            $self->parseIf( $EXPR->{$lang}->{LIT_IF}, $nextExpression, $keep, $wasSimplified );
        }
        elsif ( $nextExprType =~ /$EXPR->{$lang}->{ELSE}/s ) {
            ( $nextExprType, $nextConditional ) = $self->parseLines($keep);
        }
        if ( $nextExprType !~ /$EXPR->{$lang}->{ENDIF}/s ) {
            $self->{error} = "unterminated if ($expression)";
            print STDERR "".$self->{error}."\n" if($showErrs);
            return;
        }
        # do not echo endif
    }
    else {

        #unresolved condition: echo if
        #if we find an elsif, resolve condition, and remove code if false
        #if we find an else, echo that.
        #echo endif

        $line = $litExpr . $expression;
        $line =~ s/$LINE_CONT/\\\n/g;

        print $OUTFILE $line . "\n" if $self->shouldPrint($keep);
        ( $nextExprType, $nextConditional ) = $self->parseLines($keep);
        if (!$nextExprType) {
            $self->{error} = "Unmatched  $litExpr  $expression at eof";
            print STDERR "".$self->{error}."\n" if($showErrs);
            return;
          }

        while ( $nextExprType =~ /$EXPR->{$lang}->{ELSEIF}/s ) {
            my ($nextExpression, $wasSimplified) = $self->parseCondition($nextConditional);

            #my $nextExpression = $nextConditional;
            if ( $wasSimplified && $nextExpression =~ m/($WS)$TRUE_PTRN($WS)/ ) {
                print $OUTFILE $EXPR->{$lang}->{LIT_ELSE} . "\n" if $self->shouldPrint($keep);
                ( $nextExprType, $nextConditional ) = $self->parseLines($keep);
                while ( !( $nextExprType =~ /$EXPR->{$lang}->{ENDIF}/s ) ) {
                    if (!$nextExprType) {
                        $self->{error} = "unexpected end of file";
                        print STDERR "".$self->{error}."\n" if($showErrs);
                        return;
                      }
                    ( $nextExprType, $nextConditional ) = $self->parseLines(0);
                }
            }
            elsif ( $wasSimplified && $nextExpression =~ /($WS)$FALSE_PTRN($WS)/ ) {
                ( $nextExprType, $nextConditional ) = $self->parseLines(0);
            }
            else {
                $line = $EXPR->{$lang}->{LIT_ELSEIF} . $nextExpression;
                $line =~ s/$LINE_CONT/\\\n/g;
                print $OUTFILE $line . "\n" if $self->shouldPrint($keep);
                ( $nextExprType, $nextConditional ) = $self->parseLines($keep);
            }
        }
        if ( $nextExprType =~ /$EXPR->{$lang}->{ELSE}/s ) {
            $line = $nextExprType . $nextConditional;
            $line =~ s/$LINE_CONT/\\\n/g;
            print $OUTFILE $line . "\n" if $self->shouldPrint($keep);
            ( $nextExprType, $nextConditional ) = $self->parseLines($keep);
        }
        if ( $nextExprType !~ /$EXPR->{$lang}->{ENDIF}/s ) {
            $self->{error} = "expected endif ($nextExprType)";
            print STDERR "".$self->{error}."\n" if($showErrs);
            return;
        }
        # this line must be an endif, echo, and return
        $line = $nextExprType . $nextConditional;
        $line =~ s/$LINE_CONT/\\\n/g;
        print $OUTFILE $line . "\n" if $self->shouldPrint($keep);
    }

    return (1);
}

sub parseLines {
    my $self = shift;
    my $keep = shift;
    my $lang = $self->{lang};
    my $line;
    my $newLine;
    my $litExpr;
    my $expression;

    while (1) {

        $line = ( shift @{ $self->{inlines} } );
        return unless defined($line);
        chomp $line;

        while ( $line =~ /^(.*)\\$/s ) {

#switch \ at end of line to whitespace -- but we will need to recover it later on,
#
            $line = $1 . $LINE_CONT . ( shift @{ $self->{inlines} } );
            chomp $line;
        }
        if ( $line =~ m#$EXPR->{$lang}->{START_ML_COMMENT}#s ) {

            # Treat multi-line comments as a single line
            while ( $newLine = ( shift @{ $self->{inlines} } ) ) {
                chomp $newLine;
                $line = $line . "\n" . $newLine;
                last if $newLine =~ m#$EXPR->{$lang}->{END_ML_COMMENT}#s;
            }
        }
        if ( $line =~ /$EXPR->{$lang}->{IFDEF}/s ) {
            $litExpr    = $1;
            $expression = $2;

            #$macro = $expression =~ /^\s+(\S+)/s ;
            my $macro = trim($expression);
            print DBGOUT
              "litExpr=$litExpr, expression=$expression, macro=.$macro.\n"
              if $self->{dbg};
            if ( $self->{defines}->{$macro} ) {
                print DBGOUT "calling parseIf TRUE\n" if $self->{dbg};
                $self->parseIf( $EXPR->{$lang}->{LIT_IF}, "$TRUE_RESOLVED", $keep, 1 );
                $self->{wasModified}=1;
            }
            elsif ( $self->{undefines}->{$macro} ) {
                print DBGOUT "calling parseIf FALSE\n" if $self->{dbg};
                $self->parseIf( $EXPR->{$lang}->{LIT_IF}, "$FALSE_RESOLVED", $keep, 1 );
                $self->{wasModified}=1;
            }
            else {
                print DBGOUT "calling parseIf $expression\n" if $self->{dbg};
                $self->parseIf( $litExpr, $expression, $keep, 0 );
            }
        }
        elsif ( $line =~ /$EXPR->{$lang}->{IFNDEF}/s ) {
            $litExpr    = $1;
            $expression = $2;

            #$macro = $expression =~ /^\s+(\S+)/s ;
            my $macro = trim($expression);
            if ( $self->{defines}->{$macro} ) {
                $self->parseIf( $EXPR->{$lang}->{LIT_IF}, "$FALSE_RESOLVED", $keep, 1 );
                $self->{wasModified}=1;
            }
            elsif ( $self->{undefines}->{$macro} ) {
                $self->parseIf( $EXPR->{$lang}->{LIT_IF}, "$TRUE_RESOLVED", $keep, 1 );
                $self->{wasModified}=1;
            }
            else {
                $self->parseIf( $litExpr, $expression, $keep, 0 );
            }
        }
        elsif ( $line =~ /$EXPR->{$lang}->{IF}/s ) {
            $litExpr    = $1;
            my $wasSimplified;
            ($expression, $wasSimplified) = $self->parseCondition($2);
            $self->parseIf( $litExpr, $expression, $keep, $wasSimplified );
        }
        elsif ( $line =~ /$EXPR->{$lang}->{ELSEIF}/s ) {
            $litExpr    = $1;
            $expression = $2;
            return ( $litExpr, $expression );
        }
        elsif ( $line =~ /$EXPR->{$lang}->{ELSE}/s ) {
            $litExpr    = $1;
            $expression = $2;
            return ( $litExpr, $expression );
        }
        elsif ( $line =~ /$EXPR->{$lang}->{ENDIF}/s ) {
            $litExpr    = $1;
            $expression = $2;
            return ( $litExpr, $expression );
        }
        else {

            # replace stripped out line cont's:
            $line =~ s/$LINE_CONT/\\\n/g;
            print $OUTFILE $line . "\n" if $self->shouldPrint($keep);
        }
    }
}

## --------------------------------------------------------------------------
## KCONFIG support:
##
## Note: Kconfig work very differently than Makefiles or C files
##
##
# NOTE: if behaviour is actually simplified a bit:  in reality, if appends a 
# condition to all items inside of it.  (So, if you had an 
#
#    if x
#    source Kconfig.foo
#    endif
#
# then source Kconfig.foo would still get expanded, only every entry in
# Kconfig.foo would have if x appended to its end. This code treats if
# differently, in that it would remove source Kconfig.foo entirely if x 
# was false...

sub kconfigSimplifyExprTop {
    my $self = shift;
    my $origExpr = shift;

    my %realval = ( $Y => "y", $M => "m", $N => "n");
    my $check = join '|', keys %realval;
    
    my $expr = $self->kconfigSimplifyExpr($origExpr);
    my $rtExpr = $expr;
    $rtExpr =~ s/($check)/$realval{$1}/g;
    $self->{wasModified} = 1 if ( $origExpr ne $rtExpr );
    return (trim($rtExpr),trim($expr));
}

sub kconfigSimplifyExpr {
    my $self = shift;
    my $expr = shift;

    my ($ws1, $ws2, $ws3, $ws4, $ws5, $ws6, $op1, $op2);
    my ($ws11, $ws12, $ws21, $ws22);
    my ($e1, $e2);

    if ($expr =~ /^($BRACE_MATCH)\|\|($BRACE_MATCH)$/ ) {
        ($e1, $e2) = ($1,$2);
        ($ws11,$op1,$ws12) = trimWs($self->kconfigSimplifyExpr($e1)); 
        ($ws21,$op2,$ws22) = trimWs($self->kconfigSimplifyExpr($e2));
        return $Y if ($op1 eq $Y || $op2 eq $Y);
        return $M if (($op1 eq $M || $op1 eq $Y) && ($op2 eq $M || $op2 eq $Y));
        return $N if ($op1 eq $N && $op2 eq $N);
        return "".$ws11.$op1 if ($op2 eq $N);
        return "".$ws11.$op2 if ($op1 eq $N);
        return "".$ws11.$op1.$ws12."||".$ws21.$M if ($op2 eq $M);
        return "".$ws11.$M.$ws12."||".$ws21.$op2 if ($op1 eq $M);   
        return "".$ws11.$op1.$ws12."||".$ws21.$op2.$ws22;
    }
    elsif ($expr =~ /^($BRACE_MATCH)&&($BRACE_MATCH)$/ ) {
        ($e1, $e2) = ($1,$2);
        ($ws11,$op1,$ws12) = trimWs($self->kconfigSimplifyExpr($e1)); 
        ($ws21,$op2,$ws22) = trimWs($self->kconfigSimplifyExpr($e2));
        return $Y if ($op1 eq $Y && $op2 eq $Y);
        return $M if (($op1 eq $M || $op1 eq $Y) && ($op2 eq $M || $op2 eq $Y));
        return $N if ($op1 eq $N || $op2 eq $N);
        return "".$ws11.$op1.$ws12 if ($op2 eq $Y);
        return "".$ws11.$op2.$ws12 if ($op1 eq $Y);
        return "".$ws11.$op1.$ws12."&&".$ws21.$M.$ws22 if ($op2 eq $M);
        return "".$ws11.$M.$ws12."&&".$ws21.$op2 if ($op1 eq $M);
        return "".$ws11.$op1.$ws12."&&".$ws21.$op2.$ws22;
    }
    elsif ($expr =~ /^(\s*)!($BRACE_MATCH)$/ ) {
        ($ws1, $e1) =($1,$2);
        ($ws11,$op1,$ws12) = trimWs($self->kconfigSimplifyExpr($e1));
        return $ws11.$N.$ws12 if ($op1 eq $Y);
        return $ws11.$M.$ws12 if ($op1 eq $M);
        return $ws11.$Y.$ws12 if ($op1 eq $N);
        return "".$ws1."!".$ws11.$op1.$ws12;
    }
    elsif ($expr =~ /^(\s*)\(($BRACE_MATCH)\)(\s*)$/ ) {
        ($ws1, $e1, $ws2) =($1,$2,$3);
        ($ws11,$op1,$ws12) = trimWs($self->kconfigSimplifyExpr($e1));
        return $ws1.$Y.$ws2 if ($op1 eq $Y);
        return $ws1.$M.$ws2 if ($op1 eq $M);
        return $ws1.$N.$ws2 if ($op1 eq $N);
        #do not remove braces on unknown expressions:
        #return $ws1.$op1.$ws2 if ($op1 =~ /^\s*\w+\s*$/);
        return "".$ws1."(".$ws11.$op1.$ws12.")".$ws2;
    }
    elsif ($expr =~ /^($BRACE_MATCH)!=($BRACE_MATCH)$/ ) {
        ($e1, $e2) = ($1,$2);
        ($ws11,$op1,$ws12) = trimWs($self->kconfigSimplifyExpr($e1)); 
        ($ws21,$op2,$ws22) = trimWs($self->kconfigSimplifyExpr($e2));
        if (    ($op1 eq $Y || $op1 eq $M || $op1 eq $N)
             && ($op2 eq $Y || $op2 eq $M || $op2 eq $N)) {
            return $N if ($op1 eq $op2);
            return $Y;
        }
        else {
            return "".$ws11.$op1.$ws12."!=".$ws21.$op2.$ws22;
        }              
    }
    elsif ($expr =~ /^($BRACE_MATCH)=($BRACE_MATCH)$/) {
        ($e1, $e2) = ($1,$2);
        ($ws11,$op1,$ws12) = trimWs($self->kconfigSimplifyExpr($e1)); 
        ($ws21,$op2,$ws22) = trimWs($self->kconfigSimplifyExpr($e2));
        if (    ($op1 eq $Y || $op1 eq $M || $op1 eq $N)
             && ($op2 eq $Y || $op2 eq $M || $op2 eq $N)) {
            return $Y if ($op1 eq $op2);
            return $N;
        }
        else {
            return "".$ws11.$op1.$ws12."=".$ws21.$op2.$ws22;
        }              
    }
    elsif ($expr =~ /^(\s*)((?:\w|\")+)(\s*)$/) {
        return $1.$self->{kDefines}{$2}.$3 if defined $self->{kDefines}{trim($expr)};
        return $expr;
    }

    die "WARNING -- could not resolve <$expr>!!\n";
    return $expr;

}

my $LINE_SEP = "\r\t  \t   \t \t \t   \t   \t    \t\r";

# returns the visible lenght of whitespace (assuming tabs are 8 characters wide)
sub wslength {
    my $str = shift;
    # code taken from eugene y on 
    # http://stackoverflow.com/questions/5997404/perl-program-to-replace-tabs-with-spaces
    while($str =~ s/\t/" " x (8 - $-[0]%8)/e) {}    
    return length($str);
}

# attempts to read attributes
# stops when a line without indents is found
sub kconfigReadAttributes {
    my $self = shift;
    my $outLinesRef = shift;

    # clear output:
    @$outLinesRef = ();
    my $isVisible = 1;

    while (1) { 
        my $line = shift(@{$self->{inlines}});
        print DBGOUT "   - $line...\n" if ($self->{dbg});
        last unless defined($line);
        chomp($line);
        # hack: combine two lines into one.
        while (substr($line, -1) eq "\\") {
            $line = substr($line,0,-1) . $LINE_SEP . shift(@{$self->{inlines}});
            chomp($line);
        }


        if ($line =~ $COMMENT) {
            #comment: just push it
            $line =~ s/$LINE_SEP/\\\n/g;
            push(@$outLinesRef, $line."\n");
        }
        elsif ($line =~ /^(\s*)$/) {
            #blank line -- end of attributes.  Include blank line as 
            #part of item.
            $line =~ s/$LINE_SEP/\\\n/g;
            push(@$outLinesRef, $line."\n");
            last;
        }
        elsif ($line =~ /^[^\s]/) {
            #line with no preceding whitespace.  Though the spec says there should
            #be whitespace between entries, this rule isn't followed everywhere.
            #unshif the line, and finish:
            unshift(@{$self->{inlines}}, $line);
            last;
        }
        elsif ($line =~ /^(\s+)-*\s*help\s*-*.*$/)
        {
            #special handling for help: it is multiline and ends when the
            #first line of text has less indentation than the first.
            $line =~ s/$LINE_SEP/\\\n/g;
            push(@$outLinesRef, $line."\n");
            my $line = shift(@{$self->{inlines}});
            if (defined($line)) {
                chomp($line);

                if ($line =~ /^(\s+)/) {
                    # Grumble grumble: in at least one kernel Kconfig 
                    # file, they mixed up tabs and spaces...:
                    my $blankLines = 0;
                    my $helpIndentLen = wslength($1);
                    $line =~ s/$LINE_SEP/\\\n/g;
                    push(@$outLinesRef, $line."\n");
                    while (1) {
                        my $line = shift(@{$self->{inlines}});
                        last unless defined($line);
                        chomp($line);
                        if (length($line) == 0) {
                           $blankLines++; 
                        }
                        else {
                            $line =~ /^(\s*)/;
                            if (wslength($1) < $helpIndentLen) {
                                unshift(@{$self->{inlines}}, $line);
                                while($blankLines--) {unshift(@{$self->{inlines}}, "");};
                                last;
                            } else {
                                while($blankLines > 0) {$blankLines--; push(@$outLinesRef, "\n");};
                                $line =~ s/$LINE_SEP/\\\n/g;
                                push(@$outLinesRef, $line."\n");
                            }
                        } 
                    }
                }
                else {
                    # Grumble grumle -- apperently it's possible to have empty
                    # help fields...
                    unshift(@{$self->{inlines}}, $line);
                }
            }
        }
        elsif ($line =~ /^(\s+depends on\s*)(.*)$/) {
            my $tmp = $1;
            my ($condition,$result) = $self->kconfigSimplifyExprTop($2);
            if ($result eq $N) {
                $isVisible = 0;
            }
            if ($result eq $Y || $result eq $M) {
                #do not echo
            }
            else {
                $condition =~ s/$LINE_SEP/\\\n/g;
                push(@$outLinesRef, $tmp . $condition . "\n");  
            }
        }
        elsif ($line =~ /^(\s+(?:[^"]|(?:"(?:[^"\\]|\\.)*"))+)(\s+if\s+)(.*?)(\s*\#.*)?$/) {
            my $term = $1;
            my $ifTerm = $2;
            my $eolComment = defined($4) ? $4 : "";
            print DBGOUT "   conditional statement: $3" if ($self->{dbg});
            my ($condition,$result) = $self->kconfigSimplifyExprTop($3);
            if ($result eq $N) {
            }
            elsif ($result eq $Y || $result eq $M) {
                $term =~ s/$LINE_SEP/\\\n/g;
                $eolComment =~ s/$LINE_SEP/\\\n/g;
                push(@$outLinesRef, $term . $eolComment . "\n");
            }
            else {          
                $term =~ s/$LINE_SEP/\\\n/g;
                $ifTerm =~ s/$LINE_SEP/\\\n/g;
                $condition =~ s/$LINE_SEP/\\\n/g;
                $eolComment =~ s/$LINE_SEP/\\\n/g;                
                push(@$outLinesRef, $term . $ifTerm . $condition . $eolComment ."\n");
            }
        }
        elsif ($line =~ /^\s+(\w+)\s*(.*)$/) {
            $line =~ s/$LINE_SEP/\\\n/g;
            print DBGOUT "   + - $line...\n" if ($self->{dbg});
            push(@$outLinesRef, $line."\n");
        }
        else  {
            $self->{error} = "Error parsing Kconfig file ($line)";          
            print STDERR "".$self->{error}."\n" if($showErrs);
            last;
        }
    }
    return $isVisible;
}

# return 0 invisible entry, return 1 visible entry, 2 for end-block command
sub kconfigReadNextEntry {
    my $self = shift;
    my $outLinesRef = shift;
    
    my $isVisible = 1;
    my $hideEndif = 0;
    my $line;
    
    do {
        $line = ( shift @{ $self->{inlines} } );
        print DBGOUT "   > $line...\n" if ($self->{dbg});
        return 1 unless defined($line);
        chomp($line);
        # in the case of an unexpected endblock, push the endblock back onto the array,
        # and return 2.  The caller will process the endblock line.
        if ($line =~ "\^endchoice" || $line =~ "\^endmenu" || $line =~ "\^endif") {
            unshift(@{ $self->{inlines} }, $line."\n");
            return 2;
        }
        push(@$outLinesRef, $line."\n");        
    } while(    $line =~ /$COMMENT/ || $line =~ /$BLANK_LINE/);

    if ( $line =~ /^source\s/ || $line =~ /^mainmenu\s/ ) {
        return 1;
    }
    
    if (    $line =~ /^config\s/ || $line =~ /^menuconfig\s/ || $line =~ /^comment/ 
         || $line =~ /^choice/ || $line =~ /^menu/) {
        #read attributes
        my @entryAttributes = ();
        
        $isVisible = $self->kconfigReadAttributes(\@entryAttributes);
        $self->{wasModified} = 1 unless ($isVisible);
        push(@$outLinesRef, @entryAttributes) if ($isVisible);
    }

    my $endStr = "";
    $endStr = qw/endchoice/     if ($line =~ /^choice\s*$/);
    $endStr = qw/endmenu/       if ($line =~ /^menu\s/);
    $endStr = qw/endif/         if ($line =~ /^if\s/);

    if ($endStr) {
        $hideEndif = 0;
        #special handling for if: we need to modify the condition
        if ($line =~ /^if\s+(.*?)(\s*(?:\#.*$))?$/) {
            my ($condition, $result, $eolComment) = ($self->kconfigSimplifyExprTop($1), $2);
            $eolComment = "" if !defined($eolComment);
            if ($condition ne $1) {   
                # expression changed -- rewriting      
                if ($result eq $N) {
                    $isVisible = 0 ;
                    $self->{wasModified} = 1;
                } elsif ($result eq $Y || $result eq $M) {
                    #pop off if statement (if statement may be multiline...)
                    #print STDERR "condition: ".$condition."    Y:".$Y."\n";
                    while (! (pop(@$outLinesRef) =~ /^if\s/) ) {};
                    $self->{wasModified} = 1;
                    $hideEndif = 1 ;
                } else {                          
                    #pop off if statement (if statement may be multiline...)
                    while (! (pop(@$outLinesRef) =~ /^if\s/) ) {};
                    push(@$outLinesRef, "if ".$condition.$eolComment."\n");
                }
            }
        }
        
        #read block:                
        while(1) {                  
            my @subEntryOutLines = ();
            last if (scalar(@{$self->{inlines}}) <= 0);
            my $isNextEntryVisible = $self->kconfigReadNextEntry(\@subEntryOutLines);
            if ($isNextEntryVisible != 0) {
                push(@$outLinesRef, @subEntryOutLines) if ($isVisible);
            }
            else {
                $self->{wasModified} = 1;
            }
            
            if ($isNextEntryVisible == 2) {
                #Get next entry found endmenu, endchoice or endif.
                #Last line was unshifted back into inLines
                my $closingLine = ( shift @{ $self->{inlines} } );

                if (!defined($closingLine)) {
                    $self->{error} = "Internal Error (1)\n";
                    print STDERR "".$self->{error}."\n" if($showErrs);
                    return;
                }
                if ( $closingLine =~ /^$endStr/) {
                    push(@$outLinesRef, $closingLine) if ($isVisible && !$hideEndif);
                    last;
                }
                else {
                    $self->{error} =  "Unexpected closing line: <".trim($closingLine)."> (expecting <$endStr>)\n";
                    print STDERR "".$self->{error}."\n" if($showErrs);
                    return;
                }   
            }
        }
    }
    return $isVisible;
}

1;