PageRenderTime 6ms CodeModel.GetById 91ms app.highlight 106ms RepoModel.GetById 13ms app.codeStats 0ms

/data/addon/scripts/jsdecoder.js

https://github.com/jinbo51/DiscuzX
JavaScript | 1149 lines | 1131 code | 9 blank | 9 comment | 2 complexity | 47379b5dd85ef8e50a88b4c27f253ff3 MD5 | raw file
   1/*
   2 * DO NOT REMOVE THIS NOTICE
   3 *
   4 * PROJECT:   JsDecoder
   5 * VERSION:   1.1.0
   6 * COPYRIGHT: (c) 2004-2008 Cezary Tomczak
   7 * LINK:      http://code.gosu.pl
   8 * LICENSE:   GPL
   9 */
  10
  11function JsDecoder()
  12{
  13    this.s = '';
  14    this.len = 0;
  15    
  16    this.i = 0;
  17    this.lvl = 0; /* indent level */
  18    this.code = [''];
  19    this.row = 0;
  20    this.switches = [];
  21
  22    this.lastWord = '';
  23    this.nextChar = '';
  24    this.prevChar = '';
  25    this.isAssign = false;
  26
  27    this.decode = function ()
  28    {
  29        this.s = this.s.replace(/[\r\n\f]+/g, "\n");
  30        this.len = this.s.length;
  31        while (this.i < this.len)
  32        {
  33            var c = this.s.charAt(this.i);
  34            this.charInit();
  35            this.switch_c(c);
  36            this.i++;
  37        }
  38        return this.code.join("\n");
  39    };
  40    this.switch_c = function(c)
  41    {
  42        switch (c)
  43        {
  44            case "\n":
  45                this.linefeed(); 
  46                break;
  47
  48            case ' ':
  49            case "\t":
  50                this.space();
  51                break;
  52
  53            case '{':  this.blockBracketOn();  	break;
  54            case '}':  this.blockBracketOff(); 	break;
  55
  56            case ':':  this.colon();     		break;
  57            case ';':  this.semicolon(); 		break;
  58
  59            case '(':  this.bracketOn();        break;
  60            case ')':  this.bracketOff();       break;
  61            case '[':  this.squareBracketOn();  break;
  62            case ']':  this.squareBracketOff(); break;
  63
  64            case '"':
  65            case "'":
  66                this.quotation(c);
  67                break;
  68
  69            case '/':
  70                if ('/' == this.nextChar) {
  71                    this.lineComment();
  72                } else if ('*' == this.nextChar) {
  73                    this.comment();
  74                } else {
  75                    this.slash();
  76                }
  77                break;
  78
  79            case ',':  this.comma(); break;
  80            case '.':  this.dot(); break;
  81
  82            case '~':
  83            case '^':
  84                this.symbol1(c);
  85                break;
  86
  87            case '-': case '+': case '*': case '%':
  88            case '<': case '=': case '>': case '?':
  89            case ':': case '&': case '|': case '/':
  90                this.symbol2(c);
  91                break;
  92
  93            case '!':
  94                if ('=' == this.nextChar) {
  95                    this.symbol2(c);
  96                } else {
  97                    this.symbol1(c);
  98                }
  99                break;
 100
 101            default:
 102                if (/\w/.test(c)) { this.alphanumeric(c); }
 103                else { this.unknown(c); }
 104                break;
 105        }
 106        c = this.s.charAt(this.i);
 107        if (!/\w/.test(c)) {
 108            this.lastWord = '';
 109        }
 110    };
 111    this.blockBracketOn = function ()
 112    {
 113        this.isAssign = false;
 114        var nextNW = this.nextNonWhite(this.i);
 115        if ('}' == nextNW) {
 116            var ss = (this.prevChar == ')' ? ' ' : '');
 117            this.write(ss+'{');
 118            this.lvl++;
 119            return;
 120            
 121        }
 122        if (/^\s*switch\s/.test(this.getCurrentLine())) {
 123            this.switches.push(this.lvl);
 124        }
 125        var line = this.getCurrentLine();
 126        var line_row = this.row;
 127        var re = /(,)\s*(\w+\s*:\s*function\s*\([^\)]*\)\s*)$/;
 128        if (re.test(line)) {
 129            this.replaceLine(this.code[line_row].replace(re, '$1'));
 130            this.writeLine();
 131            var match = re.exec(line);
 132            this.write(match[2]);
 133        }
 134
 135        /* example: return {
 136            title: 'Jack Slocum',
 137            iconCls: 'user'}
 138            After return bracket cannot be on another line
 139        */
 140        if (/^\s*return\s*/.test(this.code[this.row])) {
 141            if (/^\s*return\s+\w+/.test(this.code[this.row])) {
 142                this.writeLine();
 143            } else if (this.prevChar != ' ') {
 144                this.write(' ');
 145            }
 146            this.write('{');
 147            this.writeLine();
 148            this.lvl++;
 149            return;
 150        }
 151
 152        if (/function\s*/.test(this.code[this.row]) || this.isBlockBig()) {
 153            //this.writeLine();
 154        } else {
 155            if (this.prevChar != ' ' && this.prevChar != "\n" && this.prevChar != '(') {
 156                /*  && this.prevChar != '(' && this.prevChar != '[' */
 157                this.write(' ');
 158            }
 159        }
 160        this.write('{');
 161        this.lvl++;
 162        if ('{' != nextNW) {
 163            this.writeLine();
 164        }
 165    };
 166    this.isBlockBig = function()
 167    {
 168        var i = this.i + 1;
 169        var count = 0;
 170        var opened = 0;
 171        var closed = 0;
 172        while (i < this.len - 1)
 173        {
 174            i++;
 175            var c = this.s.charAt(i);
 176            if (/\s/.test(c)) {
 177                continue;
 178            }
 179            if ('}' == c && opened == closed) {
 180                break;
 181            }
 182            if ('{' == c) { opened++; }
 183            if ('}' == c) { closed++; }
 184            count++;
 185            if (count > 80) {
 186                return true;
 187            }
 188        }
 189        return (count > 80);
 190    };
 191    this.blockBracketOff = function ()
 192    {
 193        var nextNW = this.nextNonWhite(this.i);
 194        var prevNW = this.prevNonWhite(this.i);
 195        var line = this.getCurrentLine();
 196
 197        if (prevNW != '{')
 198        {
 199            if (line.length && nextNW != ';' && nextNW != '}' && nextNW != ')' && nextNW != ',') {
 200                //this.semicolon();
 201                this.writeLine();
 202            } else if (line.length && prevNW != ';' && nextNW == '}' && this.isAssign) {
 203                this.semicolon();
 204            } else if (line.length && this.isAssign && prevNW != ';') {
 205                this.semicolon();
 206            } else if (line.length && prevNW != ';') {
 207                if (/^\s*(else)?\s*return[\s(]+/i.test(line)) {
 208                    this.semicolon();
 209                } else {
 210                    this.writeLine();
 211                }
 212            }
 213        }
 214        this.write('}');
 215
 216        if (',' == nextNW) {
 217            this.write(',');
 218            this.goNextNonWhite();
 219        }
 220        var next3 = this.nextManyNW(3);
 221        if (next3 == '(),') {
 222            this.write('(),');
 223            this.goNextManyNW('(),');
 224            this.writeLine();
 225        }
 226        else if (next3 == '();') {
 227            this.write('();');
 228            this.goNextManyNW('();');
 229            this.writeLine();
 230        }
 231        else if (next3 == '():') {
 232            this.write('()');
 233            this.goNextManyNW('()');
 234            this.write(' : ');
 235            this.goNextNonWhite();
 236        }
 237        else
 238        {
 239            if ('{' == prevNW) {
 240                if (',' == nextNW && this.getCurrentLine().length < 80) {
 241                    this.write(' ');
 242                } else {
 243                    if (this.nextWord() || '}' == nextNW) {
 244                        this.writeLine();
 245                    }
 246                }
 247            } else {
 248                if (')' != nextNW && ']' != nextNW) {
 249                    if (',' == nextNW && /^[\s\w,]+\)/.test(this.s.substr(this.i, 20))) {
 250                        this.write(' ');
 251                    } else {
 252                        this.writeLine();
 253                    }
 254                }
 255            }
 256        }
 257        this.lvl--;
 258
 259        if (this.switches.length && this.switches[this.switches.length - 1] == this.lvl)
 260        {
 261            var row = this.row - 1;
 262            var spaces1 = str_repeat(' ', this.lvl * 4);
 263            var spaces2 = str_repeat(' ', (this.lvl + 1) * 4);
 264            var sw1 = new RegExp('^'+spaces1+'(switch\\s|{)');
 265            var sw2 = new RegExp('^'+spaces2+'(case|default)[\\s:]');
 266            var sw3 = new RegExp('^'+spaces2+'[^\\s]');
 267            while (row > 0) {
 268                row--;
 269                if (sw1.test(this.code[row])) {
 270                    break;
 271                }
 272                if (sw2.test(this.code[row])) {
 273                    continue;
 274                }
 275                this.replaceLine('    ' + this.code[row], row);
 276                /*
 277                if (sw3.test(this.code[row])) {
 278                    this.replaceLine('    ' + this.code[row], row);
 279                }
 280                */
 281            }
 282            this.switches.pop();
 283        }
 284
 285        // fix missing brackets for sub blocks
 286
 287        if (this.sub) {
 288            return;
 289        }
 290
 291        var re1 = /^(\s*else\s*if)\s*\(/;
 292        var re2 = /^(\s*else)\s+[^{]+/;
 293
 294        var part = this.s.substr(this.i+1, 100);
 295        
 296        if (re1.test(part)) {
 297            this.i += re1.exec(part)[1].length;
 298            this.write('else if');
 299            this.lastWord = 'if';
 300            //debug(this.getCurrentLine(), 're1');
 301            this.fixSub('else if');
 302            //debug(this.getCurrentLine(), 're1 after');
 303        } else if (re2.test(part)) {
 304            this.i += re2.exec(part)[1].length;
 305            this.write('else');
 306            this.lastWord = 'else';
 307            //debug(this.getCurrentLine(), 're2');
 308            this.fixSub('else');
 309            //debug(this.getCurrentLine(), 're2 after');
 310        }
 311    };
 312    this.bracketOn = function ()
 313    {
 314        if (this.isKeyword() && this.prevChar != ' ' && this.prevChar != "\n") {
 315            this.write(' (');
 316        } else {
 317            this.write('(');
 318        }
 319    };
 320    this.bracketOff = function ()
 321    {
 322        this.write(')');
 323        /*
 324        if (/\w/.test(this.nextNonWhite(this.i))) {
 325            this.semicolon();
 326        }
 327        */
 328        if (this.sub) {
 329            return;
 330        }
 331        var re = new RegExp('^\\s*(if|for|while|do)\\s*\\([^{}]+\\)$', 'i');
 332        var line = this.getCurrentLine();
 333        if (re.test(line)) {
 334            var c = this.nextNonWhite(this.i);
 335            if ('{' != c && ';' != c && ')' != c) {
 336                var opened = 0;
 337                var closed = 0;
 338                var foundFirst = false;
 339                var semicolon = false;
 340                var fix = false;
 341                for (var k = 0; k < line.length; k++) {
 342                    if (line.charAt(k) == '(') {
 343                        foundFirst = true;
 344                        opened++;
 345                    }
 346                    if (line.charAt(k) == ')') {
 347                        closed++;
 348                        if (foundFirst && opened == closed) {
 349                            if (k == line.length - 1) {
 350                                fix = true;
 351                            } else {
 352                                break;
 353                            }
 354                        }
 355                    }
 356                }
 357                if (fix) {
 358                    //alert(this.s.substr(this.i));
 359                    //throw 'asdas';
 360                    //alert(line);
 361                    this.fixSub(re.exec(line)[1]);
 362                    /*
 363                    this.writeLine();
 364                    this.lvl2++;
 365                    var indent = '';
 366                    for (var j = 0; j < this.lvl2; j++) {
 367                        indent += '    ';
 368                    }
 369                    this.write(indent);
 370                    */
 371                }
 372            }
 373        }
 374    };
 375    this.sub = false;
 376    
 377    this.orig_i = null;
 378    this.orig_lvl = null;
 379    this.orig_code = null;
 380    this.orig_row = null;
 381    this.orig_switches = null;
 382
 383    this.restoreOrig = function (omit_i)
 384    {
 385        this.sub = false;
 386        
 387        if (!omit_i) { this.i = this.orig_i; }
 388        this.lvl = this.orig_lvl;
 389        this.code = this.orig_code;
 390        this.row = this.orig_row;
 391        this.switches = this.orig_switches;
 392
 393        this.prevCharInit();
 394        
 395        this.lastWord = '';
 396        this.charInit();
 397        this.isAssign = false;
 398    };
 399    this.combineSub = function ()
 400    {
 401        //debug(this.orig_code, 'orig_code');
 402        for (i = 0; i < this.code.length; i++) {
 403            var line = this.orig_code[this.orig_row];
 404            if (0 == i && line.length) {
 405                if (line.substr(line.length-1, 1) != ' ') {
 406                    this.orig_code[this.orig_row] += ' ';
 407                }
 408                this.orig_code[this.orig_row] += this.code[i].trim();
 409            } else {
 410                this.orig_code[this.orig_row+i] = this.code[i];
 411            }
 412        }
 413        //debug(this.code, 'sub_code');
 414        //debug(this.orig_code, 'code');
 415    };
 416    this.fixSub = function (keyword)
 417    {
 418        // repair missing {}: for, if, while, do, else, else if
 419
 420        if (this.sub) {
 421            return;
 422        }
 423
 424        if ('{' == this.nextNonWhite(this.i)) {
 425            return;
 426        }
 427
 428        var firstWord = this.nextWord();
 429
 430        //debug(this.code, 'fixSub('+keyword+') start');
 431
 432        this.orig_i = this.i;
 433        this.orig_lvl = this.lvl;
 434        this.orig_code = this.code;
 435        this.orig_row = this.row;
 436        this.orig_switches = this.switches;
 437        
 438        this.sub = true;
 439        this.code = [''];
 440        this.prevChar = '';
 441        this.row = 0;
 442        this.switches = [];
 443        this.isAssign = false;
 444
 445        this.i++;
 446
 447        var b1 = 0;
 448        var b2 = 0;
 449        var b3 = 0;
 450
 451        if ('else if' == keyword) {
 452            var first_b2_closed = false;
 453        }
 454
 455        var found = false;
 456
 457        /*
 458            try catch
 459            switch
 460            while do
 461            if else else else...
 462
 463            todo: nestings
 464            if ()
 465                if () 
 466                    if ()
 467                        for ()
 468                            if () asd();
 469                    else
 470                        asd();
 471                else
 472                    if ()
 473                        try {
 474                        } catch {}
 475            else
 476            if ()
 477        */
 478        var b1_lastWord = false;
 479        var b2_lastWord = false;
 480
 481        while (!found && this.i < this.len)
 482        {
 483            var c = this.s.charAt(this.i);
 484            this.charInit();
 485            switch (c)
 486            {
 487                case '{': b1++; break;
 488                case '}':
 489                    b1--;
 490                    // case: for(){if (!c.m(g))c.g(f, n[t] + g + ';')}
 491                    if (0 == b1 && 0 == b2 && 0 == b3 && this.lvl-1 == this.orig_lvl)
 492                    {
 493                        var nextWord = this.nextWord();
 494                        if ('switch' == firstWord) {
 495                            found = true;
 496                            break;
 497                        }
 498                        if ('try' == firstWord && 'catch' == b1_lastWord) {
 499                            found = true;
 500                            break;
 501                        }
 502                        if ('while' == firstWord && 'do' == b1_lastWord) {
 503                            found = true;
 504                            break;
 505                        }
 506                        if ('if' == firstWord) {
 507                            // todo
 508                        }
 509                        if ('if' == keyword && 'else' == nextWord && 'if' != firstWord) {
 510                            found = true;
 511                            break;
 512                        }
 513                        b1_lastWord = nextWord;
 514                    }
 515                    break;
 516                case '(': b2++; break;
 517                case ')':
 518                    b2--;
 519                    if ('else if' == keyword && 0 == b2 && !first_b2_closed) {
 520                        if (this.nextNonWhite(this.i) == '{') {
 521                            this.write(c);
 522                            this.combineSub();
 523                            this.restoreOrig(true);
 524                            //debug(this.code, 'fixSub('+keyword+') b2 return');
 525                            //debug(this.s.charAt(this.i), ' b2 current char');
 526                            return;
 527                        }
 528                        // do not restore orig i
 529                        this.write(c);
 530                        this.combineSub();
 531                        this.restoreOrig(true);
 532                        this.fixSub('if');
 533                        //debug(this.code, 'fixSub('+keyword+') b2 return');
 534                        return;
 535                    }
 536                    break;
 537                case '[': b3++; break;
 538                case ']': b3--; break;
 539                case ';':
 540                    //debug(this.getCurrentLine(), 'semicolon');
 541                    //debug([b1, b2, b3]);
 542                    if (0 == b1 && 0 == b2 && 0 == b3 && this.lvl == this.orig_lvl && 'if' != firstWord) {
 543                        found = true;
 544                    }
 545                    break;
 546            }
 547            if (-1 == b1 && b2 == 0 && b3 == 0 && this.prevNonWhite(this.i) != '}') {
 548                this.write(';');
 549                this.i--;
 550                found = true;
 551            } else if (b1 < 0 || b2 < 0 || b3 < 0) {
 552                found = false;
 553                break;
 554            } else {
 555                this.switch_c(c);
 556            }
 557            this.i++;
 558        }
 559        this.i--;
 560
 561        if (found)
 562        {
 563            /*
 564            var re = /^\s*(else\s+[\s\S]*)$/;
 565            if ('if' == keyword && re.test(this.getCurrentLine())) {
 566                this.i = this.i - re.exec(this.getCurrentLine())[1].length;
 567                this.code[this.row] = '';
 568            }
 569            */
 570            this.s = this.s.substr(0, this.orig_i+1) + '{' + this.code.join("\n") + '}' + this.s.substr(this.i+1);
 571            this.len = this.s.length;
 572        }
 573
 574        //debug("{\n" + this.code.join("\n") + '}', 'fixSub('+keyword+') result');
 575        //debug(found, 'found');
 576
 577        this.restoreOrig(false);
 578    };
 579    this.squareBracketOn = function ()
 580    {
 581        this.checkKeyword();
 582        this.write('[');
 583    };
 584    this.squareBracketOff = function ()
 585    {
 586        this.write(']');
 587    };
 588    this.isKeyword = function ()
 589    {
 590        // Check if this.lastWord is a keyword
 591        return this.lastWord.length && this.keywords.indexOf(this.lastWord) != -1;
 592    };
 593    this.linefeed = function () {};
 594    this.space = function ()
 595    {
 596        if (!this.prevChar.length) {
 597            return;
 598        }
 599        if (' ' == this.prevChar || "\n" == this.prevChar) {
 600            return;
 601        }
 602        if ('}' == this.prevChar && ']' == this.nextChar) {
 603            //return;
 604        }
 605        this.write(' ');
 606        return;
 607        
 608        /*
 609        if (this.isKeyword()) {
 610            this.write(' ');
 611            this.lastWord = '';
 612        } else {
 613            var multi = ['in', 'new'];
 614            for (var i = 0; i < multi.length; i++) {
 615                var isKeywordNext = true;
 616                for (var j = 0; j < multi[i].length; j++) {
 617                    if (multi[i][j] != this.s.charAt(this.i + 1 + j)) {
 618                        isKeywordNext = false;
 619                        break;
 620                    }
 621                }
 622                if (isKeywordNext) {
 623                    this.write(' ');
 624                    this.lastWord = '';
 625                    break;
 626                }
 627            }
 628        }
 629        */
 630    };
 631    this.checkKeyword = function ()
 632    {
 633        if (this.isKeyword() && this.prevChar != ' ' && this.prevChar != "\n") {
 634            this.write(' ');
 635        }
 636    };
 637    this.nextWord = function ()
 638    {
 639        var i = this.i;
 640        var word = '';
 641        while (i < this.len - 1)
 642        {
 643            i++;
 644            var c = this.s.charAt(i);
 645            if (word.length) {
 646                if (/\s/.test(c)) {
 647                    break;
 648                } else if (/\w/.test(c)) {
 649                    word += c;
 650                } else {
 651                    break;
 652                }
 653            } else {
 654                if (/\s/.test(c)) {
 655                    continue;
 656                } else if (/\w/.test(c)) {
 657                    word += c;
 658                } else {
 659                    break;
 660                }
 661            }
 662        }
 663        if (word.length) {
 664            return word;
 665        }
 666        return false;
 667    };
 668    this.nextManyNW = function(many)
 669    {
 670        var ret = '';
 671        var i = this.i;
 672        while (i < this.len - 1)
 673        {
 674            i++;
 675            var c = this.s.charAt(i);
 676            if (!/^\s+$/.test(c)) {
 677                ret += c;
 678                if (ret.length == many) {
 679                    return ret;
 680                }
 681            }
 682        }
 683        return false;
 684    }
 685    this.goNextManyNW = function (cc)
 686    {
 687        var ret = '';
 688        var i = this.i;
 689        while (i < this.len - 1)
 690        {
 691            i++;
 692            var c = this.s.charAt(i);
 693            if (!/^\s+$/.test(c)) {
 694                ret += c;
 695                if (ret == cc) {
 696                    this.i = i;
 697                    this.charInit();
 698                    return true;
 699                }
 700                if (ret.length >= cc.length) {
 701                    return false;
 702                }
 703            }
 704        }
 705        return false;
 706    };
 707    this.nextNonWhite = function (i)
 708    {
 709        while (i < this.len - 1)
 710        {
 711            i++;
 712            var c = this.s.charAt(i);
 713            if (!/^\s+$/.test(c)) {
 714                return c;
 715            }
 716        }
 717        return false;
 718    };
 719    this.prevNonWhite = function (i)
 720    {
 721        while (i > 0)
 722        {
 723            i--;
 724            var c = this.s.charAt(i);
 725            if (!/^\s+$/.test(c)) {
 726                return c;
 727            }
 728        }
 729        return false;
 730    };
 731    this.goNextNonWhite = function ()
 732    {
 733        // you need to write() this nonWhite char when calling this func
 734        var i = this.i;
 735        while (i < this.len - 1)
 736        {
 737            i++;
 738            var c = this.s.charAt(i);
 739            if (!/^\s+$/.test(c)) {
 740                this.i = i;
 741                this.charInit();
 742                return true;
 743            }
 744        }
 745        return false;
 746    };
 747    this.colon = function ()
 748    {
 749        //alert(this.getCurrentLine());
 750        /* case 6: expr ? stat : stat */
 751        var line = this.getCurrentLine();
 752        if (/^\s*case\s/.test(line) || /^\s*default$/.test(line)) {
 753            this.write(':');
 754            this.writeLine();
 755        } else {
 756            this.symbol2(':');
 757        }
 758    };
 759    this.isStart = function ()
 760    {
 761        return this.getCurrentLine().length === 0;
 762    };
 763    this.backLine = function ()
 764    {
 765        if (!this.isStart) {
 766            throw 'backLine() may be called only at the start of the line';
 767        }
 768        this.code.length = this.code.length-1;
 769        this.row--;
 770    };
 771    this.semicolon = function ()
 772    {
 773        /* for statement: for (i = 1; i < len; i++) */
 774        this.isAssign = false;
 775        if (this.isStart()) {
 776            this.backLine();
 777        }
 778        this.write(';');
 779        if (/^\s*for\s/.test(this.getCurrentLine())) {
 780            this.write(' ');
 781        } else {
 782            this.writeLine();
 783        }
 784    };
 785    this.quotation = function (quotation)
 786    {
 787        this.checkKeyword();
 788        var escaped = false;
 789        this.write(quotation);
 790        while (this.i < this.len - 1) {
 791            this.i++;
 792            var c = this.s.charAt(this.i);
 793            if ('\\' == c) {
 794                escaped = (escaped ? false : true);
 795            }
 796            this.write(c);
 797            if (c == quotation) {
 798                if (!escaped) {
 799                    break;
 800                }
 801            }
 802            if ('\\' != c) {
 803                escaped = false;
 804            }
 805        }
 806        //debug(this.getCurrentLine(), 'quotation');
 807        //debug(this.s.charAt(this.i), 'char');
 808    };
 809    this.lineComment = function ()
 810    {
 811        this.write('//');
 812        this.i++;
 813        while (this.i < this.len - 1) {
 814            this.i++;
 815            var c = this.s.charAt(this.i);
 816            if ("\n" == c) {
 817                this.writeLine();
 818                break;
 819            }
 820            this.write(c);
 821        }
 822    };
 823    this.comment = function ()
 824    {
 825        this.write('/*');
 826        this.i++;
 827        var c = '';
 828        var prevC = '';
 829        while (this.i < this.len - 1)
 830        {
 831            this.i++;
 832            prevC = c;
 833            c = this.s.charAt(this.i);
 834            if (' ' == c || "\t" == c || "\n" == c) {
 835                if (' ' == c) {
 836                    if (this.getCurrentLine().length > 100) {
 837                        this.writeLine();
 838                    } else {
 839                        this.write(' ', true);
 840                    }
 841                } else if ("\t" == c) {
 842                    this.write('    ', true);
 843                } else if ("\n" == c) {
 844                    this.writeLine();
 845                }
 846            } else {
 847                this.write(c, true);
 848            }
 849            if ('/' == c && '*' == prevC) {
 850                break;
 851            }
 852        }
 853        this.writeLine();
 854    };
 855    this.slash = function ()
 856    {
 857        /*
 858        divisor /= or *\/ (4/5 , a/5)
 859        regexp /\w/ (//.test() , var asd = /some/;)
 860        asd /= 5;
 861        bbb = * / (4/5)
 862        asd =( a/5);
 863        regexp = /\w/;
 864        /a/.test();
 865        var asd = /some/;
 866        obj = { sasd : /pattern/ig }
 867        */
 868        var a_i = this.i - 1;
 869        var a_c = this.s.charAt(a_i);
 870        for (a_i = this.i - 1; a_i >= 0; a_i--) {
 871            var c2 = this.s.charAt(a_i);
 872            if (' ' == c2 || '\t' == c2) {
 873                continue;
 874            }
 875            a_c = this.s.charAt(a_i);
 876            break;
 877        }
 878        var a = /^\w+$/.test(a_c) || ']' == a_c || ')' == a_c;
 879        var b = ('*' == this.prevChar);
 880        if (a || b) {
 881            if (a) {
 882                if ('=' == this.nextChar) {
 883                    var ss = this.prevChar == ' ' ? '' : ' ';
 884                    this.write(ss+'/');
 885                } else {
 886                    this.write(' / ');
 887                }
 888            } else if (b) {
 889                this.write('/ ');
 890            }
 891        } else if (')' == this.prevChar) {
 892            this.write(' / ');
 893        } else {
 894            var ret = '';
 895            if ('=' == this.prevChar || ':' == this.prevChar) {
 896                ret += ' /';
 897            } else {
 898                ret += '/';
 899            }
 900            var escaped = false;
 901            while (this.i < this.len - 1) {
 902                this.i++;
 903                var c = this.s.charAt(this.i);
 904                if ('\\' == c) {
 905                    escaped = (escaped ? false : true);
 906                }
 907                ret += c;
 908                if ('/' == c) {
 909                    if (!escaped) {
 910                        break;
 911                    }
 912                }
 913                if ('\\' != c) {
 914                    escaped = false;
 915                }
 916            }
 917            this.write(ret);
 918        }
 919    };
 920    this.comma = function ()
 921    {
 922        /*
 923         * function arguments seperator
 924         * array values seperator
 925         * object values seperator
 926         */
 927        this.write(', ');
 928        var line = this.getCurrentLine();
 929        if (line.replace(' ', '').length > 100) {
 930            this.writeLine();
 931        }
 932    };
 933    this.dot = function ()
 934    {
 935        this.write('.');
 936    };
 937    this.symbol1 = function (c)
 938    {
 939        if ('=' == this.prevChar && '!' == c) {
 940            this.write(' '+c);
 941        } else {
 942            this.write(c);
 943        }
 944    };
 945    this.symbol2 = function (c)
 946    {
 947        // && !p
 948        // ===
 949        if ('+' == c || '-' == c) {
 950            if (c == this.nextChar || c == this.prevChar) {
 951                this.write(c);
 952                return;
 953            }
 954        }
 955        var ss = (this.prevChar == ' ' ? '' : ' ');
 956        var ss2 = ' ';
 957        if ('(' == this.prevChar) {
 958            ss = '';
 959            ss2 = '';
 960        }
 961        if ('-' == c && ('>' == this.prevChar || '>' == this.prevChar)) {
 962            this.write(' '+c);
 963            return;
 964        }
 965        if (this.symbols2.indexOf(this.prevChar) != -1) {
 966            if (this.symbols2.indexOf(this.nextChar) != -1) {
 967                this.write(c + (this.nextChar == '!' ? ' ' : ''));
 968            } else {
 969                this.write(c + ss2);
 970            }
 971        } else {
 972            if (this.symbols2.indexOf(this.nextChar) != -1) {
 973                this.write(ss + c);
 974            } else {
 975                this.write(ss + c + ss2);
 976            }
 977        }
 978        if ('=' == c && /^[\w\]]$/.test(this.prevNonWhite(this.i)) && /^[\w\'\"\[]$/.test(this.nextNonWhite(this.i))) {
 979            this.isAssign = true;
 980        }
 981    };
 982    this.alphanumeric = function (c)
 983    {
 984        /* /[a-zA-Z0-9_]/ == /\w/ */
 985        if (this.lastWord) {
 986            this.lastWord += c;
 987        } else {
 988            this.lastWord = c;
 989        }
 990        if (')' == this.prevChar) {
 991            c = ' '+c;
 992        }
 993        this.write(c);
 994    };
 995    this.unknown = function (c)
 996    {
 997        //throw 'Unknown char: "'+c+'" , this.i = ' + this.i;
 998        this.write(c);
 999    };
1000
1001    this.charInit = function ()
1002    {
1003        /*
1004        if (this.i > 0) {
1005            //this.prevChar = this.s.charAt(this.i - 1);
1006            var line = this.code[this.row];
1007            if (line.length) {
1008                this.prevChar = line.substr(line.length-1, 1);
1009            } else {
1010                this.prevChar = '';
1011            }
1012        } else {
1013            this.prevChar = '';
1014        }
1015        */
1016        if (this.len - 1 === this.i) {
1017            this.nextChar = '';
1018        } else {
1019            this.nextChar = this.s.charAt(this.i + 1);
1020        }
1021    };
1022    this.write = function (s, isComment)
1023    {
1024        if (isComment) {
1025            if (!/\s/.test(s)) {
1026                if (this.code[this.row].length < this.lvl * 4) {
1027                    this.code[this.row] += str_repeat(' ', this.lvl * 4 - this.code[this.row].length);
1028                }
1029            }
1030            this.code[this.row] += s;
1031        } else {
1032            if (0 === this.code[this.row].length) {
1033                var lvl = ('}' == s ? this.lvl - 1 : this.lvl);
1034                for (var i = 0; i < lvl; i++) {
1035                    this.code[this.row] += '    ';
1036                }
1037                    this.code[this.row] += s;
1038            } else {
1039                this.code[this.row] += s;
1040            }
1041        }
1042        this.prevCharInit();
1043    };
1044    this.writeLine = function ()
1045    {
1046        this.code.push('');
1047        this.row++;
1048        this.prevChar = "\n";
1049    };
1050    this.replaceLine = function (line, row)
1051    {
1052        if ('undefined' == typeof row) {
1053            row = false;
1054        }
1055        if (row !== false) {
1056            if (!/^\d+$/.test(row) || row < 0 || row > this.row) {
1057                throw 'replaceLine() failed: invalid row='+row;
1058            }
1059        }
1060        if (row !== false) {
1061            this.code[row] = line;
1062        } else {
1063            this.code[this.row] = line;
1064        }
1065        if (row === false || row == this.row) {
1066            this.prevCharInit();
1067        }
1068    };
1069    this.prevCharInit = function ()
1070    {
1071        this.prevChar = this.code[this.row].charAt(this.code[this.row].length - 1);
1072    };
1073    this.writeTab = function ()
1074    {
1075        this.write('    ');
1076        this.prevChar = ' ';
1077    };
1078    this.getCurrentLine = function ()
1079    {
1080        return this.code[this.row];
1081    };
1082
1083    this.symbols1 = '~!^';
1084    this.symbols2 = '-+*%<=>?:&|/!';
1085    this.keywords = ['abstract', 'boolean', 'break', 'byte', 'case', 'catch', 'char', 'class',
1086        'const', 'continue', 'default', 'delete', 'do', 'double', 'else', 'extends', 'false',
1087        'final', 'finally', 'float', 'for', 'function', 'goto', 'if', 'implements', 'import',
1088        'in', 'instanceof', 'int', 'interface', 'long', 'native', 'new', 'null', 'package',
1089        'private', 'protected', 'public', 'return', 'short', 'static', 'super', 'switch',
1090        'synchronized', 'this', 'throw', 'throws', 'transient', 'true', 'try', 'typeof', 'var',
1091        'void', 'while', 'with'];
1092}
1093
1094if (typeof Array.prototype.indexOf == 'undefined') {
1095    /* Finds the index of the first occurence of item in the array, or -1 if not found */
1096    Array.prototype.indexOf = function(item) {
1097        for (var i = 0; i < this.length; i++) {
1098            if ((typeof this[i] == typeof item) && (this[i] == item)) {
1099                return i;
1100            }
1101        }
1102        return -1;
1103    };
1104}
1105if (!String.prototype.trim) {
1106    String.prototype.trim = function() {
1107        return this.replace(/^\s*|\s*$/g, '');
1108    };
1109}
1110
1111function str_repeat(str, repeat)
1112{
1113    ret = '';
1114    for (var i = 0; i < repeat; i++) {
1115        ret += str;
1116    }
1117    return ret;
1118}
1119
1120var debug_w;
1121function debug (arr, name)
1122{
1123    if (!debug_w) 
1124    {
1125        var width = 600;
1126        var height = 600;
1127        var x = (screen.width-width)/2;
1128        var y = (screen.height-height)/2;
1129        debug_w = window.open('', '', 'scrollbars=yes,resizable=yes,width='+width+',height='+height+',screenX='+(x)+',screenY='+y+',left='+x+',top='+y);
1130        debug_w.document.open();
1131        debug_w.document.write('<html><head><style>body{margin: 1em;padding: 0;font-family: courier new; font-size: 12px;}h1,h2{margin: 0.2em 0;}</style></head><body><h1>Debug</h1></body></html>');
1132        debug_w.document.close();
1133    }
1134    var ret = '';
1135    if ('undefined' !== typeof name && name.length) {
1136        ret = '<h2>'+name+'</h2>'+"\n";
1137    }
1138    if ('object' === typeof arr) {
1139        for (var i = 0; i < arr.length; i++) {
1140            ret += '['+i+'] => '+arr[i]+"\n";
1141        }
1142    } else if ('string' == typeof arr) {
1143        ret += arr;
1144    } else {
1145        try { ret += arr.toString(); } catch (e) {}
1146        ret += ' ('+typeof arr+')';
1147    }
1148    debug_w.document.body.innerHTML += '<pre>'+ret+'</pre>';
1149}