Index: trunk/phase3/includes/libs/jsminplus.php |
— | — | @@ -1,7 +1,7 @@ |
2 | 2 | <?php |
3 | 3 | |
4 | 4 | /** |
5 | | - * JSMinPlus version 1.3 |
| 5 | + * JSMinPlus version 1.4 |
6 | 6 | * |
7 | 7 | * Minifies a javascript file using a javascript parser |
8 | 8 | * |
— | — | @@ -15,8 +15,10 @@ |
16 | 16 | * Usage: $minified = JSMinPlus::minify($script [, $filename]) |
17 | 17 | * |
18 | 18 | * Versionlog (see also changelog.txt): |
19 | | - * 19-07-2011 - expanded operator and keyword defines. Fixes the notices when creating several JSTokenizer |
20 | | - * 17-05-2009 - fixed hook:colon precedence, fixed empty body in loop and if-constructs |
| 19 | + * 23-07-2011 - remove dynamic creation of OP_* and KEYWORD_* defines and declare them on top |
| 20 | + * reduce memory footprint by minifying by block-scope |
| 21 | + * some small byte-saving and performance improvements |
| 22 | + * 12-05-2009 - fixed hook:colon precedence, fixed empty body in loop and if-constructs |
21 | 23 | * 18-04-2009 - fixed crashbug in PHP 5.2.9 and several other bugfixes |
22 | 24 | * 12-04-2009 - some small bugfixes and performance improvements |
23 | 25 | * 09-04-2009 - initial open sourced version 1.0 |
— | — | @@ -46,7 +48,7 @@ |
47 | 49 | * the Initial Developer. All Rights Reserved. |
48 | 50 | * |
49 | 51 | * Contributor(s): Tino Zijdel <crisp@tweakers.net> |
50 | | - * PHP port, modifications and minifier routine are (C) 2009 |
| 52 | + * PHP port, modifications and minifier routine are (C) 2009-2011 |
51 | 53 | * |
52 | 54 | * Alternatively, the contents of this file may be used under the terms of |
53 | 55 | * either the GNU General Public License Version 2 or later (the "GPL"), or |
— | — | @@ -86,6 +88,8 @@ |
87 | 89 | define('JS_GROUP', 112); |
88 | 90 | define('JS_LIST', 113); |
89 | 91 | |
| 92 | +define('JS_MINIFIED', 999); |
| 93 | + |
90 | 94 | define('DECLARED_FORM', 0); |
91 | 95 | define('EXPRESSED_FORM', 1); |
92 | 96 | define('STATEMENT_FORM', 2); |
— | — | @@ -188,7 +192,7 @@ |
189 | 193 | |
190 | 194 | private function __construct() |
191 | 195 | { |
192 | | - $this->parser = new JSParser(); |
| 196 | + $this->parser = new JSParser($this); |
193 | 197 | } |
194 | 198 | |
195 | 199 | public static function minify($js, $filename='') |
— | — | @@ -217,22 +221,18 @@ |
218 | 222 | return false; |
219 | 223 | } |
220 | 224 | |
221 | | - private function parseTree($n, $noBlockGrouping = false) |
| 225 | + public function parseTree($n, $noBlockGrouping = false) |
222 | 226 | { |
223 | 227 | $s = ''; |
224 | 228 | |
225 | 229 | switch ($n->type) |
226 | 230 | { |
227 | | - case KEYWORD_FUNCTION: |
228 | | - $s .= 'function' . ($n->name ? ' ' . $n->name : '') . '('; |
229 | | - $params = $n->params; |
230 | | - for ($i = 0, $j = count($params); $i < $j; $i++) |
231 | | - $s .= ($i ? ',' : '') . $params[$i]; |
232 | | - $s .= '){' . $this->parseTree($n->body, true) . '}'; |
| 231 | + case JS_MINIFIED: |
| 232 | + $s = $n->value; |
233 | 233 | break; |
234 | 234 | |
235 | 235 | case JS_SCRIPT: |
236 | | - // we do nothing with funDecls or varDecls |
| 236 | + // we do nothing yet with funDecls or varDecls |
237 | 237 | $noBlockGrouping = true; |
238 | 238 | // FALL THROUGH |
239 | 239 | |
— | — | @@ -279,6 +279,14 @@ |
280 | 280 | } |
281 | 281 | break; |
282 | 282 | |
| 283 | + case KEYWORD_FUNCTION: |
| 284 | + $s .= 'function' . ($n->name ? ' ' . $n->name : '') . '('; |
| 285 | + $params = $n->params; |
| 286 | + for ($i = 0, $j = count($params); $i < $j; $i++) |
| 287 | + $s .= ($i ? ',' : '') . $params[$i]; |
| 288 | + $s .= '){' . $this->parseTree($n->body, true) . '}'; |
| 289 | + break; |
| 290 | + |
283 | 291 | case KEYWORD_IF: |
284 | 292 | $s = 'if(' . $this->parseTree($n->condition) . ')'; |
285 | 293 | $thenPart = $this->parseTree($n->thenPart); |
— | — | @@ -385,19 +393,14 @@ |
386 | 394 | break; |
387 | 395 | |
388 | 396 | case KEYWORD_THROW: |
389 | | - $s = 'throw ' . $this->parseTree($n->exception); |
390 | | - break; |
391 | | - |
392 | 397 | case KEYWORD_RETURN: |
393 | | - $s = 'return'; |
| 398 | + $s = $n->type; |
394 | 399 | if ($n->value) |
395 | 400 | { |
396 | 401 | $t = $this->parseTree($n->value); |
397 | 402 | if (strlen($t)) |
398 | 403 | { |
399 | | - if ( $t[0] != '(' && $t[0] != '[' && $t[0] != '{' && |
400 | | - $t[0] != '"' && $t[0] != "'" && $t[0] != '/' |
401 | | - ) |
| 404 | + if ($this->isWordChar($t[0]) || $t[0] == '\\') |
402 | 405 | $s .= ' '; |
403 | 406 | |
404 | 407 | $s .= $t; |
— | — | @@ -423,6 +426,40 @@ |
424 | 427 | } |
425 | 428 | break; |
426 | 429 | |
| 430 | + case KEYWORD_IN: |
| 431 | + case KEYWORD_INSTANCEOF: |
| 432 | + $left = $this->parseTree($n->treeNodes[0]); |
| 433 | + $right = $this->parseTree($n->treeNodes[1]); |
| 434 | + |
| 435 | + $s = $left; |
| 436 | + |
| 437 | + if ($this->isWordChar(substr($left, -1))) |
| 438 | + $s .= ' '; |
| 439 | + |
| 440 | + $s .= $n->type; |
| 441 | + |
| 442 | + if ($this->isWordChar($right[0]) || $right[0] == '\\') |
| 443 | + $s .= ' '; |
| 444 | + |
| 445 | + $s .= $right; |
| 446 | + break; |
| 447 | + |
| 448 | + case KEYWORD_DELETE: |
| 449 | + case KEYWORD_TYPEOF: |
| 450 | + $right = $this->parseTree($n->treeNodes[0]); |
| 451 | + |
| 452 | + $s = $n->type; |
| 453 | + |
| 454 | + if ($this->isWordChar($right[0]) || $right[0] == '\\') |
| 455 | + $s .= ' '; |
| 456 | + |
| 457 | + $s .= $right; |
| 458 | + break; |
| 459 | + |
| 460 | + case KEYWORD_VOID: |
| 461 | + $s = 'void(' . $this->parseTree($n->treeNodes[0]) . ')'; |
| 462 | + break; |
| 463 | + |
427 | 464 | case KEYWORD_DEBUGGER: |
428 | 465 | throw new Exception('NOT IMPLEMENTED: DEBUGGER'); |
429 | 466 | break; |
— | — | @@ -497,26 +534,6 @@ |
498 | 535 | } |
499 | 536 | break; |
500 | 537 | |
501 | | - case KEYWORD_IN: |
502 | | - $s = $this->parseTree($n->treeNodes[0]) . ' in ' . $this->parseTree($n->treeNodes[1]); |
503 | | - break; |
504 | | - |
505 | | - case KEYWORD_INSTANCEOF: |
506 | | - $s = $this->parseTree($n->treeNodes[0]) . ' instanceof ' . $this->parseTree($n->treeNodes[1]); |
507 | | - break; |
508 | | - |
509 | | - case KEYWORD_DELETE: |
510 | | - $s = 'delete ' . $this->parseTree($n->treeNodes[0]); |
511 | | - break; |
512 | | - |
513 | | - case KEYWORD_VOID: |
514 | | - $s = 'void(' . $this->parseTree($n->treeNodes[0]) . ')'; |
515 | | - break; |
516 | | - |
517 | | - case KEYWORD_TYPEOF: |
518 | | - $s = 'typeof ' . $this->parseTree($n->treeNodes[0]); |
519 | | - break; |
520 | | - |
521 | 538 | case OP_NOT: |
522 | 539 | case OP_BITWISE_NOT: |
523 | 540 | case OP_UNARY_PLUS: |
— | — | @@ -606,13 +623,33 @@ |
607 | 624 | $s .= '}'; |
608 | 625 | break; |
609 | 626 | |
| 627 | + case TOKEN_NUMBER: |
| 628 | + $s = $n->value; |
| 629 | + if (preg_match('/^([1-9]+)(0{3,})$/', $s, $m)) |
| 630 | + $s = $m[1] . 'e' . strlen($m[2]); |
| 631 | + break; |
| 632 | + |
610 | 633 | case KEYWORD_NULL: case KEYWORD_THIS: case KEYWORD_TRUE: case KEYWORD_FALSE: |
611 | | - case TOKEN_IDENTIFIER: case TOKEN_NUMBER: case TOKEN_STRING: case TOKEN_REGEXP: |
| 634 | + case TOKEN_IDENTIFIER: case TOKEN_STRING: case TOKEN_REGEXP: |
612 | 635 | $s = $n->value; |
613 | 636 | break; |
614 | 637 | |
615 | 638 | case JS_GROUP: |
616 | | - $s = '(' . $this->parseTree($n->treeNodes[0]) . ')'; |
| 639 | + if (in_array( |
| 640 | + $n->treeNodes[0]->type, |
| 641 | + array( |
| 642 | + JS_ARRAY_INIT, JS_OBJECT_INIT, JS_GROUP, |
| 643 | + TOKEN_NUMBER, TOKEN_STRING, TOKEN_REGEXP, TOKEN_IDENTIFIER, |
| 644 | + KEYWORD_NULL, KEYWORD_THIS, KEYWORD_TRUE, KEYWORD_FALSE |
| 645 | + ) |
| 646 | + )) |
| 647 | + { |
| 648 | + $s = $this->parseTree($n->treeNodes[0]); |
| 649 | + } |
| 650 | + else |
| 651 | + { |
| 652 | + $s = '(' . $this->parseTree($n->treeNodes[0]) . ')'; |
| 653 | + } |
617 | 654 | break; |
618 | 655 | |
619 | 656 | default: |
— | — | @@ -626,11 +663,17 @@ |
627 | 664 | { |
628 | 665 | return preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $string) && !in_array($string, $this->reserved); |
629 | 666 | } |
| 667 | + |
| 668 | + private function isWordChar($char) |
| 669 | + { |
| 670 | + return $char == '_' || $char == '$' || ctype_alnum($char); |
| 671 | + } |
630 | 672 | } |
631 | 673 | |
632 | 674 | class JSParser |
633 | 675 | { |
634 | 676 | private $t; |
| 677 | + private $minifier; |
635 | 678 | |
636 | 679 | private $opPrecedence = array( |
637 | 680 | ';' => 0, |
— | — | @@ -680,8 +723,9 @@ |
681 | 724 | TOKEN_CONDCOMMENT_START => 1, TOKEN_CONDCOMMENT_END => 1 |
682 | 725 | ); |
683 | 726 | |
684 | | - public function __construct() |
| 727 | + public function __construct($minifier=null) |
685 | 728 | { |
| 729 | + $this->minifier = $minifier; |
686 | 730 | $this->t = new JSTokenizer(); |
687 | 731 | } |
688 | 732 | |
— | — | @@ -705,6 +749,19 @@ |
706 | 750 | $n->funDecls = $x->funDecls; |
707 | 751 | $n->varDecls = $x->varDecls; |
708 | 752 | |
| 753 | + // minify by scope |
| 754 | + if ($this->minifier) |
| 755 | + { |
| 756 | + $n->value = $this->minifier->parseTree($n); |
| 757 | + |
| 758 | + // clear tree from node to save memory |
| 759 | + $n->treeNodes = null; |
| 760 | + $n->funDecls = null; |
| 761 | + $n->varDecls = null; |
| 762 | + |
| 763 | + $n->type = JS_MINIFIED; |
| 764 | + } |
| 765 | + |
709 | 766 | return $n; |
710 | 767 | } |
711 | 768 | |
— | — | @@ -963,7 +1020,7 @@ |
964 | 1021 | |
965 | 1022 | case KEYWORD_THROW: |
966 | 1023 | $n = new JSNode($this->t); |
967 | | - $n->exception = $this->Expression($x); |
| 1024 | + $n->value = $this->Expression($x); |
968 | 1025 | break; |
969 | 1026 | |
970 | 1027 | case KEYWORD_RETURN: |
— | — | @@ -1678,44 +1735,11 @@ |
1679 | 1736 | ); |
1680 | 1737 | |
1681 | 1738 | private $opTypeNames = array( |
1682 | | - ';' => 'SEMICOLON', |
1683 | | - ',' => 'COMMA', |
1684 | | - '?' => 'HOOK', |
1685 | | - ':' => 'COLON', |
1686 | | - '||' => 'OR', |
1687 | | - '&&' => 'AND', |
1688 | | - '|' => 'BITWISE_OR', |
1689 | | - '^' => 'BITWISE_XOR', |
1690 | | - '&' => 'BITWISE_AND', |
1691 | | - '===' => 'STRICT_EQ', |
1692 | | - '==' => 'EQ', |
1693 | | - '=' => 'ASSIGN', |
1694 | | - '!==' => 'STRICT_NE', |
1695 | | - '!=' => 'NE', |
1696 | | - '<<' => 'LSH', |
1697 | | - '<=' => 'LE', |
1698 | | - '<' => 'LT', |
1699 | | - '>>>' => 'URSH', |
1700 | | - '>>' => 'RSH', |
1701 | | - '>=' => 'GE', |
1702 | | - '>' => 'GT', |
1703 | | - '++' => 'INCREMENT', |
1704 | | - '--' => 'DECREMENT', |
1705 | | - '+' => 'PLUS', |
1706 | | - '-' => 'MINUS', |
1707 | | - '*' => 'MUL', |
1708 | | - '/' => 'DIV', |
1709 | | - '%' => 'MOD', |
1710 | | - '!' => 'NOT', |
1711 | | - '~' => 'BITWISE_NOT', |
1712 | | - '.' => 'DOT', |
1713 | | - '[' => 'LEFT_BRACKET', |
1714 | | - ']' => 'RIGHT_BRACKET', |
1715 | | - '{' => 'LEFT_CURLY', |
1716 | | - '}' => 'RIGHT_CURLY', |
1717 | | - '(' => 'LEFT_PAREN', |
1718 | | - ')' => 'RIGHT_PAREN', |
1719 | | - '@*/' => 'CONDCOMMENT_END' |
| 1739 | + ';', ',', '?', ':', '||', '&&', '|', '^', |
| 1740 | + '&', '===', '==', '=', '!==', '!=', '<<', '<=', |
| 1741 | + '<', '>>>', '>>', '>=', '>', '++', '--', '+', |
| 1742 | + '-', '*', '/', '%', '!', '~', '.', '[', |
| 1743 | + ']', '{', '}', '(', ')', '@*/' |
1720 | 1744 | ); |
1721 | 1745 | |
1722 | 1746 | private $assignOps = array('|', '^', '&', '<<', '>>', '>>>', '+', '-', '*', '/', '%'); |
— | — | @@ -1723,7 +1747,7 @@ |
1724 | 1748 | |
1725 | 1749 | public function __construct() |
1726 | 1750 | { |
1727 | | - $this->opRegExp = '#^(' . implode('|', array_map('preg_quote', array_keys($this->opTypeNames))) . ')#'; |
| 1751 | + $this->opRegExp = '#^(' . implode('|', array_map('preg_quote', $this->opTypeNames)) . ')#'; |
1728 | 1752 | } |
1729 | 1753 | |
1730 | 1754 | public function init($source, $filename = '', $lineno = 1) |
— | — | @@ -1874,22 +1898,38 @@ |
1875 | 1899 | { |
1876 | 1900 | switch ($input[0]) |
1877 | 1901 | { |
1878 | | - case '0': case '1': case '2': case '3': case '4': |
1879 | | - case '5': case '6': case '7': case '8': case '9': |
1880 | | - if (preg_match('/^\d+\.\d*(?:[eE][-+]?\d+)?|^\d+(?:\.\d*)?[eE][-+]?\d+/', $input, $match)) |
| 1902 | + case '0': |
| 1903 | + // hexadecimal |
| 1904 | + if (($input[1] == 'x' || $input[1] == 'X') && preg_match('/^0x[0-9a-f]+/i', $input, $match)) |
1881 | 1905 | { |
1882 | 1906 | $tt = TOKEN_NUMBER; |
| 1907 | + break; |
1883 | 1908 | } |
1884 | | - else if (preg_match('/^0[xX][\da-fA-F]+|^0[0-7]*|^\d+/', $input, $match)) |
| 1909 | + // FALL THROUGH |
| 1910 | + |
| 1911 | + case '1': case '2': case '3': case '4': case '5': |
| 1912 | + case '6': case '7': case '8': case '9': |
| 1913 | + // should always match |
| 1914 | + preg_match('/^\d+(?:\.\d*)?(?:[eE][-+]?\d+)?/', $input, $match); |
| 1915 | + $tt = TOKEN_NUMBER; |
| 1916 | + break; |
| 1917 | + |
| 1918 | + case "'": |
| 1919 | + if (preg_match('/^\'(?:[^\\\\\'\r\n]++|\\\\(?:.|\r?\n))*\'/', $input, $match)) |
1885 | 1920 | { |
1886 | | - // this should always match because of \d+ |
1887 | | - $tt = TOKEN_NUMBER; |
| 1921 | + $tt = TOKEN_STRING; |
1888 | 1922 | } |
| 1923 | + else |
| 1924 | + { |
| 1925 | + if ($chunksize) |
| 1926 | + return $this->get(null); // retry with a full chunk fetch |
| 1927 | + |
| 1928 | + throw $this->newSyntaxError('Unterminated string literal'); |
| 1929 | + } |
1889 | 1930 | break; |
1890 | 1931 | |
1891 | 1932 | case '"': |
1892 | | - case "'": |
1893 | | - if (preg_match('/^"(?:\\\\(?:.|\r?\n)|[^\\\\"\r\n]+)*"|^\'(?:\\\\(?:.|\r?\n)|[^\\\\\'\r\n]+)*\'/', $input, $match)) |
| 1933 | + if (preg_match('/^"(?:[^\\\\"\r\n]++|\\\\(?:.|\r?\n))*"/', $input, $match)) |
1894 | 1934 | { |
1895 | 1935 | $tt = TOKEN_STRING; |
1896 | 1936 | } |
— | — | @@ -2044,4 +2084,3 @@ |
2045 | 2085 | public $lineno; |
2046 | 2086 | public $assignOp; |
2047 | 2087 | } |
2048 | | - |