#!/usr/bin/perl -w

# php indenter
#  Reformats your php source code
#
# $Id: phpindent,v 1.1 2002/05/20 12:54:03 weasel Exp $
#
#
# Depends: Parse::RecDescent
# 
#
# (c) 2002 Florian Reitmeir <squat@riot.org>
#          Peter Palfrader <peter@palfrader.org>
#
#     This program is free software; you can redistribute it and/or modify
#     it under the terms of the GNU General Public License as published by
#     the Free Software Foundation; either version 2 of the License, or
#     (at your option) any later version.
#     
#     This program is distributed in the hope that it will be useful,
#     but WITHOUT ANY WARRANTY; without even the implied warranty of
#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#     GNU General Public License for more details.
#     
#     You should have received a copy of the GNU General Public License
#     along with this program; if not, write to the Free Software
#     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
#
#
# Usage: phpindent < orig.php > new.php
#
#
# - It might corrupt your code
# - It might not even work at all
# - It does not understand all of php
# - It's damn slow (several hours for one of our 800 line examples on a gHz
#       CPU)
# - But it was fun to write and merely to test out Parse::RecDescent
#
# It was just a proof of concept - don't use it in production
#
# Did I mention it was slow?
#
#
# Q: So why did you write it?
# A: It was an ad hoc quick hack which went out of control but was
#    real fun.
#
# Q: So are there any real php indenters?
# A: We didn't find any which is why we tried this nonsense at all.
#    If you find any just let us know and we will link to them.
#
# Q: Will you improve this indenter/fix bugs I report?
# A: Probably not. But if you happen to have lots of time we would
#    welcome a patch or two.
#
# Q: You know your grammar sucks?
# A: yepp.
#

#######################################################################
package MyToken;

sub new
{
    my ($class, %args) = @_;
    bless \%args, $class;
}

#######################################################################
package MyBinaryOperator;
@ISA = qw( MyToken );

sub reprint
{
    my ($self) = @_;
    return
    sprintf "%s %s %s",
        $self->{left}->reprint(),
        $self->{'operator'},
        $self->{right}->reprint();
};

#######################################################################
package MyPostOperator;
@ISA = qw( MyToken );

sub reprint
{
    my ($self) = @_;
    return
    sprintf "%s %s",
        $self->{left}->reprint(),
        $self->{'operator'};
};

#######################################################################
package MyPreOperator;
@ISA = qw( MyToken );

sub reprint
{
    my ($self) = @_;
    return
    sprintf "%s %s",
        $self->{'operator'},
        $self->{right}->reprint();
};

#######################################################################
package MyAtom;
@ISA = qw( MyToken );

sub reprint
{
    my ($self) = @_;

    return
    sprintf "%s",
        $self->{'value'};
};

#######################################################################
package MyStatement;
@ISA = qw( MyToken );

sub reprint
{
    my ($self) = @_;

    return
    sprintf "%s;\n",
        $self->{'value'};
};

#######################################################################
package MyComment;
@ISA = qw( MyToken );

sub reprint
{
    my ($self, %args) = @_;
    my $il = $args{'indent'} || 1;
    my $i = "\t" x ($il -1 );

    my $value = $self->{'value'};
    $value =~ s,#\s*,# ,;
    $value =~ s,//\s*,\n\n$i// ,;
    return
        $value."\n";
};

#######################################################################
package MyCommentMultiLine;
@ISA = qw( MyToken );

sub reprint
{
    my ($self, %args) = @_;
    my $il = $args{'indent'}-1 || 0;
    my $i = "\t" x $il;

    my $value = $self->{'value'};
    $value =~ s,/\*\s*\n?,,;
    $value =~ s,\n?\s*\*/,,;
    my @lines = map { s/^\s*\*?[ \t]*/$i * /; $_ } split /\n/, $value;
    unshift @lines, "/*";
    push @lines, "$i */";
    
    return
    sprintf "%s\n",
        join ("\n", @lines)
};

#######################################################################
package MyCommentDoc;
@ISA = qw( MyToken );

