// narcissus.tree2code.js
// 
// Copyright (c) 2010 Guillaume Lathoud
// MIT License
//
// Take a Narcissus tree (as produced by ./narcissus.jsparse.js) and
// generate code.
//
// Optionally, one can map parts of the code using `mapper`
// (e.g. to rename variables, or replace entire chunks of code).

/*global array from narcissus log number*/

from.req('narcissus.jsdef.js')('narcissus.tree2code.js', function () {
    
    var jsdef = narcissus.jsdef

    , fallback = function (/*object | any*/o, /*?string?*/p, /*?string?*/fb_str) {
        return (p ? o[p] : o) || (fb_str || "");
    }

    , fb = fallback

    , t2c

    , loop = function ( tree, /*?function?*/mapper, /*?string?*/join_str ) {
        return array.map( tree, function (t) { return t2c(t, mapper); } )
            .join( join_str || '');
    }
    
    ;

    narcissus.tree2code_safe = function (/*object*/tree, /*?function?*/mapper) {
        
        var ret = { 
            code: ""
            , errors: []
            , warnings: []
        };
        
        try {
            ret.code = narcissus.tree2code( tree, mapper );
        } catch (e) {
            ret.errors.push( 'narcissus.tree2code() caught an exception: "' + e + '"' );
        }
        return ret;
    };
    

    t2c = narcissus.tree2code = function (/*object*/tree, /*?function?*/mapper) {
        
        // The user can replace parts of the code through `mapper`
        // (e.g. rename variables, or replace entire chunks of code).

        var ret = tree && mapper && mapper(tree);
        if (typeof ret === 'string')
            return ret; // string

        var 
        loop_m  = function (t, j) { return loop(t, mapper, j); }
        , t2c_m = mapper ? function (t) { return t2c(t, mapper); } : t2c
        ;

        // Standard case: no user mapping, just follow the code
        // structure and spit out code (string).

        return (function (f) { 

            var x = f();
            return fb(tree && (fb(x, "flesh") || 
                               ( fb(tree, "value") + 
                                 fb(x,    "before") + 
                                 loop_m( tree ) + 
                                 fb(x,    "after") )));
            
        })( /*f*/ function () {

            if (!tree)
                return "";
            
            var x = {};

            switch (tree.type) {
                
            case jsdef.AND:
            case jsdef.BITWISE_AND:
            case jsdef.BITWISE_NOT:
            case jsdef.BITWISE_OR:
            case jsdef.BITWISE_XOR:
            case jsdef.DIV:
            case jsdef.EQ:
            case jsdef.GE:
            case jsdef.GT:
            case jsdef.LSH:
            case jsdef.LE:
            case jsdef.LT:
            case jsdef.MINUS:
            case jsdef.MOD:
            case jsdef.MUL:
            case jsdef.NE:
            case jsdef.OR:
            case jsdef.PLUS:
            case jsdef.RSH:
            case jsdef.STRICT_EQ:
            case jsdef.STRICT_NE:
            case jsdef.URSH:

                x.flesh = t2c_m( tree[0] ) + jsdef.tokens[ tree.type ] + t2c_m( tree[1] );

                break;

            case jsdef.ARRAY_INIT:

                x.flesh = '[' + loop_m( tree, ',' ) + ']';

                break;

            case jsdef.ASSIGN:
                
                x.flesh = t2c_m( tree[0] ) + jsdef.tokens[ tree.type ] + t2c_m( tree[1] );

                break;

            case jsdef.BLOCK:

                x.after = '}';

                break;

            case jsdef.BREAK:
            case jsdef.CONTINUE:

                x.after = (tree.label ? (' ' + tree.label) : '') + ';';

                break;

            case jsdef.CALL:

                x.flesh = t2c_m( tree[0] ) + t2c_m( tree[1] );

                break;

            case jsdef.CASE:

                x.after = ' ' + t2c_m( tree.caseLabel ) + ':' + loop_m( tree.statements );

                break;

            case jsdef.CATCH:

                x.after = '(' + tree.varName + ')' + t2c_m( tree.block );

                break;

            case jsdef.COLON:
            case jsdef.CONDITIONAL:
            case jsdef.ELSE:
            case jsdef.END:
            case jsdef.LEFT_BRACKET:
            case jsdef.LEFT_CURLY:
            case jsdef.LEFT_PAREN:
            case jsdef.NEWLINE:
            case jsdef.RIGHT_BRACKET:
            case jsdef.RIGHT_CURLY:
            case jsdef.RIGHT_PAREN:
                
                throw new Error('narcissus.tree2code: unexpected encounter with type ' + tree.type + 
                                ', token: ' + jsdef.tokens[ tree.type ]);

                break;

            case jsdef.COMMA:

                x.flesh = loop_m( tree, ',' );

                break;

            case jsdef.CONST:
            case jsdef.DEBUGGER:
            case jsdef.ENUM:
                
                throw new Error(jsdef.tokens[ tree.type ] + ' is a FutureReservedWord in ECMAScript 3!');

                break;
                
            case jsdef.DECREMENT:
            case jsdef.INCREMENT:
                
                var tmp = [ jsdef.tokens[ tree.type ], t2c_m(tree[0]) ];
                if (tree.postfix)
                    tmp.reverse();
                
                x.flesh = tmp.join('');

                break;

            case jsdef.DEFAULT:

                x.after = ':' + loop_m( tree.statements );

                break;

            case jsdef.DELETE:
            case jsdef.TYPEOF:
            case jsdef.VOID:

                x.before = " ";

                break;

            case jsdef.DO:

                x.flesh = 'do ' + t2c_m( tree.body ) + ' while(' + t2c_m( tree.condition ) + ');';

                break;

            case jsdef.DOT:

                x.flesh = loop_m( tree, '.' );

                break;

            case jsdef.FALSE:
            case jsdef.NULL:
            case jsdef.TRUE:

                x.flesh = tree.value;

                break;

            case jsdef.FINALLY:
                
                x.after = t2c_m( tree.block );

                break;

            case jsdef.FOR:

                var setup = t2c_m( tree.setup ) || '';
                if (!/;\s*$/.test(setup))
                    setup += ';';

                x.flesh = 'for(' + setup +
                    loop_m( [ tree.condition, tree.update ], ';' ) + 
                    ')' + 
                    t2c_m( tree.body );

                break;

            case jsdef.FOR_IN:

                x.flesh = 'for(' + (tree.varDecl ? 'var ' : '') + t2c_m(tree.iterator) + ' in ' + t2c_m(tree.object) + ')' +
                    t2c_m(tree.body);

                break;

            case jsdef.FUNCTION:

                x.after = (tree.name ? (' ' + tree.name) : '') + '(' + tree.params.join( ',' ) + ')' + t2c_m( tree.body );

                break;

            case jsdef.GETTER:

                throw new Error('GETTER is not part of ECMAScript 3!');

                break;

            case jsdef.GROUP:

                x.after = ')';

                break;

            case jsdef.HOOK:

                x.flesh = t2c_m(tree[0]) + '?' + t2c_m(tree[1]) + ':' + t2c_m(tree[2]);

                break;

            case jsdef.IDENTIFIER:

                if (tree.initializer) 
                    x.after = '=' + t2c_m(tree.initializer);

                break;

            case jsdef.IF:
                
                x.flesh = 'if(' + t2c_m(tree.condition) + ')' + t2c_m(tree.thenPart);

                if (tree.elsePart)
                    x.flesh += 'else' + ((tree.elsePart.type === jsdef.BLOCK) ? '' : ' ') + t2c_m(tree.elsePart);
                
                break;
                
            case jsdef.IN:
            case jsdef.INSTANCEOF:

                x.flesh = [ t2c_m( tree[0] ), jsdef.tokens[ tree.type ], t2c_m( tree[1] ) ].join(' ');

                break;

            case jsdef.INDEX:

                x.flesh = t2c_m(tree[0]) + '[' + t2c_m(tree[1]) + ']';

                break;

            case jsdef.LABEL:

                x.flesh = tree.label + ':' + (tree.statement && t2c_m( tree.statement ));

                break;

            case jsdef.LIST:

                x.flesh = "(" + loop_m( tree, ',' ) + ")";

                break;

            case jsdef.NEW:

                x.before = " ";

                break;

            case jsdef.NEW_WITH_ARGS:

                x.before = " ";

                break;

            case jsdef.NOT:

                break;

            case jsdef.NUMBER:

                x.flesh = '' + tree.value;

                break;

            case jsdef.OBJECT_INIT:

                x.flesh = '{' + loop_m( tree, ',' ) + '}';

                break;

            case jsdef.PROPERTY_INIT:

                x.flesh = loop_m( tree, ':' );

                break;

            case jsdef.REGEXP:

                break;

            case jsdef.RETURN:

                x.flesh = 'return ' + ((tree.value instanceof Object) ? t2c_m(tree.value) : '') + ';';

                break;

            case jsdef.SCRIPT:
                
                if (tree.value === '{')
                    x.after = '}';

                break;

            case jsdef.SEMICOLON:

                x.flesh = t2c_m( tree.expression ) + ';';
                
                break;

            case jsdef.SETTER:

                throw new Error('SETTER is not part of ECMAScript 3!');

                break;

            case jsdef.STRING:
                
                x.flesh = tree.tokenizer.source.substring( tree.start, tree.end );
                
                break;
                
            case jsdef.SWITCH:

                x.after = '(' + t2c_m(tree.discriminant) + '){' + loop_m(tree.cases) + '}';
                
                break;

            case jsdef.THIS:

                break;

            case jsdef.THROW:

                x.flesh = "throw " + t2c_m( tree.exception ) + ";";

                break;
                
            case jsdef.TRY:

                x.flesh = tree.value + t2c_m(tree.tryBlock) + loop_m(tree.catchClauses) + 
                    (tree.finallyBlock ? ('finally' + t2c_m(tree.finallyBlock)) : '');

                break;

            case jsdef.UNARY_MINUS:
            case jsdef.UNARY_PLUS:

                break;

            case jsdef.VAR:

                x.flesh = "var " + loop_m(tree, ',') + ';';

                break;

            case jsdef.WHILE:

                x.flesh = 'while(' + t2c_m( tree.condition ) + ')' + t2c_m( tree.body ); 

                break;

            case jsdef.WITH:

                x.flesh = "with(" + t2c_m( tree.object ) + ")" + t2c_m( tree.body );

                break;

            default:
                throw new Error("narcissus.tree2code(): unexpected tree.type:" + tree.type);
            }


            if (number.isFiniteNumber( tree.assignOp )) {
                if (x.flesh)
                    x.flesh += jsdef.tokens[ tree.assignOp ];
                else if (!x.after)
                    x.after = jsdef.tokens[ tree.assignOp ];
                else
                    throw new Error('narcissus.tree2code(): I am buggy! Issue with `tree.assignOp` for `tree.type`:' + tree.type);
            }
            
            return x;        
        });

    }    
    ;

});

