package Pugs::Emitter::Perl6::Perl5;

# p6-ast to perl5 emitter

use strict;
use warnings;
use Data::Dumper;
$Data::Dumper::Indent = 1;

sub _mangle_var {
    my $s = shift;
    # globals
    return '$::_EXCL_' if $s eq '$!';  

    substr($s,1)   =~ s/ ([^a-zA-Z0-9_:]) / '_'.ord($1).'_' /xge;
    return $s;
}

sub _not_implemented {
    my ( $n, $what ) = @_;
    return "die q(not implemented $what: " . Dumper( $n ) . ")";
}

sub emit {
    my ($grammar, $ast) = @_;
    # runtime parameters: $grammar, $string, $state, $arg_list
    # rule parameters: see Runtime::Rule.pm
    return _emit( $ast, '' );
        #"do{\n" .
        #_emit( $ast, '    ' ) . "\n" .
        #"}";
}

sub _emit {
    my $n = $_[0];
    #die "_emit: ", Dumper( $n ); 
    #warn "fixity: $n->{fixity}\n" if exists $n->{fixity};
    
    # 'undef' example: parameter list, in a sub call without parameters
    return ''
        unless defined $n;
    
    die "unknown node: ", Dumper( $n )
        unless ref( $n ) eq 'HASH';
        
    return $n->{bareword} 
        if exists $n->{bareword};
        
    return $n->{code} 
        if exists $n->{code};
        
    return $n->{int} 
        if exists $n->{int};
        
    return $n->{num} 
        if exists $n->{num};
        
    return _mangle_var( $n->{scalar} )
        if exists $n->{scalar};
        
    return $n->{array} 
        if exists $n->{array};
        
    return $n->{hash} 
        if exists $n->{hash};
        
    return '"' . $n->{double_quoted} . '"' 
        if exists $n->{double_quoted};
            
    return '\'' . $n->{single_quoted} . '\'' 
        if exists $n->{single_quoted};
            
    return 'qw(' . $n->{angle_quoted} . ')' 
        if exists $n->{angle_quoted};
            
    return assoc_list( $n )
        if exists $n->{assoc}  && $n->{assoc}  eq 'list';
        
    if ( exists $n->{fixity} ) {
        return infix( $n )
            if $n->{fixity} eq 'infix';
        return prefix( $n )
            if $n->{fixity} eq 'prefix';
        return postfix( $n )
            if $n->{fixity} eq 'postfix';
        return circumfix( $n )
            if $n->{fixity} eq 'circumfix';
        return postcircumfix( $n )
            if $n->{fixity} eq 'postcircumfix';
    }
    
    return statement( $n )
        if ref $n->{op1} && exists $n->{op1}{stmt};

    return default( $n );
}

sub assoc_list {
    my $n = $_[0];
    # print "list emit_rule: ", Dumper( $n );

    if ( $n->{op1} eq ';' ||
         $n->{op1} eq ',' ) {
        return join ( $n->{op1} . "\n", 
            map { _emit( $_ ) } @{$n->{list}} 
        );
    }
    
    return _not_implemented( $n->{op1}, "list-op" );
}

sub _emit_parameter_binding {
    my $n = $_[0];
    
    # no parameters
    return ''
        if  ! defined $n ||
            @$n == 0;
    
    # XXX - $n should be hashref?
    #warn "parameter list: ",Dumper $n->[0];
    
    my $param = _emit( $n->[0] );
    return "my ($param) = \@_;\n";
        
    #if ( @$n == 1 ) {
    #    # just one parameter
    #    my $param = _emit( $n->[0] );
    #    return "my $param = \$_[0];\n";
    #}
    #
    #return " # XXX - " . (scalar @$n) . " parameters\n";
}