sub reprint
{
    my ($self, %args) = @_;
    my $il = $args{'indent'}-1 || 0;
    my $i = "\t" x $il;

    my $value = $self->{'value'};
    $value =~ s,^/\*\*[ \t]*\n?,,;
    $value =~ s,\n?[ \t]*\*/$,,;
    $value =~ s/\t/        /g;
    my @lines = split /\n/, $value;

    my $maxwhitespace;
    for (@lines) {
        my ($leadwhitespace) = ($_ =~ /^(\s*)/);
        $maxwhitespace = (!defined ($maxwhitespace) || length($leadwhitespace) < $maxwhitespace) ?
            length($leadwhitespace) :
            $maxwhitespace;
    };
    my $leadwhitespace = " " x $maxwhitespace;
    @lines = map { s/^$leadwhitespace//; s/^/$i   /; $_ } @lines;

    unshift @lines, "/**";
    push @lines, "$i */";
    
    return
    sprintf "%s\n",
        join ("\n", @lines)
};

#######################################################################
package MyConditionalBlock;
@ISA = qw( MyToken );

sub reprint
{
    my ($self, %args) = @_;
    my $il = $args{'indent'} || 0;
    my $i = "\t" x $il;

    my $block = $self->{block}->reprint(indent => $il, noend=>1);
    if ($block =~ /^\s*\{/) {
        $block = "\t" x ($il-1) . $block;
        $isblock = 1;
    } else {
        $block = "\t" x ($il) . $block;
        $isblock = 0;
    };


    my $elseblock;
    if ($self->{type} eq 'ifelse') {
        $elseblock = $self->{elseblock}->reprint(indent => $il, noend=>1);
        if ($elseblock =~ /^\s*\{/) {
            $elseblock = "\t" x ($il-1) . $elseblock;
            $elseisblock = 1;
        } else {
            $elseblock = "\t" x ($il) . $elseblock;
            $elseisblock = 0;
        };
    };

    if ($self->{type} eq 'if' || $self->{type} eq 'while') {
        return
            $self->{type}.
            " (".
            $self->{condition}->reprint().
            ")\n".
            $block.
            ($isblock ? ";\n" : "");
    } elsif ($self->{type} eq 'ifelse') {
        return
            "if".
            " (".
            $self->{condition}->reprint().
            ")\n".
            $block.
            ($elseisblock ? "\n" : "").
            "\t"x($il-1)  .  "else\n".
            $elseblock.
            ($elseisblock ? ";\n" : "");
    } elsif ($self->{type} eq 'dowhile') {
        return
            "do\n".
            $block.
            " while (".
            $self->{condition}->reprint().
            ");\n";
    } elsif ($self->{type} eq 'for') {
        return
            $self->{type}.
            " (".
            $self->{part1}->reprint().
            "; ".
            $self->{part2}->reprint().
            "; ".
            $self->{part3}->reprint().
            ")\n".
            $block.
            ($isblock ? ";\n" : "");
    } elsif ($self->{type} eq 'foreach') {
        return
            $self->{type}.
            " (".
            $self->{part1}->reprint().
            " as ".
            $self->{part2}->reprint().
            ")\n".
            $block.
            ($isblock ? ";\n" : "");
    };
    
};

#######################################################################
package MyParam;
@ISA = qw( MyToken );

sub reprint
{
    my ($self, %args) = @_;
    my $il = $args{'indent'} || 1;
    my $i = "\t" x $il;

    my $statements = join (", ", map { $_->reprint() } @{$self->{'statements'}});

    return
        $statements;
};

#######################################################################
package MyFunction;
@ISA = qw( MyToken );

sub reprint
{
    my ($self, %args) = @_;
    my $il = $args{'indent'} || 1;
    my $i = "\t" x $il;

    return
        $self->{name}->reprint().
        " (".
        ((defined $self->{params}) ? $self->{params}->reprint() : "").
        ")";
};

#######################################################################
package MyBlock;
@ISA = qw( MyToken );

sub reprint
{
    my ($self, %args) = @_;
    my $il = $args{'indent'} || 1;
    my $i = "\t" x $il;

    my $statements = join ($i, map { $_->reprint(indent => $il + 1) } @{$self->{'statements'}});

    $statements = $i.$statements if ( substr($statements,0,1) ne "\n" );
    
    return
        ((defined $args{'nobraces'}) ? "" : "{\n").
        $statements.
        "\t" x ($il-1).
        ((defined $args{'nobraces'}) ? "" : "}").
        ((defined $args{'noend'}) ? "" : ";\n");
};

#######################################################################
package MyFunctionDefinition;
@ISA = qw( MyToken );

sub reprint
{
    my ($self, %args) = @_;

    my $il = $args{'indent'} || 1;
    my $i = "\t" x ($il-1);

    return
        "\n".
        $i."function ".
        $self->{'header'}->reprint().  "\n".
        $i.$self->{'block'}->reprint(indent => $il)."\n";
};




#######################################################################
package main;

use strict;
use Parse::RecDescent;


my $input;
local $/ = undef;
$input = <>;

my $grammar = q
{
    Script:             /<\?(php)?/ Block '?>'      { MyAtom->new ( value => "<?\n" . $item[2]->reprint(nobraces=>1) ."?>\n" ) }

    GroupedBlock:       '{' Block '}' /;?/          { MyBlock->new ( statements => [ @{$item[2]->{'statements'}} ] ) }

    Block:              Statement Block         { MyBlock->new ( statements => [ $item[1], @{$item[2]->{'statements'}} ] ) }
                    |   Statement               { MyBlock->new ( statements => [ $item[1] ] ) }

    Statement:          Expression ';'          { MyStatement->new ( value => $item[1]->reprint() ) }
                    |   ';'                     { MyStatement->new ( value => '' ) }
                    |   GroupedBlock ';'        { $item[1] }
                    |   GroupedBlock
                    |   Comment
                    |   IfThenElseBlock
                    |   IfBlock
                    |   WhileBlock
                    |   DoWhileBlock
                    |   ForLoop
                    |   ForEachLoop
                    |   FunctionDefinition

    FunctionDefinition: "function" FunctionCall GroupedBlock
                                                    { MyFunctionDefinition->new ( header => $item[2], block => $item[3] ) }


    IfThenElseBlock:    "if" "(" Expression ")" Statement "else" Statement
                                                { MyConditionalBlock->new ( type      => 'ifelse',
                                                                            condition => $item[3],
                                                                            block   => $item[5],
                                                                            elseblock => $item[7] ) }
    IfBlock:            "if" "(" Expression ")" Statement
                                                { MyConditionalBlock->new ( type      => 'if',
                                                                            condition => $item[3],
                                                                            block     => $item[5] ) }
    WhileBlock:         "while" "(" Expression ")" Statement
                                                { MyConditionalBlock->new ( type      => 'while',
                                                                            condition => $item[3],
                                                                            block     => $item[5] ) }
    DoWhileBlock:       "do" GroupedBlock "while" "(" Expression ")" ";"
                                                { MyConditionalBlock->new ( type      => 'dowhile',
                                                                            condition => $item[5],
                                                                            block     => $item[2] ) }
    ForLoop:            "for" "(" Expression ";" Expression ";" Expression ")" Statement
                                                { MyConditionalBlock->new ( type      => 'for',
                                                                            part1     => $item[3],
                                                                            part2     => $item[5],
                                                                            part3     => $item[7],
                                                                            block     => $item[9] ) }
    ForEachLoop:        "foreach" "(" Expression "as" Expression ")" Statement
                                                { MyConditionalBlock->new ( type      => 'foreach',
                                                                            part1     => $item[3],
                                                                            part2     => $item[5],
                                                                            block     => $item[7] ) }
    Expression:         "(" Expression ")"      { $item[2] }
                    |   FunctionCall Operator Expression
                                                { MyBinaryOperator->new ( left=>$item[1], operator=>$item[2], right=>$item[3] ) }
                    |   Atom Operator Expression
                                                { MyBinaryOperator->new ( left=>$item[1], operator=>$item[2], right=>$item[3] ) }
                    |   Atom Operator           { MyPostOperator->new ( left=>$item[1], operator=>$item[2] ) }
                    |   PreOperator Expression  { MyPreOperator->new ( right=>$item[2], operator=>$item[1] ) }
                    |   FunctionCall
                    |   Atom

                                                
    Comment:            CommentHash
                    |   CommentSlash
                    |   CommentDoc
                    |   CommentMultiLine

    CommentHash:        /#.*?$/m                { MyComment->new ( value => $item[1] ) }
    CommentSlash:       /\/\/.*?$/m             { MyComment->new ( value => $item[1] ) }
    CommentDoc:         /\/\*\*.*?\*\//s        { MyCommentDoc->new ( value => $item[1] ) }
    CommentMultiLine:   /\/\*.*?\*\//s          { MyCommentMultiLine->new ( value => $item[1] ) }

    Operator:            '==='
                    |   '!=='
                    |   '+='
                    |   '-='
                    |   '=='
                    |   '!='
                    |   '=>'
                    |   '<='
                    |   '>='
                    |   '++'
                    |   '--'
                    |   '&&'
                    |   '.='
                    |   '||'
                    |   '&'
                    |   '|'
                    |   '>'
                    |   '<'
                    |   '='
                    |   '/'
                    |   '+'
                    |   '-'
                    |   '*'
                    |   '.'
                    |   ':'
                    |   '?'

    PreOperator:        "new"
                    |   "print"
                    |   "return"
                    |   "echo"
                    |   "not"
                    |   "++"
                    |   "--"
                    |   "-"
                    |   "+"
                    |   "1"
                    |   "!"



    FunctionCall:       Atom "(" FunctionParameter ")"
                                                { MyFunction->new ( name => $item[1], params => $item[3] ) }
                    |   Atom "(" ")"
                                                { MyFunction->new ( name => $item[1] ) }

    Atom:               Variable
                    |   String
                    |   Identifier
                    |   Numerical


    FunctionParameter:  Expression "," FunctionParameter
                                                { MyParam->new ( statements => [ $item[1], @{$item[3]->{'statements'}} ] ) }
                    |   Expression              { MyParam->new ( statements => [ $item[1] ] ) }

    
    Variable:           '$' Identifier Array
                                                { MyAtom->new ( value => '$'.$item[2]->reprint() . $item[3]->reprint() ) }
                    |   '$' Identifier          { MyAtom->new ( value => '$'.$item[2]->reprint() ) }


    Array:              "[" "]" Array           { MyAtom->new ( value => '[]'.$item[3]->reprint() ) }
                    |   "[" Expression "]" Array
                                                { MyAtom->new ( value => '['.$item[2]->reprint().']'.$item[4]->reprint() ) }
                    |   "[" Expression "]"      { MyAtom->new ( value => '['.$item[2]->reprint().']' ) }
                    |   "[" "]"                 { MyAtom->new ( value => '[]' ) }


    Identifier:         SimpleIdentifier "->" Identifier
                                                { MyAtom->new ( value => $item[1]->reprint() ."->" .$item[3]->reprint() ) }
                    |   SimpleIdentifier

    SimpleIdentifier:   /[a-zA-Z][a-zA-Z0-9_]*/ { MyAtom->new ( value => $item[1] ) }

    Numerical:          /[\d]+(\.[\d+])?/       { MyAtom->new ( value => $item[1] ) }

    String:             EmptyDQString
                    |   NotEmptyDQString
                    |   EmptySQString
                    |   NotEmptySQString
    NotEmptyDQString:       /".*?[^\\\\]"/s     { MyAtom->new ( value => $item[1] ) }
    EmptyDQString:      '""'                    { MyAtom->new ( value => '""' ) }
    NotEmptySQString:       /'.*?[^\\\\]'/s     { MyAtom->new ( value => $item[1] ) }
    EmptySQString:      "''"                    { MyAtom->new ( value => "''" ) }
};

#$::RD_HINT = 1;
my $parser = new Parse::RecDescent ($grammar);
my $tree = $parser->Script($input);

print $tree->reprint();



# vim:set ts=4:
# vim:set shiftwidth=4:

syntax highlighted by Code2HTML, v. 0.9.1