]> scripts.mit.edu Git - autoinstalls/mediawiki.git/blob - includes/libs/JavaScriptMinifier.php
MediaWiki 1.30.2-scripts2
[autoinstalls/mediawiki.git] / includes / libs / JavaScriptMinifier.php
1 <?php
2 // @codingStandardsIgnoreFile File external to MediaWiki. Ignore coding conventions checks.
3 /**
4  * JavaScript Minifier
5  *
6  * @file
7  * @author Paul Copperman <paul.copperman@gmail.com>
8  * @license Choose any of Apache, MIT, GPL, LGPL
9  */
10
11 /**
12  * This class is meant to safely minify javascript code, while leaving syntactically correct
13  * programs intact. Other libraries, such as JSMin require a certain coding style to work
14  * correctly. OTOH, libraries like jsminplus, that do parse the code correctly are rather
15  * slow, because they construct a complete parse tree before outputting the code minified.
16  * So this class is meant to allow arbitrary (but syntactically correct) input, while being
17  * fast enough to be used for on-the-fly minifying.
18  */
19 class JavaScriptMinifier {
20
21         /* Class constants */
22         /* Parsing states.
23          * The state machine is only necessary to decide whether to parse a slash as division
24          * operator or as regexp literal.
25          * States are named after the next expected item. We only distinguish states when the
26          * distinction is relevant for our purpose.
27          */
28         const STATEMENT                = 0;
29         const CONDITION                = 1;
30         const PROPERTY_ASSIGNMENT      = 2;
31         const EXPRESSION               = 3;
32         const EXPRESSION_NO_NL         = 4; // only relevant for semicolon insertion
33         const EXPRESSION_OP            = 5;
34         const EXPRESSION_FUNC          = 6;
35         const EXPRESSION_TERNARY       = 7; // used to determine the role of a colon
36         const EXPRESSION_TERNARY_OP    = 8;
37         const EXPRESSION_TERNARY_FUNC  = 9;
38         const PAREN_EXPRESSION         = 10; // expression which is not on the top level
39         const PAREN_EXPRESSION_OP      = 11;
40         const PAREN_EXPRESSION_FUNC    = 12;
41         const PROPERTY_EXPRESSION      = 13; // expression which is within an object literal
42         const PROPERTY_EXPRESSION_OP   = 14;
43         const PROPERTY_EXPRESSION_FUNC = 15;
44
45         /* Token types */
46         const TYPE_UN_OP       = 1; // unary operators
47         const TYPE_INCR_OP     = 2; // ++ and --
48         const TYPE_BIN_OP      = 3; // binary operators
49         const TYPE_ADD_OP      = 4; // + and - which can be either unary or binary ops
50         const TYPE_HOOK        = 5; // ?
51         const TYPE_COLON       = 6; // :
52         const TYPE_COMMA       = 7; // ,
53         const TYPE_SEMICOLON   = 8; // ;
54         const TYPE_BRACE_OPEN  = 9; // {
55         const TYPE_BRACE_CLOSE = 10; // }
56         const TYPE_PAREN_OPEN  = 11; // ( and [
57         const TYPE_PAREN_CLOSE = 12; // ) and ]
58         const TYPE_RETURN      = 13; // keywords: break, continue, return, throw
59         const TYPE_IF          = 14; // keywords: catch, for, with, switch, while, if
60         const TYPE_DO          = 15; // keywords: case, var, finally, else, do, try
61         const TYPE_FUNC        = 16; // keywords: function
62         const TYPE_LITERAL     = 17; // all literals, identifiers and unrecognised tokens
63
64         // Sanity limit to avoid excessive memory usage
65         const STACK_LIMIT = 1000;
66
67         /* Static functions */
68
69         /**
70          * Returns minified JavaScript code.
71          *
72          * NOTE: $maxLineLength isn't a strict maximum. Longer lines will be produced when
73          *       literals (e.g. quoted strings) longer than $maxLineLength are encountered
74          *       or when required to guard against semicolon insertion.
75          *
76          * @param string $s JavaScript code to minify
77          * @param bool $statementsOnOwnLine Whether to put each statement on its own line
78          * @param int $maxLineLength Maximum length of a single line, or -1 for no maximum.
79          * @return String Minified code
80          */
81         public static function minify( $s, $statementsOnOwnLine = false, $maxLineLength = 1000 ) {
82                 // First we declare a few tables that contain our parsing rules
83
84                 // $opChars : characters, which can be combined without whitespace in between them
85                 $opChars = array(
86                         '!' => true,
87                         '"' => true,
88                         '%' => true,
89                         '&' => true,
90                         "'" => true,
91                         '(' => true,
92                         ')' => true,
93                         '*' => true,
94                         '+' => true,
95                         ',' => true,
96                         '-' => true,
97                         '.' => true,
98                         '/' => true,
99                         ':' => true,
100                         ';' => true,
101                         '<' => true,
102                         '=' => true,
103                         '>' => true,
104                         '?' => true,
105                         '[' => true,
106                         ']' => true,
107                         '^' => true,
108                         '{' => true,
109                         '|' => true,
110                         '}' => true,
111                         '~' => true
112                 );
113
114                 // $tokenTypes : maps keywords and operators to their corresponding token type
115                 $tokenTypes = array(
116                         '!'          => self::TYPE_UN_OP,
117                         '~'          => self::TYPE_UN_OP,
118                         'delete'     => self::TYPE_UN_OP,
119                         'new'        => self::TYPE_UN_OP,
120                         'typeof'     => self::TYPE_UN_OP,
121                         'void'       => self::TYPE_UN_OP,
122                         '++'         => self::TYPE_INCR_OP,
123                         '--'         => self::TYPE_INCR_OP,
124                         '!='         => self::TYPE_BIN_OP,
125                         '!=='        => self::TYPE_BIN_OP,
126                         '%'          => self::TYPE_BIN_OP,
127                         '%='         => self::TYPE_BIN_OP,
128                         '&'          => self::TYPE_BIN_OP,
129                         '&&'         => self::TYPE_BIN_OP,
130                         '&='         => self::TYPE_BIN_OP,
131                         '*'          => self::TYPE_BIN_OP,
132                         '*='         => self::TYPE_BIN_OP,
133                         '+='         => self::TYPE_BIN_OP,
134                         '-='         => self::TYPE_BIN_OP,
135                         '.'          => self::TYPE_BIN_OP,
136                         '/'          => self::TYPE_BIN_OP,
137                         '/='         => self::TYPE_BIN_OP,
138                         '<'          => self::TYPE_BIN_OP,
139                         '<<'         => self::TYPE_BIN_OP,
140                         '<<='        => self::TYPE_BIN_OP,
141                         '<='         => self::TYPE_BIN_OP,
142                         '='          => self::TYPE_BIN_OP,
143                         '=='         => self::TYPE_BIN_OP,
144                         '==='        => self::TYPE_BIN_OP,
145                         '>'          => self::TYPE_BIN_OP,
146                         '>='         => self::TYPE_BIN_OP,
147                         '>>'         => self::TYPE_BIN_OP,
148                         '>>='        => self::TYPE_BIN_OP,
149                         '>>>'        => self::TYPE_BIN_OP,
150                         '>>>='       => self::TYPE_BIN_OP,
151                         '^'          => self::TYPE_BIN_OP,
152                         '^='         => self::TYPE_BIN_OP,
153                         '|'          => self::TYPE_BIN_OP,
154                         '|='         => self::TYPE_BIN_OP,
155                         '||'         => self::TYPE_BIN_OP,
156                         'in'         => self::TYPE_BIN_OP,
157                         'instanceof' => self::TYPE_BIN_OP,
158                         '+'          => self::TYPE_ADD_OP,
159                         '-'          => self::TYPE_ADD_OP,
160                         '?'          => self::TYPE_HOOK,
161                         ':'          => self::TYPE_COLON,
162                         ','          => self::TYPE_COMMA,
163                         ';'          => self::TYPE_SEMICOLON,
164                         '{'          => self::TYPE_BRACE_OPEN,
165                         '}'          => self::TYPE_BRACE_CLOSE,
166                         '('          => self::TYPE_PAREN_OPEN,
167                         '['          => self::TYPE_PAREN_OPEN,
168                         ')'          => self::TYPE_PAREN_CLOSE,
169                         ']'          => self::TYPE_PAREN_CLOSE,
170                         'break'      => self::TYPE_RETURN,
171                         'continue'   => self::TYPE_RETURN,
172                         'return'     => self::TYPE_RETURN,
173                         'throw'      => self::TYPE_RETURN,
174                         'catch'      => self::TYPE_IF,
175                         'for'        => self::TYPE_IF,
176                         'if'         => self::TYPE_IF,
177                         'switch'     => self::TYPE_IF,
178                         'while'      => self::TYPE_IF,
179                         'with'       => self::TYPE_IF,
180                         'case'       => self::TYPE_DO,
181                         'do'         => self::TYPE_DO,
182                         'else'       => self::TYPE_DO,
183                         'finally'    => self::TYPE_DO,
184                         'try'        => self::TYPE_DO,
185                         'var'        => self::TYPE_DO,
186                         'function'   => self::TYPE_FUNC
187                 );
188
189                 // $goto : This is the main table for our state machine. For every state/token pair
190                 //         the following state is defined. When no rule exists for a given pair,
191                 //         the state is left unchanged.
192                 $goto = array(
193                         self::STATEMENT => array(
194                                 self::TYPE_UN_OP      => self::EXPRESSION,
195                                 self::TYPE_INCR_OP    => self::EXPRESSION,
196                                 self::TYPE_ADD_OP     => self::EXPRESSION,
197                                 self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION,
198                                 self::TYPE_RETURN     => self::EXPRESSION_NO_NL,
199                                 self::TYPE_IF         => self::CONDITION,
200                                 self::TYPE_FUNC       => self::CONDITION,
201                                 self::TYPE_LITERAL    => self::EXPRESSION_OP
202                         ),
203                         self::CONDITION => array(
204                                 self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION
205                         ),
206                         self::PROPERTY_ASSIGNMENT => array(
207                                 self::TYPE_COLON      => self::PROPERTY_EXPRESSION,
208                                 self::TYPE_BRACE_OPEN => self::STATEMENT
209                         ),
210                         self::EXPRESSION => array(
211                                 self::TYPE_SEMICOLON  => self::STATEMENT,
212                                 self::TYPE_BRACE_OPEN => self::PROPERTY_ASSIGNMENT,
213                                 self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION,
214                                 self::TYPE_FUNC       => self::EXPRESSION_FUNC,
215                                 self::TYPE_LITERAL    => self::EXPRESSION_OP
216                         ),
217                         self::EXPRESSION_NO_NL => array(
218                                 self::TYPE_SEMICOLON  => self::STATEMENT,
219                                 self::TYPE_BRACE_OPEN => self::PROPERTY_ASSIGNMENT,
220                                 self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION,
221                                 self::TYPE_FUNC       => self::EXPRESSION_FUNC,
222                                 self::TYPE_LITERAL    => self::EXPRESSION_OP
223                         ),
224                         self::EXPRESSION_OP => array(
225                                 self::TYPE_BIN_OP     => self::EXPRESSION,
226                                 self::TYPE_ADD_OP     => self::EXPRESSION,
227                                 self::TYPE_HOOK       => self::EXPRESSION_TERNARY,
228                                 self::TYPE_COLON      => self::STATEMENT,
229                                 self::TYPE_COMMA      => self::EXPRESSION,
230                                 self::TYPE_SEMICOLON  => self::STATEMENT,
231                                 self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION
232                         ),
233                         self::EXPRESSION_FUNC => array(
234                                 self::TYPE_BRACE_OPEN => self::STATEMENT
235                         ),
236                         self::EXPRESSION_TERNARY => array(
237                                 self::TYPE_BRACE_OPEN => self::PROPERTY_ASSIGNMENT,
238                                 self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION,
239                                 self::TYPE_FUNC       => self::EXPRESSION_TERNARY_FUNC,
240                                 self::TYPE_LITERAL    => self::EXPRESSION_TERNARY_OP
241                         ),
242                         self::EXPRESSION_TERNARY_OP => array(
243                                 self::TYPE_BIN_OP     => self::EXPRESSION_TERNARY,
244                                 self::TYPE_ADD_OP     => self::EXPRESSION_TERNARY,
245                                 self::TYPE_HOOK       => self::EXPRESSION_TERNARY,
246                                 self::TYPE_COMMA      => self::EXPRESSION_TERNARY,
247                                 self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION
248                         ),
249                         self::EXPRESSION_TERNARY_FUNC => array(
250                                 self::TYPE_BRACE_OPEN => self::STATEMENT
251                         ),
252                         self::PAREN_EXPRESSION => array(
253                                 self::TYPE_BRACE_OPEN => self::PROPERTY_ASSIGNMENT,
254                                 self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION,
255                                 self::TYPE_FUNC       => self::PAREN_EXPRESSION_FUNC,
256                                 self::TYPE_LITERAL    => self::PAREN_EXPRESSION_OP
257                         ),
258                         self::PAREN_EXPRESSION_OP => array(
259                                 self::TYPE_BIN_OP     => self::PAREN_EXPRESSION,
260                                 self::TYPE_ADD_OP     => self::PAREN_EXPRESSION,
261                                 self::TYPE_HOOK       => self::PAREN_EXPRESSION,
262                                 self::TYPE_COLON      => self::PAREN_EXPRESSION,
263                                 self::TYPE_COMMA      => self::PAREN_EXPRESSION,
264                                 self::TYPE_SEMICOLON  => self::PAREN_EXPRESSION,
265                                 self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION
266                         ),
267                         self::PAREN_EXPRESSION_FUNC => array(
268                                 self::TYPE_BRACE_OPEN => self::STATEMENT
269                         ),
270                         self::PROPERTY_EXPRESSION => array(
271                                 self::TYPE_BRACE_OPEN => self::PROPERTY_ASSIGNMENT,
272                                 self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION,
273                                 self::TYPE_FUNC       => self::PROPERTY_EXPRESSION_FUNC,
274                                 self::TYPE_LITERAL    => self::PROPERTY_EXPRESSION_OP
275                         ),
276                         self::PROPERTY_EXPRESSION_OP => array(
277                                 self::TYPE_BIN_OP     => self::PROPERTY_EXPRESSION,
278                                 self::TYPE_ADD_OP     => self::PROPERTY_EXPRESSION,
279                                 self::TYPE_HOOK       => self::PROPERTY_EXPRESSION,
280                                 self::TYPE_COMMA      => self::PROPERTY_ASSIGNMENT,
281                                 self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION
282                         ),
283                         self::PROPERTY_EXPRESSION_FUNC => array(
284                                 self::TYPE_BRACE_OPEN => self::STATEMENT
285                         )
286                 );
287
288                 // $push : This table contains the rules for when to push a state onto the stack.
289                 //         The pushed state is the state to return to when the corresponding
290                 //         closing token is found
291                 $push = array(
292                         self::STATEMENT => array(
293                                 self::TYPE_BRACE_OPEN => self::STATEMENT,
294                                 self::TYPE_PAREN_OPEN => self::EXPRESSION_OP
295                         ),
296                         self::CONDITION => array(
297                                 self::TYPE_PAREN_OPEN => self::STATEMENT
298                         ),
299                         self::PROPERTY_ASSIGNMENT => array(
300                                 self::TYPE_BRACE_OPEN => self::PROPERTY_ASSIGNMENT
301                         ),
302                         self::EXPRESSION => array(
303                                 self::TYPE_BRACE_OPEN => self::EXPRESSION_OP,
304                                 self::TYPE_PAREN_OPEN => self::EXPRESSION_OP
305                         ),
306                         self::EXPRESSION_NO_NL => array(
307                                 self::TYPE_BRACE_OPEN => self::EXPRESSION_OP,
308                                 self::TYPE_PAREN_OPEN => self::EXPRESSION_OP
309                         ),
310                         self::EXPRESSION_OP => array(
311                                 self::TYPE_HOOK       => self::EXPRESSION,
312                                 self::TYPE_PAREN_OPEN => self::EXPRESSION_OP
313                         ),
314                         self::EXPRESSION_FUNC => array(
315                                 self::TYPE_BRACE_OPEN => self::EXPRESSION_OP
316                         ),
317                         self::EXPRESSION_TERNARY => array(
318                                 self::TYPE_BRACE_OPEN => self::EXPRESSION_TERNARY_OP,
319                                 self::TYPE_PAREN_OPEN => self::EXPRESSION_TERNARY_OP
320                         ),
321                         self::EXPRESSION_TERNARY_OP => array(
322                                 self::TYPE_HOOK       => self::EXPRESSION_TERNARY,
323                                 self::TYPE_PAREN_OPEN => self::EXPRESSION_TERNARY_OP
324                         ),
325                         self::EXPRESSION_TERNARY_FUNC => array(
326                                 self::TYPE_BRACE_OPEN => self::EXPRESSION_TERNARY_OP
327                         ),
328                         self::PAREN_EXPRESSION => array(
329                                 self::TYPE_BRACE_OPEN => self::PAREN_EXPRESSION_OP,
330                                 self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION_OP
331                         ),
332                         self::PAREN_EXPRESSION_OP => array(
333                                 self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION_OP
334                         ),
335                         self::PAREN_EXPRESSION_FUNC => array(
336                                 self::TYPE_BRACE_OPEN => self::PAREN_EXPRESSION_OP
337                         ),
338                         self::PROPERTY_EXPRESSION => array(
339                                 self::TYPE_BRACE_OPEN => self::PROPERTY_EXPRESSION_OP,
340                                 self::TYPE_PAREN_OPEN => self::PROPERTY_EXPRESSION_OP
341                         ),
342                         self::PROPERTY_EXPRESSION_OP => array(
343                                 self::TYPE_PAREN_OPEN => self::PROPERTY_EXPRESSION_OP
344                         ),
345                         self::PROPERTY_EXPRESSION_FUNC => array(
346                                 self::TYPE_BRACE_OPEN => self::PROPERTY_EXPRESSION_OP
347                         )
348                 );
349
350                 // $pop : Rules for when to pop a state from the stack
351                 $pop = array(
352                         self::STATEMENT              => array( self::TYPE_BRACE_CLOSE => true ),
353                         self::PROPERTY_ASSIGNMENT    => array( self::TYPE_BRACE_CLOSE => true ),
354                         self::EXPRESSION             => array( self::TYPE_BRACE_CLOSE => true ),
355                         self::EXPRESSION_NO_NL       => array( self::TYPE_BRACE_CLOSE => true ),
356                         self::EXPRESSION_OP          => array( self::TYPE_BRACE_CLOSE => true ),
357                         self::EXPRESSION_TERNARY_OP  => array( self::TYPE_COLON       => true ),
358                         self::PAREN_EXPRESSION       => array( self::TYPE_PAREN_CLOSE => true ),
359                         self::PAREN_EXPRESSION_OP    => array( self::TYPE_PAREN_CLOSE => true ),
360                         self::PROPERTY_EXPRESSION    => array( self::TYPE_BRACE_CLOSE => true ),
361                         self::PROPERTY_EXPRESSION_OP => array( self::TYPE_BRACE_CLOSE => true )
362                 );
363
364                 // $semicolon : Rules for when a semicolon insertion is appropriate
365                 $semicolon = array(
366                         self::EXPRESSION_NO_NL => array(
367                                 self::TYPE_UN_OP      => true,
368                                 self::TYPE_INCR_OP    => true,
369                                 self::TYPE_ADD_OP     => true,
370                                 self::TYPE_BRACE_OPEN => true,
371                                 self::TYPE_PAREN_OPEN => true,
372                                 self::TYPE_RETURN     => true,
373                                 self::TYPE_IF         => true,
374                                 self::TYPE_DO         => true,
375                                 self::TYPE_FUNC       => true,
376                                 self::TYPE_LITERAL    => true
377                         ),
378                         self::EXPRESSION_OP => array(
379                                 self::TYPE_UN_OP      => true,
380                                 self::TYPE_INCR_OP    => true,
381                                 self::TYPE_BRACE_OPEN => true,
382                                 self::TYPE_RETURN     => true,
383                                 self::TYPE_IF         => true,
384                                 self::TYPE_DO         => true,
385                                 self::TYPE_FUNC       => true,
386                                 self::TYPE_LITERAL    => true
387                         )
388                 );
389
390                 // Rules for when newlines should be inserted if
391                 // $statementsOnOwnLine is enabled.
392                 // $newlineBefore is checked before switching state,
393                 // $newlineAfter is checked after
394                 $newlineBefore = array(
395                         self::STATEMENT => array(
396                                 self::TYPE_BRACE_CLOSE => true,
397                         ),
398                 );
399                 $newlineAfter = array(
400                         self::STATEMENT => array(
401                                 self::TYPE_BRACE_OPEN => true,
402                                 self::TYPE_PAREN_CLOSE => true,
403                                 self::TYPE_SEMICOLON => true,
404                         ),
405                 );
406
407                 // $divStates : Contains all states that can be followed by a division operator
408                 $divStates = array(
409                         self::EXPRESSION_OP          => true,
410                         self::EXPRESSION_TERNARY_OP  => true,
411                         self::PAREN_EXPRESSION_OP    => true,
412                         self::PROPERTY_EXPRESSION_OP => true
413                 );
414
415                 // Here's where the minifying takes place: Loop through the input, looking for tokens
416                 // and output them to $out, taking actions to the above defined rules when appropriate.
417                 $out = '';
418                 $pos = 0;
419                 $length = strlen( $s );
420                 $lineLength = 0;
421                 $newlineFound = true;
422                 $state = self::STATEMENT;
423                 $stack = array();
424                 $last = ';'; // Pretend that we have seen a semicolon yet
425                 while( $pos < $length ) {
426                         // First, skip over any whitespace and multiline comments, recording whether we
427                         // found any newline character
428                         $skip = strspn( $s, " \t\n\r\xb\xc", $pos );
429                         if( !$skip ) {
430                                 $ch = $s[$pos];
431                                 if( $ch === '/' && substr( $s, $pos, 2 ) === '/*' ) {
432                                         // Multiline comment. Search for the end token or EOT.
433                                         $end = strpos( $s, '*/', $pos + 2 );
434                                         $skip = $end === false ? $length - $pos : $end - $pos + 2;
435                                 }
436                         }
437                         if( $skip ) {
438                                 // The semicolon insertion mechanism needs to know whether there was a newline
439                                 // between two tokens, so record it now.
440                                 if( !$newlineFound && strcspn( $s, "\r\n", $pos, $skip ) !== $skip ) {
441                                         $newlineFound = true;
442                                 }
443                                 $pos += $skip;
444                                 continue;
445                         }
446                         // Handle C++-style comments and html comments, which are treated as single line
447                         // comments by the browser, regardless of whether the end tag is on the same line.
448                         // Handle --> the same way, but only if it's at the beginning of the line
449                         if( ( $ch === '/' && substr( $s, $pos, 2 ) === '//' )
450                                 || ( $ch === '<' && substr( $s, $pos, 4 ) === '<!--' )
451                                 || ( $ch === '-' && $newlineFound && substr( $s, $pos, 3 ) === '-->' )
452                         ) {
453                                 $pos += strcspn( $s, "\r\n", $pos );
454                                 continue;
455                         }
456
457                         // Find out which kind of token we're handling. $end will point past the end of it.
458                         $end = $pos + 1;
459                         // Handle string literals
460                         if( $ch === "'" || $ch === '"' ) {
461                                 // Search to the end of the string literal, skipping over backslash escapes
462                                 $search = $ch . '\\';
463                                 do{
464                                         $end += strcspn( $s, $search, $end ) + 2;
465                                 } while( $end - 2 < $length && $s[$end - 2] === '\\' );
466                                 $end--;
467                         // We have to distinguish between regexp literals and division operators
468                         // A division operator is only possible in certain states
469                         } elseif( $ch === '/' && !isset( $divStates[$state] ) ) {
470                                 // Regexp literal, search to the end, skipping over backslash escapes and
471                                 // character classes
472                                 for( ; ; ) {
473                                         do{
474                                                 $end += strcspn( $s, '/[\\', $end ) + 2;
475                                         } while( $end - 2 < $length && $s[$end - 2] === '\\' );
476                                         $end--;
477                                         if( $end - 1 >= $length || $s[$end - 1] === '/' ) {
478                                                 break;
479                                         }
480                                         do{
481                                                 $end += strcspn( $s, ']\\', $end ) + 2;
482                                         } while( $end - 2 < $length && $s[$end - 2] === '\\' );
483                                         $end--;
484                                 };
485                                 // Search past the regexp modifiers (gi)
486                                 while( $end < $length && ctype_alpha( $s[$end] ) ) {
487                                         $end++;
488                                 }
489                         } elseif(
490                                 $ch === '0'
491                                 && ($pos + 1 < $length) && ($s[$pos + 1] === 'x' || $s[$pos + 1] === 'X' )
492                         ) {
493                                 // Hex numeric literal
494                                 $end++; // x or X
495                                 $len = strspn( $s, '0123456789ABCDEFabcdef', $end );
496                                 if ( !$len ) {
497                                         return self::parseError($s, $pos, 'Expected a hexadecimal number but found ' . substr( $s, $pos, 5 ) . '...' );
498                                 }
499                                 $end += $len;
500                         } elseif(
501                                 ctype_digit( $ch )
502                                 || ( $ch === '.' && $pos + 1 < $length && ctype_digit( $s[$pos + 1] ) )
503                         ) {
504                                 $end += strspn( $s, '0123456789', $end );
505                                 $decimal = strspn( $s, '.', $end );
506                                 if ($decimal) {
507                                         if ( $decimal > 2 ) {
508                                                 return self::parseError($s, $end, 'The number has too many decimal points' );
509                                         }
510                                         $end += strspn( $s, '0123456789', $end + 1 ) + $decimal;
511                                 }
512                                 $exponent = strspn( $s, 'eE', $end );
513                                 if( $exponent ) {
514                                         if ( $exponent > 1 ) {
515                                                 return self::parseError($s, $end, 'Number with several E' );
516                                         }
517                                         $end++;
518
519                                         // + sign is optional; - sign is required.
520                                         $end += strspn( $s, '-+', $end );
521                                         $len = strspn( $s, '0123456789', $end );
522                                         if ( !$len ) {
523                                                 return self::parseError($s, $pos, 'No decimal digits after e, how many zeroes should be added?' );
524                                         }
525                                         $end += $len;
526                                 }
527                         } elseif( isset( $opChars[$ch] ) ) {
528                                 // Punctuation character. Search for the longest matching operator.
529                                 while(
530                                         $end < $length
531                                         && isset( $tokenTypes[substr( $s, $pos, $end - $pos + 1 )] )
532                                 ) {
533                                         $end++;
534                                 }
535                         } else {
536                                 // Identifier or reserved word. Search for the end by excluding whitespace and
537                                 // punctuation.
538                                 $end += strcspn( $s, " \t\n.;,=<>+-{}()[]?:*/%'\"!&|^~\xb\xc\r", $end );
539                         }
540
541                         // Now get the token type from our type array
542                         $token = substr( $s, $pos, $end - $pos ); // so $end - $pos == strlen( $token )
543                         $type = isset( $tokenTypes[$token] ) ? $tokenTypes[$token] : self::TYPE_LITERAL;
544
545                         if( $newlineFound && isset( $semicolon[$state][$type] ) ) {
546                                 // This token triggers the semicolon insertion mechanism of javascript. While we
547                                 // could add the ; token here ourselves, keeping the newline has a few advantages.
548                                 $out .= "\n";
549                                 $state = self::STATEMENT;
550                                 $lineLength = 0;
551                         } elseif( $maxLineLength > 0 && $lineLength + $end - $pos > $maxLineLength &&
552                                         !isset( $semicolon[$state][$type] ) && $type !== self::TYPE_INCR_OP )
553                         {
554                                 // This line would get too long if we added $token, so add a newline first.
555                                 // Only do this if it won't trigger semicolon insertion and if it won't
556                                 // put a postfix increment operator on its own line, which is illegal in js.
557                                 $out .= "\n";
558                                 $lineLength = 0;
559                         // Check, whether we have to separate the token from the last one with whitespace
560                         } elseif( !isset( $opChars[$last] ) && !isset( $opChars[$ch] ) ) {
561                                 $out .= ' ';
562                                 $lineLength++;
563                         // Don't accidentally create ++, -- or // tokens
564                         } elseif( $last === $ch && ( $ch === '+' || $ch === '-' || $ch === '/' ) ) {
565                                 $out .= ' ';
566                                 $lineLength++;
567                         }
568                         if (
569                                 $type === self::TYPE_LITERAL
570                                 && ( $token === 'true' || $token === 'false' )
571                                 && ( $state === self::EXPRESSION || $state === self::PROPERTY_EXPRESSION )
572                                 && $last !== '.'
573                         ) {
574                                 $token = ( $token === 'true' ) ? '!0' : '!1';
575                         }
576
577                         $out .= $token;
578                         $lineLength += $end - $pos; // += strlen( $token )
579                         $last = $s[$end - 1];
580                         $pos = $end;
581                         $newlineFound = false;
582
583                         // Output a newline after the token if required
584                         // This is checked before AND after switching state
585                         $newlineAdded = false;
586                         if ( $statementsOnOwnLine && !$newlineAdded && isset( $newlineBefore[$state][$type] ) ) {
587                                 $out .= "\n";
588                                 $lineLength = 0;
589                                 $newlineAdded = true;
590                         }
591
592                         // Now that we have output our token, transition into the new state.
593                         if( isset( $push[$state][$type] ) && count( $stack ) < self::STACK_LIMIT ) {
594                                 $stack[] = $push[$state][$type];
595                         }
596                         if( $stack && isset( $pop[$state][$type] ) ) {
597                                 $state = array_pop( $stack );
598                         } elseif( isset( $goto[$state][$type] ) ) {
599                                 $state = $goto[$state][$type];
600                         }
601
602                         // Check for newline insertion again
603                         if ( $statementsOnOwnLine && !$newlineAdded && isset( $newlineAfter[$state][$type] ) ) {
604                                 $out .= "\n";
605                                 $lineLength = 0;
606                         }
607                 }
608                 return $out;
609         }
610
611         static function parseError($fullJavascript, $position, $errorMsg) {
612                 // TODO: Handle the error: trigger_error, throw exception, return false...
613                 return false;
614         }
615 }