sub default {
    my $n = $_[0];
    #warn "emit: ", Dumper( $n );
    
    if ( exists $n->{bare_block} ) {
        if ( exists $n->{trait} ) {
            # BEGIN/END
            return $n->{trait} . " {\n" . _emit( $n->{bare_block} ) . "\n }";
        }
        return  "{\n" . _emit( $n->{bare_block} ) . "\n }\n";
    }

    if ( $n->{op1} eq 'call' ) {
        # warn "call: ",Dumper $n;
        if ( $n->{sub}{bareword} eq 'use' ) {
            # use v6-pugs
            if ( exists $n->{param}{cpan_bareword} ) {
                if ( $n->{param}{cpan_bareword} eq 'v6-pugs' ) {
                    return " # use v6-pugs\n";
                }
            }
            #warn "call: ",Dumper $n;
            if ( $n->{param}{sub}{bareword} eq 'v5' ) {
                return "warn 'use v5 - not implemented'";
            }
            if ( $n->{param}{sub}{bareword} eq 'v6' ) {
                return " # use v6\n";
            }
            # use module::name 'param'
            return "use " . _emit( $n->{param} );
        }
        return " " . $n->{sub}{bareword} . " '', " . _emit( $n->{param}, '  ' ) 
            if $n->{sub}{bareword} eq 'print' ||
               $n->{sub}{bareword} eq 'warn';
        return " print '', " . _emit( $n->{param}, '  ' ) . ";\n" .
            " print " . '"\n"'
            if $n->{sub}{bareword} eq 'say';
        return ' ' . $n->{sub}{bareword} . '(' . _emit( $n->{param}, '  ' ) . ')';
    }
    
    if ( $n->{op1} eq 'method_call' ) {    
        if ( $n->{method}{bareword} eq 'print' ||
             $n->{method}{bareword} eq 'warn' ) {
            return " print '', " . _emit( $n->{self}, '  ' );
        }
        if ( $n->{method}{bareword} eq 'say' ) {
            return " print '', " . _emit( $n->{self}, '  ' ) . ', "\n"';
        }
        #warn "method_call: ", Dumper( $n );
        
        # "autobox"
        
        if ( exists $n->{self}{code} ) {
            # &code.goto;
            return 
                " \@_ = (" . _emit( $n->{param}, '  ' ) . ");\n" .
                " " . _emit( $n->{method}, '  ' ) . " " .
                    _emit( $n->{self}, '  ' );
        }
        
        if ( exists $n->{self}{scalar} ) {
            # $scalar.++;
            return 
                " Pugs::Runtime::Perl6::Scalar::" . _emit( $n->{method}, '  ' ) . 
                "(" . _emit( $n->{self}, '  ' ) .
                ", " . _emit( $n->{param}, '  ' ) . ")" ;
        }
        
        # normal methods
        
        return " " . $n->{sub}{bareword} .
            '(' .
            join ( ";\n", 
                map { _emit( $_ ) } @{$n->{param}} 
            ) .
            ')';
    }

    return _not_implemented( $n, "syntax" );
}

sub statement {
    my $n = $_[0];
    #warn "statement: ", Dumper( $n );
    
    if ( $n->{op1}{stmt} eq 'if'     || 
         $n->{op1}{stmt} eq 'unless' ) {
        return  " " . $n->{op1}{stmt} . 
                '(' . _emit( $n->{exp1} ) . ')' .
                " {\n" . _emit( $n->{exp2} ) . "\n }\n" .
                " else" .
                " {\n" . _emit( $n->{exp3} ) . "\n }";
    }

    if ( $n->{op1}{stmt} eq 'sub' ) {
        #warn "sub: ",Dumper $n;
        return  " " . $n->{op1}{stmt} . 
                ' ' . $n->{name}{bareword} . 
                " {\n" . 
                    _emit_parameter_binding( $n->{signature} ) .
                    _emit( $n->{block} ) . 
                "\n }";
    }

    if ( $n->{op1}{stmt} eq 'for' ) {
        #warn "sub: ",Dumper $n;
        if ( exists $n->{exp2}{pointy_block} ) {
            return  " " . $n->{op1}{stmt} . 
                    ' my ' . _emit( $n->{exp2}{signature} ) . '' . 
                    ' (' . _emit( $n->{exp1} ) . ')' . 
                    " {\n" . 
                        # _emit_parameter_binding( $n->{signature} ) .
                        _emit( $n->{exp2}{pointy_block} ) . 
                    "\n }";
        }
        return  " " . $n->{op1}{stmt} . 
                ' (' . _emit( $n->{exp1} ) . ')' . 
                " {\n" . 
                    # _emit_parameter_binding( $n->{signature} ) .
                    _emit( $n->{exp2} ) . 
                "\n }";
    }

    return _not_implemented( $n, "statement" );
}

