Same name in other branches
- 5.0.x advagg_js_minify/jsqueeze.inc \Patchwork\JSqueeze
- 6.0.x advagg_js_minify/jsqueeze.inc \Patchwork\JSqueeze
- 8.x-2.x advagg_js_minify/jsqueeze.inc \Patchwork\JSqueeze
- 8.x-3.x advagg_js_minify/jsqueeze.inc \Patchwork\JSqueeze
- 8.x-4.x advagg_js_minify/jsqueeze.inc \Patchwork\JSqueeze
Hierarchy
- class \Patchwork\JSqueeze
Expanded class hierarchy of JSqueeze
1 file declares its use of JSqueeze
- advagg_js_compress.php53.inc in advagg_js_compress/
advagg_js_compress.php53.inc - Advanced CSS/JS aggregation js compression php 5.3+ functions.
4 string references to 'JSqueeze'
- advagg_js_compress_configuration in advagg_js_compress/
advagg_js_compress.module - Generate the js compress configuration.
- advagg_js_compress_jsqueeze in advagg_js_compress/
advagg_js_compress.php53.inc - Compress a JS string using jsqueeze.
- advagg_js_compress_libraries_info in advagg_js_compress/
advagg_js_compress.module - Implements hook_libraries_info().
- advagg_js_compress_redo_files in advagg_js_compress/
advagg_js_compress.module - Get all js files and js files that are not compressed.
File
-
advagg_js_compress/
jsqueeze.inc, line 63
Namespace
PatchworkView source
class JSqueeze {
const SPECIAL_VAR_PACKER = '(\\$+[a-zA-Z_]|_[a-zA-Z0-9$])[a-zA-Z0-9_$]*';
public $charFreq;
protected $strings, $closures, $str0, $str1, $argFreq, $specialVarRx, $keepImportantComments, $varRx = '(?:[a-zA-Z_$])[a-zA-Z0-9_$]*', $reserved = array(
// Literals
'true',
'false',
'null',
// ES6
'break',
'case',
'class',
'catch',
'const',
'continue',
'debugger',
'default',
'delete',
'do',
'else',
'export',
'extends',
'finally',
'for',
'function',
'if',
'import',
'in',
'instanceof',
'new',
'return',
'super',
'switch',
'this',
'throw',
'try',
'typeof',
'var',
'void',
'while',
'with',
'yield',
// Future
'enum',
// Strict mode
'implements',
'package',
'protected',
'static',
'let',
'interface',
'private',
'public',
// Module
'await',
// Older standards
'abstract',
'boolean',
'byte',
'char',
'double',
'final',
'float',
'goto',
'int',
'long',
'native',
'short',
'synchronized',
'throws',
'transient',
'volatile',
);
public function __construct() {
$this->reserved = array_flip($this->reserved);
$this->charFreq = array_fill(0, 256, 0);
}
/**
* Squeezes a JavaScript source code.
*
* Set $singleLine to false if you want optional
* semi-colons to be replaced by line feeds.
*
* Set $keepImportantComments to false if you want /*! comments to be removed.
*
* $specialVarRx defines the regular expression of special variables names
* for global vars, methods, properties and in string substitution.
* Set it to false if you don't want any.
*
* If the analysed javascript source contains a single line comment like
* this one, then the directive will overwrite $specialVarRx:
*
* // jsqueeze.specialVarRx = your_special_var_regexp_here
*
* Only the first directive is parsed, others are ignored. It is not possible
* to redefine $specialVarRx in the middle of the javascript source.
*
* Example:
* $parser = new JSqueeze;
* $squeezed_js = $parser->squeeze($fat_js);
*/
public function squeeze($code, $singleLine = true, $keepImportantComments = true, $specialVarRx = false) {
$code = trim($code);
if ('' === $code) {
return '';
}
$this->argFreq = array(
-1 => 0,
);
$this->specialVarRx = $specialVarRx;
$this->keepImportantComments = !!$keepImportantComments;
if (preg_match("#//[ \t]*jsqueeze\\.specialVarRx[ \t]*=[ \t]*([\"']?)(.*)\\1#i", $code, $key)) {
if (!$key[1]) {
$key[2] = trim($key[2]);
$key[1] = strtolower($key[2]);
$key[1] = $key[1] && $key[1] != 'false' && $key[1] != 'none' && $key[1] != 'off';
}
$this->specialVarRx = $key[1] ? $key[2] : false;
}
// Remove capturing parentheses
$this->specialVarRx && ($this->specialVarRx = preg_replace('/(?<!\\\\)((?:\\\\\\\\)*)\\((?!\\?)/', '(?:', $this->specialVarRx));
false !== strpos($code, "\r") && ($code = strtr(str_replace("\r\n", "\n", $code), "\r", "\n"));
false !== strpos($code, "
") && ($code = str_replace("
", "\n", $code));
// Next Line
false !== strpos($code, "
") && ($code = str_replace("
", "\n", $code));
// Line Separator
false !== strpos($code, "
") && ($code = str_replace("
", "\n", $code));
// Paragraph Separator
list($code, $this->strings) = $this->extractStrings($code);
list($code, $this->closures) = $this->extractClosures($code);
$key = "//''\"\"#0'";
// This crap has a wonderful property: it can not happen in any valid javascript, even in strings
$this->closures[$key] =& $code;
$tree = array(
$key => array(
'parent' => false,
),
);
$this->makeVars($code, $tree[$key], $key);
$this->renameVars($tree[$key], true);
$code = substr($tree[$key]['code'], 1);
$code = preg_replace("'\\breturn !'", 'return!', $code);
$code = preg_replace("'\\}(?=(else|while)[^\$.a-zA-Z0-9_])'", "}\r", $code);
// preg_replace is much more efficient than str_replace here
// because we don't need to scan the entire code each time for every replacement
$code = preg_replace_callback('#//\'\'""\\d++(?:/?+\'|\\])#', array(
$this,
'restoreString',
), $code);
if ($singleLine) {
$code = strtr($code, "\n", ';');
}
else {
$code = str_replace("\n", ";\n", $code);
}
false !== strpos($code, "\r") && ($code = strtr(trim($code), "\r", "\n"));
// Cleanup memory
$this->charFreq = array_fill(0, 256, 0);
$this->strings = $this->closures = $this->argFreq = array();
$this->str0 = $this->str1 = '';
return $code;
}
protected function extractStrings($f) {
if ($cc_on = false !== strpos($f, '@cc_on')) {
// Protect conditional comments from being removed
$f = str_replace('#', '##', $f);
$f = str_replace('/*@', '1#@', $f);
$f = preg_replace("'//@([^\n]+)'", '2#@$1@#3', $f);
$f = str_replace('@*/', '@#1', $f);
}
$len = strlen($f);
$code = str_repeat(' ', $len);
$j = 0;
$strings = array();
$K = 0;
$instr = false;
$q = array(
"'",
'"',
"'" => 0,
'"' => 0,
);
// Extract strings, removes comments
for ($i = 0; $i < $len; ++$i) {
if ($instr) {
if ('//' == $instr) {
if ("\n" == $f[$i]) {
$f[$i--] = ' ';
$instr = false;
}
}
elseif ($f[$i] == $instr || '/' == $f[$i] && "/'" == $instr) {
if ('!' == $instr) {
}
elseif ('*' == $instr) {
if ('/' == $f[$i + 1]) {
++$i;
$instr = false;
}
}
else {
if ("/'" == $instr) {
while (isset($f[$i + 1]) && false !== strpos('gmi', $f[$i + 1])) {
$s[] = $f[$i++];
}
$s[] = $f[$i];
}
$instr = false;
}
}
elseif ('*' == $instr) {
}
elseif ('!' == $instr) {
if ('*' == $f[$i] && '/' == $f[$i + 1]) {
$s[] = "*/\r";
++$i;
$instr = false;
}
elseif ("\n" == $f[$i]) {
$s[] = "\r";
}
else {
$s[] = $f[$i];
}
}
elseif ('\\' == $f[$i]) {
++$i;
if ("\n" != $f[$i]) {
isset($q[$f[$i]]) && ++$q[$f[$i]];
$s[] = '\\' . $f[$i];
}
}
elseif ('[' == $f[$i] && "/'" == $instr) {
$instr = '/[';
$s[] = '[';
}
elseif (']' == $f[$i] && '/[' == $instr) {
$instr = "/'";
$s[] = ']';
}
elseif ("'" == $f[$i] || '"' == $f[$i]) {
++$q[$f[$i]];
$s[] = '\\' . $f[$i];
}
else {
$s[] = $f[$i];
}
}
else {
switch ($f[$i]) {
case ';':
// Remove triple semi-colon
if ($i > 0 && ';' == $f[$i - 1] && $i + 1 < $len && ';' == $f[$i + 1]) {
$f[$i] = $f[$i + 1] = '/';
}
else {
$code[++$j] = ';';
break;
}
case '/':
if ('*' == $f[$i + 1]) {
++$i;
$instr = '*';
if ($this->keepImportantComments && '!' == $f[$i + 1]) {
++$i;
// no break here
}
else {
break;
}
}
elseif ('/' == $f[$i + 1]) {
++$i;
$instr = '//';
break;
}
else {
$a = $j && (' ' == $code[$j] || "" == $code[$j]) ? $code[$j - 1] : $code[$j];
if (false !== strpos('-!%&;<=>~:^+|,()*?[{} ', $a) || false !== strpos('oenfd', $a) && preg_match("'(?<![\$.a-zA-Z0-9_])(do|else|return|typeof|yield[ ]?\\*?)[ ]?\$'", substr($code, $j - 7, 8))) {
if (')' === $a && $j > 1) {
$a = 1;
$k = $j - (' ' == $code[$j] || "" == $code[$j]) - 1;
while ($k >= 0 && $a) {
if ('(' === $code[$k]) {
--$a;
}
elseif (')' === $code[$k]) {
++$a;
}
--$k;
}
if (!preg_match("'(?<![\$.a-zA-Z0-9_])(if|for|while)[ ]?\$'", substr($code, 0, $k + 1))) {
$code[++$j] = '/';
break;
}
}
$key = "//''\"\"" . $K++ . ($instr = "/'");
$a = $j;
$code .= $key;
while (isset($key[++$j - $a - 1])) {
$code[$j] = $key[$j - $a - 1];
}
--$j;
isset($s) && ($s = implode('', $s)) && $cc_on && $this->restoreCc($s);
$strings[$key] = array(
'/',
);
$s =& $strings[$key];
}
else {
$code[++$j] = '/';
}
break;
}
case "'":
case '"':
$instr = $f[$i];
$key = "//''\"\"" . $K++ . ('!' == $instr ? ']' : "'");
$a = $j;
$code .= $key;
while (isset($key[++$j - $a - 1])) {
$code[$j] = $key[$j - $a - 1];
}
--$j;
isset($s) && ($s = implode('', $s)) && $cc_on && $this->restoreCc($s);
$strings[$key] = array();
$s =& $strings[$key];
'!' == $instr && ($s[] = "\r/*!");
break;
case "\n":
if ($j > 3) {
if (' ' == $code[$j] || "" == $code[$j]) {
--$j;
}
if (false === strpos('oefd', $code[$j]) || !preg_match("'(?<![\$.a-zA-Z0-9_])(?:do|else|typeof|void)[ ]?\$'", substr($code, $j - 6, 8))) {
$code[++$j] = false !== strpos('kend', $code[$j - 1]) && preg_match("'(?<![\$.a-zA-Z0-9_])(?:break|continue|return|yield[ ]?\\*?)[ ]?\$'", substr($code, $j - 9, 10)) ? ';' : "";
break;
}
}
case "\t":
$f[$i] = ' ';
case ' ':
if (!$j || ' ' == $code[$j] || "" == $code[$j]) {
break;
}
default:
$code[++$j] = $f[$i];
}
}
}
isset($s) && ($s = implode('', $s)) && $cc_on && $this->restoreCc($s);
unset($s);
$code = substr($code, 0, $j + 1);
$cc_on && $this->restoreCc($code, false);
// Deal with newlines before/after postfix/prefix operators
// (a string literal starts with `//` and ends with `'` at this stage)
// http://inimino.org/~inimino/blog/javascript_semicolons
// Newlines before prefix are a new statement when a completed expression precedes because postfix is a "restrictd production"
// A closing bracket `)` from if/for/while does not complete an expression, so mark possible `;` as `#` to deal with later
$code = preg_replace("#(?<=[a-zA-Z\$_\\d'\\]}])(--|\\+\\+)#", ';$1', $code);
$code = preg_replace("#(?<=\\))(--|\\+\\+)#", '#$1', $code);
// Newlines after postfix are a new statement if the following token can't be parsed otherwise
// i.e. it's a keyword, identifier, string or number literal, prefix operator, opening brace
// But a prefix operator can have a newline before its operand, so check a completed expression precedes to be sure it's a postfix
// Again mark case after closing bracket with `#` to deal with later
// Also ensure keywords that may be followed by an expression aren't mistaken for the end of a completed expression
// (note that postfix cannot apply to an expression completed with `}`)
$code = preg_replace("#(?<![\$.a-zA-Z0-9_])(do|else|return|throw|typeof|void|yield) ?+(--|\\+\\+)#", '$1$2 ', $code);
$code = preg_replace("#(?<=[a-zA-Z\$_\\d'\\]]) ?+(--|\\+\\+)(?=//|--|\\+\\+|[a-zA-Z\$_\\d[({])#", '$1;', $code);
$code = preg_replace("#(?<=\\)) ?+(--|\\+\\+)(?=//|--|\\+\\+|[a-zA-Z\$_\\d[({])#", '$1#', $code);
// Protect wanted spaces and remove unwanted ones
$code = strtr($code, "", ' ');
$code = str_replace('- -', "--", $code);
$code = str_replace('+ +', "++", $code);
$code = preg_replace("'(\\d)\\s+\\.\\s*([a-zA-Z\$_[(])'", "\$1.\$2", $code);
$code = preg_replace("# ([-!%&;<=>~:.^+|,()*?[\\]{}/']+)#", '$1', $code);
$code = preg_replace("#([-!%&;<=>~:.^+|,()*?[\\]{}/]+) #", '$1', $code);
$cc_on && ($code = preg_replace_callback("'//[^\\'].*?@#3'", function ($m) {
return strtr($m[0], ' ', "");
}, $code));
// Replace new Array/Object by []/{}
false !== strpos($code, 'new Array') && ($code = preg_replace("'new Array(?:\\(\\)|([;\\])},:]))'", '[]$1', $code));
false !== strpos($code, 'new Object') && ($code = preg_replace("'new Object(?:\\(\\)|([;\\])},:]))'", '{}$1', $code));
// Add missing semi-colons after curly braces
// This adds more semi-colons than strictly needed,
// but it seems that later gzipping is favorable to the repetition of "};"
$code = preg_replace("'\\}(?![:,;.()\\[\\]}\\|&?]|(else|catch|finally|while)[^\$.a-zA-Z0-9_])'", '};', $code);
// Tag possible empty instruction for easy detection
$code = preg_replace("'(?<![\$.a-zA-Z0-9_])if\\('", '1#(', $code);
$code = preg_replace("'(?<![\$.a-zA-Z0-9_])for\\('", '2#(', $code);
$code = preg_replace("'(?<![\$.a-zA-Z0-9_])do while\\('", '4#(', $code);
$code = preg_replace("'(?<![\$.a-zA-Z0-9_])while\\('", '3#(', $code);
$code = preg_replace("'(?<![\$.a-zA-Z0-9_])do(?![\$a-zA-Z0-9_])'", '5#', $code);
$forPool = array();
$instrPool = array();
$doPool = array();
$s = 0;
$d = 0;
$f = array();
$j = -1;
// Remove as much semi-colon as possible
$len = strlen($code);
for ($i = 0; $i < $len; ++$i) {
switch ($code[$i]) {
case '(':
if ($j >= 0 && "\n" == $f[$j]) {
$f[$j] = ';';
}
++$s;
if ($i > 1 && '#' == $code[$i - 1]) {
switch ($code[$i - 2]) {
case '3':
if (isset($doPool[$d])) {
$instrPool[$s - 1] = 5;
// `while` corresponds to `do`
unset($doPool[$d]);
}
else {
$instrPool[$s - 1] = 1;
}
break;
case '2':
$forPool[$s] = 1;
// also set $instrPool
case '1':
case '4':
$instrPool[$s - 1] = 1;
}
}
$f[++$j] = '(';
break;
case ']':
case ')':
if ($i + 1 < $len && !isset($forPool[$s]) && !isset($instrPool[$s - 1]) && preg_match("'[a-zA-Z0-9_\$]'", $code[$i + 1])) {
$f[$j] .= $code[$i];
$f[++$j] = "\n";
}
else {
$f[++$j] = $code[$i];
}
if (')' == $code[$i]) {
unset($forPool[$s]);
--$s;
if (isset($instrPool[$s]) && 5 === $instrPool[$s]) {
$f[$j - 1] .= ')';
$f[$j] = ';';
}
}
continue 2;
case '{':
++$d;
$f[++$j] = '{';
break;
case '}':
--$d;
if ("\n" == $f[$j]) {
$f[$j] = '}';
}
else {
$f[++$j] = '}';
}
break;
case '+':
case '-':
$f[++$j] = $code[$i];
if ($i + 1 < $len && ($code[$i] === $code[$i + 1] || '#' === $code[$i + 1])) {
// delay unsetting $instrPool[$s]
continue 2;
}
break;
case '#':
switch ($f[$j]) {
case '1':
$f[$j] = 'if';
break 2;
case '2':
$f[$j] = 'for';
break 2;
case '3':
$f[$j] = 'while';
break 2;
case '4':
// special case `while` that doesn't correspond to the `do`
$f[$j] = 'do while';
$doPool[$d] = 1;
break 2;
case '5':
$f[$j] = 'do';
$doPool[$d] = 1;
case ';':
// added after `do..while` - no extra `;` needed
break 2;
case ')':
case '+':
case '-':
if (isset($instrPool[$s])) {
// prefix operator in conditional/loop statement - no `;`
break 2;
}
}
case ';':
if (isset($forPool[$s]) || isset($instrPool[$s]) && 5 !== $instrPool[$s]) {
$f[++$j] = ';';
}
elseif ($j >= 0 && "\n" != $f[$j] && ';' != $f[$j]) {
$f[++$j] = "\n";
}
break;
case '[':
if ($j >= 0 && "\n" == $f[$j]) {
$f[$j] = ';';
}
default:
$f[++$j] = $code[$i];
}
unset($instrPool[$s]);
}
$f = implode('', $f);
$cc_on && ($f = str_replace('@#3', "\r", $f));
// Fix "else ;" empty instructions
$f = preg_replace("'(?<![\$.a-zA-Z0-9_])else([\n}])'", '$1', $f);
$r1 = array(
// keywords with a direct object
'case',
'delete',
'do',
'else',
'function',
'in',
'instanceof',
'of',
'break',
'new',
'return',
'throw',
'typeof',
'var',
'void',
'yield',
'let',
'if',
'const',
'get',
'set',
'continue',
);
$r2 = array(
// keywords with a subject
'in',
'instanceof',
'of',
);
// Fix missing semi-colons
$f = preg_replace("'(?<!(?<![a-zA-Z0-9_\$])" . implode(')(?<!(?<![a-zA-Z0-9_\\$])', $r1) . ') (?!(' . implode('|', $r2) . ")(?![a-zA-Z0-9_\$]))'", "\n", $f);
$f = preg_replace("'(?<!(?<![a-zA-Z0-9_\$])do)(?<!(?<![a-zA-Z0-9_\$])else) if\\('", "\nif(", $f);
$f = preg_replace("'(?<=--|\\+\\+)(?<![a-zA-Z0-9_\$])(" . implode('|', $r1) . ")(?![a-zA-Z0-9_\$])'", "\n\$1", $f);
$f = preg_replace("'(?<![a-zA-Z0-9_\$])for\neach\\('", 'for each(', $f);
$f = preg_replace("'(?<![a-zA-Z0-9_\$])\n(" . implode('|', $r2) . ")(?![a-zA-Z0-9_\$])'", '$1', $f);
// Merge strings
if ($q["'"] > $q['"']) {
$q = array(
$q[1],
$q[0],
);
}
$f = preg_replace("#//''\"\"[0-9]+'#", $q[0] . '$0' . $q[0], $f);
strpos($f, $q[0] . '+' . $q[0]) && ($f = str_replace($q[0] . '+' . $q[0], '', $f));
$len = count($strings);
foreach ($strings as $r1 => &$r2) {
$r2 = "/'" == substr($r1, -2) ? str_replace(array(
"\\'",
'\\"',
), array(
"'",
'"',
), $r2) : str_replace('\\' . $q[1], $q[1], $r2);
}
// Restore wanted spaces
$f = strtr($f, "", ' ');
return array(
$f,
$strings,
);
}
protected function extractClosures($code) {
$code = ';' . $code;
$this->argFreq[-1] += substr_count($code, '}catch(');
if ($this->argFreq[-1]) {
// Special catch scope handling
// FIXME: this implementation doesn't work with nested catch scopes who need
// access to their parent's caught variable (but who needs that?).
$f = preg_split("@}catch\\(({$this->varRx})@", $code, -1, PREG_SPLIT_DELIM_CAPTURE);
$code = 'catch$scope$var' . mt_rand();
$this->specialVarRx = $this->specialVarRx ? '(?:' . $this->specialVarRx . '|' . preg_quote($code) . ')' : preg_quote($code);
$i = count($f) - 1;
while ($i) {
$c = 1;
$j = 0;
$l = strlen($f[$i]);
while ($c && $j < $l) {
$s = $f[$i][$j++];
$c += '(' == $s ? 1 : (')' == $s ? -1 : 0);
}
if (!$c) {
do {
$s = $f[$i][$j++];
$c += '{' == $s ? 1 : ('}' == $s ? -1 : 0);
} while ($c && $j < $l);
}
$c = preg_quote($f[$i - 1], '#');
$f[$i - 2] .= '}catch(' . preg_replace("#([.,{]?)(?<![a-zA-Z0-9_\$@]){$c}\\b#", '$1' . $code, $f[$i - 1] . substr($f[$i], 0, $j)) . substr($f[$i], $j);
unset($f[$i--], $f[$i--]);
}
$code = $f[0];
}
$f = preg_split("'(?<![a-zA-Z0-9_\$])((?:function[ (]|get |set ).*?\\{)'", $code, -1, PREG_SPLIT_DELIM_CAPTURE);
$i = count($f) - 1;
$closures = array();
while ($i) {
$c = 1;
$j = 0;
$l = strlen($f[$i]);
while ($c && $j < $l) {
$s = $f[$i][$j++];
$c += '{' == $s ? 1 : ('}' == $s ? -1 : 0);
}
switch (substr($f[$i - 2], -1)) {
default:
if (false !== ($c = strpos($f[$i - 1], ' ', 8))) {
break;
}
case false:
case "\n":
case ';':
case '{':
case '}':
case ')':
case ']':
$c = strpos($f[$i - 1], '(', 4);
}
$l = "//''\"\"#{$i}'";
$code = substr($f[$i - 1], $c);
$closures[$l] = $code . substr($f[$i], 0, $j);
$f[$i - 2] .= substr($f[$i - 1], 0, $c) . $l . substr($f[$i], $j);
if ('(){' !== $code) {
$j = substr_count($code, ',');
do {
isset($this->argFreq[$j]) ? ++$this->argFreq[$j] : ($this->argFreq[$j] = 1);
} while ($j--);
}
$i -= 2;
}
return array(
$f[0],
$closures,
);
}
protected function makeVars($closure, &$tree, $key) {
$tree['code'] =& $closure;
$tree['nfe'] = false;
$tree['used'] = array();
$tree['local'] = array();
// Replace multiple "var" declarations by a single one
$closure = preg_replace_callback("'(?<=[\n\\{\\}])var [^\n\\{\\};]+(?:\nvar [^\n\\{\\};]+)+'", array(
$this,
'mergeVarDeclarations',
), $closure);
// Get all local vars (functions, arguments and "var" prefixed)
$vars =& $tree['local'];
if (preg_match("'^( [^(]*)?\\((.*?)\\)\\{'", $closure, $v)) {
if ($v[1]) {
$vars[$tree['nfe'] = substr($v[1], 1)] = -1;
$tree['parent']['local'][';' . $key] =& $vars[$tree['nfe']];
}
if ($v[2]) {
$i = 0;
$v = explode(',', $v[2]);
foreach ($v as $w) {
$vars[$w] = $this->argFreq[$i++] - 1;
// Give a bonus to argument variables
}
}
}
$v = preg_split("'(?<![\$.a-zA-Z0-9_])var '", $closure);
if ($i = count($v) - 1) {
$w = array();
while ($i) {
$j = $c = 0;
$l = strlen($v[$i]);
while ($j < $l) {
switch ($v[$i][$j]) {
case '(':
case '[':
case '{':
++$c;
break;
case ')':
case ']':
case '}':
if ($c-- <= 0) {
break 2;
}
break;
case ';':
case "\n":
if (!$c) {
break 2;
}
default:
$c || ($w[] = $v[$i][$j]);
}
++$j;
}
$w[] = ',';
--$i;
}
$v = explode(',', implode('', $w));
foreach ($v as $w) {
if (preg_match("'^{$this->varRx}'", $w, $v)) {
isset($vars[$v[0]]) || ($vars[$v[0]] = 0);
}
}
}
if (preg_match_all("@function ({$this->varRx})//''\"\"#@", $closure, $v)) {
foreach ($v[1] as $w) {
isset($vars[$w]) || ($vars[$w] = 0);
}
}
if ($this->argFreq[-1] && preg_match_all("@}catch\\(({$this->varRx})@", $closure, $v)) {
$v[0] = array();
foreach ($v[1] as $w) {
isset($v[0][$w]) ? ++$v[0][$w] : ($v[0][$w] = 1);
}
foreach ($v[0] as $w => $v) {
$vars[$w] = $this->argFreq[-1] - $v;
}
}
// Get all used vars, local and non-local
$vars =& $tree['used'];
if (preg_match_all("#([.,{]?(?:[gs]et )?)(?<![a-zA-Z0-9_\$])({$this->varRx})(:?)#", $closure, $w, PREG_SET_ORDER)) {
foreach ($w as $k) {
if (isset($k[1][0]) && (',' === $k[1][0] || '{' === $k[1][0])) {
if (':' === $k[3]) {
$k = '.' . $k[2];
}
elseif ('get ' === substr($k[1], 1, 4) || 'set ' === substr($k[1], 1, 4)) {
++$this->charFreq[ord($k[1][1])];
// "g" or "s"
++$this->charFreq[101];
// "e"
++$this->charFreq[116];
// "t"
$k = '.' . $k[2];
}
else {
$k = $k[2];
}
}
else {
$k = $k[1] . $k[2];
}
isset($vars[$k]) ? ++$vars[$k] : ($vars[$k] = 1);
}
}
if (preg_match_all("#//''\"\"[0-9]+(?:['!]|/')#", $closure, $w)) {
foreach ($w[0] as $a) {
$v = "'" === substr($a, -1) && "/'" !== substr($a, -2) && $this->specialVarRx ? preg_split("#([.,{]?(?:[gs]et )?(?<![a-zA-Z0-9_\$@]){$this->specialVarRx}:?)#", $this->strings[$a], -1, PREG_SPLIT_DELIM_CAPTURE) : array(
$this->strings[$a],
);
$a = count($v);
for ($i = 0; $i < $a; ++$i) {
$k = $v[$i];
if (1 === $i % 2) {
if (',' === $k[0] || '{' === $k[0]) {
if (':' === substr($k, -1)) {
$k = '.' . substr($k, 1, -1);
}
elseif ('get ' === substr($k, 1, 4) || 'set ' === substr($k, 1, 4)) {
++$this->charFreq[ord($k[1])];
// "g" or "s"
++$this->charFreq[101];
// "e"
++$this->charFreq[116];
// "t"
$k = '.' . substr($k, 5);
}
else {
$k = substr($k, 1);
}
}
elseif (':' === substr($k, -1)) {
$k = substr($k, 0, -1);
}
$w =& $tree;
while (isset($w['parent']) && !(isset($w['used'][$k]) || isset($w['local'][$k]))) {
$w =& $w['parent'];
}
(isset($w['used'][$k]) || isset($w['local'][$k])) && (isset($vars[$k]) ? ++$vars[$k] : ($vars[$k] = 1));
unset($w);
}
if (0 === $i % 2 || !isset($vars[$k])) {
foreach (count_chars($v[$i], 1) as $k => $w) {
$this->charFreq[$k] += $w;
}
}
}
}
}
// Propagate the usage number to parents
foreach ($vars as $w => $a) {
$k =& $tree;
$chain = array();
do {
$vars =& $k['local'];
$chain[] =& $k;
if (isset($vars[$w])) {
unset($k['used'][$w]);
if (isset($vars[$w])) {
$vars[$w] += $a;
}
else {
$vars[$w] = $a;
}
$a = false;
break;
}
} while ($k['parent'] && ($k =& $k['parent']));
if ($a && !$k['parent']) {
if (isset($vars[$w])) {
$vars[$w] += $a;
}
else {
$vars[$w] = $a;
}
}
if (isset($tree['used'][$w]) && isset($vars[$w])) {
foreach ($chain as &$b) {
isset($b['local'][$w]) || ($b['used'][$w] =& $vars[$w]);
}
}
}
// Analyse children
$tree['children'] = array();
$vars =& $tree['children'];
if (preg_match_all("@//''\"\"#[0-9]+'@", $closure, $w)) {
foreach ($w[0] as $a) {
$vars[$a] = array(
'parent' => &$tree,
);
$this->makeVars($this->closures[$a], $vars[$a], $a);
}
}
}
protected function mergeVarDeclarations($m) {
return str_replace("\nvar ", ',', $m[0]);
}
protected function renameVars(&$tree, $root) {
if ($root) {
$tree['local'] += $tree['used'];
$tree['used'] = array();
foreach ($tree['local'] as $k => $v) {
if ('.' == $k[0]) {
$k = substr($k, 1);
}
if ('true' === $k) {
$this->charFreq[48] += $v;
}
elseif ('false' === $k) {
$this->charFreq[49] += $v;
}
elseif (!$this->specialVarRx || !preg_match("#^{$this->specialVarRx}\$#", $k)) {
foreach (count_chars($k, 1) as $k => $w) {
$this->charFreq[$k] += $w * $v;
}
}
elseif (2 == strlen($k)) {
$tree['used'][] = $k[1];
}
}
$this->charFreq = $this->rsort($this->charFreq);
$this->str0 = '';
$this->str1 = '';
foreach ($this->charFreq as $k => $v) {
if (!$v) {
break;
}
$v = chr($k);
if (64 < $k && $k < 91 || 96 < $k && $k < 123) {
// A-Z a-z
$this->str0 .= $v;
$this->str1 .= $v;
}
elseif (47 < $k && $k < 58) {
// 0-9
$this->str1 .= $v;
}
}
if ('' === $this->str0) {
$this->str0 = 'claspemitdbfrugnjvhowkxqyzCLASPEMITDBFRUGNJVHOWKXQYZ';
$this->str1 = $this->str0 . '0123456789';
}
foreach ($tree['local'] as $var => $root) {
if ('.' != substr($var, 0, 1) && isset($tree['local'][".{$var}"])) {
$tree['local'][$var] += $tree['local'][".{$var}"];
}
}
foreach ($tree['local'] as $var => $root) {
if ('.' == substr($var, 0, 1) && isset($tree['local'][substr($var, 1)])) {
$tree['local'][$var] = $tree['local'][substr($var, 1)];
}
}
$tree['local'] = $this->rsort($tree['local']);
foreach ($tree['local'] as $var => $root) {
switch (substr($var, 0, 1)) {
case '.':
if (!isset($tree['local'][substr($var, 1)])) {
$tree['local'][$var] = '#' . ($this->specialVarRx && 3 < strlen($var) && preg_match("'^\\.{$this->specialVarRx}\$'", $var) ? $this->getNextName($tree) . '$' : substr($var, 1));
}
break;
case ';':
$tree['local'][$var] = 0 === $root ? '' : $this->getNextName($tree);
case '#':
break;
default:
$root = $this->specialVarRx && 2 < strlen($var) && preg_match("'^{$this->specialVarRx}\$'", $var) ? $this->getNextName($tree) . '$' : $var;
$tree['local'][$var] = $root;
if (isset($tree['local'][".{$var}"])) {
$tree['local'][".{$var}"] = '#' . $root;
}
}
}
foreach ($tree['local'] as $var => $root) {
$tree['local'][$var] = preg_replace("'^#'", '.', $tree['local'][$var]);
}
}
else {
$tree['local'] = $this->rsort($tree['local']);
if (false !== $tree['nfe']) {
$tree['used'][] = $tree['local'][$tree['nfe']];
}
foreach ($tree['local'] as $var => $root) {
if ($tree['nfe'] !== $var) {
$tree['local'][$var] = 0 === $root ? '' : $this->getNextName($tree);
}
}
}
$this->local_tree =& $tree['local'];
$this->used_tree =& $tree['used'];
$tree['code'] = preg_replace_callback("#[.,{ ]?(?:[gs]et )?(?<![a-zA-Z0-9_\$@]){$this->varRx}:?#", array(
$this,
'getNewName',
), $tree['code']);
if ($this->specialVarRx && preg_match_all("#//''\"\"[0-9]+'#", $tree['code'], $b)) {
foreach ($b[0] as $a) {
$this->strings[$a] = preg_replace_callback("#[.,{]?(?:[gs]et )?(?<![a-zA-Z0-9_\$@]){$this->specialVarRx}:?#", array(
$this,
'getNewName',
), $this->strings[$a]);
}
}
foreach ($tree['children'] as $a => &$b) {
$this->renameVars($b, false);
$tree['code'] = str_replace($a, $b['code'], $tree['code']);
unset($tree['children'][$a]);
}
}
protected function getNewName($m) {
$m = $m[0];
$pre = '.' === $m[0] ? '.' : '';
$post = '';
if (',' === $m[0] || '{' === $m[0] || ' ' === $m[0]) {
$pre = $m[0];
if (':' === substr($m, -1)) {
$post = ':';
$m = (' ' !== $m[0] ? '.' : '') . substr($m, 1, -1);
}
elseif ('get ' === substr($m, 1, 4) || 'set ' === substr($m, 1, 4)) {
$pre .= substr($m, 1, 4);
$m = '.' . substr($m, 5);
}
else {
$m = substr($m, 1);
}
}
elseif (':' === substr($m, -1)) {
$post = ':';
$m = substr($m, 0, -1);
}
$post = (isset($this->reserved[$m]) ? 'true' === $m ? '!0' : ('false' === $m ? '!1' : $m) : (isset($this->local_tree[$m]) ? $this->local_tree[$m] : (isset($this->used_tree[$m]) ? $this->used_tree[$m] : $m))) . $post;
return '' === $post ? '' : $pre . ('.' === $post[0] ? substr($post, 1) : $post);
}
protected function getNextName(&$tree = array(), &$counter = false) {
if (false === $counter) {
$counter =& $tree['counter'];
isset($counter) || ($counter = -1);
$exclude = array_flip($tree['used']);
}
else {
$exclude = $tree;
}
++$counter;
$len0 = strlen($this->str0);
$len1 = strlen($this->str0);
$name = $this->str0[$counter % $len0];
$i = intval($counter / $len0) - 1;
while ($i >= 0) {
$name .= $this->str1[$i % $len1];
$i = intval($i / $len1) - 1;
}
return !(isset($this->reserved[$name]) || isset($exclude[$name])) ? $name : $this->getNextName($exclude, $counter);
}
protected function restoreCc(&$s, $lf = true) {
$lf && ($s = str_replace('@#3', '', $s));
$s = str_replace('@#1', '@*/', $s);
$s = str_replace('2#@', '//@', $s);
$s = str_replace('1#@', '/*@', $s);
$s = str_replace('##', '#', $s);
}
protected function restoreString($m) {
return $this->strings[$m[0]];
}
private function rsort($array) {
if (!$array) {
return $array;
}
$i = 0;
$tuples = array();
foreach ($array as $k => &$v) {
$tuples[] = array(
++$i,
$k,
&$v,
);
}
usort($tuples, function ($a, $b) {
if ($b[2] > $a[2]) {
return 1;
}
if ($b[2] < $a[2]) {
return -1;
}
if ($b[0] > $a[0]) {
return -1;
}
if ($b[0] < $a[0]) {
return 1;
}
return 0;
});
$array = array();
foreach ($tuples as $t) {
$array[$t[1]] =& $t[2];
}
return $array;
}
}
Members
Title Sort descending | Modifiers | Object type | Summary |
---|---|---|---|
JSqueeze::$charFreq | public | property | |
JSqueeze::$strings | protected | property | |
JSqueeze::extractClosures | protected | function | |
JSqueeze::extractStrings | protected | function | |
JSqueeze::getNewName | protected | function | |
JSqueeze::getNextName | protected | function | |
JSqueeze::makeVars | protected | function | |
JSqueeze::mergeVarDeclarations | protected | function | |
JSqueeze::renameVars | protected | function | |
JSqueeze::restoreCc | protected | function | |
JSqueeze::restoreString | protected | function | |
JSqueeze::rsort | private | function | |
JSqueeze::SPECIAL_VAR_PACKER | constant | ||
JSqueeze::squeeze | public | function | Squeezes a JavaScript source code. |
JSqueeze::__construct | public | function |