sub infix {
    my $n = $_[0];
    # print "infix: ", Dumper( $n );
    
    if ( $n->{op1}{op} eq '~' ) {
        return _emit( $n->{exp1} ) . ' . ' . _emit( $n->{exp2} );
    }
    
    if ( $n->{op1}{op} eq ':=' ) {
        #warn "bind: ", Dumper( $n );
        return " tie " . _emit( $n->{exp1} ) . 
            ", 'Pugs::Runtime::Perl6::Scalar::Alias', " .
            "\\" . _emit( $n->{exp2} );
    }

    if ( exists $n->{exp2}{bare_block} ) {
        # $a = { 42 } 
        return " " . _emit( $n->{exp1} ) . ' ' . 
            $n->{op1}{op} . ' ' . "sub " . _emit( $n->{exp2} );
    }

    return _emit( $n->{exp1} ) . ' ' . 
        $n->{op1}{op} . ' ' . _emit( $n->{exp2} );
}

sub circumfix {
    my $n = $_[0];
    # print "infix: ", Dumper( $n );
    
    if ( $n->{op1}{op} eq '(' &&
         $n->{op2}{op} eq ')' ) {
        return '()'
            unless defined  $n->{exp1};
        return '(' . _emit( $n->{exp1} ) . ')';
    }
    
    return _not_implemented( $n, "circumfix" );
}

sub postcircumfix {
    my $n = $_[0];
    #print "postcircumfix: ", Dumper( $n );
    
    if ( $n->{op1}{op} eq '[' &&
         $n->{op2}{op} eq ']' ) {
        #return '()'
        #    unless defined  $n->{exp1};
        
        # avoid p5 warning - "@a[1] better written as $a[1]"
        if (   (  exists $n->{exp2}{int} 
               || exists $n->{exp2}{scalar} 
               ) 
               && exists $n->{exp1}{array} ) {
            my $name = _emit( $n->{exp1} );
            $name =~ s/^\@/\$/;
            return $name . '[' . _emit( $n->{exp2} ) . ']';
        }
        
        return _emit( $n->{exp1} ) . '[' . _emit( $n->{exp2} ) . ']';
    }
    
    return _not_implemented( $n, "postcircumfix" );
}

sub prefix {
    my $n = $_[0];
    # print "prefix: ", Dumper( $n );
    
    if ( $n->{op1}{op} eq 'my' ||
         $n->{op1}{op} eq 'our' ) {
        return $n->{op1}{op} . ' ' . _emit( $n->{exp1} );
    }
    if ( $n->{op1}{op} eq 'try' ) {
        #warn "try: ", Dumper( $n );
        #if ( exists $n->{trait} ) {
        #    # CATCH/CONTROL
        #    return $n->{trait} . " {\n" . _emit( $n->{bare_block} ) . "\n }";
        #}
        return 'eval ' . _emit( $n->{exp1} ) . "; " . 
            _mangle_var( '$!' ) . " = \$@;";
    }
    if ( $n->{op1}{op} eq 'eval' ) {
        return 
            'do { ' . 
            'use Pugs::Compiler::Perl6; ' . # XXX - load at start
            'local $@; ' .
            # call Perl::Tidy here? - see v6.pm ???
            'my $p6 = Pugs::Compiler::Perl6->compile( ' . _emit( $n->{exp1} ) . ' ); ' .
            'my @result = eval $p6->{perl5}; ' .     # XXX - test want()
            _mangle_var( '$!' ) . ' = $@; ' .
            '@result }';  # /do
    }
    if ( $n->{op1}{op} eq '++' ||
         $n->{op1}{op} eq '--' ||
         $n->{op1}{op} eq '+'  ) {
        return $n->{op1}{op} . _emit( $n->{exp1} );
    }
    
    return _not_implemented( $n, "prefix" );
}

sub postfix {
    my $n = $_[0];
    # print "postfix: ", Dumper( $n );

    if ( $n->{op1}{op} eq '++' ||
         $n->{op1}{op} eq '--' ) {
        return _emit( $n->{exp1} ) . $n->{op1}{op};
    }
    
    return _not_implemented( $n, "postfix" );
}

1;
